From 9b0fbcf1d9daba7fadc4bc27e479399e507aa116 Mon Sep 17 00:00:00 2001 From: ford-jones Date: Thu, 21 Aug 2025 11:55:53 +1200 Subject: [PATCH 001/691] Enabled deletion of files created by the range-test module --- src/modules/RangeTestModule.cpp | 19 ++++++++++++++++++- src/modules/RangeTestModule.h | 5 +++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/modules/RangeTestModule.cpp b/src/modules/RangeTestModule.cpp index 6f3d69acf..2f8659db0 100644 --- a/src/modules/RangeTestModule.cpp +++ b/src/modules/RangeTestModule.cpp @@ -298,4 +298,21 @@ bool RangeTestModuleRadio::appendFile(const meshtastic_MeshPacket &mp) #endif return 1; -} \ No newline at end of file +} + +bool RangeTestModuleRadio::removeFile() +{ +#ifdef ARCH_ESP32 + char *fp = "/static/rangetest.csv"; + if (FSCom.exists(fp)) { + LOG_INFO("Deleting previous range test."); + bool result = FSCom.remove(fp); + if (!result) { + LOG_ERROR("Failed to delete rangeTest.csv"); + return 0; + } + } +#endif + + return 1; +}; \ No newline at end of file diff --git a/src/modules/RangeTestModule.h b/src/modules/RangeTestModule.h index b632d343e..0512e70a8 100644 --- a/src/modules/RangeTestModule.h +++ b/src/modules/RangeTestModule.h @@ -44,6 +44,11 @@ class RangeTestModuleRadio : public SinglePortModule */ bool appendFile(const meshtastic_MeshPacket &mp); + /** + * Cleanup range test data from filesystem + */ + bool removeFile(); + protected: /** Called to handle a particular incoming message From f6bb1977bc8ad4bc663c4a71f117ca1aea2ec156 Mon Sep 17 00:00:00 2001 From: ford-jones Date: Thu, 21 Aug 2025 12:00:19 +1200 Subject: [PATCH 002/691] Use string constants in place of char* --- src/modules/RangeTestModule.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/modules/RangeTestModule.cpp b/src/modules/RangeTestModule.cpp index 2f8659db0..38f29e93b 100644 --- a/src/modules/RangeTestModule.cpp +++ b/src/modules/RangeTestModule.cpp @@ -303,16 +303,16 @@ bool RangeTestModuleRadio::appendFile(const meshtastic_MeshPacket &mp) bool RangeTestModuleRadio::removeFile() { #ifdef ARCH_ESP32 - char *fp = "/static/rangetest.csv"; - if (FSCom.exists(fp)) { + if (FSCom.exists("/static/rangetest.csv")) { LOG_INFO("Deleting previous range test."); - bool result = FSCom.remove(fp); + bool result = FSCom.remove("/static/rangetest.csv"); if (!result) { LOG_ERROR("Failed to delete rangeTest.csv"); return 0; } + LOG_INFO("Range test removed."); } #endif return 1; -}; \ No newline at end of file +} \ No newline at end of file From e6a2df5b6d90e4c7991103a1a78e9b35713c7aee Mon Sep 17 00:00:00 2001 From: ford-jones Date: Thu, 21 Aug 2025 12:01:45 +1200 Subject: [PATCH 003/691] Check filesystem mounted --- src/modules/RangeTestModule.cpp | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/src/modules/RangeTestModule.cpp b/src/modules/RangeTestModule.cpp index 38f29e93b..415614dd2 100644 --- a/src/modules/RangeTestModule.cpp +++ b/src/modules/RangeTestModule.cpp @@ -303,15 +303,24 @@ bool RangeTestModuleRadio::appendFile(const meshtastic_MeshPacket &mp) bool RangeTestModuleRadio::removeFile() { #ifdef ARCH_ESP32 - if (FSCom.exists("/static/rangetest.csv")) { - LOG_INFO("Deleting previous range test."); - bool result = FSCom.remove("/static/rangetest.csv"); - if (!result) { - LOG_ERROR("Failed to delete rangeTest.csv"); - return 0; - } - LOG_INFO("Range test removed."); + if (!FSBegin()) { + LOG_DEBUG("An Error has occurred while mounting the filesystem"); + return 0; } + + if (!FSCom.exists("/static/rangetest.csv")) { + LOG_DEBUG("No range tests found."); + return 0; + } + + LOG_INFO("Deleting previous range test."); + bool result = FSCom.remove("/static/rangetest.csv"); + + if (!result) { + LOG_ERROR("Failed to delete range test."); + return 0; + } + LOG_INFO("Range test removed."); #endif return 1; From 236d2b92dcf40f37d357c1db802a0fec9a104aa6 Mon Sep 17 00:00:00 2001 From: ford-jones Date: Thu, 21 Aug 2025 12:12:13 +1200 Subject: [PATCH 004/691] Enable protobufs to include rangetest deletion configuration --- 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 +++++++++---- src/modules/RangeTestModule.cpp | 4 +++- 4 files changed, 14 insertions(+), 7 deletions(-) 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..468a31a59 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; } 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_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, 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 diff --git a/src/modules/RangeTestModule.cpp b/src/modules/RangeTestModule.cpp index 415614dd2..25aa3c443 100644 --- a/src/modules/RangeTestModule.cpp +++ b/src/modules/RangeTestModule.cpp @@ -144,7 +144,9 @@ ProcessMessage RangeTestModuleRadio::handleReceived(const meshtastic_MeshPacket if (moduleConfig.range_test.save) { appendFile(mp); - } + } else if (moduleConfig.range_test.clear) { + removeFile(); + }; /* NodeInfoLite *n = nodeDB->getMeshNode(getFrom(&mp)); From caf21800758e951582d29a2728e9dbad488ee756 Mon Sep 17 00:00:00 2001 From: ford-jones Date: Thu, 21 Aug 2025 19:28:52 +1200 Subject: [PATCH 005/691] If specified, Clean out range test results on module init --- src/modules/RangeTestModule.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/modules/RangeTestModule.cpp b/src/modules/RangeTestModule.cpp index 25aa3c443..c3d070602 100644 --- a/src/modules/RangeTestModule.cpp +++ b/src/modules/RangeTestModule.cpp @@ -41,12 +41,12 @@ int32_t RangeTestModule::runOnce() // moduleConfig.range_test.enabled = 1; // moduleConfig.range_test.sender = 30; // moduleConfig.range_test.save = 1; + // moduleConfig.range_test.clear = 1; // Fixed position is useful when testing indoors. // config.position.fixed_position = 1; uint32_t senderHeartbeat = moduleConfig.range_test.sender * 1000; - if (moduleConfig.range_test.enabled) { if (firstTime) { @@ -54,6 +54,11 @@ int32_t RangeTestModule::runOnce() firstTime = 0; + if (moduleConfig.range_test.clear) { + // User wants to delete previous range test(s) + LOG_INFO("Range Test Module - Clearing out previous test file"); + rangeTestModuleRadio->removeFile(); + } if (moduleConfig.range_test.sender) { LOG_INFO("Init Range Test Module -- Sender"); started = millis(); // make a note of when we started @@ -141,12 +146,9 @@ ProcessMessage RangeTestModuleRadio::handleReceived(const meshtastic_MeshPacket */ if (!isFromUs(&mp)) { - if (moduleConfig.range_test.save) { appendFile(mp); - } else if (moduleConfig.range_test.clear) { - removeFile(); - }; + } /* NodeInfoLite *n = nodeDB->getMeshNode(getFrom(&mp)); From 35d9e68053eac668c017eb4fc33d11999a5cef2b Mon Sep 17 00:00:00 2001 From: ford-jones Date: Thu, 21 Aug 2025 11:55:53 +1200 Subject: [PATCH 006/691] Enabled deletion of files created by the range-test module --- src/modules/RangeTestModule.cpp | 19 ++++++++++++++++++- src/modules/RangeTestModule.h | 5 +++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/modules/RangeTestModule.cpp b/src/modules/RangeTestModule.cpp index 6f3d69acf..2f8659db0 100644 --- a/src/modules/RangeTestModule.cpp +++ b/src/modules/RangeTestModule.cpp @@ -298,4 +298,21 @@ bool RangeTestModuleRadio::appendFile(const meshtastic_MeshPacket &mp) #endif return 1; -} \ No newline at end of file +} + +bool RangeTestModuleRadio::removeFile() +{ +#ifdef ARCH_ESP32 + char *fp = "/static/rangetest.csv"; + if (FSCom.exists(fp)) { + LOG_INFO("Deleting previous range test."); + bool result = FSCom.remove(fp); + if (!result) { + LOG_ERROR("Failed to delete rangeTest.csv"); + return 0; + } + } +#endif + + return 1; +}; \ No newline at end of file diff --git a/src/modules/RangeTestModule.h b/src/modules/RangeTestModule.h index b632d343e..0512e70a8 100644 --- a/src/modules/RangeTestModule.h +++ b/src/modules/RangeTestModule.h @@ -44,6 +44,11 @@ class RangeTestModuleRadio : public SinglePortModule */ bool appendFile(const meshtastic_MeshPacket &mp); + /** + * Cleanup range test data from filesystem + */ + bool removeFile(); + protected: /** Called to handle a particular incoming message From 7b24d3163610dda9f3c4a75294553370df9936f4 Mon Sep 17 00:00:00 2001 From: ford-jones Date: Thu, 21 Aug 2025 12:00:19 +1200 Subject: [PATCH 007/691] Use string constants in place of char* --- src/modules/RangeTestModule.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/modules/RangeTestModule.cpp b/src/modules/RangeTestModule.cpp index 2f8659db0..38f29e93b 100644 --- a/src/modules/RangeTestModule.cpp +++ b/src/modules/RangeTestModule.cpp @@ -303,16 +303,16 @@ bool RangeTestModuleRadio::appendFile(const meshtastic_MeshPacket &mp) bool RangeTestModuleRadio::removeFile() { #ifdef ARCH_ESP32 - char *fp = "/static/rangetest.csv"; - if (FSCom.exists(fp)) { + if (FSCom.exists("/static/rangetest.csv")) { LOG_INFO("Deleting previous range test."); - bool result = FSCom.remove(fp); + bool result = FSCom.remove("/static/rangetest.csv"); if (!result) { LOG_ERROR("Failed to delete rangeTest.csv"); return 0; } + LOG_INFO("Range test removed."); } #endif return 1; -}; \ No newline at end of file +} \ No newline at end of file From 8e32d5807748ece03cd0397370a73e1071d67bb8 Mon Sep 17 00:00:00 2001 From: ford-jones Date: Thu, 21 Aug 2025 12:01:45 +1200 Subject: [PATCH 008/691] Check filesystem mounted --- src/modules/RangeTestModule.cpp | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/src/modules/RangeTestModule.cpp b/src/modules/RangeTestModule.cpp index 38f29e93b..415614dd2 100644 --- a/src/modules/RangeTestModule.cpp +++ b/src/modules/RangeTestModule.cpp @@ -303,15 +303,24 @@ bool RangeTestModuleRadio::appendFile(const meshtastic_MeshPacket &mp) bool RangeTestModuleRadio::removeFile() { #ifdef ARCH_ESP32 - if (FSCom.exists("/static/rangetest.csv")) { - LOG_INFO("Deleting previous range test."); - bool result = FSCom.remove("/static/rangetest.csv"); - if (!result) { - LOG_ERROR("Failed to delete rangeTest.csv"); - return 0; - } - LOG_INFO("Range test removed."); + if (!FSBegin()) { + LOG_DEBUG("An Error has occurred while mounting the filesystem"); + return 0; } + + if (!FSCom.exists("/static/rangetest.csv")) { + LOG_DEBUG("No range tests found."); + return 0; + } + + LOG_INFO("Deleting previous range test."); + bool result = FSCom.remove("/static/rangetest.csv"); + + if (!result) { + LOG_ERROR("Failed to delete range test."); + return 0; + } + LOG_INFO("Range test removed."); #endif return 1; From 9d560fe9e1f9fe2a39437ccfea079a051430cfc3 Mon Sep 17 00:00:00 2001 From: ford-jones Date: Thu, 21 Aug 2025 12:12:13 +1200 Subject: [PATCH 009/691] Enable protobufs to include rangetest deletion configuration --- 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 +++++++++---- src/modules/RangeTestModule.cpp | 4 +++- 4 files changed, 14 insertions(+), 7 deletions(-) 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..468a31a59 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; } 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_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, 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 diff --git a/src/modules/RangeTestModule.cpp b/src/modules/RangeTestModule.cpp index 415614dd2..25aa3c443 100644 --- a/src/modules/RangeTestModule.cpp +++ b/src/modules/RangeTestModule.cpp @@ -144,7 +144,9 @@ ProcessMessage RangeTestModuleRadio::handleReceived(const meshtastic_MeshPacket if (moduleConfig.range_test.save) { appendFile(mp); - } + } else if (moduleConfig.range_test.clear) { + removeFile(); + }; /* NodeInfoLite *n = nodeDB->getMeshNode(getFrom(&mp)); From 4dfcd61d461870b5a2be73b8a3980c1b181f40f9 Mon Sep 17 00:00:00 2001 From: ford-jones Date: Thu, 21 Aug 2025 19:28:52 +1200 Subject: [PATCH 010/691] If specified, Clean out range test results on module init --- src/modules/RangeTestModule.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/modules/RangeTestModule.cpp b/src/modules/RangeTestModule.cpp index 25aa3c443..c3d070602 100644 --- a/src/modules/RangeTestModule.cpp +++ b/src/modules/RangeTestModule.cpp @@ -41,12 +41,12 @@ int32_t RangeTestModule::runOnce() // moduleConfig.range_test.enabled = 1; // moduleConfig.range_test.sender = 30; // moduleConfig.range_test.save = 1; + // moduleConfig.range_test.clear = 1; // Fixed position is useful when testing indoors. // config.position.fixed_position = 1; uint32_t senderHeartbeat = moduleConfig.range_test.sender * 1000; - if (moduleConfig.range_test.enabled) { if (firstTime) { @@ -54,6 +54,11 @@ int32_t RangeTestModule::runOnce() firstTime = 0; + if (moduleConfig.range_test.clear) { + // User wants to delete previous range test(s) + LOG_INFO("Range Test Module - Clearing out previous test file"); + rangeTestModuleRadio->removeFile(); + } if (moduleConfig.range_test.sender) { LOG_INFO("Init Range Test Module -- Sender"); started = millis(); // make a note of when we started @@ -141,12 +146,9 @@ ProcessMessage RangeTestModuleRadio::handleReceived(const meshtastic_MeshPacket */ if (!isFromUs(&mp)) { - if (moduleConfig.range_test.save) { appendFile(mp); - } else if (moduleConfig.range_test.clear) { - removeFile(); - }; + } /* NodeInfoLite *n = nodeDB->getMeshNode(getFrom(&mp)); From a7f63d5783fe9b84a534191903d77f1e3a93a59e Mon Sep 17 00:00:00 2001 From: Dane Evans Date: Fri, 1 Aug 2025 17:04:24 +1000 Subject: [PATCH 011/691] add merge queue --- .github/workflows/merge_queue.yml | 508 ++++++++++++++++++++++++++++++ 1 file changed, 508 insertions(+) create mode 100644 .github/workflows/merge_queue.yml diff --git a/.github/workflows/merge_queue.yml b/.github/workflows/merge_queue.yml new file mode 100644 index 000000000..9e34727ad --- /dev/null +++ b/.github/workflows/merge_queue.yml @@ -0,0 +1,508 @@ + +name: Merge Queue +concurrency: + group: merge-queue-${{ github.head_ref || github.run_id }} + cancel-in-progress: true +on: + # Merge group is a special trigger that is used to trigger the workflow when a merge group is created. + merge_group: + +env: + FAIL_FAST_PER_ARCH: true + + +jobs: + setup: + strategy: + fail-fast: true + matrix: + arch: + - esp32 + - esp32s3 + - esp32c3 + - esp32c6 + - nrf52840 + - rp2040 + - rp2350 + - stm32 + - check + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: 3.x + cache: pip + - run: pip install -U platformio + - name: Generate matrix + id: jsonStep + run: | + if [[ "$GITHUB_HEAD_REF" == "" ]]; then + TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}}) + else + TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}} pr) + fi + echo "Name: $GITHUB_REF_NAME Base: $GITHUB_BASE_REF Ref: $GITHUB_REF Targets: $TARGETS" + echo "${{matrix.arch}}=$(jq -cn --argjson environments "$TARGETS" '{board: $environments}')" >> $GITHUB_OUTPUT + outputs: + esp32: ${{ steps.jsonStep.outputs.esp32 }} + esp32s3: ${{ steps.jsonStep.outputs.esp32s3 }} + esp32c3: ${{ steps.jsonStep.outputs.esp32c3 }} + esp32c6: ${{ steps.jsonStep.outputs.esp32c6 }} + nrf52840: ${{ steps.jsonStep.outputs.nrf52840 }} + rp2040: ${{ steps.jsonStep.outputs.rp2040 }} + rp2350: ${{ steps.jsonStep.outputs.rp2350 }} + stm32: ${{ steps.jsonStep.outputs.stm32 }} + check: ${{ steps.jsonStep.outputs.check }} + + version: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Get release version string + run: | + echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT + echo "deb=$(./bin/buildinfo.py deb)" >> $GITHUB_OUTPUT + id: version + env: + BUILD_LOCATION: local + outputs: + long: ${{ steps.version.outputs.long }} + deb: ${{ steps.version.outputs.deb }} + + check: + needs: setup + strategy: + fail-fast: true + matrix: ${{ fromJson(needs.setup.outputs.check) }} + + runs-on: ubuntu-latest + if: ${{ github.event_name != 'workflow_dispatch' }} + steps: + - uses: actions/checkout@v4 + - name: Build base + id: base + uses: ./.github/actions/setup-base + - name: Check ${{ matrix.board }} + run: bin/check-all.sh ${{ matrix.board }} + + build-esp32: + needs: [setup, version] + strategy: + fail-fast: ${{ env.FAIL_FAST_PER_ARCH }} + matrix: ${{ fromJson(needs.setup.outputs.esp32) }} + uses: ./.github/workflows/build_firmware.yml + with: + version: ${{ needs.version.outputs.long }} + pio_env: ${{ matrix.board }} + platform: esp32 + + build-esp32s3: + needs: [setup, version] + strategy: + fail-fast: ${{ env.FAIL_FAST_PER_ARCH }} + matrix: ${{ fromJson(needs.setup.outputs.esp32s3) }} + uses: ./.github/workflows/build_firmware.yml + with: + version: ${{ needs.version.outputs.long }} + pio_env: ${{ matrix.board }} + platform: esp32s3 + + build-esp32c3: + needs: [setup, version] + strategy: + fail-fast: ${{ env.FAIL_FAST_PER_ARCH }} + matrix: ${{ fromJson(needs.setup.outputs.esp32c3) }} + uses: ./.github/workflows/build_firmware.yml + with: + version: ${{ needs.version.outputs.long }} + pio_env: ${{ matrix.board }} + platform: esp32c3 + + build-esp32c6: + needs: [setup, version] + strategy: + fail-fast: ${{ env.FAIL_FAST_PER_ARCH }} + matrix: ${{ fromJson(needs.setup.outputs.esp32c6) }} + uses: ./.github/workflows/build_firmware.yml + with: + version: ${{ needs.version.outputs.long }} + pio_env: ${{ matrix.board }} + platform: esp32c6 + + build-nrf52840: + needs: [setup, version] + strategy: + fail-fast: ${{ env.FAIL_FAST_PER_ARCH }} + matrix: ${{ fromJson(needs.setup.outputs.nrf52840) }} + uses: ./.github/workflows/build_firmware.yml + with: + version: ${{ needs.version.outputs.long }} + pio_env: ${{ matrix.board }} + platform: nrf52840 + + build-rp2040: + needs: [setup, version] + strategy: + fail-fast: ${{ env.FAIL_FAST_PER_ARCH }} + matrix: ${{ fromJson(needs.setup.outputs.rp2040) }} + uses: ./.github/workflows/build_firmware.yml + with: + version: ${{ needs.version.outputs.long }} + pio_env: ${{ matrix.board }} + platform: rp2040 + + build-rp2350: + needs: [setup, version] + strategy: + fail-fast: ${{ env.FAIL_FAST_PER_ARCH }} + matrix: ${{ fromJson(needs.setup.outputs.rp2350) }} + uses: ./.github/workflows/build_firmware.yml + with: + version: ${{ needs.version.outputs.long }} + pio_env: ${{ matrix.board }} + platform: rp2350 + + build-stm32: + needs: [setup, version] + strategy: + fail-fast: ${{ env.FAIL_FAST_PER_ARCH }} + matrix: ${{ fromJson(needs.setup.outputs.stm32) }} + uses: ./.github/workflows/build_firmware.yml + with: + version: ${{ needs.version.outputs.long }} + pio_env: ${{ matrix.board }} + platform: stm32 + + build-debian-src: + if: github.repository == 'meshtastic/firmware' + uses: ./.github/workflows/build_debian_src.yml + with: + series: UNRELEASED + build_location: local + secrets: inherit + + package-pio-deps-native-tft: + if: ${{ github.event_name == 'workflow_dispatch' }} + uses: ./.github/workflows/package_pio_deps.yml + with: + pio_env: native-tft + secrets: inherit + + test-native: + if: ${{ !contains(github.ref_name, 'event/') }} + uses: ./.github/workflows/test_native.yml + + docker-deb-amd64: + uses: ./.github/workflows/docker_build.yml + with: + distro: debian + platform: linux/amd64 + runs-on: ubuntu-24.04 + push: false + + docker-deb-amd64-tft: + uses: ./.github/workflows/docker_build.yml + with: + distro: debian + platform: linux/amd64 + runs-on: ubuntu-24.04 + push: false + pio_env: native-tft + + docker-alp-amd64: + uses: ./.github/workflows/docker_build.yml + with: + distro: alpine + platform: linux/amd64 + runs-on: ubuntu-24.04 + push: false + + docker-alp-amd64-tft: + uses: ./.github/workflows/docker_build.yml + with: + distro: alpine + platform: linux/amd64 + runs-on: ubuntu-24.04 + push: false + pio_env: native-tft + + docker-deb-arm64: + uses: ./.github/workflows/docker_build.yml + with: + distro: debian + platform: linux/arm64 + runs-on: ubuntu-24.04-arm + push: false + + docker-deb-armv7: + uses: ./.github/workflows/docker_build.yml + with: + distro: debian + platform: linux/arm/v7 + runs-on: ubuntu-24.04-arm + push: false + + gather-artifacts: + permissions: + contents: write + pull-requests: write + strategy: + fail-fast: false + matrix: + arch: + - esp32 + - esp32s3 + - esp32c3 + - esp32c6 + - nrf52840 + - rp2040 + - rp2350 + - stm32 + runs-on: ubuntu-latest + needs: + [ + version, + build-esp32, + build-esp32s3, + build-esp32c3, + build-esp32c6, + build-nrf52840, + build-rp2040, + build-rp2350, + build-stm32, + ] + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + ref: ${{github.event.pull_request.head.ref}} + repository: ${{github.event.pull_request.head.repo.full_name}} + + - uses: actions/download-artifact@v4 + with: + path: ./ + pattern: firmware-${{matrix.arch}}-* + merge-multiple: true + + - name: Display structure of downloaded files + run: ls -R + + - name: Move files up + run: mv -b -t ./ ./bin/device-*.sh ./bin/device-*.bat + + - name: Repackage in single firmware zip + uses: actions/upload-artifact@v4 + with: + name: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }} + overwrite: true + path: | + ./firmware-*.bin + ./firmware-*.uf2 + ./firmware-*.hex + ./firmware-*-ota.zip + ./device-*.sh + ./device-*.bat + ./littlefs-*.bin + ./bleota*bin + ./Meshtastic_nRF52_factory_erase*.uf2 + retention-days: 30 + + - uses: actions/download-artifact@v4 + with: + name: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }} + merge-multiple: true + path: ./output + + # For diagnostics + - name: Show artifacts + run: ls -lR + + - name: Device scripts permissions + run: | + chmod +x ./output/device-install.sh + chmod +x ./output/device-update.sh + + - name: Zip firmware + run: zip -j -9 -r ./firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip ./output + + - name: Repackage in single elfs zip + uses: actions/upload-artifact@v4 + with: + name: debug-elfs-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip + overwrite: true + path: ./*.elf + retention-days: 30 + + - uses: scruplelesswizard/comment-artifact@main + if: ${{ github.event_name == 'pull_request' }} + with: + name: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }} + description: "Download firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip. This artifact will be available for 90 days from creation" + github-token: ${{ secrets.GITHUB_TOKEN }} + + release-artifacts: + runs-on: ubuntu-latest + if: ${{ github.event_name == 'workflow_dispatch' }} + outputs: + upload_url: ${{ steps.create_release.outputs.upload_url }} + needs: + - version + - gather-artifacts + - build-debian-src + - package-pio-deps-native-tft + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: 3.x + + - name: Create release + uses: softprops/action-gh-release@v2 + id: create_release + with: + draft: true + prerelease: true + name: Meshtastic Firmware ${{ needs.version.outputs.long }} Alpha + tag_name: v${{ needs.version.outputs.long }} + body: | + Autogenerated by github action, developer should edit as required before publishing... + + - name: Download source deb + uses: actions/download-artifact@v4 + with: + pattern: firmware-debian-${{ needs.version.outputs.deb }}~UNRELEASED-src + merge-multiple: true + path: ./output/debian-src + + - name: Download `native-tft` pio deps + uses: actions/download-artifact@v4 + with: + pattern: platformio-deps-native-tft-${{ needs.version.outputs.long }} + merge-multiple: true + path: ./output/pio-deps-native-tft + + - name: Zip Linux sources + working-directory: output + run: | + zip -j -9 -r ./meshtasticd-${{ needs.version.outputs.deb }}-src.zip ./debian-src + zip -9 -r ./platformio-deps-native-tft-${{ needs.version.outputs.long }}.zip ./pio-deps-native-tft + + # For diagnostics + - name: Display structure of downloaded files + run: ls -lR + + - name: Add Linux sources to GtiHub Release + # Only run when targeting master branch with workflow_dispatch + if: ${{ github.ref_name == 'master' }} + run: | + gh release upload v${{ needs.version.outputs.long }} ./output/meshtasticd-${{ needs.version.outputs.deb }}-src.zip + gh release upload v${{ needs.version.outputs.long }} ./output/platformio-deps-native-tft-${{ needs.version.outputs.long }}.zip + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + release-firmware: + strategy: + fail-fast: false + matrix: + arch: + - esp32 + - esp32s3 + - esp32c3 + - esp32c6 + - nrf52840 + - rp2040 + - rp2350 + - stm32 + runs-on: ubuntu-latest + if: ${{ github.event_name == 'workflow_dispatch' }} + needs: [release-artifacts, version] + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: 3.x + + - uses: actions/download-artifact@v4 + with: + pattern: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }} + merge-multiple: true + path: ./output + + - name: Display structure of downloaded files + run: ls -lR + + - name: Device scripts permissions + run: | + chmod +x ./output/device-install.sh + chmod +x ./output/device-update.sh + + - name: Zip firmware + run: zip -j -9 -r ./firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip ./output + + - uses: actions/download-artifact@v4 + with: + name: debug-elfs-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip + merge-multiple: true + path: ./elfs + + - name: Zip debug elfs + run: zip -j -9 -r ./debug-elfs-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip ./elfs + + # For diagnostics + - name: Display structure of downloaded files + run: ls -lR + + - name: Add bins and debug elfs to GitHub Release + # Only run when targeting master branch with workflow_dispatch + if: ${{ github.ref_name == 'master' }} + run: | + gh release upload v${{ needs.version.outputs.long }} ./firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip + gh release upload v${{ needs.version.outputs.long }} ./debug-elfs-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + publish-firmware: + runs-on: ubuntu-24.04 + if: ${{ github.event_name == 'workflow_dispatch' }} + needs: [release-firmware, version] + env: + targets: |- + esp32,esp32s3,esp32c3,esp32c6,nrf52840,rp2040,rp2350,stm32 + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: 3.x + + - uses: actions/download-artifact@v4 + with: + pattern: firmware-{${{ env.targets }}}-${{ needs.version.outputs.long }} + merge-multiple: true + path: ./publish + + - name: Publish firmware to meshtastic.github.io + uses: peaceiris/actions-gh-pages@v4 + env: + # On event/* branches, use the event name as the destination prefix + DEST_PREFIX: ${{ contains(github.ref_name, 'event/') && format('{0}/', github.ref_name) || '' }} + with: + deploy_key: ${{ secrets.DIST_PAGES_DEPLOY_KEY }} + external_repository: meshtastic/meshtastic.github.io + publish_branch: master + publish_dir: ./publish + destination_dir: ${{ env.DEST_PREFIX }}firmware-${{ needs.version.outputs.long }} + keep_files: true + user_name: github-actions[bot] + user_email: github-actions[bot]@users.noreply.github.com + commit_message: ${{ needs.version.outputs.long }} + enable_jekyll: true From e39b56547e1721dd53c471fc51e145542302a3f0 Mon Sep 17 00:00:00 2001 From: Dane Evans Date: Fri, 1 Aug 2025 17:08:03 +1000 Subject: [PATCH 012/691] try vars --- .github/workflows/merge_queue.yml | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/.github/workflows/merge_queue.yml b/.github/workflows/merge_queue.yml index 9e34727ad..e24d7e906 100644 --- a/.github/workflows/merge_queue.yml +++ b/.github/workflows/merge_queue.yml @@ -1,8 +1,9 @@ name: Merge Queue -concurrency: - group: merge-queue-${{ github.head_ref || github.run_id }} - cancel-in-progress: true +# Not sure how concu +# concurrency: +# group: merge-queue-${{ github.head_ref || github.run_id }} +# cancel-in-progress: true on: # Merge group is a special trigger that is used to trigger the workflow when a merge group is created. merge_group: @@ -89,7 +90,7 @@ jobs: build-esp32: needs: [setup, version] strategy: - fail-fast: ${{ env.FAIL_FAST_PER_ARCH }} + fail-fast: ${{ vars.FAIL_FAST_PER_ARCH }} matrix: ${{ fromJson(needs.setup.outputs.esp32) }} uses: ./.github/workflows/build_firmware.yml with: @@ -100,7 +101,7 @@ jobs: build-esp32s3: needs: [setup, version] strategy: - fail-fast: ${{ env.FAIL_FAST_PER_ARCH }} + fail-fast: ${{ vars.FAIL_FAST_PER_ARCH }} matrix: ${{ fromJson(needs.setup.outputs.esp32s3) }} uses: ./.github/workflows/build_firmware.yml with: @@ -111,7 +112,7 @@ jobs: build-esp32c3: needs: [setup, version] strategy: - fail-fast: ${{ env.FAIL_FAST_PER_ARCH }} + fail-fast: ${{ vars.FAIL_FAST_PER_ARCH }} matrix: ${{ fromJson(needs.setup.outputs.esp32c3) }} uses: ./.github/workflows/build_firmware.yml with: @@ -122,7 +123,7 @@ jobs: build-esp32c6: needs: [setup, version] strategy: - fail-fast: ${{ env.FAIL_FAST_PER_ARCH }} + fail-fast: ${{ vars.FAIL_FAST_PER_ARCH }} matrix: ${{ fromJson(needs.setup.outputs.esp32c6) }} uses: ./.github/workflows/build_firmware.yml with: @@ -133,7 +134,7 @@ jobs: build-nrf52840: needs: [setup, version] strategy: - fail-fast: ${{ env.FAIL_FAST_PER_ARCH }} + fail-fast: ${{ vars.FAIL_FAST_PER_ARCH }} matrix: ${{ fromJson(needs.setup.outputs.nrf52840) }} uses: ./.github/workflows/build_firmware.yml with: @@ -144,7 +145,7 @@ jobs: build-rp2040: needs: [setup, version] strategy: - fail-fast: ${{ env.FAIL_FAST_PER_ARCH }} + fail-fast: ${{ vars.FAIL_FAST_PER_ARCH }} matrix: ${{ fromJson(needs.setup.outputs.rp2040) }} uses: ./.github/workflows/build_firmware.yml with: @@ -155,7 +156,7 @@ jobs: build-rp2350: needs: [setup, version] strategy: - fail-fast: ${{ env.FAIL_FAST_PER_ARCH }} + fail-fast: ${{ vars.FAIL_FAST_PER_ARCH }} matrix: ${{ fromJson(needs.setup.outputs.rp2350) }} uses: ./.github/workflows/build_firmware.yml with: @@ -166,7 +167,7 @@ jobs: build-stm32: needs: [setup, version] strategy: - fail-fast: ${{ env.FAIL_FAST_PER_ARCH }} + fail-fast: ${{ vars.FAIL_FAST_PER_ARCH }} matrix: ${{ fromJson(needs.setup.outputs.stm32) }} uses: ./.github/workflows/build_firmware.yml with: From 40d728a14b98b75bfa2666e2210fba5c43260b68 Mon Sep 17 00:00:00 2001 From: Dane Evans Date: Fri, 1 Aug 2025 17:11:13 +1000 Subject: [PATCH 013/691] kerning in yaml. --- .github/workflows/merge_queue.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/merge_queue.yml b/.github/workflows/merge_queue.yml index e24d7e906..66eaeac6a 100644 --- a/.github/workflows/merge_queue.yml +++ b/.github/workflows/merge_queue.yml @@ -167,7 +167,7 @@ jobs: build-stm32: needs: [setup, version] strategy: - fail-fast: ${{ vars.FAIL_FAST_PER_ARCH }} + fail-fast: ${{ vars.FAIL_FAST_PER_ARCH }} matrix: ${{ fromJson(needs.setup.outputs.stm32) }} uses: ./.github/workflows/build_firmware.yml with: From ea1d968777fe30cbba4a888e5cb34cd96fffd32c Mon Sep 17 00:00:00 2001 From: Dane Evans Date: Fri, 1 Aug 2025 17:51:45 +1000 Subject: [PATCH 014/691] update comment --- .github/workflows/merge_queue.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/merge_queue.yml b/.github/workflows/merge_queue.yml index 66eaeac6a..1863582fc 100644 --- a/.github/workflows/merge_queue.yml +++ b/.github/workflows/merge_queue.yml @@ -1,6 +1,6 @@ name: Merge Queue -# Not sure how concu +# Not sure how concurrency works in merge_queue, removing for now. # concurrency: # group: merge-queue-${{ github.head_ref || github.run_id }} # cancel-in-progress: true From 590db89643cf48aa5a60102e6990235ce277ad13 Mon Sep 17 00:00:00 2001 From: Dane Evans Date: Fri, 1 Aug 2025 18:06:44 +1000 Subject: [PATCH 015/691] lint etc --- .github/workflows/merge_queue.yml | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/.github/workflows/merge_queue.yml b/.github/workflows/merge_queue.yml index 1863582fc..d3df35ac2 100644 --- a/.github/workflows/merge_queue.yml +++ b/.github/workflows/merge_queue.yml @@ -1,4 +1,3 @@ - name: Merge Queue # Not sure how concurrency works in merge_queue, removing for now. # concurrency: @@ -11,7 +10,6 @@ on: env: FAIL_FAST_PER_ARCH: true - jobs: setup: strategy: @@ -90,7 +88,7 @@ jobs: build-esp32: needs: [setup, version] strategy: - fail-fast: ${{ vars.FAIL_FAST_PER_ARCH }} + fail-fast: ${{ vars.FAIL_FAST_PER_ARCH == true }} matrix: ${{ fromJson(needs.setup.outputs.esp32) }} uses: ./.github/workflows/build_firmware.yml with: @@ -101,7 +99,7 @@ jobs: build-esp32s3: needs: [setup, version] strategy: - fail-fast: ${{ vars.FAIL_FAST_PER_ARCH }} + fail-fast: ${{ vars.FAIL_FAST_PER_ARCH == true }} matrix: ${{ fromJson(needs.setup.outputs.esp32s3) }} uses: ./.github/workflows/build_firmware.yml with: @@ -112,7 +110,7 @@ jobs: build-esp32c3: needs: [setup, version] strategy: - fail-fast: ${{ vars.FAIL_FAST_PER_ARCH }} + fail-fast: ${{ vars.FAIL_FAST_PER_ARCH == true }} matrix: ${{ fromJson(needs.setup.outputs.esp32c3) }} uses: ./.github/workflows/build_firmware.yml with: @@ -123,7 +121,7 @@ jobs: build-esp32c6: needs: [setup, version] strategy: - fail-fast: ${{ vars.FAIL_FAST_PER_ARCH }} + fail-fast: ${{ vars.FAIL_FAST_PER_ARCH == true }} matrix: ${{ fromJson(needs.setup.outputs.esp32c6) }} uses: ./.github/workflows/build_firmware.yml with: @@ -134,7 +132,7 @@ jobs: build-nrf52840: needs: [setup, version] strategy: - fail-fast: ${{ vars.FAIL_FAST_PER_ARCH }} + fail-fast: ${{ vars.FAIL_FAST_PER_ARCH == true }} matrix: ${{ fromJson(needs.setup.outputs.nrf52840) }} uses: ./.github/workflows/build_firmware.yml with: @@ -145,7 +143,7 @@ jobs: build-rp2040: needs: [setup, version] strategy: - fail-fast: ${{ vars.FAIL_FAST_PER_ARCH }} + fail-fast: ${{ vars.FAIL_FAST_PER_ARCH == true }} matrix: ${{ fromJson(needs.setup.outputs.rp2040) }} uses: ./.github/workflows/build_firmware.yml with: @@ -156,7 +154,7 @@ jobs: build-rp2350: needs: [setup, version] strategy: - fail-fast: ${{ vars.FAIL_FAST_PER_ARCH }} + fail-fast: ${{ vars.FAIL_FAST_PER_ARCH == true }} matrix: ${{ fromJson(needs.setup.outputs.rp2350) }} uses: ./.github/workflows/build_firmware.yml with: @@ -167,7 +165,7 @@ jobs: build-stm32: needs: [setup, version] strategy: - fail-fast: ${{ vars.FAIL_FAST_PER_ARCH }} + fail-fast: ${{ vars.FAIL_FAST_PER_ARCH == true }} matrix: ${{ fromJson(needs.setup.outputs.stm32) }} uses: ./.github/workflows/build_firmware.yml with: From 8791cd7851c467dc48d43860e3a31de9d2942f86 Mon Sep 17 00:00:00 2001 From: Dane Evans Date: Fri, 1 Aug 2025 18:11:57 +1000 Subject: [PATCH 016/691] touching to check grandfathering --- .github/workflows/main_matrix.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml index ed14907dc..0a72bdc3e 100644 --- a/.github/workflows/main_matrix.yml +++ b/.github/workflows/main_matrix.yml @@ -3,7 +3,7 @@ concurrency: group: ci-${{ github.head_ref || github.run_id }} cancel-in-progress: true on: - # # Triggers the workflow on push but only for the master branch + # # Triggers the workflow on push but only for the main branches push: branches: - master From 18d005d7e6805b95f50eebf02a43fa289fc795c8 Mon Sep 17 00:00:00 2001 From: Dane Evans Date: Fri, 1 Aug 2025 18:17:34 +1000 Subject: [PATCH 017/691] explicit ignores --- .github/workflows/main_matrix.yml | 1 + .github/workflows/merge_queue.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml index 0a72bdc3e..815b03c86 100644 --- a/.github/workflows/main_matrix.yml +++ b/.github/workflows/main_matrix.yml @@ -258,6 +258,7 @@ jobs: push: false gather-artifacts: + # trunk-ignore(checkov/CKV2_GHA_1) permissions: contents: write pull-requests: write diff --git a/.github/workflows/merge_queue.yml b/.github/workflows/merge_queue.yml index d3df35ac2..e2264e250 100644 --- a/.github/workflows/merge_queue.yml +++ b/.github/workflows/merge_queue.yml @@ -243,6 +243,7 @@ jobs: push: false gather-artifacts: + # trunk-ignore(checkov/CKV2_GHA_1) permissions: contents: write pull-requests: write From 1a279c6053f485fdfb606145767124b208fb20a4 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 26 Aug 2025 06:31:38 -0500 Subject: [PATCH 018/691] Upgrade trunk (#7677) Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com> --- .trunk/trunk.yaml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml index de38e3ec0..a0dcf2ff5 100644 --- a/.trunk/trunk.yaml +++ b/.trunk/trunk.yaml @@ -4,19 +4,19 @@ cli: plugins: sources: - id: trunk - ref: v1.7.1 + ref: v1.7.2 uri: https://github.com/trunk-io/plugins lint: enabled: - - checkov@3.2.461 - - renovate@41.74.0 + - checkov@3.2.465 + - renovate@41.82.10 - prettier@3.6.2 - trufflehog@3.90.5 - yamllint@1.37.1 - bandit@1.8.6 - - trivy@0.64.1 - - taplo@0.9.3 - - ruff@0.12.7 + - trivy@0.65.0 + - taplo@0.10.0 + - ruff@0.12.10 - isort@6.0.1 - markdownlint@0.45.0 - oxipng@9.1.5 @@ -25,7 +25,7 @@ lint: - flake8@7.3.0 - hadolint@2.12.1-beta - shfmt@3.6.0 - - shellcheck@0.10.0 + - shellcheck@0.11.0 - black@25.1.0 - git-diff-check - gitleaks@8.28.0 From 3f5c30e3b35c5a8c779ff3542c52cfe7b107c871 Mon Sep 17 00:00:00 2001 From: Manuel <71137295+mverch67@users.noreply.github.com> Date: Tue, 26 Aug 2025 16:35:25 +0200 Subject: [PATCH 019/691] T-Lora Pager (#7613) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * initial commit * preset rotary1 encoder * define TAB+ESC * haptic feedback * allow switch off haptic feedback * enable audio amplifier * include PR4684 * fix for tft target * add ES8311 audio codec * fix KB scan duplicate * display workaround to avoid debris * fix debris on display * keyboard backlight * enable screen options * fsm based bounce-free rotary encoder implementation * use fsm RotaryEncoder only for T-Lora Pager * change inputbroker default config to allow using rotary wheel for screens AND menues --------- Co-authored-by: Thomas Göttgens Co-authored-by: Ben Meadors --- src/detect/ScanI2C.h | 3 +- src/detect/ScanI2CTwoWire.cpp | 10 +- src/graphics/Screen.cpp | 4 +- src/graphics/ScreenFonts.h | 2 +- src/graphics/TFTDisplay.cpp | 199 +++++++++++++-- src/graphics/TFTDisplay.h | 2 + src/graphics/draw/DebugRenderer.cpp | 8 +- src/graphics/draw/MenuHandler.cpp | 10 +- src/graphics/draw/UIRenderer.cpp | 2 +- src/graphics/images.h | 3 +- src/input/RotaryEncoderImpl.cpp | 73 ++++++ src/input/RotaryEncoderImpl.h | 28 +++ src/input/TLoraPagerKeyboard.cpp | 230 ++++++++++++++++++ src/input/TLoraPagerKeyboard.h | 23 +- src/input/cardKbI2cImpl.cpp | 4 +- src/main.cpp | 37 ++- src/main.h | 2 +- src/mesh/NodeDB.cpp | 11 +- src/modules/Modules.cpp | 10 + src/platform/esp32/architecture.h | 2 + .../extra_variants/t_lora_pager/variant.cpp | 27 ++ variants/esp32s3/tlora-pager/pins_arduino.h | 19 ++ variants/esp32s3/tlora-pager/platformio.ini | 70 ++++++ variants/esp32s3/tlora-pager/variant.h | 125 ++++++++++ 24 files changed, 855 insertions(+), 49 deletions(-) create mode 100644 src/input/RotaryEncoderImpl.cpp create mode 100644 src/input/RotaryEncoderImpl.h create mode 100644 src/input/TLoraPagerKeyboard.cpp create mode 100644 src/platform/extra_variants/t_lora_pager/variant.cpp create mode 100644 variants/esp32s3/tlora-pager/pins_arduino.h create mode 100644 variants/esp32s3/tlora-pager/platformio.ini create mode 100644 variants/esp32s3/tlora-pager/variant.h diff --git a/src/detect/ScanI2C.h b/src/detect/ScanI2C.h index c1358861b..e46c6f623 100644 --- a/src/detect/ScanI2C.h +++ b/src/detect/ScanI2C.h @@ -79,7 +79,8 @@ class ScanI2C BQ27220, LTR553ALS, BHI260AP, - BMM150 + BMM150, + DRV2605 } DeviceType; // typedef uint8_t DeviceAddress; diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp index 8b3670cd9..9aef9defe 100644 --- a/src/detect/ScanI2CTwoWire.cpp +++ b/src/detect/ScanI2CTwoWire.cpp @@ -483,8 +483,14 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize) type = MLX90614; logFoundDevice("MLX90614", (uint8_t)addr.address); } else { - type = MPR121KB; - logFoundDevice("MPR121KB", (uint8_t)addr.address); + registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x00), 1); // DRV2605_REG_STATUS + if (registerValue == 0xe0) { + type = DRV2605; + logFoundDevice("DRV2605", (uint8_t)addr.address); + } else { + type = MPR121KB; + logFoundDevice("MPR121KB", (uint8_t)addr.address); + } } break; diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index fa71e17d8..dea08d5ba 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -318,7 +318,7 @@ Screen::Screen(ScanI2C::DeviceAddress address, meshtastic_Config_DisplayConfig_O dispdev = new SSD1306Wire(address.address, -1, -1, geometry, (address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE); #elif defined(ST7735_CS) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7789_CS) || \ - defined(RAK14014) || defined(HX8357_CS) || defined(ILI9488_CS) + defined(RAK14014) || defined(HX8357_CS) || defined(ILI9488_CS) || defined(ST7796_CS) dispdev = new TFTDisplay(address.address, -1, -1, geometry, (address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE); #elif defined(USE_EINK) && !defined(USE_EINK_DYNAMICDISPLAY) @@ -550,7 +550,7 @@ void Screen::setup() #else if (!config.display.flip_screen) { #if defined(ST7701_CS) || defined(ST7735_CS) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7789_CS) || \ - defined(RAK14014) || defined(HX8357_CS) || defined(ILI9488_CS) + defined(RAK14014) || defined(HX8357_CS) || defined(ILI9488_CS) || defined(ST7796_CS) static_cast(dispdev)->flipScreenVertically(); #elif defined(USE_ST7789) static_cast(dispdev)->flipScreenVertically(); diff --git a/src/graphics/ScreenFonts.h b/src/graphics/ScreenFonts.h index 84ec45977..a25417b05 100644 --- a/src/graphics/ScreenFonts.h +++ b/src/graphics/ScreenFonts.h @@ -73,7 +73,7 @@ #endif #if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \ - defined(ST7789_CS) || defined(USE_ST7789) || defined(HX8357_CS) || defined(ILI9488_CS)) && \ + defined(ST7789_CS) || defined(USE_ST7789) || defined(HX8357_CS) || defined(ILI9488_CS) || defined(ST7796_CS)) && \ !defined(DISPLAY_FORCE_SMALL_FONTS) // The screen is bigger so use bigger fonts #define FONT_SMALL FONT_MEDIUM_LOCAL // Height: 19 diff --git a/src/graphics/TFTDisplay.cpp b/src/graphics/TFTDisplay.cpp index f8787612f..b1814005e 100644 --- a/src/graphics/TFTDisplay.cpp +++ b/src/graphics/TFTDisplay.cpp @@ -562,6 +562,91 @@ class LGFX : public lgfx::LGFX_Device static LGFX *tft = nullptr; +#elif defined(ST7796_CS) +#include // Graphics and font library for ST7796 driver chip + +class LGFX : public lgfx::LGFX_Device +{ + lgfx::Panel_ST7796 _panel_instance; + lgfx::Bus_SPI _bus_instance; + lgfx::Light_PWM _light_instance; + + public: + LGFX(void) + { + { + auto cfg = _bus_instance.config(); + + // SPI + cfg.spi_host = ST7796_SPI_HOST; + cfg.spi_mode = 0; + cfg.freq_write = SPI_FREQUENCY; // SPI clock for transmission (up to 80MHz, rounded to the value obtained by dividing + // 80MHz by an integer) + cfg.freq_read = SPI_READ_FREQUENCY; // SPI clock when receiving + cfg.spi_3wire = false; + cfg.use_lock = true; // Set to true to use transaction locking + cfg.dma_channel = SPI_DMA_CH_AUTO; // SPI_DMA_CH_AUTO; // Set DMA channel to use (0=not use DMA / 1=1ch / 2=ch / + // SPI_DMA_CH_AUTO=auto setting) + cfg.pin_sclk = ST7796_SCK; // Set SPI SCLK pin number + cfg.pin_mosi = ST7796_SDA; // Set SPI MOSI pin number + cfg.pin_miso = ST7796_MISO; // Set SPI MISO pin number (-1 = disable) + cfg.pin_dc = ST7796_RS; // Set SPI DC pin number (-1 = disable) + + _bus_instance.config(cfg); // applies the set value to the bus. + _panel_instance.setBus(&_bus_instance); // set the bus on the panel. + } + + { // Set the display panel control. + auto cfg = _panel_instance.config(); // Gets a structure for display panel settings. + + cfg.pin_cs = ST7796_CS; // Pin number where CS is connected (-1 = disable) + cfg.pin_rst = ST7796_RESET; // Pin number where RST is connected (-1 = disable) + cfg.pin_busy = ST7796_BUSY; // Pin number where BUSY is connected (-1 = disable) + + // cfg.memory_width = TFT_WIDTH; // Maximum width supported by the driver IC + // cfg.memory_height = TFT_HEIGHT; // Maximum height supported by the driver IC + cfg.panel_width = TFT_WIDTH; // actual displayable width + cfg.panel_height = TFT_HEIGHT; // actual displayable height + cfg.offset_x = TFT_OFFSET_X; // Panel offset amount in X direction + cfg.offset_y = TFT_OFFSET_Y; // Panel offset amount in Y direction + cfg.offset_rotation = TFT_OFFSET_ROTATION; // Rotation direction value offset 0~7 (4~7 is mirrored) +#ifdef TFT_DUMMY_READ_PIXELS + cfg.dummy_read_pixel = TFT_DUMMY_READ_PIXELS; // Number of bits for dummy read before pixel readout +#else + cfg.dummy_read_pixel = 8; // Number of bits for dummy read before pixel readout +#endif + cfg.dummy_read_bits = 1; // Number of bits for dummy read before non-pixel data read + cfg.readable = true; // Set to true if data can be read + cfg.invert = true; // Set to true if the light/darkness of the panel is reversed + cfg.rgb_order = false; // Set to true if the panel's red and blue are swapped + cfg.dlen_16bit = + false; // Set to true for panels that transmit data length in 16-bit units with 16-bit parallel or SPI + cfg.bus_shared = true; // If the bus is shared with the SD card, set to true (bus control with drawJpgFile etc.) + + _panel_instance.config(cfg); + } + +#ifdef ST7796_BL + // Set the backlight control. (delete if not necessary) + { + auto cfg = _light_instance.config(); // Gets a structure for backlight settings. + + cfg.pin_bl = ST7796_BL; // Pin number to which the backlight is connected + cfg.invert = false; // true to invert the brightness of the backlight + cfg.freq = 44100; + cfg.pwm_channel = 7; + + _light_instance.config(cfg); + _panel_instance.setLight(&_light_instance); // Set the backlight on the panel. + } +#endif + + setPanel(&_panel_instance); // Sets the panel to use. + } +}; + +static LGFX *tft = nullptr; + #elif defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) #include // Graphics and font library for ILI9341/ILI9342 driver chip @@ -997,8 +1082,9 @@ static LGFX *tft = nullptr; #endif -#if defined(ST7701_CS) || defined(ST7735_CS) || defined(ST7789_CS) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || \ - defined(RAK14014) || defined(HX8357_CS) || defined(ILI9488_CS) || defined(ST72xx_DE) || (ARCH_PORTDUINO && HAS_SCREEN != 0) +#if defined(ST7701_CS) || defined(ST7735_CS) || defined(ST7789_CS) || defined(ST7796_CS) || defined(ILI9341_DRIVER) || \ + defined(ILI9342_DRIVER) || defined(RAK14014) || defined(HX8357_CS) || defined(ILI9488_CS) || defined(ST72xx_DE) || \ + (ARCH_PORTDUINO && HAS_SCREEN != 0) #include "SPILock.h" #include "TFTDisplay.h" #include @@ -1047,32 +1133,97 @@ void TFTDisplay::display(bool fromBlank) { if (fromBlank) tft->fillScreen(TFT_BLACK); - // tft->clear(); + concurrency::LockGuard g(spiLock); - uint16_t x, y; + uint32_t x, y; + uint32_t y_byteIndex; + uint8_t y_byteMask; + uint32_t x_FirstPixelUpdate; + uint32_t x_LastPixelUpdate; + bool isset, dblbuf_isset; + uint16_t colorTftMesh, colorTftBlack; + bool somethingChanged = false; - for (y = 0; y < displayHeight; y++) { - for (x = 0; x < displayWidth; x++) { - auto isset = buffer[x + (y / 8) * displayWidth] & (1 << (y & 7)); + // Store colors byte-reversed so that TFT_eSPI doesn't have to swap bytes in a separate step + colorTftMesh = (TFT_MESH >> 8) | ((TFT_MESH & 0xFF) << 8); + colorTftBlack = (TFT_BLACK >> 8) | ((TFT_BLACK & 0xFF) << 8); + + y = 0; + while (y < displayHeight) { + y_byteIndex = (y / 8) * displayWidth; + y_byteMask = (1 << (y & 7)); + + // Step 1: Do a quick scan of 8 rows together. This allows fast-forwarding over unchanged screen areas. + if (y_byteMask == 1) { if (!fromBlank) { - // get src pixel in the page based ordering the OLED lib uses FIXME, super inefficent - auto dblbuf_isset = buffer_back[x + (y / 8) * displayWidth] & (1 << (y & 7)); - if (isset != dblbuf_isset) { - tft->drawPixel(x, y, isset ? TFT_MESH : TFT_BLACK); + for (x = 0; x < displayWidth; x++) { + if (buffer[x + y_byteIndex] != buffer_back[x + y_byteIndex]) + break; } - } else if (isset) { - tft->drawPixel(x, y, TFT_MESH); + } else { + for (x = 0; x < displayWidth; x++) { + if (buffer[x + y_byteIndex] != 0) + break; + } + } + if (x >= displayWidth) { + // No changed pixels found in these 8 rows, fast-forward to the next 8 + y = y + 8; + continue; } } + + // Step 2: Scan each of the 8 rows individually. Find the first pixel in each row that needs updating + for (x_FirstPixelUpdate = 0; x_FirstPixelUpdate < displayWidth; x_FirstPixelUpdate++) { + isset = buffer[x_FirstPixelUpdate + y_byteIndex] & y_byteMask; + + if (!fromBlank) { + // get src pixel in the page based ordering the OLED lib uses + dblbuf_isset = buffer_back[x_FirstPixelUpdate + y_byteIndex] & y_byteMask; + if (isset != dblbuf_isset) { + break; + } + } else if (isset) { + break; + } + } + + // Did we find a pixel that needs updating on this row? + if (x_FirstPixelUpdate < displayWidth) { + + // Quickly write out the first changed pixel (saves another array lookup) + linePixelBuffer[x_FirstPixelUpdate] = isset ? colorTftMesh : colorTftBlack; + x_LastPixelUpdate = x_FirstPixelUpdate; + + // Step 3: copy all remaining pixels in this row into the pixel line buffer, + // while also recording the last pixel in the row that needs updating + for (x = x_FirstPixelUpdate + 1; x < displayWidth; x++) { + isset = buffer[x + y_byteIndex] & y_byteMask; + linePixelBuffer[x] = isset ? colorTftMesh : colorTftBlack; + + if (!fromBlank) { + dblbuf_isset = buffer_back[x + y_byteIndex] & y_byteMask; + if (isset != dblbuf_isset) { + x_LastPixelUpdate = x; + } + } else if (isset) { + x_LastPixelUpdate = x; + } + } + + // Step 4: Send the changed pixels on this line to the screen as a single block transfer. + // This function accepts pixel data MSB first so it can dump the memory straight out the SPI port. + tft->pushRect(x_FirstPixelUpdate, y, (x_LastPixelUpdate - x_FirstPixelUpdate + 1), 1, + &linePixelBuffer[x_FirstPixelUpdate]); + + somethingChanged = true; + } + y++; } // Copy the Buffer to the Back Buffer - for (y = 0; y < (displayHeight / 8); y++) { - for (x = 0; x < displayWidth; x++) { - uint16_t pos = x + y * displayWidth; - buffer_back[pos] = buffer[pos]; - } - } + if (somethingChanged) + memcpy(buffer_back, buffer, displayBufferSize); } void TFTDisplay::sdlLoop() @@ -1264,13 +1415,21 @@ bool TFTDisplay::connect() tft->setRotation(1); // T-Deck has the TFT in landscape #elif defined(T_WATCH_S3) tft->setRotation(2); // T-Watch S3 left-handed orientation -#elif ARCH_PORTDUINO || defined(SENSECAP_INDICATOR) +#elif ARCH_PORTDUINO || defined(SENSECAP_INDICATOR) || defined(T_LORA_PAGER) tft->setRotation(0); // use config.yaml to set rotation #else tft->setRotation(3); // Orient horizontal and wide underneath the silkscreen name label #endif tft->fillScreen(TFT_BLACK); + if (this->linePixelBuffer == NULL) { + this->linePixelBuffer = (uint16_t *)malloc(sizeof(uint16_t) * displayWidth); + + if (!this->linePixelBuffer) { + LOG_ERROR("Not enough memory to create TFT line buffer\n"); + return false; + } + } return true; } diff --git a/src/graphics/TFTDisplay.h b/src/graphics/TFTDisplay.h index 60adfdf7c..27672ad29 100644 --- a/src/graphics/TFTDisplay.h +++ b/src/graphics/TFTDisplay.h @@ -58,4 +58,6 @@ class TFTDisplay : public OLEDDisplay // Connect to the display virtual bool connect() override; + + uint16_t *linePixelBuffer = nullptr; }; \ No newline at end of file diff --git a/src/graphics/draw/DebugRenderer.cpp b/src/graphics/draw/DebugRenderer.cpp index 5d9b5a33b..a0f29f10d 100644 --- a/src/graphics/draw/DebugRenderer.cpp +++ b/src/graphics/draw/DebugRenderer.cpp @@ -94,7 +94,8 @@ void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16 if (!Throttle::isWithinTimespanMs(storeForwardModule->lastHeartbeat, (storeForwardModule->heartbeatInterval * 1200))) { // no heartbeat, overlap a bit #if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \ - defined(ST7789_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(HX8357_CS) || ARCH_PORTDUINO) && \ + defined(ST7789_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(HX8357_CS) || defined(ST7796_CS) || \ + ARCH_PORTDUINO) && \ !defined(DISPLAY_FORCE_SMALL_FONTS) display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(screen->ourId), y + 3 + FONT_HEIGHT_SMALL, 12, 8, imgQuestionL1); @@ -106,7 +107,7 @@ void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16 #endif } else { #if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \ - defined(ST7789_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(HX8357_CS)) && \ + defined(ST7789_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(HX8357_CS) || defined(ST7796_CS)) && \ !defined(DISPLAY_FORCE_SMALL_FONTS) display->drawFastImage(x + SCREEN_WIDTH - 18 - display->getStringWidth(screen->ourId), y + 3 + FONT_HEIGHT_SMALL, 16, 8, imgSFL1); @@ -121,7 +122,8 @@ void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16 } else { // TODO: Raspberry Pi supports more than just the one screen size #if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \ - defined(ST7789_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(HX8357_CS) || ARCH_PORTDUINO) && \ + defined(ST7789_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(HX8357_CS) || defined(ST7796_CS) || \ + ARCH_PORTDUINO) && \ !defined(DISPLAY_FORCE_SMALL_FONTS) display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(screen->ourId), y + 3 + FONT_HEIGHT_SMALL, 12, 8, imgInfoL1); diff --git a/src/graphics/draw/MenuHandler.cpp b/src/graphics/draw/MenuHandler.cpp index 512f650ec..bcd8d8ee8 100644 --- a/src/graphics/draw/MenuHandler.cpp +++ b/src/graphics/draw/MenuHandler.cpp @@ -434,8 +434,8 @@ void menuHandler::systemBaseMenu() optionsArray[options] = "Notifications"; optionsEnumArray[options++] = Notifications; -#if defined(ST7789_CS) || defined(USE_OLED) || defined(USE_SSD1306) || defined(USE_SH1106) || defined(USE_SH1107) || \ - defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) || HAS_TFT +#if defined(ST7789_CS) || defined(ST7796_CS) || defined(USE_OLED) || defined(USE_SSD1306) || defined(USE_SH1106) || \ + defined(USE_SH1107) || defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) || HAS_TFT optionsArray[options] = "Screen Options"; optionsEnumArray[options++] = ScreenOptions; #endif @@ -725,7 +725,7 @@ void menuHandler::BrightnessPickerMenu() #if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) // For HELTEC devices, use analogWrite to control backlight analogWrite(VTFT_LEDA, uiconfig.screen_brightness); -#elif defined(ST7789_CS) +#elif defined(ST7789_CS) || defined(ST7796_CS) static_cast(screen->getDisplayDevice())->setDisplayBrightness(uiconfig.screen_brightness); #elif defined(USE_OLED) || defined(USE_SSD1306) || defined(USE_SH1106) || defined(USE_SH1107) screen->getDisplayDevice()->setBrightness(uiconfig.screen_brightness); @@ -768,7 +768,7 @@ void menuHandler::TFTColorPickerMenu(OLEDDisplay *display) bannerOptions.optionsArrayPtr = optionsArray; bannerOptions.optionsCount = 10; bannerOptions.bannerCallback = [display](int selected) -> void { -#if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) || defined(T_DECK) || HAS_TFT +#if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) || defined(T_DECK) || defined(T_LORA_PAGER) || HAS_TFT uint8_t TFT_MESH_r = 0; uint8_t TFT_MESH_g = 0; uint8_t TFT_MESH_b = 0; @@ -1045,7 +1045,7 @@ void menuHandler::screenOptionsMenu() } // Only show screen color for TFT displays -#if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) || defined(T_DECK) || HAS_TFT +#if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) || defined(T_DECK) || defined(T_LORA_PAGER) || HAS_TFT optionsArray[options] = "Screen Color"; optionsEnumArray[options++] = ScreenColor; #endif diff --git a/src/graphics/draw/UIRenderer.cpp b/src/graphics/draw/UIRenderer.cpp index 71d92616f..049722df8 100644 --- a/src/graphics/draw/UIRenderer.cpp +++ b/src/graphics/draw/UIRenderer.cpp @@ -194,7 +194,7 @@ void UIRenderer::drawNodes(OLEDDisplay *display, int16_t x, int16_t y, const mes } #if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \ - defined(ST7789_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(HX8357_CS)) && \ + defined(ST7789_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(HX8357_CS) || defined(ST7796_CS)) && \ !defined(DISPLAY_FORCE_SMALL_FONTS) if (isHighResolution) { diff --git a/src/graphics/images.h b/src/graphics/images.h index beef3a1b2..c66e4b992 100644 --- a/src/graphics/images.h +++ b/src/graphics/images.h @@ -27,7 +27,8 @@ const uint8_t bluetoothConnectedIcon[36] PROGMEM = {0xfe, 0x01, 0xff, 0x03, 0x03 0xfe, 0x31, 0x00, 0x30, 0x30, 0x30, 0x30, 0x30, 0xf0, 0x3f, 0xe0, 0x1f}; #if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \ - defined(ST7789_CS) || defined(USE_ST7789) || defined(HX8357_CS) || defined(ILI9488_CS) || ARCH_PORTDUINO) && \ + defined(ST7789_CS) || defined(USE_ST7789) || defined(HX8357_CS) || defined(ILI9488_CS) || defined(ST7796_CS) || \ + ARCH_PORTDUINO) && \ !defined(DISPLAY_FORCE_SMALL_FONTS) const uint8_t imgQuestionL1[] PROGMEM = {0xff, 0x01, 0x01, 0x32, 0x7b, 0x49, 0x49, 0x6f, 0x26, 0x01, 0x01, 0xff}; const uint8_t imgQuestionL2[] PROGMEM = {0x0f, 0x08, 0x08, 0x08, 0x06, 0x0f, 0x0f, 0x06, 0x08, 0x08, 0x08, 0x0f}; diff --git a/src/input/RotaryEncoderImpl.cpp b/src/input/RotaryEncoderImpl.cpp new file mode 100644 index 000000000..b71e800e0 --- /dev/null +++ b/src/input/RotaryEncoderImpl.cpp @@ -0,0 +1,73 @@ +#ifdef T_LORA_PAGER + +#include "RotaryEncoderImpl.h" +#include "InputBroker.h" +#include "RotaryEncoder.h" + +#define ORIGIN_NAME "RotaryEncoder" + +RotaryEncoderImpl *rotaryEncoderImpl; + +RotaryEncoderImpl::RotaryEncoderImpl() : concurrency::OSThread(ORIGIN_NAME), originName(ORIGIN_NAME) {} + +bool RotaryEncoderImpl::init() +{ + if (!moduleConfig.canned_message.updown1_enabled || moduleConfig.canned_message.inputbroker_pin_a == 0 || + moduleConfig.canned_message.inputbroker_pin_b == 0) { + // Input device is disabled. + disable(); + return false; + } + + eventCw = static_cast(moduleConfig.canned_message.inputbroker_event_cw); + eventCcw = static_cast(moduleConfig.canned_message.inputbroker_event_ccw); + eventPressed = static_cast(moduleConfig.canned_message.inputbroker_event_press); + + rotary = new RotaryEncoder(moduleConfig.canned_message.inputbroker_pin_a, moduleConfig.canned_message.inputbroker_pin_b, + moduleConfig.canned_message.inputbroker_pin_press); + rotary->resetButton(); + + inputBroker->registerSource(this); + + LOG_INFO("RotaryEncoder initialized pins(%d, %d, %d), events(%d, %d, %d)", moduleConfig.canned_message.inputbroker_pin_a, + moduleConfig.canned_message.inputbroker_pin_b, moduleConfig.canned_message.inputbroker_pin_press, eventCw, eventCcw, + eventPressed); + return true; +} + +int32_t RotaryEncoderImpl::runOnce() +{ + InputEvent e; + e.inputEvent = INPUT_BROKER_NONE; + e.source = this->originName; + + static uint32_t lastPressed = millis(); + if (rotary->readButton() == RotaryEncoder::ButtonState::BUTTON_PRESSED) { + if (lastPressed + 200 < millis()) { + LOG_DEBUG("Rotary event Press"); + lastPressed = millis(); + e.inputEvent = this->eventPressed; + } + } else { + switch (rotary->process()) { + case RotaryEncoder::DIRECTION_CW: + LOG_DEBUG("Rotary event CW"); + e.inputEvent = this->eventCw; + break; + case RotaryEncoder::DIRECTION_CCW: + LOG_DEBUG("Rotary event CCW"); + e.inputEvent = this->eventCcw; + break; + default: + break; + } + } + + if (e.inputEvent != INPUT_BROKER_NONE) { + this->notifyObservers(&e); + } + + return 20; +} + +#endif \ No newline at end of file diff --git a/src/input/RotaryEncoderImpl.h b/src/input/RotaryEncoderImpl.h new file mode 100644 index 000000000..ae2a7c6fd --- /dev/null +++ b/src/input/RotaryEncoderImpl.h @@ -0,0 +1,28 @@ +#pragma once + +// This is a non-interrupt version of RotaryEncoder which is based on a debounce inherent FSM table (see RotaryEncoder library) + +#include "InputBroker.h" +#include "concurrency/OSThread.h" +#include "mesh/NodeDB.h" + +class RotaryEncoder; + +class RotaryEncoderImpl : public Observable, public concurrency::OSThread +{ + public: + RotaryEncoderImpl(); + bool init(void); + + protected: + virtual int32_t runOnce() override; + + input_broker_event eventCw = INPUT_BROKER_NONE; + input_broker_event eventCcw = INPUT_BROKER_NONE; + input_broker_event eventPressed = INPUT_BROKER_NONE; + + RotaryEncoder *rotary; + const char *originName; +}; + +extern RotaryEncoderImpl *rotaryEncoderImpl; diff --git a/src/input/TLoraPagerKeyboard.cpp b/src/input/TLoraPagerKeyboard.cpp new file mode 100644 index 000000000..b3f62a36c --- /dev/null +++ b/src/input/TLoraPagerKeyboard.cpp @@ -0,0 +1,230 @@ +#if defined(T_LORA_PAGER) + +#include "TLoraPagerKeyboard.h" +#include "main.h" + +#ifndef LEDC_BACKLIGHT_CHANNEL +#define LEDC_BACKLIGHT_CHANNEL 4 +#endif + +#ifndef LEDC_BACKLIGHT_BIT_WIDTH +#define LEDC_BACKLIGHT_BIT_WIDTH 8 +#endif + +#ifndef LEDC_BACKLIGHT_FREQ +#define LEDC_BACKLIGHT_FREQ 1000 // Hz +#endif + +#define _TCA8418_COLS 10 +#define _TCA8418_ROWS 4 +#define _TCA8418_NUM_KEYS 31 + +#define _TCA8418_MULTI_TAP_THRESHOLD 1500 + +using Key = TCA8418KeyboardBase::TCA8418Key; + +constexpr uint8_t modifierRightShiftKey = 29 - 1; // keynum -1 +constexpr uint8_t modifierRightShift = 0b0001; +constexpr uint8_t modifierSymKey = 21 - 1; +constexpr uint8_t modifierSym = 0b0010; + +// Num chars per key, Modulus for rotating through characters +static uint8_t TLoraPagerTapMod[_TCA8418_NUM_KEYS] = {3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3}; + +static unsigned char TLoraPagerTapMap[_TCA8418_NUM_KEYS][3] = {{'q', 'Q', '1'}, + {'w', 'W', '2'}, + {'e', 'E', '3'}, + {'r', 'R', '4'}, + {'t', 'T', '5'}, + {'y', 'Y', '6'}, + {'u', 'U', '7'}, + {'i', 'I', '8'}, + {'o', 'O', '9'}, + {'p', 'P', '0'}, + {'a', 'A', '*'}, + {'s', 'S', '/'}, + {'d', 'D', '+'}, + {'f', 'F', '-'}, + {'g', 'G', '='}, + {'h', 'H', ':'}, + {'j', 'J', '\''}, + {'k', 'K', '"'}, + {'l', 'L', '@'}, + {Key::SELECT, 0x00, Key::TAB}, + {0x00, 0x00, 0x00}, + {'z', 'Z', '_'}, + {'x', 'X', '$'}, + {'c', 'C', ';'}, + {'v', 'V', '?'}, + {'b', 'B', '!'}, + {'n', 'N', ','}, + {'m', 'M', '.'}, + {0x00, 0x00, 0x00}, + {Key::BSP, 0x00, Key::ESC}, + {' ', 0x00, Key::BL_TOGGLE}}; + +TLoraPagerKeyboard::TLoraPagerKeyboard() + : TCA8418KeyboardBase(_TCA8418_ROWS, _TCA8418_COLS), modifierFlag(0), last_modifier_time(0), last_key(-1), next_key(-1), + last_tap(0L), char_idx(0), tap_interval(0) +{ +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) + ledcAttach(KB_BL_PIN, LEDC_BACKLIGHT_FREQ, LEDC_BACKLIGHT_BIT_WIDTH); +#else + ledcSetup(LEDC_BACKLIGHT_CHANNEL, LEDC_BACKLIGHT_FREQ, LEDC_BACKLIGHT_BIT_WIDTH); + ledcAttachPin(KB_BL_PIN, LEDC_BACKLIGHT_CHANNEL); +#endif + reset(); +} + +void TLoraPagerKeyboard::reset(void) +{ + TCA8418KeyboardBase::reset(); + pinMode(KB_BL_PIN, OUTPUT); + digitalWrite(KB_BL_PIN, LOW); + setBacklight(false); +} + +// handle multi-key presses (shift and alt) +void TLoraPagerKeyboard::trigger() +{ + uint8_t count = keyCount(); + if (count == 0) + return; + for (uint8_t i = 0; i < count; ++i) { + uint8_t k = readRegister(TCA8418_REG_KEY_EVENT_A + i); + uint8_t key = k & 0x7F; + if (k & 0x80) { + pressed(key); + } else { + released(); + state = Idle; + } + } +} + +void TLoraPagerKeyboard::setBacklight(bool on) +{ + toggleBacklight(!on); +} + +void TLoraPagerKeyboard::pressed(uint8_t key) +{ + if (state == Init || state == Busy) { + return; + } + if (config.device.buzzer_mode == meshtastic_Config_DeviceConfig_BuzzerMode_ALL_ENABLED || + config.device.buzzer_mode == meshtastic_Config_DeviceConfig_BuzzerMode_SYSTEM_ONLY) { + hapticFeedback(); + } + + if (modifierFlag && (millis() - last_modifier_time > _TCA8418_MULTI_TAP_THRESHOLD)) { + modifierFlag = 0; + } + + uint8_t next_key = 0; + int row = (key - 1) / 10; + int col = (key - 1) % 10; + + if (row >= _TCA8418_ROWS || col >= _TCA8418_COLS) { + return; // Invalid key + } + + next_key = row * _TCA8418_COLS + col; + state = Held; + + uint32_t now = millis(); + tap_interval = now - last_tap; + + updateModifierFlag(next_key); + if (isModifierKey(next_key)) { + last_modifier_time = now; + } + + if (tap_interval < 0) { + last_tap = 0; + state = Busy; + return; + } + + if (next_key != last_key || tap_interval > _TCA8418_MULTI_TAP_THRESHOLD) { + char_idx = 0; + } else { + char_idx += 1; + } + + last_key = next_key; + last_tap = now; +} + +void TLoraPagerKeyboard::released() +{ + if (state != Held) { + return; + } + + if (last_key < 0 || last_key >= _TCA8418_NUM_KEYS) { + last_key = -1; + state = Idle; + return; + } + + uint32_t now = millis(); + last_tap = now; + + if (TLoraPagerTapMap[last_key][modifierFlag % TLoraPagerTapMod[last_key]] == Key::BL_TOGGLE) { + toggleBacklight(); + return; + } + + queueEvent(TLoraPagerTapMap[last_key][modifierFlag % TLoraPagerTapMod[last_key]]); + if (isModifierKey(last_key) == false) + modifierFlag = 0; +} + +void TLoraPagerKeyboard::hapticFeedback() +{ + drv.setWaveform(0, 14); // strong buzz 100% + drv.setWaveform(1, 0); // end waveform + drv.go(); +} + +// toggle brightness of the backlight in three steps +void TLoraPagerKeyboard::toggleBacklight(bool off) +{ + static uint32_t brightness = 0; + if (off) { + brightness = 0; + } else { + if (brightness == 0) { + brightness = 40; + } else if (brightness == 40) { + brightness = 127; + } else if (brightness >= 127) { + brightness = 0; + } + } + LOG_DEBUG("Toggle backlight: %d", brightness); + +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) + ledcWrite(KB_BL_PIN, brightness); +#else + ledcWrite(LEDC_BACKLIGHT_CHANNEL, brightness); +#endif +} + +void TLoraPagerKeyboard::updateModifierFlag(uint8_t key) +{ + if (key == modifierRightShiftKey) { + modifierFlag ^= modifierRightShift; + } else if (key == modifierSymKey) { + modifierFlag ^= modifierSym; + } +} + +bool TLoraPagerKeyboard::isModifierKey(uint8_t key) +{ + return (key == modifierRightShiftKey || key == modifierSymKey); +} + +#endif \ No newline at end of file diff --git a/src/input/TLoraPagerKeyboard.h b/src/input/TLoraPagerKeyboard.h index d31b05978..4dabbac64 100644 --- a/src/input/TLoraPagerKeyboard.h +++ b/src/input/TLoraPagerKeyboard.h @@ -4,9 +4,26 @@ class TLoraPagerKeyboard : public TCA8418KeyboardBase { public: TLoraPagerKeyboard(); - void setBacklight(bool on) override{}; + void reset(void); + void trigger(void) override; + void setBacklight(bool on) override; + virtual ~TLoraPagerKeyboard() {} protected: - void pressed(uint8_t key) override{}; - void released(void) override{}; + void pressed(uint8_t key) override; + void released(void) override; + void hapticFeedback(void); + + void updateModifierFlag(uint8_t key); + bool isModifierKey(uint8_t key); + void toggleBacklight(bool off = false); + + private: + uint8_t modifierFlag; // Flag to indicate if a modifier key is pressed + uint32_t last_modifier_time; // Timestamp of the last modifier key press + int8_t last_key; + int8_t next_key; + uint32_t last_tap; + uint8_t char_idx; + int32_t tap_interval; }; diff --git a/src/input/cardKbI2cImpl.cpp b/src/input/cardKbI2cImpl.cpp index fcbdd0a3f..9b0926a1d 100644 --- a/src/input/cardKbI2cImpl.cpp +++ b/src/input/cardKbI2cImpl.cpp @@ -12,8 +12,8 @@ void CardKbI2cImpl::init() #if !MESHTASTIC_EXCLUDE_I2C && !defined(ARCH_PORTDUINO) && !defined(I2C_NO_RESCAN) 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, XPOWERS_AXP192_AXP2101_ADDRESS}; - uint8_t i2caddr_asize = 5; + uint8_t i2caddr_scan[] = {CARDKB_ADDR, TDECK_KB_ADDR, BBQ10_KB_ADDR, MPR121_KB_ADDR, TCA8418_KB_ADDR}; + uint8_t i2caddr_asize = sizeof(i2caddr_scan) / sizeof(i2caddr_scan[0]); auto i2cScanner = std::unique_ptr(new ScanI2CTwoWire()); #if WIRE_INTERFACES_COUNT == 2 diff --git a/src/main.cpp b/src/main.cpp index 0260cbc07..338487914 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -135,8 +135,9 @@ AccelerometerThread *accelerometerThread = nullptr; AudioThread *audioThread = nullptr; #endif -#ifdef USE_PCA9557 -PCA9557 IOEXP; +#ifdef USE_XL9555 +#include "ExtensionIOXL9555.hpp" +ExtensionIOXL9555 io; #endif #if HAS_TFT @@ -201,7 +202,7 @@ ScanI2C::FoundDevice rgb_found = ScanI2C::FoundDevice(ScanI2C::DeviceType::NONE, /// The I2C address of our Air Quality Indicator (if found) ScanI2C::DeviceAddress aqi_found = ScanI2C::ADDRESS_NONE; -#ifdef T_WATCH_S3 +#if defined(T_WATCH_S3) || defined(T_LORA_PAGER) Adafruit_DRV2605 drv; #endif @@ -359,6 +360,30 @@ void setup() digitalWrite(SDCARD_CS, HIGH); pinMode(PIN_EINK_CS, OUTPUT); digitalWrite(PIN_EINK_CS, HIGH); +#elif defined(T_LORA_PAGER) + pinMode(LORA_CS, OUTPUT); + digitalWrite(LORA_CS, HIGH); + pinMode(SDCARD_CS, OUTPUT); + digitalWrite(SDCARD_CS, HIGH); + pinMode(TFT_CS, OUTPUT); + digitalWrite(TFT_CS, HIGH); + // io expander + io.begin(Wire, XL9555_SLAVE_ADDRESS0, SDA, SCL); + io.pinMode(EXPANDS_DRV_EN, OUTPUT); + io.digitalWrite(EXPANDS_DRV_EN, HIGH); + io.pinMode(EXPANDS_AMP_EN, OUTPUT); + io.digitalWrite(EXPANDS_AMP_EN, HIGH); + io.pinMode(EXPANDS_LORA_EN, OUTPUT); + io.digitalWrite(EXPANDS_LORA_EN, HIGH); + io.pinMode(EXPANDS_GPS_EN, OUTPUT); + io.digitalWrite(EXPANDS_GPS_EN, HIGH); + io.pinMode(EXPANDS_KB_EN, OUTPUT); + io.digitalWrite(EXPANDS_KB_EN, HIGH); + io.pinMode(EXPANDS_SD_EN, OUTPUT); + io.digitalWrite(EXPANDS_SD_EN, HIGH); + io.pinMode(EXPANDS_GPIO_EN, OUTPUT); + io.digitalWrite(EXPANDS_GPIO_EN, HIGH); + io.pinMode(EXPANDS_SD_PULLEN, INPUT); #endif concurrency::hasBeenSetup = true; @@ -805,7 +830,7 @@ void setup() #endif #endif -#ifdef T_WATCH_S3 +#if defined(T_WATCH_S3) || defined(T_LORA_PAGER) drv.begin(); drv.selectLibrary(1); // I2C trigger by sending 'go' command @@ -851,7 +876,7 @@ void setup() if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) { #if defined(ST7701_CS) || defined(ST7735_CS) || defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || \ - defined(ST7789_CS) || defined(HX8357_CS) || defined(USE_ST7789) || defined(ILI9488_CS) + 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]) && @@ -1114,7 +1139,7 @@ void setup() // Don't call screen setup until after nodedb is setup (because we need // the current region name) #if defined(ST7701_CS) || defined(ST7735_CS) || defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || \ - defined(ST7789_CS) || defined(HX8357_CS) || defined(USE_ST7789) || defined(ILI9488_CS) + defined(ST7789_CS) || defined(HX8357_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(ST7796_CS) if (screen) screen->setup(); #elif defined(ARCH_PORTDUINO) diff --git a/src/main.h b/src/main.h index 3568daad2..ef1f241ef 100644 --- a/src/main.h +++ b/src/main.h @@ -41,7 +41,7 @@ extern bool eink_found; extern bool pmu_found; extern bool isUSBPowered; -#ifdef T_WATCH_S3 +#if defined(T_WATCH_S3) || defined(T_LORA_PAGER) #include extern Adafruit_DRV2605 drv; #endif diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 18014eb02..d544d0174 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -663,7 +663,7 @@ void NodeDB::installDefaultConfig(bool preserveKey = false) config.bluetooth.fixed_pin = defaultBLEPin; #if defined(ST7735_CS) || defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7789_CS) || \ - defined(HX8357_CS) || defined(USE_ST7789) || defined(ILI9488_CS) + defined(HX8357_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(ST7796_CS) bool hasScreen = true; #ifdef HELTEC_MESH_NODE_T114 uint32_t st7789_id = get_st7789_id(ST7789_NSS, ST7789_SCK, ST7789_SDA, ST7789_RS, ST7789_RESET); @@ -830,6 +830,15 @@ void NodeDB::installDefaultModuleConfig() moduleConfig.external_notification.alert_message = true; moduleConfig.external_notification.output_ms = 1000; moduleConfig.external_notification.nag_timeout = 60; +#endif +#ifdef T_LORA_PAGER + moduleConfig.canned_message.updown1_enabled = true; + moduleConfig.canned_message.inputbroker_pin_a = ROTARY_A; + moduleConfig.canned_message.inputbroker_pin_b = ROTARY_B; + moduleConfig.canned_message.inputbroker_pin_press = ROTARY_PRESS; + moduleConfig.canned_message.inputbroker_event_cw = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar(28); + moduleConfig.canned_message.inputbroker_event_ccw = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar(29); + moduleConfig.canned_message.inputbroker_event_press = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_SELECT; #endif moduleConfig.has_canned_message = true; #if USERPREFS_MQTT_ENABLED && !MESHTASTIC_EXCLUDE_MQTT diff --git a/src/modules/Modules.cpp b/src/modules/Modules.cpp index 3528f57f5..0d405fa81 100644 --- a/src/modules/Modules.cpp +++ b/src/modules/Modules.cpp @@ -3,6 +3,7 @@ #include "buzz/BuzzerFeedbackThread.h" #include "input/ExpressLRSFiveWay.h" #include "input/InputBroker.h" +#include "input/RotaryEncoderImpl.h" #include "input/RotaryEncoderInterruptImpl1.h" #include "input/SerialKeyboardImpl.h" #include "input/TrackballInterruptImpl1.h" @@ -170,11 +171,20 @@ void setupModules() delete rotaryEncoderInterruptImpl1; rotaryEncoderInterruptImpl1 = nullptr; } +#ifdef T_LORA_PAGER + // use a special FSM based rotary encoder version for T-LoRa Pager + rotaryEncoderImpl = new RotaryEncoderImpl(); + if (!rotaryEncoderImpl->init()) { + delete rotaryEncoderImpl; + rotaryEncoderImpl = nullptr; + } +#else upDownInterruptImpl1 = new UpDownInterruptImpl1(); if (!upDownInterruptImpl1->init()) { delete upDownInterruptImpl1; upDownInterruptImpl1 = nullptr; } +#endif cardKbI2cImpl = new CardKbI2cImpl(); cardKbI2cImpl->init(); #ifdef INPUTBROKER_MATRIX_TYPE diff --git a/src/platform/esp32/architecture.h b/src/platform/esp32/architecture.h index 522e862ac..cb0f0dab3 100644 --- a/src/platform/esp32/architecture.h +++ b/src/platform/esp32/architecture.h @@ -192,6 +192,8 @@ #define HW_VENDOR meshtastic_HardwareModel_LINK_32 #elif defined(T_DECK_PRO) #define HW_VENDOR meshtastic_HardwareModel_T_DECK_PRO +#elif defined(T_LORA_PAGER) +#define HW_VENDOR meshtastic_HardwareModel_T_LORA_PAGER #endif // ----------------------------------------------------------------------------- diff --git a/src/platform/extra_variants/t_lora_pager/variant.cpp b/src/platform/extra_variants/t_lora_pager/variant.cpp new file mode 100644 index 000000000..ea5773d30 --- /dev/null +++ b/src/platform/extra_variants/t_lora_pager/variant.cpp @@ -0,0 +1,27 @@ +#include "configuration.h" + +#ifdef T_LORA_PAGER + +#include "AudioBoard.h" + +DriverPins PinsAudioBoardES8311; +AudioBoard board(AudioDriverES8311, PinsAudioBoardES8311); + +// TLora Pager specific init +void lateInitVariant() +{ + // AudioDriverLogger.begin(Serial, AudioDriverLogLevel::Debug); + // I2C: function, scl, sda + PinsAudioBoardES8311.addI2C(PinFunction::CODEC, Wire); + // I2S: function, mclk, bck, ws, data_out, data_in + PinsAudioBoardES8311.addI2S(PinFunction::CODEC, DAC_I2S_MCLK, DAC_I2S_BCK, DAC_I2S_WS, DAC_I2S_DOUT, DAC_I2S_DIN); + + // configure codec + CodecConfig cfg; + cfg.input_device = ADC_INPUT_LINE1; + cfg.output_device = DAC_OUTPUT_ALL; + cfg.i2s.bits = BIT_LENGTH_16BITS; + cfg.i2s.rate = RATE_44K; + board.begin(cfg); +} +#endif \ No newline at end of file diff --git a/variants/esp32s3/tlora-pager/pins_arduino.h b/variants/esp32s3/tlora-pager/pins_arduino.h new file mode 100644 index 000000000..a6321f510 --- /dev/null +++ b/variants/esp32s3/tlora-pager/pins_arduino.h @@ -0,0 +1,19 @@ +#ifndef Pins_Arduino_h +#define Pins_Arduino_h + +#include + +#define USB_VID 0x303a +#define USB_PID 0x1001 + +// used for keyboard, battery gauge, charger and haptic driver +static const uint8_t SDA = 3; +static const uint8_t SCL = 2; + +// Default SPI will be mapped to Radio +static const uint8_t SS = 36; +static const uint8_t MOSI = 34; +static const uint8_t MISO = 33; +static const uint8_t SCK = 35; + +#endif /* Pins_Arduino_h */ diff --git a/variants/esp32s3/tlora-pager/platformio.ini b/variants/esp32s3/tlora-pager/platformio.ini new file mode 100644 index 000000000..3d77d879c --- /dev/null +++ b/variants/esp32s3/tlora-pager/platformio.ini @@ -0,0 +1,70 @@ +; LilyGo T-Lora-Pager +[env:tlora-pager] +extends = esp32s3_base +board = t-deck-pro ; same as T-Deck Pro +board_check = true +board_build.partitions = default_16MB.csv +upload_protocol = esptool + +build_flags = ${esp32s3_base.build_flags} + -I variants/esp32s3/tlora-pager + -D T_LORA_PAGER + -D BOARD_HAS_PSRAM + -D GPS_POWER_TOGGLE + -D HAS_SDCARD + -D SDCARD_USE_SPI1 + -D ENABLE_ROTARY_PULLUP + -D ENABLE_BUTTON_PULLUP + -D HALF_STEP + +lib_deps = ${esp32s3_base.lib_deps} + lovyan03/LovyanGFX@1.2.7 + earlephilhower/ESP8266Audio@1.9.9 + earlephilhower/ESP8266SAM@1.0.1 + adafruit/Adafruit DRV2605 Library@1.2.4 + lewisxhe/PCF8563_Library@1.0.1 + 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 + +[env:tlora-pager-tft] +extends = env:tlora-pager +build_flags = + ${env:tlora-pager.build_flags} + -D CONFIG_DISABLE_HAL_LOCKS=1 + -D INPUTDRIVER_ROTARY_TYPE=1 + -D INPUTDRIVER_ROTARY_UP=40 + -D INPUTDRIVER_ROTARY_DOWN=41 + -D INPUTDRIVER_ROTARY_BTN=7 + -D INPUTDRIVER_BUTTON_TYPE=0 + -D HAS_SCREEN=1 + -D HAS_TFT=1 + -D USE_I2S_BUZZER + -D RAM_SIZE=5120 + -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 USE_LOG_DEBUG + -D LOG_DEBUG_INC=\"DebugConfiguration.h\" + -D RADIOLIB_SPI_PARANOID=0 + -D LGFX_SCREEN_WIDTH=222 + -D LGFX_SCREEN_HEIGHT=480 + -D DISPLAY_SIZE=480x222 ; landscape mode + -D DISPLAY_SET_RESOLUTION + -D LGFX_DRIVER=LGFX_TLORA_PAGER + -D GFX_DRIVER_INC=\"graphics/LGFX/LGFX_T_LORA_PAGER.h\" +; -D LVGL_DRIVER=LVGL_T_LORA_PAGER +; -D LV_USE_ST7796=1 + -D VIEW_480x222 + -D USE_PACKET_API + -D MAP_FULL_REDRAW + +lib_deps = + ${env:tlora-pager.lib_deps} + ${device-ui_base.lib_deps} diff --git a/variants/esp32s3/tlora-pager/variant.h b/variants/esp32s3/tlora-pager/variant.h new file mode 100644 index 000000000..ee48088c8 --- /dev/null +++ b/variants/esp32s3/tlora-pager/variant.h @@ -0,0 +1,125 @@ +// ST7796 TFT LCD +#define TFT_CS 38 +#define ST7796_CS TFT_CS +#define ST7796_RS 37 // DC +#define ST7796_SDA MOSI // MOSI +#define ST7796_SCK SCK +#define ST7796_RESET -1 +#define ST7796_MISO MISO +#define ST7796_BUSY -1 +#define ST7796_BL 42 +#define ST7796_SPI_HOST SPI2_HOST +#define TFT_BL 42 +#define SPI_FREQUENCY 75000000 +#define SPI_READ_FREQUENCY 16000000 +#define TFT_HEIGHT 480 +#define TFT_WIDTH 222 +#define TFT_OFFSET_X 49 +#define TFT_OFFSET_Y 0 +#define TFT_OFFSET_ROTATION 3 +#define SCREEN_ROTATE +#define SCREEN_TRANSITION_FRAMERATE 5 +#define BRIGHTNESS_DEFAULT 130 // Medium Low Brightness + +#define I2C_SDA SDA +#define I2C_SCL SCL + +#define USE_POWERSAVE +#define SLEEP_TIME 120 + +// GNNS +#define HAS_GPS 1 +#define GPS_BAUDRATE 38400 +#define GPS_RX_PIN 4 +#define GPS_TX_PIN 12 +#define PIN_GPS_PPS 13 + +// PCF8563 RTC Module +#if __has_include("pcf8563.h") +#include "pcf8563.h" +#endif +#define PCF8563_RTC 0x51 +#define HAS_RTC 1 + +// Rotary +#define ROTARY_A (40) +#define ROTARY_B (41) +#define ROTARY_PRESS (7) + +#define BUTTON_PIN 0 + +// SPI interface SD card slot +#define SPI_MOSI MOSI +#define SPI_SCK SCK +#define SPI_MISO MISO +#define SPI_CS 21 +#define SDCARD_CS SPI_CS +#define SD_SPI_FREQUENCY 75000000U + +// TCA8418 keyboard +#define I2C_NO_RESCAN +#define KB_BL_PIN 46 +#define KB_INT 6 +#define CANNED_MESSAGE_MODULE_ENABLE 1 + +// audio codec ES8311 +#define HAS_I2S +#define DAC_I2S_BCK 11 +#define DAC_I2S_WS 18 +#define DAC_I2S_DOUT 45 +#define DAC_I2S_DIN 17 +#define DAC_I2S_MCLK 10 + +// gyroscope BHI260AP +#define HAS_BHI260AP + +// battery charger BQ25896 +#define HAS_PPM 1 +#define XPOWERS_CHIP_BQ25896 + +// battery quality management BQ27220 +#define HAS_BQ27220 1 +#define BQ27220_I2C_SDA SDA +#define BQ27220_I2C_SCL SCL +#define BQ27220_DESIGN_CAPACITY 1500 + +// NFC ST25R3916 +#define NFC_INT 5 +#define NFC_CS 39 + +// External expansion chip XL9555 +#define USE_XL9555 +#define EXPANDS_DRV_EN (0) +#define EXPANDS_AMP_EN (1) +#define EXPANDS_KB_RST (2) +#define EXPANDS_LORA_EN (3) +#define EXPANDS_GPS_EN (4) +#define EXPANDS_NFC_EN (5) +#define EXPANDS_GPS_RST (7) +#define EXPANDS_KB_EN (8) +#define EXPANDS_GPIO_EN (9) +#define EXPANDS_SD_DET (10) +#define EXPANDS_SD_PULLEN (11) +#define EXPANDS_SD_EN (12) + +// LoRa +#define USE_SX1262 +#define USE_SX1268 + +#define LORA_SCK 35 +#define LORA_MISO 33 +#define LORA_MOSI 34 +#define LORA_CS 36 + +#define LORA_DIO0 -1 // a No connect on the SX1262 module +#define LORA_RESET 47 +#define LORA_DIO1 14 // SX1262 IRQ +#define LORA_DIO2 48 // SX1262 BUSY +#define LORA_DIO3 // Not connected on PCB, but internally on the TTGO SX1262, if DIO3 is high the TXCO is enabled + +#define SX126X_CS LORA_CS +#define SX126X_DIO1 LORA_DIO1 +#define SX126X_BUSY LORA_DIO2 +#define SX126X_RESET LORA_RESET +#define SX126X_DIO2_AS_RF_SWITCH +#define SX126X_DIO3_TCXO_VOLTAGE 3.0 From 596cd7e0b6c4225ea21509601ede589bce205937 Mon Sep 17 00:00:00 2001 From: Manuel <71137295+mverch67@users.noreply.github.com> Date: Tue, 26 Aug 2025 20:39:43 +0200 Subject: [PATCH 020/691] enable device telemetry (#7757) --- variants/esp32s3/elecrow_panel/platformio.ini | 2 -- 1 file changed, 2 deletions(-) diff --git a/variants/esp32s3/elecrow_panel/platformio.ini b/variants/esp32s3/elecrow_panel/platformio.ini index 59bc26000..065f22538 100644 --- a/variants/esp32s3/elecrow_panel/platformio.ini +++ b/variants/esp32s3/elecrow_panel/platformio.ini @@ -19,8 +19,6 @@ build_flags = ${esp32s3_base.build_flags} -Os -D MESHTASTIC_EXCLUDE_SERIAL=1 -D MESHTASTIC_EXCLUDE_SOCKETAPI=1 -D MESHTASTIC_EXCLUDE_SCREEN=1 - -D MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR=1 - -D HAS_TELEMETRY=0 -D CONFIG_DISABLE_HAL_LOCKS=1 -D USE_PIN_BUZZER -D HAS_SCREEN=0 From 2c071a32836ff2837f36c2b6e62d1028b4c8d7c6 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Tue, 26 Aug 2025 13:41:33 -0500 Subject: [PATCH 021/691] Don't use pin 0 on RAK for input (#7755) * Don't use pin 0 on RAK for input * Use boolean instead of define --------- Co-authored-by: Ben Meadors --- src/input/RotaryEncoderInterruptBase.cpp | 23 ++++++++++++++++------- src/input/UpDownInterruptBase.cpp | 23 ++++++++++++++++------- 2 files changed, 32 insertions(+), 14 deletions(-) diff --git a/src/input/RotaryEncoderInterruptBase.cpp b/src/input/RotaryEncoderInterruptBase.cpp index 0557bc180..88b07a389 100644 --- a/src/input/RotaryEncoderInterruptBase.cpp +++ b/src/input/RotaryEncoderInterruptBase.cpp @@ -18,14 +18,23 @@ void RotaryEncoderInterruptBase::init( this->_eventCcw = eventCcw; this->_eventPressed = eventPressed; - pinMode(pinPress, INPUT_PULLUP); - pinMode(this->_pinA, INPUT_PULLUP); - pinMode(this->_pinB, INPUT_PULLUP); + bool isRAK = false; +#ifdef RAK_4631 + isRAK = true; +#endif - // attachInterrupt(pinPress, onIntPress, RISING); - attachInterrupt(pinPress, onIntPress, RISING); - attachInterrupt(this->_pinA, onIntA, CHANGE); - attachInterrupt(this->_pinB, onIntB, CHANGE); + if (!isRAK || pinPress != 0) { + pinMode(pinPress, INPUT_PULLUP); + attachInterrupt(pinPress, onIntPress, RISING); + } + if (!isRAK || this->_pinA != 0) { + pinMode(this->_pinA, INPUT_PULLUP); + attachInterrupt(this->_pinA, onIntA, CHANGE); + } + if (!isRAK || this->_pinA != 0) { + pinMode(this->_pinB, INPUT_PULLUP); + attachInterrupt(this->_pinB, onIntB, CHANGE); + } this->rotaryLevelA = digitalRead(this->_pinA); this->rotaryLevelB = digitalRead(this->_pinB); diff --git a/src/input/UpDownInterruptBase.cpp b/src/input/UpDownInterruptBase.cpp index c66eb13d0..26b281aaf 100644 --- a/src/input/UpDownInterruptBase.cpp +++ b/src/input/UpDownInterruptBase.cpp @@ -15,14 +15,23 @@ void UpDownInterruptBase::init(uint8_t pinDown, uint8_t pinUp, uint8_t pinPress, this->_eventDown = eventDown; this->_eventUp = eventUp; this->_eventPressed = eventPressed; + bool isRAK = false; +#ifdef RAK_4631 + isRAK = true; +#endif - pinMode(pinPress, INPUT_PULLUP); - pinMode(this->_pinDown, INPUT_PULLUP); - pinMode(this->_pinUp, INPUT_PULLUP); - - attachInterrupt(pinPress, onIntPress, RISING); - attachInterrupt(this->_pinDown, onIntDown, RISING); - attachInterrupt(this->_pinUp, onIntUp, RISING); + if (!isRAK || pinPress != 0) { + pinMode(pinPress, INPUT_PULLUP); + attachInterrupt(pinPress, onIntPress, RISING); + } + if (!isRAK || this->_pinDown != 0) { + pinMode(this->_pinDown, INPUT_PULLUP); + attachInterrupt(this->_pinDown, onIntDown, RISING); + } + if (!isRAK || this->_pinUp != 0) { + pinMode(this->_pinUp, INPUT_PULLUP); + attachInterrupt(this->_pinUp, onIntUp, RISING); + } LOG_DEBUG("Up/down/press GPIO initialized (%d, %d, %d)", this->_pinUp, this->_pinDown, pinPress); From 3dd384dd53e56cd3ecf0b6721790ac968ad926f8 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Tue, 26 Aug 2025 19:45:26 -0500 Subject: [PATCH 022/691] Null check --- src/modules/AdminModule.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index 9e8ce2e6b..407003f7e 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -505,7 +505,7 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta if (mp.decoded.want_response && !myReply) { myReply = allocErrorResponse(meshtastic_Routing_Error_NONE, &mp); } - if (mp.pki_encrypted) { + if (mp.pki_encrypted && myReply) { myReply->pki_encrypted = true; } return handled; From f8ba392a2413fdf749d7e2a649dfd8246f737baf Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Tue, 26 Aug 2025 20:29:11 -0500 Subject: [PATCH 023/691] Add BaseUI support for L1 EInk (#7751) * Add BaseUI support for L1 EInk * Fix Eink offset * Add joystick * Updates * Adjust Seeed Wio Tracker L1 E-Ink variant (#7326) * Rename variant Needs the -inkhud suffix to work correctly with the web flasher * Display driver for ZJY122250_0213BAAMFGN * Remove dead code from nicheGraphics.h Remnants of T-Echo's nicheGraphics.h file, which was used as a template. * Use ZJY122250_0213BAAMFGN driver Improves display health. We don't need as many full refreshes now. * Tidying * board_check = true --------- Co-authored-by: Ben Meadors * Consolidation * Add hack for existing InkHUD button functionality --------- Co-authored-by: todd-herbert --- src/configuration.h | 2 +- src/graphics/EInkDisplay2.cpp | 19 ++++-- src/graphics/EInkDisplay2.h | 2 +- .../Drivers/EInk/ZJY122250_0213BAAMFGN.cpp | 68 +++++++++++++++++++ .../Drivers/EInk/ZJY122250_0213BAAMFGN.h | 42 ++++++++++++ src/graphics/niche/InkHUD/DisplayHealth.cpp | 5 -- .../seeed_wio_tracker_L1_eink/nicheGraphics.h | 30 ++------ .../seeed_wio_tracker_L1_eink/platformio.ini | 38 +++++++++-- .../seeed_wio_tracker_L1_eink/variant.h | 25 ++++--- 9 files changed, 181 insertions(+), 50 deletions(-) create mode 100644 src/graphics/niche/Drivers/EInk/ZJY122250_0213BAAMFGN.cpp create mode 100644 src/graphics/niche/Drivers/EInk/ZJY122250_0213BAAMFGN.h diff --git a/src/configuration.h b/src/configuration.h index 0e24990b5..8b4fd82c7 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -135,7 +135,7 @@ along with this program. If not, see . // ----------------------------------------------------------------------------- // OLED & Input // ----------------------------------------------------------------------------- -#if defined(SEEED_WIO_TRACKER_L1) +#if defined(SEEED_WIO_TRACKER_L1) && !defined(SEEED_WIO_TRACKER_L1_EINK) #define SSD1306_ADDRESS 0x3D #define USE_SH1106 #else diff --git a/src/graphics/EInkDisplay2.cpp b/src/graphics/EInkDisplay2.cpp index 1c9f290b6..c0c09cc27 100644 --- a/src/graphics/EInkDisplay2.cpp +++ b/src/graphics/EInkDisplay2.cpp @@ -67,20 +67,28 @@ bool EInkDisplay::forceDisplay(uint32_t msecLimit) // FIXME - only draw bits have changed (use backbuf similar to the other displays) const bool flipped = config.display.flip_screen; + // HACK for L1 EInk +#if defined(SEEED_WIO_TRACKER_L1_EINK) + // For SEEED_WIO_TRACKER_L1_EINK, setRotation(3) is correct but mirrored; flip both axes + for (uint32_t y = 0; y < displayHeight; y++) { + for (uint32_t x = 0; x < displayWidth; x++) { + auto b = buffer[x + (y / 8) * displayWidth]; + auto isset = b & (1 << (y & 7)); + adafruitDisplay->drawPixel((displayWidth - 1) - x, (displayHeight - 1) - y, isset ? GxEPD_BLACK : GxEPD_WHITE); + } + } +#else for (uint32_t y = 0; y < displayHeight; y++) { for (uint32_t x = 0; x < displayWidth; x++) { - // get src pixel in the page based ordering the OLED lib uses FIXME, super inefficient auto b = buffer[x + (y / 8) * displayWidth]; auto isset = b & (1 << (y & 7)); - - // Handle flip here, rather than with setRotation(), - // Avoids issues when display width is not a multiple of 8 if (flipped) adafruitDisplay->drawPixel((displayWidth - 1) - x, (displayHeight - 1) - y, isset ? GxEPD_BLACK : GxEPD_WHITE); else adafruitDisplay->drawPixel(x, y, isset ? GxEPD_BLACK : GxEPD_WHITE); } } +#endif // Trigger the refresh in GxEPD2 LOG_DEBUG("Update E-Paper"); @@ -235,7 +243,7 @@ bool EInkDisplay::connect() adafruitDisplay->setRotation(1); adafruitDisplay->setPartialWindow(0, 0, EINK_WIDTH, EINK_HEIGHT); } -#elif defined(HELTEC_MESH_POCKET) +#elif defined(HELTEC_MESH_POCKET) || defined(SEEED_WIO_TRACKER_L1_EINK) { spi1 = &SPI1; spi1->begin(); @@ -249,6 +257,7 @@ bool EInkDisplay::connect() // Init GxEPD2 adafruitDisplay->init(); adafruitDisplay->setRotation(3); + adafruitDisplay->setPartialWindow(0, 0, EINK_WIDTH, EINK_HEIGHT); } #elif defined(HELTEC_WIRELESS_PAPER) || defined(HELTEC_VISION_MASTER_E213) diff --git a/src/graphics/EInkDisplay2.h b/src/graphics/EInkDisplay2.h index b840ce9ba..b4cee81fe 100644 --- a/src/graphics/EInkDisplay2.h +++ b/src/graphics/EInkDisplay2.h @@ -84,7 +84,7 @@ class EInkDisplay : public OLEDDisplay SPIClass *hspi = NULL; #endif -#if defined(HELTEC_MESH_POCKET) +#if defined(HELTEC_MESH_POCKET) || defined(SEEED_WIO_TRACKER_L1_EINK) SPIClass *spi1 = NULL; #endif diff --git a/src/graphics/niche/Drivers/EInk/ZJY122250_0213BAAMFGN.cpp b/src/graphics/niche/Drivers/EInk/ZJY122250_0213BAAMFGN.cpp new file mode 100644 index 000000000..e83588905 --- /dev/null +++ b/src/graphics/niche/Drivers/EInk/ZJY122250_0213BAAMFGN.cpp @@ -0,0 +1,68 @@ +#include "./ZJY122250_0213BAAMFGN.h" + +#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS + +using namespace NicheGraphics::Drivers; + +// Map the display controller IC's output to the connected panel +void ZJY122250_0213BAAMFGN::configScanning() +{ + // "Driver output control" + // Scan gates from 0 to 249 (vertical resolution 250px) + sendCommand(0x01); + sendData(0xF9); + sendData(0x00); + sendData(0x00); +} + +// Specify which information is used to control the sequence of voltages applied to move the pixels +// - For this display, configUpdateSequence() specifies that a suitable LUT will be loaded from +// the controller IC's OTP memory, when the update procedure begins. +void ZJY122250_0213BAAMFGN::configWaveform() +{ + switch (updateType) { + case FAST: + sendCommand(0x3C); // Border waveform: + sendData(0x80); // VCOM + break; + case FULL: + default: + sendCommand(0x3C); // Border waveform: + sendData(0x01); // Follow LUT 1 (blink same as white pixels) + break; + } + + sendCommand(0x18); // Temperature sensor: + sendData(0x80); // Use internal temperature sensor to select an appropriate refresh waveform +} + +void ZJY122250_0213BAAMFGN::configUpdateSequence() +{ + switch (updateType) { + case FAST: + sendCommand(0x22); // Set "update sequence" + sendData(0xFF); // Will load LUT from OTP memory, Display mode 2 "differential refresh" + break; + + case FULL: + default: + sendCommand(0x22); // Set "update sequence" + sendData(0xF7); // Will load LUT from OTP memory + break; + } +} + +// Once the refresh operation has been started, +// begin periodically polling the display to check for completion, using the normal Meshtastic threading code +// Only used when refresh is "async" +void ZJY122250_0213BAAMFGN::detachFromUpdate() +{ + switch (updateType) { + case FAST: + return beginPolling(50, 500); // At least 500ms for fast refresh + case FULL: + default: + return beginPolling(100, 2000); // At least 2 seconds for full refresh + } +} +#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS \ No newline at end of file diff --git a/src/graphics/niche/Drivers/EInk/ZJY122250_0213BAAMFGN.h b/src/graphics/niche/Drivers/EInk/ZJY122250_0213BAAMFGN.h new file mode 100644 index 000000000..82c4ec107 --- /dev/null +++ b/src/graphics/niche/Drivers/EInk/ZJY122250_0213BAAMFGN.h @@ -0,0 +1,42 @@ +/* + +E-Ink display driver + - ZJY122250_0213BAAMFGN + - Manufacturer: Zhongjingyuan + - Size: 2.13 inch + - Resolution: 250px x 122px + - Flex connector marking (not a unique identifier): FPC-A002 + +*/ + +#pragma once + +#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS + +#include "configuration.h" + +#include "./SSD16XX.h" + +namespace NicheGraphics::Drivers +{ +class ZJY122250_0213BAAMFGN : public SSD16XX +{ + // Display properties + private: + static constexpr uint32_t width = 122; + static constexpr uint32_t height = 250; + static constexpr UpdateTypes supported = (UpdateTypes)(FULL | FAST); + + public: + ZJY122250_0213BAAMFGN() : SSD16XX(width, height, supported) {} + + protected: + virtual void configScanning() override; + virtual void configWaveform() override; + virtual void configUpdateSequence() override; + void detachFromUpdate() override; +}; + +} // namespace NicheGraphics::Drivers + +#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS \ No newline at end of file diff --git a/src/graphics/niche/InkHUD/DisplayHealth.cpp b/src/graphics/niche/InkHUD/DisplayHealth.cpp index 7e1accafd..e8849b72e 100644 --- a/src/graphics/niche/InkHUD/DisplayHealth.cpp +++ b/src/graphics/niche/InkHUD/DisplayHealth.cpp @@ -7,12 +7,7 @@ using namespace NicheGraphics; // Timing for "maintenance" // Paying off full-refresh debt with unprovoked updates, if the display is not very active - -#ifdef SEEED_WIO_TRACKER_L1 -static constexpr uint32_t MAINTENANCE_MS_INITIAL = 5 * 1000UL; -#else static constexpr uint32_t MAINTENANCE_MS_INITIAL = 60 * 1000UL; -#endif static constexpr uint32_t MAINTENANCE_MS = 60 * 60 * 1000UL; InkHUD::DisplayHealth::DisplayHealth() : concurrency::OSThread("Mediator") diff --git a/variants/nrf52840/seeed_wio_tracker_L1_eink/nicheGraphics.h b/variants/nrf52840/seeed_wio_tracker_L1_eink/nicheGraphics.h index a32753343..7fb890303 100644 --- a/variants/nrf52840/seeed_wio_tracker_L1_eink/nicheGraphics.h +++ b/variants/nrf52840/seeed_wio_tracker_L1_eink/nicheGraphics.h @@ -18,16 +18,9 @@ // Shared NicheGraphics components // -------------------------------- -#include "graphics/niche/Drivers/Backlight/LatchingBacklight.h" -#include "graphics/niche/Drivers/EInk/GDEY0213B74.h" +#include "graphics/niche/Drivers/EInk/ZJY122250_0213BAAMFGN.h" #include "graphics/niche/Inputs/TwoButton.h" -// Special case - fix T-Echo's touch button -// ---------------------------------------- -// On a handful of T-Echos, LoRa TX triggers the capacitive touch -// To avoid this, we lockout the button during TX -#include "mesh/RadioLibInterface.h" - void setupNicheGraphics() { using namespace NicheGraphics; @@ -41,7 +34,7 @@ void setupNicheGraphics() // E-Ink Driver // ----------------------------- - Drivers::EInk *driver = new Drivers::GDEY0213B74; + Drivers::EInk *driver = new Drivers::ZJY122250_0213BAAMFGN; driver->begin(&SPI1, PIN_EINK_DC, PIN_EINK_CS, PIN_EINK_BUSY, PIN_EINK_RES); // InkHUD @@ -53,8 +46,7 @@ void setupNicheGraphics() inkhud->setDriver(driver); // Set how many FAST updates per FULL update - // Set how unhealthy additional FAST updates beyond this number are - inkhud->setDisplayResilience(7, 1.5); + inkhud->setDisplayResilience(15); // Select fonts InkHUD::Applet::fontLarge = FREESANS_12PT_WIN1252; @@ -62,16 +54,10 @@ void setupNicheGraphics() InkHUD::Applet::fontSmall = FREESANS_6PT_WIN1252; // Customize default settings - inkhud->persistence->settings.userTiles.maxCount = 2; // Two applets side-by-side - // 270 degrees clockwise + inkhud->persistence->settings.rotation = 1; // 90 degrees clockwise inkhud->persistence->settings.optionalFeatures.batteryIcon = true; // Device definitely has a battery - inkhud->persistence->settings.optionalMenuItems.backlight = true; // Until proves capacitive button works by touching it - inkhud->persistence->settings.userTiles.count = 1; // One tile only by default, keep things simple for new users - - // Setup backlight controller - // Note: AUX button attached further down - Drivers::LatchingBacklight *backlight = Drivers::LatchingBacklight::getInstance(); - backlight->setPin(PIN_EINK_EN); + inkhud->persistence->settings.userTiles.count = 1; // One tile only by default, keep things simple for new users + inkhud->persistence->settings.userTiles.maxCount = 2; // Two applets side-by-side // Pick applets // Note: order of applets determines priority of "auto-show" feature @@ -83,11 +69,9 @@ void setupNicheGraphics() inkhud->addApplet("Recents List", new InkHUD::RecentsListApplet); // - inkhud->addApplet("Heard", new InkHUD::HeardApplet, true, false, 0); // Activated, no autoshow, default on tile 0 - inkhud->persistence->settings.rotation = 1; - // inkhud->persistence->printSettings(&inkhud->persistence->settings); // Start running InkHUD inkhud->begin(); - // inkhud->persistence->printSettings(&inkhud->persistence->settings); + // Buttons // -------------------------- diff --git a/variants/nrf52840/seeed_wio_tracker_L1_eink/platformio.ini b/variants/nrf52840/seeed_wio_tracker_L1_eink/platformio.ini index 52ff39d49..7f9eb0e2c 100644 --- a/variants/nrf52840/seeed_wio_tracker_L1_eink/platformio.ini +++ b/variants/nrf52840/seeed_wio_tracker_L1_eink/platformio.ini @@ -1,17 +1,47 @@ [env:seeed_wio_tracker_L1_eink] board = seeed_wio_tracker_L1 -extends = nrf52840_base, inkhud +extends = nrf52840_base ;board_level = extra build_flags = ${nrf52840_base.build_flags} - ${inkhud.build_flags} -I variants/nrf52840/seeed_wio_tracker_L1_eink -D SEEED_WIO_TRACKER_L1_EINK -D SEEED_WIO_TRACKER_L1 -I src/platform/nrf52/softdevice -I src/platform/nrf52/softdevice/nrf52 + -DUSE_EINK + -DEINK_DISPLAY_MODEL=GxEPD2_213_B74 + -DEINK_WIDTH=250 + -DEINK_HEIGHT=122 + -DUSE_EINK_DYNAMICDISPLAY ; Enable Dynamic EInk + -DEINK_LIMIT_FASTREFRESH=10 ; How many consecutive fast-refreshes are permitted + -DEINK_LIMIT_RATE_BACKGROUND_SEC=30 ; Minimum interval between BACKGROUND updates + -DEINK_LIMIT_RATE_RESPONSIVE_SEC=1 ; Minimum interval between RESPONSIVE updates +; -D EINK_LIMIT_GHOSTING_PX=2000 ; (Optional) How much image ghosting is tolerated + -DEINK_BACKGROUND_USES_FAST ; (Optional) Use FAST refresh for both BACKGROUND and RESPONSIVE, until a limit is reached. + -DEINK_HASQUIRK_GHOSTING ; Display model is identified as "prone to ghosting" + -DEINK_HASQUIRK_WEAKFASTREFRESH ; Pixels set with fast-refresh are easy to clear, disrupted by sunlight board_build.ldscript = src/platform/nrf52/nrf52840_s140_v7.ld -build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/seeed_wio_tracker_L1_eink> ${inkhud.build_src_filter} +build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/seeed_wio_tracker_L1_eink> lib_deps = - ${inkhud.lib_deps} ${nrf52840_base.lib_deps} + https://github.com/meshtastic/GxEPD2#b202ebfec6a4821e098cf7a625ba0f6f2400292d debug_tool = jlink + +[env:seeed_wio_tracker_L1_eink-inkhud] +board = seeed_wio_tracker_L1 +extends = nrf52840_base, inkhud +build_flags = + ${nrf52840_base.build_flags} + ${inkhud.build_flags} + -I variants/nrf52840/seeed_wio_tracker_L1_eink + -D SEEED_WIO_TRACKER_L1 + -D BUTTON_PIN=D13 +board_build.ldscript = src/platform/nrf52/nrf52840_s140_v7.ld +build_src_filter = + ${nrf52_base.build_src_filter} + ${inkhud.build_src_filter} + +<../variants/nrf52840/seeed_wio_tracker_L1_eink> +lib_deps = + ${inkhud.lib_deps} ; Before base libs_deps, so we use ZinggJM/GFXRoot instead of AdafruitGFX (saves space) + ${nrf52840_base.lib_deps} +debug_tool = jlink \ No newline at end of file diff --git a/variants/nrf52840/seeed_wio_tracker_L1_eink/variant.h b/variants/nrf52840/seeed_wio_tracker_L1_eink/variant.h index 98a7b2c39..f33d200b1 100644 --- a/variants/nrf52840/seeed_wio_tracker_L1_eink/variant.h +++ b/variants/nrf52840/seeed_wio_tracker_L1_eink/variant.h @@ -33,17 +33,10 @@ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ // Button Configuration // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - -#ifdef BUTTON_PIN -#undef BUTTON_PIN -#endif - -#define BUTTON_PIN D13 // This is the Program Button +#define CANCEL_BUTTON_PIN D13 // This is the Program Button // #define BUTTON_NEED_PULLUP 1 -#define BUTTON_ACTIVE_LOW true -#define BUTTON_ACTIVE_PULLUP false - -#define BUTTON_PIN_TOUCH 13 // Touch button +#define CANCEL_BUTTON_ACTIVE_LOW true +#define CANCEL_BUTTON_ACTIVE_PULLUP false // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ // Digital Pin Mapping (D0-D10) // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ @@ -116,7 +109,7 @@ static const uint8_t SCL = PIN_WIRE_SCL; #define PIN_EINK_SCLK 31 #define PIN_EINK_MOSI 33 #define PIN_EINK_EN 14 // unused -#define PIN_SPI1_MISO 15 // unused +#define PIN_SPI1_MISO -1 // 15 unused #define PIN_SPI1_MOSI PIN_EINK_MOSI #define PIN_SPI1_SCK PIN_EINK_SCLK @@ -175,7 +168,17 @@ static const uint8_t SCL = PIN_WIRE_SCL; // joystick // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +// trackball +#define HAS_TRACKBALL 1 +#define TB_UP 25 +#define TB_DOWN 26 +#define TB_LEFT 27 +#define TB_RIGHT 28 +#define TB_PRESS 29 +#define TB_DIRECTION FALLING + #define CANNED_MESSAGE_MODULE_ENABLE 1 +#define CANNED_MESSAGE_ADD_CONFIRMATION 1 // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ // Compatibility Definitions From 0903ed8232d6693c5f3aac948760b3f8ce294a14 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Wed, 27 Aug 2025 06:02:54 -0500 Subject: [PATCH 024/691] Mesh solar integrate (#7764) * Added HELTEC MeshSolar board. (#7499) * Added HELTEC MeshSolar board. * Set emergency shutdown pin as high impedance * Set emergency shutdown pin as high impedance Set emergency shutdown pin as high impedance * Update variants/nrf52840/heltec_mesh_solar/variant.h Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update variants/nrf52840/heltec_mesh_solar/variant.h Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update variants/nrf52840/heltec_mesh_solar/variant.h Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update I2C SCL pin definition in variant.h --------- Co-authored-by: Ben Meadors Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Updates --------- Co-authored-by: Quency-D <55523105+Quency-D@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- boards/heltec_mesh_solar.json | 54 ++++++ src/Power.cpp | 74 +++++++++ src/SerialConsole.cpp | 8 + src/mesh/StreamAPI.cpp | 119 ++++++++----- src/mesh/StreamAPI.h | 3 + src/modules/SerialModule.cpp | 23 ++- src/platform/nrf52/architecture.h | 2 + src/platform/nrf52/main-nrf52.cpp | 2 +- src/power.h | 2 + .../nrf52840/heltec_mesh_solar/platformio.ini | 19 +++ .../nrf52840/heltec_mesh_solar/variant.cpp | 36 ++++ variants/nrf52840/heltec_mesh_solar/variant.h | 157 ++++++++++++++++++ 12 files changed, 452 insertions(+), 47 deletions(-) create mode 100644 boards/heltec_mesh_solar.json create mode 100644 variants/nrf52840/heltec_mesh_solar/platformio.ini create mode 100644 variants/nrf52840/heltec_mesh_solar/variant.cpp create mode 100644 variants/nrf52840/heltec_mesh_solar/variant.h diff --git a/boards/heltec_mesh_solar.json b/boards/heltec_mesh_solar.json new file mode 100644 index 000000000..9e551c082 --- /dev/null +++ b/boards/heltec_mesh_solar.json @@ -0,0 +1,54 @@ +{ + "build": { + "arduino": { + "ldscript": "nrf52840_s140_v6.ld" + }, + "core": "nRF5", + "cpu": "cortex-m4", + "extra_flags": "-DNRF52840_XXAA", + "f_cpu": "64000000L", + "hwids": [ + ["0x239A", "0x4405"], + ["0x239A", "0x0029"], + ["0x239A", "0x002A"], + ["0x239A", "0x0071"] + ], + "usb_product": "HT-n5262", + "mcu": "nrf52840", + "variant": "heltec_mesh_solar", + "variants_dir": "variants", + "bsp": { + "name": "adafruit" + }, + "softdevice": { + "sd_flags": "-DS140", + "sd_name": "s140", + "sd_version": "6.1.1", + "sd_fwid": "0x00B6" + }, + "bootloader": { + "settings_addr": "0xFF000" + } + }, + "connectivity": ["bluetooth"], + "debug": { + "jlink_device": "nRF52840_xxAA", + "onboard_tools": ["jlink"], + "svd_path": "nrf52840.svd", + "openocd_target": "nrf52840-mdk-rs" + }, + "frameworks": ["arduino"], + "name": "Heltec nrf (Adafruit BSP)", + "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": "https://heltec.org/project/meshsolar/", + "vendor": "Heltec" +} diff --git a/src/Power.cpp b/src/Power.cpp index 8a16132f1..bf74f6e53 100644 --- a/src/Power.cpp +++ b/src/Power.cpp @@ -681,6 +681,8 @@ bool Power::setup() found = true; } else if (lipoChargerInit()) { found = true; + } else if (meshSolarInit()) { + found = true; } else if (analogInit()) { found = true; } @@ -1450,3 +1452,75 @@ bool Power::lipoChargerInit() return false; } #endif + + + +#ifdef HELTEC_MESH_SOLAR +#include "meshSolarApp.h" + +/** + * meshSolar class for an SMBUS battery sensor. + */ +class meshSolarBatteryLevel : public HasBatteryLevel +{ + + public: + /** + * Init the I2C meshSolar battery level sensor + */ + bool runOnce() + { + meshSolarStart(); + return true; + } + + /** + * Battery state of charge, from 0 to 100 or -1 for unknown + */ + virtual int getBatteryPercent() override { return meshSolarGetBatteryPercent(); } + + /** + * The raw voltage of the battery in millivolts, or NAN if unknown + */ + virtual uint16_t getBattVoltage() override { return meshSolarGetBattVoltage(); } + + /** + * return true if there is a battery installed in this unit + */ + virtual bool isBatteryConnect() override { return meshSolarIsBatteryConnect(); } + + /** + * return true if there is an external power source detected + */ + virtual bool isVbusIn() override { return meshSolarIsVbusIn();} + + /** + * return true if the battery is currently charging + */ + virtual bool isCharging() override { return meshSolarIsCharging(); } +}; + +meshSolarBatteryLevel meshSolarLevel; + +/** + * Init the meshSolar battery level sensor + */ +bool Power::meshSolarInit() +{ + bool result = meshSolarLevel.runOnce(); + LOG_DEBUG("Power::meshSolarInit mesh solar sensor is %s", result ? "ready" : "not ready yet"); + if (!result) + return false; + batteryLevel = &meshSolarLevel; + return true; +} + +#else +/** + * The meshSolar battery level sensor is unavailable - default to AnalogBatteryLevel + */ +bool Power::meshSolarInit() +{ + return false; +} +#endif \ No newline at end of file diff --git a/src/SerialConsole.cpp b/src/SerialConsole.cpp index 68c41980d..093a24678 100644 --- a/src/SerialConsole.cpp +++ b/src/SerialConsole.cpp @@ -64,6 +64,14 @@ SerialConsole::SerialConsole() : StreamAPI(&Port), RedirectablePrint(&Port), con int32_t SerialConsole::runOnce() { +#ifdef HELTEC_MESH_SOLAR + //After enabling the mesh solar serial port module configuration, command processing is handled by the serial port module. + if(moduleConfig.serial.enabled && moduleConfig.serial.override_console_serial_port + && moduleConfig.serial.mode==meshtastic_ModuleConfig_SerialConfig_Serial_Mode_MS_CONFIG) + { + return 250; + } +#endif return runOncePart(); } diff --git a/src/mesh/StreamAPI.cpp b/src/mesh/StreamAPI.cpp index 4a42e5197..3d652b6d6 100644 --- a/src/mesh/StreamAPI.cpp +++ b/src/mesh/StreamAPI.cpp @@ -15,9 +15,65 @@ int32_t StreamAPI::runOncePart() checkConnectionTimeout(); return result; } +int32_t StreamAPI::runOncePart(char *buf, uint16_t bufLen) +{ + auto result = readStream(buf, bufLen); + writeStream(); + checkConnectionTimeout(); + return result; +} + +int32_t StreamAPI::handleRecStream(char *buf, uint16_t bufLen) +{ + uint16_t index = 0; + while (bufLen > index) { // Currently we never want to block + int cInt = buf[index++]; + if (cInt < 0) + break; // We ran out of characters (even though available said otherwise) - this can happen on rf52 adafruit + // arduino + + uint8_t c = (uint8_t)cInt; + + // Use the read pointer for a little state machine, first look for framing, then length bytes, then payload + size_t ptr = rxPtr; + + rxPtr++; // assume we will probably advance the rxPtr + rxBuf[ptr] = c; // store all bytes (including framing) + + // console->printf("rxPtr %d ptr=%d c=0x%x\n", rxPtr, ptr, c); + + if (ptr == 0) { // looking for START1 + if (c != START1) + rxPtr = 0; // failed to find framing + } else if (ptr == 1) { // looking for START2 + if (c != START2) + rxPtr = 0; // failed to find framing + } else if (ptr >= HEADER_LEN - 1) { // we have at least read our 4 byte framing + uint32_t len = (rxBuf[2] << 8) + rxBuf[3]; // big endian 16 bit length follows framing + + // console->printf("len %d\n", len); + + if (ptr == HEADER_LEN - 1) { + // we _just_ finished our 4 byte header, validate length now (note: a length of zero is a valid + // protobuf also) + if (len > MAX_TO_FROM_RADIO_SIZE) + rxPtr = 0; // length is bogus, restart search for framing + } + + if (rxPtr != 0) // Is packet still considered 'good'? + if (ptr + 1 >= len + HEADER_LEN) { // have we received all of the payload? + rxPtr = 0; // start over again on the next packet + + // If we didn't just fail the packet and we now have the right # of bytes, parse it + handleToRadio(rxBuf + HEADER_LEN, len); + } + } + } + return 0; +} /** - * Read any rx chars from the link and call handleToRadio + * Read any rx chars from the link and call handleRecStream */ int32_t StreamAPI::readStream() { @@ -26,50 +82,29 @@ int32_t StreamAPI::readStream() bool recentRx = Throttle::isWithinTimespanMs(lastRxMsec, 2000); return recentRx ? 5 : 250; } else { + char buf[1]; while (stream->available()) { // Currently we never want to block - int cInt = stream->read(); - if (cInt < 0) - break; // We ran out of characters (even though available said otherwise) - this can happen on rf52 adafruit - // arduino - - uint8_t c = (uint8_t)cInt; - - // Use the read pointer for a little state machine, first look for framing, then length bytes, then payload - size_t ptr = rxPtr; - - rxPtr++; // assume we will probably advance the rxPtr - rxBuf[ptr] = c; // store all bytes (including framing) - - // console->printf("rxPtr %d ptr=%d c=0x%x\n", rxPtr, ptr, c); - - if (ptr == 0) { // looking for START1 - if (c != START1) - rxPtr = 0; // failed to find framing - } else if (ptr == 1) { // looking for START2 - if (c != START2) - rxPtr = 0; // failed to find framing - } else if (ptr >= HEADER_LEN - 1) { // we have at least read our 4 byte framing - uint32_t len = (rxBuf[2] << 8) + rxBuf[3]; // big endian 16 bit length follows framing - - // console->printf("len %d\n", len); - - if (ptr == HEADER_LEN - 1) { - // we _just_ finished our 4 byte header, validate length now (note: a length of zero is a valid - // protobuf also) - if (len > MAX_TO_FROM_RADIO_SIZE) - rxPtr = 0; // length is bogus, restart search for framing - } - - if (rxPtr != 0) // Is packet still considered 'good'? - if (ptr + 1 >= len + HEADER_LEN) { // have we received all of the payload? - rxPtr = 0; // start over again on the next packet - - // If we didn't just fail the packet and we now have the right # of bytes, parse it - handleToRadio(rxBuf + HEADER_LEN, len); - } - } + buf[0] = stream->read(); + handleRecStream(buf, 1); } + // we had bytes available this time, so assume we might have them next time also + lastRxMsec = millis(); + return 0; + } +} +/** + * Read any rx chars from the link and call handleRecStream + */ +int32_t StreamAPI::readStream(char *buf, uint16_t bufLen) +{ + uint16_t index = 0; + if (bufLen < 1) { + // Nothing available this time, if the computer has talked to us recently, poll often, otherwise let CPU sleep a long time + bool recentRx = Throttle::isWithinTimespanMs(lastRxMsec, 2000); + return recentRx ? 5 : 250; + } else { + handleRecStream(buf, bufLen); // we had bytes available this time, so assume we might have them next time also lastRxMsec = millis(); return 0; diff --git a/src/mesh/StreamAPI.h b/src/mesh/StreamAPI.h index 6e0364bc1..547dd0175 100644 --- a/src/mesh/StreamAPI.h +++ b/src/mesh/StreamAPI.h @@ -50,12 +50,15 @@ class StreamAPI : public PhoneAPI * phone. */ virtual int32_t runOncePart(); + virtual int32_t runOncePart(char *buf,uint16_t bufLen); private: /** * Read any rx chars from the link and call handleToRadio */ int32_t readStream(); + int32_t readStream(char *buf,uint16_t bufLen); + int32_t handleRecStream(char *buf,uint16_t bufLen); /** * call getFromRadio() and deliver encapsulated packets to the Stream diff --git a/src/modules/SerialModule.cpp b/src/modules/SerialModule.cpp index 866497ecc..a2dbb07d3 100644 --- a/src/modules/SerialModule.cpp +++ b/src/modules/SerialModule.cpp @@ -45,6 +45,9 @@ */ +#ifdef HELTEC_MESH_SOLAR +#include "meshSolarApp.h" +#endif #if (defined(ARCH_ESP32) || defined(ARCH_NRF52) || defined(ARCH_RP2040)) && !defined(CONFIG_IDF_TARGET_ESP32S2) && \ !defined(CONFIG_IDF_TARGET_ESP32C3) @@ -60,8 +63,9 @@ SerialModule *serialModule; SerialModuleRadio *serialModuleRadio; -#if defined(TTGO_T_ECHO) || defined(T_ECHO_LITE) || defined(CANARYONE) || defined(MESHLINK) || defined(ELECROW_ThinkNode_M1) || \ - defined(ELECROW_ThinkNode_M5) + +#if defined(TTGO_T_ECHO) || defined(CANARYONE) || defined(MESHLINK) || defined(ELECROW_ThinkNode_M1) || \ + defined(ELECROW_ThinkNode_M5) || defined(HELTEC_MESH_SOLAR) SerialModule::SerialModule() : StreamAPI(&Serial), concurrency::OSThread("Serial") {} static Print *serialPrint = &Serial; #elif defined(CONFIG_IDF_TARGET_ESP32C6) @@ -78,7 +82,8 @@ size_t serialPayloadSize; bool SerialModule::isValidConfig(const meshtastic_ModuleConfig_SerialConfig &config) { if (config.override_console_serial_port && !IS_ONE_OF(config.mode, meshtastic_ModuleConfig_SerialConfig_Serial_Mode_NMEA, - meshtastic_ModuleConfig_SerialConfig_Serial_Mode_CALTOPO)) { + meshtastic_ModuleConfig_SerialConfig_Serial_Mode_CALTOPO, + meshtastic_ModuleConfig_SerialConfig_Serial_Mode_MS_CONFIG)) { const char *warning = "Invalid Serial config: override console serial port is only supported in NMEA and CalTopo output-only modes."; LOG_ERROR(warning); @@ -241,7 +246,17 @@ int32_t SerialModule::runOnce() else if ((moduleConfig.serial.mode == meshtastic_ModuleConfig_SerialConfig_Serial_Mode_WS85)) { processWXSerial(); - } else { + } +#if defined(HELTEC_MESH_SOLAR) + else if ((moduleConfig.serial.mode == meshtastic_ModuleConfig_SerialConfig_Serial_Mode_MS_CONFIG)) { + serialPayloadSize = Serial.readBytes(serialBytes, sizeof(serialBytes)-1); + //If the parsing fails, the following parsing will be performed. + if((serialPayloadSize > 0) && (meshSolarCmdHandle(serialBytes)!=0)) { + return runOncePart(serialBytes,serialPayloadSize); + } + } +#endif + else { #if defined(CONFIG_IDF_TARGET_ESP32C6) while (Serial1.available()) { serialPayloadSize = Serial1.readBytes(serialBytes, meshtastic_Constants_DATA_PAYLOAD_LEN); diff --git a/src/platform/nrf52/architecture.h b/src/platform/nrf52/architecture.h index 064bd8ef0..c9938062e 100644 --- a/src/platform/nrf52/architecture.h +++ b/src/platform/nrf52/architecture.h @@ -98,6 +98,8 @@ #define HW_VENDOR meshtastic_HardwareModel_SEEED_WIO_TRACKER_L1_EINK #elif defined(SEEED_WIO_TRACKER_L1) #define HW_VENDOR meshtastic_HardwareModel_SEEED_WIO_TRACKER_L1 +#elif defined(HELTEC_MESH_SOLAR) +#define HW_VENDOR meshtastic_HardwareModel_HELTEC_MESH_SOLAR #else #define HW_VENDOR meshtastic_HardwareModel_NRF52_UNKNOWN #endif diff --git a/src/platform/nrf52/main-nrf52.cpp b/src/platform/nrf52/main-nrf52.cpp index 590d2f0ae..8ce74d5f7 100644 --- a/src/platform/nrf52/main-nrf52.cpp +++ b/src/platform/nrf52/main-nrf52.cpp @@ -323,7 +323,7 @@ void cpuDeepSleep(uint32_t msecToWake) #endif #endif -#ifdef HELTEC_MESH_NODE_T114 +#if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_MESH_SOLAR) nrf_gpio_cfg_default(PIN_GPS_PPS); detachInterrupt(PIN_GPS_PPS); detachInterrupt(PIN_BUTTON1); diff --git a/src/power.h b/src/power.h index 1c078c06d..e96f5b022 100644 --- a/src/power.h +++ b/src/power.h @@ -128,6 +128,8 @@ class Power : private concurrency::OSThread bool lipoInit(); /// Setup a Lipo charger bool lipoChargerInit(); + /// Setup a meshSolar battery sensor + bool meshSolarInit(); private: void shutdown(); diff --git a/variants/nrf52840/heltec_mesh_solar/platformio.ini b/variants/nrf52840/heltec_mesh_solar/platformio.ini new file mode 100644 index 000000000..65d26dc40 --- /dev/null +++ b/variants/nrf52840/heltec_mesh_solar/platformio.ini @@ -0,0 +1,19 @@ +; First prototype nrf52840/sx1262 device +[env:heltec-mesh-solar] +extends = nrf52840_base +board = heltec_mesh_solar +board_level = pr +debug_tool = jlink + +# add -DCFG_SYSVIEW if you want to use the Segger systemview tool for OS profiling. +build_flags = ${nrf52840_base.build_flags} + -Ivariants/nrf52840/heltec_mesh_solar + -DGPS_POWER_TOGGLE + -DHELTEC_MESH_SOLAR + +build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/heltec_mesh_solar> +lib_deps = + ${nrf52840_base.lib_deps} + https://github.com/NMIoT/meshsolar/archive/dfc5330dad443982e6cdd37a61d33fc7252f468b.zip + lewisxhe/PCF8563_Library@^1.0.1 + ArduinoJson@6.21.4 diff --git a/variants/nrf52840/heltec_mesh_solar/variant.cpp b/variants/nrf52840/heltec_mesh_solar/variant.cpp new file mode 100644 index 000000000..8236d7cf4 --- /dev/null +++ b/variants/nrf52840/heltec_mesh_solar/variant.cpp @@ -0,0 +1,36 @@ +/* + 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 - pins 0 and 1 are hardwired for xtal and should never be enabled + 0xff, 0xff, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + + // P1 + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47}; + +void initVariant() +{ + pinMode(BQ4050_EMERGENCY_SHUTDOWN_PIN, INPUT); +} diff --git a/variants/nrf52840/heltec_mesh_solar/variant.h b/variants/nrf52840/heltec_mesh_solar/variant.h new file mode 100644 index 000000000..33c2b2556 --- /dev/null +++ b/variants/nrf52840/heltec_mesh_solar/variant.h @@ -0,0 +1,157 @@ +/* + 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_HELTEC_NRF_ +#define _VARIANT_HELTEC_NRF_ +/** Master clock frequency */ +#define VARIANT_MCK (64000000ul) + +#define USE_LFXO // Board uses 32khz crystal for LF + +/*---------------------------------------------------------------------------- + * Headers + *----------------------------------------------------------------------------*/ + +#include "WVariant.h" + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +// Number of pins defined in PinDescription array +#define PINS_COUNT (48) +#define NUM_DIGITAL_PINS (48) +#define NUM_ANALOG_INPUTS (1) +#define NUM_ANALOG_OUTPUTS (0) + + +#define PIN_LED1 (0 + 12) // green (confirmed on 1.0 board) +#define LED_BLUE PIN_LED1 // fake for bluefruit library +#define LED_GREEN PIN_LED1 +#define LED_BUILTIN LED_GREEN +#define LED_STATE_ON 0 // State when LED is lit + +#define HAS_NEOPIXEL // Enable the use of neopixels +#define NEOPIXEL_COUNT 1 // How many neopixels are connected +#define NEOPIXEL_DATA (32+15) // gpio pin used to send data to the neopixels +#define NEOPIXEL_TYPE (NEO_GRB + NEO_KHZ800) // type of neopixels in use + +/* + * Buttons + */ +#define PIN_BUTTON1 (32 + 10) +// #define PIN_BUTTON2 (0 + 18) // 0.18 is labeled on the board as RESET but we configure it in the bootloader as a regular +// GPIO + +/* +No longer populated on PCB +*/ +#define PIN_SERIAL2_RX (0 + 9) +#define PIN_SERIAL2_TX (0 + 10) +// #define PIN_SERIAL2_EN (0 + 17) + +/* + * I2C + */ + +#define WIRE_INTERFACES_COUNT 2 + +// I2C bus 0 +// Routed to footprint for PCF8563TS RTC +// Not populated on T114 V1, maybe in future? +#define PIN_WIRE_SDA (0 + 6) // P0.26 +#define PIN_WIRE_SCL (0 + 26) // P0.26 + +// I2C bus 1 +// Available on header pins, for general use +#define PIN_WIRE1_SDA (0 + 30) // P0.30 +#define PIN_WIRE1_SCL (0 + 5) // P0.13 + +/* + * Lora radio + */ + +#define USE_SX1262 +// #define USE_SX1268 +#define SX126X_CS (0 + 24) // FIXME - we really should define LORA_CS instead +#define LORA_CS (0 + 24) +#define SX126X_DIO1 (0 + 20) +// Note DIO2 is attached internally to the module to an analog switch for TX/RX switching +// #define SX1262_DIO3 (0 + 21) +// This is used as an *output* from the sx1262 and connected internally to power the tcxo, do not drive from the +// main +// CPU? +#define SX126X_BUSY (0 + 17) +#define SX126X_RESET (0 + 25) +// Not really an E22 but TTGO seems to be trying to clone that +#define SX126X_DIO2_AS_RF_SWITCH +#define SX126X_DIO3_TCXO_VOLTAGE 1.8 + +/* + * GPS pins + */ + +#define GPS_L76K + +// #define PIN_GPS_RESET (32 + 6) // An output to reset L76K GPS. As per datasheet, low for > 100ms will reset the L76K +// #define GPS_RESET_MODE LOW +// #define PIN_GPS_EN (21) +#define PERIPHERAL_WARMUP_MS 1000 // Make sure I2C QuickLink has stable power before continuing +#define VEXT_ON_VALUE HIGH +// #define GPS_EN_ACTIVE HIGH +#define PIN_GPS_STANDBY (32 + 2) // An output to wake GPS, low means allow sleep, high means force wake +#define PIN_GPS_PPS (32 + 4) +// Seems to be missing on this new board +// #define PIN_GPS_PPS (32 + 4) // Pulse per second input from the GPS +#define GPS_TX_PIN (32 + 5) // This is for bits going TOWARDS the CPU +#define GPS_RX_PIN (32 + 7) // This is for bits going TOWARDS the GPS + +#define GPS_THREAD_INTERVAL 50 + +#define PIN_SERIAL1_RX GPS_TX_PIN +#define PIN_SERIAL1_TX GPS_RX_PIN + +/* + * SPI Interfaces + */ +#define SPI_INTERFACES_COUNT 1 + +// For LORA, spi 0 +#define PIN_SPI_MISO (0 + 23) +#define PIN_SPI_MOSI (0 + 22) +#define PIN_SPI_SCK (0 + 19) + +// #define PIN_PWR_EN (0 + 6) + +// To debug via the segger JLINK console rather than the CDC-ACM serial device +// #define USE_SEGGER + +#define BQ4050_SDA_PIN (32+1) // I2C data line pin +#define BQ4050_SCL_PIN (32+0) // I2C clock line pin +#define BQ4050_EMERGENCY_SHUTDOWN_PIN (32+3) // Emergency shutdown pin + +#define HAS_RTC 0 +#ifdef __cplusplus +} +#endif + +/*---------------------------------------------------------------------------- + * Arduino objects - C++ only + *----------------------------------------------------------------------------*/ + +#endif From 3120bb8fd77bd3dc340ca14b923a0847b95f6ad4 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Wed, 27 Aug 2025 06:50:53 -0500 Subject: [PATCH 025/691] Fix check --- src/input/RotaryEncoderImpl.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/input/RotaryEncoderImpl.cpp b/src/input/RotaryEncoderImpl.cpp index b71e800e0..d3fcbbf9d 100644 --- a/src/input/RotaryEncoderImpl.cpp +++ b/src/input/RotaryEncoderImpl.cpp @@ -8,7 +8,10 @@ RotaryEncoderImpl *rotaryEncoderImpl; -RotaryEncoderImpl::RotaryEncoderImpl() : concurrency::OSThread(ORIGIN_NAME), originName(ORIGIN_NAME) {} +RotaryEncoderImpl::RotaryEncoderImpl() : concurrency::OSThread(ORIGIN_NAME), originName(ORIGIN_NAME) +{ + rotary = nullptr; +} bool RotaryEncoderImpl::init() { From 06bccef46204d073f72c7b98b9f5e9e3cc424b64 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Wed, 27 Aug 2025 07:17:46 -0500 Subject: [PATCH 026/691] Reinstitute previous streamapi readStream --- src/mesh/StreamAPI.cpp | 80 +++++++++++++++++++++++------------------- 1 file changed, 43 insertions(+), 37 deletions(-) diff --git a/src/mesh/StreamAPI.cpp b/src/mesh/StreamAPI.cpp index 3d652b6d6..a45e11ac3 100644 --- a/src/mesh/StreamAPI.cpp +++ b/src/mesh/StreamAPI.cpp @@ -73,7 +73,7 @@ int32_t StreamAPI::handleRecStream(char *buf, uint16_t bufLen) } /** - * Read any rx chars from the link and call handleRecStream + * Read any rx chars from the link and call handleToRadio */ int32_t StreamAPI::readStream() { @@ -82,50 +82,56 @@ int32_t StreamAPI::readStream() bool recentRx = Throttle::isWithinTimespanMs(lastRxMsec, 2000); return recentRx ? 5 : 250; } else { - char buf[1]; while (stream->available()) { // Currently we never want to block - buf[0] = stream->read(); - handleRecStream(buf, 1); + int cInt = stream->read(); + if (cInt < 0) + break; // We ran out of characters (even though available said otherwise) - this can happen on rf52 adafruit + // arduino + + uint8_t c = (uint8_t)cInt; + + // Use the read pointer for a little state machine, first look for framing, then length bytes, then payload + size_t ptr = rxPtr; + + rxPtr++; // assume we will probably advance the rxPtr + rxBuf[ptr] = c; // store all bytes (including framing) + + // console->printf("rxPtr %d ptr=%d c=0x%x\n", rxPtr, ptr, c); + + if (ptr == 0) { // looking for START1 + if (c != START1) + rxPtr = 0; // failed to find framing + } else if (ptr == 1) { // looking for START2 + if (c != START2) + rxPtr = 0; // failed to find framing + } else if (ptr >= HEADER_LEN - 1) { // we have at least read our 4 byte framing + uint32_t len = (rxBuf[2] << 8) + rxBuf[3]; // big endian 16 bit length follows framing + + // console->printf("len %d\n", len); + + if (ptr == HEADER_LEN - 1) { + // we _just_ finished our 4 byte header, validate length now (note: a length of zero is a valid + // protobuf also) + if (len > MAX_TO_FROM_RADIO_SIZE) + rxPtr = 0; // length is bogus, restart search for framing + } + + if (rxPtr != 0) // Is packet still considered 'good'? + if (ptr + 1 >= len + HEADER_LEN) { // have we received all of the payload? + rxPtr = 0; // start over again on the next packet + + // If we didn't just fail the packet and we now have the right # of bytes, parse it + handleToRadio(rxBuf + HEADER_LEN, len); + } + } } + // we had bytes available this time, so assume we might have them next time also lastRxMsec = millis(); return 0; } } -/** - * Read any rx chars from the link and call handleRecStream - */ -int32_t StreamAPI::readStream(char *buf, uint16_t bufLen) -{ - uint16_t index = 0; - if (bufLen < 1) { - // Nothing available this time, if the computer has talked to us recently, poll often, otherwise let CPU sleep a long time - bool recentRx = Throttle::isWithinTimespanMs(lastRxMsec, 2000); - return recentRx ? 5 : 250; - } else { - handleRecStream(buf, bufLen); - // we had bytes available this time, so assume we might have them next time also - lastRxMsec = millis(); - return 0; - } -} - -/** - * call getFromRadio() and deliver encapsulated packets to the Stream - */ -void StreamAPI::writeStream() -{ - if (canWrite) { - uint32_t len; - do { - // Send every packet we can - len = getFromRadio(txBuf + HEADER_LEN); - emitTxBuffer(len); - } while (len); - } -} - /** * Send the current txBuffer over our stream */ From 237b8908f75bab38c18cc8ec735b86ea59983b76 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Wed, 27 Aug 2025 09:54:39 -0500 Subject: [PATCH 027/691] Chainsaw took too much off the top --- src/mesh/StreamAPI.cpp | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/mesh/StreamAPI.cpp b/src/mesh/StreamAPI.cpp index a45e11ac3..20026767e 100644 --- a/src/mesh/StreamAPI.cpp +++ b/src/mesh/StreamAPI.cpp @@ -15,6 +15,7 @@ int32_t StreamAPI::runOncePart() checkConnectionTimeout(); return result; } + int32_t StreamAPI::runOncePart(char *buf, uint16_t bufLen) { auto result = readStream(buf, bufLen); @@ -23,6 +24,38 @@ int32_t StreamAPI::runOncePart(char *buf, uint16_t bufLen) return result; } +/** + * Read any rx chars from the link and call handleRecStream + */ +int32_t StreamAPI::readStream(char *buf, uint16_t bufLen) +{ + if (bufLen < 1) { + // Nothing available this time, if the computer has talked to us recently, poll often, otherwise let CPU sleep a long time + bool recentRx = Throttle::isWithinTimespanMs(lastRxMsec, 2000); + return recentRx ? 5 : 250; + } else { + handleRecStream(buf, bufLen); + // we had bytes available this time, so assume we might have them next time also + lastRxMsec = millis(); + return 0; + } +} + +/** + * call getFromRadio() and deliver encapsulated packets to the Stream + */ +void StreamAPI::writeStream() +{ + if (canWrite) { + uint32_t len; + do { + // Send every packet we can + len = getFromRadio(txBuf + HEADER_LEN); + emitTxBuffer(len); + } while (len); + } +} + int32_t StreamAPI::handleRecStream(char *buf, uint16_t bufLen) { uint16_t index = 0; From 26c38ffc8e3f181e004445a1e86d4da3ecbaf1da Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Wed, 27 Aug 2025 11:55:27 -0500 Subject: [PATCH 028/691] Remove debug logging --- variants/esp32s3/unphone/platformio.ini | 2 -- 1 file changed, 2 deletions(-) diff --git a/variants/esp32s3/unphone/platformio.ini b/variants/esp32s3/unphone/platformio.ini index ecb1cbd67..f6c3e2855 100644 --- a/variants/esp32s3/unphone/platformio.ini +++ b/variants/esp32s3/unphone/platformio.ini @@ -52,8 +52,6 @@ build_flags = -D LV_USE_PERF_MONITOR=0 -D LV_USE_MEM_MONITOR=0 -D LV_USE_LOG=0 - -D USE_LOG_DEBUG - -D LOG_DEBUG_INC=\"DebugConfiguration.h\" -D LGFX_SCREEN_WIDTH=320 -D LGFX_SCREEN_HEIGHT=480 -D DISPLAY_SIZE=320x480 ; portrait mode From d21d6d208542b8ca645333e67e3b1e743811eb75 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 27 Aug 2025 13:53:34 -0500 Subject: [PATCH 029/691] Update meshtastic/device-ui digest to a3e0e1b (#7766) 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 543205996..ef0fef791 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/0f32b64dca418c6465763ec576509a6a2bfbc50a.zip + https://github.com/meshtastic/device-ui/archive/a3e0e1be372d069f47b4c19d718f5267251744d7.zip ; Common libs for environmental measurements in telemetry module [environmental_base] From a4d96bebfbe8c699fdef8662a0e28fa8a0c6014b Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Wed, 27 Aug 2025 14:35:29 -0500 Subject: [PATCH 030/691] Drop for now --- variants/esp32s3/unphone/platformio.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/variants/esp32s3/unphone/platformio.ini b/variants/esp32s3/unphone/platformio.ini index f6c3e2855..476858ff5 100644 --- a/variants/esp32s3/unphone/platformio.ini +++ b/variants/esp32s3/unphone/platformio.ini @@ -32,6 +32,7 @@ lib_deps = ${esp32s3_base.lib_deps} [env:unphone-tft] +board_level = extra extends = env:unphone build_flags = ${env:unphone.build_flags} From 25a19b49ad9a20fd8296b120183154976b46c0cc Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Wed, 27 Aug 2025 15:18:26 -0500 Subject: [PATCH 031/691] This one is not working yet --- variants/esp32s3/tlora-pager/platformio.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/variants/esp32s3/tlora-pager/platformio.ini b/variants/esp32s3/tlora-pager/platformio.ini index 3d77d879c..b16e516a7 100644 --- a/variants/esp32s3/tlora-pager/platformio.ini +++ b/variants/esp32s3/tlora-pager/platformio.ini @@ -29,6 +29,7 @@ lib_deps = ${esp32s3_base.lib_deps} https://github.com/mverch67/RotaryEncoder [env:tlora-pager-tft] +board_level = extra extends = env:tlora-pager build_flags = ${env:tlora-pager.build_flags} From 834c3c5cc2be305cf3ee465c600e4282e0928f46 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Wed, 27 Aug 2025 16:24:57 -0500 Subject: [PATCH 032/691] Add this back in --- src/modules/SerialModule.cpp | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/modules/SerialModule.cpp b/src/modules/SerialModule.cpp index a2dbb07d3..880768839 100644 --- a/src/modules/SerialModule.cpp +++ b/src/modules/SerialModule.cpp @@ -45,7 +45,7 @@ */ -#ifdef HELTEC_MESH_SOLAR +#ifdef HELTEC_MESH_SOLAR #include "meshSolarApp.h" #endif @@ -63,9 +63,8 @@ SerialModule *serialModule; SerialModuleRadio *serialModuleRadio; - #if defined(TTGO_T_ECHO) || defined(CANARYONE) || defined(MESHLINK) || defined(ELECROW_ThinkNode_M1) || \ - defined(ELECROW_ThinkNode_M5) || defined(HELTEC_MESH_SOLAR) + 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) @@ -83,7 +82,7 @@ bool SerialModule::isValidConfig(const meshtastic_ModuleConfig_SerialConfig &con { if (config.override_console_serial_port && !IS_ONE_OF(config.mode, meshtastic_ModuleConfig_SerialConfig_Serial_Mode_NMEA, meshtastic_ModuleConfig_SerialConfig_Serial_Mode_CALTOPO, - meshtastic_ModuleConfig_SerialConfig_Serial_Mode_MS_CONFIG)) { + meshtastic_ModuleConfig_SerialConfig_Serial_Mode_MS_CONFIG)) { const char *warning = "Invalid Serial config: override console serial port is only supported in NMEA and CalTopo output-only modes."; LOG_ERROR(warning); @@ -249,11 +248,11 @@ int32_t SerialModule::runOnce() } #if defined(HELTEC_MESH_SOLAR) else if ((moduleConfig.serial.mode == meshtastic_ModuleConfig_SerialConfig_Serial_Mode_MS_CONFIG)) { - serialPayloadSize = Serial.readBytes(serialBytes, sizeof(serialBytes)-1); - //If the parsing fails, the following parsing will be performed. - if((serialPayloadSize > 0) && (meshSolarCmdHandle(serialBytes)!=0)) { - return runOncePart(serialBytes,serialPayloadSize); - } + serialPayloadSize = Serial.readBytes(serialBytes, sizeof(serialBytes) - 1); + // If the parsing fails, the following parsing will be performed. + if ((serialPayloadSize > 0) && (meshSolarCmdHandle(serialBytes) != 0)) { + return runOncePart(serialBytes, serialPayloadSize); + } } #endif else { From 75b01e17bc6c8b39acfad6bdea9e4e5760722279 Mon Sep 17 00:00:00 2001 From: thebentern <9000580+thebentern@users.noreply.github.com> Date: Thu, 28 Aug 2025 10:33:29 +0000 Subject: [PATCH 033/691] Automated version bumps --- bin/org.meshtastic.meshtasticd.metainfo.xml | 3 +++ debian/changelog | 5 +++-- version.properties | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/bin/org.meshtastic.meshtasticd.metainfo.xml b/bin/org.meshtastic.meshtasticd.metainfo.xml index f3b3bb14d..1d97e2a66 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.7 + https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.6 diff --git a/debian/changelog b/debian/changelog index b36a22168..ff59db89e 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,4 +1,4 @@ -meshtasticd (2.7.6.0) UNRELEASED; urgency=medium +meshtasticd (2.7.7.0) UNRELEASED; urgency=medium [ Austin Lane ] * Initial packaging @@ -39,5 +39,6 @@ meshtasticd (2.7.6.0) UNRELEASED; urgency=medium [ ] * GitHub Actions Automatic version bump + * GitHub Actions Automatic version bump - -- Tue, 12 Aug 2025 23:48:48 +0000 + -- Ubuntu Thu, 28 Aug 2025 10:33:25 +0000 diff --git a/version.properties b/version.properties index f9e2cb279..2e5193d49 100644 --- a/version.properties +++ b/version.properties @@ -1,4 +1,4 @@ [VERSION] major = 2 minor = 7 -build = 6 +build = 7 From 6c7cff7de2a13f7665e98f85050d8df4d968cf3f Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Thu, 28 Aug 2025 06:02:24 -0500 Subject: [PATCH 034/691] Merge pull request #7777 from meshtastic/create-pull-request/bump-version Bump release version --- bin/org.meshtastic.meshtasticd.metainfo.xml | 3 +++ debian/changelog | 5 +++-- version.properties | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/bin/org.meshtastic.meshtasticd.metainfo.xml b/bin/org.meshtastic.meshtasticd.metainfo.xml index f3b3bb14d..1d97e2a66 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.7 + https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.6 diff --git a/debian/changelog b/debian/changelog index b36a22168..ff59db89e 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,4 +1,4 @@ -meshtasticd (2.7.6.0) UNRELEASED; urgency=medium +meshtasticd (2.7.7.0) UNRELEASED; urgency=medium [ Austin Lane ] * Initial packaging @@ -39,5 +39,6 @@ meshtasticd (2.7.6.0) UNRELEASED; urgency=medium [ ] * GitHub Actions Automatic version bump + * GitHub Actions Automatic version bump - -- Tue, 12 Aug 2025 23:48:48 +0000 + -- Ubuntu Thu, 28 Aug 2025 10:33:25 +0000 diff --git a/version.properties b/version.properties index f9e2cb279..2e5193d49 100644 --- a/version.properties +++ b/version.properties @@ -1,4 +1,4 @@ [VERSION] major = 2 minor = 7 -build = 6 +build = 7 From dd2f77ea0c68870f527c316d072149584dc6116f Mon Sep 17 00:00:00 2001 From: Jason P Date: Thu, 28 Aug 2025 11:23:24 -0500 Subject: [PATCH 035/691] BaseUI Show/Hide Frame Functionality (#7382) * Rename System Frame (from Memory) in code base * Create menu options to Show/Hide frames: Node Lists, Bearings, Position, LoRa, Clock and Favorites frames * Move Region Picker into submenu * Tweak wording for Send Position vs Node Info if the device has GPS --- src/graphics/Screen.cpp | 221 +++++++++++++++++++--------- src/graphics/Screen.h | 30 +++- src/graphics/draw/DebugRenderer.cpp | 2 +- src/graphics/draw/DebugRenderer.h | 4 +- src/graphics/draw/MenuHandler.cpp | 149 ++++++++++++++++++- src/graphics/draw/MenuHandler.h | 3 + src/graphics/images.h | 4 +- 7 files changed, 330 insertions(+), 83 deletions(-) diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index 9ef7cce86..76c423133 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -941,71 +941,86 @@ void Screen::setFrames(FrameFocus focus) } #if defined(DISPLAY_CLOCK_FRAME) - fsi.positions.clock = numframes; - normalFrames[numframes++] = uiconfig.is_clockface_analog ? graphics::ClockRenderer::drawAnalogClockFrame - : graphics::ClockRenderer::drawDigitalClockFrame; - indicatorIcons.push_back(digital_icon_clock); + if (!hiddenFrames.clock) { + fsi.positions.clock = numframes; + normalFrames[numframes++] = uiconfig.is_clockface_analog ? graphics::ClockRenderer::drawAnalogClockFrame + : graphics::ClockRenderer::drawDigitalClockFrame; + indicatorIcons.push_back(digital_icon_clock); + } #endif // Declare this early so it’s available in FOCUS_PRESERVE block bool willInsertTextMessage = shouldDrawMessage(&devicestate.rx_text_message); - fsi.positions.home = numframes; - normalFrames[numframes++] = graphics::UIRenderer::drawDeviceFocused; - indicatorIcons.push_back(icon_home); + if (!hiddenFrames.home) { + fsi.positions.home = numframes; + normalFrames[numframes++] = graphics::UIRenderer::drawDeviceFocused; + indicatorIcons.push_back(icon_home); + } fsi.positions.textMessage = numframes; normalFrames[numframes++] = graphics::MessageRenderer::drawTextMessageFrame; indicatorIcons.push_back(icon_mail); #ifndef USE_EINK - fsi.positions.nodelist = numframes; - normalFrames[numframes++] = graphics::NodeListRenderer::drawDynamicNodeListScreen; - indicatorIcons.push_back(icon_nodes); + if (!hiddenFrames.nodelist) { + fsi.positions.nodelist = numframes; + normalFrames[numframes++] = graphics::NodeListRenderer::drawDynamicNodeListScreen; + indicatorIcons.push_back(icon_nodes); + } #endif // Show detailed node views only on E-Ink builds #ifdef USE_EINK - fsi.positions.nodelist_lastheard = numframes; - normalFrames[numframes++] = graphics::NodeListRenderer::drawLastHeardScreen; - indicatorIcons.push_back(icon_nodes); - - fsi.positions.nodelist_hopsignal = numframes; - normalFrames[numframes++] = graphics::NodeListRenderer::drawHopSignalScreen; - indicatorIcons.push_back(icon_signal); - - fsi.positions.nodelist_distance = numframes; - normalFrames[numframes++] = graphics::NodeListRenderer::drawDistanceScreen; - indicatorIcons.push_back(icon_distance); + if (!hiddenFrames.nodelist_lastheard) { + fsi.positions.nodelist_lastheard = numframes; + normalFrames[numframes++] = graphics::NodeListRenderer::drawLastHeardScreen; + indicatorIcons.push_back(icon_nodes); + } + if (!hiddenFrames.nodelist_hopsignal) { + fsi.positions.nodelist_hopsignal = numframes; + normalFrames[numframes++] = graphics::NodeListRenderer::drawHopSignalScreen; + indicatorIcons.push_back(icon_signal); + } + if (!hiddenFrames.nodelist_distance) { + fsi.positions.nodelist_distance = numframes; + normalFrames[numframes++] = graphics::NodeListRenderer::drawDistanceScreen; + indicatorIcons.push_back(icon_distance); + } #endif #if HAS_GPS - fsi.positions.nodelist_bearings = numframes; - normalFrames[numframes++] = graphics::NodeListRenderer::drawNodeListWithCompasses; - indicatorIcons.push_back(icon_list); - - fsi.positions.gps = numframes; - normalFrames[numframes++] = graphics::UIRenderer::drawCompassAndLocationScreen; - indicatorIcons.push_back(icon_compass); + if (!hiddenFrames.nodelist_bearings) { + fsi.positions.nodelist_bearings = numframes; + normalFrames[numframes++] = graphics::NodeListRenderer::drawNodeListWithCompasses; + indicatorIcons.push_back(icon_list); + } + if (!hiddenFrames.gps) { + fsi.positions.gps = numframes; + normalFrames[numframes++] = graphics::UIRenderer::drawCompassAndLocationScreen; + indicatorIcons.push_back(icon_compass); + } #endif - if (RadioLibInterface::instance) { + if (RadioLibInterface::instance && !hiddenFrames.lora) { fsi.positions.lora = numframes; normalFrames[numframes++] = graphics::DebugRenderer::drawLoRaFocused; indicatorIcons.push_back(icon_radio); } - if (!dismissedFrames.memory) { - fsi.positions.memory = numframes; - normalFrames[numframes++] = graphics::DebugRenderer::drawMemoryUsage; - indicatorIcons.push_back(icon_memory); + if (!hiddenFrames.system) { + fsi.positions.system = numframes; + normalFrames[numframes++] = graphics::DebugRenderer::drawSystemScreen; + indicatorIcons.push_back(icon_system); } #if !defined(DISPLAY_CLOCK_FRAME) - fsi.positions.clock = numframes; - normalFrames[numframes++] = uiconfig.is_clockface_analog ? graphics::ClockRenderer::drawAnalogClockFrame - : graphics::ClockRenderer::drawDigitalClockFrame; - indicatorIcons.push_back(digital_icon_clock); + if (!hiddenFrames.clock) { + fsi.positions.clock = numframes; + normalFrames[numframes++] = uiconfig.is_clockface_analog ? graphics::ClockRenderer::drawAnalogClockFrame + : graphics::ClockRenderer::drawDigitalClockFrame; + indicatorIcons.push_back(digital_icon_clock); + } #endif #if HAS_WIFI && !defined(ARCH_PORTDUINO) - if (!dismissedFrames.wifi && isWifiAvailable()) { + if (!hiddenFrames.wifi && isWifiAvailable()) { fsi.positions.wifi = numframes; normalFrames[numframes++] = graphics::DebugRenderer::drawDebugInfoWiFiTrampoline; indicatorIcons.push_back(icon_wifi); @@ -1047,27 +1062,29 @@ void Screen::setFrames(FrameFocus focus) if (numMeshNodes > 0) numMeshNodes--; - // Temporary array to hold favorite node frames - std::vector favoriteFrames; + if (!hiddenFrames.show_favorites) { + // Temporary array to hold favorite node frames + std::vector favoriteFrames; - for (size_t i = 0; i < nodeDB->getNumMeshNodes(); i++) { - const meshtastic_NodeInfoLite *n = nodeDB->getMeshNodeByIndex(i); - if (n && n->num != nodeDB->getNodeNum() && n->is_favorite) { - favoriteFrames.push_back(graphics::UIRenderer::drawNodeInfo); + for (size_t i = 0; i < nodeDB->getNumMeshNodes(); i++) { + const meshtastic_NodeInfoLite *n = nodeDB->getMeshNodeByIndex(i); + if (n && n->num != nodeDB->getNodeNum() && n->is_favorite) { + favoriteFrames.push_back(graphics::UIRenderer::drawNodeInfo); + } } - } - // Insert favorite frames *after* collecting them all - if (!favoriteFrames.empty()) { - fsi.positions.firstFavorite = numframes; - for (const auto &f : favoriteFrames) { - normalFrames[numframes++] = f; - indicatorIcons.push_back(icon_node); + // Insert favorite frames *after* collecting them all + if (!favoriteFrames.empty()) { + fsi.positions.firstFavorite = numframes; + for (const auto &f : favoriteFrames) { + normalFrames[numframes++] = f; + indicatorIcons.push_back(icon_node); + } + fsi.positions.lastFavorite = numframes - 1; + } else { + fsi.positions.firstFavorite = 255; + fsi.positions.lastFavorite = 255; } - fsi.positions.lastFavorite = numframes - 1; - } else { - fsi.positions.firstFavorite = 255; - fsi.positions.lastFavorite = 255; } fsi.frameCount = numframes; // Total framecount is used to apply FOCUS_PRESERVE @@ -1106,7 +1123,7 @@ void Screen::setFrames(FrameFocus focus) ui->switchToFrame(fsi.positions.clock); break; case FOCUS_SYSTEM: - ui->switchToFrame(fsi.positions.memory); + ui->switchToFrame(fsi.positions.system); break; case FOCUS_PRESERVE: @@ -1134,30 +1151,96 @@ void Screen::setFrameImmediateDraw(FrameCallback *drawFrames) setFastFramerate(); } +void Screen::toggleFrameVisibility(const std::string &frameName) +{ +#ifndef USE_EINK + if (frameName == "nodelist") { + hiddenFrames.nodelist = !hiddenFrames.nodelist; + } +#endif +#ifdef USE_EINK + if (frameName == "nodelist_lastheard") { + hiddenFrames.nodelist_lastheard = !hiddenFrames.nodelist_lastheard; + } + if (frameName == "nodelist_hopsignal") { + hiddenFrames.nodelist_hopsignal = !hiddenFrames.nodelist_hopsignal; + } + if (frameName == "nodelist_distance") { + hiddenFrames.nodelist_distance = !hiddenFrames.nodelist_distance; + } +#endif +#if HAS_GPS + if (frameName == "nodelist_bearings") { + hiddenFrames.nodelist_bearings = !hiddenFrames.nodelist_bearings; + } + if (frameName == "gps") { + hiddenFrames.gps = !hiddenFrames.gps; + } +#endif + if (frameName == "lora") { + hiddenFrames.lora = !hiddenFrames.lora; + } + if (frameName == "clock") { + hiddenFrames.clock = !hiddenFrames.clock; + } + if (frameName == "show_favorites") { + hiddenFrames.show_favorites = !hiddenFrames.show_favorites; + } +} + +bool Screen::isFrameHidden(const std::string &frameName) const +{ +#ifndef USE_EINK + if (frameName == "nodelist") + return hiddenFrames.nodelist; +#endif +#ifdef USE_EINK + if (frameName == "nodelist_lastheard") + return hiddenFrames.nodelist_lastheard; + if (frameName == "nodelist_hopsignal") + return hiddenFrames.nodelist_hopsignal; + if (frameName == "nodelist_distance") + return hiddenFrames.nodelist_distance; +#endif +#if HAS_GPS + if (frameName == "nodelist_bearings") + return hiddenFrames.nodelist_bearings; + if (frameName == "gps") + return hiddenFrames.gps; +#endif + if (frameName == "lora") + return hiddenFrames.lora; + if (frameName == "clock") + return hiddenFrames.clock; + if (frameName == "show_favorites") + return hiddenFrames.show_favorites; + + return false; +} + // Dismisses the currently displayed screen frame, if possible // Relevant for text message, waypoint, others in future? // Triggered with a CardKB keycombo -void Screen::dismissCurrentFrame() +void Screen::hideCurrentFrame() { uint8_t currentFrame = ui->getUiState()->currentFrame; bool dismissed = false; - if (currentFrame == framesetInfo.positions.textMessage && devicestate.has_rx_text_message) { - LOG_INFO("Dismiss Text Message"); + LOG_INFO("Hide Text Message"); devicestate.has_rx_text_message = false; memset(&devicestate.rx_text_message, 0, sizeof(devicestate.rx_text_message)); } else if (currentFrame == framesetInfo.positions.waypoint && devicestate.has_rx_waypoint) { - LOG_DEBUG("Dismiss Waypoint"); + LOG_DEBUG("Hide Waypoint"); devicestate.has_rx_waypoint = false; - dismissedFrames.waypoint = true; + hiddenFrames.waypoint = true; dismissed = true; } else if (currentFrame == framesetInfo.positions.wifi) { - LOG_DEBUG("Dismiss WiFi Screen"); - dismissedFrames.wifi = true; + LOG_DEBUG("Hide WiFi Screen"); + hiddenFrames.wifi = true; dismissed = true; - } else if (currentFrame == framesetInfo.positions.memory) { - LOG_INFO("Dismiss Memory"); - dismissedFrames.memory = true; + } else if (currentFrame == framesetInfo.positions.lora) { + LOG_INFO("Hide LoRa"); + hiddenFrames.lora = true; dismissed = true; } @@ -1309,7 +1392,7 @@ int Screen::handleTextMessage(const meshtastic_MeshPacket *packet) // Outgoing message (likely sent from phone) devicestate.has_rx_text_message = false; memset(&devicestate.rx_text_message, 0, sizeof(devicestate.rx_text_message)); - dismissedFrames.textMessage = true; + hiddenFrames.textMessage = true; hasUnreadMessage = false; // Clear unread state when user replies setFrames(FOCUS_PRESERVE); // Stay on same frame, silently update frame list @@ -1439,7 +1522,7 @@ int Screen::handleInputEvent(const InputEvent *event) } else if (event->inputEvent == INPUT_BROKER_SELECT) { if (this->ui->getUiState()->currentFrame == framesetInfo.positions.home) { menuHandler::homeBaseMenu(); - } else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.memory) { + } else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.system) { menuHandler::systemBaseMenu(); #if HAS_GPS } else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.gps && gps) { @@ -1448,7 +1531,7 @@ int Screen::handleInputEvent(const InputEvent *event) } else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.clock) { menuHandler::clockMenu(); } else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.lora) { - menuHandler::LoraRegionPicker(); + menuHandler::loraMenu(); } else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.textMessage) { if (devicestate.rx_text_message.from) { menuHandler::messageResponseMenu(); diff --git a/src/graphics/Screen.h b/src/graphics/Screen.h index 0f100d455..8c13bcf9a 100644 --- a/src/graphics/Screen.h +++ b/src/graphics/Screen.h @@ -593,7 +593,11 @@ class Screen : public concurrency::OSThread void setSSLFrames(); // Dismiss the currently focussed frame, if possible (e.g. text message, waypoint) - void dismissCurrentFrame(); + void hideCurrentFrame(); + + // Menu-driven Show / Hide Toggle + void toggleFrameVisibility(const std::string &frameName); + bool isFrameHidden(const std::string &frameName) const; #ifdef USE_EINK /// Draw an image to remain on E-Ink display after screen off @@ -655,7 +659,7 @@ class Screen : public concurrency::OSThread uint8_t settings = 255; uint8_t wifi = 255; uint8_t deviceFocused = 255; - uint8_t memory = 255; + uint8_t system = 255; uint8_t gps = 255; uint8_t home = 255; uint8_t textMessage = 255; @@ -673,12 +677,28 @@ class Screen : public concurrency::OSThread uint8_t frameCount = 0; } framesetInfo; - struct DismissedFrames { + struct hiddenFrames { bool textMessage = false; bool waypoint = false; bool wifi = false; - bool memory = false; - } dismissedFrames; + bool system = false; + bool home = false; + bool clock = false; +#ifndef USE_EINK + bool nodelist = false; +#endif +#ifdef USE_EINK + bool nodelist_lastheard = false; + bool nodelist_hopsignal = false; + bool nodelist_distance = false; +#endif +#if HAS_GPS + bool nodelist_bearings = false; + bool gps = false; +#endif + bool lora = false; + bool show_favorites = false; + } hiddenFrames; /// Try to start drawing ASAP void setFastFramerate(); diff --git a/src/graphics/draw/DebugRenderer.cpp b/src/graphics/draw/DebugRenderer.cpp index a0f29f10d..446fe7863 100644 --- a/src/graphics/draw/DebugRenderer.cpp +++ b/src/graphics/draw/DebugRenderer.cpp @@ -487,7 +487,7 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, // **************************** // * System Screen * // **************************** -void drawMemoryUsage(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) +void drawSystemScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) { display->clear(); display->setFont(FONT_SMALL); diff --git a/src/graphics/draw/DebugRenderer.h b/src/graphics/draw/DebugRenderer.h index f4d484f58..3382e931d 100644 --- a/src/graphics/draw/DebugRenderer.h +++ b/src/graphics/draw/DebugRenderer.h @@ -31,8 +31,8 @@ void drawDebugInfoWiFiTrampoline(OLEDDisplay *display, OLEDDisplayUiState *state // LoRa information display void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y); -// Memory screen display -void drawMemoryUsage(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y); +// System screen display +void drawSystemScreen(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 a4f25797a..e92a54751 100644 --- a/src/graphics/draw/MenuHandler.cpp +++ b/src/graphics/draw/MenuHandler.cpp @@ -29,6 +29,24 @@ menuHandler::screenMenus menuHandler::menuQueue = menu_none; bool test_enabled = false; uint8_t test_count = 0; +void menuHandler::loraMenu() +{ + static const char *optionsArray[] = {"Back", "Region Picker"}; + enum optionsNumbers { Back = 0, lora_picker = 1 }; + BannerOverlayOptions bannerOptions; + bannerOptions.message = "LoRa Actions"; + bannerOptions.optionsArrayPtr = optionsArray; + bannerOptions.optionsCount = 2; + bannerOptions.bannerCallback = [](int selected) -> void { + if (selected == Back) { + // No action + } else if (selected == lora_picker) { + menuHandler::menuQueue = menuHandler::lora_picker; + } + }; + screen->showOverlayBanner(bannerOptions); +} + void menuHandler::OnboardMessage() { static const char *optionsArray[] = {"OK", "Got it!"}; @@ -308,7 +326,7 @@ void menuHandler::messageResponseMenu() bannerOptions.optionsCount = options; bannerOptions.bannerCallback = [](int selected) -> void { if (selected == Dismiss) { - screen->dismissCurrentFrame(); + screen->hideCurrentFrame(); } else if (selected == Preset) { if (devicestate.rx_text_message.to == NODENUM_BROADCAST) { cannedMessageModule->LaunchWithDestination(NODENUM_BROADCAST, devicestate.rx_text_message.channel); @@ -349,8 +367,11 @@ void menuHandler::homeBaseMenu() optionsArray[options] = "Sleep Screen"; optionsEnumArray[options++] = Sleep; #endif - - optionsArray[options] = "Send Position"; + if (config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_ENABLED) { + optionsArray[options] = "Send Position"; + } else { + optionsArray[options] = "Send Node Info"; + } optionsEnumArray[options++] = Position; optionsArray[options] = "New Preset Msg"; optionsEnumArray[options++] = Preset; @@ -430,7 +451,7 @@ void menuHandler::textMessageBaseMenu() void menuHandler::systemBaseMenu() { - enum optionsNumbers { Back, Notifications, ScreenOptions, Bluetooth, PowerMenu, Test, enumEnd }; + enum optionsNumbers { Back, Notifications, ScreenOptions, Bluetooth, PowerMenu, FrameToggles, Test, enumEnd }; static const char *optionsArray[enumEnd] = {"Back"}; static int optionsEnumArray[enumEnd] = {Back}; int options = 1; @@ -443,6 +464,9 @@ void menuHandler::systemBaseMenu() optionsEnumArray[options++] = ScreenOptions; #endif + optionsArray[options] = "Frame Visiblity Toggle"; + optionsEnumArray[options++] = FrameToggles; + optionsArray[options] = "Bluetooth Toggle"; optionsEnumArray[options++] = Bluetooth; @@ -469,6 +493,9 @@ void menuHandler::systemBaseMenu() } else if (selected == PowerMenu) { menuHandler::menuQueue = menuHandler::power_menu; screen->runNow(); + } else if (selected == FrameToggles) { + menuHandler::menuQueue = menuHandler::FrameToggles; + screen->runNow(); } else if (selected == Test) { menuHandler::menuQueue = menuHandler::test_menu; screen->runNow(); @@ -535,6 +562,7 @@ void menuHandler::positionBaseMenu() optionsArray[options] = "Compass Calibrate"; optionsEnumArray[options++] = CompassCalibrate; } + BannerOverlayOptions bannerOptions; bannerOptions.message = "Position Action"; bannerOptions.optionsArrayPtr = optionsArray; @@ -1146,6 +1174,116 @@ void menuHandler::keyVerificationFinalPrompt() } } +void menuHandler::FrameToggles_menu() +{ + enum optionsNumbers { + Finish, + nodelist, + nodelist_lastheard, + nodelist_hopsignal, + nodelist_distance, + nodelist_bearings, + gps, + lora, + clock, + show_favorites, + enumEnd + }; + static const char *optionsArray[enumEnd] = {"Finish"}; + static int optionsEnumArray[enumEnd] = {Finish}; + int options = 1; + + // Track last selected index (not enum value!) + static int lastSelectedIndex = 0; + +#ifndef USE_EINK + optionsArray[options] = screen->isFrameHidden("nodelist") ? "Show Node List" : "Hide Node List"; + optionsEnumArray[options++] = nodelist; +#endif +#ifdef USE_EINK + optionsArray[options] = screen->isFrameHidden("nodelist_lastheard") ? "Show NL - Last Heard" : "Hide NL - Last Heard"; + optionsEnumArray[options++] = nodelist_lastheard; + optionsArray[options] = screen->isFrameHidden("nodelist_hopsignal") ? "Show NL - Hops/Signal" : "Hide NL - Hops/Signal"; + optionsEnumArray[options++] = nodelist_hopsignal; + optionsArray[options] = screen->isFrameHidden("nodelist_distance") ? "Show NL - Distance" : "Hide NL - Distance"; + optionsEnumArray[options++] = nodelist_distance; +#endif +#if HAS_GPS + optionsArray[options] = screen->isFrameHidden("nodelist_bearings") ? "Show Bearings" : "Hide Bearings"; + optionsEnumArray[options++] = nodelist_bearings; + + optionsArray[options] = screen->isFrameHidden("gps") ? "Show Position" : "Hide Position"; + optionsEnumArray[options++] = gps; +#endif + + optionsArray[options] = screen->isFrameHidden("lora") ? "Show LoRa" : "Hide LoRa"; + optionsEnumArray[options++] = lora; + + optionsArray[options] = screen->isFrameHidden("clock") ? "Show Clock" : "Hide Clock"; + optionsEnumArray[options++] = clock; + + optionsArray[options] = screen->isFrameHidden("show_favorites") ? "Show Favorites" : "Hide Favorites"; + optionsEnumArray[options++] = show_favorites; + + BannerOverlayOptions bannerOptions; + bannerOptions.message = "Show/Hide Frames"; + bannerOptions.optionsArrayPtr = optionsArray; + bannerOptions.optionsCount = options; + bannerOptions.optionsEnumPtr = optionsEnumArray; + bannerOptions.InitialSelected = lastSelectedIndex; // Use index, not enum value + + bannerOptions.bannerCallback = [optionsEnumArray, options](int selected) mutable -> void { + // Find the index of selected in optionsEnumArray + int idx = 0; + for (; idx < options; ++idx) { + if (optionsEnumArray[idx] == selected) + break; + } + lastSelectedIndex = idx; + + if (selected == Finish) { + screen->setFrames(Screen::FOCUS_DEFAULT); + } else if (selected == nodelist) { + screen->toggleFrameVisibility("nodelist"); + menuHandler::menuQueue = menuHandler::FrameToggles; + screen->runNow(); + } else if (selected == nodelist_lastheard) { + screen->toggleFrameVisibility("nodelist_lastheard"); + menuHandler::menuQueue = menuHandler::FrameToggles; + screen->runNow(); + } else if (selected == nodelist_hopsignal) { + screen->toggleFrameVisibility("nodelist_hopsignal"); + menuHandler::menuQueue = menuHandler::FrameToggles; + screen->runNow(); + } else if (selected == nodelist_distance) { + screen->toggleFrameVisibility("nodelist_distance"); + menuHandler::menuQueue = menuHandler::FrameToggles; + screen->runNow(); + } else if (selected == nodelist_bearings) { + screen->toggleFrameVisibility("nodelist_bearings"); + menuHandler::menuQueue = menuHandler::FrameToggles; + screen->runNow(); + } else if (selected == gps) { + screen->toggleFrameVisibility("gps"); + menuHandler::menuQueue = menuHandler::FrameToggles; + screen->runNow(); + } else if (selected == lora) { + screen->toggleFrameVisibility("lora"); + menuHandler::menuQueue = menuHandler::FrameToggles; + screen->runNow(); + } else if (selected == clock) { + screen->toggleFrameVisibility("clock"); + menuHandler::menuQueue = menuHandler::FrameToggles; + screen->runNow(); + } else if (selected == show_favorites) { + screen->toggleFrameVisibility("show_favorites"); + menuHandler::menuQueue = menuHandler::FrameToggles; + screen->runNow(); + } + }; + screen->showOverlayBanner(bannerOptions); +} + void menuHandler::handleMenuSwitch(OLEDDisplay *display) { if (menuQueue != menu_none) @@ -1242,6 +1380,9 @@ void menuHandler::handleMenuSwitch(OLEDDisplay *display) case power_menu: powerMenu(); break; + case FrameToggles: + FrameToggles_menu(); + break; case throttle_message: screen->showSimpleBanner("Too Many Attempts\nTry again in 60 seconds.", 5000); break; diff --git a/src/graphics/draw/MenuHandler.h b/src/graphics/draw/MenuHandler.h index b15cf237d..00df22d6c 100644 --- a/src/graphics/draw/MenuHandler.h +++ b/src/graphics/draw/MenuHandler.h @@ -39,11 +39,13 @@ class menuHandler key_verification_final_prompt, trace_route_menu, throttle_message, + FrameToggles }; static screenMenus menuQueue; static void OnboardMessage(); static void LoraRegionPicker(uint32_t duration = 30000); + static void loraMenu(); static void handleMenuSwitch(OLEDDisplay *display); static void showConfirmationBanner(const char *message, std::function onConfirm); static void clockMenu(); @@ -76,6 +78,7 @@ class menuHandler static void notificationsMenu(); static void screenOptionsMenu(); static void powerMenu(); + static void FrameToggles_menu(); private: static void saveUIConfig(); diff --git a/src/graphics/images.h b/src/graphics/images.h index c66e4b992..e349cb6e0 100644 --- a/src/graphics/images.h +++ b/src/graphics/images.h @@ -118,8 +118,8 @@ const uint8_t icon_radio[] PROGMEM = { 0xA9 // Row 7: #..#.#.# }; -// 🪙 Memory Icon -const uint8_t icon_memory[] PROGMEM = { +// 🪙 System Icon +const uint8_t icon_system[] PROGMEM = { 0x24, // Row 0: ..#..#.. 0x3C, // Row 1: ..####.. 0xC3, // Row 2: ##....## From b0e8321514d91da0f5a90c324e3d9dce0b36af5b Mon Sep 17 00:00:00 2001 From: Tom Fifield Date: Fri, 29 Aug 2025 09:45:46 +1000 Subject: [PATCH 036/691] Only send Neighbours if we have some to send. (#7493) * Only send Neighbours if we have some to send. The original intent of NeighborInfo was that when a NeighbourInfo was sent all of the nodes that saw it would reply with NeighbourInfo. So, NeighbourInfo was sent even if there were no hop-zero nodes in the NodeDB. Since 2023, when this was implemented, our understanding of running city-wide meshes has improved substantially. We have taken steps to reduce the impact of NeighborInfo over LoRa. This change aligns with those ideas: we will now only send NeighborInfo if we have some neighbors to contribute. The impact of this change is that a node must first see another directly connected node in another packet type before NeighborInfo is sent. This means that a node with no neighbors is no longer able to trigger other nodes to broadcast NeighborInfo. It will, however, receive the regular periodic broadcast of NeighborInfo, and will be able to send NeighborInfo if it has at least 1 neighbor. * Include all the things * AvOid memleak --- src/modules/NeighborInfoModule.cpp | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/modules/NeighborInfoModule.cpp b/src/modules/NeighborInfoModule.cpp index eebf428a4..97dc17001 100644 --- a/src/modules/NeighborInfoModule.cpp +++ b/src/modules/NeighborInfoModule.cpp @@ -105,14 +105,15 @@ void NeighborInfoModule::sendNeighborInfo(NodeNum dest, bool wantReplies) { meshtastic_NeighborInfo neighborInfo = meshtastic_NeighborInfo_init_zero; collectNeighborInfo(&neighborInfo); - meshtastic_MeshPacket *p = allocDataProtobuf(neighborInfo); - // send regardless of whether or not we have neighbors in our DB, - // because we want to get neighbors for the next cycle - p->to = dest; - p->decoded.want_response = wantReplies; - p->priority = meshtastic_MeshPacket_Priority_BACKGROUND; - printNeighborInfo("SENDING", &neighborInfo); - service->sendToMesh(p, RX_SRC_LOCAL, true); + // only send neighbours if we have some to send + if (neighborInfo.neighbors_count > 0) { + meshtastic_MeshPacket *p = allocDataProtobuf(neighborInfo); + p->to = dest; + p->decoded.want_response = wantReplies; + p->priority = meshtastic_MeshPacket_Priority_BACKGROUND; + printNeighborInfo("SENDING", &neighborInfo); + service->sendToMesh(p, RX_SRC_LOCAL, true); + } } /* @@ -214,4 +215,4 @@ meshtastic_Neighbor *NeighborInfoModule::getOrCreateNeighbor(NodeNum originalSen neighbors.push_back(new_nbr); } return &neighbors.back(); -} \ No newline at end of file +} From 5f8503c62ddb5a1d40cdbf71960d11a08e6d5e07 Mon Sep 17 00:00:00 2001 From: Tom Fifield Date: Sat, 30 Aug 2025 00:08:33 +1000 Subject: [PATCH 037/691] We don't gotTime if time is 2019. (#7772) There are certain GPS chips that have a hard-coded time in firmware that they will return before lock. We set our own hard-coded time, BUILD_EPOCH, that should be newer and use the comparison to not set a bad time. In https://github.com/meshtastic/firmware/pull/7261 we introduced the RTCSetResult and improved it in https://github.com/meshtastic/firmware/pull/7375 . However, the original try-fix left logic in GPS.cpp that could still result in broadcasting the bad time. Further, as part of our fix we cleared the GPS buffer if we didn't get a good time. The mesh was hurting at the time, so this was a reasonable approach. However, given time tends to come in when we're trying to get early lock, this had the potential side effect of throwing away valuable information to get position lock. This change reverses the clearBuffer and changes the logic so if time is not set it will not be broadcast. Fixes https://github.com/meshtastic/firmware/issues/7771 Fixes https://github.com/meshtastic/firmware/issues/7750 --- src/gps/GPS.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index ae74f0fe2..881021975 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -1504,7 +1504,7 @@ static int32_t toDegInt(RawDegrees d) * Perform any processing that should be done only while the GPS is awake and looking for a fix. * Override this method to check for new locations * - * @return true if we've acquired a new location + * @return true if we've set a new time */ bool GPS::lookForTime() { @@ -1544,11 +1544,12 @@ The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of s 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) == RTCSetResultInvalidTime) { - // Clear the GPS buffer if we got an invalid time - clearBuffer(); + if (perhapsSetRTC(RTCQualityGPS, t) == RTCSetResultSuccess) { + LOG_DEBUG("Time set."); + return true; + } else { + return false; } - return true; } else return false; } else From d3e3a91096f2189ea710d94c7dda145f1046c6ad Mon Sep 17 00:00:00 2001 From: Tom Fifield Date: Sat, 30 Aug 2025 00:08:33 +1000 Subject: [PATCH 038/691] We don't gotTime if time is 2019. (#7772) There are certain GPS chips that have a hard-coded time in firmware that they will return before lock. We set our own hard-coded time, BUILD_EPOCH, that should be newer and use the comparison to not set a bad time. In https://github.com/meshtastic/firmware/pull/7261 we introduced the RTCSetResult and improved it in https://github.com/meshtastic/firmware/pull/7375 . However, the original try-fix left logic in GPS.cpp that could still result in broadcasting the bad time. Further, as part of our fix we cleared the GPS buffer if we didn't get a good time. The mesh was hurting at the time, so this was a reasonable approach. However, given time tends to come in when we're trying to get early lock, this had the potential side effect of throwing away valuable information to get position lock. This change reverses the clearBuffer and changes the logic so if time is not set it will not be broadcast. Fixes https://github.com/meshtastic/firmware/issues/7771 Fixes https://github.com/meshtastic/firmware/issues/7750 --- src/gps/GPS.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index ae74f0fe2..881021975 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -1504,7 +1504,7 @@ static int32_t toDegInt(RawDegrees d) * Perform any processing that should be done only while the GPS is awake and looking for a fix. * Override this method to check for new locations * - * @return true if we've acquired a new location + * @return true if we've set a new time */ bool GPS::lookForTime() { @@ -1544,11 +1544,12 @@ The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of s 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) == RTCSetResultInvalidTime) { - // Clear the GPS buffer if we got an invalid time - clearBuffer(); + if (perhapsSetRTC(RTCQualityGPS, t) == RTCSetResultSuccess) { + LOG_DEBUG("Time set."); + return true; + } else { + return false; } - return true; } else return false; } else From fb34dac08dcc72c50a4fa164291c4d9bcc5045fa Mon Sep 17 00:00:00 2001 From: Wilson Date: Fri, 29 Aug 2025 23:26:27 +0800 Subject: [PATCH 039/691] Add On-Screen Keyboard for UpDown Encoder and Rotary Encoder. (#7762) * Add On-Screen Keyboard for UpDownInterrupt. Pls notice the new keyboard layout was inspired and adviced by https://github.com/csrutil * Add longPress event for RotaryEncoder Press. * Update UpdownInterrupt UP and DOWN on main UI. * Change the interrupt trigger mode from rising edge to falling edge to improve button response. --- src/buzz/BuzzerFeedbackThread.cpp | 2 + src/graphics/draw/NotificationRenderer.cpp | 58 ++++++---- src/input/ButtonThread.h | 3 + src/input/InputBroker.h | 4 +- src/input/RotaryEncoderInterruptBase.cpp | 41 ++++++- src/input/RotaryEncoderInterruptBase.h | 14 ++- src/input/RotaryEncoderInterruptImpl1.cpp | 5 +- src/input/UpDownInterruptBase.cpp | 120 +++++++++++++++++---- src/input/UpDownInterruptBase.h | 30 +++++- src/input/UpDownInterruptImpl1.cpp | 14 ++- 10 files changed, 236 insertions(+), 55 deletions(-) diff --git a/src/buzz/BuzzerFeedbackThread.cpp b/src/buzz/BuzzerFeedbackThread.cpp index 838224c69..12b30a705 100644 --- a/src/buzz/BuzzerFeedbackThread.cpp +++ b/src/buzz/BuzzerFeedbackThread.cpp @@ -33,7 +33,9 @@ int BuzzerFeedbackThread::handleInputEvent(const InputEvent *event) break; case INPUT_BROKER_UP: + case INPUT_BROKER_UP_LONG: case INPUT_BROKER_DOWN: + case INPUT_BROKER_DOWN_LONG: case INPUT_BROKER_LEFT: case INPUT_BROKER_RIGHT: playChirp(); // Navigation feedback diff --git a/src/graphics/draw/NotificationRenderer.cpp b/src/graphics/draw/NotificationRenderer.cpp index 221d95075..b53cd2f3f 100644 --- a/src/graphics/draw/NotificationRenderer.cpp +++ b/src/graphics/draw/NotificationRenderer.cpp @@ -7,10 +7,18 @@ #include "graphics/ScreenFonts.h" #include "graphics/SharedUIDisplay.h" #include "graphics/images.h" +#include "input/RotaryEncoderInterruptImpl1.h" +#include "input/UpDownInterruptImpl1.h" +#if HAS_BUTTON +#include "input/ButtonThread.h" +#endif #include "main.h" #include #include #include +#if HAS_TRACKBALL +#include "input/TrackballInterruptImpl1.h" +#endif #ifdef ARCH_ESP32 #include "esp_task_wdt.h" @@ -18,6 +26,11 @@ using namespace meshtastic; +#if HAS_BUTTON +// Global button thread pointer defined in main.cpp +extern ::ButtonThread *UserButtonThread; +#endif + // External references to global variables from Screen.cpp extern std::vector functionSymbol; extern std::string functionSymbolString; @@ -288,12 +301,9 @@ void NotificationRenderer::drawNodePicker(OLEDDisplay *display, OLEDDisplayUiSta if (nodeDB->getMeshNodeByIndex(i + 1)->has_user) { std::string sanitized = sanitizeString(nodeDB->getMeshNodeByIndex(i + 1)->user.long_name); strncpy(temp_name, sanitized.c_str(), sizeof(temp_name) - 1); - } else { snprintf(temp_name, sizeof(temp_name), "(%04X)", (uint16_t)(nodeDB->getMeshNodeByIndex(i + 1)->num & 0xFFFF)); } - // make temp buffer for name - // fi if (i == curSelected) { selectedNodenum = nodeDB->getMeshNodeByIndex(i + 1)->num; if (isHighResolution) { @@ -307,7 +317,8 @@ void NotificationRenderer::drawNodePicker(OLEDDisplay *display, OLEDDisplayUiSta } scratchLineBuffer[scratchLineNum][39] = '\0'; } else { - strncpy(scratchLineBuffer[scratchLineNum], temp_name, 36); + strncpy(scratchLineBuffer[scratchLineNum], temp_name, 39); + scratchLineBuffer[scratchLineNum][39] = '\0'; } linePointers[linesShown] = scratchLineBuffer[scratchLineNum++]; } @@ -623,59 +634,68 @@ void NotificationRenderer::drawTextInput(OLEDDisplay *display, OLEDDisplayUiStat return; } - // Handle input events for virtual keyboard navigation if (inEvent.inputEvent != INPUT_BROKER_NONE) { if (inEvent.inputEvent == INPUT_BROKER_UP) { - virtualKeyboard->moveCursorUp(); + // high frequency for move cursor left/right than up/down with encoders + extern ::RotaryEncoderInterruptImpl1 *rotaryEncoderInterruptImpl1; + extern ::UpDownInterruptImpl1 *upDownInterruptImpl1; + if (::rotaryEncoderInterruptImpl1 || ::upDownInterruptImpl1) { + virtualKeyboard->moveCursorLeft(); + } else { + virtualKeyboard->moveCursorUp(); + } } else if (inEvent.inputEvent == INPUT_BROKER_DOWN) { - virtualKeyboard->moveCursorDown(); + extern ::RotaryEncoderInterruptImpl1 *rotaryEncoderInterruptImpl1; + extern ::UpDownInterruptImpl1 *upDownInterruptImpl1; + if (::rotaryEncoderInterruptImpl1 || ::upDownInterruptImpl1) { + virtualKeyboard->moveCursorRight(); + } else { + virtualKeyboard->moveCursorDown(); + } } else if (inEvent.inputEvent == INPUT_BROKER_LEFT) { virtualKeyboard->moveCursorLeft(); } else if (inEvent.inputEvent == INPUT_BROKER_RIGHT) { virtualKeyboard->moveCursorRight(); + } else if (inEvent.inputEvent == INPUT_BROKER_UP_LONG) { + virtualKeyboard->moveCursorUp(); + } else if (inEvent.inputEvent == INPUT_BROKER_DOWN_LONG) { + virtualKeyboard->moveCursorDown(); } else if (inEvent.inputEvent == INPUT_BROKER_ALT_PRESS) { - // Long press UP = move left virtualKeyboard->moveCursorLeft(); } else if (inEvent.inputEvent == INPUT_BROKER_USER_PRESS) { - // Long press DOWN = move right virtualKeyboard->moveCursorRight(); } else if (inEvent.inputEvent == INPUT_BROKER_SELECT) { virtualKeyboard->handlePress(); } else if (inEvent.inputEvent == INPUT_BROKER_SELECT_LONG) { virtualKeyboard->handleLongPress(); } else if (inEvent.inputEvent == INPUT_BROKER_CANCEL) { - // Cancel virtual keyboard - call callback with empty string - auto callback = textInputCallback; // Store callback before clearing - - // Clean up first to prevent re-entry + auto callback = textInputCallback; delete virtualKeyboard; virtualKeyboard = nullptr; textInputCallback = nullptr; resetBanner(); - - // Call callback after cleanup if (callback) { callback(""); } - - // Restore normal overlays if (screen) { screen->setFrames(graphics::Screen::FOCUS_PRESERVE); } return; } - // Reset input event after processing + // Consume the event after processing for virtual keyboard inEvent.inputEvent = INPUT_BROKER_NONE; } - // Clear the display and draw virtual keyboard + // Clear the screen to avoid overlapping with underlying frames or overlays display->setColor(BLACK); display->fillRect(0, 0, display->getWidth(), display->getHeight()); display->setColor(WHITE); + // Draw the virtual keyboard virtualKeyboard->draw(display, 0, 0); } else { // If virtualKeyboard is null, reset the banner to avoid getting stuck + LOG_INFO("Virtual keyboard is null - resetting banner"); resetBanner(); } } diff --git a/src/input/ButtonThread.h b/src/input/ButtonThread.h index c6d6557e2..7de38341c 100644 --- a/src/input/ButtonThread.h +++ b/src/input/ButtonThread.h @@ -76,6 +76,9 @@ class ButtonThread : public Observable, public concurrency:: return digitalRead(buttonPin); // Most buttons are active low by default } + // Returns true while this thread's button is physically held down + bool isHeld() { return isButtonPressed(_pinNum); } + // Disconnect and reconnect interrupts for light sleep #ifdef ARCH_ESP32 int beforeLightSleep(void *unused); diff --git a/src/input/InputBroker.h b/src/input/InputBroker.h index 012a403f5..2cdfa2ae2 100644 --- a/src/input/InputBroker.h +++ b/src/input/InputBroker.h @@ -4,7 +4,9 @@ enum input_broker_event { INPUT_BROKER_NONE = 0, INPUT_BROKER_SELECT = 10, - INPUT_BROKER_SELECT_LONG, + INPUT_BROKER_SELECT_LONG = 11, + INPUT_BROKER_UP_LONG = 12, + INPUT_BROKER_DOWN_LONG = 13, INPUT_BROKER_UP = 17, INPUT_BROKER_DOWN = 18, INPUT_BROKER_LEFT = 19, diff --git a/src/input/RotaryEncoderInterruptBase.cpp b/src/input/RotaryEncoderInterruptBase.cpp index 88b07a389..204a0fbf0 100644 --- a/src/input/RotaryEncoderInterruptBase.cpp +++ b/src/input/RotaryEncoderInterruptBase.cpp @@ -8,15 +8,17 @@ RotaryEncoderInterruptBase::RotaryEncoderInterruptBase(const char *name) : concu void RotaryEncoderInterruptBase::init( uint8_t pinA, uint8_t pinB, uint8_t pinPress, input_broker_event eventCw, input_broker_event eventCcw, - input_broker_event eventPressed, + input_broker_event eventPressed, input_broker_event eventPressedLong, // std::function onIntA, std::function onIntB, std::function onIntPress) : void (*onIntA)(), void (*onIntB)(), void (*onIntPress)()) { this->_pinA = pinA; this->_pinB = pinB; + this->_pinPress = pinPress; this->_eventCw = eventCw; this->_eventCcw = eventCcw; this->_eventPressed = eventPressed; + this->_eventPressedLong = eventPressedLong; bool isRAK = false; #ifdef RAK_4631 @@ -46,10 +48,37 @@ int32_t RotaryEncoderInterruptBase::runOnce() InputEvent e; e.inputEvent = INPUT_BROKER_NONE; e.source = this->_originName; + unsigned long now = millis(); + // Handle press long/short detection if (this->action == ROTARY_ACTION_PRESSED) { - LOG_DEBUG("Rotary event Press"); - e.inputEvent = this->_eventPressed; + bool buttonPressed = !digitalRead(_pinPress); + if (!pressDetected && buttonPressed) { + pressDetected = true; + pressStartTime = now; + } + + if (pressDetected) { + uint32_t duration = now - pressStartTime; + if (!buttonPressed) { + // released -> if short press, send short, else already sent long + if (duration < LONG_PRESS_DURATION && now - lastPressKeyTime >= pressDebounceMs) { + lastPressKeyTime = now; + LOG_DEBUG("Rotary event Press short"); + e.inputEvent = this->_eventPressed; + } + pressDetected = false; + pressStartTime = 0; + lastPressLongEventTime = 0; + this->action = ROTARY_ACTION_NONE; + } else if (duration >= LONG_PRESS_DURATION && this->_eventPressedLong != INPUT_BROKER_NONE && + lastPressLongEventTime == 0) { + // fire single-shot long press + lastPressLongEventTime = now; + LOG_DEBUG("Rotary event Press long"); + e.inputEvent = this->_eventPressedLong; + } + } } else if (this->action == ROTARY_ACTION_CW) { LOG_DEBUG("Rotary event CW"); e.inputEvent = this->_eventCw; @@ -62,7 +91,9 @@ int32_t RotaryEncoderInterruptBase::runOnce() this->notifyObservers(&e); } - this->action = ROTARY_ACTION_NONE; + if (!pressDetected) { + this->action = ROTARY_ACTION_NONE; + } return INT32_MAX; } @@ -70,7 +101,7 @@ int32_t RotaryEncoderInterruptBase::runOnce() void RotaryEncoderInterruptBase::intPressHandler() { this->action = ROTARY_ACTION_PRESSED; - setIntervalFromNow(20); // TODO: this modifies a non-volatile variable! + setIntervalFromNow(20); // start checking for long/short } void RotaryEncoderInterruptBase::intAHandler() diff --git a/src/input/RotaryEncoderInterruptBase.h b/src/input/RotaryEncoderInterruptBase.h index 9bdab4730..4f9757609 100644 --- a/src/input/RotaryEncoderInterruptBase.h +++ b/src/input/RotaryEncoderInterruptBase.h @@ -13,7 +13,7 @@ class RotaryEncoderInterruptBase : public Observable, public public: explicit RotaryEncoderInterruptBase(const char *name); void init(uint8_t pinA, uint8_t pinB, uint8_t pinPress, input_broker_event eventCw, input_broker_event eventCcw, - input_broker_event eventPressed, + input_broker_event eventPressed, input_broker_event eventPressedLong, // std::function onIntA, std::function onIntB, std::function onIntPress); void (*onIntA)(), void (*onIntB)(), void (*onIntPress)()); void intPressHandler(); @@ -33,10 +33,22 @@ class RotaryEncoderInterruptBase : public Observable, public volatile RotaryEncoderInterruptBaseActionType action = ROTARY_ACTION_NONE; private: + // pins and events uint8_t _pinA = 0; uint8_t _pinB = 0; + uint8_t _pinPress = 0; input_broker_event _eventCw = INPUT_BROKER_NONE; input_broker_event _eventCcw = INPUT_BROKER_NONE; input_broker_event _eventPressed = INPUT_BROKER_NONE; + input_broker_event _eventPressedLong = INPUT_BROKER_NONE; const char *_originName; + + // Long press detection variables + uint32_t pressStartTime = 0; + bool pressDetected = false; + uint32_t lastPressLongEventTime = 0; + unsigned long lastPressKeyTime = 0; + static const uint32_t LONG_PRESS_DURATION = 300; // ms + static const uint32_t LONG_PRESS_REPEAT_INTERVAL = 0; // 0 = single-shot for rotary select + const unsigned long pressDebounceMs = 200; // ms }; diff --git a/src/input/RotaryEncoderInterruptImpl1.cpp b/src/input/RotaryEncoderInterruptImpl1.cpp index 4f19c8b0b..12cbc36fb 100644 --- a/src/input/RotaryEncoderInterruptImpl1.cpp +++ b/src/input/RotaryEncoderInterruptImpl1.cpp @@ -1,5 +1,6 @@ #include "RotaryEncoderInterruptImpl1.h" #include "InputBroker.h" +extern bool osk_found; RotaryEncoderInterruptImpl1 *rotaryEncoderInterruptImpl1; @@ -19,12 +20,14 @@ bool RotaryEncoderInterruptImpl1::init() input_broker_event eventCw = static_cast(moduleConfig.canned_message.inputbroker_event_cw); input_broker_event eventCcw = static_cast(moduleConfig.canned_message.inputbroker_event_ccw); input_broker_event eventPressed = static_cast(moduleConfig.canned_message.inputbroker_event_press); + input_broker_event eventPressedLong = INPUT_BROKER_SELECT_LONG; // moduleConfig.canned_message.ext_notification_module_output - RotaryEncoderInterruptBase::init(pinA, pinB, pinPress, eventCw, eventCcw, eventPressed, + RotaryEncoderInterruptBase::init(pinA, pinB, pinPress, eventCw, eventCcw, eventPressed, eventPressedLong, RotaryEncoderInterruptImpl1::handleIntA, RotaryEncoderInterruptImpl1::handleIntB, RotaryEncoderInterruptImpl1::handleIntPressed); inputBroker->registerSource(this); + osk_found = true; return true; } diff --git a/src/input/UpDownInterruptBase.cpp b/src/input/UpDownInterruptBase.cpp index 26b281aaf..0bf0f5cd4 100644 --- a/src/input/UpDownInterruptBase.cpp +++ b/src/input/UpDownInterruptBase.cpp @@ -7,14 +7,22 @@ UpDownInterruptBase::UpDownInterruptBase(const char *name) : concurrency::OSThre } void UpDownInterruptBase::init(uint8_t pinDown, uint8_t pinUp, uint8_t pinPress, input_broker_event eventDown, - input_broker_event eventUp, input_broker_event eventPressed, void (*onIntDown)(), + input_broker_event eventUp, input_broker_event eventPressed, input_broker_event eventPressedLong, + input_broker_event eventUpLong, input_broker_event eventDownLong, void (*onIntDown)(), void (*onIntUp)(), void (*onIntPress)(), unsigned long updownDebounceMs) { this->_pinDown = pinDown; this->_pinUp = pinUp; + this->_pinPress = pinPress; this->_eventDown = eventDown; this->_eventUp = eventUp; this->_eventPressed = eventPressed; + this->_eventPressedLong = eventPressedLong; + this->_eventUpLong = eventUpLong; + this->_eventDownLong = eventDownLong; + + // Store debounce configuration passed by caller + this->updownDebounceMs = updownDebounceMs; bool isRAK = false; #ifdef RAK_4631 isRAK = true; @@ -22,20 +30,20 @@ void UpDownInterruptBase::init(uint8_t pinDown, uint8_t pinUp, uint8_t pinPress, if (!isRAK || pinPress != 0) { pinMode(pinPress, INPUT_PULLUP); - attachInterrupt(pinPress, onIntPress, RISING); + attachInterrupt(pinPress, onIntPress, FALLING); } if (!isRAK || this->_pinDown != 0) { pinMode(this->_pinDown, INPUT_PULLUP); - attachInterrupt(this->_pinDown, onIntDown, RISING); + attachInterrupt(this->_pinDown, onIntDown, FALLING); } if (!isRAK || this->_pinUp != 0) { pinMode(this->_pinUp, INPUT_PULLUP); - attachInterrupt(this->_pinUp, onIntUp, RISING); + attachInterrupt(this->_pinUp, onIntUp, FALLING); } LOG_DEBUG("Up/down/press GPIO initialized (%d, %d, %d)", this->_pinUp, this->_pinDown, pinPress); - this->setInterval(100); + this->setInterval(20); } int32_t UpDownInterruptBase::runOnce() @@ -43,23 +51,88 @@ int32_t UpDownInterruptBase::runOnce() InputEvent e; e.inputEvent = INPUT_BROKER_NONE; unsigned long now = millis(); - if (this->action == UPDOWN_ACTION_PRESSED) { - if (now - lastPressKeyTime >= pressDebounceMs) { - lastPressKeyTime = now; - LOG_DEBUG("GPIO event Press"); - e.inputEvent = this->_eventPressed; + + // Read all button states once at the beginning + bool pressButtonPressed = !digitalRead(_pinPress); + bool upButtonPressed = !digitalRead(_pinUp); + bool downButtonPressed = !digitalRead(_pinDown); + + // Handle initial button press detection - only if not already detected + if (this->action == UPDOWN_ACTION_PRESSED && pressButtonPressed && !pressDetected) { + pressDetected = true; + pressStartTime = now; + } else if (this->action == UPDOWN_ACTION_UP && upButtonPressed && !upDetected) { + upDetected = true; + upStartTime = now; + } else if (this->action == UPDOWN_ACTION_DOWN && downButtonPressed && !downDetected) { + downDetected = true; + downStartTime = now; + } + + // Handle long press detection for press button + if (pressDetected && pressStartTime > 0) { + uint32_t pressDuration = now - pressStartTime; + + if (!pressButtonPressed) { + // Button released + if (pressDuration < LONG_PRESS_DURATION && now - lastPressKeyTime >= pressDebounceMs) { + lastPressKeyTime = now; + e.inputEvent = this->_eventPressed; + } + // Reset state + pressDetected = false; + pressStartTime = 0; + lastPressLongEventTime = 0; + } else if (pressDuration >= LONG_PRESS_DURATION && lastPressLongEventTime == 0) { + // First long press event only - avoid repeated events causing lag + e.inputEvent = this->_eventPressedLong; + lastPressLongEventTime = now; } - } else if (this->action == UPDOWN_ACTION_UP) { - if (now - lastUpKeyTime >= updownDebounceMs) { - lastUpKeyTime = now; - LOG_DEBUG("GPIO event Up"); - e.inputEvent = this->_eventUp; + } + + // Handle long press detection for up button + if (upDetected && upStartTime > 0) { + uint32_t upDuration = now - upStartTime; + + if (!upButtonPressed) { + // Button released + if (upDuration < LONG_PRESS_DURATION && now - lastUpKeyTime >= updownDebounceMs) { + lastUpKeyTime = now; + e.inputEvent = this->_eventUp; + } + // Reset state + upDetected = false; + upStartTime = 0; + lastUpLongEventTime = 0; + } else if (upDuration >= LONG_PRESS_DURATION) { + // Auto-repeat long press events + if (lastUpLongEventTime == 0 || (now - lastUpLongEventTime) >= LONG_PRESS_REPEAT_INTERVAL) { + e.inputEvent = this->_eventUpLong; + lastUpLongEventTime = now; + } } - } else if (this->action == UPDOWN_ACTION_DOWN) { - if (now - lastDownKeyTime >= updownDebounceMs) { - lastDownKeyTime = now; - LOG_DEBUG("GPIO event Down"); - e.inputEvent = this->_eventDown; + } + + // Handle long press detection for down button + if (downDetected && downStartTime > 0) { + uint32_t downDuration = now - downStartTime; + + if (!downButtonPressed) { + // Button released + if (downDuration < LONG_PRESS_DURATION && now - lastDownKeyTime >= updownDebounceMs) { + lastDownKeyTime = now; + e.inputEvent = this->_eventDown; + } + // Reset state + downDetected = false; + downStartTime = 0; + lastDownLongEventTime = 0; + } else if (downDuration >= LONG_PRESS_DURATION) { + // Auto-repeat long press events + if (lastDownLongEventTime == 0 || (now - lastDownLongEventTime) >= LONG_PRESS_REPEAT_INTERVAL) { + e.inputEvent = this->_eventDownLong; + lastDownLongEventTime = now; + } } } @@ -69,8 +142,11 @@ int32_t UpDownInterruptBase::runOnce() this->notifyObservers(&e); } - this->action = UPDOWN_ACTION_NONE; - return 100; + if (!pressDetected && !upDetected && !downDetected) { + this->action = UPDOWN_ACTION_NONE; + } + + return 20; // This will control how the input frequency } void UpDownInterruptBase::intPressHandler() diff --git a/src/input/UpDownInterruptBase.h b/src/input/UpDownInterruptBase.h index a83a298f2..ae84efdaf 100644 --- a/src/input/UpDownInterruptBase.h +++ b/src/input/UpDownInterruptBase.h @@ -8,7 +8,8 @@ class UpDownInterruptBase : public Observable, public concur public: explicit UpDownInterruptBase(const char *name); void init(uint8_t pinDown, uint8_t pinUp, uint8_t pinPress, input_broker_event eventDown, input_broker_event eventUp, - input_broker_event eventPressed, void (*onIntDown)(), void (*onIntUp)(), void (*onIntPress)(), + input_broker_event eventPressed, input_broker_event eventPressedLong, input_broker_event eventUpLong, + input_broker_event eventDownLong, void (*onIntDown)(), void (*onIntUp)(), void (*onIntPress)(), unsigned long updownDebounceMs = 50); void intPressHandler(); void intDownHandler(); @@ -17,16 +18,41 @@ class UpDownInterruptBase : public Observable, public concur int32_t runOnce() override; protected: - enum UpDownInterruptBaseActionType { UPDOWN_ACTION_NONE, UPDOWN_ACTION_PRESSED, UPDOWN_ACTION_UP, UPDOWN_ACTION_DOWN }; + enum UpDownInterruptBaseActionType { + UPDOWN_ACTION_NONE, + UPDOWN_ACTION_PRESSED, + UPDOWN_ACTION_PRESSED_LONG, + UPDOWN_ACTION_UP, + UPDOWN_ACTION_UP_LONG, + UPDOWN_ACTION_DOWN, + UPDOWN_ACTION_DOWN_LONG + }; volatile UpDownInterruptBaseActionType action = UPDOWN_ACTION_NONE; + // Long press detection variables + uint32_t pressStartTime = 0; + uint32_t upStartTime = 0; + uint32_t downStartTime = 0; + bool pressDetected = false; + bool upDetected = false; + bool downDetected = false; + uint32_t lastPressLongEventTime = 0; + uint32_t lastUpLongEventTime = 0; + uint32_t lastDownLongEventTime = 0; + static const uint32_t LONG_PRESS_DURATION = 300; + static const uint32_t LONG_PRESS_REPEAT_INTERVAL = 300; + private: uint8_t _pinDown = 0; uint8_t _pinUp = 0; + uint8_t _pinPress = 0; input_broker_event _eventDown = INPUT_BROKER_NONE; input_broker_event _eventUp = INPUT_BROKER_NONE; input_broker_event _eventPressed = INPUT_BROKER_NONE; + input_broker_event _eventPressedLong = INPUT_BROKER_NONE; + input_broker_event _eventUpLong = INPUT_BROKER_NONE; + input_broker_event _eventDownLong = INPUT_BROKER_NONE; const char *_originName; unsigned long lastUpKeyTime = 0; diff --git a/src/input/UpDownInterruptImpl1.cpp b/src/input/UpDownInterruptImpl1.cpp index 761b92348..9b0b1f39e 100644 --- a/src/input/UpDownInterruptImpl1.cpp +++ b/src/input/UpDownInterruptImpl1.cpp @@ -1,5 +1,6 @@ #include "UpDownInterruptImpl1.h" #include "InputBroker.h" +extern bool osk_found; UpDownInterruptImpl1 *upDownInterruptImpl1; @@ -17,13 +18,18 @@ bool UpDownInterruptImpl1::init() uint8_t pinDown = moduleConfig.canned_message.inputbroker_pin_b; uint8_t pinPress = moduleConfig.canned_message.inputbroker_pin_press; - input_broker_event eventDown = INPUT_BROKER_DOWN; - input_broker_event eventUp = INPUT_BROKER_UP; + input_broker_event eventDown = INPUT_BROKER_USER_PRESS; // acts like RIGHT/DOWN + input_broker_event eventUp = INPUT_BROKER_ALT_PRESS; // acts like LEFT/UP input_broker_event eventPressed = INPUT_BROKER_SELECT; + input_broker_event eventPressedLong = INPUT_BROKER_SELECT_LONG; + input_broker_event eventUpLong = INPUT_BROKER_UP_LONG; + input_broker_event eventDownLong = INPUT_BROKER_DOWN_LONG; - UpDownInterruptBase::init(pinDown, pinUp, pinPress, eventDown, eventUp, eventPressed, UpDownInterruptImpl1::handleIntDown, - UpDownInterruptImpl1::handleIntUp, UpDownInterruptImpl1::handleIntPressed); + UpDownInterruptBase::init(pinDown, pinUp, pinPress, eventDown, eventUp, eventPressed, eventPressedLong, eventUpLong, + eventDownLong, UpDownInterruptImpl1::handleIntDown, UpDownInterruptImpl1::handleIntUp, + UpDownInterruptImpl1::handleIntPressed); inputBroker->registerSource(this); + osk_found = true; return true; } From 9b41131af88c7aef00480a4be203f990f51dacb1 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Fri, 29 Aug 2025 10:31:55 -0500 Subject: [PATCH 040/691] Backmerge (#7782) * Merge pull request #7777 from meshtastic/create-pull-request/bump-version Bump release version * Only send Neighbours if we have some to send. (#7493) * Only send Neighbours if we have some to send. The original intent of NeighborInfo was that when a NeighbourInfo was sent all of the nodes that saw it would reply with NeighbourInfo. So, NeighbourInfo was sent even if there were no hop-zero nodes in the NodeDB. Since 2023, when this was implemented, our understanding of running city-wide meshes has improved substantially. We have taken steps to reduce the impact of NeighborInfo over LoRa. This change aligns with those ideas: we will now only send NeighborInfo if we have some neighbors to contribute. The impact of this change is that a node must first see another directly connected node in another packet type before NeighborInfo is sent. This means that a node with no neighbors is no longer able to trigger other nodes to broadcast NeighborInfo. It will, however, receive the regular periodic broadcast of NeighborInfo, and will be able to send NeighborInfo if it has at least 1 neighbor. * Include all the things * AvOid memleak * We don't gotTime if time is 2019. (#7772) There are certain GPS chips that have a hard-coded time in firmware that they will return before lock. We set our own hard-coded time, BUILD_EPOCH, that should be newer and use the comparison to not set a bad time. In https://github.com/meshtastic/firmware/pull/7261 we introduced the RTCSetResult and improved it in https://github.com/meshtastic/firmware/pull/7375 . However, the original try-fix left logic in GPS.cpp that could still result in broadcasting the bad time. Further, as part of our fix we cleared the GPS buffer if we didn't get a good time. The mesh was hurting at the time, so this was a reasonable approach. However, given time tends to come in when we're trying to get early lock, this had the potential side effect of throwing away valuable information to get position lock. This change reverses the clearBuffer and changes the logic so if time is not set it will not be broadcast. Fixes https://github.com/meshtastic/firmware/issues/7771 Fixes https://github.com/meshtastic/firmware/issues/7750 --------- Co-authored-by: Tom Fifield --- src/modules/NeighborInfoModule.cpp | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/modules/NeighborInfoModule.cpp b/src/modules/NeighborInfoModule.cpp index eebf428a4..97dc17001 100644 --- a/src/modules/NeighborInfoModule.cpp +++ b/src/modules/NeighborInfoModule.cpp @@ -105,14 +105,15 @@ void NeighborInfoModule::sendNeighborInfo(NodeNum dest, bool wantReplies) { meshtastic_NeighborInfo neighborInfo = meshtastic_NeighborInfo_init_zero; collectNeighborInfo(&neighborInfo); - meshtastic_MeshPacket *p = allocDataProtobuf(neighborInfo); - // send regardless of whether or not we have neighbors in our DB, - // because we want to get neighbors for the next cycle - p->to = dest; - p->decoded.want_response = wantReplies; - p->priority = meshtastic_MeshPacket_Priority_BACKGROUND; - printNeighborInfo("SENDING", &neighborInfo); - service->sendToMesh(p, RX_SRC_LOCAL, true); + // only send neighbours if we have some to send + if (neighborInfo.neighbors_count > 0) { + meshtastic_MeshPacket *p = allocDataProtobuf(neighborInfo); + p->to = dest; + p->decoded.want_response = wantReplies; + p->priority = meshtastic_MeshPacket_Priority_BACKGROUND; + printNeighborInfo("SENDING", &neighborInfo); + service->sendToMesh(p, RX_SRC_LOCAL, true); + } } /* @@ -214,4 +215,4 @@ meshtastic_Neighbor *NeighborInfoModule::getOrCreateNeighbor(NodeNum originalSen neighbors.push_back(new_nbr); } return &neighbors.back(); -} \ No newline at end of file +} From 4e03df5ea7a30ac6458545df59ec24557a46598d Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Fri, 29 Aug 2025 12:09:22 -0500 Subject: [PATCH 041/691] Fix freetext hang (#7781) * Fixed freetext hangs by adding canned modules back to self-sourced packets and transition to SENDING_ACTIVE state * Update meshmodule handling --- src/mesh/MeshModule.cpp | 11 ++--------- src/mesh/MeshModule.h | 2 +- src/mesh/NodeDB.cpp | 8 ++++---- src/mesh/Router.cpp | 6 +++--- src/modules/CannedMessageModule.cpp | 3 +-- src/modules/RoutingModule.cpp | 2 +- src/modules/RoutingModule.h | 2 -- 7 files changed, 12 insertions(+), 22 deletions(-) diff --git a/src/mesh/MeshModule.cpp b/src/mesh/MeshModule.cpp index 409c52179..22fcec663 100644 --- a/src/mesh/MeshModule.cpp +++ b/src/mesh/MeshModule.cpp @@ -85,11 +85,8 @@ meshtastic_MeshPacket *MeshModule::allocErrorResponse(meshtastic_Routing_Error e return r; } -void MeshModule::callModules(meshtastic_MeshPacket &mp, RxSource src, const char *specificModule) +void MeshModule::callModules(meshtastic_MeshPacket &mp, RxSource src) { - if (specificModule) { - LOG_DEBUG("Calling specific module: %s", specificModule); - } // LOG_DEBUG("In call modules"); bool moduleFound = false; @@ -103,15 +100,11 @@ void MeshModule::callModules(meshtastic_MeshPacket &mp, RxSource src, const char // Was this message directed to us specifically? Will be false if we are sniffing someone elses packets auto ourNodeNum = nodeDB->getNodeNum(); bool toUs = isBroadcast(mp.to) || isToUs(&mp); + bool fromUs = mp.from == ourNodeNum; for (auto i = modules->begin(); i != modules->end(); ++i) { auto &pi = **i; - // If specificModule is provided, only call that specific module - if (specificModule && (!pi.name || strcmp(pi.name, specificModule) != 0)) { - continue; - } - pi.currentRequest = ∓ /// We only call modules that are interested in the packet (and the message is destined to us or we are promiscious) diff --git a/src/mesh/MeshModule.h b/src/mesh/MeshModule.h index bf735439f..eda3f8881 100644 --- a/src/mesh/MeshModule.h +++ b/src/mesh/MeshModule.h @@ -73,7 +73,7 @@ class MeshModule /** For use only by MeshService */ - static void callModules(meshtastic_MeshPacket &mp, RxSource src = RX_SRC_RADIO, const char *specificModule = nullptr); + static void callModules(meshtastic_MeshPacket &mp, RxSource src = RX_SRC_RADIO); static std::vector GetMeshModulesWithUIFrames(int startIndex); static void observeUIEvents(Observer *observer); diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index d544d0174..c8eba1b2e 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -1711,10 +1711,10 @@ bool NodeDB::updateUser(uint32_t nodeId, meshtastic_User &p, uint8_t channelInde /// we updateGUI and updateGUIforNode if we think our this change is big enough for a redraw void NodeDB::updateFrom(const meshtastic_MeshPacket &mp) { - // if (mp.from == getNodeNum()) { - // LOG_DEBUG("Ignore update from self"); - // return; - // } + if (mp.from == getNodeNum()) { + LOG_DEBUG("Ignore update from self"); + return; + } if (mp.which_payload_variant == meshtastic_MeshPacket_decoded_tag && mp.from) { LOG_DEBUG("Update DB node 0x%x, rx_time=%u", mp.from, mp.rx_time); diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index 1f835bca7..c7e32c4a1 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -562,7 +562,7 @@ meshtastic_Routing_Error perhapsEncode(meshtastic_MeshPacket *p) // Now that we are encrypting the packet channel should be the hash (no longer the index) p->channel = hash; if (hash < 0) { - // No suitable channel could be found for sending + // No suitable channel could be found for return meshtastic_Routing_Error_NO_CHANNEL; } crypto->encryptPacket(getFrom(p), p->id, numbytes, bytes); @@ -578,7 +578,7 @@ meshtastic_Routing_Error perhapsEncode(meshtastic_MeshPacket *p) // Now that we are encrypting the packet channel should be the hash (no longer the index) p->channel = hash; if (hash < 0) { - // No suitable channel could be found for sending + // No suitable channel could be found for return meshtastic_Routing_Error_NO_CHANNEL; } crypto->encryptPacket(getFrom(p), p->id, numbytes, bytes); @@ -671,7 +671,7 @@ void Router::handleReceived(meshtastic_MeshPacket *p, RxSource src) mqtt->onSend(*p_encrypted, *p, p->channel); #endif } else if (p->from == nodeDB->getNodeNum() && !skipHandle) { - MeshModule::callModules(*p, src, ROUTING_MODULE); + MeshModule::callModules(*p, src); } packetPool.release(p_encrypted); // Release the encrypted packet diff --git a/src/modules/CannedMessageModule.cpp b/src/modules/CannedMessageModule.cpp index d40dcd24f..e9165e57c 100644 --- a/src/modules/CannedMessageModule.cpp +++ b/src/modules/CannedMessageModule.cpp @@ -632,10 +632,10 @@ bool CannedMessageModule::handleMessageSelectorInput(const InputEvent *event, bo // Normal canned message selection if (runState == CANNED_MESSAGE_RUN_STATE_INACTIVE || runState == CANNED_MESSAGE_RUN_STATE_DISABLED) { } else { +#if CANNED_MESSAGE_ADD_CONFIRMATION // Show confirmation dialog before sending canned message NodeNum destNode = dest; ChannelIndex chan = channel; -#if CANNED_MESSAGE_ADD_CONFIRMATION graphics::menuHandler::showConfirmationBanner("Send message?", [this, destNode, chan, current]() { this->sendText(destNode, chan, current, false); payload = runState; @@ -991,7 +991,6 @@ int32_t CannedMessageModule::runOnce() this->runState = CANNED_MESSAGE_RUN_STATE_INACTIVE; } } - e.action = UIFrameEvent::Action::REGENERATE_FRAMESET; this->currentMessageIndex = -1; this->freetext = ""; this->cursor = 0; diff --git a/src/modules/RoutingModule.cpp b/src/modules/RoutingModule.cpp index b10413cc8..e7e92c79a 100644 --- a/src/modules/RoutingModule.cpp +++ b/src/modules/RoutingModule.cpp @@ -73,7 +73,7 @@ uint8_t RoutingModule::getHopLimitForResponse(uint8_t hopStart, uint8_t hopLimit return Default::getConfiguredOrDefaultHopLimit(config.lora.hop_limit); // Use the default hop limit } -RoutingModule::RoutingModule() : ProtobufModule(ROUTING_MODULE, meshtastic_PortNum_ROUTING_APP, &meshtastic_Routing_msg) +RoutingModule::RoutingModule() : ProtobufModule("routing", meshtastic_PortNum_ROUTING_APP, &meshtastic_Routing_msg) { isPromiscuous = true; diff --git a/src/modules/RoutingModule.h b/src/modules/RoutingModule.h index 7b43a6e98..c047f6e29 100644 --- a/src/modules/RoutingModule.h +++ b/src/modules/RoutingModule.h @@ -2,8 +2,6 @@ #include "Channels.h" #include "ProtobufModule.h" -static const char *ROUTING_MODULE = "routing"; - /** * Routing module for router control messages */ From 10c683626325166605aa56a7ecbafc5a303cfd77 Mon Sep 17 00:00:00 2001 From: Tom Fifield Date: Sat, 30 Aug 2025 04:22:23 +1000 Subject: [PATCH 042/691] Can't trust RTCs to tell the time. (#7779) Further to https://github.com/meshtastic/firmware/pull/7772 , we discovered that some RTCs have hard-coded start times well in the past. This patch gives RTCs the same treatment as GPS - if the time is earlier than BUILD_EPOCH, we don't use it. Fixes #7771 Fixes #7750 --- src/gps/RTC.cpp | 26 ++++++++++++++++++++++---- src/gps/RTC.h | 2 +- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/src/gps/RTC.cpp b/src/gps/RTC.cpp index d574c9ad0..185adacd9 100644 --- a/src/gps/RTC.cpp +++ b/src/gps/RTC.cpp @@ -23,7 +23,7 @@ static uint64_t zeroOffsetSecs; // GPS based time in secs since 1970 - only upda * Reads the current date and time from the RTC module and updates the system time. * @return True if the RTC was successfully read and the system time was updated, false otherwise. */ -void readFromRTC() +RTCSetResult readFromRTC() { struct timeval tv; /* btw settimeofday() is helpful here too*/ #ifdef RV3028_RTC @@ -44,8 +44,15 @@ void readFromRTC() t.tm_sec = rtc.getSecond(); tv.tv_sec = gm_mktime(&t); tv.tv_usec = 0; - 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) { + LOG_WARN("Ignore time (%ld) before build epoch (%ld)!", printableEpoch, BUILD_EPOCH); + return RTCSetResultInvalidTime; + } +#endif + 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; @@ -53,6 +60,7 @@ void readFromRTC() if (currentQuality == RTCQualityNone) { currentQuality = RTCQualityDevice; } + return RTCSetResultSuccess; } #elif defined(PCF8563_RTC) if (rtc_found.address == PCF8563_RTC) { @@ -75,8 +83,15 @@ void readFromRTC() t.tm_sec = tc.second; tv.tv_sec = gm_mktime(&t); tv.tv_usec = 0; - 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) { + LOG_WARN("Ignore time (%ld) before build epoch (%ld)!", printableEpoch, BUILD_EPOCH); + return RTCSetResultInvalidTime; + } +#endif + 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; @@ -84,6 +99,7 @@ void readFromRTC() if (currentQuality == RTCQualityNone) { currentQuality = RTCQualityDevice; } + return RTCSetResultSuccess; } #else if (!gettimeofday(&tv, NULL)) { @@ -92,8 +108,10 @@ void readFromRTC() LOG_DEBUG("Read RTC time as %ld", printableEpoch); timeStartMsec = now; zeroOffsetSecs = tv.tv_sec; + return RTCSetResultSuccess; } #endif + return RTCSetResultNotSet; } /** @@ -101,7 +119,7 @@ void readFromRTC() * * @param q The quality of the provided time. * @param tv A pointer to a timeval struct containing the time to potentially set the RTC to. - * @return True if the RTC was set, false otherwise. + * @return RTCSetResult * * If we haven't yet set our RTC this boot, set it from a GPS derived time */ diff --git a/src/gps/RTC.h b/src/gps/RTC.h index 96dec575b..010be6886 100644 --- a/src/gps/RTC.h +++ b/src/gps/RTC.h @@ -48,7 +48,7 @@ uint32_t getTime(bool local = false); /// Return time since 1970 in secs. If quality is RTCQualityNone return zero uint32_t getValidTime(RTCQuality minQuality, bool local = false); -void readFromRTC(); +RTCSetResult readFromRTC(); time_t gm_mktime(struct tm *tm); From 11db6d4dcc1d886f79696b797659e3ac58cb9d43 Mon Sep 17 00:00:00 2001 From: Tom Fifield Date: Sat, 30 Aug 2025 04:22:23 +1000 Subject: [PATCH 043/691] Can't trust RTCs to tell the time. (#7779) Further to https://github.com/meshtastic/firmware/pull/7772 , we discovered that some RTCs have hard-coded start times well in the past. This patch gives RTCs the same treatment as GPS - if the time is earlier than BUILD_EPOCH, we don't use it. Fixes #7771 Fixes #7750 --- src/gps/RTC.cpp | 26 ++++++++++++++++++++++---- src/gps/RTC.h | 2 +- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/src/gps/RTC.cpp b/src/gps/RTC.cpp index d574c9ad0..185adacd9 100644 --- a/src/gps/RTC.cpp +++ b/src/gps/RTC.cpp @@ -23,7 +23,7 @@ static uint64_t zeroOffsetSecs; // GPS based time in secs since 1970 - only upda * Reads the current date and time from the RTC module and updates the system time. * @return True if the RTC was successfully read and the system time was updated, false otherwise. */ -void readFromRTC() +RTCSetResult readFromRTC() { struct timeval tv; /* btw settimeofday() is helpful here too*/ #ifdef RV3028_RTC @@ -44,8 +44,15 @@ void readFromRTC() t.tm_sec = rtc.getSecond(); tv.tv_sec = gm_mktime(&t); tv.tv_usec = 0; - 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) { + LOG_WARN("Ignore time (%ld) before build epoch (%ld)!", printableEpoch, BUILD_EPOCH); + return RTCSetResultInvalidTime; + } +#endif + 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; @@ -53,6 +60,7 @@ void readFromRTC() if (currentQuality == RTCQualityNone) { currentQuality = RTCQualityDevice; } + return RTCSetResultSuccess; } #elif defined(PCF8563_RTC) if (rtc_found.address == PCF8563_RTC) { @@ -75,8 +83,15 @@ void readFromRTC() t.tm_sec = tc.second; tv.tv_sec = gm_mktime(&t); tv.tv_usec = 0; - 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) { + LOG_WARN("Ignore time (%ld) before build epoch (%ld)!", printableEpoch, BUILD_EPOCH); + return RTCSetResultInvalidTime; + } +#endif + 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; @@ -84,6 +99,7 @@ void readFromRTC() if (currentQuality == RTCQualityNone) { currentQuality = RTCQualityDevice; } + return RTCSetResultSuccess; } #else if (!gettimeofday(&tv, NULL)) { @@ -92,8 +108,10 @@ void readFromRTC() LOG_DEBUG("Read RTC time as %ld", printableEpoch); timeStartMsec = now; zeroOffsetSecs = tv.tv_sec; + return RTCSetResultSuccess; } #endif + return RTCSetResultNotSet; } /** @@ -101,7 +119,7 @@ void readFromRTC() * * @param q The quality of the provided time. * @param tv A pointer to a timeval struct containing the time to potentially set the RTC to. - * @return True if the RTC was set, false otherwise. + * @return RTCSetResult * * If we haven't yet set our RTC this boot, set it from a GPS derived time */ diff --git a/src/gps/RTC.h b/src/gps/RTC.h index 96dec575b..010be6886 100644 --- a/src/gps/RTC.h +++ b/src/gps/RTC.h @@ -48,7 +48,7 @@ uint32_t getTime(bool local = false); /// Return time since 1970 in secs. If quality is RTCQualityNone return zero uint32_t getValidTime(RTCQuality minQuality, bool local = false); -void readFromRTC(); +RTCSetResult readFromRTC(); time_t gm_mktime(struct tm *tm); From ed394f5f9df0cc854d97ab342ab364e027f58006 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:58:32 -0500 Subject: [PATCH 044/691] Update protobufs (#7784) 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 8985852d7..4c4427c4a 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit 8985852d752de3f7210f9a4a3e0923120ec438b3 +Subproject commit 4c4427c4a73c86fed7dc8632188bb8be95349d81 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 045/691] 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 b53dd2ec904a63d82a7b86d545e12b5de199ce87 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 046/691] 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 4a669032dcc55012e1e60bd66f2619599b332a6f Mon Sep 17 00:00:00 2001 From: Wilson Date: Sat, 30 Aug 2025 08:37:18 +0800 Subject: [PATCH 047/691] Change user button to cancel button on meshtiny. (#7789) --- variants/nrf52840/meshtiny/platformio.ini | 10 +--------- variants/nrf52840/meshtiny/variant.h | 9 +++------ 2 files changed, 4 insertions(+), 15 deletions(-) diff --git a/variants/nrf52840/meshtiny/platformio.ini b/variants/nrf52840/meshtiny/platformio.ini index ef744a1c3..5f03f5cb2 100644 --- a/variants/nrf52840/meshtiny/platformio.ini +++ b/variants/nrf52840/meshtiny/platformio.ini @@ -4,15 +4,7 @@ extends = nrf52840_base board = meshtiny board_level = extra build_flags = ${nrf52840_base.build_flags} -Ivariants/nrf52840/meshtiny -D MESHTINY - -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 - -D INPUTDRIVER_ENCODER_TYPE=2 - -D INPUTDRIVER_ENCODER_UP=4 - -D INPUTDRIVER_ENCODER_DOWN=26 - -D INPUTDRIVER_ENCODER_BTN=28 - -D USE_PIN_BUZZER=PIN_BUZZER + -D USE_PIN_BUZZER -D MESHTASTIC_EXCLUDE_GPS=1 build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/meshtiny> lib_deps = diff --git a/variants/nrf52840/meshtiny/variant.h b/variants/nrf52840/meshtiny/variant.h index 83ad4c5b9..d1139b3be 100644 --- a/variants/nrf52840/meshtiny/variant.h +++ b/variants/nrf52840/meshtiny/variant.h @@ -21,8 +21,6 @@ #define MESHTINY -// #define RAK4630 - /** Master clock frequency */ #define VARIANT_MCK (64000000ul) @@ -76,11 +74,10 @@ extern "C" { * Buttons */ -#define PIN_BUTTON1 9 +#define CANCEL_BUTTON_PIN 9 #define BUTTON_NEED_PULLUP -#define PIN_BUTTON2 12 -#define PIN_BUTTON3 24 -#define PIN_BUTTON4 25 +#define CANCEL_BUTTON_ACTIVE_LOW true +#define CANCEL_BUTTON_ACTIVE_PULLUP false /* * Analog pins From ca79760372c930b7cbf16937cff6dc9c80e0ae26 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Sun, 31 Aug 2025 21:08:58 -0500 Subject: [PATCH 048/691] 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 bdabf5e37..31ce039f9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -421,7 +421,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 44688e83630fcfcff94184f7a47de2faa5634bbb Mon Sep 17 00:00:00 2001 From: Tom Fifield Date: Mon, 1 Sep 2025 14:16:24 +1000 Subject: [PATCH 049/691] 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 050/691] 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 051/691] 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 052/691] 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 053/691] 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 054/691] 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 088be6bf6af84607a8131088461406f5ec8e3b11 Mon Sep 17 00:00:00 2001 From: Tom Fifield Date: Tue, 2 Sep 2025 14:22:48 +1000 Subject: [PATCH 055/691] Fix device-install.bat baud rate (master --> develop) (#7816) * Upgrade trunk (#7763) Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com> * Fix device-install.bat baud rate 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 --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com> --- .trunk/trunk.yaml | 4 ++-- bin/device-install.bat | 3 +-- 2 files changed, 3 insertions(+), 4 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 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 d66665b96e23220e8f22be8b41ca0251aab101dd 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 056/691] 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 102c447fe3871fa897d8506440bb1a8a4a00b241 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 057/691] 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 16d7de5989a7627cf0c9c44d7abd0686e0afe641 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 058/691] 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 7d1300ab66c4107f13e78769ab258760241d3331 Mon Sep 17 00:00:00 2001 From: Wilson Date: Tue, 2 Sep 2025 13:06:24 +0800 Subject: [PATCH 059/691] 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 b8d7222423e52098c67e04ea8dffebcd437a28f3 Mon Sep 17 00:00:00 2001 From: Jason P Date: Tue, 2 Sep 2025 05:55:57 -0500 Subject: [PATCH 060/691] 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 446fe7863..3e4030e0f 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 e721431b1..20a0bdbd1 100644 --- a/src/mesh/RadioInterface.cpp +++ b/src/mesh/RadioInterface.cpp @@ -589,7 +589,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 c5fad6cca1b1f68f9c47d3d9d260badbf9e7a3f9 Mon Sep 17 00:00:00 2001 From: Tom Fifield Date: Tue, 2 Sep 2025 21:05:14 +1000 Subject: [PATCH 061/691] 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 cfc1bf10c9db958df5aa01e6826c109d66f3e3e7 Mon Sep 17 00:00:00 2001 From: Jason P Date: Tue, 2 Sep 2025 05:55:57 -0500 Subject: [PATCH 062/691] 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 063/691] 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 064/691] 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 7612799ef667c15e8e158605dcbd4e7dcf41dc14 Mon Sep 17 00:00:00 2001 From: Tom Fifield Date: Tue, 2 Sep 2025 21:40:59 +1000 Subject: [PATCH 065/691] 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 3040e5a7bb4c89ba5f22ecc95ec751c0cfd3e301 Mon Sep 17 00:00:00 2001 From: Tom Fifield Date: Tue, 2 Sep 2025 21:40:59 +1000 Subject: [PATCH 066/691] 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 067/691] 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 0952007805e447a185ead329aceb88eb21f39d3d Mon Sep 17 00:00:00 2001 From: Chloe Bethel Date: Tue, 2 Sep 2025 13:08:57 +0100 Subject: [PATCH 068/691] 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 31ce039f9..73f68e95e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1531,7 +1531,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 0bd4cefad3e5fe419901343388c466ae82e10308 Mon Sep 17 00:00:00 2001 From: Chloe Bethel Date: Tue, 2 Sep 2025 13:08:57 +0100 Subject: [PATCH 069/691] 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 070/691] 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 071/691] 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 072/691] 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 073/691] 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 142abb2a4e04d5cfde75ef034db12d4dc25f04a8 Mon Sep 17 00:00:00 2001 From: ford-jones Date: Wed, 3 Sep 2025 12:06:35 +1200 Subject: [PATCH 074/691] Updated naming to match protobuf --- src/mesh/generated/meshtastic/module_config.pb.h | 6 +++--- src/modules/RangeTestModule.cpp | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/mesh/generated/meshtastic/module_config.pb.h b/src/mesh/generated/meshtastic/module_config.pb.h index 468a31a59..16c4c230c 100644 --- a/src/mesh/generated/meshtastic/module_config.pb.h +++ b/src/mesh/generated/meshtastic/module_config.pb.h @@ -319,7 +319,7 @@ typedef struct _meshtastic_ModuleConfig_RangeTestConfig { bool save; /* Bool indicating that the node should cleanup / destroy it's RangeTest.csv file. ESP32 Only */ - bool clear; + bool clear_on_reboot; } meshtastic_ModuleConfig_RangeTestConfig; /* Configuration for both device and environment metrics */ @@ -613,7 +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_tag 4 +#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 @@ -808,7 +808,7 @@ X(a, STATIC, SINGULAR, BOOL, is_server, 6) 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, clear, 4) +X(a, STATIC, SINGULAR, BOOL, clear_on_reboot, 4) #define meshtastic_ModuleConfig_RangeTestConfig_CALLBACK NULL #define meshtastic_ModuleConfig_RangeTestConfig_DEFAULT NULL diff --git a/src/modules/RangeTestModule.cpp b/src/modules/RangeTestModule.cpp index c3d070602..c119cd8c4 100644 --- a/src/modules/RangeTestModule.cpp +++ b/src/modules/RangeTestModule.cpp @@ -54,7 +54,7 @@ int32_t RangeTestModule::runOnce() firstTime = 0; - if (moduleConfig.range_test.clear) { + if (moduleConfig.range_test.clear_on_reboot) { // User wants to delete previous range test(s) LOG_INFO("Range Test Module - Clearing out previous test file"); rangeTestModuleRadio->removeFile(); 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 075/691] 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 ba582d6ef4b475f7bcc4929894bfae4090cc5ee7 Mon Sep 17 00:00:00 2001 From: ford-jones Date: Wed, 3 Sep 2025 12:23:59 +1200 Subject: [PATCH 076/691] Protobuf naming reflected in config-switch --- src/modules/RangeTestModule.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/RangeTestModule.cpp b/src/modules/RangeTestModule.cpp index c119cd8c4..20e243584 100644 --- a/src/modules/RangeTestModule.cpp +++ b/src/modules/RangeTestModule.cpp @@ -41,7 +41,7 @@ int32_t RangeTestModule::runOnce() // moduleConfig.range_test.enabled = 1; // moduleConfig.range_test.sender = 30; // moduleConfig.range_test.save = 1; - // moduleConfig.range_test.clear = 1; + // moduleConfig.range_test.clear_on_reboot = 1; // Fixed position is useful when testing indoors. // config.position.fixed_position = 1; From c62f262f632d90724bb9d76d581655b9affa67af Mon Sep 17 00:00:00 2001 From: ford-jones Date: Wed, 3 Sep 2025 13:38:39 +1200 Subject: [PATCH 077/691] Trunk fmt --- src/SerialConsole.cpp | 7 +++---- src/mesh/StreamAPI.h | 6 +++--- variants/esp32s3/t-deck-pro/variant.h | 15 +++++++------ .../nrf52840/heltec_mesh_solar/variant.cpp | 2 +- variants/nrf52840/heltec_mesh_solar/variant.h | 21 +++++++++---------- 5 files changed, 24 insertions(+), 27 deletions(-) diff --git a/src/SerialConsole.cpp b/src/SerialConsole.cpp index 093a24678..2e6ae68a5 100644 --- a/src/SerialConsole.cpp +++ b/src/SerialConsole.cpp @@ -65,10 +65,9 @@ SerialConsole::SerialConsole() : StreamAPI(&Port), RedirectablePrint(&Port), con int32_t SerialConsole::runOnce() { #ifdef HELTEC_MESH_SOLAR - //After enabling the mesh solar serial port module configuration, command processing is handled by the serial port module. - if(moduleConfig.serial.enabled && moduleConfig.serial.override_console_serial_port - && moduleConfig.serial.mode==meshtastic_ModuleConfig_SerialConfig_Serial_Mode_MS_CONFIG) - { + // After enabling the mesh solar serial port module configuration, command processing is handled by the serial port module. + if (moduleConfig.serial.enabled && moduleConfig.serial.override_console_serial_port && + moduleConfig.serial.mode == meshtastic_ModuleConfig_SerialConfig_Serial_Mode_MS_CONFIG) { return 250; } #endif diff --git a/src/mesh/StreamAPI.h b/src/mesh/StreamAPI.h index 547dd0175..4ca2c197f 100644 --- a/src/mesh/StreamAPI.h +++ b/src/mesh/StreamAPI.h @@ -50,15 +50,15 @@ class StreamAPI : public PhoneAPI * phone. */ virtual int32_t runOncePart(); - virtual int32_t runOncePart(char *buf,uint16_t bufLen); + virtual int32_t runOncePart(char *buf, uint16_t bufLen); private: /** * Read any rx chars from the link and call handleToRadio */ int32_t readStream(); - int32_t readStream(char *buf,uint16_t bufLen); - int32_t handleRecStream(char *buf,uint16_t bufLen); + int32_t readStream(char *buf, uint16_t bufLen); + int32_t handleRecStream(char *buf, uint16_t bufLen); /** * call getFromRadio() and deliver encapsulated packets to the Stream diff --git a/variants/esp32s3/t-deck-pro/variant.h b/variants/esp32s3/t-deck-pro/variant.h index abe0a772a..35cb99435 100644 --- a/variants/esp32s3/t-deck-pro/variant.h +++ b/variants/esp32s3/t-deck-pro/variant.h @@ -93,11 +93,10 @@ // Internally the TTGO module hooks the SX1262-DIO2 in to control the TX/RX switch (which is the default for the sx1262interface // code) -#define MODEM_POWER_EN 41 -#define MODEM_PWRKEY 40 -#define MODEM_RST 9 -#define MODEM_RI 7 -#define MODEM_DTR 8 -#define MODEM_RX 10 -#define MODEM_TX 11 - +#define MODEM_POWER_EN 41 +#define MODEM_PWRKEY 40 +#define MODEM_RST 9 +#define MODEM_RI 7 +#define MODEM_DTR 8 +#define MODEM_RX 10 +#define MODEM_TX 11 diff --git a/variants/nrf52840/heltec_mesh_solar/variant.cpp b/variants/nrf52840/heltec_mesh_solar/variant.cpp index 8236d7cf4..05d7a32e2 100644 --- a/variants/nrf52840/heltec_mesh_solar/variant.cpp +++ b/variants/nrf52840/heltec_mesh_solar/variant.cpp @@ -32,5 +32,5 @@ const uint32_t g_ADigitalPinMap[] = { void initVariant() { - pinMode(BQ4050_EMERGENCY_SHUTDOWN_PIN, INPUT); + pinMode(BQ4050_EMERGENCY_SHUTDOWN_PIN, INPUT); } diff --git a/variants/nrf52840/heltec_mesh_solar/variant.h b/variants/nrf52840/heltec_mesh_solar/variant.h index 33c2b2556..4165bc349 100644 --- a/variants/nrf52840/heltec_mesh_solar/variant.h +++ b/variants/nrf52840/heltec_mesh_solar/variant.h @@ -39,16 +39,15 @@ extern "C" { #define NUM_ANALOG_INPUTS (1) #define NUM_ANALOG_OUTPUTS (0) - #define PIN_LED1 (0 + 12) // green (confirmed on 1.0 board) #define LED_BLUE PIN_LED1 // fake for bluefruit library #define LED_GREEN PIN_LED1 #define LED_BUILTIN LED_GREEN -#define LED_STATE_ON 0 // State when LED is lit +#define LED_STATE_ON 0 // State when LED is lit #define HAS_NEOPIXEL // Enable the use of neopixels #define NEOPIXEL_COUNT 1 // How many neopixels are connected -#define NEOPIXEL_DATA (32+15) // gpio pin used to send data to the neopixels +#define NEOPIXEL_DATA (32 + 15) // gpio pin used to send data to the neopixels #define NEOPIXEL_TYPE (NEO_GRB + NEO_KHZ800) // type of neopixels in use /* @@ -74,13 +73,13 @@ No longer populated on PCB // I2C bus 0 // Routed to footprint for PCF8563TS RTC // Not populated on T114 V1, maybe in future? -#define PIN_WIRE_SDA (0 + 6) // P0.26 +#define PIN_WIRE_SDA (0 + 6) // P0.26 #define PIN_WIRE_SCL (0 + 26) // P0.26 // I2C bus 1 // Available on header pins, for general use #define PIN_WIRE1_SDA (0 + 30) // P0.30 -#define PIN_WIRE1_SCL (0 + 5) // P0.13 +#define PIN_WIRE1_SCL (0 + 5) // P0.13 /* * Lora radio @@ -89,14 +88,14 @@ No longer populated on PCB #define USE_SX1262 // #define USE_SX1268 #define SX126X_CS (0 + 24) // FIXME - we really should define LORA_CS instead -#define LORA_CS (0 + 24) +#define LORA_CS (0 + 24) #define SX126X_DIO1 (0 + 20) // Note DIO2 is attached internally to the module to an analog switch for TX/RX switching // #define SX1262_DIO3 (0 + 21) // This is used as an *output* from the sx1262 and connected internally to power the tcxo, do not drive from the // main // CPU? -#define SX126X_BUSY (0 + 17) +#define SX126X_BUSY (0 + 17) #define SX126X_RESET (0 + 25) // Not really an E22 but TTGO seems to be trying to clone that #define SX126X_DIO2_AS_RF_SWITCH @@ -134,16 +133,16 @@ No longer populated on PCB // For LORA, spi 0 #define PIN_SPI_MISO (0 + 23) #define PIN_SPI_MOSI (0 + 22) -#define PIN_SPI_SCK (0 + 19) +#define PIN_SPI_SCK (0 + 19) // #define PIN_PWR_EN (0 + 6) // To debug via the segger JLINK console rather than the CDC-ACM serial device // #define USE_SEGGER -#define BQ4050_SDA_PIN (32+1) // I2C data line pin -#define BQ4050_SCL_PIN (32+0) // I2C clock line pin -#define BQ4050_EMERGENCY_SHUTDOWN_PIN (32+3) // Emergency shutdown pin +#define BQ4050_SDA_PIN (32 + 1) // I2C data line pin +#define BQ4050_SCL_PIN (32 + 0) // I2C clock line pin +#define BQ4050_EMERGENCY_SHUTDOWN_PIN (32 + 3) // Emergency shutdown pin #define HAS_RTC 0 #ifdef __cplusplus From 6c89ea7cee2f7422bddf96d8367112b505c51df5 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Wed, 3 Sep 2025 06:16:00 -0500 Subject: [PATCH 078/691] 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 079/691] 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 080/691] 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 081/691] 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 082/691] 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 083/691] 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 084/691] 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 085/691] 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 086/691] 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 087/691] 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 088/691] 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 089/691] 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 090/691] 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 091/691] 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 092/691] 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 093/691] 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 094/691] 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 095/691] 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 096/691] 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 097/691] 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 098/691] 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 099/691] 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 100/691] 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 101/691] 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 102/691] 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 103/691] 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 104/691] 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 105/691] 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 106/691] 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 107/691] 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 108/691] 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 109/691] 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 110/691] 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 111/691] 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 112/691] 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 113/691] 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 114/691] 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 115/691] 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 116/691] 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 117/691] 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 118/691] 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 119/691] 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 120/691] 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 121/691] 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 122/691] 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 123/691] =?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 124/691] 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 125/691] 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 126/691] 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 127/691] 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 128/691] 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 129/691] 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 130/691] 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 131/691] 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 132/691] 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 133/691] 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 134/691] 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 135/691] 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 136/691] 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 137/691] 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 138/691] 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 139/691] 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

From d5bb566276606a2f43cdf31e36b002a793d84538 Mon Sep 17 00:00:00 2001
From: Tom Fifield 
Date: Mon, 8 Sep 2025 20:53:49 +1000
Subject: [PATCH 140/691] 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 9ae7ae97d..88984a890 100644
--- a/src/gps/GPS.cpp
+++ b/src/gps/GPS.cpp
@@ -1532,10 +1532,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 6c697806154258e2ec590e4709b3cba0d5263003 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Mon, 8 Sep 2025 16:52:21 -0500
Subject: [PATCH 141/691] chore(deps): update meshtastic/device-ui digest to
 3677476 (#7925)

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 16bb0eb96..81f95a7e3 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/233d18ef42e9d189f90fdfe621f0cd7edff2d221.zip
+	https://github.com/meshtastic/device-ui/archive/3677476c8a823ee85056b5fb1d146a3e193f8276.zip
 
 ; Common libs for environmental measurements in telemetry module
 [environmental_base]

From 803e96800e9d348d7003170c7c65c0ccdae30b33 Mon Sep 17 00:00:00 2001
From: Ben Meadors 
Date: Mon, 8 Sep 2025 17:21:55 -0500
Subject: [PATCH 142/691] ATAK module should be disabled for non-TAK roles
 (#7928)

---
 src/modules/Modules.cpp | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/src/modules/Modules.cpp b/src/modules/Modules.cpp
index b9b4dd3e5..85d183aef 100644
--- a/src/modules/Modules.cpp
+++ b/src/modules/Modules.cpp
@@ -141,7 +141,10 @@ void setupModules()
         detectionSensorModule = new DetectionSensorModule();
 #endif
 #if !MESHTASTIC_EXCLUDE_ATAK
-        atakPluginModule = new AtakPluginModule();
+        if (IS_ONE_OF(config.device.role, meshtastic_Config_DeviceConfig_Role_TAK,
+                      meshtastic_Config_DeviceConfig_Role_TAK_TRACKER)) {
+            atakPluginModule = new AtakPluginModule();
+        }
 #endif
 #if !MESHTASTIC_EXCLUDE_PKI
         keyVerificationModule = new KeyVerificationModule();

From b75e8913e0854dd6e78fb2735e2e2adc7c77e675 Mon Sep 17 00:00:00 2001
From: ford-jones 
Date: Tue, 9 Sep 2025 13:14:20 +1200
Subject: [PATCH 143/691] Fix: Compile latest protobufs

---
 src/mesh/generated/meshtastic/config.pb.h    | 10 +++++-----
 src/mesh/generated/meshtastic/telemetry.pb.h |  8 +++++---
 2 files changed, 10 insertions(+), 8 deletions(-)

diff --git a/src/mesh/generated/meshtastic/config.pb.h b/src/mesh/generated/meshtastic/config.pb.h
index c8202bdc9..59e55db3f 100644
--- a/src/mesh/generated/meshtastic/config.pb.h
+++ b/src/mesh/generated/meshtastic/config.pb.h
@@ -212,10 +212,10 @@ typedef enum _meshtastic_Config_DisplayConfig_OledType {
     meshtastic_Config_DisplayConfig_OledType_OLED_SSD1306 = 1,
     /* Default / Autodetect */
     meshtastic_Config_DisplayConfig_OledType_OLED_SH1106 = 2,
-    /* Can not be auto detected but set by proto. Used for 128x128 screens */
-    meshtastic_Config_DisplayConfig_OledType_OLED_SH1107 = 3,
     /* Can not be auto detected but set by proto. Used for 128x64 screens */
-    meshtastic_Config_DisplayConfig_OledType_OLED_SH1107_128_64 = 4
+    meshtastic_Config_DisplayConfig_OledType_OLED_SH1107 = 3,
+    /* Can not be auto detected but set by proto. Used for 128x128 screens */
+    meshtastic_Config_DisplayConfig_OledType_OLED_SH1107_128_128 = 4
 } meshtastic_Config_DisplayConfig_OledType;
 
 typedef enum _meshtastic_Config_DisplayConfig_DisplayMode {
@@ -687,8 +687,8 @@ extern "C" {
 #define _meshtastic_Config_DisplayConfig_DisplayUnits_ARRAYSIZE ((meshtastic_Config_DisplayConfig_DisplayUnits)(meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL+1))
 
 #define _meshtastic_Config_DisplayConfig_OledType_MIN meshtastic_Config_DisplayConfig_OledType_OLED_AUTO
-#define _meshtastic_Config_DisplayConfig_OledType_MAX meshtastic_Config_DisplayConfig_OledType_OLED_SH1107_128_64
-#define _meshtastic_Config_DisplayConfig_OledType_ARRAYSIZE ((meshtastic_Config_DisplayConfig_OledType)(meshtastic_Config_DisplayConfig_OledType_OLED_SH1107_128_64+1))
+#define _meshtastic_Config_DisplayConfig_OledType_MAX meshtastic_Config_DisplayConfig_OledType_OLED_SH1107_128_128
+#define _meshtastic_Config_DisplayConfig_OledType_ARRAYSIZE ((meshtastic_Config_DisplayConfig_OledType)(meshtastic_Config_DisplayConfig_OledType_OLED_SH1107_128_128+1))
 
 #define _meshtastic_Config_DisplayConfig_DisplayMode_MIN meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT
 #define _meshtastic_Config_DisplayConfig_DisplayMode_MAX meshtastic_Config_DisplayConfig_DisplayMode_COLOR
diff --git a/src/mesh/generated/meshtastic/telemetry.pb.h b/src/mesh/generated/meshtastic/telemetry.pb.h
index f758995c2..9af095e78 100644
--- a/src/mesh/generated/meshtastic/telemetry.pb.h
+++ b/src/mesh/generated/meshtastic/telemetry.pb.h
@@ -99,7 +99,9 @@ typedef enum _meshtastic_TelemetrySensorType {
     /* Sensirion SFA30 Formaldehyde sensor */
     meshtastic_TelemetrySensorType_SFA30 = 42,
     /* SEN5X PM SENSORS */
-    meshtastic_TelemetrySensorType_SEN5X = 43
+    meshtastic_TelemetrySensorType_SEN5X = 43,
+    /* TSL2561 light sensor */
+    meshtastic_TelemetrySensorType_TSL2561 = 44
 } meshtastic_TelemetrySensorType;
 
 /* Struct definitions */
@@ -434,8 +436,8 @@ extern "C" {
 
 /* Helper constants for enums */
 #define _meshtastic_TelemetrySensorType_MIN meshtastic_TelemetrySensorType_SENSOR_UNSET
-#define _meshtastic_TelemetrySensorType_MAX meshtastic_TelemetrySensorType_SEN5X
-#define _meshtastic_TelemetrySensorType_ARRAYSIZE ((meshtastic_TelemetrySensorType)(meshtastic_TelemetrySensorType_SEN5X+1))
+#define _meshtastic_TelemetrySensorType_MAX meshtastic_TelemetrySensorType_TSL2561
+#define _meshtastic_TelemetrySensorType_ARRAYSIZE ((meshtastic_TelemetrySensorType)(meshtastic_TelemetrySensorType_TSL2561+1))
 
 
 

From 2191fe465c3e0407579b6dd0d740f39490586f5e Mon Sep 17 00:00:00 2001
From: Wilson 
Date: Tue, 9 Sep 2025 14:20:24 +0800
Subject: [PATCH 144/691] Remove board_level from Meshtiny. (#7933)

---
 variants/nrf52840/meshtiny/platformio.ini | 1 -
 1 file changed, 1 deletion(-)

diff --git a/variants/nrf52840/meshtiny/platformio.ini b/variants/nrf52840/meshtiny/platformio.ini
index 5f03f5cb2..f14d1b229 100644
--- a/variants/nrf52840/meshtiny/platformio.ini
+++ b/variants/nrf52840/meshtiny/platformio.ini
@@ -2,7 +2,6 @@
 [env:meshtiny]
 extends = nrf52840_base
 board = meshtiny
-board_level = extra
 build_flags = ${nrf52840_base.build_flags} -Ivariants/nrf52840/meshtiny -D MESHTINY
   -D USE_PIN_BUZZER
   -D MESHTASTIC_EXCLUDE_GPS=1

From 1643249db79a56db8ca64dbe77b44f96b0ff1be4 Mon Sep 17 00:00:00 2001
From: Ben Meadors 
Date: Tue, 9 Sep 2025 05:48:05 -0500
Subject: [PATCH 145/691] Revert "Remove board_level from Meshtiny. (#7933)"
 (#7935)

This reverts commit 2191fe465c3e0407579b6dd0d740f39490586f5e.
---
 variants/nrf52840/meshtiny/platformio.ini | 1 +
 1 file changed, 1 insertion(+)

diff --git a/variants/nrf52840/meshtiny/platformio.ini b/variants/nrf52840/meshtiny/platformio.ini
index f14d1b229..5f03f5cb2 100644
--- a/variants/nrf52840/meshtiny/platformio.ini
+++ b/variants/nrf52840/meshtiny/platformio.ini
@@ -2,6 +2,7 @@
 [env:meshtiny]
 extends = nrf52840_base
 board = meshtiny
+board_level = extra
 build_flags = ${nrf52840_base.build_flags} -Ivariants/nrf52840/meshtiny -D MESHTINY
   -D USE_PIN_BUZZER
   -D MESHTASTIC_EXCLUDE_GPS=1

From c8afbe68b535b5f76201b0f78320336682f8edd3 Mon Sep 17 00:00:00 2001
From: Ben Meadors 
Date: Tue, 9 Sep 2025 06:34:38 -0500
Subject: [PATCH 146/691] Use char buffer for probeResponse (#7870)

* Use char buffer for probeResponse

* \Update src/gps/GPS.cpp

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Revert "\Update src/gps/GPS.cpp"

This reverts commit 54d64e19f710c2971347507bff5e506b2209602f.

* Remove string

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---
 src/gps/GPS.cpp | 29 ++++++++++++++++++-----------
 1 file changed, 18 insertions(+), 11 deletions(-)

diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp
index 88984a890..d4e9076d9 100644
--- a/src/gps/GPS.cpp
+++ b/src/gps/GPS.cpp
@@ -1,5 +1,4 @@
 #include  // Include for strstr
-#include 
 #include 
 
 #include "configuration.h"
@@ -1370,34 +1369,42 @@ GnssModel_t GPS::probe(int serialSpeed)
 
 GnssModel_t GPS::getProbeResponse(unsigned long timeout, const std::vector &responseMap)
 {
-    String response = "";
+    char response[256] = {0}; // Fixed buffer instead of String
+    uint16_t responseLen = 0;
     unsigned long start = millis();
     while (millis() - start < timeout) {
         if (_serial_gps->available()) {
-            response += (char)_serial_gps->read();
+            char c = _serial_gps->read();
 
-            if (response.endsWith(",") || response.endsWith("\r\n")) {
+            // Add char to buffer if there's space
+            if (responseLen < sizeof(response) - 1) {
+                response[responseLen++] = c;
+                response[responseLen] = '\0';
+            }
+
+            if (c == ',' || (responseLen >= 2 && response[responseLen - 2] == '\r' && response[responseLen - 1] == '\n')) {
 #ifdef GPS_DEBUG
-                LOG_DEBUG(response.c_str());
+                LOG_DEBUG(response);
 #endif
                 // check if we can see our chips
                 for (const auto &chipInfo : responseMap) {
-                    if (strstr(response.c_str(), chipInfo.detectionString.c_str()) != nullptr) {
+                    if (strstr(response, chipInfo.detectionString.c_str()) != nullptr) {
                         LOG_INFO("%s detected", chipInfo.chipName.c_str());
                         return chipInfo.driver;
                     }
                 }
             }
-            if (response.endsWith("\r\n")) {
-                response.trim();
-                response = ""; // Reset the response string for the next potential message
+            if (responseLen >= 2 && response[responseLen - 2] == '\r' && response[responseLen - 1] == '\n') {
+                // Reset the response buffer for the next potential message
+                responseLen = 0;
+                response[0] = '\0';
             }
         }
     }
 #ifdef GPS_DEBUG
-    LOG_DEBUG(response.c_str());
+    LOG_DEBUG(response);
 #endif
-    return GNSS_MODEL_UNKNOWN; // Return empty string on timeout
+    return GNSS_MODEL_UNKNOWN; // Return unknown on timeout
 }
 
 GPS *GPS::createGps()

From d1d16fc25f0d58027c151733b75772041511b580 Mon Sep 17 00:00:00 2001
From: Ben Meadors 
Date: Tue, 9 Sep 2025 08:21:46 -0500
Subject: [PATCH 147/691] Make phone queues use a static pointer queue (#7919)

* Make phone queues use a static pointer queue

* Static init

* Compile time constants now

* Instead, lets just use the normal pointerqueue for linux native builds and static for IoT platforms

* Add missing method

* Missing methods

* Update variant.h
---
 src/mesh/MeshService.cpp                      |   6 +-
 src/mesh/MeshService.h                        |  21 +++
 src/mesh/StaticPointerQueue.h                 |  77 +++++++++++
 src/mesh/mesh-pb-constants.h                  |  15 +++
 .../ports/test_text_message.cpp               | 127 +++++++++++++-----
 variants/native/portduino-buildroot/variant.h |   2 +-
 6 files changed, 213 insertions(+), 35 deletions(-)
 create mode 100644 src/mesh/StaticPointerQueue.h

diff --git a/src/mesh/MeshService.cpp b/src/mesh/MeshService.cpp
index 2cc4197c1..7e35fccd8 100644
--- a/src/mesh/MeshService.cpp
+++ b/src/mesh/MeshService.cpp
@@ -61,8 +61,10 @@ Allocator &queueStatusPool = staticQueueStatusPool;
 #include "Router.h"
 
 MeshService::MeshService()
-    : toPhoneQueue(MAX_RX_TOPHONE), toPhoneQueueStatusQueue(MAX_RX_TOPHONE), toPhoneMqttProxyQueue(MAX_RX_TOPHONE),
-      toPhoneClientNotificationQueue(MAX_RX_TOPHONE / 2)
+#ifdef ARCH_PORTDUINO
+    : toPhoneQueue(MAX_RX_TOPHONE), toPhoneQueueStatusQueue(MAX_RX_QUEUESTATUS_TOPHONE),
+      toPhoneMqttProxyQueue(MAX_RX_MQTTPROXY_TOPHONE), toPhoneClientNotificationQueue(MAX_RX_NOTIFICATION_TOPHONE)
+#endif
 {
     lastQueueStatus = {0, 0, 16, 0};
 }
diff --git a/src/mesh/MeshService.h b/src/mesh/MeshService.h
index f7d79366e..5d074368f 100644
--- a/src/mesh/MeshService.h
+++ b/src/mesh/MeshService.h
@@ -9,7 +9,12 @@
 #include "MeshRadio.h"
 #include "MeshTypes.h"
 #include "Observer.h"
+#ifdef ARCH_PORTDUINO
 #include "PointerQueue.h"
+#else
+#include "StaticPointerQueue.h"
+#endif
+#include "mesh-pb-constants.h"
 #if defined(ARCH_PORTDUINO)
 #include "../platform/portduino/SimRadio.h"
 #endif
@@ -37,16 +42,32 @@ class MeshService
     /// FIXME, change to a DropOldestQueue and keep a count of the number of dropped packets to ensure
     /// we never hang because android hasn't been there in a while
     /// FIXME - save this to flash on deep sleep
+#ifdef ARCH_PORTDUINO
     PointerQueue toPhoneQueue;
+#else
+    StaticPointerQueue toPhoneQueue;
+#endif
 
     // keep list of QueueStatus packets to be send to the phone
+#ifdef ARCH_PORTDUINO
     PointerQueue toPhoneQueueStatusQueue;
+#else
+    StaticPointerQueue toPhoneQueueStatusQueue;
+#endif
 
     // keep list of MqttClientProxyMessages to be send to the client for delivery
+#ifdef ARCH_PORTDUINO
     PointerQueue toPhoneMqttProxyQueue;
+#else
+    StaticPointerQueue toPhoneMqttProxyQueue;
+#endif
 
     // keep list of ClientNotifications to be send to the client (phone)
+#ifdef ARCH_PORTDUINO
     PointerQueue toPhoneClientNotificationQueue;
+#else
+    StaticPointerQueue toPhoneClientNotificationQueue;
+#endif
 
     // This holds the last QueueStatus send
     meshtastic_QueueStatus lastQueueStatus;
diff --git a/src/mesh/StaticPointerQueue.h b/src/mesh/StaticPointerQueue.h
new file mode 100644
index 000000000..398ee450c
--- /dev/null
+++ b/src/mesh/StaticPointerQueue.h
@@ -0,0 +1,77 @@
+#pragma once
+
+#include "concurrency/OSThread.h"
+#include "freertosinc.h"
+#include 
+
+/**
+ * A static circular buffer queue for pointers.
+ * This provides the same interface as PointerQueue but uses a statically allocated
+ * buffer instead of dynamic allocation.
+ */
+template  class StaticPointerQueue
+{
+    static_assert(MaxElements > 0, "MaxElements must be greater than 0");
+
+    T *buffer[MaxElements];
+    int head = 0;
+    int tail = 0;
+    int count = 0;
+    concurrency::OSThread *reader = nullptr;
+
+  public:
+    StaticPointerQueue()
+    {
+        // Initialize all buffer elements to nullptr to silence warnings and ensure clean state
+        for (int i = 0; i < MaxElements; i++) {
+            buffer[i] = nullptr;
+        }
+    }
+
+    int numFree() const { return MaxElements - count; }
+
+    bool isEmpty() const { return count == 0; }
+
+    int numUsed() const { return count; }
+
+    bool enqueue(T *x, TickType_t maxWait = portMAX_DELAY)
+    {
+        if (count >= MaxElements) {
+            return false; // Queue is full
+        }
+
+        if (reader) {
+            reader->setInterval(0);
+            concurrency::mainDelay.interrupt();
+        }
+
+        buffer[tail] = x;
+        tail = (tail + 1) % MaxElements;
+        count++;
+        return true;
+    }
+
+    bool dequeue(T **p, TickType_t maxWait = portMAX_DELAY)
+    {
+        if (count == 0) {
+            return false; // Queue is empty
+        }
+
+        *p = buffer[head];
+        head = (head + 1) % MaxElements;
+        count--;
+        return true;
+    }
+
+    // returns a ptr or null if the queue was empty
+    T *dequeuePtr(TickType_t maxWait = portMAX_DELAY)
+    {
+        T *p;
+        return dequeue(&p, maxWait) ? p : nullptr;
+    }
+
+    void setReader(concurrency::OSThread *t) { reader = t; }
+
+    // For compatibility with PointerQueue interface
+    int getMaxLen() const { return MaxElements; }
+};
diff --git a/src/mesh/mesh-pb-constants.h b/src/mesh/mesh-pb-constants.h
index 08c03dc6b..224f45de2 100644
--- a/src/mesh/mesh-pb-constants.h
+++ b/src/mesh/mesh-pb-constants.h
@@ -18,6 +18,21 @@
 #define MAX_RX_TOPHONE 32
 #endif
 
+/// max number of QueueStatus packets which can be waiting for delivery to phone
+#ifndef MAX_RX_QUEUESTATUS_TOPHONE
+#define MAX_RX_QUEUESTATUS_TOPHONE 4
+#endif
+
+/// max number of MqttClientProxyMessage packets which can be waiting for delivery to phone
+#ifndef MAX_RX_MQTTPROXY_TOPHONE
+#define MAX_RX_MQTTPROXY_TOPHONE 32
+#endif
+
+/// max number of ClientNotification packets which can be waiting for delivery to phone
+#ifndef MAX_RX_NOTIFICATION_TOPHONE
+#define MAX_RX_NOTIFICATION_TOPHONE 4
+#endif
+
 /// Verify baseline assumption of node size. If it increases, we need to reevaluate
 /// the impact of its memory footprint, notably on MAX_NUM_NODES.
 static_assert(sizeof(meshtastic_NodeInfoLite) <= 200, "NodeInfoLite size increased. Reconsider impact on MAX_NUM_NODES.");
diff --git a/test/test_meshpacket_serializer/ports/test_text_message.cpp b/test/test_meshpacket_serializer/ports/test_text_message.cpp
index de3f34541..0f3b0bc6d 100644
--- a/test/test_meshpacket_serializer/ports/test_text_message.cpp
+++ b/test/test_meshpacket_serializer/ports/test_text_message.cpp
@@ -1,42 +1,105 @@
 #include "../test_helpers.h"
+#include 
+
+// Helper function to test common packet fields and structure
+void verify_text_message_packet_structure(const std::string &json, const char *expected_text)
+{
+    TEST_ASSERT_TRUE(json.length() > 0);
+
+    // Use smart pointer for automatic memory management
+    std::unique_ptr root(JSON::Parse(json.c_str()));
+    TEST_ASSERT_NOT_NULL(root.get());
+    TEST_ASSERT_TRUE(root->IsObject());
+
+    JSONObject jsonObj = root->AsObject();
+
+    // Check basic packet fields - use helper function to reduce duplication
+    auto check_field = [&](const char *field, uint32_t expected_value) {
+        auto it = jsonObj.find(field);
+        TEST_ASSERT_TRUE(it != jsonObj.end());
+        TEST_ASSERT_EQUAL(expected_value, (uint32_t)it->second->AsNumber());
+    };
+
+    check_field("from", 0x11223344);
+    check_field("to", 0x55667788);
+    check_field("id", 0x9999);
+
+    // Check message type
+    auto type_it = jsonObj.find("type");
+    TEST_ASSERT_TRUE(type_it != jsonObj.end());
+    TEST_ASSERT_EQUAL_STRING("text", type_it->second->AsString().c_str());
+
+    // Check payload
+    auto payload_it = jsonObj.find("payload");
+    TEST_ASSERT_TRUE(payload_it != jsonObj.end());
+    TEST_ASSERT_TRUE(payload_it->second->IsObject());
+
+    JSONObject payload = payload_it->second->AsObject();
+    auto text_it = payload.find("text");
+    TEST_ASSERT_TRUE(text_it != payload.end());
+    TEST_ASSERT_EQUAL_STRING(expected_text, text_it->second->AsString().c_str());
+
+    // No need for manual delete with smart pointer
+}
 
 // Test TEXT_MESSAGE_APP port
 void test_text_message_serialization()
 {
     const char *test_text = "Hello Meshtastic!";
     meshtastic_MeshPacket packet =
-        create_test_packet(meshtastic_PortNum_TEXT_MESSAGE_APP, (const uint8_t *)test_text, strlen(test_text));
+        create_test_packet(meshtastic_PortNum_TEXT_MESSAGE_APP, reinterpret_cast(test_text), strlen(test_text));
 
     std::string json = MeshPacketSerializer::JsonSerialize(&packet, false);
-    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 message type
-    TEST_ASSERT_TRUE(jsonObj.find("type") != jsonObj.end());
-    TEST_ASSERT_EQUAL_STRING("text", jsonObj["type"]->AsString().c_str());
-
-    // Check payload
-    TEST_ASSERT_TRUE(jsonObj.find("payload") != jsonObj.end());
-    TEST_ASSERT_TRUE(jsonObj["payload"]->IsObject());
-
-    JSONObject payload = jsonObj["payload"]->AsObject();
-    TEST_ASSERT_TRUE(payload.find("text") != payload.end());
-    TEST_ASSERT_EQUAL_STRING("Hello Meshtastic!", payload["text"]->AsString().c_str());
-
-    delete root;
+    verify_text_message_packet_structure(json, test_text);
 }
+
+// Test with nullptr to check robustness
+void test_text_message_serialization_null()
+{
+    meshtastic_MeshPacket packet = create_test_packet(meshtastic_PortNum_TEXT_MESSAGE_APP, nullptr, 0);
+
+    std::string json = MeshPacketSerializer::JsonSerialize(&packet, false);
+    verify_text_message_packet_structure(json, "");
+}
+
+// Test TEXT_MESSAGE_APP port with very long message (boundary testing)
+void test_text_message_serialization_long_text()
+{
+    // Test with actual message size limits
+    constexpr size_t MAX_MESSAGE_SIZE = 200; // Typical LoRa payload limit
+    std::string long_text(MAX_MESSAGE_SIZE, 'A');
+
+    meshtastic_MeshPacket packet = create_test_packet(meshtastic_PortNum_TEXT_MESSAGE_APP,
+                                                      reinterpret_cast(long_text.c_str()), long_text.length());
+
+    std::string json = MeshPacketSerializer::JsonSerialize(&packet, false);
+    verify_text_message_packet_structure(json, long_text.c_str());
+}
+
+// Test with message over size limit (should fail)
+void test_text_message_serialization_oversized()
+{
+    constexpr size_t OVERSIZED_MESSAGE = 250; // Over the limit
+    std::string oversized_text(OVERSIZED_MESSAGE, 'B');
+
+    meshtastic_MeshPacket packet = create_test_packet(
+        meshtastic_PortNum_TEXT_MESSAGE_APP, reinterpret_cast(oversized_text.c_str()), oversized_text.length());
+
+    // Should fail or return empty/error
+    std::string json = MeshPacketSerializer::JsonSerialize(&packet, false);
+    // Should only verify first 234 characters for oversized messages
+    std::string expected_text = oversized_text.substr(0, 234);
+    verify_text_message_packet_structure(json, expected_text.c_str());
+}
+
+// Add test for malformed UTF-8 sequences
+void test_text_message_serialization_invalid_utf8()
+{
+    const uint8_t invalid_utf8[] = {0xFF, 0xFE, 0xFD, 0x00}; // Invalid UTF-8
+    meshtastic_MeshPacket packet =
+        create_test_packet(meshtastic_PortNum_TEXT_MESSAGE_APP, invalid_utf8, sizeof(invalid_utf8) - 1);
+
+    // Should not crash, may produce replacement characters
+    std::string json = MeshPacketSerializer::JsonSerialize(&packet, false);
+    TEST_ASSERT_TRUE(json.length() > 0);
+}
\ No newline at end of file
diff --git a/variants/native/portduino-buildroot/variant.h b/variants/native/portduino-buildroot/variant.h
index b7b39d6e8..11a6c0bd3 100644
--- a/variants/native/portduino-buildroot/variant.h
+++ b/variants/native/portduino-buildroot/variant.h
@@ -2,4 +2,4 @@
 #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]

From e7741c20e4dd5b3345eace8e6604ed1e93bb3c28 Mon Sep 17 00:00:00 2001
From: Jonathan Bennett 
Date: Tue, 9 Sep 2025 10:29:07 -0500
Subject: [PATCH 148/691] Add LOG_HEAP log type, and more heap debug messages
 (#7937)

---
 src/DebugConfiguration.h                  |  7 +++++++
 src/Power.cpp                             |  6 +++---
 src/concurrency/OSThread.cpp              |  4 ++--
 src/mesh/MemoryPool.h                     |  2 ++
 src/mesh/MeshService.cpp                  | 13 +++++++++++--
 src/mesh/ReliableRouter.cpp               |  6 +++++-
 src/mesh/Router.cpp                       |  8 ++++++++
 src/modules/NodeInfoModule.cpp            |  4 ++++
 src/modules/Telemetry/DeviceTelemetry.cpp |  5 +++++
 9 files changed, 47 insertions(+), 8 deletions(-)

diff --git a/src/DebugConfiguration.h b/src/DebugConfiguration.h
index a34710eb0..26f2db1f4 100644
--- a/src/DebugConfiguration.h
+++ b/src/DebugConfiguration.h
@@ -23,6 +23,7 @@
 #define MESHTASTIC_LOG_LEVEL_ERROR "ERROR"
 #define MESHTASTIC_LOG_LEVEL_CRIT "CRIT "
 #define MESHTASTIC_LOG_LEVEL_TRACE "TRACE"
+#define MESHTASTIC_LOG_LEVEL_HEAP "HEAP"
 
 #include "SerialConsole.h"
 
@@ -62,6 +63,12 @@
 #endif
 #endif
 
+#if defined(DEBUG_HEAP)
+#define LOG_HEAP(...) DEBUG_PORT.log(MESHTASTIC_LOG_LEVEL_HEAP, __VA_ARGS__)
+#else
+#define LOG_HEAP(...)
+#endif
+
 /// A C wrapper for LOG_DEBUG that can be used from arduino C libs that don't know about C++ or meshtastic
 extern "C" void logLegacy(const char *level, const char *fmt, ...);
 
diff --git a/src/Power.cpp b/src/Power.cpp
index 06c6a9089..7de82b8d6 100644
--- a/src/Power.cpp
+++ b/src/Power.cpp
@@ -851,9 +851,9 @@ void Power::readPowerStatus()
                 running++;
             }
         }
-        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));
+        LOG_HEAP(threadlist);
+        LOG_HEAP("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();
     }
 #ifdef DEBUG_HEAP_MQTT
diff --git a/src/concurrency/OSThread.cpp b/src/concurrency/OSThread.cpp
index d9bb901b2..5aee03bbf 100644
--- a/src/concurrency/OSThread.cpp
+++ b/src/concurrency/OSThread.cpp
@@ -86,9 +86,9 @@ void OSThread::run()
 #ifdef DEBUG_HEAP
     auto newHeap = memGet.getFreeHeap();
     if (newHeap < heap)
-        LOG_DEBUG("------ Thread %s leaked heap %d -> %d (%d) ------", ThreadName.c_str(), heap, newHeap, newHeap - heap);
+        LOG_HEAP("------ Thread %s leaked heap %d -> %d (%d) ------", ThreadName.c_str(), heap, newHeap, newHeap - heap);
     if (heap < newHeap)
-        LOG_DEBUG("++++++ Thread %s freed heap %d -> %d (%d) ++++++", ThreadName.c_str(), heap, newHeap, newHeap - heap);
+        LOG_HEAP("++++++ Thread %s freed heap %d -> %d (%d) ++++++", ThreadName.c_str(), heap, newHeap, newHeap - heap);
 #endif
 
     runned();
diff --git a/src/mesh/MemoryPool.h b/src/mesh/MemoryPool.h
index c4af3c4ac..ea7c8f583 100644
--- a/src/mesh/MemoryPool.h
+++ b/src/mesh/MemoryPool.h
@@ -84,6 +84,8 @@ template  class MemoryDynamic : public Allocator
     virtual void release(T *p) override
     {
         assert(p);
+        LOG_HEAP("Freeing 0x%x", p);
+
         free(p);
     }
 
diff --git a/src/mesh/MeshService.cpp b/src/mesh/MeshService.cpp
index 7e35fccd8..157a2eda3 100644
--- a/src/mesh/MeshService.cpp
+++ b/src/mesh/MeshService.cpp
@@ -193,8 +193,12 @@ void MeshService::handleToRadio(meshtastic_MeshPacket &p)
                                                  // (so we update our nodedb for the local node)
 
     // Send the packet into the mesh
+    auto heapBefore = memGet.getFreeHeap();
+    auto a = packetPool.allocCopy(p);
+    auto heapAfter = memGet.getFreeHeap();
+    LOG_HEAP("Alloc in MeshService::handleToRadio() pointer 0x%x, size: %u, free: %u", a, heapBefore - heapAfter, heapAfter);
 
-    sendToMesh(packetPool.allocCopy(p), RX_SRC_USER);
+    sendToMesh(a, RX_SRC_USER);
 
     bool loopback = false; // if true send any packet the phone sends back itself (for testing)
     if (loopback) {
@@ -250,7 +254,12 @@ void MeshService::sendToMesh(meshtastic_MeshPacket *p, RxSource src, bool ccToPh
     }
 
     if ((res == ERRNO_OK || res == ERRNO_SHOULD_RELEASE) && ccToPhone) { // Check if p is not released in case it couldn't be sent
-        sendToPhone(packetPool.allocCopy(*p));
+        auto heapBefore = memGet.getFreeHeap();
+        auto a = packetPool.allocCopy(*p);
+        auto heapAfter = memGet.getFreeHeap();
+        LOG_HEAP("Alloc in MeshService::sendToMesh() pointer 0x%x, size: %u, free: %u", a, heapBefore - heapAfter, heapAfter);
+
+        sendToPhone(a);
     }
 
     // Router may ask us to release the packet if it wasn't sent
diff --git a/src/mesh/ReliableRouter.cpp b/src/mesh/ReliableRouter.cpp
index e9ceeaef1..890d42b00 100644
--- a/src/mesh/ReliableRouter.cpp
+++ b/src/mesh/ReliableRouter.cpp
@@ -2,6 +2,7 @@
 #include "Default.h"
 #include "MeshTypes.h"
 #include "configuration.h"
+#include "memGet.h"
 #include "mesh-pb-constants.h"
 #include "modules/NodeInfoModule.h"
 #include "modules/RoutingModule.h"
@@ -21,8 +22,11 @@ ErrorCode ReliableRouter::send(meshtastic_MeshPacket *p)
         if (p->hop_limit == 0) {
             p->hop_limit = Default::getConfiguredOrDefaultHopLimit(config.lora.hop_limit);
         }
-
+        auto heapBefore = memGet.getFreeHeap();
         auto copy = packetPool.allocCopy(*p);
+        auto heapAfter = memGet.getFreeHeap();
+        LOG_HEAP("Alloc in ReliableRouter::send() pointer 0x%x, size: %u, free: %u", copy, heapBefore - heapAfter, heapAfter);
+
         startRetransmission(copy, NUM_RELIABLE_RETX);
     }
 
diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp
index c7e32c4a1..603dfda4a 100644
--- a/src/mesh/Router.cpp
+++ b/src/mesh/Router.cpp
@@ -275,7 +275,12 @@ ErrorCode Router::send(meshtastic_MeshPacket *p)
     // If the packet is not yet encrypted, do so now
     if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) {
         ChannelIndex chIndex = p->channel; // keep as a local because we are about to change it
+
+        auto heapBefore = memGet.getFreeHeap();
         meshtastic_MeshPacket *p_decoded = packetPool.allocCopy(*p);
+        auto heapAfter = memGet.getFreeHeap();
+
+        LOG_HEAP("Alloc in Router::send pointer 0x%x, size: %u, free: %u", p_decoded, heapBefore - heapAfter, heapAfter);
 
         auto encodeResult = perhapsEncode(p);
         if (encodeResult != meshtastic_Routing_Error_NONE) {
@@ -608,7 +613,10 @@ void Router::handleReceived(meshtastic_MeshPacket *p, RxSource src)
     // Also, we should set the time from the ISR and it should have msec level resolution
     p->rx_time = getValidTime(RTCQualityFromNet); // store the arrival timestamp for the phone
     // Store a copy of encrypted packet for MQTT
+    auto heapBefore = memGet.getFreeHeap();
     meshtastic_MeshPacket *p_encrypted = packetPool.allocCopy(*p);
+    auto heapAfter = memGet.getFreeHeap();
+    LOG_HEAP("Alloc in Router::handleReceived pointer 0x%x, size: %u, free: %u", p_encrypted, heapBefore - heapAfter, heapAfter);
 
     // Take those raw bytes and convert them back into a well structured protobuf we can understand
     auto decodedState = perhapsDecode(p);
diff --git a/src/modules/NodeInfoModule.cpp b/src/modules/NodeInfoModule.cpp
index 0060e99fa..82632f667 100644
--- a/src/modules/NodeInfoModule.cpp
+++ b/src/modules/NodeInfoModule.cpp
@@ -44,7 +44,11 @@ void NodeInfoModule::sendOurNodeInfo(NodeNum dest, bool wantReplies, uint8_t cha
     if (prevPacketId) // if we wrap around to zero, we'll simply fail to cancel in that rare case (no big deal)
         service->cancelSending(prevPacketId);
     shorterTimeout = _shorterTimeout;
+    auto heapBefore = memGet.getFreeHeap();
     meshtastic_MeshPacket *p = allocReply();
+    auto heapAfter = memGet.getFreeHeap();
+
+    LOG_HEAP("Alloc in NodeInfoModule::sendOurNodeInfo pointer 0x%x, size: %u, free: %u", p, heapBefore - heapAfter, heapAfter);
     if (p) { // Check whether we didn't ignore it
         p->to = dest;
         p->decoded.want_response = (config.device.role != meshtastic_Config_DeviceConfig_Role_TRACKER &&
diff --git a/src/modules/Telemetry/DeviceTelemetry.cpp b/src/modules/Telemetry/DeviceTelemetry.cpp
index 08fd09db0..8694de993 100644
--- a/src/modules/Telemetry/DeviceTelemetry.cpp
+++ b/src/modules/Telemetry/DeviceTelemetry.cpp
@@ -172,7 +172,12 @@ bool DeviceTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly)
              telemetry.variant.device_metrics.battery_level, telemetry.variant.device_metrics.voltage,
              telemetry.variant.device_metrics.uptime_seconds);
 
+    auto heapBefore = memGet.getFreeHeap();
     meshtastic_MeshPacket *p = allocDataProtobuf(telemetry);
+    auto heapAfter = memGet.getFreeHeap();
+    LOG_HEAP("Alloc in DeviceTelemetryModule::sendTelemetry() pointer 0x%x, size: %u, free: %u", p, heapBefore - heapAfter,
+             heapAfter);
+
     p->to = dest;
     p->decoded.want_response = false;
     p->priority = meshtastic_MeshPacket_Priority_BACKGROUND;

From 31fdb369874f30306fbb76f720884359eac1c5c4 Mon Sep 17 00:00:00 2001
From: Ben Meadors 
Date: Tue, 9 Sep 2025 10:46:33 -0500
Subject: [PATCH 149/691] Detection sensor add module only when enabled

---
 src/modules/Modules.cpp | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/src/modules/Modules.cpp b/src/modules/Modules.cpp
index 85d183aef..0b051687d 100644
--- a/src/modules/Modules.cpp
+++ b/src/modules/Modules.cpp
@@ -138,7 +138,9 @@ void setupModules()
         neighborInfoModule = new NeighborInfoModule();
 #endif
 #if !MESHTASTIC_EXCLUDE_DETECTIONSENSOR
-        detectionSensorModule = new DetectionSensorModule();
+        if (moduleConfig.has_detection_sensor && moduleConfig.detection_sensor.enabled) {
+            detectionSensorModule = new DetectionSensorModule();
+        }
 #endif
 #if !MESHTASTIC_EXCLUDE_ATAK
         if (IS_ONE_OF(config.device.role, meshtastic_Config_DeviceConfig_Role_TAK,

From 0cd860e30080e724d5207cfcc1d04bfdb1eb5195 Mon Sep 17 00:00:00 2001
From: Ben Meadors 
Date: Tue, 9 Sep 2025 10:53:18 -0500
Subject: [PATCH 150/691] RangeTest must be enabled

---
 src/modules/Modules.cpp | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/src/modules/Modules.cpp b/src/modules/Modules.cpp
index 0b051687d..6b3f44bed 100644
--- a/src/modules/Modules.cpp
+++ b/src/modules/Modules.cpp
@@ -276,7 +276,8 @@ void setupModules()
         externalNotificationModule = new ExternalNotificationModule();
 #endif
 #if !MESHTASTIC_EXCLUDE_RANGETEST && !MESHTASTIC_EXCLUDE_GPS
-        new RangeTestModule();
+        if (moduleConfig.has_range_test && moduleConfig.range_test.enabled)
+            new RangeTestModule();
 #endif
     } else {
 #if !MESHTASTIC_EXCLUDE_ADMIN

From f267b5f5f77c1911e7fd99af5cd9db0d814f3c11 Mon Sep 17 00:00:00 2001
From: Ben Meadors 
Date: Tue, 9 Sep 2025 11:15:55 -0500
Subject: [PATCH 151/691] Exclude trackball if we aren't a trackball device

---
 src/modules/Modules.cpp | 40 +++++++++++++++++++++++++++++-----------
 1 file changed, 29 insertions(+), 11 deletions(-)

diff --git a/src/modules/Modules.cpp b/src/modules/Modules.cpp
index 6b3f44bed..d4beb6824 100644
--- a/src/modules/Modules.cpp
+++ b/src/modules/Modules.cpp
@@ -6,9 +6,12 @@
 #include "input/RotaryEncoderImpl.h"
 #include "input/RotaryEncoderInterruptImpl1.h"
 #include "input/SerialKeyboardImpl.h"
-#include "input/TrackballInterruptImpl1.h"
 #include "input/UpDownInterruptImpl1.h"
 #include "modules/SystemCommandsModule.h"
+#if HAS_TRACKBALL
+#include "input/TrackballInterruptImpl1.h"
+#endif
+
 #if !MESHTASTIC_EXCLUDE_I2C
 #include "input/cardKbI2cImpl.h"
 #endif
@@ -135,7 +138,9 @@ void setupModules()
         traceRouteModule = new TraceRouteModule();
 #endif
 #if !MESHTASTIC_EXCLUDE_NEIGHBORINFO
-        neighborInfoModule = new NeighborInfoModule();
+        if (moduleConfig.has_neighbor_info && moduleConfig.neighbor_info.enabled) {
+            neighborInfoModule = new NeighborInfoModule();
+        }
 #endif
 #if !MESHTASTIC_EXCLUDE_DETECTIONSENSOR
         if (moduleConfig.has_detection_sensor && moduleConfig.detection_sensor.enabled) {
@@ -212,7 +217,7 @@ void setupModules()
             aLinuxInputImpl->init();
         }
 #endif
-#if !MESHTASTIC_EXCLUDE_INPUTBROKER
+#if !MESHTASTIC_EXCLUDE_INPUTBROKER && HAS_TRACKBALL
         if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
             trackballInterruptImpl1 = new TrackballInterruptImpl1();
             trackballInterruptImpl1->init(TB_DOWN, TB_UP, TB_LEFT, TB_RIGHT, TB_PRESS);
@@ -232,11 +237,14 @@ void setupModules()
 #if HAS_TELEMETRY
         new DeviceTelemetryModule();
 #endif
-// TODO: How to improve this?
 #if HAS_SENSOR && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
-        new EnvironmentTelemetryModule();
+        if (moduleConfig.has_telemetry &&
+            (moduleConfig.telemetry.environment_measurement_enabled || moduleConfig.telemetry.environment_screen_enabled)) {
+            new EnvironmentTelemetryModule();
+        }
 #if __has_include("Adafruit_PM25AQI.h")
-        if (nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_PMSA003I].first > 0) {
+        if (moduleConfig.has_telemetry && moduleConfig.telemetry.air_quality_enabled &&
+            nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_PMSA003I].first > 0) {
             new AirQualityTelemetryModule();
         }
 #endif
@@ -248,12 +256,16 @@ void setupModules()
 #endif
 #endif
 #if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_POWER_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
-        new PowerTelemetryModule();
+        if (moduleConfig.has_telemetry &&
+            (moduleConfig.telemetry.power_measurement_enabled || moduleConfig.telemetry.power_screen_enabled)) {
+            new PowerTelemetryModule();
+        }
 #endif
 #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) {
+        if (moduleConfig.has_serial && moduleConfig.serial.enabled &&
+            config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
             new SerialModule();
         }
 #endif
@@ -264,16 +276,22 @@ void setupModules()
         audioModule = new AudioModule();
 #endif
 #if !MESHTASTIC_EXCLUDE_PAXCOUNTER
-        paxcounterModule = new PaxcounterModule();
+        if (moduleConfig.has_paxcounter && moduleConfig.paxcounter.enabled) {
+            paxcounterModule = new PaxcounterModule();
+        }
 #endif
 #endif
 #if defined(ARCH_ESP32) || defined(ARCH_PORTDUINO)
 #if !MESHTASTIC_EXCLUDE_STOREFORWARD
-        storeForwardModule = new StoreForwardModule();
+        if (moduleConfig.has_store_forward && moduleConfig.store_forward.enabled) {
+            storeForwardModule = new StoreForwardModule();
+        }
 #endif
 #endif
 #if !MESHTASTIC_EXCLUDE_EXTERNALNOTIFICATION
-        externalNotificationModule = new ExternalNotificationModule();
+        if (moduleConfig.has_external_notification && moduleConfig.external_notification.enabled) {
+            externalNotificationModule = new ExternalNotificationModule();
+        }
 #endif
 #if !MESHTASTIC_EXCLUDE_RANGETEST && !MESHTASTIC_EXCLUDE_GPS
         if (moduleConfig.has_range_test && moduleConfig.range_test.enabled)

From 088318512a8662179335ff66a343bf675723313d Mon Sep 17 00:00:00 2001
From: Ben Meadors 
Date: Tue, 9 Sep 2025 11:20:27 -0500
Subject: [PATCH 152/691] Duplicate

---
 .../ports/test_text_message.cpp               | 42 -------------------
 1 file changed, 42 deletions(-)

diff --git a/test/test_meshpacket_serializer/ports/test_text_message.cpp b/test/test_meshpacket_serializer/ports/test_text_message.cpp
index 7dddf3d7c..0f3b0bc6d 100644
--- a/test/test_meshpacket_serializer/ports/test_text_message.cpp
+++ b/test/test_meshpacket_serializer/ports/test_text_message.cpp
@@ -1,48 +1,6 @@
 #include "../test_helpers.h"
 #include 
 
-// Helper function to test common packet fields and structure
-void verify_text_message_packet_structure(const std::string &json, const char *expected_text)
-{
-    TEST_ASSERT_TRUE(json.length() > 0);
-
-    // Use smart pointer for automatic memory management
-    std::unique_ptr root(JSON::Parse(json.c_str()));
-    TEST_ASSERT_NOT_NULL(root.get());
-    TEST_ASSERT_TRUE(root->IsObject());
-
-    JSONObject jsonObj = root->AsObject();
-
-    // Check basic packet fields - use helper function to reduce duplication
-    auto check_field = [&](const char *field, uint32_t expected_value) {
-        auto it = jsonObj.find(field);
-        TEST_ASSERT_TRUE(it != jsonObj.end());
-        TEST_ASSERT_EQUAL(expected_value, (uint32_t)it->second->AsNumber());
-    };
-
-    check_field("from", 0x11223344);
-    check_field("to", 0x55667788);
-    check_field("id", 0x9999);
-
-    // Check message type
-    auto type_it = jsonObj.find("type");
-    TEST_ASSERT_TRUE(type_it != jsonObj.end());
-    TEST_ASSERT_EQUAL_STRING("text", type_it->second->AsString().c_str());
-
-    // Check payload
-    auto payload_it = jsonObj.find("payload");
-    TEST_ASSERT_TRUE(payload_it != jsonObj.end());
-    TEST_ASSERT_TRUE(payload_it->second->IsObject());
-
-    JSONObject payload = payload_it->second->AsObject();
-    auto text_it = payload.find("text");
-    TEST_ASSERT_TRUE(text_it != payload.end());
-    TEST_ASSERT_EQUAL_STRING(expected_text, text_it->second->AsString().c_str());
-
-    // No need for manual delete with smart pointer
-}
-#include 
-
 // Helper function to test common packet fields and structure
 void verify_text_message_packet_structure(const std::string &json, const char *expected_text)
 {

From 0aa48c9c2213db6d8cdb93865146cdbad620732e Mon Sep 17 00:00:00 2001
From: Austin 
Date: Tue, 9 Sep 2025 16:57:36 -0400
Subject: [PATCH 153/691] Use `sh` in debhelper scripts (#7941)

---
 debian/meshtasticd.postinst | 2 +-
 debian/meshtasticd.postrm   | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/debian/meshtasticd.postinst b/debian/meshtasticd.postinst
index d569cb43e..fe0dbc332 100755
--- a/debian/meshtasticd.postinst
+++ b/debian/meshtasticd.postinst
@@ -1,4 +1,4 @@
-#!/bin/bash
+#!/bin/sh
 # postinst script for meshtasticd
 #
 # see: dh_installdeb(1)
diff --git a/debian/meshtasticd.postrm b/debian/meshtasticd.postrm
index dc25680a8..bb2c32a5b 100755
--- a/debian/meshtasticd.postrm
+++ b/debian/meshtasticd.postrm
@@ -1,4 +1,4 @@
-#!/bin/bash
+#!/bin/sh
 # postrm script for meshtasticd
 #
 # see: dh_installdeb(1)

From 95dc61f57bf2803bb4d37c83f5da2551c2a6e46b Mon Sep 17 00:00:00 2001
From: Austin 
Date: Tue, 9 Sep 2025 16:59:43 -0400
Subject: [PATCH 154/691] Debian: Correctly generate changelog entries (#7945)

---
 debian/changelog       | 153 ++++++++++++++++++++++++++++-------------
 debian/ci_changelog.sh |   5 +-
 2 files changed, 109 insertions(+), 49 deletions(-)

diff --git a/debian/changelog b/debian/changelog
index 29841d0db..286349dd2 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,50 +1,109 @@
-meshtasticd (2.7.9.0) UNRELEASED; urgency=medium
+meshtasticd (2.7.9.0) unstable; urgency=medium
+
+  * Version 2.7.9
+
+ -- GitHub Actions   Wed, 03 Sep 2025 23:39:17 +0000
+
+meshtasticd (2.7.8.0) unstable; urgency=medium
+
+  * Version 2.7.8
+
+ -- GitHub Actions   Sat, 30 Aug 2025 00:26:04 +0000
+
+meshtasticd (2.7.7.0) unstable; urgency=medium
+
+  * Version 2.7.7
+
+ -- GitHub Actions   Thu, 28 Aug 2025 10:33:25 +0000
+
+meshtasticd (2.7.6.0) unstable; urgency=medium
+
+  * Version 2.7.6
+
+ -- GitHub Actions   Tue, 12 Aug 2025 23:48:48 +0000
+
+meshtasticd (2.7.5.0) unstable; urgency=medium
+
+  * Version 2.7.5
+
+ -- GitHub Actions   Sat, 09 Aug 2025 12:46:53 +0000
+
+meshtasticd (2.7.4.0) unstable; urgency=medium
+
+  * Version 2.7.4
+
+ -- GitHub Actions   Sat, 19 Jul 2025 11:36:55 +0000
+
+meshtasticd (2.7.3.0) unstable; urgency=medium
+
+  * Version 2.7.3
+
+ -- GitHub Actions   Thu, 10 Jul 2025 16:29:27 +0000
+
+meshtasticd (2.7.2.0) unstable; urgency=medium
+
+  * Version 2.7.2
+
+ -- GitHub Actions   Fri, 04 Jul 2025 11:58:01 +0000
+
+meshtasticd (2.7.1.0) unstable; urgency=medium
+
+  * Version 2.7.1
+
+ -- GitHub Actions   Fri, 27 Jun 2025 20:12:21 +0000
+
+meshtasticd (2.6.13) unstable; urgency=medium
+
+  * Version 2.6.13
+
+ -- GitHub Actions   Mon, 16 Jun 2025 02:10:49 +0000
+
+meshtasticd (2.6.11) unstable; urgency=medium
+
+  * Version 2.6.11
+
+ -- GitHub Actions   Mon, 02 Jun 2025 20:00:55 +0000
+
+meshtasticd (2.6.10) unstable; urgency=medium
+
+  * Version 2.6.10
+
+ -- GitHub Actions   Sun, 25 May 2025 20:46:49 +0000
+
+meshtasticd (2.6.9) unstable; urgency=medium
+
+  * Version 2.6.9
+  * Run as non-root user, 'meshtasticd'
+
+ -- GitHub Actions   Thu, 15 May 2025 11:13:30 +0000
+
+meshtasticd (2.6.8) unstable; urgency=medium
+
+  * Version 2.6.8
+
+ -- GitHub Actions   Tue, 06 May 2025 01:32:49 +0000
+
+meshtasticd (2.5.22) unstable; urgency=medium
+
+  * Version 2.5.22
+
+ -- GitHub Actions   Wed, 05 Feb 2025 01:10:33 +0000
+
+meshtasticd (2.5.21) unstable; urgency=medium
+
+  * Version 2.5.21
+
+ -- GitHub Actions   Sat, 25 Jan 2025 01:39:16 +0000
+
+meshtasticd (2.5.20) unstable; urgency=medium
+
+  * Version 2.5.20
+
+ -- GitHub Actions   Mon, 13 Jan 2025 19:24:14 +0000
+
+meshtasticd (2.5.19) unstable; urgency=medium
 
-  [ Austin Lane ]
   * Initial packaging
-  * GitHub Actions Automatic version bump
-  * GitHub Actions Automatic version bump
-  * GitHub Actions Automatic version bump
-  * GitHub Actions Automatic version bump
+  * Version 2.5.19
 
-  [  ]
-  * GitHub Actions Automatic version bump
-
-  [  ]
-  * GitHub Actions Automatic version bump
-
-  [  ]
-  * GitHub Actions Automatic version bump
-
-  [  ]
-  * GitHub Actions Automatic version bump
-
-  [  ]
-  * GitHub Actions Automatic version bump
-
-  [  ]
-  * GitHub Actions Automatic version bump
-
-  [  ]
-  * GitHub Actions Automatic version bump
-
-  [ Ubuntu ]
-  * GitHub Actions Automatic version bump
-
-  [  ]
-  * GitHub Actions Automatic version bump
-
-  [  ]
-  * GitHub Actions Automatic version bump
-
-  [  ]
-  * GitHub Actions Automatic version bump
-  * GitHub Actions Automatic version bump
-
-  [  ]
-  * GitHub Actions Automatic version bump
-
-  [  ]
-  * GitHub Actions Automatic version bump
-
- --    Wed, 03 Sep 2025 23:39:17 +0000
+ -- Austin Lane   Thu, 02 Jan 2025 12:00:00 +0000
diff --git a/debian/ci_changelog.sh b/debian/ci_changelog.sh
index f7e875977..16b33207c 100755
--- a/debian/ci_changelog.sh
+++ b/debian/ci_changelog.sh
@@ -1,7 +1,8 @@
 #!/usr/bin/bash
+export DEBFULLNAME="GitHub Actions"
 export DEBEMAIL="github-actions[bot]@users.noreply.github.com"
 PKG_VERSION=$(python3 bin/buildinfo.py short)
 
 dch --newversion "$PKG_VERSION.0" \
-	--distribution UNRELEASED \
-	"GitHub Actions Automatic version bump"
+	--distribution unstable \
+	"Version $PKG_VERSION"

From 6f7149e9a2e54fcb85cfe14cfd2d1db1b25a05b0 Mon Sep 17 00:00:00 2001
From: Austin 
Date: Tue, 9 Sep 2025 17:01:04 -0400
Subject: [PATCH 155/691] PPA: Enable Ubuntu 25.10 (questing) (#7940)

---
 .github/workflows/daily_packaging.yml  | 4 ++--
 .github/workflows/release_channels.yml | 6 +++---
 2 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/.github/workflows/daily_packaging.yml b/.github/workflows/daily_packaging.yml
index eb61554f2..df5ed27d5 100644
--- a/.github/workflows/daily_packaging.yml
+++ b/.github/workflows/daily_packaging.yml
@@ -31,8 +31,8 @@ jobs:
       fail-fast: false
       matrix:
         series:
-          - jammy # 22.04
-          - noble # 24.04
+          - jammy # 22.04 LTS
+          - noble # 24.04 LTS
           - plucky # 25.04
           - questing # 25.10
     uses: ./.github/workflows/package_ppa.yml
diff --git a/.github/workflows/release_channels.yml b/.github/workflows/release_channels.yml
index 486f4b1a6..d5d642db4 100644
--- a/.github/workflows/release_channels.yml
+++ b/.github/workflows/release_channels.yml
@@ -21,10 +21,10 @@ jobs:
       fail-fast: false
       matrix:
         series:
-          - jammy # 22.04
-          - noble # 24.04
+          - jammy # 22.04 LTS
+          - noble # 24.04 LTS
           - plucky # 25.04
-          # - questing # 25.10
+          - questing # 25.10
     uses: ./.github/workflows/package_ppa.yml
     with:
       ppa_repo: |-

From 108bdf7b0d9ebbb27c7c778d609c45695e5757be Mon Sep 17 00:00:00 2001
From: Ben Meadors 
Date: Tue, 9 Sep 2025 19:11:39 -0500
Subject: [PATCH 156/691] Close should set heartbeatReceived = false

---
 src/mesh/PhoneAPI.cpp | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/mesh/PhoneAPI.cpp b/src/mesh/PhoneAPI.cpp
index a3a8a2087..9fb1b589f 100644
--- a/src/mesh/PhoneAPI.cpp
+++ b/src/mesh/PhoneAPI.cpp
@@ -100,6 +100,7 @@ void PhoneAPI::close()
         config_nonce = 0;
         config_state = 0;
         pauseBluetoothLogging = false;
+        heartbeatReceived = false;
     }
 }
 

From 701028b749f905fdeaba7c35cb8c777b008cc665 Mon Sep 17 00:00:00 2001
From: Ben Meadors 
Date: Wed, 10 Sep 2025 15:29:50 -0500
Subject: [PATCH 157/691] Unify build epoch to add flag in platformio-custom.py
 (#7917)

* Unify build_epoch replacement logic in platformio-custom

* Missed one
---
 .github/actions/setup-base/action.yml | 5 -----
 bin/build-firmware.sh                 | 2 --
 bin/platformio-custom.py              | 7 +++++++
 platformio.ini                        | 2 +-
 4 files changed, 8 insertions(+), 8 deletions(-)

diff --git a/.github/actions/setup-base/action.yml b/.github/actions/setup-base/action.yml
index 350ca290c..f6c1fd80c 100644
--- a/.github/actions/setup-base/action.yml
+++ b/.github/actions/setup-base/action.yml
@@ -11,11 +11,6 @@ runs:
         ref: ${{github.event.pull_request.head.ref}}
         repository: ${{github.event.pull_request.head.repo.full_name}}
 
-    - name: Uncomment build epoch
-      shell: bash
-      run: |
-        sed -i 's/#-DBUILD_EPOCH=$UNIX_TIME/-DBUILD_EPOCH=$UNIX_TIME/' platformio.ini
-
     - name: Install dependencies
       shell: bash
       run: |
diff --git a/bin/build-firmware.sh b/bin/build-firmware.sh
index fdd7caa11..7bd19aaa9 100644
--- a/bin/build-firmware.sh
+++ b/bin/build-firmware.sh
@@ -1,7 +1,5 @@
 #!/usr/bin/env bash
 
-sed -i 's/#-DBUILD_EPOCH=$UNIX_TIME/-DBUILD_EPOCH=$UNIX_TIME/' platformio.ini
-
 export PIP_BREAK_SYSTEM_PACKAGES=1
 
 if (echo $2 | grep -q "esp32"); then
diff --git a/bin/platformio-custom.py b/bin/platformio-custom.py
index fc1b4bc2e..e54d1586f 100644
--- a/bin/platformio-custom.py
+++ b/bin/platformio-custom.py
@@ -6,6 +6,8 @@ from os.path import join
 import subprocess
 import json
 import re
+import time
+from datetime import datetime
 
 from readprops import readProps
 
@@ -125,11 +127,16 @@ for pref in userPrefs:
         pref_flags.append("-D" + pref + "=" + env.StringifyMacro(userPrefs[pref]) + "")
 
 # General options that are passed to the C and C++ compilers
+# Calculate unix epoch for current day (midnight)
+current_date = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)
+build_epoch = int(current_date.timestamp())
+
 flags = [
         "-DAPP_VERSION=" + verObj["long"],
         "-DAPP_VERSION_SHORT=" + verObj["short"],
         "-DAPP_ENV=" + env.get("PIOENV"),
         "-DAPP_REPO=" + repo_owner,
+        "-DBUILD_EPOCH=" + str(build_epoch),
     ] + pref_flags
 
 print ("Using flags:")
diff --git a/platformio.ini b/platformio.ini
index 81f95a7e3..e2e5e1a18 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -53,7 +53,7 @@ build_flags = -Wno-missing-field-initializers
 	-DMESHTASTIC_EXCLUDE_POWERSTRESS=1 ; exclude power stress test module from main firmware
 	-DMESHTASTIC_EXCLUDE_GENERIC_THREAD_MODULE=1
 	-D MAX_THREADS=40 ; As we've split modules, we have more threads to manage
-	#-DBUILD_EPOCH=$UNIX_TIME
+	#-DBUILD_EPOCH=$UNIX_TIME ; set in platformio-custom.py now
 	#-D OLED_PL=1
 
 monitor_speed = 115200

From abc0eb196a64190c220f4762e19b047a69f68cf4 Mon Sep 17 00:00:00 2001
From: Tom Fifield 
Date: Mon, 8 Sep 2025 20:54:08 +1000
Subject: [PATCH 158/691] 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 9da92626e51466b328b1d5aba1de5abf7886c2ea Mon Sep 17 00:00:00 2001
From: ford-jones 
Date: Thu, 11 Sep 2025 14:16:48 +1200
Subject: [PATCH 159/691] Create channel-mute toggle function

---
 src/mesh/Channels.cpp | 10 ++++++++++
 src/mesh/Channels.h   |  6 ++++++
 2 files changed, 16 insertions(+)

diff --git a/src/mesh/Channels.cpp b/src/mesh/Channels.cpp
index 70e4127d8..3ada5fa0c 100644
--- a/src/mesh/Channels.cpp
+++ b/src/mesh/Channels.cpp
@@ -343,6 +343,16 @@ void Channels::setChannel(const meshtastic_Channel &c)
     old = c; // slam in the new settings/role
 }
 
+void Channels::setMute(ChannelIndex chIndex)
+{
+    if (chIndex < channelFile.channels_count) {
+        meshtastic_Channel *ch = channelFile.channels + chIndex;
+        ch->settings.mute = !ch->settings.mute;
+    } else {
+        LOG_ERROR("Failed to mute. Invalid channel index %d > %d", chIndex, channelFile.channels_count);
+    };
+};
+
 bool Channels::anyMqttEnabled()
 {
 #if USERPREFS_EVENT_MODE && !MESHTASTIC_EXCLUDE_MQTT
diff --git a/src/mesh/Channels.h b/src/mesh/Channels.h
index 7873a306a..e7c6ddb78 100644
--- a/src/mesh/Channels.h
+++ b/src/mesh/Channels.h
@@ -47,6 +47,12 @@ class Channels
      */
     void setChannel(const meshtastic_Channel &c);
 
+    /**
+     * Toggles the mute state of the channel associated with the channel index.
+     * I.e. if it's off turn it on and vice-versa.
+     */
+    void setMute(ChannelIndex chIndex);
+
     /** Return a human friendly name for this channel (and expand any short strings as needed)
      */
     const char *getName(size_t chIndex);

From 67ecb60bcd0bec5061b1d5129f92b33e6e811124 Mon Sep 17 00:00:00 2001
From: ford-jones 
Date: Thu, 11 Sep 2025 14:18:00 +1200
Subject: [PATCH 160/691] Added mute state to channel settings

---
 protobufs                                     |  2 +-
 src/mesh/generated/meshtastic/apponly.pb.h    |  2 +-
 src/mesh/generated/meshtastic/channel.pb.h    | 14 +++++++++-----
 src/mesh/generated/meshtastic/deviceonly.pb.h |  4 ++--
 4 files changed, 13 insertions(+), 9 deletions(-)

diff --git a/protobufs b/protobufs
index 8985852d7..550702a69 160000
--- a/protobufs
+++ b/protobufs
@@ -1 +1 @@
-Subproject commit 8985852d752de3f7210f9a4a3e0923120ec438b3
+Subproject commit 550702a695bc31651c758757ccf70f0fbe9cc43c
diff --git a/src/mesh/generated/meshtastic/apponly.pb.h b/src/mesh/generated/meshtastic/apponly.pb.h
index f4c33bd79..db9dedaaf 100644
--- a/src/mesh/generated/meshtastic/apponly.pb.h
+++ b/src/mesh/generated/meshtastic/apponly.pb.h
@@ -55,7 +55,7 @@ extern const pb_msgdesc_t meshtastic_ChannelSet_msg;
 
 /* Maximum encoded size of messages (where known) */
 #define MESHTASTIC_MESHTASTIC_APPONLY_PB_H_MAX_SIZE meshtastic_ChannelSet_size
-#define meshtastic_ChannelSet_size               679
+#define meshtastic_ChannelSet_size               695
 
 #ifdef __cplusplus
 } /* extern "C" */
diff --git a/src/mesh/generated/meshtastic/channel.pb.h b/src/mesh/generated/meshtastic/channel.pb.h
index ca4310bf1..594d15929 100644
--- a/src/mesh/generated/meshtastic/channel.pb.h
+++ b/src/mesh/generated/meshtastic/channel.pb.h
@@ -97,6 +97,8 @@ typedef struct _meshtastic_ChannelSettings {
     /* Per-channel module settings. */
     bool has_module_settings;
     meshtastic_ModuleSettings module_settings;
+    /* Whether or not we should receive notifactions / alerts from this channel */
+    bool mute;
 } meshtastic_ChannelSettings;
 
 /* A pair of a channel number, mode and the (sharable) settings for that channel */
@@ -128,10 +130,10 @@ extern "C" {
 
 
 /* Initializer values for message structs */
-#define meshtastic_ChannelSettings_init_default  {0, {0, {0}}, "", 0, 0, 0, false, meshtastic_ModuleSettings_init_default}
+#define meshtastic_ChannelSettings_init_default  {0, {0, {0}}, "", 0, 0, 0, false, meshtastic_ModuleSettings_init_default, 0}
 #define meshtastic_ModuleSettings_init_default   {0, 0}
 #define meshtastic_Channel_init_default          {0, false, meshtastic_ChannelSettings_init_default, _meshtastic_Channel_Role_MIN}
-#define meshtastic_ChannelSettings_init_zero     {0, {0, {0}}, "", 0, 0, 0, false, meshtastic_ModuleSettings_init_zero}
+#define meshtastic_ChannelSettings_init_zero     {0, {0, {0}}, "", 0, 0, 0, false, meshtastic_ModuleSettings_init_zero, 0}
 #define meshtastic_ModuleSettings_init_zero      {0, 0}
 #define meshtastic_Channel_init_zero             {0, false, meshtastic_ChannelSettings_init_zero, _meshtastic_Channel_Role_MIN}
 
@@ -145,6 +147,7 @@ extern "C" {
 #define meshtastic_ChannelSettings_uplink_enabled_tag 5
 #define meshtastic_ChannelSettings_downlink_enabled_tag 6
 #define meshtastic_ChannelSettings_module_settings_tag 7
+#define meshtastic_ChannelSettings_mute_tag      8
 #define meshtastic_Channel_index_tag             1
 #define meshtastic_Channel_settings_tag          2
 #define meshtastic_Channel_role_tag              3
@@ -157,7 +160,8 @@ X(a, STATIC,   SINGULAR, STRING,   name,              3) \
 X(a, STATIC,   SINGULAR, FIXED32,  id,                4) \
 X(a, STATIC,   SINGULAR, BOOL,     uplink_enabled,    5) \
 X(a, STATIC,   SINGULAR, BOOL,     downlink_enabled,   6) \
-X(a, STATIC,   OPTIONAL, MESSAGE,  module_settings,   7)
+X(a, STATIC,   OPTIONAL, MESSAGE,  module_settings,   7) \
+X(a, STATIC,   SINGULAR, BOOL,     mute,              8)
 #define meshtastic_ChannelSettings_CALLBACK NULL
 #define meshtastic_ChannelSettings_DEFAULT NULL
 #define meshtastic_ChannelSettings_module_settings_MSGTYPE meshtastic_ModuleSettings
@@ -187,8 +191,8 @@ extern const pb_msgdesc_t meshtastic_Channel_msg;
 
 /* Maximum encoded size of messages (where known) */
 #define MESHTASTIC_MESHTASTIC_CHANNEL_PB_H_MAX_SIZE meshtastic_Channel_size
-#define meshtastic_ChannelSettings_size          72
-#define meshtastic_Channel_size                  87
+#define meshtastic_ChannelSettings_size          74
+#define meshtastic_Channel_size                  89
 #define meshtastic_ModuleSettings_size           8
 
 #ifdef __cplusplus
diff --git a/src/mesh/generated/meshtastic/deviceonly.pb.h b/src/mesh/generated/meshtastic/deviceonly.pb.h
index f47091384..59c70cc9e 100644
--- a/src/mesh/generated/meshtastic/deviceonly.pb.h
+++ b/src/mesh/generated/meshtastic/deviceonly.pb.h
@@ -360,8 +360,8 @@ 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_ChannelFile_size              718
+#define meshtastic_BackupPreferences_size        2287
+#define meshtastic_ChannelFile_size              734
 #define meshtastic_DeviceState_size              1737
 #define meshtastic_NodeInfoLite_size             196
 #define meshtastic_PositionLite_size             28

From fa1ccf477989afaaef74eaa0ad3a0ce9795a391e Mon Sep 17 00:00:00 2001
From: ford-jones 
Date: Thu, 11 Sep 2025 17:30:59 +1200
Subject: [PATCH 161/691] Create node-mute toggle functions

---
 src/modules/AdminModule.cpp | 18 ++++++++++++++++++
 1 file changed, 18 insertions(+)

diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp
index 4014e1c36..5cfdd2063 100644
--- a/src/modules/AdminModule.cpp
+++ b/src/modules/AdminModule.cpp
@@ -365,6 +365,24 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta
         }
         break;
     }
+    case meshtastic_AdminMessage_set_muted_node_tag: {
+        LOG_INFO("Client received set_muted_node command");
+        meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(r->set_muted_node);
+        if (node != NULL) {
+            node->is_muted = true;
+            saveChanges(SEGMENT_NODEDATABASE, false);
+        }
+        break;
+    }
+    case meshtastic_AdminMessage_remove_muted_node_tag: {
+        LOG_INFO("Client received remove_muted_node command");
+        meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(r->remove_muted_node);
+        if (node != NULL) {
+            node->is_muted = false;
+            saveChanges(SEGMENT_NODEDATABASE, false);
+        }
+        break;
+    }
     case meshtastic_AdminMessage_set_fixed_position_tag: {
         LOG_INFO("Client received set_fixed_position command");
         meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(nodeDB->getNodeNum());

From 6b7ad9c4e187180fb46e319a1510972fbf08e636 Mon Sep 17 00:00:00 2001
From: ford-jones 
Date: Thu, 11 Sep 2025 17:32:12 +1200
Subject: [PATCH 162/691] Added mute state to nodedb entries

---
 protobufs                                     |  2 +-
 src/mesh/generated/meshtastic/admin.pb.h      |  8 ++++++++
 src/mesh/generated/meshtastic/deviceonly.pb.h | 19 ++++++++++++-------
 3 files changed, 21 insertions(+), 8 deletions(-)

diff --git a/protobufs b/protobufs
index 550702a69..638917dea 160000
--- a/protobufs
+++ b/protobufs
@@ -1 +1 @@
-Subproject commit 550702a695bc31651c758757ccf70f0fbe9cc43c
+Subproject commit 638917dea8bb36b2823261c9fbc87430c859b3c3
diff --git a/src/mesh/generated/meshtastic/admin.pb.h b/src/mesh/generated/meshtastic/admin.pb.h
index bc0b780b9..616b7e8ee 100644
--- a/src/mesh/generated/meshtastic/admin.pb.h
+++ b/src/mesh/generated/meshtastic/admin.pb.h
@@ -247,6 +247,10 @@ typedef struct _meshtastic_AdminMessage {
         uint32_t set_ignored_node;
         /* Set specified node-num to be un-ignored on the NodeDB on the device */
         uint32_t remove_ignored_node;
+        /* Set specified node-num to be muted */
+        uint32_t set_muted_node;
+        /* Set specified node-num to be heard / not-muted */
+        uint32_t remove_muted_node;
         /* Begins an edit transaction for config, module config, owner, and channel settings changes
      This will delay the standard *implicit* save to the file system and subsequent reboot behavior until committed (commit_edit_settings) */
         bool begin_edit_settings;
@@ -388,6 +392,8 @@ extern "C" {
 #define meshtastic_AdminMessage_store_ui_config_tag 46
 #define meshtastic_AdminMessage_set_ignored_node_tag 47
 #define meshtastic_AdminMessage_remove_ignored_node_tag 48
+#define meshtastic_AdminMessage_set_muted_node_tag 49
+#define meshtastic_AdminMessage_remove_muted_node_tag 50
 #define meshtastic_AdminMessage_begin_edit_settings_tag 64
 #define meshtastic_AdminMessage_commit_edit_settings_tag 65
 #define meshtastic_AdminMessage_add_contact_tag  66
@@ -446,6 +452,8 @@ X(a, STATIC,   ONEOF,    MESSAGE,  (payload_variant,get_ui_config_response,get_u
 X(a, STATIC,   ONEOF,    MESSAGE,  (payload_variant,store_ui_config,store_ui_config),  46) \
 X(a, STATIC,   ONEOF,    UINT32,   (payload_variant,set_ignored_node,set_ignored_node),  47) \
 X(a, STATIC,   ONEOF,    UINT32,   (payload_variant,remove_ignored_node,remove_ignored_node),  48) \
+X(a, STATIC,   ONEOF,    UINT32,   (payload_variant,set_muted_node,set_muted_node),  49) \
+X(a, STATIC,   ONEOF,    UINT32,   (payload_variant,remove_muted_node,remove_muted_node),  50) \
 X(a, STATIC,   ONEOF,    BOOL,     (payload_variant,begin_edit_settings,begin_edit_settings),  64) \
 X(a, STATIC,   ONEOF,    BOOL,     (payload_variant,commit_edit_settings,commit_edit_settings),  65) \
 X(a, STATIC,   ONEOF,    MESSAGE,  (payload_variant,add_contact,add_contact),  66) \
diff --git a/src/mesh/generated/meshtastic/deviceonly.pb.h b/src/mesh/generated/meshtastic/deviceonly.pb.h
index 59c70cc9e..55c433699 100644
--- a/src/mesh/generated/meshtastic/deviceonly.pb.h
+++ b/src/mesh/generated/meshtastic/deviceonly.pb.h
@@ -94,6 +94,9 @@ typedef struct _meshtastic_NodeInfoLite {
     /* True if node is in our ignored list
  Persists between NodeDB internal clean ups */
     bool is_ignored;
+    /* True if node has been muted
+ Persists between NodeDB internal clean ups */
+    bool is_muted;
     /* Last byte of the node number of the node that should be used as the next hop to reach this node. */
     uint8_t next_hop;
     /* Bitfield for storing booleans.
@@ -190,14 +193,14 @@ extern "C" {
 /* Initializer values for message structs */
 #define meshtastic_PositionLite_init_default     {0, 0, 0, 0, _meshtastic_Position_LocSource_MIN}
 #define meshtastic_UserLite_init_default         {{0}, "", "", _meshtastic_HardwareModel_MIN, 0, _meshtastic_Config_DeviceConfig_Role_MIN, {0, {0}}, false, 0}
-#define meshtastic_NodeInfoLite_init_default     {0, false, meshtastic_UserLite_init_default, false, meshtastic_PositionLite_init_default, 0, 0, false, meshtastic_DeviceMetrics_init_default, 0, 0, false, 0, 0, 0, 0, 0}
+#define meshtastic_NodeInfoLite_init_default     {0, false, meshtastic_UserLite_init_default, false, meshtastic_PositionLite_init_default, 0, 0, false, meshtastic_DeviceMetrics_init_default, 0, 0, false, 0, 0, 0, 0, 0, 0}
 #define meshtastic_DeviceState_init_default      {false, meshtastic_MyNodeInfo_init_default, false, meshtastic_User_init_default, 0, {meshtastic_MeshPacket_init_default}, false, meshtastic_MeshPacket_init_default, 0, 0, 0, false, meshtastic_MeshPacket_init_default, 0, {meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default}}
 #define meshtastic_NodeDatabase_init_default     {0, {0}}
 #define meshtastic_ChannelFile_init_default      {0, {meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default}, 0}
 #define meshtastic_BackupPreferences_init_default {0, 0, false, meshtastic_LocalConfig_init_default, false, meshtastic_LocalModuleConfig_init_default, false, meshtastic_ChannelFile_init_default, false, meshtastic_User_init_default}
 #define meshtastic_PositionLite_init_zero        {0, 0, 0, 0, _meshtastic_Position_LocSource_MIN}
 #define meshtastic_UserLite_init_zero            {{0}, "", "", _meshtastic_HardwareModel_MIN, 0, _meshtastic_Config_DeviceConfig_Role_MIN, {0, {0}}, false, 0}
-#define meshtastic_NodeInfoLite_init_zero        {0, false, meshtastic_UserLite_init_zero, false, meshtastic_PositionLite_init_zero, 0, 0, false, meshtastic_DeviceMetrics_init_zero, 0, 0, false, 0, 0, 0, 0, 0}
+#define meshtastic_NodeInfoLite_init_zero        {0, false, meshtastic_UserLite_init_zero, false, meshtastic_PositionLite_init_zero, 0, 0, false, meshtastic_DeviceMetrics_init_zero, 0, 0, false, 0, 0, 0, 0, 0, 0}
 #define meshtastic_DeviceState_init_zero         {false, meshtastic_MyNodeInfo_init_zero, false, meshtastic_User_init_zero, 0, {meshtastic_MeshPacket_init_zero}, false, meshtastic_MeshPacket_init_zero, 0, 0, 0, false, meshtastic_MeshPacket_init_zero, 0, {meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero}}
 #define meshtastic_NodeDatabase_init_zero        {0, {0}}
 #define meshtastic_ChannelFile_init_zero         {0, {meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero}, 0}
@@ -228,8 +231,9 @@ extern "C" {
 #define meshtastic_NodeInfoLite_hops_away_tag    9
 #define meshtastic_NodeInfoLite_is_favorite_tag  10
 #define meshtastic_NodeInfoLite_is_ignored_tag   11
-#define meshtastic_NodeInfoLite_next_hop_tag     12
-#define meshtastic_NodeInfoLite_bitfield_tag     13
+#define meshtastic_NodeInfoLite_is_muted_tag     12
+#define meshtastic_NodeInfoLite_next_hop_tag     13
+#define meshtastic_NodeInfoLite_bitfield_tag     14
 #define meshtastic_DeviceState_my_node_tag       2
 #define meshtastic_DeviceState_owner_tag         3
 #define meshtastic_DeviceState_receive_queue_tag 5
@@ -284,8 +288,9 @@ X(a, STATIC,   SINGULAR, BOOL,     via_mqtt,          8) \
 X(a, STATIC,   OPTIONAL, UINT32,   hops_away,         9) \
 X(a, STATIC,   SINGULAR, BOOL,     is_favorite,      10) \
 X(a, STATIC,   SINGULAR, BOOL,     is_ignored,       11) \
-X(a, STATIC,   SINGULAR, UINT32,   next_hop,         12) \
-X(a, STATIC,   SINGULAR, UINT32,   bitfield,         13)
+X(a, STATIC,   SINGULAR, BOOL,     is_muted,         12) \
+X(a, STATIC,   SINGULAR, UINT32,   next_hop,         13) \
+X(a, STATIC,   SINGULAR, UINT32,   bitfield,         14)
 #define meshtastic_NodeInfoLite_CALLBACK NULL
 #define meshtastic_NodeInfoLite_DEFAULT NULL
 #define meshtastic_NodeInfoLite_user_MSGTYPE meshtastic_UserLite
@@ -363,7 +368,7 @@ extern const pb_msgdesc_t meshtastic_BackupPreferences_msg;
 #define meshtastic_BackupPreferences_size        2287
 #define meshtastic_ChannelFile_size              734
 #define meshtastic_DeviceState_size              1737
-#define meshtastic_NodeInfoLite_size             196
+#define meshtastic_NodeInfoLite_size             198
 #define meshtastic_PositionLite_size             28
 #define meshtastic_UserLite_size                 98
 

From 1594421214cf287ad9cc3672966e4567a5129ded Mon Sep 17 00:00:00 2001
From: ford-jones 
Date: Thu, 11 Sep 2025 21:49:25 +1200
Subject: [PATCH 163/691] Rebase protos

---
 protobufs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/protobufs b/protobufs
index 638917dea..a8f3ee5b0 160000
--- a/protobufs
+++ b/protobufs
@@ -1 +1 @@
-Subproject commit 638917dea8bb36b2823261c9fbc87430c859b3c3
+Subproject commit a8f3ee5b016688fc9a4ac5e11cf317feab9718bf

From a31fdf01ced293ac4077e388f353a75e2d36c068 Mon Sep 17 00:00:00 2001
From: ford-jones 
Date: Thu, 11 Sep 2025 22:23:42 +1200
Subject: [PATCH 164/691] Decouple protobuf changes

---
 protobufs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

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

From e17c50bb86b66eec3bb8e5fe1574f712ac3b1330 Mon Sep 17 00:00:00 2001
From: Ben Meadors 
Date: Thu, 11 Sep 2025 07:57:42 -0500
Subject: [PATCH 165/691] Put guards in place around debug heap operations
 (#7955)

* Put guards in place around debug heap operations

* Add macros to clean up code

* Add pointer as well
---
 src/DebugConfiguration.h                  | 19 +++++++++++++++++++
 src/memGet.cpp                            | 12 ++++++++++++
 src/mesh/MeshService.cpp                  | 11 ++++-------
 src/mesh/ReliableRouter.cpp               |  5 ++---
 src/mesh/Router.cpp                       | 12 +++++-------
 src/modules/NodeInfoModule.cpp            |  5 ++---
 src/modules/Telemetry/DeviceTelemetry.cpp |  6 ++----
 7 files changed, 46 insertions(+), 24 deletions(-)

diff --git a/src/DebugConfiguration.h b/src/DebugConfiguration.h
index 26f2db1f4..98bbe0f72 100644
--- a/src/DebugConfiguration.h
+++ b/src/DebugConfiguration.h
@@ -2,6 +2,12 @@
 
 #include "configuration.h"
 
+// Forward declarations
+#if defined(DEBUG_HEAP)
+class MemGet;
+extern MemGet memGet;
+#endif
+
 // DEBUG LED
 #ifndef LED_STATE_ON
 #define LED_STATE_ON 1
@@ -65,8 +71,21 @@
 
 #if defined(DEBUG_HEAP)
 #define LOG_HEAP(...) DEBUG_PORT.log(MESHTASTIC_LOG_LEVEL_HEAP, __VA_ARGS__)
+
+// Macro-based heap debugging
+#define DEBUG_HEAP_BEFORE auto heapBefore = memGet.getFreeHeap();
+#define DEBUG_HEAP_AFTER(context, ptr)                                                                                           \
+    do {                                                                                                                         \
+        auto heapAfter = memGet.getFreeHeap();                                                                                   \
+        if (heapBefore != heapAfter) {                                                                                           \
+            LOG_HEAP("Alloc in %s pointer 0x%x, size: %u, free: %u", context, ptr, heapBefore - heapAfter, heapAfter);           \
+        }                                                                                                                        \
+    } while (0)
+
 #else
 #define LOG_HEAP(...)
+#define DEBUG_HEAP_BEFORE
+#define DEBUG_HEAP_AFTER(context, ptr)
 #endif
 
 /// A C wrapper for LOG_DEBUG that can be used from arduino C libs that don't know about C++ or meshtastic
diff --git a/src/memGet.cpp b/src/memGet.cpp
index e8cd177dd..14e614014 100644
--- a/src/memGet.cpp
+++ b/src/memGet.cpp
@@ -88,4 +88,16 @@ uint32_t MemGet::getPsramSize()
 #else
     return 0;
 #endif
+}
+
+void displayPercentHeapFree()
+{
+    uint32_t freeHeap = memGet.getFreeHeap();
+    uint32_t totalHeap = memGet.getHeapSize();
+    if (totalHeap == 0 || totalHeap == UINT32_MAX) {
+        LOG_INFO("Heap size unavailable");
+        return;
+    }
+    int percent = (int)((freeHeap * 100) / totalHeap);
+    LOG_INFO("Heap free: %d%% (%u/%u bytes)", percent, freeHeap, totalHeap);
 }
\ No newline at end of file
diff --git a/src/mesh/MeshService.cpp b/src/mesh/MeshService.cpp
index 157a2eda3..607766ab6 100644
--- a/src/mesh/MeshService.cpp
+++ b/src/mesh/MeshService.cpp
@@ -193,11 +193,9 @@ void MeshService::handleToRadio(meshtastic_MeshPacket &p)
                                                  // (so we update our nodedb for the local node)
 
     // Send the packet into the mesh
-    auto heapBefore = memGet.getFreeHeap();
+    DEBUG_HEAP_BEFORE;
     auto a = packetPool.allocCopy(p);
-    auto heapAfter = memGet.getFreeHeap();
-    LOG_HEAP("Alloc in MeshService::handleToRadio() pointer 0x%x, size: %u, free: %u", a, heapBefore - heapAfter, heapAfter);
-
+    DEBUG_HEAP_AFTER("MeshService::handleToRadio", a);
     sendToMesh(a, RX_SRC_USER);
 
     bool loopback = false; // if true send any packet the phone sends back itself (for testing)
@@ -254,10 +252,9 @@ void MeshService::sendToMesh(meshtastic_MeshPacket *p, RxSource src, bool ccToPh
     }
 
     if ((res == ERRNO_OK || res == ERRNO_SHOULD_RELEASE) && ccToPhone) { // Check if p is not released in case it couldn't be sent
-        auto heapBefore = memGet.getFreeHeap();
+        DEBUG_HEAP_BEFORE;
         auto a = packetPool.allocCopy(*p);
-        auto heapAfter = memGet.getFreeHeap();
-        LOG_HEAP("Alloc in MeshService::sendToMesh() pointer 0x%x, size: %u, free: %u", a, heapBefore - heapAfter, heapAfter);
+        DEBUG_HEAP_AFTER("MeshService::sendToMesh", a);
 
         sendToPhone(a);
     }
diff --git a/src/mesh/ReliableRouter.cpp b/src/mesh/ReliableRouter.cpp
index 890d42b00..6d098b669 100644
--- a/src/mesh/ReliableRouter.cpp
+++ b/src/mesh/ReliableRouter.cpp
@@ -22,10 +22,9 @@ ErrorCode ReliableRouter::send(meshtastic_MeshPacket *p)
         if (p->hop_limit == 0) {
             p->hop_limit = Default::getConfiguredOrDefaultHopLimit(config.lora.hop_limit);
         }
-        auto heapBefore = memGet.getFreeHeap();
+        DEBUG_HEAP_BEFORE;
         auto copy = packetPool.allocCopy(*p);
-        auto heapAfter = memGet.getFreeHeap();
-        LOG_HEAP("Alloc in ReliableRouter::send() pointer 0x%x, size: %u, free: %u", copy, heapBefore - heapAfter, heapAfter);
+        DEBUG_HEAP_AFTER("ReliableRouter::send", copy);
 
         startRetransmission(copy, NUM_RELIABLE_RETX);
     }
diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp
index 603dfda4a..4442b5d50 100644
--- a/src/mesh/Router.cpp
+++ b/src/mesh/Router.cpp
@@ -276,11 +276,9 @@ ErrorCode Router::send(meshtastic_MeshPacket *p)
     if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) {
         ChannelIndex chIndex = p->channel; // keep as a local because we are about to change it
 
-        auto heapBefore = memGet.getFreeHeap();
+        DEBUG_HEAP_BEFORE;
         meshtastic_MeshPacket *p_decoded = packetPool.allocCopy(*p);
-        auto heapAfter = memGet.getFreeHeap();
-
-        LOG_HEAP("Alloc in Router::send pointer 0x%x, size: %u, free: %u", p_decoded, heapBefore - heapAfter, heapAfter);
+        DEBUG_HEAP_AFTER("Router::send", p_decoded);
 
         auto encodeResult = perhapsEncode(p);
         if (encodeResult != meshtastic_Routing_Error_NONE) {
@@ -612,11 +610,11 @@ void Router::handleReceived(meshtastic_MeshPacket *p, RxSource src)
     bool skipHandle = false;
     // Also, we should set the time from the ISR and it should have msec level resolution
     p->rx_time = getValidTime(RTCQualityFromNet); // store the arrival timestamp for the phone
+
     // Store a copy of encrypted packet for MQTT
-    auto heapBefore = memGet.getFreeHeap();
+    DEBUG_HEAP_BEFORE;
     meshtastic_MeshPacket *p_encrypted = packetPool.allocCopy(*p);
-    auto heapAfter = memGet.getFreeHeap();
-    LOG_HEAP("Alloc in Router::handleReceived pointer 0x%x, size: %u, free: %u", p_encrypted, heapBefore - heapAfter, heapAfter);
+    DEBUG_HEAP_AFTER("Router::handleReceived", p_encrypted);
 
     // Take those raw bytes and convert them back into a well structured protobuf we can understand
     auto decodedState = perhapsDecode(p);
diff --git a/src/modules/NodeInfoModule.cpp b/src/modules/NodeInfoModule.cpp
index 82632f667..86ceaae24 100644
--- a/src/modules/NodeInfoModule.cpp
+++ b/src/modules/NodeInfoModule.cpp
@@ -44,11 +44,10 @@ void NodeInfoModule::sendOurNodeInfo(NodeNum dest, bool wantReplies, uint8_t cha
     if (prevPacketId) // if we wrap around to zero, we'll simply fail to cancel in that rare case (no big deal)
         service->cancelSending(prevPacketId);
     shorterTimeout = _shorterTimeout;
-    auto heapBefore = memGet.getFreeHeap();
+    DEBUG_HEAP_BEFORE;
     meshtastic_MeshPacket *p = allocReply();
-    auto heapAfter = memGet.getFreeHeap();
+    DEBUG_HEAP_AFTER("NodeInfoModule::sendOurNodeInfo", p);
 
-    LOG_HEAP("Alloc in NodeInfoModule::sendOurNodeInfo pointer 0x%x, size: %u, free: %u", p, heapBefore - heapAfter, heapAfter);
     if (p) { // Check whether we didn't ignore it
         p->to = dest;
         p->decoded.want_response = (config.device.role != meshtastic_Config_DeviceConfig_Role_TRACKER &&
diff --git a/src/modules/Telemetry/DeviceTelemetry.cpp b/src/modules/Telemetry/DeviceTelemetry.cpp
index 8694de993..98d5b19d0 100644
--- a/src/modules/Telemetry/DeviceTelemetry.cpp
+++ b/src/modules/Telemetry/DeviceTelemetry.cpp
@@ -172,11 +172,9 @@ bool DeviceTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly)
              telemetry.variant.device_metrics.battery_level, telemetry.variant.device_metrics.voltage,
              telemetry.variant.device_metrics.uptime_seconds);
 
-    auto heapBefore = memGet.getFreeHeap();
+    DEBUG_HEAP_BEFORE;
     meshtastic_MeshPacket *p = allocDataProtobuf(telemetry);
-    auto heapAfter = memGet.getFreeHeap();
-    LOG_HEAP("Alloc in DeviceTelemetryModule::sendTelemetry() pointer 0x%x, size: %u, free: %u", p, heapBefore - heapAfter,
-             heapAfter);
+    DEBUG_HEAP_AFTER("DeviceTelemetryModule::sendTelemetry", p);
 
     p->to = dest;
     p->decoded.want_response = false;

From ac4bcd2f5639130f1b0aaac697c754a1d9a6de45 Mon Sep 17 00:00:00 2001
From: Ben Meadors 
Date: Thu, 11 Sep 2025 18:57:30 -0500
Subject: [PATCH 166/691] Cleanup

---
 src/mesh/MeshModule.cpp        | 1 -
 src/mesh/Router.cpp            | 4 +---
 src/modules/NodeInfoModule.cpp | 4 ++--
 3 files changed, 3 insertions(+), 6 deletions(-)

diff --git a/src/mesh/MeshModule.cpp b/src/mesh/MeshModule.cpp
index 22fcec663..c5748a560 100644
--- a/src/mesh/MeshModule.cpp
+++ b/src/mesh/MeshModule.cpp
@@ -100,7 +100,6 @@ void MeshModule::callModules(meshtastic_MeshPacket &mp, RxSource src)
     // Was this message directed to us specifically?  Will be false if we are sniffing someone elses packets
     auto ourNodeNum = nodeDB->getNodeNum();
     bool toUs = isBroadcast(mp.to) || isToUs(&mp);
-    bool fromUs = mp.from == ourNodeNum;
 
     for (auto i = modules->begin(); i != modules->end(); ++i) {
         auto &pi = **i;
diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp
index 4442b5d50..44d09637f 100644
--- a/src/mesh/Router.cpp
+++ b/src/mesh/Router.cpp
@@ -662,7 +662,7 @@ void Router::handleReceived(meshtastic_MeshPacket *p, RxSource src)
 
     // call modules here
     // If this could be a spoofed packet, don't let the modules see it.
-    if (!skipHandle && p->from != nodeDB->getNodeNum()) {
+    if (!skipHandle) {
         MeshModule::callModules(*p, src);
 
 #if !MESHTASTIC_EXCLUDE_MQTT
@@ -676,8 +676,6 @@ void Router::handleReceived(meshtastic_MeshPacket *p, RxSource src)
             !isFromUs(p) && mqtt)
             mqtt->onSend(*p_encrypted, *p, p->channel);
 #endif
-    } else if (p->from == nodeDB->getNodeNum() && !skipHandle) {
-        MeshModule::callModules(*p, src);
     }
 
     packetPool.release(p_encrypted); // Release the encrypted packet
diff --git a/src/modules/NodeInfoModule.cpp b/src/modules/NodeInfoModule.cpp
index 86ceaae24..276a11b3a 100644
--- a/src/modules/NodeInfoModule.cpp
+++ b/src/modules/NodeInfoModule.cpp
@@ -12,12 +12,12 @@ NodeInfoModule *nodeInfoModule;
 
 bool NodeInfoModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_User *pptr)
 {
-    auto p = *pptr;
-
     if (mp.from == nodeDB->getNodeNum()) {
         LOG_WARN("Ignoring packet supposed to be from our own node: %08x", mp.from);
         return false;
     }
+
+    auto p = *pptr;
     if (p.is_licensed != owner.is_licensed) {
         LOG_WARN("Invalid nodeInfo detected, is_licensed mismatch!");
         return true;

From d5300a11410f220f2068ab80b1ec6f9c6b394926 Mon Sep 17 00:00:00 2001
From: ford-jones 
Date: Fri, 12 Sep 2025 13:54:52 +1200
Subject: [PATCH 167/691] Disable bell-invoked ext notifs for muted nodes

---
 src/modules/ExternalNotificationModule.cpp | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/src/modules/ExternalNotificationModule.cpp b/src/modules/ExternalNotificationModule.cpp
index 2f2934984..b73547c02 100644
--- a/src/modules/ExternalNotificationModule.cpp
+++ b/src/modules/ExternalNotificationModule.cpp
@@ -506,8 +506,9 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP
                     }
                 }
             }
+            meshtastic_NodeInfoLite *sender = nodeDB->getMeshNode(mp.from);
 
-            if (moduleConfig.external_notification.alert_message) {
+            if (moduleConfig.external_notification.alert_message && !sender->is_muted) {
                 LOG_INFO("externalNotificationModule - Notification Module");
                 isNagging = true;
                 setExternalState(0, true);
@@ -518,7 +519,7 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP
                 }
             }
 
-            if (moduleConfig.external_notification.alert_message_vibra) {
+            if (moduleConfig.external_notification.alert_message_vibra && !sender->is_muted) {
                 LOG_INFO("externalNotificationModule - Notification Module (Vibra)");
                 isNagging = true;
                 setExternalState(1, true);
@@ -529,7 +530,7 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP
                 }
             }
 
-            if (moduleConfig.external_notification.alert_message_buzzer) {
+            if (moduleConfig.external_notification.alert_message_buzzer && !sender->is_muted) {
                 LOG_INFO("externalNotificationModule - Notification Module (Buzzer)");
                 isNagging = true;
                 if (!moduleConfig.external_notification.use_pwm && !moduleConfig.external_notification.use_i2s_as_buzzer) {

From 8c9c00172cdf5f7bb2d78f4f370198a95f5843d9 Mon Sep 17 00:00:00 2001
From: ford-jones 
Date: Fri, 12 Sep 2025 14:12:22 +1200
Subject: [PATCH 168/691] Clearly dilineate module mute from sender or channel
 mute

---
 src/modules/ExternalNotificationModule.h | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/src/modules/ExternalNotificationModule.h b/src/modules/ExternalNotificationModule.h
index 19cf9eb7b..f667f7be9 100644
--- a/src/modules/ExternalNotificationModule.h
+++ b/src/modules/ExternalNotificationModule.h
@@ -43,8 +43,8 @@ class ExternalNotificationModule : public SinglePortModule, private concurrency:
     void setExternalState(uint8_t index = 0, bool on = false);
     bool getExternal(uint8_t index = 0);
 
-    void setMute(bool mute) { isMuted = mute; }
-    bool getMute() { return isMuted; }
+    void setMute(bool mute) { isSilenced = mute; }
+    bool getMute() { return isSilenced; }
 
     bool canBuzz();
     bool nagging();
@@ -67,7 +67,7 @@ class ExternalNotificationModule : public SinglePortModule, private concurrency:
 
     bool isNagging = false;
 
-    bool isMuted = false;
+    bool isSilenced = false;
 
     virtual AdminMessageHandleResult handleAdminMessageForModule(const meshtastic_MeshPacket &mp,
                                                                  meshtastic_AdminMessage *request,

From 4e879a7b261e38fc6ef3f0bc9286229aa7e82f0a Mon Sep 17 00:00:00 2001
From: ford-jones 
Date: Fri, 12 Sep 2025 14:12:55 +1200
Subject: [PATCH 169/691] Disable bell-invoked ext notifs for muted channels

---
 src/modules/ExternalNotificationModule.cpp | 12 +++++++-----
 1 file changed, 7 insertions(+), 5 deletions(-)

diff --git a/src/modules/ExternalNotificationModule.cpp b/src/modules/ExternalNotificationModule.cpp
index b73547c02..c54306ffd 100644
--- a/src/modules/ExternalNotificationModule.cpp
+++ b/src/modules/ExternalNotificationModule.cpp
@@ -440,7 +440,7 @@ ExternalNotificationModule::ExternalNotificationModule()
 
 ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshPacket &mp)
 {
-    if (moduleConfig.external_notification.enabled && !isMuted) {
+    if (moduleConfig.external_notification.enabled && !isSilenced) {
 #ifdef T_WATCH_S3
         drv.setWaveform(0, 75);
         drv.setWaveform(1, 56);
@@ -506,9 +506,11 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP
                     }
                 }
             }
-            meshtastic_NodeInfoLite *sender = nodeDB->getMeshNode(mp.from);
 
-            if (moduleConfig.external_notification.alert_message && !sender->is_muted) {
+            meshtastic_NodeInfoLite *sender = nodeDB->getMeshNode(mp.from);
+            meshtastic_Channel ch = channels.getByIndex(sender->channel ? sender->channel : channels.getPrimaryIndex());
+
+            if (moduleConfig.external_notification.alert_message && !sender->is_muted && !ch.settings.mute) {
                 LOG_INFO("externalNotificationModule - Notification Module");
                 isNagging = true;
                 setExternalState(0, true);
@@ -519,7 +521,7 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP
                 }
             }
 
-            if (moduleConfig.external_notification.alert_message_vibra && !sender->is_muted) {
+            if (moduleConfig.external_notification.alert_message_vibra && !sender->is_muted && !ch.settings.mute) {
                 LOG_INFO("externalNotificationModule - Notification Module (Vibra)");
                 isNagging = true;
                 setExternalState(1, true);
@@ -530,7 +532,7 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP
                 }
             }
 
-            if (moduleConfig.external_notification.alert_message_buzzer && !sender->is_muted) {
+            if (moduleConfig.external_notification.alert_message_buzzer && !sender->is_muted && !ch.settings.mute) {
                 LOG_INFO("externalNotificationModule - Notification Module (Buzzer)");
                 isNagging = true;
                 if (!moduleConfig.external_notification.use_pwm && !moduleConfig.external_notification.use_i2s_as_buzzer) {

From 71f659cba6c26503a6881a54b98910cfc0dbb4f8 Mon Sep 17 00:00:00 2001
From: ford-jones 
Date: Fri, 12 Sep 2025 14:15:06 +1200
Subject: [PATCH 170/691] Trunk fmt

---
 src/SerialConsole.cpp                         |  7 +++----
 src/mesh/StreamAPI.h                          |  6 +++---
 variants/esp32s3/t-deck-pro/variant.h         | 15 +++++++------
 .../nrf52840/heltec_mesh_solar/variant.cpp    |  2 +-
 variants/nrf52840/heltec_mesh_solar/variant.h | 21 +++++++++----------
 5 files changed, 24 insertions(+), 27 deletions(-)

diff --git a/src/SerialConsole.cpp b/src/SerialConsole.cpp
index 093a24678..2e6ae68a5 100644
--- a/src/SerialConsole.cpp
+++ b/src/SerialConsole.cpp
@@ -65,10 +65,9 @@ SerialConsole::SerialConsole() : StreamAPI(&Port), RedirectablePrint(&Port), con
 int32_t SerialConsole::runOnce()
 {
 #ifdef HELTEC_MESH_SOLAR
-    //After enabling the mesh solar serial port module configuration, command processing is handled by the serial port module.
-    if(moduleConfig.serial.enabled && moduleConfig.serial.override_console_serial_port
-        && moduleConfig.serial.mode==meshtastic_ModuleConfig_SerialConfig_Serial_Mode_MS_CONFIG)
-    {
+    // After enabling the mesh solar serial port module configuration, command processing is handled by the serial port module.
+    if (moduleConfig.serial.enabled && moduleConfig.serial.override_console_serial_port &&
+        moduleConfig.serial.mode == meshtastic_ModuleConfig_SerialConfig_Serial_Mode_MS_CONFIG) {
         return 250;
     }
 #endif
diff --git a/src/mesh/StreamAPI.h b/src/mesh/StreamAPI.h
index 547dd0175..4ca2c197f 100644
--- a/src/mesh/StreamAPI.h
+++ b/src/mesh/StreamAPI.h
@@ -50,15 +50,15 @@ class StreamAPI : public PhoneAPI
      * phone.
      */
     virtual int32_t runOncePart();
-    virtual int32_t runOncePart(char *buf,uint16_t bufLen);
+    virtual int32_t runOncePart(char *buf, uint16_t bufLen);
 
   private:
     /**
      * Read any rx chars from the link and call handleToRadio
      */
     int32_t readStream();
-    int32_t readStream(char *buf,uint16_t bufLen);
-    int32_t handleRecStream(char *buf,uint16_t bufLen);
+    int32_t readStream(char *buf, uint16_t bufLen);
+    int32_t handleRecStream(char *buf, uint16_t bufLen);
 
     /**
      * call getFromRadio() and deliver encapsulated packets to the Stream
diff --git a/variants/esp32s3/t-deck-pro/variant.h b/variants/esp32s3/t-deck-pro/variant.h
index abe0a772a..35cb99435 100644
--- a/variants/esp32s3/t-deck-pro/variant.h
+++ b/variants/esp32s3/t-deck-pro/variant.h
@@ -93,11 +93,10 @@
 // Internally the TTGO module hooks the SX1262-DIO2 in to control the TX/RX switch (which is the default for the sx1262interface
 // code)
 
-#define MODEM_POWER_EN  41
-#define MODEM_PWRKEY    40
-#define MODEM_RST  9
-#define MODEM_RI  7
-#define MODEM_DTR  8
-#define MODEM_RX  10
-#define MODEM_TX  11
-
+#define MODEM_POWER_EN 41
+#define MODEM_PWRKEY 40
+#define MODEM_RST 9
+#define MODEM_RI 7
+#define MODEM_DTR 8
+#define MODEM_RX 10
+#define MODEM_TX 11
diff --git a/variants/nrf52840/heltec_mesh_solar/variant.cpp b/variants/nrf52840/heltec_mesh_solar/variant.cpp
index 8236d7cf4..05d7a32e2 100644
--- a/variants/nrf52840/heltec_mesh_solar/variant.cpp
+++ b/variants/nrf52840/heltec_mesh_solar/variant.cpp
@@ -32,5 +32,5 @@ const uint32_t g_ADigitalPinMap[] = {
 
 void initVariant()
 {
-  pinMode(BQ4050_EMERGENCY_SHUTDOWN_PIN, INPUT);
+    pinMode(BQ4050_EMERGENCY_SHUTDOWN_PIN, INPUT);
 }
diff --git a/variants/nrf52840/heltec_mesh_solar/variant.h b/variants/nrf52840/heltec_mesh_solar/variant.h
index 33c2b2556..4165bc349 100644
--- a/variants/nrf52840/heltec_mesh_solar/variant.h
+++ b/variants/nrf52840/heltec_mesh_solar/variant.h
@@ -39,16 +39,15 @@ extern "C" {
 #define NUM_ANALOG_INPUTS (1)
 #define NUM_ANALOG_OUTPUTS (0)
 
-
 #define PIN_LED1 (0 + 12) // green (confirmed on 1.0 board)
 #define LED_BLUE PIN_LED1 // fake for bluefruit library
 #define LED_GREEN PIN_LED1
 #define LED_BUILTIN LED_GREEN
-#define LED_STATE_ON 0  // State when LED is lit
+#define LED_STATE_ON 0 // State when LED is lit
 
 #define HAS_NEOPIXEL                         // Enable the use of neopixels
 #define NEOPIXEL_COUNT 1                     // How many neopixels are connected
-#define NEOPIXEL_DATA (32+15)                // gpio pin used to send data to the neopixels
+#define NEOPIXEL_DATA (32 + 15)              // gpio pin used to send data to the neopixels
 #define NEOPIXEL_TYPE (NEO_GRB + NEO_KHZ800) // type of neopixels in use
 
 /*
@@ -74,13 +73,13 @@ No longer populated on PCB
 // I2C bus 0
 // Routed to footprint for PCF8563TS RTC
 // Not populated on T114 V1, maybe in future?
-#define PIN_WIRE_SDA (0 + 6) // P0.26
+#define PIN_WIRE_SDA (0 + 6)  // P0.26
 #define PIN_WIRE_SCL (0 + 26) // P0.26
 
 // I2C bus 1
 // Available on header pins, for general use
 #define PIN_WIRE1_SDA (0 + 30) // P0.30
-#define PIN_WIRE1_SCL (0 + 5) // P0.13
+#define PIN_WIRE1_SCL (0 + 5)  // P0.13
 
 /*
  * Lora radio
@@ -89,14 +88,14 @@ No longer populated on PCB
 #define USE_SX1262
 // #define USE_SX1268
 #define SX126X_CS (0 + 24) // FIXME - we really should define LORA_CS instead
-#define LORA_CS  (0 + 24)
+#define LORA_CS (0 + 24)
 #define SX126X_DIO1 (0 + 20)
 // Note DIO2 is attached internally to the module to an analog switch for TX/RX switching
 // #define SX1262_DIO3 (0 + 21)
 // This is used as an *output* from the sx1262 and connected internally to power the tcxo, do not drive from the
 //    main
 // CPU?
-#define SX126X_BUSY  (0 + 17)
+#define SX126X_BUSY (0 + 17)
 #define SX126X_RESET (0 + 25)
 // Not really an E22 but TTGO seems to be trying to clone that
 #define SX126X_DIO2_AS_RF_SWITCH
@@ -134,16 +133,16 @@ No longer populated on PCB
 // For LORA, spi 0
 #define PIN_SPI_MISO (0 + 23)
 #define PIN_SPI_MOSI (0 + 22)
-#define PIN_SPI_SCK  (0 + 19)
+#define PIN_SPI_SCK (0 + 19)
 
 // #define PIN_PWR_EN (0 + 6)
 
 // To debug via the segger JLINK console rather than the CDC-ACM serial device
 // #define USE_SEGGER
 
-#define BQ4050_SDA_PIN                      (32+1) // I2C data line pin
-#define BQ4050_SCL_PIN                      (32+0) // I2C clock line pin
-#define BQ4050_EMERGENCY_SHUTDOWN_PIN       (32+3) // Emergency shutdown pin
+#define BQ4050_SDA_PIN (32 + 1)                // I2C data line pin
+#define BQ4050_SCL_PIN (32 + 0)                // I2C clock line pin
+#define BQ4050_EMERGENCY_SHUTDOWN_PIN (32 + 3) // Emergency shutdown pin
 
 #define HAS_RTC 0
 #ifdef __cplusplus

From 693181b2be40a2280f4a1984313aeb7394bf068a Mon Sep 17 00:00:00 2001
From: ford-jones 
Date: Fri, 12 Sep 2025 15:44:37 +1200
Subject: [PATCH 171/691] Disable message-invoked ext notifs for muted channels
 and nodes

---
 src/modules/ExternalNotificationModule.cpp | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/src/modules/ExternalNotificationModule.cpp b/src/modules/ExternalNotificationModule.cpp
index c54306ffd..4dd8811ec 100644
--- a/src/modules/ExternalNotificationModule.cpp
+++ b/src/modules/ExternalNotificationModule.cpp
@@ -457,7 +457,10 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP
                 }
             }
 
-            if (moduleConfig.external_notification.alert_bell) {
+            meshtastic_NodeInfoLite *sender = nodeDB->getMeshNode(mp.from);
+            meshtastic_Channel ch = channels.getByIndex(sender->channel ? sender->channel : channels.getPrimaryIndex());
+
+            if (moduleConfig.external_notification.alert_bell && !sender->is_muted && !ch.settings.mute) {
                 if (containsBell) {
                     LOG_INFO("externalNotificationModule - Notification Bell");
                     isNagging = true;
@@ -470,7 +473,7 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP
                 }
             }
 
-            if (moduleConfig.external_notification.alert_bell_vibra) {
+            if (moduleConfig.external_notification.alert_bell_vibra && !sender->is_muted && !ch.settings.mute) {
                 if (containsBell) {
                     LOG_INFO("externalNotificationModule - Notification Bell (Vibra)");
                     isNagging = true;
@@ -483,7 +486,7 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP
                 }
             }
 
-            if (moduleConfig.external_notification.alert_bell_buzzer && canBuzz()) {
+            if (moduleConfig.external_notification.alert_bell_buzzer && canBuzz() && !sender->is_muted && !ch.settings.mute) {
                 if (containsBell) {
                     LOG_INFO("externalNotificationModule - Notification Bell (Buzzer)");
                     isNagging = true;
@@ -507,9 +510,6 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP
                 }
             }
 
-            meshtastic_NodeInfoLite *sender = nodeDB->getMeshNode(mp.from);
-            meshtastic_Channel ch = channels.getByIndex(sender->channel ? sender->channel : channels.getPrimaryIndex());
-
             if (moduleConfig.external_notification.alert_message && !sender->is_muted && !ch.settings.mute) {
                 LOG_INFO("externalNotificationModule - Notification Module");
                 isNagging = true;

From 7e00054fd7ec5c77d8a33384c7357d06c42a128d Mon Sep 17 00:00:00 2001
From: Mike Robbins 
Date: Fri, 5 Sep 2025 13:38:01 -0700
Subject: [PATCH 172/691] Rename startTransmitTimerSNR to
 startTransmitTimerRebroadcast

---
 src/mesh/RadioLibInterface.cpp      | 4 ++--
 src/mesh/RadioLibInterface.h        | 2 +-
 src/platform/portduino/SimRadio.cpp | 4 ++--
 src/platform/portduino/SimRadio.h   | 2 +-
 4 files changed, 6 insertions(+), 6 deletions(-)

diff --git a/src/mesh/RadioLibInterface.cpp b/src/mesh/RadioLibInterface.cpp
index c18612101..ced8e48a5 100644
--- a/src/mesh/RadioLibInterface.cpp
+++ b/src/mesh/RadioLibInterface.cpp
@@ -323,7 +323,7 @@ void RadioLibInterface::setTransmitDelay()
     } else {
         // If there is a SNR, start a timer scaled based on that SNR.
         LOG_DEBUG("rx_snr found. hop_limit:%d rx_snr:%f", p->hop_limit, p->rx_snr);
-        startTransmitTimerSNR(p->rx_snr);
+        startTransmitTimerRebroadcast(p->rx_snr);
     }
 }
 
@@ -336,7 +336,7 @@ void RadioLibInterface::startTransmitTimer(bool withDelay)
     }
 }
 
-void RadioLibInterface::startTransmitTimerSNR(float snr)
+void RadioLibInterface::startTransmitTimerRebroadcast(float snr)
 {
     // If we have work to do and the timer wasn't already scheduled, schedule it now
     if (!txQueue.empty()) {
diff --git a/src/mesh/RadioLibInterface.h b/src/mesh/RadioLibInterface.h
index 2ab2679c0..f0c6027a8 100644
--- a/src/mesh/RadioLibInterface.h
+++ b/src/mesh/RadioLibInterface.h
@@ -161,7 +161,7 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified
      * timer scaled to SNR of to be flooded packet
      * @return Timestamp after which the packet may be sent
      */
-    void startTransmitTimerSNR(float snr);
+    void startTransmitTimerRebroadcast(float snr);
 
     void handleTransmitInterrupt();
     void handleReceiveInterrupt();
diff --git a/src/platform/portduino/SimRadio.cpp b/src/platform/portduino/SimRadio.cpp
index 4e748c5f9..6771c30c9 100644
--- a/src/platform/portduino/SimRadio.cpp
+++ b/src/platform/portduino/SimRadio.cpp
@@ -43,7 +43,7 @@ void SimRadio::setTransmitDelay()
     } else {
         // If there is a SNR, start a timer scaled based on that SNR.
         LOG_DEBUG("rx_snr found. hop_limit:%d rx_snr:%f", p->hop_limit, p->rx_snr);
-        startTransmitTimerSNR(p->rx_snr);
+        startTransmitTimerRebroadcast(p->rx_snr);
     }
 }
 
@@ -57,7 +57,7 @@ void SimRadio::startTransmitTimer(bool withDelay)
     }
 }
 
-void SimRadio::startTransmitTimerSNR(float snr)
+void SimRadio::startTransmitTimerRebroadcast(float snr)
 {
     // If we have work to do and the timer wasn't already scheduled, schedule it now
     if (!txQueue.empty()) {
diff --git a/src/platform/portduino/SimRadio.h b/src/platform/portduino/SimRadio.h
index ea534bd65..a8d3a6394 100644
--- a/src/platform/portduino/SimRadio.h
+++ b/src/platform/portduino/SimRadio.h
@@ -64,7 +64,7 @@ class SimRadio : public RadioInterface, protected concurrency::NotifiedWorkerThr
     void startTransmitTimer(bool withDelay = true);
 
     /** timer scaled to SNR of to be flooded packet */
-    void startTransmitTimerSNR(float snr);
+    void startTransmitTimerRebroadcast(float snr);
 
     void handleTransmitInterrupt();
     void handleReceiveInterrupt();

From 3cc2b70e4f35d44daac0db3584bd5170aa175eb6 Mon Sep 17 00:00:00 2001
From: Mike Robbins 
Date: Fri, 5 Sep 2025 13:53:59 -0700
Subject: [PATCH 173/691] Pass meshtastic_MeshPacket down into
 startTransmitTimerRebroadcast and getTxDelayMsecWeighted

---
 src/mesh/RadioInterface.cpp         | 2 +-
 src/mesh/RadioInterface.h           | 2 +-
 src/mesh/RadioLibInterface.cpp      | 8 ++++----
 src/mesh/RadioLibInterface.h        | 2 +-
 src/platform/portduino/SimRadio.cpp | 6 +++---
 src/platform/portduino/SimRadio.h   | 2 +-
 6 files changed, 11 insertions(+), 11 deletions(-)

diff --git a/src/mesh/RadioInterface.cpp b/src/mesh/RadioInterface.cpp
index 20a0bdbd1..c7b57a36c 100644
--- a/src/mesh/RadioInterface.cpp
+++ b/src/mesh/RadioInterface.cpp
@@ -315,7 +315,7 @@ uint32_t RadioInterface::getTxDelayMsecWeightedWorst(float snr)
 }
 
 /** The delay to use when we want to flood a message */
-uint32_t RadioInterface::getTxDelayMsecWeighted(float snr)
+uint32_t RadioInterface::getTxDelayMsecWeighted(float snr, meshtastic_MeshPacket *p)
 {
     //  high SNR = large CW size (Long Delay)
     //  low SNR = small CW size (Short Delay)
diff --git a/src/mesh/RadioInterface.h b/src/mesh/RadioInterface.h
index c9e71cfa8..7e36ac442 100644
--- a/src/mesh/RadioInterface.h
+++ b/src/mesh/RadioInterface.h
@@ -181,7 +181,7 @@ class RadioInterface
     uint32_t getTxDelayMsecWeightedWorst(float snr);
 
     /** The delay to use when we want to flood a message. Use a weighted scale based on SNR */
-    uint32_t getTxDelayMsecWeighted(float snr);
+    uint32_t getTxDelayMsecWeighted(float snr, meshtastic_MeshPacket *p);
 
     /** If the packet is not already in the late rebroadcast window, move it there */
     virtual void clampToLateRebroadcastWindow(NodeNum from, PacketId id) { return; }
diff --git a/src/mesh/RadioLibInterface.cpp b/src/mesh/RadioLibInterface.cpp
index ced8e48a5..3fcced2f5 100644
--- a/src/mesh/RadioLibInterface.cpp
+++ b/src/mesh/RadioLibInterface.cpp
@@ -310,7 +310,7 @@ void RadioLibInterface::setTransmitDelay()
     // So we want to make sure the other side has had a chance to reconfigure its radio.
 
     if (p->tx_after) {
-        unsigned long add_delay = p->rx_rssi ? getTxDelayMsecWeighted(p->rx_snr) : getTxDelayMsec();
+        unsigned long add_delay = p->rx_rssi ? getTxDelayMsecWeighted(p->rx_snr, p) : getTxDelayMsec();
         unsigned long now = millis();
         p->tx_after = min(max(p->tx_after + add_delay, now + add_delay), now + 2 * getTxDelayMsecWeightedWorst(p->rx_snr));
         notifyLater(p->tx_after - now, TRANSMIT_DELAY_COMPLETED, false);
@@ -323,7 +323,7 @@ void RadioLibInterface::setTransmitDelay()
     } else {
         // If there is a SNR, start a timer scaled based on that SNR.
         LOG_DEBUG("rx_snr found. hop_limit:%d rx_snr:%f", p->hop_limit, p->rx_snr);
-        startTransmitTimerRebroadcast(p->rx_snr);
+        startTransmitTimerRebroadcast(p->rx_snr, p);
     }
 }
 
@@ -336,11 +336,11 @@ void RadioLibInterface::startTransmitTimer(bool withDelay)
     }
 }
 
-void RadioLibInterface::startTransmitTimerRebroadcast(float snr)
+void RadioLibInterface::startTransmitTimerRebroadcast(float snr, meshtastic_MeshPacket *p)
 {
     // If we have work to do and the timer wasn't already scheduled, schedule it now
     if (!txQueue.empty()) {
-        uint32_t delay = getTxDelayMsecWeighted(snr);
+        uint32_t delay = getTxDelayMsecWeighted(snr, p);
         notifyLater(delay, TRANSMIT_DELAY_COMPLETED, false); // This will implicitly enable
     }
 }
diff --git a/src/mesh/RadioLibInterface.h b/src/mesh/RadioLibInterface.h
index f0c6027a8..224ac6376 100644
--- a/src/mesh/RadioLibInterface.h
+++ b/src/mesh/RadioLibInterface.h
@@ -161,7 +161,7 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified
      * timer scaled to SNR of to be flooded packet
      * @return Timestamp after which the packet may be sent
      */
-    void startTransmitTimerRebroadcast(float snr);
+    void startTransmitTimerRebroadcast(float snr, meshtastic_MeshPacket *p);
 
     void handleTransmitInterrupt();
     void handleReceiveInterrupt();
diff --git a/src/platform/portduino/SimRadio.cpp b/src/platform/portduino/SimRadio.cpp
index 6771c30c9..504383aee 100644
--- a/src/platform/portduino/SimRadio.cpp
+++ b/src/platform/portduino/SimRadio.cpp
@@ -43,7 +43,7 @@ void SimRadio::setTransmitDelay()
     } else {
         // If there is a SNR, start a timer scaled based on that SNR.
         LOG_DEBUG("rx_snr found. hop_limit:%d rx_snr:%f", p->hop_limit, p->rx_snr);
-        startTransmitTimerRebroadcast(p->rx_snr);
+        startTransmitTimerRebroadcast(p->rx_snr, p);
     }
 }
 
@@ -57,11 +57,11 @@ void SimRadio::startTransmitTimer(bool withDelay)
     }
 }
 
-void SimRadio::startTransmitTimerRebroadcast(float snr)
+void SimRadio::startTransmitTimerRebroadcast(float snr, meshtastic_MeshPacket *p)
 {
     // If we have work to do and the timer wasn't already scheduled, schedule it now
     if (!txQueue.empty()) {
-        uint32_t delayMsec = getTxDelayMsecWeighted(snr);
+        uint32_t delayMsec = getTxDelayMsecWeighted(snr, p);
         // LOG_DEBUG("xmit timer %d", delay);
         notifyLater(delayMsec, TRANSMIT_DELAY_COMPLETED, false);
     }
diff --git a/src/platform/portduino/SimRadio.h b/src/platform/portduino/SimRadio.h
index a8d3a6394..86f07c7b3 100644
--- a/src/platform/portduino/SimRadio.h
+++ b/src/platform/portduino/SimRadio.h
@@ -64,7 +64,7 @@ class SimRadio : public RadioInterface, protected concurrency::NotifiedWorkerThr
     void startTransmitTimer(bool withDelay = true);
 
     /** timer scaled to SNR of to be flooded packet */
-    void startTransmitTimerRebroadcast(float snr);
+    void startTransmitTimerRebroadcast(float snr, meshtastic_MeshPacket *p);
 
     void handleTransmitInterrupt();
     void handleReceiveInterrupt();

From 484b4cd8486b6e8c3d140e928c73a7df26c482ef Mon Sep 17 00:00:00 2001
From: Mike Robbins 
Date: Fri, 5 Sep 2025 14:25:09 -0700
Subject: [PATCH 174/691] Add NodeDB::isFavorite,
 NodeDB::isFromOrToFavoritedNode

---
 src/mesh/NodeDB.cpp | 14 ++++++++++++++
 src/mesh/NodeDB.h   | 10 ++++++++++
 2 files changed, 24 insertions(+)

diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp
index 52a18a53f..3bdfad30f 100644
--- a/src/mesh/NodeDB.cpp
+++ b/src/mesh/NodeDB.cpp
@@ -1770,6 +1770,20 @@ void NodeDB::set_favorite(bool is_favorite, uint32_t nodeId)
     }
 }
 
+bool NodeDB::isFavorite(uint32_t nodeId)
+{
+    meshtastic_NodeInfoLite *lite = getMeshNode(nodeId);
+    if (lite) {
+        return lite->is_favorite;
+    }
+    return false;
+}
+
+bool NodeDB::isFromOrToFavoritedNode(const meshtastic_MeshPacket &p)
+{
+    return isFavorite(p.from) || isFavorite(p.to);
+}
+
 void NodeDB::pause_sort(bool paused)
 {
     sortingIsPaused = paused;
diff --git a/src/mesh/NodeDB.h b/src/mesh/NodeDB.h
index 167dc1337..f73f64f92 100644
--- a/src/mesh/NodeDB.h
+++ b/src/mesh/NodeDB.h
@@ -185,6 +185,16 @@ class NodeDB
      */
     void set_favorite(bool is_favorite, uint32_t nodeId);
 
+    /*
+     * Returns true if the node is in the NodeDB and marked as favorite
+     */
+    bool isFavorite(uint32_t nodeId);
+
+    /*
+     * Returns true if p->from or p->to is a favorited node
+     */
+    bool isFromOrToFavoritedNode(const meshtastic_MeshPacket &p);
+
     /**
      * Other functions like the node picker can request a pause in the node sorting
      */

From ab5332950c628f5dbbc34c8d08b15b33f70a2564 Mon Sep 17 00:00:00 2001
From: Mike Robbins 
Date: Fri, 5 Sep 2025 14:26:06 -0700
Subject: [PATCH 175/691] Add RadioInterface::shouldRebroadcastEarlyLikeRouter
 and add CLIENT_BASE condition

---
 src/mesh/RadioInterface.cpp | 20 ++++++++++++++++++--
 src/mesh/RadioInterface.h   |  3 +++
 2 files changed, 21 insertions(+), 2 deletions(-)

diff --git a/src/mesh/RadioInterface.cpp b/src/mesh/RadioInterface.cpp
index c7b57a36c..1cbee6f47 100644
--- a/src/mesh/RadioInterface.cpp
+++ b/src/mesh/RadioInterface.cpp
@@ -314,6 +314,23 @@ uint32_t RadioInterface::getTxDelayMsecWeightedWorst(float snr)
     return (2 * CWmax * slotTimeMsec) + pow_of_2(CWsize) * slotTimeMsec;
 }
 
+/** Returns true if we should rebroadcast early like a ROUTER */
+bool RadioInterface::shouldRebroadcastEarlyLikeRouter(meshtastic_MeshPacket *p)
+{
+    // If we are a ROUTER or REPEATER, we always rebroadcast early
+    if (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER ||
+        config.device.role == meshtastic_Config_DeviceConfig_Role_REPEATER) {
+        return true;
+    }
+
+    // If we are a CLIENT_BASE and the packet is from or to a favorited node, we should rebroadcast early
+    if (config.device.role == meshtastic_Config_DeviceConfig_Role_CLIENT_BASE) {
+        return nodeDB->isFromOrToFavoritedNode(*p);
+    }
+
+    return false;
+}
+
 /** The delay to use when we want to flood a message */
 uint32_t RadioInterface::getTxDelayMsecWeighted(float snr, meshtastic_MeshPacket *p)
 {
@@ -322,8 +339,7 @@ uint32_t RadioInterface::getTxDelayMsecWeighted(float snr, meshtastic_MeshPacket
     uint32_t delay = 0;
     uint8_t CWsize = getCWsize(snr);
     // LOG_DEBUG("rx_snr of %f so setting CWsize to:%d", snr, CWsize);
-    if (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER ||
-        config.device.role == meshtastic_Config_DeviceConfig_Role_REPEATER) {
+    if (shouldRebroadcastEarlyLikeRouter(p)) {
         delay = random(0, 2 * CWsize) * slotTimeMsec;
         LOG_DEBUG("rx_snr found in packet. Router: setting tx delay:%d", delay);
     } else {
diff --git a/src/mesh/RadioInterface.h b/src/mesh/RadioInterface.h
index 7e36ac442..a89fa33dd 100644
--- a/src/mesh/RadioInterface.h
+++ b/src/mesh/RadioInterface.h
@@ -180,6 +180,9 @@ class RadioInterface
     /** The worst-case SNR_based packet delay */
     uint32_t getTxDelayMsecWeightedWorst(float snr);
 
+    /** Returns true if we should rebroadcast early like a ROUTER */
+    bool shouldRebroadcastEarlyLikeRouter(meshtastic_MeshPacket *p);
+
     /** The delay to use when we want to flood a message. Use a weighted scale based on SNR */
     uint32_t getTxDelayMsecWeighted(float snr, meshtastic_MeshPacket *p);
 

From b305acf7e500f0855c51e039a6aba4627d327984 Mon Sep 17 00:00:00 2001
From: Mike Robbins 
Date: Fri, 5 Sep 2025 15:10:37 -0700
Subject: [PATCH 176/691] Add FloodingRouter::roleAllowsCancelingDupe and
 condition for CLIENT_BASE

---
 src/mesh/FloodingRouter.cpp | 25 ++++++++++++++++++++++---
 src/mesh/FloodingRouter.h   |  3 +++
 2 files changed, 25 insertions(+), 3 deletions(-)

diff --git a/src/mesh/FloodingRouter.cpp b/src/mesh/FloodingRouter.cpp
index dbd458b61..ce9b91029 100644
--- a/src/mesh/FloodingRouter.cpp
+++ b/src/mesh/FloodingRouter.cpp
@@ -43,11 +43,30 @@ bool FloodingRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
     return Router::shouldFilterReceived(p);
 }
 
+bool FloodingRouter::roleAllowsCancelingDupe(const meshtastic_MeshPacket *p)
+{
+    if (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER ||
+        config.device.role == meshtastic_Config_DeviceConfig_Role_REPEATER ||
+        config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER_LATE) {
+        // ROUTER, REPEATER, ROUTER_LATE should never cancel relaying a packet (i.e. we should always rebroadcast),
+        // even if we've heard another station rebroadcast it already.
+        return false;
+    }
+
+    if (config.device.role == meshtastic_Config_DeviceConfig_Role_CLIENT_BASE) {
+        // CLIENT_BASE: if the packet is from or to a favorited node,
+        // we should act like a ROUTER and should never cancel a rebroadcast (i.e. we should always rebroadcast),
+        // even if we've heard another station rebroadcast it already.
+        return !nodeDB->isFromOrToFavoritedNode(*p);
+    }
+
+    // All other roles (such as CLIENT) should cancel a rebroadcast if they hear another station's rebroadcast.
+    return true;
+}
+
 void FloodingRouter::perhapsCancelDupe(const meshtastic_MeshPacket *p)
 {
-    if (config.device.role != meshtastic_Config_DeviceConfig_Role_ROUTER &&
-        config.device.role != meshtastic_Config_DeviceConfig_Role_REPEATER &&
-        config.device.role != meshtastic_Config_DeviceConfig_Role_ROUTER_LATE &&
+    if (roleAllowsCancelingDupe(p) &&
         p->transport_mechanism == meshtastic_MeshPacket_TransportMechanism_TRANSPORT_LORA) {
         // cancel rebroadcast of this message *if* there was already one, unless we're a router/repeater!
         // But only LoRa packets should be able to trigger this.
diff --git a/src/mesh/FloodingRouter.h b/src/mesh/FloodingRouter.h
index 36c6ad8aa..68ba2a6e1 100644
--- a/src/mesh/FloodingRouter.h
+++ b/src/mesh/FloodingRouter.h
@@ -59,6 +59,9 @@ class FloodingRouter : public Router
      */
     virtual void sniffReceived(const meshtastic_MeshPacket *p, const meshtastic_Routing *c) override;
 
+    // Return false for roles like ROUTER or REPEATER which should always rebroadcast even when we've heard another rebroadcast of the same packet
+    bool roleAllowsCancelingDupe(const meshtastic_MeshPacket *p);
+
     /* Call when receiving a duplicate packet to check whether we should cancel a packet in the Tx queue */
     void perhapsCancelDupe(const meshtastic_MeshPacket *p);
 

From b1f55ef6e83c5a5e62b66f53171675e03dce0296 Mon Sep 17 00:00:00 2001
From: Mike Robbins 
Date: Sun, 7 Sep 2025 17:11:35 -0700
Subject: [PATCH 177/691] Fix linter

---
 src/mesh/FloodingRouter.cpp | 3 +--
 src/mesh/FloodingRouter.h   | 3 ++-
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/src/mesh/FloodingRouter.cpp b/src/mesh/FloodingRouter.cpp
index ce9b91029..31c0d6bd6 100644
--- a/src/mesh/FloodingRouter.cpp
+++ b/src/mesh/FloodingRouter.cpp
@@ -66,8 +66,7 @@ bool FloodingRouter::roleAllowsCancelingDupe(const meshtastic_MeshPacket *p)
 
 void FloodingRouter::perhapsCancelDupe(const meshtastic_MeshPacket *p)
 {
-    if (roleAllowsCancelingDupe(p) &&
-        p->transport_mechanism == meshtastic_MeshPacket_TransportMechanism_TRANSPORT_LORA) {
+    if (roleAllowsCancelingDupe(p) && p->transport_mechanism == meshtastic_MeshPacket_TransportMechanism_TRANSPORT_LORA) {
         // cancel rebroadcast of this message *if* there was already one, unless we're a router/repeater!
         // But only LoRa packets should be able to trigger this.
         if (Router::cancelSending(p->from, p->id))
diff --git a/src/mesh/FloodingRouter.h b/src/mesh/FloodingRouter.h
index 68ba2a6e1..30ad5945b 100644
--- a/src/mesh/FloodingRouter.h
+++ b/src/mesh/FloodingRouter.h
@@ -59,7 +59,8 @@ class FloodingRouter : public Router
      */
     virtual void sniffReceived(const meshtastic_MeshPacket *p, const meshtastic_Routing *c) override;
 
-    // Return false for roles like ROUTER or REPEATER which should always rebroadcast even when we've heard another rebroadcast of the same packet
+    // Return false for roles like ROUTER or REPEATER which should always rebroadcast even when we've heard another rebroadcast of
+    // the same packet
     bool roleAllowsCancelingDupe(const meshtastic_MeshPacket *p);
 
     /* Call when receiving a duplicate packet to check whether we should cancel a packet in the Tx queue */

From c63102a312ce1716cf71fd03dd848cd282d012df Mon Sep 17 00:00:00 2001
From: Mike Robbins 
Date: Sun, 7 Sep 2025 17:13:46 -0700
Subject: [PATCH 178/691] Swap expression order to allow short-circuit
 evaluation

---
 src/mesh/FloodingRouter.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/mesh/FloodingRouter.cpp b/src/mesh/FloodingRouter.cpp
index 31c0d6bd6..f805055c8 100644
--- a/src/mesh/FloodingRouter.cpp
+++ b/src/mesh/FloodingRouter.cpp
@@ -66,7 +66,7 @@ bool FloodingRouter::roleAllowsCancelingDupe(const meshtastic_MeshPacket *p)
 
 void FloodingRouter::perhapsCancelDupe(const meshtastic_MeshPacket *p)
 {
-    if (roleAllowsCancelingDupe(p) && p->transport_mechanism == meshtastic_MeshPacket_TransportMechanism_TRANSPORT_LORA) {
+    if (p->transport_mechanism == meshtastic_MeshPacket_TransportMechanism_TRANSPORT_LORA && roleAllowsCancelingDupe(p)) {
         // cancel rebroadcast of this message *if* there was already one, unless we're a router/repeater!
         // But only LoRa packets should be able to trigger this.
         if (Router::cancelSending(p->from, p->id))

From b768860866c378be7512ecb121e3886f0516b3e0 Mon Sep 17 00:00:00 2001
From: Mike Robbins 
Date: Sun, 7 Sep 2025 21:08:00 -0700
Subject: [PATCH 179/691] NodeDB::isFromOrToFavoritedNode: skip search for
 NODENUM_BROADCAST; one-pass search and early exit

---
 src/mesh/NodeDB.cpp | 47 ++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 46 insertions(+), 1 deletion(-)

diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp
index 3bdfad30f..1859ca27b 100644
--- a/src/mesh/NodeDB.cpp
+++ b/src/mesh/NodeDB.cpp
@@ -1772,7 +1772,14 @@ void NodeDB::set_favorite(bool is_favorite, uint32_t nodeId)
 
 bool NodeDB::isFavorite(uint32_t nodeId)
 {
+    // returns true if nodeId is_favorite; false if not or not found
+
+    // NODENUM_BROADCAST will never be in the DB
+    if (nodeId == NODENUM_BROADCAST)
+        return false;
+
     meshtastic_NodeInfoLite *lite = getMeshNode(nodeId);
+
     if (lite) {
         return lite->is_favorite;
     }
@@ -1781,7 +1788,45 @@ bool NodeDB::isFavorite(uint32_t nodeId)
 
 bool NodeDB::isFromOrToFavoritedNode(const meshtastic_MeshPacket &p)
 {
-    return isFavorite(p.from) || isFavorite(p.to);
+    // This method is logically equivalent to:
+    //   return isFavorite(p.from) || isFavorite(p.to);
+    // but is more efficient by:
+    //   1. doing only one pass through the database, instead of two
+    //   2. exiting early when a favorite is found, or if both from and to have been seen
+
+    if (p.to == NODENUM_BROADCAST)
+        return isFavorite(p.from); // we never store NODENUM_BROADCAST in the DB, so we only need to check p.from
+
+    meshtastic_NodeInfoLite *lite = NULL;
+
+    bool seenFrom = false;
+    bool seenTo = false;
+
+    for (int i = 0; i < numMeshNodes; i++) {
+        lite = &meshNodes->at(i);
+
+        if (lite->num == p.from) {
+            if (lite->is_favorite)
+                return true;
+
+            seenFrom = true;
+        }
+
+        if (lite->num == p.to) {
+            if (lite->is_favorite)
+                return true;
+
+            seenTo = true;
+        }
+
+        if (seenFrom && seenTo)
+            return false; // we've seen both, and neither is a favorite, so we can stop searching early
+
+        // Note: if we knew that sortMeshDB was always called after any change to is_favorite, we could exit early after searching
+        // all favorited nodes first.
+    }
+
+    return false;
 }
 
 void NodeDB::pause_sort(bool paused)

From 5a463373f22f1790f0e6405fea8728227a539ddf Mon Sep 17 00:00:00 2001
From: Mike Robbins 
Date: Sun, 7 Sep 2025 21:12:07 -0700
Subject: [PATCH 180/691] Remove changes to
 src/mesh/generated/meshtastic/config.pb.h from this PR

---
 src/mesh/generated/meshtastic/config.pb.h | 11 +++--------
 1 file changed, 3 insertions(+), 8 deletions(-)

diff --git a/src/mesh/generated/meshtastic/config.pb.h b/src/mesh/generated/meshtastic/config.pb.h
index 59e55db3f..67d461611 100644
--- a/src/mesh/generated/meshtastic/config.pb.h
+++ b/src/mesh/generated/meshtastic/config.pb.h
@@ -64,12 +64,7 @@ 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,
-    /* 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_ROUTER_LATE = 11
 } meshtastic_Config_DeviceConfig_Role;
 
 /* Defines the device's behavior for how messages are rebroadcast */
@@ -651,8 +646,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_CLIENT_BASE
-#define _meshtastic_Config_DeviceConfig_Role_ARRAYSIZE ((meshtastic_Config_DeviceConfig_Role)(meshtastic_Config_DeviceConfig_Role_CLIENT_BASE+1))
+#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_RebroadcastMode_MIN meshtastic_Config_DeviceConfig_RebroadcastMode_ALL
 #define _meshtastic_Config_DeviceConfig_RebroadcastMode_MAX meshtastic_Config_DeviceConfig_RebroadcastMode_CORE_PORTNUMS_ONLY

From 27cdd464d1f3e3b9b61c9921e7ca085c3a27d943 Mon Sep 17 00:00:00 2001
From: Mike Robbins 
Date: Sun, 7 Sep 2025 21:46:49 -0700
Subject: [PATCH 181/691] getTxDelayMsecWeighted and
 startTransmitTimerRebroadcast: extract p->rxSnr

---
 src/mesh/RadioInterface.cpp         | 3 ++-
 src/mesh/RadioInterface.h           | 2 +-
 src/mesh/RadioLibInterface.cpp      | 8 ++++----
 src/mesh/RadioLibInterface.h        | 2 +-
 src/platform/portduino/SimRadio.cpp | 6 +++---
 src/platform/portduino/SimRadio.h   | 2 +-
 6 files changed, 12 insertions(+), 11 deletions(-)

diff --git a/src/mesh/RadioInterface.cpp b/src/mesh/RadioInterface.cpp
index 1cbee6f47..31c68c302 100644
--- a/src/mesh/RadioInterface.cpp
+++ b/src/mesh/RadioInterface.cpp
@@ -332,10 +332,11 @@ bool RadioInterface::shouldRebroadcastEarlyLikeRouter(meshtastic_MeshPacket *p)
 }
 
 /** The delay to use when we want to flood a message */
-uint32_t RadioInterface::getTxDelayMsecWeighted(float snr, meshtastic_MeshPacket *p)
+uint32_t RadioInterface::getTxDelayMsecWeighted(meshtastic_MeshPacket *p)
 {
     //  high SNR = large CW size (Long Delay)
     //  low SNR = small CW size (Short Delay)
+    float snr = p->rx_snr;
     uint32_t delay = 0;
     uint8_t CWsize = getCWsize(snr);
     // LOG_DEBUG("rx_snr of %f so setting CWsize to:%d", snr, CWsize);
diff --git a/src/mesh/RadioInterface.h b/src/mesh/RadioInterface.h
index a89fa33dd..eff284747 100644
--- a/src/mesh/RadioInterface.h
+++ b/src/mesh/RadioInterface.h
@@ -184,7 +184,7 @@ class RadioInterface
     bool shouldRebroadcastEarlyLikeRouter(meshtastic_MeshPacket *p);
 
     /** The delay to use when we want to flood a message. Use a weighted scale based on SNR */
-    uint32_t getTxDelayMsecWeighted(float snr, meshtastic_MeshPacket *p);
+    uint32_t getTxDelayMsecWeighted(meshtastic_MeshPacket *p);
 
     /** If the packet is not already in the late rebroadcast window, move it there */
     virtual void clampToLateRebroadcastWindow(NodeNum from, PacketId id) { return; }
diff --git a/src/mesh/RadioLibInterface.cpp b/src/mesh/RadioLibInterface.cpp
index 3fcced2f5..19d0f794a 100644
--- a/src/mesh/RadioLibInterface.cpp
+++ b/src/mesh/RadioLibInterface.cpp
@@ -310,7 +310,7 @@ void RadioLibInterface::setTransmitDelay()
     // So we want to make sure the other side has had a chance to reconfigure its radio.
 
     if (p->tx_after) {
-        unsigned long add_delay = p->rx_rssi ? getTxDelayMsecWeighted(p->rx_snr, p) : getTxDelayMsec();
+        unsigned long add_delay = p->rx_rssi ? getTxDelayMsecWeighted(p) : getTxDelayMsec();
         unsigned long now = millis();
         p->tx_after = min(max(p->tx_after + add_delay, now + add_delay), now + 2 * getTxDelayMsecWeightedWorst(p->rx_snr));
         notifyLater(p->tx_after - now, TRANSMIT_DELAY_COMPLETED, false);
@@ -323,7 +323,7 @@ void RadioLibInterface::setTransmitDelay()
     } else {
         // If there is a SNR, start a timer scaled based on that SNR.
         LOG_DEBUG("rx_snr found. hop_limit:%d rx_snr:%f", p->hop_limit, p->rx_snr);
-        startTransmitTimerRebroadcast(p->rx_snr, p);
+        startTransmitTimerRebroadcast(p);
     }
 }
 
@@ -336,11 +336,11 @@ void RadioLibInterface::startTransmitTimer(bool withDelay)
     }
 }
 
-void RadioLibInterface::startTransmitTimerRebroadcast(float snr, meshtastic_MeshPacket *p)
+void RadioLibInterface::startTransmitTimerRebroadcast(meshtastic_MeshPacket *p)
 {
     // If we have work to do and the timer wasn't already scheduled, schedule it now
     if (!txQueue.empty()) {
-        uint32_t delay = getTxDelayMsecWeighted(snr, p);
+        uint32_t delay = getTxDelayMsecWeighted(p);
         notifyLater(delay, TRANSMIT_DELAY_COMPLETED, false); // This will implicitly enable
     }
 }
diff --git a/src/mesh/RadioLibInterface.h b/src/mesh/RadioLibInterface.h
index 224ac6376..9f497812f 100644
--- a/src/mesh/RadioLibInterface.h
+++ b/src/mesh/RadioLibInterface.h
@@ -161,7 +161,7 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified
      * timer scaled to SNR of to be flooded packet
      * @return Timestamp after which the packet may be sent
      */
-    void startTransmitTimerRebroadcast(float snr, meshtastic_MeshPacket *p);
+    void startTransmitTimerRebroadcast(meshtastic_MeshPacket *p);
 
     void handleTransmitInterrupt();
     void handleReceiveInterrupt();
diff --git a/src/platform/portduino/SimRadio.cpp b/src/platform/portduino/SimRadio.cpp
index 504383aee..cea1eab3a 100644
--- a/src/platform/portduino/SimRadio.cpp
+++ b/src/platform/portduino/SimRadio.cpp
@@ -43,7 +43,7 @@ void SimRadio::setTransmitDelay()
     } else {
         // If there is a SNR, start a timer scaled based on that SNR.
         LOG_DEBUG("rx_snr found. hop_limit:%d rx_snr:%f", p->hop_limit, p->rx_snr);
-        startTransmitTimerRebroadcast(p->rx_snr, p);
+        startTransmitTimerRebroadcast(p);
     }
 }
 
@@ -57,11 +57,11 @@ void SimRadio::startTransmitTimer(bool withDelay)
     }
 }
 
-void SimRadio::startTransmitTimerRebroadcast(float snr, meshtastic_MeshPacket *p)
+void SimRadio::startTransmitTimerRebroadcast(meshtastic_MeshPacket *p)
 {
     // If we have work to do and the timer wasn't already scheduled, schedule it now
     if (!txQueue.empty()) {
-        uint32_t delayMsec = getTxDelayMsecWeighted(snr, p);
+        uint32_t delayMsec = getTxDelayMsecWeighted(p);
         // LOG_DEBUG("xmit timer %d", delay);
         notifyLater(delayMsec, TRANSMIT_DELAY_COMPLETED, false);
     }
diff --git a/src/platform/portduino/SimRadio.h b/src/platform/portduino/SimRadio.h
index 86f07c7b3..d8b53739f 100644
--- a/src/platform/portduino/SimRadio.h
+++ b/src/platform/portduino/SimRadio.h
@@ -64,7 +64,7 @@ class SimRadio : public RadioInterface, protected concurrency::NotifiedWorkerThr
     void startTransmitTimer(bool withDelay = true);
 
     /** timer scaled to SNR of to be flooded packet */
-    void startTransmitTimerRebroadcast(float snr, meshtastic_MeshPacket *p);
+    void startTransmitTimerRebroadcast(meshtastic_MeshPacket *p);
 
     void handleTransmitInterrupt();
     void handleReceiveInterrupt();

From 4140ecfb4983a0530bee348a2638c20ceb07677f Mon Sep 17 00:00:00 2001
From: Mike Robbins 
Date: Mon, 8 Sep 2025 11:06:27 -0700
Subject: [PATCH 182/691] Bring src/mesh/generated/meshtastic/config.pb.h from
 develop after rebase

---
 src/mesh/generated/meshtastic/config.pb.h | 11 ++++++++---
 1 file changed, 8 insertions(+), 3 deletions(-)

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 527e88ca46107df1fb9325a40a52cbe3234ef89f Mon Sep 17 00:00:00 2001
From: Mike Robbins 
Date: Mon, 8 Sep 2025 11:22:56 -0700
Subject: [PATCH 183/691] Add CLIENT_BASE to DisplayFormatters::getDeviceRole

---
 src/DisplayFormatters.cpp | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/src/DisplayFormatters.cpp b/src/DisplayFormatters.cpp
index b2749806c..5193e1cb4 100644
--- a/src/DisplayFormatters.cpp
+++ b/src/DisplayFormatters.cpp
@@ -52,6 +52,9 @@ const char *DisplayFormatters::getDeviceRole(meshtastic_Config_DeviceConfig_Role
     case meshtastic_Config_DeviceConfig_Role_CLIENT_HIDDEN:
         return "Client Hidden";
         break;
+    case meshtastic_Config_DeviceConfig_Role_CLIENT_BASE:
+        return "Client Base";
+        break;
     case meshtastic_Config_DeviceConfig_Role_LOST_AND_FOUND:
         return "Lost and Found";
         break;

From 87eff2c4a96ec0bce7ed8f3f608adf68691686aa Mon Sep 17 00:00:00 2001
From: Mike Robbins 
Date: Mon, 8 Sep 2025 11:34:29 -0700
Subject: [PATCH 184/691] Fix logic in Screen::shouldWakeOnReceivedMessage and
 add CLIENT_HIDDEN and CLIENT_BASE to be treated the same as CLIENT and
 CLIENT_MUTE

---
 src/graphics/Screen.cpp | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp
index eb8093947..22840ccb2 100644
--- a/src/graphics/Screen.cpp
+++ b/src/graphics/Screen.cpp
@@ -1605,13 +1605,15 @@ bool shouldWakeOnReceivedMessage()
     /*
     The goal here is to determine when we do NOT wake up the screen on message received:
     - Any ext. notifications are turned on
-    - If role is not client / client_mute
+    - If role is not CLIENT / CLIENT_MUTE / CLIENT_HIDDEN / CLIENT_BASE
     - If the battery level is very low
     */
     if (moduleConfig.external_notification.enabled) {
         return false;
     }
-    if (!meshtastic_Config_DeviceConfig_Role_CLIENT && !meshtastic_Config_DeviceConfig_Role_CLIENT_MUTE) {
+    if (!IS_ONE_OF(config.device.role, meshtastic_Config_DeviceConfig_Role_CLIENT,
+                   meshtastic_Config_DeviceConfig_Role_CLIENT_MUTE, meshtastic_Config_DeviceConfig_Role_CLIENT_HIDDEN,
+                   meshtastic_Config_DeviceConfig_Role_CLIENT_BASE)) {
         return false;
     }
     if (powerStatus && powerStatus->getBatteryChargePercent() < 10) {

From 4ab125bbf74408dca8d7867d102e69eae52fecfb Mon Sep 17 00:00:00 2001
From: Mike Robbins 
Date: Mon, 8 Sep 2025 11:57:46 -0700
Subject: [PATCH 185/691] src/graphics/Screen.cpp: move #include "meshUtils.h"
 outside of "#ifdef HAS_SCREEN" so IS_ONE_OF works on all devices

---
 src/graphics/Screen.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp
index 22840ccb2..14ed91a1e 100644
--- a/src/graphics/Screen.cpp
+++ b/src/graphics/Screen.cpp
@@ -25,6 +25,7 @@ along with this program.  If not, see .
 #include "PowerMon.h"
 #include "Throttle.h"
 #include "configuration.h"
+#include "meshUtils.h"
 #if HAS_SCREEN
 #include 
 
@@ -58,7 +59,6 @@ along with this program.  If not, see .
 #include "mesh-pb-constants.h"
 #include "mesh/Channels.h"
 #include "mesh/generated/meshtastic/deviceonly.pb.h"
-#include "meshUtils.h"
 #include "modules/ExternalNotificationModule.h"
 #include "modules/TextMessageModule.h"
 #include "modules/WaypointModule.h"

From 35340fc6e23d71a048b029e0de6b3a9f4de78c9f Mon Sep 17 00:00:00 2001
From: Mike Robbins 
Date: Tue, 9 Sep 2025 23:17:48 -0700
Subject: [PATCH 186/691] NextHopRouter::roleAllowsCancelingFromTxQueue (same
 logic as FloodingRouter::roleAllowsCancelingDupe)

---
 src/mesh/NextHopRouter.cpp | 13 ++++++++++---
 src/mesh/NextHopRouter.h   |  3 +++
 2 files changed, 13 insertions(+), 3 deletions(-)

diff --git a/src/mesh/NextHopRouter.cpp b/src/mesh/NextHopRouter.cpp
index 7ceca2195..608e069e6 100644
--- a/src/mesh/NextHopRouter.cpp
+++ b/src/mesh/NextHopRouter.cpp
@@ -161,6 +161,15 @@ bool NextHopRouter::stopRetransmission(NodeNum from, PacketId id)
     return stopRetransmission(key);
 }
 
+bool NextHopRouter::roleAllowsCancelingFromTxQueue(const meshtastic_MeshPacket *p)
+{
+    // Return true if we're allowed to cancel a packet in the txQueue (so we may never transmit it even once)
+
+    // Return false for roles like ROUTER, REPEATER, ROUTER_LATE which should always transmit the packet at least once.
+
+    return roleAllowsCancelingDupe(p); // same logic as FloodingRouter::roleAllowsCancelingDupe
+}
+
 bool NextHopRouter::stopRetransmission(GlobalPacketId key)
 {
     auto old = findPendingPacket(key);
@@ -170,9 +179,7 @@ bool NextHopRouter::stopRetransmission(GlobalPacketId key)
           to avoid canceling a transmission if it was ACKed super fast via MQTT */
         if (old->numRetransmissions < NUM_RELIABLE_RETX - 1) {
             // We only cancel it if we are the original sender or if we're not a router(_late)/repeater
-            if (isFromUs(p) || (config.device.role != meshtastic_Config_DeviceConfig_Role_ROUTER &&
-                                config.device.role != meshtastic_Config_DeviceConfig_Role_REPEATER &&
-                                config.device.role != meshtastic_Config_DeviceConfig_Role_ROUTER_LATE)) {
+            if (isFromUs(p) || roleAllowsCancelingFromTxQueue(p)) {
                 // remove the 'original' (identified by originator and packet->id) from the txqueue and free it
                 cancelSending(getFrom(p), p->id);
                 // now free the pooled copy for retransmission too
diff --git a/src/mesh/NextHopRouter.h b/src/mesh/NextHopRouter.h
index 6c2764aff..0022644e9 100644
--- a/src/mesh/NextHopRouter.h
+++ b/src/mesh/NextHopRouter.h
@@ -121,6 +121,9 @@ class NextHopRouter : public FloodingRouter
      */
     PendingPacket *startRetransmission(meshtastic_MeshPacket *p, uint8_t numReTx = NUM_INTERMEDIATE_RETX);
 
+    // Return true if we're allowed to cancel a packet in the txQueue (so we may never transmit it even once)
+    bool roleAllowsCancelingFromTxQueue(const meshtastic_MeshPacket *p);
+
     /**
      * Stop any retransmissions we are doing of the specified node/packet ID pair
      *

From 5579d87845f0063af1e50daf8d81ebd231a3f0ce Mon Sep 17 00:00:00 2001
From: ford-jones 
Date: Fri, 12 Sep 2025 19:52:34 +1200
Subject: [PATCH 187/691] Disable on-screen 'new message' popup for muted nodes
 and channels

---
 src/graphics/Screen.cpp | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp
index eb8093947..e688b2ccc 100644
--- a/src/graphics/Screen.cpp
+++ b/src/graphics/Screen.cpp
@@ -1419,6 +1419,7 @@ int Screen::handleTextMessage(const meshtastic_MeshPacket *packet)
             }
             // === Prepare banner content ===
             const meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(packet->from);
+            const meshtastic_Channel channel = channels.getByIndex(node->channel ? node->channel : channels.getPrimaryIndex());
             const char *longName = (node && node->has_user) ? node->user.long_name : nullptr;
 
             const char *msgRaw = reinterpret_cast(packet->decoded.payload.bytes);
@@ -1440,15 +1441,14 @@ int Screen::handleTextMessage(const meshtastic_MeshPacket *packet)
                 } else {
                     strcpy(banner, "Alert Received");
                 }
-            } else {
+            } else if (!node->is_muted && !channel.settings.mute) {
                 if (longName && longName[0]) {
                     snprintf(banner, sizeof(banner), "New Message from\n%s", longName);
                 } else {
                     strcpy(banner, "New Message");
                 }
+                screen->showSimpleBanner(banner, 3000);
             }
-
-            screen->showSimpleBanner(banner, 3000);
         }
     }
 

From e0890b2a1328321d7728179d3e891d9f64f37ff5 Mon Sep 17 00:00:00 2001
From: ford-jones 
Date: Fri, 12 Sep 2025 23:01:42 +1200
Subject: [PATCH 188/691] Don't mute alerts

---
 src/graphics/Screen.cpp                    | 22 ++++++++++++++--------
 src/modules/ExternalNotificationModule.cpp |  8 ++++----
 2 files changed, 18 insertions(+), 12 deletions(-)

diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp
index e688b2ccc..9a82d572a 100644
--- a/src/graphics/Screen.cpp
+++ b/src/graphics/Screen.cpp
@@ -95,7 +95,7 @@ namespace graphics
 #define NUM_EXTRA_FRAMES 3 // text message and debug frame
 // if defined a pixel will blink to show redraws
 // #define SHOW_REDRAWS
-
+#define ASCII_BELL '\x07'
 // A text message frame + debug frame + all the node infos
 FrameCallback *normalFrames;
 static uint32_t targetFramerate = IDLE_FRAMERATE;
@@ -1426,21 +1426,27 @@ int Screen::handleTextMessage(const meshtastic_MeshPacket *packet)
 
             char banner[256];
 
-            // Check for bell character in message to determine alert type
             bool isAlert = false;
-            for (size_t i = 0; i < packet->decoded.payload.size && i < 100; i++) {
-                if (msgRaw[i] == '\x07') {
-                    isAlert = true;
-                    break;
-                }
-            }
 
+            if (moduleConfig.external_notification.alert_bell || moduleConfig.external_notification.alert_bell_vibra ||
+                moduleConfig.external_notification.alert_bell_buzzer)
+                // Check for bell character to determine if this message is an alert
+                for (size_t i = 0; i < packet->decoded.payload.size && i < 100; i++) {
+                    if (msgRaw[i] == ASCII_BELL) {
+                        isAlert = true;
+                        break;
+                    }
+                }
+
+            // Unlike generic messages, alerts (when enabled via the ext notif module) ignore any
+            // 'mute' preferences set to any specific node or channel.
             if (isAlert) {
                 if (longName && longName[0]) {
                     snprintf(banner, sizeof(banner), "Alert Received from\n%s", longName);
                 } else {
                     strcpy(banner, "Alert Received");
                 }
+                screen->showSimpleBanner(banner, 3000);
             } else if (!node->is_muted && !channel.settings.mute) {
                 if (longName && longName[0]) {
                     snprintf(banner, sizeof(banner), "New Message from\n%s", longName);
diff --git a/src/modules/ExternalNotificationModule.cpp b/src/modules/ExternalNotificationModule.cpp
index 4dd8811ec..92590f149 100644
--- a/src/modules/ExternalNotificationModule.cpp
+++ b/src/modules/ExternalNotificationModule.cpp
@@ -451,7 +451,7 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP
             // Check if the message contains a bell character. Don't do this loop for every pin, just once.
             auto &p = mp.decoded;
             bool containsBell = false;
-            for (int i = 0; i < p.payload.size; i++) {
+            for (size_t i = 0; i < p.payload.size; i++) {
                 if (p.payload.bytes[i] == ASCII_BELL) {
                     containsBell = true;
                 }
@@ -460,7 +460,7 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP
             meshtastic_NodeInfoLite *sender = nodeDB->getMeshNode(mp.from);
             meshtastic_Channel ch = channels.getByIndex(sender->channel ? sender->channel : channels.getPrimaryIndex());
 
-            if (moduleConfig.external_notification.alert_bell && !sender->is_muted && !ch.settings.mute) {
+            if (moduleConfig.external_notification.alert_bell) {
                 if (containsBell) {
                     LOG_INFO("externalNotificationModule - Notification Bell");
                     isNagging = true;
@@ -473,7 +473,7 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP
                 }
             }
 
-            if (moduleConfig.external_notification.alert_bell_vibra && !sender->is_muted && !ch.settings.mute) {
+            if (moduleConfig.external_notification.alert_bell_vibra) {
                 if (containsBell) {
                     LOG_INFO("externalNotificationModule - Notification Bell (Vibra)");
                     isNagging = true;
@@ -486,7 +486,7 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP
                 }
             }
 
-            if (moduleConfig.external_notification.alert_bell_buzzer && canBuzz() && !sender->is_muted && !ch.settings.mute) {
+            if (moduleConfig.external_notification.alert_bell_buzzer && canBuzz()) {
                 if (containsBell) {
                     LOG_INFO("externalNotificationModule - Notification Bell (Buzzer)");
                     isNagging = true;

From 0fc33c352a4b5541e7054064fee9dcddfcd24fb4 Mon Sep 17 00:00:00 2001
From: Mike Robbins 
Date: Fri, 12 Sep 2025 10:40:13 -0700
Subject: [PATCH 189/691] Fix memory leak in NextHopRouter: always free packet
 copy when removing from pending

---
 src/mesh/NextHopRouter.cpp | 10 ++++++++--
 1 file changed, 8 insertions(+), 2 deletions(-)

diff --git a/src/mesh/NextHopRouter.cpp b/src/mesh/NextHopRouter.cpp
index 608e069e6..9bb8b240c 100644
--- a/src/mesh/NextHopRouter.cpp
+++ b/src/mesh/NextHopRouter.cpp
@@ -182,12 +182,18 @@ bool NextHopRouter::stopRetransmission(GlobalPacketId key)
             if (isFromUs(p) || roleAllowsCancelingFromTxQueue(p)) {
                 // remove the 'original' (identified by originator and packet->id) from the txqueue and free it
                 cancelSending(getFrom(p), p->id);
-                // now free the pooled copy for retransmission too
-                packetPool.release(p);
             }
         }
+
+        // Regardless of whether or not we canceled this packet from the txQueue, remove it from our pending list so it doesn't
+        // get scheduled again. (This is the core of stopRetransmission.)
         auto numErased = pending.erase(key);
         assert(numErased == 1);
+
+        // When we remove an entry from pending, always be sure to release the copy of the packet that was allocated in the call
+        // to startRetransmission.
+        packetPool.release(p);
+
         return true;
     } else
         return false;

From 962e5d513cb4dda47518ae76034e703245693367 Mon Sep 17 00:00:00 2001
From: Mike Robbins 
Date: Fri, 12 Sep 2025 10:40:13 -0700
Subject: [PATCH 190/691] Fix memory leak in NextHopRouter: always free packet
 copy when removing from pending

---
 src/mesh/NextHopRouter.cpp | 10 ++++++++--
 1 file changed, 8 insertions(+), 2 deletions(-)

diff --git a/src/mesh/NextHopRouter.cpp b/src/mesh/NextHopRouter.cpp
index 7ceca2195..db3d62038 100644
--- a/src/mesh/NextHopRouter.cpp
+++ b/src/mesh/NextHopRouter.cpp
@@ -175,12 +175,18 @@ bool NextHopRouter::stopRetransmission(GlobalPacketId key)
                                 config.device.role != meshtastic_Config_DeviceConfig_Role_ROUTER_LATE)) {
                 // remove the 'original' (identified by originator and packet->id) from the txqueue and free it
                 cancelSending(getFrom(p), p->id);
-                // now free the pooled copy for retransmission too
-                packetPool.release(p);
             }
         }
+
+        // Regardless of whether or not we canceled this packet from the txQueue, remove it from our pending list so it doesn't
+        // get scheduled again. (This is the core of stopRetransmission.)
         auto numErased = pending.erase(key);
         assert(numErased == 1);
+
+        // When we remove an entry from pending, always be sure to release the copy of the packet that was allocated in the call
+        // to startRetransmission.
+        packetPool.release(p);
+
         return true;
     } else
         return false;

From 43cf12edfbe39304f2fb8c7150d464d53a49cabf Mon Sep 17 00:00:00 2001
From: Mike Robbins 
Date: Fri, 12 Sep 2025 13:00:17 -0700
Subject: [PATCH 191/691] Fix memory leak in NimbleBluetooth: allocate
 BluetoothStatus on stack, not heap

---
 src/nimble/NimbleBluetooth.cpp | 10 ++++++----
 1 file changed, 6 insertions(+), 4 deletions(-)

diff --git a/src/nimble/NimbleBluetooth.cpp b/src/nimble/NimbleBluetooth.cpp
index 95e191c8e..ee95168c3 100644
--- a/src/nimble/NimbleBluetooth.cpp
+++ b/src/nimble/NimbleBluetooth.cpp
@@ -133,7 +133,8 @@ class NimbleBluetoothServerCallback : public NimBLEServerCallbacks
         LOG_INFO("*** Enter passkey %d on the peer side ***", passkey);
 
         powerFSM.trigger(EVENT_BLUETOOTH_PAIR);
-        bluetoothStatus->updateStatus(new meshtastic::BluetoothStatus(std::to_string(passkey)));
+        meshtastic::BluetoothStatus newStatus(std::to_string(passkey));
+        bluetoothStatus->updateStatus(&newStatus);
 
 #if HAS_SCREEN // Todo: migrate this display code back into Screen class, and observe bluetoothStatus
         if (screen) {
@@ -173,7 +174,8 @@ class NimbleBluetoothServerCallback : public NimBLEServerCallbacks
     {
         LOG_INFO("BLE authentication complete");
 
-        bluetoothStatus->updateStatus(new meshtastic::BluetoothStatus(meshtastic::BluetoothStatus::ConnectionState::CONNECTED));
+        meshtastic::BluetoothStatus newStatus(meshtastic::BluetoothStatus::ConnectionState::CONNECTED);
+        bluetoothStatus->updateStatus(&newStatus);
 
         // Todo: migrate this display code back into Screen class, and observe bluetoothStatus
         if (passkeyShowing) {
@@ -187,8 +189,8 @@ class NimbleBluetoothServerCallback : public NimBLEServerCallbacks
     {
         LOG_INFO("BLE disconnect");
 
-        bluetoothStatus->updateStatus(
-            new meshtastic::BluetoothStatus(meshtastic::BluetoothStatus::ConnectionState::DISCONNECTED));
+        meshtastic::BluetoothStatus newStatus(meshtastic::BluetoothStatus::ConnectionState::DISCONNECTED);
+        bluetoothStatus->updateStatus(&newStatus);
 
         if (bluetoothPhoneAPI) {
             std::lock_guard guard(bluetoothPhoneAPI->nimble_mutex);

From ead67446a3ff154e4d85058b0d4b680729197ca7 Mon Sep 17 00:00:00 2001
From: Mike Robbins 
Date: Fri, 12 Sep 2025 13:15:52 -0700
Subject: [PATCH 192/691] Fix memory leak in NRF52Bluetooth: allocate
 BluetoothStatus on stack, not heap

---
 src/platform/nrf52/NRF52Bluetooth.cpp | 16 ++++++++++------
 1 file changed, 10 insertions(+), 6 deletions(-)

diff --git a/src/platform/nrf52/NRF52Bluetooth.cpp b/src/platform/nrf52/NRF52Bluetooth.cpp
index 6f0e7250f..f8366ae32 100644
--- a/src/platform/nrf52/NRF52Bluetooth.cpp
+++ b/src/platform/nrf52/NRF52Bluetooth.cpp
@@ -59,7 +59,8 @@ void onConnect(uint16_t conn_handle)
     LOG_INFO("BLE Connected to %s", central_name);
 
     // Notify UI (or any other interested firmware components)
-    bluetoothStatus->updateStatus(new meshtastic::BluetoothStatus(meshtastic::BluetoothStatus::ConnectionState::CONNECTED));
+    meshtastic::BluetoothStatus newStatus(meshtastic::BluetoothStatus::ConnectionState::CONNECTED);
+    bluetoothStatus->updateStatus(&newStatus);
 }
 /**
  * Callback invoked when a connection is dropped
@@ -74,7 +75,8 @@ void onDisconnect(uint16_t conn_handle, uint8_t reason)
     }
 
     // Notify UI (or any other interested firmware components)
-    bluetoothStatus->updateStatus(new meshtastic::BluetoothStatus(meshtastic::BluetoothStatus::ConnectionState::DISCONNECTED));
+    meshtastic::BluetoothStatus newStatus(meshtastic::BluetoothStatus::ConnectionState::DISCONNECTED);
+    bluetoothStatus->updateStatus(&newStatus);
 }
 void onCccd(uint16_t conn_hdl, BLECharacteristic *chr, uint16_t cccd_value)
 {
@@ -326,7 +328,8 @@ bool NRF52Bluetooth::onPairingPasskey(uint16_t conn_handle, uint8_t const passke
         textkey += (char)passkey[i];
 
     // Notify UI (or other components) of pairing event and passkey
-    bluetoothStatus->updateStatus(new meshtastic::BluetoothStatus(textkey));
+    meshtastic::BluetoothStatus newStatus(textkey);
+    bluetoothStatus->updateStatus(&newStatus);
 
 #if !defined(MESHTASTIC_EXCLUDE_SCREEN) // Todo: migrate this display code back into Screen class, and observe bluetoothStatus
     if (screen) {
@@ -398,12 +401,13 @@ void NRF52Bluetooth::onPairingCompleted(uint16_t conn_handle, uint8_t auth_statu
 {
     if (auth_status == BLE_GAP_SEC_STATUS_SUCCESS) {
         LOG_INFO("BLE pair success");
-        bluetoothStatus->updateStatus(new meshtastic::BluetoothStatus(meshtastic::BluetoothStatus::ConnectionState::CONNECTED));
+        meshtastic::BluetoothStatus newConnectedStatus(meshtastic::BluetoothStatus::ConnectionState::CONNECTED);
+        bluetoothStatus->updateStatus(&newConnectedStatus);
     } else {
         LOG_INFO("BLE pair failed");
         // Notify UI (or any other interested firmware components)
-        bluetoothStatus->updateStatus(
-            new meshtastic::BluetoothStatus(meshtastic::BluetoothStatus::ConnectionState::DISCONNECTED));
+        meshtastic::BluetoothStatus newDisconnectedStatus(meshtastic::BluetoothStatus::ConnectionState::DISCONNECTED);
+        bluetoothStatus->updateStatus(&newDisconnectedStatus);
     }
 
     // Todo: migrate this display code back into Screen class, and observe bluetoothStatus

From 1914fa0321ebbe801dbae04548f4d1bbcb256d5f Mon Sep 17 00:00:00 2001
From: Ben Meadors 
Date: Fri, 12 Sep 2025 15:49:56 -0500
Subject: [PATCH 193/691] Formatting

---
 src/mesh/MemoryPool.h | 76 +++++++++++++++++++++++++++++++++++++++----
 1 file changed, 69 insertions(+), 7 deletions(-)

diff --git a/src/mesh/MemoryPool.h b/src/mesh/MemoryPool.h
index ea7c8f583..0c5ba6c71 100644
--- a/src/mesh/MemoryPool.h
+++ b/src/mesh/MemoryPool.h
@@ -6,6 +6,7 @@
 #include 
 
 #include "PointerQueue.h"
+#include "configuration.h" // For LOG_WARN, LOG_DEBUG, LOG_HEAP
 
 template  class Allocator
 {
@@ -14,13 +15,14 @@ template  class Allocator
     Allocator() : deleter([this](T *p) { this->release(p); }) {}
     virtual ~Allocator() {}
 
-    /// Return a queable object which has been prefilled with zeros.  Panic if no buffer is available
+    /// Return a queable object which has been prefilled with zeros.  Return nullptr if no buffer is available
     /// Note: this method is safe to call from regular OR ISR code
     T *allocZeroed()
     {
         T *p = allocZeroed(0);
-
-        assert(p); // FIXME panic instead
+        if (!p) {
+            LOG_WARN("Failed to allocate zeroed memory");
+        }
         return p;
     }
 
@@ -39,10 +41,12 @@ template  class Allocator
     T *allocCopy(const T &src, TickType_t maxWait = portMAX_DELAY)
     {
         T *p = alloc(maxWait);
-        assert(p);
+        if (!p) {
+            LOG_WARN("Failed to allocate memory for copy");
+            return nullptr;
+        }
 
-        if (p)
-            *p = src;
+        *p = src;
         return p;
     }
 
@@ -83,7 +87,9 @@ template  class MemoryDynamic : public Allocator
     /// Return a buffer for use by others
     virtual void release(T *p) override
     {
-        assert(p);
+        if (p == nullptr)
+            return;
+
         LOG_HEAP("Freeing 0x%x", p);
 
         free(p);
@@ -98,3 +104,59 @@ template  class MemoryDynamic : public Allocator
         return p;
     }
 };
+
+/**
+ * A static memory pool that uses a fixed buffer instead of heap allocation
+ */
+template  class MemoryPool : public Allocator
+{
+  private:
+    T pool[MaxSize];
+    bool used[MaxSize];
+
+  public:
+    MemoryPool()
+    {
+        // Initialize the used array to false (all slots free)
+        for (int i = 0; i < MaxSize; i++) {
+            used[i] = false;
+        }
+    }
+
+    /// Return a buffer for use by others
+    virtual void release(T *p) override
+    {
+        if (!p) {
+            LOG_DEBUG("Failed to release memory, pointer is null");
+            return;
+        }
+
+        // Find the index of this pointer in our pool
+        int index = p - pool;
+        if (index >= 0 && index < MaxSize) {
+            assert(used[index]); // Should be marked as used
+            used[index] = false;
+            LOG_HEAP("Released static pool item %d at 0x%x", index, p);
+        } else {
+            LOG_WARN("Pointer 0x%x not from our pool!", p);
+        }
+    }
+
+  protected:
+    // Alloc some storage from our static pool
+    virtual T *alloc(TickType_t maxWait) override
+    {
+        // Find first free slot
+        for (int i = 0; i < MaxSize; i++) {
+            if (!used[i]) {
+                used[i] = true;
+                LOG_HEAP("Allocated static pool item %d at 0x%x", i, &pool[i]);
+                return &pool[i];
+            }
+        }
+
+        // No free slots available - return nullptr instead of asserting
+        LOG_WARN("No free slots available in static memory pool!");
+        return nullptr;
+    }
+};

From 8989de118cd61f1aac75b27a28b4db8c90ea895c Mon Sep 17 00:00:00 2001
From: Ben Meadors 
Date: Fri, 12 Sep 2025 16:07:27 -0500
Subject: [PATCH 194/691] Only queue 2 client notification

---
 src/mesh/mesh-pb-constants.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/mesh/mesh-pb-constants.h b/src/mesh/mesh-pb-constants.h
index 224f45de2..12aec98cd 100644
--- a/src/mesh/mesh-pb-constants.h
+++ b/src/mesh/mesh-pb-constants.h
@@ -30,7 +30,7 @@
 
 /// max number of ClientNotification packets which can be waiting for delivery to phone
 #ifndef MAX_RX_NOTIFICATION_TOPHONE
-#define MAX_RX_NOTIFICATION_TOPHONE 4
+#define MAX_RX_NOTIFICATION_TOPHONE 2
 #endif
 
 /// Verify baseline assumption of node size. If it increases, we need to reevaluate

From e49b07ac8ca1e71936f44c0a16f46ca90568b8d8 Mon Sep 17 00:00:00 2001
From: Ben Meadors 
Date: Fri, 12 Sep 2025 17:12:18 -0500
Subject: [PATCH 195/691] Merge pull request #7965 from
 compumike/compumike/fix-nrf52-bluetooth-memory-leak

Fix memory leak in `NRF52Bluetooth`: allocate `BluetoothStatus` on stack, not heap
---
 src/platform/nrf52/NRF52Bluetooth.cpp | 16 ++++++++++------
 1 file changed, 10 insertions(+), 6 deletions(-)

diff --git a/src/platform/nrf52/NRF52Bluetooth.cpp b/src/platform/nrf52/NRF52Bluetooth.cpp
index 6f0e7250f..f8366ae32 100644
--- a/src/platform/nrf52/NRF52Bluetooth.cpp
+++ b/src/platform/nrf52/NRF52Bluetooth.cpp
@@ -59,7 +59,8 @@ void onConnect(uint16_t conn_handle)
     LOG_INFO("BLE Connected to %s", central_name);
 
     // Notify UI (or any other interested firmware components)
-    bluetoothStatus->updateStatus(new meshtastic::BluetoothStatus(meshtastic::BluetoothStatus::ConnectionState::CONNECTED));
+    meshtastic::BluetoothStatus newStatus(meshtastic::BluetoothStatus::ConnectionState::CONNECTED);
+    bluetoothStatus->updateStatus(&newStatus);
 }
 /**
  * Callback invoked when a connection is dropped
@@ -74,7 +75,8 @@ void onDisconnect(uint16_t conn_handle, uint8_t reason)
     }
 
     // Notify UI (or any other interested firmware components)
-    bluetoothStatus->updateStatus(new meshtastic::BluetoothStatus(meshtastic::BluetoothStatus::ConnectionState::DISCONNECTED));
+    meshtastic::BluetoothStatus newStatus(meshtastic::BluetoothStatus::ConnectionState::DISCONNECTED);
+    bluetoothStatus->updateStatus(&newStatus);
 }
 void onCccd(uint16_t conn_hdl, BLECharacteristic *chr, uint16_t cccd_value)
 {
@@ -326,7 +328,8 @@ bool NRF52Bluetooth::onPairingPasskey(uint16_t conn_handle, uint8_t const passke
         textkey += (char)passkey[i];
 
     // Notify UI (or other components) of pairing event and passkey
-    bluetoothStatus->updateStatus(new meshtastic::BluetoothStatus(textkey));
+    meshtastic::BluetoothStatus newStatus(textkey);
+    bluetoothStatus->updateStatus(&newStatus);
 
 #if !defined(MESHTASTIC_EXCLUDE_SCREEN) // Todo: migrate this display code back into Screen class, and observe bluetoothStatus
     if (screen) {
@@ -398,12 +401,13 @@ void NRF52Bluetooth::onPairingCompleted(uint16_t conn_handle, uint8_t auth_statu
 {
     if (auth_status == BLE_GAP_SEC_STATUS_SUCCESS) {
         LOG_INFO("BLE pair success");
-        bluetoothStatus->updateStatus(new meshtastic::BluetoothStatus(meshtastic::BluetoothStatus::ConnectionState::CONNECTED));
+        meshtastic::BluetoothStatus newConnectedStatus(meshtastic::BluetoothStatus::ConnectionState::CONNECTED);
+        bluetoothStatus->updateStatus(&newConnectedStatus);
     } else {
         LOG_INFO("BLE pair failed");
         // Notify UI (or any other interested firmware components)
-        bluetoothStatus->updateStatus(
-            new meshtastic::BluetoothStatus(meshtastic::BluetoothStatus::ConnectionState::DISCONNECTED));
+        meshtastic::BluetoothStatus newDisconnectedStatus(meshtastic::BluetoothStatus::ConnectionState::DISCONNECTED);
+        bluetoothStatus->updateStatus(&newDisconnectedStatus);
     }
 
     // Todo: migrate this display code back into Screen class, and observe bluetoothStatus

From d00b2afe1d5a7ffb9fc571fc60efefcd872bc349 Mon Sep 17 00:00:00 2001
From: Ben Meadors 
Date: Fri, 12 Sep 2025 17:12:27 -0500
Subject: [PATCH 196/691] Merge pull request #7964 from
 compumike/compumike/fix-nimble-bluetooth-memory-leak

Fix memory leak in `NimbleBluetooth`: allocate `BluetoothStatus` on stack, not heap
---
 src/nimble/NimbleBluetooth.cpp | 10 ++++++----
 1 file changed, 6 insertions(+), 4 deletions(-)

diff --git a/src/nimble/NimbleBluetooth.cpp b/src/nimble/NimbleBluetooth.cpp
index 95e191c8e..ee95168c3 100644
--- a/src/nimble/NimbleBluetooth.cpp
+++ b/src/nimble/NimbleBluetooth.cpp
@@ -133,7 +133,8 @@ class NimbleBluetoothServerCallback : public NimBLEServerCallbacks
         LOG_INFO("*** Enter passkey %d on the peer side ***", passkey);
 
         powerFSM.trigger(EVENT_BLUETOOTH_PAIR);
-        bluetoothStatus->updateStatus(new meshtastic::BluetoothStatus(std::to_string(passkey)));
+        meshtastic::BluetoothStatus newStatus(std::to_string(passkey));
+        bluetoothStatus->updateStatus(&newStatus);
 
 #if HAS_SCREEN // Todo: migrate this display code back into Screen class, and observe bluetoothStatus
         if (screen) {
@@ -173,7 +174,8 @@ class NimbleBluetoothServerCallback : public NimBLEServerCallbacks
     {
         LOG_INFO("BLE authentication complete");
 
-        bluetoothStatus->updateStatus(new meshtastic::BluetoothStatus(meshtastic::BluetoothStatus::ConnectionState::CONNECTED));
+        meshtastic::BluetoothStatus newStatus(meshtastic::BluetoothStatus::ConnectionState::CONNECTED);
+        bluetoothStatus->updateStatus(&newStatus);
 
         // Todo: migrate this display code back into Screen class, and observe bluetoothStatus
         if (passkeyShowing) {
@@ -187,8 +189,8 @@ class NimbleBluetoothServerCallback : public NimBLEServerCallbacks
     {
         LOG_INFO("BLE disconnect");
 
-        bluetoothStatus->updateStatus(
-            new meshtastic::BluetoothStatus(meshtastic::BluetoothStatus::ConnectionState::DISCONNECTED));
+        meshtastic::BluetoothStatus newStatus(meshtastic::BluetoothStatus::ConnectionState::DISCONNECTED);
+        bluetoothStatus->updateStatus(&newStatus);
 
         if (bluetoothPhoneAPI) {
             std::lock_guard guard(bluetoothPhoneAPI->nimble_mutex);

From ccff2769fedac1d43056c41ef29721946ee1bf5d Mon Sep 17 00:00:00 2001
From: ford-jones 
Date: Sat, 13 Sep 2025 13:39:32 +1200
Subject: [PATCH 197/691] Make use of pre-existing
 channel_settings.module_settings.is_client_muted setting

---
 src/graphics/Screen.cpp                       |  2 +-
 src/mesh/Channels.cpp                         |  2 +-
 src/mesh/generated/meshtastic/apponly.pb.h    |  2 +-
 src/mesh/generated/meshtastic/channel.pb.h    | 14 +++++---------
 src/mesh/generated/meshtastic/deviceonly.pb.h |  4 ++--
 src/modules/ExternalNotificationModule.cpp    |  9 ++++++---
 6 files changed, 16 insertions(+), 17 deletions(-)

diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp
index 9a82d572a..5150a19ee 100644
--- a/src/graphics/Screen.cpp
+++ b/src/graphics/Screen.cpp
@@ -1447,7 +1447,7 @@ int Screen::handleTextMessage(const meshtastic_MeshPacket *packet)
                     strcpy(banner, "Alert Received");
                 }
                 screen->showSimpleBanner(banner, 3000);
-            } else if (!node->is_muted && !channel.settings.mute) {
+            } else if (!node->is_muted && !channel.settings.module_settings.is_client_muted) {
                 if (longName && longName[0]) {
                     snprintf(banner, sizeof(banner), "New Message from\n%s", longName);
                 } else {
diff --git a/src/mesh/Channels.cpp b/src/mesh/Channels.cpp
index affe05285..2eaafe39c 100644
--- a/src/mesh/Channels.cpp
+++ b/src/mesh/Channels.cpp
@@ -347,7 +347,7 @@ void Channels::setMute(ChannelIndex chIndex)
 {
     if (chIndex < channelFile.channels_count) {
         meshtastic_Channel *ch = channelFile.channels + chIndex;
-        ch->settings.mute = !ch->settings.mute;
+        ch->settings.module_settings.is_client_muted = !ch->settings.module_settings.is_client_muted;
     } else {
         LOG_ERROR("Failed to mute. Invalid channel index %d > %d", chIndex, channelFile.channels_count);
     };
diff --git a/src/mesh/generated/meshtastic/apponly.pb.h b/src/mesh/generated/meshtastic/apponly.pb.h
index db9dedaaf..f4c33bd79 100644
--- a/src/mesh/generated/meshtastic/apponly.pb.h
+++ b/src/mesh/generated/meshtastic/apponly.pb.h
@@ -55,7 +55,7 @@ extern const pb_msgdesc_t meshtastic_ChannelSet_msg;
 
 /* Maximum encoded size of messages (where known) */
 #define MESHTASTIC_MESHTASTIC_APPONLY_PB_H_MAX_SIZE meshtastic_ChannelSet_size
-#define meshtastic_ChannelSet_size               695
+#define meshtastic_ChannelSet_size               679
 
 #ifdef __cplusplus
 } /* extern "C" */
diff --git a/src/mesh/generated/meshtastic/channel.pb.h b/src/mesh/generated/meshtastic/channel.pb.h
index 594d15929..ca4310bf1 100644
--- a/src/mesh/generated/meshtastic/channel.pb.h
+++ b/src/mesh/generated/meshtastic/channel.pb.h
@@ -97,8 +97,6 @@ typedef struct _meshtastic_ChannelSettings {
     /* Per-channel module settings. */
     bool has_module_settings;
     meshtastic_ModuleSettings module_settings;
-    /* Whether or not we should receive notifactions / alerts from this channel */
-    bool mute;
 } meshtastic_ChannelSettings;
 
 /* A pair of a channel number, mode and the (sharable) settings for that channel */
@@ -130,10 +128,10 @@ extern "C" {
 
 
 /* Initializer values for message structs */
-#define meshtastic_ChannelSettings_init_default  {0, {0, {0}}, "", 0, 0, 0, false, meshtastic_ModuleSettings_init_default, 0}
+#define meshtastic_ChannelSettings_init_default  {0, {0, {0}}, "", 0, 0, 0, false, meshtastic_ModuleSettings_init_default}
 #define meshtastic_ModuleSettings_init_default   {0, 0}
 #define meshtastic_Channel_init_default          {0, false, meshtastic_ChannelSettings_init_default, _meshtastic_Channel_Role_MIN}
-#define meshtastic_ChannelSettings_init_zero     {0, {0, {0}}, "", 0, 0, 0, false, meshtastic_ModuleSettings_init_zero, 0}
+#define meshtastic_ChannelSettings_init_zero     {0, {0, {0}}, "", 0, 0, 0, false, meshtastic_ModuleSettings_init_zero}
 #define meshtastic_ModuleSettings_init_zero      {0, 0}
 #define meshtastic_Channel_init_zero             {0, false, meshtastic_ChannelSettings_init_zero, _meshtastic_Channel_Role_MIN}
 
@@ -147,7 +145,6 @@ extern "C" {
 #define meshtastic_ChannelSettings_uplink_enabled_tag 5
 #define meshtastic_ChannelSettings_downlink_enabled_tag 6
 #define meshtastic_ChannelSettings_module_settings_tag 7
-#define meshtastic_ChannelSettings_mute_tag      8
 #define meshtastic_Channel_index_tag             1
 #define meshtastic_Channel_settings_tag          2
 #define meshtastic_Channel_role_tag              3
@@ -160,8 +157,7 @@ X(a, STATIC,   SINGULAR, STRING,   name,              3) \
 X(a, STATIC,   SINGULAR, FIXED32,  id,                4) \
 X(a, STATIC,   SINGULAR, BOOL,     uplink_enabled,    5) \
 X(a, STATIC,   SINGULAR, BOOL,     downlink_enabled,   6) \
-X(a, STATIC,   OPTIONAL, MESSAGE,  module_settings,   7) \
-X(a, STATIC,   SINGULAR, BOOL,     mute,              8)
+X(a, STATIC,   OPTIONAL, MESSAGE,  module_settings,   7)
 #define meshtastic_ChannelSettings_CALLBACK NULL
 #define meshtastic_ChannelSettings_DEFAULT NULL
 #define meshtastic_ChannelSettings_module_settings_MSGTYPE meshtastic_ModuleSettings
@@ -191,8 +187,8 @@ extern const pb_msgdesc_t meshtastic_Channel_msg;
 
 /* Maximum encoded size of messages (where known) */
 #define MESHTASTIC_MESHTASTIC_CHANNEL_PB_H_MAX_SIZE meshtastic_Channel_size
-#define meshtastic_ChannelSettings_size          74
-#define meshtastic_Channel_size                  89
+#define meshtastic_ChannelSettings_size          72
+#define meshtastic_Channel_size                  87
 #define meshtastic_ModuleSettings_size           8
 
 #ifdef __cplusplus
diff --git a/src/mesh/generated/meshtastic/deviceonly.pb.h b/src/mesh/generated/meshtastic/deviceonly.pb.h
index 55c433699..4f6e6a106 100644
--- a/src/mesh/generated/meshtastic/deviceonly.pb.h
+++ b/src/mesh/generated/meshtastic/deviceonly.pb.h
@@ -365,8 +365,8 @@ 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        2287
-#define meshtastic_ChannelFile_size              734
+#define meshtastic_BackupPreferences_size        2271
+#define meshtastic_ChannelFile_size              718
 #define meshtastic_DeviceState_size              1737
 #define meshtastic_NodeInfoLite_size             198
 #define meshtastic_PositionLite_size             28
diff --git a/src/modules/ExternalNotificationModule.cpp b/src/modules/ExternalNotificationModule.cpp
index 92590f149..28e451f49 100644
--- a/src/modules/ExternalNotificationModule.cpp
+++ b/src/modules/ExternalNotificationModule.cpp
@@ -510,7 +510,8 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP
                 }
             }
 
-            if (moduleConfig.external_notification.alert_message && !sender->is_muted && !ch.settings.mute) {
+            if (moduleConfig.external_notification.alert_message && !sender->is_muted &&
+                !ch.settings.module_settings.is_client_muted) {
                 LOG_INFO("externalNotificationModule - Notification Module");
                 isNagging = true;
                 setExternalState(0, true);
@@ -521,7 +522,8 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP
                 }
             }
 
-            if (moduleConfig.external_notification.alert_message_vibra && !sender->is_muted && !ch.settings.mute) {
+            if (moduleConfig.external_notification.alert_message_vibra && !sender->is_muted &&
+                !ch.settings.module_settings.is_client_muted) {
                 LOG_INFO("externalNotificationModule - Notification Module (Vibra)");
                 isNagging = true;
                 setExternalState(1, true);
@@ -532,7 +534,8 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP
                 }
             }
 
-            if (moduleConfig.external_notification.alert_message_buzzer && !sender->is_muted && !ch.settings.mute) {
+            if (moduleConfig.external_notification.alert_message_buzzer && !sender->is_muted &&
+                !ch.settings.module_settings.is_client_muted) {
                 LOG_INFO("externalNotificationModule - Notification Module (Buzzer)");
                 isNagging = true;
                 if (!moduleConfig.external_notification.use_pwm && !moduleConfig.external_notification.use_i2s_as_buzzer) {

From f8d44f8f6cd79c9c443cebd72bdd9f4be1b2c9e0 Mon Sep 17 00:00:00 2001
From: ford-jones 
Date: Sat, 13 Sep 2025 17:45:07 +1200
Subject: [PATCH 198/691] Revert previous commit - this needs it's own proto

---
 src/graphics/Screen.cpp                       |  2 +-
 src/mesh/Channels.cpp                         |  2 +-
 src/mesh/generated/meshtastic/admin.pb.h      |  8 -------
 src/mesh/generated/meshtastic/apponly.pb.h    |  2 +-
 src/mesh/generated/meshtastic/channel.pb.h    | 16 +++++++++----
 src/mesh/generated/meshtastic/deviceonly.pb.h | 23 ++++++++-----------
 src/modules/ExternalNotificationModule.cpp    |  9 +++-----
 7 files changed, 26 insertions(+), 36 deletions(-)

diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp
index 5150a19ee..9a82d572a 100644
--- a/src/graphics/Screen.cpp
+++ b/src/graphics/Screen.cpp
@@ -1447,7 +1447,7 @@ int Screen::handleTextMessage(const meshtastic_MeshPacket *packet)
                     strcpy(banner, "Alert Received");
                 }
                 screen->showSimpleBanner(banner, 3000);
-            } else if (!node->is_muted && !channel.settings.module_settings.is_client_muted) {
+            } else if (!node->is_muted && !channel.settings.mute) {
                 if (longName && longName[0]) {
                     snprintf(banner, sizeof(banner), "New Message from\n%s", longName);
                 } else {
diff --git a/src/mesh/Channels.cpp b/src/mesh/Channels.cpp
index 2eaafe39c..affe05285 100644
--- a/src/mesh/Channels.cpp
+++ b/src/mesh/Channels.cpp
@@ -347,7 +347,7 @@ void Channels::setMute(ChannelIndex chIndex)
 {
     if (chIndex < channelFile.channels_count) {
         meshtastic_Channel *ch = channelFile.channels + chIndex;
-        ch->settings.module_settings.is_client_muted = !ch->settings.module_settings.is_client_muted;
+        ch->settings.mute = !ch->settings.mute;
     } else {
         LOG_ERROR("Failed to mute. Invalid channel index %d > %d", chIndex, channelFile.channels_count);
     };
diff --git a/src/mesh/generated/meshtastic/admin.pb.h b/src/mesh/generated/meshtastic/admin.pb.h
index 616b7e8ee..bc0b780b9 100644
--- a/src/mesh/generated/meshtastic/admin.pb.h
+++ b/src/mesh/generated/meshtastic/admin.pb.h
@@ -247,10 +247,6 @@ typedef struct _meshtastic_AdminMessage {
         uint32_t set_ignored_node;
         /* Set specified node-num to be un-ignored on the NodeDB on the device */
         uint32_t remove_ignored_node;
-        /* Set specified node-num to be muted */
-        uint32_t set_muted_node;
-        /* Set specified node-num to be heard / not-muted */
-        uint32_t remove_muted_node;
         /* Begins an edit transaction for config, module config, owner, and channel settings changes
      This will delay the standard *implicit* save to the file system and subsequent reboot behavior until committed (commit_edit_settings) */
         bool begin_edit_settings;
@@ -392,8 +388,6 @@ extern "C" {
 #define meshtastic_AdminMessage_store_ui_config_tag 46
 #define meshtastic_AdminMessage_set_ignored_node_tag 47
 #define meshtastic_AdminMessage_remove_ignored_node_tag 48
-#define meshtastic_AdminMessage_set_muted_node_tag 49
-#define meshtastic_AdminMessage_remove_muted_node_tag 50
 #define meshtastic_AdminMessage_begin_edit_settings_tag 64
 #define meshtastic_AdminMessage_commit_edit_settings_tag 65
 #define meshtastic_AdminMessage_add_contact_tag  66
@@ -452,8 +446,6 @@ X(a, STATIC,   ONEOF,    MESSAGE,  (payload_variant,get_ui_config_response,get_u
 X(a, STATIC,   ONEOF,    MESSAGE,  (payload_variant,store_ui_config,store_ui_config),  46) \
 X(a, STATIC,   ONEOF,    UINT32,   (payload_variant,set_ignored_node,set_ignored_node),  47) \
 X(a, STATIC,   ONEOF,    UINT32,   (payload_variant,remove_ignored_node,remove_ignored_node),  48) \
-X(a, STATIC,   ONEOF,    UINT32,   (payload_variant,set_muted_node,set_muted_node),  49) \
-X(a, STATIC,   ONEOF,    UINT32,   (payload_variant,remove_muted_node,remove_muted_node),  50) \
 X(a, STATIC,   ONEOF,    BOOL,     (payload_variant,begin_edit_settings,begin_edit_settings),  64) \
 X(a, STATIC,   ONEOF,    BOOL,     (payload_variant,commit_edit_settings,commit_edit_settings),  65) \
 X(a, STATIC,   ONEOF,    MESSAGE,  (payload_variant,add_contact,add_contact),  66) \
diff --git a/src/mesh/generated/meshtastic/apponly.pb.h b/src/mesh/generated/meshtastic/apponly.pb.h
index f4c33bd79..db9dedaaf 100644
--- a/src/mesh/generated/meshtastic/apponly.pb.h
+++ b/src/mesh/generated/meshtastic/apponly.pb.h
@@ -55,7 +55,7 @@ extern const pb_msgdesc_t meshtastic_ChannelSet_msg;
 
 /* Maximum encoded size of messages (where known) */
 #define MESHTASTIC_MESHTASTIC_APPONLY_PB_H_MAX_SIZE meshtastic_ChannelSet_size
-#define meshtastic_ChannelSet_size               679
+#define meshtastic_ChannelSet_size               695
 
 #ifdef __cplusplus
 } /* extern "C" */
diff --git a/src/mesh/generated/meshtastic/channel.pb.h b/src/mesh/generated/meshtastic/channel.pb.h
index ca4310bf1..7331109a3 100644
--- a/src/mesh/generated/meshtastic/channel.pb.h
+++ b/src/mesh/generated/meshtastic/channel.pb.h
@@ -97,6 +97,10 @@ typedef struct _meshtastic_ChannelSettings {
     /* Per-channel module settings. */
     bool has_module_settings;
     meshtastic_ModuleSettings module_settings;
+    /* Whether or not we should receive notifactions / alerts through this channel
+ Note: This is NOT the same as module_settings.is_client_mute which pertains 
+ to the device role. */
+    bool mute;
 } meshtastic_ChannelSettings;
 
 /* A pair of a channel number, mode and the (sharable) settings for that channel */
@@ -128,10 +132,10 @@ extern "C" {
 
 
 /* Initializer values for message structs */
-#define meshtastic_ChannelSettings_init_default  {0, {0, {0}}, "", 0, 0, 0, false, meshtastic_ModuleSettings_init_default}
+#define meshtastic_ChannelSettings_init_default  {0, {0, {0}}, "", 0, 0, 0, false, meshtastic_ModuleSettings_init_default, 0}
 #define meshtastic_ModuleSettings_init_default   {0, 0}
 #define meshtastic_Channel_init_default          {0, false, meshtastic_ChannelSettings_init_default, _meshtastic_Channel_Role_MIN}
-#define meshtastic_ChannelSettings_init_zero     {0, {0, {0}}, "", 0, 0, 0, false, meshtastic_ModuleSettings_init_zero}
+#define meshtastic_ChannelSettings_init_zero     {0, {0, {0}}, "", 0, 0, 0, false, meshtastic_ModuleSettings_init_zero, 0}
 #define meshtastic_ModuleSettings_init_zero      {0, 0}
 #define meshtastic_Channel_init_zero             {0, false, meshtastic_ChannelSettings_init_zero, _meshtastic_Channel_Role_MIN}
 
@@ -145,6 +149,7 @@ extern "C" {
 #define meshtastic_ChannelSettings_uplink_enabled_tag 5
 #define meshtastic_ChannelSettings_downlink_enabled_tag 6
 #define meshtastic_ChannelSettings_module_settings_tag 7
+#define meshtastic_ChannelSettings_mute_tag      8
 #define meshtastic_Channel_index_tag             1
 #define meshtastic_Channel_settings_tag          2
 #define meshtastic_Channel_role_tag              3
@@ -157,7 +162,8 @@ X(a, STATIC,   SINGULAR, STRING,   name,              3) \
 X(a, STATIC,   SINGULAR, FIXED32,  id,                4) \
 X(a, STATIC,   SINGULAR, BOOL,     uplink_enabled,    5) \
 X(a, STATIC,   SINGULAR, BOOL,     downlink_enabled,   6) \
-X(a, STATIC,   OPTIONAL, MESSAGE,  module_settings,   7)
+X(a, STATIC,   OPTIONAL, MESSAGE,  module_settings,   7) \
+X(a, STATIC,   SINGULAR, BOOL,     mute,              8)
 #define meshtastic_ChannelSettings_CALLBACK NULL
 #define meshtastic_ChannelSettings_DEFAULT NULL
 #define meshtastic_ChannelSettings_module_settings_MSGTYPE meshtastic_ModuleSettings
@@ -187,8 +193,8 @@ extern const pb_msgdesc_t meshtastic_Channel_msg;
 
 /* Maximum encoded size of messages (where known) */
 #define MESHTASTIC_MESHTASTIC_CHANNEL_PB_H_MAX_SIZE meshtastic_Channel_size
-#define meshtastic_ChannelSettings_size          72
-#define meshtastic_Channel_size                  87
+#define meshtastic_ChannelSettings_size          74
+#define meshtastic_Channel_size                  89
 #define meshtastic_ModuleSettings_size           8
 
 #ifdef __cplusplus
diff --git a/src/mesh/generated/meshtastic/deviceonly.pb.h b/src/mesh/generated/meshtastic/deviceonly.pb.h
index 4f6e6a106..59c70cc9e 100644
--- a/src/mesh/generated/meshtastic/deviceonly.pb.h
+++ b/src/mesh/generated/meshtastic/deviceonly.pb.h
@@ -94,9 +94,6 @@ typedef struct _meshtastic_NodeInfoLite {
     /* True if node is in our ignored list
  Persists between NodeDB internal clean ups */
     bool is_ignored;
-    /* True if node has been muted
- Persists between NodeDB internal clean ups */
-    bool is_muted;
     /* Last byte of the node number of the node that should be used as the next hop to reach this node. */
     uint8_t next_hop;
     /* Bitfield for storing booleans.
@@ -193,14 +190,14 @@ extern "C" {
 /* Initializer values for message structs */
 #define meshtastic_PositionLite_init_default     {0, 0, 0, 0, _meshtastic_Position_LocSource_MIN}
 #define meshtastic_UserLite_init_default         {{0}, "", "", _meshtastic_HardwareModel_MIN, 0, _meshtastic_Config_DeviceConfig_Role_MIN, {0, {0}}, false, 0}
-#define meshtastic_NodeInfoLite_init_default     {0, false, meshtastic_UserLite_init_default, false, meshtastic_PositionLite_init_default, 0, 0, false, meshtastic_DeviceMetrics_init_default, 0, 0, false, 0, 0, 0, 0, 0, 0}
+#define meshtastic_NodeInfoLite_init_default     {0, false, meshtastic_UserLite_init_default, false, meshtastic_PositionLite_init_default, 0, 0, false, meshtastic_DeviceMetrics_init_default, 0, 0, false, 0, 0, 0, 0, 0}
 #define meshtastic_DeviceState_init_default      {false, meshtastic_MyNodeInfo_init_default, false, meshtastic_User_init_default, 0, {meshtastic_MeshPacket_init_default}, false, meshtastic_MeshPacket_init_default, 0, 0, 0, false, meshtastic_MeshPacket_init_default, 0, {meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default}}
 #define meshtastic_NodeDatabase_init_default     {0, {0}}
 #define meshtastic_ChannelFile_init_default      {0, {meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default}, 0}
 #define meshtastic_BackupPreferences_init_default {0, 0, false, meshtastic_LocalConfig_init_default, false, meshtastic_LocalModuleConfig_init_default, false, meshtastic_ChannelFile_init_default, false, meshtastic_User_init_default}
 #define meshtastic_PositionLite_init_zero        {0, 0, 0, 0, _meshtastic_Position_LocSource_MIN}
 #define meshtastic_UserLite_init_zero            {{0}, "", "", _meshtastic_HardwareModel_MIN, 0, _meshtastic_Config_DeviceConfig_Role_MIN, {0, {0}}, false, 0}
-#define meshtastic_NodeInfoLite_init_zero        {0, false, meshtastic_UserLite_init_zero, false, meshtastic_PositionLite_init_zero, 0, 0, false, meshtastic_DeviceMetrics_init_zero, 0, 0, false, 0, 0, 0, 0, 0, 0}
+#define meshtastic_NodeInfoLite_init_zero        {0, false, meshtastic_UserLite_init_zero, false, meshtastic_PositionLite_init_zero, 0, 0, false, meshtastic_DeviceMetrics_init_zero, 0, 0, false, 0, 0, 0, 0, 0}
 #define meshtastic_DeviceState_init_zero         {false, meshtastic_MyNodeInfo_init_zero, false, meshtastic_User_init_zero, 0, {meshtastic_MeshPacket_init_zero}, false, meshtastic_MeshPacket_init_zero, 0, 0, 0, false, meshtastic_MeshPacket_init_zero, 0, {meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero}}
 #define meshtastic_NodeDatabase_init_zero        {0, {0}}
 #define meshtastic_ChannelFile_init_zero         {0, {meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero}, 0}
@@ -231,9 +228,8 @@ extern "C" {
 #define meshtastic_NodeInfoLite_hops_away_tag    9
 #define meshtastic_NodeInfoLite_is_favorite_tag  10
 #define meshtastic_NodeInfoLite_is_ignored_tag   11
-#define meshtastic_NodeInfoLite_is_muted_tag     12
-#define meshtastic_NodeInfoLite_next_hop_tag     13
-#define meshtastic_NodeInfoLite_bitfield_tag     14
+#define meshtastic_NodeInfoLite_next_hop_tag     12
+#define meshtastic_NodeInfoLite_bitfield_tag     13
 #define meshtastic_DeviceState_my_node_tag       2
 #define meshtastic_DeviceState_owner_tag         3
 #define meshtastic_DeviceState_receive_queue_tag 5
@@ -288,9 +284,8 @@ X(a, STATIC,   SINGULAR, BOOL,     via_mqtt,          8) \
 X(a, STATIC,   OPTIONAL, UINT32,   hops_away,         9) \
 X(a, STATIC,   SINGULAR, BOOL,     is_favorite,      10) \
 X(a, STATIC,   SINGULAR, BOOL,     is_ignored,       11) \
-X(a, STATIC,   SINGULAR, BOOL,     is_muted,         12) \
-X(a, STATIC,   SINGULAR, UINT32,   next_hop,         13) \
-X(a, STATIC,   SINGULAR, UINT32,   bitfield,         14)
+X(a, STATIC,   SINGULAR, UINT32,   next_hop,         12) \
+X(a, STATIC,   SINGULAR, UINT32,   bitfield,         13)
 #define meshtastic_NodeInfoLite_CALLBACK NULL
 #define meshtastic_NodeInfoLite_DEFAULT NULL
 #define meshtastic_NodeInfoLite_user_MSGTYPE meshtastic_UserLite
@@ -365,10 +360,10 @@ 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_ChannelFile_size              718
+#define meshtastic_BackupPreferences_size        2287
+#define meshtastic_ChannelFile_size              734
 #define meshtastic_DeviceState_size              1737
-#define meshtastic_NodeInfoLite_size             198
+#define meshtastic_NodeInfoLite_size             196
 #define meshtastic_PositionLite_size             28
 #define meshtastic_UserLite_size                 98
 
diff --git a/src/modules/ExternalNotificationModule.cpp b/src/modules/ExternalNotificationModule.cpp
index 28e451f49..92590f149 100644
--- a/src/modules/ExternalNotificationModule.cpp
+++ b/src/modules/ExternalNotificationModule.cpp
@@ -510,8 +510,7 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP
                 }
             }
 
-            if (moduleConfig.external_notification.alert_message && !sender->is_muted &&
-                !ch.settings.module_settings.is_client_muted) {
+            if (moduleConfig.external_notification.alert_message && !sender->is_muted && !ch.settings.mute) {
                 LOG_INFO("externalNotificationModule - Notification Module");
                 isNagging = true;
                 setExternalState(0, true);
@@ -522,8 +521,7 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP
                 }
             }
 
-            if (moduleConfig.external_notification.alert_message_vibra && !sender->is_muted &&
-                !ch.settings.module_settings.is_client_muted) {
+            if (moduleConfig.external_notification.alert_message_vibra && !sender->is_muted && !ch.settings.mute) {
                 LOG_INFO("externalNotificationModule - Notification Module (Vibra)");
                 isNagging = true;
                 setExternalState(1, true);
@@ -534,8 +532,7 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP
                 }
             }
 
-            if (moduleConfig.external_notification.alert_message_buzzer && !sender->is_muted &&
-                !ch.settings.module_settings.is_client_muted) {
+            if (moduleConfig.external_notification.alert_message_buzzer && !sender->is_muted && !ch.settings.mute) {
                 LOG_INFO("externalNotificationModule - Notification Module (Buzzer)");
                 isNagging = true;
                 if (!moduleConfig.external_notification.use_pwm && !moduleConfig.external_notification.use_i2s_as_buzzer) {

From bfadd9c866fa60f4cd5bf1eeea2fc273502b21ec Mon Sep 17 00:00:00 2001
From: ford-jones 
Date: Sat, 13 Sep 2025 17:51:52 +1200
Subject: [PATCH 199/691] Regen protos

---
 src/mesh/generated/meshtastic/admin.pb.h      |  8 ++++++++
 src/mesh/generated/meshtastic/channel.pb.h    |  2 +-
 src/mesh/generated/meshtastic/deviceonly.pb.h | 19 ++++++++++++-------
 3 files changed, 21 insertions(+), 8 deletions(-)

diff --git a/src/mesh/generated/meshtastic/admin.pb.h b/src/mesh/generated/meshtastic/admin.pb.h
index bc0b780b9..616b7e8ee 100644
--- a/src/mesh/generated/meshtastic/admin.pb.h
+++ b/src/mesh/generated/meshtastic/admin.pb.h
@@ -247,6 +247,10 @@ typedef struct _meshtastic_AdminMessage {
         uint32_t set_ignored_node;
         /* Set specified node-num to be un-ignored on the NodeDB on the device */
         uint32_t remove_ignored_node;
+        /* Set specified node-num to be muted */
+        uint32_t set_muted_node;
+        /* Set specified node-num to be heard / not-muted */
+        uint32_t remove_muted_node;
         /* Begins an edit transaction for config, module config, owner, and channel settings changes
      This will delay the standard *implicit* save to the file system and subsequent reboot behavior until committed (commit_edit_settings) */
         bool begin_edit_settings;
@@ -388,6 +392,8 @@ extern "C" {
 #define meshtastic_AdminMessage_store_ui_config_tag 46
 #define meshtastic_AdminMessage_set_ignored_node_tag 47
 #define meshtastic_AdminMessage_remove_ignored_node_tag 48
+#define meshtastic_AdminMessage_set_muted_node_tag 49
+#define meshtastic_AdminMessage_remove_muted_node_tag 50
 #define meshtastic_AdminMessage_begin_edit_settings_tag 64
 #define meshtastic_AdminMessage_commit_edit_settings_tag 65
 #define meshtastic_AdminMessage_add_contact_tag  66
@@ -446,6 +452,8 @@ X(a, STATIC,   ONEOF,    MESSAGE,  (payload_variant,get_ui_config_response,get_u
 X(a, STATIC,   ONEOF,    MESSAGE,  (payload_variant,store_ui_config,store_ui_config),  46) \
 X(a, STATIC,   ONEOF,    UINT32,   (payload_variant,set_ignored_node,set_ignored_node),  47) \
 X(a, STATIC,   ONEOF,    UINT32,   (payload_variant,remove_ignored_node,remove_ignored_node),  48) \
+X(a, STATIC,   ONEOF,    UINT32,   (payload_variant,set_muted_node,set_muted_node),  49) \
+X(a, STATIC,   ONEOF,    UINT32,   (payload_variant,remove_muted_node,remove_muted_node),  50) \
 X(a, STATIC,   ONEOF,    BOOL,     (payload_variant,begin_edit_settings,begin_edit_settings),  64) \
 X(a, STATIC,   ONEOF,    BOOL,     (payload_variant,commit_edit_settings,commit_edit_settings),  65) \
 X(a, STATIC,   ONEOF,    MESSAGE,  (payload_variant,add_contact,add_contact),  66) \
diff --git a/src/mesh/generated/meshtastic/channel.pb.h b/src/mesh/generated/meshtastic/channel.pb.h
index 7331109a3..1e1b95df7 100644
--- a/src/mesh/generated/meshtastic/channel.pb.h
+++ b/src/mesh/generated/meshtastic/channel.pb.h
@@ -98,7 +98,7 @@ typedef struct _meshtastic_ChannelSettings {
     bool has_module_settings;
     meshtastic_ModuleSettings module_settings;
     /* Whether or not we should receive notifactions / alerts through this channel
- Note: This is NOT the same as module_settings.is_client_mute which pertains 
+ Note: This is NOT the same as module_settings.is_client_mute which pertains
  to the device role. */
     bool mute;
 } meshtastic_ChannelSettings;
diff --git a/src/mesh/generated/meshtastic/deviceonly.pb.h b/src/mesh/generated/meshtastic/deviceonly.pb.h
index 59c70cc9e..55c433699 100644
--- a/src/mesh/generated/meshtastic/deviceonly.pb.h
+++ b/src/mesh/generated/meshtastic/deviceonly.pb.h
@@ -94,6 +94,9 @@ typedef struct _meshtastic_NodeInfoLite {
     /* True if node is in our ignored list
  Persists between NodeDB internal clean ups */
     bool is_ignored;
+    /* True if node has been muted
+ Persists between NodeDB internal clean ups */
+    bool is_muted;
     /* Last byte of the node number of the node that should be used as the next hop to reach this node. */
     uint8_t next_hop;
     /* Bitfield for storing booleans.
@@ -190,14 +193,14 @@ extern "C" {
 /* Initializer values for message structs */
 #define meshtastic_PositionLite_init_default     {0, 0, 0, 0, _meshtastic_Position_LocSource_MIN}
 #define meshtastic_UserLite_init_default         {{0}, "", "", _meshtastic_HardwareModel_MIN, 0, _meshtastic_Config_DeviceConfig_Role_MIN, {0, {0}}, false, 0}
-#define meshtastic_NodeInfoLite_init_default     {0, false, meshtastic_UserLite_init_default, false, meshtastic_PositionLite_init_default, 0, 0, false, meshtastic_DeviceMetrics_init_default, 0, 0, false, 0, 0, 0, 0, 0}
+#define meshtastic_NodeInfoLite_init_default     {0, false, meshtastic_UserLite_init_default, false, meshtastic_PositionLite_init_default, 0, 0, false, meshtastic_DeviceMetrics_init_default, 0, 0, false, 0, 0, 0, 0, 0, 0}
 #define meshtastic_DeviceState_init_default      {false, meshtastic_MyNodeInfo_init_default, false, meshtastic_User_init_default, 0, {meshtastic_MeshPacket_init_default}, false, meshtastic_MeshPacket_init_default, 0, 0, 0, false, meshtastic_MeshPacket_init_default, 0, {meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default}}
 #define meshtastic_NodeDatabase_init_default     {0, {0}}
 #define meshtastic_ChannelFile_init_default      {0, {meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default}, 0}
 #define meshtastic_BackupPreferences_init_default {0, 0, false, meshtastic_LocalConfig_init_default, false, meshtastic_LocalModuleConfig_init_default, false, meshtastic_ChannelFile_init_default, false, meshtastic_User_init_default}
 #define meshtastic_PositionLite_init_zero        {0, 0, 0, 0, _meshtastic_Position_LocSource_MIN}
 #define meshtastic_UserLite_init_zero            {{0}, "", "", _meshtastic_HardwareModel_MIN, 0, _meshtastic_Config_DeviceConfig_Role_MIN, {0, {0}}, false, 0}
-#define meshtastic_NodeInfoLite_init_zero        {0, false, meshtastic_UserLite_init_zero, false, meshtastic_PositionLite_init_zero, 0, 0, false, meshtastic_DeviceMetrics_init_zero, 0, 0, false, 0, 0, 0, 0, 0}
+#define meshtastic_NodeInfoLite_init_zero        {0, false, meshtastic_UserLite_init_zero, false, meshtastic_PositionLite_init_zero, 0, 0, false, meshtastic_DeviceMetrics_init_zero, 0, 0, false, 0, 0, 0, 0, 0, 0}
 #define meshtastic_DeviceState_init_zero         {false, meshtastic_MyNodeInfo_init_zero, false, meshtastic_User_init_zero, 0, {meshtastic_MeshPacket_init_zero}, false, meshtastic_MeshPacket_init_zero, 0, 0, 0, false, meshtastic_MeshPacket_init_zero, 0, {meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero}}
 #define meshtastic_NodeDatabase_init_zero        {0, {0}}
 #define meshtastic_ChannelFile_init_zero         {0, {meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero}, 0}
@@ -228,8 +231,9 @@ extern "C" {
 #define meshtastic_NodeInfoLite_hops_away_tag    9
 #define meshtastic_NodeInfoLite_is_favorite_tag  10
 #define meshtastic_NodeInfoLite_is_ignored_tag   11
-#define meshtastic_NodeInfoLite_next_hop_tag     12
-#define meshtastic_NodeInfoLite_bitfield_tag     13
+#define meshtastic_NodeInfoLite_is_muted_tag     12
+#define meshtastic_NodeInfoLite_next_hop_tag     13
+#define meshtastic_NodeInfoLite_bitfield_tag     14
 #define meshtastic_DeviceState_my_node_tag       2
 #define meshtastic_DeviceState_owner_tag         3
 #define meshtastic_DeviceState_receive_queue_tag 5
@@ -284,8 +288,9 @@ X(a, STATIC,   SINGULAR, BOOL,     via_mqtt,          8) \
 X(a, STATIC,   OPTIONAL, UINT32,   hops_away,         9) \
 X(a, STATIC,   SINGULAR, BOOL,     is_favorite,      10) \
 X(a, STATIC,   SINGULAR, BOOL,     is_ignored,       11) \
-X(a, STATIC,   SINGULAR, UINT32,   next_hop,         12) \
-X(a, STATIC,   SINGULAR, UINT32,   bitfield,         13)
+X(a, STATIC,   SINGULAR, BOOL,     is_muted,         12) \
+X(a, STATIC,   SINGULAR, UINT32,   next_hop,         13) \
+X(a, STATIC,   SINGULAR, UINT32,   bitfield,         14)
 #define meshtastic_NodeInfoLite_CALLBACK NULL
 #define meshtastic_NodeInfoLite_DEFAULT NULL
 #define meshtastic_NodeInfoLite_user_MSGTYPE meshtastic_UserLite
@@ -363,7 +368,7 @@ extern const pb_msgdesc_t meshtastic_BackupPreferences_msg;
 #define meshtastic_BackupPreferences_size        2287
 #define meshtastic_ChannelFile_size              734
 #define meshtastic_DeviceState_size              1737
-#define meshtastic_NodeInfoLite_size             196
+#define meshtastic_NodeInfoLite_size             198
 #define meshtastic_PositionLite_size             28
 #define meshtastic_UserLite_size                 98
 

From b6dd99917d365872a94138c74d04658180265afd Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
 <41898282+github-actions[bot]@users.noreply.github.com>
Date: Sat, 13 Sep 2025 06:37:58 -0500
Subject: [PATCH 200/691] Update protobufs (#7973)

Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
---
 protobufs                                    | 2 +-
 src/mesh/generated/meshtastic/device_ui.pb.h | 2 ++
 2 files changed, 3 insertions(+), 1 deletion(-)

diff --git a/protobufs b/protobufs
index a84657c22..8caf42396 160000
--- a/protobufs
+++ b/protobufs
@@ -1 +1 @@
-Subproject commit a84657c220421536f18d11fc5edf680efadbceeb
+Subproject commit 8caf42396438f0d8a0305143485fd671c1fc7126
diff --git a/src/mesh/generated/meshtastic/device_ui.pb.h b/src/mesh/generated/meshtastic/device_ui.pb.h
index 8313438f8..8f693e570 100644
--- a/src/mesh/generated/meshtastic/device_ui.pb.h
+++ b/src/mesh/generated/meshtastic/device_ui.pb.h
@@ -66,6 +66,8 @@ typedef enum _meshtastic_Language {
     meshtastic_Language_UKRAINIAN = 16,
     /* Bulgarian */
     meshtastic_Language_BULGARIAN = 17,
+    /* Czech */
+    meshtastic_Language_CZECH = 18,
     /* Simplified Chinese (experimental) */
     meshtastic_Language_SIMPLIFIED_CHINESE = 30,
     /* Traditional Chinese (experimental) */

From 566c2c3fdf8389ced45846a59c7b27dd611e30a4 Mon Sep 17 00:00:00 2001
From: WillyJL 
Date: Sat, 13 Sep 2025 13:50:02 +0200
Subject: [PATCH 201/691] T-Lora Pager: Support LR1121 and SX1280 models
 (#7956)

* T-Lora Pager: Support LR1121 and SX1280 models

* Remove ifdefs
---
 variants/esp32s3/tlora-pager/rfswitch.h | 18 ++++++++++++++++++
 variants/esp32s3/tlora-pager/variant.h  | 19 ++++++++++++++++++-
 2 files changed, 36 insertions(+), 1 deletion(-)
 create mode 100644 variants/esp32s3/tlora-pager/rfswitch.h

diff --git a/variants/esp32s3/tlora-pager/rfswitch.h b/variants/esp32s3/tlora-pager/rfswitch.h
new file mode 100644
index 000000000..1e5eb7a9e
--- /dev/null
+++ b/variants/esp32s3/tlora-pager/rfswitch.h
@@ -0,0 +1,18 @@
+#include "RadioLib.h"
+
+static const uint32_t rfswitch_dio_pins[] = {
+    RADIOLIB_LR11X0_DIO5, RADIOLIB_LR11X0_DIO6,
+    RADIOLIB_NC, RADIOLIB_NC, RADIOLIB_NC
+};
+
+static const Module::RfSwitchMode_t rfswitch_table[] = {
+    // mode                  DIO5  DIO6
+    { LR11x0::MODE_STBY,   { LOW,  LOW  } },
+    { LR11x0::MODE_RX,     { LOW, HIGH  } },
+    { LR11x0::MODE_TX,     { HIGH,  LOW } },
+    { LR11x0::MODE_TX_HP,  { HIGH,  LOW } },
+    { LR11x0::MODE_TX_HF,  { LOW,  LOW  } },
+    { LR11x0::MODE_GNSS,   { LOW,  LOW  } },
+    { LR11x0::MODE_WIFI,   { LOW,  LOW  } },
+    END_OF_MODE_TABLE,
+};
\ No newline at end of file
diff --git a/variants/esp32s3/tlora-pager/variant.h b/variants/esp32s3/tlora-pager/variant.h
index ee48088c8..2875f6804 100644
--- a/variants/esp32s3/tlora-pager/variant.h
+++ b/variants/esp32s3/tlora-pager/variant.h
@@ -105,14 +105,16 @@
 // LoRa
 #define USE_SX1262
 #define USE_SX1268
+#define USE_SX1280
+#define USE_LR1121
 
 #define LORA_SCK 35
 #define LORA_MISO 33
 #define LORA_MOSI 34
 #define LORA_CS 36
+#define LORA_RESET 47
 
 #define LORA_DIO0 -1 // a No connect on the SX1262 module
-#define LORA_RESET 47
 #define LORA_DIO1 14 // SX1262 IRQ
 #define LORA_DIO2 48 // SX1262 BUSY
 #define LORA_DIO3    // Not connected on PCB, but internally on the TTGO SX1262, if DIO3 is high the TXCO is enabled
@@ -123,3 +125,18 @@
 #define SX126X_RESET LORA_RESET
 #define SX126X_DIO2_AS_RF_SWITCH
 #define SX126X_DIO3_TCXO_VOLTAGE 3.0
+
+#define SX128X_CS LORA_CS
+#define SX128X_DIO1 LORA_DIO1
+#define SX128X_BUSY LORA_DIO2
+#define SX128X_RESET LORA_RESET
+
+#define LR1121_IRQ_PIN LORA_DIO1
+#define LR1121_NRESET_PIN LORA_RESET
+#define LR1121_BUSY_PIN LORA_DIO2
+#define LR1121_SPI_NSS_PIN LORA_CS
+#define LR1121_SPI_SCK_PIN LORA_SCK
+#define LR1121_SPI_MOSI_PIN LORA_MOSI
+#define LR1121_SPI_MISO_PIN LORA_MISO
+#define LR11X0_DIO3_TCXO_VOLTAGE 3.0
+#define LR11X0_DIO_AS_RF_SWITCH

From 6d2093650ad16b4c3a8fe8c71d9cc96350710f3f Mon Sep 17 00:00:00 2001
From: WillyJL 
Date: Sat, 13 Sep 2025 13:50:02 +0200
Subject: [PATCH 202/691] T-Lora Pager: Support LR1121 and SX1280 models
 (#7956)

* T-Lora Pager: Support LR1121 and SX1280 models

* Remove ifdefs
---
 variants/esp32s3/tlora-pager/rfswitch.h | 18 ++++++++++++++++++
 variants/esp32s3/tlora-pager/variant.h  | 19 ++++++++++++++++++-
 2 files changed, 36 insertions(+), 1 deletion(-)
 create mode 100644 variants/esp32s3/tlora-pager/rfswitch.h

diff --git a/variants/esp32s3/tlora-pager/rfswitch.h b/variants/esp32s3/tlora-pager/rfswitch.h
new file mode 100644
index 000000000..1e5eb7a9e
--- /dev/null
+++ b/variants/esp32s3/tlora-pager/rfswitch.h
@@ -0,0 +1,18 @@
+#include "RadioLib.h"
+
+static const uint32_t rfswitch_dio_pins[] = {
+    RADIOLIB_LR11X0_DIO5, RADIOLIB_LR11X0_DIO6,
+    RADIOLIB_NC, RADIOLIB_NC, RADIOLIB_NC
+};
+
+static const Module::RfSwitchMode_t rfswitch_table[] = {
+    // mode                  DIO5  DIO6
+    { LR11x0::MODE_STBY,   { LOW,  LOW  } },
+    { LR11x0::MODE_RX,     { LOW, HIGH  } },
+    { LR11x0::MODE_TX,     { HIGH,  LOW } },
+    { LR11x0::MODE_TX_HP,  { HIGH,  LOW } },
+    { LR11x0::MODE_TX_HF,  { LOW,  LOW  } },
+    { LR11x0::MODE_GNSS,   { LOW,  LOW  } },
+    { LR11x0::MODE_WIFI,   { LOW,  LOW  } },
+    END_OF_MODE_TABLE,
+};
\ No newline at end of file
diff --git a/variants/esp32s3/tlora-pager/variant.h b/variants/esp32s3/tlora-pager/variant.h
index ee48088c8..2875f6804 100644
--- a/variants/esp32s3/tlora-pager/variant.h
+++ b/variants/esp32s3/tlora-pager/variant.h
@@ -105,14 +105,16 @@
 // LoRa
 #define USE_SX1262
 #define USE_SX1268
+#define USE_SX1280
+#define USE_LR1121
 
 #define LORA_SCK 35
 #define LORA_MISO 33
 #define LORA_MOSI 34
 #define LORA_CS 36
+#define LORA_RESET 47
 
 #define LORA_DIO0 -1 // a No connect on the SX1262 module
-#define LORA_RESET 47
 #define LORA_DIO1 14 // SX1262 IRQ
 #define LORA_DIO2 48 // SX1262 BUSY
 #define LORA_DIO3    // Not connected on PCB, but internally on the TTGO SX1262, if DIO3 is high the TXCO is enabled
@@ -123,3 +125,18 @@
 #define SX126X_RESET LORA_RESET
 #define SX126X_DIO2_AS_RF_SWITCH
 #define SX126X_DIO3_TCXO_VOLTAGE 3.0
+
+#define SX128X_CS LORA_CS
+#define SX128X_DIO1 LORA_DIO1
+#define SX128X_BUSY LORA_DIO2
+#define SX128X_RESET LORA_RESET
+
+#define LR1121_IRQ_PIN LORA_DIO1
+#define LR1121_NRESET_PIN LORA_RESET
+#define LR1121_BUSY_PIN LORA_DIO2
+#define LR1121_SPI_NSS_PIN LORA_CS
+#define LR1121_SPI_SCK_PIN LORA_SCK
+#define LR1121_SPI_MOSI_PIN LORA_MOSI
+#define LR1121_SPI_MISO_PIN LORA_MISO
+#define LR11X0_DIO3_TCXO_VOLTAGE 3.0
+#define LR11X0_DIO_AS_RF_SWITCH

From 51acd92a37e1dd760fdcc0af1007f584a0de590c Mon Sep 17 00:00:00 2001
From: Ben Meadors 
Date: Sat, 13 Sep 2025 06:51:18 -0500
Subject: [PATCH 203/691] Trunk

---
 variants/esp32s3/tlora-pager/rfswitch.h | 17 +++++------------
 1 file changed, 5 insertions(+), 12 deletions(-)

diff --git a/variants/esp32s3/tlora-pager/rfswitch.h b/variants/esp32s3/tlora-pager/rfswitch.h
index 1e5eb7a9e..0fba5a305 100644
--- a/variants/esp32s3/tlora-pager/rfswitch.h
+++ b/variants/esp32s3/tlora-pager/rfswitch.h
@@ -1,18 +1,11 @@
 #include "RadioLib.h"
 
-static const uint32_t rfswitch_dio_pins[] = {
-    RADIOLIB_LR11X0_DIO5, RADIOLIB_LR11X0_DIO6,
-    RADIOLIB_NC, RADIOLIB_NC, RADIOLIB_NC
-};
+static const uint32_t rfswitch_dio_pins[] = {RADIOLIB_LR11X0_DIO5, RADIOLIB_LR11X0_DIO6, RADIOLIB_NC, RADIOLIB_NC, RADIOLIB_NC};
 
 static const Module::RfSwitchMode_t rfswitch_table[] = {
     // mode                  DIO5  DIO6
-    { LR11x0::MODE_STBY,   { LOW,  LOW  } },
-    { LR11x0::MODE_RX,     { LOW, HIGH  } },
-    { LR11x0::MODE_TX,     { HIGH,  LOW } },
-    { LR11x0::MODE_TX_HP,  { HIGH,  LOW } },
-    { LR11x0::MODE_TX_HF,  { LOW,  LOW  } },
-    { LR11x0::MODE_GNSS,   { LOW,  LOW  } },
-    { LR11x0::MODE_WIFI,   { LOW,  LOW  } },
-    END_OF_MODE_TABLE,
+    {LR11x0::MODE_STBY, {LOW, LOW}},  {LR11x0::MODE_RX, {LOW, HIGH}},
+    {LR11x0::MODE_TX, {HIGH, LOW}},   {LR11x0::MODE_TX_HP, {HIGH, LOW}},
+    {LR11x0::MODE_TX_HF, {LOW, LOW}}, {LR11x0::MODE_GNSS, {LOW, LOW}},
+    {LR11x0::MODE_WIFI, {LOW, LOW}},  END_OF_MODE_TABLE,
 };
\ No newline at end of file

From 70ac3601b04ef3f9243777fde545befbe254c08e Mon Sep 17 00:00:00 2001
From: Ben Meadors 
Date: Sat, 13 Sep 2025 06:57:12 -0500
Subject: [PATCH 204/691] Trunk


From 9211b1bb4b0cf9fcfa6a889cbab49938743f7414 Mon Sep 17 00:00:00 2001
From: Ben Meadors 
Date: Sat, 13 Sep 2025 07:01:07 -0500
Subject: [PATCH 205/691] Static memory pool allocation (#7966)

* Static memory pool

* Initializer

* T-Lora Pager: Support LR1121 and SX1280 models (#7956)

* T-Lora Pager: Support LR1121 and SX1280 models

* Remove ifdefs

---------

Co-authored-by: WillyJL 
---
 src/mesh/MemoryPool.h                   |  9 ++++-----
 src/mesh/MeshService.cpp                |  9 ++++++---
 src/mesh/Router.cpp                     |  3 +--
 variants/esp32s3/tlora-pager/rfswitch.h | 12 ++++++++----
 4 files changed, 19 insertions(+), 14 deletions(-)

diff --git a/src/mesh/MemoryPool.h b/src/mesh/MemoryPool.h
index 0c5ba6c71..eb5ac5109 100644
--- a/src/mesh/MemoryPool.h
+++ b/src/mesh/MemoryPool.h
@@ -115,12 +115,11 @@ template  class MemoryPool : public Allocator
     bool used[MaxSize];
 
   public:
-    MemoryPool()
+    MemoryPool() : pool{}, used{}
     {
-        // Initialize the used array to false (all slots free)
-        for (int i = 0; i < MaxSize; i++) {
-            used[i] = false;
-        }
+        // Arrays are now zero-initialized by member initializer list
+        // pool array: all elements are default-constructed (zero for POD types)
+        // used array: all elements are false (zero-initialized)
     }
 
     /// Return a buffer for use by others
diff --git a/src/mesh/MeshService.cpp b/src/mesh/MeshService.cpp
index 607766ab6..96782cda5 100644
--- a/src/mesh/MeshService.cpp
+++ b/src/mesh/MeshService.cpp
@@ -46,11 +46,14 @@ the new node can build its node db)
 
 MeshService *service;
 
-static MemoryDynamic staticMqttClientProxyMessagePool;
+#define MAX_MQTT_PROXY_MESSAGES 16
+static MemoryPool staticMqttClientProxyMessagePool;
 
-static MemoryDynamic staticQueueStatusPool;
+#define MAX_QUEUE_STATUS 4
+static MemoryPool staticQueueStatusPool;
 
-static MemoryDynamic staticClientNotificationPool;
+#define MAX_CLIENT_NOTIFICATIONS 4
+static MemoryPool staticClientNotificationPool;
 
 Allocator &mqttClientProxyMessagePool = staticMqttClientProxyMessagePool;
 
diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp
index 44d09637f..b5ae1ec0c 100644
--- a/src/mesh/Router.cpp
+++ b/src/mesh/Router.cpp
@@ -31,8 +31,7 @@
     (MAX_RX_TOPHONE + MAX_RX_FROMRADIO + 2 * MAX_TX_QUEUE +                                                                      \
      2) // max number of packets which can be in flight (either queued from reception or queued for sending)
 
-// static MemoryPool staticPool(MAX_PACKETS);
-static MemoryDynamic staticPool;
+static MemoryPool staticPool;
 
 Allocator &packetPool = staticPool;
 
diff --git a/variants/esp32s3/tlora-pager/rfswitch.h b/variants/esp32s3/tlora-pager/rfswitch.h
index 0fba5a305..337346ec5 100644
--- a/variants/esp32s3/tlora-pager/rfswitch.h
+++ b/variants/esp32s3/tlora-pager/rfswitch.h
@@ -4,8 +4,12 @@ static const uint32_t rfswitch_dio_pins[] = {RADIOLIB_LR11X0_DIO5, RADIOLIB_LR11
 
 static const Module::RfSwitchMode_t rfswitch_table[] = {
     // mode                  DIO5  DIO6
-    {LR11x0::MODE_STBY, {LOW, LOW}},  {LR11x0::MODE_RX, {LOW, HIGH}},
-    {LR11x0::MODE_TX, {HIGH, LOW}},   {LR11x0::MODE_TX_HP, {HIGH, LOW}},
-    {LR11x0::MODE_TX_HF, {LOW, LOW}}, {LR11x0::MODE_GNSS, {LOW, LOW}},
-    {LR11x0::MODE_WIFI, {LOW, LOW}},  END_OF_MODE_TABLE,
+    {LR11x0::MODE_STBY, {LOW, LOW}},
+    {LR11x0::MODE_RX, {LOW, HIGH}},
+    {LR11x0::MODE_TX, {HIGH, LOW}},
+    {LR11x0::MODE_TX_HP, {HIGH, LOW}},
+    {LR11x0::MODE_TX_HF, {LOW, LOW}},
+    {LR11x0::MODE_GNSS, {LOW, LOW}},
+    {LR11x0::MODE_WIFI, {LOW, LOW}},
+    END_OF_MODE_TABLE,
 };
\ No newline at end of file

From 90ddbf6f2cb381616e1f5d144b3795124976470f Mon Sep 17 00:00:00 2001
From: "Trent V." 
Date: Sat, 13 Sep 2025 11:56:23 -0500
Subject: [PATCH 206/691] updated shebang to use a more standard path for bash
 (#7922)

Signed-off-by: Trenton VanderWert 
---
 bin/device-install.sh | 2 +-
 bin/device-update.sh  | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/bin/device-install.sh b/bin/device-install.sh
index 594f9dd6b..ede75bbba 100755
--- a/bin/device-install.sh
+++ b/bin/device-install.sh
@@ -1,4 +1,4 @@
-#!/bin/bash
+#!/usr/bin/env bash
 
 PYTHON=${PYTHON:-$(which python3 python | head -n 1)}
 BPS_RESET=false
diff --git a/bin/device-update.sh b/bin/device-update.sh
index 6f29496e9..f64280a5b 100755
--- a/bin/device-update.sh
+++ b/bin/device-update.sh
@@ -1,4 +1,4 @@
-#!/bin/bash
+#!/usr/bin/env bash
 
 PYTHON=${PYTHON:-$(which python3 python|head -n 1)}
 CHANGE_MODE=false

From 78dfb05eeb475af7dfff1903f7d2923e5bba74ca Mon Sep 17 00:00:00 2001
From: Ben Meadors 
Date: Sat, 13 Sep 2025 11:59:50 -0500
Subject: [PATCH 207/691] Portduino dynamic alloc

---
 src/mesh/Router.cpp | 14 +++++++++++++-
 1 file changed, 13 insertions(+), 1 deletion(-)

diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp
index b5ae1ec0c..c5eed5180 100644
--- a/src/mesh/Router.cpp
+++ b/src/mesh/Router.cpp
@@ -5,6 +5,7 @@
 #include "MeshService.h"
 #include "NodeDB.h"
 #include "RTC.h"
+
 #include "configuration.h"
 #include "detect/LoRaRadioType.h"
 #include "main.h"
@@ -27,13 +28,24 @@
 
 // I think this is right, one packet for each of the three fifos + one packet being currently assembled for TX or RX
 // And every TX packet might have a retransmission packet or an ack alive at any moment
+
+#ifdef ARCH_PORTDUINO
+// Portduino (native) targets can use dynamic memory pools with runtime-configurable sizes
 #define MAX_PACKETS                                                                                                              \
     (MAX_RX_TOPHONE + MAX_RX_FROMRADIO + 2 * MAX_TX_QUEUE +                                                                      \
      2) // max number of packets which can be in flight (either queued from reception or queued for sending)
 
-static MemoryPool staticPool;
+static MemoryDynamic dynamicPool(MAX_PACKETS);
+Allocator &packetPool = dynamicPool;
+#else
+// Embedded targets use static memory pools with compile-time constants
+#define MAX_PACKETS_STATIC                                                                                                       \
+    (MAX_RX_TOPHONE + MAX_RX_FROMRADIO + 2 * MAX_TX_QUEUE +                                                                      \
+     2) // max number of packets which can be in flight (either queued from reception or queued for sending)
 
+static MemoryPool staticPool;
 Allocator &packetPool = staticPool;
+#endif
 
 static uint8_t bytes[MAX_LORA_PAYLOAD_LEN + 1] __attribute__((__aligned__));
 

From 4ee07226e4574b35fb864f337ff3753fee40cae1 Mon Sep 17 00:00:00 2001
From: Ben Meadors 
Date: Sat, 13 Sep 2025 11:59:58 -0500
Subject: [PATCH 208/691] Missed


From ae814b54630c470d023b00b5b53146692208f394 Mon Sep 17 00:00:00 2001
From: Ben Meadors 
Date: Sat, 13 Sep 2025 12:07:14 -0500
Subject: [PATCH 209/691] Drop the limit

---
 src/mesh/Router.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp
index c5eed5180..6c5d08a93 100644
--- a/src/mesh/Router.cpp
+++ b/src/mesh/Router.cpp
@@ -35,7 +35,7 @@
     (MAX_RX_TOPHONE + MAX_RX_FROMRADIO + 2 * MAX_TX_QUEUE +                                                                      \
      2) // max number of packets which can be in flight (either queued from reception or queued for sending)
 
-static MemoryDynamic dynamicPool(MAX_PACKETS);
+static MemoryDynamic dynamicPool;
 Allocator &packetPool = dynamicPool;
 #else
 // Embedded targets use static memory pools with compile-time constants

From de3a65579dd0d31d36a6e764563c4fb4dde413a4 Mon Sep 17 00:00:00 2001
From: Jason P 
Date: Sat, 13 Sep 2025 15:06:36 -0500
Subject: [PATCH 210/691] Add formatting and menu picking for other GPS format
 options (#7974)

* Add back options for other GPS format options

* Rename variables and don't overlap elements

* Fix default value

* Should probably add a menu while I'm here!

* Shorten names just a bit to fit on screens

* Fix off by one

* Labels try to make things better

* Missed a label
---
 src/graphics/draw/MenuHandler.cpp |  55 ++++++++++++++--
 src/graphics/draw/MenuHandler.h   |   2 +
 src/graphics/draw/UIRenderer.cpp  | 100 +++++++++++++++++-------------
 src/graphics/draw/UIRenderer.h    |   3 +-
 4 files changed, 111 insertions(+), 49 deletions(-)

diff --git a/src/graphics/draw/MenuHandler.cpp b/src/graphics/draw/MenuHandler.cpp
index dab3040f0..73381a92b 100644
--- a/src/graphics/draw/MenuHandler.cpp
+++ b/src/graphics/draw/MenuHandler.cpp
@@ -588,11 +588,11 @@ void menuHandler::favoriteBaseMenu()
 
 void menuHandler::positionBaseMenu()
 {
-    enum optionsNumbers { Back, GPSToggle, CompassMenu, CompassCalibrate, enumEnd };
+    enum optionsNumbers { Back, GPSToggle, GPSFormat, CompassMenu, CompassCalibrate, enumEnd };
 
-    static const char *optionsArray[enumEnd] = {"Back", "GPS Toggle", "Compass"};
-    static int optionsEnumArray[enumEnd] = {Back, GPSToggle, CompassMenu};
-    int options = 3;
+    static const char *optionsArray[enumEnd] = {"Back", "GPS Toggle", "GPS Format", "Compass"};
+    static int optionsEnumArray[enumEnd] = {Back, GPSToggle, GPSFormat, CompassMenu};
+    int options = 4;
 
     if (accelerometerThread) {
         optionsArray[options] = "Compass Calibrate";
@@ -608,6 +608,9 @@ void menuHandler::positionBaseMenu()
         if (selected == GPSToggle) {
             menuQueue = gps_toggle_menu;
             screen->runNow();
+        } else if (selected == GPSFormat) {
+            menuQueue = gps_format_menu;
+            screen->runNow();
         } else if (selected == CompassMenu) {
             menuQueue = compass_point_north_menu;
             screen->runNow();
@@ -726,6 +729,47 @@ void menuHandler::GPSToggleMenu()
     bannerOptions.InitialSelected = config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_ENABLED ? 1 : 2;
     screen->showOverlayBanner(bannerOptions);
 }
+void menuHandler::GPSFormatMenu()
+{
+
+    static const char *optionsArray[] = {"Back",
+                                         isHighResolution ? "Decimal Degrees" : "DEC",
+                                         isHighResolution ? "Degrees Minutes Seconds" : "DMS",
+                                         isHighResolution ? "Universal Transverse Mercator" : "UTM",
+                                         isHighResolution ? "Military Grid Reference System" : "MGRS",
+                                         isHighResolution ? "Open Location Code" : "OLC",
+                                         isHighResolution ? "Ordnance Survey Grid Ref" : "OSGR"};
+    BannerOverlayOptions bannerOptions;
+    bannerOptions.message = "GPS Format";
+    bannerOptions.optionsArrayPtr = optionsArray;
+    bannerOptions.optionsCount = 7;
+    bannerOptions.bannerCallback = [](int selected) -> void {
+        if (selected == 1) {
+            config.display.gps_format = meshtastic_Config_DisplayConfig_GpsCoordinateFormat_DEC;
+            service->reloadConfig(SEGMENT_CONFIG);
+        } else if (selected == 2) {
+            config.display.gps_format = meshtastic_Config_DisplayConfig_GpsCoordinateFormat_DMS;
+            service->reloadConfig(SEGMENT_CONFIG);
+        } else if (selected == 3) {
+            config.display.gps_format = meshtastic_Config_DisplayConfig_GpsCoordinateFormat_UTM;
+            service->reloadConfig(SEGMENT_CONFIG);
+        } else if (selected == 4) {
+            config.display.gps_format = meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MGRS;
+            service->reloadConfig(SEGMENT_CONFIG);
+        } else if (selected == 5) {
+            config.display.gps_format = meshtastic_Config_DisplayConfig_GpsCoordinateFormat_OLC;
+            service->reloadConfig(SEGMENT_CONFIG);
+        } else if (selected == 6) {
+            config.display.gps_format = meshtastic_Config_DisplayConfig_GpsCoordinateFormat_OSGR;
+            service->reloadConfig(SEGMENT_CONFIG);
+        } else {
+            menuQueue = position_base_menu;
+            screen->runNow();
+        }
+    };
+    bannerOptions.InitialSelected = config.display.gps_format + 1;
+    screen->showOverlayBanner(bannerOptions);
+}
 #endif
 
 void menuHandler::BluetoothToggleMenu()
@@ -1378,6 +1422,9 @@ void menuHandler::handleMenuSwitch(OLEDDisplay *display)
     case gps_toggle_menu:
         GPSToggleMenu();
         break;
+    case gps_format_menu:
+        GPSFormatMenu();
+        break;
 #endif
     case compass_point_north_menu:
         compassNorthMenu();
diff --git a/src/graphics/draw/MenuHandler.h b/src/graphics/draw/MenuHandler.h
index 2be8e58a6..e8a2904a0 100644
--- a/src/graphics/draw/MenuHandler.h
+++ b/src/graphics/draw/MenuHandler.h
@@ -19,6 +19,7 @@ class menuHandler
         clock_menu,
         position_base_menu,
         gps_toggle_menu,
+        gps_format_menu,
         compass_point_north_menu,
         reset_node_db_menu,
         buzzermodemenupicker,
@@ -63,6 +64,7 @@ class menuHandler
     static void positionBaseMenu();
     static void compassNorthMenu();
     static void GPSToggleMenu();
+    static void GPSFormatMenu();
     static void BuzzerModeMenu();
     static void switchToMUIMenu();
     static void TFTColorPickerMenu(OLEDDisplay *display);
diff --git a/src/graphics/draw/UIRenderer.cpp b/src/graphics/draw/UIRenderer.cpp
index e9da66712..2a7f3aeee 100644
--- a/src/graphics/draw/UIRenderer.cpp
+++ b/src/graphics/draw/UIRenderer.cpp
@@ -116,64 +116,78 @@ void UIRenderer::drawGpsAltitude(OLEDDisplay *display, int16_t x, int16_t y, con
 }
 
 // Draw GPS status coordinates
-void UIRenderer::drawGpsCoordinates(OLEDDisplay *display, int16_t x, int16_t y, const meshtastic::GPSStatus *gps)
+void UIRenderer::drawGpsCoordinates(OLEDDisplay *display, int16_t x, int16_t y, const meshtastic::GPSStatus *gps,
+                                    const char *mode)
 {
     auto gpsFormat = config.display.gps_format;
     char displayLine[32];
 
     if (!gps->getIsConnected() && !config.position.fixed_position) {
         strcpy(displayLine, "No GPS present");
-        display->drawString(x + (display->getWidth() - (display->getStringWidth(displayLine))) / 2, y, displayLine);
+        display->drawString(x, y, displayLine);
     } else if (!gps->getHasLock() && !config.position.fixed_position) {
         strcpy(displayLine, "No GPS Lock");
-        display->drawString(x + (display->getWidth() - (display->getStringWidth(displayLine))) / 2, y, displayLine);
+        display->drawString(x, y, displayLine);
     } else {
 
         geoCoord.updateCoords(int32_t(gps->getLatitude()), int32_t(gps->getLongitude()), int32_t(gps->getAltitude()));
 
         if (gpsFormat != meshtastic_Config_DisplayConfig_GpsCoordinateFormat_DMS) {
-            char coordinateLine[22];
+            char coordinateLine_1[22];
+            char coordinateLine_2[22];
             if (gpsFormat == meshtastic_Config_DisplayConfig_GpsCoordinateFormat_DEC) { // Decimal Degrees
-                snprintf(coordinateLine, sizeof(coordinateLine), "%f %f", geoCoord.getLatitude() * 1e-7,
-                         geoCoord.getLongitude() * 1e-7);
+                snprintf(coordinateLine_1, sizeof(coordinateLine_1), "Lat: %f", geoCoord.getLatitude() * 1e-7);
+                snprintf(coordinateLine_2, sizeof(coordinateLine_2), "Lon: %f", geoCoord.getLongitude() * 1e-7);
             } else if (gpsFormat == meshtastic_Config_DisplayConfig_GpsCoordinateFormat_UTM) { // Universal Transverse Mercator
-                snprintf(coordinateLine, sizeof(coordinateLine), "%2i%1c %06u %07u", geoCoord.getUTMZone(), geoCoord.getUTMBand(),
-                         geoCoord.getUTMEasting(), geoCoord.getUTMNorthing());
+                snprintf(coordinateLine_1, sizeof(coordinateLine_1), "%2i%1c %06u E", geoCoord.getUTMZone(),
+                         geoCoord.getUTMBand(), geoCoord.getUTMEasting());
+                snprintf(coordinateLine_2, sizeof(coordinateLine_2), "%07u N", geoCoord.getUTMNorthing());
             } else if (gpsFormat == meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MGRS) { // Military Grid Reference System
-                snprintf(coordinateLine, sizeof(coordinateLine), "%2i%1c %1c%1c %05u %05u", geoCoord.getMGRSZone(),
-                         geoCoord.getMGRSBand(), geoCoord.getMGRSEast100k(), geoCoord.getMGRSNorth100k(),
-                         geoCoord.getMGRSEasting(), geoCoord.getMGRSNorthing());
+                snprintf(coordinateLine_1, sizeof(coordinateLine_1), "%2i%1c %1c%1c", geoCoord.getMGRSZone(),
+                         geoCoord.getMGRSBand(), geoCoord.getMGRSEast100k(), geoCoord.getMGRSNorth100k());
+                snprintf(coordinateLine_2, sizeof(coordinateLine_2), "%05u E %05u N", geoCoord.getMGRSEasting(),
+                         geoCoord.getMGRSNorthing());
             } else if (gpsFormat == meshtastic_Config_DisplayConfig_GpsCoordinateFormat_OLC) { // Open Location Code
-                geoCoord.getOLCCode(coordinateLine);
+                geoCoord.getOLCCode(coordinateLine_1);
+                coordinateLine_2[0] = '\0';
             } else if (gpsFormat == meshtastic_Config_DisplayConfig_GpsCoordinateFormat_OSGR) { // Ordnance Survey Grid Reference
-                if (geoCoord.getOSGRE100k() == 'I' || geoCoord.getOSGRN100k() == 'I') // OSGR is only valid around the UK region
-                    snprintf(coordinateLine, sizeof(coordinateLine), "%s", "Out of Boundary");
-                else
-                    snprintf(coordinateLine, sizeof(coordinateLine), "%1c%1c %05u %05u", geoCoord.getOSGRE100k(),
-                             geoCoord.getOSGRN100k(), geoCoord.getOSGREasting(), geoCoord.getOSGRNorthing());
+                if (geoCoord.getOSGRE100k() == 'I' || geoCoord.getOSGRN100k() == 'I') { // OSGR is only valid around the UK region
+                    snprintf(coordinateLine_1, sizeof(coordinateLine_1), "%s", "Out of Boundary");
+                    coordinateLine_2[0] = '\0';
+                } else {
+                    snprintf(coordinateLine_1, sizeof(coordinateLine_1), "%1c%1c", geoCoord.getOSGRE100k(),
+                             geoCoord.getOSGRN100k());
+                    snprintf(coordinateLine_2, sizeof(coordinateLine_2), "%05u E %05u N", geoCoord.getOSGREasting(),
+                             geoCoord.getOSGRNorthing());
+                }
             }
 
-            // If fixed position, display text "Fixed GPS" alternating with the coordinates.
-            if (config.position.fixed_position) {
-                if ((millis() / 10000) % 2) {
-                    display->drawString(x + (display->getWidth() - (display->getStringWidth(coordinateLine))) / 2, y,
-                                        coordinateLine);
-                } else {
-                    display->drawString(x + (display->getWidth() - (display->getStringWidth("Fixed GPS"))) / 2, y, "Fixed GPS");
+            if (strcmp(mode, "line1") == 0) {
+                display->drawString(x, y, coordinateLine_1);
+            } else if (strcmp(mode, "line2") == 0) {
+                display->drawString(x, y, coordinateLine_2);
+            } else if (strcmp(mode, "combined") == 0) {
+                display->drawString(x, y, coordinateLine_1);
+                if (coordinateLine_2[0] != '\0') {
+                    display->drawString(x + display->getStringWidth(coordinateLine_1), y, coordinateLine_2);
                 }
-            } else {
-                display->drawString(x + (display->getWidth() - (display->getStringWidth(coordinateLine))) / 2, y, coordinateLine);
             }
+
         } else {
-            char latLine[22];
-            char lonLine[22];
-            snprintf(latLine, sizeof(latLine), "%2i° %2i' %2u\" %1c", geoCoord.getDMSLatDeg(), geoCoord.getDMSLatMin(),
-                     geoCoord.getDMSLatSec(), geoCoord.getDMSLatCP());
-            snprintf(lonLine, sizeof(lonLine), "%3i° %2i' %2u\" %1c", geoCoord.getDMSLonDeg(), geoCoord.getDMSLonMin(),
-                     geoCoord.getDMSLonSec(), geoCoord.getDMSLonCP());
-            display->drawString(x + (display->getWidth() - (display->getStringWidth(latLine))) / 2, y - FONT_HEIGHT_SMALL * 1,
-                                latLine);
-            display->drawString(x + (display->getWidth() - (display->getStringWidth(lonLine))) / 2, y, lonLine);
+            char coordinateLine_1[22];
+            char coordinateLine_2[22];
+            snprintf(coordinateLine_1, sizeof(coordinateLine_1), "Lat: %2i° %2i' %2u\" %1c", geoCoord.getDMSLatDeg(),
+                     geoCoord.getDMSLatMin(), geoCoord.getDMSLatSec(), geoCoord.getDMSLatCP());
+            snprintf(coordinateLine_2, sizeof(coordinateLine_2), "Lon: %3i° %2i' %2u\" %1c", geoCoord.getDMSLonDeg(),
+                     geoCoord.getDMSLonMin(), geoCoord.getDMSLonSec(), geoCoord.getDMSLonCP());
+            if (strcmp(mode, "line1") == 0) {
+                display->drawString(x, y, coordinateLine_1);
+            } else if (strcmp(mode, "line2") == 0) {
+                display->drawString(x, y, coordinateLine_2);
+            } else { // both
+                display->drawString(x, y, coordinateLine_1);
+                display->drawString(x, y + 10, coordinateLine_2);
+            }
         }
     }
 }
@@ -978,17 +992,15 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU
             display->drawString(0, getTextPositions(display)[line++], "Last: ?");
         }
 
-        // === Third Row: Latitude ===
-        char latStr[32];
-        snprintf(latStr, sizeof(latStr), "Lat: %.5f", geoCoord.getLatitude() * 1e-7);
-        display->drawString(x, getTextPositions(display)[line++], latStr);
+        // === Third Row: Line 1 GPS Info ===
+        UIRenderer::drawGpsCoordinates(display, x, getTextPositions(display)[line++], gpsStatus, "line1");
 
-        // === Fourth Row: Longitude ===
-        char lonStr[32];
-        snprintf(lonStr, sizeof(lonStr), "Lon: %.5f", geoCoord.getLongitude() * 1e-7);
-        display->drawString(x, getTextPositions(display)[line++], lonStr);
+        if (config.display.gps_format != meshtastic_Config_DisplayConfig_GpsCoordinateFormat_OLC) {
+            // === Fourth Row: Line 2 GPS Info ===
+            UIRenderer::drawGpsCoordinates(display, x, getTextPositions(display)[line++], gpsStatus, "line2");
+        }
 
-        // === Fifth Row: Altitude ===
+        // === Fourth/Fifth Row: Altitude ===
         char DisplayLineTwo[32] = {0};
         int32_t alt = (strcmp(displayLine, "Phone GPS") == 0 && ourNode && nodeDB->hasValidPosition(ourNode))
                           ? ourNode->position.altitude
diff --git a/src/graphics/draw/UIRenderer.h b/src/graphics/draw/UIRenderer.h
index eada150f9..438d56cc2 100644
--- a/src/graphics/draw/UIRenderer.h
+++ b/src/graphics/draw/UIRenderer.h
@@ -38,7 +38,8 @@ class UIRenderer
 
     // GPS status functions
     static void drawGps(OLEDDisplay *display, int16_t x, int16_t y, const meshtastic::GPSStatus *gpsStatus);
-    static void drawGpsCoordinates(OLEDDisplay *display, int16_t x, int16_t y, const meshtastic::GPSStatus *gpsStatus);
+    static void drawGpsCoordinates(OLEDDisplay *display, int16_t x, int16_t y, const meshtastic::GPSStatus *gpsStatus,
+                                   const char *mode = "line1");
     static void drawGpsAltitude(OLEDDisplay *display, int16_t x, int16_t y, const meshtastic::GPSStatus *gpsStatus);
     static void drawGpsPowerStatus(OLEDDisplay *display, int16_t x, int16_t y, const meshtastic::GPSStatus *gpsStatus);
 

From 6165b4f7a9b7ad43936c338cd3d43002d0cbaae0 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Sat, 13 Sep 2025 16:31:56 -0500
Subject: [PATCH 211/691] Update meshtastic-esp8266-oled-ssd1306 digest to
 0cbc26b (#7977)

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 e2e5e1a18..47b5f823d 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -60,7 +60,7 @@ monitor_speed = 115200
 monitor_filters = direct
 lib_deps =
 	# renovate: datasource=git-refs depName=meshtastic-esp8266-oled-ssd1306 packageName=https://github.com/meshtastic/esp8266-oled-ssd1306 gitBranch=master
-	https://github.com/meshtastic/esp8266-oled-ssd1306/archive/9573abb64dc9c94f3051348f2bf4fc5cedf03c22.zip
+	https://github.com/meshtastic/esp8266-oled-ssd1306/archive/0cbc26b1f8f61957af0475f486b362eafe7cc4e2.zip
 	# renovate: datasource=git-refs depName=meshtastic-OneButton packageName=https://github.com/meshtastic/OneButton gitBranch=master
 	https://github.com/meshtastic/OneButton/archive/fa352d668c53f290cfa480a5f79ad422cd828c70.zip
 	# renovate: datasource=git-refs depName=meshtastic-arduino-fsm packageName=https://github.com/meshtastic/arduino-fsm gitBranch=master

From 760471d62021b0a02f1481e9b1cdce267c430f19 Mon Sep 17 00:00:00 2001
From: Ben Meadors 
Date: Sat, 13 Sep 2025 18:52:46 -0500
Subject: [PATCH 212/691] Fix json report crashes on esp32 (#7978)

---
 src/mesh/http/ContentHandler.cpp | 39 ++++++++++++++++++++------------
 1 file changed, 24 insertions(+), 15 deletions(-)

diff --git a/src/mesh/http/ContentHandler.cpp b/src/mesh/http/ContentHandler.cpp
index 74953d8fc..fb66dae7c 100644
--- a/src/mesh/http/ContentHandler.cpp
+++ b/src/mesh/http/ContentHandler.cpp
@@ -292,11 +292,14 @@ JSONArray htmlListDir(const char *dirname, uint8_t levels)
             JSONObject thisFileMap;
             thisFileMap["size"] = new JSONValue((int)file.size());
 #ifdef ARCH_ESP32
-            thisFileMap["name"] = new JSONValue(String(file.path()).substring(1).c_str());
+            String fileName = String(file.path()).substring(1);
+            thisFileMap["name"] = new JSONValue(fileName.c_str());
 #else
-            thisFileMap["name"] = new JSONValue(String(file.name()).substring(1).c_str());
+            String fileName = String(file.name()).substring(1);
+            thisFileMap["name"] = new JSONValue(fileName.c_str());
 #endif
-            if (String(file.name()).substring(1).endsWith(".gz")) {
+            String tempName = String(file.name()).substring(1);
+            if (tempName.endsWith(".gz")) {
 #ifdef ARCH_ESP32
                 String modifiedFile = String(file.path()).substring(1);
 #else
@@ -339,7 +342,8 @@ void handleFsBrowseStatic(HTTPRequest *req, HTTPResponse *res)
 
     JSONValue *value = new JSONValue(jsonObjOuter);
 
-    res->print(value->Stringify().c_str());
+    std::string jsonString = value->Stringify();
+    res->print(jsonString.c_str());
 
     delete value;
 
@@ -367,7 +371,8 @@ void handleFsDeleteStatic(HTTPRequest *req, HTTPResponse *res)
             JSONObject jsonObjOuter;
             jsonObjOuter["status"] = new JSONValue("ok");
             JSONValue *value = new JSONValue(jsonObjOuter);
-            res->print(value->Stringify().c_str());
+            std::string jsonString = value->Stringify();
+            res->print(jsonString.c_str());
             delete value;
             return;
         } else {
@@ -376,7 +381,8 @@ void handleFsDeleteStatic(HTTPRequest *req, HTTPResponse *res)
             JSONObject jsonObjOuter;
             jsonObjOuter["status"] = new JSONValue("Error");
             JSONValue *value = new JSONValue(jsonObjOuter);
-            res->print(value->Stringify().c_str());
+            std::string jsonString = value->Stringify();
+            res->print(jsonString.c_str());
             delete value;
             return;
         }
@@ -622,10 +628,7 @@ void handleReport(HTTPRequest *req, HTTPResponse *res)
             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;
-        }
+        // Note: Don't delete tempArray elements here - JSONValue now owns them
         return result;
     };
 
@@ -656,7 +659,9 @@ void handleReport(HTTPRequest *req, HTTPResponse *res)
     // data->wifi
     JSONObject jsonObjWifi;
     jsonObjWifi["rssi"] = new JSONValue(WiFi.RSSI());
-    jsonObjWifi["ip"] = new JSONValue(WiFi.localIP().toString().c_str());
+    String wifiIPString = WiFi.localIP().toString();
+    std::string wifiIP = wifiIPString.c_str();
+    jsonObjWifi["ip"] = new JSONValue(wifiIP.c_str());
 
     // data->memory
     JSONObject jsonObjMemory;
@@ -702,7 +707,8 @@ void handleReport(HTTPRequest *req, HTTPResponse *res)
     jsonObjOuter["status"] = new JSONValue("ok");
     // serialize and write it to the stream
     JSONValue *value = new JSONValue(jsonObjOuter);
-    res->print(value->Stringify().c_str());
+    std::string jsonString = value->Stringify();
+    res->print(jsonString.c_str());
     delete value;
 }
 
@@ -773,7 +779,8 @@ void handleNodes(HTTPRequest *req, HTTPResponse *res)
     jsonObjOuter["status"] = new JSONValue("ok");
     // serialize and write it to the stream
     JSONValue *value = new JSONValue(jsonObjOuter);
-    res->print(value->Stringify().c_str());
+    std::string jsonString = value->Stringify();
+    res->print(jsonString.c_str());
     delete value;
 
     // Clean up the nodesArray to prevent memory leak
@@ -926,7 +933,8 @@ void handleBlinkLED(HTTPRequest *req, HTTPResponse *res)
     JSONObject jsonObjOuter;
     jsonObjOuter["status"] = new JSONValue("ok");
     JSONValue *value = new JSONValue(jsonObjOuter);
-    res->print(value->Stringify().c_str());
+    std::string jsonString = value->Stringify();
+    res->print(jsonString.c_str());
     delete value;
 }
 
@@ -968,7 +976,8 @@ void handleScanNetworks(HTTPRequest *req, HTTPResponse *res)
 
     // serialize and write it to the stream
     JSONValue *value = new JSONValue(jsonObjOuter);
-    res->print(value->Stringify().c_str());
+    std::string jsonString = value->Stringify();
+    res->print(jsonString.c_str());
     delete value;
 
     // Clean up the networkObjs to prevent memory leak

From 096afa07f8bda37629a9ba1eafc51cde5890c2ea Mon Sep 17 00:00:00 2001
From: Ben Meadors 
Date: Sat, 13 Sep 2025 18:57:00 -0500
Subject: [PATCH 213/691] Tweak maximums

---
 src/mesh/mesh-pb-constants.h | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/mesh/mesh-pb-constants.h b/src/mesh/mesh-pb-constants.h
index 12aec98cd..868670f42 100644
--- a/src/mesh/mesh-pb-constants.h
+++ b/src/mesh/mesh-pb-constants.h
@@ -20,12 +20,12 @@
 
 /// max number of QueueStatus packets which can be waiting for delivery to phone
 #ifndef MAX_RX_QUEUESTATUS_TOPHONE
-#define MAX_RX_QUEUESTATUS_TOPHONE 4
+#define MAX_RX_QUEUESTATUS_TOPHONE 2
 #endif
 
 /// max number of MqttClientProxyMessage packets which can be waiting for delivery to phone
 #ifndef MAX_RX_MQTTPROXY_TOPHONE
-#define MAX_RX_MQTTPROXY_TOPHONE 32
+#define MAX_RX_MQTTPROXY_TOPHONE 16
 #endif
 
 /// max number of ClientNotification packets which can be waiting for delivery to phone

From 99770354995c9b463fbf570993b7e67289f65dc9 Mon Sep 17 00:00:00 2001
From: Ben Meadors 
Date: Sat, 13 Sep 2025 20:14:10 -0500
Subject: [PATCH 214/691] Fix DRAM overflow on old esp32 targets

---
 src/mesh/mesh-pb-constants.h | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/src/mesh/mesh-pb-constants.h b/src/mesh/mesh-pb-constants.h
index 868670f42..e4f65aa28 100644
--- a/src/mesh/mesh-pb-constants.h
+++ b/src/mesh/mesh-pb-constants.h
@@ -15,8 +15,12 @@
 // FIXME - max_count is actually 32 but we save/load this as one long string of preencoded MeshPacket bytes - not a big array in
 // RAM #define MAX_RX_TOPHONE (member_size(DeviceState, receive_queue) / member_size(DeviceState, receive_queue[0]))
 #ifndef MAX_RX_TOPHONE
+#if defined(ARCH_ESP32) && !(defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32S3))
+#define MAX_RX_TOPHONE 8
+#else
 #define MAX_RX_TOPHONE 32
 #endif
+#endif
 
 /// max number of QueueStatus packets which can be waiting for delivery to phone
 #ifndef MAX_RX_QUEUESTATUS_TOPHONE
@@ -25,7 +29,7 @@
 
 /// max number of MqttClientProxyMessage packets which can be waiting for delivery to phone
 #ifndef MAX_RX_MQTTPROXY_TOPHONE
-#define MAX_RX_MQTTPROXY_TOPHONE 16
+#define MAX_RX_MQTTPROXY_TOPHONE 8
 #endif
 
 /// max number of ClientNotification packets which can be waiting for delivery to phone

From d201f6a1ed07bf3b159cfdfdc29a230c7f0c10dc Mon Sep 17 00:00:00 2001
From: Tom Fifield 
Date: Mon, 8 Sep 2025 10:29:26 +1000
Subject: [PATCH 215/691] 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 00772996b69b2b5ab614564e861a1587f4d5058b Mon Sep 17 00:00:00 2001
From: Mike Robbins 
Date: Sun, 14 Sep 2025 03:05:06 -0700
Subject: [PATCH 216/691] Fix GPS gm_mktime memory leak (#7981)

---
 src/gps/RTC.cpp | 40 +++++++++++++++++++++++++++++++++-------
 1 file changed, 33 insertions(+), 7 deletions(-)

diff --git a/src/gps/RTC.cpp b/src/gps/RTC.cpp
index 39b633e47..e75102deb 100644
--- a/src/gps/RTC.cpp
+++ b/src/gps/RTC.cpp
@@ -324,14 +324,40 @@ uint32_t getValidTime(RTCQuality minQuality, bool local)
 time_t gm_mktime(struct tm *tm)
 {
 #if !MESHTASTIC_EXCLUDE_TZ
-    setenv("TZ", "GMT0", 1);
-    time_t res = mktime(tm);
-    if (*config.device.tzdef) {
-        setenv("TZ", config.device.tzdef, 1);
-    } else {
-        setenv("TZ", "UTC0", 1);
+    time_t result = 0;
+
+    // First, get us to the start of tm->year, by calcuating the number of days since the Unix epoch.
+    int year = 1900 + tm->tm_year; // tm_year is years since 1900
+    int year_minus_one = year - 1;
+    int days_before_this_year = 0;
+    days_before_this_year += year_minus_one * 365;
+    // leap days: every 4 years, except 100s, but including 400s.
+    days_before_this_year += year_minus_one / 4 - year_minus_one / 100 + year_minus_one / 400;
+    // subtract from 1970-01-01 to get days since epoch
+    days_before_this_year -= 719162; // (1969 * 365 + 1969 / 4 - 1969 / 100 + 1969 / 400);
+
+    // Now, within this tm->year, compute the days *before* this tm->month starts.
+    int days_before_month[12] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334}; // non-leap year
+    int days_this_year_before_this_month = days_before_month[tm->tm_mon];                // tm->tm_mon is 0..11
+
+    // If this is a leap year, and we're past February, add a day:
+    if (tm->tm_mon >= 2 && (year % 4) == 0 && ((year % 100) != 0 || (year % 400) == 0)) {
+        days_this_year_before_this_month += 1;
     }
-    return res;
+
+    // And within this month:
+    int days_this_month_before_today = tm->tm_mday - 1; // tm->tm_mday is 1..31
+
+    // Now combine them all together, and convert days to seconds:
+    result += (days_before_this_year + days_this_year_before_this_month + days_this_month_before_today);
+    result *= 86400L;
+
+    // Finally, add in the hours, minutes, and seconds of today:
+    result += tm->tm_hour * 3600;
+    result += tm->tm_min * 60;
+    result += tm->tm_sec;
+
+    return result;
 #else
     return mktime(tm);
 #endif

From 2dc7760508360732be6605d708b5f046530f2be7 Mon Sep 17 00:00:00 2001
From: Ben Meadors 
Date: Sun, 14 Sep 2025 06:31:17 -0500
Subject: [PATCH 217/691] Scale probe buffer size based on current baud rate
 (#7975)

* Scale probe buffer size based on current baud rate

* Throttle bad time validation logging and fix time comparison logic

* Remove comment

* Missed the other instances

* Copy pasta
---
 src/gps/GPS.cpp | 19 +++++++++++++----
 src/gps/GPS.h   |  2 +-
 src/gps/RTC.cpp | 54 ++++++++++++++++++++++++++++++++-----------------
 src/gps/RTC.h   |  2 +-
 4 files changed, 53 insertions(+), 24 deletions(-)

diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp
index d4e9076d9..a663f46c4 100644
--- a/src/gps/GPS.cpp
+++ b/src/gps/GPS.cpp
@@ -1205,7 +1205,7 @@ static const char *DETECTED_MESSAGE = "%s detected";
         LOG_DEBUG(PROBE_MESSAGE, COMMAND, FAMILY_NAME);                                                                          \
         clearBuffer();                                                                                                           \
         _serial_gps->write(COMMAND "\r\n");                                                                                      \
-        GnssModel_t detectedDriver = getProbeResponse(TIMEOUT, RESPONSE_MAP);                                                    \
+        GnssModel_t detectedDriver = getProbeResponse(TIMEOUT, RESPONSE_MAP, serialSpeed);                                       \
         if (detectedDriver != GNSS_MODEL_UNKNOWN) {                                                                              \
             return detectedDriver;                                                                                               \
         }                                                                                                                        \
@@ -1367,9 +1367,18 @@ GnssModel_t GPS::probe(int serialSpeed)
     return GNSS_MODEL_UNKNOWN;
 }
 
-GnssModel_t GPS::getProbeResponse(unsigned long timeout, const std::vector &responseMap)
+GnssModel_t GPS::getProbeResponse(unsigned long timeout, const std::vector &responseMap, int serialSpeed)
 {
-    char response[256] = {0}; // Fixed buffer instead of String
+    // Calculate buffer size based on baud rate - 256 bytes for 9600 baud as baseline
+    // Higher baud rates get proportionally larger buffers to handle more data
+    int bufferSize = (serialSpeed * 256) / 9600;
+    // Clamp buffer size between reasonable limits
+    if (bufferSize < 128)
+        bufferSize = 128;
+    if (bufferSize > 2048)
+        bufferSize = 2048;
+
+    char *response = new char[bufferSize](); // Dynamically allocate based on baud rate
     uint16_t responseLen = 0;
     unsigned long start = millis();
     while (millis() - start < timeout) {
@@ -1377,7 +1386,7 @@ GnssModel_t GPS::getProbeResponse(unsigned long timeout, const std::vectorread();
 
             // Add char to buffer if there's space
-            if (responseLen < sizeof(response) - 1) {
+            if (responseLen < bufferSize - 1) {
                 response[responseLen++] = c;
                 response[responseLen] = '\0';
             }
@@ -1390,6 +1399,7 @@ GnssModel_t GPS::getProbeResponse(unsigned long timeout, const std::vector &responseMap);
+    GnssModel_t getProbeResponse(unsigned long timeout, const std::vector &responseMap, int serialSpeed);
 
     // Get GNSS model
     GnssModel_t probe(int serialSpeed);
diff --git a/src/gps/RTC.cpp b/src/gps/RTC.cpp
index 39b633e47..3e410d236 100644
--- a/src/gps/RTC.cpp
+++ b/src/gps/RTC.cpp
@@ -9,6 +9,9 @@
 static RTCQuality currentQuality = RTCQualityNone;
 uint32_t lastSetFromPhoneNtpOrGps = 0;
 
+static uint32_t lastTimeValidationWarning = 0;
+static const uint32_t TIME_VALIDATION_WARNING_INTERVAL_MS = 15000; // 15 seconds
+
 RTCQuality getRTCQuality()
 {
     return currentQuality;
@@ -48,7 +51,9 @@ RTCSetResult readFromRTC()
 
 #ifdef BUILD_EPOCH
         if (tv.tv_sec < BUILD_EPOCH) {
-            LOG_WARN("Ignore time (%ld) before build epoch (%ld)!", printableEpoch, BUILD_EPOCH);
+            if (Throttle::isWithinTimespanMs(lastTimeValidationWarning, TIME_VALIDATION_WARNING_INTERVAL_MS) == false) {
+                LOG_WARN("Ignore time (%ld) before build epoch (%ld)!", printableEpoch, BUILD_EPOCH);
+            }
             return RTCSetResultInvalidTime;
         }
 #endif
@@ -87,7 +92,10 @@ RTCSetResult readFromRTC()
 
 #ifdef BUILD_EPOCH
         if (tv.tv_sec < BUILD_EPOCH) {
-            LOG_WARN("Ignore time (%ld) before build epoch (%ld)!", printableEpoch, BUILD_EPOCH);
+            if (Throttle::isWithinTimespanMs(lastTimeValidationWarning, TIME_VALIDATION_WARNING_INTERVAL_MS) == false) {
+                LOG_WARN("Ignore time (%ld) before build epoch (%ld)!", printableEpoch, BUILD_EPOCH);
+                lastTimeValidationWarning = millis();
+            }
             return RTCSetResultInvalidTime;
         }
 #endif
@@ -130,15 +138,20 @@ 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
+        if (Throttle::isWithinTimespanMs(lastTimeValidationWarning, TIME_VALIDATION_WARNING_INTERVAL_MS) == false) {
+            LOG_WARN("Ignore time (%ld) before build epoch (%ld)!", printableEpoch, BUILD_EPOCH);
+            lastTimeValidationWarning = millis();
+        }
         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
+    } else if (tv->tv_sec > (time_t)(BUILD_EPOCH + FORTY_YEARS)) {
+        if (Throttle::isWithinTimespanMs(lastTimeValidationWarning, TIME_VALIDATION_WARNING_INTERVAL_MS) == false) {
+            // Calculate max allowed time safely to avoid overflow in logging
+            uint64_t maxAllowedTime = (uint64_t)BUILD_EPOCH + FORTY_YEARS;
+            uint32_t maxAllowedPrintable = (maxAllowedTime > UINT32_MAX) ? UINT32_MAX : (uint32_t)maxAllowedTime;
+            LOG_WARN("Ignore time (%ld) too far in the future (build epoch: %ld, max allowed: %ld)!", printableEpoch,
+                     (uint32_t)BUILD_EPOCH, maxAllowedPrintable);
+            lastTimeValidationWarning = millis();
+        }
         return RTCSetResultInvalidTime;
     }
 #endif
@@ -256,15 +269,20 @@ 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
+        if (Throttle::isWithinTimespanMs(lastTimeValidationWarning, TIME_VALIDATION_WARNING_INTERVAL_MS) == false) {
+            LOG_WARN("Ignore time (%ld) before build epoch (%ld)!", printableEpoch, BUILD_EPOCH);
+            lastTimeValidationWarning = millis();
+        }
         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
+    } else if (tv.tv_sec > (time_t)(BUILD_EPOCH + FORTY_YEARS)) {
+        if (Throttle::isWithinTimespanMs(lastTimeValidationWarning, TIME_VALIDATION_WARNING_INTERVAL_MS) == false) {
+            // Calculate max allowed time safely to avoid overflow in logging
+            uint64_t maxAllowedTime = (uint64_t)BUILD_EPOCH + FORTY_YEARS;
+            uint32_t maxAllowedPrintable = (maxAllowedTime > UINT32_MAX) ? UINT32_MAX : (uint32_t)maxAllowedTime;
+            LOG_WARN("Ignore time (%ld) too far in the future (build epoch: %ld, max allowed: %ld)!", printableEpoch,
+                     (uint32_t)BUILD_EPOCH, maxAllowedPrintable);
+            lastTimeValidationWarning = millis();
+        }
         return RTCSetResultInvalidTime;
     }
 #endif
diff --git a/src/gps/RTC.h b/src/gps/RTC.h
index 03350823c..1ecde79ae 100644
--- a/src/gps/RTC.h
+++ b/src/gps/RTC.h
@@ -56,5 +56,5 @@ time_t gm_mktime(struct tm *tm);
 #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
+#define FORTY_YEARS (40ULL * 365 * SEC_PER_DAY) // Use 64-bit arithmetic to prevent overflow
 #endif

From bf4e2e8e866c2f522f2e8f24ad14bb76f356fd7f Mon Sep 17 00:00:00 2001
From: Mike Robbins 
Date: Sun, 14 Sep 2025 03:05:06 -0700
Subject: [PATCH 218/691] Fix GPS gm_mktime memory leak (#7981)

---
 src/gps/RTC.cpp | 40 +++++++++++++++++++++++++++++++++-------
 1 file changed, 33 insertions(+), 7 deletions(-)

diff --git a/src/gps/RTC.cpp b/src/gps/RTC.cpp
index 3e410d236..4a629d755 100644
--- a/src/gps/RTC.cpp
+++ b/src/gps/RTC.cpp
@@ -342,14 +342,40 @@ uint32_t getValidTime(RTCQuality minQuality, bool local)
 time_t gm_mktime(struct tm *tm)
 {
 #if !MESHTASTIC_EXCLUDE_TZ
-    setenv("TZ", "GMT0", 1);
-    time_t res = mktime(tm);
-    if (*config.device.tzdef) {
-        setenv("TZ", config.device.tzdef, 1);
-    } else {
-        setenv("TZ", "UTC0", 1);
+    time_t result = 0;
+
+    // First, get us to the start of tm->year, by calcuating the number of days since the Unix epoch.
+    int year = 1900 + tm->tm_year; // tm_year is years since 1900
+    int year_minus_one = year - 1;
+    int days_before_this_year = 0;
+    days_before_this_year += year_minus_one * 365;
+    // leap days: every 4 years, except 100s, but including 400s.
+    days_before_this_year += year_minus_one / 4 - year_minus_one / 100 + year_minus_one / 400;
+    // subtract from 1970-01-01 to get days since epoch
+    days_before_this_year -= 719162; // (1969 * 365 + 1969 / 4 - 1969 / 100 + 1969 / 400);
+
+    // Now, within this tm->year, compute the days *before* this tm->month starts.
+    int days_before_month[12] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334}; // non-leap year
+    int days_this_year_before_this_month = days_before_month[tm->tm_mon];                // tm->tm_mon is 0..11
+
+    // If this is a leap year, and we're past February, add a day:
+    if (tm->tm_mon >= 2 && (year % 4) == 0 && ((year % 100) != 0 || (year % 400) == 0)) {
+        days_this_year_before_this_month += 1;
     }
-    return res;
+
+    // And within this month:
+    int days_this_month_before_today = tm->tm_mday - 1; // tm->tm_mday is 1..31
+
+    // Now combine them all together, and convert days to seconds:
+    result += (days_before_this_year + days_this_year_before_this_month + days_this_month_before_today);
+    result *= 86400L;
+
+    // Finally, add in the hours, minutes, and seconds of today:
+    result += tm->tm_hour * 3600;
+    result += tm->tm_min * 60;
+    result += tm->tm_sec;
+
+    return result;
 #else
     return mktime(tm);
 #endif

From 70724bef72684c96f8a6d2972d80d37648eb8de4 Mon Sep 17 00:00:00 2001
From: Ben Meadors 
Date: Sun, 14 Sep 2025 08:12:38 -0500
Subject: [PATCH 219/691] Fix overflow of time value (#7984)

* Fix overflow of time value

* Revert "Fix overflow of time value"

This reverts commit 084796920179e80a7500d36c25fd4d82b3ef4214.

* That got boogered up
---
 src/gps/RTC.cpp | 4 ++--
 src/gps/RTC.h   | 2 +-
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/src/gps/RTC.cpp b/src/gps/RTC.cpp
index 4a629d755..da20e28eb 100644
--- a/src/gps/RTC.cpp
+++ b/src/gps/RTC.cpp
@@ -143,7 +143,7 @@ RTCSetResult perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpd
             lastTimeValidationWarning = millis();
         }
         return RTCSetResultInvalidTime;
-    } else if (tv->tv_sec > (time_t)(BUILD_EPOCH + FORTY_YEARS)) {
+    } else if ((uint64_t)tv->tv_sec > ((uint64_t)BUILD_EPOCH + FORTY_YEARS)) {
         if (Throttle::isWithinTimespanMs(lastTimeValidationWarning, TIME_VALIDATION_WARNING_INTERVAL_MS) == false) {
             // Calculate max allowed time safely to avoid overflow in logging
             uint64_t maxAllowedTime = (uint64_t)BUILD_EPOCH + FORTY_YEARS;
@@ -274,7 +274,7 @@ RTCSetResult perhapsSetRTC(RTCQuality q, struct tm &t)
             lastTimeValidationWarning = millis();
         }
         return RTCSetResultInvalidTime;
-    } else if (tv.tv_sec > (time_t)(BUILD_EPOCH + FORTY_YEARS)) {
+    } else if ((uint64_t)tv.tv_sec > ((uint64_t)BUILD_EPOCH + FORTY_YEARS)) {
         if (Throttle::isWithinTimespanMs(lastTimeValidationWarning, TIME_VALIDATION_WARNING_INTERVAL_MS) == false) {
             // Calculate max allowed time safely to avoid overflow in logging
             uint64_t maxAllowedTime = (uint64_t)BUILD_EPOCH + FORTY_YEARS;
diff --git a/src/gps/RTC.h b/src/gps/RTC.h
index 1ecde79ae..eca17bf35 100644
--- a/src/gps/RTC.h
+++ b/src/gps/RTC.h
@@ -56,5 +56,5 @@ time_t gm_mktime(struct tm *tm);
 #define SEC_PER_HOUR 3600
 #define SEC_PER_MIN 60
 #ifdef BUILD_EPOCH
-#define FORTY_YEARS (40ULL * 365 * SEC_PER_DAY) // Use 64-bit arithmetic to prevent overflow
+static constexpr uint64_t FORTY_YEARS = (40ULL * 365 * SEC_PER_DAY); // Use 64-bit arithmetic to prevent overflow
 #endif

From 3d86c99c259a91da4c32d832b8f290a9ee6534b8 Mon Sep 17 00:00:00 2001
From: WillyJL 
Date: Fri, 12 Sep 2025 05:53:35 +0200
Subject: [PATCH 220/691] T-Lora Pager: Interrupt based rotary encoder

---
 src/input/RotaryEncoderImpl.cpp             | 65 +++++++++++++++------
 src/input/RotaryEncoderImpl.h               |  7 +++
 variants/esp32s3/tlora-pager/platformio.ini |  4 +-
 3 files changed, 55 insertions(+), 21 deletions(-)

diff --git a/src/input/RotaryEncoderImpl.cpp b/src/input/RotaryEncoderImpl.cpp
index 7d638dd71..cede1b87c 100644
--- a/src/input/RotaryEncoderImpl.cpp
+++ b/src/input/RotaryEncoderImpl.cpp
@@ -6,6 +6,8 @@
 
 #define ORIGIN_NAME "RotaryEncoder"
 
+#define ROTARY_INTERRUPT_FLAG _BV(0)
+
 RotaryEncoderImpl *rotaryEncoderImpl;
 
 RotaryEncoderImpl::RotaryEncoderImpl() : concurrency::OSThread(ORIGIN_NAME), originName(ORIGIN_NAME)
@@ -30,6 +32,17 @@ bool RotaryEncoderImpl::init()
                                moduleConfig.canned_message.inputbroker_pin_press);
     rotary->resetButton();
 
+    inputQueue = xQueueCreate(5, sizeof(input_broker_event));
+    interruptFlag = xEventGroupCreate();
+    interruptInstance = this;
+    auto interruptHandler = []() {
+        xEventGroupSetBits(interruptInstance->interruptFlag, ROTARY_INTERRUPT_FLAG);
+    };
+    attachInterrupt(moduleConfig.canned_message.inputbroker_pin_a, interruptHandler, CHANGE);
+    attachInterrupt(moduleConfig.canned_message.inputbroker_pin_b, interruptHandler, CHANGE);
+    attachInterrupt(moduleConfig.canned_message.inputbroker_pin_press, interruptHandler, CHANGE);
+    xTaskCreate(inputWorker, "rotary", 2 * 1024, this, 10, &inputWorkerTask);
+
     inputBroker->registerSource(this);
 
     LOG_INFO("RotaryEncoder initialized pins(%d, %d, %d), events(%d, %d, %d)", moduleConfig.canned_message.inputbroker_pin_a,
@@ -38,35 +51,49 @@ bool RotaryEncoderImpl::init()
     return true;
 }
 
-int32_t RotaryEncoderImpl::runOnce()
+void RotaryEncoderImpl::dispatchInputs()
 {
-    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()) {
-            LOG_DEBUG("Rotary event Press");
+            // LOG_DEBUG("Rotary event Press");
             lastPressed = millis();
-            e.inputEvent = this->eventPressed;
-        }
-    } else {
-        switch (rotary->process()) {
-        case RotaryEncoder::DIRECTION_CW:
-            LOG_DEBUG("Rotary event CW");
-            e.inputEvent = this->eventCw;
-            break;
-        case RotaryEncoder::DIRECTION_CCW:
-            LOG_DEBUG("Rotary event CCW");
-            e.inputEvent = this->eventCcw;
-            break;
-        default:
-            break;
+            xQueueSend(inputQueue, &this->eventPressed, portMAX_DELAY);
         }
     }
 
-    if (e.inputEvent != INPUT_BROKER_NONE) {
+    switch (rotary->process()) {
+    case RotaryEncoder::DIRECTION_CW:
+        // LOG_DEBUG("Rotary event CW");
+        xQueueSend(inputQueue, &this->eventCw, portMAX_DELAY);
+        break;
+    case RotaryEncoder::DIRECTION_CCW:
+        // LOG_DEBUG("Rotary event CCW");
+        xQueueSend(inputQueue, &this->eventCcw, portMAX_DELAY);
+        break;
+    default:
+        break;
+    }
+}
+
+void RotaryEncoderImpl::inputWorker(void *p)
+{
+    RotaryEncoderImpl* instance = (RotaryEncoderImpl*)p;
+    while (true) {
+        xEventGroupWaitBits(instance->interruptFlag, ROTARY_INTERRUPT_FLAG, pdTRUE, pdTRUE, portMAX_DELAY);
+        instance->dispatchInputs();
+    }
+    vTaskDelete(NULL);
+}
+
+RotaryEncoderImpl* RotaryEncoderImpl::interruptInstance;
+
+int32_t RotaryEncoderImpl::runOnce()
+{
+    InputEvent e{originName, INPUT_BROKER_NONE, 0, 0, 0};
+    while(xQueueReceive(inputQueue, &e.inputEvent, 0) == pdPASS) {
         this->notifyObservers(&e);
     }
-
     return 10;
 }
 
diff --git a/src/input/RotaryEncoderImpl.h b/src/input/RotaryEncoderImpl.h
index ae2a7c6fd..1d92617d5 100644
--- a/src/input/RotaryEncoderImpl.h
+++ b/src/input/RotaryEncoderImpl.h
@@ -17,6 +17,13 @@ class RotaryEncoderImpl : public Observable, public concurre
   protected:
     virtual int32_t runOnce() override;
 
+    QueueHandle_t inputQueue;
+    void dispatchInputs(void);
+    TaskHandle_t inputWorkerTask;
+    static void inputWorker(void *p);
+    EventGroupHandle_t  interruptFlag;
+    static RotaryEncoderImpl* interruptInstance;
+
     input_broker_event eventCw = INPUT_BROKER_NONE;
     input_broker_event eventCcw = INPUT_BROKER_NONE;
     input_broker_event eventPressed = INPUT_BROKER_NONE;
diff --git a/variants/esp32s3/tlora-pager/platformio.ini b/variants/esp32s3/tlora-pager/platformio.ini
index 312d46259..9800161bb 100644
--- a/variants/esp32s3/tlora-pager/platformio.ini
+++ b/variants/esp32s3/tlora-pager/platformio.ini
@@ -15,7 +15,7 @@ build_flags = ${esp32s3_base.build_flags}
   -D SDCARD_USE_SPI1
   -D ENABLE_ROTARY_PULLUP
   -D ENABLE_BUTTON_PULLUP
-  -D HALF_STEP
+  -D ROTARY_BUXTRONICS
 
 lib_deps = ${esp32s3_base.lib_deps}
   lovyan03/LovyanGFX@1.2.7
@@ -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/archive/25a59d5745a6645536f921427d80b08e78f886d4.zip
+  https://github.com/mverch67/RotaryEncoder/archive/da958a21389cbcd485989705df602a33e092dd88.zip
 
 [env:tlora-pager-tft]
 board_level = extra

From 42e4759634c88b5429d69e02c6a630976362a077 Mon Sep 17 00:00:00 2001
From: WillyJL 
Date: Sun, 14 Sep 2025 04:22:01 +0200
Subject: [PATCH 221/691] T-Lora Pager: Fix amplifier fuzzing/popping

---
 src/AudioThread.h | 17 +++++++++++++++++
 src/main.cpp      |  2 +-
 2 files changed, 18 insertions(+), 1 deletion(-)

diff --git a/src/AudioThread.h b/src/AudioThread.h
index 286729909..f794d3a79 100644
--- a/src/AudioThread.h
+++ b/src/AudioThread.h
@@ -11,6 +11,11 @@
 #include 
 #include 
 
+#ifdef USE_XL9555
+#include "ExtensionIOXL9555.hpp"
+extern ExtensionIOXL9555 io;
+#endif
+
 #define AUDIO_THREAD_INTERVAL_MS 100
 
 class AudioThread : public concurrency::OSThread
@@ -20,6 +25,9 @@ class AudioThread : public concurrency::OSThread
 
     void beginRttl(const void *data, uint32_t len)
     {
+#ifdef USE_XL9555
+        io.digitalWrite(EXPANDS_AMP_EN, HIGH);
+#endif
         setCPUFast(true);
         rtttlFile = new AudioFileSourcePROGMEM(data, len);
         i2sRtttl = new AudioGeneratorRTTTL();
@@ -45,6 +53,9 @@ class AudioThread : public concurrency::OSThread
         rtttlFile = nullptr;
 
         setCPUFast(false);
+#ifdef USE_XL9555
+        io.digitalWrite(EXPANDS_AMP_EN, LOW);
+#endif
     }
 
     void readAloud(const char *text)
@@ -55,10 +66,16 @@ class AudioThread : public concurrency::OSThread
             i2sRtttl = nullptr;
         }
 
+#ifdef USE_XL9555
+        io.digitalWrite(EXPANDS_AMP_EN, HIGH);
+#endif
         ESP8266SAM *sam = new ESP8266SAM;
         sam->Say(audioOut, text);
         delete sam;
         setCPUFast(false);
+#ifdef USE_XL9555
+        io.digitalWrite(EXPANDS_AMP_EN, LOW);
+#endif
     }
 
   protected:
diff --git a/src/main.cpp b/src/main.cpp
index 8d576f008..b821310ce 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -374,7 +374,7 @@ void setup()
     io.pinMode(EXPANDS_DRV_EN, OUTPUT);
     io.digitalWrite(EXPANDS_DRV_EN, HIGH);
     io.pinMode(EXPANDS_AMP_EN, OUTPUT);
-    io.digitalWrite(EXPANDS_AMP_EN, HIGH);
+    io.digitalWrite(EXPANDS_AMP_EN, LOW);
     io.pinMode(EXPANDS_LORA_EN, OUTPUT);
     io.digitalWrite(EXPANDS_LORA_EN, HIGH);
     io.pinMode(EXPANDS_GPS_EN, OUTPUT);

From 20f68929c8b6da3f8f3fc797149437c2012fc687 Mon Sep 17 00:00:00 2001
From: WillyJL 
Date: Sun, 14 Sep 2025 20:17:24 +0200
Subject: [PATCH 222/691] Fix build for other variants

---
 src/input/RotaryEncoderImpl.h | 6 +++++-
 src/modules/Modules.cpp       | 2 ++
 2 files changed, 7 insertions(+), 1 deletion(-)

diff --git a/src/input/RotaryEncoderImpl.h b/src/input/RotaryEncoderImpl.h
index 1d92617d5..4922b4333 100644
--- a/src/input/RotaryEncoderImpl.h
+++ b/src/input/RotaryEncoderImpl.h
@@ -1,6 +1,8 @@
 #pragma once
 
-// This is a non-interrupt version of RotaryEncoder which is based on a debounce inherent FSM table (see RotaryEncoder library)
+#ifdef T_LORA_PAGER
+
+// This is a version of RotaryEncoder which is based on a debounce inherent FSM table (see RotaryEncoder library)
 
 #include "InputBroker.h"
 #include "concurrency/OSThread.h"
@@ -33,3 +35,5 @@ class RotaryEncoderImpl : public Observable, public concurre
 };
 
 extern RotaryEncoderImpl *rotaryEncoderImpl;
+
+#endif
diff --git a/src/modules/Modules.cpp b/src/modules/Modules.cpp
index d4beb6824..65139fb6c 100644
--- a/src/modules/Modules.cpp
+++ b/src/modules/Modules.cpp
@@ -3,7 +3,9 @@
 #include "buzz/BuzzerFeedbackThread.h"
 #include "input/ExpressLRSFiveWay.h"
 #include "input/InputBroker.h"
+#ifdef T_LORA_PAGER
 #include "input/RotaryEncoderImpl.h"
+#endif
 #include "input/RotaryEncoderInterruptImpl1.h"
 #include "input/SerialKeyboardImpl.h"
 #include "input/UpDownInterruptImpl1.h"

From a76f59123197bdd7acfef8d6dc816d33bc7e3262 Mon Sep 17 00:00:00 2001
From: ford-jones 
Date: Mon, 15 Sep 2025 15:08:02 +1200
Subject: [PATCH 223/691] Fix - reference actual channel when changing settings

---
 src/mesh/Channels.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/mesh/Channels.cpp b/src/mesh/Channels.cpp
index affe05285..bc1d5ab20 100644
--- a/src/mesh/Channels.cpp
+++ b/src/mesh/Channels.cpp
@@ -346,7 +346,7 @@ void Channels::setChannel(const meshtastic_Channel &c)
 void Channels::setMute(ChannelIndex chIndex)
 {
     if (chIndex < channelFile.channels_count) {
-        meshtastic_Channel *ch = channelFile.channels + chIndex;
+        meshtastic_Channel *ch = &getByIndex(chIndex);
         ch->settings.mute = !ch->settings.mute;
     } else {
         LOG_ERROR("Failed to mute. Invalid channel index %d > %d", chIndex, channelFile.channels_count);

From 5fca3a30ecefd33ad472c0b04c1b29188e9c3155 Mon Sep 17 00:00:00 2001
From: ford-jones 
Date: Mon, 15 Sep 2025 15:13:25 +1200
Subject: [PATCH 224/691] Update protos

---
 protobufs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/protobufs b/protobufs
index 4c4427c4a..8caf42396 160000
--- a/protobufs
+++ b/protobufs
@@ -1 +1 @@
-Subproject commit 4c4427c4a73c86fed7dc8632188bb8be95349d81
+Subproject commit 8caf42396438f0d8a0305143485fd671c1fc7126

From f0b7aab03081494932bb5c773ae45bc6e4d578ab Mon Sep 17 00:00:00 2001
From: ford-jones 
Date: Mon, 15 Sep 2025 15:21:40 +1200
Subject: [PATCH 225/691] Refactor ref syntax

---
 src/mesh/Channels.cpp | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/mesh/Channels.cpp b/src/mesh/Channels.cpp
index bc1d5ab20..1c2bfdcfb 100644
--- a/src/mesh/Channels.cpp
+++ b/src/mesh/Channels.cpp
@@ -346,8 +346,8 @@ void Channels::setChannel(const meshtastic_Channel &c)
 void Channels::setMute(ChannelIndex chIndex)
 {
     if (chIndex < channelFile.channels_count) {
-        meshtastic_Channel *ch = &getByIndex(chIndex);
-        ch->settings.mute = !ch->settings.mute;
+        meshtastic_Channel &ch = getByIndex(chIndex);
+        ch.settings.mute = !ch.settings.mute;
     } else {
         LOG_ERROR("Failed to mute. Invalid channel index %d > %d", chIndex, channelFile.channels_count);
     };

From 6c932d51ec1288eeba38805f64103169f1f2144a Mon Sep 17 00:00:00 2001
From: WillyJL 
Date: Mon, 15 Sep 2025 17:49:03 +0200
Subject: [PATCH 226/691] Fix defines

---
 src/AudioThread.h | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/src/AudioThread.h b/src/AudioThread.h
index f794d3a79..8073ee51b 100644
--- a/src/AudioThread.h
+++ b/src/AudioThread.h
@@ -25,7 +25,7 @@ class AudioThread : public concurrency::OSThread
 
     void beginRttl(const void *data, uint32_t len)
     {
-#ifdef USE_XL9555
+#ifdef T_LORA_PAGER
         io.digitalWrite(EXPANDS_AMP_EN, HIGH);
 #endif
         setCPUFast(true);
@@ -53,7 +53,7 @@ class AudioThread : public concurrency::OSThread
         rtttlFile = nullptr;
 
         setCPUFast(false);
-#ifdef USE_XL9555
+#ifdef T_LORA_PAGER
         io.digitalWrite(EXPANDS_AMP_EN, LOW);
 #endif
     }
@@ -66,14 +66,14 @@ class AudioThread : public concurrency::OSThread
             i2sRtttl = nullptr;
         }
 
-#ifdef USE_XL9555
+#ifdef T_LORA_PAGER
         io.digitalWrite(EXPANDS_AMP_EN, HIGH);
 #endif
         ESP8266SAM *sam = new ESP8266SAM;
         sam->Say(audioOut, text);
         delete sam;
         setCPUFast(false);
-#ifdef USE_XL9555
+#ifdef T_LORA_PAGER
         io.digitalWrite(EXPANDS_AMP_EN, LOW);
 #endif
     }

From 5d3c92f1a28654c7e3bf885113663b1469d1ca76 Mon Sep 17 00:00:00 2001
From: Mike Robbins 
Date: Mon, 15 Sep 2025 12:50:38 -0700
Subject: [PATCH 227/691] When DEBUG_HEAP is defined, add free heap bytes to
 every log line in RedirectablePrint::log_to_serial

---
 platformio.ini            |  1 +
 src/RedirectablePrint.cpp | 11 +++++++++++
 2 files changed, 12 insertions(+)

diff --git a/platformio.ini b/platformio.ini
index 47b5f823d..30e8b1aa6 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -55,6 +55,7 @@ build_flags = -Wno-missing-field-initializers
 	-D MAX_THREADS=40 ; As we've split modules, we have more threads to manage
 	#-DBUILD_EPOCH=$UNIX_TIME ; set in platformio-custom.py now
 	#-D OLED_PL=1
+	#-D DEBUG_HEAP=1 ; uncomment to add free heap space / memory leak debugging logs
 
 monitor_speed = 115200
 monitor_filters = direct
diff --git a/src/RedirectablePrint.cpp b/src/RedirectablePrint.cpp
index efab84399..9624a4593 100644
--- a/src/RedirectablePrint.cpp
+++ b/src/RedirectablePrint.cpp
@@ -4,6 +4,7 @@
 #include "concurrency/OSThread.h"
 #include "configuration.h"
 #include "main.h"
+#include "memGet.h"
 #include "mesh/generated/meshtastic/mesh.pb.h"
 #include 
 #include 
@@ -166,6 +167,16 @@ void RedirectablePrint::log_to_serial(const char *logLevel, const char *format,
         print(thread->ThreadName);
         print("] ");
     }
+
+#ifdef DEBUG_HEAP
+    // Add heap free space bytes prefix before every log message
+#ifdef ARCH_PORTDUINO
+    ::printf("[heap %u] ", memGet.getFreeHeap());
+#else
+    printf("[heap %u] ", memGet.getFreeHeap());
+#endif
+#endif // DEBUG_HEAP
+
     r += vprintf(logLevel, format, arg);
 }
 

From b9d53d667ee9e6b8eeede142c0e9c6c92dfeab97 Mon Sep 17 00:00:00 2001
From: Michael 
Date: Tue, 16 Sep 2025 02:29:47 +0200
Subject: [PATCH 228/691] Feature: Seamless Cross-Preset Communication via UDP
 Multicast Bridging (#7753)

* Added compatibility between nodes on different Presets through `Mesh via UDP`

* Optimize multicast handling and channel mapping

- FloodingRouter: remove redundant UDP-encrypted rebroadcast suppression.
- Router: guard multicast fallback with HAS_UDP_MULTICAST and map fallback-decoded packets
  to the local default channel via isDefaultChannel()
- UdpMulticastHandler: set transport_mechanism only after successful decode

* trunk fmt

* Move setting transport mechanism.

---------

Co-authored-by: GUVWAF 
---
 src/mesh/Channels.cpp                 | 26 +++++++++++++++++++++++
 src/mesh/Channels.h                   |  2 ++
 src/mesh/Router.cpp                   | 30 +++++++++++++++++++++++++++
 src/mesh/udp/UdpMulticastHandler.h    |  2 +-
 variants/esp32s3/t-deck-pro/variant.h | 15 +++++++-------
 5 files changed, 66 insertions(+), 9 deletions(-)

diff --git a/src/mesh/Channels.cpp b/src/mesh/Channels.cpp
index 4ef41ddfb..4c0a0edad 100644
--- a/src/mesh/Channels.cpp
+++ b/src/mesh/Channels.cpp
@@ -423,6 +423,32 @@ bool Channels::decryptForHash(ChannelIndex chIndex, ChannelHash channelHash)
     }
 }
 
+bool Channels::setDefaultPresetCryptoForHash(ChannelHash channelHash)
+{
+    // Iterate all known presets
+    for (int preset = _meshtastic_Config_LoRaConfig_ModemPreset_MIN; preset <= _meshtastic_Config_LoRaConfig_ModemPreset_MAX;
+         ++preset) {
+        const char *name = DisplayFormatters::getModemPresetDisplayName((meshtastic_Config_LoRaConfig_ModemPreset)preset, false);
+        if (!name)
+            continue;
+        if (strcmp(name, "Invalid") == 0)
+            continue; // skip invalid placeholder
+        uint8_t h = xorHash((const uint8_t *)name, strlen(name));
+        // Expand default PSK alias 1 to actual bytes and xor into hash
+        uint8_t tmp = h ^ xorHash(defaultpsk, sizeof(defaultpsk));
+        if (tmp == channelHash) {
+            // Set crypto to defaultpsk and report success
+            CryptoKey k;
+            memcpy(k.bytes, defaultpsk, sizeof(defaultpsk));
+            k.length = sizeof(defaultpsk);
+            crypto->setKey(k);
+            LOG_INFO("Matched default preset '%s' for hash 0x%x; set default PSK", name, channelHash);
+            return true;
+        }
+    }
+    return false;
+}
+
 /** Given a channel index setup crypto for encoding that channel (or the primary channel if that channel is unsecured)
  *
  * This method is called before encoding outbound packets
diff --git a/src/mesh/Channels.h b/src/mesh/Channels.h
index 7873a306a..b53f552fa 100644
--- a/src/mesh/Channels.h
+++ b/src/mesh/Channels.h
@@ -94,6 +94,8 @@ class Channels
 
     bool ensureLicensedOperation();
 
+    bool setDefaultPresetCryptoForHash(ChannelHash channelHash);
+
   private:
     /** Given a channel index, change to use the crypto key specified by that index
      *
diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp
index 37b70dab6..09fb079c5 100644
--- a/src/mesh/Router.cpp
+++ b/src/mesh/Router.cpp
@@ -430,6 +430,36 @@ DecodeState perhapsDecode(meshtastic_MeshPacket *p)
             }
         }
     }
+
+#if HAS_UDP_MULTICAST
+    // Fallback: for UDP multicast, try default preset names with default PSK if normal channel match failed
+    if (!decrypted && p->transport_mechanism == meshtastic_MeshPacket_TransportMechanism_TRANSPORT_MULTICAST_UDP) {
+        if (channels.setDefaultPresetCryptoForHash(p->channel)) {
+            memcpy(bytes, p->encrypted.bytes, rawSize);
+            crypto->decrypt(p->from, p->id, rawSize, bytes);
+
+            meshtastic_Data decodedtmp;
+            memset(&decodedtmp, 0, sizeof(decodedtmp));
+            if (pb_decode_from_bytes(bytes, rawSize, &meshtastic_Data_msg, &decodedtmp) &&
+                decodedtmp.portnum != meshtastic_PortNum_UNKNOWN_APP) {
+                p->decoded = decodedtmp;
+                p->which_payload_variant = meshtastic_MeshPacket_decoded_tag;
+                // Map to our local default channel index (name+PSK default), not necessarily primary
+                ChannelIndex defaultIndex = channels.getPrimaryIndex();
+                for (ChannelIndex i = 0; i < channels.getNumChannels(); ++i) {
+                    if (channels.isDefaultChannel(i)) {
+                        defaultIndex = i;
+                        break;
+                    }
+                }
+                chIndex = defaultIndex;
+                decrypted = true;
+            } else {
+                LOG_WARN("UDP fallback decode attempted but failed for hash 0x%x", p->channel);
+            }
+        }
+    }
+#endif
     if (decrypted) {
         // parsing was successful
         p->channel = chIndex; // change to store the index instead of the hash
diff --git a/src/mesh/udp/UdpMulticastHandler.h b/src/mesh/udp/UdpMulticastHandler.h
index 9650668a8..2df8686a3 100644
--- a/src/mesh/udp/UdpMulticastHandler.h
+++ b/src/mesh/udp/UdpMulticastHandler.h
@@ -50,10 +50,10 @@ class UdpMulticastHandler final
         LOG_DEBUG("UDP broadcast from: %s, len=%u", packet.remoteIP().toString().c_str(), packetLength);
 #endif
         meshtastic_MeshPacket mp;
-        mp.transport_mechanism = meshtastic_MeshPacket_TransportMechanism_TRANSPORT_MULTICAST_UDP;
         LOG_DEBUG("Decoding MeshPacket from UDP len=%u", packetLength);
         bool isPacketDecoded = pb_decode_from_bytes(packet.data(), packetLength, &meshtastic_MeshPacket_msg, &mp);
         if (isPacketDecoded && router && mp.which_payload_variant == meshtastic_MeshPacket_encrypted_tag) {
+            mp.transport_mechanism = meshtastic_MeshPacket_TransportMechanism_TRANSPORT_MULTICAST_UDP;
             mp.pki_encrypted = false;
             mp.public_key.size = 0;
             memset(mp.public_key.bytes, 0, sizeof(mp.public_key.bytes));
diff --git a/variants/esp32s3/t-deck-pro/variant.h b/variants/esp32s3/t-deck-pro/variant.h
index abe0a772a..35cb99435 100644
--- a/variants/esp32s3/t-deck-pro/variant.h
+++ b/variants/esp32s3/t-deck-pro/variant.h
@@ -93,11 +93,10 @@
 // Internally the TTGO module hooks the SX1262-DIO2 in to control the TX/RX switch (which is the default for the sx1262interface
 // code)
 
-#define MODEM_POWER_EN  41
-#define MODEM_PWRKEY    40
-#define MODEM_RST  9
-#define MODEM_RI  7
-#define MODEM_DTR  8
-#define MODEM_RX  10
-#define MODEM_TX  11
-
+#define MODEM_POWER_EN 41
+#define MODEM_PWRKEY 40
+#define MODEM_RST 9
+#define MODEM_RI 7
+#define MODEM_DTR 8
+#define MODEM_RX 10
+#define MODEM_TX 11

From 1c256ccfd79b4908ea794b503e5e4b58559590cb Mon Sep 17 00:00:00 2001
From: ford-jones 
Date: Tue, 16 Sep 2025 15:43:13 +1200
Subject: [PATCH 229/691] Update comments and remove unused function

---
 src/mesh/Channels.cpp                      | 10 ----------
 src/mesh/Channels.h                        |  6 ------
 src/mesh/generated/meshtastic/channel.pb.h |  4 +---
 3 files changed, 1 insertion(+), 19 deletions(-)

diff --git a/src/mesh/Channels.cpp b/src/mesh/Channels.cpp
index 1c2bfdcfb..4ef41ddfb 100644
--- a/src/mesh/Channels.cpp
+++ b/src/mesh/Channels.cpp
@@ -343,16 +343,6 @@ void Channels::setChannel(const meshtastic_Channel &c)
     old = c; // slam in the new settings/role
 }
 
-void Channels::setMute(ChannelIndex chIndex)
-{
-    if (chIndex < channelFile.channels_count) {
-        meshtastic_Channel &ch = getByIndex(chIndex);
-        ch.settings.mute = !ch.settings.mute;
-    } else {
-        LOG_ERROR("Failed to mute. Invalid channel index %d > %d", chIndex, channelFile.channels_count);
-    };
-};
-
 bool Channels::anyMqttEnabled()
 {
 #if USERPREFS_EVENT_MODE && !MESHTASTIC_EXCLUDE_MQTT
diff --git a/src/mesh/Channels.h b/src/mesh/Channels.h
index e7c6ddb78..7873a306a 100644
--- a/src/mesh/Channels.h
+++ b/src/mesh/Channels.h
@@ -47,12 +47,6 @@ class Channels
      */
     void setChannel(const meshtastic_Channel &c);
 
-    /**
-     * Toggles the mute state of the channel associated with the channel index.
-     * I.e. if it's off turn it on and vice-versa.
-     */
-    void setMute(ChannelIndex chIndex);
-
     /** Return a human friendly name for this channel (and expand any short strings as needed)
      */
     const char *getName(size_t chIndex);
diff --git a/src/mesh/generated/meshtastic/channel.pb.h b/src/mesh/generated/meshtastic/channel.pb.h
index 1e1b95df7..594d15929 100644
--- a/src/mesh/generated/meshtastic/channel.pb.h
+++ b/src/mesh/generated/meshtastic/channel.pb.h
@@ -97,9 +97,7 @@ typedef struct _meshtastic_ChannelSettings {
     /* Per-channel module settings. */
     bool has_module_settings;
     meshtastic_ModuleSettings module_settings;
-    /* Whether or not we should receive notifactions / alerts through this channel
- Note: This is NOT the same as module_settings.is_client_mute which pertains
- to the device role. */
+    /* Whether or not we should receive notifactions / alerts from this channel */
     bool mute;
 } meshtastic_ChannelSettings;
 

From c9702fe4d011c7a6b6ad20ec946865b86b7c4f47 Mon Sep 17 00:00:00 2001
From: ford-jones 
Date: Tue, 16 Sep 2025 19:21:53 +1200
Subject: [PATCH 230/691] Regen protos

---
 src/mesh/generated/meshtastic/admin.pb.h      |  8 -------
 src/mesh/generated/meshtastic/apponly.pb.h    |  2 +-
 src/mesh/generated/meshtastic/channel.pb.h    | 14 ++++-------
 src/mesh/generated/meshtastic/config.pb.h     | 11 ++++++---
 src/mesh/generated/meshtastic/deviceonly.pb.h | 23 ++++++++-----------
 src/mesh/generated/meshtastic/localonly.pb.h  |  2 +-
 src/mesh/generated/meshtastic/mesh.pb.h       |  2 ++
 .../generated/meshtastic/module_config.pb.h   | 13 +++++++----
 8 files changed, 35 insertions(+), 40 deletions(-)

diff --git a/src/mesh/generated/meshtastic/admin.pb.h b/src/mesh/generated/meshtastic/admin.pb.h
index 616b7e8ee..bc0b780b9 100644
--- a/src/mesh/generated/meshtastic/admin.pb.h
+++ b/src/mesh/generated/meshtastic/admin.pb.h
@@ -247,10 +247,6 @@ typedef struct _meshtastic_AdminMessage {
         uint32_t set_ignored_node;
         /* Set specified node-num to be un-ignored on the NodeDB on the device */
         uint32_t remove_ignored_node;
-        /* Set specified node-num to be muted */
-        uint32_t set_muted_node;
-        /* Set specified node-num to be heard / not-muted */
-        uint32_t remove_muted_node;
         /* Begins an edit transaction for config, module config, owner, and channel settings changes
      This will delay the standard *implicit* save to the file system and subsequent reboot behavior until committed (commit_edit_settings) */
         bool begin_edit_settings;
@@ -392,8 +388,6 @@ extern "C" {
 #define meshtastic_AdminMessage_store_ui_config_tag 46
 #define meshtastic_AdminMessage_set_ignored_node_tag 47
 #define meshtastic_AdminMessage_remove_ignored_node_tag 48
-#define meshtastic_AdminMessage_set_muted_node_tag 49
-#define meshtastic_AdminMessage_remove_muted_node_tag 50
 #define meshtastic_AdminMessage_begin_edit_settings_tag 64
 #define meshtastic_AdminMessage_commit_edit_settings_tag 65
 #define meshtastic_AdminMessage_add_contact_tag  66
@@ -452,8 +446,6 @@ X(a, STATIC,   ONEOF,    MESSAGE,  (payload_variant,get_ui_config_response,get_u
 X(a, STATIC,   ONEOF,    MESSAGE,  (payload_variant,store_ui_config,store_ui_config),  46) \
 X(a, STATIC,   ONEOF,    UINT32,   (payload_variant,set_ignored_node,set_ignored_node),  47) \
 X(a, STATIC,   ONEOF,    UINT32,   (payload_variant,remove_ignored_node,remove_ignored_node),  48) \
-X(a, STATIC,   ONEOF,    UINT32,   (payload_variant,set_muted_node,set_muted_node),  49) \
-X(a, STATIC,   ONEOF,    UINT32,   (payload_variant,remove_muted_node,remove_muted_node),  50) \
 X(a, STATIC,   ONEOF,    BOOL,     (payload_variant,begin_edit_settings,begin_edit_settings),  64) \
 X(a, STATIC,   ONEOF,    BOOL,     (payload_variant,commit_edit_settings,commit_edit_settings),  65) \
 X(a, STATIC,   ONEOF,    MESSAGE,  (payload_variant,add_contact,add_contact),  66) \
diff --git a/src/mesh/generated/meshtastic/apponly.pb.h b/src/mesh/generated/meshtastic/apponly.pb.h
index db9dedaaf..f4c33bd79 100644
--- a/src/mesh/generated/meshtastic/apponly.pb.h
+++ b/src/mesh/generated/meshtastic/apponly.pb.h
@@ -55,7 +55,7 @@ extern const pb_msgdesc_t meshtastic_ChannelSet_msg;
 
 /* Maximum encoded size of messages (where known) */
 #define MESHTASTIC_MESHTASTIC_APPONLY_PB_H_MAX_SIZE meshtastic_ChannelSet_size
-#define meshtastic_ChannelSet_size               695
+#define meshtastic_ChannelSet_size               679
 
 #ifdef __cplusplus
 } /* extern "C" */
diff --git a/src/mesh/generated/meshtastic/channel.pb.h b/src/mesh/generated/meshtastic/channel.pb.h
index 594d15929..ca4310bf1 100644
--- a/src/mesh/generated/meshtastic/channel.pb.h
+++ b/src/mesh/generated/meshtastic/channel.pb.h
@@ -97,8 +97,6 @@ typedef struct _meshtastic_ChannelSettings {
     /* Per-channel module settings. */
     bool has_module_settings;
     meshtastic_ModuleSettings module_settings;
-    /* Whether or not we should receive notifactions / alerts from this channel */
-    bool mute;
 } meshtastic_ChannelSettings;
 
 /* A pair of a channel number, mode and the (sharable) settings for that channel */
@@ -130,10 +128,10 @@ extern "C" {
 
 
 /* Initializer values for message structs */
-#define meshtastic_ChannelSettings_init_default  {0, {0, {0}}, "", 0, 0, 0, false, meshtastic_ModuleSettings_init_default, 0}
+#define meshtastic_ChannelSettings_init_default  {0, {0, {0}}, "", 0, 0, 0, false, meshtastic_ModuleSettings_init_default}
 #define meshtastic_ModuleSettings_init_default   {0, 0}
 #define meshtastic_Channel_init_default          {0, false, meshtastic_ChannelSettings_init_default, _meshtastic_Channel_Role_MIN}
-#define meshtastic_ChannelSettings_init_zero     {0, {0, {0}}, "", 0, 0, 0, false, meshtastic_ModuleSettings_init_zero, 0}
+#define meshtastic_ChannelSettings_init_zero     {0, {0, {0}}, "", 0, 0, 0, false, meshtastic_ModuleSettings_init_zero}
 #define meshtastic_ModuleSettings_init_zero      {0, 0}
 #define meshtastic_Channel_init_zero             {0, false, meshtastic_ChannelSettings_init_zero, _meshtastic_Channel_Role_MIN}
 
@@ -147,7 +145,6 @@ extern "C" {
 #define meshtastic_ChannelSettings_uplink_enabled_tag 5
 #define meshtastic_ChannelSettings_downlink_enabled_tag 6
 #define meshtastic_ChannelSettings_module_settings_tag 7
-#define meshtastic_ChannelSettings_mute_tag      8
 #define meshtastic_Channel_index_tag             1
 #define meshtastic_Channel_settings_tag          2
 #define meshtastic_Channel_role_tag              3
@@ -160,8 +157,7 @@ X(a, STATIC,   SINGULAR, STRING,   name,              3) \
 X(a, STATIC,   SINGULAR, FIXED32,  id,                4) \
 X(a, STATIC,   SINGULAR, BOOL,     uplink_enabled,    5) \
 X(a, STATIC,   SINGULAR, BOOL,     downlink_enabled,   6) \
-X(a, STATIC,   OPTIONAL, MESSAGE,  module_settings,   7) \
-X(a, STATIC,   SINGULAR, BOOL,     mute,              8)
+X(a, STATIC,   OPTIONAL, MESSAGE,  module_settings,   7)
 #define meshtastic_ChannelSettings_CALLBACK NULL
 #define meshtastic_ChannelSettings_DEFAULT NULL
 #define meshtastic_ChannelSettings_module_settings_MSGTYPE meshtastic_ModuleSettings
@@ -191,8 +187,8 @@ extern const pb_msgdesc_t meshtastic_Channel_msg;
 
 /* Maximum encoded size of messages (where known) */
 #define MESHTASTIC_MESHTASTIC_CHANNEL_PB_H_MAX_SIZE meshtastic_Channel_size
-#define meshtastic_ChannelSettings_size          74
-#define meshtastic_Channel_size                  89
+#define meshtastic_ChannelSettings_size          72
+#define meshtastic_Channel_size                  87
 #define meshtastic_ModuleSettings_size           8
 
 #ifdef __cplusplus
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
diff --git a/src/mesh/generated/meshtastic/deviceonly.pb.h b/src/mesh/generated/meshtastic/deviceonly.pb.h
index 55c433699..9b6330596 100644
--- a/src/mesh/generated/meshtastic/deviceonly.pb.h
+++ b/src/mesh/generated/meshtastic/deviceonly.pb.h
@@ -94,9 +94,6 @@ typedef struct _meshtastic_NodeInfoLite {
     /* True if node is in our ignored list
  Persists between NodeDB internal clean ups */
     bool is_ignored;
-    /* True if node has been muted
- Persists between NodeDB internal clean ups */
-    bool is_muted;
     /* Last byte of the node number of the node that should be used as the next hop to reach this node. */
     uint8_t next_hop;
     /* Bitfield for storing booleans.
@@ -193,14 +190,14 @@ extern "C" {
 /* Initializer values for message structs */
 #define meshtastic_PositionLite_init_default     {0, 0, 0, 0, _meshtastic_Position_LocSource_MIN}
 #define meshtastic_UserLite_init_default         {{0}, "", "", _meshtastic_HardwareModel_MIN, 0, _meshtastic_Config_DeviceConfig_Role_MIN, {0, {0}}, false, 0}
-#define meshtastic_NodeInfoLite_init_default     {0, false, meshtastic_UserLite_init_default, false, meshtastic_PositionLite_init_default, 0, 0, false, meshtastic_DeviceMetrics_init_default, 0, 0, false, 0, 0, 0, 0, 0, 0}
+#define meshtastic_NodeInfoLite_init_default     {0, false, meshtastic_UserLite_init_default, false, meshtastic_PositionLite_init_default, 0, 0, false, meshtastic_DeviceMetrics_init_default, 0, 0, false, 0, 0, 0, 0, 0}
 #define meshtastic_DeviceState_init_default      {false, meshtastic_MyNodeInfo_init_default, false, meshtastic_User_init_default, 0, {meshtastic_MeshPacket_init_default}, false, meshtastic_MeshPacket_init_default, 0, 0, 0, false, meshtastic_MeshPacket_init_default, 0, {meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default}}
 #define meshtastic_NodeDatabase_init_default     {0, {0}}
 #define meshtastic_ChannelFile_init_default      {0, {meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default}, 0}
 #define meshtastic_BackupPreferences_init_default {0, 0, false, meshtastic_LocalConfig_init_default, false, meshtastic_LocalModuleConfig_init_default, false, meshtastic_ChannelFile_init_default, false, meshtastic_User_init_default}
 #define meshtastic_PositionLite_init_zero        {0, 0, 0, 0, _meshtastic_Position_LocSource_MIN}
 #define meshtastic_UserLite_init_zero            {{0}, "", "", _meshtastic_HardwareModel_MIN, 0, _meshtastic_Config_DeviceConfig_Role_MIN, {0, {0}}, false, 0}
-#define meshtastic_NodeInfoLite_init_zero        {0, false, meshtastic_UserLite_init_zero, false, meshtastic_PositionLite_init_zero, 0, 0, false, meshtastic_DeviceMetrics_init_zero, 0, 0, false, 0, 0, 0, 0, 0, 0}
+#define meshtastic_NodeInfoLite_init_zero        {0, false, meshtastic_UserLite_init_zero, false, meshtastic_PositionLite_init_zero, 0, 0, false, meshtastic_DeviceMetrics_init_zero, 0, 0, false, 0, 0, 0, 0, 0}
 #define meshtastic_DeviceState_init_zero         {false, meshtastic_MyNodeInfo_init_zero, false, meshtastic_User_init_zero, 0, {meshtastic_MeshPacket_init_zero}, false, meshtastic_MeshPacket_init_zero, 0, 0, 0, false, meshtastic_MeshPacket_init_zero, 0, {meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero}}
 #define meshtastic_NodeDatabase_init_zero        {0, {0}}
 #define meshtastic_ChannelFile_init_zero         {0, {meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero}, 0}
@@ -231,9 +228,8 @@ extern "C" {
 #define meshtastic_NodeInfoLite_hops_away_tag    9
 #define meshtastic_NodeInfoLite_is_favorite_tag  10
 #define meshtastic_NodeInfoLite_is_ignored_tag   11
-#define meshtastic_NodeInfoLite_is_muted_tag     12
-#define meshtastic_NodeInfoLite_next_hop_tag     13
-#define meshtastic_NodeInfoLite_bitfield_tag     14
+#define meshtastic_NodeInfoLite_next_hop_tag     12
+#define meshtastic_NodeInfoLite_bitfield_tag     13
 #define meshtastic_DeviceState_my_node_tag       2
 #define meshtastic_DeviceState_owner_tag         3
 #define meshtastic_DeviceState_receive_queue_tag 5
@@ -288,9 +284,8 @@ X(a, STATIC,   SINGULAR, BOOL,     via_mqtt,          8) \
 X(a, STATIC,   OPTIONAL, UINT32,   hops_away,         9) \
 X(a, STATIC,   SINGULAR, BOOL,     is_favorite,      10) \
 X(a, STATIC,   SINGULAR, BOOL,     is_ignored,       11) \
-X(a, STATIC,   SINGULAR, BOOL,     is_muted,         12) \
-X(a, STATIC,   SINGULAR, UINT32,   next_hop,         13) \
-X(a, STATIC,   SINGULAR, UINT32,   bitfield,         14)
+X(a, STATIC,   SINGULAR, UINT32,   next_hop,         12) \
+X(a, STATIC,   SINGULAR, UINT32,   bitfield,         13)
 #define meshtastic_NodeInfoLite_CALLBACK NULL
 #define meshtastic_NodeInfoLite_DEFAULT NULL
 #define meshtastic_NodeInfoLite_user_MSGTYPE meshtastic_UserLite
@@ -365,10 +360,10 @@ 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        2287
-#define meshtastic_ChannelFile_size              734
+#define meshtastic_BackupPreferences_size        2273
+#define meshtastic_ChannelFile_size              718
 #define meshtastic_DeviceState_size              1737
-#define meshtastic_NodeInfoLite_size             198
+#define meshtastic_NodeInfoLite_size             196
 #define meshtastic_PositionLite_size             28
 #define meshtastic_UserLite_size                 98
 
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/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.
  ------------------------------------------------------------------------------------------------------------------------------------------ */
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 4ac99c5df1278e2c506e3b5e3d5b7078744cdc26 Mon Sep 17 00:00:00 2001
From: ford-jones 
Date: Tue, 16 Sep 2025 19:26:22 +1200
Subject: [PATCH 231/691] Regen protobuffs again

---
 src/mesh/generated/meshtastic/admin.pb.h      |  8 +++++++
 src/mesh/generated/meshtastic/apponly.pb.h    |  2 +-
 src/mesh/generated/meshtastic/channel.pb.h    | 14 +++++++----
 src/mesh/generated/meshtastic/deviceonly.pb.h | 23 +++++++++++--------
 src/mesh/generated/meshtastic/mesh.pb.h       |  6 +++--
 5 files changed, 36 insertions(+), 17 deletions(-)

diff --git a/src/mesh/generated/meshtastic/admin.pb.h b/src/mesh/generated/meshtastic/admin.pb.h
index bc0b780b9..616b7e8ee 100644
--- a/src/mesh/generated/meshtastic/admin.pb.h
+++ b/src/mesh/generated/meshtastic/admin.pb.h
@@ -247,6 +247,10 @@ typedef struct _meshtastic_AdminMessage {
         uint32_t set_ignored_node;
         /* Set specified node-num to be un-ignored on the NodeDB on the device */
         uint32_t remove_ignored_node;
+        /* Set specified node-num to be muted */
+        uint32_t set_muted_node;
+        /* Set specified node-num to be heard / not-muted */
+        uint32_t remove_muted_node;
         /* Begins an edit transaction for config, module config, owner, and channel settings changes
      This will delay the standard *implicit* save to the file system and subsequent reboot behavior until committed (commit_edit_settings) */
         bool begin_edit_settings;
@@ -388,6 +392,8 @@ extern "C" {
 #define meshtastic_AdminMessage_store_ui_config_tag 46
 #define meshtastic_AdminMessage_set_ignored_node_tag 47
 #define meshtastic_AdminMessage_remove_ignored_node_tag 48
+#define meshtastic_AdminMessage_set_muted_node_tag 49
+#define meshtastic_AdminMessage_remove_muted_node_tag 50
 #define meshtastic_AdminMessage_begin_edit_settings_tag 64
 #define meshtastic_AdminMessage_commit_edit_settings_tag 65
 #define meshtastic_AdminMessage_add_contact_tag  66
@@ -446,6 +452,8 @@ X(a, STATIC,   ONEOF,    MESSAGE,  (payload_variant,get_ui_config_response,get_u
 X(a, STATIC,   ONEOF,    MESSAGE,  (payload_variant,store_ui_config,store_ui_config),  46) \
 X(a, STATIC,   ONEOF,    UINT32,   (payload_variant,set_ignored_node,set_ignored_node),  47) \
 X(a, STATIC,   ONEOF,    UINT32,   (payload_variant,remove_ignored_node,remove_ignored_node),  48) \
+X(a, STATIC,   ONEOF,    UINT32,   (payload_variant,set_muted_node,set_muted_node),  49) \
+X(a, STATIC,   ONEOF,    UINT32,   (payload_variant,remove_muted_node,remove_muted_node),  50) \
 X(a, STATIC,   ONEOF,    BOOL,     (payload_variant,begin_edit_settings,begin_edit_settings),  64) \
 X(a, STATIC,   ONEOF,    BOOL,     (payload_variant,commit_edit_settings,commit_edit_settings),  65) \
 X(a, STATIC,   ONEOF,    MESSAGE,  (payload_variant,add_contact,add_contact),  66) \
diff --git a/src/mesh/generated/meshtastic/apponly.pb.h b/src/mesh/generated/meshtastic/apponly.pb.h
index f4c33bd79..db9dedaaf 100644
--- a/src/mesh/generated/meshtastic/apponly.pb.h
+++ b/src/mesh/generated/meshtastic/apponly.pb.h
@@ -55,7 +55,7 @@ extern const pb_msgdesc_t meshtastic_ChannelSet_msg;
 
 /* Maximum encoded size of messages (where known) */
 #define MESHTASTIC_MESHTASTIC_APPONLY_PB_H_MAX_SIZE meshtastic_ChannelSet_size
-#define meshtastic_ChannelSet_size               679
+#define meshtastic_ChannelSet_size               695
 
 #ifdef __cplusplus
 } /* extern "C" */
diff --git a/src/mesh/generated/meshtastic/channel.pb.h b/src/mesh/generated/meshtastic/channel.pb.h
index ca4310bf1..594d15929 100644
--- a/src/mesh/generated/meshtastic/channel.pb.h
+++ b/src/mesh/generated/meshtastic/channel.pb.h
@@ -97,6 +97,8 @@ typedef struct _meshtastic_ChannelSettings {
     /* Per-channel module settings. */
     bool has_module_settings;
     meshtastic_ModuleSettings module_settings;
+    /* Whether or not we should receive notifactions / alerts from this channel */
+    bool mute;
 } meshtastic_ChannelSettings;
 
 /* A pair of a channel number, mode and the (sharable) settings for that channel */
@@ -128,10 +130,10 @@ extern "C" {
 
 
 /* Initializer values for message structs */
-#define meshtastic_ChannelSettings_init_default  {0, {0, {0}}, "", 0, 0, 0, false, meshtastic_ModuleSettings_init_default}
+#define meshtastic_ChannelSettings_init_default  {0, {0, {0}}, "", 0, 0, 0, false, meshtastic_ModuleSettings_init_default, 0}
 #define meshtastic_ModuleSettings_init_default   {0, 0}
 #define meshtastic_Channel_init_default          {0, false, meshtastic_ChannelSettings_init_default, _meshtastic_Channel_Role_MIN}
-#define meshtastic_ChannelSettings_init_zero     {0, {0, {0}}, "", 0, 0, 0, false, meshtastic_ModuleSettings_init_zero}
+#define meshtastic_ChannelSettings_init_zero     {0, {0, {0}}, "", 0, 0, 0, false, meshtastic_ModuleSettings_init_zero, 0}
 #define meshtastic_ModuleSettings_init_zero      {0, 0}
 #define meshtastic_Channel_init_zero             {0, false, meshtastic_ChannelSettings_init_zero, _meshtastic_Channel_Role_MIN}
 
@@ -145,6 +147,7 @@ extern "C" {
 #define meshtastic_ChannelSettings_uplink_enabled_tag 5
 #define meshtastic_ChannelSettings_downlink_enabled_tag 6
 #define meshtastic_ChannelSettings_module_settings_tag 7
+#define meshtastic_ChannelSettings_mute_tag      8
 #define meshtastic_Channel_index_tag             1
 #define meshtastic_Channel_settings_tag          2
 #define meshtastic_Channel_role_tag              3
@@ -157,7 +160,8 @@ X(a, STATIC,   SINGULAR, STRING,   name,              3) \
 X(a, STATIC,   SINGULAR, FIXED32,  id,                4) \
 X(a, STATIC,   SINGULAR, BOOL,     uplink_enabled,    5) \
 X(a, STATIC,   SINGULAR, BOOL,     downlink_enabled,   6) \
-X(a, STATIC,   OPTIONAL, MESSAGE,  module_settings,   7)
+X(a, STATIC,   OPTIONAL, MESSAGE,  module_settings,   7) \
+X(a, STATIC,   SINGULAR, BOOL,     mute,              8)
 #define meshtastic_ChannelSettings_CALLBACK NULL
 #define meshtastic_ChannelSettings_DEFAULT NULL
 #define meshtastic_ChannelSettings_module_settings_MSGTYPE meshtastic_ModuleSettings
@@ -187,8 +191,8 @@ extern const pb_msgdesc_t meshtastic_Channel_msg;
 
 /* Maximum encoded size of messages (where known) */
 #define MESHTASTIC_MESHTASTIC_CHANNEL_PB_H_MAX_SIZE meshtastic_Channel_size
-#define meshtastic_ChannelSettings_size          72
-#define meshtastic_Channel_size                  87
+#define meshtastic_ChannelSettings_size          74
+#define meshtastic_Channel_size                  89
 #define meshtastic_ModuleSettings_size           8
 
 #ifdef __cplusplus
diff --git a/src/mesh/generated/meshtastic/deviceonly.pb.h b/src/mesh/generated/meshtastic/deviceonly.pb.h
index 9b6330596..148261fc8 100644
--- a/src/mesh/generated/meshtastic/deviceonly.pb.h
+++ b/src/mesh/generated/meshtastic/deviceonly.pb.h
@@ -94,6 +94,9 @@ typedef struct _meshtastic_NodeInfoLite {
     /* True if node is in our ignored list
  Persists between NodeDB internal clean ups */
     bool is_ignored;
+    /* True if node has been muted
+ Persists between NodeDB internal clean ups */
+    bool is_muted;
     /* Last byte of the node number of the node that should be used as the next hop to reach this node. */
     uint8_t next_hop;
     /* Bitfield for storing booleans.
@@ -190,14 +193,14 @@ extern "C" {
 /* Initializer values for message structs */
 #define meshtastic_PositionLite_init_default     {0, 0, 0, 0, _meshtastic_Position_LocSource_MIN}
 #define meshtastic_UserLite_init_default         {{0}, "", "", _meshtastic_HardwareModel_MIN, 0, _meshtastic_Config_DeviceConfig_Role_MIN, {0, {0}}, false, 0}
-#define meshtastic_NodeInfoLite_init_default     {0, false, meshtastic_UserLite_init_default, false, meshtastic_PositionLite_init_default, 0, 0, false, meshtastic_DeviceMetrics_init_default, 0, 0, false, 0, 0, 0, 0, 0}
+#define meshtastic_NodeInfoLite_init_default     {0, false, meshtastic_UserLite_init_default, false, meshtastic_PositionLite_init_default, 0, 0, false, meshtastic_DeviceMetrics_init_default, 0, 0, false, 0, 0, 0, 0, 0, 0}
 #define meshtastic_DeviceState_init_default      {false, meshtastic_MyNodeInfo_init_default, false, meshtastic_User_init_default, 0, {meshtastic_MeshPacket_init_default}, false, meshtastic_MeshPacket_init_default, 0, 0, 0, false, meshtastic_MeshPacket_init_default, 0, {meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default}}
 #define meshtastic_NodeDatabase_init_default     {0, {0}}
 #define meshtastic_ChannelFile_init_default      {0, {meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default}, 0}
 #define meshtastic_BackupPreferences_init_default {0, 0, false, meshtastic_LocalConfig_init_default, false, meshtastic_LocalModuleConfig_init_default, false, meshtastic_ChannelFile_init_default, false, meshtastic_User_init_default}
 #define meshtastic_PositionLite_init_zero        {0, 0, 0, 0, _meshtastic_Position_LocSource_MIN}
 #define meshtastic_UserLite_init_zero            {{0}, "", "", _meshtastic_HardwareModel_MIN, 0, _meshtastic_Config_DeviceConfig_Role_MIN, {0, {0}}, false, 0}
-#define meshtastic_NodeInfoLite_init_zero        {0, false, meshtastic_UserLite_init_zero, false, meshtastic_PositionLite_init_zero, 0, 0, false, meshtastic_DeviceMetrics_init_zero, 0, 0, false, 0, 0, 0, 0, 0}
+#define meshtastic_NodeInfoLite_init_zero        {0, false, meshtastic_UserLite_init_zero, false, meshtastic_PositionLite_init_zero, 0, 0, false, meshtastic_DeviceMetrics_init_zero, 0, 0, false, 0, 0, 0, 0, 0, 0}
 #define meshtastic_DeviceState_init_zero         {false, meshtastic_MyNodeInfo_init_zero, false, meshtastic_User_init_zero, 0, {meshtastic_MeshPacket_init_zero}, false, meshtastic_MeshPacket_init_zero, 0, 0, 0, false, meshtastic_MeshPacket_init_zero, 0, {meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero}}
 #define meshtastic_NodeDatabase_init_zero        {0, {0}}
 #define meshtastic_ChannelFile_init_zero         {0, {meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero}, 0}
@@ -228,8 +231,9 @@ extern "C" {
 #define meshtastic_NodeInfoLite_hops_away_tag    9
 #define meshtastic_NodeInfoLite_is_favorite_tag  10
 #define meshtastic_NodeInfoLite_is_ignored_tag   11
-#define meshtastic_NodeInfoLite_next_hop_tag     12
-#define meshtastic_NodeInfoLite_bitfield_tag     13
+#define meshtastic_NodeInfoLite_is_muted_tag     12
+#define meshtastic_NodeInfoLite_next_hop_tag     13
+#define meshtastic_NodeInfoLite_bitfield_tag     14
 #define meshtastic_DeviceState_my_node_tag       2
 #define meshtastic_DeviceState_owner_tag         3
 #define meshtastic_DeviceState_receive_queue_tag 5
@@ -284,8 +288,9 @@ X(a, STATIC,   SINGULAR, BOOL,     via_mqtt,          8) \
 X(a, STATIC,   OPTIONAL, UINT32,   hops_away,         9) \
 X(a, STATIC,   SINGULAR, BOOL,     is_favorite,      10) \
 X(a, STATIC,   SINGULAR, BOOL,     is_ignored,       11) \
-X(a, STATIC,   SINGULAR, UINT32,   next_hop,         12) \
-X(a, STATIC,   SINGULAR, UINT32,   bitfield,         13)
+X(a, STATIC,   SINGULAR, BOOL,     is_muted,         12) \
+X(a, STATIC,   SINGULAR, UINT32,   next_hop,         13) \
+X(a, STATIC,   SINGULAR, UINT32,   bitfield,         14)
 #define meshtastic_NodeInfoLite_CALLBACK NULL
 #define meshtastic_NodeInfoLite_DEFAULT NULL
 #define meshtastic_NodeInfoLite_user_MSGTYPE meshtastic_UserLite
@@ -360,10 +365,10 @@ 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        2273
-#define meshtastic_ChannelFile_size              718
+#define meshtastic_BackupPreferences_size        2289
+#define meshtastic_ChannelFile_size              734
 #define meshtastic_DeviceState_size              1737
-#define meshtastic_NodeInfoLite_size             196
+#define meshtastic_NodeInfoLite_size             198
 #define meshtastic_PositionLite_size             28
 #define meshtastic_UserLite_size                 98
 
diff --git a/src/mesh/generated/meshtastic/mesh.pb.h b/src/mesh/generated/meshtastic/mesh.pb.h
index 2a4e77870..294f0beac 100644
--- a/src/mesh/generated/meshtastic/mesh.pb.h
+++ b/src/mesh/generated/meshtastic/mesh.pb.h
@@ -259,8 +259,8 @@ typedef enum _meshtastic_HardwareModel {
     meshtastic_HardwareModel_T_DECK_PRO = 102,
     /* Lilygo TLora Pager */
     meshtastic_HardwareModel_T_LORA_PAGER = 103,
-    /* GAT562 Mesh Trial Tracker */
-    meshtastic_HardwareModel_GAT562_MESH_TRIAL_TRACKER = 104,
+    /* M5Stack Reserved */
+    meshtastic_HardwareModel_M5STACK_RESERVED = 104, /* 0x68 */
     /* RAKwireless WisMesh Tag */
     meshtastic_HardwareModel_WISMESH_TAG = 105,
     /* RAKwireless WisBlock Core RAK3312 https://docs.rakwireless.com/product-categories/wisduo/rak3112-module/overview/ */
@@ -274,6 +274,8 @@ typedef enum _meshtastic_HardwareModel {
     meshtastic_HardwareModel_T_ECHO_LITE = 109,
     /* New Heltec LoRA32 with ESP32-S3 CPU */
     meshtastic_HardwareModel_HELTEC_V4 = 110,
+    /* M5Stack C6L */
+    meshtastic_HardwareModel_M5STACK_C6L = 111,
     /* ------------------------------------------------------------------------------------------------------------------------------------------
  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 43078a40ebe6e42e34e83f29c80afba9f62298cc Mon Sep 17 00:00:00 2001
From: ford-jones 
Date: Tue, 16 Sep 2025 21:57:51 +1200
Subject: [PATCH 232/691] Fix build failure in ci, add missing argument

---
 src/mesh/Channels.cpp | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/src/mesh/Channels.cpp b/src/mesh/Channels.cpp
index 4c0a0edad..50e51261e 100644
--- a/src/mesh/Channels.cpp
+++ b/src/mesh/Channels.cpp
@@ -428,7 +428,8 @@ bool Channels::setDefaultPresetCryptoForHash(ChannelHash channelHash)
     // Iterate all known presets
     for (int preset = _meshtastic_Config_LoRaConfig_ModemPreset_MIN; preset <= _meshtastic_Config_LoRaConfig_ModemPreset_MAX;
          ++preset) {
-        const char *name = DisplayFormatters::getModemPresetDisplayName((meshtastic_Config_LoRaConfig_ModemPreset)preset, false);
+        const char *name =
+            DisplayFormatters::getModemPresetDisplayName((meshtastic_Config_LoRaConfig_ModemPreset)preset, false, true);
         if (!name)
             continue;
         if (strcmp(name, "Invalid") == 0)

From d31e3839fbbcd13b92f8d499add265c5c4c1d441 Mon Sep 17 00:00:00 2001
From: Ben Meadors 
Date: Tue, 16 Sep 2025 06:11:29 -0500
Subject: [PATCH 233/691] Use long name

---
 src/mesh/Channels.cpp | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/src/mesh/Channels.cpp b/src/mesh/Channels.cpp
index 4c0a0edad..aec112a3e 100644
--- a/src/mesh/Channels.cpp
+++ b/src/mesh/Channels.cpp
@@ -428,7 +428,8 @@ bool Channels::setDefaultPresetCryptoForHash(ChannelHash channelHash)
     // Iterate all known presets
     for (int preset = _meshtastic_Config_LoRaConfig_ModemPreset_MIN; preset <= _meshtastic_Config_LoRaConfig_ModemPreset_MAX;
          ++preset) {
-        const char *name = DisplayFormatters::getModemPresetDisplayName((meshtastic_Config_LoRaConfig_ModemPreset)preset, false);
+        const char *name =
+            DisplayFormatters::getModemPresetDisplayName((meshtastic_Config_LoRaConfig_ModemPreset)preset, false, false);
         if (!name)
             continue;
         if (strcmp(name, "Invalid") == 0)

From 22fcd102a081bf1fda6977e67c8ce44e8d40ebad Mon Sep 17 00:00:00 2001
From: Tom <116762865+NomDeTom@users.noreply.github.com>
Date: Tue, 16 Sep 2025 12:41:22 +0100
Subject: [PATCH 234/691] (resubmission) Manual GitHub actions to allow
 building one target or arch (#7997)

* Reset the modified files

* Fix some changes

* Fix some changes

* Trunk. That is all.

---------

Co-authored-by: Tom <116762865+Nestpebble@users.noreply.github.com>
---
 .github/workflows/build_one_arch.yml   | 500 +++++++++++++++++++++++++
 .github/workflows/build_one_target.yml | 395 +++++++++++++++++++
 .github/workflows/daily_packaging.yml  |   4 +
 .github/workflows/main_matrix.yml      |  17 +-
 4 files changed, 912 insertions(+), 4 deletions(-)
 create mode 100644 .github/workflows/build_one_arch.yml
 create mode 100644 .github/workflows/build_one_target.yml

diff --git a/.github/workflows/build_one_arch.yml b/.github/workflows/build_one_arch.yml
new file mode 100644
index 000000000..5901a335c
--- /dev/null
+++ b/.github/workflows/build_one_arch.yml
@@ -0,0 +1,500 @@
+name: Build One Arch
+
+on:
+  workflow_dispatch:
+    inputs:
+      arch:
+        type: choice
+        options:
+          - esp32
+          - esp32s3
+          - esp32c3
+          - esp32c6
+          - nrf52840
+          - rp2040
+          - rp2350
+          - stm32
+          - native
+
+jobs:
+  setup:
+    strategy:
+      fail-fast: false
+    runs-on: ubuntu-24.04
+    steps:
+      - uses: actions/checkout@v4
+      - uses: actions/setup-python@v5
+        with:
+          python-version: 3.x
+          cache: pip
+      - run: pip install -U platformio
+      - name: Generate matrix
+        id: jsonStep
+        run: |
+          if [[ "$GITHUB_HEAD_REF" == "" ]]; then
+            TARGETS=$(./bin/generate_ci_matrix.py ${{inputs.arch}} extra)
+          else  
+            TARGETS=$(./bin/generate_ci_matrix.py ${{inputs.arch}} pr)
+          fi
+          echo "Name: $GITHUB_REF_NAME Base: $GITHUB_BASE_REF Ref: $GITHUB_REF Targets: $TARGETS"
+          echo "${{inputs.arch}}=$(jq -cn --argjson environments "$TARGETS" '{board: $environments}')" >> $GITHUB_OUTPUT
+    outputs:
+      esp32: ${{ steps.jsonStep.outputs.esp32 }}
+      esp32s3: ${{ steps.jsonStep.outputs.esp32s3 }}
+      esp32c3: ${{ steps.jsonStep.outputs.esp32c3 }}
+      esp32c6: ${{ steps.jsonStep.outputs.esp32c6 }}
+      nrf52840: ${{ steps.jsonStep.outputs.nrf52840 }}
+      rp2040: ${{ steps.jsonStep.outputs.rp2040 }}
+      rp2350: ${{ steps.jsonStep.outputs.rp2350 }}
+      stm32: ${{ steps.jsonStep.outputs.stm32 }}
+      check: ${{ steps.jsonStep.outputs.check }}
+
+  version:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v4
+      - name: Get release version string
+        run: |
+          echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
+          echo "deb=$(./bin/buildinfo.py deb)" >> $GITHUB_OUTPUT
+        id: version
+        env:
+          BUILD_LOCATION: local
+    outputs:
+      long: ${{ steps.version.outputs.long }}
+      deb: ${{ steps.version.outputs.deb }}
+
+  build-esp32:
+    if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'esp32'}}
+    needs: [setup, version]
+    strategy:
+      fail-fast: false
+      matrix: ${{ fromJson(needs.setup.outputs.esp32) }}
+    uses: ./.github/workflows/build_firmware.yml
+    with:
+      version: ${{ needs.version.outputs.long }}
+      pio_env: ${{ matrix.board }}
+      platform: esp32
+
+  build-esp32s3:
+    if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'esp32s3'}}
+    needs: [setup, version]
+    strategy:
+      fail-fast: false
+      matrix: ${{ fromJson(needs.setup.outputs.esp32s3) }}
+    uses: ./.github/workflows/build_firmware.yml
+    with:
+      version: ${{ needs.version.outputs.long }}
+      pio_env: ${{ matrix.board }}
+      platform: esp32s3
+
+  build-esp32c3:
+    if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'esp32c3'}}
+    needs: [setup, version]
+    strategy:
+      fail-fast: false
+      matrix: ${{ fromJson(needs.setup.outputs.esp32c3) }}
+    uses: ./.github/workflows/build_firmware.yml
+    with:
+      version: ${{ needs.version.outputs.long }}
+      pio_env: ${{ matrix.board }}
+      platform: esp32c3
+
+  build-esp32c6:
+    if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'esp32c6'}}
+    needs: [setup, version]
+    strategy:
+      fail-fast: false
+      matrix: ${{ fromJson(needs.setup.outputs.esp32c6) }}
+    uses: ./.github/workflows/build_firmware.yml
+    with:
+      version: ${{ needs.version.outputs.long }}
+      pio_env: ${{ matrix.board }}
+      platform: esp32c6
+
+  build-nrf52840:
+    if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'nrf52840'}}
+    needs: [setup, version]
+    strategy:
+      fail-fast: false
+      matrix: ${{ fromJson(needs.setup.outputs.nrf52840) }}
+    uses: ./.github/workflows/build_firmware.yml
+    with:
+      version: ${{ needs.version.outputs.long }}
+      pio_env: ${{ matrix.board }}
+      platform: nrf52840
+
+  build-rp2040:
+    if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'rp2040'}}
+    needs: [setup, version]
+    strategy:
+      fail-fast: false
+      matrix: ${{ fromJson(needs.setup.outputs.rp2040) }}
+    uses: ./.github/workflows/build_firmware.yml
+    with:
+      version: ${{ needs.version.outputs.long }}
+      pio_env: ${{ matrix.board }}
+      platform: rp2040
+
+  build-rp2350:
+    if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'rp2350'}}
+    needs: [setup, version]
+    strategy:
+      fail-fast: false
+      matrix: ${{ fromJson(needs.setup.outputs.rp2350) }}
+    uses: ./.github/workflows/build_firmware.yml
+    with:
+      version: ${{ needs.version.outputs.long }}
+      pio_env: ${{ matrix.board }}
+      platform: rp2350
+
+  build-stm32:
+    if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'stm32' }}
+    needs: [setup, version]
+    strategy:
+      fail-fast: false
+      matrix: ${{ fromJson(needs.setup.outputs.stm32) }}
+    uses: ./.github/workflows/build_firmware.yml
+    with:
+      version: ${{ needs.version.outputs.long }}
+      pio_env: ${{ matrix.board }}
+      platform: stm32
+
+  build-debian-src:
+    if: ${{ github.repository == 'meshtastic/firmware' && github.event_name != 'workflow_dispatch' || inputs.arch == 'native' }}
+    uses: ./.github/workflows/build_debian_src.yml
+    with:
+      series: UNRELEASED
+      build_location: local
+    secrets: inherit
+
+  package-pio-deps-native-tft:
+    if: ${{ inputs.arch == 'native' }}
+    uses: ./.github/workflows/package_pio_deps.yml
+    with:
+      pio_env: native-tft
+    secrets: inherit
+
+  test-native:
+    if: ${{ !contains(github.ref_name, 'event/') && github.event_name != 'workflow_dispatch' ||  !contains(github.ref_name, 'event/') && inputs.arch == 'native' }}
+    uses: ./.github/workflows/test_native.yml
+
+  docker-deb-amd64:
+    if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'native' }}
+    uses: ./.github/workflows/docker_build.yml
+    with:
+      distro: debian
+      platform: linux/amd64
+      runs-on: ubuntu-24.04
+      push: false
+
+  docker-deb-amd64-tft:
+    if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'native' }}
+    uses: ./.github/workflows/docker_build.yml
+    with:
+      distro: debian
+      platform: linux/amd64
+      runs-on: ubuntu-24.04
+      push: false
+      pio_env: native-tft
+
+  docker-alp-amd64:
+    if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'native' }}
+    uses: ./.github/workflows/docker_build.yml
+    with:
+      distro: alpine
+      platform: linux/amd64
+      runs-on: ubuntu-24.04
+      push: false
+
+  docker-alp-amd64-tft:
+    if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'native' }}
+    uses: ./.github/workflows/docker_build.yml
+    with:
+      distro: alpine
+      platform: linux/amd64
+      runs-on: ubuntu-24.04
+      push: false
+      pio_env: native-tft
+
+  docker-deb-arm64:
+    if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'native' }}
+    uses: ./.github/workflows/docker_build.yml
+    with:
+      distro: debian
+      platform: linux/arm64
+      runs-on: ubuntu-24.04-arm
+      push: false
+
+  docker-deb-armv7:
+    if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'native' }}
+    uses: ./.github/workflows/docker_build.yml
+    with:
+      distro: debian
+      platform: linux/arm/v7
+      runs-on: ubuntu-24.04-arm
+      push: false
+
+  gather-artifacts:
+    permissions:
+      contents: write
+      pull-requests: write
+    strategy:
+      fail-fast: false
+      matrix:
+        arch:
+          - esp32
+          - esp32s3
+          - esp32c3
+          - esp32c6
+          - nrf52840
+          - rp2040
+          - rp2350
+          - stm32
+    runs-on: ubuntu-latest
+    needs:
+      [
+        version,
+        build-esp32,
+        build-esp32s3,
+        build-esp32c3,
+        build-esp32c6,
+        build-nrf52840,
+        build-rp2040,
+        build-rp2350,
+        build-stm32,
+      ]
+    steps:
+      - name: Checkout code
+        uses: actions/checkout@v4
+        with:
+          ref: ${{github.event.pull_request.head.ref}}
+          repository: ${{github.event.pull_request.head.repo.full_name}}
+
+      - uses: actions/download-artifact@v4
+        with:
+          path: ./
+          pattern: firmware-${{inputs.arch}}-*
+          merge-multiple: true
+
+      - name: Display structure of downloaded files
+        run: ls -R
+
+      - name: Move files up
+        run: mv -b -t ./ ./bin/device-*.sh ./bin/device-*.bat
+
+      - name: Repackage in single firmware zip
+        uses: actions/upload-artifact@v4
+        with:
+          name: firmware-${{inputs.arch}}-${{ needs.version.outputs.long }}
+          overwrite: true
+          path: |
+            ./firmware-*.bin
+            ./firmware-*.uf2
+            ./firmware-*.hex
+            ./firmware-*-ota.zip
+            ./device-*.sh
+            ./device-*.bat
+            ./littlefs-*.bin
+            ./bleota*bin
+            ./Meshtastic_nRF52_factory_erase*.uf2
+          retention-days: 30
+
+      - uses: actions/download-artifact@v4
+        with:
+          name: firmware-${{inputs.arch}}-${{ needs.version.outputs.long }}
+          merge-multiple: true
+          path: ./output
+
+      # For diagnostics
+      - name: Show artifacts
+        run: ls -lR
+
+      - name: Device scripts permissions
+        run: |
+          chmod +x ./output/device-install.sh
+          chmod +x ./output/device-update.sh
+
+      - name: Zip firmware
+        run: zip -j -9 -r ./firmware-${{inputs.arch}}-${{ needs.version.outputs.long }}.zip ./output
+
+      - name: Repackage in single elfs zip
+        uses: actions/upload-artifact@v4
+        with:
+          name: debug-elfs-${{inputs.arch}}-${{ needs.version.outputs.long }}.zip
+          overwrite: true
+          path: ./*.elf
+          retention-days: 30
+
+      - uses: scruplelesswizard/comment-artifact@main
+        if: ${{ github.event_name == 'pull_request' }}
+        with:
+          name: firmware-${{inputs.arch}}-${{ needs.version.outputs.long }}
+          description: "Download firmware-${{inputs.arch}}-${{ needs.version.outputs.long }}.zip. This artifact will be available for 90 days from creation"
+          github-token: ${{ secrets.GITHUB_TOKEN }}
+
+  release-artifacts:
+    runs-on: ubuntu-latest
+    if: ${{ github.event_name == 'workflow_dispatch' }}
+    outputs:
+      upload_url: ${{ steps.create_release.outputs.upload_url }}
+    needs:
+      - version
+      - gather-artifacts
+      - build-debian-src
+      - package-pio-deps-native-tft
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v4
+
+      - name: Setup Python
+        uses: actions/setup-python@v5
+        with:
+          python-version: 3.x
+
+      - name: Create release
+        uses: softprops/action-gh-release@v2
+        id: create_release
+        with:
+          draft: true
+          prerelease: true
+          name: Meshtastic Firmware ${{ needs.version.outputs.long }} Alpha
+          tag_name: v${{ needs.version.outputs.long }}
+          body: |
+            Autogenerated by github action, developer should edit as required before publishing...
+
+      - name: Download source deb
+        uses: actions/download-artifact@v4
+        with:
+          pattern: firmware-debian-${{ needs.version.outputs.deb }}~UNRELEASED-src
+          merge-multiple: true
+          path: ./output/debian-src
+
+      - name: Download `native-tft` pio deps
+        uses: actions/download-artifact@v4
+        with:
+          pattern: platformio-deps-native-tft-${{ needs.version.outputs.long }}
+          merge-multiple: true
+          path: ./output/pio-deps-native-tft
+
+      - name: Zip Linux sources
+        working-directory: output
+        run: |
+          zip -j -9 -r ./meshtasticd-${{ needs.version.outputs.deb }}-src.zip ./debian-src
+          zip -9 -r ./platformio-deps-native-tft-${{ needs.version.outputs.long }}.zip ./pio-deps-native-tft
+
+      # For diagnostics
+      - name: Display structure of downloaded files
+        run: ls -lR
+
+      - name: Add Linux sources to GtiHub Release
+        # Only run when targeting master branch with workflow_dispatch
+        if: ${{ github.ref_name == 'master' }}
+        run: |
+          gh release upload v${{ needs.version.outputs.long }} ./output/meshtasticd-${{ needs.version.outputs.deb }}-src.zip
+          gh release upload v${{ needs.version.outputs.long }} ./output/platformio-deps-native-tft-${{ needs.version.outputs.long }}.zip
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+  release-firmware:
+    strategy:
+      fail-fast: false
+      matrix:
+        arch:
+          - esp32
+          - esp32s3
+          - esp32c3
+          - esp32c6
+          - nrf52840
+          - rp2040
+          - rp2350
+          - stm32
+    runs-on: ubuntu-latest
+    if: ${{ github.event_name == 'workflow_dispatch' }}
+    needs: [release-artifacts, version]
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v4
+
+      - name: Setup Python
+        uses: actions/setup-python@v5
+        with:
+          python-version: 3.x
+
+      - uses: actions/download-artifact@v4
+        with:
+          pattern: firmware-${{inputs.arch}}-${{ needs.version.outputs.long }}
+          merge-multiple: true
+          path: ./output
+
+      - name: Display structure of downloaded files
+        run: ls -lR
+
+      - name: Device scripts permissions
+        run: |
+          chmod +x ./output/device-install.sh
+          chmod +x ./output/device-update.sh
+
+      - name: Zip firmware
+        run: zip -j -9 -r ./firmware-${{inputs.arch}}-${{ needs.version.outputs.long }}.zip ./output
+
+      - uses: actions/download-artifact@v4
+        with:
+          name: debug-elfs-${{inputs.arch}}-${{ needs.version.outputs.long }}.zip
+          merge-multiple: true
+          path: ./elfs
+
+      - name: Zip debug elfs
+        run: zip -j -9 -r ./debug-elfs-${{inputs.arch}}-${{ needs.version.outputs.long }}.zip ./elfs
+
+      # For diagnostics
+      - name: Display structure of downloaded files
+        run: ls -lR
+
+      - name: Add bins and debug elfs to GitHub Release
+        # Only run when targeting master branch with workflow_dispatch
+        if: ${{ github.ref_name == 'master' }}
+        run: |
+          gh release upload v${{ needs.version.outputs.long }} ./firmware-${{inputs.arch}}-${{ needs.version.outputs.long }}.zip
+          gh release upload v${{ needs.version.outputs.long }} ./debug-elfs-${{inputs.arch}}-${{ needs.version.outputs.long }}.zip
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+  publish-firmware:
+    runs-on: ubuntu-24.04
+    if: ${{ github.event_name == 'workflow_dispatch' }}
+    needs: [release-firmware, version]
+    env:
+      targets: |-
+        esp32,esp32s3,esp32c3,esp32c6,nrf52840,rp2040,rp2350,stm32
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v4
+
+      - name: Setup Python
+        uses: actions/setup-python@v5
+        with:
+          python-version: 3.x
+
+      - uses: actions/download-artifact@v4
+        with:
+          pattern: firmware-{${{ env.targets }}}-${{ needs.version.outputs.long }}
+          merge-multiple: true
+          path: ./publish
+
+      - name: Publish firmware to meshtastic.github.io
+        uses: peaceiris/actions-gh-pages@v4
+        env:
+          # On event/* branches, use the event name as the destination prefix
+          DEST_PREFIX: ${{ contains(github.ref_name, 'event/') && format('{0}/', github.ref_name) || '' }}
+        with:
+          deploy_key: ${{ secrets.DIST_PAGES_DEPLOY_KEY }}
+          external_repository: meshtastic/meshtastic.github.io
+          publish_branch: master
+          publish_dir: ./publish
+          destination_dir: ${{ env.DEST_PREFIX }}firmware-${{ needs.version.outputs.long }}
+          keep_files: true
+          user_name: github-actions[bot]
+          user_email: github-actions[bot]@users.noreply.github.com
+          commit_message: ${{ needs.version.outputs.long }}
+          enable_jekyll: true
diff --git a/.github/workflows/build_one_target.yml b/.github/workflows/build_one_target.yml
new file mode 100644
index 000000000..07478ff93
--- /dev/null
+++ b/.github/workflows/build_one_target.yml
@@ -0,0 +1,395 @@
+name: Build One Target
+
+on:
+  workflow_dispatch:
+    inputs:
+      arch:
+        type: choice
+        options:
+          - esp32
+          - esp32s3
+          - esp32c3
+          - esp32c6
+          - nrf52840
+          - rp2040
+          - rp2350
+          - stm32
+          - native
+      target:
+        type: string
+        required: false
+        description: Choose the target board, e.g. nrf52_promicro_diy_tcxo. If blank, will find available targets.
+
+      # find-target:
+      #   type: boolean
+      #   default: true
+      #   description: 'Find the available targets'
+jobs:
+  find-targets:
+    if: ${{ inputs.target == '' }}
+    strategy:
+      fail-fast: false
+      matrix:
+        arch:
+          - esp32
+          - esp32s3
+          - esp32c3
+          - esp32c6
+          - nrf52840
+          - rp2040
+          - rp2350
+          - stm32
+
+    runs-on: ubuntu-24.04
+    steps:
+      - uses: actions/checkout@v4
+      - uses: actions/setup-python@v5
+        with:
+          python-version: 3.x
+          cache: pip
+      - run: pip install -U platformio
+      - name: Generate matrix
+        id: jsonStep
+        run: |
+          TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}} extra)
+          echo "Name: $GITHUB_REF_NAME" >> $GITHUB_STEP_SUMMARY
+          echo "Base: $GITHUB_BASE_REF" >> $GITHUB_STEP_SUMMARY
+          echo "Arch: ${{matrix.arch}}" >> $GITHUB_STEP_SUMMARY
+          echo "Ref: $GITHUB_REF" >> $GITHUB_STEP_SUMMARY
+          echo "Targets:" >> $GITHUB_STEP_SUMMARY
+          echo $TARGETS | sed 's/[][]//g; s/", "/\n- /g; s/"//g; s/^/- /' >> $GITHUB_STEP_SUMMARY
+
+  version:
+    if: ${{ inputs.target != '' }}
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v4
+      - name: Get release version string
+        run: |
+          echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
+          echo "deb=$(./bin/buildinfo.py deb)" >> $GITHUB_OUTPUT
+        id: version
+        env:
+          BUILD_LOCATION: local
+    outputs:
+      long: ${{ steps.version.outputs.long }}
+      deb: ${{ steps.version.outputs.deb }}
+
+  build-arch:
+    if: ${{ inputs.target != '' && inputs.arch != 'native' }}
+    needs: [version]
+    strategy:
+      fail-fast: false
+    uses: ./.github/workflows/build_firmware.yml
+    with:
+      version: ${{ needs.version.outputs.long }}
+      pio_env: ${{ inputs.target }}
+      platform: ${{ inputs.arch }}
+
+  build-debian-src:
+    if: ${{ github.repository == 'meshtastic/firmware' && inputs.arch == 'native' }}
+    uses: ./.github/workflows/build_debian_src.yml
+    with:
+      series: UNRELEASED
+      build_location: local
+    secrets: inherit
+
+  package-pio-deps-native-tft:
+    if: ${{ inputs.arch == 'native' }}
+    uses: ./.github/workflows/package_pio_deps.yml
+    with:
+      pio_env: native-tft
+    secrets: inherit
+
+  test-native:
+    if: ${{ !contains(github.ref_name, 'event/') && github.event_name != 'workflow_dispatch' ||  !contains(github.ref_name, 'event/') && inputs.arch == 'native' && inputs.target != '' }}
+    uses: ./.github/workflows/test_native.yml
+
+  docker-deb-amd64:
+    if: ${{ inputs.target != '' && inputs.arch ==  'native' }}
+    uses: ./.github/workflows/docker_build.yml
+    with:
+      distro: debian
+      platform: linux/amd64
+      runs-on: ubuntu-24.04
+      push: false
+
+  docker-deb-amd64-tft:
+    if: ${{ inputs.target != '' && inputs.arch ==  'native' }}
+    uses: ./.github/workflows/docker_build.yml
+    with:
+      distro: debian
+      platform: linux/amd64
+      runs-on: ubuntu-24.04
+      push: false
+      pio_env: native-tft
+
+  docker-alp-amd64:
+    if: ${{ inputs.target != '' && inputs.arch ==  'native' }}
+    uses: ./.github/workflows/docker_build.yml
+    with:
+      distro: alpine
+      platform: linux/amd64
+      runs-on: ubuntu-24.04
+      push: false
+
+  docker-alp-amd64-tft:
+    if: ${{ inputs.target != '' && inputs.arch ==  'native' }}
+    uses: ./.github/workflows/docker_build.yml
+    with:
+      distro: alpine
+      platform: linux/amd64
+      runs-on: ubuntu-24.04
+      push: false
+      pio_env: native-tft
+
+  docker-deb-arm64:
+    if: ${{ inputs.target != '' && inputs.arch ==  'native' }}
+    uses: ./.github/workflows/docker_build.yml
+    with:
+      distro: debian
+      platform: linux/arm64
+      runs-on: ubuntu-24.04-arm
+      push: false
+
+  docker-deb-armv7:
+    if: ${{ inputs.target != '' && inputs.arch ==  'native' }}
+    uses: ./.github/workflows/docker_build.yml
+    with:
+      distro: debian
+      platform: linux/arm/v7
+      runs-on: ubuntu-24.04-arm
+      push: false
+
+  gather-artifacts:
+    permissions:
+      contents: write
+      pull-requests: write
+    strategy:
+      fail-fast: false
+    runs-on: ubuntu-latest
+    needs: [version, build-arch]
+    steps:
+      - name: Checkout code
+        uses: actions/checkout@v4
+        with:
+          ref: ${{github.event.pull_request.head.ref}}
+          repository: ${{github.event.pull_request.head.repo.full_name}}
+
+      - uses: actions/download-artifact@v4
+        with:
+          path: ./
+          pattern: firmware-*-*
+          merge-multiple: true
+
+      - name: Display structure of downloaded files
+        run: ls -R
+
+      - name: Move files up
+        run: mv -b -t ./ ./bin/device-*.sh ./bin/device-*.bat
+
+      - name: Repackage in single firmware zip
+        uses: actions/upload-artifact@v4
+        with:
+          name: firmware-${{inputs.target}}-${{ needs.version.outputs.long }}
+          overwrite: true
+          path: |
+            ./firmware-*.bin
+            ./firmware-*.uf2
+            ./firmware-*.hex
+            ./firmware-*-ota.zip
+            ./device-*.sh
+            ./device-*.bat
+            ./littlefs-*.bin
+            ./bleota*bin
+            ./Meshtastic_nRF52_factory_erase*.uf2
+          retention-days: 30
+
+      - uses: actions/download-artifact@v4
+        with:
+          pattern: firmware-*-${{ needs.version.outputs.long }}
+          merge-multiple: true
+          path: ./output
+
+      # For diagnostics
+      - name: Show artifacts
+        run: ls -lR
+
+      - name: Device scripts permissions
+        run: |
+          chmod +x ./output/device-install.sh
+          chmod +x ./output/device-update.sh
+
+      - name: Zip firmware
+        run: zip -j -9 -r ./firmware-${{inputs.target}}-${{ needs.version.outputs.long }}.zip ./output
+
+      - name: Repackage in single elfs zip
+        uses: actions/upload-artifact@v4
+        with:
+          name: debug-elfs-${{inputs.target}}-${{ needs.version.outputs.long }}.zip
+          overwrite: true
+          path: ./*.elf
+          retention-days: 30
+
+      - uses: scruplelesswizard/comment-artifact@main
+        if: ${{ github.event_name == 'pull_request' }}
+        with:
+          name: firmware-${{inputs.target}}-${{ needs.version.outputs.long }}
+          description: "Download firmware-${{inputs.target}}-${{ needs.version.outputs.long }}.zip. This artifact will be available for 90 days from creation"
+          github-token: ${{ secrets.GITHUB_TOKEN }}
+
+  release-artifacts:
+    runs-on: ubuntu-latest
+    if: ${{ github.event_name == 'workflow_dispatch' && inputs.target != ''}}
+    outputs:
+      upload_url: ${{ steps.create_release.outputs.upload_url }}
+    needs:
+      - version
+      - gather-artifacts
+      - build-debian-src
+      - package-pio-deps-native-tft
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v4
+
+      - name: Setup Python
+        uses: actions/setup-python@v5
+        with:
+          python-version: 3.x
+
+      - name: Create release
+        uses: softprops/action-gh-release@v2
+        id: create_release
+        with:
+          draft: true
+          prerelease: true
+          name: Meshtastic Firmware ${{ needs.version.outputs.long }} Alpha
+          tag_name: v${{ needs.version.outputs.long }}
+          body: |
+            Autogenerated by github action, developer should edit as required before publishing...
+
+      - name: Download source deb
+        uses: actions/download-artifact@v4
+        with:
+          pattern: firmware-debian-${{ needs.version.outputs.deb }}~UNRELEASED-src
+          merge-multiple: true
+          path: ./output/debian-src
+
+      - name: Download `native-tft` pio deps
+        uses: actions/download-artifact@v4
+        with:
+          pattern: platformio-deps-native-tft-${{ needs.version.outputs.long }}
+          merge-multiple: true
+          path: ./output/pio-deps-native-tft
+
+      - name: Zip Linux sources
+        working-directory: output
+        run: |
+          zip -j -9 -r ./meshtasticd-${{ needs.version.outputs.deb }}-src.zip ./debian-src
+          zip -9 -r ./platformio-deps-native-tft-${{ needs.version.outputs.long }}.zip ./pio-deps-native-tft
+
+      # For diagnostics
+      - name: Display structure of downloaded files
+        run: ls -lR
+
+      - name: Add Linux sources to GtiHub Release
+        # Only run when targeting master branch with workflow_dispatch
+        if: ${{ github.ref_name == 'master' }}
+        run: |
+          gh release upload v${{ needs.version.outputs.long }} ./output/meshtasticd-${{ needs.version.outputs.deb }}-src.zip
+          gh release upload v${{ needs.version.outputs.long }} ./output/platformio-deps-native-tft-${{ needs.version.outputs.long }}.zip
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+  release-firmware:
+    strategy:
+      fail-fast: false
+    runs-on: ubuntu-latest
+    if: ${{ github.event_name == 'workflow_dispatch' && inputs.target != ''}}
+    needs: [release-artifacts, version]
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v4
+
+      - name: Setup Python
+        uses: actions/setup-python@v5
+        with:
+          python-version: 3.x
+
+      - uses: actions/download-artifact@v4
+        with:
+          pattern: firmware-*-${{ needs.version.outputs.long }}
+          merge-multiple: true
+          path: ./output
+
+      - name: Display structure of downloaded files
+        run: ls -lR
+
+      - name: Device scripts permissions
+        run: |
+          chmod +x ./output/device-install.sh
+          chmod +x ./output/device-update.sh
+
+      - name: Zip firmware
+        run: zip -j -9 -r ./firmware-${{inputs.target}}-${{ needs.version.outputs.long }}.zip ./output
+
+      - uses: actions/download-artifact@v4
+        with:
+          pattern: debug-elfs-*-${{ needs.version.outputs.long }}.zip
+          merge-multiple: true
+          path: ./elfs
+
+      - name: Zip debug elfs
+        run: zip -j -9 -r ./debug-elfs-${{inputs.target}}-${{ needs.version.outputs.long }}.zip ./elfs
+
+      # For diagnostics
+      - name: Display structure of downloaded files
+        run: ls -lR
+
+      - name: Add bins and debug elfs to GitHub Release
+        # Only run when targeting master branch with workflow_dispatch
+        if: ${{ github.ref_name == 'master' }}
+        run: |
+          gh release upload v${{ needs.version.outputs.long }} ./firmware-${{inputs.target}}-${{ needs.version.outputs.long }}.zip
+          gh release upload v${{ needs.version.outputs.long }} ./debug-elfs-${{inputs.target}}-${{ needs.version.outputs.long }}.zip
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+  publish-firmware:
+    runs-on: ubuntu-24.04
+    if: ${{ github.event_name == 'workflow_dispatch' && github.repository == 'meshtastic/firmware' && inputs.target != '' }}
+    needs: [release-firmware, version]
+    env:
+      targets: |-
+        esp32,esp32s3,esp32c3,esp32c6,nrf52840,rp2040,rp2350,stm32
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v4
+
+      - name: Setup Python
+        uses: actions/setup-python@v5
+        with:
+          python-version: 3.x
+
+      - uses: actions/download-artifact@v4
+        with:
+          pattern: firmware-{${{ env.targets }}}-${{ needs.version.outputs.long }}
+          merge-multiple: true
+          path: ./publish
+
+      - name: Publish firmware to meshtastic.github.io
+        uses: peaceiris/actions-gh-pages@v4
+        env:
+          # On event/* branches, use the event name as the destination prefix
+          DEST_PREFIX: ${{ contains(github.ref_name, 'event/') && format('{0}/', github.ref_name) || '' }}
+        with:
+          deploy_key: ${{ secrets.DIST_PAGES_DEPLOY_KEY }}
+          external_repository: meshtastic/meshtastic.github.io
+          publish_branch: master
+          publish_dir: ./publish
+          destination_dir: ${{ env.DEST_PREFIX }}firmware-${{ needs.version.outputs.long }}
+          keep_files: true
+          user_name: github-actions[bot]
+          user_email: github-actions[bot]@users.noreply.github.com
+          commit_message: ${{ needs.version.outputs.long }}
+          enable_jekyll: true
diff --git a/.github/workflows/daily_packaging.yml b/.github/workflows/daily_packaging.yml
index df5ed27d5..392faeb8a 100644
--- a/.github/workflows/daily_packaging.yml
+++ b/.github/workflows/daily_packaging.yml
@@ -21,12 +21,14 @@ permissions:
 
 jobs:
   docker-multiarch:
+    if: github.repository == 'meshtastic/firmware'
     uses: ./.github/workflows/docker_manifest.yml
     with:
       release_channel: daily
     secrets: inherit
 
   package-ppa:
+    if: github.repository == 'meshtastic/firmware'
     strategy:
       fail-fast: false
       matrix:
@@ -42,6 +44,7 @@ jobs:
     secrets: inherit
 
   package-obs:
+    if: github.repository == 'meshtastic/firmware'
     uses: ./.github/workflows/package_obs.yml
     with:
       obs_project: network:Meshtastic:daily
@@ -49,6 +52,7 @@ jobs:
     secrets: inherit
 
   hook-copr:
+    if: github.repository == 'meshtastic/firmware'
     uses: ./.github/workflows/hook_copr.yml
     with:
       copr_project: daily
diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml
index 6ff12221b..9b4199223 100644
--- a/.github/workflows/main_matrix.yml
+++ b/.github/workflows/main_matrix.yml
@@ -27,6 +27,7 @@ on:
 
 jobs:
   setup:
+    if: github.repository == 'meshtastic/firmware'
     strategy:
       fail-fast: false
       matrix:
@@ -74,6 +75,7 @@ jobs:
       check: ${{ steps.jsonStep.outputs.check }}
 
   version:
+    if: github.repository == 'meshtastic/firmware'
     runs-on: ubuntu-latest
     steps:
       - uses: actions/checkout@v5
@@ -95,7 +97,7 @@ jobs:
       matrix: ${{ fromJson(needs.setup.outputs.check) }}
 
     runs-on: ubuntu-latest
-    if: ${{ github.event_name != 'workflow_dispatch' }}
+    if: ${{ github.event_name != 'workflow_dispatch' && github.repository == 'meshtastic/firmware' }}
     steps:
       - uses: actions/checkout@v5
       - name: Build base
@@ -208,10 +210,11 @@ jobs:
     secrets: inherit
 
   test-native:
-    if: ${{ !contains(github.ref_name, 'event/') }}
+    if: ${{ !contains(github.ref_name, 'event/') && github.repository == 'meshtastic/firmware' }}
     uses: ./.github/workflows/test_native.yml
 
   docker-deb-amd64:
+    if: github.repository == 'meshtastic/firmware'
     uses: ./.github/workflows/docker_build.yml
     with:
       distro: debian
@@ -220,6 +223,7 @@ jobs:
       push: false
 
   docker-deb-amd64-tft:
+    if: github.repository == 'meshtastic/firmware'
     uses: ./.github/workflows/docker_build.yml
     with:
       distro: debian
@@ -229,6 +233,7 @@ jobs:
       pio_env: native-tft
 
   docker-alp-amd64:
+    if: github.repository == 'meshtastic/firmware'
     uses: ./.github/workflows/docker_build.yml
     with:
       distro: alpine
@@ -237,6 +242,7 @@ jobs:
       push: false
 
   docker-alp-amd64-tft:
+    if: github.repository == 'meshtastic/firmware'
     uses: ./.github/workflows/docker_build.yml
     with:
       distro: alpine
@@ -246,6 +252,7 @@ jobs:
       pio_env: native-tft
 
   docker-deb-arm64:
+    if: github.repository == 'meshtastic/firmware'
     uses: ./.github/workflows/docker_build.yml
     with:
       distro: debian
@@ -254,6 +261,7 @@ jobs:
       push: false
 
   docker-deb-armv7:
+    if: github.repository == 'meshtastic/firmware'
     uses: ./.github/workflows/docker_build.yml
     with:
       distro: debian
@@ -262,6 +270,7 @@ jobs:
       push: false
 
   gather-artifacts:
+    if: github.repository == 'meshtastic/firmware'
     permissions:
       contents: write
       pull-requests: write
@@ -361,7 +370,7 @@ jobs:
 
   release-artifacts:
     runs-on: ubuntu-latest
-    if: ${{ github.event_name == 'workflow_dispatch' }}
+    if: ${{ github.event_name == 'workflow_dispatch' && github.repository == 'meshtastic/firmware' }}
     outputs:
       upload_url: ${{ steps.create_release.outputs.upload_url }}
     needs:
@@ -436,7 +445,7 @@ jobs:
           - rp2350
           - stm32
     runs-on: ubuntu-latest
-    if: ${{ github.event_name == 'workflow_dispatch' }}
+    if: ${{ github.event_name == 'workflow_dispatch' && github.repository == 'meshtastic/firmware'}}
     needs: [release-artifacts, version]
     steps:
       - name: Checkout

From 13ebceb3bc5d01a4b3d82187e82ae9d45461a700 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Tue, 16 Sep 2025 06:42:08 -0500
Subject: [PATCH 235/691] Update meshtastic/device-ui digest to 9ed5355 (#7987)

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 47b5f823d..e2eb55dce 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/3677476c8a823ee85056b5fb1d146a3e193f8276.zip
+	https://github.com/meshtastic/device-ui/archive/9ed5355a24059750e9b2eb5d669574d9ea42a37b.zip
 
 ; Common libs for environmental measurements in telemetry module
 [environmental_base]

From cc3c568501cb258ffc97487fab731610f6e5120b Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
 <41898282+github-actions[bot]@users.noreply.github.com>
Date: Tue, 16 Sep 2025 07:20:44 -0500
Subject: [PATCH 236/691] Update protobufs (#8005)

Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
---
 protobufs                               | 2 +-
 src/mesh/generated/meshtastic/mesh.pb.h | 6 ++++--
 2 files changed, 5 insertions(+), 3 deletions(-)

diff --git a/protobufs b/protobufs
index 8caf42396..945b796a9 160000
--- a/protobufs
+++ b/protobufs
@@ -1 +1 @@
-Subproject commit 8caf42396438f0d8a0305143485fd671c1fc7126
+Subproject commit 945b796a982f38171a9e0d28b5c8b1f7d53c5cd1
diff --git a/src/mesh/generated/meshtastic/mesh.pb.h b/src/mesh/generated/meshtastic/mesh.pb.h
index 2a4e77870..294f0beac 100644
--- a/src/mesh/generated/meshtastic/mesh.pb.h
+++ b/src/mesh/generated/meshtastic/mesh.pb.h
@@ -259,8 +259,8 @@ typedef enum _meshtastic_HardwareModel {
     meshtastic_HardwareModel_T_DECK_PRO = 102,
     /* Lilygo TLora Pager */
     meshtastic_HardwareModel_T_LORA_PAGER = 103,
-    /* GAT562 Mesh Trial Tracker */
-    meshtastic_HardwareModel_GAT562_MESH_TRIAL_TRACKER = 104,
+    /* M5Stack Reserved */
+    meshtastic_HardwareModel_M5STACK_RESERVED = 104, /* 0x68 */
     /* RAKwireless WisMesh Tag */
     meshtastic_HardwareModel_WISMESH_TAG = 105,
     /* RAKwireless WisBlock Core RAK3312 https://docs.rakwireless.com/product-categories/wisduo/rak3112-module/overview/ */
@@ -274,6 +274,8 @@ typedef enum _meshtastic_HardwareModel {
     meshtastic_HardwareModel_T_ECHO_LITE = 109,
     /* New Heltec LoRA32 with ESP32-S3 CPU */
     meshtastic_HardwareModel_HELTEC_V4 = 110,
+    /* M5Stack C6L */
+    meshtastic_HardwareModel_M5STACK_C6L = 111,
     /* ------------------------------------------------------------------------------------------------------------------------------------------
  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 d427b477e39f77434e08b6d7b9f1199b995c9d62 Mon Sep 17 00:00:00 2001
From: WillyJL 
Date: Wed, 17 Sep 2025 02:07:24 +0200
Subject: [PATCH 237/691] Format

---
 src/input/RotaryEncoderImpl.cpp | 10 ++++------
 src/input/RotaryEncoderImpl.h   |  4 ++--
 2 files changed, 6 insertions(+), 8 deletions(-)

diff --git a/src/input/RotaryEncoderImpl.cpp b/src/input/RotaryEncoderImpl.cpp
index cede1b87c..216e92382 100644
--- a/src/input/RotaryEncoderImpl.cpp
+++ b/src/input/RotaryEncoderImpl.cpp
@@ -35,9 +35,7 @@ bool RotaryEncoderImpl::init()
     inputQueue = xQueueCreate(5, sizeof(input_broker_event));
     interruptFlag = xEventGroupCreate();
     interruptInstance = this;
-    auto interruptHandler = []() {
-        xEventGroupSetBits(interruptInstance->interruptFlag, ROTARY_INTERRUPT_FLAG);
-    };
+    auto interruptHandler = []() { xEventGroupSetBits(interruptInstance->interruptFlag, ROTARY_INTERRUPT_FLAG); };
     attachInterrupt(moduleConfig.canned_message.inputbroker_pin_a, interruptHandler, CHANGE);
     attachInterrupt(moduleConfig.canned_message.inputbroker_pin_b, interruptHandler, CHANGE);
     attachInterrupt(moduleConfig.canned_message.inputbroker_pin_press, interruptHandler, CHANGE);
@@ -78,7 +76,7 @@ void RotaryEncoderImpl::dispatchInputs()
 
 void RotaryEncoderImpl::inputWorker(void *p)
 {
-    RotaryEncoderImpl* instance = (RotaryEncoderImpl*)p;
+    RotaryEncoderImpl *instance = (RotaryEncoderImpl *)p;
     while (true) {
         xEventGroupWaitBits(instance->interruptFlag, ROTARY_INTERRUPT_FLAG, pdTRUE, pdTRUE, portMAX_DELAY);
         instance->dispatchInputs();
@@ -86,12 +84,12 @@ void RotaryEncoderImpl::inputWorker(void *p)
     vTaskDelete(NULL);
 }
 
-RotaryEncoderImpl* RotaryEncoderImpl::interruptInstance;
+RotaryEncoderImpl *RotaryEncoderImpl::interruptInstance;
 
 int32_t RotaryEncoderImpl::runOnce()
 {
     InputEvent e{originName, INPUT_BROKER_NONE, 0, 0, 0};
-    while(xQueueReceive(inputQueue, &e.inputEvent, 0) == pdPASS) {
+    while (xQueueReceive(inputQueue, &e.inputEvent, 0) == pdPASS) {
         this->notifyObservers(&e);
     }
     return 10;
diff --git a/src/input/RotaryEncoderImpl.h b/src/input/RotaryEncoderImpl.h
index 4922b4333..e5ff251e8 100644
--- a/src/input/RotaryEncoderImpl.h
+++ b/src/input/RotaryEncoderImpl.h
@@ -23,8 +23,8 @@ class RotaryEncoderImpl : public Observable, public concurre
     void dispatchInputs(void);
     TaskHandle_t inputWorkerTask;
     static void inputWorker(void *p);
-    EventGroupHandle_t  interruptFlag;
-    static RotaryEncoderImpl* interruptInstance;
+    EventGroupHandle_t interruptFlag;
+    static RotaryEncoderImpl *interruptInstance;
 
     input_broker_event eventCw = INPUT_BROKER_NONE;
     input_broker_event eventCcw = INPUT_BROKER_NONE;

From 6f5bdd73cb7dd05ee70584f28b9378ef87b9ac48 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
 <41898282+github-actions[bot]@users.noreply.github.com>
Date: Wed, 17 Sep 2025 06:09:18 -0500
Subject: [PATCH 238/691] Upgrade trunk (#7868)

Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com>
---
 .trunk/trunk.yaml | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml
index e10e20a04..c1fde9602 100644
--- a/.trunk/trunk.yaml
+++ b/.trunk/trunk.yaml
@@ -8,15 +8,15 @@ plugins:
       uri: https://github.com/trunk-io/plugins
 lint:
   enabled:
-    - checkov@3.2.469
-    - renovate@41.94.0
+    - checkov@3.2.471
+    - renovate@41.115.2
     - prettier@3.6.2
-    - trufflehog@3.90.5
+    - trufflehog@3.90.6
     - yamllint@1.37.1
     - bandit@1.8.6
     - trivy@0.66.0
     - taplo@0.10.0
-    - ruff@0.12.11
+    - ruff@0.13.0
     - isort@6.0.1
     - markdownlint@0.45.0
     - oxipng@9.1.5

From ba18467bd1459aae0aca9546a2f94571b8ed5f1c Mon Sep 17 00:00:00 2001
From: Jonathan Bennett 
Date: Wed, 17 Sep 2025 08:37:51 -0500
Subject: [PATCH 239/691] Auto-favorite remote admin node

---
 src/modules/AdminModule.cpp | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp
index 78c101765..441a6e12b 100644
--- a/src/modules/AdminModule.cpp
+++ b/src/modules/AdminModule.cpp
@@ -104,6 +104,10 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta
             (config.security.admin_key[2].size == 32 &&
              memcmp(mp.public_key.bytes, config.security.admin_key[2].bytes, 32) == 0)) {
             LOG_INFO("PKC admin payload with authorized sender key");
+            auto remoteNode = nodeDB->getMeshNode(mp.from);
+            if (remoteNode && !remoteNode->is_favorite) {
+                remoteNode->is_favorite = true;
+            }
         } else {
             myReply = allocErrorResponse(meshtastic_Routing_Error_ADMIN_PUBLIC_KEY_UNAUTHORIZED, &mp);
             LOG_INFO("Received PKC admin payload, but the sender public key does not match the admin authorized key!");

From 71d84404c62af883eb73c261eddb31968d202bd4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= 
Date: Wed, 17 Sep 2025 22:40:55 +0200
Subject: [PATCH 240/691] add WIP for Unit C6L (#7433)

* add WIP for Unit C6L
* adapt to new config structure
* Add c6l BLE and screen support (#7991)
* Minor c6l fix
* Move out of PRIVATE_HW
---------
Co-authored-by: Austin 
Co-authored-by: Ben Meadors 
Co-authored-by: Jonathan Bennett 
Co-authored-by: Jason P 
Co-authored-by: Markus 
---
 src/AmbientLightingThread.h                   |   6 +-
 src/detect/ScanI2CTwoWire.cpp                 |   2 +
 src/graphics/Screen.cpp                       |  40 +++++-
 src/graphics/Screen.h                         |   2 +
 src/graphics/ScreenFonts.h                    |   4 +
 src/graphics/SharedUIDisplay.cpp              |   4 +-
 src/graphics/draw/DebugRenderer.cpp           |  42 +++++-
 src/graphics/draw/MenuHandler.cpp             |  83 ++++++++++-
 src/graphics/draw/MenuHandler.h               |   1 +
 src/graphics/draw/MessageRenderer.cpp         |  70 +++++++++-
 src/graphics/draw/NodeListRenderer.cpp        |  32 ++++-
 src/graphics/draw/NotificationRenderer.cpp    | 130 ++++++++++++++++++
 src/graphics/draw/UIRenderer.cpp              |  88 ++++++++++--
 src/graphics/images.h                         |   5 +-
 src/graphics/img/icon_small.xbm               |  30 ++++
 src/input/i2cButton.cpp                       |  95 +++++++++++++
 src/input/i2cButton.h                         |  18 +++
 src/main.cpp                                  |  13 +-
 src/mesh/NodeDB.cpp                           |   2 +-
 src/modules/CannedMessageModule.cpp           |  20 ++-
 src/modules/Modules.cpp                       |   4 +
 src/nimble/NimbleBluetooth.cpp                | 108 ++++++++++++++-
 src/nimble/NimbleBluetooth.h                  |   5 +
 src/platform/esp32/architecture.h             |   2 +
 .../esp32c6/m5stack_unitc6l/pins_arduino.h    |  28 ++++
 .../esp32c6/m5stack_unitc6l/platformio.ini    |  35 +++++
 variants/esp32c6/m5stack_unitc6l/variant.cpp  |  74 ++++++++++
 variants/esp32c6/m5stack_unitc6l/variant.h    |  52 +++++++
 28 files changed, 952 insertions(+), 43 deletions(-)
 create mode 100644 src/graphics/img/icon_small.xbm
 create mode 100644 src/input/i2cButton.cpp
 create mode 100644 src/input/i2cButton.h
 create mode 100644 variants/esp32c6/m5stack_unitc6l/pins_arduino.h
 create mode 100644 variants/esp32c6/m5stack_unitc6l/platformio.ini
 create mode 100644 variants/esp32c6/m5stack_unitc6l/variant.cpp
 create mode 100644 variants/esp32c6/m5stack_unitc6l/variant.h

diff --git a/src/AmbientLightingThread.h b/src/AmbientLightingThread.h
index e4ef3b443..947b1e054 100644
--- a/src/AmbientLightingThread.h
+++ b/src/AmbientLightingThread.h
@@ -183,9 +183,9 @@ class AmbientLightingThread : public concurrency::OSThread
 #endif
 #endif
             pixels.show();
-            LOG_DEBUG("Init NeoPixel Ambient light w/ brightness(current)=%d, red=%d, green=%d, blue=%d",
-                      moduleConfig.ambient_lighting.current, moduleConfig.ambient_lighting.red,
-                      moduleConfig.ambient_lighting.green, moduleConfig.ambient_lighting.blue);
+            // LOG_DEBUG("Init NeoPixel Ambient light w/ brightness(current)=%d, red=%d, green=%d, blue=%d",
+            //        moduleConfig.ambient_lighting.current, moduleConfig.ambient_lighting.red,
+            //        moduleConfig.ambient_lighting.green, moduleConfig.ambient_lighting.blue);
 #endif
 #ifdef RGBLED_CA
             analogWrite(RGBLED_RED, 255 - moduleConfig.ambient_lighting.red);
diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp
index 5cb4fca32..01a630b52 100644
--- a/src/detect/ScanI2CTwoWire.cpp
+++ b/src/detect/ScanI2CTwoWire.cpp
@@ -294,6 +294,7 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
                 type = AHT10;
                 break;
 #endif
+#if !defined(M5STACK_UNITC6L)
             case INA_ADDR:
             case INA_ADDR_ALTERNATE:
             case INA_ADDR_WAVESHARE_UPS:
@@ -340,6 +341,7 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
                     // else: probably a RAK12500/UBLOX GPS on I2C
                 }
                 break;
+#endif
             case MCP9808_ADDR:
                 // We need to check for STK8BAXX first, since register 0x07 is new data flag for the z-axis and can produce some
                 // weird result. and register 0x00 doesn't seems to be colliding with MCP9808 and LIS3DH chips.
diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp
index dea08d5ba..0a2229d0e 100644
--- a/src/graphics/Screen.cpp
+++ b/src/graphics/Screen.cpp
@@ -317,6 +317,14 @@ Screen::Screen(ScanI2C::DeviceAddress address, meshtastic_Config_DisplayConfig_O
 #elif defined(USE_SSD1306)
     dispdev = new SSD1306Wire(address.address, -1, -1, geometry,
                               (address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE);
+#elif defined(USE_SPISSD1306)
+    dispdev = new SSD1306Spi(SSD1306_RESET, SSD1306_RS, SSD1306_NSS, GEOMETRY_64_48);
+    if (!dispdev->init()) {
+        LOG_DEBUG("Error: SSD1306 not detected!");
+    } else {
+        static_cast(dispdev)->setHorizontalOffset(32);
+        LOG_INFO("SSD1306 init success");
+    }
 #elif defined(ST7735_CS) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7789_CS) ||    \
     defined(RAK14014) || defined(HX8357_CS) || defined(ILI9488_CS) || defined(ST7796_CS)
     dispdev = new TFTDisplay(address.address, -1, -1, geometry,
@@ -507,7 +515,7 @@ void Screen::setup()
     // === Apply loaded brightness ===
 #if defined(ST7789_CS)
     static_cast(dispdev)->setDisplayBrightness(brightness);
-#elif defined(USE_OLED) || defined(USE_SSD1306) || defined(USE_SH1106) || defined(USE_SH1107)
+#elif defined(USE_OLED) || defined(USE_SSD1306) || defined(USE_SH1106) || defined(USE_SH1107) || defined(USE_SPISSD1306)
     dispdev->setBrightness(brightness);
 #endif
     LOG_INFO("Applied screen brightness: %d", brightness);
@@ -554,7 +562,7 @@ void Screen::setup()
         static_cast(dispdev)->flipScreenVertically();
 #elif defined(USE_ST7789)
         static_cast(dispdev)->flipScreenVertically();
-#else
+#elif !defined(M5STACK_UNITC6L)
         dispdev->flipScreenVertically();
 #endif
     }
@@ -692,7 +700,11 @@ int32_t Screen::runOnce()
 
 #ifndef DISABLE_WELCOME_UNSET
     if (!NotificationRenderer::isOverlayBannerShowing() && config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_UNSET) {
+#if defined(M5STACK_UNITC6L)
+        menuHandler::LoraRegionPicker();
+#else
         menuHandler::OnboardMessage();
+#endif
     }
 #endif
     if (!NotificationRenderer::isOverlayBannerShowing() && rebootAtMsec != 0) {
@@ -890,8 +902,12 @@ void Screen::setFrames(FrameFocus focus)
 
 #if defined(DISPLAY_CLOCK_FRAME)
     fsi.positions.clock = numframes;
+#if defined(M5STACK_UNITC6L)
+    normalFrames[numframes++] = graphics::ClockRenderer::drawAnalogClockFrame;
+#else
     normalFrames[numframes++] = uiconfig.is_clockface_analog ? graphics::ClockRenderer::drawAnalogClockFrame
                                                              : graphics::ClockRenderer::drawDigitalClockFrame;
+#endif
     indicatorIcons.push_back(digital_icon_clock);
 #endif
 
@@ -1226,6 +1242,10 @@ void Screen::handleShowNextFrame()
 
 void Screen::setFastFramerate()
 {
+#if defined(M5STACK_UNITC6L)
+    dispdev->clear();
+    dispdev->display();
+#endif
     // We are about to start a transition so speed up fps
     targetFramerate = SCREEN_TRANSITION_FRAMERATE;
 
@@ -1297,13 +1317,23 @@ int Screen::handleTextMessage(const meshtastic_MeshPacket *packet)
                 }
             } else {
                 if (longName && longName[0]) {
+#if defined(M5STACK_UNITC6L)
+                    strcpy(banner, "New Message");
+#else
                     snprintf(banner, sizeof(banner), "New Message from\n%s", longName);
+#endif
+
                 } else {
                     strcpy(banner, "New Message");
                 }
             }
-
+#if defined(M5STACK_UNITC6L)
+            screen->setOn(true);
+            screen->showSimpleBanner(banner, 1500);
+            playLongBeep();
+#else
             screen->showSimpleBanner(banner, 3000);
+#endif
         }
     }
 
@@ -1386,7 +1416,11 @@ int Screen::handleInputEvent(const InputEvent *event)
                     if (devicestate.rx_text_message.from) {
                         menuHandler::messageResponseMenu();
                     } else {
+#if defined(M5STACK_UNITC6L)
+                        menuHandler::textMessageMenu();
+#else
                         menuHandler::textMessageBaseMenu();
+#endif
                     }
                 } else if (framesetInfo.positions.firstFavorite != 255 &&
                            this->ui->getUiState()->currentFrame >= framesetInfo.positions.firstFavorite &&
diff --git a/src/graphics/Screen.h b/src/graphics/Screen.h
index 265900131..ecc39ac60 100644
--- a/src/graphics/Screen.h
+++ b/src/graphics/Screen.h
@@ -81,6 +81,8 @@ class Screen
 #include 
 #elif defined(USE_ST7789)
 #include 
+#elif defined(USE_SPISSD1306)
+#include 
 #else
 // the SH1106/SSD1306 variant is auto-detected
 #include 
diff --git a/src/graphics/ScreenFonts.h b/src/graphics/ScreenFonts.h
index a25417b05..c497a27b2 100644
--- a/src/graphics/ScreenFonts.h
+++ b/src/graphics/ScreenFonts.h
@@ -79,6 +79,10 @@
 #define FONT_SMALL FONT_MEDIUM_LOCAL // Height: 19
 #define FONT_MEDIUM FONT_LARGE_LOCAL // Height: 28
 #define FONT_LARGE FONT_LARGE_LOCAL  // Height: 28
+#elif defined(M5STACK_UNITC6L)
+#define FONT_SMALL FONT_SMALL_LOCAL  // Height: 13
+#define FONT_MEDIUM FONT_SMALL_LOCAL // Height: 13
+#define FONT_LARGE FONT_SMALL_LOCAL  // Height: 13
 #else
 #define FONT_SMALL FONT_SMALL_LOCAL   // Height: 13
 #define FONT_MEDIUM FONT_MEDIUM_LOCAL // Height: 19
diff --git a/src/graphics/SharedUIDisplay.cpp b/src/graphics/SharedUIDisplay.cpp
index b458e54e4..13691665a 100644
--- a/src/graphics/SharedUIDisplay.cpp
+++ b/src/graphics/SharedUIDisplay.cpp
@@ -124,7 +124,7 @@ void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *ti
 
     int batteryX = 1;
     int batteryY = HEADER_OFFSET_Y + 1;
-
+#if !defined(M5STACK_UNITC6L)
     // === Battery Icons ===
     if (usbPowered && !isCharging) { // This is a basic check to determine USB Powered is flagged but not charging
         batteryX += 1;
@@ -337,7 +337,7 @@ void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *ti
             }
         }
     }
-
+#endif
     display->setColor(WHITE); // Reset for other UI
 }
 
diff --git a/src/graphics/draw/DebugRenderer.cpp b/src/graphics/draw/DebugRenderer.cpp
index d5835a335..6137ddef8 100644
--- a/src/graphics/draw/DebugRenderer.cpp
+++ b/src/graphics/draw/DebugRenderer.cpp
@@ -277,12 +277,13 @@ void drawFrameSettings(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t
     std::string uptime = UIRenderer::drawTimeDelta(days, hours, minutes, seconds);
 
     // Line 1 (Still)
+#if !defined(M5STACK_UNITC6L)
     display->drawString(x + SCREEN_WIDTH - display->getStringWidth(uptime.c_str()), y, uptime.c_str());
     if (config.display.heading_bold)
         display->drawString(x - 1 + SCREEN_WIDTH - display->getStringWidth(uptime.c_str()), y, uptime.c_str());
 
     display->setColor(WHITE);
-
+#endif
     // Setup string to assemble analogClock string
     std::string analogClock = "";
 
@@ -386,17 +387,24 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
     char shortnameble[35];
     getMacAddr(dmac);
     snprintf(screen->ourId, sizeof(screen->ourId), "%02x%02x", dmac[4], dmac[5]);
+#if defined(M5STACK_UNITC6L)
+    snprintf(shortnameble, sizeof(shortnameble), "%s", screen->ourId);
+#else
     snprintf(shortnameble, sizeof(shortnameble), "BLE: %s", screen->ourId);
+#endif
     int textWidth = display->getStringWidth(shortnameble);
     int nameX = (SCREEN_WIDTH - textWidth);
     display->drawString(nameX, getTextPositions(display)[line++], shortnameble);
-
     // === Second 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) {
+#if defined(M5STACK_UNITC6L)
+        snprintf(regionradiopreset, sizeof(regionradiopreset), "%s", region);
+#else
         snprintf(regionradiopreset, sizeof(regionradiopreset), "%s/%s", region, mode);
+#endif
     }
     textWidth = display->getStringWidth(regionradiopreset);
     nameX = (SCREEN_WIDTH - textWidth) / 2;
@@ -408,9 +416,17 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
     float freq = RadioLibInterface::instance->getFreq();
     snprintf(freqStr, sizeof(freqStr), "%.3f", freq);
     if (config.lora.channel_num == 0) {
+#if defined(M5STACK_UNITC6L)
+        snprintf(frequencyslot, sizeof(frequencyslot), "%sMHz", freqStr);
+#else
         snprintf(frequencyslot, sizeof(frequencyslot), "Freq: %sMHz", freqStr);
+#endif
     } else {
+#if defined(M5STACK_UNITC6L)
+        snprintf(frequencyslot, sizeof(frequencyslot), "%sMHz (%d)", freqStr, config.lora.channel_num);
+#else
         snprintf(frequencyslot, sizeof(frequencyslot), "Freq/Ch: %sMHz (%d)", freqStr, config.lora.channel_num);
+#endif
     }
     size_t len = strlen(frequencyslot);
     if (len >= 4 && strcmp(frequencyslot + len - 4, " (0)") == 0) {
@@ -420,6 +436,7 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
     nameX = (SCREEN_WIDTH - textWidth) / 2;
     display->drawString(nameX, getTextPositions(display)[line++], frequencyslot);
 
+#if !defined(M5STACK_UNITC6L)
     // === Fourth Row: Channel Utilization ===
     const char *chUtil = "ChUtil:";
     char chUtilPercentage[10];
@@ -476,6 +493,7 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
 
     display->drawString(starting_position + chUtil_x + chutil_bar_width + extraoffset, getTextPositions(display)[4],
                         chUtilPercentage);
+#endif
 }
 
 // ****************************
@@ -501,8 +519,11 @@ void drawMemoryUsage(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
 #ifdef USE_EINK
     barsOffset -= 12;
 #endif
+#if defined(M5STACK_UNITC6L)
+    const int barX = x + 45 + barsOffset;
+#else
     const int barX = x + 40 + barsOffset;
-
+#endif
     auto drawUsageRow = [&](const char *label, uint32_t used, uint32_t total, bool isHeap = false) {
         if (total == 0)
             return;
@@ -527,7 +548,7 @@ void drawMemoryUsage(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
         // Label
         display->setTextAlignment(TEXT_ALIGN_LEFT);
         display->drawString(labelX, getTextPositions(display)[line], label);
-
+#if !defined(M5STACK_UNITC6L)
         // Bar
         int barY = getTextPositions(display)[line] + (FONT_HEIGHT_SMALL - barHeight) / 2;
         display->setColor(WHITE);
@@ -535,7 +556,7 @@ void drawMemoryUsage(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
 
         display->fillRect(barX, barY, fillWidth, barHeight);
         display->setColor(WHITE);
-
+#endif
         // Value string
         display->setTextAlignment(TEXT_ALIGN_RIGHT);
         display->drawString(SCREEN_WIDTH - 2, getTextPositions(display)[line], combinedStr);
@@ -588,10 +609,16 @@ void drawMemoryUsage(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
         line += 1;
     }
     line += 1;
+
     char appversionstr[35];
     snprintf(appversionstr, sizeof(appversionstr), "Ver: %s", optstr(APP_VERSION));
     char appversionstr_formatted[40];
     char *lastDot = strrchr(appversionstr, '.');
+#if defined(M5STACK_UNITC6L)
+    if (lastDot != nullptr) {
+        *lastDot = '\0'; // truncate string
+    }
+#else
     if (lastDot) {
         size_t prefixLen = lastDot - appversionstr;
         strncpy(appversionstr_formatted, appversionstr, prefixLen);
@@ -602,10 +629,12 @@ void drawMemoryUsage(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
         strncpy(appversionstr, appversionstr_formatted, sizeof(appversionstr) - 1);
         appversionstr[sizeof(appversionstr) - 1] = '\0';
     }
+#endif
     int textWidth = display->getStringWidth(appversionstr);
     int nameX = (SCREEN_WIDTH - textWidth) / 2;
-    display->drawString(nameX, getTextPositions(display)[line], appversionstr);
 
+    display->drawString(nameX, getTextPositions(display)[line], appversionstr);
+#if !defined(M5STACK_UNITC6L)
     if (SCREEN_HEIGHT > 64 || (SCREEN_HEIGHT <= 64 && line < 4)) { // Only show uptime if the screen can show it
         line += 1;
         char uptimeStr[32] = "";
@@ -624,6 +653,7 @@ void drawMemoryUsage(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
         nameX = (SCREEN_WIDTH - textWidth) / 2;
         display->drawString(nameX, getTextPositions(display)[line], uptimeStr);
     }
+#endif
 }
 } // namespace DebugRenderer
 } // namespace graphics
diff --git a/src/graphics/draw/MenuHandler.cpp b/src/graphics/draw/MenuHandler.cpp
index bcd8d8ee8..ba554dbd6 100644
--- a/src/graphics/draw/MenuHandler.cpp
+++ b/src/graphics/draw/MenuHandler.cpp
@@ -79,7 +79,11 @@ void menuHandler::LoraRegionPicker(uint32_t duration)
                                          "NP_865",
                                          "BR_902"};
     BannerOverlayOptions bannerOptions;
+#if defined(M5STACK_UNITC6L)
+    bannerOptions.message = "LoRa Region";
+#else
     bannerOptions.message = "Set the LoRa region";
+#endif
     bannerOptions.durationMs = duration;
     bannerOptions.optionsArrayPtr = optionsArray;
     bannerOptions.optionsCount = 27;
@@ -260,7 +264,11 @@ void menuHandler::TZPicker()
 
 void menuHandler::clockMenu()
 {
+#if defined(M5STACK_UNITC6L)
+    static const char *optionsArray[] = {"Back", "Time Format", "Timezone"};
+#else
     static const char *optionsArray[] = {"Back", "Clock Face", "Time Format", "Timezone"};
+#endif
     enum optionsNumbers { Back = 0, Clock = 1, Time = 2, Timezone = 3 };
     BannerOverlayOptions bannerOptions;
     bannerOptions.message = "Clock Action";
@@ -284,8 +292,11 @@ void menuHandler::clockMenu()
 void menuHandler::messageResponseMenu()
 {
     enum optionsNumbers { Back = 0, Dismiss = 1, Preset = 2, Freetext = 3, Aloud = 4, enumEnd = 5 };
-
+#if defined(M5STACK_UNITC6L)
+    static const char *optionsArray[enumEnd] = {"Back", "Dismiss", "Reply Preset"};
+#else
     static const char *optionsArray[enumEnd] = {"Back", "Dismiss", "Reply via Preset"};
+#endif
     static int optionsEnumArray[enumEnd] = {Back, Dismiss, Preset};
     int options = 3;
 
@@ -299,7 +310,11 @@ void menuHandler::messageResponseMenu()
     optionsEnumArray[options++] = Aloud;
 #endif
     BannerOverlayOptions bannerOptions;
+#if defined(M5STACK_UNITC6L)
+    bannerOptions.message = "Message";
+#else
     bannerOptions.message = "Message Action";
+#endif
     bannerOptions.optionsArrayPtr = optionsArray;
     bannerOptions.optionsEnumPtr = optionsEnumArray;
     bannerOptions.optionsCount = options;
@@ -349,7 +364,11 @@ void menuHandler::homeBaseMenu()
 
     optionsArray[options] = "Send Position";
     optionsEnumArray[options++] = Position;
+#if defined(M5STACK_UNITC6L)
+    optionsArray[options] = "New Preset";
+#else
     optionsArray[options] = "New Preset Msg";
+#endif
     optionsEnumArray[options++] = Preset;
     if (kb_found) {
         optionsArray[options] = "New Freetext Msg";
@@ -357,7 +376,11 @@ void menuHandler::homeBaseMenu()
     }
 
     BannerOverlayOptions bannerOptions;
+#if defined(M5STACK_UNITC6L)
+    bannerOptions.message = "Home";
+#else
     bannerOptions.message = "Home Action";
+#endif
     bannerOptions.optionsArrayPtr = optionsArray;
     bannerOptions.optionsEnumPtr = optionsEnumArray;
     bannerOptions.optionsCount = options;
@@ -396,6 +419,11 @@ void menuHandler::homeBaseMenu()
     screen->showOverlayBanner(bannerOptions);
 }
 
+void menuHandler::textMessageMenu()
+{
+    cannedMessageModule->LaunchWithDestination(NODENUM_BROADCAST);
+}
+
 void menuHandler::textMessageBaseMenu()
 {
     enum optionsNumbers { Back, Preset, Freetext, enumEnd };
@@ -439,11 +467,17 @@ void menuHandler::systemBaseMenu()
     optionsArray[options] = "Screen Options";
     optionsEnumArray[options++] = ScreenOptions;
 #endif
-
+#if defined(M5STACK_UNITC6L)
+    optionsArray[options] = "Bluetooth";
+#else
     optionsArray[options] = "Bluetooth Toggle";
+#endif
     optionsEnumArray[options++] = Bluetooth;
-
+#if defined(M5STACK_UNITC6L)
+    optionsArray[options] = "Power";
+#else
     optionsArray[options] = "Reboot/Shutdown";
+#endif
     optionsEnumArray[options++] = PowerMenu;
 
     if (test_enabled) {
@@ -452,7 +486,11 @@ void menuHandler::systemBaseMenu()
     }
 
     BannerOverlayOptions bannerOptions;
+#if defined(M5STACK_UNITC6L)
+    bannerOptions.message = "System";
+#else
     bannerOptions.message = "System Action";
+#endif
     bannerOptions.optionsArrayPtr = optionsArray;
     bannerOptions.optionsCount = options;
     bannerOptions.optionsEnumPtr = optionsEnumArray;
@@ -485,7 +523,11 @@ void menuHandler::systemBaseMenu()
 void menuHandler::favoriteBaseMenu()
 {
     enum optionsNumbers { Back, Preset, Freetext, Remove, TraceRoute, enumEnd };
+#if defined(M5STACK_UNITC6L)
+    static const char *optionsArray[enumEnd] = {"Back", "New Preset"};
+#else
     static const char *optionsArray[enumEnd] = {"Back", "New Preset Msg"};
+#endif
     static int optionsEnumArray[enumEnd] = {Back, Preset};
     int options = 2;
 
@@ -493,13 +535,19 @@ void menuHandler::favoriteBaseMenu()
         optionsArray[options] = "New Freetext Msg";
         optionsEnumArray[options++] = Freetext;
     }
+#if !defined(M5STACK_UNITC6L)
     optionsArray[options] = "Trace Route";
     optionsEnumArray[options++] = TraceRoute;
+#endif
     optionsArray[options] = "Remove Favorite";
     optionsEnumArray[options++] = Remove;
 
     BannerOverlayOptions bannerOptions;
+#if defined(M5STACK_UNITC6L)
+    bannerOptions.message = "Favorites";
+#else
     bannerOptions.message = "Favorites Action";
+#endif
     bannerOptions.optionsArrayPtr = optionsArray;
     bannerOptions.optionsEnumPtr = optionsEnumArray;
     bannerOptions.optionsCount = options;
@@ -554,11 +602,19 @@ void menuHandler::positionBaseMenu()
 void menuHandler::nodeListMenu()
 {
     enum optionsNumbers { Back, Favorite, TraceRoute, Verify, Reset, enumEnd };
+#if defined(M5STACK_UNITC6L)
+    static const char *optionsArray[] = {"Back", "Add Favorite", "Reset Node"};
+#else
     static const char *optionsArray[] = {"Back", "Add Favorite", "Trace Route", "Key Verification", "Reset NodeDB"};
+#endif
     BannerOverlayOptions bannerOptions;
     bannerOptions.message = "Node Action";
     bannerOptions.optionsArrayPtr = optionsArray;
+#if defined(M5STACK_UNITC6L)
+    bannerOptions.optionsCount = 3;
+#else
     bannerOptions.optionsCount = 5;
+#endif
     bannerOptions.bannerCallback = [](int selected) -> void {
         if (selected == Favorite) {
             menuQueue = add_favorite;
@@ -665,7 +721,11 @@ void menuHandler::BluetoothToggleMenu()
 {
     static const char *optionsArray[] = {"Back", "Enabled", "Disabled"};
     BannerOverlayOptions bannerOptions;
+#if defined(M5STACK_UNITC6L)
+    bannerOptions.message = "Bluetooth";
+#else
     bannerOptions.message = "Toggle Bluetooth";
+#endif
     bannerOptions.optionsArrayPtr = optionsArray;
     bannerOptions.optionsCount = 3;
     bannerOptions.bannerCallback = [](int selected) -> void {
@@ -857,7 +917,11 @@ void menuHandler::rebootMenu()
 {
     static const char *optionsArray[] = {"Back", "Confirm"};
     BannerOverlayOptions bannerOptions;
+#if defined(M5STACK_UNITC6L)
+    bannerOptions.message = "Reboot";
+#else
     bannerOptions.message = "Reboot Device?";
+#endif
     bannerOptions.optionsArrayPtr = optionsArray;
     bannerOptions.optionsCount = 2;
     bannerOptions.bannerCallback = [](int selected) -> void {
@@ -877,7 +941,11 @@ void menuHandler::shutdownMenu()
 {
     static const char *optionsArray[] = {"Back", "Confirm"};
     BannerOverlayOptions bannerOptions;
+#if defined(M5STACK_UNITC6L)
+    bannerOptions.message = "Shutdown";
+#else
     bannerOptions.message = "Shutdown Device?";
+#endif
     bannerOptions.optionsArrayPtr = optionsArray;
     bannerOptions.optionsCount = 2;
     bannerOptions.bannerCallback = [](int selected) -> void {
@@ -894,7 +962,12 @@ void menuHandler::shutdownMenu()
 
 void menuHandler::addFavoriteMenu()
 {
+#if defined(M5STACK_UNITC6L)
+    screen->showNodePicker("Node Favorite", 30000, [](uint32_t nodenum) -> void {
+#else
     screen->showNodePicker("Node To Favorite", 30000, [](uint32_t nodenum) -> void {
+
+#endif
         LOG_WARN("Nodenum: %u", nodenum);
         nodeDB->set_favorite(true, nodenum);
         screen->setFrames(graphics::Screen::FOCUS_PRESERVE);
@@ -1090,7 +1163,11 @@ void menuHandler::powerMenu()
 #endif
 
     BannerOverlayOptions bannerOptions;
+#if defined(M5STACK_UNITC6L)
+    bannerOptions.message = "Power";
+#else
     bannerOptions.message = "Reboot / Shutdown";
+#endif
     bannerOptions.optionsArrayPtr = optionsArray;
     bannerOptions.optionsCount = options;
     bannerOptions.optionsEnumPtr = optionsEnumArray;
diff --git a/src/graphics/draw/MenuHandler.h b/src/graphics/draw/MenuHandler.h
index b15cf237d..ed49a89fb 100644
--- a/src/graphics/draw/MenuHandler.h
+++ b/src/graphics/draw/MenuHandler.h
@@ -76,6 +76,7 @@ class menuHandler
     static void notificationsMenu();
     static void screenOptionsMenu();
     static void powerMenu();
+    static void textMessageMenu();
 
   private:
     static void saveUIConfig();
diff --git a/src/graphics/draw/MessageRenderer.cpp b/src/graphics/draw/MessageRenderer.cpp
index 117829167..6971826de 100644
--- a/src/graphics/draw/MessageRenderer.cpp
+++ b/src/graphics/draw/MessageRenderer.cpp
@@ -181,12 +181,19 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
     display->clear();
     display->setTextAlignment(TEXT_ALIGN_LEFT);
     display->setFont(FONT_SMALL);
-
+#if defined(M5STACK_UNITC6L)
+    const int fixedTopHeight = 24;
+    const int windowX = 0;
+    const int windowY = fixedTopHeight;
+    const int windowWidth = 64;
+    const int windowHeight = SCREEN_HEIGHT - fixedTopHeight;
+#else
     const int navHeight = FONT_HEIGHT_SMALL;
     const int scrollBottom = SCREEN_HEIGHT - navHeight;
     const int usableHeight = scrollBottom;
     const int textWidth = SCREEN_WIDTH;
 
+#endif
     bool isInverted = (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_INVERTED);
     bool isBold = config.display.heading_bold;
 
@@ -201,7 +208,11 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
         graphics::drawCommonHeader(display, x, y, titleStr);
         const char *messageString = "No messages";
         int center_text = (SCREEN_WIDTH / 2) - (display->getStringWidth(messageString) / 2);
+#if defined(M5STACK_UNITC6L)
+        display->drawString(center_text, windowY + (windowHeight / 2) - (FONT_HEIGHT_SMALL / 2) - 5, messageString);
+#else
         display->drawString(center_text, getTextPositions(display)[2], messageString);
+#endif
         return;
     }
 
@@ -209,6 +220,10 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
     meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(getFrom(&mp));
     char headerStr[80];
     const char *sender = "???";
+#if defined(M5STACK_UNITC6L)
+    if (node && node->has_user)
+        sender = node->user.short_name;
+#else
     if (node && node->has_user) {
         if (SCREEN_WIDTH >= 200 && strlen(node->user.long_name) > 0) {
             sender = node->user.long_name;
@@ -216,6 +231,7 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
             sender = node->user.short_name;
         }
     }
+#endif
     uint32_t seconds = sinceReceived(&mp), minutes = seconds / 60, hours = minutes / 60, days = hours / 24;
     uint8_t timestampHours, timestampMinutes;
     int32_t daysAgo;
@@ -235,10 +251,61 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
                      sender);
         }
     } else {
+#if defined(M5STACK_UNITC6L)
+        snprintf(headerStr, sizeof(headerStr), "%s from %s", UIRenderer::drawTimeDelta(days, hours, minutes, seconds).c_str(),
+                 sender);
+#else
         snprintf(headerStr, sizeof(headerStr), "%s ago from %s", UIRenderer::drawTimeDelta(days, hours, minutes, seconds).c_str(),
                  sender);
+#endif
     }
+#if defined(M5STACK_UNITC6L)
+    graphics::drawCommonHeader(display, x, y, titleStr);
+    int headerY = getTextPositions(display)[1];
+    display->drawString(x, headerY, headerStr);
+    for (int separatorX = 0; separatorX < SCREEN_WIDTH; separatorX += 2) {
+        display->setPixel(separatorX, fixedTopHeight - 1);
+    }
+    cachedLines.clear();
+    std::string fullMsg(messageBuf);
+    std::string currentLine;
+    for (size_t i = 0; i < fullMsg.size();) {
+        unsigned char c = fullMsg[i];
+        size_t charLen = 1;
+        if ((c & 0xE0) == 0xC0)
+            charLen = 2;
+        else if ((c & 0xF0) == 0xE0)
+            charLen = 3;
+        else if ((c & 0xF8) == 0xF0)
+            charLen = 4;
+        std::string nextChar = fullMsg.substr(i, charLen);
+        std::string testLine = currentLine + nextChar;
+        if (display->getStringWidth(testLine.c_str()) > windowWidth) {
+            cachedLines.push_back(currentLine);
+            currentLine = nextChar;
+        } else {
+            currentLine = testLine;
+        }
 
+        i += charLen;
+    }
+    if (!currentLine.empty())
+        cachedLines.push_back(currentLine);
+    cachedHeights = calculateLineHeights(cachedLines, emotes);
+    int yOffset = windowY;
+    int linesDrawn = 0;
+    for (size_t i = 0; i < cachedLines.size(); ++i) {
+        if (linesDrawn >= 2)
+            break;
+        int lineHeight = cachedHeights[i];
+        if (yOffset + lineHeight > windowY + windowHeight)
+            break;
+        drawStringWithEmotes(display, windowX, yOffset, cachedLines[i], emotes, numEmotes);
+        yOffset += lineHeight;
+        linesDrawn++;
+    }
+    screen->forceDisplay();
+#else
     uint32_t now = millis();
 #ifndef EXCLUDE_EMOJI
     // === Bounce animation setup ===
@@ -355,6 +422,7 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
 
     // Draw header at the end to sort out overlapping elements
     graphics::drawCommonHeader(display, x, y, titleStr);
+#endif
 }
 
 std::vector generateLines(OLEDDisplay *display, const char *headerStr, const char *messageBuf, int textWidth)
diff --git a/src/graphics/draw/NodeListRenderer.cpp b/src/graphics/draw/NodeListRenderer.cpp
index d8746fb69..7d6a38dd3 100644
--- a/src/graphics/draw/NodeListRenderer.cpp
+++ b/src/graphics/draw/NodeListRenderer.cpp
@@ -21,6 +21,10 @@ extern bool haveGlyphs(const char *str);
 // Global screen instance
 extern graphics::Screen *screen;
 
+#if defined(M5STACK_UNITC6L)
+static uint32_t lastSwitchTime = 0;
+#else
+#endif
 namespace graphics
 {
 namespace NodeListRenderer
@@ -393,9 +397,11 @@ void drawNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t
 {
     const int COMMON_HEADER_HEIGHT = FONT_HEIGHT_SMALL - 1;
     const int rowYOffset = FONT_HEIGHT_SMALL - 3;
-
+#if defined(M5STACK_UNITC6L)
+    int columnWidth = display->getWidth();
+#else
     int columnWidth = display->getWidth() / 2;
-
+#endif
     display->clear();
 
     // Draw the battery/time header
@@ -408,8 +414,11 @@ void drawNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t
     int totalRowsAvailable = (display->getHeight() - y) / rowYOffset;
 
     int visibleNodeRows = totalRowsAvailable;
+#if defined(M5STACK_UNITC6L)
+    int totalColumns = 1;
+#else
     int totalColumns = 2;
-
+#endif
     int startIndex = scrollIndex * visibleNodeRows * totalColumns;
     if (nodeDB->getMeshNodeByIndex(startIndex)->num == nodeDB->getNodeNum()) {
         startIndex++; // skip own node
@@ -445,12 +454,14 @@ void drawNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t
         }
     }
 
+#if !defined(M5STACK_UNITC6L)
     // Draw column separator
     if (shownCount > 0) {
         const int firstNodeY = y + 3;
         drawColumnSeparator(display, x, firstNodeY, lastNodeY);
     }
 
+#endif
     const int scrollStartY = y + 3;
     drawScrollbar(display, visibleNodeRows, totalEntries, scrollIndex, 2, scrollStartY);
 }
@@ -468,6 +479,13 @@ void drawDynamicNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state,
 
     unsigned long now = millis();
 
+#if defined(M5STACK_UNITC6L)
+    display->clear();
+    if (now - lastSwitchTime >= 3000) {
+        display->display();
+        lastSwitchTime = now;
+    }
+#endif
     // On very first call (on boot or state enter)
     if (lastRenderedMode == MODE_COUNT) {
         currentMode = MODE_LAST_HEARD;
@@ -522,6 +540,14 @@ void drawNodeListWithCompasses(OLEDDisplay *display, OLEDDisplayUiState *state,
     double lat = DegD(ourNode->position.latitude_i);
     double lon = DegD(ourNode->position.longitude_i);
 
+#if defined(M5STACK_UNITC6L)
+    display->clear();
+    uint32_t now = millis();
+    if (now - lastSwitchTime >= 2000) {
+        display->display();
+        lastSwitchTime = now;
+    }
+#endif
     if (uiconfig.compass_mode != meshtastic_CompassMode_FREEZE_HEADING) {
 #if HAS_GPS
         if (screen->hasHeading()) {
diff --git a/src/graphics/draw/NotificationRenderer.cpp b/src/graphics/draw/NotificationRenderer.cpp
index 3d635e588..c2bd1ba66 100644
--- a/src/graphics/draw/NotificationRenderer.cpp
+++ b/src/graphics/draw/NotificationRenderer.cpp
@@ -459,6 +459,135 @@ void NotificationRenderer::drawNotificationBox(OLEDDisplay *display, OLEDDisplay
     // count lines
 
     uint16_t boxWidth = hPadding * 2 + maxWidth;
+#if defined(M5STACK_UNITC6L)
+    if (needs_bell) {
+        if (isHighResolution && boxWidth <= 150)
+            boxWidth += 26;
+        if (!isHighResolution && boxWidth <= 100)
+            boxWidth += 20;
+    }
+
+    uint16_t screenHeight = display->height();
+    uint8_t effectiveLineHeight = FONT_HEIGHT_SMALL - 3;
+    uint8_t visibleTotalLines = std::min(lineCount, (screenHeight - vPadding * 2) / effectiveLineHeight);
+    uint16_t contentHeight = visibleTotalLines * effectiveLineHeight;
+    uint16_t boxHeight = contentHeight + vPadding * 2;
+    if (visibleTotalLines == 1)
+        boxHeight += (isHighResolution ? 4 : 3);
+
+    int16_t boxLeft = (display->width() / 2) - (boxWidth / 2);
+    if (totalLines > visibleTotalLines)
+        boxWidth += (isHighResolution ? 4 : 2);
+    int16_t boxTop = (display->height() / 2) - (boxHeight / 2);
+
+    if (visibleTotalLines == 1) {
+        boxTop += 25;
+    }
+    if (alertBannerOptions < 3) {
+        int missingLines = 3 - alertBannerOptions;
+        int moveUp = missingLines * (effectiveLineHeight / 2);
+        boxTop -= moveUp;
+        if (boxTop < 0)
+            boxTop = 0;
+    }
+
+    // === Draw Box ===
+    display->setColor(BLACK);
+    display->fillRect(boxLeft, boxTop, boxWidth, boxHeight);
+    display->setColor(WHITE);
+    display->drawRect(boxLeft, boxTop, boxWidth, boxHeight);
+    display->fillRect(boxLeft, boxTop - 2, boxWidth, 1);
+    display->fillRect(boxLeft - 2, boxTop, 1, boxHeight);
+    display->fillRect(boxLeft + boxWidth + 1, boxTop, 1, boxHeight);
+    display->setColor(BLACK);
+    display->fillRect(boxLeft, boxTop, 1, 1);
+    display->fillRect(boxLeft + boxWidth - 1, boxTop, 1, 1);
+    display->fillRect(boxLeft, boxTop + boxHeight - 1, 1, 1);
+    display->fillRect(boxLeft + boxWidth - 1, boxTop + boxHeight - 1, 1, 1);
+    display->setColor(WHITE);
+    int16_t lineY = boxTop + vPadding;
+    int swingRange = 8;
+    static int swingOffset = 0;
+    static bool swingRight = true;
+    static unsigned long lastSwingTime = 0;
+    unsigned long now = millis();
+    int swingSpeedMs = 10 / (swingRange * 2);
+    if (now - lastSwingTime >= (unsigned long)swingSpeedMs) {
+        lastSwingTime = now;
+        if (swingRight) {
+            swingOffset++;
+            if (swingOffset >= swingRange)
+                swingRight = false;
+        } else {
+            swingOffset--;
+            if (swingOffset <= 0)
+                swingRight = true;
+        }
+    }
+    for (int i = 0; i < lineCount; i++) {
+        bool isTitle = (i == 0);
+        int globalOptionIndex = (i - 1) + firstOptionToShow;
+        bool isSelectedOption = (!isTitle && globalOptionIndex >= 0 && globalOptionIndex == curSelected);
+
+        uint16_t visibleWidth = 64 - hPadding * 2;
+        if (totalLines > visibleTotalLines)
+            visibleWidth -= 6;
+        char lineBuffer[lineLengths[i] + 1];
+        strncpy(lineBuffer, lines[i], lineLengths[i]);
+        lineBuffer[lineLengths[i]] = '\0';
+
+        if (isTitle) {
+            if (visibleTotalLines == 1) {
+                display->setColor(BLACK);
+                display->fillRect(boxLeft, boxTop, boxWidth, effectiveLineHeight);
+                display->setColor(WHITE);
+                display->drawString(boxLeft + (boxWidth - lineWidths[i]) / 2, boxTop, lineBuffer);
+            } else {
+                display->setColor(WHITE);
+                display->fillRect(boxLeft, boxTop, boxWidth, effectiveLineHeight);
+                display->setColor(BLACK);
+                display->drawString(boxLeft + (boxWidth - lineWidths[i]) / 2, boxTop, lineBuffer);
+                display->setColor(WHITE);
+                if (needs_bell) {
+                    int bellY = boxTop + (FONT_HEIGHT_SMALL - 8) / 2;
+                    display->drawXbm(boxLeft + (boxWidth - lineWidths[i]) / 2 - 10, bellY, 8, 8, bell_alert);
+                    display->drawXbm(boxLeft + (boxWidth + lineWidths[i]) / 2 + 2, bellY, 8, 8, bell_alert);
+                }
+            }
+            lineY = boxTop + effectiveLineHeight + 1;
+        } else if (isSelectedOption) {
+            display->setColor(WHITE);
+            display->fillRect(boxLeft, lineY, boxWidth, effectiveLineHeight);
+            display->setColor(BLACK);
+            if (lineLengths[i] > 15 && lineWidths[i] > visibleWidth) {
+                int textX = boxLeft + hPadding + swingOffset;
+                display->drawString(textX, lineY - 1, lineBuffer);
+            } else {
+                display->drawString(boxLeft + (boxWidth - lineWidths[i]) / 2, lineY - 1, lineBuffer);
+            }
+            display->setColor(WHITE);
+            lineY += effectiveLineHeight;
+        } else {
+            display->setColor(BLACK);
+            display->fillRect(boxLeft, lineY, boxWidth, effectiveLineHeight);
+            display->setColor(WHITE);
+            display->drawString(boxLeft + (boxWidth - lineWidths[i]) / 2, lineY, lineBuffer);
+            lineY += effectiveLineHeight;
+        }
+    }
+    if (totalLines > visibleTotalLines) {
+        const uint8_t scrollBarWidth = 5;
+        int16_t scrollBarX = boxLeft + boxWidth - scrollBarWidth - 2;
+        int16_t scrollBarY = boxTop + vPadding + effectiveLineHeight;
+        uint16_t scrollBarHeight = boxHeight - vPadding * 2 - effectiveLineHeight;
+        float ratio = (float)visibleTotalLines / totalLines;
+        uint16_t indicatorHeight = std::max((int)(scrollBarHeight * ratio), 4);
+        float scrollRatio = (float)(firstOptionToShow + lineCount - visibleTotalLines) / (totalLines - visibleTotalLines);
+        uint16_t indicatorY = scrollBarY + scrollRatio * (scrollBarHeight - indicatorHeight);
+        display->drawRect(scrollBarX, scrollBarY, scrollBarWidth, scrollBarHeight);
+        display->fillRect(scrollBarX + 1, indicatorY, scrollBarWidth - 2, indicatorHeight);
+    }
+#else
     if (needs_bell) {
         if (isHighResolution && boxWidth <= 150)
             boxWidth += 26;
@@ -547,6 +676,7 @@ void NotificationRenderer::drawNotificationBox(OLEDDisplay *display, OLEDDisplay
         display->drawRect(scrollBarX, scrollBarY, scrollBarWidth, scrollBarHeight);
         display->fillRect(scrollBarX + 1, indicatorY, scrollBarWidth - 2, indicatorHeight);
     }
+#endif
 }
 
 /// Draw the last text message we received
diff --git a/src/graphics/draw/UIRenderer.cpp b/src/graphics/draw/UIRenderer.cpp
index 049722df8..e00b19b2f 100644
--- a/src/graphics/draw/UIRenderer.cpp
+++ b/src/graphics/draw/UIRenderer.cpp
@@ -20,7 +20,7 @@
 
 // External variables
 extern graphics::Screen *screen;
-
+static uint32_t lastSwitchTime = 0;
 namespace graphics
 {
 NodeNum UIRenderer::currentFavoriteNodeNum = 0;
@@ -218,7 +218,6 @@ void UIRenderer::drawNodes(OLEDDisplay *display, int16_t x, int16_t y, const mes
 // **********************
 void UIRenderer::drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *state, int16_t x, int16_t y)
 {
-
     if (favoritedNodes.empty())
         return;
 
@@ -230,8 +229,15 @@ void UIRenderer::drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *st
     meshtastic_NodeInfoLite *node = favoritedNodes[nodeIndex];
     if (!node || node->num == nodeDB->getNodeNum() || !node->is_favorite)
         return;
-
+    uint32_t now = millis();
     display->clear();
+#if defined(M5STACK_UNITC6L)
+    if (now - lastSwitchTime >= 10000) // 10000 ms = 10 秒
+    {
+        display->display();
+        lastSwitchTime = now;
+    }
+#endif
     currentFavoriteNodeNum = node->num;
     // === Create the shortName and title string ===
     const char *shortName = (node->has_user && haveGlyphs(node->user.short_name)) ? node->user.short_name : "Node";
@@ -250,9 +256,13 @@ void UIRenderer::drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *st
     // List of available macro Y positions in order, from top to bottom.
     int line = 1; // which slot to use next
     std::string usernameStr;
-
     // === 1. Long Name (always try to show first) ===
+#if defined(M5STACK_UNITC6L)
+    const char *username = (node->has_user && node->user.long_name[0]) ? node->user.short_name : nullptr;
+#else
     const char *username = (node->has_user && node->user.long_name[0]) ? node->user.long_name : nullptr;
+#endif
+
     if (username) {
         usernameStr = sanitizeString(username); // Sanitize the incoming long_name just in case
         // Print node's long name (e.g. "Backpack Node")
@@ -307,7 +317,7 @@ void UIRenderer::drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *st
     if (seenStr[0] && line < 5) {
         display->drawString(x, getTextPositions(display)[line++], seenStr);
     }
-
+#if !defined(M5STACK_UNITC6L)
     // === 4. Uptime (only show if metric is present) ===
     char uptimeStr[32] = "";
     if (node->has_device_metrics && node->device_metrics.has_uptime_seconds) {
@@ -479,6 +489,7 @@ void UIRenderer::drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *st
         }
         // else show nothing
     }
+#endif
 }
 
 // ****************************
@@ -492,7 +503,11 @@ void UIRenderer::drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *sta
     int line = 1;
 
     // === Header ===
+#if defined(M5STACK_UNITC6L)
+    graphics::drawCommonHeader(display, x, y, "Home");
+#else
     graphics::drawCommonHeader(display, x, y, "");
+#endif
 
     // === Content below header ===
 
@@ -507,20 +522,25 @@ void UIRenderer::drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *sta
     config.display.heading_bold = false;
 
     // Display Region and Channel Utilization
+#if defined(M5STACK_UNITC6L)
+    drawNodes(display, x, getTextPositions(display)[line] + 2, nodeStatus, -1, false, "online");
+#else
     drawNodes(display, x + 1, getTextPositions(display)[line] + 2, nodeStatus, -1, false, "online");
-
+#endif
     char uptimeStr[32] = "";
     uint32_t uptime = millis() / 1000;
     uint32_t days = uptime / 86400;
     uint32_t hours = (uptime % 86400) / 3600;
     uint32_t mins = (uptime % 3600) / 60;
     // Show as "Up: 2d 3h", "Up: 5h 14m", or "Up: 37m"
+#if !defined(M5STACK_UNITC6L)
     if (days)
         snprintf(uptimeStr, sizeof(uptimeStr), "Up: %ud %uh", days, hours);
     else if (hours)
         snprintf(uptimeStr, sizeof(uptimeStr), "Up: %uh %um", hours, mins);
     else
         snprintf(uptimeStr, sizeof(uptimeStr), "Up: %um", mins);
+#endif
     display->drawString(SCREEN_WIDTH - display->getStringWidth(uptimeStr), getTextPositions(display)[line++], uptimeStr);
 
     // === Second Row: Satellites and Voltage ===
@@ -549,6 +569,21 @@ void UIRenderer::drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *sta
     }
 #endif
 
+#if defined(M5STACK_UNITC6L)
+    line += 1;
+
+    // === Node Identity ===
+    int textWidth = 0;
+    int nameX = 0;
+    char shortnameble[35];
+    snprintf(shortnameble, sizeof(shortnameble), "%s",
+             graphics::UIRenderer::haveGlyphs(owner.short_name) ? owner.short_name : "");
+
+    // === ShortName Centered ===
+    textWidth = display->getStringWidth(shortnameble);
+    nameX = (SCREEN_WIDTH - textWidth) / 2;
+    display->drawString(nameX, getTextPositions(display)[line++], shortnameble);
+#else
     if (powerStatus->getHasBattery()) {
         char batStr[20];
         int batV = powerStatus->getBatteryVoltageMv() / 1000;
@@ -674,6 +709,7 @@ void UIRenderer::drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *sta
         nameX = (SCREEN_WIDTH - textWidth) / 2;
         display->drawString(nameX, getTextPositions(display)[line++], shortnameble);
     }
+#endif
 }
 
 // Start Functions to write date/time to the screen
@@ -832,6 +868,28 @@ void UIRenderer::drawIconScreen(const char *upperMsg, OLEDDisplay *display, OLED
     // needs to be drawn relative to x and y
 
     // draw centered icon left to right and centered above the one line of app text
+#if defined(M5STACK_UNITC6L)
+    display->drawXbm(x + (SCREEN_WIDTH - 50) / 2, y + (SCREEN_HEIGHT - 28) / 2, icon_width, icon_height, icon_bits);
+    display->setFont(FONT_MEDIUM);
+    display->setTextAlignment(TEXT_ALIGN_LEFT);
+    display->setFont(FONT_SMALL);
+    // Draw region in upper left
+    if (upperMsg) {
+        int msgWidth = display->getStringWidth(upperMsg);
+        int msgX = x + (SCREEN_WIDTH - msgWidth) / 2;
+        int msgY = y;
+        display->drawString(msgX, msgY, upperMsg);
+    }
+    // Draw version and short name in bottom middle
+    char buf[25];
+    snprintf(buf, sizeof(buf), "%s   %s", xstr(APP_VERSION_SHORT),
+             graphics::UIRenderer::haveGlyphs(owner.short_name) ? owner.short_name : "");
+
+    display->drawString(x + getStringCenteredX(buf), y + SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM, buf);
+    screen->forceDisplay();
+
+    display->setTextAlignment(TEXT_ALIGN_LEFT); // Restore left align, just to be kind to any other unsuspecting code
+#else
     display->drawXbm(x + (SCREEN_WIDTH - icon_width) / 2, y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - icon_height) / 2 + 2,
                      icon_width, icon_height, icon_bits);
 
@@ -840,7 +898,6 @@ void UIRenderer::drawIconScreen(const char *upperMsg, OLEDDisplay *display, OLED
     const char *title = "meshtastic.org";
     display->drawString(x + getStringCenteredX(title), y + SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM, title);
     display->setFont(FONT_SMALL);
-
     // Draw region in upper left
     if (upperMsg)
         display->drawString(x + 0, y + 0, upperMsg);
@@ -855,6 +912,7 @@ void UIRenderer::drawIconScreen(const char *upperMsg, OLEDDisplay *display, OLED
     screen->forceDisplay();
 
     display->setTextAlignment(TEXT_ALIGN_LEFT); // Restore left align, just to be kind to any other unsuspecting code
+#endif
 }
 
 // ****************************
@@ -930,15 +988,26 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU
         UIRenderer::formatDateTime(datetimeStr, sizeof(datetimeStr), rtc_sec, display, showTime);
         char fullLine[40];
         snprintf(fullLine, sizeof(fullLine), " Date: %s", datetimeStr);
+#if !defined(M5STACK_UNITC6L)
         display->drawString(0, getTextPositions(display)[line++], fullLine);
+#endif
 
         // === Third Row: Latitude ===
         char latStr[32];
+#if defined(M5STACK_UNITC6L)
+        snprintf(latStr, sizeof(latStr), "Lat:%.5f", geoCoord.getLatitude() * 1e-7);
+        display->drawString(x, getTextPositions(display)[line++] + 2, latStr);
+#else
         snprintf(latStr, sizeof(latStr), " Lat: %.5f", geoCoord.getLatitude() * 1e-7);
         display->drawString(x, getTextPositions(display)[line++], latStr);
+#endif
 
         // === Fourth Row: Longitude ===
         char lonStr[32];
+#if defined(M5STACK_UNITC6L)
+        snprintf(lonStr, sizeof(lonStr), "Lon:%.3f", geoCoord.getLongitude() * 1e-7);
+        display->drawString(x, getTextPositions(display)[line++] + 4, lonStr);
+#else
         snprintf(lonStr, sizeof(lonStr), " Lon: %.5f", geoCoord.getLongitude() * 1e-7);
         display->drawString(x, getTextPositions(display)[line++], lonStr);
 
@@ -950,8 +1019,9 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU
             snprintf(DisplayLineTwo, sizeof(DisplayLineTwo), " Alt: %.0im", geoCoord.getAltitude());
         }
         display->drawString(x, getTextPositions(display)[line++], DisplayLineTwo);
+#endif
     }
-
+#if !defined(M5STACK_UNITC6L)
     // === Draw Compass if heading is valid ===
     if (validHeading) {
         // --- Compass Rendering: landscape (wide) screens use original side-aligned logic ---
@@ -1034,6 +1104,7 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU
         }
     }
 #endif
+#endif
 }
 
 #ifdef USERPREFS_OEM_TEXT
@@ -1190,7 +1261,6 @@ void UIRenderer::drawNavigationBar(OLEDDisplay *display, OLEDDisplayUiState *sta
             display->setColor(WHITE);
         }
     }
-
     // Knock the corners off the square
     display->setColor(BLACK);
     display->drawRect(rectX, y - 2, 1, 1);
diff --git a/src/graphics/images.h b/src/graphics/images.h
index c66e4b992..4a58edb3b 100644
--- a/src/graphics/images.h
+++ b/src/graphics/images.h
@@ -287,6 +287,9 @@ const uint8_t digital_icon_clock[] PROGMEM = {0b00111100, 0b01000010, 0b10000101
 #define analog_icon_clock_height 8
 const uint8_t analog_icon_clock[] PROGMEM = {0b11111111, 0b01000010, 0b00100100, 0b00011000,
                                              0b00100100, 0b01000010, 0b01000010, 0b11111111};
-
+#ifdef M5STACK_UNITC6L
+#include "img/icon_small.xbm"
+#else
 #include "img/icon.xbm"
+#endif
 static_assert(sizeof(icon_bits) >= 0, "Silence unused variable warning");
\ No newline at end of file
diff --git a/src/graphics/img/icon_small.xbm b/src/graphics/img/icon_small.xbm
new file mode 100644
index 000000000..e320a1fea
--- /dev/null
+++ b/src/graphics/img/icon_small.xbm
@@ -0,0 +1,30 @@
+#ifndef USERPREFS_HAS_SPLASH
+#define icon_width 50
+#define icon_height 20
+static uint8_t icon_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x80, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00, 0x80,
+  0x07, 0xc0, 0x07, 0x00, 0x00, 0x00, 0xc0, 0x0f,
+  0xc0, 0x0f, 0x00, 0x00, 0x00, 0xe0, 0x07, 0xe0,
+  0x0f, 0x00, 0x00, 0x00, 0xe0, 0x07, 0xf0, 0x1f,
+  0x00, 0x00, 0x00, 0xf0, 0x03, 0xf0, 0x3f, 0x00,
+  0x00, 0x00, 0xf8, 0x03, 0xf8, 0x7f, 0x00, 0x00,
+  0x00, 0xf8, 0x01, 0xfc, 0x7e, 0x00, 0x00, 0x00,
+  0xfc, 0x00, 0xfc, 0xfc, 0x00, 0x00, 0x00, 0xfe,
+  0x00, 0x7e, 0xf8, 0x00, 0x00, 0x00, 0x7e, 0x00,
+  0x3f, 0xf8, 0x01, 0x00, 0x00, 0x3f, 0x00, 0x1f,
+  0xf0, 0x01, 0x00, 0x00, 0x1f, 0x80, 0x1f, 0xe0,
+  0x03, 0x00, 0x80, 0x1f, 0xc0, 0x0f, 0xe0, 0x03,
+  0x00, 0x80, 0x0f, 0xc0, 0x07, 0xc0, 0x07, 0x00,
+  0xc0, 0x0f, 0xe0, 0x07, 0x80, 0x0f, 0x00, 0xe0,
+  0x07, 0xf0, 0x03, 0x80, 0x1f, 0x00, 0xe0, 0x03,
+  0xf8, 0x03, 0x00, 0x1f, 0x00, 0xf0, 0x03, 0xf8,
+  0x01, 0x00, 0x3f, 0x00, 0xf8, 0x01, 0xfc, 0x00,
+  0x00, 0x7e, 0x00, 0xfc, 0x00, 0xfe, 0x00, 0x00,
+  0x7e, 0x00, 0xfc, 0x00, 0x7e, 0x00, 0x00, 0xfc,
+  0x00, 0x7e, 0x00, 0x3f, 0x00, 0x00, 0xf8, 0x00,
+  0x7e, 0x00, 0x3e, 0x00, 0x00, 0xf8, 0x00, 0x38,
+  0x00, 0x1c, 0x00, 0x00, 0x70, 0x00, 0x10, 0x00,
+  0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00 };
+#endif
\ No newline at end of file
diff --git a/src/input/i2cButton.cpp b/src/input/i2cButton.cpp
new file mode 100644
index 000000000..d874146cd
--- /dev/null
+++ b/src/input/i2cButton.cpp
@@ -0,0 +1,95 @@
+#include "i2cButton.h"
+#include "meshUtils.h"
+
+#include "configuration.h"
+#if defined(M5STACK_UNITC6L)
+
+#include "MeshService.h"
+#include "RadioLibInterface.h"
+#include "buzz.h"
+#include "input/InputBroker.h"
+#include "main.h"
+#include "modules/CannedMessageModule.h"
+#include "modules/ExternalNotificationModule.h"
+#include "power.h"
+#include "sleep.h"
+#ifdef ARCH_PORTDUINO
+#include "platform/portduino/PortduinoGlue.h"
+#endif
+
+i2cButtonThread *i2cButton;
+
+using namespace concurrency;
+
+extern void i2c_read_byte(uint8_t addr, uint8_t reg, uint8_t *value);
+
+extern void i2c_write_byte(uint8_t addr, uint8_t reg, uint8_t value);
+
+#define PI4IO_M_ADDR 0x43
+#define getbit(x, y) ((x) >> (y)&0x01)
+#define PI4IO_REG_IRQ_STA 0x13
+#define PI4IO_REG_IN_STA 0x0F
+#define PI4IO_REG_CHIP_RESET 0x01
+
+i2cButtonThread::i2cButtonThread(const char *name) : OSThread(name)
+{
+    _originName = name;
+    if (inputBroker)
+        inputBroker->registerSource(this);
+}
+
+int32_t i2cButtonThread::runOnce()
+{
+    static bool btn1_pressed = false;
+    static uint32_t press_start_time = 0;
+    const uint32_t LONG_PRESS_TIME = 1000;
+    static bool long_press_triggered = false;
+
+    uint8_t in_data;
+    i2c_read_byte(PI4IO_M_ADDR, PI4IO_REG_IRQ_STA, &in_data);
+    i2c_write_byte(PI4IO_M_ADDR, PI4IO_REG_IRQ_STA, in_data);
+    if (getbit(in_data, 0)) {
+        uint8_t input_state;
+        i2c_read_byte(PI4IO_M_ADDR, PI4IO_REG_IN_STA, &input_state);
+
+        if (!getbit(input_state, 0)) {
+            if (!btn1_pressed) {
+                btn1_pressed = true;
+                press_start_time = millis();
+                long_press_triggered = false;
+            }
+        } else {
+            if (btn1_pressed) {
+                btn1_pressed = false;
+                uint32_t press_duration = millis() - press_start_time;
+                if (long_press_triggered) {
+                    long_press_triggered = false;
+                    return 50;
+                }
+
+                if (press_duration < LONG_PRESS_TIME) {
+                    InputEvent evt;
+                    evt.source = "UserButton";
+                    evt.inputEvent = INPUT_BROKER_USER_PRESS;
+                    evt.kbchar = 0;
+                    evt.touchX = 0;
+                    evt.touchY = 0;
+                    this->notifyObservers(&evt);
+                }
+            }
+        }
+    }
+
+    if (btn1_pressed && !long_press_triggered && (millis() - press_start_time >= LONG_PRESS_TIME)) {
+        long_press_triggered = true;
+        InputEvent evt;
+        evt.source = "UserButton";
+        evt.inputEvent = INPUT_BROKER_SELECT;
+        evt.kbchar = 0;
+        evt.touchX = 0;
+        evt.touchY = 0;
+        this->notifyObservers(&evt);
+    }
+    return 50;
+}
+#endif
\ No newline at end of file
diff --git a/src/input/i2cButton.h b/src/input/i2cButton.h
new file mode 100644
index 000000000..1ad908606
--- /dev/null
+++ b/src/input/i2cButton.h
@@ -0,0 +1,18 @@
+#pragma once
+
+#include "InputBroker.h"
+#include "OneButton.h"
+#include "concurrency/OSThread.h"
+#include "configuration.h"
+#if defined(M5STACK_UNITC6L)
+
+class i2cButtonThread : public Observable, public concurrency::OSThread
+{
+  public:
+    const char *_originName;
+    explicit i2cButtonThread(const char *name);
+    int32_t runOnce() override;
+};
+
+extern i2cButtonThread *i2cButton;
+#endif
diff --git a/src/main.cpp b/src/main.cpp
index 401ea7592..d7e866a2a 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -385,7 +385,6 @@ void setup()
     io.digitalWrite(EXPANDS_GPIO_EN, HIGH);
     io.pinMode(EXPANDS_SD_PULLEN, INPUT);
 #endif
-
     concurrency::hasBeenSetup = true;
 #if ARCH_PORTDUINO
     SPISettings spiSettings(settingsMap[spiSpeed], MSBFIRST, SPI_MODE0);
@@ -544,6 +543,12 @@ void setup()
 #endif
 #endif
 
+#if defined(M5STACK_UNITC6L)
+    pinMode(LORA_CS, OUTPUT);
+    digitalWrite(LORA_CS, 1);
+    c6l_init();
+#endif
+
 #ifdef PIN_LCD_RESET
     // FIXME - move this someplace better, LCD is at address 0x3F
     pinMode(PIN_LCD_RESET, OUTPUT);
@@ -877,7 +882,8 @@ void setup()
     if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
 
 #if defined(ST7701_CS) || defined(ST7735_CS) || defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) ||       \
-    defined(ST7789_CS) || defined(HX8357_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(ST7796_CS)
+    defined(ST7789_CS) || defined(HX8357_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(ST7796_CS) ||              \
+    defined(USE_SPISSD1306)
         screen = new graphics::Screen(screen_found, screen_model, screen_geometry);
 #elif defined(ARCH_PORTDUINO)
         if ((screen_found.port != ScanI2C::I2CPort::NO_I2C || settingsMap[displayPanel]) &&
@@ -1140,7 +1146,8 @@ void setup()
 // Don't call screen setup until after nodedb is setup (because we need
 // the current region name)
 #if defined(ST7701_CS) || defined(ST7735_CS) || defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) ||       \
-    defined(ST7789_CS) || defined(HX8357_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(ST7796_CS)
+    defined(ST7789_CS) || defined(HX8357_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(ST7796_CS) ||              \
+    defined(USE_SPISSD1306)
     if (screen)
         screen->setup();
 #elif defined(ARCH_PORTDUINO)
diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp
index c8eba1b2e..6473722d7 100644
--- a/src/mesh/NodeDB.cpp
+++ b/src/mesh/NodeDB.cpp
@@ -663,7 +663,7 @@ void NodeDB::installDefaultConfig(bool preserveKey = false)
     config.bluetooth.fixed_pin = defaultBLEPin;
 
 #if defined(ST7735_CS) || defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7789_CS) ||       \
-    defined(HX8357_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(ST7796_CS)
+    defined(HX8357_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(ST7796_CS) || defined(USE_SPISSD1306)
     bool hasScreen = true;
 #ifdef HELTEC_MESH_NODE_T114
     uint32_t st7789_id = get_st7789_id(ST7789_NSS, ST7789_SCK, ST7789_SDA, ST7789_RS, ST7789_RESET);
diff --git a/src/modules/CannedMessageModule.cpp b/src/modules/CannedMessageModule.cpp
index e9165e57c..2fc0bf4a6 100644
--- a/src/modules/CannedMessageModule.cpp
+++ b/src/modules/CannedMessageModule.cpp
@@ -1432,10 +1432,17 @@ void CannedMessageModule::drawDestinationSelectionScreen(OLEDDisplay *display, O
                 meshtastic_NodeInfoLite *node = this->filteredNodes[nodeIndex].node;
                 if (node) {
                     if (node->is_favorite) {
+#if defined(M5STACK_UNITC6L)
+                        snprintf(entryText, sizeof(entryText), "* %s", node->user.short_name);
+                    } else {
+                        snprintf(entryText, sizeof(entryText), "%s", node->user.short_name);
+                    }
+#else
                         snprintf(entryText, sizeof(entryText), "* %s", node->user.long_name);
                     } else {
                         snprintf(entryText, sizeof(entryText), "%s", node->user.long_name);
                     }
+#endif
                 }
             }
         }
@@ -1606,7 +1613,11 @@ void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *st
         int yOffset = y + 10;
 #else
         display->setFont(FONT_MEDIUM);
+#if defined(M5STACK_UNITC6L)
+        int yOffset = y;
+#else
         int yOffset = y + 10;
+#endif
 #endif
 
         // --- Delivery Status Message ---
@@ -1631,13 +1642,20 @@ void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *st
         }
 
         display->drawString(display->getWidth() / 2 + x, yOffset, buffer);
+#if defined(M5STACK_UNITC6L)
+        yOffset += lineCount * FONT_HEIGHT_MEDIUM - 5; // only 1 line gap, no extra padding
+#else
         yOffset += lineCount * FONT_HEIGHT_MEDIUM; // only 1 line gap, no extra padding
-
+#endif
 #ifndef USE_EINK
         // --- SNR + RSSI Compact Line ---
         if (this->ack) {
             display->setFont(FONT_SMALL);
+#if defined(M5STACK_UNITC6L)
+            snprintf(buffer, sizeof(buffer), "SNR: %.1f dB \nRSSI: %d", this->lastRxSnr, this->lastRxRssi);
+#else
             snprintf(buffer, sizeof(buffer), "SNR: %.1f dB   RSSI: %d", this->lastRxSnr, this->lastRxRssi);
+#endif
             display->drawString(display->getWidth() / 2 + x, yOffset, buffer);
         }
 #endif
diff --git a/src/modules/Modules.cpp b/src/modules/Modules.cpp
index d4beb6824..757753d45 100644
--- a/src/modules/Modules.cpp
+++ b/src/modules/Modules.cpp
@@ -7,6 +7,7 @@
 #include "input/RotaryEncoderInterruptImpl1.h"
 #include "input/SerialKeyboardImpl.h"
 #include "input/UpDownInterruptImpl1.h"
+#include "input/i2cButton.h"
 #include "modules/SystemCommandsModule.h"
 #if HAS_TRACKBALL
 #include "input/TrackballInterruptImpl1.h"
@@ -196,6 +197,9 @@ void setupModules()
 #endif
             cardKbI2cImpl = new CardKbI2cImpl();
             cardKbI2cImpl->init();
+#if defined(M5STACK_UNITC6L)
+            i2cButton = new i2cButtonThread("i2cButtonThread");
+#endif
 #ifdef INPUTBROKER_MATRIX_TYPE
             kbMatrixImpl = new KbMatrixImpl();
             kbMatrixImpl->init();
diff --git a/src/nimble/NimbleBluetooth.cpp b/src/nimble/NimbleBluetooth.cpp
index ee95168c3..0eb8e9bdd 100644
--- a/src/nimble/NimbleBluetooth.cpp
+++ b/src/nimble/NimbleBluetooth.cpp
@@ -11,6 +11,12 @@
 #include 
 #include 
 
+#ifdef NIMBLE_TWO
+#include "NimBLEAdvertising.h"
+#include "NimBLEExtAdvertising.h"
+#include "PowerStatus.h"
+#endif
+
 NimBLECharacteristic *fromNumCharacteristic;
 NimBLECharacteristic *BatteryCharacteristic;
 NimBLECharacteristic *logRadioCharacteristic;
@@ -56,13 +62,18 @@ class BluetoothPhoneAPI : public PhoneAPI, public concurrency::OSThread
     {
         PhoneAPI::onNowHasData(fromRadioNum);
 
-        LOG_DEBUG("BLE notify fromNum");
+        uint8_t cc = bleServer->getConnectedCount();
+        LOG_DEBUG("BLE notify fromNum: %d connections: %d", fromRadioNum, cc);
 
         uint8_t val[4];
         put_le32(val, fromRadioNum);
 
         fromNumCharacteristic->setValue(val, sizeof(val));
+#ifdef NIMBLE_TWO
+        fromNumCharacteristic->notify(val, sizeof(val), BLE_HS_CONN_HANDLE_NONE);
+#else
         fromNumCharacteristic->notify();
+#endif
     }
 
     /// Check the current underlying physical link to see if the client is currently connected
@@ -79,7 +90,12 @@ static uint8_t lastToRadio[MAX_TO_FROM_RADIO_SIZE];
 
 class NimbleBluetoothToRadioCallback : public NimBLECharacteristicCallbacks
 {
+#ifdef NIMBLE_TWO
+    virtual void onWrite(NimBLECharacteristic *pCharacteristic, NimBLEConnInfo &connInfo)
+#else
     virtual void onWrite(NimBLECharacteristic *pCharacteristic)
+
+#endif
     {
         auto val = pCharacteristic->getValue();
 
@@ -97,7 +113,11 @@ class NimbleBluetoothToRadioCallback : public NimBLECharacteristicCallbacks
 
 class NimbleBluetoothFromRadioCallback : public NimBLECharacteristicCallbacks
 {
+#ifdef NIMBLE_TWO
+    virtual void onRead(NimBLECharacteristic *pCharacteristic, NimBLEConnInfo &connInfo)
+#else
     virtual void onRead(NimBLECharacteristic *pCharacteristic)
+#endif
     {
         int tries = 0;
         bluetoothPhoneAPI->phoneWants = true;
@@ -107,9 +127,7 @@ class NimbleBluetoothFromRadioCallback : public NimBLECharacteristicCallbacks
             tries++;
         }
         std::lock_guard guard(bluetoothPhoneAPI->nimble_mutex);
-        std::string fromRadioByteString(bluetoothPhoneAPI->fromRadioBytes,
-                                        bluetoothPhoneAPI->fromRadioBytes + bluetoothPhoneAPI->numBytes);
-        pCharacteristic->setValue(fromRadioByteString);
+        pCharacteristic->setValue(bluetoothPhoneAPI->fromRadioBytes, bluetoothPhoneAPI->numBytes);
 
         if (bluetoothPhoneAPI->numBytes != 0) // if we did send something, queue it up right away to reload
             bluetoothPhoneAPI->setIntervalFromNow(0);
@@ -121,7 +139,17 @@ class NimbleBluetoothFromRadioCallback : public NimBLECharacteristicCallbacks
 
 class NimbleBluetoothServerCallback : public NimBLEServerCallbacks
 {
+#ifdef NIMBLE_TWO
+  public:
+    NimbleBluetoothServerCallback(NimbleBluetooth *ble) { this->ble = ble; }
+
+  private:
+    NimbleBluetooth *ble;
+
+    virtual uint32_t onPassKeyDisplay()
+#else
     virtual uint32_t onPassKeyRequest()
+#endif
     {
         uint32_t passkey = config.bluetooth.fixed_pin;
 
@@ -170,7 +198,11 @@ class NimbleBluetoothServerCallback : public NimBLEServerCallbacks
         return passkey;
     }
 
+#ifdef NIMBLE_TWO
+    virtual void onAuthenticationComplete(NimBLEConnInfo &connInfo)
+#else
     virtual void onAuthenticationComplete(ble_gap_conn_desc *desc)
+#endif
     {
         LOG_INFO("BLE authentication complete");
 
@@ -185,9 +217,20 @@ class NimbleBluetoothServerCallback : public NimBLEServerCallbacks
         }
     }
 
+#ifdef NIMBLE_TWO
+    virtual void onConnect(NimBLEServer *pServer, NimBLEConnInfo &connInfo)
+    {
+        LOG_INFO("BLE incoming connection %s", connInfo.getAddress().toString().c_str());
+    }
+
+    virtual void onDisconnect(NimBLEServer *pServer, NimBLEConnInfo &connInfo, int reason)
+    {
+        LOG_INFO("BLE disconnect reason: %d", reason);
+#else
     virtual void onDisconnect(NimBLEServer *pServer, ble_gap_conn_desc *desc)
     {
         LOG_INFO("BLE disconnect");
+#endif
 
         meshtastic::BluetoothStatus newStatus(meshtastic::BluetoothStatus::ConnectionState::DISCONNECTED);
         bluetoothStatus->updateStatus(&newStatus);
@@ -200,6 +243,10 @@ class NimbleBluetoothServerCallback : public NimBLEServerCallbacks
             bluetoothPhoneAPI->numBytes = 0;
             bluetoothPhoneAPI->queue_size = 0;
         }
+#ifdef NIMBLE_TWO
+        // Restart Advertising
+        ble->startAdvertising();
+#endif
     }
 };
 
@@ -251,7 +298,11 @@ int NimbleBluetooth::getRssi()
     if (bleServer && isConnected()) {
         auto service = bleServer->getServiceByUUID(MESH_SERVICE_UUID);
         uint16_t handle = service->getHandle();
+#ifdef NIMBLE_TWO
+        return NimBLEDevice::getClientByHandle(handle)->getRssi();
+#else
         return NimBLEDevice::getClientByID(handle)->getRssi();
+#endif
     }
     return 0; // FIXME figure out where to source this
 }
@@ -273,8 +324,11 @@ void NimbleBluetooth::setup()
         NimBLEDevice::setSecurityIOCap(BLE_HS_IO_DISPLAY_ONLY);
     }
     bleServer = NimBLEDevice::createServer();
-
+#ifdef NIMBLE_TWO
+    NimbleBluetoothServerCallback *serverCallbacks = new NimbleBluetoothServerCallback(this);
+#else
     NimbleBluetoothServerCallback *serverCallbacks = new NimbleBluetoothServerCallback();
+#endif
     bleServer->setCallbacks(serverCallbacks, true);
     setupService();
     startAdvertising();
@@ -318,8 +372,11 @@ void NimbleBluetooth::setupService()
     NimBLEService *batteryService = bleServer->createService(NimBLEUUID((uint16_t)0x180f)); // 0x180F is the Battery Service
     BatteryCharacteristic = batteryService->createCharacteristic( // 0x2A19 is the Battery Level characteristic)
         (uint16_t)0x2a19, NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::NOTIFY, 1);
-
+#ifdef NIMBLE_TWO
+    NimBLE2904 *batteryLevelDescriptor = BatteryCharacteristic->create2904();
+#else
     NimBLE2904 *batteryLevelDescriptor = (NimBLE2904 *)BatteryCharacteristic->createDescriptor((uint16_t)0x2904);
+#endif
     batteryLevelDescriptor->setFormat(NimBLE2904::FORMAT_UINT8);
     batteryLevelDescriptor->setNamespace(1);
     batteryLevelDescriptor->setUnit(0x27ad);
@@ -329,11 +386,40 @@ void NimbleBluetooth::setupService()
 
 void NimbleBluetooth::startAdvertising()
 {
+#ifdef NIMBLE_TWO
+    NimBLEExtAdvertising *pAdvertising = NimBLEDevice::getAdvertising();
+    NimBLEExtAdvertisement legacyAdvertising;
+
+    legacyAdvertising.setLegacyAdvertising(true);
+    legacyAdvertising.setScannable(true);
+    legacyAdvertising.setConnectable(true);
+    legacyAdvertising.setFlags(BLE_HS_ADV_F_DISC_GEN);
+    if (powerStatus->getHasBattery() == 1) {
+        legacyAdvertising.setCompleteServices(NimBLEUUID((uint16_t)0x180f));
+    }
+    legacyAdvertising.setCompleteServices(NimBLEUUID(MESH_SERVICE_UUID));
+    legacyAdvertising.setMinInterval(500);
+    legacyAdvertising.setMaxInterval(1000);
+
+    NimBLEExtAdvertisement legacyScanResponse;
+    legacyScanResponse.setLegacyAdvertising(true);
+    legacyScanResponse.setConnectable(true);
+    legacyScanResponse.setName(getDeviceName());
+
+    if (!pAdvertising->setInstanceData(0, legacyAdvertising)) {
+        LOG_ERROR("BLE failed to set legacyAdvertising");
+    } else if (!pAdvertising->setScanResponseData(0, legacyScanResponse)) {
+        LOG_ERROR("BLE failed to set legacyScanResponse");
+    } else if (!pAdvertising->start(0, 0, 0)) {
+        LOG_ERROR("BLE failed to start legacyAdvertising");
+    }
+#else
     NimBLEAdvertising *pAdvertising = NimBLEDevice::getAdvertising();
     pAdvertising->reset();
     pAdvertising->addServiceUUID(MESH_SERVICE_UUID);
     pAdvertising->addServiceUUID(NimBLEUUID((uint16_t)0x180f)); // 0x180F is the Battery Service
     pAdvertising->start(0);
+#endif
 }
 
 /// Given a level between 0-100, update the BLE attribute
@@ -341,7 +427,11 @@ void updateBatteryLevel(uint8_t level)
 {
     if ((config.bluetooth.enabled == true) && bleServer && nimbleBluetooth->isConnected()) {
         BatteryCharacteristic->setValue(&level, 1);
+#ifdef NIMBLE_TWO
+        BatteryCharacteristic->notify(&level, 1, BLE_HS_CONN_HANDLE_NONE);
+#else
         BatteryCharacteristic->notify();
+#endif
     }
 }
 
@@ -356,7 +446,11 @@ void NimbleBluetooth::sendLog(const uint8_t *logMessage, size_t length)
     if (!bleServer || !isConnected() || length > 512) {
         return;
     }
+#ifdef NIMBLE_TWO
+    logRadioCharacteristic->notify(logMessage, length, BLE_HS_CONN_HANDLE_NONE);
+#else
     logRadioCharacteristic->notify(logMessage, length, true);
+#endif
 }
 
 void clearNVS()
@@ -366,4 +460,4 @@ void clearNVS()
     ESP.restart();
 #endif
 }
-#endif
+#endif
\ No newline at end of file
diff --git a/src/nimble/NimbleBluetooth.h b/src/nimble/NimbleBluetooth.h
index 45602e088..899355b4d 100644
--- a/src/nimble/NimbleBluetooth.h
+++ b/src/nimble/NimbleBluetooth.h
@@ -12,10 +12,15 @@ class NimbleBluetooth : BluetoothApi
     bool isConnected();
     int getRssi();
     void sendLog(const uint8_t *logMessage, size_t length);
+#if defined(NIMBLE_TWO)
+    void startAdvertising();
+#endif
 
   private:
     void setupService();
+#if !defined(NIMBLE_TWO)
     void startAdvertising();
+#endif
 };
 
 void setBluetoothEnable(bool enable);
diff --git a/src/platform/esp32/architecture.h b/src/platform/esp32/architecture.h
index cb0f0dab3..22ce6487f 100644
--- a/src/platform/esp32/architecture.h
+++ b/src/platform/esp32/architecture.h
@@ -194,6 +194,8 @@
 #define HW_VENDOR meshtastic_HardwareModel_T_DECK_PRO
 #elif defined(T_LORA_PAGER)
 #define HW_VENDOR meshtastic_HardwareModel_T_LORA_PAGER
+#elif defined(M5STACK_UNITC6L)
+#define HW_VENDOR meshtastic_HardwareModel_M5STACK_C6L
 #endif
 
 // -----------------------------------------------------------------------------
diff --git a/variants/esp32c6/m5stack_unitc6l/pins_arduino.h b/variants/esp32c6/m5stack_unitc6l/pins_arduino.h
new file mode 100644
index 000000000..5b169a2d4
--- /dev/null
+++ b/variants/esp32c6/m5stack_unitc6l/pins_arduino.h
@@ -0,0 +1,28 @@
+#ifndef Pins_Arduino_h
+#define Pins_Arduino_h
+
+#include 
+
+#define USB_VID 0x2886
+#define USB_PID 0x0048
+
+static const uint8_t TX = 16;
+static const uint8_t RX = 17;
+
+static const uint8_t SDA = 10;
+static const uint8_t SCL = 8;
+
+// Default SPI will be mapped to Radio
+static const uint8_t MISO = 22;
+static const uint8_t SCK = 20;
+static const uint8_t MOSI = 21;
+static const uint8_t SS = 6;
+
+// #define SPI_MOSI (11)
+// #define SPI_SCK (14)
+// #define SPI_MISO (2)
+// #define SPI_CS (13)
+
+// #define SDCARD_CS SPI_CS
+
+#endif /* Pins_Arduino_h */
diff --git a/variants/esp32c6/m5stack_unitc6l/platformio.ini b/variants/esp32c6/m5stack_unitc6l/platformio.ini
new file mode 100644
index 000000000..da1c70c0a
--- /dev/null
+++ b/variants/esp32c6/m5stack_unitc6l/platformio.ini
@@ -0,0 +1,35 @@
+[env:m5stack-unitc6l]
+extends = esp32c6_base
+board = esp32-c6-devkitc-1
+;OpenOCD flash method
+;upload_protocol = esp-builtin
+;Normal method
+upload_protocol = esptool
+;upload_port = /dev/ttyACM2
+build_unflags =
+  -D HAS_BLUETOOTH
+  -D MESHTASTIC_EXCLUDE_BLUETOOTH
+  -D HAS_WIFI
+lib_deps =
+  ${esp32c6_base.lib_deps}
+  adafruit/Adafruit NeoPixel@^1.12.3
+  h2zero/NimBLE-Arduino@^2.3.6
+build_flags = 
+  ${esp32c6_base.build_flags}
+  -D M5STACK_UNITC6L
+  -I variants/esp32c6/m5stack_unitc6l
+  -DMESHTASTIC_EXCLUDE_PAXCOUNTER=1
+  -DARDUINO_USB_CDC_ON_BOOT=1
+  -DARDUINO_USB_MODE=1
+  -D HAS_BLUETOOTH=1
+  -D MESHTASTIC_EXCLUDE_WEBSERVER
+  -D MESHTASTIC_EXCLUDE_MQTT
+	-DCONFIG_BT_NIMBLE_EXT_ADV=1
+	-DCONFIG_BT_NIMBLE_MAX_EXT_ADV_INSTANCES=2
+  -D NIMBLE_TWO
+monitor_speed=115200
+lib_ignore =
+  NonBlockingRTTTL
+  libpax
+build_src_filter = 
+ ${esp32c6_base.build_src_filter} +<../variants/esp32c6/m5stack_unitc6l>
\ No newline at end of file
diff --git a/variants/esp32c6/m5stack_unitc6l/variant.cpp b/variants/esp32c6/m5stack_unitc6l/variant.cpp
new file mode 100644
index 000000000..8e26b4ab7
--- /dev/null
+++ b/variants/esp32c6/m5stack_unitc6l/variant.cpp
@@ -0,0 +1,74 @@
+#include "driver/gpio.h"
+#include 
+#include 
+// I2C device addr
+#define PI4IO_M_ADDR 0x43
+
+// PI4IO registers
+#define PI4IO_REG_CHIP_RESET 0x01
+#define PI4IO_REG_IO_DIR 0x03
+#define PI4IO_REG_OUT_SET 0x05
+#define PI4IO_REG_OUT_H_IM 0x07
+#define PI4IO_REG_IN_DEF_STA 0x09
+#define PI4IO_REG_PULL_EN 0x0B
+#define PI4IO_REG_PULL_SEL 0x0D
+#define PI4IO_REG_IN_STA 0x0F
+#define PI4IO_REG_INT_MASK 0x11
+#define PI4IO_REG_IRQ_STA 0x13
+// PI4IO
+
+#define setbit(x, y) x |= (0x01 << y)
+#define clrbit(x, y) x &= ~(0x01 << y)
+#define reversebit(x, y) x ^= (0x01 << y)
+#define getbit(x, y) ((x) >> (y)&0x01)
+
+void i2c_read_byte(uint8_t addr, uint8_t reg, uint8_t *value)
+{
+    Wire.beginTransmission(addr);
+    Wire.write(reg);
+    Wire.endTransmission();
+    Wire.requestFrom(addr, 1);
+    *value = Wire.read();
+}
+
+/*******************************************************************/
+void i2c_write_byte(uint8_t addr, uint8_t reg, uint8_t value)
+{
+    Wire.beginTransmission(addr);
+    Wire.write(reg);
+    Wire.write(value);
+    Wire.endTransmission();
+}
+/*******************************************************************/
+void c6l_init()
+{
+    // P7 LoRa Reset
+    // P6 RF Switch
+    // P5 LNA Enable
+
+    printf("pi4io_init\n");
+    uint8_t in_data;
+    i2c_write_byte(PI4IO_M_ADDR, PI4IO_REG_CHIP_RESET, 0xFF);
+    vTaskDelay(10 / portTICK_PERIOD_MS);
+    i2c_read_byte(PI4IO_M_ADDR, PI4IO_REG_CHIP_RESET, &in_data);
+    vTaskDelay(10 / portTICK_PERIOD_MS);
+    i2c_write_byte(PI4IO_M_ADDR, PI4IO_REG_IO_DIR, 0b11000000); // 0: input 1: output
+    vTaskDelay(10 / portTICK_PERIOD_MS);
+    i2c_write_byte(PI4IO_M_ADDR, PI4IO_REG_OUT_H_IM, 0b00111100); // 使用到的引脚关闭High-Impedance
+    vTaskDelay(10 / portTICK_PERIOD_MS);
+    i2c_write_byte(PI4IO_M_ADDR, PI4IO_REG_PULL_SEL, 0b11000011); // pull up/down select, 0 down, 1 up
+    vTaskDelay(10 / portTICK_PERIOD_MS);
+    i2c_write_byte(PI4IO_M_ADDR, PI4IO_REG_PULL_EN, 0b11000011); // pull up/down enable, 0 disable, 1 enable
+    vTaskDelay(10 / portTICK_PERIOD_MS);
+    i2c_write_byte(PI4IO_M_ADDR, PI4IO_REG_IN_DEF_STA, 0b00000011); // P0 P1 默认高电平, 按键按下触发中断
+    vTaskDelay(10 / portTICK_PERIOD_MS);
+    i2c_write_byte(PI4IO_M_ADDR, PI4IO_REG_INT_MASK, 0b11111100); // P0 P1 中断使能 0 enable, 1 disable
+    vTaskDelay(10 / portTICK_PERIOD_MS);
+    i2c_write_byte(PI4IO_M_ADDR, PI4IO_REG_OUT_SET, 0b10000000); // 默认输出为0
+    vTaskDelay(10 / portTICK_PERIOD_MS);
+    i2c_read_byte(PI4IO_M_ADDR, PI4IO_REG_IRQ_STA, &in_data); // 读取IRQ_STA清除标志
+
+    i2c_read_byte(PI4IO_M_ADDR, PI4IO_REG_OUT_SET, &in_data);
+    setbit(in_data, 6); // HIGH
+    i2c_write_byte(PI4IO_M_ADDR, PI4IO_REG_OUT_SET, in_data);
+}
diff --git a/variants/esp32c6/m5stack_unitc6l/variant.h b/variants/esp32c6/m5stack_unitc6l/variant.h
new file mode 100644
index 000000000..d973aa281
--- /dev/null
+++ b/variants/esp32c6/m5stack_unitc6l/variant.h
@@ -0,0 +1,52 @@
+void c6l_init();
+
+#define HAS_GPS 1
+#define GPS_RX_PIN 4
+#define GPS_TX_PIN 5
+
+#define I2C_SDA 10
+#define I2C_SCL 8
+
+#define PIN_BUZZER 11
+
+#define HAS_NEOPIXEL                         // Enable the use of neopixels
+#define NEOPIXEL_COUNT 1                     // How many neopixels are connected
+#define NEOPIXEL_DATA 2                      // gpio pin used to send data to the neopixels
+#define NEOPIXEL_TYPE (NEO_GRB + NEO_KHZ800) // type of neopixels in use
+#define ENABLE_AMBIENTLIGHTING               // Turn on Ambient Lighting
+
+// #define BUTTON_PIN 9
+#define BUTTON_EXTENDER
+
+#undef LORA_SCK
+#undef LORA_MISO
+#undef LORA_MOSI
+#undef LORA_CS
+
+// WaveShare Core1262-868M OK
+// https://www.waveshare.com/wiki/Core1262-868M
+#define USE_SX1262
+
+#define LORA_MISO 22
+#define LORA_SCK 20
+#define LORA_MOSI 21
+#define LORA_CS 23
+#define LORA_RESET RADIOLIB_NC
+#define LORA_DIO1 7
+#define LORA_BUSY 19
+#define SX126X_CS LORA_CS
+#define SX126X_DIO1 LORA_DIO1
+#define SX126X_BUSY LORA_BUSY
+#define SX126X_RESET LORA_RESET
+#define SX126X_DIO2_AS_RF_SWITCH
+#define SX126X_DIO3_TCXO_VOLTAGE 3.0
+
+#define USE_SPISSD1306
+#ifdef USE_SPISSD1306
+#define SSD1306_NSS 6 // CS
+#define SSD1306_RS 18 // DC
+#define SSD1306_RESET 15
+// #define OLED_DG 1
+#endif
+#define SCREEN_TRANSITION_FRAMERATE 10
+#define BRIGHTNESS_DEFAULT 130 // Medium Low Brightness

From 6a8732bbaa0e7a6b741bc8874197229acc4bf73d Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Thu, 18 Sep 2025 05:51:02 -0500
Subject: [PATCH 241/691] Update Adafruit BusIO to v1.17.3 (#8018)

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 e2eb55dce..42453a33e 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -124,7 +124,7 @@ lib_deps =
 [environmental_base]
 lib_deps =
 	# renovate: datasource=custom.pio depName=Adafruit BusIO packageName=adafruit/library/Adafruit BusIO
-	adafruit/Adafruit BusIO@1.17.2
+	adafruit/Adafruit BusIO@1.17.3
 	# renovate: datasource=custom.pio depName=Adafruit Unified Sensor packageName=adafruit/library/Adafruit Unified Sensor
 	adafruit/Adafruit Unified Sensor@1.1.15
 	# renovate: datasource=custom.pio depName=Adafruit BMP280 packageName=adafruit/library/Adafruit BMP280 Library

From a70ffae82ca53c7e10395e85ae23a641ddc46d16 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Thu, 18 Sep 2025 05:51:22 -0500
Subject: [PATCH 242/691] Update actions/checkout action to v5 (#8020)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
 .github/workflows/merge_queue.yml | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)

diff --git a/.github/workflows/merge_queue.yml b/.github/workflows/merge_queue.yml
index e2264e250..3243d5867 100644
--- a/.github/workflows/merge_queue.yml
+++ b/.github/workflows/merge_queue.yml
@@ -27,7 +27,7 @@ jobs:
           - check
     runs-on: ubuntu-24.04
     steps:
-      - uses: actions/checkout@v4
+      - uses: actions/checkout@v5
       - uses: actions/setup-python@v5
         with:
           python-version: 3.x
@@ -57,7 +57,7 @@ jobs:
   version:
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout@v4
+      - uses: actions/checkout@v5
       - name: Get release version string
         run: |
           echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
@@ -78,7 +78,7 @@ jobs:
     runs-on: ubuntu-latest
     if: ${{ github.event_name != 'workflow_dispatch' }}
     steps:
-      - uses: actions/checkout@v4
+      - uses: actions/checkout@v5
       - name: Build base
         id: base
         uses: ./.github/actions/setup-base
@@ -274,7 +274,7 @@ jobs:
       ]
     steps:
       - name: Checkout code
-        uses: actions/checkout@v4
+        uses: actions/checkout@v5
         with:
           ref: ${{github.event.pull_request.head.ref}}
           repository: ${{github.event.pull_request.head.repo.full_name}}
@@ -353,7 +353,7 @@ jobs:
       - package-pio-deps-native-tft
     steps:
       - name: Checkout
-        uses: actions/checkout@v4
+        uses: actions/checkout@v5
 
       - name: Setup Python
         uses: actions/setup-python@v5
@@ -422,7 +422,7 @@ jobs:
     needs: [release-artifacts, version]
     steps:
       - name: Checkout
-        uses: actions/checkout@v4
+        uses: actions/checkout@v5
 
       - name: Setup Python
         uses: actions/setup-python@v5
@@ -477,7 +477,7 @@ jobs:
         esp32,esp32s3,esp32c3,esp32c6,nrf52840,rp2040,rp2350,stm32
     steps:
       - name: Checkout
-        uses: actions/checkout@v4
+        uses: actions/checkout@v5
 
       - name: Setup Python
         uses: actions/setup-python@v5

From ec7415b3fd8ab8b44c3b2ee642045da3d0314870 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Thu, 18 Sep 2025 05:55:24 -0500
Subject: [PATCH 243/691] Update actions/setup-python action to v6 (#8023)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
 .github/workflows/merge_queue.yml | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/.github/workflows/merge_queue.yml b/.github/workflows/merge_queue.yml
index 3243d5867..472ec2a9e 100644
--- a/.github/workflows/merge_queue.yml
+++ b/.github/workflows/merge_queue.yml
@@ -28,7 +28,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
@@ -356,7 +356,7 @@ jobs:
         uses: actions/checkout@v5
 
       - name: Setup Python
-        uses: actions/setup-python@v5
+        uses: actions/setup-python@v6
         with:
           python-version: 3.x
 
@@ -425,7 +425,7 @@ jobs:
         uses: actions/checkout@v5
 
       - name: Setup Python
-        uses: actions/setup-python@v5
+        uses: actions/setup-python@v6
         with:
           python-version: 3.x
 
@@ -480,7 +480,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 953fdc9eedfc5b8042df6e23ae5fd9300e86c7f4 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
 <41898282+github-actions[bot]@users.noreply.github.com>
Date: Thu, 18 Sep 2025 05:55:44 -0500
Subject: [PATCH 244/691] Upgrade trunk (#8025)

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 c1fde9602..f23c41810 100644
--- a/.trunk/trunk.yaml
+++ b/.trunk/trunk.yaml
@@ -9,7 +9,7 @@ plugins:
 lint:
   enabled:
     - checkov@3.2.471
-    - renovate@41.115.2
+    - renovate@41.115.6
     - prettier@3.6.2
     - trufflehog@3.90.6
     - yamllint@1.37.1

From 188283b38290b94dccafd3c1bf753961ac658895 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Thu, 18 Sep 2025 05:56:05 -0500
Subject: [PATCH 245/691] Update actions/download-artifact action to v5 (#8021)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
 .github/workflows/merge_queue.yml | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)

diff --git a/.github/workflows/merge_queue.yml b/.github/workflows/merge_queue.yml
index 472ec2a9e..7f397ce18 100644
--- a/.github/workflows/merge_queue.yml
+++ b/.github/workflows/merge_queue.yml
@@ -279,7 +279,7 @@ jobs:
           ref: ${{github.event.pull_request.head.ref}}
           repository: ${{github.event.pull_request.head.repo.full_name}}
 
-      - uses: actions/download-artifact@v4
+      - uses: actions/download-artifact@v5
         with:
           path: ./
           pattern: firmware-${{matrix.arch}}-*
@@ -308,7 +308,7 @@ jobs:
             ./Meshtastic_nRF52_factory_erase*.uf2
           retention-days: 30
 
-      - uses: actions/download-artifact@v4
+      - uses: actions/download-artifact@v5
         with:
           name: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}
           merge-multiple: true
@@ -372,14 +372,14 @@ jobs:
             Autogenerated by github action, developer should edit as required before publishing...
 
       - name: Download source deb
-        uses: actions/download-artifact@v4
+        uses: actions/download-artifact@v5
         with:
           pattern: firmware-debian-${{ needs.version.outputs.deb }}~UNRELEASED-src
           merge-multiple: true
           path: ./output/debian-src
 
       - name: Download `native-tft` pio deps
-        uses: actions/download-artifact@v4
+        uses: actions/download-artifact@v5
         with:
           pattern: platformio-deps-native-tft-${{ needs.version.outputs.long }}
           merge-multiple: true
@@ -429,7 +429,7 @@ jobs:
         with:
           python-version: 3.x
 
-      - uses: actions/download-artifact@v4
+      - uses: actions/download-artifact@v5
         with:
           pattern: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}
           merge-multiple: true
@@ -446,7 +446,7 @@ jobs:
       - name: Zip firmware
         run: zip -j -9 -r ./firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip ./output
 
-      - uses: actions/download-artifact@v4
+      - uses: actions/download-artifact@v5
         with:
           name: debug-elfs-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip
           merge-multiple: true
@@ -484,7 +484,7 @@ jobs:
         with:
           python-version: 3.x
 
-      - uses: actions/download-artifact@v4
+      - uses: actions/download-artifact@v5
         with:
           pattern: firmware-{${{ env.targets }}}-${{ needs.version.outputs.long }}
           merge-multiple: true

From d8381aa905dbe48daa3569fecde137547c9664eb Mon Sep 17 00:00:00 2001
From: Markus <974709+Links2004@users.noreply.github.com>
Date: Thu, 18 Sep 2025 13:32:56 +0200
Subject: [PATCH 246/691] Fix init for InputEvent (#8015)

---
 src/input/ExpressLRSFiveWay.cpp          |  2 +-
 src/input/LinuxInput.cpp                 |  2 +-
 src/input/RotaryEncoderInterruptBase.cpp |  2 +-
 src/input/SeesawRotary.cpp               |  2 +-
 src/input/SerialKeyboard.cpp             |  4 ++--
 src/input/TouchScreenImpl1.cpp           |  2 +-
 src/input/TrackballInterruptBase.cpp     |  2 +-
 src/input/UpDownInterruptBase.cpp        |  2 +-
 src/input/kbI2cBase.cpp                  | 10 +++++-----
 src/input/kbMatrixBase.cpp               |  2 +-
 10 files changed, 15 insertions(+), 15 deletions(-)

diff --git a/src/input/ExpressLRSFiveWay.cpp b/src/input/ExpressLRSFiveWay.cpp
index 776b9001d..01712ad2a 100644
--- a/src/input/ExpressLRSFiveWay.cpp
+++ b/src/input/ExpressLRSFiveWay.cpp
@@ -188,7 +188,7 @@ void ExpressLRSFiveWay::determineAction(KeyType key, PressLength length)
 // Feed input to the canned messages module
 void ExpressLRSFiveWay::sendKey(input_broker_event key)
 {
-    InputEvent e;
+    InputEvent e = {};
     e.source = inputSourceName;
     e.inputEvent = key;
     notifyObservers(&e);
diff --git a/src/input/LinuxInput.cpp b/src/input/LinuxInput.cpp
index 1f80fd5d3..fee7c8ded 100644
--- a/src/input/LinuxInput.cpp
+++ b/src/input/LinuxInput.cpp
@@ -73,7 +73,7 @@ int32_t LinuxInput::runOnce()
         int rd = read(events[i].data.fd, ev, sizeof(ev));
         assert(rd > ((signed int)sizeof(struct input_event)));
         for (int j = 0; j < rd / ((signed int)sizeof(struct input_event)); j++) {
-            InputEvent e;
+            InputEvent e = {};
             e.inputEvent = INPUT_BROKER_NONE;
             e.source = this->_originName;
             e.kbchar = 0;
diff --git a/src/input/RotaryEncoderInterruptBase.cpp b/src/input/RotaryEncoderInterruptBase.cpp
index 204a0fbf0..016727cd7 100644
--- a/src/input/RotaryEncoderInterruptBase.cpp
+++ b/src/input/RotaryEncoderInterruptBase.cpp
@@ -45,7 +45,7 @@ void RotaryEncoderInterruptBase::init(
 
 int32_t RotaryEncoderInterruptBase::runOnce()
 {
-    InputEvent e;
+    InputEvent e = {};
     e.inputEvent = INPUT_BROKER_NONE;
     e.source = this->_originName;
     unsigned long now = millis();
diff --git a/src/input/SeesawRotary.cpp b/src/input/SeesawRotary.cpp
index c212773c4..0a6e6e974 100644
--- a/src/input/SeesawRotary.cpp
+++ b/src/input/SeesawRotary.cpp
@@ -49,7 +49,7 @@ bool SeesawRotary::init()
 
 int32_t SeesawRotary::runOnce()
 {
-    InputEvent e;
+    InputEvent e = {};
     e.inputEvent = INPUT_BROKER_NONE;
     bool currentlyPressed = !ss.digitalRead(SS_SWITCH);
 
diff --git a/src/input/SerialKeyboard.cpp b/src/input/SerialKeyboard.cpp
index 63501bda5..2df1ace70 100644
--- a/src/input/SerialKeyboard.cpp
+++ b/src/input/SerialKeyboard.cpp
@@ -29,7 +29,7 @@ SerialKeyboard::SerialKeyboard(const char *name) : concurrency::OSThread(name)
 
 void SerialKeyboard::erase()
 {
-    InputEvent e;
+    InputEvent e = {};
     e.inputEvent = INPUT_BROKER_BACK;
     e.kbchar = 0x08;
     e.source = this->_originName;
@@ -80,7 +80,7 @@ int32_t SerialKeyboard::runOnce()
 
         if (keys < prevKeys) { // a new key has been pressed (and not released), doesn't works for multiple presses at once but
                                // shouldn't be a limitation
-            InputEvent e;
+            InputEvent e = {};
             e.inputEvent = INPUT_BROKER_NONE;
             e.source = this->_originName;
             // SELECT OR SEND OR CANCEL EVENT
diff --git a/src/input/TouchScreenImpl1.cpp b/src/input/TouchScreenImpl1.cpp
index c0e220941..69dcab04e 100644
--- a/src/input/TouchScreenImpl1.cpp
+++ b/src/input/TouchScreenImpl1.cpp
@@ -47,7 +47,7 @@ bool TouchScreenImpl1::getTouch(int16_t &x, int16_t &y)
  */
 void TouchScreenImpl1::onEvent(const TouchEvent &event)
 {
-    InputEvent e;
+    InputEvent e = {};
     e.source = event.source;
     e.kbchar = 0;
     e.touchX = event.x;
diff --git a/src/input/TrackballInterruptBase.cpp b/src/input/TrackballInterruptBase.cpp
index 4c8ce6409..4ddaf7064 100644
--- a/src/input/TrackballInterruptBase.cpp
+++ b/src/input/TrackballInterruptBase.cpp
@@ -51,7 +51,7 @@ void TrackballInterruptBase::init(uint8_t pinDown, uint8_t pinUp, uint8_t pinLef
 
 int32_t TrackballInterruptBase::runOnce()
 {
-    InputEvent e;
+    InputEvent e = {};
     e.inputEvent = INPUT_BROKER_NONE;
 
     // Handle long press detection for press button
diff --git a/src/input/UpDownInterruptBase.cpp b/src/input/UpDownInterruptBase.cpp
index 0bf0f5cd4..d597c8d8f 100644
--- a/src/input/UpDownInterruptBase.cpp
+++ b/src/input/UpDownInterruptBase.cpp
@@ -48,7 +48,7 @@ void UpDownInterruptBase::init(uint8_t pinDown, uint8_t pinUp, uint8_t pinPress,
 
 int32_t UpDownInterruptBase::runOnce()
 {
-    InputEvent e;
+    InputEvent e = {};
     e.inputEvent = INPUT_BROKER_NONE;
     unsigned long now = millis();
 
diff --git a/src/input/kbI2cBase.cpp b/src/input/kbI2cBase.cpp
index 5db1e39a9..3f5d4d184 100644
--- a/src/input/kbI2cBase.cpp
+++ b/src/input/kbI2cBase.cpp
@@ -90,7 +90,7 @@ int32_t KbI2cBase::runOnce()
         while (keyCount--) {
             const BBQ10Keyboard::KeyEvent key = Q10keyboard.keyEvent();
             if ((key.key != 0x00) && (key.state == BBQ10Keyboard::StateRelease)) {
-                InputEvent e;
+                InputEvent e = {};
                 e.inputEvent = INPUT_BROKER_NONE;
                 e.source = this->_originName;
                 switch (key.key) {
@@ -187,7 +187,7 @@ int32_t KbI2cBase::runOnce()
     }
     case 0x37: { // MPR121
         MPRkeyboard.trigger();
-        InputEvent e;
+        InputEvent e = {};
 
         while (MPRkeyboard.hasEvent()) {
             char nextEvent = MPRkeyboard.dequeueEvent();
@@ -250,7 +250,7 @@ int32_t KbI2cBase::runOnce()
     }
     case 0x84: { // Adafruit TCA8418
         TCAKeyboard.trigger();
-        InputEvent e;
+        InputEvent e = {};
         while (TCAKeyboard.hasEvent()) {
             char nextEvent = TCAKeyboard.dequeueEvent();
             e.inputEvent = INPUT_BROKER_ANYKEY;
@@ -350,7 +350,7 @@ int32_t KbI2cBase::runOnce()
         }
         if (PrintDataBuf != 0) {
             LOG_DEBUG("RAK14004 key 0x%x pressed", PrintDataBuf);
-            InputEvent e;
+            InputEvent e = {};
             e.inputEvent = INPUT_BROKER_MATRIXKEY;
             e.source = this->_originName;
             e.kbchar = PrintDataBuf;
@@ -365,7 +365,7 @@ int32_t KbI2cBase::runOnce()
 
         if (i2cBus->available()) {
             char c = i2cBus->read();
-            InputEvent e;
+            InputEvent e = {};
             e.inputEvent = INPUT_BROKER_NONE;
             e.source = this->_originName;
             switch (c) {
diff --git a/src/input/kbMatrixBase.cpp b/src/input/kbMatrixBase.cpp
index 05f4d8177..18243f3bf 100644
--- a/src/input/kbMatrixBase.cpp
+++ b/src/input/kbMatrixBase.cpp
@@ -72,7 +72,7 @@ int32_t KbMatrixBase::runOnce()
             if (key != 0) {
                 LOG_DEBUG("Key 0x%x pressed", key);
                 // reset shift now that we have a keypress
-                InputEvent e;
+                InputEvent e = {};
                 e.inputEvent = INPUT_BROKER_NONE;
                 e.source = this->_originName;
                 switch (key) {

From 2567c03a3fbd30a7a1d3223328820dd60c8861fe Mon Sep 17 00:00:00 2001
From: Markus <974709+Links2004@users.noreply.github.com>
Date: Thu, 18 Sep 2025 13:32:56 +0200
Subject: [PATCH 247/691] Fix init for InputEvent (#8015)

---
 src/input/ExpressLRSFiveWay.cpp          |  2 +-
 src/input/LinuxInput.cpp                 |  2 +-
 src/input/RotaryEncoderInterruptBase.cpp |  2 +-
 src/input/SeesawRotary.cpp               |  2 +-
 src/input/SerialKeyboard.cpp             |  4 ++--
 src/input/TouchScreenImpl1.cpp           |  2 +-
 src/input/TrackballInterruptBase.cpp     |  2 +-
 src/input/UpDownInterruptBase.cpp        |  2 +-
 src/input/kbI2cBase.cpp                  | 10 +++++-----
 src/input/kbMatrixBase.cpp               |  2 +-
 10 files changed, 15 insertions(+), 15 deletions(-)

diff --git a/src/input/ExpressLRSFiveWay.cpp b/src/input/ExpressLRSFiveWay.cpp
index 776b9001d..01712ad2a 100644
--- a/src/input/ExpressLRSFiveWay.cpp
+++ b/src/input/ExpressLRSFiveWay.cpp
@@ -188,7 +188,7 @@ void ExpressLRSFiveWay::determineAction(KeyType key, PressLength length)
 // Feed input to the canned messages module
 void ExpressLRSFiveWay::sendKey(input_broker_event key)
 {
-    InputEvent e;
+    InputEvent e = {};
     e.source = inputSourceName;
     e.inputEvent = key;
     notifyObservers(&e);
diff --git a/src/input/LinuxInput.cpp b/src/input/LinuxInput.cpp
index 90f06ecc9..7620080f0 100644
--- a/src/input/LinuxInput.cpp
+++ b/src/input/LinuxInput.cpp
@@ -73,7 +73,7 @@ int32_t LinuxInput::runOnce()
         int rd = read(events[i].data.fd, ev, sizeof(ev));
         assert(rd > ((signed int)sizeof(struct input_event)));
         for (int j = 0; j < rd / ((signed int)sizeof(struct input_event)); j++) {
-            InputEvent e;
+            InputEvent e = {};
             e.inputEvent = INPUT_BROKER_NONE;
             e.source = this->_originName;
             e.kbchar = 0;
diff --git a/src/input/RotaryEncoderInterruptBase.cpp b/src/input/RotaryEncoderInterruptBase.cpp
index 88b07a389..3511a990a 100644
--- a/src/input/RotaryEncoderInterruptBase.cpp
+++ b/src/input/RotaryEncoderInterruptBase.cpp
@@ -43,7 +43,7 @@ void RotaryEncoderInterruptBase::init(
 
 int32_t RotaryEncoderInterruptBase::runOnce()
 {
-    InputEvent e;
+    InputEvent e = {};
     e.inputEvent = INPUT_BROKER_NONE;
     e.source = this->_originName;
 
diff --git a/src/input/SeesawRotary.cpp b/src/input/SeesawRotary.cpp
index c212773c4..0a6e6e974 100644
--- a/src/input/SeesawRotary.cpp
+++ b/src/input/SeesawRotary.cpp
@@ -49,7 +49,7 @@ bool SeesawRotary::init()
 
 int32_t SeesawRotary::runOnce()
 {
-    InputEvent e;
+    InputEvent e = {};
     e.inputEvent = INPUT_BROKER_NONE;
     bool currentlyPressed = !ss.digitalRead(SS_SWITCH);
 
diff --git a/src/input/SerialKeyboard.cpp b/src/input/SerialKeyboard.cpp
index 63501bda5..2df1ace70 100644
--- a/src/input/SerialKeyboard.cpp
+++ b/src/input/SerialKeyboard.cpp
@@ -29,7 +29,7 @@ SerialKeyboard::SerialKeyboard(const char *name) : concurrency::OSThread(name)
 
 void SerialKeyboard::erase()
 {
-    InputEvent e;
+    InputEvent e = {};
     e.inputEvent = INPUT_BROKER_BACK;
     e.kbchar = 0x08;
     e.source = this->_originName;
@@ -80,7 +80,7 @@ int32_t SerialKeyboard::runOnce()
 
         if (keys < prevKeys) { // a new key has been pressed (and not released), doesn't works for multiple presses at once but
                                // shouldn't be a limitation
-            InputEvent e;
+            InputEvent e = {};
             e.inputEvent = INPUT_BROKER_NONE;
             e.source = this->_originName;
             // SELECT OR SEND OR CANCEL EVENT
diff --git a/src/input/TouchScreenImpl1.cpp b/src/input/TouchScreenImpl1.cpp
index cea47faeb..50a70d66e 100644
--- a/src/input/TouchScreenImpl1.cpp
+++ b/src/input/TouchScreenImpl1.cpp
@@ -47,7 +47,7 @@ bool TouchScreenImpl1::getTouch(int16_t &x, int16_t &y)
  */
 void TouchScreenImpl1::onEvent(const TouchEvent &event)
 {
-    InputEvent e;
+    InputEvent e = {};
     e.source = event.source;
     e.kbchar = 0;
     e.touchX = event.x;
diff --git a/src/input/TrackballInterruptBase.cpp b/src/input/TrackballInterruptBase.cpp
index d41ad2fd6..a11628e1d 100644
--- a/src/input/TrackballInterruptBase.cpp
+++ b/src/input/TrackballInterruptBase.cpp
@@ -48,7 +48,7 @@ void TrackballInterruptBase::init(uint8_t pinDown, uint8_t pinUp, uint8_t pinLef
 
 int32_t TrackballInterruptBase::runOnce()
 {
-    InputEvent e;
+    InputEvent e = {};
     e.inputEvent = INPUT_BROKER_NONE;
 #if defined(T_DECK) // T-deck gets a super-simple debounce on trackball
     if (this->action == TB_ACTION_PRESSED) {
diff --git a/src/input/UpDownInterruptBase.cpp b/src/input/UpDownInterruptBase.cpp
index 26b281aaf..87b34b92f 100644
--- a/src/input/UpDownInterruptBase.cpp
+++ b/src/input/UpDownInterruptBase.cpp
@@ -40,7 +40,7 @@ void UpDownInterruptBase::init(uint8_t pinDown, uint8_t pinUp, uint8_t pinPress,
 
 int32_t UpDownInterruptBase::runOnce()
 {
-    InputEvent e;
+    InputEvent e = {};
     e.inputEvent = INPUT_BROKER_NONE;
     unsigned long now = millis();
     if (this->action == UPDOWN_ACTION_PRESSED) {
diff --git a/src/input/kbI2cBase.cpp b/src/input/kbI2cBase.cpp
index 5db1e39a9..3f5d4d184 100644
--- a/src/input/kbI2cBase.cpp
+++ b/src/input/kbI2cBase.cpp
@@ -90,7 +90,7 @@ int32_t KbI2cBase::runOnce()
         while (keyCount--) {
             const BBQ10Keyboard::KeyEvent key = Q10keyboard.keyEvent();
             if ((key.key != 0x00) && (key.state == BBQ10Keyboard::StateRelease)) {
-                InputEvent e;
+                InputEvent e = {};
                 e.inputEvent = INPUT_BROKER_NONE;
                 e.source = this->_originName;
                 switch (key.key) {
@@ -187,7 +187,7 @@ int32_t KbI2cBase::runOnce()
     }
     case 0x37: { // MPR121
         MPRkeyboard.trigger();
-        InputEvent e;
+        InputEvent e = {};
 
         while (MPRkeyboard.hasEvent()) {
             char nextEvent = MPRkeyboard.dequeueEvent();
@@ -250,7 +250,7 @@ int32_t KbI2cBase::runOnce()
     }
     case 0x84: { // Adafruit TCA8418
         TCAKeyboard.trigger();
-        InputEvent e;
+        InputEvent e = {};
         while (TCAKeyboard.hasEvent()) {
             char nextEvent = TCAKeyboard.dequeueEvent();
             e.inputEvent = INPUT_BROKER_ANYKEY;
@@ -350,7 +350,7 @@ int32_t KbI2cBase::runOnce()
         }
         if (PrintDataBuf != 0) {
             LOG_DEBUG("RAK14004 key 0x%x pressed", PrintDataBuf);
-            InputEvent e;
+            InputEvent e = {};
             e.inputEvent = INPUT_BROKER_MATRIXKEY;
             e.source = this->_originName;
             e.kbchar = PrintDataBuf;
@@ -365,7 +365,7 @@ int32_t KbI2cBase::runOnce()
 
         if (i2cBus->available()) {
             char c = i2cBus->read();
-            InputEvent e;
+            InputEvent e = {};
             e.inputEvent = INPUT_BROKER_NONE;
             e.source = this->_originName;
             switch (c) {
diff --git a/src/input/kbMatrixBase.cpp b/src/input/kbMatrixBase.cpp
index 05f4d8177..18243f3bf 100644
--- a/src/input/kbMatrixBase.cpp
+++ b/src/input/kbMatrixBase.cpp
@@ -72,7 +72,7 @@ int32_t KbMatrixBase::runOnce()
             if (key != 0) {
                 LOG_DEBUG("Key 0x%x pressed", key);
                 // reset shift now that we have a keypress
-                InputEvent e;
+                InputEvent e = {};
                 e.inputEvent = INPUT_BROKER_NONE;
                 e.source = this->_originName;
                 switch (key) {

From c71c1f2d15e65aa883b8ac954bafb89d6c5cd74f Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
 <41898282+github-actions[bot]@users.noreply.github.com>
Date: Thu, 18 Sep 2025 18:17:14 -0500
Subject: [PATCH 248/691] Automated version bumps (#8028)

Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
---
 bin/org.meshtastic.meshtasticd.metainfo.xml |   3 +
 debian/changelog                            | 150 ++++++--------------
 version.properties                          |   2 +-
 3 files changed, 49 insertions(+), 106 deletions(-)

diff --git a/bin/org.meshtastic.meshtasticd.metainfo.xml b/bin/org.meshtastic.meshtasticd.metainfo.xml
index 108ca4910..090c141fa 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.10
+    
     
       https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.9
     
diff --git a/debian/changelog b/debian/changelog
index 286349dd2..76e390001 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,109 +1,49 @@
-meshtasticd (2.7.9.0) unstable; urgency=medium
-
-  * Version 2.7.9
-
- -- GitHub Actions   Wed, 03 Sep 2025 23:39:17 +0000
-
-meshtasticd (2.7.8.0) unstable; urgency=medium
-
-  * Version 2.7.8
-
- -- GitHub Actions   Sat, 30 Aug 2025 00:26:04 +0000
-
-meshtasticd (2.7.7.0) unstable; urgency=medium
-
-  * Version 2.7.7
-
- -- GitHub Actions   Thu, 28 Aug 2025 10:33:25 +0000
-
-meshtasticd (2.7.6.0) unstable; urgency=medium
-
-  * Version 2.7.6
-
- -- GitHub Actions   Tue, 12 Aug 2025 23:48:48 +0000
-
-meshtasticd (2.7.5.0) unstable; urgency=medium
-
-  * Version 2.7.5
-
- -- GitHub Actions   Sat, 09 Aug 2025 12:46:53 +0000
-
-meshtasticd (2.7.4.0) unstable; urgency=medium
-
-  * Version 2.7.4
-
- -- GitHub Actions   Sat, 19 Jul 2025 11:36:55 +0000
-
-meshtasticd (2.7.3.0) unstable; urgency=medium
-
-  * Version 2.7.3
-
- -- GitHub Actions   Thu, 10 Jul 2025 16:29:27 +0000
-
-meshtasticd (2.7.2.0) unstable; urgency=medium
-
-  * Version 2.7.2
-
- -- GitHub Actions   Fri, 04 Jul 2025 11:58:01 +0000
-
-meshtasticd (2.7.1.0) unstable; urgency=medium
-
-  * Version 2.7.1
-
- -- GitHub Actions   Fri, 27 Jun 2025 20:12:21 +0000
-
-meshtasticd (2.6.13) unstable; urgency=medium
-
-  * Version 2.6.13
-
- -- GitHub Actions   Mon, 16 Jun 2025 02:10:49 +0000
-
-meshtasticd (2.6.11) unstable; urgency=medium
-
-  * Version 2.6.11
-
- -- GitHub Actions   Mon, 02 Jun 2025 20:00:55 +0000
-
-meshtasticd (2.6.10) unstable; urgency=medium
-
-  * Version 2.6.10
-
- -- GitHub Actions   Sun, 25 May 2025 20:46:49 +0000
-
-meshtasticd (2.6.9) unstable; urgency=medium
-
-  * Version 2.6.9
-  * Run as non-root user, 'meshtasticd'
-
- -- GitHub Actions   Thu, 15 May 2025 11:13:30 +0000
-
-meshtasticd (2.6.8) unstable; urgency=medium
-
-  * Version 2.6.8
-
- -- GitHub Actions   Tue, 06 May 2025 01:32:49 +0000
-
-meshtasticd (2.5.22) unstable; urgency=medium
-
-  * Version 2.5.22
-
- -- GitHub Actions   Wed, 05 Feb 2025 01:10:33 +0000
-
-meshtasticd (2.5.21) unstable; urgency=medium
-
-  * Version 2.5.21
-
- -- GitHub Actions   Sat, 25 Jan 2025 01:39:16 +0000
-
-meshtasticd (2.5.20) unstable; urgency=medium
-
-  * Version 2.5.20
-
- -- GitHub Actions   Mon, 13 Jan 2025 19:24:14 +0000
-
-meshtasticd (2.5.19) unstable; urgency=medium
+meshtasticd (2.7.10.0) UNRELEASED; urgency=medium
 
   * Initial packaging
   * Version 2.5.19
 
- -- Austin Lane   Thu, 02 Jan 2025 12:00:00 +0000
+  [  ]
+  * GitHub Actions Automatic version bump
+
+  [  ]
+  * GitHub Actions Automatic version bump
+
+  [  ]
+  * GitHub Actions Automatic version bump
+
+  [  ]
+  * GitHub Actions Automatic version bump
+
+  [  ]
+  * GitHub Actions Automatic version bump
+
+  [  ]
+  * GitHub Actions Automatic version bump
+
+  [  ]
+  * GitHub Actions Automatic version bump
+
+  [ Ubuntu ]
+  * GitHub Actions Automatic version bump
+
+  [  ]
+  * GitHub Actions Automatic version bump
+
+  [  ]
+  * GitHub Actions Automatic version bump
+
+  [  ]
+  * GitHub Actions Automatic version bump
+  * GitHub Actions Automatic version bump
+
+  [  ]
+  * GitHub Actions Automatic version bump
+
+  [  ]
+  * GitHub Actions Automatic version bump
+
+  [  ]
+  * GitHub Actions Automatic version bump
+
+ --    Thu, 18 Sep 2025 22:11:37 +0000
diff --git a/version.properties b/version.properties
index cbf8265d9..cc0449a72 100644
--- a/version.properties
+++ b/version.properties
@@ -1,4 +1,4 @@
 [VERSION]  
 major = 2
 minor = 7
-build = 9
+build = 10

From e3772858b319199bd8f0e0a82a8c6f27342e7024 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
 <41898282+github-actions[bot]@users.noreply.github.com>
Date: Thu, 18 Sep 2025 18:17:14 -0500
Subject: [PATCH 249/691] Automated version bumps (#8028)

Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
---
 bin/org.meshtastic.meshtasticd.metainfo.xml |  3 ++
 debian/changelog                            | 50 ++-------------------
 version.properties                          |  2 +-
 3 files changed, 7 insertions(+), 48 deletions(-)

diff --git a/bin/org.meshtastic.meshtasticd.metainfo.xml b/bin/org.meshtastic.meshtasticd.metainfo.xml
index 108ca4910..090c141fa 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.10
+    
     
       https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.9
     
diff --git a/debian/changelog b/debian/changelog
index 29841d0db..d4ba6b7a0 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,50 +1,6 @@
-meshtasticd (2.7.9.0) UNRELEASED; urgency=medium
+meshtasticd (2.7.10.0) UNRELEASED; urgency=medium
 
-  [ Austin Lane ]
   * Initial packaging
-  * GitHub Actions Automatic version bump
-  * GitHub Actions Automatic version bump
-  * GitHub Actions Automatic version bump
-  * GitHub Actions Automatic version bump
+  * Version 2.5.19
 
-  [  ]
-  * GitHub Actions Automatic version bump
-
-  [  ]
-  * GitHub Actions Automatic version bump
-
-  [  ]
-  * GitHub Actions Automatic version bump
-
-  [  ]
-  * GitHub Actions Automatic version bump
-
-  [  ]
-  * GitHub Actions Automatic version bump
-
-  [  ]
-  * GitHub Actions Automatic version bump
-
-  [  ]
-  * GitHub Actions Automatic version bump
-
-  [ Ubuntu ]
-  * GitHub Actions Automatic version bump
-
-  [  ]
-  * GitHub Actions Automatic version bump
-
-  [  ]
-  * GitHub Actions Automatic version bump
-
-  [  ]
-  * GitHub Actions Automatic version bump
-  * GitHub Actions Automatic version bump
-
-  [  ]
-  * GitHub Actions Automatic version bump
-
-  [  ]
-  * GitHub Actions Automatic version bump
-
- --    Wed, 03 Sep 2025 23:39:17 +0000
+ -- Austin Lane   Thu, 02 Jan 2025 12:00:00 +0000
diff --git a/version.properties b/version.properties
index cbf8265d9..cc0449a72 100644
--- a/version.properties
+++ b/version.properties
@@ -1,4 +1,4 @@
 [VERSION]  
 major = 2
 minor = 7
-build = 9
+build = 10

From 89cccdbbe27f0e4259e1ef3ad5f05db8efcf8cdc Mon Sep 17 00:00:00 2001
From: Markus <974709+Links2004@users.noreply.github.com>
Date: Fri, 19 Sep 2025 02:25:58 +0200
Subject: [PATCH 250/691] Allow Left / Right Events for selection and improve
 encoder responsives (#8016)

* Allow Left / Right Events for selection and improve encoder responsives

* add define for ROTARY_DELAY
---
 src/configuration.h                        |  7 +++++++
 src/graphics/draw/NotificationRenderer.cpp | 12 ++++++++----
 src/input/RotaryEncoderInterruptBase.cpp   |  2 +-
 src/modules/CannedMessageModule.cpp        |  4 ++--
 4 files changed, 18 insertions(+), 7 deletions(-)

diff --git a/src/configuration.h b/src/configuration.h
index d5adba028..515380cfb 100644
--- a/src/configuration.h
+++ b/src/configuration.h
@@ -262,6 +262,13 @@ along with this program.  If not, see .
 #define VEXT_ON_VALUE LOW
 #endif
 
+// -----------------------------------------------------------------------------
+// Rotary encoder
+// -----------------------------------------------------------------------------
+#ifndef ROTARY_DELAY
+#define ROTARY_DELAY 5
+#endif
+
 // -----------------------------------------------------------------------------
 // GPS
 // -----------------------------------------------------------------------------
diff --git a/src/graphics/draw/NotificationRenderer.cpp b/src/graphics/draw/NotificationRenderer.cpp
index 8c8ea800b..26bfe8447 100644
--- a/src/graphics/draw/NotificationRenderer.cpp
+++ b/src/graphics/draw/NotificationRenderer.cpp
@@ -250,9 +250,11 @@ void NotificationRenderer::drawNodePicker(OLEDDisplay *display, OLEDDisplayUiSta
     }
 
     // Handle input
-    if (inEvent.inputEvent == INPUT_BROKER_UP || inEvent.inputEvent == INPUT_BROKER_ALT_PRESS) {
+    if (inEvent.inputEvent == INPUT_BROKER_UP || inEvent.inputEvent == INPUT_BROKER_LEFT ||
+        inEvent.inputEvent == INPUT_BROKER_ALT_PRESS) {
         curSelected--;
-    } else if (inEvent.inputEvent == INPUT_BROKER_DOWN || inEvent.inputEvent == INPUT_BROKER_USER_PRESS) {
+    } else if (inEvent.inputEvent == INPUT_BROKER_DOWN || inEvent.inputEvent == INPUT_BROKER_RIGHT ||
+               inEvent.inputEvent == INPUT_BROKER_USER_PRESS) {
         curSelected++;
     } else if (inEvent.inputEvent == INPUT_BROKER_SELECT) {
         alertBannerCallback(selectedNodenum);
@@ -365,9 +367,11 @@ void NotificationRenderer::drawAlertBannerOverlay(OLEDDisplay *display, OLEDDisp
 
     // Handle input
     if (alertBannerOptions > 0) {
-        if (inEvent.inputEvent == INPUT_BROKER_UP || inEvent.inputEvent == INPUT_BROKER_ALT_PRESS) {
+        if (inEvent.inputEvent == INPUT_BROKER_UP || inEvent.inputEvent == INPUT_BROKER_LEFT ||
+            inEvent.inputEvent == INPUT_BROKER_ALT_PRESS) {
             curSelected--;
-        } else if (inEvent.inputEvent == INPUT_BROKER_DOWN || inEvent.inputEvent == INPUT_BROKER_USER_PRESS) {
+        } else if (inEvent.inputEvent == INPUT_BROKER_DOWN || inEvent.inputEvent == INPUT_BROKER_RIGHT ||
+                   inEvent.inputEvent == INPUT_BROKER_USER_PRESS) {
             curSelected++;
         } else if (inEvent.inputEvent == INPUT_BROKER_SELECT) {
             if (optionsEnumPtr != nullptr) {
diff --git a/src/input/RotaryEncoderInterruptBase.cpp b/src/input/RotaryEncoderInterruptBase.cpp
index 016727cd7..980057ce4 100644
--- a/src/input/RotaryEncoderInterruptBase.cpp
+++ b/src/input/RotaryEncoderInterruptBase.cpp
@@ -151,7 +151,7 @@ RotaryEncoderInterruptBaseStateType RotaryEncoderInterruptBase::intHandler(bool
         // Logic to prevent bouncing.
         newState = ROTARY_EVENT_CLEARED;
     }
-    setIntervalFromNow(50); // TODO: this modifies a non-volatile variable!
+    setIntervalFromNow(ROTARY_DELAY); // TODO: this modifies a non-volatile variable!
 
     return newState;
 }
diff --git a/src/modules/CannedMessageModule.cpp b/src/modules/CannedMessageModule.cpp
index ffbc6640e..e9f52bb7d 100644
--- a/src/modules/CannedMessageModule.cpp
+++ b/src/modules/CannedMessageModule.cpp
@@ -404,14 +404,14 @@ bool CannedMessageModule::isUpEvent(const InputEvent *event)
     return event->inputEvent == INPUT_BROKER_UP ||
            ((runState == CANNED_MESSAGE_RUN_STATE_ACTIVE || runState == CANNED_MESSAGE_RUN_STATE_EMOTE_PICKER ||
              runState == CANNED_MESSAGE_RUN_STATE_DESTINATION_SELECTION) &&
-            event->inputEvent == INPUT_BROKER_ALT_PRESS);
+            (event->inputEvent == INPUT_BROKER_LEFT || event->inputEvent == INPUT_BROKER_ALT_PRESS));
 }
 bool CannedMessageModule::isDownEvent(const InputEvent *event)
 {
     return event->inputEvent == INPUT_BROKER_DOWN ||
            ((runState == CANNED_MESSAGE_RUN_STATE_ACTIVE || runState == CANNED_MESSAGE_RUN_STATE_EMOTE_PICKER ||
              runState == CANNED_MESSAGE_RUN_STATE_DESTINATION_SELECTION) &&
-            event->inputEvent == INPUT_BROKER_USER_PRESS);
+            (event->inputEvent == INPUT_BROKER_RIGHT || event->inputEvent == INPUT_BROKER_USER_PRESS));
 }
 bool CannedMessageModule::isSelectEvent(const InputEvent *event)
 {

From 017d07e1082dbfcf77305169db5e6e306dd9dd42 Mon Sep 17 00:00:00 2001
From: Ben Meadors 
Date: Thu, 18 Sep 2025 19:28:10 -0500
Subject: [PATCH 251/691] Extra chirpy

---
 src/graphics/draw/DebugRenderer.cpp | 26 --------------------------
 1 file changed, 26 deletions(-)

diff --git a/src/graphics/draw/DebugRenderer.cpp b/src/graphics/draw/DebugRenderer.cpp
index aa0e7cf1b..5fe10a1b8 100644
--- a/src/graphics/draw/DebugRenderer.cpp
+++ b/src/graphics/draw/DebugRenderer.cpp
@@ -693,32 +693,6 @@ void drawChirpy(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int1
     display->drawString(textX, getTextPositions(display)[line++], "World!");
 }
 
-// ****************************
-// * 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

From ec29100a88915cb33bfa1d9b430bf67e3b1cc786 Mon Sep 17 00:00:00 2001
From: Markus <974709+Links2004@users.noreply.github.com>
Date: Fri, 19 Sep 2025 02:25:58 +0200
Subject: [PATCH 252/691] Allow Left / Right Events for selection and improve
 encoder responsives (#8016)

* Allow Left / Right Events for selection and improve encoder responsives

* add define for ROTARY_DELAY
---
 src/configuration.h                        |  7 +++++++
 src/graphics/draw/NotificationRenderer.cpp | 12 ++++++++----
 src/input/RotaryEncoderInterruptBase.cpp   |  2 +-
 src/modules/CannedMessageModule.cpp        |  4 ++--
 4 files changed, 18 insertions(+), 7 deletions(-)

diff --git a/src/configuration.h b/src/configuration.h
index 81632c89e..1b386ec17 100644
--- a/src/configuration.h
+++ b/src/configuration.h
@@ -262,6 +262,13 @@ along with this program.  If not, see .
 #define VEXT_ON_VALUE LOW
 #endif
 
+// -----------------------------------------------------------------------------
+// Rotary encoder
+// -----------------------------------------------------------------------------
+#ifndef ROTARY_DELAY
+#define ROTARY_DELAY 5
+#endif
+
 // -----------------------------------------------------------------------------
 // GPS
 // -----------------------------------------------------------------------------
diff --git a/src/graphics/draw/NotificationRenderer.cpp b/src/graphics/draw/NotificationRenderer.cpp
index c2bd1ba66..710609f60 100644
--- a/src/graphics/draw/NotificationRenderer.cpp
+++ b/src/graphics/draw/NotificationRenderer.cpp
@@ -216,9 +216,11 @@ void NotificationRenderer::drawNodePicker(OLEDDisplay *display, OLEDDisplayUiSta
     }
 
     // Handle input
-    if (inEvent.inputEvent == INPUT_BROKER_UP || inEvent.inputEvent == INPUT_BROKER_ALT_PRESS) {
+    if (inEvent.inputEvent == INPUT_BROKER_UP || inEvent.inputEvent == INPUT_BROKER_LEFT ||
+        inEvent.inputEvent == INPUT_BROKER_ALT_PRESS) {
         curSelected--;
-    } else if (inEvent.inputEvent == INPUT_BROKER_DOWN || inEvent.inputEvent == INPUT_BROKER_USER_PRESS) {
+    } else if (inEvent.inputEvent == INPUT_BROKER_DOWN || inEvent.inputEvent == INPUT_BROKER_RIGHT ||
+               inEvent.inputEvent == INPUT_BROKER_USER_PRESS) {
         curSelected++;
     } else if (inEvent.inputEvent == INPUT_BROKER_SELECT) {
         alertBannerCallback(selectedNodenum);
@@ -333,9 +335,11 @@ void NotificationRenderer::drawAlertBannerOverlay(OLEDDisplay *display, OLEDDisp
 
     // Handle input
     if (alertBannerOptions > 0) {
-        if (inEvent.inputEvent == INPUT_BROKER_UP || inEvent.inputEvent == INPUT_BROKER_ALT_PRESS) {
+        if (inEvent.inputEvent == INPUT_BROKER_UP || inEvent.inputEvent == INPUT_BROKER_LEFT ||
+            inEvent.inputEvent == INPUT_BROKER_ALT_PRESS) {
             curSelected--;
-        } else if (inEvent.inputEvent == INPUT_BROKER_DOWN || inEvent.inputEvent == INPUT_BROKER_USER_PRESS) {
+        } else if (inEvent.inputEvent == INPUT_BROKER_DOWN || inEvent.inputEvent == INPUT_BROKER_RIGHT ||
+                   inEvent.inputEvent == INPUT_BROKER_USER_PRESS) {
             curSelected++;
         } else if (inEvent.inputEvent == INPUT_BROKER_SELECT) {
             if (optionsEnumPtr != nullptr) {
diff --git a/src/input/RotaryEncoderInterruptBase.cpp b/src/input/RotaryEncoderInterruptBase.cpp
index 3511a990a..598353f98 100644
--- a/src/input/RotaryEncoderInterruptBase.cpp
+++ b/src/input/RotaryEncoderInterruptBase.cpp
@@ -120,7 +120,7 @@ RotaryEncoderInterruptBaseStateType RotaryEncoderInterruptBase::intHandler(bool
         // Logic to prevent bouncing.
         newState = ROTARY_EVENT_CLEARED;
     }
-    setIntervalFromNow(50); // TODO: this modifies a non-volatile variable!
+    setIntervalFromNow(ROTARY_DELAY); // TODO: this modifies a non-volatile variable!
 
     return newState;
 }
diff --git a/src/modules/CannedMessageModule.cpp b/src/modules/CannedMessageModule.cpp
index 2fc0bf4a6..1af1dd7d3 100644
--- a/src/modules/CannedMessageModule.cpp
+++ b/src/modules/CannedMessageModule.cpp
@@ -394,14 +394,14 @@ bool CannedMessageModule::isUpEvent(const InputEvent *event)
     return event->inputEvent == INPUT_BROKER_UP ||
            ((runState == CANNED_MESSAGE_RUN_STATE_ACTIVE || runState == CANNED_MESSAGE_RUN_STATE_EMOTE_PICKER ||
              runState == CANNED_MESSAGE_RUN_STATE_DESTINATION_SELECTION) &&
-            event->inputEvent == INPUT_BROKER_ALT_PRESS);
+            (event->inputEvent == INPUT_BROKER_LEFT || event->inputEvent == INPUT_BROKER_ALT_PRESS));
 }
 bool CannedMessageModule::isDownEvent(const InputEvent *event)
 {
     return event->inputEvent == INPUT_BROKER_DOWN ||
            ((runState == CANNED_MESSAGE_RUN_STATE_ACTIVE || runState == CANNED_MESSAGE_RUN_STATE_EMOTE_PICKER ||
              runState == CANNED_MESSAGE_RUN_STATE_DESTINATION_SELECTION) &&
-            event->inputEvent == INPUT_BROKER_USER_PRESS);
+            (event->inputEvent == INPUT_BROKER_RIGHT || event->inputEvent == INPUT_BROKER_USER_PRESS));
 }
 bool CannedMessageModule::isSelectEvent(const InputEvent *event)
 {

From 902405a985d0d58f317a68f669769149a63df3be Mon Sep 17 00:00:00 2001
From: Ben Meadors 
Date: Thu, 18 Sep 2025 19:47:16 -0500
Subject: [PATCH 253/691] Extra endif

---
 src/graphics/draw/UIRenderer.cpp | 1 -
 1 file changed, 1 deletion(-)

diff --git a/src/graphics/draw/UIRenderer.cpp b/src/graphics/draw/UIRenderer.cpp
index 4326d3fd4..53ed6e737 100644
--- a/src/graphics/draw/UIRenderer.cpp
+++ b/src/graphics/draw/UIRenderer.cpp
@@ -1154,7 +1154,6 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU
         }
     }
 #endif
-#endif
 }
 
 #ifdef USERPREFS_OEM_TEXT

From 9345bdcb224c4e42dd75470a1631a6e9ca03dddc Mon Sep 17 00:00:00 2001
From: WillyJL 
Date: Sat, 13 Sep 2025 13:50:02 +0200
Subject: [PATCH 254/691] T-Lora Pager: Support LR1121 and SX1280 models
 (#7956)

* T-Lora Pager: Support LR1121 and SX1280 models

* Remove ifdefs
---
 variants/esp32s3/tlora-pager/rfswitch.h | 12 ++++--------
 1 file changed, 4 insertions(+), 8 deletions(-)

diff --git a/variants/esp32s3/tlora-pager/rfswitch.h b/variants/esp32s3/tlora-pager/rfswitch.h
index 337346ec5..0fba5a305 100644
--- a/variants/esp32s3/tlora-pager/rfswitch.h
+++ b/variants/esp32s3/tlora-pager/rfswitch.h
@@ -4,12 +4,8 @@ static const uint32_t rfswitch_dio_pins[] = {RADIOLIB_LR11X0_DIO5, RADIOLIB_LR11
 
 static const Module::RfSwitchMode_t rfswitch_table[] = {
     // mode                  DIO5  DIO6
-    {LR11x0::MODE_STBY, {LOW, LOW}},
-    {LR11x0::MODE_RX, {LOW, HIGH}},
-    {LR11x0::MODE_TX, {HIGH, LOW}},
-    {LR11x0::MODE_TX_HP, {HIGH, LOW}},
-    {LR11x0::MODE_TX_HF, {LOW, LOW}},
-    {LR11x0::MODE_GNSS, {LOW, LOW}},
-    {LR11x0::MODE_WIFI, {LOW, LOW}},
-    END_OF_MODE_TABLE,
+    {LR11x0::MODE_STBY, {LOW, LOW}},  {LR11x0::MODE_RX, {LOW, HIGH}},
+    {LR11x0::MODE_TX, {HIGH, LOW}},   {LR11x0::MODE_TX_HP, {HIGH, LOW}},
+    {LR11x0::MODE_TX_HF, {LOW, LOW}}, {LR11x0::MODE_GNSS, {LOW, LOW}},
+    {LR11x0::MODE_WIFI, {LOW, LOW}},  END_OF_MODE_TABLE,
 };
\ No newline at end of file

From c73fe85ec8b02ea057f8b104f88afa56f501c01f Mon Sep 17 00:00:00 2001
From: Tom <116762865+NomDeTom@users.noreply.github.com>
Date: Tue, 16 Sep 2025 12:41:22 +0100
Subject: [PATCH 255/691] (resubmission) Manual GitHub actions to allow
 building one target or arch (#7997)

* Reset the modified files

* Fix some changes

* Fix some changes

* Trunk. That is all.

---------

Co-authored-by: Tom <116762865+Nestpebble@users.noreply.github.com>
---
 .github/workflows/build_one_arch.yml   | 500 +++++++++++++++++++++++++
 .github/workflows/build_one_target.yml | 395 +++++++++++++++++++
 .github/workflows/daily_packaging.yml  |   4 +
 .github/workflows/main_matrix.yml      |  17 +-
 4 files changed, 912 insertions(+), 4 deletions(-)
 create mode 100644 .github/workflows/build_one_arch.yml
 create mode 100644 .github/workflows/build_one_target.yml

diff --git a/.github/workflows/build_one_arch.yml b/.github/workflows/build_one_arch.yml
new file mode 100644
index 000000000..5901a335c
--- /dev/null
+++ b/.github/workflows/build_one_arch.yml
@@ -0,0 +1,500 @@
+name: Build One Arch
+
+on:
+  workflow_dispatch:
+    inputs:
+      arch:
+        type: choice
+        options:
+          - esp32
+          - esp32s3
+          - esp32c3
+          - esp32c6
+          - nrf52840
+          - rp2040
+          - rp2350
+          - stm32
+          - native
+
+jobs:
+  setup:
+    strategy:
+      fail-fast: false
+    runs-on: ubuntu-24.04
+    steps:
+      - uses: actions/checkout@v4
+      - uses: actions/setup-python@v5
+        with:
+          python-version: 3.x
+          cache: pip
+      - run: pip install -U platformio
+      - name: Generate matrix
+        id: jsonStep
+        run: |
+          if [[ "$GITHUB_HEAD_REF" == "" ]]; then
+            TARGETS=$(./bin/generate_ci_matrix.py ${{inputs.arch}} extra)
+          else  
+            TARGETS=$(./bin/generate_ci_matrix.py ${{inputs.arch}} pr)
+          fi
+          echo "Name: $GITHUB_REF_NAME Base: $GITHUB_BASE_REF Ref: $GITHUB_REF Targets: $TARGETS"
+          echo "${{inputs.arch}}=$(jq -cn --argjson environments "$TARGETS" '{board: $environments}')" >> $GITHUB_OUTPUT
+    outputs:
+      esp32: ${{ steps.jsonStep.outputs.esp32 }}
+      esp32s3: ${{ steps.jsonStep.outputs.esp32s3 }}
+      esp32c3: ${{ steps.jsonStep.outputs.esp32c3 }}
+      esp32c6: ${{ steps.jsonStep.outputs.esp32c6 }}
+      nrf52840: ${{ steps.jsonStep.outputs.nrf52840 }}
+      rp2040: ${{ steps.jsonStep.outputs.rp2040 }}
+      rp2350: ${{ steps.jsonStep.outputs.rp2350 }}
+      stm32: ${{ steps.jsonStep.outputs.stm32 }}
+      check: ${{ steps.jsonStep.outputs.check }}
+
+  version:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v4
+      - name: Get release version string
+        run: |
+          echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
+          echo "deb=$(./bin/buildinfo.py deb)" >> $GITHUB_OUTPUT
+        id: version
+        env:
+          BUILD_LOCATION: local
+    outputs:
+      long: ${{ steps.version.outputs.long }}
+      deb: ${{ steps.version.outputs.deb }}
+
+  build-esp32:
+    if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'esp32'}}
+    needs: [setup, version]
+    strategy:
+      fail-fast: false
+      matrix: ${{ fromJson(needs.setup.outputs.esp32) }}
+    uses: ./.github/workflows/build_firmware.yml
+    with:
+      version: ${{ needs.version.outputs.long }}
+      pio_env: ${{ matrix.board }}
+      platform: esp32
+
+  build-esp32s3:
+    if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'esp32s3'}}
+    needs: [setup, version]
+    strategy:
+      fail-fast: false
+      matrix: ${{ fromJson(needs.setup.outputs.esp32s3) }}
+    uses: ./.github/workflows/build_firmware.yml
+    with:
+      version: ${{ needs.version.outputs.long }}
+      pio_env: ${{ matrix.board }}
+      platform: esp32s3
+
+  build-esp32c3:
+    if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'esp32c3'}}
+    needs: [setup, version]
+    strategy:
+      fail-fast: false
+      matrix: ${{ fromJson(needs.setup.outputs.esp32c3) }}
+    uses: ./.github/workflows/build_firmware.yml
+    with:
+      version: ${{ needs.version.outputs.long }}
+      pio_env: ${{ matrix.board }}
+      platform: esp32c3
+
+  build-esp32c6:
+    if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'esp32c6'}}
+    needs: [setup, version]
+    strategy:
+      fail-fast: false
+      matrix: ${{ fromJson(needs.setup.outputs.esp32c6) }}
+    uses: ./.github/workflows/build_firmware.yml
+    with:
+      version: ${{ needs.version.outputs.long }}
+      pio_env: ${{ matrix.board }}
+      platform: esp32c6
+
+  build-nrf52840:
+    if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'nrf52840'}}
+    needs: [setup, version]
+    strategy:
+      fail-fast: false
+      matrix: ${{ fromJson(needs.setup.outputs.nrf52840) }}
+    uses: ./.github/workflows/build_firmware.yml
+    with:
+      version: ${{ needs.version.outputs.long }}
+      pio_env: ${{ matrix.board }}
+      platform: nrf52840
+
+  build-rp2040:
+    if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'rp2040'}}
+    needs: [setup, version]
+    strategy:
+      fail-fast: false
+      matrix: ${{ fromJson(needs.setup.outputs.rp2040) }}
+    uses: ./.github/workflows/build_firmware.yml
+    with:
+      version: ${{ needs.version.outputs.long }}
+      pio_env: ${{ matrix.board }}
+      platform: rp2040
+
+  build-rp2350:
+    if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'rp2350'}}
+    needs: [setup, version]
+    strategy:
+      fail-fast: false
+      matrix: ${{ fromJson(needs.setup.outputs.rp2350) }}
+    uses: ./.github/workflows/build_firmware.yml
+    with:
+      version: ${{ needs.version.outputs.long }}
+      pio_env: ${{ matrix.board }}
+      platform: rp2350
+
+  build-stm32:
+    if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'stm32' }}
+    needs: [setup, version]
+    strategy:
+      fail-fast: false
+      matrix: ${{ fromJson(needs.setup.outputs.stm32) }}
+    uses: ./.github/workflows/build_firmware.yml
+    with:
+      version: ${{ needs.version.outputs.long }}
+      pio_env: ${{ matrix.board }}
+      platform: stm32
+
+  build-debian-src:
+    if: ${{ github.repository == 'meshtastic/firmware' && github.event_name != 'workflow_dispatch' || inputs.arch == 'native' }}
+    uses: ./.github/workflows/build_debian_src.yml
+    with:
+      series: UNRELEASED
+      build_location: local
+    secrets: inherit
+
+  package-pio-deps-native-tft:
+    if: ${{ inputs.arch == 'native' }}
+    uses: ./.github/workflows/package_pio_deps.yml
+    with:
+      pio_env: native-tft
+    secrets: inherit
+
+  test-native:
+    if: ${{ !contains(github.ref_name, 'event/') && github.event_name != 'workflow_dispatch' ||  !contains(github.ref_name, 'event/') && inputs.arch == 'native' }}
+    uses: ./.github/workflows/test_native.yml
+
+  docker-deb-amd64:
+    if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'native' }}
+    uses: ./.github/workflows/docker_build.yml
+    with:
+      distro: debian
+      platform: linux/amd64
+      runs-on: ubuntu-24.04
+      push: false
+
+  docker-deb-amd64-tft:
+    if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'native' }}
+    uses: ./.github/workflows/docker_build.yml
+    with:
+      distro: debian
+      platform: linux/amd64
+      runs-on: ubuntu-24.04
+      push: false
+      pio_env: native-tft
+
+  docker-alp-amd64:
+    if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'native' }}
+    uses: ./.github/workflows/docker_build.yml
+    with:
+      distro: alpine
+      platform: linux/amd64
+      runs-on: ubuntu-24.04
+      push: false
+
+  docker-alp-amd64-tft:
+    if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'native' }}
+    uses: ./.github/workflows/docker_build.yml
+    with:
+      distro: alpine
+      platform: linux/amd64
+      runs-on: ubuntu-24.04
+      push: false
+      pio_env: native-tft
+
+  docker-deb-arm64:
+    if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'native' }}
+    uses: ./.github/workflows/docker_build.yml
+    with:
+      distro: debian
+      platform: linux/arm64
+      runs-on: ubuntu-24.04-arm
+      push: false
+
+  docker-deb-armv7:
+    if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'native' }}
+    uses: ./.github/workflows/docker_build.yml
+    with:
+      distro: debian
+      platform: linux/arm/v7
+      runs-on: ubuntu-24.04-arm
+      push: false
+
+  gather-artifacts:
+    permissions:
+      contents: write
+      pull-requests: write
+    strategy:
+      fail-fast: false
+      matrix:
+        arch:
+          - esp32
+          - esp32s3
+          - esp32c3
+          - esp32c6
+          - nrf52840
+          - rp2040
+          - rp2350
+          - stm32
+    runs-on: ubuntu-latest
+    needs:
+      [
+        version,
+        build-esp32,
+        build-esp32s3,
+        build-esp32c3,
+        build-esp32c6,
+        build-nrf52840,
+        build-rp2040,
+        build-rp2350,
+        build-stm32,
+      ]
+    steps:
+      - name: Checkout code
+        uses: actions/checkout@v4
+        with:
+          ref: ${{github.event.pull_request.head.ref}}
+          repository: ${{github.event.pull_request.head.repo.full_name}}
+
+      - uses: actions/download-artifact@v4
+        with:
+          path: ./
+          pattern: firmware-${{inputs.arch}}-*
+          merge-multiple: true
+
+      - name: Display structure of downloaded files
+        run: ls -R
+
+      - name: Move files up
+        run: mv -b -t ./ ./bin/device-*.sh ./bin/device-*.bat
+
+      - name: Repackage in single firmware zip
+        uses: actions/upload-artifact@v4
+        with:
+          name: firmware-${{inputs.arch}}-${{ needs.version.outputs.long }}
+          overwrite: true
+          path: |
+            ./firmware-*.bin
+            ./firmware-*.uf2
+            ./firmware-*.hex
+            ./firmware-*-ota.zip
+            ./device-*.sh
+            ./device-*.bat
+            ./littlefs-*.bin
+            ./bleota*bin
+            ./Meshtastic_nRF52_factory_erase*.uf2
+          retention-days: 30
+
+      - uses: actions/download-artifact@v4
+        with:
+          name: firmware-${{inputs.arch}}-${{ needs.version.outputs.long }}
+          merge-multiple: true
+          path: ./output
+
+      # For diagnostics
+      - name: Show artifacts
+        run: ls -lR
+
+      - name: Device scripts permissions
+        run: |
+          chmod +x ./output/device-install.sh
+          chmod +x ./output/device-update.sh
+
+      - name: Zip firmware
+        run: zip -j -9 -r ./firmware-${{inputs.arch}}-${{ needs.version.outputs.long }}.zip ./output
+
+      - name: Repackage in single elfs zip
+        uses: actions/upload-artifact@v4
+        with:
+          name: debug-elfs-${{inputs.arch}}-${{ needs.version.outputs.long }}.zip
+          overwrite: true
+          path: ./*.elf
+          retention-days: 30
+
+      - uses: scruplelesswizard/comment-artifact@main
+        if: ${{ github.event_name == 'pull_request' }}
+        with:
+          name: firmware-${{inputs.arch}}-${{ needs.version.outputs.long }}
+          description: "Download firmware-${{inputs.arch}}-${{ needs.version.outputs.long }}.zip. This artifact will be available for 90 days from creation"
+          github-token: ${{ secrets.GITHUB_TOKEN }}
+
+  release-artifacts:
+    runs-on: ubuntu-latest
+    if: ${{ github.event_name == 'workflow_dispatch' }}
+    outputs:
+      upload_url: ${{ steps.create_release.outputs.upload_url }}
+    needs:
+      - version
+      - gather-artifacts
+      - build-debian-src
+      - package-pio-deps-native-tft
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v4
+
+      - name: Setup Python
+        uses: actions/setup-python@v5
+        with:
+          python-version: 3.x
+
+      - name: Create release
+        uses: softprops/action-gh-release@v2
+        id: create_release
+        with:
+          draft: true
+          prerelease: true
+          name: Meshtastic Firmware ${{ needs.version.outputs.long }} Alpha
+          tag_name: v${{ needs.version.outputs.long }}
+          body: |
+            Autogenerated by github action, developer should edit as required before publishing...
+
+      - name: Download source deb
+        uses: actions/download-artifact@v4
+        with:
+          pattern: firmware-debian-${{ needs.version.outputs.deb }}~UNRELEASED-src
+          merge-multiple: true
+          path: ./output/debian-src
+
+      - name: Download `native-tft` pio deps
+        uses: actions/download-artifact@v4
+        with:
+          pattern: platformio-deps-native-tft-${{ needs.version.outputs.long }}
+          merge-multiple: true
+          path: ./output/pio-deps-native-tft
+
+      - name: Zip Linux sources
+        working-directory: output
+        run: |
+          zip -j -9 -r ./meshtasticd-${{ needs.version.outputs.deb }}-src.zip ./debian-src
+          zip -9 -r ./platformio-deps-native-tft-${{ needs.version.outputs.long }}.zip ./pio-deps-native-tft
+
+      # For diagnostics
+      - name: Display structure of downloaded files
+        run: ls -lR
+
+      - name: Add Linux sources to GtiHub Release
+        # Only run when targeting master branch with workflow_dispatch
+        if: ${{ github.ref_name == 'master' }}
+        run: |
+          gh release upload v${{ needs.version.outputs.long }} ./output/meshtasticd-${{ needs.version.outputs.deb }}-src.zip
+          gh release upload v${{ needs.version.outputs.long }} ./output/platformio-deps-native-tft-${{ needs.version.outputs.long }}.zip
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+  release-firmware:
+    strategy:
+      fail-fast: false
+      matrix:
+        arch:
+          - esp32
+          - esp32s3
+          - esp32c3
+          - esp32c6
+          - nrf52840
+          - rp2040
+          - rp2350
+          - stm32
+    runs-on: ubuntu-latest
+    if: ${{ github.event_name == 'workflow_dispatch' }}
+    needs: [release-artifacts, version]
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v4
+
+      - name: Setup Python
+        uses: actions/setup-python@v5
+        with:
+          python-version: 3.x
+
+      - uses: actions/download-artifact@v4
+        with:
+          pattern: firmware-${{inputs.arch}}-${{ needs.version.outputs.long }}
+          merge-multiple: true
+          path: ./output
+
+      - name: Display structure of downloaded files
+        run: ls -lR
+
+      - name: Device scripts permissions
+        run: |
+          chmod +x ./output/device-install.sh
+          chmod +x ./output/device-update.sh
+
+      - name: Zip firmware
+        run: zip -j -9 -r ./firmware-${{inputs.arch}}-${{ needs.version.outputs.long }}.zip ./output
+
+      - uses: actions/download-artifact@v4
+        with:
+          name: debug-elfs-${{inputs.arch}}-${{ needs.version.outputs.long }}.zip
+          merge-multiple: true
+          path: ./elfs
+
+      - name: Zip debug elfs
+        run: zip -j -9 -r ./debug-elfs-${{inputs.arch}}-${{ needs.version.outputs.long }}.zip ./elfs
+
+      # For diagnostics
+      - name: Display structure of downloaded files
+        run: ls -lR
+
+      - name: Add bins and debug elfs to GitHub Release
+        # Only run when targeting master branch with workflow_dispatch
+        if: ${{ github.ref_name == 'master' }}
+        run: |
+          gh release upload v${{ needs.version.outputs.long }} ./firmware-${{inputs.arch}}-${{ needs.version.outputs.long }}.zip
+          gh release upload v${{ needs.version.outputs.long }} ./debug-elfs-${{inputs.arch}}-${{ needs.version.outputs.long }}.zip
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+  publish-firmware:
+    runs-on: ubuntu-24.04
+    if: ${{ github.event_name == 'workflow_dispatch' }}
+    needs: [release-firmware, version]
+    env:
+      targets: |-
+        esp32,esp32s3,esp32c3,esp32c6,nrf52840,rp2040,rp2350,stm32
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v4
+
+      - name: Setup Python
+        uses: actions/setup-python@v5
+        with:
+          python-version: 3.x
+
+      - uses: actions/download-artifact@v4
+        with:
+          pattern: firmware-{${{ env.targets }}}-${{ needs.version.outputs.long }}
+          merge-multiple: true
+          path: ./publish
+
+      - name: Publish firmware to meshtastic.github.io
+        uses: peaceiris/actions-gh-pages@v4
+        env:
+          # On event/* branches, use the event name as the destination prefix
+          DEST_PREFIX: ${{ contains(github.ref_name, 'event/') && format('{0}/', github.ref_name) || '' }}
+        with:
+          deploy_key: ${{ secrets.DIST_PAGES_DEPLOY_KEY }}
+          external_repository: meshtastic/meshtastic.github.io
+          publish_branch: master
+          publish_dir: ./publish
+          destination_dir: ${{ env.DEST_PREFIX }}firmware-${{ needs.version.outputs.long }}
+          keep_files: true
+          user_name: github-actions[bot]
+          user_email: github-actions[bot]@users.noreply.github.com
+          commit_message: ${{ needs.version.outputs.long }}
+          enable_jekyll: true
diff --git a/.github/workflows/build_one_target.yml b/.github/workflows/build_one_target.yml
new file mode 100644
index 000000000..07478ff93
--- /dev/null
+++ b/.github/workflows/build_one_target.yml
@@ -0,0 +1,395 @@
+name: Build One Target
+
+on:
+  workflow_dispatch:
+    inputs:
+      arch:
+        type: choice
+        options:
+          - esp32
+          - esp32s3
+          - esp32c3
+          - esp32c6
+          - nrf52840
+          - rp2040
+          - rp2350
+          - stm32
+          - native
+      target:
+        type: string
+        required: false
+        description: Choose the target board, e.g. nrf52_promicro_diy_tcxo. If blank, will find available targets.
+
+      # find-target:
+      #   type: boolean
+      #   default: true
+      #   description: 'Find the available targets'
+jobs:
+  find-targets:
+    if: ${{ inputs.target == '' }}
+    strategy:
+      fail-fast: false
+      matrix:
+        arch:
+          - esp32
+          - esp32s3
+          - esp32c3
+          - esp32c6
+          - nrf52840
+          - rp2040
+          - rp2350
+          - stm32
+
+    runs-on: ubuntu-24.04
+    steps:
+      - uses: actions/checkout@v4
+      - uses: actions/setup-python@v5
+        with:
+          python-version: 3.x
+          cache: pip
+      - run: pip install -U platformio
+      - name: Generate matrix
+        id: jsonStep
+        run: |
+          TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}} extra)
+          echo "Name: $GITHUB_REF_NAME" >> $GITHUB_STEP_SUMMARY
+          echo "Base: $GITHUB_BASE_REF" >> $GITHUB_STEP_SUMMARY
+          echo "Arch: ${{matrix.arch}}" >> $GITHUB_STEP_SUMMARY
+          echo "Ref: $GITHUB_REF" >> $GITHUB_STEP_SUMMARY
+          echo "Targets:" >> $GITHUB_STEP_SUMMARY
+          echo $TARGETS | sed 's/[][]//g; s/", "/\n- /g; s/"//g; s/^/- /' >> $GITHUB_STEP_SUMMARY
+
+  version:
+    if: ${{ inputs.target != '' }}
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v4
+      - name: Get release version string
+        run: |
+          echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
+          echo "deb=$(./bin/buildinfo.py deb)" >> $GITHUB_OUTPUT
+        id: version
+        env:
+          BUILD_LOCATION: local
+    outputs:
+      long: ${{ steps.version.outputs.long }}
+      deb: ${{ steps.version.outputs.deb }}
+
+  build-arch:
+    if: ${{ inputs.target != '' && inputs.arch != 'native' }}
+    needs: [version]
+    strategy:
+      fail-fast: false
+    uses: ./.github/workflows/build_firmware.yml
+    with:
+      version: ${{ needs.version.outputs.long }}
+      pio_env: ${{ inputs.target }}
+      platform: ${{ inputs.arch }}
+
+  build-debian-src:
+    if: ${{ github.repository == 'meshtastic/firmware' && inputs.arch == 'native' }}
+    uses: ./.github/workflows/build_debian_src.yml
+    with:
+      series: UNRELEASED
+      build_location: local
+    secrets: inherit
+
+  package-pio-deps-native-tft:
+    if: ${{ inputs.arch == 'native' }}
+    uses: ./.github/workflows/package_pio_deps.yml
+    with:
+      pio_env: native-tft
+    secrets: inherit
+
+  test-native:
+    if: ${{ !contains(github.ref_name, 'event/') && github.event_name != 'workflow_dispatch' ||  !contains(github.ref_name, 'event/') && inputs.arch == 'native' && inputs.target != '' }}
+    uses: ./.github/workflows/test_native.yml
+
+  docker-deb-amd64:
+    if: ${{ inputs.target != '' && inputs.arch ==  'native' }}
+    uses: ./.github/workflows/docker_build.yml
+    with:
+      distro: debian
+      platform: linux/amd64
+      runs-on: ubuntu-24.04
+      push: false
+
+  docker-deb-amd64-tft:
+    if: ${{ inputs.target != '' && inputs.arch ==  'native' }}
+    uses: ./.github/workflows/docker_build.yml
+    with:
+      distro: debian
+      platform: linux/amd64
+      runs-on: ubuntu-24.04
+      push: false
+      pio_env: native-tft
+
+  docker-alp-amd64:
+    if: ${{ inputs.target != '' && inputs.arch ==  'native' }}
+    uses: ./.github/workflows/docker_build.yml
+    with:
+      distro: alpine
+      platform: linux/amd64
+      runs-on: ubuntu-24.04
+      push: false
+
+  docker-alp-amd64-tft:
+    if: ${{ inputs.target != '' && inputs.arch ==  'native' }}
+    uses: ./.github/workflows/docker_build.yml
+    with:
+      distro: alpine
+      platform: linux/amd64
+      runs-on: ubuntu-24.04
+      push: false
+      pio_env: native-tft
+
+  docker-deb-arm64:
+    if: ${{ inputs.target != '' && inputs.arch ==  'native' }}
+    uses: ./.github/workflows/docker_build.yml
+    with:
+      distro: debian
+      platform: linux/arm64
+      runs-on: ubuntu-24.04-arm
+      push: false
+
+  docker-deb-armv7:
+    if: ${{ inputs.target != '' && inputs.arch ==  'native' }}
+    uses: ./.github/workflows/docker_build.yml
+    with:
+      distro: debian
+      platform: linux/arm/v7
+      runs-on: ubuntu-24.04-arm
+      push: false
+
+  gather-artifacts:
+    permissions:
+      contents: write
+      pull-requests: write
+    strategy:
+      fail-fast: false
+    runs-on: ubuntu-latest
+    needs: [version, build-arch]
+    steps:
+      - name: Checkout code
+        uses: actions/checkout@v4
+        with:
+          ref: ${{github.event.pull_request.head.ref}}
+          repository: ${{github.event.pull_request.head.repo.full_name}}
+
+      - uses: actions/download-artifact@v4
+        with:
+          path: ./
+          pattern: firmware-*-*
+          merge-multiple: true
+
+      - name: Display structure of downloaded files
+        run: ls -R
+
+      - name: Move files up
+        run: mv -b -t ./ ./bin/device-*.sh ./bin/device-*.bat
+
+      - name: Repackage in single firmware zip
+        uses: actions/upload-artifact@v4
+        with:
+          name: firmware-${{inputs.target}}-${{ needs.version.outputs.long }}
+          overwrite: true
+          path: |
+            ./firmware-*.bin
+            ./firmware-*.uf2
+            ./firmware-*.hex
+            ./firmware-*-ota.zip
+            ./device-*.sh
+            ./device-*.bat
+            ./littlefs-*.bin
+            ./bleota*bin
+            ./Meshtastic_nRF52_factory_erase*.uf2
+          retention-days: 30
+
+      - uses: actions/download-artifact@v4
+        with:
+          pattern: firmware-*-${{ needs.version.outputs.long }}
+          merge-multiple: true
+          path: ./output
+
+      # For diagnostics
+      - name: Show artifacts
+        run: ls -lR
+
+      - name: Device scripts permissions
+        run: |
+          chmod +x ./output/device-install.sh
+          chmod +x ./output/device-update.sh
+
+      - name: Zip firmware
+        run: zip -j -9 -r ./firmware-${{inputs.target}}-${{ needs.version.outputs.long }}.zip ./output
+
+      - name: Repackage in single elfs zip
+        uses: actions/upload-artifact@v4
+        with:
+          name: debug-elfs-${{inputs.target}}-${{ needs.version.outputs.long }}.zip
+          overwrite: true
+          path: ./*.elf
+          retention-days: 30
+
+      - uses: scruplelesswizard/comment-artifact@main
+        if: ${{ github.event_name == 'pull_request' }}
+        with:
+          name: firmware-${{inputs.target}}-${{ needs.version.outputs.long }}
+          description: "Download firmware-${{inputs.target}}-${{ needs.version.outputs.long }}.zip. This artifact will be available for 90 days from creation"
+          github-token: ${{ secrets.GITHUB_TOKEN }}
+
+  release-artifacts:
+    runs-on: ubuntu-latest
+    if: ${{ github.event_name == 'workflow_dispatch' && inputs.target != ''}}
+    outputs:
+      upload_url: ${{ steps.create_release.outputs.upload_url }}
+    needs:
+      - version
+      - gather-artifacts
+      - build-debian-src
+      - package-pio-deps-native-tft
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v4
+
+      - name: Setup Python
+        uses: actions/setup-python@v5
+        with:
+          python-version: 3.x
+
+      - name: Create release
+        uses: softprops/action-gh-release@v2
+        id: create_release
+        with:
+          draft: true
+          prerelease: true
+          name: Meshtastic Firmware ${{ needs.version.outputs.long }} Alpha
+          tag_name: v${{ needs.version.outputs.long }}
+          body: |
+            Autogenerated by github action, developer should edit as required before publishing...
+
+      - name: Download source deb
+        uses: actions/download-artifact@v4
+        with:
+          pattern: firmware-debian-${{ needs.version.outputs.deb }}~UNRELEASED-src
+          merge-multiple: true
+          path: ./output/debian-src
+
+      - name: Download `native-tft` pio deps
+        uses: actions/download-artifact@v4
+        with:
+          pattern: platformio-deps-native-tft-${{ needs.version.outputs.long }}
+          merge-multiple: true
+          path: ./output/pio-deps-native-tft
+
+      - name: Zip Linux sources
+        working-directory: output
+        run: |
+          zip -j -9 -r ./meshtasticd-${{ needs.version.outputs.deb }}-src.zip ./debian-src
+          zip -9 -r ./platformio-deps-native-tft-${{ needs.version.outputs.long }}.zip ./pio-deps-native-tft
+
+      # For diagnostics
+      - name: Display structure of downloaded files
+        run: ls -lR
+
+      - name: Add Linux sources to GtiHub Release
+        # Only run when targeting master branch with workflow_dispatch
+        if: ${{ github.ref_name == 'master' }}
+        run: |
+          gh release upload v${{ needs.version.outputs.long }} ./output/meshtasticd-${{ needs.version.outputs.deb }}-src.zip
+          gh release upload v${{ needs.version.outputs.long }} ./output/platformio-deps-native-tft-${{ needs.version.outputs.long }}.zip
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+  release-firmware:
+    strategy:
+      fail-fast: false
+    runs-on: ubuntu-latest
+    if: ${{ github.event_name == 'workflow_dispatch' && inputs.target != ''}}
+    needs: [release-artifacts, version]
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v4
+
+      - name: Setup Python
+        uses: actions/setup-python@v5
+        with:
+          python-version: 3.x
+
+      - uses: actions/download-artifact@v4
+        with:
+          pattern: firmware-*-${{ needs.version.outputs.long }}
+          merge-multiple: true
+          path: ./output
+
+      - name: Display structure of downloaded files
+        run: ls -lR
+
+      - name: Device scripts permissions
+        run: |
+          chmod +x ./output/device-install.sh
+          chmod +x ./output/device-update.sh
+
+      - name: Zip firmware
+        run: zip -j -9 -r ./firmware-${{inputs.target}}-${{ needs.version.outputs.long }}.zip ./output
+
+      - uses: actions/download-artifact@v4
+        with:
+          pattern: debug-elfs-*-${{ needs.version.outputs.long }}.zip
+          merge-multiple: true
+          path: ./elfs
+
+      - name: Zip debug elfs
+        run: zip -j -9 -r ./debug-elfs-${{inputs.target}}-${{ needs.version.outputs.long }}.zip ./elfs
+
+      # For diagnostics
+      - name: Display structure of downloaded files
+        run: ls -lR
+
+      - name: Add bins and debug elfs to GitHub Release
+        # Only run when targeting master branch with workflow_dispatch
+        if: ${{ github.ref_name == 'master' }}
+        run: |
+          gh release upload v${{ needs.version.outputs.long }} ./firmware-${{inputs.target}}-${{ needs.version.outputs.long }}.zip
+          gh release upload v${{ needs.version.outputs.long }} ./debug-elfs-${{inputs.target}}-${{ needs.version.outputs.long }}.zip
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+  publish-firmware:
+    runs-on: ubuntu-24.04
+    if: ${{ github.event_name == 'workflow_dispatch' && github.repository == 'meshtastic/firmware' && inputs.target != '' }}
+    needs: [release-firmware, version]
+    env:
+      targets: |-
+        esp32,esp32s3,esp32c3,esp32c6,nrf52840,rp2040,rp2350,stm32
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v4
+
+      - name: Setup Python
+        uses: actions/setup-python@v5
+        with:
+          python-version: 3.x
+
+      - uses: actions/download-artifact@v4
+        with:
+          pattern: firmware-{${{ env.targets }}}-${{ needs.version.outputs.long }}
+          merge-multiple: true
+          path: ./publish
+
+      - name: Publish firmware to meshtastic.github.io
+        uses: peaceiris/actions-gh-pages@v4
+        env:
+          # On event/* branches, use the event name as the destination prefix
+          DEST_PREFIX: ${{ contains(github.ref_name, 'event/') && format('{0}/', github.ref_name) || '' }}
+        with:
+          deploy_key: ${{ secrets.DIST_PAGES_DEPLOY_KEY }}
+          external_repository: meshtastic/meshtastic.github.io
+          publish_branch: master
+          publish_dir: ./publish
+          destination_dir: ${{ env.DEST_PREFIX }}firmware-${{ needs.version.outputs.long }}
+          keep_files: true
+          user_name: github-actions[bot]
+          user_email: github-actions[bot]@users.noreply.github.com
+          commit_message: ${{ needs.version.outputs.long }}
+          enable_jekyll: true
diff --git a/.github/workflows/daily_packaging.yml b/.github/workflows/daily_packaging.yml
index eb61554f2..7e2316e3d 100644
--- a/.github/workflows/daily_packaging.yml
+++ b/.github/workflows/daily_packaging.yml
@@ -21,12 +21,14 @@ permissions:
 
 jobs:
   docker-multiarch:
+    if: github.repository == 'meshtastic/firmware'
     uses: ./.github/workflows/docker_manifest.yml
     with:
       release_channel: daily
     secrets: inherit
 
   package-ppa:
+    if: github.repository == 'meshtastic/firmware'
     strategy:
       fail-fast: false
       matrix:
@@ -42,6 +44,7 @@ jobs:
     secrets: inherit
 
   package-obs:
+    if: github.repository == 'meshtastic/firmware'
     uses: ./.github/workflows/package_obs.yml
     with:
       obs_project: network:Meshtastic:daily
@@ -49,6 +52,7 @@ jobs:
     secrets: inherit
 
   hook-copr:
+    if: github.repository == 'meshtastic/firmware'
     uses: ./.github/workflows/hook_copr.yml
     with:
       copr_project: daily
diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml
index 562ce00f9..4f2317bd0 100644
--- a/.github/workflows/main_matrix.yml
+++ b/.github/workflows/main_matrix.yml
@@ -27,6 +27,7 @@ on:
 
 jobs:
   setup:
+    if: github.repository == 'meshtastic/firmware'
     strategy:
       fail-fast: false
       matrix:
@@ -70,6 +71,7 @@ jobs:
       check: ${{ steps.jsonStep.outputs.check }}
 
   version:
+    if: github.repository == 'meshtastic/firmware'
     runs-on: ubuntu-latest
     steps:
       - uses: actions/checkout@v5
@@ -91,7 +93,7 @@ jobs:
       matrix: ${{ fromJson(needs.setup.outputs.check) }}
 
     runs-on: ubuntu-latest
-    if: ${{ github.event_name != 'workflow_dispatch' }}
+    if: ${{ github.event_name != 'workflow_dispatch' && github.repository == 'meshtastic/firmware' }}
     steps:
       - uses: actions/checkout@v5
       - name: Build base
@@ -204,10 +206,11 @@ jobs:
     secrets: inherit
 
   test-native:
-    if: ${{ !contains(github.ref_name, 'event/') }}
+    if: ${{ !contains(github.ref_name, 'event/') && github.repository == 'meshtastic/firmware' }}
     uses: ./.github/workflows/test_native.yml
 
   docker-deb-amd64:
+    if: github.repository == 'meshtastic/firmware'
     uses: ./.github/workflows/docker_build.yml
     with:
       distro: debian
@@ -216,6 +219,7 @@ jobs:
       push: false
 
   docker-deb-amd64-tft:
+    if: github.repository == 'meshtastic/firmware'
     uses: ./.github/workflows/docker_build.yml
     with:
       distro: debian
@@ -225,6 +229,7 @@ jobs:
       pio_env: native-tft
 
   docker-alp-amd64:
+    if: github.repository == 'meshtastic/firmware'
     uses: ./.github/workflows/docker_build.yml
     with:
       distro: alpine
@@ -233,6 +238,7 @@ jobs:
       push: false
 
   docker-alp-amd64-tft:
+    if: github.repository == 'meshtastic/firmware'
     uses: ./.github/workflows/docker_build.yml
     with:
       distro: alpine
@@ -242,6 +248,7 @@ jobs:
       pio_env: native-tft
 
   docker-deb-arm64:
+    if: github.repository == 'meshtastic/firmware'
     uses: ./.github/workflows/docker_build.yml
     with:
       distro: debian
@@ -250,6 +257,7 @@ jobs:
       push: false
 
   docker-deb-armv7:
+    if: github.repository == 'meshtastic/firmware'
     uses: ./.github/workflows/docker_build.yml
     with:
       distro: debian
@@ -259,6 +267,7 @@ jobs:
 
   gather-artifacts:
     # trunk-ignore(checkov/CKV2_GHA_1)
+    if: github.repository == 'meshtastic/firmware'
     permissions:
       contents: write
       pull-requests: write
@@ -358,7 +367,7 @@ jobs:
 
   release-artifacts:
     runs-on: ubuntu-latest
-    if: ${{ github.event_name == 'workflow_dispatch' }}
+    if: ${{ github.event_name == 'workflow_dispatch' && github.repository == 'meshtastic/firmware' }}
     outputs:
       upload_url: ${{ steps.create_release.outputs.upload_url }}
     needs:
@@ -433,7 +442,7 @@ jobs:
           - rp2350
           - stm32
     runs-on: ubuntu-latest
-    if: ${{ github.event_name == 'workflow_dispatch' }}
+    if: ${{ github.event_name == 'workflow_dispatch' && github.repository == 'meshtastic/firmware'}}
     needs: [release-artifacts, version]
     steps:
       - name: Checkout

From 953fcca304ed37c94a0c20d19dcc8df86a853b00 Mon Sep 17 00:00:00 2001
From: Jason P 
Date: Thu, 28 Aug 2025 11:23:24 -0500
Subject: [PATCH 256/691] BaseUI Show/Hide Frame Functionality (#7382)

* Rename System Frame (from Memory) in code base

* Create menu options to Show/Hide frames: Node Lists, Bearings, Position, LoRa, Clock and Favorites frames

* Move Region Picker into submenu

* Tweak wording for Send Position vs Node Info if the device has GPS
---
 src/graphics/Screen.cpp             | 223 +++++++++++++++++++---------
 src/graphics/Screen.h               |  30 +++-
 src/graphics/draw/DebugRenderer.cpp |   2 +-
 src/graphics/draw/DebugRenderer.h   |   4 +-
 src/graphics/draw/MenuHandler.cpp   | 149 ++++++++++++++++++-
 src/graphics/draw/MenuHandler.h     |   3 +
 src/graphics/images.h               |   4 +-
 7 files changed, 331 insertions(+), 84 deletions(-)

diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp
index 0a2229d0e..5bddb6cc8 100644
--- a/src/graphics/Screen.cpp
+++ b/src/graphics/Screen.cpp
@@ -901,75 +901,90 @@ void Screen::setFrames(FrameFocus focus)
     }
 
 #if defined(DISPLAY_CLOCK_FRAME)
-    fsi.positions.clock = numframes;
+    if (!hiddenFrames.clock) {
+        fsi.positions.clock = numframes;
 #if defined(M5STACK_UNITC6L)
-    normalFrames[numframes++] = graphics::ClockRenderer::drawAnalogClockFrame;
+        normalFrames[numframes++] = graphics::ClockRenderer::drawAnalogClockFrame;
 #else
-    normalFrames[numframes++] = uiconfig.is_clockface_analog ? graphics::ClockRenderer::drawAnalogClockFrame
-                                                             : graphics::ClockRenderer::drawDigitalClockFrame;
+        normalFrames[numframes++] = uiconfig.is_clockface_analog ? graphics::ClockRenderer::drawAnalogClockFrame
+                                                                 : graphics::ClockRenderer::drawDigitalClockFrame;
 #endif
-    indicatorIcons.push_back(digital_icon_clock);
+        indicatorIcons.push_back(digital_icon_clock);
+    }
 #endif
 
     // Declare this early so it’s available in FOCUS_PRESERVE block
     bool willInsertTextMessage = shouldDrawMessage(&devicestate.rx_text_message);
 
-    fsi.positions.home = numframes;
-    normalFrames[numframes++] = graphics::UIRenderer::drawDeviceFocused;
-    indicatorIcons.push_back(icon_home);
+    if (!hiddenFrames.home) {
+        fsi.positions.home = numframes;
+        normalFrames[numframes++] = graphics::UIRenderer::drawDeviceFocused;
+        indicatorIcons.push_back(icon_home);
+    }
 
     fsi.positions.textMessage = numframes;
     normalFrames[numframes++] = graphics::MessageRenderer::drawTextMessageFrame;
     indicatorIcons.push_back(icon_mail);
 
 #ifndef USE_EINK
-    fsi.positions.nodelist = numframes;
-    normalFrames[numframes++] = graphics::NodeListRenderer::drawDynamicNodeListScreen;
-    indicatorIcons.push_back(icon_nodes);
+    if (!hiddenFrames.nodelist) {
+        fsi.positions.nodelist = numframes;
+        normalFrames[numframes++] = graphics::NodeListRenderer::drawDynamicNodeListScreen;
+        indicatorIcons.push_back(icon_nodes);
+    }
 #endif
 
 // Show detailed node views only on E-Ink builds
 #ifdef USE_EINK
-    fsi.positions.nodelist_lastheard = numframes;
-    normalFrames[numframes++] = graphics::NodeListRenderer::drawLastHeardScreen;
-    indicatorIcons.push_back(icon_nodes);
-
-    fsi.positions.nodelist_hopsignal = numframes;
-    normalFrames[numframes++] = graphics::NodeListRenderer::drawHopSignalScreen;
-    indicatorIcons.push_back(icon_signal);
-
-    fsi.positions.nodelist_distance = numframes;
-    normalFrames[numframes++] = graphics::NodeListRenderer::drawDistanceScreen;
-    indicatorIcons.push_back(icon_distance);
+    if (!hiddenFrames.nodelist_lastheard) {
+        fsi.positions.nodelist_lastheard = numframes;
+        normalFrames[numframes++] = graphics::NodeListRenderer::drawLastHeardScreen;
+        indicatorIcons.push_back(icon_nodes);
+    }
+    if (!hiddenFrames.nodelist_hopsignal) {
+        fsi.positions.nodelist_hopsignal = numframes;
+        normalFrames[numframes++] = graphics::NodeListRenderer::drawHopSignalScreen;
+        indicatorIcons.push_back(icon_signal);
+    }
+    if (!hiddenFrames.nodelist_distance) {
+        fsi.positions.nodelist_distance = numframes;
+        normalFrames[numframes++] = graphics::NodeListRenderer::drawDistanceScreen;
+        indicatorIcons.push_back(icon_distance);
+    }
 #endif
 #if HAS_GPS
-    fsi.positions.nodelist_bearings = numframes;
-    normalFrames[numframes++] = graphics::NodeListRenderer::drawNodeListWithCompasses;
-    indicatorIcons.push_back(icon_list);
-
-    fsi.positions.gps = numframes;
-    normalFrames[numframes++] = graphics::UIRenderer::drawCompassAndLocationScreen;
-    indicatorIcons.push_back(icon_compass);
+    if (!hiddenFrames.nodelist_bearings) {
+        fsi.positions.nodelist_bearings = numframes;
+        normalFrames[numframes++] = graphics::NodeListRenderer::drawNodeListWithCompasses;
+        indicatorIcons.push_back(icon_list);
+    }
+    if (!hiddenFrames.gps) {
+        fsi.positions.gps = numframes;
+        normalFrames[numframes++] = graphics::UIRenderer::drawCompassAndLocationScreen;
+        indicatorIcons.push_back(icon_compass);
+    }
 #endif
-    if (RadioLibInterface::instance) {
+    if (RadioLibInterface::instance && !hiddenFrames.lora) {
         fsi.positions.lora = numframes;
         normalFrames[numframes++] = graphics::DebugRenderer::drawLoRaFocused;
         indicatorIcons.push_back(icon_radio);
     }
-    if (!dismissedFrames.memory) {
-        fsi.positions.memory = numframes;
-        normalFrames[numframes++] = graphics::DebugRenderer::drawMemoryUsage;
-        indicatorIcons.push_back(icon_memory);
+    if (!hiddenFrames.system) {
+        fsi.positions.system = numframes;
+        normalFrames[numframes++] = graphics::DebugRenderer::drawSystemScreen;
+        indicatorIcons.push_back(icon_system);
     }
 #if !defined(DISPLAY_CLOCK_FRAME)
-    fsi.positions.clock = numframes;
-    normalFrames[numframes++] = uiconfig.is_clockface_analog ? graphics::ClockRenderer::drawAnalogClockFrame
-                                                             : graphics::ClockRenderer::drawDigitalClockFrame;
-    indicatorIcons.push_back(digital_icon_clock);
+    if (!hiddenFrames.clock) {
+        fsi.positions.clock = numframes;
+        normalFrames[numframes++] = uiconfig.is_clockface_analog ? graphics::ClockRenderer::drawAnalogClockFrame
+                                                                 : graphics::ClockRenderer::drawDigitalClockFrame;
+        indicatorIcons.push_back(digital_icon_clock);
+    }
 #endif
 
 #if HAS_WIFI && !defined(ARCH_PORTDUINO)
-    if (!dismissedFrames.wifi && isWifiAvailable()) {
+    if (!hiddenFrames.wifi && isWifiAvailable()) {
         fsi.positions.wifi = numframes;
         normalFrames[numframes++] = graphics::DebugRenderer::drawDebugInfoWiFiTrampoline;
         indicatorIcons.push_back(icon_wifi);
@@ -1011,27 +1026,29 @@ void Screen::setFrames(FrameFocus focus)
     if (numMeshNodes > 0)
         numMeshNodes--;
 
-    // Temporary array to hold favorite node frames
-    std::vector favoriteFrames;
+    if (!hiddenFrames.show_favorites) {
+        // Temporary array to hold favorite node frames
+        std::vector favoriteFrames;
 
-    for (size_t i = 0; i < nodeDB->getNumMeshNodes(); i++) {
-        const meshtastic_NodeInfoLite *n = nodeDB->getMeshNodeByIndex(i);
-        if (n && n->num != nodeDB->getNodeNum() && n->is_favorite) {
-            favoriteFrames.push_back(graphics::UIRenderer::drawNodeInfo);
+        for (size_t i = 0; i < nodeDB->getNumMeshNodes(); i++) {
+            const meshtastic_NodeInfoLite *n = nodeDB->getMeshNodeByIndex(i);
+            if (n && n->num != nodeDB->getNodeNum() && n->is_favorite) {
+                favoriteFrames.push_back(graphics::UIRenderer::drawNodeInfo);
+            }
         }
-    }
 
-    // Insert favorite frames *after* collecting them all
-    if (!favoriteFrames.empty()) {
-        fsi.positions.firstFavorite = numframes;
-        for (const auto &f : favoriteFrames) {
-            normalFrames[numframes++] = f;
-            indicatorIcons.push_back(icon_node);
+        // Insert favorite frames *after* collecting them all
+        if (!favoriteFrames.empty()) {
+            fsi.positions.firstFavorite = numframes;
+            for (const auto &f : favoriteFrames) {
+                normalFrames[numframes++] = f;
+                indicatorIcons.push_back(icon_node);
+            }
+            fsi.positions.lastFavorite = numframes - 1;
+        } else {
+            fsi.positions.firstFavorite = 255;
+            fsi.positions.lastFavorite = 255;
         }
-        fsi.positions.lastFavorite = numframes - 1;
-    } else {
-        fsi.positions.firstFavorite = 255;
-        fsi.positions.lastFavorite = 255;
     }
 
     fsi.frameCount = numframes;   // Total framecount is used to apply FOCUS_PRESERVE
@@ -1070,7 +1087,7 @@ void Screen::setFrames(FrameFocus focus)
         ui->switchToFrame(fsi.positions.clock);
         break;
     case FOCUS_SYSTEM:
-        ui->switchToFrame(fsi.positions.memory);
+        ui->switchToFrame(fsi.positions.system);
         break;
 
     case FOCUS_PRESERVE:
@@ -1098,30 +1115,96 @@ void Screen::setFrameImmediateDraw(FrameCallback *drawFrames)
     setFastFramerate();
 }
 
+void Screen::toggleFrameVisibility(const std::string &frameName)
+{
+#ifndef USE_EINK
+    if (frameName == "nodelist") {
+        hiddenFrames.nodelist = !hiddenFrames.nodelist;
+    }
+#endif
+#ifdef USE_EINK
+    if (frameName == "nodelist_lastheard") {
+        hiddenFrames.nodelist_lastheard = !hiddenFrames.nodelist_lastheard;
+    }
+    if (frameName == "nodelist_hopsignal") {
+        hiddenFrames.nodelist_hopsignal = !hiddenFrames.nodelist_hopsignal;
+    }
+    if (frameName == "nodelist_distance") {
+        hiddenFrames.nodelist_distance = !hiddenFrames.nodelist_distance;
+    }
+#endif
+#if HAS_GPS
+    if (frameName == "nodelist_bearings") {
+        hiddenFrames.nodelist_bearings = !hiddenFrames.nodelist_bearings;
+    }
+    if (frameName == "gps") {
+        hiddenFrames.gps = !hiddenFrames.gps;
+    }
+#endif
+    if (frameName == "lora") {
+        hiddenFrames.lora = !hiddenFrames.lora;
+    }
+    if (frameName == "clock") {
+        hiddenFrames.clock = !hiddenFrames.clock;
+    }
+    if (frameName == "show_favorites") {
+        hiddenFrames.show_favorites = !hiddenFrames.show_favorites;
+    }
+}
+
+bool Screen::isFrameHidden(const std::string &frameName) const
+{
+#ifndef USE_EINK
+    if (frameName == "nodelist")
+        return hiddenFrames.nodelist;
+#endif
+#ifdef USE_EINK
+    if (frameName == "nodelist_lastheard")
+        return hiddenFrames.nodelist_lastheard;
+    if (frameName == "nodelist_hopsignal")
+        return hiddenFrames.nodelist_hopsignal;
+    if (frameName == "nodelist_distance")
+        return hiddenFrames.nodelist_distance;
+#endif
+#if HAS_GPS
+    if (frameName == "nodelist_bearings")
+        return hiddenFrames.nodelist_bearings;
+    if (frameName == "gps")
+        return hiddenFrames.gps;
+#endif
+    if (frameName == "lora")
+        return hiddenFrames.lora;
+    if (frameName == "clock")
+        return hiddenFrames.clock;
+    if (frameName == "show_favorites")
+        return hiddenFrames.show_favorites;
+
+    return false;
+}
+
 // Dismisses the currently displayed screen frame, if possible
 // Relevant for text message, waypoint, others in future?
 // Triggered with a CardKB keycombo
-void Screen::dismissCurrentFrame()
+void Screen::hideCurrentFrame()
 {
     uint8_t currentFrame = ui->getUiState()->currentFrame;
     bool dismissed = false;
-
     if (currentFrame == framesetInfo.positions.textMessage && devicestate.has_rx_text_message) {
-        LOG_INFO("Dismiss Text Message");
+        LOG_INFO("Hide Text Message");
         devicestate.has_rx_text_message = false;
         memset(&devicestate.rx_text_message, 0, sizeof(devicestate.rx_text_message));
     } else if (currentFrame == framesetInfo.positions.waypoint && devicestate.has_rx_waypoint) {
-        LOG_DEBUG("Dismiss Waypoint");
+        LOG_DEBUG("Hide Waypoint");
         devicestate.has_rx_waypoint = false;
-        dismissedFrames.waypoint = true;
+        hiddenFrames.waypoint = true;
         dismissed = true;
     } else if (currentFrame == framesetInfo.positions.wifi) {
-        LOG_DEBUG("Dismiss WiFi Screen");
-        dismissedFrames.wifi = true;
+        LOG_DEBUG("Hide WiFi Screen");
+        hiddenFrames.wifi = true;
         dismissed = true;
-    } else if (currentFrame == framesetInfo.positions.memory) {
-        LOG_INFO("Dismiss Memory");
-        dismissedFrames.memory = true;
+    } else if (currentFrame == framesetInfo.positions.lora) {
+        LOG_INFO("Hide LoRa");
+        hiddenFrames.lora = true;
         dismissed = true;
     }
 
@@ -1277,7 +1360,7 @@ int Screen::handleTextMessage(const meshtastic_MeshPacket *packet)
             // Outgoing message (likely sent from phone)
             devicestate.has_rx_text_message = false;
             memset(&devicestate.rx_text_message, 0, sizeof(devicestate.rx_text_message));
-            dismissedFrames.textMessage = true;
+            hiddenFrames.textMessage = true;
             hasUnreadMessage = false; // Clear unread state when user replies
 
             setFrames(FOCUS_PRESERVE); // Stay on same frame, silently update frame list
@@ -1402,7 +1485,7 @@ int Screen::handleInputEvent(const InputEvent *event)
             } else if (event->inputEvent == INPUT_BROKER_SELECT) {
                 if (this->ui->getUiState()->currentFrame == framesetInfo.positions.home) {
                     menuHandler::homeBaseMenu();
-                } else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.memory) {
+                } else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.system) {
                     menuHandler::systemBaseMenu();
 #if HAS_GPS
                 } else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.gps && gps) {
@@ -1411,7 +1494,7 @@ int Screen::handleInputEvent(const InputEvent *event)
                 } else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.clock) {
                     menuHandler::clockMenu();
                 } else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.lora) {
-                    menuHandler::LoraRegionPicker();
+                    menuHandler::loraMenu();
                 } else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.textMessage) {
                     if (devicestate.rx_text_message.from) {
                         menuHandler::messageResponseMenu();
diff --git a/src/graphics/Screen.h b/src/graphics/Screen.h
index ecc39ac60..85dcfaf69 100644
--- a/src/graphics/Screen.h
+++ b/src/graphics/Screen.h
@@ -593,7 +593,11 @@ class Screen : public concurrency::OSThread
     void setSSLFrames();
 
     // Dismiss the currently focussed frame, if possible (e.g. text message, waypoint)
-    void dismissCurrentFrame();
+    void hideCurrentFrame();
+
+    // Menu-driven Show / Hide Toggle
+    void toggleFrameVisibility(const std::string &frameName);
+    bool isFrameHidden(const std::string &frameName) const;
 
 #ifdef USE_EINK
     /// Draw an image to remain on E-Ink display after screen off
@@ -655,7 +659,7 @@ class Screen : public concurrency::OSThread
             uint8_t settings = 255;
             uint8_t wifi = 255;
             uint8_t deviceFocused = 255;
-            uint8_t memory = 255;
+            uint8_t system = 255;
             uint8_t gps = 255;
             uint8_t home = 255;
             uint8_t textMessage = 255;
@@ -673,12 +677,28 @@ class Screen : public concurrency::OSThread
         uint8_t frameCount = 0;
     } framesetInfo;
 
-    struct DismissedFrames {
+    struct hiddenFrames {
         bool textMessage = false;
         bool waypoint = false;
         bool wifi = false;
-        bool memory = false;
-    } dismissedFrames;
+        bool system = false;
+        bool home = false;
+        bool clock = false;
+#ifndef USE_EINK
+        bool nodelist = false;
+#endif
+#ifdef USE_EINK
+        bool nodelist_lastheard = false;
+        bool nodelist_hopsignal = false;
+        bool nodelist_distance = false;
+#endif
+#if HAS_GPS
+        bool nodelist_bearings = false;
+        bool gps = false;
+#endif
+        bool lora = false;
+        bool show_favorites = false;
+    } hiddenFrames;
 
     /// Try to start drawing ASAP
     void setFastFramerate();
diff --git a/src/graphics/draw/DebugRenderer.cpp b/src/graphics/draw/DebugRenderer.cpp
index 6137ddef8..61e919208 100644
--- a/src/graphics/draw/DebugRenderer.cpp
+++ b/src/graphics/draw/DebugRenderer.cpp
@@ -499,7 +499,7 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
 // ****************************
 // *      System Screen       *
 // ****************************
-void drawMemoryUsage(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
+void drawSystemScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
 {
     display->clear();
     display->setFont(FONT_SMALL);
diff --git a/src/graphics/draw/DebugRenderer.h b/src/graphics/draw/DebugRenderer.h
index f4d484f58..3382e931d 100644
--- a/src/graphics/draw/DebugRenderer.h
+++ b/src/graphics/draw/DebugRenderer.h
@@ -31,8 +31,8 @@ void drawDebugInfoWiFiTrampoline(OLEDDisplay *display, OLEDDisplayUiState *state
 // LoRa information display
 void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
 
-// Memory screen display
-void drawMemoryUsage(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
+// System screen display
+void drawSystemScreen(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 ba554dbd6..48e7e808b 100644
--- a/src/graphics/draw/MenuHandler.cpp
+++ b/src/graphics/draw/MenuHandler.cpp
@@ -26,6 +26,24 @@ menuHandler::screenMenus menuHandler::menuQueue = menu_none;
 bool test_enabled = false;
 uint8_t test_count = 0;
 
+void menuHandler::loraMenu()
+{
+    static const char *optionsArray[] = {"Back", "Region Picker"};
+    enum optionsNumbers { Back = 0, lora_picker = 1 };
+    BannerOverlayOptions bannerOptions;
+    bannerOptions.message = "LoRa Actions";
+    bannerOptions.optionsArrayPtr = optionsArray;
+    bannerOptions.optionsCount = 2;
+    bannerOptions.bannerCallback = [](int selected) -> void {
+        if (selected == Back) {
+            // No action
+        } else if (selected == lora_picker) {
+            menuHandler::menuQueue = menuHandler::lora_picker;
+        }
+    };
+    screen->showOverlayBanner(bannerOptions);
+}
+
 void menuHandler::OnboardMessage()
 {
     static const char *optionsArray[] = {"OK", "Got it!"};
@@ -320,7 +338,7 @@ void menuHandler::messageResponseMenu()
     bannerOptions.optionsCount = options;
     bannerOptions.bannerCallback = [](int selected) -> void {
         if (selected == Dismiss) {
-            screen->dismissCurrentFrame();
+            screen->hideCurrentFrame();
         } else if (selected == Preset) {
             if (devicestate.rx_text_message.to == NODENUM_BROADCAST) {
                 cannedMessageModule->LaunchWithDestination(NODENUM_BROADCAST, devicestate.rx_text_message.channel);
@@ -361,8 +379,11 @@ void menuHandler::homeBaseMenu()
     optionsArray[options] = "Sleep Screen";
     optionsEnumArray[options++] = Sleep;
 #endif
-
-    optionsArray[options] = "Send Position";
+    if (config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_ENABLED) {
+        optionsArray[options] = "Send Position";
+    } else {
+        optionsArray[options] = "Send Node Info";
+    }
     optionsEnumArray[options++] = Position;
 #if defined(M5STACK_UNITC6L)
     optionsArray[options] = "New Preset";
@@ -455,7 +476,7 @@ void menuHandler::textMessageBaseMenu()
 
 void menuHandler::systemBaseMenu()
 {
-    enum optionsNumbers { Back, Notifications, ScreenOptions, Bluetooth, PowerMenu, Test, enumEnd };
+    enum optionsNumbers { Back, Notifications, ScreenOptions, Bluetooth, PowerMenu, FrameToggles, Test, enumEnd };
     static const char *optionsArray[enumEnd] = {"Back"};
     static int optionsEnumArray[enumEnd] = {Back};
     int options = 1;
@@ -467,6 +488,9 @@ void menuHandler::systemBaseMenu()
     optionsArray[options] = "Screen Options";
     optionsEnumArray[options++] = ScreenOptions;
 #endif
+
+    optionsArray[options] = "Frame Visiblity Toggle";
+    optionsEnumArray[options++] = FrameToggles;
 #if defined(M5STACK_UNITC6L)
     optionsArray[options] = "Bluetooth";
 #else
@@ -504,6 +528,9 @@ void menuHandler::systemBaseMenu()
         } else if (selected == PowerMenu) {
             menuHandler::menuQueue = menuHandler::power_menu;
             screen->runNow();
+        } else if (selected == FrameToggles) {
+            menuHandler::menuQueue = menuHandler::FrameToggles;
+            screen->runNow();
         } else if (selected == Test) {
             menuHandler::menuQueue = menuHandler::test_menu;
             screen->runNow();
@@ -580,6 +607,7 @@ void menuHandler::positionBaseMenu()
         optionsArray[options] = "Compass Calibrate";
         optionsEnumArray[options++] = CompassCalibrate;
     }
+
     BannerOverlayOptions bannerOptions;
     bannerOptions.message = "Position Action";
     bannerOptions.optionsArrayPtr = optionsArray;
@@ -1220,6 +1248,116 @@ void menuHandler::keyVerificationFinalPrompt()
     }
 }
 
+void menuHandler::FrameToggles_menu()
+{
+    enum optionsNumbers {
+        Finish,
+        nodelist,
+        nodelist_lastheard,
+        nodelist_hopsignal,
+        nodelist_distance,
+        nodelist_bearings,
+        gps,
+        lora,
+        clock,
+        show_favorites,
+        enumEnd
+    };
+    static const char *optionsArray[enumEnd] = {"Finish"};
+    static int optionsEnumArray[enumEnd] = {Finish};
+    int options = 1;
+
+    // Track last selected index (not enum value!)
+    static int lastSelectedIndex = 0;
+
+#ifndef USE_EINK
+    optionsArray[options] = screen->isFrameHidden("nodelist") ? "Show Node List" : "Hide Node List";
+    optionsEnumArray[options++] = nodelist;
+#endif
+#ifdef USE_EINK
+    optionsArray[options] = screen->isFrameHidden("nodelist_lastheard") ? "Show NL - Last Heard" : "Hide NL - Last Heard";
+    optionsEnumArray[options++] = nodelist_lastheard;
+    optionsArray[options] = screen->isFrameHidden("nodelist_hopsignal") ? "Show NL - Hops/Signal" : "Hide NL - Hops/Signal";
+    optionsEnumArray[options++] = nodelist_hopsignal;
+    optionsArray[options] = screen->isFrameHidden("nodelist_distance") ? "Show NL - Distance" : "Hide NL - Distance";
+    optionsEnumArray[options++] = nodelist_distance;
+#endif
+#if HAS_GPS
+    optionsArray[options] = screen->isFrameHidden("nodelist_bearings") ? "Show Bearings" : "Hide Bearings";
+    optionsEnumArray[options++] = nodelist_bearings;
+
+    optionsArray[options] = screen->isFrameHidden("gps") ? "Show Position" : "Hide Position";
+    optionsEnumArray[options++] = gps;
+#endif
+
+    optionsArray[options] = screen->isFrameHidden("lora") ? "Show LoRa" : "Hide LoRa";
+    optionsEnumArray[options++] = lora;
+
+    optionsArray[options] = screen->isFrameHidden("clock") ? "Show Clock" : "Hide Clock";
+    optionsEnumArray[options++] = clock;
+
+    optionsArray[options] = screen->isFrameHidden("show_favorites") ? "Show Favorites" : "Hide Favorites";
+    optionsEnumArray[options++] = show_favorites;
+
+    BannerOverlayOptions bannerOptions;
+    bannerOptions.message = "Show/Hide Frames";
+    bannerOptions.optionsArrayPtr = optionsArray;
+    bannerOptions.optionsCount = options;
+    bannerOptions.optionsEnumPtr = optionsEnumArray;
+    bannerOptions.InitialSelected = lastSelectedIndex; // Use index, not enum value
+
+    bannerOptions.bannerCallback = [optionsEnumArray, options](int selected) mutable -> void {
+        // Find the index of selected in optionsEnumArray
+        int idx = 0;
+        for (; idx < options; ++idx) {
+            if (optionsEnumArray[idx] == selected)
+                break;
+        }
+        lastSelectedIndex = idx;
+
+        if (selected == Finish) {
+            screen->setFrames(Screen::FOCUS_DEFAULT);
+        } else if (selected == nodelist) {
+            screen->toggleFrameVisibility("nodelist");
+            menuHandler::menuQueue = menuHandler::FrameToggles;
+            screen->runNow();
+        } else if (selected == nodelist_lastheard) {
+            screen->toggleFrameVisibility("nodelist_lastheard");
+            menuHandler::menuQueue = menuHandler::FrameToggles;
+            screen->runNow();
+        } else if (selected == nodelist_hopsignal) {
+            screen->toggleFrameVisibility("nodelist_hopsignal");
+            menuHandler::menuQueue = menuHandler::FrameToggles;
+            screen->runNow();
+        } else if (selected == nodelist_distance) {
+            screen->toggleFrameVisibility("nodelist_distance");
+            menuHandler::menuQueue = menuHandler::FrameToggles;
+            screen->runNow();
+        } else if (selected == nodelist_bearings) {
+            screen->toggleFrameVisibility("nodelist_bearings");
+            menuHandler::menuQueue = menuHandler::FrameToggles;
+            screen->runNow();
+        } else if (selected == gps) {
+            screen->toggleFrameVisibility("gps");
+            menuHandler::menuQueue = menuHandler::FrameToggles;
+            screen->runNow();
+        } else if (selected == lora) {
+            screen->toggleFrameVisibility("lora");
+            menuHandler::menuQueue = menuHandler::FrameToggles;
+            screen->runNow();
+        } else if (selected == clock) {
+            screen->toggleFrameVisibility("clock");
+            menuHandler::menuQueue = menuHandler::FrameToggles;
+            screen->runNow();
+        } else if (selected == show_favorites) {
+            screen->toggleFrameVisibility("show_favorites");
+            menuHandler::menuQueue = menuHandler::FrameToggles;
+            screen->runNow();
+        }
+    };
+    screen->showOverlayBanner(bannerOptions);
+}
+
 void menuHandler::handleMenuSwitch(OLEDDisplay *display)
 {
     if (menuQueue != menu_none)
@@ -1316,6 +1454,9 @@ void menuHandler::handleMenuSwitch(OLEDDisplay *display)
     case power_menu:
         powerMenu();
         break;
+    case FrameToggles:
+        FrameToggles_menu();
+        break;
     case throttle_message:
         screen->showSimpleBanner("Too Many Attempts\nTry again in 60 seconds.", 5000);
         break;
diff --git a/src/graphics/draw/MenuHandler.h b/src/graphics/draw/MenuHandler.h
index ed49a89fb..4e7e02173 100644
--- a/src/graphics/draw/MenuHandler.h
+++ b/src/graphics/draw/MenuHandler.h
@@ -39,11 +39,13 @@ class menuHandler
         key_verification_final_prompt,
         trace_route_menu,
         throttle_message,
+        FrameToggles
     };
     static screenMenus menuQueue;
 
     static void OnboardMessage();
     static void LoraRegionPicker(uint32_t duration = 30000);
+    static void loraMenu();
     static void handleMenuSwitch(OLEDDisplay *display);
     static void showConfirmationBanner(const char *message, std::function onConfirm);
     static void clockMenu();
@@ -77,6 +79,7 @@ class menuHandler
     static void screenOptionsMenu();
     static void powerMenu();
     static void textMessageMenu();
+    static void FrameToggles_menu();
 
   private:
     static void saveUIConfig();
diff --git a/src/graphics/images.h b/src/graphics/images.h
index 4a58edb3b..fd9a2db0f 100644
--- a/src/graphics/images.h
+++ b/src/graphics/images.h
@@ -118,8 +118,8 @@ const uint8_t icon_radio[] PROGMEM = {
     0xA9  // Row 7: #..#.#.#
 };
 
-// 🪙 Memory Icon
-const uint8_t icon_memory[] PROGMEM = {
+// 🪙 System Icon
+const uint8_t icon_system[] PROGMEM = {
     0x24, // Row 0: ..#..#..
     0x3C, // Row 1: ..####..
     0xC3, // Row 2: ##....##

From 8e1da8561e76f0645d25a3761bd04506a52640e7 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Thu, 18 Sep 2025 19:56:47 -0500
Subject: [PATCH 257/691] Update actions/checkout action to v5 (#8031)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
 .github/workflows/build_one_arch.yml   | 12 ++++++------
 .github/workflows/build_one_target.yml | 12 ++++++------
 2 files changed, 12 insertions(+), 12 deletions(-)

diff --git a/.github/workflows/build_one_arch.yml b/.github/workflows/build_one_arch.yml
index 5901a335c..204130711 100644
--- a/.github/workflows/build_one_arch.yml
+++ b/.github/workflows/build_one_arch.yml
@@ -22,7 +22,7 @@ jobs:
       fail-fast: false
     runs-on: ubuntu-24.04
     steps:
-      - uses: actions/checkout@v4
+      - uses: actions/checkout@v5
       - uses: actions/setup-python@v5
         with:
           python-version: 3.x
@@ -52,7 +52,7 @@ jobs:
   version:
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout@v4
+      - uses: actions/checkout@v5
       - name: Get release version string
         run: |
           echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
@@ -266,7 +266,7 @@ jobs:
       ]
     steps:
       - name: Checkout code
-        uses: actions/checkout@v4
+        uses: actions/checkout@v5
         with:
           ref: ${{github.event.pull_request.head.ref}}
           repository: ${{github.event.pull_request.head.repo.full_name}}
@@ -345,7 +345,7 @@ jobs:
       - package-pio-deps-native-tft
     steps:
       - name: Checkout
-        uses: actions/checkout@v4
+        uses: actions/checkout@v5
 
       - name: Setup Python
         uses: actions/setup-python@v5
@@ -414,7 +414,7 @@ jobs:
     needs: [release-artifacts, version]
     steps:
       - name: Checkout
-        uses: actions/checkout@v4
+        uses: actions/checkout@v5
 
       - name: Setup Python
         uses: actions/setup-python@v5
@@ -469,7 +469,7 @@ jobs:
         esp32,esp32s3,esp32c3,esp32c6,nrf52840,rp2040,rp2350,stm32
     steps:
       - name: Checkout
-        uses: actions/checkout@v4
+        uses: actions/checkout@v5
 
       - name: Setup Python
         uses: actions/setup-python@v5
diff --git a/.github/workflows/build_one_target.yml b/.github/workflows/build_one_target.yml
index 07478ff93..f5831ed52 100644
--- a/.github/workflows/build_one_target.yml
+++ b/.github/workflows/build_one_target.yml
@@ -42,7 +42,7 @@ jobs:
 
     runs-on: ubuntu-24.04
     steps:
-      - uses: actions/checkout@v4
+      - uses: actions/checkout@v5
       - uses: actions/setup-python@v5
         with:
           python-version: 3.x
@@ -63,7 +63,7 @@ jobs:
     if: ${{ inputs.target != '' }}
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout@v4
+      - uses: actions/checkout@v5
       - name: Get release version string
         run: |
           echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
@@ -171,7 +171,7 @@ jobs:
     needs: [version, build-arch]
     steps:
       - name: Checkout code
-        uses: actions/checkout@v4
+        uses: actions/checkout@v5
         with:
           ref: ${{github.event.pull_request.head.ref}}
           repository: ${{github.event.pull_request.head.repo.full_name}}
@@ -250,7 +250,7 @@ jobs:
       - package-pio-deps-native-tft
     steps:
       - name: Checkout
-        uses: actions/checkout@v4
+        uses: actions/checkout@v5
 
       - name: Setup Python
         uses: actions/setup-python@v5
@@ -309,7 +309,7 @@ jobs:
     needs: [release-artifacts, version]
     steps:
       - name: Checkout
-        uses: actions/checkout@v4
+        uses: actions/checkout@v5
 
       - name: Setup Python
         uses: actions/setup-python@v5
@@ -364,7 +364,7 @@ jobs:
         esp32,esp32s3,esp32c3,esp32c6,nrf52840,rp2040,rp2350,stm32
     steps:
       - name: Checkout
-        uses: actions/checkout@v4
+        uses: actions/checkout@v5
 
       - name: Setup Python
         uses: actions/setup-python@v5

From f083864f1fbb20b04df2fd646bf25bd27d0775fb Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Thu, 18 Sep 2025 19:56:57 -0500
Subject: [PATCH 258/691] Update actions/download-artifact action to v5 (#8032)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
 .github/workflows/build_one_arch.yml   | 14 +++++++-------
 .github/workflows/build_one_target.yml | 14 +++++++-------
 2 files changed, 14 insertions(+), 14 deletions(-)

diff --git a/.github/workflows/build_one_arch.yml b/.github/workflows/build_one_arch.yml
index 204130711..3b11a9758 100644
--- a/.github/workflows/build_one_arch.yml
+++ b/.github/workflows/build_one_arch.yml
@@ -271,7 +271,7 @@ jobs:
           ref: ${{github.event.pull_request.head.ref}}
           repository: ${{github.event.pull_request.head.repo.full_name}}
 
-      - uses: actions/download-artifact@v4
+      - uses: actions/download-artifact@v5
         with:
           path: ./
           pattern: firmware-${{inputs.arch}}-*
@@ -300,7 +300,7 @@ jobs:
             ./Meshtastic_nRF52_factory_erase*.uf2
           retention-days: 30
 
-      - uses: actions/download-artifact@v4
+      - uses: actions/download-artifact@v5
         with:
           name: firmware-${{inputs.arch}}-${{ needs.version.outputs.long }}
           merge-multiple: true
@@ -364,14 +364,14 @@ jobs:
             Autogenerated by github action, developer should edit as required before publishing...
 
       - name: Download source deb
-        uses: actions/download-artifact@v4
+        uses: actions/download-artifact@v5
         with:
           pattern: firmware-debian-${{ needs.version.outputs.deb }}~UNRELEASED-src
           merge-multiple: true
           path: ./output/debian-src
 
       - name: Download `native-tft` pio deps
-        uses: actions/download-artifact@v4
+        uses: actions/download-artifact@v5
         with:
           pattern: platformio-deps-native-tft-${{ needs.version.outputs.long }}
           merge-multiple: true
@@ -421,7 +421,7 @@ jobs:
         with:
           python-version: 3.x
 
-      - uses: actions/download-artifact@v4
+      - uses: actions/download-artifact@v5
         with:
           pattern: firmware-${{inputs.arch}}-${{ needs.version.outputs.long }}
           merge-multiple: true
@@ -438,7 +438,7 @@ jobs:
       - name: Zip firmware
         run: zip -j -9 -r ./firmware-${{inputs.arch}}-${{ needs.version.outputs.long }}.zip ./output
 
-      - uses: actions/download-artifact@v4
+      - uses: actions/download-artifact@v5
         with:
           name: debug-elfs-${{inputs.arch}}-${{ needs.version.outputs.long }}.zip
           merge-multiple: true
@@ -476,7 +476,7 @@ jobs:
         with:
           python-version: 3.x
 
-      - uses: actions/download-artifact@v4
+      - uses: actions/download-artifact@v5
         with:
           pattern: firmware-{${{ env.targets }}}-${{ needs.version.outputs.long }}
           merge-multiple: true
diff --git a/.github/workflows/build_one_target.yml b/.github/workflows/build_one_target.yml
index f5831ed52..4bf5dfe74 100644
--- a/.github/workflows/build_one_target.yml
+++ b/.github/workflows/build_one_target.yml
@@ -176,7 +176,7 @@ jobs:
           ref: ${{github.event.pull_request.head.ref}}
           repository: ${{github.event.pull_request.head.repo.full_name}}
 
-      - uses: actions/download-artifact@v4
+      - uses: actions/download-artifact@v5
         with:
           path: ./
           pattern: firmware-*-*
@@ -205,7 +205,7 @@ jobs:
             ./Meshtastic_nRF52_factory_erase*.uf2
           retention-days: 30
 
-      - uses: actions/download-artifact@v4
+      - uses: actions/download-artifact@v5
         with:
           pattern: firmware-*-${{ needs.version.outputs.long }}
           merge-multiple: true
@@ -269,14 +269,14 @@ jobs:
             Autogenerated by github action, developer should edit as required before publishing...
 
       - name: Download source deb
-        uses: actions/download-artifact@v4
+        uses: actions/download-artifact@v5
         with:
           pattern: firmware-debian-${{ needs.version.outputs.deb }}~UNRELEASED-src
           merge-multiple: true
           path: ./output/debian-src
 
       - name: Download `native-tft` pio deps
-        uses: actions/download-artifact@v4
+        uses: actions/download-artifact@v5
         with:
           pattern: platformio-deps-native-tft-${{ needs.version.outputs.long }}
           merge-multiple: true
@@ -316,7 +316,7 @@ jobs:
         with:
           python-version: 3.x
 
-      - uses: actions/download-artifact@v4
+      - uses: actions/download-artifact@v5
         with:
           pattern: firmware-*-${{ needs.version.outputs.long }}
           merge-multiple: true
@@ -333,7 +333,7 @@ jobs:
       - name: Zip firmware
         run: zip -j -9 -r ./firmware-${{inputs.target}}-${{ needs.version.outputs.long }}.zip ./output
 
-      - uses: actions/download-artifact@v4
+      - uses: actions/download-artifact@v5
         with:
           pattern: debug-elfs-*-${{ needs.version.outputs.long }}.zip
           merge-multiple: true
@@ -371,7 +371,7 @@ jobs:
         with:
           python-version: 3.x
 
-      - uses: actions/download-artifact@v4
+      - uses: actions/download-artifact@v5
         with:
           pattern: firmware-{${{ env.targets }}}-${{ needs.version.outputs.long }}
           merge-multiple: true

From 7821919faea62d751352d8859531d5a35caa17a3 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Thu, 18 Sep 2025 20:12:28 -0500
Subject: [PATCH 259/691] Update actions/setup-python action to v6 (#8033)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
 .github/workflows/build_one_arch.yml   | 8 ++++----
 .github/workflows/build_one_target.yml | 8 ++++----
 2 files changed, 8 insertions(+), 8 deletions(-)

diff --git a/.github/workflows/build_one_arch.yml b/.github/workflows/build_one_arch.yml
index 3b11a9758..f5352b3c4 100644
--- a/.github/workflows/build_one_arch.yml
+++ b/.github/workflows/build_one_arch.yml
@@ -23,7 +23,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
@@ -348,7 +348,7 @@ jobs:
         uses: actions/checkout@v5
 
       - name: Setup Python
-        uses: actions/setup-python@v5
+        uses: actions/setup-python@v6
         with:
           python-version: 3.x
 
@@ -417,7 +417,7 @@ jobs:
         uses: actions/checkout@v5
 
       - name: Setup Python
-        uses: actions/setup-python@v5
+        uses: actions/setup-python@v6
         with:
           python-version: 3.x
 
@@ -472,7 +472,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/build_one_target.yml b/.github/workflows/build_one_target.yml
index 4bf5dfe74..3c83ce960 100644
--- a/.github/workflows/build_one_target.yml
+++ b/.github/workflows/build_one_target.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
@@ -253,7 +253,7 @@ jobs:
         uses: actions/checkout@v5
 
       - name: Setup Python
-        uses: actions/setup-python@v5
+        uses: actions/setup-python@v6
         with:
           python-version: 3.x
 
@@ -312,7 +312,7 @@ jobs:
         uses: actions/checkout@v5
 
       - name: Setup Python
-        uses: actions/setup-python@v5
+        uses: actions/setup-python@v6
         with:
           python-version: 3.x
 
@@ -367,7 +367,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 dcd53eb7cb61a751903851215551b037e28c3301 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 260/691] 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 e00b19b2f..cd88d2f3d 100644
--- a/src/graphics/draw/UIRenderer.cpp
+++ b/src/graphics/draw/UIRenderer.cpp
@@ -937,7 +937,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 {
@@ -954,6 +973,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);
     }
 
@@ -1013,6 +1033,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 39648e609a6229e89571d1b1495d1c8632fd06f5 Mon Sep 17 00:00:00 2001
From: Ben Meadors 
Date: Tue, 16 Sep 2025 13:11:18 -0500
Subject: [PATCH 261/691] Merge pull request #8004 from
 compumike/compumike/debug-heap-add-free-heap-debugging-to-all-log-lines

When `DEBUG_HEAP` is defined, add free heap bytes to every log line in `RedirectablePrint::log_to_serial`
---
 platformio.ini            |  1 +
 src/RedirectablePrint.cpp | 11 +++++++++++
 2 files changed, 12 insertions(+)

diff --git a/platformio.ini b/platformio.ini
index 42453a33e..941e33beb 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -55,6 +55,7 @@ build_flags = -Wno-missing-field-initializers
 	-D MAX_THREADS=40 ; As we've split modules, we have more threads to manage
 	#-DBUILD_EPOCH=$UNIX_TIME ; set in platformio-custom.py now
 	#-D OLED_PL=1
+	#-D DEBUG_HEAP=1 ; uncomment to add free heap space / memory leak debugging logs
 
 monitor_speed = 115200
 monitor_filters = direct
diff --git a/src/RedirectablePrint.cpp b/src/RedirectablePrint.cpp
index 7c8d77651..1a5fd6e6f 100644
--- a/src/RedirectablePrint.cpp
+++ b/src/RedirectablePrint.cpp
@@ -4,6 +4,7 @@
 #include "concurrency/OSThread.h"
 #include "configuration.h"
 #include "main.h"
+#include "memGet.h"
 #include "mesh/generated/meshtastic/mesh.pb.h"
 #include 
 #include 
@@ -166,6 +167,16 @@ void RedirectablePrint::log_to_serial(const char *logLevel, const char *format,
         print(thread->ThreadName);
         print("] ");
     }
+
+#ifdef DEBUG_HEAP
+    // Add heap free space bytes prefix before every log message
+#ifdef ARCH_PORTDUINO
+    ::printf("[heap %u] ", memGet.getFreeHeap());
+#else
+    printf("[heap %u] ", memGet.getFreeHeap());
+#endif
+#endif // DEBUG_HEAP
+
     r += vprintf(logLevel, format, arg);
 }
 

From 2bafac242efe0257b7dae5d07f1143a6d07e587b Mon Sep 17 00:00:00 2001
From: Michael 
Date: Tue, 16 Sep 2025 02:29:47 +0200
Subject: [PATCH 262/691] Feature: Seamless Cross-Preset Communication via UDP
 Multicast Bridging (#7753)

* Added compatibility between nodes on different Presets through `Mesh via UDP`

* Optimize multicast handling and channel mapping

- FloodingRouter: remove redundant UDP-encrypted rebroadcast suppression.
- Router: guard multicast fallback with HAS_UDP_MULTICAST and map fallback-decoded packets
  to the local default channel via isDefaultChannel()
- UdpMulticastHandler: set transport_mechanism only after successful decode

* trunk fmt

* Move setting transport mechanism.

---------

Co-authored-by: GUVWAF 
---
 src/mesh/Channels.cpp                 | 26 +++++++++++++++++++++++
 src/mesh/Channels.h                   |  2 ++
 src/mesh/Router.cpp                   | 30 +++++++++++++++++++++++++++
 src/mesh/udp/UdpMulticastHandler.h    |  2 +-
 variants/esp32s3/t-deck-pro/variant.h | 15 +++++++-------
 5 files changed, 66 insertions(+), 9 deletions(-)

diff --git a/src/mesh/Channels.cpp b/src/mesh/Channels.cpp
index 4ef41ddfb..4c0a0edad 100644
--- a/src/mesh/Channels.cpp
+++ b/src/mesh/Channels.cpp
@@ -423,6 +423,32 @@ bool Channels::decryptForHash(ChannelIndex chIndex, ChannelHash channelHash)
     }
 }
 
+bool Channels::setDefaultPresetCryptoForHash(ChannelHash channelHash)
+{
+    // Iterate all known presets
+    for (int preset = _meshtastic_Config_LoRaConfig_ModemPreset_MIN; preset <= _meshtastic_Config_LoRaConfig_ModemPreset_MAX;
+         ++preset) {
+        const char *name = DisplayFormatters::getModemPresetDisplayName((meshtastic_Config_LoRaConfig_ModemPreset)preset, false);
+        if (!name)
+            continue;
+        if (strcmp(name, "Invalid") == 0)
+            continue; // skip invalid placeholder
+        uint8_t h = xorHash((const uint8_t *)name, strlen(name));
+        // Expand default PSK alias 1 to actual bytes and xor into hash
+        uint8_t tmp = h ^ xorHash(defaultpsk, sizeof(defaultpsk));
+        if (tmp == channelHash) {
+            // Set crypto to defaultpsk and report success
+            CryptoKey k;
+            memcpy(k.bytes, defaultpsk, sizeof(defaultpsk));
+            k.length = sizeof(defaultpsk);
+            crypto->setKey(k);
+            LOG_INFO("Matched default preset '%s' for hash 0x%x; set default PSK", name, channelHash);
+            return true;
+        }
+    }
+    return false;
+}
+
 /** Given a channel index setup crypto for encoding that channel (or the primary channel if that channel is unsecured)
  *
  * This method is called before encoding outbound packets
diff --git a/src/mesh/Channels.h b/src/mesh/Channels.h
index 7873a306a..b53f552fa 100644
--- a/src/mesh/Channels.h
+++ b/src/mesh/Channels.h
@@ -94,6 +94,8 @@ class Channels
 
     bool ensureLicensedOperation();
 
+    bool setDefaultPresetCryptoForHash(ChannelHash channelHash);
+
   private:
     /** Given a channel index, change to use the crypto key specified by that index
      *
diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp
index 6c5d08a93..8bfa7ae9f 100644
--- a/src/mesh/Router.cpp
+++ b/src/mesh/Router.cpp
@@ -430,6 +430,36 @@ DecodeState perhapsDecode(meshtastic_MeshPacket *p)
             }
         }
     }
+
+#if HAS_UDP_MULTICAST
+    // Fallback: for UDP multicast, try default preset names with default PSK if normal channel match failed
+    if (!decrypted && p->transport_mechanism == meshtastic_MeshPacket_TransportMechanism_TRANSPORT_MULTICAST_UDP) {
+        if (channels.setDefaultPresetCryptoForHash(p->channel)) {
+            memcpy(bytes, p->encrypted.bytes, rawSize);
+            crypto->decrypt(p->from, p->id, rawSize, bytes);
+
+            meshtastic_Data decodedtmp;
+            memset(&decodedtmp, 0, sizeof(decodedtmp));
+            if (pb_decode_from_bytes(bytes, rawSize, &meshtastic_Data_msg, &decodedtmp) &&
+                decodedtmp.portnum != meshtastic_PortNum_UNKNOWN_APP) {
+                p->decoded = decodedtmp;
+                p->which_payload_variant = meshtastic_MeshPacket_decoded_tag;
+                // Map to our local default channel index (name+PSK default), not necessarily primary
+                ChannelIndex defaultIndex = channels.getPrimaryIndex();
+                for (ChannelIndex i = 0; i < channels.getNumChannels(); ++i) {
+                    if (channels.isDefaultChannel(i)) {
+                        defaultIndex = i;
+                        break;
+                    }
+                }
+                chIndex = defaultIndex;
+                decrypted = true;
+            } else {
+                LOG_WARN("UDP fallback decode attempted but failed for hash 0x%x", p->channel);
+            }
+        }
+    }
+#endif
     if (decrypted) {
         // parsing was successful
         p->channel = chIndex; // change to store the index instead of the hash
diff --git a/src/mesh/udp/UdpMulticastHandler.h b/src/mesh/udp/UdpMulticastHandler.h
index 9650668a8..2df8686a3 100644
--- a/src/mesh/udp/UdpMulticastHandler.h
+++ b/src/mesh/udp/UdpMulticastHandler.h
@@ -50,10 +50,10 @@ class UdpMulticastHandler final
         LOG_DEBUG("UDP broadcast from: %s, len=%u", packet.remoteIP().toString().c_str(), packetLength);
 #endif
         meshtastic_MeshPacket mp;
-        mp.transport_mechanism = meshtastic_MeshPacket_TransportMechanism_TRANSPORT_MULTICAST_UDP;
         LOG_DEBUG("Decoding MeshPacket from UDP len=%u", packetLength);
         bool isPacketDecoded = pb_decode_from_bytes(packet.data(), packetLength, &meshtastic_MeshPacket_msg, &mp);
         if (isPacketDecoded && router && mp.which_payload_variant == meshtastic_MeshPacket_encrypted_tag) {
+            mp.transport_mechanism = meshtastic_MeshPacket_TransportMechanism_TRANSPORT_MULTICAST_UDP;
             mp.pki_encrypted = false;
             mp.public_key.size = 0;
             memset(mp.public_key.bytes, 0, sizeof(mp.public_key.bytes));
diff --git a/variants/esp32s3/t-deck-pro/variant.h b/variants/esp32s3/t-deck-pro/variant.h
index abe0a772a..35cb99435 100644
--- a/variants/esp32s3/t-deck-pro/variant.h
+++ b/variants/esp32s3/t-deck-pro/variant.h
@@ -93,11 +93,10 @@
 // Internally the TTGO module hooks the SX1262-DIO2 in to control the TX/RX switch (which is the default for the sx1262interface
 // code)
 
-#define MODEM_POWER_EN  41
-#define MODEM_PWRKEY    40
-#define MODEM_RST  9
-#define MODEM_RI  7
-#define MODEM_DTR  8
-#define MODEM_RX  10
-#define MODEM_TX  11
-
+#define MODEM_POWER_EN 41
+#define MODEM_PWRKEY 40
+#define MODEM_RST 9
+#define MODEM_RI 7
+#define MODEM_DTR 8
+#define MODEM_RX 10
+#define MODEM_TX 11

From 68ba3b315c5d7e5b6b35764330b865d1343a1fa1 Mon Sep 17 00:00:00 2001
From: Jonathan Bennett 
Date: Wed, 17 Sep 2025 08:37:51 -0500
Subject: [PATCH 263/691] Auto-favorite remote admin node

---
 src/modules/AdminModule.cpp | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp
index 407003f7e..79ea7bc0c 100644
--- a/src/modules/AdminModule.cpp
+++ b/src/modules/AdminModule.cpp
@@ -104,6 +104,10 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta
             (config.security.admin_key[2].size == 32 &&
              memcmp(mp.public_key.bytes, config.security.admin_key[2].bytes, 32) == 0)) {
             LOG_INFO("PKC admin payload with authorized sender key");
+            auto remoteNode = nodeDB->getMeshNode(mp.from);
+            if (remoteNode && !remoteNode->is_favorite) {
+                remoteNode->is_favorite = true;
+            }
         } else {
             myReply = allocErrorResponse(meshtastic_Routing_Error_ADMIN_PUBLIC_KEY_UNAUTHORIZED, &mp);
             LOG_INFO("Received PKC admin payload, but the sender public key does not match the admin authorized key!");

From b14e5770d540512f46cfa93fc046f9d2cdd8f804 Mon Sep 17 00:00:00 2001
From: Ben Meadors 
Date: Fri, 12 Sep 2025 13:11:53 -0500
Subject: [PATCH 264/691] Merge pull request #7873 from
 compumike/compumike/client-base-role

Add `CLIENT_BASE` role: `ROUTER` for favorites, `CLIENT` otherwise (for attic/roof nodes!)
---
 src/DisplayFormatters.cpp           | 45 ++++++++++++++++++++++
 src/graphics/Screen.cpp             |  8 ++--
 src/mesh/FloodingRouter.cpp         | 26 +++++++++++--
 src/mesh/FloodingRouter.h           |  4 ++
 src/mesh/NextHopRouter.cpp          | 13 +++++--
 src/mesh/NextHopRouter.h            |  3 ++
 src/mesh/NodeDB.cpp                 | 59 +++++++++++++++++++++++++++++
 src/mesh/NodeDB.h                   | 10 +++++
 src/mesh/RadioInterface.cpp         | 23 +++++++++--
 src/mesh/RadioInterface.h           |  5 ++-
 src/mesh/RadioLibInterface.cpp      |  8 ++--
 src/mesh/RadioLibInterface.h        |  2 +-
 src/platform/portduino/SimRadio.cpp |  6 +--
 src/platform/portduino/SimRadio.h   |  2 +-
 14 files changed, 191 insertions(+), 23 deletions(-)

diff --git a/src/DisplayFormatters.cpp b/src/DisplayFormatters.cpp
index d367aa661..5193e1cb4 100644
--- a/src/DisplayFormatters.cpp
+++ b/src/DisplayFormatters.cpp
@@ -38,4 +38,49 @@ 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_CLIENT_BASE:
+        return "Client Base";
+        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/graphics/Screen.cpp b/src/graphics/Screen.cpp
index 5bddb6cc8..807cb5867 100644
--- a/src/graphics/Screen.cpp
+++ b/src/graphics/Screen.cpp
@@ -25,6 +25,7 @@ along with this program.  If not, see .
 #include "PowerMon.h"
 #include "Throttle.h"
 #include "configuration.h"
+#include "meshUtils.h"
 #if HAS_SCREEN
 #include 
 
@@ -58,7 +59,6 @@ along with this program.  If not, see .
 #include "mesh-pb-constants.h"
 #include "mesh/Channels.h"
 #include "mesh/generated/meshtastic/deviceonly.pb.h"
-#include "meshUtils.h"
 #include "modules/ExternalNotificationModule.h"
 #include "modules/TextMessageModule.h"
 #include "modules/WaypointModule.h"
@@ -1562,13 +1562,15 @@ bool shouldWakeOnReceivedMessage()
     /*
     The goal here is to determine when we do NOT wake up the screen on message received:
     - Any ext. notifications are turned on
-    - If role is not client / client_mute
+    - If role is not CLIENT / CLIENT_MUTE / CLIENT_HIDDEN / CLIENT_BASE
     - If the battery level is very low
     */
     if (moduleConfig.external_notification.enabled) {
         return false;
     }
-    if (!meshtastic_Config_DeviceConfig_Role_CLIENT && !meshtastic_Config_DeviceConfig_Role_CLIENT_MUTE) {
+    if (!IS_ONE_OF(config.device.role, meshtastic_Config_DeviceConfig_Role_CLIENT,
+                   meshtastic_Config_DeviceConfig_Role_CLIENT_MUTE, meshtastic_Config_DeviceConfig_Role_CLIENT_HIDDEN,
+                   meshtastic_Config_DeviceConfig_Role_CLIENT_BASE)) {
         return false;
     }
     if (powerStatus && powerStatus->getBatteryChargePercent() < 10) {
diff --git a/src/mesh/FloodingRouter.cpp b/src/mesh/FloodingRouter.cpp
index dbd458b61..f805055c8 100644
--- a/src/mesh/FloodingRouter.cpp
+++ b/src/mesh/FloodingRouter.cpp
@@ -43,12 +43,30 @@ bool FloodingRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
     return Router::shouldFilterReceived(p);
 }
 
+bool FloodingRouter::roleAllowsCancelingDupe(const meshtastic_MeshPacket *p)
+{
+    if (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER ||
+        config.device.role == meshtastic_Config_DeviceConfig_Role_REPEATER ||
+        config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER_LATE) {
+        // ROUTER, REPEATER, ROUTER_LATE should never cancel relaying a packet (i.e. we should always rebroadcast),
+        // even if we've heard another station rebroadcast it already.
+        return false;
+    }
+
+    if (config.device.role == meshtastic_Config_DeviceConfig_Role_CLIENT_BASE) {
+        // CLIENT_BASE: if the packet is from or to a favorited node,
+        // we should act like a ROUTER and should never cancel a rebroadcast (i.e. we should always rebroadcast),
+        // even if we've heard another station rebroadcast it already.
+        return !nodeDB->isFromOrToFavoritedNode(*p);
+    }
+
+    // All other roles (such as CLIENT) should cancel a rebroadcast if they hear another station's rebroadcast.
+    return true;
+}
+
 void FloodingRouter::perhapsCancelDupe(const meshtastic_MeshPacket *p)
 {
-    if (config.device.role != meshtastic_Config_DeviceConfig_Role_ROUTER &&
-        config.device.role != meshtastic_Config_DeviceConfig_Role_REPEATER &&
-        config.device.role != meshtastic_Config_DeviceConfig_Role_ROUTER_LATE &&
-        p->transport_mechanism == meshtastic_MeshPacket_TransportMechanism_TRANSPORT_LORA) {
+    if (p->transport_mechanism == meshtastic_MeshPacket_TransportMechanism_TRANSPORT_LORA && roleAllowsCancelingDupe(p)) {
         // cancel rebroadcast of this message *if* there was already one, unless we're a router/repeater!
         // But only LoRa packets should be able to trigger this.
         if (Router::cancelSending(p->from, p->id))
diff --git a/src/mesh/FloodingRouter.h b/src/mesh/FloodingRouter.h
index 36c6ad8aa..30ad5945b 100644
--- a/src/mesh/FloodingRouter.h
+++ b/src/mesh/FloodingRouter.h
@@ -59,6 +59,10 @@ class FloodingRouter : public Router
      */
     virtual void sniffReceived(const meshtastic_MeshPacket *p, const meshtastic_Routing *c) override;
 
+    // Return false for roles like ROUTER or REPEATER which should always rebroadcast even when we've heard another rebroadcast of
+    // the same packet
+    bool roleAllowsCancelingDupe(const meshtastic_MeshPacket *p);
+
     /* Call when receiving a duplicate packet to check whether we should cancel a packet in the Tx queue */
     void perhapsCancelDupe(const meshtastic_MeshPacket *p);
 
diff --git a/src/mesh/NextHopRouter.cpp b/src/mesh/NextHopRouter.cpp
index db3d62038..9bb8b240c 100644
--- a/src/mesh/NextHopRouter.cpp
+++ b/src/mesh/NextHopRouter.cpp
@@ -161,6 +161,15 @@ bool NextHopRouter::stopRetransmission(NodeNum from, PacketId id)
     return stopRetransmission(key);
 }
 
+bool NextHopRouter::roleAllowsCancelingFromTxQueue(const meshtastic_MeshPacket *p)
+{
+    // Return true if we're allowed to cancel a packet in the txQueue (so we may never transmit it even once)
+
+    // Return false for roles like ROUTER, REPEATER, ROUTER_LATE which should always transmit the packet at least once.
+
+    return roleAllowsCancelingDupe(p); // same logic as FloodingRouter::roleAllowsCancelingDupe
+}
+
 bool NextHopRouter::stopRetransmission(GlobalPacketId key)
 {
     auto old = findPendingPacket(key);
@@ -170,9 +179,7 @@ bool NextHopRouter::stopRetransmission(GlobalPacketId key)
           to avoid canceling a transmission if it was ACKed super fast via MQTT */
         if (old->numRetransmissions < NUM_RELIABLE_RETX - 1) {
             // We only cancel it if we are the original sender or if we're not a router(_late)/repeater
-            if (isFromUs(p) || (config.device.role != meshtastic_Config_DeviceConfig_Role_ROUTER &&
-                                config.device.role != meshtastic_Config_DeviceConfig_Role_REPEATER &&
-                                config.device.role != meshtastic_Config_DeviceConfig_Role_ROUTER_LATE)) {
+            if (isFromUs(p) || roleAllowsCancelingFromTxQueue(p)) {
                 // remove the 'original' (identified by originator and packet->id) from the txqueue and free it
                 cancelSending(getFrom(p), p->id);
             }
diff --git a/src/mesh/NextHopRouter.h b/src/mesh/NextHopRouter.h
index 6c2764aff..0022644e9 100644
--- a/src/mesh/NextHopRouter.h
+++ b/src/mesh/NextHopRouter.h
@@ -121,6 +121,9 @@ class NextHopRouter : public FloodingRouter
      */
     PendingPacket *startRetransmission(meshtastic_MeshPacket *p, uint8_t numReTx = NUM_INTERMEDIATE_RETX);
 
+    // Return true if we're allowed to cancel a packet in the txQueue (so we may never transmit it even once)
+    bool roleAllowsCancelingFromTxQueue(const meshtastic_MeshPacket *p);
+
     /**
      * Stop any retransmissions we are doing of the specified node/packet ID pair
      *
diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp
index 6473722d7..65d2f4d1c 100644
--- a/src/mesh/NodeDB.cpp
+++ b/src/mesh/NodeDB.cpp
@@ -1750,6 +1750,65 @@ void NodeDB::set_favorite(bool is_favorite, uint32_t nodeId)
     }
 }
 
+bool NodeDB::isFavorite(uint32_t nodeId)
+{
+    // returns true if nodeId is_favorite; false if not or not found
+
+    // NODENUM_BROADCAST will never be in the DB
+    if (nodeId == NODENUM_BROADCAST)
+        return false;
+
+    meshtastic_NodeInfoLite *lite = getMeshNode(nodeId);
+
+    if (lite) {
+        return lite->is_favorite;
+    }
+    return false;
+}
+
+bool NodeDB::isFromOrToFavoritedNode(const meshtastic_MeshPacket &p)
+{
+    // This method is logically equivalent to:
+    //   return isFavorite(p.from) || isFavorite(p.to);
+    // but is more efficient by:
+    //   1. doing only one pass through the database, instead of two
+    //   2. exiting early when a favorite is found, or if both from and to have been seen
+
+    if (p.to == NODENUM_BROADCAST)
+        return isFavorite(p.from); // we never store NODENUM_BROADCAST in the DB, so we only need to check p.from
+
+    meshtastic_NodeInfoLite *lite = NULL;
+
+    bool seenFrom = false;
+    bool seenTo = false;
+
+    for (int i = 0; i < numMeshNodes; i++) {
+        lite = &meshNodes->at(i);
+
+        if (lite->num == p.from) {
+            if (lite->is_favorite)
+                return true;
+
+            seenFrom = true;
+        }
+
+        if (lite->num == p.to) {
+            if (lite->is_favorite)
+                return true;
+
+            seenTo = true;
+        }
+
+        if (seenFrom && seenTo)
+            return false; // we've seen both, and neither is a favorite, so we can stop searching early
+
+        // Note: if we knew that sortMeshDB was always called after any change to is_favorite, we could exit early after searching
+        // all favorited nodes first.
+    }
+
+    return false;
+}
+
 void NodeDB::pause_sort(bool paused)
 {
     sortingIsPaused = paused;
diff --git a/src/mesh/NodeDB.h b/src/mesh/NodeDB.h
index 167dc1337..f73f64f92 100644
--- a/src/mesh/NodeDB.h
+++ b/src/mesh/NodeDB.h
@@ -185,6 +185,16 @@ class NodeDB
      */
     void set_favorite(bool is_favorite, uint32_t nodeId);
 
+    /*
+     * Returns true if the node is in the NodeDB and marked as favorite
+     */
+    bool isFavorite(uint32_t nodeId);
+
+    /*
+     * Returns true if p->from or p->to is a favorited node
+     */
+    bool isFromOrToFavoritedNode(const meshtastic_MeshPacket &p);
+
     /**
      * Other functions like the node picker can request a pause in the node sorting
      */
diff --git a/src/mesh/RadioInterface.cpp b/src/mesh/RadioInterface.cpp
index a5c293868..71fcf1e74 100644
--- a/src/mesh/RadioInterface.cpp
+++ b/src/mesh/RadioInterface.cpp
@@ -311,16 +311,33 @@ uint32_t RadioInterface::getTxDelayMsecWeightedWorst(float snr)
     return (2 * CWmax * slotTimeMsec) + pow_of_2(CWsize) * slotTimeMsec;
 }
 
+/** Returns true if we should rebroadcast early like a ROUTER */
+bool RadioInterface::shouldRebroadcastEarlyLikeRouter(meshtastic_MeshPacket *p)
+{
+    // If we are a ROUTER or REPEATER, we always rebroadcast early
+    if (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER ||
+        config.device.role == meshtastic_Config_DeviceConfig_Role_REPEATER) {
+        return true;
+    }
+
+    // If we are a CLIENT_BASE and the packet is from or to a favorited node, we should rebroadcast early
+    if (config.device.role == meshtastic_Config_DeviceConfig_Role_CLIENT_BASE) {
+        return nodeDB->isFromOrToFavoritedNode(*p);
+    }
+
+    return false;
+}
+
 /** The delay to use when we want to flood a message */
-uint32_t RadioInterface::getTxDelayMsecWeighted(float snr)
+uint32_t RadioInterface::getTxDelayMsecWeighted(meshtastic_MeshPacket *p)
 {
     //  high SNR = large CW size (Long Delay)
     //  low SNR = small CW size (Short Delay)
+    float snr = p->rx_snr;
     uint32_t delay = 0;
     uint8_t CWsize = getCWsize(snr);
     // LOG_DEBUG("rx_snr of %f so setting CWsize to:%d", snr, CWsize);
-    if (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER ||
-        config.device.role == meshtastic_Config_DeviceConfig_Role_REPEATER) {
+    if (shouldRebroadcastEarlyLikeRouter(p)) {
         delay = random(0, 2 * CWsize) * slotTimeMsec;
         LOG_DEBUG("rx_snr found in packet. Router: setting tx delay:%d", delay);
     } else {
diff --git a/src/mesh/RadioInterface.h b/src/mesh/RadioInterface.h
index c9e71cfa8..eff284747 100644
--- a/src/mesh/RadioInterface.h
+++ b/src/mesh/RadioInterface.h
@@ -180,8 +180,11 @@ class RadioInterface
     /** The worst-case SNR_based packet delay */
     uint32_t getTxDelayMsecWeightedWorst(float snr);
 
+    /** Returns true if we should rebroadcast early like a ROUTER */
+    bool shouldRebroadcastEarlyLikeRouter(meshtastic_MeshPacket *p);
+
     /** The delay to use when we want to flood a message. Use a weighted scale based on SNR */
-    uint32_t getTxDelayMsecWeighted(float snr);
+    uint32_t getTxDelayMsecWeighted(meshtastic_MeshPacket *p);
 
     /** If the packet is not already in the late rebroadcast window, move it there */
     virtual void clampToLateRebroadcastWindow(NodeNum from, PacketId id) { return; }
diff --git a/src/mesh/RadioLibInterface.cpp b/src/mesh/RadioLibInterface.cpp
index e3ef58f14..56341adf9 100644
--- a/src/mesh/RadioLibInterface.cpp
+++ b/src/mesh/RadioLibInterface.cpp
@@ -310,7 +310,7 @@ void RadioLibInterface::setTransmitDelay()
     // So we want to make sure the other side has had a chance to reconfigure its radio.
 
     if (p->tx_after) {
-        unsigned long add_delay = p->rx_rssi ? getTxDelayMsecWeighted(p->rx_snr) : getTxDelayMsec();
+        unsigned long add_delay = p->rx_rssi ? getTxDelayMsecWeighted(p) : getTxDelayMsec();
         unsigned long now = millis();
         p->tx_after = min(max(p->tx_after + add_delay, now + add_delay), now + 2 * getTxDelayMsecWeightedWorst(p->rx_snr));
         notifyLater(p->tx_after - now, TRANSMIT_DELAY_COMPLETED, false);
@@ -323,7 +323,7 @@ void RadioLibInterface::setTransmitDelay()
     } else {
         // If there is a SNR, start a timer scaled based on that SNR.
         LOG_DEBUG("rx_snr found. hop_limit:%d rx_snr:%f", p->hop_limit, p->rx_snr);
-        startTransmitTimerSNR(p->rx_snr);
+        startTransmitTimerRebroadcast(p);
     }
 }
 
@@ -336,11 +336,11 @@ void RadioLibInterface::startTransmitTimer(bool withDelay)
     }
 }
 
-void RadioLibInterface::startTransmitTimerSNR(float snr)
+void RadioLibInterface::startTransmitTimerRebroadcast(meshtastic_MeshPacket *p)
 {
     // If we have work to do and the timer wasn't already scheduled, schedule it now
     if (!txQueue.empty()) {
-        uint32_t delay = getTxDelayMsecWeighted(snr);
+        uint32_t delay = getTxDelayMsecWeighted(p);
         notifyLater(delay, TRANSMIT_DELAY_COMPLETED, false); // This will implicitly enable
     }
 }
diff --git a/src/mesh/RadioLibInterface.h b/src/mesh/RadioLibInterface.h
index 2ab2679c0..9f497812f 100644
--- a/src/mesh/RadioLibInterface.h
+++ b/src/mesh/RadioLibInterface.h
@@ -161,7 +161,7 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified
      * timer scaled to SNR of to be flooded packet
      * @return Timestamp after which the packet may be sent
      */
-    void startTransmitTimerSNR(float snr);
+    void startTransmitTimerRebroadcast(meshtastic_MeshPacket *p);
 
     void handleTransmitInterrupt();
     void handleReceiveInterrupt();
diff --git a/src/platform/portduino/SimRadio.cpp b/src/platform/portduino/SimRadio.cpp
index 4e748c5f9..cea1eab3a 100644
--- a/src/platform/portduino/SimRadio.cpp
+++ b/src/platform/portduino/SimRadio.cpp
@@ -43,7 +43,7 @@ void SimRadio::setTransmitDelay()
     } else {
         // If there is a SNR, start a timer scaled based on that SNR.
         LOG_DEBUG("rx_snr found. hop_limit:%d rx_snr:%f", p->hop_limit, p->rx_snr);
-        startTransmitTimerSNR(p->rx_snr);
+        startTransmitTimerRebroadcast(p);
     }
 }
 
@@ -57,11 +57,11 @@ void SimRadio::startTransmitTimer(bool withDelay)
     }
 }
 
-void SimRadio::startTransmitTimerSNR(float snr)
+void SimRadio::startTransmitTimerRebroadcast(meshtastic_MeshPacket *p)
 {
     // If we have work to do and the timer wasn't already scheduled, schedule it now
     if (!txQueue.empty()) {
-        uint32_t delayMsec = getTxDelayMsecWeighted(snr);
+        uint32_t delayMsec = getTxDelayMsecWeighted(p);
         // LOG_DEBUG("xmit timer %d", delay);
         notifyLater(delayMsec, TRANSMIT_DELAY_COMPLETED, false);
     }
diff --git a/src/platform/portduino/SimRadio.h b/src/platform/portduino/SimRadio.h
index ea534bd65..d8b53739f 100644
--- a/src/platform/portduino/SimRadio.h
+++ b/src/platform/portduino/SimRadio.h
@@ -64,7 +64,7 @@ class SimRadio : public RadioInterface, protected concurrency::NotifiedWorkerThr
     void startTransmitTimer(bool withDelay = true);
 
     /** timer scaled to SNR of to be flooded packet */
-    void startTransmitTimerSNR(float snr);
+    void startTransmitTimerRebroadcast(meshtastic_MeshPacket *p);
 
     void handleTransmitInterrupt();
     void handleReceiveInterrupt();

From 901bcc24ee1a21211fde8eb8c140a9394741cd5f Mon Sep 17 00:00:00 2001
From: ford-jones 
Date: Fri, 19 Sep 2025 22:17:03 +1200
Subject: [PATCH 265/691] Reflect requirement of ESP32 hardware in rangetest
 logs

---
 src/modules/RangeTestModule.cpp | 13 +++++++++++--
 1 file changed, 11 insertions(+), 2 deletions(-)

diff --git a/src/modules/RangeTestModule.cpp b/src/modules/RangeTestModule.cpp
index 4dd95a7a3..3d78d0dc9 100644
--- a/src/modules/RangeTestModule.cpp
+++ b/src/modules/RangeTestModule.cpp
@@ -299,9 +299,14 @@ bool RangeTestModuleRadio::appendFile(const meshtastic_MeshPacket &mp)
     fileToAppend.printf("\"%s\"\n", p.payload.bytes);
     fileToAppend.flush();
     fileToAppend.close();
-#endif
 
     return 1;
+
+#else
+    LOG_ERROR("Failed to store range test results - feature only available for ESP32");
+
+    return 0;
+#endif
 }
 
 bool RangeTestModuleRadio::removeFile()
@@ -325,7 +330,11 @@ bool RangeTestModuleRadio::removeFile()
         return 0;
     }
     LOG_INFO("Range test removed.");
-#endif
 
     return 1;
+#else
+    LOG_ERROR("Failed to remove range test results - feature only available for ESP32");
+
+    return 0;
+#endif
 }
\ No newline at end of file

From e2ce36978274b7d5661ee77ff34934de2b3932cc Mon Sep 17 00:00:00 2001
From: Ben Meadors 
Date: Fri, 19 Sep 2025 06:29:18 -0500
Subject: [PATCH 266/691] Fixes

---
 src/DisplayFormatters.h | 1 +
 src/mesh/Channels.cpp   | 3 ++-
 2 files changed, 3 insertions(+), 1 deletion(-)

diff --git a/src/DisplayFormatters.h b/src/DisplayFormatters.h
index 2d7a3e8db..981010b33 100644
--- a/src/DisplayFormatters.h
+++ b/src/DisplayFormatters.h
@@ -6,4 +6,5 @@ class DisplayFormatters
   public:
     static const char *getModemPresetDisplayName(meshtastic_Config_LoRaConfig_ModemPreset preset, bool useShortName,
                                                  bool usePreset);
+    static const char *getDeviceRole(meshtastic_Config_DeviceConfig_Role role);
 };
diff --git a/src/mesh/Channels.cpp b/src/mesh/Channels.cpp
index 4c0a0edad..aec112a3e 100644
--- a/src/mesh/Channels.cpp
+++ b/src/mesh/Channels.cpp
@@ -428,7 +428,8 @@ bool Channels::setDefaultPresetCryptoForHash(ChannelHash channelHash)
     // Iterate all known presets
     for (int preset = _meshtastic_Config_LoRaConfig_ModemPreset_MIN; preset <= _meshtastic_Config_LoRaConfig_ModemPreset_MAX;
          ++preset) {
-        const char *name = DisplayFormatters::getModemPresetDisplayName((meshtastic_Config_LoRaConfig_ModemPreset)preset, false);
+        const char *name =
+            DisplayFormatters::getModemPresetDisplayName((meshtastic_Config_LoRaConfig_ModemPreset)preset, false, false);
         if (!name)
             continue;
         if (strcmp(name, "Invalid") == 0)

From 3fbe7fd8b21df6528bc8df19b7cad7458b5e32f7 Mon Sep 17 00:00:00 2001
From: Jason P 
Date: Fri, 5 Sep 2025 20:44:32 -0500
Subject: [PATCH 267/691] 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
---
 .vscode/settings.json               |  5 ++
 protobufs                           |  2 +-
 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 | 47 ++++++++++++++++--
 src/graphics/draw/DebugRenderer.h   |  3 ++
 src/graphics/draw/MenuHandler.cpp   | 77 +++++++++++++++++++++++++----
 src/graphics/draw/MenuHandler.h     |  3 ++
 src/graphics/draw/UIRenderer.cpp    | 44 ++++++++++++++++-
 src/graphics/images.h               | 72 +++++++++++++++++++++++++++
 12 files changed, 277 insertions(+), 17 deletions(-)

diff --git a/.vscode/settings.json b/.vscode/settings.json
index 81deca8f9..a54386544 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -10,5 +10,10 @@
   },
   "[powershell]": {
     "editor.defaultFormatter": "ms-vscode.powershell"
+  },
+  "files.associations": {
+    "deque": "cpp",
+    "string": "cpp",
+    "vector": "cpp"
   }
 }
diff --git a/protobufs b/protobufs
index 945b796a9..27d9a99bd 160000
--- a/protobufs
+++ b/protobufs
@@ -1 +1 @@
-Subproject commit 945b796a982f38171a9e0d28b5c8b1f7d53c5cd1
+Subproject commit 27d9a99bd03efe35f91cafd7116c2386be5e26a1
diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp
index 807cb5867..0c88ed0a6 100644
--- a/src/graphics/Screen.cpp
+++ b/src/graphics/Screen.cpp
@@ -982,6 +982,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()) {
@@ -1150,6 +1155,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
@@ -1178,6 +1186,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 85dcfaf69..262ba4175 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 13691665a..0f32b0896 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 61e919208..fb35134fd 100644
--- a/src/graphics/draw/DebugRenderer.cpp
+++ b/src/graphics/draw/DebugRenderer.cpp
@@ -395,8 +395,18 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
     int textWidth = display->getStringWidth(shortnameble);
     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) {
@@ -410,7 +420,7 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
     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();
@@ -437,7 +447,7 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
     display->drawString(nameX, getTextPositions(display)[line++], frequencyslot);
 
 #if !defined(M5STACK_UNITC6L)
-    // === Fourth Row: Channel Utilization ===
+    // === Fifth Row: Channel Utilization ===
     const char *chUtil = "ChUtil:";
     char chUtilPercentage[10];
     snprintf(chUtilPercentage, sizeof(chUtilPercentage), "%2.0f%%", airTime->channelUtilizationPercent());
@@ -454,7 +464,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) {
@@ -491,7 +501,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);
 #endif
 }
@@ -655,6 +665,33 @@ void drawSystemScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x
     }
 #endif
 }
+
+// ****************************
+// * 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 48e7e808b..975fc7c0a 100644
--- a/src/graphics/draw/MenuHandler.cpp
+++ b/src/graphics/draw/MenuHandler.cpp
@@ -28,17 +28,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);
@@ -141,6 +143,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"};
@@ -1038,16 +1074,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);
@@ -1306,7 +1359,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) {
@@ -1365,9 +1418,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 4e7e02173..1bfdf128f 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 cd88d2f3d..8df3ee517 100644
--- a/src/graphics/draw/UIRenderer.cpp
+++ b/src/graphics/draw/UIRenderer.cpp
@@ -1019,6 +1019,45 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU
         display->drawString(x, getTextPositions(display)[line++] + 2, latStr);
 #else
         snprintf(latStr, sizeof(latStr), " Lat: %.5f", geoCoord.getLatitude() * 1e-7);
+        // === 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];
+        snprintf(latStr, sizeof(latStr), "Lat: %.5f", geoCoord.getLatitude() * 1e-7);
         display->drawString(x, getTextPositions(display)[line++], latStr);
 #endif
 
@@ -1029,6 +1068,7 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU
         display->drawString(x, getTextPositions(display)[line++] + 4, lonStr);
 #else
         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 ===
@@ -1037,9 +1077,9 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU
                           ? 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);
+            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);
 #endif
diff --git a/src/graphics/images.h b/src/graphics/images.h
index fd9a2db0f..72dda7886 100644
--- a/src/graphics/images.h
+++ b/src/graphics/images.h
@@ -290,6 +290,78 @@ const uint8_t analog_icon_clock[] PROGMEM = {0b11111111, 0b01000010, 0b00100100,
 #ifdef M5STACK_UNITC6L
 #include "img/icon_small.xbm"
 #else
+#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"
 #endif
 static_assert(sizeof(icon_bits) >= 0, "Silence unused variable warning");
\ No newline at end of file

From e20a91b94542ba59645523cd1d560a8c95f18b5c 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 268/691] 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 | 55 ++++++++++++++++++++++++++------
 2 files changed, 55 insertions(+), 9 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 8df3ee517..e76a39398 100644
--- a/src/graphics/draw/UIRenderer.cpp
+++ b/src/graphics/draw/UIRenderer.cpp
@@ -1000,17 +1000,54 @@ 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) {
-
-        // === 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);
+        /* 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);
 #if !defined(M5STACK_UNITC6L)
-        display->drawString(0, getTextPositions(display)[line++], fullLine);
+            display->drawString(0, getTextPositions(display)[line++], fullLine);
 #endif
+        */
+
+        // === 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 6a92358b6883f6dc86e9c456b45662732fd8b45e Mon Sep 17 00:00:00 2001
From: Ben Meadors 
Date: Fri, 19 Sep 2025 07:22:23 -0500
Subject: [PATCH 269/691] Fix

---
 src/graphics/draw/UIRenderer.cpp | 40 --------------------------------
 1 file changed, 40 deletions(-)

diff --git a/src/graphics/draw/UIRenderer.cpp b/src/graphics/draw/UIRenderer.cpp
index e76a39398..5623c9026 100644
--- a/src/graphics/draw/UIRenderer.cpp
+++ b/src/graphics/draw/UIRenderer.cpp
@@ -1056,45 +1056,6 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU
         display->drawString(x, getTextPositions(display)[line++] + 2, latStr);
 #else
         snprintf(latStr, sizeof(latStr), " Lat: %.5f", geoCoord.getLatitude() * 1e-7);
-        // === 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];
-        snprintf(latStr, sizeof(latStr), "Lat: %.5f", geoCoord.getLatitude() * 1e-7);
         display->drawString(x, getTextPositions(display)[line++], latStr);
 #endif
 
@@ -1105,7 +1066,6 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU
         display->drawString(x, getTextPositions(display)[line++] + 4, lonStr);
 #else
         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 ===

From 1ac2382d7cad544c5685aa50a18096d1fe20aadb Mon Sep 17 00:00:00 2001
From: Ben Meadors 
Date: Fri, 19 Sep 2025 07:29:54 -0500
Subject: [PATCH 270/691] Revert "Fix excluded modules configuration handling
 (#7838)"

This reverts commit 9c6544ebfa8248412d1008f0d32c986c1dc99d38.
---
 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, 2 insertions(+), 114 deletions(-)

diff --git a/src/configuration.h b/src/configuration.h
index 515380cfb..1b386ec17 100644
--- a/src/configuration.h
+++ b/src/configuration.h
@@ -438,7 +438,6 @@ 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 d6f9fb95b..3f84a5e66 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -1513,9 +1513,6 @@ 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
@@ -1538,21 +1535,10 @@ extern meshtastic_DeviceMetadata getDeviceMetadata()
 #if NO_EXT_GPIO && NO_GPS || MESHTASTIC_EXCLUDE_SERIAL
     deviceMetadata.excluded_modules |= meshtastic_ExcludedModules_SERIAL_CONFIG;
 #endif
-#if defined(ARCH_ESP32) && !MESHTASTIC_EXCLUDE_PAXCOUNTER
-    // PAXCOUNTER is only supported on ESP32 due to memory constraints
-#else
+#ifndef ARCH_ESP32
     deviceMetadata.excluded_modules |= meshtastic_ExcludedModules_PAXCOUNTER_CONFIG;
 #endif
-#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)
+#if !defined(HAS_RGB_LED) && !RAK_4631
     deviceMetadata.excluded_modules |= meshtastic_ExcludedModules_AMBIENTLIGHTING_CONFIG;
 #endif
 
diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp
index 53a796169..237b4286c 100644
--- a/src/mesh/NodeDB.cpp
+++ b/src/mesh/NodeDB.cpp
@@ -775,9 +775,7 @@ 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;
@@ -843,12 +841,6 @@ 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
@@ -891,14 +883,12 @@ 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();
 }
@@ -1438,25 +1428,15 @@ 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 c6c3415bb..9fb1b589f 100644
--- a/src/mesh/PhoneAPI.cpp
+++ b/src/mesh/PhoneAPI.cpp
@@ -34,21 +34,6 @@
 // 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();
@@ -370,35 +355,20 @@ 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;
@@ -410,15 +380,10 @@ 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;
@@ -435,25 +400,15 @@ 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 6b4bb6fc1..0d7772d17 100644
--- a/src/mesh/PhoneAPI.h
+++ b/src/mesh/PhoneAPI.h
@@ -172,7 +172,4 @@ 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 441a6e12b..79ea7bc0c 100644
--- a/src/modules/AdminModule.cpp
+++ b/src/modules/AdminModule.cpp
@@ -1044,32 +1044,19 @@ 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");
@@ -1082,13 +1069,9 @@ 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");
@@ -1101,31 +1084,19 @@ 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 cc579dd0bdb83bf607486e9c365907b9aa4a8804 Mon Sep 17 00:00:00 2001
From: Jonathan Bennett 
Date: Wed, 3 Sep 2025 17:50:26 -0500
Subject: [PATCH 271/691] 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 1a5fd6e6f..9624a4593 100644
--- a/src/RedirectablePrint.cpp
+++ b/src/RedirectablePrint.cpp
@@ -58,7 +58,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
@@ -100,7 +100,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
@@ -299,7 +299,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 {
@@ -308,18 +308,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 a663f46c4..cc0cfca08 100644
--- a/src/gps/GPS.cpp
+++ b/src/gps/GPS.cpp
@@ -1440,7 +1440,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 0c88ed0a6..007ce56ea 100644
--- a/src/graphics/Screen.cpp
+++ b/src/graphics/Screen.cpp
@@ -340,7 +340,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);
@@ -588,7 +588,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 37ea9b94a..3eeb17ef0 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)
@@ -1240,7 +1240,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!");
@@ -1288,8 +1288,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();
@@ -1312,8 +1312,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 7620080f0..fee7c8ded 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 50a70d66e..69dcab04e 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 92db8720e..4991cabe2 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 d7e866a2a..107944dd5 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -387,7 +387,7 @@ void setup()
 #endif
     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
@@ -532,9 +532,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");
     }
@@ -586,7 +586,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);
     }
@@ -859,7 +859,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
@@ -886,7 +886,7 @@ void setup()
     defined(USE_SPISSD1306)
         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);
         }
@@ -987,13 +987,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;
@@ -1151,7 +1151,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();
     }
@@ -1167,15 +1167,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:
@@ -1192,31 +1187,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
@@ -1238,20 +1236,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);
@@ -1462,7 +1446,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 65d2f4d1c..237b4286c 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 56341adf9..0db101ce6 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 8bfa7ae9f..09fb079c5 100644
--- a/src/mesh/Router.cpp
+++ b/src/mesh/Router.cpp
@@ -490,7 +490,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
@@ -730,7 +730,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 11a6c0bd3..3e91c6820 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]
+#define MAX_RX_TOPHONE portduino_config.maxtophone
+#define MAX_NUM_NODES portduino_config.MaxNodes
diff --git a/variants/native/portduino/variant.h b/variants/native/portduino/variant.h
index 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 7b2ff7e196f8fb2c0fb1dde5997535908745ba58 Mon Sep 17 00:00:00 2001
From: "Trent V." 
Date: Sat, 13 Sep 2025 11:56:23 -0500
Subject: [PATCH 272/691] updated shebang to use a more standard path for bash
 (#7922)

Signed-off-by: Trenton VanderWert 
---
 bin/device-install.sh | 2 +-
 bin/device-update.sh  | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/bin/device-install.sh b/bin/device-install.sh
index 594f9dd6b..ede75bbba 100755
--- a/bin/device-install.sh
+++ b/bin/device-install.sh
@@ -1,4 +1,4 @@
-#!/bin/bash
+#!/usr/bin/env bash
 
 PYTHON=${PYTHON:-$(which python3 python | head -n 1)}
 BPS_RESET=false
diff --git a/bin/device-update.sh b/bin/device-update.sh
index 6f29496e9..f64280a5b 100755
--- a/bin/device-update.sh
+++ b/bin/device-update.sh
@@ -1,4 +1,4 @@
-#!/bin/bash
+#!/usr/bin/env bash
 
 PYTHON=${PYTHON:-$(which python3 python|head -n 1)}
 CHANGE_MODE=false

From a1cf305336d60bc4128121318aaf1c8d4f24d852 Mon Sep 17 00:00:00 2001
From: Jason P 
Date: Sun, 7 Sep 2025 14:34:07 -0500
Subject: [PATCH 273/691] 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           |  7 +--
 src/graphics/draw/UIRenderer.h                |  1 +
 .../Telemetry/EnvironmentTelemetry.cpp        |  3 +-
 src/modules/Telemetry/PowerTelemetry.cpp      |  3 +-
 6 files changed, 46 insertions(+), 18 deletions(-)

diff --git a/src/graphics/SharedUIDisplay.cpp b/src/graphics/SharedUIDisplay.cpp
index 0f32b0896..3937bcf50 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 d046bda6f..aec0a701f 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()) {
@@ -324,8 +322,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()) {
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 c11680fcc0d9f68d3716f46b7259c58162cdf97b Mon Sep 17 00:00:00 2001
From: Ben Meadors 
Date: Fri, 19 Sep 2025 08:37:58 -0500
Subject: [PATCH 274/691] Fix formatting and trunk issues

---
 .github/workflows/main_matrix.yml |    1 -
 src/graphics/Screen.cpp           | 1186 +++++++++++++++--------------
 src/mesh/StreamAPI.h              |    6 +-
 3 files changed, 598 insertions(+), 595 deletions(-)

diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml
index b4fa0c56e..f61e314a7 100644
--- a/.github/workflows/main_matrix.yml
+++ b/.github/workflows/main_matrix.yml
@@ -270,7 +270,6 @@ jobs:
       push: false
 
   gather-artifacts:
-    if: github.repository == 'meshtastic/firmware'
     # trunk-ignore(checkov/CKV2_GHA_1)
     if: github.repository == 'meshtastic/firmware'
     permissions:
diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp
index 6925bbc26..689c550d3 100644
--- a/src/graphics/Screen.cpp
+++ b/src/graphics/Screen.cpp
@@ -955,703 +955,707 @@ void Screen::setFrames(FrameFocus focus)
 #if defined(DISPLAY_CLOCK_FRAME)
     if (!hiddenFrames.clock) {
         if (!hiddenFrames.clock) {
-        fsi.positions.clock = numframes;
-    #if defined(M5STACK_UNITC6L)
-        normalFrames[numframes++] = graphics::ClockRenderer::drawAnalogClockFrame;
+            fsi.positions.clock = numframes;
+#if defined(M5STACK_UNITC6L)
+            normalFrames[numframes++] = graphics::ClockRenderer::drawAnalogClockFrame;
 #else
-    normalFrames[numframes++] = uiconfig.is_clockface_analog ? graphics::ClockRenderer::drawAnalogClockFrame
-                                                             : graphics::ClockRenderer::drawDigitalClockFrame;
+            normalFrames[numframes++] = uiconfig.is_clockface_analog ? graphics::ClockRenderer::drawAnalogClockFrame
+                                                                     : graphics::ClockRenderer::drawDigitalClockFrame;
 #endif
-    indicatorIcons.push_back(digital_icon_clock);
+            indicatorIcons.push_back(digital_icon_clock);
 #endif
 
-    // Declare this early so it’s available in FOCUS_PRESERVE block
-    bool willInsertTextMessage = shouldDrawMessage(&devicestate.rx_text_message);
+            // Declare this early so it’s available in FOCUS_PRESERVE block
+            bool willInsertTextMessage = shouldDrawMessage(&devicestate.rx_text_message);
 
-    if (!hiddenFrames.home) {
-        fsi.positions.home = numframes;
-        normalFrames[numframes++] = graphics::UIRenderer::drawDeviceFocused;
-        indicatorIcons.push_back(icon_home);
-    }
+            if (!hiddenFrames.home) {
+                fsi.positions.home = numframes;
+                normalFrames[numframes++] = graphics::UIRenderer::drawDeviceFocused;
+                indicatorIcons.push_back(icon_home);
+            }
 
-    fsi.positions.textMessage = numframes;
-    normalFrames[numframes++] = graphics::MessageRenderer::drawTextMessageFrame;
-    indicatorIcons.push_back(icon_mail);
+            fsi.positions.textMessage = numframes;
+            normalFrames[numframes++] = graphics::MessageRenderer::drawTextMessageFrame;
+            indicatorIcons.push_back(icon_mail);
 
 #ifndef USE_EINK
-    if (!hiddenFrames.nodelist) {
-        fsi.positions.nodelist = numframes;
-        normalFrames[numframes++] = graphics::NodeListRenderer::drawDynamicNodeListScreen;
-        indicatorIcons.push_back(icon_nodes);
-    }
+            if (!hiddenFrames.nodelist) {
+                fsi.positions.nodelist = numframes;
+                normalFrames[numframes++] = graphics::NodeListRenderer::drawDynamicNodeListScreen;
+                indicatorIcons.push_back(icon_nodes);
+            }
 #endif
 
 // Show detailed node views only on E-Ink builds
 #ifdef USE_EINK
-    if (!hiddenFrames.nodelist_lastheard) {
-        fsi.positions.nodelist_lastheard = numframes;
-        normalFrames[numframes++] = graphics::NodeListRenderer::drawLastHeardScreen;
-        indicatorIcons.push_back(icon_nodes);
-    }
-    if (!hiddenFrames.nodelist_hopsignal) {
-        fsi.positions.nodelist_hopsignal = numframes;
-        normalFrames[numframes++] = graphics::NodeListRenderer::drawHopSignalScreen;
-        indicatorIcons.push_back(icon_signal);
-    }
-    if (!hiddenFrames.nodelist_distance) {
-        fsi.positions.nodelist_distance = numframes;
-        normalFrames[numframes++] = graphics::NodeListRenderer::drawDistanceScreen;
-        indicatorIcons.push_back(icon_distance);
-    }
+            if (!hiddenFrames.nodelist_lastheard) {
+                fsi.positions.nodelist_lastheard = numframes;
+                normalFrames[numframes++] = graphics::NodeListRenderer::drawLastHeardScreen;
+                indicatorIcons.push_back(icon_nodes);
+            }
+            if (!hiddenFrames.nodelist_hopsignal) {
+                fsi.positions.nodelist_hopsignal = numframes;
+                normalFrames[numframes++] = graphics::NodeListRenderer::drawHopSignalScreen;
+                indicatorIcons.push_back(icon_signal);
+            }
+            if (!hiddenFrames.nodelist_distance) {
+                fsi.positions.nodelist_distance = numframes;
+                normalFrames[numframes++] = graphics::NodeListRenderer::drawDistanceScreen;
+                indicatorIcons.push_back(icon_distance);
+            }
 #endif
 #if HAS_GPS
-    if (!hiddenFrames.nodelist_bearings) {
-        fsi.positions.nodelist_bearings = numframes;
-        normalFrames[numframes++] = graphics::NodeListRenderer::drawNodeListWithCompasses;
-        indicatorIcons.push_back(icon_list);
-    }
-    if (!hiddenFrames.gps) {
-        fsi.positions.gps = numframes;
-        normalFrames[numframes++] = graphics::UIRenderer::drawCompassAndLocationScreen;
-        indicatorIcons.push_back(icon_compass);
-    }
+            if (!hiddenFrames.nodelist_bearings) {
+                fsi.positions.nodelist_bearings = numframes;
+                normalFrames[numframes++] = graphics::NodeListRenderer::drawNodeListWithCompasses;
+                indicatorIcons.push_back(icon_list);
+            }
+            if (!hiddenFrames.gps) {
+                fsi.positions.gps = numframes;
+                normalFrames[numframes++] = graphics::UIRenderer::drawCompassAndLocationScreen;
+                indicatorIcons.push_back(icon_compass);
+            }
 #endif
-    if (RadioLibInterface::instance && !hiddenFrames.lora) {
-        fsi.positions.lora = numframes;
-        normalFrames[numframes++] = graphics::DebugRenderer::drawLoRaFocused;
-        indicatorIcons.push_back(icon_radio);
-    }
-    if (!hiddenFrames.system) {
-        fsi.positions.system = numframes;
-        normalFrames[numframes++] = graphics::DebugRenderer::drawSystemScreen;
-        indicatorIcons.push_back(icon_system);
-    }
+            if (RadioLibInterface::instance && !hiddenFrames.lora) {
+                fsi.positions.lora = numframes;
+                normalFrames[numframes++] = graphics::DebugRenderer::drawLoRaFocused;
+                indicatorIcons.push_back(icon_radio);
+            }
+            if (!hiddenFrames.system) {
+                fsi.positions.system = numframes;
+                normalFrames[numframes++] = graphics::DebugRenderer::drawSystemScreen;
+                indicatorIcons.push_back(icon_system);
+            }
 #if !defined(DISPLAY_CLOCK_FRAME)
-    if (!hiddenFrames.clock) {
-        fsi.positions.clock = numframes;
-        normalFrames[numframes++] = uiconfig.is_clockface_analog ? graphics::ClockRenderer::drawAnalogClockFrame
-                                                                 : graphics::ClockRenderer::drawDigitalClockFrame;
-        indicatorIcons.push_back(digital_icon_clock);
-    }
+            if (!hiddenFrames.clock) {
+                fsi.positions.clock = numframes;
+                normalFrames[numframes++] = uiconfig.is_clockface_analog ? graphics::ClockRenderer::drawAnalogClockFrame
+                                                                         : graphics::ClockRenderer::drawDigitalClockFrame;
+                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 (!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()) {
-        fsi.positions.wifi = numframes;
-        normalFrames[numframes++] = graphics::DebugRenderer::drawDebugInfoWiFiTrampoline;
-        indicatorIcons.push_back(icon_wifi);
-    }
+            if (!hiddenFrames.wifi && isWifiAvailable()) {
+                fsi.positions.wifi = numframes;
+                normalFrames[numframes++] = graphics::DebugRenderer::drawDebugInfoWiFiTrampoline;
+                indicatorIcons.push_back(icon_wifi);
+            }
 #endif
 
-    // Beware of what changes you make in this code!
-    // We pass numframes into GetMeshModulesWithUIFrames() which is highly important!
-    // Inside of that callback, goes over to MeshModule.cpp and we run
-    // modulesWithUIFrames.resize(startIndex, nullptr), to insert nullptr
-    // entries until we're ready to start building the matching entries.
-    // We are doing our best to keep the normalFrames vector
-    // and the moduleFrames vector in lock step.
-    moduleFrames = MeshModule::GetMeshModulesWithUIFrames(numframes);
-    LOG_DEBUG("Show %d module frames", moduleFrames.size());
+            // Beware of what changes you make in this code!
+            // We pass numframes into GetMeshModulesWithUIFrames() which is highly important!
+            // Inside of that callback, goes over to MeshModule.cpp and we run
+            // modulesWithUIFrames.resize(startIndex, nullptr), to insert nullptr
+            // entries until we're ready to start building the matching entries.
+            // We are doing our best to keep the normalFrames vector
+            // and the moduleFrames vector in lock step.
+            moduleFrames = MeshModule::GetMeshModulesWithUIFrames(numframes);
+            LOG_DEBUG("Show %d module frames", moduleFrames.size());
 
-    for (auto i = moduleFrames.begin(); i != moduleFrames.end(); ++i) {
-        // Draw the module frame, using the hack described above
-        if (*i != nullptr) {
-            normalFrames[numframes] = drawModuleFrame;
+            for (auto i = moduleFrames.begin(); i != moduleFrames.end(); ++i) {
+                // Draw the module frame, using the hack described above
+                if (*i != nullptr) {
+                    normalFrames[numframes] = drawModuleFrame;
 
-            // Check if the module being drawn has requested focus
-            // We will honor this request later, if setFrames was triggered by a UIFrameEvent
-            MeshModule *m = *i;
-            if (m && m->isRequestingFocus())
-                fsi.positions.focusedModule = numframes;
-            if (m && m == waypointModule)
-                fsi.positions.waypoint = numframes;
+                    // Check if the module being drawn has requested focus
+                    // We will honor this request later, if setFrames was triggered by a UIFrameEvent
+                    MeshModule *m = *i;
+                    if (m && m->isRequestingFocus())
+                        fsi.positions.focusedModule = numframes;
+                    if (m && m == waypointModule)
+                        fsi.positions.waypoint = numframes;
 
-            indicatorIcons.push_back(icon_module);
-            numframes++;
+                    indicatorIcons.push_back(icon_module);
+                    numframes++;
+                }
+            }
+
+            LOG_DEBUG("Added modules.  numframes: %d", numframes);
+
+            // We don't show the node info of our node (if we have it yet - we should)
+            size_t numMeshNodes = nodeDB->getNumMeshNodes();
+            if (numMeshNodes > 0)
+                numMeshNodes--;
+
+            if (!hiddenFrames.show_favorites) {
+                // Temporary array to hold favorite node frames
+                std::vector favoriteFrames;
+
+                for (size_t i = 0; i < nodeDB->getNumMeshNodes(); i++) {
+                    const meshtastic_NodeInfoLite *n = nodeDB->getMeshNodeByIndex(i);
+                    if (n && n->num != nodeDB->getNodeNum() && n->is_favorite) {
+                        favoriteFrames.push_back(graphics::UIRenderer::drawNodeInfo);
+                    }
+                }
+
+                // Insert favorite frames *after* collecting them all
+                if (!favoriteFrames.empty()) {
+                    fsi.positions.firstFavorite = numframes;
+                    for (const auto &f : favoriteFrames) {
+                        normalFrames[numframes++] = f;
+                        indicatorIcons.push_back(icon_node);
+                    }
+                    fsi.positions.lastFavorite = numframes - 1;
+                } else {
+                    fsi.positions.firstFavorite = 255;
+                    fsi.positions.lastFavorite = 255;
+                }
+            }
+
+            fsi.frameCount = numframes;   // Total framecount is used to apply FOCUS_PRESERVE
+            this->frameCount = numframes; // ✅ Save frame count for use in custom overlay
+            LOG_DEBUG("Finished build frames. numframes: %d", numframes);
+
+            ui->setFrames(normalFrames, numframes);
+            ui->disableAllIndicators();
+
+            // Add overlays: frame icons and alert banner)
+            static OverlayCallback overlays[] = {graphics::UIRenderer::drawNavigationBar,
+                                                 NotificationRenderer::drawBannercallback};
+            ui->setOverlays(overlays, sizeof(overlays) / sizeof(overlays[0]));
+
+            prevFrame = -1; // Force drawNodeInfo to pick a new node (because our list just changed)
+
+            // Focus on a specific frame, in the frame set we just created
+            switch (focus) {
+            case FOCUS_DEFAULT:
+                ui->switchToFrame(fsi.positions.deviceFocused);
+                break;
+            case FOCUS_FAULT:
+                ui->switchToFrame(fsi.positions.fault);
+                break;
+            case FOCUS_TEXTMESSAGE:
+                hasUnreadMessage = false; // ✅ Clear when message is *viewed*
+                ui->switchToFrame(fsi.positions.textMessage);
+                break;
+            case FOCUS_MODULE:
+                // Whichever frame was marked by MeshModule::requestFocus(), if any
+                // If no module requested focus, will show the first frame instead
+                ui->switchToFrame(fsi.positions.focusedModule);
+                break;
+            case FOCUS_CLOCK:
+                // Whichever frame was marked by MeshModule::requestFocus(), if any
+                // If no module requested focus, will show the first frame instead
+                ui->switchToFrame(fsi.positions.clock);
+                break;
+            case FOCUS_SYSTEM:
+                ui->switchToFrame(fsi.positions.system);
+                break;
+
+            case FOCUS_PRESERVE:
+                //  No more adjustment — force stay on same index
+                if (previousFrameCount > fsi.frameCount) {
+                    ui->switchToFrame(originalPosition - 1);
+                } else if (previousFrameCount < fsi.frameCount) {
+                    ui->switchToFrame(originalPosition + 1);
+                } else {
+                    ui->switchToFrame(originalPosition);
+                }
+                break;
+            }
+
+            // Store the info about this frameset, for future setFrames calls
+            this->framesetInfo = fsi;
+
+            setFastFramerate(); // Draw ASAP
         }
-    }
 
-    LOG_DEBUG("Added modules.  numframes: %d", numframes);
+        void Screen::setFrameImmediateDraw(FrameCallback * drawFrames)
+        {
+            ui->disableAllIndicators();
+            ui->setFrames(drawFrames, 1);
+            setFastFramerate();
+        }
 
-    // We don't show the node info of our node (if we have it yet - we should)
-    size_t numMeshNodes = nodeDB->getNumMeshNodes();
-    if (numMeshNodes > 0)
-        numMeshNodes--;
-
-    if (!hiddenFrames.show_favorites) {
-        // Temporary array to hold favorite node frames
-        std::vector favoriteFrames;
-
-        for (size_t i = 0; i < nodeDB->getNumMeshNodes(); i++) {
-            const meshtastic_NodeInfoLite *n = nodeDB->getMeshNodeByIndex(i);
-            if (n && n->num != nodeDB->getNodeNum() && n->is_favorite) {
-                favoriteFrames.push_back(graphics::UIRenderer::drawNodeInfo);
+        void Screen::toggleFrameVisibility(const std::string &frameName)
+        {
+#ifndef USE_EINK
+            if (frameName == "nodelist") {
+                hiddenFrames.nodelist = !hiddenFrames.nodelist;
+            }
+#endif
+#ifdef USE_EINK
+            if (frameName == "nodelist_lastheard") {
+                hiddenFrames.nodelist_lastheard = !hiddenFrames.nodelist_lastheard;
+            }
+            if (frameName == "nodelist_hopsignal") {
+                hiddenFrames.nodelist_hopsignal = !hiddenFrames.nodelist_hopsignal;
+            }
+            if (frameName == "nodelist_distance") {
+                hiddenFrames.nodelist_distance = !hiddenFrames.nodelist_distance;
+            }
+#endif
+#if HAS_GPS
+            if (frameName == "nodelist_bearings") {
+                hiddenFrames.nodelist_bearings = !hiddenFrames.nodelist_bearings;
+            }
+            if (frameName == "gps") {
+                hiddenFrames.gps = !hiddenFrames.gps;
+            }
+#endif
+            if (frameName == "lora") {
+                hiddenFrames.lora = !hiddenFrames.lora;
+            }
+            if (frameName == "clock") {
+                hiddenFrames.clock = !hiddenFrames.clock;
+            }
+            if (frameName == "show_favorites") {
+                hiddenFrames.show_favorites = !hiddenFrames.show_favorites;
+            }
+            if (frameName == "chirpy") {
+                hiddenFrames.chirpy = !hiddenFrames.chirpy;
             }
         }
 
-        // Insert favorite frames *after* collecting them all
-        if (!favoriteFrames.empty()) {
-            fsi.positions.firstFavorite = numframes;
-            for (const auto &f : favoriteFrames) {
-                normalFrames[numframes++] = f;
-                indicatorIcons.push_back(icon_node);
+        bool Screen::isFrameHidden(const std::string &frameName) const
+        {
+#ifndef USE_EINK
+            if (frameName == "nodelist")
+                return hiddenFrames.nodelist;
+#endif
+#ifdef USE_EINK
+            if (frameName == "nodelist_lastheard")
+                return hiddenFrames.nodelist_lastheard;
+            if (frameName == "nodelist_hopsignal")
+                return hiddenFrames.nodelist_hopsignal;
+            if (frameName == "nodelist_distance")
+                return hiddenFrames.nodelist_distance;
+#endif
+#if HAS_GPS
+            if (frameName == "nodelist_bearings")
+                return hiddenFrames.nodelist_bearings;
+            if (frameName == "gps")
+                return hiddenFrames.gps;
+#endif
+            if (frameName == "lora")
+                return hiddenFrames.lora;
+            if (frameName == "clock")
+                return hiddenFrames.clock;
+            if (frameName == "show_favorites")
+                return hiddenFrames.show_favorites;
+            if (frameName == "chirpy")
+                return hiddenFrames.chirpy;
+
+            return false;
+        }
+
+        // Dismisses the currently displayed screen frame, if possible
+        // Relevant for text message, waypoint, others in future?
+        // Triggered with a CardKB keycombo
+        void Screen::hideCurrentFrame()
+        {
+            uint8_t currentFrame = ui->getUiState()->currentFrame;
+            bool dismissed = false;
+            if (currentFrame == framesetInfo.positions.textMessage && devicestate.has_rx_text_message) {
+                LOG_INFO("Hide Text Message");
+                devicestate.has_rx_text_message = false;
+                memset(&devicestate.rx_text_message, 0, sizeof(devicestate.rx_text_message));
+            } else if (currentFrame == framesetInfo.positions.waypoint && devicestate.has_rx_waypoint) {
+                LOG_DEBUG("Hide Waypoint");
+                devicestate.has_rx_waypoint = false;
+                hiddenFrames.waypoint = true;
+                dismissed = true;
+            } else if (currentFrame == framesetInfo.positions.wifi) {
+                LOG_DEBUG("Hide WiFi Screen");
+                hiddenFrames.wifi = true;
+                dismissed = true;
+            } else if (currentFrame == framesetInfo.positions.lora) {
+                LOG_INFO("Hide LoRa");
+                hiddenFrames.lora = true;
+                dismissed = true;
+            }
+
+            if (dismissed) {
+                setFrames(FOCUS_DEFAULT); // You could also use FOCUS_PRESERVE
             }
-            fsi.positions.lastFavorite = numframes - 1;
-        } else {
-            fsi.positions.firstFavorite = 255;
-            fsi.positions.lastFavorite = 255;
         }
-    }
 
-    fsi.frameCount = numframes;   // Total framecount is used to apply FOCUS_PRESERVE
-    this->frameCount = numframes; // ✅ Save frame count for use in custom overlay
-    LOG_DEBUG("Finished build frames. numframes: %d", numframes);
+        void Screen::handleStartFirmwareUpdateScreen()
+        {
+            LOG_DEBUG("Show firmware screen");
+            showingNormalScreen = false;
+            EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // E-Ink: Explicitly use fast-refresh for next frame
 
-    ui->setFrames(normalFrames, numframes);
-    ui->disableAllIndicators();
-
-    // Add overlays: frame icons and alert banner)
-    static OverlayCallback overlays[] = {graphics::UIRenderer::drawNavigationBar, NotificationRenderer::drawBannercallback};
-    ui->setOverlays(overlays, sizeof(overlays) / sizeof(overlays[0]));
-
-    prevFrame = -1; // Force drawNodeInfo to pick a new node (because our list just changed)
-
-    // Focus on a specific frame, in the frame set we just created
-    switch (focus) {
-    case FOCUS_DEFAULT:
-        ui->switchToFrame(fsi.positions.deviceFocused);
-        break;
-    case FOCUS_FAULT:
-        ui->switchToFrame(fsi.positions.fault);
-        break;
-    case FOCUS_TEXTMESSAGE:
-        hasUnreadMessage = false; // ✅ Clear when message is *viewed*
-        ui->switchToFrame(fsi.positions.textMessage);
-        break;
-    case FOCUS_MODULE:
-        // Whichever frame was marked by MeshModule::requestFocus(), if any
-        // If no module requested focus, will show the first frame instead
-        ui->switchToFrame(fsi.positions.focusedModule);
-        break;
-    case FOCUS_CLOCK:
-        // Whichever frame was marked by MeshModule::requestFocus(), if any
-        // If no module requested focus, will show the first frame instead
-        ui->switchToFrame(fsi.positions.clock);
-        break;
-    case FOCUS_SYSTEM:
-        ui->switchToFrame(fsi.positions.system);
-        break;
-
-    case FOCUS_PRESERVE:
-        //  No more adjustment — force stay on same index
-        if (previousFrameCount > fsi.frameCount) {
-            ui->switchToFrame(originalPosition - 1);
-        } else if (previousFrameCount < fsi.frameCount) {
-            ui->switchToFrame(originalPosition + 1);
-        } else {
-            ui->switchToFrame(originalPosition);
+            static FrameCallback frames[] = {graphics::NotificationRenderer::drawFrameFirmware};
+            setFrameImmediateDraw(frames);
         }
-        break;
-    }
 
-    // Store the info about this frameset, for future setFrames calls
-    this->framesetInfo = fsi;
+        void Screen::blink()
+        {
+            setFastFramerate();
+            uint8_t count = 10;
+            dispdev->setBrightness(254);
+            while (count > 0) {
+                dispdev->fillRect(0, 0, dispdev->getWidth(), dispdev->getHeight());
+                dispdev->display();
+                delay(50);
+                dispdev->clear();
+                dispdev->display();
+                delay(50);
+                count = count - 1;
+            }
+            // The dispdev->setBrightness does not work for t-deck display, it seems to run the setBrightness function in
+            // OLEDDisplay.
+            dispdev->setBrightness(brightness);
+        }
 
-    setFastFramerate(); // Draw ASAP
-}
-
-void Screen::setFrameImmediateDraw(FrameCallback *drawFrames)
-{
-    ui->disableAllIndicators();
-    ui->setFrames(drawFrames, 1);
-    setFastFramerate();
-}
-
-void Screen::toggleFrameVisibility(const std::string &frameName)
-{
-#ifndef USE_EINK
-    if (frameName == "nodelist") {
-        hiddenFrames.nodelist = !hiddenFrames.nodelist;
-    }
-#endif
-#ifdef USE_EINK
-    if (frameName == "nodelist_lastheard") {
-        hiddenFrames.nodelist_lastheard = !hiddenFrames.nodelist_lastheard;
-    }
-    if (frameName == "nodelist_hopsignal") {
-        hiddenFrames.nodelist_hopsignal = !hiddenFrames.nodelist_hopsignal;
-    }
-    if (frameName == "nodelist_distance") {
-        hiddenFrames.nodelist_distance = !hiddenFrames.nodelist_distance;
-    }
-#endif
-#if HAS_GPS
-    if (frameName == "nodelist_bearings") {
-        hiddenFrames.nodelist_bearings = !hiddenFrames.nodelist_bearings;
-    }
-    if (frameName == "gps") {
-        hiddenFrames.gps = !hiddenFrames.gps;
-    }
-#endif
-    if (frameName == "lora") {
-        hiddenFrames.lora = !hiddenFrames.lora;
-    }
-    if (frameName == "clock") {
-        hiddenFrames.clock = !hiddenFrames.clock;
-    }
-    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
-{
-#ifndef USE_EINK
-    if (frameName == "nodelist")
-        return hiddenFrames.nodelist;
-#endif
-#ifdef USE_EINK
-    if (frameName == "nodelist_lastheard")
-        return hiddenFrames.nodelist_lastheard;
-    if (frameName == "nodelist_hopsignal")
-        return hiddenFrames.nodelist_hopsignal;
-    if (frameName == "nodelist_distance")
-        return hiddenFrames.nodelist_distance;
-#endif
-#if HAS_GPS
-    if (frameName == "nodelist_bearings")
-        return hiddenFrames.nodelist_bearings;
-    if (frameName == "gps")
-        return hiddenFrames.gps;
-#endif
-    if (frameName == "lora")
-        return hiddenFrames.lora;
-    if (frameName == "clock")
-        return hiddenFrames.clock;
-    if (frameName == "show_favorites")
-        return hiddenFrames.show_favorites;
-    if (frameName == "chirpy")
-        return hiddenFrames.chirpy;
-
-    return false;
-}
-
-// Dismisses the currently displayed screen frame, if possible
-// Relevant for text message, waypoint, others in future?
-// Triggered with a CardKB keycombo
-void Screen::hideCurrentFrame()
-{
-    uint8_t currentFrame = ui->getUiState()->currentFrame;
-    bool dismissed = false;
-    if (currentFrame == framesetInfo.positions.textMessage && devicestate.has_rx_text_message) {
-        LOG_INFO("Hide Text Message");
-        devicestate.has_rx_text_message = false;
-        memset(&devicestate.rx_text_message, 0, sizeof(devicestate.rx_text_message));
-    } else if (currentFrame == framesetInfo.positions.waypoint && devicestate.has_rx_waypoint) {
-        LOG_DEBUG("Hide Waypoint");
-        devicestate.has_rx_waypoint = false;
-        hiddenFrames.waypoint = true;
-        dismissed = true;
-    } else if (currentFrame == framesetInfo.positions.wifi) {
-        LOG_DEBUG("Hide WiFi Screen");
-        hiddenFrames.wifi = true;
-        dismissed = true;
-    } else if (currentFrame == framesetInfo.positions.lora) {
-        LOG_INFO("Hide LoRa");
-        hiddenFrames.lora = true;
-        dismissed = true;
-    }
-
-    if (dismissed) {
-        setFrames(FOCUS_DEFAULT); // You could also use FOCUS_PRESERVE
-    }
-}
-
-void Screen::handleStartFirmwareUpdateScreen()
-{
-    LOG_DEBUG("Show firmware screen");
-    showingNormalScreen = false;
-    EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // E-Ink: Explicitly use fast-refresh for next frame
-
-    static FrameCallback frames[] = {graphics::NotificationRenderer::drawFrameFirmware};
-    setFrameImmediateDraw(frames);
-}
-
-void Screen::blink()
-{
-    setFastFramerate();
-    uint8_t count = 10;
-    dispdev->setBrightness(254);
-    while (count > 0) {
-        dispdev->fillRect(0, 0, dispdev->getWidth(), dispdev->getHeight());
-        dispdev->display();
-        delay(50);
-        dispdev->clear();
-        dispdev->display();
-        delay(50);
-        count = count - 1;
-    }
-    // The dispdev->setBrightness does not work for t-deck display, it seems to run the setBrightness function in OLEDDisplay.
-    dispdev->setBrightness(brightness);
-}
-
-void Screen::increaseBrightness()
-{
-    brightness = ((brightness + 62) > 254) ? brightness : (brightness + 62);
+        void Screen::increaseBrightness()
+        {
+            brightness = ((brightness + 62) > 254) ? brightness : (brightness + 62);
 
 #if defined(ST7789_CS)
-    // run the setDisplayBrightness function. This works on t-decks
-    static_cast(dispdev)->setDisplayBrightness(brightness);
+            // run the setDisplayBrightness function. This works on t-decks
+            static_cast(dispdev)->setDisplayBrightness(brightness);
 #endif
 
-    /* TO DO: add little popup in center of screen saying what brightness level it is set to*/
-}
+            /* TO DO: add little popup in center of screen saying what brightness level it is set to*/
+        }
 
-void Screen::decreaseBrightness()
-{
-    brightness = (brightness < 70) ? brightness : (brightness - 62);
+        void Screen::decreaseBrightness()
+        {
+            brightness = (brightness < 70) ? brightness : (brightness - 62);
 
 #if defined(ST7789_CS)
-    static_cast(dispdev)->setDisplayBrightness(brightness);
+            static_cast(dispdev)->setDisplayBrightness(brightness);
 #endif
 
-    /* TO DO: add little popup in center of screen saying what brightness level it is set to*/
-}
-
-void Screen::setFunctionSymbol(std::string sym)
-{
-    if (std::find(functionSymbol.begin(), functionSymbol.end(), sym) == functionSymbol.end()) {
-        functionSymbol.push_back(sym);
-        functionSymbolString = "";
-        for (auto symbol : functionSymbol) {
-            functionSymbolString = symbol + " " + functionSymbolString;
+            /* TO DO: add little popup in center of screen saying what brightness level it is set to*/
         }
-        setFastFramerate();
-    }
-}
 
-void Screen::removeFunctionSymbol(std::string sym)
-{
-    functionSymbol.erase(std::remove(functionSymbol.begin(), functionSymbol.end(), sym), functionSymbol.end());
-    functionSymbolString = "";
-    for (auto symbol : functionSymbol) {
-        functionSymbolString = symbol + " " + functionSymbolString;
-    }
-    setFastFramerate();
-}
+        void Screen::setFunctionSymbol(std::string sym)
+        {
+            if (std::find(functionSymbol.begin(), functionSymbol.end(), sym) == functionSymbol.end()) {
+                functionSymbol.push_back(sym);
+                functionSymbolString = "";
+                for (auto symbol : functionSymbol) {
+                    functionSymbolString = symbol + " " + functionSymbolString;
+                }
+                setFastFramerate();
+            }
+        }
 
-void Screen::handleOnPress()
-{
-    // If screen was off, just wake it, otherwise advance to next frame
-    // If we are in a transition, the press must have bounced, drop it.
-    if (ui->getUiState()->frameState == FIXED) {
-        ui->nextFrame();
-        lastScreenTransition = millis();
-        setFastFramerate();
-    }
-}
+        void Screen::removeFunctionSymbol(std::string sym)
+        {
+            functionSymbol.erase(std::remove(functionSymbol.begin(), functionSymbol.end(), sym), functionSymbol.end());
+            functionSymbolString = "";
+            for (auto symbol : functionSymbol) {
+                functionSymbolString = symbol + " " + functionSymbolString;
+            }
+            setFastFramerate();
+        }
 
-void Screen::handleShowPrevFrame()
-{
-    // If screen was off, just wake it, otherwise go back to previous frame
-    // If we are in a transition, the press must have bounced, drop it.
-    if (ui->getUiState()->frameState == FIXED) {
-        ui->previousFrame();
-        lastScreenTransition = millis();
-        setFastFramerate();
-    }
-}
+        void Screen::handleOnPress()
+        {
+            // If screen was off, just wake it, otherwise advance to next frame
+            // If we are in a transition, the press must have bounced, drop it.
+            if (ui->getUiState()->frameState == FIXED) {
+                ui->nextFrame();
+                lastScreenTransition = millis();
+                setFastFramerate();
+            }
+        }
 
-void Screen::handleShowNextFrame()
-{
-    // If screen was off, just wake it, otherwise advance to next frame
-    // If we are in a transition, the press must have bounced, drop it.
-    if (ui->getUiState()->frameState == FIXED) {
-        ui->nextFrame();
-        lastScreenTransition = millis();
-        setFastFramerate();
-    }
-}
+        void Screen::handleShowPrevFrame()
+        {
+            // If screen was off, just wake it, otherwise go back to previous frame
+            // If we are in a transition, the press must have bounced, drop it.
+            if (ui->getUiState()->frameState == FIXED) {
+                ui->previousFrame();
+                lastScreenTransition = millis();
+                setFastFramerate();
+            }
+        }
+
+        void Screen::handleShowNextFrame()
+        {
+            // If screen was off, just wake it, otherwise advance to next frame
+            // If we are in a transition, the press must have bounced, drop it.
+            if (ui->getUiState()->frameState == FIXED) {
+                ui->nextFrame();
+                lastScreenTransition = millis();
+                setFastFramerate();
+            }
+        }
 
 #ifndef SCREEN_TRANSITION_FRAMERATE
 #define SCREEN_TRANSITION_FRAMERATE 30 // fps
 #endif
 
-void Screen::setFastFramerate()
-{
+        void Screen::setFastFramerate()
+        {
 #if defined(M5STACK_UNITC6L)
-    dispdev->clear();
-    dispdev->display();
+            dispdev->clear();
+            dispdev->display();
 #endif
-    // We are about to start a transition so speed up fps
-    targetFramerate = SCREEN_TRANSITION_FRAMERATE;
+            // We are about to start a transition so speed up fps
+            targetFramerate = SCREEN_TRANSITION_FRAMERATE;
 
-    ui->setTargetFPS(targetFramerate);
-    setInterval(0); // redraw ASAP
-    runASAP = true;
-}
-
-int Screen::handleStatusUpdate(const meshtastic::Status *arg)
-{
-    // LOG_DEBUG("Screen got status update %d", arg->getStatusType());
-    switch (arg->getStatusType()) {
-    case STATUS_TYPE_NODE:
-        if (showingNormalScreen && nodeStatus->getLastNumTotal() != nodeStatus->getNumTotal()) {
-            setFrames(FOCUS_PRESERVE); // Regen the list of screen frames (returning to same frame, if possible)
+            ui->setTargetFPS(targetFramerate);
+            setInterval(0); // redraw ASAP
+            runASAP = true;
         }
-        nodeDB->updateGUI = false;
-        break;
-    }
 
-    return 0;
-}
-
-// Handles when message is received; will jump to text message frame.
-int Screen::handleTextMessage(const meshtastic_MeshPacket *packet)
-{
-    if (showingNormalScreen) {
-        if (packet->from == 0) {
-            // Outgoing message (likely sent from phone)
-            devicestate.has_rx_text_message = false;
-            memset(&devicestate.rx_text_message, 0, sizeof(devicestate.rx_text_message));
-            hiddenFrames.textMessage = true;
-            hasUnreadMessage = false; // Clear unread state when user replies
-
-            setFrames(FOCUS_PRESERVE); // Stay on same frame, silently update frame list
-        } else {
-            // Incoming message
-            devicestate.has_rx_text_message = true; // Needed to include the message frame
-            hasUnreadMessage = true;                // Enables mail icon in the header
-            setFrames(FOCUS_PRESERVE);              // Refresh frame list without switching view
-
-            // Only wake/force display if the configuration allows it
-            if (shouldWakeOnReceivedMessage()) {
-                setOn(true);    // Wake up the screen first
-                forceDisplay(); // Forces screen redraw
-            }
-            // === Prepare banner content ===
-            const meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(packet->from);
-            const char *longName = (node && node->has_user) ? node->user.long_name : nullptr;
-
-            const char *msgRaw = reinterpret_cast(packet->decoded.payload.bytes);
-
-            char banner[256];
-
-            // Check for bell character in message to determine alert type
-            bool isAlert = false;
-            for (size_t i = 0; i < packet->decoded.payload.size && i < 100; i++) {
-                if (msgRaw[i] == '\x07') {
-                    isAlert = true;
-                    break;
+        int Screen::handleStatusUpdate(const meshtastic::Status *arg)
+        {
+            // LOG_DEBUG("Screen got status update %d", arg->getStatusType());
+            switch (arg->getStatusType()) {
+            case STATUS_TYPE_NODE:
+                if (showingNormalScreen && nodeStatus->getLastNumTotal() != nodeStatus->getNumTotal()) {
+                    setFrames(FOCUS_PRESERVE); // Regen the list of screen frames (returning to same frame, if possible)
                 }
+                nodeDB->updateGUI = false;
+                break;
             }
 
-            if (isAlert) {
-                if (longName && longName[0]) {
-                    snprintf(banner, sizeof(banner), "Alert Received from\n%s", longName);
+            return 0;
+        }
+
+        // Handles when message is received; will jump to text message frame.
+        int Screen::handleTextMessage(const meshtastic_MeshPacket *packet)
+        {
+            if (showingNormalScreen) {
+                if (packet->from == 0) {
+                    // Outgoing message (likely sent from phone)
+                    devicestate.has_rx_text_message = false;
+                    memset(&devicestate.rx_text_message, 0, sizeof(devicestate.rx_text_message));
+                    hiddenFrames.textMessage = true;
+                    hasUnreadMessage = false; // Clear unread state when user replies
+
+                    setFrames(FOCUS_PRESERVE); // Stay on same frame, silently update frame list
                 } else {
-                    strcpy(banner, "Alert Received");
-                }
-            } else {
-                if (longName && longName[0]) {
+                    // Incoming message
+                    devicestate.has_rx_text_message = true; // Needed to include the message frame
+                    hasUnreadMessage = true;                // Enables mail icon in the header
+                    setFrames(FOCUS_PRESERVE);              // Refresh frame list without switching view
+
+                    // Only wake/force display if the configuration allows it
+                    if (shouldWakeOnReceivedMessage()) {
+                        setOn(true);    // Wake up the screen first
+                        forceDisplay(); // Forces screen redraw
+                    }
+                    // === Prepare banner content ===
+                    const meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(packet->from);
+                    const char *longName = (node && node->has_user) ? node->user.long_name : nullptr;
+
+                    const char *msgRaw = reinterpret_cast(packet->decoded.payload.bytes);
+
+                    char banner[256];
+
+                    // Check for bell character in message to determine alert type
+                    bool isAlert = false;
+                    for (size_t i = 0; i < packet->decoded.payload.size && i < 100; i++) {
+                        if (msgRaw[i] == '\x07') {
+                            isAlert = true;
+                            break;
+                        }
+                    }
+
+                    if (isAlert) {
+                        if (longName && longName[0]) {
+                            snprintf(banner, sizeof(banner), "Alert Received from\n%s", longName);
+                        } else {
+                            strcpy(banner, "Alert Received");
+                        }
+                    } else {
+                        if (longName && longName[0]) {
 #if defined(M5STACK_UNITC6L)
-                    strcpy(banner, "New Message");
+                            strcpy(banner, "New Message");
 #else
                     snprintf(banner, sizeof(banner), "New Message from\n%s", longName);
 #endif
 
-                } else {
-                    strcpy(banner, "New Message");
-                }
-            }
+                        } else {
+                            strcpy(banner, "New Message");
+                        }
+                    }
 #if defined(M5STACK_UNITC6L)
-            screen->setOn(true);
-            screen->showSimpleBanner(banner, 1500);
-            playLongBeep();
+                    screen->setOn(true);
+                    screen->showSimpleBanner(banner, 1500);
+                    playLongBeep();
 #else
             screen->showSimpleBanner(banner, 3000);
 #endif
+                }
+            }
+
+            return 0;
         }
-    }
 
-    return 0;
-}
+        // Triggered by MeshModules
+        int Screen::handleUIFrameEvent(const UIFrameEvent *event)
+        {
+            // Block UI frame events when virtual keyboard is active
+            if (NotificationRenderer::current_notification_type == notificationTypeEnum::text_input) {
+                return 0;
+            }
 
-// Triggered by MeshModules
-int Screen::handleUIFrameEvent(const UIFrameEvent *event)
-{
-    // Block UI frame events when virtual keyboard is active
-    if (NotificationRenderer::current_notification_type == notificationTypeEnum::text_input) {
-        return 0;
-    }
+            if (showingNormalScreen) {
+                // Regenerate the frameset, potentially honoring a module's internal requestFocus() call
+                if (event->action == UIFrameEvent::Action::REGENERATE_FRAMESET)
+                    setFrames(FOCUS_MODULE);
 
-    if (showingNormalScreen) {
-        // Regenerate the frameset, potentially honoring a module's internal requestFocus() call
-        if (event->action == UIFrameEvent::Action::REGENERATE_FRAMESET)
-            setFrames(FOCUS_MODULE);
+                // Regenerate the frameset, while Attempt to maintain focus on the current frame
+                else if (event->action == UIFrameEvent::Action::REGENERATE_FRAMESET_BACKGROUND)
+                    setFrames(FOCUS_PRESERVE);
 
-        // Regenerate the frameset, while Attempt to maintain focus on the current frame
-        else if (event->action == UIFrameEvent::Action::REGENERATE_FRAMESET_BACKGROUND)
-            setFrames(FOCUS_PRESERVE);
+                // Don't regenerate the frameset, just re-draw whatever is on screen ASAP
+                else if (event->action == UIFrameEvent::Action::REDRAW_ONLY)
+                    setFastFramerate();
+            }
 
-        // Don't regenerate the frameset, just re-draw whatever is on screen ASAP
-        else if (event->action == UIFrameEvent::Action::REDRAW_ONLY)
-            setFastFramerate();
-    }
+            return 0;
+        }
 
-    return 0;
-}
+        int Screen::handleInputEvent(const InputEvent *event)
+        {
+            if (!screenOn)
+                return 0;
 
-int Screen::handleInputEvent(const InputEvent *event)
-{
-    if (!screenOn)
-        return 0;
-
-    // Handle text input notifications specially - pass input to virtual keyboard
-    if (NotificationRenderer::current_notification_type == notificationTypeEnum::text_input) {
-        NotificationRenderer::inEvent = *event;
-        static OverlayCallback overlays[] = {graphics::UIRenderer::drawNavigationBar, NotificationRenderer::drawBannercallback};
-        ui->setOverlays(overlays, sizeof(overlays) / sizeof(overlays[0]));
-        setFastFramerate(); // Draw ASAP
-        ui->update();
-        return 0;
-    }
+            // Handle text input notifications specially - pass input to virtual keyboard
+            if (NotificationRenderer::current_notification_type == notificationTypeEnum::text_input) {
+                NotificationRenderer::inEvent = *event;
+                static OverlayCallback overlays[] = {graphics::UIRenderer::drawNavigationBar,
+                                                     NotificationRenderer::drawBannercallback};
+                ui->setOverlays(overlays, sizeof(overlays) / sizeof(overlays[0]));
+                setFastFramerate(); // Draw ASAP
+                ui->update();
+                return 0;
+            }
 
 #ifdef USE_EINK // the screen is the last input handler, so if an event makes it here, we can assume it will prompt a screen draw.
-    EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // Use fast-refresh for next frame, no skip please
-    EINK_ADD_FRAMEFLAG(dispdev, BLOCKING);    // Edge case: if this frame is promoted to COSMETIC, wait for update
-    handleSetOn(true);                        // Ensure power-on to receive deep-sleep screensaver (PowerFSM should handle?)
-    setFastFramerate();                       // Draw ASAP
+            EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // Use fast-refresh for next frame, no skip please
+            EINK_ADD_FRAMEFLAG(dispdev, BLOCKING);    // Edge case: if this frame is promoted to COSMETIC, wait for update
+            handleSetOn(true);  // Ensure power-on to receive deep-sleep screensaver (PowerFSM should handle?)
+            setFastFramerate(); // Draw ASAP
 #endif
-    if (NotificationRenderer::isOverlayBannerShowing()) {
-        NotificationRenderer::inEvent = *event;
-        static OverlayCallback overlays[] = {graphics::UIRenderer::drawNavigationBar, NotificationRenderer::drawBannercallback};
-        ui->setOverlays(overlays, sizeof(overlays) / sizeof(overlays[0]));
-        setFastFramerate(); // Draw ASAP
-        ui->update();
+            if (NotificationRenderer::isOverlayBannerShowing()) {
+                NotificationRenderer::inEvent = *event;
+                static OverlayCallback overlays[] = {graphics::UIRenderer::drawNavigationBar,
+                                                     NotificationRenderer::drawBannercallback};
+                ui->setOverlays(overlays, sizeof(overlays) / sizeof(overlays[0]));
+                setFastFramerate(); // Draw ASAP
+                ui->update();
 
-        menuHandler::handleMenuSwitch(dispdev);
-        return 0;
-    }
+                menuHandler::handleMenuSwitch(dispdev);
+                return 0;
+            }
 
-    // Use left or right input from a keyboard to move between frames,
-    // so long as a mesh module isn't using these events for some other purpose
-    if (showingNormalScreen) {
+            // Use left or right input from a keyboard to move between frames,
+            // so long as a mesh module isn't using these events for some other purpose
+            if (showingNormalScreen) {
 
-        // Ask any MeshModules if they're handling keyboard input right now
-        bool inputIntercepted = false;
-        for (MeshModule *module : moduleFrames) {
-            if (module && module->interceptingKeyboardInput())
-                inputIntercepted = true;
-        }
+                // Ask any MeshModules if they're handling keyboard input right now
+                bool inputIntercepted = false;
+                for (MeshModule *module : moduleFrames) {
+                    if (module && module->interceptingKeyboardInput())
+                        inputIntercepted = true;
+                }
 
-        // If no modules are using the input, move between frames
-        if (!inputIntercepted) {
-            if (event->inputEvent == INPUT_BROKER_LEFT || event->inputEvent == INPUT_BROKER_ALT_PRESS) {
-                showPrevFrame();
-            } else if (event->inputEvent == INPUT_BROKER_RIGHT || event->inputEvent == INPUT_BROKER_USER_PRESS) {
-                showNextFrame();
-            } else if (event->inputEvent == INPUT_BROKER_SELECT) {
-                if (this->ui->getUiState()->currentFrame == framesetInfo.positions.home) {
-                    menuHandler::homeBaseMenu();
-                } else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.system) {
-                    menuHandler::systemBaseMenu();
+                // If no modules are using the input, move between frames
+                if (!inputIntercepted) {
+                    if (event->inputEvent == INPUT_BROKER_LEFT || event->inputEvent == INPUT_BROKER_ALT_PRESS) {
+                        showPrevFrame();
+                    } else if (event->inputEvent == INPUT_BROKER_RIGHT || event->inputEvent == INPUT_BROKER_USER_PRESS) {
+                        showNextFrame();
+                    } else if (event->inputEvent == INPUT_BROKER_SELECT) {
+                        if (this->ui->getUiState()->currentFrame == framesetInfo.positions.home) {
+                            menuHandler::homeBaseMenu();
+                        } else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.system) {
+                            menuHandler::systemBaseMenu();
 #if HAS_GPS
-                } else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.gps && gps) {
-                    menuHandler::positionBaseMenu();
+                        } else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.gps && gps) {
+                            menuHandler::positionBaseMenu();
 #endif
-                } else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.clock) {
-                    menuHandler::clockMenu();
-                } else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.lora) {
-                    menuHandler::loraMenu();
-                } else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.textMessage) {
-                    if (devicestate.rx_text_message.from) {
-                        menuHandler::messageResponseMenu();
-                    } else {
+                        } else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.clock) {
+                            menuHandler::clockMenu();
+                        } else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.lora) {
+                            menuHandler::loraMenu();
+                        } else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.textMessage) {
+                            if (devicestate.rx_text_message.from) {
+                                menuHandler::messageResponseMenu();
+                            } else {
 #if defined(M5STACK_UNITC6L)
-                        menuHandler::textMessageMenu();
+                                menuHandler::textMessageMenu();
 #else
                         menuHandler::textMessageBaseMenu();
 #endif
+                            }
+                        } else if (framesetInfo.positions.firstFavorite != 255 &&
+                                   this->ui->getUiState()->currentFrame >= framesetInfo.positions.firstFavorite &&
+                                   this->ui->getUiState()->currentFrame <= framesetInfo.positions.lastFavorite) {
+                            menuHandler::favoriteBaseMenu();
+                        } else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.nodelist ||
+                                   this->ui->getUiState()->currentFrame == framesetInfo.positions.nodelist_lastheard ||
+                                   this->ui->getUiState()->currentFrame == framesetInfo.positions.nodelist_hopsignal ||
+                                   this->ui->getUiState()->currentFrame == framesetInfo.positions.nodelist_distance ||
+                                   this->ui->getUiState()->currentFrame == framesetInfo.positions.nodelist_hopsignal ||
+                                   this->ui->getUiState()->currentFrame == framesetInfo.positions.nodelist_bearings) {
+                            menuHandler::nodeListMenu();
+                        } else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.wifi) {
+                            menuHandler::wifiBaseMenu();
+                        }
+                    } else if (event->inputEvent == INPUT_BROKER_BACK) {
+                        showPrevFrame();
+                    } else if (event->inputEvent == INPUT_BROKER_CANCEL) {
+                        setOn(false);
                     }
-                } else if (framesetInfo.positions.firstFavorite != 255 &&
-                           this->ui->getUiState()->currentFrame >= framesetInfo.positions.firstFavorite &&
-                           this->ui->getUiState()->currentFrame <= framesetInfo.positions.lastFavorite) {
-                    menuHandler::favoriteBaseMenu();
-                } else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.nodelist ||
-                           this->ui->getUiState()->currentFrame == framesetInfo.positions.nodelist_lastheard ||
-                           this->ui->getUiState()->currentFrame == framesetInfo.positions.nodelist_hopsignal ||
-                           this->ui->getUiState()->currentFrame == framesetInfo.positions.nodelist_distance ||
-                           this->ui->getUiState()->currentFrame == framesetInfo.positions.nodelist_hopsignal ||
-                           this->ui->getUiState()->currentFrame == framesetInfo.positions.nodelist_bearings) {
-                    menuHandler::nodeListMenu();
-                } else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.wifi) {
-                    menuHandler::wifiBaseMenu();
                 }
-            } else if (event->inputEvent == INPUT_BROKER_BACK) {
-                showPrevFrame();
-            } else if (event->inputEvent == INPUT_BROKER_CANCEL) {
-                setOn(false);
             }
+
+            return 0;
         }
-    }
 
-    return 0;
-}
+        int Screen::handleAdminMessage(AdminModule_ObserverData * arg)
+        {
+            switch (arg->request->which_payload_variant) {
+            // Node removed manually (i.e. via app)
+            case meshtastic_AdminMessage_remove_by_nodenum_tag:
+                setFrames(FOCUS_PRESERVE);
+                *arg->result = AdminMessageHandleResult::HANDLED;
+                break;
 
-int Screen::handleAdminMessage(AdminModule_ObserverData *arg)
-{
-    switch (arg->request->which_payload_variant) {
-    // Node removed manually (i.e. via app)
-    case meshtastic_AdminMessage_remove_by_nodenum_tag:
-        setFrames(FOCUS_PRESERVE);
-        *arg->result = AdminMessageHandleResult::HANDLED;
-        break;
+            // Default no-op, in case the admin message observable gets used by other classes in future
+            default:
+                break;
+            }
+            return 0;
+        }
 
-    // Default no-op, in case the admin message observable gets used by other classes in future
-    default:
-        break;
-    }
-    return 0;
-}
+        bool Screen::isOverlayBannerShowing()
+        {
+            return NotificationRenderer::isOverlayBannerShowing();
+        }
 
-bool Screen::isOverlayBannerShowing()
-{
-    return NotificationRenderer::isOverlayBannerShowing();
-}
-
-} // namespace graphics
+    } // namespace graphics
 
 #else
 graphics::Screen::Screen(ScanI2C::DeviceAddress, meshtastic_Config_DisplayConfig_OledType, OLEDDISPLAY_GEOMETRY) {}
 #endif // HAS_SCREEN
 
-bool shouldWakeOnReceivedMessage()
-{
-    /*
-    The goal here is to determine when we do NOT wake up the screen on message received:
-    - Any ext. notifications are turned on
-    - If role is not CLIENT / CLIENT_MUTE / CLIENT_HIDDEN / CLIENT_BASE
-    - If the battery level is very low
-    */
-    if (moduleConfig.external_notification.enabled) {
-        return false;
+    bool shouldWakeOnReceivedMessage()
+    {
+        /*
+        The goal here is to determine when we do NOT wake up the screen on message received:
+        - Any ext. notifications are turned on
+        - If role is not CLIENT / CLIENT_MUTE / CLIENT_HIDDEN / CLIENT_BASE
+        - If the battery level is very low
+        */
+        if (moduleConfig.external_notification.enabled) {
+            return false;
+        }
+        if (!IS_ONE_OF(config.device.role, meshtastic_Config_DeviceConfig_Role_CLIENT,
+                       meshtastic_Config_DeviceConfig_Role_CLIENT_MUTE, meshtastic_Config_DeviceConfig_Role_CLIENT_HIDDEN,
+                       meshtastic_Config_DeviceConfig_Role_CLIENT_BASE)) {
+            return false;
+        }
+        if (powerStatus && powerStatus->getBatteryChargePercent() < 10) {
+            return false;
+        }
+        return true;
     }
-    if (!IS_ONE_OF(config.device.role, meshtastic_Config_DeviceConfig_Role_CLIENT,
-                   meshtastic_Config_DeviceConfig_Role_CLIENT_MUTE, meshtastic_Config_DeviceConfig_Role_CLIENT_HIDDEN,
-                   meshtastic_Config_DeviceConfig_Role_CLIENT_BASE)) {
-        return false;
-    }
-    if (powerStatus && powerStatus->getBatteryChargePercent() < 10) {
-        return false;
-    }
-    return true;
-}
diff --git a/src/mesh/StreamAPI.h b/src/mesh/StreamAPI.h
index 547dd0175..4ca2c197f 100644
--- a/src/mesh/StreamAPI.h
+++ b/src/mesh/StreamAPI.h
@@ -50,15 +50,15 @@ class StreamAPI : public PhoneAPI
      * phone.
      */
     virtual int32_t runOncePart();
-    virtual int32_t runOncePart(char *buf,uint16_t bufLen);
+    virtual int32_t runOncePart(char *buf, uint16_t bufLen);
 
   private:
     /**
      * Read any rx chars from the link and call handleToRadio
      */
     int32_t readStream();
-    int32_t readStream(char *buf,uint16_t bufLen);
-    int32_t handleRecStream(char *buf,uint16_t bufLen);
+    int32_t readStream(char *buf, uint16_t bufLen);
+    int32_t handleRecStream(char *buf, uint16_t bufLen);
 
     /**
      * call getFromRadio() and deliver encapsulated packets to the Stream

From 8264d4d65ed742b5675576aa8e08a0b5057edf4d Mon Sep 17 00:00:00 2001
From: Jason P 
Date: Fri, 5 Sep 2025 20:44:32 -0500
Subject: [PATCH 275/691] 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
---
 src/graphics/draw/ClockRenderer.cpp |  2 ++
 src/graphics/draw/UIRenderer.cpp    | 40 +++++++++++++++++++++++++++++
 2 files changed, 42 insertions(+)

diff --git a/src/graphics/draw/ClockRenderer.cpp b/src/graphics/draw/ClockRenderer.cpp
index aec0a701f..5afcf094c 100644
--- a/src/graphics/draw/ClockRenderer.cpp
+++ b/src/graphics/draw/ClockRenderer.cpp
@@ -190,6 +190,7 @@ void drawDigitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int1
     const char *titleStr = "";
     // === Header ===
     graphics::drawCommonHeader(display, x, y, titleStr, true, true);
+    int line = 0;
 
 #ifdef T_WATCH_S3
     if (nimbleBluetooth && nimbleBluetooth->isConnected()) {
@@ -323,6 +324,7 @@ void drawAnalogClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
     const char *titleStr = "";
     // === Header ===
     graphics::drawCommonHeader(display, x, y, titleStr, true, true);
+    int line = 0;
 
 #ifdef T_WATCH_S3
     if (nimbleBluetooth && nimbleBluetooth->isConnected()) {
diff --git a/src/graphics/draw/UIRenderer.cpp b/src/graphics/draw/UIRenderer.cpp
index 5623c9026..e76a39398 100644
--- a/src/graphics/draw/UIRenderer.cpp
+++ b/src/graphics/draw/UIRenderer.cpp
@@ -1056,6 +1056,45 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU
         display->drawString(x, getTextPositions(display)[line++] + 2, latStr);
 #else
         snprintf(latStr, sizeof(latStr), " Lat: %.5f", geoCoord.getLatitude() * 1e-7);
+        // === 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];
+        snprintf(latStr, sizeof(latStr), "Lat: %.5f", geoCoord.getLatitude() * 1e-7);
         display->drawString(x, getTextPositions(display)[line++], latStr);
 #endif
 
@@ -1066,6 +1105,7 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU
         display->drawString(x, getTextPositions(display)[line++] + 4, lonStr);
 #else
         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 ===

From e6adb197e44c76a378210598f115ff482200a6b9 Mon Sep 17 00:00:00 2001
From: Jason P 
Date: Sat, 13 Sep 2025 15:06:36 -0500
Subject: [PATCH 276/691] Add formatting and menu picking for other GPS format
 options (#7974)

* Add back options for other GPS format options

* Rename variables and don't overlap elements

* Fix default value

* Should probably add a menu while I'm here!

* Shorten names just a bit to fit on screens

* Fix off by one

* Labels try to make things better

* Missed a label
---
 src/graphics/draw/MenuHandler.cpp | 55 ++++++++++++++++--
 src/graphics/draw/MenuHandler.h   |  2 +
 src/graphics/draw/UIRenderer.cpp  | 93 +++++++++++++++++++------------
 src/graphics/draw/UIRenderer.h    |  3 +-
 4 files changed, 112 insertions(+), 41 deletions(-)

diff --git a/src/graphics/draw/MenuHandler.cpp b/src/graphics/draw/MenuHandler.cpp
index 975fc7c0a..edf1d5d1f 100644
--- a/src/graphics/draw/MenuHandler.cpp
+++ b/src/graphics/draw/MenuHandler.cpp
@@ -633,11 +633,11 @@ void menuHandler::favoriteBaseMenu()
 
 void menuHandler::positionBaseMenu()
 {
-    enum optionsNumbers { Back, GPSToggle, CompassMenu, CompassCalibrate, enumEnd };
+    enum optionsNumbers { Back, GPSToggle, GPSFormat, CompassMenu, CompassCalibrate, enumEnd };
 
-    static const char *optionsArray[enumEnd] = {"Back", "GPS Toggle", "Compass"};
-    static int optionsEnumArray[enumEnd] = {Back, GPSToggle, CompassMenu};
-    int options = 3;
+    static const char *optionsArray[enumEnd] = {"Back", "GPS Toggle", "GPS Format", "Compass"};
+    static int optionsEnumArray[enumEnd] = {Back, GPSToggle, GPSFormat, CompassMenu};
+    int options = 4;
 
     if (accelerometerThread) {
         optionsArray[options] = "Compass Calibrate";
@@ -653,6 +653,9 @@ void menuHandler::positionBaseMenu()
         if (selected == GPSToggle) {
             menuQueue = gps_toggle_menu;
             screen->runNow();
+        } else if (selected == GPSFormat) {
+            menuQueue = gps_format_menu;
+            screen->runNow();
         } else if (selected == CompassMenu) {
             menuQueue = compass_point_north_menu;
             screen->runNow();
@@ -779,6 +782,47 @@ void menuHandler::GPSToggleMenu()
     bannerOptions.InitialSelected = config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_ENABLED ? 1 : 2;
     screen->showOverlayBanner(bannerOptions);
 }
+void menuHandler::GPSFormatMenu()
+{
+
+    static const char *optionsArray[] = {"Back",
+                                         isHighResolution ? "Decimal Degrees" : "DEC",
+                                         isHighResolution ? "Degrees Minutes Seconds" : "DMS",
+                                         isHighResolution ? "Universal Transverse Mercator" : "UTM",
+                                         isHighResolution ? "Military Grid Reference System" : "MGRS",
+                                         isHighResolution ? "Open Location Code" : "OLC",
+                                         isHighResolution ? "Ordnance Survey Grid Ref" : "OSGR"};
+    BannerOverlayOptions bannerOptions;
+    bannerOptions.message = "GPS Format";
+    bannerOptions.optionsArrayPtr = optionsArray;
+    bannerOptions.optionsCount = 7;
+    bannerOptions.bannerCallback = [](int selected) -> void {
+        if (selected == 1) {
+            config.display.gps_format = meshtastic_Config_DisplayConfig_GpsCoordinateFormat_DEC;
+            service->reloadConfig(SEGMENT_CONFIG);
+        } else if (selected == 2) {
+            config.display.gps_format = meshtastic_Config_DisplayConfig_GpsCoordinateFormat_DMS;
+            service->reloadConfig(SEGMENT_CONFIG);
+        } else if (selected == 3) {
+            config.display.gps_format = meshtastic_Config_DisplayConfig_GpsCoordinateFormat_UTM;
+            service->reloadConfig(SEGMENT_CONFIG);
+        } else if (selected == 4) {
+            config.display.gps_format = meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MGRS;
+            service->reloadConfig(SEGMENT_CONFIG);
+        } else if (selected == 5) {
+            config.display.gps_format = meshtastic_Config_DisplayConfig_GpsCoordinateFormat_OLC;
+            service->reloadConfig(SEGMENT_CONFIG);
+        } else if (selected == 6) {
+            config.display.gps_format = meshtastic_Config_DisplayConfig_GpsCoordinateFormat_OSGR;
+            service->reloadConfig(SEGMENT_CONFIG);
+        } else {
+            menuQueue = position_base_menu;
+            screen->runNow();
+        }
+    };
+    bannerOptions.InitialSelected = config.display.gps_format + 1;
+    screen->showOverlayBanner(bannerOptions);
+}
 #endif
 
 void menuHandler::BluetoothToggleMenu()
@@ -1452,6 +1496,9 @@ void menuHandler::handleMenuSwitch(OLEDDisplay *display)
     case gps_toggle_menu:
         GPSToggleMenu();
         break;
+    case gps_format_menu:
+        GPSFormatMenu();
+        break;
 #endif
     case compass_point_north_menu:
         compassNorthMenu();
diff --git a/src/graphics/draw/MenuHandler.h b/src/graphics/draw/MenuHandler.h
index 1bfdf128f..8afcba6c8 100644
--- a/src/graphics/draw/MenuHandler.h
+++ b/src/graphics/draw/MenuHandler.h
@@ -19,6 +19,7 @@ class menuHandler
         clock_menu,
         position_base_menu,
         gps_toggle_menu,
+        gps_format_menu,
         compass_point_north_menu,
         reset_node_db_menu,
         buzzermodemenupicker,
@@ -63,6 +64,7 @@ class menuHandler
     static void positionBaseMenu();
     static void compassNorthMenu();
     static void GPSToggleMenu();
+    static void GPSFormatMenu();
     static void BuzzerModeMenu();
     static void switchToMUIMenu();
     static void TFTColorPickerMenu(OLEDDisplay *display);
diff --git a/src/graphics/draw/UIRenderer.cpp b/src/graphics/draw/UIRenderer.cpp
index e76a39398..52398d905 100644
--- a/src/graphics/draw/UIRenderer.cpp
+++ b/src/graphics/draw/UIRenderer.cpp
@@ -116,64 +116,78 @@ void UIRenderer::drawGpsAltitude(OLEDDisplay *display, int16_t x, int16_t y, con
 }
 
 // Draw GPS status coordinates
-void UIRenderer::drawGpsCoordinates(OLEDDisplay *display, int16_t x, int16_t y, const meshtastic::GPSStatus *gps)
+void UIRenderer::drawGpsCoordinates(OLEDDisplay *display, int16_t x, int16_t y, const meshtastic::GPSStatus *gps,
+                                    const char *mode)
 {
     auto gpsFormat = config.display.gps_format;
     char displayLine[32];
 
     if (!gps->getIsConnected() && !config.position.fixed_position) {
         strcpy(displayLine, "No GPS present");
-        display->drawString(x + (display->getWidth() - (display->getStringWidth(displayLine))) / 2, y, displayLine);
+        display->drawString(x, y, displayLine);
     } else if (!gps->getHasLock() && !config.position.fixed_position) {
         strcpy(displayLine, "No GPS Lock");
-        display->drawString(x + (display->getWidth() - (display->getStringWidth(displayLine))) / 2, y, displayLine);
+        display->drawString(x, y, displayLine);
     } else {
 
         geoCoord.updateCoords(int32_t(gps->getLatitude()), int32_t(gps->getLongitude()), int32_t(gps->getAltitude()));
 
         if (gpsFormat != meshtastic_Config_DisplayConfig_GpsCoordinateFormat_DMS) {
-            char coordinateLine[22];
+            char coordinateLine_1[22];
+            char coordinateLine_2[22];
             if (gpsFormat == meshtastic_Config_DisplayConfig_GpsCoordinateFormat_DEC) { // Decimal Degrees
-                snprintf(coordinateLine, sizeof(coordinateLine), "%f %f", geoCoord.getLatitude() * 1e-7,
-                         geoCoord.getLongitude() * 1e-7);
+                snprintf(coordinateLine_1, sizeof(coordinateLine_1), "Lat: %f", geoCoord.getLatitude() * 1e-7);
+                snprintf(coordinateLine_2, sizeof(coordinateLine_2), "Lon: %f", geoCoord.getLongitude() * 1e-7);
             } else if (gpsFormat == meshtastic_Config_DisplayConfig_GpsCoordinateFormat_UTM) { // Universal Transverse Mercator
-                snprintf(coordinateLine, sizeof(coordinateLine), "%2i%1c %06u %07u", geoCoord.getUTMZone(), geoCoord.getUTMBand(),
-                         geoCoord.getUTMEasting(), geoCoord.getUTMNorthing());
+                snprintf(coordinateLine_1, sizeof(coordinateLine_1), "%2i%1c %06u E", geoCoord.getUTMZone(),
+                         geoCoord.getUTMBand(), geoCoord.getUTMEasting());
+                snprintf(coordinateLine_2, sizeof(coordinateLine_2), "%07u N", geoCoord.getUTMNorthing());
             } else if (gpsFormat == meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MGRS) { // Military Grid Reference System
-                snprintf(coordinateLine, sizeof(coordinateLine), "%2i%1c %1c%1c %05u %05u", geoCoord.getMGRSZone(),
-                         geoCoord.getMGRSBand(), geoCoord.getMGRSEast100k(), geoCoord.getMGRSNorth100k(),
-                         geoCoord.getMGRSEasting(), geoCoord.getMGRSNorthing());
+                snprintf(coordinateLine_1, sizeof(coordinateLine_1), "%2i%1c %1c%1c", geoCoord.getMGRSZone(),
+                         geoCoord.getMGRSBand(), geoCoord.getMGRSEast100k(), geoCoord.getMGRSNorth100k());
+                snprintf(coordinateLine_2, sizeof(coordinateLine_2), "%05u E %05u N", geoCoord.getMGRSEasting(),
+                         geoCoord.getMGRSNorthing());
             } else if (gpsFormat == meshtastic_Config_DisplayConfig_GpsCoordinateFormat_OLC) { // Open Location Code
-                geoCoord.getOLCCode(coordinateLine);
+                geoCoord.getOLCCode(coordinateLine_1);
+                coordinateLine_2[0] = '\0';
             } else if (gpsFormat == meshtastic_Config_DisplayConfig_GpsCoordinateFormat_OSGR) { // Ordnance Survey Grid Reference
-                if (geoCoord.getOSGRE100k() == 'I' || geoCoord.getOSGRN100k() == 'I') // OSGR is only valid around the UK region
-                    snprintf(coordinateLine, sizeof(coordinateLine), "%s", "Out of Boundary");
-                else
-                    snprintf(coordinateLine, sizeof(coordinateLine), "%1c%1c %05u %05u", geoCoord.getOSGRE100k(),
-                             geoCoord.getOSGRN100k(), geoCoord.getOSGREasting(), geoCoord.getOSGRNorthing());
+                if (geoCoord.getOSGRE100k() == 'I' || geoCoord.getOSGRN100k() == 'I') { // OSGR is only valid around the UK region
+                    snprintf(coordinateLine_1, sizeof(coordinateLine_1), "%s", "Out of Boundary");
+                    coordinateLine_2[0] = '\0';
+                } else {
+                    snprintf(coordinateLine_1, sizeof(coordinateLine_1), "%1c%1c", geoCoord.getOSGRE100k(),
+                             geoCoord.getOSGRN100k());
+                    snprintf(coordinateLine_2, sizeof(coordinateLine_2), "%05u E %05u N", geoCoord.getOSGREasting(),
+                             geoCoord.getOSGRNorthing());
+                }
             }
 
-            // If fixed position, display text "Fixed GPS" alternating with the coordinates.
-            if (config.position.fixed_position) {
-                if ((millis() / 10000) % 2) {
-                    display->drawString(x + (display->getWidth() - (display->getStringWidth(coordinateLine))) / 2, y,
-                                        coordinateLine);
-                } else {
-                    display->drawString(x + (display->getWidth() - (display->getStringWidth("Fixed GPS"))) / 2, y, "Fixed GPS");
+            if (strcmp(mode, "line1") == 0) {
+                display->drawString(x, y, coordinateLine_1);
+            } else if (strcmp(mode, "line2") == 0) {
+                display->drawString(x, y, coordinateLine_2);
+            } else if (strcmp(mode, "combined") == 0) {
+                display->drawString(x, y, coordinateLine_1);
+                if (coordinateLine_2[0] != '\0') {
+                    display->drawString(x + display->getStringWidth(coordinateLine_1), y, coordinateLine_2);
                 }
-            } else {
-                display->drawString(x + (display->getWidth() - (display->getStringWidth(coordinateLine))) / 2, y, coordinateLine);
             }
+
         } else {
-            char latLine[22];
-            char lonLine[22];
-            snprintf(latLine, sizeof(latLine), "%2i° %2i' %2u\" %1c", geoCoord.getDMSLatDeg(), geoCoord.getDMSLatMin(),
-                     geoCoord.getDMSLatSec(), geoCoord.getDMSLatCP());
-            snprintf(lonLine, sizeof(lonLine), "%3i° %2i' %2u\" %1c", geoCoord.getDMSLonDeg(), geoCoord.getDMSLonMin(),
-                     geoCoord.getDMSLonSec(), geoCoord.getDMSLonCP());
-            display->drawString(x + (display->getWidth() - (display->getStringWidth(latLine))) / 2, y - FONT_HEIGHT_SMALL * 1,
-                                latLine);
-            display->drawString(x + (display->getWidth() - (display->getStringWidth(lonLine))) / 2, y, lonLine);
+            char coordinateLine_1[22];
+            char coordinateLine_2[22];
+            snprintf(coordinateLine_1, sizeof(coordinateLine_1), "Lat: %2i° %2i' %2u\" %1c", geoCoord.getDMSLatDeg(),
+                     geoCoord.getDMSLatMin(), geoCoord.getDMSLatSec(), geoCoord.getDMSLatCP());
+            snprintf(coordinateLine_2, sizeof(coordinateLine_2), "Lon: %3i° %2i' %2u\" %1c", geoCoord.getDMSLonDeg(),
+                     geoCoord.getDMSLonMin(), geoCoord.getDMSLonSec(), geoCoord.getDMSLonCP());
+            if (strcmp(mode, "line1") == 0) {
+                display->drawString(x, y, coordinateLine_1);
+            } else if (strcmp(mode, "line2") == 0) {
+                display->drawString(x, y, coordinateLine_2);
+            } else { // both
+                display->drawString(x, y, coordinateLine_1);
+                display->drawString(x, y + 10, coordinateLine_2);
+            }
         }
     }
 }
@@ -1092,6 +1106,13 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU
             display->drawString(0, getTextPositions(display)[line++], "Last: ?");
         }
 
+        // === Third Row: Line 1 GPS Info ===
+        UIRenderer::drawGpsCoordinates(display, x, getTextPositions(display)[line++], gpsStatus, "line1");
+
+        if (config.display.gps_format != meshtastic_Config_DisplayConfig_GpsCoordinateFormat_OLC) {
+            // === Fourth Row: Line 2 GPS Info ===
+            UIRenderer::drawGpsCoordinates(display, x, getTextPositions(display)[line++], gpsStatus, "line2");
+        }
         // === Third Row: Latitude ===
         char latStr[32];
         snprintf(latStr, sizeof(latStr), "Lat: %.5f", geoCoord.getLatitude() * 1e-7);
@@ -1108,7 +1129,7 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU
         snprintf(lonStr, sizeof(lonStr), "Lon: %.5f", geoCoord.getLongitude() * 1e-7);
         display->drawString(x, getTextPositions(display)[line++], lonStr);
 
-        // === Fifth Row: Altitude ===
+        // === Fourth/Fifth Row: Altitude ===
         char DisplayLineTwo[32] = {0};
         int32_t alt = (strcmp(displayLine, "Phone GPS") == 0 && ourNode && nodeDB->hasValidPosition(ourNode))
                           ? ourNode->position.altitude
diff --git a/src/graphics/draw/UIRenderer.h b/src/graphics/draw/UIRenderer.h
index eada150f9..438d56cc2 100644
--- a/src/graphics/draw/UIRenderer.h
+++ b/src/graphics/draw/UIRenderer.h
@@ -38,7 +38,8 @@ class UIRenderer
 
     // GPS status functions
     static void drawGps(OLEDDisplay *display, int16_t x, int16_t y, const meshtastic::GPSStatus *gpsStatus);
-    static void drawGpsCoordinates(OLEDDisplay *display, int16_t x, int16_t y, const meshtastic::GPSStatus *gpsStatus);
+    static void drawGpsCoordinates(OLEDDisplay *display, int16_t x, int16_t y, const meshtastic::GPSStatus *gpsStatus,
+                                   const char *mode = "line1");
     static void drawGpsAltitude(OLEDDisplay *display, int16_t x, int16_t y, const meshtastic::GPSStatus *gpsStatus);
     static void drawGpsPowerStatus(OLEDDisplay *display, int16_t x, int16_t y, const meshtastic::GPSStatus *gpsStatus);
 

From 42fbb62f18a9b29db9afc2e29ee9259b294fae9a Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
 <41898282+github-actions[bot]@users.noreply.github.com>
Date: Fri, 19 Sep 2025 08:47:53 -0500
Subject: [PATCH 277/691] Update protobufs (#8038)

Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
---
 protobufs                                     |  2 +-
 src/mesh/generated/meshtastic/config.pb.h     | 49 +++++++------------
 .../generated/meshtastic/device_ui.pb.cpp     |  2 +
 src/mesh/generated/meshtastic/device_ui.pb.h  | 43 ++++++++++++++--
 src/mesh/generated/meshtastic/deviceonly.pb.h |  2 +-
 src/mesh/generated/meshtastic/localonly.pb.h  |  2 +-
 src/mesh/generated/meshtastic/mesh.pb.h       |  2 +
 7 files changed, 64 insertions(+), 38 deletions(-)

diff --git a/protobufs b/protobufs
index 27d9a99bd..6a8b80a10 160000
--- a/protobufs
+++ b/protobufs
@@ -1 +1 @@
-Subproject commit 27d9a99bd03efe35f91cafd7116c2386be5e26a1
+Subproject commit 6a8b80a10835acf48b2dfa2ad8aa0cc596219619
diff --git a/src/mesh/generated/meshtastic/config.pb.h b/src/mesh/generated/meshtastic/config.pb.h
index 59e55db3f..0453ecad2 100644
--- a/src/mesh/generated/meshtastic/config.pb.h
+++ b/src/mesh/generated/meshtastic/config.pb.h
@@ -173,28 +173,10 @@ typedef enum _meshtastic_Config_NetworkConfig_ProtocolFlags {
     meshtastic_Config_NetworkConfig_ProtocolFlags_UDP_BROADCAST = 1
 } meshtastic_Config_NetworkConfig_ProtocolFlags;
 
-/* How the GPS coordinates are displayed on the OLED screen. */
-typedef enum _meshtastic_Config_DisplayConfig_GpsCoordinateFormat {
-    /* GPS coordinates are displayed in the normal decimal degrees format:
- DD.DDDDDD DDD.DDDDDD */
-    meshtastic_Config_DisplayConfig_GpsCoordinateFormat_DEC = 0,
-    /* GPS coordinates are displayed in the degrees minutes seconds format:
- DD°MM'SS"C DDD°MM'SS"C, where C is the compass point representing the locations quadrant */
-    meshtastic_Config_DisplayConfig_GpsCoordinateFormat_DMS = 1,
-    /* Universal Transverse Mercator format:
- ZZB EEEEEE NNNNNNN, where Z is zone, B is band, E is easting, N is northing */
-    meshtastic_Config_DisplayConfig_GpsCoordinateFormat_UTM = 2,
-    /* Military Grid Reference System format:
- ZZB CD EEEEE NNNNN, where Z is zone, B is band, C is the east 100k square, D is the north 100k square,
- E is easting, N is northing */
-    meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MGRS = 3,
-    /* Open Location Code (aka Plus Codes). */
-    meshtastic_Config_DisplayConfig_GpsCoordinateFormat_OLC = 4,
-    /* Ordnance Survey Grid Reference (the National Grid System of the UK).
- Format: AB EEEEE NNNNN, where A is the east 100k square, B is the north 100k square,
- E is the easting, N is the northing */
-    meshtastic_Config_DisplayConfig_GpsCoordinateFormat_OSGR = 5
-} meshtastic_Config_DisplayConfig_GpsCoordinateFormat;
+/* Deprecated in 2.7.4: Unused */
+typedef enum _meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat {
+    meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat_UNUSED = 0
+} meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat;
 
 /* Unit display preference */
 typedef enum _meshtastic_Config_DisplayConfig_DisplayUnits {
@@ -491,7 +473,7 @@ typedef struct _meshtastic_Config_DisplayConfig {
     uint32_t screen_on_secs;
     /* Deprecated in 2.7.4: Unused
  How the GPS coordinates are formatted on the OLED screen. */
-    meshtastic_Config_DisplayConfig_GpsCoordinateFormat gps_format;
+    meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat gps_format;
     /* Automatically toggles to the next page on the screen like a carousel, based the specified interval in seconds.
  Potentially useful for devices without user buttons. */
     uint32_t auto_screen_carousel_secs;
@@ -515,6 +497,9 @@ typedef struct _meshtastic_Config_DisplayConfig {
     /* If false (default), the device will display the time in 24-hour format on screen.
  If true, the device will display the time in 12-hour format on screen. */
     bool use_12h_clock;
+    /* If false (default), the device will use short names for various display screens.
+ If true, node names will show in long format */
+    bool use_long_node_name;
 } meshtastic_Config_DisplayConfig;
 
 /* Lora Config */
@@ -678,9 +663,9 @@ extern "C" {
 #define _meshtastic_Config_NetworkConfig_ProtocolFlags_MAX meshtastic_Config_NetworkConfig_ProtocolFlags_UDP_BROADCAST
 #define _meshtastic_Config_NetworkConfig_ProtocolFlags_ARRAYSIZE ((meshtastic_Config_NetworkConfig_ProtocolFlags)(meshtastic_Config_NetworkConfig_ProtocolFlags_UDP_BROADCAST+1))
 
-#define _meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MIN meshtastic_Config_DisplayConfig_GpsCoordinateFormat_DEC
-#define _meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MAX meshtastic_Config_DisplayConfig_GpsCoordinateFormat_OSGR
-#define _meshtastic_Config_DisplayConfig_GpsCoordinateFormat_ARRAYSIZE ((meshtastic_Config_DisplayConfig_GpsCoordinateFormat)(meshtastic_Config_DisplayConfig_GpsCoordinateFormat_OSGR+1))
+#define _meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat_MIN meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat_UNUSED
+#define _meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat_MAX meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat_UNUSED
+#define _meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat_ARRAYSIZE ((meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat)(meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat_UNUSED+1))
 
 #define _meshtastic_Config_DisplayConfig_DisplayUnits_MIN meshtastic_Config_DisplayConfig_DisplayUnits_METRIC
 #define _meshtastic_Config_DisplayConfig_DisplayUnits_MAX meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL
@@ -721,7 +706,7 @@ extern "C" {
 #define meshtastic_Config_NetworkConfig_address_mode_ENUMTYPE meshtastic_Config_NetworkConfig_AddressMode
 
 
-#define meshtastic_Config_DisplayConfig_gps_format_ENUMTYPE meshtastic_Config_DisplayConfig_GpsCoordinateFormat
+#define meshtastic_Config_DisplayConfig_gps_format_ENUMTYPE meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat
 #define meshtastic_Config_DisplayConfig_units_ENUMTYPE meshtastic_Config_DisplayConfig_DisplayUnits
 #define meshtastic_Config_DisplayConfig_oled_ENUMTYPE meshtastic_Config_DisplayConfig_OledType
 #define meshtastic_Config_DisplayConfig_displaymode_ENUMTYPE meshtastic_Config_DisplayConfig_DisplayMode
@@ -742,7 +727,7 @@ extern "C" {
 #define meshtastic_Config_PowerConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0}
 #define meshtastic_Config_NetworkConfig_init_default {0, "", "", "", 0, _meshtastic_Config_NetworkConfig_AddressMode_MIN, false, meshtastic_Config_NetworkConfig_IpV4Config_init_default, "", 0, 0}
 #define meshtastic_Config_NetworkConfig_IpV4Config_init_default {0, 0, 0, 0}
-#define meshtastic_Config_DisplayConfig_init_default {0, _meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MIN, 0, 0, 0, _meshtastic_Config_DisplayConfig_DisplayUnits_MIN, _meshtastic_Config_DisplayConfig_OledType_MIN, _meshtastic_Config_DisplayConfig_DisplayMode_MIN, 0, 0, _meshtastic_Config_DisplayConfig_CompassOrientation_MIN, 0}
+#define meshtastic_Config_DisplayConfig_init_default {0, _meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat_MIN, 0, 0, 0, _meshtastic_Config_DisplayConfig_DisplayUnits_MIN, _meshtastic_Config_DisplayConfig_OledType_MIN, _meshtastic_Config_DisplayConfig_DisplayMode_MIN, 0, 0, _meshtastic_Config_DisplayConfig_CompassOrientation_MIN, 0, 0}
 #define meshtastic_Config_LoRaConfig_init_default {0, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, _meshtastic_Config_LoRaConfig_RegionCode_MIN, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}, 0, 0}
 #define meshtastic_Config_BluetoothConfig_init_default {0, _meshtastic_Config_BluetoothConfig_PairingMode_MIN, 0}
 #define meshtastic_Config_SecurityConfig_init_default {{0, {0}}, {0, {0}}, 0, {{0, {0}}, {0, {0}}, {0, {0}}}, 0, 0, 0, 0}
@@ -753,7 +738,7 @@ extern "C" {
 #define meshtastic_Config_PowerConfig_init_zero  {0, 0, 0, 0, 0, 0, 0, 0, 0}
 #define meshtastic_Config_NetworkConfig_init_zero {0, "", "", "", 0, _meshtastic_Config_NetworkConfig_AddressMode_MIN, false, meshtastic_Config_NetworkConfig_IpV4Config_init_zero, "", 0, 0}
 #define meshtastic_Config_NetworkConfig_IpV4Config_init_zero {0, 0, 0, 0}
-#define meshtastic_Config_DisplayConfig_init_zero {0, _meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MIN, 0, 0, 0, _meshtastic_Config_DisplayConfig_DisplayUnits_MIN, _meshtastic_Config_DisplayConfig_OledType_MIN, _meshtastic_Config_DisplayConfig_DisplayMode_MIN, 0, 0, _meshtastic_Config_DisplayConfig_CompassOrientation_MIN, 0}
+#define meshtastic_Config_DisplayConfig_init_zero {0, _meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat_MIN, 0, 0, 0, _meshtastic_Config_DisplayConfig_DisplayUnits_MIN, _meshtastic_Config_DisplayConfig_OledType_MIN, _meshtastic_Config_DisplayConfig_DisplayMode_MIN, 0, 0, _meshtastic_Config_DisplayConfig_CompassOrientation_MIN, 0, 0}
 #define meshtastic_Config_LoRaConfig_init_zero   {0, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, _meshtastic_Config_LoRaConfig_RegionCode_MIN, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}, 0, 0}
 #define meshtastic_Config_BluetoothConfig_init_zero {0, _meshtastic_Config_BluetoothConfig_PairingMode_MIN, 0}
 #define meshtastic_Config_SecurityConfig_init_zero {{0, {0}}, {0, {0}}, 0, {{0, {0}}, {0, {0}}, {0, {0}}}, 0, 0, 0, 0}
@@ -820,6 +805,7 @@ extern "C" {
 #define meshtastic_Config_DisplayConfig_wake_on_tap_or_motion_tag 10
 #define meshtastic_Config_DisplayConfig_compass_orientation_tag 11
 #define meshtastic_Config_DisplayConfig_use_12h_clock_tag 12
+#define meshtastic_Config_DisplayConfig_use_long_node_name_tag 13
 #define meshtastic_Config_LoRaConfig_use_preset_tag 1
 #define meshtastic_Config_LoRaConfig_modem_preset_tag 2
 #define meshtastic_Config_LoRaConfig_bandwidth_tag 3
@@ -965,7 +951,8 @@ X(a, STATIC,   SINGULAR, UENUM,    displaymode,       8) \
 X(a, STATIC,   SINGULAR, BOOL,     heading_bold,      9) \
 X(a, STATIC,   SINGULAR, BOOL,     wake_on_tap_or_motion,  10) \
 X(a, STATIC,   SINGULAR, UENUM,    compass_orientation,  11) \
-X(a, STATIC,   SINGULAR, BOOL,     use_12h_clock,    12)
+X(a, STATIC,   SINGULAR, BOOL,     use_12h_clock,    12) \
+X(a, STATIC,   SINGULAR, BOOL,     use_long_node_name,  13)
 #define meshtastic_Config_DisplayConfig_CALLBACK NULL
 #define meshtastic_Config_DisplayConfig_DEFAULT NULL
 
@@ -1043,7 +1030,7 @@ extern const pb_msgdesc_t meshtastic_Config_SessionkeyConfig_msg;
 #define MESHTASTIC_MESHTASTIC_CONFIG_PB_H_MAX_SIZE meshtastic_Config_size
 #define meshtastic_Config_BluetoothConfig_size   10
 #define meshtastic_Config_DeviceConfig_size      100
-#define meshtastic_Config_DisplayConfig_size     32
+#define meshtastic_Config_DisplayConfig_size     34
 #define meshtastic_Config_LoRaConfig_size        85
 #define meshtastic_Config_NetworkConfig_IpV4Config_size 20
 #define meshtastic_Config_NetworkConfig_size     204
diff --git a/src/mesh/generated/meshtastic/device_ui.pb.cpp b/src/mesh/generated/meshtastic/device_ui.pb.cpp
index 2fc8d9461..01940265f 100644
--- a/src/mesh/generated/meshtastic/device_ui.pb.cpp
+++ b/src/mesh/generated/meshtastic/device_ui.pb.cpp
@@ -28,3 +28,5 @@ PB_BIND(meshtastic_Map, meshtastic_Map, AUTO)
 
 
 
+
+
diff --git a/src/mesh/generated/meshtastic/device_ui.pb.h b/src/mesh/generated/meshtastic/device_ui.pb.h
index 8f693e570..d9eb90773 100644
--- a/src/mesh/generated/meshtastic/device_ui.pb.h
+++ b/src/mesh/generated/meshtastic/device_ui.pb.h
@@ -74,6 +74,32 @@ typedef enum _meshtastic_Language {
     meshtastic_Language_TRADITIONAL_CHINESE = 31
 } meshtastic_Language;
 
+/* How the GPS coordinates are displayed on the OLED screen. */
+typedef enum _meshtastic_DeviceUIConfig_GpsCoordinateFormat {
+    /* GPS coordinates are displayed in the normal decimal degrees format:
+ DD.DDDDDD DDD.DDDDDD */
+    meshtastic_DeviceUIConfig_GpsCoordinateFormat_DEC = 0,
+    /* GPS coordinates are displayed in the degrees minutes seconds format:
+ DD°MM'SS"C DDD°MM'SS"C, where C is the compass point representing the locations quadrant */
+    meshtastic_DeviceUIConfig_GpsCoordinateFormat_DMS = 1,
+    /* Universal Transverse Mercator format:
+ ZZB EEEEEE NNNNNNN, where Z is zone, B is band, E is easting, N is northing */
+    meshtastic_DeviceUIConfig_GpsCoordinateFormat_UTM = 2,
+    /* Military Grid Reference System format:
+ ZZB CD EEEEE NNNNN, where Z is zone, B is band, C is the east 100k square, D is the north 100k square,
+ E is easting, N is northing */
+    meshtastic_DeviceUIConfig_GpsCoordinateFormat_MGRS = 3,
+    /* Open Location Code (aka Plus Codes). */
+    meshtastic_DeviceUIConfig_GpsCoordinateFormat_OLC = 4,
+    /* Ordnance Survey Grid Reference (the National Grid System of the UK).
+ Format: AB EEEEE NNNNN, where A is the east 100k square, B is the north 100k square,
+ E is the easting, N is the northing */
+    meshtastic_DeviceUIConfig_GpsCoordinateFormat_OSGR = 5,
+    /* Maidenhead Locator System
+ Described here: https://en.wikipedia.org/wiki/Maidenhead_Locator_System */
+    meshtastic_DeviceUIConfig_GpsCoordinateFormat_MLS = 6
+} meshtastic_DeviceUIConfig_GpsCoordinateFormat;
+
 /* Struct definitions */
 typedef struct _meshtastic_NodeFilter {
     /* Filter unknown nodes */
@@ -163,6 +189,8 @@ typedef struct _meshtastic_DeviceUIConfig {
     /* Clockface analog style
  true for analog clockface, false for digital clockface */
     bool is_clockface_analog;
+    /* How the GPS coordinates are formatted on the OLED screen. */
+    meshtastic_DeviceUIConfig_GpsCoordinateFormat gps_format;
 } meshtastic_DeviceUIConfig;
 
 
@@ -183,9 +211,14 @@ extern "C" {
 #define _meshtastic_Language_MAX meshtastic_Language_TRADITIONAL_CHINESE
 #define _meshtastic_Language_ARRAYSIZE ((meshtastic_Language)(meshtastic_Language_TRADITIONAL_CHINESE+1))
 
+#define _meshtastic_DeviceUIConfig_GpsCoordinateFormat_MIN meshtastic_DeviceUIConfig_GpsCoordinateFormat_DEC
+#define _meshtastic_DeviceUIConfig_GpsCoordinateFormat_MAX meshtastic_DeviceUIConfig_GpsCoordinateFormat_MLS
+#define _meshtastic_DeviceUIConfig_GpsCoordinateFormat_ARRAYSIZE ((meshtastic_DeviceUIConfig_GpsCoordinateFormat)(meshtastic_DeviceUIConfig_GpsCoordinateFormat_MLS+1))
+
 #define meshtastic_DeviceUIConfig_theme_ENUMTYPE meshtastic_Theme
 #define meshtastic_DeviceUIConfig_language_ENUMTYPE meshtastic_Language
 #define meshtastic_DeviceUIConfig_compass_mode_ENUMTYPE meshtastic_CompassMode
+#define meshtastic_DeviceUIConfig_gps_format_ENUMTYPE meshtastic_DeviceUIConfig_GpsCoordinateFormat
 
 
 
@@ -193,12 +226,12 @@ extern "C" {
 
 
 /* Initializer values for message structs */
-#define meshtastic_DeviceUIConfig_init_default   {0, 0, 0, 0, 0, 0, _meshtastic_Theme_MIN, 0, 0, 0, _meshtastic_Language_MIN, false, meshtastic_NodeFilter_init_default, false, meshtastic_NodeHighlight_init_default, {0, {0}}, false, meshtastic_Map_init_default, _meshtastic_CompassMode_MIN, 0, 0}
+#define meshtastic_DeviceUIConfig_init_default   {0, 0, 0, 0, 0, 0, _meshtastic_Theme_MIN, 0, 0, 0, _meshtastic_Language_MIN, false, meshtastic_NodeFilter_init_default, false, meshtastic_NodeHighlight_init_default, {0, {0}}, false, meshtastic_Map_init_default, _meshtastic_CompassMode_MIN, 0, 0, _meshtastic_DeviceUIConfig_GpsCoordinateFormat_MIN}
 #define meshtastic_NodeFilter_init_default       {0, 0, 0, 0, 0, "", 0}
 #define meshtastic_NodeHighlight_init_default    {0, 0, 0, 0, ""}
 #define meshtastic_GeoPoint_init_default         {0, 0, 0}
 #define meshtastic_Map_init_default              {false, meshtastic_GeoPoint_init_default, "", 0}
-#define meshtastic_DeviceUIConfig_init_zero      {0, 0, 0, 0, 0, 0, _meshtastic_Theme_MIN, 0, 0, 0, _meshtastic_Language_MIN, false, meshtastic_NodeFilter_init_zero, false, meshtastic_NodeHighlight_init_zero, {0, {0}}, false, meshtastic_Map_init_zero, _meshtastic_CompassMode_MIN, 0, 0}
+#define meshtastic_DeviceUIConfig_init_zero      {0, 0, 0, 0, 0, 0, _meshtastic_Theme_MIN, 0, 0, 0, _meshtastic_Language_MIN, false, meshtastic_NodeFilter_init_zero, false, meshtastic_NodeHighlight_init_zero, {0, {0}}, false, meshtastic_Map_init_zero, _meshtastic_CompassMode_MIN, 0, 0, _meshtastic_DeviceUIConfig_GpsCoordinateFormat_MIN}
 #define meshtastic_NodeFilter_init_zero          {0, 0, 0, 0, 0, "", 0}
 #define meshtastic_NodeHighlight_init_zero       {0, 0, 0, 0, ""}
 #define meshtastic_GeoPoint_init_zero            {0, 0, 0}
@@ -241,6 +274,7 @@ extern "C" {
 #define meshtastic_DeviceUIConfig_compass_mode_tag 16
 #define meshtastic_DeviceUIConfig_screen_rgb_color_tag 17
 #define meshtastic_DeviceUIConfig_is_clockface_analog_tag 18
+#define meshtastic_DeviceUIConfig_gps_format_tag 19
 
 /* Struct field encoding specification for nanopb */
 #define meshtastic_DeviceUIConfig_FIELDLIST(X, a) \
@@ -261,7 +295,8 @@ X(a, STATIC,   SINGULAR, BYTES,    calibration_data,  14) \
 X(a, STATIC,   OPTIONAL, MESSAGE,  map_data,         15) \
 X(a, STATIC,   SINGULAR, UENUM,    compass_mode,     16) \
 X(a, STATIC,   SINGULAR, UINT32,   screen_rgb_color,  17) \
-X(a, STATIC,   SINGULAR, BOOL,     is_clockface_analog,  18)
+X(a, STATIC,   SINGULAR, BOOL,     is_clockface_analog,  18) \
+X(a, STATIC,   SINGULAR, UENUM,    gps_format,       19)
 #define meshtastic_DeviceUIConfig_CALLBACK NULL
 #define meshtastic_DeviceUIConfig_DEFAULT NULL
 #define meshtastic_DeviceUIConfig_node_filter_MSGTYPE meshtastic_NodeFilter
@@ -318,7 +353,7 @@ extern const pb_msgdesc_t meshtastic_Map_msg;
 
 /* Maximum encoded size of messages (where known) */
 #define MESHTASTIC_MESHTASTIC_DEVICE_UI_PB_H_MAX_SIZE meshtastic_DeviceUIConfig_size
-#define meshtastic_DeviceUIConfig_size           201
+#define meshtastic_DeviceUIConfig_size           204
 #define meshtastic_GeoPoint_size                 33
 #define meshtastic_Map_size                      58
 #define meshtastic_NodeFilter_size               47
diff --git a/src/mesh/generated/meshtastic/deviceonly.pb.h b/src/mesh/generated/meshtastic/deviceonly.pb.h
index 9b6330596..c6cad8a2a 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        2273
+#define meshtastic_BackupPreferences_size        2275
 #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 da224fb94..1fa4f33dd 100644
--- a/src/mesh/generated/meshtastic/localonly.pb.h
+++ b/src/mesh/generated/meshtastic/localonly.pb.h
@@ -187,7 +187,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_LocalConfig_size              749
 #define meshtastic_LocalModuleConfig_size        671
 
 #ifdef __cplusplus
diff --git a/src/mesh/generated/meshtastic/mesh.pb.h b/src/mesh/generated/meshtastic/mesh.pb.h
index 294f0beac..6292ce070 100644
--- a/src/mesh/generated/meshtastic/mesh.pb.h
+++ b/src/mesh/generated/meshtastic/mesh.pb.h
@@ -276,6 +276,8 @@ typedef enum _meshtastic_HardwareModel {
     meshtastic_HardwareModel_HELTEC_V4 = 110,
     /* M5Stack C6L */
     meshtastic_HardwareModel_M5STACK_C6L = 111,
+    /* M5Stack Cardputer Adv */
+    meshtastic_HardwareModel_M5STACK_CARDPUTER_ADV = 112,
     /* ------------------------------------------------------------------------------------------------------------------------------------------
  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 c8f69913d693b06831840781694c4a267b51a449 Mon Sep 17 00:00:00 2001
From: Jason P 
Date: Sat, 13 Sep 2025 15:06:36 -0500
Subject: [PATCH 278/691] Add formatting and menu picking for other GPS format
 options (#7974)

* Add back options for other GPS format options

* Rename variables and don't overlap elements

* Fix default value

* Should probably add a menu while I'm here!

* Shorten names just a bit to fit on screens

* Fix off by one

* Labels try to make things better

* Missed a label

From af26408d73a0adbb858caaa98b006c67f1d4f92f 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 279/691] 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 cc0cfca08..a4c7464b8 100644
--- a/src/gps/GPS.cpp
+++ b/src/gps/GPS.cpp
@@ -807,6 +807,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;
     }
@@ -1239,9 +1247,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 46701f611..cba767460 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 72b9a02f3e278d409f2be114203a1969ee6162f4 Mon Sep 17 00:00:00 2001
From: Tom <116762865+NomDeTom@users.noreply.github.com>
Date: Tue, 16 Sep 2025 12:41:22 +0100
Subject: [PATCH 280/691] (resubmission) Manual GitHub actions to allow
 building one target or arch (#7997)

* Reset the modified files

* Fix some changes

* Fix some changes

* Trunk. That is all.

---------

Co-authored-by: Tom <116762865+Nestpebble@users.noreply.github.com>

From 8095261dfd615a615a5b1141ddb0e03ef29d9760 Mon Sep 17 00:00:00 2001
From: Austin 
Date: Tue, 9 Sep 2025 17:01:04 -0400
Subject: [PATCH 281/691] PPA: Enable Ubuntu 25.10 (questing) (#7940)

---
 .github/workflows/daily_packaging.yml  | 4 ++--
 .github/workflows/release_channels.yml | 6 +++---
 2 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/.github/workflows/daily_packaging.yml b/.github/workflows/daily_packaging.yml
index 7e2316e3d..392faeb8a 100644
--- a/.github/workflows/daily_packaging.yml
+++ b/.github/workflows/daily_packaging.yml
@@ -33,8 +33,8 @@ jobs:
       fail-fast: false
       matrix:
         series:
-          - jammy # 22.04
-          - noble # 24.04
+          - jammy # 22.04 LTS
+          - noble # 24.04 LTS
           - plucky # 25.04
           - questing # 25.10
     uses: ./.github/workflows/package_ppa.yml
diff --git a/.github/workflows/release_channels.yml b/.github/workflows/release_channels.yml
index 486f4b1a6..d5d642db4 100644
--- a/.github/workflows/release_channels.yml
+++ b/.github/workflows/release_channels.yml
@@ -21,10 +21,10 @@ jobs:
       fail-fast: false
       matrix:
         series:
-          - jammy # 22.04
-          - noble # 24.04
+          - jammy # 22.04 LTS
+          - noble # 24.04 LTS
           - plucky # 25.04
-          # - questing # 25.10
+          - questing # 25.10
     uses: ./.github/workflows/package_ppa.yml
     with:
       ppa_repo: |-

From f32e06a3218af120d66f02dfe23e2dd6e259b3ec Mon Sep 17 00:00:00 2001
From: Jason P 
Date: Fri, 19 Sep 2025 10:51:07 -0500
Subject: [PATCH 282/691] Update Protobuf usage, add MLS, fix clock (#8041)

---
 src/graphics/draw/ClockRenderer.cpp |  23 +----
 src/graphics/draw/DebugRenderer.cpp |   3 +-
 src/graphics/draw/MenuHandler.cpp   |  22 ++--
 src/graphics/draw/UIRenderer.cpp    | 149 ++++++++++------------------
 4 files changed, 69 insertions(+), 128 deletions(-)

diff --git a/src/graphics/draw/ClockRenderer.cpp b/src/graphics/draw/ClockRenderer.cpp
index 5afcf094c..d0c4e5c6e 100644
--- a/src/graphics/draw/ClockRenderer.cpp
+++ b/src/graphics/draw/ClockRenderer.cpp
@@ -2,6 +2,7 @@
 #if HAS_SCREEN
 #include "ClockRenderer.h"
 #include "NodeDB.h"
+#include "UIRenderer.h"
 #include "configuration.h"
 #include "gps/GeoCoord.h"
 #include "gps/RTC.h"
@@ -300,15 +301,6 @@ void drawDigitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int1
     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)
@@ -522,19 +514,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];
-        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 fb35134fd..2c641accb 100644
--- a/src/graphics/draw/DebugRenderer.cpp
+++ b/src/graphics/draw/DebugRenderer.cpp
@@ -330,8 +330,7 @@ void drawFrameSettings(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t
 #if HAS_GPS
     if (config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_ENABLED) {
         // Line 3
-        if (config.display.gps_format !=
-            meshtastic_Config_DisplayConfig_GpsCoordinateFormat_DMS) // if DMS then don't draw altitude
+        if (uiconfig.gps_format != meshtastic_DeviceUIConfig_GpsCoordinateFormat_DMS) // if DMS then don't draw altitude
             UIRenderer::drawGpsAltitude(display, x, y + FONT_HEIGHT_SMALL * 2, gpsStatus);
 
         // Line 4
diff --git a/src/graphics/draw/MenuHandler.cpp b/src/graphics/draw/MenuHandler.cpp
index edf1d5d1f..c98217e96 100644
--- a/src/graphics/draw/MenuHandler.cpp
+++ b/src/graphics/draw/MenuHandler.cpp
@@ -791,36 +791,40 @@ void menuHandler::GPSFormatMenu()
                                          isHighResolution ? "Universal Transverse Mercator" : "UTM",
                                          isHighResolution ? "Military Grid Reference System" : "MGRS",
                                          isHighResolution ? "Open Location Code" : "OLC",
-                                         isHighResolution ? "Ordnance Survey Grid Ref" : "OSGR"};
+                                         isHighResolution ? "Ordnance Survey Grid Ref" : "OSGR",
+                                         isHighResolution ? "Maidenhead Locator" : "MLS"};
     BannerOverlayOptions bannerOptions;
     bannerOptions.message = "GPS Format";
     bannerOptions.optionsArrayPtr = optionsArray;
-    bannerOptions.optionsCount = 7;
+    bannerOptions.optionsCount = 8;
     bannerOptions.bannerCallback = [](int selected) -> void {
         if (selected == 1) {
-            config.display.gps_format = meshtastic_Config_DisplayConfig_GpsCoordinateFormat_DEC;
+            uiconfig.gps_format = meshtastic_DeviceUIConfig_GpsCoordinateFormat_DEC;
             service->reloadConfig(SEGMENT_CONFIG);
         } else if (selected == 2) {
-            config.display.gps_format = meshtastic_Config_DisplayConfig_GpsCoordinateFormat_DMS;
+            uiconfig.gps_format = meshtastic_DeviceUIConfig_GpsCoordinateFormat_DMS;
             service->reloadConfig(SEGMENT_CONFIG);
         } else if (selected == 3) {
-            config.display.gps_format = meshtastic_Config_DisplayConfig_GpsCoordinateFormat_UTM;
+            uiconfig.gps_format = meshtastic_DeviceUIConfig_GpsCoordinateFormat_UTM;
             service->reloadConfig(SEGMENT_CONFIG);
         } else if (selected == 4) {
-            config.display.gps_format = meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MGRS;
+            uiconfig.gps_format = meshtastic_DeviceUIConfig_GpsCoordinateFormat_MGRS;
             service->reloadConfig(SEGMENT_CONFIG);
         } else if (selected == 5) {
-            config.display.gps_format = meshtastic_Config_DisplayConfig_GpsCoordinateFormat_OLC;
+            uiconfig.gps_format = meshtastic_DeviceUIConfig_GpsCoordinateFormat_OLC;
             service->reloadConfig(SEGMENT_CONFIG);
         } else if (selected == 6) {
-            config.display.gps_format = meshtastic_Config_DisplayConfig_GpsCoordinateFormat_OSGR;
+            uiconfig.gps_format = meshtastic_DeviceUIConfig_GpsCoordinateFormat_OSGR;
+            service->reloadConfig(SEGMENT_CONFIG);
+        } else if (selected == 7) {
+            uiconfig.gps_format = meshtastic_DeviceUIConfig_GpsCoordinateFormat_MLS;
             service->reloadConfig(SEGMENT_CONFIG);
         } else {
             menuQueue = position_base_menu;
             screen->runNow();
         }
     };
-    bannerOptions.InitialSelected = config.display.gps_format + 1;
+    bannerOptions.InitialSelected = uiconfig.gps_format + 1;
     screen->showOverlayBanner(bannerOptions);
 }
 #endif
diff --git a/src/graphics/draw/UIRenderer.cpp b/src/graphics/draw/UIRenderer.cpp
index 52398d905..46a7a9ea0 100644
--- a/src/graphics/draw/UIRenderer.cpp
+++ b/src/graphics/draw/UIRenderer.cpp
@@ -119,7 +119,7 @@ void UIRenderer::drawGpsAltitude(OLEDDisplay *display, int16_t x, int16_t y, con
 void UIRenderer::drawGpsCoordinates(OLEDDisplay *display, int16_t x, int16_t y, const meshtastic::GPSStatus *gps,
                                     const char *mode)
 {
-    auto gpsFormat = config.display.gps_format;
+    auto gpsFormat = uiconfig.gps_format;
     char displayLine[32];
 
     if (!gps->getIsConnected() && !config.position.fixed_position) {
@@ -132,25 +132,25 @@ void UIRenderer::drawGpsCoordinates(OLEDDisplay *display, int16_t x, int16_t y,
 
         geoCoord.updateCoords(int32_t(gps->getLatitude()), int32_t(gps->getLongitude()), int32_t(gps->getAltitude()));
 
-        if (gpsFormat != meshtastic_Config_DisplayConfig_GpsCoordinateFormat_DMS) {
+        if (gpsFormat != meshtastic_DeviceUIConfig_GpsCoordinateFormat_DMS) {
             char coordinateLine_1[22];
             char coordinateLine_2[22];
-            if (gpsFormat == meshtastic_Config_DisplayConfig_GpsCoordinateFormat_DEC) { // Decimal Degrees
+            if (gpsFormat == meshtastic_DeviceUIConfig_GpsCoordinateFormat_DEC) { // Decimal Degrees
                 snprintf(coordinateLine_1, sizeof(coordinateLine_1), "Lat: %f", geoCoord.getLatitude() * 1e-7);
                 snprintf(coordinateLine_2, sizeof(coordinateLine_2), "Lon: %f", geoCoord.getLongitude() * 1e-7);
-            } else if (gpsFormat == meshtastic_Config_DisplayConfig_GpsCoordinateFormat_UTM) { // Universal Transverse Mercator
+            } else if (gpsFormat == meshtastic_DeviceUIConfig_GpsCoordinateFormat_UTM) { // Universal Transverse Mercator
                 snprintf(coordinateLine_1, sizeof(coordinateLine_1), "%2i%1c %06u E", geoCoord.getUTMZone(),
                          geoCoord.getUTMBand(), geoCoord.getUTMEasting());
                 snprintf(coordinateLine_2, sizeof(coordinateLine_2), "%07u N", geoCoord.getUTMNorthing());
-            } else if (gpsFormat == meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MGRS) { // Military Grid Reference System
+            } else if (gpsFormat == meshtastic_DeviceUIConfig_GpsCoordinateFormat_MGRS) { // Military Grid Reference System
                 snprintf(coordinateLine_1, sizeof(coordinateLine_1), "%2i%1c %1c%1c", geoCoord.getMGRSZone(),
                          geoCoord.getMGRSBand(), geoCoord.getMGRSEast100k(), geoCoord.getMGRSNorth100k());
                 snprintf(coordinateLine_2, sizeof(coordinateLine_2), "%05u E %05u N", geoCoord.getMGRSEasting(),
                          geoCoord.getMGRSNorthing());
-            } else if (gpsFormat == meshtastic_Config_DisplayConfig_GpsCoordinateFormat_OLC) { // Open Location Code
+            } else if (gpsFormat == meshtastic_DeviceUIConfig_GpsCoordinateFormat_OLC) { // Open Location Code
                 geoCoord.getOLCCode(coordinateLine_1);
                 coordinateLine_2[0] = '\0';
-            } else if (gpsFormat == meshtastic_Config_DisplayConfig_GpsCoordinateFormat_OSGR) { // Ordnance Survey Grid Reference
+            } else if (gpsFormat == meshtastic_DeviceUIConfig_GpsCoordinateFormat_OSGR) { // Ordnance Survey Grid Reference
                 if (geoCoord.getOSGRE100k() == 'I' || geoCoord.getOSGRN100k() == 'I') { // OSGR is only valid around the UK region
                     snprintf(coordinateLine_1, sizeof(coordinateLine_1), "%s", "Out of Boundary");
                     coordinateLine_2[0] = '\0';
@@ -160,6 +160,48 @@ void UIRenderer::drawGpsCoordinates(OLEDDisplay *display, int16_t x, int16_t y,
                     snprintf(coordinateLine_2, sizeof(coordinateLine_2), "%05u E %05u N", geoCoord.getOSGREasting(),
                              geoCoord.getOSGRNorthing());
                 }
+            } else if (gpsFormat == meshtastic_DeviceUIConfig_GpsCoordinateFormat_MLS) { // Maidenhead Locator System
+                double lat = geoCoord.getLatitude() * 1e-7;
+                double lon = geoCoord.getLongitude() * 1e-7;
+
+                // Normalize
+                if (lat > 90.0)
+                    lat = 90.0;
+                if (lat < -90.0)
+                    lat = -90.0;
+                while (lon < -180.0)
+                    lon += 360.0;
+                while (lon >= 180.0)
+                    lon -= 360.0;
+
+                double adjLon = lon + 180.0;
+                double adjLat = lat + 90.0;
+
+                char maiden[10]; // enough for 8-char + null
+
+                // Field (2 letters)
+                int lonField = int(adjLon / 20.0);
+                int latField = int(adjLat / 10.0);
+                adjLon -= lonField * 20.0;
+                adjLat -= latField * 10.0;
+
+                // Square (2 digits)
+                int lonSquare = int(adjLon / 2.0);
+                int latSquare = int(adjLat / 1.0);
+                adjLon -= lonSquare * 2.0;
+                adjLat -= latSquare * 1.0;
+
+                // Subsquare (2 letters)
+                double lonUnit = 2.0 / 24.0;
+                double latUnit = 1.0 / 24.0;
+                int lonSub = int(adjLon / lonUnit);
+                int latSub = int(adjLat / latUnit);
+
+                snprintf(maiden, sizeof(maiden), "%c%c%c%c%c%c", 'A' + lonField, 'A' + latField, '0' + lonSquare, '0' + latSquare,
+                         'A' + lonSub, 'A' + latSub);
+
+                snprintf(coordinateLine_1, sizeof(coordinateLine_1), "MH: %s", maiden);
+                coordinateLine_2[0] = '\0'; // only need one line
             }
 
             if (strcmp(mode, "line1") == 0) {
@@ -1014,19 +1056,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);
-#if !defined(M5STACK_UNITC6L)
-            display->drawString(0, getTextPositions(display)[line++], fullLine);
-#endif
-        */
-
         // === Second Row: Last GPS Fix ===
         if (gpsStatus->getLastFixMillis() > 0) {
             uint32_t delta = (millis() - gpsStatus->getLastFixMillis()) / 1000; // seconds since last fix
@@ -1039,54 +1068,11 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU
 #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);
+                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 {
-                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];
-#if defined(M5STACK_UNITC6L)
-        snprintf(latStr, sizeof(latStr), "Lat:%.5f", geoCoord.getLatitude() * 1e-7);
-        display->drawString(x, getTextPositions(display)[line++] + 2, latStr);
-#else
-        snprintf(latStr, sizeof(latStr), " Lat: %.5f", geoCoord.getLatitude() * 1e-7);
-        // === 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);
+                snprintf(buf, sizeof(buf), "Last: %um", mins);
             }
 #else
             // Non E-Ink: include seconds where useful
@@ -1109,38 +1095,11 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU
         // === Third Row: Line 1 GPS Info ===
         UIRenderer::drawGpsCoordinates(display, x, getTextPositions(display)[line++], gpsStatus, "line1");
 
-        if (config.display.gps_format != meshtastic_Config_DisplayConfig_GpsCoordinateFormat_OLC) {
+        if (uiconfig.gps_format != meshtastic_DeviceUIConfig_GpsCoordinateFormat_OLC &&
+            uiconfig.gps_format != meshtastic_DeviceUIConfig_GpsCoordinateFormat_MLS) {
             // === Fourth Row: Line 2 GPS Info ===
             UIRenderer::drawGpsCoordinates(display, x, getTextPositions(display)[line++], gpsStatus, "line2");
         }
-        // === Third Row: Latitude ===
-        char latStr[32];
-        snprintf(latStr, sizeof(latStr), "Lat: %.5f", geoCoord.getLatitude() * 1e-7);
-        display->drawString(x, getTextPositions(display)[line++], latStr);
-#endif
-
-        // === Fourth Row: Longitude ===
-        char lonStr[32];
-#if defined(M5STACK_UNITC6L)
-        snprintf(lonStr, sizeof(lonStr), "Lon:%.3f", geoCoord.getLongitude() * 1e-7);
-        display->drawString(x, getTextPositions(display)[line++] + 4, lonStr);
-#else
-        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);
-
-        // === Fourth/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 {
-            snprintf(DisplayLineTwo, sizeof(DisplayLineTwo), "Alt: %.0im", geoCoord.getAltitude());
-        }
-        display->drawString(x, getTextPositions(display)[line++], DisplayLineTwo);
-#endif
     }
 #if !defined(M5STACK_UNITC6L)
     // === Draw Compass if heading is valid ===

From 3a63a56cffba779b5f0973c493e4d22c22b22bfe Mon Sep 17 00:00:00 2001
From: WillyJL 
Date: Fri, 19 Sep 2025 19:00:59 +0200
Subject: [PATCH 283/691] Fix build fail on develop branch (#8043)

---
 src/graphics/draw/ClockRenderer.cpp | 1 +
 src/graphics/draw/MenuHandler.h     | 1 -
 2 files changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/graphics/draw/ClockRenderer.cpp b/src/graphics/draw/ClockRenderer.cpp
index d0c4e5c6e..751db8d88 100644
--- a/src/graphics/draw/ClockRenderer.cpp
+++ b/src/graphics/draw/ClockRenderer.cpp
@@ -8,6 +8,7 @@
 #include "gps/RTC.h"
 #include "graphics/ScreenFonts.h"
 #include "graphics/SharedUIDisplay.h"
+#include "graphics/draw/UIRenderer.h"
 #include "graphics/emotes.h"
 #include "graphics/images.h"
 #include "main.h"
diff --git a/src/graphics/draw/MenuHandler.h b/src/graphics/draw/MenuHandler.h
index 510f8097a..56477258f 100644
--- a/src/graphics/draw/MenuHandler.h
+++ b/src/graphics/draw/MenuHandler.h
@@ -85,7 +85,6 @@ class menuHandler
     static void powerMenu();
     static void FrameToggles_menu();
     static void textMessageMenu();
-    static void FrameToggles_menu();
 
   private:
     static void saveUIConfig();

From 6677255f6cff797e107e311f1d4af30d4c382ce6 Mon Sep 17 00:00:00 2001
From: Ben Meadors 
Date: Fri, 19 Sep 2025 12:09:56 -0500
Subject: [PATCH 284/691] Fix

---
 src/graphics/draw/UIRenderer.cpp | 6 ++----
 1 file changed, 2 insertions(+), 4 deletions(-)

diff --git a/src/graphics/draw/UIRenderer.cpp b/src/graphics/draw/UIRenderer.cpp
index 2da53bda4..6844aa50f 100644
--- a/src/graphics/draw/UIRenderer.cpp
+++ b/src/graphics/draw/UIRenderer.cpp
@@ -117,10 +117,7 @@ void UIRenderer::drawGpsAltitude(OLEDDisplay *display, int16_t x, int16_t y, con
 
 // Draw GPS status coordinates
 void UIRenderer::drawGpsCoordinates(OLEDDisplay *display, int16_t x, int16_t y, const meshtastic::GPSStatus *gps,
-                                    const char *mode) void UIRenderer::drawGpsCoordinates(OLEDDisplay *display, int16_t x,
-                                                                                          int16_t y,
-                                                                                          const meshtastic::GPSStatus *gps,
-                                                                                          const char *mode)
+                                    const char *mode)
 {
     auto gpsFormat = uiconfig.gps_format;
     char displayLine[32];
@@ -1377,4 +1374,5 @@ std::string UIRenderer::drawTimeDelta(uint32_t days, uint32_t hours, uint32_t mi
 
 } // namespace graphics
 
+#endif // HAS_GPS
 #endif // HAS_SCREEN
\ No newline at end of file

From 0e26702c4600a4c39e9ef2249e09e35c9763fd73 Mon Sep 17 00:00:00 2001
From: WillyJL 
Date: Fri, 19 Sep 2025 19:08:38 +0200
Subject: [PATCH 285/691] InputPollable: System for polling after interrupts

---
 src/input/InputBroker.cpp | 46 +++++++++++++++++++++++++++++++++++++--
 src/input/InputBroker.h   | 22 +++++++++++++++++++
 src/main.cpp              |  3 +++
 3 files changed, 69 insertions(+), 2 deletions(-)

diff --git a/src/input/InputBroker.cpp b/src/input/InputBroker.cpp
index ef6d8df91..5ca890b43 100644
--- a/src/input/InputBroker.cpp
+++ b/src/input/InputBroker.cpp
@@ -3,16 +3,58 @@
 
 InputBroker *inputBroker = nullptr;
 
-InputBroker::InputBroker(){};
+InputBroker::InputBroker()
+{
+#ifdef HAS_FREE_RTOS
+    inputEventQueue = xQueueCreate(5, sizeof(InputEvent));
+    pollSoonQueue = xQueueCreate(5, sizeof(InputPollable *));
+    xTaskCreate(pollSoonWorker, "input-pollSoon", 2 * 1024, this, 10, &pollSoonTask);
+#endif
+}
 
 void InputBroker::registerSource(Observable *source)
 {
     this->inputEventObserver.observe(source);
 }
 
+#ifdef HAS_FREE_RTOS
+void InputBroker::pollSoonRequestFromIsr(InputPollable *pollable)
+{
+    xQueueSendFromISR(pollSoonQueue, &pollable, NULL);
+}
+
+void InputBroker::queueInputEvent(const InputEvent *event)
+{
+    xQueueSend(inputEventQueue, event, portMAX_DELAY);
+}
+
+void InputBroker::processInputEventQueue()
+{
+    InputEvent event;
+    while (xQueueReceive(inputEventQueue, &event, 0)) {
+        handleInputEvent(&event);
+    }
+}
+#endif
+
 int InputBroker::handleInputEvent(const InputEvent *event)
 {
     powerFSM.trigger(EVENT_INPUT); // todo: not every input should wake, like long hold release
     this->notifyObservers(event);
     return 0;
-}
\ No newline at end of file
+}
+
+#ifdef HAS_FREE_RTOS
+void InputBroker::pollSoonWorker(void *p)
+{
+    InputBroker *instance = (InputBroker *)p;
+    while (true) {
+        InputPollable *pollable = NULL;
+        xQueueReceive(instance->pollSoonQueue, &pollable, portMAX_DELAY);
+        if (pollable) {
+            pollable->pollOnce();
+        }
+    }
+    vTaskDelete(NULL);
+}
+#endif
diff --git a/src/input/InputBroker.h b/src/input/InputBroker.h
index 2cdfa2ae2..82af184f3 100644
--- a/src/input/InputBroker.h
+++ b/src/input/InputBroker.h
@@ -1,5 +1,7 @@
 #pragma once
+
 #include "Observer.h"
+#include "freertosinc.h"
 
 enum input_broker_event {
     INPUT_BROKER_NONE = 0,
@@ -41,6 +43,13 @@ typedef struct _InputEvent {
     uint16_t touchX;
     uint16_t touchY;
 } InputEvent;
+
+class InputPollable
+{
+  public:
+    virtual void pollOnce() = 0;
+};
+
 class InputBroker : public Observable
 {
     CallbackObserver inputEventObserver =
@@ -50,9 +59,22 @@ class InputBroker : public Observable
     InputBroker();
     void registerSource(Observable *source);
     void injectInputEvent(const InputEvent *event) { handleInputEvent(event); }
+#ifdef HAS_FREE_RTOS
+    void pollSoonRequestFromIsr(InputPollable *pollable);
+    void queueInputEvent(const InputEvent *event);
+    void processInputEventQueue();
+#endif
 
   protected:
     int handleInputEvent(const InputEvent *event);
+
+  private:
+#ifdef HAS_FREE_RTOS
+    QueueHandle_t inputEventQueue;
+    QueueHandle_t pollSoonQueue;
+    TaskHandle_t pollSoonTask;
+    static void pollSoonWorker(void *p);
+#endif
 };
 
 extern InputBroker *inputBroker;
\ No newline at end of file
diff --git a/src/main.cpp b/src/main.cpp
index b821310ce..510d2b898 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -1602,6 +1602,9 @@ void loop()
 #endif
 
     service->loop();
+#if !MESHTASTIC_EXCLUDE_INPUTBROKER
+    inputBroker->processInputEventQueue();
+#endif
 #if defined(LGFX_SDL)
     if (screen) {
         auto dispdev = screen->getDisplayDevice();

From 54f9f7a5917711adf9971bcd44dcdddd8f40812d Mon Sep 17 00:00:00 2001
From: WillyJL 
Date: Fri, 19 Sep 2025 22:05:18 +0200
Subject: [PATCH 286/691] T-Lora Pager: Use InputPollable for RotaryEncoderImpl

---
 src/input/RotaryEncoderImpl.cpp | 50 +++++++++------------------------
 src/input/RotaryEncoderImpl.h   | 11 ++------
 2 files changed, 16 insertions(+), 45 deletions(-)

diff --git a/src/input/RotaryEncoderImpl.cpp b/src/input/RotaryEncoderImpl.cpp
index 216e92382..213dd4faa 100644
--- a/src/input/RotaryEncoderImpl.cpp
+++ b/src/input/RotaryEncoderImpl.cpp
@@ -6,11 +6,9 @@
 
 #define ORIGIN_NAME "RotaryEncoder"
 
-#define ROTARY_INTERRUPT_FLAG _BV(0)
-
 RotaryEncoderImpl *rotaryEncoderImpl;
 
-RotaryEncoderImpl::RotaryEncoderImpl() : concurrency::OSThread(ORIGIN_NAME), originName(ORIGIN_NAME)
+RotaryEncoderImpl::RotaryEncoderImpl()
 {
     rotary = nullptr;
 }
@@ -20,7 +18,6 @@ bool RotaryEncoderImpl::init()
     if (!moduleConfig.canned_message.updown1_enabled || moduleConfig.canned_message.inputbroker_pin_a == 0 ||
         moduleConfig.canned_message.inputbroker_pin_b == 0) {
         // Input device is disabled.
-        disable();
         return false;
     }
 
@@ -32,16 +29,11 @@ bool RotaryEncoderImpl::init()
                                moduleConfig.canned_message.inputbroker_pin_press);
     rotary->resetButton();
 
-    inputQueue = xQueueCreate(5, sizeof(input_broker_event));
-    interruptFlag = xEventGroupCreate();
     interruptInstance = this;
-    auto interruptHandler = []() { xEventGroupSetBits(interruptInstance->interruptFlag, ROTARY_INTERRUPT_FLAG); };
+    auto interruptHandler = []() { inputBroker->pollSoonRequestFromIsr(interruptInstance); };
     attachInterrupt(moduleConfig.canned_message.inputbroker_pin_a, interruptHandler, CHANGE);
     attachInterrupt(moduleConfig.canned_message.inputbroker_pin_b, interruptHandler, CHANGE);
     attachInterrupt(moduleConfig.canned_message.inputbroker_pin_press, interruptHandler, CHANGE);
-    xTaskCreate(inputWorker, "rotary", 2 * 1024, this, 10, &inputWorkerTask);
-
-    inputBroker->registerSource(this);
 
     LOG_INFO("RotaryEncoder initialized pins(%d, %d, %d), events(%d, %d, %d)", moduleConfig.canned_message.inputbroker_pin_a,
              moduleConfig.canned_message.inputbroker_pin_b, moduleConfig.canned_message.inputbroker_pin_press, eventCw, eventCcw,
@@ -49,50 +41,36 @@ bool RotaryEncoderImpl::init()
     return true;
 }
 
-void RotaryEncoderImpl::dispatchInputs()
+void RotaryEncoderImpl::pollOnce()
 {
+    InputEvent e{ORIGIN_NAME, INPUT_BROKER_NONE, 0, 0, 0};
+
     static uint32_t lastPressed = millis();
     if (rotary->readButton() == RotaryEncoder::ButtonState::BUTTON_PRESSED) {
         if (lastPressed + 200 < millis()) {
-            // LOG_DEBUG("Rotary event Press");
+            LOG_DEBUG("Rotary event Press");
             lastPressed = millis();
-            xQueueSend(inputQueue, &this->eventPressed, portMAX_DELAY);
+            e.inputEvent = this->eventPressed;
+            inputBroker->queueInputEvent(&e);
         }
     }
 
     switch (rotary->process()) {
     case RotaryEncoder::DIRECTION_CW:
-        // LOG_DEBUG("Rotary event CW");
-        xQueueSend(inputQueue, &this->eventCw, portMAX_DELAY);
+        LOG_DEBUG("Rotary event CW");
+        e.inputEvent = this->eventCw;
+        inputBroker->queueInputEvent(&e);
         break;
     case RotaryEncoder::DIRECTION_CCW:
-        // LOG_DEBUG("Rotary event CCW");
-        xQueueSend(inputQueue, &this->eventCcw, portMAX_DELAY);
+        LOG_DEBUG("Rotary event CCW");
+        e.inputEvent = this->eventCcw;
+        inputBroker->queueInputEvent(&e);
         break;
     default:
         break;
     }
 }
 
-void RotaryEncoderImpl::inputWorker(void *p)
-{
-    RotaryEncoderImpl *instance = (RotaryEncoderImpl *)p;
-    while (true) {
-        xEventGroupWaitBits(instance->interruptFlag, ROTARY_INTERRUPT_FLAG, pdTRUE, pdTRUE, portMAX_DELAY);
-        instance->dispatchInputs();
-    }
-    vTaskDelete(NULL);
-}
-
 RotaryEncoderImpl *RotaryEncoderImpl::interruptInstance;
 
-int32_t RotaryEncoderImpl::runOnce()
-{
-    InputEvent e{originName, INPUT_BROKER_NONE, 0, 0, 0};
-    while (xQueueReceive(inputQueue, &e.inputEvent, 0) == pdPASS) {
-        this->notifyObservers(&e);
-    }
-    return 10;
-}
-
 #endif
\ No newline at end of file
diff --git a/src/input/RotaryEncoderImpl.h b/src/input/RotaryEncoderImpl.h
index e5ff251e8..af70d1bf4 100644
--- a/src/input/RotaryEncoderImpl.h
+++ b/src/input/RotaryEncoderImpl.h
@@ -10,20 +10,14 @@
 
 class RotaryEncoder;
 
-class RotaryEncoderImpl : public Observable, public concurrency::OSThread
+class RotaryEncoderImpl : public InputPollable
 {
   public:
     RotaryEncoderImpl();
     bool init(void);
+    virtual void pollOnce() override;
 
   protected:
-    virtual int32_t runOnce() override;
-
-    QueueHandle_t inputQueue;
-    void dispatchInputs(void);
-    TaskHandle_t inputWorkerTask;
-    static void inputWorker(void *p);
-    EventGroupHandle_t interruptFlag;
     static RotaryEncoderImpl *interruptInstance;
 
     input_broker_event eventCw = INPUT_BROKER_NONE;
@@ -31,7 +25,6 @@ class RotaryEncoderImpl : public Observable, public concurre
     input_broker_event eventPressed = INPUT_BROKER_NONE;
 
     RotaryEncoder *rotary;
-    const char *originName;
 };
 
 extern RotaryEncoderImpl *rotaryEncoderImpl;

From 787642ad4c4d063fc5bfe4248a60bbe63ad9ceab Mon Sep 17 00:00:00 2001
From: WillyJL 
Date: Fri, 19 Sep 2025 22:17:31 +0200
Subject: [PATCH 287/691] Fix more build failures (#8044)

---
 src/graphics/draw/UIRenderer.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/graphics/draw/UIRenderer.cpp b/src/graphics/draw/UIRenderer.cpp
index 6844aa50f..202a28835 100644
--- a/src/graphics/draw/UIRenderer.cpp
+++ b/src/graphics/draw/UIRenderer.cpp
@@ -1184,6 +1184,7 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU
         }
     }
 #endif
+#endif // HAS_GPS
 }
 
 #ifdef USERPREFS_OEM_TEXT
@@ -1374,5 +1375,4 @@ std::string UIRenderer::drawTimeDelta(uint32_t days, uint32_t hours, uint32_t mi
 
 } // namespace graphics
 
-#endif // HAS_GPS
 #endif // HAS_SCREEN
\ No newline at end of file

From fdc879605233202b132a4c64487ff515a6bf8a38 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
 <41898282+github-actions[bot]@users.noreply.github.com>
Date: Fri, 19 Sep 2025 15:50:33 -0500
Subject: [PATCH 288/691] Update protobufs (#8045)

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 6a8b80a10..46b81e822 160000
--- a/protobufs
+++ b/protobufs
@@ -1 +1 @@
-Subproject commit 6a8b80a10835acf48b2dfa2ad8aa0cc596219619
+Subproject commit 46b81e822af1b8e408f437092337f129dee693e6
diff --git a/src/mesh/generated/meshtastic/deviceonly.pb.h b/src/mesh/generated/meshtastic/deviceonly.pb.h
index c6cad8a2a..7fab82ff7 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        2275
+#define meshtastic_BackupPreferences_size        2277
 #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 1fa4f33dd..3ab6f02c1 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              749
-#define meshtastic_LocalModuleConfig_size        671
+#define meshtastic_LocalModuleConfig_size        673
 
 #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 16c4c230c..47d3b5baa 100644
--- a/src/mesh/generated/meshtastic/module_config.pb.h
+++ b/src/mesh/generated/meshtastic/module_config.pb.h
@@ -356,6 +356,9 @@ typedef struct _meshtastic_ModuleConfig_TelemetryConfig {
     uint32_t health_update_interval;
     /* Enable/Disable the health telemetry module on-device display */
     bool health_screen_enabled;
+    /* Enable/Disable the device telemetry module to send metrics to the mesh
+ Note: We will still send telemtry to the connected phone / client every minute over the API */
+    bool device_telemetry_enabled;
 } meshtastic_ModuleConfig_TelemetryConfig;
 
 /* Canned Messages Module Config */
@@ -523,7 +526,7 @@ extern "C" {
 #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, 0}
-#define meshtastic_ModuleConfig_TelemetryConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
+#define meshtastic_ModuleConfig_TelemetryConfig_init_default {0, 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}
 #define meshtastic_RemoteHardwarePin_init_default {0, "", _meshtastic_RemoteHardwarePinType_MIN}
@@ -539,7 +542,7 @@ extern "C" {
 #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, 0}
-#define meshtastic_ModuleConfig_TelemetryConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
+#define meshtastic_ModuleConfig_TelemetryConfig_init_zero {0, 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}
 #define meshtastic_RemoteHardwarePin_init_zero   {0, "", _meshtastic_RemoteHardwarePinType_MIN}
@@ -627,6 +630,7 @@ extern "C" {
 #define meshtastic_ModuleConfig_TelemetryConfig_health_measurement_enabled_tag 11
 #define meshtastic_ModuleConfig_TelemetryConfig_health_update_interval_tag 12
 #define meshtastic_ModuleConfig_TelemetryConfig_health_screen_enabled_tag 13
+#define meshtastic_ModuleConfig_TelemetryConfig_device_telemetry_enabled_tag 14
 #define meshtastic_ModuleConfig_CannedMessageConfig_rotary1_enabled_tag 1
 #define meshtastic_ModuleConfig_CannedMessageConfig_inputbroker_pin_a_tag 2
 #define meshtastic_ModuleConfig_CannedMessageConfig_inputbroker_pin_b_tag 3
@@ -825,7 +829,8 @@ X(a, STATIC,   SINGULAR, UINT32,   power_update_interval,   9) \
 X(a, STATIC,   SINGULAR, BOOL,     power_screen_enabled,  10) \
 X(a, STATIC,   SINGULAR, BOOL,     health_measurement_enabled,  11) \
 X(a, STATIC,   SINGULAR, UINT32,   health_update_interval,  12) \
-X(a, STATIC,   SINGULAR, BOOL,     health_screen_enabled,  13)
+X(a, STATIC,   SINGULAR, BOOL,     health_screen_enabled,  13) \
+X(a, STATIC,   SINGULAR, BOOL,     device_telemetry_enabled,  14)
 #define meshtastic_ModuleConfig_TelemetryConfig_CALLBACK NULL
 #define meshtastic_ModuleConfig_TelemetryConfig_DEFAULT NULL
 
@@ -910,7 +915,7 @@ extern const pb_msgdesc_t meshtastic_RemoteHardwarePin_msg;
 #define meshtastic_ModuleConfig_RemoteHardwareConfig_size 96
 #define meshtastic_ModuleConfig_SerialConfig_size 28
 #define meshtastic_ModuleConfig_StoreForwardConfig_size 24
-#define meshtastic_ModuleConfig_TelemetryConfig_size 46
+#define meshtastic_ModuleConfig_TelemetryConfig_size 48
 #define meshtastic_ModuleConfig_size             227
 #define meshtastic_RemoteHardwarePin_size        21
 

From 9b6a7ed3bb1bea5d4eb949be04cfb2c206de6951 Mon Sep 17 00:00:00 2001
From: Ben Meadors 
Date: Fri, 19 Sep 2025 16:00:24 -0500
Subject: [PATCH 289/691] Fix icon

---
 src/graphics/img/icon_small.xbm | 88 ++++++++++++++++++++++++++++++++-
 1 file changed, 87 insertions(+), 1 deletion(-)

diff --git a/src/graphics/img/icon_small.xbm b/src/graphics/img/icon_small.xbm
index e320a1fea..97884edad 100644
--- a/src/graphics/img/icon_small.xbm
+++ b/src/graphics/img/icon_small.xbm
@@ -27,4 +27,90 @@ static uint8_t icon_bits[] = {
   0x00, 0x1c, 0x00, 0x00, 0x70, 0x00, 0x10, 0x00,
   0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00 };
-#endif
\ No newline at end of file
+#endif
+
+// Chirpy image definitions for M5STACK_UNITC6L compatibility
+#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, 0xe0, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, 0x00, 0xfc,
+    0x03, 0xe0, 0x1f, 0xff, 0xff, 0xff, 0xf8, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03,
+    0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0,
+    0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f,
+    0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe,
+    0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff,
+    0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f,
+    0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0,
+    0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f,
+    0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00,
+    0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc,
+    0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03,
+    0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0,
+    0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f,
+    0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe,
+    0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff,
+    0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f,
+    0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0,
+    0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f,
+    0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00,
+    0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc,
+    0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03,
+    0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0,
+    0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x1f,
+    0xff, 0xff, 0xff, 0xf8, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0x7f, 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, 0x86, 0x61, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x86, 0x61, 0x00,
+    0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x86, 0x61, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x80, 0x87, 0xe1, 0x01, 0x00,
+    0x00, 0xfc, 0x03, 0x00, 0x00, 0x80, 0x87, 0xe1, 0x01, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0xc0, 0x83, 0xc1, 0x03, 0x00, 0x00,
+    0xfc, 0x03, 0x00, 0x00, 0xc0, 0x83, 0xc1, 0x03, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0xe0, 0x01, 0x80, 0x07, 0x00, 0x00, 0xfc,
+    0x03, 0x00, 0x00, 0xe0, 0x01, 0x80, 0x07, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x0f, 0x00, 0x00, 0xfc, 0x03,
+    0x00, 0x00, 0xf0, 0x00, 0x00, 0x0f, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x78, 0x00, 0x00, 0x1e, 0x00, 0x00, 0xfc, 0x03, 0x00,
+    0x00, 0x78, 0x00, 0x00, 0x1e, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x3c, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00,
+    0x3c, 0x00, 0x00, 0x3c, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x78, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x1e,
+    0x00, 0x00, 0x78, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x0f, 0x00, 0x00, 0xf0, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x0f, 0x00,
+    0x00, 0xf0, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x80, 0x07, 0x00, 0x00, 0xe0, 0x01, 0x00, 0xfc, 0x03, 0x00, 0x80, 0x07, 0x00, 0x00,
+    0xe0, 0x01, 0x00, 0xfc, 0x03, 0x00, 0xc0, 0x03, 0x00, 0x00, 0xc0, 0x03, 0x00, 0xfc, 0x03, 0x00, 0xc0, 0x03, 0x00, 0x00, 0xc0,
+    0x03, 0x00, 0xfc, 0x03, 0x00, 0xe0, 0x01, 0x00, 0x00, 0x80, 0x07, 0x00, 0xfc, 0x03, 0x00, 0xe0, 0x01, 0x00, 0x00, 0x80, 0x07,
+    0x00, 0xfc, 0x03, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x00, 0xfc, 0x03, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x00,
+    0xfc, 0x03, 0x00, 0x78, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x00, 0xfc, 0x03, 0x00, 0x78, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x00, 0xfc,
+    0x03, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x00, 0xfc, 0x03, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x00, 0xfc, 0x03,
+    0x00, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x78, 0x00, 0xfc, 0x03, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x78, 0x00, 0xfc, 0x03, 0x00,
+    0x0f, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0xfc, 0x03, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0xfc, 0x03, 0x80, 0x07,
+    0x00, 0x00, 0x00, 0x00, 0xe0, 0x01, 0xfc, 0x03, 0x80, 0x07, 0x00, 0x00, 0x00, 0x00, 0xe0, 0x01, 0xfc, 0x03, 0xc0, 0x03, 0x00,
+    0x00, 0x00, 0x00, 0xc0, 0x03, 0xfc, 0x03, 0xc0, 0x03, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x03, 0xfc, 0x03, 0xe0, 0x01, 0x00, 0x00,
+    0x00, 0x00, 0x80, 0x07, 0xfc, 0x03, 0xe0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x80, 0x07, 0xfc, 0x03, 0xf0, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x0f, 0xfc, 0x03, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 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};
\ No newline at end of file

From 6f56ccd283f4279af88a4f241cabc7e0c19392f8 Mon Sep 17 00:00:00 2001
From: Jonathan Bennett 
Date: Fri, 19 Sep 2025 21:16:19 -0500
Subject: [PATCH 290/691] C6l fixes (#8047)

---
 src/gps/GPS.cpp                | 2 ++
 src/nimble/NimbleBluetooth.cpp | 7 +++++++
 src/nimble/NimbleBluetooth.h   | 1 +
 3 files changed, 10 insertions(+)

diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp
index a4c7464b8..3bf2710fe 100644
--- a/src/gps/GPS.cpp
+++ b/src/gps/GPS.cpp
@@ -516,6 +516,7 @@ bool GPS::setup()
                 }
             }
             // Rare Serial Speeds
+#ifndef CONFIG_IDF_TARGET_ESP32C6
             if (probeTries == GPS_PROBETRIES) {
                 LOG_DEBUG("Probe for GPS at %d", rareSerialSpeeds[speedSelect]);
                 gnssModel = probe(rareSerialSpeeds[speedSelect]);
@@ -526,6 +527,7 @@ bool GPS::setup()
                     }
                 }
             }
+#endif
         }
 
         if (gnssModel != GNSS_MODEL_UNKNOWN) {
diff --git a/src/nimble/NimbleBluetooth.cpp b/src/nimble/NimbleBluetooth.cpp
index 0eb8e9bdd..accf6c5dc 100644
--- a/src/nimble/NimbleBluetooth.cpp
+++ b/src/nimble/NimbleBluetooth.cpp
@@ -231,6 +231,10 @@ class NimbleBluetoothServerCallback : public NimBLEServerCallbacks
     {
         LOG_INFO("BLE disconnect");
 #endif
+#ifdef NIMBLE_TWO
+        if (ble->isDeInit)
+            return;
+#endif
 
         meshtastic::BluetoothStatus newStatus(meshtastic::BluetoothStatus::ConnectionState::DISCONNECTED);
         bluetoothStatus->updateStatus(&newStatus);
@@ -270,6 +274,7 @@ void NimbleBluetooth::deinit()
 {
 #ifdef ARCH_ESP32
     LOG_INFO("Disable bluetooth until reboot");
+    isDeInit = true;
 
 #ifdef BLE_LED
 #ifdef BLE_LED_INVERTED
@@ -278,8 +283,10 @@ void NimbleBluetooth::deinit()
     digitalWrite(BLE_LED, LOW);
 #endif
 #endif
+#ifndef NIMBLE_TWO
     NimBLEDevice::deinit();
 #endif
+#endif
 }
 
 // Has initial setup been completed
diff --git a/src/nimble/NimbleBluetooth.h b/src/nimble/NimbleBluetooth.h
index 899355b4d..458fa4a67 100644
--- a/src/nimble/NimbleBluetooth.h
+++ b/src/nimble/NimbleBluetooth.h
@@ -15,6 +15,7 @@ class NimbleBluetooth : BluetoothApi
 #if defined(NIMBLE_TWO)
     void startAdvertising();
 #endif
+    bool isDeInit = false;
 
   private:
     void setupService();

From 2ccf91f443f3098b2228c9a54f6dbc0eee0678b1 Mon Sep 17 00:00:00 2001
From: ford-jones 
Date: Sat, 20 Sep 2025 14:38:05 +1200
Subject: [PATCH 291/691] Regen protos

---
 protobufs                                     |  2 +-
 src/mesh/generated/meshtastic/config.pb.h     | 49 ++++++++++++-------
 .../generated/meshtastic/device_ui.pb.cpp     |  2 -
 src/mesh/generated/meshtastic/device_ui.pb.h  | 45 ++---------------
 src/mesh/generated/meshtastic/deviceonly.pb.h |  2 +-
 src/mesh/generated/meshtastic/localonly.pb.h  |  4 +-
 src/mesh/generated/meshtastic/mesh.pb.h       |  8 +--
 .../generated/meshtastic/module_config.pb.h   | 13 ++---
 8 files changed, 45 insertions(+), 80 deletions(-)

diff --git a/protobufs b/protobufs
index 46b81e822..a84657c22 160000
--- a/protobufs
+++ b/protobufs
@@ -1 +1 @@
-Subproject commit 46b81e822af1b8e408f437092337f129dee693e6
+Subproject commit a84657c220421536f18d11fc5edf680efadbceeb
diff --git a/src/mesh/generated/meshtastic/config.pb.h b/src/mesh/generated/meshtastic/config.pb.h
index 0453ecad2..59e55db3f 100644
--- a/src/mesh/generated/meshtastic/config.pb.h
+++ b/src/mesh/generated/meshtastic/config.pb.h
@@ -173,10 +173,28 @@ typedef enum _meshtastic_Config_NetworkConfig_ProtocolFlags {
     meshtastic_Config_NetworkConfig_ProtocolFlags_UDP_BROADCAST = 1
 } meshtastic_Config_NetworkConfig_ProtocolFlags;
 
-/* Deprecated in 2.7.4: Unused */
-typedef enum _meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat {
-    meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat_UNUSED = 0
-} meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat;
+/* How the GPS coordinates are displayed on the OLED screen. */
+typedef enum _meshtastic_Config_DisplayConfig_GpsCoordinateFormat {
+    /* GPS coordinates are displayed in the normal decimal degrees format:
+ DD.DDDDDD DDD.DDDDDD */
+    meshtastic_Config_DisplayConfig_GpsCoordinateFormat_DEC = 0,
+    /* GPS coordinates are displayed in the degrees minutes seconds format:
+ DD°MM'SS"C DDD°MM'SS"C, where C is the compass point representing the locations quadrant */
+    meshtastic_Config_DisplayConfig_GpsCoordinateFormat_DMS = 1,
+    /* Universal Transverse Mercator format:
+ ZZB EEEEEE NNNNNNN, where Z is zone, B is band, E is easting, N is northing */
+    meshtastic_Config_DisplayConfig_GpsCoordinateFormat_UTM = 2,
+    /* Military Grid Reference System format:
+ ZZB CD EEEEE NNNNN, where Z is zone, B is band, C is the east 100k square, D is the north 100k square,
+ E is easting, N is northing */
+    meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MGRS = 3,
+    /* Open Location Code (aka Plus Codes). */
+    meshtastic_Config_DisplayConfig_GpsCoordinateFormat_OLC = 4,
+    /* Ordnance Survey Grid Reference (the National Grid System of the UK).
+ Format: AB EEEEE NNNNN, where A is the east 100k square, B is the north 100k square,
+ E is the easting, N is the northing */
+    meshtastic_Config_DisplayConfig_GpsCoordinateFormat_OSGR = 5
+} meshtastic_Config_DisplayConfig_GpsCoordinateFormat;
 
 /* Unit display preference */
 typedef enum _meshtastic_Config_DisplayConfig_DisplayUnits {
@@ -473,7 +491,7 @@ typedef struct _meshtastic_Config_DisplayConfig {
     uint32_t screen_on_secs;
     /* Deprecated in 2.7.4: Unused
  How the GPS coordinates are formatted on the OLED screen. */
-    meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat gps_format;
+    meshtastic_Config_DisplayConfig_GpsCoordinateFormat gps_format;
     /* Automatically toggles to the next page on the screen like a carousel, based the specified interval in seconds.
  Potentially useful for devices without user buttons. */
     uint32_t auto_screen_carousel_secs;
@@ -497,9 +515,6 @@ typedef struct _meshtastic_Config_DisplayConfig {
     /* If false (default), the device will display the time in 24-hour format on screen.
  If true, the device will display the time in 12-hour format on screen. */
     bool use_12h_clock;
-    /* If false (default), the device will use short names for various display screens.
- If true, node names will show in long format */
-    bool use_long_node_name;
 } meshtastic_Config_DisplayConfig;
 
 /* Lora Config */
@@ -663,9 +678,9 @@ extern "C" {
 #define _meshtastic_Config_NetworkConfig_ProtocolFlags_MAX meshtastic_Config_NetworkConfig_ProtocolFlags_UDP_BROADCAST
 #define _meshtastic_Config_NetworkConfig_ProtocolFlags_ARRAYSIZE ((meshtastic_Config_NetworkConfig_ProtocolFlags)(meshtastic_Config_NetworkConfig_ProtocolFlags_UDP_BROADCAST+1))
 
-#define _meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat_MIN meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat_UNUSED
-#define _meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat_MAX meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat_UNUSED
-#define _meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat_ARRAYSIZE ((meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat)(meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat_UNUSED+1))
+#define _meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MIN meshtastic_Config_DisplayConfig_GpsCoordinateFormat_DEC
+#define _meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MAX meshtastic_Config_DisplayConfig_GpsCoordinateFormat_OSGR
+#define _meshtastic_Config_DisplayConfig_GpsCoordinateFormat_ARRAYSIZE ((meshtastic_Config_DisplayConfig_GpsCoordinateFormat)(meshtastic_Config_DisplayConfig_GpsCoordinateFormat_OSGR+1))
 
 #define _meshtastic_Config_DisplayConfig_DisplayUnits_MIN meshtastic_Config_DisplayConfig_DisplayUnits_METRIC
 #define _meshtastic_Config_DisplayConfig_DisplayUnits_MAX meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL
@@ -706,7 +721,7 @@ extern "C" {
 #define meshtastic_Config_NetworkConfig_address_mode_ENUMTYPE meshtastic_Config_NetworkConfig_AddressMode
 
 
-#define meshtastic_Config_DisplayConfig_gps_format_ENUMTYPE meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat
+#define meshtastic_Config_DisplayConfig_gps_format_ENUMTYPE meshtastic_Config_DisplayConfig_GpsCoordinateFormat
 #define meshtastic_Config_DisplayConfig_units_ENUMTYPE meshtastic_Config_DisplayConfig_DisplayUnits
 #define meshtastic_Config_DisplayConfig_oled_ENUMTYPE meshtastic_Config_DisplayConfig_OledType
 #define meshtastic_Config_DisplayConfig_displaymode_ENUMTYPE meshtastic_Config_DisplayConfig_DisplayMode
@@ -727,7 +742,7 @@ extern "C" {
 #define meshtastic_Config_PowerConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0}
 #define meshtastic_Config_NetworkConfig_init_default {0, "", "", "", 0, _meshtastic_Config_NetworkConfig_AddressMode_MIN, false, meshtastic_Config_NetworkConfig_IpV4Config_init_default, "", 0, 0}
 #define meshtastic_Config_NetworkConfig_IpV4Config_init_default {0, 0, 0, 0}
-#define meshtastic_Config_DisplayConfig_init_default {0, _meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat_MIN, 0, 0, 0, _meshtastic_Config_DisplayConfig_DisplayUnits_MIN, _meshtastic_Config_DisplayConfig_OledType_MIN, _meshtastic_Config_DisplayConfig_DisplayMode_MIN, 0, 0, _meshtastic_Config_DisplayConfig_CompassOrientation_MIN, 0, 0}
+#define meshtastic_Config_DisplayConfig_init_default {0, _meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MIN, 0, 0, 0, _meshtastic_Config_DisplayConfig_DisplayUnits_MIN, _meshtastic_Config_DisplayConfig_OledType_MIN, _meshtastic_Config_DisplayConfig_DisplayMode_MIN, 0, 0, _meshtastic_Config_DisplayConfig_CompassOrientation_MIN, 0}
 #define meshtastic_Config_LoRaConfig_init_default {0, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, _meshtastic_Config_LoRaConfig_RegionCode_MIN, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}, 0, 0}
 #define meshtastic_Config_BluetoothConfig_init_default {0, _meshtastic_Config_BluetoothConfig_PairingMode_MIN, 0}
 #define meshtastic_Config_SecurityConfig_init_default {{0, {0}}, {0, {0}}, 0, {{0, {0}}, {0, {0}}, {0, {0}}}, 0, 0, 0, 0}
@@ -738,7 +753,7 @@ extern "C" {
 #define meshtastic_Config_PowerConfig_init_zero  {0, 0, 0, 0, 0, 0, 0, 0, 0}
 #define meshtastic_Config_NetworkConfig_init_zero {0, "", "", "", 0, _meshtastic_Config_NetworkConfig_AddressMode_MIN, false, meshtastic_Config_NetworkConfig_IpV4Config_init_zero, "", 0, 0}
 #define meshtastic_Config_NetworkConfig_IpV4Config_init_zero {0, 0, 0, 0}
-#define meshtastic_Config_DisplayConfig_init_zero {0, _meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat_MIN, 0, 0, 0, _meshtastic_Config_DisplayConfig_DisplayUnits_MIN, _meshtastic_Config_DisplayConfig_OledType_MIN, _meshtastic_Config_DisplayConfig_DisplayMode_MIN, 0, 0, _meshtastic_Config_DisplayConfig_CompassOrientation_MIN, 0, 0}
+#define meshtastic_Config_DisplayConfig_init_zero {0, _meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MIN, 0, 0, 0, _meshtastic_Config_DisplayConfig_DisplayUnits_MIN, _meshtastic_Config_DisplayConfig_OledType_MIN, _meshtastic_Config_DisplayConfig_DisplayMode_MIN, 0, 0, _meshtastic_Config_DisplayConfig_CompassOrientation_MIN, 0}
 #define meshtastic_Config_LoRaConfig_init_zero   {0, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, _meshtastic_Config_LoRaConfig_RegionCode_MIN, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}, 0, 0}
 #define meshtastic_Config_BluetoothConfig_init_zero {0, _meshtastic_Config_BluetoothConfig_PairingMode_MIN, 0}
 #define meshtastic_Config_SecurityConfig_init_zero {{0, {0}}, {0, {0}}, 0, {{0, {0}}, {0, {0}}, {0, {0}}}, 0, 0, 0, 0}
@@ -805,7 +820,6 @@ extern "C" {
 #define meshtastic_Config_DisplayConfig_wake_on_tap_or_motion_tag 10
 #define meshtastic_Config_DisplayConfig_compass_orientation_tag 11
 #define meshtastic_Config_DisplayConfig_use_12h_clock_tag 12
-#define meshtastic_Config_DisplayConfig_use_long_node_name_tag 13
 #define meshtastic_Config_LoRaConfig_use_preset_tag 1
 #define meshtastic_Config_LoRaConfig_modem_preset_tag 2
 #define meshtastic_Config_LoRaConfig_bandwidth_tag 3
@@ -951,8 +965,7 @@ X(a, STATIC,   SINGULAR, UENUM,    displaymode,       8) \
 X(a, STATIC,   SINGULAR, BOOL,     heading_bold,      9) \
 X(a, STATIC,   SINGULAR, BOOL,     wake_on_tap_or_motion,  10) \
 X(a, STATIC,   SINGULAR, UENUM,    compass_orientation,  11) \
-X(a, STATIC,   SINGULAR, BOOL,     use_12h_clock,    12) \
-X(a, STATIC,   SINGULAR, BOOL,     use_long_node_name,  13)
+X(a, STATIC,   SINGULAR, BOOL,     use_12h_clock,    12)
 #define meshtastic_Config_DisplayConfig_CALLBACK NULL
 #define meshtastic_Config_DisplayConfig_DEFAULT NULL
 
@@ -1030,7 +1043,7 @@ extern const pb_msgdesc_t meshtastic_Config_SessionkeyConfig_msg;
 #define MESHTASTIC_MESHTASTIC_CONFIG_PB_H_MAX_SIZE meshtastic_Config_size
 #define meshtastic_Config_BluetoothConfig_size   10
 #define meshtastic_Config_DeviceConfig_size      100
-#define meshtastic_Config_DisplayConfig_size     34
+#define meshtastic_Config_DisplayConfig_size     32
 #define meshtastic_Config_LoRaConfig_size        85
 #define meshtastic_Config_NetworkConfig_IpV4Config_size 20
 #define meshtastic_Config_NetworkConfig_size     204
diff --git a/src/mesh/generated/meshtastic/device_ui.pb.cpp b/src/mesh/generated/meshtastic/device_ui.pb.cpp
index 01940265f..2fc8d9461 100644
--- a/src/mesh/generated/meshtastic/device_ui.pb.cpp
+++ b/src/mesh/generated/meshtastic/device_ui.pb.cpp
@@ -28,5 +28,3 @@ PB_BIND(meshtastic_Map, meshtastic_Map, AUTO)
 
 
 
-
-
diff --git a/src/mesh/generated/meshtastic/device_ui.pb.h b/src/mesh/generated/meshtastic/device_ui.pb.h
index d9eb90773..8313438f8 100644
--- a/src/mesh/generated/meshtastic/device_ui.pb.h
+++ b/src/mesh/generated/meshtastic/device_ui.pb.h
@@ -66,40 +66,12 @@ typedef enum _meshtastic_Language {
     meshtastic_Language_UKRAINIAN = 16,
     /* Bulgarian */
     meshtastic_Language_BULGARIAN = 17,
-    /* Czech */
-    meshtastic_Language_CZECH = 18,
     /* Simplified Chinese (experimental) */
     meshtastic_Language_SIMPLIFIED_CHINESE = 30,
     /* Traditional Chinese (experimental) */
     meshtastic_Language_TRADITIONAL_CHINESE = 31
 } meshtastic_Language;
 
-/* How the GPS coordinates are displayed on the OLED screen. */
-typedef enum _meshtastic_DeviceUIConfig_GpsCoordinateFormat {
-    /* GPS coordinates are displayed in the normal decimal degrees format:
- DD.DDDDDD DDD.DDDDDD */
-    meshtastic_DeviceUIConfig_GpsCoordinateFormat_DEC = 0,
-    /* GPS coordinates are displayed in the degrees minutes seconds format:
- DD°MM'SS"C DDD°MM'SS"C, where C is the compass point representing the locations quadrant */
-    meshtastic_DeviceUIConfig_GpsCoordinateFormat_DMS = 1,
-    /* Universal Transverse Mercator format:
- ZZB EEEEEE NNNNNNN, where Z is zone, B is band, E is easting, N is northing */
-    meshtastic_DeviceUIConfig_GpsCoordinateFormat_UTM = 2,
-    /* Military Grid Reference System format:
- ZZB CD EEEEE NNNNN, where Z is zone, B is band, C is the east 100k square, D is the north 100k square,
- E is easting, N is northing */
-    meshtastic_DeviceUIConfig_GpsCoordinateFormat_MGRS = 3,
-    /* Open Location Code (aka Plus Codes). */
-    meshtastic_DeviceUIConfig_GpsCoordinateFormat_OLC = 4,
-    /* Ordnance Survey Grid Reference (the National Grid System of the UK).
- Format: AB EEEEE NNNNN, where A is the east 100k square, B is the north 100k square,
- E is the easting, N is the northing */
-    meshtastic_DeviceUIConfig_GpsCoordinateFormat_OSGR = 5,
-    /* Maidenhead Locator System
- Described here: https://en.wikipedia.org/wiki/Maidenhead_Locator_System */
-    meshtastic_DeviceUIConfig_GpsCoordinateFormat_MLS = 6
-} meshtastic_DeviceUIConfig_GpsCoordinateFormat;
-
 /* Struct definitions */
 typedef struct _meshtastic_NodeFilter {
     /* Filter unknown nodes */
@@ -189,8 +161,6 @@ typedef struct _meshtastic_DeviceUIConfig {
     /* Clockface analog style
  true for analog clockface, false for digital clockface */
     bool is_clockface_analog;
-    /* How the GPS coordinates are formatted on the OLED screen. */
-    meshtastic_DeviceUIConfig_GpsCoordinateFormat gps_format;
 } meshtastic_DeviceUIConfig;
 
 
@@ -211,14 +181,9 @@ extern "C" {
 #define _meshtastic_Language_MAX meshtastic_Language_TRADITIONAL_CHINESE
 #define _meshtastic_Language_ARRAYSIZE ((meshtastic_Language)(meshtastic_Language_TRADITIONAL_CHINESE+1))
 
-#define _meshtastic_DeviceUIConfig_GpsCoordinateFormat_MIN meshtastic_DeviceUIConfig_GpsCoordinateFormat_DEC
-#define _meshtastic_DeviceUIConfig_GpsCoordinateFormat_MAX meshtastic_DeviceUIConfig_GpsCoordinateFormat_MLS
-#define _meshtastic_DeviceUIConfig_GpsCoordinateFormat_ARRAYSIZE ((meshtastic_DeviceUIConfig_GpsCoordinateFormat)(meshtastic_DeviceUIConfig_GpsCoordinateFormat_MLS+1))
-
 #define meshtastic_DeviceUIConfig_theme_ENUMTYPE meshtastic_Theme
 #define meshtastic_DeviceUIConfig_language_ENUMTYPE meshtastic_Language
 #define meshtastic_DeviceUIConfig_compass_mode_ENUMTYPE meshtastic_CompassMode
-#define meshtastic_DeviceUIConfig_gps_format_ENUMTYPE meshtastic_DeviceUIConfig_GpsCoordinateFormat
 
 
 
@@ -226,12 +191,12 @@ extern "C" {
 
 
 /* Initializer values for message structs */
-#define meshtastic_DeviceUIConfig_init_default   {0, 0, 0, 0, 0, 0, _meshtastic_Theme_MIN, 0, 0, 0, _meshtastic_Language_MIN, false, meshtastic_NodeFilter_init_default, false, meshtastic_NodeHighlight_init_default, {0, {0}}, false, meshtastic_Map_init_default, _meshtastic_CompassMode_MIN, 0, 0, _meshtastic_DeviceUIConfig_GpsCoordinateFormat_MIN}
+#define meshtastic_DeviceUIConfig_init_default   {0, 0, 0, 0, 0, 0, _meshtastic_Theme_MIN, 0, 0, 0, _meshtastic_Language_MIN, false, meshtastic_NodeFilter_init_default, false, meshtastic_NodeHighlight_init_default, {0, {0}}, false, meshtastic_Map_init_default, _meshtastic_CompassMode_MIN, 0, 0}
 #define meshtastic_NodeFilter_init_default       {0, 0, 0, 0, 0, "", 0}
 #define meshtastic_NodeHighlight_init_default    {0, 0, 0, 0, ""}
 #define meshtastic_GeoPoint_init_default         {0, 0, 0}
 #define meshtastic_Map_init_default              {false, meshtastic_GeoPoint_init_default, "", 0}
-#define meshtastic_DeviceUIConfig_init_zero      {0, 0, 0, 0, 0, 0, _meshtastic_Theme_MIN, 0, 0, 0, _meshtastic_Language_MIN, false, meshtastic_NodeFilter_init_zero, false, meshtastic_NodeHighlight_init_zero, {0, {0}}, false, meshtastic_Map_init_zero, _meshtastic_CompassMode_MIN, 0, 0, _meshtastic_DeviceUIConfig_GpsCoordinateFormat_MIN}
+#define meshtastic_DeviceUIConfig_init_zero      {0, 0, 0, 0, 0, 0, _meshtastic_Theme_MIN, 0, 0, 0, _meshtastic_Language_MIN, false, meshtastic_NodeFilter_init_zero, false, meshtastic_NodeHighlight_init_zero, {0, {0}}, false, meshtastic_Map_init_zero, _meshtastic_CompassMode_MIN, 0, 0}
 #define meshtastic_NodeFilter_init_zero          {0, 0, 0, 0, 0, "", 0}
 #define meshtastic_NodeHighlight_init_zero       {0, 0, 0, 0, ""}
 #define meshtastic_GeoPoint_init_zero            {0, 0, 0}
@@ -274,7 +239,6 @@ extern "C" {
 #define meshtastic_DeviceUIConfig_compass_mode_tag 16
 #define meshtastic_DeviceUIConfig_screen_rgb_color_tag 17
 #define meshtastic_DeviceUIConfig_is_clockface_analog_tag 18
-#define meshtastic_DeviceUIConfig_gps_format_tag 19
 
 /* Struct field encoding specification for nanopb */
 #define meshtastic_DeviceUIConfig_FIELDLIST(X, a) \
@@ -295,8 +259,7 @@ X(a, STATIC,   SINGULAR, BYTES,    calibration_data,  14) \
 X(a, STATIC,   OPTIONAL, MESSAGE,  map_data,         15) \
 X(a, STATIC,   SINGULAR, UENUM,    compass_mode,     16) \
 X(a, STATIC,   SINGULAR, UINT32,   screen_rgb_color,  17) \
-X(a, STATIC,   SINGULAR, BOOL,     is_clockface_analog,  18) \
-X(a, STATIC,   SINGULAR, UENUM,    gps_format,       19)
+X(a, STATIC,   SINGULAR, BOOL,     is_clockface_analog,  18)
 #define meshtastic_DeviceUIConfig_CALLBACK NULL
 #define meshtastic_DeviceUIConfig_DEFAULT NULL
 #define meshtastic_DeviceUIConfig_node_filter_MSGTYPE meshtastic_NodeFilter
@@ -353,7 +316,7 @@ extern const pb_msgdesc_t meshtastic_Map_msg;
 
 /* Maximum encoded size of messages (where known) */
 #define MESHTASTIC_MESHTASTIC_DEVICE_UI_PB_H_MAX_SIZE meshtastic_DeviceUIConfig_size
-#define meshtastic_DeviceUIConfig_size           204
+#define meshtastic_DeviceUIConfig_size           201
 #define meshtastic_GeoPoint_size                 33
 #define meshtastic_Map_size                      58
 #define meshtastic_NodeFilter_size               47
diff --git a/src/mesh/generated/meshtastic/deviceonly.pb.h b/src/mesh/generated/meshtastic/deviceonly.pb.h
index 7fab82ff7..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        2277
+#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 3ab6f02c1..da224fb94 100644
--- a/src/mesh/generated/meshtastic/localonly.pb.h
+++ b/src/mesh/generated/meshtastic/localonly.pb.h
@@ -187,8 +187,8 @@ 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              749
-#define meshtastic_LocalModuleConfig_size        673
+#define meshtastic_LocalConfig_size              747
+#define meshtastic_LocalModuleConfig_size        671
 
 #ifdef __cplusplus
 } /* extern "C" */
diff --git a/src/mesh/generated/meshtastic/mesh.pb.h b/src/mesh/generated/meshtastic/mesh.pb.h
index 6292ce070..2a4e77870 100644
--- a/src/mesh/generated/meshtastic/mesh.pb.h
+++ b/src/mesh/generated/meshtastic/mesh.pb.h
@@ -259,8 +259,8 @@ typedef enum _meshtastic_HardwareModel {
     meshtastic_HardwareModel_T_DECK_PRO = 102,
     /* Lilygo TLora Pager */
     meshtastic_HardwareModel_T_LORA_PAGER = 103,
-    /* M5Stack Reserved */
-    meshtastic_HardwareModel_M5STACK_RESERVED = 104, /* 0x68 */
+    /* GAT562 Mesh Trial Tracker */
+    meshtastic_HardwareModel_GAT562_MESH_TRIAL_TRACKER = 104,
     /* RAKwireless WisMesh Tag */
     meshtastic_HardwareModel_WISMESH_TAG = 105,
     /* RAKwireless WisBlock Core RAK3312 https://docs.rakwireless.com/product-categories/wisduo/rak3112-module/overview/ */
@@ -274,10 +274,6 @@ typedef enum _meshtastic_HardwareModel {
     meshtastic_HardwareModel_T_ECHO_LITE = 109,
     /* New Heltec LoRA32 with ESP32-S3 CPU */
     meshtastic_HardwareModel_HELTEC_V4 = 110,
-    /* M5Stack C6L */
-    meshtastic_HardwareModel_M5STACK_C6L = 111,
-    /* M5Stack Cardputer Adv */
-    meshtastic_HardwareModel_M5STACK_CARDPUTER_ADV = 112,
     /* ------------------------------------------------------------------------------------------------------------------------------------------
  Reserved ID For developing private Ports. These will show up in live traffic sparsely, so we can use a high number. Keep it within 8 bits.
  ------------------------------------------------------------------------------------------------------------------------------------------ */
diff --git a/src/mesh/generated/meshtastic/module_config.pb.h b/src/mesh/generated/meshtastic/module_config.pb.h
index 47d3b5baa..16c4c230c 100644
--- a/src/mesh/generated/meshtastic/module_config.pb.h
+++ b/src/mesh/generated/meshtastic/module_config.pb.h
@@ -356,9 +356,6 @@ typedef struct _meshtastic_ModuleConfig_TelemetryConfig {
     uint32_t health_update_interval;
     /* Enable/Disable the health telemetry module on-device display */
     bool health_screen_enabled;
-    /* Enable/Disable the device telemetry module to send metrics to the mesh
- Note: We will still send telemtry to the connected phone / client every minute over the API */
-    bool device_telemetry_enabled;
 } meshtastic_ModuleConfig_TelemetryConfig;
 
 /* Canned Messages Module Config */
@@ -526,7 +523,7 @@ extern "C" {
 #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, 0}
-#define meshtastic_ModuleConfig_TelemetryConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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}
 #define meshtastic_RemoteHardwarePin_init_default {0, "", _meshtastic_RemoteHardwarePinType_MIN}
@@ -542,7 +539,7 @@ extern "C" {
 #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, 0}
-#define meshtastic_ModuleConfig_TelemetryConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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}
 #define meshtastic_RemoteHardwarePin_init_zero   {0, "", _meshtastic_RemoteHardwarePinType_MIN}
@@ -630,7 +627,6 @@ extern "C" {
 #define meshtastic_ModuleConfig_TelemetryConfig_health_measurement_enabled_tag 11
 #define meshtastic_ModuleConfig_TelemetryConfig_health_update_interval_tag 12
 #define meshtastic_ModuleConfig_TelemetryConfig_health_screen_enabled_tag 13
-#define meshtastic_ModuleConfig_TelemetryConfig_device_telemetry_enabled_tag 14
 #define meshtastic_ModuleConfig_CannedMessageConfig_rotary1_enabled_tag 1
 #define meshtastic_ModuleConfig_CannedMessageConfig_inputbroker_pin_a_tag 2
 #define meshtastic_ModuleConfig_CannedMessageConfig_inputbroker_pin_b_tag 3
@@ -829,8 +825,7 @@ X(a, STATIC,   SINGULAR, UINT32,   power_update_interval,   9) \
 X(a, STATIC,   SINGULAR, BOOL,     power_screen_enabled,  10) \
 X(a, STATIC,   SINGULAR, BOOL,     health_measurement_enabled,  11) \
 X(a, STATIC,   SINGULAR, UINT32,   health_update_interval,  12) \
-X(a, STATIC,   SINGULAR, BOOL,     health_screen_enabled,  13) \
-X(a, STATIC,   SINGULAR, BOOL,     device_telemetry_enabled,  14)
+X(a, STATIC,   SINGULAR, BOOL,     health_screen_enabled,  13)
 #define meshtastic_ModuleConfig_TelemetryConfig_CALLBACK NULL
 #define meshtastic_ModuleConfig_TelemetryConfig_DEFAULT NULL
 
@@ -915,7 +910,7 @@ extern const pb_msgdesc_t meshtastic_RemoteHardwarePin_msg;
 #define meshtastic_ModuleConfig_RemoteHardwareConfig_size 96
 #define meshtastic_ModuleConfig_SerialConfig_size 28
 #define meshtastic_ModuleConfig_StoreForwardConfig_size 24
-#define meshtastic_ModuleConfig_TelemetryConfig_size 48
+#define meshtastic_ModuleConfig_TelemetryConfig_size 46
 #define meshtastic_ModuleConfig_size             227
 #define meshtastic_RemoteHardwarePin_size        21
 

From 22b71a1e9508ce6681ffc6d709ca0508bb4a14b0 Mon Sep 17 00:00:00 2001
From: ford-jones 
Date: Sat, 20 Sep 2025 17:52:41 +1200
Subject: [PATCH 292/691] Pull latest changes from
 https://github.com/meshtastic/protobufs.git

---
 protobufs                                     |  2 +-
 src/mesh/generated/meshtastic/config.pb.h     | 49 +++++++------------
 .../generated/meshtastic/device_ui.pb.cpp     |  2 +
 src/mesh/generated/meshtastic/device_ui.pb.h  | 45 +++++++++++++++--
 src/mesh/generated/meshtastic/deviceonly.pb.h |  2 +-
 src/mesh/generated/meshtastic/localonly.pb.h  |  4 +-
 src/mesh/generated/meshtastic/mesh.pb.h       |  8 ++-
 .../generated/meshtastic/module_config.pb.h   | 13 +++--
 8 files changed, 80 insertions(+), 45 deletions(-)

diff --git a/protobufs b/protobufs
index a84657c22..46b81e822 160000
--- a/protobufs
+++ b/protobufs
@@ -1 +1 @@
-Subproject commit a84657c220421536f18d11fc5edf680efadbceeb
+Subproject commit 46b81e822af1b8e408f437092337f129dee693e6
diff --git a/src/mesh/generated/meshtastic/config.pb.h b/src/mesh/generated/meshtastic/config.pb.h
index 59e55db3f..0453ecad2 100644
--- a/src/mesh/generated/meshtastic/config.pb.h
+++ b/src/mesh/generated/meshtastic/config.pb.h
@@ -173,28 +173,10 @@ typedef enum _meshtastic_Config_NetworkConfig_ProtocolFlags {
     meshtastic_Config_NetworkConfig_ProtocolFlags_UDP_BROADCAST = 1
 } meshtastic_Config_NetworkConfig_ProtocolFlags;
 
-/* How the GPS coordinates are displayed on the OLED screen. */
-typedef enum _meshtastic_Config_DisplayConfig_GpsCoordinateFormat {
-    /* GPS coordinates are displayed in the normal decimal degrees format:
- DD.DDDDDD DDD.DDDDDD */
-    meshtastic_Config_DisplayConfig_GpsCoordinateFormat_DEC = 0,
-    /* GPS coordinates are displayed in the degrees minutes seconds format:
- DD°MM'SS"C DDD°MM'SS"C, where C is the compass point representing the locations quadrant */
-    meshtastic_Config_DisplayConfig_GpsCoordinateFormat_DMS = 1,
-    /* Universal Transverse Mercator format:
- ZZB EEEEEE NNNNNNN, where Z is zone, B is band, E is easting, N is northing */
-    meshtastic_Config_DisplayConfig_GpsCoordinateFormat_UTM = 2,
-    /* Military Grid Reference System format:
- ZZB CD EEEEE NNNNN, where Z is zone, B is band, C is the east 100k square, D is the north 100k square,
- E is easting, N is northing */
-    meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MGRS = 3,
-    /* Open Location Code (aka Plus Codes). */
-    meshtastic_Config_DisplayConfig_GpsCoordinateFormat_OLC = 4,
-    /* Ordnance Survey Grid Reference (the National Grid System of the UK).
- Format: AB EEEEE NNNNN, where A is the east 100k square, B is the north 100k square,
- E is the easting, N is the northing */
-    meshtastic_Config_DisplayConfig_GpsCoordinateFormat_OSGR = 5
-} meshtastic_Config_DisplayConfig_GpsCoordinateFormat;
+/* Deprecated in 2.7.4: Unused */
+typedef enum _meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat {
+    meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat_UNUSED = 0
+} meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat;
 
 /* Unit display preference */
 typedef enum _meshtastic_Config_DisplayConfig_DisplayUnits {
@@ -491,7 +473,7 @@ typedef struct _meshtastic_Config_DisplayConfig {
     uint32_t screen_on_secs;
     /* Deprecated in 2.7.4: Unused
  How the GPS coordinates are formatted on the OLED screen. */
-    meshtastic_Config_DisplayConfig_GpsCoordinateFormat gps_format;
+    meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat gps_format;
     /* Automatically toggles to the next page on the screen like a carousel, based the specified interval in seconds.
  Potentially useful for devices without user buttons. */
     uint32_t auto_screen_carousel_secs;
@@ -515,6 +497,9 @@ typedef struct _meshtastic_Config_DisplayConfig {
     /* If false (default), the device will display the time in 24-hour format on screen.
  If true, the device will display the time in 12-hour format on screen. */
     bool use_12h_clock;
+    /* If false (default), the device will use short names for various display screens.
+ If true, node names will show in long format */
+    bool use_long_node_name;
 } meshtastic_Config_DisplayConfig;
 
 /* Lora Config */
@@ -678,9 +663,9 @@ extern "C" {
 #define _meshtastic_Config_NetworkConfig_ProtocolFlags_MAX meshtastic_Config_NetworkConfig_ProtocolFlags_UDP_BROADCAST
 #define _meshtastic_Config_NetworkConfig_ProtocolFlags_ARRAYSIZE ((meshtastic_Config_NetworkConfig_ProtocolFlags)(meshtastic_Config_NetworkConfig_ProtocolFlags_UDP_BROADCAST+1))
 
-#define _meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MIN meshtastic_Config_DisplayConfig_GpsCoordinateFormat_DEC
-#define _meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MAX meshtastic_Config_DisplayConfig_GpsCoordinateFormat_OSGR
-#define _meshtastic_Config_DisplayConfig_GpsCoordinateFormat_ARRAYSIZE ((meshtastic_Config_DisplayConfig_GpsCoordinateFormat)(meshtastic_Config_DisplayConfig_GpsCoordinateFormat_OSGR+1))
+#define _meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat_MIN meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat_UNUSED
+#define _meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat_MAX meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat_UNUSED
+#define _meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat_ARRAYSIZE ((meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat)(meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat_UNUSED+1))
 
 #define _meshtastic_Config_DisplayConfig_DisplayUnits_MIN meshtastic_Config_DisplayConfig_DisplayUnits_METRIC
 #define _meshtastic_Config_DisplayConfig_DisplayUnits_MAX meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL
@@ -721,7 +706,7 @@ extern "C" {
 #define meshtastic_Config_NetworkConfig_address_mode_ENUMTYPE meshtastic_Config_NetworkConfig_AddressMode
 
 
-#define meshtastic_Config_DisplayConfig_gps_format_ENUMTYPE meshtastic_Config_DisplayConfig_GpsCoordinateFormat
+#define meshtastic_Config_DisplayConfig_gps_format_ENUMTYPE meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat
 #define meshtastic_Config_DisplayConfig_units_ENUMTYPE meshtastic_Config_DisplayConfig_DisplayUnits
 #define meshtastic_Config_DisplayConfig_oled_ENUMTYPE meshtastic_Config_DisplayConfig_OledType
 #define meshtastic_Config_DisplayConfig_displaymode_ENUMTYPE meshtastic_Config_DisplayConfig_DisplayMode
@@ -742,7 +727,7 @@ extern "C" {
 #define meshtastic_Config_PowerConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0}
 #define meshtastic_Config_NetworkConfig_init_default {0, "", "", "", 0, _meshtastic_Config_NetworkConfig_AddressMode_MIN, false, meshtastic_Config_NetworkConfig_IpV4Config_init_default, "", 0, 0}
 #define meshtastic_Config_NetworkConfig_IpV4Config_init_default {0, 0, 0, 0}
-#define meshtastic_Config_DisplayConfig_init_default {0, _meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MIN, 0, 0, 0, _meshtastic_Config_DisplayConfig_DisplayUnits_MIN, _meshtastic_Config_DisplayConfig_OledType_MIN, _meshtastic_Config_DisplayConfig_DisplayMode_MIN, 0, 0, _meshtastic_Config_DisplayConfig_CompassOrientation_MIN, 0}
+#define meshtastic_Config_DisplayConfig_init_default {0, _meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat_MIN, 0, 0, 0, _meshtastic_Config_DisplayConfig_DisplayUnits_MIN, _meshtastic_Config_DisplayConfig_OledType_MIN, _meshtastic_Config_DisplayConfig_DisplayMode_MIN, 0, 0, _meshtastic_Config_DisplayConfig_CompassOrientation_MIN, 0, 0}
 #define meshtastic_Config_LoRaConfig_init_default {0, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, _meshtastic_Config_LoRaConfig_RegionCode_MIN, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}, 0, 0}
 #define meshtastic_Config_BluetoothConfig_init_default {0, _meshtastic_Config_BluetoothConfig_PairingMode_MIN, 0}
 #define meshtastic_Config_SecurityConfig_init_default {{0, {0}}, {0, {0}}, 0, {{0, {0}}, {0, {0}}, {0, {0}}}, 0, 0, 0, 0}
@@ -753,7 +738,7 @@ extern "C" {
 #define meshtastic_Config_PowerConfig_init_zero  {0, 0, 0, 0, 0, 0, 0, 0, 0}
 #define meshtastic_Config_NetworkConfig_init_zero {0, "", "", "", 0, _meshtastic_Config_NetworkConfig_AddressMode_MIN, false, meshtastic_Config_NetworkConfig_IpV4Config_init_zero, "", 0, 0}
 #define meshtastic_Config_NetworkConfig_IpV4Config_init_zero {0, 0, 0, 0}
-#define meshtastic_Config_DisplayConfig_init_zero {0, _meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MIN, 0, 0, 0, _meshtastic_Config_DisplayConfig_DisplayUnits_MIN, _meshtastic_Config_DisplayConfig_OledType_MIN, _meshtastic_Config_DisplayConfig_DisplayMode_MIN, 0, 0, _meshtastic_Config_DisplayConfig_CompassOrientation_MIN, 0}
+#define meshtastic_Config_DisplayConfig_init_zero {0, _meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat_MIN, 0, 0, 0, _meshtastic_Config_DisplayConfig_DisplayUnits_MIN, _meshtastic_Config_DisplayConfig_OledType_MIN, _meshtastic_Config_DisplayConfig_DisplayMode_MIN, 0, 0, _meshtastic_Config_DisplayConfig_CompassOrientation_MIN, 0, 0}
 #define meshtastic_Config_LoRaConfig_init_zero   {0, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, _meshtastic_Config_LoRaConfig_RegionCode_MIN, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}, 0, 0}
 #define meshtastic_Config_BluetoothConfig_init_zero {0, _meshtastic_Config_BluetoothConfig_PairingMode_MIN, 0}
 #define meshtastic_Config_SecurityConfig_init_zero {{0, {0}}, {0, {0}}, 0, {{0, {0}}, {0, {0}}, {0, {0}}}, 0, 0, 0, 0}
@@ -820,6 +805,7 @@ extern "C" {
 #define meshtastic_Config_DisplayConfig_wake_on_tap_or_motion_tag 10
 #define meshtastic_Config_DisplayConfig_compass_orientation_tag 11
 #define meshtastic_Config_DisplayConfig_use_12h_clock_tag 12
+#define meshtastic_Config_DisplayConfig_use_long_node_name_tag 13
 #define meshtastic_Config_LoRaConfig_use_preset_tag 1
 #define meshtastic_Config_LoRaConfig_modem_preset_tag 2
 #define meshtastic_Config_LoRaConfig_bandwidth_tag 3
@@ -965,7 +951,8 @@ X(a, STATIC,   SINGULAR, UENUM,    displaymode,       8) \
 X(a, STATIC,   SINGULAR, BOOL,     heading_bold,      9) \
 X(a, STATIC,   SINGULAR, BOOL,     wake_on_tap_or_motion,  10) \
 X(a, STATIC,   SINGULAR, UENUM,    compass_orientation,  11) \
-X(a, STATIC,   SINGULAR, BOOL,     use_12h_clock,    12)
+X(a, STATIC,   SINGULAR, BOOL,     use_12h_clock,    12) \
+X(a, STATIC,   SINGULAR, BOOL,     use_long_node_name,  13)
 #define meshtastic_Config_DisplayConfig_CALLBACK NULL
 #define meshtastic_Config_DisplayConfig_DEFAULT NULL
 
@@ -1043,7 +1030,7 @@ extern const pb_msgdesc_t meshtastic_Config_SessionkeyConfig_msg;
 #define MESHTASTIC_MESHTASTIC_CONFIG_PB_H_MAX_SIZE meshtastic_Config_size
 #define meshtastic_Config_BluetoothConfig_size   10
 #define meshtastic_Config_DeviceConfig_size      100
-#define meshtastic_Config_DisplayConfig_size     32
+#define meshtastic_Config_DisplayConfig_size     34
 #define meshtastic_Config_LoRaConfig_size        85
 #define meshtastic_Config_NetworkConfig_IpV4Config_size 20
 #define meshtastic_Config_NetworkConfig_size     204
diff --git a/src/mesh/generated/meshtastic/device_ui.pb.cpp b/src/mesh/generated/meshtastic/device_ui.pb.cpp
index 2fc8d9461..01940265f 100644
--- a/src/mesh/generated/meshtastic/device_ui.pb.cpp
+++ b/src/mesh/generated/meshtastic/device_ui.pb.cpp
@@ -28,3 +28,5 @@ PB_BIND(meshtastic_Map, meshtastic_Map, AUTO)
 
 
 
+
+
diff --git a/src/mesh/generated/meshtastic/device_ui.pb.h b/src/mesh/generated/meshtastic/device_ui.pb.h
index 8313438f8..d9eb90773 100644
--- a/src/mesh/generated/meshtastic/device_ui.pb.h
+++ b/src/mesh/generated/meshtastic/device_ui.pb.h
@@ -66,12 +66,40 @@ typedef enum _meshtastic_Language {
     meshtastic_Language_UKRAINIAN = 16,
     /* Bulgarian */
     meshtastic_Language_BULGARIAN = 17,
+    /* Czech */
+    meshtastic_Language_CZECH = 18,
     /* Simplified Chinese (experimental) */
     meshtastic_Language_SIMPLIFIED_CHINESE = 30,
     /* Traditional Chinese (experimental) */
     meshtastic_Language_TRADITIONAL_CHINESE = 31
 } meshtastic_Language;
 
+/* How the GPS coordinates are displayed on the OLED screen. */
+typedef enum _meshtastic_DeviceUIConfig_GpsCoordinateFormat {
+    /* GPS coordinates are displayed in the normal decimal degrees format:
+ DD.DDDDDD DDD.DDDDDD */
+    meshtastic_DeviceUIConfig_GpsCoordinateFormat_DEC = 0,
+    /* GPS coordinates are displayed in the degrees minutes seconds format:
+ DD°MM'SS"C DDD°MM'SS"C, where C is the compass point representing the locations quadrant */
+    meshtastic_DeviceUIConfig_GpsCoordinateFormat_DMS = 1,
+    /* Universal Transverse Mercator format:
+ ZZB EEEEEE NNNNNNN, where Z is zone, B is band, E is easting, N is northing */
+    meshtastic_DeviceUIConfig_GpsCoordinateFormat_UTM = 2,
+    /* Military Grid Reference System format:
+ ZZB CD EEEEE NNNNN, where Z is zone, B is band, C is the east 100k square, D is the north 100k square,
+ E is easting, N is northing */
+    meshtastic_DeviceUIConfig_GpsCoordinateFormat_MGRS = 3,
+    /* Open Location Code (aka Plus Codes). */
+    meshtastic_DeviceUIConfig_GpsCoordinateFormat_OLC = 4,
+    /* Ordnance Survey Grid Reference (the National Grid System of the UK).
+ Format: AB EEEEE NNNNN, where A is the east 100k square, B is the north 100k square,
+ E is the easting, N is the northing */
+    meshtastic_DeviceUIConfig_GpsCoordinateFormat_OSGR = 5,
+    /* Maidenhead Locator System
+ Described here: https://en.wikipedia.org/wiki/Maidenhead_Locator_System */
+    meshtastic_DeviceUIConfig_GpsCoordinateFormat_MLS = 6
+} meshtastic_DeviceUIConfig_GpsCoordinateFormat;
+
 /* Struct definitions */
 typedef struct _meshtastic_NodeFilter {
     /* Filter unknown nodes */
@@ -161,6 +189,8 @@ typedef struct _meshtastic_DeviceUIConfig {
     /* Clockface analog style
  true for analog clockface, false for digital clockface */
     bool is_clockface_analog;
+    /* How the GPS coordinates are formatted on the OLED screen. */
+    meshtastic_DeviceUIConfig_GpsCoordinateFormat gps_format;
 } meshtastic_DeviceUIConfig;
 
 
@@ -181,9 +211,14 @@ extern "C" {
 #define _meshtastic_Language_MAX meshtastic_Language_TRADITIONAL_CHINESE
 #define _meshtastic_Language_ARRAYSIZE ((meshtastic_Language)(meshtastic_Language_TRADITIONAL_CHINESE+1))
 
+#define _meshtastic_DeviceUIConfig_GpsCoordinateFormat_MIN meshtastic_DeviceUIConfig_GpsCoordinateFormat_DEC
+#define _meshtastic_DeviceUIConfig_GpsCoordinateFormat_MAX meshtastic_DeviceUIConfig_GpsCoordinateFormat_MLS
+#define _meshtastic_DeviceUIConfig_GpsCoordinateFormat_ARRAYSIZE ((meshtastic_DeviceUIConfig_GpsCoordinateFormat)(meshtastic_DeviceUIConfig_GpsCoordinateFormat_MLS+1))
+
 #define meshtastic_DeviceUIConfig_theme_ENUMTYPE meshtastic_Theme
 #define meshtastic_DeviceUIConfig_language_ENUMTYPE meshtastic_Language
 #define meshtastic_DeviceUIConfig_compass_mode_ENUMTYPE meshtastic_CompassMode
+#define meshtastic_DeviceUIConfig_gps_format_ENUMTYPE meshtastic_DeviceUIConfig_GpsCoordinateFormat
 
 
 
@@ -191,12 +226,12 @@ extern "C" {
 
 
 /* Initializer values for message structs */
-#define meshtastic_DeviceUIConfig_init_default   {0, 0, 0, 0, 0, 0, _meshtastic_Theme_MIN, 0, 0, 0, _meshtastic_Language_MIN, false, meshtastic_NodeFilter_init_default, false, meshtastic_NodeHighlight_init_default, {0, {0}}, false, meshtastic_Map_init_default, _meshtastic_CompassMode_MIN, 0, 0}
+#define meshtastic_DeviceUIConfig_init_default   {0, 0, 0, 0, 0, 0, _meshtastic_Theme_MIN, 0, 0, 0, _meshtastic_Language_MIN, false, meshtastic_NodeFilter_init_default, false, meshtastic_NodeHighlight_init_default, {0, {0}}, false, meshtastic_Map_init_default, _meshtastic_CompassMode_MIN, 0, 0, _meshtastic_DeviceUIConfig_GpsCoordinateFormat_MIN}
 #define meshtastic_NodeFilter_init_default       {0, 0, 0, 0, 0, "", 0}
 #define meshtastic_NodeHighlight_init_default    {0, 0, 0, 0, ""}
 #define meshtastic_GeoPoint_init_default         {0, 0, 0}
 #define meshtastic_Map_init_default              {false, meshtastic_GeoPoint_init_default, "", 0}
-#define meshtastic_DeviceUIConfig_init_zero      {0, 0, 0, 0, 0, 0, _meshtastic_Theme_MIN, 0, 0, 0, _meshtastic_Language_MIN, false, meshtastic_NodeFilter_init_zero, false, meshtastic_NodeHighlight_init_zero, {0, {0}}, false, meshtastic_Map_init_zero, _meshtastic_CompassMode_MIN, 0, 0}
+#define meshtastic_DeviceUIConfig_init_zero      {0, 0, 0, 0, 0, 0, _meshtastic_Theme_MIN, 0, 0, 0, _meshtastic_Language_MIN, false, meshtastic_NodeFilter_init_zero, false, meshtastic_NodeHighlight_init_zero, {0, {0}}, false, meshtastic_Map_init_zero, _meshtastic_CompassMode_MIN, 0, 0, _meshtastic_DeviceUIConfig_GpsCoordinateFormat_MIN}
 #define meshtastic_NodeFilter_init_zero          {0, 0, 0, 0, 0, "", 0}
 #define meshtastic_NodeHighlight_init_zero       {0, 0, 0, 0, ""}
 #define meshtastic_GeoPoint_init_zero            {0, 0, 0}
@@ -239,6 +274,7 @@ extern "C" {
 #define meshtastic_DeviceUIConfig_compass_mode_tag 16
 #define meshtastic_DeviceUIConfig_screen_rgb_color_tag 17
 #define meshtastic_DeviceUIConfig_is_clockface_analog_tag 18
+#define meshtastic_DeviceUIConfig_gps_format_tag 19
 
 /* Struct field encoding specification for nanopb */
 #define meshtastic_DeviceUIConfig_FIELDLIST(X, a) \
@@ -259,7 +295,8 @@ X(a, STATIC,   SINGULAR, BYTES,    calibration_data,  14) \
 X(a, STATIC,   OPTIONAL, MESSAGE,  map_data,         15) \
 X(a, STATIC,   SINGULAR, UENUM,    compass_mode,     16) \
 X(a, STATIC,   SINGULAR, UINT32,   screen_rgb_color,  17) \
-X(a, STATIC,   SINGULAR, BOOL,     is_clockface_analog,  18)
+X(a, STATIC,   SINGULAR, BOOL,     is_clockface_analog,  18) \
+X(a, STATIC,   SINGULAR, UENUM,    gps_format,       19)
 #define meshtastic_DeviceUIConfig_CALLBACK NULL
 #define meshtastic_DeviceUIConfig_DEFAULT NULL
 #define meshtastic_DeviceUIConfig_node_filter_MSGTYPE meshtastic_NodeFilter
@@ -316,7 +353,7 @@ extern const pb_msgdesc_t meshtastic_Map_msg;
 
 /* Maximum encoded size of messages (where known) */
 #define MESHTASTIC_MESHTASTIC_DEVICE_UI_PB_H_MAX_SIZE meshtastic_DeviceUIConfig_size
-#define meshtastic_DeviceUIConfig_size           201
+#define meshtastic_DeviceUIConfig_size           204
 #define meshtastic_GeoPoint_size                 33
 #define meshtastic_Map_size                      58
 #define meshtastic_NodeFilter_size               47
diff --git a/src/mesh/generated/meshtastic/deviceonly.pb.h b/src/mesh/generated/meshtastic/deviceonly.pb.h
index 9b6330596..7fab82ff7 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        2273
+#define meshtastic_BackupPreferences_size        2277
 #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 da224fb94..3ab6f02c1 100644
--- a/src/mesh/generated/meshtastic/localonly.pb.h
+++ b/src/mesh/generated/meshtastic/localonly.pb.h
@@ -187,8 +187,8 @@ 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        671
+#define meshtastic_LocalConfig_size              749
+#define meshtastic_LocalModuleConfig_size        673
 
 #ifdef __cplusplus
 } /* extern "C" */
diff --git a/src/mesh/generated/meshtastic/mesh.pb.h b/src/mesh/generated/meshtastic/mesh.pb.h
index 2a4e77870..6292ce070 100644
--- a/src/mesh/generated/meshtastic/mesh.pb.h
+++ b/src/mesh/generated/meshtastic/mesh.pb.h
@@ -259,8 +259,8 @@ typedef enum _meshtastic_HardwareModel {
     meshtastic_HardwareModel_T_DECK_PRO = 102,
     /* Lilygo TLora Pager */
     meshtastic_HardwareModel_T_LORA_PAGER = 103,
-    /* GAT562 Mesh Trial Tracker */
-    meshtastic_HardwareModel_GAT562_MESH_TRIAL_TRACKER = 104,
+    /* M5Stack Reserved */
+    meshtastic_HardwareModel_M5STACK_RESERVED = 104, /* 0x68 */
     /* RAKwireless WisMesh Tag */
     meshtastic_HardwareModel_WISMESH_TAG = 105,
     /* RAKwireless WisBlock Core RAK3312 https://docs.rakwireless.com/product-categories/wisduo/rak3112-module/overview/ */
@@ -274,6 +274,10 @@ typedef enum _meshtastic_HardwareModel {
     meshtastic_HardwareModel_T_ECHO_LITE = 109,
     /* New Heltec LoRA32 with ESP32-S3 CPU */
     meshtastic_HardwareModel_HELTEC_V4 = 110,
+    /* M5Stack C6L */
+    meshtastic_HardwareModel_M5STACK_C6L = 111,
+    /* M5Stack Cardputer Adv */
+    meshtastic_HardwareModel_M5STACK_CARDPUTER_ADV = 112,
     /* ------------------------------------------------------------------------------------------------------------------------------------------
  Reserved ID For developing private Ports. These will show up in live traffic sparsely, so we can use a high number. Keep it within 8 bits.
  ------------------------------------------------------------------------------------------------------------------------------------------ */
diff --git a/src/mesh/generated/meshtastic/module_config.pb.h b/src/mesh/generated/meshtastic/module_config.pb.h
index 16c4c230c..47d3b5baa 100644
--- a/src/mesh/generated/meshtastic/module_config.pb.h
+++ b/src/mesh/generated/meshtastic/module_config.pb.h
@@ -356,6 +356,9 @@ typedef struct _meshtastic_ModuleConfig_TelemetryConfig {
     uint32_t health_update_interval;
     /* Enable/Disable the health telemetry module on-device display */
     bool health_screen_enabled;
+    /* Enable/Disable the device telemetry module to send metrics to the mesh
+ Note: We will still send telemtry to the connected phone / client every minute over the API */
+    bool device_telemetry_enabled;
 } meshtastic_ModuleConfig_TelemetryConfig;
 
 /* Canned Messages Module Config */
@@ -523,7 +526,7 @@ extern "C" {
 #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, 0}
-#define meshtastic_ModuleConfig_TelemetryConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
+#define meshtastic_ModuleConfig_TelemetryConfig_init_default {0, 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}
 #define meshtastic_RemoteHardwarePin_init_default {0, "", _meshtastic_RemoteHardwarePinType_MIN}
@@ -539,7 +542,7 @@ extern "C" {
 #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, 0}
-#define meshtastic_ModuleConfig_TelemetryConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
+#define meshtastic_ModuleConfig_TelemetryConfig_init_zero {0, 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}
 #define meshtastic_RemoteHardwarePin_init_zero   {0, "", _meshtastic_RemoteHardwarePinType_MIN}
@@ -627,6 +630,7 @@ extern "C" {
 #define meshtastic_ModuleConfig_TelemetryConfig_health_measurement_enabled_tag 11
 #define meshtastic_ModuleConfig_TelemetryConfig_health_update_interval_tag 12
 #define meshtastic_ModuleConfig_TelemetryConfig_health_screen_enabled_tag 13
+#define meshtastic_ModuleConfig_TelemetryConfig_device_telemetry_enabled_tag 14
 #define meshtastic_ModuleConfig_CannedMessageConfig_rotary1_enabled_tag 1
 #define meshtastic_ModuleConfig_CannedMessageConfig_inputbroker_pin_a_tag 2
 #define meshtastic_ModuleConfig_CannedMessageConfig_inputbroker_pin_b_tag 3
@@ -825,7 +829,8 @@ X(a, STATIC,   SINGULAR, UINT32,   power_update_interval,   9) \
 X(a, STATIC,   SINGULAR, BOOL,     power_screen_enabled,  10) \
 X(a, STATIC,   SINGULAR, BOOL,     health_measurement_enabled,  11) \
 X(a, STATIC,   SINGULAR, UINT32,   health_update_interval,  12) \
-X(a, STATIC,   SINGULAR, BOOL,     health_screen_enabled,  13)
+X(a, STATIC,   SINGULAR, BOOL,     health_screen_enabled,  13) \
+X(a, STATIC,   SINGULAR, BOOL,     device_telemetry_enabled,  14)
 #define meshtastic_ModuleConfig_TelemetryConfig_CALLBACK NULL
 #define meshtastic_ModuleConfig_TelemetryConfig_DEFAULT NULL
 
@@ -910,7 +915,7 @@ extern const pb_msgdesc_t meshtastic_RemoteHardwarePin_msg;
 #define meshtastic_ModuleConfig_RemoteHardwareConfig_size 96
 #define meshtastic_ModuleConfig_SerialConfig_size 28
 #define meshtastic_ModuleConfig_StoreForwardConfig_size 24
-#define meshtastic_ModuleConfig_TelemetryConfig_size 46
+#define meshtastic_ModuleConfig_TelemetryConfig_size 48
 #define meshtastic_ModuleConfig_size             227
 #define meshtastic_RemoteHardwarePin_size        21
 

From 1fc07607cb7f6b141245fbb8b08f0a14b425ae6b Mon Sep 17 00:00:00 2001
From: GUVWAF 
Date: Sat, 20 Sep 2025 13:02:43 +0200
Subject: [PATCH 293/691] Make sure next-hop is only set when they received us
 directly

---
 src/mesh/FloodingRouter.cpp |  3 +--
 src/mesh/NextHopRouter.cpp  | 11 +++++++----
 src/mesh/PacketHistory.cpp  | 32 ++++++++++++++++++++------------
 src/mesh/PacketHistory.h    | 10 ++++------
 4 files changed, 32 insertions(+), 24 deletions(-)

diff --git a/src/mesh/FloodingRouter.cpp b/src/mesh/FloodingRouter.cpp
index f805055c8..0f4b57983 100644
--- a/src/mesh/FloodingRouter.cpp
+++ b/src/mesh/FloodingRouter.cpp
@@ -102,8 +102,7 @@ void FloodingRouter::perhapsRebroadcast(const meshtastic_MeshPacket *p)
 
                 LOG_INFO("Rebroadcast received floodmsg");
                 // Note: we are careful to resend using the original senders node id
-                // We are careful not to call our hooked version of send() - because we don't want to check this again
-                Router::send(tosend);
+                send(tosend);
             } else {
                 LOG_DEBUG("No rebroadcast: Role = CLIENT_MUTE or Rebroadcast Mode = NONE");
             }
diff --git a/src/mesh/NextHopRouter.cpp b/src/mesh/NextHopRouter.cpp
index 9bb8b240c..084452eed 100644
--- a/src/mesh/NextHopRouter.cpp
+++ b/src/mesh/NextHopRouter.cpp
@@ -76,11 +76,14 @@ void NextHopRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtast
             if (origTx) {
                 // 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) ||
-                    (p->hop_start != 0 && p->hop_start == p->hop_limit &&
-                     wasSoleRelayer(ourRelayID, p->decoded.request_id, p->to))) {
+                bool wasAlreadyRelayer = wasRelayer(p->relay_node, p->decoded.request_id, p->to);
+                bool weWereSoleRelayer = false;
+                bool weWereRelayer = wasRelayer(ourRelayID, p->decoded.request_id, p->to, &weWereSoleRelayer);
+                if ((weWereRelayer && wasAlreadyRelayer) ||
+                    (p->hop_start != 0 && p->hop_start == p->hop_limit && weWereSoleRelayer)) {
                     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);
+                        LOG_INFO("Update next hop of 0x%x to 0x%x based on ACK/reply (was relayer %d we were sole %d)", p->from,
+                                 p->relay_node, wasAlreadyRelayer, weWereSoleRelayer);
                         origTx->next_hop = p->relay_node;
                     }
                 }
diff --git a/src/mesh/PacketHistory.cpp b/src/mesh/PacketHistory.cpp
index 735386d79..09b1f84c9 100644
--- a/src/mesh/PacketHistory.cpp
+++ b/src/mesh/PacketHistory.cpp
@@ -66,7 +66,13 @@ bool PacketHistory::wasSeenRecently(const meshtastic_MeshPacket *p, bool withUpd
     r.id = p->id;
     r.sender = getFrom(p); // If 0 then use our ID
     r.next_hop = p->next_hop;
-    r.relayed_by[0] = p->relay_node;
+    bool weWillRelay = false;
+    uint8_t ourRelayID = nodeDB->getLastByteOfNodeNum(nodeDB->getNodeNum());
+    if (p->relay_node == ourRelayID) { // If the relay_node is us, store it
+        weWillRelay = true;
+        r.ourTxHopLimit = p->hop_limit;
+        r.relayed_by[0] = p->relay_node;
+    }
 
     r.rxTimeMsec = millis(); //
     if (r.rxTimeMsec == 0)   // =0 every 49.7 days? 0 is special
@@ -82,8 +88,6 @@ bool PacketHistory::wasSeenRecently(const meshtastic_MeshPacket *p, bool withUpd
     bool seenRecently = (found != NULL);        // If found -> the packet was seen recently
 
     if (seenRecently) {
-        uint8_t ourRelayID = nodeDB->getLastByteOfNodeNum(nodeDB->getNodeNum()); // Get our relay ID from our node number
-
         if (wasFallback) {
             // If it was seen with a next-hop not set to us and now it's NO_NEXT_HOP_PREFERENCE, and the relayer relayed already
             // before, it's a fallback to flooding. If we didn't already relay and the next-hop neither, we might need to handle
@@ -125,11 +129,23 @@ bool PacketHistory::wasSeenRecently(const meshtastic_MeshPacket *p, bool withUpd
                       found->sender, found->id, found->next_hop, found->relayed_by[0], found->relayed_by[1], found->relayed_by[2],
                       millis() - found->rxTimeMsec);
 #endif
+            // Only update the relayer if it heard us directly (meaning hopLimit is decreased by 1)
+            uint8_t startIdx = weWillRelay ? 1 : 0;
+            if (!weWillRelay) {
+                bool weWereRelayer = wasRelayer(ourRelayID, *found);
+                // We were a relayer and the packet came in with a hop limit that is one less than when we sent it out
+                if (weWereRelayer && p->hop_limit == found->ourTxHopLimit - 1) {
+                    r.relayed_by[0] = p->relay_node;
+                    startIdx = 1; // Start copying existing relayers from index 1
+                }
+                // keep the original ourTxHopLimit
+                r.ourTxHopLimit = found->ourTxHopLimit;
+            }
 
             // Add the existing relayed_by to the new record
             for (uint8_t i = 0; i < (NUM_RELAYERS - 1); i++) {
                 if (found->relayed_by[i] != 0)
-                    r.relayed_by[i + 1] = found->relayed_by[i];
+                    r.relayed_by[i + startIdx] = found->relayed_by[i];
             }
             r.next_hop = found->next_hop; // keep the original next_hop (such that we check whether we were originally asked)
 #if VERBOSE_PACKET_HISTORY
@@ -352,14 +368,6 @@ bool PacketHistory::wasRelayer(const uint8_t relayer, const PacketRecord &r, boo
     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
 void PacketHistory::removeRelayer(const uint8_t relayer, const uint32_t id, const NodeNum sender)
 {
diff --git a/src/mesh/PacketHistory.h b/src/mesh/PacketHistory.h
index 4b53c8f6a..20aac9999 100644
--- a/src/mesh/PacketHistory.h
+++ b/src/mesh/PacketHistory.h
@@ -2,8 +2,8 @@
 
 #include "NodeDB.h"
 
-#define NUM_RELAYERS                                                                                                             \
-    3 // Number of relayer we keep track of. Use 3 to be efficient with memory alignment of PacketRecord to 16 bytes
+// Number of relayers we keep track of. Use 6 to be efficient with memory alignment of PacketRecord to 20 bytes
+#define NUM_RELAYERS 6
 
 /**
  * This is a mixin that adds a record of past packets we have seen
@@ -16,8 +16,9 @@ class PacketHistory
         PacketId id;
         uint32_t rxTimeMsec;              // Unix time in msecs - the time we received it,  0 means empty
         uint8_t next_hop;                 // The next hop asked for this packet
+        uint8_t ourTxHopLimit;            // The hop limit of the packet when we first transmitted it
         uint8_t relayed_by[NUM_RELAYERS]; // Array of nodes that relayed this packet
-    };                                    // 4B + 4B + 4B + 1B + 3B = 16B
+    };                                    // 4B + 4B + 4B + 1B + 1B + 6B = 20B
 
     uint32_t recentPacketsCapacity =
         0; // Can be set in constructor, no need to recompile. Used to allocate memory for mx_recentPackets.
@@ -59,9 +60,6 @@ class PacketHistory
      * @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 *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 8db9b24934f04f2475efcf803ea9a5093a7c3d6e Mon Sep 17 00:00:00 2001
From: Markus <974709+Links2004@users.noreply.github.com>
Date: Sat, 20 Sep 2025 13:33:41 +0200
Subject: [PATCH 294/691] fix build with HAS_TELEMETRY 0 (#8051)

---
 src/modules/Modules.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/modules/Modules.cpp b/src/modules/Modules.cpp
index 757753d45..b3c15e764 100644
--- a/src/modules/Modules.cpp
+++ b/src/modules/Modules.cpp
@@ -241,7 +241,7 @@ void setupModules()
 #if HAS_TELEMETRY
         new DeviceTelemetryModule();
 #endif
-#if HAS_SENSOR && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
+#if HAS_TELEMETRY && HAS_SENSOR && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
         if (moduleConfig.has_telemetry &&
             (moduleConfig.telemetry.environment_measurement_enabled || moduleConfig.telemetry.environment_screen_enabled)) {
             new EnvironmentTelemetryModule();

From 44968415a5a71a7320bbed13c1534e3686aefd9f Mon Sep 17 00:00:00 2001
From: Markus <974709+Links2004@users.noreply.github.com>
Date: Sat, 20 Sep 2025 13:33:41 +0200
Subject: [PATCH 295/691] fix build with HAS_TELEMETRY 0 (#8051)

---
 src/modules/Modules.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/modules/Modules.cpp b/src/modules/Modules.cpp
index 757753d45..b3c15e764 100644
--- a/src/modules/Modules.cpp
+++ b/src/modules/Modules.cpp
@@ -241,7 +241,7 @@ void setupModules()
 #if HAS_TELEMETRY
         new DeviceTelemetryModule();
 #endif
-#if HAS_SENSOR && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
+#if HAS_TELEMETRY && HAS_SENSOR && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
         if (moduleConfig.has_telemetry &&
             (moduleConfig.telemetry.environment_measurement_enabled || moduleConfig.telemetry.environment_screen_enabled)) {
             new EnvironmentTelemetryModule();

From 1d3c47c5fa813e6fe44627063fa041aa28735057 Mon Sep 17 00:00:00 2001
From: GUVWAF <78759985+GUVWAF@users.noreply.github.com>
Date: Sat, 20 Sep 2025 13:37:40 +0200
Subject: [PATCH 296/691] Make sure to ACK ACKs/replies if next-hop routing is
 used (#8052)

* Make sure to ACK ACKs/replies if next-hop routing is used
To stop their retransmissions; hop limit of 0 is enough

* Update src/mesh/ReliableRouter.cpp

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Ben Meadors 
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---
 src/mesh/ReliableRouter.cpp | 45 +++++++++++++++++++++----------------
 1 file changed, 26 insertions(+), 19 deletions(-)

diff --git a/src/mesh/ReliableRouter.cpp b/src/mesh/ReliableRouter.cpp
index 6d098b669..cca838ff0 100644
--- a/src/mesh/ReliableRouter.cpp
+++ b/src/mesh/ReliableRouter.cpp
@@ -97,27 +97,34 @@ bool ReliableRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
 void ReliableRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtastic_Routing *c)
 {
     if (isToUs(p)) { // ignore ack/nak/want_ack packets that are not address to us (we only handle 0 hop reliability)
-        if (p->want_ack) {
-            if (MeshModule::currentReply) {
-                LOG_DEBUG("Another module replied to this message, no need for 2nd ack");
-            } else if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) {
-                // A response may be set to want_ack for retransmissions, but we don't need to ACK a response if it received an
-                // implicit ACK already. If we received it directly, only ACK with a hop limit of 0
-                if (!p->decoded.request_id)
-                    sendAckNak(meshtastic_Routing_Error_NONE, getFrom(p), p->id, p->channel,
+        if (!MeshModule::currentReply) {
+            if (p->want_ack) {
+                if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) {
+                    /* A response may be set to want_ack for retransmissions, but we don't need to ACK a response if it received
+                      an implicit ACK already. If we received it directly or via NextHopRouter, only ACK with a hop limit of 0 to
+                      make sure the other side stops retransmitting. */
+                    if (!p->decoded.request_id && !p->decoded.reply_id) {
+                        sendAckNak(meshtastic_Routing_Error_NONE, getFrom(p), p->id, p->channel,
+                                   routingModule->getHopLimitForResponse(p->hop_start, p->hop_limit));
+                    } else if ((p->hop_start > 0 && p->hop_start == p->hop_limit) || p->next_hop != NO_NEXT_HOP_PREFERENCE) {
+                        sendAckNak(meshtastic_Routing_Error_NONE, getFrom(p), p->id, p->channel, 0);
+                    }
+                } else if (p->which_payload_variant == meshtastic_MeshPacket_encrypted_tag && p->channel == 0 &&
+                           (nodeDB->getMeshNode(p->from) == nullptr || nodeDB->getMeshNode(p->from)->user.public_key.size == 0)) {
+                    LOG_INFO("PKI packet from unknown node, send PKI_UNKNOWN_PUBKEY");
+                    sendAckNak(meshtastic_Routing_Error_PKI_UNKNOWN_PUBKEY, getFrom(p), p->id, channels.getPrimaryIndex(),
                                routingModule->getHopLimitForResponse(p->hop_start, p->hop_limit));
-                else if (p->hop_start > 0 && p->hop_start == p->hop_limit)
-                    sendAckNak(meshtastic_Routing_Error_NONE, getFrom(p), p->id, p->channel, 0);
-            } else if (p->which_payload_variant == meshtastic_MeshPacket_encrypted_tag && p->channel == 0 &&
-                       (nodeDB->getMeshNode(p->from) == nullptr || nodeDB->getMeshNode(p->from)->user.public_key.size == 0)) {
-                LOG_INFO("PKI packet from unknown node, send PKI_UNKNOWN_PUBKEY");
-                sendAckNak(meshtastic_Routing_Error_PKI_UNKNOWN_PUBKEY, getFrom(p), p->id, channels.getPrimaryIndex(),
-                           routingModule->getHopLimitForResponse(p->hop_start, p->hop_limit));
-            } else {
-                // Send a 'NO_CHANNEL' error on the primary channel if want_ack packet destined for us cannot be decoded
-                sendAckNak(meshtastic_Routing_Error_NO_CHANNEL, getFrom(p), p->id, channels.getPrimaryIndex(),
-                           routingModule->getHopLimitForResponse(p->hop_start, p->hop_limit));
+                } else {
+                    // Send a 'NO_CHANNEL' error on the primary channel if want_ack packet destined for us cannot be decoded
+                    sendAckNak(meshtastic_Routing_Error_NO_CHANNEL, getFrom(p), p->id, channels.getPrimaryIndex(),
+                               routingModule->getHopLimitForResponse(p->hop_start, p->hop_limit));
+                }
+            } else if (p->next_hop == nodeDB->getLastByteOfNodeNum(getNodeNum()) && p->hop_limit > 0) {
+                // No wantAck, but we need to ACK with hop limit of 0 if we were the next hop to stop their retransmissions
+                sendAckNak(meshtastic_Routing_Error_NONE, getFrom(p), p->id, p->channel, 0);
             }
+        } else {
+            LOG_DEBUG("Another module replied to this message, no need for 2nd ack");
         }
         if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag && c &&
             c->error_reason == meshtastic_Routing_Error_PKI_UNKNOWN_PUBKEY) {

From db2f79b6c467fc77ac7c024fb6838bb8d27c0324 Mon Sep 17 00:00:00 2001
From: WillyJL 
Date: Sat, 20 Sep 2025 14:04:27 +0200
Subject: [PATCH 297/691] Fix last build issues on develop (#8046)

---
 src/graphics/Screen.cpp | 1161 +++++++++++++++++++--------------------
 1 file changed, 579 insertions(+), 582 deletions(-)

diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp
index 689c550d3..a440ecab9 100644
--- a/src/graphics/Screen.cpp
+++ b/src/graphics/Screen.cpp
@@ -954,708 +954,705 @@ void Screen::setFrames(FrameFocus focus)
 
 #if defined(DISPLAY_CLOCK_FRAME)
     if (!hiddenFrames.clock) {
-        if (!hiddenFrames.clock) {
-            fsi.positions.clock = numframes;
+        fsi.positions.clock = numframes;
 #if defined(M5STACK_UNITC6L)
-            normalFrames[numframes++] = graphics::ClockRenderer::drawAnalogClockFrame;
+        normalFrames[numframes++] = graphics::ClockRenderer::drawAnalogClockFrame;
 #else
-            normalFrames[numframes++] = uiconfig.is_clockface_analog ? graphics::ClockRenderer::drawAnalogClockFrame
-                                                                     : graphics::ClockRenderer::drawDigitalClockFrame;
+        normalFrames[numframes++] = uiconfig.is_clockface_analog ? graphics::ClockRenderer::drawAnalogClockFrame
+                                                                 : graphics::ClockRenderer::drawDigitalClockFrame;
 #endif
-            indicatorIcons.push_back(digital_icon_clock);
+        indicatorIcons.push_back(digital_icon_clock);
+    }
 #endif
 
-            // Declare this early so it’s available in FOCUS_PRESERVE block
-            bool willInsertTextMessage = shouldDrawMessage(&devicestate.rx_text_message);
+    // Declare this early so it’s available in FOCUS_PRESERVE block
+    bool willInsertTextMessage = shouldDrawMessage(&devicestate.rx_text_message);
 
-            if (!hiddenFrames.home) {
-                fsi.positions.home = numframes;
-                normalFrames[numframes++] = graphics::UIRenderer::drawDeviceFocused;
-                indicatorIcons.push_back(icon_home);
-            }
+    if (!hiddenFrames.home) {
+        fsi.positions.home = numframes;
+        normalFrames[numframes++] = graphics::UIRenderer::drawDeviceFocused;
+        indicatorIcons.push_back(icon_home);
+    }
 
-            fsi.positions.textMessage = numframes;
-            normalFrames[numframes++] = graphics::MessageRenderer::drawTextMessageFrame;
-            indicatorIcons.push_back(icon_mail);
+    fsi.positions.textMessage = numframes;
+    normalFrames[numframes++] = graphics::MessageRenderer::drawTextMessageFrame;
+    indicatorIcons.push_back(icon_mail);
 
 #ifndef USE_EINK
-            if (!hiddenFrames.nodelist) {
-                fsi.positions.nodelist = numframes;
-                normalFrames[numframes++] = graphics::NodeListRenderer::drawDynamicNodeListScreen;
-                indicatorIcons.push_back(icon_nodes);
-            }
+    if (!hiddenFrames.nodelist) {
+        fsi.positions.nodelist = numframes;
+        normalFrames[numframes++] = graphics::NodeListRenderer::drawDynamicNodeListScreen;
+        indicatorIcons.push_back(icon_nodes);
+    }
 #endif
 
 // Show detailed node views only on E-Ink builds
 #ifdef USE_EINK
-            if (!hiddenFrames.nodelist_lastheard) {
-                fsi.positions.nodelist_lastheard = numframes;
-                normalFrames[numframes++] = graphics::NodeListRenderer::drawLastHeardScreen;
-                indicatorIcons.push_back(icon_nodes);
-            }
-            if (!hiddenFrames.nodelist_hopsignal) {
-                fsi.positions.nodelist_hopsignal = numframes;
-                normalFrames[numframes++] = graphics::NodeListRenderer::drawHopSignalScreen;
-                indicatorIcons.push_back(icon_signal);
-            }
-            if (!hiddenFrames.nodelist_distance) {
-                fsi.positions.nodelist_distance = numframes;
-                normalFrames[numframes++] = graphics::NodeListRenderer::drawDistanceScreen;
-                indicatorIcons.push_back(icon_distance);
-            }
+    if (!hiddenFrames.nodelist_lastheard) {
+        fsi.positions.nodelist_lastheard = numframes;
+        normalFrames[numframes++] = graphics::NodeListRenderer::drawLastHeardScreen;
+        indicatorIcons.push_back(icon_nodes);
+    }
+    if (!hiddenFrames.nodelist_hopsignal) {
+        fsi.positions.nodelist_hopsignal = numframes;
+        normalFrames[numframes++] = graphics::NodeListRenderer::drawHopSignalScreen;
+        indicatorIcons.push_back(icon_signal);
+    }
+    if (!hiddenFrames.nodelist_distance) {
+        fsi.positions.nodelist_distance = numframes;
+        normalFrames[numframes++] = graphics::NodeListRenderer::drawDistanceScreen;
+        indicatorIcons.push_back(icon_distance);
+    }
 #endif
 #if HAS_GPS
-            if (!hiddenFrames.nodelist_bearings) {
-                fsi.positions.nodelist_bearings = numframes;
-                normalFrames[numframes++] = graphics::NodeListRenderer::drawNodeListWithCompasses;
-                indicatorIcons.push_back(icon_list);
-            }
-            if (!hiddenFrames.gps) {
-                fsi.positions.gps = numframes;
-                normalFrames[numframes++] = graphics::UIRenderer::drawCompassAndLocationScreen;
-                indicatorIcons.push_back(icon_compass);
-            }
+    if (!hiddenFrames.nodelist_bearings) {
+        fsi.positions.nodelist_bearings = numframes;
+        normalFrames[numframes++] = graphics::NodeListRenderer::drawNodeListWithCompasses;
+        indicatorIcons.push_back(icon_list);
+    }
+    if (!hiddenFrames.gps) {
+        fsi.positions.gps = numframes;
+        normalFrames[numframes++] = graphics::UIRenderer::drawCompassAndLocationScreen;
+        indicatorIcons.push_back(icon_compass);
+    }
 #endif
-            if (RadioLibInterface::instance && !hiddenFrames.lora) {
-                fsi.positions.lora = numframes;
-                normalFrames[numframes++] = graphics::DebugRenderer::drawLoRaFocused;
-                indicatorIcons.push_back(icon_radio);
-            }
-            if (!hiddenFrames.system) {
-                fsi.positions.system = numframes;
-                normalFrames[numframes++] = graphics::DebugRenderer::drawSystemScreen;
-                indicatorIcons.push_back(icon_system);
-            }
+    if (RadioLibInterface::instance && !hiddenFrames.lora) {
+        fsi.positions.lora = numframes;
+        normalFrames[numframes++] = graphics::DebugRenderer::drawLoRaFocused;
+        indicatorIcons.push_back(icon_radio);
+    }
+    if (!hiddenFrames.system) {
+        fsi.positions.system = numframes;
+        normalFrames[numframes++] = graphics::DebugRenderer::drawSystemScreen;
+        indicatorIcons.push_back(icon_system);
+    }
 #if !defined(DISPLAY_CLOCK_FRAME)
-            if (!hiddenFrames.clock) {
-                fsi.positions.clock = numframes;
-                normalFrames[numframes++] = uiconfig.is_clockface_analog ? graphics::ClockRenderer::drawAnalogClockFrame
-                                                                         : graphics::ClockRenderer::drawDigitalClockFrame;
-                indicatorIcons.push_back(digital_icon_clock);
-            }
+    if (!hiddenFrames.clock) {
+        fsi.positions.clock = numframes;
+        normalFrames[numframes++] = uiconfig.is_clockface_analog ? graphics::ClockRenderer::drawAnalogClockFrame
+                                                                 : graphics::ClockRenderer::drawDigitalClockFrame;
+        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 (!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()) {
-                fsi.positions.wifi = numframes;
-                normalFrames[numframes++] = graphics::DebugRenderer::drawDebugInfoWiFiTrampoline;
-                indicatorIcons.push_back(icon_wifi);
-            }
+    if (!hiddenFrames.wifi && isWifiAvailable()) {
+        fsi.positions.wifi = numframes;
+        normalFrames[numframes++] = graphics::DebugRenderer::drawDebugInfoWiFiTrampoline;
+        indicatorIcons.push_back(icon_wifi);
+    }
 #endif
 
-            // Beware of what changes you make in this code!
-            // We pass numframes into GetMeshModulesWithUIFrames() which is highly important!
-            // Inside of that callback, goes over to MeshModule.cpp and we run
-            // modulesWithUIFrames.resize(startIndex, nullptr), to insert nullptr
-            // entries until we're ready to start building the matching entries.
-            // We are doing our best to keep the normalFrames vector
-            // and the moduleFrames vector in lock step.
-            moduleFrames = MeshModule::GetMeshModulesWithUIFrames(numframes);
-            LOG_DEBUG("Show %d module frames", moduleFrames.size());
+    // Beware of what changes you make in this code!
+    // We pass numframes into GetMeshModulesWithUIFrames() which is highly important!
+    // Inside of that callback, goes over to MeshModule.cpp and we run
+    // modulesWithUIFrames.resize(startIndex, nullptr), to insert nullptr
+    // entries until we're ready to start building the matching entries.
+    // We are doing our best to keep the normalFrames vector
+    // and the moduleFrames vector in lock step.
+    moduleFrames = MeshModule::GetMeshModulesWithUIFrames(numframes);
+    LOG_DEBUG("Show %d module frames", moduleFrames.size());
 
-            for (auto i = moduleFrames.begin(); i != moduleFrames.end(); ++i) {
-                // Draw the module frame, using the hack described above
-                if (*i != nullptr) {
-                    normalFrames[numframes] = drawModuleFrame;
+    for (auto i = moduleFrames.begin(); i != moduleFrames.end(); ++i) {
+        // Draw the module frame, using the hack described above
+        if (*i != nullptr) {
+            normalFrames[numframes] = drawModuleFrame;
 
-                    // Check if the module being drawn has requested focus
-                    // We will honor this request later, if setFrames was triggered by a UIFrameEvent
-                    MeshModule *m = *i;
-                    if (m && m->isRequestingFocus())
-                        fsi.positions.focusedModule = numframes;
-                    if (m && m == waypointModule)
-                        fsi.positions.waypoint = numframes;
+            // Check if the module being drawn has requested focus
+            // We will honor this request later, if setFrames was triggered by a UIFrameEvent
+            MeshModule *m = *i;
+            if (m && m->isRequestingFocus())
+                fsi.positions.focusedModule = numframes;
+            if (m && m == waypointModule)
+                fsi.positions.waypoint = numframes;
 
-                    indicatorIcons.push_back(icon_module);
-                    numframes++;
-                }
+            indicatorIcons.push_back(icon_module);
+            numframes++;
+        }
+    }
+
+    LOG_DEBUG("Added modules.  numframes: %d", numframes);
+
+    // We don't show the node info of our node (if we have it yet - we should)
+    size_t numMeshNodes = nodeDB->getNumMeshNodes();
+    if (numMeshNodes > 0)
+        numMeshNodes--;
+
+    if (!hiddenFrames.show_favorites) {
+        // Temporary array to hold favorite node frames
+        std::vector favoriteFrames;
+
+        for (size_t i = 0; i < nodeDB->getNumMeshNodes(); i++) {
+            const meshtastic_NodeInfoLite *n = nodeDB->getMeshNodeByIndex(i);
+            if (n && n->num != nodeDB->getNodeNum() && n->is_favorite) {
+                favoriteFrames.push_back(graphics::UIRenderer::drawNodeInfo);
             }
-
-            LOG_DEBUG("Added modules.  numframes: %d", numframes);
-
-            // We don't show the node info of our node (if we have it yet - we should)
-            size_t numMeshNodes = nodeDB->getNumMeshNodes();
-            if (numMeshNodes > 0)
-                numMeshNodes--;
-
-            if (!hiddenFrames.show_favorites) {
-                // Temporary array to hold favorite node frames
-                std::vector favoriteFrames;
-
-                for (size_t i = 0; i < nodeDB->getNumMeshNodes(); i++) {
-                    const meshtastic_NodeInfoLite *n = nodeDB->getMeshNodeByIndex(i);
-                    if (n && n->num != nodeDB->getNodeNum() && n->is_favorite) {
-                        favoriteFrames.push_back(graphics::UIRenderer::drawNodeInfo);
-                    }
-                }
-
-                // Insert favorite frames *after* collecting them all
-                if (!favoriteFrames.empty()) {
-                    fsi.positions.firstFavorite = numframes;
-                    for (const auto &f : favoriteFrames) {
-                        normalFrames[numframes++] = f;
-                        indicatorIcons.push_back(icon_node);
-                    }
-                    fsi.positions.lastFavorite = numframes - 1;
-                } else {
-                    fsi.positions.firstFavorite = 255;
-                    fsi.positions.lastFavorite = 255;
-                }
-            }
-
-            fsi.frameCount = numframes;   // Total framecount is used to apply FOCUS_PRESERVE
-            this->frameCount = numframes; // ✅ Save frame count for use in custom overlay
-            LOG_DEBUG("Finished build frames. numframes: %d", numframes);
-
-            ui->setFrames(normalFrames, numframes);
-            ui->disableAllIndicators();
-
-            // Add overlays: frame icons and alert banner)
-            static OverlayCallback overlays[] = {graphics::UIRenderer::drawNavigationBar,
-                                                 NotificationRenderer::drawBannercallback};
-            ui->setOverlays(overlays, sizeof(overlays) / sizeof(overlays[0]));
-
-            prevFrame = -1; // Force drawNodeInfo to pick a new node (because our list just changed)
-
-            // Focus on a specific frame, in the frame set we just created
-            switch (focus) {
-            case FOCUS_DEFAULT:
-                ui->switchToFrame(fsi.positions.deviceFocused);
-                break;
-            case FOCUS_FAULT:
-                ui->switchToFrame(fsi.positions.fault);
-                break;
-            case FOCUS_TEXTMESSAGE:
-                hasUnreadMessage = false; // ✅ Clear when message is *viewed*
-                ui->switchToFrame(fsi.positions.textMessage);
-                break;
-            case FOCUS_MODULE:
-                // Whichever frame was marked by MeshModule::requestFocus(), if any
-                // If no module requested focus, will show the first frame instead
-                ui->switchToFrame(fsi.positions.focusedModule);
-                break;
-            case FOCUS_CLOCK:
-                // Whichever frame was marked by MeshModule::requestFocus(), if any
-                // If no module requested focus, will show the first frame instead
-                ui->switchToFrame(fsi.positions.clock);
-                break;
-            case FOCUS_SYSTEM:
-                ui->switchToFrame(fsi.positions.system);
-                break;
-
-            case FOCUS_PRESERVE:
-                //  No more adjustment — force stay on same index
-                if (previousFrameCount > fsi.frameCount) {
-                    ui->switchToFrame(originalPosition - 1);
-                } else if (previousFrameCount < fsi.frameCount) {
-                    ui->switchToFrame(originalPosition + 1);
-                } else {
-                    ui->switchToFrame(originalPosition);
-                }
-                break;
-            }
-
-            // Store the info about this frameset, for future setFrames calls
-            this->framesetInfo = fsi;
-
-            setFastFramerate(); // Draw ASAP
         }
 
-        void Screen::setFrameImmediateDraw(FrameCallback * drawFrames)
-        {
-            ui->disableAllIndicators();
-            ui->setFrames(drawFrames, 1);
-            setFastFramerate();
+        // Insert favorite frames *after* collecting them all
+        if (!favoriteFrames.empty()) {
+            fsi.positions.firstFavorite = numframes;
+            for (const auto &f : favoriteFrames) {
+                normalFrames[numframes++] = f;
+                indicatorIcons.push_back(icon_node);
+            }
+            fsi.positions.lastFavorite = numframes - 1;
+        } else {
+            fsi.positions.firstFavorite = 255;
+            fsi.positions.lastFavorite = 255;
         }
+    }
 
-        void Screen::toggleFrameVisibility(const std::string &frameName)
-        {
+    fsi.frameCount = numframes;   // Total framecount is used to apply FOCUS_PRESERVE
+    this->frameCount = numframes; // ✅ Save frame count for use in custom overlay
+    LOG_DEBUG("Finished build frames. numframes: %d", numframes);
+
+    ui->setFrames(normalFrames, numframes);
+    ui->disableAllIndicators();
+
+    // Add overlays: frame icons and alert banner)
+    static OverlayCallback overlays[] = {graphics::UIRenderer::drawNavigationBar, NotificationRenderer::drawBannercallback};
+    ui->setOverlays(overlays, sizeof(overlays) / sizeof(overlays[0]));
+
+    prevFrame = -1; // Force drawNodeInfo to pick a new node (because our list just changed)
+
+    // Focus on a specific frame, in the frame set we just created
+    switch (focus) {
+    case FOCUS_DEFAULT:
+        ui->switchToFrame(fsi.positions.deviceFocused);
+        break;
+    case FOCUS_FAULT:
+        ui->switchToFrame(fsi.positions.fault);
+        break;
+    case FOCUS_TEXTMESSAGE:
+        hasUnreadMessage = false; // ✅ Clear when message is *viewed*
+        ui->switchToFrame(fsi.positions.textMessage);
+        break;
+    case FOCUS_MODULE:
+        // Whichever frame was marked by MeshModule::requestFocus(), if any
+        // If no module requested focus, will show the first frame instead
+        ui->switchToFrame(fsi.positions.focusedModule);
+        break;
+    case FOCUS_CLOCK:
+        // Whichever frame was marked by MeshModule::requestFocus(), if any
+        // If no module requested focus, will show the first frame instead
+        ui->switchToFrame(fsi.positions.clock);
+        break;
+    case FOCUS_SYSTEM:
+        ui->switchToFrame(fsi.positions.system);
+        break;
+
+    case FOCUS_PRESERVE:
+        //  No more adjustment — force stay on same index
+        if (previousFrameCount > fsi.frameCount) {
+            ui->switchToFrame(originalPosition - 1);
+        } else if (previousFrameCount < fsi.frameCount) {
+            ui->switchToFrame(originalPosition + 1);
+        } else {
+            ui->switchToFrame(originalPosition);
+        }
+        break;
+    }
+
+    // Store the info about this frameset, for future setFrames calls
+    this->framesetInfo = fsi;
+
+    setFastFramerate(); // Draw ASAP
+}
+
+void Screen::setFrameImmediateDraw(FrameCallback *drawFrames)
+{
+    ui->disableAllIndicators();
+    ui->setFrames(drawFrames, 1);
+    setFastFramerate();
+}
+
+void Screen::toggleFrameVisibility(const std::string &frameName)
+{
 #ifndef USE_EINK
-            if (frameName == "nodelist") {
-                hiddenFrames.nodelist = !hiddenFrames.nodelist;
-            }
+    if (frameName == "nodelist") {
+        hiddenFrames.nodelist = !hiddenFrames.nodelist;
+    }
 #endif
 #ifdef USE_EINK
-            if (frameName == "nodelist_lastheard") {
-                hiddenFrames.nodelist_lastheard = !hiddenFrames.nodelist_lastheard;
-            }
-            if (frameName == "nodelist_hopsignal") {
-                hiddenFrames.nodelist_hopsignal = !hiddenFrames.nodelist_hopsignal;
-            }
-            if (frameName == "nodelist_distance") {
-                hiddenFrames.nodelist_distance = !hiddenFrames.nodelist_distance;
-            }
+    if (frameName == "nodelist_lastheard") {
+        hiddenFrames.nodelist_lastheard = !hiddenFrames.nodelist_lastheard;
+    }
+    if (frameName == "nodelist_hopsignal") {
+        hiddenFrames.nodelist_hopsignal = !hiddenFrames.nodelist_hopsignal;
+    }
+    if (frameName == "nodelist_distance") {
+        hiddenFrames.nodelist_distance = !hiddenFrames.nodelist_distance;
+    }
 #endif
 #if HAS_GPS
-            if (frameName == "nodelist_bearings") {
-                hiddenFrames.nodelist_bearings = !hiddenFrames.nodelist_bearings;
-            }
-            if (frameName == "gps") {
-                hiddenFrames.gps = !hiddenFrames.gps;
-            }
+    if (frameName == "nodelist_bearings") {
+        hiddenFrames.nodelist_bearings = !hiddenFrames.nodelist_bearings;
+    }
+    if (frameName == "gps") {
+        hiddenFrames.gps = !hiddenFrames.gps;
+    }
 #endif
-            if (frameName == "lora") {
-                hiddenFrames.lora = !hiddenFrames.lora;
-            }
-            if (frameName == "clock") {
-                hiddenFrames.clock = !hiddenFrames.clock;
-            }
-            if (frameName == "show_favorites") {
-                hiddenFrames.show_favorites = !hiddenFrames.show_favorites;
-            }
-            if (frameName == "chirpy") {
-                hiddenFrames.chirpy = !hiddenFrames.chirpy;
-            }
-        }
+    if (frameName == "lora") {
+        hiddenFrames.lora = !hiddenFrames.lora;
+    }
+    if (frameName == "clock") {
+        hiddenFrames.clock = !hiddenFrames.clock;
+    }
+    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
-        {
+bool Screen::isFrameHidden(const std::string &frameName) const
+{
 #ifndef USE_EINK
-            if (frameName == "nodelist")
-                return hiddenFrames.nodelist;
+    if (frameName == "nodelist")
+        return hiddenFrames.nodelist;
 #endif
 #ifdef USE_EINK
-            if (frameName == "nodelist_lastheard")
-                return hiddenFrames.nodelist_lastheard;
-            if (frameName == "nodelist_hopsignal")
-                return hiddenFrames.nodelist_hopsignal;
-            if (frameName == "nodelist_distance")
-                return hiddenFrames.nodelist_distance;
+    if (frameName == "nodelist_lastheard")
+        return hiddenFrames.nodelist_lastheard;
+    if (frameName == "nodelist_hopsignal")
+        return hiddenFrames.nodelist_hopsignal;
+    if (frameName == "nodelist_distance")
+        return hiddenFrames.nodelist_distance;
 #endif
 #if HAS_GPS
-            if (frameName == "nodelist_bearings")
-                return hiddenFrames.nodelist_bearings;
-            if (frameName == "gps")
-                return hiddenFrames.gps;
+    if (frameName == "nodelist_bearings")
+        return hiddenFrames.nodelist_bearings;
+    if (frameName == "gps")
+        return hiddenFrames.gps;
 #endif
-            if (frameName == "lora")
-                return hiddenFrames.lora;
-            if (frameName == "clock")
-                return hiddenFrames.clock;
-            if (frameName == "show_favorites")
-                return hiddenFrames.show_favorites;
-            if (frameName == "chirpy")
-                return hiddenFrames.chirpy;
+    if (frameName == "lora")
+        return hiddenFrames.lora;
+    if (frameName == "clock")
+        return hiddenFrames.clock;
+    if (frameName == "show_favorites")
+        return hiddenFrames.show_favorites;
+    if (frameName == "chirpy")
+        return hiddenFrames.chirpy;
 
-            return false;
-        }
+    return false;
+}
 
-        // Dismisses the currently displayed screen frame, if possible
-        // Relevant for text message, waypoint, others in future?
-        // Triggered with a CardKB keycombo
-        void Screen::hideCurrentFrame()
-        {
-            uint8_t currentFrame = ui->getUiState()->currentFrame;
-            bool dismissed = false;
-            if (currentFrame == framesetInfo.positions.textMessage && devicestate.has_rx_text_message) {
-                LOG_INFO("Hide Text Message");
-                devicestate.has_rx_text_message = false;
-                memset(&devicestate.rx_text_message, 0, sizeof(devicestate.rx_text_message));
-            } else if (currentFrame == framesetInfo.positions.waypoint && devicestate.has_rx_waypoint) {
-                LOG_DEBUG("Hide Waypoint");
-                devicestate.has_rx_waypoint = false;
-                hiddenFrames.waypoint = true;
-                dismissed = true;
-            } else if (currentFrame == framesetInfo.positions.wifi) {
-                LOG_DEBUG("Hide WiFi Screen");
-                hiddenFrames.wifi = true;
-                dismissed = true;
-            } else if (currentFrame == framesetInfo.positions.lora) {
-                LOG_INFO("Hide LoRa");
-                hiddenFrames.lora = true;
-                dismissed = true;
-            }
+// Dismisses the currently displayed screen frame, if possible
+// Relevant for text message, waypoint, others in future?
+// Triggered with a CardKB keycombo
+void Screen::hideCurrentFrame()
+{
+    uint8_t currentFrame = ui->getUiState()->currentFrame;
+    bool dismissed = false;
+    if (currentFrame == framesetInfo.positions.textMessage && devicestate.has_rx_text_message) {
+        LOG_INFO("Hide Text Message");
+        devicestate.has_rx_text_message = false;
+        memset(&devicestate.rx_text_message, 0, sizeof(devicestate.rx_text_message));
+    } else if (currentFrame == framesetInfo.positions.waypoint && devicestate.has_rx_waypoint) {
+        LOG_DEBUG("Hide Waypoint");
+        devicestate.has_rx_waypoint = false;
+        hiddenFrames.waypoint = true;
+        dismissed = true;
+    } else if (currentFrame == framesetInfo.positions.wifi) {
+        LOG_DEBUG("Hide WiFi Screen");
+        hiddenFrames.wifi = true;
+        dismissed = true;
+    } else if (currentFrame == framesetInfo.positions.lora) {
+        LOG_INFO("Hide LoRa");
+        hiddenFrames.lora = true;
+        dismissed = true;
+    }
 
-            if (dismissed) {
-                setFrames(FOCUS_DEFAULT); // You could also use FOCUS_PRESERVE
-            }
-        }
+    if (dismissed) {
+        setFrames(FOCUS_DEFAULT); // You could also use FOCUS_PRESERVE
+    }
+}
 
-        void Screen::handleStartFirmwareUpdateScreen()
-        {
-            LOG_DEBUG("Show firmware screen");
-            showingNormalScreen = false;
-            EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // E-Ink: Explicitly use fast-refresh for next frame
+void Screen::handleStartFirmwareUpdateScreen()
+{
+    LOG_DEBUG("Show firmware screen");
+    showingNormalScreen = false;
+    EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // E-Ink: Explicitly use fast-refresh for next frame
 
-            static FrameCallback frames[] = {graphics::NotificationRenderer::drawFrameFirmware};
-            setFrameImmediateDraw(frames);
-        }
+    static FrameCallback frames[] = {graphics::NotificationRenderer::drawFrameFirmware};
+    setFrameImmediateDraw(frames);
+}
 
-        void Screen::blink()
-        {
-            setFastFramerate();
-            uint8_t count = 10;
-            dispdev->setBrightness(254);
-            while (count > 0) {
-                dispdev->fillRect(0, 0, dispdev->getWidth(), dispdev->getHeight());
-                dispdev->display();
-                delay(50);
-                dispdev->clear();
-                dispdev->display();
-                delay(50);
-                count = count - 1;
-            }
-            // The dispdev->setBrightness does not work for t-deck display, it seems to run the setBrightness function in
-            // OLEDDisplay.
-            dispdev->setBrightness(brightness);
-        }
+void Screen::blink()
+{
+    setFastFramerate();
+    uint8_t count = 10;
+    dispdev->setBrightness(254);
+    while (count > 0) {
+        dispdev->fillRect(0, 0, dispdev->getWidth(), dispdev->getHeight());
+        dispdev->display();
+        delay(50);
+        dispdev->clear();
+        dispdev->display();
+        delay(50);
+        count = count - 1;
+    }
+    // The dispdev->setBrightness does not work for t-deck display, it seems to run the setBrightness function in
+    // OLEDDisplay.
+    dispdev->setBrightness(brightness);
+}
 
-        void Screen::increaseBrightness()
-        {
-            brightness = ((brightness + 62) > 254) ? brightness : (brightness + 62);
+void Screen::increaseBrightness()
+{
+    brightness = ((brightness + 62) > 254) ? brightness : (brightness + 62);
 
 #if defined(ST7789_CS)
-            // run the setDisplayBrightness function. This works on t-decks
-            static_cast(dispdev)->setDisplayBrightness(brightness);
+    // run the setDisplayBrightness function. This works on t-decks
+    static_cast(dispdev)->setDisplayBrightness(brightness);
 #endif
 
-            /* TO DO: add little popup in center of screen saying what brightness level it is set to*/
-        }
+    /* TO DO: add little popup in center of screen saying what brightness level it is set to*/
+}
 
-        void Screen::decreaseBrightness()
-        {
-            brightness = (brightness < 70) ? brightness : (brightness - 62);
+void Screen::decreaseBrightness()
+{
+    brightness = (brightness < 70) ? brightness : (brightness - 62);
 
 #if defined(ST7789_CS)
-            static_cast(dispdev)->setDisplayBrightness(brightness);
+    static_cast(dispdev)->setDisplayBrightness(brightness);
 #endif
 
-            /* TO DO: add little popup in center of screen saying what brightness level it is set to*/
-        }
+    /* TO DO: add little popup in center of screen saying what brightness level it is set to*/
+}
 
-        void Screen::setFunctionSymbol(std::string sym)
-        {
-            if (std::find(functionSymbol.begin(), functionSymbol.end(), sym) == functionSymbol.end()) {
-                functionSymbol.push_back(sym);
-                functionSymbolString = "";
-                for (auto symbol : functionSymbol) {
-                    functionSymbolString = symbol + " " + functionSymbolString;
-                }
-                setFastFramerate();
-            }
+void Screen::setFunctionSymbol(std::string sym)
+{
+    if (std::find(functionSymbol.begin(), functionSymbol.end(), sym) == functionSymbol.end()) {
+        functionSymbol.push_back(sym);
+        functionSymbolString = "";
+        for (auto symbol : functionSymbol) {
+            functionSymbolString = symbol + " " + functionSymbolString;
         }
+        setFastFramerate();
+    }
+}
 
-        void Screen::removeFunctionSymbol(std::string sym)
-        {
-            functionSymbol.erase(std::remove(functionSymbol.begin(), functionSymbol.end(), sym), functionSymbol.end());
-            functionSymbolString = "";
-            for (auto symbol : functionSymbol) {
-                functionSymbolString = symbol + " " + functionSymbolString;
-            }
-            setFastFramerate();
-        }
+void Screen::removeFunctionSymbol(std::string sym)
+{
+    functionSymbol.erase(std::remove(functionSymbol.begin(), functionSymbol.end(), sym), functionSymbol.end());
+    functionSymbolString = "";
+    for (auto symbol : functionSymbol) {
+        functionSymbolString = symbol + " " + functionSymbolString;
+    }
+    setFastFramerate();
+}
 
-        void Screen::handleOnPress()
-        {
-            // If screen was off, just wake it, otherwise advance to next frame
-            // If we are in a transition, the press must have bounced, drop it.
-            if (ui->getUiState()->frameState == FIXED) {
-                ui->nextFrame();
-                lastScreenTransition = millis();
-                setFastFramerate();
-            }
-        }
+void Screen::handleOnPress()
+{
+    // If screen was off, just wake it, otherwise advance to next frame
+    // If we are in a transition, the press must have bounced, drop it.
+    if (ui->getUiState()->frameState == FIXED) {
+        ui->nextFrame();
+        lastScreenTransition = millis();
+        setFastFramerate();
+    }
+}
 
-        void Screen::handleShowPrevFrame()
-        {
-            // If screen was off, just wake it, otherwise go back to previous frame
-            // If we are in a transition, the press must have bounced, drop it.
-            if (ui->getUiState()->frameState == FIXED) {
-                ui->previousFrame();
-                lastScreenTransition = millis();
-                setFastFramerate();
-            }
-        }
+void Screen::handleShowPrevFrame()
+{
+    // If screen was off, just wake it, otherwise go back to previous frame
+    // If we are in a transition, the press must have bounced, drop it.
+    if (ui->getUiState()->frameState == FIXED) {
+        ui->previousFrame();
+        lastScreenTransition = millis();
+        setFastFramerate();
+    }
+}
 
-        void Screen::handleShowNextFrame()
-        {
-            // If screen was off, just wake it, otherwise advance to next frame
-            // If we are in a transition, the press must have bounced, drop it.
-            if (ui->getUiState()->frameState == FIXED) {
-                ui->nextFrame();
-                lastScreenTransition = millis();
-                setFastFramerate();
-            }
-        }
+void Screen::handleShowNextFrame()
+{
+    // If screen was off, just wake it, otherwise advance to next frame
+    // If we are in a transition, the press must have bounced, drop it.
+    if (ui->getUiState()->frameState == FIXED) {
+        ui->nextFrame();
+        lastScreenTransition = millis();
+        setFastFramerate();
+    }
+}
 
 #ifndef SCREEN_TRANSITION_FRAMERATE
 #define SCREEN_TRANSITION_FRAMERATE 30 // fps
 #endif
 
-        void Screen::setFastFramerate()
-        {
+void Screen::setFastFramerate()
+{
 #if defined(M5STACK_UNITC6L)
-            dispdev->clear();
-            dispdev->display();
+    dispdev->clear();
+    dispdev->display();
 #endif
-            // We are about to start a transition so speed up fps
-            targetFramerate = SCREEN_TRANSITION_FRAMERATE;
+    // We are about to start a transition so speed up fps
+    targetFramerate = SCREEN_TRANSITION_FRAMERATE;
 
-            ui->setTargetFPS(targetFramerate);
-            setInterval(0); // redraw ASAP
-            runASAP = true;
+    ui->setTargetFPS(targetFramerate);
+    setInterval(0); // redraw ASAP
+    runASAP = true;
+}
+
+int Screen::handleStatusUpdate(const meshtastic::Status *arg)
+{
+    // LOG_DEBUG("Screen got status update %d", arg->getStatusType());
+    switch (arg->getStatusType()) {
+    case STATUS_TYPE_NODE:
+        if (showingNormalScreen && nodeStatus->getLastNumTotal() != nodeStatus->getNumTotal()) {
+            setFrames(FOCUS_PRESERVE); // Regen the list of screen frames (returning to same frame, if possible)
         }
+        nodeDB->updateGUI = false;
+        break;
+    }
 
-        int Screen::handleStatusUpdate(const meshtastic::Status *arg)
-        {
-            // LOG_DEBUG("Screen got status update %d", arg->getStatusType());
-            switch (arg->getStatusType()) {
-            case STATUS_TYPE_NODE:
-                if (showingNormalScreen && nodeStatus->getLastNumTotal() != nodeStatus->getNumTotal()) {
-                    setFrames(FOCUS_PRESERVE); // Regen the list of screen frames (returning to same frame, if possible)
+    return 0;
+}
+
+// Handles when message is received; will jump to text message frame.
+int Screen::handleTextMessage(const meshtastic_MeshPacket *packet)
+{
+    if (showingNormalScreen) {
+        if (packet->from == 0) {
+            // Outgoing message (likely sent from phone)
+            devicestate.has_rx_text_message = false;
+            memset(&devicestate.rx_text_message, 0, sizeof(devicestate.rx_text_message));
+            hiddenFrames.textMessage = true;
+            hasUnreadMessage = false; // Clear unread state when user replies
+
+            setFrames(FOCUS_PRESERVE); // Stay on same frame, silently update frame list
+        } else {
+            // Incoming message
+            devicestate.has_rx_text_message = true; // Needed to include the message frame
+            hasUnreadMessage = true;                // Enables mail icon in the header
+            setFrames(FOCUS_PRESERVE);              // Refresh frame list without switching view
+
+            // Only wake/force display if the configuration allows it
+            if (shouldWakeOnReceivedMessage()) {
+                setOn(true);    // Wake up the screen first
+                forceDisplay(); // Forces screen redraw
+            }
+            // === Prepare banner content ===
+            const meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(packet->from);
+            const char *longName = (node && node->has_user) ? node->user.long_name : nullptr;
+
+            const char *msgRaw = reinterpret_cast(packet->decoded.payload.bytes);
+
+            char banner[256];
+
+            // Check for bell character in message to determine alert type
+            bool isAlert = false;
+            for (size_t i = 0; i < packet->decoded.payload.size && i < 100; i++) {
+                if (msgRaw[i] == '\x07') {
+                    isAlert = true;
+                    break;
                 }
-                nodeDB->updateGUI = false;
-                break;
             }
 
-            return 0;
-        }
-
-        // Handles when message is received; will jump to text message frame.
-        int Screen::handleTextMessage(const meshtastic_MeshPacket *packet)
-        {
-            if (showingNormalScreen) {
-                if (packet->from == 0) {
-                    // Outgoing message (likely sent from phone)
-                    devicestate.has_rx_text_message = false;
-                    memset(&devicestate.rx_text_message, 0, sizeof(devicestate.rx_text_message));
-                    hiddenFrames.textMessage = true;
-                    hasUnreadMessage = false; // Clear unread state when user replies
-
-                    setFrames(FOCUS_PRESERVE); // Stay on same frame, silently update frame list
+            if (isAlert) {
+                if (longName && longName[0]) {
+                    snprintf(banner, sizeof(banner), "Alert Received from\n%s", longName);
                 } else {
-                    // Incoming message
-                    devicestate.has_rx_text_message = true; // Needed to include the message frame
-                    hasUnreadMessage = true;                // Enables mail icon in the header
-                    setFrames(FOCUS_PRESERVE);              // Refresh frame list without switching view
-
-                    // Only wake/force display if the configuration allows it
-                    if (shouldWakeOnReceivedMessage()) {
-                        setOn(true);    // Wake up the screen first
-                        forceDisplay(); // Forces screen redraw
-                    }
-                    // === Prepare banner content ===
-                    const meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(packet->from);
-                    const char *longName = (node && node->has_user) ? node->user.long_name : nullptr;
-
-                    const char *msgRaw = reinterpret_cast(packet->decoded.payload.bytes);
-
-                    char banner[256];
-
-                    // Check for bell character in message to determine alert type
-                    bool isAlert = false;
-                    for (size_t i = 0; i < packet->decoded.payload.size && i < 100; i++) {
-                        if (msgRaw[i] == '\x07') {
-                            isAlert = true;
-                            break;
-                        }
-                    }
-
-                    if (isAlert) {
-                        if (longName && longName[0]) {
-                            snprintf(banner, sizeof(banner), "Alert Received from\n%s", longName);
-                        } else {
-                            strcpy(banner, "Alert Received");
-                        }
-                    } else {
-                        if (longName && longName[0]) {
+                    strcpy(banner, "Alert Received");
+                }
+            } else {
+                if (longName && longName[0]) {
 #if defined(M5STACK_UNITC6L)
-                            strcpy(banner, "New Message");
+                    strcpy(banner, "New Message");
 #else
                     snprintf(banner, sizeof(banner), "New Message from\n%s", longName);
 #endif
 
-                        } else {
-                            strcpy(banner, "New Message");
-                        }
-                    }
+                } else {
+                    strcpy(banner, "New Message");
+                }
+            }
 #if defined(M5STACK_UNITC6L)
-                    screen->setOn(true);
-                    screen->showSimpleBanner(banner, 1500);
-                    playLongBeep();
+            screen->setOn(true);
+            screen->showSimpleBanner(banner, 1500);
+            playLongBeep();
 #else
             screen->showSimpleBanner(banner, 3000);
 #endif
-                }
-            }
-
-            return 0;
         }
+    }
 
-        // Triggered by MeshModules
-        int Screen::handleUIFrameEvent(const UIFrameEvent *event)
-        {
-            // Block UI frame events when virtual keyboard is active
-            if (NotificationRenderer::current_notification_type == notificationTypeEnum::text_input) {
-                return 0;
-            }
+    return 0;
+}
 
-            if (showingNormalScreen) {
-                // Regenerate the frameset, potentially honoring a module's internal requestFocus() call
-                if (event->action == UIFrameEvent::Action::REGENERATE_FRAMESET)
-                    setFrames(FOCUS_MODULE);
+// Triggered by MeshModules
+int Screen::handleUIFrameEvent(const UIFrameEvent *event)
+{
+    // Block UI frame events when virtual keyboard is active
+    if (NotificationRenderer::current_notification_type == notificationTypeEnum::text_input) {
+        return 0;
+    }
 
-                // Regenerate the frameset, while Attempt to maintain focus on the current frame
-                else if (event->action == UIFrameEvent::Action::REGENERATE_FRAMESET_BACKGROUND)
-                    setFrames(FOCUS_PRESERVE);
+    if (showingNormalScreen) {
+        // Regenerate the frameset, potentially honoring a module's internal requestFocus() call
+        if (event->action == UIFrameEvent::Action::REGENERATE_FRAMESET)
+            setFrames(FOCUS_MODULE);
 
-                // Don't regenerate the frameset, just re-draw whatever is on screen ASAP
-                else if (event->action == UIFrameEvent::Action::REDRAW_ONLY)
-                    setFastFramerate();
-            }
+        // Regenerate the frameset, while Attempt to maintain focus on the current frame
+        else if (event->action == UIFrameEvent::Action::REGENERATE_FRAMESET_BACKGROUND)
+            setFrames(FOCUS_PRESERVE);
 
-            return 0;
-        }
+        // Don't regenerate the frameset, just re-draw whatever is on screen ASAP
+        else if (event->action == UIFrameEvent::Action::REDRAW_ONLY)
+            setFastFramerate();
+    }
 
-        int Screen::handleInputEvent(const InputEvent *event)
-        {
-            if (!screenOn)
-                return 0;
+    return 0;
+}
 
-            // Handle text input notifications specially - pass input to virtual keyboard
-            if (NotificationRenderer::current_notification_type == notificationTypeEnum::text_input) {
-                NotificationRenderer::inEvent = *event;
-                static OverlayCallback overlays[] = {graphics::UIRenderer::drawNavigationBar,
-                                                     NotificationRenderer::drawBannercallback};
-                ui->setOverlays(overlays, sizeof(overlays) / sizeof(overlays[0]));
-                setFastFramerate(); // Draw ASAP
-                ui->update();
-                return 0;
-            }
+int Screen::handleInputEvent(const InputEvent *event)
+{
+    if (!screenOn)
+        return 0;
+
+    // Handle text input notifications specially - pass input to virtual keyboard
+    if (NotificationRenderer::current_notification_type == notificationTypeEnum::text_input) {
+        NotificationRenderer::inEvent = *event;
+        static OverlayCallback overlays[] = {graphics::UIRenderer::drawNavigationBar, NotificationRenderer::drawBannercallback};
+        ui->setOverlays(overlays, sizeof(overlays) / sizeof(overlays[0]));
+        setFastFramerate(); // Draw ASAP
+        ui->update();
+        return 0;
+    }
 
 #ifdef USE_EINK // the screen is the last input handler, so if an event makes it here, we can assume it will prompt a screen draw.
-            EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // Use fast-refresh for next frame, no skip please
-            EINK_ADD_FRAMEFLAG(dispdev, BLOCKING);    // Edge case: if this frame is promoted to COSMETIC, wait for update
-            handleSetOn(true);  // Ensure power-on to receive deep-sleep screensaver (PowerFSM should handle?)
-            setFastFramerate(); // Draw ASAP
+    EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // Use fast-refresh for next frame, no skip please
+    EINK_ADD_FRAMEFLAG(dispdev, BLOCKING);    // Edge case: if this frame is promoted to COSMETIC, wait for update
+    handleSetOn(true);                        // Ensure power-on to receive deep-sleep screensaver (PowerFSM should handle?)
+    setFastFramerate();                       // Draw ASAP
 #endif
-            if (NotificationRenderer::isOverlayBannerShowing()) {
-                NotificationRenderer::inEvent = *event;
-                static OverlayCallback overlays[] = {graphics::UIRenderer::drawNavigationBar,
-                                                     NotificationRenderer::drawBannercallback};
-                ui->setOverlays(overlays, sizeof(overlays) / sizeof(overlays[0]));
-                setFastFramerate(); // Draw ASAP
-                ui->update();
+    if (NotificationRenderer::isOverlayBannerShowing()) {
+        NotificationRenderer::inEvent = *event;
+        static OverlayCallback overlays[] = {graphics::UIRenderer::drawNavigationBar, NotificationRenderer::drawBannercallback};
+        ui->setOverlays(overlays, sizeof(overlays) / sizeof(overlays[0]));
+        setFastFramerate(); // Draw ASAP
+        ui->update();
 
-                menuHandler::handleMenuSwitch(dispdev);
-                return 0;
-            }
+        menuHandler::handleMenuSwitch(dispdev);
+        return 0;
+    }
 
-            // Use left or right input from a keyboard to move between frames,
-            // so long as a mesh module isn't using these events for some other purpose
-            if (showingNormalScreen) {
+    // Use left or right input from a keyboard to move between frames,
+    // so long as a mesh module isn't using these events for some other purpose
+    if (showingNormalScreen) {
 
-                // Ask any MeshModules if they're handling keyboard input right now
-                bool inputIntercepted = false;
-                for (MeshModule *module : moduleFrames) {
-                    if (module && module->interceptingKeyboardInput())
-                        inputIntercepted = true;
-                }
+        // Ask any MeshModules if they're handling keyboard input right now
+        bool inputIntercepted = false;
+        for (MeshModule *module : moduleFrames) {
+            if (module && module->interceptingKeyboardInput())
+                inputIntercepted = true;
+        }
 
-                // If no modules are using the input, move between frames
-                if (!inputIntercepted) {
-                    if (event->inputEvent == INPUT_BROKER_LEFT || event->inputEvent == INPUT_BROKER_ALT_PRESS) {
-                        showPrevFrame();
-                    } else if (event->inputEvent == INPUT_BROKER_RIGHT || event->inputEvent == INPUT_BROKER_USER_PRESS) {
-                        showNextFrame();
-                    } else if (event->inputEvent == INPUT_BROKER_SELECT) {
-                        if (this->ui->getUiState()->currentFrame == framesetInfo.positions.home) {
-                            menuHandler::homeBaseMenu();
-                        } else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.system) {
-                            menuHandler::systemBaseMenu();
+        // If no modules are using the input, move between frames
+        if (!inputIntercepted) {
+            if (event->inputEvent == INPUT_BROKER_LEFT || event->inputEvent == INPUT_BROKER_ALT_PRESS) {
+                showPrevFrame();
+            } else if (event->inputEvent == INPUT_BROKER_RIGHT || event->inputEvent == INPUT_BROKER_USER_PRESS) {
+                showNextFrame();
+            } else if (event->inputEvent == INPUT_BROKER_SELECT) {
+                if (this->ui->getUiState()->currentFrame == framesetInfo.positions.home) {
+                    menuHandler::homeBaseMenu();
+                } else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.system) {
+                    menuHandler::systemBaseMenu();
 #if HAS_GPS
-                        } else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.gps && gps) {
-                            menuHandler::positionBaseMenu();
+                } else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.gps && gps) {
+                    menuHandler::positionBaseMenu();
 #endif
-                        } else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.clock) {
-                            menuHandler::clockMenu();
-                        } else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.lora) {
-                            menuHandler::loraMenu();
-                        } else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.textMessage) {
-                            if (devicestate.rx_text_message.from) {
-                                menuHandler::messageResponseMenu();
-                            } else {
+                } else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.clock) {
+                    menuHandler::clockMenu();
+                } else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.lora) {
+                    menuHandler::loraMenu();
+                } else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.textMessage) {
+                    if (devicestate.rx_text_message.from) {
+                        menuHandler::messageResponseMenu();
+                    } else {
 #if defined(M5STACK_UNITC6L)
-                                menuHandler::textMessageMenu();
+                        menuHandler::textMessageMenu();
 #else
                         menuHandler::textMessageBaseMenu();
 #endif
-                            }
-                        } else if (framesetInfo.positions.firstFavorite != 255 &&
-                                   this->ui->getUiState()->currentFrame >= framesetInfo.positions.firstFavorite &&
-                                   this->ui->getUiState()->currentFrame <= framesetInfo.positions.lastFavorite) {
-                            menuHandler::favoriteBaseMenu();
-                        } else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.nodelist ||
-                                   this->ui->getUiState()->currentFrame == framesetInfo.positions.nodelist_lastheard ||
-                                   this->ui->getUiState()->currentFrame == framesetInfo.positions.nodelist_hopsignal ||
-                                   this->ui->getUiState()->currentFrame == framesetInfo.positions.nodelist_distance ||
-                                   this->ui->getUiState()->currentFrame == framesetInfo.positions.nodelist_hopsignal ||
-                                   this->ui->getUiState()->currentFrame == framesetInfo.positions.nodelist_bearings) {
-                            menuHandler::nodeListMenu();
-                        } else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.wifi) {
-                            menuHandler::wifiBaseMenu();
-                        }
-                    } else if (event->inputEvent == INPUT_BROKER_BACK) {
-                        showPrevFrame();
-                    } else if (event->inputEvent == INPUT_BROKER_CANCEL) {
-                        setOn(false);
                     }
+                } else if (framesetInfo.positions.firstFavorite != 255 &&
+                           this->ui->getUiState()->currentFrame >= framesetInfo.positions.firstFavorite &&
+                           this->ui->getUiState()->currentFrame <= framesetInfo.positions.lastFavorite) {
+                    menuHandler::favoriteBaseMenu();
+                } else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.nodelist ||
+                           this->ui->getUiState()->currentFrame == framesetInfo.positions.nodelist_lastheard ||
+                           this->ui->getUiState()->currentFrame == framesetInfo.positions.nodelist_hopsignal ||
+                           this->ui->getUiState()->currentFrame == framesetInfo.positions.nodelist_distance ||
+                           this->ui->getUiState()->currentFrame == framesetInfo.positions.nodelist_hopsignal ||
+                           this->ui->getUiState()->currentFrame == framesetInfo.positions.nodelist_bearings) {
+                    menuHandler::nodeListMenu();
+                } else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.wifi) {
+                    menuHandler::wifiBaseMenu();
                 }
+            } else if (event->inputEvent == INPUT_BROKER_BACK) {
+                showPrevFrame();
+            } else if (event->inputEvent == INPUT_BROKER_CANCEL) {
+                setOn(false);
             }
-
-            return 0;
         }
+    }
 
-        int Screen::handleAdminMessage(AdminModule_ObserverData * arg)
-        {
-            switch (arg->request->which_payload_variant) {
-            // Node removed manually (i.e. via app)
-            case meshtastic_AdminMessage_remove_by_nodenum_tag:
-                setFrames(FOCUS_PRESERVE);
-                *arg->result = AdminMessageHandleResult::HANDLED;
-                break;
+    return 0;
+}
 
-            // Default no-op, in case the admin message observable gets used by other classes in future
-            default:
-                break;
-            }
-            return 0;
-        }
+int Screen::handleAdminMessage(AdminModule_ObserverData *arg)
+{
+    switch (arg->request->which_payload_variant) {
+    // Node removed manually (i.e. via app)
+    case meshtastic_AdminMessage_remove_by_nodenum_tag:
+        setFrames(FOCUS_PRESERVE);
+        *arg->result = AdminMessageHandleResult::HANDLED;
+        break;
 
-        bool Screen::isOverlayBannerShowing()
-        {
-            return NotificationRenderer::isOverlayBannerShowing();
-        }
+    // Default no-op, in case the admin message observable gets used by other classes in future
+    default:
+        break;
+    }
+    return 0;
+}
 
-    } // namespace graphics
+bool Screen::isOverlayBannerShowing()
+{
+    return NotificationRenderer::isOverlayBannerShowing();
+}
+
+} // namespace graphics
 
 #else
 graphics::Screen::Screen(ScanI2C::DeviceAddress, meshtastic_Config_DisplayConfig_OledType, OLEDDISPLAY_GEOMETRY) {}
 #endif // HAS_SCREEN
 
-    bool shouldWakeOnReceivedMessage()
-    {
-        /*
-        The goal here is to determine when we do NOT wake up the screen on message received:
-        - Any ext. notifications are turned on
-        - If role is not CLIENT / CLIENT_MUTE / CLIENT_HIDDEN / CLIENT_BASE
-        - If the battery level is very low
-        */
-        if (moduleConfig.external_notification.enabled) {
-            return false;
-        }
-        if (!IS_ONE_OF(config.device.role, meshtastic_Config_DeviceConfig_Role_CLIENT,
-                       meshtastic_Config_DeviceConfig_Role_CLIENT_MUTE, meshtastic_Config_DeviceConfig_Role_CLIENT_HIDDEN,
-                       meshtastic_Config_DeviceConfig_Role_CLIENT_BASE)) {
-            return false;
-        }
-        if (powerStatus && powerStatus->getBatteryChargePercent() < 10) {
-            return false;
-        }
-        return true;
+bool shouldWakeOnReceivedMessage()
+{
+    /*
+    The goal here is to determine when we do NOT wake up the screen on message received:
+    - Any ext. notifications are turned on
+    - If role is not CLIENT / CLIENT_MUTE / CLIENT_HIDDEN / CLIENT_BASE
+    - If the battery level is very low
+    */
+    if (moduleConfig.external_notification.enabled) {
+        return false;
     }
+    if (!IS_ONE_OF(config.device.role, meshtastic_Config_DeviceConfig_Role_CLIENT,
+                   meshtastic_Config_DeviceConfig_Role_CLIENT_MUTE, meshtastic_Config_DeviceConfig_Role_CLIENT_HIDDEN,
+                   meshtastic_Config_DeviceConfig_Role_CLIENT_BASE)) {
+        return false;
+    }
+    if (powerStatus && powerStatus->getBatteryChargePercent() < 10) {
+        return false;
+    }
+    return true;
+}

From a76cc88dc2cae630eb7793eb7b14cb223f9ddd8d Mon Sep 17 00:00:00 2001
From: WillyJL 
Date: Sat, 20 Sep 2025 18:53:30 +0200
Subject: [PATCH 298/691] Rename RotaryEncoderImpl to TLoraPagerRotaryEncoder

---
 ...ryEncoderImpl.cpp => TLoraPagerRotaryEncoder.cpp} | 12 ++++++------
 ...RotaryEncoderImpl.h => TLoraPagerRotaryEncoder.h} |  8 ++++----
 src/modules/Modules.cpp                              | 10 +++++-----
 3 files changed, 15 insertions(+), 15 deletions(-)
 rename src/input/{RotaryEncoderImpl.cpp => TLoraPagerRotaryEncoder.cpp} (89%)
 rename src/input/{RotaryEncoderImpl.h => TLoraPagerRotaryEncoder.h} (73%)

diff --git a/src/input/RotaryEncoderImpl.cpp b/src/input/TLoraPagerRotaryEncoder.cpp
similarity index 89%
rename from src/input/RotaryEncoderImpl.cpp
rename to src/input/TLoraPagerRotaryEncoder.cpp
index 213dd4faa..1b99defee 100644
--- a/src/input/RotaryEncoderImpl.cpp
+++ b/src/input/TLoraPagerRotaryEncoder.cpp
@@ -1,19 +1,19 @@
 #ifdef T_LORA_PAGER
 
-#include "RotaryEncoderImpl.h"
+#include "TLoraPagerRotaryEncoder.h"
 #include "InputBroker.h"
 #include "RotaryEncoder.h"
 
 #define ORIGIN_NAME "RotaryEncoder"
 
-RotaryEncoderImpl *rotaryEncoderImpl;
+TLoraPagerRotaryEncoder *tLoraPagerRotaryEncoder;
 
-RotaryEncoderImpl::RotaryEncoderImpl()
+TLoraPagerRotaryEncoder::TLoraPagerRotaryEncoder()
 {
     rotary = nullptr;
 }
 
-bool RotaryEncoderImpl::init()
+bool TLoraPagerRotaryEncoder::init()
 {
     if (!moduleConfig.canned_message.updown1_enabled || moduleConfig.canned_message.inputbroker_pin_a == 0 ||
         moduleConfig.canned_message.inputbroker_pin_b == 0) {
@@ -41,7 +41,7 @@ bool RotaryEncoderImpl::init()
     return true;
 }
 
-void RotaryEncoderImpl::pollOnce()
+void TLoraPagerRotaryEncoder::pollOnce()
 {
     InputEvent e{ORIGIN_NAME, INPUT_BROKER_NONE, 0, 0, 0};
 
@@ -71,6 +71,6 @@ void RotaryEncoderImpl::pollOnce()
     }
 }
 
-RotaryEncoderImpl *RotaryEncoderImpl::interruptInstance;
+TLoraPagerRotaryEncoder *TLoraPagerRotaryEncoder::interruptInstance;
 
 #endif
\ No newline at end of file
diff --git a/src/input/RotaryEncoderImpl.h b/src/input/TLoraPagerRotaryEncoder.h
similarity index 73%
rename from src/input/RotaryEncoderImpl.h
rename to src/input/TLoraPagerRotaryEncoder.h
index af70d1bf4..d5e2edf4e 100644
--- a/src/input/RotaryEncoderImpl.h
+++ b/src/input/TLoraPagerRotaryEncoder.h
@@ -10,15 +10,15 @@
 
 class RotaryEncoder;
 
-class RotaryEncoderImpl : public InputPollable
+class TLoraPagerRotaryEncoder : public InputPollable
 {
   public:
-    RotaryEncoderImpl();
+    TLoraPagerRotaryEncoder();
     bool init(void);
     virtual void pollOnce() override;
 
   protected:
-    static RotaryEncoderImpl *interruptInstance;
+    static TLoraPagerRotaryEncoder *interruptInstance;
 
     input_broker_event eventCw = INPUT_BROKER_NONE;
     input_broker_event eventCcw = INPUT_BROKER_NONE;
@@ -27,6 +27,6 @@ class RotaryEncoderImpl : public InputPollable
     RotaryEncoder *rotary;
 };
 
-extern RotaryEncoderImpl *rotaryEncoderImpl;
+extern TLoraPagerRotaryEncoder *tLoraPagerRotaryEncoder;
 
 #endif
diff --git a/src/modules/Modules.cpp b/src/modules/Modules.cpp
index abafce070..35e509b83 100644
--- a/src/modules/Modules.cpp
+++ b/src/modules/Modules.cpp
@@ -4,7 +4,7 @@
 #include "input/ExpressLRSFiveWay.h"
 #include "input/InputBroker.h"
 #ifdef T_LORA_PAGER
-#include "input/RotaryEncoderImpl.h"
+#include "input/TLoraPagerRotaryEncoder.h"
 #endif
 #include "input/RotaryEncoderInterruptImpl1.h"
 #include "input/SerialKeyboardImpl.h"
@@ -185,10 +185,10 @@ void setupModules()
             }
 #ifdef T_LORA_PAGER
             // use a special FSM based rotary encoder version for T-LoRa Pager
-            rotaryEncoderImpl = new RotaryEncoderImpl();
-            if (!rotaryEncoderImpl->init()) {
-                delete rotaryEncoderImpl;
-                rotaryEncoderImpl = nullptr;
+            tLoraPagerRotaryEncoder = new TLoraPagerRotaryEncoder();
+            if (!tLoraPagerRotaryEncoder->init()) {
+                delete tLoraPagerRotaryEncoder;
+                tLoraPagerRotaryEncoder = nullptr;
             }
 #else
             upDownInterruptImpl1 = new UpDownInterruptImpl1();

From 6a3b2ceafe020e384e45463c99156a433bdc1686 Mon Sep 17 00:00:00 2001
From: Markus <974709+Links2004@users.noreply.github.com>
Date: Sat, 20 Sep 2025 19:15:41 +0200
Subject: [PATCH 299/691] move HTTP contentTypes to Flash - saves 768 Bytes of
 RAM (#8055)

---
 src/mesh/http/ContentHandler.cpp | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/src/mesh/http/ContentHandler.cpp b/src/mesh/http/ContentHandler.cpp
index fb66dae7c..f87c6e3b0 100644
--- a/src/mesh/http/ContentHandler.cpp
+++ b/src/mesh/http/ContentHandler.cpp
@@ -55,12 +55,12 @@ HTTPClient httpClient;
 
 // We need to specify some content-type mapping, so the resources get delivered with the
 // right content type and are displayed correctly in the browser
-char contentTypes[][2][32] = {{".txt", "text/plain"},     {".html", "text/html"},
-                              {".js", "text/javascript"}, {".png", "image/png"},
-                              {".jpg", "image/jpg"},      {".gz", "application/gzip"},
-                              {".gif", "image/gif"},      {".json", "application/json"},
-                              {".css", "text/css"},       {".ico", "image/vnd.microsoft.icon"},
-                              {".svg", "image/svg+xml"},  {"", ""}};
+char const *contentTypes[][2] = {{".txt", "text/plain"},     {".html", "text/html"},
+                                 {".js", "text/javascript"}, {".png", "image/png"},
+                                 {".jpg", "image/jpg"},      {".gz", "application/gzip"},
+                                 {".gif", "image/gif"},      {".json", "application/json"},
+                                 {".css", "text/css"},       {".ico", "image/vnd.microsoft.icon"},
+                                 {".svg", "image/svg+xml"},  {"", ""}};
 
 // const char *certificate = NULL; // change this as needed, leave as is for no TLS check (yolo security)
 

From 9b6cf53730e7d04da4689c82f874bc752570a8f8 Mon Sep 17 00:00:00 2001
From: Markus <974709+Links2004@users.noreply.github.com>
Date: Sat, 20 Sep 2025 19:15:41 +0200
Subject: [PATCH 300/691] move HTTP contentTypes to Flash - saves 768 Bytes of
 RAM (#8055)

---
 src/mesh/http/ContentHandler.cpp | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/src/mesh/http/ContentHandler.cpp b/src/mesh/http/ContentHandler.cpp
index fb66dae7c..f87c6e3b0 100644
--- a/src/mesh/http/ContentHandler.cpp
+++ b/src/mesh/http/ContentHandler.cpp
@@ -55,12 +55,12 @@ HTTPClient httpClient;
 
 // We need to specify some content-type mapping, so the resources get delivered with the
 // right content type and are displayed correctly in the browser
-char contentTypes[][2][32] = {{".txt", "text/plain"},     {".html", "text/html"},
-                              {".js", "text/javascript"}, {".png", "image/png"},
-                              {".jpg", "image/jpg"},      {".gz", "application/gzip"},
-                              {".gif", "image/gif"},      {".json", "application/json"},
-                              {".css", "text/css"},       {".ico", "image/vnd.microsoft.icon"},
-                              {".svg", "image/svg+xml"},  {"", ""}};
+char const *contentTypes[][2] = {{".txt", "text/plain"},     {".html", "text/html"},
+                                 {".js", "text/javascript"}, {".png", "image/png"},
+                                 {".jpg", "image/jpg"},      {".gz", "application/gzip"},
+                                 {".gif", "image/gif"},      {".json", "application/json"},
+                                 {".css", "text/css"},       {".ico", "image/vnd.microsoft.icon"},
+                                 {".svg", "image/svg+xml"},  {"", ""}};
 
 // const char *certificate = NULL; // change this as needed, leave as is for no TLS check (yolo security)
 

From 52527e281dbd1b15064aabf8383bc254fc8a88da Mon Sep 17 00:00:00 2001
From: GUVWAF <78759985+GUVWAF@users.noreply.github.com>
Date: Sat, 20 Sep 2025 19:17:14 +0200
Subject: [PATCH 301/691] Use `lora.use_preset` config to get name (#8057)

---
 src/mesh/Channels.cpp | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/mesh/Channels.cpp b/src/mesh/Channels.cpp
index aec112a3e..4dcd94e3b 100644
--- a/src/mesh/Channels.cpp
+++ b/src/mesh/Channels.cpp
@@ -428,8 +428,8 @@ bool Channels::setDefaultPresetCryptoForHash(ChannelHash channelHash)
     // Iterate all known presets
     for (int preset = _meshtastic_Config_LoRaConfig_ModemPreset_MIN; preset <= _meshtastic_Config_LoRaConfig_ModemPreset_MAX;
          ++preset) {
-        const char *name =
-            DisplayFormatters::getModemPresetDisplayName((meshtastic_Config_LoRaConfig_ModemPreset)preset, false, false);
+        const char *name = DisplayFormatters::getModemPresetDisplayName((meshtastic_Config_LoRaConfig_ModemPreset)preset, false,
+                                                                        config.lora.use_preset);
         if (!name)
             continue;
         if (strcmp(name, "Invalid") == 0)

From 34c2191f6323b0518ceeec9e53933ca0de57a8c0 Mon Sep 17 00:00:00 2001
From: GUVWAF <78759985+GUVWAF@users.noreply.github.com>
Date: Sat, 20 Sep 2025 19:17:14 +0200
Subject: [PATCH 302/691] Use `lora.use_preset` config to get name (#8057)

---
 src/mesh/Channels.cpp | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/mesh/Channels.cpp b/src/mesh/Channels.cpp
index aec112a3e..4dcd94e3b 100644
--- a/src/mesh/Channels.cpp
+++ b/src/mesh/Channels.cpp
@@ -428,8 +428,8 @@ bool Channels::setDefaultPresetCryptoForHash(ChannelHash channelHash)
     // Iterate all known presets
     for (int preset = _meshtastic_Config_LoRaConfig_ModemPreset_MIN; preset <= _meshtastic_Config_LoRaConfig_ModemPreset_MAX;
          ++preset) {
-        const char *name =
-            DisplayFormatters::getModemPresetDisplayName((meshtastic_Config_LoRaConfig_ModemPreset)preset, false, false);
+        const char *name = DisplayFormatters::getModemPresetDisplayName((meshtastic_Config_LoRaConfig_ModemPreset)preset, false,
+                                                                        config.lora.use_preset);
         if (!name)
             continue;
         if (strcmp(name, "Invalid") == 0)

From 4100ba83a365be2a3edba673d0d2e6da0b192e5f Mon Sep 17 00:00:00 2001
From: WillyJL 
Date: Sun, 21 Sep 2025 03:23:16 +0200
Subject: [PATCH 303/691] Revert "Rename RotaryEncoderImpl to
 TLoraPagerRotaryEncoder"

This reverts commit a76cc88dc2cae630eb7793eb7b14cb223f9ddd8d.
---
 ...aPagerRotaryEncoder.cpp => RotaryEncoderImpl.cpp} | 12 ++++++------
 ...TLoraPagerRotaryEncoder.h => RotaryEncoderImpl.h} |  8 ++++----
 src/modules/Modules.cpp                              | 10 +++++-----
 3 files changed, 15 insertions(+), 15 deletions(-)
 rename src/input/{TLoraPagerRotaryEncoder.cpp => RotaryEncoderImpl.cpp} (89%)
 rename src/input/{TLoraPagerRotaryEncoder.h => RotaryEncoderImpl.h} (73%)

diff --git a/src/input/TLoraPagerRotaryEncoder.cpp b/src/input/RotaryEncoderImpl.cpp
similarity index 89%
rename from src/input/TLoraPagerRotaryEncoder.cpp
rename to src/input/RotaryEncoderImpl.cpp
index 1b99defee..213dd4faa 100644
--- a/src/input/TLoraPagerRotaryEncoder.cpp
+++ b/src/input/RotaryEncoderImpl.cpp
@@ -1,19 +1,19 @@
 #ifdef T_LORA_PAGER
 
-#include "TLoraPagerRotaryEncoder.h"
+#include "RotaryEncoderImpl.h"
 #include "InputBroker.h"
 #include "RotaryEncoder.h"
 
 #define ORIGIN_NAME "RotaryEncoder"
 
-TLoraPagerRotaryEncoder *tLoraPagerRotaryEncoder;
+RotaryEncoderImpl *rotaryEncoderImpl;
 
-TLoraPagerRotaryEncoder::TLoraPagerRotaryEncoder()
+RotaryEncoderImpl::RotaryEncoderImpl()
 {
     rotary = nullptr;
 }
 
-bool TLoraPagerRotaryEncoder::init()
+bool RotaryEncoderImpl::init()
 {
     if (!moduleConfig.canned_message.updown1_enabled || moduleConfig.canned_message.inputbroker_pin_a == 0 ||
         moduleConfig.canned_message.inputbroker_pin_b == 0) {
@@ -41,7 +41,7 @@ bool TLoraPagerRotaryEncoder::init()
     return true;
 }
 
-void TLoraPagerRotaryEncoder::pollOnce()
+void RotaryEncoderImpl::pollOnce()
 {
     InputEvent e{ORIGIN_NAME, INPUT_BROKER_NONE, 0, 0, 0};
 
@@ -71,6 +71,6 @@ void TLoraPagerRotaryEncoder::pollOnce()
     }
 }
 
-TLoraPagerRotaryEncoder *TLoraPagerRotaryEncoder::interruptInstance;
+RotaryEncoderImpl *RotaryEncoderImpl::interruptInstance;
 
 #endif
\ No newline at end of file
diff --git a/src/input/TLoraPagerRotaryEncoder.h b/src/input/RotaryEncoderImpl.h
similarity index 73%
rename from src/input/TLoraPagerRotaryEncoder.h
rename to src/input/RotaryEncoderImpl.h
index d5e2edf4e..af70d1bf4 100644
--- a/src/input/TLoraPagerRotaryEncoder.h
+++ b/src/input/RotaryEncoderImpl.h
@@ -10,15 +10,15 @@
 
 class RotaryEncoder;
 
-class TLoraPagerRotaryEncoder : public InputPollable
+class RotaryEncoderImpl : public InputPollable
 {
   public:
-    TLoraPagerRotaryEncoder();
+    RotaryEncoderImpl();
     bool init(void);
     virtual void pollOnce() override;
 
   protected:
-    static TLoraPagerRotaryEncoder *interruptInstance;
+    static RotaryEncoderImpl *interruptInstance;
 
     input_broker_event eventCw = INPUT_BROKER_NONE;
     input_broker_event eventCcw = INPUT_BROKER_NONE;
@@ -27,6 +27,6 @@ class TLoraPagerRotaryEncoder : public InputPollable
     RotaryEncoder *rotary;
 };
 
-extern TLoraPagerRotaryEncoder *tLoraPagerRotaryEncoder;
+extern RotaryEncoderImpl *rotaryEncoderImpl;
 
 #endif
diff --git a/src/modules/Modules.cpp b/src/modules/Modules.cpp
index 35e509b83..abafce070 100644
--- a/src/modules/Modules.cpp
+++ b/src/modules/Modules.cpp
@@ -4,7 +4,7 @@
 #include "input/ExpressLRSFiveWay.h"
 #include "input/InputBroker.h"
 #ifdef T_LORA_PAGER
-#include "input/TLoraPagerRotaryEncoder.h"
+#include "input/RotaryEncoderImpl.h"
 #endif
 #include "input/RotaryEncoderInterruptImpl1.h"
 #include "input/SerialKeyboardImpl.h"
@@ -185,10 +185,10 @@ void setupModules()
             }
 #ifdef T_LORA_PAGER
             // use a special FSM based rotary encoder version for T-LoRa Pager
-            tLoraPagerRotaryEncoder = new TLoraPagerRotaryEncoder();
-            if (!tLoraPagerRotaryEncoder->init()) {
-                delete tLoraPagerRotaryEncoder;
-                tLoraPagerRotaryEncoder = nullptr;
+            rotaryEncoderImpl = new RotaryEncoderImpl();
+            if (!rotaryEncoderImpl->init()) {
+                delete rotaryEncoderImpl;
+                rotaryEncoderImpl = nullptr;
             }
 #else
             upDownInterruptImpl1 = new UpDownInterruptImpl1();

From d558df8a3a57d93fa8d358cabf05884b296bb2af Mon Sep 17 00:00:00 2001
From: WillyJL 
Date: Sun, 21 Sep 2025 03:29:52 +0200
Subject: [PATCH 304/691] Revert unnecessary ifdefs

---
 src/input/RotaryEncoderImpl.h | 4 ----
 src/modules/Modules.cpp       | 2 --
 2 files changed, 6 deletions(-)

diff --git a/src/input/RotaryEncoderImpl.h b/src/input/RotaryEncoderImpl.h
index af70d1bf4..6f8e9fe5f 100644
--- a/src/input/RotaryEncoderImpl.h
+++ b/src/input/RotaryEncoderImpl.h
@@ -1,7 +1,5 @@
 #pragma once
 
-#ifdef T_LORA_PAGER
-
 // This is a version of RotaryEncoder which is based on a debounce inherent FSM table (see RotaryEncoder library)
 
 #include "InputBroker.h"
@@ -28,5 +26,3 @@ class RotaryEncoderImpl : public InputPollable
 };
 
 extern RotaryEncoderImpl *rotaryEncoderImpl;
-
-#endif
diff --git a/src/modules/Modules.cpp b/src/modules/Modules.cpp
index abafce070..757753d45 100644
--- a/src/modules/Modules.cpp
+++ b/src/modules/Modules.cpp
@@ -3,9 +3,7 @@
 #include "buzz/BuzzerFeedbackThread.h"
 #include "input/ExpressLRSFiveWay.h"
 #include "input/InputBroker.h"
-#ifdef T_LORA_PAGER
 #include "input/RotaryEncoderImpl.h"
-#endif
 #include "input/RotaryEncoderInterruptImpl1.h"
 #include "input/SerialKeyboardImpl.h"
 #include "input/UpDownInterruptImpl1.h"

From 040b3b8c7f9fb87e621fdcdd2cb7a931501fca37 Mon Sep 17 00:00:00 2001
From: Jason P 
Date: Sat, 20 Sep 2025 20:33:47 -0500
Subject: [PATCH 305/691] Resolve many warnings for BaseUI during builds
 (#8063)

* Resolve many warnings for BaseUI during builds

* Don't display "No GPS Lock" twice
---
 src/graphics/Screen.cpp          |  2 +-
 src/graphics/draw/UIRenderer.cpp | 18 ++++---
 src/graphics/images.h            |  6 +--
 src/graphics/img/icon_small.xbm  | 88 +-------------------------------
 4 files changed, 15 insertions(+), 99 deletions(-)

diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp
index a440ecab9..204fbd451 100644
--- a/src/graphics/Screen.cpp
+++ b/src/graphics/Screen.cpp
@@ -1037,7 +1037,7 @@ void Screen::setFrames(FrameFocus focus)
     if (!hiddenFrames.chirpy) {
         fsi.positions.chirpy = numframes;
         normalFrames[numframes++] = graphics::DebugRenderer::drawChirpy;
-        indicatorIcons.push_back(small_chirpy);
+        indicatorIcons.push_back(chirpy_small);
     }
 
 #if HAS_WIFI && !defined(ARCH_PORTDUINO)
diff --git a/src/graphics/draw/UIRenderer.cpp b/src/graphics/draw/UIRenderer.cpp
index 202a28835..988e7eb53 100644
--- a/src/graphics/draw/UIRenderer.cpp
+++ b/src/graphics/draw/UIRenderer.cpp
@@ -20,7 +20,9 @@
 
 // External variables
 extern graphics::Screen *screen;
+#if defined(M5STACK_UNITC6L)
 static uint32_t lastSwitchTime = 0;
+#endif
 namespace graphics
 {
 NodeNum UIRenderer::currentFavoriteNodeNum = 0;
@@ -126,8 +128,10 @@ void UIRenderer::drawGpsCoordinates(OLEDDisplay *display, int16_t x, int16_t y,
         strcpy(displayLine, "No GPS present");
         display->drawString(x, y, displayLine);
     } else if (!gps->getHasLock() && !config.position.fixed_position) {
-        strcpy(displayLine, "No GPS Lock");
-        display->drawString(x, y, displayLine);
+        if (strcmp(mode, "line1") == 0) {
+            strcpy(displayLine, "No GPS Lock");
+            display->drawString(x, y, displayLine);
+        }
     } else {
 
         geoCoord.updateCoords(int32_t(gps->getLatitude()), int32_t(gps->getLongitude()), int32_t(gps->getAltitude()));
@@ -285,9 +289,9 @@ void UIRenderer::drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *st
     meshtastic_NodeInfoLite *node = favoritedNodes[nodeIndex];
     if (!node || node->num == nodeDB->getNodeNum() || !node->is_favorite)
         return;
-    uint32_t now = millis();
     display->clear();
 #if defined(M5STACK_UNITC6L)
+    uint32_t now = millis();
     if (now - lastSwitchTime >= 10000) // 10000 ms = 10 秒
     {
         display->display();
@@ -732,7 +736,6 @@ void UIRenderer::drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *sta
     int textWidth = 0;
     int nameX = 0;
     int yOffset = (isHighResolution) ? 0 : 5;
-    const char *longName = nullptr;
     std::string longNameStr;
 
     meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
@@ -1277,14 +1280,13 @@ void UIRenderer::drawNavigationBar(OLEDDisplay *display, OLEDDisplayUiState *sta
     const int totalWidth = (pageEnd - pageStart) * iconSize + (pageEnd - pageStart - 1) * spacing;
     const int xStart = (SCREEN_WIDTH - totalWidth) / 2;
 
-    // Only show bar briefly after switching frames
-    static uint32_t navBarLastShown = 0;
-    static bool cosmeticRefreshDone = false;
-
     bool navBarVisible = millis() - lastFrameChangeTime <= ICON_DISPLAY_DURATION_MS;
     int y = navBarVisible ? (SCREEN_HEIGHT - iconSize - 1) : SCREEN_HEIGHT;
 
 #if defined(USE_EINK)
+    // Only show bar briefly after switching frames
+    static uint32_t navBarLastShown = 0;
+    static bool cosmeticRefreshDone = false;
     static bool navBarPrevVisible = false;
 
     if (navBarVisible && !navBarPrevVisible) {
diff --git a/src/graphics/images.h b/src/graphics/images.h
index 72dda7886..7b80fc5ab 100644
--- a/src/graphics/images.h
+++ b/src/graphics/images.h
@@ -292,7 +292,7 @@ const uint8_t analog_icon_clock[] PROGMEM = {0b11111111, 0b01000010, 0b00100100,
 #else
 #define chirpy_width 38
 #define chirpy_height 50
-static unsigned char chirpy[] = {
+const uint8_t 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,
@@ -308,7 +308,7 @@ static unsigned char chirpy[] = {
 
 #define chirpy_width_hirez 76
 #define chirpy_height_hirez 100
-static unsigned char chirpy_hirez[] = {
+const uint8_t 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,
@@ -360,7 +360,7 @@ static unsigned char chirpy_hirez[] = {
 
 #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};
+const uint8_t chirpy_small[] = {0x7f, 0x41, 0x55, 0x55, 0x55, 0x55, 0x41, 0x7f};
 
 #include "img/icon.xbm"
 #endif
diff --git a/src/graphics/img/icon_small.xbm b/src/graphics/img/icon_small.xbm
index 97884edad..e320a1fea 100644
--- a/src/graphics/img/icon_small.xbm
+++ b/src/graphics/img/icon_small.xbm
@@ -27,90 +27,4 @@ static uint8_t icon_bits[] = {
   0x00, 0x1c, 0x00, 0x00, 0x70, 0x00, 0x10, 0x00,
   0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00 };
-#endif
-
-// Chirpy image definitions for M5STACK_UNITC6L compatibility
-#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, 0xe0, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, 0x00, 0xfc,
-    0x03, 0xe0, 0x1f, 0xff, 0xff, 0xff, 0xf8, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03,
-    0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0,
-    0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f,
-    0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe,
-    0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff,
-    0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f,
-    0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0,
-    0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f,
-    0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00,
-    0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc,
-    0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03,
-    0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0,
-    0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f,
-    0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe,
-    0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff,
-    0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f,
-    0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0,
-    0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f,
-    0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00,
-    0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc,
-    0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03,
-    0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0,
-    0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x1f,
-    0xff, 0xff, 0xff, 0xf8, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0xff, 0xff,
-    0xff, 0xff, 0xff, 0x7f, 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, 0x86, 0x61, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x86, 0x61, 0x00,
-    0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x86, 0x61, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x80, 0x87, 0xe1, 0x01, 0x00,
-    0x00, 0xfc, 0x03, 0x00, 0x00, 0x80, 0x87, 0xe1, 0x01, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0xc0, 0x83, 0xc1, 0x03, 0x00, 0x00,
-    0xfc, 0x03, 0x00, 0x00, 0xc0, 0x83, 0xc1, 0x03, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0xe0, 0x01, 0x80, 0x07, 0x00, 0x00, 0xfc,
-    0x03, 0x00, 0x00, 0xe0, 0x01, 0x80, 0x07, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x0f, 0x00, 0x00, 0xfc, 0x03,
-    0x00, 0x00, 0xf0, 0x00, 0x00, 0x0f, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x78, 0x00, 0x00, 0x1e, 0x00, 0x00, 0xfc, 0x03, 0x00,
-    0x00, 0x78, 0x00, 0x00, 0x1e, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x3c, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00,
-    0x3c, 0x00, 0x00, 0x3c, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x78, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x1e,
-    0x00, 0x00, 0x78, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x0f, 0x00, 0x00, 0xf0, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x0f, 0x00,
-    0x00, 0xf0, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x80, 0x07, 0x00, 0x00, 0xe0, 0x01, 0x00, 0xfc, 0x03, 0x00, 0x80, 0x07, 0x00, 0x00,
-    0xe0, 0x01, 0x00, 0xfc, 0x03, 0x00, 0xc0, 0x03, 0x00, 0x00, 0xc0, 0x03, 0x00, 0xfc, 0x03, 0x00, 0xc0, 0x03, 0x00, 0x00, 0xc0,
-    0x03, 0x00, 0xfc, 0x03, 0x00, 0xe0, 0x01, 0x00, 0x00, 0x80, 0x07, 0x00, 0xfc, 0x03, 0x00, 0xe0, 0x01, 0x00, 0x00, 0x80, 0x07,
-    0x00, 0xfc, 0x03, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x00, 0xfc, 0x03, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x00,
-    0xfc, 0x03, 0x00, 0x78, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x00, 0xfc, 0x03, 0x00, 0x78, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x00, 0xfc,
-    0x03, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x00, 0xfc, 0x03, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x00, 0xfc, 0x03,
-    0x00, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x78, 0x00, 0xfc, 0x03, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x78, 0x00, 0xfc, 0x03, 0x00,
-    0x0f, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0xfc, 0x03, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0xfc, 0x03, 0x80, 0x07,
-    0x00, 0x00, 0x00, 0x00, 0xe0, 0x01, 0xfc, 0x03, 0x80, 0x07, 0x00, 0x00, 0x00, 0x00, 0xe0, 0x01, 0xfc, 0x03, 0xc0, 0x03, 0x00,
-    0x00, 0x00, 0x00, 0xc0, 0x03, 0xfc, 0x03, 0xc0, 0x03, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x03, 0xfc, 0x03, 0xe0, 0x01, 0x00, 0x00,
-    0x00, 0x00, 0x80, 0x07, 0xfc, 0x03, 0xe0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x80, 0x07, 0xfc, 0x03, 0xf0, 0x00, 0x00, 0x00, 0x00,
-    0x00, 0x00, 0x0f, 0xfc, 0x03, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 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};
\ No newline at end of file
+#endif
\ No newline at end of file

From 2010871e4ba1167ca0a16770245af99e837f25f3 Mon Sep 17 00:00:00 2001
From: Markus <974709+Links2004@users.noreply.github.com>
Date: Sun, 21 Sep 2025 13:22:29 +0200
Subject: [PATCH 306/691] Fix Rotary Encoder Button (#8001)

this fixes the Rotary Encoder Button, currenlty its not working at all.
Currently the action `ROTARY_ACTION_PRESSED` is only triggerd with a IRQ on RISING, which results in nothing since the function detects the "not longer" pressed button --> no action.

the `ROTARY_ACTION_PRESSED` implementation needs to be called on both edges (on press and release of the button)

changing the interupt setting to `CHANGE` fixes the problem.
---
 src/input/RotaryEncoderInterruptBase.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/input/RotaryEncoderInterruptBase.cpp b/src/input/RotaryEncoderInterruptBase.cpp
index 980057ce4..c315f23d9 100644
--- a/src/input/RotaryEncoderInterruptBase.cpp
+++ b/src/input/RotaryEncoderInterruptBase.cpp
@@ -27,7 +27,7 @@ void RotaryEncoderInterruptBase::init(
 
     if (!isRAK || pinPress != 0) {
         pinMode(pinPress, INPUT_PULLUP);
-        attachInterrupt(pinPress, onIntPress, RISING);
+        attachInterrupt(pinPress, onIntPress, CHANGE);
     }
     if (!isRAK || this->_pinA != 0) {
         pinMode(this->_pinA, INPUT_PULLUP);

From c42513d7c8a22106da4d1ab83ff8ab296d9bfb38 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Sun, 21 Sep 2025 06:25:32 -0500
Subject: [PATCH 307/691] Update RadioLib to v7.3.0 (#8065)

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 941e33beb..5da8c2e67 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -114,7 +114,7 @@ lib_deps =
 [radiolib_base]
 lib_deps =
 	# renovate: datasource=custom.pio depName=RadioLib packageName=jgromes/library/RadioLib
-	jgromes/RadioLib@7.2.1
+	jgromes/RadioLib@7.3.0
 
 [device-ui_base]
 lib_deps =

From 5701755608394c5f65c16967950e2e1b22a4f498 Mon Sep 17 00:00:00 2001
From: Tom <116762865+NomDeTom@users.noreply.github.com>
Date: Sun, 21 Sep 2025 12:27:39 +0100
Subject: [PATCH 308/691] Add another seeed_xiao_nrf52840_kit build environment
 for I2C pinout (#8036)

* Update platformio.ini

* Remove some more extraneous lines
---
 variants/nrf52840/seeed_xiao_nrf52840_kit/platformio.ini | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/variants/nrf52840/seeed_xiao_nrf52840_kit/platformio.ini b/variants/nrf52840/seeed_xiao_nrf52840_kit/platformio.ini
index 623eace71..4c68b40e8 100644
--- a/variants/nrf52840/seeed_xiao_nrf52840_kit/platformio.ini
+++ b/variants/nrf52840/seeed_xiao_nrf52840_kit/platformio.ini
@@ -16,3 +16,11 @@ lib_deps =
 debug_tool = jlink
 ; If not set we will default to uploading over serial (first it forces bootloader entry by talking 1200bps to cdcacm)
 ;upload_protocol = jlink
+
+; Seeed Xiao BLE but with GPS undefined, and therefore i2c active
+[env:seeed_xiao_nrf52840_kit_i2c]
+extends = env:seeed_xiao_nrf52840_kit
+board_level = extra
+build_flags = ${env:seeed_xiao_nrf52840_kit.build_flags}
+  -DSEEED_XIAO_NRF52840_KIT
+build_unflags = -DGPS_L76K

From 27b07cd1c52b2c166b547f876a4cf62041d25694 Mon Sep 17 00:00:00 2001
From: Markus <974709+Links2004@users.noreply.github.com>
Date: Sun, 21 Sep 2025 13:22:29 +0200
Subject: [PATCH 309/691] Fix Rotary Encoder Button (#8001)

this fixes the Rotary Encoder Button, currenlty its not working at all.
Currently the action `ROTARY_ACTION_PRESSED` is only triggerd with a IRQ on RISING, which results in nothing since the function detects the "not longer" pressed button --> no action.

the `ROTARY_ACTION_PRESSED` implementation needs to be called on both edges (on press and release of the button)

changing the interupt setting to `CHANGE` fixes the problem.
---
 src/input/RotaryEncoderInterruptBase.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/input/RotaryEncoderInterruptBase.cpp b/src/input/RotaryEncoderInterruptBase.cpp
index 598353f98..f31da8c21 100644
--- a/src/input/RotaryEncoderInterruptBase.cpp
+++ b/src/input/RotaryEncoderInterruptBase.cpp
@@ -25,7 +25,7 @@ void RotaryEncoderInterruptBase::init(
 
     if (!isRAK || pinPress != 0) {
         pinMode(pinPress, INPUT_PULLUP);
-        attachInterrupt(pinPress, onIntPress, RISING);
+        attachInterrupt(pinPress, onIntPress, CHANGE);
     }
     if (!isRAK || this->_pinA != 0) {
         pinMode(this->_pinA, INPUT_PULLUP);

From d1fd102952e3580d563be56f6b48d6862d398b9f Mon Sep 17 00:00:00 2001
From: Tom <116762865+NomDeTom@users.noreply.github.com>
Date: Sun, 21 Sep 2025 12:27:39 +0100
Subject: [PATCH 310/691] Add another seeed_xiao_nrf52840_kit build environment
 for I2C pinout (#8036)

* Update platformio.ini

* Remove some more extraneous lines
---
 variants/nrf52840/seeed_xiao_nrf52840_kit/platformio.ini | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/variants/nrf52840/seeed_xiao_nrf52840_kit/platformio.ini b/variants/nrf52840/seeed_xiao_nrf52840_kit/platformio.ini
index 623eace71..4c68b40e8 100644
--- a/variants/nrf52840/seeed_xiao_nrf52840_kit/platformio.ini
+++ b/variants/nrf52840/seeed_xiao_nrf52840_kit/platformio.ini
@@ -16,3 +16,11 @@ lib_deps =
 debug_tool = jlink
 ; If not set we will default to uploading over serial (first it forces bootloader entry by talking 1200bps to cdcacm)
 ;upload_protocol = jlink
+
+; Seeed Xiao BLE but with GPS undefined, and therefore i2c active
+[env:seeed_xiao_nrf52840_kit_i2c]
+extends = env:seeed_xiao_nrf52840_kit
+board_level = extra
+build_flags = ${env:seeed_xiao_nrf52840_kit.build_flags}
+  -DSEEED_XIAO_NRF52840_KIT
+build_unflags = -DGPS_L76K

From 11eb4a5b9011caabc76826cb85f28d947429d9c4 Mon Sep 17 00:00:00 2001
From: Quency-D <55523105+Quency-D@users.noreply.github.com>
Date: Sun, 21 Sep 2025 20:03:44 +0800
Subject: [PATCH 311/691] Add heltec_v4 board. (#7845)

* add heltec_v4 board.

* Update variants/esp32s3/heltec_v4/platformio.ini

Co-authored-by: Austin 

* Limit the maximum output power.

* Trunk fixes

Fixes formatting to match meshtastic trunk linter.

* Apply suggestion from @Copilot

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Apply suggestion from @Copilot

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Ben Meadors 
Co-authored-by: Austin 
Co-authored-by: Tom Fifield 
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---
 boards/heltec_v4.json                     | 43 ++++++++++++++
 src/mesh/SX126xInterface.cpp              | 36 +++++++++++-
 src/mesh/SX126xInterface.h                |  4 ++
 src/platform/esp32/architecture.h         |  2 +
 src/sleep.cpp                             |  6 ++
 variants/esp32s3/heltec_v4/pins_arduino.h | 70 +++++++++++++++++++++++
 variants/esp32s3/heltec_v4/platformio.ini | 10 ++++
 variants/esp32s3/heltec_v4/variant.h      | 58 +++++++++++++++++++
 8 files changed, 227 insertions(+), 2 deletions(-)
 create mode 100644 boards/heltec_v4.json
 create mode 100644 variants/esp32s3/heltec_v4/pins_arduino.h
 create mode 100644 variants/esp32s3/heltec_v4/platformio.ini
 create mode 100644 variants/esp32s3/heltec_v4/variant.h

diff --git a/boards/heltec_v4.json b/boards/heltec_v4.json
new file mode 100644
index 000000000..8eac3a9b2
--- /dev/null
+++ b/boards/heltec_v4.json
@@ -0,0 +1,43 @@
+{
+  "build": {
+    "arduino": {
+      "ldscript": "esp32s3_out.ld",
+      "partitions": "default_16MB.csv",
+      "memory_type": "qio_qspi"
+    },
+    "core": "esp32",
+    "extra_flags": [
+      "-DBOARD_HAS_PSRAM",
+      "-DARDUINO_USB_CDC_ON_BOOT=1",
+      "-DARDUINO_USB_MODE=0",
+      "-DARDUINO_RUNNING_CORE=1",
+      "-DARDUINO_EVENT_RUNNING_CORE=1"
+    ],
+    "f_cpu": "240000000L",
+    "f_flash": "80000000L",
+    "flash_mode": "qio",
+    "psram_type": "qspi",
+    "hwids": [["0x303A", "0x1001"]],
+    "mcu": "esp32s3",
+    "variant": "heltec_v4"
+  },
+  "connectivity": ["wifi", "bluetooth", "lora"],
+  "debug": {
+    "default_tool": "esp-builtin",
+    "onboard_tools": ["esp-builtin"],
+    "openocd_target": "esp32s3.cfg"
+  },
+  "frameworks": ["arduino", "espidf"],
+  "name": "heltec_wifi_lora_32 v4 (16 MB FLASH, 2 MB PSRAM)",
+  "upload": {
+    "flash_size": "16MB",
+    "maximum_ram_size": 2097152,
+    "maximum_size": 16777216,
+    "use_1200bps_touch": true,
+    "wait_for_upload_port": true,
+    "require_upload_port": true,
+    "speed": 921600
+  },
+  "url": "https://heltec.org/",
+  "vendor": "heltec"
+}
diff --git a/src/mesh/SX126xInterface.cpp b/src/mesh/SX126xInterface.cpp
index 49dc562d4..3fc2562b3 100644
--- a/src/mesh/SX126xInterface.cpp
+++ b/src/mesh/SX126xInterface.cpp
@@ -52,6 +52,16 @@ template  bool SX126xInterface::init()
     pinMode(SX126X_POWER_EN, OUTPUT);
 #endif
 
+#ifdef HELTEC_V4
+    pinMode(LORA_PA_POWER, OUTPUT);
+    digitalWrite(LORA_PA_POWER, HIGH);
+
+    pinMode(LORA_PA_EN, OUTPUT);
+    digitalWrite(LORA_PA_EN, LOW);
+    pinMode(LORA_PA_TX_EN, OUTPUT);
+    digitalWrite(LORA_PA_TX_EN, LOW);
+#endif
+
 #if ARCH_PORTDUINO
     tcxoVoltage = (float)portduino_config.dio3_tcxo_voltage / 1000;
     if (portduino_config.lora_sx126x_ant_sw_pin.pin != RADIOLIB_NC) {
@@ -63,7 +73,7 @@ template  bool SX126xInterface::init()
         LOG_DEBUG("SX126X_DIO3_TCXO_VOLTAGE not defined, not using DIO3 as TCXO reference voltage");
     else
         LOG_DEBUG("SX126X_DIO3_TCXO_VOLTAGE defined, using DIO3 as TCXO reference voltage at %f V", tcxoVoltage);
-
+    setTransmitEnable(false);
     // FIXME: May want to set depending on a definition, currently all SX126x variant files use the DC-DC regulator option
     bool useRegulatorLDO = false; // Seems to depend on the connection to pin 9/DCC_SW - if an inductor DCDC?
 
@@ -259,6 +269,7 @@ template  void SX126xInterface::addReceiveMetadata(meshtastic_Mes
  */
 template  void SX126xInterface::configHardwareForSend()
 {
+    setTransmitEnable(true);
     RadioLibInterface::configHardwareForSend();
 }
 
@@ -271,6 +282,7 @@ template  void SX126xInterface::startReceive()
     sleep();
 #else
 
+    setTransmitEnable(false);
     setStandby();
 
     // We use a 16 bit preamble so this should save some power by letting radio sit in standby mostly.
@@ -298,7 +310,7 @@ template  bool SX126xInterface::isChannelActive()
                                        .irqFlags = RADIOLIB_IRQ_CAD_DEFAULT_FLAGS,
                                        .irqMask = RADIOLIB_IRQ_CAD_DEFAULT_MASK}};
     int16_t result;
-
+    setTransmitEnable(false);
     setStandby();
     result = lora.scanChannel(cfg);
     if (result == RADIOLIB_LORA_DETECTED)
@@ -337,6 +349,26 @@ template  bool SX126xInterface::sleep()
     digitalWrite(SX126X_POWER_EN, LOW);
 #endif
 
+#ifdef HELTEC_V4
+    /*
+     * Do not switch the power on and off frequently.
+     * After turning off LORA_PA_EN, the power consumption has dropped to the uA level.
+     *  // digitalWrite(LORA_PA_POWER, LOW);
+     */
+    digitalWrite(LORA_PA_EN, LOW);
+    digitalWrite(LORA_PA_TX_EN, LOW);
+#endif
     return true;
 }
+
+/** Some boards require GPIO control of tx vs rx paths */
+template  void SX126xInterface::setTransmitEnable(bool txon)
+{
+#ifdef HELTEC_V4
+    digitalWrite(LORA_PA_POWER, HIGH);
+    digitalWrite(LORA_PA_EN, HIGH);
+    digitalWrite(LORA_PA_TX_EN, txon ? 1 : 0);
+#endif
+}
+
 #endif
\ No newline at end of file
diff --git a/src/mesh/SX126xInterface.h b/src/mesh/SX126xInterface.h
index 47b07c284..dc7024daa 100644
--- a/src/mesh/SX126xInterface.h
+++ b/src/mesh/SX126xInterface.h
@@ -71,5 +71,9 @@ template  class SX126xInterface : public RadioLibInterface
     virtual void addReceiveMetadata(meshtastic_MeshPacket *mp) override;
 
     virtual void setStandby() override;
+
+  private:
+    /** Some boards require GPIO control of tx vs rx paths */
+    void setTransmitEnable(bool txon);
 };
 #endif
\ No newline at end of file
diff --git a/src/platform/esp32/architecture.h b/src/platform/esp32/architecture.h
index a87391739..6b658c2a3 100644
--- a/src/platform/esp32/architecture.h
+++ b/src/platform/esp32/architecture.h
@@ -197,6 +197,8 @@
 #define HW_VENDOR meshtastic_HardwareModel_T_DECK_PRO
 #elif defined(T_LORA_PAGER)
 #define HW_VENDOR meshtastic_HardwareModel_T_LORA_PAGER
+#elif defined(HELTEC_V4)
+#define HW_VENDOR meshtastic_HardwareModel_HELTEC_V4
 #elif defined(M5STACK_UNITC6L)
 #define HW_VENDOR meshtastic_HardwareModel_M5STACK_C6L
 #endif
diff --git a/src/sleep.cpp b/src/sleep.cpp
index 83597e349..d6eb865e5 100644
--- a/src/sleep.cpp
+++ b/src/sleep.cpp
@@ -545,6 +545,12 @@ void enableLoraInterrupt()
     gpio_pullup_en((gpio_num_t)LORA_CS);
 #endif
 
+#ifdef HELTEC_V4
+    gpio_pullup_en((gpio_num_t)LORA_PA_POWER);
+    gpio_pullup_en((gpio_num_t)LORA_PA_EN);
+    gpio_pulldown_en((gpio_num_t)LORA_PA_TX_EN);
+#endif
+
     LOG_INFO("setup LORA_DIO1 (GPIO%02d) with wakeup by gpio interrupt", LORA_DIO1);
     gpio_wakeup_enable((gpio_num_t)LORA_DIO1, GPIO_INTR_HIGH_LEVEL);
 
diff --git a/variants/esp32s3/heltec_v4/pins_arduino.h b/variants/esp32s3/heltec_v4/pins_arduino.h
new file mode 100644
index 000000000..45561b4b5
--- /dev/null
+++ b/variants/esp32s3/heltec_v4/pins_arduino.h
@@ -0,0 +1,70 @@
+#ifndef Pins_Arduino_h
+#define Pins_Arduino_h
+
+#include 
+
+#define USB_VID 0x303a
+#define USB_PID 0x1001
+
+static const uint8_t LED_BUILTIN = 35;
+#define BUILTIN_LED LED_BUILTIN // backward compatibility
+#define LED_BUILTIN LED_BUILTIN // allow testing #ifdef LED_BUILTIN
+
+static const uint8_t TX = 43;
+static const uint8_t RX = 44;
+
+static const uint8_t SDA = 3;
+static const uint8_t SCL = 4;
+
+static const uint8_t SS = 8;
+static const uint8_t MOSI = 10;
+static const uint8_t MISO = 11;
+static const uint8_t SCK = 9;
+
+static const uint8_t A0 = 1;
+static const uint8_t A1 = 2;
+static const uint8_t A2 = 3;
+static const uint8_t A3 = 4;
+static const uint8_t A4 = 5;
+static const uint8_t A5 = 6;
+static const uint8_t A6 = 7;
+static const uint8_t A7 = 8;
+static const uint8_t A8 = 9;
+static const uint8_t A9 = 10;
+static const uint8_t A10 = 11;
+static const uint8_t A11 = 12;
+static const uint8_t A12 = 13;
+static const uint8_t A13 = 14;
+static const uint8_t A14 = 15;
+static const uint8_t A15 = 16;
+static const uint8_t A16 = 17;
+static const uint8_t A17 = 18;
+static const uint8_t A18 = 19;
+static const uint8_t A19 = 20;
+
+static const uint8_t T1 = 1;
+static const uint8_t T2 = 2;
+static const uint8_t T3 = 3;
+static const uint8_t T4 = 4;
+static const uint8_t T5 = 5;
+static const uint8_t T6 = 6;
+static const uint8_t T7 = 7;
+static const uint8_t T8 = 8;
+static const uint8_t T9 = 9;
+static const uint8_t T10 = 10;
+static const uint8_t T11 = 11;
+static const uint8_t T12 = 12;
+static const uint8_t T13 = 13;
+static const uint8_t T14 = 14;
+
+static const uint8_t Vext = 36;
+static const uint8_t LED = 35;
+static const uint8_t RST_OLED = 21;
+static const uint8_t SCL_OLED = 18;
+static const uint8_t SDA_OLED = 17;
+
+static const uint8_t RST_LoRa = 12;
+static const uint8_t BUSY_LoRa = 13;
+static const uint8_t DIO0 = 14;
+
+#endif /* Pins_Arduino_h */
\ No newline at end of file
diff --git a/variants/esp32s3/heltec_v4/platformio.ini b/variants/esp32s3/heltec_v4/platformio.ini
new file mode 100644
index 000000000..0177342a4
--- /dev/null
+++ b/variants/esp32s3/heltec_v4/platformio.ini
@@ -0,0 +1,10 @@
+[env:heltec-v4] 
+extends = esp32s3_base
+board = heltec_v4
+board_check = true
+build_flags = 
+  ${esp32s3_base.build_flags}
+  -D HELTEC_V4
+  -I variants/esp32s3/heltec_v4
+  -D GPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely.
+  -D SX126X_MAX_POWER=11
diff --git a/variants/esp32s3/heltec_v4/variant.h b/variants/esp32s3/heltec_v4/variant.h
new file mode 100644
index 000000000..2b6b7af3d
--- /dev/null
+++ b/variants/esp32s3/heltec_v4/variant.h
@@ -0,0 +1,58 @@
+#define LED_PIN 35
+
+#define USE_SSD1306 // Heltec_v4 has an SSD1315 display (compatible with SSD1306 driver)
+
+#define RESET_OLED 21
+#define I2C_SDA 17 // I2C pins for this board
+#define I2C_SCL 18
+
+#define VEXT_ENABLE 36 // active low, powers the oled display and the lora antenna boost
+#define BUTTON_PIN 0
+
+#define ADC_CTRL 37
+#define ADC_CTRL_ENABLED HIGH
+#define BATTERY_PIN 1 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage
+#define ADC_CHANNEL ADC1_GPIO1_CHANNEL
+#define ADC_ATTENUATION ADC_ATTEN_DB_2_5 // lower dB for high resistance voltage divider
+#define ADC_MULTIPLIER 4.9 * 1.045
+
+#define USE_SX1262
+
+#define LORA_DIO0 -1 // a No connect on the SX1262 module
+#define LORA_RESET 12
+#define LORA_DIO1 14 // SX1262 IRQ
+#define LORA_DIO2 13 // SX1262 BUSY
+#define LORA_DIO3    // Not connected on PCB, but internally on the TTGO SX1262, if DIO3 is high the TCXO is enabled
+
+#define LORA_SCK 9
+#define LORA_MISO 11
+#define LORA_MOSI 10
+#define LORA_CS 8
+
+#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
+
+#define LORA_PA_POWER 7 // power en
+#define LORA_PA_EN 2
+#define LORA_PA_TX_EN 46 // enable tx
+
+/*
+ * GPS pins
+ */
+#define GPS_L76K
+#define PIN_GPS_RESET (42) // An output to reset L76K GPS. As per datasheet, low for > 100ms will reset the L76K
+#define GPS_RESET_MODE LOW
+#define PIN_GPS_EN (34)
+#define GPS_EN_ACTIVE LOW
+#define PERIPHERAL_WARMUP_MS 1000 // Make sure I2C QuickLink has stable power before continuing
+#define PIN_GPS_STANDBY (40)      // An output to wake GPS, low means allow sleep, high means force wake
+#define PIN_GPS_PPS (41)
+// Seems to be missing on this new board
+#define GPS_TX_PIN (38) // This is for bits going TOWARDS the CPU
+#define GPS_RX_PIN (39) // This is for bits going TOWARDS the GPS
+#define GPS_THREAD_INTERVAL 50
\ No newline at end of file

From cea9e1238bf48b766e38984f2aa326e2ce821073 Mon Sep 17 00:00:00 2001
From: Quency-D <55523105+Quency-D@users.noreply.github.com>
Date: Sun, 21 Sep 2025 20:03:44 +0800
Subject: [PATCH 312/691] Add heltec_v4 board. (#7845)

* add heltec_v4 board.

* Update variants/esp32s3/heltec_v4/platformio.ini

Co-authored-by: Austin 

* Limit the maximum output power.

* Trunk fixes

Fixes formatting to match meshtastic trunk linter.

* Apply suggestion from @Copilot

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Apply suggestion from @Copilot

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Ben Meadors 
Co-authored-by: Austin 
Co-authored-by: Tom Fifield 
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---
 boards/heltec_v4.json                     | 43 ++++++++++++++
 src/mesh/SX126xInterface.cpp              | 36 +++++++++++-
 src/mesh/SX126xInterface.h                |  4 ++
 src/platform/esp32/architecture.h         |  2 +
 src/sleep.cpp                             |  6 ++
 variants/esp32s3/heltec_v4/pins_arduino.h | 70 +++++++++++++++++++++++
 variants/esp32s3/heltec_v4/platformio.ini | 10 ++++
 variants/esp32s3/heltec_v4/variant.h      | 58 +++++++++++++++++++
 8 files changed, 227 insertions(+), 2 deletions(-)
 create mode 100644 boards/heltec_v4.json
 create mode 100644 variants/esp32s3/heltec_v4/pins_arduino.h
 create mode 100644 variants/esp32s3/heltec_v4/platformio.ini
 create mode 100644 variants/esp32s3/heltec_v4/variant.h

diff --git a/boards/heltec_v4.json b/boards/heltec_v4.json
new file mode 100644
index 000000000..8eac3a9b2
--- /dev/null
+++ b/boards/heltec_v4.json
@@ -0,0 +1,43 @@
+{
+  "build": {
+    "arduino": {
+      "ldscript": "esp32s3_out.ld",
+      "partitions": "default_16MB.csv",
+      "memory_type": "qio_qspi"
+    },
+    "core": "esp32",
+    "extra_flags": [
+      "-DBOARD_HAS_PSRAM",
+      "-DARDUINO_USB_CDC_ON_BOOT=1",
+      "-DARDUINO_USB_MODE=0",
+      "-DARDUINO_RUNNING_CORE=1",
+      "-DARDUINO_EVENT_RUNNING_CORE=1"
+    ],
+    "f_cpu": "240000000L",
+    "f_flash": "80000000L",
+    "flash_mode": "qio",
+    "psram_type": "qspi",
+    "hwids": [["0x303A", "0x1001"]],
+    "mcu": "esp32s3",
+    "variant": "heltec_v4"
+  },
+  "connectivity": ["wifi", "bluetooth", "lora"],
+  "debug": {
+    "default_tool": "esp-builtin",
+    "onboard_tools": ["esp-builtin"],
+    "openocd_target": "esp32s3.cfg"
+  },
+  "frameworks": ["arduino", "espidf"],
+  "name": "heltec_wifi_lora_32 v4 (16 MB FLASH, 2 MB PSRAM)",
+  "upload": {
+    "flash_size": "16MB",
+    "maximum_ram_size": 2097152,
+    "maximum_size": 16777216,
+    "use_1200bps_touch": true,
+    "wait_for_upload_port": true,
+    "require_upload_port": true,
+    "speed": 921600
+  },
+  "url": "https://heltec.org/",
+  "vendor": "heltec"
+}
diff --git a/src/mesh/SX126xInterface.cpp b/src/mesh/SX126xInterface.cpp
index 49dc562d4..3fc2562b3 100644
--- a/src/mesh/SX126xInterface.cpp
+++ b/src/mesh/SX126xInterface.cpp
@@ -52,6 +52,16 @@ template  bool SX126xInterface::init()
     pinMode(SX126X_POWER_EN, OUTPUT);
 #endif
 
+#ifdef HELTEC_V4
+    pinMode(LORA_PA_POWER, OUTPUT);
+    digitalWrite(LORA_PA_POWER, HIGH);
+
+    pinMode(LORA_PA_EN, OUTPUT);
+    digitalWrite(LORA_PA_EN, LOW);
+    pinMode(LORA_PA_TX_EN, OUTPUT);
+    digitalWrite(LORA_PA_TX_EN, LOW);
+#endif
+
 #if ARCH_PORTDUINO
     tcxoVoltage = (float)portduino_config.dio3_tcxo_voltage / 1000;
     if (portduino_config.lora_sx126x_ant_sw_pin.pin != RADIOLIB_NC) {
@@ -63,7 +73,7 @@ template  bool SX126xInterface::init()
         LOG_DEBUG("SX126X_DIO3_TCXO_VOLTAGE not defined, not using DIO3 as TCXO reference voltage");
     else
         LOG_DEBUG("SX126X_DIO3_TCXO_VOLTAGE defined, using DIO3 as TCXO reference voltage at %f V", tcxoVoltage);
-
+    setTransmitEnable(false);
     // FIXME: May want to set depending on a definition, currently all SX126x variant files use the DC-DC regulator option
     bool useRegulatorLDO = false; // Seems to depend on the connection to pin 9/DCC_SW - if an inductor DCDC?
 
@@ -259,6 +269,7 @@ template  void SX126xInterface::addReceiveMetadata(meshtastic_Mes
  */
 template  void SX126xInterface::configHardwareForSend()
 {
+    setTransmitEnable(true);
     RadioLibInterface::configHardwareForSend();
 }
 
@@ -271,6 +282,7 @@ template  void SX126xInterface::startReceive()
     sleep();
 #else
 
+    setTransmitEnable(false);
     setStandby();
 
     // We use a 16 bit preamble so this should save some power by letting radio sit in standby mostly.
@@ -298,7 +310,7 @@ template  bool SX126xInterface::isChannelActive()
                                        .irqFlags = RADIOLIB_IRQ_CAD_DEFAULT_FLAGS,
                                        .irqMask = RADIOLIB_IRQ_CAD_DEFAULT_MASK}};
     int16_t result;
-
+    setTransmitEnable(false);
     setStandby();
     result = lora.scanChannel(cfg);
     if (result == RADIOLIB_LORA_DETECTED)
@@ -337,6 +349,26 @@ template  bool SX126xInterface::sleep()
     digitalWrite(SX126X_POWER_EN, LOW);
 #endif
 
+#ifdef HELTEC_V4
+    /*
+     * Do not switch the power on and off frequently.
+     * After turning off LORA_PA_EN, the power consumption has dropped to the uA level.
+     *  // digitalWrite(LORA_PA_POWER, LOW);
+     */
+    digitalWrite(LORA_PA_EN, LOW);
+    digitalWrite(LORA_PA_TX_EN, LOW);
+#endif
     return true;
 }
+
+/** Some boards require GPIO control of tx vs rx paths */
+template  void SX126xInterface::setTransmitEnable(bool txon)
+{
+#ifdef HELTEC_V4
+    digitalWrite(LORA_PA_POWER, HIGH);
+    digitalWrite(LORA_PA_EN, HIGH);
+    digitalWrite(LORA_PA_TX_EN, txon ? 1 : 0);
+#endif
+}
+
 #endif
\ No newline at end of file
diff --git a/src/mesh/SX126xInterface.h b/src/mesh/SX126xInterface.h
index 47b07c284..dc7024daa 100644
--- a/src/mesh/SX126xInterface.h
+++ b/src/mesh/SX126xInterface.h
@@ -71,5 +71,9 @@ template  class SX126xInterface : public RadioLibInterface
     virtual void addReceiveMetadata(meshtastic_MeshPacket *mp) override;
 
     virtual void setStandby() override;
+
+  private:
+    /** Some boards require GPIO control of tx vs rx paths */
+    void setTransmitEnable(bool txon);
 };
 #endif
\ No newline at end of file
diff --git a/src/platform/esp32/architecture.h b/src/platform/esp32/architecture.h
index 22ce6487f..c58b44718 100644
--- a/src/platform/esp32/architecture.h
+++ b/src/platform/esp32/architecture.h
@@ -194,6 +194,8 @@
 #define HW_VENDOR meshtastic_HardwareModel_T_DECK_PRO
 #elif defined(T_LORA_PAGER)
 #define HW_VENDOR meshtastic_HardwareModel_T_LORA_PAGER
+#elif defined(HELTEC_V4)
+#define HW_VENDOR meshtastic_HardwareModel_HELTEC_V4
 #elif defined(M5STACK_UNITC6L)
 #define HW_VENDOR meshtastic_HardwareModel_M5STACK_C6L
 #endif
diff --git a/src/sleep.cpp b/src/sleep.cpp
index 83597e349..d6eb865e5 100644
--- a/src/sleep.cpp
+++ b/src/sleep.cpp
@@ -545,6 +545,12 @@ void enableLoraInterrupt()
     gpio_pullup_en((gpio_num_t)LORA_CS);
 #endif
 
+#ifdef HELTEC_V4
+    gpio_pullup_en((gpio_num_t)LORA_PA_POWER);
+    gpio_pullup_en((gpio_num_t)LORA_PA_EN);
+    gpio_pulldown_en((gpio_num_t)LORA_PA_TX_EN);
+#endif
+
     LOG_INFO("setup LORA_DIO1 (GPIO%02d) with wakeup by gpio interrupt", LORA_DIO1);
     gpio_wakeup_enable((gpio_num_t)LORA_DIO1, GPIO_INTR_HIGH_LEVEL);
 
diff --git a/variants/esp32s3/heltec_v4/pins_arduino.h b/variants/esp32s3/heltec_v4/pins_arduino.h
new file mode 100644
index 000000000..45561b4b5
--- /dev/null
+++ b/variants/esp32s3/heltec_v4/pins_arduino.h
@@ -0,0 +1,70 @@
+#ifndef Pins_Arduino_h
+#define Pins_Arduino_h
+
+#include 
+
+#define USB_VID 0x303a
+#define USB_PID 0x1001
+
+static const uint8_t LED_BUILTIN = 35;
+#define BUILTIN_LED LED_BUILTIN // backward compatibility
+#define LED_BUILTIN LED_BUILTIN // allow testing #ifdef LED_BUILTIN
+
+static const uint8_t TX = 43;
+static const uint8_t RX = 44;
+
+static const uint8_t SDA = 3;
+static const uint8_t SCL = 4;
+
+static const uint8_t SS = 8;
+static const uint8_t MOSI = 10;
+static const uint8_t MISO = 11;
+static const uint8_t SCK = 9;
+
+static const uint8_t A0 = 1;
+static const uint8_t A1 = 2;
+static const uint8_t A2 = 3;
+static const uint8_t A3 = 4;
+static const uint8_t A4 = 5;
+static const uint8_t A5 = 6;
+static const uint8_t A6 = 7;
+static const uint8_t A7 = 8;
+static const uint8_t A8 = 9;
+static const uint8_t A9 = 10;
+static const uint8_t A10 = 11;
+static const uint8_t A11 = 12;
+static const uint8_t A12 = 13;
+static const uint8_t A13 = 14;
+static const uint8_t A14 = 15;
+static const uint8_t A15 = 16;
+static const uint8_t A16 = 17;
+static const uint8_t A17 = 18;
+static const uint8_t A18 = 19;
+static const uint8_t A19 = 20;
+
+static const uint8_t T1 = 1;
+static const uint8_t T2 = 2;
+static const uint8_t T3 = 3;
+static const uint8_t T4 = 4;
+static const uint8_t T5 = 5;
+static const uint8_t T6 = 6;
+static const uint8_t T7 = 7;
+static const uint8_t T8 = 8;
+static const uint8_t T9 = 9;
+static const uint8_t T10 = 10;
+static const uint8_t T11 = 11;
+static const uint8_t T12 = 12;
+static const uint8_t T13 = 13;
+static const uint8_t T14 = 14;
+
+static const uint8_t Vext = 36;
+static const uint8_t LED = 35;
+static const uint8_t RST_OLED = 21;
+static const uint8_t SCL_OLED = 18;
+static const uint8_t SDA_OLED = 17;
+
+static const uint8_t RST_LoRa = 12;
+static const uint8_t BUSY_LoRa = 13;
+static const uint8_t DIO0 = 14;
+
+#endif /* Pins_Arduino_h */
\ No newline at end of file
diff --git a/variants/esp32s3/heltec_v4/platformio.ini b/variants/esp32s3/heltec_v4/platformio.ini
new file mode 100644
index 000000000..0177342a4
--- /dev/null
+++ b/variants/esp32s3/heltec_v4/platformio.ini
@@ -0,0 +1,10 @@
+[env:heltec-v4] 
+extends = esp32s3_base
+board = heltec_v4
+board_check = true
+build_flags = 
+  ${esp32s3_base.build_flags}
+  -D HELTEC_V4
+  -I variants/esp32s3/heltec_v4
+  -D GPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely.
+  -D SX126X_MAX_POWER=11
diff --git a/variants/esp32s3/heltec_v4/variant.h b/variants/esp32s3/heltec_v4/variant.h
new file mode 100644
index 000000000..2b6b7af3d
--- /dev/null
+++ b/variants/esp32s3/heltec_v4/variant.h
@@ -0,0 +1,58 @@
+#define LED_PIN 35
+
+#define USE_SSD1306 // Heltec_v4 has an SSD1315 display (compatible with SSD1306 driver)
+
+#define RESET_OLED 21
+#define I2C_SDA 17 // I2C pins for this board
+#define I2C_SCL 18
+
+#define VEXT_ENABLE 36 // active low, powers the oled display and the lora antenna boost
+#define BUTTON_PIN 0
+
+#define ADC_CTRL 37
+#define ADC_CTRL_ENABLED HIGH
+#define BATTERY_PIN 1 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage
+#define ADC_CHANNEL ADC1_GPIO1_CHANNEL
+#define ADC_ATTENUATION ADC_ATTEN_DB_2_5 // lower dB for high resistance voltage divider
+#define ADC_MULTIPLIER 4.9 * 1.045
+
+#define USE_SX1262
+
+#define LORA_DIO0 -1 // a No connect on the SX1262 module
+#define LORA_RESET 12
+#define LORA_DIO1 14 // SX1262 IRQ
+#define LORA_DIO2 13 // SX1262 BUSY
+#define LORA_DIO3    // Not connected on PCB, but internally on the TTGO SX1262, if DIO3 is high the TCXO is enabled
+
+#define LORA_SCK 9
+#define LORA_MISO 11
+#define LORA_MOSI 10
+#define LORA_CS 8
+
+#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
+
+#define LORA_PA_POWER 7 // power en
+#define LORA_PA_EN 2
+#define LORA_PA_TX_EN 46 // enable tx
+
+/*
+ * GPS pins
+ */
+#define GPS_L76K
+#define PIN_GPS_RESET (42) // An output to reset L76K GPS. As per datasheet, low for > 100ms will reset the L76K
+#define GPS_RESET_MODE LOW
+#define PIN_GPS_EN (34)
+#define GPS_EN_ACTIVE LOW
+#define PERIPHERAL_WARMUP_MS 1000 // Make sure I2C QuickLink has stable power before continuing
+#define PIN_GPS_STANDBY (40)      // An output to wake GPS, low means allow sleep, high means force wake
+#define PIN_GPS_PPS (41)
+// Seems to be missing on this new board
+#define GPS_TX_PIN (38) // This is for bits going TOWARDS the CPU
+#define GPS_RX_PIN (39) // This is for bits going TOWARDS the GPS
+#define GPS_THREAD_INTERVAL 50
\ No newline at end of file

From b3df32c6c5016eae4298fce8b47a543f1201ef77 Mon Sep 17 00:00:00 2001
From: Jason P 
Date: Sun, 21 Sep 2025 14:04:17 -0500
Subject: [PATCH 313/691] Fix build errors (#8067)

---
 src/graphics/images.h | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/src/graphics/images.h b/src/graphics/images.h
index 7b80fc5ab..b5010b116 100644
--- a/src/graphics/images.h
+++ b/src/graphics/images.h
@@ -287,9 +287,7 @@ const uint8_t digital_icon_clock[] PROGMEM = {0b00111100, 0b01000010, 0b10000101
 #define analog_icon_clock_height 8
 const uint8_t analog_icon_clock[] PROGMEM = {0b11111111, 0b01000010, 0b00100100, 0b00011000,
                                              0b00100100, 0b01000010, 0b01000010, 0b11111111};
-#ifdef M5STACK_UNITC6L
-#include "img/icon_small.xbm"
-#else
+
 #define chirpy_width 38
 #define chirpy_height 50
 const uint8_t chirpy[] = {
@@ -362,6 +360,9 @@ const uint8_t chirpy_hirez[] = {
 #define chirpy_small_image_height 8
 const uint8_t chirpy_small[] = {0x7f, 0x41, 0x55, 0x55, 0x55, 0x55, 0x41, 0x7f};
 
+#ifdef M5STACK_UNITC6L
+#include "img/icon_small.xbm"
+#else
 #include "img/icon.xbm"
 #endif
 static_assert(sizeof(icon_bits) >= 0, "Silence unused variable warning");
\ No newline at end of file

From 3d51287ba77693e5b0140d81abfe4cd218e0c2ae Mon Sep 17 00:00:00 2001
From: Jason P 
Date: Sun, 21 Sep 2025 17:54:54 -0500
Subject: [PATCH 314/691] Introduce Radio Preset elections through BaseUI
 (#8071)

---
 src/graphics/draw/MenuHandler.cpp | 62 ++++++++++++++++++++++++++++---
 src/graphics/draw/MenuHandler.h   |  2 +
 2 files changed, 59 insertions(+), 5 deletions(-)

diff --git a/src/graphics/draw/MenuHandler.cpp b/src/graphics/draw/MenuHandler.cpp
index 9be0e0b02..7c4f4e05f 100644
--- a/src/graphics/draw/MenuHandler.cpp
+++ b/src/graphics/draw/MenuHandler.cpp
@@ -31,19 +31,21 @@ uint8_t test_count = 0;
 
 void menuHandler::loraMenu()
 {
-    static const char *optionsArray[] = {"Back", "Region Picker", "Device Role"};
-    enum optionsNumbers { Back = 0, lora_picker = 1, device_role_picker = 2 };
+    static const char *optionsArray[] = {"Back", "Device Role", "Radio Preset", "LoRa Region"};
+    enum optionsNumbers { Back = 0, device_role_picker = 1, radio_preset_picker = 2, lora_picker = 3 };
     BannerOverlayOptions bannerOptions;
     bannerOptions.message = "LoRa Actions";
     bannerOptions.optionsArrayPtr = optionsArray;
-    bannerOptions.optionsCount = 3;
+    bannerOptions.optionsCount = 4;
     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;
+        } else if (selected == radio_preset_picker) {
+            menuHandler::menuQueue = menuHandler::radio_preset_picker;
+        } else if (selected == lora_picker) {
+            menuHandler::menuQueue = menuHandler::lora_picker;
         }
     };
     screen->showOverlayBanner(bannerOptions);
@@ -180,6 +182,53 @@ void menuHandler::DeviceRolePicker()
     screen->showOverlayBanner(bannerOptions);
 }
 
+void menuHandler::RadioPresetPicker()
+{
+    static const char *optionsArray[] = {"Back",       "LongSlow",  "LongModerate", "LongFast",  "MediumSlow",
+                                         "MediumFast", "ShortSlow", "ShortFast",    "ShortTurbo"};
+    enum optionsNumbers {
+        Back = 0,
+        radiopreset_LongSlow = 1,
+        radiopreset_LongModerate = 2,
+        radiopreset_LongFast = 3,
+        radiopreset_MediumSlow = 4,
+        radiopreset_MediumFast = 5,
+        radiopreset_ShortSlow = 6,
+        radiopreset_ShortFast = 7,
+        radiopreset_ShortTurbo = 8
+    };
+    BannerOverlayOptions bannerOptions;
+    bannerOptions.message = "Radio Preset";
+    bannerOptions.optionsArrayPtr = optionsArray;
+    bannerOptions.optionsCount = 9;
+    bannerOptions.bannerCallback = [](int selected) -> void {
+        if (selected == Back) {
+            menuHandler::menuQueue = menuHandler::lora_Menu;
+            screen->runNow();
+            return;
+        } else if (selected == radiopreset_LongSlow) {
+            config.lora.modem_preset = meshtastic_Config_LoRaConfig_ModemPreset_LONG_SLOW;
+        } else if (selected == radiopreset_LongModerate) {
+            config.lora.modem_preset = meshtastic_Config_LoRaConfig_ModemPreset_LONG_MODERATE;
+        } else if (selected == radiopreset_LongFast) {
+            config.lora.modem_preset = meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST;
+        } else if (selected == radiopreset_MediumSlow) {
+            config.lora.modem_preset = meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_SLOW;
+        } else if (selected == radiopreset_MediumFast) {
+            config.lora.modem_preset = meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_FAST;
+        } else if (selected == radiopreset_ShortSlow) {
+            config.lora.modem_preset = meshtastic_Config_LoRaConfig_ModemPreset_SHORT_SLOW;
+        } else if (selected == radiopreset_ShortFast) {
+            config.lora.modem_preset = meshtastic_Config_LoRaConfig_ModemPreset_SHORT_FAST;
+        } else if (selected == radiopreset_ShortTurbo) {
+            config.lora.modem_preset = meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO;
+        }
+        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"};
@@ -1478,6 +1527,9 @@ void menuHandler::handleMenuSwitch(OLEDDisplay *display)
     case device_role_picker:
         DeviceRolePicker();
         break;
+    case radio_preset_picker:
+        RadioPresetPicker();
+        break;
     case no_timeout_lora_picker:
         LoraRegionPicker(0);
         break;
diff --git a/src/graphics/draw/MenuHandler.h b/src/graphics/draw/MenuHandler.h
index 56477258f..47dbb2543 100644
--- a/src/graphics/draw/MenuHandler.h
+++ b/src/graphics/draw/MenuHandler.h
@@ -12,6 +12,7 @@ class menuHandler
         lora_Menu,
         lora_picker,
         device_role_picker,
+        radio_preset_picker,
         no_timeout_lora_picker,
         TZ_picker,
         twelve_hour_picker,
@@ -50,6 +51,7 @@ class menuHandler
     static void LoraRegionPicker(uint32_t duration = 30000);
     static void loraMenu();
     static void DeviceRolePicker();
+    static void RadioPresetPicker();
     static void handleMenuSwitch(OLEDDisplay *display);
     static void showConfirmationBanner(const char *message, std::function onConfirm);
     static void clockMenu();

From 388c821028cffea0b78db84d07ec6a4ee8b1fe40 Mon Sep 17 00:00:00 2001
From: Tom Fifield 
Date: Mon, 22 Sep 2025 10:23:42 +1000
Subject: [PATCH 315/691] Allow label enforcement job to run on self-hosted
 runners (#7909)

Previously, this check would only run on github-provided runners.
---
 .github/workflows/pr_enforce_labels.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.github/workflows/pr_enforce_labels.yml b/.github/workflows/pr_enforce_labels.yml
index 5fca90961..543e23558 100644
--- a/.github/workflows/pr_enforce_labels.yml
+++ b/.github/workflows/pr_enforce_labels.yml
@@ -10,7 +10,7 @@ permissions:
 
 jobs:
   check-label:
-    runs-on: ubuntu-24.04
+    runs-on: ubuntu-latest
     steps:
       - name: Check for PR labels
         uses: actions/github-script@v8

From 59f9e2a00b730971097975e690aed463660218db Mon Sep 17 00:00:00 2001
From: ford-jones 
Date: Mon, 22 Sep 2025 14:59:45 +1200
Subject: [PATCH 316/691] Regen protos

---
 src/mesh/generated/meshtastic/admin.pb.h      |  8 ---
 src/mesh/generated/meshtastic/apponly.pb.h    |  2 +-
 src/mesh/generated/meshtastic/channel.pb.h    | 14 ++----
 src/mesh/generated/meshtastic/config.pb.h     | 49 ++++++++++++-------
 .../generated/meshtastic/device_ui.pb.cpp     |  2 -
 src/mesh/generated/meshtastic/device_ui.pb.h  | 43 ++--------------
 src/mesh/generated/meshtastic/deviceonly.pb.h | 23 ++++-----
 src/mesh/generated/meshtastic/localonly.pb.h  |  4 +-
 src/mesh/generated/meshtastic/mesh.pb.h       |  8 +--
 .../generated/meshtastic/module_config.pb.h   | 13 ++---
 10 files changed, 58 insertions(+), 108 deletions(-)

diff --git a/src/mesh/generated/meshtastic/admin.pb.h b/src/mesh/generated/meshtastic/admin.pb.h
index 616b7e8ee..bc0b780b9 100644
--- a/src/mesh/generated/meshtastic/admin.pb.h
+++ b/src/mesh/generated/meshtastic/admin.pb.h
@@ -247,10 +247,6 @@ typedef struct _meshtastic_AdminMessage {
         uint32_t set_ignored_node;
         /* Set specified node-num to be un-ignored on the NodeDB on the device */
         uint32_t remove_ignored_node;
-        /* Set specified node-num to be muted */
-        uint32_t set_muted_node;
-        /* Set specified node-num to be heard / not-muted */
-        uint32_t remove_muted_node;
         /* Begins an edit transaction for config, module config, owner, and channel settings changes
      This will delay the standard *implicit* save to the file system and subsequent reboot behavior until committed (commit_edit_settings) */
         bool begin_edit_settings;
@@ -392,8 +388,6 @@ extern "C" {
 #define meshtastic_AdminMessage_store_ui_config_tag 46
 #define meshtastic_AdminMessage_set_ignored_node_tag 47
 #define meshtastic_AdminMessage_remove_ignored_node_tag 48
-#define meshtastic_AdminMessage_set_muted_node_tag 49
-#define meshtastic_AdminMessage_remove_muted_node_tag 50
 #define meshtastic_AdminMessage_begin_edit_settings_tag 64
 #define meshtastic_AdminMessage_commit_edit_settings_tag 65
 #define meshtastic_AdminMessage_add_contact_tag  66
@@ -452,8 +446,6 @@ X(a, STATIC,   ONEOF,    MESSAGE,  (payload_variant,get_ui_config_response,get_u
 X(a, STATIC,   ONEOF,    MESSAGE,  (payload_variant,store_ui_config,store_ui_config),  46) \
 X(a, STATIC,   ONEOF,    UINT32,   (payload_variant,set_ignored_node,set_ignored_node),  47) \
 X(a, STATIC,   ONEOF,    UINT32,   (payload_variant,remove_ignored_node,remove_ignored_node),  48) \
-X(a, STATIC,   ONEOF,    UINT32,   (payload_variant,set_muted_node,set_muted_node),  49) \
-X(a, STATIC,   ONEOF,    UINT32,   (payload_variant,remove_muted_node,remove_muted_node),  50) \
 X(a, STATIC,   ONEOF,    BOOL,     (payload_variant,begin_edit_settings,begin_edit_settings),  64) \
 X(a, STATIC,   ONEOF,    BOOL,     (payload_variant,commit_edit_settings,commit_edit_settings),  65) \
 X(a, STATIC,   ONEOF,    MESSAGE,  (payload_variant,add_contact,add_contact),  66) \
diff --git a/src/mesh/generated/meshtastic/apponly.pb.h b/src/mesh/generated/meshtastic/apponly.pb.h
index db9dedaaf..f4c33bd79 100644
--- a/src/mesh/generated/meshtastic/apponly.pb.h
+++ b/src/mesh/generated/meshtastic/apponly.pb.h
@@ -55,7 +55,7 @@ extern const pb_msgdesc_t meshtastic_ChannelSet_msg;
 
 /* Maximum encoded size of messages (where known) */
 #define MESHTASTIC_MESHTASTIC_APPONLY_PB_H_MAX_SIZE meshtastic_ChannelSet_size
-#define meshtastic_ChannelSet_size               695
+#define meshtastic_ChannelSet_size               679
 
 #ifdef __cplusplus
 } /* extern "C" */
diff --git a/src/mesh/generated/meshtastic/channel.pb.h b/src/mesh/generated/meshtastic/channel.pb.h
index 594d15929..ca4310bf1 100644
--- a/src/mesh/generated/meshtastic/channel.pb.h
+++ b/src/mesh/generated/meshtastic/channel.pb.h
@@ -97,8 +97,6 @@ typedef struct _meshtastic_ChannelSettings {
     /* Per-channel module settings. */
     bool has_module_settings;
     meshtastic_ModuleSettings module_settings;
-    /* Whether or not we should receive notifactions / alerts from this channel */
-    bool mute;
 } meshtastic_ChannelSettings;
 
 /* A pair of a channel number, mode and the (sharable) settings for that channel */
@@ -130,10 +128,10 @@ extern "C" {
 
 
 /* Initializer values for message structs */
-#define meshtastic_ChannelSettings_init_default  {0, {0, {0}}, "", 0, 0, 0, false, meshtastic_ModuleSettings_init_default, 0}
+#define meshtastic_ChannelSettings_init_default  {0, {0, {0}}, "", 0, 0, 0, false, meshtastic_ModuleSettings_init_default}
 #define meshtastic_ModuleSettings_init_default   {0, 0}
 #define meshtastic_Channel_init_default          {0, false, meshtastic_ChannelSettings_init_default, _meshtastic_Channel_Role_MIN}
-#define meshtastic_ChannelSettings_init_zero     {0, {0, {0}}, "", 0, 0, 0, false, meshtastic_ModuleSettings_init_zero, 0}
+#define meshtastic_ChannelSettings_init_zero     {0, {0, {0}}, "", 0, 0, 0, false, meshtastic_ModuleSettings_init_zero}
 #define meshtastic_ModuleSettings_init_zero      {0, 0}
 #define meshtastic_Channel_init_zero             {0, false, meshtastic_ChannelSettings_init_zero, _meshtastic_Channel_Role_MIN}
 
@@ -147,7 +145,6 @@ extern "C" {
 #define meshtastic_ChannelSettings_uplink_enabled_tag 5
 #define meshtastic_ChannelSettings_downlink_enabled_tag 6
 #define meshtastic_ChannelSettings_module_settings_tag 7
-#define meshtastic_ChannelSettings_mute_tag      8
 #define meshtastic_Channel_index_tag             1
 #define meshtastic_Channel_settings_tag          2
 #define meshtastic_Channel_role_tag              3
@@ -160,8 +157,7 @@ X(a, STATIC,   SINGULAR, STRING,   name,              3) \
 X(a, STATIC,   SINGULAR, FIXED32,  id,                4) \
 X(a, STATIC,   SINGULAR, BOOL,     uplink_enabled,    5) \
 X(a, STATIC,   SINGULAR, BOOL,     downlink_enabled,   6) \
-X(a, STATIC,   OPTIONAL, MESSAGE,  module_settings,   7) \
-X(a, STATIC,   SINGULAR, BOOL,     mute,              8)
+X(a, STATIC,   OPTIONAL, MESSAGE,  module_settings,   7)
 #define meshtastic_ChannelSettings_CALLBACK NULL
 #define meshtastic_ChannelSettings_DEFAULT NULL
 #define meshtastic_ChannelSettings_module_settings_MSGTYPE meshtastic_ModuleSettings
@@ -191,8 +187,8 @@ extern const pb_msgdesc_t meshtastic_Channel_msg;
 
 /* Maximum encoded size of messages (where known) */
 #define MESHTASTIC_MESHTASTIC_CHANNEL_PB_H_MAX_SIZE meshtastic_Channel_size
-#define meshtastic_ChannelSettings_size          74
-#define meshtastic_Channel_size                  89
+#define meshtastic_ChannelSettings_size          72
+#define meshtastic_Channel_size                  87
 #define meshtastic_ModuleSettings_size           8
 
 #ifdef __cplusplus
diff --git a/src/mesh/generated/meshtastic/config.pb.h b/src/mesh/generated/meshtastic/config.pb.h
index 0453ecad2..59e55db3f 100644
--- a/src/mesh/generated/meshtastic/config.pb.h
+++ b/src/mesh/generated/meshtastic/config.pb.h
@@ -173,10 +173,28 @@ typedef enum _meshtastic_Config_NetworkConfig_ProtocolFlags {
     meshtastic_Config_NetworkConfig_ProtocolFlags_UDP_BROADCAST = 1
 } meshtastic_Config_NetworkConfig_ProtocolFlags;
 
-/* Deprecated in 2.7.4: Unused */
-typedef enum _meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat {
-    meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat_UNUSED = 0
-} meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat;
+/* How the GPS coordinates are displayed on the OLED screen. */
+typedef enum _meshtastic_Config_DisplayConfig_GpsCoordinateFormat {
+    /* GPS coordinates are displayed in the normal decimal degrees format:
+ DD.DDDDDD DDD.DDDDDD */
+    meshtastic_Config_DisplayConfig_GpsCoordinateFormat_DEC = 0,
+    /* GPS coordinates are displayed in the degrees minutes seconds format:
+ DD°MM'SS"C DDD°MM'SS"C, where C is the compass point representing the locations quadrant */
+    meshtastic_Config_DisplayConfig_GpsCoordinateFormat_DMS = 1,
+    /* Universal Transverse Mercator format:
+ ZZB EEEEEE NNNNNNN, where Z is zone, B is band, E is easting, N is northing */
+    meshtastic_Config_DisplayConfig_GpsCoordinateFormat_UTM = 2,
+    /* Military Grid Reference System format:
+ ZZB CD EEEEE NNNNN, where Z is zone, B is band, C is the east 100k square, D is the north 100k square,
+ E is easting, N is northing */
+    meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MGRS = 3,
+    /* Open Location Code (aka Plus Codes). */
+    meshtastic_Config_DisplayConfig_GpsCoordinateFormat_OLC = 4,
+    /* Ordnance Survey Grid Reference (the National Grid System of the UK).
+ Format: AB EEEEE NNNNN, where A is the east 100k square, B is the north 100k square,
+ E is the easting, N is the northing */
+    meshtastic_Config_DisplayConfig_GpsCoordinateFormat_OSGR = 5
+} meshtastic_Config_DisplayConfig_GpsCoordinateFormat;
 
 /* Unit display preference */
 typedef enum _meshtastic_Config_DisplayConfig_DisplayUnits {
@@ -473,7 +491,7 @@ typedef struct _meshtastic_Config_DisplayConfig {
     uint32_t screen_on_secs;
     /* Deprecated in 2.7.4: Unused
  How the GPS coordinates are formatted on the OLED screen. */
-    meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat gps_format;
+    meshtastic_Config_DisplayConfig_GpsCoordinateFormat gps_format;
     /* Automatically toggles to the next page on the screen like a carousel, based the specified interval in seconds.
  Potentially useful for devices without user buttons. */
     uint32_t auto_screen_carousel_secs;
@@ -497,9 +515,6 @@ typedef struct _meshtastic_Config_DisplayConfig {
     /* If false (default), the device will display the time in 24-hour format on screen.
  If true, the device will display the time in 12-hour format on screen. */
     bool use_12h_clock;
-    /* If false (default), the device will use short names for various display screens.
- If true, node names will show in long format */
-    bool use_long_node_name;
 } meshtastic_Config_DisplayConfig;
 
 /* Lora Config */
@@ -663,9 +678,9 @@ extern "C" {
 #define _meshtastic_Config_NetworkConfig_ProtocolFlags_MAX meshtastic_Config_NetworkConfig_ProtocolFlags_UDP_BROADCAST
 #define _meshtastic_Config_NetworkConfig_ProtocolFlags_ARRAYSIZE ((meshtastic_Config_NetworkConfig_ProtocolFlags)(meshtastic_Config_NetworkConfig_ProtocolFlags_UDP_BROADCAST+1))
 
-#define _meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat_MIN meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat_UNUSED
-#define _meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat_MAX meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat_UNUSED
-#define _meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat_ARRAYSIZE ((meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat)(meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat_UNUSED+1))
+#define _meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MIN meshtastic_Config_DisplayConfig_GpsCoordinateFormat_DEC
+#define _meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MAX meshtastic_Config_DisplayConfig_GpsCoordinateFormat_OSGR
+#define _meshtastic_Config_DisplayConfig_GpsCoordinateFormat_ARRAYSIZE ((meshtastic_Config_DisplayConfig_GpsCoordinateFormat)(meshtastic_Config_DisplayConfig_GpsCoordinateFormat_OSGR+1))
 
 #define _meshtastic_Config_DisplayConfig_DisplayUnits_MIN meshtastic_Config_DisplayConfig_DisplayUnits_METRIC
 #define _meshtastic_Config_DisplayConfig_DisplayUnits_MAX meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL
@@ -706,7 +721,7 @@ extern "C" {
 #define meshtastic_Config_NetworkConfig_address_mode_ENUMTYPE meshtastic_Config_NetworkConfig_AddressMode
 
 
-#define meshtastic_Config_DisplayConfig_gps_format_ENUMTYPE meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat
+#define meshtastic_Config_DisplayConfig_gps_format_ENUMTYPE meshtastic_Config_DisplayConfig_GpsCoordinateFormat
 #define meshtastic_Config_DisplayConfig_units_ENUMTYPE meshtastic_Config_DisplayConfig_DisplayUnits
 #define meshtastic_Config_DisplayConfig_oled_ENUMTYPE meshtastic_Config_DisplayConfig_OledType
 #define meshtastic_Config_DisplayConfig_displaymode_ENUMTYPE meshtastic_Config_DisplayConfig_DisplayMode
@@ -727,7 +742,7 @@ extern "C" {
 #define meshtastic_Config_PowerConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0}
 #define meshtastic_Config_NetworkConfig_init_default {0, "", "", "", 0, _meshtastic_Config_NetworkConfig_AddressMode_MIN, false, meshtastic_Config_NetworkConfig_IpV4Config_init_default, "", 0, 0}
 #define meshtastic_Config_NetworkConfig_IpV4Config_init_default {0, 0, 0, 0}
-#define meshtastic_Config_DisplayConfig_init_default {0, _meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat_MIN, 0, 0, 0, _meshtastic_Config_DisplayConfig_DisplayUnits_MIN, _meshtastic_Config_DisplayConfig_OledType_MIN, _meshtastic_Config_DisplayConfig_DisplayMode_MIN, 0, 0, _meshtastic_Config_DisplayConfig_CompassOrientation_MIN, 0, 0}
+#define meshtastic_Config_DisplayConfig_init_default {0, _meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MIN, 0, 0, 0, _meshtastic_Config_DisplayConfig_DisplayUnits_MIN, _meshtastic_Config_DisplayConfig_OledType_MIN, _meshtastic_Config_DisplayConfig_DisplayMode_MIN, 0, 0, _meshtastic_Config_DisplayConfig_CompassOrientation_MIN, 0}
 #define meshtastic_Config_LoRaConfig_init_default {0, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, _meshtastic_Config_LoRaConfig_RegionCode_MIN, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}, 0, 0}
 #define meshtastic_Config_BluetoothConfig_init_default {0, _meshtastic_Config_BluetoothConfig_PairingMode_MIN, 0}
 #define meshtastic_Config_SecurityConfig_init_default {{0, {0}}, {0, {0}}, 0, {{0, {0}}, {0, {0}}, {0, {0}}}, 0, 0, 0, 0}
@@ -738,7 +753,7 @@ extern "C" {
 #define meshtastic_Config_PowerConfig_init_zero  {0, 0, 0, 0, 0, 0, 0, 0, 0}
 #define meshtastic_Config_NetworkConfig_init_zero {0, "", "", "", 0, _meshtastic_Config_NetworkConfig_AddressMode_MIN, false, meshtastic_Config_NetworkConfig_IpV4Config_init_zero, "", 0, 0}
 #define meshtastic_Config_NetworkConfig_IpV4Config_init_zero {0, 0, 0, 0}
-#define meshtastic_Config_DisplayConfig_init_zero {0, _meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat_MIN, 0, 0, 0, _meshtastic_Config_DisplayConfig_DisplayUnits_MIN, _meshtastic_Config_DisplayConfig_OledType_MIN, _meshtastic_Config_DisplayConfig_DisplayMode_MIN, 0, 0, _meshtastic_Config_DisplayConfig_CompassOrientation_MIN, 0, 0}
+#define meshtastic_Config_DisplayConfig_init_zero {0, _meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MIN, 0, 0, 0, _meshtastic_Config_DisplayConfig_DisplayUnits_MIN, _meshtastic_Config_DisplayConfig_OledType_MIN, _meshtastic_Config_DisplayConfig_DisplayMode_MIN, 0, 0, _meshtastic_Config_DisplayConfig_CompassOrientation_MIN, 0}
 #define meshtastic_Config_LoRaConfig_init_zero   {0, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, _meshtastic_Config_LoRaConfig_RegionCode_MIN, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}, 0, 0}
 #define meshtastic_Config_BluetoothConfig_init_zero {0, _meshtastic_Config_BluetoothConfig_PairingMode_MIN, 0}
 #define meshtastic_Config_SecurityConfig_init_zero {{0, {0}}, {0, {0}}, 0, {{0, {0}}, {0, {0}}, {0, {0}}}, 0, 0, 0, 0}
@@ -805,7 +820,6 @@ extern "C" {
 #define meshtastic_Config_DisplayConfig_wake_on_tap_or_motion_tag 10
 #define meshtastic_Config_DisplayConfig_compass_orientation_tag 11
 #define meshtastic_Config_DisplayConfig_use_12h_clock_tag 12
-#define meshtastic_Config_DisplayConfig_use_long_node_name_tag 13
 #define meshtastic_Config_LoRaConfig_use_preset_tag 1
 #define meshtastic_Config_LoRaConfig_modem_preset_tag 2
 #define meshtastic_Config_LoRaConfig_bandwidth_tag 3
@@ -951,8 +965,7 @@ X(a, STATIC,   SINGULAR, UENUM,    displaymode,       8) \
 X(a, STATIC,   SINGULAR, BOOL,     heading_bold,      9) \
 X(a, STATIC,   SINGULAR, BOOL,     wake_on_tap_or_motion,  10) \
 X(a, STATIC,   SINGULAR, UENUM,    compass_orientation,  11) \
-X(a, STATIC,   SINGULAR, BOOL,     use_12h_clock,    12) \
-X(a, STATIC,   SINGULAR, BOOL,     use_long_node_name,  13)
+X(a, STATIC,   SINGULAR, BOOL,     use_12h_clock,    12)
 #define meshtastic_Config_DisplayConfig_CALLBACK NULL
 #define meshtastic_Config_DisplayConfig_DEFAULT NULL
 
@@ -1030,7 +1043,7 @@ extern const pb_msgdesc_t meshtastic_Config_SessionkeyConfig_msg;
 #define MESHTASTIC_MESHTASTIC_CONFIG_PB_H_MAX_SIZE meshtastic_Config_size
 #define meshtastic_Config_BluetoothConfig_size   10
 #define meshtastic_Config_DeviceConfig_size      100
-#define meshtastic_Config_DisplayConfig_size     34
+#define meshtastic_Config_DisplayConfig_size     32
 #define meshtastic_Config_LoRaConfig_size        85
 #define meshtastic_Config_NetworkConfig_IpV4Config_size 20
 #define meshtastic_Config_NetworkConfig_size     204
diff --git a/src/mesh/generated/meshtastic/device_ui.pb.cpp b/src/mesh/generated/meshtastic/device_ui.pb.cpp
index 01940265f..2fc8d9461 100644
--- a/src/mesh/generated/meshtastic/device_ui.pb.cpp
+++ b/src/mesh/generated/meshtastic/device_ui.pb.cpp
@@ -28,5 +28,3 @@ PB_BIND(meshtastic_Map, meshtastic_Map, AUTO)
 
 
 
-
-
diff --git a/src/mesh/generated/meshtastic/device_ui.pb.h b/src/mesh/generated/meshtastic/device_ui.pb.h
index d9eb90773..8f693e570 100644
--- a/src/mesh/generated/meshtastic/device_ui.pb.h
+++ b/src/mesh/generated/meshtastic/device_ui.pb.h
@@ -74,32 +74,6 @@ typedef enum _meshtastic_Language {
     meshtastic_Language_TRADITIONAL_CHINESE = 31
 } meshtastic_Language;
 
-/* How the GPS coordinates are displayed on the OLED screen. */
-typedef enum _meshtastic_DeviceUIConfig_GpsCoordinateFormat {
-    /* GPS coordinates are displayed in the normal decimal degrees format:
- DD.DDDDDD DDD.DDDDDD */
-    meshtastic_DeviceUIConfig_GpsCoordinateFormat_DEC = 0,
-    /* GPS coordinates are displayed in the degrees minutes seconds format:
- DD°MM'SS"C DDD°MM'SS"C, where C is the compass point representing the locations quadrant */
-    meshtastic_DeviceUIConfig_GpsCoordinateFormat_DMS = 1,
-    /* Universal Transverse Mercator format:
- ZZB EEEEEE NNNNNNN, where Z is zone, B is band, E is easting, N is northing */
-    meshtastic_DeviceUIConfig_GpsCoordinateFormat_UTM = 2,
-    /* Military Grid Reference System format:
- ZZB CD EEEEE NNNNN, where Z is zone, B is band, C is the east 100k square, D is the north 100k square,
- E is easting, N is northing */
-    meshtastic_DeviceUIConfig_GpsCoordinateFormat_MGRS = 3,
-    /* Open Location Code (aka Plus Codes). */
-    meshtastic_DeviceUIConfig_GpsCoordinateFormat_OLC = 4,
-    /* Ordnance Survey Grid Reference (the National Grid System of the UK).
- Format: AB EEEEE NNNNN, where A is the east 100k square, B is the north 100k square,
- E is the easting, N is the northing */
-    meshtastic_DeviceUIConfig_GpsCoordinateFormat_OSGR = 5,
-    /* Maidenhead Locator System
- Described here: https://en.wikipedia.org/wiki/Maidenhead_Locator_System */
-    meshtastic_DeviceUIConfig_GpsCoordinateFormat_MLS = 6
-} meshtastic_DeviceUIConfig_GpsCoordinateFormat;
-
 /* Struct definitions */
 typedef struct _meshtastic_NodeFilter {
     /* Filter unknown nodes */
@@ -189,8 +163,6 @@ typedef struct _meshtastic_DeviceUIConfig {
     /* Clockface analog style
  true for analog clockface, false for digital clockface */
     bool is_clockface_analog;
-    /* How the GPS coordinates are formatted on the OLED screen. */
-    meshtastic_DeviceUIConfig_GpsCoordinateFormat gps_format;
 } meshtastic_DeviceUIConfig;
 
 
@@ -211,14 +183,9 @@ extern "C" {
 #define _meshtastic_Language_MAX meshtastic_Language_TRADITIONAL_CHINESE
 #define _meshtastic_Language_ARRAYSIZE ((meshtastic_Language)(meshtastic_Language_TRADITIONAL_CHINESE+1))
 
-#define _meshtastic_DeviceUIConfig_GpsCoordinateFormat_MIN meshtastic_DeviceUIConfig_GpsCoordinateFormat_DEC
-#define _meshtastic_DeviceUIConfig_GpsCoordinateFormat_MAX meshtastic_DeviceUIConfig_GpsCoordinateFormat_MLS
-#define _meshtastic_DeviceUIConfig_GpsCoordinateFormat_ARRAYSIZE ((meshtastic_DeviceUIConfig_GpsCoordinateFormat)(meshtastic_DeviceUIConfig_GpsCoordinateFormat_MLS+1))
-
 #define meshtastic_DeviceUIConfig_theme_ENUMTYPE meshtastic_Theme
 #define meshtastic_DeviceUIConfig_language_ENUMTYPE meshtastic_Language
 #define meshtastic_DeviceUIConfig_compass_mode_ENUMTYPE meshtastic_CompassMode
-#define meshtastic_DeviceUIConfig_gps_format_ENUMTYPE meshtastic_DeviceUIConfig_GpsCoordinateFormat
 
 
 
@@ -226,12 +193,12 @@ extern "C" {
 
 
 /* Initializer values for message structs */
-#define meshtastic_DeviceUIConfig_init_default   {0, 0, 0, 0, 0, 0, _meshtastic_Theme_MIN, 0, 0, 0, _meshtastic_Language_MIN, false, meshtastic_NodeFilter_init_default, false, meshtastic_NodeHighlight_init_default, {0, {0}}, false, meshtastic_Map_init_default, _meshtastic_CompassMode_MIN, 0, 0, _meshtastic_DeviceUIConfig_GpsCoordinateFormat_MIN}
+#define meshtastic_DeviceUIConfig_init_default   {0, 0, 0, 0, 0, 0, _meshtastic_Theme_MIN, 0, 0, 0, _meshtastic_Language_MIN, false, meshtastic_NodeFilter_init_default, false, meshtastic_NodeHighlight_init_default, {0, {0}}, false, meshtastic_Map_init_default, _meshtastic_CompassMode_MIN, 0, 0}
 #define meshtastic_NodeFilter_init_default       {0, 0, 0, 0, 0, "", 0}
 #define meshtastic_NodeHighlight_init_default    {0, 0, 0, 0, ""}
 #define meshtastic_GeoPoint_init_default         {0, 0, 0}
 #define meshtastic_Map_init_default              {false, meshtastic_GeoPoint_init_default, "", 0}
-#define meshtastic_DeviceUIConfig_init_zero      {0, 0, 0, 0, 0, 0, _meshtastic_Theme_MIN, 0, 0, 0, _meshtastic_Language_MIN, false, meshtastic_NodeFilter_init_zero, false, meshtastic_NodeHighlight_init_zero, {0, {0}}, false, meshtastic_Map_init_zero, _meshtastic_CompassMode_MIN, 0, 0, _meshtastic_DeviceUIConfig_GpsCoordinateFormat_MIN}
+#define meshtastic_DeviceUIConfig_init_zero      {0, 0, 0, 0, 0, 0, _meshtastic_Theme_MIN, 0, 0, 0, _meshtastic_Language_MIN, false, meshtastic_NodeFilter_init_zero, false, meshtastic_NodeHighlight_init_zero, {0, {0}}, false, meshtastic_Map_init_zero, _meshtastic_CompassMode_MIN, 0, 0}
 #define meshtastic_NodeFilter_init_zero          {0, 0, 0, 0, 0, "", 0}
 #define meshtastic_NodeHighlight_init_zero       {0, 0, 0, 0, ""}
 #define meshtastic_GeoPoint_init_zero            {0, 0, 0}
@@ -274,7 +241,6 @@ extern "C" {
 #define meshtastic_DeviceUIConfig_compass_mode_tag 16
 #define meshtastic_DeviceUIConfig_screen_rgb_color_tag 17
 #define meshtastic_DeviceUIConfig_is_clockface_analog_tag 18
-#define meshtastic_DeviceUIConfig_gps_format_tag 19
 
 /* Struct field encoding specification for nanopb */
 #define meshtastic_DeviceUIConfig_FIELDLIST(X, a) \
@@ -295,8 +261,7 @@ X(a, STATIC,   SINGULAR, BYTES,    calibration_data,  14) \
 X(a, STATIC,   OPTIONAL, MESSAGE,  map_data,         15) \
 X(a, STATIC,   SINGULAR, UENUM,    compass_mode,     16) \
 X(a, STATIC,   SINGULAR, UINT32,   screen_rgb_color,  17) \
-X(a, STATIC,   SINGULAR, BOOL,     is_clockface_analog,  18) \
-X(a, STATIC,   SINGULAR, UENUM,    gps_format,       19)
+X(a, STATIC,   SINGULAR, BOOL,     is_clockface_analog,  18)
 #define meshtastic_DeviceUIConfig_CALLBACK NULL
 #define meshtastic_DeviceUIConfig_DEFAULT NULL
 #define meshtastic_DeviceUIConfig_node_filter_MSGTYPE meshtastic_NodeFilter
@@ -353,7 +318,7 @@ extern const pb_msgdesc_t meshtastic_Map_msg;
 
 /* Maximum encoded size of messages (where known) */
 #define MESHTASTIC_MESHTASTIC_DEVICE_UI_PB_H_MAX_SIZE meshtastic_DeviceUIConfig_size
-#define meshtastic_DeviceUIConfig_size           204
+#define meshtastic_DeviceUIConfig_size           201
 #define meshtastic_GeoPoint_size                 33
 #define meshtastic_Map_size                      58
 #define meshtastic_NodeFilter_size               47
diff --git a/src/mesh/generated/meshtastic/deviceonly.pb.h b/src/mesh/generated/meshtastic/deviceonly.pb.h
index 148261fc8..9b6330596 100644
--- a/src/mesh/generated/meshtastic/deviceonly.pb.h
+++ b/src/mesh/generated/meshtastic/deviceonly.pb.h
@@ -94,9 +94,6 @@ typedef struct _meshtastic_NodeInfoLite {
     /* True if node is in our ignored list
  Persists between NodeDB internal clean ups */
     bool is_ignored;
-    /* True if node has been muted
- Persists between NodeDB internal clean ups */
-    bool is_muted;
     /* Last byte of the node number of the node that should be used as the next hop to reach this node. */
     uint8_t next_hop;
     /* Bitfield for storing booleans.
@@ -193,14 +190,14 @@ extern "C" {
 /* Initializer values for message structs */
 #define meshtastic_PositionLite_init_default     {0, 0, 0, 0, _meshtastic_Position_LocSource_MIN}
 #define meshtastic_UserLite_init_default         {{0}, "", "", _meshtastic_HardwareModel_MIN, 0, _meshtastic_Config_DeviceConfig_Role_MIN, {0, {0}}, false, 0}
-#define meshtastic_NodeInfoLite_init_default     {0, false, meshtastic_UserLite_init_default, false, meshtastic_PositionLite_init_default, 0, 0, false, meshtastic_DeviceMetrics_init_default, 0, 0, false, 0, 0, 0, 0, 0, 0}
+#define meshtastic_NodeInfoLite_init_default     {0, false, meshtastic_UserLite_init_default, false, meshtastic_PositionLite_init_default, 0, 0, false, meshtastic_DeviceMetrics_init_default, 0, 0, false, 0, 0, 0, 0, 0}
 #define meshtastic_DeviceState_init_default      {false, meshtastic_MyNodeInfo_init_default, false, meshtastic_User_init_default, 0, {meshtastic_MeshPacket_init_default}, false, meshtastic_MeshPacket_init_default, 0, 0, 0, false, meshtastic_MeshPacket_init_default, 0, {meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default}}
 #define meshtastic_NodeDatabase_init_default     {0, {0}}
 #define meshtastic_ChannelFile_init_default      {0, {meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default}, 0}
 #define meshtastic_BackupPreferences_init_default {0, 0, false, meshtastic_LocalConfig_init_default, false, meshtastic_LocalModuleConfig_init_default, false, meshtastic_ChannelFile_init_default, false, meshtastic_User_init_default}
 #define meshtastic_PositionLite_init_zero        {0, 0, 0, 0, _meshtastic_Position_LocSource_MIN}
 #define meshtastic_UserLite_init_zero            {{0}, "", "", _meshtastic_HardwareModel_MIN, 0, _meshtastic_Config_DeviceConfig_Role_MIN, {0, {0}}, false, 0}
-#define meshtastic_NodeInfoLite_init_zero        {0, false, meshtastic_UserLite_init_zero, false, meshtastic_PositionLite_init_zero, 0, 0, false, meshtastic_DeviceMetrics_init_zero, 0, 0, false, 0, 0, 0, 0, 0, 0}
+#define meshtastic_NodeInfoLite_init_zero        {0, false, meshtastic_UserLite_init_zero, false, meshtastic_PositionLite_init_zero, 0, 0, false, meshtastic_DeviceMetrics_init_zero, 0, 0, false, 0, 0, 0, 0, 0}
 #define meshtastic_DeviceState_init_zero         {false, meshtastic_MyNodeInfo_init_zero, false, meshtastic_User_init_zero, 0, {meshtastic_MeshPacket_init_zero}, false, meshtastic_MeshPacket_init_zero, 0, 0, 0, false, meshtastic_MeshPacket_init_zero, 0, {meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero}}
 #define meshtastic_NodeDatabase_init_zero        {0, {0}}
 #define meshtastic_ChannelFile_init_zero         {0, {meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero}, 0}
@@ -231,9 +228,8 @@ extern "C" {
 #define meshtastic_NodeInfoLite_hops_away_tag    9
 #define meshtastic_NodeInfoLite_is_favorite_tag  10
 #define meshtastic_NodeInfoLite_is_ignored_tag   11
-#define meshtastic_NodeInfoLite_is_muted_tag     12
-#define meshtastic_NodeInfoLite_next_hop_tag     13
-#define meshtastic_NodeInfoLite_bitfield_tag     14
+#define meshtastic_NodeInfoLite_next_hop_tag     12
+#define meshtastic_NodeInfoLite_bitfield_tag     13
 #define meshtastic_DeviceState_my_node_tag       2
 #define meshtastic_DeviceState_owner_tag         3
 #define meshtastic_DeviceState_receive_queue_tag 5
@@ -288,9 +284,8 @@ X(a, STATIC,   SINGULAR, BOOL,     via_mqtt,          8) \
 X(a, STATIC,   OPTIONAL, UINT32,   hops_away,         9) \
 X(a, STATIC,   SINGULAR, BOOL,     is_favorite,      10) \
 X(a, STATIC,   SINGULAR, BOOL,     is_ignored,       11) \
-X(a, STATIC,   SINGULAR, BOOL,     is_muted,         12) \
-X(a, STATIC,   SINGULAR, UINT32,   next_hop,         13) \
-X(a, STATIC,   SINGULAR, UINT32,   bitfield,         14)
+X(a, STATIC,   SINGULAR, UINT32,   next_hop,         12) \
+X(a, STATIC,   SINGULAR, UINT32,   bitfield,         13)
 #define meshtastic_NodeInfoLite_CALLBACK NULL
 #define meshtastic_NodeInfoLite_DEFAULT NULL
 #define meshtastic_NodeInfoLite_user_MSGTYPE meshtastic_UserLite
@@ -365,10 +360,10 @@ 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        2289
-#define meshtastic_ChannelFile_size              734
+#define meshtastic_BackupPreferences_size        2273
+#define meshtastic_ChannelFile_size              718
 #define meshtastic_DeviceState_size              1737
-#define meshtastic_NodeInfoLite_size             198
+#define meshtastic_NodeInfoLite_size             196
 #define meshtastic_PositionLite_size             28
 #define meshtastic_UserLite_size                 98
 
diff --git a/src/mesh/generated/meshtastic/localonly.pb.h b/src/mesh/generated/meshtastic/localonly.pb.h
index 3ab6f02c1..da224fb94 100644
--- a/src/mesh/generated/meshtastic/localonly.pb.h
+++ b/src/mesh/generated/meshtastic/localonly.pb.h
@@ -187,8 +187,8 @@ 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              749
-#define meshtastic_LocalModuleConfig_size        673
+#define meshtastic_LocalConfig_size              747
+#define meshtastic_LocalModuleConfig_size        671
 
 #ifdef __cplusplus
 } /* extern "C" */
diff --git a/src/mesh/generated/meshtastic/mesh.pb.h b/src/mesh/generated/meshtastic/mesh.pb.h
index 6292ce070..2a4e77870 100644
--- a/src/mesh/generated/meshtastic/mesh.pb.h
+++ b/src/mesh/generated/meshtastic/mesh.pb.h
@@ -259,8 +259,8 @@ typedef enum _meshtastic_HardwareModel {
     meshtastic_HardwareModel_T_DECK_PRO = 102,
     /* Lilygo TLora Pager */
     meshtastic_HardwareModel_T_LORA_PAGER = 103,
-    /* M5Stack Reserved */
-    meshtastic_HardwareModel_M5STACK_RESERVED = 104, /* 0x68 */
+    /* GAT562 Mesh Trial Tracker */
+    meshtastic_HardwareModel_GAT562_MESH_TRIAL_TRACKER = 104,
     /* RAKwireless WisMesh Tag */
     meshtastic_HardwareModel_WISMESH_TAG = 105,
     /* RAKwireless WisBlock Core RAK3312 https://docs.rakwireless.com/product-categories/wisduo/rak3112-module/overview/ */
@@ -274,10 +274,6 @@ typedef enum _meshtastic_HardwareModel {
     meshtastic_HardwareModel_T_ECHO_LITE = 109,
     /* New Heltec LoRA32 with ESP32-S3 CPU */
     meshtastic_HardwareModel_HELTEC_V4 = 110,
-    /* M5Stack C6L */
-    meshtastic_HardwareModel_M5STACK_C6L = 111,
-    /* M5Stack Cardputer Adv */
-    meshtastic_HardwareModel_M5STACK_CARDPUTER_ADV = 112,
     /* ------------------------------------------------------------------------------------------------------------------------------------------
  Reserved ID For developing private Ports. These will show up in live traffic sparsely, so we can use a high number. Keep it within 8 bits.
  ------------------------------------------------------------------------------------------------------------------------------------------ */
diff --git a/src/mesh/generated/meshtastic/module_config.pb.h b/src/mesh/generated/meshtastic/module_config.pb.h
index 47d3b5baa..16c4c230c 100644
--- a/src/mesh/generated/meshtastic/module_config.pb.h
+++ b/src/mesh/generated/meshtastic/module_config.pb.h
@@ -356,9 +356,6 @@ typedef struct _meshtastic_ModuleConfig_TelemetryConfig {
     uint32_t health_update_interval;
     /* Enable/Disable the health telemetry module on-device display */
     bool health_screen_enabled;
-    /* Enable/Disable the device telemetry module to send metrics to the mesh
- Note: We will still send telemtry to the connected phone / client every minute over the API */
-    bool device_telemetry_enabled;
 } meshtastic_ModuleConfig_TelemetryConfig;
 
 /* Canned Messages Module Config */
@@ -526,7 +523,7 @@ extern "C" {
 #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, 0}
-#define meshtastic_ModuleConfig_TelemetryConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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}
 #define meshtastic_RemoteHardwarePin_init_default {0, "", _meshtastic_RemoteHardwarePinType_MIN}
@@ -542,7 +539,7 @@ extern "C" {
 #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, 0}
-#define meshtastic_ModuleConfig_TelemetryConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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}
 #define meshtastic_RemoteHardwarePin_init_zero   {0, "", _meshtastic_RemoteHardwarePinType_MIN}
@@ -630,7 +627,6 @@ extern "C" {
 #define meshtastic_ModuleConfig_TelemetryConfig_health_measurement_enabled_tag 11
 #define meshtastic_ModuleConfig_TelemetryConfig_health_update_interval_tag 12
 #define meshtastic_ModuleConfig_TelemetryConfig_health_screen_enabled_tag 13
-#define meshtastic_ModuleConfig_TelemetryConfig_device_telemetry_enabled_tag 14
 #define meshtastic_ModuleConfig_CannedMessageConfig_rotary1_enabled_tag 1
 #define meshtastic_ModuleConfig_CannedMessageConfig_inputbroker_pin_a_tag 2
 #define meshtastic_ModuleConfig_CannedMessageConfig_inputbroker_pin_b_tag 3
@@ -829,8 +825,7 @@ X(a, STATIC,   SINGULAR, UINT32,   power_update_interval,   9) \
 X(a, STATIC,   SINGULAR, BOOL,     power_screen_enabled,  10) \
 X(a, STATIC,   SINGULAR, BOOL,     health_measurement_enabled,  11) \
 X(a, STATIC,   SINGULAR, UINT32,   health_update_interval,  12) \
-X(a, STATIC,   SINGULAR, BOOL,     health_screen_enabled,  13) \
-X(a, STATIC,   SINGULAR, BOOL,     device_telemetry_enabled,  14)
+X(a, STATIC,   SINGULAR, BOOL,     health_screen_enabled,  13)
 #define meshtastic_ModuleConfig_TelemetryConfig_CALLBACK NULL
 #define meshtastic_ModuleConfig_TelemetryConfig_DEFAULT NULL
 
@@ -915,7 +910,7 @@ extern const pb_msgdesc_t meshtastic_RemoteHardwarePin_msg;
 #define meshtastic_ModuleConfig_RemoteHardwareConfig_size 96
 #define meshtastic_ModuleConfig_SerialConfig_size 28
 #define meshtastic_ModuleConfig_StoreForwardConfig_size 24
-#define meshtastic_ModuleConfig_TelemetryConfig_size 48
+#define meshtastic_ModuleConfig_TelemetryConfig_size 46
 #define meshtastic_ModuleConfig_size             227
 #define meshtastic_RemoteHardwarePin_size        21
 

From 0db2e40ee356c02203b5e91963d4b5cd5ac14032 Mon Sep 17 00:00:00 2001
From: ford-jones 
Date: Mon, 22 Sep 2025 15:05:53 +1200
Subject: [PATCH 317/691] Use latest protos

---
 src/mesh/generated/meshtastic/config.pb.h     | 49 +++++++------------
 .../generated/meshtastic/device_ui.pb.cpp     |  2 +
 src/mesh/generated/meshtastic/device_ui.pb.h  | 43 ++++++++++++++--
 src/mesh/generated/meshtastic/deviceonly.pb.h |  2 +-
 src/mesh/generated/meshtastic/localonly.pb.h  |  4 +-
 src/mesh/generated/meshtastic/mesh.pb.h       |  8 ++-
 .../generated/meshtastic/module_config.pb.h   | 13 +++--
 7 files changed, 77 insertions(+), 44 deletions(-)

diff --git a/src/mesh/generated/meshtastic/config.pb.h b/src/mesh/generated/meshtastic/config.pb.h
index 59e55db3f..0453ecad2 100644
--- a/src/mesh/generated/meshtastic/config.pb.h
+++ b/src/mesh/generated/meshtastic/config.pb.h
@@ -173,28 +173,10 @@ typedef enum _meshtastic_Config_NetworkConfig_ProtocolFlags {
     meshtastic_Config_NetworkConfig_ProtocolFlags_UDP_BROADCAST = 1
 } meshtastic_Config_NetworkConfig_ProtocolFlags;
 
-/* How the GPS coordinates are displayed on the OLED screen. */
-typedef enum _meshtastic_Config_DisplayConfig_GpsCoordinateFormat {
-    /* GPS coordinates are displayed in the normal decimal degrees format:
- DD.DDDDDD DDD.DDDDDD */
-    meshtastic_Config_DisplayConfig_GpsCoordinateFormat_DEC = 0,
-    /* GPS coordinates are displayed in the degrees minutes seconds format:
- DD°MM'SS"C DDD°MM'SS"C, where C is the compass point representing the locations quadrant */
-    meshtastic_Config_DisplayConfig_GpsCoordinateFormat_DMS = 1,
-    /* Universal Transverse Mercator format:
- ZZB EEEEEE NNNNNNN, where Z is zone, B is band, E is easting, N is northing */
-    meshtastic_Config_DisplayConfig_GpsCoordinateFormat_UTM = 2,
-    /* Military Grid Reference System format:
- ZZB CD EEEEE NNNNN, where Z is zone, B is band, C is the east 100k square, D is the north 100k square,
- E is easting, N is northing */
-    meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MGRS = 3,
-    /* Open Location Code (aka Plus Codes). */
-    meshtastic_Config_DisplayConfig_GpsCoordinateFormat_OLC = 4,
-    /* Ordnance Survey Grid Reference (the National Grid System of the UK).
- Format: AB EEEEE NNNNN, where A is the east 100k square, B is the north 100k square,
- E is the easting, N is the northing */
-    meshtastic_Config_DisplayConfig_GpsCoordinateFormat_OSGR = 5
-} meshtastic_Config_DisplayConfig_GpsCoordinateFormat;
+/* Deprecated in 2.7.4: Unused */
+typedef enum _meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat {
+    meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat_UNUSED = 0
+} meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat;
 
 /* Unit display preference */
 typedef enum _meshtastic_Config_DisplayConfig_DisplayUnits {
@@ -491,7 +473,7 @@ typedef struct _meshtastic_Config_DisplayConfig {
     uint32_t screen_on_secs;
     /* Deprecated in 2.7.4: Unused
  How the GPS coordinates are formatted on the OLED screen. */
-    meshtastic_Config_DisplayConfig_GpsCoordinateFormat gps_format;
+    meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat gps_format;
     /* Automatically toggles to the next page on the screen like a carousel, based the specified interval in seconds.
  Potentially useful for devices without user buttons. */
     uint32_t auto_screen_carousel_secs;
@@ -515,6 +497,9 @@ typedef struct _meshtastic_Config_DisplayConfig {
     /* If false (default), the device will display the time in 24-hour format on screen.
  If true, the device will display the time in 12-hour format on screen. */
     bool use_12h_clock;
+    /* If false (default), the device will use short names for various display screens.
+ If true, node names will show in long format */
+    bool use_long_node_name;
 } meshtastic_Config_DisplayConfig;
 
 /* Lora Config */
@@ -678,9 +663,9 @@ extern "C" {
 #define _meshtastic_Config_NetworkConfig_ProtocolFlags_MAX meshtastic_Config_NetworkConfig_ProtocolFlags_UDP_BROADCAST
 #define _meshtastic_Config_NetworkConfig_ProtocolFlags_ARRAYSIZE ((meshtastic_Config_NetworkConfig_ProtocolFlags)(meshtastic_Config_NetworkConfig_ProtocolFlags_UDP_BROADCAST+1))
 
-#define _meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MIN meshtastic_Config_DisplayConfig_GpsCoordinateFormat_DEC
-#define _meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MAX meshtastic_Config_DisplayConfig_GpsCoordinateFormat_OSGR
-#define _meshtastic_Config_DisplayConfig_GpsCoordinateFormat_ARRAYSIZE ((meshtastic_Config_DisplayConfig_GpsCoordinateFormat)(meshtastic_Config_DisplayConfig_GpsCoordinateFormat_OSGR+1))
+#define _meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat_MIN meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat_UNUSED
+#define _meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat_MAX meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat_UNUSED
+#define _meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat_ARRAYSIZE ((meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat)(meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat_UNUSED+1))
 
 #define _meshtastic_Config_DisplayConfig_DisplayUnits_MIN meshtastic_Config_DisplayConfig_DisplayUnits_METRIC
 #define _meshtastic_Config_DisplayConfig_DisplayUnits_MAX meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL
@@ -721,7 +706,7 @@ extern "C" {
 #define meshtastic_Config_NetworkConfig_address_mode_ENUMTYPE meshtastic_Config_NetworkConfig_AddressMode
 
 
-#define meshtastic_Config_DisplayConfig_gps_format_ENUMTYPE meshtastic_Config_DisplayConfig_GpsCoordinateFormat
+#define meshtastic_Config_DisplayConfig_gps_format_ENUMTYPE meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat
 #define meshtastic_Config_DisplayConfig_units_ENUMTYPE meshtastic_Config_DisplayConfig_DisplayUnits
 #define meshtastic_Config_DisplayConfig_oled_ENUMTYPE meshtastic_Config_DisplayConfig_OledType
 #define meshtastic_Config_DisplayConfig_displaymode_ENUMTYPE meshtastic_Config_DisplayConfig_DisplayMode
@@ -742,7 +727,7 @@ extern "C" {
 #define meshtastic_Config_PowerConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0}
 #define meshtastic_Config_NetworkConfig_init_default {0, "", "", "", 0, _meshtastic_Config_NetworkConfig_AddressMode_MIN, false, meshtastic_Config_NetworkConfig_IpV4Config_init_default, "", 0, 0}
 #define meshtastic_Config_NetworkConfig_IpV4Config_init_default {0, 0, 0, 0}
-#define meshtastic_Config_DisplayConfig_init_default {0, _meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MIN, 0, 0, 0, _meshtastic_Config_DisplayConfig_DisplayUnits_MIN, _meshtastic_Config_DisplayConfig_OledType_MIN, _meshtastic_Config_DisplayConfig_DisplayMode_MIN, 0, 0, _meshtastic_Config_DisplayConfig_CompassOrientation_MIN, 0}
+#define meshtastic_Config_DisplayConfig_init_default {0, _meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat_MIN, 0, 0, 0, _meshtastic_Config_DisplayConfig_DisplayUnits_MIN, _meshtastic_Config_DisplayConfig_OledType_MIN, _meshtastic_Config_DisplayConfig_DisplayMode_MIN, 0, 0, _meshtastic_Config_DisplayConfig_CompassOrientation_MIN, 0, 0}
 #define meshtastic_Config_LoRaConfig_init_default {0, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, _meshtastic_Config_LoRaConfig_RegionCode_MIN, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}, 0, 0}
 #define meshtastic_Config_BluetoothConfig_init_default {0, _meshtastic_Config_BluetoothConfig_PairingMode_MIN, 0}
 #define meshtastic_Config_SecurityConfig_init_default {{0, {0}}, {0, {0}}, 0, {{0, {0}}, {0, {0}}, {0, {0}}}, 0, 0, 0, 0}
@@ -753,7 +738,7 @@ extern "C" {
 #define meshtastic_Config_PowerConfig_init_zero  {0, 0, 0, 0, 0, 0, 0, 0, 0}
 #define meshtastic_Config_NetworkConfig_init_zero {0, "", "", "", 0, _meshtastic_Config_NetworkConfig_AddressMode_MIN, false, meshtastic_Config_NetworkConfig_IpV4Config_init_zero, "", 0, 0}
 #define meshtastic_Config_NetworkConfig_IpV4Config_init_zero {0, 0, 0, 0}
-#define meshtastic_Config_DisplayConfig_init_zero {0, _meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MIN, 0, 0, 0, _meshtastic_Config_DisplayConfig_DisplayUnits_MIN, _meshtastic_Config_DisplayConfig_OledType_MIN, _meshtastic_Config_DisplayConfig_DisplayMode_MIN, 0, 0, _meshtastic_Config_DisplayConfig_CompassOrientation_MIN, 0}
+#define meshtastic_Config_DisplayConfig_init_zero {0, _meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat_MIN, 0, 0, 0, _meshtastic_Config_DisplayConfig_DisplayUnits_MIN, _meshtastic_Config_DisplayConfig_OledType_MIN, _meshtastic_Config_DisplayConfig_DisplayMode_MIN, 0, 0, _meshtastic_Config_DisplayConfig_CompassOrientation_MIN, 0, 0}
 #define meshtastic_Config_LoRaConfig_init_zero   {0, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, _meshtastic_Config_LoRaConfig_RegionCode_MIN, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}, 0, 0}
 #define meshtastic_Config_BluetoothConfig_init_zero {0, _meshtastic_Config_BluetoothConfig_PairingMode_MIN, 0}
 #define meshtastic_Config_SecurityConfig_init_zero {{0, {0}}, {0, {0}}, 0, {{0, {0}}, {0, {0}}, {0, {0}}}, 0, 0, 0, 0}
@@ -820,6 +805,7 @@ extern "C" {
 #define meshtastic_Config_DisplayConfig_wake_on_tap_or_motion_tag 10
 #define meshtastic_Config_DisplayConfig_compass_orientation_tag 11
 #define meshtastic_Config_DisplayConfig_use_12h_clock_tag 12
+#define meshtastic_Config_DisplayConfig_use_long_node_name_tag 13
 #define meshtastic_Config_LoRaConfig_use_preset_tag 1
 #define meshtastic_Config_LoRaConfig_modem_preset_tag 2
 #define meshtastic_Config_LoRaConfig_bandwidth_tag 3
@@ -965,7 +951,8 @@ X(a, STATIC,   SINGULAR, UENUM,    displaymode,       8) \
 X(a, STATIC,   SINGULAR, BOOL,     heading_bold,      9) \
 X(a, STATIC,   SINGULAR, BOOL,     wake_on_tap_or_motion,  10) \
 X(a, STATIC,   SINGULAR, UENUM,    compass_orientation,  11) \
-X(a, STATIC,   SINGULAR, BOOL,     use_12h_clock,    12)
+X(a, STATIC,   SINGULAR, BOOL,     use_12h_clock,    12) \
+X(a, STATIC,   SINGULAR, BOOL,     use_long_node_name,  13)
 #define meshtastic_Config_DisplayConfig_CALLBACK NULL
 #define meshtastic_Config_DisplayConfig_DEFAULT NULL
 
@@ -1043,7 +1030,7 @@ extern const pb_msgdesc_t meshtastic_Config_SessionkeyConfig_msg;
 #define MESHTASTIC_MESHTASTIC_CONFIG_PB_H_MAX_SIZE meshtastic_Config_size
 #define meshtastic_Config_BluetoothConfig_size   10
 #define meshtastic_Config_DeviceConfig_size      100
-#define meshtastic_Config_DisplayConfig_size     32
+#define meshtastic_Config_DisplayConfig_size     34
 #define meshtastic_Config_LoRaConfig_size        85
 #define meshtastic_Config_NetworkConfig_IpV4Config_size 20
 #define meshtastic_Config_NetworkConfig_size     204
diff --git a/src/mesh/generated/meshtastic/device_ui.pb.cpp b/src/mesh/generated/meshtastic/device_ui.pb.cpp
index 2fc8d9461..01940265f 100644
--- a/src/mesh/generated/meshtastic/device_ui.pb.cpp
+++ b/src/mesh/generated/meshtastic/device_ui.pb.cpp
@@ -28,3 +28,5 @@ PB_BIND(meshtastic_Map, meshtastic_Map, AUTO)
 
 
 
+
+
diff --git a/src/mesh/generated/meshtastic/device_ui.pb.h b/src/mesh/generated/meshtastic/device_ui.pb.h
index 8f693e570..d9eb90773 100644
--- a/src/mesh/generated/meshtastic/device_ui.pb.h
+++ b/src/mesh/generated/meshtastic/device_ui.pb.h
@@ -74,6 +74,32 @@ typedef enum _meshtastic_Language {
     meshtastic_Language_TRADITIONAL_CHINESE = 31
 } meshtastic_Language;
 
+/* How the GPS coordinates are displayed on the OLED screen. */
+typedef enum _meshtastic_DeviceUIConfig_GpsCoordinateFormat {
+    /* GPS coordinates are displayed in the normal decimal degrees format:
+ DD.DDDDDD DDD.DDDDDD */
+    meshtastic_DeviceUIConfig_GpsCoordinateFormat_DEC = 0,
+    /* GPS coordinates are displayed in the degrees minutes seconds format:
+ DD°MM'SS"C DDD°MM'SS"C, where C is the compass point representing the locations quadrant */
+    meshtastic_DeviceUIConfig_GpsCoordinateFormat_DMS = 1,
+    /* Universal Transverse Mercator format:
+ ZZB EEEEEE NNNNNNN, where Z is zone, B is band, E is easting, N is northing */
+    meshtastic_DeviceUIConfig_GpsCoordinateFormat_UTM = 2,
+    /* Military Grid Reference System format:
+ ZZB CD EEEEE NNNNN, where Z is zone, B is band, C is the east 100k square, D is the north 100k square,
+ E is easting, N is northing */
+    meshtastic_DeviceUIConfig_GpsCoordinateFormat_MGRS = 3,
+    /* Open Location Code (aka Plus Codes). */
+    meshtastic_DeviceUIConfig_GpsCoordinateFormat_OLC = 4,
+    /* Ordnance Survey Grid Reference (the National Grid System of the UK).
+ Format: AB EEEEE NNNNN, where A is the east 100k square, B is the north 100k square,
+ E is the easting, N is the northing */
+    meshtastic_DeviceUIConfig_GpsCoordinateFormat_OSGR = 5,
+    /* Maidenhead Locator System
+ Described here: https://en.wikipedia.org/wiki/Maidenhead_Locator_System */
+    meshtastic_DeviceUIConfig_GpsCoordinateFormat_MLS = 6
+} meshtastic_DeviceUIConfig_GpsCoordinateFormat;
+
 /* Struct definitions */
 typedef struct _meshtastic_NodeFilter {
     /* Filter unknown nodes */
@@ -163,6 +189,8 @@ typedef struct _meshtastic_DeviceUIConfig {
     /* Clockface analog style
  true for analog clockface, false for digital clockface */
     bool is_clockface_analog;
+    /* How the GPS coordinates are formatted on the OLED screen. */
+    meshtastic_DeviceUIConfig_GpsCoordinateFormat gps_format;
 } meshtastic_DeviceUIConfig;
 
 
@@ -183,9 +211,14 @@ extern "C" {
 #define _meshtastic_Language_MAX meshtastic_Language_TRADITIONAL_CHINESE
 #define _meshtastic_Language_ARRAYSIZE ((meshtastic_Language)(meshtastic_Language_TRADITIONAL_CHINESE+1))
 
+#define _meshtastic_DeviceUIConfig_GpsCoordinateFormat_MIN meshtastic_DeviceUIConfig_GpsCoordinateFormat_DEC
+#define _meshtastic_DeviceUIConfig_GpsCoordinateFormat_MAX meshtastic_DeviceUIConfig_GpsCoordinateFormat_MLS
+#define _meshtastic_DeviceUIConfig_GpsCoordinateFormat_ARRAYSIZE ((meshtastic_DeviceUIConfig_GpsCoordinateFormat)(meshtastic_DeviceUIConfig_GpsCoordinateFormat_MLS+1))
+
 #define meshtastic_DeviceUIConfig_theme_ENUMTYPE meshtastic_Theme
 #define meshtastic_DeviceUIConfig_language_ENUMTYPE meshtastic_Language
 #define meshtastic_DeviceUIConfig_compass_mode_ENUMTYPE meshtastic_CompassMode
+#define meshtastic_DeviceUIConfig_gps_format_ENUMTYPE meshtastic_DeviceUIConfig_GpsCoordinateFormat
 
 
 
@@ -193,12 +226,12 @@ extern "C" {
 
 
 /* Initializer values for message structs */
-#define meshtastic_DeviceUIConfig_init_default   {0, 0, 0, 0, 0, 0, _meshtastic_Theme_MIN, 0, 0, 0, _meshtastic_Language_MIN, false, meshtastic_NodeFilter_init_default, false, meshtastic_NodeHighlight_init_default, {0, {0}}, false, meshtastic_Map_init_default, _meshtastic_CompassMode_MIN, 0, 0}
+#define meshtastic_DeviceUIConfig_init_default   {0, 0, 0, 0, 0, 0, _meshtastic_Theme_MIN, 0, 0, 0, _meshtastic_Language_MIN, false, meshtastic_NodeFilter_init_default, false, meshtastic_NodeHighlight_init_default, {0, {0}}, false, meshtastic_Map_init_default, _meshtastic_CompassMode_MIN, 0, 0, _meshtastic_DeviceUIConfig_GpsCoordinateFormat_MIN}
 #define meshtastic_NodeFilter_init_default       {0, 0, 0, 0, 0, "", 0}
 #define meshtastic_NodeHighlight_init_default    {0, 0, 0, 0, ""}
 #define meshtastic_GeoPoint_init_default         {0, 0, 0}
 #define meshtastic_Map_init_default              {false, meshtastic_GeoPoint_init_default, "", 0}
-#define meshtastic_DeviceUIConfig_init_zero      {0, 0, 0, 0, 0, 0, _meshtastic_Theme_MIN, 0, 0, 0, _meshtastic_Language_MIN, false, meshtastic_NodeFilter_init_zero, false, meshtastic_NodeHighlight_init_zero, {0, {0}}, false, meshtastic_Map_init_zero, _meshtastic_CompassMode_MIN, 0, 0}
+#define meshtastic_DeviceUIConfig_init_zero      {0, 0, 0, 0, 0, 0, _meshtastic_Theme_MIN, 0, 0, 0, _meshtastic_Language_MIN, false, meshtastic_NodeFilter_init_zero, false, meshtastic_NodeHighlight_init_zero, {0, {0}}, false, meshtastic_Map_init_zero, _meshtastic_CompassMode_MIN, 0, 0, _meshtastic_DeviceUIConfig_GpsCoordinateFormat_MIN}
 #define meshtastic_NodeFilter_init_zero          {0, 0, 0, 0, 0, "", 0}
 #define meshtastic_NodeHighlight_init_zero       {0, 0, 0, 0, ""}
 #define meshtastic_GeoPoint_init_zero            {0, 0, 0}
@@ -241,6 +274,7 @@ extern "C" {
 #define meshtastic_DeviceUIConfig_compass_mode_tag 16
 #define meshtastic_DeviceUIConfig_screen_rgb_color_tag 17
 #define meshtastic_DeviceUIConfig_is_clockface_analog_tag 18
+#define meshtastic_DeviceUIConfig_gps_format_tag 19
 
 /* Struct field encoding specification for nanopb */
 #define meshtastic_DeviceUIConfig_FIELDLIST(X, a) \
@@ -261,7 +295,8 @@ X(a, STATIC,   SINGULAR, BYTES,    calibration_data,  14) \
 X(a, STATIC,   OPTIONAL, MESSAGE,  map_data,         15) \
 X(a, STATIC,   SINGULAR, UENUM,    compass_mode,     16) \
 X(a, STATIC,   SINGULAR, UINT32,   screen_rgb_color,  17) \
-X(a, STATIC,   SINGULAR, BOOL,     is_clockface_analog,  18)
+X(a, STATIC,   SINGULAR, BOOL,     is_clockface_analog,  18) \
+X(a, STATIC,   SINGULAR, UENUM,    gps_format,       19)
 #define meshtastic_DeviceUIConfig_CALLBACK NULL
 #define meshtastic_DeviceUIConfig_DEFAULT NULL
 #define meshtastic_DeviceUIConfig_node_filter_MSGTYPE meshtastic_NodeFilter
@@ -318,7 +353,7 @@ extern const pb_msgdesc_t meshtastic_Map_msg;
 
 /* Maximum encoded size of messages (where known) */
 #define MESHTASTIC_MESHTASTIC_DEVICE_UI_PB_H_MAX_SIZE meshtastic_DeviceUIConfig_size
-#define meshtastic_DeviceUIConfig_size           201
+#define meshtastic_DeviceUIConfig_size           204
 #define meshtastic_GeoPoint_size                 33
 #define meshtastic_Map_size                      58
 #define meshtastic_NodeFilter_size               47
diff --git a/src/mesh/generated/meshtastic/deviceonly.pb.h b/src/mesh/generated/meshtastic/deviceonly.pb.h
index 9b6330596..7fab82ff7 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        2273
+#define meshtastic_BackupPreferences_size        2277
 #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 da224fb94..3ab6f02c1 100644
--- a/src/mesh/generated/meshtastic/localonly.pb.h
+++ b/src/mesh/generated/meshtastic/localonly.pb.h
@@ -187,8 +187,8 @@ 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        671
+#define meshtastic_LocalConfig_size              749
+#define meshtastic_LocalModuleConfig_size        673
 
 #ifdef __cplusplus
 } /* extern "C" */
diff --git a/src/mesh/generated/meshtastic/mesh.pb.h b/src/mesh/generated/meshtastic/mesh.pb.h
index 2a4e77870..6292ce070 100644
--- a/src/mesh/generated/meshtastic/mesh.pb.h
+++ b/src/mesh/generated/meshtastic/mesh.pb.h
@@ -259,8 +259,8 @@ typedef enum _meshtastic_HardwareModel {
     meshtastic_HardwareModel_T_DECK_PRO = 102,
     /* Lilygo TLora Pager */
     meshtastic_HardwareModel_T_LORA_PAGER = 103,
-    /* GAT562 Mesh Trial Tracker */
-    meshtastic_HardwareModel_GAT562_MESH_TRIAL_TRACKER = 104,
+    /* M5Stack Reserved */
+    meshtastic_HardwareModel_M5STACK_RESERVED = 104, /* 0x68 */
     /* RAKwireless WisMesh Tag */
     meshtastic_HardwareModel_WISMESH_TAG = 105,
     /* RAKwireless WisBlock Core RAK3312 https://docs.rakwireless.com/product-categories/wisduo/rak3112-module/overview/ */
@@ -274,6 +274,10 @@ typedef enum _meshtastic_HardwareModel {
     meshtastic_HardwareModel_T_ECHO_LITE = 109,
     /* New Heltec LoRA32 with ESP32-S3 CPU */
     meshtastic_HardwareModel_HELTEC_V4 = 110,
+    /* M5Stack C6L */
+    meshtastic_HardwareModel_M5STACK_C6L = 111,
+    /* M5Stack Cardputer Adv */
+    meshtastic_HardwareModel_M5STACK_CARDPUTER_ADV = 112,
     /* ------------------------------------------------------------------------------------------------------------------------------------------
  Reserved ID For developing private Ports. These will show up in live traffic sparsely, so we can use a high number. Keep it within 8 bits.
  ------------------------------------------------------------------------------------------------------------------------------------------ */
diff --git a/src/mesh/generated/meshtastic/module_config.pb.h b/src/mesh/generated/meshtastic/module_config.pb.h
index 16c4c230c..47d3b5baa 100644
--- a/src/mesh/generated/meshtastic/module_config.pb.h
+++ b/src/mesh/generated/meshtastic/module_config.pb.h
@@ -356,6 +356,9 @@ typedef struct _meshtastic_ModuleConfig_TelemetryConfig {
     uint32_t health_update_interval;
     /* Enable/Disable the health telemetry module on-device display */
     bool health_screen_enabled;
+    /* Enable/Disable the device telemetry module to send metrics to the mesh
+ Note: We will still send telemtry to the connected phone / client every minute over the API */
+    bool device_telemetry_enabled;
 } meshtastic_ModuleConfig_TelemetryConfig;
 
 /* Canned Messages Module Config */
@@ -523,7 +526,7 @@ extern "C" {
 #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, 0}
-#define meshtastic_ModuleConfig_TelemetryConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
+#define meshtastic_ModuleConfig_TelemetryConfig_init_default {0, 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}
 #define meshtastic_RemoteHardwarePin_init_default {0, "", _meshtastic_RemoteHardwarePinType_MIN}
@@ -539,7 +542,7 @@ extern "C" {
 #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, 0}
-#define meshtastic_ModuleConfig_TelemetryConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
+#define meshtastic_ModuleConfig_TelemetryConfig_init_zero {0, 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}
 #define meshtastic_RemoteHardwarePin_init_zero   {0, "", _meshtastic_RemoteHardwarePinType_MIN}
@@ -627,6 +630,7 @@ extern "C" {
 #define meshtastic_ModuleConfig_TelemetryConfig_health_measurement_enabled_tag 11
 #define meshtastic_ModuleConfig_TelemetryConfig_health_update_interval_tag 12
 #define meshtastic_ModuleConfig_TelemetryConfig_health_screen_enabled_tag 13
+#define meshtastic_ModuleConfig_TelemetryConfig_device_telemetry_enabled_tag 14
 #define meshtastic_ModuleConfig_CannedMessageConfig_rotary1_enabled_tag 1
 #define meshtastic_ModuleConfig_CannedMessageConfig_inputbroker_pin_a_tag 2
 #define meshtastic_ModuleConfig_CannedMessageConfig_inputbroker_pin_b_tag 3
@@ -825,7 +829,8 @@ X(a, STATIC,   SINGULAR, UINT32,   power_update_interval,   9) \
 X(a, STATIC,   SINGULAR, BOOL,     power_screen_enabled,  10) \
 X(a, STATIC,   SINGULAR, BOOL,     health_measurement_enabled,  11) \
 X(a, STATIC,   SINGULAR, UINT32,   health_update_interval,  12) \
-X(a, STATIC,   SINGULAR, BOOL,     health_screen_enabled,  13)
+X(a, STATIC,   SINGULAR, BOOL,     health_screen_enabled,  13) \
+X(a, STATIC,   SINGULAR, BOOL,     device_telemetry_enabled,  14)
 #define meshtastic_ModuleConfig_TelemetryConfig_CALLBACK NULL
 #define meshtastic_ModuleConfig_TelemetryConfig_DEFAULT NULL
 
@@ -910,7 +915,7 @@ extern const pb_msgdesc_t meshtastic_RemoteHardwarePin_msg;
 #define meshtastic_ModuleConfig_RemoteHardwareConfig_size 96
 #define meshtastic_ModuleConfig_SerialConfig_size 28
 #define meshtastic_ModuleConfig_StoreForwardConfig_size 24
-#define meshtastic_ModuleConfig_TelemetryConfig_size 46
+#define meshtastic_ModuleConfig_TelemetryConfig_size 48
 #define meshtastic_ModuleConfig_size             227
 #define meshtastic_RemoteHardwarePin_size        21
 

From 319cd6fa7bf50a2c409874b3438efef68c64712f Mon Sep 17 00:00:00 2001
From: ford-jones 
Date: Mon, 22 Sep 2025 15:14:31 +1200
Subject: [PATCH 318/691] Regen protos for latest changes

---
 src/mesh/generated/meshtastic/admin.pb.h      |  8 +++++++
 src/mesh/generated/meshtastic/apponly.pb.h    |  2 +-
 src/mesh/generated/meshtastic/channel.pb.h    | 14 +++++++----
 src/mesh/generated/meshtastic/deviceonly.pb.h | 23 +++++++++++--------
 4 files changed, 32 insertions(+), 15 deletions(-)

diff --git a/src/mesh/generated/meshtastic/admin.pb.h b/src/mesh/generated/meshtastic/admin.pb.h
index bc0b780b9..616b7e8ee 100644
--- a/src/mesh/generated/meshtastic/admin.pb.h
+++ b/src/mesh/generated/meshtastic/admin.pb.h
@@ -247,6 +247,10 @@ typedef struct _meshtastic_AdminMessage {
         uint32_t set_ignored_node;
         /* Set specified node-num to be un-ignored on the NodeDB on the device */
         uint32_t remove_ignored_node;
+        /* Set specified node-num to be muted */
+        uint32_t set_muted_node;
+        /* Set specified node-num to be heard / not-muted */
+        uint32_t remove_muted_node;
         /* Begins an edit transaction for config, module config, owner, and channel settings changes
      This will delay the standard *implicit* save to the file system and subsequent reboot behavior until committed (commit_edit_settings) */
         bool begin_edit_settings;
@@ -388,6 +392,8 @@ extern "C" {
 #define meshtastic_AdminMessage_store_ui_config_tag 46
 #define meshtastic_AdminMessage_set_ignored_node_tag 47
 #define meshtastic_AdminMessage_remove_ignored_node_tag 48
+#define meshtastic_AdminMessage_set_muted_node_tag 49
+#define meshtastic_AdminMessage_remove_muted_node_tag 50
 #define meshtastic_AdminMessage_begin_edit_settings_tag 64
 #define meshtastic_AdminMessage_commit_edit_settings_tag 65
 #define meshtastic_AdminMessage_add_contact_tag  66
@@ -446,6 +452,8 @@ X(a, STATIC,   ONEOF,    MESSAGE,  (payload_variant,get_ui_config_response,get_u
 X(a, STATIC,   ONEOF,    MESSAGE,  (payload_variant,store_ui_config,store_ui_config),  46) \
 X(a, STATIC,   ONEOF,    UINT32,   (payload_variant,set_ignored_node,set_ignored_node),  47) \
 X(a, STATIC,   ONEOF,    UINT32,   (payload_variant,remove_ignored_node,remove_ignored_node),  48) \
+X(a, STATIC,   ONEOF,    UINT32,   (payload_variant,set_muted_node,set_muted_node),  49) \
+X(a, STATIC,   ONEOF,    UINT32,   (payload_variant,remove_muted_node,remove_muted_node),  50) \
 X(a, STATIC,   ONEOF,    BOOL,     (payload_variant,begin_edit_settings,begin_edit_settings),  64) \
 X(a, STATIC,   ONEOF,    BOOL,     (payload_variant,commit_edit_settings,commit_edit_settings),  65) \
 X(a, STATIC,   ONEOF,    MESSAGE,  (payload_variant,add_contact,add_contact),  66) \
diff --git a/src/mesh/generated/meshtastic/apponly.pb.h b/src/mesh/generated/meshtastic/apponly.pb.h
index f4c33bd79..db9dedaaf 100644
--- a/src/mesh/generated/meshtastic/apponly.pb.h
+++ b/src/mesh/generated/meshtastic/apponly.pb.h
@@ -55,7 +55,7 @@ extern const pb_msgdesc_t meshtastic_ChannelSet_msg;
 
 /* Maximum encoded size of messages (where known) */
 #define MESHTASTIC_MESHTASTIC_APPONLY_PB_H_MAX_SIZE meshtastic_ChannelSet_size
-#define meshtastic_ChannelSet_size               679
+#define meshtastic_ChannelSet_size               695
 
 #ifdef __cplusplus
 } /* extern "C" */
diff --git a/src/mesh/generated/meshtastic/channel.pb.h b/src/mesh/generated/meshtastic/channel.pb.h
index ca4310bf1..594d15929 100644
--- a/src/mesh/generated/meshtastic/channel.pb.h
+++ b/src/mesh/generated/meshtastic/channel.pb.h
@@ -97,6 +97,8 @@ typedef struct _meshtastic_ChannelSettings {
     /* Per-channel module settings. */
     bool has_module_settings;
     meshtastic_ModuleSettings module_settings;
+    /* Whether or not we should receive notifactions / alerts from this channel */
+    bool mute;
 } meshtastic_ChannelSettings;
 
 /* A pair of a channel number, mode and the (sharable) settings for that channel */
@@ -128,10 +130,10 @@ extern "C" {
 
 
 /* Initializer values for message structs */
-#define meshtastic_ChannelSettings_init_default  {0, {0, {0}}, "", 0, 0, 0, false, meshtastic_ModuleSettings_init_default}
+#define meshtastic_ChannelSettings_init_default  {0, {0, {0}}, "", 0, 0, 0, false, meshtastic_ModuleSettings_init_default, 0}
 #define meshtastic_ModuleSettings_init_default   {0, 0}
 #define meshtastic_Channel_init_default          {0, false, meshtastic_ChannelSettings_init_default, _meshtastic_Channel_Role_MIN}
-#define meshtastic_ChannelSettings_init_zero     {0, {0, {0}}, "", 0, 0, 0, false, meshtastic_ModuleSettings_init_zero}
+#define meshtastic_ChannelSettings_init_zero     {0, {0, {0}}, "", 0, 0, 0, false, meshtastic_ModuleSettings_init_zero, 0}
 #define meshtastic_ModuleSettings_init_zero      {0, 0}
 #define meshtastic_Channel_init_zero             {0, false, meshtastic_ChannelSettings_init_zero, _meshtastic_Channel_Role_MIN}
 
@@ -145,6 +147,7 @@ extern "C" {
 #define meshtastic_ChannelSettings_uplink_enabled_tag 5
 #define meshtastic_ChannelSettings_downlink_enabled_tag 6
 #define meshtastic_ChannelSettings_module_settings_tag 7
+#define meshtastic_ChannelSettings_mute_tag      8
 #define meshtastic_Channel_index_tag             1
 #define meshtastic_Channel_settings_tag          2
 #define meshtastic_Channel_role_tag              3
@@ -157,7 +160,8 @@ X(a, STATIC,   SINGULAR, STRING,   name,              3) \
 X(a, STATIC,   SINGULAR, FIXED32,  id,                4) \
 X(a, STATIC,   SINGULAR, BOOL,     uplink_enabled,    5) \
 X(a, STATIC,   SINGULAR, BOOL,     downlink_enabled,   6) \
-X(a, STATIC,   OPTIONAL, MESSAGE,  module_settings,   7)
+X(a, STATIC,   OPTIONAL, MESSAGE,  module_settings,   7) \
+X(a, STATIC,   SINGULAR, BOOL,     mute,              8)
 #define meshtastic_ChannelSettings_CALLBACK NULL
 #define meshtastic_ChannelSettings_DEFAULT NULL
 #define meshtastic_ChannelSettings_module_settings_MSGTYPE meshtastic_ModuleSettings
@@ -187,8 +191,8 @@ extern const pb_msgdesc_t meshtastic_Channel_msg;
 
 /* Maximum encoded size of messages (where known) */
 #define MESHTASTIC_MESHTASTIC_CHANNEL_PB_H_MAX_SIZE meshtastic_Channel_size
-#define meshtastic_ChannelSettings_size          72
-#define meshtastic_Channel_size                  87
+#define meshtastic_ChannelSettings_size          74
+#define meshtastic_Channel_size                  89
 #define meshtastic_ModuleSettings_size           8
 
 #ifdef __cplusplus
diff --git a/src/mesh/generated/meshtastic/deviceonly.pb.h b/src/mesh/generated/meshtastic/deviceonly.pb.h
index 7fab82ff7..04264ae76 100644
--- a/src/mesh/generated/meshtastic/deviceonly.pb.h
+++ b/src/mesh/generated/meshtastic/deviceonly.pb.h
@@ -94,6 +94,9 @@ typedef struct _meshtastic_NodeInfoLite {
     /* True if node is in our ignored list
  Persists between NodeDB internal clean ups */
     bool is_ignored;
+    /* True if node has been muted
+ Persists between NodeDB internal clean ups */
+    bool is_muted;
     /* Last byte of the node number of the node that should be used as the next hop to reach this node. */
     uint8_t next_hop;
     /* Bitfield for storing booleans.
@@ -190,14 +193,14 @@ extern "C" {
 /* Initializer values for message structs */
 #define meshtastic_PositionLite_init_default     {0, 0, 0, 0, _meshtastic_Position_LocSource_MIN}
 #define meshtastic_UserLite_init_default         {{0}, "", "", _meshtastic_HardwareModel_MIN, 0, _meshtastic_Config_DeviceConfig_Role_MIN, {0, {0}}, false, 0}
-#define meshtastic_NodeInfoLite_init_default     {0, false, meshtastic_UserLite_init_default, false, meshtastic_PositionLite_init_default, 0, 0, false, meshtastic_DeviceMetrics_init_default, 0, 0, false, 0, 0, 0, 0, 0}
+#define meshtastic_NodeInfoLite_init_default     {0, false, meshtastic_UserLite_init_default, false, meshtastic_PositionLite_init_default, 0, 0, false, meshtastic_DeviceMetrics_init_default, 0, 0, false, 0, 0, 0, 0, 0, 0}
 #define meshtastic_DeviceState_init_default      {false, meshtastic_MyNodeInfo_init_default, false, meshtastic_User_init_default, 0, {meshtastic_MeshPacket_init_default}, false, meshtastic_MeshPacket_init_default, 0, 0, 0, false, meshtastic_MeshPacket_init_default, 0, {meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default}}
 #define meshtastic_NodeDatabase_init_default     {0, {0}}
 #define meshtastic_ChannelFile_init_default      {0, {meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default}, 0}
 #define meshtastic_BackupPreferences_init_default {0, 0, false, meshtastic_LocalConfig_init_default, false, meshtastic_LocalModuleConfig_init_default, false, meshtastic_ChannelFile_init_default, false, meshtastic_User_init_default}
 #define meshtastic_PositionLite_init_zero        {0, 0, 0, 0, _meshtastic_Position_LocSource_MIN}
 #define meshtastic_UserLite_init_zero            {{0}, "", "", _meshtastic_HardwareModel_MIN, 0, _meshtastic_Config_DeviceConfig_Role_MIN, {0, {0}}, false, 0}
-#define meshtastic_NodeInfoLite_init_zero        {0, false, meshtastic_UserLite_init_zero, false, meshtastic_PositionLite_init_zero, 0, 0, false, meshtastic_DeviceMetrics_init_zero, 0, 0, false, 0, 0, 0, 0, 0}
+#define meshtastic_NodeInfoLite_init_zero        {0, false, meshtastic_UserLite_init_zero, false, meshtastic_PositionLite_init_zero, 0, 0, false, meshtastic_DeviceMetrics_init_zero, 0, 0, false, 0, 0, 0, 0, 0, 0}
 #define meshtastic_DeviceState_init_zero         {false, meshtastic_MyNodeInfo_init_zero, false, meshtastic_User_init_zero, 0, {meshtastic_MeshPacket_init_zero}, false, meshtastic_MeshPacket_init_zero, 0, 0, 0, false, meshtastic_MeshPacket_init_zero, 0, {meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero}}
 #define meshtastic_NodeDatabase_init_zero        {0, {0}}
 #define meshtastic_ChannelFile_init_zero         {0, {meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero}, 0}
@@ -228,8 +231,9 @@ extern "C" {
 #define meshtastic_NodeInfoLite_hops_away_tag    9
 #define meshtastic_NodeInfoLite_is_favorite_tag  10
 #define meshtastic_NodeInfoLite_is_ignored_tag   11
-#define meshtastic_NodeInfoLite_next_hop_tag     12
-#define meshtastic_NodeInfoLite_bitfield_tag     13
+#define meshtastic_NodeInfoLite_is_muted_tag     12
+#define meshtastic_NodeInfoLite_next_hop_tag     13
+#define meshtastic_NodeInfoLite_bitfield_tag     14
 #define meshtastic_DeviceState_my_node_tag       2
 #define meshtastic_DeviceState_owner_tag         3
 #define meshtastic_DeviceState_receive_queue_tag 5
@@ -284,8 +288,9 @@ X(a, STATIC,   SINGULAR, BOOL,     via_mqtt,          8) \
 X(a, STATIC,   OPTIONAL, UINT32,   hops_away,         9) \
 X(a, STATIC,   SINGULAR, BOOL,     is_favorite,      10) \
 X(a, STATIC,   SINGULAR, BOOL,     is_ignored,       11) \
-X(a, STATIC,   SINGULAR, UINT32,   next_hop,         12) \
-X(a, STATIC,   SINGULAR, UINT32,   bitfield,         13)
+X(a, STATIC,   SINGULAR, BOOL,     is_muted,         12) \
+X(a, STATIC,   SINGULAR, UINT32,   next_hop,         13) \
+X(a, STATIC,   SINGULAR, UINT32,   bitfield,         14)
 #define meshtastic_NodeInfoLite_CALLBACK NULL
 #define meshtastic_NodeInfoLite_DEFAULT NULL
 #define meshtastic_NodeInfoLite_user_MSGTYPE meshtastic_UserLite
@@ -360,10 +365,10 @@ 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        2277
-#define meshtastic_ChannelFile_size              718
+#define meshtastic_BackupPreferences_size        2293
+#define meshtastic_ChannelFile_size              734
 #define meshtastic_DeviceState_size              1737
-#define meshtastic_NodeInfoLite_size             196
+#define meshtastic_NodeInfoLite_size             198
 #define meshtastic_PositionLite_size             28
 #define meshtastic_UserLite_size                 98
 

From 13e1f99c7e6ce507028f19282ab987701257d182 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
 <41898282+github-actions[bot]@users.noreply.github.com>
Date: Mon, 22 Sep 2025 05:58:47 -0500
Subject: [PATCH 319/691] Upgrade trunk (#8078)

Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com>
---
 .trunk/trunk.yaml | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml
index f23c41810..4dd040287 100644
--- a/.trunk/trunk.yaml
+++ b/.trunk/trunk.yaml
@@ -9,14 +9,14 @@ plugins:
 lint:
   enabled:
     - checkov@3.2.471
-    - renovate@41.115.6
+    - renovate@41.118.1
     - prettier@3.6.2
-    - trufflehog@3.90.6
+    - trufflehog@3.90.8
     - yamllint@1.37.1
     - bandit@1.8.6
     - trivy@0.66.0
     - taplo@0.10.0
-    - ruff@0.13.0
+    - ruff@0.13.1
     - isort@6.0.1
     - markdownlint@0.45.0
     - oxipng@9.1.5
@@ -26,7 +26,7 @@ lint:
     - hadolint@2.13.1
     - shfmt@3.6.0
     - shellcheck@0.11.0
-    - black@25.1.0
+    - black@25.9.0
     - git-diff-check
     - gitleaks@8.28.0
     - clang-format@16.0.3

From db941bff3b0f7ecad1825bd8611876ff520f132a Mon Sep 17 00:00:00 2001
From: Jonathan Bennett 
Date: Mon, 22 Sep 2025 12:00:01 -0500
Subject: [PATCH 320/691] portduino bump to fix gpiod bug (#8083)

An earlier portduino causes problems with initializing gpiod lines. This pulls in the fix.
---
 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 95c3bf3d9..f3fd00de7 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/c490bcd019e0658404088a61b96e653c9da22c45.zip
+  https://github.com/meshtastic/platform-native/archive/d3f6e339534233c7217818867368767590ce549e.zip
 framework = arduino
 
 build_src_filter = 

From e7840122e8f1622a4ea36d58873bd63f81f3e3a3 Mon Sep 17 00:00:00 2001
From: ford-jones 
Date: Tue, 23 Sep 2025 11:40:45 +1200
Subject: [PATCH 321/691] Decouple node-mute from channel-mute

---
 src/graphics/Screen.cpp                       |  2 +-
 src/mesh/generated/meshtastic/admin.pb.h      |  8 --------
 src/mesh/generated/meshtastic/deviceonly.pb.h | 11 +++--------
 src/modules/AdminModule.cpp                   | 18 ------------------
 src/modules/ExternalNotificationModule.cpp    |  6 +++---
 5 files changed, 7 insertions(+), 38 deletions(-)

diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp
index df0b2a1cc..c8381db36 100644
--- a/src/graphics/Screen.cpp
+++ b/src/graphics/Screen.cpp
@@ -1468,7 +1468,7 @@ int Screen::handleTextMessage(const meshtastic_MeshPacket *packet)
                     strcpy(banner, "Alert Received");
                 }
                 screen->showSimpleBanner(banner, 3000);
-            } else if (!node->is_muted && !channel.settings.mute) {
+            } else if (!channel.settings.mute) {
                 if (longName && longName[0]) {
 #if defined(M5STACK_UNITC6L)
                     strcpy(banner, "New Message");
diff --git a/src/mesh/generated/meshtastic/admin.pb.h b/src/mesh/generated/meshtastic/admin.pb.h
index 616b7e8ee..bc0b780b9 100644
--- a/src/mesh/generated/meshtastic/admin.pb.h
+++ b/src/mesh/generated/meshtastic/admin.pb.h
@@ -247,10 +247,6 @@ typedef struct _meshtastic_AdminMessage {
         uint32_t set_ignored_node;
         /* Set specified node-num to be un-ignored on the NodeDB on the device */
         uint32_t remove_ignored_node;
-        /* Set specified node-num to be muted */
-        uint32_t set_muted_node;
-        /* Set specified node-num to be heard / not-muted */
-        uint32_t remove_muted_node;
         /* Begins an edit transaction for config, module config, owner, and channel settings changes
      This will delay the standard *implicit* save to the file system and subsequent reboot behavior until committed (commit_edit_settings) */
         bool begin_edit_settings;
@@ -392,8 +388,6 @@ extern "C" {
 #define meshtastic_AdminMessage_store_ui_config_tag 46
 #define meshtastic_AdminMessage_set_ignored_node_tag 47
 #define meshtastic_AdminMessage_remove_ignored_node_tag 48
-#define meshtastic_AdminMessage_set_muted_node_tag 49
-#define meshtastic_AdminMessage_remove_muted_node_tag 50
 #define meshtastic_AdminMessage_begin_edit_settings_tag 64
 #define meshtastic_AdminMessage_commit_edit_settings_tag 65
 #define meshtastic_AdminMessage_add_contact_tag  66
@@ -452,8 +446,6 @@ X(a, STATIC,   ONEOF,    MESSAGE,  (payload_variant,get_ui_config_response,get_u
 X(a, STATIC,   ONEOF,    MESSAGE,  (payload_variant,store_ui_config,store_ui_config),  46) \
 X(a, STATIC,   ONEOF,    UINT32,   (payload_variant,set_ignored_node,set_ignored_node),  47) \
 X(a, STATIC,   ONEOF,    UINT32,   (payload_variant,remove_ignored_node,remove_ignored_node),  48) \
-X(a, STATIC,   ONEOF,    UINT32,   (payload_variant,set_muted_node,set_muted_node),  49) \
-X(a, STATIC,   ONEOF,    UINT32,   (payload_variant,remove_muted_node,remove_muted_node),  50) \
 X(a, STATIC,   ONEOF,    BOOL,     (payload_variant,begin_edit_settings,begin_edit_settings),  64) \
 X(a, STATIC,   ONEOF,    BOOL,     (payload_variant,commit_edit_settings,commit_edit_settings),  65) \
 X(a, STATIC,   ONEOF,    MESSAGE,  (payload_variant,add_contact,add_contact),  66) \
diff --git a/src/mesh/generated/meshtastic/deviceonly.pb.h b/src/mesh/generated/meshtastic/deviceonly.pb.h
index 04264ae76..a7a6b81d9 100644
--- a/src/mesh/generated/meshtastic/deviceonly.pb.h
+++ b/src/mesh/generated/meshtastic/deviceonly.pb.h
@@ -94,9 +94,6 @@ typedef struct _meshtastic_NodeInfoLite {
     /* True if node is in our ignored list
  Persists between NodeDB internal clean ups */
     bool is_ignored;
-    /* True if node has been muted
- Persists between NodeDB internal clean ups */
-    bool is_muted;
     /* Last byte of the node number of the node that should be used as the next hop to reach this node. */
     uint8_t next_hop;
     /* Bitfield for storing booleans.
@@ -193,14 +190,14 @@ extern "C" {
 /* Initializer values for message structs */
 #define meshtastic_PositionLite_init_default     {0, 0, 0, 0, _meshtastic_Position_LocSource_MIN}
 #define meshtastic_UserLite_init_default         {{0}, "", "", _meshtastic_HardwareModel_MIN, 0, _meshtastic_Config_DeviceConfig_Role_MIN, {0, {0}}, false, 0}
-#define meshtastic_NodeInfoLite_init_default     {0, false, meshtastic_UserLite_init_default, false, meshtastic_PositionLite_init_default, 0, 0, false, meshtastic_DeviceMetrics_init_default, 0, 0, false, 0, 0, 0, 0, 0, 0}
+#define meshtastic_NodeInfoLite_init_default     {0, false, meshtastic_UserLite_init_default, false, meshtastic_PositionLite_init_default, 0, 0, false, meshtastic_DeviceMetrics_init_default, 0, 0, false, 0, 0, 0, 0, 0}
 #define meshtastic_DeviceState_init_default      {false, meshtastic_MyNodeInfo_init_default, false, meshtastic_User_init_default, 0, {meshtastic_MeshPacket_init_default}, false, meshtastic_MeshPacket_init_default, 0, 0, 0, false, meshtastic_MeshPacket_init_default, 0, {meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default}}
 #define meshtastic_NodeDatabase_init_default     {0, {0}}
 #define meshtastic_ChannelFile_init_default      {0, {meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default}, 0}
 #define meshtastic_BackupPreferences_init_default {0, 0, false, meshtastic_LocalConfig_init_default, false, meshtastic_LocalModuleConfig_init_default, false, meshtastic_ChannelFile_init_default, false, meshtastic_User_init_default}
 #define meshtastic_PositionLite_init_zero        {0, 0, 0, 0, _meshtastic_Position_LocSource_MIN}
 #define meshtastic_UserLite_init_zero            {{0}, "", "", _meshtastic_HardwareModel_MIN, 0, _meshtastic_Config_DeviceConfig_Role_MIN, {0, {0}}, false, 0}
-#define meshtastic_NodeInfoLite_init_zero        {0, false, meshtastic_UserLite_init_zero, false, meshtastic_PositionLite_init_zero, 0, 0, false, meshtastic_DeviceMetrics_init_zero, 0, 0, false, 0, 0, 0, 0, 0, 0}
+#define meshtastic_NodeInfoLite_init_zero        {0, false, meshtastic_UserLite_init_zero, false, meshtastic_PositionLite_init_zero, 0, 0, false, meshtastic_DeviceMetrics_init_zero, 0, 0, false, 0, 0, 0, 0, 0}
 #define meshtastic_DeviceState_init_zero         {false, meshtastic_MyNodeInfo_init_zero, false, meshtastic_User_init_zero, 0, {meshtastic_MeshPacket_init_zero}, false, meshtastic_MeshPacket_init_zero, 0, 0, 0, false, meshtastic_MeshPacket_init_zero, 0, {meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero}}
 #define meshtastic_NodeDatabase_init_zero        {0, {0}}
 #define meshtastic_ChannelFile_init_zero         {0, {meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero}, 0}
@@ -231,7 +228,6 @@ extern "C" {
 #define meshtastic_NodeInfoLite_hops_away_tag    9
 #define meshtastic_NodeInfoLite_is_favorite_tag  10
 #define meshtastic_NodeInfoLite_is_ignored_tag   11
-#define meshtastic_NodeInfoLite_is_muted_tag     12
 #define meshtastic_NodeInfoLite_next_hop_tag     13
 #define meshtastic_NodeInfoLite_bitfield_tag     14
 #define meshtastic_DeviceState_my_node_tag       2
@@ -288,7 +284,6 @@ X(a, STATIC,   SINGULAR, BOOL,     via_mqtt,          8) \
 X(a, STATIC,   OPTIONAL, UINT32,   hops_away,         9) \
 X(a, STATIC,   SINGULAR, BOOL,     is_favorite,      10) \
 X(a, STATIC,   SINGULAR, BOOL,     is_ignored,       11) \
-X(a, STATIC,   SINGULAR, BOOL,     is_muted,         12) \
 X(a, STATIC,   SINGULAR, UINT32,   next_hop,         13) \
 X(a, STATIC,   SINGULAR, UINT32,   bitfield,         14)
 #define meshtastic_NodeInfoLite_CALLBACK NULL
@@ -368,7 +363,7 @@ extern const pb_msgdesc_t meshtastic_BackupPreferences_msg;
 #define meshtastic_BackupPreferences_size        2293
 #define meshtastic_ChannelFile_size              734
 #define meshtastic_DeviceState_size              1737
-#define meshtastic_NodeInfoLite_size             198
+#define meshtastic_NodeInfoLite_size             196
 #define meshtastic_PositionLite_size             28
 #define meshtastic_UserLite_size                 98
 
diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp
index 0877c969b..79ea7bc0c 100644
--- a/src/modules/AdminModule.cpp
+++ b/src/modules/AdminModule.cpp
@@ -369,24 +369,6 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta
         }
         break;
     }
-    case meshtastic_AdminMessage_set_muted_node_tag: {
-        LOG_INFO("Client received set_muted_node command");
-        meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(r->set_muted_node);
-        if (node != NULL) {
-            node->is_muted = true;
-            saveChanges(SEGMENT_NODEDATABASE, false);
-        }
-        break;
-    }
-    case meshtastic_AdminMessage_remove_muted_node_tag: {
-        LOG_INFO("Client received remove_muted_node command");
-        meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(r->remove_muted_node);
-        if (node != NULL) {
-            node->is_muted = false;
-            saveChanges(SEGMENT_NODEDATABASE, false);
-        }
-        break;
-    }
     case meshtastic_AdminMessage_set_fixed_position_tag: {
         LOG_INFO("Client received set_fixed_position command");
         meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(nodeDB->getNodeNum());
diff --git a/src/modules/ExternalNotificationModule.cpp b/src/modules/ExternalNotificationModule.cpp
index 92590f149..5ee7834db 100644
--- a/src/modules/ExternalNotificationModule.cpp
+++ b/src/modules/ExternalNotificationModule.cpp
@@ -510,7 +510,7 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP
                 }
             }
 
-            if (moduleConfig.external_notification.alert_message && !sender->is_muted && !ch.settings.mute) {
+            if (moduleConfig.external_notification.alert_message && !ch.settings.mute) {
                 LOG_INFO("externalNotificationModule - Notification Module");
                 isNagging = true;
                 setExternalState(0, true);
@@ -521,7 +521,7 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP
                 }
             }
 
-            if (moduleConfig.external_notification.alert_message_vibra && !sender->is_muted && !ch.settings.mute) {
+            if (moduleConfig.external_notification.alert_message_vibra && !ch.settings.mute) {
                 LOG_INFO("externalNotificationModule - Notification Module (Vibra)");
                 isNagging = true;
                 setExternalState(1, true);
@@ -532,7 +532,7 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP
                 }
             }
 
-            if (moduleConfig.external_notification.alert_message_buzzer && !sender->is_muted && !ch.settings.mute) {
+            if (moduleConfig.external_notification.alert_message_buzzer && !ch.settings.mute) {
                 LOG_INFO("externalNotificationModule - Notification Module (Buzzer)");
                 isNagging = true;
                 if (!moduleConfig.external_notification.use_pwm && !moduleConfig.external_notification.use_i2s_as_buzzer) {

From 2fbfb193049639d5128f5bafe76d04c84985dfb6 Mon Sep 17 00:00:00 2001
From: ford-jones 
Date: Tue, 23 Sep 2025 12:40:48 +1200
Subject: [PATCH 322/691] Regen protos

---
 src/mesh/generated/meshtastic/channel.pb.h    | 2 +-
 src/mesh/generated/meshtastic/deviceonly.pb.h | 8 ++++----
 2 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/src/mesh/generated/meshtastic/channel.pb.h b/src/mesh/generated/meshtastic/channel.pb.h
index 594d15929..d5573a1e2 100644
--- a/src/mesh/generated/meshtastic/channel.pb.h
+++ b/src/mesh/generated/meshtastic/channel.pb.h
@@ -97,7 +97,7 @@ typedef struct _meshtastic_ChannelSettings {
     /* Per-channel module settings. */
     bool has_module_settings;
     meshtastic_ModuleSettings module_settings;
-    /* Whether or not we should receive notifactions / alerts from this channel */
+    /* Whether or not we should receive notifactions / alerts through this channel */
     bool mute;
 } meshtastic_ChannelSettings;
 
diff --git a/src/mesh/generated/meshtastic/deviceonly.pb.h b/src/mesh/generated/meshtastic/deviceonly.pb.h
index a7a6b81d9..b5b116137 100644
--- a/src/mesh/generated/meshtastic/deviceonly.pb.h
+++ b/src/mesh/generated/meshtastic/deviceonly.pb.h
@@ -228,8 +228,8 @@ extern "C" {
 #define meshtastic_NodeInfoLite_hops_away_tag    9
 #define meshtastic_NodeInfoLite_is_favorite_tag  10
 #define meshtastic_NodeInfoLite_is_ignored_tag   11
-#define meshtastic_NodeInfoLite_next_hop_tag     13
-#define meshtastic_NodeInfoLite_bitfield_tag     14
+#define meshtastic_NodeInfoLite_next_hop_tag     12
+#define meshtastic_NodeInfoLite_bitfield_tag     13
 #define meshtastic_DeviceState_my_node_tag       2
 #define meshtastic_DeviceState_owner_tag         3
 #define meshtastic_DeviceState_receive_queue_tag 5
@@ -284,8 +284,8 @@ X(a, STATIC,   SINGULAR, BOOL,     via_mqtt,          8) \
 X(a, STATIC,   OPTIONAL, UINT32,   hops_away,         9) \
 X(a, STATIC,   SINGULAR, BOOL,     is_favorite,      10) \
 X(a, STATIC,   SINGULAR, BOOL,     is_ignored,       11) \
-X(a, STATIC,   SINGULAR, UINT32,   next_hop,         13) \
-X(a, STATIC,   SINGULAR, UINT32,   bitfield,         14)
+X(a, STATIC,   SINGULAR, UINT32,   next_hop,         12) \
+X(a, STATIC,   SINGULAR, UINT32,   bitfield,         13)
 #define meshtastic_NodeInfoLite_CALLBACK NULL
 #define meshtastic_NodeInfoLite_DEFAULT NULL
 #define meshtastic_NodeInfoLite_user_MSGTYPE meshtastic_UserLite

From e1485b530ffc6525981260d2d56aee6905ea7035 Mon Sep 17 00:00:00 2001
From: Ben Meadors 
Date: Mon, 22 Sep 2025 19:59:05 -0500
Subject: [PATCH 323/691] Handle ext. notification module things even if not
 enabled (#8089)

---
 src/modules/Modules.cpp | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/src/modules/Modules.cpp b/src/modules/Modules.cpp
index b3c15e764..bd8997493 100644
--- a/src/modules/Modules.cpp
+++ b/src/modules/Modules.cpp
@@ -293,9 +293,7 @@ void setupModules()
 #endif
 #endif
 #if !MESHTASTIC_EXCLUDE_EXTERNALNOTIFICATION
-        if (moduleConfig.has_external_notification && moduleConfig.external_notification.enabled) {
-            externalNotificationModule = new ExternalNotificationModule();
-        }
+        externalNotificationModule = new ExternalNotificationModule();
 #endif
 #if !MESHTASTIC_EXCLUDE_RANGETEST && !MESHTASTIC_EXCLUDE_GPS
         if (moduleConfig.has_range_test && moduleConfig.range_test.enabled)

From 07b58a82d516e404691956436e3ed6a5401e224d Mon Sep 17 00:00:00 2001
From: Jonathan Bennett 
Date: Mon, 22 Sep 2025 21:06:23 -0500
Subject: [PATCH 324/691] tlora-pager wake on button, and kb backlight toggling
 (#8090)

---
 src/graphics/Screen.cpp           | 18 ++++++++++++++++++
 src/graphics/Screen.h             | 10 +---------
 src/input/TCA8418KeyboardBase.cpp |  5 +++++
 src/input/TCA8418KeyboardBase.h   |  2 ++
 src/input/TLoraPagerKeyboard.cpp  | 16 +++++++++-------
 src/input/TLoraPagerKeyboard.h    |  1 +
 src/input/kbI2cBase.cpp           | 10 +++++++++-
 src/input/kbI2cBase.h             |  1 +
 src/main.cpp                      |  1 +
 src/sleep.cpp                     | 17 +++++++++++++----
 10 files changed, 60 insertions(+), 21 deletions(-)

diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp
index 007ce56ea..d52b3dfb7 100644
--- a/src/graphics/Screen.cpp
+++ b/src/graphics/Screen.cpp
@@ -83,6 +83,11 @@ extern uint16_t TFT_MESH;
 #include "platform/portduino/PortduinoGlue.h"
 #endif
 
+#if defined(T_LORA_PAGER)
+// KB backlight control
+#include "input/cardKbI2cImpl.h"
+#endif
+
 using namespace meshtastic; /** @todo remove */
 
 namespace graphics
@@ -617,6 +622,19 @@ void Screen::setup()
     MeshModule::observeUIEvents(&uiFrameEventObserver);
 }
 
+void Screen::setOn(bool on, FrameCallback einkScreensaver)
+{
+#if defined(T_LORA_PAGER)
+    if (cardKbI2cImpl)
+        cardKbI2cImpl->toggleBacklight(on);
+#endif
+    if (!on)
+        // We handle off commands immediately, because they might be called because the CPU is shutting down
+        handleSetOn(false, einkScreensaver);
+    else
+        enqueueCmd(ScreenCmd{.cmd = Cmd::SET_ON});
+}
+
 void Screen::forceDisplay(bool forceUiUpdate)
 {
     // Nasty hack to force epaper updates for 'key' frames.  FIXME, cleanup.
diff --git a/src/graphics/Screen.h b/src/graphics/Screen.h
index 262ba4175..35058903b 100644
--- a/src/graphics/Screen.h
+++ b/src/graphics/Screen.h
@@ -259,15 +259,7 @@ class Screen : public concurrency::OSThread
     void setup();
 
     /// Turns the screen on/off. Optionally, pass a custom screensaver frame for E-Ink
-    void setOn(bool on, FrameCallback einkScreensaver = NULL)
-    {
-        if (!on)
-            // We handle off commands immediately, because they might be called because the CPU is shutting down
-            handleSetOn(false, einkScreensaver);
-        else
-            enqueueCmd(ScreenCmd{.cmd = Cmd::SET_ON});
-    }
-
+    void setOn(bool on, FrameCallback einkScreensaver = NULL);
     /**
      * Prepare the display for the unit going to the lowest power mode possible.  Most screens will just
      * poweroff, but eink screens will show a "I'm sleeping" graphic, possibly with a QR code
diff --git a/src/input/TCA8418KeyboardBase.cpp b/src/input/TCA8418KeyboardBase.cpp
index aafc4c36c..00aed9962 100644
--- a/src/input/TCA8418KeyboardBase.cpp
+++ b/src/input/TCA8418KeyboardBase.cpp
@@ -200,6 +200,11 @@ uint8_t TCA8418KeyboardBase::flush()
     return count;
 }
 
+void TCA8418KeyboardBase::clearInt()
+{
+    writeRegister(TCA8418_REG_INT_STAT, 3);
+}
+
 uint8_t TCA8418KeyboardBase::digitalRead(uint8_t pinnum) const
 {
     if (pinnum > TCA8418_COL9)
diff --git a/src/input/TCA8418KeyboardBase.h b/src/input/TCA8418KeyboardBase.h
index 5d6c4f7e9..8e509ac7e 100644
--- a/src/input/TCA8418KeyboardBase.h
+++ b/src/input/TCA8418KeyboardBase.h
@@ -37,6 +37,8 @@ class TCA8418KeyboardBase
     virtual void begin(i2c_com_fptr_t r, i2c_com_fptr_t w, uint8_t addr = TCA8418_KB_ADDR);
 
     virtual void reset(void);
+    void clearInt(void);
+
     virtual void trigger(void);
 
     virtual void setBacklight(bool on);
diff --git a/src/input/TLoraPagerKeyboard.cpp b/src/input/TLoraPagerKeyboard.cpp
index b3f62a36c..9a4fd8679 100644
--- a/src/input/TLoraPagerKeyboard.cpp
+++ b/src/input/TLoraPagerKeyboard.cpp
@@ -105,7 +105,14 @@ void TLoraPagerKeyboard::trigger()
 
 void TLoraPagerKeyboard::setBacklight(bool on)
 {
-    toggleBacklight(!on);
+    uint32_t _brightness = 0;
+    if (on)
+        _brightness = brightness;
+#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
+    ledcWrite(KB_BL_PIN, _brightness);
+#else
+    ledcWrite(LEDC_BACKLIGHT_CHANNEL, _brightness);
+#endif
 }
 
 void TLoraPagerKeyboard::pressed(uint8_t key)
@@ -192,7 +199,6 @@ void TLoraPagerKeyboard::hapticFeedback()
 // toggle brightness of the backlight in three steps
 void TLoraPagerKeyboard::toggleBacklight(bool off)
 {
-    static uint32_t brightness = 0;
     if (off) {
         brightness = 0;
     } else {
@@ -206,11 +212,7 @@ void TLoraPagerKeyboard::toggleBacklight(bool off)
     }
     LOG_DEBUG("Toggle backlight: %d", brightness);
 
-#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
-    ledcWrite(KB_BL_PIN, brightness);
-#else
-    ledcWrite(LEDC_BACKLIGHT_CHANNEL, brightness);
-#endif
+    setBacklight(true);
 }
 
 void TLoraPagerKeyboard::updateModifierFlag(uint8_t key)
diff --git a/src/input/TLoraPagerKeyboard.h b/src/input/TLoraPagerKeyboard.h
index 4dabbac64..f04d2ce6a 100644
--- a/src/input/TLoraPagerKeyboard.h
+++ b/src/input/TLoraPagerKeyboard.h
@@ -26,4 +26,5 @@ class TLoraPagerKeyboard : public TCA8418KeyboardBase
     uint32_t last_tap;
     uint8_t char_idx;
     int32_t tap_interval;
+    uint32_t brightness = 0;
 };
diff --git a/src/input/kbI2cBase.cpp b/src/input/kbI2cBase.cpp
index 3f5d4d184..0ed2df116 100644
--- a/src/input/kbI2cBase.cpp
+++ b/src/input/kbI2cBase.cpp
@@ -333,6 +333,7 @@ int32_t KbI2cBase::runOnce()
             }
             TCAKeyboard.trigger();
         }
+        TCAKeyboard.clearInt();
         break;
     }
     case 0x02: {
@@ -519,4 +520,11 @@ int32_t KbI2cBase::runOnce()
         LOG_WARN("Unknown kb_model 0x%02x", kb_model);
     }
     return 300;
-}
\ No newline at end of file
+}
+
+void KbI2cBase::toggleBacklight(bool on)
+{
+#if defined(T_LORA_PAGER)
+    TCAKeyboard.setBacklight(on);
+#endif
+}
diff --git a/src/input/kbI2cBase.h b/src/input/kbI2cBase.h
index af7602979..ae769dff8 100644
--- a/src/input/kbI2cBase.h
+++ b/src/input/kbI2cBase.h
@@ -12,6 +12,7 @@ class KbI2cBase : public Observable, public concurrency::OST
 {
   public:
     explicit KbI2cBase(const char *name);
+    void toggleBacklight(bool on);
 
   protected:
     virtual int32_t runOnce() override;
diff --git a/src/main.cpp b/src/main.cpp
index 107944dd5..ecaea26f7 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -367,6 +367,7 @@ void setup()
     digitalWrite(SDCARD_CS, HIGH);
     pinMode(TFT_CS, OUTPUT);
     digitalWrite(TFT_CS, HIGH);
+    pinMode(KB_INT, INPUT_PULLUP);
     // io expander
     io.begin(Wire, XL9555_SLAVE_ADDRESS0, SDA, SCL);
     io.pinMode(EXPANDS_DRV_EN, OUTPUT);
diff --git a/src/sleep.cpp b/src/sleep.cpp
index d6eb865e5..0f8931292 100644
--- a/src/sleep.cpp
+++ b/src/sleep.cpp
@@ -411,12 +411,16 @@ esp_sleep_wakeup_cause_t doLightSleep(uint64_t sleepMsec) // FIXME, use a more r
     // assert(uart_set_wakeup_threshold(UART_NUM_0, 3) == ESP_OK);
     // assert(esp_sleep_enable_uart_wakeup(0) == ESP_OK);
 #endif
-#ifdef BUTTON_PIN
+#ifdef ROTARY_PRESS
     // The enableLoraInterrupt() method is using ext0_wakeup, so we are forced to use GPIO wakeup
+    gpio_wakeup_enable((gpio_num_t)ROTARY_PRESS, GPIO_INTR_LOW_LEVEL);
+#endif
+#ifdef KB_INT
+    gpio_wakeup_enable((gpio_num_t)KB_INT, GPIO_INTR_LOW_LEVEL);
+#endif
+#ifdef BUTTON_PIN
     gpio_num_t pin = (gpio_num_t)(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN);
-
     gpio_wakeup_enable(pin, GPIO_INTR_LOW_LEVEL);
-    esp_sleep_enable_gpio_wakeup();
 #endif
 #ifdef INPUTDRIVER_ENCODER_BTN
     gpio_wakeup_enable((gpio_num_t)INPUTDRIVER_ENCODER_BTN, GPIO_INTR_LOW_LEVEL);
@@ -450,7 +454,12 @@ esp_sleep_wakeup_cause_t doLightSleep(uint64_t sleepMsec) // FIXME, use a more r
     // commented out because it's not that crucial;
     // if it sporadically happens the node will go into light sleep during the next round
     // assert(res == ESP_OK);
-
+#ifdef ROTARY_PRESS
+    gpio_wakeup_disable((gpio_num_t)ROTARY_PRESS);
+#endif
+#ifdef KB_INT
+    gpio_wakeup_disable((gpio_num_t)KB_INT);
+#endif
 #ifdef BUTTON_PIN
     // Disable wake-on-button interrupt. Re-attach normal button-interrupts
     gpio_wakeup_disable(pin);

From f77ca2533bfa85d841efc81c614826cecfa1c79f Mon Sep 17 00:00:00 2001
From: Ben Meadors 
Date: Mon, 22 Sep 2025 21:46:35 -0500
Subject: [PATCH 325/691] Try-fix: Unstick that PhoneAPI state (#8091)

Co-authored-by: Jonathan Bennett 
---
 src/mesh/PhoneAPI.cpp | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/src/mesh/PhoneAPI.cpp b/src/mesh/PhoneAPI.cpp
index 9fb1b589f..f6f1bc027 100644
--- a/src/mesh/PhoneAPI.cpp
+++ b/src/mesh/PhoneAPI.cpp
@@ -250,6 +250,7 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
         if (config_nonce == SPECIAL_NONCE_ONLY_NODES) {
             // If client only wants node info, jump directly to sending nodes
             state = STATE_SEND_OTHER_NODEINFOS;
+            onNowHasData(0);
         } else {
             state = STATE_SEND_METADATA;
         }
@@ -423,6 +424,7 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
                 state = STATE_SEND_FILEMANIFEST;
             } else {
                 state = STATE_SEND_OTHER_NODEINFOS;
+                onNowHasData(0);
             }
             config_state = 0;
         }
@@ -588,6 +590,7 @@ bool PhoneAPI::available()
                 nodeInfoForPhone.snr = isUs ? 0 : nodeInfoForPhone.snr;
                 nodeInfoForPhone.via_mqtt = isUs ? false : nodeInfoForPhone.via_mqtt;
                 nodeInfoForPhone.is_favorite = nodeInfoForPhone.is_favorite || isUs; // Our node is always a favorite
+                onNowHasData(0);
             }
         }
         return true; // Always say we have something, because we might need to advance our state machine

From a8c66547cce2a96a3429087ec17fcfa1292a3d8f Mon Sep 17 00:00:00 2001
From: Jonathan Bennett 
Date: Mon, 22 Sep 2025 21:46:57 -0500
Subject: [PATCH 326/691] Also pull a deviceID from esp32c6 devices (#8092)

---
 src/mesh/NodeDB.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp
index 237b4286c..97dfb3e52 100644
--- a/src/mesh/NodeDB.cpp
+++ b/src/mesh/NodeDB.cpp
@@ -204,7 +204,7 @@ NodeDB::NodeDB()
 
     int saveWhat = 0;
     // Get device unique id
-#if defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32S3)
+#if defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C6)
     uint32_t unique_id[4];
     // ESP32 factory burns a unique id in efuse for S2+ series and evidently C3+ series
     // This is used for HMACs in the esp-rainmaker AIOT platform and seems to be a good choice for us

From 91efaba3890275d992f9d61125e4dd20dce731c3 Mon Sep 17 00:00:00 2001
From: Jonathan Bennett 
Date: Mon, 22 Sep 2025 21:59:00 -0500
Subject: [PATCH 327/691] Remove line from BLE pin screen, to make pin readible
 on tiny screens

---
 src/nimble/NimbleBluetooth.cpp | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/nimble/NimbleBluetooth.cpp b/src/nimble/NimbleBluetooth.cpp
index accf6c5dc..13446be45 100644
--- a/src/nimble/NimbleBluetooth.cpp
+++ b/src/nimble/NimbleBluetooth.cpp
@@ -174,11 +174,11 @@ class NimbleBluetoothServerCallback : public NimBLEServerCallbacks
                 display->setTextAlignment(TEXT_ALIGN_CENTER);
                 display->setFont(FONT_MEDIUM);
                 display->drawString(x_offset + x, y_offset + y, "Bluetooth");
-
+#if !defined(M5STACK_UNITC6L)
                 display->setFont(FONT_SMALL);
                 y_offset = display->height() == 64 ? y_offset + FONT_HEIGHT_MEDIUM - 4 : y_offset + FONT_HEIGHT_MEDIUM + 5;
                 display->drawString(x_offset + x, y_offset + y, "Enter this code");
-
+#endif
                 display->setFont(FONT_LARGE);
                 char pin[8];
                 snprintf(pin, sizeof(pin), "%.3s %.3s", btPIN, btPIN + 3);

From d998f70b5633e8b2f88823cfb73761625bbc3423 Mon Sep 17 00:00:00 2001
From: Jason P 
Date: Sun, 21 Sep 2025 14:04:17 -0500
Subject: [PATCH 328/691] Fix build errors (#8067)

---
 src/graphics/images.h | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/src/graphics/images.h b/src/graphics/images.h
index 72dda7886..a6ad1c1cb 100644
--- a/src/graphics/images.h
+++ b/src/graphics/images.h
@@ -287,9 +287,7 @@ const uint8_t digital_icon_clock[] PROGMEM = {0b00111100, 0b01000010, 0b10000101
 #define analog_icon_clock_height 8
 const uint8_t analog_icon_clock[] PROGMEM = {0b11111111, 0b01000010, 0b00100100, 0b00011000,
                                              0b00100100, 0b01000010, 0b01000010, 0b11111111};
-#ifdef M5STACK_UNITC6L
-#include "img/icon_small.xbm"
-#else
+
 #define chirpy_width 38
 #define chirpy_height 50
 static unsigned char chirpy[] = {
@@ -362,6 +360,9 @@ static unsigned char chirpy_hirez[] = {
 #define chirpy_small_image_height 8
 static unsigned char small_chirpy[] = {0x7f, 0x41, 0x55, 0x55, 0x55, 0x55, 0x41, 0x7f};
 
+#ifdef M5STACK_UNITC6L
+#include "img/icon_small.xbm"
+#else
 #include "img/icon.xbm"
 #endif
 static_assert(sizeof(icon_bits) >= 0, "Silence unused variable warning");
\ No newline at end of file

From 8e608e8186e370786703168a34f0e2e4de059593 Mon Sep 17 00:00:00 2001
From: Ben Meadors 
Date: Tue, 23 Sep 2025 06:04:47 -0500
Subject: [PATCH 329/691] Heltec V4 is 16mb

---
 variants/esp32s3/heltec_v4/platformio.ini | 1 +
 1 file changed, 1 insertion(+)

diff --git a/variants/esp32s3/heltec_v4/platformio.ini b/variants/esp32s3/heltec_v4/platformio.ini
index 0177342a4..1a448bc99 100644
--- a/variants/esp32s3/heltec_v4/platformio.ini
+++ b/variants/esp32s3/heltec_v4/platformio.ini
@@ -2,6 +2,7 @@
 extends = esp32s3_base
 board = heltec_v4
 board_check = true
+board_build.partitions = default_16MB.csv
 build_flags = 
   ${esp32s3_base.build_flags}
   -D HELTEC_V4

From 1968a009dd21303f6c5979bab21287e99dd4102b Mon Sep 17 00:00:00 2001
From: Ben Meadors 
Date: Tue, 23 Sep 2025 07:31:25 -0500
Subject: [PATCH 330/691] Clear lasttoradio on BLE disconnect (#8095)

* On disconnect, clear the lastToRadio buffer

* Move it, bucko!
---
 src/nimble/NimbleBluetooth.cpp        | 3 +++
 src/platform/nrf52/NRF52Bluetooth.cpp | 8 ++++++--
 2 files changed, 9 insertions(+), 2 deletions(-)

diff --git a/src/nimble/NimbleBluetooth.cpp b/src/nimble/NimbleBluetooth.cpp
index 13446be45..0c4e30785 100644
--- a/src/nimble/NimbleBluetooth.cpp
+++ b/src/nimble/NimbleBluetooth.cpp
@@ -247,6 +247,9 @@ class NimbleBluetoothServerCallback : public NimBLEServerCallbacks
             bluetoothPhoneAPI->numBytes = 0;
             bluetoothPhoneAPI->queue_size = 0;
         }
+
+        // Clear the last ToRadio packet buffer to avoid rejecting first packet from new connection
+        memset(lastToRadio, 0, sizeof(lastToRadio));
 #ifdef NIMBLE_TWO
         // Restart Advertising
         ble->startAdvertising();
diff --git a/src/platform/nrf52/NRF52Bluetooth.cpp b/src/platform/nrf52/NRF52Bluetooth.cpp
index f8366ae32..b457c35b7 100644
--- a/src/platform/nrf52/NRF52Bluetooth.cpp
+++ b/src/platform/nrf52/NRF52Bluetooth.cpp
@@ -28,6 +28,9 @@ static BLEDfuSecure bledfusecure;                                             //
 static uint8_t fromRadioBytes[meshtastic_FromRadio_size];
 static uint8_t toRadioBytes[meshtastic_ToRadio_size];
 
+// Last ToRadio value received from the phone
+static uint8_t lastToRadio[MAX_TO_FROM_RADIO_SIZE];
+
 static uint16_t connectionHandle;
 
 class BluetoothPhoneAPI : public PhoneAPI
@@ -74,6 +77,9 @@ void onDisconnect(uint16_t conn_handle, uint8_t reason)
         bluetoothPhoneAPI->close();
     }
 
+    // Clear the last ToRadio packet buffer to avoid rejecting first packet from new connection
+    memset(lastToRadio, 0, sizeof(lastToRadio));
+
     // Notify UI (or any other interested firmware components)
     meshtastic::BluetoothStatus newStatus(meshtastic::BluetoothStatus::ConnectionState::DISCONNECTED);
     bluetoothStatus->updateStatus(&newStatus);
@@ -145,8 +151,6 @@ void onFromRadioAuthorize(uint16_t conn_hdl, BLECharacteristic *chr, ble_gatts_e
     }
     authorizeRead(conn_hdl);
 }
-// Last ToRadio value received from the phone
-static uint8_t lastToRadio[MAX_TO_FROM_RADIO_SIZE];
 
 void onToRadioWrite(uint16_t conn_hdl, BLECharacteristic *chr, uint8_t *data, uint16_t len)
 {

From 94d4bdf05cf1554be140ef855a2f7cfc8878fdba Mon Sep 17 00:00:00 2001
From: Ben Meadors 
Date: Tue, 23 Sep 2025 08:57:04 -0500
Subject: [PATCH 331/691] Revert "Fix build errors (#8067)"

This reverts commit d998f70b5633e8b2f88823cfb73761625bbc3423.
---
 src/graphics/images.h | 7 +++----
 1 file changed, 3 insertions(+), 4 deletions(-)

diff --git a/src/graphics/images.h b/src/graphics/images.h
index a6ad1c1cb..72dda7886 100644
--- a/src/graphics/images.h
+++ b/src/graphics/images.h
@@ -287,7 +287,9 @@ const uint8_t digital_icon_clock[] PROGMEM = {0b00111100, 0b01000010, 0b10000101
 #define analog_icon_clock_height 8
 const uint8_t analog_icon_clock[] PROGMEM = {0b11111111, 0b01000010, 0b00100100, 0b00011000,
                                              0b00100100, 0b01000010, 0b01000010, 0b11111111};
-
+#ifdef M5STACK_UNITC6L
+#include "img/icon_small.xbm"
+#else
 #define chirpy_width 38
 #define chirpy_height 50
 static unsigned char chirpy[] = {
@@ -360,9 +362,6 @@ static unsigned char chirpy_hirez[] = {
 #define chirpy_small_image_height 8
 static unsigned char small_chirpy[] = {0x7f, 0x41, 0x55, 0x55, 0x55, 0x55, 0x41, 0x7f};
 
-#ifdef M5STACK_UNITC6L
-#include "img/icon_small.xbm"
-#else
 #include "img/icon.xbm"
 #endif
 static_assert(sizeof(icon_bits) >= 0, "Silence unused variable warning");
\ No newline at end of file

From a1ca553bc0ac5695d512879ac2bffe1c858cbb1d Mon Sep 17 00:00:00 2001
From: WillyJL 
Date: Tue, 23 Sep 2025 22:30:01 +0200
Subject: [PATCH 332/691] Fix desktop build

---
 src/main.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/main.cpp b/src/main.cpp
index 5f2501cc8..48ecc14c7 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -1596,7 +1596,7 @@ void loop()
 #endif
 
     service->loop();
-#if !MESHTASTIC_EXCLUDE_INPUTBROKER
+#if !MESHTASTIC_EXCLUDE_INPUTBROKER && defined(HAS_FREE_RTOS)
     inputBroker->processInputEventQueue();
 #endif
 #if defined(LGFX_SDL)

From 060a1299953166c77414f759ddba2558ae23a175 Mon Sep 17 00:00:00 2001
From: WillyJL 
Date: Wed, 24 Sep 2025 03:05:26 +0200
Subject: [PATCH 333/691] More flexible InputPollable paradigm

---
 src/input/InputBroker.cpp       | 14 +++++++++++---
 src/input/InputBroker.h         |  2 +-
 src/input/RotaryEncoderImpl.cpp |  2 +-
 3 files changed, 13 insertions(+), 5 deletions(-)

diff --git a/src/input/InputBroker.cpp b/src/input/InputBroker.cpp
index 5ca890b43..c588a9a0f 100644
--- a/src/input/InputBroker.cpp
+++ b/src/input/InputBroker.cpp
@@ -18,14 +18,22 @@ void InputBroker::registerSource(Observable *source)
 }
 
 #ifdef HAS_FREE_RTOS
-void InputBroker::pollSoonRequestFromIsr(InputPollable *pollable)
+void InputBroker::requestPollSoon(InputPollable *pollable)
 {
-    xQueueSendFromISR(pollSoonQueue, &pollable, NULL);
+    if (xPortInIsrContext() == pdTRUE) {
+        xQueueSendFromISR(pollSoonQueue, &pollable, NULL);
+    } else {
+        xQueueSend(pollSoonQueue, &pollable, 0);
+    }
 }
 
 void InputBroker::queueInputEvent(const InputEvent *event)
 {
-    xQueueSend(inputEventQueue, event, portMAX_DELAY);
+    if (xPortInIsrContext() == pdTRUE) {
+        xQueueSendFromISR(inputEventQueue, event, NULL);
+    } else {
+        xQueueSend(inputEventQueue, event, portMAX_DELAY);
+    }
 }
 
 void InputBroker::processInputEventQueue()
diff --git a/src/input/InputBroker.h b/src/input/InputBroker.h
index 82af184f3..192bd7e8b 100644
--- a/src/input/InputBroker.h
+++ b/src/input/InputBroker.h
@@ -60,7 +60,7 @@ class InputBroker : public Observable
     void registerSource(Observable *source);
     void injectInputEvent(const InputEvent *event) { handleInputEvent(event); }
 #ifdef HAS_FREE_RTOS
-    void pollSoonRequestFromIsr(InputPollable *pollable);
+    void requestPollSoon(InputPollable *pollable);
     void queueInputEvent(const InputEvent *event);
     void processInputEventQueue();
 #endif
diff --git a/src/input/RotaryEncoderImpl.cpp b/src/input/RotaryEncoderImpl.cpp
index 213dd4faa..7b43fa256 100644
--- a/src/input/RotaryEncoderImpl.cpp
+++ b/src/input/RotaryEncoderImpl.cpp
@@ -30,7 +30,7 @@ bool RotaryEncoderImpl::init()
     rotary->resetButton();
 
     interruptInstance = this;
-    auto interruptHandler = []() { inputBroker->pollSoonRequestFromIsr(interruptInstance); };
+    auto interruptHandler = []() { inputBroker->requestPollSoon(interruptInstance); };
     attachInterrupt(moduleConfig.canned_message.inputbroker_pin_a, interruptHandler, CHANGE);
     attachInterrupt(moduleConfig.canned_message.inputbroker_pin_b, interruptHandler, CHANGE);
     attachInterrupt(moduleConfig.canned_message.inputbroker_pin_press, interruptHandler, CHANGE);

From edb5c0f88ed9adbf0d0b30bdbc89f69046d6ce26 Mon Sep 17 00:00:00 2001
From: WillyJL 
Date: Wed, 24 Sep 2025 04:19:11 +0200
Subject: [PATCH 334/691] Custom xPortInIsrContext() for nRF52/RP2xx0

---
 src/platform/nrf52/architecture.h  | 3 +++
 src/platform/rp2xx0/architecture.h | 5 ++++-
 2 files changed, 7 insertions(+), 1 deletion(-)

diff --git a/src/platform/nrf52/architecture.h b/src/platform/nrf52/architecture.h
index c9938062e..56b46088a 100644
--- a/src/platform/nrf52/architecture.h
+++ b/src/platform/nrf52/architecture.h
@@ -149,3 +149,6 @@
 // No serial ports on this board - ONLY use segger in memory console
 #define USE_SEGGER
 #endif
+
+// Detect if running in ISR context (ARM Cortex-M4)
+#define xPortInIsrContext() ((SCB->ICSR & SCB_ICSR_VECTACTIVE_Msk) == 0 ? pdFALSE : pdTRUE)
diff --git a/src/platform/rp2xx0/architecture.h b/src/platform/rp2xx0/architecture.h
index 506c19c83..0c168ceee 100644
--- a/src/platform/rp2xx0/architecture.h
+++ b/src/platform/rp2xx0/architecture.h
@@ -35,4 +35,7 @@
 #define HW_VENDOR meshtastic_HardwareModel_RP2040_FEATHER_RFM95
 #elif defined(PRIVATE_HW)
 #define HW_VENDOR meshtastic_HardwareModel_PRIVATE_HW
-#endif
\ No newline at end of file
+#endif
+
+// Detect if running in ISR context (ARM Cortex-M33 / RISC-V)
+#define xPortInIsrContext() (__get_current_exception() == 0 ? pdFALSE : pdTRUE)

From 1ed7aad976ca54ba7cc6b47caf78bc6aaa8622fb Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
 <41898282+github-actions[bot]@users.noreply.github.com>
Date: Wed, 24 Sep 2025 06:01:31 -0500
Subject: [PATCH 335/691] Automated version bumps (#8100)

Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
---
 bin/org.meshtastic.meshtasticd.metainfo.xml |  3 ++
 debian/changelog                            | 45 ++-------------------
 version.properties                          |  2 +-
 3 files changed, 7 insertions(+), 43 deletions(-)

diff --git a/bin/org.meshtastic.meshtasticd.metainfo.xml b/bin/org.meshtastic.meshtasticd.metainfo.xml
index 090c141fa..0d03dd349 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.11
+    
     
       https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.10
     
diff --git a/debian/changelog b/debian/changelog
index 76e390001..15c8604f6 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,49 +1,10 @@
-meshtasticd (2.7.10.0) UNRELEASED; urgency=medium
+meshtasticd (2.7.11.0) UNRELEASED; urgency=medium
 
+  [ Austin Lane ]
   * Initial packaging
   * Version 2.5.19
 
   [  ]
   * GitHub Actions Automatic version bump
 
-  [  ]
-  * GitHub Actions Automatic version bump
-
-  [  ]
-  * GitHub Actions Automatic version bump
-
-  [  ]
-  * GitHub Actions Automatic version bump
-
-  [  ]
-  * GitHub Actions Automatic version bump
-
-  [  ]
-  * GitHub Actions Automatic version bump
-
-  [  ]
-  * GitHub Actions Automatic version bump
-
-  [ Ubuntu ]
-  * GitHub Actions Automatic version bump
-
-  [  ]
-  * GitHub Actions Automatic version bump
-
-  [  ]
-  * GitHub Actions Automatic version bump
-
-  [  ]
-  * GitHub Actions Automatic version bump
-  * GitHub Actions Automatic version bump
-
-  [  ]
-  * GitHub Actions Automatic version bump
-
-  [  ]
-  * GitHub Actions Automatic version bump
-
-  [  ]
-  * GitHub Actions Automatic version bump
-
- --    Thu, 18 Sep 2025 22:11:37 +0000
+ --    Wed, 24 Sep 2025 11:01:13 +0000
diff --git a/version.properties b/version.properties
index cc0449a72..ce1205f2b 100644
--- a/version.properties
+++ b/version.properties
@@ -1,4 +1,4 @@
 [VERSION]  
 major = 2
 minor = 7
-build = 10
+build = 11

From 83be632a1a91d15ad3a33efe8e68df47ec5e1a21 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
 <41898282+github-actions[bot]@users.noreply.github.com>
Date: Wed, 24 Sep 2025 06:01:31 -0500
Subject: [PATCH 336/691] Automated version bumps (#8100)

Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
---
 bin/org.meshtastic.meshtasticd.metainfo.xml | 3 +++
 debian/changelog                            | 8 ++++++--
 version.properties                          | 2 +-
 3 files changed, 10 insertions(+), 3 deletions(-)

diff --git a/bin/org.meshtastic.meshtasticd.metainfo.xml b/bin/org.meshtastic.meshtasticd.metainfo.xml
index 090c141fa..0d03dd349 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.11
+    
     
       https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.10
     
diff --git a/debian/changelog b/debian/changelog
index d4ba6b7a0..15c8604f6 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,6 +1,10 @@
-meshtasticd (2.7.10.0) UNRELEASED; urgency=medium
+meshtasticd (2.7.11.0) UNRELEASED; urgency=medium
 
+  [ Austin Lane ]
   * Initial packaging
   * Version 2.5.19
 
- -- Austin Lane   Thu, 02 Jan 2025 12:00:00 +0000
+  [  ]
+  * GitHub Actions Automatic version bump
+
+ --    Wed, 24 Sep 2025 11:01:13 +0000
diff --git a/version.properties b/version.properties
index cc0449a72..ce1205f2b 100644
--- a/version.properties
+++ b/version.properties
@@ -1,4 +1,4 @@
 [VERSION]  
 major = 2
 minor = 7
-build = 10
+build = 11

From 1835ff2d781ab505e2e5ae28829b97d7591ada5e Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
 <41898282+github-actions[bot]@users.noreply.github.com>
Date: Wed, 24 Sep 2025 06:05:36 -0500
Subject: [PATCH 337/691] Upgrade trunk (#8094)

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 4dd040287..491ab32ba 100644
--- a/.trunk/trunk.yaml
+++ b/.trunk/trunk.yaml
@@ -9,7 +9,7 @@ plugins:
 lint:
   enabled:
     - checkov@3.2.471
-    - renovate@41.118.1
+    - renovate@41.125.3
     - prettier@3.6.2
     - trufflehog@3.90.8
     - yamllint@1.37.1
@@ -23,7 +23,7 @@ lint:
     - svgo@4.0.0
     - actionlint@1.7.7
     - flake8@7.3.0
-    - hadolint@2.13.1
+    - hadolint@2.14.0
     - shfmt@3.6.0
     - shellcheck@0.11.0
     - black@25.9.0

From c33c36831574d58fa7b1478f40ecf6dc41f7406d Mon Sep 17 00:00:00 2001
From: Quency-D <55523105+Quency-D@users.noreply.github.com>
Date: Wed, 24 Sep 2025 19:14:24 +0800
Subject: [PATCH 338/691] Add three expansion screens for heltec mesh solar.
 (#7995)

* Add three expansion screens for heltec mesh solar.

* delete whitespace

Update variants/nrf52840/heltec_mesh_solar/variant.h

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* delete whitespace

Update variants/nrf52840/heltec_mesh_solar/platformio.ini

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Ben Meadors 
---
 src/graphics/EInkDisplay2.cpp                 |  2 +-
 src/graphics/EInkDisplay2.h                   |  2 +-
 .../heltec_mesh_solar/nicheGraphics.h         | 90 +++++++++++++++++
 .../nrf52840/heltec_mesh_solar/platformio.ini | 99 ++++++++++++++++++-
 .../nrf52840/heltec_mesh_solar/variant.cpp    |  5 +
 variants/nrf52840/heltec_mesh_solar/variant.h | 16 ++-
 6 files changed, 202 insertions(+), 12 deletions(-)
 create mode 100644 variants/nrf52840/heltec_mesh_solar/nicheGraphics.h

diff --git a/src/graphics/EInkDisplay2.cpp b/src/graphics/EInkDisplay2.cpp
index c0c09cc27..4209baf5d 100644
--- a/src/graphics/EInkDisplay2.cpp
+++ b/src/graphics/EInkDisplay2.cpp
@@ -243,7 +243,7 @@ bool EInkDisplay::connect()
         adafruitDisplay->setRotation(1);
         adafruitDisplay->setPartialWindow(0, 0, EINK_WIDTH, EINK_HEIGHT);
     }
-#elif defined(HELTEC_MESH_POCKET) || defined(SEEED_WIO_TRACKER_L1_EINK)
+#elif defined(HELTEC_MESH_POCKET) || defined(SEEED_WIO_TRACKER_L1_EINK) || defined(HELTEC_MESH_SOLAR_EINK)
     {
         spi1 = &SPI1;
         spi1->begin();
diff --git a/src/graphics/EInkDisplay2.h b/src/graphics/EInkDisplay2.h
index b4cee81fe..9975527aa 100644
--- a/src/graphics/EInkDisplay2.h
+++ b/src/graphics/EInkDisplay2.h
@@ -84,7 +84,7 @@ class EInkDisplay : public OLEDDisplay
     SPIClass *hspi = NULL;
 #endif
 
-#if defined(HELTEC_MESH_POCKET) || defined(SEEED_WIO_TRACKER_L1_EINK)
+#if defined(HELTEC_MESH_POCKET) || defined(SEEED_WIO_TRACKER_L1_EINK) || defined(HELTEC_MESH_SOLAR_EINK)
     SPIClass *spi1 = NULL;
 #endif
 
diff --git a/variants/nrf52840/heltec_mesh_solar/nicheGraphics.h b/variants/nrf52840/heltec_mesh_solar/nicheGraphics.h
new file mode 100644
index 000000000..125f50590
--- /dev/null
+++ b/variants/nrf52840/heltec_mesh_solar/nicheGraphics.h
@@ -0,0 +1,90 @@
+#pragma once
+
+#include "configuration.h"
+
+#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
+
+// InkHUD-specific components
+// ---------------------------
+#include "graphics/niche/InkHUD/InkHUD.h"
+
+// Applets
+#include "graphics/niche/InkHUD/Applets/User/AllMessage/AllMessageApplet.h"
+#include "graphics/niche/InkHUD/Applets/User/DM/DMApplet.h"
+#include "graphics/niche/InkHUD/Applets/User/Heard/HeardApplet.h"
+#include "graphics/niche/InkHUD/Applets/User/Positions/PositionsApplet.h"
+#include "graphics/niche/InkHUD/Applets/User/RecentsList/RecentsListApplet.h"
+#include "graphics/niche/InkHUD/Applets/User/ThreadedMessage/ThreadedMessageApplet.h"
+
+// Shared NicheGraphics components
+// --------------------------------
+#include "graphics/niche/Drivers/EInk/E0213A367.h"
+#include "graphics/niche/Inputs/TwoButton.h"
+
+void setupNicheGraphics()
+{
+    using namespace NicheGraphics;
+
+    // SPI
+    // -----------------------------
+
+    // For NRF52 platforms, SPI pins are defined in variant.h
+    SPI1.begin();
+
+    // E-Ink Driver
+    // -----------------------------
+
+    Drivers::EInk *driver = new Drivers::E0213A367;
+    driver->begin(&SPI1, PIN_EINK_DC, PIN_EINK_CS, PIN_EINK_BUSY, PIN_EINK_RES);
+
+    // InkHUD
+    // ----------------------------
+
+    InkHUD::InkHUD *inkhud = InkHUD::InkHUD::getInstance();
+
+    // Set the E-Ink driver
+    inkhud->setDriver(driver);
+
+    // Set how many FAST updates per FULL update
+    // Set how unhealthy additional FAST updates beyond this number are
+    inkhud->setDisplayResilience(10, 1.5);
+
+    // Select fonts
+    InkHUD::Applet::fontLarge = FREESANS_12PT_WIN1252;
+    InkHUD::Applet::fontMedium = FREESANS_9PT_WIN1252;
+    InkHUD::Applet::fontSmall = FREESANS_6PT_WIN1252;
+
+    // Customize default settings
+    inkhud->persistence->settings.userTiles.maxCount = 2; // How many tiles can the display handle?
+    inkhud->persistence->settings.rotation = 3;           // 270 degrees clockwise
+    inkhud->persistence->settings.userTiles.count = 1;    // One tile only by default, keep things simple for new users
+    inkhud->persistence->settings.optionalMenuItems.nextTile = true;
+
+    // Pick applets
+    // Note: order of applets determines priority of "auto-show" feature
+    inkhud->addApplet("All Messages", new InkHUD::AllMessageApplet, true, true); // Activated, autoshown
+    inkhud->addApplet("DMs", new InkHUD::DMApplet);                              // -
+    inkhud->addApplet("Channel 0", new InkHUD::ThreadedMessageApplet(0));        // -
+    inkhud->addApplet("Channel 1", new InkHUD::ThreadedMessageApplet(1));        // -
+    inkhud->addApplet("Positions", new InkHUD::PositionsApplet, true);           // Activated
+    inkhud->addApplet("Recents List", new InkHUD::RecentsListApplet);            // -
+    inkhud->addApplet("Heard", new InkHUD::HeardApplet, true, false, 0);         // Activated, no autoshow, default on tile 0
+
+    // Start running InkHUD
+    inkhud->begin();
+
+    // Buttons
+    // --------------------------
+
+    Inputs::TwoButton *buttons = Inputs::TwoButton::getInstance(); // Shared NicheGraphics component
+
+    // #0: Main User Button
+    buttons->setWiring(0, Inputs::TwoButton::getUserButtonPin());
+    buttons->setHandlerShortPress(0, [inkhud]() { inkhud->shortpress(); });
+    buttons->setHandlerLongPress(0, [inkhud]() { inkhud->longpress(); });
+
+    // Begin handling button events
+    buttons->start();
+}
+
+#endif
\ No newline at end of file
diff --git a/variants/nrf52840/heltec_mesh_solar/platformio.ini b/variants/nrf52840/heltec_mesh_solar/platformio.ini
index 65d26dc40..625dd1d43 100644
--- a/variants/nrf52840/heltec_mesh_solar/platformio.ini
+++ b/variants/nrf52840/heltec_mesh_solar/platformio.ini
@@ -1,5 +1,5 @@
 ; First prototype nrf52840/sx1262 device
-[env:heltec-mesh-solar]
+[heltec_mesh_solar_base]
 extends = nrf52840_base
 board = heltec_mesh_solar
 board_level = pr
@@ -17,3 +17,100 @@ lib_deps =
   https://github.com/NMIoT/meshsolar/archive/dfc5330dad443982e6cdd37a61d33fc7252f468b.zip
   lewisxhe/PCF8563_Library@^1.0.1
   ArduinoJson@6.21.4
+[env:heltec-mesh-solar]
+extends = heltec_mesh_solar_base
+build_flags = ${heltec_mesh_solar_base.build_flags}
+    -DSPI_INTERFACES_COUNT=1
+
+[env:heltec-mesh-solar-eink]
+extends = heltec_mesh_solar_base
+build_flags = ${heltec_mesh_solar_base.build_flags}
+  -DHELTEC_MESH_SOLAR_EINK
+  -DSPI_INTERFACES_COUNT=2
+  -DUSE_EINK
+  -DPIN_SCREEN_VDD_CTL=3
+  -DPIN_EINK_CS=41
+  -DPIN_EINK_BUSY=11
+  -DPIN_EINK_DC=13
+  -DPIN_EINK_RES=40
+  -DPIN_EINK_SCLK=12
+  -DPIN_EINK_MOSI=2
+  -DPIN_SPI1_MISO=-1
+  -DPIN_SPI1_MOSI=PIN_EINK_MOSI
+  -DPIN_SPI1_SCK=PIN_EINK_SCLK
+  -DEINK_DISPLAY_MODEL=GxEPD2_213_E0213A367
+  -DEINK_WIDTH=250
+  -DEINK_HEIGHT=122
+  -DUSE_EINK_DYNAMICDISPLAY            ; Enable Dynamic EInk
+  -DEINK_LIMIT_FASTREFRESH=10          ; How many consecutive fast-refreshes are permitted
+  -DEINK_LIMIT_RATE_BACKGROUND_SEC=30  ; Minimum interval between BACKGROUND updates
+  -DEINK_LIMIT_RATE_RESPONSIVE_SEC=1   ; Minimum interval between RESPONSIVE updates
+  -DEINK_BACKGROUND_USES_FAST          ; (Optional) Use FAST refresh for both BACKGROUND and RESPONSIVE, until a limit is reached.
+  -DEINK_HASQUIRK_GHOSTING             ; Display model is identified as "prone to ghosting"
+lib_deps =
+  ${heltec_mesh_solar_base.lib_deps}
+  https://github.com/meshtastic/GxEPD2/archive/a05c11c02862624266b61599b0d6ba93e33c6f24.zip
+
+[env:heltec-mesh-solar-inkhud]
+extends = heltec_mesh_solar_base, inkhud
+build_src_filter = ${heltec_mesh_solar_base.build_src_filter} ${inkhud.build_src_filter}
+build_flags = ${heltec_mesh_solar_base.build_flags}
+  ${inkhud.build_flags}
+  -DHELTEC_MESH_SOLAR_INKHUD
+  -DSPI_INTERFACES_COUNT=2
+  -DPIN_SCREEN_VDD_CTL=3
+  -DPIN_EINK_CS=41
+  -DPIN_EINK_BUSY=11
+  -DPIN_EINK_DC=13
+  -DPIN_EINK_RES=40
+  -DPIN_EINK_SCLK=12
+  -DPIN_EINK_MOSI=2
+  -DPIN_SPI1_MISO=-1
+  -DPIN_SPI1_MOSI=PIN_EINK_MOSI
+  -DPIN_SPI1_SCK=PIN_EINK_SCLK
+lib_deps =
+  ${inkhud.lib_deps} ; InkHUD libs first, so we get GFXRoot instead of AdafruitGFX
+  ${heltec_mesh_solar_base.lib_deps}
+
+
+[env:heltec-mesh-solar-oled]
+extends = heltec_mesh_solar_base
+build_flags = ${heltec_mesh_solar_base.build_flags}
+  -DHELTEC_MESH_SOLAR_OLED
+  -DSPI_INTERFACES_COUNT=1
+  -DPIN_SCREEN_VDD_CTL=3
+  -DHAS_SCREEN=1
+  -DRESET_OLED=40
+  -DPIN_WIRE_SDA=2
+  -DPIN_WIRE_SCL=12
+
+[env:heltec-mesh-solar-tft]
+extends = heltec_mesh_solar_base
+build_flags = ${heltec_mesh_solar_base.build_flags}
+  -DHELTEC_MESH_SOLAR_TFT
+  -DSPI_INTERFACES_COUNT=2
+  -DUSE_ST7789
+  -DST7789_NSS=41
+  -DST7789_RS=13
+  -DST7789_SDA=2
+  -DST7789_SCK=12
+  -DST7789_RESET=40
+  -DST7789_MISO=-1
+  -DST7789_BUSY=-1
+  -DVTFT_CTRL=3
+  -DVTFT_LEDA=11
+  -DTFT_BACKLIGHT_ON=HIGH
+  -DST7789_SPI_HOST=SPI2_HOST
+  -DSPI_FREQUENCY=10000000
+  -DSPI_READ_FREQUENCY=10000000
+  -DTFT_HEIGHT=170
+  -DTFT_WIDTH=320
+  -DTFT_OFFSET_X=0
+  -DTFT_OFFSET_Y=0
+  -DBRIGHTNESS_DEFAULT=100
+  -DPIN_SPI1_MISO=ST7789_MISO
+  -DPIN_SPI1_MOSI=ST7789_SDA
+  -DPIN_SPI1_SCK=ST7789_SCK
+lib_deps =
+  ${heltec_mesh_solar_base.lib_deps}
+  https://github.com/meshtastic/st7789/archive/bd33ea58ddfe4a5e4a66d53300ccbd38d66ac21f.zip
diff --git a/variants/nrf52840/heltec_mesh_solar/variant.cpp b/variants/nrf52840/heltec_mesh_solar/variant.cpp
index 8236d7cf4..ab54621cc 100644
--- a/variants/nrf52840/heltec_mesh_solar/variant.cpp
+++ b/variants/nrf52840/heltec_mesh_solar/variant.cpp
@@ -33,4 +33,9 @@ const uint32_t g_ADigitalPinMap[] = {
 void initVariant()
 {
   pinMode(BQ4050_EMERGENCY_SHUTDOWN_PIN, INPUT);
+#if defined(PIN_SCREEN_VDD_CTL)
+  pinMode(PIN_SCREEN_VDD_CTL, OUTPUT);
+  digitalWrite(PIN_SCREEN_VDD_CTL, LOW); // Start with power on
+#endif
+
 }
diff --git a/variants/nrf52840/heltec_mesh_solar/variant.h b/variants/nrf52840/heltec_mesh_solar/variant.h
index 33c2b2556..161a66a5a 100644
--- a/variants/nrf52840/heltec_mesh_solar/variant.h
+++ b/variants/nrf52840/heltec_mesh_solar/variant.h
@@ -40,7 +40,7 @@ extern "C" {
 #define NUM_ANALOG_OUTPUTS (0)
 
 
-#define PIN_LED1 (0 + 12) // green (confirmed on 1.0 board)
+#define PIN_LED1 (0 + 4) // green (confirmed on 1.0 board)
 #define LED_BLUE PIN_LED1 // fake for bluefruit library
 #define LED_GREEN PIN_LED1
 #define LED_BUILTIN LED_GREEN
@@ -63,7 +63,6 @@ No longer populated on PCB
 */
 #define PIN_SERIAL2_RX (0 + 9)
 #define PIN_SERIAL2_TX (0 + 10)
-//  #define PIN_SERIAL2_EN (0 + 17)
 
 /*
  * I2C
@@ -71,16 +70,16 @@ No longer populated on PCB
 
 #define WIRE_INTERFACES_COUNT 2
 
+#ifndef HELTEC_MESH_SOLAR_OLED
 // I2C bus 0
-// Routed to footprint for PCF8563TS RTC
-// Not populated on T114 V1, maybe in future?
-#define PIN_WIRE_SDA (0 + 6) // P0.26
-#define PIN_WIRE_SCL (0 + 26) // P0.26
+#define PIN_WIRE_SDA (0 + 6)
+#define PIN_WIRE_SCL (0 + 26)
+#endif
 
 // I2C bus 1
 // Available on header pins, for general use
-#define PIN_WIRE1_SDA (0 + 30) // P0.30
-#define PIN_WIRE1_SCL (0 + 5) // P0.13
+#define PIN_WIRE1_SDA (0 + 30)
+#define PIN_WIRE1_SCL (0 + 5)
 
 /*
  * Lora radio
@@ -129,7 +128,6 @@ No longer populated on PCB
 /*
  * SPI Interfaces
  */
-#define SPI_INTERFACES_COUNT 1
 
 // For LORA, spi 0
 #define PIN_SPI_MISO (0 + 23)

From ca3c45a2f39a947fdcd80dbd33f5ee6699701b4e Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Wed, 24 Sep 2025 06:15:00 -0500
Subject: [PATCH 339/691] Update Adafruit BusIO to v1.17.4 (#8098)

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 5da8c2e67..bd2e8fa37 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -125,7 +125,7 @@ lib_deps =
 [environmental_base]
 lib_deps =
 	# renovate: datasource=custom.pio depName=Adafruit BusIO packageName=adafruit/library/Adafruit BusIO
-	adafruit/Adafruit BusIO@1.17.3
+	adafruit/Adafruit BusIO@1.17.4
 	# renovate: datasource=custom.pio depName=Adafruit Unified Sensor packageName=adafruit/library/Adafruit Unified Sensor
 	adafruit/Adafruit Unified Sensor@1.1.15
 	# renovate: datasource=custom.pio depName=Adafruit BMP280 packageName=adafruit/library/Adafruit BMP280 Library

From 949f881ae8cc9ec9c2a330168ef81a80be8db4a4 Mon Sep 17 00:00:00 2001
From: Quency-D <55523105+Quency-D@users.noreply.github.com>
Date: Wed, 24 Sep 2025 19:14:24 +0800
Subject: [PATCH 340/691] Add three expansion screens for heltec mesh solar.
 (#7995)

* Add three expansion screens for heltec mesh solar.

* delete whitespace

Update variants/nrf52840/heltec_mesh_solar/variant.h

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* delete whitespace

Update variants/nrf52840/heltec_mesh_solar/platformio.ini

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Ben Meadors 
---
 src/graphics/EInkDisplay2.cpp                 |  2 +-
 src/graphics/EInkDisplay2.h                   |  2 +-
 .../heltec_mesh_solar/nicheGraphics.h         | 90 +++++++++++++++++
 .../nrf52840/heltec_mesh_solar/platformio.ini | 99 ++++++++++++++++++-
 .../nrf52840/heltec_mesh_solar/variant.cpp    |  5 +
 variants/nrf52840/heltec_mesh_solar/variant.h | 16 ++-
 6 files changed, 202 insertions(+), 12 deletions(-)
 create mode 100644 variants/nrf52840/heltec_mesh_solar/nicheGraphics.h

diff --git a/src/graphics/EInkDisplay2.cpp b/src/graphics/EInkDisplay2.cpp
index c0c09cc27..4209baf5d 100644
--- a/src/graphics/EInkDisplay2.cpp
+++ b/src/graphics/EInkDisplay2.cpp
@@ -243,7 +243,7 @@ bool EInkDisplay::connect()
         adafruitDisplay->setRotation(1);
         adafruitDisplay->setPartialWindow(0, 0, EINK_WIDTH, EINK_HEIGHT);
     }
-#elif defined(HELTEC_MESH_POCKET) || defined(SEEED_WIO_TRACKER_L1_EINK)
+#elif defined(HELTEC_MESH_POCKET) || defined(SEEED_WIO_TRACKER_L1_EINK) || defined(HELTEC_MESH_SOLAR_EINK)
     {
         spi1 = &SPI1;
         spi1->begin();
diff --git a/src/graphics/EInkDisplay2.h b/src/graphics/EInkDisplay2.h
index b4cee81fe..9975527aa 100644
--- a/src/graphics/EInkDisplay2.h
+++ b/src/graphics/EInkDisplay2.h
@@ -84,7 +84,7 @@ class EInkDisplay : public OLEDDisplay
     SPIClass *hspi = NULL;
 #endif
 
-#if defined(HELTEC_MESH_POCKET) || defined(SEEED_WIO_TRACKER_L1_EINK)
+#if defined(HELTEC_MESH_POCKET) || defined(SEEED_WIO_TRACKER_L1_EINK) || defined(HELTEC_MESH_SOLAR_EINK)
     SPIClass *spi1 = NULL;
 #endif
 
diff --git a/variants/nrf52840/heltec_mesh_solar/nicheGraphics.h b/variants/nrf52840/heltec_mesh_solar/nicheGraphics.h
new file mode 100644
index 000000000..125f50590
--- /dev/null
+++ b/variants/nrf52840/heltec_mesh_solar/nicheGraphics.h
@@ -0,0 +1,90 @@
+#pragma once
+
+#include "configuration.h"
+
+#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
+
+// InkHUD-specific components
+// ---------------------------
+#include "graphics/niche/InkHUD/InkHUD.h"
+
+// Applets
+#include "graphics/niche/InkHUD/Applets/User/AllMessage/AllMessageApplet.h"
+#include "graphics/niche/InkHUD/Applets/User/DM/DMApplet.h"
+#include "graphics/niche/InkHUD/Applets/User/Heard/HeardApplet.h"
+#include "graphics/niche/InkHUD/Applets/User/Positions/PositionsApplet.h"
+#include "graphics/niche/InkHUD/Applets/User/RecentsList/RecentsListApplet.h"
+#include "graphics/niche/InkHUD/Applets/User/ThreadedMessage/ThreadedMessageApplet.h"
+
+// Shared NicheGraphics components
+// --------------------------------
+#include "graphics/niche/Drivers/EInk/E0213A367.h"
+#include "graphics/niche/Inputs/TwoButton.h"
+
+void setupNicheGraphics()
+{
+    using namespace NicheGraphics;
+
+    // SPI
+    // -----------------------------
+
+    // For NRF52 platforms, SPI pins are defined in variant.h
+    SPI1.begin();
+
+    // E-Ink Driver
+    // -----------------------------
+
+    Drivers::EInk *driver = new Drivers::E0213A367;
+    driver->begin(&SPI1, PIN_EINK_DC, PIN_EINK_CS, PIN_EINK_BUSY, PIN_EINK_RES);
+
+    // InkHUD
+    // ----------------------------
+
+    InkHUD::InkHUD *inkhud = InkHUD::InkHUD::getInstance();
+
+    // Set the E-Ink driver
+    inkhud->setDriver(driver);
+
+    // Set how many FAST updates per FULL update
+    // Set how unhealthy additional FAST updates beyond this number are
+    inkhud->setDisplayResilience(10, 1.5);
+
+    // Select fonts
+    InkHUD::Applet::fontLarge = FREESANS_12PT_WIN1252;
+    InkHUD::Applet::fontMedium = FREESANS_9PT_WIN1252;
+    InkHUD::Applet::fontSmall = FREESANS_6PT_WIN1252;
+
+    // Customize default settings
+    inkhud->persistence->settings.userTiles.maxCount = 2; // How many tiles can the display handle?
+    inkhud->persistence->settings.rotation = 3;           // 270 degrees clockwise
+    inkhud->persistence->settings.userTiles.count = 1;    // One tile only by default, keep things simple for new users
+    inkhud->persistence->settings.optionalMenuItems.nextTile = true;
+
+    // Pick applets
+    // Note: order of applets determines priority of "auto-show" feature
+    inkhud->addApplet("All Messages", new InkHUD::AllMessageApplet, true, true); // Activated, autoshown
+    inkhud->addApplet("DMs", new InkHUD::DMApplet);                              // -
+    inkhud->addApplet("Channel 0", new InkHUD::ThreadedMessageApplet(0));        // -
+    inkhud->addApplet("Channel 1", new InkHUD::ThreadedMessageApplet(1));        // -
+    inkhud->addApplet("Positions", new InkHUD::PositionsApplet, true);           // Activated
+    inkhud->addApplet("Recents List", new InkHUD::RecentsListApplet);            // -
+    inkhud->addApplet("Heard", new InkHUD::HeardApplet, true, false, 0);         // Activated, no autoshow, default on tile 0
+
+    // Start running InkHUD
+    inkhud->begin();
+
+    // Buttons
+    // --------------------------
+
+    Inputs::TwoButton *buttons = Inputs::TwoButton::getInstance(); // Shared NicheGraphics component
+
+    // #0: Main User Button
+    buttons->setWiring(0, Inputs::TwoButton::getUserButtonPin());
+    buttons->setHandlerShortPress(0, [inkhud]() { inkhud->shortpress(); });
+    buttons->setHandlerLongPress(0, [inkhud]() { inkhud->longpress(); });
+
+    // Begin handling button events
+    buttons->start();
+}
+
+#endif
\ No newline at end of file
diff --git a/variants/nrf52840/heltec_mesh_solar/platformio.ini b/variants/nrf52840/heltec_mesh_solar/platformio.ini
index 65d26dc40..625dd1d43 100644
--- a/variants/nrf52840/heltec_mesh_solar/platformio.ini
+++ b/variants/nrf52840/heltec_mesh_solar/platformio.ini
@@ -1,5 +1,5 @@
 ; First prototype nrf52840/sx1262 device
-[env:heltec-mesh-solar]
+[heltec_mesh_solar_base]
 extends = nrf52840_base
 board = heltec_mesh_solar
 board_level = pr
@@ -17,3 +17,100 @@ lib_deps =
   https://github.com/NMIoT/meshsolar/archive/dfc5330dad443982e6cdd37a61d33fc7252f468b.zip
   lewisxhe/PCF8563_Library@^1.0.1
   ArduinoJson@6.21.4
+[env:heltec-mesh-solar]
+extends = heltec_mesh_solar_base
+build_flags = ${heltec_mesh_solar_base.build_flags}
+    -DSPI_INTERFACES_COUNT=1
+
+[env:heltec-mesh-solar-eink]
+extends = heltec_mesh_solar_base
+build_flags = ${heltec_mesh_solar_base.build_flags}
+  -DHELTEC_MESH_SOLAR_EINK
+  -DSPI_INTERFACES_COUNT=2
+  -DUSE_EINK
+  -DPIN_SCREEN_VDD_CTL=3
+  -DPIN_EINK_CS=41
+  -DPIN_EINK_BUSY=11
+  -DPIN_EINK_DC=13
+  -DPIN_EINK_RES=40
+  -DPIN_EINK_SCLK=12
+  -DPIN_EINK_MOSI=2
+  -DPIN_SPI1_MISO=-1
+  -DPIN_SPI1_MOSI=PIN_EINK_MOSI
+  -DPIN_SPI1_SCK=PIN_EINK_SCLK
+  -DEINK_DISPLAY_MODEL=GxEPD2_213_E0213A367
+  -DEINK_WIDTH=250
+  -DEINK_HEIGHT=122
+  -DUSE_EINK_DYNAMICDISPLAY            ; Enable Dynamic EInk
+  -DEINK_LIMIT_FASTREFRESH=10          ; How many consecutive fast-refreshes are permitted
+  -DEINK_LIMIT_RATE_BACKGROUND_SEC=30  ; Minimum interval between BACKGROUND updates
+  -DEINK_LIMIT_RATE_RESPONSIVE_SEC=1   ; Minimum interval between RESPONSIVE updates
+  -DEINK_BACKGROUND_USES_FAST          ; (Optional) Use FAST refresh for both BACKGROUND and RESPONSIVE, until a limit is reached.
+  -DEINK_HASQUIRK_GHOSTING             ; Display model is identified as "prone to ghosting"
+lib_deps =
+  ${heltec_mesh_solar_base.lib_deps}
+  https://github.com/meshtastic/GxEPD2/archive/a05c11c02862624266b61599b0d6ba93e33c6f24.zip
+
+[env:heltec-mesh-solar-inkhud]
+extends = heltec_mesh_solar_base, inkhud
+build_src_filter = ${heltec_mesh_solar_base.build_src_filter} ${inkhud.build_src_filter}
+build_flags = ${heltec_mesh_solar_base.build_flags}
+  ${inkhud.build_flags}
+  -DHELTEC_MESH_SOLAR_INKHUD
+  -DSPI_INTERFACES_COUNT=2
+  -DPIN_SCREEN_VDD_CTL=3
+  -DPIN_EINK_CS=41
+  -DPIN_EINK_BUSY=11
+  -DPIN_EINK_DC=13
+  -DPIN_EINK_RES=40
+  -DPIN_EINK_SCLK=12
+  -DPIN_EINK_MOSI=2
+  -DPIN_SPI1_MISO=-1
+  -DPIN_SPI1_MOSI=PIN_EINK_MOSI
+  -DPIN_SPI1_SCK=PIN_EINK_SCLK
+lib_deps =
+  ${inkhud.lib_deps} ; InkHUD libs first, so we get GFXRoot instead of AdafruitGFX
+  ${heltec_mesh_solar_base.lib_deps}
+
+
+[env:heltec-mesh-solar-oled]
+extends = heltec_mesh_solar_base
+build_flags = ${heltec_mesh_solar_base.build_flags}
+  -DHELTEC_MESH_SOLAR_OLED
+  -DSPI_INTERFACES_COUNT=1
+  -DPIN_SCREEN_VDD_CTL=3
+  -DHAS_SCREEN=1
+  -DRESET_OLED=40
+  -DPIN_WIRE_SDA=2
+  -DPIN_WIRE_SCL=12
+
+[env:heltec-mesh-solar-tft]
+extends = heltec_mesh_solar_base
+build_flags = ${heltec_mesh_solar_base.build_flags}
+  -DHELTEC_MESH_SOLAR_TFT
+  -DSPI_INTERFACES_COUNT=2
+  -DUSE_ST7789
+  -DST7789_NSS=41
+  -DST7789_RS=13
+  -DST7789_SDA=2
+  -DST7789_SCK=12
+  -DST7789_RESET=40
+  -DST7789_MISO=-1
+  -DST7789_BUSY=-1
+  -DVTFT_CTRL=3
+  -DVTFT_LEDA=11
+  -DTFT_BACKLIGHT_ON=HIGH
+  -DST7789_SPI_HOST=SPI2_HOST
+  -DSPI_FREQUENCY=10000000
+  -DSPI_READ_FREQUENCY=10000000
+  -DTFT_HEIGHT=170
+  -DTFT_WIDTH=320
+  -DTFT_OFFSET_X=0
+  -DTFT_OFFSET_Y=0
+  -DBRIGHTNESS_DEFAULT=100
+  -DPIN_SPI1_MISO=ST7789_MISO
+  -DPIN_SPI1_MOSI=ST7789_SDA
+  -DPIN_SPI1_SCK=ST7789_SCK
+lib_deps =
+  ${heltec_mesh_solar_base.lib_deps}
+  https://github.com/meshtastic/st7789/archive/bd33ea58ddfe4a5e4a66d53300ccbd38d66ac21f.zip
diff --git a/variants/nrf52840/heltec_mesh_solar/variant.cpp b/variants/nrf52840/heltec_mesh_solar/variant.cpp
index 8236d7cf4..ab54621cc 100644
--- a/variants/nrf52840/heltec_mesh_solar/variant.cpp
+++ b/variants/nrf52840/heltec_mesh_solar/variant.cpp
@@ -33,4 +33,9 @@ const uint32_t g_ADigitalPinMap[] = {
 void initVariant()
 {
   pinMode(BQ4050_EMERGENCY_SHUTDOWN_PIN, INPUT);
+#if defined(PIN_SCREEN_VDD_CTL)
+  pinMode(PIN_SCREEN_VDD_CTL, OUTPUT);
+  digitalWrite(PIN_SCREEN_VDD_CTL, LOW); // Start with power on
+#endif
+
 }
diff --git a/variants/nrf52840/heltec_mesh_solar/variant.h b/variants/nrf52840/heltec_mesh_solar/variant.h
index 33c2b2556..161a66a5a 100644
--- a/variants/nrf52840/heltec_mesh_solar/variant.h
+++ b/variants/nrf52840/heltec_mesh_solar/variant.h
@@ -40,7 +40,7 @@ extern "C" {
 #define NUM_ANALOG_OUTPUTS (0)
 
 
-#define PIN_LED1 (0 + 12) // green (confirmed on 1.0 board)
+#define PIN_LED1 (0 + 4) // green (confirmed on 1.0 board)
 #define LED_BLUE PIN_LED1 // fake for bluefruit library
 #define LED_GREEN PIN_LED1
 #define LED_BUILTIN LED_GREEN
@@ -63,7 +63,6 @@ No longer populated on PCB
 */
 #define PIN_SERIAL2_RX (0 + 9)
 #define PIN_SERIAL2_TX (0 + 10)
-//  #define PIN_SERIAL2_EN (0 + 17)
 
 /*
  * I2C
@@ -71,16 +70,16 @@ No longer populated on PCB
 
 #define WIRE_INTERFACES_COUNT 2
 
+#ifndef HELTEC_MESH_SOLAR_OLED
 // I2C bus 0
-// Routed to footprint for PCF8563TS RTC
-// Not populated on T114 V1, maybe in future?
-#define PIN_WIRE_SDA (0 + 6) // P0.26
-#define PIN_WIRE_SCL (0 + 26) // P0.26
+#define PIN_WIRE_SDA (0 + 6)
+#define PIN_WIRE_SCL (0 + 26)
+#endif
 
 // I2C bus 1
 // Available on header pins, for general use
-#define PIN_WIRE1_SDA (0 + 30) // P0.30
-#define PIN_WIRE1_SCL (0 + 5) // P0.13
+#define PIN_WIRE1_SDA (0 + 30)
+#define PIN_WIRE1_SCL (0 + 5)
 
 /*
  * Lora radio
@@ -129,7 +128,6 @@ No longer populated on PCB
 /*
  * SPI Interfaces
  */
-#define SPI_INTERFACES_COUNT 1
 
 // For LORA, spi 0
 #define PIN_SPI_MISO (0 + 23)

From db55d8a59d7fe8da4a5762a1ee25c4d99de88da5 Mon Sep 17 00:00:00 2001
From: Ben Meadors 
Date: Wed, 24 Sep 2025 06:16:38 -0500
Subject: [PATCH 341/691] Trunk

---
 .../nrf52840/heltec_mesh_solar/variant.cpp    |  7 +++----
 variants/nrf52840/heltec_mesh_solar/variant.h | 19 +++++++++----------
 2 files changed, 12 insertions(+), 14 deletions(-)

diff --git a/variants/nrf52840/heltec_mesh_solar/variant.cpp b/variants/nrf52840/heltec_mesh_solar/variant.cpp
index ab54621cc..c13f006d7 100644
--- a/variants/nrf52840/heltec_mesh_solar/variant.cpp
+++ b/variants/nrf52840/heltec_mesh_solar/variant.cpp
@@ -32,10 +32,9 @@ const uint32_t g_ADigitalPinMap[] = {
 
 void initVariant()
 {
-  pinMode(BQ4050_EMERGENCY_SHUTDOWN_PIN, INPUT);
+    pinMode(BQ4050_EMERGENCY_SHUTDOWN_PIN, INPUT);
 #if defined(PIN_SCREEN_VDD_CTL)
-  pinMode(PIN_SCREEN_VDD_CTL, OUTPUT);
-  digitalWrite(PIN_SCREEN_VDD_CTL, LOW); // Start with power on
+    pinMode(PIN_SCREEN_VDD_CTL, OUTPUT);
+    digitalWrite(PIN_SCREEN_VDD_CTL, LOW); // Start with power on
 #endif
-
 }
diff --git a/variants/nrf52840/heltec_mesh_solar/variant.h b/variants/nrf52840/heltec_mesh_solar/variant.h
index 161a66a5a..7c43d8ba7 100644
--- a/variants/nrf52840/heltec_mesh_solar/variant.h
+++ b/variants/nrf52840/heltec_mesh_solar/variant.h
@@ -39,16 +39,15 @@ extern "C" {
 #define NUM_ANALOG_INPUTS (1)
 #define NUM_ANALOG_OUTPUTS (0)
 
-
-#define PIN_LED1 (0 + 4) // green (confirmed on 1.0 board)
+#define PIN_LED1 (0 + 4)  // green (confirmed on 1.0 board)
 #define LED_BLUE PIN_LED1 // fake for bluefruit library
 #define LED_GREEN PIN_LED1
 #define LED_BUILTIN LED_GREEN
-#define LED_STATE_ON 0  // State when LED is lit
+#define LED_STATE_ON 0 // State when LED is lit
 
 #define HAS_NEOPIXEL                         // Enable the use of neopixels
 #define NEOPIXEL_COUNT 1                     // How many neopixels are connected
-#define NEOPIXEL_DATA (32+15)                // gpio pin used to send data to the neopixels
+#define NEOPIXEL_DATA (32 + 15)              // gpio pin used to send data to the neopixels
 #define NEOPIXEL_TYPE (NEO_GRB + NEO_KHZ800) // type of neopixels in use
 
 /*
@@ -88,14 +87,14 @@ No longer populated on PCB
 #define USE_SX1262
 // #define USE_SX1268
 #define SX126X_CS (0 + 24) // FIXME - we really should define LORA_CS instead
-#define LORA_CS  (0 + 24)
+#define LORA_CS (0 + 24)
 #define SX126X_DIO1 (0 + 20)
 // Note DIO2 is attached internally to the module to an analog switch for TX/RX switching
 // #define SX1262_DIO3 (0 + 21)
 // This is used as an *output* from the sx1262 and connected internally to power the tcxo, do not drive from the
 //    main
 // CPU?
-#define SX126X_BUSY  (0 + 17)
+#define SX126X_BUSY (0 + 17)
 #define SX126X_RESET (0 + 25)
 // Not really an E22 but TTGO seems to be trying to clone that
 #define SX126X_DIO2_AS_RF_SWITCH
@@ -132,16 +131,16 @@ No longer populated on PCB
 // For LORA, spi 0
 #define PIN_SPI_MISO (0 + 23)
 #define PIN_SPI_MOSI (0 + 22)
-#define PIN_SPI_SCK  (0 + 19)
+#define PIN_SPI_SCK (0 + 19)
 
 // #define PIN_PWR_EN (0 + 6)
 
 // To debug via the segger JLINK console rather than the CDC-ACM serial device
 // #define USE_SEGGER
 
-#define BQ4050_SDA_PIN                      (32+1) // I2C data line pin
-#define BQ4050_SCL_PIN                      (32+0) // I2C clock line pin
-#define BQ4050_EMERGENCY_SHUTDOWN_PIN       (32+3) // Emergency shutdown pin
+#define BQ4050_SDA_PIN (32 + 1)                // I2C data line pin
+#define BQ4050_SCL_PIN (32 + 0)                // I2C clock line pin
+#define BQ4050_EMERGENCY_SHUTDOWN_PIN (32 + 3) // Emergency shutdown pin
 
 #define HAS_RTC 0
 #ifdef __cplusplus

From 14e64d6b9e0987c62e1ec6e1bf62b5b57dc27b3a Mon Sep 17 00:00:00 2001
From: Links2004 
Date: Wed, 24 Sep 2025 16:45:40 +0200
Subject: [PATCH 342/691] move SerialConsole to event based trigger

---
 arch/esp32/esp32.ini  |  1 +
 src/SerialConsole.cpp | 29 ++++++++++++++++++++++++-----
 src/SerialConsole.h   |  3 +++
 3 files changed, 28 insertions(+), 5 deletions(-)

diff --git a/arch/esp32/esp32.ini b/arch/esp32/esp32.ini
index 8990053eb..d2c933461 100644
--- a/arch/esp32/esp32.ini
+++ b/arch/esp32/esp32.ini
@@ -36,6 +36,7 @@ build_flags =
   -DCONFIG_BT_NIMBLE_HOST_TASK_STACK_SIZE=8192
   -DESP_OPENSSL_SUPPRESS_LEGACY_WARNING
   -DSERIAL_BUFFER_SIZE=4096
+  -DSERIAL_HAS_ON_RECEIVE
   -DLIBPAX_ARDUINO
   -DLIBPAX_WIFI
   -DLIBPAX_BLE
diff --git a/src/SerialConsole.cpp b/src/SerialConsole.cpp
index 093a24678..2e8c8ab61 100644
--- a/src/SerialConsole.cpp
+++ b/src/SerialConsole.cpp
@@ -22,7 +22,10 @@ SerialConsole *console;
 
 void consoleInit()
 {
-    new SerialConsole(); // Must be dynamically allocated because we are now inheriting from thread
+    auto sc = new SerialConsole(); // Must be dynamically allocated because we are now inheriting from thread
+#ifdef SERIAL_HAS_ON_RECEIVE
+    Port.onReceive([sc]() { sc->rxInt(); });
+#endif
     DEBUG_PORT.rpInit(); // Simply sets up semaphore
 }
 
@@ -65,14 +68,18 @@ SerialConsole::SerialConsole() : StreamAPI(&Port), RedirectablePrint(&Port), con
 int32_t SerialConsole::runOnce()
 {
 #ifdef HELTEC_MESH_SOLAR
-    //After enabling the mesh solar serial port module configuration, command processing is handled by the serial port module.
-    if(moduleConfig.serial.enabled && moduleConfig.serial.override_console_serial_port
-        && moduleConfig.serial.mode==meshtastic_ModuleConfig_SerialConfig_Serial_Mode_MS_CONFIG)
-    {
+    // After enabling the mesh solar serial port module configuration, command processing is handled by the serial port module.
+    if (moduleConfig.serial.enabled && moduleConfig.serial.override_console_serial_port &&
+        moduleConfig.serial.mode == meshtastic_ModuleConfig_SerialConfig_Serial_Mode_MS_CONFIG) {
         return 250;
     }
 #endif
+#ifdef SERIAL_HAS_ON_RECEIVE
+    int32_t delay = runOncePart();
+    return Port.available() ? delay : INT32_MAX;
+#else
     return runOncePart();
+#endif
 }
 
 void SerialConsole::flush()
@@ -80,6 +87,18 @@ void SerialConsole::flush()
     Port.flush();
 }
 
+// trigger tx of serial data
+void SerialConsole::onNowHasData(uint32_t fromRadioNum)
+{
+    setIntervalFromNow(0);
+}
+
+// trigger rx of serial data
+void SerialConsole::rxInt()
+{
+    setIntervalFromNow(0);
+}
+
 // For the serial port we can't really detect if any client is on the other side, so instead just look for recent messages
 bool SerialConsole::checkIsConnected()
 {
diff --git a/src/SerialConsole.h b/src/SerialConsole.h
index f1e636c9d..98577e4bc 100644
--- a/src/SerialConsole.h
+++ b/src/SerialConsole.h
@@ -32,11 +32,14 @@ class SerialConsole : public StreamAPI, public RedirectablePrint, private concur
     virtual int32_t runOnce() override;
 
     void flush();
+    void rxInt();
 
   protected:
     /// Check the current underlying physical link to see if the client is currently connected
     virtual bool checkIsConnected() override;
 
+    virtual void onNowHasData(uint32_t fromRadioNum) override;
+
     /// Possibly switch to protobufs if we see a valid protobuf message
     virtual void log_to_serial(const char *logLevel, const char *format, va_list arg);
 };

From 91e2e3f0e8a33c840a0d7de09a60cca68c4d7647 Mon Sep 17 00:00:00 2001
From: Links2004 
Date: Wed, 24 Sep 2025 16:47:49 +0200
Subject: [PATCH 343/691] remove OSThread from BuzzerFeedbackThread

---
 src/buzz/BuzzerFeedbackThread.cpp | 19 ++-----------------
 src/buzz/BuzzerFeedbackThread.h   |  9 +--------
 2 files changed, 3 insertions(+), 25 deletions(-)

diff --git a/src/buzz/BuzzerFeedbackThread.cpp b/src/buzz/BuzzerFeedbackThread.cpp
index 12b30a705..afa8c96e2 100644
--- a/src/buzz/BuzzerFeedbackThread.cpp
+++ b/src/buzz/BuzzerFeedbackThread.cpp
@@ -5,7 +5,7 @@
 
 BuzzerFeedbackThread *buzzerFeedbackThread;
 
-BuzzerFeedbackThread::BuzzerFeedbackThread() : OSThread("BuzzerFeedback")
+BuzzerFeedbackThread::BuzzerFeedbackThread()
 {
     if (inputBroker)
         inputObserver.observe(inputBroker);
@@ -19,10 +19,6 @@ int BuzzerFeedbackThread::handleInputEvent(const InputEvent *event)
         return 0; // Let other handlers process the event
     }
 
-    // Track last event time for potential future use
-    lastEventTime = millis();
-    needsUpdate = true;
-
     // Handle different input events with appropriate buzzer feedback
     switch (event->inputEvent) {
     case INPUT_BROKER_USER_PRESS:
@@ -61,15 +57,4 @@ int BuzzerFeedbackThread::handleInputEvent(const InputEvent *event)
     }
 
     return 0; // Allow other handlers to process the event
-}
-
-int32_t BuzzerFeedbackThread::runOnce()
-{
-    // This thread is primarily event-driven, but we can use runOnce
-    // for any periodic tasks if needed in the future
-
-    needsUpdate = false;
-
-    // Run every 100ms when active, less frequently when idle
-    return needsUpdate ? 100 : 1000;
-}
+}
\ No newline at end of file
diff --git a/src/buzz/BuzzerFeedbackThread.h b/src/buzz/BuzzerFeedbackThread.h
index dedea9860..7dc08ead5 100644
--- a/src/buzz/BuzzerFeedbackThread.h
+++ b/src/buzz/BuzzerFeedbackThread.h
@@ -4,7 +4,7 @@
 #include "concurrency/OSThread.h"
 #include "input/InputBroker.h"
 
-class BuzzerFeedbackThread : public concurrency::OSThread
+class BuzzerFeedbackThread
 {
     CallbackObserver inputObserver =
         CallbackObserver(this, &BuzzerFeedbackThread::handleInputEvent);
@@ -12,13 +12,6 @@ class BuzzerFeedbackThread : public concurrency::OSThread
   public:
     BuzzerFeedbackThread();
     int handleInputEvent(const InputEvent *event);
-
-  protected:
-    virtual int32_t runOnce() override;
-
-  private:
-    uint32_t lastEventTime = 0;
-    bool needsUpdate = false;
 };
 
 extern BuzzerFeedbackThread *buzzerFeedbackThread;

From 0b4a28866b2772e4dafa873c5f3fe28d94fb8c55 Mon Sep 17 00:00:00 2001
From: Links2004 
Date: Wed, 24 Sep 2025 16:56:09 +0200
Subject: [PATCH 344/691] add optional debug logging to see which OSThread /
 loops have what delays

---
 platformio.ini               | 1 +
 src/concurrency/OSThread.cpp | 4 +++-
 src/main.cpp                 | 3 +++
 3 files changed, 7 insertions(+), 1 deletion(-)

diff --git a/platformio.ini b/platformio.ini
index bd2e8fa37..f6c0f3867 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -56,6 +56,7 @@ build_flags = -Wno-missing-field-initializers
 	#-DBUILD_EPOCH=$UNIX_TIME ; set in platformio-custom.py now
 	#-D OLED_PL=1
 	#-D DEBUG_HEAP=1 ; uncomment to add free heap space / memory leak debugging logs
+	#-D DEBUG_LOOP_TIMING=1 ; uncomment to add main loop timing logs
 
 monitor_speed = 115200
 monitor_filters = direct
diff --git a/src/concurrency/OSThread.cpp b/src/concurrency/OSThread.cpp
index 5aee03bbf..ce9a256b7 100644
--- a/src/concurrency/OSThread.cpp
+++ b/src/concurrency/OSThread.cpp
@@ -90,7 +90,9 @@ void OSThread::run()
     if (heap < newHeap)
         LOG_HEAP("++++++ Thread %s freed heap %d -> %d (%d) ++++++", ThreadName.c_str(), heap, newHeap, newHeap - heap);
 #endif
-
+#ifdef DEBUG_LOOP_TIMING
+    LOG_DEBUG("====== Thread next run in: %d", newDelay);
+#endif
     runned();
 
     if (newDelay >= 0)
diff --git a/src/main.cpp b/src/main.cpp
index 5a1b361b3..635664eb7 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -1607,6 +1607,9 @@ void loop()
 
     // We want to sleep as long as possible here - because it saves power
     if (!runASAP && loopCanSleep()) {
+#ifdef DEBUG_LOOP_TIMING
+        LOG_DEBUG("main loop delay: %d", delayMsec);
+#endif
         mainDelay.delay(delayMsec);
     }
 }

From 85cdcad1940943f0ed4fb6bccb874f203c5c8bcc Mon Sep 17 00:00:00 2001
From: Links2004 
Date: Wed, 24 Sep 2025 16:58:15 +0200
Subject: [PATCH 345/691] only run the ButtonThread if a button is pressed

---
 src/input/ButtonThread.cpp | 7 ++++++-
 src/main.cpp               | 6 ++++++
 2 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/src/input/ButtonThread.cpp b/src/input/ButtonThread.cpp
index 32882f7ae..1d26e0758 100644
--- a/src/input/ButtonThread.cpp
+++ b/src/input/ButtonThread.cpp
@@ -274,7 +274,12 @@ int32_t ButtonThread::runOnce()
         }
     }
     btnEvent = BUTTON_EVENT_NONE;
-    return 50;
+
+    // only pull when the button is pressed, we get notified via IRQ on a new press
+    if (!userButton.isIdle() || waitingForLongPress) {
+        return 50;
+    }
+    return INT32_MAX;
 }
 
 /*
diff --git a/src/main.cpp b/src/main.cpp
index 635664eb7..38c8e8ca5 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -1002,6 +1002,7 @@ void setup()
             config.pullupSense = INPUT_PULLUP;
             config.intRoutine = []() {
                 UserButtonThread->userButton.tick();
+                UserButtonThread->setIntervalFromNow(0);
                 runASAP = true;
                 BaseType_t higherWake = 0;
                 mainDelay.interruptFromISR(&higherWake);
@@ -1022,6 +1023,7 @@ void setup()
     touchConfig.pullupSense = pullup_sense;
     touchConfig.intRoutine = []() {
         TouchButtonThread->userButton.tick();
+        TouchButtonThread->setIntervalFromNow(0);
         runASAP = true;
         BaseType_t higherWake = 0;
         mainDelay.interruptFromISR(&higherWake);
@@ -1041,6 +1043,7 @@ void setup()
     cancelConfig.pullupSense = pullup_sense;
     cancelConfig.intRoutine = []() {
         CancelButtonThread->userButton.tick();
+        CancelButtonThread->setIntervalFromNow(0);
         runASAP = true;
         BaseType_t higherWake = 0;
         mainDelay.interruptFromISR(&higherWake);
@@ -1061,6 +1064,7 @@ void setup()
     backConfig.pullupSense = pullup_sense;
     backConfig.intRoutine = []() {
         BackButtonThread->userButton.tick();
+        BackButtonThread->setIntervalFromNow(0);
         runASAP = true;
         BaseType_t higherWake = 0;
         mainDelay.interruptFromISR(&higherWake);
@@ -1095,6 +1099,7 @@ void setup()
         userConfig.pullupSense = pullup_sense;
         userConfig.intRoutine = []() {
             UserButtonThread->userButton.tick();
+            UserButtonThread->setIntervalFromNow(0);
             runASAP = true;
             BaseType_t higherWake = 0;
             mainDelay.interruptFromISR(&higherWake);
@@ -1112,6 +1117,7 @@ void setup()
         userConfigNoScreen.pullupSense = pullup_sense;
         userConfigNoScreen.intRoutine = []() {
             UserButtonThread->userButton.tick();
+            UserButtonThread->setIntervalFromNow(0);
             runASAP = true;
             BaseType_t higherWake = 0;
             mainDelay.interruptFromISR(&higherWake);

From 2fdc0d0928ca52b139f948f231395d0f393aed79 Mon Sep 17 00:00:00 2001
From: Links2004 
Date: Wed, 24 Sep 2025 17:02:52 +0200
Subject: [PATCH 346/691] save CPU cycles in ExternalNotificationModule

e.g. no need for a 25ms loop when we only blink a LED at 1sec
---
 src/modules/ExternalNotificationModule.cpp | 25 +++++++++++-----------
 1 file changed, 12 insertions(+), 13 deletions(-)

diff --git a/src/modules/ExternalNotificationModule.cpp b/src/modules/ExternalNotificationModule.cpp
index 2f2934984..4b5b160be 100644
--- a/src/modules/ExternalNotificationModule.cpp
+++ b/src/modules/ExternalNotificationModule.cpp
@@ -69,7 +69,7 @@ bool ascending = true;
 #endif
 #define EXT_NOTIFICATION_MODULE_OUTPUT_MS 1000
 
-#define EXT_NOTIFICATION_DEFAULT_THREAD_MS 25
+#define EXT_NOTIFICATION_FAST_THREAD_MS 25
 
 #define ASCII_BELL 0x07
 
@@ -88,7 +88,7 @@ int32_t ExternalNotificationModule::runOnce()
     if (!moduleConfig.external_notification.enabled) {
         return INT32_MAX; // we don't need this thread here...
     } else {
-
+        uint32_t delay = EXT_NOTIFICATION_MODULE_OUTPUT_MS;
         bool isPlaying = rtttl::isPlaying();
 #ifdef HAS_I2S
         isPlaying = rtttl::isPlaying() || audioThread->isPlaying();
@@ -116,21 +116,16 @@ int32_t ExternalNotificationModule::runOnce()
 
         // If the output is turned on, turn it back off after the given period of time.
         if (isNagging) {
-            if (externalTurnedOn[0] + (moduleConfig.external_notification.output_ms ? moduleConfig.external_notification.output_ms
-                                                                                    : EXT_NOTIFICATION_MODULE_OUTPUT_MS) <
-                millis()) {
+            delay = (moduleConfig.external_notification.output_ms ? moduleConfig.external_notification.output_ms
+                                                                  : EXT_NOTIFICATION_MODULE_OUTPUT_MS);
+            if (externalTurnedOn[0] + delay < millis()) {
                 setExternalState(0, !getExternal(0));
             }
-            if (externalTurnedOn[1] + (moduleConfig.external_notification.output_ms ? moduleConfig.external_notification.output_ms
-                                                                                    : EXT_NOTIFICATION_MODULE_OUTPUT_MS) <
-                millis()) {
+            if (externalTurnedOn[1] + delay < millis()) {
                 setExternalState(1, !getExternal(1));
             }
             // Only toggle buzzer output if not using PWM mode (to avoid conflict with RTTTL)
-            if (!moduleConfig.external_notification.use_pwm &&
-                externalTurnedOn[2] + (moduleConfig.external_notification.output_ms ? moduleConfig.external_notification.output_ms
-                                                                                    : EXT_NOTIFICATION_MODULE_OUTPUT_MS) <
-                    millis()) {
+            if (!moduleConfig.external_notification.use_pwm && externalTurnedOn[2] + delay < millis()) {
                 LOG_DEBUG("EXTERNAL 2 %d compared to %d", externalTurnedOn[2] + moduleConfig.external_notification.output_ms,
                           millis());
                 setExternalState(2, !getExternal(2));
@@ -181,6 +176,8 @@ int32_t ExternalNotificationModule::runOnce()
                     colorState = 1;
                 }
             }
+            // we need fast updates for the color change
+            delay = EXT_NOTIFICATION_FAST_THREAD_MS;
 #endif
 
 #ifdef T_WATCH_S3
@@ -206,9 +203,11 @@ int32_t ExternalNotificationModule::runOnce()
                 // start the song again if we have time left
                 rtttl::begin(config.device.buzzer_gpio, rtttlConfig.ringtone);
             }
+            // we need fast updates to play the RTTTL
+            delay = EXT_NOTIFICATION_FAST_THREAD_MS;
         }
 
-        return EXT_NOTIFICATION_DEFAULT_THREAD_MS;
+        return delay;
     }
 }
 

From bb6f19dddf9edafb92e9852369ce873061c6792c Mon Sep 17 00:00:00 2001
From: Links2004 
Date: Wed, 24 Sep 2025 17:06:56 +0200
Subject: [PATCH 347/691] the BluetoothPhoneAPI runOnce is triggerd by events
 any way no need to loop

---
 src/nimble/NimbleBluetooth.cpp | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/src/nimble/NimbleBluetooth.cpp b/src/nimble/NimbleBluetooth.cpp
index 0c4e30785..5524765f3 100644
--- a/src/nimble/NimbleBluetooth.cpp
+++ b/src/nimble/NimbleBluetooth.cpp
@@ -53,7 +53,8 @@ class BluetoothPhoneAPI : public PhoneAPI, public concurrency::OSThread
             hasChecked = true;
         }
 
-        return 100;
+        // the run is triggered via NimbleBluetoothToRadioCallback and NimbleBluetoothFromRadioCallback
+        return INT32_MAX;
     }
     /**
      * Subclasses can use this as a hook to provide custom notifications for their transport (i.e. bluetooth notifies)

From 17ecd69416a115a4fc1ff72553d11875d84e19bc Mon Sep 17 00:00:00 2001
From: Links2004 
Date: Wed, 24 Sep 2025 21:07:48 +0200
Subject: [PATCH 348/691] onReceive does only exist for HardwareSerial not for
 USB CDC serial but we can at least check for USB connection in a longer
 interval

---
 src/SerialConsole.cpp | 19 ++++++++++++++++---
 1 file changed, 16 insertions(+), 3 deletions(-)

diff --git a/src/SerialConsole.cpp b/src/SerialConsole.cpp
index 2e8c8ab61..51dbcb7be 100644
--- a/src/SerialConsole.cpp
+++ b/src/SerialConsole.cpp
@@ -6,6 +6,14 @@
 #include "configuration.h"
 #include "time.h"
 
+#if defined(ARDUINO_USB_CDC_ON_BOOT) && ARDUINO_USB_CDC_ON_BOOT
+#define IS_USB_SERIAL
+#ifdef SERIAL_HAS_ON_RECEIVE
+#undef SERIAL_HAS_ON_RECEIVE
+#endif
+#include "HWCDC.h"
+#endif
+
 #ifdef RP2040_SLOW_CLOCK
 #define Port Serial2
 #else
@@ -23,7 +31,9 @@ SerialConsole *console;
 void consoleInit()
 {
     auto sc = new SerialConsole(); // Must be dynamically allocated because we are now inheriting from thread
-#ifdef SERIAL_HAS_ON_RECEIVE
+
+#if defined(SERIAL_HAS_ON_RECEIVE)
+    // onReceive does only exist for HardwareSerial not for USB CDC serial
     Port.onReceive([sc]() { sc->rxInt(); });
 #endif
     DEBUG_PORT.rpInit(); // Simply sets up semaphore
@@ -74,11 +84,14 @@ int32_t SerialConsole::runOnce()
         return 250;
     }
 #endif
-#ifdef SERIAL_HAS_ON_RECEIVE
+
     int32_t delay = runOncePart();
+#if defined(SERIAL_HAS_ON_RECEIVE)
     return Port.available() ? delay : INT32_MAX;
+#elif defined(IS_USB_SERIAL)
+    return HWCDC::isPlugged() ? delay : (1000 * 20);
 #else
-    return runOncePart();
+    return delay;
 #endif
 }
 

From 47a82bdb98c46841847be0034818ea3d8da279f1 Mon Sep 17 00:00:00 2001
From: WillyJL 
Date: Wed, 24 Sep 2025 23:36:14 +0200
Subject: [PATCH 349/691] Fix duplicated lines from merge (#8104)

---
 variants/nrf52840/heltec_mesh_solar/variant.h | 3 ---
 1 file changed, 3 deletions(-)

diff --git a/variants/nrf52840/heltec_mesh_solar/variant.h b/variants/nrf52840/heltec_mesh_solar/variant.h
index d37e078f9..7c43d8ba7 100644
--- a/variants/nrf52840/heltec_mesh_solar/variant.h
+++ b/variants/nrf52840/heltec_mesh_solar/variant.h
@@ -69,7 +69,6 @@ No longer populated on PCB
 
 #define WIRE_INTERFACES_COUNT 2
 
-#ifndef HELTEC_MESH_SOLAR_OLED
 #ifndef HELTEC_MESH_SOLAR_OLED
 // I2C bus 0
 #define PIN_WIRE_SDA (0 + 6)
@@ -80,8 +79,6 @@ No longer populated on PCB
 // Available on header pins, for general use
 #define PIN_WIRE1_SDA (0 + 30)
 #define PIN_WIRE1_SCL (0 + 5)
-#define PIN_WIRE1_SDA (0 + 30)
-#define PIN_WIRE1_SCL (0 + 5)
 
 /*
  * Lora radio

From 0ad6b813fcf2076c63052d2a2e6856136fccb46e Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
 <41898282+github-actions[bot]@users.noreply.github.com>
Date: Thu, 25 Sep 2025 04:45:14 -0500
Subject: [PATCH 350/691] Upgrade trunk (#8105)

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 491ab32ba..e9bbba9ff 100644
--- a/.trunk/trunk.yaml
+++ b/.trunk/trunk.yaml
@@ -9,7 +9,7 @@ plugins:
 lint:
   enabled:
     - checkov@3.2.471
-    - renovate@41.125.3
+    - renovate@41.127.2
     - prettier@3.6.2
     - trufflehog@3.90.8
     - yamllint@1.37.1

From 18058ef507ac1b643dbc295de12acc94e53010d3 Mon Sep 17 00:00:00 2001
From: Chloe Bethel 
Date: Thu, 25 Sep 2025 10:50:56 +0100
Subject: [PATCH 351/691] Fix 2.4GHz reconfiguration on LR11xx (#8102)

Co-authored-by: Ben Meadors 
---
 src/mesh/LR11x0Interface.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/mesh/LR11x0Interface.cpp b/src/mesh/LR11x0Interface.cpp
index f83522c8b..0e23405e5 100644
--- a/src/mesh/LR11x0Interface.cpp
+++ b/src/mesh/LR11x0Interface.cpp
@@ -155,7 +155,7 @@ template  bool LR11x0Interface::reconfigure()
     if (err != RADIOLIB_ERR_NONE)
         RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING);
 
-    err = lora.setBandwidth(bw);
+    err = lora.setBandwidth(bw, wideLora() && (getFreq() > 1000.0f));
     if (err != RADIOLIB_ERR_NONE)
         RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING);
 

From fd5ca8b73c8a92a10490d021cb8ceb104f53e483 Mon Sep 17 00:00:00 2001
From: Clive Blackledge 
Date: Thu, 25 Sep 2025 03:17:51 -0700
Subject: [PATCH 352/691] Feat/0-cost hops for favorite routers (#7992)

* feat: implement router hop preservation for router-to-router communication

- Preserve hop_limit when both local device and previous relay are routers/CLIENT_BASE
- Only preserve hops for favorite routers to prevent abuse
- Apply to both FloodingRouter and NextHopRouter
- Update hop counting logic in MeshService for router-to-router communication

This allows routers to communicate over longer distances without
consuming hop limits, improving mesh network efficiency for
infrastructure nodes.

* chore: update protobufs submodule to latest

* Optimized to check friend list first before nodedb.

* Reverting unintended changes

* revert: remove protobufs submodule update

This reverts the protobufs submodule back to a84657c22 to remove
unintended changes from this branch.

* Slight rewrite to remove flawed NO_RELAY_NODE logic and added logic to add isFirstHop. If isFirstHop, always decrease hop_limit to avoid retry logic.

* DRY code. Remove NodeInfo logic that was left over.

* Trunk formatting
---
 src/mesh/FloodingRouter.cpp | 11 ++++++--
 src/mesh/NextHopRouter.cpp  |  9 ++++++-
 src/mesh/Router.cpp         | 52 +++++++++++++++++++++++++++++++++++++
 src/mesh/Router.h           | 12 +++++++++
 4 files changed, 81 insertions(+), 3 deletions(-)

diff --git a/src/mesh/FloodingRouter.cpp b/src/mesh/FloodingRouter.cpp
index f805055c8..14d17778e 100644
--- a/src/mesh/FloodingRouter.cpp
+++ b/src/mesh/FloodingRouter.cpp
@@ -1,5 +1,6 @@
 #include "FloodingRouter.h"
-
+#include "MeshTypes.h"
+#include "NodeDB.h"
 #include "configuration.h"
 #include "mesh-pb-constants.h"
 
@@ -90,7 +91,12 @@ void FloodingRouter::perhapsRebroadcast(const meshtastic_MeshPacket *p)
             if (isRebroadcaster()) {
                 meshtastic_MeshPacket *tosend = packetPool.allocCopy(*p); // keep a copy because we will be sending it
 
-                tosend->hop_limit--; // bump down the hop count
+                // Use shared logic to determine if hop_limit should be decremented
+                if (shouldDecrementHopLimit(p)) {
+                    tosend->hop_limit--; // bump down the hop count
+                } else {
+                    LOG_INFO("favorite-ROUTER/CLIENT_BASE-to-ROUTER/CLIENT_BASE flood: preserving hop_limit");
+                }
 #if USERPREFS_EVENT_MODE
                 if (tosend->hop_limit > 2) {
                     // if we are "correcting" the hop_limit, "correct" the hop_start by the same amount to preserve hops away.
@@ -98,6 +104,7 @@ void FloodingRouter::perhapsRebroadcast(const meshtastic_MeshPacket *p)
                     tosend->hop_limit = 2;
                 }
 #endif
+
                 tosend->next_hop = NO_NEXT_HOP_PREFERENCE; // this should already be the case, but just in case
 
                 LOG_INFO("Rebroadcast received floodmsg");
diff --git a/src/mesh/NextHopRouter.cpp b/src/mesh/NextHopRouter.cpp
index 9bb8b240c..791b6a749 100644
--- a/src/mesh/NextHopRouter.cpp
+++ b/src/mesh/NextHopRouter.cpp
@@ -1,4 +1,5 @@
 #include "NextHopRouter.h"
+#include "NodeDB.h"
 
 NextHopRouter::NextHopRouter() {}
 
@@ -108,7 +109,13 @@ bool NextHopRouter::perhapsRelay(const meshtastic_MeshPacket *p)
                 meshtastic_MeshPacket *tosend = packetPool.allocCopy(*p); // keep a copy because we will be sending it
                 LOG_INFO("Relaying received message coming from %x", p->relay_node);
 
-                tosend->hop_limit--; // bump down the hop count
+                // Use shared logic to determine if hop_limit should be decremented
+                if (shouldDecrementHopLimit(p)) {
+                    tosend->hop_limit--; // bump down the hop count
+                } else {
+                    LOG_INFO("Router/CLIENT_BASE-to-favorite-router/CLIENT_BASE relay: preserving hop_limit");
+                }
+
                 NextHopRouter::send(tosend);
 
                 return true;
diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp
index 09fb079c5..171c383ba 100644
--- a/src/mesh/Router.cpp
+++ b/src/mesh/Router.cpp
@@ -69,6 +69,58 @@ Router::Router() : concurrency::OSThread("Router"), fromRadioQueue(MAX_RX_FROMRA
     cryptLock = new concurrency::Lock();
 }
 
+bool Router::shouldDecrementHopLimit(const meshtastic_MeshPacket *p)
+{
+    // First hop MUST always decrement to prevent retry issues
+    bool isFirstHop = (p->hop_start != 0 && p->hop_start == p->hop_limit);
+    if (isFirstHop) {
+        return true; // Always decrement on first hop
+    }
+
+    // Check if both local device and previous relay are routers (including CLIENT_BASE)
+    bool localIsRouter =
+        IS_ONE_OF(config.device.role, meshtastic_Config_DeviceConfig_Role_ROUTER, meshtastic_Config_DeviceConfig_Role_ROUTER_LATE,
+                  meshtastic_Config_DeviceConfig_Role_CLIENT_BASE);
+
+    // If local device isn't a router, always decrement
+    if (!localIsRouter) {
+        return true;
+    }
+
+    // For subsequent hops, check if previous relay is a favorite router
+    // Optimized search for favorite routers with matching last byte
+    // Check ordering optimized for IoT devices (cheapest checks first)
+    for (int i = 0; i < nodeDB->getNumMeshNodes(); i++) {
+        meshtastic_NodeInfoLite *node = nodeDB->getMeshNodeByIndex(i);
+        if (!node)
+            continue;
+
+        // Check 1: is_favorite (cheapest - single bool)
+        if (!node->is_favorite)
+            continue;
+
+        // Check 2: has_user (cheap - single bool)
+        if (!node->has_user)
+            continue;
+
+        // Check 3: role check (moderate cost - multiple comparisons)
+        if (!IS_ONE_OF(node->user.role, meshtastic_Config_DeviceConfig_Role_ROUTER,
+                       meshtastic_Config_DeviceConfig_Role_ROUTER_LATE, meshtastic_Config_DeviceConfig_Role_CLIENT_BASE)) {
+            continue;
+        }
+
+        // Check 4: last byte extraction and comparison (most expensive)
+        if (nodeDB->getLastByteOfNodeNum(node->num) == p->relay_node) {
+            // Found a favorite router match
+            LOG_DEBUG("Identified favorite relay router 0x%x from last byte 0x%x", node->num, p->relay_node);
+            return false; // Don't decrement hop_limit
+        }
+    }
+
+    // No favorite router match found, decrement hop_limit
+    return true;
+}
+
 /**
  * do idle processing
  * Mostly looking in our incoming rxPacket queue and calling handleReceived.
diff --git a/src/mesh/Router.h b/src/mesh/Router.h
index 58ca50f3d..075248af9 100644
--- a/src/mesh/Router.h
+++ b/src/mesh/Router.h
@@ -104,6 +104,18 @@ class Router : protected concurrency::OSThread, protected PacketHistory
      */
     virtual bool shouldFilterReceived(const meshtastic_MeshPacket *p) { return false; }
 
+    /**
+     * Determine if hop_limit should be decremented for a relay operation.
+     * Returns false (preserve hop_limit) only if all conditions are met:
+     * - It's NOT the first hop (first hop must always decrement)
+     * - Local device is a ROUTER, ROUTER_LATE, or CLIENT_BASE
+     * - Previous relay is a favorite ROUTER, ROUTER_LATE, or CLIENT_BASE
+     *
+     * @param p The packet being relayed
+     * @return true if hop_limit should be decremented, false to preserve it
+     */
+    bool shouldDecrementHopLimit(const meshtastic_MeshPacket *p);
+
     /**
      * Every (non duplicate) packet this node receives will be passed through this method.  This allows subclasses to
      * update routing tables etc... based on what we overhear (even for messages not destined to our node)

From 3c25652cdf56bb4f0f7d3d74dff8a04a52f52985 Mon Sep 17 00:00:00 2001
From: Erayd 
Date: Thu, 25 Sep 2025 22:44:49 +1200
Subject: [PATCH 353/691] If a packet is heard multiple times, rebroadcast
 using the highest hop limit (#5534)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* If a packet is heard multiple times, rebroadcast using the highest hop limit

Sometimes a packet will be in the TX queue waiting to be transmitted,
when it is overheard being rebroadcast by another node, with a higher
hop limit remaining. When this occurs, modify the pending packet in
the TX queue to avoid unnecessarily wasting hops.

* Reprocess instead of modifying queued packet

In order to ensure that the traceroute module works correctly, rather
than modifying the hop limnit of the existing queued version of the
packet, simply drop it ifrom the queue and reprocess the version of the
packet with the superior hop limit.

* Update protobufs submodule

* Merge upstream/develop into overheard-hoptimisation branch

Resolved conflicts in:
- src/mesh/FloodingRouter.cpp: Integrated hop limit optimization with refactored duplicate handling
- src/mesh/MeshPacketQueue.h: Kept both hop_limit_lt parameter and new find() method

* Improve method naming and code clarity

- Rename findPacket() to getPacketFromQueue() for better clarity
- Make code DRY by having find() use getPacketFromQueue() internally
- Resolves method overloading conflict with clearer naming

* If a packet is heard multiple times, rebroadcast using the highest hop limit

Sometimes a packet will be in the TX queue waiting to be transmitted,
when it is overheard being rebroadcast by another node, with a higher
hop limit remaining. When this occurs, modify the pending packet in
the TX queue to avoid unnecessarily wasting hops.

* Improve router role checking using IS_ONE_OF macro

- Replace multiple individual role checks with cleaner IS_ONE_OF macro
- Add CLIENT_BASE support as suggested in PR #7992
- Include MeshTypes.h for IS_ONE_OF macro
- Makes code more maintainable and consistent with other parts of codebase

* Apply IS_ONE_OF improvement to NextHopRouter.cpp

- Replace multiple individual role checks with cleaner IS_ONE_OF macro
- Add CLIENT_BASE support for consistency
- Include MeshTypes.h for IS_ONE_OF macro
- Matches the pattern used in FloodingRouter.cpp

* Create and apply IS_ROUTER_ROLE() macro across codebase

- Add IS_ROUTER_ROLE() macro to meshUtils.h for consistent router role checking
- Update FloodingRouter.cpp to use macro in multiple locations
- Update NextHopRouter.cpp to use macro
- Include CLIENT_BASE role support

* Core Changes:
- Add hop_limit field to PacketRecord (17B→20B due to alignment)
- Extend wasSeenRecently() with wasUpgraded parameter
- Enable router optimization without duplicate app delivery
- Handle ROUTER_LATE delayed transmission properly

Technical Details:
- Memory overhead: ~4000 bytes for 1000 records
- Prevents duplicate message delivery while enabling routing optimization
- Maintains protocol integrity for ACK/NAK handling
- Supports upgrade from hop_limit=0 to hop_limit>0 scenarios

* Delete files accdentally added for merge

* Trunk formatting

* Packets are supposed to be unsigned. Thankfully, it's only a log message.

* Upgrade all packets, not just 0 hop packets.

* Not just unsigned, but hex. Updating packet log IDs.

* Fixed order of operations issue that prevented packetrs from being removed from the queue

* Fixing some bugs after testing. Only storing the maximum hop value in PacketRecord which makes sense. Also, updating messaging to make more sense in the logs.

* Fixed flow logic about how to handle re-inserting duplicate packets. Removed IS_ROUTER_ROLE macro and replaced it with better isRebroadcaster().

* Add logic to re-run modules, but avoid re-sending to phone.

* Refactor how to process the new packet with hops. Only update nodeDB and traceRouteModule.

* - Apply changes to both FloodingRouter and NextHopRouter classes to make packets mutable for traceroute
- MESHTASTIC_EXCLUDE_TRACEROUTE guard for when we don't want traceroute

* Allow MeshPacket to be modified in-place in processUpgradePacket

* let's not make a copy where a copy is unncessary.

---------

Co-authored-by: Clive Blackledge 
Co-authored-by: Clive Blackledge 
Co-authored-by: Ben Meadors 
---
 src/mesh/FloodingRouter.cpp       | 38 +++++++++++++++++++++++++++++--
 src/mesh/MeshPacketQueue.cpp      | 29 ++++++++++++++---------
 src/mesh/MeshPacketQueue.h        |  6 ++++-
 src/mesh/MeshService.cpp          |  2 +-
 src/mesh/MeshService.h            |  2 +-
 src/mesh/NextHopRouter.cpp        | 37 ++++++++++++++++++++++++++++--
 src/mesh/PacketHistory.cpp        | 19 +++++++++++++++-
 src/mesh/PacketHistory.h          |  6 +++--
 src/mesh/RadioInterface.h         |  8 ++++++-
 src/mesh/RadioLibInterface.cpp    | 20 ++++++++++++++++
 src/mesh/RadioLibInterface.h      |  9 +++++++-
 src/mesh/Router.h                 |  2 +-
 src/modules/TextMessageModule.cpp |  3 ++-
 src/modules/TraceRouteModule.cpp  | 16 ++++++++++++-
 src/modules/TraceRouteModule.h    |  4 +++-
 15 files changed, 174 insertions(+), 27 deletions(-)

diff --git a/src/mesh/FloodingRouter.cpp b/src/mesh/FloodingRouter.cpp
index 14d17778e..dc211b20a 100644
--- a/src/mesh/FloodingRouter.cpp
+++ b/src/mesh/FloodingRouter.cpp
@@ -3,6 +3,10 @@
 #include "NodeDB.h"
 #include "configuration.h"
 #include "mesh-pb-constants.h"
+#include "meshUtils.h"
+#if !MESHTASTIC_EXCLUDE_TRACEROUTE
+#include "modules/TraceRouteModule.h"
+#endif
 
 FloodingRouter::FloodingRouter() {}
 
@@ -22,7 +26,37 @@ ErrorCode FloodingRouter::send(meshtastic_MeshPacket *p)
 
 bool FloodingRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
 {
-    if (wasSeenRecently(p)) { // Note: this will also add a recent packet record
+    bool wasUpgraded = false;
+    bool seenRecently =
+        wasSeenRecently(p, true, nullptr, nullptr, &wasUpgraded); // Updates history; returns false when an upgrade is detected
+
+    // Handle hop_limit upgrade scenario for rebroadcasters
+    // isRebroadcaster() is duplicated in perhapsRebroadcast(), but this avoids confusing log messages
+    if (wasUpgraded && isRebroadcaster() && iface && p->hop_limit > 0) {
+        // wasSeenRecently() reports false in upgrade cases so we handle replacement before the duplicate short-circuit
+        // If we overhear a duplicate copy of the packet with more hops left than the one we are waiting to
+        // rebroadcast, then remove the packet currently sitting in the TX queue and use this one instead.
+        uint8_t dropThreshold = p->hop_limit; // remove queued packets that have fewer hops remaining
+        if (iface->removePendingTXPacket(getFrom(p), p->id, dropThreshold)) {
+            LOG_DEBUG("Processing upgraded packet 0x%08x for rebroadcast with hop limit %d (dropping queued < %d)", p->id,
+                      p->hop_limit, dropThreshold);
+
+            if (nodeDB)
+                nodeDB->updateFrom(*p);
+#if !MESHTASTIC_EXCLUDE_TRACEROUTE
+            if (traceRouteModule && p->which_payload_variant == meshtastic_MeshPacket_decoded_tag &&
+                p->decoded.portnum == meshtastic_PortNum_TRACEROUTE_APP)
+                traceRouteModule->processUpgradedPacket(*p);
+#endif
+
+            perhapsRebroadcast(p);
+
+            // We already enqueued the improved copy, so make sure the incoming packet stops here.
+            return true;
+        }
+    }
+
+    if (seenRecently) {
         printPacket("Ignore dupe incoming msg", p);
         rxDupe++;
 
@@ -134,4 +168,4 @@ void FloodingRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtas
 
     // handle the packet as normal
     Router::sniffReceived(p, c);
-}
\ No newline at end of file
+}
diff --git a/src/mesh/MeshPacketQueue.cpp b/src/mesh/MeshPacketQueue.cpp
index a64678a7f..ef5380eb8 100644
--- a/src/mesh/MeshPacketQueue.cpp
+++ b/src/mesh/MeshPacketQueue.cpp
@@ -103,12 +103,26 @@ meshtastic_MeshPacket *MeshPacketQueue::getFront()
     return p;
 }
 
-/** Attempt to find and remove a packet from this queue.  Returns a pointer to the removed packet, or NULL if not found */
-meshtastic_MeshPacket *MeshPacketQueue::remove(NodeNum from, PacketId id, bool tx_normal, bool tx_late)
+/** Get a packet from this queue. Returns a pointer to the packet, or NULL if not found. */
+meshtastic_MeshPacket *MeshPacketQueue::getPacketFromQueue(NodeNum from, PacketId id)
 {
     for (auto it = queue.begin(); it != queue.end(); it++) {
         auto p = (*it);
-        if (getFrom(p) == from && p->id == id && ((tx_normal && !p->tx_after) || (tx_late && p->tx_after))) {
+        if (getFrom(p) == from && p->id == id) {
+            return p;
+        }
+    }
+
+    return NULL;
+}
+
+/** Attempt to find and remove a packet from this queue.  Returns a pointer to the removed packet, or NULL if not found */
+meshtastic_MeshPacket *MeshPacketQueue::remove(NodeNum from, PacketId id, bool tx_normal, bool tx_late, uint8_t hop_limit_lt)
+{
+    for (auto it = queue.begin(); it != queue.end(); it++) {
+        auto p = (*it);
+        if (getFrom(p) == from && p->id == id && ((tx_normal && !p->tx_after) || (tx_late && p->tx_after)) &&
+            (!hop_limit_lt || p->hop_limit < hop_limit_lt)) {
             queue.erase(it);
             return p;
         }
@@ -120,14 +134,7 @@ meshtastic_MeshPacket *MeshPacketQueue::remove(NodeNum from, PacketId id, bool t
 /* Attempt to find a packet from this queue. Return true if it was found. */
 bool MeshPacketQueue::find(const NodeNum from, const PacketId id)
 {
-    for (auto it = queue.begin(); it != queue.end(); it++) {
-        const auto *p = *it;
-        if (getFrom(p) == from && p->id == id) {
-            return true;
-        }
-    }
-
-    return false;
+    return getPacketFromQueue(from, id) != NULL;
 }
 
 /**
diff --git a/src/mesh/MeshPacketQueue.h b/src/mesh/MeshPacketQueue.h
index 1b338f9ed..ea52eb5bf 100644
--- a/src/mesh/MeshPacketQueue.h
+++ b/src/mesh/MeshPacketQueue.h
@@ -35,8 +35,12 @@ class MeshPacketQueue
 
     meshtastic_MeshPacket *getFront();
 
+    /** Get a packet from this queue. Returns a pointer to the packet, or NULL if not found. */
+    meshtastic_MeshPacket *getPacketFromQueue(NodeNum from, PacketId id);
+
     /** Attempt to find and remove a packet from this queue.  Returns the packet which was removed from the queue */
-    meshtastic_MeshPacket *remove(NodeNum from, PacketId id, bool tx_normal = true, bool tx_late = true);
+    meshtastic_MeshPacket *remove(NodeNum from, PacketId id, bool tx_normal = true, bool tx_late = true,
+                                  uint8_t hop_limit_lt = 0);
 
     /* Attempt to find a packet from this queue. Return true if it was found. */
     bool find(const NodeNum from, const PacketId id);
diff --git a/src/mesh/MeshService.cpp b/src/mesh/MeshService.cpp
index 96782cda5..a138ad1d1 100644
--- a/src/mesh/MeshService.cpp
+++ b/src/mesh/MeshService.cpp
@@ -453,4 +453,4 @@ uint32_t MeshService::GetTimeSinceMeshPacket(const meshtastic_MeshPacket *mp)
         delta = 0;
 
     return delta;
-}
\ No newline at end of file
+}
diff --git a/src/mesh/MeshService.h b/src/mesh/MeshService.h
index 5d074368f..66d9d9679 100644
--- a/src/mesh/MeshService.h
+++ b/src/mesh/MeshService.h
@@ -190,4 +190,4 @@ class MeshService
     friend class RoutingModule;
 };
 
-extern MeshService *service;
\ No newline at end of file
+extern MeshService *service;
diff --git a/src/mesh/NextHopRouter.cpp b/src/mesh/NextHopRouter.cpp
index 791b6a749..a90fe7ad2 100644
--- a/src/mesh/NextHopRouter.cpp
+++ b/src/mesh/NextHopRouter.cpp
@@ -1,4 +1,9 @@
 #include "NextHopRouter.h"
+#include "MeshTypes.h"
+#include "meshUtils.h"
+#if !MESHTASTIC_EXCLUDE_TRACEROUTE
+#include "modules/TraceRouteModule.h"
+#endif
 #include "NodeDB.h"
 
 NextHopRouter::NextHopRouter() {}
@@ -33,7 +38,35 @@ bool NextHopRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
 {
     bool wasFallback = false;
     bool weWereNextHop = false;
-    if (wasSeenRecently(p, true, &wasFallback, &weWereNextHop)) { // Note: this will also add a recent packet record
+    bool wasUpgraded = false;
+    bool seenRecently = wasSeenRecently(p, true, &wasFallback, &weWereNextHop,
+                                        &wasUpgraded); // Updates history; returns false when an upgrade is detected
+
+    // Handle hop_limit upgrade scenario for rebroadcasters
+    // isRebroadcaster() is duplicated in perhapsRelay(), but this avoids confusing log messages
+    if (wasUpgraded && isRebroadcaster() && iface && p->hop_limit > 0) {
+        // Upgrade detection bypasses the duplicate short-circuit so we replace the queued packet before exiting
+        uint8_t dropThreshold = p->hop_limit; // remove queued packets that have fewer hops remaining
+        if (iface->removePendingTXPacket(getFrom(p), p->id, dropThreshold)) {
+            LOG_DEBUG("Processing upgraded packet 0x%08x for relay with hop limit %d (dropping queued < %d)", p->id, p->hop_limit,
+                      dropThreshold);
+
+            if (nodeDB)
+                nodeDB->updateFrom(*p);
+#if !MESHTASTIC_EXCLUDE_TRACEROUTE
+            if (traceRouteModule && p->which_payload_variant == meshtastic_MeshPacket_decoded_tag &&
+                p->decoded.portnum == meshtastic_PortNum_TRACEROUTE_APP)
+                traceRouteModule->processUpgradedPacket(*p);
+#endif
+
+            perhapsRelay(p);
+
+            // We already enqueued the improved copy, so make sure the incoming packet stops here.
+            return true;
+        }
+    }
+
+    if (seenRecently) {
         printPacket("Ignore dupe incoming msg", p);
 
         if (p->transport_mechanism == meshtastic_MeshPacket_TransportMechanism_TRANSPORT_LORA) {
@@ -298,4 +331,4 @@ void NextHopRouter::setNextTx(PendingPacket *pending)
     LOG_DEBUG("Setting next retransmission in %u msecs: ", d);
     printPacket("", pending->packet);
     setReceivedMessage(); // Run ASAP, so we can figure out our correct sleep time
-}
\ No newline at end of file
+}
diff --git a/src/mesh/PacketHistory.cpp b/src/mesh/PacketHistory.cpp
index 735386d79..6d14856da 100644
--- a/src/mesh/PacketHistory.cpp
+++ b/src/mesh/PacketHistory.cpp
@@ -45,7 +45,8 @@ PacketHistory::~PacketHistory()
 }
 
 /** Update recentPackets and return true if we have already seen this packet */
-bool PacketHistory::wasSeenRecently(const meshtastic_MeshPacket *p, bool withUpdate, bool *wasFallback, bool *weWereNextHop)
+bool PacketHistory::wasSeenRecently(const meshtastic_MeshPacket *p, bool withUpdate, bool *wasFallback, bool *weWereNextHop,
+                                    bool *wasUpgraded)
 {
     if (!initOk()) {
         LOG_ERROR("Packet History - Was Seen Recently: NOT INITIALIZED!");
@@ -66,6 +67,7 @@ bool PacketHistory::wasSeenRecently(const meshtastic_MeshPacket *p, bool withUpd
     r.id = p->id;
     r.sender = getFrom(p); // If 0 then use our ID
     r.next_hop = p->next_hop;
+    r.hop_limit = p->hop_limit;
     r.relayed_by[0] = p->relay_node;
 
     r.rxTimeMsec = millis(); //
@@ -81,6 +83,16 @@ bool PacketHistory::wasSeenRecently(const meshtastic_MeshPacket *p, bool withUpd
     PacketRecord *found = find(r.sender, r.id); // Find the packet record in the recentPackets array
     bool seenRecently = (found != NULL);        // If found -> the packet was seen recently
 
+    // Check for hop_limit upgrade scenario
+    if (seenRecently && wasUpgraded && found->hop_limit < p->hop_limit) {
+        LOG_DEBUG("Packet History - Hop limit upgrade: packet 0x%08x from hop_limit=%d to hop_limit=%d", p->id, found->hop_limit,
+                  p->hop_limit);
+        *wasUpgraded = true;
+        seenRecently = false; // Allow router processing but prevent duplicate app delivery
+    } else if (wasUpgraded) {
+        *wasUpgraded = false; // Initialize to false if not an upgrade
+    }
+
     if (seenRecently) {
         uint8_t ourRelayID = nodeDB->getLastByteOfNodeNum(nodeDB->getNodeNum()); // Get our relay ID from our node number
 
@@ -126,6 +138,11 @@ bool PacketHistory::wasSeenRecently(const meshtastic_MeshPacket *p, bool withUpd
                       millis() - found->rxTimeMsec);
 #endif
 
+            // Preserve the highest hop_limit we've ever seen for this packet so upgrades aren't lost when a later copy has
+            // fewer hops remaining.
+            if (found->hop_limit > r.hop_limit)
+                r.hop_limit = found->hop_limit;
+
             // Add the existing relayed_by to the new record
             for (uint8_t i = 0; i < (NUM_RELAYERS - 1); i++) {
                 if (found->relayed_by[i] != 0)
diff --git a/src/mesh/PacketHistory.h b/src/mesh/PacketHistory.h
index 4b53c8f6a..3ff7a893d 100644
--- a/src/mesh/PacketHistory.h
+++ b/src/mesh/PacketHistory.h
@@ -16,8 +16,9 @@ class PacketHistory
         PacketId id;
         uint32_t rxTimeMsec;              // Unix time in msecs - the time we received it,  0 means empty
         uint8_t next_hop;                 // The next hop asked for this packet
+        uint8_t hop_limit;                // Highest hop limit observed for this packet
         uint8_t relayed_by[NUM_RELAYERS]; // Array of nodes that relayed this packet
-    };                                    // 4B + 4B + 4B + 1B + 3B = 16B
+    };                                    // 4B + 4B + 4B + 1B + 1B + 3B = 17B (will be padded to 20B)
 
     uint32_t recentPacketsCapacity =
         0; // Can be set in constructor, no need to recompile. Used to allocate memory for mx_recentPackets.
@@ -50,9 +51,10 @@ class PacketHistory
      * @param withUpdate if true and not found we add an entry to recentPackets
      * @param wasFallback if not nullptr, packet will be checked for fallback to flooding and value will be set to true if so
      * @param weWereNextHop if not nullptr, packet will be checked for us being the next hop and value will be set to true if so
+     * @param wasUpgraded if not nullptr, will be set to true if this packet has better hop_limit than previously seen
      */
     bool wasSeenRecently(const meshtastic_MeshPacket *p, bool withUpdate = true, bool *wasFallback = nullptr,
-                         bool *weWereNextHop = nullptr);
+                         bool *weWereNextHop = nullptr, bool *wasUpgraded = 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
diff --git a/src/mesh/RadioInterface.h b/src/mesh/RadioInterface.h
index eff284747..0c5b6cd1a 100644
--- a/src/mesh/RadioInterface.h
+++ b/src/mesh/RadioInterface.h
@@ -189,6 +189,12 @@ class RadioInterface
     /** If the packet is not already in the late rebroadcast window, move it there */
     virtual void clampToLateRebroadcastWindow(NodeNum from, PacketId id) { return; }
 
+    /**
+     * If there is a packet pending TX in the queue with a worse hop limit, remove it pending replacement with a better version
+     * @return Whether a pending packet was removed
+     */
+    virtual bool removePendingTXPacket(NodeNum from, PacketId id, uint32_t hop_limit_lt) { return false; }
+
     /**
      * Calculate airtime per
      * https://www.rs-online.com/designspark/rel-assets/ds-assets/uploads/knowledge-items/application-notes-for-the-internet-of-things/LoRa%20Design%20Guide.pdf
@@ -266,4 +272,4 @@ class RadioInterface
 };
 
 /// Debug printing for packets
-void printPacket(const char *prefix, const meshtastic_MeshPacket *p);
\ No newline at end of file
+void printPacket(const char *prefix, const meshtastic_MeshPacket *p);
diff --git a/src/mesh/RadioLibInterface.cpp b/src/mesh/RadioLibInterface.cpp
index 19d0f794a..3717e8780 100644
--- a/src/mesh/RadioLibInterface.cpp
+++ b/src/mesh/RadioLibInterface.cpp
@@ -362,6 +362,26 @@ void RadioLibInterface::clampToLateRebroadcastWindow(NodeNum from, PacketId id)
     }
 }
 
+/**
+ * If there is a packet pending TX in the queue with a worse hop limit, remove it pending replacement with a better version
+ * @return Whether a pending packet was removed
+ */
+bool RadioLibInterface::removePendingTXPacket(NodeNum from, PacketId id, uint32_t hop_limit_lt)
+{
+    meshtastic_MeshPacket *p = txQueue.remove(from, id, true, true, hop_limit_lt);
+    if (p) {
+        LOG_DEBUG("Dropping pending-TX packet 0x%08x with hop limit %d", p->id, p->hop_limit);
+        packetPool.release(p);
+        return true;
+    }
+    return false;
+}
+
+/**
+ * Remove a packet that is eligible for replacement from the TX queue
+ */
+// void RadioLibInterface::removePending
+
 void RadioLibInterface::handleTransmitInterrupt()
 {
     // This can be null if we forced the device to enter standby mode.  In that case
diff --git a/src/mesh/RadioLibInterface.h b/src/mesh/RadioLibInterface.h
index 9f497812f..3444b1a2c 100644
--- a/src/mesh/RadioLibInterface.h
+++ b/src/mesh/RadioLibInterface.h
@@ -215,4 +215,11 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified
      * If the packet is not already in the late rebroadcast window, move it there
      */
     void clampToLateRebroadcastWindow(NodeNum from, PacketId id);
-};
\ No newline at end of file
+
+    /**
+     * If there is a packet pending TX in the queue with a worse hop limit, remove it pending replacement with a better version
+     * @return Whether a pending packet was removed
+     */
+
+    bool removePendingTXPacket(NodeNum from, PacketId id, uint32_t hop_limit_lt) override;
+};
diff --git a/src/mesh/Router.h b/src/mesh/Router.h
index 075248af9..92a5a06e5 100644
--- a/src/mesh/Router.h
+++ b/src/mesh/Router.h
@@ -174,4 +174,4 @@ PacketId generatePacketId();
 #define BITFIELD_WANT_RESPONSE_SHIFT 1
 #define BITFIELD_OK_TO_MQTT_SHIFT 0
 #define BITFIELD_WANT_RESPONSE_MASK (1 << BITFIELD_WANT_RESPONSE_SHIFT)
-#define BITFIELD_OK_TO_MQTT_MASK (1 << BITFIELD_OK_TO_MQTT_SHIFT)
\ No newline at end of file
+#define BITFIELD_OK_TO_MQTT_MASK (1 << BITFIELD_OK_TO_MQTT_SHIFT)
diff --git a/src/modules/TextMessageModule.cpp b/src/modules/TextMessageModule.cpp
index 72df330c5..aee359158 100644
--- a/src/modules/TextMessageModule.cpp
+++ b/src/modules/TextMessageModule.cpp
@@ -13,6 +13,7 @@ ProcessMessage TextMessageModule::handleReceived(const meshtastic_MeshPacket &mp
     auto &p = mp.decoded;
     LOG_INFO("Received text msg from=0x%0x, id=0x%x, msg=%.*s", mp.from, mp.id, p.payload.size, p.payload.bytes);
 #endif
+
     // We only store/display messages destined for us.
     // Keep a copy of the most recent text message.
     devicestate.rx_text_message = mp;
@@ -30,4 +31,4 @@ ProcessMessage TextMessageModule::handleReceived(const meshtastic_MeshPacket &mp
 bool TextMessageModule::wantPacket(const meshtastic_MeshPacket *p)
 {
     return MeshService::isTextPayload(p);
-}
\ No newline at end of file
+}
diff --git a/src/modules/TraceRouteModule.cpp b/src/modules/TraceRouteModule.cpp
index d7df90bb5..8f69c504a 100644
--- a/src/modules/TraceRouteModule.cpp
+++ b/src/modules/TraceRouteModule.cpp
@@ -153,6 +153,20 @@ void TraceRouteModule::alterReceivedProtobuf(meshtastic_MeshPacket &p, meshtasti
     }
 }
 
+void TraceRouteModule::processUpgradedPacket(const meshtastic_MeshPacket &mp)
+{
+    if (mp.which_payload_variant != meshtastic_MeshPacket_decoded_tag || mp.decoded.portnum != meshtastic_PortNum_TRACEROUTE_APP)
+        return;
+
+    meshtastic_RouteDiscovery decoded = meshtastic_RouteDiscovery_init_zero;
+    if (!pb_decode_from_bytes(mp.decoded.payload.bytes, mp.decoded.payload.size, &meshtastic_RouteDiscovery_msg, &decoded))
+        return;
+
+    handleReceivedProtobuf(mp, &decoded);
+    // Intentionally modify the packet in-place so downstream relays see our updates.
+    alterReceivedProtobuf(const_cast(mp), &decoded);
+}
+
 void TraceRouteModule::insertUnknownHops(meshtastic_MeshPacket &p, meshtastic_RouteDiscovery *r, bool isTowardsDestination)
 {
     pb_size_t *route_count;
@@ -760,4 +774,4 @@ int32_t TraceRouteModule::runOnce()
     }
 
     return INT32_MAX;
-}
\ No newline at end of file
+}
diff --git a/src/modules/TraceRouteModule.h b/src/modules/TraceRouteModule.h
index 51d98826e..a069f7157 100644
--- a/src/modules/TraceRouteModule.h
+++ b/src/modules/TraceRouteModule.h
@@ -35,6 +35,8 @@ class TraceRouteModule : public ProtobufModule,
     virtual bool wantUIFrame() override { return shouldDraw(); }
     virtual Observable *getUIFrameObservable() override { return this; }
 
+    void processUpgradedPacket(const meshtastic_MeshPacket &mp);
+
   protected:
     bool handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_RouteDiscovery *r) override;
 
@@ -70,4 +72,4 @@ class TraceRouteModule : public ProtobufModule,
     bool initialized = false;
 };
 
-extern TraceRouteModule *traceRouteModule;
\ No newline at end of file
+extern TraceRouteModule *traceRouteModule;

From 12c3ddf457a80390107e1cf253cf58fca03fbcdd Mon Sep 17 00:00:00 2001
From: GUVWAF 
Date: Thu, 25 Sep 2025 19:54:29 +0200
Subject: [PATCH 354/691] Resolve comments

---
 src/mesh/PacketHistory.cpp | 51 +++++++++++++++++++++++++++++++++-----
 src/mesh/PacketHistory.h   | 11 +++++++-
 2 files changed, 55 insertions(+), 7 deletions(-)

diff --git a/src/mesh/PacketHistory.cpp b/src/mesh/PacketHistory.cpp
index 09b1f84c9..97ed82b3d 100644
--- a/src/mesh/PacketHistory.cpp
+++ b/src/mesh/PacketHistory.cpp
@@ -66,11 +66,12 @@ bool PacketHistory::wasSeenRecently(const meshtastic_MeshPacket *p, bool withUpd
     r.id = p->id;
     r.sender = getFrom(p); // If 0 then use our ID
     r.next_hop = p->next_hop;
+    setHighestHopLimit(r, p->hop_limit);
     bool weWillRelay = false;
     uint8_t ourRelayID = nodeDB->getLastByteOfNodeNum(nodeDB->getNodeNum());
     if (p->relay_node == ourRelayID) { // If the relay_node is us, store it
         weWillRelay = true;
-        r.ourTxHopLimit = p->hop_limit;
+        setOurTxHopLimit(r, p->hop_limit);
         r.relayed_by[0] = p->relay_node;
     }
 
@@ -134,18 +135,35 @@ bool PacketHistory::wasSeenRecently(const meshtastic_MeshPacket *p, bool withUpd
             if (!weWillRelay) {
                 bool weWereRelayer = wasRelayer(ourRelayID, *found);
                 // We were a relayer and the packet came in with a hop limit that is one less than when we sent it out
-                if (weWereRelayer && p->hop_limit == found->ourTxHopLimit - 1) {
+                if (weWereRelayer && (p->hop_limit == getOurTxHopLimit(*found) || p->hop_limit == getOurTxHopLimit(*found) - 1)) {
                     r.relayed_by[0] = p->relay_node;
                     startIdx = 1; // Start copying existing relayers from index 1
                 }
                 // keep the original ourTxHopLimit
-                r.ourTxHopLimit = found->ourTxHopLimit;
+                setOurTxHopLimit(r, getOurTxHopLimit(*found));
             }
 
-            // Add the existing relayed_by to the new record
-            for (uint8_t i = 0; i < (NUM_RELAYERS - 1); i++) {
-                if (found->relayed_by[i] != 0)
+            // Preserve the highest hop_limit we've ever seen for this packet so upgrades aren't lost when a later copy has
+            // fewer hops remaining.
+            if (getHighestHopLimit(*found) > getHighestHopLimit(r))
+                setHighestHopLimit(r, getHighestHopLimit(*found));
+
+            // Add the existing relayed_by to the new record, avoiding duplicates
+            for (uint8_t i = 0; i < (NUM_RELAYERS - startIdx); i++) {
+                if (found->relayed_by[i] == 0)
+                    continue;
+
+                bool exists = false;
+                for (uint8_t j = 0; j < NUM_RELAYERS; j++) {
+                    if (r.relayed_by[j] == found->relayed_by[i]) {
+                        exists = true;
+                        break;
+                    }
+                }
+
+                if (!exists) {
                     r.relayed_by[i + startIdx] = found->relayed_by[i];
+                }
             }
             r.next_hop = found->next_hop; // keep the original next_hop (such that we check whether we were originally asked)
 #if VERBOSE_PACKET_HISTORY
@@ -409,3 +427,24 @@ void PacketHistory::removeRelayer(const uint8_t relayer, const uint32_t id, cons
               found->id, found->relayed_by[0], found->relayed_by[1], found->relayed_by[2], relayer, i != j);
 #endif
 }
+
+// Getters and setters for hop limit fields packed in hop_limit
+inline uint8_t PacketHistory::getHighestHopLimit(PacketRecord &r)
+{
+    return r.hop_limit & HOP_LIMIT_HIGHEST_MASK;
+}
+
+inline void PacketHistory::setHighestHopLimit(PacketRecord &r, uint8_t hopLimit)
+{
+    r.hop_limit = (r.hop_limit & ~HOP_LIMIT_HIGHEST_MASK) | (hopLimit & HOP_LIMIT_HIGHEST_MASK);
+}
+
+inline uint8_t PacketHistory::getOurTxHopLimit(PacketRecord &r)
+{
+    return (r.hop_limit & HOP_LIMIT_OUR_TX_MASK) >> HOP_LIMIT_OUR_TX_SHIFT;
+}
+
+inline void PacketHistory::setOurTxHopLimit(PacketRecord &r, uint8_t hopLimit)
+{
+    r.hop_limit = (r.hop_limit & ~HOP_LIMIT_OUR_TX_MASK) | ((hopLimit << HOP_LIMIT_OUR_TX_SHIFT) & HOP_LIMIT_OUR_TX_MASK);
+}
\ No newline at end of file
diff --git a/src/mesh/PacketHistory.h b/src/mesh/PacketHistory.h
index 20aac9999..08a625fe1 100644
--- a/src/mesh/PacketHistory.h
+++ b/src/mesh/PacketHistory.h
@@ -4,6 +4,9 @@
 
 // Number of relayers we keep track of. Use 6 to be efficient with memory alignment of PacketRecord to 20 bytes
 #define NUM_RELAYERS 6
+#define HOP_LIMIT_HIGHEST_MASK 0x07 // Bits 0-2
+#define HOP_LIMIT_OUR_TX_MASK 0x38  // Bits 3-5
+#define HOP_LIMIT_OUR_TX_SHIFT 3    // Bits 3-5
 
 /**
  * This is a mixin that adds a record of past packets we have seen
@@ -16,7 +19,8 @@ class PacketHistory
         PacketId id;
         uint32_t rxTimeMsec;              // Unix time in msecs - the time we received it,  0 means empty
         uint8_t next_hop;                 // The next hop asked for this packet
-        uint8_t ourTxHopLimit;            // The hop limit of the packet when we first transmitted it
+        uint8_t hop_limit;                // bit 0-2: Highest hop limit observed for this packet,
+                                          // bit 3-5: our hop limit when we first transmitted it
         uint8_t relayed_by[NUM_RELAYERS]; // Array of nodes that relayed this packet
     };                                    // 4B + 4B + 4B + 1B + 1B + 6B = 20B
 
@@ -39,6 +43,11 @@ class PacketHistory
      * @return true if node was indeed a relayer, false if not */
     bool wasRelayer(const uint8_t relayer, const PacketRecord &r, bool *wasSole = nullptr);
 
+    uint8_t getHighestHopLimit(PacketRecord &r);
+    void setHighestHopLimit(PacketRecord &r, uint8_t hopLimit);
+    uint8_t getOurTxHopLimit(PacketRecord &r);
+    void setOurTxHopLimit(PacketRecord &r, uint8_t hopLimit);
+
     PacketHistory(const PacketHistory &);            // non construction-copyable
     PacketHistory &operator=(const PacketHistory &); // non copyable
   public:

From 9980c56d8132999dddf752b0e447e6aea9a0aeee Mon Sep 17 00:00:00 2001
From: Jason P 
Date: Thu, 25 Sep 2025 17:48:34 -0500
Subject: [PATCH 355/691] Correct Inverted Mute Icon on Clock Display (#8111)

---
 src/graphics/SharedUIDisplay.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/graphics/SharedUIDisplay.cpp b/src/graphics/SharedUIDisplay.cpp
index 3937bcf50..dcaa5d69b 100644
--- a/src/graphics/SharedUIDisplay.cpp
+++ b/src/graphics/SharedUIDisplay.cpp
@@ -284,7 +284,7 @@ void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *ti
                 int iconX = iconRightEdge - mute_symbol_big_width;
                 int iconY = textY + (FONT_HEIGHT_SMALL - mute_symbol_big_height) / 2;
 
-                if (isInverted) {
+                if (isInverted && !force_no_invert) {
                     display->setColor(WHITE);
                     display->fillRect(iconX - 1, iconY - 1, mute_symbol_big_width + 2, mute_symbol_big_height + 2);
                     display->setColor(BLACK);

From 06240596833a31b08b9e95530027b3708a7d635f Mon Sep 17 00:00:00 2001
From: Jason P 
Date: Fri, 26 Sep 2025 11:17:15 -0500
Subject: [PATCH 356/691] Saving changes are required (#8122)

---
 src/graphics/draw/MenuHandler.cpp | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/src/graphics/draw/MenuHandler.cpp b/src/graphics/draw/MenuHandler.cpp
index 7c4f4e05f..43b3fb8ab 100644
--- a/src/graphics/draw/MenuHandler.cpp
+++ b/src/graphics/draw/MenuHandler.cpp
@@ -852,24 +852,31 @@ void menuHandler::GPSFormatMenu()
     bannerOptions.bannerCallback = [](int selected) -> void {
         if (selected == 1) {
             uiconfig.gps_format = meshtastic_DeviceUIConfig_GpsCoordinateFormat_DEC;
+            saveUIConfig();
             service->reloadConfig(SEGMENT_CONFIG);
         } else if (selected == 2) {
             uiconfig.gps_format = meshtastic_DeviceUIConfig_GpsCoordinateFormat_DMS;
+            saveUIConfig();
             service->reloadConfig(SEGMENT_CONFIG);
         } else if (selected == 3) {
             uiconfig.gps_format = meshtastic_DeviceUIConfig_GpsCoordinateFormat_UTM;
+            saveUIConfig();
             service->reloadConfig(SEGMENT_CONFIG);
         } else if (selected == 4) {
             uiconfig.gps_format = meshtastic_DeviceUIConfig_GpsCoordinateFormat_MGRS;
+            saveUIConfig();
             service->reloadConfig(SEGMENT_CONFIG);
         } else if (selected == 5) {
             uiconfig.gps_format = meshtastic_DeviceUIConfig_GpsCoordinateFormat_OLC;
+            saveUIConfig();
             service->reloadConfig(SEGMENT_CONFIG);
         } else if (selected == 6) {
             uiconfig.gps_format = meshtastic_DeviceUIConfig_GpsCoordinateFormat_OSGR;
+            saveUIConfig();
             service->reloadConfig(SEGMENT_CONFIG);
         } else if (selected == 7) {
             uiconfig.gps_format = meshtastic_DeviceUIConfig_GpsCoordinateFormat_MLS;
+            saveUIConfig();
             service->reloadConfig(SEGMENT_CONFIG);
         } else {
             menuQueue = position_base_menu;

From 2f1198ddf3a5558f31ccde577edf763a8e80f8f1 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
 <41898282+github-actions[bot]@users.noreply.github.com>
Date: Fri, 26 Sep 2025 11:17:38 -0500
Subject: [PATCH 357/691] Upgrade trunk (#8118)

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 e9bbba9ff..4e9de6a02 100644
--- a/.trunk/trunk.yaml
+++ b/.trunk/trunk.yaml
@@ -9,7 +9,7 @@ plugins:
 lint:
   enabled:
     - checkov@3.2.471
-    - renovate@41.127.2
+    - renovate@41.130.1
     - prettier@3.6.2
     - trufflehog@3.90.8
     - yamllint@1.37.1

From bc3db1b5c137db93088476467219fc1ab36e4932 Mon Sep 17 00:00:00 2001
From: Jonathan Bennett 
Date: Fri, 26 Sep 2025 18:23:09 -0500
Subject: [PATCH 358/691] Properly output the TCXO Voltage in yaml (#8128)

---
 src/platform/portduino/PortduinoGlue.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/platform/portduino/PortduinoGlue.h b/src/platform/portduino/PortduinoGlue.h
index 106900c48..ec6209487 100644
--- a/src/platform/portduino/PortduinoGlue.h
+++ b/src/platform/portduino/PortduinoGlue.h
@@ -224,7 +224,7 @@ extern struct portduino_config_struct {
             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;
+            out << YAML::Key << "DIO3_TCXO_VOLTAGE" << YAML::Value << YAML::Precision(3) << (float)dio3_tcxo_voltage / 1000;
         if (lora_usb_pid != 0x5512)
             out << YAML::Key << "USB_PID" << YAML::Value << YAML::Hex << lora_usb_pid;
         if (lora_usb_vid != 0x1A86)

From a2d86454d3c67599953b37354bb727b944ce0abd Mon Sep 17 00:00:00 2001
From: WillyJL 
Date: Sat, 27 Sep 2025 07:07:38 +0200
Subject: [PATCH 359/691] I2S: Fix silent RTTTL regression (#8129)

---
 src/AudioThread.h                          |  1 +
 src/modules/ExternalNotificationModule.cpp | 11 +++++++----
 2 files changed, 8 insertions(+), 4 deletions(-)

diff --git a/src/AudioThread.h b/src/AudioThread.h
index 286729909..cb1f804ac 100644
--- a/src/AudioThread.h
+++ b/src/AudioThread.h
@@ -26,6 +26,7 @@ class AudioThread : public concurrency::OSThread
         i2sRtttl->begin(rtttlFile, audioOut);
     }
 
+    // Also handles actually playing the RTTTL, needs to be called in loop
     bool isPlaying()
     {
         if (i2sRtttl != nullptr) {
diff --git a/src/modules/ExternalNotificationModule.cpp b/src/modules/ExternalNotificationModule.cpp
index 4b5b160be..1279078b1 100644
--- a/src/modules/ExternalNotificationModule.cpp
+++ b/src/modules/ExternalNotificationModule.cpp
@@ -89,11 +89,12 @@ int32_t ExternalNotificationModule::runOnce()
         return INT32_MAX; // we don't need this thread here...
     } else {
         uint32_t delay = EXT_NOTIFICATION_MODULE_OUTPUT_MS;
-        bool isPlaying = rtttl::isPlaying();
+        bool isRtttlPlaying = rtttl::isPlaying();
 #ifdef HAS_I2S
-        isPlaying = rtttl::isPlaying() || audioThread->isPlaying();
+        // audioThread->isPlaying() also handles actually playing the RTTTL, needs to be called in loop
+        isRtttlPlaying = isRtttlPlaying || audioThread->isPlaying();
 #endif
-        if ((nagCycleCutoff < millis()) && !isPlaying) {
+        if ((nagCycleCutoff < millis()) && !isRtttlPlaying) {
             // let the song finish if we reach timeout
             nagCycleCutoff = UINT32_MAX;
             LOG_INFO("Turning off external notification: ");
@@ -187,12 +188,14 @@ int32_t ExternalNotificationModule::runOnce()
 
         // Play RTTTL over i2s audio interface if enabled as buzzer
 #ifdef HAS_I2S
-        if (moduleConfig.external_notification.use_i2s_as_buzzer && canBuzz()) {
+        if (moduleConfig.external_notification.use_i2s_as_buzzer) {
             if (audioThread->isPlaying()) {
                 // Continue playing
             } else if (isNagging && (nagCycleCutoff >= millis())) {
                 audioThread->beginRttl(rtttlConfig.ringtone, strlen_P(rtttlConfig.ringtone));
             }
+            // we need fast updates to play the RTTTL
+            delay = EXT_NOTIFICATION_FAST_THREAD_MS;
         }
 #endif
         // now let the PWM buzzer play

From ab00e991f6bd616f572f25686acb830c63749b76 Mon Sep 17 00:00:00 2001
From: Ben Meadors 
Date: Sat, 27 Sep 2025 07:09:24 -0500
Subject: [PATCH 360/691] Revert cross-preset default-key bridging with UDP and
 disable UDP by default (#8130)

* Revert cross-preset UDP bridging

* Don't enable UDP by default
---
 src/mesh/NodeDB.cpp |  2 +-
 src/mesh/Router.cpp | 29 -----------------------------
 2 files changed, 1 insertion(+), 30 deletions(-)

diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp
index 97dfb3e52..a7172f4d1 100644
--- a/src/mesh/NodeDB.cpp
+++ b/src/mesh/NodeDB.cpp
@@ -701,7 +701,7 @@ void NodeDB::installDefaultConfig(bool preserveKey = false)
 #ifdef USERPREFS_NETWORK_ENABLED_PROTOCOLS
     config.network.enabled_protocols = USERPREFS_NETWORK_ENABLED_PROTOCOLS;
 #else
-    config.network.enabled_protocols = 1;
+    config.network.enabled_protocols = 0;
 #endif
 #endif
 
diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp
index 171c383ba..145b4dde8 100644
--- a/src/mesh/Router.cpp
+++ b/src/mesh/Router.cpp
@@ -483,35 +483,6 @@ DecodeState perhapsDecode(meshtastic_MeshPacket *p)
         }
     }
 
-#if HAS_UDP_MULTICAST
-    // Fallback: for UDP multicast, try default preset names with default PSK if normal channel match failed
-    if (!decrypted && p->transport_mechanism == meshtastic_MeshPacket_TransportMechanism_TRANSPORT_MULTICAST_UDP) {
-        if (channels.setDefaultPresetCryptoForHash(p->channel)) {
-            memcpy(bytes, p->encrypted.bytes, rawSize);
-            crypto->decrypt(p->from, p->id, rawSize, bytes);
-
-            meshtastic_Data decodedtmp;
-            memset(&decodedtmp, 0, sizeof(decodedtmp));
-            if (pb_decode_from_bytes(bytes, rawSize, &meshtastic_Data_msg, &decodedtmp) &&
-                decodedtmp.portnum != meshtastic_PortNum_UNKNOWN_APP) {
-                p->decoded = decodedtmp;
-                p->which_payload_variant = meshtastic_MeshPacket_decoded_tag;
-                // Map to our local default channel index (name+PSK default), not necessarily primary
-                ChannelIndex defaultIndex = channels.getPrimaryIndex();
-                for (ChannelIndex i = 0; i < channels.getNumChannels(); ++i) {
-                    if (channels.isDefaultChannel(i)) {
-                        defaultIndex = i;
-                        break;
-                    }
-                }
-                chIndex = defaultIndex;
-                decrypted = true;
-            } else {
-                LOG_WARN("UDP fallback decode attempted but failed for hash 0x%x", p->channel);
-            }
-        }
-    }
-#endif
     if (decrypted) {
         // parsing was successful
         p->channel = chIndex; // change to store the index instead of the hash

From e8627b2d0124e96aa8dc055f44cef1c7ff47058b Mon Sep 17 00:00:00 2001
From: Dzmitry Plashchynski 
Date: Sat, 27 Sep 2025 15:56:52 +0300
Subject: [PATCH 361/691] UIRenderer: display "No GPS present" only on the
 first line to avoid duplication

---
 src/graphics/draw/UIRenderer.cpp | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/src/graphics/draw/UIRenderer.cpp b/src/graphics/draw/UIRenderer.cpp
index 988e7eb53..2ccc0f861 100644
--- a/src/graphics/draw/UIRenderer.cpp
+++ b/src/graphics/draw/UIRenderer.cpp
@@ -125,8 +125,10 @@ void UIRenderer::drawGpsCoordinates(OLEDDisplay *display, int16_t x, int16_t y,
     char displayLine[32];
 
     if (!gps->getIsConnected() && !config.position.fixed_position) {
-        strcpy(displayLine, "No GPS present");
-        display->drawString(x, y, displayLine);
+        if (strcmp(mode, "line1") == 0) {
+            strcpy(displayLine, "No GPS present");
+            display->drawString(x, y, displayLine);
+        }
     } else if (!gps->getHasLock() && !config.position.fixed_position) {
         if (strcmp(mode, "line1") == 0) {
             strcpy(displayLine, "No GPS Lock");

From 045176789e2c4fe50ce4b8ce8a44b4bbfc710cdc Mon Sep 17 00:00:00 2001
From: Ben Meadors 
Date: Sat, 27 Sep 2025 08:32:43 -0500
Subject: [PATCH 362/691] Fix int comparison and client_base base should really
 not be on this list

---
 src/mesh/Router.cpp | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp
index 145b4dde8..7f17737e5 100644
--- a/src/mesh/Router.cpp
+++ b/src/mesh/Router.cpp
@@ -90,7 +90,7 @@ bool Router::shouldDecrementHopLimit(const meshtastic_MeshPacket *p)
     // For subsequent hops, check if previous relay is a favorite router
     // Optimized search for favorite routers with matching last byte
     // Check ordering optimized for IoT devices (cheapest checks first)
-    for (int i = 0; i < nodeDB->getNumMeshNodes(); i++) {
+    for (size_t i = 0; i < nodeDB->getNumMeshNodes(); i++) {
         meshtastic_NodeInfoLite *node = nodeDB->getMeshNodeByIndex(i);
         if (!node)
             continue;
@@ -105,7 +105,7 @@ bool Router::shouldDecrementHopLimit(const meshtastic_MeshPacket *p)
 
         // Check 3: role check (moderate cost - multiple comparisons)
         if (!IS_ONE_OF(node->user.role, meshtastic_Config_DeviceConfig_Role_ROUTER,
-                       meshtastic_Config_DeviceConfig_Role_ROUTER_LATE, meshtastic_Config_DeviceConfig_Role_CLIENT_BASE)) {
+                       meshtastic_Config_DeviceConfig_Role_ROUTER_LATE)) {
             continue;
         }
 

From bc516ebbacf9cb1cf112c55386771863ef2121a5 Mon Sep 17 00:00:00 2001
From: dfsx1 <60702962+dfsx1@users.noreply.github.com>
Date: Sat, 27 Sep 2025 13:33:07 +0000
Subject: [PATCH 363/691] Remove memcpy (#8079)

Obsolete since #7652 returns false for mismatching keys

Co-authored-by: dfsx1 
Co-authored-by: Ben Meadors 
---
 src/mesh/NodeDB.cpp | 3 ---
 1 file changed, 3 deletions(-)

diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp
index a7172f4d1..b512ae675 100644
--- a/src/mesh/NodeDB.cpp
+++ b/src/mesh/NodeDB.cpp
@@ -1667,9 +1667,6 @@ bool NodeDB::updateUser(uint32_t nodeId, meshtastic_User &p, uint8_t channelInde
             return false;
         }
         LOG_INFO("Public Key set for node, not updating!");
-        // we copy the key into the incoming packet, to prevent overwrite
-        p.public_key.size = 32;
-        memcpy(p.public_key.bytes, info->user.public_key.bytes, 32);
     } else if (p.public_key.size == 32) {
         LOG_INFO("Update Node Pubkey!");
     }

From 6448f069f84a0732026d192a37fa99a1a430711f Mon Sep 17 00:00:00 2001
From: ford-jones 
Date: Sun, 28 Sep 2025 13:17:57 +1300
Subject: [PATCH 364/691] Use channel as specified in the received packet

---
 src/modules/ExternalNotificationModule.cpp | 40 +++++++++++-----------
 1 file changed, 20 insertions(+), 20 deletions(-)

diff --git a/src/modules/ExternalNotificationModule.cpp b/src/modules/ExternalNotificationModule.cpp
index 5ee7834db..97d51fbd2 100644
--- a/src/modules/ExternalNotificationModule.cpp
+++ b/src/modules/ExternalNotificationModule.cpp
@@ -69,7 +69,7 @@ bool ascending = true;
 #endif
 #define EXT_NOTIFICATION_MODULE_OUTPUT_MS 1000
 
-#define EXT_NOTIFICATION_DEFAULT_THREAD_MS 25
+#define EXT_NOTIFICATION_FAST_THREAD_MS 25
 
 #define ASCII_BELL 0x07
 
@@ -88,12 +88,13 @@ int32_t ExternalNotificationModule::runOnce()
     if (!moduleConfig.external_notification.enabled) {
         return INT32_MAX; // we don't need this thread here...
     } else {
-
-        bool isPlaying = rtttl::isPlaying();
+        uint32_t delay = EXT_NOTIFICATION_MODULE_OUTPUT_MS;
+        bool isRtttlPlaying = rtttl::isPlaying();
 #ifdef HAS_I2S
-        isPlaying = rtttl::isPlaying() || audioThread->isPlaying();
+        // audioThread->isPlaying() also handles actually playing the RTTTL, needs to be called in loop
+        isRtttlPlaying = isRtttlPlaying || audioThread->isPlaying();
 #endif
-        if ((nagCycleCutoff < millis()) && !isPlaying) {
+        if ((nagCycleCutoff < millis()) && !isRtttlPlaying) {
             // let the song finish if we reach timeout
             nagCycleCutoff = UINT32_MAX;
             LOG_INFO("Turning off external notification: ");
@@ -116,21 +117,16 @@ int32_t ExternalNotificationModule::runOnce()
 
         // If the output is turned on, turn it back off after the given period of time.
         if (isNagging) {
-            if (externalTurnedOn[0] + (moduleConfig.external_notification.output_ms ? moduleConfig.external_notification.output_ms
-                                                                                    : EXT_NOTIFICATION_MODULE_OUTPUT_MS) <
-                millis()) {
+            delay = (moduleConfig.external_notification.output_ms ? moduleConfig.external_notification.output_ms
+                                                                  : EXT_NOTIFICATION_MODULE_OUTPUT_MS);
+            if (externalTurnedOn[0] + delay < millis()) {
                 setExternalState(0, !getExternal(0));
             }
-            if (externalTurnedOn[1] + (moduleConfig.external_notification.output_ms ? moduleConfig.external_notification.output_ms
-                                                                                    : EXT_NOTIFICATION_MODULE_OUTPUT_MS) <
-                millis()) {
+            if (externalTurnedOn[1] + delay < millis()) {
                 setExternalState(1, !getExternal(1));
             }
             // Only toggle buzzer output if not using PWM mode (to avoid conflict with RTTTL)
-            if (!moduleConfig.external_notification.use_pwm &&
-                externalTurnedOn[2] + (moduleConfig.external_notification.output_ms ? moduleConfig.external_notification.output_ms
-                                                                                    : EXT_NOTIFICATION_MODULE_OUTPUT_MS) <
-                    millis()) {
+            if (!moduleConfig.external_notification.use_pwm && externalTurnedOn[2] + delay < millis()) {
                 LOG_DEBUG("EXTERNAL 2 %d compared to %d", externalTurnedOn[2] + moduleConfig.external_notification.output_ms,
                           millis());
                 setExternalState(2, !getExternal(2));
@@ -181,6 +177,8 @@ int32_t ExternalNotificationModule::runOnce()
                     colorState = 1;
                 }
             }
+            // we need fast updates for the color change
+            delay = EXT_NOTIFICATION_FAST_THREAD_MS;
 #endif
 
 #ifdef T_WATCH_S3
@@ -190,12 +188,14 @@ int32_t ExternalNotificationModule::runOnce()
 
         // Play RTTTL over i2s audio interface if enabled as buzzer
 #ifdef HAS_I2S
-        if (moduleConfig.external_notification.use_i2s_as_buzzer && canBuzz()) {
+        if (moduleConfig.external_notification.use_i2s_as_buzzer) {
             if (audioThread->isPlaying()) {
                 // Continue playing
             } else if (isNagging && (nagCycleCutoff >= millis())) {
                 audioThread->beginRttl(rtttlConfig.ringtone, strlen_P(rtttlConfig.ringtone));
             }
+            // we need fast updates to play the RTTTL
+            delay = EXT_NOTIFICATION_FAST_THREAD_MS;
         }
 #endif
         // now let the PWM buzzer play
@@ -206,9 +206,11 @@ int32_t ExternalNotificationModule::runOnce()
                 // start the song again if we have time left
                 rtttl::begin(config.device.buzzer_gpio, rtttlConfig.ringtone);
             }
+            // we need fast updates to play the RTTTL
+            delay = EXT_NOTIFICATION_FAST_THREAD_MS;
         }
 
-        return EXT_NOTIFICATION_DEFAULT_THREAD_MS;
+        return delay;
     }
 }
 
@@ -457,9 +459,7 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP
                 }
             }
 
-            meshtastic_NodeInfoLite *sender = nodeDB->getMeshNode(mp.from);
-            meshtastic_Channel ch = channels.getByIndex(sender->channel ? sender->channel : channels.getPrimaryIndex());
-
+            meshtastic_Channel ch = channels.getByIndex(mp.channel ? mp.channel : channels.getPrimaryIndex());
             if (moduleConfig.external_notification.alert_bell) {
                 if (containsBell) {
                     LOG_INFO("externalNotificationModule - Notification Bell");

From abc011aeb974d24e905cdf9babf38bcdbc99451b Mon Sep 17 00:00:00 2001
From: ford-jones 
Date: Sun, 28 Sep 2025 16:26:45 +1300
Subject: [PATCH 365/691] Use channel as specified in the received packet for
 OLED screen notifications

---
 src/graphics/Screen.cpp | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp
index 44421e8fa..e6cee1824 100644
--- a/src/graphics/Screen.cpp
+++ b/src/graphics/Screen.cpp
@@ -1458,7 +1458,8 @@ int Screen::handleTextMessage(const meshtastic_MeshPacket *packet)
             }
             // === Prepare banner content ===
             const meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(packet->from);
-            const meshtastic_Channel channel = channels.getByIndex(node->channel ? node->channel : channels.getPrimaryIndex());
+            const meshtastic_Channel channel =
+                channels.getByIndex(packet->channel ? packet->channel : channels.getPrimaryIndex());
             const char *longName = (node && node->has_user) ? node->user.long_name : nullptr;
 
             const char *msgRaw = reinterpret_cast(packet->decoded.payload.bytes);

From 067939ca24ee3f40f8be9e8fa5bfc215a1a17272 Mon Sep 17 00:00:00 2001
From: Jason P 
Date: Sun, 28 Sep 2025 06:11:01 -0500
Subject: [PATCH 366/691] Correct altitudeLine getting clobbered in the great
 merge (#8138)

* Correct altitudeLine getting clobbered in the great merge

* Fix variable usage in altitude calculation
---
 src/graphics/draw/UIRenderer.cpp | 12 ++++++++++++
 1 file changed, 12 insertions(+)

diff --git a/src/graphics/draw/UIRenderer.cpp b/src/graphics/draw/UIRenderer.cpp
index 2ccc0f861..ff8cd20c5 100644
--- a/src/graphics/draw/UIRenderer.cpp
+++ b/src/graphics/draw/UIRenderer.cpp
@@ -1105,6 +1105,18 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU
             // === Fourth Row: Line 2 GPS Info ===
             UIRenderer::drawGpsCoordinates(display, x, getTextPositions(display)[line++], gpsStatus, "line2");
         }
+
+        // === Final Row: Altitude ===
+        char altitudeLine[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(altitudeLine, sizeof(altitudeLine), "Alt: %.0fft", alt * METERS_TO_FEET);
+        } else {
+            snprintf(altitudeLine, sizeof(altitudeLine), "Alt: %.0im", alt);
+        }
+        display->drawString(x, getTextPositions(display)[line++], altitudeLine);
     }
 #if !defined(M5STACK_UNITC6L)
     // === Draw Compass if heading is valid ===

From 8717c60f13e640d4b9367cd0ddc02765e6d457ca Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
 <41898282+github-actions[bot]@users.noreply.github.com>
Date: Sun, 28 Sep 2025 07:35:56 -0500
Subject: [PATCH 367/691] Update protobufs (#8142)

Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
---
 protobufs                                     |  2 +-
 src/mesh/generated/meshtastic/apponly.pb.h    |  2 +-
 src/mesh/generated/meshtastic/channel.pb.h    | 14 +++++++++-----
 src/mesh/generated/meshtastic/config.pb.h     |  3 ++-
 src/mesh/generated/meshtastic/deviceonly.pb.h |  4 ++--
 5 files changed, 15 insertions(+), 10 deletions(-)

diff --git a/protobufs b/protobufs
index 46b81e822..082bb7cfe 160000
--- a/protobufs
+++ b/protobufs
@@ -1 +1 @@
-Subproject commit 46b81e822af1b8e408f437092337f129dee693e6
+Subproject commit 082bb7cfeb2cba9d41be139cd324c4b43a14b3f9
diff --git a/src/mesh/generated/meshtastic/apponly.pb.h b/src/mesh/generated/meshtastic/apponly.pb.h
index f4c33bd79..db9dedaaf 100644
--- a/src/mesh/generated/meshtastic/apponly.pb.h
+++ b/src/mesh/generated/meshtastic/apponly.pb.h
@@ -55,7 +55,7 @@ extern const pb_msgdesc_t meshtastic_ChannelSet_msg;
 
 /* Maximum encoded size of messages (where known) */
 #define MESHTASTIC_MESHTASTIC_APPONLY_PB_H_MAX_SIZE meshtastic_ChannelSet_size
-#define meshtastic_ChannelSet_size               679
+#define meshtastic_ChannelSet_size               695
 
 #ifdef __cplusplus
 } /* extern "C" */
diff --git a/src/mesh/generated/meshtastic/channel.pb.h b/src/mesh/generated/meshtastic/channel.pb.h
index ca4310bf1..d5573a1e2 100644
--- a/src/mesh/generated/meshtastic/channel.pb.h
+++ b/src/mesh/generated/meshtastic/channel.pb.h
@@ -97,6 +97,8 @@ typedef struct _meshtastic_ChannelSettings {
     /* Per-channel module settings. */
     bool has_module_settings;
     meshtastic_ModuleSettings module_settings;
+    /* Whether or not we should receive notifactions / alerts through this channel */
+    bool mute;
 } meshtastic_ChannelSettings;
 
 /* A pair of a channel number, mode and the (sharable) settings for that channel */
@@ -128,10 +130,10 @@ extern "C" {
 
 
 /* Initializer values for message structs */
-#define meshtastic_ChannelSettings_init_default  {0, {0, {0}}, "", 0, 0, 0, false, meshtastic_ModuleSettings_init_default}
+#define meshtastic_ChannelSettings_init_default  {0, {0, {0}}, "", 0, 0, 0, false, meshtastic_ModuleSettings_init_default, 0}
 #define meshtastic_ModuleSettings_init_default   {0, 0}
 #define meshtastic_Channel_init_default          {0, false, meshtastic_ChannelSettings_init_default, _meshtastic_Channel_Role_MIN}
-#define meshtastic_ChannelSettings_init_zero     {0, {0, {0}}, "", 0, 0, 0, false, meshtastic_ModuleSettings_init_zero}
+#define meshtastic_ChannelSettings_init_zero     {0, {0, {0}}, "", 0, 0, 0, false, meshtastic_ModuleSettings_init_zero, 0}
 #define meshtastic_ModuleSettings_init_zero      {0, 0}
 #define meshtastic_Channel_init_zero             {0, false, meshtastic_ChannelSettings_init_zero, _meshtastic_Channel_Role_MIN}
 
@@ -145,6 +147,7 @@ extern "C" {
 #define meshtastic_ChannelSettings_uplink_enabled_tag 5
 #define meshtastic_ChannelSettings_downlink_enabled_tag 6
 #define meshtastic_ChannelSettings_module_settings_tag 7
+#define meshtastic_ChannelSettings_mute_tag      8
 #define meshtastic_Channel_index_tag             1
 #define meshtastic_Channel_settings_tag          2
 #define meshtastic_Channel_role_tag              3
@@ -157,7 +160,8 @@ X(a, STATIC,   SINGULAR, STRING,   name,              3) \
 X(a, STATIC,   SINGULAR, FIXED32,  id,                4) \
 X(a, STATIC,   SINGULAR, BOOL,     uplink_enabled,    5) \
 X(a, STATIC,   SINGULAR, BOOL,     downlink_enabled,   6) \
-X(a, STATIC,   OPTIONAL, MESSAGE,  module_settings,   7)
+X(a, STATIC,   OPTIONAL, MESSAGE,  module_settings,   7) \
+X(a, STATIC,   SINGULAR, BOOL,     mute,              8)
 #define meshtastic_ChannelSettings_CALLBACK NULL
 #define meshtastic_ChannelSettings_DEFAULT NULL
 #define meshtastic_ChannelSettings_module_settings_MSGTYPE meshtastic_ModuleSettings
@@ -187,8 +191,8 @@ extern const pb_msgdesc_t meshtastic_Channel_msg;
 
 /* Maximum encoded size of messages (where known) */
 #define MESHTASTIC_MESHTASTIC_CHANNEL_PB_H_MAX_SIZE meshtastic_Channel_size
-#define meshtastic_ChannelSettings_size          72
-#define meshtastic_Channel_size                  87
+#define meshtastic_ChannelSettings_size          74
+#define meshtastic_Channel_size                  89
 #define meshtastic_ModuleSettings_size           8
 
 #ifdef __cplusplus
diff --git a/src/mesh/generated/meshtastic/config.pb.h b/src/mesh/generated/meshtastic/config.pb.h
index 0453ecad2..327568316 100644
--- a/src/mesh/generated/meshtastic/config.pb.h
+++ b/src/mesh/generated/meshtastic/config.pb.h
@@ -26,7 +26,8 @@ typedef enum _meshtastic_Config_DeviceConfig_Role {
     meshtastic_Config_DeviceConfig_Role_ROUTER_CLIENT = 3,
     /* Description: Infrastructure node for extending network coverage by relaying messages with minimal overhead. Not visible in Nodes list.
  Technical Details: Mesh packets will simply be rebroadcasted over this node. Nodes configured with this role will not originate NodeInfo, Position, Telemetry
-   or any other packet type. They will simply rebroadcast any mesh packets on the same frequency, channel num, spread factor, and coding rate. */
+   or any other packet type. They will simply rebroadcast any mesh packets on the same frequency, channel num, spread factor, and coding rate.
+ Deprecated in v2.7.11 because it creates "holes" in the mesh rebroadcast chain. */
     meshtastic_Config_DeviceConfig_Role_REPEATER = 4,
     /* Description: Broadcasts GPS position packets as priority.
  Technical Details: Position Mesh packets will be prioritized higher and sent more frequently by default.
diff --git a/src/mesh/generated/meshtastic/deviceonly.pb.h b/src/mesh/generated/meshtastic/deviceonly.pb.h
index 7fab82ff7..b5b116137 100644
--- a/src/mesh/generated/meshtastic/deviceonly.pb.h
+++ b/src/mesh/generated/meshtastic/deviceonly.pb.h
@@ -360,8 +360,8 @@ 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        2277
-#define meshtastic_ChannelFile_size              718
+#define meshtastic_BackupPreferences_size        2293
+#define meshtastic_ChannelFile_size              734
 #define meshtastic_DeviceState_size              1737
 #define meshtastic_NodeInfoLite_size             196
 #define meshtastic_PositionLite_size             28

From 033fc0c8f392d203194cbb8d5a93fea03977db64 Mon Sep 17 00:00:00 2001
From: Ben Meadors 
Date: Sun, 28 Sep 2025 13:13:07 -0500
Subject: [PATCH 368/691] Validate CR and SF lora config (#8146)

* Validate CR and SF lora config

* No zero-bw

* Update src/modules/AdminModule.cpp

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Fix braces

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---
 src/modules/AdminModule.cpp | 45 +++++++++++++++++++++++++++----------
 1 file changed, 33 insertions(+), 12 deletions(-)

diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp
index 79ea7bc0c..1e2d4ebe8 100644
--- a/src/modules/AdminModule.cpp
+++ b/src/modules/AdminModule.cpp
@@ -707,20 +707,40 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c)
 #endif
         config.display = c.payload_variant.display;
         break;
-    case meshtastic_Config_lora_tag:
+
+    case meshtastic_Config_lora_tag: {
+        // Wrap the entire case in a block to scope variables and avoid crossing initialization
+        auto oldLoraConfig = config.lora;
+        auto validatedLora = c.payload_variant.lora;
+
         LOG_INFO("Set config: LoRa");
         config.has_lora = true;
+
+        if (validatedLora.coding_rate < 4 || validatedLora.coding_rate > 8) {
+            LOG_WARN("Invalid coding_rate %d, setting to 5", validatedLora.coding_rate);
+            validatedLora.coding_rate = 5;
+        }
+
+        if (validatedLora.spread_factor < 7 || validatedLora.spread_factor > 12) {
+            LOG_WARN("Invalid spread_factor %d, setting to 11", validatedLora.spread_factor);
+            validatedLora.spread_factor = 11;
+        }
+
+        if (validatedLora.bandwidth == 0) {
+            int originalBandwidth = validatedLora.bandwidth;
+            validatedLora.bandwidth = myRegion->wideLora ? 800 : 250;
+            LOG_WARN("Invalid bandwidth %d, setting to default", originalBandwidth);
+        }
+
         // If no lora radio parameters change, don't need to reboot
-        if (config.lora.use_preset == c.payload_variant.lora.use_preset && config.lora.region == c.payload_variant.lora.region &&
-            config.lora.modem_preset == c.payload_variant.lora.modem_preset &&
-            config.lora.bandwidth == c.payload_variant.lora.bandwidth &&
-            config.lora.spread_factor == c.payload_variant.lora.spread_factor &&
-            config.lora.coding_rate == c.payload_variant.lora.coding_rate &&
-            config.lora.tx_power == c.payload_variant.lora.tx_power &&
-            config.lora.frequency_offset == c.payload_variant.lora.frequency_offset &&
-            config.lora.override_frequency == c.payload_variant.lora.override_frequency &&
-            config.lora.channel_num == c.payload_variant.lora.channel_num &&
-            config.lora.sx126x_rx_boosted_gain == c.payload_variant.lora.sx126x_rx_boosted_gain) {
+        if (oldLoraConfig.use_preset == validatedLora.use_preset && oldLoraConfig.region == validatedLora.region &&
+            oldLoraConfig.modem_preset == validatedLora.modem_preset && oldLoraConfig.bandwidth == validatedLora.bandwidth &&
+            oldLoraConfig.spread_factor == validatedLora.spread_factor &&
+            oldLoraConfig.coding_rate == validatedLora.coding_rate && oldLoraConfig.tx_power == validatedLora.tx_power &&
+            oldLoraConfig.frequency_offset == validatedLora.frequency_offset &&
+            oldLoraConfig.override_frequency == validatedLora.override_frequency &&
+            oldLoraConfig.channel_num == validatedLora.channel_num &&
+            oldLoraConfig.sx126x_rx_boosted_gain == validatedLora.sx126x_rx_boosted_gain) {
             requiresReboot = false;
         }
 
@@ -739,7 +759,7 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c)
             digitalWrite(RF95_FAN_EN, HIGH ^ 0);
         }
 #endif
-        config.lora = c.payload_variant.lora;
+        config.lora = validatedLora;
         // If we're setting region for the first time, init the region and regenerate the keys
         if (isRegionUnset && config.lora.region > meshtastic_Config_LoRaConfig_RegionCode_UNSET) {
             if (!owner.is_licensed) {
@@ -771,6 +791,7 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c)
             }
         }
         break;
+    }
     case meshtastic_Config_bluetooth_tag:
         LOG_INFO("Set config: Bluetooth");
         config.has_bluetooth = true;

From a15d6547678272726f5338f232183411008a1844 Mon Sep 17 00:00:00 2001
From: Ben Meadors 
Date: Sun, 28 Sep 2025 15:30:53 -0500
Subject: [PATCH 369/691] Finish deprecating the Repeater role behavior (#8144)

* Finish deprecating the Repeater role behavior

* Validate

* Fixed bad if/else block

* Get your crap together!
---
 src/DisplayFormatters.cpp                 |   3 -
 src/gps/GPS.cpp                           |   5 -
 src/main.cpp                              |  12 +-
 src/mesh/FloodingRouter.cpp               |   5 +-
 src/mesh/FloodingRouter.h                 |   2 +-
 src/mesh/MeshService.cpp                  |   7 +-
 src/mesh/NextHopRouter.cpp                |   5 +-
 src/mesh/NodeDB.cpp                       |  10 +-
 src/mesh/RadioInterface.cpp               |   5 +-
 src/mesh/Router.cpp                       |   4 -
 src/modules/AdminModule.cpp               |  15 +-
 src/modules/Modules.cpp                   | 219 ++++++++++------------
 src/modules/NodeInfoModule.cpp            |   5 -
 src/modules/RoutingModule.cpp             |   2 -
 src/modules/Telemetry/DeviceTelemetry.cpp |   5 -
 src/modules/Telemetry/HostMetrics.cpp     |   4 -
 src/sleep.cpp                             |   3 +-
 userPrefs.jsonc                           |   2 +-
 18 files changed, 126 insertions(+), 187 deletions(-)

diff --git a/src/DisplayFormatters.cpp b/src/DisplayFormatters.cpp
index 5193e1cb4..246cf0022 100644
--- a/src/DisplayFormatters.cpp
+++ b/src/DisplayFormatters.cpp
@@ -76,9 +76,6 @@ const char *DisplayFormatters::getDeviceRole(meshtastic_Config_DeviceConfig_Role
     case meshtastic_Config_DeviceConfig_Role_ROUTER_LATE:
         return "Router Late";
         break;
-    case meshtastic_Config_DeviceConfig_Role_REPEATER:
-        return "Repeater";
-        break;
     default:
         return "Unknown";
         break;
diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp
index 3bf2710fe..0487d1fea 100644
--- a/src/gps/GPS.cpp
+++ b/src/gps/GPS.cpp
@@ -1104,11 +1104,6 @@ int32_t GPS::runOnce()
         publishUpdate();
     }
 
-    // Repeaters have no need for GPS
-    if (config.device.role == meshtastic_Config_DeviceConfig_Role_REPEATER) {
-        return disable();
-    }
-
     if (whileActive()) {
         // if we have received valid NMEA claim we are connected
         setConnected();
diff --git a/src/main.cpp b/src/main.cpp
index 38c8e8ca5..29a608dfe 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -793,14 +793,7 @@ void setup()
     }
 #endif
 
-    // If we're taking on the repeater role, use NextHopRouter and turn off 3V3_S rail because peripherals are not needed
-    if (config.device.role == meshtastic_Config_DeviceConfig_Role_REPEATER) {
-        router = new NextHopRouter();
-#ifdef PIN_3V3_EN
-        digitalWrite(PIN_3V3_EN, LOW);
-#endif
-    } else
-        router = new ReliableRouter();
+    router = new ReliableRouter();
 
     // only play start melody when role is not tracker or sensor
     if (config.power.is_power_saving == true &&
@@ -926,8 +919,7 @@ void setup()
     if (sensor_detected == false) {
 #endif
         if (HAS_GPS) {
-            if (config.device.role != meshtastic_Config_DeviceConfig_Role_REPEATER &&
-                config.position.gps_mode != meshtastic_Config_PositionConfig_GpsMode_NOT_PRESENT) {
+            if (config.position.gps_mode != meshtastic_Config_PositionConfig_GpsMode_NOT_PRESENT) {
                 gps = GPS::createGps();
                 if (gps) {
                     gpsStatus->observe(&gps->newStatus);
diff --git a/src/mesh/FloodingRouter.cpp b/src/mesh/FloodingRouter.cpp
index 3caee78f8..5d2d7653a 100644
--- a/src/mesh/FloodingRouter.cpp
+++ b/src/mesh/FloodingRouter.cpp
@@ -81,9 +81,8 @@ bool FloodingRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
 bool FloodingRouter::roleAllowsCancelingDupe(const meshtastic_MeshPacket *p)
 {
     if (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER ||
-        config.device.role == meshtastic_Config_DeviceConfig_Role_REPEATER ||
         config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER_LATE) {
-        // ROUTER, REPEATER, ROUTER_LATE should never cancel relaying a packet (i.e. we should always rebroadcast),
+        // ROUTER, ROUTER_LATE should never cancel relaying a packet (i.e. we should always rebroadcast),
         // even if we've heard another station rebroadcast it already.
         return false;
     }
@@ -102,7 +101,7 @@ bool FloodingRouter::roleAllowsCancelingDupe(const meshtastic_MeshPacket *p)
 void FloodingRouter::perhapsCancelDupe(const meshtastic_MeshPacket *p)
 {
     if (p->transport_mechanism == meshtastic_MeshPacket_TransportMechanism_TRANSPORT_LORA && roleAllowsCancelingDupe(p)) {
-        // cancel rebroadcast of this message *if* there was already one, unless we're a router/repeater!
+        // cancel rebroadcast of this message *if* there was already one, unless we're a router!
         // But only LoRa packets should be able to trigger this.
         if (Router::cancelSending(p->from, p->id))
             txRelayCanceled++;
diff --git a/src/mesh/FloodingRouter.h b/src/mesh/FloodingRouter.h
index 30ad5945b..eaf71d294 100644
--- a/src/mesh/FloodingRouter.h
+++ b/src/mesh/FloodingRouter.h
@@ -59,7 +59,7 @@ class FloodingRouter : public Router
      */
     virtual void sniffReceived(const meshtastic_MeshPacket *p, const meshtastic_Routing *c) override;
 
-    // Return false for roles like ROUTER or REPEATER which should always rebroadcast even when we've heard another rebroadcast of
+    // Return false for roles like ROUTER which should always rebroadcast even when we've heard another rebroadcast of
     // the same packet
     bool roleAllowsCancelingDupe(const meshtastic_MeshPacket *p);
 
diff --git a/src/mesh/MeshService.cpp b/src/mesh/MeshService.cpp
index a138ad1d1..1b2af082d 100644
--- a/src/mesh/MeshService.cpp
+++ b/src/mesh/MeshService.cpp
@@ -85,12 +85,11 @@ int MeshService::handleFromRadio(const meshtastic_MeshPacket *mp)
     powerFSM.trigger(EVENT_PACKET_FOR_PHONE); // Possibly keep the node from sleeping
 
     nodeDB->updateFrom(*mp); // update our DB state based off sniffing every RX packet from the radio
-    bool isPreferredRebroadcaster =
-        IS_ONE_OF(config.device.role, meshtastic_Config_DeviceConfig_Role_ROUTER, meshtastic_Config_DeviceConfig_Role_REPEATER);
+    bool isPreferredRebroadcaster = config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER;
     if (mp->which_payload_variant == meshtastic_MeshPacket_decoded_tag &&
         mp->decoded.portnum == meshtastic_PortNum_TELEMETRY_APP && mp->decoded.request_id > 0) {
-        LOG_DEBUG("Received telemetry response. Skip sending our NodeInfo"); //  because this potentially a Repeater which will
-                                                                             //  ignore our request for its NodeInfo
+        LOG_DEBUG("Received telemetry response. Skip sending our NodeInfo");
+        //  ignore our request for its NodeInfo
     } else if (mp->which_payload_variant == meshtastic_MeshPacket_decoded_tag && !nodeDB->getMeshNode(mp->from)->has_user &&
                nodeInfoModule && !isPreferredRebroadcaster && !nodeDB->isFull()) {
         if (airTime->isTxAllowedChannelUtil(true)) {
diff --git a/src/mesh/NextHopRouter.cpp b/src/mesh/NextHopRouter.cpp
index 4ae0a818e..cb483bc97 100644
--- a/src/mesh/NextHopRouter.cpp
+++ b/src/mesh/NextHopRouter.cpp
@@ -170,7 +170,6 @@ bool NextHopRouter::perhapsRelay(const meshtastic_MeshPacket *p)
  */
 uint8_t NextHopRouter::getNextHop(NodeNum to, uint8_t relay_node)
 {
-    // When we're a repeater router->sniffReceived will call NextHopRouter directly without checking for broadcast
     if (isBroadcast(to))
         return NO_NEXT_HOP_PREFERENCE;
 
@@ -208,7 +207,7 @@ bool NextHopRouter::roleAllowsCancelingFromTxQueue(const meshtastic_MeshPacket *
 {
     // Return true if we're allowed to cancel a packet in the txQueue (so we may never transmit it even once)
 
-    // Return false for roles like ROUTER, REPEATER, ROUTER_LATE which should always transmit the packet at least once.
+    // Return false for roles like ROUTER, ROUTER_LATE which should always transmit the packet at least once.
 
     return roleAllowsCancelingDupe(p); // same logic as FloodingRouter::roleAllowsCancelingDupe
 }
@@ -221,7 +220,7 @@ bool NextHopRouter::stopRetransmission(GlobalPacketId key)
         /* Only when we already transmitted a packet via LoRa, we will cancel the packet in the Tx queue
           to avoid canceling a transmission if it was ACKed super fast via MQTT */
         if (old->numRetransmissions < NUM_RELIABLE_RETX - 1) {
-            // We only cancel it if we are the original sender or if we're not a router(_late)/repeater
+            // We only cancel it if we are the original sender or if we're not a router(_late)
             if (isFromUs(p) || roleAllowsCancelingFromTxQueue(p)) {
                 // remove the 'original' (identified by originator and packet->id) from the txqueue and free it
                 cancelSending(getFrom(p), p->id);
diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp
index b512ae675..a32ee37f9 100644
--- a/src/mesh/NodeDB.cpp
+++ b/src/mesh/NodeDB.cpp
@@ -554,10 +554,9 @@ void NodeDB::installDefaultConfig(bool preserveKey = false)
 #endif
 
 #ifdef USERPREFS_CONFIG_DEVICE_ROLE
-    // Restrict ROUTER*, LOST AND FOUND, and REPEATER roles for security reasons
+    // Restrict ROUTER*, LOST AND FOUND roles for security reasons
     if (IS_ONE_OF(USERPREFS_CONFIG_DEVICE_ROLE, meshtastic_Config_DeviceConfig_Role_ROUTER,
-                  meshtastic_Config_DeviceConfig_Role_ROUTER_LATE, meshtastic_Config_DeviceConfig_Role_REPEATER,
-                  meshtastic_Config_DeviceConfig_Role_LOST_AND_FOUND)) {
+                  meshtastic_Config_DeviceConfig_Role_ROUTER_LATE, meshtastic_Config_DeviceConfig_Role_LOST_AND_FOUND)) {
         LOG_WARN("ROUTER roles are restricted, falling back to CLIENT role");
         config.device.role = meshtastic_Config_DeviceConfig_Role_CLIENT;
     } else {
@@ -906,11 +905,6 @@ void NodeDB::installRoleDefaults(meshtastic_Config_DeviceConfig_Role role)
         moduleConfig.telemetry.device_update_interval = ONE_DAY;
         owner.has_is_unmessagable = true;
         owner.is_unmessagable = true;
-    } else if (role == meshtastic_Config_DeviceConfig_Role_REPEATER) {
-        owner.has_is_unmessagable = true;
-        owner.is_unmessagable = true;
-        config.display.screen_on_secs = 1;
-        config.device.rebroadcast_mode = meshtastic_Config_DeviceConfig_RebroadcastMode_CORE_PORTNUMS_ONLY;
     } else if (role == meshtastic_Config_DeviceConfig_Role_SENSOR) {
         owner.has_is_unmessagable = true;
         owner.is_unmessagable = true;
diff --git a/src/mesh/RadioInterface.cpp b/src/mesh/RadioInterface.cpp
index 31c68c302..5233b8698 100644
--- a/src/mesh/RadioInterface.cpp
+++ b/src/mesh/RadioInterface.cpp
@@ -317,9 +317,8 @@ uint32_t RadioInterface::getTxDelayMsecWeightedWorst(float snr)
 /** Returns true if we should rebroadcast early like a ROUTER */
 bool RadioInterface::shouldRebroadcastEarlyLikeRouter(meshtastic_MeshPacket *p)
 {
-    // If we are a ROUTER or REPEATER, we always rebroadcast early
-    if (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER ||
-        config.device.role == meshtastic_Config_DeviceConfig_Role_REPEATER) {
+    // If we are a ROUTER, we always rebroadcast early
+    if (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER) {
         return true;
     }
 
diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp
index 7f17737e5..8f9fb28f9 100644
--- a/src/mesh/Router.cpp
+++ b/src/mesh/Router.cpp
@@ -399,10 +399,6 @@ DecodeState perhapsDecode(meshtastic_MeshPacket *p)
 {
     concurrency::LockGuard g(cryptLock);
 
-    if (config.device.role == meshtastic_Config_DeviceConfig_Role_REPEATER &&
-        config.device.rebroadcast_mode == meshtastic_Config_DeviceConfig_RebroadcastMode_ALL_SKIP_DECODING)
-        return DecodeState::DECODE_FAILURE;
-
     if (config.device.rebroadcast_mode == meshtastic_Config_DeviceConfig_RebroadcastMode_KNOWN_ONLY &&
         (nodeDB->getMeshNode(p->from) == NULL || !nodeDB->getMeshNode(p->from)->has_user)) {
         LOG_DEBUG("Node 0x%x not in nodeDB-> Rebroadcast mode KNOWN_ONLY will ignore packet", p->from);
diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp
index 1e2d4ebe8..7544db121 100644
--- a/src/modules/AdminModule.cpp
+++ b/src/modules/AdminModule.cpp
@@ -611,10 +611,9 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c)
         }
         config.device = c.payload_variant.device;
         if (config.device.rebroadcast_mode == meshtastic_Config_DeviceConfig_RebroadcastMode_NONE &&
-            IS_ONE_OF(config.device.role, meshtastic_Config_DeviceConfig_Role_ROUTER,
-                      meshtastic_Config_DeviceConfig_Role_REPEATER)) {
+            config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER) {
             config.device.rebroadcast_mode = meshtastic_Config_DeviceConfig_RebroadcastMode_ALL;
-            const char *warning = "Rebroadcast mode can't be set to NONE for a router or repeater";
+            const char *warning = "Rebroadcast mode can't be set to NONE for a router";
             LOG_WARN(warning);
             sendWarning(warning);
         }
@@ -627,8 +626,9 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c)
             LOG_DEBUG("Tried to set node_info_broadcast_secs too low, setting to %d", min_node_info_broadcast_secs);
             config.device.node_info_broadcast_secs = min_node_info_broadcast_secs;
         }
-        // Router Client is deprecated; Set it to client
-        if (c.payload_variant.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER_CLIENT) {
+        // Router Client and Repeater deprecated; Set it to client
+        if (IS_ONE_OF(c.payload_variant.device.role, meshtastic_Config_DeviceConfig_Role_ROUTER_CLIENT,
+                      meshtastic_Config_DeviceConfig_Role_REPEATER)) {
             config.device.role = meshtastic_Config_DeviceConfig_Role_CLIENT;
             if (moduleConfig.store_forward.enabled && !moduleConfig.store_forward.is_server) {
                 moduleConfig.store_forward.is_server = true;
@@ -637,10 +637,9 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c)
             }
         }
 #if USERPREFS_EVENT_MODE
-        // If we're in event mode, nobody is a Router or Repeater
+        // If we're in event mode, nobody is a Router or Router Late
         if (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER ||
-            config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER_LATE ||
-            config.device.role == meshtastic_Config_DeviceConfig_Role_REPEATER) {
+            config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER_LATE) {
             config.device.role = meshtastic_Config_DeviceConfig_Role_CLIENT;
         }
 #endif
diff --git a/src/modules/Modules.cpp b/src/modules/Modules.cpp
index bd8997493..02962dd59 100644
--- a/src/modules/Modules.cpp
+++ b/src/modules/Modules.cpp
@@ -112,204 +112,191 @@
  */
 void setupModules()
 {
-    if (config.device.role != meshtastic_Config_DeviceConfig_Role_REPEATER) {
 #if (HAS_BUTTON || ARCH_PORTDUINO) && !MESHTASTIC_EXCLUDE_INPUTBROKER
-        if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
-            inputBroker = new InputBroker();
-            systemCommandsModule = new SystemCommandsModule();
-            buzzerFeedbackThread = new BuzzerFeedbackThread();
-        }
+    if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
+        inputBroker = new InputBroker();
+        systemCommandsModule = new SystemCommandsModule();
+        buzzerFeedbackThread = new BuzzerFeedbackThread();
+    }
 #endif
 #if !MESHTASTIC_EXCLUDE_ADMIN
-        adminModule = new AdminModule();
+    adminModule = new AdminModule();
 #endif
 #if !MESHTASTIC_EXCLUDE_NODEINFO
-        nodeInfoModule = new NodeInfoModule();
+    nodeInfoModule = new NodeInfoModule();
 #endif
 #if !MESHTASTIC_EXCLUDE_GPS
-        positionModule = new PositionModule();
+    positionModule = new PositionModule();
 #endif
 #if !MESHTASTIC_EXCLUDE_WAYPOINT
-        waypointModule = new WaypointModule();
+    waypointModule = new WaypointModule();
 #endif
 #if !MESHTASTIC_EXCLUDE_TEXTMESSAGE
-        textMessageModule = new TextMessageModule();
+    textMessageModule = new TextMessageModule();
 #endif
 #if !MESHTASTIC_EXCLUDE_TRACEROUTE
-        traceRouteModule = new TraceRouteModule();
+    traceRouteModule = new TraceRouteModule();
 #endif
 #if !MESHTASTIC_EXCLUDE_NEIGHBORINFO
-        if (moduleConfig.has_neighbor_info && moduleConfig.neighbor_info.enabled) {
-            neighborInfoModule = new NeighborInfoModule();
-        }
+    if (moduleConfig.has_neighbor_info && moduleConfig.neighbor_info.enabled) {
+        neighborInfoModule = new NeighborInfoModule();
+    }
 #endif
 #if !MESHTASTIC_EXCLUDE_DETECTIONSENSOR
-        if (moduleConfig.has_detection_sensor && moduleConfig.detection_sensor.enabled) {
-            detectionSensorModule = new DetectionSensorModule();
-        }
+    if (moduleConfig.has_detection_sensor && moduleConfig.detection_sensor.enabled) {
+        detectionSensorModule = new DetectionSensorModule();
+    }
 #endif
 #if !MESHTASTIC_EXCLUDE_ATAK
-        if (IS_ONE_OF(config.device.role, meshtastic_Config_DeviceConfig_Role_TAK,
-                      meshtastic_Config_DeviceConfig_Role_TAK_TRACKER)) {
-            atakPluginModule = new AtakPluginModule();
-        }
+    if (IS_ONE_OF(config.device.role, meshtastic_Config_DeviceConfig_Role_TAK, meshtastic_Config_DeviceConfig_Role_TAK_TRACKER)) {
+        atakPluginModule = new AtakPluginModule();
+    }
 #endif
 #if !MESHTASTIC_EXCLUDE_PKI
-        keyVerificationModule = new KeyVerificationModule();
+    keyVerificationModule = new KeyVerificationModule();
 #endif
 #if !MESHTASTIC_EXCLUDE_DROPZONE
-        dropzoneModule = new DropzoneModule();
+    dropzoneModule = new DropzoneModule();
 #endif
 #if !MESHTASTIC_EXCLUDE_GENERIC_THREAD_MODULE
-        new GenericThreadModule();
+    new GenericThreadModule();
 #endif
-        // Note: if the rest of meshtastic doesn't need to explicitly use your module, you do not need to assign the instance
-        // to a global variable.
+    // Note: if the rest of meshtastic doesn't need to explicitly use your module, you do not need to assign the instance
+    // to a global variable.
 
 #if !MESHTASTIC_EXCLUDE_REMOTEHARDWARE
-        new RemoteHardwareModule();
+    new RemoteHardwareModule();
 #endif
 #if !MESHTASTIC_EXCLUDE_POWERSTRESS
-        new PowerStressModule();
+    new PowerStressModule();
 #endif
-        // Example: Put your module here
-        // new ReplyModule();
+    // Example: Put your module here
+    // new ReplyModule();
 #if (HAS_BUTTON || ARCH_PORTDUINO) && !MESHTASTIC_EXCLUDE_INPUTBROKER
-        if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
-            rotaryEncoderInterruptImpl1 = new RotaryEncoderInterruptImpl1();
-            if (!rotaryEncoderInterruptImpl1->init()) {
-                delete rotaryEncoderInterruptImpl1;
-                rotaryEncoderInterruptImpl1 = nullptr;
-            }
+    if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
+        rotaryEncoderInterruptImpl1 = new RotaryEncoderInterruptImpl1();
+        if (!rotaryEncoderInterruptImpl1->init()) {
+            delete rotaryEncoderInterruptImpl1;
+            rotaryEncoderInterruptImpl1 = nullptr;
+        }
 #ifdef T_LORA_PAGER
-            // use a special FSM based rotary encoder version for T-LoRa Pager
-            rotaryEncoderImpl = new RotaryEncoderImpl();
-            if (!rotaryEncoderImpl->init()) {
-                delete rotaryEncoderImpl;
-                rotaryEncoderImpl = nullptr;
-            }
+        // use a special FSM based rotary encoder version for T-LoRa Pager
+        rotaryEncoderImpl = new RotaryEncoderImpl();
+        if (!rotaryEncoderImpl->init()) {
+            delete rotaryEncoderImpl;
+            rotaryEncoderImpl = nullptr;
+        }
 #else
-            upDownInterruptImpl1 = new UpDownInterruptImpl1();
-            if (!upDownInterruptImpl1->init()) {
-                delete upDownInterruptImpl1;
-                upDownInterruptImpl1 = nullptr;
-            }
+        upDownInterruptImpl1 = new UpDownInterruptImpl1();
+        if (!upDownInterruptImpl1->init()) {
+            delete upDownInterruptImpl1;
+            upDownInterruptImpl1 = nullptr;
+        }
 #endif
-            cardKbI2cImpl = new CardKbI2cImpl();
-            cardKbI2cImpl->init();
+        cardKbI2cImpl = new CardKbI2cImpl();
+        cardKbI2cImpl->init();
 #if defined(M5STACK_UNITC6L)
-            i2cButton = new i2cButtonThread("i2cButtonThread");
+        i2cButton = new i2cButtonThread("i2cButtonThread");
 #endif
 #ifdef INPUTBROKER_MATRIX_TYPE
-            kbMatrixImpl = new KbMatrixImpl();
-            kbMatrixImpl->init();
+        kbMatrixImpl = new KbMatrixImpl();
+        kbMatrixImpl->init();
 #endif // INPUTBROKER_MATRIX_TYPE
 #ifdef INPUTBROKER_SERIAL_TYPE
-            aSerialKeyboardImpl = new SerialKeyboardImpl();
-            aSerialKeyboardImpl->init();
+        aSerialKeyboardImpl = new SerialKeyboardImpl();
+        aSerialKeyboardImpl->init();
 #endif // INPUTBROKER_MATRIX_TYPE
-        }
+    }
 #endif // HAS_BUTTON
 #if ARCH_PORTDUINO
-        if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
-            seesawRotary = new SeesawRotary("SeesawRotary");
-            if (!seesawRotary->init()) {
-                delete seesawRotary;
-                seesawRotary = nullptr;
-            }
-            aLinuxInputImpl = new LinuxInputImpl();
-            aLinuxInputImpl->init();
+    if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
+        seesawRotary = new SeesawRotary("SeesawRotary");
+        if (!seesawRotary->init()) {
+            delete seesawRotary;
+            seesawRotary = nullptr;
         }
+        aLinuxInputImpl = new LinuxInputImpl();
+        aLinuxInputImpl->init();
+    }
 #endif
 #if !MESHTASTIC_EXCLUDE_INPUTBROKER && HAS_TRACKBALL
-        if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
-            trackballInterruptImpl1 = new TrackballInterruptImpl1();
-            trackballInterruptImpl1->init(TB_DOWN, TB_UP, TB_LEFT, TB_RIGHT, TB_PRESS);
-        }
+    if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
+        trackballInterruptImpl1 = new TrackballInterruptImpl1();
+        trackballInterruptImpl1->init(TB_DOWN, TB_UP, TB_LEFT, TB_RIGHT, TB_PRESS);
+    }
 #endif
 #ifdef INPUTBROKER_EXPRESSLRSFIVEWAY_TYPE
-        expressLRSFiveWayInput = new ExpressLRSFiveWay();
+    expressLRSFiveWayInput = new ExpressLRSFiveWay();
 #endif
 #if HAS_SCREEN && !MESHTASTIC_EXCLUDE_CANNEDMESSAGES
-        if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
-            cannedMessageModule = new CannedMessageModule();
-        }
+    if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
+        cannedMessageModule = new CannedMessageModule();
+    }
 #endif
 #if ARCH_PORTDUINO
-        new HostMetricsModule();
+    new HostMetricsModule();
 #endif
 #if HAS_TELEMETRY
-        new DeviceTelemetryModule();
+    new DeviceTelemetryModule();
 #endif
 #if HAS_TELEMETRY && HAS_SENSOR && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
-        if (moduleConfig.has_telemetry &&
-            (moduleConfig.telemetry.environment_measurement_enabled || moduleConfig.telemetry.environment_screen_enabled)) {
-            new EnvironmentTelemetryModule();
-        }
+    if (moduleConfig.has_telemetry &&
+        (moduleConfig.telemetry.environment_measurement_enabled || moduleConfig.telemetry.environment_screen_enabled)) {
+        new EnvironmentTelemetryModule();
+    }
 #if __has_include("Adafruit_PM25AQI.h")
-        if (moduleConfig.has_telemetry && moduleConfig.telemetry.air_quality_enabled &&
-            nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_PMSA003I].first > 0) {
-            new AirQualityTelemetryModule();
-        }
+    if (moduleConfig.has_telemetry && moduleConfig.telemetry.air_quality_enabled &&
+        nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_PMSA003I].first > 0) {
+        new AirQualityTelemetryModule();
+    }
 #endif
 #if !MESHTASTIC_EXCLUDE_HEALTH_TELEMETRY
-        if (nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_MAX30102].first > 0 ||
-            nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_MLX90614].first > 0) {
-            new HealthTelemetryModule();
-        }
+    if (nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_MAX30102].first > 0 ||
+        nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_MLX90614].first > 0) {
+        new HealthTelemetryModule();
+    }
 #endif
 #endif
 #if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_POWER_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
-        if (moduleConfig.has_telemetry &&
-            (moduleConfig.telemetry.power_measurement_enabled || moduleConfig.telemetry.power_screen_enabled)) {
-            new PowerTelemetryModule();
-        }
+    if (moduleConfig.has_telemetry &&
+        (moduleConfig.telemetry.power_measurement_enabled || moduleConfig.telemetry.power_screen_enabled)) {
+        new PowerTelemetryModule();
+    }
 #endif
 #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 (moduleConfig.has_serial && moduleConfig.serial.enabled &&
-            config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
-            new SerialModule();
-        }
+    if (moduleConfig.has_serial && moduleConfig.serial.enabled &&
+        config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
+        new SerialModule();
+    }
 #endif
 #endif
 #ifdef ARCH_ESP32
-        // Only run on an esp32 based device.
+    // Only run on an esp32 based device.
 #if defined(USE_SX1280) && !MESHTASTIC_EXCLUDE_AUDIO
-        audioModule = new AudioModule();
+    audioModule = new AudioModule();
 #endif
 #if !MESHTASTIC_EXCLUDE_PAXCOUNTER
-        if (moduleConfig.has_paxcounter && moduleConfig.paxcounter.enabled) {
-            paxcounterModule = new PaxcounterModule();
-        }
+    if (moduleConfig.has_paxcounter && moduleConfig.paxcounter.enabled) {
+        paxcounterModule = new PaxcounterModule();
+    }
 #endif
 #endif
 #if defined(ARCH_ESP32) || defined(ARCH_PORTDUINO)
 #if !MESHTASTIC_EXCLUDE_STOREFORWARD
-        if (moduleConfig.has_store_forward && moduleConfig.store_forward.enabled) {
-            storeForwardModule = new StoreForwardModule();
-        }
+    if (moduleConfig.has_store_forward && moduleConfig.store_forward.enabled) {
+        storeForwardModule = new StoreForwardModule();
+    }
 #endif
 #endif
 #if !MESHTASTIC_EXCLUDE_EXTERNALNOTIFICATION
-        externalNotificationModule = new ExternalNotificationModule();
+    externalNotificationModule = new ExternalNotificationModule();
 #endif
 #if !MESHTASTIC_EXCLUDE_RANGETEST && !MESHTASTIC_EXCLUDE_GPS
-        if (moduleConfig.has_range_test && moduleConfig.range_test.enabled)
-            new RangeTestModule();
+    if (moduleConfig.has_range_test && moduleConfig.range_test.enabled)
+        new RangeTestModule();
 #endif
-    } else {
-#if !MESHTASTIC_EXCLUDE_ADMIN
-        adminModule = new AdminModule();
-#endif
-#if HAS_TELEMETRY
-        new DeviceTelemetryModule();
-#endif
-#if !MESHTASTIC_EXCLUDE_TRACEROUTE
-        traceRouteModule = new TraceRouteModule();
-#endif
-    }
     // NOTE! This module must be added LAST because it likes to check for replies from other modules and avoid sending extra
     // acks
     routingModule = new RoutingModule();
diff --git a/src/modules/NodeInfoModule.cpp b/src/modules/NodeInfoModule.cpp
index 276a11b3a..2c3c274d2 100644
--- a/src/modules/NodeInfoModule.cpp
+++ b/src/modules/NodeInfoModule.cpp
@@ -94,11 +94,6 @@ meshtastic_MeshPacket *NodeInfoModule::allocReply()
             u.public_key.bytes[0] = 0;
             u.public_key.size = 0;
         }
-        // Coerce unmessagable for Repeater role
-        if (u.role == meshtastic_Config_DeviceConfig_Role_REPEATER) {
-            u.has_is_unmessagable = true;
-            u.is_unmessagable = true;
-        }
 
         LOG_INFO("Send owner %s/%s/%s", u.id, u.long_name, u.short_name);
         lastSentToMesh = millis();
diff --git a/src/modules/RoutingModule.cpp b/src/modules/RoutingModule.cpp
index e7e92c79a..fbe3a9cee 100644
--- a/src/modules/RoutingModule.cpp
+++ b/src/modules/RoutingModule.cpp
@@ -42,8 +42,6 @@ bool RoutingModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, mesh
 
 meshtastic_MeshPacket *RoutingModule::allocReply()
 {
-    if (config.device.role == meshtastic_Config_DeviceConfig_Role_REPEATER)
-        return NULL;
     assert(currentRequest);
 
     return NULL;
diff --git a/src/modules/Telemetry/DeviceTelemetry.cpp b/src/modules/Telemetry/DeviceTelemetry.cpp
index 98d5b19d0..ad148b759 100644
--- a/src/modules/Telemetry/DeviceTelemetry.cpp
+++ b/src/modules/Telemetry/DeviceTelemetry.cpp
@@ -26,7 +26,6 @@ int32_t DeviceTelemetryModule::runOnce()
           Default::getConfiguredOrDefaultMsScaled(moduleConfig.telemetry.device_update_interval,
                                                   default_telemetry_broadcast_interval_secs, numOnlineNodes))) &&
         airTime->isTxAllowedChannelUtil(!isImpoliteRole) && airTime->isTxAllowedAirUtil() &&
-        config.device.role != meshtastic_Config_DeviceConfig_Role_REPEATER &&
         config.device.role != meshtastic_Config_DeviceConfig_Role_CLIENT_HIDDEN) {
         sendTelemetry();
         lastSentToMesh = uptimeLastMs;
@@ -44,10 +43,6 @@ int32_t DeviceTelemetryModule::runOnce()
 
 bool DeviceTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_Telemetry *t)
 {
-    // Don't worry about storing telemetry in NodeDB if we're a repeater
-    if (config.device.role == meshtastic_Config_DeviceConfig_Role_REPEATER)
-        return false;
-
     if (t->which_variant == meshtastic_Telemetry_device_metrics_tag) {
 #if defined(DEBUG_PORT) && !defined(DEBUG_MUTE)
         const char *sender = getSenderShortName(mp);
diff --git a/src/modules/Telemetry/HostMetrics.cpp b/src/modules/Telemetry/HostMetrics.cpp
index dcde495a2..577132006 100644
--- a/src/modules/Telemetry/HostMetrics.cpp
+++ b/src/modules/Telemetry/HostMetrics.cpp
@@ -22,10 +22,6 @@ int32_t HostMetricsModule::runOnce()
 
 bool HostMetricsModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_Telemetry *t)
 {
-    // Don't worry about storing telemetry in NodeDB if we're a repeater
-    if (config.device.role == meshtastic_Config_DeviceConfig_Role_REPEATER)
-        return false;
-
     if (t->which_variant == meshtastic_Telemetry_host_metrics_tag) {
 #if defined(DEBUG_PORT) && !defined(DEBUG_MUTE)
         const char *sender = getSenderShortName(mp);
diff --git a/src/sleep.cpp b/src/sleep.cpp
index 0f8931292..ec560cb90 100644
--- a/src/sleep.cpp
+++ b/src/sleep.cpp
@@ -532,8 +532,7 @@ void enableModemSleep()
 
 bool shouldLoraWake(uint32_t msecToWake)
 {
-    return msecToWake < portMAX_DELAY && (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER ||
-                                          config.device.role == meshtastic_Config_DeviceConfig_Role_REPEATER);
+    return msecToWake < portMAX_DELAY && (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER);
 }
 
 void enableLoraInterrupt()
diff --git a/userPrefs.jsonc b/userPrefs.jsonc
index f6f3ef995..464eb968c 100644
--- a/userPrefs.jsonc
+++ b/userPrefs.jsonc
@@ -21,7 +21,7 @@
   // "USERPREFS_CONFIG_LORA_REGION": "meshtastic_Config_LoRaConfig_RegionCode_US",
   // "USERPREFS_CONFIG_OWNER_LONG_NAME": "My Long Name",
   // "USERPREFS_CONFIG_OWNER_SHORT_NAME": "MLN",
-  // "USERPREFS_CONFIG_DEVICE_ROLE": "meshtastic_Config_DeviceConfig_Role_CLIENT", // Defaults to CLIENT. ROUTER*, LOST AND FOUND, and REPEATER roles are restricted.
+  // "USERPREFS_CONFIG_DEVICE_ROLE": "meshtastic_Config_DeviceConfig_Role_CLIENT", // Defaults to CLIENT. ROUTER*, and LOST AND FOUND roles are restricted.
   // "USERPREFS_EVENT_MODE": "1",
   // "USERPREFS_FIRMWARE_EDITION": "meshtastic_FirmwareEdition_BURNING_MAN",
   // "USERPREFS_FIXED_BLUETOOTH": "121212",

From 777e11bad97777d6351c04cf0e09c4983b0faff0 Mon Sep 17 00:00:00 2001
From: Clive Blackledge 
Date: Sun, 28 Sep 2025 14:42:51 -0700
Subject: [PATCH 370/691] Bug / Send upgraded (duplicate) packets to phone if
 the queue removal failed. (#8148)

* Add seenRecently = true if wasUpgraded is true but unable to remove from queue (i.e. already sent/processed).

* Consistent comment between FloodingRouter and HopRouter
---
 src/mesh/FloodingRouter.cpp | 4 ++++
 src/mesh/NextHopRouter.cpp  | 4 ++++
 2 files changed, 8 insertions(+)

diff --git a/src/mesh/FloodingRouter.cpp b/src/mesh/FloodingRouter.cpp
index 3caee78f8..7ce106317 100644
--- a/src/mesh/FloodingRouter.cpp
+++ b/src/mesh/FloodingRouter.cpp
@@ -54,6 +54,10 @@ bool FloodingRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
             // We already enqueued the improved copy, so make sure the incoming packet stops here.
             return true;
         }
+
+        // No queue entry was replaced by this upgraded copy, so treat it as a duplicate to avoid
+        // delivering the same packet to applications/phone twice with different hop limits.
+        seenRecently = true;
     }
 
     if (seenRecently) {
diff --git a/src/mesh/NextHopRouter.cpp b/src/mesh/NextHopRouter.cpp
index 4ae0a818e..dce436471 100644
--- a/src/mesh/NextHopRouter.cpp
+++ b/src/mesh/NextHopRouter.cpp
@@ -64,6 +64,10 @@ bool NextHopRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
             // We already enqueued the improved copy, so make sure the incoming packet stops here.
             return true;
         }
+
+        // No queue entry was replaced by this upgraded copy, so treat it as a duplicate to avoid
+        // delivering the same packet to applications/phone twice with different hop limits.
+        seenRecently = true;
     }
 
     if (seenRecently) {

From a1c658a467ee802b1fb64f8d82c6d335a379d986 Mon Sep 17 00:00:00 2001
From: Clive Blackledge 
Date: Sun, 28 Sep 2025 14:42:51 -0700
Subject: [PATCH 371/691] Bug / Send upgraded (duplicate) packets to phone if
 the queue removal failed. (#8148)

* Add seenRecently = true if wasUpgraded is true but unable to remove from queue (i.e. already sent/processed).

* Consistent comment between FloodingRouter and HopRouter
---
 src/mesh/FloodingRouter.cpp | 4 ++++
 src/mesh/NextHopRouter.cpp  | 4 ++++
 2 files changed, 8 insertions(+)

diff --git a/src/mesh/FloodingRouter.cpp b/src/mesh/FloodingRouter.cpp
index 5d2d7653a..1d8ac247f 100644
--- a/src/mesh/FloodingRouter.cpp
+++ b/src/mesh/FloodingRouter.cpp
@@ -54,6 +54,10 @@ bool FloodingRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
             // We already enqueued the improved copy, so make sure the incoming packet stops here.
             return true;
         }
+
+        // No queue entry was replaced by this upgraded copy, so treat it as a duplicate to avoid
+        // delivering the same packet to applications/phone twice with different hop limits.
+        seenRecently = true;
     }
 
     if (seenRecently) {
diff --git a/src/mesh/NextHopRouter.cpp b/src/mesh/NextHopRouter.cpp
index cb483bc97..0461d7eb6 100644
--- a/src/mesh/NextHopRouter.cpp
+++ b/src/mesh/NextHopRouter.cpp
@@ -64,6 +64,10 @@ bool NextHopRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
             // We already enqueued the improved copy, so make sure the incoming packet stops here.
             return true;
         }
+
+        // No queue entry was replaced by this upgraded copy, so treat it as a duplicate to avoid
+        // delivering the same packet to applications/phone twice with different hop limits.
+        seenRecently = true;
     }
 
     if (seenRecently) {

From a3e6f16378bfd89bcae91191dfd734278dd9cc6b Mon Sep 17 00:00:00 2001
From: Tom Fifield 
Date: Tue, 30 Sep 2025 08:20:39 +1000
Subject: [PATCH 372/691] Introduce non-linear TX_GAIN_LORA (#8107)

* Introduce non-linear TX_GAIN_LORA

Previously, our TX_GAIN_LORA setting was a single number, intended
to represent the signal gain going through a power amp (plus or minus
antenna, attenuator, and other parts of the RF chain).

It turns out the relationship between the input power (i.e. from an SX1262)
and total output power is often non-linear. While we fudged a 1dBm difference
here and there with existing chips, the Heltec v4 has a 5dBm difference in gain
depending on which end of the input power (and frequency) you are at.

To allow people to run their Heltec v4 at max power when legal, and future
proof our code, this patch introduced an optional array-based TX_GAIN_LORA.

Define NUM_PA_POINTS and set TX_GAIN_LORA to gain values for a given input
power in 1dBm increments, and all will work.

For linear systems, just continue to define TX_GAIN_LORA as a number.

Fixes https://github.com/meshtastic/firmware/issues/8070

* Remove temporary power limit on heltec v4

* Add function RadioLibInterface::checkOutputPower

* Ensure SX126x reaches minimum supported power.

* Keep it simple, instead.
---
 src/configuration.h                       |  6 ++++++
 src/mesh/RadioInterface.cpp               | 14 ++++++++++++++
 src/mesh/SX126xInterface.cpp              |  7 +++++--
 variants/esp32s3/heltec_v4/platformio.ini |  1 -
 4 files changed, 25 insertions(+), 3 deletions(-)

diff --git a/src/configuration.h b/src/configuration.h
index 1b386ec17..91181890b 100644
--- a/src/configuration.h
+++ b/src/configuration.h
@@ -117,6 +117,12 @@ along with this program.  If not, see .
 #define SX126X_MAX_POWER 22
 #endif
 
+#ifdef HELTEC_V4
+// Power Amps are often non-linear, so we can use an array of values for the power curve
+#define NUM_PA_POINTS 22
+#define TX_GAIN_LORA 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 10, 10, 9, 9, 8, 7
+#endif
+
 // Default system gain to 0 if not defined
 #ifndef TX_GAIN_LORA
 #define TX_GAIN_LORA 0
diff --git a/src/mesh/RadioInterface.cpp b/src/mesh/RadioInterface.cpp
index 31c68c302..b891ec89c 100644
--- a/src/mesh/RadioInterface.cpp
+++ b/src/mesh/RadioInterface.cpp
@@ -674,11 +674,25 @@ void RadioInterface::limitPower(int8_t loraMaxPower)
         power = maxPower;
     }
 
+#ifndef NUM_PA_POINTS
     if (TX_GAIN_LORA > 0) {
         LOG_INFO("Requested Tx power: %d dBm; Device LoRa Tx gain: %d dB", power, TX_GAIN_LORA);
         power -= TX_GAIN_LORA;
     }
+#else
+    // we have an array of PA gain values.  Find the highest power setting that works.
+    const uint16_t tx_gain[NUM_PA_POINTS] = {TX_GAIN_LORA};
+    for (int radio_dbm = 0; radio_dbm < NUM_PA_POINTS; radio_dbm++) {
+        if (((radio_dbm + tx_gain[radio_dbm]) > power) ||
+            ((radio_dbm == (NUM_PA_POINTS - 1)) && ((radio_dbm + tx_gain[radio_dbm]) <= power))) {
+            // we've exceeded the power limit, or hit the max we can do
+            LOG_INFO("Requested Tx power: %d dBm; Device LoRa Tx gain: %d dB", power, tx_gain[radio_dbm]);
+            power -= tx_gain[radio_dbm];
+            break;
+        }
+    }
 
+#endif
     if (power > loraMaxPower) // Clamp power to maximum defined level
         power = loraMaxPower;
 
diff --git a/src/mesh/SX126xInterface.cpp b/src/mesh/SX126xInterface.cpp
index 3fc2562b3..785338483 100644
--- a/src/mesh/SX126xInterface.cpp
+++ b/src/mesh/SX126xInterface.cpp
@@ -80,6 +80,9 @@ template  bool SX126xInterface::init()
     RadioLibInterface::init();
 
     limitPower(SX126X_MAX_POWER);
+    // Make sure we reach the minimum power supported to turn the chip on (-9dBm)
+    if (power < -9)
+        power = -9;
 
     int res = lora.begin(getFreq(), bw, sf, cr, syncWord, power, preambleLength, tcxoVoltage, useRegulatorLDO);
     // \todo Display actual typename of the adapter, not just `SX126x`
@@ -118,8 +121,8 @@ template  bool SX126xInterface::init()
         LOG_DEBUG("Set DIO2 as %sRF switch, result: %d", dio2AsRfSwitch ? "" : "not ", res);
     }
 
-    // If a pin isn't defined, we set it to RADIOLIB_NC, it is safe to always do external RF switching with RADIOLIB_NC as it has
-    // no effect
+// If a pin isn't defined, we set it to RADIOLIB_NC, it is safe to always do external RF switching with RADIOLIB_NC as it has
+// 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", portduino_config.lora_rxen_pin.pin,
diff --git a/variants/esp32s3/heltec_v4/platformio.ini b/variants/esp32s3/heltec_v4/platformio.ini
index 1a448bc99..d0a250ad3 100644
--- a/variants/esp32s3/heltec_v4/platformio.ini
+++ b/variants/esp32s3/heltec_v4/platformio.ini
@@ -8,4 +8,3 @@ build_flags =
   -D HELTEC_V4
   -I variants/esp32s3/heltec_v4
   -D GPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely.
-  -D SX126X_MAX_POWER=11

From 8d323a1cf1e0570a31f1cb9499b8bb552b8bb72e Mon Sep 17 00:00:00 2001
From: Quency-D 
Date: Mon, 29 Sep 2025 17:49:31 +0800
Subject: [PATCH 373/691] add heltec tracker v2 board.

---
 boards/heltec_wireless_tracker_v2.json        | 37 +++++++++
 src/graphics/Screen.cpp                       |  2 +-
 src/mesh/SX126xInterface.cpp                  |  6 +-
 src/platform/esp32/architecture.h             |  2 +
 src/sleep.cpp                                 |  2 +-
 .../heltec_wireless_tracker_v2/pins_arduino.h | 71 +++++++++++++++++
 .../heltec_wireless_tracker_v2/platformio.ini | 15 ++++
 .../heltec_wireless_tracker_v2/variant.h      | 78 +++++++++++++++++++
 8 files changed, 208 insertions(+), 5 deletions(-)
 create mode 100644 boards/heltec_wireless_tracker_v2.json
 create mode 100644 variants/esp32s3/heltec_wireless_tracker_v2/pins_arduino.h
 create mode 100644 variants/esp32s3/heltec_wireless_tracker_v2/platformio.ini
 create mode 100644 variants/esp32s3/heltec_wireless_tracker_v2/variant.h

diff --git a/boards/heltec_wireless_tracker_v2.json b/boards/heltec_wireless_tracker_v2.json
new file mode 100644
index 000000000..502954e69
--- /dev/null
+++ b/boards/heltec_wireless_tracker_v2.json
@@ -0,0 +1,37 @@
+{
+  "build": {
+    "arduino": {
+      "ldscript": "esp32s3_out.ld",
+      "partitions": "default_8MB.csv"
+    },
+    "core": "esp32",
+    "extra_flags": [
+      "-DARDUINO_USB_CDC_ON_BOOT=1",
+      "-DARDUINO_USB_MODE=0",
+      "-DARDUINO_RUNNING_CORE=1",
+      "-DARDUINO_EVENT_RUNNING_CORE=1"
+    ],
+    "f_cpu": "240000000L",
+    "f_flash": "80000000L",
+    "flash_mode": "qio",
+    "hwids": [["0x303A", "0x1001"]],
+    "mcu": "esp32s3",
+    "variant": "heltec_wireless_tracker_v2"
+  },
+  "connectivity": ["wifi", "bluetooth", "lora"],
+  "debug": {
+    "openocd_target": "esp32s3.cfg"
+  },
+  "frameworks": ["arduino", "espidf"],
+  "name": "Heltec Wireless Tracker V2",
+  "upload": {
+    "flash_size": "8MB",
+    "maximum_ram_size": 327680,
+    "maximum_size": 8388608,
+    "wait_for_upload_port": true,
+    "require_upload_port": true,
+    "speed": 921600
+  },
+  "url": "https://heltec.org",
+  "vendor": "Heltec"
+}
diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp
index 1a9cf484b..5b585e485 100644
--- a/src/graphics/Screen.cpp
+++ b/src/graphics/Screen.cpp
@@ -453,7 +453,7 @@ void Screen::handleSetOn(bool on, FrameCallback einkScreensaver)
 #endif
 
             dispdev->displayOn();
-#ifdef HELTEC_TRACKER_V1_X
+#if defined(HELTEC_TRACKER_V1_X) || defined(HELTEC_WIRELESS_TRACKER_V2)
             ui->init();
 #endif
 #ifdef USE_ST7789
diff --git a/src/mesh/SX126xInterface.cpp b/src/mesh/SX126xInterface.cpp
index 785338483..a858c5e3b 100644
--- a/src/mesh/SX126xInterface.cpp
+++ b/src/mesh/SX126xInterface.cpp
@@ -52,7 +52,7 @@ template  bool SX126xInterface::init()
     pinMode(SX126X_POWER_EN, OUTPUT);
 #endif
 
-#ifdef HELTEC_V4
+#if defined(HELTEC_V4)||defined(HELTEC_WIRELESS_TRACKER_V2)
     pinMode(LORA_PA_POWER, OUTPUT);
     digitalWrite(LORA_PA_POWER, HIGH);
 
@@ -352,7 +352,7 @@ template  bool SX126xInterface::sleep()
     digitalWrite(SX126X_POWER_EN, LOW);
 #endif
 
-#ifdef HELTEC_V4
+#if defined(HELTEC_V4)||defined(HELTEC_WIRELESS_TRACKER_V2)
     /*
      * Do not switch the power on and off frequently.
      * After turning off LORA_PA_EN, the power consumption has dropped to the uA level.
@@ -367,7 +367,7 @@ template  bool SX126xInterface::sleep()
 /** Some boards require GPIO control of tx vs rx paths */
 template  void SX126xInterface::setTransmitEnable(bool txon)
 {
-#ifdef HELTEC_V4
+#if defined(HELTEC_V4)||defined(HELTEC_WIRELESS_TRACKER_V2)
     digitalWrite(LORA_PA_POWER, HIGH);
     digitalWrite(LORA_PA_EN, HIGH);
     digitalWrite(LORA_PA_TX_EN, txon ? 1 : 0);
diff --git a/src/platform/esp32/architecture.h b/src/platform/esp32/architecture.h
index 6b658c2a3..53b23124d 100644
--- a/src/platform/esp32/architecture.h
+++ b/src/platform/esp32/architecture.h
@@ -201,6 +201,8 @@
 #define HW_VENDOR meshtastic_HardwareModel_HELTEC_V4
 #elif defined(M5STACK_UNITC6L)
 #define HW_VENDOR meshtastic_HardwareModel_M5STACK_C6L
+#elif defined(HELTEC_WIRELESS_TRACKER_V2)
+#define HW_VENDOR meshtastic_HardwareModel_HELTEC_WIRELESS_TRACKER_V2
 #endif
 
 // -----------------------------------------------------------------------------
diff --git a/src/sleep.cpp b/src/sleep.cpp
index 0f8931292..cfc8fbcd6 100644
--- a/src/sleep.cpp
+++ b/src/sleep.cpp
@@ -554,7 +554,7 @@ void enableLoraInterrupt()
     gpio_pullup_en((gpio_num_t)LORA_CS);
 #endif
 
-#ifdef HELTEC_V4
+#if defined(HELTEC_V4)||defined(HELTEC_WIRELESS_TRACKER_V2)
     gpio_pullup_en((gpio_num_t)LORA_PA_POWER);
     gpio_pullup_en((gpio_num_t)LORA_PA_EN);
     gpio_pulldown_en((gpio_num_t)LORA_PA_TX_EN);
diff --git a/variants/esp32s3/heltec_wireless_tracker_v2/pins_arduino.h b/variants/esp32s3/heltec_wireless_tracker_v2/pins_arduino.h
new file mode 100644
index 000000000..61c319109
--- /dev/null
+++ b/variants/esp32s3/heltec_wireless_tracker_v2/pins_arduino.h
@@ -0,0 +1,71 @@
+#ifndef Pins_Arduino_h
+#define Pins_Arduino_h
+
+#include "soc/soc_caps.h"
+#include 
+
+#define DISPLAY_HEIGHT 80
+#define DISPLAY_WIDTH 160
+
+#define USB_VID 0x303a
+#define USB_PID 0x1001
+
+static const uint8_t LED_BUILTIN = 18;
+#define BUILTIN_LED LED_BUILTIN // backward compatibility
+#define LED_BUILTIN LED_BUILTIN
+
+static const uint8_t TX = 43;
+static const uint8_t RX = 44;
+
+static const uint8_t SDA = 5;
+static const uint8_t SCL = 6;
+
+static const uint8_t SS = 8;
+static const uint8_t MOSI = 10;
+static const uint8_t MISO = 11;
+static const uint8_t SCK = 9;
+
+static const uint8_t A0 = 1;
+static const uint8_t A1 = 2;
+static const uint8_t A2 = 3;
+static const uint8_t A3 = 4;
+static const uint8_t A4 = 5;
+static const uint8_t A5 = 6;
+static const uint8_t A6 = 7;
+static const uint8_t A7 = 8;
+static const uint8_t A8 = 9;
+static const uint8_t A9 = 10;
+static const uint8_t A10 = 11;
+static const uint8_t A11 = 12;
+static const uint8_t A12 = 13;
+static const uint8_t A13 = 14;
+static const uint8_t A14 = 15;
+static const uint8_t A15 = 16;
+static const uint8_t A16 = 17;
+static const uint8_t A17 = 18;
+static const uint8_t A18 = 19;
+static const uint8_t A19 = 20;
+
+static const uint8_t T1 = 1;
+static const uint8_t T2 = 2;
+static const uint8_t T3 = 3;
+static const uint8_t T4 = 4;
+static const uint8_t T5 = 5;
+static const uint8_t T6 = 6;
+static const uint8_t T7 = 7;
+static const uint8_t T8 = 8;
+static const uint8_t T9 = 9;
+static const uint8_t T10 = 10;
+static const uint8_t T11 = 11;
+static const uint8_t T12 = 12;
+static const uint8_t T13 = 13;
+static const uint8_t T14 = 14;
+
+static const uint8_t Vext = 3;
+static const uint8_t LED = 18;
+
+static const uint8_t RST_LoRa = 12;
+static const uint8_t BUSY_LoRa = 13;
+static const uint8_t DIO0 = 14;
+
+#endif /* Pins_Arduino_h */
diff --git a/variants/esp32s3/heltec_wireless_tracker_v2/platformio.ini b/variants/esp32s3/heltec_wireless_tracker_v2/platformio.ini
new file mode 100644
index 000000000..41952bf78
--- /dev/null
+++ b/variants/esp32s3/heltec_wireless_tracker_v2/platformio.ini
@@ -0,0 +1,15 @@
+[env:heltec-wireless-tracker-v2]
+extends = esp32s3_base
+board = heltec_wireless_tracker_v2
+board_build.partitions = default_8MB.csv
+upload_protocol = esptool
+
+build_flags = 
+  ${esp32s3_base.build_flags}
+  -I variants/esp32s3/heltec_wireless_tracker_v2
+  -D HELTEC_WIRELESS_TRACKER_V2
+  -D GPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely.
+  -D SX126X_MAX_POWER=11 ;The latter limit is the largest, and will be updated after the V4 update.
+lib_deps =
+  ${esp32s3_base.lib_deps}
+  lovyan03/LovyanGFX@^1.2.0
diff --git a/variants/esp32s3/heltec_wireless_tracker_v2/variant.h b/variants/esp32s3/heltec_wireless_tracker_v2/variant.h
new file mode 100644
index 000000000..39769f1e0
--- /dev/null
+++ b/variants/esp32s3/heltec_wireless_tracker_v2/variant.h
@@ -0,0 +1,78 @@
+#define LED_PIN 18
+
+#define _VARIANT_HELTEC_WIRELESS_TRACKER
+
+// I2C
+#define I2C_SDA SDA
+#define I2C_SCL SCL
+
+// ST7735S TFT LCD
+#define ST7735S 1 // there are different (sub-)versions of ST7735
+#define ST7735_CS 38
+#define ST7735_RS 40  // DC
+#define ST7735_SDA 42 // MOSI
+#define ST7735_SCK 41
+#define ST7735_RESET 39
+#define ST7735_MISO -1
+#define ST7735_BUSY -1
+#define TFT_BL 21 
+#define ST7735_SPI_HOST SPI3_HOST
+#define SPI_FREQUENCY 40000000
+#define SPI_READ_FREQUENCY 16000000
+#define SCREEN_ROTATE
+#define TFT_HEIGHT DISPLAY_WIDTH
+#define TFT_WIDTH DISPLAY_HEIGHT
+#define TFT_OFFSET_X 24
+#define TFT_OFFSET_Y 0
+#define TFT_INVERT false
+#define SCREEN_TRANSITION_FRAMERATE 3 // fps
+#define DISPLAY_FORCE_SMALL_FONTS
+
+
+#define VEXT_ENABLE 3 // active HIGH - powers the GPS, GPS LNA and OLED
+#define VEXT_ON_VALUE HIGH
+#define BUTTON_PIN 0
+
+#define BATTERY_PIN 1 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage
+#define ADC_CHANNEL ADC1_GPIO1_CHANNEL
+#define ADC_ATTENUATION ADC_ATTEN_DB_2_5 // lower dB for high resistance voltage divider
+#define ADC_MULTIPLIER 4.9 * 1.045
+#define ADC_CTRL 2     // active HIGH, powers the voltage divider. 
+#define ADC_USE_PULLUP // Use internal pullup/pulldown instead of actively driving the output
+
+#undef GPS_RX_PIN
+#undef GPS_TX_PIN
+#define GPS_RX_PIN 33
+#define GPS_TX_PIN 34
+#define PIN_GPS_RESET 35
+#define PIN_GPS_PPS 36
+// #define PIN_GPS_EN 3    // Uncomment to power off the GPS with triple-click on Tracker v2, though we'll also lose the
+// display.
+
+#define GPS_RESET_MODE LOW
+#define GPS_UC6580
+#define GPS_BAUDRATE 115200
+
+#define USE_SX1262
+#define LORA_DIO0 -1 // a No connect on the SX1262 module
+#define LORA_RESET 12
+#define LORA_DIO1 14 // SX1262 IRQ
+#define LORA_DIO2 13 // SX1262 BUSY
+#define LORA_DIO3    // Not connected on PCB, but internally on the TTGO SX1262, if DIO3 is high the TXCO is enabled
+
+#define LORA_SCK 9
+#define LORA_MISO 11
+#define LORA_MOSI 10
+#define LORA_CS 8
+
+#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
+
+#define LORA_PA_POWER 7 // power en
+#define LORA_PA_EN    4
+#define LORA_PA_TX_EN 46 // enable tx
\ No newline at end of file

From 0f6131d2c86beae9e2fe91884b6bcbcd86834938 Mon Sep 17 00:00:00 2001
From: Tom Fifield 
Date: Tue, 30 Sep 2025 08:30:18 +1000
Subject: [PATCH 374/691] Use common power amp definition for Heltec v4 and
 Heltec Tracker v2

---
 src/configuration.h                                   | 2 +-
 src/mesh/SX126xInterface.cpp                          | 6 +++---
 src/sleep.cpp                                         | 2 +-
 variants/esp32s3/heltec_v4/variant.h                  | 1 +
 variants/esp32s3/heltec_wireless_tracker_v2/variant.h | 8 ++++----
 5 files changed, 10 insertions(+), 9 deletions(-)

diff --git a/src/configuration.h b/src/configuration.h
index 91181890b..e67f0b8e5 100644
--- a/src/configuration.h
+++ b/src/configuration.h
@@ -117,7 +117,7 @@ along with this program.  If not, see .
 #define SX126X_MAX_POWER 22
 #endif
 
-#ifdef HELTEC_V4
+#ifdef USE_GC1109_PA
 // Power Amps are often non-linear, so we can use an array of values for the power curve
 #define NUM_PA_POINTS 22
 #define TX_GAIN_LORA 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 10, 10, 9, 9, 8, 7
diff --git a/src/mesh/SX126xInterface.cpp b/src/mesh/SX126xInterface.cpp
index a858c5e3b..c60ef3a80 100644
--- a/src/mesh/SX126xInterface.cpp
+++ b/src/mesh/SX126xInterface.cpp
@@ -52,7 +52,7 @@ template  bool SX126xInterface::init()
     pinMode(SX126X_POWER_EN, OUTPUT);
 #endif
 
-#if defined(HELTEC_V4)||defined(HELTEC_WIRELESS_TRACKER_V2)
+#if defined(USE_GC1109_PA)
     pinMode(LORA_PA_POWER, OUTPUT);
     digitalWrite(LORA_PA_POWER, HIGH);
 
@@ -352,7 +352,7 @@ template  bool SX126xInterface::sleep()
     digitalWrite(SX126X_POWER_EN, LOW);
 #endif
 
-#if defined(HELTEC_V4)||defined(HELTEC_WIRELESS_TRACKER_V2)
+#if defined(USE_GC1109_PA)
     /*
      * Do not switch the power on and off frequently.
      * After turning off LORA_PA_EN, the power consumption has dropped to the uA level.
@@ -367,7 +367,7 @@ template  bool SX126xInterface::sleep()
 /** Some boards require GPIO control of tx vs rx paths */
 template  void SX126xInterface::setTransmitEnable(bool txon)
 {
-#if defined(HELTEC_V4)||defined(HELTEC_WIRELESS_TRACKER_V2)
+#if defined(USE_GC1109_PA)
     digitalWrite(LORA_PA_POWER, HIGH);
     digitalWrite(LORA_PA_EN, HIGH);
     digitalWrite(LORA_PA_TX_EN, txon ? 1 : 0);
diff --git a/src/sleep.cpp b/src/sleep.cpp
index cfc8fbcd6..68867e0d0 100644
--- a/src/sleep.cpp
+++ b/src/sleep.cpp
@@ -554,7 +554,7 @@ void enableLoraInterrupt()
     gpio_pullup_en((gpio_num_t)LORA_CS);
 #endif
 
-#if defined(HELTEC_V4)||defined(HELTEC_WIRELESS_TRACKER_V2)
+#if defined(USE_GC1109_PA)
     gpio_pullup_en((gpio_num_t)LORA_PA_POWER);
     gpio_pullup_en((gpio_num_t)LORA_PA_EN);
     gpio_pulldown_en((gpio_num_t)LORA_PA_TX_EN);
diff --git a/variants/esp32s3/heltec_v4/variant.h b/variants/esp32s3/heltec_v4/variant.h
index 2b6b7af3d..1c9516e39 100644
--- a/variants/esp32s3/heltec_v4/variant.h
+++ b/variants/esp32s3/heltec_v4/variant.h
@@ -37,6 +37,7 @@
 #define SX126X_DIO2_AS_RF_SWITCH
 #define SX126X_DIO3_TCXO_VOLTAGE 1.8
 
+#define USE_GC1109_PA   // We have a GC1109 power amplifier+attenuator
 #define LORA_PA_POWER 7 // power en
 #define LORA_PA_EN 2
 #define LORA_PA_TX_EN 46 // enable tx
diff --git a/variants/esp32s3/heltec_wireless_tracker_v2/variant.h b/variants/esp32s3/heltec_wireless_tracker_v2/variant.h
index 39769f1e0..aec31bba2 100644
--- a/variants/esp32s3/heltec_wireless_tracker_v2/variant.h
+++ b/variants/esp32s3/heltec_wireless_tracker_v2/variant.h
@@ -15,7 +15,7 @@
 #define ST7735_RESET 39
 #define ST7735_MISO -1
 #define ST7735_BUSY -1
-#define TFT_BL 21 
+#define TFT_BL 21
 #define ST7735_SPI_HOST SPI3_HOST
 #define SPI_FREQUENCY 40000000
 #define SPI_READ_FREQUENCY 16000000
@@ -28,7 +28,6 @@
 #define SCREEN_TRANSITION_FRAMERATE 3 // fps
 #define DISPLAY_FORCE_SMALL_FONTS
 
-
 #define VEXT_ENABLE 3 // active HIGH - powers the GPS, GPS LNA and OLED
 #define VEXT_ON_VALUE HIGH
 #define BUTTON_PIN 0
@@ -37,7 +36,7 @@
 #define ADC_CHANNEL ADC1_GPIO1_CHANNEL
 #define ADC_ATTENUATION ADC_ATTEN_DB_2_5 // lower dB for high resistance voltage divider
 #define ADC_MULTIPLIER 4.9 * 1.045
-#define ADC_CTRL 2     // active HIGH, powers the voltage divider. 
+#define ADC_CTRL 2     // active HIGH, powers the voltage divider.
 #define ADC_USE_PULLUP // Use internal pullup/pulldown instead of actively driving the output
 
 #undef GPS_RX_PIN
@@ -73,6 +72,7 @@
 #define SX126X_DIO2_AS_RF_SWITCH
 #define SX126X_DIO3_TCXO_VOLTAGE 1.8
 
+#define USE_GC1109_PA   // We have a GC1109 power amplifier+attenuator
 #define LORA_PA_POWER 7 // power en
-#define LORA_PA_EN    4
+#define LORA_PA_EN 4
 #define LORA_PA_TX_EN 46 // enable tx
\ No newline at end of file

From 02efef3aaf7895353172bc541cc044ebc01b4e14 Mon Sep 17 00:00:00 2001
From: ford-jones 
Date: Tue, 30 Sep 2025 16:36:52 +1300
Subject: [PATCH 375/691] Set appropriate mqtt root upon lora region change

---
 src/modules/AdminModule.cpp | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp
index 79ea7bc0c..1cea85d7a 100644
--- a/src/modules/AdminModule.cpp
+++ b/src/modules/AdminModule.cpp
@@ -770,6 +770,15 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c)
                 changes = SEGMENT_CONFIG | SEGMENT_MODULECONFIG;
             }
         }
+        if (config.lora.region != myRegion->code) {
+            //  Region has changed so check whether there is a regulatory one we should be using instead.
+            //  Additionally as a side-effect, assume a new value under myRegion
+            initRegion();
+
+            //  subscribe to appropriate MQTT root topic for this region
+            sprintf(moduleConfig.mqtt.root, "%s/%s", default_mqtt_root, myRegion->name);
+            changes = SEGMENT_CONFIG | SEGMENT_MODULECONFIG;
+        }
         break;
     case meshtastic_Config_bluetooth_tag:
         LOG_INFO("Set config: Bluetooth");

From ee8fa9f328665233827d2cd220cd29c6f91c8cfd Mon Sep 17 00:00:00 2001
From: ford-jones 
Date: Tue, 30 Sep 2025 18:04:42 +1300
Subject: [PATCH 376/691] Use user preferences root topic if present

---
 src/modules/AdminModule.cpp | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp
index 1cea85d7a..8421e3bea 100644
--- a/src/modules/AdminModule.cpp
+++ b/src/modules/AdminModule.cpp
@@ -776,7 +776,11 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c)
             initRegion();
 
             //  subscribe to appropriate MQTT root topic for this region
+#ifdef USERPREFS_MQTT_ROOT_TOPIC
+            sprintf(moduleConfig.mqtt.root, "%s/%s", USERPREFS_MQTT_ROOT_TOPIC, myRegion->name);
+#else
             sprintf(moduleConfig.mqtt.root, "%s/%s", default_mqtt_root, myRegion->name);
+#endif
             changes = SEGMENT_CONFIG | SEGMENT_MODULECONFIG;
         }
         break;

From 500e7920ae9c2ae47e4fe4b22be2e3440bdc7f85 Mon Sep 17 00:00:00 2001
From: Quency-D <55523105+Quency-D@users.noreply.github.com>
Date: Tue, 30 Sep 2025 14:06:46 +0800
Subject: [PATCH 377/691] delete SX126X_MAX_POWER=11

---
 variants/esp32s3/heltec_wireless_tracker_v2/platformio.ini | 1 -
 1 file changed, 1 deletion(-)

diff --git a/variants/esp32s3/heltec_wireless_tracker_v2/platformio.ini b/variants/esp32s3/heltec_wireless_tracker_v2/platformio.ini
index 41952bf78..4872561db 100644
--- a/variants/esp32s3/heltec_wireless_tracker_v2/platformio.ini
+++ b/variants/esp32s3/heltec_wireless_tracker_v2/platformio.ini
@@ -9,7 +9,6 @@ build_flags =
   -I variants/esp32s3/heltec_wireless_tracker_v2
   -D HELTEC_WIRELESS_TRACKER_V2
   -D GPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely.
-  -D SX126X_MAX_POWER=11 ;The latter limit is the largest, and will be updated after the V4 update.
 lib_deps =
   ${esp32s3_base.lib_deps}
   lovyan03/LovyanGFX@^1.2.0

From ee6857511a159f1127e6344db17e35a7e7cfa7bf Mon Sep 17 00:00:00 2001
From: Ben Meadors 
Date: Tue, 30 Sep 2025 08:05:00 -0500
Subject: [PATCH 378/691] Fix Heltec V3 missed button presses (#8167)

---
 src/input/ButtonThread.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/input/ButtonThread.cpp b/src/input/ButtonThread.cpp
index 1d26e0758..9f53b06f4 100644
--- a/src/input/ButtonThread.cpp
+++ b/src/input/ButtonThread.cpp
@@ -279,7 +279,7 @@ int32_t ButtonThread::runOnce()
     if (!userButton.isIdle() || waitingForLongPress) {
         return 50;
     }
-    return INT32_MAX;
+    return 100; // FIXME: Why can't we rely on interrupts and use INT32_MAX here?
 }
 
 /*

From b08e4efb78b7fd98063101bd1931754788654cbb Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
 <41898282+github-actions[bot]@users.noreply.github.com>
Date: Tue, 30 Sep 2025 13:34:40 -0500
Subject: [PATCH 379/691] Update protobufs (#8172)

Co-authored-by: jp-bennett <5630967+jp-bennett@users.noreply.github.com>
---
 protobufs                               | 2 +-
 src/mesh/generated/meshtastic/mesh.pb.h | 4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/protobufs b/protobufs
index 082bb7cfe..5fa4c44d9 160000
--- a/protobufs
+++ b/protobufs
@@ -1 +1 @@
-Subproject commit 082bb7cfeb2cba9d41be139cd324c4b43a14b3f9
+Subproject commit 5fa4c44d914aba67971ba15ace22e4b21af64ae5
diff --git a/src/mesh/generated/meshtastic/mesh.pb.h b/src/mesh/generated/meshtastic/mesh.pb.h
index 6292ce070..601d52007 100644
--- a/src/mesh/generated/meshtastic/mesh.pb.h
+++ b/src/mesh/generated/meshtastic/mesh.pb.h
@@ -253,8 +253,8 @@ typedef enum _meshtastic_HardwareModel {
     meshtastic_HardwareModel_SEEED_WIO_TRACKER_L1 = 99,
     /* Seeed Tracker L1 EINK driver */
     meshtastic_HardwareModel_SEEED_WIO_TRACKER_L1_EINK = 100,
-    /* Reserved ID for future and past use */
-    meshtastic_HardwareModel_QWANTZ_TINY_ARMS = 101,
+    /* Muzi Works R1 Neo */
+    meshtastic_HardwareModel_MUZI_R1_NEO = 101,
     /* Lilygo T-Deck Pro */
     meshtastic_HardwareModel_T_DECK_PRO = 102,
     /* Lilygo TLora Pager */

From 69c61f82479eb35186ab77a567ccbe824cf1e52a Mon Sep 17 00:00:00 2001
From: ford-jones 
Date: Wed, 1 Oct 2025 11:14:27 +1300
Subject: [PATCH 380/691] Assume previous root on topic change

---
 src/modules/AdminModule.cpp | 17 +++++++++++------
 1 file changed, 11 insertions(+), 6 deletions(-)

diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp
index 8421e3bea..db31ea852 100644
--- a/src/modules/AdminModule.cpp
+++ b/src/modules/AdminModule.cpp
@@ -775,12 +775,17 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c)
             //  Additionally as a side-effect, assume a new value under myRegion
             initRegion();
 
-            //  subscribe to appropriate MQTT root topic for this region
-#ifdef USERPREFS_MQTT_ROOT_TOPIC
-            sprintf(moduleConfig.mqtt.root, "%s/%s", USERPREFS_MQTT_ROOT_TOPIC, myRegion->name);
-#else
-            sprintf(moduleConfig.mqtt.root, "%s/%s", default_mqtt_root, myRegion->name);
-#endif
+            std::string current = moduleConfig.mqtt.root;
+            size_t location = current.find_first_of('/');
+
+            char root[location + 1];
+            memset(root, 0, location);
+            memcpy(root, moduleConfig.mqtt.root, location);
+            root[location] = '\0';
+
+            //  subscribe to the appropriate MQTT root topic for this region
+            sprintf(moduleConfig.mqtt.root, "%s/%s", root, myRegion->name);
+
             changes = SEGMENT_CONFIG | SEGMENT_MODULECONFIG;
         }
         break;

From 34a595b88e7d8a1703d3de48b2adfc37c663ad1e Mon Sep 17 00:00:00 2001
From: ford-jones 
Date: Wed, 1 Oct 2025 16:14:21 +1300
Subject: [PATCH 381/691] update mqtt root when region is changed via OLED menu
 handler

---
 src/graphics/draw/MenuHandler.cpp | 12 +++++++++++-
 src/modules/AdminModule.cpp       | 15 +++++----------
 2 files changed, 16 insertions(+), 11 deletions(-)

diff --git a/src/graphics/draw/MenuHandler.cpp b/src/graphics/draw/MenuHandler.cpp
index 43b3fb8ab..f131bc298 100644
--- a/src/graphics/draw/MenuHandler.cpp
+++ b/src/graphics/draw/MenuHandler.cpp
@@ -116,6 +116,8 @@ void menuHandler::LoraRegionPicker(uint32_t duration)
     bannerOptions.bannerCallback = [](int selected) -> void {
         if (selected != 0 && config.lora.region != _meshtastic_Config_LoRaConfig_RegionCode(selected)) {
             config.lora.region = _meshtastic_Config_LoRaConfig_RegionCode(selected);
+            auto changes = SEGMENT_CONFIG;
+
             // This is needed as we wait til picking the LoRa region to generate keys for the first time.
             if (!owner.is_licensed) {
                 bool keygenSuccess = false;
@@ -124,6 +126,7 @@ void menuHandler::LoraRegionPicker(uint32_t duration)
                     if (crypto->regeneratePublicKey(config.security.public_key.bytes, config.security.private_key.bytes)) {
                         keygenSuccess = true;
                     }
+
                 } else {
                     LOG_INFO("Generate new PKI keys");
                     crypto->generateKeyPair(config.security.public_key.bytes, config.security.private_key.bytes);
@@ -141,7 +144,14 @@ void menuHandler::LoraRegionPicker(uint32_t duration)
             if (myRegion->dutyCycle < 100) {
                 config.lora.ignore_mqtt = true; // Ignore MQTT by default if region has a duty cycle limit
             }
-            service->reloadConfig(SEGMENT_CONFIG);
+
+            if (strncmp(moduleConfig.mqtt.root, default_mqtt_root, 3) == 0) {
+                //  Default broker is in use, so subscribe to the appropriate MQTT root topic for this region
+                sprintf(moduleConfig.mqtt.root, "%s/%s", default_mqtt_root, myRegion->name);
+                changes |= SEGMENT_MODULECONFIG;
+            };
+
+            service->reloadConfig(changes);
             rebootAtMsec = (millis() + DEFAULT_REBOOT_SECONDS * 1000);
         }
     };
diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp
index db31ea852..367a0f617 100644
--- a/src/modules/AdminModule.cpp
+++ b/src/modules/AdminModule.cpp
@@ -765,6 +765,7 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c)
             if (myRegion->dutyCycle < 100) {
                 config.lora.ignore_mqtt = true; // Ignore MQTT by default if region has a duty cycle limit
             }
+            //  Compare the entire string, we are sure of the length as a topic has never been set
             if (strcmp(moduleConfig.mqtt.root, default_mqtt_root) == 0) {
                 sprintf(moduleConfig.mqtt.root, "%s/%s", default_mqtt_root, myRegion->name);
                 changes = SEGMENT_CONFIG | SEGMENT_MODULECONFIG;
@@ -775,16 +776,10 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c)
             //  Additionally as a side-effect, assume a new value under myRegion
             initRegion();
 
-            std::string current = moduleConfig.mqtt.root;
-            size_t location = current.find_first_of('/');
-
-            char root[location + 1];
-            memset(root, 0, location);
-            memcpy(root, moduleConfig.mqtt.root, location);
-            root[location] = '\0';
-
-            //  subscribe to the appropriate MQTT root topic for this region
-            sprintf(moduleConfig.mqtt.root, "%s/%s", root, myRegion->name);
+            if (strncmp(moduleConfig.mqtt.root, default_mqtt_root, 3) == 0) {
+                //  Default broker is in use, so subscribe to the appropriate MQTT root topic for this region
+                sprintf(moduleConfig.mqtt.root, "%s/%s", default_mqtt_root, myRegion->name);
+            }
 
             changes = SEGMENT_CONFIG | SEGMENT_MODULECONFIG;
         }

From dae9b1c024bcdd205cc154b644f09cf05bae7c5d Mon Sep 17 00:00:00 2001
From: ford-jones 
Date: Wed, 1 Oct 2025 17:58:14 +1300
Subject: [PATCH 382/691] Regen protos

---
 protobufs                               | 2 +-
 src/mesh/generated/meshtastic/mesh.pb.h | 8 ++++++--
 2 files changed, 7 insertions(+), 3 deletions(-)

diff --git a/protobufs b/protobufs
index 082bb7cfe..394268b02 160000
--- a/protobufs
+++ b/protobufs
@@ -1 +1 @@
-Subproject commit 082bb7cfeb2cba9d41be139cd324c4b43a14b3f9
+Subproject commit 394268b02ebbc7797de31b09fe72fe2a7bdbbcab
diff --git a/src/mesh/generated/meshtastic/mesh.pb.h b/src/mesh/generated/meshtastic/mesh.pb.h
index 6292ce070..d8d2f2e8a 100644
--- a/src/mesh/generated/meshtastic/mesh.pb.h
+++ b/src/mesh/generated/meshtastic/mesh.pb.h
@@ -253,8 +253,8 @@ typedef enum _meshtastic_HardwareModel {
     meshtastic_HardwareModel_SEEED_WIO_TRACKER_L1 = 99,
     /* Seeed Tracker L1 EINK driver */
     meshtastic_HardwareModel_SEEED_WIO_TRACKER_L1_EINK = 100,
-    /* Reserved ID for future and past use */
-    meshtastic_HardwareModel_QWANTZ_TINY_ARMS = 101,
+    /* Muzi Works R1 Neo */
+    meshtastic_HardwareModel_MUZI_R1_NEO = 101,
     /* Lilygo T-Deck Pro */
     meshtastic_HardwareModel_T_DECK_PRO = 102,
     /* Lilygo TLora Pager */
@@ -278,6 +278,10 @@ typedef enum _meshtastic_HardwareModel {
     meshtastic_HardwareModel_M5STACK_C6L = 111,
     /* M5Stack Cardputer Adv */
     meshtastic_HardwareModel_M5STACK_CARDPUTER_ADV = 112,
+    /* ESP32S3 main controller with GPS and TFT screen. */
+    meshtastic_HardwareModel_HELTEC_WIRELESS_TRACKER_V2 = 113,
+    /* LilyGo T-Watch Ultra */
+    meshtastic_HardwareModel_T_WATCH_ULTRA = 114,
     /* ------------------------------------------------------------------------------------------------------------------------------------------
  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 4fd568f38429fae506a52daab9009bf6438ab254 Mon Sep 17 00:00:00 2001
From: Jonathan Bennett 
Date: Tue, 30 Sep 2025 13:00:01 -0500
Subject: [PATCH 383/691] Initial support for T-Rex

---
 boards/t-rex.json                     |  52 +++++++++
 src/detect/ScanI2C.cpp                |   4 +-
 src/detect/ScanI2C.h                  |   1 +
 src/detect/ScanI2CTwoWire.cpp         |   3 +
 src/gps/RTC.cpp                       |  30 ++++++
 src/gps/RTC.h                         |   4 +
 src/main.cpp                          |   6 ++
 src/platform/nrf52/NRF52Bluetooth.cpp |   3 +-
 src/power.h                           |   2 +
 variants/t-rex/platformio.ini         |  17 +++
 variants/t-rex/variant.cpp            |  45 ++++++++
 variants/t-rex/variant.h              | 148 ++++++++++++++++++++++++++
 12 files changed, 312 insertions(+), 3 deletions(-)
 create mode 100644 boards/t-rex.json
 create mode 100644 variants/t-rex/platformio.ini
 create mode 100644 variants/t-rex/variant.cpp
 create mode 100644 variants/t-rex/variant.h

diff --git a/boards/t-rex.json b/boards/t-rex.json
new file mode 100644
index 000000000..d827c55dd
--- /dev/null
+++ b/boards/t-rex.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": "Muzi T-REX",
+    "mcu": "nrf52840",
+    "variant": "t-rex",
+    "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": "WisCore RAK4631 Board",
+  "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": "https://muzi.works/",
+  "vendor": "Muzi Works"
+}
diff --git a/src/detect/ScanI2C.cpp b/src/detect/ScanI2C.cpp
index 170bef3a6..8ac503b83 100644
--- a/src/detect/ScanI2C.cpp
+++ b/src/detect/ScanI2C.cpp
@@ -25,8 +25,8 @@ ScanI2C::FoundDevice ScanI2C::firstScreen() const
 
 ScanI2C::FoundDevice ScanI2C::firstRTC() const
 {
-    ScanI2C::DeviceType types[] = {RTC_RV3028, RTC_PCF8563};
-    return firstOfOrNONE(2, types);
+    ScanI2C::DeviceType types[] = {RTC_RV3028, RTC_PCF8563, RTC_RX8130CE};
+    return firstOfOrNONE(3, types);
 }
 
 ScanI2C::FoundDevice ScanI2C::firstKeyboard() const
diff --git a/src/detect/ScanI2C.h b/src/detect/ScanI2C.h
index 470a416c0..2e602338c 100644
--- a/src/detect/ScanI2C.h
+++ b/src/detect/ScanI2C.h
@@ -14,6 +14,7 @@ class ScanI2C
         SCREEN_ST7567,
         RTC_RV3028,
         RTC_PCF8563,
+        RTC_RX8130CE,
         CARDKB,
         TDECKKB,
         BBQ10KB,
diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp
index 01a630b52..6df3f8be1 100644
--- a/src/detect/ScanI2CTwoWire.cpp
+++ b/src/detect/ScanI2CTwoWire.cpp
@@ -197,6 +197,9 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
 #ifdef PCF8563_RTC
                 SCAN_SIMPLE_CASE(PCF8563_RTC, RTC_PCF8563, "PCF8563", (uint8_t)addr.address)
 #endif
+#ifdef RX8130CE_RTC
+                SCAN_SIMPLE_CASE(RX8130CE_RTC, RTC_RX8130CE, "RX8130CE", (uint8_t)addr.address)
+#endif
 
             case CARDKB_ADDR:
                 // Do we have the RAK14006 instead?
diff --git a/src/gps/RTC.cpp b/src/gps/RTC.cpp
index da20e28eb..97590c6a8 100644
--- a/src/gps/RTC.cpp
+++ b/src/gps/RTC.cpp
@@ -109,6 +109,25 @@ RTCSetResult readFromRTC()
         }
         return RTCSetResultSuccess;
     }
+#elif defined(RX8130CE_RTC)
+    if (rtc_found.address == RX8130CE_RTC) {
+        uint32_t now = millis();
+        ArtronShop_RX8130CE rtc(&Wire);
+        tm t;
+        if (rtc.getTime(&t)) {
+            tv.tv_sec = gm_mktime(&t);
+            tv.tv_usec = 0;
+
+            uint32_t printableEpoch = tv.tv_sec; // Print lib only supports 32 bit but time_t can be 64 bit on some platforms
+            LOG_DEBUG("Read RTC time from RX8130CE 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) {
+                currentQuality = RTCQualityDevice;
+            }
+        }
+    }
 #else
     if (!gettimeofday(&tv, NULL)) {
         uint32_t now = millis();
@@ -214,6 +233,17 @@ RTCSetResult perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpd
             LOG_DEBUG("PCF8563_RTC setDateTime %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);
         }
+#elif defined(RX8130CE_RTC)
+        if (rtc_found.address == RX8130CE_RTC) {
+            ArtronShop_RX8130CE rtc(&Wire);
+            tm *t = gmtime(&tv->tv_sec);
+            if (rtc.setTime(*t)) {
+                LOG_DEBUG("RX8130CE setDateTime %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);
+            } else {
+                LOG_WARN("Failed to set time for RX8130CE");
+            }
+        }
 #elif defined(ARCH_ESP32)
         settimeofday(tv, NULL);
 #endif
diff --git a/src/gps/RTC.h b/src/gps/RTC.h
index eca17bf35..06dd34c16 100644
--- a/src/gps/RTC.h
+++ b/src/gps/RTC.h
@@ -4,6 +4,10 @@
 #include "sys/time.h"
 #include 
 
+#ifdef RX8130CE_RTC
+#include 
+#endif
+
 enum RTCQuality {
 
     /// We haven't had our RTC set yet
diff --git a/src/main.cpp b/src/main.cpp
index 29a608dfe..1df4ebe3b 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -297,6 +297,12 @@ void printInfo()
 #ifndef PIO_UNIT_TESTING
 void setup()
 {
+#if defined(T_REX) // XXX
+    pinMode(DCDC_EN_HOLD, OUTPUT);
+    digitalWrite(DCDC_EN_HOLD, HIGH);
+    pinMode(NRF_ON, OUTPUT);
+    digitalWrite(NRF_ON, HIGH);
+#endif
 
 #if defined(PIN_POWER_EN)
     pinMode(PIN_POWER_EN, OUTPUT);
diff --git a/src/platform/nrf52/NRF52Bluetooth.cpp b/src/platform/nrf52/NRF52Bluetooth.cpp
index b457c35b7..79eef8f76 100644
--- a/src/platform/nrf52/NRF52Bluetooth.cpp
+++ b/src/platform/nrf52/NRF52Bluetooth.cpp
@@ -335,7 +335,8 @@ bool NRF52Bluetooth::onPairingPasskey(uint16_t conn_handle, uint8_t const passke
     meshtastic::BluetoothStatus newStatus(textkey);
     bluetoothStatus->updateStatus(&newStatus);
 
-#if !defined(MESHTASTIC_EXCLUDE_SCREEN) // Todo: migrate this display code back into Screen class, and observe bluetoothStatus
+#if HAS_SCREEN &&                                                                                                                \
+    !defined(MESHTASTIC_EXCLUDE_SCREEN) // Todo: migrate this display code back into Screen class, and observe bluetoothStatus
     if (screen) {
         screen->startAlert([](OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -> void {
             char btPIN[16] = "888888";
diff --git a/src/power.h b/src/power.h
index e96f5b022..10ec07a30 100644
--- a/src/power.h
+++ b/src/power.h
@@ -34,6 +34,8 @@
 #define OCV_ARRAY 4200, 3876, 3826, 3763, 3713, 3660, 3573, 3485, 3422, 3359, 3300
 #elif defined(SEEED_SOLAR_NODE)
 #define OCV_ARRAY 4200, 3986, 3922, 3812, 3734, 3645, 3527, 3420, 3281, 3087, 2786
+#elif defined(T_REX)
+#define OCV_ARRAY 4330, 4292, 4254, 4216, 4178, 4140, 4102, 4064, 4026, 3988, 3950
 #else // LiIon
 #define OCV_ARRAY 4190, 4050, 3990, 3890, 3800, 3720, 3630, 3530, 3420, 3300, 3100
 #endif
diff --git a/variants/t-rex/platformio.ini b/variants/t-rex/platformio.ini
new file mode 100644
index 000000000..c2b72e7d3
--- /dev/null
+++ b/variants/t-rex/platformio.ini
@@ -0,0 +1,17 @@
+; The t-rex Muzi board
+[env:t-rex]
+extends = nrf52840_base
+board = t-rex
+board_check = true
+build_flags = ${nrf52840_base.build_flags} -Ivariants/t-rex -D T_REX
+  -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/t-rex> + +
+lib_deps = 
+  ${nrf52840_base.lib_deps}
+  ${networking_base.lib_deps}
+  https://github.com/RAKWireless/RAK13800-W5100S/archive/1.0.2.zip
+  rakwireless/RAKwireless NCP5623 RGB LED library@^1.0.2
+  artronshop/ArtronShop_RX8130CE@1.0.0
diff --git a/variants/t-rex/variant.cpp b/variants/t-rex/variant.cpp
new file mode 100644
index 000000000..f87c041aa
--- /dev/null
+++ b/variants/t-rex/variant.cpp
@@ -0,0 +1,45 @@
+/*
+  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);
+}
diff --git a/variants/t-rex/variant.h b/variants/t-rex/variant.h
new file mode 100644
index 000000000..33bf7093a
--- /dev/null
+++ b/variants/t-rex/variant.h
@@ -0,0 +1,148 @@
+/*
+  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_TREX_
+#define _VARIANT_TREX_
+
+#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 (1)
+#define NUM_ANALOG_OUTPUTS (0)
+
+// LEDs
+#define PIN_LED1 (32 + 4) // P1.04 Controls Green LED
+#define PIN_LED2 (28)     // P0.28 Controls Blue LED
+
+#define LED_BUILTIN PIN_LED1
+#define LED_CONN PIN_LED2
+
+#define LED_GREEN PIN_LED1
+#define LED_BLUE PIN_LED2
+
+#define LED_STATE_ON 1 // State when LED is litted
+
+// Button
+#define PIN_BUTTON1 (26)
+#define BUTTON_ACTIVE_LOW 0
+#define BUTTON_ACTIVE_PULLUP 0
+
+#define ADC_RESOLUTION 14 // ???
+
+// Serial for GPS
+#define PIN_SERIAL1_RX (25)
+#define PIN_SERIAL1_TX (24)
+
+// Connected to Jlink CDC
+#define PIN_SERIAL2_RX (8) // ???
+#define PIN_SERIAL2_TX (6) // ???
+
+/*
+ * SPI Interfaces
+ */
+#define SPI_INTERFACES_COUNT 1
+
+#define PIN_SPI_MISO (45)
+#define PIN_SPI_MOSI (44)
+#define PIN_SPI_SCK (43)
+
+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;
+
+// T-Rex Extras
+#define DCDC_EN_HOLD (13) // P0.13 Keeps DCDC alive after user button  is pressed
+#define NRF_ON (29)       // P0.29 Tells IO controller device is on
+
+// RAKRGB
+#define HAS_NCP5623 // ???
+
+#define HAS_SCREEN 0
+
+/*
+ * Wire Interfaces
+ */
+#define WIRE_INTERFACES_COUNT 1
+
+#define PIN_WIRE_SDA (19) // P0.19 RTC_SDA
+#define PIN_WIRE_SCL (20) // P0.20 RTC_SDA
+
+#define PIN_BUZZER (0 + 3) // P0.03
+
+#define USE_SX1262
+#define SX126X_CS (42)
+#define SX126X_DIO1 (47)
+#define SX126X_BUSY (46)
+#define SX126X_RESET (38)
+#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
+
+#define PIN_GPS_EN (32 + 1) // P1.01
+#define PIN_GPS_PPS (2)     // P0.02 Pulse per second input from the GPS
+
+#define GPS_RX_PIN PIN_SERIAL1_RX
+#define GPS_TX_PIN PIN_SERIAL1_TX
+
+// Battery
+#define BATTERY_PIN (0 + 31) // P0.31 ADC_VBAT
+// 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 RX8130CE_RTC 0x32
+
+#ifdef __cplusplus
+}
+#endif
+
+/*----------------------------------------------------------------------------
+ *        Arduino objects - C++ only
+ *----------------------------------------------------------------------------*/
+
+#endif

From f9937967fa9b21eb85f6fdfb2e79bcada012db46 Mon Sep 17 00:00:00 2001
From: Jonathan Bennett 
Date: Tue, 30 Sep 2025 15:56:01 -0500
Subject: [PATCH 384/691] Add HardwareModel and correct directories

---
 src/platform/nrf52/architecture.h            | 2 ++
 variants/{ => nrf52840}/t-rex/platformio.ini | 6 ++++--
 variants/{ => nrf52840}/t-rex/variant.cpp    | 0
 variants/{ => nrf52840}/t-rex/variant.h      | 0
 4 files changed, 6 insertions(+), 2 deletions(-)
 rename variants/{ => nrf52840}/t-rex/platformio.ini (83%)
 rename variants/{ => nrf52840}/t-rex/variant.cpp (100%)
 rename variants/{ => nrf52840}/t-rex/variant.h (100%)

diff --git a/src/platform/nrf52/architecture.h b/src/platform/nrf52/architecture.h
index c9938062e..9d0447067 100644
--- a/src/platform/nrf52/architecture.h
+++ b/src/platform/nrf52/architecture.h
@@ -98,6 +98,8 @@
 #define HW_VENDOR meshtastic_HardwareModel_SEEED_WIO_TRACKER_L1_EINK
 #elif defined(SEEED_WIO_TRACKER_L1)
 #define HW_VENDOR meshtastic_HardwareModel_SEEED_WIO_TRACKER_L1
+#elif defined(T_REX)
+#define HW_VENDOR meshtastic_HardwareModel_MUZI_R1_NEO
 #elif defined(HELTEC_MESH_SOLAR)
 #define HW_VENDOR meshtastic_HardwareModel_HELTEC_MESH_SOLAR
 #else
diff --git a/variants/t-rex/platformio.ini b/variants/nrf52840/t-rex/platformio.ini
similarity index 83%
rename from variants/t-rex/platformio.ini
rename to variants/nrf52840/t-rex/platformio.ini
index c2b72e7d3..33b8f96f5 100644
--- a/variants/t-rex/platformio.ini
+++ b/variants/nrf52840/t-rex/platformio.ini
@@ -3,12 +3,14 @@
 extends = nrf52840_base
 board = t-rex
 board_check = true
-build_flags = ${nrf52840_base.build_flags} -Ivariants/t-rex -D T_REX
+build_flags = ${nrf52840_base.build_flags}
+  -Ivariants/nrf52840/t-rex
+  -D T_REX
   -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/t-rex> + +
+build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/t-rex> + +
 lib_deps = 
   ${nrf52840_base.lib_deps}
   ${networking_base.lib_deps}
diff --git a/variants/t-rex/variant.cpp b/variants/nrf52840/t-rex/variant.cpp
similarity index 100%
rename from variants/t-rex/variant.cpp
rename to variants/nrf52840/t-rex/variant.cpp
diff --git a/variants/t-rex/variant.h b/variants/nrf52840/t-rex/variant.h
similarity index 100%
rename from variants/t-rex/variant.h
rename to variants/nrf52840/t-rex/variant.h

From 8b466b1db3cd9941a328f956308476adc688bb9f Mon Sep 17 00:00:00 2001
From: Jonathan Bennett 
Date: Tue, 30 Sep 2025 18:00:29 -0500
Subject: [PATCH 385/691] T-rex comment cleanup

---
 src/main.cpp                      |  2 +-
 variants/nrf52840/t-rex/variant.h | 18 +++++++++---------
 2 files changed, 10 insertions(+), 10 deletions(-)

diff --git a/src/main.cpp b/src/main.cpp
index 1df4ebe3b..382f35065 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -297,7 +297,7 @@ void printInfo()
 #ifndef PIO_UNIT_TESTING
 void setup()
 {
-#if defined(T_REX) // XXX
+#if defined(T_REX)
     pinMode(DCDC_EN_HOLD, OUTPUT);
     digitalWrite(DCDC_EN_HOLD, HIGH);
     pinMode(NRF_ON, OUTPUT);
diff --git a/variants/nrf52840/t-rex/variant.h b/variants/nrf52840/t-rex/variant.h
index 33bf7093a..9ab0cadf8 100644
--- a/variants/nrf52840/t-rex/variant.h
+++ b/variants/nrf52840/t-rex/variant.h
@@ -38,8 +38,8 @@ extern "C" {
 #endif // __cplusplus
 
 // Number of pins defined in PinDescription array
-#define PINS_COUNT (48)       // ???
-#define NUM_DIGITAL_PINS (48) // ???
+#define PINS_COUNT (48)
+#define NUM_DIGITAL_PINS (48)
 #define NUM_ANALOG_INPUTS (1)
 #define NUM_ANALOG_OUTPUTS (0)
 
@@ -60,15 +60,15 @@ extern "C" {
 #define BUTTON_ACTIVE_LOW 0
 #define BUTTON_ACTIVE_PULLUP 0
 
-#define ADC_RESOLUTION 14 // ???
+#define ADC_RESOLUTION 14
 
 // Serial for GPS
 #define PIN_SERIAL1_RX (25)
 #define PIN_SERIAL1_TX (24)
 
 // Connected to Jlink CDC
-#define PIN_SERIAL2_RX (8) // ???
-#define PIN_SERIAL2_TX (6) // ???
+#define PIN_SERIAL2_RX (8)
+#define PIN_SERIAL2_TX (6)
 
 /*
  * SPI Interfaces
@@ -89,7 +89,7 @@ static const uint8_t SCK = PIN_SPI_SCK;
 #define NRF_ON (29)       // P0.29 Tells IO controller device is on
 
 // RAKRGB
-#define HAS_NCP5623 // ???
+#define HAS_NCP5623
 
 #define HAS_SCREEN 0
 
@@ -99,7 +99,7 @@ static const uint8_t SCK = PIN_SPI_SCK;
 #define WIRE_INTERFACES_COUNT 1
 
 #define PIN_WIRE_SDA (19) // P0.19 RTC_SDA
-#define PIN_WIRE_SCL (20) // P0.20 RTC_SDA
+#define PIN_WIRE_SCL (20) // P0.20 RTC_SCL
 
 #define PIN_BUZZER (0 + 3) // P0.03
 
@@ -126,12 +126,12 @@ static const uint8_t SCK = PIN_SPI_SCK;
 // Battery
 #define BATTERY_PIN (0 + 31) // P0.31 ADC_VBAT
 // and has 12 bit resolution
-#define BATTERY_SENSE_RESOLUTION_BITS 12 // ???
+#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 ADC_MULTIPLIER 1.73
 
 #define HAS_RTC 1
 

From ad44940732b4f7bb1f664e399cf53f405fba4b0a Mon Sep 17 00:00:00 2001
From: Jonathan Bennett 
Date: Tue, 30 Sep 2025 20:46:38 -0500
Subject: [PATCH 386/691] Put the GPIO in the right state for wake from sleep

---
 variants/nrf52840/t-rex/variant.h | 1 +
 1 file changed, 1 insertion(+)

diff --git a/variants/nrf52840/t-rex/variant.h b/variants/nrf52840/t-rex/variant.h
index 9ab0cadf8..4735e340d 100644
--- a/variants/nrf52840/t-rex/variant.h
+++ b/variants/nrf52840/t-rex/variant.h
@@ -59,6 +59,7 @@ extern "C" {
 #define PIN_BUTTON1 (26)
 #define BUTTON_ACTIVE_LOW 0
 #define BUTTON_ACTIVE_PULLUP 0
+#define BUTTON_SENSE_TYPE INPUT_SENSE_HIGH
 
 #define ADC_RESOLUTION 14
 

From d5164b4fbfe6b75328e90497e73d785faadcf85d Mon Sep 17 00:00:00 2001
From: Jonathan Bennett 
Date: Tue, 30 Sep 2025 21:38:32 -0500
Subject: [PATCH 387/691] Check the BUILD_EPOCH if defined

---
 src/gps/RTC.cpp | 14 ++++++++++++--
 1 file changed, 12 insertions(+), 2 deletions(-)

diff --git a/src/gps/RTC.cpp b/src/gps/RTC.cpp
index 97590c6a8..665a9aaa3 100644
--- a/src/gps/RTC.cpp
+++ b/src/gps/RTC.cpp
@@ -121,11 +121,21 @@ RTCSetResult readFromRTC()
             uint32_t printableEpoch = tv.tv_sec; // Print lib only supports 32 bit but time_t can be 64 bit on some platforms
             LOG_DEBUG("Read RTC time from RX8130CE 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;
+#ifdef BUILD_EPOCH
+            if (tv.tv_sec < BUILD_EPOCH) {
+                if (Throttle::isWithinTimespanMs(lastTimeValidationWarning, TIME_VALIDATION_WARNING_INTERVAL_MS) == false) {
+                    LOG_WARN("Ignore time (%ld) before build epoch (%ld)!", printableEpoch, BUILD_EPOCH);
+                    lastTimeValidationWarning = millis();
+                }
+                return RTCSetResultInvalidTime;
+            }
+#endif
             if (currentQuality == RTCQualityNone) {
+                timeStartMsec = now;
+                zeroOffsetSecs = tv.tv_sec;
                 currentQuality = RTCQualityDevice;
             }
+            return RTCSetResultSuccess;
         }
     }
 #else

From 17afdb9ccf9f89334b5087c99b47c7a469ef53f3 Mon Sep 17 00:00:00 2001
From: rcarteraz 
Date: Wed, 1 Oct 2025 08:21:20 -0700
Subject: [PATCH 388/691] no more t-rex

---
 variants/nrf52840/{t-rex => r1-neo}/platformio.ini | 12 ++++++------
 variants/nrf52840/{t-rex => r1-neo}/variant.cpp    |  0
 variants/nrf52840/{t-rex => r1-neo}/variant.h      |  6 +++---
 3 files changed, 9 insertions(+), 9 deletions(-)
 rename variants/nrf52840/{t-rex => r1-neo}/platformio.ini (82%)
 rename variants/nrf52840/{t-rex => r1-neo}/variant.cpp (100%)
 rename variants/nrf52840/{t-rex => r1-neo}/variant.h (98%)

diff --git a/variants/nrf52840/t-rex/platformio.ini b/variants/nrf52840/r1-neo/platformio.ini
similarity index 82%
rename from variants/nrf52840/t-rex/platformio.ini
rename to variants/nrf52840/r1-neo/platformio.ini
index 33b8f96f5..6feb55dc9 100644
--- a/variants/nrf52840/t-rex/platformio.ini
+++ b/variants/nrf52840/r1-neo/platformio.ini
@@ -1,16 +1,16 @@
-; The t-rex Muzi board
-[env:t-rex]
+; The R1 Neo board
+[env:r1-neo]
 extends = nrf52840_base
-board = t-rex
+board = r1-neo
 board_check = true
 build_flags = ${nrf52840_base.build_flags}
-  -Ivariants/nrf52840/t-rex
-  -D T_REX
+  -Ivariants/nrf52840/r1-neo
+  -D R1_NEO
   -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/t-rex> + +
+build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/r1-neo> + +
 lib_deps = 
   ${nrf52840_base.lib_deps}
   ${networking_base.lib_deps}
diff --git a/variants/nrf52840/t-rex/variant.cpp b/variants/nrf52840/r1-neo/variant.cpp
similarity index 100%
rename from variants/nrf52840/t-rex/variant.cpp
rename to variants/nrf52840/r1-neo/variant.cpp
diff --git a/variants/nrf52840/t-rex/variant.h b/variants/nrf52840/r1-neo/variant.h
similarity index 98%
rename from variants/nrf52840/t-rex/variant.h
rename to variants/nrf52840/r1-neo/variant.h
index 4735e340d..901e993e3 100644
--- a/variants/nrf52840/t-rex/variant.h
+++ b/variants/nrf52840/r1-neo/variant.h
@@ -16,8 +16,8 @@
   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */
 
-#ifndef _VARIANT_TREX_
-#define _VARIANT_TREX_
+#ifndef _VARIANT_R1NEO_
+#define _VARIANT_R1NEO_
 
 #define RAK4630
 
@@ -85,7 +85,7 @@ static const uint8_t MOSI = PIN_SPI_MOSI;
 static const uint8_t MISO = PIN_SPI_MISO;
 static const uint8_t SCK = PIN_SPI_SCK;
 
-// T-Rex Extras
+// R1 Neo Extras
 #define DCDC_EN_HOLD (13) // P0.13 Keeps DCDC alive after user button  is pressed
 #define NRF_ON (29)       // P0.29 Tells IO controller device is on
 

From b28d09509676c8edd66fae0051562354655119dd Mon Sep 17 00:00:00 2001
From: rcarteraz 
Date: Wed, 1 Oct 2025 09:30:46 -0700
Subject: [PATCH 389/691] missed t-rexes

---
 boards/{t-rex.json => r1-neo.json} | 4 ++--
 src/main.cpp                       | 2 +-
 src/platform/nrf52/architecture.h  | 2 +-
 src/power.h                        | 2 +-
 4 files changed, 5 insertions(+), 5 deletions(-)
 rename boards/{t-rex.json => r1-neo.json} (95%)

diff --git a/boards/t-rex.json b/boards/r1-neo.json
similarity index 95%
rename from boards/t-rex.json
rename to boards/r1-neo.json
index d827c55dd..0383a2f48 100644
--- a/boards/t-rex.json
+++ b/boards/r1-neo.json
@@ -13,9 +13,9 @@
       ["0x239A", "0x002A"],
       ["0x239A", "0x802A"]
     ],
-    "usb_product": "Muzi T-REX",
+    "usb_product": "Muzi R1 Neo",
     "mcu": "nrf52840",
-    "variant": "t-rex",
+    "variant": "r1-neo",
     "bsp": {
       "name": "adafruit"
     },
diff --git a/src/main.cpp b/src/main.cpp
index 382f35065..d49111414 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -297,7 +297,7 @@ void printInfo()
 #ifndef PIO_UNIT_TESTING
 void setup()
 {
-#if defined(T_REX)
+#if defined(R1_NEO)
     pinMode(DCDC_EN_HOLD, OUTPUT);
     digitalWrite(DCDC_EN_HOLD, HIGH);
     pinMode(NRF_ON, OUTPUT);
diff --git a/src/platform/nrf52/architecture.h b/src/platform/nrf52/architecture.h
index 9d0447067..8212bcbc3 100644
--- a/src/platform/nrf52/architecture.h
+++ b/src/platform/nrf52/architecture.h
@@ -98,7 +98,7 @@
 #define HW_VENDOR meshtastic_HardwareModel_SEEED_WIO_TRACKER_L1_EINK
 #elif defined(SEEED_WIO_TRACKER_L1)
 #define HW_VENDOR meshtastic_HardwareModel_SEEED_WIO_TRACKER_L1
-#elif defined(T_REX)
+#elif defined(R1_NEO)
 #define HW_VENDOR meshtastic_HardwareModel_MUZI_R1_NEO
 #elif defined(HELTEC_MESH_SOLAR)
 #define HW_VENDOR meshtastic_HardwareModel_HELTEC_MESH_SOLAR
diff --git a/src/power.h b/src/power.h
index 10ec07a30..23eb95064 100644
--- a/src/power.h
+++ b/src/power.h
@@ -34,7 +34,7 @@
 #define OCV_ARRAY 4200, 3876, 3826, 3763, 3713, 3660, 3573, 3485, 3422, 3359, 3300
 #elif defined(SEEED_SOLAR_NODE)
 #define OCV_ARRAY 4200, 3986, 3922, 3812, 3734, 3645, 3527, 3420, 3281, 3087, 2786
-#elif defined(T_REX)
+#elif defined(R1_NEO)
 #define OCV_ARRAY 4330, 4292, 4254, 4216, 4178, 4140, 4102, 4064, 4026, 3988, 3950
 #else // LiIon
 #define OCV_ARRAY 4190, 4050, 3990, 3890, 3800, 3720, 3630, 3530, 3420, 3300, 3100

From 849bbad2798900ab1b1a5f153de8fca1122c7369 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
 <41898282+github-actions[bot]@users.noreply.github.com>
Date: Wed, 1 Oct 2025 15:13:28 -0500
Subject: [PATCH 390/691] Automated version bumps (#8177)

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 0d03dd349..3505f1940 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.12
+    
     
       https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.11
     
diff --git a/debian/changelog b/debian/changelog
index 15c8604f6..8bd053b25 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,4 +1,4 @@
-meshtasticd (2.7.11.0) UNRELEASED; urgency=medium
+meshtasticd (2.7.12.0) unstable; urgency=medium
 
   [ Austin Lane ]
   * Initial packaging
@@ -7,4 +7,7 @@ meshtasticd (2.7.11.0) UNRELEASED; urgency=medium
   [  ]
   * GitHub Actions Automatic version bump
 
- --    Wed, 24 Sep 2025 11:01:13 +0000
+  [ GitHub Actions ]
+  * Version 2.7.12
+
+ -- GitHub Actions   Wed, 01 Oct 2025 19:51:41 +0000
diff --git a/version.properties b/version.properties
index ce1205f2b..5c84a8e65 100644
--- a/version.properties
+++ b/version.properties
@@ -1,4 +1,4 @@
 [VERSION]  
 major = 2
 minor = 7
-build = 11
+build = 12

From f7469159cf8c992f9869fc67ca04531edab4479a Mon Sep 17 00:00:00 2001
From: Mike Robbins 
Date: Wed, 1 Oct 2025 16:31:53 -0400
Subject: [PATCH 391/691] Reliable ACKs for DMs (#8165)

* RoutingModule::sendAckNak takes ackWantsAck arg to set want_ack on the ACK itself

* Use reliable delivery for traceroute requests (which will be copied to traceroute responses by setReplyTo)

* Update ReliableRouter::sniffReceived to use ReliableRouter::shouldSuccessAckWithWantAck

* Use isFromUs

* Update MockRoutingModule::sendAckNak to include ackWantsAck argument (currently ignored)

---------

Co-authored-by: Ben Meadors 
---
 src/mesh/PhoneAPI.cpp            |  7 +++++
 src/mesh/ReliableRouter.cpp      | 44 +++++++++++++++++++++++++++++++-
 src/mesh/ReliableRouter.h        |  6 +++++
 src/mesh/Router.cpp              |  5 ++--
 src/mesh/Router.h                |  3 ++-
 src/modules/RoutingModule.cpp    |  6 ++++-
 src/modules/RoutingModule.h      |  4 +--
 src/modules/TraceRouteModule.cpp |  6 +++++
 test/test_mqtt/MQTT.cpp          |  4 +--
 9 files changed, 76 insertions(+), 9 deletions(-)

diff --git a/src/mesh/PhoneAPI.cpp b/src/mesh/PhoneAPI.cpp
index f6f1bc027..07f314415 100644
--- a/src/mesh/PhoneAPI.cpp
+++ b/src/mesh/PhoneAPI.cpp
@@ -710,6 +710,13 @@ bool PhoneAPI::handleToRadioPacket(meshtastic_MeshPacket &p)
         // sendNotification(meshtastic_LogRecord_Level_WARNING, p.id, "Text messages can only be sent once every 2 seconds");
         return false;
     }
+
+    // Upgrade traceroute requests from phone to use reliable delivery, matching TraceRouteModule
+    if (p.decoded.portnum == meshtastic_PortNum_TRACEROUTE_APP && !isBroadcast(p.to)) {
+        // Use reliable delivery for traceroute requests (which will be copied to traceroute responses by setReplyTo)
+        p.want_ack = true;
+    }
+
     lastPortNumToRadio[p.decoded.portnum] = millis();
     service->handleToRadio(p);
     return true;
diff --git a/src/mesh/ReliableRouter.cpp b/src/mesh/ReliableRouter.cpp
index cca838ff0..b31c352fe 100644
--- a/src/mesh/ReliableRouter.cpp
+++ b/src/mesh/ReliableRouter.cpp
@@ -103,10 +103,20 @@ void ReliableRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtas
                     /* A response may be set to want_ack for retransmissions, but we don't need to ACK a response if it received
                       an implicit ACK already. If we received it directly or via NextHopRouter, only ACK with a hop limit of 0 to
                       make sure the other side stops retransmitting. */
-                    if (!p->decoded.request_id && !p->decoded.reply_id) {
+
+                    if (shouldSuccessAckWithWantAck(p)) {
+                        // If this packet should always be ACKed reliably with want_ack back to the original sender, make sure we
+                        // do that unconditionally.
+                        sendAckNak(meshtastic_Routing_Error_NONE, getFrom(p), p->id, p->channel,
+                                   routingModule->getHopLimitForResponse(p->hop_start, p->hop_limit), true);
+                    } else if (!p->decoded.request_id && !p->decoded.reply_id) {
+                        // If it's not an ACK or a reply, send an ACK.
                         sendAckNak(meshtastic_Routing_Error_NONE, getFrom(p), p->id, p->channel,
                                    routingModule->getHopLimitForResponse(p->hop_start, p->hop_limit));
                     } else if ((p->hop_start > 0 && p->hop_start == p->hop_limit) || p->next_hop != NO_NEXT_HOP_PREFERENCE) {
+                        // If we received the packet directly from the original sender, send a 0-hop ACK since the original sender
+                        // won't overhear any implicit ACKs. If we received the packet via NextHopRouter, also send a 0-hop ACK to
+                        // stop the immediate relayer's retransmissions.
                         sendAckNak(meshtastic_Routing_Error_NONE, getFrom(p), p->id, p->channel, 0);
                     }
                 } else if (p->which_payload_variant == meshtastic_MeshPacket_encrypted_tag && p->channel == 0 &&
@@ -152,4 +162,36 @@ void ReliableRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtas
 
     // handle the packet as normal
     isBroadcast(p->to) ? FloodingRouter::sniffReceived(p, c) : NextHopRouter::sniffReceived(p, c);
+}
+
+/**
+ * If we ACK this packet, should we set want_ack=true on the ACK for reliable delivery of the ACK packet?
+ */
+bool ReliableRouter::shouldSuccessAckWithWantAck(const meshtastic_MeshPacket *p)
+{
+    // Don't ACK-with-want-ACK outgoing packets
+    if (isFromUs(p))
+        return false;
+
+    // Only ACK-with-want-ACK if the original packet asked for want_ack
+    if (!p->want_ack)
+        return false;
+
+    // Only ACK-with-want-ACK packets to us (not broadcast)
+    if (!isToUs(p))
+        return false;
+
+    // Special case for text message DMs:
+    bool isTextMessage =
+        (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) &&
+        IS_ONE_OF(p->decoded.portnum, meshtastic_PortNum_TEXT_MESSAGE_APP, meshtastic_PortNum_TEXT_MESSAGE_COMPRESSED_APP);
+
+    if (isTextMessage) {
+        // If it's a non-broadcast text message, and the original asked for want_ack,
+        // let's send an ACK that is itself want_ack to improve reliability of confirming delivery back to the sender.
+        // This should include all DMs regardless of whether or not reply_id is set.
+        return true;
+    }
+
+    return false;
 }
\ No newline at end of file
diff --git a/src/mesh/ReliableRouter.h b/src/mesh/ReliableRouter.h
index 2cf10fb99..33121de6b 100644
--- a/src/mesh/ReliableRouter.h
+++ b/src/mesh/ReliableRouter.h
@@ -31,4 +31,10 @@ class ReliableRouter : public NextHopRouter
      * We hook this method so we can see packets before FloodingRouter says they should be discarded
      */
     virtual bool shouldFilterReceived(const meshtastic_MeshPacket *p) override;
+
+  private:
+    /**
+     * Should this packet be ACKed with a want_ack for reliable delivery?
+     */
+    bool shouldSuccessAckWithWantAck(const meshtastic_MeshPacket *p);
 };
\ No newline at end of file
diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp
index 8f9fb28f9..60637cbd1 100644
--- a/src/mesh/Router.cpp
+++ b/src/mesh/Router.cpp
@@ -198,9 +198,10 @@ meshtastic_MeshPacket *Router::allocForSending()
 /**
  * Send an ack or a nak packet back towards whoever sent idFrom
  */
-void Router::sendAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex, uint8_t hopLimit)
+void Router::sendAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex, uint8_t hopLimit,
+                        bool ackWantsAck)
 {
-    routingModule->sendAckNak(err, to, idFrom, chIndex, hopLimit);
+    routingModule->sendAckNak(err, to, idFrom, chIndex, hopLimit, ackWantsAck);
 }
 
 void Router::abortSendAndNak(meshtastic_Routing_Error err, meshtastic_MeshPacket *p)
diff --git a/src/mesh/Router.h b/src/mesh/Router.h
index 92a5a06e5..10a3771a7 100644
--- a/src/mesh/Router.h
+++ b/src/mesh/Router.h
@@ -125,7 +125,8 @@ class Router : protected concurrency::OSThread, protected PacketHistory
     /**
      * Send an ack or a nak packet back towards whoever sent idFrom
      */
-    void sendAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex, uint8_t hopLimit = 0);
+    void sendAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex, uint8_t hopLimit = 0,
+                    bool ackWantsAck = false);
 
   private:
     /**
diff --git a/src/modules/RoutingModule.cpp b/src/modules/RoutingModule.cpp
index fbe3a9cee..05173983c 100644
--- a/src/modules/RoutingModule.cpp
+++ b/src/modules/RoutingModule.cpp
@@ -47,10 +47,14 @@ meshtastic_MeshPacket *RoutingModule::allocReply()
     return NULL;
 }
 
-void RoutingModule::sendAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex, uint8_t hopLimit)
+void RoutingModule::sendAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex, uint8_t hopLimit,
+                               bool ackWantsAck)
 {
     auto p = allocAckNak(err, to, idFrom, chIndex, hopLimit);
 
+    // Allow the caller to set want_ack on this ACK packet if it's important that the ACK be delivered reliably
+    p->want_ack = ackWantsAck;
+
     router->sendLocal(p); // we sometimes send directly to the local node
 }
 
diff --git a/src/modules/RoutingModule.h b/src/modules/RoutingModule.h
index c047f6e29..a4e0679d0 100644
--- a/src/modules/RoutingModule.h
+++ b/src/modules/RoutingModule.h
@@ -13,8 +13,8 @@ class RoutingModule : public ProtobufModule
      */
     RoutingModule();
 
-    virtual void sendAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex,
-                            uint8_t hopLimit = 0);
+    virtual void sendAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex, uint8_t hopLimit = 0,
+                            bool ackWantsAck = false);
 
     // Given the hopStart and hopLimit upon reception of a request, return the hop limit to use for the response
     uint8_t getHopLimitForResponse(uint8_t hopStart, uint8_t hopLimit);
diff --git a/src/modules/TraceRouteModule.cpp b/src/modules/TraceRouteModule.cpp
index 8f69c504a..fc2cc232b 100644
--- a/src/modules/TraceRouteModule.cpp
+++ b/src/modules/TraceRouteModule.cpp
@@ -419,6 +419,9 @@ bool TraceRouteModule::startTraceRoute(NodeNum node)
         p->decoded.portnum = meshtastic_PortNum_TRACEROUTE_APP;
         p->decoded.want_response = true;
 
+        // Use reliable delivery for traceroute requests (which will be copied to traceroute responses by setReplyTo)
+        p->want_ack = true;
+
         // Manually encode the RouteDiscovery payload
         p->decoded.payload.size =
             pb_encode_to_bytes(p->decoded.payload.bytes, sizeof(p->decoded.payload.bytes), &meshtastic_RouteDiscovery_msg, &req);
@@ -532,6 +535,9 @@ void TraceRouteModule::launch(NodeNum node)
         p->decoded.portnum = meshtastic_PortNum_TRACEROUTE_APP;
         p->decoded.want_response = true;
 
+        // Use reliable delivery for traceroute requests (which will be copied to traceroute responses by setReplyTo)
+        p->want_ack = true;
+
         p->decoded.payload.size =
             pb_encode_to_bytes(p->decoded.payload.bytes, sizeof(p->decoded.payload.bytes), &meshtastic_RouteDiscovery_msg, &req);
 
diff --git a/test/test_mqtt/MQTT.cpp b/test/test_mqtt/MQTT.cpp
index 32d81f6b4..ede3d22b7 100644
--- a/test/test_mqtt/MQTT.cpp
+++ b/test/test_mqtt/MQTT.cpp
@@ -83,8 +83,8 @@ class MockNodeDB : public NodeDB
 class MockRoutingModule : public RoutingModule
 {
   public:
-    void sendAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex,
-                    uint8_t hopLimit = 0) override
+    void sendAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex, uint8_t hopLimit = 0,
+                    bool ackWantsAck = false) override
     {
         ackNacks_.emplace_back(err, to, idFrom, chIndex, hopLimit);
     }

From 641a2fc63d1e412ff1326f00e63e7fa5964d17d0 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
 <41898282+github-actions[bot]@users.noreply.github.com>
Date: Wed, 1 Oct 2025 15:32:06 -0500
Subject: [PATCH 392/691] Update protobufs (#8178)

Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
---
 protobufs                                |  2 +-
 src/mesh/generated/meshtastic/admin.pb.h | 12 ++++++++----
 src/mesh/generated/meshtastic/mesh.pb.h  |  4 ++++
 3 files changed, 13 insertions(+), 5 deletions(-)

diff --git a/protobufs b/protobufs
index 5fa4c44d9..60c3e6600 160000
--- a/protobufs
+++ b/protobufs
@@ -1 +1 @@
-Subproject commit 5fa4c44d914aba67971ba15ace22e4b21af64ae5
+Subproject commit 60c3e6600a2f4e6f49e45aeb47aafd8291a0015c
diff --git a/src/mesh/generated/meshtastic/admin.pb.h b/src/mesh/generated/meshtastic/admin.pb.h
index bc0b780b9..7cc896292 100644
--- a/src/mesh/generated/meshtastic/admin.pb.h
+++ b/src/mesh/generated/meshtastic/admin.pb.h
@@ -132,6 +132,8 @@ typedef struct _meshtastic_SharedContact {
     meshtastic_User user;
     /* Add this contact to the blocked / ignored list */
     bool should_ignore;
+    /* Set the IS_KEY_MANUALLY_VERIFIED bit */
+    bool manually_verified;
 } meshtastic_SharedContact;
 
 /* This message is used by a client to initiate or complete a key verification */
@@ -319,13 +321,13 @@ extern "C" {
 #define meshtastic_AdminMessage_InputEvent_init_default {0, 0, 0, 0}
 #define meshtastic_HamParameters_init_default    {"", 0, 0, ""}
 #define meshtastic_NodeRemoteHardwarePinsResponse_init_default {0, {meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default}}
-#define meshtastic_SharedContact_init_default    {0, false, meshtastic_User_init_default, 0}
+#define meshtastic_SharedContact_init_default    {0, false, meshtastic_User_init_default, 0, 0}
 #define meshtastic_KeyVerificationAdmin_init_default {_meshtastic_KeyVerificationAdmin_MessageType_MIN, 0, 0, false, 0}
 #define meshtastic_AdminMessage_init_zero        {0, {0}, {0, {0}}}
 #define meshtastic_AdminMessage_InputEvent_init_zero {0, 0, 0, 0}
 #define meshtastic_HamParameters_init_zero       {"", 0, 0, ""}
 #define meshtastic_NodeRemoteHardwarePinsResponse_init_zero {0, {meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero}}
-#define meshtastic_SharedContact_init_zero       {0, false, meshtastic_User_init_zero, 0}
+#define meshtastic_SharedContact_init_zero       {0, false, meshtastic_User_init_zero, 0, 0}
 #define meshtastic_KeyVerificationAdmin_init_zero {_meshtastic_KeyVerificationAdmin_MessageType_MIN, 0, 0, false, 0}
 
 /* Field tags (for use in manual encoding/decoding) */
@@ -341,6 +343,7 @@ extern "C" {
 #define meshtastic_SharedContact_node_num_tag    1
 #define meshtastic_SharedContact_user_tag        2
 #define meshtastic_SharedContact_should_ignore_tag 3
+#define meshtastic_SharedContact_manually_verified_tag 4
 #define meshtastic_KeyVerificationAdmin_message_type_tag 1
 #define meshtastic_KeyVerificationAdmin_remote_nodenum_tag 2
 #define meshtastic_KeyVerificationAdmin_nonce_tag 3
@@ -504,7 +507,8 @@ X(a, STATIC,   REPEATED, MESSAGE,  node_remote_hardware_pins,   1)
 #define meshtastic_SharedContact_FIELDLIST(X, a) \
 X(a, STATIC,   SINGULAR, UINT32,   node_num,          1) \
 X(a, STATIC,   OPTIONAL, MESSAGE,  user,              2) \
-X(a, STATIC,   SINGULAR, BOOL,     should_ignore,     3)
+X(a, STATIC,   SINGULAR, BOOL,     should_ignore,     3) \
+X(a, STATIC,   SINGULAR, BOOL,     manually_verified,   4)
 #define meshtastic_SharedContact_CALLBACK NULL
 #define meshtastic_SharedContact_DEFAULT NULL
 #define meshtastic_SharedContact_user_MSGTYPE meshtastic_User
@@ -539,7 +543,7 @@ extern const pb_msgdesc_t meshtastic_KeyVerificationAdmin_msg;
 #define meshtastic_HamParameters_size            31
 #define meshtastic_KeyVerificationAdmin_size     25
 #define meshtastic_NodeRemoteHardwarePinsResponse_size 496
-#define meshtastic_SharedContact_size            125
+#define meshtastic_SharedContact_size            127
 
 #ifdef __cplusplus
 } /* extern "C" */
diff --git a/src/mesh/generated/meshtastic/mesh.pb.h b/src/mesh/generated/meshtastic/mesh.pb.h
index 601d52007..d8d2f2e8a 100644
--- a/src/mesh/generated/meshtastic/mesh.pb.h
+++ b/src/mesh/generated/meshtastic/mesh.pb.h
@@ -278,6 +278,10 @@ typedef enum _meshtastic_HardwareModel {
     meshtastic_HardwareModel_M5STACK_C6L = 111,
     /* M5Stack Cardputer Adv */
     meshtastic_HardwareModel_M5STACK_CARDPUTER_ADV = 112,
+    /* ESP32S3 main controller with GPS and TFT screen. */
+    meshtastic_HardwareModel_HELTEC_WIRELESS_TRACKER_V2 = 113,
+    /* LilyGo T-Watch Ultra */
+    meshtastic_HardwareModel_T_WATCH_ULTRA = 114,
     /* ------------------------------------------------------------------------------------------------------------------------------------------
  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 ec28c383af11cd24afd6b9fd72235789c38c43ad Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
 <41898282+github-actions[bot]@users.noreply.github.com>
Date: Wed, 1 Oct 2025 15:32:25 -0500
Subject: [PATCH 393/691] Upgrade trunk (#8159)

Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com>
---
 .trunk/trunk.yaml | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml
index 4e9de6a02..fa6fa6d7d 100644
--- a/.trunk/trunk.yaml
+++ b/.trunk/trunk.yaml
@@ -8,15 +8,15 @@ plugins:
       uri: https://github.com/trunk-io/plugins
 lint:
   enabled:
-    - checkov@3.2.471
-    - renovate@41.130.1
+    - checkov@3.2.473
+    - renovate@41.132.2
     - prettier@3.6.2
     - trufflehog@3.90.8
     - yamllint@1.37.1
     - bandit@1.8.6
-    - trivy@0.66.0
+    - trivy@0.67.0
     - taplo@0.10.0
-    - ruff@0.13.1
+    - ruff@0.13.2
     - isort@6.0.1
     - markdownlint@0.45.0
     - oxipng@9.1.5

From f82667d71e151398327c678af5bc2326b94ee17f Mon Sep 17 00:00:00 2001
From: ford-jones 
Date: Thu, 2 Oct 2025 10:24:32 +1300
Subject: [PATCH 394/691] Removed magic numbers

---
 src/graphics/draw/MenuHandler.cpp | 4 ++--
 src/modules/AdminModule.cpp       | 4 ++--
 2 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/src/graphics/draw/MenuHandler.cpp b/src/graphics/draw/MenuHandler.cpp
index f131bc298..c064866ca 100644
--- a/src/graphics/draw/MenuHandler.cpp
+++ b/src/graphics/draw/MenuHandler.cpp
@@ -145,11 +145,11 @@ void menuHandler::LoraRegionPicker(uint32_t duration)
                 config.lora.ignore_mqtt = true; // Ignore MQTT by default if region has a duty cycle limit
             }
 
-            if (strncmp(moduleConfig.mqtt.root, default_mqtt_root, 3) == 0) {
+            if (strncmp(moduleConfig.mqtt.root, default_mqtt_root, strlen(default_mqtt_root)) == 0) {
                 //  Default broker is in use, so subscribe to the appropriate MQTT root topic for this region
                 sprintf(moduleConfig.mqtt.root, "%s/%s", default_mqtt_root, myRegion->name);
                 changes |= SEGMENT_MODULECONFIG;
-            };
+            }
 
             service->reloadConfig(changes);
             rebootAtMsec = (millis() + DEFAULT_REBOOT_SECONDS * 1000);
diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp
index 367a0f617..d1e4ed081 100644
--- a/src/modules/AdminModule.cpp
+++ b/src/modules/AdminModule.cpp
@@ -776,8 +776,8 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c)
             //  Additionally as a side-effect, assume a new value under myRegion
             initRegion();
 
-            if (strncmp(moduleConfig.mqtt.root, default_mqtt_root, 3) == 0) {
-                //  Default broker is in use, so subscribe to the appropriate MQTT root topic for this region
+            if (strncmp(moduleConfig.mqtt.root, default_mqtt_root, strlen(default_mqtt_root)) == 0) {
+                //  Default root is in use, so subscribe to the appropriate MQTT topic for this region
                 sprintf(moduleConfig.mqtt.root, "%s/%s", default_mqtt_root, myRegion->name);
             }
 

From 9bb7bb467bf56eba2911906116490288d49c747c Mon Sep 17 00:00:00 2001
From: nexpspace <380097+nexpspace@users.noreply.github.com>
Date: Thu, 2 Oct 2025 02:36:17 +0200
Subject: [PATCH 395/691] Add DIRECT_MSG_ONLY buzzer mode (#8158)

* Handle existing special case for M5STACK_UNITC6L for DIRECT_MSG_ONLY buzz mode

There already exists a special case for M5STACK_UNITC6L.
Modified it to adhere to new DIRECT_MSG_ONLY buzzer mode

* Add new buzzer mode DIRECT_MSG_ONLY to BuzzerModeMenu

* Disable notifications when buzzer mode is DIRECT_MSG_ONLY

* Change alert_message_buzzer in notification module in DIRECT_MSG_ONLY buzz mode

Better comments

* Fixed spelling in debug log

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: nexpspace <4kosjdicx@mozmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Ben Meadors 
---
 src/buzz/BuzzerFeedbackThread.cpp          |  3 +-
 src/graphics/Screen.cpp                    |  8 +++++-
 src/graphics/draw/MenuHandler.cpp          |  4 +--
 src/modules/ExternalNotificationModule.cpp | 33 +++++++++++++---------
 4 files changed, 31 insertions(+), 17 deletions(-)

diff --git a/src/buzz/BuzzerFeedbackThread.cpp b/src/buzz/BuzzerFeedbackThread.cpp
index afa8c96e2..7de6c0740 100644
--- a/src/buzz/BuzzerFeedbackThread.cpp
+++ b/src/buzz/BuzzerFeedbackThread.cpp
@@ -15,7 +15,8 @@ int BuzzerFeedbackThread::handleInputEvent(const InputEvent *event)
 {
     // Only provide feedback if buzzer is enabled for notifications
     if (config.device.buzzer_mode == meshtastic_Config_DeviceConfig_BuzzerMode_DISABLED ||
-        config.device.buzzer_mode == meshtastic_Config_DeviceConfig_BuzzerMode_NOTIFICATIONS_ONLY) {
+        config.device.buzzer_mode == meshtastic_Config_DeviceConfig_BuzzerMode_NOTIFICATIONS_ONLY ||
+        config.device.buzzer_mode == meshtastic_Config_DeviceConfig_BuzzerMode_DIRECT_MSG_ONLY) {
         return 0; // Let other handlers process the event
     }
 
diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp
index 1a9cf484b..a3dbbc853 100644
--- a/src/graphics/Screen.cpp
+++ b/src/graphics/Screen.cpp
@@ -1494,7 +1494,13 @@ int Screen::handleTextMessage(const meshtastic_MeshPacket *packet)
 #if defined(M5STACK_UNITC6L)
             screen->setOn(true);
             screen->showSimpleBanner(banner, 1500);
-            playLongBeep();
+            if (config.device.buzzer_mode != meshtastic_Config_DeviceConfig_BuzzerMode_DIRECT_MSG_ONLY ||
+                (isAlert && moduleConfig.external_notification.alert_bell_buzzer) || (!isBroadcast(packet->to) && isToUs(p))) {
+                // Beep if not in DIRECT_MSG_ONLY mode or if in DIRECT_MSG_ONLY mode and either
+                // - packet contains an alert and alert bell buzzer is enabled
+                // - packet is a non-broadcast that is addressed to this node
+                playLongBeep();
+            }
 #else
             screen->showSimpleBanner(banner, 3000);
 #endif
diff --git a/src/graphics/draw/MenuHandler.cpp b/src/graphics/draw/MenuHandler.cpp
index 43b3fb8ab..6c781e94f 100644
--- a/src/graphics/draw/MenuHandler.cpp
+++ b/src/graphics/draw/MenuHandler.cpp
@@ -911,11 +911,11 @@ void menuHandler::BluetoothToggleMenu()
 
 void menuHandler::BuzzerModeMenu()
 {
-    static const char *optionsArray[] = {"All Enabled", "Disabled", "Notifications", "System Only"};
+    static const char *optionsArray[] = {"All Enabled", "Disabled", "Notifications", "System Only", "DMs Only"};
     BannerOverlayOptions bannerOptions;
     bannerOptions.message = "Buzzer Mode";
     bannerOptions.optionsArrayPtr = optionsArray;
-    bannerOptions.optionsCount = 4;
+    bannerOptions.optionsCount = 5;
     bannerOptions.bannerCallback = [](int selected) -> void {
         config.device.buzzer_mode = (meshtastic_Config_DeviceConfig_BuzzerMode)selected;
         service->reloadConfig(SEGMENT_CONFIG);
diff --git a/src/modules/ExternalNotificationModule.cpp b/src/modules/ExternalNotificationModule.cpp
index 1279078b1..3eddcf789 100644
--- a/src/modules/ExternalNotificationModule.cpp
+++ b/src/modules/ExternalNotificationModule.cpp
@@ -533,23 +533,30 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP
 
             if (moduleConfig.external_notification.alert_message_buzzer) {
                 LOG_INFO("externalNotificationModule - Notification Module (Buzzer)");
-                isNagging = true;
-                if (!moduleConfig.external_notification.use_pwm && !moduleConfig.external_notification.use_i2s_as_buzzer) {
-                    setExternalState(2, true);
-                } else {
+                if (config.device.buzzer_mode != meshtastic_Config_DeviceConfig_BuzzerMode_DIRECT_MSG_ONLY ||
+                    (!isBroadcast(mp.to) && isToUs(&mp))) {
+                    // Buzz if buzzer mode is not in DIRECT_MSG_ONLY or is DM to us
+                    isNagging = true;
+                    if (!moduleConfig.external_notification.use_pwm && !moduleConfig.external_notification.use_i2s_as_buzzer) {
+                        setExternalState(2, true);
+                    } else {
 #ifdef HAS_I2S
-                    if (moduleConfig.external_notification.use_i2s_as_buzzer) {
-                        audioThread->beginRttl(rtttlConfig.ringtone, strlen_P(rtttlConfig.ringtone));
-                    } else
+                        if (moduleConfig.external_notification.use_i2s_as_buzzer) {
+                            audioThread->beginRttl(rtttlConfig.ringtone, strlen_P(rtttlConfig.ringtone));
+                        } else
 #endif
-                        if (moduleConfig.external_notification.use_pwm) {
-                        rtttl::begin(config.device.buzzer_gpio, rtttlConfig.ringtone);
+                            if (moduleConfig.external_notification.use_pwm) {
+                            rtttl::begin(config.device.buzzer_gpio, rtttlConfig.ringtone);
+                        }
+                    }
+                    if (moduleConfig.external_notification.nag_timeout) {
+                        nagCycleCutoff = millis() + moduleConfig.external_notification.nag_timeout * 1000;
+                    } else {
+                        nagCycleCutoff = millis() + moduleConfig.external_notification.output_ms;
                     }
-                }
-                if (moduleConfig.external_notification.nag_timeout) {
-                    nagCycleCutoff = millis() + moduleConfig.external_notification.nag_timeout * 1000;
                 } else {
-                    nagCycleCutoff = millis() + moduleConfig.external_notification.output_ms;
+                    // Don't beep if buzzer mode is "direct messages only" and it is no direct message
+                    LOG_INFO("Message buzzer was suppressed because buzzer mode DIRECT_MSG_ONLY");
                 }
             }
             setIntervalFromNow(0); // run once so we know if we should do something

From 76d480713022f7be1314b8902157fbcad94d601f Mon Sep 17 00:00:00 2001
From: Jonathan Bennett 
Date: Wed, 1 Oct 2025 21:07:30 -0500
Subject: [PATCH 396/691] Add support for the manually_verified bool in
 SharedContact (#8180)

---
 src/mesh/NodeDB.cpp | 17 ++++++++++++++---
 1 file changed, 14 insertions(+), 3 deletions(-)

diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp
index a32ee37f9..5b235485d 100644
--- a/src/mesh/NodeDB.cpp
+++ b/src/mesh/NodeDB.cpp
@@ -1597,9 +1597,18 @@ void NodeDB::updateTelemetry(uint32_t nodeId, const meshtastic_Telemetry &t, RxS
 void NodeDB::addFromContact(meshtastic_SharedContact contact)
 {
     meshtastic_NodeInfoLite *info = getOrCreateMeshNode(contact.node_num);
-    if (!info) {
+    if (!info || !contact.has_user) {
         return;
     }
+    // If the local node has this node marked as manually verified
+    // and the client does not, do not allow the client to update the
+    // saved public key.
+    if ((info->bitfield & NODEINFO_BITFIELD_IS_KEY_MANUALLY_VERIFIED_MASK) && !contact.manually_verified) {
+        if (contact.user.public_key.size != info->user.public_key.size ||
+            memcmp(contact.user.public_key.bytes, info->user.public_key.bytes, info->user.public_key.size) != 0) {
+            return;
+        }
+    }
     info->num = contact.node_num;
     info->has_user = true;
     info->user = TypeConversions::ConvertToUserLite(contact.user);
@@ -1614,10 +1623,12 @@ void NodeDB::addFromContact(meshtastic_SharedContact contact)
     } else {
         info->last_heard = getValidTime(RTCQualityNTP);
         info->is_favorite = true;
-        info->bitfield |= NODEINFO_BITFIELD_IS_KEY_MANUALLY_VERIFIED_MASK;
+        // As the clients will begin sending the contact with DMs, we want to strictly check if the node is manually verified
+        if (contact.manually_verified) {
+            info->bitfield |= NODEINFO_BITFIELD_IS_KEY_MANUALLY_VERIFIED_MASK;
+        }
         // Mark the node's key as manually verified to indicate trustworthiness.
         updateGUIforNode = info;
-        // powerFSM.trigger(EVENT_NODEDB_UPDATED); This event has been retired
         sortMeshDB();
         notifyObservers(true); // Force an update whether or not our node counts have changed
     }

From 51ad9d02442f8cae661d3f06a31eb407ee563ba5 Mon Sep 17 00:00:00 2001
From: ford-jones 
Date: Thu, 2 Oct 2025 17:02:47 +1300
Subject: [PATCH 397/691] run trunk fmt

---
 src/graphics/Screen.cpp | 19 ++++++++++---------
 1 file changed, 10 insertions(+), 9 deletions(-)

diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp
index 2aef9d6f9..4a9e98446 100644
--- a/src/graphics/Screen.cpp
+++ b/src/graphics/Screen.cpp
@@ -1499,15 +1499,16 @@ int Screen::handleTextMessage(const meshtastic_MeshPacket *packet)
                     strcpy(banner, "New Message");
                 }
 #if defined(M5STACK_UNITC6L)
-            screen->setOn(true);
-            screen->showSimpleBanner(banner, 1500);
-            if (config.device.buzzer_mode != meshtastic_Config_DeviceConfig_BuzzerMode_DIRECT_MSG_ONLY ||
-                (isAlert && moduleConfig.external_notification.alert_bell_buzzer) || (!isBroadcast(packet->to) && isToUs(p))) {
-                // Beep if not in DIRECT_MSG_ONLY mode or if in DIRECT_MSG_ONLY mode and either
-                // - packet contains an alert and alert bell buzzer is enabled
-                // - packet is a non-broadcast that is addressed to this node
-                playLongBeep();
-            }
+                screen->setOn(true);
+                screen->showSimpleBanner(banner, 1500);
+                if (config.device.buzzer_mode != meshtastic_Config_DeviceConfig_BuzzerMode_DIRECT_MSG_ONLY ||
+                    (isAlert && moduleConfig.external_notification.alert_bell_buzzer) ||
+                    (!isBroadcast(packet->to) && isToUs(p))) {
+                    // Beep if not in DIRECT_MSG_ONLY mode or if in DIRECT_MSG_ONLY mode and either
+                    // - packet contains an alert and alert bell buzzer is enabled
+                    // - packet is a non-broadcast that is addressed to this node
+                    playLongBeep();
+                }
 #else
                 screen->showSimpleBanner(banner, 3000);
 #endif

From b978c6c86c7d7303de921c01b9d948d067ba6c07 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
 <41898282+github-actions[bot]@users.noreply.github.com>
Date: Thu, 2 Oct 2025 05:15:36 -0500
Subject: [PATCH 398/691] Upgrade trunk (#8183)

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 fa6fa6d7d..ec1563931 100644
--- a/.trunk/trunk.yaml
+++ b/.trunk/trunk.yaml
@@ -9,7 +9,7 @@ plugins:
 lint:
   enabled:
     - checkov@3.2.473
-    - renovate@41.132.2
+    - renovate@41.132.5
     - prettier@3.6.2
     - trufflehog@3.90.8
     - yamllint@1.37.1

From 878ac3ec84f4779004e285c60fdbc597fdcbc207 Mon Sep 17 00:00:00 2001
From: Ben Meadors 
Date: Thu, 2 Oct 2025 06:09:52 -0500
Subject: [PATCH 399/691] Update
 variants/esp32s3/heltec_wireless_tracker_v2/variant.h

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---
 variants/esp32s3/heltec_wireless_tracker_v2/variant.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/variants/esp32s3/heltec_wireless_tracker_v2/variant.h b/variants/esp32s3/heltec_wireless_tracker_v2/variant.h
index aec31bba2..9ac064ea2 100644
--- a/variants/esp32s3/heltec_wireless_tracker_v2/variant.h
+++ b/variants/esp32s3/heltec_wireless_tracker_v2/variant.h
@@ -57,7 +57,7 @@
 #define LORA_RESET 12
 #define LORA_DIO1 14 // SX1262 IRQ
 #define LORA_DIO2 13 // SX1262 BUSY
-#define LORA_DIO3    // Not connected on PCB, but internally on the TTGO SX1262, if DIO3 is high the TXCO is enabled
+#define LORA_DIO3    // Not connected on PCB, but internally on the TTGO SX1262, if DIO3 is high the TCXO is enabled
 
 #define LORA_SCK 9
 #define LORA_MISO 11

From 305f5138348e774606e0274bc85cca7d581ea8b1 Mon Sep 17 00:00:00 2001
From: Jonathan Bennett 
Date: Thu, 2 Oct 2025 10:40:32 -0500
Subject: [PATCH 400/691] Properly set Muzi Works R1 Neo HardwareModel

---
 src/platform/nrf52/architecture.h | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/platform/nrf52/architecture.h b/src/platform/nrf52/architecture.h
index 8212bcbc3..47ec4e500 100644
--- a/src/platform/nrf52/architecture.h
+++ b/src/platform/nrf52/architecture.h
@@ -55,6 +55,8 @@
 #define HW_VENDOR meshtastic_HardwareModel_GAT562_MESH_TRIAL_TRACKER
 #elif defined(NOMADSTAR_METEOR_PRO)
 #define HW_VENDOR meshtastic_HardwareModel_NOMADSTAR_METEOR_PRO
+#elif defined(R1_NEO)
+#define HW_VENDOR meshtastic_HardwareModel_MUZI_R1_NEO
 // MAke sure all custom RAK4630 boards are defined before the generic RAK4630
 #elif defined(RAK4630)
 #define HW_VENDOR meshtastic_HardwareModel_RAK4631
@@ -98,8 +100,6 @@
 #define HW_VENDOR meshtastic_HardwareModel_SEEED_WIO_TRACKER_L1_EINK
 #elif defined(SEEED_WIO_TRACKER_L1)
 #define HW_VENDOR meshtastic_HardwareModel_SEEED_WIO_TRACKER_L1
-#elif defined(R1_NEO)
-#define HW_VENDOR meshtastic_HardwareModel_MUZI_R1_NEO
 #elif defined(HELTEC_MESH_SOLAR)
 #define HW_VENDOR meshtastic_HardwareModel_HELTEC_MESH_SOLAR
 #else

From 76c1d69560bb4c59bf865ec3aa752f6f1f07f55a Mon Sep 17 00:00:00 2001
From: ford-jones 
Date: Fri, 3 Oct 2025 15:28:08 +1300
Subject: [PATCH 401/691] Regen protos

---
 protobufs                                |  2 +-
 src/mesh/generated/meshtastic/admin.pb.h | 12 ++++--------
 2 files changed, 5 insertions(+), 9 deletions(-)

diff --git a/protobufs b/protobufs
index 60c3e6600..394268b02 160000
--- a/protobufs
+++ b/protobufs
@@ -1 +1 @@
-Subproject commit 60c3e6600a2f4e6f49e45aeb47aafd8291a0015c
+Subproject commit 394268b02ebbc7797de31b09fe72fe2a7bdbbcab
diff --git a/src/mesh/generated/meshtastic/admin.pb.h b/src/mesh/generated/meshtastic/admin.pb.h
index 7cc896292..bc0b780b9 100644
--- a/src/mesh/generated/meshtastic/admin.pb.h
+++ b/src/mesh/generated/meshtastic/admin.pb.h
@@ -132,8 +132,6 @@ typedef struct _meshtastic_SharedContact {
     meshtastic_User user;
     /* Add this contact to the blocked / ignored list */
     bool should_ignore;
-    /* Set the IS_KEY_MANUALLY_VERIFIED bit */
-    bool manually_verified;
 } meshtastic_SharedContact;
 
 /* This message is used by a client to initiate or complete a key verification */
@@ -321,13 +319,13 @@ extern "C" {
 #define meshtastic_AdminMessage_InputEvent_init_default {0, 0, 0, 0}
 #define meshtastic_HamParameters_init_default    {"", 0, 0, ""}
 #define meshtastic_NodeRemoteHardwarePinsResponse_init_default {0, {meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default}}
-#define meshtastic_SharedContact_init_default    {0, false, meshtastic_User_init_default, 0, 0}
+#define meshtastic_SharedContact_init_default    {0, false, meshtastic_User_init_default, 0}
 #define meshtastic_KeyVerificationAdmin_init_default {_meshtastic_KeyVerificationAdmin_MessageType_MIN, 0, 0, false, 0}
 #define meshtastic_AdminMessage_init_zero        {0, {0}, {0, {0}}}
 #define meshtastic_AdminMessage_InputEvent_init_zero {0, 0, 0, 0}
 #define meshtastic_HamParameters_init_zero       {"", 0, 0, ""}
 #define meshtastic_NodeRemoteHardwarePinsResponse_init_zero {0, {meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero}}
-#define meshtastic_SharedContact_init_zero       {0, false, meshtastic_User_init_zero, 0, 0}
+#define meshtastic_SharedContact_init_zero       {0, false, meshtastic_User_init_zero, 0}
 #define meshtastic_KeyVerificationAdmin_init_zero {_meshtastic_KeyVerificationAdmin_MessageType_MIN, 0, 0, false, 0}
 
 /* Field tags (for use in manual encoding/decoding) */
@@ -343,7 +341,6 @@ extern "C" {
 #define meshtastic_SharedContact_node_num_tag    1
 #define meshtastic_SharedContact_user_tag        2
 #define meshtastic_SharedContact_should_ignore_tag 3
-#define meshtastic_SharedContact_manually_verified_tag 4
 #define meshtastic_KeyVerificationAdmin_message_type_tag 1
 #define meshtastic_KeyVerificationAdmin_remote_nodenum_tag 2
 #define meshtastic_KeyVerificationAdmin_nonce_tag 3
@@ -507,8 +504,7 @@ X(a, STATIC,   REPEATED, MESSAGE,  node_remote_hardware_pins,   1)
 #define meshtastic_SharedContact_FIELDLIST(X, a) \
 X(a, STATIC,   SINGULAR, UINT32,   node_num,          1) \
 X(a, STATIC,   OPTIONAL, MESSAGE,  user,              2) \
-X(a, STATIC,   SINGULAR, BOOL,     should_ignore,     3) \
-X(a, STATIC,   SINGULAR, BOOL,     manually_verified,   4)
+X(a, STATIC,   SINGULAR, BOOL,     should_ignore,     3)
 #define meshtastic_SharedContact_CALLBACK NULL
 #define meshtastic_SharedContact_DEFAULT NULL
 #define meshtastic_SharedContact_user_MSGTYPE meshtastic_User
@@ -543,7 +539,7 @@ extern const pb_msgdesc_t meshtastic_KeyVerificationAdmin_msg;
 #define meshtastic_HamParameters_size            31
 #define meshtastic_KeyVerificationAdmin_size     25
 #define meshtastic_NodeRemoteHardwarePinsResponse_size 496
-#define meshtastic_SharedContact_size            127
+#define meshtastic_SharedContact_size            125
 
 #ifdef __cplusplus
 } /* extern "C" */

From 50cfe7c70504ca5423f38c48add1f65d324c6481 Mon Sep 17 00:00:00 2001
From: ford-jones 
Date: Fri, 3 Oct 2025 15:49:50 +1300
Subject: [PATCH 402/691] Pull latest protobufs

---
 protobufs                                |  2 +-
 src/mesh/generated/meshtastic/admin.pb.h | 12 ++++++++----
 2 files changed, 9 insertions(+), 5 deletions(-)

diff --git a/protobufs b/protobufs
index 394268b02..60c3e6600 160000
--- a/protobufs
+++ b/protobufs
@@ -1 +1 @@
-Subproject commit 394268b02ebbc7797de31b09fe72fe2a7bdbbcab
+Subproject commit 60c3e6600a2f4e6f49e45aeb47aafd8291a0015c
diff --git a/src/mesh/generated/meshtastic/admin.pb.h b/src/mesh/generated/meshtastic/admin.pb.h
index bc0b780b9..7cc896292 100644
--- a/src/mesh/generated/meshtastic/admin.pb.h
+++ b/src/mesh/generated/meshtastic/admin.pb.h
@@ -132,6 +132,8 @@ typedef struct _meshtastic_SharedContact {
     meshtastic_User user;
     /* Add this contact to the blocked / ignored list */
     bool should_ignore;
+    /* Set the IS_KEY_MANUALLY_VERIFIED bit */
+    bool manually_verified;
 } meshtastic_SharedContact;
 
 /* This message is used by a client to initiate or complete a key verification */
@@ -319,13 +321,13 @@ extern "C" {
 #define meshtastic_AdminMessage_InputEvent_init_default {0, 0, 0, 0}
 #define meshtastic_HamParameters_init_default    {"", 0, 0, ""}
 #define meshtastic_NodeRemoteHardwarePinsResponse_init_default {0, {meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default}}
-#define meshtastic_SharedContact_init_default    {0, false, meshtastic_User_init_default, 0}
+#define meshtastic_SharedContact_init_default    {0, false, meshtastic_User_init_default, 0, 0}
 #define meshtastic_KeyVerificationAdmin_init_default {_meshtastic_KeyVerificationAdmin_MessageType_MIN, 0, 0, false, 0}
 #define meshtastic_AdminMessage_init_zero        {0, {0}, {0, {0}}}
 #define meshtastic_AdminMessage_InputEvent_init_zero {0, 0, 0, 0}
 #define meshtastic_HamParameters_init_zero       {"", 0, 0, ""}
 #define meshtastic_NodeRemoteHardwarePinsResponse_init_zero {0, {meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero}}
-#define meshtastic_SharedContact_init_zero       {0, false, meshtastic_User_init_zero, 0}
+#define meshtastic_SharedContact_init_zero       {0, false, meshtastic_User_init_zero, 0, 0}
 #define meshtastic_KeyVerificationAdmin_init_zero {_meshtastic_KeyVerificationAdmin_MessageType_MIN, 0, 0, false, 0}
 
 /* Field tags (for use in manual encoding/decoding) */
@@ -341,6 +343,7 @@ extern "C" {
 #define meshtastic_SharedContact_node_num_tag    1
 #define meshtastic_SharedContact_user_tag        2
 #define meshtastic_SharedContact_should_ignore_tag 3
+#define meshtastic_SharedContact_manually_verified_tag 4
 #define meshtastic_KeyVerificationAdmin_message_type_tag 1
 #define meshtastic_KeyVerificationAdmin_remote_nodenum_tag 2
 #define meshtastic_KeyVerificationAdmin_nonce_tag 3
@@ -504,7 +507,8 @@ X(a, STATIC,   REPEATED, MESSAGE,  node_remote_hardware_pins,   1)
 #define meshtastic_SharedContact_FIELDLIST(X, a) \
 X(a, STATIC,   SINGULAR, UINT32,   node_num,          1) \
 X(a, STATIC,   OPTIONAL, MESSAGE,  user,              2) \
-X(a, STATIC,   SINGULAR, BOOL,     should_ignore,     3)
+X(a, STATIC,   SINGULAR, BOOL,     should_ignore,     3) \
+X(a, STATIC,   SINGULAR, BOOL,     manually_verified,   4)
 #define meshtastic_SharedContact_CALLBACK NULL
 #define meshtastic_SharedContact_DEFAULT NULL
 #define meshtastic_SharedContact_user_MSGTYPE meshtastic_User
@@ -539,7 +543,7 @@ extern const pb_msgdesc_t meshtastic_KeyVerificationAdmin_msg;
 #define meshtastic_HamParameters_size            31
 #define meshtastic_KeyVerificationAdmin_size     25
 #define meshtastic_NodeRemoteHardwarePinsResponse_size 496
-#define meshtastic_SharedContact_size            125
+#define meshtastic_SharedContact_size            127
 
 #ifdef __cplusplus
 } /* extern "C" */

From 0ddaf710e4f39a60aa679001aa5c2cfa592ffb8d Mon Sep 17 00:00:00 2001
From: Jonathan Bennett 
Date: Fri, 3 Oct 2025 06:33:37 -0500
Subject: [PATCH 403/691] Add FACTORY_INSTALL option to do a filesystem reset
 on first boot (#8185)

* Add FACTORY_INSTALL option to do a filesystem reset on first boot

* Check for valid file handle before using

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---
 src/mesh/NodeDB.cpp | 18 ++++++++++++++++++
 1 file changed, 18 insertions(+)

diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp
index 5b235485d..a43ef17bc 100644
--- a/src/mesh/NodeDB.cpp
+++ b/src/mesh/NodeDB.cpp
@@ -56,6 +56,10 @@
 #include 
 #endif
 
+// stringify
+#define xstr(s) str(s)
+#define str(s) #s
+
 NodeDB *nodeDB = nullptr;
 
 // we have plenty of ram so statically alloc this tempbuf (for now)
@@ -1152,6 +1156,20 @@ void NodeDB::loadFromDisk()
     spiLock->unlock();
 #endif
 #ifdef FSCom
+#ifdef FACTORY_INSTALL
+    spiLock->lock();
+    if (!FSCom.exists("/prefs/" xstr(BUILD_EPOCH))) {
+        LOG_WARN("Factory Install Reset!");
+        FSCom.format();
+        FSCom.mkdir("/prefs");
+        File f2 = FSCom.open("/prefs/" xstr(BUILD_EPOCH), FILE_O_WRITE);
+        if (f2) {
+            f2.flush();
+            f2.close();
+        }
+    }
+    spiLock->unlock();
+#endif
     spiLock->lock();
     if (FSCom.exists(legacyPrefFileName)) {
         spiLock->unlock();

From 03baad2c11865d662c2ea7e48218a8dc2ecd8824 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
 <41898282+github-actions[bot]@users.noreply.github.com>
Date: Fri, 3 Oct 2025 06:33:53 -0500
Subject: [PATCH 404/691] Update protobufs (#8191)

Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
---
 protobufs                                    | 2 +-
 src/mesh/generated/meshtastic/device_ui.pb.h | 2 ++
 2 files changed, 3 insertions(+), 1 deletion(-)

diff --git a/protobufs b/protobufs
index 60c3e6600..c1e31a965 160000
--- a/protobufs
+++ b/protobufs
@@ -1 +1 @@
-Subproject commit 60c3e6600a2f4e6f49e45aeb47aafd8291a0015c
+Subproject commit c1e31a9655e9920a8b5b8eccdf7c69ef1ae42a49
diff --git a/src/mesh/generated/meshtastic/device_ui.pb.h b/src/mesh/generated/meshtastic/device_ui.pb.h
index d9eb90773..b99fb10b9 100644
--- a/src/mesh/generated/meshtastic/device_ui.pb.h
+++ b/src/mesh/generated/meshtastic/device_ui.pb.h
@@ -68,6 +68,8 @@ typedef enum _meshtastic_Language {
     meshtastic_Language_BULGARIAN = 17,
     /* Czech */
     meshtastic_Language_CZECH = 18,
+    /* Danish */
+    meshtastic_Language_DANISH = 19,
     /* Simplified Chinese (experimental) */
     meshtastic_Language_SIMPLIFIED_CHINESE = 30,
     /* Traditional Chinese (experimental) */

From da98622f593260fa6b08775793b511c149705f33 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
 <41898282+github-actions[bot]@users.noreply.github.com>
Date: Fri, 3 Oct 2025 06:34:11 -0500
Subject: [PATCH 405/691] Upgrade trunk (#8190)

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 ec1563931..74b850b64 100644
--- a/.trunk/trunk.yaml
+++ b/.trunk/trunk.yaml
@@ -17,7 +17,7 @@ lint:
     - trivy@0.67.0
     - taplo@0.10.0
     - ruff@0.13.2
-    - isort@6.0.1
+    - isort@6.1.0
     - markdownlint@0.45.0
     - oxipng@9.1.5
     - svgo@4.0.0

From f72a4c50bdc5952182cdd763c9c7e8eee3329dc9 Mon Sep 17 00:00:00 2001
From: Austin Lane 
Date: Fri, 3 Oct 2025 17:14:00 -0400
Subject: [PATCH 406/691] Don't use IS_ONE_OF when loading Modules

---
 src/modules/Modules.cpp | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/src/modules/Modules.cpp b/src/modules/Modules.cpp
index 02962dd59..e477574dd 100644
--- a/src/modules/Modules.cpp
+++ b/src/modules/Modules.cpp
@@ -148,7 +148,8 @@ void setupModules()
     }
 #endif
 #if !MESHTASTIC_EXCLUDE_ATAK
-    if (IS_ONE_OF(config.device.role, meshtastic_Config_DeviceConfig_Role_TAK, meshtastic_Config_DeviceConfig_Role_TAK_TRACKER)) {
+    if (config.device.role == meshtastic_Config_DeviceConfig_Role_TAK ||
+        config.device.role == meshtastic_Config_DeviceConfig_Role_TAK_TRACKER) {
         atakPluginModule = new AtakPluginModule();
     }
 #endif

From 037e56b1fdf6a384d713b4b301cced43f5eed1cd Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Fri, 3 Oct 2025 16:34:19 -0500
Subject: [PATCH 407/691] Update meshtastic/device-ui digest to 505ffad (#8195)

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 f6c0f3867..b6d6733e3 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -120,7 +120,7 @@ lib_deps =
 [device-ui_base]
 lib_deps =
 	# renovate: datasource=git-refs depName=meshtastic/device-ui packageName=https://github.com/meshtastic/device-ui gitBranch=master
-	https://github.com/meshtastic/device-ui/archive/9ed5355a24059750e9b2eb5d669574d9ea42a37b.zip
+	https://github.com/meshtastic/device-ui/archive/505ffadaa7a931df5dc8153229b719a07bbb028c.zip
 
 ; Common libs for environmental measurements in telemetry module
 [environmental_base]

From 78d010fd29401938824f0eaf5a203e24156a98e1 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Fri, 3 Oct 2025 16:35:23 -0500
Subject: [PATCH 408/691] Update actions/stale action to v10.1.0 (#8196)

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 32e2c2c8b..a80619e90 100644
--- a/.github/workflows/stale_bot.yml
+++ b/.github/workflows/stale_bot.yml
@@ -17,7 +17,7 @@ jobs:
 
     steps:
       - name: Stale PR+Issues
-        uses: actions/stale@v10.0.0
+        uses: actions/stale@v10.1.0
         with:
           days-before-stale: 45
           exempt-issue-labels: pinned,3.0

From 0c2283e19eeec8a2a6c078caab3f2a6daa5cf2fc Mon Sep 17 00:00:00 2001
From: Austin 
Date: Fri, 3 Oct 2025 17:48:21 -0400
Subject: [PATCH 409/691] GAT562: Use PRIVATE_HW (fix build) (#8198)

---
 variants/nrf52840/gat562_mesh_trial_tracker/platformio.ini | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/variants/nrf52840/gat562_mesh_trial_tracker/platformio.ini b/variants/nrf52840/gat562_mesh_trial_tracker/platformio.ini
index 5c1047aae..c6a5a7399 100644
--- a/variants/nrf52840/gat562_mesh_trial_tracker/platformio.ini
+++ b/variants/nrf52840/gat562_mesh_trial_tracker/platformio.ini
@@ -6,7 +6,8 @@ board = gat562_mesh_trial_tracker
 board_check = true
 build_flags = ${nrf52840_base.build_flags}
   -I variants/nrf52840/gat562_mesh_trial_tracker
-  -D GAT562_MESH_TRIAL_TRACKER
+  ;-D GAT562_MESH_TRIAL_TRACKER
+  -D PRIVATE_HW
   -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

From b7f6a2acb6402ef8d2a9469e508ff1865eb04cf8 Mon Sep 17 00:00:00 2001
From: Austin 
Date: Fri, 3 Oct 2025 19:52:51 -0400
Subject: [PATCH 410/691] ESP32s2 doesn't implement HWCDC (#8199)

---
 src/SerialConsole.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/SerialConsole.cpp b/src/SerialConsole.cpp
index 51dbcb7be..fad0fb92f 100644
--- a/src/SerialConsole.cpp
+++ b/src/SerialConsole.cpp
@@ -86,7 +86,7 @@ int32_t SerialConsole::runOnce()
 #endif
 
     int32_t delay = runOncePart();
-#if defined(SERIAL_HAS_ON_RECEIVE)
+#if defined(SERIAL_HAS_ON_RECEIVE) || defined(CONFIG_IDF_TARGET_ESP32S2)
     return Port.available() ? delay : INT32_MAX;
 #elif defined(IS_USB_SERIAL)
     return HWCDC::isPlugged() ? delay : (1000 * 20);

From 0e38fef5bfc1ad7dadec19b5b51963b7c8475061 Mon Sep 17 00:00:00 2001
From: Ken Piper 
Date: Fri, 3 Oct 2025 18:53:18 -0500
Subject: [PATCH 411/691] Fix build script failure under certain conditions for
 devices that use UF2 binaries  (#8150)

* Validate CR and SF lora config (#8146)

* Validate CR and SF lora config

* No zero-bw

* Update src/modules/AdminModule.cpp

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Fix braces

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Quote firmware paths given to uf2conv

---------

Co-authored-by: Ben Meadors 
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---
 bin/platformio-custom.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/bin/platformio-custom.py b/bin/platformio-custom.py
index e54d1586f..4a1887d9d 100644
--- a/bin/platformio-custom.py
+++ b/bin/platformio-custom.py
@@ -86,7 +86,7 @@ if platform.name == "espressif32":
 
 if platform.name == "nordicnrf52":
     env.AddPostAction("$BUILD_DIR/${PROGNAME}.hex",
-                      env.VerboseAction(f"\"{sys.executable}\" ./bin/uf2conv.py $BUILD_DIR/firmware.hex -c -f 0xADA52840 -o $BUILD_DIR/firmware.uf2",
+                      env.VerboseAction(f"\"{sys.executable}\" ./bin/uf2conv.py \"$BUILD_DIR/firmware.hex\" -c -f 0xADA52840 -o \"$BUILD_DIR/firmware.uf2\"",
                                         "Generating UF2 file"))
 
 Import("projenv")

From e8296914a5167b3636c43d01391f19fcc654c7c1 Mon Sep 17 00:00:00 2001
From: GUVWAF <78759985+GUVWAF@users.noreply.github.com>
Date: Sat, 4 Oct 2025 12:29:25 +0200
Subject: [PATCH 412/691] Calculate airtime of transmitted and received packets
 separately (#8205)

---
 src/mesh/LR11x0Interface.h          |  2 ++
 src/mesh/RF95Interface.h            |  4 ++-
 src/mesh/RadioInterface.cpp         | 35 +++---------------------
 src/mesh/RadioInterface.h           |  9 +++----
 src/mesh/RadioLibInterface.cpp      | 37 ++++++++++++++------------
 src/mesh/RadioLibInterface.h        | 41 +++++++++++++++++++++++++++++
 src/mesh/ReliableRouter.cpp         |  2 +-
 src/mesh/SX126xInterface.h          |  2 ++
 src/mesh/SX128xInterface.h          |  2 ++
 src/platform/portduino/SimRadio.cpp | 33 ++++++++++++++++++++---
 src/platform/portduino/SimRadio.h   |  2 ++
 11 files changed, 110 insertions(+), 59 deletions(-)

diff --git a/src/mesh/LR11x0Interface.h b/src/mesh/LR11x0Interface.h
index 4829ddc1d..840184bbf 100644
--- a/src/mesh/LR11x0Interface.h
+++ b/src/mesh/LR11x0Interface.h
@@ -65,5 +65,7 @@ template  class LR11x0Interface : public RadioLibInterface
     virtual void addReceiveMetadata(meshtastic_MeshPacket *mp) override;
 
     virtual void setStandby() override;
+
+    uint32_t getPacketTime(uint32_t pl, bool received) override { return computePacketTime(lora, pl, received); }
 };
 #endif
\ No newline at end of file
diff --git a/src/mesh/RF95Interface.h b/src/mesh/RF95Interface.h
index 327e57900..ffd8ae008 100644
--- a/src/mesh/RF95Interface.h
+++ b/src/mesh/RF95Interface.h
@@ -65,8 +65,10 @@ class RF95Interface : public RadioLibInterface
      */
     virtual void configHardwareForSend() override;
 
+    uint32_t getPacketTime(uint32_t pl, bool received) override { return computePacketTime(*lora, pl, received); }
+
   private:
     /** Some boards require GPIO control of tx vs rx paths */
     void setTransmitEnable(bool txon);
 };
-#endif
\ No newline at end of file
+#endif
diff --git a/src/mesh/RadioInterface.cpp b/src/mesh/RadioInterface.cpp
index 29f91060c..88218e406 100644
--- a/src/mesh/RadioInterface.cpp
+++ b/src/mesh/RadioInterface.cpp
@@ -230,33 +230,7 @@ The band is from 902 to 928 MHz. It mentions channel number and its respective c
 separated by 2.16 MHz with respect to the adjacent channels. Channel zero starts at 903.08 MHz center frequency.
 */
 
-/**
- * Calculate airtime per
- * https://www.rs-online.com/designspark/rel-assets/ds-assets/uploads/knowledge-items/application-notes-for-the-internet-of-things/LoRa%20Design%20Guide.pdf
- * section 4
- *
- * @return num msecs for the packet
- */
-uint32_t RadioInterface::getPacketTime(uint32_t pl)
-{
-    float bandwidthHz = bw * 1000.0f;
-    bool headDisable = false; // we currently always use the header
-    float tSym = (1 << sf) / bandwidthHz;
-
-    bool lowDataOptEn = tSym > 16e-3 ? true : false; // Needed if symbol time is >16ms
-
-    float tPreamble = (preambleLength + 4.25f) * tSym;
-    float numPayloadSym =
-        8 + max(ceilf(((8.0f * pl - 4 * sf + 28 + 16 - 20 * headDisable) / (4 * (sf - 2 * lowDataOptEn))) * cr), 0.0f);
-    float tPayload = numPayloadSym * tSym;
-    float tPacket = tPreamble + tPayload;
-
-    uint32_t msecs = tPacket * 1000;
-
-    return msecs;
-}
-
-uint32_t RadioInterface::getPacketTime(const meshtastic_MeshPacket *p)
+uint32_t RadioInterface::getPacketTime(const meshtastic_MeshPacket *p, bool received)
 {
     uint32_t pl = 0;
     if (p->which_payload_variant == meshtastic_MeshPacket_encrypted_tag) {
@@ -265,7 +239,7 @@ uint32_t RadioInterface::getPacketTime(const meshtastic_MeshPacket *p)
         size_t numbytes = pb_encode_to_bytes(bytes, sizeof(bytes), &meshtastic_Data_msg, &p->decoded);
         pl = numbytes + sizeof(PacketHeader);
     }
-    return getPacketTime(pl);
+    return getPacketTime(pl, received);
 }
 
 /** The delay to use for retransmitting dropped packets */
@@ -624,8 +598,7 @@ void RadioInterface::applyModemConfig()
     saveFreq(freq + loraConfig.frequency_offset);
 
     slotTimeMsec = computeSlotTimeMsec();
-    preambleTimeMsec = getPacketTime((uint32_t)0);
-    maxPacketTimeMsec = getPacketTime(meshtastic_Constants_DATA_PAYLOAD_LEN + sizeof(PacketHeader));
+    preambleTimeMsec = preambleLength * (pow_of_2(sf) / bw);
 
     LOG_INFO("Radio freq=%.3f, config.lora.frequency_offset=%.3f", freq, loraConfig.frequency_offset);
     LOG_INFO("Set radio: region=%s, name=%s, config=%u, ch=%d, power=%d", myRegion->name, channelName, loraConfig.modem_preset,
@@ -635,7 +608,7 @@ void RadioInterface::applyModemConfig()
     LOG_INFO("numChannels: %d x %.3fkHz", numChannels, bw);
     LOG_INFO("channel_num: %d", channel_num + 1);
     LOG_INFO("frequency: %f", getFreq());
-    LOG_INFO("Slot time: %u msec", slotTimeMsec);
+    LOG_INFO("Slot time: %u msec, preamble time: %u msec", slotTimeMsec, preambleTimeMsec);
 }
 
 /** Slottime is the time to detect a transmission has started, consisting of:
diff --git a/src/mesh/RadioInterface.h b/src/mesh/RadioInterface.h
index 0c5b6cd1a..6049a11cc 100644
--- a/src/mesh/RadioInterface.h
+++ b/src/mesh/RadioInterface.h
@@ -87,9 +87,8 @@ class RadioInterface
     const uint8_t NUM_SYM_CAD = 2;       // Number of symbols used for CAD, 2 is the default since RadioLib 6.3.0 as per AN1200.48
     const uint8_t NUM_SYM_CAD_24GHZ = 4; // Number of symbols used for CAD in 2.4 GHz, 4 is recommended in AN1200.22 of SX1280
     uint32_t slotTimeMsec = computeSlotTimeMsec();
-    uint16_t preambleLength = 16;      // 8 is default, but we use longer to increase the amount of sleep time when receiving
-    uint32_t preambleTimeMsec = 165;   // calculated on startup, this is the default for LongFast
-    uint32_t maxPacketTimeMsec = 3246; // calculated on startup, this is the default for LongFast
+    uint16_t preambleLength = 16;    // 8 is default, but we use longer to increase the amount of sleep time when receiving
+    uint32_t preambleTimeMsec = 165; // calculated on startup, this is the default for LongFast
     const uint32_t PROCESSING_TIME_MSEC =
         4500;                // time to construct, process and construct a packet again (empirically determined)
     const uint8_t CWmin = 3; // minimum CWsize
@@ -202,8 +201,8 @@ class RadioInterface
      *
      * @return num msecs for the packet
      */
-    uint32_t getPacketTime(const meshtastic_MeshPacket *p);
-    uint32_t getPacketTime(uint32_t totalPacketLen);
+    uint32_t getPacketTime(const meshtastic_MeshPacket *p, bool received = false);
+    virtual uint32_t getPacketTime(uint32_t totalPacketLen, bool received = false) = 0;
 
     /**
      * Get the channel we saved.
diff --git a/src/mesh/RadioLibInterface.cpp b/src/mesh/RadioLibInterface.cpp
index 3717e8780..4a18d4139 100644
--- a/src/mesh/RadioLibInterface.cpp
+++ b/src/mesh/RadioLibInterface.cpp
@@ -116,16 +116,21 @@ bool RadioLibInterface::receiveDetected(uint16_t irq, ulong syncWordHeaderValidF
     if (detected) {
         if (!activeReceiveStart) {
             activeReceiveStart = millis();
-        } else if (!Throttle::isWithinTimespanMs(activeReceiveStart, 2 * preambleTimeMsec) && !(irq & syncWordHeaderValidFlag)) {
-            // The HEADER_VALID flag should be set by now if it was really a packet, so ignore PREAMBLE_DETECTED flag
-            activeReceiveStart = 0;
-            LOG_DEBUG("Ignore false preamble detection");
-            return false;
-        } else if (!Throttle::isWithinTimespanMs(activeReceiveStart, maxPacketTimeMsec)) {
-            // We should have gotten an RX_DONE IRQ by now if it was really a packet, so ignore HEADER_VALID flag
-            activeReceiveStart = 0;
-            LOG_DEBUG("Ignore false header detection");
-            return false;
+        } else if (!Throttle::isWithinTimespanMs(activeReceiveStart, 2 * preambleTimeMsec)) {
+            if (!(irq & syncWordHeaderValidFlag)) {
+                // The HEADER_VALID flag should be set by now if it was really a packet, so ignore PREAMBLE_DETECTED flag
+                activeReceiveStart = 0;
+                LOG_DEBUG("Ignore false preamble detection");
+                return false;
+            } else {
+                uint32_t maxPacketTimeMsec = getPacketTime(meshtastic_Constants_DATA_PAYLOAD_LEN + sizeof(PacketHeader));
+                if (!Throttle::isWithinTimespanMs(activeReceiveStart, maxPacketTimeMsec)) {
+                    // We should have gotten an RX_DONE IRQ by now if it was really a packet, so ignore HEADER_VALID flag
+                    activeReceiveStart = 0;
+                    LOG_DEBUG("Ignore false header detection");
+                    return false;
+                }
+            }
         }
     }
     return detected;
@@ -411,8 +416,6 @@ void RadioLibInterface::completeSending()
 
 void RadioLibInterface::handleReceiveInterrupt()
 {
-    uint32_t xmitMsec;
-
     // when this is called, we should be in receive mode - if we are not, just jump out instead of bombing. Possible Race
     // Condition?
     if (!isReceiving) {
@@ -425,12 +428,12 @@ void RadioLibInterface::handleReceiveInterrupt()
     // read the number of actually received bytes
     size_t length = iface->getPacketLength();
 
-    xmitMsec = getPacketTime(length);
+    uint32_t rxMsec = getPacketTime(length, true);
 
 #ifndef DISABLE_WELCOME_UNSET
     if (config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_UNSET) {
         LOG_WARN("lora rx disabled: Region unset");
-        airTime->logAirtime(RX_ALL_LOG, xmitMsec);
+        airTime->logAirtime(RX_ALL_LOG, rxMsec);
         return;
     }
 #endif
@@ -446,7 +449,7 @@ void RadioLibInterface::handleReceiveInterrupt()
                   radioBuffer.header.to, radioBuffer.header.from, radioBuffer.header.flags);
         rxBad++;
 
-        airTime->logAirtime(RX_ALL_LOG, xmitMsec);
+        airTime->logAirtime(RX_ALL_LOG, rxMsec);
 
     } else {
         // Skip the 4 headers that are at the beginning of the rxBuf
@@ -456,7 +459,7 @@ void RadioLibInterface::handleReceiveInterrupt()
         if (payloadLen < 0) {
             LOG_WARN("Ignore received packet too short");
             rxBad++;
-            airTime->logAirtime(RX_ALL_LOG, xmitMsec);
+            airTime->logAirtime(RX_ALL_LOG, rxMsec);
         } else {
             rxGood++;
             // altered packet with "from == 0" can do Remote Node Administration without permission
@@ -494,7 +497,7 @@ void RadioLibInterface::handleReceiveInterrupt()
 
             printPacket("Lora RX", mp);
 
-            airTime->logAirtime(RX_LOG, xmitMsec);
+            airTime->logAirtime(RX_LOG, rxMsec);
 
             deliverToReceiver(mp);
         }
diff --git a/src/mesh/RadioLibInterface.h b/src/mesh/RadioLibInterface.h
index 3444b1a2c..d8f38cad3 100644
--- a/src/mesh/RadioLibInterface.h
+++ b/src/mesh/RadioLibInterface.h
@@ -61,6 +61,17 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified
     MeshPacketQueue txQueue = MeshPacketQueue(MAX_TX_QUEUE);
 
   protected:
+    ModemType_t modemType = RADIOLIB_MODEM_LORA;
+    DataRate_t getDataRate() const { return {.lora = {.spreadingFactor = sf, .bandwidth = bw, .codingRate = cr}}; }
+    PacketConfig_t getPacketConfig() const
+    {
+        return {.lora = {.preambleLength = preambleLength,
+                         .implicitHeader = false,
+                         .crcEnabled = true,
+                         // We use auto LDRO, meaning it is enabled if the symbol time is >= 16msec
+                         .ldrOptimize = (1 << sf) / bw >= 16}};
+    }
+
     /**
      * We use a meshtastic sync word, but hashed with the Channel name.  For releases before 1.2 we used 0x12 (or for very old
      * loads 0x14) Note: do not use 0x34 - that is reserved for lorawan
@@ -209,6 +220,36 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified
      */
     virtual void setStandby();
 
+    /**
+     * Derive packet time either for a received (using header info) or a transmitted packet
+     */
+    template  uint32_t computePacketTime(T &lora, uint32_t pl, bool received)
+    {
+        if (received) {
+            // First get the actual coding rate and CRC status from the received packet
+            uint8_t rxCR;
+            bool hasCRC;
+            lora.getLoRaRxHeaderInfo(&rxCR, &hasCRC);
+            // Go from raw header value to denominator
+            if (rxCR < 5) {
+                rxCR += 4;
+            } else if (rxCR == 7) {
+                rxCR = 8;
+            }
+
+            // Received packet configuration must be the same as configured, except for coding rate and CRC
+            DataRate_t dr = getDataRate();
+            dr.lora.codingRate = rxCR;
+
+            PacketConfig_t pc = getPacketConfig();
+            pc.lora.crcEnabled = hasCRC;
+
+            return lora.calculateTimeOnAir(modemType, dr, pc, pl) / 1000;
+        }
+
+        return lora.getTimeOnAir(pl) / 1000;
+    }
+
     const char *radioLibErr = "RadioLib err=";
 
     /**
diff --git a/src/mesh/ReliableRouter.cpp b/src/mesh/ReliableRouter.cpp
index b31c352fe..00066a7a3 100644
--- a/src/mesh/ReliableRouter.cpp
+++ b/src/mesh/ReliableRouter.cpp
@@ -76,7 +76,7 @@ bool ReliableRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
        If we don't add this, we will likely retransmit too early.
     */
     for (auto i = pending.begin(); i != pending.end(); i++) {
-        i->second.nextTxMsec += iface->getPacketTime(p);
+        i->second.nextTxMsec += iface->getPacketTime(p, true);
     }
 
     return isBroadcast(p->to) ? FloodingRouter::shouldFilterReceived(p) : NextHopRouter::shouldFilterReceived(p);
diff --git a/src/mesh/SX126xInterface.h b/src/mesh/SX126xInterface.h
index dc7024daa..b8f16ac6d 100644
--- a/src/mesh/SX126xInterface.h
+++ b/src/mesh/SX126xInterface.h
@@ -72,6 +72,8 @@ template  class SX126xInterface : public RadioLibInterface
 
     virtual void setStandby() override;
 
+    uint32_t getPacketTime(uint32_t pl, bool received) override { return computePacketTime(lora, pl, received); }
+
   private:
     /** Some boards require GPIO control of tx vs rx paths */
     void setTransmitEnable(bool txon);
diff --git a/src/mesh/SX128xInterface.h b/src/mesh/SX128xInterface.h
index bba31dab4..acdcbbb27 100644
--- a/src/mesh/SX128xInterface.h
+++ b/src/mesh/SX128xInterface.h
@@ -67,4 +67,6 @@ template  class SX128xInterface : public RadioLibInterface
     virtual void addReceiveMetadata(meshtastic_MeshPacket *mp) override;
 
     virtual void setStandby() override;
+
+    uint32_t getPacketTime(uint32_t pl, bool received) override { return computePacketTime(lora, pl, received); }
 };
diff --git a/src/platform/portduino/SimRadio.cpp b/src/platform/portduino/SimRadio.cpp
index cea1eab3a..0af2bf47f 100644
--- a/src/platform/portduino/SimRadio.cpp
+++ b/src/platform/portduino/SimRadio.cpp
@@ -182,7 +182,7 @@ void SimRadio::onNotify(uint32_t notification)
                     assert(txp);
                     startSend(txp);
                     // Packet has been sent, count it toward our TX airtime utilization.
-                    uint32_t xmitMsec = getPacketTime(txp);
+                    uint32_t xmitMsec = RadioInterface::getPacketTime(txp);
                     airTime->logAirtime(TX_LOG, xmitMsec);
 
                     notifyLater(xmitMsec, ISR_TX, false); // Model the time it is busy sending
@@ -252,7 +252,7 @@ void SimRadio::startReceive(meshtastic_MeshPacket *p)
     if (isActivelyReceiving()) {
         LOG_WARN("Collision detected, dropping current and previous packet!");
         rxBad++;
-        airTime->logAirtime(RX_ALL_LOG, getPacketTime(receivingPacket));
+        airTime->logAirtime(RX_ALL_LOG, getPacketTime(receivingPacket, true));
         packetPool.release(receivingPacket);
         receivingPacket = nullptr;
         return;
@@ -270,7 +270,7 @@ void SimRadio::startReceive(meshtastic_MeshPacket *p)
     }
     isReceiving = true;
     receivingPacket = packetPool.allocCopy(*p);
-    uint32_t airtimeMsec = getPacketTime(p);
+    uint32_t airtimeMsec = getPacketTime(p, true);
     notifyLater(airtimeMsec, ISR_RX, false); // Model the time it is busy receiving
 #else
     isReceiving = true;
@@ -311,7 +311,7 @@ void SimRadio::handleReceiveInterrupt()
 
     printPacket("Lora RX", mp);
 
-    airTime->logAirtime(RX_LOG, getPacketTime(mp));
+    airTime->logAirtime(RX_LOG, RadioInterface::getPacketTime(mp, true));
 
     deliverToReceiver(mp);
 }
@@ -332,4 +332,29 @@ int16_t SimRadio::readData(uint8_t *data, size_t len)
     }
 
     return state;
+}
+
+/**
+ * Calculate airtime per
+ * https://www.rs-online.com/designspark/rel-assets/ds-assets/uploads/knowledge-items/application-notes-for-the-internet-of-things/LoRa%20Design%20Guide.pdf
+ * section 4
+ *
+ * @return num msecs for the packet
+ */
+uint32_t SimRadio::getPacketTime(uint32_t pl, bool received)
+{
+    float bandwidthHz = bw * 1000.0f;
+    bool headDisable = false; // we currently always use the header
+    float tSym = (1 << sf) / bandwidthHz;
+
+    bool lowDataOptEn = tSym > 16e-3 ? true : false; // Needed if symbol time is >16ms
+
+    float tPreamble = (preambleLength + 4.25f) * tSym;
+    float numPayloadSym =
+        8 + max(ceilf(((8.0f * pl - 4 * sf + 28 + 16 - 20 * headDisable) / (4 * (sf - 2 * lowDataOptEn))) * cr), 0.0f);
+    float tPayload = numPayloadSym * tSym;
+    float tPacket = tPreamble + tPayload;
+
+    uint32_t msecs = tPacket * 1000;
+    return msecs;
 }
\ No newline at end of file
diff --git a/src/platform/portduino/SimRadio.h b/src/platform/portduino/SimRadio.h
index d8b53739f..d87e6be75 100644
--- a/src/platform/portduino/SimRadio.h
+++ b/src/platform/portduino/SimRadio.h
@@ -88,6 +88,8 @@ class SimRadio : public RadioInterface, protected concurrency::NotifiedWorkerThr
     /**
      * If a send was in progress finish it and return the buffer to the pool */
     void completeSending();
+
+    virtual uint32_t getPacketTime(uint32_t pl, bool received = false) override;
 };
 
 extern SimRadio *simRadio;
\ No newline at end of file

From 1b97cf57ad4b1c384edbb9f139764a63c32be9d3 Mon Sep 17 00:00:00 2001
From: Szetya 
Date: Sat, 4 Oct 2025 12:44:47 +0200
Subject: [PATCH 413/691] Correcting GPS PINs (#8087)

https://github.com/meshtastic/firmware/issues/8084

Co-authored-by: Ben Meadors 
---
 variants/nrf52840/t-echo-lite/variant.h | 13 +++++++------
 1 file changed, 7 insertions(+), 6 deletions(-)

diff --git a/variants/nrf52840/t-echo-lite/variant.h b/variants/nrf52840/t-echo-lite/variant.h
index 2e2cdce72..34dcee40c 100644
--- a/variants/nrf52840/t-echo-lite/variant.h
+++ b/variants/nrf52840/t-echo-lite/variant.h
@@ -140,11 +140,12 @@ static const uint8_t A0 = PIN_A0;
 #define HAS_GPS 1
 // #define PIN_GPS_REINIT (32 + 5) // An output to reset L76K GPS. As per datasheet, low for > 100ms will reset the L76K
 
-#define PIN_GPS_STANDBY (32 + 10) // An output to wake GPS, low means allow sleep, high means force wake
-// Seems to be missing on this new board
-#define PIN_GPS_PPS (0 + 29) // Pulse per second input from the GPS
-#define GPS_TX_PIN (32 + 15) // This is for bits going TOWARDS the CPU
-#define GPS_RX_PIN (32 + 13) // This is for bits going TOWARDS the GPS
+#define PIN_GPS_EN (32 + 11) // GPS power
+#define GPS_EN_ACTIVE 1
+#define PIN_GPS_STANDBY (32 + 13) // wakeup pin
+#define PIN_GPS_PPS (32 + 15)
+#define GPS_TX_PIN (32 + 10) // L76K module RX PIN
+#define GPS_RX_PIN (0 + 29) // L76K module TX PIN
 
 #define GPS_THREAD_INTERVAL 50
 
@@ -204,4 +205,4 @@ static const uint8_t A0 = PIN_A0;
  *        Arduino objects - C++ only
  *----------------------------------------------------------------------------*/
 
-#endif
\ No newline at end of file
+#endif

From ed32650b9b162dcc5c8f98f93c2ea819381ed6d0 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
 <41898282+github-actions[bot]@users.noreply.github.com>
Date: Sat, 4 Oct 2025 05:52:04 -0500
Subject: [PATCH 414/691] Update protobufs (#8206)

Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
---
 protobufs                                    |  2 +-
 src/mesh/generated/meshtastic/telemetry.pb.h | 12 ++++++++----
 2 files changed, 9 insertions(+), 5 deletions(-)

diff --git a/protobufs b/protobufs
index c1e31a965..a1b8c3d17 160000
--- a/protobufs
+++ b/protobufs
@@ -1 +1 @@
-Subproject commit c1e31a9655e9920a8b5b8eccdf7c69ef1ae42a49
+Subproject commit a1b8c3d171445b2eebfd4b5bd1e4876f3bbed605
diff --git a/src/mesh/generated/meshtastic/telemetry.pb.h b/src/mesh/generated/meshtastic/telemetry.pb.h
index 9af095e78..a8ca96e95 100644
--- a/src/mesh/generated/meshtastic/telemetry.pb.h
+++ b/src/mesh/generated/meshtastic/telemetry.pb.h
@@ -357,6 +357,8 @@ typedef struct _meshtastic_LocalStats {
     uint32_t heap_total_bytes;
     /* Number of bytes free in the heap */
     uint32_t heap_free_bytes;
+    /* Number of packets that were dropped because the transmit queue was full. */
+    uint16_t num_tx_dropped;
 } meshtastic_LocalStats;
 
 /* Health telemetry metrics */
@@ -454,7 +456,7 @@ extern "C" {
 #define meshtastic_EnvironmentMetrics_init_default {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0}
 #define meshtastic_PowerMetrics_init_default     {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0}
 #define meshtastic_AirQualityMetrics_init_default {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0}
-#define meshtastic_LocalStats_init_default       {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
+#define meshtastic_LocalStats_init_default       {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
 #define meshtastic_HealthMetrics_init_default    {false, 0, false, 0, false, 0}
 #define meshtastic_HostMetrics_init_default      {0, 0, 0, false, 0, false, 0, 0, 0, 0, false, ""}
 #define meshtastic_Telemetry_init_default        {0, 0, {meshtastic_DeviceMetrics_init_default}}
@@ -463,7 +465,7 @@ extern "C" {
 #define meshtastic_EnvironmentMetrics_init_zero  {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0}
 #define meshtastic_PowerMetrics_init_zero        {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0}
 #define meshtastic_AirQualityMetrics_init_zero   {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0}
-#define meshtastic_LocalStats_init_zero          {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
+#define meshtastic_LocalStats_init_zero          {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
 #define meshtastic_HealthMetrics_init_zero       {false, 0, false, 0, false, 0}
 #define meshtastic_HostMetrics_init_zero         {0, 0, 0, false, 0, false, 0, 0, 0, 0, false, ""}
 #define meshtastic_Telemetry_init_zero           {0, 0, {meshtastic_DeviceMetrics_init_zero}}
@@ -551,6 +553,7 @@ extern "C" {
 #define meshtastic_LocalStats_num_tx_relay_canceled_tag 11
 #define meshtastic_LocalStats_heap_total_bytes_tag 12
 #define meshtastic_LocalStats_heap_free_bytes_tag 13
+#define meshtastic_LocalStats_num_tx_dropped_tag 14
 #define meshtastic_HealthMetrics_heart_bpm_tag   1
 #define meshtastic_HealthMetrics_spO2_tag        2
 #define meshtastic_HealthMetrics_temperature_tag 3
@@ -672,7 +675,8 @@ X(a, STATIC,   SINGULAR, UINT32,   num_rx_dupe,       9) \
 X(a, STATIC,   SINGULAR, UINT32,   num_tx_relay,     10) \
 X(a, STATIC,   SINGULAR, UINT32,   num_tx_relay_canceled,  11) \
 X(a, STATIC,   SINGULAR, UINT32,   heap_total_bytes,  12) \
-X(a, STATIC,   SINGULAR, UINT32,   heap_free_bytes,  13)
+X(a, STATIC,   SINGULAR, UINT32,   heap_free_bytes,  13) \
+X(a, STATIC,   SINGULAR, UINT32,   num_tx_dropped,   14)
 #define meshtastic_LocalStats_CALLBACK NULL
 #define meshtastic_LocalStats_DEFAULT NULL
 
@@ -749,7 +753,7 @@ extern const pb_msgdesc_t meshtastic_Nau7802Config_msg;
 #define meshtastic_EnvironmentMetrics_size       113
 #define meshtastic_HealthMetrics_size            11
 #define meshtastic_HostMetrics_size              264
-#define meshtastic_LocalStats_size               72
+#define meshtastic_LocalStats_size               76
 #define meshtastic_Nau7802Config_size            16
 #define meshtastic_PowerMetrics_size             81
 #define meshtastic_Telemetry_size                272

From 7c5e2bc95acf81a0997169e7a4243d2a0af963e7 Mon Sep 17 00:00:00 2001
From: Ben Meadors 
Date: Sat, 4 Oct 2025 06:42:36 -0500
Subject: [PATCH 415/691] Clear out user.id except for sending to phone (#8202)

* Null out user.id except for sending to phone

* Fix

* Update src/modules/NodeInfoModule.cpp

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Copilot garbage

* This is unnecessary, because we don't stored user.id on userlite

* Don't need this

* Fix warning

* Just alter the protobuf

* Alter protobuf doesn't do anything with the altered data, so let's re-encode it

* Check inputbroker before access

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---
 src/main.cpp                   |  3 ++-
 src/mesh/NodeDB.cpp            |  9 +++++----
 src/modules/AdminModule.cpp    |  6 ++----
 src/modules/NodeInfoModule.cpp | 24 ++++++++++++++++++++++--
 src/modules/NodeInfoModule.h   |  3 +++
 5 files changed, 34 insertions(+), 11 deletions(-)

diff --git a/src/main.cpp b/src/main.cpp
index bca2da2cb..b0f086f14 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -1601,7 +1601,8 @@ void loop()
 
     service->loop();
 #if !MESHTASTIC_EXCLUDE_INPUTBROKER && defined(HAS_FREE_RTOS)
-    inputBroker->processInputEventQueue();
+    if (inputBroker)
+        inputBroker->processInputEventQueue();
 #endif
 #if defined(LGFX_SDL)
     if (screen) {
diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp
index a43ef17bc..e3240462d 100644
--- a/src/mesh/NodeDB.cpp
+++ b/src/mesh/NodeDB.cpp
@@ -56,10 +56,6 @@
 #include 
 #endif
 
-// stringify
-#define xstr(s) str(s)
-#define str(s) #s
-
 NodeDB *nodeDB = nullptr;
 
 // we have plenty of ram so statically alloc this tempbuf (for now)
@@ -260,6 +256,8 @@ NodeDB::NodeDB()
     owner.role = config.device.role;
     // Ensure macaddr is set to our macaddr as it will be copied in our info below
     memcpy(owner.macaddr, ourMacAddr, sizeof(owner.macaddr));
+    // Ensure owner.id is always derived from the node number
+    snprintf(owner.id, sizeof(owner.id), "!%08x", getNodeNum());
 
     if (!config.has_security) {
         config.has_security = true;
@@ -1695,6 +1693,9 @@ bool NodeDB::updateUser(uint32_t nodeId, meshtastic_User &p, uint8_t channelInde
     }
 #endif
 
+    // Always ensure user.id is derived from nodeId, regardless of what was received
+    snprintf(p.id, sizeof(p.id), "!%08x", nodeId);
+
     // Both of info->user and p start as filled with zero so I think this is okay
     auto lite = TypeConversions::ConvertToUserLite(p);
     bool changed = memcmp(&info->user, &lite, sizeof(info->user)) || (info->channel != channelIndex);
diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp
index 353db1a11..d300ff53b 100644
--- a/src/modules/AdminModule.cpp
+++ b/src/modules/AdminModule.cpp
@@ -554,10 +554,8 @@ void AdminModule::handleSetOwner(const meshtastic_User &o)
         changed |= strcmp(owner.short_name, o.short_name);
         strncpy(owner.short_name, o.short_name, sizeof(owner.short_name));
     }
-    if (*o.id) {
-        changed |= strcmp(owner.id, o.id);
-        strncpy(owner.id, o.id, sizeof(owner.id));
-    }
+    snprintf(owner.id, sizeof(owner.id), "!%08x", nodeDB->getNodeNum());
+
     if (owner.is_licensed != o.is_licensed) {
         changed = 1;
         owner.is_licensed = o.is_licensed;
diff --git a/src/modules/NodeInfoModule.cpp b/src/modules/NodeInfoModule.cpp
index 2c3c274d2..9b94b3933 100644
--- a/src/modules/NodeInfoModule.cpp
+++ b/src/modules/NodeInfoModule.cpp
@@ -30,14 +30,32 @@ bool NodeInfoModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, mes
 
     bool wasBroadcast = isBroadcast(mp.to);
 
+    // LOG_DEBUG("did encode");
     // if user has changed while packet was not for us, inform phone
-    if (hasChanged && !wasBroadcast && !isToUs(&mp))
-        service->sendToPhone(packetPool.allocCopy(mp));
+    if (hasChanged && !wasBroadcast && !isToUs(&mp)) {
+        auto packetCopy = packetPool.allocCopy(mp); // Keep a copy of the packet for later analysis
+
+        // Re-encode the user protobuf, as we have stripped out the user.id
+        packetCopy->decoded.payload.size = pb_encode_to_bytes(
+            packetCopy->decoded.payload.bytes, sizeof(packetCopy->decoded.payload.bytes), &meshtastic_User_msg, &p);
+
+        service->sendToPhone(packetCopy);
+    }
 
     // LOG_DEBUG("did handleReceived");
     return false; // Let others look at this message also if they want
 }
 
+void NodeInfoModule::alterReceivedProtobuf(meshtastic_MeshPacket &mp, meshtastic_User *p)
+{
+    // Coerce user.id to be derived from the node number
+    snprintf(p->id, sizeof(p->id), "!%08x", getFrom(&mp));
+
+    // Re-encode the altered protobuf back into the packet
+    mp.decoded.payload.size =
+        pb_encode_to_bytes(mp.decoded.payload.bytes, sizeof(mp.decoded.payload.bytes), &meshtastic_User_msg, p);
+}
+
 void NodeInfoModule::sendOurNodeInfo(NodeNum dest, bool wantReplies, uint8_t channel, bool _shorterTimeout)
 {
     // cancel any not yet sent (now stale) position packets
@@ -95,6 +113,8 @@ meshtastic_MeshPacket *NodeInfoModule::allocReply()
             u.public_key.size = 0;
         }
 
+        // Clear the user.id field since it should be derived from node number on the receiving end
+        u.id[0] = '\0';
         LOG_INFO("Send owner %s/%s/%s", u.id, u.long_name, u.short_name);
         lastSentToMesh = millis();
         return allocDataProtobuf(u);
diff --git a/src/modules/NodeInfoModule.h b/src/modules/NodeInfoModule.h
index c1fb9ccce..572b81700 100644
--- a/src/modules/NodeInfoModule.h
+++ b/src/modules/NodeInfoModule.h
@@ -30,6 +30,9 @@ class NodeInfoModule : public ProtobufModule, private concurren
     */
     virtual bool handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_User *p) override;
 
+    /** Called to alter received User protobuf */
+    virtual void alterReceivedProtobuf(meshtastic_MeshPacket &mp, meshtastic_User *p) override;
+
     /** Messages can be received that have the want_response bit set.  If set, this callback will be invoked
      * so that subclasses can (optionally) send a response back to the original sender.  */
     virtual meshtastic_MeshPacket *allocReply() override;

From 888692a3735b10225bc347eeb89ecd178a22f9b4 Mon Sep 17 00:00:00 2001
From: GUVWAF <78759985+GUVWAF@users.noreply.github.com>
Date: Sat, 4 Oct 2025 15:13:58 +0200
Subject: [PATCH 416/691] Add dropped packet count to LocalStats (#8207)

* Add dropped packet count to LocalStats
In case the transmit queue was full

* Trunked

---------

Co-authored-by: Ben Meadors 
---
 src/mesh/MeshPacketQueue.cpp              |  9 ++++++++-
 src/mesh/MeshPacketQueue.h                |  6 ++++--
 src/mesh/RadioLibInterface.cpp            | 13 +++++++++++--
 src/mesh/RadioLibInterface.h              |  1 +
 src/modules/Telemetry/DeviceTelemetry.cpp |  2 ++
 src/platform/portduino/SimRadio.cpp       |  7 ++++++-
 src/platform/portduino/SimRadio.h         |  1 +
 variants/nrf52840/t-echo-lite/variant.h   |  2 +-
 8 files changed, 34 insertions(+), 7 deletions(-)

diff --git a/src/mesh/MeshPacketQueue.cpp b/src/mesh/MeshPacketQueue.cpp
index ef5380eb8..cbea85c62 100644
--- a/src/mesh/MeshPacketQueue.cpp
+++ b/src/mesh/MeshPacketQueue.cpp
@@ -65,7 +65,7 @@ void fixPriority(meshtastic_MeshPacket *p)
 }
 
 /** enqueue a packet, return false if full */
-bool MeshPacketQueue::enqueue(meshtastic_MeshPacket *p)
+bool MeshPacketQueue::enqueue(meshtastic_MeshPacket *p, bool *dropped)
 {
     // no space - try to replace a lower priority packet in the queue
     if (queue.size() >= maxLen) {
@@ -73,9 +73,16 @@ bool MeshPacketQueue::enqueue(meshtastic_MeshPacket *p)
         if (!replaced) {
             LOG_WARN("TX queue is full, and there is no lower-priority packet available to evict in favour of 0x%08x", p->id);
         }
+        if (dropped) {
+            *dropped = true;
+        }
         return replaced;
     }
 
+    if (dropped) {
+        *dropped = false;
+    }
+
     // Find the correct position using upper_bound to maintain a stable order
     auto it = std::upper_bound(queue.begin(), queue.end(), p, CompareMeshPacketFunc);
     queue.insert(it, p); // Insert packet at the found position
diff --git a/src/mesh/MeshPacketQueue.h b/src/mesh/MeshPacketQueue.h
index ea52eb5bf..3d3902c1e 100644
--- a/src/mesh/MeshPacketQueue.h
+++ b/src/mesh/MeshPacketQueue.h
@@ -19,8 +19,10 @@ class MeshPacketQueue
   public:
     explicit MeshPacketQueue(size_t _maxLen);
 
-    /** enqueue a packet, return false if full */
-    bool enqueue(meshtastic_MeshPacket *p);
+    /** enqueue a packet, return false if full
+     * @param dropped Optional pointer to a bool that will be set to true if a packet was dropped
+     */
+    bool enqueue(meshtastic_MeshPacket *p, bool *dropped = nullptr);
 
     /** return true if the queue is empty */
     bool empty();
diff --git a/src/mesh/RadioLibInterface.cpp b/src/mesh/RadioLibInterface.cpp
index 4a18d4139..2567d9e7f 100644
--- a/src/mesh/RadioLibInterface.cpp
+++ b/src/mesh/RadioLibInterface.cpp
@@ -177,7 +177,12 @@ ErrorCode RadioLibInterface::send(meshtastic_MeshPacket *p)
     printPacket("enqueue for send", p);
 
     LOG_DEBUG("txGood=%d,txRelay=%d,rxGood=%d,rxBad=%d", txGood, txRelay, rxGood, rxBad);
-    ErrorCode res = txQueue.enqueue(p) ? ERRNO_OK : ERRNO_UNKNOWN;
+    bool dropped = false;
+    ErrorCode res = txQueue.enqueue(p, &dropped) ? ERRNO_OK : ERRNO_UNKNOWN;
+
+    if (dropped) {
+        txDrop++;
+    }
 
     if (res != ERRNO_OK) { // we weren't able to queue it, so we must drop it to prevent leaks
         packetPool.release(p);
@@ -359,11 +364,15 @@ void RadioLibInterface::clampToLateRebroadcastWindow(NodeNum from, PacketId id)
     meshtastic_MeshPacket *p = txQueue.remove(from, id, true, false);
     if (p) {
         p->tx_after = millis() + getTxDelayMsecWeightedWorst(p->rx_snr);
-        if (txQueue.enqueue(p)) {
+        bool dropped = false;
+        if (txQueue.enqueue(p, &dropped)) {
             LOG_DEBUG("Move existing queued packet to the late rebroadcast window %dms from now", p->tx_after - millis());
         } else {
             packetPool.release(p);
         }
+        if (dropped) {
+            txDrop++;
+        }
     }
 }
 
diff --git a/src/mesh/RadioLibInterface.h b/src/mesh/RadioLibInterface.h
index d8f38cad3..833c88710 100644
--- a/src/mesh/RadioLibInterface.h
+++ b/src/mesh/RadioLibInterface.h
@@ -116,6 +116,7 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified
      * Debugging counts
      */
     uint32_t rxBad = 0, rxGood = 0, txGood = 0, txRelay = 0;
+    uint16_t txDrop = 0;
 
   public:
     RadioLibInterface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst,
diff --git a/src/modules/Telemetry/DeviceTelemetry.cpp b/src/modules/Telemetry/DeviceTelemetry.cpp
index ad148b759..7e3018564 100644
--- a/src/modules/Telemetry/DeviceTelemetry.cpp
+++ b/src/modules/Telemetry/DeviceTelemetry.cpp
@@ -121,6 +121,7 @@ meshtastic_Telemetry DeviceTelemetryModule::getLocalStatsTelemetry()
         telemetry.variant.local_stats.num_packets_rx = RadioLibInterface::instance->rxGood + RadioLibInterface::instance->rxBad;
         telemetry.variant.local_stats.num_packets_rx_bad = RadioLibInterface::instance->rxBad;
         telemetry.variant.local_stats.num_tx_relay = RadioLibInterface::instance->txRelay;
+        telemetry.variant.local_stats.num_tx_dropped = RadioLibInterface::instance->txDrop;
     }
 #ifdef ARCH_PORTDUINO
     if (SimRadio::instance) {
@@ -128,6 +129,7 @@ meshtastic_Telemetry DeviceTelemetryModule::getLocalStatsTelemetry()
         telemetry.variant.local_stats.num_packets_rx = SimRadio::instance->rxGood + SimRadio::instance->rxBad;
         telemetry.variant.local_stats.num_packets_rx_bad = SimRadio::instance->rxBad;
         telemetry.variant.local_stats.num_tx_relay = SimRadio::instance->txRelay;
+        telemetry.variant.local_stats.num_tx_dropped = SimRadio::instance->txDrop;
     }
 #else
     telemetry.variant.local_stats.heap_total_bytes = memGet.getHeapSize();
diff --git a/src/platform/portduino/SimRadio.cpp b/src/platform/portduino/SimRadio.cpp
index 0af2bf47f..6e7fe24cb 100644
--- a/src/platform/portduino/SimRadio.cpp
+++ b/src/platform/portduino/SimRadio.cpp
@@ -13,7 +13,12 @@ ErrorCode SimRadio::send(meshtastic_MeshPacket *p)
 {
     printPacket("enqueuing for send", p);
 
-    ErrorCode res = txQueue.enqueue(p) ? ERRNO_OK : ERRNO_UNKNOWN;
+    bool dropped = false;
+    ErrorCode res = txQueue.enqueue(p, &dropped) ? ERRNO_OK : ERRNO_UNKNOWN;
+
+    if (dropped) {
+        txDrop++;
+    }
 
     if (res != ERRNO_OK) { // we weren't able to queue it, so we must drop it to prevent leaks
         packetPool.release(p);
diff --git a/src/platform/portduino/SimRadio.h b/src/platform/portduino/SimRadio.h
index d87e6be75..6f80989da 100644
--- a/src/platform/portduino/SimRadio.h
+++ b/src/platform/portduino/SimRadio.h
@@ -52,6 +52,7 @@ class SimRadio : public RadioInterface, protected concurrency::NotifiedWorkerThr
      * Debugging counts
      */
     uint32_t rxBad = 0, rxGood = 0, txGood = 0, txRelay = 0;
+    uint16_t txDrop = 0;
 
   protected:
     /// are _trying_ to receive a packet currently (note - we might just be waiting for one)
diff --git a/variants/nrf52840/t-echo-lite/variant.h b/variants/nrf52840/t-echo-lite/variant.h
index 34dcee40c..0748f6d48 100644
--- a/variants/nrf52840/t-echo-lite/variant.h
+++ b/variants/nrf52840/t-echo-lite/variant.h
@@ -145,7 +145,7 @@ static const uint8_t A0 = PIN_A0;
 #define PIN_GPS_STANDBY (32 + 13) // wakeup pin
 #define PIN_GPS_PPS (32 + 15)
 #define GPS_TX_PIN (32 + 10) // L76K module RX PIN
-#define GPS_RX_PIN (0 + 29) // L76K module TX PIN
+#define GPS_RX_PIN (0 + 29)  // L76K module TX PIN
 
 #define GPS_THREAD_INTERVAL 50
 

From 9ded6a52153d894880a98298ee4896d4316a2c46 Mon Sep 17 00:00:00 2001
From: Jonathan Bennett 
Date: Mon, 8 Sep 2025 18:39:36 -0500
Subject: [PATCH 417/691] Pull in panel_sdl directly and drop native-sdl target

---
 src/graphics/Panel_sdl.cpp               | 687 +++++++++++++++++++++++
 src/graphics/Panel_sdl.hpp               | 165 ++++++
 src/graphics/TFTDisplay.cpp              |  32 +-
 src/main.cpp                             |   5 +-
 variants/native/portduino/platformio.ini |  20 +-
 5 files changed, 871 insertions(+), 38 deletions(-)
 create mode 100644 src/graphics/Panel_sdl.cpp
 create mode 100644 src/graphics/Panel_sdl.hpp

diff --git a/src/graphics/Panel_sdl.cpp b/src/graphics/Panel_sdl.cpp
new file mode 100644
index 000000000..bad6072f9
--- /dev/null
+++ b/src/graphics/Panel_sdl.cpp
@@ -0,0 +1,687 @@
+/*----------------------------------------------------------------------------/
+  Lovyan GFX - Graphics library for embedded devices.
+
+Original Source:
+ https://github.com/lovyan03/LovyanGFX/
+
+Licence:
+ [FreeBSD](https://github.com/lovyan03/LovyanGFX/blob/master/license.txt)
+
+Author:
+ [lovyan03](https://twitter.com/lovyan03)
+
+Contributors:
+ [ciniml](https://github.com/ciniml)
+ [mongonta0716](https://github.com/mongonta0716)
+ [tobozo](https://github.com/tobozo)
+
+Porting for SDL:
+ [imliubo](https://github.com/imliubo)
+/----------------------------------------------------------------------------*/
+#include "Panel_sdl.hpp"
+
+#if defined(SDL_h_)
+
+// #include "../common.hpp"
+// #include "../../misc/common_function.hpp"
+// #include "../../Bus.hpp"
+
+#include 
+#include 
+#include 
+#ifndef M_PI
+#define M_PI 3.14159265358979323846
+#endif
+
+namespace lgfx
+{
+inline namespace v1
+{
+SDL_Keymod Panel_sdl::_keymod = KMOD_NONE;
+static SDL_semaphore *_update_in_semaphore = nullptr;
+static SDL_semaphore *_update_out_semaphore = nullptr;
+volatile static uint32_t _in_step_exec = 0;
+volatile static uint32_t _msec_step_exec = 512;
+static bool _inited = false;
+static bool _all_close = false;
+
+volatile uint8_t Panel_sdl::_gpio_dummy_values[EMULATED_GPIO_MAX];
+
+static inline void *heap_alloc_dma(size_t length)
+{
+    return malloc(length);
+} // aligned_alloc(16, length);
+static inline void heap_free(void *buf)
+{
+    free(buf);
+}
+
+static std::list _list_monitor;
+
+static monitor_t *const getMonitorByWindowID(uint32_t windowID)
+{
+    for (auto &m : _list_monitor) {
+        if (SDL_GetWindowID(m->window) == windowID) {
+            return m;
+        }
+    }
+    return nullptr;
+}
+//----------------------------------------------------------------------------
+
+static std::vector _key_code_map;
+
+void Panel_sdl::addKeyCodeMapping(SDL_KeyCode keyCode, uint8_t gpio)
+{
+    if (gpio > EMULATED_GPIO_MAX)
+        return;
+    KeyCodeMapping_t map;
+    map.keycode = keyCode;
+    map.gpio = gpio;
+    _key_code_map.push_back(map);
+}
+
+int Panel_sdl::getKeyCodeMapping(SDL_KeyCode keyCode)
+{
+    for (const auto &i : _key_code_map) {
+        if (i.keycode == keyCode)
+            return i.gpio;
+    }
+    return -1;
+}
+
+void Panel_sdl::_event_proc(void)
+{
+    SDL_Event event;
+    while (SDL_PollEvent(&event)) {
+        if ((event.type == SDL_KEYDOWN) || (event.type == SDL_KEYUP)) {
+            auto mon = getMonitorByWindowID(event.button.windowID);
+            int gpio = -1;
+
+            /// Check key mapping
+            gpio = getKeyCodeMapping((SDL_KeyCode)event.key.keysym.sym);
+            if (gpio < 0) {
+                switch (event.key.keysym.sym) { /// M5StackのBtnA~BtnCのエミュレート;
+                // case SDLK_LEFT:  gpio = 39; break;
+                // case SDLK_DOWN:  gpio = 38; break;
+                // case SDLK_RIGHT: gpio = 37; break;
+                // case SDLK_UP:    gpio = 36; break;
+
+                /// L/Rキーで画面回転
+                case SDLK_r:
+                case SDLK_l:
+                    if (event.type == SDL_KEYDOWN && event.key.keysym.mod == _keymod) {
+                        if (mon != nullptr) {
+                            mon->frame_rotation = (mon->frame_rotation += event.key.keysym.sym == SDLK_r ? 1 : -1);
+                            int x, y, w, h;
+                            SDL_GetWindowSize(mon->window, &w, &h);
+                            SDL_GetWindowPosition(mon->window, &x, &y);
+                            SDL_SetWindowSize(mon->window, h, w);
+                            SDL_SetWindowPosition(mon->window, x + (w - h) / 2, y + (h - w) / 2);
+                            mon->panel->sdl_invalidate();
+                        }
+                    }
+                    break;
+
+                /// 1~6キーで画面拡大率変更
+                case SDLK_1:
+                case SDLK_2:
+                case SDLK_3:
+                case SDLK_4:
+                case SDLK_5:
+                case SDLK_6:
+                    if (event.type == SDL_KEYDOWN && event.key.keysym.mod == _keymod) {
+                        if (mon != nullptr) {
+                            int size = 1 + (event.key.keysym.sym - SDLK_1);
+                            _update_scaling(mon, size, size);
+                        }
+                    }
+                    break;
+                default:
+                    continue;
+                }
+            }
+
+            if (event.type == SDL_KEYDOWN) {
+                Panel_sdl::gpio_lo(gpio);
+            } else {
+                Panel_sdl::gpio_hi(gpio);
+            }
+        } else if (event.type == SDL_MOUSEBUTTONDOWN || event.type == SDL_MOUSEBUTTONUP || event.type == SDL_MOUSEMOTION) {
+            auto mon = getMonitorByWindowID(event.button.windowID);
+            if (mon != nullptr) {
+                {
+                    int x, y, w, h;
+                    SDL_GetWindowSize(mon->window, &w, &h);
+                    SDL_GetMouseState(&x, &y);
+                    float sf = sinf(mon->frame_angle * M_PI / 180);
+                    float cf = cosf(mon->frame_angle * M_PI / 180);
+                    x -= w / 2.0f;
+                    y -= h / 2.0f;
+                    float nx = y * sf + x * cf;
+                    float ny = y * cf - x * sf;
+                    if (mon->frame_rotation & 1) {
+                        std::swap(w, h);
+                    }
+                    x = (nx * mon->frame_width / w) + (mon->frame_width >> 1);
+                    y = (ny * mon->frame_height / h) + (mon->frame_height >> 1);
+                    mon->touch_x = x - mon->frame_inner_x;
+                    mon->touch_y = y - mon->frame_inner_y;
+                }
+                if (event.type == SDL_MOUSEBUTTONDOWN && event.button.button == SDL_BUTTON_LEFT) {
+                    mon->touched = true;
+                }
+                if (event.type == SDL_MOUSEBUTTONUP && event.button.button == SDL_BUTTON_LEFT) {
+                    mon->touched = false;
+                }
+            }
+        } else if (event.type == SDL_WINDOWEVENT) {
+            auto monitor = getMonitorByWindowID(event.window.windowID);
+            if (monitor) {
+                if (event.window.event == SDL_WINDOWEVENT_RESIZED) {
+                    int mw, mh;
+                    SDL_GetRendererOutputSize(monitor->renderer, &mw, &mh);
+                    if (monitor->frame_rotation & 1) {
+                        std::swap(mw, mh);
+                    }
+                    monitor->scaling_x = (mw * 2 / monitor->frame_width) / 2.0f;
+                    monitor->scaling_y = (mh * 2 / monitor->frame_height) / 2.0f;
+                    monitor->panel->sdl_invalidate();
+                } else if (event.window.event == SDL_WINDOWEVENT_CLOSE) {
+                    monitor->closing = true;
+                }
+            }
+        } else if (event.type == SDL_QUIT) {
+            for (auto &m : _list_monitor) {
+                m->closing = true;
+            }
+        }
+    }
+}
+
+/// デバッガでステップ実行されていることを検出するスレッド用関数。
+static int detectDebugger(bool *running)
+{
+    uint32_t prev_ms = SDL_GetTicks();
+    do {
+        SDL_Delay(1);
+        uint32_t ms = SDL_GetTicks();
+        /// 時間間隔が広すぎる場合はステップ実行中 (ブレークポイントで止まった)と判断する。
+        /// また、解除されたと判断した後も1023msecほど状態を維持する。
+        if (ms - prev_ms > 64) {
+            _in_step_exec = _msec_step_exec;
+        } else if (_in_step_exec) {
+            --_in_step_exec;
+        }
+        prev_ms = ms;
+    } while (*running);
+    return 0;
+}
+
+void Panel_sdl::_update_proc(void)
+{
+    for (auto it = _list_monitor.begin(); it != _list_monitor.end();) {
+        if ((*it)->closing) {
+            if ((*it)->texture_frameimage) {
+                SDL_DestroyTexture((*it)->texture_frameimage);
+            }
+            SDL_DestroyTexture((*it)->texture);
+            SDL_DestroyRenderer((*it)->renderer);
+            SDL_DestroyWindow((*it)->window);
+            _list_monitor.erase(it++);
+            if (_list_monitor.empty()) {
+                _all_close = true;
+                return;
+            }
+            continue;
+        }
+        (*it)->panel->sdl_update();
+        ++it;
+    }
+}
+
+int Panel_sdl::setup(void)
+{
+    if (_inited)
+        return 1;
+    _inited = true;
+
+    /// Add default keycode mapping
+    /// M5StackのBtnA~BtnCのエミュレート;
+    addKeyCodeMapping(SDLK_LEFT, 39);
+    addKeyCodeMapping(SDLK_DOWN, 38);
+    addKeyCodeMapping(SDLK_RIGHT, 37);
+    addKeyCodeMapping(SDLK_UP, 36);
+
+    SDL_CreateThread((SDL_ThreadFunction)detectDebugger, "dbg", &_inited);
+
+    _update_in_semaphore = SDL_CreateSemaphore(0);
+    _update_out_semaphore = SDL_CreateSemaphore(0);
+    for (size_t pin = 0; pin < EMULATED_GPIO_MAX; ++pin) {
+        gpio_hi(pin);
+    }
+    /*Initialize the SDL*/
+    SDL_Init(SDL_INIT_VIDEO);
+    SDL_StartTextInput();
+
+    // SDL_SetThreadPriority(SDL_ThreadPriority::SDL_THREAD_PRIORITY_HIGH);
+    return 0;
+}
+
+int Panel_sdl::loop(void)
+{
+    if (!_inited)
+        return 1;
+
+    _event_proc();
+    SDL_SemWaitTimeout(_update_in_semaphore, 1);
+    _update_proc();
+    _event_proc();
+    if (SDL_SemValue(_update_out_semaphore) == 0) {
+        SDL_SemPost(_update_out_semaphore);
+    }
+
+    return _all_close;
+}
+
+int Panel_sdl::close(void)
+{
+    if (!_inited)
+        return 1;
+    _inited = false;
+
+    SDL_StopTextInput();
+    SDL_DestroySemaphore(_update_in_semaphore);
+    SDL_DestroySemaphore(_update_out_semaphore);
+    SDL_Quit();
+    return 0;
+}
+
+int Panel_sdl::main(int (*fn)(bool *), uint32_t msec_step_exec)
+{
+    _msec_step_exec = msec_step_exec;
+
+    /// SDLの準備
+    if (0 != Panel_sdl::setup()) {
+        return 1;
+    }
+
+    /// ユーザコード関数の動作・停止フラグ
+    bool running = true;
+
+    /// ユーザコード関数を起動する
+    auto thread = SDL_CreateThread((SDL_ThreadFunction)fn, "fn", &running);
+
+    /// 全部のウィンドウが閉じられるまでSDLのイベント・描画処理を継続
+    while (0 == Panel_sdl::loop()) {
+    };
+
+    /// ユーザコード関数を終了する
+    running = false;
+    SDL_WaitThread(thread, nullptr);
+
+    /// SDLを終了する
+    return Panel_sdl::close();
+}
+
+void Panel_sdl::setScaling(uint_fast8_t scaling_x, uint_fast8_t scaling_y)
+{
+    monitor.scaling_x = scaling_x;
+    monitor.scaling_y = scaling_y;
+}
+
+void Panel_sdl::setFrameImage(const void *frame_image, int frame_width, int frame_height, int inner_x, int inner_y)
+{
+    monitor.frame_image = frame_image;
+    monitor.frame_width = frame_width;
+    monitor.frame_height = frame_height;
+    monitor.frame_inner_x = inner_x;
+    monitor.frame_inner_y = inner_y;
+}
+
+void Panel_sdl::setFrameRotation(uint_fast16_t frame_rotation)
+{
+    monitor.frame_rotation = frame_rotation;
+    monitor.frame_angle = (monitor.frame_rotation) * 90;
+}
+
+Panel_sdl::~Panel_sdl(void)
+{
+    _list_monitor.remove(&monitor);
+    SDL_DestroyMutex(_sdl_mutex);
+}
+
+Panel_sdl::Panel_sdl(void) : Panel_FrameBufferBase()
+{
+    _sdl_mutex = SDL_CreateMutex();
+    _auto_display = true;
+    monitor.panel = this;
+}
+
+bool Panel_sdl::init(bool use_reset)
+{
+    initFrameBuffer(_cfg.panel_width * 4, _cfg.panel_height);
+    bool res = Panel_FrameBufferBase::init(use_reset);
+
+    _list_monitor.push_back(&monitor);
+
+    return res;
+}
+
+color_depth_t Panel_sdl::setColorDepth(color_depth_t depth)
+{
+    auto bits = depth & color_depth_t::bit_mask;
+    if (bits >= 16) {
+        depth = (bits > 16) ? rgb888_3Byte : rgb565_2Byte;
+    } else {
+        depth = (depth == color_depth_t::grayscale_8bit) ? grayscale_8bit : rgb332_1Byte;
+    }
+    _write_depth = depth;
+    _read_depth = depth;
+
+    return depth;
+}
+
+Panel_sdl::lock_t::lock_t(Panel_sdl *parent) : _parent{parent}
+{
+    SDL_LockMutex(parent->_sdl_mutex);
+};
+
+Panel_sdl::lock_t::~lock_t(void)
+{
+    ++_parent->_modified_counter;
+    SDL_UnlockMutex(_parent->_sdl_mutex);
+    if (SDL_SemValue(_update_in_semaphore) < 2) {
+        SDL_SemPost(_update_in_semaphore);
+        if (!_in_step_exec) {
+            SDL_SemWaitTimeout(_update_out_semaphore, 1);
+        }
+    }
+};
+
+void Panel_sdl::drawPixelPreclipped(uint_fast16_t x, uint_fast16_t y, uint32_t rawcolor)
+{
+    lock_t lock(this);
+    Panel_FrameBufferBase::drawPixelPreclipped(x, y, rawcolor);
+}
+
+void Panel_sdl::writeFillRectPreclipped(uint_fast16_t x, uint_fast16_t y, uint_fast16_t w, uint_fast16_t h, uint32_t rawcolor)
+{
+    lock_t lock(this);
+    Panel_FrameBufferBase::writeFillRectPreclipped(x, y, w, h, rawcolor);
+}
+
+void Panel_sdl::writeBlock(uint32_t rawcolor, uint32_t length)
+{
+    //    lock_t lock(this);
+    Panel_FrameBufferBase::writeBlock(rawcolor, length);
+}
+
+void Panel_sdl::writeImage(uint_fast16_t x, uint_fast16_t y, uint_fast16_t w, uint_fast16_t h, pixelcopy_t *param, bool use_dma)
+{
+    lock_t lock(this);
+    Panel_FrameBufferBase::writeImage(x, y, w, h, param, use_dma);
+}
+
+void Panel_sdl::writeImageARGB(uint_fast16_t x, uint_fast16_t y, uint_fast16_t w, uint_fast16_t h, pixelcopy_t *param)
+{
+    lock_t lock(this);
+    Panel_FrameBufferBase::writeImageARGB(x, y, w, h, param);
+}
+
+void Panel_sdl::writePixels(pixelcopy_t *param, uint32_t len, bool use_dma)
+{
+    lock_t lock(this);
+    Panel_FrameBufferBase::writePixels(param, len, use_dma);
+}
+
+void Panel_sdl::display(uint_fast16_t x, uint_fast16_t y, uint_fast16_t w, uint_fast16_t h)
+{
+    (void)x;
+    (void)y;
+    (void)w;
+    (void)h;
+    if (_in_step_exec) {
+        if (_display_counter != _modified_counter) {
+            do {
+                SDL_SemPost(_update_in_semaphore);
+                SDL_SemWaitTimeout(_update_out_semaphore, 1);
+            } while (_display_counter != _modified_counter);
+            SDL_Delay(1);
+        }
+    }
+}
+
+uint_fast8_t Panel_sdl::getTouchRaw(touch_point_t *tp, uint_fast8_t count)
+{
+    (void)count;
+    tp->x = monitor.touch_x;
+    tp->y = monitor.touch_y;
+    tp->size = monitor.touched ? 1 : 0;
+    tp->id = 0;
+    return monitor.touched;
+}
+
+void Panel_sdl::setWindowTitle(const char *title)
+{
+    _window_title = title;
+    if (monitor.window) {
+        SDL_SetWindowTitle(monitor.window, _window_title);
+    }
+}
+
+void Panel_sdl::_update_scaling(monitor_t *mon, float sx, float sy)
+{
+    mon->scaling_x = sx;
+    mon->scaling_y = sy;
+    int nw = mon->frame_width;
+    int nh = mon->frame_height;
+    if (mon->frame_rotation & 1) {
+        std::swap(nw, nh);
+    }
+
+    int x, y, w, h;
+    int rw, rh;
+    SDL_GetRendererOutputSize(mon->renderer, &rw, &rh);
+    SDL_GetWindowSize(mon->window, &w, &h);
+    nw = nw * sx * w / rw;
+    nh = nh * sy * h / rh;
+    SDL_GetWindowPosition(mon->window, &x, &y);
+    SDL_SetWindowSize(mon->window, nw, nh);
+    SDL_SetWindowPosition(mon->window, x + (w - nw) / 2, y + (h - nh) / 2);
+    mon->panel->sdl_invalidate();
+}
+
+void Panel_sdl::sdl_create(monitor_t *m)
+{
+    int flag = SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI;
+#if SDL_FULLSCREEN
+    flag |= SDL_WINDOW_FULLSCREEN;
+#endif
+
+    if (m->frame_width < _cfg.panel_width) {
+        m->frame_width = _cfg.panel_width;
+    }
+    if (m->frame_height < _cfg.panel_height) {
+        m->frame_height = _cfg.panel_height;
+    }
+
+    int window_width = m->frame_width * m->scaling_x;
+    int window_height = m->frame_height * m->scaling_y;
+    int scaling_x = m->scaling_x;
+    int scaling_y = m->scaling_y;
+    if (m->frame_rotation & 1) {
+        std::swap(window_width, window_height);
+        std::swap(scaling_x, scaling_y);
+    }
+
+    {
+        m->window = SDL_CreateWindow(_window_title, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, window_width, window_height,
+                                     flag); /*last param. SDL_WINDOW_BORDERLESS to hide borders*/
+    }
+    m->renderer = SDL_CreateRenderer(m->window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
+    m->texture =
+        SDL_CreateTexture(m->renderer, SDL_PIXELFORMAT_RGB24, SDL_TEXTUREACCESS_STREAMING, _cfg.panel_width, _cfg.panel_height);
+    SDL_SetTextureBlendMode(m->texture, SDL_BLENDMODE_NONE);
+
+    if (m->frame_image) {
+        // 枠画像用のサーフェイスを作成
+        auto sf = SDL_CreateRGBSurfaceFrom((void *)m->frame_image, m->frame_width, m->frame_height, 32, m->frame_width * 4,
+                                           0xFF000000, 0xFF0000, 0xFF00, 0xFF);
+        if (sf != nullptr) {
+            // 枠画像からテクスチャを作成
+            m->texture_frameimage = SDL_CreateTextureFromSurface(m->renderer, sf);
+            SDL_FreeSurface(sf);
+        }
+    }
+    SDL_SetTextureBlendMode(m->texture_frameimage, SDL_BLENDMODE_BLEND);
+    _update_scaling(m, scaling_x, scaling_y);
+}
+
+void Panel_sdl::sdl_update(void)
+{
+    if (monitor.renderer == nullptr) {
+        sdl_create(&monitor);
+    }
+
+    bool step_exec = _in_step_exec;
+
+    if (_texupdate_counter != _modified_counter) {
+        pixelcopy_t pc(nullptr, color_depth_t::rgb888_3Byte, _write_depth, false);
+        if (_write_depth == rgb565_2Byte) {
+            pc.fp_copy = pixelcopy_t::copy_rgb_fast;
+        } else if (_write_depth == rgb888_3Byte) {
+            pc.fp_copy = pixelcopy_t::copy_rgb_fast;
+        } else if (_write_depth == rgb332_1Byte) {
+            pc.fp_copy = pixelcopy_t::copy_rgb_fast;
+        } else if (_write_depth == grayscale_8bit) {
+            pc.fp_copy = pixelcopy_t::copy_rgb_fast;
+        }
+
+        if (0 == SDL_LockMutex(_sdl_mutex)) {
+            _texupdate_counter = _modified_counter;
+            for (int y = 0; y < _cfg.panel_height; ++y) {
+                pc.src_x32 = 0;
+                pc.src_data = _lines_buffer[y];
+                pc.fp_copy(&_texturebuf[y * _cfg.panel_width], 0, _cfg.panel_width, &pc);
+            }
+            SDL_UnlockMutex(_sdl_mutex);
+            SDL_UpdateTexture(monitor.texture, nullptr, _texturebuf, _cfg.panel_width * sizeof(rgb888_t));
+        }
+    }
+
+    int angle = monitor.frame_angle;
+    int target = (monitor.frame_rotation) * 90;
+    angle = (((target * 4) + (angle * 4) + (angle < target ? 8 : 0)) >> 3);
+
+    if (monitor.frame_angle != angle) { // 表示する向きを変える
+        monitor.frame_angle = angle;
+        sdl_invalidate();
+    } else if (monitor.frame_rotation & ~3u) {
+        monitor.frame_rotation &= 3;
+        monitor.frame_angle = (monitor.frame_rotation) * 90;
+        sdl_invalidate();
+    }
+
+    if (_invalidated || (_display_counter != _texupdate_counter)) {
+        SDL_RendererInfo info;
+        if (0 == SDL_GetRendererInfo(monitor.renderer, &info)) {
+            // ステップ実行中はVSYNCを待機しない
+            if (((bool)(info.flags & SDL_RENDERER_PRESENTVSYNC)) == step_exec) {
+                SDL_RenderSetVSync(monitor.renderer, !step_exec);
+            }
+        }
+        {
+            int red = 0;
+            int green = 0;
+            int blue = 0;
+#if defined(M5GFX_BACK_COLOR)
+            red = ((M5GFX_BACK_COLOR) >> 16) & 0xFF;
+            green = ((M5GFX_BACK_COLOR) >> 8) & 0xFF;
+            blue = ((M5GFX_BACK_COLOR)) & 0xFF;
+#endif
+            SDL_SetRenderDrawColor(monitor.renderer, red, green, blue, 0xFF);
+        }
+        SDL_RenderClear(monitor.renderer);
+        if (_invalidated) {
+            _invalidated = false;
+            int mw, mh;
+            SDL_GetRendererOutputSize(monitor.renderer, &mw, &mh);
+        }
+        render_texture(monitor.texture, monitor.frame_inner_x, monitor.frame_inner_y, _cfg.panel_width, _cfg.panel_height, angle);
+        render_texture(monitor.texture_frameimage, 0, 0, monitor.frame_width, monitor.frame_height, angle);
+        SDL_RenderPresent(monitor.renderer);
+        _display_counter = _texupdate_counter;
+        if (_invalidated) {
+            _invalidated = false;
+            SDL_SetRenderDrawColor(monitor.renderer, 0, 0, 0, 0xFF);
+            SDL_RenderClear(monitor.renderer);
+            render_texture(monitor.texture, monitor.frame_inner_x, monitor.frame_inner_y, _cfg.panel_width, _cfg.panel_height,
+                           angle);
+            render_texture(monitor.texture_frameimage, 0, 0, monitor.frame_width, monitor.frame_height, angle);
+            SDL_RenderPresent(monitor.renderer);
+        }
+    }
+}
+
+void Panel_sdl::render_texture(SDL_Texture *texture, int tx, int ty, int tw, int th, float angle)
+{
+    SDL_Point pivot;
+    pivot.x = (monitor.frame_width / 2.0f - tx) * (float)monitor.scaling_x;
+    pivot.y = (monitor.frame_height / 2.0f - ty) * (float)monitor.scaling_y;
+    SDL_Rect dstrect;
+    dstrect.w = tw * monitor.scaling_x;
+    dstrect.h = th * monitor.scaling_y;
+    int mw, mh;
+    SDL_GetRendererOutputSize(monitor.renderer, &mw, &mh);
+    dstrect.x = mw / 2.0f - pivot.x;
+    dstrect.y = mh / 2.0f - pivot.y;
+    SDL_RenderCopyEx(monitor.renderer, texture, nullptr, &dstrect, angle, &pivot, SDL_RendererFlip::SDL_FLIP_NONE);
+}
+
+bool Panel_sdl::initFrameBuffer(size_t width, size_t height)
+{
+    uint8_t **lineArray = (uint8_t **)heap_alloc_dma(height * sizeof(uint8_t *));
+    if (nullptr == lineArray) {
+        return false;
+    }
+
+    _texturebuf = (rgb888_t *)heap_alloc_dma(width * height * sizeof(rgb888_t));
+
+    /// 8byte alignment;
+    width = (width + 7) & ~7u;
+
+    _lines_buffer = lineArray;
+    memset(lineArray, 0, height * sizeof(uint8_t *));
+
+    uint8_t *framebuffer = (uint8_t *)heap_alloc_dma(width * height + 16);
+
+    auto fb = framebuffer;
+    {
+        for (size_t y = 0; y < height; ++y) {
+            lineArray[y] = fb;
+            fb += width;
+        }
+    }
+    return true;
+}
+
+void Panel_sdl::deinitFrameBuffer(void)
+{
+    auto lines = _lines_buffer;
+    _lines_buffer = nullptr;
+    if (lines != nullptr) {
+        heap_free(lines[0]);
+        heap_free(lines);
+    }
+    if (_texturebuf) {
+        heap_free(_texturebuf);
+        _texturebuf = nullptr;
+    }
+}
+
+//----------------------------------------------------------------------------
+} // namespace v1
+} // namespace lgfx
+
+#endif
diff --git a/src/graphics/Panel_sdl.hpp b/src/graphics/Panel_sdl.hpp
new file mode 100644
index 000000000..c80c27b73
--- /dev/null
+++ b/src/graphics/Panel_sdl.hpp
@@ -0,0 +1,165 @@
+/*----------------------------------------------------------------------------/
+  Lovyan GFX - Graphics library for embedded devices.
+
+Original Source:
+ https://github.com/lovyan03/LovyanGFX/
+
+Licence:
+ [FreeBSD](https://github.com/lovyan03/LovyanGFX/blob/master/license.txt)
+
+Author:
+ [lovyan03](https://twitter.com/lovyan03)
+
+Contributors:
+ [ciniml](https://github.com/ciniml)
+ [mongonta0716](https://github.com/mongonta0716)
+ [tobozo](https://github.com/tobozo)
+
+Porting for SDL:
+ [imliubo](https://github.com/imliubo)
+/----------------------------------------------------------------------------*/
+#pragma once
+
+#define SDL_MAIN_HANDLED
+#if __has_include()
+#include 
+#include 
+#elif __has_include()
+#include 
+#include 
+#endif
+
+#if defined(SDL_h_)
+#include "lgfx/v1/Touch.hpp"
+#include "lgfx/v1/misc/range.hpp"
+#include "lgfx/v1/panel/Panel_FrameBufferBase.hpp"
+#include 
+
+namespace lgfx
+{
+inline namespace v1
+{
+
+struct Panel_sdl;
+struct monitor_t {
+    SDL_Window *window = nullptr;
+    SDL_Renderer *renderer = nullptr;
+    SDL_Texture *texture = nullptr;
+    SDL_Texture *texture_frameimage = nullptr;
+    Panel_sdl *panel = nullptr;
+
+    // 外枠
+    const void *frame_image = 0;
+    uint_fast16_t frame_width = 0;
+    uint_fast16_t frame_height = 0;
+    uint_fast16_t frame_inner_x = 0;
+    uint_fast16_t frame_inner_y = 0;
+    int_fast16_t frame_rotation = 0;
+    int_fast16_t frame_angle = 0;
+
+    float scaling_x = 1;
+    float scaling_y = 1;
+    int_fast16_t touch_x, touch_y;
+    bool touched = false;
+    bool closing = false;
+};
+//----------------------------------------------------------------------------
+
+struct Touch_sdl : public ITouch {
+    bool init(void) override { return true; }
+    void wakeup(void) override {}
+    void sleep(void) override {}
+    bool isEnable(void) override { return true; };
+    uint_fast8_t getTouchRaw(touch_point_t *tp, uint_fast8_t count) override { return 0; }
+};
+
+//----------------------------------------------------------------------------
+
+struct Panel_sdl : public Panel_FrameBufferBase {
+    static constexpr size_t EMULATED_GPIO_MAX = 128;
+    static volatile uint8_t _gpio_dummy_values[EMULATED_GPIO_MAX];
+
+  public:
+    Panel_sdl(void);
+    virtual ~Panel_sdl(void);
+
+    bool init(bool use_reset) override;
+
+    color_depth_t setColorDepth(color_depth_t depth) override;
+
+    void display(uint_fast16_t x, uint_fast16_t y, uint_fast16_t w, uint_fast16_t h) override;
+
+    // void setInvert(bool invert) override {}
+    void drawPixelPreclipped(uint_fast16_t x, uint_fast16_t y, uint32_t rawcolor) override;
+    void writeFillRectPreclipped(uint_fast16_t x, uint_fast16_t y, uint_fast16_t w, uint_fast16_t h, uint32_t rawcolor) override;
+    void writeBlock(uint32_t rawcolor, uint32_t length) override;
+    void writeImage(uint_fast16_t x, uint_fast16_t y, uint_fast16_t w, uint_fast16_t h, pixelcopy_t *param,
+                    bool use_dma) override;
+    void writeImageARGB(uint_fast16_t x, uint_fast16_t y, uint_fast16_t w, uint_fast16_t h, pixelcopy_t *param) override;
+    void writePixels(pixelcopy_t *param, uint32_t len, bool use_dma) override;
+
+    uint_fast8_t getTouchRaw(touch_point_t *tp, uint_fast8_t count) override;
+
+    void setWindowTitle(const char *title);
+    void setScaling(uint_fast8_t scaling_x, uint_fast8_t scaling_y);
+    void setFrameImage(const void *frame_image, int frame_width, int frame_height, int inner_x, int inner_y);
+    void setFrameRotation(uint_fast16_t frame_rotaion);
+    void setBrightness(uint8_t brightness) override{};
+
+    static volatile void gpio_hi(uint32_t pin) { _gpio_dummy_values[pin & (EMULATED_GPIO_MAX - 1)] = 1; }
+    static volatile void gpio_lo(uint32_t pin) { _gpio_dummy_values[pin & (EMULATED_GPIO_MAX - 1)] = 0; }
+    static volatile bool gpio_in(uint32_t pin) { return _gpio_dummy_values[pin & (EMULATED_GPIO_MAX - 1)]; }
+
+    static int setup(void);
+    static int loop(void);
+    static int close(void);
+
+    static int main(int (*fn)(bool *), uint32_t msec_step_exec = 512);
+
+    static void setShortcutKeymod(SDL_Keymod keymod) { _keymod = keymod; }
+
+    struct KeyCodeMapping_t {
+        SDL_KeyCode keycode = SDLK_UNKNOWN;
+        uint8_t gpio = 0;
+    };
+    static void addKeyCodeMapping(SDL_KeyCode keyCode, uint8_t gpio);
+    static int getKeyCodeMapping(SDL_KeyCode keyCode);
+
+  protected:
+    const char *_window_title = "LGFX Simulator";
+    SDL_mutex *_sdl_mutex = nullptr;
+
+    void sdl_create(monitor_t *m);
+    void sdl_update(void);
+
+    touch_point_t _touch_point;
+    monitor_t monitor;
+
+    rgb888_t *_texturebuf = nullptr;
+    uint_fast16_t _modified_counter;
+    uint_fast16_t _texupdate_counter;
+    uint_fast16_t _display_counter;
+    bool _invalidated;
+
+    static void _event_proc(void);
+    static void _update_proc(void);
+    static void _update_scaling(monitor_t *m, float sx, float sy);
+    void sdl_invalidate(void) { _invalidated = true; }
+    void render_texture(SDL_Texture *texture, int tx, int ty, int tw, int th, float angle);
+    bool initFrameBuffer(size_t width, size_t height);
+    void deinitFrameBuffer(void);
+
+    static SDL_Keymod _keymod;
+
+    struct lock_t {
+        lock_t(Panel_sdl *parent);
+        ~lock_t();
+
+      protected:
+        Panel_sdl *_parent;
+    };
+};
+//----------------------------------------------------------------------------
+} // namespace v1
+} // namespace lgfx
+#endif
\ No newline at end of file
diff --git a/src/graphics/TFTDisplay.cpp b/src/graphics/TFTDisplay.cpp
index 3eeb17ef0..0663602d9 100644
--- a/src/graphics/TFTDisplay.cpp
+++ b/src/graphics/TFTDisplay.cpp
@@ -751,10 +751,8 @@ static LGFX *tft = nullptr;
 
 static TFT_eSPI *tft = nullptr; // Invoke library, pins defined in User_Setup.h
 #elif ARCH_PORTDUINO
+#include "Panel_sdl.hpp"
 #include  // Graphics and font library for ST7735 driver chip
-#if defined(LGFX_SDL)
-#include 
-#endif
 
 class LGFX : public lgfx::LGFX_Device
 {
@@ -783,10 +781,10 @@ class LGFX : public lgfx::LGFX_Device
             _panel_instance = new lgfx::Panel_ILI9488;
         else if (portduino_config.displayPanel == hx8357d)
             _panel_instance = new lgfx::Panel_HX8357D;
-#if defined(LGFX_SDL)
-        else if (portduino_config.displayPanel == x11) {
+#if defined(SDL_h_)
+
+        else if (portduino_config.displayPanel == x11)
             _panel_instance = new lgfx::Panel_sdl;
-        }
 #endif
         else {
             _panel_instance = new lgfx::Panel_NULL;
@@ -799,8 +797,9 @@ class LGFX : public lgfx::LGFX_Device
 
         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.
+        _bus_instance.config(buscfg); // applies the set value to the bus.
+        if (portduino_config.displayPanel != x11)
+            _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", portduino_config.displayWidth, portduino_config.displayHeight);
@@ -848,7 +847,7 @@ class LGFX : public lgfx::LGFX_Device
             _touch_instance->config(touch_cfg);
             _panel_instance->setTouch(_touch_instance);
         }
-#if defined(LGFX_SDL)
+#if defined(SDL_h_)
         if (portduino_config.displayPanel == x11) {
             lgfx::Panel_sdl *sdl_panel_ = (lgfx::Panel_sdl *)_panel_instance;
             sdl_panel_->setup();
@@ -1237,7 +1236,7 @@ void TFTDisplay::display(bool fromBlank)
 
 void TFTDisplay::sdlLoop()
 {
-#if defined(LGFX_SDL)
+#if defined(SDL_h_)
     static int lastPressed = 0;
     static int shuttingDown = false;
     if (portduino_config.displayPanel == x11) {
@@ -1247,27 +1246,26 @@ void TFTDisplay::sdlLoop()
             InputEvent event = {.inputEvent = (input_broker_event)INPUT_BROKER_SHUTDOWN, .kbchar = 0, .touchX = 0, .touchY = 0};
             inputBroker->injectInputEvent(&event);
         }
-
         // debounce
-        if (lastPressed != 0 && !lgfx::v1::gpio_in(lastPressed))
+        if (lastPressed != 0 && !sdl_panel_->gpio_in(lastPressed))
             return;
-        if (!lgfx::v1::gpio_in(37)) {
+        if (!sdl_panel_->gpio_in(37)) {
             lastPressed = 37;
             InputEvent event = {.inputEvent = (input_broker_event)INPUT_BROKER_RIGHT, .kbchar = 0, .touchX = 0, .touchY = 0};
             inputBroker->injectInputEvent(&event);
-        } else if (!lgfx::v1::gpio_in(36)) {
+        } else if (!sdl_panel_->gpio_in(36)) {
             lastPressed = 36;
             InputEvent event = {.inputEvent = (input_broker_event)INPUT_BROKER_UP, .kbchar = 0, .touchX = 0, .touchY = 0};
             inputBroker->injectInputEvent(&event);
-        } else if (!lgfx::v1::gpio_in(38)) {
+        } else if (!sdl_panel_->gpio_in(38)) {
             lastPressed = 38;
             InputEvent event = {.inputEvent = (input_broker_event)INPUT_BROKER_DOWN, .kbchar = 0, .touchX = 0, .touchY = 0};
             inputBroker->injectInputEvent(&event);
-        } else if (!lgfx::v1::gpio_in(39)) {
+        } else if (!sdl_panel_->gpio_in(39)) {
             lastPressed = 39;
             InputEvent event = {.inputEvent = (input_broker_event)INPUT_BROKER_LEFT, .kbchar = 0, .touchX = 0, .touchY = 0};
             inputBroker->injectInputEvent(&event);
-        } else if (!lgfx::v1::gpio_in(SDL_SCANCODE_KP_ENTER)) {
+        } else if (!sdl_panel_->gpio_in(SDL_SCANCODE_KP_ENTER)) {
             lastPressed = SDL_SCANCODE_KP_ENTER;
             InputEvent event = {.inputEvent = (input_broker_event)INPUT_BROKER_SELECT, .kbchar = 0, .touchX = 0, .touchY = 0};
             inputBroker->injectInputEvent(&event);
diff --git a/src/main.cpp b/src/main.cpp
index b0f086f14..029b8d708 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -1604,8 +1604,9 @@ void loop()
     if (inputBroker)
         inputBroker->processInputEventQueue();
 #endif
-#if defined(LGFX_SDL)
-    if (screen) {
+#if ARCH_PORTDUINO && HAS_TFT
+    if (screen && portduino_config.displayPanel == x11 &&
+        config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
         auto dispdev = screen->getDisplayDevice();
         if (dispdev)
             static_cast(dispdev)->sdlLoop();
diff --git a/variants/native/portduino/platformio.ini b/variants/native/portduino/platformio.ini
index c47ab8bf1..61eadb459 100644
--- a/variants/native/portduino/platformio.ini
+++ b/variants/native/portduino/platformio.ini
@@ -40,28 +40,10 @@ build_flags = ${native_base.build_flags} -Os -lX11 -linput -lxkbcommon -ffunctio
   -D VIEW_320x240
   !pkg-config --libs libulfius --silence-errors || :
   !pkg-config --libs openssl --silence-errors || :
+  !pkg-config --cflags --libs sdl2 --silence-errors || :
 build_src_filter =
   ${native_base.build_src_filter}
 
-[env:native-sdl]
-extends = native_base
-build_type = release
-lib_deps =
-  ${env.lib_deps}
-  ${networking_base.lib_deps}
-  ${radiolib_base.lib_deps}
-  ${environmental_base.lib_deps}
-  # renovate: datasource=custom.pio depName=rweather/Crypto packageName=rweather/library/Crypto
-  rweather/Crypto@0.4.0
-  # renovate: datasource=git-refs depName=libch341-spi-userspace packageName=https://github.com/pine64/libch341-spi-userspace gitBranch=main
-  https://github.com/pine64/libch341-spi-userspace/archive/af9bc27c9c30fa90772279925b7c5913dff789b4.zip
-  # renovate: datasource=custom.pio depName=adafruit/Adafruit seesaw Library packageName=adafruit/library/Adafruit seesaw Library
-	adafruit/Adafruit seesaw Library@1.7.9
-  https://github.com/jp-bennett/LovyanGFX/archive/7458f84a126c1f8fdc7b038074f71be903f6e4c0.zip
-build_flags = ${native_base.build_flags}
-  !pkg-config --cflags --libs sdl2 --silence-errors || :
-  -D LGFX_SDL=1
-
 [env:native-fb]
 extends = native_base
 build_type = release

From cbd30f95f351c2f94a35a855ed42461e583e9cb6 Mon Sep 17 00:00:00 2001
From: Jonathan Bennett 
Date: Mon, 8 Sep 2025 20:08:16 -0500
Subject: [PATCH 418/691] Portduino: Only short-circuit hardware support when
 forcing sim mode

---
 src/platform/portduino/PortduinoGlue.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/platform/portduino/PortduinoGlue.cpp b/src/platform/portduino/PortduinoGlue.cpp
index b11d2547b..dbc90f9a4 100644
--- a/src/platform/portduino/PortduinoGlue.cpp
+++ b/src/platform/portduino/PortduinoGlue.cpp
@@ -202,7 +202,7 @@ void portduinoSetup()
         exit(EXIT_SUCCESS);
     }
 
-    if (portduino_config.lora_module == use_simradio) {
+    if (portduino_config.force_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

From 6022b749bab67f23cbf75482fd0cf64ec41010d0 Mon Sep 17 00:00:00 2001
From: Jonathan Bennett 
Date: Mon, 8 Sep 2025 20:08:31 -0500
Subject: [PATCH 419/691] Don't forget to break!

---
 src/platform/portduino/PortduinoGlue.h | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/src/platform/portduino/PortduinoGlue.h b/src/platform/portduino/PortduinoGlue.h
index ec6209487..3fe017d5e 100644
--- a/src/platform/portduino/PortduinoGlue.h
+++ b/src/platform/portduino/PortduinoGlue.h
@@ -444,12 +444,16 @@ extern struct portduino_config_struct {
             switch (configDisplayMode) {
             case meshtastic_Config_DisplayConfig_DisplayMode_TWOCOLOR:
                 out << YAML::Key << "DisplayMode" << YAML::Value << "TWOCOLOR";
+                break;
             case meshtastic_Config_DisplayConfig_DisplayMode_INVERTED:
                 out << YAML::Key << "DisplayMode" << YAML::Value << "INVERTED";
+                break;
             case meshtastic_Config_DisplayConfig_DisplayMode_COLOR:
                 out << YAML::Key << "DisplayMode" << YAML::Value << "COLOR";
+                break;
             case meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT:
                 out << YAML::Key << "DisplayMode" << YAML::Value << "DEFAULT";
+                break;
             }
 
             out << YAML::EndMap; // Config

From 7c4367cddc1e68945131ffe2e2a7c6acea2a7bb1 Mon Sep 17 00:00:00 2001
From: Jonathan Bennett 
Date: Mon, 8 Sep 2025 20:32:07 -0500
Subject: [PATCH 420/691] Cppckeck suppress bogus error

---
 src/graphics/Panel_sdl.hpp | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/graphics/Panel_sdl.hpp b/src/graphics/Panel_sdl.hpp
index c80c27b73..802c6c5dc 100644
--- a/src/graphics/Panel_sdl.hpp
+++ b/src/graphics/Panel_sdl.hpp
@@ -21,6 +21,7 @@ Porting for SDL:
 #pragma once
 
 #define SDL_MAIN_HANDLED
+// cppcheck-suppress preprocessorErrorDirective
 #if __has_include()
 #include 
 #include 

From de6a02756dacf6bf6a879beffafb1de9dce6f3ef Mon Sep 17 00:00:00 2001
From: GUVWAF 
Date: Sun, 5 Oct 2025 14:03:55 +0200
Subject: [PATCH 421/691] De-duplicate handling upgraded packet and
 rebroadcasting logic

---
 src/mesh/FloodingRouter.cpp | 98 ++++++++++++++-----------------------
 src/mesh/FloodingRouter.h   | 15 ++++--
 src/mesh/NextHopRouter.cpp  | 91 ++++++++++++++++------------------
 src/mesh/NextHopRouter.h    |  6 +--
 src/mesh/PacketHistory.cpp  |  1 -
 5 files changed, 92 insertions(+), 119 deletions(-)

diff --git a/src/mesh/FloodingRouter.cpp b/src/mesh/FloodingRouter.cpp
index 1d8ac247f..1225263a2 100644
--- a/src/mesh/FloodingRouter.cpp
+++ b/src/mesh/FloodingRouter.cpp
@@ -31,33 +31,8 @@ bool FloodingRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
         wasSeenRecently(p, true, nullptr, nullptr, &wasUpgraded); // Updates history; returns false when an upgrade is detected
 
     // Handle hop_limit upgrade scenario for rebroadcasters
-    // isRebroadcaster() is duplicated in perhapsRebroadcast(), but this avoids confusing log messages
-    if (wasUpgraded && isRebroadcaster() && iface && p->hop_limit > 0) {
-        // wasSeenRecently() reports false in upgrade cases so we handle replacement before the duplicate short-circuit
-        // If we overhear a duplicate copy of the packet with more hops left than the one we are waiting to
-        // rebroadcast, then remove the packet currently sitting in the TX queue and use this one instead.
-        uint8_t dropThreshold = p->hop_limit; // remove queued packets that have fewer hops remaining
-        if (iface->removePendingTXPacket(getFrom(p), p->id, dropThreshold)) {
-            LOG_DEBUG("Processing upgraded packet 0x%08x for rebroadcast with hop limit %d (dropping queued < %d)", p->id,
-                      p->hop_limit, dropThreshold);
-
-            if (nodeDB)
-                nodeDB->updateFrom(*p);
-#if !MESHTASTIC_EXCLUDE_TRACEROUTE
-            if (traceRouteModule && p->which_payload_variant == meshtastic_MeshPacket_decoded_tag &&
-                p->decoded.portnum == meshtastic_PortNum_TRACEROUTE_APP)
-                traceRouteModule->processUpgradedPacket(*p);
-#endif
-
-            perhapsRebroadcast(p);
-
-            // We already enqueued the improved copy, so make sure the incoming packet stops here.
-            return true;
-        }
-
-        // No queue entry was replaced by this upgraded copy, so treat it as a duplicate to avoid
-        // delivering the same packet to applications/phone twice with different hop limits.
-        seenRecently = true;
+    if (wasUpgraded && perhapsHandleUpgradedPacket(p)) {
+        return true; // we handled it, so stop processing
     }
 
     if (seenRecently) {
@@ -82,6 +57,40 @@ bool FloodingRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
     return Router::shouldFilterReceived(p);
 }
 
+bool FloodingRouter::perhapsHandleUpgradedPacket(const meshtastic_MeshPacket *p)
+{
+    // isRebroadcaster() is duplicated in perhapsRebroadcast(), but this avoids confusing log messages
+    if (isRebroadcaster() && iface && p->hop_limit > 0) {
+        // If we overhear a duplicate copy of the packet with more hops left than the one we are waiting to
+        // rebroadcast, then remove the packet currently sitting in the TX queue and use this one instead.
+        uint8_t dropThreshold = p->hop_limit; // remove queued packets that have fewer hops remaining
+        if (iface->removePendingTXPacket(getFrom(p), p->id, dropThreshold)) {
+            LOG_DEBUG("Processing upgraded packet 0x%08x for rebroadcast with hop limit %d (dropping queued < %d)", p->id,
+                      p->hop_limit, dropThreshold);
+
+            reprocessPacket(p);
+            perhapsRebroadcast(p);
+
+            rxDupe++;
+            // We already enqueued the improved copy, so make sure the incoming packet stops here.
+            return true;
+        }
+    }
+
+    return false;
+}
+
+void FloodingRouter::reprocessPacket(const meshtastic_MeshPacket *p)
+{
+    if (nodeDB)
+        nodeDB->updateFrom(*p);
+#if !MESHTASTIC_EXCLUDE_TRACEROUTE
+    if (traceRouteModule && p->which_payload_variant == meshtastic_MeshPacket_decoded_tag &&
+        p->decoded.portnum == meshtastic_PortNum_TRACEROUTE_APP)
+        traceRouteModule->processUpgradedPacket(*p);
+#endif
+}
+
 bool FloodingRouter::roleAllowsCancelingDupe(const meshtastic_MeshPacket *p)
 {
     if (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER ||
@@ -121,41 +130,6 @@ bool FloodingRouter::isRebroadcaster()
            config.device.rebroadcast_mode != meshtastic_Config_DeviceConfig_RebroadcastMode_NONE;
 }
 
-void FloodingRouter::perhapsRebroadcast(const meshtastic_MeshPacket *p)
-{
-    if (!isToUs(p) && (p->hop_limit > 0) && !isFromUs(p)) {
-        if (p->id != 0) {
-            if (isRebroadcaster()) {
-                meshtastic_MeshPacket *tosend = packetPool.allocCopy(*p); // keep a copy because we will be sending it
-
-                // Use shared logic to determine if hop_limit should be decremented
-                if (shouldDecrementHopLimit(p)) {
-                    tosend->hop_limit--; // bump down the hop count
-                } else {
-                    LOG_INFO("favorite-ROUTER/CLIENT_BASE-to-ROUTER/CLIENT_BASE flood: preserving hop_limit");
-                }
-#if USERPREFS_EVENT_MODE
-                if (tosend->hop_limit > 2) {
-                    // if we are "correcting" the hop_limit, "correct" the hop_start by the same amount to preserve hops away.
-                    tosend->hop_start -= (tosend->hop_limit - 2);
-                    tosend->hop_limit = 2;
-                }
-#endif
-
-                tosend->next_hop = NO_NEXT_HOP_PREFERENCE; // this should already be the case, but just in case
-
-                LOG_INFO("Rebroadcast received floodmsg");
-                // Note: we are careful to resend using the original senders node id
-                send(tosend);
-            } else {
-                LOG_DEBUG("No rebroadcast: Role = CLIENT_MUTE or Rebroadcast Mode = NONE");
-            }
-        } else {
-            LOG_DEBUG("Ignore 0 id broadcast");
-        }
-    }
-}
-
 void FloodingRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtastic_Routing *c)
 {
     bool isAckorReply = (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) &&
diff --git a/src/mesh/FloodingRouter.h b/src/mesh/FloodingRouter.h
index eaf71d294..e8a2e9685 100644
--- a/src/mesh/FloodingRouter.h
+++ b/src/mesh/FloodingRouter.h
@@ -27,10 +27,6 @@
  */
 class FloodingRouter : public Router
 {
-  private:
-    /* Check if we should rebroadcast this packet, and do so if needed */
-    void perhapsRebroadcast(const meshtastic_MeshPacket *p);
-
   public:
     /**
      * Constructor
@@ -59,6 +55,17 @@ class FloodingRouter : public Router
      */
     virtual void sniffReceived(const meshtastic_MeshPacket *p, const meshtastic_Routing *c) override;
 
+    /* Check if we should rebroadcast this packet, and do so if needed */
+    virtual bool perhapsRebroadcast(const meshtastic_MeshPacket *p) = 0;
+
+    /* Check if we should handle an upgraded packet (with higher hop_limit)
+     * @return true if we handled it (so stop processing)
+     */
+    bool perhapsHandleUpgradedPacket(const meshtastic_MeshPacket *p);
+
+    /* Call when we receive a packet that needs some reprocessing, but afterwards should be filtered */
+    void reprocessPacket(const meshtastic_MeshPacket *p);
+
     // Return false for roles like ROUTER which should always rebroadcast even when we've heard another rebroadcast of
     // the same packet
     bool roleAllowsCancelingDupe(const meshtastic_MeshPacket *p);
diff --git a/src/mesh/NextHopRouter.cpp b/src/mesh/NextHopRouter.cpp
index 0461d7eb6..7340c0e87 100644
--- a/src/mesh/NextHopRouter.cpp
+++ b/src/mesh/NextHopRouter.cpp
@@ -43,31 +43,8 @@ bool NextHopRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
                                         &wasUpgraded); // Updates history; returns false when an upgrade is detected
 
     // Handle hop_limit upgrade scenario for rebroadcasters
-    // isRebroadcaster() is duplicated in perhapsRelay(), but this avoids confusing log messages
-    if (wasUpgraded && isRebroadcaster() && iface && p->hop_limit > 0) {
-        // Upgrade detection bypasses the duplicate short-circuit so we replace the queued packet before exiting
-        uint8_t dropThreshold = p->hop_limit; // remove queued packets that have fewer hops remaining
-        if (iface->removePendingTXPacket(getFrom(p), p->id, dropThreshold)) {
-            LOG_DEBUG("Processing upgraded packet 0x%08x for relay with hop limit %d (dropping queued < %d)", p->id, p->hop_limit,
-                      dropThreshold);
-
-            if (nodeDB)
-                nodeDB->updateFrom(*p);
-#if !MESHTASTIC_EXCLUDE_TRACEROUTE
-            if (traceRouteModule && p->which_payload_variant == meshtastic_MeshPacket_decoded_tag &&
-                p->decoded.portnum == meshtastic_PortNum_TRACEROUTE_APP)
-                traceRouteModule->processUpgradedPacket(*p);
-#endif
-
-            perhapsRelay(p);
-
-            // We already enqueued the improved copy, so make sure the incoming packet stops here.
-            return true;
-        }
-
-        // No queue entry was replaced by this upgraded copy, so treat it as a duplicate to avoid
-        // delivering the same packet to applications/phone twice with different hop limits.
-        seenRecently = true;
+    if (wasUpgraded && perhapsHandleUpgradedPacket(p)) {
+        return true; // we handled it, so stop processing
     }
 
     if (seenRecently) {
@@ -107,13 +84,14 @@ void NextHopRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtast
     bool isAckorReply = (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) &&
                         (p->decoded.request_id != 0 || p->decoded.reply_id != 0);
     if (isAckorReply) {
-        // Update next-hop for the original transmitter of this successful transmission to the relay node, but ONLY if "from" is
-        // not 0 (means implicit ACK) and original packet was also relayed by this node, or we sent it directly to the destination
+        // Update next-hop for the original transmitter of this successful transmission to the relay node, but ONLY if "from"
+        // is not 0 (means implicit ACK) and original packet was also relayed by this node, or we sent it directly to the
+        // destination
         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 *only* 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
                 bool wasAlreadyRelayer = wasRelayer(p->relay_node, p->decoded.request_id, p->to);
                 bool weWereSoleRelayer = false;
                 bool weWereRelayer = wasRelayer(ourRelayID, p->decoded.request_id, p->to, &weWereSoleRelayer);
@@ -134,34 +112,49 @@ void NextHopRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtast
         }
     }
 
-    perhapsRelay(p);
+    perhapsRebroadcast(p);
 
     // handle the packet as normal
     Router::sniffReceived(p, c);
 }
 
-/* Check if we should be relaying this packet if so, do so. */
-bool NextHopRouter::perhapsRelay(const meshtastic_MeshPacket *p)
+/* Check if we should be rebroadcasting this packet if so, do so. */
+bool NextHopRouter::perhapsRebroadcast(const meshtastic_MeshPacket *p)
 {
     if (!isToUs(p) && !isFromUs(p) && p->hop_limit > 0) {
-        if (p->next_hop == NO_NEXT_HOP_PREFERENCE || p->next_hop == nodeDB->getLastByteOfNodeNum(getNodeNum())) {
+        if (p->id != 0) {
             if (isRebroadcaster()) {
-                meshtastic_MeshPacket *tosend = packetPool.allocCopy(*p); // keep a copy because we will be sending it
-                LOG_INFO("Relaying received message coming from %x", p->relay_node);
+                if (p->next_hop == NO_NEXT_HOP_PREFERENCE || p->next_hop == nodeDB->getLastByteOfNodeNum(getNodeNum())) {
+                    meshtastic_MeshPacket *tosend = packetPool.allocCopy(*p); // keep a copy because we will be sending it
+                    LOG_INFO("Rebroadcast received message coming from %x", p->relay_node);
 
-                // Use shared logic to determine if hop_limit should be decremented
-                if (shouldDecrementHopLimit(p)) {
-                    tosend->hop_limit--; // bump down the hop count
-                } else {
-                    LOG_INFO("Router/CLIENT_BASE-to-favorite-router/CLIENT_BASE relay: preserving hop_limit");
+                    // Use shared logic to determine if hop_limit should be decremented
+                    if (shouldDecrementHopLimit(p)) {
+                        tosend->hop_limit--; // bump down the hop count
+                    } else {
+                        LOG_INFO("favorite-ROUTER/CLIENT_BASE-to-ROUTER/CLIENT_BASE flood: preserving hop_limit");
+                    }
+#if USERPREFS_EVENT_MODE
+                    if (tosend->hop_limit > 2) {
+                        // if we are "correcting" the hop_limit, "correct" the hop_start by the same amount to preserve hops away.
+                        tosend->hop_start -= (tosend->hop_limit - 2);
+                        tosend->hop_limit = 2;
+                    }
+#endif
+
+                    if (p->next_hop == NO_NEXT_HOP_PREFERENCE) {
+                        FloodingRouter::send(tosend);
+                    } else {
+                        NextHopRouter::send(tosend);
+                    }
+
+                    return true;
                 }
-
-                NextHopRouter::send(tosend);
-
-                return true;
             } else {
-                LOG_DEBUG("Not rebroadcasting: Role = CLIENT_MUTE or Rebroadcast Mode = NONE");
+                LOG_DEBUG("No rebroadcast: Role = CLIENT_MUTE or Rebroadcast Mode = NONE");
             }
+        } else {
+            LOG_DEBUG("Ignore 0 id broadcast");
         }
     }
 
@@ -231,13 +224,13 @@ bool NextHopRouter::stopRetransmission(GlobalPacketId key)
             }
         }
 
-        // Regardless of whether or not we canceled this packet from the txQueue, remove it from our pending list so it doesn't
-        // get scheduled again. (This is the core of stopRetransmission.)
+        // Regardless of whether or not we canceled this packet from the txQueue, remove it from our pending list so it
+        // doesn't get scheduled again. (This is the core of stopRetransmission.)
         auto numErased = pending.erase(key);
         assert(numErased == 1);
 
-        // When we remove an entry from pending, always be sure to release the copy of the packet that was allocated in the call
-        // to startRetransmission.
+        // When we remove an entry from pending, always be sure to release the copy of the packet that was allocated in the
+        // call to startRetransmission.
         packetPool.release(p);
 
         return true;
diff --git a/src/mesh/NextHopRouter.h b/src/mesh/NextHopRouter.h
index 0022644e9..c1df3596b 100644
--- a/src/mesh/NextHopRouter.h
+++ b/src/mesh/NextHopRouter.h
@@ -148,7 +148,7 @@ class NextHopRouter : public FloodingRouter
      */
     uint8_t getNextHop(NodeNum to, uint8_t relay_node);
 
-    /** Check if we should be relaying this packet if so, do so.
-     *  @return true if we did relay */
-    bool perhapsRelay(const meshtastic_MeshPacket *p);
+    /** Check if we should be rebroadcasting this packet if so, do so.
+     *  @return true if we did rebroadcast */
+    bool perhapsRebroadcast(const meshtastic_MeshPacket *p) override;
 };
\ No newline at end of file
diff --git a/src/mesh/PacketHistory.cpp b/src/mesh/PacketHistory.cpp
index 49d581d9a..b4af707ae 100644
--- a/src/mesh/PacketHistory.cpp
+++ b/src/mesh/PacketHistory.cpp
@@ -94,7 +94,6 @@ bool PacketHistory::wasSeenRecently(const meshtastic_MeshPacket *p, bool withUpd
         LOG_DEBUG("Packet History - Hop limit upgrade: packet 0x%08x from hop_limit=%d to hop_limit=%d", p->id, found->hop_limit,
                   p->hop_limit);
         *wasUpgraded = true;
-        seenRecently = false; // Allow router processing but prevent duplicate app delivery
     } else if (wasUpgraded) {
         *wasUpgraded = false; // Initialize to false if not an upgrade
     }

From 7c373b76c40cf74dd69cf9c3cd07f1729cc0702d Mon Sep 17 00:00:00 2001
From: GUVWAF 
Date: Sun, 5 Oct 2025 14:04:35 +0200
Subject: [PATCH 422/691] Reprocess repeated packets also

---
 src/mesh/FloodingRouter.cpp |  4 +++-
 src/mesh/NextHopRouter.cpp  | 14 ++++++++++----
 2 files changed, 13 insertions(+), 5 deletions(-)

diff --git a/src/mesh/FloodingRouter.cpp b/src/mesh/FloodingRouter.cpp
index 1225263a2..032be241b 100644
--- a/src/mesh/FloodingRouter.cpp
+++ b/src/mesh/FloodingRouter.cpp
@@ -45,8 +45,10 @@ bool FloodingRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
         if (isRepeated) {
             LOG_DEBUG("Repeated reliable tx");
             // Check if it's still in the Tx queue, if not, we have to relay it again
-            if (!findInTxQueue(p->from, p->id))
+            if (!findInTxQueue(p->from, p->id)) {
+                reprocessPacket(p);
                 perhapsRebroadcast(p);
+            }
         } else {
             perhapsCancelDupe(p);
         }
diff --git a/src/mesh/NextHopRouter.cpp b/src/mesh/NextHopRouter.cpp
index 7340c0e87..1ab4b43ed 100644
--- a/src/mesh/NextHopRouter.cpp
+++ b/src/mesh/NextHopRouter.cpp
@@ -59,14 +59,20 @@ bool NextHopRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
         if (wasFallback) {
             LOG_INFO("Fallback to flooding from relay_node=0x%x", p->relay_node);
             // Check if it's still in the Tx queue, if not, we have to relay it again
-            if (!findInTxQueue(p->from, p->id))
-                perhapsRelay(p);
+            if (!findInTxQueue(p->from, p->id)) {
+                reprocessPacket(p);
+                perhapsRebroadcast(p);
+            }
         } else {
             bool isRepeated = p->hop_start > 0 && p->hop_start == p->hop_limit;
             // If repeated and not in Tx queue anymore, try relaying again, or if we are the destination, send the ACK again
             if (isRepeated) {
-                if (!findInTxQueue(p->from, p->id) && !perhapsRelay(p) && isToUs(p) && p->want_ack)
-                    sendAckNak(meshtastic_Routing_Error_NONE, getFrom(p), p->id, p->channel, 0);
+                if (!findInTxQueue(p->from, p->id)) {
+                    reprocessPacket(p);
+                    if (!perhapsRebroadcast(p) && isToUs(p) && p->want_ack) {
+                        sendAckNak(meshtastic_Routing_Error_NONE, getFrom(p), p->id, p->channel, 0);
+                    }
+                }
             } else if (!weWereNextHop) {
                 perhapsCancelDupe(p); // If it's a dupe, cancel relay if we were not explicitly asked to relay
             }

From f7cf5e6b0ad11fc17eed25f20cb99517ebaf6b06 Mon Sep 17 00:00:00 2001
From: GUVWAF 
Date: Sun, 5 Oct 2025 15:56:45 +0200
Subject: [PATCH 423/691] Change to "rebroadcast"

---
 src/mesh/NextHopRouter.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/mesh/NextHopRouter.cpp b/src/mesh/NextHopRouter.cpp
index 1ab4b43ed..afdb4d096 100644
--- a/src/mesh/NextHopRouter.cpp
+++ b/src/mesh/NextHopRouter.cpp
@@ -138,7 +138,7 @@ bool NextHopRouter::perhapsRebroadcast(const meshtastic_MeshPacket *p)
                     if (shouldDecrementHopLimit(p)) {
                         tosend->hop_limit--; // bump down the hop count
                     } else {
-                        LOG_INFO("favorite-ROUTER/CLIENT_BASE-to-ROUTER/CLIENT_BASE flood: preserving hop_limit");
+                        LOG_INFO("favorite-ROUTER/CLIENT_BASE-to-ROUTER/CLIENT_BASE rebroadcast: preserving hop_limit");
                     }
 #if USERPREFS_EVENT_MODE
                     if (tosend->hop_limit > 2) {

From c147ce9a85396a38527a250d586ae2d380786a10 Mon Sep 17 00:00:00 2001
From: GUVWAF 
Date: Sun, 5 Oct 2025 16:58:42 +0200
Subject: [PATCH 424/691] Update next-hops based on traceroute result

---
 src/modules/TraceRouteModule.cpp | 64 ++++++++++++++++++++++++++++++++
 src/modules/TraceRouteModule.h   |  6 +++
 2 files changed, 70 insertions(+)

diff --git a/src/modules/TraceRouteModule.cpp b/src/modules/TraceRouteModule.cpp
index fc2cc232b..c2c5669c9 100644
--- a/src/modules/TraceRouteModule.cpp
+++ b/src/modules/TraceRouteModule.cpp
@@ -21,6 +21,11 @@ void TraceRouteModule::alterReceivedProtobuf(meshtastic_MeshPacket &p, meshtasti
 {
     const meshtastic_Data &incoming = p.decoded;
 
+    // Update next-hops using returned route
+    if (incoming.request_id) {
+        updateNextHops(p, r);
+    }
+
     // Insert unknown hops if necessary
     insertUnknownHops(p, r, !incoming.request_id);
 
@@ -153,6 +158,65 @@ void TraceRouteModule::alterReceivedProtobuf(meshtastic_MeshPacket &p, meshtasti
     }
 }
 
+void TraceRouteModule::updateNextHops(meshtastic_MeshPacket &p, meshtastic_RouteDiscovery *r)
+{
+    // E.g. if the route is A->B->C->D and we are B, we can set C as next-hop for C and D
+    // Similarly, if we are C, we can set D as next-hop for D
+    // If we are A, we can set B as next-hop for B, C and D
+
+    // First check if we were the original sender or in the original route
+    int8_t nextHopIndex = -1;
+    if (isToUs(&p)) {
+        nextHopIndex = 0; // We are the original sender, next hop is first in route
+    } else {
+        // Check if we are in the original route
+        for (uint8_t i = 0; i < r->route_count; i++) {
+            if (r->route[i] == nodeDB->getNodeNum()) {
+                nextHopIndex = i + 1; // Next hop is the one after us
+                break;
+            }
+        }
+    }
+
+    // If we are in the original route, update the next hops
+    if (nextHopIndex != -1) {
+        // For every node after us, we can set the next-hop to the first node after us
+        NodeNum nextHop;
+        if (nextHopIndex == r->route_count) {
+            nextHop = p.from; // We are the last in the route, next hop is destination
+        } else {
+            nextHop = r->route[nextHopIndex];
+        }
+
+        if (nextHop == NODENUM_BROADCAST) {
+            return;
+        }
+        uint8_t nextHopByte = nodeDB->getLastByteOfNodeNum(nextHop);
+
+        // For the rest of the nodes in the route, set their next-hop
+        // Note: if we are the last in the route, this loop will not run
+        for (int8_t i = nextHopIndex; i < r->route_count; i++) {
+            NodeNum targetNode = r->route[i];
+            maybeSetNextHop(targetNode, nextHopByte);
+        }
+
+        // Also set next-hop for the destination node
+        maybeSetNextHop(p.from, nextHopByte);
+    }
+}
+
+void TraceRouteModule::maybeSetNextHop(NodeNum target, uint8_t nextHopByte)
+{
+    if (target == NODENUM_BROADCAST)
+        return;
+
+    meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(target);
+    if (node && node->next_hop != nextHopByte) {
+        LOG_INFO("Updating next-hop for 0x%08x to 0x%08x based on traceroute", target, nextHopByte);
+        node->next_hop = nextHopByte;
+    }
+}
+
 void TraceRouteModule::processUpgradedPacket(const meshtastic_MeshPacket &mp)
 {
     if (mp.which_payload_variant != meshtastic_MeshPacket_decoded_tag || mp.decoded.portnum != meshtastic_PortNum_TRACEROUTE_APP)
diff --git a/src/modules/TraceRouteModule.h b/src/modules/TraceRouteModule.h
index a069f7157..dac422388 100644
--- a/src/modules/TraceRouteModule.h
+++ b/src/modules/TraceRouteModule.h
@@ -55,6 +55,12 @@ class TraceRouteModule : public ProtobufModule,
     // Call to add your ID to the route array of a RouteDiscovery message
     void appendMyIDandSNR(meshtastic_RouteDiscovery *r, float snr, bool isTowardsDestination, bool SNRonly);
 
+    // Update next-hops in the routing table based on the returned route
+    void updateNextHops(meshtastic_MeshPacket &p, meshtastic_RouteDiscovery *r);
+
+    // Helper to update next-hop for a single node
+    void maybeSetNextHop(NodeNum target, uint8_t nextHopByte);
+
     /* Call to print the route array of a RouteDiscovery message.
        Set origin to where the request came from.
        Set dest to the ID of its destination, or NODENUM_BROADCAST if it has not yet arrived there. */

From 5c2997ef535c8abeb5ac58c42abbdaed7ea2d6fc Mon Sep 17 00:00:00 2001
From: GUVWAF 
Date: Sun, 5 Oct 2025 17:03:52 +0200
Subject: [PATCH 425/691] Print only one byte

---
 src/modules/TraceRouteModule.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/modules/TraceRouteModule.cpp b/src/modules/TraceRouteModule.cpp
index c2c5669c9..5bdde1919 100644
--- a/src/modules/TraceRouteModule.cpp
+++ b/src/modules/TraceRouteModule.cpp
@@ -212,7 +212,7 @@ void TraceRouteModule::maybeSetNextHop(NodeNum target, uint8_t nextHopByte)
 
     meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(target);
     if (node && node->next_hop != nextHopByte) {
-        LOG_INFO("Updating next-hop for 0x%08x to 0x%08x based on traceroute", target, nextHopByte);
+        LOG_INFO("Updating next-hop for 0x%08x to 0x%02x based on traceroute", target, nextHopByte);
         node->next_hop = nextHopByte;
     }
 }

From 036a58735e582a1622e42786777a8a0726378824 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
 <41898282+github-actions[bot]@users.noreply.github.com>
Date: Mon, 6 Oct 2025 05:50:16 -0500
Subject: [PATCH 426/691] Upgrade trunk (#8229)

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 74b850b64..8c981850d 100644
--- a/.trunk/trunk.yaml
+++ b/.trunk/trunk.yaml
@@ -16,7 +16,7 @@ lint:
     - bandit@1.8.6
     - trivy@0.67.0
     - taplo@0.10.0
-    - ruff@0.13.2
+    - ruff@0.13.3
     - isort@6.1.0
     - markdownlint@0.45.0
     - oxipng@9.1.5

From 29f4d99bf64e503f3cf9cb9dcc33d037ea15cf86 Mon Sep 17 00:00:00 2001
From: Dmitry Dubinin <4762973+capricornusx@users.noreply.github.com>
Date: Mon, 6 Oct 2025 13:52:40 +0300
Subject: [PATCH 427/691] Add Adaptive Polling Intervals to WebServer (#7864)

* feat: add adaptive polling intervals to WebServer

Replace fixed 5ms polling with adaptive intervals based on HTTP activity:
- 50ms during active periods (first 5 seconds after request)
- 200ms during medium activity (5-30 seconds)
- 1000ms during idle periods (30+ seconds)

Reduces CPU usage significantly during idle periods while maintaining
responsiveness when handling HTTP requests.

* Fix integer overflow and magic numbers in WebServer

- Handle millis() overflow in getAdaptiveInterval()
- Replace magic numbers with named constants
- Improve code readability and maintainability
---
 src/mesh/http/ContentHandler.cpp |  5 +++++
 src/mesh/http/WebServer.cpp      | 35 ++++++++++++++++++++++++++++++--
 src/mesh/http/WebServer.h        |  4 ++++
 3 files changed, 42 insertions(+), 2 deletions(-)

diff --git a/src/mesh/http/ContentHandler.cpp b/src/mesh/http/ContentHandler.cpp
index f87c6e3b0..7b7ebb595 100644
--- a/src/mesh/http/ContentHandler.cpp
+++ b/src/mesh/http/ContentHandler.cpp
@@ -148,6 +148,8 @@ void registerHandlers(HTTPServer *insecureServer, HTTPSServer *secureServer)
 
 void handleAPIv1FromRadio(HTTPRequest *req, HTTPResponse *res)
 {
+    if (webServerThread)
+        webServerThread->markActivity();
 
     LOG_DEBUG("webAPI handleAPIv1FromRadio");
 
@@ -391,6 +393,9 @@ void handleFsDeleteStatic(HTTPRequest *req, HTTPResponse *res)
 
 void handleStatic(HTTPRequest *req, HTTPResponse *res)
 {
+    if (webServerThread)
+        webServerThread->markActivity();
+
     // Get access to the parameters
     ResourceParameters *params = req->getParams();
 
diff --git a/src/mesh/http/WebServer.cpp b/src/mesh/http/WebServer.cpp
index bf170de59..3a264fa5a 100644
--- a/src/mesh/http/WebServer.cpp
+++ b/src/mesh/http/WebServer.cpp
@@ -49,6 +49,12 @@ Preferences prefs;
 using namespace httpsserver;
 #include "mesh/http/ContentHandler.h"
 
+static const uint32_t ACTIVE_THRESHOLD_MS = 5000;
+static const uint32_t MEDIUM_THRESHOLD_MS = 30000;
+static const int32_t ACTIVE_INTERVAL_MS = 50;
+static const int32_t MEDIUM_INTERVAL_MS = 200;
+static const int32_t IDLE_INTERVAL_MS = 1000;
+
 static SSLCert *cert;
 static HTTPSServer *secureServer;
 static HTTPServer *insecureServer;
@@ -175,6 +181,32 @@ WebServerThread::WebServerThread() : concurrency::OSThread("WebServer")
     if (!config.network.wifi_enabled && !config.network.eth_enabled) {
         disable();
     }
+    lastActivityTime = millis();
+}
+
+void WebServerThread::markActivity()
+{
+    lastActivityTime = millis();
+}
+
+int32_t WebServerThread::getAdaptiveInterval()
+{
+    uint32_t currentTime = millis();
+    uint32_t timeSinceActivity;
+
+    if (currentTime >= lastActivityTime) {
+        timeSinceActivity = currentTime - lastActivityTime;
+    } else {
+        timeSinceActivity = (UINT32_MAX - lastActivityTime) + currentTime + 1;
+    }
+
+    if (timeSinceActivity < ACTIVE_THRESHOLD_MS) {
+        return ACTIVE_INTERVAL_MS;
+    } else if (timeSinceActivity < MEDIUM_THRESHOLD_MS) {
+        return MEDIUM_INTERVAL_MS;
+    } else {
+        return IDLE_INTERVAL_MS;
+    }
 }
 
 int32_t WebServerThread::runOnce()
@@ -189,8 +221,7 @@ int32_t WebServerThread::runOnce()
         ESP.restart();
     }
 
-    // Loop every 5ms.
-    return (5);
+    return getAdaptiveInterval();
 }
 
 void initWebServer()
diff --git a/src/mesh/http/WebServer.h b/src/mesh/http/WebServer.h
index 815d87432..e7a29a5a7 100644
--- a/src/mesh/http/WebServer.h
+++ b/src/mesh/http/WebServer.h
@@ -10,13 +10,17 @@ void createSSLCert();
 
 class WebServerThread : private concurrency::OSThread
 {
+  private:
+    uint32_t lastActivityTime = 0;
 
   public:
     WebServerThread();
     uint32_t requestRestart = 0;
+    void markActivity();
 
   protected:
     virtual int32_t runOnce() override;
+    int32_t getAdaptiveInterval();
 };
 
 extern WebServerThread *webServerThread;

From 627c0145e7df9f52109a822a73c35f08a2951a43 Mon Sep 17 00:00:00 2001
From: Ben Meadors 
Date: Mon, 6 Oct 2025 07:56:27 -0500
Subject: [PATCH 428/691] Centralize getNodeId and fix references to owner.id
 (#8230)

---
 src/mesh/NodeDB.cpp                           |  7 ++++
 src/mesh/NodeDB.h                             |  4 ++
 src/mesh/PhoneAPI.cpp                         |  2 +
 src/mesh/wifi/WiFiAPClient.cpp                |  4 +-
 src/mqtt/MQTT.cpp                             | 38 ++++++++++++++-----
 src/serialization/MeshPacketSerializer.cpp    |  2 +-
 .../MeshPacketSerializer_nRF52.cpp            |  2 +-
 test/test_mqtt/MQTT.cpp                       |  4 +-
 8 files changed, 47 insertions(+), 16 deletions(-)

diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp
index e3240462d..dec8411fe 100644
--- a/src/mesh/NodeDB.cpp
+++ b/src/mesh/NodeDB.cpp
@@ -1874,6 +1874,13 @@ uint8_t NodeDB::getMeshNodeChannel(NodeNum n)
     return info->channel;
 }
 
+std::string NodeDB::getNodeId() const
+{
+    char nodeId[16];
+    snprintf(nodeId, sizeof(nodeId), "!%08x", myNodeInfo.my_node_num);
+    return std::string(nodeId);
+}
+
 /// Find a node in our DB, return null for missing
 /// NOTE: This function might be called from an ISR
 meshtastic_NodeInfoLite *NodeDB::getMeshNode(NodeNum n)
diff --git a/src/mesh/NodeDB.h b/src/mesh/NodeDB.h
index f73f64f92..e8724f2c9 100644
--- a/src/mesh/NodeDB.h
+++ b/src/mesh/NodeDB.h
@@ -5,6 +5,7 @@
 #include 
 #include 
 #include 
+#include 
 #include 
 
 #include "MeshTypes.h"
@@ -203,6 +204,9 @@ class NodeDB
     /// @return our node number
     NodeNum getNodeNum() { return myNodeInfo.my_node_num; }
 
+    /// @return our node ID as a string in the format "!xxxxxxxx"
+    std::string getNodeId() const;
+
     // @return last byte of a NodeNum, 0xFF if it ended at 0x00
     uint8_t getLastByteOfNodeNum(NodeNum num) { return (uint8_t)((num & 0xFF) ? (num & 0xFF) : 0xFF); }
 
diff --git a/src/mesh/PhoneAPI.cpp b/src/mesh/PhoneAPI.cpp
index 07f314415..210e0ba06 100644
--- a/src/mesh/PhoneAPI.cpp
+++ b/src/mesh/PhoneAPI.cpp
@@ -433,6 +433,8 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
     case STATE_SEND_OTHER_NODEINFOS: {
         LOG_DEBUG("Send known nodes");
         if (nodeInfoForPhone.num != 0) {
+            // Just in case we stored a different user.id in the past, but should never happen going forward
+            sprintf(nodeInfoForPhone.user.id, "!%08x", nodeInfoForPhone.num);
             LOG_INFO("nodeinfo: num=0x%x, lastseen=%u, id=%s, name=%s", nodeInfoForPhone.num, nodeInfoForPhone.last_heard,
                      nodeInfoForPhone.user.id, nodeInfoForPhone.user.long_name);
             fromRadioScratch.which_payload_variant = meshtastic_FromRadio_node_info_tag;
diff --git a/src/mesh/wifi/WiFiAPClient.cpp b/src/mesh/wifi/WiFiAPClient.cpp
index 1133ad424..7d210dd33 100644
--- a/src/mesh/wifi/WiFiAPClient.cpp
+++ b/src/mesh/wifi/WiFiAPClient.cpp
@@ -94,11 +94,11 @@ static void onNetworkConnected()
 // ESPmDNS (ESP32) and SimpleMDNS (RP2040) have slightly different APIs for adding TXT records
 #ifdef ARCH_ESP32
             MDNS.addServiceTxt("meshtastic", "tcp", "shortname", String(owner.short_name));
-            MDNS.addServiceTxt("meshtastic", "tcp", "id", String(owner.id));
+            MDNS.addServiceTxt("meshtastic", "tcp", "id", String(nodeDB->getNodeId().c_str()));
             // ESP32 prints obtained IP address in WiFiEvent
 #elif defined(ARCH_RP2040)
             MDNS.addServiceTxt("meshtastic", "shortname", owner.short_name);
-            MDNS.addServiceTxt("meshtastic", "id", owner.id);
+            MDNS.addServiceTxt("meshtastic", "id", nodeDB->getNodeId().c_str());
             LOG_INFO("Obtained IP address: %s", WiFi.localIP().toString().c_str());
 #endif
         }
diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp
index 7f7a9d511..8ce352f14 100644
--- a/src/mqtt/MQTT.cpp
+++ b/src/mqtt/MQTT.cpp
@@ -60,7 +60,9 @@ inline void onReceiveProto(char *topic, byte *payload, size_t length)
         return;
     }
     const meshtastic_Channel &ch = channels.getByName(e.channel_id);
-    if (strcmp(e.gateway_id, owner.id) == 0) {
+    // Generate node ID from nodenum for comparison
+    std::string nodeId = nodeDB->getNodeId();
+    if (strcmp(e.gateway_id, nodeId.c_str()) == 0) {
         // Generate an implicit ACK towards ourselves (handled and processed only locally!) for this message.
         // We do this because packets are not rebroadcasted back into MQTT anymore and we assume that at least one node
         // receives it when we get our own packet back. Then we'll stop our retransmissions.
@@ -128,8 +130,10 @@ inline void onReceiveProto(char *topic, byte *payload, size_t length)
 // returns true if this is a valid JSON envelope which we accept on downlink
 inline bool isValidJsonEnvelope(JSONObject &json)
 {
+    // Generate node ID from nodenum for comparison
+    std::string nodeId = nodeDB->getNodeId();
     // if "sender" is provided, avoid processing packets we uplinked
-    return (json.find("sender") != json.end() ? (json["sender"]->AsString().compare(owner.id) != 0) : true) &&
+    return (json.find("sender") != json.end() ? (json["sender"]->AsString().compare(nodeId) != 0) : true) &&
            (json.find("hopLimit") != json.end() ? json["hopLimit"]->IsNumber() : true) && // hop limit should be a number
            (json.find("from") != json.end()) && json["from"]->IsNumber() &&
            (json["from"]->AsNumber() == nodeDB->getNodeNum()) &&            // only accept message if the "from" is us
@@ -297,7 +301,9 @@ bool connectPubSub(const PubSubConfig &config, PubSubClient &pubSub, Client &cli
     LOG_INFO("Connecting directly to MQTT server %s, port: %d, username: %s, password: %s", config.serverAddr.c_str(),
              config.serverPort, config.mqttUsername, config.mqttPassword);
 
-    const bool connected = pubSub.connect(owner.id, config.mqttUsername, config.mqttPassword);
+    // Generate node ID from nodenum for client identification
+    std::string nodeId = nodeDB->getNodeId();
+    const bool connected = pubSub.connect(nodeId.c_str(), config.mqttUsername, config.mqttPassword);
     if (connected) {
         LOG_INFO("MQTT connected");
     } else {
@@ -687,11 +693,14 @@ void MQTT::publishQueuedMessages()
     if (jsonString.length() == 0)
         return;
 
+    // Generate node ID from nodenum for topic
+    std::string nodeId = nodeDB->getNodeId();
+
     std::string topicJson;
     if (env.packet->pki_encrypted) {
-        topicJson = jsonTopic + "PKI/" + owner.id;
+        topicJson = jsonTopic + "PKI/" + nodeId;
     } else {
-        topicJson = jsonTopic + env.channel_id + "/" + owner.id;
+        topicJson = jsonTopic + env.channel_id + "/" + nodeId;
     }
     LOG_INFO("JSON publish message to %s, %u bytes: %s", topicJson.c_str(), jsonString.length(), jsonString.c_str());
     publish(topicJson.c_str(), jsonString.c_str(), false);
@@ -749,10 +758,14 @@ void MQTT::onSend(const meshtastic_MeshPacket &mp_encrypted, const meshtastic_Me
         return; // Don't upload a still-encrypted PKI packet if not encryption_enabled
     }
 
-    const meshtastic_ServiceEnvelope env = {
-        .packet = const_cast(p), .channel_id = const_cast(channelId), .gateway_id = owner.id};
+    // Generate node ID from nodenum for service envelope
+    std::string nodeId = nodeDB->getNodeId();
+
+    const meshtastic_ServiceEnvelope env = {.packet = const_cast(p),
+                                            .channel_id = const_cast(channelId),
+                                            .gateway_id = const_cast(nodeId.c_str())};
     size_t numBytes = pb_encode_to_bytes(bytes, sizeof(bytes), &meshtastic_ServiceEnvelope_msg, &env);
-    std::string topic = cryptTopic + channelId + "/" + owner.id;
+    std::string topic = cryptTopic + channelId + "/" + nodeId;
 
     if (moduleConfig.mqtt.proxy_to_client_enabled || this->isConnectedDirectly()) {
         LOG_DEBUG("MQTT Publish %s, %u bytes", topic.c_str(), numBytes);
@@ -766,7 +779,9 @@ void MQTT::onSend(const meshtastic_MeshPacket &mp_encrypted, const meshtastic_Me
         auto jsonString = MeshPacketSerializer::JsonSerialize(&mp_decoded);
         if (jsonString.length() == 0)
             return;
-        std::string topicJson = jsonTopic + channelId + "/" + owner.id;
+        // Generate node ID from nodenum for JSON topic
+        std::string nodeIdForJson = nodeDB->getNodeId();
+        std::string topicJson = jsonTopic + channelId + "/" + nodeIdForJson;
         LOG_INFO("JSON publish message to %s, %u bytes: %s", topicJson.c_str(), jsonString.length(), jsonString.c_str());
         publish(topicJson.c_str(), jsonString.c_str(), false);
 #endif // ARCH_NRF52 NRF52_USE_JSON
@@ -845,11 +860,14 @@ void MQTT::perhapsReportToMap()
     mp->decoded.payload.size =
         pb_encode_to_bytes(mp->decoded.payload.bytes, sizeof(mp->decoded.payload.bytes), &meshtastic_MapReport_msg, &mapReport);
 
+    // Generate node ID from nodenum for service envelope
+    std::string nodeId = nodeDB->getNodeId();
+
     // Encode the MeshPacket into a binary ServiceEnvelope and publish
     const meshtastic_ServiceEnvelope se = {
         .packet = mp,
         .channel_id = (char *)channels.getGlobalId(channels.getPrimaryIndex()), // Use primary channel as the channel_id
-        .gateway_id = owner.id};
+        .gateway_id = const_cast(nodeId.c_str())};
     size_t numBytes = pb_encode_to_bytes(bytes, sizeof(bytes), &meshtastic_ServiceEnvelope_msg, &se);
 
     LOG_INFO("MQTT Publish map report to %s", mapTopic.c_str());
diff --git a/src/serialization/MeshPacketSerializer.cpp b/src/serialization/MeshPacketSerializer.cpp
index 5a1f8ed7e..b31d2dc2e 100644
--- a/src/serialization/MeshPacketSerializer.cpp
+++ b/src/serialization/MeshPacketSerializer.cpp
@@ -413,7 +413,7 @@ std::string MeshPacketSerializer::JsonSerialize(const meshtastic_MeshPacket *mp,
     jsonObj["from"] = new JSONValue((unsigned int)mp->from);
     jsonObj["channel"] = new JSONValue((unsigned int)mp->channel);
     jsonObj["type"] = new JSONValue(msgType.c_str());
-    jsonObj["sender"] = new JSONValue(owner.id);
+    jsonObj["sender"] = new JSONValue(nodeDB->getNodeId().c_str());
     if (mp->rx_rssi != 0)
         jsonObj["rssi"] = new JSONValue((int)mp->rx_rssi);
     if (mp->rx_snr != 0)
diff --git a/src/serialization/MeshPacketSerializer_nRF52.cpp b/src/serialization/MeshPacketSerializer_nRF52.cpp
index e0daa1a88..353c710a1 100644
--- a/src/serialization/MeshPacketSerializer_nRF52.cpp
+++ b/src/serialization/MeshPacketSerializer_nRF52.cpp
@@ -353,7 +353,7 @@ std::string MeshPacketSerializer::JsonSerialize(const meshtastic_MeshPacket *mp,
     jsonObj["from"] = (unsigned int)mp->from;
     jsonObj["channel"] = (unsigned int)mp->channel;
     jsonObj["type"] = msgType.c_str();
-    jsonObj["sender"] = owner.id;
+    jsonObj["sender"] = nodeDB->getNodeId().c_str();
     if (mp->rx_rssi != 0)
         jsonObj["rssi"] = (int)mp->rx_rssi;
     if (mp->rx_snr != 0)
diff --git a/test/test_mqtt/MQTT.cpp b/test/test_mqtt/MQTT.cpp
index ede3d22b7..8726d1ccb 100644
--- a/test/test_mqtt/MQTT.cpp
+++ b/test/test_mqtt/MQTT.cpp
@@ -591,7 +591,7 @@ void test_receiveEncryptedPKITopicToUs(void)
 // Should ignore messages published to MQTT by this gateway.
 void test_receiveIgnoresOwnPublishedMessages(void)
 {
-    unitTest->publish(&decoded, owner.id);
+    unitTest->publish(&decoded, nodeDB->getNodeId().c_str());
 
     TEST_ASSERT_TRUE(mockRouter->packets_.empty());
     TEST_ASSERT_TRUE(mockRoutingModule->ackNacks_.empty());
@@ -603,7 +603,7 @@ void test_receiveAcksOwnSentMessages(void)
     meshtastic_MeshPacket p = decoded;
     p.from = myNodeInfo.my_node_num;
 
-    unitTest->publish(&p, owner.id);
+    unitTest->publish(&p, nodeDB->getNodeId().c_str());
 
     TEST_ASSERT_TRUE(mockRouter->packets_.empty());
     TEST_ASSERT_EQUAL(1, mockRoutingModule->ackNacks_.size());

From 329a494ce2e35b4c13c726602f4be1fad24d54c7 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Mon, 6 Oct 2025 12:59:40 -0500
Subject: [PATCH 429/691] Update meshtastic-ArduinoThread digest to b841b04
 (#8233)

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 b6d6733e3..2e6f851df 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -70,7 +70,7 @@ lib_deps =
 	# renovate: datasource=git-refs depName=meshtastic-TinyGPSPlus packageName=https://github.com/meshtastic/TinyGPSPlus gitBranch=master
 	https://github.com/meshtastic/TinyGPSPlus/archive/71a82db35f3b973440044c476d4bcdc673b104f4.zip
 	# renovate: datasource=git-refs depName=meshtastic-ArduinoThread packageName=https://github.com/meshtastic/ArduinoThread gitBranch=master
-	https://github.com/meshtastic/ArduinoThread/archive/7c3ee9e1951551b949763b1f5280f8db1fa4068d.zip
+	https://github.com/meshtastic/ArduinoThread/archive/b841b0415721f1341ea41cccfb4adccfaf951567.zip
 	# renovate: datasource=custom.pio depName=Nanopb packageName=nanopb/library/Nanopb
 	nanopb/Nanopb@0.4.91
 	# renovate: datasource=custom.pio depName=ErriezCRC32 packageName=erriez/library/ErriezCRC32

From 87e3540f48eb17cffd6cda2cbebef274ced29885 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Mon, 6 Oct 2025 12:59:50 -0500
Subject: [PATCH 430/691] Update meshtastic/device-ui digest to f920b12 (#8234)

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 2e6f851df..4ae2da54f 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -120,7 +120,7 @@ lib_deps =
 [device-ui_base]
 lib_deps =
 	# renovate: datasource=git-refs depName=meshtastic/device-ui packageName=https://github.com/meshtastic/device-ui gitBranch=master
-	https://github.com/meshtastic/device-ui/archive/505ffadaa7a931df5dc8153229b719a07bbb028c.zip
+	https://github.com/meshtastic/device-ui/archive/f920b1273df750c2e3e01385d3ba30553b913afa.zip
 
 ; Common libs for environmental measurements in telemetry module
 [environmental_base]

From 735784e6e422ab5383dcb5afc29eb7c915117a9d Mon Sep 17 00:00:00 2001
From: Jonathan Bennett 
Date: Mon, 6 Oct 2025 13:00:44 -0500
Subject: [PATCH 431/691] Run Integration test in simulator mode (#8232)

---
 .github/workflows/test_native.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.github/workflows/test_native.yml b/.github/workflows/test_native.yml
index 6b788f4c7..9cf1c9e53 100644
--- a/.github/workflows/test_native.yml
+++ b/.github/workflows/test_native.yml
@@ -40,7 +40,7 @@ jobs:
 
       - name: Integration test
         run: |
-          .pio/build/coverage/program &
+          .pio/build/coverage/program -s &
           PID=$!
           timeout 20 bash -c "until ls -al /proc/$PID/fd | grep socket; do sleep 1; done"
           echo "Simulator started, launching python test..."

From fc1737c9495937ba142d9018593df4b3937e29ed Mon Sep 17 00:00:00 2001
From: Austin 
Date: Mon, 6 Oct 2025 20:58:00 -0400
Subject: [PATCH 432/691] Actions: Simplify matrices, cleanup build_one_*
 (#8218)

---
 .github/workflows/build_firmware.yml          |   3 +
 .github/workflows/build_one_arch.yml          | 302 +-----------------
 .github/workflows/build_one_target.yml        | 173 +---------
 .github/workflows/main_matrix.yml             | 141 ++------
 .github/workflows/merge_queue.yml             | 134 +-------
 bin/generate_ci_matrix.py                     |  55 ++--
 .../native/portduino-buildroot/platformio.ini |   1 +
 variants/native/portduino/platformio.ini      |   3 +-
 8 files changed, 92 insertions(+), 720 deletions(-)

diff --git a/.github/workflows/build_firmware.yml b/.github/workflows/build_firmware.yml
index 2ef67405a..b62729332 100644
--- a/.github/workflows/build_firmware.yml
+++ b/.github/workflows/build_firmware.yml
@@ -19,6 +19,8 @@ jobs:
   pio-build:
     name: build-${{ inputs.platform }}
     runs-on: ubuntu-24.04
+    outputs:
+      artifact-id: ${{ steps.upload.outputs.artifact-id }}
     steps:
       - uses: actions/checkout@v5
         with:
@@ -55,6 +57,7 @@ jobs:
 
       - name: Store binaries as an artifact
         uses: actions/upload-artifact@v4
+        id: upload
         with:
           name: firmware-${{ inputs.platform }}-${{ inputs.pio_env }}-${{ inputs.version }}.zip
           overwrite: true
diff --git a/.github/workflows/build_one_arch.yml b/.github/workflows/build_one_arch.yml
index f5352b3c4..6d9134941 100644
--- a/.github/workflows/build_one_arch.yml
+++ b/.github/workflows/build_one_arch.yml
@@ -3,6 +3,7 @@ name: Build One Arch
 on:
   workflow_dispatch:
     inputs:
+      # trunk-ignore(checkov/CKV_GHA_7)
       arch:
         type: choice
         options:
@@ -16,10 +17,13 @@ on:
           - stm32
           - native
 
+permissions: read-all
+
+env:
+  INPUT_ARCH: ${{ github.event.inputs.arch }}
+
 jobs:
   setup:
-    strategy:
-      fail-fast: false
     runs-on: ubuntu-24.04
     steps:
       - uses: actions/checkout@v5
@@ -31,23 +35,11 @@ jobs:
       - name: Generate matrix
         id: jsonStep
         run: |
-          if [[ "$GITHUB_HEAD_REF" == "" ]]; then
-            TARGETS=$(./bin/generate_ci_matrix.py ${{inputs.arch}} extra)
-          else  
-            TARGETS=$(./bin/generate_ci_matrix.py ${{inputs.arch}} pr)
-          fi
-          echo "Name: $GITHUB_REF_NAME Base: $GITHUB_BASE_REF Ref: $GITHUB_REF Targets: $TARGETS"
-          echo "${{inputs.arch}}=$(jq -cn --argjson environments "$TARGETS" '{board: $environments}')" >> $GITHUB_OUTPUT
+          TARGETS=$(./bin/generate_ci_matrix.py $INPUT_ARCH --level extra)
+          echo "Name: $GITHUB_REF_NAME Base: $GITHUB_BASE_REF Ref: $GITHUB_REF"
+          echo "selected_arch=$TARGETS" >> $GITHUB_OUTPUT
     outputs:
-      esp32: ${{ steps.jsonStep.outputs.esp32 }}
-      esp32s3: ${{ steps.jsonStep.outputs.esp32s3 }}
-      esp32c3: ${{ steps.jsonStep.outputs.esp32c3 }}
-      esp32c6: ${{ steps.jsonStep.outputs.esp32c6 }}
-      nrf52840: ${{ steps.jsonStep.outputs.nrf52840 }}
-      rp2040: ${{ steps.jsonStep.outputs.rp2040 }}
-      rp2350: ${{ steps.jsonStep.outputs.rp2350 }}
-      stm32: ${{ steps.jsonStep.outputs.stm32 }}
-      check: ${{ steps.jsonStep.outputs.check }}
+      selected_arch: ${{ steps.jsonStep.outputs.selected_arch }}
 
   version:
     runs-on: ubuntu-latest
@@ -64,101 +56,18 @@ jobs:
       long: ${{ steps.version.outputs.long }}
       deb: ${{ steps.version.outputs.deb }}
 
-  build-esp32:
-    if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'esp32'}}
+  build:
+    if: ${{ github.event_name != 'workflow_dispatch' }}
     needs: [setup, version]
     strategy:
       fail-fast: false
-      matrix: ${{ fromJson(needs.setup.outputs.esp32) }}
+      matrix:
+        build: ${{ fromJson(needs.setup.outputs.selected_arch) }}
     uses: ./.github/workflows/build_firmware.yml
     with:
       version: ${{ needs.version.outputs.long }}
-      pio_env: ${{ matrix.board }}
-      platform: esp32
-
-  build-esp32s3:
-    if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'esp32s3'}}
-    needs: [setup, version]
-    strategy:
-      fail-fast: false
-      matrix: ${{ fromJson(needs.setup.outputs.esp32s3) }}
-    uses: ./.github/workflows/build_firmware.yml
-    with:
-      version: ${{ needs.version.outputs.long }}
-      pio_env: ${{ matrix.board }}
-      platform: esp32s3
-
-  build-esp32c3:
-    if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'esp32c3'}}
-    needs: [setup, version]
-    strategy:
-      fail-fast: false
-      matrix: ${{ fromJson(needs.setup.outputs.esp32c3) }}
-    uses: ./.github/workflows/build_firmware.yml
-    with:
-      version: ${{ needs.version.outputs.long }}
-      pio_env: ${{ matrix.board }}
-      platform: esp32c3
-
-  build-esp32c6:
-    if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'esp32c6'}}
-    needs: [setup, version]
-    strategy:
-      fail-fast: false
-      matrix: ${{ fromJson(needs.setup.outputs.esp32c6) }}
-    uses: ./.github/workflows/build_firmware.yml
-    with:
-      version: ${{ needs.version.outputs.long }}
-      pio_env: ${{ matrix.board }}
-      platform: esp32c6
-
-  build-nrf52840:
-    if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'nrf52840'}}
-    needs: [setup, version]
-    strategy:
-      fail-fast: false
-      matrix: ${{ fromJson(needs.setup.outputs.nrf52840) }}
-    uses: ./.github/workflows/build_firmware.yml
-    with:
-      version: ${{ needs.version.outputs.long }}
-      pio_env: ${{ matrix.board }}
-      platform: nrf52840
-
-  build-rp2040:
-    if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'rp2040'}}
-    needs: [setup, version]
-    strategy:
-      fail-fast: false
-      matrix: ${{ fromJson(needs.setup.outputs.rp2040) }}
-    uses: ./.github/workflows/build_firmware.yml
-    with:
-      version: ${{ needs.version.outputs.long }}
-      pio_env: ${{ matrix.board }}
-      platform: rp2040
-
-  build-rp2350:
-    if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'rp2350'}}
-    needs: [setup, version]
-    strategy:
-      fail-fast: false
-      matrix: ${{ fromJson(needs.setup.outputs.rp2350) }}
-    uses: ./.github/workflows/build_firmware.yml
-    with:
-      version: ${{ needs.version.outputs.long }}
-      pio_env: ${{ matrix.board }}
-      platform: rp2350
-
-  build-stm32:
-    if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'stm32' }}
-    needs: [setup, version]
-    strategy:
-      fail-fast: false
-      matrix: ${{ fromJson(needs.setup.outputs.stm32) }}
-    uses: ./.github/workflows/build_firmware.yml
-    with:
-      version: ${{ needs.version.outputs.long }}
-      pio_env: ${{ matrix.board }}
-      platform: stm32
+      pio_env: ${{ matrix.build.board }}
+      platform: ${{ matrix.build.arch }}
 
   build-debian-src:
     if: ${{ github.repository == 'meshtastic/firmware' && github.event_name != 'workflow_dispatch' || inputs.arch == 'native' }}
@@ -252,18 +161,7 @@ jobs:
           - rp2350
           - stm32
     runs-on: ubuntu-latest
-    needs:
-      [
-        version,
-        build-esp32,
-        build-esp32s3,
-        build-esp32c3,
-        build-esp32c6,
-        build-nrf52840,
-        build-rp2040,
-        build-rp2350,
-        build-stm32,
-      ]
+    needs: [version, build]
     steps:
       - name: Checkout code
         uses: actions/checkout@v5
@@ -332,169 +230,3 @@ jobs:
           name: firmware-${{inputs.arch}}-${{ needs.version.outputs.long }}
           description: "Download firmware-${{inputs.arch}}-${{ needs.version.outputs.long }}.zip. This artifact will be available for 90 days from creation"
           github-token: ${{ secrets.GITHUB_TOKEN }}
-
-  release-artifacts:
-    runs-on: ubuntu-latest
-    if: ${{ github.event_name == 'workflow_dispatch' }}
-    outputs:
-      upload_url: ${{ steps.create_release.outputs.upload_url }}
-    needs:
-      - version
-      - gather-artifacts
-      - build-debian-src
-      - package-pio-deps-native-tft
-    steps:
-      - name: Checkout
-        uses: actions/checkout@v5
-
-      - name: Setup Python
-        uses: actions/setup-python@v6
-        with:
-          python-version: 3.x
-
-      - name: Create release
-        uses: softprops/action-gh-release@v2
-        id: create_release
-        with:
-          draft: true
-          prerelease: true
-          name: Meshtastic Firmware ${{ needs.version.outputs.long }} Alpha
-          tag_name: v${{ needs.version.outputs.long }}
-          body: |
-            Autogenerated by github action, developer should edit as required before publishing...
-
-      - name: Download source deb
-        uses: actions/download-artifact@v5
-        with:
-          pattern: firmware-debian-${{ needs.version.outputs.deb }}~UNRELEASED-src
-          merge-multiple: true
-          path: ./output/debian-src
-
-      - name: Download `native-tft` pio deps
-        uses: actions/download-artifact@v5
-        with:
-          pattern: platformio-deps-native-tft-${{ needs.version.outputs.long }}
-          merge-multiple: true
-          path: ./output/pio-deps-native-tft
-
-      - name: Zip Linux sources
-        working-directory: output
-        run: |
-          zip -j -9 -r ./meshtasticd-${{ needs.version.outputs.deb }}-src.zip ./debian-src
-          zip -9 -r ./platformio-deps-native-tft-${{ needs.version.outputs.long }}.zip ./pio-deps-native-tft
-
-      # For diagnostics
-      - name: Display structure of downloaded files
-        run: ls -lR
-
-      - name: Add Linux sources to GtiHub Release
-        # Only run when targeting master branch with workflow_dispatch
-        if: ${{ github.ref_name == 'master' }}
-        run: |
-          gh release upload v${{ needs.version.outputs.long }} ./output/meshtasticd-${{ needs.version.outputs.deb }}-src.zip
-          gh release upload v${{ needs.version.outputs.long }} ./output/platformio-deps-native-tft-${{ needs.version.outputs.long }}.zip
-        env:
-          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-
-  release-firmware:
-    strategy:
-      fail-fast: false
-      matrix:
-        arch:
-          - esp32
-          - esp32s3
-          - esp32c3
-          - esp32c6
-          - nrf52840
-          - rp2040
-          - rp2350
-          - stm32
-    runs-on: ubuntu-latest
-    if: ${{ github.event_name == 'workflow_dispatch' }}
-    needs: [release-artifacts, version]
-    steps:
-      - name: Checkout
-        uses: actions/checkout@v5
-
-      - name: Setup Python
-        uses: actions/setup-python@v6
-        with:
-          python-version: 3.x
-
-      - uses: actions/download-artifact@v5
-        with:
-          pattern: firmware-${{inputs.arch}}-${{ needs.version.outputs.long }}
-          merge-multiple: true
-          path: ./output
-
-      - name: Display structure of downloaded files
-        run: ls -lR
-
-      - name: Device scripts permissions
-        run: |
-          chmod +x ./output/device-install.sh
-          chmod +x ./output/device-update.sh
-
-      - name: Zip firmware
-        run: zip -j -9 -r ./firmware-${{inputs.arch}}-${{ needs.version.outputs.long }}.zip ./output
-
-      - uses: actions/download-artifact@v5
-        with:
-          name: debug-elfs-${{inputs.arch}}-${{ needs.version.outputs.long }}.zip
-          merge-multiple: true
-          path: ./elfs
-
-      - name: Zip debug elfs
-        run: zip -j -9 -r ./debug-elfs-${{inputs.arch}}-${{ needs.version.outputs.long }}.zip ./elfs
-
-      # For diagnostics
-      - name: Display structure of downloaded files
-        run: ls -lR
-
-      - name: Add bins and debug elfs to GitHub Release
-        # Only run when targeting master branch with workflow_dispatch
-        if: ${{ github.ref_name == 'master' }}
-        run: |
-          gh release upload v${{ needs.version.outputs.long }} ./firmware-${{inputs.arch}}-${{ needs.version.outputs.long }}.zip
-          gh release upload v${{ needs.version.outputs.long }} ./debug-elfs-${{inputs.arch}}-${{ needs.version.outputs.long }}.zip
-        env:
-          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-
-  publish-firmware:
-    runs-on: ubuntu-24.04
-    if: ${{ github.event_name == 'workflow_dispatch' }}
-    needs: [release-firmware, version]
-    env:
-      targets: |-
-        esp32,esp32s3,esp32c3,esp32c6,nrf52840,rp2040,rp2350,stm32
-    steps:
-      - name: Checkout
-        uses: actions/checkout@v5
-
-      - name: Setup Python
-        uses: actions/setup-python@v6
-        with:
-          python-version: 3.x
-
-      - uses: actions/download-artifact@v5
-        with:
-          pattern: firmware-{${{ env.targets }}}-${{ needs.version.outputs.long }}
-          merge-multiple: true
-          path: ./publish
-
-      - name: Publish firmware to meshtastic.github.io
-        uses: peaceiris/actions-gh-pages@v4
-        env:
-          # On event/* branches, use the event name as the destination prefix
-          DEST_PREFIX: ${{ contains(github.ref_name, 'event/') && format('{0}/', github.ref_name) || '' }}
-        with:
-          deploy_key: ${{ secrets.DIST_PAGES_DEPLOY_KEY }}
-          external_repository: meshtastic/meshtastic.github.io
-          publish_branch: master
-          publish_dir: ./publish
-          destination_dir: ${{ env.DEST_PREFIX }}firmware-${{ needs.version.outputs.long }}
-          keep_files: true
-          user_name: github-actions[bot]
-          user_email: github-actions[bot]@users.noreply.github.com
-          commit_message: ${{ needs.version.outputs.long }}
-          enable_jekyll: true
diff --git a/.github/workflows/build_one_target.yml b/.github/workflows/build_one_target.yml
index 3c83ce960..ba1d5080f 100644
--- a/.github/workflows/build_one_target.yml
+++ b/.github/workflows/build_one_target.yml
@@ -3,6 +3,7 @@ name: Build One Target
 on:
   workflow_dispatch:
     inputs:
+      # trunk-ignore(checkov/CKV_GHA_7)
       arch:
         type: choice
         options:
@@ -19,11 +20,13 @@ on:
         type: string
         required: false
         description: Choose the target board, e.g. nrf52_promicro_diy_tcxo. If blank, will find available targets.
-
       # find-target:
       #   type: boolean
       #   default: true
       #   description: 'Find the available targets'
+
+permissions: read-all
+
 jobs:
   find-targets:
     if: ${{ inputs.target == '' }}
@@ -51,13 +54,13 @@ jobs:
       - name: Generate matrix
         id: jsonStep
         run: |
-          TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}} extra)
+          TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}} --level extra)
           echo "Name: $GITHUB_REF_NAME" >> $GITHUB_STEP_SUMMARY
           echo "Base: $GITHUB_BASE_REF" >> $GITHUB_STEP_SUMMARY
           echo "Arch: ${{matrix.arch}}" >> $GITHUB_STEP_SUMMARY
           echo "Ref: $GITHUB_REF" >> $GITHUB_STEP_SUMMARY
           echo "Targets:" >> $GITHUB_STEP_SUMMARY
-          echo $TARGETS | sed 's/[][]//g; s/", "/\n- /g; s/"//g; s/^/- /' >> $GITHUB_STEP_SUMMARY
+          echo $TARGETS >> $GITHUB_STEP_SUMMARY
 
   version:
     if: ${{ inputs.target != '' }}
@@ -75,11 +78,9 @@ jobs:
       long: ${{ steps.version.outputs.long }}
       deb: ${{ steps.version.outputs.deb }}
 
-  build-arch:
+  build:
     if: ${{ inputs.target != '' && inputs.arch != 'native' }}
     needs: [version]
-    strategy:
-      fail-fast: false
     uses: ./.github/workflows/build_firmware.yml
     with:
       version: ${{ needs.version.outputs.long }}
@@ -165,10 +166,8 @@ jobs:
     permissions:
       contents: write
       pull-requests: write
-    strategy:
-      fail-fast: false
     runs-on: ubuntu-latest
-    needs: [version, build-arch]
+    needs: [version, build]
     steps:
       - name: Checkout code
         uses: actions/checkout@v5
@@ -237,159 +236,3 @@ jobs:
           name: firmware-${{inputs.target}}-${{ needs.version.outputs.long }}
           description: "Download firmware-${{inputs.target}}-${{ needs.version.outputs.long }}.zip. This artifact will be available for 90 days from creation"
           github-token: ${{ secrets.GITHUB_TOKEN }}
-
-  release-artifacts:
-    runs-on: ubuntu-latest
-    if: ${{ github.event_name == 'workflow_dispatch' && inputs.target != ''}}
-    outputs:
-      upload_url: ${{ steps.create_release.outputs.upload_url }}
-    needs:
-      - version
-      - gather-artifacts
-      - build-debian-src
-      - package-pio-deps-native-tft
-    steps:
-      - name: Checkout
-        uses: actions/checkout@v5
-
-      - name: Setup Python
-        uses: actions/setup-python@v6
-        with:
-          python-version: 3.x
-
-      - name: Create release
-        uses: softprops/action-gh-release@v2
-        id: create_release
-        with:
-          draft: true
-          prerelease: true
-          name: Meshtastic Firmware ${{ needs.version.outputs.long }} Alpha
-          tag_name: v${{ needs.version.outputs.long }}
-          body: |
-            Autogenerated by github action, developer should edit as required before publishing...
-
-      - name: Download source deb
-        uses: actions/download-artifact@v5
-        with:
-          pattern: firmware-debian-${{ needs.version.outputs.deb }}~UNRELEASED-src
-          merge-multiple: true
-          path: ./output/debian-src
-
-      - name: Download `native-tft` pio deps
-        uses: actions/download-artifact@v5
-        with:
-          pattern: platformio-deps-native-tft-${{ needs.version.outputs.long }}
-          merge-multiple: true
-          path: ./output/pio-deps-native-tft
-
-      - name: Zip Linux sources
-        working-directory: output
-        run: |
-          zip -j -9 -r ./meshtasticd-${{ needs.version.outputs.deb }}-src.zip ./debian-src
-          zip -9 -r ./platformio-deps-native-tft-${{ needs.version.outputs.long }}.zip ./pio-deps-native-tft
-
-      # For diagnostics
-      - name: Display structure of downloaded files
-        run: ls -lR
-
-      - name: Add Linux sources to GtiHub Release
-        # Only run when targeting master branch with workflow_dispatch
-        if: ${{ github.ref_name == 'master' }}
-        run: |
-          gh release upload v${{ needs.version.outputs.long }} ./output/meshtasticd-${{ needs.version.outputs.deb }}-src.zip
-          gh release upload v${{ needs.version.outputs.long }} ./output/platformio-deps-native-tft-${{ needs.version.outputs.long }}.zip
-        env:
-          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-
-  release-firmware:
-    strategy:
-      fail-fast: false
-    runs-on: ubuntu-latest
-    if: ${{ github.event_name == 'workflow_dispatch' && inputs.target != ''}}
-    needs: [release-artifacts, version]
-    steps:
-      - name: Checkout
-        uses: actions/checkout@v5
-
-      - name: Setup Python
-        uses: actions/setup-python@v6
-        with:
-          python-version: 3.x
-
-      - uses: actions/download-artifact@v5
-        with:
-          pattern: firmware-*-${{ needs.version.outputs.long }}
-          merge-multiple: true
-          path: ./output
-
-      - name: Display structure of downloaded files
-        run: ls -lR
-
-      - name: Device scripts permissions
-        run: |
-          chmod +x ./output/device-install.sh
-          chmod +x ./output/device-update.sh
-
-      - name: Zip firmware
-        run: zip -j -9 -r ./firmware-${{inputs.target}}-${{ needs.version.outputs.long }}.zip ./output
-
-      - uses: actions/download-artifact@v5
-        with:
-          pattern: debug-elfs-*-${{ needs.version.outputs.long }}.zip
-          merge-multiple: true
-          path: ./elfs
-
-      - name: Zip debug elfs
-        run: zip -j -9 -r ./debug-elfs-${{inputs.target}}-${{ needs.version.outputs.long }}.zip ./elfs
-
-      # For diagnostics
-      - name: Display structure of downloaded files
-        run: ls -lR
-
-      - name: Add bins and debug elfs to GitHub Release
-        # Only run when targeting master branch with workflow_dispatch
-        if: ${{ github.ref_name == 'master' }}
-        run: |
-          gh release upload v${{ needs.version.outputs.long }} ./firmware-${{inputs.target}}-${{ needs.version.outputs.long }}.zip
-          gh release upload v${{ needs.version.outputs.long }} ./debug-elfs-${{inputs.target}}-${{ needs.version.outputs.long }}.zip
-        env:
-          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-
-  publish-firmware:
-    runs-on: ubuntu-24.04
-    if: ${{ github.event_name == 'workflow_dispatch' && github.repository == 'meshtastic/firmware' && inputs.target != '' }}
-    needs: [release-firmware, version]
-    env:
-      targets: |-
-        esp32,esp32s3,esp32c3,esp32c6,nrf52840,rp2040,rp2350,stm32
-    steps:
-      - name: Checkout
-        uses: actions/checkout@v5
-
-      - name: Setup Python
-        uses: actions/setup-python@v6
-        with:
-          python-version: 3.x
-
-      - uses: actions/download-artifact@v5
-        with:
-          pattern: firmware-{${{ env.targets }}}-${{ needs.version.outputs.long }}
-          merge-multiple: true
-          path: ./publish
-
-      - name: Publish firmware to meshtastic.github.io
-        uses: peaceiris/actions-gh-pages@v4
-        env:
-          # On event/* branches, use the event name as the destination prefix
-          DEST_PREFIX: ${{ contains(github.ref_name, 'event/') && format('{0}/', github.ref_name) || '' }}
-        with:
-          deploy_key: ${{ secrets.DIST_PAGES_DEPLOY_KEY }}
-          external_repository: meshtastic/meshtastic.github.io
-          publish_branch: master
-          publish_dir: ./publish
-          destination_dir: ${{ env.DEST_PREFIX }}firmware-${{ needs.version.outputs.long }}
-          keep_files: true
-          user_name: github-actions[bot]
-          user_email: github-actions[bot]@users.noreply.github.com
-          commit_message: ${{ needs.version.outputs.long }}
-          enable_jekyll: true
diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml
index f61e314a7..887bf3081 100644
--- a/.github/workflows/main_matrix.yml
+++ b/.github/workflows/main_matrix.yml
@@ -27,19 +27,11 @@ on:
 
 jobs:
   setup:
-    if: github.repository == 'meshtastic/firmware'
     strategy:
-      fail-fast: false
+      fail-fast: true
       matrix:
         arch:
-          - esp32
-          - esp32s3
-          - esp32c3
-          - esp32c6
-          - nrf52840
-          - rp2040
-          - rp2350
-          - stm32
+          - all
           - check
     runs-on: ubuntu-24.04
     steps:
@@ -49,33 +41,22 @@ 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: |
           if [[ "$GITHUB_HEAD_REF" == "" ]]; then
             TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}})
           else  
-            TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}} pr)
+            TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}} --level pr)
           fi
-          echo "Name: $GITHUB_REF_NAME Base: $GITHUB_BASE_REF Ref: $GITHUB_REF Targets: $TARGETS"
-          echo "${{matrix.arch}}=$(jq -cn --argjson environments "$TARGETS" '{board: $environments}')" >> $GITHUB_OUTPUT
+          echo "Name: $GITHUB_REF_NAME Base: $GITHUB_BASE_REF Ref: $GITHUB_REF"
+          echo "${{matrix.arch}}=$TARGETS" >> $GITHUB_OUTPUT
+          echo "$TARGETS" >> $GITHUB_STEP_SUMMARY
     outputs:
-      esp32: ${{ steps.jsonStep.outputs.esp32 }}
-      esp32s3: ${{ steps.jsonStep.outputs.esp32s3 }}
-      esp32c3: ${{ steps.jsonStep.outputs.esp32c3 }}
-      esp32c6: ${{ steps.jsonStep.outputs.esp32c6 }}
-      nrf52840: ${{ steps.jsonStep.outputs.nrf52840 }}
-      rp2040: ${{ steps.jsonStep.outputs.rp2040 }}
-      rp2350: ${{ steps.jsonStep.outputs.rp2350 }}
-      stm32: ${{ steps.jsonStep.outputs.stm32 }}
+      all: ${{ steps.jsonStep.outputs.all }}
       check: ${{ steps.jsonStep.outputs.check }}
 
   version:
-    if: github.repository == 'meshtastic/firmware'
     runs-on: ubuntu-latest
     steps:
       - uses: actions/checkout@v5
@@ -94,7 +75,8 @@ jobs:
     needs: setup
     strategy:
       fail-fast: false
-      matrix: ${{ fromJson(needs.setup.outputs.check) }}
+      matrix:
+        check: ${{ fromJson(needs.setup.outputs.check) }}
 
     runs-on: ubuntu-latest
     if: ${{ github.event_name != 'workflow_dispatch' && github.repository == 'meshtastic/firmware' }}
@@ -103,96 +85,20 @@ jobs:
       - name: Build base
         id: base
         uses: ./.github/actions/setup-base
-      - name: Check ${{ matrix.board }}
-        run: bin/check-all.sh ${{ matrix.board }}
+      - name: Check ${{ matrix.check.board }}
+        run: bin/check-all.sh ${{ matrix.check.board }}
 
-  build-esp32:
+  build:
     needs: [setup, version]
     strategy:
       fail-fast: false
-      matrix: ${{ fromJson(needs.setup.outputs.esp32) }}
+      matrix:
+        build: ${{ fromJson(needs.setup.outputs.all) }}
     uses: ./.github/workflows/build_firmware.yml
     with:
       version: ${{ needs.version.outputs.long }}
-      pio_env: ${{ matrix.board }}
-      platform: esp32
-
-  build-esp32s3:
-    needs: [setup, version]
-    strategy:
-      fail-fast: false
-      matrix: ${{ fromJson(needs.setup.outputs.esp32s3) }}
-    uses: ./.github/workflows/build_firmware.yml
-    with:
-      version: ${{ needs.version.outputs.long }}
-      pio_env: ${{ matrix.board }}
-      platform: esp32s3
-
-  build-esp32c3:
-    needs: [setup, version]
-    strategy:
-      fail-fast: false
-      matrix: ${{ fromJson(needs.setup.outputs.esp32c3) }}
-    uses: ./.github/workflows/build_firmware.yml
-    with:
-      version: ${{ needs.version.outputs.long }}
-      pio_env: ${{ matrix.board }}
-      platform: esp32c3
-
-  build-esp32c6:
-    needs: [setup, version]
-    strategy:
-      fail-fast: false
-      matrix: ${{ fromJson(needs.setup.outputs.esp32c6) }}
-    uses: ./.github/workflows/build_firmware.yml
-    with:
-      version: ${{ needs.version.outputs.long }}
-      pio_env: ${{ matrix.board }}
-      platform: esp32c6
-
-  build-nrf52840:
-    needs: [setup, version]
-    strategy:
-      fail-fast: false
-      matrix: ${{ fromJson(needs.setup.outputs.nrf52840) }}
-    uses: ./.github/workflows/build_firmware.yml
-    with:
-      version: ${{ needs.version.outputs.long }}
-      pio_env: ${{ matrix.board }}
-      platform: nrf52840
-
-  build-rp2040:
-    needs: [setup, version]
-    strategy:
-      fail-fast: false
-      matrix: ${{ fromJson(needs.setup.outputs.rp2040) }}
-    uses: ./.github/workflows/build_firmware.yml
-    with:
-      version: ${{ needs.version.outputs.long }}
-      pio_env: ${{ matrix.board }}
-      platform: rp2040
-
-  build-rp2350:
-    needs: [setup, version]
-    strategy:
-      fail-fast: false
-      matrix: ${{ fromJson(needs.setup.outputs.rp2350) }}
-    uses: ./.github/workflows/build_firmware.yml
-    with:
-      version: ${{ needs.version.outputs.long }}
-      pio_env: ${{ matrix.board }}
-      platform: rp2350
-
-  build-stm32:
-    needs: [setup, version]
-    strategy:
-      fail-fast: false
-      matrix: ${{ fromJson(needs.setup.outputs.stm32) }}
-    uses: ./.github/workflows/build_firmware.yml
-    with:
-      version: ${{ needs.version.outputs.long }}
-      pio_env: ${{ matrix.board }}
-      platform: stm32
+      pio_env: ${{ matrix.build.board }}
+      platform: ${{ matrix.build.platform }}
 
   build-debian-src:
     if: github.repository == 'meshtastic/firmware'
@@ -203,7 +109,7 @@ jobs:
     secrets: inherit
 
   package-pio-deps-native-tft:
-    if: ${{ github.event_name == 'workflow_dispatch' }}
+    if: ${{ github.repository == 'meshtastic/firmware' && github.event_name == 'workflow_dispatch' }}
     uses: ./.github/workflows/package_pio_deps.yml
     with:
       pio_env: native-tft
@@ -288,18 +194,7 @@ jobs:
           - rp2350
           - stm32
     runs-on: ubuntu-latest
-    needs:
-      [
-        version,
-        build-esp32,
-        build-esp32s3,
-        build-esp32c3,
-        build-esp32c6,
-        build-nrf52840,
-        build-rp2040,
-        build-rp2350,
-        build-stm32,
-      ]
+    needs: [version, build]
     steps:
       - name: Checkout code
         uses: actions/checkout@v5
diff --git a/.github/workflows/merge_queue.yml b/.github/workflows/merge_queue.yml
index 7f397ce18..79e8b0803 100644
--- a/.github/workflows/merge_queue.yml
+++ b/.github/workflows/merge_queue.yml
@@ -7,23 +7,13 @@ on:
   # Merge group is a special trigger that is used to trigger the workflow when a merge group is created.
   merge_group:
 
-env:
-  FAIL_FAST_PER_ARCH: true
-
 jobs:
   setup:
     strategy:
       fail-fast: true
       matrix:
         arch:
-          - esp32
-          - esp32s3
-          - esp32c3
-          - esp32c6
-          - nrf52840
-          - rp2040
-          - rp2350
-          - stm32
+          - all
           - check
     runs-on: ubuntu-24.04
     steps:
@@ -39,19 +29,12 @@ jobs:
           if [[ "$GITHUB_HEAD_REF" == "" ]]; then
             TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}})
           else  
-            TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}} pr)
+            TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}} --level pr)
           fi
-          echo "Name: $GITHUB_REF_NAME Base: $GITHUB_BASE_REF Ref: $GITHUB_REF Targets: $TARGETS"
-          echo "${{matrix.arch}}=$(jq -cn --argjson environments "$TARGETS" '{board: $environments}')" >> $GITHUB_OUTPUT
+          echo "Name: $GITHUB_REF_NAME Base: $GITHUB_BASE_REF Ref: $GITHUB_REF"
+          echo "${{matrix.arch}}=$TARGETS" >> $GITHUB_OUTPUT
     outputs:
-      esp32: ${{ steps.jsonStep.outputs.esp32 }}
-      esp32s3: ${{ steps.jsonStep.outputs.esp32s3 }}
-      esp32c3: ${{ steps.jsonStep.outputs.esp32c3 }}
-      esp32c6: ${{ steps.jsonStep.outputs.esp32c6 }}
-      nrf52840: ${{ steps.jsonStep.outputs.nrf52840 }}
-      rp2040: ${{ steps.jsonStep.outputs.rp2040 }}
-      rp2350: ${{ steps.jsonStep.outputs.rp2350 }}
-      stm32: ${{ steps.jsonStep.outputs.stm32 }}
+      all: ${{ steps.jsonStep.outputs.all }}
       check: ${{ steps.jsonStep.outputs.check }}
 
   version:
@@ -73,7 +56,8 @@ jobs:
     needs: setup
     strategy:
       fail-fast: true
-      matrix: ${{ fromJson(needs.setup.outputs.check) }}
+      matrix:
+        check: ${{ fromJson(needs.setup.outputs.check) }}
 
     runs-on: ubuntu-latest
     if: ${{ github.event_name != 'workflow_dispatch' }}
@@ -82,96 +66,19 @@ jobs:
       - name: Build base
         id: base
         uses: ./.github/actions/setup-base
-      - name: Check ${{ matrix.board }}
-        run: bin/check-all.sh ${{ matrix.board }}
+      - name: Check ${{ matrix.check.board }}
+        run: bin/check-all.sh ${{ matrix.check.board }}
 
-  build-esp32:
+  build:
     needs: [setup, version]
     strategy:
-      fail-fast: ${{ vars.FAIL_FAST_PER_ARCH == true }}
-      matrix: ${{ fromJson(needs.setup.outputs.esp32) }}
+      matrix:
+        build: ${{ fromJson(needs.setup.outputs.all) }}
     uses: ./.github/workflows/build_firmware.yml
     with:
       version: ${{ needs.version.outputs.long }}
-      pio_env: ${{ matrix.board }}
-      platform: esp32
-
-  build-esp32s3:
-    needs: [setup, version]
-    strategy:
-      fail-fast: ${{ vars.FAIL_FAST_PER_ARCH == true }}
-      matrix: ${{ fromJson(needs.setup.outputs.esp32s3) }}
-    uses: ./.github/workflows/build_firmware.yml
-    with:
-      version: ${{ needs.version.outputs.long }}
-      pio_env: ${{ matrix.board }}
-      platform: esp32s3
-
-  build-esp32c3:
-    needs: [setup, version]
-    strategy:
-      fail-fast: ${{ vars.FAIL_FAST_PER_ARCH == true }}
-      matrix: ${{ fromJson(needs.setup.outputs.esp32c3) }}
-    uses: ./.github/workflows/build_firmware.yml
-    with:
-      version: ${{ needs.version.outputs.long }}
-      pio_env: ${{ matrix.board }}
-      platform: esp32c3
-
-  build-esp32c6:
-    needs: [setup, version]
-    strategy:
-      fail-fast: ${{ vars.FAIL_FAST_PER_ARCH == true }}
-      matrix: ${{ fromJson(needs.setup.outputs.esp32c6) }}
-    uses: ./.github/workflows/build_firmware.yml
-    with:
-      version: ${{ needs.version.outputs.long }}
-      pio_env: ${{ matrix.board }}
-      platform: esp32c6
-
-  build-nrf52840:
-    needs: [setup, version]
-    strategy:
-      fail-fast: ${{ vars.FAIL_FAST_PER_ARCH == true }}
-      matrix: ${{ fromJson(needs.setup.outputs.nrf52840) }}
-    uses: ./.github/workflows/build_firmware.yml
-    with:
-      version: ${{ needs.version.outputs.long }}
-      pio_env: ${{ matrix.board }}
-      platform: nrf52840
-
-  build-rp2040:
-    needs: [setup, version]
-    strategy:
-      fail-fast: ${{ vars.FAIL_FAST_PER_ARCH == true }}
-      matrix: ${{ fromJson(needs.setup.outputs.rp2040) }}
-    uses: ./.github/workflows/build_firmware.yml
-    with:
-      version: ${{ needs.version.outputs.long }}
-      pio_env: ${{ matrix.board }}
-      platform: rp2040
-
-  build-rp2350:
-    needs: [setup, version]
-    strategy:
-      fail-fast: ${{ vars.FAIL_FAST_PER_ARCH == true }}
-      matrix: ${{ fromJson(needs.setup.outputs.rp2350) }}
-    uses: ./.github/workflows/build_firmware.yml
-    with:
-      version: ${{ needs.version.outputs.long }}
-      pio_env: ${{ matrix.board }}
-      platform: rp2350
-
-  build-stm32:
-    needs: [setup, version]
-    strategy:
-      fail-fast: ${{ vars.FAIL_FAST_PER_ARCH == true }}
-      matrix: ${{ fromJson(needs.setup.outputs.stm32) }}
-    uses: ./.github/workflows/build_firmware.yml
-    with:
-      version: ${{ needs.version.outputs.long }}
-      pio_env: ${{ matrix.board }}
-      platform: stm32
+      pio_env: ${{ matrix.build.board }}
+      platform: ${{ matrix.build.platform }}
 
   build-debian-src:
     if: github.repository == 'meshtastic/firmware'
@@ -260,18 +167,7 @@ jobs:
           - rp2350
           - stm32
     runs-on: ubuntu-latest
-    needs:
-      [
-        version,
-        build-esp32,
-        build-esp32s3,
-        build-esp32c3,
-        build-esp32c6,
-        build-nrf52840,
-        build-rp2040,
-        build-rp2350,
-        build-stm32,
-      ]
+    needs: [version, build]
     steps:
       - name: Checkout code
         uses: actions/checkout@v5
diff --git a/bin/generate_ci_matrix.py b/bin/generate_ci_matrix.py
index aaa76aa45..b4c18c05b 100755
--- a/bin/generate_ci_matrix.py
+++ b/bin/generate_ci_matrix.py
@@ -1,28 +1,32 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 """Generate the CI matrix."""
 
+import argparse
 import json
-import sys
-import random
 import re
 from platformio.project.config import ProjectConfig
 
-options = sys.argv[1:]
+parser = argparse.ArgumentParser(description="Generate the CI matrix")
+parser.add_argument("platform", help="Platform to build for")
+parser.add_argument(
+  "--level",
+  choices=["extra", "pr"],
+  nargs="*",
+  default=[],
+  help="Board level to build for (omit for full release boards)",
+)
+args = parser.parse_args()
 
 outlist = []
 
-if len(options) < 1:
-  print(json.dumps(outlist))
-  exit(1)
-
 cfg = ProjectConfig.get_instance()
 pio_envs = cfg.envs()
 
 # Gather all PlatformIO environments for filtering later
 all_envs = []
 for pio_env in pio_envs:
-  env_build_flags = cfg.get(f"env:{pio_env}", 'build_flags')
+  env_build_flags = cfg.get(f"env:{pio_env}", "build_flags")
   env_platform = None
   for flag in env_build_flags:
     # Extract the platform from the build flags
@@ -37,36 +41,35 @@ for pio_env in pio_envs:
     exit(1)
   # Store env details as a dictionary, and add to 'all_envs' list
   env = {
-    'name': pio_env,
-    'platform': env_platform,
-    'board_level': cfg.get(f"env:{pio_env}", 'board_level', default=None),
-    'board_check': bool(cfg.get(f"env:{pio_env}", 'board_check', default=False))
+    "ci": {"board": pio_env, "platform": env_platform},
+    "board_level": cfg.get(f"env:{pio_env}", "board_level", default=None),
+    "board_check": bool(cfg.get(f"env:{pio_env}", "board_check", default=False)),
   }
   all_envs.append(env)
 
 # Filter outputs based on options
 # Check is mutually exclusive with other options (except 'pr')
-if "check" in options:
+if "check" in args.platform:
   for env in all_envs:
-    if env['board_check']:
-      if "pr" in options:
-        if env['board_level'] == 'pr':
-          outlist.append(env['name'])
+    if env["board_check"]:
+      if "pr" in args.level:
+        if env["board_level"] == "pr":
+          outlist.append(env["ci"])
       else:
-        outlist.append(env['name'])
+        outlist.append(env["ci"])
 # Filter (non-check) builds by platform
 else:
   for env in all_envs:
-    if options[0] == env['platform']:
+    if args.platform == env["ci"]["platform"] or args.platform == "all":
       # Always include board_level = 'pr'
-      if env['board_level'] == 'pr':
-        outlist.append(env['name'])
+      if env["board_level"] == "pr":
+        outlist.append(env["ci"])
       # Include board_level = 'extra' when requested
-      elif "extra" in options and env['board_level'] == "extra":
-        outlist.append(env['name'])
+      elif "extra" in args.level and env["board_level"] == "extra":
+        outlist.append(env["ci"])
       # If no board level is specified, include in release builds (not PR)
-      elif "pr" not in options and not env['board_level']:
-        outlist.append(env['name'])
+      elif "pr" not in args.level and not env["board_level"]:
+        outlist.append(env["ci"])
 
 # Return as a JSON list
 print(json.dumps(outlist))
diff --git a/variants/native/portduino-buildroot/platformio.ini b/variants/native/portduino-buildroot/platformio.ini
index d1bd39e10..a3d0f4639 100644
--- a/variants/native/portduino-buildroot/platformio.ini
+++ b/variants/native/portduino-buildroot/platformio.ini
@@ -4,5 +4,6 @@ extends = portduino_base
 ; environment variable in the buildroot environment.
 build_flags = ${portduino_base.build_flags} -O0 -I variants/native/portduino-buildroot
 board = buildroot
+board_level = extra
 lib_deps = ${portduino_base.lib_deps}
 build_src_filter = ${portduino_base.build_src_filter}
\ No newline at end of file
diff --git a/variants/native/portduino/platformio.ini b/variants/native/portduino/platformio.ini
index 61eadb459..2ccdfbbfc 100644
--- a/variants/native/portduino/platformio.ini
+++ b/variants/native/portduino/platformio.ini
@@ -3,6 +3,7 @@ extends = portduino_base
 build_flags = ${portduino_base.build_flags} -I variants/native/portduino
   -I /usr/include
 board = cross_platform
+board_level = extra
 lib_deps = 
   ${portduino_base.lib_deps}
   melopero/Melopero RV3028@^1.1.0
@@ -50,7 +51,6 @@ build_type = release
 lib_deps =
   ${native_base.lib_deps}
   ${device-ui_base.lib_deps}
-board_level = extra
 build_flags = ${native_base.build_flags} -Os -ffunction-sections -fdata-sections -Wl,--gc-sections
   -D RAM_SIZE=8192
   -D USE_FRAMEBUFFER=1
@@ -79,7 +79,6 @@ build_type = debug
 lib_deps =
   ${native_base.lib_deps}
   ${device-ui_base.lib_deps}
-board_level = extra
 build_flags = ${native_base.build_flags} -O0 -fsanitize=address -lX11 -linput -lxkbcommon
   -D DEBUG_HEAP
   -D RAM_SIZE=16384

From 518680514f6249866ade68b7b11c5906ee1599ca Mon Sep 17 00:00:00 2001
From: Tom Fifield 
Date: Tue, 7 Oct 2025 13:37:13 +1100
Subject: [PATCH 433/691] Actions: Simplify matrices, cleanup build_one_*
 (#8218) (#8239)

Co-authored-by: Austin 
---
 .github/workflows/build_firmware.yml          |   3 +
 .github/workflows/build_one_arch.yml          | 302 +-----------------
 .github/workflows/build_one_target.yml        | 173 +---------
 .github/workflows/main_matrix.yml             | 135 ++------
 .github/workflows/merge_queue.yml             | 134 +-------
 bin/generate_ci_matrix.py                     |  55 ++--
 .../native/portduino-buildroot/platformio.ini |   1 +
 variants/native/portduino/platformio.ini      |   3 +-
 8 files changed, 92 insertions(+), 714 deletions(-)

diff --git a/.github/workflows/build_firmware.yml b/.github/workflows/build_firmware.yml
index 2ef67405a..b62729332 100644
--- a/.github/workflows/build_firmware.yml
+++ b/.github/workflows/build_firmware.yml
@@ -19,6 +19,8 @@ jobs:
   pio-build:
     name: build-${{ inputs.platform }}
     runs-on: ubuntu-24.04
+    outputs:
+      artifact-id: ${{ steps.upload.outputs.artifact-id }}
     steps:
       - uses: actions/checkout@v5
         with:
@@ -55,6 +57,7 @@ jobs:
 
       - name: Store binaries as an artifact
         uses: actions/upload-artifact@v4
+        id: upload
         with:
           name: firmware-${{ inputs.platform }}-${{ inputs.pio_env }}-${{ inputs.version }}.zip
           overwrite: true
diff --git a/.github/workflows/build_one_arch.yml b/.github/workflows/build_one_arch.yml
index f5352b3c4..6d9134941 100644
--- a/.github/workflows/build_one_arch.yml
+++ b/.github/workflows/build_one_arch.yml
@@ -3,6 +3,7 @@ name: Build One Arch
 on:
   workflow_dispatch:
     inputs:
+      # trunk-ignore(checkov/CKV_GHA_7)
       arch:
         type: choice
         options:
@@ -16,10 +17,13 @@ on:
           - stm32
           - native
 
+permissions: read-all
+
+env:
+  INPUT_ARCH: ${{ github.event.inputs.arch }}
+
 jobs:
   setup:
-    strategy:
-      fail-fast: false
     runs-on: ubuntu-24.04
     steps:
       - uses: actions/checkout@v5
@@ -31,23 +35,11 @@ jobs:
       - name: Generate matrix
         id: jsonStep
         run: |
-          if [[ "$GITHUB_HEAD_REF" == "" ]]; then
-            TARGETS=$(./bin/generate_ci_matrix.py ${{inputs.arch}} extra)
-          else  
-            TARGETS=$(./bin/generate_ci_matrix.py ${{inputs.arch}} pr)
-          fi
-          echo "Name: $GITHUB_REF_NAME Base: $GITHUB_BASE_REF Ref: $GITHUB_REF Targets: $TARGETS"
-          echo "${{inputs.arch}}=$(jq -cn --argjson environments "$TARGETS" '{board: $environments}')" >> $GITHUB_OUTPUT
+          TARGETS=$(./bin/generate_ci_matrix.py $INPUT_ARCH --level extra)
+          echo "Name: $GITHUB_REF_NAME Base: $GITHUB_BASE_REF Ref: $GITHUB_REF"
+          echo "selected_arch=$TARGETS" >> $GITHUB_OUTPUT
     outputs:
-      esp32: ${{ steps.jsonStep.outputs.esp32 }}
-      esp32s3: ${{ steps.jsonStep.outputs.esp32s3 }}
-      esp32c3: ${{ steps.jsonStep.outputs.esp32c3 }}
-      esp32c6: ${{ steps.jsonStep.outputs.esp32c6 }}
-      nrf52840: ${{ steps.jsonStep.outputs.nrf52840 }}
-      rp2040: ${{ steps.jsonStep.outputs.rp2040 }}
-      rp2350: ${{ steps.jsonStep.outputs.rp2350 }}
-      stm32: ${{ steps.jsonStep.outputs.stm32 }}
-      check: ${{ steps.jsonStep.outputs.check }}
+      selected_arch: ${{ steps.jsonStep.outputs.selected_arch }}
 
   version:
     runs-on: ubuntu-latest
@@ -64,101 +56,18 @@ jobs:
       long: ${{ steps.version.outputs.long }}
       deb: ${{ steps.version.outputs.deb }}
 
-  build-esp32:
-    if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'esp32'}}
+  build:
+    if: ${{ github.event_name != 'workflow_dispatch' }}
     needs: [setup, version]
     strategy:
       fail-fast: false
-      matrix: ${{ fromJson(needs.setup.outputs.esp32) }}
+      matrix:
+        build: ${{ fromJson(needs.setup.outputs.selected_arch) }}
     uses: ./.github/workflows/build_firmware.yml
     with:
       version: ${{ needs.version.outputs.long }}
-      pio_env: ${{ matrix.board }}
-      platform: esp32
-
-  build-esp32s3:
-    if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'esp32s3'}}
-    needs: [setup, version]
-    strategy:
-      fail-fast: false
-      matrix: ${{ fromJson(needs.setup.outputs.esp32s3) }}
-    uses: ./.github/workflows/build_firmware.yml
-    with:
-      version: ${{ needs.version.outputs.long }}
-      pio_env: ${{ matrix.board }}
-      platform: esp32s3
-
-  build-esp32c3:
-    if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'esp32c3'}}
-    needs: [setup, version]
-    strategy:
-      fail-fast: false
-      matrix: ${{ fromJson(needs.setup.outputs.esp32c3) }}
-    uses: ./.github/workflows/build_firmware.yml
-    with:
-      version: ${{ needs.version.outputs.long }}
-      pio_env: ${{ matrix.board }}
-      platform: esp32c3
-
-  build-esp32c6:
-    if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'esp32c6'}}
-    needs: [setup, version]
-    strategy:
-      fail-fast: false
-      matrix: ${{ fromJson(needs.setup.outputs.esp32c6) }}
-    uses: ./.github/workflows/build_firmware.yml
-    with:
-      version: ${{ needs.version.outputs.long }}
-      pio_env: ${{ matrix.board }}
-      platform: esp32c6
-
-  build-nrf52840:
-    if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'nrf52840'}}
-    needs: [setup, version]
-    strategy:
-      fail-fast: false
-      matrix: ${{ fromJson(needs.setup.outputs.nrf52840) }}
-    uses: ./.github/workflows/build_firmware.yml
-    with:
-      version: ${{ needs.version.outputs.long }}
-      pio_env: ${{ matrix.board }}
-      platform: nrf52840
-
-  build-rp2040:
-    if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'rp2040'}}
-    needs: [setup, version]
-    strategy:
-      fail-fast: false
-      matrix: ${{ fromJson(needs.setup.outputs.rp2040) }}
-    uses: ./.github/workflows/build_firmware.yml
-    with:
-      version: ${{ needs.version.outputs.long }}
-      pio_env: ${{ matrix.board }}
-      platform: rp2040
-
-  build-rp2350:
-    if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'rp2350'}}
-    needs: [setup, version]
-    strategy:
-      fail-fast: false
-      matrix: ${{ fromJson(needs.setup.outputs.rp2350) }}
-    uses: ./.github/workflows/build_firmware.yml
-    with:
-      version: ${{ needs.version.outputs.long }}
-      pio_env: ${{ matrix.board }}
-      platform: rp2350
-
-  build-stm32:
-    if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'stm32' }}
-    needs: [setup, version]
-    strategy:
-      fail-fast: false
-      matrix: ${{ fromJson(needs.setup.outputs.stm32) }}
-    uses: ./.github/workflows/build_firmware.yml
-    with:
-      version: ${{ needs.version.outputs.long }}
-      pio_env: ${{ matrix.board }}
-      platform: stm32
+      pio_env: ${{ matrix.build.board }}
+      platform: ${{ matrix.build.arch }}
 
   build-debian-src:
     if: ${{ github.repository == 'meshtastic/firmware' && github.event_name != 'workflow_dispatch' || inputs.arch == 'native' }}
@@ -252,18 +161,7 @@ jobs:
           - rp2350
           - stm32
     runs-on: ubuntu-latest
-    needs:
-      [
-        version,
-        build-esp32,
-        build-esp32s3,
-        build-esp32c3,
-        build-esp32c6,
-        build-nrf52840,
-        build-rp2040,
-        build-rp2350,
-        build-stm32,
-      ]
+    needs: [version, build]
     steps:
       - name: Checkout code
         uses: actions/checkout@v5
@@ -332,169 +230,3 @@ jobs:
           name: firmware-${{inputs.arch}}-${{ needs.version.outputs.long }}
           description: "Download firmware-${{inputs.arch}}-${{ needs.version.outputs.long }}.zip. This artifact will be available for 90 days from creation"
           github-token: ${{ secrets.GITHUB_TOKEN }}
-
-  release-artifacts:
-    runs-on: ubuntu-latest
-    if: ${{ github.event_name == 'workflow_dispatch' }}
-    outputs:
-      upload_url: ${{ steps.create_release.outputs.upload_url }}
-    needs:
-      - version
-      - gather-artifacts
-      - build-debian-src
-      - package-pio-deps-native-tft
-    steps:
-      - name: Checkout
-        uses: actions/checkout@v5
-
-      - name: Setup Python
-        uses: actions/setup-python@v6
-        with:
-          python-version: 3.x
-
-      - name: Create release
-        uses: softprops/action-gh-release@v2
-        id: create_release
-        with:
-          draft: true
-          prerelease: true
-          name: Meshtastic Firmware ${{ needs.version.outputs.long }} Alpha
-          tag_name: v${{ needs.version.outputs.long }}
-          body: |
-            Autogenerated by github action, developer should edit as required before publishing...
-
-      - name: Download source deb
-        uses: actions/download-artifact@v5
-        with:
-          pattern: firmware-debian-${{ needs.version.outputs.deb }}~UNRELEASED-src
-          merge-multiple: true
-          path: ./output/debian-src
-
-      - name: Download `native-tft` pio deps
-        uses: actions/download-artifact@v5
-        with:
-          pattern: platformio-deps-native-tft-${{ needs.version.outputs.long }}
-          merge-multiple: true
-          path: ./output/pio-deps-native-tft
-
-      - name: Zip Linux sources
-        working-directory: output
-        run: |
-          zip -j -9 -r ./meshtasticd-${{ needs.version.outputs.deb }}-src.zip ./debian-src
-          zip -9 -r ./platformio-deps-native-tft-${{ needs.version.outputs.long }}.zip ./pio-deps-native-tft
-
-      # For diagnostics
-      - name: Display structure of downloaded files
-        run: ls -lR
-
-      - name: Add Linux sources to GtiHub Release
-        # Only run when targeting master branch with workflow_dispatch
-        if: ${{ github.ref_name == 'master' }}
-        run: |
-          gh release upload v${{ needs.version.outputs.long }} ./output/meshtasticd-${{ needs.version.outputs.deb }}-src.zip
-          gh release upload v${{ needs.version.outputs.long }} ./output/platformio-deps-native-tft-${{ needs.version.outputs.long }}.zip
-        env:
-          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-
-  release-firmware:
-    strategy:
-      fail-fast: false
-      matrix:
-        arch:
-          - esp32
-          - esp32s3
-          - esp32c3
-          - esp32c6
-          - nrf52840
-          - rp2040
-          - rp2350
-          - stm32
-    runs-on: ubuntu-latest
-    if: ${{ github.event_name == 'workflow_dispatch' }}
-    needs: [release-artifacts, version]
-    steps:
-      - name: Checkout
-        uses: actions/checkout@v5
-
-      - name: Setup Python
-        uses: actions/setup-python@v6
-        with:
-          python-version: 3.x
-
-      - uses: actions/download-artifact@v5
-        with:
-          pattern: firmware-${{inputs.arch}}-${{ needs.version.outputs.long }}
-          merge-multiple: true
-          path: ./output
-
-      - name: Display structure of downloaded files
-        run: ls -lR
-
-      - name: Device scripts permissions
-        run: |
-          chmod +x ./output/device-install.sh
-          chmod +x ./output/device-update.sh
-
-      - name: Zip firmware
-        run: zip -j -9 -r ./firmware-${{inputs.arch}}-${{ needs.version.outputs.long }}.zip ./output
-
-      - uses: actions/download-artifact@v5
-        with:
-          name: debug-elfs-${{inputs.arch}}-${{ needs.version.outputs.long }}.zip
-          merge-multiple: true
-          path: ./elfs
-
-      - name: Zip debug elfs
-        run: zip -j -9 -r ./debug-elfs-${{inputs.arch}}-${{ needs.version.outputs.long }}.zip ./elfs
-
-      # For diagnostics
-      - name: Display structure of downloaded files
-        run: ls -lR
-
-      - name: Add bins and debug elfs to GitHub Release
-        # Only run when targeting master branch with workflow_dispatch
-        if: ${{ github.ref_name == 'master' }}
-        run: |
-          gh release upload v${{ needs.version.outputs.long }} ./firmware-${{inputs.arch}}-${{ needs.version.outputs.long }}.zip
-          gh release upload v${{ needs.version.outputs.long }} ./debug-elfs-${{inputs.arch}}-${{ needs.version.outputs.long }}.zip
-        env:
-          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-
-  publish-firmware:
-    runs-on: ubuntu-24.04
-    if: ${{ github.event_name == 'workflow_dispatch' }}
-    needs: [release-firmware, version]
-    env:
-      targets: |-
-        esp32,esp32s3,esp32c3,esp32c6,nrf52840,rp2040,rp2350,stm32
-    steps:
-      - name: Checkout
-        uses: actions/checkout@v5
-
-      - name: Setup Python
-        uses: actions/setup-python@v6
-        with:
-          python-version: 3.x
-
-      - uses: actions/download-artifact@v5
-        with:
-          pattern: firmware-{${{ env.targets }}}-${{ needs.version.outputs.long }}
-          merge-multiple: true
-          path: ./publish
-
-      - name: Publish firmware to meshtastic.github.io
-        uses: peaceiris/actions-gh-pages@v4
-        env:
-          # On event/* branches, use the event name as the destination prefix
-          DEST_PREFIX: ${{ contains(github.ref_name, 'event/') && format('{0}/', github.ref_name) || '' }}
-        with:
-          deploy_key: ${{ secrets.DIST_PAGES_DEPLOY_KEY }}
-          external_repository: meshtastic/meshtastic.github.io
-          publish_branch: master
-          publish_dir: ./publish
-          destination_dir: ${{ env.DEST_PREFIX }}firmware-${{ needs.version.outputs.long }}
-          keep_files: true
-          user_name: github-actions[bot]
-          user_email: github-actions[bot]@users.noreply.github.com
-          commit_message: ${{ needs.version.outputs.long }}
-          enable_jekyll: true
diff --git a/.github/workflows/build_one_target.yml b/.github/workflows/build_one_target.yml
index 3c83ce960..ba1d5080f 100644
--- a/.github/workflows/build_one_target.yml
+++ b/.github/workflows/build_one_target.yml
@@ -3,6 +3,7 @@ name: Build One Target
 on:
   workflow_dispatch:
     inputs:
+      # trunk-ignore(checkov/CKV_GHA_7)
       arch:
         type: choice
         options:
@@ -19,11 +20,13 @@ on:
         type: string
         required: false
         description: Choose the target board, e.g. nrf52_promicro_diy_tcxo. If blank, will find available targets.
-
       # find-target:
       #   type: boolean
       #   default: true
       #   description: 'Find the available targets'
+
+permissions: read-all
+
 jobs:
   find-targets:
     if: ${{ inputs.target == '' }}
@@ -51,13 +54,13 @@ jobs:
       - name: Generate matrix
         id: jsonStep
         run: |
-          TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}} extra)
+          TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}} --level extra)
           echo "Name: $GITHUB_REF_NAME" >> $GITHUB_STEP_SUMMARY
           echo "Base: $GITHUB_BASE_REF" >> $GITHUB_STEP_SUMMARY
           echo "Arch: ${{matrix.arch}}" >> $GITHUB_STEP_SUMMARY
           echo "Ref: $GITHUB_REF" >> $GITHUB_STEP_SUMMARY
           echo "Targets:" >> $GITHUB_STEP_SUMMARY
-          echo $TARGETS | sed 's/[][]//g; s/", "/\n- /g; s/"//g; s/^/- /' >> $GITHUB_STEP_SUMMARY
+          echo $TARGETS >> $GITHUB_STEP_SUMMARY
 
   version:
     if: ${{ inputs.target != '' }}
@@ -75,11 +78,9 @@ jobs:
       long: ${{ steps.version.outputs.long }}
       deb: ${{ steps.version.outputs.deb }}
 
-  build-arch:
+  build:
     if: ${{ inputs.target != '' && inputs.arch != 'native' }}
     needs: [version]
-    strategy:
-      fail-fast: false
     uses: ./.github/workflows/build_firmware.yml
     with:
       version: ${{ needs.version.outputs.long }}
@@ -165,10 +166,8 @@ jobs:
     permissions:
       contents: write
       pull-requests: write
-    strategy:
-      fail-fast: false
     runs-on: ubuntu-latest
-    needs: [version, build-arch]
+    needs: [version, build]
     steps:
       - name: Checkout code
         uses: actions/checkout@v5
@@ -237,159 +236,3 @@ jobs:
           name: firmware-${{inputs.target}}-${{ needs.version.outputs.long }}
           description: "Download firmware-${{inputs.target}}-${{ needs.version.outputs.long }}.zip. This artifact will be available for 90 days from creation"
           github-token: ${{ secrets.GITHUB_TOKEN }}
-
-  release-artifacts:
-    runs-on: ubuntu-latest
-    if: ${{ github.event_name == 'workflow_dispatch' && inputs.target != ''}}
-    outputs:
-      upload_url: ${{ steps.create_release.outputs.upload_url }}
-    needs:
-      - version
-      - gather-artifacts
-      - build-debian-src
-      - package-pio-deps-native-tft
-    steps:
-      - name: Checkout
-        uses: actions/checkout@v5
-
-      - name: Setup Python
-        uses: actions/setup-python@v6
-        with:
-          python-version: 3.x
-
-      - name: Create release
-        uses: softprops/action-gh-release@v2
-        id: create_release
-        with:
-          draft: true
-          prerelease: true
-          name: Meshtastic Firmware ${{ needs.version.outputs.long }} Alpha
-          tag_name: v${{ needs.version.outputs.long }}
-          body: |
-            Autogenerated by github action, developer should edit as required before publishing...
-
-      - name: Download source deb
-        uses: actions/download-artifact@v5
-        with:
-          pattern: firmware-debian-${{ needs.version.outputs.deb }}~UNRELEASED-src
-          merge-multiple: true
-          path: ./output/debian-src
-
-      - name: Download `native-tft` pio deps
-        uses: actions/download-artifact@v5
-        with:
-          pattern: platformio-deps-native-tft-${{ needs.version.outputs.long }}
-          merge-multiple: true
-          path: ./output/pio-deps-native-tft
-
-      - name: Zip Linux sources
-        working-directory: output
-        run: |
-          zip -j -9 -r ./meshtasticd-${{ needs.version.outputs.deb }}-src.zip ./debian-src
-          zip -9 -r ./platformio-deps-native-tft-${{ needs.version.outputs.long }}.zip ./pio-deps-native-tft
-
-      # For diagnostics
-      - name: Display structure of downloaded files
-        run: ls -lR
-
-      - name: Add Linux sources to GtiHub Release
-        # Only run when targeting master branch with workflow_dispatch
-        if: ${{ github.ref_name == 'master' }}
-        run: |
-          gh release upload v${{ needs.version.outputs.long }} ./output/meshtasticd-${{ needs.version.outputs.deb }}-src.zip
-          gh release upload v${{ needs.version.outputs.long }} ./output/platformio-deps-native-tft-${{ needs.version.outputs.long }}.zip
-        env:
-          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-
-  release-firmware:
-    strategy:
-      fail-fast: false
-    runs-on: ubuntu-latest
-    if: ${{ github.event_name == 'workflow_dispatch' && inputs.target != ''}}
-    needs: [release-artifacts, version]
-    steps:
-      - name: Checkout
-        uses: actions/checkout@v5
-
-      - name: Setup Python
-        uses: actions/setup-python@v6
-        with:
-          python-version: 3.x
-
-      - uses: actions/download-artifact@v5
-        with:
-          pattern: firmware-*-${{ needs.version.outputs.long }}
-          merge-multiple: true
-          path: ./output
-
-      - name: Display structure of downloaded files
-        run: ls -lR
-
-      - name: Device scripts permissions
-        run: |
-          chmod +x ./output/device-install.sh
-          chmod +x ./output/device-update.sh
-
-      - name: Zip firmware
-        run: zip -j -9 -r ./firmware-${{inputs.target}}-${{ needs.version.outputs.long }}.zip ./output
-
-      - uses: actions/download-artifact@v5
-        with:
-          pattern: debug-elfs-*-${{ needs.version.outputs.long }}.zip
-          merge-multiple: true
-          path: ./elfs
-
-      - name: Zip debug elfs
-        run: zip -j -9 -r ./debug-elfs-${{inputs.target}}-${{ needs.version.outputs.long }}.zip ./elfs
-
-      # For diagnostics
-      - name: Display structure of downloaded files
-        run: ls -lR
-
-      - name: Add bins and debug elfs to GitHub Release
-        # Only run when targeting master branch with workflow_dispatch
-        if: ${{ github.ref_name == 'master' }}
-        run: |
-          gh release upload v${{ needs.version.outputs.long }} ./firmware-${{inputs.target}}-${{ needs.version.outputs.long }}.zip
-          gh release upload v${{ needs.version.outputs.long }} ./debug-elfs-${{inputs.target}}-${{ needs.version.outputs.long }}.zip
-        env:
-          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-
-  publish-firmware:
-    runs-on: ubuntu-24.04
-    if: ${{ github.event_name == 'workflow_dispatch' && github.repository == 'meshtastic/firmware' && inputs.target != '' }}
-    needs: [release-firmware, version]
-    env:
-      targets: |-
-        esp32,esp32s3,esp32c3,esp32c6,nrf52840,rp2040,rp2350,stm32
-    steps:
-      - name: Checkout
-        uses: actions/checkout@v5
-
-      - name: Setup Python
-        uses: actions/setup-python@v6
-        with:
-          python-version: 3.x
-
-      - uses: actions/download-artifact@v5
-        with:
-          pattern: firmware-{${{ env.targets }}}-${{ needs.version.outputs.long }}
-          merge-multiple: true
-          path: ./publish
-
-      - name: Publish firmware to meshtastic.github.io
-        uses: peaceiris/actions-gh-pages@v4
-        env:
-          # On event/* branches, use the event name as the destination prefix
-          DEST_PREFIX: ${{ contains(github.ref_name, 'event/') && format('{0}/', github.ref_name) || '' }}
-        with:
-          deploy_key: ${{ secrets.DIST_PAGES_DEPLOY_KEY }}
-          external_repository: meshtastic/meshtastic.github.io
-          publish_branch: master
-          publish_dir: ./publish
-          destination_dir: ${{ env.DEST_PREFIX }}firmware-${{ needs.version.outputs.long }}
-          keep_files: true
-          user_name: github-actions[bot]
-          user_email: github-actions[bot]@users.noreply.github.com
-          commit_message: ${{ needs.version.outputs.long }}
-          enable_jekyll: true
diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml
index f61e314a7..812990eca 100644
--- a/.github/workflows/main_matrix.yml
+++ b/.github/workflows/main_matrix.yml
@@ -29,17 +29,10 @@ jobs:
   setup:
     if: github.repository == 'meshtastic/firmware'
     strategy:
-      fail-fast: false
+      fail-fast: true
       matrix:
         arch:
-          - esp32
-          - esp32s3
-          - esp32c3
-          - esp32c6
-          - nrf52840
-          - rp2040
-          - rp2350
-          - stm32
+          - all
           - check
     runs-on: ubuntu-24.04
     steps:
@@ -59,19 +52,13 @@ jobs:
           if [[ "$GITHUB_HEAD_REF" == "" ]]; then
             TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}})
           else  
-            TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}} pr)
+            TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}} --level pr)
           fi
-          echo "Name: $GITHUB_REF_NAME Base: $GITHUB_BASE_REF Ref: $GITHUB_REF Targets: $TARGETS"
-          echo "${{matrix.arch}}=$(jq -cn --argjson environments "$TARGETS" '{board: $environments}')" >> $GITHUB_OUTPUT
+          echo "Name: $GITHUB_REF_NAME Base: $GITHUB_BASE_REF Ref: $GITHUB_REF"
+          echo "${{matrix.arch}}=$TARGETS" >> $GITHUB_OUTPUT
+          echo "$TARGETS" >> $GITHUB_STEP_SUMMARY
     outputs:
-      esp32: ${{ steps.jsonStep.outputs.esp32 }}
-      esp32s3: ${{ steps.jsonStep.outputs.esp32s3 }}
-      esp32c3: ${{ steps.jsonStep.outputs.esp32c3 }}
-      esp32c6: ${{ steps.jsonStep.outputs.esp32c6 }}
-      nrf52840: ${{ steps.jsonStep.outputs.nrf52840 }}
-      rp2040: ${{ steps.jsonStep.outputs.rp2040 }}
-      rp2350: ${{ steps.jsonStep.outputs.rp2350 }}
-      stm32: ${{ steps.jsonStep.outputs.stm32 }}
+      all: ${{ steps.jsonStep.outputs.all }}
       check: ${{ steps.jsonStep.outputs.check }}
 
   version:
@@ -94,7 +81,8 @@ jobs:
     needs: setup
     strategy:
       fail-fast: false
-      matrix: ${{ fromJson(needs.setup.outputs.check) }}
+      matrix:
+        check: ${{ fromJson(needs.setup.outputs.check) }}
 
     runs-on: ubuntu-latest
     if: ${{ github.event_name != 'workflow_dispatch' && github.repository == 'meshtastic/firmware' }}
@@ -103,96 +91,20 @@ jobs:
       - name: Build base
         id: base
         uses: ./.github/actions/setup-base
-      - name: Check ${{ matrix.board }}
-        run: bin/check-all.sh ${{ matrix.board }}
+      - name: Check ${{ matrix.check.board }}
+        run: bin/check-all.sh ${{ matrix.check.board }}
 
-  build-esp32:
+  build:
     needs: [setup, version]
     strategy:
       fail-fast: false
-      matrix: ${{ fromJson(needs.setup.outputs.esp32) }}
+      matrix:
+        build: ${{ fromJson(needs.setup.outputs.all) }}
     uses: ./.github/workflows/build_firmware.yml
     with:
       version: ${{ needs.version.outputs.long }}
-      pio_env: ${{ matrix.board }}
-      platform: esp32
-
-  build-esp32s3:
-    needs: [setup, version]
-    strategy:
-      fail-fast: false
-      matrix: ${{ fromJson(needs.setup.outputs.esp32s3) }}
-    uses: ./.github/workflows/build_firmware.yml
-    with:
-      version: ${{ needs.version.outputs.long }}
-      pio_env: ${{ matrix.board }}
-      platform: esp32s3
-
-  build-esp32c3:
-    needs: [setup, version]
-    strategy:
-      fail-fast: false
-      matrix: ${{ fromJson(needs.setup.outputs.esp32c3) }}
-    uses: ./.github/workflows/build_firmware.yml
-    with:
-      version: ${{ needs.version.outputs.long }}
-      pio_env: ${{ matrix.board }}
-      platform: esp32c3
-
-  build-esp32c6:
-    needs: [setup, version]
-    strategy:
-      fail-fast: false
-      matrix: ${{ fromJson(needs.setup.outputs.esp32c6) }}
-    uses: ./.github/workflows/build_firmware.yml
-    with:
-      version: ${{ needs.version.outputs.long }}
-      pio_env: ${{ matrix.board }}
-      platform: esp32c6
-
-  build-nrf52840:
-    needs: [setup, version]
-    strategy:
-      fail-fast: false
-      matrix: ${{ fromJson(needs.setup.outputs.nrf52840) }}
-    uses: ./.github/workflows/build_firmware.yml
-    with:
-      version: ${{ needs.version.outputs.long }}
-      pio_env: ${{ matrix.board }}
-      platform: nrf52840
-
-  build-rp2040:
-    needs: [setup, version]
-    strategy:
-      fail-fast: false
-      matrix: ${{ fromJson(needs.setup.outputs.rp2040) }}
-    uses: ./.github/workflows/build_firmware.yml
-    with:
-      version: ${{ needs.version.outputs.long }}
-      pio_env: ${{ matrix.board }}
-      platform: rp2040
-
-  build-rp2350:
-    needs: [setup, version]
-    strategy:
-      fail-fast: false
-      matrix: ${{ fromJson(needs.setup.outputs.rp2350) }}
-    uses: ./.github/workflows/build_firmware.yml
-    with:
-      version: ${{ needs.version.outputs.long }}
-      pio_env: ${{ matrix.board }}
-      platform: rp2350
-
-  build-stm32:
-    needs: [setup, version]
-    strategy:
-      fail-fast: false
-      matrix: ${{ fromJson(needs.setup.outputs.stm32) }}
-    uses: ./.github/workflows/build_firmware.yml
-    with:
-      version: ${{ needs.version.outputs.long }}
-      pio_env: ${{ matrix.board }}
-      platform: stm32
+      pio_env: ${{ matrix.build.board }}
+      platform: ${{ matrix.build.platform }}
 
   build-debian-src:
     if: github.repository == 'meshtastic/firmware'
@@ -203,7 +115,7 @@ jobs:
     secrets: inherit
 
   package-pio-deps-native-tft:
-    if: ${{ github.event_name == 'workflow_dispatch' }}
+    if: ${{ github.repository == 'meshtastic/firmware' && github.event_name == 'workflow_dispatch' }}
     uses: ./.github/workflows/package_pio_deps.yml
     with:
       pio_env: native-tft
@@ -288,18 +200,7 @@ jobs:
           - rp2350
           - stm32
     runs-on: ubuntu-latest
-    needs:
-      [
-        version,
-        build-esp32,
-        build-esp32s3,
-        build-esp32c3,
-        build-esp32c6,
-        build-nrf52840,
-        build-rp2040,
-        build-rp2350,
-        build-stm32,
-      ]
+    needs: [version, build]
     steps:
       - name: Checkout code
         uses: actions/checkout@v5
diff --git a/.github/workflows/merge_queue.yml b/.github/workflows/merge_queue.yml
index 7f397ce18..79e8b0803 100644
--- a/.github/workflows/merge_queue.yml
+++ b/.github/workflows/merge_queue.yml
@@ -7,23 +7,13 @@ on:
   # Merge group is a special trigger that is used to trigger the workflow when a merge group is created.
   merge_group:
 
-env:
-  FAIL_FAST_PER_ARCH: true
-
 jobs:
   setup:
     strategy:
       fail-fast: true
       matrix:
         arch:
-          - esp32
-          - esp32s3
-          - esp32c3
-          - esp32c6
-          - nrf52840
-          - rp2040
-          - rp2350
-          - stm32
+          - all
           - check
     runs-on: ubuntu-24.04
     steps:
@@ -39,19 +29,12 @@ jobs:
           if [[ "$GITHUB_HEAD_REF" == "" ]]; then
             TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}})
           else  
-            TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}} pr)
+            TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}} --level pr)
           fi
-          echo "Name: $GITHUB_REF_NAME Base: $GITHUB_BASE_REF Ref: $GITHUB_REF Targets: $TARGETS"
-          echo "${{matrix.arch}}=$(jq -cn --argjson environments "$TARGETS" '{board: $environments}')" >> $GITHUB_OUTPUT
+          echo "Name: $GITHUB_REF_NAME Base: $GITHUB_BASE_REF Ref: $GITHUB_REF"
+          echo "${{matrix.arch}}=$TARGETS" >> $GITHUB_OUTPUT
     outputs:
-      esp32: ${{ steps.jsonStep.outputs.esp32 }}
-      esp32s3: ${{ steps.jsonStep.outputs.esp32s3 }}
-      esp32c3: ${{ steps.jsonStep.outputs.esp32c3 }}
-      esp32c6: ${{ steps.jsonStep.outputs.esp32c6 }}
-      nrf52840: ${{ steps.jsonStep.outputs.nrf52840 }}
-      rp2040: ${{ steps.jsonStep.outputs.rp2040 }}
-      rp2350: ${{ steps.jsonStep.outputs.rp2350 }}
-      stm32: ${{ steps.jsonStep.outputs.stm32 }}
+      all: ${{ steps.jsonStep.outputs.all }}
       check: ${{ steps.jsonStep.outputs.check }}
 
   version:
@@ -73,7 +56,8 @@ jobs:
     needs: setup
     strategy:
       fail-fast: true
-      matrix: ${{ fromJson(needs.setup.outputs.check) }}
+      matrix:
+        check: ${{ fromJson(needs.setup.outputs.check) }}
 
     runs-on: ubuntu-latest
     if: ${{ github.event_name != 'workflow_dispatch' }}
@@ -82,96 +66,19 @@ jobs:
       - name: Build base
         id: base
         uses: ./.github/actions/setup-base
-      - name: Check ${{ matrix.board }}
-        run: bin/check-all.sh ${{ matrix.board }}
+      - name: Check ${{ matrix.check.board }}
+        run: bin/check-all.sh ${{ matrix.check.board }}
 
-  build-esp32:
+  build:
     needs: [setup, version]
     strategy:
-      fail-fast: ${{ vars.FAIL_FAST_PER_ARCH == true }}
-      matrix: ${{ fromJson(needs.setup.outputs.esp32) }}
+      matrix:
+        build: ${{ fromJson(needs.setup.outputs.all) }}
     uses: ./.github/workflows/build_firmware.yml
     with:
       version: ${{ needs.version.outputs.long }}
-      pio_env: ${{ matrix.board }}
-      platform: esp32
-
-  build-esp32s3:
-    needs: [setup, version]
-    strategy:
-      fail-fast: ${{ vars.FAIL_FAST_PER_ARCH == true }}
-      matrix: ${{ fromJson(needs.setup.outputs.esp32s3) }}
-    uses: ./.github/workflows/build_firmware.yml
-    with:
-      version: ${{ needs.version.outputs.long }}
-      pio_env: ${{ matrix.board }}
-      platform: esp32s3
-
-  build-esp32c3:
-    needs: [setup, version]
-    strategy:
-      fail-fast: ${{ vars.FAIL_FAST_PER_ARCH == true }}
-      matrix: ${{ fromJson(needs.setup.outputs.esp32c3) }}
-    uses: ./.github/workflows/build_firmware.yml
-    with:
-      version: ${{ needs.version.outputs.long }}
-      pio_env: ${{ matrix.board }}
-      platform: esp32c3
-
-  build-esp32c6:
-    needs: [setup, version]
-    strategy:
-      fail-fast: ${{ vars.FAIL_FAST_PER_ARCH == true }}
-      matrix: ${{ fromJson(needs.setup.outputs.esp32c6) }}
-    uses: ./.github/workflows/build_firmware.yml
-    with:
-      version: ${{ needs.version.outputs.long }}
-      pio_env: ${{ matrix.board }}
-      platform: esp32c6
-
-  build-nrf52840:
-    needs: [setup, version]
-    strategy:
-      fail-fast: ${{ vars.FAIL_FAST_PER_ARCH == true }}
-      matrix: ${{ fromJson(needs.setup.outputs.nrf52840) }}
-    uses: ./.github/workflows/build_firmware.yml
-    with:
-      version: ${{ needs.version.outputs.long }}
-      pio_env: ${{ matrix.board }}
-      platform: nrf52840
-
-  build-rp2040:
-    needs: [setup, version]
-    strategy:
-      fail-fast: ${{ vars.FAIL_FAST_PER_ARCH == true }}
-      matrix: ${{ fromJson(needs.setup.outputs.rp2040) }}
-    uses: ./.github/workflows/build_firmware.yml
-    with:
-      version: ${{ needs.version.outputs.long }}
-      pio_env: ${{ matrix.board }}
-      platform: rp2040
-
-  build-rp2350:
-    needs: [setup, version]
-    strategy:
-      fail-fast: ${{ vars.FAIL_FAST_PER_ARCH == true }}
-      matrix: ${{ fromJson(needs.setup.outputs.rp2350) }}
-    uses: ./.github/workflows/build_firmware.yml
-    with:
-      version: ${{ needs.version.outputs.long }}
-      pio_env: ${{ matrix.board }}
-      platform: rp2350
-
-  build-stm32:
-    needs: [setup, version]
-    strategy:
-      fail-fast: ${{ vars.FAIL_FAST_PER_ARCH == true }}
-      matrix: ${{ fromJson(needs.setup.outputs.stm32) }}
-    uses: ./.github/workflows/build_firmware.yml
-    with:
-      version: ${{ needs.version.outputs.long }}
-      pio_env: ${{ matrix.board }}
-      platform: stm32
+      pio_env: ${{ matrix.build.board }}
+      platform: ${{ matrix.build.platform }}
 
   build-debian-src:
     if: github.repository == 'meshtastic/firmware'
@@ -260,18 +167,7 @@ jobs:
           - rp2350
           - stm32
     runs-on: ubuntu-latest
-    needs:
-      [
-        version,
-        build-esp32,
-        build-esp32s3,
-        build-esp32c3,
-        build-esp32c6,
-        build-nrf52840,
-        build-rp2040,
-        build-rp2350,
-        build-stm32,
-      ]
+    needs: [version, build]
     steps:
       - name: Checkout code
         uses: actions/checkout@v5
diff --git a/bin/generate_ci_matrix.py b/bin/generate_ci_matrix.py
index aaa76aa45..b4c18c05b 100755
--- a/bin/generate_ci_matrix.py
+++ b/bin/generate_ci_matrix.py
@@ -1,28 +1,32 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 """Generate the CI matrix."""
 
+import argparse
 import json
-import sys
-import random
 import re
 from platformio.project.config import ProjectConfig
 
-options = sys.argv[1:]
+parser = argparse.ArgumentParser(description="Generate the CI matrix")
+parser.add_argument("platform", help="Platform to build for")
+parser.add_argument(
+  "--level",
+  choices=["extra", "pr"],
+  nargs="*",
+  default=[],
+  help="Board level to build for (omit for full release boards)",
+)
+args = parser.parse_args()
 
 outlist = []
 
-if len(options) < 1:
-  print(json.dumps(outlist))
-  exit(1)
-
 cfg = ProjectConfig.get_instance()
 pio_envs = cfg.envs()
 
 # Gather all PlatformIO environments for filtering later
 all_envs = []
 for pio_env in pio_envs:
-  env_build_flags = cfg.get(f"env:{pio_env}", 'build_flags')
+  env_build_flags = cfg.get(f"env:{pio_env}", "build_flags")
   env_platform = None
   for flag in env_build_flags:
     # Extract the platform from the build flags
@@ -37,36 +41,35 @@ for pio_env in pio_envs:
     exit(1)
   # Store env details as a dictionary, and add to 'all_envs' list
   env = {
-    'name': pio_env,
-    'platform': env_platform,
-    'board_level': cfg.get(f"env:{pio_env}", 'board_level', default=None),
-    'board_check': bool(cfg.get(f"env:{pio_env}", 'board_check', default=False))
+    "ci": {"board": pio_env, "platform": env_platform},
+    "board_level": cfg.get(f"env:{pio_env}", "board_level", default=None),
+    "board_check": bool(cfg.get(f"env:{pio_env}", "board_check", default=False)),
   }
   all_envs.append(env)
 
 # Filter outputs based on options
 # Check is mutually exclusive with other options (except 'pr')
-if "check" in options:
+if "check" in args.platform:
   for env in all_envs:
-    if env['board_check']:
-      if "pr" in options:
-        if env['board_level'] == 'pr':
-          outlist.append(env['name'])
+    if env["board_check"]:
+      if "pr" in args.level:
+        if env["board_level"] == "pr":
+          outlist.append(env["ci"])
       else:
-        outlist.append(env['name'])
+        outlist.append(env["ci"])
 # Filter (non-check) builds by platform
 else:
   for env in all_envs:
-    if options[0] == env['platform']:
+    if args.platform == env["ci"]["platform"] or args.platform == "all":
       # Always include board_level = 'pr'
-      if env['board_level'] == 'pr':
-        outlist.append(env['name'])
+      if env["board_level"] == "pr":
+        outlist.append(env["ci"])
       # Include board_level = 'extra' when requested
-      elif "extra" in options and env['board_level'] == "extra":
-        outlist.append(env['name'])
+      elif "extra" in args.level and env["board_level"] == "extra":
+        outlist.append(env["ci"])
       # If no board level is specified, include in release builds (not PR)
-      elif "pr" not in options and not env['board_level']:
-        outlist.append(env['name'])
+      elif "pr" not in args.level and not env["board_level"]:
+        outlist.append(env["ci"])
 
 # Return as a JSON list
 print(json.dumps(outlist))
diff --git a/variants/native/portduino-buildroot/platformio.ini b/variants/native/portduino-buildroot/platformio.ini
index d1bd39e10..a3d0f4639 100644
--- a/variants/native/portduino-buildroot/platformio.ini
+++ b/variants/native/portduino-buildroot/platformio.ini
@@ -4,5 +4,6 @@ extends = portduino_base
 ; environment variable in the buildroot environment.
 build_flags = ${portduino_base.build_flags} -O0 -I variants/native/portduino-buildroot
 board = buildroot
+board_level = extra
 lib_deps = ${portduino_base.lib_deps}
 build_src_filter = ${portduino_base.build_src_filter}
\ No newline at end of file
diff --git a/variants/native/portduino/platformio.ini b/variants/native/portduino/platformio.ini
index 61eadb459..2ccdfbbfc 100644
--- a/variants/native/portduino/platformio.ini
+++ b/variants/native/portduino/platformio.ini
@@ -3,6 +3,7 @@ extends = portduino_base
 build_flags = ${portduino_base.build_flags} -I variants/native/portduino
   -I /usr/include
 board = cross_platform
+board_level = extra
 lib_deps = 
   ${portduino_base.lib_deps}
   melopero/Melopero RV3028@^1.1.0
@@ -50,7 +51,6 @@ build_type = release
 lib_deps =
   ${native_base.lib_deps}
   ${device-ui_base.lib_deps}
-board_level = extra
 build_flags = ${native_base.build_flags} -Os -ffunction-sections -fdata-sections -Wl,--gc-sections
   -D RAM_SIZE=8192
   -D USE_FRAMEBUFFER=1
@@ -79,7 +79,6 @@ build_type = debug
 lib_deps =
   ${native_base.lib_deps}
   ${device-ui_base.lib_deps}
-board_level = extra
 build_flags = ${native_base.build_flags} -O0 -fsanitize=address -lX11 -linput -lxkbcommon
   -D DEBUG_HEAP
   -D RAM_SIZE=16384

From 68a2c4adda4236bcc3e260751617b5ca2106200d Mon Sep 17 00:00:00 2001
From: Tom Fifield 
Date: Tue, 7 Oct 2025 16:11:36 +1100
Subject: [PATCH 434/691] Run Integration test in simulator mode (#8232)
 (#8242)

Co-authored-by: Jonathan Bennett 
---
 .github/workflows/test_native.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.github/workflows/test_native.yml b/.github/workflows/test_native.yml
index 6b788f4c7..9cf1c9e53 100644
--- a/.github/workflows/test_native.yml
+++ b/.github/workflows/test_native.yml
@@ -40,7 +40,7 @@ jobs:
 
       - name: Integration test
         run: |
-          .pio/build/coverage/program &
+          .pio/build/coverage/program -s &
           PID=$!
           timeout 20 bash -c "until ls -al /proc/$PID/fd | grep socket; do sleep 1; done"
           echo "Simulator started, launching python test..."

From b214f09ca1b8b63df62ee8733d7d4138ff6b3979 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Tue, 7 Oct 2025 16:34:00 +1100
Subject: [PATCH 435/691] Update meshtastic/web to v2.6.6 (#7583)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
 bin/web.version | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/bin/web.version b/bin/web.version
index e46a05b19..952f449f1 100644
--- a/bin/web.version
+++ b/bin/web.version
@@ -1 +1 @@
-2.6.4
\ No newline at end of file
+2.6.6
\ No newline at end of file

From b696e083f39dc76b3dd364838671ae6881d74c83 Mon Sep 17 00:00:00 2001
From: Jonathan Bennett 
Date: Tue, 7 Oct 2025 00:37:04 -0500
Subject: [PATCH 436/691] Log antispam (#8241)

* less power spam

* Don't warn about the first 4 GPS checksum failures
---
 src/Power.cpp   | 7 +++++--
 src/gps/GPS.cpp | 8 ++++++--
 src/power.h     | 1 +
 3 files changed, 12 insertions(+), 4 deletions(-)

diff --git a/src/Power.cpp b/src/Power.cpp
index 7de82b8d6..bb5d16d10 100644
--- a/src/Power.cpp
+++ b/src/Power.cpp
@@ -828,8 +828,11 @@ void Power::readPowerStatus()
 
     // Notify any status instances that are observing us
     const PowerStatus powerStatus2 = PowerStatus(hasBattery, usbPowered, isChargingNow, batteryVoltageMv, batteryChargePercent);
-    LOG_DEBUG("Battery: usbPower=%d, isCharging=%d, batMv=%d, batPct=%d", powerStatus2.getHasUSB(), powerStatus2.getIsCharging(),
-              powerStatus2.getBatteryVoltageMv(), powerStatus2.getBatteryChargePercent());
+    if (millis() > lastLogTime + 50 * 1000) {
+        LOG_DEBUG("Battery: usbPower=%d, isCharging=%d, batMv=%d, batPct=%d", powerStatus2.getHasUSB(),
+                  powerStatus2.getIsCharging(), powerStatus2.getBatteryVoltageMv(), powerStatus2.getBatteryChargePercent());
+        lastLogTime = millis();
+    }
     newStatus.notifyObservers(&powerStatus2);
 #ifdef DEBUG_HEAP
     if (lastheap != memGet.getFreeHeap()) {
diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp
index 0487d1fea..36ec7c580 100644
--- a/src/gps/GPS.cpp
+++ b/src/gps/GPS.cpp
@@ -1589,8 +1589,12 @@ bool GPS::lookForLocation()
 
 #ifndef TINYGPS_OPTION_NO_STATISTICS
     if (reader.failedChecksum() > lastChecksumFailCount) {
-        LOG_WARN("%u new GPS checksum failures, for a total of %u", reader.failedChecksum() - lastChecksumFailCount,
-                 reader.failedChecksum());
+// In a GPS_DEBUG build we want to log all of these. In production, we only care if there are many of them.
+#ifndef GPS_DEBUG
+        if (reader.failedChecksum() > 4)
+#endif
+            LOG_WARN("%u new GPS checksum failures, for a total of %u", reader.failedChecksum() - lastChecksumFailCount,
+                     reader.failedChecksum());
         lastChecksumFailCount = reader.failedChecksum();
     }
 #endif
diff --git a/src/power.h b/src/power.h
index 23eb95064..cdbdd3ea0 100644
--- a/src/power.h
+++ b/src/power.h
@@ -138,6 +138,7 @@ class Power : private concurrency::OSThread
     void reboot();
     // open circuit voltage lookup table
     uint8_t low_voltage_counter;
+    int32_t lastLogTime = 0;
 #ifdef DEBUG_HEAP
     uint32_t lastheap;
 #endif

From 1d5b3438363190226e5bb294f404766ab12299ef Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Tue, 7 Oct 2025 16:56:13 +1100
Subject: [PATCH 437/691] Update meshtastic/device-ui digest to e564d78 (#8235)

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 4ae2da54f..2e101a8a2 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -120,7 +120,7 @@ lib_deps =
 [device-ui_base]
 lib_deps =
 	# renovate: datasource=git-refs depName=meshtastic/device-ui packageName=https://github.com/meshtastic/device-ui gitBranch=master
-	https://github.com/meshtastic/device-ui/archive/f920b1273df750c2e3e01385d3ba30553b913afa.zip
+	https://github.com/meshtastic/device-ui/archive/e564d78ae1a7e9a225aaf4a73b1cb84c549f510f.zip
 
 ; Common libs for environmental measurements in telemetry module
 [environmental_base]

From 668cc9fd64513fc41b94627bd6fc7c7ec3b69e63 Mon Sep 17 00:00:00 2001
From: Jonathan Bennett 
Date: Tue, 7 Oct 2025 05:58:39 -0500
Subject: [PATCH 438/691] Do slightly better at threading the search for GPS
 hardware (#8240)

* Do slightly better at threading the search for GPS hardware

* Formatting and minor logic fix

* Remove now-spam GPS Log messages

---------

Co-authored-by: Ben Meadors 
---
 src/gps/GPS.cpp | 316 ++++++++++++++++++++++++++----------------------
 src/gps/GPS.h   |   2 +
 2 files changed, 176 insertions(+), 142 deletions(-)

diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp
index 36ec7c580..e70eed362 100644
--- a/src/gps/GPS.cpp
+++ b/src/gps/GPS.cpp
@@ -506,10 +506,9 @@ bool GPS::setup()
             delay(1000);
 #endif
             if (probeTries < GPS_PROBETRIES) {
-                LOG_DEBUG("Probe for GPS at %d", serialSpeeds[speedSelect]);
                 gnssModel = probe(serialSpeeds[speedSelect]);
                 if (gnssModel == GNSS_MODEL_UNKNOWN) {
-                    if (++speedSelect == array_count(serialSpeeds)) {
+                    if (currentStep == 0 && ++speedSelect == array_count(serialSpeeds)) {
                         speedSelect = 0;
                         ++probeTries;
                     }
@@ -518,10 +517,9 @@ bool GPS::setup()
             // Rare Serial Speeds
 #ifndef CONFIG_IDF_TARGET_ESP32C6
             if (probeTries == GPS_PROBETRIES) {
-                LOG_DEBUG("Probe for GPS at %d", rareSerialSpeeds[speedSelect]);
                 gnssModel = probe(rareSerialSpeeds[speedSelect]);
                 if (gnssModel == GNSS_MODEL_UNKNOWN) {
-                    if (++speedSelect == array_count(rareSerialSpeeds)) {
+                    if (currentStep == 0 && ++speedSelect == array_count(rareSerialSpeeds)) {
                         LOG_WARN("Give up on GPS probe and set to %d", GPS_BAUDRATE);
                         return true;
                     }
@@ -1094,7 +1092,7 @@ int32_t GPS::runOnce()
             return disable();
         }
         if (!setup())
-            return 2000; // Setup failed, re-run in two seconds
+            return currentDelay; // Setup failed, re-run in two seconds
 
         // We have now loaded our saved preferences from flash
         if (config.position.gps_mode != meshtastic_Config_PositionConfig_GpsMode_ENABLED) {
@@ -1218,163 +1216,197 @@ static const char *DETECTED_MESSAGE = "%s detected";
 
 GnssModel_t GPS::probe(int serialSpeed)
 {
+    uint8_t buffer[768] = {0};
+
+    switch (currentStep) {
+    case 0: {
 #if defined(ARCH_NRF52) || defined(ARCH_PORTDUINO) || defined(ARCH_STM32WL)
-    _serial_gps->end();
-    _serial_gps->begin(serialSpeed);
+        _serial_gps->end();
+        _serial_gps->begin(serialSpeed);
 #elif defined(ARCH_RP2040)
-    _serial_gps->end();
-    _serial_gps->setFIFOSize(256);
-    _serial_gps->begin(serialSpeed);
+        _serial_gps->end();
+        _serial_gps->setFIFOSize(256);
+        _serial_gps->begin(serialSpeed);
 #else
-    if (_serial_gps->baudRate() != serialSpeed) {
-        LOG_DEBUG("Set Baud to %i", serialSpeed);
-        _serial_gps->updateBaudRate(serialSpeed);
-    }
+        if (_serial_gps->baudRate() != serialSpeed) {
+            LOG_DEBUG("Set GPS Baud to %i", serialSpeed);
+            _serial_gps->updateBaudRate(serialSpeed);
+        }
 #endif
 
-    memset(&ublox_info, 0, sizeof(ublox_info));
-    uint8_t buffer[768] = {0};
-    delay(100);
+        memset(&ublox_info, 0, sizeof(ublox_info));
+        delay(100);
 
-    // Close all NMEA sentences, valid for L76K, ATGM336H (and likely other AT6558 devices)
-    _serial_gps->write("$PCAS03,0,0,0,0,0,0,0,0,0,0,,,0,0*02\r\n");
-    delay(20);
-    // Close NMEA sequences on Ublox
-    _serial_gps->write("$PUBX,40,GLL,0,0,0,0,0,0*5C\r\n");
-    _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,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 = {
-        {"ATGM336H", "$GPTXT,01,01,02,HW=ATGM336H", GNSS_MODEL_ATGM336H},
-        /* ATGM332D series (-11(GPS), -21(BDS), -31(GPS+BDS), -51(GPS+GLONASS), -71-0(GPS+BDS+GLONASS)) based on AT6558 */
-        {"ATGM332D", "$GPTXT,01,01,02,HW=ATGM332D", GNSS_MODEL_ATGM336H}};
-    PROBE_FAMILY("ATGM33xx Family", "$PCAS06,1*1A", atgm, 500);
-
-    /* Airoha (Mediatek) AG3335A/M/S, A3352Q, Quectel L89 2.0, SimCom SIM65M */
-    _serial_gps->write("$PAIR062,2,0*3C\r\n"); // GSA OFF to reduce volume
-    _serial_gps->write("$PAIR062,3,0*3D\r\n"); // GSV OFF to reduce volume
-    _serial_gps->write("$PAIR513*3D\r\n");     // save configuration
-    std::vector airoha = {{"AG3335", "$PAIR021,AG3335", GNSS_MODEL_AG3335},
-                                    {"AG3352", "$PAIR021,AG3352", GNSS_MODEL_AG3352},
-                                    {"RYS3520", "$PAIR021,REYAX_RYS3520_V2", GNSS_MODEL_AG3352}};
-    PROBE_FAMILY("Airoha Family", "$PAIR021*39", airoha, 1000);
-
-    PROBE_SIMPLE("LC86", "$PQTMVERNO*58", "$PQTMVERNO,LC86", GNSS_MODEL_AG3352, 500);
-    PROBE_SIMPLE("L76K", "$PCAS06,0*1B", "$GPTXT,01,01,02,SW=", GNSS_MODEL_MTK, 500);
-
-    // Close all NMEA sentences, valid for MTK3333 and MTK3339 platforms
-    _serial_gps->write("$PMTK514,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0*2E\r\n");
-    delay(20);
-    std::vector mtk = {{"L76B", "Quectel-L76B", GNSS_MODEL_MTK_L76B}, {"PA1010D", "1010D", GNSS_MODEL_MTK_PA1010D},
-                                 {"PA1616S", "1616S", GNSS_MODEL_MTK_PA1616S},  {"LS20031", "MC-1513", GNSS_MODEL_MTK_L76B},
-                                 {"L96", "Quectel-L96", GNSS_MODEL_MTK_L76B},   {"L80-R", "_3337_", GNSS_MODEL_MTK_L76B},
-                                 {"L80", "_3339_", GNSS_MODEL_MTK_L76B}};
-
-    PROBE_FAMILY("MTK Family", "$PMTK605*31", mtk, 500);
-
-    uint8_t cfg_rate[] = {0xB5, 0x62, 0x06, 0x08, 0x00, 0x00, 0x00, 0x00};
-    UBXChecksum(cfg_rate, sizeof(cfg_rate));
-    clearBuffer();
-    _serial_gps->write(cfg_rate, sizeof(cfg_rate));
-    // Check that the returned response class and message ID are correct
-    GPS_RESPONSE response = getACK(0x06, 0x08, 750);
-    if (response == GNSS_RESPONSE_NONE) {
-        LOG_WARN("No GNSS Module (baudrate %d)", serialSpeed);
+        // Close all NMEA sentences, valid for L76K, ATGM336H (and likely other AT6558 devices)
+        _serial_gps->write("$PCAS03,0,0,0,0,0,0,0,0,0,0,,,0,0*02\r\n");
+        delay(20);
+        // Close NMEA sequences on Ublox
+        _serial_gps->write("$PUBX,40,GLL,0,0,0,0,0,0*5C\r\n");
+        _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");
+        currentDelay = 20;
+        currentStep = 1;
         return GNSS_MODEL_UNKNOWN;
-    } else if (response == GNSS_RESPONSE_FRAME_ERRORS) {
-        LOG_INFO("UBlox Frame Errors (baudrate %d)", serialSpeed);
     }
+    case 1: {
 
-    memset(buffer, 0, sizeof(buffer));
-    uint8_t _message_MONVER[8] = {
-        0xB5, 0x62, // Sync message for UBX protocol
-        0x0A, 0x04, // Message class and ID (UBX-MON-VER)
-        0x00, 0x00, // Length of payload (we're asking for an answer, so no payload)
-        0x00, 0x00  // Checksum
-    };
-    //  Get Ublox gnss module hardware and software info
-    UBXChecksum(_message_MONVER, sizeof(_message_MONVER));
-    clearBuffer();
-    _serial_gps->write(_message_MONVER, sizeof(_message_MONVER));
+        // 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);
+        currentDelay = 20;
+        currentStep = 2;
+        return GNSS_MODEL_UNKNOWN;
+    }
+    case 2: {
+        std::vector atgm = {
+            {"ATGM336H", "$GPTXT,01,01,02,HW=ATGM336H", GNSS_MODEL_ATGM336H},
+            /* ATGM332D series (-11(GPS), -21(BDS), -31(GPS+BDS), -51(GPS+GLONASS), -71-0(GPS+BDS+GLONASS)) based on AT6558 */
+            {"ATGM332D", "$GPTXT,01,01,02,HW=ATGM332D", GNSS_MODEL_ATGM336H}};
+        PROBE_FAMILY("ATGM33xx Family", "$PCAS06,1*1A", atgm, 500);
+        currentDelay = 20;
+        currentStep = 3;
+        return GNSS_MODEL_UNKNOWN;
+    }
+    case 3: {
+        /* Airoha (Mediatek) AG3335A/M/S, A3352Q, Quectel L89 2.0, SimCom SIM65M */
+        _serial_gps->write("$PAIR062,2,0*3C\r\n"); // GSA OFF to reduce volume
+        _serial_gps->write("$PAIR062,3,0*3D\r\n"); // GSV OFF to reduce volume
+        _serial_gps->write("$PAIR513*3D\r\n");     // save configuration
+        std::vector airoha = {{"AG3335", "$PAIR021,AG3335", GNSS_MODEL_AG3335},
+                                        {"AG3352", "$PAIR021,AG3352", GNSS_MODEL_AG3352},
+                                        {"RYS3520", "$PAIR021,REYAX_RYS3520_V2", GNSS_MODEL_AG3352}};
+        PROBE_FAMILY("Airoha Family", "$PAIR021*39", airoha, 1000);
+        currentDelay = 20;
+        currentStep = 4;
+        return GNSS_MODEL_UNKNOWN;
+    }
+    case 4: {
+        PROBE_SIMPLE("LC86", "$PQTMVERNO*58", "$PQTMVERNO,LC86", GNSS_MODEL_AG3352, 500);
+        PROBE_SIMPLE("L76K", "$PCAS06,0*1B", "$GPTXT,01,01,02,SW=", GNSS_MODEL_MTK, 500);
+        currentDelay = 20;
+        currentStep = 5;
+        return GNSS_MODEL_UNKNOWN;
+    }
+    case 5: {
 
-    uint16_t len = getACK(buffer, sizeof(buffer), 0x0A, 0x04, 1200);
-    if (len) {
-        uint16_t position = 0;
-        for (int i = 0; i < 30; i++) {
-            ublox_info.swVersion[i] = buffer[position];
-            position++;
-        }
-        for (int i = 0; i < 10; i++) {
-            ublox_info.hwVersion[i] = buffer[position];
-            position++;
-        }
+        // Close all NMEA sentences, valid for MTK3333 and MTK3339 platforms
+        _serial_gps->write("$PMTK514,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0*2E\r\n");
+        delay(20);
+        std::vector mtk = {{"L76B", "Quectel-L76B", GNSS_MODEL_MTK_L76B}, {"PA1010D", "1010D", GNSS_MODEL_MTK_PA1010D},
+                                     {"PA1616S", "1616S", GNSS_MODEL_MTK_PA1616S},  {"LS20031", "MC-1513", GNSS_MODEL_MTK_L76B},
+                                     {"L96", "Quectel-L96", GNSS_MODEL_MTK_L76B},   {"L80-R", "_3337_", GNSS_MODEL_MTK_L76B},
+                                     {"L80", "_3339_", GNSS_MODEL_MTK_L76B}};
 
-        while (len >= position + 30) {
-            for (int i = 0; i < 30; i++) {
-                ublox_info.extension[ublox_info.extensionNo][i] = buffer[position];
-                position++;
-            }
-            ublox_info.extensionNo++;
-            if (ublox_info.extensionNo > 9)
-                break;
-        }
-
-        LOG_DEBUG("Module Info : ");
-        LOG_DEBUG("Soft version: %s", ublox_info.swVersion);
-        LOG_DEBUG("Hard version: %s", ublox_info.hwVersion);
-        LOG_DEBUG("Extensions:%d", ublox_info.extensionNo);
-        for (int i = 0; i < ublox_info.extensionNo; i++) {
-            LOG_DEBUG("  %s", ublox_info.extension[i]);
+        PROBE_FAMILY("MTK Family", "$PMTK605*31", mtk, 500);
+        currentDelay = 20;
+        currentStep = 6;
+        return GNSS_MODEL_UNKNOWN;
+    }
+    case 6: {
+        uint8_t cfg_rate[] = {0xB5, 0x62, 0x06, 0x08, 0x00, 0x00, 0x00, 0x00};
+        UBXChecksum(cfg_rate, sizeof(cfg_rate));
+        clearBuffer();
+        _serial_gps->write(cfg_rate, sizeof(cfg_rate));
+        // Check that the returned response class and message ID are correct
+        GPS_RESPONSE response = getACK(0x06, 0x08, 750);
+        if (response == GNSS_RESPONSE_NONE) {
+            LOG_WARN("No GNSS Module (baudrate %d)", serialSpeed);
+            currentDelay = 2000;
+            currentStep = 0;
+            return GNSS_MODEL_UNKNOWN;
+        } else if (response == GNSS_RESPONSE_FRAME_ERRORS) {
+            LOG_INFO("UBlox Frame Errors (baudrate %d)", serialSpeed);
         }
 
         memset(buffer, 0, sizeof(buffer));
+        uint8_t _message_MONVER[8] = {
+            0xB5, 0x62, // Sync message for UBX protocol
+            0x0A, 0x04, // Message class and ID (UBX-MON-VER)
+            0x00, 0x00, // Length of payload (we're asking for an answer, so no payload)
+            0x00, 0x00  // Checksum
+        };
+        //  Get Ublox gnss module hardware and software info
+        UBXChecksum(_message_MONVER, sizeof(_message_MONVER));
+        clearBuffer();
+        _serial_gps->write(_message_MONVER, sizeof(_message_MONVER));
 
-        // tips: extensionNo field is 0 on some 6M GNSS modules
-        for (int i = 0; i < ublox_info.extensionNo; ++i) {
-            if (!strncmp(ublox_info.extension[i], "MOD=", 4)) {
-                strncpy((char *)buffer, &(ublox_info.extension[i][4]), sizeof(buffer));
-            } else if (!strncmp(ublox_info.extension[i], "PROTVER", 7)) {
-                char *ptr = nullptr;
-                memset(buffer, 0, sizeof(buffer));
-                strncpy((char *)buffer, &(ublox_info.extension[i][8]), sizeof(buffer));
-                LOG_DEBUG("Protocol Version:%s", (char *)buffer);
-                if (strlen((char *)buffer)) {
-                    ublox_info.protocol_version = strtoul((char *)buffer, &ptr, 10);
-                    LOG_DEBUG("ProtVer=%d", ublox_info.protocol_version);
-                } else {
-                    ublox_info.protocol_version = 0;
+        uint16_t len = getACK(buffer, sizeof(buffer), 0x0A, 0x04, 1200);
+        if (len) {
+            uint16_t position = 0;
+            for (int i = 0; i < 30; i++) {
+                ublox_info.swVersion[i] = buffer[position];
+                position++;
+            }
+            for (int i = 0; i < 10; i++) {
+                ublox_info.hwVersion[i] = buffer[position];
+                position++;
+            }
+
+            while (len >= position + 30) {
+                for (int i = 0; i < 30; i++) {
+                    ublox_info.extension[ublox_info.extensionNo][i] = buffer[position];
+                    position++;
+                }
+                ublox_info.extensionNo++;
+                if (ublox_info.extensionNo > 9)
+                    break;
+            }
+
+            LOG_DEBUG("Module Info : ");
+            LOG_DEBUG("Soft version: %s", ublox_info.swVersion);
+            LOG_DEBUG("Hard version: %s", ublox_info.hwVersion);
+            LOG_DEBUG("Extensions:%d", ublox_info.extensionNo);
+            for (int i = 0; i < ublox_info.extensionNo; i++) {
+                LOG_DEBUG("  %s", ublox_info.extension[i]);
+            }
+
+            memset(buffer, 0, sizeof(buffer));
+
+            // tips: extensionNo field is 0 on some 6M GNSS modules
+            for (int i = 0; i < ublox_info.extensionNo; ++i) {
+                if (!strncmp(ublox_info.extension[i], "MOD=", 4)) {
+                    strncpy((char *)buffer, &(ublox_info.extension[i][4]), sizeof(buffer));
+                } else if (!strncmp(ublox_info.extension[i], "PROTVER", 7)) {
+                    char *ptr = nullptr;
+                    memset(buffer, 0, sizeof(buffer));
+                    strncpy((char *)buffer, &(ublox_info.extension[i][8]), sizeof(buffer));
+                    LOG_DEBUG("Protocol Version:%s", (char *)buffer);
+                    if (strlen((char *)buffer)) {
+                        ublox_info.protocol_version = strtoul((char *)buffer, &ptr, 10);
+                        LOG_DEBUG("ProtVer=%d", ublox_info.protocol_version);
+                    } else {
+                        ublox_info.protocol_version = 0;
+                    }
                 }
             }
-        }
-        if (strncmp(ublox_info.hwVersion, "00040007", 8) == 0) {
-            LOG_INFO(DETECTED_MESSAGE, "U-blox 6", "6");
-            return GNSS_MODEL_UBLOX6;
-        } else if (strncmp(ublox_info.hwVersion, "00070000", 8) == 0) {
-            LOG_INFO(DETECTED_MESSAGE, "U-blox 7", "7");
-            return GNSS_MODEL_UBLOX7;
-        } else if (strncmp(ublox_info.hwVersion, "00080000", 8) == 0) {
-            LOG_INFO(DETECTED_MESSAGE, "U-blox 8", "8");
-            return GNSS_MODEL_UBLOX8;
-        } else if (strncmp(ublox_info.hwVersion, "00190000", 8) == 0) {
-            LOG_INFO(DETECTED_MESSAGE, "U-blox 9", "9");
-            return GNSS_MODEL_UBLOX9;
-        } else if (strncmp(ublox_info.hwVersion, "000A0000", 8) == 0) {
-            LOG_INFO(DETECTED_MESSAGE, "U-blox 10", "10");
-            return GNSS_MODEL_UBLOX10;
+            if (strncmp(ublox_info.hwVersion, "00040007", 8) == 0) {
+                LOG_INFO(DETECTED_MESSAGE, "U-blox 6", "6");
+                return GNSS_MODEL_UBLOX6;
+            } else if (strncmp(ublox_info.hwVersion, "00070000", 8) == 0) {
+                LOG_INFO(DETECTED_MESSAGE, "U-blox 7", "7");
+                return GNSS_MODEL_UBLOX7;
+            } else if (strncmp(ublox_info.hwVersion, "00080000", 8) == 0) {
+                LOG_INFO(DETECTED_MESSAGE, "U-blox 8", "8");
+                return GNSS_MODEL_UBLOX8;
+            } else if (strncmp(ublox_info.hwVersion, "00190000", 8) == 0) {
+                LOG_INFO(DETECTED_MESSAGE, "U-blox 9", "9");
+                return GNSS_MODEL_UBLOX9;
+            } else if (strncmp(ublox_info.hwVersion, "000A0000", 8) == 0) {
+                LOG_INFO(DETECTED_MESSAGE, "U-blox 10", "10");
+                return GNSS_MODEL_UBLOX10;
+            }
         }
     }
+    }
     LOG_WARN("No GNSS Module (baudrate %d)", serialSpeed);
+    currentDelay = 2000;
+    currentStep = 0;
     return GNSS_MODEL_UNKNOWN;
 }
 
diff --git a/src/gps/GPS.h b/src/gps/GPS.h
index cba767460..f3bcf37f5 100644
--- a/src/gps/GPS.h
+++ b/src/gps/GPS.h
@@ -151,6 +151,8 @@ class GPS : private concurrency::OSThread
     TinyGPSPlus reader;
     uint8_t fixQual = 0; // fix quality from GPGGA
     uint32_t lastChecksumFailCount = 0;
+    uint8_t currentStep = 0;
+    int32_t currentDelay = 2000;
 
 #ifndef TINYGPS_OPTION_NO_CUSTOM_FIELDS
     // (20210908) TinyGps++ can only read the GPGSA "FIX TYPE" field

From 5bcc47dddb6aa339e51651a05ff7f2bdc6e27bd2 Mon Sep 17 00:00:00 2001
From: Tom Fifield 
Date: Tue, 7 Oct 2025 22:00:09 +1100
Subject: [PATCH 439/691] Revert "develop --> Master" (#8244)

---
 .github/workflows/main_matrix.yml |   6 ++
 src/Power.cpp                     |   7 +-
 src/gps/GPS.cpp                   |   8 +--
 src/mesh/FloodingRouter.cpp       | 102 ++++++++++++++++++-----------
 src/mesh/FloodingRouter.h         |  15 ++---
 src/mesh/NextHopRouter.cpp        | 105 +++++++++++++++---------------
 src/mesh/NextHopRouter.h          |   6 +-
 src/mesh/PacketHistory.cpp        |   1 +
 src/mesh/http/ContentHandler.cpp  |   5 --
 src/mesh/http/WebServer.cpp       |  35 +---------
 src/mesh/http/WebServer.h         |   4 --
 src/modules/TraceRouteModule.cpp  |  64 ------------------
 src/modules/TraceRouteModule.h    |   6 --
 src/power.h                       |   1 -
 14 files changed, 136 insertions(+), 229 deletions(-)

diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml
index 887bf3081..812990eca 100644
--- a/.github/workflows/main_matrix.yml
+++ b/.github/workflows/main_matrix.yml
@@ -27,6 +27,7 @@ on:
 
 jobs:
   setup:
+    if: github.repository == 'meshtastic/firmware'
     strategy:
       fail-fast: true
       matrix:
@@ -41,6 +42,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: |
@@ -57,6 +62,7 @@ jobs:
       check: ${{ steps.jsonStep.outputs.check }}
 
   version:
+    if: github.repository == 'meshtastic/firmware'
     runs-on: ubuntu-latest
     steps:
       - uses: actions/checkout@v5
diff --git a/src/Power.cpp b/src/Power.cpp
index bb5d16d10..7de82b8d6 100644
--- a/src/Power.cpp
+++ b/src/Power.cpp
@@ -828,11 +828,8 @@ void Power::readPowerStatus()
 
     // Notify any status instances that are observing us
     const PowerStatus powerStatus2 = PowerStatus(hasBattery, usbPowered, isChargingNow, batteryVoltageMv, batteryChargePercent);
-    if (millis() > lastLogTime + 50 * 1000) {
-        LOG_DEBUG("Battery: usbPower=%d, isCharging=%d, batMv=%d, batPct=%d", powerStatus2.getHasUSB(),
-                  powerStatus2.getIsCharging(), powerStatus2.getBatteryVoltageMv(), powerStatus2.getBatteryChargePercent());
-        lastLogTime = millis();
-    }
+    LOG_DEBUG("Battery: usbPower=%d, isCharging=%d, batMv=%d, batPct=%d", powerStatus2.getHasUSB(), powerStatus2.getIsCharging(),
+              powerStatus2.getBatteryVoltageMv(), powerStatus2.getBatteryChargePercent());
     newStatus.notifyObservers(&powerStatus2);
 #ifdef DEBUG_HEAP
     if (lastheap != memGet.getFreeHeap()) {
diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp
index 36ec7c580..0487d1fea 100644
--- a/src/gps/GPS.cpp
+++ b/src/gps/GPS.cpp
@@ -1589,12 +1589,8 @@ bool GPS::lookForLocation()
 
 #ifndef TINYGPS_OPTION_NO_STATISTICS
     if (reader.failedChecksum() > lastChecksumFailCount) {
-// In a GPS_DEBUG build we want to log all of these. In production, we only care if there are many of them.
-#ifndef GPS_DEBUG
-        if (reader.failedChecksum() > 4)
-#endif
-            LOG_WARN("%u new GPS checksum failures, for a total of %u", reader.failedChecksum() - lastChecksumFailCount,
-                     reader.failedChecksum());
+        LOG_WARN("%u new GPS checksum failures, for a total of %u", reader.failedChecksum() - lastChecksumFailCount,
+                 reader.failedChecksum());
         lastChecksumFailCount = reader.failedChecksum();
     }
 #endif
diff --git a/src/mesh/FloodingRouter.cpp b/src/mesh/FloodingRouter.cpp
index 032be241b..1d8ac247f 100644
--- a/src/mesh/FloodingRouter.cpp
+++ b/src/mesh/FloodingRouter.cpp
@@ -31,8 +31,33 @@ bool FloodingRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
         wasSeenRecently(p, true, nullptr, nullptr, &wasUpgraded); // Updates history; returns false when an upgrade is detected
 
     // Handle hop_limit upgrade scenario for rebroadcasters
-    if (wasUpgraded && perhapsHandleUpgradedPacket(p)) {
-        return true; // we handled it, so stop processing
+    // isRebroadcaster() is duplicated in perhapsRebroadcast(), but this avoids confusing log messages
+    if (wasUpgraded && isRebroadcaster() && iface && p->hop_limit > 0) {
+        // wasSeenRecently() reports false in upgrade cases so we handle replacement before the duplicate short-circuit
+        // If we overhear a duplicate copy of the packet with more hops left than the one we are waiting to
+        // rebroadcast, then remove the packet currently sitting in the TX queue and use this one instead.
+        uint8_t dropThreshold = p->hop_limit; // remove queued packets that have fewer hops remaining
+        if (iface->removePendingTXPacket(getFrom(p), p->id, dropThreshold)) {
+            LOG_DEBUG("Processing upgraded packet 0x%08x for rebroadcast with hop limit %d (dropping queued < %d)", p->id,
+                      p->hop_limit, dropThreshold);
+
+            if (nodeDB)
+                nodeDB->updateFrom(*p);
+#if !MESHTASTIC_EXCLUDE_TRACEROUTE
+            if (traceRouteModule && p->which_payload_variant == meshtastic_MeshPacket_decoded_tag &&
+                p->decoded.portnum == meshtastic_PortNum_TRACEROUTE_APP)
+                traceRouteModule->processUpgradedPacket(*p);
+#endif
+
+            perhapsRebroadcast(p);
+
+            // We already enqueued the improved copy, so make sure the incoming packet stops here.
+            return true;
+        }
+
+        // No queue entry was replaced by this upgraded copy, so treat it as a duplicate to avoid
+        // delivering the same packet to applications/phone twice with different hop limits.
+        seenRecently = true;
     }
 
     if (seenRecently) {
@@ -45,10 +70,8 @@ bool FloodingRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
         if (isRepeated) {
             LOG_DEBUG("Repeated reliable tx");
             // Check if it's still in the Tx queue, if not, we have to relay it again
-            if (!findInTxQueue(p->from, p->id)) {
-                reprocessPacket(p);
+            if (!findInTxQueue(p->from, p->id))
                 perhapsRebroadcast(p);
-            }
         } else {
             perhapsCancelDupe(p);
         }
@@ -59,40 +82,6 @@ bool FloodingRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
     return Router::shouldFilterReceived(p);
 }
 
-bool FloodingRouter::perhapsHandleUpgradedPacket(const meshtastic_MeshPacket *p)
-{
-    // isRebroadcaster() is duplicated in perhapsRebroadcast(), but this avoids confusing log messages
-    if (isRebroadcaster() && iface && p->hop_limit > 0) {
-        // If we overhear a duplicate copy of the packet with more hops left than the one we are waiting to
-        // rebroadcast, then remove the packet currently sitting in the TX queue and use this one instead.
-        uint8_t dropThreshold = p->hop_limit; // remove queued packets that have fewer hops remaining
-        if (iface->removePendingTXPacket(getFrom(p), p->id, dropThreshold)) {
-            LOG_DEBUG("Processing upgraded packet 0x%08x for rebroadcast with hop limit %d (dropping queued < %d)", p->id,
-                      p->hop_limit, dropThreshold);
-
-            reprocessPacket(p);
-            perhapsRebroadcast(p);
-
-            rxDupe++;
-            // We already enqueued the improved copy, so make sure the incoming packet stops here.
-            return true;
-        }
-    }
-
-    return false;
-}
-
-void FloodingRouter::reprocessPacket(const meshtastic_MeshPacket *p)
-{
-    if (nodeDB)
-        nodeDB->updateFrom(*p);
-#if !MESHTASTIC_EXCLUDE_TRACEROUTE
-    if (traceRouteModule && p->which_payload_variant == meshtastic_MeshPacket_decoded_tag &&
-        p->decoded.portnum == meshtastic_PortNum_TRACEROUTE_APP)
-        traceRouteModule->processUpgradedPacket(*p);
-#endif
-}
-
 bool FloodingRouter::roleAllowsCancelingDupe(const meshtastic_MeshPacket *p)
 {
     if (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER ||
@@ -132,6 +121,41 @@ bool FloodingRouter::isRebroadcaster()
            config.device.rebroadcast_mode != meshtastic_Config_DeviceConfig_RebroadcastMode_NONE;
 }
 
+void FloodingRouter::perhapsRebroadcast(const meshtastic_MeshPacket *p)
+{
+    if (!isToUs(p) && (p->hop_limit > 0) && !isFromUs(p)) {
+        if (p->id != 0) {
+            if (isRebroadcaster()) {
+                meshtastic_MeshPacket *tosend = packetPool.allocCopy(*p); // keep a copy because we will be sending it
+
+                // Use shared logic to determine if hop_limit should be decremented
+                if (shouldDecrementHopLimit(p)) {
+                    tosend->hop_limit--; // bump down the hop count
+                } else {
+                    LOG_INFO("favorite-ROUTER/CLIENT_BASE-to-ROUTER/CLIENT_BASE flood: preserving hop_limit");
+                }
+#if USERPREFS_EVENT_MODE
+                if (tosend->hop_limit > 2) {
+                    // if we are "correcting" the hop_limit, "correct" the hop_start by the same amount to preserve hops away.
+                    tosend->hop_start -= (tosend->hop_limit - 2);
+                    tosend->hop_limit = 2;
+                }
+#endif
+
+                tosend->next_hop = NO_NEXT_HOP_PREFERENCE; // this should already be the case, but just in case
+
+                LOG_INFO("Rebroadcast received floodmsg");
+                // Note: we are careful to resend using the original senders node id
+                send(tosend);
+            } else {
+                LOG_DEBUG("No rebroadcast: Role = CLIENT_MUTE or Rebroadcast Mode = NONE");
+            }
+        } else {
+            LOG_DEBUG("Ignore 0 id broadcast");
+        }
+    }
+}
+
 void FloodingRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtastic_Routing *c)
 {
     bool isAckorReply = (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) &&
diff --git a/src/mesh/FloodingRouter.h b/src/mesh/FloodingRouter.h
index e8a2e9685..eaf71d294 100644
--- a/src/mesh/FloodingRouter.h
+++ b/src/mesh/FloodingRouter.h
@@ -27,6 +27,10 @@
  */
 class FloodingRouter : public Router
 {
+  private:
+    /* Check if we should rebroadcast this packet, and do so if needed */
+    void perhapsRebroadcast(const meshtastic_MeshPacket *p);
+
   public:
     /**
      * Constructor
@@ -55,17 +59,6 @@ class FloodingRouter : public Router
      */
     virtual void sniffReceived(const meshtastic_MeshPacket *p, const meshtastic_Routing *c) override;
 
-    /* Check if we should rebroadcast this packet, and do so if needed */
-    virtual bool perhapsRebroadcast(const meshtastic_MeshPacket *p) = 0;
-
-    /* Check if we should handle an upgraded packet (with higher hop_limit)
-     * @return true if we handled it (so stop processing)
-     */
-    bool perhapsHandleUpgradedPacket(const meshtastic_MeshPacket *p);
-
-    /* Call when we receive a packet that needs some reprocessing, but afterwards should be filtered */
-    void reprocessPacket(const meshtastic_MeshPacket *p);
-
     // Return false for roles like ROUTER which should always rebroadcast even when we've heard another rebroadcast of
     // the same packet
     bool roleAllowsCancelingDupe(const meshtastic_MeshPacket *p);
diff --git a/src/mesh/NextHopRouter.cpp b/src/mesh/NextHopRouter.cpp
index afdb4d096..0461d7eb6 100644
--- a/src/mesh/NextHopRouter.cpp
+++ b/src/mesh/NextHopRouter.cpp
@@ -43,8 +43,31 @@ bool NextHopRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
                                         &wasUpgraded); // Updates history; returns false when an upgrade is detected
 
     // Handle hop_limit upgrade scenario for rebroadcasters
-    if (wasUpgraded && perhapsHandleUpgradedPacket(p)) {
-        return true; // we handled it, so stop processing
+    // isRebroadcaster() is duplicated in perhapsRelay(), but this avoids confusing log messages
+    if (wasUpgraded && isRebroadcaster() && iface && p->hop_limit > 0) {
+        // Upgrade detection bypasses the duplicate short-circuit so we replace the queued packet before exiting
+        uint8_t dropThreshold = p->hop_limit; // remove queued packets that have fewer hops remaining
+        if (iface->removePendingTXPacket(getFrom(p), p->id, dropThreshold)) {
+            LOG_DEBUG("Processing upgraded packet 0x%08x for relay with hop limit %d (dropping queued < %d)", p->id, p->hop_limit,
+                      dropThreshold);
+
+            if (nodeDB)
+                nodeDB->updateFrom(*p);
+#if !MESHTASTIC_EXCLUDE_TRACEROUTE
+            if (traceRouteModule && p->which_payload_variant == meshtastic_MeshPacket_decoded_tag &&
+                p->decoded.portnum == meshtastic_PortNum_TRACEROUTE_APP)
+                traceRouteModule->processUpgradedPacket(*p);
+#endif
+
+            perhapsRelay(p);
+
+            // We already enqueued the improved copy, so make sure the incoming packet stops here.
+            return true;
+        }
+
+        // No queue entry was replaced by this upgraded copy, so treat it as a duplicate to avoid
+        // delivering the same packet to applications/phone twice with different hop limits.
+        seenRecently = true;
     }
 
     if (seenRecently) {
@@ -59,20 +82,14 @@ bool NextHopRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
         if (wasFallback) {
             LOG_INFO("Fallback to flooding from relay_node=0x%x", p->relay_node);
             // Check if it's still in the Tx queue, if not, we have to relay it again
-            if (!findInTxQueue(p->from, p->id)) {
-                reprocessPacket(p);
-                perhapsRebroadcast(p);
-            }
+            if (!findInTxQueue(p->from, p->id))
+                perhapsRelay(p);
         } else {
             bool isRepeated = p->hop_start > 0 && p->hop_start == p->hop_limit;
             // If repeated and not in Tx queue anymore, try relaying again, or if we are the destination, send the ACK again
             if (isRepeated) {
-                if (!findInTxQueue(p->from, p->id)) {
-                    reprocessPacket(p);
-                    if (!perhapsRebroadcast(p) && isToUs(p) && p->want_ack) {
-                        sendAckNak(meshtastic_Routing_Error_NONE, getFrom(p), p->id, p->channel, 0);
-                    }
-                }
+                if (!findInTxQueue(p->from, p->id) && !perhapsRelay(p) && isToUs(p) && p->want_ack)
+                    sendAckNak(meshtastic_Routing_Error_NONE, getFrom(p), p->id, p->channel, 0);
             } else if (!weWereNextHop) {
                 perhapsCancelDupe(p); // If it's a dupe, cancel relay if we were not explicitly asked to relay
             }
@@ -90,14 +107,13 @@ void NextHopRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtast
     bool isAckorReply = (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) &&
                         (p->decoded.request_id != 0 || p->decoded.reply_id != 0);
     if (isAckorReply) {
-        // Update next-hop for the original transmitter of this successful transmission to the relay node, but ONLY if "from"
-        // is not 0 (means implicit ACK) and original packet was also relayed by this node, or we sent it directly to the
-        // destination
+        // Update next-hop for the original transmitter of this successful transmission to the relay node, but ONLY if "from" is
+        // not 0 (means implicit ACK) and original packet was also relayed by this node, or we sent it directly to the destination
         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 *only* 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
                 bool wasAlreadyRelayer = wasRelayer(p->relay_node, p->decoded.request_id, p->to);
                 bool weWereSoleRelayer = false;
                 bool weWereRelayer = wasRelayer(ourRelayID, p->decoded.request_id, p->to, &weWereSoleRelayer);
@@ -118,49 +134,34 @@ void NextHopRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtast
         }
     }
 
-    perhapsRebroadcast(p);
+    perhapsRelay(p);
 
     // handle the packet as normal
     Router::sniffReceived(p, c);
 }
 
-/* Check if we should be rebroadcasting this packet if so, do so. */
-bool NextHopRouter::perhapsRebroadcast(const meshtastic_MeshPacket *p)
+/* Check if we should be relaying this packet if so, do so. */
+bool NextHopRouter::perhapsRelay(const meshtastic_MeshPacket *p)
 {
     if (!isToUs(p) && !isFromUs(p) && p->hop_limit > 0) {
-        if (p->id != 0) {
+        if (p->next_hop == NO_NEXT_HOP_PREFERENCE || p->next_hop == nodeDB->getLastByteOfNodeNum(getNodeNum())) {
             if (isRebroadcaster()) {
-                if (p->next_hop == NO_NEXT_HOP_PREFERENCE || p->next_hop == nodeDB->getLastByteOfNodeNum(getNodeNum())) {
-                    meshtastic_MeshPacket *tosend = packetPool.allocCopy(*p); // keep a copy because we will be sending it
-                    LOG_INFO("Rebroadcast received message coming from %x", p->relay_node);
+                meshtastic_MeshPacket *tosend = packetPool.allocCopy(*p); // keep a copy because we will be sending it
+                LOG_INFO("Relaying received message coming from %x", p->relay_node);
 
-                    // Use shared logic to determine if hop_limit should be decremented
-                    if (shouldDecrementHopLimit(p)) {
-                        tosend->hop_limit--; // bump down the hop count
-                    } else {
-                        LOG_INFO("favorite-ROUTER/CLIENT_BASE-to-ROUTER/CLIENT_BASE rebroadcast: preserving hop_limit");
-                    }
-#if USERPREFS_EVENT_MODE
-                    if (tosend->hop_limit > 2) {
-                        // if we are "correcting" the hop_limit, "correct" the hop_start by the same amount to preserve hops away.
-                        tosend->hop_start -= (tosend->hop_limit - 2);
-                        tosend->hop_limit = 2;
-                    }
-#endif
-
-                    if (p->next_hop == NO_NEXT_HOP_PREFERENCE) {
-                        FloodingRouter::send(tosend);
-                    } else {
-                        NextHopRouter::send(tosend);
-                    }
-
-                    return true;
+                // Use shared logic to determine if hop_limit should be decremented
+                if (shouldDecrementHopLimit(p)) {
+                    tosend->hop_limit--; // bump down the hop count
+                } else {
+                    LOG_INFO("Router/CLIENT_BASE-to-favorite-router/CLIENT_BASE relay: preserving hop_limit");
                 }
+
+                NextHopRouter::send(tosend);
+
+                return true;
             } else {
-                LOG_DEBUG("No rebroadcast: Role = CLIENT_MUTE or Rebroadcast Mode = NONE");
+                LOG_DEBUG("Not rebroadcasting: Role = CLIENT_MUTE or Rebroadcast Mode = NONE");
             }
-        } else {
-            LOG_DEBUG("Ignore 0 id broadcast");
         }
     }
 
@@ -230,13 +231,13 @@ bool NextHopRouter::stopRetransmission(GlobalPacketId key)
             }
         }
 
-        // Regardless of whether or not we canceled this packet from the txQueue, remove it from our pending list so it
-        // doesn't get scheduled again. (This is the core of stopRetransmission.)
+        // Regardless of whether or not we canceled this packet from the txQueue, remove it from our pending list so it doesn't
+        // get scheduled again. (This is the core of stopRetransmission.)
         auto numErased = pending.erase(key);
         assert(numErased == 1);
 
-        // When we remove an entry from pending, always be sure to release the copy of the packet that was allocated in the
-        // call to startRetransmission.
+        // When we remove an entry from pending, always be sure to release the copy of the packet that was allocated in the call
+        // to startRetransmission.
         packetPool.release(p);
 
         return true;
diff --git a/src/mesh/NextHopRouter.h b/src/mesh/NextHopRouter.h
index c1df3596b..0022644e9 100644
--- a/src/mesh/NextHopRouter.h
+++ b/src/mesh/NextHopRouter.h
@@ -148,7 +148,7 @@ class NextHopRouter : public FloodingRouter
      */
     uint8_t getNextHop(NodeNum to, uint8_t relay_node);
 
-    /** Check if we should be rebroadcasting this packet if so, do so.
-     *  @return true if we did rebroadcast */
-    bool perhapsRebroadcast(const meshtastic_MeshPacket *p) override;
+    /** Check if we should be relaying this packet if so, do so.
+     *  @return true if we did relay */
+    bool perhapsRelay(const meshtastic_MeshPacket *p);
 };
\ No newline at end of file
diff --git a/src/mesh/PacketHistory.cpp b/src/mesh/PacketHistory.cpp
index b4af707ae..49d581d9a 100644
--- a/src/mesh/PacketHistory.cpp
+++ b/src/mesh/PacketHistory.cpp
@@ -94,6 +94,7 @@ bool PacketHistory::wasSeenRecently(const meshtastic_MeshPacket *p, bool withUpd
         LOG_DEBUG("Packet History - Hop limit upgrade: packet 0x%08x from hop_limit=%d to hop_limit=%d", p->id, found->hop_limit,
                   p->hop_limit);
         *wasUpgraded = true;
+        seenRecently = false; // Allow router processing but prevent duplicate app delivery
     } else if (wasUpgraded) {
         *wasUpgraded = false; // Initialize to false if not an upgrade
     }
diff --git a/src/mesh/http/ContentHandler.cpp b/src/mesh/http/ContentHandler.cpp
index 7b7ebb595..f87c6e3b0 100644
--- a/src/mesh/http/ContentHandler.cpp
+++ b/src/mesh/http/ContentHandler.cpp
@@ -148,8 +148,6 @@ void registerHandlers(HTTPServer *insecureServer, HTTPSServer *secureServer)
 
 void handleAPIv1FromRadio(HTTPRequest *req, HTTPResponse *res)
 {
-    if (webServerThread)
-        webServerThread->markActivity();
 
     LOG_DEBUG("webAPI handleAPIv1FromRadio");
 
@@ -393,9 +391,6 @@ void handleFsDeleteStatic(HTTPRequest *req, HTTPResponse *res)
 
 void handleStatic(HTTPRequest *req, HTTPResponse *res)
 {
-    if (webServerThread)
-        webServerThread->markActivity();
-
     // Get access to the parameters
     ResourceParameters *params = req->getParams();
 
diff --git a/src/mesh/http/WebServer.cpp b/src/mesh/http/WebServer.cpp
index 3a264fa5a..bf170de59 100644
--- a/src/mesh/http/WebServer.cpp
+++ b/src/mesh/http/WebServer.cpp
@@ -49,12 +49,6 @@ Preferences prefs;
 using namespace httpsserver;
 #include "mesh/http/ContentHandler.h"
 
-static const uint32_t ACTIVE_THRESHOLD_MS = 5000;
-static const uint32_t MEDIUM_THRESHOLD_MS = 30000;
-static const int32_t ACTIVE_INTERVAL_MS = 50;
-static const int32_t MEDIUM_INTERVAL_MS = 200;
-static const int32_t IDLE_INTERVAL_MS = 1000;
-
 static SSLCert *cert;
 static HTTPSServer *secureServer;
 static HTTPServer *insecureServer;
@@ -181,32 +175,6 @@ WebServerThread::WebServerThread() : concurrency::OSThread("WebServer")
     if (!config.network.wifi_enabled && !config.network.eth_enabled) {
         disable();
     }
-    lastActivityTime = millis();
-}
-
-void WebServerThread::markActivity()
-{
-    lastActivityTime = millis();
-}
-
-int32_t WebServerThread::getAdaptiveInterval()
-{
-    uint32_t currentTime = millis();
-    uint32_t timeSinceActivity;
-
-    if (currentTime >= lastActivityTime) {
-        timeSinceActivity = currentTime - lastActivityTime;
-    } else {
-        timeSinceActivity = (UINT32_MAX - lastActivityTime) + currentTime + 1;
-    }
-
-    if (timeSinceActivity < ACTIVE_THRESHOLD_MS) {
-        return ACTIVE_INTERVAL_MS;
-    } else if (timeSinceActivity < MEDIUM_THRESHOLD_MS) {
-        return MEDIUM_INTERVAL_MS;
-    } else {
-        return IDLE_INTERVAL_MS;
-    }
 }
 
 int32_t WebServerThread::runOnce()
@@ -221,7 +189,8 @@ int32_t WebServerThread::runOnce()
         ESP.restart();
     }
 
-    return getAdaptiveInterval();
+    // Loop every 5ms.
+    return (5);
 }
 
 void initWebServer()
diff --git a/src/mesh/http/WebServer.h b/src/mesh/http/WebServer.h
index e7a29a5a7..815d87432 100644
--- a/src/mesh/http/WebServer.h
+++ b/src/mesh/http/WebServer.h
@@ -10,17 +10,13 @@ void createSSLCert();
 
 class WebServerThread : private concurrency::OSThread
 {
-  private:
-    uint32_t lastActivityTime = 0;
 
   public:
     WebServerThread();
     uint32_t requestRestart = 0;
-    void markActivity();
 
   protected:
     virtual int32_t runOnce() override;
-    int32_t getAdaptiveInterval();
 };
 
 extern WebServerThread *webServerThread;
diff --git a/src/modules/TraceRouteModule.cpp b/src/modules/TraceRouteModule.cpp
index 5bdde1919..fc2cc232b 100644
--- a/src/modules/TraceRouteModule.cpp
+++ b/src/modules/TraceRouteModule.cpp
@@ -21,11 +21,6 @@ void TraceRouteModule::alterReceivedProtobuf(meshtastic_MeshPacket &p, meshtasti
 {
     const meshtastic_Data &incoming = p.decoded;
 
-    // Update next-hops using returned route
-    if (incoming.request_id) {
-        updateNextHops(p, r);
-    }
-
     // Insert unknown hops if necessary
     insertUnknownHops(p, r, !incoming.request_id);
 
@@ -158,65 +153,6 @@ void TraceRouteModule::alterReceivedProtobuf(meshtastic_MeshPacket &p, meshtasti
     }
 }
 
-void TraceRouteModule::updateNextHops(meshtastic_MeshPacket &p, meshtastic_RouteDiscovery *r)
-{
-    // E.g. if the route is A->B->C->D and we are B, we can set C as next-hop for C and D
-    // Similarly, if we are C, we can set D as next-hop for D
-    // If we are A, we can set B as next-hop for B, C and D
-
-    // First check if we were the original sender or in the original route
-    int8_t nextHopIndex = -1;
-    if (isToUs(&p)) {
-        nextHopIndex = 0; // We are the original sender, next hop is first in route
-    } else {
-        // Check if we are in the original route
-        for (uint8_t i = 0; i < r->route_count; i++) {
-            if (r->route[i] == nodeDB->getNodeNum()) {
-                nextHopIndex = i + 1; // Next hop is the one after us
-                break;
-            }
-        }
-    }
-
-    // If we are in the original route, update the next hops
-    if (nextHopIndex != -1) {
-        // For every node after us, we can set the next-hop to the first node after us
-        NodeNum nextHop;
-        if (nextHopIndex == r->route_count) {
-            nextHop = p.from; // We are the last in the route, next hop is destination
-        } else {
-            nextHop = r->route[nextHopIndex];
-        }
-
-        if (nextHop == NODENUM_BROADCAST) {
-            return;
-        }
-        uint8_t nextHopByte = nodeDB->getLastByteOfNodeNum(nextHop);
-
-        // For the rest of the nodes in the route, set their next-hop
-        // Note: if we are the last in the route, this loop will not run
-        for (int8_t i = nextHopIndex; i < r->route_count; i++) {
-            NodeNum targetNode = r->route[i];
-            maybeSetNextHop(targetNode, nextHopByte);
-        }
-
-        // Also set next-hop for the destination node
-        maybeSetNextHop(p.from, nextHopByte);
-    }
-}
-
-void TraceRouteModule::maybeSetNextHop(NodeNum target, uint8_t nextHopByte)
-{
-    if (target == NODENUM_BROADCAST)
-        return;
-
-    meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(target);
-    if (node && node->next_hop != nextHopByte) {
-        LOG_INFO("Updating next-hop for 0x%08x to 0x%02x based on traceroute", target, nextHopByte);
-        node->next_hop = nextHopByte;
-    }
-}
-
 void TraceRouteModule::processUpgradedPacket(const meshtastic_MeshPacket &mp)
 {
     if (mp.which_payload_variant != meshtastic_MeshPacket_decoded_tag || mp.decoded.portnum != meshtastic_PortNum_TRACEROUTE_APP)
diff --git a/src/modules/TraceRouteModule.h b/src/modules/TraceRouteModule.h
index dac422388..a069f7157 100644
--- a/src/modules/TraceRouteModule.h
+++ b/src/modules/TraceRouteModule.h
@@ -55,12 +55,6 @@ class TraceRouteModule : public ProtobufModule,
     // Call to add your ID to the route array of a RouteDiscovery message
     void appendMyIDandSNR(meshtastic_RouteDiscovery *r, float snr, bool isTowardsDestination, bool SNRonly);
 
-    // Update next-hops in the routing table based on the returned route
-    void updateNextHops(meshtastic_MeshPacket &p, meshtastic_RouteDiscovery *r);
-
-    // Helper to update next-hop for a single node
-    void maybeSetNextHop(NodeNum target, uint8_t nextHopByte);
-
     /* Call to print the route array of a RouteDiscovery message.
        Set origin to where the request came from.
        Set dest to the ID of its destination, or NODENUM_BROADCAST if it has not yet arrived there. */
diff --git a/src/power.h b/src/power.h
index cdbdd3ea0..23eb95064 100644
--- a/src/power.h
+++ b/src/power.h
@@ -138,7 +138,6 @@ class Power : private concurrency::OSThread
     void reboot();
     // open circuit voltage lookup table
     uint8_t low_voltage_counter;
-    int32_t lastLogTime = 0;
 #ifdef DEBUG_HEAP
     uint32_t lastheap;
 #endif

From 81a5aeff7480021dce73d0a75ab0f5e77d8fe8c5 Mon Sep 17 00:00:00 2001
From: Chloe Bethel 
Date: Tue, 7 Oct 2025 12:11:26 +0100
Subject: [PATCH 440/691] Fix serial pins for Ebyte E77 MBL board (#8246)

Also move RAK3172 and new EBYTE_E77_MBL define to variant.h, as this makes VSCode know about the defines properly...

Co-authored-by: Ben Meadors 
---
 src/modules/SerialModule.cpp                  | 2 +-
 variants/stm32/CDEBYTE_E77-MBL/platformio.ini | 5 ++++-
 variants/stm32/CDEBYTE_E77-MBL/variant.h      | 2 ++
 variants/stm32/rak3172/platformio.ini         | 1 -
 variants/stm32/rak3172/variant.h              | 2 ++
 5 files changed, 9 insertions(+), 3 deletions(-)

diff --git a/src/modules/SerialModule.cpp b/src/modules/SerialModule.cpp
index 7485f1c2d..a9ec8f6a8 100644
--- a/src/modules/SerialModule.cpp
+++ b/src/modules/SerialModule.cpp
@@ -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) || defined(RAK3172)
+#elif defined(CONFIG_IDF_TARGET_ESP32C6) || defined(RAK3172) || defined(EBYTE_E77_MBL)
 SerialModule::SerialModule() : StreamAPI(&Serial1), concurrency::OSThread("Serial") {}
 static Print *serialPrint = &Serial1;
 #else
diff --git a/variants/stm32/CDEBYTE_E77-MBL/platformio.ini b/variants/stm32/CDEBYTE_E77-MBL/platformio.ini
index 290982405..c5af9a4a4 100644
--- a/variants/stm32/CDEBYTE_E77-MBL/platformio.ini
+++ b/variants/stm32/CDEBYTE_E77-MBL/platformio.ini
@@ -6,9 +6,12 @@ board_level = extra
 build_flags =
   ${stm32_base.build_flags}
   -Ivariants/stm32/CDEBYTE_E77-MBL
-  -DSERIAL_UART_INSTANCE=1
+  -DSERIAL_UART_INSTANCE=2
   -DPIN_SERIAL_RX=PA3
   -DPIN_SERIAL_TX=PA2
+  -DENABLE_HWSERIAL1
+  -DPIN_SERIAL1_RX=PB7
+  -DPIN_SERIAL1_TX=PB6
   -DMESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR=1
   -DMESHTASTIC_EXCLUDE_I2C=1
   -DMESHTASTIC_EXCLUDE_GPS=1
diff --git a/variants/stm32/CDEBYTE_E77-MBL/variant.h b/variants/stm32/CDEBYTE_E77-MBL/variant.h
index 317f44489..e3d111a33 100644
--- a/variants/stm32/CDEBYTE_E77-MBL/variant.h
+++ b/variants/stm32/CDEBYTE_E77-MBL/variant.h
@@ -18,4 +18,6 @@ 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
+
+#define EBYTE_E77_MBL
 #endif
diff --git a/variants/stm32/rak3172/platformio.ini b/variants/stm32/rak3172/platformio.ini
index 7fc6c7cba..b9a4b8a04 100644
--- a/variants/stm32/rak3172/platformio.ini
+++ b/variants/stm32/rak3172/platformio.ini
@@ -6,7 +6,6 @@ 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
diff --git a/variants/stm32/rak3172/variant.h b/variants/stm32/rak3172/variant.h
index 45752b481..30d2b57b4 100644
--- a/variants/stm32/rak3172/variant.h
+++ b/variants/stm32/rak3172/variant.h
@@ -16,4 +16,6 @@ Do not expect a working Meshtastic device with this target.
 #define LED_PIN PA0 // Green LED
 #define LED_STATE_ON 1
 
+#define RAK3172
+
 #endif

From bd9076b7401aed73b1810e746fbb1aed30ba8649 Mon Sep 17 00:00:00 2001
From: Ben Meadors 
Date: Tue, 7 Oct 2025 06:14:35 -0500
Subject: [PATCH 441/691] Remove risky change

---
 src/modules/NodeInfoModule.cpp | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)

diff --git a/src/modules/NodeInfoModule.cpp b/src/modules/NodeInfoModule.cpp
index 9b94b3933..aaab019d6 100644
--- a/src/modules/NodeInfoModule.cpp
+++ b/src/modules/NodeInfoModule.cpp
@@ -113,8 +113,12 @@ meshtastic_MeshPacket *NodeInfoModule::allocReply()
             u.public_key.size = 0;
         }
 
-        // Clear the user.id field since it should be derived from node number on the receiving end
-        u.id[0] = '\0';
+        // FIXME: Clear the user.id field since it should be derived from node number on the receiving end
+        // u.id[0] = '\0';
+
+        // Ensure our user.id is derived correctly
+        strcpy(u.id, nodeDB->getNodeId().c_str());
+
         LOG_INFO("Send owner %s/%s/%s", u.id, u.long_name, u.short_name);
         lastSentToMesh = millis();
         return allocDataProtobuf(u);

From 468b40e8dbf2bf899ec0020e57fa2035b4f03b54 Mon Sep 17 00:00:00 2001
From: Tom Fifield 
Date: Tue, 7 Oct 2025 22:24:09 +1100
Subject: [PATCH 442/691] Wait until after GPS lock hold before updating
 position, if we can. (#8064)

* Wait until after GPS lock hold before updating position, if we can.

After the recent patch, we hold lock for a bit before updating the position.
The positions that come in after the hold are genuinely better positions
 than what we've been doing before. However, they only come 20 seconds
 after we've got lock.

Previously, we would update the local position as soon as we got a lock as well
as at the end of the hold, since a hold was not always possible.
With this patch, if the settings allow, we should skip that first local position update.

Fixes https://github.com/meshtastic/firmware/issues/8029

* Fix falling edge handling.

* spelling

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Congeal lock handling

* Add named constants

* define unit to avoid confusion

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* ifdef, not if.

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Add handling for when we first turn on.

* Don't run if not active

* Reset fixhold

* Logic fixes

* Add path for ACTIVE--> IDLE --> ACTIVE

Previously we only covered HARDSLEEP --> ACTIVE.

* Change hold time to gps_update_interval - 10s

* Update comment

* Add extra buffer to avoid re-starting hold

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Ben Meadors 
---
 src/gps/GPS.cpp | 125 +++++++++++++++++++++++++++++++++---------------
 src/gps/GPS.h   |   5 +-
 2 files changed, 90 insertions(+), 40 deletions(-)

diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp
index 1125cf998..2cfa558ed 100644
--- a/src/gps/GPS.cpp
+++ b/src/gps/GPS.cpp
@@ -1031,7 +1031,7 @@ void GPS::down()
     LOG_DEBUG("%us until next search", sleepTime / 1000);
 
     // If update interval less than 10 seconds, no attempt to sleep
-    if (updateInterval <= 10 * 1000UL || sleepTime == 0)
+    if (updateInterval <= GPS_UPDATE_ALWAYS_ON_THRESHOLD_MS || sleepTime == 0)
         setPowerState(GPS_IDLE);
 
     else {
@@ -1102,6 +1102,29 @@ int32_t GPS::runOnce()
         publishUpdate();
     }
 
+    // ======================== GPS_ACTIVE state ========================
+    // In GPS_ACTIVE state, GPS is powered on and we're receiving NMEA messages.
+    // We use the following logic to determine when to update the local position
+    // or time by running GPS::publishUpdate.
+    // Note: Local position update is asynchronous to position broadcast. We
+    // generally run this state every gps_update_interval seconds, and in most cases
+    // gps_update_interval is faster than the position broadcast interval so there's a
+    // fresh position ready when the device wants to broadcast one on the mesh.
+    //
+    // 1. Got a time for the first time --> set the time, don't publish.
+    // 2. Got a lock for the first time
+    //   --> If gps_update_interval is <= 10s --> publishUpdate
+    //   --> Otherwise, hold for MIN(gps_update_interval - GPS_UPDATE_ALWAYS_ON_THRESHOLD_MS, 20s)
+    //  3. Got a lock after turning back on
+    //   --> If gps_update_interval is <= 10s --> publishUpdate
+    //   --> Otherwise, hold for MIN(gps_update_interval - GPS_UPDATE_ALWAYS_ON_THRESHOLD_MS, 20s)
+    // 4. Hold has expired
+    //   --> If we have a time and a location --> publishUpdate
+    //   --> down()
+    // 5. Search time has expired
+    //   --> If we have a time and a location --> publishUpdate
+    //   --> If we had a location before but don't now --> publishUpdate
+    //   --> down()
     if (whileActive()) {
         // if we have received valid NMEA claim we are connected
         setConnected();
@@ -1111,55 +1134,81 @@ int32_t GPS::runOnce()
     if (!config.position.fixed_position && powerState != GPS_ACTIVE && scheduling.isUpdateDue())
         up();
 
-    // If we've already set time from the GPS, no need to ask the GPS
-    bool gotTime = (getRTCQuality() >= RTCQualityGPS);
-    if (!gotTime && lookForTime()) { // Note: we count on this && short-circuiting and not resetting the RTC time
-        gotTime = true;
-        shouldPublish = true;
-    }
-
+    // quality of the previous fix. We set it to 0 when we go down, so it's a way
+    // to check if we're getting a lock after being GPS_OFF.
     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
-    }
+    if (powerState == GPS_ACTIVE) {
+        // if gps_update_interval is <=10s, GPS never goes off, so we treat that differently
+        uint32_t updateInterval = Default::getConfiguredOrDefaultMs(config.position.gps_update_interval);
 
-    bool tooLong = scheduling.searchedTooLong();
-    if (tooLong)
-        LOG_WARN("Couldn't publish a valid location: didn't get a GPS lock in time");
+        // 1. Got a time for the first time
+        bool gotTime = (getRTCQuality() >= RTCQualityGPS);
+        if (!gotTime && lookForTime()) { // Note: we count on this && short-circuiting and not resetting the RTC time
+            gotTime = true;
+        }
 
-    // Once we get a location we no longer desperately want an update
-    if ((gotLoc && gotTime) || tooLong) {
+        // 2. Got a lock for the first time, or 3. Got a lock after turning back on
+        bool gotLoc = lookForLocation();
+        if (gotLoc) {
+#ifdef GPS_DEBUG
+            if (!hasValidLocation) { // declare that we have location ASAP
+                LOG_DEBUG("hasValidLocation RISING EDGE");
+            }
+#endif
+            if (updateInterval <= GPS_UPDATE_ALWAYS_ON_THRESHOLD_MS) {
+                hasValidLocation = true;
+                shouldPublish = true;
+            } else if (!hasValidLocation || prev_fixQual == 0 || (fixHoldEnds + GPS_THREAD_INTERVAL) < millis()) {
+                hasValidLocation = true;
+                // Hold for up to 20secs after getting a lock to download ephemeris etc
+                uint32_t holdTime = updateInterval - GPS_UPDATE_ALWAYS_ON_THRESHOLD_MS;
+                if (holdTime > GPS_FIX_HOLD_MAX_MS)
+                    holdTime = GPS_FIX_HOLD_MAX_MS;
+                fixHoldEnds = millis() + holdTime;
+#ifdef GPS_DEBUG
+                LOG_DEBUG("Holding for %ums after lock", holdTime);
+#endif
+            }
+        }
+
+        bool tooLong = scheduling.searchedTooLong();
         if (tooLong && !gotLoc) {
+            LOG_WARN("Couldn't publish a valid location: didn't get a GPS lock in time");
             // we didn't get a location during this ack window, therefore declare loss of lock
             if (hasValidLocation) {
-                LOG_DEBUG("hasValidLocation FALLING EDGE");
-            }
-            p = meshtastic_Position_init_default;
-            hasValidLocation = false;
-        }
-        if (millis() > fixHoldEnds) {
-            shouldPublish = true; // publish our update at the end of the lock hold
-            publishUpdate();
-            down();
+                p = meshtastic_Position_init_default;
+                hasValidLocation = false;
+                shouldPublish = true;
 #ifdef GPS_DEBUG
-        } else {
+                LOG_DEBUG("hasValidLocation FALLING EDGE");
+#endif
+            }
+        }
+
+        // Hold has expired , Search time has expired, we got a time only, or we never needed to hold.
+        bool holdExpired = (fixHoldEnds != 0 && millis() > fixHoldEnds);
+        if (shouldPublish || tooLong || holdExpired) {
+            if (gotTime && hasValidLocation) {
+                shouldPublish = true;
+            }
+            if (shouldPublish) {
+                fixHoldEnds = 0;
+                publishUpdate();
+            }
+
+            // There's a chance we just got a time, so keep going to see if we can get a location too
+            if (tooLong || holdExpired) {
+                down();
+            }
+
+#ifdef GPS_DEBUG
+        } else if (fixHoldEnds != 0) {
             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
-    publishUpdate();
+    // ===================== end GPS_ACTIVE state ========================
 
     if (config.position.fixed_position == true && hasValidLocation)
         return disable(); // This should trigger when we have a fixed position, and get that first position
diff --git a/src/gps/GPS.h b/src/gps/GPS.h
index f3bcf37f5..8ba1ce0a6 100644
--- a/src/gps/GPS.h
+++ b/src/gps/GPS.h
@@ -16,6 +16,9 @@
 #define GPS_EN_ACTIVE 1
 #endif
 
+static constexpr uint32_t GPS_UPDATE_ALWAYS_ON_THRESHOLD_MS = 10 * 1000UL;
+static constexpr uint32_t GPS_FIX_HOLD_MAX_MS = 20000;
+
 typedef enum {
     GNSS_MODEL_ATGM336H,
     GNSS_MODEL_MTK,
@@ -175,8 +178,6 @@ class GPS : private concurrency::OSThread
      */
     bool hasValidLocation = false; // default to false, until we complete our first read
 
-    bool isInPowersave = false;
-
     bool shouldPublish = false; // If we've changed GPS state, this will force a publish the next loop()
 
     bool hasGPS = false; // Do we have a GPS we are talking to

From f0e4ea76649a49f614e391bbb7c16519e163a219 Mon Sep 17 00:00:00 2001
From: szlifier 
Date: Tue, 7 Oct 2025 13:25:38 +0200
Subject: [PATCH 443/691] Add SHT4x serial number for detection (#8222)

SHT4X chip recognized as SHT31, registerValue that stores first bytes of chip's serial number did not mach the chip.
Added a serial number match for SHT40 found in a SONOFF SNZB-02P.
---
 src/detect/ScanI2CTwoWire.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp
index 6df3f8be1..89a0610b4 100644
--- a/src/detect/ScanI2CTwoWire.cpp
+++ b/src/detect/ScanI2CTwoWire.cpp
@@ -378,7 +378,7 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
             case SHT31_4x_ADDR:     // same as OPT3001_ADDR_ALT
             case SHT31_4x_ADDR_ALT: // same as OPT3001_ADDR
                 registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x89), 2);
-                if (registerValue == 0x11a2 || registerValue == 0x11da || registerValue == 0xe9c || registerValue == 0xc8d) {
+                if (registerValue == 0x11a2 || registerValue == 0x11da || registerValue == 0x11f3 || registerValue == 0xe9c || registerValue == 0xc8d) {
                     type = SHT4X;
                     logFoundDevice("SHT4X", (uint8_t)addr.address);
                 } else if (getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x7E), 2) == 0x5449) {

From e4ec719e6f888c47ee3a1cc78afd2b96d5f196d1 Mon Sep 17 00:00:00 2001
From: Austin Lane 
Date: Tue, 7 Oct 2025 12:54:02 -0400
Subject: [PATCH 444/691] Force coverage tests to run in simulation mode

---
 variants/native/portduino/platformio.ini | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/variants/native/portduino/platformio.ini b/variants/native/portduino/platformio.ini
index 2ccdfbbfc..1385aa0c7 100644
--- a/variants/native/portduino/platformio.ini
+++ b/variants/native/portduino/platformio.ini
@@ -107,3 +107,5 @@ build_src_filter = ${env:native-tft.build_src_filter}
 [env:coverage]
 extends = env:native
 build_flags = -lgcov --coverage -fprofile-abs-path -fsanitize=address ${env:native.build_flags}
+; https://docs.platformio.org/en/latest/projectconf/sections/env/options/test/test_testing_command.html
+test_testing_command = ${platformio.build_dir}/${this.__env__}/program -s

From e8e8ee0993ff1fddfa6b0ac42dc114a77e36d90b Mon Sep 17 00:00:00 2001
From: Ben Meadors 
Date: Tue, 7 Oct 2025 12:04:50 -0500
Subject: [PATCH 445/691] Revert "Force coverage tests to run in simulation
 mode"

This reverts commit e4ec719e6f888c47ee3a1cc78afd2b96d5f196d1.
---
 variants/native/portduino/platformio.ini | 2 --
 1 file changed, 2 deletions(-)

diff --git a/variants/native/portduino/platformio.ini b/variants/native/portduino/platformio.ini
index 1385aa0c7..2ccdfbbfc 100644
--- a/variants/native/portduino/platformio.ini
+++ b/variants/native/portduino/platformio.ini
@@ -107,5 +107,3 @@ build_src_filter = ${env:native-tft.build_src_filter}
 [env:coverage]
 extends = env:native
 build_flags = -lgcov --coverage -fprofile-abs-path -fsanitize=address ${env:native.build_flags}
-; https://docs.platformio.org/en/latest/projectconf/sections/env/options/test/test_testing_command.html
-test_testing_command = ${platformio.build_dir}/${this.__env__}/program -s

From 74e6723ad90f1211421f7b8d6b5be62405eb0a7d Mon Sep 17 00:00:00 2001
From: Austin 
Date: Tue, 7 Oct 2025 14:14:19 -0400
Subject: [PATCH 446/691] Force coverage tests to run in simulation mode
 (#8251)

* Force coverage tests to run in simulation mode

* Revert "Force coverage tests to run in simulation mode"

This reverts commit e4ec719e6f888c47ee3a1cc78afd2b96d5f196d1.

* Force coverage tests to run in simulation mode

---------

Co-authored-by: Ben Meadors 
---
 variants/native/portduino/platformio.ini | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/variants/native/portduino/platformio.ini b/variants/native/portduino/platformio.ini
index 2ccdfbbfc..4e6a592de 100644
--- a/variants/native/portduino/platformio.ini
+++ b/variants/native/portduino/platformio.ini
@@ -107,3 +107,7 @@ build_src_filter = ${env:native-tft.build_src_filter}
 [env:coverage]
 extends = env:native
 build_flags = -lgcov --coverage -fprofile-abs-path -fsanitize=address ${env:native.build_flags}
+; https://docs.platformio.org/en/latest/projectconf/sections/env/options/test/test_testing_command.html
+test_testing_command =
+  ${platformio.build_dir}/${this.__env__}/program
+  -s

From 0c2673ee2f4d3f41f2ff27801ec8af020c1f8a2b Mon Sep 17 00:00:00 2001
From: Ben Meadors 
Date: Tue, 7 Oct 2025 14:32:36 -0500
Subject: [PATCH 447/691] Mercy

---
 test/test_mqtt/MQTT.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/test/test_mqtt/MQTT.cpp b/test/test_mqtt/MQTT.cpp
index 8726d1ccb..1c2f0642a 100644
--- a/test/test_mqtt/MQTT.cpp
+++ b/test/test_mqtt/MQTT.cpp
@@ -332,7 +332,7 @@ void setUp(void)
     };
     channelFile.channels_count = 1;
     owner = meshtastic_User{.id = "!12345678"};
-    myNodeInfo = meshtastic_MyNodeInfo{.my_node_num = 10};
+    myNodeInfo = meshtastic_MyNodeInfo{.my_node_num = 0x12345678}; // Match the expected gateway ID in topic
     localPosition =
         meshtastic_Position{.has_latitude_i = true, .latitude_i = 7 * 1e7, .has_longitude_i = true, .longitude_i = 3 * 1e7};
 

From fcb1d64eb936fa77522cd9b8e7867e680e6b27e3 Mon Sep 17 00:00:00 2001
From: Ben Meadors 
Date: Tue, 7 Oct 2025 17:47:08 -0500
Subject: [PATCH 448/691] Bloop

---
 src/graphics/Screen.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp
index 31c211019..4485ca7a3 100644
--- a/src/graphics/Screen.cpp
+++ b/src/graphics/Screen.cpp
@@ -1503,7 +1503,7 @@ int Screen::handleTextMessage(const meshtastic_MeshPacket *packet)
                 screen->showSimpleBanner(banner, 1500);
                 if (config.device.buzzer_mode != meshtastic_Config_DeviceConfig_BuzzerMode_DIRECT_MSG_ONLY ||
                     (isAlert && moduleConfig.external_notification.alert_bell_buzzer) ||
-                    (!isBroadcast(packet->to) && isToUs(p))) {
+                    (!isBroadcast(packet->to) && isToUs(packet))) {
                     // Beep if not in DIRECT_MSG_ONLY mode or if in DIRECT_MSG_ONLY mode and either
                     // - packet contains an alert and alert bell buzzer is enabled
                     // - packet is a non-broadcast that is addressed to this node

From d332dfa19b851d93b1dc39f5fe3831995136e971 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Wed, 8 Oct 2025 17:33:39 +1100
Subject: [PATCH 449/691] Update python Docker tag to v3.14 (#8255)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
 Dockerfile        | 2 +-
 alpine.Dockerfile | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/Dockerfile b/Dockerfile
index b1e151ac7..111dd69fc 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -3,7 +3,7 @@
 # trunk-ignore-all(hadolint/DL3008): Do not pin apt package versions
 # trunk-ignore-all(hadolint/DL3013): Do not pin pip package versions
 
-FROM python:3.13-slim-trixie AS builder
+FROM python:3.14-slim-trixie AS builder
 ARG PIO_ENV=native
 ENV DEBIAN_FRONTEND=noninteractive
 ENV TZ=Etc/UTC
diff --git a/alpine.Dockerfile b/alpine.Dockerfile
index 670736241..0469874e4 100644
--- a/alpine.Dockerfile
+++ b/alpine.Dockerfile
@@ -3,7 +3,7 @@
 # trunk-ignore-all(hadolint/DL3018): Do not pin apk package versions
 # trunk-ignore-all(hadolint/DL3013): Do not pin pip package versions
 
-FROM python:3.13-alpine3.22 AS builder
+FROM python:3.14-alpine3.22 AS builder
 ARG PIO_ENV=native
 ENV PIP_ROOT_USER_ACTION=ignore
 

From 7822f28152cab468b13567b5cef583cde8468832 Mon Sep 17 00:00:00 2001
From: Andrew Yong 
Date: Thu, 9 Oct 2025 00:33:50 +0800
Subject: [PATCH 450/691] fix: Move `#include "variant.h"` to top of file
 (fixes #8276) (#8278)

* Force coverage tests to run in simulation mode

* Revert "Force coverage tests to run in simulation mode"

This reverts commit e4ec719e6f888c47ee3a1cc78afd2b96d5f196d1.

* fix: Move `#include "variant.h"` to top of file (fixes #8276)

The original line being further down the file causes any #ifdef/defined() checks for definitions in variant.h to silently skip.

This was noticed when `USE_GC1109_PA` in Heltec v4 and Heltec Wireless Tracker failed to correct program TX_GAIN_LORA, but will also affect any variant.h-dependent configurations in this file, if they would have been defined above where the `#include` previously was.

---------

Co-authored-by: Austin Lane 
Co-authored-by: Ben Meadors 
---
 src/configuration.h | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/src/configuration.h b/src/configuration.h
index e67f0b8e5..c6c8d673c 100644
--- a/src/configuration.h
+++ b/src/configuration.h
@@ -33,6 +33,9 @@ along with this program.  If not, see .
 #include "pcf8563.h"
 #endif
 
+/* Offer chance for variant-specific defines */
+#include "variant.h"
+
 // -----------------------------------------------------------------------------
 // Version
 // -----------------------------------------------------------------------------
@@ -260,9 +263,6 @@ along with this program.  If not, see .
 // convert 24-bit color to 16-bit (56K)
 #define COLOR565(r, g, b) (((r & 0xF8) << 8) | ((g & 0xFC) << 3) | ((b & 0xF8) >> 3))
 
-/* Step #1: offer chance for variant-specific defines */
-#include "variant.h"
-
 #if defined(VEXT_ENABLE) && !defined(VEXT_ON_VALUE)
 // Older variant.h files might not be defining this value, so stay with the old default
 #define VEXT_ON_VALUE LOW

From adae68fbfe94d1415e54a1bafb842f144e02a4df Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Wed, 8 Oct 2025 11:34:11 -0500
Subject: [PATCH 451/691] Update meshtastic/device-ui digest to 6d8cc22 (#8275)

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 2e101a8a2..7121f00b7 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -120,7 +120,7 @@ lib_deps =
 [device-ui_base]
 lib_deps =
 	# renovate: datasource=git-refs depName=meshtastic/device-ui packageName=https://github.com/meshtastic/device-ui gitBranch=master
-	https://github.com/meshtastic/device-ui/archive/e564d78ae1a7e9a225aaf4a73b1cb84c549f510f.zip
+	https://github.com/meshtastic/device-ui/archive/6d8cc228298a1ecd9913aed757187e9527c1facc.zip
 
 ; Common libs for environmental measurements in telemetry module
 [environmental_base]

From 828e11cc482a802b3385c9a4aa7cb7b00a519360 Mon Sep 17 00:00:00 2001
From: Ben Meadors 
Date: Wed, 8 Oct 2025 14:16:57 -0500
Subject: [PATCH 452/691] NimBLE speedup (#8281)

* Remove status polling code in NimBLE

* Goober

---------

Co-authored-by: Jonathan Bennett 
---
 src/mesh/PhoneAPI.cpp          |  2 +-
 src/nimble/NimbleBluetooth.cpp | 19 ++++++++++++-------
 2 files changed, 13 insertions(+), 8 deletions(-)

diff --git a/src/mesh/PhoneAPI.cpp b/src/mesh/PhoneAPI.cpp
index 210e0ba06..d2ed2d4cb 100644
--- a/src/mesh/PhoneAPI.cpp
+++ b/src/mesh/PhoneAPI.cpp
@@ -431,7 +431,6 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
         break;
 
     case STATE_SEND_OTHER_NODEINFOS: {
-        LOG_DEBUG("Send known nodes");
         if (nodeInfoForPhone.num != 0) {
             // Just in case we stored a different user.id in the past, but should never happen going forward
             sprintf(nodeInfoForPhone.user.id, "!%08x", nodeInfoForPhone.num);
@@ -592,6 +591,7 @@ bool PhoneAPI::available()
                 nodeInfoForPhone.snr = isUs ? 0 : nodeInfoForPhone.snr;
                 nodeInfoForPhone.via_mqtt = isUs ? false : nodeInfoForPhone.via_mqtt;
                 nodeInfoForPhone.is_favorite = nodeInfoForPhone.is_favorite || isUs; // Our node is always a favorite
+
                 onNowHasData(0);
             }
         }
diff --git a/src/nimble/NimbleBluetooth.cpp b/src/nimble/NimbleBluetooth.cpp
index 5524765f3..030e00d6b 100644
--- a/src/nimble/NimbleBluetooth.cpp
+++ b/src/nimble/NimbleBluetooth.cpp
@@ -120,14 +120,19 @@ class NimbleBluetoothFromRadioCallback : public NimBLECharacteristicCallbacks
     virtual void onRead(NimBLECharacteristic *pCharacteristic)
 #endif
     {
-        int tries = 0;
-        bluetoothPhoneAPI->phoneWants = true;
-        while (!bluetoothPhoneAPI->hasChecked && tries < 100) {
-            bluetoothPhoneAPI->setIntervalFromNow(0);
-            delay(20);
-            tries++;
-        }
         std::lock_guard guard(bluetoothPhoneAPI->nimble_mutex);
+
+        // If we don't have fresh data, trigger a refresh
+        if (!bluetoothPhoneAPI->hasChecked || bluetoothPhoneAPI->numBytes == 0) {
+            bluetoothPhoneAPI->phoneWants = true;
+            bluetoothPhoneAPI->setIntervalFromNow(0);
+
+            // Get fresh data immediately
+            bluetoothPhoneAPI->numBytes = bluetoothPhoneAPI->getFromRadio(bluetoothPhoneAPI->fromRadioBytes);
+            bluetoothPhoneAPI->hasChecked = true;
+        }
+
+        // Set the characteristic value with whatever data we have
         pCharacteristic->setValue(bluetoothPhoneAPI->fromRadioBytes, bluetoothPhoneAPI->numBytes);
 
         if (bluetoothPhoneAPI->numBytes != 0) // if we did send something, queue it up right away to reload

From 05edcc5d6ce1ed755fe40013459b718bafe84e24 Mon Sep 17 00:00:00 2001
From: Tom Fifield 
Date: Thu, 9 Oct 2025 08:03:26 +1100
Subject: [PATCH 453/691] Fix Station G2 Lora Power Settings (#8273)

* Force coverage tests to run in simulation mode

* Revert "Force coverage tests to run in simulation mode"

This reverts commit e4ec719e6f888c47ee3a1cc78afd2b96d5f196d1.

* Fix Station G2 Lora Power Settings

In #8107 we introduced the ability to specify gain values for
non-linear power amplifiers.

This patch adds appropriate values for the Station G2, based on
the table at https://wiki.uniteng.com/en/meshtastic/station-g2#summary-for-lora-power-amplifier-conduction-test

Fixes https://github.com/meshtastic/firmware/issues/7239

---------

Co-authored-by: Austin Lane 
Co-authored-by: Ben Meadors 
---
 src/configuration.h | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/src/configuration.h b/src/configuration.h
index c6c8d673c..b6b1c1e5e 100644
--- a/src/configuration.h
+++ b/src/configuration.h
@@ -126,6 +126,11 @@ along with this program.  If not, see .
 #define TX_GAIN_LORA 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 10, 10, 9, 9, 8, 7
 #endif
 
+#ifdef STATION_G2
+#define NUM_PA_POINTS 19
+#define TX_GAIN_LORA 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 19, 19, 18, 18
+#endif
+
 // Default system gain to 0 if not defined
 #ifndef TX_GAIN_LORA
 #define TX_GAIN_LORA 0

From 2a1469652540d85710c62ab80b65626a3ee17b08 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Thu, 9 Oct 2025 09:32:41 +1100
Subject: [PATCH 454/691] chore(deps): update github/codeql-action action to v4
 (#8250)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
 .github/workflows/sec_sast_semgrep_cron.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.github/workflows/sec_sast_semgrep_cron.yml b/.github/workflows/sec_sast_semgrep_cron.yml
index 96c993cba..2059fde2c 100644
--- a/.github/workflows/sec_sast_semgrep_cron.yml
+++ b/.github/workflows/sec_sast_semgrep_cron.yml
@@ -41,7 +41,7 @@ jobs:
 
       # step 4
       - name: publish code scanning alerts
-        uses: github/codeql-action/upload-sarif@v3
+        uses: github/codeql-action/upload-sarif@v4
         with:
           sarif_file: report.sarif
           category: semgrep

From 45f15b8fe66b42ac31b05fe06e65d54467317c7b Mon Sep 17 00:00:00 2001
From: Ben Meadors 
Date: Thu, 9 Oct 2025 12:44:00 -0500
Subject: [PATCH 455/691] Fix BLE stateful issues (#8287)

---
 src/mesh/PhoneAPI.cpp          |  2 +-
 src/nimble/NimbleBluetooth.cpp | 23 +++--------------------
 2 files changed, 4 insertions(+), 21 deletions(-)

diff --git a/src/mesh/PhoneAPI.cpp b/src/mesh/PhoneAPI.cpp
index d2ed2d4cb..e8dc9843c 100644
--- a/src/mesh/PhoneAPI.cpp
+++ b/src/mesh/PhoneAPI.cpp
@@ -734,7 +734,7 @@ int PhoneAPI::onNotify(uint32_t newValue)
         LOG_INFO("Tell client we have new packets %u", newValue);
         onNowHasData(newValue);
     } else {
-        LOG_DEBUG("(Client not yet interested in packets)");
+        LOG_DEBUG("Client not yet interested in packets (state=%d)", state);
     }
 
     return timeout ? -1 : 0; // If we timed out, MeshService should stop iterating through observers as we just removed one
diff --git a/src/nimble/NimbleBluetooth.cpp b/src/nimble/NimbleBluetooth.cpp
index 030e00d6b..0b3009d39 100644
--- a/src/nimble/NimbleBluetooth.cpp
+++ b/src/nimble/NimbleBluetooth.cpp
@@ -31,11 +31,8 @@ class BluetoothPhoneAPI : public PhoneAPI, public concurrency::OSThread
     std::vector nimble_queue;
     std::mutex nimble_mutex;
     uint8_t queue_size = 0;
-    bool has_fromRadio = false;
     uint8_t fromRadioBytes[meshtastic_FromRadio_size] = {0};
     size_t numBytes = 0;
-    bool hasChecked = false;
-    bool phoneWants = false;
 
   protected:
     virtual int32_t runOnce() override
@@ -48,10 +45,7 @@ class BluetoothPhoneAPI : public PhoneAPI, public concurrency::OSThread
             LOG_DEBUG("Queue_size %u", queue_size);
             queue_size = 0;
         }
-        if (hasChecked == false && phoneWants == true) {
-            numBytes = getFromRadio(fromRadioBytes);
-            hasChecked = true;
-        }
+        // Note: phoneWants/hasChecked logic removed since onRead() handles getFromRadio() directly
 
         // the run is triggered via NimbleBluetoothToRadioCallback and NimbleBluetoothFromRadioCallback
         return INT32_MAX;
@@ -122,15 +116,8 @@ class NimbleBluetoothFromRadioCallback : public NimBLECharacteristicCallbacks
     {
         std::lock_guard guard(bluetoothPhoneAPI->nimble_mutex);
 
-        // If we don't have fresh data, trigger a refresh
-        if (!bluetoothPhoneAPI->hasChecked || bluetoothPhoneAPI->numBytes == 0) {
-            bluetoothPhoneAPI->phoneWants = true;
-            bluetoothPhoneAPI->setIntervalFromNow(0);
-
-            // Get fresh data immediately
-            bluetoothPhoneAPI->numBytes = bluetoothPhoneAPI->getFromRadio(bluetoothPhoneAPI->fromRadioBytes);
-            bluetoothPhoneAPI->hasChecked = true;
-        }
+        // Get fresh data immediately when client reads
+        bluetoothPhoneAPI->numBytes = bluetoothPhoneAPI->getFromRadio(bluetoothPhoneAPI->fromRadioBytes);
 
         // Set the characteristic value with whatever data we have
         pCharacteristic->setValue(bluetoothPhoneAPI->fromRadioBytes, bluetoothPhoneAPI->numBytes);
@@ -138,8 +125,6 @@ class NimbleBluetoothFromRadioCallback : public NimBLECharacteristicCallbacks
         if (bluetoothPhoneAPI->numBytes != 0) // if we did send something, queue it up right away to reload
             bluetoothPhoneAPI->setIntervalFromNow(0);
         bluetoothPhoneAPI->numBytes = 0;
-        bluetoothPhoneAPI->hasChecked = false;
-        bluetoothPhoneAPI->phoneWants = false;
     }
 };
 
@@ -248,8 +233,6 @@ class NimbleBluetoothServerCallback : public NimBLEServerCallbacks
         if (bluetoothPhoneAPI) {
             std::lock_guard guard(bluetoothPhoneAPI->nimble_mutex);
             bluetoothPhoneAPI->close();
-            bluetoothPhoneAPI->hasChecked = false;
-            bluetoothPhoneAPI->phoneWants = false;
             bluetoothPhoneAPI->numBytes = 0;
             bluetoothPhoneAPI->queue_size = 0;
         }

From e5a2ce54e74f6cfaec8e44c43764fa062b1b862b Mon Sep 17 00:00:00 2001
From: Jonathan Bennett 
Date: Wed, 8 Oct 2025 23:13:02 -0500
Subject: [PATCH 456/691] Attach an interrupt to EXT_PWR_DETECT if present, and
 force a screen redraw on a power change.

---
 src/Power.cpp           | 10 ++++++++++
 src/graphics/Screen.cpp |  3 +++
 2 files changed, 13 insertions(+)

diff --git a/src/Power.cpp b/src/Power.cpp
index 7de82b8d6..39d47610b 100644
--- a/src/Power.cpp
+++ b/src/Power.cpp
@@ -691,6 +691,16 @@ bool Power::setup()
 #ifdef NRF_APM
     found = true;
 #endif
+#ifdef EXT_PWR_DETECT
+    attachInterrupt(
+        EXT_PWR_DETECT,
+        []() {
+            power->setIntervalFromNow(0);
+            runASAP = true;
+            BaseType_t higherWake = 0;
+        },
+        CHANGE);
+#endif
 
     enabled = found;
     low_voltage_counter = 0;
diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp
index 4485ca7a3..767f97eed 100644
--- a/src/graphics/Screen.cpp
+++ b/src/graphics/Screen.cpp
@@ -1428,6 +1428,9 @@ int Screen::handleStatusUpdate(const meshtastic::Status *arg)
         }
         nodeDB->updateGUI = false;
         break;
+    case STATUS_TYPE_POWER:
+        forceDisplay(true);
+        break;
     }
 
     return 0;

From 05febc25e1d6ca0fd37752fa9be23d6d515730c1 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Sat, 11 Oct 2025 17:12:10 +1100
Subject: [PATCH 457/691] Update XPowersLib to v0.3.1 (#8303)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
 arch/esp32/esp32.ini   | 2 +-
 arch/esp32/esp32c6.ini | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/arch/esp32/esp32.ini b/arch/esp32/esp32.ini
index d2c933461..327444a60 100644
--- a/arch/esp32/esp32.ini
+++ b/arch/esp32/esp32.ini
@@ -56,7 +56,7 @@ lib_deps =
   # renovate: datasource=git-refs depName=libpax packageName=https://github.com/dbinfrago/libpax gitBranch=master
   https://github.com/dbinfrago/libpax/archive/3cdc0371c375676a97967547f4065607d4c53fd1.zip
   # renovate: datasource=github-tags depName=XPowersLib packageName=lewisxhe/XPowersLib
-  https://github.com/lewisxhe/XPowersLib/archive/v0.3.0.zip
+  https://github.com/lewisxhe/XPowersLib/archive/v0.3.1.zip
   # renovate: datasource=git-refs depName=meshtastic-ESP32_Codec2 packageName=https://github.com/meshtastic/ESP32_Codec2 gitBranch=master
   https://github.com/meshtastic/ESP32_Codec2/archive/633326c78ac251c059ab3a8c430fcdf25b41672f.zip
   # renovate: datasource=custom.pio depName=rweather/Crypto packageName=rweather/library/Crypto
diff --git a/arch/esp32/esp32c6.ini b/arch/esp32/esp32c6.ini
index 1afb9b547..7b06f4cd8 100644
--- a/arch/esp32/esp32c6.ini
+++ b/arch/esp32/esp32c6.ini
@@ -28,7 +28,7 @@ lib_deps =
   ${environmental_extra.lib_deps}
   ${radiolib_base.lib_deps}
   # renovate: datasource=custom.pio depName=XPowersLib packageName=lewisxhe/library/XPowersLib
-  lewisxhe/XPowersLib@0.3.0
+  lewisxhe/XPowersLib@0.3.1
   # renovate: datasource=git-refs depName=meshtastic-ESP32_Codec2 packageName=https://github.com/meshtastic/ESP32_Codec2 gitBranch=master
   https://github.com/meshtastic/ESP32_Codec2/archive/633326c78ac251c059ab3a8c430fcdf25b41672f.zip
   # renovate: datasource=custom.pio depName=rweather/Crypto packageName=rweather/library/Crypto

From 0bb1c1fe6f537cefff17c9a360be772d731b075b Mon Sep 17 00:00:00 2001
From: thebentern <9000580+thebentern@users.noreply.github.com>
Date: Sat, 11 Oct 2025 15:27:33 +0000
Subject: [PATCH 458/691] Automated version bumps

---
 bin/org.meshtastic.meshtasticd.metainfo.xml | 3 +++
 debian/changelog                            | 6 ++++++
 version.properties                          | 2 +-
 3 files changed, 10 insertions(+), 1 deletion(-)

diff --git a/bin/org.meshtastic.meshtasticd.metainfo.xml b/bin/org.meshtastic.meshtasticd.metainfo.xml
index 3505f1940..6fc5e8597 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.13
+    
     
       https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.12
     
diff --git a/debian/changelog b/debian/changelog
index 8bd053b25..e124f8929 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+meshtasticd (2.7.13.0) unstable; urgency=medium
+
+  * Version 2.7.13
+
+ -- GitHub Actions   Sat, 11 Oct 2025 15:27:28 +0000
+
 meshtasticd (2.7.12.0) unstable; urgency=medium
 
   [ Austin Lane ]
diff --git a/version.properties b/version.properties
index 5c84a8e65..f33f0f1cb 100644
--- a/version.properties
+++ b/version.properties
@@ -1,4 +1,4 @@
 [VERSION]  
 major = 2
 minor = 7
-build = 12
+build = 13

From 694b669eb7c4f24825ee7c058875505c5f31ff68 Mon Sep 17 00:00:00 2001
From: Ben Meadors 
Date: Sat, 11 Oct 2025 10:30:33 -0500
Subject: [PATCH 459/691] Double the number of bluetooth bonds NimBLE will
 store (from 3 to 6) (#8296)

---
 arch/esp32/esp32.ini | 1 +
 1 file changed, 1 insertion(+)

diff --git a/arch/esp32/esp32.ini b/arch/esp32/esp32.ini
index 327444a60..810c9780e 100644
--- a/arch/esp32/esp32.ini
+++ b/arch/esp32/esp32.ini
@@ -31,6 +31,7 @@ build_flags =
   -DMYNEWT_VAL_BLE_HS_LOG_LVL=LOG_LEVEL_CRITICAL
   -DAXP_DEBUG_PORT=Serial
   -DCONFIG_BT_NIMBLE_ENABLED
+  -DCONFIG_BT_NIMBLE_MAX_BONDS=6 # default is 3
   -DCONFIG_NIMBLE_CPP_LOG_LEVEL=2
   -DCONFIG_BT_NIMBLE_MAX_CCCDS=20
   -DCONFIG_BT_NIMBLE_HOST_TASK_STACK_SIZE=8192

From cafb007ec466c283c671fec92e1915fde109c80c Mon Sep 17 00:00:00 2001
From: Austin 
Date: Sat, 11 Oct 2025 11:30:47 -0400
Subject: [PATCH 460/691] mDNS: Advertise pio_env (for OTA scripts) (#8298)

---
 src/mesh/wifi/WiFiAPClient.cpp | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/src/mesh/wifi/WiFiAPClient.cpp b/src/mesh/wifi/WiFiAPClient.cpp
index 7d210dd33..18f67706a 100644
--- a/src/mesh/wifi/WiFiAPClient.cpp
+++ b/src/mesh/wifi/WiFiAPClient.cpp
@@ -95,10 +95,12 @@ static void onNetworkConnected()
 #ifdef ARCH_ESP32
             MDNS.addServiceTxt("meshtastic", "tcp", "shortname", String(owner.short_name));
             MDNS.addServiceTxt("meshtastic", "tcp", "id", String(nodeDB->getNodeId().c_str()));
+            MDNS.addServiceTxt("meshtastic", "tcp", "pio_env", optstr(APP_ENV));
             // ESP32 prints obtained IP address in WiFiEvent
 #elif defined(ARCH_RP2040)
             MDNS.addServiceTxt("meshtastic", "shortname", owner.short_name);
             MDNS.addServiceTxt("meshtastic", "id", nodeDB->getNodeId().c_str());
+            MDNS.addServiceTxt("meshtastic", "pio_env", optstr(APP_ENV));
             LOG_INFO("Obtained IP address: %s", WiFi.localIP().toString().c_str());
 #endif
         }

From fca5343460f0253a08c80e6e972b5046e62148c0 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Wed, 8 Oct 2025 17:33:39 +1100
Subject: [PATCH 461/691] Update python Docker tag to v3.14 (#8255)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
 Dockerfile        | 2 +-
 alpine.Dockerfile | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/Dockerfile b/Dockerfile
index b1e151ac7..111dd69fc 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -3,7 +3,7 @@
 # trunk-ignore-all(hadolint/DL3008): Do not pin apt package versions
 # trunk-ignore-all(hadolint/DL3013): Do not pin pip package versions
 
-FROM python:3.13-slim-trixie AS builder
+FROM python:3.14-slim-trixie AS builder
 ARG PIO_ENV=native
 ENV DEBIAN_FRONTEND=noninteractive
 ENV TZ=Etc/UTC
diff --git a/alpine.Dockerfile b/alpine.Dockerfile
index 670736241..0469874e4 100644
--- a/alpine.Dockerfile
+++ b/alpine.Dockerfile
@@ -3,7 +3,7 @@
 # trunk-ignore-all(hadolint/DL3018): Do not pin apk package versions
 # trunk-ignore-all(hadolint/DL3013): Do not pin pip package versions
 
-FROM python:3.13-alpine3.22 AS builder
+FROM python:3.14-alpine3.22 AS builder
 ARG PIO_ENV=native
 ENV PIP_ROOT_USER_ACTION=ignore
 

From 64bfe73c06d6da0c3f1c4976f063f0190c07ad43 Mon Sep 17 00:00:00 2001
From: Andrew Yong 
Date: Thu, 9 Oct 2025 00:33:50 +0800
Subject: [PATCH 462/691] fix: Move `#include "variant.h"` to top of file
 (fixes #8276) (#8278)

* Force coverage tests to run in simulation mode

* Revert "Force coverage tests to run in simulation mode"

This reverts commit e4ec719e6f888c47ee3a1cc78afd2b96d5f196d1.

* fix: Move `#include "variant.h"` to top of file (fixes #8276)

The original line being further down the file causes any #ifdef/defined() checks for definitions in variant.h to silently skip.

This was noticed when `USE_GC1109_PA` in Heltec v4 and Heltec Wireless Tracker failed to correct program TX_GAIN_LORA, but will also affect any variant.h-dependent configurations in this file, if they would have been defined above where the `#include` previously was.

---------

Co-authored-by: Austin Lane 
Co-authored-by: Ben Meadors 
---
 src/configuration.h | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/src/configuration.h b/src/configuration.h
index e67f0b8e5..c6c8d673c 100644
--- a/src/configuration.h
+++ b/src/configuration.h
@@ -33,6 +33,9 @@ along with this program.  If not, see .
 #include "pcf8563.h"
 #endif
 
+/* Offer chance for variant-specific defines */
+#include "variant.h"
+
 // -----------------------------------------------------------------------------
 // Version
 // -----------------------------------------------------------------------------
@@ -260,9 +263,6 @@ along with this program.  If not, see .
 // convert 24-bit color to 16-bit (56K)
 #define COLOR565(r, g, b) (((r & 0xF8) << 8) | ((g & 0xFC) << 3) | ((b & 0xF8) >> 3))
 
-/* Step #1: offer chance for variant-specific defines */
-#include "variant.h"
-
 #if defined(VEXT_ENABLE) && !defined(VEXT_ON_VALUE)
 // Older variant.h files might not be defining this value, so stay with the old default
 #define VEXT_ON_VALUE LOW

From 91d928d4c5ad4daa92da86067f57e1bf096fd220 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Wed, 8 Oct 2025 11:34:11 -0500
Subject: [PATCH 463/691] Update meshtastic/device-ui digest to 6d8cc22 (#8275)

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 2e101a8a2..7121f00b7 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -120,7 +120,7 @@ lib_deps =
 [device-ui_base]
 lib_deps =
 	# renovate: datasource=git-refs depName=meshtastic/device-ui packageName=https://github.com/meshtastic/device-ui gitBranch=master
-	https://github.com/meshtastic/device-ui/archive/e564d78ae1a7e9a225aaf4a73b1cb84c549f510f.zip
+	https://github.com/meshtastic/device-ui/archive/6d8cc228298a1ecd9913aed757187e9527c1facc.zip
 
 ; Common libs for environmental measurements in telemetry module
 [environmental_base]

From f99747180e2fe3ea68d4826ce81219c30286fe9a Mon Sep 17 00:00:00 2001
From: Ben Meadors 
Date: Wed, 8 Oct 2025 14:16:57 -0500
Subject: [PATCH 464/691] NimBLE speedup (#8281)

* Remove status polling code in NimBLE

* Goober

---------

Co-authored-by: Jonathan Bennett 
---
 src/mesh/PhoneAPI.cpp          |  2 +-
 src/nimble/NimbleBluetooth.cpp | 19 ++++++++++++-------
 2 files changed, 13 insertions(+), 8 deletions(-)

diff --git a/src/mesh/PhoneAPI.cpp b/src/mesh/PhoneAPI.cpp
index 210e0ba06..d2ed2d4cb 100644
--- a/src/mesh/PhoneAPI.cpp
+++ b/src/mesh/PhoneAPI.cpp
@@ -431,7 +431,6 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
         break;
 
     case STATE_SEND_OTHER_NODEINFOS: {
-        LOG_DEBUG("Send known nodes");
         if (nodeInfoForPhone.num != 0) {
             // Just in case we stored a different user.id in the past, but should never happen going forward
             sprintf(nodeInfoForPhone.user.id, "!%08x", nodeInfoForPhone.num);
@@ -592,6 +591,7 @@ bool PhoneAPI::available()
                 nodeInfoForPhone.snr = isUs ? 0 : nodeInfoForPhone.snr;
                 nodeInfoForPhone.via_mqtt = isUs ? false : nodeInfoForPhone.via_mqtt;
                 nodeInfoForPhone.is_favorite = nodeInfoForPhone.is_favorite || isUs; // Our node is always a favorite
+
                 onNowHasData(0);
             }
         }
diff --git a/src/nimble/NimbleBluetooth.cpp b/src/nimble/NimbleBluetooth.cpp
index 5524765f3..030e00d6b 100644
--- a/src/nimble/NimbleBluetooth.cpp
+++ b/src/nimble/NimbleBluetooth.cpp
@@ -120,14 +120,19 @@ class NimbleBluetoothFromRadioCallback : public NimBLECharacteristicCallbacks
     virtual void onRead(NimBLECharacteristic *pCharacteristic)
 #endif
     {
-        int tries = 0;
-        bluetoothPhoneAPI->phoneWants = true;
-        while (!bluetoothPhoneAPI->hasChecked && tries < 100) {
-            bluetoothPhoneAPI->setIntervalFromNow(0);
-            delay(20);
-            tries++;
-        }
         std::lock_guard guard(bluetoothPhoneAPI->nimble_mutex);
+
+        // If we don't have fresh data, trigger a refresh
+        if (!bluetoothPhoneAPI->hasChecked || bluetoothPhoneAPI->numBytes == 0) {
+            bluetoothPhoneAPI->phoneWants = true;
+            bluetoothPhoneAPI->setIntervalFromNow(0);
+
+            // Get fresh data immediately
+            bluetoothPhoneAPI->numBytes = bluetoothPhoneAPI->getFromRadio(bluetoothPhoneAPI->fromRadioBytes);
+            bluetoothPhoneAPI->hasChecked = true;
+        }
+
+        // Set the characteristic value with whatever data we have
         pCharacteristic->setValue(bluetoothPhoneAPI->fromRadioBytes, bluetoothPhoneAPI->numBytes);
 
         if (bluetoothPhoneAPI->numBytes != 0) // if we did send something, queue it up right away to reload

From 30d6962e791a6aaf7040d328ad860d1ab73fb3d7 Mon Sep 17 00:00:00 2001
From: Tom Fifield 
Date: Thu, 9 Oct 2025 08:03:26 +1100
Subject: [PATCH 465/691] Fix Station G2 Lora Power Settings (#8273)

* Force coverage tests to run in simulation mode

* Revert "Force coverage tests to run in simulation mode"

This reverts commit e4ec719e6f888c47ee3a1cc78afd2b96d5f196d1.

* Fix Station G2 Lora Power Settings

In #8107 we introduced the ability to specify gain values for
non-linear power amplifiers.

This patch adds appropriate values for the Station G2, based on
the table at https://wiki.uniteng.com/en/meshtastic/station-g2#summary-for-lora-power-amplifier-conduction-test

Fixes https://github.com/meshtastic/firmware/issues/7239

---------

Co-authored-by: Austin Lane 
Co-authored-by: Ben Meadors 
---
 src/configuration.h | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/src/configuration.h b/src/configuration.h
index c6c8d673c..b6b1c1e5e 100644
--- a/src/configuration.h
+++ b/src/configuration.h
@@ -126,6 +126,11 @@ along with this program.  If not, see .
 #define TX_GAIN_LORA 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 10, 10, 9, 9, 8, 7
 #endif
 
+#ifdef STATION_G2
+#define NUM_PA_POINTS 19
+#define TX_GAIN_LORA 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 19, 19, 18, 18
+#endif
+
 // Default system gain to 0 if not defined
 #ifndef TX_GAIN_LORA
 #define TX_GAIN_LORA 0

From eee80ce6364fee804e6f92ceaea41f69a5d206b5 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Thu, 9 Oct 2025 09:32:41 +1100
Subject: [PATCH 466/691] chore(deps): update github/codeql-action action to v4
 (#8250)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
 .github/workflows/sec_sast_semgrep_cron.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.github/workflows/sec_sast_semgrep_cron.yml b/.github/workflows/sec_sast_semgrep_cron.yml
index 96c993cba..2059fde2c 100644
--- a/.github/workflows/sec_sast_semgrep_cron.yml
+++ b/.github/workflows/sec_sast_semgrep_cron.yml
@@ -41,7 +41,7 @@ jobs:
 
       # step 4
       - name: publish code scanning alerts
-        uses: github/codeql-action/upload-sarif@v3
+        uses: github/codeql-action/upload-sarif@v4
         with:
           sarif_file: report.sarif
           category: semgrep

From 73cadce58125141d3d7c7027d3a380416935a5ed Mon Sep 17 00:00:00 2001
From: Ben Meadors 
Date: Thu, 9 Oct 2025 12:44:00 -0500
Subject: [PATCH 467/691] Fix BLE stateful issues (#8287)

---
 src/mesh/PhoneAPI.cpp          |  2 +-
 src/nimble/NimbleBluetooth.cpp | 23 +++--------------------
 2 files changed, 4 insertions(+), 21 deletions(-)

diff --git a/src/mesh/PhoneAPI.cpp b/src/mesh/PhoneAPI.cpp
index d2ed2d4cb..e8dc9843c 100644
--- a/src/mesh/PhoneAPI.cpp
+++ b/src/mesh/PhoneAPI.cpp
@@ -734,7 +734,7 @@ int PhoneAPI::onNotify(uint32_t newValue)
         LOG_INFO("Tell client we have new packets %u", newValue);
         onNowHasData(newValue);
     } else {
-        LOG_DEBUG("(Client not yet interested in packets)");
+        LOG_DEBUG("Client not yet interested in packets (state=%d)", state);
     }
 
     return timeout ? -1 : 0; // If we timed out, MeshService should stop iterating through observers as we just removed one
diff --git a/src/nimble/NimbleBluetooth.cpp b/src/nimble/NimbleBluetooth.cpp
index 030e00d6b..0b3009d39 100644
--- a/src/nimble/NimbleBluetooth.cpp
+++ b/src/nimble/NimbleBluetooth.cpp
@@ -31,11 +31,8 @@ class BluetoothPhoneAPI : public PhoneAPI, public concurrency::OSThread
     std::vector nimble_queue;
     std::mutex nimble_mutex;
     uint8_t queue_size = 0;
-    bool has_fromRadio = false;
     uint8_t fromRadioBytes[meshtastic_FromRadio_size] = {0};
     size_t numBytes = 0;
-    bool hasChecked = false;
-    bool phoneWants = false;
 
   protected:
     virtual int32_t runOnce() override
@@ -48,10 +45,7 @@ class BluetoothPhoneAPI : public PhoneAPI, public concurrency::OSThread
             LOG_DEBUG("Queue_size %u", queue_size);
             queue_size = 0;
         }
-        if (hasChecked == false && phoneWants == true) {
-            numBytes = getFromRadio(fromRadioBytes);
-            hasChecked = true;
-        }
+        // Note: phoneWants/hasChecked logic removed since onRead() handles getFromRadio() directly
 
         // the run is triggered via NimbleBluetoothToRadioCallback and NimbleBluetoothFromRadioCallback
         return INT32_MAX;
@@ -122,15 +116,8 @@ class NimbleBluetoothFromRadioCallback : public NimBLECharacteristicCallbacks
     {
         std::lock_guard guard(bluetoothPhoneAPI->nimble_mutex);
 
-        // If we don't have fresh data, trigger a refresh
-        if (!bluetoothPhoneAPI->hasChecked || bluetoothPhoneAPI->numBytes == 0) {
-            bluetoothPhoneAPI->phoneWants = true;
-            bluetoothPhoneAPI->setIntervalFromNow(0);
-
-            // Get fresh data immediately
-            bluetoothPhoneAPI->numBytes = bluetoothPhoneAPI->getFromRadio(bluetoothPhoneAPI->fromRadioBytes);
-            bluetoothPhoneAPI->hasChecked = true;
-        }
+        // Get fresh data immediately when client reads
+        bluetoothPhoneAPI->numBytes = bluetoothPhoneAPI->getFromRadio(bluetoothPhoneAPI->fromRadioBytes);
 
         // Set the characteristic value with whatever data we have
         pCharacteristic->setValue(bluetoothPhoneAPI->fromRadioBytes, bluetoothPhoneAPI->numBytes);
@@ -138,8 +125,6 @@ class NimbleBluetoothFromRadioCallback : public NimBLECharacteristicCallbacks
         if (bluetoothPhoneAPI->numBytes != 0) // if we did send something, queue it up right away to reload
             bluetoothPhoneAPI->setIntervalFromNow(0);
         bluetoothPhoneAPI->numBytes = 0;
-        bluetoothPhoneAPI->hasChecked = false;
-        bluetoothPhoneAPI->phoneWants = false;
     }
 };
 
@@ -248,8 +233,6 @@ class NimbleBluetoothServerCallback : public NimBLEServerCallbacks
         if (bluetoothPhoneAPI) {
             std::lock_guard guard(bluetoothPhoneAPI->nimble_mutex);
             bluetoothPhoneAPI->close();
-            bluetoothPhoneAPI->hasChecked = false;
-            bluetoothPhoneAPI->phoneWants = false;
             bluetoothPhoneAPI->numBytes = 0;
             bluetoothPhoneAPI->queue_size = 0;
         }

From 8bf32dc042fc3e6f7a5d9b988c12492da9930305 Mon Sep 17 00:00:00 2001
From: Jonathan Bennett 
Date: Wed, 8 Oct 2025 23:13:02 -0500
Subject: [PATCH 468/691] Attach an interrupt to EXT_PWR_DETECT if present, and
 force a screen redraw on a power change.

---
 src/Power.cpp           | 10 ++++++++++
 src/graphics/Screen.cpp |  3 +++
 2 files changed, 13 insertions(+)

diff --git a/src/Power.cpp b/src/Power.cpp
index 7de82b8d6..39d47610b 100644
--- a/src/Power.cpp
+++ b/src/Power.cpp
@@ -691,6 +691,16 @@ bool Power::setup()
 #ifdef NRF_APM
     found = true;
 #endif
+#ifdef EXT_PWR_DETECT
+    attachInterrupt(
+        EXT_PWR_DETECT,
+        []() {
+            power->setIntervalFromNow(0);
+            runASAP = true;
+            BaseType_t higherWake = 0;
+        },
+        CHANGE);
+#endif
 
     enabled = found;
     low_voltage_counter = 0;
diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp
index 4485ca7a3..767f97eed 100644
--- a/src/graphics/Screen.cpp
+++ b/src/graphics/Screen.cpp
@@ -1428,6 +1428,9 @@ int Screen::handleStatusUpdate(const meshtastic::Status *arg)
         }
         nodeDB->updateGUI = false;
         break;
+    case STATUS_TYPE_POWER:
+        forceDisplay(true);
+        break;
     }
 
     return 0;

From 29458cd8c4f7699a43e6b97315ff1334c2376d1f Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Sat, 11 Oct 2025 17:12:10 +1100
Subject: [PATCH 469/691] Update XPowersLib to v0.3.1 (#8303)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
 arch/esp32/esp32.ini   | 2 +-
 arch/esp32/esp32c6.ini | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/arch/esp32/esp32.ini b/arch/esp32/esp32.ini
index d2c933461..327444a60 100644
--- a/arch/esp32/esp32.ini
+++ b/arch/esp32/esp32.ini
@@ -56,7 +56,7 @@ lib_deps =
   # renovate: datasource=git-refs depName=libpax packageName=https://github.com/dbinfrago/libpax gitBranch=master
   https://github.com/dbinfrago/libpax/archive/3cdc0371c375676a97967547f4065607d4c53fd1.zip
   # renovate: datasource=github-tags depName=XPowersLib packageName=lewisxhe/XPowersLib
-  https://github.com/lewisxhe/XPowersLib/archive/v0.3.0.zip
+  https://github.com/lewisxhe/XPowersLib/archive/v0.3.1.zip
   # renovate: datasource=git-refs depName=meshtastic-ESP32_Codec2 packageName=https://github.com/meshtastic/ESP32_Codec2 gitBranch=master
   https://github.com/meshtastic/ESP32_Codec2/archive/633326c78ac251c059ab3a8c430fcdf25b41672f.zip
   # renovate: datasource=custom.pio depName=rweather/Crypto packageName=rweather/library/Crypto
diff --git a/arch/esp32/esp32c6.ini b/arch/esp32/esp32c6.ini
index 1afb9b547..7b06f4cd8 100644
--- a/arch/esp32/esp32c6.ini
+++ b/arch/esp32/esp32c6.ini
@@ -28,7 +28,7 @@ lib_deps =
   ${environmental_extra.lib_deps}
   ${radiolib_base.lib_deps}
   # renovate: datasource=custom.pio depName=XPowersLib packageName=lewisxhe/library/XPowersLib
-  lewisxhe/XPowersLib@0.3.0
+  lewisxhe/XPowersLib@0.3.1
   # renovate: datasource=git-refs depName=meshtastic-ESP32_Codec2 packageName=https://github.com/meshtastic/ESP32_Codec2 gitBranch=master
   https://github.com/meshtastic/ESP32_Codec2/archive/633326c78ac251c059ab3a8c430fcdf25b41672f.zip
   # renovate: datasource=custom.pio depName=rweather/Crypto packageName=rweather/library/Crypto

From 554112ceb5be8994ff785aa16fcede0cd133c2a1 Mon Sep 17 00:00:00 2001
From: thebentern <9000580+thebentern@users.noreply.github.com>
Date: Sat, 11 Oct 2025 15:27:33 +0000
Subject: [PATCH 470/691] Automated version bumps

---
 bin/org.meshtastic.meshtasticd.metainfo.xml | 3 +++
 debian/changelog                            | 6 ++++++
 version.properties                          | 2 +-
 3 files changed, 10 insertions(+), 1 deletion(-)

diff --git a/bin/org.meshtastic.meshtasticd.metainfo.xml b/bin/org.meshtastic.meshtasticd.metainfo.xml
index 3505f1940..6fc5e8597 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.13
+    
     
       https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.12
     
diff --git a/debian/changelog b/debian/changelog
index 8bd053b25..e124f8929 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+meshtasticd (2.7.13.0) unstable; urgency=medium
+
+  * Version 2.7.13
+
+ -- GitHub Actions   Sat, 11 Oct 2025 15:27:28 +0000
+
 meshtasticd (2.7.12.0) unstable; urgency=medium
 
   [ Austin Lane ]
diff --git a/version.properties b/version.properties
index 5c84a8e65..f33f0f1cb 100644
--- a/version.properties
+++ b/version.properties
@@ -1,4 +1,4 @@
 [VERSION]  
 major = 2
 minor = 7
-build = 12
+build = 13

From 9056915e7b62c505678a5c7644ed97acd064f093 Mon Sep 17 00:00:00 2001
From: Ben Meadors 
Date: Sat, 11 Oct 2025 10:30:33 -0500
Subject: [PATCH 471/691] Double the number of bluetooth bonds NimBLE will
 store (from 3 to 6) (#8296)

---
 arch/esp32/esp32.ini | 1 +
 1 file changed, 1 insertion(+)

diff --git a/arch/esp32/esp32.ini b/arch/esp32/esp32.ini
index 327444a60..810c9780e 100644
--- a/arch/esp32/esp32.ini
+++ b/arch/esp32/esp32.ini
@@ -31,6 +31,7 @@ build_flags =
   -DMYNEWT_VAL_BLE_HS_LOG_LVL=LOG_LEVEL_CRITICAL
   -DAXP_DEBUG_PORT=Serial
   -DCONFIG_BT_NIMBLE_ENABLED
+  -DCONFIG_BT_NIMBLE_MAX_BONDS=6 # default is 3
   -DCONFIG_NIMBLE_CPP_LOG_LEVEL=2
   -DCONFIG_BT_NIMBLE_MAX_CCCDS=20
   -DCONFIG_BT_NIMBLE_HOST_TASK_STACK_SIZE=8192

From 981d058e9f427a851a363222349f1c0a60fa0c96 Mon Sep 17 00:00:00 2001
From: Austin 
Date: Sat, 11 Oct 2025 16:56:59 -0400
Subject: [PATCH 472/691] Actions: CI docker with a fancy matrix (#8253)

---
 .github/workflows/build_one_arch.yml   | 56 -------------------
 .github/workflows/build_one_target.yml | 56 -------------------
 .github/workflows/main_matrix.yml      | 76 ++++++--------------------
 .github/workflows/merge_queue.yml      | 64 ++++++----------------
 4 files changed, 36 insertions(+), 216 deletions(-)

diff --git a/.github/workflows/build_one_arch.yml b/.github/workflows/build_one_arch.yml
index 6d9134941..9c57f8b7d 100644
--- a/.github/workflows/build_one_arch.yml
+++ b/.github/workflows/build_one_arch.yml
@@ -88,62 +88,6 @@ jobs:
     if: ${{ !contains(github.ref_name, 'event/') && github.event_name != 'workflow_dispatch' ||  !contains(github.ref_name, 'event/') && inputs.arch == 'native' }}
     uses: ./.github/workflows/test_native.yml
 
-  docker-deb-amd64:
-    if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'native' }}
-    uses: ./.github/workflows/docker_build.yml
-    with:
-      distro: debian
-      platform: linux/amd64
-      runs-on: ubuntu-24.04
-      push: false
-
-  docker-deb-amd64-tft:
-    if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'native' }}
-    uses: ./.github/workflows/docker_build.yml
-    with:
-      distro: debian
-      platform: linux/amd64
-      runs-on: ubuntu-24.04
-      push: false
-      pio_env: native-tft
-
-  docker-alp-amd64:
-    if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'native' }}
-    uses: ./.github/workflows/docker_build.yml
-    with:
-      distro: alpine
-      platform: linux/amd64
-      runs-on: ubuntu-24.04
-      push: false
-
-  docker-alp-amd64-tft:
-    if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'native' }}
-    uses: ./.github/workflows/docker_build.yml
-    with:
-      distro: alpine
-      platform: linux/amd64
-      runs-on: ubuntu-24.04
-      push: false
-      pio_env: native-tft
-
-  docker-deb-arm64:
-    if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'native' }}
-    uses: ./.github/workflows/docker_build.yml
-    with:
-      distro: debian
-      platform: linux/arm64
-      runs-on: ubuntu-24.04-arm
-      push: false
-
-  docker-deb-armv7:
-    if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'native' }}
-    uses: ./.github/workflows/docker_build.yml
-    with:
-      distro: debian
-      platform: linux/arm/v7
-      runs-on: ubuntu-24.04-arm
-      push: false
-
   gather-artifacts:
     permissions:
       contents: write
diff --git a/.github/workflows/build_one_target.yml b/.github/workflows/build_one_target.yml
index ba1d5080f..15b3fdba9 100644
--- a/.github/workflows/build_one_target.yml
+++ b/.github/workflows/build_one_target.yml
@@ -106,62 +106,6 @@ jobs:
     if: ${{ !contains(github.ref_name, 'event/') && github.event_name != 'workflow_dispatch' ||  !contains(github.ref_name, 'event/') && inputs.arch == 'native' && inputs.target != '' }}
     uses: ./.github/workflows/test_native.yml
 
-  docker-deb-amd64:
-    if: ${{ inputs.target != '' && inputs.arch ==  'native' }}
-    uses: ./.github/workflows/docker_build.yml
-    with:
-      distro: debian
-      platform: linux/amd64
-      runs-on: ubuntu-24.04
-      push: false
-
-  docker-deb-amd64-tft:
-    if: ${{ inputs.target != '' && inputs.arch ==  'native' }}
-    uses: ./.github/workflows/docker_build.yml
-    with:
-      distro: debian
-      platform: linux/amd64
-      runs-on: ubuntu-24.04
-      push: false
-      pio_env: native-tft
-
-  docker-alp-amd64:
-    if: ${{ inputs.target != '' && inputs.arch ==  'native' }}
-    uses: ./.github/workflows/docker_build.yml
-    with:
-      distro: alpine
-      platform: linux/amd64
-      runs-on: ubuntu-24.04
-      push: false
-
-  docker-alp-amd64-tft:
-    if: ${{ inputs.target != '' && inputs.arch ==  'native' }}
-    uses: ./.github/workflows/docker_build.yml
-    with:
-      distro: alpine
-      platform: linux/amd64
-      runs-on: ubuntu-24.04
-      push: false
-      pio_env: native-tft
-
-  docker-deb-arm64:
-    if: ${{ inputs.target != '' && inputs.arch ==  'native' }}
-    uses: ./.github/workflows/docker_build.yml
-    with:
-      distro: debian
-      platform: linux/arm64
-      runs-on: ubuntu-24.04-arm
-      push: false
-
-  docker-deb-armv7:
-    if: ${{ inputs.target != '' && inputs.arch ==  'native' }}
-    uses: ./.github/workflows/docker_build.yml
-    with:
-      distro: debian
-      platform: linux/arm/v7
-      runs-on: ubuntu-24.04-arm
-      push: false
-
   gather-artifacts:
     permissions:
       contents: write
diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml
index 812990eca..02a4c23b8 100644
--- a/.github/workflows/main_matrix.yml
+++ b/.github/workflows/main_matrix.yml
@@ -27,7 +27,6 @@ on:
 
 jobs:
   setup:
-    if: github.repository == 'meshtastic/firmware'
     strategy:
       fail-fast: true
       matrix:
@@ -42,10 +41,6 @@ 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: |
@@ -62,7 +57,6 @@ jobs:
       check: ${{ steps.jsonStep.outputs.check }}
 
   version:
-    if: github.repository == 'meshtastic/firmware'
     runs-on: ubuntu-latest
     steps:
       - uses: actions/checkout@v5
@@ -125,60 +119,26 @@ jobs:
     if: ${{ !contains(github.ref_name, 'event/') && github.repository == 'meshtastic/firmware' }}
     uses: ./.github/workflows/test_native.yml
 
-  docker-deb-amd64:
-    if: github.repository == 'meshtastic/firmware'
+  docker:
+    strategy:
+      fail-fast: false
+      matrix:
+        distro: [debian, alpine]
+        platform: [linux/amd64, linux/arm64, linux/arm/v7]
+        pio_env: [native, native-tft]
+        exclude:
+          - distro: alpine
+            platform: linux/arm/v7
+          - pio_env: native-tft
+            platform: linux/arm64
+          - pio_env: native-tft
+            platform: linux/arm/v7
     uses: ./.github/workflows/docker_build.yml
     with:
-      distro: debian
-      platform: linux/amd64
-      runs-on: ubuntu-24.04
-      push: false
-
-  docker-deb-amd64-tft:
-    if: github.repository == 'meshtastic/firmware'
-    uses: ./.github/workflows/docker_build.yml
-    with:
-      distro: debian
-      platform: linux/amd64
-      runs-on: ubuntu-24.04
-      push: false
-      pio_env: native-tft
-
-  docker-alp-amd64:
-    if: github.repository == 'meshtastic/firmware'
-    uses: ./.github/workflows/docker_build.yml
-    with:
-      distro: alpine
-      platform: linux/amd64
-      runs-on: ubuntu-24.04
-      push: false
-
-  docker-alp-amd64-tft:
-    if: github.repository == 'meshtastic/firmware'
-    uses: ./.github/workflows/docker_build.yml
-    with:
-      distro: alpine
-      platform: linux/amd64
-      runs-on: ubuntu-24.04
-      push: false
-      pio_env: native-tft
-
-  docker-deb-arm64:
-    if: github.repository == 'meshtastic/firmware'
-    uses: ./.github/workflows/docker_build.yml
-    with:
-      distro: debian
-      platform: linux/arm64
-      runs-on: ubuntu-24.04-arm
-      push: false
-
-  docker-deb-armv7:
-    if: github.repository == 'meshtastic/firmware'
-    uses: ./.github/workflows/docker_build.yml
-    with:
-      distro: debian
-      platform: linux/arm/v7
-      runs-on: ubuntu-24.04-arm
+      distro: ${{ matrix.distro }}
+      platform: ${{ matrix.platform }}
+      runs-on: ${{ contains(matrix.platform, 'arm') && 'ubuntu-24.04-arm' || 'ubuntu-24.04' }}
+      pio_env: ${{ matrix.pio_env }}
       push: false
 
   gather-artifacts:
diff --git a/.github/workflows/merge_queue.yml b/.github/workflows/merge_queue.yml
index 79e8b0803..e8c3d3450 100644
--- a/.github/workflows/merge_queue.yml
+++ b/.github/workflows/merge_queue.yml
@@ -99,54 +99,26 @@ jobs:
     if: ${{ !contains(github.ref_name, 'event/') }}
     uses: ./.github/workflows/test_native.yml
 
-  docker-deb-amd64:
+  docker:
+    strategy:
+      fail-fast: false
+      matrix:
+        distro: [debian, alpine]
+        platform: [linux/amd64, linux/arm64, linux/arm/v7]
+        pio_env: [native, native-tft]
+        exclude:
+          - distro: alpine
+            platform: linux/arm/v7
+          - pio_env: native-tft
+            platform: linux/arm64
+          - pio_env: native-tft
+            platform: linux/arm/v7
     uses: ./.github/workflows/docker_build.yml
     with:
-      distro: debian
-      platform: linux/amd64
-      runs-on: ubuntu-24.04
-      push: false
-
-  docker-deb-amd64-tft:
-    uses: ./.github/workflows/docker_build.yml
-    with:
-      distro: debian
-      platform: linux/amd64
-      runs-on: ubuntu-24.04
-      push: false
-      pio_env: native-tft
-
-  docker-alp-amd64:
-    uses: ./.github/workflows/docker_build.yml
-    with:
-      distro: alpine
-      platform: linux/amd64
-      runs-on: ubuntu-24.04
-      push: false
-
-  docker-alp-amd64-tft:
-    uses: ./.github/workflows/docker_build.yml
-    with:
-      distro: alpine
-      platform: linux/amd64
-      runs-on: ubuntu-24.04
-      push: false
-      pio_env: native-tft
-
-  docker-deb-arm64:
-    uses: ./.github/workflows/docker_build.yml
-    with:
-      distro: debian
-      platform: linux/arm64
-      runs-on: ubuntu-24.04-arm
-      push: false
-
-  docker-deb-armv7:
-    uses: ./.github/workflows/docker_build.yml
-    with:
-      distro: debian
-      platform: linux/arm/v7
-      runs-on: ubuntu-24.04-arm
+      distro: ${{ matrix.distro }}
+      platform: ${{ matrix.platform }}
+      runs-on: ${{ contains(matrix.platform, 'arm') && 'ubuntu-24.04-arm' || 'ubuntu-24.04' }}
+      pio_env: ${{ matrix.pio_env }}
       push: false
 
   gather-artifacts:

From 464663b496708b9845b878d38e122272bc2c1ae6 Mon Sep 17 00:00:00 2001
From: Jonathan Bennett 
Date: Sun, 12 Oct 2025 05:33:34 -0500
Subject: [PATCH 473/691] GPS_POWER_TOGGLE no longer has a function, so purge
 (#8312)

---
 variants/esp32/diy/hydra/variant.h                            | 1 -
 variants/esp32/heltec_v2.1/platformio.ini                     | 1 -
 variants/esp32/heltec_wsl_v2.1/platformio.ini                 | 1 -
 variants/esp32/tbeam/platformio.ini                           | 1 -
 variants/esp32/tlora_v2_1_16/platformio.ini                   | 1 -
 variants/esp32/tlora_v2_1_16_tcxo/platformio.ini              | 1 -
 variants/esp32/tlora_v3_3_0_tcxo/platformio.ini               | 1 -
 variants/esp32s3/CDEBYTE_EoRa-S3/platformio.ini               | 1 -
 variants/esp32s3/crowpanel-esp32s3-5-epaper/platformio.ini    | 3 ---
 variants/esp32s3/heltec_capsule_sensor_v3/platformio.ini      | 1 -
 variants/esp32s3/heltec_v3/platformio.ini                     | 1 -
 variants/esp32s3/heltec_v4/platformio.ini                     | 1 -
 variants/esp32s3/heltec_wireless_tracker/platformio.ini       | 1 -
 variants/esp32s3/heltec_wireless_tracker_V1_0/platformio.ini  | 1 -
 variants/esp32s3/heltec_wireless_tracker_v2/platformio.ini    | 1 -
 variants/esp32s3/heltec_wsl_v3/platformio.ini                 | 1 -
 variants/esp32s3/link32_s3_v1/platformio.ini                  | 1 -
 variants/esp32s3/t-deck-pro/platformio.ini                    | 1 -
 variants/esp32s3/t-deck/platformio.ini                        | 1 -
 variants/esp32s3/t-eth-elite/platformio.ini                   | 1 -
 variants/esp32s3/tlora-pager/platformio.ini                   | 1 -
 variants/esp32s3/tlora_t3s3_epaper/platformio.ini             | 1 -
 variants/esp32s3/tlora_t3s3_v1/platformio.ini                 | 1 -
 variants/esp32s3/tracksenger/platformio.ini                   | 3 ---
 variants/nrf52840/ELECROW-ThinkNode-M1/platformio.ini         | 1 -
 variants/nrf52840/ME25LS01-4Y10TD/platformio.ini              | 1 -
 variants/nrf52840/ME25LS01-4Y10TD_e-ink/platformio.ini        | 1 -
 variants/nrf52840/MS24SF1/platformio.ini                      | 1 -
 variants/nrf52840/diy/nrf52_promicro_diy_tcxo/variant.h       | 1 -
 variants/nrf52840/diy/nrf52_promicro_diy_xtal/variant.h       | 1 -
 variants/nrf52840/gat562_mesh_trial_tracker/platformio.ini    | 1 -
 variants/nrf52840/heltec_mesh_node_t114/platformio.ini        | 1 -
 variants/nrf52840/heltec_mesh_solar/platformio.ini            | 1 -
 variants/nrf52840/meshlink/platformio.ini                     | 1 -
 variants/nrf52840/meshlink_eink/platformio.ini                | 1 -
 variants/nrf52840/r1-neo/platformio.ini                       | 1 -
 variants/nrf52840/rak2560/platformio.ini                      | 1 -
 variants/nrf52840/rak4631/platformio.ini                      | 1 -
 variants/nrf52840/rak4631_eth_gw/platformio.ini               | 1 -
 variants/nrf52840/rak4631_nomadstar_meteor_pro/platformio.ini | 1 -
 variants/nrf52840/rak_wismeshtag/platformio.ini               | 1 -
 variants/nrf52840/rak_wismeshtap/platformio.ini               | 1 -
 variants/nrf52840/t-echo-lite/platformio.ini                  | 1 -
 variants/nrf52840/t-echo/platformio.ini                       | 1 -
 variants/nrf52840/tracker-t1000-e/platformio.ini              | 1 -
 variants/nrf52840/wio-sdk-wm1110/platformio.ini               | 1 -
 variants/nrf52840/wio-t1000-s/platformio.ini                  | 1 -
 variants/nrf52840/wio-tracker-wm1110/platformio.ini           | 1 -
 48 files changed, 52 deletions(-)

diff --git a/variants/esp32/diy/hydra/variant.h b/variants/esp32/diy/hydra/variant.h
index 0d64c1b5e..e5c10e26b 100644
--- a/variants/esp32/diy/hydra/variant.h
+++ b/variants/esp32/diy/hydra/variant.h
@@ -6,7 +6,6 @@
 #define GPS_TX_PIN 15
 #define GPS_RX_PIN 12
 #define PIN_GPS_EN 4
-#define GPS_POWER_TOGGLE // Moved definition from platformio.ini to here
 
 #define BUTTON_PIN 39 // The middle button GPIO on the T-Beam
 // Note: On the ESP32 base version, gpio34-39 are input-only, and do not have internal pull-ups.
diff --git a/variants/esp32/heltec_v2.1/platformio.ini b/variants/esp32/heltec_v2.1/platformio.ini
index 763f9764c..4dcd9e583 100644
--- a/variants/esp32/heltec_v2.1/platformio.ini
+++ b/variants/esp32/heltec_v2.1/platformio.ini
@@ -7,4 +7,3 @@ build_flags =
   ${esp32_base.build_flags}
   -D HELTEC_V2_1
   -I variants/esp32/heltec_v2.1
-  -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely.
diff --git a/variants/esp32/heltec_wsl_v2.1/platformio.ini b/variants/esp32/heltec_wsl_v2.1/platformio.ini
index eb44c88d2..6a77cf11b 100644
--- a/variants/esp32/heltec_wsl_v2.1/platformio.ini
+++ b/variants/esp32/heltec_wsl_v2.1/platformio.ini
@@ -6,4 +6,3 @@ build_flags =
   ${esp32_base.build_flags}
   -D PRIVATE_HW
   -I variants/esp32/heltec_wsl_v2.1
-  -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely.
diff --git a/variants/esp32/tbeam/platformio.ini b/variants/esp32/tbeam/platformio.ini
index ea17751c6..e53f22d30 100644
--- a/variants/esp32/tbeam/platformio.ini
+++ b/variants/esp32/tbeam/platformio.ini
@@ -10,7 +10,6 @@ build_flags =
   ${esp32_base.build_flags}
   -D TBEAM_V10
   -I variants/esp32/tbeam
-  -DGPS_POWER_TOGGLE ; comment this line to disable double press function on the user button to turn off gps entirely.
   -DBOARD_HAS_PSRAM
   -mfix-esp32-psram-cache-issue
 upload_speed = 921600
diff --git a/variants/esp32/tlora_v2_1_16/platformio.ini b/variants/esp32/tlora_v2_1_16/platformio.ini
index bd85aa847..6967bb480 100644
--- a/variants/esp32/tlora_v2_1_16/platformio.ini
+++ b/variants/esp32/tlora_v2_1_16/platformio.ini
@@ -4,5 +4,4 @@ board = ttgo-lora32-v21
 board_check = true
 build_flags = 
   ${esp32_base.build_flags} -D TLORA_V2_1_16 -I variants/esp32/tlora_v2_1_16
-  -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely.
 upload_speed = 115200
diff --git a/variants/esp32/tlora_v2_1_16_tcxo/platformio.ini b/variants/esp32/tlora_v2_1_16_tcxo/platformio.ini
index 9404faa02..a6b9d2254 100644
--- a/variants/esp32/tlora_v2_1_16_tcxo/platformio.ini
+++ b/variants/esp32/tlora_v2_1_16_tcxo/platformio.ini
@@ -6,6 +6,5 @@ build_flags =
   ${esp32_base.build_flags}
   -D TLORA_V2_1_16
   -I variants/esp32/tlora_v2_1_16
-  -D GPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely.
   -D LORA_TCXO_GPIO=33
 upload_speed = 115200
\ No newline at end of file
diff --git a/variants/esp32/tlora_v3_3_0_tcxo/platformio.ini b/variants/esp32/tlora_v3_3_0_tcxo/platformio.ini
index f1110386e..1258fd8b7 100644
--- a/variants/esp32/tlora_v3_3_0_tcxo/platformio.ini
+++ b/variants/esp32/tlora_v3_3_0_tcxo/platformio.ini
@@ -5,6 +5,5 @@ build_flags =
   ${esp32_base.build_flags}
   -D TLORA_V2_1_16
   -I variants/esp32/tlora_v2_1_16
-  -D GPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely.
   -D LORA_TCXO_GPIO=12
   -D BUTTON_PIN=0
\ No newline at end of file
diff --git a/variants/esp32s3/CDEBYTE_EoRa-S3/platformio.ini b/variants/esp32s3/CDEBYTE_EoRa-S3/platformio.ini
index dbd420f04..3fcfbf281 100644
--- a/variants/esp32s3/CDEBYTE_EoRa-S3/platformio.ini
+++ b/variants/esp32s3/CDEBYTE_EoRa-S3/platformio.ini
@@ -5,4 +5,3 @@ build_flags =
   ${esp32s3_base.build_flags}
   -D CDEBYTE_EORA_S3
   -I variants/esp32s3/CDEBYTE_EoRa-S3
-  -D GPS_POWER_TOGGLE
diff --git a/variants/esp32s3/crowpanel-esp32s3-5-epaper/platformio.ini b/variants/esp32s3/crowpanel-esp32s3-5-epaper/platformio.ini
index 49e84bf4f..eed21a412 100644
--- a/variants/esp32s3/crowpanel-esp32s3-5-epaper/platformio.ini
+++ b/variants/esp32s3/crowpanel-esp32s3-5-epaper/platformio.ini
@@ -16,7 +16,6 @@ build_flags =
   -I variants/esp32s3/crowpanel-esp32s3-5-epaper
   -D PRIVATE_HW
   -DBOARD_HAS_PSRAM
-  -DGPS_POWER_TOGGLE
   -DEINK_DISPLAY_MODEL=GxEPD2_579_GDEY0579T93 ;https://www.good-display.com/product/439.html
   -DEINK_WIDTH=792
   -DEINK_HEIGHT=272
@@ -46,7 +45,6 @@ build_flags =
   -I variants/esp32s3/crowpanel-esp32s3-5-epaper
   -D PRIVATE_HW
   -DBOARD_HAS_PSRAM
-  -DGPS_POWER_TOGGLE
   -DEINK_DISPLAY_MODEL=GxEPD2_420_GYE042A87   ; similar Panel: GDEY042T81 : https://www.good-display.com/product/386.html
   -DEINK_WIDTH=400
   -DEINK_HEIGHT=300
@@ -76,7 +74,6 @@ build_flags =
   -I variants/esp32s3/crowpanel-esp32s3-5-epaper
   -D PRIVATE_HW
   -DBOARD_HAS_PSRAM
-  -DGPS_POWER_TOGGLE
   -DEINK_DISPLAY_MODEL=GxEPD2_290_GDEY029T94 ;https://www.good-display.com/product/389.html
   -DEINK_WIDTH=296
   -DEINK_HEIGHT=128
diff --git a/variants/esp32s3/heltec_capsule_sensor_v3/platformio.ini b/variants/esp32s3/heltec_capsule_sensor_v3/platformio.ini
index d43ffd0df..0bb21581a 100644
--- a/variants/esp32s3/heltec_capsule_sensor_v3/platformio.ini
+++ b/variants/esp32s3/heltec_capsule_sensor_v3/platformio.ini
@@ -6,5 +6,4 @@ board_build.partitions = default_8MB.csv
 build_flags = 
   ${esp32s3_base.build_flags} -I variants/esp32s3/heltec_capsule_sensor_v3
   -D HELTEC_CAPSULE_SENSOR_V3
-  -D GPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely.
   ;-D DEBUG_DISABLED ; uncomment this line to disable DEBUG output
diff --git a/variants/esp32s3/heltec_v3/platformio.ini b/variants/esp32s3/heltec_v3/platformio.ini
index b521e11ca..af0854e49 100644
--- a/variants/esp32s3/heltec_v3/platformio.ini
+++ b/variants/esp32s3/heltec_v3/platformio.ini
@@ -8,4 +8,3 @@ build_flags =
   ${esp32s3_base.build_flags}
   -D HELTEC_V3
   -I variants/esp32s3/heltec_v3
-  -D GPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely.
diff --git a/variants/esp32s3/heltec_v4/platformio.ini b/variants/esp32s3/heltec_v4/platformio.ini
index d0a250ad3..7057f9646 100644
--- a/variants/esp32s3/heltec_v4/platformio.ini
+++ b/variants/esp32s3/heltec_v4/platformio.ini
@@ -7,4 +7,3 @@ build_flags =
   ${esp32s3_base.build_flags}
   -D HELTEC_V4
   -I variants/esp32s3/heltec_v4
-  -D GPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely.
diff --git a/variants/esp32s3/heltec_wireless_tracker/platformio.ini b/variants/esp32s3/heltec_wireless_tracker/platformio.ini
index 2faba45a8..3a373bf4f 100644
--- a/variants/esp32s3/heltec_wireless_tracker/platformio.ini
+++ b/variants/esp32s3/heltec_wireless_tracker/platformio.ini
@@ -8,7 +8,6 @@ build_flags =
   ${esp32s3_base.build_flags}
   -I variants/esp32s3/heltec_wireless_tracker
   -D HELTEC_TRACKER_V1_1
-  -D GPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely.
   ;-D DEBUG_DISABLED ; uncomment this line to disable DEBUG output
 
 lib_deps =
diff --git a/variants/esp32s3/heltec_wireless_tracker_V1_0/platformio.ini b/variants/esp32s3/heltec_wireless_tracker_V1_0/platformio.ini
index 89fe4b385..cd961533d 100644
--- a/variants/esp32s3/heltec_wireless_tracker_V1_0/platformio.ini
+++ b/variants/esp32s3/heltec_wireless_tracker_V1_0/platformio.ini
@@ -8,7 +8,6 @@ build_flags =
   ${esp32s3_base.build_flags}
   -I variants/esp32s3/heltec_wireless_tracker_V1_0
   -D HELTEC_TRACKER_V1_0
-  -D GPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely.
   ;-D DEBUG_DISABLED ; uncomment this line to disable DEBUG output
 lib_deps =
   ${esp32s3_base.lib_deps}
diff --git a/variants/esp32s3/heltec_wireless_tracker_v2/platformio.ini b/variants/esp32s3/heltec_wireless_tracker_v2/platformio.ini
index 4872561db..0f9265f91 100644
--- a/variants/esp32s3/heltec_wireless_tracker_v2/platformio.ini
+++ b/variants/esp32s3/heltec_wireless_tracker_v2/platformio.ini
@@ -8,7 +8,6 @@ build_flags =
   ${esp32s3_base.build_flags}
   -I variants/esp32s3/heltec_wireless_tracker_v2
   -D HELTEC_WIRELESS_TRACKER_V2
-  -D GPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely.
 lib_deps =
   ${esp32s3_base.lib_deps}
   lovyan03/LovyanGFX@^1.2.0
diff --git a/variants/esp32s3/heltec_wsl_v3/platformio.ini b/variants/esp32s3/heltec_wsl_v3/platformio.ini
index 06cde2304..c038a463e 100644
--- a/variants/esp32s3/heltec_wsl_v3/platformio.ini
+++ b/variants/esp32s3/heltec_wsl_v3/platformio.ini
@@ -7,4 +7,3 @@ build_flags =
   ${esp32s3_base.build_flags}
   -D HELTEC_WSL_V3
   -I variants/esp32s3/heltec_wsl_v3
-  -D GPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely.
diff --git a/variants/esp32s3/link32_s3_v1/platformio.ini b/variants/esp32s3/link32_s3_v1/platformio.ini
index c1b71b3b5..8d88075c4 100644
--- a/variants/esp32s3/link32_s3_v1/platformio.ini
+++ b/variants/esp32s3/link32_s3_v1/platformio.ini
@@ -5,7 +5,6 @@ build_flags =
   ${esp32_base.build_flags}
   -D LINK_32
   -I variants/esp32s3/link32_s3_v1
-  -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely.
   -DARDUINO_USB_CDC_ON_BOOT
   -DARDUINO_USB_MODE=1
   -DRADIOLIB_EXCLUDE_SX128X=1
diff --git a/variants/esp32s3/t-deck-pro/platformio.ini b/variants/esp32s3/t-deck-pro/platformio.ini
index 45c3ae4ea..0b3c7843a 100644
--- a/variants/esp32s3/t-deck-pro/platformio.ini
+++ b/variants/esp32s3/t-deck-pro/platformio.ini
@@ -7,7 +7,6 @@ upload_protocol = esptool
 build_flags = 
   ${esp32_base.build_flags} -I variants/esp32s3/t-deck-pro
   -D T_DECK_PRO
-  -D GPS_POWER_TOGGLE
   -D USE_EINK
   -D EINK_DISPLAY_MODEL=GxEPD2_310_GDEQ031T10
   -D EINK_WIDTH=240
diff --git a/variants/esp32s3/t-deck/platformio.ini b/variants/esp32s3/t-deck/platformio.ini
index 7c8070c3e..9ab0756d1 100644
--- a/variants/esp32s3/t-deck/platformio.ini
+++ b/variants/esp32s3/t-deck/platformio.ini
@@ -9,7 +9,6 @@ upload_protocol = esptool
 build_flags = ${esp32s3_base.build_flags} 
   -D T_DECK 
   -D BOARD_HAS_PSRAM
-  -D GPS_POWER_TOGGLE
   -I variants/esp32s3/t-deck
 
 lib_deps = ${esp32s3_base.lib_deps}
diff --git a/variants/esp32s3/t-eth-elite/platformio.ini b/variants/esp32s3/t-eth-elite/platformio.ini
index 6107185ce..1a5823bc3 100644
--- a/variants/esp32s3/t-eth-elite/platformio.ini
+++ b/variants/esp32s3/t-eth-elite/platformio.ini
@@ -9,7 +9,6 @@ build_flags =
   -D T_ETH_ELITE
   -D HAS_UDP_MULTICAST=1
   -I variants/esp32s3/t-eth-elite
-  -D GPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely.
 
 lib_ignore = 
     Ethernet
diff --git a/variants/esp32s3/tlora-pager/platformio.ini b/variants/esp32s3/tlora-pager/platformio.ini
index 9800161bb..d63537904 100644
--- a/variants/esp32s3/tlora-pager/platformio.ini
+++ b/variants/esp32s3/tlora-pager/platformio.ini
@@ -10,7 +10,6 @@ build_flags = ${esp32s3_base.build_flags}
   -I variants/esp32s3/tlora-pager
   -D T_LORA_PAGER 
   -D BOARD_HAS_PSRAM
-  -D GPS_POWER_TOGGLE
   -D HAS_SDCARD
   -D SDCARD_USE_SPI1
   -D ENABLE_ROTARY_PULLUP
diff --git a/variants/esp32s3/tlora_t3s3_epaper/platformio.ini b/variants/esp32s3/tlora_t3s3_epaper/platformio.ini
index 71644ee77..eca052f57 100644
--- a/variants/esp32s3/tlora_t3s3_epaper/platformio.ini
+++ b/variants/esp32s3/tlora_t3s3_epaper/platformio.ini
@@ -8,7 +8,6 @@ build_flags =
   ${esp32_base.build_flags}
   -D TLORA_T3S3_EPAPER
   -I variants/esp32s3/tlora_t3s3_epaper
-  -DGPS_POWER_TOGGLE
   -DUSE_EINK
   -DEINK_DISPLAY_MODEL=GxEPD2_213_BN
   -DEINK_WIDTH=250
diff --git a/variants/esp32s3/tlora_t3s3_v1/platformio.ini b/variants/esp32s3/tlora_t3s3_v1/platformio.ini
index d9624f043..56ece0d62 100644
--- a/variants/esp32s3/tlora_t3s3_v1/platformio.ini
+++ b/variants/esp32s3/tlora_t3s3_v1/platformio.ini
@@ -6,4 +6,3 @@ upload_protocol = esptool
 
 build_flags = 
   ${esp32_base.build_flags} -D TLORA_T3S3_V1 -I variants/esp32s3/tlora_t3s3_v1
-  -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely.
diff --git a/variants/esp32s3/tracksenger/platformio.ini b/variants/esp32s3/tracksenger/platformio.ini
index 0e9f08541..213a917b1 100644
--- a/variants/esp32s3/tracksenger/platformio.ini
+++ b/variants/esp32s3/tracksenger/platformio.ini
@@ -8,7 +8,6 @@ build_flags =
   ${esp32s3_base.build_flags}
   -I variants/esp32s3/tracksenger/internal
   -D HELTEC_TRACKER_V1_1
-  -D GPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely.
   ;-D DEBUG_DISABLED ; uncomment this line to disable DEBUG output
 
 lib_deps =
@@ -25,7 +24,6 @@ build_flags =
   ${esp32s3_base.build_flags}
   -I variants/esp32s3/tracksenger/lcd
   -D HELTEC_TRACKER_V1_1
-  -D GPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely.
   ;-D DEBUG_DISABLED ; uncomment this line to disable DEBUG output
 
 lib_deps =
@@ -42,5 +40,4 @@ build_flags =
   ${esp32s3_base.build_flags}
   -I variants/esp32s3/tracksenger/oled
   -D HELTEC_TRACKER_V1_1
-  -D GPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely.
   ;-D DEBUG_DISABLED ; uncomment this line to disable DEBUG output
diff --git a/variants/nrf52840/ELECROW-ThinkNode-M1/platformio.ini b/variants/nrf52840/ELECROW-ThinkNode-M1/platformio.ini
index 0578bcfe8..f89b05d1f 100644
--- a/variants/nrf52840/ELECROW-ThinkNode-M1/platformio.ini
+++ b/variants/nrf52840/ELECROW-ThinkNode-M1/platformio.ini
@@ -9,7 +9,6 @@ debug_tool = jlink
 build_flags = ${nrf52840_base.build_flags}
   -Ivariants/nrf52840/ELECROW-ThinkNode-M1
   -DELECROW_ThinkNode_M1
-  -DGPS_POWER_TOGGLE
   -DUSE_EINK
   -DEINK_DISPLAY_MODEL=GxEPD2_154_D67
   -DEINK_WIDTH=200
diff --git a/variants/nrf52840/ME25LS01-4Y10TD/platformio.ini b/variants/nrf52840/ME25LS01-4Y10TD/platformio.ini
index 89a45694c..1279f12c6 100644
--- a/variants/nrf52840/ME25LS01-4Y10TD/platformio.ini
+++ b/variants/nrf52840/ME25LS01-4Y10TD/platformio.ini
@@ -8,7 +8,6 @@ build_flags = ${nrf52840_base.build_flags}
   -Isrc/platform/nrf52/softdevice
   -Isrc/platform/nrf52/softdevice/nrf52
   -DME25LS01_4Y10TD
-  -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely.
 board_build.ldscript = src/platform/nrf52/nrf52840_s140_v7.ld
 build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/ME25LS01-4Y10TD>
 lib_deps = 
diff --git a/variants/nrf52840/ME25LS01-4Y10TD_e-ink/platformio.ini b/variants/nrf52840/ME25LS01-4Y10TD_e-ink/platformio.ini
index ad5867bd5..f8d6da008 100644
--- a/variants/nrf52840/ME25LS01-4Y10TD_e-ink/platformio.ini
+++ b/variants/nrf52840/ME25LS01-4Y10TD_e-ink/platformio.ini
@@ -8,7 +8,6 @@ build_flags = ${nrf52840_base.build_flags}
   -Isrc/platform/nrf52/softdevice
   -Isrc/platform/nrf52/softdevice/nrf52
   -DME25LS01_4Y10TD
-  -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely.
   -DEINK_DISPLAY_MODEL=GxEPD2_420_GDEY042T81
   -DEINK_WIDTH=400
   -DEINK_HEIGHT=300
diff --git a/variants/nrf52840/MS24SF1/platformio.ini b/variants/nrf52840/MS24SF1/platformio.ini
index f162cbd60..df15b5605 100644
--- a/variants/nrf52840/MS24SF1/platformio.ini
+++ b/variants/nrf52840/MS24SF1/platformio.ini
@@ -7,7 +7,6 @@ build_flags = ${nrf52840_base.build_flags}
   -Ivariants/nrf52840/MS24SF1
   -Isrc/platform/nrf52/softdevice
   -Isrc/platform/nrf52/softdevice/nrf52
-  -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely.
 board_build.ldscript = src/platform/nrf52/nrf52840_s140_v7.ld
 build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/MS24SF1>
 lib_deps =
diff --git a/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/variant.h b/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/variant.h
index e93442c7e..87342a02f 100644
--- a/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/variant.h
+++ b/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/variant.h
@@ -94,7 +94,6 @@ NRF52 PRO MICRO PIN ASSIGNMENT
 #define PIN_GPS_RX (0 + 20) // P0.20
 
 #define PIN_GPS_EN (0 + 24) // P0.24
-#define GPS_POWER_TOGGLE
 #define GPS_UBLOX
 // define GPS_DEBUG
 
diff --git a/variants/nrf52840/diy/nrf52_promicro_diy_xtal/variant.h b/variants/nrf52840/diy/nrf52_promicro_diy_xtal/variant.h
index 7aafab7da..6e208e79f 100644
--- a/variants/nrf52840/diy/nrf52_promicro_diy_xtal/variant.h
+++ b/variants/nrf52840/diy/nrf52_promicro_diy_xtal/variant.h
@@ -93,7 +93,6 @@ NRF52 PRO MICRO PIN ASSIGNMENT
 #define PIN_GPS_RX (0 + 20) // P0.20
 
 #define PIN_GPS_EN (0 + 24) // P0.24
-#define GPS_POWER_TOGGLE
 #define GPS_UBLOX
 // define GPS_DEBUG
 
diff --git a/variants/nrf52840/gat562_mesh_trial_tracker/platformio.ini b/variants/nrf52840/gat562_mesh_trial_tracker/platformio.ini
index c6a5a7399..c6cd23314 100644
--- a/variants/nrf52840/gat562_mesh_trial_tracker/platformio.ini
+++ b/variants/nrf52840/gat562_mesh_trial_tracker/platformio.ini
@@ -8,7 +8,6 @@ build_flags = ${nrf52840_base.build_flags}
   -I variants/nrf52840/gat562_mesh_trial_tracker
   ;-D GAT562_MESH_TRIAL_TRACKER
   -D PRIVATE_HW
-  -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
diff --git a/variants/nrf52840/heltec_mesh_node_t114/platformio.ini b/variants/nrf52840/heltec_mesh_node_t114/platformio.ini
index c7b30b339..c49dadd56 100644
--- a/variants/nrf52840/heltec_mesh_node_t114/platformio.ini
+++ b/variants/nrf52840/heltec_mesh_node_t114/platformio.ini
@@ -8,7 +8,6 @@ debug_tool = jlink
 # add -DCFG_SYSVIEW if you want to use the Segger systemview tool for OS profiling.
 build_flags = ${nrf52840_base.build_flags}
   -Ivariants/nrf52840/heltec_mesh_node_t114
-  -DGPS_POWER_TOGGLE
   -DHELTEC_T114
 
 build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/heltec_mesh_node_t114>
diff --git a/variants/nrf52840/heltec_mesh_solar/platformio.ini b/variants/nrf52840/heltec_mesh_solar/platformio.ini
index 625dd1d43..36a7904d6 100644
--- a/variants/nrf52840/heltec_mesh_solar/platformio.ini
+++ b/variants/nrf52840/heltec_mesh_solar/platformio.ini
@@ -8,7 +8,6 @@ debug_tool = jlink
 # add -DCFG_SYSVIEW if you want to use the Segger systemview tool for OS profiling.
 build_flags = ${nrf52840_base.build_flags}
   -Ivariants/nrf52840/heltec_mesh_solar
-  -DGPS_POWER_TOGGLE
   -DHELTEC_MESH_SOLAR
 
 build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/heltec_mesh_solar>
diff --git a/variants/nrf52840/meshlink/platformio.ini b/variants/nrf52840/meshlink/platformio.ini
index 466362242..2a4e27fe8 100644
--- a/variants/nrf52840/meshlink/platformio.ini
+++ b/variants/nrf52840/meshlink/platformio.ini
@@ -9,7 +9,6 @@ board_level = extra
 build_flags = ${nrf52840_base.build_flags}
   -I variants/nrf52840/meshlink
   -D MESHLINK
-  -D GPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely.
   -D EINK_DISPLAY_MODEL=GxEPD2_213_B74
   -D EINK_WIDTH=250
   -D EINK_HEIGHT=122
diff --git a/variants/nrf52840/meshlink_eink/platformio.ini b/variants/nrf52840/meshlink_eink/platformio.ini
index af5a0040e..c0c0cb1dd 100644
--- a/variants/nrf52840/meshlink_eink/platformio.ini
+++ b/variants/nrf52840/meshlink_eink/platformio.ini
@@ -9,7 +9,6 @@ board_level = extra
 build_flags = ${nrf52840_base.build_flags}
   -I variants/nrf52840/meshlink_eink
   -D MESHLINK
-  -D GPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely.
   -D EINK_DISPLAY_MODEL=GxEPD2_213_B74
   -D EINK_WIDTH=250
   -D EINK_HEIGHT=122
diff --git a/variants/nrf52840/r1-neo/platformio.ini b/variants/nrf52840/r1-neo/platformio.ini
index 6feb55dc9..60f1f6ae1 100644
--- a/variants/nrf52840/r1-neo/platformio.ini
+++ b/variants/nrf52840/r1-neo/platformio.ini
@@ -6,7 +6,6 @@ board_check = true
 build_flags = ${nrf52840_base.build_flags}
   -Ivariants/nrf52840/r1-neo
   -D R1_NEO
-  -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
diff --git a/variants/nrf52840/rak2560/platformio.ini b/variants/nrf52840/rak2560/platformio.ini
index edc648b9b..021e6d03b 100644
--- a/variants/nrf52840/rak2560/platformio.ini
+++ b/variants/nrf52840/rak2560/platformio.ini
@@ -6,7 +6,6 @@ board_check = true
 build_flags = ${nrf52840_base.build_flags}
   -I variants/nrf52840/rak2560
   -D RAK_4631
-  -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
diff --git a/variants/nrf52840/rak4631/platformio.ini b/variants/nrf52840/rak4631/platformio.ini
index 6bf5f44cb..205966529 100644
--- a/variants/nrf52840/rak4631/platformio.ini
+++ b/variants/nrf52840/rak4631/platformio.ini
@@ -7,7 +7,6 @@ board_check = true
 build_flags = ${nrf52840_base.build_flags}
   -I variants/nrf52840/rak4631
   -D RAK_4631
-  -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely.
   -DEINK_DISPLAY_MODEL=GxEPD2_213_BN
   -DEINK_WIDTH=250
   -DEINK_HEIGHT=122
diff --git a/variants/nrf52840/rak4631_eth_gw/platformio.ini b/variants/nrf52840/rak4631_eth_gw/platformio.ini
index 4be8843a2..3c61e3498 100644
--- a/variants/nrf52840/rak4631_eth_gw/platformio.ini
+++ b/variants/nrf52840/rak4631_eth_gw/platformio.ini
@@ -6,7 +6,6 @@ board_check = true
 build_flags = ${nrf52840_base.build_flags}
   -I variants/nrf52840/rak4631_eth_gw
   -D RAK_4631
-  -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely.
   -DHAS_UDP_MULTICAST=1
   -DEINK_DISPLAY_MODEL=GxEPD2_213_BN
   -DEINK_WIDTH=250
diff --git a/variants/nrf52840/rak4631_nomadstar_meteor_pro/platformio.ini b/variants/nrf52840/rak4631_nomadstar_meteor_pro/platformio.ini
index e94eef1ee..d7dab2678 100644
--- a/variants/nrf52840/rak4631_nomadstar_meteor_pro/platformio.ini
+++ b/variants/nrf52840/rak4631_nomadstar_meteor_pro/platformio.ini
@@ -6,7 +6,6 @@ board_check = true
 build_flags = ${nrf52840_base.build_flags}
   -I variants/nrf52840/rak4631_nomadstar_meteor_pro
   -D NOMADSTAR_METEOR_PRO
-  ;-DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely.
   -DEINK_DISPLAY_MODEL=GxEPD2_213_BN
   -DEINK_WIDTH=250
   -DEINK_HEIGHT=122
diff --git a/variants/nrf52840/rak_wismeshtag/platformio.ini b/variants/nrf52840/rak_wismeshtag/platformio.ini
index 08e723302..f04d1f186 100644
--- a/variants/nrf52840/rak_wismeshtag/platformio.ini
+++ b/variants/nrf52840/rak_wismeshtag/platformio.ini
@@ -7,7 +7,6 @@ build_flags = ${nrf52840_base.build_flags}
   -I variants/nrf52840/rak_wismeshtag
   -D WISMESH_TAG
   -D RAK_4631
-  -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
diff --git a/variants/nrf52840/rak_wismeshtap/platformio.ini b/variants/nrf52840/rak_wismeshtap/platformio.ini
index adf301537..3369f9c77 100644
--- a/variants/nrf52840/rak_wismeshtap/platformio.ini
+++ b/variants/nrf52840/rak_wismeshtap/platformio.ini
@@ -6,7 +6,6 @@ build_flags = ${nrf52840_base.build_flags}
   -Ivariants/nrf52840/rak_wismeshtap
   -DWISMESH_TAP
   -DRAK_4631
-  -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely.
   -DEINK_DISPLAY_MODEL=GxEPD2_213_BN
   -DEINK_WIDTH=250
   -DEINK_HEIGHT=122
diff --git a/variants/nrf52840/t-echo-lite/platformio.ini b/variants/nrf52840/t-echo-lite/platformio.ini
index 68ae59dcb..90e6487a7 100644
--- a/variants/nrf52840/t-echo-lite/platformio.ini
+++ b/variants/nrf52840/t-echo-lite/platformio.ini
@@ -9,7 +9,6 @@ debug_tool = jlink
 build_flags = ${nrf52840_base.build_flags}
   -Ivariants/nrf52840/t-echo-lite
   -D T_ECHO_LITE
-  -D GPS_POWER_TOGGLE
   -D EINK_DISPLAY_MODEL=GxEPD2_122_T61
   -D EINK_WIDTH=192
   -D EINK_HEIGHT=176
diff --git a/variants/nrf52840/t-echo/platformio.ini b/variants/nrf52840/t-echo/platformio.ini
index 6541c9796..051fb3099 100644
--- a/variants/nrf52840/t-echo/platformio.ini
+++ b/variants/nrf52840/t-echo/platformio.ini
@@ -9,7 +9,6 @@ debug_tool = jlink
 # add -DCFG_SYSVIEW if you want to use the Segger systemview tool for OS profiling.
 build_flags = ${nrf52840_base.build_flags}
   -Ivariants/nrf52840/t-echo
-  -DGPS_POWER_TOGGLE
   -DEINK_DISPLAY_MODEL=GxEPD2_154_D67
   -DEINK_WIDTH=200
   -DEINK_HEIGHT=200
diff --git a/variants/nrf52840/tracker-t1000-e/platformio.ini b/variants/nrf52840/tracker-t1000-e/platformio.ini
index c6c3f269c..905d751fd 100644
--- a/variants/nrf52840/tracker-t1000-e/platformio.ini
+++ b/variants/nrf52840/tracker-t1000-e/platformio.ini
@@ -7,7 +7,6 @@ build_flags = ${nrf52840_base.build_flags}
   -Isrc/platform/nrf52/softdevice
   -Isrc/platform/nrf52/softdevice/nrf52
   -DTRACKER_T1000_E
-  -DGPS_POWER_TOGGLE
   -DMESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR_EXTERNAL=1
   -DMESHTASTIC_EXCLUDE_CANNEDMESSAGES=1
   -DMESHTASTIC_EXCLUDE_SCREEN=1
diff --git a/variants/nrf52840/wio-sdk-wm1110/platformio.ini b/variants/nrf52840/wio-sdk-wm1110/platformio.ini
index 2c65246b8..028129783 100644
--- a/variants/nrf52840/wio-sdk-wm1110/platformio.ini
+++ b/variants/nrf52840/wio-sdk-wm1110/platformio.ini
@@ -14,7 +14,6 @@ build_flags = ${nrf52840_base.build_flags}
   -Isrc/platform/nrf52/softdevice
   -Isrc/platform/nrf52/softdevice/nrf52
   -DWIO_WM1110
-  -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely.
   -DCFG_TUD_CDC=0
 board_build.ldscript = src/platform/nrf52/nrf52840_s140_v7.ld
 build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/wio-sdk-wm1110>
diff --git a/variants/nrf52840/wio-t1000-s/platformio.ini b/variants/nrf52840/wio-t1000-s/platformio.ini
index 3594bcf07..c6b61fc8a 100644
--- a/variants/nrf52840/wio-t1000-s/platformio.ini
+++ b/variants/nrf52840/wio-t1000-s/platformio.ini
@@ -8,7 +8,6 @@ build_flags = ${nrf52840_base.build_flags}
   -Isrc/platform/nrf52/softdevice
   -Isrc/platform/nrf52/softdevice/nrf52
   -DWIO_WM1110
-  -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely.
 board_build.ldscript = src/platform/nrf52/nrf52840_s140_v7.ld
 build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/wio-t1000-s>
 lib_deps = 
diff --git a/variants/nrf52840/wio-tracker-wm1110/platformio.ini b/variants/nrf52840/wio-tracker-wm1110/platformio.ini
index b383043bb..73b7dedd4 100644
--- a/variants/nrf52840/wio-tracker-wm1110/platformio.ini
+++ b/variants/nrf52840/wio-tracker-wm1110/platformio.ini
@@ -7,7 +7,6 @@ build_flags = ${nrf52840_base.build_flags}
   -Isrc/platform/nrf52/softdevice
   -Isrc/platform/nrf52/softdevice/nrf52
   -DWIO_WM1110
-  -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely.
 board_build.ldscript = src/platform/nrf52/nrf52840_s140_v7.ld
 build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/wio-tracker-wm1110>
 lib_deps = 

From cb11e6b720fcf3eceed1b0e557013ec68153421e Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
 <41898282+github-actions[bot]@users.noreply.github.com>
Date: Sun, 12 Oct 2025 05:34:00 -0500
Subject: [PATCH 474/691] Update protobufs (#8305)

Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
---
 protobufs                                     |  2 +-
 src/mesh/generated/meshtastic/apponly.pb.h    |  2 +-
 src/mesh/generated/meshtastic/channel.pb.h    | 22 ++++++++-----------
 src/mesh/generated/meshtastic/deviceonly.pb.h |  4 ++--
 4 files changed, 13 insertions(+), 17 deletions(-)

diff --git a/protobufs b/protobufs
index a1b8c3d17..38638f19f 160000
--- a/protobufs
+++ b/protobufs
@@ -1 +1 @@
-Subproject commit a1b8c3d171445b2eebfd4b5bd1e4876f3bbed605
+Subproject commit 38638f19f84ad886222b484d6bf5a8459aed8c7e
diff --git a/src/mesh/generated/meshtastic/apponly.pb.h b/src/mesh/generated/meshtastic/apponly.pb.h
index db9dedaaf..f4c33bd79 100644
--- a/src/mesh/generated/meshtastic/apponly.pb.h
+++ b/src/mesh/generated/meshtastic/apponly.pb.h
@@ -55,7 +55,7 @@ extern const pb_msgdesc_t meshtastic_ChannelSet_msg;
 
 /* Maximum encoded size of messages (where known) */
 #define MESHTASTIC_MESHTASTIC_APPONLY_PB_H_MAX_SIZE meshtastic_ChannelSet_size
-#define meshtastic_ChannelSet_size               695
+#define meshtastic_ChannelSet_size               679
 
 #ifdef __cplusplus
 } /* extern "C" */
diff --git a/src/mesh/generated/meshtastic/channel.pb.h b/src/mesh/generated/meshtastic/channel.pb.h
index d5573a1e2..9dc757ab4 100644
--- a/src/mesh/generated/meshtastic/channel.pb.h
+++ b/src/mesh/generated/meshtastic/channel.pb.h
@@ -34,9 +34,9 @@ typedef enum _meshtastic_Channel_Role {
 typedef struct _meshtastic_ModuleSettings {
     /* Bits of precision for the location sent in position packets. */
     uint32_t position_precision;
-    /* Controls whether or not the phone / clients should mute the current channel
+    /* Controls whether or not the client / device should mute the current channel
  Useful for noisy public channels you don't necessarily want to disable */
-    bool is_client_muted;
+    bool is_muted;
 } meshtastic_ModuleSettings;
 
 typedef PB_BYTES_ARRAY_T(32) meshtastic_ChannelSettings_psk_t;
@@ -97,8 +97,6 @@ typedef struct _meshtastic_ChannelSettings {
     /* Per-channel module settings. */
     bool has_module_settings;
     meshtastic_ModuleSettings module_settings;
-    /* Whether or not we should receive notifactions / alerts through this channel */
-    bool mute;
 } meshtastic_ChannelSettings;
 
 /* A pair of a channel number, mode and the (sharable) settings for that channel */
@@ -130,16 +128,16 @@ extern "C" {
 
 
 /* Initializer values for message structs */
-#define meshtastic_ChannelSettings_init_default  {0, {0, {0}}, "", 0, 0, 0, false, meshtastic_ModuleSettings_init_default, 0}
+#define meshtastic_ChannelSettings_init_default  {0, {0, {0}}, "", 0, 0, 0, false, meshtastic_ModuleSettings_init_default}
 #define meshtastic_ModuleSettings_init_default   {0, 0}
 #define meshtastic_Channel_init_default          {0, false, meshtastic_ChannelSettings_init_default, _meshtastic_Channel_Role_MIN}
-#define meshtastic_ChannelSettings_init_zero     {0, {0, {0}}, "", 0, 0, 0, false, meshtastic_ModuleSettings_init_zero, 0}
+#define meshtastic_ChannelSettings_init_zero     {0, {0, {0}}, "", 0, 0, 0, false, meshtastic_ModuleSettings_init_zero}
 #define meshtastic_ModuleSettings_init_zero      {0, 0}
 #define meshtastic_Channel_init_zero             {0, false, meshtastic_ChannelSettings_init_zero, _meshtastic_Channel_Role_MIN}
 
 /* Field tags (for use in manual encoding/decoding) */
 #define meshtastic_ModuleSettings_position_precision_tag 1
-#define meshtastic_ModuleSettings_is_client_muted_tag 2
+#define meshtastic_ModuleSettings_is_muted_tag   2
 #define meshtastic_ChannelSettings_channel_num_tag 1
 #define meshtastic_ChannelSettings_psk_tag       2
 #define meshtastic_ChannelSettings_name_tag      3
@@ -147,7 +145,6 @@ extern "C" {
 #define meshtastic_ChannelSettings_uplink_enabled_tag 5
 #define meshtastic_ChannelSettings_downlink_enabled_tag 6
 #define meshtastic_ChannelSettings_module_settings_tag 7
-#define meshtastic_ChannelSettings_mute_tag      8
 #define meshtastic_Channel_index_tag             1
 #define meshtastic_Channel_settings_tag          2
 #define meshtastic_Channel_role_tag              3
@@ -160,15 +157,14 @@ X(a, STATIC,   SINGULAR, STRING,   name,              3) \
 X(a, STATIC,   SINGULAR, FIXED32,  id,                4) \
 X(a, STATIC,   SINGULAR, BOOL,     uplink_enabled,    5) \
 X(a, STATIC,   SINGULAR, BOOL,     downlink_enabled,   6) \
-X(a, STATIC,   OPTIONAL, MESSAGE,  module_settings,   7) \
-X(a, STATIC,   SINGULAR, BOOL,     mute,              8)
+X(a, STATIC,   OPTIONAL, MESSAGE,  module_settings,   7)
 #define meshtastic_ChannelSettings_CALLBACK NULL
 #define meshtastic_ChannelSettings_DEFAULT NULL
 #define meshtastic_ChannelSettings_module_settings_MSGTYPE meshtastic_ModuleSettings
 
 #define meshtastic_ModuleSettings_FIELDLIST(X, a) \
 X(a, STATIC,   SINGULAR, UINT32,   position_precision,   1) \
-X(a, STATIC,   SINGULAR, BOOL,     is_client_muted,   2)
+X(a, STATIC,   SINGULAR, BOOL,     is_muted,          2)
 #define meshtastic_ModuleSettings_CALLBACK NULL
 #define meshtastic_ModuleSettings_DEFAULT NULL
 
@@ -191,8 +187,8 @@ extern const pb_msgdesc_t meshtastic_Channel_msg;
 
 /* Maximum encoded size of messages (where known) */
 #define MESHTASTIC_MESHTASTIC_CHANNEL_PB_H_MAX_SIZE meshtastic_Channel_size
-#define meshtastic_ChannelSettings_size          74
-#define meshtastic_Channel_size                  89
+#define meshtastic_ChannelSettings_size          72
+#define meshtastic_Channel_size                  87
 #define meshtastic_ModuleSettings_size           8
 
 #ifdef __cplusplus
diff --git a/src/mesh/generated/meshtastic/deviceonly.pb.h b/src/mesh/generated/meshtastic/deviceonly.pb.h
index b5b116137..7fab82ff7 100644
--- a/src/mesh/generated/meshtastic/deviceonly.pb.h
+++ b/src/mesh/generated/meshtastic/deviceonly.pb.h
@@ -360,8 +360,8 @@ 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        2293
-#define meshtastic_ChannelFile_size              734
+#define meshtastic_BackupPreferences_size        2277
+#define meshtastic_ChannelFile_size              718
 #define meshtastic_DeviceState_size              1737
 #define meshtastic_NodeInfoLite_size             196
 #define meshtastic_PositionLite_size             28

From 11aff46af1e41a4800ae0cead411641a13c5822d Mon Sep 17 00:00:00 2001
From: Tom Fifield 
Date: Sun, 12 Oct 2025 21:34:34 +1100
Subject: [PATCH 475/691] Remove T1000E GPS startup delay sequence (#8236)

8 months ago, when this was added to the code, the GPS probe code
was still a little flaky.

Particularly after #6114 and #6116 were added, reliability improved
for all devices as we were sending fewer calls on the bus.

Today, the T1000E is the only Meshtastic device that regularly takes
2, 3, or 4 attempts to be detected via the probe code.

Removing these lines, on my T1000E, results in the AG3335 being
detected immediately.

Co-authored-by: Jonathan Bennett 
---
 src/gps/GPS.cpp | 11 -----------
 1 file changed, 11 deletions(-)

diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp
index 2cfa558ed..297ed3dfa 100644
--- a/src/gps/GPS.cpp
+++ b/src/gps/GPS.cpp
@@ -494,17 +494,6 @@ bool GPS::setup()
     if (!didSerialInit) {
         int msglen = 0;
         if (tx_gpio && gnssModel == GNSS_MODEL_UNKNOWN) {
-#ifdef TRACKER_T1000_E
-            // add power up/down strategy, improve ag3335 detection success
-            digitalWrite(PIN_GPS_EN, LOW);
-            delay(500);
-            digitalWrite(GPS_VRTC_EN, LOW);
-            delay(1000);
-            digitalWrite(GPS_VRTC_EN, HIGH);
-            delay(500);
-            digitalWrite(PIN_GPS_EN, HIGH);
-            delay(1000);
-#endif
             if (probeTries < GPS_PROBETRIES) {
                 gnssModel = probe(serialSpeeds[speedSelect]);
                 if (gnssModel == GNSS_MODEL_UNKNOWN) {

From fb08e17c39f625c5ddfea85918a0f6d86c67a019 Mon Sep 17 00:00:00 2001
From: Clive Blackledge 
Date: Sun, 12 Oct 2025 03:35:00 -0700
Subject: [PATCH 476/691] Increase bluetooth 5.0 PHY speed and MTU on esp32_s3
 (#8261)

* Increase Bluetooth speed to 2MB, increase MTU

* Adding esp32c6

* trunk fmt..
---
 src/detect/ScanI2CTwoWire.cpp  |  3 +-
 src/nimble/NimbleBluetooth.cpp | 62 +++++++++++++++++++++++++++++++++-
 2 files changed, 63 insertions(+), 2 deletions(-)

diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp
index 89a0610b4..d6daf7b7a 100644
--- a/src/detect/ScanI2CTwoWire.cpp
+++ b/src/detect/ScanI2CTwoWire.cpp
@@ -378,7 +378,8 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
             case SHT31_4x_ADDR:     // same as OPT3001_ADDR_ALT
             case SHT31_4x_ADDR_ALT: // same as OPT3001_ADDR
                 registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x89), 2);
-                if (registerValue == 0x11a2 || registerValue == 0x11da || registerValue == 0x11f3 || registerValue == 0xe9c || registerValue == 0xc8d) {
+                if (registerValue == 0x11a2 || registerValue == 0x11da || registerValue == 0x11f3 || registerValue == 0xe9c ||
+                    registerValue == 0xc8d) {
                     type = SHT4X;
                     logFoundDevice("SHT4X", (uint8_t)addr.address);
                 } else if (getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x7E), 2) == 0x5449) {
diff --git a/src/nimble/NimbleBluetooth.cpp b/src/nimble/NimbleBluetooth.cpp
index 0b3009d39..60165b2c0 100644
--- a/src/nimble/NimbleBluetooth.cpp
+++ b/src/nimble/NimbleBluetooth.cpp
@@ -17,6 +17,21 @@
 #include "PowerStatus.h"
 #endif
 
+#if defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C6)
+#if defined(CONFIG_NIMBLE_CPP_IDF)
+#include "host/ble_gap.h"
+#else
+#include "nimble/nimble/host/include/host/ble_gap.h"
+#endif
+
+namespace
+{
+constexpr uint16_t kPreferredBleMtu = 517;
+constexpr uint16_t kPreferredBleTxOctets = 251;
+constexpr uint16_t kPreferredBleTxTimeUs = (kPreferredBleTxOctets + 14) * 8;
+} // namespace
+#endif
+
 NimBLECharacteristic *fromNumCharacteristic;
 NimBLECharacteristic *BatteryCharacteristic;
 NimBLECharacteristic *logRadioCharacteristic;
@@ -212,6 +227,27 @@ class NimbleBluetoothServerCallback : public NimBLEServerCallbacks
     virtual void onConnect(NimBLEServer *pServer, NimBLEConnInfo &connInfo)
     {
         LOG_INFO("BLE incoming connection %s", connInfo.getAddress().toString().c_str());
+
+#if defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C6)
+        const uint16_t connHandle = connInfo.getConnHandle();
+        int phyResult =
+            ble_gap_set_prefered_le_phy(connHandle, BLE_GAP_LE_PHY_2M_MASK, BLE_GAP_LE_PHY_2M_MASK, BLE_GAP_LE_PHY_CODED_ANY);
+        if (phyResult == 0) {
+            LOG_INFO("BLE conn %u requested 2M PHY", connHandle);
+        } else {
+            LOG_WARN("Failed to prefer 2M PHY for conn %u, rc=%d", connHandle, phyResult);
+        }
+
+        int dataLenResult = ble_gap_set_data_len(connHandle, kPreferredBleTxOctets, kPreferredBleTxTimeUs);
+        if (dataLenResult == 0) {
+            LOG_INFO("BLE conn %u requested data length %u bytes", connHandle, kPreferredBleTxOctets);
+        } else {
+            LOG_WARN("Failed to raise data length for conn %u, rc=%d", connHandle, dataLenResult);
+        }
+
+        LOG_INFO("BLE conn %u initial MTU %u (target %u)", connHandle, connInfo.getMTU(), kPreferredBleMtu);
+        pServer->updateConnParams(connHandle, 6, 12, 0, 200);
+#endif
     }
 
     virtual void onDisconnect(NimBLEServer *pServer, NimBLEConnInfo &connInfo, int reason)
@@ -316,6 +352,30 @@ void NimbleBluetooth::setup()
     NimBLEDevice::init(getDeviceName());
     NimBLEDevice::setPower(ESP_PWR_LVL_P9);
 
+#if defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C6)
+    int mtuResult = NimBLEDevice::setMTU(kPreferredBleMtu);
+    if (mtuResult == 0) {
+        LOG_INFO("BLE MTU request set to %u", kPreferredBleMtu);
+    } else {
+        LOG_WARN("Unable to request MTU %u, rc=%d", kPreferredBleMtu, mtuResult);
+    }
+
+    int phyResult = ble_gap_set_prefered_default_le_phy(BLE_GAP_LE_PHY_2M_MASK, BLE_GAP_LE_PHY_2M_MASK);
+    if (phyResult == 0) {
+        LOG_INFO("BLE default PHY preference set to 2M");
+    } else {
+        LOG_WARN("Failed to prefer 2M PHY by default, rc=%d", phyResult);
+    }
+
+    int dataLenResult = ble_gap_write_sugg_def_data_len(kPreferredBleTxOctets, kPreferredBleTxTimeUs);
+    if (dataLenResult == 0) {
+        LOG_INFO("BLE suggested data length set to %u bytes", kPreferredBleTxOctets);
+    } else {
+        LOG_WARN("Failed to raise suggested data length (%u/%u), rc=%d", kPreferredBleTxOctets, kPreferredBleTxTimeUs,
+                 dataLenResult);
+    }
+#endif
+
     if (config.bluetooth.mode != meshtastic_Config_BluetoothConfig_PairingMode_NO_PIN) {
         NimBLEDevice::setSecurityAuth(BLE_SM_PAIR_AUTHREQ_BOND | BLE_SM_PAIR_AUTHREQ_MITM | BLE_SM_PAIR_AUTHREQ_SC);
         NimBLEDevice::setSecurityInitKey(BLE_SM_PAIR_KEY_DIST_ENC | BLE_SM_PAIR_KEY_DIST_ID);
@@ -459,4 +519,4 @@ void clearNVS()
     ESP.restart();
 #endif
 }
-#endif
\ No newline at end of file
+#endif

From f0126d44e2c6e464144a1df98a24b660c6c92496 Mon Sep 17 00:00:00 2001
From: Jason P 
Date: Sun, 12 Oct 2025 06:28:23 -0500
Subject: [PATCH 477/691] More BaseUI Frame Visibility Toggles (#8252)

* Add Power and Environmental Telemetry Hide/Show

* Allow Power and Telemetry Frames even if module disabled

---------

Co-authored-by: Ben Meadors 
---
 src/graphics/draw/MenuHandler.cpp | 16 ++++++++++++++++
 1 file changed, 16 insertions(+)

diff --git a/src/graphics/draw/MenuHandler.cpp b/src/graphics/draw/MenuHandler.cpp
index c93e34545..3e139c0d6 100644
--- a/src/graphics/draw/MenuHandler.cpp
+++ b/src/graphics/draw/MenuHandler.cpp
@@ -1431,6 +1431,8 @@ void menuHandler::FrameToggles_menu()
         lora,
         clock,
         show_favorites,
+        show_telemetry,
+        show_power,
         enumEnd
     };
     static const char *optionsArray[enumEnd] = {"Finish"};
@@ -1469,6 +1471,12 @@ void menuHandler::FrameToggles_menu()
     optionsArray[options] = screen->isFrameHidden("show_favorites") ? "Show Favorites" : "Hide Favorites";
     optionsEnumArray[options++] = show_favorites;
 
+    optionsArray[options] = moduleConfig.telemetry.environment_screen_enabled ? "Hide Telemetry" : "Show Telemetry";
+    optionsEnumArray[options++] = show_telemetry;
+
+    optionsArray[options] = moduleConfig.telemetry.power_screen_enabled ? "Hide Power" : "Show Power";
+    optionsEnumArray[options++] = show_power;
+
     BannerOverlayOptions bannerOptions;
     bannerOptions.message = "Show/Hide Frames";
     bannerOptions.optionsArrayPtr = optionsArray;
@@ -1523,6 +1531,14 @@ void menuHandler::FrameToggles_menu()
             screen->toggleFrameVisibility("show_favorites");
             menuHandler::menuQueue = menuHandler::FrameToggles;
             screen->runNow();
+        } else if (selected == show_telemetry) {
+            moduleConfig.telemetry.environment_screen_enabled = !moduleConfig.telemetry.environment_screen_enabled;
+            menuHandler::menuQueue = menuHandler::FrameToggles;
+            screen->runNow();
+        } else if (selected == show_power) {
+            moduleConfig.telemetry.power_screen_enabled = !moduleConfig.telemetry.power_screen_enabled;
+            menuHandler::menuQueue = menuHandler::FrameToggles;
+            screen->runNow();
         }
     };
     screen->showOverlayBanner(bannerOptions);

From a6732682deb60b31f0779f730b464499e30f5da4 Mon Sep 17 00:00:00 2001
From: Ben Meadors 
Date: Sun, 12 Oct 2025 06:30:17 -0500
Subject: [PATCH 478/691] Opt in to telemetry going forward (#8059)

---
 src/modules/Telemetry/DeviceTelemetry.cpp | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/src/modules/Telemetry/DeviceTelemetry.cpp b/src/modules/Telemetry/DeviceTelemetry.cpp
index 7e3018564..066b9361d 100644
--- a/src/modules/Telemetry/DeviceTelemetry.cpp
+++ b/src/modules/Telemetry/DeviceTelemetry.cpp
@@ -26,7 +26,8 @@ int32_t DeviceTelemetryModule::runOnce()
           Default::getConfiguredOrDefaultMsScaled(moduleConfig.telemetry.device_update_interval,
                                                   default_telemetry_broadcast_interval_secs, numOnlineNodes))) &&
         airTime->isTxAllowedChannelUtil(!isImpoliteRole) && airTime->isTxAllowedAirUtil() &&
-        config.device.role != meshtastic_Config_DeviceConfig_Role_CLIENT_HIDDEN) {
+        config.device.role != meshtastic_Config_DeviceConfig_Role_CLIENT_HIDDEN &&
+        moduleConfig.telemetry.device_telemetry_enabled) {
         sendTelemetry();
         lastSentToMesh = uptimeLastMs;
     } else if (service->isToPhoneQueueEmpty()) {

From 661e596dbb71a91f3c30098ac0a82a608e933b70 Mon Sep 17 00:00:00 2001
From: Ben Meadors 
Date: Sun, 12 Oct 2025 07:39:23 -0500
Subject: [PATCH 479/691] Fix muted channel compile errors after protobuf move
 (#8316)

---
 src/graphics/Screen.cpp                    | 2 +-
 src/modules/ExternalNotificationModule.cpp | 9 ++++++---
 2 files changed, 7 insertions(+), 4 deletions(-)

diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp
index 767f97eed..e1cc0ccad 100644
--- a/src/graphics/Screen.cpp
+++ b/src/graphics/Screen.cpp
@@ -1490,7 +1490,7 @@ int Screen::handleTextMessage(const meshtastic_MeshPacket *packet)
                     strcpy(banner, "Alert Received");
                 }
                 screen->showSimpleBanner(banner, 3000);
-            } else if (!channel.settings.mute) {
+            } else if (!channel.settings.has_module_settings || !channel.settings.module_settings.is_muted) {
                 if (longName && longName[0]) {
 #if defined(M5STACK_UNITC6L)
                     strcpy(banner, "New Message");
diff --git a/src/modules/ExternalNotificationModule.cpp b/src/modules/ExternalNotificationModule.cpp
index 2346cd299..ffc789275 100644
--- a/src/modules/ExternalNotificationModule.cpp
+++ b/src/modules/ExternalNotificationModule.cpp
@@ -510,7 +510,8 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP
                 }
             }
 
-            if (moduleConfig.external_notification.alert_message && !ch.settings.mute) {
+            if (moduleConfig.external_notification.alert_message &&
+                (!ch.settings.has_module_settings || !ch.settings.module_settings.is_muted)) {
                 LOG_INFO("externalNotificationModule - Notification Module");
                 isNagging = true;
                 setExternalState(0, true);
@@ -521,7 +522,8 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP
                 }
             }
 
-            if (moduleConfig.external_notification.alert_message_vibra && !ch.settings.mute) {
+            if (moduleConfig.external_notification.alert_message_vibra &&
+                (!ch.settings.has_module_settings || !ch.settings.module_settings.is_muted)) {
                 LOG_INFO("externalNotificationModule - Notification Module (Vibra)");
                 isNagging = true;
                 setExternalState(1, true);
@@ -532,7 +534,8 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP
                 }
             }
 
-            if (moduleConfig.external_notification.alert_message_buzzer && !ch.settings.mute) {
+            if (moduleConfig.external_notification.alert_message_buzzer &&
+                (!ch.settings.has_module_settings || !ch.settings.module_settings.is_muted)) {
                 LOG_INFO("externalNotificationModule - Notification Module (Buzzer)");
                 if (config.device.buzzer_mode != meshtastic_Config_DeviceConfig_BuzzerMode_DIRECT_MSG_ONLY ||
                     (!isBroadcast(mp.to) && isToUs(&mp))) {

From 5eeffdb290b1e1f1a35d186066ffaf2cdf76dbfc Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Sun, 12 Oct 2025 07:44:54 -0500
Subject: [PATCH 480/691] chore(deps): update meshtastic/device-ui digest to
 3fb7c0e (#8291)

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 7121f00b7..5b7f5ddcf 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -120,7 +120,7 @@ lib_deps =
 [device-ui_base]
 lib_deps =
 	# renovate: datasource=git-refs depName=meshtastic/device-ui packageName=https://github.com/meshtastic/device-ui gitBranch=master
-	https://github.com/meshtastic/device-ui/archive/6d8cc228298a1ecd9913aed757187e9527c1facc.zip
+	https://github.com/meshtastic/device-ui/archive/3fb7c0e28e8e51fc0a7d56facacf3411f1d29fe0.zip
 
 ; Common libs for environmental measurements in telemetry module
 [environmental_base]

From 7537d28419d2bd2dc70519889b509cc1994789ff Mon Sep 17 00:00:00 2001
From: l0g-lab <72365840+l0g-lab@users.noreply.github.com>
Date: Sun, 12 Oct 2025 09:25:15 -0400
Subject: [PATCH 481/691] Nodelist: choice of long or short name (#7926)

* update to use long names for pager

* remove duplicate

* add menu item

* fix after conflict

* menu name change. use sanitizeString

* fix formatting issue. should pass trunk now.

* remove auto-generated protobufs

* remove log, add tdeck, improvements.

---------

Co-authored-by: l0g-lab 
Co-authored-by: Tom Fifield 
Co-authored-by: Ben Meadors 
---
 src/graphics/draw/MenuHandler.cpp      | 42 ++++++++++++++++++++++++--
 src/graphics/draw/MenuHandler.h        |  2 ++
 src/graphics/draw/NodeListRenderer.cpp | 32 ++++++++++++--------
 3 files changed, 60 insertions(+), 16 deletions(-)

diff --git a/src/graphics/draw/MenuHandler.cpp b/src/graphics/draw/MenuHandler.cpp
index 3e139c0d6..701062e08 100644
--- a/src/graphics/draw/MenuHandler.cpp
+++ b/src/graphics/draw/MenuHandler.cpp
@@ -762,6 +762,31 @@ void menuHandler::nodeListMenu()
     screen->showOverlayBanner(bannerOptions);
 }
 
+void menuHandler::nodeNameLengthMenu()
+{
+    enum OptionsNumbers { Back, Long, Short };
+    static const char *optionsArray[] = {"Back", "Long", "Short"};
+    BannerOverlayOptions bannerOptions;
+    bannerOptions.message = "Node Name Length";
+    bannerOptions.optionsArrayPtr = optionsArray;
+    bannerOptions.optionsCount = 3;
+    bannerOptions.bannerCallback = [](int selected) -> void {
+        if (selected == Long) {
+            // Set names to long
+            LOG_INFO("Setting names to long");
+            config.display.use_long_node_name = true;
+        } else if (selected == Short) {
+            // Set names to short
+            LOG_INFO("Setting names to short");
+            config.display.use_long_node_name = false;
+        } else if (selected == Back) {
+            menuQueue = screen_options_menu;
+            screen->runNow();
+        }
+    };
+    screen->showOverlayBanner(bannerOptions);
+}
+
 void menuHandler::resetNodeDBMenu()
 {
     static const char *optionsArray[] = {"Back", "Confirm"};
@@ -1304,11 +1329,16 @@ void menuHandler::screenOptionsMenu()
     hasSupportBrightness = false;
 #endif
 
-    enum optionsNumbers { Back, Brightness, ScreenColor };
-    static const char *optionsArray[4] = {"Back"};
-    static int optionsEnumArray[4] = {Back};
+    enum optionsNumbers { Back, NodeNameLength, Brightness, ScreenColor };
+    static const char *optionsArray[5] = {"Back"};
+    static int optionsEnumArray[5] = {Back};
     int options = 1;
 
+#if defined(T_DECK) || defined(T_LORA_PAGER)
+    optionsArray[options] = "Show Long/Short Name";
+    optionsEnumArray[options++] = NodeNameLength;
+#endif
+
     // Only show brightness for B&W displays
     if (hasSupportBrightness) {
         optionsArray[options] = "Brightness";
@@ -1333,6 +1363,9 @@ void menuHandler::screenOptionsMenu()
         } else if (selected == ScreenColor) {
             menuHandler::menuQueue = menuHandler::tftcolormenupicker;
             screen->runNow();
+        } else if (selected == NodeNameLength) {
+            menuHandler::menuQueue = menuHandler::node_name_length_menu;
+            screen->runNow();
         } else {
             menuQueue = system_base_menu;
             screen->runNow();
@@ -1610,6 +1643,9 @@ void menuHandler::handleMenuSwitch(OLEDDisplay *display)
     case brightness_picker:
         BrightnessPickerMenu();
         break;
+    case node_name_length_menu:
+        nodeNameLengthMenu();
+        break;
     case reboot_menu:
         rebootMenu();
         break;
diff --git a/src/graphics/draw/MenuHandler.h b/src/graphics/draw/MenuHandler.h
index 47dbb2543..1f7bbac8c 100644
--- a/src/graphics/draw/MenuHandler.h
+++ b/src/graphics/draw/MenuHandler.h
@@ -43,6 +43,7 @@ class menuHandler
         key_verification_final_prompt,
         trace_route_menu,
         throttle_message,
+        node_name_length_menu,
         FrameToggles
     };
     static screenMenus menuQueue;
@@ -85,6 +86,7 @@ class menuHandler
     static void notificationsMenu();
     static void screenOptionsMenu();
     static void powerMenu();
+    static void nodeNameLengthMenu();
     static void FrameToggles_menu();
     static void textMessageMenu();
 
diff --git a/src/graphics/draw/NodeListRenderer.cpp b/src/graphics/draw/NodeListRenderer.cpp
index 7d6a38dd3..07577db8c 100644
--- a/src/graphics/draw/NodeListRenderer.cpp
+++ b/src/graphics/draw/NodeListRenderer.cpp
@@ -55,26 +55,32 @@ static int scrollIndex = 0;
 
 const char *getSafeNodeName(meshtastic_NodeInfoLite *node)
 {
+    const char *name = NULL;
     static char nodeName[16] = "?";
-    if (node->has_user && strlen(node->user.short_name) > 0) {
-        bool valid = true;
-        const char *name = node->user.short_name;
-        for (size_t i = 0; i < strlen(name); i++) {
-            uint8_t c = (uint8_t)name[i];
-            if (c < 32 || c > 126) {
-                valid = false;
-                break;
-            }
-        }
-        if (valid) {
-            strncpy(nodeName, name, sizeof(nodeName) - 1);
-            nodeName[sizeof(nodeName) - 1] = '\0';
+    if (config.display.use_long_node_name == true) {
+        if (node->has_user && strlen(node->user.long_name) > 0) {
+            name = node->user.long_name;
         } else {
             snprintf(nodeName, sizeof(nodeName), "(%04X)", (uint16_t)(node->num & 0xFFFF));
         }
+    } else {
+        if (node->has_user && strlen(node->user.short_name) > 0) {
+            name = node->user.short_name;
+        } else {
+            snprintf(nodeName, sizeof(nodeName), "(%04X)", (uint16_t)(node->num & 0xFFFF));
+        }
+    }
+
+    // Use sanitizeString() function and copy directly into nodeName
+    std::string sanitized_name = sanitizeString(name ? name : "");
+
+    if (!sanitized_name.empty()) {
+        strncpy(nodeName, sanitized_name.c_str(), sizeof(nodeName) - 1);
+        nodeName[sizeof(nodeName) - 1] = '\0';
     } else {
         snprintf(nodeName, sizeof(nodeName), "(%04X)", (uint16_t)(node->num & 0xFFFF));
     }
+
     return nodeName;
 }
 

From e24e2ccf6267b34e05ba098ef45a5490af34c1fe Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
 <41898282+github-actions[bot]@users.noreply.github.com>
Date: Sun, 12 Oct 2025 08:25:34 -0500
Subject: [PATCH 482/691] Upgrade trunk (#8245)

Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com>
---
 .trunk/trunk.yaml | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml
index 8c981850d..2d3c0aa2d 100644
--- a/.trunk/trunk.yaml
+++ b/.trunk/trunk.yaml
@@ -4,19 +4,19 @@ cli:
 plugins:
   sources:
     - id: trunk
-      ref: v1.7.2
+      ref: v1.7.3
       uri: https://github.com/trunk-io/plugins
 lint:
   enabled:
-    - checkov@3.2.473
-    - renovate@41.132.5
+    - checkov@3.2.477
+    - renovate@41.143.2
     - prettier@3.6.2
     - trufflehog@3.90.8
     - yamllint@1.37.1
     - bandit@1.8.6
-    - trivy@0.67.0
+    - trivy@0.67.1
     - taplo@0.10.0
-    - ruff@0.13.3
+    - ruff@0.14.0
     - isort@6.1.0
     - markdownlint@0.45.0
     - oxipng@9.1.5

From fcaa168d2d7a36f7ca37e741f79e77c11768ac33 Mon Sep 17 00:00:00 2001
From: Clive Blackledge 
Date: Mon, 13 Oct 2025 04:32:05 -0700
Subject: [PATCH 483/691] Ble reconnect prefetch bug fix, plus some speed
 enhancements (#8324)

* Fixing bluetooth reconnects and adding performance

* Added comments
---
 src/mesh/PhoneAPI.cpp          | 58 +++++++++++++++++++++++-----------
 src/mesh/PhoneAPI.h            |  7 ++++
 src/nimble/NimbleBluetooth.cpp | 51 ++++++++++++++++++++++++++----
 3 files changed, 91 insertions(+), 25 deletions(-)

diff --git a/src/mesh/PhoneAPI.cpp b/src/mesh/PhoneAPI.cpp
index e8dc9843c..9eeadf5a2 100644
--- a/src/mesh/PhoneAPI.cpp
+++ b/src/mesh/PhoneAPI.cpp
@@ -72,6 +72,7 @@ void PhoneAPI::handleStartConfig()
 
     LOG_INFO("Start API client config");
     nodeInfoForPhone.num = 0; // Don't keep returning old nodeinfos
+    nodeInfoQueue.clear();
     resetReadIndex();
 }
 
@@ -94,6 +95,7 @@ void PhoneAPI::close()
         fromRadioScratch = {};
         toRadioScratch = {};
         nodeInfoForPhone = {};
+        nodeInfoQueue.clear();
         packetForPhone = NULL;
         filesManifest.clear();
         fromRadioNum = 0;
@@ -431,17 +433,25 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
         break;
 
     case STATE_SEND_OTHER_NODEINFOS: {
+        LOG_DEBUG("Send known nodes");
+        if (nodeInfoForPhone.num == 0 && !nodeInfoQueue.empty()) {
+            // Serve the next cached node without re-reading from the DB iterator.
+            nodeInfoForPhone = nodeInfoQueue.front();
+            nodeInfoQueue.pop_front();
+        }
+
         if (nodeInfoForPhone.num != 0) {
             // Just in case we stored a different user.id in the past, but should never happen going forward
             sprintf(nodeInfoForPhone.user.id, "!%08x", nodeInfoForPhone.num);
-            LOG_INFO("nodeinfo: num=0x%x, lastseen=%u, id=%s, name=%s", nodeInfoForPhone.num, nodeInfoForPhone.last_heard,
-                     nodeInfoForPhone.user.id, nodeInfoForPhone.user.long_name);
+            LOG_DEBUG("nodeinfo: num=0x%x, lastseen=%u, id=%s, name=%s", nodeInfoForPhone.num, nodeInfoForPhone.last_heard,
+                      nodeInfoForPhone.user.id, nodeInfoForPhone.user.long_name);
             fromRadioScratch.which_payload_variant = meshtastic_FromRadio_node_info_tag;
             fromRadioScratch.node_info = nodeInfoForPhone;
-            // Stay in current state until done sending nodeinfos
-            nodeInfoForPhone.num = 0; // We just consumed a nodeinfo, will need a new one next time
+            nodeInfoForPhone = {};
+            prefetchNodeInfos();
         } else {
             LOG_DEBUG("Done sending nodeinfo");
+            nodeInfoQueue.clear();
             state = STATE_SEND_FILEMANIFEST;
             // Go ahead and send that ID right now
             return getFromRadio(buf);
@@ -545,6 +555,30 @@ void PhoneAPI::releaseQueueStatusPhonePacket()
     }
 }
 
+void PhoneAPI::prefetchNodeInfos()
+{
+    bool added = false;
+    // Keep the queue topped up so BLE reads stay responsive even if DB fetches take a moment.
+    while (nodeInfoQueue.size() < kNodePrefetchDepth) {
+        auto nextNode = nodeDB->readNextMeshNode(readIndex);
+        if (!nextNode)
+            break;
+
+        auto info = TypeConversions::ConvertToNodeInfo(nextNode);
+        bool isUs = info.num == nodeDB->getNodeNum();
+        info.hops_away = isUs ? 0 : info.hops_away;
+        info.last_heard = isUs ? getValidTime(RTCQualityFromNet) : info.last_heard;
+        info.snr = isUs ? 0 : info.snr;
+        info.via_mqtt = isUs ? false : info.via_mqtt;
+        info.is_favorite = info.is_favorite || isUs;
+        nodeInfoQueue.push_back(info);
+        added = true;
+    }
+
+    if (added)
+        onNowHasData(0);
+}
+
 void PhoneAPI::releaseMqttClientProxyPhonePacket()
 {
     if (mqttClientProxyMessageForPhone) {
@@ -581,20 +615,8 @@ bool PhoneAPI::available()
         return true;
 
     case STATE_SEND_OTHER_NODEINFOS:
-        if (nodeInfoForPhone.num == 0) {
-            auto nextNode = nodeDB->readNextMeshNode(readIndex);
-            if (nextNode) {
-                nodeInfoForPhone = TypeConversions::ConvertToNodeInfo(nextNode);
-                bool isUs = nodeInfoForPhone.num == nodeDB->getNodeNum();
-                nodeInfoForPhone.hops_away = isUs ? 0 : nodeInfoForPhone.hops_away;
-                nodeInfoForPhone.last_heard = isUs ? getValidTime(RTCQualityFromNet) : nodeInfoForPhone.last_heard;
-                nodeInfoForPhone.snr = isUs ? 0 : nodeInfoForPhone.snr;
-                nodeInfoForPhone.via_mqtt = isUs ? false : nodeInfoForPhone.via_mqtt;
-                nodeInfoForPhone.is_favorite = nodeInfoForPhone.is_favorite || isUs; // Our node is always a favorite
-
-                onNowHasData(0);
-            }
-        }
+        if (nodeInfoQueue.empty())
+            prefetchNodeInfos();
         return true; // Always say we have something, because we might need to advance our state machine
     case STATE_SEND_PACKETS: {
         if (!queueStatusPacketForPhone)
diff --git a/src/mesh/PhoneAPI.h b/src/mesh/PhoneAPI.h
index 0d7772d17..692fdd0b9 100644
--- a/src/mesh/PhoneAPI.h
+++ b/src/mesh/PhoneAPI.h
@@ -3,6 +3,7 @@
 #include "Observer.h"
 #include "mesh-pb-constants.h"
 #include "meshtastic/portnums.pb.h"
+#include 
 #include 
 #include 
 #include 
@@ -79,6 +80,10 @@ class PhoneAPI
 
     /// We temporarily keep the nodeInfo here between the call to available and getFromRadio
     meshtastic_NodeInfo nodeInfoForPhone = meshtastic_NodeInfo_init_default;
+    // Prefetched node info entries ready for immediate transmission to the phone.
+    std::deque nodeInfoQueue;
+    // Tunable size of the node info cache so we can keep BLE reads non-blocking.
+    static constexpr size_t kNodePrefetchDepth = 4;
 
     meshtastic_ToRadio toRadioScratch = {
         0}; // this is a static scratch object, any data must be copied elsewhere before returning
@@ -158,6 +163,8 @@ class PhoneAPI
 
     void releaseQueueStatusPhonePacket();
 
+    void prefetchNodeInfos();
+
     void releaseMqttClientProxyPhonePacket();
 
     void releaseClientNotification();
diff --git a/src/nimble/NimbleBluetooth.cpp b/src/nimble/NimbleBluetooth.cpp
index 60165b2c0..4b0c33609 100644
--- a/src/nimble/NimbleBluetooth.cpp
+++ b/src/nimble/NimbleBluetooth.cpp
@@ -48,6 +48,8 @@ class BluetoothPhoneAPI : public PhoneAPI, public concurrency::OSThread
     uint8_t queue_size = 0;
     uint8_t fromRadioBytes[meshtastic_FromRadio_size] = {0};
     size_t numBytes = 0;
+    bool hasChecked = false;
+    bool phoneWants = false;
 
   protected:
     virtual int32_t runOnce() override
@@ -60,7 +62,11 @@ class BluetoothPhoneAPI : public PhoneAPI, public concurrency::OSThread
             LOG_DEBUG("Queue_size %u", queue_size);
             queue_size = 0;
         }
-        // Note: phoneWants/hasChecked logic removed since onRead() handles getFromRadio() directly
+        if (!hasChecked && phoneWants) {
+            // Pull fresh data while we're outside of the NimBLE callback context.
+            numBytes = getFromRadio(fromRadioBytes);
+            hasChecked = true;
+        }
 
         // the run is triggered via NimbleBluetoothToRadioCallback and NimbleBluetoothFromRadioCallback
         return INT32_MAX;
@@ -117,6 +123,8 @@ class NimbleBluetoothToRadioCallback : public NimBLECharacteristicCallbacks
                 bluetoothPhoneAPI->queue_size++;
                 bluetoothPhoneAPI->setIntervalFromNow(0);
             }
+        } else {
+            LOG_DEBUG("Drop duplicate ToRadio packet (%u bytes)", val.length());
         }
     }
 };
@@ -129,17 +137,32 @@ class NimbleBluetoothFromRadioCallback : public NimBLECharacteristicCallbacks
     virtual void onRead(NimBLECharacteristic *pCharacteristic)
 #endif
     {
+        bluetoothPhoneAPI->phoneWants = true;
+        bluetoothPhoneAPI->setIntervalFromNow(0);
         std::lock_guard guard(bluetoothPhoneAPI->nimble_mutex);
 
-        // Get fresh data immediately when client reads
-        bluetoothPhoneAPI->numBytes = bluetoothPhoneAPI->getFromRadio(bluetoothPhoneAPI->fromRadioBytes);
+        if (!bluetoothPhoneAPI->hasChecked) {
+            // Fetch payload on demand; prefetch keeps this fast for the first read.
+            bluetoothPhoneAPI->numBytes = bluetoothPhoneAPI->getFromRadio(bluetoothPhoneAPI->fromRadioBytes);
+            bluetoothPhoneAPI->hasChecked = true;
+        }
 
-        // Set the characteristic value with whatever data we have
         pCharacteristic->setValue(bluetoothPhoneAPI->fromRadioBytes, bluetoothPhoneAPI->numBytes);
 
+        if (bluetoothPhoneAPI->numBytes != 0) {
+#ifdef NIMBLE_TWO
+            // Notify immediately so subscribed clients see the packet without an extra read.
+            pCharacteristic->notify(bluetoothPhoneAPI->fromRadioBytes, bluetoothPhoneAPI->numBytes, BLE_HS_CONN_HANDLE_NONE);
+#else
+            pCharacteristic->notify();
+#endif
+        }
+
         if (bluetoothPhoneAPI->numBytes != 0) // if we did send something, queue it up right away to reload
             bluetoothPhoneAPI->setIntervalFromNow(0);
         bluetoothPhoneAPI->numBytes = 0;
+        bluetoothPhoneAPI->hasChecked = false;
+        bluetoothPhoneAPI->phoneWants = false;
     }
 };
 
@@ -271,6 +294,8 @@ class NimbleBluetoothServerCallback : public NimBLEServerCallbacks
             bluetoothPhoneAPI->close();
             bluetoothPhoneAPI->numBytes = 0;
             bluetoothPhoneAPI->queue_size = 0;
+            bluetoothPhoneAPI->hasChecked = false;
+            bluetoothPhoneAPI->phoneWants = false;
         }
 
         // Clear the last ToRadio packet buffer to avoid rejecting first packet from new connection
@@ -278,6 +303,15 @@ class NimbleBluetoothServerCallback : public NimBLEServerCallbacks
 #ifdef NIMBLE_TWO
         // Restart Advertising
         ble->startAdvertising();
+#else
+        NimBLEAdvertising *pAdvertising = NimBLEDevice::getAdvertising();
+        if (!pAdvertising->start(0)) {
+            if (pAdvertising->isAdvertising()) {
+                LOG_DEBUG("BLE advertising already running");
+            } else {
+                LOG_ERROR("BLE failed to restart advertising");
+            }
+        }
 #endif
     }
 };
@@ -401,15 +435,18 @@ void NimbleBluetooth::setupService()
     // Define the characteristics that the app is looking for
     if (config.bluetooth.mode == meshtastic_Config_BluetoothConfig_PairingMode_NO_PIN) {
         ToRadioCharacteristic = bleService->createCharacteristic(TORADIO_UUID, NIMBLE_PROPERTY::WRITE);
-        FromRadioCharacteristic = bleService->createCharacteristic(FROMRADIO_UUID, NIMBLE_PROPERTY::READ);
+        // Allow notifications so phones can stream FromRadio without polling.
+        FromRadioCharacteristic =
+            bleService->createCharacteristic(FROMRADIO_UUID, NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::NOTIFY);
         fromNumCharacteristic = bleService->createCharacteristic(FROMNUM_UUID, NIMBLE_PROPERTY::NOTIFY | NIMBLE_PROPERTY::READ);
         logRadioCharacteristic =
             bleService->createCharacteristic(LOGRADIO_UUID, NIMBLE_PROPERTY::NOTIFY | NIMBLE_PROPERTY::READ, 512U);
     } else {
         ToRadioCharacteristic = bleService->createCharacteristic(
             TORADIO_UUID, NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::WRITE_AUTHEN | NIMBLE_PROPERTY::WRITE_ENC);
-        FromRadioCharacteristic = bleService->createCharacteristic(
-            FROMRADIO_UUID, NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::READ_AUTHEN | NIMBLE_PROPERTY::READ_ENC);
+        FromRadioCharacteristic =
+            bleService->createCharacteristic(FROMRADIO_UUID, NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::READ_AUTHEN |
+                                                                 NIMBLE_PROPERTY::READ_ENC | NIMBLE_PROPERTY::NOTIFY);
         fromNumCharacteristic =
             bleService->createCharacteristic(FROMNUM_UUID, NIMBLE_PROPERTY::NOTIFY | NIMBLE_PROPERTY::READ |
                                                                NIMBLE_PROPERTY::READ_AUTHEN | NIMBLE_PROPERTY::READ_ENC);

From 1212c2c11b32bfaa251df3ef76f7d5df893e0959 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
 <41898282+github-actions[bot]@users.noreply.github.com>
Date: Mon, 13 Oct 2025 06:32:21 -0500
Subject: [PATCH 484/691] Upgrade trunk (#8326)

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 2d3c0aa2d..637372700 100644
--- a/.trunk/trunk.yaml
+++ b/.trunk/trunk.yaml
@@ -9,7 +9,7 @@ plugins:
 lint:
   enabled:
     - checkov@3.2.477
-    - renovate@41.143.2
+    - renovate@41.144.1
     - prettier@3.6.2
     - trufflehog@3.90.8
     - yamllint@1.37.1

From fe6509a0f2f07661b38bccbcd765ed9612e493c1 Mon Sep 17 00:00:00 2001
From: Dirk Mueller 
Date: Mon, 13 Oct 2025 13:57:21 +0200
Subject: [PATCH 485/691] Avoid exceeding allocated buffers when doing MQTT
 proxying (#8320)

the topic length could be longer than 65 characters. similarly for the
payload.

Co-authored-by: Ben Meadors 
---
 src/mqtt/MQTT.cpp | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp
index 8ce352f14..33887557f 100644
--- a/src/mqtt/MQTT.cpp
+++ b/src/mqtt/MQTT.cpp
@@ -473,7 +473,9 @@ bool MQTT::publish(const char *topic, const uint8_t *payload, size_t length, boo
     if (moduleConfig.mqtt.proxy_to_client_enabled) {
         meshtastic_MqttClientProxyMessage *msg = mqttClientProxyMessagePool.allocZeroed();
         msg->which_payload_variant = meshtastic_MqttClientProxyMessage_data_tag;
-        strcpy(msg->topic, topic);
+        strlcpy(msg->topic, topic, sizeof(msg->topic));
+        if (length > sizeof(msg->payload_variant.data.bytes))
+            length = sizeof(msg->payload_variant.data.bytes);
         msg->payload_variant.data.size = length;
         memcpy(msg->payload_variant.data.bytes, payload, length);
         msg->retained = retained;

From 130833b5be168f0c78c25d32fe87f6b1496da11a Mon Sep 17 00:00:00 2001
From: Tom Fifield 
Date: Mon, 13 Oct 2025 23:50:57 +1100
Subject: [PATCH 486/691] Fix erroneous limiting of power in Ham Mode (#8322)

Ham Mode ignores region regulatory limits, so regardless of whether
we set a single TX_GAIN_LORA or an array with a non-linear PA,
we shouldn't limit the power.

Co-authored-by: Ben Meadors 
---
 src/mesh/RadioInterface.cpp | 23 ++++++++++++-----------
 1 file changed, 12 insertions(+), 11 deletions(-)

diff --git a/src/mesh/RadioInterface.cpp b/src/mesh/RadioInterface.cpp
index 88218e406..31ec5acc5 100644
--- a/src/mesh/RadioInterface.cpp
+++ b/src/mesh/RadioInterface.cpp
@@ -647,23 +647,24 @@ void RadioInterface::limitPower(int8_t loraMaxPower)
     }
 
 #ifndef NUM_PA_POINTS
-    if (TX_GAIN_LORA > 0) {
+    if (TX_GAIN_LORA > 0 && !devicestate.owner.is_licensed) {
         LOG_INFO("Requested Tx power: %d dBm; Device LoRa Tx gain: %d dB", power, TX_GAIN_LORA);
         power -= TX_GAIN_LORA;
     }
 #else
-    // we have an array of PA gain values.  Find the highest power setting that works.
-    const uint16_t tx_gain[NUM_PA_POINTS] = {TX_GAIN_LORA};
-    for (int radio_dbm = 0; radio_dbm < NUM_PA_POINTS; radio_dbm++) {
-        if (((radio_dbm + tx_gain[radio_dbm]) > power) ||
-            ((radio_dbm == (NUM_PA_POINTS - 1)) && ((radio_dbm + tx_gain[radio_dbm]) <= power))) {
-            // we've exceeded the power limit, or hit the max we can do
-            LOG_INFO("Requested Tx power: %d dBm; Device LoRa Tx gain: %d dB", power, tx_gain[radio_dbm]);
-            power -= tx_gain[radio_dbm];
-            break;
+    if (!devicestate.owner.is_licensed) {
+        // we have an array of PA gain values.  Find the highest power setting that works.
+        const uint16_t tx_gain[NUM_PA_POINTS] = {TX_GAIN_LORA};
+        for (int radio_dbm = 0; radio_dbm < NUM_PA_POINTS; radio_dbm++) {
+            if (((radio_dbm + tx_gain[radio_dbm]) > power) ||
+                ((radio_dbm == (NUM_PA_POINTS - 1)) && ((radio_dbm + tx_gain[radio_dbm]) <= power))) {
+                // we've exceeded the power limit, or hit the max we can do
+                LOG_INFO("Requested Tx power: %d dBm; Device LoRa Tx gain: %d dB", power, tx_gain[radio_dbm]);
+                power -= tx_gain[radio_dbm];
+                break;
+            }
         }
     }
-
 #endif
     if (power > loraMaxPower) // Clamp power to maximum defined level
         power = loraMaxPower;

From 9df5aa8c708d20546f145f594ebc4ff0a3298178 Mon Sep 17 00:00:00 2001
From: Steven Wu <31466039+steven52880@users.noreply.github.com>
Date: Mon, 13 Oct 2025 21:15:21 +0800
Subject: [PATCH 487/691] Fix can not detect battery status while using INA226
 (#8330)

Co-authored-by: Ben Meadors 
---
 src/Power.cpp | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/Power.cpp b/src/Power.cpp
index 39d47610b..1f4c341f0 100644
--- a/src/Power.cpp
+++ b/src/Power.cpp
@@ -562,6 +562,7 @@ class AnalogBatteryLevel : public HasBatteryLevel
                    config.power.device_battery_ina_address) {
             if (!ina226Sensor.isInitialized())
                 return ina226Sensor.runOnce() > 0;
+            return ina226Sensor.isRunning();
         } else if (nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_INA260].first ==
                    config.power.device_battery_ina_address) {
             if (!ina260Sensor.isInitialized())

From a71b47b5bb0dad7267bb5b97c4088741668a7cc9 Mon Sep 17 00:00:00 2001
From: Markus <974709+Links2004@users.noreply.github.com>
Date: Mon, 13 Oct 2025 18:09:33 +0200
Subject: [PATCH 488/691] rework sensor instantiation to saves memory by
 removing the static allocation (#8054)

* rework I2C sensor init

the goal is to only instantiate sensors that are pressend to save memory.
side effacts:
 - easyer sensor integration (less C&P code)
 - nodeTelemetrySensorsMap can be removed when all devices are migrated

* add missing ifdef

* refactor a bunch of more sensors

RAM -816
Flash -916

* fix build for t1000

* refactor more sensors

RAM -192
Flash -60

* improve error handling

Flash -112

* fix build

* fix build

* fix IndicatorSensor

* fix tracker-t1000-e build

not sure what magic is used but it works

* Apply suggestion from @Copilot

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Apply suggestion from @Copilot

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Apply suggestion from @Copilot

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update src/modules/Telemetry/Sensor/DFRobotGravitySensor.h

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Fix

---------

Co-authored-by: Ben Meadors 
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---
 src/detect/ScanI2CConsumer.cpp                |  16 +
 src/detect/ScanI2CConsumer.h                  |  13 +
 src/detect/ScanI2CTwoWire.cpp                 |   2 +-
 src/detect/ScanI2CTwoWire.h                   |   4 +-
 src/main.cpp                                  |  32 +-
 .../Telemetry/EnvironmentTelemetry.cpp        | 534 +++++-------------
 src/modules/Telemetry/EnvironmentTelemetry.h  |   9 +-
 src/modules/Telemetry/Sensor/AHT10.cpp        |  25 +-
 src/modules/Telemetry/Sensor/AHT10.h          |   5 +-
 src/modules/Telemetry/Sensor/BME280Sensor.cpp |  13 +-
 src/modules/Telemetry/Sensor/BME280Sensor.h   |   5 +-
 src/modules/Telemetry/Sensor/BME680Sensor.cpp |  19 +-
 src/modules/Telemetry/Sensor/BME680Sensor.h   |   3 +-
 src/modules/Telemetry/Sensor/BMP085Sensor.cpp |  13 +-
 src/modules/Telemetry/Sensor/BMP085Sensor.h   |   5 +-
 src/modules/Telemetry/Sensor/BMP280Sensor.cpp |  16 +-
 src/modules/Telemetry/Sensor/BMP280Sensor.h   |   5 +-
 src/modules/Telemetry/Sensor/BMP3XXSensor.cpp |  15 +-
 src/modules/Telemetry/Sensor/BMP3XXSensor.h   |   3 +-
 .../Telemetry/Sensor/CGRadSensSensor.cpp      |  14 +-
 .../Telemetry/Sensor/CGRadSensSensor.h        |   3 +-
 .../Telemetry/Sensor/DFRobotGravitySensor.cpp |  34 +-
 .../Telemetry/Sensor/DFRobotGravitySensor.h   |   8 +-
 .../Telemetry/Sensor/DFRobotLarkSensor.cpp    |  13 +-
 .../Telemetry/Sensor/DFRobotLarkSensor.h      |   5 +-
 src/modules/Telemetry/Sensor/DPS310Sensor.cpp |  13 +-
 src/modules/Telemetry/Sensor/DPS310Sensor.h   |   5 +-
 .../Telemetry/Sensor/IndicatorSensor.cpp      |   4 +-
 .../Telemetry/Sensor/IndicatorSensor.h        |   8 +-
 .../Telemetry/Sensor/LPS22HBSensor.cpp        |  16 +-
 src/modules/Telemetry/Sensor/LPS22HBSensor.h  |   5 +-
 .../Telemetry/Sensor/LTR390UVSensor.cpp       |  14 +-
 src/modules/Telemetry/Sensor/LTR390UVSensor.h |   5 +-
 .../Telemetry/Sensor/MCP9808Sensor.cpp        |  16 +-
 src/modules/Telemetry/Sensor/MCP9808Sensor.h  |   5 +-
 .../Telemetry/Sensor/MLX90632Sensor.cpp       |  13 +-
 src/modules/Telemetry/Sensor/MLX90632Sensor.h |   5 +-
 .../Telemetry/Sensor/NAU7802Sensor.cpp        |  13 +-
 src/modules/Telemetry/Sensor/NAU7802Sensor.h  |   3 +-
 .../Telemetry/Sensor/OPT3001Sensor.cpp        |  19 +-
 src/modules/Telemetry/Sensor/OPT3001Sensor.h  |   8 +-
 .../Telemetry/Sensor/PCT2075Sensor.cpp        |  14 +-
 src/modules/Telemetry/Sensor/PCT2075Sensor.h  |   5 +-
 .../Telemetry/Sensor/RAK12035Sensor.cpp       |  15 +-
 src/modules/Telemetry/Sensor/RAK12035Sensor.h |   9 +-
 .../Telemetry/Sensor/RCWL9620Sensor.cpp       |  12 +-
 src/modules/Telemetry/Sensor/RCWL9620Sensor.h |   3 +-
 src/modules/Telemetry/Sensor/SHT31Sensor.cpp  |  17 +-
 src/modules/Telemetry/Sensor/SHT31Sensor.h    |   5 +-
 src/modules/Telemetry/Sensor/SHT4XSensor.cpp  |  18 +-
 src/modules/Telemetry/Sensor/SHT4XSensor.h    |   5 +-
 src/modules/Telemetry/Sensor/SHTC3Sensor.cpp  |  14 +-
 src/modules/Telemetry/Sensor/SHTC3Sensor.h    |   5 +-
 src/modules/Telemetry/Sensor/T1000xSensor.cpp |  12 +-
 src/modules/Telemetry/Sensor/T1000xSensor.h   |   5 +-
 .../Telemetry/Sensor/TSL2561Sensor.cpp        |  19 +-
 src/modules/Telemetry/Sensor/TSL2561Sensor.h  |   5 +-
 .../Telemetry/Sensor/TSL2591Sensor.cpp        |  17 +-
 src/modules/Telemetry/Sensor/TSL2591Sensor.h  |   5 +-
 .../Telemetry/Sensor/TelemetrySensor.h        |  15 +-
 .../Telemetry/Sensor/VEML7700Sensor.cpp       |  13 +-
 src/modules/Telemetry/Sensor/VEML7700Sensor.h |   5 +-
 src/modules/Telemetry/Sensor/nullSensor.cpp   |   2 +-
 63 files changed, 428 insertions(+), 758 deletions(-)
 create mode 100644 src/detect/ScanI2CConsumer.cpp
 create mode 100644 src/detect/ScanI2CConsumer.h

diff --git a/src/detect/ScanI2CConsumer.cpp b/src/detect/ScanI2CConsumer.cpp
new file mode 100644
index 000000000..a70fa5398
--- /dev/null
+++ b/src/detect/ScanI2CConsumer.cpp
@@ -0,0 +1,16 @@
+#include "ScanI2CConsumer.h"
+#include 
+
+static std::forward_list ScanI2CConsumers;
+
+ScanI2CConsumer::ScanI2CConsumer()
+{
+    ScanI2CConsumers.push_front(this);
+}
+
+void ScanI2CCompleted(ScanI2C *i2cScanner)
+{
+    for (ScanI2CConsumer *consumer : ScanI2CConsumers) {
+        consumer->i2cScanFinished(i2cScanner);
+    }
+}
\ No newline at end of file
diff --git a/src/detect/ScanI2CConsumer.h b/src/detect/ScanI2CConsumer.h
new file mode 100644
index 000000000..fd97f7edc
--- /dev/null
+++ b/src/detect/ScanI2CConsumer.h
@@ -0,0 +1,13 @@
+#pragma once
+
+#include "ScanI2C.h"
+#include 
+
+class ScanI2CConsumer
+{
+  public:
+    ScanI2CConsumer();
+    virtual void i2cScanFinished(ScanI2C *i2cScanner) = 0;
+};
+
+void ScanI2CCompleted(ScanI2C *i2cScanner);
\ No newline at end of file
diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp
index d6daf7b7a..da2a57fee 100644
--- a/src/detect/ScanI2CTwoWire.cpp
+++ b/src/detect/ScanI2CTwoWire.cpp
@@ -581,7 +581,7 @@ void ScanI2CTwoWire::scanPort(I2CPort port)
     scanPort(port, nullptr, 0);
 }
 
-TwoWire *ScanI2CTwoWire::fetchI2CBus(ScanI2C::DeviceAddress address) const
+TwoWire *ScanI2CTwoWire::fetchI2CBus(ScanI2C::DeviceAddress address)
 {
     if (address.port == ScanI2C::I2CPort::WIRE) {
         return &Wire;
diff --git a/src/detect/ScanI2CTwoWire.h b/src/detect/ScanI2CTwoWire.h
index 6988091ad..c5b791920 100644
--- a/src/detect/ScanI2CTwoWire.h
+++ b/src/detect/ScanI2CTwoWire.h
@@ -23,12 +23,12 @@ class ScanI2CTwoWire : public ScanI2C
 
     ScanI2C::FoundDevice find(ScanI2C::DeviceType) const override;
 
-    TwoWire *fetchI2CBus(ScanI2C::DeviceAddress) const;
-
     bool exists(ScanI2C::DeviceType) const override;
 
     size_t countDevices() const override;
 
+    static TwoWire *fetchI2CBus(ScanI2C::DeviceAddress);
+
   protected:
     FoundDevice firstOfOrNONE(size_t, DeviceType[]) const override;
 
diff --git a/src/main.cpp b/src/main.cpp
index 029b8d708..bb97a1aa6 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -23,6 +23,7 @@
 #include "power.h"
 
 #if !MESHTASTIC_EXCLUDE_I2C
+#include "detect/ScanI2CConsumer.h"
 #include "detect/ScanI2CTwoWire.h"
 #include 
 #endif
@@ -718,46 +719,21 @@ void setup()
     LOG_DEBUG("acc_info = %i", acc_info.type);
 #endif
 
-    scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::BME_680, meshtastic_TelemetrySensorType_BME680);
-    scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::BME_280, meshtastic_TelemetrySensorType_BME280);
-    scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::BMP_280, meshtastic_TelemetrySensorType_BMP280);
-    scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::BMP_3XX, meshtastic_TelemetrySensorType_BMP3XX);
-    scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::BMP_085, meshtastic_TelemetrySensorType_BMP085);
     scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::INA260, meshtastic_TelemetrySensorType_INA260);
     scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::INA226, meshtastic_TelemetrySensorType_INA226);
     scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::INA219, meshtastic_TelemetrySensorType_INA219);
     scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::INA3221, meshtastic_TelemetrySensorType_INA3221);
     scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::MAX17048, meshtastic_TelemetrySensorType_MAX17048);
-    scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::MCP9808, meshtastic_TelemetrySensorType_MCP9808);
-    scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::SHT31, meshtastic_TelemetrySensorType_SHT31);
-    scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::SHTC3, meshtastic_TelemetrySensorType_SHTC3);
-    scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::LPS22HB, meshtastic_TelemetrySensorType_LPS22);
     scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::QMC6310, meshtastic_TelemetrySensorType_QMC6310);
     scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::QMI8658, meshtastic_TelemetrySensorType_QMI8658);
     scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::QMC5883L, meshtastic_TelemetrySensorType_QMC5883L);
     scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::HMC5883L, meshtastic_TelemetrySensorType_QMC5883L);
     scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::PMSA0031, meshtastic_TelemetrySensorType_PMSA003I);
-    scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::RCWL9620, meshtastic_TelemetrySensorType_RCWL9620);
-    scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::VEML7700, meshtastic_TelemetrySensorType_VEML7700);
-    scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::TSL2591, meshtastic_TelemetrySensorType_TSL25911FN);
-    scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::OPT3001, meshtastic_TelemetrySensorType_OPT3001);
-    scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::MLX90632, meshtastic_TelemetrySensorType_MLX90632);
     scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::MLX90614, meshtastic_TelemetrySensorType_MLX90614);
-    scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::SHT4X, meshtastic_TelemetrySensorType_SHT4X);
-    scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::AHT10, meshtastic_TelemetrySensorType_AHT10);
-    scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::DFROBOT_LARK, meshtastic_TelemetrySensorType_DFROBOT_LARK);
     scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::ICM20948, meshtastic_TelemetrySensorType_ICM20948);
     scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::MAX30102, meshtastic_TelemetrySensorType_MAX30102);
-    scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::CGRADSENS, meshtastic_TelemetrySensorType_RADSENS);
-    scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::DFROBOT_RAIN, meshtastic_TelemetrySensorType_DFROBOT_RAIN);
-    scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::LTR390UV, meshtastic_TelemetrySensorType_LTR390UV);
-    scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::DPS310, meshtastic_TelemetrySensorType_DPS310);
-    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
 
 #ifdef HAS_SDCARD
@@ -964,6 +940,12 @@ void setup()
     // Now that the mesh service is created, create any modules
     setupModules();
 
+#if !MESHTASTIC_EXCLUDE_I2C
+    // Inform modules about I2C devices
+    ScanI2CCompleted(i2cScanner.get());
+    i2cScanner.reset();
+#endif
+
     // warn the user about a low entropy key
     if (nodeDB->keyIsLowEntropy && !nodeDB->hasWarned) {
         LOG_WARN(LOW_ENTROPY_WARNING);
diff --git a/src/modules/Telemetry/EnvironmentTelemetry.cpp b/src/modules/Telemetry/EnvironmentTelemetry.cpp
index 8ac160f8b..95947560d 100644
--- a/src/modules/Telemetry/EnvironmentTelemetry.cpp
+++ b/src/modules/Telemetry/EnvironmentTelemetry.cpp
@@ -35,175 +35,103 @@ extern void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const c
 }
 #if __has_include()
 #include "Sensor/AHT10.h"
-AHT10Sensor aht10Sensor;
-#else
-NullSensor aht10Sensor;
 #endif
+
 #if __has_include()
 #include "Sensor/BME280Sensor.h"
-BME280Sensor bme280Sensor;
-#else
-NullSensor bmp280Sensor;
 #endif
 
 #if __has_include()
 #include "Sensor/BMP085Sensor.h"
-BMP085Sensor bmp085Sensor;
-#else
-NullSensor bmp085Sensor;
 #endif
 
 #if __has_include()
 #include "Sensor/BMP280Sensor.h"
-BMP280Sensor bmp280Sensor;
-#else
-NullSensor bme280Sensor;
 #endif
 
 #if __has_include()
 #include "Sensor/LTR390UVSensor.h"
-LTR390UVSensor ltr390uvSensor;
-#else
-NullSensor ltr390uvSensor;
 #endif
 
 #if __has_include()
 #include "Sensor/BME680Sensor.h"
-BME680Sensor bme680Sensor;
-#else
-NullSensor bme680Sensor;
 #endif
 
 #if __has_include()
 #include "Sensor/DPS310Sensor.h"
-DPS310Sensor dps310Sensor;
-#else
-NullSensor dps310Sensor;
 #endif
 
 #if __has_include()
 #include "Sensor/MCP9808Sensor.h"
-MCP9808Sensor mcp9808Sensor;
-#else
-NullSensor mcp9808Sensor;
 #endif
 
 #if __has_include()
 #include "Sensor/SHT31Sensor.h"
-SHT31Sensor sht31Sensor;
-#else
-NullSensor sht31Sensor;
 #endif
 
 #if __has_include()
 #include "Sensor/LPS22HBSensor.h"
-LPS22HBSensor lps22hbSensor;
-#else
-NullSensor lps22hbSensor;
 #endif
 
 #if __has_include()
 #include "Sensor/SHTC3Sensor.h"
-SHTC3Sensor shtc3Sensor;
-#else
-NullSensor shtc3Sensor;
 #endif
 
 #if __has_include("RAK12035_SoilMoisture.h") && defined(RAK_4631) && RAK_4631 == 1
 #include "Sensor/RAK12035Sensor.h"
-RAK12035Sensor rak12035Sensor;
-#else
-NullSensor rak12035Sensor;
 #endif
 
 #if __has_include()
 #include "Sensor/VEML7700Sensor.h"
-VEML7700Sensor veml7700Sensor;
-#else
-NullSensor veml7700Sensor;
 #endif
 
 #if __has_include()
 #include "Sensor/TSL2591Sensor.h"
-TSL2591Sensor tsl2591Sensor;
-#else
-NullSensor tsl2591Sensor;
 #endif
 
 #if __has_include()
 #include "Sensor/OPT3001Sensor.h"
-OPT3001Sensor opt3001Sensor;
-#else
-NullSensor opt3001Sensor;
 #endif
 
 #if __has_include()
 #include "Sensor/SHT4XSensor.h"
-SHT4XSensor sht4xSensor;
-#else
-NullSensor sht4xSensor;
 #endif
 
 #if __has_include()
 #include "Sensor/MLX90632Sensor.h"
-MLX90632Sensor mlx90632Sensor;
-#else
-NullSensor mlx90632Sensor;
 #endif
 
 #if __has_include()
 #include "Sensor/DFRobotLarkSensor.h"
-DFRobotLarkSensor dfRobotLarkSensor;
-#else
-NullSensor dfRobotLarkSensor;
 #endif
 
 #if __has_include()
 #include "Sensor/DFRobotGravitySensor.h"
-DFRobotGravitySensor dfRobotGravitySensor;
-#else
-NullSensor dfRobotGravitySensor;
 #endif
 
 #if __has_include()
 #include "Sensor/NAU7802Sensor.h"
-NAU7802Sensor nau7802Sensor;
-#else
-NullSensor nau7802Sensor;
 #endif
 
 #if __has_include()
 #include "Sensor/BMP3XXSensor.h"
-BMP3XXSensor bmp3xxSensor;
-#else
-NullSensor bmp3xxSensor;
 #endif
 
 #if __has_include()
 #include "Sensor/PCT2075Sensor.h"
-PCT2075Sensor pct2075Sensor;
-#else
-NullSensor pct2075Sensor;
 #endif
 
-RCWL9620Sensor rcwl9620Sensor;
-CGRadSensSensor cgRadSens;
-
 #endif
 #ifdef T1000X_SENSOR_EN
 #include "Sensor/T1000xSensor.h"
-T1000xSensor t1000xSensor;
 #endif
+
 #ifdef SENSECAP_INDICATOR
 #include "Sensor/IndicatorSensor.h"
-IndicatorSensor indicatorSensor;
 #endif
 
 #if __has_include()
 #include "Sensor/TSL2561Sensor.h"
-TSL2561Sensor tsl2561Sensor;
-#else
-NullSensor tsl2561Sensor;
 #endif
 
 #define FAILED_STATE_SENSOR_READ_MULTIPLIER 10
@@ -212,6 +140,132 @@ NullSensor tsl2561Sensor;
 #include "graphics/ScreenFonts.h"
 #include 
 
+#include 
+
+static std::forward_list sensors;
+
+template  void addSensor(ScanI2C *i2cScanner, ScanI2C::DeviceType type)
+{
+    ScanI2C::FoundDevice dev = i2cScanner->find(type);
+    if (dev.type != ScanI2C::DeviceType::NONE || type == ScanI2C::DeviceType::NONE) {
+        TelemetrySensor *sensor = new T();
+#if WIRE_INTERFACES_COUNT > 1
+        TwoWire *bus = ScanI2CTwoWire::fetchI2CBus(dev.address);
+        if (dev.address.port != ScanI2C::I2CPort::WIRE1 && sensor->onlyWire1()) {
+            // This sensor only works on Wire (Wire1 is not supported)
+            delete sensor;
+            return;
+        }
+#else
+        TwoWire *bus = &Wire;
+#endif
+        if (sensor->initDevice(bus, &dev)) {
+            sensors.push_front(sensor);
+            return;
+        }
+        // destroy sensor
+        delete sensor;
+    }
+}
+
+void EnvironmentTelemetryModule::i2cScanFinished(ScanI2C *i2cScanner)
+{
+    if (!moduleConfig.telemetry.environment_measurement_enabled && !ENVIRONMENTAL_TELEMETRY_MODULE_ENABLE) {
+        return;
+    }
+    LOG_INFO("Environment Telemetry adding I2C devices...");
+
+    // order by priority of metrics/values (low top, high bottom)
+
+#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
+#ifdef T1000X_SENSOR_EN
+    // Not a real I2C device
+    addSensor(i2cScanner, ScanI2C::DeviceType::NONE);
+#else
+#ifdef SENSECAP_INDICATOR
+    // Not a real I2C device, uses UART
+    addSensor(i2cScanner, ScanI2C::DeviceType::NONE);
+#endif
+    addSensor(i2cScanner, ScanI2C::DeviceType::RCWL9620);
+    addSensor(i2cScanner, ScanI2C::DeviceType::CGRADSENS);
+#endif
+#endif
+
+#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR_EXTERNAL
+#if __has_include()
+    addSensor(i2cScanner, ScanI2C::DeviceType::DFROBOT_LARK);
+#endif
+#if __has_include()
+    addSensor(i2cScanner, ScanI2C::DeviceType::DFROBOT_RAIN);
+#endif
+#if __has_include()
+    addSensor(i2cScanner, ScanI2C::DeviceType::AHT10);
+#endif
+#if __has_include()
+    addSensor(i2cScanner, ScanI2C::DeviceType::BMP_085);
+#endif
+#if __has_include()
+    addSensor(i2cScanner, ScanI2C::DeviceType::BME_280);
+#endif
+#if __has_include()
+    addSensor(i2cScanner, ScanI2C::DeviceType::LTR390UV);
+#endif
+#if __has_include()
+    addSensor(i2cScanner, ScanI2C::DeviceType::BME_680);
+#endif
+#if __has_include()
+    addSensor(i2cScanner, ScanI2C::DeviceType::BMP_280);
+#endif
+#if __has_include()
+    addSensor(i2cScanner, ScanI2C::DeviceType::DPS310);
+#endif
+#if __has_include()
+    addSensor(i2cScanner, ScanI2C::DeviceType::MCP9808);
+#endif
+#if __has_include()
+    addSensor(i2cScanner, ScanI2C::DeviceType::SHT31);
+#endif
+#if __has_include()
+    addSensor(i2cScanner, ScanI2C::DeviceType::LPS22HB);
+#endif
+#if __has_include()
+    addSensor(i2cScanner, ScanI2C::DeviceType::SHTC3);
+#endif
+#if __has_include("RAK12035_SoilMoisture.h") && defined(RAK_4631) && RAK_4631 == 1
+    addSensor(i2cScanner, ScanI2C::DeviceType::RAK12035);
+#endif
+#if __has_include()
+    addSensor(i2cScanner, ScanI2C::DeviceType::VEML7700);
+#endif
+#if __has_include()
+    addSensor(i2cScanner, ScanI2C::DeviceType::TSL2591);
+#endif
+#if __has_include()
+    addSensor(i2cScanner, ScanI2C::DeviceType::OPT3001);
+#endif
+#if __has_include()
+    addSensor(i2cScanner, ScanI2C::DeviceType::SHT4X);
+#endif
+#if __has_include()
+    addSensor(i2cScanner, ScanI2C::DeviceType::MLX90632);
+#endif
+
+#if __has_include()
+    addSensor(i2cScanner, ScanI2C::DeviceType::BMP_3XX);
+#endif
+#if __has_include()
+    addSensor(i2cScanner, ScanI2C::DeviceType::PCT2075);
+#endif
+#if __has_include()
+    addSensor(i2cScanner, ScanI2C::DeviceType::TSL2561);
+#endif
+#if __has_include()
+    addSensor(i2cScanner, ScanI2C::DeviceType::NAU7802);
+#endif
+
+#endif
+}
+
 int32_t EnvironmentTelemetryModule::runOnce()
 {
     if (sleepOnNextExecution == true) {
@@ -244,81 +298,27 @@ int32_t EnvironmentTelemetryModule::runOnce()
 
         if (moduleConfig.telemetry.environment_measurement_enabled || ENVIRONMENTAL_TELEMETRY_MODULE_ENABLE) {
             LOG_INFO("Environment Telemetry: init");
-#ifdef SENSECAP_INDICATOR
-            result = indicatorSensor.runOnce();
-#endif
+
+            // check if we have at least one sensor
+            if (!sensors.empty()) {
+                result = DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS;
+            }
+
 #ifdef T1000X_SENSOR_EN
-            result = t1000xSensor.runOnce();
 #elif !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR_EXTERNAL
-            if (dfRobotLarkSensor.hasSensor())
-                result = dfRobotLarkSensor.runOnce();
-            if (dfRobotGravitySensor.hasSensor())
-                result = dfRobotGravitySensor.runOnce();
-            if (bmp085Sensor.hasSensor())
-                result = bmp085Sensor.runOnce();
-#if __has_include()
-            if (bmp280Sensor.hasSensor())
-                result = bmp280Sensor.runOnce();
-#endif
-            if (bme280Sensor.hasSensor())
-                result = bme280Sensor.runOnce();
-            if (ltr390uvSensor.hasSensor())
-                result = ltr390uvSensor.runOnce();
-            if (bmp3xxSensor.hasSensor())
-                result = bmp3xxSensor.runOnce();
-            if (bme680Sensor.hasSensor())
-                result = bme680Sensor.runOnce();
-            if (dps310Sensor.hasSensor())
-                result = dps310Sensor.runOnce();
-            if (mcp9808Sensor.hasSensor())
-                result = mcp9808Sensor.runOnce();
-            if (shtc3Sensor.hasSensor())
-                result = shtc3Sensor.runOnce();
-            if (lps22hbSensor.hasSensor())
-                result = lps22hbSensor.runOnce();
-            if (sht31Sensor.hasSensor())
-                result = sht31Sensor.runOnce();
-            if (sht4xSensor.hasSensor())
-                result = sht4xSensor.runOnce();
             if (ina219Sensor.hasSensor())
                 result = ina219Sensor.runOnce();
             if (ina260Sensor.hasSensor())
                 result = ina260Sensor.runOnce();
             if (ina3221Sensor.hasSensor())
                 result = ina3221Sensor.runOnce();
-            if (veml7700Sensor.hasSensor())
-                result = veml7700Sensor.runOnce();
-            if (tsl2591Sensor.hasSensor())
-                result = tsl2591Sensor.runOnce();
-            if (opt3001Sensor.hasSensor())
-                result = opt3001Sensor.runOnce();
-            if (rcwl9620Sensor.hasSensor())
-                result = rcwl9620Sensor.runOnce();
-            if (aht10Sensor.hasSensor())
-                result = aht10Sensor.runOnce();
-            if (mlx90632Sensor.hasSensor())
-                result = mlx90632Sensor.runOnce();
-            if (nau7802Sensor.hasSensor())
-                result = nau7802Sensor.runOnce();
             if (max17048Sensor.hasSensor())
                 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
                 // sensormap here.
 #ifdef HAS_RAKPROT
-
             result = rak9154Sensor.runOnce();
 #endif
-#if __has_include("RAK12035_SoilMoisture.h") && defined(RAK_4631) && RAK_4631 == 1
-            if (rak12035Sensor.hasSensor()) {
-                result = rak12035Sensor.runOnce();
-            }
-#endif
 #endif
         }
         // it's possible to have this module enabled, only for displaying values on the screen.
@@ -328,11 +328,13 @@ int32_t EnvironmentTelemetryModule::runOnce()
         // if we somehow got to a second run of this module with measurement disabled, then just wait forever
         if (!moduleConfig.telemetry.environment_measurement_enabled && !ENVIRONMENTAL_TELEMETRY_MODULE_ENABLE) {
             return disable();
-        } else {
-#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR_EXTERNAL
-            if (bme680Sensor.hasSensor())
-                result = bme680Sensor.runTrigger();
-#endif
+        }
+
+        for (TelemetrySensor *sensor : sensors) {
+            uint32_t delay = sensor->runOnce();
+            if (delay < result) {
+                result = delay;
+            }
         }
 
         if (((lastSentToMesh == 0) ||
@@ -550,72 +552,12 @@ bool EnvironmentTelemetryModule::getEnvironmentTelemetry(meshtastic_Telemetry *m
     m->which_variant = meshtastic_Telemetry_environment_metrics_tag;
     m->variant.environment_metrics = meshtastic_EnvironmentMetrics_init_zero;
 
-#ifdef SENSECAP_INDICATOR
-    valid = valid && indicatorSensor.getMetrics(m);
-    hasSensor = true;
-#endif
-#ifdef T1000X_SENSOR_EN // add by WayenWeng
-    valid = valid && t1000xSensor.getMetrics(m);
-    hasSensor = true;
-#else
-    if (dfRobotLarkSensor.hasSensor()) {
-        valid = valid && dfRobotLarkSensor.getMetrics(m);
-        hasSensor = true;
-    }
-    if (dfRobotGravitySensor.hasSensor()) {
-        valid = valid && dfRobotGravitySensor.getMetrics(m);
-        hasSensor = true;
-    }
-    if (sht31Sensor.hasSensor()) {
-        valid = valid && sht31Sensor.getMetrics(m);
-        hasSensor = true;
-    }
-    if (sht4xSensor.hasSensor()) {
-        valid = valid && sht4xSensor.getMetrics(m);
-        hasSensor = true;
-    }
-    if (lps22hbSensor.hasSensor()) {
-        valid = valid && lps22hbSensor.getMetrics(m);
-        hasSensor = true;
-    }
-    if (shtc3Sensor.hasSensor()) {
-        valid = valid && shtc3Sensor.getMetrics(m);
-        hasSensor = true;
-    }
-    if (bmp085Sensor.hasSensor()) {
-        valid = valid && bmp085Sensor.getMetrics(m);
-        hasSensor = true;
-    }
-#if __has_include()
-    if (bmp280Sensor.hasSensor()) {
-        valid = valid && bmp280Sensor.getMetrics(m);
-        hasSensor = true;
-    }
-#endif
-    if (bme280Sensor.hasSensor()) {
-        valid = valid && bme280Sensor.getMetrics(m);
-        hasSensor = true;
-    }
-    if (ltr390uvSensor.hasSensor()) {
-        valid = valid && ltr390uvSensor.getMetrics(m);
-        hasSensor = true;
-    }
-    if (bmp3xxSensor.hasSensor()) {
-        valid = valid && bmp3xxSensor.getMetrics(m);
-        hasSensor = true;
-    }
-    if (bme680Sensor.hasSensor()) {
-        valid = valid && bme680Sensor.getMetrics(m);
-        hasSensor = true;
-    }
-    if (dps310Sensor.hasSensor()) {
-        valid = valid && dps310Sensor.getMetrics(m);
-        hasSensor = true;
-    }
-    if (mcp9808Sensor.hasSensor()) {
-        valid = valid && mcp9808Sensor.getMetrics(m);
+    for (TelemetrySensor *sensor : sensors) {
+        valid = valid && sensor->getMetrics(m);
         hasSensor = true;
     }
+
+#ifndef T1000X_SENSOR_EN
     if (ina219Sensor.hasSensor()) {
         valid = valid && ina219Sensor.getMetrics(m);
         hasSensor = true;
@@ -628,78 +570,14 @@ bool EnvironmentTelemetryModule::getEnvironmentTelemetry(meshtastic_Telemetry *m
         valid = valid && ina3221Sensor.getMetrics(m);
         hasSensor = true;
     }
-    if (veml7700Sensor.hasSensor()) {
-        valid = valid && veml7700Sensor.getMetrics(m);
-        hasSensor = true;
-    }
-    if (tsl2591Sensor.hasSensor()) {
-        valid = valid && tsl2591Sensor.getMetrics(m);
-        hasSensor = true;
-    }
-    if (opt3001Sensor.hasSensor()) {
-        valid = valid && opt3001Sensor.getMetrics(m);
-        hasSensor = true;
-    }
-    if (mlx90632Sensor.hasSensor()) {
-        valid = valid && mlx90632Sensor.getMetrics(m);
-        hasSensor = true;
-    }
-    if (rcwl9620Sensor.hasSensor()) {
-        valid = valid && rcwl9620Sensor.getMetrics(m);
-        hasSensor = true;
-    }
-    if (nau7802Sensor.hasSensor()) {
-        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);
-            hasSensor = true;
-        } else if (bmp280Sensor.hasSensor()) {
-            // prefer bmp280 temp if both sensors are present, fetch only humidity
-            meshtastic_Telemetry m_ahtx = meshtastic_Telemetry_init_zero;
-            LOG_INFO("AHTX0+BMP280 module detected: using temp from BMP280 and humy from AHTX0");
-            aht10Sensor.getMetrics(&m_ahtx);
-            m->variant.environment_metrics.relative_humidity = m_ahtx.variant.environment_metrics.relative_humidity;
-            m->variant.environment_metrics.has_relative_humidity = m_ahtx.variant.environment_metrics.has_relative_humidity;
-        } else {
-            // prefer bmp3xx temp if both sensors are present, fetch only humidity
-            meshtastic_Telemetry m_ahtx = meshtastic_Telemetry_init_zero;
-            LOG_INFO("AHTX0+BMP3XX module detected: using temp from BMP3XX and humy from AHTX0");
-            aht10Sensor.getMetrics(&m_ahtx);
-            m->variant.environment_metrics.relative_humidity = m_ahtx.variant.environment_metrics.relative_humidity;
-            m->variant.environment_metrics.has_relative_humidity = m_ahtx.variant.environment_metrics.has_relative_humidity;
-        }
-    }
     if (max17048Sensor.hasSensor()) {
         valid = valid && max17048Sensor.getMetrics(m);
         hasSensor = true;
     }
-    if (cgRadSens.hasSensor()) {
-        valid = valid && cgRadSens.getMetrics(m);
-        hasSensor = true;
-    }
-    if (pct2075Sensor.hasSensor()) {
-        valid = valid && pct2075Sensor.getMetrics(m);
-        hasSensor = true;
-    }
+#endif
 #ifdef HAS_RAKPROT
     valid = valid && rak9154Sensor.getMetrics(m);
     hasSensor = true;
-#endif
-#if __has_include("RAK12035_SoilMoisture.h") && defined(RAK_4631) &&                                                             \
-                  RAK_4631 ==                                                                                                    \
-                      1 // Not really needed, but may as well just skip at a lower level it if no library or not a RAK_4631
-    if (rak12035Sensor.hasSensor()) {
-        valid = valid && rak12035Sensor.getMetrics(m);
-        hasSensor = true;
-    }
-#endif
 #endif
     return valid && hasSensor;
 }
@@ -737,11 +615,8 @@ bool EnvironmentTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly)
     meshtastic_Telemetry m = meshtastic_Telemetry_init_zero;
     m.which_variant = meshtastic_Telemetry_environment_metrics_tag;
     m.time = getTime();
-#ifdef T1000X_SENSOR_EN
-    if (t1000xSensor.getMetrics(&m)) {
-#else
+
     if (getEnvironmentTelemetry(&m)) {
-#endif
         LOG_INFO("Send: barometric_pressure=%f, current=%f, gas_resistance=%f, relative_humidity=%f, temperature=%f",
                  m.variant.environment_metrics.barometric_pressure, m.variant.environment_metrics.current,
                  m.variant.environment_metrics.gas_resistance, m.variant.environment_metrics.relative_humidity,
@@ -803,71 +678,13 @@ AdminMessageHandleResult EnvironmentTelemetryModule::handleAdminMessageForModule
 {
     AdminMessageHandleResult result = AdminMessageHandleResult::NOT_HANDLED;
 #if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR_EXTERNAL
-    if (dfRobotLarkSensor.hasSensor()) {
-        result = dfRobotLarkSensor.handleAdminMessage(mp, request, response);
-        if (result != AdminMessageHandleResult::NOT_HANDLED)
-            return result;
-    }
-    if (dfRobotGravitySensor.hasSensor()) {
-        result = dfRobotGravitySensor.handleAdminMessage(mp, request, response);
-        if (result != AdminMessageHandleResult::NOT_HANDLED)
-            return result;
-    }
-    if (sht31Sensor.hasSensor()) {
-        result = sht31Sensor.handleAdminMessage(mp, request, response);
-        if (result != AdminMessageHandleResult::NOT_HANDLED)
-            return result;
-    }
-    if (lps22hbSensor.hasSensor()) {
-        result = lps22hbSensor.handleAdminMessage(mp, request, response);
-        if (result != AdminMessageHandleResult::NOT_HANDLED)
-            return result;
-    }
-    if (shtc3Sensor.hasSensor()) {
-        result = shtc3Sensor.handleAdminMessage(mp, request, response);
-        if (result != AdminMessageHandleResult::NOT_HANDLED)
-            return result;
-    }
-    if (bmp085Sensor.hasSensor()) {
-        result = bmp085Sensor.handleAdminMessage(mp, request, response);
-        if (result != AdminMessageHandleResult::NOT_HANDLED)
-            return result;
-    }
-    if (bmp280Sensor.hasSensor()) {
-        result = bmp280Sensor.handleAdminMessage(mp, request, response);
-        if (result != AdminMessageHandleResult::NOT_HANDLED)
-            return result;
-    }
-    if (bme280Sensor.hasSensor()) {
-        result = bme280Sensor.handleAdminMessage(mp, request, response);
-        if (result != AdminMessageHandleResult::NOT_HANDLED)
-            return result;
-    }
-    if (ltr390uvSensor.hasSensor()) {
-        result = ltr390uvSensor.handleAdminMessage(mp, request, response);
-        if (result != AdminMessageHandleResult::NOT_HANDLED)
-            return result;
-    }
-    if (bmp3xxSensor.hasSensor()) {
-        result = bmp3xxSensor.handleAdminMessage(mp, request, response);
-        if (result != AdminMessageHandleResult::NOT_HANDLED)
-            return result;
-    }
-    if (bme680Sensor.hasSensor()) {
-        result = bme680Sensor.handleAdminMessage(mp, request, response);
-        if (result != AdminMessageHandleResult::NOT_HANDLED)
-            return result;
-    }
-    if (dps310Sensor.hasSensor()) {
-        result = dps310Sensor.handleAdminMessage(mp, request, response);
-        if (result != AdminMessageHandleResult::NOT_HANDLED)
-            return result;
-    }
-    if (mcp9808Sensor.hasSensor()) {
-        result = mcp9808Sensor.handleAdminMessage(mp, request, response);
+
+    for (TelemetrySensor *sensor : sensors) {
+        result = sensor->handleAdminMessage(mp, request, response);
         if (result != AdminMessageHandleResult::NOT_HANDLED)
             return result;
     }
+
     if (ina219Sensor.hasSensor()) {
         result = ina219Sensor.handleAdminMessage(mp, request, response);
         if (result != AdminMessageHandleResult::NOT_HANDLED)
@@ -883,60 +700,11 @@ AdminMessageHandleResult EnvironmentTelemetryModule::handleAdminMessageForModule
         if (result != AdminMessageHandleResult::NOT_HANDLED)
             return result;
     }
-    if (veml7700Sensor.hasSensor()) {
-        result = veml7700Sensor.handleAdminMessage(mp, request, response);
-        if (result != AdminMessageHandleResult::NOT_HANDLED)
-            return result;
-    }
-    if (tsl2591Sensor.hasSensor()) {
-        result = tsl2591Sensor.handleAdminMessage(mp, request, response);
-        if (result != AdminMessageHandleResult::NOT_HANDLED)
-            return result;
-    }
-    if (opt3001Sensor.hasSensor()) {
-        result = opt3001Sensor.handleAdminMessage(mp, request, response);
-        if (result != AdminMessageHandleResult::NOT_HANDLED)
-            return result;
-    }
-    if (mlx90632Sensor.hasSensor()) {
-        result = mlx90632Sensor.handleAdminMessage(mp, request, response);
-        if (result != AdminMessageHandleResult::NOT_HANDLED)
-            return result;
-    }
-    if (rcwl9620Sensor.hasSensor()) {
-        result = rcwl9620Sensor.handleAdminMessage(mp, request, response);
-        if (result != AdminMessageHandleResult::NOT_HANDLED)
-            return result;
-    }
-    if (nau7802Sensor.hasSensor()) {
-        result = nau7802Sensor.handleAdminMessage(mp, request, response);
-        if (result != AdminMessageHandleResult::NOT_HANDLED)
-            return result;
-    }
-    if (aht10Sensor.hasSensor()) {
-        result = aht10Sensor.handleAdminMessage(mp, request, response);
-        if (result != AdminMessageHandleResult::NOT_HANDLED)
-            return result;
-    }
     if (max17048Sensor.hasSensor()) {
         result = max17048Sensor.handleAdminMessage(mp, request, response);
         if (result != AdminMessageHandleResult::NOT_HANDLED)
             return result;
     }
-    if (cgRadSens.hasSensor()) {
-        result = cgRadSens.handleAdminMessage(mp, request, response);
-        if (result != AdminMessageHandleResult::NOT_HANDLED)
-            return result;
-    }
-#if __has_include("RAK12035_SoilMoisture.h") && defined(RAK_4631) &&                                                             \
-                  RAK_4631 ==                                                                                                    \
-                      1 // Not really needed, but may as well just skip it at a lower level if no library or not a RAK_4631
-    if (rak12035Sensor.hasSensor()) {
-        result = rak12035Sensor.handleAdminMessage(mp, request, response);
-        if (result != AdminMessageHandleResult::NOT_HANDLED)
-            return result;
-    }
-#endif
 #endif
     return result;
 }
diff --git a/src/modules/Telemetry/EnvironmentTelemetry.h b/src/modules/Telemetry/EnvironmentTelemetry.h
index d70c063fc..6e4ce82e7 100644
--- a/src/modules/Telemetry/EnvironmentTelemetry.h
+++ b/src/modules/Telemetry/EnvironmentTelemetry.h
@@ -11,10 +11,13 @@
 #include "../mesh/generated/meshtastic/telemetry.pb.h"
 #include "NodeDB.h"
 #include "ProtobufModule.h"
+#include "detect/ScanI2CConsumer.h"
 #include 
 #include 
 
-class EnvironmentTelemetryModule : private concurrency::OSThread, public ProtobufModule
+class EnvironmentTelemetryModule : private concurrency::OSThread,
+                                   public ScanI2CConsumer,
+                                   public ProtobufModule
 {
     CallbackObserver nodeStatusObserver =
         CallbackObserver(this,
@@ -22,7 +25,7 @@ class EnvironmentTelemetryModule : private concurrency::OSThread, public Protobu
 
   public:
     EnvironmentTelemetryModule()
-        : concurrency::OSThread("EnvironmentTelemetry"),
+        : concurrency::OSThread("EnvironmentTelemetry"), ScanI2CConsumer(),
           ProtobufModule("EnvironmentTelemetry", meshtastic_PortNum_TELEMETRY_APP, &meshtastic_Telemetry_msg)
     {
         lastMeasurementPacket = nullptr;
@@ -56,6 +59,8 @@ class EnvironmentTelemetryModule : private concurrency::OSThread, public Protobu
                                                                  meshtastic_AdminMessage *request,
                                                                  meshtastic_AdminMessage *response) override;
 
+    void i2cScanFinished(ScanI2C *i2cScanner);
+
   private:
     bool firstTime = 1;
     meshtastic_MeshPacket *lastMeasurementPacket;
diff --git a/src/modules/Telemetry/Sensor/AHT10.cpp b/src/modules/Telemetry/Sensor/AHT10.cpp
index 35934533b..52fdc05c0 100644
--- a/src/modules/Telemetry/Sensor/AHT10.cpp
+++ b/src/modules/Telemetry/Sensor/AHT10.cpp
@@ -15,20 +15,16 @@
 
 AHT10Sensor::AHT10Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_AHT10, "AHT10") {}
 
-int32_t AHT10Sensor::runOnce()
+bool AHT10Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev)
 {
     LOG_INFO("Init sensor: %s", sensorName);
-    if (!hasSensor()) {
-        return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS;
-    }
     aht10 = Adafruit_AHTX0();
-    status = aht10.begin(nodeTelemetrySensorsMap[sensorType].second, 0, nodeTelemetrySensorsMap[sensorType].first);
+    status = aht10.begin(bus, 0, dev->address.address);
 
-    return initI2CSensor();
+    initI2CSensor();
+    return status;
 }
 
-void AHT10Sensor::setup() {}
-
 bool AHT10Sensor::getMetrics(meshtastic_Telemetry *measurement)
 {
     LOG_DEBUG("AHT10 getMetrics");
@@ -36,11 +32,16 @@ bool AHT10Sensor::getMetrics(meshtastic_Telemetry *measurement)
     sensors_event_t humidity, temp;
     aht10.getEvent(&humidity, &temp);
 
-    measurement->variant.environment_metrics.has_temperature = true;
-    measurement->variant.environment_metrics.has_relative_humidity = true;
+    // prefer other sensors like bmp280, bmp3xx
+    if (!measurement->variant.environment_metrics.has_temperature) {
+        measurement->variant.environment_metrics.has_temperature = true;
+        measurement->variant.environment_metrics.temperature = temp.temperature;
+    }
 
-    measurement->variant.environment_metrics.temperature = temp.temperature;
-    measurement->variant.environment_metrics.relative_humidity = humidity.relative_humidity;
+    if (!measurement->variant.environment_metrics.has_relative_humidity) {
+        measurement->variant.environment_metrics.has_relative_humidity = true;
+        measurement->variant.environment_metrics.relative_humidity = humidity.relative_humidity;
+    }
 
     return true;
 }
diff --git a/src/modules/Telemetry/Sensor/AHT10.h b/src/modules/Telemetry/Sensor/AHT10.h
index a6fa19952..ab3f5806c 100644
--- a/src/modules/Telemetry/Sensor/AHT10.h
+++ b/src/modules/Telemetry/Sensor/AHT10.h
@@ -15,13 +15,10 @@ class AHT10Sensor : public TelemetrySensor
   private:
     Adafruit_AHTX0 aht10;
 
-  protected:
-    virtual void setup() override;
-
   public:
     AHT10Sensor();
-    virtual int32_t runOnce() override;
     virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
+    virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override;
 };
 
 #endif
diff --git a/src/modules/Telemetry/Sensor/BME280Sensor.cpp b/src/modules/Telemetry/Sensor/BME280Sensor.cpp
index d7b0a8a38..779b2e603 100644
--- a/src/modules/Telemetry/Sensor/BME280Sensor.cpp
+++ b/src/modules/Telemetry/Sensor/BME280Sensor.cpp
@@ -10,13 +10,13 @@
 
 BME280Sensor::BME280Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_BME280, "BME280") {}
 
-int32_t BME280Sensor::runOnce()
+bool BME280Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev)
 {
     LOG_INFO("Init sensor: %s", sensorName);
-    if (!hasSensor()) {
-        return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS;
+    status = bme280.begin(dev->address.address, bus);
+    if (!status) {
+        return status;
     }
-    status = bme280.begin(nodeTelemetrySensorsMap[sensorType].first, nodeTelemetrySensorsMap[sensorType].second);
 
     bme280.setSampling(Adafruit_BME280::MODE_FORCED,
                        Adafruit_BME280::SAMPLING_X1, // Temp. oversampling
@@ -24,11 +24,10 @@ int32_t BME280Sensor::runOnce()
                        Adafruit_BME280::SAMPLING_X1, // Humidity oversampling
                        Adafruit_BME280::FILTER_OFF, Adafruit_BME280::STANDBY_MS_1000);
 
-    return initI2CSensor();
+    initI2CSensor();
+    return status;
 }
 
-void BME280Sensor::setup() {}
-
 bool BME280Sensor::getMetrics(meshtastic_Telemetry *measurement)
 {
     measurement->variant.environment_metrics.has_temperature = true;
diff --git a/src/modules/Telemetry/Sensor/BME280Sensor.h b/src/modules/Telemetry/Sensor/BME280Sensor.h
index d1e21c8d5..fadae46cd 100644
--- a/src/modules/Telemetry/Sensor/BME280Sensor.h
+++ b/src/modules/Telemetry/Sensor/BME280Sensor.h
@@ -11,13 +11,10 @@ class BME280Sensor : public TelemetrySensor
   private:
     Adafruit_BME280 bme280;
 
-  protected:
-    virtual void setup() override;
-
   public:
     BME280Sensor();
-    virtual int32_t runOnce() override;
     virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
+    virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override;
 };
 
 #endif
\ No newline at end of file
diff --git a/src/modules/Telemetry/Sensor/BME680Sensor.cpp b/src/modules/Telemetry/Sensor/BME680Sensor.cpp
index fce029deb..95f3dc5f0 100644
--- a/src/modules/Telemetry/Sensor/BME680Sensor.cpp
+++ b/src/modules/Telemetry/Sensor/BME680Sensor.cpp
@@ -10,7 +10,7 @@
 
 BME680Sensor::BME680Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_BME680, "BME680") {}
 
-int32_t BME680Sensor::runTrigger()
+int32_t BME680Sensor::runOnce()
 {
     if (!bme680.run()) {
         checkStatus("runTrigger");
@@ -18,13 +18,10 @@ int32_t BME680Sensor::runTrigger()
     return 35;
 }
 
-int32_t BME680Sensor::runOnce()
+bool BME680Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev)
 {
-
-    if (!hasSensor()) {
-        return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS;
-    }
-    if (!bme680.begin(nodeTelemetrySensorsMap[sensorType].first, *nodeTelemetrySensorsMap[sensorType].second))
+    status = 0;
+    if (!bme680.begin(dev->address.address, *bus))
         checkStatus("begin");
 
     if (bme680.status == BSEC_OK) {
@@ -40,17 +37,15 @@ int32_t BME680Sensor::runOnce()
         }
         LOG_INFO("Init sensor: %s with the BSEC Library version %d.%d.%d.%d ", sensorName, bme680.version.major,
                  bme680.version.minor, bme680.version.major_bugfix, bme680.version.minor_bugfix);
-    } else {
-        status = 0;
     }
+
     if (status == 0)
         LOG_DEBUG("BME680Sensor::runOnce: bme680.status %d", bme680.status);
 
-    return initI2CSensor();
+    initI2CSensor();
+    return status;
 }
 
-void BME680Sensor::setup() {}
-
 bool BME680Sensor::getMetrics(meshtastic_Telemetry *measurement)
 {
     if (bme680.getData(BSEC_OUTPUT_RAW_PRESSURE).signal == 0)
diff --git a/src/modules/Telemetry/Sensor/BME680Sensor.h b/src/modules/Telemetry/Sensor/BME680Sensor.h
index ce1fa4f3b..f4ead95f7 100644
--- a/src/modules/Telemetry/Sensor/BME680Sensor.h
+++ b/src/modules/Telemetry/Sensor/BME680Sensor.h
@@ -18,7 +18,6 @@ class BME680Sensor : public TelemetrySensor
     Bsec2 bme680;
 
   protected:
-    virtual void setup() override;
     const char *bsecConfigFileName = "/prefs/bsec.dat";
     uint8_t bsecState[BSEC_MAX_STATE_BLOB_SIZE] = {0};
     uint8_t accuracy = 0;
@@ -38,9 +37,9 @@ class BME680Sensor : public TelemetrySensor
 
   public:
     BME680Sensor();
-    int32_t runTrigger();
     virtual int32_t runOnce() override;
     virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
+    virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override;
 };
 
 #endif
\ No newline at end of file
diff --git a/src/modules/Telemetry/Sensor/BMP085Sensor.cpp b/src/modules/Telemetry/Sensor/BMP085Sensor.cpp
index 8087eb4b9..1fb2ecc28 100644
--- a/src/modules/Telemetry/Sensor/BMP085Sensor.cpp
+++ b/src/modules/Telemetry/Sensor/BMP085Sensor.cpp
@@ -10,20 +10,17 @@
 
 BMP085Sensor::BMP085Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_BMP085, "BMP085") {}
 
-int32_t BMP085Sensor::runOnce()
+bool BMP085Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev)
 {
     LOG_INFO("Init sensor: %s", sensorName);
-    if (!hasSensor()) {
-        return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS;
-    }
+
     bmp085 = Adafruit_BMP085();
-    status = bmp085.begin(nodeTelemetrySensorsMap[sensorType].first, nodeTelemetrySensorsMap[sensorType].second);
+    status = bmp085.begin(dev->address.address, bus);
 
-    return initI2CSensor();
+    initI2CSensor();
+    return status;
 }
 
-void BMP085Sensor::setup() {}
-
 bool BMP085Sensor::getMetrics(meshtastic_Telemetry *measurement)
 {
     measurement->variant.environment_metrics.has_temperature = true;
diff --git a/src/modules/Telemetry/Sensor/BMP085Sensor.h b/src/modules/Telemetry/Sensor/BMP085Sensor.h
index 8dadceab4..12ccf35a1 100644
--- a/src/modules/Telemetry/Sensor/BMP085Sensor.h
+++ b/src/modules/Telemetry/Sensor/BMP085Sensor.h
@@ -11,13 +11,10 @@ class BMP085Sensor : public TelemetrySensor
   private:
     Adafruit_BMP085 bmp085;
 
-  protected:
-    virtual void setup() override;
-
   public:
     BMP085Sensor();
-    virtual int32_t runOnce() override;
     virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
+    virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override;
 };
 
 #endif
\ No newline at end of file
diff --git a/src/modules/Telemetry/Sensor/BMP280Sensor.cpp b/src/modules/Telemetry/Sensor/BMP280Sensor.cpp
index 47069b8e0..2b7407c43 100644
--- a/src/modules/Telemetry/Sensor/BMP280Sensor.cpp
+++ b/src/modules/Telemetry/Sensor/BMP280Sensor.cpp
@@ -10,25 +10,25 @@
 
 BMP280Sensor::BMP280Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_BMP280, "BMP280") {}
 
-int32_t BMP280Sensor::runOnce()
+bool BMP280Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev)
 {
     LOG_INFO("Init sensor: %s", sensorName);
-    if (!hasSensor()) {
-        return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS;
+
+    bmp280 = Adafruit_BMP280(bus);
+    status = bmp280.begin(dev->address.address);
+    if (!status) {
+        return status;
     }
-    bmp280 = Adafruit_BMP280(nodeTelemetrySensorsMap[sensorType].second);
-    status = bmp280.begin(nodeTelemetrySensorsMap[sensorType].first);
 
     bmp280.setSampling(Adafruit_BMP280::MODE_FORCED,
                        Adafruit_BMP280::SAMPLING_X1, // Temp. oversampling
                        Adafruit_BMP280::SAMPLING_X1, // Pressure oversampling
                        Adafruit_BMP280::FILTER_OFF, Adafruit_BMP280::STANDBY_MS_1000);
 
-    return initI2CSensor();
+    initI2CSensor();
+    return status;
 }
 
-void BMP280Sensor::setup() {}
-
 bool BMP280Sensor::getMetrics(meshtastic_Telemetry *measurement)
 {
     measurement->variant.environment_metrics.has_temperature = true;
diff --git a/src/modules/Telemetry/Sensor/BMP280Sensor.h b/src/modules/Telemetry/Sensor/BMP280Sensor.h
index d615411b2..2199fc0cd 100644
--- a/src/modules/Telemetry/Sensor/BMP280Sensor.h
+++ b/src/modules/Telemetry/Sensor/BMP280Sensor.h
@@ -11,13 +11,10 @@ class BMP280Sensor : public TelemetrySensor
   private:
     Adafruit_BMP280 bmp280;
 
-  protected:
-    virtual void setup() override;
-
   public:
     BMP280Sensor();
-    virtual int32_t runOnce() override;
     virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
+    virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override;
 };
 
 #endif
\ No newline at end of file
diff --git a/src/modules/Telemetry/Sensor/BMP3XXSensor.cpp b/src/modules/Telemetry/Sensor/BMP3XXSensor.cpp
index 28a71b48f..ac80732bf 100644
--- a/src/modules/Telemetry/Sensor/BMP3XXSensor.cpp
+++ b/src/modules/Telemetry/Sensor/BMP3XXSensor.cpp
@@ -6,20 +6,18 @@
 
 BMP3XXSensor::BMP3XXSensor() : TelemetrySensor(meshtastic_TelemetrySensorType_BMP3XX, "BMP3XX") {}
 
-void BMP3XXSensor::setup() {}
-
-int32_t BMP3XXSensor::runOnce()
+bool BMP3XXSensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev)
 {
     LOG_INFO("Init sensor: %s", sensorName);
-    if (!hasSensor()) {
-        return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS;
-    }
 
     // Get a singleton instance and initialise the bmp3xx
     if (bmp3xx == nullptr) {
         bmp3xx = BMP3XXSingleton::GetInstance();
     }
-    status = bmp3xx->begin_I2C(nodeTelemetrySensorsMap[sensorType].first, nodeTelemetrySensorsMap[sensorType].second);
+    status = bmp3xx->begin_I2C(dev->address.address, bus);
+    if (!status) {
+        return status;
+    }
 
     // set up oversampling and filter initialization
     bmp3xx->setTemperatureOversampling(BMP3_OVERSAMPLING_4X);
@@ -31,7 +29,8 @@ int32_t BMP3XXSensor::runOnce()
     for (int i = 0; i < 3; i++) {
         bmp3xx->performReading();
     }
-    return initI2CSensor();
+    initI2CSensor();
+    return status;
 }
 
 bool BMP3XXSensor::getMetrics(meshtastic_Telemetry *measurement)
diff --git a/src/modules/Telemetry/Sensor/BMP3XXSensor.h b/src/modules/Telemetry/Sensor/BMP3XXSensor.h
index 6ab0f533d..7ce14d9db 100644
--- a/src/modules/Telemetry/Sensor/BMP3XXSensor.h
+++ b/src/modules/Telemetry/Sensor/BMP3XXSensor.h
@@ -43,12 +43,11 @@ class BMP3XXSensor : public TelemetrySensor
 {
   protected:
     BMP3XXSingleton *bmp3xx = nullptr;
-    virtual void setup() override;
 
   public:
     BMP3XXSensor();
-    virtual int32_t runOnce() override;
     virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
+    virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override;
 };
 
 #endif
diff --git a/src/modules/Telemetry/Sensor/CGRadSensSensor.cpp b/src/modules/Telemetry/Sensor/CGRadSensSensor.cpp
index ac5df1b81..e7b191398 100644
--- a/src/modules/Telemetry/Sensor/CGRadSensSensor.cpp
+++ b/src/modules/Telemetry/Sensor/CGRadSensSensor.cpp
@@ -14,22 +14,16 @@
 
 CGRadSensSensor::CGRadSensSensor() : TelemetrySensor(meshtastic_TelemetrySensorType_RADSENS, "RadSens") {}
 
-int32_t CGRadSensSensor::runOnce()
+bool CGRadSensSensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev)
 {
     // Initialize the sensor following the same pattern as RCWL9620Sensor
     LOG_INFO("Init sensor: %s", sensorName);
-    if (!hasSensor()) {
-        return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS;
-    }
-
     status = true;
-    begin(nodeTelemetrySensorsMap[sensorType].second, nodeTelemetrySensorsMap[sensorType].first);
-
-    return initI2CSensor();
+    begin(bus, dev->address.address);
+    initI2CSensor();
+    return status;
 }
 
-void CGRadSensSensor::setup() {}
-
 void CGRadSensSensor::begin(TwoWire *wire, uint8_t addr)
 {
     // Store the Wire and address to the sensor following the same pattern as RCWL9620Sensor
diff --git a/src/modules/Telemetry/Sensor/CGRadSensSensor.h b/src/modules/Telemetry/Sensor/CGRadSensSensor.h
index 3b15a19a2..c677e8899 100644
--- a/src/modules/Telemetry/Sensor/CGRadSensSensor.h
+++ b/src/modules/Telemetry/Sensor/CGRadSensSensor.h
@@ -17,14 +17,13 @@ class CGRadSensSensor : public TelemetrySensor
     TwoWire *_wire = &Wire;
 
   protected:
-    virtual void setup() override;
     void begin(TwoWire *wire = &Wire, uint8_t addr = 0x66);
     float getStaticRadiation();
 
   public:
     CGRadSensSensor();
-    virtual int32_t runOnce() override;
     virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
+    virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override;
 };
 
 #endif
\ No newline at end of file
diff --git a/src/modules/Telemetry/Sensor/DFRobotGravitySensor.cpp b/src/modules/Telemetry/Sensor/DFRobotGravitySensor.cpp
index 9581057b0..59a98e291 100644
--- a/src/modules/Telemetry/Sensor/DFRobotGravitySensor.cpp
+++ b/src/modules/Telemetry/Sensor/DFRobotGravitySensor.cpp
@@ -10,31 +10,39 @@
 
 DFRobotGravitySensor::DFRobotGravitySensor() : TelemetrySensor(meshtastic_TelemetrySensorType_DFROBOT_RAIN, "DFROBOT_RAIN") {}
 
-int32_t DFRobotGravitySensor::runOnce()
+DFRobotGravitySensor::~DFRobotGravitySensor()
 {
-    LOG_INFO("Init sensor: %s", sensorName);
-    if (!hasSensor()) {
-        return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS;
+    if (gravity) {
+        delete gravity;
+        gravity = nullptr;
     }
-
-    gravity = DFRobot_RainfallSensor_I2C(nodeTelemetrySensorsMap[sensorType].second);
-    status = gravity.begin();
-
-    return initI2CSensor();
 }
 
-void DFRobotGravitySensor::setup()
+bool DFRobotGravitySensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev)
 {
-    LOG_DEBUG("%s VID: %x, PID: %x, Version: %s", sensorName, gravity.vid, gravity.pid, gravity.getFirmwareVersion().c_str());
+    LOG_INFO("Init sensor: %s", sensorName);
+
+    gravity = new DFRobot_RainfallSensor_I2C(bus);
+    status = gravity->begin();
+
+    LOG_DEBUG("%s VID: %x, PID: %x, Version: %s", sensorName, gravity->vid, gravity->pid, gravity->getFirmwareVersion().c_str());
+
+    initI2CSensor();
+    return status;
 }
 
 bool DFRobotGravitySensor::getMetrics(meshtastic_Telemetry *measurement)
 {
+    if (!gravity) {
+        LOG_ERROR("DFRobotGravitySensor not initialized");
+        return false;
+    }
+
     measurement->variant.environment_metrics.has_rainfall_1h = true;
     measurement->variant.environment_metrics.has_rainfall_24h = true;
 
-    measurement->variant.environment_metrics.rainfall_1h = gravity.getRainfall(1);
-    measurement->variant.environment_metrics.rainfall_24h = gravity.getRainfall(24);
+    measurement->variant.environment_metrics.rainfall_1h = gravity->getRainfall(1);
+    measurement->variant.environment_metrics.rainfall_24h = gravity->getRainfall(24);
 
     LOG_INFO("Rain 1h: %f mm", measurement->variant.environment_metrics.rainfall_1h);
     LOG_INFO("Rain 24h: %f mm", measurement->variant.environment_metrics.rainfall_24h);
diff --git a/src/modules/Telemetry/Sensor/DFRobotGravitySensor.h b/src/modules/Telemetry/Sensor/DFRobotGravitySensor.h
index dfd81a913..2b4890e30 100644
--- a/src/modules/Telemetry/Sensor/DFRobotGravitySensor.h
+++ b/src/modules/Telemetry/Sensor/DFRobotGravitySensor.h
@@ -14,15 +14,13 @@
 class DFRobotGravitySensor : public TelemetrySensor
 {
   private:
-    DFRobot_RainfallSensor_I2C gravity = DFRobot_RainfallSensor_I2C(nodeTelemetrySensorsMap[sensorType].second);
-
-  protected:
-    virtual void setup() override;
+    DFRobot_RainfallSensor_I2C *gravity = nullptr;
 
   public:
     DFRobotGravitySensor();
-    virtual int32_t runOnce() override;
+    ~DFRobotGravitySensor();
     virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
+    virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override;
 };
 
 #endif
diff --git a/src/modules/Telemetry/Sensor/DFRobotLarkSensor.cpp b/src/modules/Telemetry/Sensor/DFRobotLarkSensor.cpp
index d962f1634..2c2aeed6d 100644
--- a/src/modules/Telemetry/Sensor/DFRobotLarkSensor.cpp
+++ b/src/modules/Telemetry/Sensor/DFRobotLarkSensor.cpp
@@ -11,14 +11,10 @@
 
 DFRobotLarkSensor::DFRobotLarkSensor() : TelemetrySensor(meshtastic_TelemetrySensorType_DFROBOT_LARK, "DFROBOT_LARK") {}
 
-int32_t DFRobotLarkSensor::runOnce()
+bool DFRobotLarkSensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev)
 {
     LOG_INFO("Init sensor: %s", sensorName);
-    if (!hasSensor()) {
-        return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS;
-    }
-
-    lark = DFRobot_LarkWeatherStation_I2C(nodeTelemetrySensorsMap[sensorType].first, nodeTelemetrySensorsMap[sensorType].second);
+    lark = DFRobot_LarkWeatherStation_I2C(dev->address.address, bus);
 
     if (lark.begin() == 0) // DFRobotLarkSensor init
     {
@@ -28,11 +24,10 @@ int32_t DFRobotLarkSensor::runOnce()
         LOG_ERROR("DFRobotLarkSensor Init Failed");
         status = false;
     }
-    return initI2CSensor();
+    initI2CSensor();
+    return status;
 }
 
-void DFRobotLarkSensor::setup() {}
-
 bool DFRobotLarkSensor::getMetrics(meshtastic_Telemetry *measurement)
 {
     measurement->variant.environment_metrics.has_temperature = true;
diff --git a/src/modules/Telemetry/Sensor/DFRobotLarkSensor.h b/src/modules/Telemetry/Sensor/DFRobotLarkSensor.h
index 7b67bc5b6..f3e4661a1 100644
--- a/src/modules/Telemetry/Sensor/DFRobotLarkSensor.h
+++ b/src/modules/Telemetry/Sensor/DFRobotLarkSensor.h
@@ -16,13 +16,10 @@ class DFRobotLarkSensor : public TelemetrySensor
   private:
     DFRobot_LarkWeatherStation_I2C lark = DFRobot_LarkWeatherStation_I2C();
 
-  protected:
-    virtual void setup() override;
-
   public:
     DFRobotLarkSensor();
-    virtual int32_t runOnce() override;
     virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
+    virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override;
 };
 
 #endif
diff --git a/src/modules/Telemetry/Sensor/DPS310Sensor.cpp b/src/modules/Telemetry/Sensor/DPS310Sensor.cpp
index cc9b83af8..19e54aa4b 100644
--- a/src/modules/Telemetry/Sensor/DPS310Sensor.cpp
+++ b/src/modules/Telemetry/Sensor/DPS310Sensor.cpp
@@ -9,23 +9,22 @@
 
 DPS310Sensor::DPS310Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_DPS310, "DPS310") {}
 
-int32_t DPS310Sensor::runOnce()
+bool DPS310Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev)
 {
     LOG_INFO("Init sensor: %s", sensorName);
-    if (!hasSensor()) {
-        return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS;
+    status = dps310.begin_I2C(dev->address.address, bus);
+    if (!status) {
+        return status;
     }
-    status = dps310.begin_I2C(nodeTelemetrySensorsMap[sensorType].first, nodeTelemetrySensorsMap[sensorType].second);
 
     dps310.configurePressure(DPS310_1HZ, DPS310_4SAMPLES);
     dps310.configureTemperature(DPS310_1HZ, DPS310_4SAMPLES);
     dps310.setMode(DPS310_CONT_PRESTEMP);
 
-    return initI2CSensor();
+    initI2CSensor();
+    return status;
 }
 
-void DPS310Sensor::setup() {}
-
 bool DPS310Sensor::getMetrics(meshtastic_Telemetry *measurement)
 {
     sensors_event_t temp, press;
diff --git a/src/modules/Telemetry/Sensor/DPS310Sensor.h b/src/modules/Telemetry/Sensor/DPS310Sensor.h
index e9b4ece89..4de8b2d1a 100644
--- a/src/modules/Telemetry/Sensor/DPS310Sensor.h
+++ b/src/modules/Telemetry/Sensor/DPS310Sensor.h
@@ -11,13 +11,10 @@ class DPS310Sensor : public TelemetrySensor
   private:
     Adafruit_DPS310 dps310;
 
-  protected:
-    virtual void setup() override;
-
   public:
     DPS310Sensor();
-    virtual int32_t runOnce() override;
     virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
+    virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override;
 };
 
 #endif
\ No newline at end of file
diff --git a/src/modules/Telemetry/Sensor/IndicatorSensor.cpp b/src/modules/Telemetry/Sensor/IndicatorSensor.cpp
index 317357137..26a4bc5fc 100644
--- a/src/modules/Telemetry/Sensor/IndicatorSensor.cpp
+++ b/src/modules/Telemetry/Sensor/IndicatorSensor.cpp
@@ -61,11 +61,11 @@ static int cmd_send(uint8_t cmd, const char *p_data, uint8_t len)
     return -1;
 }
 
-int32_t IndicatorSensor::runOnce()
+bool IndicatorSensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev)
 {
     LOG_INFO("%s: init", sensorName);
     setup();
-    return 2 * DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS; // give it some time to start up
+    return true;
 }
 
 void IndicatorSensor::setup()
diff --git a/src/modules/Telemetry/Sensor/IndicatorSensor.h b/src/modules/Telemetry/Sensor/IndicatorSensor.h
index 48ecef8de..22a0d9c83 100644
--- a/src/modules/Telemetry/Sensor/IndicatorSensor.h
+++ b/src/modules/Telemetry/Sensor/IndicatorSensor.h
@@ -7,13 +7,13 @@
 
 class IndicatorSensor : public TelemetrySensor
 {
-  protected:
-    virtual void setup() override;
-
   public:
     IndicatorSensor();
-    virtual int32_t runOnce() override;
     virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
+    virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override;
+
+  private:
+    void setup();
 };
 
 #endif
\ No newline at end of file
diff --git a/src/modules/Telemetry/Sensor/LPS22HBSensor.cpp b/src/modules/Telemetry/Sensor/LPS22HBSensor.cpp
index cf0fbe4a9..4ed78dcb0 100644
--- a/src/modules/Telemetry/Sensor/LPS22HBSensor.cpp
+++ b/src/modules/Telemetry/Sensor/LPS22HBSensor.cpp
@@ -10,19 +10,17 @@
 
 LPS22HBSensor::LPS22HBSensor() : TelemetrySensor(meshtastic_TelemetrySensorType_LPS22, "LPS22HB") {}
 
-int32_t LPS22HBSensor::runOnce()
+bool LPS22HBSensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev)
 {
     LOG_INFO("Init sensor: %s", sensorName);
-    if (!hasSensor()) {
-        return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS;
+    status = lps22hb.begin_I2C(dev->address.address, bus);
+    if (!status) {
+        return status;
     }
-    status = lps22hb.begin_I2C(nodeTelemetrySensorsMap[sensorType].first, nodeTelemetrySensorsMap[sensorType].second);
-    return initI2CSensor();
-}
-
-void LPS22HBSensor::setup()
-{
     lps22hb.setDataRate(LPS22_RATE_10_HZ);
+
+    initI2CSensor();
+    return status;
 }
 
 bool LPS22HBSensor::getMetrics(meshtastic_Telemetry *measurement)
diff --git a/src/modules/Telemetry/Sensor/LPS22HBSensor.h b/src/modules/Telemetry/Sensor/LPS22HBSensor.h
index 24d75e903..90b006fa2 100644
--- a/src/modules/Telemetry/Sensor/LPS22HBSensor.h
+++ b/src/modules/Telemetry/Sensor/LPS22HBSensor.h
@@ -12,13 +12,10 @@ class LPS22HBSensor : public TelemetrySensor
   private:
     Adafruit_LPS22 lps22hb;
 
-  protected:
-    virtual void setup() override;
-
   public:
     LPS22HBSensor();
-    virtual int32_t runOnce() override;
     virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
+    virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override;
 };
 
 #endif
\ No newline at end of file
diff --git a/src/modules/Telemetry/Sensor/LTR390UVSensor.cpp b/src/modules/Telemetry/Sensor/LTR390UVSensor.cpp
index fb84700c4..cb7290fee 100644
--- a/src/modules/Telemetry/Sensor/LTR390UVSensor.cpp
+++ b/src/modules/Telemetry/Sensor/LTR390UVSensor.cpp
@@ -9,23 +9,23 @@
 
 LTR390UVSensor::LTR390UVSensor() : TelemetrySensor(meshtastic_TelemetrySensorType_LTR390UV, "LTR390UV") {}
 
-int32_t LTR390UVSensor::runOnce()
+bool LTR390UVSensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev)
 {
     LOG_INFO("Init sensor: %s", sensorName);
-    if (!hasSensor()) {
-        return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS;
+
+    status = ltr390uv.begin(bus);
+    if (!status) {
+        return status;
     }
 
-    status = ltr390uv.begin(nodeTelemetrySensorsMap[sensorType].second);
     ltr390uv.setMode(LTR390_MODE_UVS);
     ltr390uv.setGain(LTR390_GAIN_18);                // Datasheet default
     ltr390uv.setResolution(LTR390_RESOLUTION_20BIT); // Datasheet default
 
-    return initI2CSensor();
+    initI2CSensor();
+    return status;
 }
 
-void LTR390UVSensor::setup() {}
-
 bool LTR390UVSensor::getMetrics(meshtastic_Telemetry *measurement)
 {
     LOG_DEBUG("LTR390UV getMetrics");
diff --git a/src/modules/Telemetry/Sensor/LTR390UVSensor.h b/src/modules/Telemetry/Sensor/LTR390UVSensor.h
index 40206bce8..e12d17274 100644
--- a/src/modules/Telemetry/Sensor/LTR390UVSensor.h
+++ b/src/modules/Telemetry/Sensor/LTR390UVSensor.h
@@ -13,13 +13,10 @@ class LTR390UVSensor : public TelemetrySensor
     float lastLuxReading = 0;
     float lastUVReading = 0;
 
-  protected:
-    virtual void setup() override;
-
   public:
     LTR390UVSensor();
-    virtual int32_t runOnce() override;
     virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
+    virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override;
 };
 
 #endif
\ No newline at end of file
diff --git a/src/modules/Telemetry/Sensor/MCP9808Sensor.cpp b/src/modules/Telemetry/Sensor/MCP9808Sensor.cpp
index 906634c40..c93d6a927 100644
--- a/src/modules/Telemetry/Sensor/MCP9808Sensor.cpp
+++ b/src/modules/Telemetry/Sensor/MCP9808Sensor.cpp
@@ -9,19 +9,17 @@
 
 MCP9808Sensor::MCP9808Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_MCP9808, "MCP9808") {}
 
-int32_t MCP9808Sensor::runOnce()
+bool MCP9808Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev)
 {
     LOG_INFO("Init sensor: %s", sensorName);
-    if (!hasSensor()) {
-        return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS;
+    status = mcp9808.begin(dev->address.address, bus);
+    if (!status) {
+        return status;
     }
-    status = mcp9808.begin(nodeTelemetrySensorsMap[sensorType].first, nodeTelemetrySensorsMap[sensorType].second);
-    return initI2CSensor();
-}
-
-void MCP9808Sensor::setup()
-{
     mcp9808.setResolution(2);
+
+    initI2CSensor();
+    return status;
 }
 
 bool MCP9808Sensor::getMetrics(meshtastic_Telemetry *measurement)
diff --git a/src/modules/Telemetry/Sensor/MCP9808Sensor.h b/src/modules/Telemetry/Sensor/MCP9808Sensor.h
index 705a71700..cef7a48c2 100644
--- a/src/modules/Telemetry/Sensor/MCP9808Sensor.h
+++ b/src/modules/Telemetry/Sensor/MCP9808Sensor.h
@@ -11,13 +11,10 @@ class MCP9808Sensor : public TelemetrySensor
   private:
     Adafruit_MCP9808 mcp9808;
 
-  protected:
-    virtual void setup() override;
-
   public:
     MCP9808Sensor();
-    virtual int32_t runOnce() override;
     virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
+    virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override;
 };
 
 #endif
\ No newline at end of file
diff --git a/src/modules/Telemetry/Sensor/MLX90632Sensor.cpp b/src/modules/Telemetry/Sensor/MLX90632Sensor.cpp
index dfc049023..eb84edffc 100644
--- a/src/modules/Telemetry/Sensor/MLX90632Sensor.cpp
+++ b/src/modules/Telemetry/Sensor/MLX90632Sensor.cpp
@@ -8,16 +8,12 @@
 
 MLX90632Sensor::MLX90632Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_MLX90632, "MLX90632") {}
 
-int32_t MLX90632Sensor::runOnce()
+bool MLX90632Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev)
 {
     LOG_INFO("Init sensor: %s", sensorName);
-    if (!hasSensor()) {
-        return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS;
-    }
 
     MLX90632::status returnError;
-    if (mlx.begin(nodeTelemetrySensorsMap[sensorType].first, *nodeTelemetrySensorsMap[sensorType].second, returnError) ==
-        true) // MLX90632 init
+    if (mlx.begin(dev->address.address, *bus, returnError) == true) // MLX90632 init
     {
         LOG_DEBUG("MLX90632 Init Succeed");
         status = true;
@@ -25,11 +21,10 @@ int32_t MLX90632Sensor::runOnce()
         LOG_ERROR("MLX90632 Init Failed");
         status = false;
     }
-    return initI2CSensor();
+    initI2CSensor();
+    return status;
 }
 
-void MLX90632Sensor::setup() {}
-
 bool MLX90632Sensor::getMetrics(meshtastic_Telemetry *measurement)
 {
     measurement->variant.environment_metrics.has_temperature = true;
diff --git a/src/modules/Telemetry/Sensor/MLX90632Sensor.h b/src/modules/Telemetry/Sensor/MLX90632Sensor.h
index ef7be180a..566db8319 100644
--- a/src/modules/Telemetry/Sensor/MLX90632Sensor.h
+++ b/src/modules/Telemetry/Sensor/MLX90632Sensor.h
@@ -11,13 +11,10 @@ class MLX90632Sensor : public TelemetrySensor
   private:
     MLX90632 mlx = MLX90632();
 
-  protected:
-    virtual void setup() override;
-
   public:
     MLX90632Sensor();
-    virtual int32_t runOnce() override;
     virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
+    virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override;
 };
 
 #endif
\ No newline at end of file
diff --git a/src/modules/Telemetry/Sensor/NAU7802Sensor.cpp b/src/modules/Telemetry/Sensor/NAU7802Sensor.cpp
index b6b5d89f7..e67b78145 100644
--- a/src/modules/Telemetry/Sensor/NAU7802Sensor.cpp
+++ b/src/modules/Telemetry/Sensor/NAU7802Sensor.cpp
@@ -16,24 +16,23 @@ meshtastic_Nau7802Config nau7802config = meshtastic_Nau7802Config_init_zero;
 
 NAU7802Sensor::NAU7802Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_NAU7802, "NAU7802") {}
 
-int32_t NAU7802Sensor::runOnce()
+bool NAU7802Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev)
 {
     LOG_INFO("Init sensor: %s", sensorName);
-    if (!hasSensor()) {
-        return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS;
+    status = nau7802.begin(*bus);
+    if (!status) {
+        return status;
     }
-    status = nau7802.begin(*nodeTelemetrySensorsMap[sensorType].second);
     nau7802.setSampleRate(NAU7802_SPS_320);
     if (!loadCalibrationData()) {
         LOG_ERROR("Failed to load calibration data");
     }
     nau7802.calibrateAFE();
     LOG_INFO("Offset: %d, Calibration factor: %.2f", nau7802.getZeroOffset(), nau7802.getCalibrationFactor());
-    return initI2CSensor();
+    initI2CSensor();
+    return status;
 }
 
-void NAU7802Sensor::setup() {}
-
 bool NAU7802Sensor::getMetrics(meshtastic_Telemetry *measurement)
 {
     LOG_DEBUG("NAU7802 getMetrics");
diff --git a/src/modules/Telemetry/Sensor/NAU7802Sensor.h b/src/modules/Telemetry/Sensor/NAU7802Sensor.h
index cb9e64829..a45e9a78a 100644
--- a/src/modules/Telemetry/Sensor/NAU7802Sensor.h
+++ b/src/modules/Telemetry/Sensor/NAU7802Sensor.h
@@ -13,15 +13,14 @@ class NAU7802Sensor : public TelemetrySensor
     NAU7802 nau7802;
 
   protected:
-    virtual void setup() override;
     const char *nau7802ConfigFileName = "/prefs/nau7802.dat";
     bool saveCalibrationData();
     bool loadCalibrationData();
 
   public:
     NAU7802Sensor();
-    virtual int32_t runOnce() override;
     virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
+    virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override;
     void tare();
     void calibrate(float weight);
     AdminMessageHandleResult handleAdminMessage(const meshtastic_MeshPacket &mp, meshtastic_AdminMessage *request,
diff --git a/src/modules/Telemetry/Sensor/OPT3001Sensor.cpp b/src/modules/Telemetry/Sensor/OPT3001Sensor.cpp
index 1f0407374..3407f2f0f 100644
--- a/src/modules/Telemetry/Sensor/OPT3001Sensor.cpp
+++ b/src/modules/Telemetry/Sensor/OPT3001Sensor.cpp
@@ -9,20 +9,15 @@
 
 OPT3001Sensor::OPT3001Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_OPT3001, "OPT3001") {}
 
-int32_t OPT3001Sensor::runOnce()
+bool OPT3001Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev)
 {
     LOG_INFO("Init sensor: %s", sensorName);
-    if (!hasSensor()) {
-        return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS;
-    }
-    auto errorCode = opt3001.begin(nodeTelemetrySensorsMap[sensorType].first);
+    auto errorCode = opt3001.begin(dev->address.address);
     status = errorCode == NO_ERROR;
+    if (!status) {
+        return status;
+    }
 
-    return initI2CSensor();
-}
-
-void OPT3001Sensor::setup()
-{
     OPT3001_Config newConfig;
 
     newConfig.RangeNumber = 0b1100;
@@ -34,6 +29,10 @@ void OPT3001Sensor::setup()
     if (errorConfig != NO_ERROR) {
         LOG_ERROR("OPT3001 configuration error #%d", errorConfig);
     }
+    status = errorConfig == NO_ERROR;
+
+    initI2CSensor();
+    return status;
 }
 
 bool OPT3001Sensor::getMetrics(meshtastic_Telemetry *measurement)
diff --git a/src/modules/Telemetry/Sensor/OPT3001Sensor.h b/src/modules/Telemetry/Sensor/OPT3001Sensor.h
index a9da2d705..c8a140b51 100644
--- a/src/modules/Telemetry/Sensor/OPT3001Sensor.h
+++ b/src/modules/Telemetry/Sensor/OPT3001Sensor.h
@@ -12,13 +12,13 @@ class OPT3001Sensor : public TelemetrySensor
   private:
     ClosedCube_OPT3001 opt3001;
 
-  protected:
-    virtual void setup() override;
-
   public:
     OPT3001Sensor();
-    virtual int32_t runOnce() override;
+#if WIRE_INTERFACES_COUNT > 1
+    virtual bool onlyWire1() { return true; }
+#endif
     virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
+    virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override;
 };
 
 #endif
\ No newline at end of file
diff --git a/src/modules/Telemetry/Sensor/PCT2075Sensor.cpp b/src/modules/Telemetry/Sensor/PCT2075Sensor.cpp
index d2b50d983..189317bf2 100644
--- a/src/modules/Telemetry/Sensor/PCT2075Sensor.cpp
+++ b/src/modules/Telemetry/Sensor/PCT2075Sensor.cpp
@@ -9,24 +9,18 @@
 
 PCT2075Sensor::PCT2075Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_PCT2075, "PCT2075") {}
 
-int32_t PCT2075Sensor::runOnce()
+bool PCT2075Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev)
 {
     LOG_INFO("Init sensor: %s", sensorName);
-    if (!hasSensor()) {
-        return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS;
-    }
+    status = pct2075.begin(dev->address.address, bus);
 
-    status = pct2075.begin(nodeTelemetrySensorsMap[sensorType].first, nodeTelemetrySensorsMap[sensorType].second);
-
-    return initI2CSensor();
+    initI2CSensor();
+    return status;
 }
 
-void PCT2075Sensor::setup() {}
-
 bool PCT2075Sensor::getMetrics(meshtastic_Telemetry *measurement)
 {
     measurement->variant.environment_metrics.has_temperature = true;
-
     measurement->variant.environment_metrics.temperature = pct2075.getTemperature();
 
     return true;
diff --git a/src/modules/Telemetry/Sensor/PCT2075Sensor.h b/src/modules/Telemetry/Sensor/PCT2075Sensor.h
index 842c973d0..55f9423d4 100644
--- a/src/modules/Telemetry/Sensor/PCT2075Sensor.h
+++ b/src/modules/Telemetry/Sensor/PCT2075Sensor.h
@@ -12,13 +12,10 @@ class PCT2075Sensor : public TelemetrySensor
   private:
     Adafruit_PCT2075 pct2075;
 
-  protected:
-    virtual void setup() override;
-
   public:
     PCT2075Sensor();
-    virtual int32_t runOnce() override;
     virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
+    virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override;
 };
 
 #endif
diff --git a/src/modules/Telemetry/Sensor/RAK12035Sensor.cpp b/src/modules/Telemetry/Sensor/RAK12035Sensor.cpp
index 7a1bb01ce..ff0628cc3 100644
--- a/src/modules/Telemetry/Sensor/RAK12035Sensor.cpp
+++ b/src/modules/Telemetry/Sensor/RAK12035Sensor.cpp
@@ -6,16 +6,12 @@
 
 RAK12035Sensor::RAK12035Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_RAK12035, "RAK12035") {}
 
-int32_t RAK12035Sensor::runOnce()
+bool RAK12035Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev)
 {
-    if (!hasSensor()) {
-        return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS;
-    }
-
     // TODO:: check for up to 2 additional sensors and start them if present.
     sensor.set_sensor_addr(RAK120351_ADDR);
     delay(100);
-    sensor.begin(nodeTelemetrySensorsMap[sensorType].first);
+    sensor.begin(dev->address.address);
 
     // Get sensor firmware version
     uint8_t data = 0;
@@ -31,8 +27,13 @@ int32_t RAK12035Sensor::runOnce()
         LOG_ERROR("RAK12035Sensor Init Failed");
         status = false;
     }
+    if (!status) {
+        return status;
+    }
+    setup();
 
-    return initI2CSensor();
+    initI2CSensor();
+    return status;
 }
 
 void RAK12035Sensor::setup()
diff --git a/src/modules/Telemetry/Sensor/RAK12035Sensor.h b/src/modules/Telemetry/Sensor/RAK12035Sensor.h
index 2c32a840d..6a38d2eb3 100644
--- a/src/modules/Telemetry/Sensor/RAK12035Sensor.h
+++ b/src/modules/Telemetry/Sensor/RAK12035Sensor.h
@@ -16,13 +16,14 @@ class RAK12035Sensor : public TelemetrySensor
 {
   private:
     RAK12035 sensor;
-
-  protected:
-    virtual void setup() override;
+    void setup();
 
   public:
     RAK12035Sensor();
-    virtual int32_t runOnce() override;
+#if WIRE_INTERFACES_COUNT > 1
+    virtual bool onlyWire1() { return true; }
+#endif
     virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
+    virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override;
 };
 #endif
diff --git a/src/modules/Telemetry/Sensor/RCWL9620Sensor.cpp b/src/modules/Telemetry/Sensor/RCWL9620Sensor.cpp
index 9f7a55cc5..3dbd06e8d 100644
--- a/src/modules/Telemetry/Sensor/RCWL9620Sensor.cpp
+++ b/src/modules/Telemetry/Sensor/RCWL9620Sensor.cpp
@@ -8,19 +8,15 @@
 
 RCWL9620Sensor::RCWL9620Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_RCWL9620, "RCWL9620") {}
 
-int32_t RCWL9620Sensor::runOnce()
+bool RCWL9620Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev)
 {
     LOG_INFO("Init sensor: %s", sensorName);
-    if (!hasSensor()) {
-        return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS;
-    }
     status = 1;
-    begin(nodeTelemetrySensorsMap[sensorType].second, nodeTelemetrySensorsMap[sensorType].first);
-    return initI2CSensor();
+    begin(bus, dev->address.address);
+    initI2CSensor();
+    return status;
 }
 
-void RCWL9620Sensor::setup() {}
-
 bool RCWL9620Sensor::getMetrics(meshtastic_Telemetry *measurement)
 {
     measurement->variant.environment_metrics.has_distance = true;
diff --git a/src/modules/Telemetry/Sensor/RCWL9620Sensor.h b/src/modules/Telemetry/Sensor/RCWL9620Sensor.h
index 7f9486d25..408db3633 100644
--- a/src/modules/Telemetry/Sensor/RCWL9620Sensor.h
+++ b/src/modules/Telemetry/Sensor/RCWL9620Sensor.h
@@ -16,14 +16,13 @@ class RCWL9620Sensor : public TelemetrySensor
     uint32_t _speed = 200000UL;
 
   protected:
-    virtual void setup() override;
     void begin(TwoWire *wire = &Wire, uint8_t addr = 0x57, uint8_t sda = -1, uint8_t scl = -1, uint32_t speed = 200000UL);
     float getDistance();
 
   public:
     RCWL9620Sensor();
-    virtual int32_t runOnce() override;
     virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
+    virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override;
 };
 
 #endif
\ No newline at end of file
diff --git a/src/modules/Telemetry/Sensor/SHT31Sensor.cpp b/src/modules/Telemetry/Sensor/SHT31Sensor.cpp
index 8619a7905..67a36933d 100644
--- a/src/modules/Telemetry/Sensor/SHT31Sensor.cpp
+++ b/src/modules/Telemetry/Sensor/SHT31Sensor.cpp
@@ -9,20 +9,13 @@
 
 SHT31Sensor::SHT31Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_SHT31, "SHT31") {}
 
-int32_t SHT31Sensor::runOnce()
+bool SHT31Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev)
 {
     LOG_INFO("Init sensor: %s", sensorName);
-    if (!hasSensor()) {
-        return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS;
-    }
-    sht31 = Adafruit_SHT31(nodeTelemetrySensorsMap[sensorType].second);
-    status = sht31.begin(nodeTelemetrySensorsMap[sensorType].first);
-    return initI2CSensor();
-}
-
-void SHT31Sensor::setup()
-{
-    // Set up oversampling and filter initialization
+    sht31 = Adafruit_SHT31(bus);
+    status = sht31.begin(dev->address.address);
+    initI2CSensor();
+    return status;
 }
 
 bool SHT31Sensor::getMetrics(meshtastic_Telemetry *measurement)
diff --git a/src/modules/Telemetry/Sensor/SHT31Sensor.h b/src/modules/Telemetry/Sensor/SHT31Sensor.h
index c3d81af95..ecb7d63a6 100644
--- a/src/modules/Telemetry/Sensor/SHT31Sensor.h
+++ b/src/modules/Telemetry/Sensor/SHT31Sensor.h
@@ -11,13 +11,10 @@ class SHT31Sensor : public TelemetrySensor
   private:
     Adafruit_SHT31 sht31;
 
-  protected:
-    virtual void setup() override;
-
   public:
     SHT31Sensor();
-    virtual int32_t runOnce() override;
     virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
+    virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override;
 };
 
 #endif
\ No newline at end of file
diff --git a/src/modules/Telemetry/Sensor/SHT4XSensor.cpp b/src/modules/Telemetry/Sensor/SHT4XSensor.cpp
index 83fdaf6c6..b11795d97 100644
--- a/src/modules/Telemetry/Sensor/SHT4XSensor.cpp
+++ b/src/modules/Telemetry/Sensor/SHT4XSensor.cpp
@@ -9,16 +9,16 @@
 
 SHT4XSensor::SHT4XSensor() : TelemetrySensor(meshtastic_TelemetrySensorType_SHT4X, "SHT4X") {}
 
-int32_t SHT4XSensor::runOnce()
+bool SHT4XSensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev)
 {
     LOG_INFO("Init sensor: %s", sensorName);
-    if (!hasSensor()) {
-        return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS;
-    }
 
     uint32_t serialNumber = 0;
 
-    sht4x.begin(nodeTelemetrySensorsMap[sensorType].second);
+    status = sht4x.begin(bus);
+    if (!status) {
+        return status;
+    }
 
     serialNumber = sht4x.readSerial();
     if (serialNumber != 0) {
@@ -29,12 +29,8 @@ int32_t SHT4XSensor::runOnce()
         status = 0;
     }
 
-    return initI2CSensor();
-}
-
-void SHT4XSensor::setup()
-{
-    // Set up oversampling and filter initialization
+    initI2CSensor();
+    return status;
 }
 
 bool SHT4XSensor::getMetrics(meshtastic_Telemetry *measurement)
diff --git a/src/modules/Telemetry/Sensor/SHT4XSensor.h b/src/modules/Telemetry/Sensor/SHT4XSensor.h
index da608cb82..7311d2366 100644
--- a/src/modules/Telemetry/Sensor/SHT4XSensor.h
+++ b/src/modules/Telemetry/Sensor/SHT4XSensor.h
@@ -11,13 +11,10 @@ class SHT4XSensor : public TelemetrySensor
   private:
     Adafruit_SHT4x sht4x = Adafruit_SHT4x();
 
-  protected:
-    virtual void setup() override;
-
   public:
     SHT4XSensor();
-    virtual int32_t runOnce() override;
     virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
+    virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override;
 };
 
 #endif
\ No newline at end of file
diff --git a/src/modules/Telemetry/Sensor/SHTC3Sensor.cpp b/src/modules/Telemetry/Sensor/SHTC3Sensor.cpp
index e9c4d2a0b..fdab0b266 100644
--- a/src/modules/Telemetry/Sensor/SHTC3Sensor.cpp
+++ b/src/modules/Telemetry/Sensor/SHTC3Sensor.cpp
@@ -9,19 +9,13 @@
 
 SHTC3Sensor::SHTC3Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_SHTC3, "SHTC3") {}
 
-int32_t SHTC3Sensor::runOnce()
+bool SHTC3Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev)
 {
     LOG_INFO("Init sensor: %s", sensorName);
-    if (!hasSensor()) {
-        return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS;
-    }
-    status = shtc3.begin(nodeTelemetrySensorsMap[sensorType].second);
-    return initI2CSensor();
-}
+    status = shtc3.begin(bus);
 
-void SHTC3Sensor::setup()
-{
-    // Set up oversampling and filter initialization
+    initI2CSensor();
+    return status;
 }
 
 bool SHTC3Sensor::getMetrics(meshtastic_Telemetry *measurement)
diff --git a/src/modules/Telemetry/Sensor/SHTC3Sensor.h b/src/modules/Telemetry/Sensor/SHTC3Sensor.h
index 458af6465..51cee18f7 100644
--- a/src/modules/Telemetry/Sensor/SHTC3Sensor.h
+++ b/src/modules/Telemetry/Sensor/SHTC3Sensor.h
@@ -11,13 +11,10 @@ class SHTC3Sensor : public TelemetrySensor
   private:
     Adafruit_SHTC3 shtc3 = Adafruit_SHTC3();
 
-  protected:
-    virtual void setup() override;
-
   public:
     SHTC3Sensor();
-    virtual int32_t runOnce() override;
     virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
+    virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override;
 };
 
 #endif
\ No newline at end of file
diff --git a/src/modules/Telemetry/Sensor/T1000xSensor.cpp b/src/modules/Telemetry/Sensor/T1000xSensor.cpp
index 068969e8e..b123450ec 100644
--- a/src/modules/Telemetry/Sensor/T1000xSensor.cpp
+++ b/src/modules/Telemetry/Sensor/T1000xSensor.cpp
@@ -38,18 +38,10 @@ int8_t ntc_temp2[136] = {
 
 T1000xSensor::T1000xSensor() : TelemetrySensor(meshtastic_TelemetrySensorType_SENSOR_UNSET, "T1000x") {}
 
-int32_t T1000xSensor::runOnce()
+bool T1000xSensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev)
 {
     LOG_INFO("Init sensor: %s", sensorName);
-    if (!hasSensor()) {
-        return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS;
-    }
-    return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS;
-}
-
-void T1000xSensor::setup()
-{
-    // Set up oversampling and filter initialization
+    return true;
 }
 
 float T1000xSensor::getLux()
diff --git a/src/modules/Telemetry/Sensor/T1000xSensor.h b/src/modules/Telemetry/Sensor/T1000xSensor.h
index a1c771cfa..b840a2d88 100644
--- a/src/modules/Telemetry/Sensor/T1000xSensor.h
+++ b/src/modules/Telemetry/Sensor/T1000xSensor.h
@@ -7,13 +7,10 @@
 
 class T1000xSensor : public TelemetrySensor
 {
-  protected:
-    virtual void setup() override;
-
   public:
     T1000xSensor();
-    virtual int32_t runOnce() override;
     virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
+    virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override;
     virtual float getLux();
     virtual float getTemp();
 };
diff --git a/src/modules/Telemetry/Sensor/TSL2561Sensor.cpp b/src/modules/Telemetry/Sensor/TSL2561Sensor.cpp
index 9f3b7e460..4e02af642 100644
--- a/src/modules/Telemetry/Sensor/TSL2561Sensor.cpp
+++ b/src/modules/Telemetry/Sensor/TSL2561Sensor.cpp
@@ -9,22 +9,19 @@
 
 TSL2561Sensor::TSL2561Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_TSL2561, "TSL2561") {}
 
-int32_t TSL2561Sensor::runOnce()
+bool TSL2561Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev)
 {
     LOG_INFO("Init sensor: %s", sensorName);
-    if (!hasSensor()) {
-        return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS;
+
+    status = tsl.begin(bus);
+    if (!status) {
+        return status;
     }
-
-    status = tsl.begin(nodeTelemetrySensorsMap[sensorType].second);
-
-    return initI2CSensor();
-}
-
-void TSL2561Sensor::setup()
-{
     tsl.setGain(TSL2561_GAIN_1X);
     tsl.setIntegrationTime(TSL2561_INTEGRATIONTIME_101MS);
+
+    initI2CSensor();
+    return status;
 }
 
 bool TSL2561Sensor::getMetrics(meshtastic_Telemetry *measurement)
diff --git a/src/modules/Telemetry/Sensor/TSL2561Sensor.h b/src/modules/Telemetry/Sensor/TSL2561Sensor.h
index 0329becd8..abf5a8f73 100644
--- a/src/modules/Telemetry/Sensor/TSL2561Sensor.h
+++ b/src/modules/Telemetry/Sensor/TSL2561Sensor.h
@@ -12,12 +12,9 @@ class TSL2561Sensor : public TelemetrySensor
     // 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;
+    virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override;
 };
 #endif
diff --git a/src/modules/Telemetry/Sensor/TSL2591Sensor.cpp b/src/modules/Telemetry/Sensor/TSL2591Sensor.cpp
index 04443ebec..0899d4470 100644
--- a/src/modules/Telemetry/Sensor/TSL2591Sensor.cpp
+++ b/src/modules/Telemetry/Sensor/TSL2591Sensor.cpp
@@ -10,21 +10,18 @@
 
 TSL2591Sensor::TSL2591Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_TSL25911FN, "TSL2591") {}
 
-int32_t TSL2591Sensor::runOnce()
+bool TSL2591Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev)
 {
     LOG_INFO("Init sensor: %s", sensorName);
-    if (!hasSensor()) {
-        return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS;
+    status = tsl.begin(bus);
+    if (!status) {
+        return status;
     }
-    status = tsl.begin(nodeTelemetrySensorsMap[sensorType].second);
-
-    return initI2CSensor();
-}
-
-void TSL2591Sensor::setup()
-{
     tsl.setGain(TSL2591_GAIN_LOW); // 1x gain
     tsl.setTiming(TSL2591_INTEGRATIONTIME_100MS);
+
+    initI2CSensor();
+    return status;
 }
 
 bool TSL2591Sensor::getMetrics(meshtastic_Telemetry *measurement)
diff --git a/src/modules/Telemetry/Sensor/TSL2591Sensor.h b/src/modules/Telemetry/Sensor/TSL2591Sensor.h
index edf7698b1..1ac430a03 100644
--- a/src/modules/Telemetry/Sensor/TSL2591Sensor.h
+++ b/src/modules/Telemetry/Sensor/TSL2591Sensor.h
@@ -11,12 +11,9 @@ class TSL2591Sensor : public TelemetrySensor
   private:
     Adafruit_TSL2591 tsl;
 
-  protected:
-    virtual void setup() override;
-
   public:
     TSL2591Sensor();
-    virtual int32_t runOnce() override;
     virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
+    virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override;
 };
 #endif
\ No newline at end of file
diff --git a/src/modules/Telemetry/Sensor/TelemetrySensor.h b/src/modules/Telemetry/Sensor/TelemetrySensor.h
index 83d7b38b0..3c3e61808 100644
--- a/src/modules/Telemetry/Sensor/TelemetrySensor.h
+++ b/src/modules/Telemetry/Sensor/TelemetrySensor.h
@@ -6,6 +6,7 @@
 #include "../mesh/generated/meshtastic/telemetry.pb.h"
 #include "MeshModule.h"
 #include "NodeDB.h"
+#include "detect/ScanI2C.h"
 #include 
 
 #if !ARCH_PORTDUINO
@@ -42,22 +43,32 @@ class TelemetrySensor
         initialized = true;
         return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS;
     }
-    virtual void setup() = 0;
+
+    // TODO: check is setup used at all?
+    virtual void setup() {}
 
   public:
+    virtual ~TelemetrySensor() {}
+
     virtual AdminMessageHandleResult handleAdminMessage(const meshtastic_MeshPacket &mp, meshtastic_AdminMessage *request,
                                                         meshtastic_AdminMessage *response)
     {
         return AdminMessageHandleResult::NOT_HANDLED;
     }
 
+    // TODO: delete after migration
     bool hasSensor() { return nodeTelemetrySensorsMap[sensorType].first > 0; }
 
-    virtual int32_t runOnce() = 0;
+#if WIRE_INTERFACES_COUNT > 1
+    // Set to true if Implementation only works first I2C port (Wire)
+    virtual bool onlyWire1() { return false; }
+#endif
+    virtual int32_t runOnce() { return INT32_MAX; }
     virtual bool isInitialized() { return initialized; }
     virtual bool isRunning() { return status > 0; }
 
     virtual bool getMetrics(meshtastic_Telemetry *measurement) = 0;
+    virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) { return false; };
 };
 
 #endif
\ No newline at end of file
diff --git a/src/modules/Telemetry/Sensor/VEML7700Sensor.cpp b/src/modules/Telemetry/Sensor/VEML7700Sensor.cpp
index b075bf405..c89463be5 100644
--- a/src/modules/Telemetry/Sensor/VEML7700Sensor.cpp
+++ b/src/modules/Telemetry/Sensor/VEML7700Sensor.cpp
@@ -11,23 +11,22 @@
 
 VEML7700Sensor::VEML7700Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_VEML7700, "VEML7700") {}
 
-int32_t VEML7700Sensor::runOnce()
+bool VEML7700Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev)
 {
     LOG_INFO("Init sensor: %s", sensorName);
-    if (!hasSensor()) {
-        return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS;
+    status = veml7700.begin(bus);
+    if (!status) {
+        return status;
     }
-    status = veml7700.begin(nodeTelemetrySensorsMap[sensorType].second);
 
     veml7700.setLowThreshold(10000);
     veml7700.setHighThreshold(20000);
     veml7700.interruptEnable(true);
 
-    return initI2CSensor();
+    initI2CSensor();
+    return status;
 }
 
-void VEML7700Sensor::setup() {}
-
 /*!
  *    @brief Copmute lux from ALS reading.
  *    @param rawALS raw ALS register value
diff --git a/src/modules/Telemetry/Sensor/VEML7700Sensor.h b/src/modules/Telemetry/Sensor/VEML7700Sensor.h
index f40384ad3..92883df08 100644
--- a/src/modules/Telemetry/Sensor/VEML7700Sensor.h
+++ b/src/modules/Telemetry/Sensor/VEML7700Sensor.h
@@ -16,12 +16,9 @@ class VEML7700Sensor : public TelemetrySensor
     float computeLux(uint16_t rawALS, bool corrected);
     float getResolution(void);
 
-  protected:
-    virtual void setup() override;
-
   public:
     VEML7700Sensor();
-    virtual int32_t runOnce() override;
     virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
+    virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override;
 };
 #endif
\ No newline at end of file
diff --git a/src/modules/Telemetry/Sensor/nullSensor.cpp b/src/modules/Telemetry/Sensor/nullSensor.cpp
index c84b9d27f..1d545186a 100644
--- a/src/modules/Telemetry/Sensor/nullSensor.cpp
+++ b/src/modules/Telemetry/Sensor/nullSensor.cpp
@@ -11,7 +11,7 @@ NullSensor::NullSensor() : TelemetrySensor(meshtastic_TelemetrySensorType_SENSOR
 
 int32_t NullSensor::runOnce()
 {
-    return 0;
+    return INT32_MAX;
 }
 
 void NullSensor::setup() {}

From 37a0f774a2117487c45ef34f2efc96a7a06d1e45 Mon Sep 17 00:00:00 2001
From: Jason P 
Date: Mon, 13 Oct 2025 12:51:27 -0500
Subject: [PATCH 489/691] Fix multitude of warnings during builds (#8331)

---
 variants/nrf52840/meshtiny/variant.h | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/variants/nrf52840/meshtiny/variant.h b/variants/nrf52840/meshtiny/variant.h
index d1139b3be..55aabe930 100644
--- a/variants/nrf52840/meshtiny/variant.h
+++ b/variants/nrf52840/meshtiny/variant.h
@@ -19,7 +19,9 @@
 #ifndef _VARIANT_MESHTINY_
 #define _VARIANT_MESHTINY_
 
+#ifndef MESHTINY
 #define MESHTINY
+#endif
 
 /** Master clock frequency */
 #define VARIANT_MCK (64000000ul)

From 9ab9650248883c11995f4bf2cb6e68a5506bf605 Mon Sep 17 00:00:00 2001
From: Tom <116762865+NomDeTom@users.noreply.github.com>
Date: Mon, 13 Oct 2025 20:04:10 +0100
Subject: [PATCH 490/691] Update stale_bot.yml

Extend stale period to 60 days, and added a message on stale marking.
---
 .github/workflows/stale_bot.yml | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/.github/workflows/stale_bot.yml b/.github/workflows/stale_bot.yml
index a80619e90..4044278e8 100644
--- a/.github/workflows/stale_bot.yml
+++ b/.github/workflows/stale_bot.yml
@@ -19,6 +19,7 @@ jobs:
       - name: Stale PR+Issues
         uses: actions/stale@v10.1.0
         with:
-          days-before-stale: 45
+          days-before-stale: 60
+          stale-issue-message: 'This issue hasn not had any comment or update in the last 2 months. If it is still relevant, please post update comments. If no comments are made, this issue will be closed automagically in 7 days.'
           exempt-issue-labels: pinned,3.0
           exempt-pr-labels: pinned,3.0

From 910fe911f80fd713c62166d61e72cede64af53e4 Mon Sep 17 00:00:00 2001
From: Tom <116762865+NomDeTom@users.noreply.github.com>
Date: Mon, 13 Oct 2025 20:12:45 +0100
Subject: [PATCH 491/691] Update stale_bot.yml

---
 .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 4044278e8..8a0c61a80 100644
--- a/.github/workflows/stale_bot.yml
+++ b/.github/workflows/stale_bot.yml
@@ -20,6 +20,6 @@ jobs:
         uses: actions/stale@v10.1.0
         with:
           days-before-stale: 60
-          stale-issue-message: 'This issue hasn not had any comment or update in the last 2 months. If it is still relevant, please post update comments. If no comments are made, this issue will be closed automagically in 7 days.'
+          stale-issue-message: This issue hasn not had any comment or update in the last 2 months. If it is still relevant, please post update comments. If no comments are made, this issue will be closed automagically in 7 days.
           exempt-issue-labels: pinned,3.0
           exempt-pr-labels: pinned,3.0

From 5814f3e7d2efd4fd89e5f3946cb61310a0c7371b Mon Sep 17 00:00:00 2001
From: Ben Meadors 
Date: Mon, 13 Oct 2025 17:17:32 -0500
Subject: [PATCH 492/691] Revert "Fix Station G2 Lora Power Settings (#8273)"
 (#8332)

This reverts commit 05edcc5d6ce1ed755fe40013459b718bafe84e24.
---
 src/configuration.h | 5 -----
 1 file changed, 5 deletions(-)

diff --git a/src/configuration.h b/src/configuration.h
index b6b1c1e5e..c6c8d673c 100644
--- a/src/configuration.h
+++ b/src/configuration.h
@@ -126,11 +126,6 @@ along with this program.  If not, see .
 #define TX_GAIN_LORA 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 10, 10, 9, 9, 8, 7
 #endif
 
-#ifdef STATION_G2
-#define NUM_PA_POINTS 19
-#define TX_GAIN_LORA 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 19, 19, 18, 18
-#endif
-
 // Default system gain to 0 if not defined
 #ifndef TX_GAIN_LORA
 #define TX_GAIN_LORA 0

From c4d7ad2190fa41aa3eca4435391c91cf228a6c9d Mon Sep 17 00:00:00 2001
From: Ben Meadors 
Date: Mon, 13 Oct 2025 19:56:39 -0500
Subject: [PATCH 493/691] Kill github actions script

---
 bin/kill-github-actions.sh | 116 +++++++++++++++++++++++++++++++++++++
 1 file changed, 116 insertions(+)
 create mode 100755 bin/kill-github-actions.sh

diff --git a/bin/kill-github-actions.sh b/bin/kill-github-actions.sh
new file mode 100755
index 000000000..f71047c5e
--- /dev/null
+++ b/bin/kill-github-actions.sh
@@ -0,0 +1,116 @@
+#!/bin/bash
+
+# Script to cancel all running GitHub Actions workflows
+# Requires GitHub CLI (gh) to be installed and authenticated
+
+set -e
+
+# Colors for output
+RED='\033[0;31m'
+GREEN='\033[0;32m'
+YELLOW='\033[1;33m'
+NC='\033[0m' # No Color
+
+# Function to print colored output
+print_status() {
+    echo -e "${GREEN}[INFO]${NC} $1"
+}
+
+print_warning() {
+    echo -e "${YELLOW}[WARN]${NC} $1"
+}
+
+print_error() {
+    echo -e "${RED}[ERROR]${NC} $1"
+}
+
+# Check if gh CLI is installed
+if ! command -v gh &> /dev/null; then
+    print_error "GitHub CLI (gh) is not installed. Please install it first:"
+    echo "  brew install gh"
+    echo "  Or visit: https://cli.github.com/"
+    exit 1
+fi
+
+# Check if authenticated
+if ! gh auth status &> /dev/null; then
+    print_error "GitHub CLI is not authenticated. Please run:"
+    echo "  gh auth login"
+    exit 1
+fi
+
+# Get repository info
+REPO=$(gh repo view --json owner,name -q '.owner.login + "/" + .name')
+if [[ -z "$REPO" ]]; then
+    print_error "Could not determine repository. Make sure you're in a GitHub repository."
+    exit 1
+fi
+
+print_status "Working with repository: $REPO"
+
+# Get all active workflows (both queued and in-progress)
+print_status "Fetching active workflows (queued and in-progress)..."
+QUEUED_WORKFLOWS=$(gh run list --status queued --json databaseId,displayTitle,headBranch,status --limit 100)
+IN_PROGRESS_WORKFLOWS=$(gh run list --status in_progress --json databaseId,displayTitle,headBranch,status --limit 100)
+
+# Combine both lists
+ALL_WORKFLOWS=$(echo "$QUEUED_WORKFLOWS $IN_PROGRESS_WORKFLOWS" | jq -s 'add | unique_by(.databaseId)')
+
+if [[ "$ALL_WORKFLOWS" == "[]" ]]; then
+    print_status "No active workflows found."
+    exit 0
+fi
+
+# Parse and display active workflows
+echo
+print_warning "Found active workflows:"
+echo "$ALL_WORKFLOWS" | jq -r '.[] | "  - \(.displayTitle) (Branch: \(.headBranch), Status: \(.status), ID: \(.databaseId))"'
+
+echo
+read -p "Do you want to cancel ALL these workflows? (y/N): " -n 1 -r
+echo
+
+if [[ ! $REPLY =~ ^[Yy]$ ]]; then
+    print_status "Cancelled by user."
+    exit 0
+fi
+
+# Cancel each workflow
+print_status "Cancelling workflows..."
+CANCELLED_COUNT=0
+FAILED_COUNT=0
+
+while IFS= read -r WORKFLOW_ID; do
+    if [[ -n "$WORKFLOW_ID" ]]; then
+        print_status "Cancelling workflow ID: $WORKFLOW_ID"
+        if gh run cancel "$WORKFLOW_ID" 2>/dev/null; then
+            ((CANCELLED_COUNT++))
+        else
+            print_error "Failed to cancel workflow ID: $WORKFLOW_ID"
+            ((FAILED_COUNT++))
+        fi
+    fi
+done < <(echo "$ALL_WORKFLOWS" | jq -r '.[].databaseId')
+
+echo
+print_status "Summary:"
+echo "  - Cancelled: $CANCELLED_COUNT workflows"
+if [[ $FAILED_COUNT -gt 0 ]]; then
+    echo "  - Failed: $FAILED_COUNT workflows"
+fi
+
+print_status "Done!"
+
+# Optional: Show remaining active workflows
+echo
+print_status "Checking for any remaining active workflows..."
+REMAINING_QUEUED=$(gh run list --status queued --json databaseId --limit 10)
+REMAINING_IN_PROGRESS=$(gh run list --status in_progress --json databaseId --limit 10)
+REMAINING_ALL=$(echo "$REMAINING_QUEUED $REMAINING_IN_PROGRESS" | jq -s 'add | unique_by(.databaseId)')
+
+if [[ "$REMAINING_ALL" == "[]" ]]; then
+    print_status "All workflows successfully cancelled."
+else
+    REMAINING_COUNT=$(echo "$REMAINING_ALL" | jq '. | length')
+    print_warning "Still $REMAINING_COUNT workflows active (may take a moment to update status)"
+fi
\ No newline at end of file

From b8bfed28105c1ea2918449c30ec3ab1802c512dd Mon Sep 17 00:00:00 2001
From: Tom <116762865+NomDeTom@users.noreply.github.com>
Date: Tue, 14 Oct 2025 12:37:40 +0100
Subject: [PATCH 494/691] return to 45 days and put a closure message.

---
 .github/workflows/stale_bot.yml | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/.github/workflows/stale_bot.yml b/.github/workflows/stale_bot.yml
index 8a0c61a80..b528f80b2 100644
--- a/.github/workflows/stale_bot.yml
+++ b/.github/workflows/stale_bot.yml
@@ -19,7 +19,8 @@ jobs:
       - name: Stale PR+Issues
         uses: actions/stale@v10.1.0
         with:
-          days-before-stale: 60
-          stale-issue-message: This issue hasn not had any comment or update in the last 2 months. If it is still relevant, please post update comments. If no comments are made, this issue will be closed automagically in 7 days.
+          days-before-stale: 45
+          stale-issue-message: This issue has not had any comment or update in the last month. If it is still relevant, please post update comments. If no comments are made, this issue will be closed automagically in 7 days.
+          close-issue-message: This issue has not had any comment since the last notice. It has been closed automatically. If this is incorrect, or the issue becomes relevant again, please request that it is reopened.
           exempt-issue-labels: pinned,3.0
           exempt-pr-labels: pinned,3.0

From e8f4d07e9f6876317cbb47296888e782ea6d94f1 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Tue, 14 Oct 2025 15:45:33 -0500
Subject: [PATCH 495/691] Update meshtastic/device-ui digest to 19b7855 (#8346)

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 5b7f5ddcf..376f6e5a8 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -120,7 +120,7 @@ lib_deps =
 [device-ui_base]
 lib_deps =
 	# renovate: datasource=git-refs depName=meshtastic/device-ui packageName=https://github.com/meshtastic/device-ui gitBranch=master
-	https://github.com/meshtastic/device-ui/archive/3fb7c0e28e8e51fc0a7d56facacf3411f1d29fe0.zip
+	https://github.com/meshtastic/device-ui/archive/19b7855e9a1d9deff37391659ca7194e4ef57c43.zip
 
 ; Common libs for environmental measurements in telemetry module
 [environmental_base]

From 9a8aeb25abeb49065c1fbdb65cd228717cb117bb Mon Sep 17 00:00:00 2001
From: Erayd 
Date: Wed, 15 Oct 2025 13:22:45 +1300
Subject: [PATCH 496/691] Add a general-purpose packet cache (#8341)

* Add general-purpose packet cache

This commit adds a caching system that will save packet data in a much
more compact form than the regular MeshPacket protobuf. It cannot be
worked with directly to the same degree (although the packet header is
available), but consumes *much* less memory, and as a result can be used
to temporarily store large numbers of packets.

Cached packets can be retrieved either by their (from, id) tuple, or by
their hash.

This cache is a pre-requisite for the upcoming packet replay feature.

* Remove debug initialiser

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Fix ordering

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Add missing size assignment

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Add comments for hash & bucket macros

* Make it clear that this field stores a map of the original data

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Ben Meadors 
---
 src/mesh/PacketCache.cpp | 253 +++++++++++++++++++++++++++++++++++++++
 src/mesh/PacketCache.h   |  75 ++++++++++++
 2 files changed, 328 insertions(+)
 create mode 100644 src/mesh/PacketCache.cpp
 create mode 100644 src/mesh/PacketCache.h

diff --git a/src/mesh/PacketCache.cpp b/src/mesh/PacketCache.cpp
new file mode 100644
index 000000000..0edf81741
--- /dev/null
+++ b/src/mesh/PacketCache.cpp
@@ -0,0 +1,253 @@
+#include "PacketCache.h"
+#include "Router.h"
+
+PacketCache packetCache{};
+
+/**
+ * Allocate a new cache entry and copy the packet header and payload into it
+ */
+PacketCacheEntry *PacketCache::cache(const meshtastic_MeshPacket *p, bool preserveMetadata)
+{
+    size_t payload_size =
+        (p->which_payload_variant == meshtastic_MeshPacket_encrypted_tag) ? p->encrypted.size : p->decoded.payload.size;
+    PacketCacheEntry *e = (PacketCacheEntry *)malloc(sizeof(PacketCacheEntry) + payload_size +
+                                                     (preserveMetadata ? sizeof(PacketCacheMetadata) : 0));
+    if (!e) {
+        LOG_ERROR("Unable to allocate memory for packet cache entry");
+        return NULL;
+    }
+
+    *e = {};
+    e->header.from = p->from;
+    e->header.to = p->to;
+    e->header.id = p->id;
+    e->header.channel = p->channel;
+    e->header.next_hop = p->next_hop;
+    e->header.relay_node = p->relay_node;
+    e->header.flags = (p->hop_limit & PACKET_FLAGS_HOP_LIMIT_MASK) | (p->want_ack ? PACKET_FLAGS_WANT_ACK_MASK : 0) |
+                      (p->via_mqtt ? PACKET_FLAGS_VIA_MQTT_MASK : 0) |
+                      ((p->hop_start << PACKET_FLAGS_HOP_START_SHIFT) & PACKET_FLAGS_HOP_START_MASK);
+
+    PacketCacheMetadata m{};
+    if (preserveMetadata) {
+        e->has_metadata = true;
+        m.rx_rssi = (uint8_t)(p->rx_rssi + 200);
+        m.rx_snr = (uint8_t)((p->rx_snr + 30.0f) / 0.25f);
+        m.rx_time = p->rx_time;
+        m.transport_mechanism = p->transport_mechanism;
+        m.priority = p->priority;
+    }
+    if (p->which_payload_variant == meshtastic_MeshPacket_encrypted_tag) {
+        e->encrypted = true;
+        e->payload_len = p->encrypted.size;
+        memcpy(((unsigned char *)e) + sizeof(PacketCacheEntry), p->encrypted.bytes, p->encrypted.size);
+    } else if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) {
+        e->encrypted = false;
+        if (preserveMetadata) {
+            m.portnum = p->decoded.portnum;
+            m.want_response = p->decoded.want_response;
+            m.emoji = p->decoded.emoji;
+            m.bitfield = p->decoded.bitfield;
+            if (p->decoded.reply_id)
+                m.reply_id = p->decoded.reply_id;
+            else if (p->decoded.request_id)
+                m.request_id = p->decoded.request_id;
+        }
+        e->payload_len = p->decoded.payload.size;
+        memcpy(((unsigned char *)e) + sizeof(PacketCacheEntry), p->decoded.payload.bytes, p->decoded.payload.size);
+    } else {
+        LOG_ERROR("Unable to cache packet with unknown payload type %d", p->which_payload_variant);
+        free(e);
+        return NULL;
+    }
+    if (preserveMetadata)
+        memcpy(((unsigned char *)e) + sizeof(PacketCacheEntry) + e->payload_len, &m, sizeof(m));
+
+    size += sizeof(PacketCacheEntry) + e->payload_len + (e->has_metadata ? sizeof(PacketCacheMetadata) : 0);
+    insert(e);
+    return e;
+};
+
+/**
+ * Dump a list of packets into the provided buffer
+ */
+void PacketCache::dump(void *dest, const PacketCacheEntry **entries, size_t num_entries)
+{
+    unsigned char *pos = (unsigned char *)dest;
+    for (size_t i = 0; i < num_entries; i++) {
+        size_t entry_len =
+            sizeof(PacketCacheEntry) + entries[i]->payload_len + (entries[i]->has_metadata ? sizeof(PacketCacheMetadata) : 0);
+        memcpy(pos, entries[i], entry_len);
+        pos += entry_len;
+    }
+}
+
+/**
+ * Calculate the length of buffer needed to dump the specified entries
+ */
+size_t PacketCache::dumpSize(const PacketCacheEntry **entries, size_t num_entries)
+{
+    size_t total_size = 0;
+    for (size_t i = 0; i < num_entries; i++) {
+        total_size += sizeof(PacketCacheEntry) + entries[i]->payload_len;
+        if (entries[i]->has_metadata)
+            total_size += sizeof(PacketCacheMetadata);
+    }
+    return total_size;
+}
+
+/**
+ * Find a packet in the cache
+ */
+PacketCacheEntry *PacketCache::find(NodeNum from, PacketId id)
+{
+    uint16_t h = PACKET_HASH(from, id);
+    PacketCacheEntry *e = buckets[PACKET_CACHE_BUCKET(h)];
+    while (e) {
+        if (e->header.from == from && e->header.id == id)
+            return e;
+        e = e->next;
+    }
+    return NULL;
+}
+
+/**
+ * Find a packet in the cache by its hash
+ */
+PacketCacheEntry *PacketCache::find(PacketHash h)
+{
+    PacketCacheEntry *e = buckets[PACKET_CACHE_BUCKET(h)];
+    while (e) {
+        if (PACKET_HASH(e->header.from, e->header.id) == h)
+            return e;
+        e = e->next;
+    }
+    return NULL;
+}
+
+/**
+ * Load a list of packets from the provided buffer
+ */
+bool PacketCache::load(void *src, PacketCacheEntry **entries, size_t num_entries)
+{
+    memset(entries, 0, sizeof(PacketCacheEntry *) * num_entries);
+    unsigned char *pos = (unsigned char *)src;
+    for (size_t i = 0; i < num_entries; i++) {
+        PacketCacheEntry e{};
+        memcpy(&e, pos, sizeof(PacketCacheEntry));
+        size_t entry_len = sizeof(PacketCacheEntry) + e.payload_len + (e.has_metadata ? sizeof(PacketCacheMetadata) : 0);
+        entries[i] = (PacketCacheEntry *)malloc(entry_len);
+        size += entry_len;
+        if (!entries[i]) {
+            LOG_ERROR("Unable to allocate memory for packet cache entry");
+            for (size_t j = 0; j < i; j++) {
+                size -= sizeof(PacketCacheEntry) + entries[j]->payload_len +
+                        (entries[j]->has_metadata ? sizeof(PacketCacheMetadata) : 0);
+                free(entries[j]);
+                entries[j] = NULL;
+            }
+            return false;
+        }
+        memcpy(entries[i], pos, entry_len);
+        pos += entry_len;
+    }
+    for (size_t i = 0; i < num_entries; i++)
+        insert(entries[i]);
+    return true;
+}
+
+/**
+ * Copy the cached packet into the provided MeshPacket structure
+ */
+void PacketCache::rehydrate(const PacketCacheEntry *e, meshtastic_MeshPacket *p)
+{
+    if (!e || !p)
+        return;
+
+    *p = {};
+    p->from = e->header.from;
+    p->to = e->header.to;
+    p->id = e->header.id;
+    p->channel = e->header.channel;
+    p->next_hop = e->header.next_hop;
+    p->relay_node = e->header.relay_node;
+    p->hop_limit = e->header.flags & PACKET_FLAGS_HOP_LIMIT_MASK;
+    p->want_ack = !!(e->header.flags & PACKET_FLAGS_WANT_ACK_MASK);
+    p->via_mqtt = !!(e->header.flags & PACKET_FLAGS_VIA_MQTT_MASK);
+    p->hop_start = (e->header.flags & PACKET_FLAGS_HOP_START_MASK) >> PACKET_FLAGS_HOP_START_SHIFT;
+    p->which_payload_variant = e->encrypted ? meshtastic_MeshPacket_encrypted_tag : meshtastic_MeshPacket_decoded_tag;
+
+    unsigned char *payload = ((unsigned char *)e) + sizeof(PacketCacheEntry);
+    PacketCacheMetadata m{};
+    if (e->has_metadata) {
+        memcpy(&m, (payload + e->payload_len), sizeof(m));
+        p->rx_rssi = ((int)m.rx_rssi) - 200;
+        p->rx_snr = ((float)m.rx_snr * 0.25f) - 30.0f;
+        p->rx_time = m.rx_time;
+        p->transport_mechanism = (meshtastic_MeshPacket_TransportMechanism)m.transport_mechanism;
+        p->priority = (meshtastic_MeshPacket_Priority)m.priority;
+    }
+    if (e->encrypted) {
+        memcpy(p->encrypted.bytes, payload, e->payload_len);
+        p->encrypted.size = e->payload_len;
+    } else {
+        memcpy(p->decoded.payload.bytes, payload, e->payload_len);
+        p->decoded.payload.size = e->payload_len;
+        if (e->has_metadata) {
+            // Decrypted-only metadata
+            p->decoded.portnum = (meshtastic_PortNum)m.portnum;
+            p->decoded.want_response = m.want_response;
+            p->decoded.emoji = m.emoji;
+            p->decoded.bitfield = m.bitfield;
+            if (m.reply_id)
+                p->decoded.reply_id = m.reply_id;
+            else if (m.request_id)
+                p->decoded.request_id = m.request_id;
+        }
+    }
+}
+
+/**
+ * Release a cache entry
+ */
+void PacketCache::release(PacketCacheEntry *e)
+{
+    if (!e)
+        return;
+    remove(e);
+    size -= sizeof(PacketCacheEntry) + e->payload_len + (e->has_metadata ? sizeof(PacketCacheMetadata) : 0);
+    free(e);
+}
+
+/**
+ * Insert a new entry into the hash table
+ */
+void PacketCache::insert(PacketCacheEntry *e)
+{
+    assert(e);
+    PacketHash h = PACKET_HASH(e->header.from, e->header.id);
+    PacketCacheEntry **target = &buckets[PACKET_CACHE_BUCKET(h)];
+    e->next = *target;
+    *target = e;
+    num_entries++;
+}
+
+/**
+ * Remove an entry from the hash table
+ */
+void PacketCache::remove(PacketCacheEntry *e)
+{
+    assert(e);
+    PacketHash h = PACKET_HASH(e->header.from, e->header.id);
+    PacketCacheEntry **target = &buckets[PACKET_CACHE_BUCKET(h)];
+    while (*target) {
+        if (*target == e) {
+            *target = e->next;
+            e->next = NULL;
+            num_entries--;
+            break;
+        } else {
+            target = &(*target)->next;
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/mesh/PacketCache.h b/src/mesh/PacketCache.h
new file mode 100644
index 000000000..81ad455da
--- /dev/null
+++ b/src/mesh/PacketCache.h
@@ -0,0 +1,75 @@
+#pragma once
+#include "RadioInterface.h"
+
+#define PACKET_HASH(a, b) ((((a ^ b) >> 16) ^ (a ^ b)) & 0xFFFF) // 16 bit fold of packet (from, id) tuple
+typedef uint16_t PacketHash;
+
+#define PACKET_CACHE_BUCKETS 64                                    // Number of hash table buckets
+#define PACKET_CACHE_BUCKET(h) (((h >> 12) ^ (h >> 6) ^ h) & 0x3F) // Fold hash down to 6-bit bucket index
+
+typedef struct PacketCacheEntry {
+    PacketCacheEntry *next;
+    PacketHeader header;
+    uint16_t payload_len = 0;
+    union {
+        uint16_t bitfield;
+        struct {
+            uint8_t encrypted : 1;    // Payload is encrypted
+            uint8_t has_metadata : 1; // Payload includes PacketCacheMetadata
+            uint8_t : 6;              // Reserved for future use
+            uint16_t : 8;             // Reserved for future use
+        };
+    };
+} PacketCacheEntry;
+
+typedef struct PacketCacheMetadata {
+    PacketCacheMetadata() : _bitfield(0), reply_id(0), _bitfield2(0) {}
+    union {
+        uint32_t _bitfield;
+        struct {
+            uint16_t portnum : 9;       // meshtastic_MeshPacket::decoded::portnum
+            uint16_t want_response : 1; // meshtastic_MeshPacket::decoded::want_response
+            uint16_t emoji : 1;         // meshtastic_MeshPacket::decoded::emoji
+            uint16_t bitfield : 5;      // meshtastic_MeshPacket::decoded::bitfield (truncated)
+            uint8_t rx_rssi : 8;        // meshtastic_MeshPacket::rx_rssi (map via actual RSSI + 200)
+            uint8_t rx_snr : 8;         // meshtastic_MeshPacket::rx_snr (map via (p->rx_snr + 30.0f) / 0.25f)
+        };
+    };
+    union {
+        uint32_t reply_id;   // meshtastic_MeshPacket::decoded.reply_id
+        uint32_t request_id; // meshtastic_MeshPacket::decoded.request_id
+    };
+    uint32_t rx_time = 0;            // meshtastic_MeshPacket::rx_time
+    uint8_t transport_mechanism = 0; // meshtastic_MeshPacket::transport_mechanism
+    struct {
+        uint8_t _bitfield2;
+        union {
+            uint8_t priority : 7; // meshtastic_MeshPacket::priority
+            uint8_t reserved : 1; // Reserved for future use
+        };
+    };
+} PacketCacheMetadata;
+
+class PacketCache
+{
+  public:
+    PacketCacheEntry *cache(const meshtastic_MeshPacket *p, bool preserveMetadata);
+    static void dump(void *dest, const PacketCacheEntry **entries, size_t num_entries);
+    size_t dumpSize(const PacketCacheEntry **entries, size_t num_entries);
+    PacketCacheEntry *find(NodeNum from, PacketId id);
+    PacketCacheEntry *find(PacketHash h);
+    bool load(void *src, PacketCacheEntry **entries, size_t num_entries);
+    size_t getNumEntries() { return num_entries; }
+    size_t getSize() { return size; }
+    void rehydrate(const PacketCacheEntry *e, meshtastic_MeshPacket *p);
+    void release(PacketCacheEntry *e);
+
+  private:
+    PacketCacheEntry *buckets[PACKET_CACHE_BUCKETS]{};
+    size_t num_entries = 0;
+    size_t size = 0;
+    void insert(PacketCacheEntry *e);
+    void remove(PacketCacheEntry *e);
+};
+
+extern PacketCache packetCache;
\ No newline at end of file

From a6df18e60a78d7ad5600ea920fa0b1063692f5a9 Mon Sep 17 00:00:00 2001
From: Clive Blackledge 
Date: Wed, 15 Oct 2025 04:08:06 -0700
Subject: [PATCH 497/691] Guarding PhoneAPI node-info staging with mutex to
 prevent BLE future foot-gun (#8354)

* Eliminating foot-gun and placing Phone NodeInfo into a mutex

* Swapping over to concurrency::Lock from mutex
---
 src/mesh/PhoneAPI.cpp          | 103 ++++++++++++++++++++++-----------
 src/mesh/PhoneAPI.h            |   3 +
 src/nimble/NimbleBluetooth.cpp |   2 +-
 3 files changed, 72 insertions(+), 36 deletions(-)

diff --git a/src/mesh/PhoneAPI.cpp b/src/mesh/PhoneAPI.cpp
index 9eeadf5a2..36bd577e1 100644
--- a/src/mesh/PhoneAPI.cpp
+++ b/src/mesh/PhoneAPI.cpp
@@ -15,6 +15,7 @@
 #include "Router.h"
 #include "SPILock.h"
 #include "TypeConversions.h"
+#include "concurrency/LockGuard.h"
 #include "main.h"
 #include "xmodem.h"
 
@@ -71,8 +72,12 @@ void PhoneAPI::handleStartConfig()
     LOG_DEBUG("Got %d files in manifest", filesManifest.size());
 
     LOG_INFO("Start API client config");
-    nodeInfoForPhone.num = 0; // Don't keep returning old nodeinfos
-    nodeInfoQueue.clear();
+    // Protect against concurrent BLE callbacks: they run in NimBLE's FreeRTOS task and also touch nodeInfoQueue.
+    {
+        concurrency::LockGuard guard(&nodeInfoMutex);
+        nodeInfoForPhone = {};
+        nodeInfoQueue.clear();
+    }
     resetReadIndex();
 }
 
@@ -94,8 +99,12 @@ void PhoneAPI::close()
         onConnectionChanged(false);
         fromRadioScratch = {};
         toRadioScratch = {};
-        nodeInfoForPhone = {};
-        nodeInfoQueue.clear();
+        // Clear cached node info under lock because NimBLE callbacks can still be draining it.
+        {
+            concurrency::LockGuard guard(&nodeInfoMutex);
+            nodeInfoForPhone = {};
+            nodeInfoQueue.clear();
+        }
         packetForPhone = NULL;
         filesManifest.clear();
         fromRadioNum = 0;
@@ -241,13 +250,20 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
         LOG_DEBUG("Send My NodeInfo");
         auto us = nodeDB->readNextMeshNode(readIndex);
         if (us) {
-            nodeInfoForPhone = TypeConversions::ConvertToNodeInfo(us);
-            nodeInfoForPhone.has_hops_away = false;
-            nodeInfoForPhone.is_favorite = true;
+            auto info = TypeConversions::ConvertToNodeInfo(us);
+            info.has_hops_away = false;
+            info.is_favorite = true;
+            {
+                concurrency::LockGuard guard(&nodeInfoMutex);
+                nodeInfoForPhone = info;
+            }
             fromRadioScratch.which_payload_variant = meshtastic_FromRadio_node_info_tag;
-            fromRadioScratch.node_info = nodeInfoForPhone;
+            fromRadioScratch.node_info = info;
             // Should allow us to resume sending NodeInfo in STATE_SEND_OTHER_NODEINFOS
-            nodeInfoForPhone.num = 0;
+            {
+                concurrency::LockGuard guard(&nodeInfoMutex);
+                nodeInfoForPhone.num = 0;
+            }
         }
         if (config_nonce == SPECIAL_NONCE_ONLY_NODES) {
             // If client only wants node info, jump directly to sending nodes
@@ -434,23 +450,30 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
 
     case STATE_SEND_OTHER_NODEINFOS: {
         LOG_DEBUG("Send known nodes");
-        if (nodeInfoForPhone.num == 0 && !nodeInfoQueue.empty()) {
-            // Serve the next cached node without re-reading from the DB iterator.
-            nodeInfoForPhone = nodeInfoQueue.front();
-            nodeInfoQueue.pop_front();
+        meshtastic_NodeInfo infoToSend = {};
+        {
+            concurrency::LockGuard guard(&nodeInfoMutex);
+            if (nodeInfoForPhone.num == 0 && !nodeInfoQueue.empty()) {
+                // Serve the next cached node without re-reading from the DB iterator.
+                nodeInfoForPhone = nodeInfoQueue.front();
+                nodeInfoQueue.pop_front();
+            }
+            infoToSend = nodeInfoForPhone;
+            if (infoToSend.num != 0)
+                nodeInfoForPhone = {};
         }
 
-        if (nodeInfoForPhone.num != 0) {
+        if (infoToSend.num != 0) {
             // Just in case we stored a different user.id in the past, but should never happen going forward
-            sprintf(nodeInfoForPhone.user.id, "!%08x", nodeInfoForPhone.num);
-            LOG_DEBUG("nodeinfo: num=0x%x, lastseen=%u, id=%s, name=%s", nodeInfoForPhone.num, nodeInfoForPhone.last_heard,
-                      nodeInfoForPhone.user.id, nodeInfoForPhone.user.long_name);
+            sprintf(infoToSend.user.id, "!%08x", infoToSend.num);
+            LOG_DEBUG("nodeinfo: num=0x%x, lastseen=%u, id=%s, name=%s", infoToSend.num, infoToSend.last_heard,
+                      infoToSend.user.id, infoToSend.user.long_name);
             fromRadioScratch.which_payload_variant = meshtastic_FromRadio_node_info_tag;
-            fromRadioScratch.node_info = nodeInfoForPhone;
-            nodeInfoForPhone = {};
+            fromRadioScratch.node_info = infoToSend;
             prefetchNodeInfos();
         } else {
             LOG_DEBUG("Done sending nodeinfo");
+            concurrency::LockGuard guard(&nodeInfoMutex);
             nodeInfoQueue.clear();
             state = STATE_SEND_FILEMANIFEST;
             // Go ahead and send that ID right now
@@ -559,20 +582,23 @@ void PhoneAPI::prefetchNodeInfos()
 {
     bool added = false;
     // Keep the queue topped up so BLE reads stay responsive even if DB fetches take a moment.
-    while (nodeInfoQueue.size() < kNodePrefetchDepth) {
-        auto nextNode = nodeDB->readNextMeshNode(readIndex);
-        if (!nextNode)
-            break;
+    {
+        concurrency::LockGuard guard(&nodeInfoMutex);
+        while (nodeInfoQueue.size() < kNodePrefetchDepth) {
+            auto nextNode = nodeDB->readNextMeshNode(readIndex);
+            if (!nextNode)
+                break;
 
-        auto info = TypeConversions::ConvertToNodeInfo(nextNode);
-        bool isUs = info.num == nodeDB->getNodeNum();
-        info.hops_away = isUs ? 0 : info.hops_away;
-        info.last_heard = isUs ? getValidTime(RTCQualityFromNet) : info.last_heard;
-        info.snr = isUs ? 0 : info.snr;
-        info.via_mqtt = isUs ? false : info.via_mqtt;
-        info.is_favorite = info.is_favorite || isUs;
-        nodeInfoQueue.push_back(info);
-        added = true;
+            auto info = TypeConversions::ConvertToNodeInfo(nextNode);
+            bool isUs = info.num == nodeDB->getNodeNum();
+            info.hops_away = isUs ? 0 : info.hops_away;
+            info.last_heard = isUs ? getValidTime(RTCQualityFromNet) : info.last_heard;
+            info.snr = isUs ? 0 : info.snr;
+            info.via_mqtt = isUs ? false : info.via_mqtt;
+            info.is_favorite = info.is_favorite || isUs;
+            nodeInfoQueue.push_back(info);
+            added = true;
+        }
     }
 
     if (added)
@@ -614,10 +640,17 @@ bool PhoneAPI::available()
     case STATE_SEND_COMPLETE_ID:
         return true;
 
-    case STATE_SEND_OTHER_NODEINFOS:
-        if (nodeInfoQueue.empty())
-            prefetchNodeInfos();
+    case STATE_SEND_OTHER_NODEINFOS: {
+        concurrency::LockGuard guard(&nodeInfoMutex);
+        if (nodeInfoQueue.empty()) {
+            // Drop the lock before prefetching; prefetchNodeInfos() will re-acquire it.
+            goto PREFETCH_NODEINFO;
+        }
+    }
         return true; // Always say we have something, because we might need to advance our state machine
+    PREFETCH_NODEINFO:
+        prefetchNodeInfos();
+        return true;
     case STATE_SEND_PACKETS: {
         if (!queueStatusPacketForPhone)
             queueStatusPacketForPhone = service->getQueueStatusForPhone();
diff --git a/src/mesh/PhoneAPI.h b/src/mesh/PhoneAPI.h
index 692fdd0b9..a8d0faa28 100644
--- a/src/mesh/PhoneAPI.h
+++ b/src/mesh/PhoneAPI.h
@@ -1,6 +1,7 @@
 #pragma once
 
 #include "Observer.h"
+#include "concurrency/Lock.h"
 #include "mesh-pb-constants.h"
 #include "meshtastic/portnums.pb.h"
 #include 
@@ -84,6 +85,8 @@ class PhoneAPI
     std::deque nodeInfoQueue;
     // Tunable size of the node info cache so we can keep BLE reads non-blocking.
     static constexpr size_t kNodePrefetchDepth = 4;
+    // Protect nodeInfoForPhone + nodeInfoQueue because NimBLE callbacks run in a separate FreeRTOS task.
+    concurrency::Lock nodeInfoMutex;
 
     meshtastic_ToRadio toRadioScratch = {
         0}; // this is a static scratch object, any data must be copied elsewhere before returning
diff --git a/src/nimble/NimbleBluetooth.cpp b/src/nimble/NimbleBluetooth.cpp
index 4b0c33609..eb1d909f1 100644
--- a/src/nimble/NimbleBluetooth.cpp
+++ b/src/nimble/NimbleBluetooth.cpp
@@ -139,7 +139,7 @@ class NimbleBluetoothFromRadioCallback : public NimBLECharacteristicCallbacks
     {
         bluetoothPhoneAPI->phoneWants = true;
         bluetoothPhoneAPI->setIntervalFromNow(0);
-        std::lock_guard guard(bluetoothPhoneAPI->nimble_mutex);
+        std::lock_guard guard(bluetoothPhoneAPI->nimble_mutex); // BLE callbacks run in NimBLE task
 
         if (!bluetoothPhoneAPI->hasChecked) {
             // Fetch payload on demand; prefetch keeps this fast for the first read.

From 858e8c6fefba6c2fc1da588c5a1ff8335f82e827 Mon Sep 17 00:00:00 2001
From: Mike Weaver 
Date: Wed, 15 Oct 2025 05:15:57 -0600
Subject: [PATCH 498/691] portduino, handle sdl2 builds (#8355)

fix linux native build by adding sdl2 libraries
---
 variants/native/portduino/platformio.ini | 1 +
 1 file changed, 1 insertion(+)

diff --git a/variants/native/portduino/platformio.ini b/variants/native/portduino/platformio.ini
index 4e6a592de..49a8a71c7 100644
--- a/variants/native/portduino/platformio.ini
+++ b/variants/native/portduino/platformio.ini
@@ -17,6 +17,7 @@ extends = native_base
 build_flags = ${native_base.build_flags}
   !pkg-config --libs libulfius --silence-errors || :
   !pkg-config --libs openssl --silence-errors || :
+  !pkg-config --cflags --libs sdl2 --silence-errors || :
 
 [env:native-tft]
 extends = native_base

From 4e0a4cc45feb9bc5e47ae40eeeed7001d8807cac Mon Sep 17 00:00:00 2001
From: Jonathan Bennett 
Date: Wed, 15 Oct 2025 06:34:28 -0500
Subject: [PATCH 499/691] Log the lora frequency error when receiving a packet.
 (#8343)

---
 src/mesh/LR11x0Interface.cpp | 1 +
 src/mesh/SX126xInterface.cpp | 1 +
 src/mesh/SX128xInterface.cpp | 1 +
 3 files changed, 3 insertions(+)

diff --git a/src/mesh/LR11x0Interface.cpp b/src/mesh/LR11x0Interface.cpp
index 0e23405e5..3831a384d 100644
--- a/src/mesh/LR11x0Interface.cpp
+++ b/src/mesh/LR11x0Interface.cpp
@@ -218,6 +218,7 @@ template  void LR11x0Interface::addReceiveMetadata(meshtastic_Mes
     // LOG_DEBUG("PacketStatus %x", lora.getPacketStatus());
     mp->rx_snr = lora.getSNR();
     mp->rx_rssi = lround(lora.getRSSI());
+    LOG_DEBUG("Corrected frequency offset: %f", lora.getFrequencyError());
 }
 
 /** We override to turn on transmitter power as needed.
diff --git a/src/mesh/SX126xInterface.cpp b/src/mesh/SX126xInterface.cpp
index c60ef3a80..e1f07a32b 100644
--- a/src/mesh/SX126xInterface.cpp
+++ b/src/mesh/SX126xInterface.cpp
@@ -266,6 +266,7 @@ template  void SX126xInterface::addReceiveMetadata(meshtastic_Mes
     // LOG_DEBUG("PacketStatus %x", lora.getPacketStatus());
     mp->rx_snr = lora.getSNR();
     mp->rx_rssi = lround(lora.getRSSI());
+    LOG_DEBUG("Corrected frequency offset: %f", lora.getFrequencyError());
 }
 
 /** We override to turn on transmitter power as needed.
diff --git a/src/mesh/SX128xInterface.cpp b/src/mesh/SX128xInterface.cpp
index cbc98eeb1..80872af07 100644
--- a/src/mesh/SX128xInterface.cpp
+++ b/src/mesh/SX128xInterface.cpp
@@ -204,6 +204,7 @@ template  void SX128xInterface::addReceiveMetadata(meshtastic_Mes
     // LOG_DEBUG("PacketStatus %x", lora.getPacketStatus());
     mp->rx_snr = lora.getSNR();
     mp->rx_rssi = lround(lora.getRSSI());
+    LOG_DEBUG("Corrected frequency offset: %f", lora.getFrequencyError());
 }
 
 /** We override to turn on transmitter power as needed.

From ec5a54c52377bbbdb13358ae971eeabe37820537 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Pawe=C5=82=20Czaplewski?=
 <28847593+Paplewski@users.noreply.github.com>
Date: Wed, 15 Oct 2025 23:19:47 +0200
Subject: [PATCH 500/691] bind python version to 3.13 (#8362)

---
 .devcontainer/devcontainer.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
index bf1c50982..bc660170c 100644
--- a/.devcontainer/devcontainer.json
+++ b/.devcontainer/devcontainer.json
@@ -8,7 +8,7 @@
   "features": {
     "ghcr.io/devcontainers/features/python:1": {
       "installTools": true,
-      "version": "latest"
+      "version": "3.13"
     }
   },
   "customizations": {

From 51b3b937dcbfa8c3648f4125e399d9ece1d8663b Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Thu, 16 Oct 2025 08:55:11 +1100
Subject: [PATCH 501/691] Update actions/setup-node action to v6 (#8339)

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 942659348..7852fc31f 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@v5
+        uses: actions/setup-node@v6
         with:
           node-version: 22
 

From cd3b31c5dab4bda7e8fc99f503d7635496324bc6 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
 <41898282+github-actions[bot]@users.noreply.github.com>
Date: Thu, 16 Oct 2025 08:56:39 +1100
Subject: [PATCH 502/691] Upgrade trunk (#8340)

Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com>
---
 .trunk/trunk.yaml | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml
index 637372700..5bec39ae2 100644
--- a/.trunk/trunk.yaml
+++ b/.trunk/trunk.yaml
@@ -8,20 +8,20 @@ plugins:
       uri: https://github.com/trunk-io/plugins
 lint:
   enabled:
-    - checkov@3.2.477
-    - renovate@41.144.1
+    - checkov@3.2.483
+    - renovate@41.148.2
     - prettier@3.6.2
     - trufflehog@3.90.8
     - yamllint@1.37.1
     - bandit@1.8.6
-    - trivy@0.67.1
+    - trivy@0.67.2
     - taplo@0.10.0
     - ruff@0.14.0
-    - isort@6.1.0
+    - isort@7.0.0
     - markdownlint@0.45.0
     - oxipng@9.1.5
     - svgo@4.0.0
-    - actionlint@1.7.7
+    - actionlint@1.7.8
     - flake8@7.3.0
     - hadolint@2.14.0
     - shfmt@3.6.0

From 865b46ceefb4b096f594baefae9d5c1aa739cef1 Mon Sep 17 00:00:00 2001
From: Ben Meadors 
Date: Thu, 16 Oct 2025 06:07:38 -0500
Subject: [PATCH 503/691] Ignore MQTT Client Proxy messages while not in
 sendpackets state (#8358)

---
 src/mesh/PhoneAPI.cpp | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/src/mesh/PhoneAPI.cpp b/src/mesh/PhoneAPI.cpp
index 36bd577e1..51a2bc148 100644
--- a/src/mesh/PhoneAPI.cpp
+++ b/src/mesh/PhoneAPI.cpp
@@ -159,6 +159,10 @@ bool PhoneAPI::handleToRadio(const uint8_t *buf, size_t bufLength)
 #if !MESHTASTIC_EXCLUDE_MQTT
         case meshtastic_ToRadio_mqttClientProxyMessage_tag:
             LOG_DEBUG("Got MqttClientProxy message");
+            if (state != STATE_SEND_PACKETS) {
+                LOG_WARN("Ignore MqttClientProxy message while completing config handshake");
+                break;
+            }
             if (mqtt && moduleConfig.mqtt.proxy_to_client_enabled && moduleConfig.mqtt.enabled &&
                 (channels.anyMqttEnabled() || moduleConfig.mqtt.map_reporting_enabled)) {
                 mqtt->onClientProxyReceive(toRadioScratch.mqttClientProxyMessage);

From 5953b4704e5ef79242b907f88e1dabaa7569951f Mon Sep 17 00:00:00 2001
From: Jonathan Bennett 
Date: Thu, 16 Oct 2025 14:01:04 -0500
Subject: [PATCH 504/691] Force CannedMessages to another node to be a PKI DM
 (#8373)

---
 src/modules/CannedMessageModule.cpp | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/src/modules/CannedMessageModule.cpp b/src/modules/CannedMessageModule.cpp
index e9f52bb7d..9f95a9e20 100644
--- a/src/modules/CannedMessageModule.cpp
+++ b/src/modules/CannedMessageModule.cpp
@@ -255,7 +255,7 @@ void CannedMessageModule::updateDestinationSelectionList()
 
     for (size_t i = 0; i < numMeshNodes; ++i) {
         meshtastic_NodeInfoLite *node = nodeDB->getMeshNodeByIndex(i);
-        if (!node || node->num == myNodeNum)
+        if (!node || node->num == myNodeNum || !node->has_user || node->user.public_key.size != 32)
             continue;
 
         const String &nodeName = node->user.long_name;
@@ -976,6 +976,8 @@ void CannedMessageModule::sendText(NodeNum dest, ChannelIndex channel, const cha
         LOG_INFO("Proactively adding %x as favorite node", p->to);
         nodeDB->set_favorite(true, p->to);
         screen->setFrames(graphics::Screen::FOCUS_PRESERVE);
+        p->pki_encrypted = true;
+        p->channel = 0;
     }
 
     // Send to mesh and phone (even if no phone connected, to track ACKs)

From 32ebc70bcad1489e766af441bdeeca03f0115ab3 Mon Sep 17 00:00:00 2001
From: Tom Fifield 
Date: Fri, 17 Oct 2025 09:01:14 +1100
Subject: [PATCH 505/691] Update exempt labels for stale bot configuration

Adds triaged and backlog to the list of exempt labels.
---
 .github/workflows/stale_bot.yml | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/.github/workflows/stale_bot.yml b/.github/workflows/stale_bot.yml
index b528f80b2..11ba59386 100644
--- a/.github/workflows/stale_bot.yml
+++ b/.github/workflows/stale_bot.yml
@@ -22,5 +22,5 @@ jobs:
           days-before-stale: 45
           stale-issue-message: This issue has not had any comment or update in the last month. If it is still relevant, please post update comments. If no comments are made, this issue will be closed automagically in 7 days.
           close-issue-message: This issue has not had any comment since the last notice. It has been closed automatically. If this is incorrect, or the issue becomes relevant again, please request that it is reopened.
-          exempt-issue-labels: pinned,3.0
-          exempt-pr-labels: pinned,3.0
+          exempt-issue-labels: pinned,3.0,triaged,backlog
+          exempt-pr-labels: pinned,3.0,triaged,backlog

From 073c35c782447f3749bd624c2a6a94959d74b7bc Mon Sep 17 00:00:00 2001
From: Tom Fifield 
Date: Fri, 17 Oct 2025 09:01:51 +1100
Subject: [PATCH 506/691] Update exempt labels for stale bot workflow

Adds triaged and backlog to the list of exempt labels.
---
 .github/workflows/stale_bot.yml | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/.github/workflows/stale_bot.yml b/.github/workflows/stale_bot.yml
index a80619e90..441d53ee3 100644
--- a/.github/workflows/stale_bot.yml
+++ b/.github/workflows/stale_bot.yml
@@ -20,5 +20,5 @@ jobs:
         uses: actions/stale@v10.1.0
         with:
           days-before-stale: 45
-          exempt-issue-labels: pinned,3.0
-          exempt-pr-labels: pinned,3.0
+          exempt-issue-labels: pinned,3.0,triaged,backlog
+          exempt-pr-labels: pinned,3.0,triaged,backlog

From acab814b6f3070f039f231804b304545e790c28c Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Sat, 18 Oct 2025 09:14:34 +1100
Subject: [PATCH 507/691] Update meshtastic/web to v2.6.7 (#8381)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
 bin/web.version | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/bin/web.version b/bin/web.version
index 952f449f1..ba5c9fca6 100644
--- a/bin/web.version
+++ b/bin/web.version
@@ -1 +1 @@
-2.6.6
\ No newline at end of file
+2.6.7
\ No newline at end of file

From ee2ed0a8fb8518e4f519ef60bd80e4906b89b7a9 Mon Sep 17 00:00:00 2001
From: HarukiToreda <116696711+HarukiToreda@users.noreply.github.com>
Date: Fri, 17 Oct 2025 23:26:47 -0400
Subject: [PATCH 508/691] Fixe battery voltage to show missing decimals

---
 src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.cpp b/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.cpp
index 7876276a8..09f76ed46 100644
--- a/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.cpp
+++ b/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.cpp
@@ -709,7 +709,7 @@ void InkHUD::MenuApplet::drawSystemInfoPanel(int16_t left, int16_t top, uint16_t
     // Voltage
     float voltage = powerStatus->getBatteryVoltageMv() / 1000.0;
     char voltageStr[6]; // "XX.XV"
-    sprintf(voltageStr, "%.1fV", voltage);
+    sprintf(voltageStr, "%.2fV", voltage);
     printAt(colC[0], labelT, "Bat", CENTER, TOP);
     printAt(colC[0], valT, voltageStr, CENTER, TOP);
 

From d9905f3c316f37f9d95bb6b4cfb2579eb08becce Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Sat, 18 Oct 2025 19:17:34 +1100
Subject: [PATCH 509/691] Update DFRobot_RTU to v1.0.6 (#8387)

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 376f6e5a8..7c63ad7ad 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -164,7 +164,7 @@ lib_deps =
 	# 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
-	dfrobot/DFRobot_RTU@1.0.3
+	dfrobot/DFRobot_RTU@1.0.6
 	# renovate: datasource=git-refs depName=DFRobot_RainfallSensor packageName=https://github.com/DFRobot/DFRobot_RainfallSensor gitBranch=master
 	https://github.com/DFRobot/DFRobot_RainfallSensor/archive/38fea5e02b40a5430be6dab39a99a6f6347d667e.zip
 	# renovate: datasource=custom.pio depName=INA226 packageName=robtillaart/library/INA226

From 0bfc342b4832bcdef0463c7272733aa4f878763d Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Sat, 18 Oct 2025 19:20:07 +1100
Subject: [PATCH 510/691] Update mcr.microsoft.com/devcontainers/cpp Docker tag
 to v2 (#8375)

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

diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile
index 30af24bd2..a2c56fad9 100644
--- a/.devcontainer/Dockerfile
+++ b/.devcontainer/Dockerfile
@@ -1,7 +1,7 @@
 # trunk-ignore-all(terrascan/AC_DOCKER_0002): Known terrascan issue
 # trunk-ignore-all(hadolint/DL3008): Do not pin apt package versions
 # trunk-ignore-all(hadolint/DL3013): Do not pin pip package versions
-FROM mcr.microsoft.com/devcontainers/cpp:1-debian-12
+FROM mcr.microsoft.com/devcontainers/cpp:2-debian-12
 
 USER root
 

From 4df79374b0e220918eaa9c35f4a004653125afd6 Mon Sep 17 00:00:00 2001
From: Tom Fifield 
Date: Sun, 19 Oct 2025 08:52:03 +1100
Subject: [PATCH 511/691] manual merge stale bot config (#8392)

---
 .github/workflows/stale_bot.yml | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/.github/workflows/stale_bot.yml b/.github/workflows/stale_bot.yml
index 441d53ee3..11ba59386 100644
--- a/.github/workflows/stale_bot.yml
+++ b/.github/workflows/stale_bot.yml
@@ -20,5 +20,7 @@ jobs:
         uses: actions/stale@v10.1.0
         with:
           days-before-stale: 45
+          stale-issue-message: This issue has not had any comment or update in the last month. If it is still relevant, please post update comments. If no comments are made, this issue will be closed automagically in 7 days.
+          close-issue-message: This issue has not had any comment since the last notice. It has been closed automatically. If this is incorrect, or the issue becomes relevant again, please request that it is reopened.
           exempt-issue-labels: pinned,3.0,triaged,backlog
           exempt-pr-labels: pinned,3.0,triaged,backlog

From e5d67310d6c6d76881c087b23c243ed6db70803b Mon Sep 17 00:00:00 2001
From: Tom Fifield 
Date: Sun, 19 Oct 2025 08:52:56 +1100
Subject: [PATCH 512/691] Master ---> Develop (#8391)

* Update exempt labels for stale bot workflow

Adds triaged and backlog to the list of exempt labels.

* Update meshtastic/web to v2.6.7 (#8381)

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

* Update DFRobot_RTU to v1.0.6 (#8387)

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

* Update mcr.microsoft.com/devcontainers/cpp Docker tag to v2 (#8375)

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

* manual merge stale bot config (#8392)

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
 .devcontainer/Dockerfile | 2 +-
 bin/web.version          | 2 +-
 platformio.ini           | 2 +-
 3 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile
index 30af24bd2..a2c56fad9 100644
--- a/.devcontainer/Dockerfile
+++ b/.devcontainer/Dockerfile
@@ -1,7 +1,7 @@
 # trunk-ignore-all(terrascan/AC_DOCKER_0002): Known terrascan issue
 # trunk-ignore-all(hadolint/DL3008): Do not pin apt package versions
 # trunk-ignore-all(hadolint/DL3013): Do not pin pip package versions
-FROM mcr.microsoft.com/devcontainers/cpp:1-debian-12
+FROM mcr.microsoft.com/devcontainers/cpp:2-debian-12
 
 USER root
 
diff --git a/bin/web.version b/bin/web.version
index 952f449f1..ba5c9fca6 100644
--- a/bin/web.version
+++ b/bin/web.version
@@ -1 +1 @@
-2.6.6
\ No newline at end of file
+2.6.7
\ No newline at end of file
diff --git a/platformio.ini b/platformio.ini
index 376f6e5a8..7c63ad7ad 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -164,7 +164,7 @@ lib_deps =
 	# 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
-	dfrobot/DFRobot_RTU@1.0.3
+	dfrobot/DFRobot_RTU@1.0.6
 	# renovate: datasource=git-refs depName=DFRobot_RainfallSensor packageName=https://github.com/DFRobot/DFRobot_RainfallSensor gitBranch=master
 	https://github.com/DFRobot/DFRobot_RainfallSensor/archive/38fea5e02b40a5430be6dab39a99a6f6347d667e.zip
 	# renovate: datasource=custom.pio depName=INA226 packageName=robtillaart/library/INA226

From af8407aca9476398de49db15e723088de257275f Mon Sep 17 00:00:00 2001
From: "Daniel.Cao" <144674500+DanielCao0@users.noreply.github.com>
Date: Sun, 19 Oct 2025 05:53:39 +0800
Subject: [PATCH 513/691] Board support:  RAK3401+RAK13302 1-watt  (#8140)

* Add RAK3401 variant files

* Add SPI configuration for RAK3401 and RAK13302 variants

* Refactor SPI pin configuration and clean up variant definitions for RAK3401

* Add TX_GAIN_LORA for RAK13302 Power Amp

* Fix merge

---------

Co-authored-by: Ben Meadors 
Co-authored-by: Tom Fifield 
Co-authored-by: Manuel <71137295+mverch67@users.noreply.github.com>
---
 src/configuration.h                           |   5 +
 src/main.cpp                                  |   7 +
 .../nrf52840/rak3401_1watt/platformio.ini     |  31 +++
 variants/nrf52840/rak3401_1watt/variant.cpp   |  45 ++++
 variants/nrf52840/rak3401_1watt/variant.h     | 226 ++++++++++++++++++
 5 files changed, 314 insertions(+)
 create mode 100644 variants/nrf52840/rak3401_1watt/platformio.ini
 create mode 100644 variants/nrf52840/rak3401_1watt/variant.cpp
 create mode 100644 variants/nrf52840/rak3401_1watt/variant.h

diff --git a/src/configuration.h b/src/configuration.h
index c6c8d673c..baf24a636 100644
--- a/src/configuration.h
+++ b/src/configuration.h
@@ -126,6 +126,11 @@ along with this program.  If not, see .
 #define TX_GAIN_LORA 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 10, 10, 9, 9, 8, 7
 #endif
 
+#ifdef RAK13302
+#define NUM_PA_POINTS 22
+#define TX_GAIN_LORA 7, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 8
+#endif
+
 // Default system gain to 0 if not defined
 #ifndef TX_GAIN_LORA
 #define TX_GAIN_LORA 0
diff --git a/src/main.cpp b/src/main.cpp
index bb97a1aa6..3801f6f9f 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -841,7 +841,14 @@ void setup()
         SPI.begin();
     }
 #elif !defined(ARCH_ESP32) // ARCH_RP2040
+#if defined(RAK3401) || defined(RAK13302)
+    pinMode(WB_IO2, OUTPUT);
+    digitalWrite(WB_IO2, HIGH);
+    SPI1.setPins(LORA_MISO, LORA_SCK, LORA_MOSI);
+    SPI1.begin();
+#else
     SPI.begin();
+#endif
 #else
         // ESP32
 #if defined(HW_SPI1_DEVICE)
diff --git a/variants/nrf52840/rak3401_1watt/platformio.ini b/variants/nrf52840/rak3401_1watt/platformio.ini
new file mode 100644
index 000000000..1a915a6b3
--- /dev/null
+++ b/variants/nrf52840/rak3401_1watt/platformio.ini
@@ -0,0 +1,31 @@
+; The very slick RAK wireless RAK 4631 / 4630 board - Unified firmware for 5005/19003, with or without OLED RAK 1921
+[env:rak3401-1watt]
+extends = nrf52840_base
+board = wiscore_rak4631
+board_check = true
+build_flags = ${nrf52840_base.build_flags} 
+  -Ivariants/nrf52840/rak3401_1watt
+  -D RAK_4631
+;   -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely.
+  -D RAK3401
+  -D RAK13302 ; RAK 1Watt Power Amplifier
+  -DRADIOLIB_EXCLUDE_SX128X=1
+  -DRADIOLIB_EXCLUDE_SX127X=1
+  -DRADIOLIB_EXCLUDE_LR11X0=1
+build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/rak3401_1watt> +
+lib_deps = 
+  ${nrf52840_base.lib_deps}
+  ${networking_base.lib_deps}
+  melopero/Melopero RV3028@^1.1.0
+  rakwireless/RAKwireless NCP5623 RGB LED library@^1.0.2
+  beegee-tokyo/RAK12035_SoilMoisture@^1.0.4
+  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)
+; Note: as of 6/2013 the serial/bootloader based programming takes approximately 30 seconds
+;upload_protocol = jlink
+
+; Allows programming and debug via the RAK NanoDAP as the default debugger tool for the RAK4631 (it is only $10!)
+; programming time is about the same as the bootloader version.
+; For information on this see the meshtastic developers documentation for "Development on the NRF52"
+
diff --git a/variants/nrf52840/rak3401_1watt/variant.cpp b/variants/nrf52840/rak3401_1watt/variant.cpp
new file mode 100644
index 000000000..e84b60b3b
--- /dev/null
+++ b/variants/nrf52840/rak3401_1watt/variant.cpp
@@ -0,0 +1,45 @@
+/*
+  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);
+}
diff --git a/variants/nrf52840/rak3401_1watt/variant.h b/variants/nrf52840/rak3401_1watt/variant.h
new file mode 100644
index 000000000..d4bb1a175
--- /dev/null
+++ b/variants/nrf52840/rak3401_1watt/variant.h
@@ -0,0 +1,226 @@
+/*
+  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_RAK3401_
+#define _VARIANT_RAK3401_
+
+#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
+
+/*
+ * 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 WB_I2C1_SDA (13) // SENSOR_SLOT IO_SLOT
+#define WB_I2C1_SCL (14) // SENSOR_SLOT IO_SLOT
+
+#define PIN_AREF (2)
+#define PIN_NFC1 (9)
+#define WB_IO5 PIN_NFC1
+#define WB_IO4 (4)
+#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
+
+/*
+ * Wire Interfaces
+ */
+#define WIRE_INTERFACES_COUNT 1
+
+#define PIN_WIRE_SDA (WB_I2C1_SDA)
+#define PIN_WIRE_SCL (WB_I2C1_SCL)
+
+// 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
+
+// 1watt sx1262 RAK13302
+#define HW_SPI1_DEVICE 1
+
+#define LORA_SCK PIN_SPI1_SCK
+#define LORA_MISO PIN_SPI1_MISO
+#define LORA_MOSI PIN_SPI1_MOSI
+#define LORA_CS 26
+
+#define USE_SX1262
+#define SX126X_CS (26)
+#define SX126X_DIO1 (10)
+#define SX126X_BUSY (9)
+#define SX126X_RESET (4)
+
+#define SX126X_POWER_EN (21)
+// 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
+// If using a power chip like the INA3221 you can override the default battery voltage channel below
+// and comment out NRF_APM to use the INA3221 instead of the USB detection for charging
+// #define INA3221_BAT_CH INA3221_CH2
+// #define INA3221_ENV_CH INA3221_CH1
+
+// enables 3.3V periphery like GPS or IO Module
+// Do not toggle this for GPS power savings
+#define PIN_3V3_EN (34)
+#define WB_IO2 PIN_3V3_EN
+
+// 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_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 RAK_4631 1
+
+#ifdef __cplusplus
+}
+#endif
+
+/*----------------------------------------------------------------------------
+ *        Arduino objects - C++ only
+ *----------------------------------------------------------------------------*/
+
+#endif

From 0283e0658b2f615991f369307a240a7479da5632 Mon Sep 17 00:00:00 2001
From: Tom Fifield 
Date: Sun, 19 Oct 2025 08:54:56 +1100
Subject: [PATCH 514/691] Master --> develop (#8394)

* Update exempt labels for stale bot workflow

Adds triaged and backlog to the list of exempt labels.

* Update meshtastic/web to v2.6.7 (#8381)

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

* Update DFRobot_RTU to v1.0.6 (#8387)

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

* Update mcr.microsoft.com/devcontainers/cpp Docker tag to v2 (#8375)

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

* manual merge stale bot config (#8392)

* Board support:  RAK3401+RAK13302 1-watt  (#8140)

* Add RAK3401 variant files

* Add SPI configuration for RAK3401 and RAK13302 variants

* Refactor SPI pin configuration and clean up variant definitions for RAK3401

* Add TX_GAIN_LORA for RAK13302 Power Amp

* Fix merge

---------

Co-authored-by: Ben Meadors 
Co-authored-by: Tom Fifield 
Co-authored-by: Manuel <71137295+mverch67@users.noreply.github.com>

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Daniel.Cao <144674500+DanielCao0@users.noreply.github.com>
Co-authored-by: Ben Meadors 
Co-authored-by: Manuel <71137295+mverch67@users.noreply.github.com>
---
 src/configuration.h                           |   5 +
 src/main.cpp                                  |   7 +
 .../nrf52840/rak3401_1watt/platformio.ini     |  31 +++
 variants/nrf52840/rak3401_1watt/variant.cpp   |  45 ++++
 variants/nrf52840/rak3401_1watt/variant.h     | 226 ++++++++++++++++++
 5 files changed, 314 insertions(+)
 create mode 100644 variants/nrf52840/rak3401_1watt/platformio.ini
 create mode 100644 variants/nrf52840/rak3401_1watt/variant.cpp
 create mode 100644 variants/nrf52840/rak3401_1watt/variant.h

diff --git a/src/configuration.h b/src/configuration.h
index c6c8d673c..baf24a636 100644
--- a/src/configuration.h
+++ b/src/configuration.h
@@ -126,6 +126,11 @@ along with this program.  If not, see .
 #define TX_GAIN_LORA 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 10, 10, 9, 9, 8, 7
 #endif
 
+#ifdef RAK13302
+#define NUM_PA_POINTS 22
+#define TX_GAIN_LORA 7, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 8
+#endif
+
 // Default system gain to 0 if not defined
 #ifndef TX_GAIN_LORA
 #define TX_GAIN_LORA 0
diff --git a/src/main.cpp b/src/main.cpp
index bb97a1aa6..3801f6f9f 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -841,7 +841,14 @@ void setup()
         SPI.begin();
     }
 #elif !defined(ARCH_ESP32) // ARCH_RP2040
+#if defined(RAK3401) || defined(RAK13302)
+    pinMode(WB_IO2, OUTPUT);
+    digitalWrite(WB_IO2, HIGH);
+    SPI1.setPins(LORA_MISO, LORA_SCK, LORA_MOSI);
+    SPI1.begin();
+#else
     SPI.begin();
+#endif
 #else
         // ESP32
 #if defined(HW_SPI1_DEVICE)
diff --git a/variants/nrf52840/rak3401_1watt/platformio.ini b/variants/nrf52840/rak3401_1watt/platformio.ini
new file mode 100644
index 000000000..1a915a6b3
--- /dev/null
+++ b/variants/nrf52840/rak3401_1watt/platformio.ini
@@ -0,0 +1,31 @@
+; The very slick RAK wireless RAK 4631 / 4630 board - Unified firmware for 5005/19003, with or without OLED RAK 1921
+[env:rak3401-1watt]
+extends = nrf52840_base
+board = wiscore_rak4631
+board_check = true
+build_flags = ${nrf52840_base.build_flags} 
+  -Ivariants/nrf52840/rak3401_1watt
+  -D RAK_4631
+;   -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely.
+  -D RAK3401
+  -D RAK13302 ; RAK 1Watt Power Amplifier
+  -DRADIOLIB_EXCLUDE_SX128X=1
+  -DRADIOLIB_EXCLUDE_SX127X=1
+  -DRADIOLIB_EXCLUDE_LR11X0=1
+build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/rak3401_1watt> +
+lib_deps = 
+  ${nrf52840_base.lib_deps}
+  ${networking_base.lib_deps}
+  melopero/Melopero RV3028@^1.1.0
+  rakwireless/RAKwireless NCP5623 RGB LED library@^1.0.2
+  beegee-tokyo/RAK12035_SoilMoisture@^1.0.4
+  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)
+; Note: as of 6/2013 the serial/bootloader based programming takes approximately 30 seconds
+;upload_protocol = jlink
+
+; Allows programming and debug via the RAK NanoDAP as the default debugger tool for the RAK4631 (it is only $10!)
+; programming time is about the same as the bootloader version.
+; For information on this see the meshtastic developers documentation for "Development on the NRF52"
+
diff --git a/variants/nrf52840/rak3401_1watt/variant.cpp b/variants/nrf52840/rak3401_1watt/variant.cpp
new file mode 100644
index 000000000..e84b60b3b
--- /dev/null
+++ b/variants/nrf52840/rak3401_1watt/variant.cpp
@@ -0,0 +1,45 @@
+/*
+  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);
+}
diff --git a/variants/nrf52840/rak3401_1watt/variant.h b/variants/nrf52840/rak3401_1watt/variant.h
new file mode 100644
index 000000000..d4bb1a175
--- /dev/null
+++ b/variants/nrf52840/rak3401_1watt/variant.h
@@ -0,0 +1,226 @@
+/*
+  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_RAK3401_
+#define _VARIANT_RAK3401_
+
+#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
+
+/*
+ * 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 WB_I2C1_SDA (13) // SENSOR_SLOT IO_SLOT
+#define WB_I2C1_SCL (14) // SENSOR_SLOT IO_SLOT
+
+#define PIN_AREF (2)
+#define PIN_NFC1 (9)
+#define WB_IO5 PIN_NFC1
+#define WB_IO4 (4)
+#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
+
+/*
+ * Wire Interfaces
+ */
+#define WIRE_INTERFACES_COUNT 1
+
+#define PIN_WIRE_SDA (WB_I2C1_SDA)
+#define PIN_WIRE_SCL (WB_I2C1_SCL)
+
+// 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
+
+// 1watt sx1262 RAK13302
+#define HW_SPI1_DEVICE 1
+
+#define LORA_SCK PIN_SPI1_SCK
+#define LORA_MISO PIN_SPI1_MISO
+#define LORA_MOSI PIN_SPI1_MOSI
+#define LORA_CS 26
+
+#define USE_SX1262
+#define SX126X_CS (26)
+#define SX126X_DIO1 (10)
+#define SX126X_BUSY (9)
+#define SX126X_RESET (4)
+
+#define SX126X_POWER_EN (21)
+// 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
+// If using a power chip like the INA3221 you can override the default battery voltage channel below
+// and comment out NRF_APM to use the INA3221 instead of the USB detection for charging
+// #define INA3221_BAT_CH INA3221_CH2
+// #define INA3221_ENV_CH INA3221_CH1
+
+// enables 3.3V periphery like GPS or IO Module
+// Do not toggle this for GPS power savings
+#define PIN_3V3_EN (34)
+#define WB_IO2 PIN_3V3_EN
+
+// 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_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 RAK_4631 1
+
+#ifdef __cplusplus
+}
+#endif
+
+/*----------------------------------------------------------------------------
+ *        Arduino objects - C++ only
+ *----------------------------------------------------------------------------*/
+
+#endif

From 30022c93774f5bd1b363a2aaebf1c49ef7a0757c Mon Sep 17 00:00:00 2001
From: HarukiToreda <116696711+HarukiToreda@users.noreply.github.com>
Date: Sat, 18 Oct 2025 18:00:14 -0400
Subject: [PATCH 515/691] Fixe battery voltage to show missing decimals (#8386)

---
 src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.cpp b/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.cpp
index 7876276a8..09f76ed46 100644
--- a/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.cpp
+++ b/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.cpp
@@ -709,7 +709,7 @@ void InkHUD::MenuApplet::drawSystemInfoPanel(int16_t left, int16_t top, uint16_t
     // Voltage
     float voltage = powerStatus->getBatteryVoltageMv() / 1000.0;
     char voltageStr[6]; // "XX.XV"
-    sprintf(voltageStr, "%.1fV", voltage);
+    sprintf(voltageStr, "%.2fV", voltage);
     printAt(colC[0], labelT, "Bat", CENTER, TOP);
     printAt(colC[0], valT, voltageStr, CENTER, TOP);
 

From b4dea63f4499273b32565a8451b43d83a135ac1b Mon Sep 17 00:00:00 2001
From: HarukiToreda <116696711+HarukiToreda@users.noreply.github.com>
Date: Sat, 18 Oct 2025 18:00:35 -0400
Subject: [PATCH 516/691] Gatting off BaseUI code from screenless devices and
 InkHUD (#8384)

---
 src/graphics/SharedUIDisplay.cpp               | 5 ++++-
 src/graphics/VirtualKeyboard.cpp               | 4 +++-
 src/graphics/draw/CompassRenderer.cpp          | 3 +++
 src/graphics/emotes.cpp                        | 3 +++
 src/modules/Telemetry/EnvironmentTelemetry.cpp | 2 ++
 src/modules/Telemetry/PowerTelemetry.cpp       | 2 ++
 6 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/graphics/SharedUIDisplay.cpp b/src/graphics/SharedUIDisplay.cpp
index dcaa5d69b..8e1299f51 100644
--- a/src/graphics/SharedUIDisplay.cpp
+++ b/src/graphics/SharedUIDisplay.cpp
@@ -1,6 +1,8 @@
-#include "graphics/SharedUIDisplay.h"
+#include "configuration.h"
+#if HAS_SCREEN
 #include "RTC.h"
 #include "graphics/ScreenFonts.h"
+#include "graphics/SharedUIDisplay.h"
 #include "graphics/draw/UIRenderer.h"
 #include "main.h"
 #include "meshtastic/config.pb.h"
@@ -423,3 +425,4 @@ std::string sanitizeString(const std::string &input)
 }
 
 } // namespace graphics
+#endif
\ No newline at end of file
diff --git a/src/graphics/VirtualKeyboard.cpp b/src/graphics/VirtualKeyboard.cpp
index 84d5551cb..8062a0338 100644
--- a/src/graphics/VirtualKeyboard.cpp
+++ b/src/graphics/VirtualKeyboard.cpp
@@ -1,5 +1,6 @@
-#include "VirtualKeyboard.h"
 #include "configuration.h"
+#if HAS_SCREEN
+#include "VirtualKeyboard.h"
 #include "graphics/Screen.h"
 #include "graphics/ScreenFonts.h"
 #include "graphics/SharedUIDisplay.h"
@@ -736,3 +737,4 @@ bool VirtualKeyboard::isTimedOut() const
 }
 
 } // namespace graphics
+#endif
\ No newline at end of file
diff --git a/src/graphics/draw/CompassRenderer.cpp b/src/graphics/draw/CompassRenderer.cpp
index 0e5a1d727..629949ffd 100644
--- a/src/graphics/draw/CompassRenderer.cpp
+++ b/src/graphics/draw/CompassRenderer.cpp
@@ -1,3 +1,5 @@
+#include "configuration.h"
+#if HAS_SCREEN
 #include "CompassRenderer.h"
 #include "NodeDB.h"
 #include "UIRenderer.h"
@@ -135,3 +137,4 @@ uint16_t getCompassDiam(uint32_t displayWidth, uint32_t displayHeight)
 
 } // namespace CompassRenderer
 } // namespace graphics
+#endif
\ No newline at end of file
diff --git a/src/graphics/emotes.cpp b/src/graphics/emotes.cpp
index e1a105d20..a0227d365 100644
--- a/src/graphics/emotes.cpp
+++ b/src/graphics/emotes.cpp
@@ -1,3 +1,5 @@
+#include "configuration.h"
+#if HAS_SCREEN
 #include "emotes.h"
 
 namespace graphics
@@ -275,3 +277,4 @@ const unsigned char bell_icon[] PROGMEM = {
 #endif
 
 } // namespace graphics
+#endif
\ No newline at end of file
diff --git a/src/modules/Telemetry/EnvironmentTelemetry.cpp b/src/modules/Telemetry/EnvironmentTelemetry.cpp
index 95947560d..2337af808 100644
--- a/src/modules/Telemetry/EnvironmentTelemetry.cpp
+++ b/src/modules/Telemetry/EnvironmentTelemetry.cpp
@@ -361,6 +361,7 @@ bool EnvironmentTelemetryModule::wantUIFrame()
     return moduleConfig.telemetry.environment_screen_enabled;
 }
 
+#if HAS_SCREEN
 void EnvironmentTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
 {
     // === Setup display ===
@@ -510,6 +511,7 @@ void EnvironmentTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiSt
         currentY += rowHeight;
     }
 }
+#endif
 
 bool EnvironmentTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_Telemetry *t)
 {
diff --git a/src/modules/Telemetry/PowerTelemetry.cpp b/src/modules/Telemetry/PowerTelemetry.cpp
index 479861a2e..e69ee3931 100644
--- a/src/modules/Telemetry/PowerTelemetry.cpp
+++ b/src/modules/Telemetry/PowerTelemetry.cpp
@@ -108,6 +108,7 @@ bool PowerTelemetryModule::wantUIFrame()
     return moduleConfig.telemetry.power_screen_enabled;
 }
 
+#if HAS_SCREEN
 void PowerTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
 {
     display->clear();
@@ -165,6 +166,7 @@ void PowerTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *s
         drawLine("Ch3", m.ch3_voltage, m.ch3_current);
     }
 }
+#endif
 
 bool PowerTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_Telemetry *t)
 {

From e1c259ae32ade6524f43898d65fdc6c2282befe4 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
 <41898282+github-actions[bot]@users.noreply.github.com>
Date: Sat, 18 Oct 2025 18:21:24 -0500
Subject: [PATCH 517/691] Update protobufs (#8396)

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

diff --git a/protobufs b/protobufs
index 38638f19f..4a618380a 160000
--- a/protobufs
+++ b/protobufs
@@ -1 +1 @@
-Subproject commit 38638f19f84ad886222b484d6bf5a8459aed8c7e
+Subproject commit 4a618380a0d80b476bb7f278008f811f456e0a5f
diff --git a/src/mesh/generated/meshtastic/telemetry.pb.h b/src/mesh/generated/meshtastic/telemetry.pb.h
index a8ca96e95..dec89ba15 100644
--- a/src/mesh/generated/meshtastic/telemetry.pb.h
+++ b/src/mesh/generated/meshtastic/telemetry.pb.h
@@ -101,7 +101,9 @@ typedef enum _meshtastic_TelemetrySensorType {
     /* SEN5X PM SENSORS */
     meshtastic_TelemetrySensorType_SEN5X = 43,
     /* TSL2561 light sensor */
-    meshtastic_TelemetrySensorType_TSL2561 = 44
+    meshtastic_TelemetrySensorType_TSL2561 = 44,
+    /* BH1750 light sensor */
+    meshtastic_TelemetrySensorType_BH1750 = 45
 } meshtastic_TelemetrySensorType;
 
 /* Struct definitions */
@@ -438,8 +440,8 @@ extern "C" {
 
 /* Helper constants for enums */
 #define _meshtastic_TelemetrySensorType_MIN meshtastic_TelemetrySensorType_SENSOR_UNSET
-#define _meshtastic_TelemetrySensorType_MAX meshtastic_TelemetrySensorType_TSL2561
-#define _meshtastic_TelemetrySensorType_ARRAYSIZE ((meshtastic_TelemetrySensorType)(meshtastic_TelemetrySensorType_TSL2561+1))
+#define _meshtastic_TelemetrySensorType_MAX meshtastic_TelemetrySensorType_BH1750
+#define _meshtastic_TelemetrySensorType_ARRAYSIZE ((meshtastic_TelemetrySensorType)(meshtastic_TelemetrySensorType_BH1750+1))
 
 
 

From 2357ea004264fe9f433639ed1df8d197ae921e86 Mon Sep 17 00:00:00 2001
From: HarukiToreda <116696711+HarukiToreda@users.noreply.github.com>
Date: Sun, 19 Oct 2025 02:48:55 -0400
Subject: [PATCH 518/691] Clearer hop markers for inkHUD map

---
 .../InkHUD/Applets/Bases/Map/MapApplet.cpp    | 357 ++++++++++++------
 1 file changed, 232 insertions(+), 125 deletions(-)

diff --git a/src/graphics/niche/InkHUD/Applets/Bases/Map/MapApplet.cpp b/src/graphics/niche/InkHUD/Applets/Bases/Map/MapApplet.cpp
index db0805f4e..0639501ea 100644
--- a/src/graphics/niche/InkHUD/Applets/Bases/Map/MapApplet.cpp
+++ b/src/graphics/niche/InkHUD/Applets/Bases/Map/MapApplet.cpp
@@ -14,44 +14,128 @@ void InkHUD::MapApplet::onRender()
     }
 
     // Find center of map
-    // - latitude and longitude
-    // - will be placed at X(0.5), Y(0.5)
     getMapCenter(&latCenter, &lngCenter);
-
-    // Calculate North+East distance of each node to map center
-    // - which nodes to use controlled by virtual shouldDrawNode method
     calculateAllMarkers();
-
-    // Set the region shown on the map
-    // - default: fit all nodes, plus padding
-    // - maybe overriden by derived applet
-    // - getMapSize *sets* passed parameters (C-style)
     getMapSize(&widthMeters, &heightMeters);
-
-    // Set the metersToPx conversion value
     calculateMapScale();
 
-    // Special marker for own node
-    meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
-    if (ourNode && nodeDB->hasValidPosition(ourNode))
-        drawLabeledMarker(ourNode);
-
-    // Draw all markers
+    // Draw all markers first
     for (Marker m : markers) {
         int16_t x = X(0.5) + (m.eastMeters * metersToPx);
         int16_t y = Y(0.5) - (m.northMeters * metersToPx);
 
-        // Cross Size
-        constexpr uint16_t csMin = 5;
-        constexpr uint16_t csMax = 12;
+        // --- Add white halo outline first ---
+        constexpr int outlinePad = 2; // size of white outline padding
+        int boxSize = (m.hasHopsAway && m.hopsAway > config.lora.hop_limit) || !m.hasHopsAway ? 12 : 10;
+        fillRect(x - (boxSize / 2) - outlinePad, y - (boxSize / 2) - outlinePad,
+                boxSize + (outlinePad * 2), boxSize + (outlinePad * 2), WHITE);
 
-        // Too many hops away
-        if (m.hasHopsAway && m.hopsAway > config.lora.hop_limit) // Too many mops
-            printAt(x, y, "!", CENTER, MIDDLE);
-        else if (!m.hasHopsAway) // Unknown hops
-            drawCross(x, y, csMin);
-        else // The fewer hops, the larger the cross
-            drawCross(x, y, map(m.hopsAway, 0, config.lora.hop_limit, csMax, csMin));
+        // --- Draw actual marker on top ---
+        if (m.hasHopsAway && m.hopsAway > config.lora.hop_limit) {
+            fillRect(x - boxSize / 2, y - boxSize / 2, boxSize, boxSize, BLACK);
+            setFont(fontSmall); setTextColor(WHITE);
+            printAt(x, y, "X", CENTER, MIDDLE);
+        }
+        else if (!m.hasHopsAway) {
+            fillRect(x - boxSize / 2, y - boxSize / 2, boxSize, boxSize, BLACK);
+            setFont(fontSmall); setTextColor(WHITE);
+            printAt(x, y, "?", CENTER, MIDDLE);
+        }
+        else {
+            fillRect(x - boxSize / 2, y - boxSize / 2, boxSize, boxSize, BLACK);
+            char hopStr[4]; snprintf(hopStr, sizeof(hopStr), "%d", m.hopsAway);
+            setFont(fontSmall); setTextColor(WHITE);
+            printAt(x, y, hopStr, CENTER, MIDDLE);
+        }
+
+        // Restore default font and color (safety for rest of UI)
+        setFont(fontSmall); setTextColor(BLACK);
+    }
+
+    // Dual map scale bars
+    int16_t horizPx = width() * 0.25f;
+    int16_t vertPx  = height() * 0.25f;
+    float horizMeters = horizPx / metersToPx;
+    float vertMeters  = vertPx / metersToPx;
+
+    auto formatDistance = [&](float meters, char *out, size_t len) {
+        if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) {
+            float feet = meters * 3.28084f;
+            if (feet < 528) snprintf(out, len, "%.0f ft", feet);
+            else {
+                float miles = feet / 5280.0f;
+                snprintf(out, len, miles < 10 ? "%.1f mi" : "%.0f mi", miles);
+            }
+        } else {
+            if (meters >= 1000) snprintf(out, len, "%.1f km", meters / 1000.0f);
+            else snprintf(out, len, "%.0f m", meters);
+        }
+    };
+
+    // === Horizontal scale bar ===
+    int16_t horizBarY = height() - 2;
+    int16_t horizBarX = 1;
+    drawLine(horizBarX, horizBarY, horizBarX + horizPx, horizBarY, BLACK);
+    drawLine(horizBarX, horizBarY - 3, horizBarX, horizBarY + 3, BLACK);
+    drawLine(horizBarX + horizPx, horizBarY - 3, horizBarX + horizPx, horizBarY + 3, BLACK);
+
+    char horizLabel[32];
+    formatDistance(horizMeters, horizLabel, sizeof(horizLabel));
+    int16_t horizLabelW = getTextWidth(horizLabel);
+    int16_t horizLabelH = getFont().lineHeight();
+    int16_t horizLabelX = horizBarX + horizPx + 4;
+    int16_t horizLabelY = horizBarY - horizLabelH + 1;
+    fillRect(horizLabelX - 2, horizLabelY - 1, horizLabelW + 4, horizLabelH + 2, WHITE);
+    printAt(horizLabelX, horizBarY, horizLabel, LEFT, BOTTOM);
+
+    // === Vertical scale bar ===
+    int16_t vertBarX = 1;
+    int16_t vertBarBottom = horizBarY;
+    int16_t vertBarTop = vertBarBottom - vertPx;
+    drawLine(vertBarX, vertBarBottom, vertBarX, vertBarTop, BLACK);
+    drawLine(vertBarX - 3, vertBarBottom, vertBarX + 3, vertBarBottom, BLACK);
+    drawLine(vertBarX - 3, vertBarTop, vertBarX + 3, vertBarTop, BLACK);
+
+    char vertTopLabel[32];
+    formatDistance(vertMeters, vertTopLabel, sizeof(vertTopLabel));
+    int16_t topLabelY = vertBarTop - getFont().lineHeight() - 2;
+    int16_t topLabelW = getTextWidth(vertTopLabel);
+    int16_t topLabelH = getFont().lineHeight();
+    fillRect(vertBarX - 2, topLabelY - 1, topLabelW + 6, topLabelH + 2, WHITE);
+    printAt(vertBarX + (topLabelW / 2) + 1, topLabelY + (topLabelH / 2), vertTopLabel, CENTER, MIDDLE);
+
+    char vertBottomLabel[32];
+    formatDistance(horizMeters, vertBottomLabel, sizeof(vertBottomLabel));
+    int16_t bottomLabelY = vertBarBottom + 4;
+    int16_t bottomLabelW = getTextWidth(vertBottomLabel);
+    int16_t bottomLabelH = getFont().lineHeight();
+    fillRect(vertBarX - 2, bottomLabelY - 1, bottomLabelW + 6, bottomLabelH + 2, WHITE);
+    printAt(vertBarX + (bottomLabelW / 2) + 1, bottomLabelY + (bottomLabelH / 2), vertBottomLabel, CENTER, MIDDLE);
+
+    // --- Draw our node LAST with full white fill + outline ---
+    meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
+    if (ourNode && nodeDB->hasValidPosition(ourNode)) {
+        Marker self = calculateMarker(
+            ourNode->position.latitude_i * 1e-7,
+            ourNode->position.longitude_i * 1e-7,
+            false,
+            0
+        );
+
+        int16_t centerX = X(0.5) + (self.eastMeters * metersToPx);
+        int16_t centerY = Y(0.5) - (self.northMeters * metersToPx);
+
+        // --- White fill background + halo ---
+        fillCircle(centerX, centerY, 8, WHITE); // big white base
+        drawCircle(centerX, centerY, 8, WHITE); // crisp edge
+
+        // --- Black bullseye on top ---
+        drawCircle(centerX, centerY, 6, BLACK);
+        fillCircle(centerX, centerY, 2, BLACK);
+
+        // --- Crosshairs ---
+        drawLine(centerX - 8, centerY, centerX + 8, centerY, BLACK);
+        drawLine(centerX, centerY - 8, centerX, centerY + 8, BLACK);
     }
 }
 
@@ -63,110 +147,122 @@ void InkHUD::MapApplet::onRender()
 
 void InkHUD::MapApplet::getMapCenter(float *lat, float *lng)
 {
-    // Find mean lat long coords
-    // ============================
-    // - assigning X, Y and Z values to position on Earth's surface in 3D space, relative to center of planet
-    // - averages the x, y and z coords
-    // - uses tan to find angles for lat / long degrees
-    //   - longitude: triangle formed by x and y (on plane of the equator)
-    //   - latitude: triangle formed by z (north south),
-    //     and the line along plane of equator which stretches from earth's axis to where point xyz intersects planet's surface
+    // If we have a valid position for our own node, use that as the anchor
+    meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
+    if (ourNode && nodeDB->hasValidPosition(ourNode)) {
+        *lat = ourNode->position.latitude_i * 1e-7;
+        *lng = ourNode->position.longitude_i * 1e-7;
+    } else {
+        // Find mean lat long coords
+        // ============================
+        // - assigning X, Y and Z values to position on Earth's surface in 3D space, relative to center of planet
+        // - averages the x, y and z coords
+        // - uses tan to find angles for lat / long degrees
+        //   - longitude: triangle formed by x and y (on plane of the equator)
+        //   - latitude: triangle formed by z (north south),
+        //     and the line along plane of equator which stretches from earth's axis to where point xyz intersects planet's surface
 
-    // Working totals, averaged after nodeDB processed
-    uint32_t positionCount = 0;
-    float xAvg = 0;
-    float yAvg = 0;
-    float zAvg = 0;
+        // Working totals, averaged after nodeDB processed
+        uint32_t positionCount = 0;
+        float xAvg = 0;
+        float yAvg = 0;
+        float zAvg = 0;
 
-    // For each node in db
-    for (uint32_t i = 0; i < nodeDB->getNumMeshNodes(); i++) {
-        meshtastic_NodeInfoLite *node = nodeDB->getMeshNodeByIndex(i);
+        // For each node in db
+        for (uint32_t i = 0; i < nodeDB->getNumMeshNodes(); i++) {
+            meshtastic_NodeInfoLite *node = nodeDB->getMeshNodeByIndex(i);
 
-        // Skip if no position
-        if (!nodeDB->hasValidPosition(node))
-            continue;
+            // Skip if no position
+            if (!nodeDB->hasValidPosition(node))
+                continue;
 
-        // Skip if derived applet doesn't want to show this node on the map
-        if (!shouldDrawNode(node))
-            continue;
+            // Skip if derived applet doesn't want to show this node on the map
+            if (!shouldDrawNode(node))
+                continue;
 
-        // Latitude and Longitude of node, in radians
-        float latRad = node->position.latitude_i * (1e-7) * DEG_TO_RAD;
-        float lngRad = node->position.longitude_i * (1e-7) * DEG_TO_RAD;
+            // Latitude and Longitude of node, in radians
+            float latRad = node->position.latitude_i * (1e-7) * DEG_TO_RAD;
+            float lngRad = node->position.longitude_i * (1e-7) * DEG_TO_RAD;
 
-        // Convert to cartesian points, with center of earth at 0, 0, 0
-        // Exact distance from center is irrelevant, as we're only interested in the vector
-        float x = cos(latRad) * cos(lngRad);
-        float y = cos(latRad) * sin(lngRad);
-        float z = sin(latRad);
+            // Convert to cartesian points, with center of earth at 0, 0, 0
+            // Exact distance from center is irrelevant, as we're only interested in the vector
+            float x = cos(latRad) * cos(lngRad);
+            float y = cos(latRad) * sin(lngRad);
+            float z = sin(latRad);
 
-        // To find mean values shortly
-        xAvg += x;
-        yAvg += y;
-        zAvg += z;
-        positionCount++;
+            // To find mean values shortly
+            xAvg += x;
+            yAvg += y;
+            zAvg += z;
+            positionCount++;
+        }
+
+        // All NodeDB processed, find mean values
+        xAvg /= positionCount;
+        yAvg /= positionCount;
+        zAvg /= positionCount;
+
+        // Longitude from cartesian coords
+        // (Angle from 3D coords describing a point of globe's surface)
+        /*
+                          UK
+                       /-------\
+        (Top View)   /-         -\
+                   /-      (You)  -\
+                 /-           .     -\
+               /-             . X     -\
+         Asia -             ...         - USA
+               \-           Y         -/
+                 \-                 -/
+                   \-             -/
+                     \-         -/
+                       \- -----/
+                       Pacific
+
+        */
+
+        *lng = atan2(yAvg, xAvg) * RAD_TO_DEG;
+
+        // Latitude from cartesian coords
+        // (Angle from 3D coords describing a point on the globe's surface)
+        // As latitude increases, distance from the Earth's north-south axis out to our surface point decreases.
+        // Means we need to first find the hypotenuse which becomes base of our triangle in the second step
+        /*
+                           UK                                         North
+                        /-------\                 (Front View)      /-------\
+         (Top View)   /-         -\                               /-         -\
+                    /-       (You) -\                           /-(You)        -\
+                  /-         /.      -\                       /-   .             -\
+                /-    √X²+Y²/ . X      -\                   /-   Z .               -\
+        Asia   -           /...          - USA             -       .....             -
+                \-           Y         -/                   \-     √X²+Y²          -/
+                  \-                 -/                       \-                 -/
+                    \-             -/                           \-             -/
+                      \-         -/                               \-         -/
+                        \- -----/                                   \- -----/
+                         Pacific                                      South
+        */
+
+        float hypotenuse = sqrt((xAvg * xAvg) + (yAvg * yAvg)); // Distance from globe's north-south axis to surface intersect
+        *lat = atan2(zAvg, hypotenuse) * RAD_TO_DEG;
     }
 
-    // All NodeDB processed, find mean values
-    xAvg /= positionCount;
-    yAvg /= positionCount;
-    zAvg /= positionCount;
-
-    // Longitude from cartesian coords
-    // (Angle from 3D coords describing a point of globe's surface)
-    /*
-                      UK
-                   /-------\
-    (Top View)   /-         -\
-               /-      (You)  -\
-             /-           .     -\
-           /-             . X     -\
-     Asia -             ...         - USA
-           \-           Y         -/
-             \-                 -/
-               \-             -/
-                 \-         -/
-                   \- -----/
-                   Pacific
-
-    */
-
-    *lng = atan2(yAvg, xAvg) * RAD_TO_DEG;
-
-    // Latitude from cartesian coords
-    // (Angle from 3D coords describing a point on the globe's surface)
-    // As latitude increases, distance from the Earth's north-south axis out to our surface point decreases.
-    // Means we need to first find the hypotenuse which becomes base of our triangle in the second step
-    /*
-                       UK                                         North
-                    /-------\                 (Front View)      /-------\
-     (Top View)   /-         -\                               /-         -\
-                /-       (You) -\                           /-(You)        -\
-              /-         /.      -\                       /-   .             -\
-            /-    √X²+Y²/ . X      -\                   /-   Z .               -\
-    Asia   -           /...          - USA             -       .....             -
-            \-           Y         -/                   \-     √X²+Y²          -/
-              \-                 -/                       \-                 -/
-                \-             -/                           \-             -/
-                  \-         -/                               \-         -/
-                    \- -----/                                   \- -----/
-                     Pacific                                      South
-    */
-
-    float hypotenuse = sqrt((xAvg * xAvg) + (yAvg * yAvg)); // Distance from globe's north-south axis to surface intersect
-    *lat = atan2(zAvg, hypotenuse) * RAD_TO_DEG;
+    // Use either our node position, or the mean fallback as the center
+    latCenter = *lat;
+    lngCenter = *lng;
 
     // ----------------------------------------------
-    // This has given us the "mean position"
-    // This will be a position *somewhere* near the center of our nodes.
-    // What we actually want is to place our center so that our outermost nodes end up on the border of our map.
-    // The only real use of our "mean position" is to give us a reference frame:
-    // which direction is east, and which is west.
+    // This has given us either:
+    // - our actual position (preferred), or
+    // - a mean position (fallback if we had no fix)
+    //
+    // What we actually want is to place our center so that our outermost nodes
+    // end up on the border of our map. The only real use of our "center" is to give
+    // us a reference frame: which direction is east, and which is west.
     //------------------------------------------------
 
-    // Find furthest nodes from "mean lat long"
+    // Find furthest nodes from our center
     // ========================================
-
     float northernmost = latCenter;
     float southernmost = latCenter;
     float easternmost = lngCenter;
@@ -184,14 +280,14 @@ void InkHUD::MapApplet::getMapCenter(float *lat, float *lng)
             continue;
 
         // Check for a new top or bottom latitude
-        float lat = node->position.latitude_i * 1e-7;
-        northernmost = max(northernmost, lat);
-        southernmost = min(southernmost, lat);
+        float latNode = node->position.latitude_i * 1e-7;
+        northernmost = max(northernmost, latNode);
+        southernmost = min(southernmost, latNode);
 
         // Longitude is trickier
-        float lng = node->position.longitude_i * 1e-7;
-        float degEastward = fmod(((lng - lngCenter) + 360), 360);      // Degrees traveled east from lngCenter to reach node
-        float degWestward = abs(fmod(((lng - lngCenter) - 360), 360)); // Degrees traveled west from lngCenter to reach node
+        float lngNode = node->position.longitude_i * 1e-7;
+        float degEastward = fmod(((lngNode - lngCenter) + 360), 360);      // Degrees traveled east from lngCenter to reach node
+        float degWestward = abs(fmod(((lngNode - lngCenter) - 360), 360)); // Degrees traveled west from lngCenter to reach node
         if (degEastward < degWestward)
             easternmost = max(easternmost, lngCenter + degEastward);
         else
@@ -250,7 +346,6 @@ InkHUD::MapApplet::Marker InkHUD::MapApplet::calculateMarker(float lat, float ln
     m.hopsAway = hopsAway;
     return m;
 }
-
 // Draw a marker on the map for a node, with a shortname label, and backing box
 void InkHUD::MapApplet::drawLabeledMarker(meshtastic_NodeInfoLite *node)
 {
@@ -324,6 +419,18 @@ void InkHUD::MapApplet::drawLabeledMarker(meshtastic_NodeInfoLite *node)
         textX = labelX + paddingW;
     }
 
+    // Prevent overlap with scale bars and their labels
+    // Define a "safe zone" in the bottom-left where the scale bars and text are drawn
+    constexpr int16_t safeZoneHeight = 28; // adjust based on your label font height
+    constexpr int16_t safeZoneWidth  = 60; // adjust based on horizontal label width zone
+    bool overlapsScale = (labelY + labelH > height() - safeZoneHeight) && (labelX < safeZoneWidth);
+
+    // If it overlaps, shift label upward slightly above the safe zone
+    if (overlapsScale) {
+        labelY = height() - safeZoneHeight - labelH - 2;
+        textY  = labelY + (labelH / 2);
+    }
+
     // Backing box
     fillRect(labelX, labelY, labelW, labelH, WHITE);
     drawRect(labelX, labelY, labelW, labelH, BLACK);

From 68e739359f0b9387025b59f40b289ebdfb2e1622 Mon Sep 17 00:00:00 2001
From: HarukiToreda <116696711+HarukiToreda@users.noreply.github.com>
Date: Sun, 19 Oct 2025 03:36:25 -0400
Subject: [PATCH 519/691] cleanup

---
 .../InkHUD/Applets/Bases/Map/MapApplet.cpp     | 18 +++++++++---------
 1 file changed, 9 insertions(+), 9 deletions(-)

diff --git a/src/graphics/niche/InkHUD/Applets/Bases/Map/MapApplet.cpp b/src/graphics/niche/InkHUD/Applets/Bases/Map/MapApplet.cpp
index 0639501ea..89e6f35f1 100644
--- a/src/graphics/niche/InkHUD/Applets/Bases/Map/MapApplet.cpp
+++ b/src/graphics/niche/InkHUD/Applets/Bases/Map/MapApplet.cpp
@@ -24,13 +24,13 @@ void InkHUD::MapApplet::onRender()
         int16_t x = X(0.5) + (m.eastMeters * metersToPx);
         int16_t y = Y(0.5) - (m.northMeters * metersToPx);
 
-        // --- Add white halo outline first ---
-        constexpr int outlinePad = 2; // size of white outline padding
+        // Add white halo outline first
+        constexpr int outlinePad = 1; // size of white outline padding
         int boxSize = (m.hasHopsAway && m.hopsAway > config.lora.hop_limit) || !m.hasHopsAway ? 12 : 10;
         fillRect(x - (boxSize / 2) - outlinePad, y - (boxSize / 2) - outlinePad,
                 boxSize + (outlinePad * 2), boxSize + (outlinePad * 2), WHITE);
 
-        // --- Draw actual marker on top ---
+        // Draw actual marker on top
         if (m.hasHopsAway && m.hopsAway > config.lora.hop_limit) {
             fillRect(x - boxSize / 2, y - boxSize / 2, boxSize, boxSize, BLACK);
             setFont(fontSmall); setTextColor(WHITE);
@@ -72,7 +72,7 @@ void InkHUD::MapApplet::onRender()
         }
     };
 
-    // === Horizontal scale bar ===
+    // Horizontal scale bar
     int16_t horizBarY = height() - 2;
     int16_t horizBarX = 1;
     drawLine(horizBarX, horizBarY, horizBarX + horizPx, horizBarY, BLACK);
@@ -88,7 +88,7 @@ void InkHUD::MapApplet::onRender()
     fillRect(horizLabelX - 2, horizLabelY - 1, horizLabelW + 4, horizLabelH + 2, WHITE);
     printAt(horizLabelX, horizBarY, horizLabel, LEFT, BOTTOM);
 
-    // === Vertical scale bar ===
+    // Vertical scale bar
     int16_t vertBarX = 1;
     int16_t vertBarBottom = horizBarY;
     int16_t vertBarTop = vertBarBottom - vertPx;
@@ -112,7 +112,7 @@ void InkHUD::MapApplet::onRender()
     fillRect(vertBarX - 2, bottomLabelY - 1, bottomLabelW + 6, bottomLabelH + 2, WHITE);
     printAt(vertBarX + (bottomLabelW / 2) + 1, bottomLabelY + (bottomLabelH / 2), vertBottomLabel, CENTER, MIDDLE);
 
-    // --- Draw our node LAST with full white fill + outline ---
+    // Draw our node LAST with full white fill + outline
     meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
     if (ourNode && nodeDB->hasValidPosition(ourNode)) {
         Marker self = calculateMarker(
@@ -125,15 +125,15 @@ void InkHUD::MapApplet::onRender()
         int16_t centerX = X(0.5) + (self.eastMeters * metersToPx);
         int16_t centerY = Y(0.5) - (self.northMeters * metersToPx);
 
-        // --- White fill background + halo ---
+        // White fill background + halo
         fillCircle(centerX, centerY, 8, WHITE); // big white base
         drawCircle(centerX, centerY, 8, WHITE); // crisp edge
 
-        // --- Black bullseye on top ---
+        // Black bullseye on top
         drawCircle(centerX, centerY, 6, BLACK);
         fillCircle(centerX, centerY, 2, BLACK);
 
-        // --- Crosshairs ---
+        // Crosshairs
         drawLine(centerX - 8, centerY, centerX + 8, centerY, BLACK);
         drawLine(centerX, centerY - 8, centerX, centerY + 8, BLACK);
     }

From 7afc6ef8331d253b78cb597e1ff255ac56db5456 Mon Sep 17 00:00:00 2001
From: HarukiToreda <116696711+HarukiToreda@users.noreply.github.com>
Date: Sun, 19 Oct 2025 03:47:02 -0400
Subject: [PATCH 520/691] trunk

---
 .../InkHUD/Applets/Bases/Map/MapApplet.cpp    | 52 ++++++++++---------
 1 file changed, 27 insertions(+), 25 deletions(-)

diff --git a/src/graphics/niche/InkHUD/Applets/Bases/Map/MapApplet.cpp b/src/graphics/niche/InkHUD/Applets/Bases/Map/MapApplet.cpp
index 89e6f35f1..aee51c6ad 100644
--- a/src/graphics/niche/InkHUD/Applets/Bases/Map/MapApplet.cpp
+++ b/src/graphics/niche/InkHUD/Applets/Bases/Map/MapApplet.cpp
@@ -27,48 +27,54 @@ void InkHUD::MapApplet::onRender()
         // Add white halo outline first
         constexpr int outlinePad = 1; // size of white outline padding
         int boxSize = (m.hasHopsAway && m.hopsAway > config.lora.hop_limit) || !m.hasHopsAway ? 12 : 10;
-        fillRect(x - (boxSize / 2) - outlinePad, y - (boxSize / 2) - outlinePad,
-                boxSize + (outlinePad * 2), boxSize + (outlinePad * 2), WHITE);
+        fillRect(x - (boxSize / 2) - outlinePad, y - (boxSize / 2) - outlinePad, boxSize + (outlinePad * 2),
+                 boxSize + (outlinePad * 2), WHITE);
 
         // Draw actual marker on top
         if (m.hasHopsAway && m.hopsAway > config.lora.hop_limit) {
             fillRect(x - boxSize / 2, y - boxSize / 2, boxSize, boxSize, BLACK);
-            setFont(fontSmall); setTextColor(WHITE);
+            setFont(fontSmall);
+            setTextColor(WHITE);
             printAt(x, y, "X", CENTER, MIDDLE);
-        }
-        else if (!m.hasHopsAway) {
+        } else if (!m.hasHopsAway) {
             fillRect(x - boxSize / 2, y - boxSize / 2, boxSize, boxSize, BLACK);
-            setFont(fontSmall); setTextColor(WHITE);
+            setFont(fontSmall);
+            setTextColor(WHITE);
             printAt(x, y, "?", CENTER, MIDDLE);
-        }
-        else {
+        } else {
             fillRect(x - boxSize / 2, y - boxSize / 2, boxSize, boxSize, BLACK);
-            char hopStr[4]; snprintf(hopStr, sizeof(hopStr), "%d", m.hopsAway);
-            setFont(fontSmall); setTextColor(WHITE);
+            char hopStr[4];
+            snprintf(hopStr, sizeof(hopStr), "%d", m.hopsAway);
+            setFont(fontSmall);
+            setTextColor(WHITE);
             printAt(x, y, hopStr, CENTER, MIDDLE);
         }
 
         // Restore default font and color (safety for rest of UI)
-        setFont(fontSmall); setTextColor(BLACK);
+        setFont(fontSmall);
+        setTextColor(BLACK);
     }
 
     // Dual map scale bars
     int16_t horizPx = width() * 0.25f;
-    int16_t vertPx  = height() * 0.25f;
+    int16_t vertPx = height() * 0.25f;
     float horizMeters = horizPx / metersToPx;
-    float vertMeters  = vertPx / metersToPx;
+    float vertMeters = vertPx / metersToPx;
 
     auto formatDistance = [&](float meters, char *out, size_t len) {
         if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) {
             float feet = meters * 3.28084f;
-            if (feet < 528) snprintf(out, len, "%.0f ft", feet);
+            if (feet < 528)
+                snprintf(out, len, "%.0f ft", feet);
             else {
                 float miles = feet / 5280.0f;
                 snprintf(out, len, miles < 10 ? "%.1f mi" : "%.0f mi", miles);
             }
         } else {
-            if (meters >= 1000) snprintf(out, len, "%.1f km", meters / 1000.0f);
-            else snprintf(out, len, "%.0f m", meters);
+            if (meters >= 1000)
+                snprintf(out, len, "%.1f km", meters / 1000.0f);
+            else
+                snprintf(out, len, "%.0f m", meters);
         }
     };
 
@@ -115,12 +121,7 @@ void InkHUD::MapApplet::onRender()
     // Draw our node LAST with full white fill + outline
     meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
     if (ourNode && nodeDB->hasValidPosition(ourNode)) {
-        Marker self = calculateMarker(
-            ourNode->position.latitude_i * 1e-7,
-            ourNode->position.longitude_i * 1e-7,
-            false,
-            0
-        );
+        Marker self = calculateMarker(ourNode->position.latitude_i * 1e-7, ourNode->position.longitude_i * 1e-7, false, 0);
 
         int16_t centerX = X(0.5) + (self.eastMeters * metersToPx);
         int16_t centerY = Y(0.5) - (self.northMeters * metersToPx);
@@ -160,7 +161,8 @@ void InkHUD::MapApplet::getMapCenter(float *lat, float *lng)
         // - uses tan to find angles for lat / long degrees
         //   - longitude: triangle formed by x and y (on plane of the equator)
         //   - latitude: triangle formed by z (north south),
-        //     and the line along plane of equator which stretches from earth's axis to where point xyz intersects planet's surface
+        //     and the line along plane of equator which stretches from earth's axis to where point xyz intersects planet's
+        //     surface
 
         // Working totals, averaged after nodeDB processed
         uint32_t positionCount = 0;
@@ -422,13 +424,13 @@ void InkHUD::MapApplet::drawLabeledMarker(meshtastic_NodeInfoLite *node)
     // Prevent overlap with scale bars and their labels
     // Define a "safe zone" in the bottom-left where the scale bars and text are drawn
     constexpr int16_t safeZoneHeight = 28; // adjust based on your label font height
-    constexpr int16_t safeZoneWidth  = 60; // adjust based on horizontal label width zone
+    constexpr int16_t safeZoneWidth = 60;  // adjust based on horizontal label width zone
     bool overlapsScale = (labelY + labelH > height() - safeZoneHeight) && (labelX < safeZoneWidth);
 
     // If it overlaps, shift label upward slightly above the safe zone
     if (overlapsScale) {
         labelY = height() - safeZoneHeight - labelH - 2;
-        textY  = labelY + (labelH / 2);
+        textY = labelY + (labelH / 2);
     }
 
     // Backing box

From 05c176c16a1377991bc3de04f6969d2e2d3324fa Mon Sep 17 00:00:00 2001
From: igorka48 <10116759+igorka48@users.noreply.github.com>
Date: Sun, 19 Oct 2025 12:48:06 +0300
Subject: [PATCH 521/691] Added support for SugarCube device (#8187)

* Added support for SugarCube device

* Update variants/esp32/sugarcube/platformio.ini

Co-authored-by: Austin 

* added buzzer pin

* Apply PR comments

* Fix MR comments

---------

Co-authored-by: Austin 
---
 variants/esp32/tlora_v2_1_16/platformio.ini | 9 +++++++++
 variants/esp32/tlora_v2_1_16/variant.h      | 4 ++++
 2 files changed, 13 insertions(+)

diff --git a/variants/esp32/tlora_v2_1_16/platformio.ini b/variants/esp32/tlora_v2_1_16/platformio.ini
index 6967bb480..8d5bdab9e 100644
--- a/variants/esp32/tlora_v2_1_16/platformio.ini
+++ b/variants/esp32/tlora_v2_1_16/platformio.ini
@@ -5,3 +5,12 @@ board_check = true
 build_flags = 
   ${esp32_base.build_flags} -D TLORA_V2_1_16 -I variants/esp32/tlora_v2_1_16
 upload_speed = 115200
+
+[env:sugarcube]
+extends = env:tlora-v2-1-1_6
+board_level = extra
+build_flags =
+  ${env:tlora-v2-1-1_6.build_flags}
+  -DBUTTON_PIN=0
+  -DPIN_BUZZER=25
+  -DLED_PIN=-1
\ No newline at end of file
diff --git a/variants/esp32/tlora_v2_1_16/variant.h b/variants/esp32/tlora_v2_1_16/variant.h
index 48c069ab7..9584dd68b 100644
--- a/variants/esp32/tlora_v2_1_16/variant.h
+++ b/variants/esp32/tlora_v2_1_16/variant.h
@@ -8,7 +8,11 @@
 #define I2C_SDA 21 // I2C pins for this board
 #define I2C_SCL 22
 
+#if defined(LED_PIN) && LED_PIN == -1
+#undef LED_PIN
+#else
 #define LED_PIN 25 // If defined we will blink this LED
+#endif
 
 #define USE_RF95
 #define LORA_DIO0 26 // a No connect on the SX1262 module

From d2403437fff723f2bb6625fe3b5bfbb689e7e358 Mon Sep 17 00:00:00 2001
From: Chloe Bethel 
Date: Sun, 19 Oct 2025 10:35:30 +0100
Subject: [PATCH 522/691] Make packet pool dynamic again on STM32 as a
 workaround

---
 src/mesh/Router.cpp | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp
index 60637cbd1..5cf8bfa7d 100644
--- a/src/mesh/Router.cpp
+++ b/src/mesh/Router.cpp
@@ -35,6 +35,15 @@
     (MAX_RX_TOPHONE + MAX_RX_FROMRADIO + 2 * MAX_TX_QUEUE +                                                                      \
      2) // max number of packets which can be in flight (either queued from reception or queued for sending)
 
+static MemoryDynamic dynamicPool;
+Allocator &packetPool = dynamicPool;
+#elif defined(ARCH_STM32WL)
+// On STM32 there isn't enough heap left over for the rest of the firmware if we allocate this statically.
+// For now, make it dynamic again.
+#define MAX_PACKETS                                                                                                              \
+    (MAX_RX_TOPHONE + MAX_RX_FROMRADIO + 2 * MAX_TX_QUEUE +                                                                      \
+     2) // max number of packets which can be in flight (either queued from reception or queued for sending)
+
 static MemoryDynamic dynamicPool;
 Allocator &packetPool = dynamicPool;
 #else

From f2a63faddd43752decf087a7cda003c809033bb1 Mon Sep 17 00:00:00 2001
From: Mike Robbins 
Date: Sun, 19 Oct 2025 06:32:58 -0400
Subject: [PATCH 523/691] Fix NimbleBluetooth reliability and performance
 (#8385)

* Initial work to get NimbleBluetooth working reliably, and cross-task mutexes cleaned up

* Pre-fill toPhoneQueue when safe (during config/nodeinfo): runOnceToPhoneCanPreloadNextPacket

* Handle 0-byte responses breaking clients during initial config phases

* requestLowerPowerConnection

* PhoneAPI: onConfigStart and onConfigComplete callbacks for subclasses

* NimbleBluetooth: switch to high-throughput BLE mode during config, then lower-power BLE mode for steady-state

* Add some documentation to NimbleBluetooth.cpp

* make cppcheck happier

* Allow runOnceHandleToPhoneQueue to tell runOnce to shouldBreakAndRetryLater, so we don't busy-loop forever in runOnce

* Gating some logging behind DEBUG_NIMBLE_ON_READ_TIMING ifdef again; bump retry count

* Add check for connected state in NimBLE onRead()

---------

Co-authored-by: Jonathan Bennett 
---
 src/mesh/PhoneAPI.cpp          |  31 +-
 src/mesh/PhoneAPI.h            |   6 +
 src/nimble/NimbleBluetooth.cpp | 536 +++++++++++++++++++++++++++++----
 3 files changed, 512 insertions(+), 61 deletions(-)

diff --git a/src/mesh/PhoneAPI.cpp b/src/mesh/PhoneAPI.cpp
index 51a2bc148..d1e342c80 100644
--- a/src/mesh/PhoneAPI.cpp
+++ b/src/mesh/PhoneAPI.cpp
@@ -57,6 +57,9 @@ void PhoneAPI::handleStartConfig()
 #endif
     }
 
+    // Allow subclasses to prepare for high-throughput config traffic
+    onConfigStart();
+
     // even if we were already connected - restart our state machine
     if (config_nonce == SPECIAL_NONCE_ONLY_NODES) {
         // If client only wants node info, jump directly to sending nodes
@@ -71,7 +74,7 @@ void PhoneAPI::handleStartConfig()
     spiLock->unlock();
     LOG_DEBUG("Got %d files in manifest", filesManifest.size());
 
-    LOG_INFO("Start API client config");
+    LOG_INFO("Start API client config millis=%u", millis());
     // Protect against concurrent BLE callbacks: they run in NimBLE's FreeRTOS task and also touch nodeInfoQueue.
     {
         concurrency::LockGuard guard(&nodeInfoMutex);
@@ -453,7 +456,10 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
         break;
 
     case STATE_SEND_OTHER_NODEINFOS: {
-        LOG_DEBUG("Send known nodes");
+        if (readIndex == 2) { //  readIndex==2 will be true for the first non-us node
+            LOG_INFO("Start sending nodeinfos millis=%u", millis());
+        }
+
         meshtastic_NodeInfo infoToSend = {};
         {
             concurrency::LockGuard guard(&nodeInfoMutex);
@@ -470,13 +476,22 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
         if (infoToSend.num != 0) {
             // Just in case we stored a different user.id in the past, but should never happen going forward
             sprintf(infoToSend.user.id, "!%08x", infoToSend.num);
-            LOG_DEBUG("nodeinfo: num=0x%x, lastseen=%u, id=%s, name=%s", infoToSend.num, infoToSend.last_heard,
-                      infoToSend.user.id, infoToSend.user.long_name);
+
+            // Logging this really slows down sending nodes on initial connection because the serial console is so slow, so only
+            // uncomment if you really need to:
+            // LOG_INFO("nodeinfo: num=0x%x, lastseen=%u, id=%s, name=%s", nodeInfoForPhone.num, nodeInfoForPhone.last_heard,
+            // nodeInfoForPhone.user.id, nodeInfoForPhone.user.long_name);
+
+            // Occasional progress logging. (readIndex==2 will be true for the first non-us node)
+            if (readIndex == 2 || readIndex % 20 == 0) {
+                LOG_DEBUG("nodeinfo: %d/%d", readIndex, nodeDB->getNumMeshNodes());
+            }
+
             fromRadioScratch.which_payload_variant = meshtastic_FromRadio_node_info_tag;
             fromRadioScratch.node_info = infoToSend;
             prefetchNodeInfos();
         } else {
-            LOG_DEBUG("Done sending nodeinfo");
+            LOG_DEBUG("Done sending %d of %d nodeinfos millis=%u", readIndex, nodeDB->getNumMeshNodes(), millis());
             concurrency::LockGuard guard(&nodeInfoMutex);
             nodeInfoQueue.clear();
             state = STATE_SEND_FILEMANIFEST;
@@ -558,11 +573,15 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
 
 void PhoneAPI::sendConfigComplete()
 {
-    LOG_INFO("Config Send Complete");
+    LOG_INFO("Config Send Complete millis=%u", millis());
     fromRadioScratch.which_payload_variant = meshtastic_FromRadio_config_complete_id_tag;
     fromRadioScratch.config_complete_id = config_nonce;
     config_nonce = 0;
     state = STATE_SEND_PACKETS;
+
+    // Allow subclasses to know we've entered steady-state so they can lower power consumption
+    onConfigComplete();
+
     pauseBluetoothLogging = false;
 }
 
diff --git a/src/mesh/PhoneAPI.h b/src/mesh/PhoneAPI.h
index a8d0faa28..d6682684f 100644
--- a/src/mesh/PhoneAPI.h
+++ b/src/mesh/PhoneAPI.h
@@ -136,6 +136,7 @@ class PhoneAPI
     bool available();
 
     bool isConnected() { return state != STATE_SEND_NOTHING; }
+    bool isSendingPackets() { return state == STATE_SEND_PACKETS; }
 
   protected:
     /// Our fromradio packet while it is being assembled
@@ -158,6 +159,11 @@ class PhoneAPI
      */
     virtual void onNowHasData(uint32_t fromRadioNum) {}
 
+    /// Subclasses can use these lifecycle hooks for transport-specific behavior around config/steady-state
+    /// (i.e. BLE connection params)
+    virtual void onConfigStart() {}
+    virtual void onConfigComplete() {}
+
     /// begin a new connection
     void handleStartConfig();
 
diff --git a/src/nimble/NimbleBluetooth.cpp b/src/nimble/NimbleBluetooth.cpp
index eb1d909f1..9accf23c6 100644
--- a/src/nimble/NimbleBluetooth.cpp
+++ b/src/nimble/NimbleBluetooth.cpp
@@ -3,12 +3,15 @@
 #include "BluetoothCommon.h"
 #include "NimbleBluetooth.h"
 #include "PowerFSM.h"
+#include "StaticPointerQueue.h"
 
+#include "concurrency/OSThread.h"
 #include "main.h"
 #include "mesh/PhoneAPI.h"
 #include "mesh/mesh-pb-constants.h"
 #include "sleep.h"
 #include 
+#include 
 #include 
 
 #ifdef NIMBLE_TWO
@@ -32,45 +35,288 @@ constexpr uint16_t kPreferredBleTxTimeUs = (kPreferredBleTxOctets + 14) * 8;
 } // namespace
 #endif
 
+// Debugging options: careful, they slow things down quite a bit!
+// #define DEBUG_NIMBLE_ON_READ_TIMING  // uncomment to time onRead duration
+// #define DEBUG_NIMBLE_ON_WRITE_TIMING // uncomment to time onWrite duration
+// #define DEBUG_NIMBLE_NOTIFY          // uncomment to enable notify logging
+
+#define NIMBLE_BLUETOOTH_TO_PHONE_QUEUE_SIZE 3
+#define NIMBLE_BLUETOOTH_FROM_PHONE_QUEUE_SIZE 3
+
 NimBLECharacteristic *fromNumCharacteristic;
 NimBLECharacteristic *BatteryCharacteristic;
 NimBLECharacteristic *logRadioCharacteristic;
 NimBLEServer *bleServer;
 
 static bool passkeyShowing;
+static std::atomic nimbleBluetoothConnHandle{-1}; // actual handles are uint16_t, so -1 means "no connection"
 
 class BluetoothPhoneAPI : public PhoneAPI, public concurrency::OSThread
 {
+    /*
+      CAUTION: There's a lot going on here and lots of room to break things.
+
+      This NimbleBluetooth.cpp file does some tricky synchronization between the NimBLE FreeRTOS task (which runs the onRead and
+      onWrite callbacks) and the main task (which runs runOnce and the rest of PhoneAPI).
+
+      The main idea is to add a little bit of synchronization here to make it so that the rest of the codebase doesn't have to
+      know about concurrency and mutexes, and can just run happily ever after as a cooperative multitasking OSThread system, where
+      locking isn't something that anyone has to worry about too much! :)
+
+      We achieve this by having some queues and mutexes in this file only, and ensuring that all calls to getFromRadio and
+      handleToRadio are only made from the main FreeRTOS task. This way, the rest of the codebase doesn't have to worry about
+      being run concurrently, which would make everything else much much much more complicated.
+
+      PHONE -> RADIO:
+        - [NimBLE FreeRTOS task:] onWrite callback holds fromPhoneMutex and pushes received packets into fromPhoneQueue.
+        - [Main task:] runOnceHandleFromPhoneQueue in main task holds fromPhoneMutex, pulls packets from fromPhoneQueue, and calls
+      handleToRadio **in main task**.
+
+      RADIO -> PHONE:
+        - [NimBLE FreeRTOS task:] onRead callback sets onReadCallbackIsWaitingForData flag and polls in a busy loop. (unless
+      there's already a packet waiting in toPhoneQueue)
+        - [Main task:] runOnceHandleToPhoneQueue sees onReadCallbackIsWaitingForData flag, calls getFromRadio **in main task** to
+      get packets from radio, holds toPhoneMutex, pushes the packet into toPhoneQueue, and clears the
+      onReadCallbackIsWaitingForData flag.
+        - [NimBLE FreeRTOS task:] onRead callback sees that the onReadCallbackIsWaitingForData flag cleared, holds toPhoneMutex,
+      pops the packet from toPhoneQueue, and returns it to NimBLE.
+
+      MUTEXES:
+        - fromPhoneMutex protects fromPhoneQueue and fromPhoneQueueSize
+        - toPhoneMutex protects toPhoneQueue, toPhoneQueueByteSizes, and toPhoneQueueSize
+
+      ATOMICS:
+        - fromPhoneQueueSize is only increased by onWrite, and only decreased by runOnceHandleFromPhoneQueue (or onDisconnect).
+        - toPhoneQueueSize is only increased by runOnceHandleToPhoneQueue, and only decreased by onRead (or onDisconnect).
+        - onReadCallbackIsWaitingForData is a flag. It's only set by onRead, and only cleared by runOnceHandleToPhoneQueue (or
+      onDisconnect).
+
+      PRELOADING: see comments in runOnceToPhoneCanPreloadNextPacket about when it's safe to preload packets from getFromRadio.
+
+      BLE CONNECTION PARAMS:
+        - During config, we request a high-throughput, low-latency BLE connection for speed.
+        - After config, we switch to a lower-power BLE connection for steady-state use to extend battery life.
+
+      MEMORY MANAGEMENT:
+        - We keep packets on the stack and do not allocate heap.
+        - We use std::array for fromPhoneQueue and toPhoneQueue to avoid mallocs and frees across FreeRTOS tasks.
+        - Yes, we have to do some copy operations on pop because of this, but it's worth it to avoid cross-task memory management.
+
+      NOTIFY IS BROKEN:
+        - Adding NIMBLE_PROPERTY::NOTIFY to FromRadioCharacteristic appears to break things. It is NOT backwards compatible.
+
+      ZERO-SIZE READS:
+        - Returning a zero-size read from onRead breaks some clients during the config phase. So we have to block onRead until we
+      have data.
+        - During the STATE_SEND_PACKETS phase, it's totally OK to return zero-size reads, as clients are expected to do reads
+      until they get a 0-byte response.
+
+      CROSS-TASK WAKEUP:
+        - If you call: bluetoothPhoneAPI->setIntervalFromNow(0); to schedule immediate processing of new data,
+        - Then you should also call: concurrency::mainDelay.interrupt(); to wake up the main loop if it's sleeping.
+        - Otherwise, you're going to wait ~100ms or so until the main loop wakes up from some other cause.
+    */
+
   public:
-    BluetoothPhoneAPI() : concurrency::OSThread("NimbleBluetooth") { nimble_queue.resize(3); }
-    std::vector nimble_queue;
-    std::mutex nimble_mutex;
-    uint8_t queue_size = 0;
-    uint8_t fromRadioBytes[meshtastic_FromRadio_size] = {0};
-    size_t numBytes = 0;
-    bool hasChecked = false;
-    bool phoneWants = false;
+    BluetoothPhoneAPI() : concurrency::OSThread("NimbleBluetooth") {}
+
+    /* Packets from phone (BLE onWrite callback) */
+    std::mutex fromPhoneMutex;
+    std::atomic fromPhoneQueueSize{0};
+    // We use array here (and pay the cost of memcpy) to avoid dynamic memory allocations and frees across FreeRTOS tasks.
+    std::array fromPhoneQueue{};
+
+    /* Packets to phone (BLE onRead callback) */
+    std::mutex toPhoneMutex;
+    std::atomic toPhoneQueueSize{0};
+    // We use array here (and pay the cost of memcpy) to avoid dynamic memory allocations and frees across FreeRTOS tasks.
+    std::array, NIMBLE_BLUETOOTH_TO_PHONE_QUEUE_SIZE> toPhoneQueue{};
+    std::array toPhoneQueueByteSizes{};
+    // The onReadCallbackIsWaitingForData flag provides synchronization between the NimBLE task's onRead callback and our main
+    // task's runOnce. It's only set by onRead, and only cleared by runOnce.
+    std::atomic onReadCallbackIsWaitingForData{false};
+
+    /* Statistics/logging helpers */
+    std::atomic readCount{0};
+    std::atomic notifyCount{0};
+    std::atomic writeCount{0};
 
   protected:
     virtual int32_t runOnce() override
     {
-        std::lock_guard guard(nimble_mutex);
-        if (queue_size > 0) {
-            for (uint8_t i = 0; i < queue_size; i++) {
-                handleToRadio(nimble_queue.at(i).data(), nimble_queue.at(i).length());
+        bool shouldBreakAndRetryLater = false;
+
+        while (runOnceHasWorkToDo()) {
+            // Important that we service onRead first, because the onRead callback blocks NimBLE until we clear
+            // onReadCallbackIsWaitingForData.
+            shouldBreakAndRetryLater = runOnceHandleToPhoneQueue(); // push data from getFromRadio to onRead
+            runOnceHandleFromPhoneQueue();                          // pull data from onWrite to handleToRadio
+
+            if (shouldBreakAndRetryLater) {
+                // onRead still wants data, but it's not available yet. Return so we can try again when a packet may be ready.
+#ifdef DEBUG_NIMBLE_ON_READ_TIMING
+                LOG_INFO("BLE runOnce breaking to retry later (leaving onRead waiting)");
+#endif
+                return 100; // try again in 100ms
             }
-            LOG_DEBUG("Queue_size %u", queue_size);
-            queue_size = 0;
-        }
-        if (!hasChecked && phoneWants) {
-            // Pull fresh data while we're outside of the NimBLE callback context.
-            numBytes = getFromRadio(fromRadioBytes);
-            hasChecked = true;
         }
 
         // the run is triggered via NimbleBluetoothToRadioCallback and NimbleBluetoothFromRadioCallback
         return INT32_MAX;
     }
+
+    virtual void onConfigStart() override
+    {
+        LOG_INFO("BLE onConfigStart");
+
+        // Prefer high throughput during config/setup, at the cost of high power consumption (for a few seconds)
+        if (bleServer && isConnected()) {
+            int32_t conn_handle = nimbleBluetoothConnHandle.load();
+            if (conn_handle != -1) {
+                requestHighThroughputConnection(static_cast(conn_handle));
+            }
+        }
+    }
+
+    virtual void onConfigComplete() override
+    {
+        LOG_INFO("BLE onConfigComplete");
+
+        // Switch to lower power consumption BLE connection params for steady-state use after config/setup is complete
+        if (bleServer && isConnected()) {
+            int32_t conn_handle = nimbleBluetoothConnHandle.load();
+            if (conn_handle != -1) {
+                requestLowerPowerConnection(static_cast(conn_handle));
+            }
+        }
+    }
+
+    bool runOnceHasWorkToDo() { return runOnceHasWorkToPhone() || runOnceHasWorkFromPhone(); }
+
+    bool runOnceHasWorkToPhone() { return onReadCallbackIsWaitingForData || runOnceToPhoneCanPreloadNextPacket(); }
+
+    bool runOnceToPhoneCanPreloadNextPacket()
+    {
+        /*
+         * PRELOADING getFromRadio RESPONSES:
+         *
+         * It's not safe to preload packets if we're in STATE_SEND_PACKETS, because there may be a while between the time we call
+         * getFromRadio and when the client actually reads it. If the connection drops in that time, we might lose that packet
+         * forever. In STATE_SEND_PACKETS, if we wait for onRead before we call getFromRadio, we minimize the time window where
+         * the client might disconnect before completing the read.
+         *
+         * However, if we're in the setup states (sending config, nodeinfo, etc), it's safe and beneficial to preload packets into
+         * toPhoneQueue because the client will just reconnect after a disconnect, losing nothing.
+         */
+
+        if (!isConnected()) {
+            return false;
+        } else if (isSendingPackets()) {
+            // If we're in STATE_SEND_PACKETS, we must wait for onRead before calling getFromRadio.
+            return false;
+        } else {
+            // In other states, we can preload as long as there's space in the toPhoneQueue.
+            return toPhoneQueueSize < NIMBLE_BLUETOOTH_TO_PHONE_QUEUE_SIZE;
+        }
+    }
+
+    bool runOnceHandleToPhoneQueue()
+    {
+        // Returns false normally.
+        // Returns true if we should break out of runOnce and retry later, such as setup states where getFromRadio returns 0
+        // bytes.
+
+        // Stack buffer for getFromRadio packet
+        uint8_t fromRadioBytes[meshtastic_FromRadio_size] = {0};
+        size_t numBytes = 0;
+
+        if (onReadCallbackIsWaitingForData || runOnceToPhoneCanPreloadNextPacket()) {
+            numBytes = getFromRadio(fromRadioBytes);
+
+            if (numBytes == 0) {
+                // Client expected a read, but we have nothing to send.
+                // Returning a 0-byte packet breaks clients during the config phase, so we have to block onRead until there's a
+                // packet ready.
+                if (isSendingPackets()) {
+                    // In STATE_SEND_PACKETS, it is 100% OK to return a 0-byte response, as we expect clients to do read beyond
+                    // notifies regularly, to make sure they have nothing else to read.
+#ifdef DEBUG_NIMBLE_ON_READ_TIMING
+                    LOG_DEBUG("BLE getFromRadio returned numBytes=0, but in STATE_SEND_PACKETS, so clearing "
+                              "onReadCallbackIsWaitingForData flag");
+#endif
+                } else {
+                    // In other states, this breaks clients.
+                    // Return early, leaving onReadCallbackIsWaitingForData==true so onRead knows to try again.
+                    // This gives runOnce a chance to handleToRadio and produce a response.
+#ifdef DEBUG_NIMBLE_ON_READ_TIMING
+                    LOG_DEBUG("BLE getFromRadio returned numBytes=0. Blocking onRead until we have data");
+#endif
+
+                    // Return true to tell runOnce to shouldBreakAndRetryLater, so we don't busy-loop in runOnce even though
+                    // onRead is still waiting!
+                    return true;
+                }
+            } else {
+                // Push to toPhoneQueue, protected by toPhoneMutex. Hold the mutex as briefly as possible.
+                if (toPhoneQueueSize < NIMBLE_BLUETOOTH_TO_PHONE_QUEUE_SIZE) {
+                    // Note: the comparison above is safe without a mutex because we are the only method that *increases*
+                    // toPhoneQueueSize. (It's okay if toPhoneQueueSize *decreases* in the NimBLE task meanwhile.)
+
+                    { // scope for toPhoneMutex mutex
+                        std::lock_guard guard(toPhoneMutex);
+                        size_t storeAtIndex = toPhoneQueueSize.load();
+                        memcpy(toPhoneQueue[storeAtIndex].data(), fromRadioBytes, numBytes);
+                        toPhoneQueueByteSizes[storeAtIndex] = numBytes;
+                        toPhoneQueueSize++;
+                    }
+#ifdef DEBUG_NIMBLE_ON_READ_TIMING
+                    LOG_DEBUG("BLE getFromRadio returned numBytes=%u, pushed toPhoneQueueSize=%u", numBytes,
+                              toPhoneQueueSize.load());
+#endif
+                } else {
+                    // Shouldn't happen because the onRead callback shouldn't be waiting if the queue is full!
+                    LOG_ERROR("Shouldn't happen! Drop FromRadio packet, toPhoneQueue full (%u bytes)", numBytes);
+                }
+            }
+
+            // Clear the onReadCallbackIsWaitingForData flag so onRead knows it can proceed.
+            onReadCallbackIsWaitingForData = false; // only clear this flag AFTER the push
+        }
+
+        return false;
+    }
+
+    bool runOnceHasWorkFromPhone() { return fromPhoneQueueSize > 0; }
+
+    void runOnceHandleFromPhoneQueue()
+    {
+        // Handle packets we received from onWrite from the phone.
+        if (fromPhoneQueueSize > 0) {
+            // Note: the comparison above is safe without a mutex because we are the only method that *decreases*
+            // fromPhoneQueueSize. (It's okay if fromPhoneQueueSize *increases* in the NimBLE task meanwhile.)
+
+            LOG_DEBUG("NimbleBluetooth: handling ToRadio packet, fromPhoneQueueSize=%u", fromPhoneQueueSize.load());
+
+            // Pop the front of fromPhoneQueue, holding the mutex only briefly while we pop.
+            NimBLEAttValue val;
+            { // scope for fromPhoneMutex mutex
+                std::lock_guard guard(fromPhoneMutex);
+                val = fromPhoneQueue[0];
+
+                // Shift the rest of the queue down
+                for (uint8_t i = 1; i < fromPhoneQueueSize; i++) {
+                    fromPhoneQueue[i - 1] = fromPhoneQueue[i];
+                }
+
+                // Safe decrement due to onDisconnect
+                if (fromPhoneQueueSize > 0)
+                    fromPhoneQueueSize--;
+            }
+
+            handleToRadio(val.data(), val.length());
+        }
+    }
+
     /**
      * Subclasses can use this as a hook to provide custom notifications for their transport (i.e. bluetooth notifies)
      */
@@ -78,14 +324,22 @@ class BluetoothPhoneAPI : public PhoneAPI, public concurrency::OSThread
     {
         PhoneAPI::onNowHasData(fromRadioNum);
 
+        int currentNotifyCount = notifyCount.fetch_add(1);
+
         uint8_t cc = bleServer->getConnectedCount();
-        LOG_DEBUG("BLE notify fromNum: %d connections: %d", fromRadioNum, cc);
+
+#ifdef DEBUG_NIMBLE_NOTIFY
+        // This logging slows things down when there are lots of packets going to the phone, like initial connection:
+        LOG_DEBUG("BLE notify(%d) fromNum: %d connections: %d", currentNotifyCount, fromRadioNum, cc);
+#endif
 
         uint8_t val[4];
         put_le32(val, fromRadioNum);
 
         fromNumCharacteristic->setValue(val, sizeof(val));
 #ifdef NIMBLE_TWO
+        // NOTE: I don't have any NIMBLE_TWO devices, but this line makes me suspicious, and I suspect it needs to just be
+        // notify().
         fromNumCharacteristic->notify(val, sizeof(val), BLE_HS_CONN_HANDLE_NONE);
 #else
         fromNumCharacteristic->notify();
@@ -94,6 +348,54 @@ class BluetoothPhoneAPI : public PhoneAPI, public concurrency::OSThread
 
     /// Check the current underlying physical link to see if the client is currently connected
     virtual bool checkIsConnected() { return bleServer && bleServer->getConnectedCount() > 0; }
+
+    void requestHighThroughputConnection(uint16_t conn_handle)
+    {
+        /* Request a lower-latency, higher-throughput BLE connection.
+
+        This comes at the cost of higher power consumption, so we may want to only use this for initial setup, and then switch to
+        a slower mode.
+
+        See https://developer.apple.com/library/archive/qa/qa1931/_index.html for formulas to calculate values, iOS/macOS
+        constraints, and recommendations. (Android doesn't have specific constraints, but seems to be compatible with the Apple
+        recommendations.)
+
+        Selected settings:
+            minInterval (units of 1.25ms): 7.5ms = 6 (lower than the Apple recommended minimum, but allows faster when the client
+        supports it.)
+            maxInterval (units of 1.25ms): 15ms = 12
+            latency: 0 (don't allow peripheral to skip any connection events)
+            timeout (units of 10ms): 6 seconds = 600 (supervision timeout)
+
+        These are intentionally aggressive to prioritize speed over power consumption, but are only used for a few seconds at
+        setup. Not worth adjusting much.
+        */
+        LOG_INFO("BLE requestHighThroughputConnection");
+        bleServer->updateConnParams(conn_handle, 6, 12, 0, 600);
+    }
+
+    void requestLowerPowerConnection(uint16_t conn_handle)
+    {
+        /* Request a lower power consumption (but higher latency, lower throughput) BLE connection.
+
+        This is suitable for steady-state operation after initial setup is complete.
+
+        See https://developer.apple.com/library/archive/qa/qa1931/_index.html for formulas to calculate values, iOS/macOS
+        constraints, and recommendations. (Android doesn't have specific constraints, but seems to be compatible with the Apple
+        recommendations.)
+
+        Selected settings:
+            minInterval (units of 1.25ms): 30ms = 24
+            maxInterval (units of 1.25ms): 50ms = 40
+            latency: 2 (allow peripheral to skip up to 2 consecutive connection events to save power)
+            timeout (units of 10ms): 6 seconds = 600 (supervision timeout)
+
+        There's an opportunity for tuning here if anyone wants to do some power measurements, but these should allow 10-20 packets
+        per second.
+        */
+        LOG_INFO("BLE requestLowerPowerConnection");
+        bleServer->updateConnParams(conn_handle, 24, 40, 2, 600);
+    }
 };
 
 static BluetoothPhoneAPI *bluetoothPhoneAPI;
@@ -113,18 +415,45 @@ class NimbleBluetoothToRadioCallback : public NimBLECharacteristicCallbacks
 
 #endif
     {
+        // CAUTION: This callback runs in the NimBLE task!!! Don't do anything except communicate with the main task's runOnce.
+        // Assumption: onWrite is serialized by NimBLE, so we don't need to lock here against multiple concurrent onWrite calls.
+
+        int currentWriteCount = bluetoothPhoneAPI->writeCount.fetch_add(1);
+
+#ifdef DEBUG_NIMBLE_ON_WRITE_TIMING
+        int startMillis = millis();
+        LOG_DEBUG("BLE onWrite(%d): start millis=%d", currentWriteCount, startMillis);
+#endif
+
         auto val = pCharacteristic->getValue();
 
         if (memcmp(lastToRadio, val.data(), val.length()) != 0) {
-            if (bluetoothPhoneAPI->queue_size < 3) {
+            if (bluetoothPhoneAPI->fromPhoneQueueSize < NIMBLE_BLUETOOTH_FROM_PHONE_QUEUE_SIZE) {
+                // Note: the comparison above is safe without a mutex because we are the only method that *increases*
+                // fromPhoneQueueSize. (It's okay if fromPhoneQueueSize *decreases* in the main task meanwhile.)
                 memcpy(lastToRadio, val.data(), val.length());
-                std::lock_guard guard(bluetoothPhoneAPI->nimble_mutex);
-                bluetoothPhoneAPI->nimble_queue.at(bluetoothPhoneAPI->queue_size) = val;
-                bluetoothPhoneAPI->queue_size++;
+
+                { // scope for fromPhoneMutex mutex
+                    // Append to fromPhoneQueue, protected by fromPhoneMutex. Hold the mutex as briefly as possible.
+                    std::lock_guard guard(bluetoothPhoneAPI->fromPhoneMutex);
+                    bluetoothPhoneAPI->fromPhoneQueue.at(bluetoothPhoneAPI->fromPhoneQueueSize) = val;
+                    bluetoothPhoneAPI->fromPhoneQueueSize++;
+                }
+
+                // After releasing the mutex, schedule immediate processing of the new packet.
                 bluetoothPhoneAPI->setIntervalFromNow(0);
+                concurrency::mainDelay.interrupt(); // wake up main loop if sleeping
+
+#ifdef DEBUG_NIMBLE_ON_WRITE_TIMING
+                int finishMillis = millis();
+                LOG_DEBUG("BLE onWrite(%d): append to fromPhoneQueue took %u ms. numBytes=%d", currentWriteCount,
+                          finishMillis - startMillis, val.length());
+#endif
+            } else {
+                LOG_WARN("BLE onWrite(%d): Drop ToRadio packet, fromPhoneQueue full (%u bytes)", currentWriteCount, val.length());
             }
         } else {
-            LOG_DEBUG("Drop duplicate ToRadio packet (%u bytes)", val.length());
+            LOG_DEBUG("BLE onWrite(%d): Drop duplicate ToRadio packet (%u bytes)", currentWriteCount, val.length());
         }
     }
 };
@@ -137,32 +466,111 @@ class NimbleBluetoothFromRadioCallback : public NimBLECharacteristicCallbacks
     virtual void onRead(NimBLECharacteristic *pCharacteristic)
 #endif
     {
-        bluetoothPhoneAPI->phoneWants = true;
-        bluetoothPhoneAPI->setIntervalFromNow(0);
-        std::lock_guard guard(bluetoothPhoneAPI->nimble_mutex); // BLE callbacks run in NimBLE task
+        // In some cases, it seems a new connection starts with a read.
+        // The API has no bytes to send, leading to a timeout. This short-circuits this problem.
+        if (!bluetoothPhoneAPI->isConnected())
+            return;
+        // CAUTION: This callback runs in the NimBLE task!!! Don't do anything except communicate with the main task's runOnce.
 
-        if (!bluetoothPhoneAPI->hasChecked) {
-            // Fetch payload on demand; prefetch keeps this fast for the first read.
-            bluetoothPhoneAPI->numBytes = bluetoothPhoneAPI->getFromRadio(bluetoothPhoneAPI->fromRadioBytes);
-            bluetoothPhoneAPI->hasChecked = true;
-        }
+        int currentReadCount = bluetoothPhoneAPI->readCount.fetch_add(1);
+        int tries = 0;
+        int startMillis = millis();
 
-        pCharacteristic->setValue(bluetoothPhoneAPI->fromRadioBytes, bluetoothPhoneAPI->numBytes);
-
-        if (bluetoothPhoneAPI->numBytes != 0) {
-#ifdef NIMBLE_TWO
-            // Notify immediately so subscribed clients see the packet without an extra read.
-            pCharacteristic->notify(bluetoothPhoneAPI->fromRadioBytes, bluetoothPhoneAPI->numBytes, BLE_HS_CONN_HANDLE_NONE);
-#else
-            pCharacteristic->notify();
+#ifdef DEBUG_NIMBLE_ON_READ_TIMING
+        LOG_DEBUG("BLE onRead(%d): start millis=%d", currentReadCount, startMillis);
 #endif
+
+        // Is there a packet ready to go, or do we have to ask the main task to get one for us?
+        if (bluetoothPhoneAPI->toPhoneQueueSize > 0) {
+            // Note: the comparison above is safe without a mutex because we are the only method that *decreases*
+            // toPhoneQueueSize. (It's okay if toPhoneQueueSize *increases* in the main task meanwhile.)
+
+            // There's already a packet queued. Great! We don't need to wait for onReadCallbackIsWaitingForData.
+#ifdef DEBUG_NIMBLE_ON_READ_TIMING
+            LOG_DEBUG("BLE onRead(%d): packet already waiting, no need to set onReadCallbackIsWaitingForData", currentReadCount);
+#endif
+        } else {
+            // Tell the main task that we'd like a packet.
+            bluetoothPhoneAPI->onReadCallbackIsWaitingForData = true;
+
+            // Wait for the main task to produce a packet for us, up to about 20 seconds.
+            // It normally takes just a few milliseconds, but at initial startup, etc, the main task can get blocked for longer
+            // doing various setup tasks.
+            while (bluetoothPhoneAPI->onReadCallbackIsWaitingForData && tries < 4000) {
+                // Schedule the main task runOnce to run ASAP.
+                bluetoothPhoneAPI->setIntervalFromNow(0);
+                concurrency::mainDelay.interrupt(); // wake up main loop if sleeping
+
+                if (!bluetoothPhoneAPI->onReadCallbackIsWaitingForData) {
+                    // we may be able to break even before a delay, if the call to interrupt woke up the main loop and it ran
+                    // already
+#ifdef DEBUG_NIMBLE_ON_READ_TIMING
+                    LOG_DEBUG("BLE onRead(%d): broke before delay after %u ms, %d tries", currentReadCount,
+                              millis() - startMillis, tries);
+#endif
+                    break;
+                }
+
+                // This delay happens in the NimBLE FreeRTOS task, which really can't do anything until we get a value back.
+                // No harm in polling pretty frequently.
+                delay(tries < 20 ? 1 : 5);
+                tries++;
+
+                if (tries == 4000) {
+                    LOG_WARN(
+                        "BLE onRead(%d): timeout waiting for data after %u ms, %d tries, giving up and returning 0-size response",
+                        currentReadCount, millis() - startMillis, tries);
+                }
+            }
         }
 
-        if (bluetoothPhoneAPI->numBytes != 0) // if we did send something, queue it up right away to reload
+        // Pop from toPhoneQueue, protected by toPhoneMutex. Hold the mutex as briefly as possible.
+        uint8_t fromRadioBytes[meshtastic_FromRadio_size] = {0}; // Stack buffer for getFromRadio packet
+        size_t numBytes = 0;
+        { // scope for toPhoneMutex mutex
+            std::lock_guard guard(bluetoothPhoneAPI->toPhoneMutex);
+            size_t toPhoneQueueSize = bluetoothPhoneAPI->toPhoneQueueSize.load();
+            if (toPhoneQueueSize > 0) {
+                // Copy from the front of the toPhoneQueue
+                memcpy(fromRadioBytes, bluetoothPhoneAPI->toPhoneQueue[0].data(), bluetoothPhoneAPI->toPhoneQueueByteSizes[0]);
+                numBytes = bluetoothPhoneAPI->toPhoneQueueByteSizes[0];
+
+                // Shift the rest of the queue down
+                for (uint8_t i = 1; i < toPhoneQueueSize; i++) {
+                    memcpy(bluetoothPhoneAPI->toPhoneQueue[i - 1].data(), bluetoothPhoneAPI->toPhoneQueue[i].data(),
+                           bluetoothPhoneAPI->toPhoneQueueByteSizes[i]);
+                    // The above line is similar to:
+                    //   bluetoothPhoneAPI->toPhoneQueue[i - 1] = bluetoothPhoneAPI->toPhoneQueue[i]
+                    // but is usually faster because it doesn't have to copy all the trailing bytes beyond
+                    // toPhoneQueueByteSizes[i].
+                    //
+                    // We deliberately use an array here (and pay the CPU cost of some memcpy) to avoid synchronizing dynamic
+                    // memory allocations and frees across FreeRTOS tasks.
+
+                    bluetoothPhoneAPI->toPhoneQueueByteSizes[i - 1] = bluetoothPhoneAPI->toPhoneQueueByteSizes[i];
+                }
+
+                // Safe decrement due to onDisconnect
+                if (bluetoothPhoneAPI->toPhoneQueueSize > 0)
+                    bluetoothPhoneAPI->toPhoneQueueSize--;
+            } else {
+                // nothing in the toPhoneQueue; that's fine, and we'll just have numBytes=0.
+            }
+        }
+
+#ifdef DEBUG_NIMBLE_ON_READ_TIMING
+        int finishMillis = millis();
+        LOG_DEBUG("BLE onRead(%d): onReadCallbackIsWaitingForData took %u ms, %d tries. numBytes=%d", currentReadCount,
+                  finishMillis - startMillis, tries, numBytes);
+#endif
+
+        pCharacteristic->setValue(fromRadioBytes, numBytes);
+
+        // If we sent something, wake up the main loop if it's sleeping in case there are more packets ready to enqueue.
+        if (numBytes != 0) {
             bluetoothPhoneAPI->setIntervalFromNow(0);
-        bluetoothPhoneAPI->numBytes = 0;
-        bluetoothPhoneAPI->hasChecked = false;
-        bluetoothPhoneAPI->phoneWants = false;
+            concurrency::mainDelay.interrupt(); // wake up main loop if sleeping
+        }
     }
 };
 
@@ -244,6 +652,13 @@ class NimbleBluetoothServerCallback : public NimBLEServerCallbacks
             if (screen)
                 screen->endAlert();
         }
+
+        // Store the connection handle for future use
+#ifdef NIMBLE_TWO
+        nimbleBluetoothConnHandle = connInfo.getConnHandle();
+#else
+        nimbleBluetoothConnHandle = desc->conn_handle;
+#endif
     }
 
 #ifdef NIMBLE_TWO
@@ -290,16 +705,29 @@ class NimbleBluetoothServerCallback : public NimBLEServerCallbacks
         bluetoothStatus->updateStatus(&newStatus);
 
         if (bluetoothPhoneAPI) {
-            std::lock_guard guard(bluetoothPhoneAPI->nimble_mutex);
             bluetoothPhoneAPI->close();
-            bluetoothPhoneAPI->numBytes = 0;
-            bluetoothPhoneAPI->queue_size = 0;
-            bluetoothPhoneAPI->hasChecked = false;
-            bluetoothPhoneAPI->phoneWants = false;
+
+            { // scope for fromPhoneMutex mutex
+                std::lock_guard guard(bluetoothPhoneAPI->fromPhoneMutex);
+                bluetoothPhoneAPI->fromPhoneQueueSize = 0;
+            }
+
+            bluetoothPhoneAPI->onReadCallbackIsWaitingForData = false;
+            { // scope for toPhoneMutex mutex
+                std::lock_guard guard(bluetoothPhoneAPI->toPhoneMutex);
+                bluetoothPhoneAPI->toPhoneQueueSize = 0;
+            }
+
+            bluetoothPhoneAPI->readCount = 0;
+            bluetoothPhoneAPI->notifyCount = 0;
+            bluetoothPhoneAPI->writeCount = 0;
         }
 
         // Clear the last ToRadio packet buffer to avoid rejecting first packet from new connection
         memset(lastToRadio, 0, sizeof(lastToRadio));
+
+        nimbleBluetoothConnHandle = -1; // -1 means "no connection"
+
 #ifdef NIMBLE_TWO
         // Restart Advertising
         ble->startAdvertising();
@@ -436,17 +864,15 @@ void NimbleBluetooth::setupService()
     if (config.bluetooth.mode == meshtastic_Config_BluetoothConfig_PairingMode_NO_PIN) {
         ToRadioCharacteristic = bleService->createCharacteristic(TORADIO_UUID, NIMBLE_PROPERTY::WRITE);
         // Allow notifications so phones can stream FromRadio without polling.
-        FromRadioCharacteristic =
-            bleService->createCharacteristic(FROMRADIO_UUID, NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::NOTIFY);
+        FromRadioCharacteristic = bleService->createCharacteristic(FROMRADIO_UUID, NIMBLE_PROPERTY::READ);
         fromNumCharacteristic = bleService->createCharacteristic(FROMNUM_UUID, NIMBLE_PROPERTY::NOTIFY | NIMBLE_PROPERTY::READ);
         logRadioCharacteristic =
             bleService->createCharacteristic(LOGRADIO_UUID, NIMBLE_PROPERTY::NOTIFY | NIMBLE_PROPERTY::READ, 512U);
     } else {
         ToRadioCharacteristic = bleService->createCharacteristic(
             TORADIO_UUID, NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::WRITE_AUTHEN | NIMBLE_PROPERTY::WRITE_ENC);
-        FromRadioCharacteristic =
-            bleService->createCharacteristic(FROMRADIO_UUID, NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::READ_AUTHEN |
-                                                                 NIMBLE_PROPERTY::READ_ENC | NIMBLE_PROPERTY::NOTIFY);
+        FromRadioCharacteristic = bleService->createCharacteristic(
+            FROMRADIO_UUID, NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::READ_AUTHEN | NIMBLE_PROPERTY::READ_ENC);
         fromNumCharacteristic =
             bleService->createCharacteristic(FROMNUM_UUID, NIMBLE_PROPERTY::NOTIFY | NIMBLE_PROPERTY::READ |
                                                                NIMBLE_PROPERTY::READ_AUTHEN | NIMBLE_PROPERTY::READ_ENC);

From ffb168be00a576de79adf115f1c4a6581431959c Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
 <41898282+github-actions[bot]@users.noreply.github.com>
Date: Sun, 19 Oct 2025 05:53:06 -0500
Subject: [PATCH 524/691] Update protobufs (#8398)

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 4a618380a..bf149bbdc 160000
--- a/protobufs
+++ b/protobufs
@@ -1 +1 @@
-Subproject commit 4a618380a0d80b476bb7f278008f811f456e0a5f
+Subproject commit bf149bbdcce45ba7cd8643db7cb25e5c8815072b
diff --git a/src/mesh/generated/meshtastic/mesh.pb.h b/src/mesh/generated/meshtastic/mesh.pb.h
index d8d2f2e8a..059af57ae 100644
--- a/src/mesh/generated/meshtastic/mesh.pb.h
+++ b/src/mesh/generated/meshtastic/mesh.pb.h
@@ -282,6 +282,8 @@ typedef enum _meshtastic_HardwareModel {
     meshtastic_HardwareModel_HELTEC_WIRELESS_TRACKER_V2 = 113,
     /* LilyGo T-Watch Ultra */
     meshtastic_HardwareModel_T_WATCH_ULTRA = 114,
+    /* Elecrow ThinkNode M3 */
+    meshtastic_HardwareModel_THINKNODE_M3 = 115,
     /* ------------------------------------------------------------------------------------------------------------------------------------------
  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 f6eede8597fd822be7cc949f0accce16c941f4c3 Mon Sep 17 00:00:00 2001
From: Mike Robbins 
Date: Sun, 19 Oct 2025 11:00:47 -0400
Subject: [PATCH 525/691] NimbleBluetooth: process fromPhoneQueue before
 toPhoneQueue (fixes bug with 0-length reads during config phase)

---
 src/nimble/NimbleBluetooth.cpp | 74 +++++++++++++---------------------
 1 file changed, 29 insertions(+), 45 deletions(-)

diff --git a/src/nimble/NimbleBluetooth.cpp b/src/nimble/NimbleBluetooth.cpp
index 9accf23c6..4c9590103 100644
--- a/src/nimble/NimbleBluetooth.cpp
+++ b/src/nimble/NimbleBluetooth.cpp
@@ -144,21 +144,28 @@ class BluetoothPhoneAPI : public PhoneAPI, public concurrency::OSThread
   protected:
     virtual int32_t runOnce() override
     {
-        bool shouldBreakAndRetryLater = false;
-
         while (runOnceHasWorkToDo()) {
-            // Important that we service onRead first, because the onRead callback blocks NimBLE until we clear
-            // onReadCallbackIsWaitingForData.
-            shouldBreakAndRetryLater = runOnceHandleToPhoneQueue(); // push data from getFromRadio to onRead
-            runOnceHandleFromPhoneQueue();                          // pull data from onWrite to handleToRadio
+            /*
+              PROCESS fromPhoneQueue BEFORE toPhoneQueue:
 
-            if (shouldBreakAndRetryLater) {
-                // onRead still wants data, but it's not available yet. Return so we can try again when a packet may be ready.
-#ifdef DEBUG_NIMBLE_ON_READ_TIMING
-                LOG_INFO("BLE runOnce breaking to retry later (leaving onRead waiting)");
-#endif
-                return 100; // try again in 100ms
-            }
+              In normal STATE_SEND_PACKETS operation, it's unlikely that we'll have both writes and reads to process at the same
+              time, because either onWrite or onRead will trigger this runOnce. And in STATE_SEND_PACKETS, it's generally ok to
+              service either the reads or writes first.
+
+              However, during the initial setup wantConfig packet, the clients send a write and immediately send a read, and they
+              expect the read will respond to the write. (This also happens when a client goes from STATE_SEND_PACKETS back to
+              another wantConfig, like the iOS client does when requesting the nodedb after requesting the main config only.)
+
+              So it's safest to always service writes (fromPhoneQueue) before reads (toPhoneQueue), so that any "synchronous"
+              write-then-read sequences from the client work as expected, even if this means we block onRead for a while: this is
+              what the client wants!
+            */
+
+            // PHONE -> RADIO:
+            runOnceHandleFromPhoneQueue(); // pull data from onWrite to handleToRadio
+
+            // RADIO -> PHONE:
+            runOnceHandleToPhoneQueue(); // push data from getFromRadio to onRead
         }
 
         // the run is triggered via NimbleBluetoothToRadioCallback and NimbleBluetoothFromRadioCallback
@@ -220,12 +227,8 @@ class BluetoothPhoneAPI : public PhoneAPI, public concurrency::OSThread
         }
     }
 
-    bool runOnceHandleToPhoneQueue()
+    void runOnceHandleToPhoneQueue()
     {
-        // Returns false normally.
-        // Returns true if we should break out of runOnce and retry later, such as setup states where getFromRadio returns 0
-        // bytes.
-
         // Stack buffer for getFromRadio packet
         uint8_t fromRadioBytes[meshtastic_FromRadio_size] = {0};
         size_t numBytes = 0;
@@ -234,28 +237,15 @@ class BluetoothPhoneAPI : public PhoneAPI, public concurrency::OSThread
             numBytes = getFromRadio(fromRadioBytes);
 
             if (numBytes == 0) {
-                // Client expected a read, but we have nothing to send.
-                // Returning a 0-byte packet breaks clients during the config phase, so we have to block onRead until there's a
-                // packet ready.
-                if (isSendingPackets()) {
-                    // In STATE_SEND_PACKETS, it is 100% OK to return a 0-byte response, as we expect clients to do read beyond
-                    // notifies regularly, to make sure they have nothing else to read.
-#ifdef DEBUG_NIMBLE_ON_READ_TIMING
-                    LOG_DEBUG("BLE getFromRadio returned numBytes=0, but in STATE_SEND_PACKETS, so clearing "
-                              "onReadCallbackIsWaitingForData flag");
-#endif
-                } else {
-                    // In other states, this breaks clients.
-                    // Return early, leaving onReadCallbackIsWaitingForData==true so onRead knows to try again.
-                    // This gives runOnce a chance to handleToRadio and produce a response.
-#ifdef DEBUG_NIMBLE_ON_READ_TIMING
-                    LOG_DEBUG("BLE getFromRadio returned numBytes=0. Blocking onRead until we have data");
-#endif
+                /*
+                  Client expected a read, but we have nothing to send.
 
-                    // Return true to tell runOnce to shouldBreakAndRetryLater, so we don't busy-loop in runOnce even though
-                    // onRead is still waiting!
-                    return true;
-                }
+                  In STATE_SEND_PACKETS, it is 100% OK to return a 0-byte response, as we expect clients to do read beyond
+                  notifies regularly, to make sure they have nothing else to read.
+
+                  In other states, this is fine **so long as we've already processed pending onWrites first**, because the client
+                  may requesting wantConfig and immediately doing a read.
+                */
             } else {
                 // Push to toPhoneQueue, protected by toPhoneMutex. Hold the mutex as briefly as possible.
                 if (toPhoneQueueSize < NIMBLE_BLUETOOTH_TO_PHONE_QUEUE_SIZE) {
@@ -282,8 +272,6 @@ class BluetoothPhoneAPI : public PhoneAPI, public concurrency::OSThread
             // Clear the onReadCallbackIsWaitingForData flag so onRead knows it can proceed.
             onReadCallbackIsWaitingForData = false; // only clear this flag AFTER the push
         }
-
-        return false;
     }
 
     bool runOnceHasWorkFromPhone() { return fromPhoneQueueSize > 0; }
@@ -466,10 +454,6 @@ class NimbleBluetoothFromRadioCallback : public NimBLECharacteristicCallbacks
     virtual void onRead(NimBLECharacteristic *pCharacteristic)
 #endif
     {
-        // In some cases, it seems a new connection starts with a read.
-        // The API has no bytes to send, leading to a timeout. This short-circuits this problem.
-        if (!bluetoothPhoneAPI->isConnected())
-            return;
         // CAUTION: This callback runs in the NimBLE task!!! Don't do anything except communicate with the main task's runOnce.
 
         int currentReadCount = bluetoothPhoneAPI->readCount.fetch_add(1);

From 126954c2edee391e174e55caab0b068384c17cd6 Mon Sep 17 00:00:00 2001
From: Mike Robbins 
Date: Sun, 19 Oct 2025 11:10:14 -0400
Subject: [PATCH 526/691] NimbleBluetooth: reuse BLE_HS_CONN_HANDLE_NONE
 instead of creating a different constant to represent no connection

---
 src/nimble/NimbleBluetooth.cpp | 16 ++++++++--------
 1 file changed, 8 insertions(+), 8 deletions(-)

diff --git a/src/nimble/NimbleBluetooth.cpp b/src/nimble/NimbleBluetooth.cpp
index 4c9590103..6238031f6 100644
--- a/src/nimble/NimbleBluetooth.cpp
+++ b/src/nimble/NimbleBluetooth.cpp
@@ -49,7 +49,7 @@ NimBLECharacteristic *logRadioCharacteristic;
 NimBLEServer *bleServer;
 
 static bool passkeyShowing;
-static std::atomic nimbleBluetoothConnHandle{-1}; // actual handles are uint16_t, so -1 means "no connection"
+static std::atomic nimbleBluetoothConnHandle{BLE_HS_CONN_HANDLE_NONE}; // BLE_HS_CONN_HANDLE_NONE means "no connection"
 
 class BluetoothPhoneAPI : public PhoneAPI, public concurrency::OSThread
 {
@@ -178,9 +178,9 @@ class BluetoothPhoneAPI : public PhoneAPI, public concurrency::OSThread
 
         // Prefer high throughput during config/setup, at the cost of high power consumption (for a few seconds)
         if (bleServer && isConnected()) {
-            int32_t conn_handle = nimbleBluetoothConnHandle.load();
-            if (conn_handle != -1) {
-                requestHighThroughputConnection(static_cast(conn_handle));
+            uint16_t conn_handle = nimbleBluetoothConnHandle.load();
+            if (conn_handle != BLE_HS_CONN_HANDLE_NONE) {
+                requestHighThroughputConnection(conn_handle);
             }
         }
     }
@@ -191,9 +191,9 @@ class BluetoothPhoneAPI : public PhoneAPI, public concurrency::OSThread
 
         // Switch to lower power consumption BLE connection params for steady-state use after config/setup is complete
         if (bleServer && isConnected()) {
-            int32_t conn_handle = nimbleBluetoothConnHandle.load();
-            if (conn_handle != -1) {
-                requestLowerPowerConnection(static_cast(conn_handle));
+            uint16_t conn_handle = nimbleBluetoothConnHandle.load();
+            if (conn_handle != BLE_HS_CONN_HANDLE_NONE) {
+                requestLowerPowerConnection(conn_handle);
             }
         }
     }
@@ -710,7 +710,7 @@ class NimbleBluetoothServerCallback : public NimBLEServerCallbacks
         // Clear the last ToRadio packet buffer to avoid rejecting first packet from new connection
         memset(lastToRadio, 0, sizeof(lastToRadio));
 
-        nimbleBluetoothConnHandle = -1; // -1 means "no connection"
+        nimbleBluetoothConnHandle = BLE_HS_CONN_HANDLE_NONE; // BLE_HS_CONN_HANDLE_NONE means "no connection"
 
 #ifdef NIMBLE_TWO
         // Restart Advertising

From 5b9563a357f5d6c535b866be1987619869ee786e Mon Sep 17 00:00:00 2001
From: HarukiToreda <116696711+HarukiToreda@users.noreply.github.com>
Date: Sun, 19 Oct 2025 15:11:06 -0400
Subject: [PATCH 527/691] Update
 src/graphics/niche/InkHUD/Applets/Bases/Map/MapApplet.cpp

makes sense, applying did not cause any visible issues.

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---
 src/graphics/niche/InkHUD/Applets/Bases/Map/MapApplet.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/graphics/niche/InkHUD/Applets/Bases/Map/MapApplet.cpp b/src/graphics/niche/InkHUD/Applets/Bases/Map/MapApplet.cpp
index aee51c6ad..0baca4f87 100644
--- a/src/graphics/niche/InkHUD/Applets/Bases/Map/MapApplet.cpp
+++ b/src/graphics/niche/InkHUD/Applets/Bases/Map/MapApplet.cpp
@@ -111,7 +111,7 @@ void InkHUD::MapApplet::onRender()
     printAt(vertBarX + (topLabelW / 2) + 1, topLabelY + (topLabelH / 2), vertTopLabel, CENTER, MIDDLE);
 
     char vertBottomLabel[32];
-    formatDistance(horizMeters, vertBottomLabel, sizeof(vertBottomLabel));
+    formatDistance(vertMeters, vertBottomLabel, sizeof(vertBottomLabel));
     int16_t bottomLabelY = vertBarBottom + 4;
     int16_t bottomLabelW = getTextWidth(vertBottomLabel);
     int16_t bottomLabelH = getFont().lineHeight();

From 2ad52812c0e182ea4d9389cafa8125c8467da4af Mon Sep 17 00:00:00 2001
From: HarukiToreda <116696711+HarukiToreda@users.noreply.github.com>
Date: Sun, 19 Oct 2025 15:12:03 -0400
Subject: [PATCH 528/691] Update
 src/graphics/niche/InkHUD/Applets/Bases/Map/MapApplet.cpp

better for clarity

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---
 src/graphics/niche/InkHUD/Applets/Bases/Map/MapApplet.cpp | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/src/graphics/niche/InkHUD/Applets/Bases/Map/MapApplet.cpp b/src/graphics/niche/InkHUD/Applets/Bases/Map/MapApplet.cpp
index 0baca4f87..396a8dfe7 100644
--- a/src/graphics/niche/InkHUD/Applets/Bases/Map/MapApplet.cpp
+++ b/src/graphics/niche/InkHUD/Applets/Bases/Map/MapApplet.cpp
@@ -26,7 +26,12 @@ void InkHUD::MapApplet::onRender()
 
         // Add white halo outline first
         constexpr int outlinePad = 1; // size of white outline padding
-        int boxSize = (m.hasHopsAway && m.hopsAway > config.lora.hop_limit) || !m.hasHopsAway ? 12 : 10;
+        int boxSize;
+        if ((m.hasHopsAway && m.hopsAway > config.lora.hop_limit) || !m.hasHopsAway) {
+            boxSize = 12;
+        } else {
+            boxSize = 10;
+        }
         fillRect(x - (boxSize / 2) - outlinePad, y - (boxSize / 2) - outlinePad, boxSize + (outlinePad * 2),
                  boxSize + (outlinePad * 2), WHITE);
 

From cb3ce1b1a869daef5cc773ea2a5bd09b8d201b1f Mon Sep 17 00:00:00 2001
From: HarukiToreda <116696711+HarukiToreda@users.noreply.github.com>
Date: Sun, 19 Oct 2025 16:25:53 -0400
Subject: [PATCH 529/691] proper centering and rounder hops labels

---
 .../InkHUD/Applets/Bases/Map/MapApplet.cpp    | 56 +++++++++++--------
 1 file changed, 34 insertions(+), 22 deletions(-)

diff --git a/src/graphics/niche/InkHUD/Applets/Bases/Map/MapApplet.cpp b/src/graphics/niche/InkHUD/Applets/Bases/Map/MapApplet.cpp
index 396a8dfe7..818c68070 100644
--- a/src/graphics/niche/InkHUD/Applets/Bases/Map/MapApplet.cpp
+++ b/src/graphics/niche/InkHUD/Applets/Bases/Map/MapApplet.cpp
@@ -13,6 +13,23 @@ void InkHUD::MapApplet::onRender()
         return;
     }
 
+    // Helper: draw rounded rectangle centered at x,y
+    auto fillRoundedRect = [&](int16_t cx, int16_t cy, int16_t w, int16_t h, int16_t r, uint16_t color) {
+        int16_t x = cx - (w / 2);
+        int16_t y = cy - (h / 2);
+
+        // center rects
+        fillRect(x + r, y, w - 2 * r, h, color);
+        fillRect(x, y + r, r, h - 2 * r, color);
+        fillRect(x + w - r, y + r, r, h - 2 * r, color);
+
+        // corners
+        fillCircle(x + r, y + r, r, color);
+        fillCircle(x + w - r - 1, y + r, r, color);
+        fillCircle(x + r, y + h - r - 1, r, color);
+        fillCircle(x + w - r - 1, y + h - r - 1, r, color);
+    };
+
     // Find center of map
     getMapCenter(&latCenter, &lngCenter);
     calculateAllMarkers();
@@ -25,37 +42,32 @@ void InkHUD::MapApplet::onRender()
         int16_t y = Y(0.5) - (m.northMeters * metersToPx);
 
         // Add white halo outline first
-        constexpr int outlinePad = 1; // size of white outline padding
-        int boxSize;
-        if ((m.hasHopsAway && m.hopsAway > config.lora.hop_limit) || !m.hasHopsAway) {
-            boxSize = 12;
-        } else {
-            boxSize = 10;
-        }
-        fillRect(x - (boxSize / 2) - outlinePad, y - (boxSize / 2) - outlinePad, boxSize + (outlinePad * 2),
-                 boxSize + (outlinePad * 2), WHITE);
+        constexpr int outlinePad = 1;
+        int boxSize = 11;
+        int radius = 2; // rounded corner radius
+
+        // White halo background
+        fillRoundedRect(x, y, boxSize + (outlinePad * 2), boxSize + (outlinePad * 2), radius + 1, WHITE);
+
+        // Draw inner box
+        fillRoundedRect(x, y, boxSize, boxSize, radius, BLACK);
+
+        // Text inside
+        setFont(fontSmall);
+        setTextColor(WHITE);
 
         // Draw actual marker on top
         if (m.hasHopsAway && m.hopsAway > config.lora.hop_limit) {
-            fillRect(x - boxSize / 2, y - boxSize / 2, boxSize, boxSize, BLACK);
-            setFont(fontSmall);
-            setTextColor(WHITE);
-            printAt(x, y, "X", CENTER, MIDDLE);
+            printAt(x + 1, y + 1, "X", CENTER, MIDDLE);
         } else if (!m.hasHopsAway) {
-            fillRect(x - boxSize / 2, y - boxSize / 2, boxSize, boxSize, BLACK);
-            setFont(fontSmall);
-            setTextColor(WHITE);
-            printAt(x, y, "?", CENTER, MIDDLE);
+            printAt(x + 1, y + 1, "?", CENTER, MIDDLE);
         } else {
-            fillRect(x - boxSize / 2, y - boxSize / 2, boxSize, boxSize, BLACK);
             char hopStr[4];
             snprintf(hopStr, sizeof(hopStr), "%d", m.hopsAway);
-            setFont(fontSmall);
-            setTextColor(WHITE);
-            printAt(x, y, hopStr, CENTER, MIDDLE);
+            printAt(x, y + 1, hopStr, CENTER, MIDDLE);
         }
 
-        // Restore default font and color (safety for rest of UI)
+        // Restore default font and color
         setFont(fontSmall);
         setTextColor(BLACK);
     }

From b5aa16badeb902c868ea1dc1a35b40c64425016d Mon Sep 17 00:00:00 2001
From: Chloe Bethel 
Date: Sun, 19 Oct 2025 22:23:12 +0100
Subject: [PATCH 530/691] Add a banner on startup when DEBUG_MUTE is enabled
 (#8402)

---
 src/main.cpp | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/src/main.cpp b/src/main.cpp
index 3801f6f9f..689e80e35 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -436,6 +436,12 @@ void setup()
 
     LOG_INFO("\n\n//\\ E S H T /\\ S T / C\n");
 
+#if defined(DEBUG_MUTE) && defined(DEBUG_PORT)
+    DEBUG_PORT.printf("\r\n\r\n//\\ E S H T /\\ S T / C\r\n");
+    DEBUG_PORT.printf("Version %s for %s from %s\r\n", optstr(APP_VERSION), optstr(APP_ENV), optstr(APP_REPO));
+    DEBUG_PORT.printf("Debug mute is enabled, there will be no serial output.\r\n");
+#endif
+
     initDeepSleep();
 
 #if defined(MODEM_POWER_EN)

From c4656dacf7f886da0770f8778c7f7f094553e7da Mon Sep 17 00:00:00 2001
From: Jason P 
Date: Sun, 19 Oct 2025 19:14:30 -0500
Subject: [PATCH 531/691] Remove "Phone GPS" in order to correct GPS reporting
 (#8407)

* Removing Phone GPS reporting for the moment
---
 src/graphics/draw/UIRenderer.cpp | 25 +++----------------------
 1 file changed, 3 insertions(+), 22 deletions(-)

diff --git a/src/graphics/draw/UIRenderer.cpp b/src/graphics/draw/UIRenderer.cpp
index ff8cd20c5..1ff183779 100644
--- a/src/graphics/draw/UIRenderer.cpp
+++ b/src/graphics/draw/UIRenderer.cpp
@@ -563,6 +563,7 @@ void UIRenderer::drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *sta
     display->setTextAlignment(TEXT_ALIGN_LEFT);
     display->setFont(FONT_SMALL);
     int line = 1;
+    meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
 
     // === Header ===
 #if defined(M5STACK_UNITC6L)
@@ -740,7 +741,6 @@ void UIRenderer::drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *sta
     int yOffset = (isHighResolution) ? 0 : 5;
     std::string longNameStr;
 
-    meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
     if (ourNode && ourNode->has_user && strlen(ourNode->user.long_name) > 0) {
         longNameStr = sanitizeString(ourNode->user.long_name);
     }
@@ -1000,24 +1000,7 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU
     const char *displayLine = ""; // Initialize to empty string by default
     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.gps_mode != meshtastic_Config_PositionConfig_GpsMode_ENABLED) {
         if (config.position.fixed_position) {
             displayLine = "Fixed GPS";
         } else {
@@ -1108,9 +1091,7 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU
 
         // === Final Row: Altitude ===
         char altitudeLine[32] = {0};
-        int32_t alt = (strcmp(displayLine, "Phone GPS") == 0 && ourNode && nodeDB->hasValidPosition(ourNode))
-                          ? ourNode->position.altitude
-                          : geoCoord.getAltitude();
+        int32_t alt = geoCoord.getAltitude();
         if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) {
             snprintf(altitudeLine, sizeof(altitudeLine), "Alt: %.0fft", alt * METERS_TO_FEET);
         } else {

From 15ee1c2819340da759d0dbb11ce302804004d707 Mon Sep 17 00:00:00 2001
From: Ford Jones <107664313+ford-jones@users.noreply.github.com>
Date: Wed, 22 Oct 2025 22:08:17 +1300
Subject: [PATCH 532/691] Include RSSI in rangetest csv (#8395)

* Include RSSI in rangetest csv

* Fix typo

* Preserve csv column order

---------

Co-authored-by: Ben Meadors 
---
 src/modules/RangeTestModule.cpp | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/src/modules/RangeTestModule.cpp b/src/modules/RangeTestModule.cpp
index 3d78d0dc9..026b3028d 100644
--- a/src/modules/RangeTestModule.cpp
+++ b/src/modules/RangeTestModule.cpp
@@ -159,6 +159,7 @@ ProcessMessage RangeTestModuleRadio::handleReceived(const meshtastic_MeshPacket
             LOG_DEBUG("---- Received Packet:");
             LOG_DEBUG("mp.from          %d", mp.from);
             LOG_DEBUG("mp.rx_snr        %f", mp.rx_snr);
+            LOG_DEBUG("mp.rx_rssi       %f", mp.rx_rssi);
             LOG_DEBUG("mp.hop_limit     %d", mp.hop_limit);
             LOG_DEBUG("---- Node Information of Received Packet (mp.from):");
             LOG_DEBUG("n->user.long_name         %s", n->user.long_name);
@@ -234,8 +235,8 @@ bool RangeTestModuleRadio::appendFile(const meshtastic_MeshPacket &mp)
         }
 
         // Print the CSV header
-        if (fileToWrite.println(
-                "time,from,sender name,sender lat,sender long,rx lat,rx long,rx elevation,rx snr,distance,hop limit,payload")) {
+        if (fileToWrite.println("time,from,sender name,sender lat,sender long,rx lat,rx long,rx elevation,rx "
+                                "snr,distance,hop limit,payload,rx rssi")) {
             LOG_INFO("File was written");
         } else {
             LOG_ERROR("File write failed");
@@ -297,6 +298,8 @@ bool RangeTestModuleRadio::appendFile(const meshtastic_MeshPacket &mp)
 
     // TODO: If quotes are found in the payload, it has to be escaped.
     fileToAppend.printf("\"%s\"\n", p.payload.bytes);
+    fileToAppend.printf("%i,", mp.rx_rssi); // RX RSSI
+
     fileToAppend.flush();
     fileToAppend.close();
 

From 18c4956aba11cc23d8d09ea3d7f756b85b22326f Mon Sep 17 00:00:00 2001
From: Onyx Clawe <58921814+OnyxClawe@users.noreply.github.com>
Date: Wed, 22 Oct 2025 03:02:14 -0700
Subject: [PATCH 533/691] Issue: #7944 External notification module: Adjusted
 default nag timeout to 15s (from 60s) (#7946)

* External notification module: Adjusted default nag timeout to 5s (from 60s)

* Change nag to 15s

---------

Co-authored-by: Tom Fifield 
---
 src/mesh/Default.h | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/mesh/Default.h b/src/mesh/Default.h
index 2f05da98d..d0d4678ff 100644
--- a/src/mesh/Default.h
+++ b/src/mesh/Default.h
@@ -27,7 +27,7 @@
 #ifdef USERPREFS_RINGTONE_NAG_SECS
 #define default_ringtone_nag_secs USERPREFS_RINGTONE_NAG_SECS
 #else
-#define default_ringtone_nag_secs 60
+#define default_ringtone_nag_secs 15
 #endif
 
 #define default_mqtt_address "mqtt.meshtastic.org"
@@ -84,4 +84,4 @@ class Default
             return 1.0 + (nodesOverForty * throttlingFactor); // Each number of online node scales by 0.075 (default)
         }
     }
-};
\ No newline at end of file
+};

From f4e93b4a2d52c636bc4d1313696be7f9765012d7 Mon Sep 17 00:00:00 2001
From: "Daniel.Cao" <144674500+DanielCao0@users.noreply.github.com>
Date: Thu, 23 Oct 2025 18:41:24 +0800
Subject: [PATCH 534/691] Add support for RAK WISMESH TAP V2 by enabling
 SDCARD_CS pin during deep sleep (#8429)

---
 src/sleep.cpp | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/src/sleep.cpp b/src/sleep.cpp
index c6efb0efd..756582c74 100644
--- a/src/sleep.cpp
+++ b/src/sleep.cpp
@@ -244,6 +244,10 @@ void doDeepSleep(uint32_t msecToWake, bool skipPreflight = false, bool skipSaveN
     // pinMode(PIN_POWER_EN1, INPUT_PULLDOWN);
 #endif
 
+#ifdef RAK_WISMESH_TAP_V2
+    digitalWrite(SDCARD_CS, LOW);
+#endif
+
 #ifdef TRACKER_T1000_E
 #ifdef GNSS_AIROHA
     digitalWrite(GPS_VRTC_EN, LOW);

From 07d354fa02e7d9843fac8d22d4db9027165a0044 Mon Sep 17 00:00:00 2001
From: GUVWAF <78759985+GUVWAF@users.noreply.github.com>
Date: Thu, 23 Oct 2025 12:42:36 +0200
Subject: [PATCH 535/691] Move airtime calculation to when Tx is complete
 (#8427)

---
 src/mesh/RadioLibInterface.cpp | 11 +++++------
 1 file changed, 5 insertions(+), 6 deletions(-)

diff --git a/src/mesh/RadioLibInterface.cpp b/src/mesh/RadioLibInterface.cpp
index 2567d9e7f..80e51b8bc 100644
--- a/src/mesh/RadioLibInterface.cpp
+++ b/src/mesh/RadioLibInterface.cpp
@@ -289,12 +289,7 @@ void RadioLibInterface::onNotify(uint32_t notification)
                         // actual transmission as short as possible
                         txp = txQueue.dequeue();
                         assert(txp);
-                        bool sent = startSend(txp);
-                        if (sent) {
-                            // Packet has been sent, count it toward our TX airtime utilization.
-                            uint32_t xmitMsec = getPacketTime(txp);
-                            airTime->logAirtime(TX_LOG, xmitMsec);
-                        }
+                        startSend(txp);
                         LOG_DEBUG("%d packets remain in the TX queue", txQueue.getMaxLen() - txQueue.getFree());
                     }
                 }
@@ -413,6 +408,10 @@ void RadioLibInterface::completeSending()
     sendingPacket = NULL;
 
     if (p) {
+        // Packet has been sent, count it toward our TX airtime utilization.
+        uint32_t xmitMsec = getPacketTime(p);
+        airTime->logAirtime(TX_LOG, xmitMsec);
+
         txGood++;
         if (!isFromUs(p))
             txRelay++;

From 153cf65214e764481024d24c5e4df92772be2379 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
 <41898282+github-actions[bot]@users.noreply.github.com>
Date: Thu, 23 Oct 2025 06:14:14 -0500
Subject: [PATCH 536/691] Upgrade trunk (#8369)

Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com>
---
 .trunk/trunk.yaml | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml
index 5bec39ae2..0a43c3079 100644
--- a/.trunk/trunk.yaml
+++ b/.trunk/trunk.yaml
@@ -8,15 +8,15 @@ plugins:
       uri: https://github.com/trunk-io/plugins
 lint:
   enabled:
-    - checkov@3.2.483
-    - renovate@41.148.2
+    - checkov@3.2.486
+    - renovate@41.157.0
     - prettier@3.6.2
-    - trufflehog@3.90.8
+    - trufflehog@3.90.11
     - yamllint@1.37.1
     - bandit@1.8.6
     - trivy@0.67.2
     - taplo@0.10.0
-    - ruff@0.14.0
+    - ruff@0.14.1
     - isort@7.0.0
     - markdownlint@0.45.0
     - oxipng@9.1.5

From 39780656ef58b9d928902c4dcc6c60e866b74a3e Mon Sep 17 00:00:00 2001
From: korbinianbauer <64415847+korbinianbauer@users.noreply.github.com>
Date: Thu, 23 Oct 2025 16:15:12 +0200
Subject: [PATCH 537/691] Don't assign negative SNR to unsigned int type

SNR-based contention windows are broken on systems with 64-bit long integers.
Fixes #8430
---
 src/mesh/RadioInterface.cpp | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/mesh/RadioInterface.cpp b/src/mesh/RadioInterface.cpp
index 31ec5acc5..3c0da4494 100644
--- a/src/mesh/RadioInterface.cpp
+++ b/src/mesh/RadioInterface.cpp
@@ -272,10 +272,10 @@ uint32_t RadioInterface::getTxDelayMsec()
 uint8_t RadioInterface::getCWsize(float snr)
 {
     // The minimum value for a LoRa SNR
-    const uint32_t SNR_MIN = -20;
+    const int32_t SNR_MIN = -20;
 
     // The maximum value for a LoRa SNR
-    const uint32_t SNR_MAX = 10;
+    const int32_t SNR_MAX = 10;
 
     return map(snr, SNR_MIN, SNR_MAX, CWmin, CWmax);
 }

From b682ab396744af7be9deb34563ccb0573a934f38 Mon Sep 17 00:00:00 2001
From: Jason P 
Date: Thu, 23 Oct 2025 11:54:05 -0500
Subject: [PATCH 538/691] Allow vibra or buzzer only notifications to obey
 cutoff (#8342)

* Allow vibra or buzzer only notifications to obey cutoff

* Update src/modules/ExternalNotificationModule.cpp

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Ben Meadors 
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---
 src/modules/ExternalNotificationModule.cpp | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/src/modules/ExternalNotificationModule.cpp b/src/modules/ExternalNotificationModule.cpp
index ffc789275..2b1730e9c 100644
--- a/src/modules/ExternalNotificationModule.cpp
+++ b/src/modules/ExternalNotificationModule.cpp
@@ -94,8 +94,8 @@ int32_t ExternalNotificationModule::runOnce()
         // audioThread->isPlaying() also handles actually playing the RTTTL, needs to be called in loop
         isRtttlPlaying = isRtttlPlaying || audioThread->isPlaying();
 #endif
-        if ((nagCycleCutoff < millis()) && !isRtttlPlaying) {
-            // let the song finish if we reach timeout
+        if ((nagCycleCutoff <= millis())) {
+            // Turn off external notification immediately when timeout is reached, regardless of song state
             nagCycleCutoff = UINT32_MAX;
             LOG_INFO("Turning off external notification: ");
             for (int i = 0; i < 3; i++) {
@@ -103,7 +103,6 @@ int32_t ExternalNotificationModule::runOnce()
                 externalTurnedOn[i] = 0;
                 LOG_INFO("%d ", i);
             }
-            LOG_INFO("");
 #ifdef HAS_I2S
             // GPIO0 is used as mclk for I2S audio and set to OUTPUT by the sound library
             // T-Deck uses GPIO0 as trackball button, so restore the mode

From 35fa4187390e73cc0b74f54e63fcc8454cd1892f Mon Sep 17 00:00:00 2001
From: HarukiToreda <116696711+HarukiToreda@users.noreply.github.com>
Date: Thu, 23 Oct 2025 12:55:24 -0400
Subject: [PATCH 539/691] InkHUD crash fix when nodes get deleted from NodeDB
 (#8428)

* InkHUD crash fix

* trunk fix
---
 .../InkHUD/Applets/Bases/NodeList/NodeListApplet.cpp | 12 +++++++++++-
 1 file changed, 11 insertions(+), 1 deletion(-)

diff --git a/src/graphics/niche/InkHUD/Applets/Bases/NodeList/NodeListApplet.cpp b/src/graphics/niche/InkHUD/Applets/Bases/NodeList/NodeListApplet.cpp
index 1b0bfa9d0..5c9906fba 100644
--- a/src/graphics/niche/InkHUD/Applets/Bases/NodeList/NodeListApplet.cpp
+++ b/src/graphics/niche/InkHUD/Applets/Bases/NodeList/NodeListApplet.cpp
@@ -127,6 +127,11 @@ void InkHUD::NodeListApplet::onRender()
     // Y value (top) of the current card. Increases as we draw.
     uint16_t cardTopY = headerDivY + padDivH;
 
+    // Clean up deleted nodes before drawing
+    cards.erase(
+        std::remove_if(cards.begin(), cards.end(), [](const CardInfo &c) { return nodeDB->getMeshNode(c.nodeNum) == nullptr; }),
+        cards.end());
+
     // -- Each node in list --
     for (auto card = cards.begin(); card != cards.end(); ++card) {
 
@@ -141,6 +146,11 @@ void InkHUD::NodeListApplet::onRender()
 
         meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(nodeNum);
 
+        // Skip deleted nodes
+        if (!node) {
+            continue;
+        }
+
         // -- Shortname --
         // Parse special chars in the short name
         // Use "?" if unknown
@@ -188,7 +198,7 @@ void InkHUD::NodeListApplet::onRender()
             drawSignalIndicator(signalX, signalY, signalW, signalH, signal);
         }
         // Otherwise, print "hops away" info, if available
-        else if (hopsAway != CardInfo::HOPS_UNKNOWN) {
+        else if (hopsAway != CardInfo::HOPS_UNKNOWN && node) {
             std::string hopString = to_string(node->hops_away);
             hopString += " Hop";
             if (node->hops_away != 1)

From 799cf0e8b3fbb90d87f2c7f7c13303a2f38fff95 Mon Sep 17 00:00:00 2001
From: Tom Fifield 
Date: Fri, 24 Oct 2025 10:37:38 +1100
Subject: [PATCH 540/691] Master --> develop (#8436)

* Issue: #7944 External notification module: Adjusted default nag timeout to 15s (from 60s) (#7946)

* External notification module: Adjusted default nag timeout to 5s (from 60s)

* Change nag to 15s

---------

Co-authored-by: Tom Fifield 

* Add support for RAK WISMESH TAP V2 by enabling SDCARD_CS pin during deep sleep (#8429)

* Upgrade trunk (#8369)

Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com>

* Don't assign negative SNR to unsigned int type

SNR-based contention windows are broken on systems with 64-bit long integers.
Fixes #8430

* Allow vibra or buzzer only notifications to obey cutoff (#8342)

* Allow vibra or buzzer only notifications to obey cutoff

* Update src/modules/ExternalNotificationModule.cpp

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Ben Meadors 
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* InkHUD crash fix when nodes get deleted from NodeDB (#8428)

* InkHUD crash fix

* trunk fix

---------

Co-authored-by: Ben Meadors 
Co-authored-by: Onyx Clawe <58921814+OnyxClawe@users.noreply.github.com>
Co-authored-by: Daniel.Cao <144674500+DanielCao0@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com>
Co-authored-by: korbinianbauer <64415847+korbinianbauer@users.noreply.github.com>
Co-authored-by: Jason P 
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: HarukiToreda <116696711+HarukiToreda@users.noreply.github.com>
---
 .trunk/trunk.yaml                                    |  8 ++++----
 .../InkHUD/Applets/Bases/NodeList/NodeListApplet.cpp | 12 +++++++++++-
 src/mesh/Default.h                                   |  4 ++--
 src/mesh/RadioInterface.cpp                          |  4 ++--
 src/modules/ExternalNotificationModule.cpp           |  5 ++---
 src/sleep.cpp                                        |  4 ++++
 6 files changed, 25 insertions(+), 12 deletions(-)

diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml
index 5bec39ae2..0a43c3079 100644
--- a/.trunk/trunk.yaml
+++ b/.trunk/trunk.yaml
@@ -8,15 +8,15 @@ plugins:
       uri: https://github.com/trunk-io/plugins
 lint:
   enabled:
-    - checkov@3.2.483
-    - renovate@41.148.2
+    - checkov@3.2.486
+    - renovate@41.157.0
     - prettier@3.6.2
-    - trufflehog@3.90.8
+    - trufflehog@3.90.11
     - yamllint@1.37.1
     - bandit@1.8.6
     - trivy@0.67.2
     - taplo@0.10.0
-    - ruff@0.14.0
+    - ruff@0.14.1
     - isort@7.0.0
     - markdownlint@0.45.0
     - oxipng@9.1.5
diff --git a/src/graphics/niche/InkHUD/Applets/Bases/NodeList/NodeListApplet.cpp b/src/graphics/niche/InkHUD/Applets/Bases/NodeList/NodeListApplet.cpp
index 1b0bfa9d0..5c9906fba 100644
--- a/src/graphics/niche/InkHUD/Applets/Bases/NodeList/NodeListApplet.cpp
+++ b/src/graphics/niche/InkHUD/Applets/Bases/NodeList/NodeListApplet.cpp
@@ -127,6 +127,11 @@ void InkHUD::NodeListApplet::onRender()
     // Y value (top) of the current card. Increases as we draw.
     uint16_t cardTopY = headerDivY + padDivH;
 
+    // Clean up deleted nodes before drawing
+    cards.erase(
+        std::remove_if(cards.begin(), cards.end(), [](const CardInfo &c) { return nodeDB->getMeshNode(c.nodeNum) == nullptr; }),
+        cards.end());
+
     // -- Each node in list --
     for (auto card = cards.begin(); card != cards.end(); ++card) {
 
@@ -141,6 +146,11 @@ void InkHUD::NodeListApplet::onRender()
 
         meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(nodeNum);
 
+        // Skip deleted nodes
+        if (!node) {
+            continue;
+        }
+
         // -- Shortname --
         // Parse special chars in the short name
         // Use "?" if unknown
@@ -188,7 +198,7 @@ void InkHUD::NodeListApplet::onRender()
             drawSignalIndicator(signalX, signalY, signalW, signalH, signal);
         }
         // Otherwise, print "hops away" info, if available
-        else if (hopsAway != CardInfo::HOPS_UNKNOWN) {
+        else if (hopsAway != CardInfo::HOPS_UNKNOWN && node) {
             std::string hopString = to_string(node->hops_away);
             hopString += " Hop";
             if (node->hops_away != 1)
diff --git a/src/mesh/Default.h b/src/mesh/Default.h
index 2f05da98d..d0d4678ff 100644
--- a/src/mesh/Default.h
+++ b/src/mesh/Default.h
@@ -27,7 +27,7 @@
 #ifdef USERPREFS_RINGTONE_NAG_SECS
 #define default_ringtone_nag_secs USERPREFS_RINGTONE_NAG_SECS
 #else
-#define default_ringtone_nag_secs 60
+#define default_ringtone_nag_secs 15
 #endif
 
 #define default_mqtt_address "mqtt.meshtastic.org"
@@ -84,4 +84,4 @@ class Default
             return 1.0 + (nodesOverForty * throttlingFactor); // Each number of online node scales by 0.075 (default)
         }
     }
-};
\ No newline at end of file
+};
diff --git a/src/mesh/RadioInterface.cpp b/src/mesh/RadioInterface.cpp
index 31ec5acc5..3c0da4494 100644
--- a/src/mesh/RadioInterface.cpp
+++ b/src/mesh/RadioInterface.cpp
@@ -272,10 +272,10 @@ uint32_t RadioInterface::getTxDelayMsec()
 uint8_t RadioInterface::getCWsize(float snr)
 {
     // The minimum value for a LoRa SNR
-    const uint32_t SNR_MIN = -20;
+    const int32_t SNR_MIN = -20;
 
     // The maximum value for a LoRa SNR
-    const uint32_t SNR_MAX = 10;
+    const int32_t SNR_MAX = 10;
 
     return map(snr, SNR_MIN, SNR_MAX, CWmin, CWmax);
 }
diff --git a/src/modules/ExternalNotificationModule.cpp b/src/modules/ExternalNotificationModule.cpp
index ffc789275..2b1730e9c 100644
--- a/src/modules/ExternalNotificationModule.cpp
+++ b/src/modules/ExternalNotificationModule.cpp
@@ -94,8 +94,8 @@ int32_t ExternalNotificationModule::runOnce()
         // audioThread->isPlaying() also handles actually playing the RTTTL, needs to be called in loop
         isRtttlPlaying = isRtttlPlaying || audioThread->isPlaying();
 #endif
-        if ((nagCycleCutoff < millis()) && !isRtttlPlaying) {
-            // let the song finish if we reach timeout
+        if ((nagCycleCutoff <= millis())) {
+            // Turn off external notification immediately when timeout is reached, regardless of song state
             nagCycleCutoff = UINT32_MAX;
             LOG_INFO("Turning off external notification: ");
             for (int i = 0; i < 3; i++) {
@@ -103,7 +103,6 @@ int32_t ExternalNotificationModule::runOnce()
                 externalTurnedOn[i] = 0;
                 LOG_INFO("%d ", i);
             }
-            LOG_INFO("");
 #ifdef HAS_I2S
             // GPIO0 is used as mclk for I2S audio and set to OUTPUT by the sound library
             // T-Deck uses GPIO0 as trackball button, so restore the mode
diff --git a/src/sleep.cpp b/src/sleep.cpp
index c6efb0efd..756582c74 100644
--- a/src/sleep.cpp
+++ b/src/sleep.cpp
@@ -244,6 +244,10 @@ void doDeepSleep(uint32_t msecToWake, bool skipPreflight = false, bool skipSaveN
     // pinMode(PIN_POWER_EN1, INPUT_PULLDOWN);
 #endif
 
+#ifdef RAK_WISMESH_TAP_V2
+    digitalWrite(SDCARD_CS, LOW);
+#endif
+
 #ifdef TRACKER_T1000_E
 #ifdef GNSS_AIROHA
     digitalWrite(GPS_VRTC_EN, LOW);

From 664d17c519f1b1849a467e4c84367de6b0f8bf9e Mon Sep 17 00:00:00 2001
From: GUVWAF <78759985+GUVWAF@users.noreply.github.com>
Date: Sat, 25 Oct 2025 13:59:01 +0200
Subject: [PATCH 541/691] Revert "Revert "develop --> Master" (#8244)" (#8450)

This reverts commit 5bcc47dddb6aa339e51651a05ff7f2bdc6e27bd2.
---
 src/Power.cpp                    |   7 ++-
 src/gps/GPS.cpp                  |   8 ++-
 src/mesh/FloodingRouter.cpp      | 102 ++++++++++++------------------
 src/mesh/FloodingRouter.h        |  15 +++--
 src/mesh/NextHopRouter.cpp       | 105 +++++++++++++++----------------
 src/mesh/NextHopRouter.h         |   6 +-
 src/mesh/PacketHistory.cpp       |   1 -
 src/mesh/http/ContentHandler.cpp |   5 ++
 src/mesh/http/WebServer.cpp      |  35 ++++++++++-
 src/mesh/http/WebServer.h        |   4 ++
 src/modules/TraceRouteModule.cpp |  64 +++++++++++++++++++
 src/modules/TraceRouteModule.h   |   6 ++
 src/power.h                      |   1 +
 13 files changed, 229 insertions(+), 130 deletions(-)

diff --git a/src/Power.cpp b/src/Power.cpp
index 1f4c341f0..d7fd5b33b 100644
--- a/src/Power.cpp
+++ b/src/Power.cpp
@@ -839,8 +839,11 @@ void Power::readPowerStatus()
 
     // Notify any status instances that are observing us
     const PowerStatus powerStatus2 = PowerStatus(hasBattery, usbPowered, isChargingNow, batteryVoltageMv, batteryChargePercent);
-    LOG_DEBUG("Battery: usbPower=%d, isCharging=%d, batMv=%d, batPct=%d", powerStatus2.getHasUSB(), powerStatus2.getIsCharging(),
-              powerStatus2.getBatteryVoltageMv(), powerStatus2.getBatteryChargePercent());
+    if (millis() > lastLogTime + 50 * 1000) {
+        LOG_DEBUG("Battery: usbPower=%d, isCharging=%d, batMv=%d, batPct=%d", powerStatus2.getHasUSB(),
+                  powerStatus2.getIsCharging(), powerStatus2.getBatteryVoltageMv(), powerStatus2.getBatteryChargePercent());
+        lastLogTime = millis();
+    }
     newStatus.notifyObservers(&powerStatus2);
 #ifdef DEBUG_HEAP
     if (lastheap != memGet.getFreeHeap()) {
diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp
index 297ed3dfa..6c6cdba6d 100644
--- a/src/gps/GPS.cpp
+++ b/src/gps/GPS.cpp
@@ -1659,8 +1659,12 @@ bool GPS::lookForLocation()
 
 #ifndef TINYGPS_OPTION_NO_STATISTICS
     if (reader.failedChecksum() > lastChecksumFailCount) {
-        LOG_WARN("%u new GPS checksum failures, for a total of %u", reader.failedChecksum() - lastChecksumFailCount,
-                 reader.failedChecksum());
+// In a GPS_DEBUG build we want to log all of these. In production, we only care if there are many of them.
+#ifndef GPS_DEBUG
+        if (reader.failedChecksum() > 4)
+#endif
+            LOG_WARN("%u new GPS checksum failures, for a total of %u", reader.failedChecksum() - lastChecksumFailCount,
+                     reader.failedChecksum());
         lastChecksumFailCount = reader.failedChecksum();
     }
 #endif
diff --git a/src/mesh/FloodingRouter.cpp b/src/mesh/FloodingRouter.cpp
index 1d8ac247f..032be241b 100644
--- a/src/mesh/FloodingRouter.cpp
+++ b/src/mesh/FloodingRouter.cpp
@@ -31,33 +31,8 @@ bool FloodingRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
         wasSeenRecently(p, true, nullptr, nullptr, &wasUpgraded); // Updates history; returns false when an upgrade is detected
 
     // Handle hop_limit upgrade scenario for rebroadcasters
-    // isRebroadcaster() is duplicated in perhapsRebroadcast(), but this avoids confusing log messages
-    if (wasUpgraded && isRebroadcaster() && iface && p->hop_limit > 0) {
-        // wasSeenRecently() reports false in upgrade cases so we handle replacement before the duplicate short-circuit
-        // If we overhear a duplicate copy of the packet with more hops left than the one we are waiting to
-        // rebroadcast, then remove the packet currently sitting in the TX queue and use this one instead.
-        uint8_t dropThreshold = p->hop_limit; // remove queued packets that have fewer hops remaining
-        if (iface->removePendingTXPacket(getFrom(p), p->id, dropThreshold)) {
-            LOG_DEBUG("Processing upgraded packet 0x%08x for rebroadcast with hop limit %d (dropping queued < %d)", p->id,
-                      p->hop_limit, dropThreshold);
-
-            if (nodeDB)
-                nodeDB->updateFrom(*p);
-#if !MESHTASTIC_EXCLUDE_TRACEROUTE
-            if (traceRouteModule && p->which_payload_variant == meshtastic_MeshPacket_decoded_tag &&
-                p->decoded.portnum == meshtastic_PortNum_TRACEROUTE_APP)
-                traceRouteModule->processUpgradedPacket(*p);
-#endif
-
-            perhapsRebroadcast(p);
-
-            // We already enqueued the improved copy, so make sure the incoming packet stops here.
-            return true;
-        }
-
-        // No queue entry was replaced by this upgraded copy, so treat it as a duplicate to avoid
-        // delivering the same packet to applications/phone twice with different hop limits.
-        seenRecently = true;
+    if (wasUpgraded && perhapsHandleUpgradedPacket(p)) {
+        return true; // we handled it, so stop processing
     }
 
     if (seenRecently) {
@@ -70,8 +45,10 @@ bool FloodingRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
         if (isRepeated) {
             LOG_DEBUG("Repeated reliable tx");
             // Check if it's still in the Tx queue, if not, we have to relay it again
-            if (!findInTxQueue(p->from, p->id))
+            if (!findInTxQueue(p->from, p->id)) {
+                reprocessPacket(p);
                 perhapsRebroadcast(p);
+            }
         } else {
             perhapsCancelDupe(p);
         }
@@ -82,6 +59,40 @@ bool FloodingRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
     return Router::shouldFilterReceived(p);
 }
 
+bool FloodingRouter::perhapsHandleUpgradedPacket(const meshtastic_MeshPacket *p)
+{
+    // isRebroadcaster() is duplicated in perhapsRebroadcast(), but this avoids confusing log messages
+    if (isRebroadcaster() && iface && p->hop_limit > 0) {
+        // If we overhear a duplicate copy of the packet with more hops left than the one we are waiting to
+        // rebroadcast, then remove the packet currently sitting in the TX queue and use this one instead.
+        uint8_t dropThreshold = p->hop_limit; // remove queued packets that have fewer hops remaining
+        if (iface->removePendingTXPacket(getFrom(p), p->id, dropThreshold)) {
+            LOG_DEBUG("Processing upgraded packet 0x%08x for rebroadcast with hop limit %d (dropping queued < %d)", p->id,
+                      p->hop_limit, dropThreshold);
+
+            reprocessPacket(p);
+            perhapsRebroadcast(p);
+
+            rxDupe++;
+            // We already enqueued the improved copy, so make sure the incoming packet stops here.
+            return true;
+        }
+    }
+
+    return false;
+}
+
+void FloodingRouter::reprocessPacket(const meshtastic_MeshPacket *p)
+{
+    if (nodeDB)
+        nodeDB->updateFrom(*p);
+#if !MESHTASTIC_EXCLUDE_TRACEROUTE
+    if (traceRouteModule && p->which_payload_variant == meshtastic_MeshPacket_decoded_tag &&
+        p->decoded.portnum == meshtastic_PortNum_TRACEROUTE_APP)
+        traceRouteModule->processUpgradedPacket(*p);
+#endif
+}
+
 bool FloodingRouter::roleAllowsCancelingDupe(const meshtastic_MeshPacket *p)
 {
     if (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER ||
@@ -121,41 +132,6 @@ bool FloodingRouter::isRebroadcaster()
            config.device.rebroadcast_mode != meshtastic_Config_DeviceConfig_RebroadcastMode_NONE;
 }
 
-void FloodingRouter::perhapsRebroadcast(const meshtastic_MeshPacket *p)
-{
-    if (!isToUs(p) && (p->hop_limit > 0) && !isFromUs(p)) {
-        if (p->id != 0) {
-            if (isRebroadcaster()) {
-                meshtastic_MeshPacket *tosend = packetPool.allocCopy(*p); // keep a copy because we will be sending it
-
-                // Use shared logic to determine if hop_limit should be decremented
-                if (shouldDecrementHopLimit(p)) {
-                    tosend->hop_limit--; // bump down the hop count
-                } else {
-                    LOG_INFO("favorite-ROUTER/CLIENT_BASE-to-ROUTER/CLIENT_BASE flood: preserving hop_limit");
-                }
-#if USERPREFS_EVENT_MODE
-                if (tosend->hop_limit > 2) {
-                    // if we are "correcting" the hop_limit, "correct" the hop_start by the same amount to preserve hops away.
-                    tosend->hop_start -= (tosend->hop_limit - 2);
-                    tosend->hop_limit = 2;
-                }
-#endif
-
-                tosend->next_hop = NO_NEXT_HOP_PREFERENCE; // this should already be the case, but just in case
-
-                LOG_INFO("Rebroadcast received floodmsg");
-                // Note: we are careful to resend using the original senders node id
-                send(tosend);
-            } else {
-                LOG_DEBUG("No rebroadcast: Role = CLIENT_MUTE or Rebroadcast Mode = NONE");
-            }
-        } else {
-            LOG_DEBUG("Ignore 0 id broadcast");
-        }
-    }
-}
-
 void FloodingRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtastic_Routing *c)
 {
     bool isAckorReply = (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) &&
diff --git a/src/mesh/FloodingRouter.h b/src/mesh/FloodingRouter.h
index eaf71d294..e8a2e9685 100644
--- a/src/mesh/FloodingRouter.h
+++ b/src/mesh/FloodingRouter.h
@@ -27,10 +27,6 @@
  */
 class FloodingRouter : public Router
 {
-  private:
-    /* Check if we should rebroadcast this packet, and do so if needed */
-    void perhapsRebroadcast(const meshtastic_MeshPacket *p);
-
   public:
     /**
      * Constructor
@@ -59,6 +55,17 @@ class FloodingRouter : public Router
      */
     virtual void sniffReceived(const meshtastic_MeshPacket *p, const meshtastic_Routing *c) override;
 
+    /* Check if we should rebroadcast this packet, and do so if needed */
+    virtual bool perhapsRebroadcast(const meshtastic_MeshPacket *p) = 0;
+
+    /* Check if we should handle an upgraded packet (with higher hop_limit)
+     * @return true if we handled it (so stop processing)
+     */
+    bool perhapsHandleUpgradedPacket(const meshtastic_MeshPacket *p);
+
+    /* Call when we receive a packet that needs some reprocessing, but afterwards should be filtered */
+    void reprocessPacket(const meshtastic_MeshPacket *p);
+
     // Return false for roles like ROUTER which should always rebroadcast even when we've heard another rebroadcast of
     // the same packet
     bool roleAllowsCancelingDupe(const meshtastic_MeshPacket *p);
diff --git a/src/mesh/NextHopRouter.cpp b/src/mesh/NextHopRouter.cpp
index 0461d7eb6..afdb4d096 100644
--- a/src/mesh/NextHopRouter.cpp
+++ b/src/mesh/NextHopRouter.cpp
@@ -43,31 +43,8 @@ bool NextHopRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
                                         &wasUpgraded); // Updates history; returns false when an upgrade is detected
 
     // Handle hop_limit upgrade scenario for rebroadcasters
-    // isRebroadcaster() is duplicated in perhapsRelay(), but this avoids confusing log messages
-    if (wasUpgraded && isRebroadcaster() && iface && p->hop_limit > 0) {
-        // Upgrade detection bypasses the duplicate short-circuit so we replace the queued packet before exiting
-        uint8_t dropThreshold = p->hop_limit; // remove queued packets that have fewer hops remaining
-        if (iface->removePendingTXPacket(getFrom(p), p->id, dropThreshold)) {
-            LOG_DEBUG("Processing upgraded packet 0x%08x for relay with hop limit %d (dropping queued < %d)", p->id, p->hop_limit,
-                      dropThreshold);
-
-            if (nodeDB)
-                nodeDB->updateFrom(*p);
-#if !MESHTASTIC_EXCLUDE_TRACEROUTE
-            if (traceRouteModule && p->which_payload_variant == meshtastic_MeshPacket_decoded_tag &&
-                p->decoded.portnum == meshtastic_PortNum_TRACEROUTE_APP)
-                traceRouteModule->processUpgradedPacket(*p);
-#endif
-
-            perhapsRelay(p);
-
-            // We already enqueued the improved copy, so make sure the incoming packet stops here.
-            return true;
-        }
-
-        // No queue entry was replaced by this upgraded copy, so treat it as a duplicate to avoid
-        // delivering the same packet to applications/phone twice with different hop limits.
-        seenRecently = true;
+    if (wasUpgraded && perhapsHandleUpgradedPacket(p)) {
+        return true; // we handled it, so stop processing
     }
 
     if (seenRecently) {
@@ -82,14 +59,20 @@ bool NextHopRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
         if (wasFallback) {
             LOG_INFO("Fallback to flooding from relay_node=0x%x", p->relay_node);
             // Check if it's still in the Tx queue, if not, we have to relay it again
-            if (!findInTxQueue(p->from, p->id))
-                perhapsRelay(p);
+            if (!findInTxQueue(p->from, p->id)) {
+                reprocessPacket(p);
+                perhapsRebroadcast(p);
+            }
         } else {
             bool isRepeated = p->hop_start > 0 && p->hop_start == p->hop_limit;
             // If repeated and not in Tx queue anymore, try relaying again, or if we are the destination, send the ACK again
             if (isRepeated) {
-                if (!findInTxQueue(p->from, p->id) && !perhapsRelay(p) && isToUs(p) && p->want_ack)
-                    sendAckNak(meshtastic_Routing_Error_NONE, getFrom(p), p->id, p->channel, 0);
+                if (!findInTxQueue(p->from, p->id)) {
+                    reprocessPacket(p);
+                    if (!perhapsRebroadcast(p) && isToUs(p) && p->want_ack) {
+                        sendAckNak(meshtastic_Routing_Error_NONE, getFrom(p), p->id, p->channel, 0);
+                    }
+                }
             } else if (!weWereNextHop) {
                 perhapsCancelDupe(p); // If it's a dupe, cancel relay if we were not explicitly asked to relay
             }
@@ -107,13 +90,14 @@ void NextHopRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtast
     bool isAckorReply = (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) &&
                         (p->decoded.request_id != 0 || p->decoded.reply_id != 0);
     if (isAckorReply) {
-        // Update next-hop for the original transmitter of this successful transmission to the relay node, but ONLY if "from" is
-        // not 0 (means implicit ACK) and original packet was also relayed by this node, or we sent it directly to the destination
+        // Update next-hop for the original transmitter of this successful transmission to the relay node, but ONLY if "from"
+        // is not 0 (means implicit ACK) and original packet was also relayed by this node, or we sent it directly to the
+        // destination
         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 *only* 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
                 bool wasAlreadyRelayer = wasRelayer(p->relay_node, p->decoded.request_id, p->to);
                 bool weWereSoleRelayer = false;
                 bool weWereRelayer = wasRelayer(ourRelayID, p->decoded.request_id, p->to, &weWereSoleRelayer);
@@ -134,34 +118,49 @@ void NextHopRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtast
         }
     }
 
-    perhapsRelay(p);
+    perhapsRebroadcast(p);
 
     // handle the packet as normal
     Router::sniffReceived(p, c);
 }
 
-/* Check if we should be relaying this packet if so, do so. */
-bool NextHopRouter::perhapsRelay(const meshtastic_MeshPacket *p)
+/* Check if we should be rebroadcasting this packet if so, do so. */
+bool NextHopRouter::perhapsRebroadcast(const meshtastic_MeshPacket *p)
 {
     if (!isToUs(p) && !isFromUs(p) && p->hop_limit > 0) {
-        if (p->next_hop == NO_NEXT_HOP_PREFERENCE || p->next_hop == nodeDB->getLastByteOfNodeNum(getNodeNum())) {
+        if (p->id != 0) {
             if (isRebroadcaster()) {
-                meshtastic_MeshPacket *tosend = packetPool.allocCopy(*p); // keep a copy because we will be sending it
-                LOG_INFO("Relaying received message coming from %x", p->relay_node);
+                if (p->next_hop == NO_NEXT_HOP_PREFERENCE || p->next_hop == nodeDB->getLastByteOfNodeNum(getNodeNum())) {
+                    meshtastic_MeshPacket *tosend = packetPool.allocCopy(*p); // keep a copy because we will be sending it
+                    LOG_INFO("Rebroadcast received message coming from %x", p->relay_node);
 
-                // Use shared logic to determine if hop_limit should be decremented
-                if (shouldDecrementHopLimit(p)) {
-                    tosend->hop_limit--; // bump down the hop count
-                } else {
-                    LOG_INFO("Router/CLIENT_BASE-to-favorite-router/CLIENT_BASE relay: preserving hop_limit");
+                    // Use shared logic to determine if hop_limit should be decremented
+                    if (shouldDecrementHopLimit(p)) {
+                        tosend->hop_limit--; // bump down the hop count
+                    } else {
+                        LOG_INFO("favorite-ROUTER/CLIENT_BASE-to-ROUTER/CLIENT_BASE rebroadcast: preserving hop_limit");
+                    }
+#if USERPREFS_EVENT_MODE
+                    if (tosend->hop_limit > 2) {
+                        // if we are "correcting" the hop_limit, "correct" the hop_start by the same amount to preserve hops away.
+                        tosend->hop_start -= (tosend->hop_limit - 2);
+                        tosend->hop_limit = 2;
+                    }
+#endif
+
+                    if (p->next_hop == NO_NEXT_HOP_PREFERENCE) {
+                        FloodingRouter::send(tosend);
+                    } else {
+                        NextHopRouter::send(tosend);
+                    }
+
+                    return true;
                 }
-
-                NextHopRouter::send(tosend);
-
-                return true;
             } else {
-                LOG_DEBUG("Not rebroadcasting: Role = CLIENT_MUTE or Rebroadcast Mode = NONE");
+                LOG_DEBUG("No rebroadcast: Role = CLIENT_MUTE or Rebroadcast Mode = NONE");
             }
+        } else {
+            LOG_DEBUG("Ignore 0 id broadcast");
         }
     }
 
@@ -231,13 +230,13 @@ bool NextHopRouter::stopRetransmission(GlobalPacketId key)
             }
         }
 
-        // Regardless of whether or not we canceled this packet from the txQueue, remove it from our pending list so it doesn't
-        // get scheduled again. (This is the core of stopRetransmission.)
+        // Regardless of whether or not we canceled this packet from the txQueue, remove it from our pending list so it
+        // doesn't get scheduled again. (This is the core of stopRetransmission.)
         auto numErased = pending.erase(key);
         assert(numErased == 1);
 
-        // When we remove an entry from pending, always be sure to release the copy of the packet that was allocated in the call
-        // to startRetransmission.
+        // When we remove an entry from pending, always be sure to release the copy of the packet that was allocated in the
+        // call to startRetransmission.
         packetPool.release(p);
 
         return true;
diff --git a/src/mesh/NextHopRouter.h b/src/mesh/NextHopRouter.h
index 0022644e9..c1df3596b 100644
--- a/src/mesh/NextHopRouter.h
+++ b/src/mesh/NextHopRouter.h
@@ -148,7 +148,7 @@ class NextHopRouter : public FloodingRouter
      */
     uint8_t getNextHop(NodeNum to, uint8_t relay_node);
 
-    /** Check if we should be relaying this packet if so, do so.
-     *  @return true if we did relay */
-    bool perhapsRelay(const meshtastic_MeshPacket *p);
+    /** Check if we should be rebroadcasting this packet if so, do so.
+     *  @return true if we did rebroadcast */
+    bool perhapsRebroadcast(const meshtastic_MeshPacket *p) override;
 };
\ No newline at end of file
diff --git a/src/mesh/PacketHistory.cpp b/src/mesh/PacketHistory.cpp
index 49d581d9a..b4af707ae 100644
--- a/src/mesh/PacketHistory.cpp
+++ b/src/mesh/PacketHistory.cpp
@@ -94,7 +94,6 @@ bool PacketHistory::wasSeenRecently(const meshtastic_MeshPacket *p, bool withUpd
         LOG_DEBUG("Packet History - Hop limit upgrade: packet 0x%08x from hop_limit=%d to hop_limit=%d", p->id, found->hop_limit,
                   p->hop_limit);
         *wasUpgraded = true;
-        seenRecently = false; // Allow router processing but prevent duplicate app delivery
     } else if (wasUpgraded) {
         *wasUpgraded = false; // Initialize to false if not an upgrade
     }
diff --git a/src/mesh/http/ContentHandler.cpp b/src/mesh/http/ContentHandler.cpp
index f87c6e3b0..7b7ebb595 100644
--- a/src/mesh/http/ContentHandler.cpp
+++ b/src/mesh/http/ContentHandler.cpp
@@ -148,6 +148,8 @@ void registerHandlers(HTTPServer *insecureServer, HTTPSServer *secureServer)
 
 void handleAPIv1FromRadio(HTTPRequest *req, HTTPResponse *res)
 {
+    if (webServerThread)
+        webServerThread->markActivity();
 
     LOG_DEBUG("webAPI handleAPIv1FromRadio");
 
@@ -391,6 +393,9 @@ void handleFsDeleteStatic(HTTPRequest *req, HTTPResponse *res)
 
 void handleStatic(HTTPRequest *req, HTTPResponse *res)
 {
+    if (webServerThread)
+        webServerThread->markActivity();
+
     // Get access to the parameters
     ResourceParameters *params = req->getParams();
 
diff --git a/src/mesh/http/WebServer.cpp b/src/mesh/http/WebServer.cpp
index bf170de59..3a264fa5a 100644
--- a/src/mesh/http/WebServer.cpp
+++ b/src/mesh/http/WebServer.cpp
@@ -49,6 +49,12 @@ Preferences prefs;
 using namespace httpsserver;
 #include "mesh/http/ContentHandler.h"
 
+static const uint32_t ACTIVE_THRESHOLD_MS = 5000;
+static const uint32_t MEDIUM_THRESHOLD_MS = 30000;
+static const int32_t ACTIVE_INTERVAL_MS = 50;
+static const int32_t MEDIUM_INTERVAL_MS = 200;
+static const int32_t IDLE_INTERVAL_MS = 1000;
+
 static SSLCert *cert;
 static HTTPSServer *secureServer;
 static HTTPServer *insecureServer;
@@ -175,6 +181,32 @@ WebServerThread::WebServerThread() : concurrency::OSThread("WebServer")
     if (!config.network.wifi_enabled && !config.network.eth_enabled) {
         disable();
     }
+    lastActivityTime = millis();
+}
+
+void WebServerThread::markActivity()
+{
+    lastActivityTime = millis();
+}
+
+int32_t WebServerThread::getAdaptiveInterval()
+{
+    uint32_t currentTime = millis();
+    uint32_t timeSinceActivity;
+
+    if (currentTime >= lastActivityTime) {
+        timeSinceActivity = currentTime - lastActivityTime;
+    } else {
+        timeSinceActivity = (UINT32_MAX - lastActivityTime) + currentTime + 1;
+    }
+
+    if (timeSinceActivity < ACTIVE_THRESHOLD_MS) {
+        return ACTIVE_INTERVAL_MS;
+    } else if (timeSinceActivity < MEDIUM_THRESHOLD_MS) {
+        return MEDIUM_INTERVAL_MS;
+    } else {
+        return IDLE_INTERVAL_MS;
+    }
 }
 
 int32_t WebServerThread::runOnce()
@@ -189,8 +221,7 @@ int32_t WebServerThread::runOnce()
         ESP.restart();
     }
 
-    // Loop every 5ms.
-    return (5);
+    return getAdaptiveInterval();
 }
 
 void initWebServer()
diff --git a/src/mesh/http/WebServer.h b/src/mesh/http/WebServer.h
index 815d87432..e7a29a5a7 100644
--- a/src/mesh/http/WebServer.h
+++ b/src/mesh/http/WebServer.h
@@ -10,13 +10,17 @@ void createSSLCert();
 
 class WebServerThread : private concurrency::OSThread
 {
+  private:
+    uint32_t lastActivityTime = 0;
 
   public:
     WebServerThread();
     uint32_t requestRestart = 0;
+    void markActivity();
 
   protected:
     virtual int32_t runOnce() override;
+    int32_t getAdaptiveInterval();
 };
 
 extern WebServerThread *webServerThread;
diff --git a/src/modules/TraceRouteModule.cpp b/src/modules/TraceRouteModule.cpp
index fc2cc232b..5bdde1919 100644
--- a/src/modules/TraceRouteModule.cpp
+++ b/src/modules/TraceRouteModule.cpp
@@ -21,6 +21,11 @@ void TraceRouteModule::alterReceivedProtobuf(meshtastic_MeshPacket &p, meshtasti
 {
     const meshtastic_Data &incoming = p.decoded;
 
+    // Update next-hops using returned route
+    if (incoming.request_id) {
+        updateNextHops(p, r);
+    }
+
     // Insert unknown hops if necessary
     insertUnknownHops(p, r, !incoming.request_id);
 
@@ -153,6 +158,65 @@ void TraceRouteModule::alterReceivedProtobuf(meshtastic_MeshPacket &p, meshtasti
     }
 }
 
+void TraceRouteModule::updateNextHops(meshtastic_MeshPacket &p, meshtastic_RouteDiscovery *r)
+{
+    // E.g. if the route is A->B->C->D and we are B, we can set C as next-hop for C and D
+    // Similarly, if we are C, we can set D as next-hop for D
+    // If we are A, we can set B as next-hop for B, C and D
+
+    // First check if we were the original sender or in the original route
+    int8_t nextHopIndex = -1;
+    if (isToUs(&p)) {
+        nextHopIndex = 0; // We are the original sender, next hop is first in route
+    } else {
+        // Check if we are in the original route
+        for (uint8_t i = 0; i < r->route_count; i++) {
+            if (r->route[i] == nodeDB->getNodeNum()) {
+                nextHopIndex = i + 1; // Next hop is the one after us
+                break;
+            }
+        }
+    }
+
+    // If we are in the original route, update the next hops
+    if (nextHopIndex != -1) {
+        // For every node after us, we can set the next-hop to the first node after us
+        NodeNum nextHop;
+        if (nextHopIndex == r->route_count) {
+            nextHop = p.from; // We are the last in the route, next hop is destination
+        } else {
+            nextHop = r->route[nextHopIndex];
+        }
+
+        if (nextHop == NODENUM_BROADCAST) {
+            return;
+        }
+        uint8_t nextHopByte = nodeDB->getLastByteOfNodeNum(nextHop);
+
+        // For the rest of the nodes in the route, set their next-hop
+        // Note: if we are the last in the route, this loop will not run
+        for (int8_t i = nextHopIndex; i < r->route_count; i++) {
+            NodeNum targetNode = r->route[i];
+            maybeSetNextHop(targetNode, nextHopByte);
+        }
+
+        // Also set next-hop for the destination node
+        maybeSetNextHop(p.from, nextHopByte);
+    }
+}
+
+void TraceRouteModule::maybeSetNextHop(NodeNum target, uint8_t nextHopByte)
+{
+    if (target == NODENUM_BROADCAST)
+        return;
+
+    meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(target);
+    if (node && node->next_hop != nextHopByte) {
+        LOG_INFO("Updating next-hop for 0x%08x to 0x%02x based on traceroute", target, nextHopByte);
+        node->next_hop = nextHopByte;
+    }
+}
+
 void TraceRouteModule::processUpgradedPacket(const meshtastic_MeshPacket &mp)
 {
     if (mp.which_payload_variant != meshtastic_MeshPacket_decoded_tag || mp.decoded.portnum != meshtastic_PortNum_TRACEROUTE_APP)
diff --git a/src/modules/TraceRouteModule.h b/src/modules/TraceRouteModule.h
index a069f7157..dac422388 100644
--- a/src/modules/TraceRouteModule.h
+++ b/src/modules/TraceRouteModule.h
@@ -55,6 +55,12 @@ class TraceRouteModule : public ProtobufModule,
     // Call to add your ID to the route array of a RouteDiscovery message
     void appendMyIDandSNR(meshtastic_RouteDiscovery *r, float snr, bool isTowardsDestination, bool SNRonly);
 
+    // Update next-hops in the routing table based on the returned route
+    void updateNextHops(meshtastic_MeshPacket &p, meshtastic_RouteDiscovery *r);
+
+    // Helper to update next-hop for a single node
+    void maybeSetNextHop(NodeNum target, uint8_t nextHopByte);
+
     /* Call to print the route array of a RouteDiscovery message.
        Set origin to where the request came from.
        Set dest to the ID of its destination, or NODENUM_BROADCAST if it has not yet arrived there. */
diff --git a/src/power.h b/src/power.h
index 23eb95064..cdbdd3ea0 100644
--- a/src/power.h
+++ b/src/power.h
@@ -138,6 +138,7 @@ class Power : private concurrency::OSThread
     void reboot();
     // open circuit voltage lookup table
     uint8_t low_voltage_counter;
+    int32_t lastLogTime = 0;
 #ifdef DEBUG_HEAP
     uint32_t lastheap;
 #endif

From 580fa292ac68b272b76271a9a11c625c459be4a9 Mon Sep 17 00:00:00 2001
From: Jason P 
Date: Sat, 25 Oct 2025 07:12:59 -0500
Subject: [PATCH 542/691] Address longName wrapping (#8441)

* Address longName wrapping

* Update src/graphics/draw/NodeListRenderer.cpp

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Ben Meadors 
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---
 src/graphics/draw/MenuHandler.cpp      |  1 +
 src/graphics/draw/NodeListRenderer.cpp | 34 +++++++++++++++++++++-----
 2 files changed, 29 insertions(+), 6 deletions(-)

diff --git a/src/graphics/draw/MenuHandler.cpp b/src/graphics/draw/MenuHandler.cpp
index 701062e08..803fb87ea 100644
--- a/src/graphics/draw/MenuHandler.cpp
+++ b/src/graphics/draw/MenuHandler.cpp
@@ -784,6 +784,7 @@ void menuHandler::nodeNameLengthMenu()
             screen->runNow();
         }
     };
+    bannerOptions.InitialSelected = config.display.use_long_node_name == true ? 1 : 2;
     screen->showOverlayBanner(bannerOptions);
 }
 
diff --git a/src/graphics/draw/NodeListRenderer.cpp b/src/graphics/draw/NodeListRenderer.cpp
index 07577db8c..16bfa7066 100644
--- a/src/graphics/draw/NodeListRenderer.cpp
+++ b/src/graphics/draw/NodeListRenderer.cpp
@@ -53,7 +53,7 @@ static int scrollIndex = 0;
 // Utility Functions
 // =============================
 
-const char *getSafeNodeName(meshtastic_NodeInfoLite *node)
+const char *getSafeNodeName(OLEDDisplay *display, meshtastic_NodeInfoLite *node)
 {
     const char *name = NULL;
     static char nodeName[16] = "?";
@@ -81,6 +81,28 @@ const char *getSafeNodeName(meshtastic_NodeInfoLite *node)
         snprintf(nodeName, sizeof(nodeName), "(%04X)", (uint16_t)(node->num & 0xFFFF));
     }
 
+    if (config.display.use_long_node_name == true) {
+        int availWidth = (SCREEN_WIDTH / 2) - 65;
+        if (availWidth < 0)
+            availWidth = 0;
+
+        size_t origLen = strlen(nodeName);
+        while (nodeName[0] && display->getStringWidth(nodeName) > availWidth) {
+            nodeName[strlen(nodeName) - 1] = '\0';
+        }
+
+        // If we actually truncated, append "..." (ensure space remains in buffer)
+        if (strlen(nodeName) < origLen) {
+            size_t len = strlen(nodeName);
+            size_t maxLen = sizeof(nodeName) - 4; // 3 for "..." and 1 for '\0'
+            if (len > maxLen) {
+                nodeName[maxLen] = '\0';
+                len = maxLen;
+            }
+            strcat(nodeName, "...");
+        }
+    }
+
     return nodeName;
 }
 
@@ -147,7 +169,7 @@ void drawEntryLastHeard(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int
     bool isLeftCol = (x < SCREEN_WIDTH / 2);
     int timeOffset = (isHighResolution) ? (isLeftCol ? 7 : 10) : (isLeftCol ? 3 : 7);
 
-    const char *nodeName = getSafeNodeName(node);
+    const char *nodeName = getSafeNodeName(display, node);
 
     char timeStr[10];
     uint32_t seconds = sinceLastSeen(node);
@@ -192,7 +214,7 @@ void drawEntryHopSignal(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int
 
     int barsXOffset = columnWidth - barsOffset;
 
-    const char *nodeName = getSafeNodeName(node);
+    const char *nodeName = getSafeNodeName(display, node);
 
     display->setTextAlignment(TEXT_ALIGN_LEFT);
     display->setFont(FONT_SMALL);
@@ -236,7 +258,7 @@ void drawNodeDistance(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16
     bool isLeftCol = (x < SCREEN_WIDTH / 2);
     int nameMaxWidth = columnWidth - (isHighResolution ? (isLeftCol ? 25 : 28) : (isLeftCol ? 20 : 22));
 
-    const char *nodeName = getSafeNodeName(node);
+    const char *nodeName = getSafeNodeName(display, node);
     char distStr[10] = "";
 
     meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
@@ -331,7 +353,7 @@ void drawEntryCompass(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16
     // Adjust max text width depending on column and screen width
     int nameMaxWidth = columnWidth - (isHighResolution ? (isLeftCol ? 25 : 28) : (isLeftCol ? 20 : 22));
 
-    const char *nodeName = getSafeNodeName(node);
+    const char *nodeName = getSafeNodeName(display, node);
 
     display->setTextAlignment(TEXT_ALIGN_LEFT);
     display->setFont(FONT_SMALL);
@@ -362,11 +384,11 @@ void drawCompassArrow(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16
     float bearing = GeoCoord::bearing(userLat, userLon, nodeLat, nodeLon);
     float bearingToNode = RAD_TO_DEG * bearing;
     float relativeBearing = fmod((bearingToNode - myHeading + 360), 360);
-    float angle = relativeBearing * DEG_TO_RAD;
     // Shrink size by 2px
     int size = FONT_HEIGHT_SMALL - 5;
     CompassRenderer::drawArrowToNode(display, centerX, centerY, size, relativeBearing);
     /*
+    float angle = relativeBearing * DEG_TO_RAD;
     float halfSize = size / 2.0;
 
     // Point of the arrow

From dd51de85f37dd123e0e7682f70f0e0f1ecdab067 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Mon, 27 Oct 2025 19:42:32 +1100
Subject: [PATCH 543/691] Update GitHub Artifact Actions (#8443)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
 .github/actions/build-variant/action.yml    |  2 +-
 .github/workflows/build_debian_src.yml      |  2 +-
 .github/workflows/build_firmware.yml        |  2 +-
 .github/workflows/build_one_arch.yml        |  8 ++++----
 .github/workflows/build_one_target.yml      |  8 ++++----
 .github/workflows/main_matrix.yml           | 18 +++++++++---------
 .github/workflows/merge_queue.yml           | 18 +++++++++---------
 .github/workflows/package_obs.yml           |  2 +-
 .github/workflows/package_pio_deps.yml      |  2 +-
 .github/workflows/package_ppa.yml           |  2 +-
 .github/workflows/pr_tests.yml              |  2 +-
 .github/workflows/sec_sast_semgrep_cron.yml |  2 +-
 .github/workflows/test_native.yml           | 12 ++++++------
 13 files changed, 40 insertions(+), 40 deletions(-)

diff --git a/.github/actions/build-variant/action.yml b/.github/actions/build-variant/action.yml
index f611908ee..a71ddfc4d 100644
--- a/.github/actions/build-variant/action.yml
+++ b/.github/actions/build-variant/action.yml
@@ -100,7 +100,7 @@ runs:
       id: version
 
     - name: Store binaries as an artifact
-      uses: actions/upload-artifact@v4
+      uses: actions/upload-artifact@v5
       with:
         name: firmware-${{ inputs.arch }}-${{ inputs.board }}-${{ steps.version.outputs.long }}.zip
         overwrite: true
diff --git a/.github/workflows/build_debian_src.yml b/.github/workflows/build_debian_src.yml
index 7f3f8b672..d36e7fea1 100644
--- a/.github/workflows/build_debian_src.yml
+++ b/.github/workflows/build_debian_src.yml
@@ -64,7 +64,7 @@ jobs:
           PKG_VERSION: ${{ steps.version.outputs.deb }}
 
       - name: Store binaries as an artifact
-        uses: actions/upload-artifact@v4
+        uses: actions/upload-artifact@v5
         with:
           name: firmware-debian-${{ steps.version.outputs.deb }}~${{ inputs.series }}-src
           overwrite: true
diff --git a/.github/workflows/build_firmware.yml b/.github/workflows/build_firmware.yml
index b62729332..57c1e72c7 100644
--- a/.github/workflows/build_firmware.yml
+++ b/.github/workflows/build_firmware.yml
@@ -56,7 +56,7 @@ jobs:
           ota_firmware_target: ${{ steps.ota_dir.outputs.tgt || '' }}
 
       - name: Store binaries as an artifact
-        uses: actions/upload-artifact@v4
+        uses: actions/upload-artifact@v5
         id: upload
         with:
           name: firmware-${{ inputs.platform }}-${{ inputs.pio_env }}-${{ inputs.version }}.zip
diff --git a/.github/workflows/build_one_arch.yml b/.github/workflows/build_one_arch.yml
index 9c57f8b7d..6d5462c93 100644
--- a/.github/workflows/build_one_arch.yml
+++ b/.github/workflows/build_one_arch.yml
@@ -113,7 +113,7 @@ jobs:
           ref: ${{github.event.pull_request.head.ref}}
           repository: ${{github.event.pull_request.head.repo.full_name}}
 
-      - uses: actions/download-artifact@v5
+      - uses: actions/download-artifact@v6
         with:
           path: ./
           pattern: firmware-${{inputs.arch}}-*
@@ -126,7 +126,7 @@ jobs:
         run: mv -b -t ./ ./bin/device-*.sh ./bin/device-*.bat
 
       - name: Repackage in single firmware zip
-        uses: actions/upload-artifact@v4
+        uses: actions/upload-artifact@v5
         with:
           name: firmware-${{inputs.arch}}-${{ needs.version.outputs.long }}
           overwrite: true
@@ -142,7 +142,7 @@ jobs:
             ./Meshtastic_nRF52_factory_erase*.uf2
           retention-days: 30
 
-      - uses: actions/download-artifact@v5
+      - uses: actions/download-artifact@v6
         with:
           name: firmware-${{inputs.arch}}-${{ needs.version.outputs.long }}
           merge-multiple: true
@@ -161,7 +161,7 @@ jobs:
         run: zip -j -9 -r ./firmware-${{inputs.arch}}-${{ needs.version.outputs.long }}.zip ./output
 
       - name: Repackage in single elfs zip
-        uses: actions/upload-artifact@v4
+        uses: actions/upload-artifact@v5
         with:
           name: debug-elfs-${{inputs.arch}}-${{ needs.version.outputs.long }}.zip
           overwrite: true
diff --git a/.github/workflows/build_one_target.yml b/.github/workflows/build_one_target.yml
index 15b3fdba9..46362a629 100644
--- a/.github/workflows/build_one_target.yml
+++ b/.github/workflows/build_one_target.yml
@@ -119,7 +119,7 @@ jobs:
           ref: ${{github.event.pull_request.head.ref}}
           repository: ${{github.event.pull_request.head.repo.full_name}}
 
-      - uses: actions/download-artifact@v5
+      - uses: actions/download-artifact@v6
         with:
           path: ./
           pattern: firmware-*-*
@@ -132,7 +132,7 @@ jobs:
         run: mv -b -t ./ ./bin/device-*.sh ./bin/device-*.bat
 
       - name: Repackage in single firmware zip
-        uses: actions/upload-artifact@v4
+        uses: actions/upload-artifact@v5
         with:
           name: firmware-${{inputs.target}}-${{ needs.version.outputs.long }}
           overwrite: true
@@ -148,7 +148,7 @@ jobs:
             ./Meshtastic_nRF52_factory_erase*.uf2
           retention-days: 30
 
-      - uses: actions/download-artifact@v5
+      - uses: actions/download-artifact@v6
         with:
           pattern: firmware-*-${{ needs.version.outputs.long }}
           merge-multiple: true
@@ -167,7 +167,7 @@ jobs:
         run: zip -j -9 -r ./firmware-${{inputs.target}}-${{ needs.version.outputs.long }}.zip ./output
 
       - name: Repackage in single elfs zip
-        uses: actions/upload-artifact@v4
+        uses: actions/upload-artifact@v5
         with:
           name: debug-elfs-${{inputs.target}}-${{ needs.version.outputs.long }}.zip
           overwrite: true
diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml
index 02a4c23b8..7ea033d55 100644
--- a/.github/workflows/main_matrix.yml
+++ b/.github/workflows/main_matrix.yml
@@ -168,7 +168,7 @@ jobs:
           ref: ${{github.event.pull_request.head.ref}}
           repository: ${{github.event.pull_request.head.repo.full_name}}
 
-      - uses: actions/download-artifact@v5
+      - uses: actions/download-artifact@v6
         with:
           path: ./
           pattern: firmware-${{matrix.arch}}-*
@@ -181,7 +181,7 @@ jobs:
         run: mv -b -t ./ ./bin/device-*.sh ./bin/device-*.bat
 
       - name: Repackage in single firmware zip
-        uses: actions/upload-artifact@v4
+        uses: actions/upload-artifact@v5
         with:
           name: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}
           overwrite: true
@@ -197,7 +197,7 @@ jobs:
             ./Meshtastic_nRF52_factory_erase*.uf2
           retention-days: 30
 
-      - uses: actions/download-artifact@v5
+      - uses: actions/download-artifact@v6
         with:
           name: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}
           merge-multiple: true
@@ -216,7 +216,7 @@ jobs:
         run: zip -j -9 -r ./firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip ./output
 
       - name: Repackage in single elfs zip
-        uses: actions/upload-artifact@v4
+        uses: actions/upload-artifact@v5
         with:
           name: debug-elfs-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip
           overwrite: true
@@ -261,14 +261,14 @@ jobs:
             Autogenerated by github action, developer should edit as required before publishing...
 
       - name: Download source deb
-        uses: actions/download-artifact@v5
+        uses: actions/download-artifact@v6
         with:
           pattern: firmware-debian-${{ needs.version.outputs.deb }}~UNRELEASED-src
           merge-multiple: true
           path: ./output/debian-src
 
       - name: Download `native-tft` pio deps
-        uses: actions/download-artifact@v5
+        uses: actions/download-artifact@v6
         with:
           pattern: platformio-deps-native-tft-${{ needs.version.outputs.long }}
           merge-multiple: true
@@ -318,7 +318,7 @@ jobs:
         with:
           python-version: 3.x
 
-      - uses: actions/download-artifact@v5
+      - uses: actions/download-artifact@v6
         with:
           pattern: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}
           merge-multiple: true
@@ -335,7 +335,7 @@ jobs:
       - name: Zip firmware
         run: zip -j -9 -r ./firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip ./output
 
-      - uses: actions/download-artifact@v5
+      - uses: actions/download-artifact@v6
         with:
           name: debug-elfs-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip
           merge-multiple: true
@@ -373,7 +373,7 @@ jobs:
         with:
           python-version: 3.x
 
-      - uses: actions/download-artifact@v5
+      - uses: actions/download-artifact@v6
         with:
           pattern: firmware-{${{ env.targets }}}-${{ needs.version.outputs.long }}
           merge-multiple: true
diff --git a/.github/workflows/merge_queue.yml b/.github/workflows/merge_queue.yml
index e8c3d3450..6d69258c9 100644
--- a/.github/workflows/merge_queue.yml
+++ b/.github/workflows/merge_queue.yml
@@ -147,7 +147,7 @@ jobs:
           ref: ${{github.event.pull_request.head.ref}}
           repository: ${{github.event.pull_request.head.repo.full_name}}
 
-      - uses: actions/download-artifact@v5
+      - uses: actions/download-artifact@v6
         with:
           path: ./
           pattern: firmware-${{matrix.arch}}-*
@@ -160,7 +160,7 @@ jobs:
         run: mv -b -t ./ ./bin/device-*.sh ./bin/device-*.bat
 
       - name: Repackage in single firmware zip
-        uses: actions/upload-artifact@v4
+        uses: actions/upload-artifact@v5
         with:
           name: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}
           overwrite: true
@@ -176,7 +176,7 @@ jobs:
             ./Meshtastic_nRF52_factory_erase*.uf2
           retention-days: 30
 
-      - uses: actions/download-artifact@v5
+      - uses: actions/download-artifact@v6
         with:
           name: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}
           merge-multiple: true
@@ -195,7 +195,7 @@ jobs:
         run: zip -j -9 -r ./firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip ./output
 
       - name: Repackage in single elfs zip
-        uses: actions/upload-artifact@v4
+        uses: actions/upload-artifact@v5
         with:
           name: debug-elfs-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip
           overwrite: true
@@ -240,14 +240,14 @@ jobs:
             Autogenerated by github action, developer should edit as required before publishing...
 
       - name: Download source deb
-        uses: actions/download-artifact@v5
+        uses: actions/download-artifact@v6
         with:
           pattern: firmware-debian-${{ needs.version.outputs.deb }}~UNRELEASED-src
           merge-multiple: true
           path: ./output/debian-src
 
       - name: Download `native-tft` pio deps
-        uses: actions/download-artifact@v5
+        uses: actions/download-artifact@v6
         with:
           pattern: platformio-deps-native-tft-${{ needs.version.outputs.long }}
           merge-multiple: true
@@ -297,7 +297,7 @@ jobs:
         with:
           python-version: 3.x
 
-      - uses: actions/download-artifact@v5
+      - uses: actions/download-artifact@v6
         with:
           pattern: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}
           merge-multiple: true
@@ -314,7 +314,7 @@ jobs:
       - name: Zip firmware
         run: zip -j -9 -r ./firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip ./output
 
-      - uses: actions/download-artifact@v5
+      - uses: actions/download-artifact@v6
         with:
           name: debug-elfs-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip
           merge-multiple: true
@@ -352,7 +352,7 @@ jobs:
         with:
           python-version: 3.x
 
-      - uses: actions/download-artifact@v5
+      - uses: actions/download-artifact@v6
         with:
           pattern: firmware-{${{ env.targets }}}-${{ needs.version.outputs.long }}
           merge-multiple: true
diff --git a/.github/workflows/package_obs.yml b/.github/workflows/package_obs.yml
index 4c547eadc..b8a829d9a 100644
--- a/.github/workflows/package_obs.yml
+++ b/.github/workflows/package_obs.yml
@@ -58,7 +58,7 @@ jobs:
         id: version
 
       - name: Download artifacts
-        uses: actions/download-artifact@v5
+        uses: actions/download-artifact@v6
         with:
           name: firmware-debian-${{ steps.version.outputs.deb }}~${{ inputs.series }}-src
           merge-multiple: true
diff --git a/.github/workflows/package_pio_deps.yml b/.github/workflows/package_pio_deps.yml
index d8ff6e631..c52dfe348 100644
--- a/.github/workflows/package_pio_deps.yml
+++ b/.github/workflows/package_pio_deps.yml
@@ -56,7 +56,7 @@ jobs:
           PLATFORMIO_CORE_DIR: pio/core
 
       - name: Store binaries as an artifact
-        uses: actions/upload-artifact@v4
+        uses: actions/upload-artifact@v5
         with:
           name: platformio-deps-${{ inputs.pio_env }}-${{ steps.version.outputs.long }}
           overwrite: true
diff --git a/.github/workflows/package_ppa.yml b/.github/workflows/package_ppa.yml
index aece730a0..2d6c257e6 100644
--- a/.github/workflows/package_ppa.yml
+++ b/.github/workflows/package_ppa.yml
@@ -60,7 +60,7 @@ jobs:
         id: version
 
       - name: Download artifacts
-        uses: actions/download-artifact@v5
+        uses: actions/download-artifact@v6
         with:
           name: firmware-debian-${{ steps.version.outputs.deb }}~${{ inputs.series }}-src
           merge-multiple: true
diff --git a/.github/workflows/pr_tests.yml b/.github/workflows/pr_tests.yml
index 4e285852d..c3a964e04 100644
--- a/.github/workflows/pr_tests.yml
+++ b/.github/workflows/pr_tests.yml
@@ -50,7 +50,7 @@ jobs:
 
       - name: Download test artifacts
         if: needs.native-tests.result != 'skipped'
-        uses: actions/download-artifact@v5
+        uses: actions/download-artifact@v6
         with:
           name: platformio-test-report-${{ steps.version.outputs.long }}.zip
           merge-multiple: true
diff --git a/.github/workflows/sec_sast_semgrep_cron.yml b/.github/workflows/sec_sast_semgrep_cron.yml
index 2059fde2c..dfb828bf6 100644
--- a/.github/workflows/sec_sast_semgrep_cron.yml
+++ b/.github/workflows/sec_sast_semgrep_cron.yml
@@ -33,7 +33,7 @@ jobs:
 
       # step 3
       - name: save report as pipeline artifact
-        uses: actions/upload-artifact@v4
+        uses: actions/upload-artifact@v5
         with:
           name: report.sarif
           overwrite: true
diff --git a/.github/workflows/test_native.yml b/.github/workflows/test_native.yml
index 9cf1c9e53..3f3d02e4c 100644
--- a/.github/workflows/test_native.yml
+++ b/.github/workflows/test_native.yml
@@ -59,7 +59,7 @@ jobs:
         id: version
 
       - name: Save coverage information
-        uses: actions/upload-artifact@v4
+        uses: actions/upload-artifact@v5
         if: always() # run this step even if previous step failed
         with:
           name: lcov-coverage-info-native-simulator-test-${{ steps.version.outputs.long }}.zip
@@ -94,7 +94,7 @@ jobs:
 
       - name: Save test results
         if: always() # run this step even if previous step failed
-        uses: actions/upload-artifact@v4
+        uses: actions/upload-artifact@v5
         with:
           name: platformio-test-report-${{ steps.version.outputs.long }}.zip
           overwrite: true
@@ -108,7 +108,7 @@ jobs:
           sed -i -e "s#${PWD}#.#" coverage_tests.info # Make paths relative.
 
       - name: Save coverage information
-        uses: actions/upload-artifact@v4
+        uses: actions/upload-artifact@v5
         if: always() # run this step even if previous step failed
         with:
           name: lcov-coverage-info-native-platformio-tests-${{ steps.version.outputs.long }}.zip
@@ -137,7 +137,7 @@ jobs:
         id: version
 
       - name: Download test artifacts
-        uses: actions/download-artifact@v5
+        uses: actions/download-artifact@v6
         with:
           name: platformio-test-report-${{ steps.version.outputs.long }}.zip
           merge-multiple: true
@@ -150,7 +150,7 @@ jobs:
           reporter: java-junit
 
       - name: Download coverage artifacts
-        uses: actions/download-artifact@v5
+        uses: actions/download-artifact@v6
         with:
           pattern: lcov-coverage-info-native-*-${{ steps.version.outputs.long }}.zip
           path: code-coverage-report
@@ -163,7 +163,7 @@ jobs:
           genhtml --quiet --legend --prefix "${PWD}" code-coverage-report/coverage_src.info --output-directory code-coverage-report
 
       - name: Save Code Coverage Report
-        uses: actions/upload-artifact@v4
+        uses: actions/upload-artifact@v5
         with:
           name: code-coverage-report-${{ steps.version.outputs.long }}.zip
           path: code-coverage-report

From b6830a68a090de21757b183c26ff62d98867124e Mon Sep 17 00:00:00 2001
From: Tom Fifield 
Date: Mon, 27 Oct 2025 19:47:34 +1100
Subject: [PATCH 544/691] Migrate test workflow to use Node 24 (#8466)

Node 24 is now the common version amoungst all of our actions.
---
 .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 7852fc31f..1ec435512 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -49,7 +49,7 @@ jobs:
       - name: Setup Node
         uses: actions/setup-node@v6
         with:
-          node-version: 22
+          node-version: 24
 
       - name: Setup pnpm
         uses: pnpm/action-setup@v4

From f045ca2303ce57089eced1fe8ec321cb0b60bfc6 Mon Sep 17 00:00:00 2001
From: Erayd 
Date: Tue, 28 Oct 2025 00:05:59 +1300
Subject: [PATCH 545/691] Fix type to ensure correct alignment; saves 4B per
 entry (#8465)

---
 src/mesh/PacketCache.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/mesh/PacketCache.h b/src/mesh/PacketCache.h
index 81ad455da..85660922b 100644
--- a/src/mesh/PacketCache.h
+++ b/src/mesh/PacketCache.h
@@ -17,7 +17,7 @@ typedef struct PacketCacheEntry {
             uint8_t encrypted : 1;    // Payload is encrypted
             uint8_t has_metadata : 1; // Payload includes PacketCacheMetadata
             uint8_t : 6;              // Reserved for future use
-            uint16_t : 8;             // Reserved for future use
+            uint8_t : 8;              // Reserved for future use
         };
     };
 } PacketCacheEntry;

From 7d3e529b2f7cb266c3fe741c359a85141cec5d25 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Wed, 29 Oct 2025 07:16:09 +1100
Subject: [PATCH 546/691] Update node to v24 (#8476)

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 7852fc31f..1ec435512 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -49,7 +49,7 @@ jobs:
       - name: Setup Node
         uses: actions/setup-node@v6
         with:
-          node-version: 22
+          node-version: 24
 
       - name: Setup pnpm
         uses: pnpm/action-setup@v4

From 28f53d132aa0d3903482ea9b475527068389244a Mon Sep 17 00:00:00 2001
From: Clive Blackledge 
Date: Tue, 28 Oct 2025 13:32:08 -0700
Subject: [PATCH 547/691] refactor: change node count variables from uint8_t to
 uint16_t (#8478)

This is a non-breaking change that increases the internal representation
of node counts from uint8_t (max 255) to uint16_t (max 65535) to support
larger mesh networks, particularly on ESP32-S3 devices with PSRAM.

Changes:
- NodeStatus: numOnline, numTotal, lastNumTotal (uint8_t -> uint16_t)
- ProtobufModule: numOnlineNodes (uint8_t -> uint16_t)
- MapApplet: loop counters changed to size_t for consistency with getNumMeshNodes()
- NodeStatus: Fixed log format to use %u for unsigned integers

Note: Default class methods keep uint32_t for numOnlineNodes parameter
to match the public API and allow flexibility, even though internal node
counts use uint16_t (max 65535 nodes).

This change does NOT affect protobuf definitions, maintaining wire
compatibility with existing clients and devices.
---
 src/NodeStatus.h                                 | 16 ++++++++--------
 .../niche/InkHUD/Applets/Bases/Map/MapApplet.cpp |  6 +++---
 src/mesh/Default.h                               |  5 ++++-
 src/mesh/ProtobufModule.h                        |  2 +-
 4 files changed, 16 insertions(+), 13 deletions(-)

diff --git a/src/NodeStatus.h b/src/NodeStatus.h
index 60d1bdd98..550f6254a 100644
--- a/src/NodeStatus.h
+++ b/src/NodeStatus.h
@@ -14,16 +14,16 @@ class NodeStatus : public Status
     CallbackObserver statusObserver =
         CallbackObserver(this, &NodeStatus::updateStatus);
 
-    uint8_t numOnline = 0;
-    uint8_t numTotal = 0;
+    uint16_t numOnline = 0;
+    uint16_t numTotal = 0;
 
-    uint8_t lastNumTotal = 0;
+    uint16_t lastNumTotal = 0;
 
   public:
     bool forceUpdate = false;
 
     NodeStatus() { statusType = STATUS_TYPE_NODE; }
-    NodeStatus(uint8_t numOnline, uint8_t numTotal, bool forceUpdate = false) : Status()
+    NodeStatus(uint16_t numOnline, uint16_t numTotal, bool forceUpdate = false) : Status()
     {
         this->forceUpdate = forceUpdate;
         this->numOnline = numOnline;
@@ -34,11 +34,11 @@ class NodeStatus : public Status
 
     void observe(Observable *source) { statusObserver.observe(source); }
 
-    uint8_t getNumOnline() const { return numOnline; }
+    uint16_t getNumOnline() const { return numOnline; }
 
-    uint8_t getNumTotal() const { return numTotal; }
+    uint16_t getNumTotal() const { return numTotal; }
 
-    uint8_t getLastNumTotal() const { return lastNumTotal; }
+    uint16_t getLastNumTotal() const { return lastNumTotal; }
 
     bool matches(const NodeStatus *newStatus) const
     {
@@ -56,7 +56,7 @@ class NodeStatus : public Status
             numTotal = newStatus->getNumTotal();
         }
         if (isDirty || newStatus->forceUpdate) {
-            LOG_DEBUG("Node status update: %d online, %d total", numOnline, numTotal);
+            LOG_DEBUG("Node status update: %u online, %u total", numOnline, numTotal);
             onNewStatus.notifyObservers(this);
         }
         return 0;
diff --git a/src/graphics/niche/InkHUD/Applets/Bases/Map/MapApplet.cpp b/src/graphics/niche/InkHUD/Applets/Bases/Map/MapApplet.cpp
index 818c68070..d383a11e4 100644
--- a/src/graphics/niche/InkHUD/Applets/Bases/Map/MapApplet.cpp
+++ b/src/graphics/niche/InkHUD/Applets/Bases/Map/MapApplet.cpp
@@ -287,7 +287,7 @@ void InkHUD::MapApplet::getMapCenter(float *lat, float *lng)
     float easternmost = lngCenter;
     float westernmost = lngCenter;
 
-    for (uint8_t i = 0; i < nodeDB->getNumMeshNodes(); i++) {
+    for (size_t i = 0; i < nodeDB->getNumMeshNodes(); i++) {
         meshtastic_NodeInfoLite *node = nodeDB->getMeshNodeByIndex(i);
 
         // Skip if no position
@@ -474,8 +474,8 @@ void InkHUD::MapApplet::drawLabeledMarker(meshtastic_NodeInfoLite *node)
 // Need at least two, to draw a sensible map
 bool InkHUD::MapApplet::enoughMarkers()
 {
-    uint8_t count = 0;
-    for (uint8_t i = 0; i < nodeDB->getNumMeshNodes(); i++) {
+    size_t count = 0;
+    for (size_t i = 0; i < nodeDB->getNumMeshNodes(); i++) {
         meshtastic_NodeInfoLite *node = nodeDB->getMeshNodeByIndex(i);
 
         // Count nodes
diff --git a/src/mesh/Default.h b/src/mesh/Default.h
index d0d4678ff..34289ccb6 100644
--- a/src/mesh/Default.h
+++ b/src/mesh/Default.h
@@ -46,12 +46,15 @@ class Default
     static uint32_t getConfiguredOrDefaultMs(uint32_t configuredInterval);
     static uint32_t getConfiguredOrDefaultMs(uint32_t configuredInterval, uint32_t defaultInterval);
     static uint32_t getConfiguredOrDefault(uint32_t configured, uint32_t defaultValue);
+    // Note: numOnlineNodes uses uint32_t to match the public API and allow flexibility,
+    // even though internal node counts use uint16_t (max 65535 nodes)
     static uint32_t getConfiguredOrDefaultMsScaled(uint32_t configured, uint32_t defaultValue, uint32_t numOnlineNodes);
     static uint8_t getConfiguredOrDefaultHopLimit(uint8_t configured);
     static uint32_t getConfiguredOrMinimumValue(uint32_t configured, uint32_t minValue);
 
   private:
-    static float congestionScalingCoefficient(int numOnlineNodes)
+    // Note: Kept as uint32_t to match the public API parameter type
+    static float congestionScalingCoefficient(uint32_t numOnlineNodes)
     {
         // Increase frequency of broadcasts for small networks regardless of preset
         if (numOnlineNodes <= 10) {
diff --git a/src/mesh/ProtobufModule.h b/src/mesh/ProtobufModule.h
index e038e9bb8..725477eae 100644
--- a/src/mesh/ProtobufModule.h
+++ b/src/mesh/ProtobufModule.h
@@ -13,7 +13,7 @@ template  class ProtobufModule : protected SinglePortModule
     const pb_msgdesc_t *fields;
 
   public:
-    uint8_t numOnlineNodes = 0;
+    uint16_t numOnlineNodes = 0;
     /** Constructor
      * name is for debugging output
      */

From c330bfe8483beea2ab967a19f666b713de97b6d3 Mon Sep 17 00:00:00 2001
From: Jonathan Bennett 
Date: Wed, 29 Oct 2025 06:46:50 -0500
Subject: [PATCH 548/691] Turn the e-ink backlight on for any brightness value
 over 0 (#8481)

---
 src/graphics/Screen.cpp           | 2 +-
 src/graphics/draw/MenuHandler.cpp | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp
index e1cc0ccad..86599d5b3 100644
--- a/src/graphics/Screen.cpp
+++ b/src/graphics/Screen.cpp
@@ -443,7 +443,7 @@ void Screen::handleSetOn(bool on, FrameCallback einkScreensaver)
             if (uiconfig.screen_brightness == 1)
                 digitalWrite(PIN_EINK_EN, HIGH);
 #elif defined(PCA_PIN_EINK_EN)
-            if (uiconfig.screen_brightness == 1)
+            if (uiconfig.screen_brightness > 0)
                 io.digitalWrite(PCA_PIN_EINK_EN, HIGH);
 #endif
 
diff --git a/src/graphics/draw/MenuHandler.cpp b/src/graphics/draw/MenuHandler.cpp
index 803fb87ea..10c20cbd6 100644
--- a/src/graphics/draw/MenuHandler.cpp
+++ b/src/graphics/draw/MenuHandler.cpp
@@ -515,7 +515,7 @@ void menuHandler::homeBaseMenu()
             }
             saveUIConfig();
 #elif defined(PCA_PIN_EINK_EN)
-            if (uiconfig.screen_brightness == 1) {
+            if (uiconfig.screen_brightness > 0) {
                 uiconfig.screen_brightness = 0;
                 io.digitalWrite(PCA_PIN_EINK_EN, LOW);
             } else {

From 0dfa11a90987bf0858791bf269d7d980c29e8f9d Mon Sep 17 00:00:00 2001
From: Jonathan Bennett 
Date: Wed, 29 Oct 2025 22:35:54 -0500
Subject: [PATCH 549/691] Add missed debug log line in RF95 Interface (#8490)

---
 src/mesh/RF95Interface.cpp | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/mesh/RF95Interface.cpp b/src/mesh/RF95Interface.cpp
index 0f32f3427..da0039d38 100644
--- a/src/mesh/RF95Interface.cpp
+++ b/src/mesh/RF95Interface.cpp
@@ -260,6 +260,7 @@ void RF95Interface::addReceiveMetadata(meshtastic_MeshPacket *mp)
 {
     mp->rx_snr = lora->getSNR();
     mp->rx_rssi = lround(lora->getRSSI());
+    LOG_DEBUG("Corrected frequency offset: %f", lora->getFrequencyError());
 }
 
 void RF95Interface::setStandby()

From 756efa7f00cb4bed498a7f46cdbf998f20a082be Mon Sep 17 00:00:00 2001
From: Jonathan Bennett 
Date: Thu, 30 Oct 2025 06:23:11 -0500
Subject: [PATCH 550/691] Thinknode M5 ADC_MULTIPLIER to actually hit 100%
 charge (#8489)

---
 variants/esp32s3/ELECROW-ThinkNode-M5/variant.h | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/variants/esp32s3/ELECROW-ThinkNode-M5/variant.h b/variants/esp32s3/ELECROW-ThinkNode-M5/variant.h
index a55808170..129b398e9 100644
--- a/variants/esp32s3/ELECROW-ThinkNode-M5/variant.h
+++ b/variants/esp32s3/ELECROW-ThinkNode-M5/variant.h
@@ -14,6 +14,8 @@
 #define BATTERY_PIN 8
 #define ADC_CHANNEL ADC1_GPIO8_CHANNEL
 
+#define ADC_MULTIPLIER 2.11 // 2.0 + 10% for correction of display undervoltage.
+
 #define PIN_BUZZER 9
 
 // Buttons

From c145be8e05b863af78750668cd6aa4ce73e39d62 Mon Sep 17 00:00:00 2001
From: Ixitxachitl 
Date: Fri, 31 Oct 2025 03:20:29 -0700
Subject: [PATCH 551/691] Refactor emote dimensions to 16x16 pixels (#8493)

Updated the dimensions of various emotes in emotes.h from 30x30 or 25x25 to 16x16 pixels for consistency and optimization. Added new emotes including heart_smile, Heart_eyes, and others, all with the same 16x16 size. This change improves memory usage and aligns with the design specifications for smaller emotes.
---
 src/graphics/emotes.cpp | 335 +++++++++++++++++-----------------------
 src/graphics/emotes.h   | 146 +++++++++++------
 2 files changed, 243 insertions(+), 238 deletions(-)

diff --git a/src/graphics/emotes.cpp b/src/graphics/emotes.cpp
index a0227d365..bed2b7b7c 100644
--- a/src/graphics/emotes.cpp
+++ b/src/graphics/emotes.cpp
@@ -18,6 +18,8 @@ const Emote emotes[] = {
     {"\U0001F642", Slightly_Smiling, Slightly_Smiling_width, Slightly_Smiling_height}, // 🙂 Slightly Smiling Face
     {"\U0001F609", Winking_Face, Winking_Face_width, Winking_Face_height},             // 😉 Winking Face
     {"\U0001F601", Grinning_Smiling_Eyes, Grinning_Smiling_Eyes_width, Grinning_Smiling_Eyes_height}, // 😁 Grinning Smiling Eyes
+    {"\U0001F60D", Heart_eyes, Heart_eyes_width, Heart_eyes_height},                                  // 😍 Heart Eyes
+    {"\U0001F970", heart_smile, heart_smile_width, heart_smile_height}, // 🥰 Smiling Face with Hearts
 
     // --- Question/Alert ---
     {"\u2753", question, question_width, question_height}, // ❓ Question Mark
@@ -30,11 +32,15 @@ const Emote emotes[] = {
     {"\U0001F605", haha, haha_width, haha_height},                                              // 😅 Smiling with Sweat
     {"\U0001F604", Grinning_SmilingEyes2, Grinning_SmilingEyes2_width,
      Grinning_SmilingEyes2_height}, // 😄 Grinning Face with Smiling Eyes
+    {"\U0001F62D", Loudly_Crying_Face, Loudly_Crying_Face_width, Loudly_Crying_Face_height}, // 😭 Loudly Crying Face
 
     // --- Gestures and People ---
-    {"\U0001F44B", wave_icon, wave_icon_width, wave_icon_height}, // 👋 Waving Hand
-    {"\U0001F920", cowboy, cowboy_width, cowboy_height},          // 🤠 Cowboy Hat Face
-    {"\U0001F3A7", deadmau5, deadmau5_width, deadmau5_height},    // 🎧 Headphones
+    {"\U0001F44B", wave_icon, wave_icon_width, wave_icon_height},             // 👋 Waving Hand
+    {"\u270C\uFE0F", peace_sign, peace_sign_width, peace_sign_height},        // ✌️ Victory Hand
+    {"\U0001F596", vulcan_salute, vulcan_salute_width, vulcan_salute_height}, // 🖖 Vulcan Salute
+    {"\U0001F64F", Praying, Praying_width, Praying_height},                   // 🙏 Praying Hands
+    {"\U0001F920", cowboy, cowboy_width, cowboy_height},                      // 🤠 Cowboy Hat Face
+    {"\U0001F3A7", deadmau5, deadmau5_width, deadmau5_height},                // 🎧 Headphones
 
     // --- Weather ---
     {"\u2600", sun, sun_width, sun_height},              // ☀ Sun (without variation selector)
@@ -45,8 +51,12 @@ const Emote emotes[] = {
 
     // --- Misc Faces ---
     {"\U0001F608", devil, devil_width, devil_height}, // 😈 Smiling Face with Horns
+    {"\U0001F921", clown, clown_width, clown_height}, // 🤡 Clown Face
+    {"\U0001F916", robo, robo_width, robo_height},    // 🤖 Robot Face
 
     // --- Hearts (Multiple Unicode Aliases) ---
+    {"\u2665", heart, heart_width, heart_height},       // ♥ Black Heart Suit
+    {"\u2665\uFE0F", heart, heart_width, heart_height}, // ♥️ Black Heart Suit (emoji presentation)
     {"\u2764\uFE0F", heart, heart_width, heart_height}, // ❤️ Red Heart
     {"\U0001F9E1", heart, heart_width, heart_height},   // 🧡 Orange Heart
     {"\U00002763", heart, heart_width, heart_height},   // ❣ Heart Exclamation
@@ -57,223 +67,166 @@ const Emote emotes[] = {
     {"\U0001F498", heart, heart_width, heart_height},   // 💘 Heart with Arrow
 
     // --- Objects ---
-    {"\U0001F4A9", poo, poo_width, poo_height},                  // 💩 Pile of Poo
-    {"\U0001F514", bell_icon, bell_icon_width, bell_icon_height} // 🔔 Bell
+    {"\U0001F4A9", poo, poo_width, poo_height},                   // 💩 Pile of Poo
+    {"\U0001F514", bell_icon, bell_icon_width, bell_icon_height}, // 🔔 Bell
+    {"\U0001F36A", cookie, cookie_width, cookie_height},          // 🍪 Cookie
+    {"\U0001F525", Fire, Fire_width, Fire_height},                // 🔥 Fire
+    {"\u2728", Sparkles, Sparkles_width, Sparkles_height},        // ✨ Sparkles
+    {"\U0001F573\uFE0F", hole, hole_width, hole_height},          // 🕳️ Hole
+    {"\U0001F3B3", bowling, bowling_width, bowling_height}        // 🎳 Bowling
 #endif
 };
 
 const int numEmotes = sizeof(emotes) / sizeof(emotes[0]);
 
 #ifndef EXCLUDE_EMOJI
-const unsigned char thumbup[] PROGMEM = {
-    0x00, 0x1C, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x80, 0x09, 0x00, 0x00,
-    0xC0, 0x08, 0x00, 0x00, 0x40, 0x08, 0x00, 0x00, 0x20, 0x04, 0x00, 0x00, 0x10, 0x04, 0x00, 0x00, 0x18, 0x02, 0x00, 0x00,
-    0x0C, 0xCE, 0x7F, 0x00, 0x04, 0x20, 0x80, 0x00, 0x02, 0x20, 0x80, 0x00, 0x02, 0x60, 0xC0, 0x00, 0x01, 0xF8, 0xFF, 0x01,
-    0x01, 0x08, 0x00, 0x01, 0x01, 0x08, 0x00, 0x01, 0x01, 0xF8, 0xFF, 0x00, 0x01, 0x10, 0x80, 0x00, 0x01, 0x18, 0x80, 0x00,
-    0x02, 0x30, 0xC0, 0x00, 0x06, 0xE0, 0x3F, 0x00, 0x0C, 0x20, 0x30, 0x00, 0x38, 0x20, 0x10, 0x00, 0xE0, 0xCF, 0x1F, 0x00,
-};
+const unsigned char thumbup[] PROGMEM = {0x00, 0x03, 0x80, 0x04, 0x80, 0x04, 0x40, 0x04, 0x20, 0x02, 0x18,
+                                         0x02, 0x06, 0x3F, 0x06, 0x40, 0x06, 0x70, 0x06, 0x40, 0x06, 0x70,
+                                         0x06, 0x40, 0x06, 0x30, 0x08, 0x20, 0xF0, 0x1F, 0x00, 0x00};
 
-const unsigned char thumbdown[] PROGMEM = {
-    0xE0, 0xCF, 0x1F, 0x00, 0x38, 0x20, 0x10, 0x00, 0x0C, 0x20, 0x30, 0x00, 0x06, 0xE0, 0x3F, 0x00, 0x02, 0x30, 0xC0, 0x00,
-    0x01, 0x18, 0x80, 0x00, 0x01, 0x10, 0x80, 0x00, 0x01, 0xF8, 0xFF, 0x00, 0x01, 0x08, 0x00, 0x01, 0x01, 0x08, 0x00, 0x01,
-    0x01, 0xF8, 0xFF, 0x01, 0x02, 0x60, 0xC0, 0x00, 0x02, 0x20, 0x80, 0x00, 0x04, 0x20, 0x80, 0x00, 0x0C, 0xCE, 0x7F, 0x00,
-    0x18, 0x02, 0x00, 0x00, 0x10, 0x04, 0x00, 0x00, 0x20, 0x04, 0x00, 0x00, 0x40, 0x08, 0x00, 0x00, 0xC0, 0x08, 0x00, 0x00,
-    0x80, 0x09, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x00,
-};
+const unsigned char thumbdown[] PROGMEM = {0xF0, 0x1F, 0x08, 0x20, 0x06, 0x30, 0x06, 0x40, 0x06, 0x70, 0x06,
+                                           0x40, 0x06, 0x70, 0x06, 0x40, 0x06, 0x3F, 0x18, 0x02, 0x20, 0x02,
+                                           0x40, 0x04, 0x80, 0x04, 0x80, 0x04, 0x00, 0x03, 0x00, 0x00};
 
-const unsigned char Smiling_Eyes[] PROGMEM = {
-    0x00, 0xf8, 0x03, 0xc0, 0x00, 0xfe, 0x0f, 0xc0, 0x80, 0xff, 0x3f, 0xc0, 0xc0, 0xff, 0xff, 0xc0, 0xe0, 0xff, 0xff, 0xc1,
-    0xf0, 0xff, 0xff, 0xc3, 0xf8, 0xff, 0xff, 0xc7, 0xf8, 0xff, 0xff, 0xcf, 0xfc, 0xff, 0xff, 0xcf, 0xfc, 0xff, 0xff, 0xcf,
-    0x7e, 0xf8, 0xc3, 0xdf, 0x3e, 0xf0, 0x81, 0xdf, 0xbf, 0xf7, 0xbd, 0xff, 0xff, 0xff, 0xff, 0xff, 0x9f, 0xff, 0x3f, 0xff,
-    0x6f, 0xff, 0xdf, 0xfe, 0x6f, 0xff, 0xdf, 0xfe, 0x9f, 0xff, 0x3f, 0xff, 0xfe, 0xff, 0xff, 0xdf, 0x7e, 0xff, 0xdf, 0xdf,
-    0x7c, 0xff, 0xdf, 0xcf, 0xfc, 0xfe, 0xef, 0xcf, 0xf8, 0xf9, 0xf7, 0xc7, 0xf8, 0x03, 0xf8, 0xc7, 0xf0, 0xff, 0xff, 0xc3,
-    0xe0, 0xff, 0xff, 0xc1, 0xc0, 0xff, 0xff, 0xc0, 0x80, 0xff, 0x3f, 0xc0, 0x00, 0xfe, 0x0f, 0xc0, 0x00, 0xf8, 0x07, 0xc0};
+const unsigned char Smiling_Eyes[] PROGMEM = {0x00, 0x00, 0xE0, 0x07, 0x18, 0x18, 0x04, 0x20, 0x24, 0x24, 0x52,
+                                              0x4A, 0x02, 0x40, 0x02, 0x40, 0x22, 0x44, 0x22, 0x44, 0xC2, 0x43,
+                                              0x04, 0x20, 0x04, 0x20, 0x18, 0x18, 0xE0, 0x07, 0x00, 0x00};
 
-const unsigned char Grinning[] PROGMEM = {
-    0x00, 0xf8, 0x03, 0xc0, 0x00, 0xfe, 0x0f, 0xc0, 0x80, 0xff, 0x3f, 0xc0, 0xc0, 0xff, 0xff, 0xc0, 0xe0, 0xff, 0xff, 0xc1,
-    0xf0, 0xff, 0xff, 0xc3, 0xf8, 0xff, 0xff, 0xc7, 0xf8, 0xff, 0xff, 0xcf, 0xfc, 0xf9, 0xf3, 0xcf, 0xfc, 0xf0, 0xe1, 0xcf,
-    0xfe, 0xf0, 0xe1, 0xdf, 0xfe, 0xf0, 0xe1, 0xdf, 0xff, 0xf0, 0xe1, 0xff, 0xff, 0xf9, 0xf3, 0xff, 0xff, 0xff, 0xff, 0xff,
-    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 0x00, 0x80, 0xff, 0xbe, 0xff, 0xbf, 0xdf, 0x7e, 0x00, 0xc0, 0xdf,
-    0x7c, 0x00, 0xc0, 0xcf, 0xfc, 0x00, 0xe0, 0xcf, 0xf8, 0x01, 0xf0, 0xc7, 0xf8, 0x03, 0xf8, 0xc7, 0xf0, 0xff, 0xff, 0xc3,
-    0xe0, 0xff, 0xff, 0xc1, 0xc0, 0xff, 0xff, 0xc0, 0x80, 0xff, 0x3f, 0xc0, 0x00, 0xfe, 0x0f, 0xc0, 0x00, 0xf8, 0x03, 0xc0};
+const unsigned char Grinning[] PROGMEM = {0x00, 0x00, 0xE0, 0x07, 0x18, 0x18, 0x04, 0x20, 0x44, 0x22, 0x42,
+                                          0x42, 0x02, 0x40, 0x02, 0x40, 0xF2, 0x4F, 0x12, 0x48, 0x22, 0x44,
+                                          0xC4, 0x23, 0x04, 0x20, 0x18, 0x18, 0xE0, 0x07, 0x00, 0x00};
 
-const unsigned char Slightly_Smiling[] PROGMEM = {
-    0x00, 0xf8, 0x03, 0xc0, 0x00, 0xfe, 0x0f, 0xc0, 0x80, 0xff, 0x3f, 0xc0, 0xc0, 0xff, 0xff, 0xc0, 0xe0, 0xff, 0xff, 0xc1,
-    0xf0, 0xff, 0xff, 0xc3, 0xf8, 0xff, 0xff, 0xc7, 0xf8, 0xff, 0xff, 0xcf, 0xfc, 0xf9, 0xf3, 0xcf, 0xfc, 0xf0, 0xe1, 0xcf,
-    0xfe, 0xf0, 0xe1, 0xdf, 0xfe, 0xf0, 0xe1, 0xdf, 0xff, 0xf0, 0xe1, 0xff, 0xff, 0xf9, 0xf3, 0xff, 0xff, 0xff, 0xff, 0xff,
-    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xdf, 0x7e, 0xff, 0xdf, 0xdf,
-    0x7c, 0xff, 0xdf, 0xcf, 0xfc, 0xfe, 0xef, 0xcf, 0xf8, 0xf9, 0xf7, 0xc7, 0xf8, 0x03, 0xf8, 0xc7, 0xf0, 0xff, 0xff, 0xc3,
-    0xe0, 0xff, 0xff, 0xc1, 0xc0, 0xff, 0xff, 0xc0, 0x80, 0xff, 0x3f, 0xc0, 0x00, 0xfe, 0x0f, 0xc0, 0x00, 0xf8, 0x03, 0xc0};
+const unsigned char Slightly_Smiling[] PROGMEM = {0x00, 0x00, 0xE0, 0x07, 0x18, 0x18, 0x04, 0x20, 0x44, 0x22, 0x42,
+                                                  0x42, 0x02, 0x40, 0x02, 0x40, 0x12, 0x48, 0x12, 0x48, 0x22, 0x44,
+                                                  0xC4, 0x23, 0x04, 0x20, 0x18, 0x18, 0xE0, 0x07, 0x00, 0x00};
 
-const unsigned char Winking_Face[] PROGMEM = {
-    0x00, 0xf8, 0x03, 0xc0, 0x00, 0xfe, 0x0f, 0xc0, 0x80, 0xff, 0x3f, 0xc0, 0xc0, 0xff, 0xff, 0xc0, 0xe0, 0xff, 0xff, 0xc1,
-    0xf0, 0xf0, 0xff, 0xc3, 0x78, 0xef, 0xc3, 0xc7, 0xb8, 0xdf, 0xbd, 0xcf, 0xfc, 0xf9, 0x7f, 0xcf, 0xfc, 0xf0, 0xff, 0xcf,
-    0xfe, 0xf0, 0xc3, 0xdf, 0xfe, 0xf0, 0x81, 0xdf, 0xff, 0xf0, 0xbf, 0xff, 0xff, 0xf9, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
-    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xdf, 0x7e, 0xff, 0xdf, 0xdf,
-    0x7c, 0xff, 0xdf, 0xcf, 0xfc, 0xfe, 0xef, 0xcf, 0xf8, 0xf9, 0xf7, 0xc7, 0xf8, 0x03, 0xf8, 0xc7, 0xf0, 0xff, 0xff, 0xc3,
-    0xe0, 0xff, 0xff, 0xc1, 0xc0, 0xff, 0xff, 0xc0, 0x80, 0xff, 0x3f, 0xc0, 0x00, 0xfe, 0x0f, 0xc0, 0x00, 0xf8, 0x07, 0xc0};
+const unsigned char Winking_Face[] PROGMEM = {0x00, 0x00, 0xE0, 0x07, 0x18, 0x18, 0x04, 0x20, 0x44, 0x20, 0x42,
+                                              0x46, 0x02, 0x40, 0x02, 0x40, 0x12, 0x48, 0x12, 0x48, 0x22, 0x44,
+                                              0xC4, 0x23, 0x04, 0x20, 0x18, 0x18, 0xE0, 0x07, 0x00, 0x00};
 
-const unsigned char Grinning_Smiling_Eyes[] PROGMEM = {
-    0x00, 0xf8, 0x03, 0xc0, 0x00, 0xfe, 0x0f, 0xc0, 0x80, 0xff, 0x3f, 0xc0, 0xc0, 0xff, 0xff, 0xc0, 0xe0, 0xff, 0xff, 0xc1,
-    0xf0, 0xff, 0xff, 0xc3, 0xf8, 0xff, 0xff, 0xc7, 0xf8, 0xff, 0xff, 0xcf, 0xfc, 0xf8, 0xe3, 0xcf, 0x7c, 0xf7, 0xdd, 0xcf,
-    0xbe, 0xef, 0xbe, 0xdf, 0xbe, 0xef, 0xbe, 0xdf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
-    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x1f, 0x00, 0x00, 0xff, 0x5e, 0x55, 0x55, 0xdf, 0x5e, 0x55, 0x55, 0xdf,
-    0x3c, 0x00, 0x80, 0xcf, 0x7c, 0x55, 0xd5, 0xcf, 0xf8, 0x54, 0xe5, 0xc7, 0xf8, 0x03, 0xf8, 0xc7, 0xf0, 0xff, 0xff, 0xc3,
-    0xe0, 0xff, 0xff, 0xc1, 0xc0, 0xff, 0xff, 0xc0, 0x80, 0xff, 0x3f, 0xc0, 0x00, 0xfe, 0x0f, 0xc0, 0x00, 0xf8, 0x03, 0xc0};
+const unsigned char Grinning_Smiling_Eyes[] PROGMEM = {0x00, 0x00, 0xE0, 0x07, 0x18, 0x18, 0x04, 0x20, 0x24, 0x24, 0x52,
+                                                       0x4A, 0x02, 0x40, 0xFA, 0x5F, 0x0A, 0x50, 0x0A, 0x50, 0x12, 0x48,
+                                                       0x24, 0x24, 0xC4, 0x23, 0x18, 0x18, 0xE0, 0x07, 0x00, 0x00};
 
-const unsigned char question[] PROGMEM = {
-    0x00, 0x00, 0x00, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x80, 0xFF, 0x01, 0x00, 0xC0, 0xFF, 0x07, 0x00, 0xE0, 0xFF, 0x07, 0x00,
-    0xE0, 0xC3, 0x0F, 0x00, 0xF0, 0x81, 0x0F, 0x00, 0xF0, 0x01, 0x0F, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x80, 0x0F, 0x00,
-    0x00, 0xC0, 0x0F, 0x00, 0x00, 0xE0, 0x07, 0x00, 0x00, 0xF0, 0x03, 0x00, 0x00, 0xF8, 0x01, 0x00, 0x00, 0x7C, 0x00, 0x00,
-    0x00, 0x3C, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-    0x00, 0x3E, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-};
+const unsigned char heart_smile[] PROGMEM = {0x00, 0x00, 0x6C, 0x07, 0x7C, 0x18, 0x7C, 0x20, 0x38, 0x24, 0x52,
+                                             0x0A, 0x02, 0xD8, 0x02, 0xF8, 0x22, 0xFC, 0x20, 0x74, 0xDB, 0x23,
+                                             0x1F, 0x00, 0x1F, 0x20, 0x0E, 0x18, 0xE4, 0x07, 0x00, 0x00};
 
-const unsigned char bang[] PROGMEM = {
-    0xFF, 0x0F, 0xFC, 0x3F, 0xFF, 0x0F, 0xFC, 0x3F, 0xFF, 0x0F, 0xFC, 0x3F, 0xFF, 0x07, 0xF8, 0x3F, 0xFF, 0x07, 0xF8, 0x3F,
-    0xFE, 0x07, 0xF8, 0x1F, 0xFE, 0x07, 0xF8, 0x1F, 0xFE, 0x07, 0xF8, 0x1F, 0xFE, 0x07, 0xF8, 0x1F, 0xFE, 0x07, 0xF8, 0x1F,
-    0xFE, 0x03, 0xF0, 0x1F, 0xFE, 0x03, 0xF0, 0x1F, 0xFC, 0x03, 0xF0, 0x0F, 0xFC, 0x03, 0xF0, 0x0F, 0xFC, 0x03, 0xF0, 0x0F,
-    0xFC, 0x03, 0xF0, 0x0F, 0xFC, 0x03, 0xF0, 0x0F, 0xFC, 0x03, 0xF0, 0x0F, 0xFC, 0x01, 0xE0, 0x0F, 0xFC, 0x01, 0xE0, 0x0F,
-    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x00, 0xC0, 0x03, 0xFC, 0x03, 0xF0, 0x0F, 0xFE, 0x03, 0xF0, 0x1F,
-    0xFE, 0x07, 0xF8, 0x1F, 0xFE, 0x07, 0xF8, 0x1F, 0xFE, 0x07, 0xF8, 0x1F, 0xFC, 0x03, 0xF0, 0x0F, 0xF8, 0x01, 0xE0, 0x07,
-};
+const unsigned char Heart_eyes[] PROGMEM = {0x00, 0x00, 0xE0, 0x07, 0x18, 0x18, 0x04, 0x20, 0x54, 0x2A, 0xFA,
+                                            0x5F, 0x72, 0x4E, 0x22, 0x44, 0x02, 0x40, 0x12, 0x48, 0x12, 0x48,
+                                            0x24, 0x24, 0xC4, 0x23, 0x18, 0x18, 0xE0, 0x07, 0x00, 0x00};
 
-const unsigned char haha[] PROGMEM = {
-    0x00, 0xf8, 0x03, 0xc0, 0x00, 0xfe, 0x0f, 0xc0, 0x80, 0xff, 0x3f, 0xc0, 0xc0, 0xff, 0x7f, 0xc0, 0xe0, 0xf9, 0xf3, 0xc0,
-    0xf0, 0xfe, 0xef, 0xc1, 0x38, 0xff, 0x9f, 0xc3, 0xd8, 0xff, 0x7f, 0xc3, 0xfc, 0xf8, 0xe3, 0xc7, 0x7c, 0xf7, 0xdd, 0xcf,
-    0xbe, 0xef, 0xbe, 0xcf, 0xfe, 0xff, 0xff, 0xcf, 0xef, 0xff, 0xff, 0xde, 0xe7, 0xff, 0xff, 0xdc, 0xeb, 0xff, 0xff, 0xda,
-    0xed, 0xff, 0xff, 0xd6, 0xee, 0xff, 0xff, 0xce, 0x36, 0x00, 0x80, 0xcd, 0xb8, 0xff, 0xbf, 0xc3, 0x7e, 0x00, 0xc0, 0xdf,
-    0x7c, 0x00, 0xc0, 0xcf, 0xfc, 0x00, 0xe0, 0xcf, 0xf8, 0x01, 0xf0, 0xc7, 0xf8, 0x03, 0xf8, 0xc7, 0xf0, 0xff, 0xff, 0xc3,
-    0xe0, 0xff, 0xff, 0xc1, 0xc0, 0xff, 0xff, 0xc0, 0x80, 0xff, 0x3f, 0xc0, 0x00, 0xfe, 0x0f, 0xc0, 0x00, 0xf8, 0x03, 0xc0};
+const unsigned char question[] PROGMEM = {0xE0, 0x07, 0x10, 0x08, 0x08, 0x10, 0x88, 0x11, 0x48, 0x12, 0x48,
+                                          0x12, 0x48, 0x12, 0x30, 0x11, 0x80, 0x08, 0x40, 0x04, 0x40, 0x02,
+                                          0xC0, 0x03, 0x00, 0x00, 0xC0, 0x03, 0x40, 0x02, 0x80, 0x01};
 
-const unsigned char ROFL[] PROGMEM = {
-    0x00, 0x00, 0x00, 0xc0, 0x00, 0xfc, 0x07, 0xc0, 0x00, 0xff, 0x1f, 0xc0, 0x80, 0xff, 0x7f, 0xc0, 0xc0, 0xff, 0xff, 0xc0,
-    0xe0, 0x9f, 0xff, 0xc1, 0xf0, 0x9f, 0xff, 0xc0, 0xf8, 0x9f, 0x7f, 0xcb, 0xf8, 0x9f, 0xbf, 0xcb, 0xfc, 0x9f, 0xdf, 0xdb,
-    0xfc, 0x1f, 0x08, 0xdc, 0xfe, 0x1f, 0xf8, 0xfe, 0xfe, 0xff, 0xff, 0xfe, 0x1e, 0xf0, 0x7f, 0xfe, 0x1e, 0xf0, 0xbf, 0xfe,
-    0xfe, 0xf3, 0xdf, 0xfe, 0xfe, 0xf3, 0x6f, 0xfe, 0xfe, 0xf3, 0x37, 0xfe, 0xfe, 0xeb, 0x1b, 0xfe, 0xfc, 0xef, 0x0d, 0xde,
-    0xfc, 0xe7, 0x06, 0xcf, 0xf8, 0x6b, 0x83, 0xcf, 0xf8, 0x0d, 0xc0, 0xc7, 0xf0, 0xed, 0xff, 0xc7, 0xe0, 0xee, 0xff, 0xc3,
-    0xc0, 0xee, 0xff, 0xc1, 0x80, 0xee, 0xff, 0xc0, 0x00, 0xe6, 0x3f, 0xc0, 0x00, 0xf0, 0x0f, 0xc0, 0x00, 0x00, 0x00, 0xc0};
+const unsigned char bang[] PROGMEM = {0x30, 0x0C, 0x48, 0x12, 0x48, 0x12, 0x48, 0x12, 0x48, 0x12, 0x48,
+                                      0x12, 0x48, 0x12, 0x48, 0x12, 0x48, 0x12, 0x48, 0x12, 0x30, 0x0C,
+                                      0x00, 0x00, 0x30, 0x0C, 0x48, 0x12, 0x30, 0x0C, 0x00, 0x00};
 
-const unsigned char Smiling_Closed_Eyes[] PROGMEM = {
-    0x00, 0xf8, 0x03, 0xc0, 0x00, 0xfe, 0x0f, 0xc0, 0x80, 0xff, 0x3f, 0xc0, 0xc0, 0xff, 0xff, 0xc0, 0xe0, 0xff, 0xff, 0xc1,
-    0xf0, 0xff, 0xff, 0xc3, 0xf8, 0xff, 0xff, 0xc7, 0xf8, 0xff, 0xff, 0xcf, 0x7c, 0xfe, 0xcf, 0xcf, 0xfc, 0xfc, 0xe7, 0xcf,
-    0xfe, 0xf9, 0xf3, 0xdf, 0xfe, 0xf3, 0xf9, 0xdf, 0xff, 0xf9, 0xf3, 0xff, 0xff, 0xfc, 0xe7, 0xff, 0x7f, 0xfe, 0xcf, 0xff,
-    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 0x00, 0x80, 0xff, 0xbe, 0xff, 0xbf, 0xdf, 0x7e, 0x00, 0xc0, 0xdf,
-    0x7c, 0x00, 0xc0, 0xcf, 0xfc, 0x00, 0xe0, 0xcf, 0xf8, 0x01, 0xf0, 0xc7, 0xf8, 0x03, 0xf8, 0xc7, 0xf0, 0xff, 0xff, 0xc3,
-    0xe0, 0xff, 0xff, 0xc1, 0xc0, 0xff, 0xff, 0xc0, 0x80, 0xff, 0x3f, 0xc0, 0x00, 0xfe, 0x0f, 0xc0, 0x00, 0xf8, 0x03, 0xc0};
+const unsigned char haha[] PROGMEM = {0x00, 0x00, 0xE0, 0x07, 0x18, 0x18, 0x04, 0x20, 0x24, 0x24, 0x52,
+                                      0x4A, 0x0A, 0x50, 0x0E, 0x70, 0xF2, 0x4F, 0x12, 0x48, 0x32, 0x44,
+                                      0xC4, 0x23, 0x04, 0x20, 0x18, 0x18, 0xE0, 0x07, 0x00, 0x00};
 
-const unsigned char Grinning_SmilingEyes2[] PROGMEM = {
-    0x00, 0xf8, 0x03, 0xc0, 0x00, 0xfe, 0x0f, 0xc0, 0x80, 0xff, 0x3f, 0xc0, 0xc0, 0xff, 0x7f, 0xc0, 0xe0, 0xff, 0xff, 0xc0,
-    0xf0, 0xff, 0xff, 0xc1, 0xf8, 0xff, 0xff, 0xc3, 0xf8, 0xff, 0xff, 0xc3, 0xfc, 0xf8, 0xe3, 0xc7, 0x7c, 0xf7, 0xdd, 0xc7,
-    0xbe, 0xef, 0xbe, 0xcf, 0xfe, 0xff, 0xff, 0xcf, 0xff, 0xff, 0xff, 0xdf, 0xff, 0xff, 0xff, 0xdf, 0xff, 0xff, 0xff, 0xdf,
-    0xff, 0xff, 0xff, 0xdf, 0xff, 0xff, 0xff, 0xdf, 0x3f, 0x00, 0x80, 0xdf, 0xbe, 0xff, 0xbf, 0xcf, 0x7e, 0x00, 0xc0, 0xcf,
-    0x7c, 0x00, 0xc0, 0xc7, 0xfc, 0x00, 0xe0, 0xc7, 0xf8, 0x01, 0xf0, 0xc3, 0xf8, 0x03, 0xf8, 0xc3, 0xf0, 0xff, 0xff, 0xc1,
-    0xe0, 0xff, 0xff, 0xc0, 0xc0, 0xff, 0x7f, 0xc0, 0x80, 0xff, 0x3f, 0xc0, 0x00, 0xfe, 0x0f, 0xc0, 0x00, 0xf8, 0x03, 0xc0};
+const unsigned char ROFL[] PROGMEM = {0x00, 0x00, 0xE0, 0x07, 0x18, 0x18, 0x84, 0x21, 0x84, 0x20, 0x02,
+                                      0x4C, 0x02, 0x4A, 0x1A, 0x49, 0x8A, 0x48, 0x42, 0x48, 0x22, 0x44,
+                                      0xE4, 0x23, 0x04, 0x20, 0x18, 0x18, 0xE0, 0x07, 0x00, 0x00};
 
-const unsigned char wave_icon[] PROGMEM = {
-    0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x7f, 0xc0, 0x00, 0x00, 0xc0, 0xc1, 0x00, 0x00, 0x00, 0xc7,
-    0x00, 0x00, 0x1e, 0xcc, 0x00, 0x00, 0x30, 0xc8, 0x00, 0x00, 0x60, 0xd8, 0x00, 0x08, 0xc0, 0xd0, 0x00, 0x1a, 0x81, 0xd1,
-    0x00, 0x36, 0x03, 0xd3, 0x80, 0x6d, 0x06, 0xd2, 0x00, 0xdb, 0x0c, 0xc2, 0x80, 0xb6, 0x1d, 0xc0, 0x80, 0x6d, 0x1f, 0xc0,
-    0x00, 0xdb, 0x3f, 0xc0, 0x00, 0xf6, 0x7f, 0xc0, 0x00, 0xfc, 0x7f, 0xc0, 0x08, 0xf8, 0x7f, 0xc0, 0x48, 0xf0, 0x7f, 0xc0,
-    0x48, 0xe0, 0x7f, 0xc0, 0xc8, 0xc0, 0x3f, 0xc0, 0x98, 0x81, 0x1f, 0xc0, 0x10, 0x03, 0x00, 0xc0, 0x30, 0x0e, 0x00, 0xc0,
-    0x20, 0x38, 0x00, 0xc0, 0xe0, 0x00, 0x00, 0xc0, 0x80, 0x07, 0x00, 0xc0, 0x00, 0x1e, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0};
+const unsigned char Smiling_Closed_Eyes[] PROGMEM = {0x00, 0x00, 0xE0, 0x07, 0x18, 0x18, 0x04, 0x20, 0x24, 0x24, 0x42,
+                                                     0x42, 0x22, 0x44, 0x02, 0x40, 0xF2, 0x4F, 0x12, 0x48, 0x22, 0x44,
+                                                     0xC4, 0x23, 0x04, 0x20, 0x18, 0x18, 0xE0, 0x07, 0x00, 0x00};
 
-const unsigned char cowboy[] PROGMEM = {
-    0x00, 0x0c, 0x0c, 0xc0, 0x00, 0x02, 0x10, 0xc0, 0x00, 0x01, 0x20, 0xc0, 0xbc, 0x00, 0x40, 0xcf, 0xc2, 0x01, 0xe0, 0xd0,
-    0x01, 0x01, 0x20, 0xe0, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0,
-    0xc1, 0x3f, 0xff, 0xe0, 0xe1, 0xff, 0xff, 0xe1, 0xf2, 0xf3, 0xf3, 0xd3, 0xf4, 0xf1, 0xe3, 0xcb, 0xfc, 0xf1, 0xe3, 0xc7,
-    0xf8, 0xf1, 0xe3, 0xc7, 0xf8, 0xf1, 0xe3, 0xc7, 0xf8, 0xfb, 0xf7, 0xc7, 0xf8, 0xff, 0xff, 0xc7, 0xf8, 0xff, 0xff, 0xc7,
-    0x70, 0xf8, 0x8f, 0xc3, 0x70, 0x03, 0xb0, 0xc3, 0x70, 0xfe, 0xbf, 0xc3, 0x60, 0x00, 0x80, 0xc1, 0xc0, 0x00, 0xc0, 0xc0,
-    0x80, 0x01, 0x60, 0xc0, 0x00, 0x07, 0x38, 0xc0, 0x00, 0xfe, 0x1f, 0xc0, 0x00, 0xf0, 0x03, 0xc0, 0x00, 0x00, 0x00, 0xc0};
+const unsigned char Grinning_SmilingEyes2[] PROGMEM = {0x00, 0x00, 0xE0, 0x07, 0x18, 0x18, 0x04, 0x20, 0x24, 0x24, 0x52,
+                                                       0x4A, 0x02, 0x40, 0x02, 0x40, 0xF2, 0x4F, 0x12, 0x48, 0x22, 0x44,
+                                                       0xC4, 0x23, 0x04, 0x20, 0x18, 0x18, 0xE0, 0x07, 0x00, 0x00};
 
-const unsigned char deadmau5[] PROGMEM = {
-    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0x07, 0x00,
-    0x00, 0xFC, 0x03, 0x00, 0x00, 0xFF, 0x3F, 0x00, 0x80, 0xFF, 0x0F, 0x00, 0xC0, 0xFF, 0xFF, 0x00, 0xE0, 0xFF, 0x3F, 0x00,
-    0xE0, 0xFF, 0xFF, 0x01, 0xF0, 0xFF, 0x7F, 0x00, 0xF0, 0xFF, 0xFF, 0x03, 0xF8, 0xFF, 0xFF, 0x00, 0xF0, 0xFF, 0xFF, 0x07,
-    0xFC, 0xFF, 0xFF, 0x00, 0xF0, 0xFF, 0xFF, 0x0F, 0xFC, 0xFF, 0xFF, 0x00, 0xF0, 0xFF, 0xFF, 0x0F, 0xFE, 0xFF, 0xFF, 0x00,
-    0xF0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xF0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xE0, 0xFF, 0x3F, 0xFC,
-    0x0F, 0xFF, 0x7F, 0x00, 0xC0, 0xFF, 0x1F, 0xF8, 0x0F, 0xFC, 0x3F, 0x00, 0x80, 0xFF, 0x0F, 0xF8, 0x1F, 0xFC, 0x1F, 0x00,
-    0x00, 0xFF, 0x0F, 0xFC, 0x3F, 0xFC, 0x0F, 0x00, 0x00, 0xF8, 0x1F, 0xFF, 0xFF, 0xFE, 0x01, 0x00, 0x00, 0x00, 0xFC, 0xFF,
-    0xFF, 0x07, 0x00, 0x00, 0x00, 0x00, 0xFC, 0xFF, 0xFF, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00,
-    0x00, 0x00, 0x04, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00,
-    0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x00, 0xC0, 0x00, 0x00, 0x00,
-    0x00, 0x00, 0x80, 0x07, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-};
+const unsigned char Loudly_Crying_Face[] PROGMEM = {0x00, 0x00, 0xE0, 0x07, 0x18, 0x18, 0x04, 0x20, 0x34, 0x2C, 0x4A,
+                                                    0x52, 0x12, 0x48, 0x12, 0x48, 0x92, 0x49, 0x52, 0x4A, 0x52, 0x4A,
+                                                    0x54, 0x2A, 0x94, 0x29, 0x18, 0x18, 0xF0, 0x0F, 0x00, 0x00};
 
-const unsigned char sun[] PROGMEM = {
-    0x00, 0xC0, 0x00, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x30, 0xC0, 0x00, 0x03,
-    0x70, 0x00, 0x80, 0x03, 0xF0, 0x00, 0xC0, 0x03, 0xF0, 0xF8, 0xC7, 0x03, 0xE0, 0xFC, 0xCF, 0x01, 0x00, 0xFE, 0x1F, 0x00,
-    0x00, 0xFF, 0x3F, 0x00, 0x80, 0xFF, 0x7F, 0x00, 0x80, 0xFF, 0x7F, 0x00, 0x8E, 0xFF, 0x7F, 0x1C, 0x9F, 0xFF, 0x7F, 0x3E,
-    0x9F, 0xFF, 0x7F, 0x3E, 0x8E, 0xFF, 0x7F, 0x1C, 0x80, 0xFF, 0x7F, 0x00, 0x80, 0xFF, 0x7F, 0x00, 0x00, 0xFF, 0x3F, 0x00,
-    0x00, 0xFE, 0x1F, 0x00, 0x00, 0xFC, 0x0F, 0x00, 0xC0, 0xF9, 0xE7, 0x00, 0xE0, 0x01, 0xE0, 0x01, 0xF0, 0x01, 0xE0, 0x03,
-    0xF0, 0xC0, 0xC0, 0x03, 0x00, 0xE0, 0x01, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0xC0, 0x00, 0x00,
-};
+const unsigned char wave_icon[] PROGMEM = {0x00, 0x00, 0xC0, 0x18, 0x30, 0x21, 0x48, 0x5A, 0x94, 0x64, 0x24,
+                                           0x25, 0x4A, 0x24, 0x12, 0x44, 0x22, 0x44, 0x04, 0x40, 0x08, 0x40,
+                                           0x12, 0x40, 0x22, 0x20, 0xC4, 0x10, 0x18, 0x0F, 0x00, 0x00};
 
-const unsigned char rain[] PROGMEM = {
-    0xC0, 0x0F, 0xC0, 0x00, 0x40, 0x00, 0x80, 0x00, 0x20, 0x00, 0x80, 0x00, 0x20, 0x00, 0x80, 0x03, 0x38, 0x00,
-    0x00, 0x0E, 0x0C, 0x00, 0x00, 0x18, 0x02, 0x00, 0x00, 0x10, 0x03, 0x00, 0x00, 0x30, 0x01, 0x00, 0x00, 0x20,
-    0x01, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00, 0x20, 0x03, 0x00, 0x00, 0x30, 0x02, 0x00,
-    0x00, 0x10, 0x06, 0x00, 0x00, 0x08, 0xFC, 0xFF, 0xFF, 0x07, 0xF0, 0xFF, 0xFF, 0x01, 0x80, 0x00, 0x01, 0x00,
-    0xC0, 0xC0, 0x81, 0x03, 0xA0, 0x60, 0xC1, 0x03, 0x90, 0x20, 0x41, 0x01, 0xF0, 0xE0, 0xC0, 0x01, 0x60, 0x4C,
-    0x98, 0x00, 0x00, 0x0E, 0x1C, 0x00, 0x00, 0x0B, 0x12, 0x00, 0x00, 0x09, 0x1A, 0x00, 0x00, 0x06, 0x0E, 0x00,
-};
+const unsigned char cowboy[] PROGMEM = {0x70, 0x0E, 0x8F, 0xF1, 0x11, 0x88, 0x21, 0x84, 0xC2, 0x43, 0x1E,
+                                        0x78, 0xE2, 0x47, 0x42, 0x42, 0x12, 0x48, 0x12, 0x48, 0x22, 0x44,
+                                        0xC4, 0x23, 0x04, 0x20, 0x18, 0x18, 0xE0, 0x07, 0x00, 0x00};
 
-const unsigned char cloud[] PROGMEM = {
-    0x00, 0x80, 0x07, 0x00, 0x00, 0xE0, 0x1F, 0x00, 0x00, 0x70, 0x30, 0x00, 0x00, 0x10, 0x60, 0x00, 0x80, 0x1F, 0x40, 0x00,
-    0xC0, 0x0F, 0xC0, 0x00, 0xC0, 0x00, 0x80, 0x00, 0x60, 0x00, 0x80, 0x00, 0x20, 0x00, 0x80, 0x00, 0x20, 0x00, 0x80, 0x01,
-    0x20, 0x00, 0x00, 0x07, 0x38, 0x00, 0x00, 0x0C, 0x0C, 0x00, 0x00, 0x08, 0x06, 0x00, 0x00, 0x18, 0x02, 0x00, 0x00, 0x10,
-    0x02, 0x00, 0x00, 0x30, 0x03, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00, 0x20,
-    0x01, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00, 0x30, 0x03, 0x00, 0x00, 0x10,
-    0x02, 0x00, 0x00, 0x10, 0x06, 0x00, 0x00, 0x18, 0x0C, 0x00, 0x00, 0x0C, 0xFC, 0xFF, 0xFF, 0x07, 0xF0, 0xFF, 0xFF, 0x03,
-};
+const unsigned char deadmau5[] PROGMEM = {0x00, 0x00, 0xE0, 0x07, 0x18, 0x18, 0xE4, 0x27, 0x12, 0x48, 0x0A,
+                                          0x50, 0x0E, 0x70, 0x11, 0x88, 0x19, 0x98, 0x19, 0x98, 0x19, 0x98,
+                                          0x19, 0x98, 0x19, 0x98, 0x11, 0x88, 0x0E, 0x70, 0x00, 0x00};
 
-const unsigned char fog[] PROGMEM = {
-    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-    0x00, 0x00, 0x00, 0x00, 0x78, 0x00, 0x3C, 0x00, 0xFE, 0x01, 0xFF, 0x00, 0x87, 0xC7, 0xC3, 0x01, 0x03, 0xFE, 0x80, 0x01,
-    0x00, 0x38, 0x00, 0x00, 0xFC, 0x00, 0x7E, 0x00, 0xFF, 0x83, 0xFF, 0x01, 0x03, 0xFF, 0x81, 0x01, 0x00, 0x7C, 0x00, 0x00,
-    0xF8, 0x00, 0x3E, 0x00, 0xFE, 0x01, 0xFF, 0x00, 0x87, 0xC7, 0xC3, 0x01, 0x03, 0xFE, 0x80, 0x01, 0x00, 0x38, 0x00, 0x00,
-    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-};
+const unsigned char sun[] PROGMEM = {0x00, 0x00, 0x80, 0x01, 0xEC, 0x37, 0xFC, 0x3F, 0xF8, 0x1F, 0xFC,
+                                     0x3F, 0xFE, 0x7F, 0xFC, 0x3F, 0xFC, 0x3F, 0xFE, 0x7F, 0xFC, 0x3F,
+                                     0xF8, 0x1F, 0xFC, 0x3F, 0xEC, 0x37, 0x80, 0x01, 0x00, 0x00};
 
-const unsigned char devil[] PROGMEM = {
-    0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00, 0xe0, 0x03, 0x00, 0x00, 0xf0, 0x0f, 0xfc, 0x0f, 0xfc,
-    0x3f, 0xff, 0x3f, 0xff, 0xfe, 0xff, 0xff, 0xdf, 0xfe, 0xff, 0xff, 0xdf, 0xfe, 0xff, 0xff, 0xdf, 0xfc, 0xff, 0xff, 0xcf,
-    0xfc, 0xff, 0xff, 0xcf, 0xf8, 0xff, 0xff, 0xc7, 0xf0, 0xff, 0xff, 0xc3, 0xf0, 0xff, 0xff, 0xc3, 0xf0, 0xf1, 0xe3, 0xc3,
-    0xf0, 0xe7, 0xf9, 0xc3, 0xf0, 0xe7, 0xf9, 0xc3, 0xf0, 0xe3, 0xf1, 0xc3, 0xf0, 0xe3, 0xf1, 0xc3, 0xf0, 0xe7, 0xf9, 0xc3,
-    0xf0, 0xff, 0xff, 0xc3, 0xe0, 0xfd, 0xef, 0xc1, 0xe0, 0xf3, 0xf3, 0xc1, 0xc0, 0x07, 0xf8, 0xc0, 0x80, 0x1f, 0x7e, 0xc0,
-    0x00, 0xff, 0x3f, 0xc0, 0x00, 0xfe, 0x0f, 0xc0, 0x00, 0xf8, 0x03, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0};
+const unsigned char rain[] PROGMEM = {0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x38, 0x1F, 0xFC, 0x3F, 0xFE,
+                                      0x7F, 0xFE, 0x7F, 0xFE, 0x7F, 0xFC, 0x3F, 0x00, 0x00, 0x48, 0x12,
+                                      0x48, 0x12, 0x24, 0x09, 0x24, 0x09, 0x00, 0x00, 0x00, 0x00};
 
-const unsigned char heart[] PROGMEM = {
-    0x00, 0x00, 0x00, 0x00, 0xC0, 0x03, 0xF0, 0x00, 0xF8, 0x0F, 0xFC, 0x07, 0xFC, 0x1F, 0x06, 0x0E, 0xFE, 0x3F, 0x03, 0x18,
-    0xFE, 0xFF, 0x7F, 0x10, 0xFF, 0xFF, 0xFF, 0x31, 0xFF, 0xFF, 0xFF, 0x33, 0xFF, 0xFF, 0xFF, 0x37, 0xFF, 0xFF, 0xFF, 0x37,
-    0xFF, 0xFF, 0xFF, 0x3F, 0xFF, 0xFF, 0xFF, 0x3F, 0xFF, 0xFF, 0xFF, 0x3F, 0xFE, 0xFF, 0xFF, 0x1F, 0xFE, 0xFF, 0xFF, 0x1F,
-    0xFC, 0xFF, 0xFF, 0x0F, 0xFC, 0xFF, 0xFF, 0x0F, 0xF8, 0xFF, 0xFF, 0x07, 0xF0, 0xFF, 0xFF, 0x03, 0xF0, 0xFF, 0xFF, 0x03,
-    0xE0, 0xFF, 0xFF, 0x01, 0xC0, 0xFF, 0xFF, 0x00, 0x80, 0xFF, 0x7F, 0x00, 0x00, 0xFF, 0x3F, 0x00, 0x00, 0xFE, 0x1F, 0x00,
-    0x00, 0xFC, 0x0F, 0x00, 0x00, 0xF8, 0x07, 0x00, 0x00, 0xF0, 0x03, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0x80, 0x00, 0x00,
-};
+const unsigned char cloud[] PROGMEM = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x38, 0x1F, 0xFC,
+                                       0x3F, 0xFE, 0x7F, 0xFE, 0x7F, 0xFE, 0x7F, 0xFC, 0x3F, 0xF8, 0x1F,
+                                       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
 
-const unsigned char poo[] PROGMEM = {
-    0x00, 0x1c, 0x00, 0xc0, 0x00, 0x7c, 0x00, 0xc0, 0x00, 0xfc, 0x00, 0xc0, 0x00, 0x7c, 0x03, 0xc0, 0x00, 0xbe, 0x03, 0xc0,
-    0x00, 0xdf, 0x0f, 0xc0, 0x80, 0xcf, 0x0f, 0xc0, 0xc0, 0xf1, 0x0f, 0xc0, 0x60, 0xfc, 0x0f, 0xc0, 0x30, 0xff, 0x07, 0xc0,
-    0x90, 0xff, 0x3b, 0xc0, 0xc0, 0xff, 0x7d, 0xc0, 0xf8, 0xff, 0xfc, 0xc0, 0xf8, 0x3f, 0xf0, 0xc0, 0x78, 0x88, 0xc0, 0xc0,
-    0x20, 0xe3, 0x18, 0xc0, 0x98, 0xe7, 0xbc, 0xc1, 0x9c, 0x64, 0xa4, 0xc3, 0x9e, 0x64, 0xa4, 0xc7, 0xbe, 0xe4, 0xa4, 0xc7,
-    0xbc, 0x27, 0xbc, 0xc7, 0x38, 0x03, 0xd9, 0xc3, 0x00, 0xf0, 0x63, 0xc0, 0xf8, 0xfc, 0x3f, 0xcf, 0xfc, 0xff, 0x87, 0xdf,
-    0xfe, 0xff, 0xe0, 0xdf, 0xfc, 0x1f, 0xfe, 0xdf, 0xf8, 0x07, 0xf8, 0xcf, 0xf0, 0x03, 0xe0, 0xc7, 0x00, 0x00, 0x00, 0xc0};
+const unsigned char fog[] PROGMEM = {0x00, 0x00, 0x00, 0x00, 0x88, 0x88, 0x54, 0x55, 0x22, 0x22, 0x00,
+                                     0x00, 0x44, 0x44, 0xAA, 0x2A, 0x11, 0x11, 0x00, 0x00, 0x88, 0x88,
+                                     0x54, 0x55, 0x22, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
 
-const unsigned char bell_icon[] PROGMEM = {
-    0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b11110000,
-    0b00000011, 0b00000000, 0b00000000, 0b11111100, 0b00001111, 0b00000000, 0b00000000, 0b00001111, 0b00111100, 0b00000000,
-    0b00000000, 0b00000011, 0b00110000, 0b00000000, 0b10000000, 0b00000001, 0b01100000, 0b00000000, 0b11000000, 0b00000000,
-    0b11000000, 0b00000000, 0b11000000, 0b00000000, 0b11000000, 0b00000000, 0b11000000, 0b00000000, 0b11000000, 0b00000000,
-    0b11000000, 0b00000000, 0b11000000, 0b00000000, 0b11000000, 0b00000000, 0b11000000, 0b00000000, 0b11000000, 0b00000000,
-    0b11000000, 0b00000000, 0b11000000, 0b00000000, 0b11000000, 0b00000000, 0b11000000, 0b00000000, 0b11000000, 0b00000000,
-    0b11000000, 0b00000000, 0b11000000, 0b00000000, 0b01000000, 0b00000000, 0b10000000, 0b00000000, 0b01100000, 0b00000000,
-    0b10000000, 0b00000001, 0b01110000, 0b00000000, 0b10000000, 0b00000011, 0b00110000, 0b00000000, 0b00000000, 0b00000011,
-    0b00011000, 0b00000000, 0b00000000, 0b00000110, 0b11110000, 0b11111111, 0b11111111, 0b00000011, 0b00000000, 0b00001100,
-    0b00001100, 0b00000000, 0b00000000, 0b00011000, 0b00000110, 0b00000000, 0b00000000, 0b11111000, 0b00000111, 0b00000000,
-    0b00000000, 0b11100000, 0b00000001, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000,
-    0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000};
+const unsigned char devil[] PROGMEM = {0x06, 0x60, 0xCA, 0x53, 0x32, 0x4C, 0x22, 0x44, 0x44, 0x22, 0x3A,
+                                       0x5C, 0x32, 0x4C, 0x52, 0x4A, 0x72, 0x4E, 0x02, 0x40, 0x22, 0x44,
+                                       0xC4, 0x23, 0x04, 0x20, 0x18, 0x18, 0xE0, 0x07, 0x00, 0x00};
+
+const unsigned char heart[] PROGMEM = {0x00, 0x00, 0x00, 0x00, 0x3C, 0x3C, 0x7E, 0x7E, 0xFE, 0x7F, 0xFE,
+                                       0x7F, 0xFE, 0x7F, 0xFE, 0x7F, 0xFC, 0x3F, 0xF8, 0x1F, 0xF8, 0x1F,
+                                       0xF0, 0x0F, 0xE0, 0x07, 0xC0, 0x03, 0x80, 0x01, 0x00, 0x00};
+
+const unsigned char poo[] PROGMEM = {0x00, 0x00, 0x80, 0x01, 0x40, 0x02, 0x20, 0x04, 0x10, 0x04, 0xF0,
+                                     0x08, 0x10, 0x10, 0x48, 0x12, 0x08, 0x18, 0xE8, 0x21, 0x1C, 0x40,
+                                     0x42, 0x42, 0x82, 0x41, 0x02, 0x30, 0xFC, 0x0F, 0x00, 0x00};
+
+const unsigned char bell_icon[] PROGMEM = {0x00, 0x00, 0x80, 0x01, 0x80, 0x01, 0xE0, 0x07, 0xF0, 0x0F, 0xF0,
+                                           0x0F, 0xF8, 0x1F, 0xF8, 0x1F, 0xF8, 0x1F, 0xF8, 0x1F, 0xFC, 0x3F,
+                                           0xFC, 0x3F, 0xFE, 0x7F, 0xFE, 0x7F, 0x80, 0x01, 0x00, 0x00};
+
+const unsigned char cookie[] PROGMEM = {0x00, 0x00, 0xE0, 0x07, 0x18, 0x18, 0x04, 0x20, 0x34, 0x22, 0x32,
+                                        0x40, 0x02, 0x58, 0x82, 0x5B, 0x92, 0x43, 0x82, 0x43, 0x02, 0x40,
+                                        0x64, 0x28, 0x64, 0x20, 0x18, 0x18, 0xE0, 0x07, 0x00, 0x00};
+
+const unsigned char Fire[] PROGMEM = {0x30, 0x00, 0xF0, 0x00, 0xF8, 0x03, 0xF8, 0x07, 0xFC, 0x1F, 0xFC,
+                                      0x1F, 0xFE, 0x3E, 0x7E, 0x3E, 0x3E, 0x7C, 0x1E, 0x78, 0x1E, 0x70,
+                                      0x1C, 0x70, 0x1C, 0x70, 0x38, 0x38, 0x30, 0x38, 0x60, 0x0C};
+
+const unsigned char peace_sign[] PROGMEM = {0xC0, 0x30, 0x40, 0x29, 0x40, 0x25, 0x40, 0x15, 0x40, 0x12, 0x38,
+                                            0x0A, 0x54, 0x68, 0x54, 0x58, 0x54, 0x44, 0x3C, 0x22, 0x04, 0x22,
+                                            0x04, 0x12, 0x08, 0x10, 0x10, 0x08, 0xE0, 0x07, 0x00, 0x00};
+
+const unsigned char Praying[] PROGMEM = {0x00, 0x00, 0x40, 0x02, 0xA0, 0x05, 0x90, 0x09, 0x90, 0x09, 0x90,
+                                         0x09, 0x98, 0x19, 0x94, 0x29, 0xA4, 0x25, 0xA4, 0x25, 0x84, 0x21,
+                                         0x84, 0x21, 0x86, 0x61, 0x4E, 0x72, 0x7F, 0x7E, 0x3F, 0xFC};
+
+const unsigned char Sparkles[] PROGMEM = {0x00, 0x00, 0x10, 0x00, 0x38, 0x04, 0x10, 0x04, 0x00, 0x0E, 0x00,
+                                          0x1F, 0x80, 0x3F, 0xE0, 0xFF, 0x80, 0x3F, 0x10, 0x1F, 0x10, 0x0E,
+                                          0x38, 0x04, 0xFE, 0x04, 0x38, 0x00, 0x10, 0x00, 0x10, 0x00};
+
+const unsigned char clown[] PROGMEM = {0x00, 0x00, 0xEE, 0x77, 0x1A, 0x58, 0x06, 0x60, 0x24, 0x24, 0x72,
+                                       0x4E, 0x22, 0x44, 0x82, 0x41, 0x82, 0x41, 0x1A, 0x58, 0xF2, 0x4F,
+                                       0x14, 0x28, 0xE4, 0x27, 0x18, 0x18, 0xE0, 0x07, 0x00, 0x00};
+
+const unsigned char robo[] PROGMEM = {0x80, 0x01, 0xC0, 0x03, 0x80, 0x01, 0xFC, 0x3F, 0x04, 0x20, 0x74,
+                                      0x2E, 0x52, 0x4A, 0x72, 0x4E, 0x02, 0x40, 0x02, 0x40, 0xA2, 0x4A,
+                                      0x52, 0x45, 0x04, 0x20, 0x04, 0x20, 0xFC, 0x3F, 0x00, 0x00};
+
+const unsigned char hole[] PROGMEM = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                                      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x0F, 0x3C, 0x3C,
+                                      0x06, 0x60, 0x0C, 0x30, 0xF0, 0x0F, 0x00, 0x00, 0x00, 0x00};
+
+const unsigned char bowling[] PROGMEM = {0x00, 0x38, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x28, 0x00,
+                                         0x38, 0x00, 0x28, 0x78, 0x44, 0x84, 0x82, 0x22, 0x83, 0x52, 0x83,
+                                         0x02, 0x83, 0x02, 0x45, 0x84, 0x44, 0x78, 0x38, 0x00, 0x00};
+
+const unsigned char vulcan_salute[] PROGMEM = {0x08, 0x02, 0x16, 0x0D, 0x15, 0x15, 0x15, 0x15, 0xA9, 0x12, 0x4A,
+                                               0x0A, 0x02, 0x38, 0x04, 0x48, 0x04, 0x44, 0x04, 0x22, 0x04, 0x22,
+                                               0x04, 0x12, 0x08, 0x10, 0x10, 0x08, 0xE0, 0x07, 0x00, 0x00};
 #endif
 
 } // namespace graphics
diff --git a/src/graphics/emotes.h b/src/graphics/emotes.h
index 30b164cbc..b1b2d16da 100644
--- a/src/graphics/emotes.h
+++ b/src/graphics/emotes.h
@@ -17,98 +17,150 @@ extern const int numEmotes;
 
 #ifndef EXCLUDE_EMOJI
 // === Emote Bitmaps ===
-#define thumbs_height 25
-#define thumbs_width 25
+#define thumbs_height 16
+#define thumbs_width 16
 extern const unsigned char thumbup[] PROGMEM;
 extern const unsigned char thumbdown[] PROGMEM;
 
-#define Smiling_Eyes_height 30
-#define Smiling_Eyes_width 30
+#define Smiling_Eyes_height 16
+#define Smiling_Eyes_width 16
 extern const unsigned char Smiling_Eyes[] PROGMEM;
 
-#define Grinning_height 30
-#define Grinning_width 30
+#define Grinning_height 16
+#define Grinning_width 16
 extern const unsigned char Grinning[] PROGMEM;
 
-#define Slightly_Smiling_height 30
-#define Slightly_Smiling_width 30
+#define Slightly_Smiling_height 16
+#define Slightly_Smiling_width 16
 extern const unsigned char Slightly_Smiling[] PROGMEM;
 
-#define Winking_Face_height 30
-#define Winking_Face_width 30
+#define Winking_Face_height 16
+#define Winking_Face_width 16
 extern const unsigned char Winking_Face[] PROGMEM;
 
-#define Grinning_Smiling_Eyes_height 30
-#define Grinning_Smiling_Eyes_width 30
+#define Grinning_Smiling_Eyes_height 16
+#define Grinning_Smiling_Eyes_width 16
 extern const unsigned char Grinning_Smiling_Eyes[] PROGMEM;
 
-#define question_height 25
-#define question_width 25
+#define heart_smile_height 16
+#define heart_smile_width 16
+extern const unsigned char heart_smile[] PROGMEM;
+
+#define Heart_eyes_height 16
+#define Heart_eyes_width 16
+extern const unsigned char Heart_eyes[] PROGMEM;
+
+#define question_height 16
+#define question_width 16
 extern const unsigned char question[] PROGMEM;
 
-#define bang_height 30
-#define bang_width 30
+#define bang_height 16
+#define bang_width 16
 extern const unsigned char bang[] PROGMEM;
 
-#define haha_height 30
-#define haha_width 30
+#define haha_height 16
+#define haha_width 16
 extern const unsigned char haha[] PROGMEM;
 
-#define ROFL_height 30
-#define ROFL_width 30
+#define ROFL_height 16
+#define ROFL_width 16
 extern const unsigned char ROFL[] PROGMEM;
 
-#define Smiling_Closed_Eyes_height 30
-#define Smiling_Closed_Eyes_width 30
+#define Smiling_Closed_Eyes_height 16
+#define Smiling_Closed_Eyes_width 16
 extern const unsigned char Smiling_Closed_Eyes[] PROGMEM;
 
-#define Grinning_SmilingEyes2_height 30
-#define Grinning_SmilingEyes2_width 30
+#define Grinning_SmilingEyes2_height 16
+#define Grinning_SmilingEyes2_width 16
 extern const unsigned char Grinning_SmilingEyes2[] PROGMEM;
 
-#define wave_icon_height 30
-#define wave_icon_width 30
+#define Loudly_Crying_Face_height 16
+#define Loudly_Crying_Face_width 16
+extern const unsigned char Loudly_Crying_Face[] PROGMEM;
+
+#define wave_icon_height 16
+#define wave_icon_width 16
 extern const unsigned char wave_icon[] PROGMEM;
 
-#define cowboy_height 30
-#define cowboy_width 30
+#define cowboy_height 16
+#define cowboy_width 16
 extern const unsigned char cowboy[] PROGMEM;
 
-#define deadmau5_height 30
-#define deadmau5_width 60
+#define deadmau5_height 16
+#define deadmau5_width 16
 extern const unsigned char deadmau5[] PROGMEM;
 
-#define sun_height 30
-#define sun_width 30
+#define sun_height 16
+#define sun_width 16
 extern const unsigned char sun[] PROGMEM;
 
-#define rain_height 30
-#define rain_width 30
+#define rain_height 16
+#define rain_width 16
 extern const unsigned char rain[] PROGMEM;
 
-#define cloud_height 30
-#define cloud_width 30
+#define cloud_height 16
+#define cloud_width 16
 extern const unsigned char cloud[] PROGMEM;
 
-#define fog_height 25
-#define fog_width 25
+#define fog_height 16
+#define fog_width 16
 extern const unsigned char fog[] PROGMEM;
 
-#define devil_height 30
-#define devil_width 30
+#define devil_height 16
+#define devil_width 16
 extern const unsigned char devil[] PROGMEM;
 
-#define heart_height 30
-#define heart_width 30
+#define heart_height 16
+#define heart_width 16
 extern const unsigned char heart[] PROGMEM;
 
-#define poo_height 30
-#define poo_width 30
+#define poo_height 16
+#define poo_width 16
 extern const unsigned char poo[] PROGMEM;
 
-#define bell_icon_width 30
-#define bell_icon_height 30
+#define bell_icon_width 16
+#define bell_icon_height 16
 extern const unsigned char bell_icon[] PROGMEM;
+
+#define cookie_width 16
+#define cookie_height 16
+extern const unsigned char cookie[] PROGMEM;
+
+#define Fire_width 16
+#define Fire_height 16
+extern const unsigned char Fire[] PROGMEM;
+
+#define peace_sign_width 16
+#define peace_sign_height 16
+extern const unsigned char peace_sign[] PROGMEM;
+
+#define Praying_width 16
+#define Praying_height 16
+extern const unsigned char Praying[] PROGMEM;
+
+#define Sparkles_width 16
+#define Sparkles_height 16
+extern const unsigned char Sparkles[] PROGMEM;
+
+#define clown_width 16
+#define clown_height 16
+extern const unsigned char clown[] PROGMEM;
+
+#define robo_width 16
+#define robo_height 16
+extern const unsigned char robo[] PROGMEM;
+
+#define hole_width 16
+#define hole_height 16
+extern const unsigned char hole[] PROGMEM;
+
+#define bowling_width 16
+#define bowling_height 16
+extern const unsigned char bowling[] PROGMEM;
+
+#define vulcan_salute_width 16
+#define vulcan_salute_height 16
+extern const unsigned char vulcan_salute[] PROGMEM;
 #endif // EXCLUDE_EMOJI
 
-} // namespace graphics
+} // namespace graphics
\ No newline at end of file

From de83b448f96196a7cbd837be8b54013449d58d6c Mon Sep 17 00:00:00 2001
From: Chloe Bethel 
Date: Fri, 31 Oct 2025 10:54:35 +0000
Subject: [PATCH 552/691] Force stdout to be line buffered - this fixes logs
 ending early if meshtasticd crashes (#8499)

---
 src/platform/portduino/PortduinoGlue.cpp | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/src/platform/portduino/PortduinoGlue.cpp b/src/platform/portduino/PortduinoGlue.cpp
index dbc90f9a4..cfa0b560a 100644
--- a/src/platform/portduino/PortduinoGlue.cpp
+++ b/src/platform/portduino/PortduinoGlue.cpp
@@ -37,6 +37,8 @@ bool yamlOnly = false;
 
 const char *argp_program_version = optstr(APP_VERSION);
 
+char stdoutBuffer[512];
+
 // FIXME - move setBluetoothEnable into a HALPlatform class
 void setBluetoothEnable(bool enable)
 {
@@ -154,6 +156,9 @@ void portduinoSetup()
     std::string gpioChipName = "gpiochip";
     portduino_config.displayPanel = no_screen;
 
+    // Force stdout to be line buffered
+    setvbuf(stdout, stdoutBuffer, _IOLBF, sizeof(stdoutBuffer));
+
     if (portduino_config.force_simradio == true) {
         portduino_config.lora_module = use_simradio;
     } else if (configPath != nullptr) {

From 4f817d69eb03fefd37b89995bf0ccd09433df155 Mon Sep 17 00:00:00 2001
From: Andrew Yong 
Date: Fri, 31 Oct 2025 18:55:07 +0800
Subject: [PATCH 553/691] fix(wio-e5): Fix LED state inversion (#8500)

Wio-E5 currently has LED appearing to be steadily on, due to incorrect LED_STATE_ON (it is actually briefly flashing off, but visually it is hard to perceive).

Wio-E5 has LED between GPIO PB5 and VCC, so LED_STATE_ON should be 0 for LED to blink correctly. With this commit, it is now flashing correctly.

Refer to schematics:

* [Wio-E5 Development Kit](https://files.seeedstudio.com/products/113990934/LoRa-E5%20Dev%20Board%20v1.0.pdf)
* [Wio-E5 mini](https://files.seeedstudio.com/products/113990939/LoRa-E5%20mini%20v1.0.pdf)

Signed-off-by: Andrew Yong 
---
 variants/stm32/wio-e5/variant.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/variants/stm32/wio-e5/variant.h b/variants/stm32/wio-e5/variant.h
index 6098b4ce6..a312b31bd 100644
--- a/variants/stm32/wio-e5/variant.h
+++ b/variants/stm32/wio-e5/variant.h
@@ -15,7 +15,7 @@ Do not expect a working Meshtastic device with this target.
 #define USE_STM32WLx
 
 #define LED_PIN PB5
-#define LED_STATE_ON 1
+#define LED_STATE_ON 0
 
 #define WIO_E5
 

From d00fda2f4d9cab15ff59f607246c093eb180bedb Mon Sep 17 00:00:00 2001
From: Jason P 
Date: Fri, 31 Oct 2025 05:55:56 -0500
Subject: [PATCH 554/691] Better implementation of
 ExternalNotificationModule::stopNow (#8492)

* Better implementation of ExternalNotificationModule::stopNow

* Label external states turning off

* Optimize original code to actually fix issues
---
 src/modules/ExternalNotificationModule.cpp | 31 +++++++++++-----------
 1 file changed, 15 insertions(+), 16 deletions(-)

diff --git a/src/modules/ExternalNotificationModule.cpp b/src/modules/ExternalNotificationModule.cpp
index 2b1730e9c..b047e04c2 100644
--- a/src/modules/ExternalNotificationModule.cpp
+++ b/src/modules/ExternalNotificationModule.cpp
@@ -94,22 +94,10 @@ int32_t ExternalNotificationModule::runOnce()
         // audioThread->isPlaying() also handles actually playing the RTTTL, needs to be called in loop
         isRtttlPlaying = isRtttlPlaying || audioThread->isPlaying();
 #endif
-        if ((nagCycleCutoff <= millis())) {
+        if ((nagCycleCutoff < millis()) && !isRtttlPlaying) {
             // Turn off external notification immediately when timeout is reached, regardless of song state
             nagCycleCutoff = UINT32_MAX;
-            LOG_INFO("Turning off external notification: ");
-            for (int i = 0; i < 3; i++) {
-                setExternalState(i, false);
-                externalTurnedOn[i] = 0;
-                LOG_INFO("%d ", i);
-            }
-#ifdef HAS_I2S
-            // GPIO0 is used as mclk for I2S audio and set to OUTPUT by the sound library
-            // T-Deck uses GPIO0 as trackball button, so restore the mode
-#if defined(T_DECK) || (defined(BUTTON_PIN) && BUTTON_PIN == 0)
-            pinMode(0, INPUT);
-#endif
-#endif
+            ExternalNotificationModule::stopNow();
             isNagging = false;
             return INT32_MAX; // save cycles till we're needed again
         }
@@ -317,22 +305,33 @@ bool ExternalNotificationModule::nagging()
 
 void ExternalNotificationModule::stopNow()
 {
+    LOG_INFO("Turning off external notification: ");
+    LOG_INFO("Stop RTTTL playback");
     rtttl::stop();
 #ifdef HAS_I2S
+    LOG_INFO("Stop audioThread playback");
     if (audioThread->isPlaying())
         audioThread->stop();
 #endif
-    nagCycleCutoff = 1; // small value
-    isNagging = false;
     // Turn off all outputs
+    LOG_INFO("Turning off setExternalStates: ");
     for (int i = 0; i < 3; i++) {
         setExternalState(i, false);
         externalTurnedOn[i] = 0;
+        LOG_INFO("%d ", i);
     }
     setIntervalFromNow(0);
 #ifdef T_WATCH_S3
     drv.stop();
 #endif
+
+#ifdef HAS_I2S
+    // GPIO0 is used as mclk for I2S audio and set to OUTPUT by the sound library
+    // T-Deck uses GPIO0 as trackball button, so restore the mode
+#if defined(T_DECK) || (defined(BUTTON_PIN) && BUTTON_PIN == 0)
+    pinMode(0, INPUT);
+#endif
+#endif
 }
 
 ExternalNotificationModule::ExternalNotificationModule()

From 16b1280804f7a85eb59fe4e5994337650c54b3a5 Mon Sep 17 00:00:00 2001
From: Erayd 
Date: Tue, 28 Oct 2025 00:05:59 +1300
Subject: [PATCH 555/691] Fix type to ensure correct alignment; saves 4B per
 entry (#8465)

---
 src/mesh/PacketCache.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/mesh/PacketCache.h b/src/mesh/PacketCache.h
index 81ad455da..85660922b 100644
--- a/src/mesh/PacketCache.h
+++ b/src/mesh/PacketCache.h
@@ -17,7 +17,7 @@ typedef struct PacketCacheEntry {
             uint8_t encrypted : 1;    // Payload is encrypted
             uint8_t has_metadata : 1; // Payload includes PacketCacheMetadata
             uint8_t : 6;              // Reserved for future use
-            uint16_t : 8;             // Reserved for future use
+            uint8_t : 8;              // Reserved for future use
         };
     };
 } PacketCacheEntry;

From 001654e90a81ef8500aefb57a37bb32ddb01b99b Mon Sep 17 00:00:00 2001
From: Marius Faber <45321033+mariusfaber98@users.noreply.github.com>
Date: Fri, 31 Oct 2025 23:38:37 +0100
Subject: [PATCH 556/691] Add basic LR1121 support for T-Beam S3, full support
 needs #4775 fixed (#8349)

---
 variants/esp32s3/tbeam-s3-core/rfswitch.h | 11 +++++++++++
 variants/esp32s3/tbeam-s3-core/variant.h  | 16 +++++++++++++++-
 2 files changed, 26 insertions(+), 1 deletion(-)
 create mode 100644 variants/esp32s3/tbeam-s3-core/rfswitch.h

diff --git a/variants/esp32s3/tbeam-s3-core/rfswitch.h b/variants/esp32s3/tbeam-s3-core/rfswitch.h
new file mode 100644
index 000000000..19080cec6
--- /dev/null
+++ b/variants/esp32s3/tbeam-s3-core/rfswitch.h
@@ -0,0 +1,11 @@
+#include "RadioLib.h"
+
+static const uint32_t rfswitch_dio_pins[] = {RADIOLIB_LR11X0_DIO5, RADIOLIB_LR11X0_DIO6, RADIOLIB_NC, RADIOLIB_NC, RADIOLIB_NC};
+
+static const Module::RfSwitchMode_t rfswitch_table[] = {
+    // mode                  DIO5  DIO6
+    {LR11x0::MODE_STBY, {LOW, LOW}},  {LR11x0::MODE_RX, {HIGH, LOW}},
+    {LR11x0::MODE_TX, {LOW, HIGH}},   {LR11x0::MODE_TX_HP, {LOW, HIGH}},
+    {LR11x0::MODE_TX_HF, {LOW, LOW}}, {LR11x0::MODE_GNSS, {LOW, LOW}},
+    {LR11x0::MODE_WIFI, {LOW, LOW}},  END_OF_MODE_TABLE,
+};
\ No newline at end of file
diff --git a/variants/esp32s3/tbeam-s3-core/variant.h b/variants/esp32s3/tbeam-s3-core/variant.h
index 40ba0307a..1d99fbf14 100644
--- a/variants/esp32s3/tbeam-s3-core/variant.h
+++ b/variants/esp32s3/tbeam-s3-core/variant.h
@@ -15,6 +15,7 @@
 // not found then probe for SX1262
 #define USE_SX1262
 #define USE_SX1268
+#define USE_LR1121
 
 #define LORA_DIO0 -1 // a No connect on the SX1262 module
 #define LORA_RESET 5
@@ -34,6 +35,19 @@
 // code)
 #endif
 
+// LR1121
+#ifdef USE_LR1121
+#define LR1121_IRQ_PIN 1
+#define LR1121_NRESET_PIN LORA_RESET
+#define LR1121_BUSY_PIN 4
+#define LR1121_SPI_NSS_PIN 10
+#define LR1121_SPI_SCK_PIN 12
+#define LR1121_SPI_MOSI_PIN 11
+#define LR1121_SPI_MISO_PIN 13
+#define LR11X0_DIO3_TCXO_VOLTAGE 3.0
+#define LR11X0_DIO_AS_RF_SWITCH
+#endif
+
 // Leave undefined to disable our PMU IRQ handler.  DO NOT ENABLE THIS because the pmuirq can cause sperious interrupts
 // and waking from light sleep
 // #define PMU_IRQ 40
@@ -64,4 +78,4 @@
 // has 32768 Hz crystal
 #define HAS_32768HZ 1
 
-#define USE_SH1106
+#define USE_SH1106
\ No newline at end of file

From 17324fa72544300e563e43f25d49fb6621eaf52d Mon Sep 17 00:00:00 2001
From: shortwavesurfer2009
 <116814522+shortwavesurfer2009@users.noreply.github.com>
Date: Fri, 31 Oct 2025 18:39:30 -0400
Subject: [PATCH 557/691] adjust battery curve (#8137)

Co-authored-by: HarukiToreda <116696711+HarukiToreda@users.noreply.github.com>
---
 variants/nrf52840/heltec_mesh_pocket/variant.h | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/variants/nrf52840/heltec_mesh_pocket/variant.h b/variants/nrf52840/heltec_mesh_pocket/variant.h
index 79f47bd0e..e765dab66 100644
--- a/variants/nrf52840/heltec_mesh_pocket/variant.h
+++ b/variants/nrf52840/heltec_mesh_pocket/variant.h
@@ -120,7 +120,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.6425F)
 
 #undef HAS_GPS
 #define HAS_GPS 0
@@ -129,4 +129,4 @@ No longer populated on PCB
 }
 #endif
 
-#endif
\ No newline at end of file
+#endif

From 7f78a624cd23ab4720300893df15e766f8c589bc Mon Sep 17 00:00:00 2001
From: pa0lin082 
Date: Fri, 31 Oct 2025 23:40:36 +0100
Subject: [PATCH 558/691] Add support for Bh1750 Light Sensor (#8376)

* regenerate protobuf with bh1750 TelemetrySensorType

* Added wollewald/BH1750_WE@^1.1.10 dependecy

* Added support for BH1750 during i2C detection

* Create new BH1750Sensor and added in EnvironmentTelemetry

* clean code

* Attempt to fix protobuf include

---------

Co-authored-by: Tom Fifield 
---
 platformio.ini                                |  2 +
 src/detect/ScanI2C.h                          |  3 +-
 src/detect/ScanI2CTwoWire.cpp                 | 20 ++++++-
 .../Telemetry/EnvironmentTelemetry.cpp        |  7 +++
 src/modules/Telemetry/Sensor/BH1750Sensor.cpp | 54 +++++++++++++++++++
 src/modules/Telemetry/Sensor/BH1750Sensor.h   | 21 ++++++++
 6 files changed, 105 insertions(+), 2 deletions(-)
 create mode 100644 src/modules/Telemetry/Sensor/BH1750Sensor.cpp
 create mode 100644 src/modules/Telemetry/Sensor/BH1750Sensor.h

diff --git a/platformio.ini b/platformio.ini
index 7c63ad7ad..fe2f5e28a 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -181,6 +181,8 @@ lib_deps =
 	dfrobot/DFRobot_BMM150@1.0.0
 	# renovate: datasource=custom.pio depName=Adafruit_TSL2561 packageName=adafruit/library/Adafruit TSL2561
 	adafruit/Adafruit TSL2561@1.1.2
+	# renovate: datasource=custom.pio depName=BH1750_WE packageName=wollewald/BH1750_WE@^1.1.10
+	wollewald/BH1750_WE@^1.1.10
 
 ; (not included in native / portduino)
 [environmental_extra]
diff --git a/src/detect/ScanI2C.h b/src/detect/ScanI2C.h
index 2e602338c..e7bdf58c5 100644
--- a/src/detect/ScanI2C.h
+++ b/src/detect/ScanI2C.h
@@ -82,7 +82,8 @@ class ScanI2C
         BHI260AP,
         BMM150,
         TSL2561,
-        DRV2605
+        DRV2605,
+        BH1750
     } DeviceType;
 
     // typedef uint8_t DeviceAddress;
diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp
index da2a57fee..59d93d74f 100644
--- a/src/detect/ScanI2CTwoWire.cpp
+++ b/src/detect/ScanI2CTwoWire.cpp
@@ -485,7 +485,25 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
                 SCAN_SIMPLE_CASE(LTR390UV_ADDR, LTR390UV, "LTR390UV", (uint8_t)addr.address);
                 SCAN_SIMPLE_CASE(PCT2075_ADDR, PCT2075, "PCT2075", (uint8_t)addr.address);
                 SCAN_SIMPLE_CASE(CST328_ADDR, CST328, "CST328", (uint8_t)addr.address);
-                SCAN_SIMPLE_CASE(LTR553ALS_ADDR, LTR553ALS, "LTR553ALS", (uint8_t)addr.address);
+            case LTR553ALS_ADDR:
+                registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x86), 1); // Part ID register
+                if (registerValue == 0x92) {                                                       // LTR553ALS Part ID
+                    type = LTR553ALS;
+                    logFoundDevice("LTR553ALS", (uint8_t)addr.address);
+                } else {
+                    // Test BH1750 - send power on command
+                    i2cBus->beginTransmission(addr.address);
+                    i2cBus->write(0x01); // Power On command
+                    uint8_t bh1750_error = i2cBus->endTransmission();
+                    if (bh1750_error == 0) {
+                        type = BH1750;
+                        logFoundDevice("BH1750", (uint8_t)addr.address);
+                    } else {
+                        LOG_INFO("Device found at address 0x%x was not able to be enumerated", (uint8_t)addr.address);
+                    }
+                }
+                break;
+
                 SCAN_SIMPLE_CASE(BHI260AP_ADDR, BHI260AP, "BHI260AP", (uint8_t)addr.address);
                 SCAN_SIMPLE_CASE(SCD4X_ADDR, SCD4X, "SCD4X", (uint8_t)addr.address);
                 SCAN_SIMPLE_CASE(BMM150_ADDR, BMM150, "BMM150", (uint8_t)addr.address);
diff --git a/src/modules/Telemetry/EnvironmentTelemetry.cpp b/src/modules/Telemetry/EnvironmentTelemetry.cpp
index 2337af808..a923ab457 100644
--- a/src/modules/Telemetry/EnvironmentTelemetry.cpp
+++ b/src/modules/Telemetry/EnvironmentTelemetry.cpp
@@ -134,6 +134,10 @@ extern void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const c
 #include "Sensor/TSL2561Sensor.h"
 #endif
 
+#if __has_include()
+#include "Sensor/BH1750Sensor.h"
+#endif
+
 #define FAILED_STATE_SENSOR_READ_MULTIPLIER 10
 #define DISPLAY_RECEIVEID_MEASUREMENTS_ON_SCREEN true
 
@@ -262,6 +266,9 @@ void EnvironmentTelemetryModule::i2cScanFinished(ScanI2C *i2cScanner)
 #if __has_include()
     addSensor(i2cScanner, ScanI2C::DeviceType::NAU7802);
 #endif
+#if __has_include()
+    addSensor(i2cScanner, ScanI2C::DeviceType::BH1750);
+#endif
 
 #endif
 }
diff --git a/src/modules/Telemetry/Sensor/BH1750Sensor.cpp b/src/modules/Telemetry/Sensor/BH1750Sensor.cpp
new file mode 100644
index 000000000..b8790dcd5
--- /dev/null
+++ b/src/modules/Telemetry/Sensor/BH1750Sensor.cpp
@@ -0,0 +1,54 @@
+#include "configuration.h"
+
+#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && __has_include()
+
+#include "../mesh/generated/meshtastic/telemetry.pb.h"
+#include "BH1750Sensor.h"
+#include "TelemetrySensor.h"
+#include 
+
+#ifndef BH1750_SENSOR_MODE
+#define BH1750_SENSOR_MODE BH1750Mode::CHM
+#endif
+
+BH1750Sensor::BH1750Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_BH1750, "BH1750") {}
+
+bool BH1750Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev)
+{
+    LOG_INFO("Init sensor: %s with mode %d", sensorName, BH1750_SENSOR_MODE);
+
+    bh1750 = BH1750_WE(bus, dev->address.address);
+    status = bh1750.init();
+    if (!status) {
+        return status;
+    }
+
+    bh1750.setMode(BH1750_SENSOR_MODE);
+
+    initI2CSensor();
+    return status;
+}
+
+bool BH1750Sensor::getMetrics(meshtastic_Telemetry *measurement)
+{
+
+    /* An OTH and OTH_2 measurement takes ~120 ms. I suggest to wait
+    140 ms to be on the safe side.
+    An OTL measurement takes about 16 ms. I suggest to wait 20 ms
+    to be on the safe side. */
+    if (BH1750_SENSOR_MODE == BH1750Mode::OTH || BH1750_SENSOR_MODE == BH1750Mode::OTH_2) {
+        bh1750.setMode(BH1750_SENSOR_MODE);
+        delay(140); // wait for measurement to be completed
+    } else if (BH1750_SENSOR_MODE == BH1750Mode::OTL) {
+        bh1750.setMode(BH1750_SENSOR_MODE);
+        delay(20);
+    }
+
+    measurement->variant.environment_metrics.has_lux = true;
+    float lightIntensity = bh1750.getLux();
+
+    measurement->variant.environment_metrics.lux = lightIntensity;
+    return true;
+}
+
+#endif
diff --git a/src/modules/Telemetry/Sensor/BH1750Sensor.h b/src/modules/Telemetry/Sensor/BH1750Sensor.h
new file mode 100644
index 000000000..d9a4ded95
--- /dev/null
+++ b/src/modules/Telemetry/Sensor/BH1750Sensor.h
@@ -0,0 +1,21 @@
+#pragma once
+#include "configuration.h"
+
+#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && __has_include()
+
+#include "../mesh/generated/meshtastic/telemetry.pb.h"
+#include "TelemetrySensor.h"
+#include 
+
+class BH1750Sensor : public TelemetrySensor
+{
+  private:
+    BH1750_WE bh1750;
+
+  public:
+    BH1750Sensor();
+    virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
+    virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override;
+};
+
+#endif

From c46abe125c2c99728558f4ae4f4857507bdc75e4 Mon Sep 17 00:00:00 2001
From: Jonathan Bennett 
Date: Sat, 1 Nov 2025 12:45:11 -0500
Subject: [PATCH 559/691] Skip setting up Lora GPIO lines when using a ch341
 radio on native (#8506)

---
 src/platform/portduino/PortduinoGlue.cpp | 11 ++++++++---
 1 file changed, 8 insertions(+), 3 deletions(-)

diff --git a/src/platform/portduino/PortduinoGlue.cpp b/src/platform/portduino/PortduinoGlue.cpp
index dbc90f9a4..53dece150 100644
--- a/src/platform/portduino/PortduinoGlue.cpp
+++ b/src/platform/portduino/PortduinoGlue.cpp
@@ -393,11 +393,17 @@ void portduinoSetup()
     // Need to bind all the configured GPIO pins so they're not simulated
     // TODO: If one of these fails, we should log and terminate
     for (auto i : portduino_config.all_pins) {
-        if (i->enabled)
+        // In the case of a ch341 Lora device, we don't want to touch the system GPIO lines for Lora
+        // Those GPIO are handled in our usermode driver instead.
+        if (i->config_section == "Lora" && portduino_config.lora_spi_dev == "ch341") {
+            continue;
+        }
+        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
@@ -423,8 +429,7 @@ int initGPIOPin(int pinNum, const std::string gpioChipName, int line)
 {
 #ifdef PORTDUINO_LINUX_HARDWARE
     std::string gpio_name = "GPIO" + std::to_string(pinNum);
-    std::cout << gpio_name;
-    printf("\n");
+    std::cout << "Initializing " << gpio_name << " on chip " << gpioChipName << std::endl;
     try {
         GPIOPin *csPin;
         csPin = new LinuxGPIOPin(pinNum, gpioChipName.c_str(), line, gpio_name.c_str());

From bca0e1abde1076ed24d08e5e113fc8789bbc754b Mon Sep 17 00:00:00 2001
From: GUVWAF <78759985+GUVWAF@users.noreply.github.com>
Date: Sat, 1 Nov 2025 22:48:04 +0100
Subject: [PATCH 560/691] Fix boot on RP2040 by excluding new FreeRTOS task
 (#8508)

---
 src/input/InputBroker.cpp | 6 +++---
 src/input/InputBroker.h   | 4 ++--
 src/main.cpp              | 2 +-
 3 files changed, 6 insertions(+), 6 deletions(-)

diff --git a/src/input/InputBroker.cpp b/src/input/InputBroker.cpp
index c588a9a0f..39fc665c5 100644
--- a/src/input/InputBroker.cpp
+++ b/src/input/InputBroker.cpp
@@ -5,7 +5,7 @@ InputBroker *inputBroker = nullptr;
 
 InputBroker::InputBroker()
 {
-#ifdef HAS_FREE_RTOS
+#if defined(HAS_FREE_RTOS) && !defined(ARCH_RP2040)
     inputEventQueue = xQueueCreate(5, sizeof(InputEvent));
     pollSoonQueue = xQueueCreate(5, sizeof(InputPollable *));
     xTaskCreate(pollSoonWorker, "input-pollSoon", 2 * 1024, this, 10, &pollSoonTask);
@@ -17,7 +17,7 @@ void InputBroker::registerSource(Observable *source)
     this->inputEventObserver.observe(source);
 }
 
-#ifdef HAS_FREE_RTOS
+#if defined(HAS_FREE_RTOS) && !defined(ARCH_RP2040)
 void InputBroker::requestPollSoon(InputPollable *pollable)
 {
     if (xPortInIsrContext() == pdTRUE) {
@@ -52,7 +52,7 @@ int InputBroker::handleInputEvent(const InputEvent *event)
     return 0;
 }
 
-#ifdef HAS_FREE_RTOS
+#if defined(HAS_FREE_RTOS) && !defined(ARCH_RP2040)
 void InputBroker::pollSoonWorker(void *p)
 {
     InputBroker *instance = (InputBroker *)p;
diff --git a/src/input/InputBroker.h b/src/input/InputBroker.h
index 192bd7e8b..36328ca64 100644
--- a/src/input/InputBroker.h
+++ b/src/input/InputBroker.h
@@ -59,7 +59,7 @@ class InputBroker : public Observable
     InputBroker();
     void registerSource(Observable *source);
     void injectInputEvent(const InputEvent *event) { handleInputEvent(event); }
-#ifdef HAS_FREE_RTOS
+#if defined(HAS_FREE_RTOS) && !defined(ARCH_RP2040)
     void requestPollSoon(InputPollable *pollable);
     void queueInputEvent(const InputEvent *event);
     void processInputEventQueue();
@@ -69,7 +69,7 @@ class InputBroker : public Observable
     int handleInputEvent(const InputEvent *event);
 
   private:
-#ifdef HAS_FREE_RTOS
+#if defined(HAS_FREE_RTOS) && !defined(ARCH_RP2040)
     QueueHandle_t inputEventQueue;
     QueueHandle_t pollSoonQueue;
     TaskHandle_t pollSoonTask;
diff --git a/src/main.cpp b/src/main.cpp
index 689e80e35..c65482292 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -1595,7 +1595,7 @@ void loop()
 #endif
 
     service->loop();
-#if !MESHTASTIC_EXCLUDE_INPUTBROKER && defined(HAS_FREE_RTOS)
+#if !MESHTASTIC_EXCLUDE_INPUTBROKER && defined(HAS_FREE_RTOS) && !defined(ARCH_RP2040)
     if (inputBroker)
         inputBroker->processInputEventQueue();
 #endif

From 718fd118b095557ad0242fa0162d51489d51f6ab Mon Sep 17 00:00:00 2001
From: Xavier horwood <39760456+Xavierhorwood@users.noreply.github.com>
Date: Sun, 2 Nov 2025 09:25:59 +1100
Subject: [PATCH 561/691] Add IPv6 Support for esp32 (#6866)

* Update Default.h

* Update NodeDB.cpp

* Update WiFiAPClient.cpp

* Update userPrefs.jsonc

* set ipv6 to off by default

* Trunk fix

---------

Co-authored-by: Tom Fifield 
---
 src/mesh/Default.h             |  1 +
 src/mesh/NodeDB.cpp            |  6 ++++++
 src/mesh/wifi/WiFiAPClient.cpp | 33 +++++++++++++++++++++++++++++++--
 userPrefs.jsonc                |  1 +
 4 files changed, 39 insertions(+), 2 deletions(-)

diff --git a/src/mesh/Default.h b/src/mesh/Default.h
index 34289ccb6..218d8d0fb 100644
--- a/src/mesh/Default.h
+++ b/src/mesh/Default.h
@@ -29,6 +29,7 @@
 #else
 #define default_ringtone_nag_secs 15
 #endif
+#define default_network_ipv6_enabled false
 
 #define default_mqtt_address "mqtt.meshtastic.org"
 #define default_mqtt_username "meshdev"
diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp
index dec8411fe..df9aece0a 100644
--- a/src/mesh/NodeDB.cpp
+++ b/src/mesh/NodeDB.cpp
@@ -718,6 +718,12 @@ void NodeDB::installDefaultConfig(bool preserveKey = false)
     strncpy(config.network.wifi_psk, USERPREFS_NETWORK_WIFI_PSK, sizeof(config.network.wifi_psk));
 #endif
 
+#if defined(USERPREFS_NETWORK_IPV6_ENABLED)
+    config.network.ipv6_enabled = USERPREFS_NETWORK_IPV6_ENABLED;
+#else
+    config.network.ipv6_enabled = default_network_ipv6_enabled;
+#endif
+
 #ifdef DISPLAY_FLIP_SCREEN
     config.display.flip_screen = true;
 #endif
diff --git a/src/mesh/wifi/WiFiAPClient.cpp b/src/mesh/wifi/WiFiAPClient.cpp
index 18f67706a..45944872e 100644
--- a/src/mesh/wifi/WiFiAPClient.cpp
+++ b/src/mesh/wifi/WiFiAPClient.cpp
@@ -334,6 +334,23 @@ bool initWifi()
 }
 
 #ifdef ARCH_ESP32
+#if ESP_ARDUINO_VERSION <= ESP_ARDUINO_VERSION_VAL(3, 0, 0)
+// Most of the next 12 lines of code are adapted from espressif/arduino-esp32
+// Licensed under the GNU Lesser General Public License v2.1
+// https://github.com/espressif/arduino-esp32/blob/1f038677eb2eaf5e9ca6b6074486803c15468bed/libraries/WiFi/src/WiFiSTA.cpp#L755
+esp_netif_t *get_esp_interface_netif(esp_interface_t interface);
+IPv6Address GlobalIPv6()
+{
+    esp_ip6_addr_t addr;
+    if (WiFiGenericClass::getMode() == WIFI_MODE_NULL) {
+        return IPv6Address();
+    }
+    if (esp_netif_get_ip6_global(get_esp_interface_netif(ESP_IF_WIFI_STA), &addr)) {
+        return IPv6Address();
+    }
+    return IPv6Address(addr.addr);
+}
+#endif
 // Called by the Espressif SDK to
 static void WiFiEvent(WiFiEvent_t event)
 {
@@ -355,6 +372,17 @@ static void WiFiEvent(WiFiEvent_t event)
         break;
     case ARDUINO_EVENT_WIFI_STA_CONNECTED:
         LOG_INFO("Connected to access point");
+        if (config.network.ipv6_enabled) {
+#if ESP_ARDUINO_VERSION >= ESP_ARDUINO_VERSION_VAL(3, 0, 0)
+            if (!WiFi.enableIPv6()) {
+                LOG_WARN("Failed to enable IPv6");
+            }
+#else
+            if (!WiFi.enableIpV6()) {
+                LOG_WARN("Failed to enable IPv6");
+            }
+#endif
+        }
 #ifdef WIFI_LED
         digitalWrite(WIFI_LED, HIGH);
 #endif
@@ -383,7 +411,8 @@ static void WiFiEvent(WiFiEvent_t event)
         LOG_INFO("Obtained Local IP6 address: %s", WiFi.linkLocalIPv6().toString().c_str());
         LOG_INFO("Obtained GlobalIP6 address: %s", WiFi.globalIPv6().toString().c_str());
 #else
-        LOG_INFO("Obtained IP6 address: %s", WiFi.localIPv6().toString().c_str());
+        LOG_INFO("Obtained Local IP6 address: %s", WiFi.localIPv6().toString().c_str());
+        LOG_INFO("Obtained GlobalIP6 address: %s", GlobalIPv6().toString().c_str());
 #endif
         break;
     case ARDUINO_EVENT_WIFI_STA_LOST_IP:
@@ -514,4 +543,4 @@ uint8_t getWifiDisconnectReason()
 {
     return wifiDisconnectReason;
 }
-#endif // HAS_WIFI
\ No newline at end of file
+#endif // HAS_WIFI
diff --git a/userPrefs.jsonc b/userPrefs.jsonc
index 464eb968c..0c92eabcf 100644
--- a/userPrefs.jsonc
+++ b/userPrefs.jsonc
@@ -56,5 +56,6 @@
   // "USERPREFS_MQTT_ROOT_TOPIC": "event/REPLACEME",
   // "USERPREFS_RINGTONE_NAG_SECS": "60",
   "USERPREFS_RINGTONE_RTTTL": "24:d=32,o=5,b=565:f6,p,f6,4p,p,f6,p,f6,2p,p,b6,p,b6,p,b6,p,b6,p,b,p,b,p,b,p,b,p,b,p,b,p,b,p,b,1p.,2p.,p",
+  // "USERPREFS_NETWORK_IPV6_ENABLED": "1",
   "USERPREFS_TZ_STRING": "tzplaceholder                                         "
 }

From a7796fc7b486af5ce03f3f4740f6d666743ec00c Mon Sep 17 00:00:00 2001
From: Ben Meadors 
Date: Sat, 1 Nov 2025 21:11:36 -0500
Subject: [PATCH 562/691] Fix dismiss of ext. notification (#8512)

* Dismiss all ext notifications with any input broker event

* Account for nagging
---
 src/input/InputBroker.cpp                  | 8 ++++++++
 src/modules/ExternalNotificationModule.cpp | 4 ++++
 2 files changed, 12 insertions(+)

diff --git a/src/input/InputBroker.cpp b/src/input/InputBroker.cpp
index 39fc665c5..7e3ff3de9 100644
--- a/src/input/InputBroker.cpp
+++ b/src/input/InputBroker.cpp
@@ -1,5 +1,7 @@
 #include "InputBroker.h"
 #include "PowerFSM.h" // needed for event trigger
+#include "configuration.h"
+#include "modules/ExternalNotificationModule.h"
 
 InputBroker *inputBroker = nullptr;
 
@@ -48,6 +50,12 @@ void InputBroker::processInputEventQueue()
 int InputBroker::handleInputEvent(const InputEvent *event)
 {
     powerFSM.trigger(EVENT_INPUT); // todo: not every input should wake, like long hold release
+
+    if (event && event->inputEvent != INPUT_BROKER_NONE && externalNotificationModule &&
+        moduleConfig.external_notification.enabled) {
+        externalNotificationModule->stopNow();
+    }
+
     this->notifyObservers(event);
     return 0;
 }
diff --git a/src/modules/ExternalNotificationModule.cpp b/src/modules/ExternalNotificationModule.cpp
index b047e04c2..4fe49cc1b 100644
--- a/src/modules/ExternalNotificationModule.cpp
+++ b/src/modules/ExternalNotificationModule.cpp
@@ -325,6 +325,10 @@ void ExternalNotificationModule::stopNow()
     drv.stop();
 #endif
 
+    // Prevent the state machine from immediately re-triggering outputs after a manual stop.
+    isNagging = false;
+    nagCycleCutoff = UINT32_MAX;
+
 #ifdef HAS_I2S
     // GPIO0 is used as mclk for I2S audio and set to OUTPUT by the sound library
     // T-Deck uses GPIO0 as trackball button, so restore the mode

From d1b66782d128950ead5d4dfad2cc76eae7f9370d Mon Sep 17 00:00:00 2001
From: Jonathan Bennett 
Date: Sun, 2 Nov 2025 05:57:15 -0600
Subject: [PATCH 563/691] Hide nodes that don't have position in the distance
 and bearings nodelists (#8518)

---
 src/graphics/draw/NodeListRenderer.cpp | 15 ++++++++++++++-
 1 file changed, 14 insertions(+), 1 deletion(-)

diff --git a/src/graphics/draw/NodeListRenderer.cpp b/src/graphics/draw/NodeListRenderer.cpp
index 16bfa7066..2a2f71dba 100644
--- a/src/graphics/draw/NodeListRenderer.cpp
+++ b/src/graphics/draw/NodeListRenderer.cpp
@@ -425,6 +425,12 @@ void drawNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t
 {
     const int COMMON_HEADER_HEIGHT = FONT_HEIGHT_SMALL - 1;
     const int rowYOffset = FONT_HEIGHT_SMALL - 3;
+    bool locationScreen = false;
+
+    if (strcmp(title, "Bearings") == 0)
+        locationScreen = true;
+    else if (strcmp(title, "Distance") == 0)
+        locationScreen = true;
 #if defined(M5STACK_UNITC6L)
     int columnWidth = display->getWidth();
 #else
@@ -440,7 +446,7 @@ void drawNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t
 
     int totalEntries = nodeDB->getNumMeshNodes();
     int totalRowsAvailable = (display->getHeight() - y) / rowYOffset;
-
+    int numskipped = 0;
     int visibleNodeRows = totalRowsAvailable;
 #if defined(M5STACK_UNITC6L)
     int totalColumns = 1;
@@ -460,6 +466,10 @@ void drawNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t
     int rowCount = 0;
 
     for (int i = startIndex; i < endIndex; ++i) {
+        if (locationScreen && !nodeDB->getMeshNodeByIndex(i)->has_position) {
+            numskipped++;
+            continue;
+        }
         int xPos = x + (col * columnWidth);
         int yPos = y + yOffset;
         renderer(display, nodeDB->getMeshNodeByIndex(i), xPos, yPos, columnWidth);
@@ -482,6 +492,9 @@ void drawNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t
         }
     }
 
+    // This should correct the scrollbar
+    totalEntries -= numskipped;
+
 #if !defined(M5STACK_UNITC6L)
     // Draw column separator
     if (shownCount > 0) {

From 3a67204f6df571ccc1b10d03c0443fc67e5cb9ea Mon Sep 17 00:00:00 2001
From: Melon <1523107+Melonbwead@users.noreply.github.com>
Date: Sun, 2 Nov 2025 12:09:15 +0000
Subject: [PATCH 564/691] Update device-install.sh to support heltec-v4 (#8509)

* Update device-install.sh

* Update device-install.sh
---
 bin/device-install.sh | 20 +++++++++++---------
 1 file changed, 11 insertions(+), 9 deletions(-)

diff --git a/bin/device-install.sh b/bin/device-install.sh
index ede75bbba..69e4794ba 100755
--- a/bin/device-install.sh
+++ b/bin/device-install.sh
@@ -31,21 +31,23 @@ MUIDB_8MB=(
     "seeed-sensecap-indicator"
 )
 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"
+    "ESP32-S3-Pico"
+    "heltec-v4"
+    "m5stack-cores3"
+    "mesh-tab"
+    "station-g2"
+    "t-deck"
+    "t-energy-s3"
+    "t-eth-elite"
+    "t-watch-s3"
+    "tlora-pager"
 )
 S3_VARIANTS=(
     "s3"
     "-v3"
+    "-v4"
     "t-deck"
     "wireless-paper"
     "wireless-tracker"

From b5b9dc310f8a62fba1d91272adc21b1a94d099cc Mon Sep 17 00:00:00 2001
From: Melon <1523107+Melonbwead@users.noreply.github.com>
Date: Sun, 2 Nov 2025 12:09:15 +0000
Subject: [PATCH 565/691] Update device-install.sh to support heltec-v4 (#8509)

* Update device-install.sh

* Update device-install.sh
---
 bin/device-install.sh | 20 +++++++++++---------
 1 file changed, 11 insertions(+), 9 deletions(-)

diff --git a/bin/device-install.sh b/bin/device-install.sh
index ede75bbba..69e4794ba 100755
--- a/bin/device-install.sh
+++ b/bin/device-install.sh
@@ -31,21 +31,23 @@ MUIDB_8MB=(
     "seeed-sensecap-indicator"
 )
 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"
+    "ESP32-S3-Pico"
+    "heltec-v4"
+    "m5stack-cores3"
+    "mesh-tab"
+    "station-g2"
+    "t-deck"
+    "t-energy-s3"
+    "t-eth-elite"
+    "t-watch-s3"
+    "tlora-pager"
 )
 S3_VARIANTS=(
     "s3"
     "-v3"
+    "-v4"
     "t-deck"
     "wireless-paper"
     "wireless-tracker"

From 597fa0b3820e2799cb95c1a5369f3b952b20b4b9 Mon Sep 17 00:00:00 2001
From: Ben Meadors 
Date: Sun, 2 Nov 2025 06:11:47 -0600
Subject: [PATCH 566/691] Add heltec v4 to bat as well

---
 bin/device-install.bat | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/bin/device-install.bat b/bin/device-install.bat
index 9c206d718..519073b08 100755
--- a/bin/device-install.bat
+++ b/bin/device-install.bat
@@ -15,12 +15,12 @@ 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 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 "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 heltec-v4"
 SET "C3=esp32c3"
 @REM FIXME: Determine flash size from PlatformIO variant, this is unmaintainable.
 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"
+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 heltec-v4"
 
 GOTO getopts
 :help

From 7def82595dd6da1f5b2191f87de9bc5bd6413bb3 Mon Sep 17 00:00:00 2001
From: Jonathan Bennett 
Date: Sun, 2 Nov 2025 21:30:41 -0600
Subject: [PATCH 567/691] Rename screen options to display options and add
 units chooser (#8517)

* Rename screen options to display options and add units chooser

* Add InitialSelected to DisplayUnits_menu
---
 src/graphics/draw/MenuHandler.cpp | 56 ++++++++++++++++++++++++++-----
 src/graphics/draw/MenuHandler.h   |  4 ++-
 2 files changed, 50 insertions(+), 10 deletions(-)

diff --git a/src/graphics/draw/MenuHandler.cpp b/src/graphics/draw/MenuHandler.cpp
index 10c20cbd6..ddd4231f9 100644
--- a/src/graphics/draw/MenuHandler.cpp
+++ b/src/graphics/draw/MenuHandler.cpp
@@ -574,7 +574,7 @@ void menuHandler::textMessageBaseMenu()
 
 void menuHandler::systemBaseMenu()
 {
-    enum optionsNumbers { Back, Notifications, ScreenOptions, Bluetooth, PowerMenu, FrameToggles, Test, enumEnd };
+    enum optionsNumbers { Back, Notifications, ScreenOptions, Bluetooth, PowerMenu, Test, enumEnd };
     static const char *optionsArray[enumEnd] = {"Back"};
     static int optionsEnumArray[enumEnd] = {Back};
     int options = 1;
@@ -583,12 +583,10 @@ void menuHandler::systemBaseMenu()
     optionsEnumArray[options++] = Notifications;
 #if defined(ST7789_CS) || defined(ST7796_CS) || defined(USE_OLED) || defined(USE_SSD1306) || defined(USE_SH1106) ||              \
     defined(USE_SH1107) || defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) || HAS_TFT
-    optionsArray[options] = "Screen Options";
+    optionsArray[options] = "Display Options";
     optionsEnumArray[options++] = ScreenOptions;
 #endif
 
-    optionsArray[options] = "Frame Visiblity Toggle";
-    optionsEnumArray[options++] = FrameToggles;
 #if defined(M5STACK_UNITC6L)
     optionsArray[options] = "Bluetooth";
 #else
@@ -626,9 +624,6 @@ void menuHandler::systemBaseMenu()
         } else if (selected == PowerMenu) {
             menuHandler::menuQueue = menuHandler::power_menu;
             screen->runNow();
-        } else if (selected == FrameToggles) {
-            menuHandler::menuQueue = menuHandler::FrameToggles;
-            screen->runNow();
         } else if (selected == Test) {
             menuHandler::menuQueue = menuHandler::test_menu;
             screen->runNow();
@@ -1330,7 +1325,7 @@ void menuHandler::screenOptionsMenu()
     hasSupportBrightness = false;
 #endif
 
-    enum optionsNumbers { Back, NodeNameLength, Brightness, ScreenColor };
+    enum optionsNumbers { Back, NodeNameLength, Brightness, ScreenColor, FrameToggles, DisplayUnits };
     static const char *optionsArray[5] = {"Back"};
     static int optionsEnumArray[5] = {Back};
     int options = 1;
@@ -1352,8 +1347,14 @@ void menuHandler::screenOptionsMenu()
     optionsEnumArray[options++] = ScreenColor;
 #endif
 
+    optionsArray[options] = "Frame Visiblity Toggle";
+    optionsEnumArray[options++] = FrameToggles;
+
+    optionsArray[options] = "Display Units";
+    optionsEnumArray[options++] = DisplayUnits;
+
     BannerOverlayOptions bannerOptions;
-    bannerOptions.message = "Screen Options";
+    bannerOptions.message = "Display Options";
     bannerOptions.optionsArrayPtr = optionsArray;
     bannerOptions.optionsCount = options;
     bannerOptions.optionsEnumPtr = optionsEnumArray;
@@ -1367,6 +1368,12 @@ void menuHandler::screenOptionsMenu()
         } else if (selected == NodeNameLength) {
             menuHandler::menuQueue = menuHandler::node_name_length_menu;
             screen->runNow();
+        } else if (selected == FrameToggles) {
+            menuHandler::menuQueue = menuHandler::FrameToggles;
+            screen->runNow();
+        } else if (selected == DisplayUnits) {
+            menuHandler::menuQueue = menuHandler::DisplayUnits;
+            screen->runNow();
         } else {
             menuQueue = system_base_menu;
             screen->runNow();
@@ -1578,6 +1585,34 @@ void menuHandler::FrameToggles_menu()
     screen->showOverlayBanner(bannerOptions);
 }
 
+void menuHandler::DisplayUnits_menu()
+{
+    enum optionsNumbers { Back, MetricUnits, ImperialUnits };
+
+    static const char *optionsArray[] = {"Back", "Metric", "Imperial"};
+    BannerOverlayOptions bannerOptions;
+    bannerOptions.message = " Select display units";
+    bannerOptions.optionsArrayPtr = optionsArray;
+    bannerOptions.optionsCount = 3;
+    if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL)
+        bannerOptions.InitialSelected = 2;
+    else
+        bannerOptions.InitialSelected = 1;
+    bannerOptions.bannerCallback = [](int selected) -> void {
+        if (selected == MetricUnits) {
+            config.display.units = meshtastic_Config_DisplayConfig_DisplayUnits_METRIC;
+            service->reloadConfig(SEGMENT_CONFIG);
+        } else if (selected == ImperialUnits) {
+            config.display.units = meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL;
+            service->reloadConfig(SEGMENT_CONFIG);
+        } else {
+            menuHandler::menuQueue = menuHandler::screen_options_menu;
+            screen->runNow();
+        }
+    };
+    screen->showOverlayBanner(bannerOptions);
+}
+
 void menuHandler::handleMenuSwitch(OLEDDisplay *display)
 {
     if (menuQueue != menu_none)
@@ -1692,6 +1727,9 @@ void menuHandler::handleMenuSwitch(OLEDDisplay *display)
     case FrameToggles:
         FrameToggles_menu();
         break;
+    case DisplayUnits:
+        DisplayUnits_menu();
+        break;
     case throttle_message:
         screen->showSimpleBanner("Too Many Attempts\nTry again in 60 seconds.", 5000);
         break;
diff --git a/src/graphics/draw/MenuHandler.h b/src/graphics/draw/MenuHandler.h
index 1f7bbac8c..a611b7c9d 100644
--- a/src/graphics/draw/MenuHandler.h
+++ b/src/graphics/draw/MenuHandler.h
@@ -44,7 +44,8 @@ class menuHandler
         trace_route_menu,
         throttle_message,
         node_name_length_menu,
-        FrameToggles
+        FrameToggles,
+        DisplayUnits
     };
     static screenMenus menuQueue;
 
@@ -88,6 +89,7 @@ class menuHandler
     static void powerMenu();
     static void nodeNameLengthMenu();
     static void FrameToggles_menu();
+    static void DisplayUnits_menu();
     static void textMessageMenu();
 
   private:

From 0a124b7f3dbb38aa98c6edc152983fd4f37fe451 Mon Sep 17 00:00:00 2001
From: Dmitry Ivanishkin 
Date: Mon, 3 Nov 2025 21:17:52 +0700
Subject: [PATCH 568/691] Fix SHT4x detection by reading unique serial nubmer
 (#8525)

---
 src/detect/ScanI2CTwoWire.cpp | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp
index 59d93d74f..95ae5d31e 100644
--- a/src/detect/ScanI2CTwoWire.cpp
+++ b/src/detect/ScanI2CTwoWire.cpp
@@ -106,6 +106,7 @@ uint16_t ScanI2CTwoWire::getRegisterValue(const ScanI2CTwoWire::RegisterLocation
         if (i2cBus->available())
             i2cBus->read();
     }
+    LOG_DEBUG("Register value: 0x%x", value);
     return value;
 }
 
@@ -377,14 +378,13 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
                 }
             case SHT31_4x_ADDR:     // same as OPT3001_ADDR_ALT
             case SHT31_4x_ADDR_ALT: // same as OPT3001_ADDR
-                registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x89), 2);
-                if (registerValue == 0x11a2 || registerValue == 0x11da || registerValue == 0x11f3 || registerValue == 0xe9c ||
-                    registerValue == 0xc8d) {
-                    type = SHT4X;
-                    logFoundDevice("SHT4X", (uint8_t)addr.address);
-                } else if (getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x7E), 2) == 0x5449) {
+                registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x7E), 2);
+                if (registerValue == 0x5449) {
                     type = OPT3001;
                     logFoundDevice("OPT3001", (uint8_t)addr.address);
+                } else if (getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x89), 2) != 0) { // unique SHT4x serial number
+                    type = SHT4X;
+                    logFoundDevice("SHT4X", (uint8_t)addr.address);
                 } else {
                     type = SHT31;
                     logFoundDevice("SHT31", (uint8_t)addr.address);

From 3ae7e54681e858af5acc7242718091e264fbe655 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
 <41898282+github-actions[bot]@users.noreply.github.com>
Date: Mon, 3 Nov 2025 10:14:08 -0600
Subject: [PATCH 569/691] Automated version bumps (#8527)

Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
---
 bin/org.meshtastic.meshtasticd.metainfo.xml | 3 +++
 debian/changelog                            | 6 ++++++
 version.properties                          | 2 +-
 3 files changed, 10 insertions(+), 1 deletion(-)

diff --git a/bin/org.meshtastic.meshtasticd.metainfo.xml b/bin/org.meshtastic.meshtasticd.metainfo.xml
index 6fc5e8597..61ecf9fb5 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.14
+    
     
       https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.13
     
diff --git a/debian/changelog b/debian/changelog
index e124f8929..a387cc3c5 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+meshtasticd (2.7.14.0) unstable; urgency=medium
+
+  * Version 2.7.14
+
+ -- GitHub Actions   Mon, 03 Nov 2025 16:11:31 +0000
+
 meshtasticd (2.7.13.0) unstable; urgency=medium
 
   * Version 2.7.13
diff --git a/version.properties b/version.properties
index f33f0f1cb..fe1a5b31b 100644
--- a/version.properties
+++ b/version.properties
@@ -1,4 +1,4 @@
 [VERSION]  
 major = 2
 minor = 7
-build = 13
+build = 14

From 468247fb9499d2c5c36974d4a2d3712195e727f8 Mon Sep 17 00:00:00 2001
From: Melon <1523107+Melonbwead@users.noreply.github.com>
Date: Mon, 3 Nov 2025 20:14:24 +0000
Subject: [PATCH 570/691] ADD - heltec v4 support to device install bat (#8528)

---
 bin/device-install.bat | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/bin/device-install.bat b/bin/device-install.bat
index 9c206d718..0c3025af5 100755
--- a/bin/device-install.bat
+++ b/bin/device-install.bat
@@ -15,12 +15,12 @@ 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 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 "S3=s3 v3 v4 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=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"
+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 heltec-v4"
 
 GOTO getopts
 :help

From f6370bea8f498d0f74affc1a41dd1d9f2de8085d Mon Sep 17 00:00:00 2001
From: Quency-D <55523105+Quency-D@users.noreply.github.com>
Date: Tue, 4 Nov 2025 04:18:52 +0800
Subject: [PATCH 571/691] Add the identification code for the DA217 triaxial
 accelerometer. (#8526)

---
 src/configuration.h           |  1 +
 src/detect/ScanI2C.h          |  3 ++-
 src/detect/ScanI2CTwoWire.cpp | 17 ++++++++++++++++-
 3 files changed, 19 insertions(+), 2 deletions(-)

diff --git a/src/configuration.h b/src/configuration.h
index baf24a636..524dacdea 100644
--- a/src/configuration.h
+++ b/src/configuration.h
@@ -228,6 +228,7 @@ along with this program.  If not, see .
 #define ICM20948_ADDR_ALT 0x68
 #define BHI260AP_ADDR 0x28
 #define BMM150_ADDR 0x13
+#define DA217_ADDR 0x26
 
 // -----------------------------------------------------------------------------
 // LED
diff --git a/src/detect/ScanI2C.h b/src/detect/ScanI2C.h
index e7bdf58c5..cca867851 100644
--- a/src/detect/ScanI2C.h
+++ b/src/detect/ScanI2C.h
@@ -83,7 +83,8 @@ class ScanI2C
         BMM150,
         TSL2561,
         DRV2605,
-        BH1750
+        BH1750,
+        DA217
     } DeviceType;
 
     // typedef uint8_t DeviceAddress;
diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp
index 95ae5d31e..66697c109 100644
--- a/src/detect/ScanI2CTwoWire.cpp
+++ b/src/detect/ScanI2CTwoWire.cpp
@@ -465,8 +465,23 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
                 break;
 
                 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);
+            case TCA9555_ADDR:
+                registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x01), 1);
+                if (registerValue == 0x13) {
+                    registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x00), 1);
+                    if (registerValue == 0x81) {
+                        type = DA217;
+                        logFoundDevice("DA217", (uint8_t)addr.address);
+                    } else {
+                        type = TCA9555;
+                        logFoundDevice("TCA9555", (uint8_t)addr.address);
+                    }
+                } else {
+                    type = TCA9555;
+                    logFoundDevice("TCA9555", (uint8_t)addr.address);
+                }
+                break;
             case TSL25911_ADDR:
                 registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x12), 1);
                 if (registerValue == 0x50) {

From 538c05ad6c678769eca0d748ccdea3165c9a20de Mon Sep 17 00:00:00 2001
From: Ben Meadors 
Date: Mon, 3 Nov 2025 15:56:23 -0600
Subject: [PATCH 572/691] Revert "ADD - heltec v4 support to device install bat
 (#8528)" (#8532)

This reverts commit 468247fb9499d2c5c36974d4a2d3712195e727f8.
---
 bin/device-install.bat | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/bin/device-install.bat b/bin/device-install.bat
index 0c3025af5..9c206d718 100755
--- a/bin/device-install.bat
+++ b/bin/device-install.bat
@@ -15,12 +15,12 @@ SET "LOGCOUNTER=0"
 SET "BPS_RESET=0"
 
 @REM FIXME: Determine mcu from PlatformIO variant, this is unmaintainable.
-SET "S3=s3 v3 v4 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 "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=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 heltec-v4"
+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

From cf716fe5ef916a2a29dddf598c822f1d5d8e872a Mon Sep 17 00:00:00 2001
From: Manuel <71137295+mverch67@users.noreply.github.com>
Date: Tue, 4 Nov 2025 00:11:16 +0100
Subject: [PATCH 573/691] fix strlcpy compile error in Ubuntu 22.04 (#8520)

* fix strlcpy error in Ubuntu 20.04

* add to native after tests
---
 variants/native/portduino/platformio.ini | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/variants/native/portduino/platformio.ini b/variants/native/portduino/platformio.ini
index 49a8a71c7..474d45492 100644
--- a/variants/native/portduino/platformio.ini
+++ b/variants/native/portduino/platformio.ini
@@ -18,6 +18,7 @@ build_flags = ${native_base.build_flags}
   !pkg-config --libs libulfius --silence-errors || :
   !pkg-config --libs openssl --silence-errors || :
   !pkg-config --cflags --libs sdl2 --silence-errors || :
+  !pkg-config --cflags --libs libbsd-overlay --silence-errors || :
 
 [env:native-tft]
 extends = native_base
@@ -43,6 +44,7 @@ build_flags = ${native_base.build_flags} -Os -lX11 -linput -lxkbcommon -ffunctio
   !pkg-config --libs libulfius --silence-errors || :
   !pkg-config --libs openssl --silence-errors || :
   !pkg-config --cflags --libs sdl2 --silence-errors || :
+  !pkg-config --cflags --libs libbsd-overlay --silence-errors || :
 build_src_filter =
   ${native_base.build_src_filter}
 
@@ -71,6 +73,7 @@ build_flags = ${native_base.build_flags} -Os -ffunction-sections -fdata-sections
   -D MAP_FULL_REDRAW
   !pkg-config --libs libulfius --silence-errors || :
   !pkg-config --libs openssl --silence-errors || :
+  !pkg-config --cflags --libs libbsd-overlay --silence-errors || :
 build_src_filter =
   ${native_base.build_src_filter}
 
@@ -103,6 +106,7 @@ build_flags = ${native_base.build_flags} -O0 -fsanitize=address -lX11 -linput -l
   -D VIEW_320x240
   !pkg-config --libs libulfius --silence-errors || :
   !pkg-config --libs openssl --silence-errors || :
+  !pkg-config --cflags --libs libbsd-overlay --silence-errors || :
 build_src_filter = ${env:native-tft.build_src_filter}
 
 [env:coverage]

From 91621427f1d31ea2d8d0ebad78f05e405fb6c13a Mon Sep 17 00:00:00 2001
From: Austin 
Date: Mon, 3 Nov 2025 18:56:31 -0500
Subject: [PATCH 574/691] Packaging: Add libbsd where needed (#8533)

---
 .devcontainer/Dockerfile | 2 +-
 alpine.Dockerfile        | 7 ++++---
 debian/control           | 1 +
 meshtasticd.spec.rpkg    | 1 +
 4 files changed, 7 insertions(+), 4 deletions(-)

diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile
index a2c56fad9..f546d4cfd 100644
--- a/.devcontainer/Dockerfile
+++ b/.devcontainer/Dockerfile
@@ -1,7 +1,7 @@
 # trunk-ignore-all(terrascan/AC_DOCKER_0002): Known terrascan issue
 # trunk-ignore-all(hadolint/DL3008): Do not pin apt package versions
 # trunk-ignore-all(hadolint/DL3013): Do not pin pip package versions
-FROM mcr.microsoft.com/devcontainers/cpp:2-debian-12
+FROM mcr.microsoft.com/devcontainers/cpp:2-debian-13
 
 USER root
 
diff --git a/alpine.Dockerfile b/alpine.Dockerfile
index 0469874e4..bdee57d79 100644
--- a/alpine.Dockerfile
+++ b/alpine.Dockerfile
@@ -8,7 +8,8 @@ ARG PIO_ENV=native
 ENV PIP_ROOT_USER_ACTION=ignore
 
 RUN apk --no-cache add \
-        bash g++ libstdc++-dev linux-headers zip git ca-certificates libgpiod-dev yaml-cpp-dev bluez-dev \
+        bash g++ libstdc++-dev linux-headers zip git ca-certificates libbsd-dev \
+        libgpiod-dev yaml-cpp-dev bluez-dev \
         libusb-dev i2c-tools-dev libuv-dev openssl-dev pkgconf argp-standalone \
         libx11-dev libinput-dev libxkbcommon-dev \
     && rm -rf /var/cache/apk/* \
@@ -40,8 +41,8 @@ LABEL org.opencontainers.image.title="Meshtastic" \
 USER root
 
 RUN apk --no-cache add \
-        shadow libstdc++ libgpiod yaml-cpp libusb i2c-tools libuv \
-        libx11 libinput libxkbcommon \
+        shadow libstdc++ libbsd libgpiod yaml-cpp libusb \
+        i2c-tools libuv libx11 libinput libxkbcommon \
     && rm -rf /var/cache/apk/* \
     && mkdir -p /var/lib/meshtasticd \
     && mkdir -p /etc/meshtasticd/config.d \
diff --git a/debian/control b/debian/control
index 761383a5c..679a444c9 100644
--- a/debian/control
+++ b/debian/control
@@ -3,6 +3,7 @@ Section: misc
 Priority: optional
 Maintainer: Austin Lane 
 Build-Depends: debhelper-compat (= 13),
+               libc6-dev (>= 2.38) | libbsd-dev,
                lsb-release,
                tar,
                gzip,
diff --git a/meshtasticd.spec.rpkg b/meshtasticd.spec.rpkg
index eb4ab5ae7..f3b2e6960 100644
--- a/meshtasticd.spec.rpkg
+++ b/meshtasticd.spec.rpkg
@@ -33,6 +33,7 @@ BuildRequires: python3dist(grpcio[protobuf])
 BuildRequires: python3dist(grpcio-tools)
 BuildRequires: git-core
 BuildRequires: gcc-c++
+BuildRequires: (glibc-devel >= 2.38) or pkgconfig(libbsd-overlay)
 BuildRequires: pkgconfig(yaml-cpp)
 BuildRequires: pkgconfig(libgpiod)
 BuildRequires: pkgconfig(bluez)

From 83954293d8b52068750f40ae633ae7ccaf39b9c0 Mon Sep 17 00:00:00 2001
From: Sebastian Kuzminsky 
Date: Tue, 4 Nov 2025 04:35:44 -0700
Subject: [PATCH 575/691] nrf52: add watchdog (#8485)

* nrf52: add watchdog

Main thread only for now.

* bump framework-arduinoadafruitnrf52 to pick up new wdt support

* clang-format the new parts of main-nrf52.cpp

---------

Co-authored-by: Ben Meadors 
---
 arch/nrf52/nrf52.ini              |  2 +-
 src/platform/nrf52/main-nrf52.cpp | 33 +++++++++++++++++++++++++++++++
 2 files changed, 34 insertions(+), 1 deletion(-)

diff --git a/arch/nrf52/nrf52.ini b/arch/nrf52/nrf52.ini
index 36effe017..e60d47ce7 100644
--- a/arch/nrf52/nrf52.ini
+++ b/arch/nrf52/nrf52.ini
@@ -7,7 +7,7 @@ extends = arduino_base
 platform_packages =
   ; our custom Git version until they merge our PR
   # TODO renovate
-  platformio/framework-arduinoadafruitnrf52 @ https://github.com/meshtastic/Adafruit_nRF52_Arduino#e13f5820002a4fb2a5e6754b42ace185277e5adf
+  platformio/framework-arduinoadafruitnrf52 @ https://github.com/meshtastic/Adafruit_nRF52_Arduino#c770c8a16a351b55b86e347a3d9d7b74ad0bbf39
   ; Don't renovate toolchain-gccarmnoneeabi
   platformio/toolchain-gccarmnoneeabi@~1.90301.0
 
diff --git a/src/platform/nrf52/main-nrf52.cpp b/src/platform/nrf52/main-nrf52.cpp
index 8ce74d5f7..7eea46f6e 100644
--- a/src/platform/nrf52/main-nrf52.cpp
+++ b/src/platform/nrf52/main-nrf52.cpp
@@ -4,6 +4,13 @@
 #include 
 #include 
 #include 
+
+#define NRFX_WDT_ENABLED 1
+#define NRFX_WDT0_ENABLED 1
+#define NRFX_WDT_CONFIG_NO_IRQ 1
+#include 
+#include 
+
 #include 
 #include 
 #include 
@@ -19,6 +26,9 @@
 #include "BQ25713.h"
 #endif
 
+static nrfx_wdt_t nrfx_wdt = NRFX_WDT_INSTANCE(0);
+static nrfx_wdt_channel_id nrfx_wdt_channel_id_nrf52_main;
+
 static inline void debugger_break(void)
 {
     __asm volatile("bkpt #0x01\n\t"
@@ -202,6 +212,15 @@ void checkSDEvents()
 
 void nrf52Loop()
 {
+    {
+        static bool watchdog_running = false;
+        if (!watchdog_running) {
+            nrfx_wdt_enable(&nrfx_wdt);
+            watchdog_running = true;
+        }
+    }
+    nrfx_wdt_channel_feed(&nrfx_wdt, nrfx_wdt_channel_id_nrf52_main);
+
     checkSDEvents();
     reportLittleFSCorruptionOnce();
 }
@@ -269,6 +288,20 @@ void nrf52Setup()
     LOG_DEBUG("Set random seed %u", seed.seed32);
     randomSeed(seed.seed32);
     nRFCrypto.end();
+
+    // Set up nrfx watchdog. Do not enable the watchdog yet (we do that
+    // the first time through the main loop), so that other threads can
+    // allocate their own wdt channel to protect themselves from hangs.
+    nrfx_wdt_config_t wdt0_config = {
+        .behaviour = NRF_WDT_BEHAVIOUR_RUN_SLEEP, .reload_value = 2000,
+        // Note: Not using wdt interrupts.
+        // .interrupt_priority = NRFX_WDT_DEFAULT_CONFIG_IRQ_PRIORITY
+    };
+    nrfx_err_t r = nrfx_wdt_init(&nrfx_wdt, &wdt0_config,
+                                 nullptr // Watchdog event handler, not used, we just reset.
+    );
+
+    r = nrfx_wdt_channel_alloc(&nrfx_wdt, &nrfx_wdt_channel_id_nrf52_main);
 }
 
 void cpuDeepSleep(uint32_t msecToWake)

From 3ed831b8a32b2e6189a6c535d94722302ae696dc Mon Sep 17 00:00:00 2001
From: "Daniel.Cao" <144674500+DanielCao0@users.noreply.github.com>
Date: Tue, 4 Nov 2025 19:53:08 +0800
Subject: [PATCH 576/691] Add support for RAK_WISMESH_TAP_V2 and RAK3401
 hardware models (#8537)

---
 src/platform/esp32/architecture.h | 2 ++
 src/platform/nrf52/architecture.h | 2 ++
 2 files changed, 4 insertions(+)

diff --git a/src/platform/esp32/architecture.h b/src/platform/esp32/architecture.h
index 53b23124d..9b5abfba0 100644
--- a/src/platform/esp32/architecture.h
+++ b/src/platform/esp32/architecture.h
@@ -191,6 +191,8 @@
 #define HW_VENDOR meshtastic_HardwareModel_CROWPANEL
 #elif defined(RAK3312)
 #define HW_VENDOR meshtastic_HardwareModel_RAK3312
+#elif defined(RAK_WISMESH_TAP_V2)
+#define HW_VENDOR meshtastic_HardwareModel_WISMESH_TAP_V2
 #elif defined(LINK_32)
 #define HW_VENDOR meshtastic_HardwareModel_LINK_32
 #elif defined(T_DECK_PRO)
diff --git a/src/platform/nrf52/architecture.h b/src/platform/nrf52/architecture.h
index cee0e5a10..c74f02c44 100644
--- a/src/platform/nrf52/architecture.h
+++ b/src/platform/nrf52/architecture.h
@@ -60,6 +60,8 @@
 // MAke sure all custom RAK4630 boards are defined before the generic RAK4630
 #elif defined(RAK4630)
 #define HW_VENDOR meshtastic_HardwareModel_RAK4631
+#elif defined(RAK3401)
+#define HW_VENDOR meshtastic_HardwareModel_RAK3401
 #elif defined(TTGO_T_ECHO)
 #define HW_VENDOR meshtastic_HardwareModel_T_ECHO
 #elif defined(T_ECHO_LITE)

From 03f69b3b77c49a4b8977aa87a22971bf81ebd6d9 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Tue, 4 Nov 2025 06:04:56 -0600
Subject: [PATCH 577/691] Update RadioLib to v7.4.0 (#8456)

* fix strlcpy compile error in Ubuntu 22.04 (#8520)

* fix strlcpy error in Ubuntu 20.04

* add to native after tests

* Add support for RAK_WISMESH_TAP_V2 and RAK3401 hardware models (#8537)

* Update RadioLib to v7.4.0

---------

Co-authored-by: Manuel <71137295+mverch67@users.noreply.github.com>
Co-authored-by: Daniel.Cao <144674500+DanielCao0@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
 platformio.ini                           | 2 +-
 src/platform/esp32/architecture.h        | 2 ++
 src/platform/nrf52/architecture.h        | 2 ++
 variants/native/portduino/platformio.ini | 4 ++++
 4 files changed, 9 insertions(+), 1 deletion(-)

diff --git a/platformio.ini b/platformio.ini
index fe2f5e28a..80f65a9e7 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -115,7 +115,7 @@ lib_deps =
 [radiolib_base]
 lib_deps =
 	# renovate: datasource=custom.pio depName=RadioLib packageName=jgromes/library/RadioLib
-	jgromes/RadioLib@7.3.0
+	jgromes/RadioLib@7.4.0
 
 [device-ui_base]
 lib_deps =
diff --git a/src/platform/esp32/architecture.h b/src/platform/esp32/architecture.h
index 53b23124d..9b5abfba0 100644
--- a/src/platform/esp32/architecture.h
+++ b/src/platform/esp32/architecture.h
@@ -191,6 +191,8 @@
 #define HW_VENDOR meshtastic_HardwareModel_CROWPANEL
 #elif defined(RAK3312)
 #define HW_VENDOR meshtastic_HardwareModel_RAK3312
+#elif defined(RAK_WISMESH_TAP_V2)
+#define HW_VENDOR meshtastic_HardwareModel_WISMESH_TAP_V2
 #elif defined(LINK_32)
 #define HW_VENDOR meshtastic_HardwareModel_LINK_32
 #elif defined(T_DECK_PRO)
diff --git a/src/platform/nrf52/architecture.h b/src/platform/nrf52/architecture.h
index cee0e5a10..c74f02c44 100644
--- a/src/platform/nrf52/architecture.h
+++ b/src/platform/nrf52/architecture.h
@@ -60,6 +60,8 @@
 // MAke sure all custom RAK4630 boards are defined before the generic RAK4630
 #elif defined(RAK4630)
 #define HW_VENDOR meshtastic_HardwareModel_RAK4631
+#elif defined(RAK3401)
+#define HW_VENDOR meshtastic_HardwareModel_RAK3401
 #elif defined(TTGO_T_ECHO)
 #define HW_VENDOR meshtastic_HardwareModel_T_ECHO
 #elif defined(T_ECHO_LITE)
diff --git a/variants/native/portduino/platformio.ini b/variants/native/portduino/platformio.ini
index 49a8a71c7..474d45492 100644
--- a/variants/native/portduino/platformio.ini
+++ b/variants/native/portduino/platformio.ini
@@ -18,6 +18,7 @@ build_flags = ${native_base.build_flags}
   !pkg-config --libs libulfius --silence-errors || :
   !pkg-config --libs openssl --silence-errors || :
   !pkg-config --cflags --libs sdl2 --silence-errors || :
+  !pkg-config --cflags --libs libbsd-overlay --silence-errors || :
 
 [env:native-tft]
 extends = native_base
@@ -43,6 +44,7 @@ build_flags = ${native_base.build_flags} -Os -lX11 -linput -lxkbcommon -ffunctio
   !pkg-config --libs libulfius --silence-errors || :
   !pkg-config --libs openssl --silence-errors || :
   !pkg-config --cflags --libs sdl2 --silence-errors || :
+  !pkg-config --cflags --libs libbsd-overlay --silence-errors || :
 build_src_filter =
   ${native_base.build_src_filter}
 
@@ -71,6 +73,7 @@ build_flags = ${native_base.build_flags} -Os -ffunction-sections -fdata-sections
   -D MAP_FULL_REDRAW
   !pkg-config --libs libulfius --silence-errors || :
   !pkg-config --libs openssl --silence-errors || :
+  !pkg-config --cflags --libs libbsd-overlay --silence-errors || :
 build_src_filter =
   ${native_base.build_src_filter}
 
@@ -103,6 +106,7 @@ build_flags = ${native_base.build_flags} -O0 -fsanitize=address -lX11 -linput -l
   -D VIEW_320x240
   !pkg-config --libs libulfius --silence-errors || :
   !pkg-config --libs openssl --silence-errors || :
+  !pkg-config --cflags --libs libbsd-overlay --silence-errors || :
 build_src_filter = ${env:native-tft.build_src_filter}
 
 [env:coverage]

From 0a13bcaabfa20617ae514aa8b256d8d591602987 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
 <41898282+github-actions[bot]@users.noreply.github.com>
Date: Tue, 4 Nov 2025 06:07:12 -0600
Subject: [PATCH 578/691] Upgrade trunk (#8437)

Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com>
---
 .trunk/trunk.yaml | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml
index 0a43c3079..46916bf29 100644
--- a/.trunk/trunk.yaml
+++ b/.trunk/trunk.yaml
@@ -8,15 +8,15 @@ plugins:
       uri: https://github.com/trunk-io/plugins
 lint:
   enabled:
-    - checkov@3.2.486
-    - renovate@41.157.0
+    - checkov@3.2.489
+    - renovate@41.169.1
     - prettier@3.6.2
-    - trufflehog@3.90.11
+    - trufflehog@3.90.12
     - yamllint@1.37.1
     - bandit@1.8.6
     - trivy@0.67.2
     - taplo@0.10.0
-    - ruff@0.14.1
+    - ruff@0.14.3
     - isort@7.0.0
     - markdownlint@0.45.0
     - oxipng@9.1.5

From f2400c9dc6013bc64c07de823f2afda8988b6405 Mon Sep 17 00:00:00 2001
From: Jonathan Bennett 
Date: Tue, 4 Nov 2025 11:35:44 -0600
Subject: [PATCH 579/691] Update platform-native for WIFi lib fix (#8544)

Updates the WiFi library way down in Portduino, to detect TCP connection drops
---
 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 f3fd00de7..bce06f907 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/d3f6e339534233c7217818867368767590ce549e.zip
+  https://github.com/meshtastic/platform-native/archive/f566d364204416cdbf298e349213f7d551f793d9.zip
 framework = arduino
 
 build_src_filter = 

From 6b55ec6603d80077585a86310006ca6a8b02420d Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Tue, 4 Nov 2025 11:36:05 -0600
Subject: [PATCH 580/691] chore(deps): update python to v3.14.0 (#8542)

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

diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
index bc660170c..e3f076ce0 100644
--- a/.devcontainer/devcontainer.json
+++ b/.devcontainer/devcontainer.json
@@ -8,7 +8,7 @@
   "features": {
     "ghcr.io/devcontainers/features/python:1": {
       "installTools": true,
-      "version": "3.13"
+      "version": "3.14"
     }
   },
   "customizations": {

From a579a9d0116847f629dac6686a4289f611dc2739 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Tue, 4 Nov 2025 15:35:14 -0600
Subject: [PATCH 581/691] chore(deps): update adafruit pct2075 to v1.0.6
 (#8548)

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 7c63ad7ad..405fb040b 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -176,7 +176,7 @@ lib_deps =
 	# renovate: datasource=custom.pio depName=Adafruit LTR390 Library packageName=adafruit/library/Adafruit LTR390 Library
 	adafruit/Adafruit LTR390 Library@1.1.2
 	# renovate: datasource=custom.pio depName=Adafruit PCT2075 packageName=adafruit/library/Adafruit PCT2075
-	adafruit/Adafruit PCT2075@1.0.5
+	adafruit/Adafruit PCT2075@1.0.6
 	# 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

From 3e40d7896ba26e755bfdb16706277ba6cc07618d Mon Sep 17 00:00:00 2001
From: Jason P 
Date: Wed, 5 Nov 2025 10:07:28 -0600
Subject: [PATCH 582/691] Revert "nrf52: add watchdog (#8485)" (#8554)

This reverts commit 83954293d8b52068750f40ae633ae7ccaf39b9c0.
---
 arch/nrf52/nrf52.ini              |  2 +-
 src/platform/nrf52/main-nrf52.cpp | 33 -------------------------------
 2 files changed, 1 insertion(+), 34 deletions(-)

diff --git a/arch/nrf52/nrf52.ini b/arch/nrf52/nrf52.ini
index e60d47ce7..36effe017 100644
--- a/arch/nrf52/nrf52.ini
+++ b/arch/nrf52/nrf52.ini
@@ -7,7 +7,7 @@ extends = arduino_base
 platform_packages =
   ; our custom Git version until they merge our PR
   # TODO renovate
-  platformio/framework-arduinoadafruitnrf52 @ https://github.com/meshtastic/Adafruit_nRF52_Arduino#c770c8a16a351b55b86e347a3d9d7b74ad0bbf39
+  platformio/framework-arduinoadafruitnrf52 @ https://github.com/meshtastic/Adafruit_nRF52_Arduino#e13f5820002a4fb2a5e6754b42ace185277e5adf
   ; Don't renovate toolchain-gccarmnoneeabi
   platformio/toolchain-gccarmnoneeabi@~1.90301.0
 
diff --git a/src/platform/nrf52/main-nrf52.cpp b/src/platform/nrf52/main-nrf52.cpp
index 7eea46f6e..8ce74d5f7 100644
--- a/src/platform/nrf52/main-nrf52.cpp
+++ b/src/platform/nrf52/main-nrf52.cpp
@@ -4,13 +4,6 @@
 #include 
 #include 
 #include 
-
-#define NRFX_WDT_ENABLED 1
-#define NRFX_WDT0_ENABLED 1
-#define NRFX_WDT_CONFIG_NO_IRQ 1
-#include 
-#include 
-
 #include 
 #include 
 #include 
@@ -26,9 +19,6 @@
 #include "BQ25713.h"
 #endif
 
-static nrfx_wdt_t nrfx_wdt = NRFX_WDT_INSTANCE(0);
-static nrfx_wdt_channel_id nrfx_wdt_channel_id_nrf52_main;
-
 static inline void debugger_break(void)
 {
     __asm volatile("bkpt #0x01\n\t"
@@ -212,15 +202,6 @@ void checkSDEvents()
 
 void nrf52Loop()
 {
-    {
-        static bool watchdog_running = false;
-        if (!watchdog_running) {
-            nrfx_wdt_enable(&nrfx_wdt);
-            watchdog_running = true;
-        }
-    }
-    nrfx_wdt_channel_feed(&nrfx_wdt, nrfx_wdt_channel_id_nrf52_main);
-
     checkSDEvents();
     reportLittleFSCorruptionOnce();
 }
@@ -288,20 +269,6 @@ void nrf52Setup()
     LOG_DEBUG("Set random seed %u", seed.seed32);
     randomSeed(seed.seed32);
     nRFCrypto.end();
-
-    // Set up nrfx watchdog. Do not enable the watchdog yet (we do that
-    // the first time through the main loop), so that other threads can
-    // allocate their own wdt channel to protect themselves from hangs.
-    nrfx_wdt_config_t wdt0_config = {
-        .behaviour = NRF_WDT_BEHAVIOUR_RUN_SLEEP, .reload_value = 2000,
-        // Note: Not using wdt interrupts.
-        // .interrupt_priority = NRFX_WDT_DEFAULT_CONFIG_IRQ_PRIORITY
-    };
-    nrfx_err_t r = nrfx_wdt_init(&nrfx_wdt, &wdt0_config,
-                                 nullptr // Watchdog event handler, not used, we just reset.
-    );
-
-    r = nrfx_wdt_channel_alloc(&nrfx_wdt, &nrfx_wdt_channel_id_nrf52_main);
 }
 
 void cpuDeepSleep(uint32_t msecToWake)

From ce2e08e0d818cad7b729cd14227e46328cd0d4d2 Mon Sep 17 00:00:00 2001
From: Jason P 
Date: Wed, 5 Nov 2025 13:19:55 -0600
Subject: [PATCH 583/691] Don't Favorite Nodes if our Role is CLIENT_BASE
 (#8558)

* Don't Favorite Nodes if our Role is CLIENT_BASE

* Update CannedMessageModule.cpp
---
 src/modules/CannedMessageModule.cpp | 10 ++++++++--
 1 file changed, 8 insertions(+), 2 deletions(-)

diff --git a/src/modules/CannedMessageModule.cpp b/src/modules/CannedMessageModule.cpp
index 9f95a9e20..f435f6060 100644
--- a/src/modules/CannedMessageModule.cpp
+++ b/src/modules/CannedMessageModule.cpp
@@ -973,8 +973,14 @@ void CannedMessageModule::sendText(NodeNum dest, ChannelIndex channel, const cha
     LOG_INFO("Send message id=%u, dest=%x, msg=%.*s", p->id, p->to, p->decoded.payload.size, p->decoded.payload.bytes);
 
     if (p->to != 0xffffffff) {
-        LOG_INFO("Proactively adding %x as favorite node", p->to);
-        nodeDB->set_favorite(true, p->to);
+        // Only add as favorite if our role is NOT CLIENT_BASE
+        if (config.device.role != 12) {
+            LOG_INFO("Proactively adding %x as favorite node", p->to);
+            nodeDB->set_favorite(true, p->to);
+        } else {
+            LOG_DEBUG("Not favoriting node %x as we are CLIENT_BASE role", p->to);
+        }
+
         screen->setFrames(graphics::Screen::FOCUS_PRESERVE);
         p->pki_encrypted = true;
         p->channel = 0;

From 45bf2468a9b2d0317a444ceeaf013d1b148fb363 Mon Sep 17 00:00:00 2001
From: Manuel <71137295+mverch67@users.noreply.github.com>
Date: Thu, 6 Nov 2025 02:32:56 +0100
Subject: [PATCH 584/691] fix missing key 0 (#8564)

---
 src/input/TDeckProKeyboard.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/input/TDeckProKeyboard.cpp b/src/input/TDeckProKeyboard.cpp
index 098e0804a..eeafe4949 100644
--- a/src/input/TDeckProKeyboard.cpp
+++ b/src/input/TDeckProKeyboard.cpp
@@ -57,7 +57,7 @@ static unsigned char TDeckProTapMap[_TCA8418_NUM_KEYS][5] = {
     {0x00, 0x00, 0x00},
     {0x00, 0x00, 0x00},
     {0x20, 0x00, 0x00},
-    {0x00, 0x00, 0x00},
+    {0x00, 0x00, '0'},
     {0x00, 0x00, 0x00} // R_Shift, sym, space, mic, L_Shift
 };
 

From 7b14b173d95ea6dcd9c257ba83eec795720b5a2c Mon Sep 17 00:00:00 2001
From: Wessel 
Date: Thu, 6 Nov 2025 13:27:25 +0100
Subject: [PATCH 585/691] Store hop/mqtt/transport mechanism info in S&F
 (#8560)

Before this, all messages received when enabling S&F server would return
Hops away: -1
---
 src/modules/StoreForwardModule.cpp | 8 ++++++++
 src/modules/StoreForwardModule.h   | 4 ++++
 2 files changed, 12 insertions(+)

diff --git a/src/modules/StoreForwardModule.cpp b/src/modules/StoreForwardModule.cpp
index 72ac99118..b8a710bf5 100644
--- a/src/modules/StoreForwardModule.cpp
+++ b/src/modules/StoreForwardModule.cpp
@@ -204,6 +204,10 @@ void StoreForwardModule::historyAdd(const meshtastic_MeshPacket &mp)
     this->packetHistory[this->packetHistoryTotalCount].payload_size = p.payload.size;
     this->packetHistory[this->packetHistoryTotalCount].rx_rssi = mp.rx_rssi;
     this->packetHistory[this->packetHistoryTotalCount].rx_snr = mp.rx_snr;
+    this->packetHistory[this->packetHistoryTotalCount].hop_start = mp.hop_start;
+    this->packetHistory[this->packetHistoryTotalCount].hop_limit = mp.hop_limit;
+    this->packetHistory[this->packetHistoryTotalCount].via_mqtt = mp.via_mqtt;
+    this->packetHistory[this->packetHistoryTotalCount].transport_mechanism = mp.transport_mechanism;
     memcpy(this->packetHistory[this->packetHistoryTotalCount].payload, p.payload.bytes, meshtastic_Constants_DATA_PAYLOAD_LEN);
 
     this->packetHistoryTotalCount++;
@@ -256,6 +260,10 @@ meshtastic_MeshPacket *StoreForwardModule::preparePayload(NodeNum dest, uint32_t
                 p->decoded.emoji = (uint32_t)this->packetHistory[i].emoji;
                 p->rx_rssi = this->packetHistory[i].rx_rssi;
                 p->rx_snr = this->packetHistory[i].rx_snr;
+                p->hop_start = this->packetHistory[i].hop_start;
+                p->hop_limit = this->packetHistory[i].hop_limit;
+                p->via_mqtt = this->packetHistory[i].via_mqtt;
+                p->transport_mechanism = (meshtastic_MeshPacket_TransportMechanism)this->packetHistory[i].transport_mechanism;
 
                 // Let's assume that if the server received the S&F request that the client is in range.
                 //   TODO: Make this configurable.
diff --git a/src/modules/StoreForwardModule.h b/src/modules/StoreForwardModule.h
index 25836eded..148568e1b 100644
--- a/src/modules/StoreForwardModule.h
+++ b/src/modules/StoreForwardModule.h
@@ -21,6 +21,10 @@ struct PacketHistoryStruct {
     pb_size_t payload_size;
     int32_t rx_rssi;
     float rx_snr;
+    uint8_t hop_start;
+    uint8_t hop_limit;
+    bool via_mqtt;
+    uint8_t transport_mechanism;
 };
 
 class StoreForwardModule : private concurrency::OSThread, public ProtobufModule

From 69db3bd11c999d27e25a65fd258113f69ab11fea Mon Sep 17 00:00:00 2001
From: Jonathan Bennett 
Date: Thu, 6 Nov 2025 06:28:13 -0600
Subject: [PATCH 586/691] Reject legacy text message DMs (#8562)

Co-authored-by: Ben Meadors 
---
 src/mesh/Router.cpp | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp
index 5cf8bfa7d..05f47d7f4 100644
--- a/src/mesh/Router.cpp
+++ b/src/mesh/Router.cpp
@@ -479,6 +479,11 @@ DecodeState perhapsDecode(meshtastic_MeshPacket *p)
                     LOG_ERROR("Invalid protobufs in received mesh packet id=0x%08x (bad psk?)!", p->id);
                 } else if (decodedtmp.portnum == meshtastic_PortNum_UNKNOWN_APP) {
                     LOG_ERROR("Invalid portnum (bad psk?)!");
+#if !(MESHTASTIC_EXCLUDE_PKI)
+                } else if (!owner.is_licensed && isToUs(p) && decodedtmp.portnum == meshtastic_PortNum_TEXT_MESSAGE_APP) {
+                    LOG_WARN("Rejecting legacy DM");
+                    return DecodeState::DECODE_FAILURE;
+#endif
                 } else {
                     p->decoded = decodedtmp;
                     p->which_payload_variant = meshtastic_MeshPacket_decoded_tag; // change type to decoded

From 6a6c409b9a0ac15fafd56af5a36c753313a5209e Mon Sep 17 00:00:00 2001
From: Mike Robbins 
Date: Thu, 6 Nov 2025 08:10:20 -0500
Subject: [PATCH 587/691] addFromContact: Don't auto-favorite when CLIENT_BASE;
 don't update last_heard unless CLIENT_BASE (#8495)

Co-authored-by: Ben Meadors 
---
 src/mesh/NodeDB.cpp         | 23 +++++++++++++++++++++--
 src/modules/AdminModule.cpp | 11 ++++++++++-
 2 files changed, 31 insertions(+), 3 deletions(-)

diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp
index df9aece0a..443cc8eb7 100644
--- a/src/mesh/NodeDB.cpp
+++ b/src/mesh/NodeDB.cpp
@@ -1638,13 +1638,32 @@ void NodeDB::addFromContact(meshtastic_SharedContact contact)
         // If should_ignore is set,
         // we need to clear the public key and other cruft, in addition to setting the node as ignored
         info->is_ignored = true;
+        info->is_favorite = false;
         info->has_device_metrics = false;
         info->has_position = false;
         info->user.public_key.size = 0;
         info->user.public_key.bytes[0] = 0;
     } else {
-        info->last_heard = getValidTime(RTCQualityNTP);
-        info->is_favorite = true;
+        /* Clients are sending add_contact before every text message DM (because clients may hold a larger node database with
+         * public keys than the radio holds). However, we don't want to update last_heard just because we sent someone a DM!
+         */
+
+        /* "Boring old nodes" are the first to be evicted out of the node database when full. This includes a newly-zeroed
+         * nodeinfo because it has: !is_favorite && last_heard==0. To keep this from happening when we addFromContact, we set the
+         * new node as a favorite, and we leave last_heard alone (even if it's zero).
+         */
+        if (config.device.role == meshtastic_Config_DeviceConfig_Role_CLIENT_BASE) {
+            // Special case for CLIENT_BASE: is_favorite has special meaning, and we don't want to automatically set it
+            // without the user doing so deliberately. We don't normally expect users to use a CLIENT_BASE to send DMs or to add
+            // contacts, but we should make sure it doesn't auto-favorite in case they do. Instead, as a workaround, we'll set
+            // last_heard to now, so that the add_contact node doesn't immediately get evicted.
+            info->last_heard = getTime();
+        } else {
+            // Normal case: set is_favorite to prevent expiration.
+            // last_heard will remain as-is (or remain 0 if this entry wasn't in the nodeDB).
+            info->is_favorite = true;
+        }
+
         // As the clients will begin sending the contact with DMs, we want to strictly check if the node is manually verified
         if (contact.manually_verified) {
             info->bitfield |= NODEINFO_BITFIELD_IS_KEY_MANUALLY_VERIFIED_MASK;
diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp
index d300ff53b..24fb8f1f9 100644
--- a/src/modules/AdminModule.cpp
+++ b/src/modules/AdminModule.cpp
@@ -104,9 +104,18 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta
             (config.security.admin_key[2].size == 32 &&
              memcmp(mp.public_key.bytes, config.security.admin_key[2].bytes, 32) == 0)) {
             LOG_INFO("PKC admin payload with authorized sender key");
+
+            // Automatically favorite the node that is using the admin key
             auto remoteNode = nodeDB->getMeshNode(mp.from);
             if (remoteNode && !remoteNode->is_favorite) {
-                remoteNode->is_favorite = true;
+                if (config.device.role == meshtastic_Config_DeviceConfig_Role_CLIENT_BASE) {
+                    // Special case for CLIENT_BASE: is_favorite has special meaning, and we don't want to automatically set it
+                    // without the user doing so deliberately.
+                    LOG_INFO("PKC admin valid, but not auto-favoriting node %x because role==CLIENT_BASE", mp.from);
+                } else {
+                    LOG_INFO("PKC admin valid. Auto-favoriting node %x", mp.from);
+                    remoteNode->is_favorite = true;
+                }
             }
         } else {
             myReply = allocErrorResponse(meshtastic_Routing_Error_ADMIN_PUBLIC_KEY_UNAUTHORIZED, &mp);

From 5ba04ade2de1855b0d34987ee397d73c8092b34f Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
 <41898282+github-actions[bot]@users.noreply.github.com>
Date: Thu, 6 Nov 2025 07:10:57 -0600
Subject: [PATCH 588/691] Update protobufs (#8566)

Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
---
 protobufs                                | 2 +-
 src/mesh/generated/meshtastic/admin.pb.h | 7 ++++---
 src/mesh/generated/meshtastic/mesh.pb.h  | 4 ++++
 3 files changed, 9 insertions(+), 4 deletions(-)

diff --git a/protobufs b/protobufs
index bf149bbdc..7654db2e2 160000
--- a/protobufs
+++ b/protobufs
@@ -1 +1 @@
-Subproject commit bf149bbdcce45ba7cd8643db7cb25e5c8815072b
+Subproject commit 7654db2e2d1834aebde40090a9b74162ad1048ae
diff --git a/src/mesh/generated/meshtastic/admin.pb.h b/src/mesh/generated/meshtastic/admin.pb.h
index 7cc896292..a542cf29c 100644
--- a/src/mesh/generated/meshtastic/admin.pb.h
+++ b/src/mesh/generated/meshtastic/admin.pb.h
@@ -272,8 +272,9 @@ typedef struct _meshtastic_AdminMessage {
         int32_t shutdown_seconds;
         /* Tell the node to factory reset config; all device state and configuration will be returned to factory defaults; BLE bonds will be preserved. */
         int32_t factory_reset_config;
-        /* Tell the node to reset the nodedb. */
-        int32_t nodedb_reset;
+        /* Tell the node to reset the nodedb.
+     When true, favorites are preserved through reset. */
+        bool nodedb_reset;
     };
     /* The node generates this key and sends it with any get_x_response packets.
  The client MUST include the same key with any set_x commands. Key expires after 300 seconds.
@@ -459,7 +460,7 @@ X(a, STATIC,   ONEOF,    BOOL,     (payload_variant,exit_simulator,exit_simulato
 X(a, STATIC,   ONEOF,    INT32,    (payload_variant,reboot_seconds,reboot_seconds),  97) \
 X(a, STATIC,   ONEOF,    INT32,    (payload_variant,shutdown_seconds,shutdown_seconds),  98) \
 X(a, STATIC,   ONEOF,    INT32,    (payload_variant,factory_reset_config,factory_reset_config),  99) \
-X(a, STATIC,   ONEOF,    INT32,    (payload_variant,nodedb_reset,nodedb_reset), 100) \
+X(a, STATIC,   ONEOF,    BOOL,     (payload_variant,nodedb_reset,nodedb_reset), 100) \
 X(a, STATIC,   SINGULAR, BYTES,    session_passkey, 101)
 #define meshtastic_AdminMessage_CALLBACK NULL
 #define meshtastic_AdminMessage_DEFAULT NULL
diff --git a/src/mesh/generated/meshtastic/mesh.pb.h b/src/mesh/generated/meshtastic/mesh.pb.h
index 059af57ae..0da44cce0 100644
--- a/src/mesh/generated/meshtastic/mesh.pb.h
+++ b/src/mesh/generated/meshtastic/mesh.pb.h
@@ -284,6 +284,10 @@ typedef enum _meshtastic_HardwareModel {
     meshtastic_HardwareModel_T_WATCH_ULTRA = 114,
     /* Elecrow ThinkNode M3 */
     meshtastic_HardwareModel_THINKNODE_M3 = 115,
+    /* RAK WISMESH_TAP_V2 with ESP32-S3 CPU */
+    meshtastic_HardwareModel_WISMESH_TAP_V2 = 116,
+    /* RAK3401 */
+    meshtastic_HardwareModel_RAK3401 = 117,
     /* ------------------------------------------------------------------------------------------------------------------------------------------
  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 112b294ef6660429e86eaaacd5705e2b1f679490 Mon Sep 17 00:00:00 2001
From: Wessel 
Date: Thu, 6 Nov 2025 13:27:25 +0100
Subject: [PATCH 589/691] Store hop/mqtt/transport mechanism info in S&F
 (#8560)

Before this, all messages received when enabling S&F server would return
Hops away: -1
---
 src/modules/StoreForwardModule.cpp | 8 ++++++++
 src/modules/StoreForwardModule.h   | 4 ++++
 2 files changed, 12 insertions(+)

diff --git a/src/modules/StoreForwardModule.cpp b/src/modules/StoreForwardModule.cpp
index 72ac99118..b8a710bf5 100644
--- a/src/modules/StoreForwardModule.cpp
+++ b/src/modules/StoreForwardModule.cpp
@@ -204,6 +204,10 @@ void StoreForwardModule::historyAdd(const meshtastic_MeshPacket &mp)
     this->packetHistory[this->packetHistoryTotalCount].payload_size = p.payload.size;
     this->packetHistory[this->packetHistoryTotalCount].rx_rssi = mp.rx_rssi;
     this->packetHistory[this->packetHistoryTotalCount].rx_snr = mp.rx_snr;
+    this->packetHistory[this->packetHistoryTotalCount].hop_start = mp.hop_start;
+    this->packetHistory[this->packetHistoryTotalCount].hop_limit = mp.hop_limit;
+    this->packetHistory[this->packetHistoryTotalCount].via_mqtt = mp.via_mqtt;
+    this->packetHistory[this->packetHistoryTotalCount].transport_mechanism = mp.transport_mechanism;
     memcpy(this->packetHistory[this->packetHistoryTotalCount].payload, p.payload.bytes, meshtastic_Constants_DATA_PAYLOAD_LEN);
 
     this->packetHistoryTotalCount++;
@@ -256,6 +260,10 @@ meshtastic_MeshPacket *StoreForwardModule::preparePayload(NodeNum dest, uint32_t
                 p->decoded.emoji = (uint32_t)this->packetHistory[i].emoji;
                 p->rx_rssi = this->packetHistory[i].rx_rssi;
                 p->rx_snr = this->packetHistory[i].rx_snr;
+                p->hop_start = this->packetHistory[i].hop_start;
+                p->hop_limit = this->packetHistory[i].hop_limit;
+                p->via_mqtt = this->packetHistory[i].via_mqtt;
+                p->transport_mechanism = (meshtastic_MeshPacket_TransportMechanism)this->packetHistory[i].transport_mechanism;
 
                 // Let's assume that if the server received the S&F request that the client is in range.
                 //   TODO: Make this configurable.
diff --git a/src/modules/StoreForwardModule.h b/src/modules/StoreForwardModule.h
index 25836eded..148568e1b 100644
--- a/src/modules/StoreForwardModule.h
+++ b/src/modules/StoreForwardModule.h
@@ -21,6 +21,10 @@ struct PacketHistoryStruct {
     pb_size_t payload_size;
     int32_t rx_rssi;
     float rx_snr;
+    uint8_t hop_start;
+    uint8_t hop_limit;
+    bool via_mqtt;
+    uint8_t transport_mechanism;
 };
 
 class StoreForwardModule : private concurrency::OSThread, public ProtobufModule

From 4d86bbafe6c9be6930b49935bc3f147d66a8e093 Mon Sep 17 00:00:00 2001
From: Mike Robbins 
Date: Thu, 6 Nov 2025 08:10:20 -0500
Subject: [PATCH 590/691] addFromContact: Don't auto-favorite when CLIENT_BASE;
 don't update last_heard unless CLIENT_BASE (#8495)

Co-authored-by: Ben Meadors 
---
 src/mesh/NodeDB.cpp         | 23 +++++++++++++++++++++--
 src/modules/AdminModule.cpp | 11 ++++++++++-
 2 files changed, 31 insertions(+), 3 deletions(-)

diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp
index dec8411fe..8d30fb824 100644
--- a/src/mesh/NodeDB.cpp
+++ b/src/mesh/NodeDB.cpp
@@ -1632,13 +1632,32 @@ void NodeDB::addFromContact(meshtastic_SharedContact contact)
         // If should_ignore is set,
         // we need to clear the public key and other cruft, in addition to setting the node as ignored
         info->is_ignored = true;
+        info->is_favorite = false;
         info->has_device_metrics = false;
         info->has_position = false;
         info->user.public_key.size = 0;
         info->user.public_key.bytes[0] = 0;
     } else {
-        info->last_heard = getValidTime(RTCQualityNTP);
-        info->is_favorite = true;
+        /* Clients are sending add_contact before every text message DM (because clients may hold a larger node database with
+         * public keys than the radio holds). However, we don't want to update last_heard just because we sent someone a DM!
+         */
+
+        /* "Boring old nodes" are the first to be evicted out of the node database when full. This includes a newly-zeroed
+         * nodeinfo because it has: !is_favorite && last_heard==0. To keep this from happening when we addFromContact, we set the
+         * new node as a favorite, and we leave last_heard alone (even if it's zero).
+         */
+        if (config.device.role == meshtastic_Config_DeviceConfig_Role_CLIENT_BASE) {
+            // Special case for CLIENT_BASE: is_favorite has special meaning, and we don't want to automatically set it
+            // without the user doing so deliberately. We don't normally expect users to use a CLIENT_BASE to send DMs or to add
+            // contacts, but we should make sure it doesn't auto-favorite in case they do. Instead, as a workaround, we'll set
+            // last_heard to now, so that the add_contact node doesn't immediately get evicted.
+            info->last_heard = getTime();
+        } else {
+            // Normal case: set is_favorite to prevent expiration.
+            // last_heard will remain as-is (or remain 0 if this entry wasn't in the nodeDB).
+            info->is_favorite = true;
+        }
+
         // As the clients will begin sending the contact with DMs, we want to strictly check if the node is manually verified
         if (contact.manually_verified) {
             info->bitfield |= NODEINFO_BITFIELD_IS_KEY_MANUALLY_VERIFIED_MASK;
diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp
index d300ff53b..24fb8f1f9 100644
--- a/src/modules/AdminModule.cpp
+++ b/src/modules/AdminModule.cpp
@@ -104,9 +104,18 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta
             (config.security.admin_key[2].size == 32 &&
              memcmp(mp.public_key.bytes, config.security.admin_key[2].bytes, 32) == 0)) {
             LOG_INFO("PKC admin payload with authorized sender key");
+
+            // Automatically favorite the node that is using the admin key
             auto remoteNode = nodeDB->getMeshNode(mp.from);
             if (remoteNode && !remoteNode->is_favorite) {
-                remoteNode->is_favorite = true;
+                if (config.device.role == meshtastic_Config_DeviceConfig_Role_CLIENT_BASE) {
+                    // Special case for CLIENT_BASE: is_favorite has special meaning, and we don't want to automatically set it
+                    // without the user doing so deliberately.
+                    LOG_INFO("PKC admin valid, but not auto-favoriting node %x because role==CLIENT_BASE", mp.from);
+                } else {
+                    LOG_INFO("PKC admin valid. Auto-favoriting node %x", mp.from);
+                    remoteNode->is_favorite = true;
+                }
             }
         } else {
             myReply = allocErrorResponse(meshtastic_Routing_Error_ADMIN_PUBLIC_KEY_UNAUTHORIZED, &mp);

From 6cad39368801c6168e310c66052fc5de59bc02d7 Mon Sep 17 00:00:00 2001
From: Ford Jones <107664313+ford-jones@users.noreply.github.com>
Date: Fri, 7 Nov 2025 11:06:37 +1300
Subject: [PATCH 591/691] Persist favourites on NodeDB reset (#8292)

* Conditionally delete favourited nodes on reset

* trunk fmt

* Fix equality check, use existing macro for role validation

* Extend favourite persistence setting to devices of all roles

* Refactor: Decoupled role/config check and set role defaults appropriately

* Use American-English spelling

* Use existing reference

* Convert reset to bool, regen protos

* Add optional arg to nodedb_reset in favor of additional device setting

* Use correct proto commit ID

* Regen protos

* Log preservation status

* Pull latest from master
---
 src/mesh/NodeDB.cpp         | 17 +++++++++++++++--
 src/mesh/NodeDB.h           |  3 ++-
 src/modules/AdminModule.cpp |  7 ++++++-
 3 files changed, 23 insertions(+), 4 deletions(-)

diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp
index 443cc8eb7..76915397f 100644
--- a/src/mesh/NodeDB.cpp
+++ b/src/mesh/NodeDB.cpp
@@ -984,12 +984,25 @@ void NodeDB::installDefaultChannels()
     channelFile.version = DEVICESTATE_CUR_VER;
 }
 
-void NodeDB::resetNodes()
+void NodeDB::resetNodes(bool keepFavorites)
 {
     if (!config.position.fixed_position)
         clearLocalPosition();
     numMeshNodes = 1;
-    std::fill(nodeDatabase.nodes.begin() + 1, nodeDatabase.nodes.end(), meshtastic_NodeInfoLite());
+    if (keepFavorites) {
+        LOG_INFO("Clearing node database - preserving favorites");
+        for (size_t i = 0; i < meshNodes->size(); i++) {
+            meshtastic_NodeInfoLite &node = meshNodes->at(i);
+            if (i > 0 && !node.is_favorite) {
+                node = meshtastic_NodeInfoLite();
+            } else {
+                numMeshNodes += 1;
+            }
+        };
+    } else {
+        LOG_INFO("Clearing node database - removing favorites");
+        std::fill(nodeDatabase.nodes.begin() + 1, nodeDatabase.nodes.end(), meshtastic_NodeInfoLite());
+    }
     devicestate.has_rx_text_message = false;
     devicestate.has_rx_waypoint = false;
     saveNodeDatabaseToDisk();
diff --git a/src/mesh/NodeDB.h b/src/mesh/NodeDB.h
index e8724f2c9..444ac13e4 100644
--- a/src/mesh/NodeDB.h
+++ b/src/mesh/NodeDB.h
@@ -229,7 +229,8 @@ class NodeDB
      */
     size_t getNumOnlineMeshNodes(bool localOnly = false);
 
-    void initConfigIntervals(), initModuleConfigIntervals(), resetNodes(), removeNodeByNum(NodeNum nodeNum);
+    void initConfigIntervals(), initModuleConfigIntervals(), resetNodes(bool keepFavorites = false),
+        removeNodeByNum(NodeNum nodeNum);
 
     bool factoryReset(bool eraseBleBonds = false);
 
diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp
index 24fb8f1f9..a98515059 100644
--- a/src/modules/AdminModule.cpp
+++ b/src/modules/AdminModule.cpp
@@ -289,7 +289,12 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta
     case meshtastic_AdminMessage_nodedb_reset_tag: {
         disableBluetooth();
         LOG_INFO("Initiate node-db reset");
-        nodeDB->resetNodes();
+        //  CLIENT_BASE, ROUTER and ROUTER_LATE are able to preserve the remaining hop count when relaying a packet via a
+        //  favorited node, so ensure that their favorites are kept on reset
+        bool rolePreference =
+            isOneOf(config.device.role, meshtastic_Config_DeviceConfig_Role_CLIENT_BASE,
+                    meshtastic_Config_DeviceConfig_Role_ROUTER, meshtastic_Config_DeviceConfig_Role_ROUTER_LATE);
+        nodeDB->resetNodes(rolePreference ? rolePreference : r->nodedb_reset);
         reboot(DEFAULT_REBOOT_SECONDS);
         break;
     }

From 77e0a24838ffab446742af88799d7bc9cc7110af Mon Sep 17 00:00:00 2001
From: Benjamin Faershtein <119711889+RCGV1@users.noreply.github.com>
Date: Thu, 6 Nov 2025 19:01:15 -0800
Subject: [PATCH 592/691] Discard everything if downlink isn't on (#8578)

---
 src/mqtt/MQTT.cpp | 11 ++++++-----
 1 file changed, 6 insertions(+), 5 deletions(-)

diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp
index 33887557f..40d03de63 100644
--- a/src/mqtt/MQTT.cpp
+++ b/src/mqtt/MQTT.cpp
@@ -59,7 +59,13 @@ inline void onReceiveProto(char *topic, byte *payload, size_t length)
         LOG_ERROR("Invalid MQTT service envelope, topic %s, len %u!", topic, length);
         return;
     }
+
     const meshtastic_Channel &ch = channels.getByName(e.channel_id);
+    // Find channel by channel_id and check downlink_enabled
+    if (!(strcmp(e.channel_id, "PKI") == 0 ||
+          (strcmp(e.channel_id, channels.getGlobalId(ch.index)) == 0 && ch.settings.downlink_enabled))) {
+        return;
+    }
     // Generate node ID from nodenum for comparison
     std::string nodeId = nodeDB->getNodeId();
     if (strcmp(e.gateway_id, nodeId.c_str()) == 0) {
@@ -77,11 +83,6 @@ inline void onReceiveProto(char *topic, byte *payload, size_t length)
         return;
     }
 
-    // Find channel by channel_id and check downlink_enabled
-    if (!(strcmp(e.channel_id, "PKI") == 0 ||
-          (strcmp(e.channel_id, channels.getGlobalId(ch.index)) == 0 && ch.settings.downlink_enabled))) {
-        return;
-    }
     LOG_INFO("Received MQTT topic %s, len=%u", topic, length);
     if (e.packet->hop_limit > HOP_MAX || e.packet->hop_start > HOP_MAX) {
         LOG_INFO("Invalid hop_limit(%u) or hop_start(%u)", e.packet->hop_limit, e.packet->hop_start);

From 7eca061f018def60386bb20f99a1954a23c4cd2c Mon Sep 17 00:00:00 2001
From: Jonathan Bennett 
Date: Thu, 6 Nov 2025 21:01:30 -0600
Subject: [PATCH 593/691] Bugfix: Don't toggle BLE when choosing active state
 (#8579)

---
 src/graphics/draw/MenuHandler.cpp | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/src/graphics/draw/MenuHandler.cpp b/src/graphics/draw/MenuHandler.cpp
index 10c20cbd6..e1d309a10 100644
--- a/src/graphics/draw/MenuHandler.cpp
+++ b/src/graphics/draw/MenuHandler.cpp
@@ -936,7 +936,9 @@ void menuHandler::BluetoothToggleMenu()
     bannerOptions.optionsArrayPtr = optionsArray;
     bannerOptions.optionsCount = 3;
     bannerOptions.bannerCallback = [](int selected) -> void {
-        if (selected == 1 || selected == 2) {
+        if (selected == 0)
+            return;
+        else if (selected != (config.bluetooth.enabled ? 1 : 2)) {
             InputEvent event = {.inputEvent = (input_broker_event)170, .kbchar = 170, .touchX = 0, .touchY = 0};
             inputBroker->injectInputEvent(&event);
         }

From bdb3fb1477e5e9f8492c7c266181bab77d157389 Mon Sep 17 00:00:00 2001
From: Ford Jones <107664313+ford-jones@users.noreply.github.com>
Date: Fri, 7 Nov 2025 11:06:37 +1300
Subject: [PATCH 594/691] Persist favourites on NodeDB reset (#8292)

* Conditionally delete favourited nodes on reset

* trunk fmt

* Fix equality check, use existing macro for role validation

* Extend favourite persistence setting to devices of all roles

* Refactor: Decoupled role/config check and set role defaults appropriately

* Use American-English spelling

* Use existing reference

* Convert reset to bool, regen protos

* Add optional arg to nodedb_reset in favor of additional device setting

* Use correct proto commit ID

* Regen protos

* Log preservation status

* Pull latest from master
---
 src/mesh/NodeDB.cpp         | 17 +++++++++++++++--
 src/mesh/NodeDB.h           |  3 ++-
 src/modules/AdminModule.cpp |  7 ++++++-
 3 files changed, 23 insertions(+), 4 deletions(-)

diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp
index 8d30fb824..bda6f4ea4 100644
--- a/src/mesh/NodeDB.cpp
+++ b/src/mesh/NodeDB.cpp
@@ -978,12 +978,25 @@ void NodeDB::installDefaultChannels()
     channelFile.version = DEVICESTATE_CUR_VER;
 }
 
-void NodeDB::resetNodes()
+void NodeDB::resetNodes(bool keepFavorites)
 {
     if (!config.position.fixed_position)
         clearLocalPosition();
     numMeshNodes = 1;
-    std::fill(nodeDatabase.nodes.begin() + 1, nodeDatabase.nodes.end(), meshtastic_NodeInfoLite());
+    if (keepFavorites) {
+        LOG_INFO("Clearing node database - preserving favorites");
+        for (size_t i = 0; i < meshNodes->size(); i++) {
+            meshtastic_NodeInfoLite &node = meshNodes->at(i);
+            if (i > 0 && !node.is_favorite) {
+                node = meshtastic_NodeInfoLite();
+            } else {
+                numMeshNodes += 1;
+            }
+        };
+    } else {
+        LOG_INFO("Clearing node database - removing favorites");
+        std::fill(nodeDatabase.nodes.begin() + 1, nodeDatabase.nodes.end(), meshtastic_NodeInfoLite());
+    }
     devicestate.has_rx_text_message = false;
     devicestate.has_rx_waypoint = false;
     saveNodeDatabaseToDisk();
diff --git a/src/mesh/NodeDB.h b/src/mesh/NodeDB.h
index e8724f2c9..444ac13e4 100644
--- a/src/mesh/NodeDB.h
+++ b/src/mesh/NodeDB.h
@@ -229,7 +229,8 @@ class NodeDB
      */
     size_t getNumOnlineMeshNodes(bool localOnly = false);
 
-    void initConfigIntervals(), initModuleConfigIntervals(), resetNodes(), removeNodeByNum(NodeNum nodeNum);
+    void initConfigIntervals(), initModuleConfigIntervals(), resetNodes(bool keepFavorites = false),
+        removeNodeByNum(NodeNum nodeNum);
 
     bool factoryReset(bool eraseBleBonds = false);
 
diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp
index 24fb8f1f9..a98515059 100644
--- a/src/modules/AdminModule.cpp
+++ b/src/modules/AdminModule.cpp
@@ -289,7 +289,12 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta
     case meshtastic_AdminMessage_nodedb_reset_tag: {
         disableBluetooth();
         LOG_INFO("Initiate node-db reset");
-        nodeDB->resetNodes();
+        //  CLIENT_BASE, ROUTER and ROUTER_LATE are able to preserve the remaining hop count when relaying a packet via a
+        //  favorited node, so ensure that their favorites are kept on reset
+        bool rolePreference =
+            isOneOf(config.device.role, meshtastic_Config_DeviceConfig_Role_CLIENT_BASE,
+                    meshtastic_Config_DeviceConfig_Role_ROUTER, meshtastic_Config_DeviceConfig_Role_ROUTER_LATE);
+        nodeDB->resetNodes(rolePreference ? rolePreference : r->nodedb_reset);
         reboot(DEFAULT_REBOOT_SECONDS);
         break;
     }

From b25797e1b3d19ceec67493f801332ec53c4071cc Mon Sep 17 00:00:00 2001
From: Benjamin Faershtein <119711889+RCGV1@users.noreply.github.com>
Date: Thu, 6 Nov 2025 19:01:15 -0800
Subject: [PATCH 595/691] Discard everything if downlink isn't on (#8578)

---
 src/mqtt/MQTT.cpp | 11 ++++++-----
 1 file changed, 6 insertions(+), 5 deletions(-)

diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp
index 33887557f..40d03de63 100644
--- a/src/mqtt/MQTT.cpp
+++ b/src/mqtt/MQTT.cpp
@@ -59,7 +59,13 @@ inline void onReceiveProto(char *topic, byte *payload, size_t length)
         LOG_ERROR("Invalid MQTT service envelope, topic %s, len %u!", topic, length);
         return;
     }
+
     const meshtastic_Channel &ch = channels.getByName(e.channel_id);
+    // Find channel by channel_id and check downlink_enabled
+    if (!(strcmp(e.channel_id, "PKI") == 0 ||
+          (strcmp(e.channel_id, channels.getGlobalId(ch.index)) == 0 && ch.settings.downlink_enabled))) {
+        return;
+    }
     // Generate node ID from nodenum for comparison
     std::string nodeId = nodeDB->getNodeId();
     if (strcmp(e.gateway_id, nodeId.c_str()) == 0) {
@@ -77,11 +83,6 @@ inline void onReceiveProto(char *topic, byte *payload, size_t length)
         return;
     }
 
-    // Find channel by channel_id and check downlink_enabled
-    if (!(strcmp(e.channel_id, "PKI") == 0 ||
-          (strcmp(e.channel_id, channels.getGlobalId(ch.index)) == 0 && ch.settings.downlink_enabled))) {
-        return;
-    }
     LOG_INFO("Received MQTT topic %s, len=%u", topic, length);
     if (e.packet->hop_limit > HOP_MAX || e.packet->hop_start > HOP_MAX) {
         LOG_INFO("Invalid hop_limit(%u) or hop_start(%u)", e.packet->hop_limit, e.packet->hop_start);

From e76013fb60245ddabd2ff2a005da03fd3e9d7526 Mon Sep 17 00:00:00 2001
From: Ben Meadors 
Date: Fri, 7 Nov 2025 05:16:00 -0600
Subject: [PATCH 596/691] Try-fix traceroute panic (#8568)

---
 src/modules/TraceRouteModule.cpp | 214 +++++++++++++++++++------------
 src/modules/TraceRouteModule.h   |   8 ++
 2 files changed, 140 insertions(+), 82 deletions(-)

diff --git a/src/modules/TraceRouteModule.cpp b/src/modules/TraceRouteModule.cpp
index 5bdde1919..87a2f1bd2 100644
--- a/src/modules/TraceRouteModule.cpp
+++ b/src/modules/TraceRouteModule.cpp
@@ -11,6 +11,113 @@ extern graphics::Screen *screen;
 
 TraceRouteModule *traceRouteModule;
 
+void TraceRouteModule::setResultText(const String &text)
+{
+    resultText = text;
+    resultLines.clear();
+    resultLinesDirty = true;
+}
+
+void TraceRouteModule::clearResultLines()
+{
+    resultLines.clear();
+    resultLinesDirty = false;
+}
+#if HAS_SCREEN
+void TraceRouteModule::rebuildResultLines(OLEDDisplay *display)
+{
+    if (!display) {
+        resultLinesDirty = false;
+        return;
+    }
+
+    resultLines.clear();
+
+    if (resultText.length() == 0) {
+        resultLinesDirty = false;
+        return;
+    }
+
+    int maxWidth = display->getWidth() - 4;
+    if (maxWidth <= 0) {
+        resultLinesDirty = false;
+        return;
+    }
+
+    int start = 0;
+    int textLength = resultText.length();
+
+    while (start <= textLength) {
+        int newlinePos = resultText.indexOf('\n', start);
+        String segment;
+
+        if (newlinePos != -1) {
+            segment = resultText.substring(start, newlinePos);
+            start = newlinePos + 1;
+        } else {
+            segment = resultText.substring(start);
+            start = textLength + 1;
+        }
+
+        if (segment.length() == 0) {
+            resultLines.push_back("");
+            continue;
+        }
+
+        if (display->getStringWidth(segment) <= maxWidth) {
+            resultLines.push_back(segment);
+            continue;
+        }
+
+        String remaining = segment;
+
+        while (remaining.length() > 0) {
+            String tempLine = "";
+            int lastGoodBreak = -1;
+            bool lineComplete = false;
+
+            for (int i = 0; i < static_cast(remaining.length()); i++) {
+                char ch = remaining.charAt(i);
+                String testLine = tempLine + ch;
+
+                if (display->getStringWidth(testLine) > maxWidth) {
+                    if (lastGoodBreak >= 0) {
+                        resultLines.push_back(remaining.substring(0, lastGoodBreak + 1));
+                        remaining = remaining.substring(lastGoodBreak + 1);
+                        lineComplete = true;
+                        break;
+                    } else if (tempLine.length() > 0) {
+                        resultLines.push_back(tempLine);
+                        remaining = remaining.substring(i);
+                        lineComplete = true;
+                        break;
+                    } else {
+                        resultLines.push_back(String(ch));
+                        remaining = remaining.substring(i + 1);
+                        lineComplete = true;
+                        break;
+                    }
+                } else {
+                    tempLine = testLine;
+                    if (ch == ' ' || ch == '>' || ch == '<' || ch == '-' || ch == '(' || ch == ')' || ch == ',') {
+                        lastGoodBreak = i;
+                    }
+                }
+            }
+
+            if (!lineComplete) {
+                if (tempLine.length() > 0) {
+                    resultLines.push_back(tempLine);
+                }
+                break;
+            }
+        }
+    }
+
+    resultLinesDirty = false;
+}
+#endif
+
 bool TraceRouteModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_RouteDiscovery *r)
 {
     // We only alter the packet in alterReceivedProtobuf()
@@ -406,7 +513,7 @@ bool TraceRouteModule::startTraceRoute(NodeNum node)
     if (node == 0 || node == NODENUM_BROADCAST) {
         LOG_ERROR("Invalid node number for trace route: 0x%08x", node);
         runState = TRACEROUTE_STATE_RESULT;
-        resultText = "Invalid node";
+        setResultText("Invalid node");
         resultShowTime = millis();
         tracingNode = 0;
 
@@ -420,7 +527,7 @@ bool TraceRouteModule::startTraceRoute(NodeNum node)
     if (node == nodeDB->getNodeNum()) {
         LOG_ERROR("Cannot trace route to self: 0x%08x", node);
         runState = TRACEROUTE_STATE_RESULT;
-        resultText = "Cannot trace self";
+        setResultText("Cannot trace self");
         resultShowTime = millis();
         tracingNode = 0;
 
@@ -447,6 +554,8 @@ bool TraceRouteModule::startTraceRoute(NodeNum node)
         unsigned long wait = (cooldownMs - (now - lastTraceRouteTime)) / 1000;
         bannerText = String("Wait for ") + String(wait) + String("s");
         runState = TRACEROUTE_STATE_COOLDOWN;
+        resultText = "";
+        clearResultLines();
 
         requestFocus();
         UIFrameEvent e;
@@ -459,6 +568,8 @@ bool TraceRouteModule::startTraceRoute(NodeNum node)
     tracingNode = node;
     lastTraceRouteTime = now;
     runState = TRACEROUTE_STATE_TRACKING;
+    resultText = "";
+    clearResultLines();
     bannerText = String("Tracing ") + getNodeName(node);
 
     LOG_INFO("TraceRoute UI: Starting trace route to node 0x%08x, requesting focus", node);
@@ -501,7 +612,7 @@ bool TraceRouteModule::startTraceRoute(NodeNum node)
         } else {
             LOG_ERROR("MeshService is NULL!");
             runState = TRACEROUTE_STATE_RESULT;
-            resultText = "Service unavailable";
+            setResultText("Service unavailable");
             resultShowTime = millis();
             tracingNode = 0;
 
@@ -514,7 +625,7 @@ bool TraceRouteModule::startTraceRoute(NodeNum node)
     } else {
         LOG_ERROR("Failed to allocate TraceRoute packet from router");
         runState = TRACEROUTE_STATE_RESULT;
-        resultText = "Failed to send";
+        setResultText("Failed to send");
         resultShowTime = millis();
         tracingNode = 0;
 
@@ -532,7 +643,7 @@ void TraceRouteModule::launch(NodeNum node)
     if (node == 0 || node == NODENUM_BROADCAST) {
         LOG_ERROR("Invalid node number for trace route: 0x%08x", node);
         runState = TRACEROUTE_STATE_RESULT;
-        resultText = "Invalid node";
+        setResultText("Invalid node");
         resultShowTime = millis();
         tracingNode = 0;
 
@@ -546,7 +657,7 @@ void TraceRouteModule::launch(NodeNum node)
     if (node == nodeDB->getNodeNum()) {
         LOG_ERROR("Cannot trace route to self: 0x%08x", node);
         runState = TRACEROUTE_STATE_RESULT;
-        resultText = "Cannot trace self";
+        setResultText("Cannot trace self");
         resultShowTime = millis();
         tracingNode = 0;
 
@@ -568,6 +679,8 @@ void TraceRouteModule::launch(NodeNum node)
         unsigned long wait = (cooldownMs - (now - lastTraceRouteTime)) / 1000;
         bannerText = String("Wait for ") + String(wait) + String("s");
         runState = TRACEROUTE_STATE_COOLDOWN;
+        resultText = "";
+        clearResultLines();
 
         requestFocus();
         UIFrameEvent e;
@@ -580,6 +693,8 @@ void TraceRouteModule::launch(NodeNum node)
     runState = TRACEROUTE_STATE_TRACKING;
     tracingNode = node;
     lastTraceRouteTime = now;
+    resultText = "";
+    clearResultLines();
     bannerText = String("Tracing ") + getNodeName(node);
 
     requestFocus();
@@ -614,14 +729,14 @@ void TraceRouteModule::launch(NodeNum node)
         } else {
             LOG_ERROR("MeshService is NULL!");
             runState = TRACEROUTE_STATE_RESULT;
-            resultText = "Service unavailable";
+            setResultText("Service unavailable");
             resultShowTime = millis();
             tracingNode = 0;
         }
     } else {
         LOG_ERROR("Failed to allocate TraceRoute packet from router");
         runState = TRACEROUTE_STATE_RESULT;
-        resultText = "Failed to send";
+        setResultText("Failed to send");
         resultShowTime = millis();
         tracingNode = 0;
     }
@@ -629,7 +744,7 @@ void TraceRouteModule::launch(NodeNum node)
 
 void TraceRouteModule::handleTraceRouteResult(const String &result)
 {
-    resultText = result;
+    setResultText(result);
     runState = TRACEROUTE_STATE_RESULT;
     resultShowTime = millis();
     tracingNode = 0;
@@ -679,83 +794,15 @@ void TraceRouteModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state
         display->setFont(FONT_SMALL);
 
         if (resultText.length() > 0) {
-            std::vector lines;
-            String currentLine = "";
-            int maxWidth = display->getWidth() - 4;
-
-            int start = 0;
-            int newlinePos = resultText.indexOf('\n', start);
-
-            while (newlinePos != -1 || start < static_cast(resultText.length())) {
-                String segment;
-                if (newlinePos != -1) {
-                    segment = resultText.substring(start, newlinePos);
-                    start = newlinePos + 1;
-                    newlinePos = resultText.indexOf('\n', start);
-                } else {
-                    segment = resultText.substring(start);
-                    start = resultText.length();
-                }
-
-                if (display->getStringWidth(segment) <= maxWidth) {
-                    lines.push_back(segment);
-                } else {
-                    // Try to break at better positions (space, >, <, -)
-                    String remaining = segment;
-
-                    while (remaining.length() > 0) {
-                        String tempLine = "";
-                        int lastGoodBreak = -1;
-                        bool lineComplete = false;
-
-                        for (int i = 0; i < static_cast(remaining.length()); i++) {
-                            char ch = remaining.charAt(i);
-                            String testLine = tempLine + ch;
-
-                            if (display->getStringWidth(testLine) > maxWidth) {
-                                if (lastGoodBreak >= 0) {
-                                    // Break at the last good position
-                                    lines.push_back(remaining.substring(0, lastGoodBreak + 1));
-                                    remaining = remaining.substring(lastGoodBreak + 1);
-                                    lineComplete = true;
-                                    break;
-                                } else if (tempLine.length() > 0) {
-                                    lines.push_back(tempLine);
-                                    remaining = remaining.substring(i);
-                                    lineComplete = true;
-                                    break;
-                                } else {
-                                    // Single character exceeds width
-                                    lines.push_back(String(ch));
-                                    remaining = remaining.substring(i + 1);
-                                    lineComplete = true;
-                                    break;
-                                }
-                            } else {
-                                tempLine = testLine;
-                                // Mark good break positions
-                                if (ch == ' ' || ch == '>' || ch == '<' || ch == '-' || ch == '(' || ch == ')') {
-                                    lastGoodBreak = i;
-                                }
-                            }
-                        }
-
-                        if (!lineComplete) {
-                            // Reached end of remaining text
-                            if (tempLine.length() > 0) {
-                                lines.push_back(tempLine);
-                            }
-                            break;
-                        }
-                    }
-                }
+            if (resultLinesDirty) {
+                rebuildResultLines(display);
             }
 
             int lineHeight = FONT_HEIGHT_SMALL + 1; // Use proper font height with 1px spacing
-            for (size_t i = 0; i < lines.size(); i++) {
+            for (size_t i = 0; i < resultLines.size(); i++) {
                 int lineY = contentStartY + (i * lineHeight);
                 if (lineY + FONT_HEIGHT_SMALL <= display->getHeight()) {
-                    display->drawString(x + 2, lineY, lines[i]);
+                    display->drawString(x + 2, lineY, resultLines[i]);
                 }
             }
         }
@@ -779,7 +826,7 @@ int32_t TraceRouteModule::runOnce()
     if (runState == TRACEROUTE_STATE_TRACKING && now - lastTraceRouteTime > trackingTimeoutMs) {
         LOG_INFO("TraceRoute timeout, no response received");
         runState = TRACEROUTE_STATE_RESULT;
-        resultText = "No response received";
+        setResultText("No response received");
         resultShowTime = now;
         tracingNode = 0;
 
@@ -815,6 +862,8 @@ int32_t TraceRouteModule::runOnce()
             // Cooldown finished
             LOG_INFO("TraceRoute cooldown finished, returning to IDLE");
             runState = TRACEROUTE_STATE_IDLE;
+            resultText = "";
+            clearResultLines();
             bannerText = "";
             UIFrameEvent e;
             e.action = UIFrameEvent::Action::REGENERATE_FRAMESET;
@@ -828,6 +877,7 @@ int32_t TraceRouteModule::runOnce()
             LOG_INFO("TraceRoute result display timeout, returning to IDLE");
             runState = TRACEROUTE_STATE_IDLE;
             resultText = "";
+            clearResultLines();
             bannerText = "";
             tracingNode = 0;
             UIFrameEvent e;
diff --git a/src/modules/TraceRouteModule.h b/src/modules/TraceRouteModule.h
index dac422388..a40ed7733 100644
--- a/src/modules/TraceRouteModule.h
+++ b/src/modules/TraceRouteModule.h
@@ -7,6 +7,7 @@
 #if HAS_SCREEN
 #include "OLEDDisplayUi.h"
 #endif
+#include 
 
 #define ROUTE_SIZE sizeof(((meshtastic_RouteDiscovery *)0)->route) / sizeof(((meshtastic_RouteDiscovery *)0)->route[0])
 
@@ -49,6 +50,11 @@ class TraceRouteModule : public ProtobufModule,
     virtual int32_t runOnce() override;
 
   private:
+    void setResultText(const String &text);
+    void clearResultLines();
+#if HAS_SCREEN
+    void rebuildResultLines(OLEDDisplay *display);
+#endif
     // Call to add unknown hops (e.g. when a node couldn't decrypt it) to the route based on hopStart and current hopLimit
     void insertUnknownHops(meshtastic_MeshPacket &p, meshtastic_RouteDiscovery *r, bool isTowardsDestination);
 
@@ -74,6 +80,8 @@ class TraceRouteModule : public ProtobufModule,
     unsigned long trackingTimeoutMs = 10000;
     String bannerText;
     String resultText;
+    std::vector resultLines;
+    bool resultLinesDirty = false;
     NodeNum tracingNode = 0;
     bool initialized = false;
 };

From 85afd706fd31ddde11f6db6b2259dc151891abcb Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Fri, 7 Nov 2025 05:33:36 -0600
Subject: [PATCH 597/691] chore(deps): update meshtastic/device-ui digest to
 28167c6 (#8583)

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 405fb040b..d62504ae3 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -120,7 +120,7 @@ lib_deps =
 [device-ui_base]
 lib_deps =
 	# renovate: datasource=git-refs depName=meshtastic/device-ui packageName=https://github.com/meshtastic/device-ui gitBranch=master
-	https://github.com/meshtastic/device-ui/archive/19b7855e9a1d9deff37391659ca7194e4ef57c43.zip
+	https://github.com/meshtastic/device-ui/archive/28167c67dfd13015a0b5eef1828f95fe8e3ab7c3.zip
 
 ; Common libs for environmental measurements in telemetry module
 [environmental_base]

From b7070018731319bb4bcf3127329ef1de81bfb779 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
 <41898282+github-actions[bot]@users.noreply.github.com>
Date: Fri, 7 Nov 2025 05:33:54 -0600
Subject: [PATCH 598/691] Upgrade trunk (#8552)

Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com>
---
 .trunk/trunk.yaml | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml
index 46916bf29..73baa5345 100644
--- a/.trunk/trunk.yaml
+++ b/.trunk/trunk.yaml
@@ -8,10 +8,10 @@ plugins:
       uri: https://github.com/trunk-io/plugins
 lint:
   enabled:
-    - checkov@3.2.489
-    - renovate@41.169.1
+    - checkov@3.2.490
+    - renovate@41.173.1
     - prettier@3.6.2
-    - trufflehog@3.90.12
+    - trufflehog@3.90.13
     - yamllint@1.37.1
     - bandit@1.8.6
     - trivy@0.67.2
@@ -28,7 +28,7 @@ lint:
     - shellcheck@0.11.0
     - black@25.9.0
     - git-diff-check
-    - gitleaks@8.28.0
+    - gitleaks@8.29.0
     - clang-format@16.0.3
   ignore:
     - linters: [ALL]

From 531cad5e8873639d8fe147d2f58f3dbf554d9610 Mon Sep 17 00:00:00 2001
From: Jonathan Bennett 
Date: Fri, 7 Nov 2025 15:03:56 -0600
Subject: [PATCH 599/691] Add API types, state, and log message in Debug
 screen. Added persistent "Connected" icon (#8576)

* Add API types, state, and log message in Debug screen

* un-goober the API state tracking

* Set the SerialConsole api_type

* Add api_type for Ethernet

* Remove API state debugging code

* Update wording for client connection states

* Improve string width for smaller screen devices

* Reserve space on navigation bar to fit link indicator

* Add persistent Connected icon to screen

* Connect System frame to ensure text doesn't overflow

---------

Co-authored-by: Ben Meadors 
Co-authored-by: Jason P 
Co-authored-by: HarukiToreda <116696711+HarukiToreda@users.noreply.github.com>
---
 src/SerialConsole.cpp                         |  1 +
 src/graphics/SharedUIDisplay.cpp              | 39 ++++++++++++++
 src/graphics/SharedUIDisplay.h                |  3 ++
 src/graphics/draw/ClockRenderer.cpp           |  3 ++
 src/graphics/draw/DebugRenderer.cpp           | 47 ++++++++++++++---
 src/graphics/draw/MessageRenderer.cpp         |  2 +
 src/graphics/draw/NodeListRenderer.cpp        |  1 +
 src/graphics/draw/UIRenderer.cpp              | 52 ++++++++++++++++++-
 src/graphics/images.h                         |  4 ++
 src/mesh/MeshService.h                        | 12 +++++
 src/mesh/PhoneAPI.cpp                         | 25 +++++++++
 src/mesh/PhoneAPI.h                           | 12 +++++
 src/mesh/api/PacketAPI.cpp                    |  1 +
 src/mesh/api/WiFiServerAPI.cpp                |  1 +
 src/mesh/api/ethServerAPI.cpp                 |  1 +
 src/mesh/http/ContentHandler.h                |  2 +-
 src/mesh/raspihttp/PiWebServer.h              |  2 +-
 src/modules/SerialModule.cpp                  | 15 ++++--
 .../Telemetry/EnvironmentTelemetry.cpp        |  1 +
 src/modules/Telemetry/PowerTelemetry.cpp      |  1 +
 src/modules/esp32/PaxcounterModule.cpp        |  1 +
 src/nimble/NimbleBluetooth.cpp                |  2 +-
 src/platform/nrf52/NRF52Bluetooth.cpp         |  3 ++
 23 files changed, 218 insertions(+), 13 deletions(-)

diff --git a/src/SerialConsole.cpp b/src/SerialConsole.cpp
index fad0fb92f..dd2acb599 100644
--- a/src/SerialConsole.cpp
+++ b/src/SerialConsole.cpp
@@ -50,6 +50,7 @@ void consolePrintf(const char *format, ...)
 
 SerialConsole::SerialConsole() : StreamAPI(&Port), RedirectablePrint(&Port), concurrency::OSThread("SerialConsole")
 {
+    api_type = TYPE_SERIAL;
     assert(!console);
     console = this;
     canWrite = false; // We don't send packets to our port until it has talked to us first
diff --git a/src/graphics/SharedUIDisplay.cpp b/src/graphics/SharedUIDisplay.cpp
index 8e1299f51..1645789a7 100644
--- a/src/graphics/SharedUIDisplay.cpp
+++ b/src/graphics/SharedUIDisplay.cpp
@@ -1,6 +1,8 @@
 #include "configuration.h"
 #if HAS_SCREEN
+#include "MeshService.h"
 #include "RTC.h"
+#include "draw/NodeListRenderer.h"
 #include "graphics/ScreenFonts.h"
 #include "graphics/SharedUIDisplay.h"
 #include "graphics/draw/UIRenderer.h"
@@ -398,6 +400,43 @@ const int *getTextPositions(OLEDDisplay *display)
     return textPositions;
 }
 
+// *************************
+// * Common Footer Drawing *
+// *************************
+void drawCommonFooter(OLEDDisplay *display, int16_t x, int16_t y)
+{
+    bool drawConnectionState = false;
+    if (service->api_state == service->STATE_BLE || service->api_state == service->STATE_WIFI ||
+        service->api_state == service->STATE_SERIAL || service->api_state == service->STATE_PACKET ||
+        service->api_state == service->STATE_HTTP || service->api_state == service->STATE_ETH) {
+        drawConnectionState = true;
+    }
+
+    if (drawConnectionState) {
+        if (isHighResolution) {
+            const int scale = 2;
+            const int bytesPerRow = (connection_icon_width + 7) / 8;
+            int iconX = 0;
+            int iconY = SCREEN_HEIGHT - (connection_icon_height * 2);
+
+            for (int yy = 0; yy < connection_icon_height; ++yy) {
+                const uint8_t *rowPtr = connection_icon + yy * bytesPerRow;
+                for (int xx = 0; xx < connection_icon_width; ++xx) {
+                    const uint8_t byteVal = pgm_read_byte(rowPtr + (xx >> 3));
+                    const uint8_t bitMask = 1U << (xx & 7); // XBM is LSB-first
+                    if (byteVal & bitMask) {
+                        display->fillRect(iconX + xx * scale, iconY + yy * scale, scale, scale);
+                    }
+                }
+            }
+
+        } else {
+            display->drawXbm(0, SCREEN_HEIGHT - connection_icon_height, connection_icon_width, connection_icon_height,
+                             connection_icon);
+        }
+    }
+}
+
 bool isAllowedPunctuation(char c)
 {
     const std::string allowed = ".,!?;:-_()[]{}'\"@#$/\\&+=%~^ ";
diff --git a/src/graphics/SharedUIDisplay.h b/src/graphics/SharedUIDisplay.h
index e1a7c6383..b51dfea36 100644
--- a/src/graphics/SharedUIDisplay.h
+++ b/src/graphics/SharedUIDisplay.h
@@ -52,6 +52,9 @@ void drawRoundedHighlight(OLEDDisplay *display, int16_t x, int16_t y, int16_t w,
 void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *titleStr = "", bool force_no_invert = false,
                       bool show_date = false);
 
+// Shared battery/time/mail header
+void drawCommonFooter(OLEDDisplay *display, int16_t x, int16_t y);
+
 const int *getTextPositions(OLEDDisplay *display);
 
 bool isAllowedPunctuation(char c);
diff --git a/src/graphics/draw/ClockRenderer.cpp b/src/graphics/draw/ClockRenderer.cpp
index 751db8d88..97417571b 100644
--- a/src/graphics/draw/ClockRenderer.cpp
+++ b/src/graphics/draw/ClockRenderer.cpp
@@ -302,6 +302,8 @@ void drawDigitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int1
     display->drawString(startingHourMinuteTextX + timeStringWidth - xOffset, (display->getHeight() - hourMinuteTextY) - yOffset,
                         secondString);
 #endif
+
+    graphics::drawCommonFooter(display, x, y);
 }
 
 void drawBluetoothConnectedIcon(OLEDDisplay *display, int16_t x, int16_t y)
@@ -516,6 +518,7 @@ void drawAnalogClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
         display->drawLine(centerX, centerY, secondX, secondY);
 #endif
     }
+    graphics::drawCommonFooter(display, x, y);
 }
 
 } // namespace ClockRenderer
diff --git a/src/graphics/draw/DebugRenderer.cpp b/src/graphics/draw/DebugRenderer.cpp
index 60abd661e..d098fa304 100644
--- a/src/graphics/draw/DebugRenderer.cpp
+++ b/src/graphics/draw/DebugRenderer.cpp
@@ -3,6 +3,7 @@
 #include "../Screen.h"
 #include "DebugRenderer.h"
 #include "FSCommon.h"
+#include "MeshService.h"
 #include "NodeDB.h"
 #include "Throttle.h"
 #include "UIRenderer.h"
@@ -223,6 +224,8 @@ void drawFrameWiFi(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, i
 
     display->drawString(x, getTextPositions(display)[line++], "URL: http://meshtastic.local");
 
+    graphics::drawCommonFooter(display, x, y);
+
     /* Display a heartbeat pixel that blinks every time the frame is redrawn */
 #ifdef SHOW_REDRAWS
     if (heartbeat)
@@ -503,6 +506,7 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
     display->drawString(starting_position + chUtil_x + chutil_bar_width + extraoffset, getTextPositions(display)[line++],
                         chUtilPercentage);
 #endif
+    graphics::drawCommonFooter(display, x, y);
 }
 
 // ****************************
@@ -642,10 +646,9 @@ void drawSystemScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x
     int textWidth = display->getStringWidth(appversionstr);
     int nameX = (SCREEN_WIDTH - textWidth) / 2;
 
-    display->drawString(nameX, getTextPositions(display)[line], appversionstr);
-#if !defined(M5STACK_UNITC6L)
-    if (SCREEN_HEIGHT > 64 || (SCREEN_HEIGHT <= 64 && line < 4)) { // Only show uptime if the screen can show it
-        line += 1;
+    display->drawString(nameX, getTextPositions(display)[line++], appversionstr);
+
+    if (SCREEN_HEIGHT > 64 || (SCREEN_HEIGHT <= 64 && line <= 5)) { // Only show uptime if the screen can show it
         char uptimeStr[32] = "";
         uint32_t uptime = millis() / 1000;
         uint32_t days = uptime / 86400;
@@ -660,9 +663,41 @@ void drawSystemScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x
             snprintf(uptimeStr, sizeof(uptimeStr), " Uptime: %um", mins);
         textWidth = display->getStringWidth(uptimeStr);
         nameX = (SCREEN_WIDTH - textWidth) / 2;
-        display->drawString(nameX, getTextPositions(display)[line], uptimeStr);
+        display->drawString(nameX, getTextPositions(display)[line++], uptimeStr);
     }
-#endif
+
+    if (SCREEN_HEIGHT > 64 || (SCREEN_HEIGHT <= 64 && line <= 5)) { // Only show API state if the screen can show it
+        char api_state[32] = "";
+        const char *clientWord = nullptr;
+
+        // Determine if narrow or wide screen
+        if (isHighResolution) {
+            clientWord = "Client";
+        } else {
+            clientWord = "App";
+        }
+        snprintf(api_state, sizeof(api_state), "No %ss Connected", clientWord);
+
+        if (service->api_state == service->STATE_BLE) {
+            snprintf(api_state, sizeof(api_state), "%s Connected (BLE)", clientWord);
+        } else if (service->api_state == service->STATE_WIFI) {
+            snprintf(api_state, sizeof(api_state), "%s Connected (WiFi)", clientWord);
+        } else if (service->api_state == service->STATE_SERIAL) {
+            snprintf(api_state, sizeof(api_state), "%s Connected (Serial)", clientWord);
+        } else if (service->api_state == service->STATE_PACKET) {
+            snprintf(api_state, sizeof(api_state), "%s Connected (Internal)", clientWord);
+        } else if (service->api_state == service->STATE_HTTP) {
+            snprintf(api_state, sizeof(api_state), "%s Connected (HTTP)", clientWord);
+        } else if (service->api_state == service->STATE_ETH) {
+            snprintf(api_state, sizeof(api_state), "%s Connected (Ethernet)", clientWord);
+        }
+        if (api_state[0] != '\0') {
+            display->drawString((SCREEN_WIDTH - display->getStringWidth(api_state)) / 2, getTextPositions(display)[line++],
+                                api_state);
+        }
+    }
+
+    graphics::drawCommonFooter(display, x, y);
 }
 
 // ****************************
diff --git a/src/graphics/draw/MessageRenderer.cpp b/src/graphics/draw/MessageRenderer.cpp
index 6971826de..da6ec7abc 100644
--- a/src/graphics/draw/MessageRenderer.cpp
+++ b/src/graphics/draw/MessageRenderer.cpp
@@ -213,6 +213,7 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
 #else
         display->drawString(center_text, getTextPositions(display)[2], messageString);
 #endif
+        graphics::drawCommonFooter(display, x, y);
         return;
     }
 
@@ -423,6 +424,7 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
     // Draw header at the end to sort out overlapping elements
     graphics::drawCommonHeader(display, x, y, titleStr);
 #endif
+    graphics::drawCommonFooter(display, x, y);
 }
 
 std::vector generateLines(OLEDDisplay *display, const char *headerStr, const char *messageBuf, int textWidth)
diff --git a/src/graphics/draw/NodeListRenderer.cpp b/src/graphics/draw/NodeListRenderer.cpp
index 2a2f71dba..1a36a6188 100644
--- a/src/graphics/draw/NodeListRenderer.cpp
+++ b/src/graphics/draw/NodeListRenderer.cpp
@@ -505,6 +505,7 @@ void drawNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t
 #endif
     const int scrollStartY = y + 3;
     drawScrollbar(display, visibleNodeRows, totalEntries, scrollIndex, 2, scrollStartY);
+    graphics::drawCommonFooter(display, x, y);
 }
 
 // =============================
diff --git a/src/graphics/draw/UIRenderer.cpp b/src/graphics/draw/UIRenderer.cpp
index 1ff183779..538c32842 100644
--- a/src/graphics/draw/UIRenderer.cpp
+++ b/src/graphics/draw/UIRenderer.cpp
@@ -552,6 +552,7 @@ void UIRenderer::drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *st
         // else show nothing
     }
 #endif
+    graphics::drawCommonFooter(display, x, y);
 }
 
 // ****************************
@@ -771,6 +772,7 @@ void UIRenderer::drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *sta
         display->drawString(nameX, getTextPositions(display)[line++], shortnameble);
     }
 #endif
+    graphics::drawCommonFooter(display, x, y);
 }
 
 // Start Functions to write date/time to the screen
@@ -1183,6 +1185,7 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU
     }
 #endif
 #endif // HAS_GPS
+    graphics::drawCommonFooter(display, x, y);
 }
 
 #ifdef USERPREFS_OEM_TEXT
@@ -1267,7 +1270,13 @@ void UIRenderer::drawNavigationBar(OLEDDisplay *display, OLEDDisplayUiState *sta
     if (totalIcons == 0)
         return;
 
-    const size_t iconsPerPage = (SCREEN_WIDTH + spacing) / (iconSize + spacing);
+    const int navPadding = isHighResolution ? 24 : 12; // padding per side
+
+    int usableWidth = SCREEN_WIDTH - (navPadding * 2);
+    if (usableWidth < iconSize)
+        usableWidth = iconSize;
+
+    const size_t iconsPerPage = usableWidth / (iconSize + spacing);
     const size_t currentPage = currentFrame / iconsPerPage;
     const size_t pageStart = currentPage * iconsPerPage;
     const size_t pageEnd = min(pageStart + iconsPerPage, totalIcons);
@@ -1338,6 +1347,47 @@ void UIRenderer::drawNavigationBar(OLEDDisplay *display, OLEDDisplayUiState *sta
             display->setColor(WHITE);
         }
     }
+
+    // Compact arrow drawer
+    auto drawArrow = [&](bool rightSide) {
+        display->setColor(WHITE);
+
+        const int offset = isHighResolution ? 3 : 1;
+        const int halfH = rectHeight / 2;
+
+        const int top = (y - 2) + (rectHeight - halfH) / 2;
+        const int bottom = top + halfH - 1;
+        const int midY = top + (halfH / 2);
+
+        const int maxW = 4;
+
+        // Determine left X coordinate
+        int baseX = rightSide ? (rectX + rectWidth + offset) : // right arrow
+                        (rectX - offset - 1);                  // left arrow
+
+        for (int yy = top; yy <= bottom; yy++) {
+            int dist = abs(yy - midY);
+            int lineW = maxW - (dist * maxW / (halfH / 2));
+            if (lineW < 1)
+                lineW = 1;
+
+            if (rightSide) {
+                display->drawHorizontalLine(baseX, yy, lineW);
+            } else {
+                display->drawHorizontalLine(baseX - lineW + 1, yy, lineW);
+            }
+        }
+    };
+    // Right arrow
+    if (pageEnd < totalIcons) {
+        drawArrow(true);
+    }
+
+    // Left arrow
+    if (pageStart > 0) {
+        drawArrow(false);
+    }
+
     // Knock the corners off the square
     display->setColor(BLACK);
     display->drawRect(rectX, y - 2, 1, 1);
diff --git a/src/graphics/images.h b/src/graphics/images.h
index b5010b116..8670d78d9 100644
--- a/src/graphics/images.h
+++ b/src/graphics/images.h
@@ -360,6 +360,10 @@ const uint8_t chirpy_hirez[] = {
 #define chirpy_small_image_height 8
 const uint8_t chirpy_small[] = {0x7f, 0x41, 0x55, 0x55, 0x55, 0x55, 0x41, 0x7f};
 
+#define connection_icon_width 7
+#define connection_icon_height 5
+const uint8_t connection_icon[] = {0x36, 0x41, 0x5D, 0x41, 0x36};
+
 #ifdef M5STACK_UNITC6L
 #include "img/icon_small.xbm"
 #else
diff --git a/src/mesh/MeshService.h b/src/mesh/MeshService.h
index 66d9d9679..71fb544a0 100644
--- a/src/mesh/MeshService.h
+++ b/src/mesh/MeshService.h
@@ -79,6 +79,18 @@ class MeshService
     uint32_t oldFromNum = 0;
 
   public:
+    enum APIState {
+        STATE_DISCONNECTED, // Initial state, no API is connected
+        STATE_BLE,
+        STATE_WIFI,
+        STATE_SERIAL,
+        STATE_PACKET,
+        STATE_HTTP,
+        STATE_ETH
+    };
+
+    APIState api_state = STATE_DISCONNECTED;
+
     static bool isTextPayload(const meshtastic_MeshPacket *p)
     {
         if (moduleConfig.range_test.enabled && p->decoded.portnum == meshtastic_PortNum_RANGE_TEST_APP) {
diff --git a/src/mesh/PhoneAPI.cpp b/src/mesh/PhoneAPI.cpp
index d1e342c80..9050ee89d 100644
--- a/src/mesh/PhoneAPI.cpp
+++ b/src/mesh/PhoneAPI.cpp
@@ -87,6 +87,18 @@ void PhoneAPI::handleStartConfig()
 void PhoneAPI::close()
 {
     LOG_DEBUG("PhoneAPI::close()");
+    if (service->api_state == service->STATE_BLE && api_type == TYPE_BLE)
+        service->api_state = service->STATE_DISCONNECTED;
+    else if (service->api_state == service->STATE_WIFI && api_type == TYPE_WIFI)
+        service->api_state = service->STATE_DISCONNECTED;
+    else if (service->api_state == service->STATE_SERIAL && api_type == TYPE_SERIAL)
+        service->api_state = service->STATE_DISCONNECTED;
+    else if (service->api_state == service->STATE_PACKET && api_type == TYPE_PACKET)
+        service->api_state = service->STATE_DISCONNECTED;
+    else if (service->api_state == service->STATE_HTTP && api_type == TYPE_HTTP)
+        service->api_state = service->STATE_DISCONNECTED;
+    else if (service->api_state == service->STATE_ETH && api_type == TYPE_ETH)
+        service->api_state = service->STATE_DISCONNECTED;
 
     if (state != STATE_SEND_NOTHING) {
         state = STATE_SEND_NOTHING;
@@ -578,6 +590,19 @@ void PhoneAPI::sendConfigComplete()
     fromRadioScratch.config_complete_id = config_nonce;
     config_nonce = 0;
     state = STATE_SEND_PACKETS;
+    if (api_type == TYPE_BLE) {
+        service->api_state = service->STATE_BLE;
+    } else if (api_type == TYPE_WIFI) {
+        service->api_state = service->STATE_WIFI;
+    } else if (api_type == TYPE_SERIAL) {
+        service->api_state = service->STATE_SERIAL;
+    } else if (api_type == TYPE_PACKET) {
+        service->api_state = service->STATE_PACKET;
+    } else if (api_type == TYPE_HTTP) {
+        service->api_state = service->STATE_HTTP;
+    } else if (api_type == TYPE_ETH) {
+        service->api_state = service->STATE_ETH;
+    }
 
     // Allow subclasses to know we've entered steady-state so they can lower power consumption
     onConfigComplete();
diff --git a/src/mesh/PhoneAPI.h b/src/mesh/PhoneAPI.h
index d6682684f..7f79b5792 100644
--- a/src/mesh/PhoneAPI.h
+++ b/src/mesh/PhoneAPI.h
@@ -167,6 +167,18 @@ class PhoneAPI
     /// begin a new connection
     void handleStartConfig();
 
+    enum APIType {
+        TYPE_NONE, // Initial state, don't send anything until the client starts asking for config
+        TYPE_BLE,
+        TYPE_WIFI,
+        TYPE_SERIAL,
+        TYPE_PACKET,
+        TYPE_HTTP,
+        TYPE_ETH
+    };
+
+    APIType api_type = TYPE_NONE;
+
   private:
     void releasePhonePacket();
 
diff --git a/src/mesh/api/PacketAPI.cpp b/src/mesh/api/PacketAPI.cpp
index ab380d696..f4d5de540 100644
--- a/src/mesh/api/PacketAPI.cpp
+++ b/src/mesh/api/PacketAPI.cpp
@@ -19,6 +19,7 @@ PacketAPI *PacketAPI::create(PacketServer *_server)
 PacketAPI::PacketAPI(PacketServer *_server)
     : concurrency::OSThread("PacketAPI"), isConnected(false), programmingMode(false), server(_server)
 {
+    api_type = TYPE_PACKET;
 }
 
 int32_t PacketAPI::runOnce()
diff --git a/src/mesh/api/WiFiServerAPI.cpp b/src/mesh/api/WiFiServerAPI.cpp
index b19194f78..4d729f5c7 100644
--- a/src/mesh/api/WiFiServerAPI.cpp
+++ b/src/mesh/api/WiFiServerAPI.cpp
@@ -25,6 +25,7 @@ void deInitApiServer()
 
 WiFiServerAPI::WiFiServerAPI(WiFiClient &_client) : ServerAPI(_client)
 {
+    api_type = TYPE_WIFI;
     LOG_INFO("Incoming wifi connection");
 }
 
diff --git a/src/mesh/api/ethServerAPI.cpp b/src/mesh/api/ethServerAPI.cpp
index 0ccf92df7..10ff06df2 100644
--- a/src/mesh/api/ethServerAPI.cpp
+++ b/src/mesh/api/ethServerAPI.cpp
@@ -20,6 +20,7 @@ void initApiServer(int port)
 ethServerAPI::ethServerAPI(EthernetClient &_client) : ServerAPI(_client)
 {
     LOG_INFO("Incoming ethernet connection");
+    api_type = TYPE_ETH;
 }
 
 ethServerPort::ethServerPort(int port) : APIServerPort(port) {}
diff --git a/src/mesh/http/ContentHandler.h b/src/mesh/http/ContentHandler.h
index 2066a6d57..91cad3359 100644
--- a/src/mesh/http/ContentHandler.h
+++ b/src/mesh/http/ContentHandler.h
@@ -26,7 +26,7 @@ class HttpAPI : public PhoneAPI
 {
 
   public:
-    // Nothing here yet
+    HttpAPI() { api_type = TYPE_HTTP; }
 
   private:
     // Nothing here yet
diff --git a/src/mesh/raspihttp/PiWebServer.h b/src/mesh/raspihttp/PiWebServer.h
index b45348cf3..5a4adedaa 100644
--- a/src/mesh/raspihttp/PiWebServer.h
+++ b/src/mesh/raspihttp/PiWebServer.h
@@ -27,7 +27,7 @@ class HttpAPI : public PhoneAPI
 {
 
   public:
-    // Nothing here yet
+    HttpAPI() { api_type = TYPE_HTTP; }
 
   private:
     // Nothing here yet
diff --git a/src/modules/SerialModule.cpp b/src/modules/SerialModule.cpp
index a9ec8f6a8..575e9fa96 100644
--- a/src/modules/SerialModule.cpp
+++ b/src/modules/SerialModule.cpp
@@ -65,13 +65,22 @@ SerialModuleRadio *serialModuleRadio;
 
 #if defined(TTGO_T_ECHO) || defined(CANARYONE) || defined(MESHLINK) || defined(ELECROW_ThinkNode_M1) ||                          \
     defined(ELECROW_ThinkNode_M5) || defined(HELTEC_MESH_SOLAR) || defined(T_ECHO_LITE)
-SerialModule::SerialModule() : StreamAPI(&Serial), concurrency::OSThread("Serial") {}
+SerialModule::SerialModule() : StreamAPI(&Serial), concurrency::OSThread("Serial")
+{
+    api_type = TYPE_SERIAL;
+}
 static Print *serialPrint = &Serial;
 #elif defined(CONFIG_IDF_TARGET_ESP32C6) || defined(RAK3172) || defined(EBYTE_E77_MBL)
-SerialModule::SerialModule() : StreamAPI(&Serial1), concurrency::OSThread("Serial") {}
+SerialModule::SerialModule() : StreamAPI(&Serial1), concurrency::OSThread("Serial")
+{
+    api_type = TYPE_SERIAL;
+}
 static Print *serialPrint = &Serial1;
 #else
-SerialModule::SerialModule() : StreamAPI(&Serial2), concurrency::OSThread("Serial") {}
+SerialModule::SerialModule() : StreamAPI(&Serial2), concurrency::OSThread("Serial")
+{
+    api_type = TYPE_SERIAL;
+}
 static Print *serialPrint = &Serial2;
 #endif
 
diff --git a/src/modules/Telemetry/EnvironmentTelemetry.cpp b/src/modules/Telemetry/EnvironmentTelemetry.cpp
index a923ab457..29e815092 100644
--- a/src/modules/Telemetry/EnvironmentTelemetry.cpp
+++ b/src/modules/Telemetry/EnvironmentTelemetry.cpp
@@ -517,6 +517,7 @@ void EnvironmentTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiSt
 
         currentY += rowHeight;
     }
+    graphics::drawCommonFooter(display, x, y);
 }
 #endif
 
diff --git a/src/modules/Telemetry/PowerTelemetry.cpp b/src/modules/Telemetry/PowerTelemetry.cpp
index e69ee3931..29dd1def8 100644
--- a/src/modules/Telemetry/PowerTelemetry.cpp
+++ b/src/modules/Telemetry/PowerTelemetry.cpp
@@ -165,6 +165,7 @@ void PowerTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *s
     if (m.has_ch3_voltage || m.has_ch3_current) {
         drawLine("Ch3", m.ch3_voltage, m.ch3_current);
     }
+    graphics::drawCommonFooter(display, x, y);
 }
 #endif
 
diff --git a/src/modules/esp32/PaxcounterModule.cpp b/src/modules/esp32/PaxcounterModule.cpp
index 8b1fc5302..9c25177bc 100644
--- a/src/modules/esp32/PaxcounterModule.cpp
+++ b/src/modules/esp32/PaxcounterModule.cpp
@@ -141,6 +141,7 @@ void PaxcounterModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state
     display->drawStringf(display->getWidth() / 2 + x, graphics::getTextPositions(display)[line++], buffer,
                          "WiFi: %d\nBLE: %d\nUptime: %ds", count_from_libpax.wifi_count, count_from_libpax.ble_count,
                          millis() / 1000);
+    graphics::drawCommonFooter(display, x, y);
 }
 #endif // HAS_SCREEN
 
diff --git a/src/nimble/NimbleBluetooth.cpp b/src/nimble/NimbleBluetooth.cpp
index 6238031f6..76cde3cae 100644
--- a/src/nimble/NimbleBluetooth.cpp
+++ b/src/nimble/NimbleBluetooth.cpp
@@ -118,7 +118,7 @@ class BluetoothPhoneAPI : public PhoneAPI, public concurrency::OSThread
     */
 
   public:
-    BluetoothPhoneAPI() : concurrency::OSThread("NimbleBluetooth") {}
+    BluetoothPhoneAPI() : concurrency::OSThread("NimbleBluetooth") { api_type = TYPE_BLE; }
 
     /* Packets from phone (BLE onWrite callback) */
     std::mutex fromPhoneMutex;
diff --git a/src/platform/nrf52/NRF52Bluetooth.cpp b/src/platform/nrf52/NRF52Bluetooth.cpp
index 79eef8f76..4f7fb4776 100644
--- a/src/platform/nrf52/NRF52Bluetooth.cpp
+++ b/src/platform/nrf52/NRF52Bluetooth.cpp
@@ -48,6 +48,9 @@ class BluetoothPhoneAPI : public PhoneAPI
 
     /// Check the current underlying physical link to see if the client is currently connected
     virtual bool checkIsConnected() override { return Bluefruit.connected(connectionHandle); }
+
+  public:
+    BluetoothPhoneAPI() { api_type = TYPE_BLE; }
 };
 
 static BluetoothPhoneAPI *bluetoothPhoneAPI;

From 8fe98db5dd6738546db0d27c6823e3380df322d4 Mon Sep 17 00:00:00 2001
From: Benjamin Faershtein <119711889+RCGV1@users.noreply.github.com>
Date: Sat, 8 Nov 2025 03:59:45 -0800
Subject: [PATCH 600/691] Drop PKI acks if there is no downlink on
 MQTTClientProxy (#8580)

* Discard everything if downlink isn't on

* Drop PKI packets when downlink not on
---
 src/mqtt/MQTT.cpp | 14 ++++++++++++++
 1 file changed, 14 insertions(+)

diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp
index 40d03de63..f9f114039 100644
--- a/src/mqtt/MQTT.cpp
+++ b/src/mqtt/MQTT.cpp
@@ -66,6 +66,20 @@ inline void onReceiveProto(char *topic, byte *payload, size_t length)
           (strcmp(e.channel_id, channels.getGlobalId(ch.index)) == 0 && ch.settings.downlink_enabled))) {
         return;
     }
+
+    bool anyChannelHasDownlink = false;
+    size_t numChan = channels.getNumChannels();
+    for (size_t i = 0; i < numChan; ++i) {
+        const auto &c = channels.getByIndex(i);
+        if (c.settings.downlink_enabled) {
+            anyChannelHasDownlink = true;
+            break;
+        }
+    }
+
+    if (strcmp(e.channel_id, "PKI") == 0 && !anyChannelHasDownlink) {
+        return;
+    }
     // Generate node ID from nodenum for comparison
     std::string nodeId = nodeDB->getNodeId();
     if (strcmp(e.gateway_id, nodeId.c_str()) == 0) {

From b86827967e536d7f7f66b0a037d3a9ee46f725ab Mon Sep 17 00:00:00 2001
From: Benjamin Faershtein <119711889+RCGV1@users.noreply.github.com>
Date: Sat, 8 Nov 2025 03:59:45 -0800
Subject: [PATCH 601/691] Drop PKI acks if there is no downlink on
 MQTTClientProxy (#8580)

* Discard everything if downlink isn't on

* Drop PKI packets when downlink not on
---
 src/mqtt/MQTT.cpp | 14 ++++++++++++++
 1 file changed, 14 insertions(+)

diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp
index 40d03de63..f9f114039 100644
--- a/src/mqtt/MQTT.cpp
+++ b/src/mqtt/MQTT.cpp
@@ -66,6 +66,20 @@ inline void onReceiveProto(char *topic, byte *payload, size_t length)
           (strcmp(e.channel_id, channels.getGlobalId(ch.index)) == 0 && ch.settings.downlink_enabled))) {
         return;
     }
+
+    bool anyChannelHasDownlink = false;
+    size_t numChan = channels.getNumChannels();
+    for (size_t i = 0; i < numChan; ++i) {
+        const auto &c = channels.getByIndex(i);
+        if (c.settings.downlink_enabled) {
+            anyChannelHasDownlink = true;
+            break;
+        }
+    }
+
+    if (strcmp(e.channel_id, "PKI") == 0 && !anyChannelHasDownlink) {
+        return;
+    }
     // Generate node ID from nodenum for comparison
     std::string nodeId = nodeDB->getNodeId();
     if (strcmp(e.gateway_id, nodeId.c_str()) == 0) {

From 50f9be9a2b5d2407b423e6618f4579fc604fa9e0 Mon Sep 17 00:00:00 2001
From: Quency-D <55523105+Quency-D@users.noreply.github.com>
Date: Sat, 8 Nov 2025 20:47:24 +0800
Subject: [PATCH 602/691] Add the Heltec v4 expansion box. (#8539)

* Add the Heltec v4 expansion box.

* Change heltec-v4-oled to heltec-v4.

* Add touchscreen to I2C scanning.

* Add reset and busy pins to the ST7789.

* Ignore the touch interrupt pin and extend the sleep time to 1 hour.

* Remove the default sleep function.

---------

Co-authored-by: Ben Meadors 
---
 src/configuration.h                       |   1 +
 src/detect/ScanI2C.h                      |   3 +-
 src/detect/ScanI2CTwoWire.cpp             |   1 +
 src/graphics/TFTDisplay.cpp               |  53 ++++++++++-
 src/mesh/NodeDB.cpp                       |   2 +-
 variants/esp32s3/heltec_v4/pins_arduino.h |   4 +-
 variants/esp32s3/heltec_v4/platformio.ini | 105 +++++++++++++++++++++-
 variants/esp32s3/heltec_v4/variant.h      |   8 --
 8 files changed, 162 insertions(+), 15 deletions(-)

diff --git a/src/configuration.h b/src/configuration.h
index 524dacdea..8ec3b2211 100644
--- a/src/configuration.h
+++ b/src/configuration.h
@@ -251,6 +251,7 @@ along with this program.  If not, see .
 // -----------------------------------------------------------------------------
 #define FT6336U_ADDR 0x48
 #define CST328_ADDR 0x1A
+#define CHSC6X_ADDR 0x2E
 
 // -----------------------------------------------------------------------------
 // RAK12035VB Soil Monitor (using RAK12023 up to 3 RAK12035 monitors can be connected)
diff --git a/src/detect/ScanI2C.h b/src/detect/ScanI2C.h
index cca867851..55980face 100644
--- a/src/detect/ScanI2C.h
+++ b/src/detect/ScanI2C.h
@@ -84,7 +84,8 @@ class ScanI2C
         TSL2561,
         DRV2605,
         BH1750,
-        DA217
+        DA217,
+        CHSC6X
     } DeviceType;
 
     // typedef uint8_t DeviceAddress;
diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp
index 66697c109..167728ad3 100644
--- a/src/detect/ScanI2CTwoWire.cpp
+++ b/src/detect/ScanI2CTwoWire.cpp
@@ -500,6 +500,7 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
                 SCAN_SIMPLE_CASE(LTR390UV_ADDR, LTR390UV, "LTR390UV", (uint8_t)addr.address);
                 SCAN_SIMPLE_CASE(PCT2075_ADDR, PCT2075, "PCT2075", (uint8_t)addr.address);
                 SCAN_SIMPLE_CASE(CST328_ADDR, CST328, "CST328", (uint8_t)addr.address);
+                SCAN_SIMPLE_CASE(CHSC6X_ADDR, CHSC6X, "CHSC6X", (uint8_t)addr.address);
             case LTR553ALS_ADDR:
                 registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x86), 1); // Part ID register
                 if (registerValue == 0x92) {                                                       // LTR553ALS Part ID
diff --git a/src/graphics/TFTDisplay.cpp b/src/graphics/TFTDisplay.cpp
index 0663602d9..b662869dd 100644
--- a/src/graphics/TFTDisplay.cpp
+++ b/src/graphics/TFTDisplay.cpp
@@ -422,7 +422,54 @@ static LGFX *tft = nullptr;
 
 #elif defined(ST7789_CS)
 #include  // Graphics and font library for ST7735 driver chip
+#ifdef HELTEC_V4_TFT
+#include "chsc6x.h"
+#include "lgfx/v1/Touch.hpp"
+namespace lgfx
+{
+ inline namespace v1
+ {
+class TOUCH_CHSC6X : public ITouch
+{
+public:
+    TOUCH_CHSC6X(void)
+    {
+      _cfg.i2c_addr = TOUCH_SLAVE_ADDRESS;
+      _cfg.x_min = 0;
+      _cfg.x_max = 240;
+      _cfg.y_min = 0;
+      _cfg.y_max = 320;
+    };
 
+    bool init(void) override {
+        if(chsc6xTouch==nullptr) {
+            chsc6xTouch=new chsc6x(&Wire1,TOUCH_SDA_PIN,TOUCH_SCL_PIN,TOUCH_INT_PIN,TOUCH_RST_PIN);
+        }
+        chsc6xTouch->chsc6x_init();
+        return true;
+    };
+
+    uint_fast8_t getTouchRaw(touch_point_t* tp, uint_fast8_t count) override {
+        uint16_t raw_x,raw_y;
+        if (chsc6xTouch->chsc6x_read_touch_info(&raw_x, &raw_y)==0) {
+            tp[0].x = 320-1-raw_y;
+            tp[0].y = 240-1-raw_x ;
+            tp[0].size = 1;
+            tp[0].id = 1;
+            return 1;
+        }
+        tp[0].size = 0;
+        return 0;
+    };
+
+    void wakeup(void) override {};
+    void sleep(void) override {};
+  private:
+    chsc6x *chsc6xTouch=nullptr;
+  };
+}
+}
+#endif
 class LGFX : public lgfx::LGFX_Device
 {
     lgfx::Panel_ST7789 _panel_instance;
@@ -431,6 +478,8 @@ class LGFX : public lgfx::LGFX_Device
 #if HAS_TOUCHSCREEN
 #if defined(T_WATCH_S3) || defined(ELECROW)
     lgfx::Touch_FT5x06 _touch_instance;
+#elif defined(HELTEC_V4_TFT)
+    lgfx::TOUCH_CHSC6X _touch_instance;
 #else
     lgfx::Touch_GT911 _touch_instance;
 #endif
@@ -465,8 +514,8 @@ class LGFX : public lgfx::LGFX_Device
             auto cfg = _panel_instance.config(); // Gets a structure for display panel settings.
 
             cfg.pin_cs = ST7789_CS; // Pin number where CS is connected (-1 = disable)
-            cfg.pin_rst = -1;       // Pin number where RST is connected  (-1 = disable)
-            cfg.pin_busy = -1;      // Pin number where BUSY is connected (-1 = disable)
+            cfg.pin_rst = ST7789_RESET;       // Pin number where RST is connected  (-1 = disable)
+            cfg.pin_busy = ST7789_BUSY;      // Pin number where BUSY is connected (-1 = disable)
 
             // The following setting values ​​are general initial values ​​for each panel, so please comment out any
             // unknown items and try them.
diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp
index 76915397f..6291fa4cc 100644
--- a/src/mesh/NodeDB.cpp
+++ b/src/mesh/NodeDB.cpp
@@ -653,7 +653,7 @@ void NodeDB::installDefaultConfig(bool preserveKey = false)
     strncpy(config.network.ntp_server, "meshtastic.pool.ntp.org", 32);
 
 #if (defined(T_DECK) || defined(T_WATCH_S3) || defined(UNPHONE) || defined(PICOMPUTER_S3) || defined(SENSECAP_INDICATOR) ||      \
-     defined(ELECROW_PANEL)) &&                                                                                                  \
+     defined(ELECROW_PANEL)||defined(HELTEC_V4_TFT)) &&                                                                                                  \
     HAS_TFT
     // switch BT off by default; use TFT programming mode or hotkey to enable
     config.bluetooth.enabled = false;
diff --git a/variants/esp32s3/heltec_v4/pins_arduino.h b/variants/esp32s3/heltec_v4/pins_arduino.h
index 45561b4b5..d4485016d 100644
--- a/variants/esp32s3/heltec_v4/pins_arduino.h
+++ b/variants/esp32s3/heltec_v4/pins_arduino.h
@@ -13,8 +13,8 @@ static const uint8_t LED_BUILTIN = 35;
 static const uint8_t TX = 43;
 static const uint8_t RX = 44;
 
-static const uint8_t SDA = 3;
-static const uint8_t SCL = 4;
+static const uint8_t SDA = 4;
+static const uint8_t SCL = 3;
 
 static const uint8_t SS = 8;
 static const uint8_t MOSI = 10;
diff --git a/variants/esp32s3/heltec_v4/platformio.ini b/variants/esp32s3/heltec_v4/platformio.ini
index 7057f9646..4ff7ff253 100644
--- a/variants/esp32s3/heltec_v4/platformio.ini
+++ b/variants/esp32s3/heltec_v4/platformio.ini
@@ -1,4 +1,4 @@
-[env:heltec-v4] 
+[heltec_v4_base] 
 extends = esp32s3_base
 board = heltec_v4
 board_check = true
@@ -7,3 +7,106 @@ build_flags =
   ${esp32s3_base.build_flags}
   -D HELTEC_V4
   -I variants/esp32s3/heltec_v4
+lib_deps = 
+  ${esp32s3_base.lib_deps}
+
+
+[env:heltec-v4]
+extends = heltec_v4_base
+build_flags = 
+  ${heltec_v4_base.build_flags}
+  -D HELTEC_V4_OLED
+  -D USE_SSD1306 ; Heltec_v4 has an SSD1315 display (compatible with SSD1306 driver)
+  -D LED_PIN=35
+  -D RESET_OLED=21
+  -D I2C_SDA=17
+  -D I2C_SCL=18
+  -D I2C_SDA1=4
+  -D I2C_SCL1=3
+lib_deps = 
+  ${heltec_v4_base.lib_deps}
+
+[env:heltec-v4-tft]
+extends = heltec_v4_base
+build_flags = 
+  ${heltec_v4_base.build_flags} ;-Os
+  -D HELTEC_V4_TFT
+  -D I2C_SDA=4
+  -D I2C_SCL=3
+  -D I2C_SDA1=47
+  -D I2C_SCL1=48
+  -D PIN_BUTTON2=35
+  -D PIN_BUZZER=6
+  -D USE_PIN_BUZZER=PIN_BUZZER
+  -D CONFIG_ARDUHAL_LOG_COLORS
+  -D RADIOLIB_DEBUG_SPI=0
+  -D RADIOLIB_DEBUG_PROTOCOL=0
+  -D RADIOLIB_DEBUG_BASIC=0
+  -D RADIOLIB_VERBOSE_ASSERT=0
+  -D RADIOLIB_SPI_PARANOID=0
+  -D CONFIG_DISABLE_HAL_LOCKS=1
+  -D INPUTDRIVER_BUTTON_TYPE=0
+  -D HAS_SCREEN=1
+  -D HAS_TFT=1
+  -D RAM_SIZE=1560
+  -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 USE_PACKET_API
+  -D LGFX_DRIVER=LGFX_HELTEC_V4_TFT
+  -D GFX_DRIVER_INC=\"graphics/LGFX/LGFX_HELTEC_V4_TFT.h\"
+  -D VIEW_320x240
+  -D MAP_FULL_REDRAW
+  -D DISPLAY_SIZE=320x240 ; landscape mode
+  -D LGFX_PIN_SCK=17
+  -D LGFX_PIN_MOSI=33
+  -D LGFX_PIN_DC=16
+  -D LGFX_PIN_CS=15
+  -D LGFX_PIN_BL=21
+  -D LGFX_PIN_RST=18
+  -D CUSTOM_TOUCH_DRIVER
+  -D TOUCH_SDA_PIN=I2C_SDA1
+  -D TOUCH_SCL_PIN=I2C_SCL1
+  -D TOUCH_INT_PIN=-1  ;45
+  -D TOUCH_RST_PIN=44
+;base UI
+  -D TFT_CS=LGFX_PIN_CS
+  -D ST7789_CS=TFT_CS
+  -D ST7789_RS=LGFX_PIN_DC
+  -D ST7789_SDA=LGFX_PIN_MOSI 
+  -D ST7789_SCK=LGFX_PIN_SCK
+  -D ST7789_RESET=LGFX_PIN_RST
+  -D ST7789_MISO=-1
+  -D ST7789_BUSY=-1
+  -D ST7789_BL=LGFX_PIN_BL
+  -D ST7789_SPI_HOST=SPI3_HOST
+  -D TFT_BL=ST7789_BL
+  -D SPI_FREQUENCY=40000000
+  -D SPI_READ_FREQUENCY=4000000
+  -D TFT_HEIGHT=320
+  -D TFT_WIDTH=240
+  -D TFT_OFFSET_X=0
+  -D TFT_OFFSET_Y=0
+  -D TFT_OFFSET_ROTATION=0
+  -D SCREEN_ROTATE
+  -D SCREEN_TRANSITION_FRAMERATE=5
+  -D BRIGHTNESS_DEFAULT=130 ; Medium Low Brightness
+  -D HAS_TOUCHSCREEN=1
+  -D TOUCH_I2C_PORT=0
+  -D TOUCH_SLAVE_ADDRESS=0x2E
+  -D SCREEN_TOUCH_INT=TOUCH_INT_PIN
+  -D SCREEN_TOUCH_RST=TOUCH_RST_PIN
+
+lib_deps = ${heltec_v4_base.lib_deps}
+  ; ${device-ui_base.lib_deps}
+  lovyan03/LovyanGFX@1.2.0
+  https://github.com/Quency-D/chsc6x/archive/5cbead829d6b432a8d621ed1aafd4eb474fd4f27.zip
+  https://github.com/Quency-D/device-ui/archive/7c9870b8016641190b059bdd90fe16c1012a39eb.zip
diff --git a/variants/esp32s3/heltec_v4/variant.h b/variants/esp32s3/heltec_v4/variant.h
index 1c9516e39..72bbf14fc 100644
--- a/variants/esp32s3/heltec_v4/variant.h
+++ b/variants/esp32s3/heltec_v4/variant.h
@@ -1,11 +1,3 @@
-#define LED_PIN 35
-
-#define USE_SSD1306 // Heltec_v4 has an SSD1315 display (compatible with SSD1306 driver)
-
-#define RESET_OLED 21
-#define I2C_SDA 17 // I2C pins for this board
-#define I2C_SCL 18
-
 #define VEXT_ENABLE 36 // active low, powers the oled display and the lora antenna boost
 #define BUTTON_PIN 0
 

From 602945f66b0c060c1074736c023db1976c0efa25 Mon Sep 17 00:00:00 2001
From: Quency-D <55523105+Quency-D@users.noreply.github.com>
Date: Sat, 8 Nov 2025 20:47:24 +0800
Subject: [PATCH 603/691] Add the Heltec v4 expansion box. (#8539)

* Add the Heltec v4 expansion box.

* Change heltec-v4-oled to heltec-v4.

* Add touchscreen to I2C scanning.

* Add reset and busy pins to the ST7789.

* Ignore the touch interrupt pin and extend the sleep time to 1 hour.

* Remove the default sleep function.

---------

Co-authored-by: Ben Meadors 
---
 src/configuration.h                       |   1 +
 src/detect/ScanI2C.h                      |   5 +-
 src/detect/ScanI2CTwoWire.cpp             |  21 ++++-
 src/graphics/TFTDisplay.cpp               |  58 +++++++++++-
 src/mesh/NodeDB.cpp                       |   2 +-
 variants/esp32s3/heltec_v4/pins_arduino.h |   4 +-
 variants/esp32s3/heltec_v4/platformio.ini | 105 +++++++++++++++++++++-
 variants/esp32s3/heltec_v4/variant.h      |   8 --
 8 files changed, 187 insertions(+), 17 deletions(-)

diff --git a/src/configuration.h b/src/configuration.h
index baf24a636..75cdac552 100644
--- a/src/configuration.h
+++ b/src/configuration.h
@@ -250,6 +250,7 @@ along with this program.  If not, see .
 // -----------------------------------------------------------------------------
 #define FT6336U_ADDR 0x48
 #define CST328_ADDR 0x1A
+#define CHSC6X_ADDR 0x2E
 
 // -----------------------------------------------------------------------------
 // RAK12035VB Soil Monitor (using RAK12023 up to 3 RAK12035 monitors can be connected)
diff --git a/src/detect/ScanI2C.h b/src/detect/ScanI2C.h
index 2e602338c..55980face 100644
--- a/src/detect/ScanI2C.h
+++ b/src/detect/ScanI2C.h
@@ -82,7 +82,10 @@ class ScanI2C
         BHI260AP,
         BMM150,
         TSL2561,
-        DRV2605
+        DRV2605,
+        BH1750,
+        DA217,
+        CHSC6X
     } DeviceType;
 
     // typedef uint8_t DeviceAddress;
diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp
index da2a57fee..5372fbebd 100644
--- a/src/detect/ScanI2CTwoWire.cpp
+++ b/src/detect/ScanI2CTwoWire.cpp
@@ -485,7 +485,26 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
                 SCAN_SIMPLE_CASE(LTR390UV_ADDR, LTR390UV, "LTR390UV", (uint8_t)addr.address);
                 SCAN_SIMPLE_CASE(PCT2075_ADDR, PCT2075, "PCT2075", (uint8_t)addr.address);
                 SCAN_SIMPLE_CASE(CST328_ADDR, CST328, "CST328", (uint8_t)addr.address);
-                SCAN_SIMPLE_CASE(LTR553ALS_ADDR, LTR553ALS, "LTR553ALS", (uint8_t)addr.address);
+                SCAN_SIMPLE_CASE(CHSC6X_ADDR, CHSC6X, "CHSC6X", (uint8_t)addr.address);
+            case LTR553ALS_ADDR:
+                registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x86), 1); // Part ID register
+                if (registerValue == 0x92) {                                                       // LTR553ALS Part ID
+                    type = LTR553ALS;
+                    logFoundDevice("LTR553ALS", (uint8_t)addr.address);
+                } else {
+                    // Test BH1750 - send power on command
+                    i2cBus->beginTransmission(addr.address);
+                    i2cBus->write(0x01); // Power On command
+                    uint8_t bh1750_error = i2cBus->endTransmission();
+                    if (bh1750_error == 0) {
+                        type = BH1750;
+                        logFoundDevice("BH1750", (uint8_t)addr.address);
+                    } else {
+                        LOG_INFO("Device found at address 0x%x was not able to be enumerated", (uint8_t)addr.address);
+                    }
+                }
+                break;
+
                 SCAN_SIMPLE_CASE(BHI260AP_ADDR, BHI260AP, "BHI260AP", (uint8_t)addr.address);
                 SCAN_SIMPLE_CASE(SCD4X_ADDR, SCD4X, "SCD4X", (uint8_t)addr.address);
                 SCAN_SIMPLE_CASE(BMM150_ADDR, BMM150, "BMM150", (uint8_t)addr.address);
diff --git a/src/graphics/TFTDisplay.cpp b/src/graphics/TFTDisplay.cpp
index 0663602d9..87593b0d4 100644
--- a/src/graphics/TFTDisplay.cpp
+++ b/src/graphics/TFTDisplay.cpp
@@ -422,7 +422,57 @@ static LGFX *tft = nullptr;
 
 #elif defined(ST7789_CS)
 #include  // Graphics and font library for ST7735 driver chip
+#ifdef HELTEC_V4_TFT
+#include "chsc6x.h"
+#include "lgfx/v1/Touch.hpp"
+namespace lgfx
+{
+inline namespace v1
+{
+class TOUCH_CHSC6X : public ITouch
+{
+  public:
+    TOUCH_CHSC6X(void)
+    {
+        _cfg.i2c_addr = TOUCH_SLAVE_ADDRESS;
+        _cfg.x_min = 0;
+        _cfg.x_max = 240;
+        _cfg.y_min = 0;
+        _cfg.y_max = 320;
+    };
 
+    bool init(void) override
+    {
+        if (chsc6xTouch == nullptr) {
+            chsc6xTouch = new chsc6x(&Wire1, TOUCH_SDA_PIN, TOUCH_SCL_PIN, TOUCH_INT_PIN, TOUCH_RST_PIN);
+        }
+        chsc6xTouch->chsc6x_init();
+        return true;
+    };
+
+    uint_fast8_t getTouchRaw(touch_point_t *tp, uint_fast8_t count) override
+    {
+        uint16_t raw_x, raw_y;
+        if (chsc6xTouch->chsc6x_read_touch_info(&raw_x, &raw_y) == 0) {
+            tp[0].x = 320 - 1 - raw_y;
+            tp[0].y = 240 - 1 - raw_x;
+            tp[0].size = 1;
+            tp[0].id = 1;
+            return 1;
+        }
+        tp[0].size = 0;
+        return 0;
+    };
+
+    void wakeup(void) override{};
+    void sleep(void) override{};
+
+  private:
+    chsc6x *chsc6xTouch = nullptr;
+};
+} // namespace v1
+} // namespace lgfx
+#endif
 class LGFX : public lgfx::LGFX_Device
 {
     lgfx::Panel_ST7789 _panel_instance;
@@ -431,6 +481,8 @@ class LGFX : public lgfx::LGFX_Device
 #if HAS_TOUCHSCREEN
 #if defined(T_WATCH_S3) || defined(ELECROW)
     lgfx::Touch_FT5x06 _touch_instance;
+#elif defined(HELTEC_V4_TFT)
+    lgfx::TOUCH_CHSC6X _touch_instance;
 #else
     lgfx::Touch_GT911 _touch_instance;
 #endif
@@ -464,9 +516,9 @@ class LGFX : public lgfx::LGFX_Device
         {                                        // Set the display panel control.
             auto cfg = _panel_instance.config(); // Gets a structure for display panel settings.
 
-            cfg.pin_cs = ST7789_CS; // Pin number where CS is connected (-1 = disable)
-            cfg.pin_rst = -1;       // Pin number where RST is connected  (-1 = disable)
-            cfg.pin_busy = -1;      // Pin number where BUSY is connected (-1 = disable)
+            cfg.pin_cs = ST7789_CS;     // Pin number where CS is connected (-1 = disable)
+            cfg.pin_rst = ST7789_RESET; // Pin number where RST is connected  (-1 = disable)
+            cfg.pin_busy = ST7789_BUSY; // Pin number where BUSY is connected (-1 = disable)
 
             // The following setting values ​​are general initial values ​​for each panel, so please comment out any
             // unknown items and try them.
diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp
index bda6f4ea4..d047bf163 100644
--- a/src/mesh/NodeDB.cpp
+++ b/src/mesh/NodeDB.cpp
@@ -653,7 +653,7 @@ void NodeDB::installDefaultConfig(bool preserveKey = false)
     strncpy(config.network.ntp_server, "meshtastic.pool.ntp.org", 32);
 
 #if (defined(T_DECK) || defined(T_WATCH_S3) || defined(UNPHONE) || defined(PICOMPUTER_S3) || defined(SENSECAP_INDICATOR) ||      \
-     defined(ELECROW_PANEL)) &&                                                                                                  \
+     defined(ELECROW_PANEL) || defined(HELTEC_V4_TFT)) &&                                                                        \
     HAS_TFT
     // switch BT off by default; use TFT programming mode or hotkey to enable
     config.bluetooth.enabled = false;
diff --git a/variants/esp32s3/heltec_v4/pins_arduino.h b/variants/esp32s3/heltec_v4/pins_arduino.h
index 45561b4b5..d4485016d 100644
--- a/variants/esp32s3/heltec_v4/pins_arduino.h
+++ b/variants/esp32s3/heltec_v4/pins_arduino.h
@@ -13,8 +13,8 @@ static const uint8_t LED_BUILTIN = 35;
 static const uint8_t TX = 43;
 static const uint8_t RX = 44;
 
-static const uint8_t SDA = 3;
-static const uint8_t SCL = 4;
+static const uint8_t SDA = 4;
+static const uint8_t SCL = 3;
 
 static const uint8_t SS = 8;
 static const uint8_t MOSI = 10;
diff --git a/variants/esp32s3/heltec_v4/platformio.ini b/variants/esp32s3/heltec_v4/platformio.ini
index 7057f9646..4ff7ff253 100644
--- a/variants/esp32s3/heltec_v4/platformio.ini
+++ b/variants/esp32s3/heltec_v4/platformio.ini
@@ -1,4 +1,4 @@
-[env:heltec-v4] 
+[heltec_v4_base] 
 extends = esp32s3_base
 board = heltec_v4
 board_check = true
@@ -7,3 +7,106 @@ build_flags =
   ${esp32s3_base.build_flags}
   -D HELTEC_V4
   -I variants/esp32s3/heltec_v4
+lib_deps = 
+  ${esp32s3_base.lib_deps}
+
+
+[env:heltec-v4]
+extends = heltec_v4_base
+build_flags = 
+  ${heltec_v4_base.build_flags}
+  -D HELTEC_V4_OLED
+  -D USE_SSD1306 ; Heltec_v4 has an SSD1315 display (compatible with SSD1306 driver)
+  -D LED_PIN=35
+  -D RESET_OLED=21
+  -D I2C_SDA=17
+  -D I2C_SCL=18
+  -D I2C_SDA1=4
+  -D I2C_SCL1=3
+lib_deps = 
+  ${heltec_v4_base.lib_deps}
+
+[env:heltec-v4-tft]
+extends = heltec_v4_base
+build_flags = 
+  ${heltec_v4_base.build_flags} ;-Os
+  -D HELTEC_V4_TFT
+  -D I2C_SDA=4
+  -D I2C_SCL=3
+  -D I2C_SDA1=47
+  -D I2C_SCL1=48
+  -D PIN_BUTTON2=35
+  -D PIN_BUZZER=6
+  -D USE_PIN_BUZZER=PIN_BUZZER
+  -D CONFIG_ARDUHAL_LOG_COLORS
+  -D RADIOLIB_DEBUG_SPI=0
+  -D RADIOLIB_DEBUG_PROTOCOL=0
+  -D RADIOLIB_DEBUG_BASIC=0
+  -D RADIOLIB_VERBOSE_ASSERT=0
+  -D RADIOLIB_SPI_PARANOID=0
+  -D CONFIG_DISABLE_HAL_LOCKS=1
+  -D INPUTDRIVER_BUTTON_TYPE=0
+  -D HAS_SCREEN=1
+  -D HAS_TFT=1
+  -D RAM_SIZE=1560
+  -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 USE_PACKET_API
+  -D LGFX_DRIVER=LGFX_HELTEC_V4_TFT
+  -D GFX_DRIVER_INC=\"graphics/LGFX/LGFX_HELTEC_V4_TFT.h\"
+  -D VIEW_320x240
+  -D MAP_FULL_REDRAW
+  -D DISPLAY_SIZE=320x240 ; landscape mode
+  -D LGFX_PIN_SCK=17
+  -D LGFX_PIN_MOSI=33
+  -D LGFX_PIN_DC=16
+  -D LGFX_PIN_CS=15
+  -D LGFX_PIN_BL=21
+  -D LGFX_PIN_RST=18
+  -D CUSTOM_TOUCH_DRIVER
+  -D TOUCH_SDA_PIN=I2C_SDA1
+  -D TOUCH_SCL_PIN=I2C_SCL1
+  -D TOUCH_INT_PIN=-1  ;45
+  -D TOUCH_RST_PIN=44
+;base UI
+  -D TFT_CS=LGFX_PIN_CS
+  -D ST7789_CS=TFT_CS
+  -D ST7789_RS=LGFX_PIN_DC
+  -D ST7789_SDA=LGFX_PIN_MOSI 
+  -D ST7789_SCK=LGFX_PIN_SCK
+  -D ST7789_RESET=LGFX_PIN_RST
+  -D ST7789_MISO=-1
+  -D ST7789_BUSY=-1
+  -D ST7789_BL=LGFX_PIN_BL
+  -D ST7789_SPI_HOST=SPI3_HOST
+  -D TFT_BL=ST7789_BL
+  -D SPI_FREQUENCY=40000000
+  -D SPI_READ_FREQUENCY=4000000
+  -D TFT_HEIGHT=320
+  -D TFT_WIDTH=240
+  -D TFT_OFFSET_X=0
+  -D TFT_OFFSET_Y=0
+  -D TFT_OFFSET_ROTATION=0
+  -D SCREEN_ROTATE
+  -D SCREEN_TRANSITION_FRAMERATE=5
+  -D BRIGHTNESS_DEFAULT=130 ; Medium Low Brightness
+  -D HAS_TOUCHSCREEN=1
+  -D TOUCH_I2C_PORT=0
+  -D TOUCH_SLAVE_ADDRESS=0x2E
+  -D SCREEN_TOUCH_INT=TOUCH_INT_PIN
+  -D SCREEN_TOUCH_RST=TOUCH_RST_PIN
+
+lib_deps = ${heltec_v4_base.lib_deps}
+  ; ${device-ui_base.lib_deps}
+  lovyan03/LovyanGFX@1.2.0
+  https://github.com/Quency-D/chsc6x/archive/5cbead829d6b432a8d621ed1aafd4eb474fd4f27.zip
+  https://github.com/Quency-D/device-ui/archive/7c9870b8016641190b059bdd90fe16c1012a39eb.zip
diff --git a/variants/esp32s3/heltec_v4/variant.h b/variants/esp32s3/heltec_v4/variant.h
index 1c9516e39..72bbf14fc 100644
--- a/variants/esp32s3/heltec_v4/variant.h
+++ b/variants/esp32s3/heltec_v4/variant.h
@@ -1,11 +1,3 @@
-#define LED_PIN 35
-
-#define USE_SSD1306 // Heltec_v4 has an SSD1315 display (compatible with SSD1306 driver)
-
-#define RESET_OLED 21
-#define I2C_SDA 17 // I2C pins for this board
-#define I2C_SCL 18
-
 #define VEXT_ENABLE 36 // active low, powers the oled display and the lora antenna boost
 #define BUTTON_PIN 0
 

From 1c0c6b2736b9afb392a2986855456dff20dee4d1 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
 <41898282+github-actions[bot]@users.noreply.github.com>
Date: Mon, 3 Nov 2025 10:14:08 -0600
Subject: [PATCH 604/691] Automated version bumps (#8527)

Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
---
 bin/org.meshtastic.meshtasticd.metainfo.xml | 3 +++
 debian/changelog                            | 6 ++++++
 version.properties                          | 2 +-
 3 files changed, 10 insertions(+), 1 deletion(-)

diff --git a/bin/org.meshtastic.meshtasticd.metainfo.xml b/bin/org.meshtastic.meshtasticd.metainfo.xml
index 6fc5e8597..61ecf9fb5 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.14
+    
     
       https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.13
     
diff --git a/debian/changelog b/debian/changelog
index e124f8929..a387cc3c5 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+meshtasticd (2.7.14.0) unstable; urgency=medium
+
+  * Version 2.7.14
+
+ -- GitHub Actions   Mon, 03 Nov 2025 16:11:31 +0000
+
 meshtasticd (2.7.13.0) unstable; urgency=medium
 
   * Version 2.7.13
diff --git a/version.properties b/version.properties
index f33f0f1cb..fe1a5b31b 100644
--- a/version.properties
+++ b/version.properties
@@ -1,4 +1,4 @@
 [VERSION]  
 major = 2
 minor = 7
-build = 13
+build = 14

From 36c217857038f69d6a178419d91b5867e270aea6 Mon Sep 17 00:00:00 2001
From: Tom <116762865+NomDeTom@users.noreply.github.com>
Date: Sun, 9 Nov 2025 15:24:03 +0000
Subject: [PATCH 605/691] Update to Pro-micro variants (#8600)

* Update to Pro-micro variants

Schematic updated
Xtal variant removed
Extra module added to list
Extra explanation added to readme.

* Fix markdown formatting in readme.md

* Fix formatting in readme.md for RF switch section

---------

Co-authored-by: Tom <116762865+Nestpebble@users.noreply.github.com>
---
 ...Schematic_Pro-Micro_Pinouts 2024-12-14.pdf |  9836 -----
 .../Schematic_Pro-Micro_Pinouts.pdf           | 33346 ++++++++++++++++
 .../diy/nrf52_promicro_diy_tcxo/readme.md     |    15 +-
 .../diy/nrf52_promicro_diy_tcxo/variant.h     |     1 +
 .../nrf52_promicro_diy_xtal/platformio.ini    |    12 -
 .../diy/nrf52_promicro_diy_xtal/variant.cpp   |    38 -
 .../diy/nrf52_promicro_diy_xtal/variant.h     |   154 -
 7 files changed, 33360 insertions(+), 10042 deletions(-)
 delete mode 100644 variants/nrf52840/diy/nrf52_promicro_diy_tcxo/Schematic_Pro-Micro_Pinouts 2024-12-14.pdf
 create mode 100644 variants/nrf52840/diy/nrf52_promicro_diy_tcxo/Schematic_Pro-Micro_Pinouts.pdf
 delete mode 100644 variants/nrf52840/diy/nrf52_promicro_diy_xtal/platformio.ini
 delete mode 100644 variants/nrf52840/diy/nrf52_promicro_diy_xtal/variant.cpp
 delete mode 100644 variants/nrf52840/diy/nrf52_promicro_diy_xtal/variant.h

diff --git a/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/Schematic_Pro-Micro_Pinouts 2024-12-14.pdf b/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/Schematic_Pro-Micro_Pinouts 2024-12-14.pdf
deleted file mode 100644
index de87af141..000000000
--- a/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/Schematic_Pro-Micro_Pinouts 2024-12-14.pdf	
+++ /dev/null
@@ -1,9836 +0,0 @@
-%PDF-1.4
-%߬
-3 0 obj
-<>
-endobj
-4 0 obj
-<<
-/Length 102720
->>
-stream
-0.20 w
-0 G
-2 J
-0 j
-100 M
-1.00 g
-[] 0 d
-0.00 826.80 1169.00 -826.80 re
-f
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-216.000 806.000 m 
-216.000 816.000 l
-216.000 20.000 m 
-216.000 10.000 l
-412.000 806.000 m 
-412.000 816.000 l
-412.000 20.000 m 
-412.000 10.000 l
-608.000 806.000 m 
-608.000 816.000 l
-608.000 20.000 m 
-608.000 10.000 l
-804.000 806.000 m 
-804.000 816.000 l
-804.000 20.000 m 
-804.000 10.000 l
-1000.000 806.000 m 
-1000.000 816.000 l
-1000.000 20.000 m 
-1000.000 10.000 l
-20.000 610.000 m 
-10.000 610.000 l
-1149.000 610.000 m 
-1159.000 610.000 l
-20.000 414.000 m 
-10.000 414.000 l
-1149.000 414.000 m 
-1159.000 414.000 l
-20.000 218.000 m 
-10.000 218.000 l
-1149.000 218.000 m 
-1159.000 218.000 l
-20.000 22.000 m 
-10.000 22.000 l
-1149.000 22.000 m 
-1159.000 22.000 l
-S
-10.00 w
-BT
-/F1 9 Tf
-9.00 TL
-0.533 0.000 0.000 rg
-11.50 708.00 Td
-(A) Tj
-ET
-10.00 w
-BT
-/F1 9 Tf
-9.00 TL
-0.533 0.000 0.000 rg
-1150.50 708.00 Td
-(A) Tj
-ET
-10.00 w
-BT
-/F1 9 Tf
-9.00 TL
-0.533 0.000 0.000 rg
-11.50 512.00 Td
-(B) Tj
-ET
-10.00 w
-BT
-/F1 9 Tf
-9.00 TL
-0.533 0.000 0.000 rg
-1150.50 512.00 Td
-(B) Tj
-ET
-10.00 w
-BT
-/F1 9 Tf
-9.00 TL
-0.533 0.000 0.000 rg
-11.50 316.00 Td
-(C) Tj
-ET
-10.00 w
-BT
-/F1 9 Tf
-9.00 TL
-0.533 0.000 0.000 rg
-1150.50 316.00 Td
-(C) Tj
-ET
-10.00 w
-BT
-/F1 9 Tf
-9.00 TL
-0.533 0.000 0.000 rg
-11.50 120.00 Td
-(D) Tj
-ET
-10.00 w
-BT
-/F1 9 Tf
-9.00 TL
-0.533 0.000 0.000 rg
-1150.50 120.00 Td
-(D) Tj
-ET
-10.00 w
-BT
-/F1 9 Tf
-9.00 TL
-0.533 0.000 0.000 rg
-118.00 807.50 Td
-(1) Tj
-ET
-10.00 w
-BT
-/F1 9 Tf
-9.00 TL
-0.533 0.000 0.000 rg
-118.00 11.50 Td
-(1) Tj
-ET
-10.00 w
-BT
-/F1 9 Tf
-9.00 TL
-0.533 0.000 0.000 rg
-314.00 807.50 Td
-(2) Tj
-ET
-10.00 w
-BT
-/F1 9 Tf
-9.00 TL
-0.533 0.000 0.000 rg
-314.00 11.50 Td
-(2) Tj
-ET
-10.00 w
-BT
-/F1 9 Tf
-9.00 TL
-0.533 0.000 0.000 rg
-510.00 807.50 Td
-(3) Tj
-ET
-10.00 w
-BT
-/F1 9 Tf
-9.00 TL
-0.533 0.000 0.000 rg
-510.00 11.50 Td
-(3) Tj
-ET
-10.00 w
-BT
-/F1 9 Tf
-9.00 TL
-0.533 0.000 0.000 rg
-706.00 807.50 Td
-(4) Tj
-ET
-10.00 w
-BT
-/F1 9 Tf
-9.00 TL
-0.533 0.000 0.000 rg
-706.00 11.50 Td
-(4) Tj
-ET
-10.00 w
-BT
-/F1 9 Tf
-9.00 TL
-0.533 0.000 0.000 rg
-902.00 807.50 Td
-(5) Tj
-ET
-10.00 w
-BT
-/F1 9 Tf
-9.00 TL
-0.533 0.000 0.000 rg
-902.00 11.50 Td
-(5) Tj
-ET
-2 J
-0 j
-100 M
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-20.00 806.00 1129.00 -786.00 re
-S
-2 J
-0 j
-100 M
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-10.00 816.00 1149.00 -806.00 re
-S
-2 J
-0 j
-100 M
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-705.00 100.00 444.00 -80.00 re
-S
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-0.00 g
-[] 0 d
-705.100 60.750 m 
-1148.630 60.750 l
-S
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-0.00 g
-[] 0 d
-809.630 40.750 m 
-1148.630 40.750 l
-S
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-0.00 g
-[] 0 d
-1069.610 99.930 m 
-1069.630 60.750 l
-S
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-0.00 g
-[] 0 d
-1069.630 60.750 m 
-1069.630 40.750 l
-S
-10.00 w
-BT
-/F1 11 Tf
-11.00 TL
-0.533 0.000 0.000 rg
-710.00 87.00 Td
-(TITLE:) Tj
-ET
-10.00 w
-BT
-/F1 13 Tf
-13.00 TL
-0.000 0.000 1.000 rg
-767.62 74.41 Td
-(Pro-Micro Pinouts) Tj
-ET
-10.00 w
-BT
-/F1 11 Tf
-11.00 TL
-0.533 0.000 0.000 rg
-1074.62 73.75 Td
-(REV:) Tj
-ET
-10.00 w
-BT
-/F1 12 Tf
-12.00 TL
-0.000 0.000 1.000 rg
-1112.62 73.75 Td
-(1.0) Tj
-ET
-10.00 w
-BT
-/F1 11 Tf
-11.00 TL
-0.533 0.000 0.000 rg
-814.62 25.00 Td
-(Date:) Tj
-ET
-10.00 w
-BT
-/F1 12 Tf
-12.00 TL
-0.000 0.000 1.000 rg
-861.62 24.52 Td
-(2024-12-17) Tj
-ET
-10.00 w
-BT
-/F1 11 Tf
-11.00 TL
-0.533 0.000 0.000 rg
-1073.62 45.00 Td
-(Sheet:) Tj
-ET
-10.00 w
-BT
-/F1 12 Tf
-12.00 TL
-0.000 0.000 1.000 rg
-1118.62 44.52 Td
-(1/1) Tj
-ET
-10.00 w
-BT
-/F1 11 Tf
-11.00 TL
-0.533 0.000 0.000 rg
-953.62 24.75 Td
-(Drawn By:) Tj
-ET
-10.00 w
-BT
-/F1 11 Tf
-11.00 TL
-0.533 0.000 0.000 rg
-814.62 46.75 Td
-(Company:) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-0.00 g
-[] 0 d
-809.630 60.750 m 
-809.630 20.750 l
-S
-1 J
-1 j
-1.00 w
-0.20 0.80 0.20 RG
-[] 0 d
-541.000 684.000 m 
-549.000 676.000 l
-549.000 684.000 m 
-541.000 676.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 0.53 0.00 RG
-0.00 g
-[] 0 d
-665.000 680.000 m 
-635.000 680.000 l
-S
-1 J
-1 j
-1.00 w
-0.20 0.80 0.20 RG
-[] 0 d
-986.000 649.000 m 
-994.000 641.000 l
-994.000 649.000 m 
-986.000 641.000 l
-S
-1 J
-1 j
-1.00 w
-0.20 0.80 0.20 RG
-[] 0 d
-541.000 614.000 m 
-549.000 606.000 l
-549.000 614.000 m 
-541.000 606.000 l
-S
-1 J
-1 j
-1.00 w
-0.20 0.80 0.20 RG
-[] 0 d
-541.000 624.000 m 
-549.000 616.000 l
-549.000 624.000 m 
-541.000 616.000 l
-S
-1 J
-1 j
-1.00 w
-0.20 0.80 0.20 RG
-[] 0 d
-541.000 644.000 m 
-549.000 636.000 l
-549.000 644.000 m 
-541.000 636.000 l
-S
-1 J
-1 j
-1.00 w
-0.20 0.80 0.20 RG
-[] 0 d
-631.000 634.000 m 
-639.000 626.000 l
-639.000 634.000 m 
-631.000 626.000 l
-S
-BT
-/F2 12 Tf
-12.00 TL
-0.000 g
-1.00 -0.00 0.00 1.00 804.82 611.00 Tm
-(VCC) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-830.000 615.000 m 
-840.000 615.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-830.000 610.000 m 
-830.000 620.000 l
-S
-BT
-/F2 12 Tf
-12.00 TL
-0.000 g
-1.00 -0.00 0.00 1.00 797.50 621.00 Tm
-(GND) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-830.000 625.000 m 
-840.000 625.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-830.000 634.000 m 
-830.000 616.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-828.000 631.000 m 
-828.000 619.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-826.000 628.000 m 
-826.000 622.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-824.000 626.000 m 
-824.000 624.000 l
-S
-BT
-/F2 12 Tf
-12.00 TL
-0.000 g
-1.00 -0.00 0.00 1.00 1006.50 651.00 Tm
-(GND) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-1000.000 655.000 m 
-990.000 655.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-1000.000 646.000 m 
-1000.000 664.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-1002.000 649.000 m 
-1002.000 661.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-1004.000 652.000 m 
-1004.000 658.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-1006.000 654.000 m 
-1006.000 656.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 1011.94 671.15 Tm
-(IRQ) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-990.000 675.000 m 
-995.000 680.000 l
-1010.000 680.000 l
-1010.000 670.000 l
-995.000 670.000 l
-990.000 675.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 797.25 651.75 Tm
-(SCK) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-840.000 655.000 m 
-835.000 650.000 l
-820.000 650.000 l
-820.000 660.000 l
-835.000 660.000 l
-840.000 655.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 804.69 631.45 Tm
-(CS) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-840.000 635.000 m 
-835.000 630.000 l
-820.000 630.000 l
-820.000 640.000 l
-835.000 640.000 l
-840.000 635.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 791.48 661.75 Tm
-(MOSI) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-840.000 665.000 m 
-835.000 660.000 l
-820.000 660.000 l
-820.000 670.000 l
-835.000 670.000 l
-840.000 665.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 791.41 671.75 Tm
-(MISO) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-840.000 675.000 m 
-835.000 670.000 l
-820.000 670.000 l
-820.000 680.000 l
-835.000 680.000 l
-840.000 675.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 790.06 641.57 Tm
-(NRST) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-840.000 645.000 m 
-835.000 640.000 l
-820.000 640.000 l
-820.000 650.000 l
-835.000 650.000 l
-840.000 645.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 1011.92 661.15 Tm
-(BUSY) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-990.000 665.000 m 
-995.000 670.000 l
-1010.000 670.000 l
-1010.000 660.000 l
-995.000 660.000 l
-990.000 665.000 l
-S
-10.00 w
-BT
-/F3 9 Tf
-9.00 TL
-0.000 0.000 0.502 rg
-908.96 708.00 Td
-(Seeed-wio-SX1262) Tj
-ET
-10.00 w
-BT
-/F3 9 Tf
-9.00 TL
-0.000 0.000 0.502 rg
-908.96 717.00 Td
-(SEEED_WIO-SX1262) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-862.00 682.00 Td
-(RF_SW) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-849.28 686.00 Td
-(1) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-840.000 685.000 m 
-860.000 685.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-862.00 672.00 Td
-(MISO) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-849.28 676.00 Td
-(2) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-840.000 675.000 m 
-860.000 675.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-862.00 662.00 Td
-(MOSI) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-849.28 666.00 Td
-(3) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-840.000 665.000 m 
-860.000 665.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-862.00 652.00 Td
-(CLK) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-849.28 656.00 Td
-(4) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-840.000 655.000 m 
-860.000 655.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-862.00 642.00 Td
-(RST) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-849.28 646.00 Td
-(5) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-840.000 645.000 m 
-860.000 645.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-862.00 632.00 Td
-(NSS) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-849.28 636.00 Td
-(6) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-840.000 635.000 m 
-860.000 635.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-862.00 622.00 Td
-(GND) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-849.28 626.00 Td
-(7) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-840.000 625.000 m 
-860.000 625.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-862.00 612.00 Td
-(VCC) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-849.28 616.00 Td
-(8) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-840.000 615.000 m 
-860.000 615.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-949.58 642.00 Td
-(ANT) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-975.00 646.00 Td
-(9) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-990.000 645.000 m 
-970.000 645.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-947.36 652.00 Td
-(GND) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-975.00 656.00 Td
-(10) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-990.000 655.000 m 
-970.000 655.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-943.57 662.00 Td
-(BUSY) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-975.00 666.00 Td
-(11) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-990.000 665.000 m 
-970.000 665.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-944.49 672.00 Td
-(DIO1) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-975.00 676.00 Td
-(12) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-990.000 675.000 m 
-970.000 675.000 l
-S
-2 J
-0 j
-100 M
-1.00 w
-0.00 G
-[] 0 d
-860.00 705.00 110.00 -110.00 re
-S
-1.00 w
-0.00 G
-[] 0 d
-965.00 615.00 m 965.00 623.28 958.28 630.00 950.00 630.00 c
-941.72 630.00 935.00 623.28 935.00 615.00 c
-935.00 606.72 941.72 600.00 950.00 600.00 c
-958.28 600.00 965.00 606.72 965.00 615.00 c
-S
-2 J
-0 j
-100 M
-1.00 w
-0.00 G
-[] 0 d
-930.00 635.00 40.00 -40.00 re
-S
-1 J
-1 j
-1.00 w
-0.00 0.53 0.00 RG
-0.00 g
-[] 0 d
-535.000 660.000 m 
-545.000 660.000 l
-S
-BT
-/F2 12 Tf
-12.00 TL
-0.000 g
-1.00 -0.00 0.00 1.00 499.82 655.93 Tm
-(VCC) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-525.000 660.000 m 
-535.000 660.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-525.000 655.000 m 
-525.000 665.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 0.53 0.00 RG
-0.00 g
-[] 0 d
-520.000 670.000 m 
-545.000 670.000 l
-S
-BT
-/F2 12 Tf
-12.00 TL
-0.000 g
-1.00 -0.00 0.00 1.00 652.00 699.09 Tm
-(GND) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-665.000 690.000 m 
-665.000 680.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-674.000 690.000 m 
-656.000 690.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-671.000 692.000 m 
-659.000 692.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-668.000 694.000 m 
-662.000 694.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-666.000 696.000 m 
-664.000 696.000 l
-S
-BT
-/F2 12 Tf
-12.00 TL
-0.000 g
-1.00 -0.00 0.00 1.00 507.00 689.09 Tm
-(GND) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-520.000 680.000 m 
-520.000 670.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-529.000 680.000 m 
-511.000 680.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-526.000 682.000 m 
-514.000 682.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-523.000 684.000 m 
-517.000 684.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-521.000 686.000 m 
-519.000 686.000 l
-S
-BT
-/F2 12 Tf
-12.00 TL
-0.000 g
-1.00 -0.00 0.00 1.00 622.00 582.76 Tm
-(GND) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-635.000 600.000 m 
-635.000 610.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-626.000 600.000 m 
-644.000 600.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-629.000 598.000 m 
-641.000 598.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-632.000 596.000 m 
-638.000 596.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-634.000 594.000 m 
-636.000 594.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 656.92 616.15 Tm
-(BUSY) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-635.000 620.000 m 
-640.000 625.000 l
-655.000 625.000 l
-655.000 615.000 l
-640.000 615.000 l
-635.000 620.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 656.55 636.15 Tm
-(SCK) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-635.000 640.000 m 
-640.000 645.000 l
-655.000 645.000 l
-655.000 635.000 l
-640.000 635.000 l
-635.000 640.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 656.92 646.15 Tm
-(MISO) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-635.000 650.000 m 
-640.000 655.000 l
-655.000 655.000 l
-655.000 645.000 l
-640.000 645.000 l
-635.000 650.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 656.85 656.15 Tm
-(MOSI) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-635.000 660.000 m 
-640.000 665.000 l
-655.000 665.000 l
-655.000 655.000 l
-640.000 655.000 l
-635.000 660.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 657.00 666.45 Tm
-(CS) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-635.000 670.000 m 
-640.000 675.000 l
-655.000 675.000 l
-655.000 665.000 l
-640.000 665.000 l
-635.000 670.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 504.19 626.40 Tm
-(IRQ) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-545.000 630.000 m 
-540.000 625.000 l
-525.000 625.000 l
-525.000 635.000 l
-540.000 635.000 l
-545.000 630.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 495.06 646.57 Tm
-(NRST) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-545.000 650.000 m 
-540.000 645.000 l
-525.000 645.000 l
-525.000 655.000 l
-540.000 655.000 l
-545.000 650.000 l
-S
-10.00 w
-BT
-/F3 9 Tf
-9.00 TL
-0.000 0.000 0.502 rg
-584.95 693.33 Td
-(RA-01SH) Tj
-ET
-10.00 w
-BT
-/F3 9 Tf
-9.00 TL
-0.000 0.000 0.502 rg
-584.95 702.33 Td
-(HT-RA62) Tj
-ET
-2 J
-0 j
-100 M
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-557.000 690.000 m 
-623.000 690.000 l
-624.105 690.000 625.000 689.105 625.000 688.000 c
-625.000 602.000 l
-625.000 600.895 623.895 600.000 623.000 600.000 c
-557.000 600.000 l
-555.895 600.000 555.000 601.105 555.000 602.000 c
-555.000 688.000 l
-555.000 689.105 556.105 690.000 557.000 690.000 c
-S
-1.00 w
-0.53 0.00 0.00 RG
-0.53 0.00 0.00 rg
-[] 0 d
-561.50 685.00 m 561.50 685.83 560.83 686.50 560.00 686.50 c
-559.17 686.50 558.50 685.83 558.50 685.00 c
-558.50 684.17 559.17 683.50 560.00 683.50 c
-560.83 683.50 561.50 684.17 561.50 685.00 c
-B
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-558.70 676.00 Td
-(ANT) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-548.78 681.00 Td
-(1) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-545.000 680.000 m 
-555.000 680.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 g
-558.70 666.00 Td
-(GND) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 g
-548.78 671.00 Td
-(2) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-[] 0 d
-545.000 670.000 m 
-555.000 670.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-558.70 656.00 Td
-(3.3V) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-548.78 661.00 Td
-(3) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-545.000 660.000 m 
-555.000 660.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-558.70 646.00 Td
-(RESET) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-548.78 651.00 Td
-(4) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-545.000 650.000 m 
-555.000 650.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-558.70 636.00 Td
-(TXEN) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-548.78 641.00 Td
-(5) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-545.000 640.000 m 
-555.000 640.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-558.70 626.00 Td
-(DIO1) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-548.78 631.00 Td
-(6) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-545.000 630.000 m 
-555.000 630.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-558.70 616.00 Td
-(DIO2) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-548.78 621.00 Td
-(7) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-545.000 620.000 m 
-555.000 620.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-558.70 606.00 Td
-(DIO3) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-548.78 611.00 Td
-(8) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-545.000 610.000 m 
-555.000 610.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 g
-600.66 606.00 Td
-(GND) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 g
-625.50 611.00 Td
-(9) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-[] 0 d
-635.000 610.000 m 
-625.000 610.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-596.87 616.00 Td
-(BUSY) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-625.50 621.00 Td
-(10) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-635.000 620.000 m 
-625.000 620.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-596.46 626.00 Td
-(RXEN) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-625.50 631.00 Td
-(11) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-635.000 630.000 m 
-625.000 630.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-602.64 636.00 Td
-(SCK) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-625.50 641.00 Td
-(12) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-635.000 640.000 m 
-625.000 640.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-596.71 646.00 Td
-(MISO) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-625.50 651.00 Td
-(13) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-635.000 650.000 m 
-625.000 650.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-596.71 656.00 Td
-(MOSI) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-625.50 661.00 Td
-(14) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-635.000 660.000 m 
-625.000 660.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-602.27 666.00 Td
-(NSS) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-625.50 671.00 Td
-(15) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-635.000 670.000 m 
-625.000 670.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 g
-600.66 676.00 Td
-(GND) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 g
-625.50 681.00 Td
-(16) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-[] 0 d
-635.000 680.000 m 
-625.000 680.000 l
-S
-10.00 w
-BT
-/F3 9 Tf
-9.00 TL
-0.000 0.000 0.502 rg
-153.95 479.05 Td
-(AMC-U_FL) Tj
-ET
-10.00 w
-BT
-/F3 9 Tf
-9.00 TL
-0.000 0.000 0.502 rg
-153.95 488.16 Td
-(U6) Tj
-ET
-2 J
-0 j
-100 M
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-165.00 465.00 20.00 -20.00 re
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-0.00 1.00 -1.00 0.00 174.00 434.29 Tm
-(1) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-175.000 425.000 m 
-175.000 445.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-159.28 456.00 Td
-(2) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-155.000 455.000 m 
-165.000 455.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-185.00 456.00 Td
-(3) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-195.000 455.000 m 
-185.000 455.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-0.00 1.00 -1.00 0.00 174.00 465.00 Tm
-(4) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-175.000 475.000 m 
-175.000 465.000 l
-S
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-177.00 455.00 m 177.00 456.10 176.10 457.00 175.00 457.00 c
-173.90 457.00 173.00 456.10 173.00 455.00 c
-173.00 453.90 173.90 453.00 175.00 453.00 c
-176.10 453.00 177.00 453.90 177.00 455.00 c
-S
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-0.00 g
-[] 0 d
-175.000 453.000 m 
-175.000 445.000 l
-S
-1 J
-1 j
-1.00 w
-0.20 0.80 0.20 RG
-[] 0 d
-506.000 394.000 m 
-514.000 386.000 l
-514.000 394.000 m 
-506.000 386.000 l
-S
-1 J
-1 j
-1.00 w
-0.20 0.80 0.20 RG
-[] 0 d
-646.000 394.000 m 
-654.000 386.000 l
-654.000 394.000 m 
-646.000 386.000 l
-S
-BT
-/F2 12 Tf
-12.00 TL
-0.000 g
-1.00 -0.00 0.00 1.00 666.50 415.93 Tm
-(GND) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-660.000 420.000 m 
-650.000 420.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-660.000 411.000 m 
-660.000 429.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-662.000 414.000 m 
-662.000 426.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-664.000 417.000 m 
-664.000 423.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-666.000 419.000 m 
-666.000 421.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 0.53 0.00 RG
-0.00 g
-[] 0 d
-650.000 430.000 m 
-650.000 400.000 l
-S
-1 J
-1 j
-1.00 w
-0.20 0.80 0.20 RG
-[] 0 d
-796.000 389.000 m 
-804.000 381.000 l
-804.000 389.000 m 
-796.000 381.000 l
-S
-10.00 w
-BT
-/F3 9 Tf
-9.00 TL
-0.000 0.000 0.502 rg
-573.96 503.33 Td
-(E22-900M22S) Tj
-ET
-10.00 w
-BT
-/F3 9 Tf
-9.00 TL
-0.000 0.000 0.502 rg
-573.96 512.33 Td
-(E22-900M22S) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-532.00 377.00 Td
-(GND) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-513.57 381.00 Td
-(22) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-510.000 380.000 m 
-530.000 380.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-532.00 387.00 Td
-(ANT) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-513.57 391.00 Td
-(21) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-510.000 390.000 m 
-530.000 390.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-532.00 397.00 Td
-(GND) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-513.57 401.00 Td
-(20) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-510.000 400.000 m 
-530.000 400.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-532.00 417.00 Td
-(NSS) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-513.57 421.00 Td
-(19) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-510.000 420.000 m 
-530.000 420.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-532.00 427.00 Td
-(SCK) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-513.57 431.00 Td
-(18) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-510.000 430.000 m 
-530.000 430.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-532.00 437.00 Td
-(MOSI) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-513.57 441.00 Td
-(17) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-510.000 440.000 m 
-530.000 440.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-532.00 447.00 Td
-(MISO) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-513.57 451.00 Td
-(16) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-510.000 450.000 m 
-530.000 450.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-532.00 457.00 Td
-(NRST) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-513.57 461.00 Td
-(15) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-510.000 460.000 m 
-530.000 460.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-532.00 467.00 Td
-(BUSY) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-513.57 471.00 Td
-(14) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-510.000 470.000 m 
-530.000 470.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-532.00 477.00 Td
-(DIO1) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-513.57 481.00 Td
-(13) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-510.000 480.000 m 
-530.000 480.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-532.00 487.00 Td
-(GND) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-513.57 491.00 Td
-(12) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-510.000 490.000 m 
-530.000 490.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-607.36 487.00 Td
-(GND) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-635.00 491.00 Td
-(11) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-650.000 490.000 m 
-630.000 490.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-607.36 477.00 Td
-(GND) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-635.00 481.00 Td
-(10) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-650.000 480.000 m 
-630.000 480.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-609.29 467.00 Td
-(VCC) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-635.00 471.00 Td
-(9) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-650.000 470.000 m 
-630.000 470.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-604.49 457.00 Td
-(DIO2) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-635.00 461.00 Td
-(8) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-650.000 460.000 m 
-630.000 460.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-603.87 447.00 Td
-(TXEN) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-635.00 451.00 Td
-(7) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-650.000 450.000 m 
-630.000 450.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-603.16 437.00 Td
-(RXEN) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-635.00 441.00 Td
-(6) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-650.000 440.000 m 
-630.000 440.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-607.36 427.00 Td
-(GND) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-635.00 431.00 Td
-(5) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-650.000 430.000 m 
-630.000 430.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-607.36 417.00 Td
-(GND) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-635.00 421.00 Td
-(4) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-650.000 420.000 m 
-630.000 420.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-607.36 397.00 Td
-(GND) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-635.00 401.00 Td
-(3) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-650.000 400.000 m 
-630.000 400.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-607.36 387.00 Td
-(GND) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-635.00 391.00 Td
-(2) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-650.000 390.000 m 
-630.000 390.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-607.36 377.00 Td
-(GND) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-635.00 381.00 Td
-(1) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-650.000 380.000 m 
-630.000 380.000 l
-S
-2 J
-0 j
-100 M
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-532.000 500.000 m 
-628.000 500.000 l
-629.105 500.000 630.000 499.105 630.000 498.000 c
-630.000 362.000 l
-630.000 360.895 628.895 360.000 628.000 360.000 c
-532.000 360.000 l
-530.895 360.000 530.000 361.105 530.000 362.000 c
-530.000 498.000 l
-530.000 499.105 531.105 500.000 532.000 500.000 c
-S
-10.00 w
-BT
-/F3 9 Tf
-9.00 TL
-0.000 0.000 0.502 rg
-863.96 498.33 Td
-(E22-900M30S) Tj
-ET
-10.00 w
-BT
-/F3 9 Tf
-9.00 TL
-0.000 0.000 0.502 rg
-863.96 507.33 Td
-(E22-900M30S) Tj
-ET
-2 J
-0 j
-100 M
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-822.000 495.000 m 
-918.000 495.000 l
-919.105 495.000 920.000 494.105 920.000 493.000 c
-920.000 357.000 l
-920.000 355.895 918.895 355.000 918.000 355.000 c
-822.000 355.000 l
-820.895 355.000 820.000 356.105 820.000 357.000 c
-820.000 493.000 l
-820.000 494.105 821.105 495.000 822.000 495.000 c
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-897.36 372.00 Td
-(GND) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-925.00 376.00 Td
-(1) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-940.000 375.000 m 
-920.000 375.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-897.36 382.00 Td
-(GND) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-925.00 386.00 Td
-(2) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-940.000 385.000 m 
-920.000 385.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-897.36 392.00 Td
-(GND) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-925.00 396.00 Td
-(3) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-940.000 395.000 m 
-920.000 395.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-897.36 412.00 Td
-(GND) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-925.00 416.00 Td
-(4) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-940.000 415.000 m 
-920.000 415.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-897.36 422.00 Td
-(GND) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-925.00 426.00 Td
-(5) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-940.000 425.000 m 
-920.000 425.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-893.16 432.00 Td
-(RXEN) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-925.00 436.00 Td
-(6) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-940.000 435.000 m 
-920.000 435.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-893.87 442.00 Td
-(TXEN) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-925.00 446.00 Td
-(7) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-940.000 445.000 m 
-920.000 445.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-894.49 452.00 Td
-(DIO2) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-925.00 456.00 Td
-(8) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-940.000 455.000 m 
-920.000 455.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-899.29 462.00 Td
-(VCC) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-925.00 466.00 Td
-(9) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-940.000 465.000 m 
-920.000 465.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-899.29 472.00 Td
-(VCC) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-925.00 476.00 Td
-(10) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-940.000 475.000 m 
-920.000 475.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-897.36 482.00 Td
-(GND) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-925.00 486.00 Td
-(11) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-940.000 485.000 m 
-920.000 485.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-822.00 482.00 Td
-(GND) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-803.57 486.00 Td
-(12) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-800.000 485.000 m 
-820.000 485.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-822.00 472.00 Td
-(DIO1) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-803.57 476.00 Td
-(13) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-800.000 475.000 m 
-820.000 475.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-822.00 462.00 Td
-(BUSY) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-803.57 466.00 Td
-(14) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-800.000 465.000 m 
-820.000 465.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-822.00 452.00 Td
-(NRST) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-803.57 456.00 Td
-(15) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-800.000 455.000 m 
-820.000 455.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-822.00 442.00 Td
-(MISO) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-803.57 446.00 Td
-(16) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-800.000 445.000 m 
-820.000 445.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-822.00 432.00 Td
-(MOSI) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-803.57 436.00 Td
-(17) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-800.000 435.000 m 
-820.000 435.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-822.00 422.00 Td
-(SCK) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-803.57 426.00 Td
-(18) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-800.000 425.000 m 
-820.000 425.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-822.00 412.00 Td
-(NSS) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-803.57 416.00 Td
-(19) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-800.000 415.000 m 
-820.000 415.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-822.00 392.00 Td
-(GND) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-803.57 396.00 Td
-(20) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-800.000 395.000 m 
-820.000 395.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-822.00 382.00 Td
-(ANT) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-803.57 386.00 Td
-(21) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-800.000 385.000 m 
-820.000 385.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-822.00 372.00 Td
-(GND) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-803.57 376.00 Td
-(22) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-800.000 375.000 m 
-820.000 375.000 l
-S
-10.00 w
-BT
-/F3 9 Tf
-9.00 TL
-0.000 0.000 0.502 rg
-293.99 488.33 Td
-(E22-400MM22S) Tj
-ET
-10.00 w
-BT
-/F3 9 Tf
-9.00 TL
-0.000 0.000 0.502 rg
-293.99 497.33 Td
-(E22-900MM22S) Tj
-ET
-2 J
-0 j
-100 M
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-267.000 485.000 m 
-333.000 485.000 l
-334.105 485.000 335.000 484.105 335.000 483.000 c
-335.000 377.000 l
-335.000 375.895 333.895 375.000 333.000 375.000 c
-267.000 375.000 l
-265.895 375.000 265.000 376.105 265.000 377.000 c
-265.000 483.000 l
-265.000 484.105 266.105 485.000 267.000 485.000 c
-S
-1.00 w
-0.53 0.00 0.00 RG
-0.53 0.00 0.00 rg
-[] 0 d
-271.50 480.00 m 271.50 480.83 270.83 481.50 270.00 481.50 c
-269.17 481.50 268.50 480.83 268.50 480.00 c
-268.50 479.17 269.17 478.50 270.00 478.50 c
-270.83 478.50 271.50 479.17 271.50 480.00 c
-B
-BT
-/F1 9 Tf
-9.00 TL
-1.000 0.000 0.000 rg
-268.70 471.00 Td
-(VCC) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-1.000 0.000 0.000 rg
-258.79 476.00 Td
-(1) Tj
-ET
-1 J
-1 j
-1.00 w
-1.00 0.00 0.00 RG
-[] 0 d
-255.000 475.000 m 
-265.000 475.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 g
-268.70 461.00 Td
-(GND) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 g
-258.79 466.00 Td
-(2) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-[] 0 d
-255.000 465.000 m 
-265.000 465.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-268.70 451.00 Td
-(NRST) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-258.79 456.00 Td
-(3) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-255.000 455.000 m 
-265.000 455.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-268.70 441.00 Td
-(NC) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-258.79 446.00 Td
-(4) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-255.000 445.000 m 
-265.000 445.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-268.70 431.00 Td
-(NC) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-258.79 436.00 Td
-(5) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-255.000 435.000 m 
-265.000 435.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-268.70 421.00 Td
-(ANT) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-258.79 426.00 Td
-(6) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-255.000 425.000 m 
-265.000 425.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 g
-268.70 411.00 Td
-(GND) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 g
-258.79 416.00 Td
-(7) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-[] 0 d
-255.000 415.000 m 
-265.000 415.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-268.70 401.00 Td
-(NC) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-258.79 406.00 Td
-(8) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-255.000 405.000 m 
-265.000 405.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-268.70 391.00 Td
-(TXEN) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-258.79 396.00 Td
-(9) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-255.000 395.000 m 
-265.000 395.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-268.70 381.00 Td
-(RXEN) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-253.07 386.00 Td
-(10) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-255.000 385.000 m 
-265.000 385.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-306.87 381.00 Td
-(BUSY) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-335.50 386.00 Td
-(11) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-345.000 385.000 m 
-335.000 385.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-306.71 391.00 Td
-(MISO) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-335.50 396.00 Td
-(12) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-345.000 395.000 m 
-335.000 395.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-306.71 401.00 Td
-(MOSI) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-335.50 406.00 Td
-(13) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-345.000 405.000 m 
-335.000 405.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-312.27 411.00 Td
-(NSS) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-335.50 416.00 Td
-(14) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-345.000 415.000 m 
-335.000 415.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-312.64 421.00 Td
-(SCK) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-335.50 426.00 Td
-(15) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-345.000 425.000 m 
-335.000 425.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 g
-310.66 431.00 Td
-(GND) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 g
-335.50 436.00 Td
-(16) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-[] 0 d
-345.000 435.000 m 
-335.000 435.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-318.29 441.00 Td
-(NC) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-335.50 446.00 Td
-(17) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-345.000 445.000 m 
-335.000 445.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-307.79 451.00 Td
-(DIO3) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-335.50 456.00 Td
-(18) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-345.000 455.000 m 
-335.000 455.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-307.79 461.00 Td
-(DIO2) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-335.50 466.00 Td
-(19) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-345.000 465.000 m 
-335.000 465.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-307.79 471.00 Td
-(DIO1) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-335.50 476.00 Td
-(20) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-345.000 475.000 m 
-335.000 475.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 366.70 381.60 Tm
-(BUSY) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-345.000 385.000 m 
-350.000 390.000 l
-365.000 390.000 l
-365.000 380.000 l
-350.000 380.000 l
-345.000 385.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 203.19 381.40 Tm
-(RXEN) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-255.000 385.000 m 
-250.000 380.000 l
-235.000 380.000 l
-235.000 390.000 l
-250.000 390.000 l
-255.000 385.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 366.71 421.60 Tm
-(SCK) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-345.000 425.000 m 
-350.000 430.000 l
-365.000 430.000 l
-365.000 420.000 l
-350.000 420.000 l
-345.000 425.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 367.00 391.60 Tm
-(MISO) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-345.000 395.000 m 
-350.000 400.000 l
-365.000 400.000 l
-365.000 390.000 l
-350.000 390.000 l
-345.000 395.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 367.00 401.60 Tm
-(MOSI) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-345.000 405.000 m 
-350.000 410.000 l
-365.000 410.000 l
-365.000 400.000 l
-350.000 400.000 l
-345.000 405.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 367.00 411.60 Tm
-(CS) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-345.000 415.000 m 
-350.000 420.000 l
-365.000 420.000 l
-365.000 410.000 l
-350.000 410.000 l
-345.000 415.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 367.01 461.15 Tm
-(DIO2) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-345.000 465.000 m 
-350.000 470.000 l
-365.000 470.000 l
-365.000 460.000 l
-350.000 460.000 l
-345.000 465.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 367.00 471.60 Tm
-(IRQ) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-345.000 475.000 m 
-350.000 480.000 l
-365.000 480.000 l
-365.000 470.000 l
-350.000 470.000 l
-345.000 475.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 203.74 391.40 Tm
-(TXEN) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-255.000 395.000 m 
-250.000 390.000 l
-235.000 390.000 l
-235.000 400.000 l
-250.000 400.000 l
-255.000 395.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 205.06 451.57 Tm
-(NRST) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-255.000 455.000 m 
-250.000 450.000 l
-235.000 450.000 l
-235.000 460.000 l
-250.000 460.000 l
-255.000 455.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 367.01 451.15 Tm
-(DIO3) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-345.000 455.000 m 
-350.000 460.000 l
-365.000 460.000 l
-365.000 450.000 l
-350.000 450.000 l
-345.000 455.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 460.06 456.57 Tm
-(NRST) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-510.000 460.000 m 
-505.000 455.000 l
-490.000 455.000 l
-490.000 465.000 l
-505.000 465.000 l
-510.000 460.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 671.71 446.60 Tm
-(TXEN) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-650.000 450.000 m 
-655.000 455.000 l
-670.000 455.000 l
-670.000 445.000 l
-655.000 445.000 l
-650.000 450.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 469.19 476.40 Tm
-(IRQ) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-510.000 480.000 m 
-505.000 475.000 l
-490.000 475.000 l
-490.000 485.000 l
-505.000 485.000 l
-510.000 480.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 672.01 456.15 Tm
-(DIO2) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-650.000 460.000 m 
-655.000 465.000 l
-670.000 465.000 l
-670.000 455.000 l
-655.000 455.000 l
-650.000 460.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 474.69 416.40 Tm
-(CS) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-510.000 420.000 m 
-505.000 415.000 l
-490.000 415.000 l
-490.000 425.000 l
-505.000 425.000 l
-510.000 420.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 460.61 436.40 Tm
-(MOSI) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-510.000 440.000 m 
-505.000 435.000 l
-490.000 435.000 l
-490.000 445.000 l
-505.000 445.000 l
-510.000 440.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 460.61 446.40 Tm
-(MISO) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-510.000 450.000 m 
-505.000 445.000 l
-490.000 445.000 l
-490.000 455.000 l
-505.000 455.000 l
-510.000 450.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 466.77 426.40 Tm
-(SCK) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-510.000 430.000 m 
-505.000 425.000 l
-490.000 425.000 l
-490.000 435.000 l
-505.000 435.000 l
-510.000 430.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 671.71 436.60 Tm
-(RXEN) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-650.000 440.000 m 
-655.000 445.000 l
-670.000 445.000 l
-670.000 435.000 l
-655.000 435.000 l
-650.000 440.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 458.85 466.40 Tm
-(BUSY) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-510.000 470.000 m 
-505.000 465.000 l
-490.000 465.000 l
-490.000 475.000 l
-505.000 475.000 l
-510.000 470.000 l
-S
-BT
-/F2 12 Tf
-12.00 TL
-0.000 g
-1.00 -0.00 0.00 1.00 467.50 395.92 Tm
-(GND) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-500.000 400.000 m 
-510.000 400.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-500.000 409.000 m 
-500.000 391.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-498.000 406.000 m 
-498.000 394.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-496.000 403.000 m 
-496.000 397.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-494.000 401.000 m 
-494.000 399.000 l
-S
-BT
-/F2 12 Tf
-12.00 TL
-0.000 g
-1.00 -0.00 0.00 1.00 467.50 375.92 Tm
-(GND) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-500.000 380.000 m 
-510.000 380.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-500.000 389.000 m 
-500.000 371.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-498.000 386.000 m 
-498.000 374.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-496.000 383.000 m 
-496.000 377.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-494.000 381.000 m 
-494.000 379.000 l
-S
-BT
-/F2 12 Tf
-12.00 TL
-0.000 g
-1.00 -0.00 0.00 1.00 637.00 352.76 Tm
-(GND) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-650.000 370.000 m 
-650.000 380.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-641.000 370.000 m 
-659.000 370.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-644.000 368.000 m 
-656.000 368.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-647.000 366.000 m 
-653.000 366.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-649.000 364.000 m 
-651.000 364.000 l
-S
-BT
-/F2 12 Tf
-12.00 TL
-0.000 g
-1.00 -0.00 0.00 1.00 637.00 509.13 Tm
-(GND) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-650.000 500.000 m 
-650.000 490.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-659.000 500.000 m 
-641.000 500.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-656.000 502.000 m 
-644.000 502.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-653.000 504.000 m 
-647.000 504.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-651.000 506.000 m 
-649.000 506.000 l
-S
-BT
-/F2 12 Tf
-12.00 TL
-0.000 g
-1.00 -0.00 0.00 1.00 467.50 485.92 Tm
-(GND) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-500.000 490.000 m 
-510.000 490.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-500.000 499.000 m 
-500.000 481.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-498.000 496.000 m 
-498.000 484.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-496.000 493.000 m 
-496.000 487.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-494.000 491.000 m 
-494.000 489.000 l
-S
-BT
-/F2 12 Tf
-12.00 TL
-0.000 g
-1.00 -0.00 0.00 1.00 361.50 430.92 Tm
-(GND) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-355.000 435.000 m 
-345.000 435.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-355.000 426.000 m 
-355.000 444.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-357.000 429.000 m 
-357.000 441.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-359.000 432.000 m 
-359.000 438.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-361.000 434.000 m 
-361.000 436.000 l
-S
-BT
-/F2 12 Tf
-12.00 TL
-0.000 g
-1.00 -0.00 0.00 1.00 212.50 410.93 Tm
-(GND) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-245.000 415.000 m 
-255.000 415.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-245.000 424.000 m 
-245.000 406.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-243.000 421.000 m 
-243.000 409.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-241.000 418.000 m 
-241.000 412.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-239.000 416.000 m 
-239.000 414.000 l
-S
-BT
-/F2 12 Tf
-12.00 TL
-0.000 g
-1.00 -0.00 0.00 1.00 212.50 460.93 Tm
-(GND) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-245.000 465.000 m 
-255.000 465.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-245.000 474.000 m 
-245.000 456.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-243.000 471.000 m 
-243.000 459.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-241.000 468.000 m 
-241.000 462.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-239.000 466.000 m 
-239.000 464.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 0.53 0.00 RG
-0.00 g
-[] 0 d
-940.000 370.000 m 
-940.000 425.000 l
-S
-BT
-/F2 12 Tf
-12.00 TL
-0.000 g
-1.00 -0.00 0.00 1.00 757.50 480.92 Tm
-(GND) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-790.000 485.000 m 
-800.000 485.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-790.000 494.000 m 
-790.000 476.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-788.000 491.000 m 
-788.000 479.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-786.000 488.000 m 
-786.000 482.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-784.000 486.000 m 
-784.000 484.000 l
-S
-BT
-/F2 12 Tf
-12.00 TL
-0.000 g
-1.00 -0.00 0.00 1.00 927.00 504.09 Tm
-(GND) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-940.000 495.000 m 
-940.000 485.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-949.000 495.000 m 
-931.000 495.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-946.000 497.000 m 
-934.000 497.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-943.000 499.000 m 
-937.000 499.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-941.000 501.000 m 
-939.000 501.000 l
-S
-BT
-/F2 12 Tf
-12.00 TL
-0.000 g
-1.00 -0.00 0.00 1.00 757.50 370.92 Tm
-(GND) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-790.000 375.000 m 
-800.000 375.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-790.000 384.000 m 
-790.000 366.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-788.000 381.000 m 
-788.000 369.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-786.000 378.000 m 
-786.000 372.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-784.000 376.000 m 
-784.000 374.000 l
-S
-BT
-/F2 12 Tf
-12.00 TL
-0.000 g
-1.00 -0.00 0.00 1.00 757.50 390.92 Tm
-(GND) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-790.000 395.000 m 
-800.000 395.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-790.000 404.000 m 
-790.000 386.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-788.000 401.000 m 
-788.000 389.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-786.000 398.000 m 
-786.000 392.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-784.000 396.000 m 
-784.000 394.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 748.85 461.40 Tm
-(BUSY) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-800.000 465.000 m 
-795.000 460.000 l
-780.000 460.000 l
-780.000 470.000 l
-795.000 470.000 l
-800.000 465.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 961.71 431.60 Tm
-(RXEN) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-940.000 435.000 m 
-945.000 440.000 l
-960.000 440.000 l
-960.000 430.000 l
-945.000 430.000 l
-940.000 435.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 756.77 421.40 Tm
-(SCK) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-800.000 425.000 m 
-795.000 420.000 l
-780.000 420.000 l
-780.000 430.000 l
-795.000 430.000 l
-800.000 425.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 750.61 441.40 Tm
-(MISO) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-800.000 445.000 m 
-795.000 440.000 l
-780.000 440.000 l
-780.000 450.000 l
-795.000 450.000 l
-800.000 445.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 750.61 431.40 Tm
-(MOSI) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-800.000 435.000 m 
-795.000 430.000 l
-780.000 430.000 l
-780.000 440.000 l
-795.000 440.000 l
-800.000 435.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 764.69 411.40 Tm
-(CS) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-800.000 415.000 m 
-795.000 410.000 l
-780.000 410.000 l
-780.000 420.000 l
-795.000 420.000 l
-800.000 415.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 962.01 451.15 Tm
-(DIO2) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-940.000 455.000 m 
-945.000 460.000 l
-960.000 460.000 l
-960.000 450.000 l
-945.000 450.000 l
-940.000 455.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 759.19 471.40 Tm
-(IRQ) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-800.000 475.000 m 
-795.000 470.000 l
-780.000 470.000 l
-780.000 480.000 l
-795.000 480.000 l
-800.000 475.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 961.71 441.60 Tm
-(TXEN) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-940.000 445.000 m 
-945.000 450.000 l
-960.000 450.000 l
-960.000 440.000 l
-945.000 440.000 l
-940.000 445.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 750.06 451.57 Tm
-(NRST) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-800.000 455.000 m 
-795.000 450.000 l
-780.000 450.000 l
-780.000 460.000 l
-795.000 460.000 l
-800.000 455.000 l
-S
-BT
-/F2 12 Tf
-12.00 TL
-0.000 g
-1.00 -0.00 0.00 1.00 927.00 342.76 Tm
-(GND) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-940.000 360.000 m 
-940.000 370.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-931.000 360.000 m 
-949.000 360.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-934.000 358.000 m 
-946.000 358.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-937.000 356.000 m 
-943.000 356.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-939.000 354.000 m 
-941.000 354.000 l
-S
-BT
-/F2 12 Tf
-12.00 TL
-0.000 g
-1.00 -0.00 0.00 1.00 243.00 487.00 Tm
-(VCC) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-255.000 485.000 m 
-255.000 475.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-250.000 485.000 m 
-260.000 485.000 l
-S
-BT
-/F2 12 Tf
-12.00 TL
-0.000 g
-1.00 -0.00 0.00 1.00 660.50 465.93 Tm
-(VCC) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-660.000 470.000 m 
-650.000 470.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-660.000 475.000 m 
-660.000 465.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 0.53 0.00 RG
-0.00 g
-[] 0 d
-175.000 425.000 m 
-210.000 425.000 l
-255.000 425.000 l
-S
-BT
-/F2 12 Tf
-12.00 TL
-0.000 g
-1.00 -0.00 0.00 1.00 142.00 429.00 Tm
-(GND) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-155.000 445.000 m 
-155.000 455.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-146.000 445.000 m 
-164.000 445.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-149.000 443.000 m 
-161.000 443.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-152.000 441.000 m 
-158.000 441.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-154.000 439.000 m 
-156.000 439.000 l
-S
-BT
-/F2 12 Tf
-12.00 TL
-0.000 g
-1.00 -0.00 0.00 1.00 182.00 428.00 Tm
-(GND) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-195.000 445.000 m 
-195.000 455.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-186.000 445.000 m 
-204.000 445.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-189.000 443.000 m 
-201.000 443.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-192.000 441.000 m 
-198.000 441.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-194.000 439.000 m 
-196.000 439.000 l
-S
-BT
-/F2 12 Tf
-12.00 TL
-0.000 g
-1.00 -0.00 0.00 1.00 950.50 460.92 Tm
-(+5V) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-950.000 465.000 m 
-940.000 465.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-950.000 470.000 m 
-950.000 460.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 0.53 0.00 RG
-0.00 g
-[] 0 d
-650.000 490.000 m 
-650.000 480.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 0.53 0.00 RG
-0.00 g
-[] 0 d
-940.000 465.000 m 
-940.000 475.000 l
-S
-1 J
-1 j
-1.00 w
-0.20 0.80 0.20 RG
-[] 0 d
-231.000 294.000 m 
-239.000 286.000 l
-239.000 294.000 m 
-231.000 286.000 l
-S
-1 J
-1 j
-1.00 w
-0.20 0.80 0.20 RG
-[] 0 d
-371.000 244.000 m 
-379.000 236.000 l
-379.000 244.000 m 
-371.000 236.000 l
-S
-1 J
-1 j
-1.00 w
-0.20 0.80 0.20 RG
-[] 0 d
-371.000 284.000 m 
-379.000 276.000 l
-379.000 284.000 m 
-371.000 276.000 l
-S
-1 J
-1 j
-1.00 w
-0.20 0.80 0.20 RG
-[] 0 d
-371.000 274.000 m 
-379.000 266.000 l
-379.000 274.000 m 
-371.000 266.000 l
-S
-1 J
-1 j
-1.00 w
-0.20 0.80 0.20 RG
-[] 0 d
-231.000 204.000 m 
-239.000 196.000 l
-239.000 204.000 m 
-231.000 196.000 l
-S
-1 J
-1 j
-1.00 w
-0.20 0.80 0.20 RG
-[] 0 d
-371.000 204.000 m 
-379.000 196.000 l
-379.000 204.000 m 
-371.000 196.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 397.71 246.28 Tm
-(NRST) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-375.000 250.000 m 
-380.000 255.000 l
-395.000 255.000 l
-395.000 245.000 l
-380.000 245.000 l
-375.000 250.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 397.00 256.60 Tm
-(IRQ) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-375.000 260.000 m 
-380.000 265.000 l
-395.000 265.000 l
-395.000 255.000 l
-380.000 255.000 l
-375.000 260.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 183.85 236.40 Tm
-(BUSY) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-235.000 240.000 m 
-230.000 235.000 l
-215.000 235.000 l
-215.000 245.000 l
-230.000 245.000 l
-235.000 240.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 199.69 246.40 Tm
-(CS) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-235.000 250.000 m 
-230.000 245.000 l
-215.000 245.000 l
-215.000 255.000 l
-230.000 255.000 l
-235.000 250.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 185.61 266.40 Tm
-(MOSI) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-235.000 270.000 m 
-230.000 265.000 l
-215.000 265.000 l
-215.000 275.000 l
-230.000 275.000 l
-235.000 270.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 191.77 256.40 Tm
-(SCK) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-235.000 260.000 m 
-230.000 255.000 l
-215.000 255.000 l
-215.000 265.000 l
-230.000 265.000 l
-235.000 260.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 185.61 276.40 Tm
-(MISO) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-235.000 280.000 m 
-230.000 275.000 l
-215.000 275.000 l
-215.000 285.000 l
-230.000 285.000 l
-235.000 280.000 l
-S
-10.00 w
-BT
-/F3 9 Tf
-9.00 TL
-0.000 0.000 0.502 rg
-298.96 313.33 Td
-(E80-900M2213S) Tj
-ET
-10.00 w
-BT
-/F3 9 Tf
-9.00 TL
-0.000 0.000 0.502 rg
-298.96 322.33 Td
-(E80-900M2213S) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-257.00 187.00 Td
-(GND) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-238.57 191.00 Td
-(22) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-235.000 190.000 m 
-255.000 190.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-257.00 197.00 Td
-(ANT_2.4) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-238.57 201.00 Td
-(21) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-235.000 200.000 m 
-255.000 200.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-257.00 207.00 Td
-(GND) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-238.57 211.00 Td
-(20) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-235.000 210.000 m 
-255.000 210.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-257.00 227.00 Td
-(GND) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-238.57 231.00 Td
-(19) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-235.000 230.000 m 
-255.000 230.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-257.00 237.00 Td
-(BUSY) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-238.57 241.00 Td
-(18) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-235.000 240.000 m 
-255.000 240.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-257.00 247.00 Td
-(NSS) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-238.57 251.00 Td
-(17) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-235.000 250.000 m 
-255.000 250.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-257.00 257.00 Td
-(SCK) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-238.57 261.00 Td
-(16) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-235.000 260.000 m 
-255.000 260.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-257.00 267.00 Td
-(MOSI) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-238.57 271.00 Td
-(15) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-235.000 270.000 m 
-255.000 270.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-257.00 277.00 Td
-(MISO) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-238.57 281.00 Td
-(14) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-235.000 280.000 m 
-255.000 280.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-257.00 287.00 Td
-(NC) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-238.57 291.00 Td
-(13) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-235.000 290.000 m 
-255.000 290.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-257.00 297.00 Td
-(GND) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-238.57 301.00 Td
-(12) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-235.000 300.000 m 
-255.000 300.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-332.36 297.00 Td
-(GND) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-360.00 301.00 Td
-(11) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-375.000 300.000 m 
-355.000 300.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-334.29 287.00 Td
-(VCC) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-360.00 291.00 Td
-(10) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-375.000 290.000 m 
-355.000 290.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-329.49 277.00 Td
-(DIO7) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-360.00 281.00 Td
-(9) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-375.000 280.000 m 
-355.000 280.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-329.49 267.00 Td
-(DIO8) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-360.00 271.00 Td
-(8) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-375.000 270.000 m 
-355.000 270.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-329.49 257.00 Td
-(DIO9) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-360.00 261.00 Td
-(7) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-375.000 260.000 m 
-355.000 260.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-328.32 247.00 Td
-(NRST) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-360.00 251.00 Td
-(6) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-375.000 250.000 m 
-355.000 250.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-339.99 237.00 Td
-(NC) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-360.00 241.00 Td
-(5) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-375.000 240.000 m 
-355.000 240.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-332.36 227.00 Td
-(GND) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-360.00 231.00 Td
-(4) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-375.000 230.000 m 
-355.000 230.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-332.36 207.00 Td
-(GND) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-360.00 211.00 Td
-(3) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-375.000 210.000 m 
-355.000 210.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-311.72 197.00 Td
-(ANT_900) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-360.00 201.00 Td
-(2) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-375.000 200.000 m 
-355.000 200.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-332.36 187.00 Td
-(GND) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-360.00 191.00 Td
-(1) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-375.000 190.000 m 
-355.000 190.000 l
-S
-2 J
-0 j
-100 M
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-257.000 310.000 m 
-353.000 310.000 l
-354.105 310.000 355.000 309.105 355.000 308.000 c
-355.000 172.000 l
-355.000 170.895 353.895 170.000 353.000 170.000 c
-257.000 170.000 l
-255.895 170.000 255.000 171.105 255.000 172.000 c
-255.000 308.000 l
-255.000 309.105 256.105 310.000 257.000 310.000 c
-S
-BT
-/F2 12 Tf
-12.00 TL
-0.000 g
-1.00 -0.00 0.00 1.00 362.00 163.00 Tm
-(GND) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-375.000 180.000 m 
-375.000 190.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-366.000 180.000 m 
-384.000 180.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-369.000 178.000 m 
-381.000 178.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-372.000 176.000 m 
-378.000 176.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-374.000 174.000 m 
-376.000 174.000 l
-S
-BT
-/F2 12 Tf
-12.00 TL
-0.000 g
-1.00 -0.00 0.00 1.00 391.50 206.00 Tm
-(GND) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-385.000 210.000 m 
-375.000 210.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-385.000 201.000 m 
-385.000 219.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-387.000 204.000 m 
-387.000 216.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-389.000 207.000 m 
-389.000 213.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-391.000 209.000 m 
-391.000 211.000 l
-S
-BT
-/F2 12 Tf
-12.00 TL
-0.000 g
-1.00 -0.00 0.00 1.00 391.50 226.00 Tm
-(GND) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-385.000 230.000 m 
-375.000 230.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-385.000 221.000 m 
-385.000 239.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-387.000 224.000 m 
-387.000 236.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-389.000 227.000 m 
-389.000 233.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-391.000 229.000 m 
-391.000 231.000 l
-S
-BT
-/F2 12 Tf
-12.00 TL
-0.000 g
-1.00 -0.00 0.00 1.00 391.50 296.00 Tm
-(GND) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-385.000 300.000 m 
-375.000 300.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-385.000 291.000 m 
-385.000 309.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-387.000 294.000 m 
-387.000 306.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-389.000 297.000 m 
-389.000 303.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-391.000 299.000 m 
-391.000 301.000 l
-S
-BT
-/F2 12 Tf
-12.00 TL
-0.000 g
-1.00 -0.00 0.00 1.00 192.50 296.00 Tm
-(GND) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-225.000 300.000 m 
-235.000 300.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-225.000 309.000 m 
-225.000 291.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-223.000 306.000 m 
-223.000 294.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-221.000 303.000 m 
-221.000 297.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-219.000 301.000 m 
-219.000 299.000 l
-S
-BT
-/F2 12 Tf
-12.00 TL
-0.000 g
-1.00 -0.00 0.00 1.00 192.50 226.00 Tm
-(GND) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-225.000 230.000 m 
-235.000 230.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-225.000 239.000 m 
-225.000 221.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-223.000 236.000 m 
-223.000 224.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-221.000 233.000 m 
-221.000 227.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-219.000 231.000 m 
-219.000 229.000 l
-S
-BT
-/F2 12 Tf
-12.00 TL
-0.000 g
-1.00 -0.00 0.00 1.00 192.50 206.00 Tm
-(GND) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-225.000 210.000 m 
-235.000 210.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-225.000 219.000 m 
-225.000 201.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-223.000 216.000 m 
-223.000 204.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-221.000 213.000 m 
-221.000 207.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-219.000 211.000 m 
-219.000 209.000 l
-S
-BT
-/F2 12 Tf
-12.00 TL
-0.000 g
-1.00 -0.00 0.00 1.00 192.50 186.00 Tm
-(GND) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-225.000 190.000 m 
-235.000 190.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-225.000 199.000 m 
-225.000 181.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-223.000 196.000 m 
-223.000 184.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-221.000 193.000 m 
-221.000 187.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-219.000 191.000 m 
-219.000 189.000 l
-S
-BT
-/F2 12 Tf
-12.00 TL
-0.000 g
-1.00 -0.00 0.00 1.00 386.00 286.00 Tm
-(VCC) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-385.000 290.000 m 
-375.000 290.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-385.000 295.000 m 
-385.000 285.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 0.53 0.00 RG
-0.00 g
-[] 0 d
-160.000 675.000 m 
-160.000 680.000 l
-160.000 685.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 0.53 0.00 RG
-0.00 g
-[] 0 d
-145.000 655.000 m 
-160.000 655.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 0.53 0.00 RG
-0.00 g
-[] 0 d
-145.000 645.000 m 
-160.000 645.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 0.53 0.00 RG
-0.00 g
-[] 0 d
-145.000 635.000 m 
-160.000 635.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 0.53 0.00 RG
-0.00 g
-[] 0 d
-145.000 625.000 m 
-160.000 625.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 0.53 0.00 RG
-0.00 g
-[] 0 d
-145.000 615.000 m 
-160.000 615.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 0.53 0.00 RG
-0.00 g
-[] 0 d
-145.000 605.000 m 
-160.000 605.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 112.70 591.40 Tm
-(P1.06) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-160.000 595.000 m 
-155.000 590.000 l
-140.000 590.000 l
-140.000 600.000 l
-155.000 600.000 l
-160.000 595.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 112.70 701.40 Tm
-(P0.06) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-160.000 705.000 m 
-155.000 700.000 l
-140.000 700.000 l
-140.000 710.000 l
-155.000 710.000 l
-160.000 705.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 112.70 691.40 Tm
-(P0.08) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-160.000 695.000 m 
-155.000 690.000 l
-140.000 690.000 l
-140.000 700.000 l
-155.000 700.000 l
-160.000 695.000 l
-S
-10.00 w
-BT
-/F3 9 Tf
-9.00 TL
-0.000 0.000 0.502 rg
-417.00 632.75 Td
-(1.5M) Tj
-ET
-10.00 w
-BT
-/F3 9 Tf
-9.00 TL
-0.000 0.000 0.502 rg
-417.00 641.75 Td
-(R2) Tj
-ET
-2 J
-0 j
-100 M
-1.00 w
-0.63 0.00 0.00 RG
-[] 0 d
-405.00 655.00 10.00 -20.00 re
-S
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-410.000 655.000 m 
-410.000 665.000 l
-S
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-410.000 635.000 m 
-410.000 625.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-0.00 1.00 -1.00 0.00 413.70 727.50 Tm
-(Batt) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-410.000 705.000 m 
-405.000 710.000 l
-405.000 725.000 l
-415.000 725.000 l
-415.000 710.000 l
-410.000 705.000 l
-S
-1 J
-1 j
-1.00 w
-0.20 0.80 0.20 RG
-[] 0 d
-186.000 549.000 m 
-194.000 541.000 l
-194.000 549.000 m 
-186.000 541.000 l
-S
-1 J
-1 j
-1.00 w
-0.20 0.80 0.20 RG
-[] 0 d
-196.000 549.000 m 
-204.000 541.000 l
-204.000 549.000 m 
-196.000 541.000 l
-S
-1 J
-1 j
-1.00 w
-0.20 0.80 0.20 RG
-[] 0 d
-206.000 549.000 m 
-214.000 541.000 l
-214.000 549.000 m 
-206.000 541.000 l
-S
-1 J
-1 j
-1.00 w
-0.20 0.80 0.20 RG
-[] 0 d
-276.000 719.000 m 
-284.000 711.000 l
-284.000 719.000 m 
-276.000 711.000 l
-S
-1 J
-1 j
-1.00 w
-0.20 0.80 0.20 RG
-[] 0 d
-156.000 719.000 m 
-164.000 711.000 l
-164.000 719.000 m 
-156.000 711.000 l
-S
-BT
-/F2 12 Tf
-12.00 TL
-0.000 g
-1.00 -0.00 0.00 1.00 397.00 599.00 Tm
-(GND) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-410.000 615.000 m 
-410.000 625.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-401.000 615.000 m 
-419.000 615.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-404.000 613.000 m 
-416.000 613.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-407.000 611.000 m 
-413.000 611.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-409.000 609.000 m 
-411.000 609.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 0.53 0.00 RG
-0.00 g
-[] 0 d
-410.000 665.000 m 
-280.000 665.000 l
-S
-10.00 w
-BT
-/F3 9 Tf
-9.00 TL
-0.000 0.000 0.502 rg
-417.00 672.75 Td
-(1M) Tj
-ET
-10.00 w
-BT
-/F3 9 Tf
-9.00 TL
-0.000 0.000 0.502 rg
-417.00 681.75 Td
-(R1) Tj
-ET
-2 J
-0 j
-100 M
-1.00 w
-0.63 0.00 0.00 RG
-[] 0 d
-405.00 695.00 10.00 -20.00 re
-S
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-410.000 695.000 m 
-410.000 705.000 l
-S
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-410.000 675.000 m 
-410.000 665.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 0.53 0.00 RG
-0.00 g
-[] 0 d
-290.000 685.000 m 
-280.000 685.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 312.01 681.07 Tm
-(RBtn) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-290.000 685.000 m 
-295.000 690.000 l
-310.000 690.000 l
-310.000 680.000 l
-295.000 680.000 l
-290.000 685.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 99.24 621.60 Tm
-(UBtn) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-145.000 625.000 m 
-140.000 620.000 l
-125.000 620.000 l
-125.000 630.000 l
-140.000 630.000 l
-145.000 625.000 l
-S
-BT
-/F2 12 Tf
-12.00 TL
-0.000 g
-1.00 -0.00 0.00 1.00 117.50 676.00 Tm
-(GND) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-150.000 680.000 m 
-160.000 680.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-150.000 689.000 m 
-150.000 671.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-148.000 686.000 m 
-148.000 674.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-146.000 683.000 m 
-146.000 677.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-144.000 681.000 m 
-144.000 679.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 0.53 0.00 RG
-0.00 g
-[] 0 d
-290.000 595.000 m 
-280.000 595.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 0.53 0.00 RG
-0.00 g
-[] 0 d
-290.000 655.000 m 
-280.000 655.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 0.53 0.00 RG
-0.00 g
-[] 0 d
-290.000 605.000 m 
-280.000 605.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 0.53 0.00 RG
-0.00 g
-[] 0 d
-290.000 625.000 m 
-280.000 625.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 92.64 631.40 Tm
-(GPSen) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-145.000 635.000 m 
-140.000 630.000 l
-125.000 630.000 l
-125.000 640.000 l
-140.000 640.000 l
-145.000 635.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 93.85 651.40 Tm
-(GPSrx) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-145.000 655.000 m 
-140.000 650.000 l
-125.000 650.000 l
-125.000 660.000 l
-140.000 660.000 l
-145.000 655.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 94.40 641.40 Tm
-(GPStx) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-145.000 645.000 m 
-140.000 640.000 l
-125.000 640.000 l
-125.000 650.000 l
-140.000 650.000 l
-145.000 645.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 0.53 0.00 RG
-0.00 g
-[] 0 d
-290.000 615.000 m 
-280.000 615.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 0.53 0.00 RG
-0.00 g
-[] 0 d
-290.000 635.000 m 
-280.000 635.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 0.53 0.00 RG
-0.00 g
-[] 0 d
-290.000 645.000 m 
-280.000 645.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 0.53 0.00 RG
-0.00 g
-[] 0 d
-345.000 695.000 m 
-280.000 695.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 0.53 0.00 RG
-0.00 g
-[] 0 d
-345.000 675.000 m 
-280.000 675.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 312.00 601.60 Tm
-(IRQ) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-290.000 605.000 m 
-295.000 610.000 l
-310.000 610.000 l
-310.000 600.000 l
-295.000 600.000 l
-290.000 605.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 312.50 591.60 Tm
-(NRST) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-290.000 595.000 m 
-295.000 600.000 l
-310.000 600.000 l
-310.000 590.000 l
-295.000 590.000 l
-290.000 595.000 l
-S
-BT
-/F2 12 Tf
-12.00 TL
-0.000 g
-1.00 -0.00 0.00 1.00 356.00 671.00 Tm
-(VCC) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-355.000 675.000 m 
-345.000 675.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-355.000 680.000 m 
-355.000 670.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 312.00 621.60 Tm
-(CS) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-290.000 625.000 m 
-295.000 630.000 l
-310.000 630.000 l
-310.000 620.000 l
-295.000 620.000 l
-290.000 625.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 312.00 631.60 Tm
-(MOSI) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-290.000 635.000 m 
-295.000 640.000 l
-310.000 640.000 l
-310.000 630.000 l
-295.000 630.000 l
-290.000 635.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 312.00 641.60 Tm
-(MISO) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-290.000 645.000 m 
-295.000 650.000 l
-310.000 650.000 l
-310.000 640.000 l
-295.000 640.000 l
-290.000 645.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 311.84 611.60 Tm
-(SCk) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-290.000 615.000 m 
-295.000 620.000 l
-310.000 620.000 l
-310.000 610.000 l
-295.000 610.000 l
-290.000 615.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 311.70 651.60 Tm
-(BUSY) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-290.000 655.000 m 
-295.000 660.000 l
-310.000 660.000 l
-310.000 650.000 l
-295.000 650.000 l
-290.000 655.000 l
-S
-BT
-/F2 12 Tf
-12.00 TL
-0.000 g
-1.00 -0.00 0.00 1.00 361.50 691.00 Tm
-(GND) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-355.000 695.000 m 
-345.000 695.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-355.000 686.000 m 
-355.000 704.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-357.000 689.000 m 
-357.000 701.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-359.000 692.000 m 
-359.000 698.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-361.000 694.000 m 
-361.000 696.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 302.50 701.30 Tm
-(Batt) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-280.000 705.000 m 
-285.000 710.000 l
-300.000 710.000 l
-300.000 700.000 l
-285.000 700.000 l
-280.000 705.000 l
-S
-10.00 w
-BT
-/F1 13 Tf
-13.00 TL
-0.000 0.000 1.000 rg
-1015.00 25.00 Td
-(Nom De Tom) Tj
-ET
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 647.00 251.60 Tm
-(IRQ) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-625.000 255.000 m 
-630.000 260.000 l
-645.000 260.000 l
-645.000 250.000 l
-630.000 250.000 l
-625.000 255.000 l
-S
-BT
-/F2 12 Tf
-12.00 TL
-0.000 g
-1.00 -0.00 0.00 1.00 513.00 297.00 Tm
-(VCC) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-525.000 295.000 m 
-525.000 285.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-520.000 295.000 m 
-530.000 295.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 0.53 0.00 RG
-0.00 g
-[] 0 d
-625.000 175.000 m 
-625.000 215.000 l
-S
-BT
-/F2 12 Tf
-12.00 TL
-0.000 g
-1.00 -0.00 0.00 1.00 612.00 149.00 Tm
-(GND) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-625.000 165.000 m 
-625.000 175.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-616.000 165.000 m 
-634.000 165.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-619.000 163.000 m 
-631.000 163.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-622.000 161.000 m 
-628.000 161.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-624.000 159.000 m 
-626.000 159.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 474.33 201.85 Tm
-(BUSY) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-525.000 205.000 m 
-520.000 200.000 l
-505.000 200.000 l
-505.000 210.000 l
-520.000 210.000 l
-525.000 205.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 473.74 191.85 Tm
-(RXEN) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-525.000 195.000 m 
-520.000 190.000 l
-505.000 190.000 l
-505.000 200.000 l
-520.000 200.000 l
-525.000 195.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 482.41 231.85 Tm
-(SCK) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-525.000 235.000 m 
-520.000 230.000 l
-505.000 230.000 l
-505.000 240.000 l
-520.000 240.000 l
-525.000 235.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 476.41 251.85 Tm
-(MISO) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-525.000 255.000 m 
-520.000 250.000 l
-505.000 250.000 l
-505.000 260.000 l
-520.000 260.000 l
-525.000 255.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 476.48 241.85 Tm
-(MOSI) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-525.000 245.000 m 
-520.000 240.000 l
-505.000 240.000 l
-505.000 250.000 l
-520.000 250.000 l
-525.000 245.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 489.69 221.45 Tm
-(CS) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-525.000 225.000 m 
-520.000 220.000 l
-505.000 220.000 l
-505.000 230.000 l
-520.000 230.000 l
-525.000 225.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 647.01 241.15 Tm
-(DIO2) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-625.000 245.000 m 
-630.000 250.000 l
-645.000 250.000 l
-645.000 240.000 l
-630.000 240.000 l
-625.000 245.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 474.33 181.85 Tm
-(TXEN) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-525.000 185.000 m 
-520.000 180.000 l
-505.000 180.000 l
-505.000 190.000 l
-520.000 190.000 l
-525.000 185.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 475.06 261.57 Tm
-(NRST) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-525.000 265.000 m 
-520.000 260.000 l
-505.000 260.000 l
-505.000 270.000 l
-520.000 270.000 l
-525.000 265.000 l
-S
-10.00 w
-BT
-/F3 9 Tf
-9.00 TL
-0.000 0.000 0.502 rg
-568.96 298.33 Td
-(SX1262_MOD) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-584.58 282.00 Td
-(ANT) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-610.00 286.00 Td
-(16) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-625.000 285.000 m 
-605.000 285.000 l
-S
-10.00 w
-BT
-/F3 9 Tf
-9.00 TL
-0.000 0.000 0.502 rg
-568.96 307.33 Td
-(CORE_SX1262) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-582.36 202.00 Td
-(GND) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-610.00 206.00 Td
-(2) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-625.000 205.000 m 
-605.000 205.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-547.00 192.00 Td
-(RXEN) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-534.28 196.00 Td
-(3) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-525.000 195.000 m 
-545.000 195.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-547.00 182.00 Td
-(TXEN) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-534.28 186.00 Td
-(4) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-525.000 185.000 m 
-545.000 185.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-579.49 242.00 Td
-(DIO2) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-610.00 246.00 Td
-(5) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-625.000 245.000 m 
-605.000 245.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-579.49 252.00 Td
-(DIO1) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-610.00 256.00 Td
-(6) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-625.000 255.000 m 
-605.000 255.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-582.36 192.00 Td
-(GND) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-610.00 196.00 Td
-(7) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-625.000 195.000 m 
-605.000 195.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-547.00 282.00 Td
-(3V3) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-534.28 286.00 Td
-(8) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-525.000 285.000 m 
-545.000 285.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-547.00 202.00 Td
-(BUSY) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-534.28 206.00 Td
-(9) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-525.000 205.000 m 
-545.000 205.000 l
-S
-1.00 w
-0.53 0.00 0.00 RG
-545.00 265.00 m 545.00 266.66 543.66 268.00 542.00 268.00 c
-540.34 268.00 539.00 266.66 539.00 265.00 c
-539.00 263.34 540.34 262.00 542.00 262.00 c
-543.66 262.00 545.00 263.34 545.00 265.00 c
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-547.00 262.00 Td
-(RST) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-528.57 266.00 Td
-(10) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-525.000 265.000 m 
-539.000 265.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-547.00 252.00 Td
-(MISO) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-528.57 256.00 Td
-(11) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-525.000 255.000 m 
-545.000 255.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-547.00 242.00 Td
-(MOSI) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-528.57 246.00 Td
-(12) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-525.000 245.000 m 
-545.000 245.000 l
-S
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-545.000 232.000 m 
-548.000 235.000 l
-545.000 238.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-547.00 232.00 Td
-(CLK) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-528.57 236.00 Td
-(13) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-525.000 235.000 m 
-545.000 235.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-547.00 222.00 Td
-(CS) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-528.57 226.00 Td
-(14) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-525.000 225.000 m 
-545.000 225.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-582.36 182.00 Td
-(GND) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-610.00 186.00 Td
-(15) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-625.000 185.000 m 
-605.000 185.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-582.36 212.00 Td
-(GND) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-610.00 216.00 Td
-(1) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-625.000 215.000 m 
-605.000 215.000 l
-S
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-0.00 g
-[] 0 d
-550.000 295.000 m 
-600.000 295.000 l
-S
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-0.00 g
-[] 0 d
-605.000 290.000 m 
-605.000 180.000 l
-S
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-0.00 g
-[] 0 d
-600.000 175.000 m 
-550.000 175.000 l
-S
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-0.00 g
-[] 0 d
-545.000 180.000 m 
-545.000 290.000 l
-S
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-545.000 290.000 m 
-545.000 295.000 545.000 295.000 550.000 295.000 c
-S
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-600.000 295.000 m 
-605.000 295.000 605.000 295.000 605.000 290.000 c
-S
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-605.000 180.000 m 
-605.000 175.000 605.000 175.000 600.000 175.000 c
-S
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-545.000 180.000 m 
-545.000 175.000 545.000 175.000 550.000 175.000 c
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 737.72 681.45 Tm
-(MCU_RXEN) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-820.000 685.000 m 
-815.000 680.000 l
-800.000 680.000 l
-800.000 690.000 l
-815.000 690.000 l
-820.000 685.000 l
-S
-10.00 w
-BT
-/F3 9 Tf
-9.00 TL
-0.000 0.000 0.502 rg
-1115.00 362.67 Td
-(100uF) Tj
-ET
-10.00 w
-BT
-/F3 9 Tf
-9.00 TL
-0.000 0.000 0.502 rg
-1115.00 371.67 Td
-(C1) Tj
-ET
-1 J
-1 j
-1.00 w
-0.63 0.00 0.00 RG
-0.00 g
-[] 0 d
-1113.000 373.000 m 
-1097.000 373.000 l
-S
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-1105.000 365.000 m 
-1105.000 355.000 l
-S
-1 J
-1 j
-1.00 w
-0.63 0.00 0.00 RG
-0.00 g
-[] 0 d
-1105.000 385.000 m 
-1105.000 377.000 l
-S
-1 J
-1 j
-1.00 w
-0.63 0.00 0.00 RG
-0.00 g
-[] 0 d
-1097.000 377.000 m 
-1113.000 377.000 l
-S
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-1105.000 385.000 m 
-1105.000 395.000 l
-S
-1 J
-1 j
-1.00 w
-0.63 0.00 0.00 RG
-0.00 g
-[] 0 d
-1105.000 373.000 m 
-1105.000 365.000 l
-S
-10.00 w
-BT
-/F3 9 Tf
-9.00 TL
-0.000 0.000 0.502 rg
-1065.00 362.67 Td
-(100uF) Tj
-ET
-10.00 w
-BT
-/F3 9 Tf
-9.00 TL
-0.000 0.000 0.502 rg
-1065.00 371.67 Td
-(C2) Tj
-ET
-1 J
-1 j
-1.00 w
-0.63 0.00 0.00 RG
-0.00 g
-[] 0 d
-1063.000 373.000 m 
-1047.000 373.000 l
-S
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-1055.000 365.000 m 
-1055.000 355.000 l
-S
-1 J
-1 j
-1.00 w
-0.63 0.00 0.00 RG
-0.00 g
-[] 0 d
-1055.000 385.000 m 
-1055.000 377.000 l
-S
-1 J
-1 j
-1.00 w
-0.63 0.00 0.00 RG
-0.00 g
-[] 0 d
-1047.000 377.000 m 
-1063.000 377.000 l
-S
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-1055.000 385.000 m 
-1055.000 395.000 l
-S
-1 J
-1 j
-1.00 w
-0.63 0.00 0.00 RG
-0.00 g
-[] 0 d
-1055.000 373.000 m 
-1055.000 365.000 l
-S
-BT
-/F2 12 Tf
-12.00 TL
-0.000 g
-1.00 -0.00 0.00 1.00 1092.00 329.00 Tm
-(GND) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-1105.000 345.000 m 
-1105.000 355.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-1096.000 345.000 m 
-1114.000 345.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-1099.000 343.000 m 
-1111.000 343.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-1102.000 341.000 m 
-1108.000 341.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-1104.000 339.000 m 
-1106.000 339.000 l
-S
-BT
-/F2 12 Tf
-12.00 TL
-0.000 g
-1.00 -0.00 0.00 1.00 1043.00 407.00 Tm
-(VCC) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-1055.000 405.000 m 
-1055.000 395.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-1050.000 405.000 m 
-1060.000 405.000 l
-S
-BT
-/F2 12 Tf
-12.00 TL
-0.000 g
-1.00 -0.00 0.00 1.00 1042.00 327.76 Tm
-(GND) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-1055.000 345.000 m 
-1055.000 355.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-1046.000 345.000 m 
-1064.000 345.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-1049.000 343.000 m 
-1061.000 343.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-1052.000 341.000 m 
-1058.000 341.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-1054.000 339.000 m 
-1056.000 339.000 l
-S
-BT
-/F2 12 Tf
-12.00 TL
-0.000 g
-1.00 -0.00 0.00 1.00 1095.00 407.00 Tm
-(+5V) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-1105.000 405.000 m 
-1105.000 395.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-1100.000 405.000 m 
-1110.000 405.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 102.98 611.40 Tm
-(SCL) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-145.000 615.000 m 
-140.000 610.000 l
-125.000 610.000 l
-125.000 620.000 l
-140.000 620.000 l
-145.000 615.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 101.11 601.40 Tm
-(SDA) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-145.000 605.000 m 
-140.000 600.000 l
-125.000 600.000 l
-125.000 610.000 l
-140.000 610.000 l
-145.000 605.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 0.53 0.00 RG
-0.00 g
-[] 0 d
-820.000 685.000 m 
-840.000 685.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 77.72 661.45 Tm
-(MCU_RXEN) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-160.000 665.000 m 
-155.000 660.000 l
-140.000 660.000 l
-140.000 670.000 l
-155.000 670.000 l
-160.000 665.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 0.53 0.00 RG
-0.00 g
-[] 0 d
-160.000 715.000 m 
-165.000 715.000 l
-S
-1 J
-1 j
-1.00 w
-0.20 0.80 0.20 RG
-[] 0 d
-621.000 289.000 m 
-629.000 281.000 l
-629.000 289.000 m 
-621.000 281.000 l
-S
-1 J
-1 j
-1.00 w
-0.20 0.80 0.20 RG
-[] 0 d
-251.000 439.000 m 
-259.000 431.000 l
-259.000 439.000 m 
-251.000 431.000 l
-S
-1 J
-1 j
-1.00 w
-0.20 0.80 0.20 RG
-[] 0 d
-251.000 449.000 m 
-259.000 441.000 l
-259.000 449.000 m 
-251.000 441.000 l
-S
-1 J
-1 j
-1.00 w
-0.20 0.80 0.20 RG
-[] 0 d
-341.000 449.000 m 
-349.000 441.000 l
-349.000 449.000 m 
-341.000 441.000 l
-S
-1 J
-1 j
-1.00 w
-0.20 0.80 0.20 RG
-[] 0 d
-251.000 409.000 m 
-259.000 401.000 l
-259.000 409.000 m 
-251.000 401.000 l
-S
-1 J
-1 j
-1.00 w
-0.20 0.80 0.20 RG
-[] 0 d
-171.000 479.000 m 
-179.000 471.000 l
-179.000 479.000 m 
-171.000 471.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 432.69 661.28 Tm
-(ADC) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-410.000 665.000 m 
-415.000 670.000 l
-430.000 670.000 l
-430.000 660.000 l
-415.000 660.000 l
-410.000 665.000 l
-S
-10.00 w
-BT
-/F3 9 Tf
-9.00 TL
-0.000 0.000 0.502 rg
-213.86 728.26 Td
-(PRO_MICRO_NRF52840_29P) Tj
-ET
-10.00 w
-BT
-/F3 9 Tf
-9.00 TL
-0.000 0.000 0.502 rg
-213.86 737.11 Td
-(PRO-MICRO) Tj
-ET
-2 J
-0 j
-100 M
-1.00 w
-0.55 0.14 0.14 RG
-[] 0 d
-180.00 725.00 80.00 -160.00 re
-S
-BT
-/F1 7 Tf
-7.00 TL
-0.553 0.137 0.137 rg
-234.94 702.00 Td
-(BATIN) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.553 0.137 0.137 rg
-266.00 706.00 Td
-(25) Tj
-ET
-1 J
-1 j
-1.00 w
-0.55 0.14 0.14 RG
-[] 0 d
-280.000 705.000 m 
-260.000 705.000 l
-S
-BT
-/F1 7 Tf
-7.00 TL
-0.553 0.137 0.137 rg
-240.95 692.00 Td
-(GND) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.553 0.137 0.137 rg
-266.00 696.00 Td
-(24) Tj
-ET
-1 J
-1 j
-1.00 w
-0.55 0.14 0.14 RG
-[] 0 d
-280.000 695.000 m 
-260.000 695.000 l
-S
-BT
-/F1 7 Tf
-7.00 TL
-0.553 0.137 0.137 rg
-243.04 682.00 Td
-(RST) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.553 0.137 0.137 rg
-266.00 686.00 Td
-(23) Tj
-ET
-1 J
-1 j
-1.00 w
-0.55 0.14 0.14 RG
-[] 0 d
-280.000 685.000 m 
-260.000 685.000 l
-S
-BT
-/F1 7 Tf
-7.00 TL
-0.553 0.137 0.137 rg
-226.28 672.00 Td
-(3.3v Out) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.553 0.137 0.137 rg
-266.00 676.00 Td
-(22) Tj
-ET
-1 J
-1 j
-1.00 w
-0.55 0.14 0.14 RG
-[] 0 d
-280.000 675.000 m 
-260.000 675.000 l
-S
-BT
-/F1 7 Tf
-7.00 TL
-0.553 0.137 0.137 rg
-236.90 632.00 Td
-(P1.15) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.553 0.137 0.137 rg
-266.00 636.00 Td
-(18) Tj
-ET
-1 J
-1 j
-1.00 w
-0.55 0.14 0.14 RG
-[] 0 d
-280.000 635.000 m 
-260.000 635.000 l
-S
-BT
-/F1 7 Tf
-7.00 TL
-0.553 0.137 0.137 rg
-236.90 662.00 Td
-(P0.31) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.553 0.137 0.137 rg
-266.00 666.00 Td
-(21) Tj
-ET
-1 J
-1 j
-1.00 w
-0.55 0.14 0.14 RG
-[] 0 d
-280.000 665.000 m 
-260.000 665.000 l
-S
-BT
-/F1 7 Tf
-7.00 TL
-0.553 0.137 0.137 rg
-183.00 692.00 Td
-(P0.08) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.553 0.137 0.137 rg
-168.28 696.00 Td
-(3) Tj
-ET
-1 J
-1 j
-1.00 w
-0.55 0.14 0.14 RG
-[] 0 d
-160.000 695.000 m 
-180.000 695.000 l
-S
-BT
-/F1 7 Tf
-7.00 TL
-0.553 0.137 0.137 rg
-236.90 622.00 Td
-(P1.13) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.553 0.137 0.137 rg
-266.00 626.00 Td
-(17) Tj
-ET
-1 J
-1 j
-1.00 w
-0.55 0.14 0.14 RG
-[] 0 d
-280.000 625.000 m 
-260.000 625.000 l
-S
-BT
-/F1 7 Tf
-7.00 TL
-0.553 0.137 0.137 rg
-236.90 652.00 Td
-(P0.29) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.553 0.137 0.137 rg
-266.00 656.00 Td
-(20) Tj
-ET
-1 J
-1 j
-1.00 w
-0.55 0.14 0.14 RG
-[] 0 d
-280.000 655.000 m 
-260.000 655.000 l
-S
-BT
-/F1 7 Tf
-7.00 TL
-0.553 0.137 0.137 rg
-236.90 612.00 Td
-(P1.11) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.553 0.137 0.137 rg
-266.00 616.00 Td
-(16) Tj
-ET
-1 J
-1 j
-1.00 w
-0.55 0.14 0.14 RG
-[] 0 d
-280.000 615.000 m 
-260.000 615.000 l
-S
-BT
-/F1 7 Tf
-7.00 TL
-0.553 0.137 0.137 rg
-236.90 642.00 Td
-(P0.02) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.553 0.137 0.137 rg
-266.00 646.00 Td
-(19) Tj
-ET
-1 J
-1 j
-1.00 w
-0.55 0.14 0.14 RG
-[] 0 d
-280.000 645.000 m 
-260.000 645.000 l
-S
-BT
-/F1 7 Tf
-7.00 TL
-0.553 0.137 0.137 rg
-183.00 682.00 Td
-(GND) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.553 0.137 0.137 rg
-168.28 686.00 Td
-(4) Tj
-ET
-1 J
-1 j
-1.00 w
-0.55 0.14 0.14 RG
-[] 0 d
-160.000 685.000 m 
-180.000 685.000 l
-S
-1.00 w
-0.55 0.14 0.14 RG
-180.00 705.00 m 180.00 706.66 178.66 708.00 177.00 708.00 c
-175.34 708.00 174.00 706.66 174.00 705.00 c
-174.00 703.34 175.34 702.00 177.00 702.00 c
-178.66 702.00 180.00 703.34 180.00 705.00 c
-S
-BT
-/F1 7 Tf
-7.00 TL
-0.553 0.137 0.137 rg
-183.00 702.00 Td
-(P0.06) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.553 0.137 0.137 rg
-168.28 706.00 Td
-(2) Tj
-ET
-1 J
-1 j
-1.00 w
-0.55 0.14 0.14 RG
-[] 0 d
-160.000 705.000 m 
-174.000 705.000 l
-S
-BT
-/F1 7 Tf
-7.00 TL
-0.553 0.137 0.137 rg
-236.90 592.00 Td
-(P0.09) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.553 0.137 0.137 rg
-266.00 596.00 Td
-(14) Tj
-ET
-1 J
-1 j
-1.00 w
-0.55 0.14 0.14 RG
-[] 0 d
-280.000 595.000 m 
-260.000 595.000 l
-S
-BT
-/F1 7 Tf
-7.00 TL
-0.553 0.137 0.137 rg
-236.90 602.00 Td
-(P0.10) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.553 0.137 0.137 rg
-266.00 606.00 Td
-(15) Tj
-ET
-1 J
-1 j
-1.00 w
-0.55 0.14 0.14 RG
-[] 0 d
-280.000 605.000 m 
-260.000 605.000 l
-S
-BT
-/F1 7 Tf
-7.00 TL
-0.553 0.137 0.137 rg
-183.00 602.00 Td
-(P1.04) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.553 0.137 0.137 rg
-162.57 606.00 Td
-(12) Tj
-ET
-1 J
-1 j
-1.00 w
-0.55 0.14 0.14 RG
-[] 0 d
-160.000 605.000 m 
-180.000 605.000 l
-S
-BT
-/F1 7 Tf
-7.00 TL
-0.553 0.137 0.137 rg
-183.00 672.00 Td
-(GND) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.553 0.137 0.137 rg
-168.28 676.00 Td
-(5) Tj
-ET
-1 J
-1 j
-1.00 w
-0.55 0.14 0.14 RG
-[] 0 d
-160.000 675.000 m 
-180.000 675.000 l
-S
-BT
-/F1 7 Tf
-7.00 TL
-0.553 0.137 0.137 rg
-183.00 662.00 Td
-(P0.17) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.553 0.137 0.137 rg
-168.28 666.00 Td
-(6) Tj
-ET
-1 J
-1 j
-1.00 w
-0.55 0.14 0.14 RG
-[] 0 d
-160.000 665.000 m 
-180.000 665.000 l
-S
-BT
-/F1 7 Tf
-7.00 TL
-0.553 0.137 0.137 rg
-183.00 652.00 Td
-(P0.20) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.553 0.137 0.137 rg
-168.28 656.00 Td
-(7) Tj
-ET
-1 J
-1 j
-1.00 w
-0.55 0.14 0.14 RG
-[] 0 d
-160.000 655.000 m 
-180.000 655.000 l
-S
-BT
-/F1 7 Tf
-7.00 TL
-0.553 0.137 0.137 rg
-183.00 642.00 Td
-(P0.22) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.553 0.137 0.137 rg
-168.28 646.00 Td
-(8) Tj
-ET
-1 J
-1 j
-1.00 w
-0.55 0.14 0.14 RG
-[] 0 d
-160.000 645.000 m 
-180.000 645.000 l
-S
-BT
-/F1 7 Tf
-7.00 TL
-0.553 0.137 0.137 rg
-183.00 632.00 Td
-(P0.24) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.553 0.137 0.137 rg
-168.28 636.00 Td
-(9) Tj
-ET
-1 J
-1 j
-1.00 w
-0.55 0.14 0.14 RG
-[] 0 d
-160.000 635.000 m 
-180.000 635.000 l
-S
-BT
-/F1 7 Tf
-7.00 TL
-0.553 0.137 0.137 rg
-183.00 622.00 Td
-(P1.00) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.553 0.137 0.137 rg
-162.57 626.00 Td
-(10) Tj
-ET
-1 J
-1 j
-1.00 w
-0.55 0.14 0.14 RG
-[] 0 d
-160.000 625.000 m 
-180.000 625.000 l
-S
-BT
-/F1 7 Tf
-7.00 TL
-0.553 0.137 0.137 rg
-183.00 612.00 Td
-(P0.11) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.553 0.137 0.137 rg
-162.57 616.00 Td
-(11) Tj
-ET
-1 J
-1 j
-1.00 w
-0.55 0.14 0.14 RG
-[] 0 d
-160.000 615.000 m 
-180.000 615.000 l
-S
-BT
-/F1 7 Tf
-7.00 TL
-0.553 0.137 0.137 rg
-183.00 592.00 Td
-(P1.06) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.553 0.137 0.137 rg
-162.57 596.00 Td
-(13) Tj
-ET
-1 J
-1 j
-1.00 w
-0.55 0.14 0.14 RG
-[] 0 d
-160.000 595.000 m 
-180.000 595.000 l
-S
-BT
-/F1 7 Tf
-7.00 TL
-0.553 0.137 0.137 rg
-0.00 1.00 -1.00 0.00 193.00 568.00 Tm
-(P1.01) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.553 0.137 0.137 rg
-0.00 1.00 -1.00 0.00 189.00 547.57 Tm
-(27) Tj
-ET
-1 J
-1 j
-1.00 w
-0.55 0.14 0.14 RG
-[] 0 d
-190.000 545.000 m 
-190.000 565.000 l
-S
-BT
-/F1 7 Tf
-7.00 TL
-0.553 0.137 0.137 rg
-0.00 1.00 -1.00 0.00 203.00 568.00 Tm
-(P1.02) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.553 0.137 0.137 rg
-0.00 1.00 -1.00 0.00 199.00 547.57 Tm
-(28) Tj
-ET
-1 J
-1 j
-1.00 w
-0.55 0.14 0.14 RG
-[] 0 d
-200.000 545.000 m 
-200.000 565.000 l
-S
-BT
-/F1 7 Tf
-7.00 TL
-0.553 0.137 0.137 rg
-0.00 1.00 -1.00 0.00 213.00 568.00 Tm
-(P1.07) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.553 0.137 0.137 rg
-0.00 1.00 -1.00 0.00 209.00 547.57 Tm
-(29) Tj
-ET
-1 J
-1 j
-1.00 w
-0.55 0.14 0.14 RG
-[] 0 d
-210.000 545.000 m 
-210.000 565.000 l
-S
-BT
-/F1 7 Tf
-7.00 TL
-0.553 0.137 0.137 rg
-234.94 712.00 Td
-(BATIN) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.553 0.137 0.137 rg
-266.00 716.00 Td
-(26) Tj
-ET
-1 J
-1 j
-1.00 w
-0.55 0.14 0.14 RG
-[] 0 d
-280.000 715.000 m 
-260.000 715.000 l
-S
-BT
-/F1 7 Tf
-7.00 TL
-0.553 0.137 0.137 rg
-183.00 712.00 Td
-(GND) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.553 0.137 0.137 rg
-168.28 716.00 Td
-(1) Tj
-ET
-1 J
-1 j
-1.00 w
-0.55 0.14 0.14 RG
-[] 0 d
-160.000 715.000 m 
-180.000 715.000 l
-S
-10.00 w
-BT
-/F3 9 Tf
-9.00 TL
-0.000 0.000 0.502 rg
-864.25 253.00 Td
-(RA-02_C9900010926) Tj
-ET
-10.00 w
-BT
-/F3 9 Tf
-9.00 TL
-0.000 0.000 0.502 rg
-864.25 262.00 Td
-(RA-02) Tj
-ET
-2 J
-0 j
-100 M
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-837.000 250.000 m 
-903.000 250.000 l
-904.105 250.000 905.000 249.105 905.000 248.000 c
-905.000 162.000 l
-905.000 160.895 903.895 160.000 903.000 160.000 c
-837.000 160.000 l
-835.895 160.000 835.000 161.105 835.000 162.000 c
-835.000 248.000 l
-835.000 249.105 836.105 250.000 837.000 250.000 c
-S
-1.00 w
-0.53 0.00 0.00 RG
-0.53 0.00 0.00 rg
-[] 0 d
-841.50 245.00 m 841.50 245.83 840.83 246.50 840.00 246.50 c
-839.17 246.50 838.50 245.83 838.50 245.00 c
-838.50 244.17 839.17 243.50 840.00 243.50 c
-840.83 243.50 841.50 244.17 841.50 245.00 c
-B
-BT
-/F1 9 Tf
-9.00 TL
-0.000 g
-838.70 236.00 Td
-(GND) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 g
-828.78 241.00 Td
-(1) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-[] 0 d
-825.000 240.000 m 
-835.000 240.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 g
-838.70 226.00 Td
-(GND) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 g
-828.78 231.00 Td
-(2) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-[] 0 d
-825.000 230.000 m 
-835.000 230.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-838.70 216.00 Td
-(3.3V) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-828.78 221.00 Td
-(3) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-825.000 220.000 m 
-835.000 220.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-838.70 206.00 Td
-(RESET) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-828.78 211.00 Td
-(4) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-825.000 210.000 m 
-835.000 210.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-838.70 196.00 Td
-(DIO0) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-828.78 201.00 Td
-(5) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-825.000 200.000 m 
-835.000 200.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-838.70 186.00 Td
-(DIO1) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-828.78 191.00 Td
-(6) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-825.000 190.000 m 
-835.000 190.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-838.70 176.00 Td
-(DIO2) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-828.78 181.00 Td
-(7) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-825.000 180.000 m 
-835.000 180.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-838.70 166.00 Td
-(DIO3) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-828.78 171.00 Td
-(8) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-825.000 170.000 m 
-835.000 170.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 g
-880.66 166.00 Td
-(GND) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 g
-905.50 171.00 Td
-(9) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-[] 0 d
-915.000 170.000 m 
-905.000 170.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-877.79 176.00 Td
-(DIO4) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-905.50 181.00 Td
-(10) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-915.000 180.000 m 
-905.000 180.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-877.79 186.00 Td
-(DIO5) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-905.50 191.00 Td
-(11) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-915.000 190.000 m 
-905.000 190.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-882.64 196.00 Td
-(SCK) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-905.50 201.00 Td
-(12) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-915.000 200.000 m 
-905.000 200.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-876.71 206.00 Td
-(MISO) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-905.50 211.00 Td
-(13) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-915.000 210.000 m 
-905.000 210.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-876.71 216.00 Td
-(MOSI) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-905.50 221.00 Td
-(14) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-915.000 220.000 m 
-905.000 220.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-882.27 226.00 Td
-(NSS) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 0.000 1.000 rg
-905.50 231.00 Td
-(15) Tj
-ET
-1 J
-1 j
-1.00 w
-0.53 0.00 0.00 RG
-[] 0 d
-915.000 230.000 m 
-905.000 230.000 l
-S
-BT
-/F1 9 Tf
-9.00 TL
-0.000 g
-880.66 236.00 Td
-(GND) Tj
-ET
-BT
-/F1 9 Tf
-9.00 TL
-0.000 g
-905.50 241.00 Td
-(16) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-[] 0 d
-915.000 240.000 m 
-905.000 240.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 773.85 196.40 Tm
-(BUSY) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-825.000 200.000 m 
-820.000 195.000 l
-805.000 195.000 l
-805.000 205.000 l
-820.000 205.000 l
-825.000 200.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 937.00 226.60 Tm
-(CS) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-915.000 230.000 m 
-920.000 235.000 l
-935.000 235.000 l
-935.000 225.000 l
-920.000 225.000 l
-915.000 230.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 936.71 196.60 Tm
-(SCK) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-915.000 200.000 m 
-920.000 205.000 l
-935.000 205.000 l
-935.000 195.000 l
-920.000 195.000 l
-915.000 200.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 937.00 216.60 Tm
-(MOSI) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-915.000 220.000 m 
-920.000 225.000 l
-935.000 225.000 l
-935.000 215.000 l
-920.000 215.000 l
-915.000 220.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 937.00 206.60 Tm
-(MISO) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-915.000 210.000 m 
-920.000 215.000 l
-935.000 215.000 l
-935.000 205.000 l
-920.000 205.000 l
-915.000 210.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 775.06 206.57 Tm
-(NRST) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-825.000 210.000 m 
-820.000 205.000 l
-805.000 205.000 l
-805.000 215.000 l
-820.000 215.000 l
-825.000 210.000 l
-S
-BT
-/F2 11 Tf
-11.00 TL
-0.000 0.000 1.000 rg
-1.00 -0.00 0.00 1.00 784.19 186.40 Tm
-(IRQ) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 0.00 1.00 RG
-0.00 g
-[] 0 d
-825.000 190.000 m 
-820.000 185.000 l
-805.000 185.000 l
-805.000 195.000 l
-820.000 195.000 l
-825.000 190.000 l
-S
-BT
-/F2 12 Tf
-12.00 TL
-0.000 g
-1.00 -0.00 0.00 1.00 931.50 166.00 Tm
-(GND) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-925.000 170.000 m 
-915.000 170.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-925.000 161.000 m 
-925.000 179.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-927.000 164.000 m 
-927.000 176.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-929.000 167.000 m 
-929.000 173.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-931.000 169.000 m 
-931.000 171.000 l
-S
-BT
-/F2 12 Tf
-12.00 TL
-0.000 g
-1.00 -0.00 0.00 1.00 931.50 236.00 Tm
-(GND) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-925.000 240.000 m 
-915.000 240.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-925.000 231.000 m 
-925.000 249.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-927.000 234.000 m 
-927.000 246.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-929.000 237.000 m 
-929.000 243.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-931.000 239.000 m 
-931.000 241.000 l
-S
-BT
-/F2 12 Tf
-12.00 TL
-0.000 g
-1.00 -0.00 0.00 1.00 782.50 236.00 Tm
-(GND) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-815.000 240.000 m 
-825.000 240.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-815.000 249.000 m 
-815.000 231.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-813.000 246.000 m 
-813.000 234.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-811.000 243.000 m 
-811.000 237.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-809.000 241.000 m 
-809.000 239.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 0.53 0.00 RG
-0.00 g
-[] 0 d
-825.000 240.000 m 
-825.000 230.000 l
-S
-BT
-/F2 12 Tf
-12.00 TL
-0.000 g
-1.00 -0.00 0.00 1.00 789.82 216.00 Tm
-(VCC) Tj
-ET
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-815.000 220.000 m 
-825.000 220.000 l
-S
-1 J
-1 j
-1.00 w
-0.00 G
-0.00 g
-[] 0 d
-815.000 215.000 m 
-815.000 225.000 l
-S
-1 J
-1 j
-1.00 w
-0.20 0.80 0.20 RG
-[] 0 d
-911.000 194.000 m 
-919.000 186.000 l
-919.000 194.000 m 
-911.000 186.000 l
-S
-1 J
-1 j
-1.00 w
-0.20 0.80 0.20 RG
-[] 0 d
-911.000 184.000 m 
-919.000 176.000 l
-919.000 184.000 m 
-911.000 176.000 l
-S
-1 J
-1 j
-1.00 w
-0.20 0.80 0.20 RG
-[] 0 d
-821.000 184.000 m 
-829.000 176.000 l
-829.000 184.000 m 
-821.000 176.000 l
-S
-1 J
-1 j
-1.00 w
-0.20 0.80 0.20 RG
-[] 0 d
-821.000 174.000 m 
-829.000 166.000 l
-829.000 174.000 m 
-821.000 166.000 l
-S
-0.80 0.00 0.00 rg
-652.50 420.00 m 652.50 421.38 651.38 422.50 650.00 422.50 c
-648.62 422.50 647.50 421.38 647.50 420.00 c
-647.50 418.62 648.62 417.50 650.00 417.50 c
-651.38 417.50 652.50 418.62 652.50 420.00 c
-f
-0.80 0.00 0.00 rg
-942.50 415.00 m 942.50 416.38 941.38 417.50 940.00 417.50 c
-938.62 417.50 937.50 416.38 937.50 415.00 c
-937.50 413.62 938.62 412.50 940.00 412.50 c
-941.38 412.50 942.50 413.62 942.50 415.00 c
-f
-0.80 0.00 0.00 rg
-942.50 395.00 m 942.50 396.38 941.38 397.50 940.00 397.50 c
-938.62 397.50 937.50 396.38 937.50 395.00 c
-937.50 393.62 938.62 392.50 940.00 392.50 c
-941.38 392.50 942.50 393.62 942.50 395.00 c
-f
-0.80 0.00 0.00 rg
-942.50 385.00 m 942.50 386.38 941.38 387.50 940.00 387.50 c
-938.62 387.50 937.50 386.38 937.50 385.00 c
-937.50 383.62 938.62 382.50 940.00 382.50 c
-941.38 382.50 942.50 383.62 942.50 385.00 c
-f
-0.80 0.00 0.00 rg
-942.50 375.00 m 942.50 376.38 941.38 377.50 940.00 377.50 c
-938.62 377.50 937.50 376.38 937.50 375.00 c
-937.50 373.62 938.62 372.50 940.00 372.50 c
-941.38 372.50 942.50 373.62 942.50 375.00 c
-f
-0.80 0.00 0.00 rg
-652.50 490.00 m 652.50 491.38 651.38 492.50 650.00 492.50 c
-648.62 492.50 647.50 491.38 647.50 490.00 c
-647.50 488.62 648.62 487.50 650.00 487.50 c
-651.38 487.50 652.50 488.62 652.50 490.00 c
-f
-0.80 0.00 0.00 rg
-942.50 465.00 m 942.50 466.38 941.38 467.50 940.00 467.50 c
-938.62 467.50 937.50 466.38 937.50 465.00 c
-937.50 463.62 938.62 462.50 940.00 462.50 c
-941.38 462.50 942.50 463.62 942.50 465.00 c
-f
-0.80 0.00 0.00 rg
-412.50 665.00 m 412.50 666.38 411.38 667.50 410.00 667.50 c
-408.62 667.50 407.50 666.38 407.50 665.00 c
-407.50 663.62 408.62 662.50 410.00 662.50 c
-411.38 662.50 412.50 663.62 412.50 665.00 c
-f
-0.80 0.00 0.00 rg
-162.50 680.00 m 162.50 681.38 161.38 682.50 160.00 682.50 c
-158.62 682.50 157.50 681.38 157.50 680.00 c
-157.50 678.62 158.62 677.50 160.00 677.50 c
-161.38 677.50 162.50 678.62 162.50 680.00 c
-f
-0.80 0.00 0.00 rg
-627.50 185.00 m 627.50 186.38 626.38 187.50 625.00 187.50 c
-623.62 187.50 622.50 186.38 622.50 185.00 c
-622.50 183.62 623.62 182.50 625.00 182.50 c
-626.38 182.50 627.50 183.62 627.50 185.00 c
-f
-0.80 0.00 0.00 rg
-627.50 195.00 m 627.50 196.38 626.38 197.50 625.00 197.50 c
-623.62 197.50 622.50 196.38 622.50 195.00 c
-622.50 193.62 623.62 192.50 625.00 192.50 c
-626.38 192.50 627.50 193.62 627.50 195.00 c
-f
-0.80 0.00 0.00 rg
-627.50 205.00 m 627.50 206.38 626.38 207.50 625.00 207.50 c
-623.62 207.50 622.50 206.38 622.50 205.00 c
-622.50 203.62 623.62 202.50 625.00 202.50 c
-626.38 202.50 627.50 203.62 627.50 205.00 c
-f
-0.80 0.00 0.00 rg
-827.50 240.00 m 827.50 241.38 826.38 242.50 825.00 242.50 c
-823.62 242.50 822.50 241.38 822.50 240.00 c
-822.50 238.62 823.62 237.50 825.00 237.50 c
-826.38 237.50 827.50 238.62 827.50 240.00 c
-f
-q
-102.00 0 0 20.00 706.00 30.50 cm
-/I0 Do
-Q
-endstream
-endobj
-1 0 obj
-<>
-endobj
-5 0 obj
-<<
-/Descent -209
-/CapHeight 727
-/StemV 0
-/Type /FontDescriptor
-/Flags 32
-/FontBBox [-559 -303 1446 1050]
-/FontName /Verdana
-/ItalicAngle 0
-/Ascent 1005
->>
-endobj
-6 0 obj
-<>
-endobj
-7 0 obj
-<<
-/Type /Font
-/BaseFont /Times-Roman
-/Subtype /Type1
-/Encoding /WinAnsiEncoding
-/FirstChar 32
-/LastChar 255
->>
-endobj
-8 0 obj
-<<
-/Descent -325
-/CapHeight 500
-/StemV 80
-/Type /FontDescriptor
-/Flags 32
-/FontBBox [-665 -325 2000 1006]
-/FontName /Arial
-/ItalicAngle 0
-/Ascent 1006
->>
-endobj
-9 0 obj
-<>
-endobj
-10 0 obj
-<<
-/Type /XObject
-/Subtype /Image
-/Width 520
-/Height 105
-/ColorSpace /DeviceRGB
-/BitsPerComponent 8
-/DecodeParms <>
-/SMask 11 0 R
-/Length 6251
-/Filter /FlateDecode
->>
-stream
-xKqǣ{$V>:dm]uXbřZ>ـlhy-a$`!qMrN|G^_LUz8gfdf0~	SEQe"EQaAQEۢ&\gn_@QJk#o(sdAO%1GŘf!Eykn!׍&쯯),rlUӳR(p\DUnGo),m(.@Qҳ\9TEYdd#ܼsu.)rUXWqvkG<7yW(Jk)˶yns
?'ȶ섡RtOQ:oNsk(kȯ`U7(ƬjY8n+jRݠ(ʚK@+U೸֙ٚ4Bd^T7(
-W㊞g)}]
4TIQc2+D+H	]noh6<+S`?w~N*c{!q~j`z&VPRe 4hy4AQ5!vQo_}?cEQր*Q	>yû(>?-hQV₞ɡ7ߦb~>HRǸ!<^}v~[TTaYUmAX$;9lφ"w9$3;yFQ<b~x-Q.}|	ӱu
-=9ԭ,jic>9R#bɪ|X_4eb'6~/vK!I"Nbejӎa?f򃔼b꧃1N3ȺwErU4;q5A6<0tϤ"/R>X$%AZ1c{od6^Sr)k-䔎zD/⇺l,bzs&gGL1HXɲUS]ҋRBrēwLwX|楔]2CI2w,!S*Nf&Ybd]]9U`~qQ]e3}GؑJRYQ඾r֌.͌uNO79ĝ(0'OvJg{d	٥wq9
d+,өQBV=>qU6FB%1ڷD؈_gVPW)By24qaJԌ$pk8q]t	q.!y-8́},wȷP%?C>4pTa߇8'ZNm19 m>QeMi$-:1̱mt^"\˩
84R!t	"!W'
}g>fd5hDuO3|m0I?x]r
fR4H48BKrYA1cL:{%o;gZpD1[{P|=`2(K^6gr_9Nو? 9,zYD4<*W)*/ws8y+<.E	嚞˹/u%NQA>9ϴch@93hvs~~I7k[|9)s&*þ[<#e
-ٜӓ(m!èkzP\`\̞hOa^Q]Bvl:P]}~Ո~zyfduۅjz'>)o\n`i%4Nk4GRI]/[|M=.-uV'W3A̯
_:<`I-jFLP]KcH&Ϙ~
W178YnW=!l 3n4)ZPeS	V5JR1=9ʾ0ɰAʛt1_d%ghl3%c
-nŔZ!g?+sDNoz8'"L5(BY;q5~'ųZ"慊o?JaЛc\%\MsɔiLq]PE['Uʎ_z7(C3rt$@EBe~9IoN@2?ԫ܍Q972ý.3@v^_}xZɶn\rR.ɳzz_MDI"t:V^Õ
-2/;؁y9
-©h	
-5vWP޻H#n
6*8W$1{ eB*rb4$!&?˵bJŰrtǀYGȥI	+~ǰװO
-NRT62Oy\Yw񑭖C{kKl/,4_}5iCpQ~"(Py.x1m0Mrڧ/ug]|&S_.=ę!V!t=)l7	t:ݤ'REgH&w9Zr]3ܺrc;hH9yK *g\mcbG`M@Qj<|)~Et
-8;N/$%4qI_KSp{pkx;Vi7u_N$C`F67ˣM-|2(/5)M*>]	&%[aǫG$	+B=etHLEy0{oD̔KMpT7FDA7?7nL
aRqQ[^1@(R@lk7	\pRa
[yC;eDx{RSZ\;GɞJh^ipNbH݈x;^XRƼ\gbMBTK-Pp;mpP.v:j;>*o
-ĽIG1|%z((y}D
-ć%O1$(wO _=Â7)^'*Bݲ#ULc6p7nPȫN+\^y;PjG82ܚG?!Vne2doŭϼ
-q1JO텘"ӻA n_5mE~qTiwu	8+i*x؆	Vu+^f|`(pp8ԔK}
+Zq9=	%&6紦uKe	}FX\q5&HOhӴTGw;G*@ӡ`n]0ss}ZWhNѰt\\ JYEpN1'{im[/`5a!np)'6}ڟBUǭ~嗩op:Vf/fVF[
!t'<}
S1ţc8}.4baΉ"+)!u(ofO|
qB	K?wCi!bѕ`+bjb1+/miɭg.X:rgUKK,3rڐf">[)˚+Djbna5[oL"pe|r(L^e̟gQYm5P]tݬĝ鐭xj1ՅDe\X[DV7TEF6/C@hbo1A`PoT:-݈}Vq!,J_j%D1#7;D$I(St(0'KK\SW}/;*b>)9]Jw+[CDi,ТˣRIᅖZe@l=Qbk tjAT`&wm3@] r28\ݸREMP+*zE❩{~|ԃMlwǶpZfo@3/M`cw$VLQ.%D6v]/p*4B)Ddb5~|
-J
-$ ۝zW3ovѨ}.R{L%o#4>݂q)C?.$a,nl&vJ,c>l*U4v)ɢ+iͿUM`娘lSƩP0؄Ha>"IUh+VQ7aJQr.6ҷǀf3@ŕ+z$e&SR#}[M"ڊPңey׀UA_bP1%2rJTd)J2&2MhIu*k
-F\UQkM}v(FjbS(I"kc
QO%)Ǧ(4L0gUwX^4'R_hXgmׯR9YiF|yLʕ.-%]g^\ϜTRSL1hAI6E	JZt)(PŠ(\@(rF-
-endstream
-endobj
-11 0 obj
-<<
-/Type /XObject
-/Subtype /Image
-/Width 520
-/Height 105
-/ColorSpace /DeviceGray
-/BitsPerComponent 8
-/DecodeParms <>
-/Length 6577
-/Filter /FlateDecode
->>
-stream
-x]M?c=ȳJQj"*"xIJhGQ"o袌4(`B0_}{}3W3[g{{{k p*Bn"5-߯Ds0uN\'!WfBKl5Gt\1g}vD]EԄ\᾽3珧/Sډ&B!ދIɻ:fVW&>ľ~Jz\z
"`O>o/t[mGT]/u }EM4(uBW+I~
@SvXMq:~=|EP(BxF=hƕnWV/Ϗjpw,z~PvЬ]9ޕ,gv{NM[u)+++?DPɩ~^vJu?	)2tGqL
-Ï*ʋIȌ'#GR$$뵏[<ҡ䔊]]L'{KG9x)Lԉ
-0G6.ݡ92P-b|N2NU#4}K&jG
DԷc#By&ZdQ%ND#BݵԚv>)s\RPB>Wiк3k.
-p5|aΈ	ԈQQ5xO!XdAB"9g~2P<Z2s1v/tFtd/4Yx7wa鮋(:Bc7
E`~wa'S>{~
-0ㄠ)9ZmΔ>EE-#
-_xEbeAvc]DeCӏOs@j=oj+58`~W/x}љ^VzMQv7GU|CEbTK0CEңU%ێ^NO{K-q"+W`No`VW~md1JwKf.*(c*s=
-@qǭ/G|#A8tf:Vo	2g| <ʬ3i|r>V[%{gX7닅;%6&Z8(˰ؚ4+{>3'HgvM=MBt:LnukTl4ӉHDuNşvGXX~!,!5:M_cssQI>k#+0~ǎj'1\%P8ՠ*
-C"hoW!;yTv9.:64lLd6cm)gӕzʅwLd`:kj/f5O'3w2^$b^~ejS[z]*_~c#eI2[BӧC.⴨Q({z+WnݺB<=p2nCQ{w)[ב{\zZl[h.jyMt9]'pn7Fخ8Qp%| kN»3g2{ۜ#Հػ/r	@`Six΀?_wijpo"9{sS]r61B)z<;h!/
eT	nGAQis79Wbp k=i~ݵ)KX[n
-K3w̧$Apbz#D*J)2$)Wo&9VWeޱL\4@)69ֆՠH23`O@?zm~ GBQI
?=))Q,p1'nўXfb,̎ZY06GYw1=n32"0pyq0WH|,|WXm`R)^o;{ƌ&?Se
*:D?d6G+RQsi9+v
-EqwKmfP7JgNJ<ۀ3n-*:侟HvlwRs^0GjFy-NHu{T8xЪ̽
lmd`|8X~g.ty	H/m'4/8Nvor(dO9‹@1	bՄoA*e1Z쎕&L`- lR,l1xwݞ9t>D} p1~&)%,,0^j

4>N}춖1w6miW0crAI~aߊ?*{JIڢ	&ѿ+qTnLdz.~vKDFyо\y%[6mjrg">tCrRY)S@ڂ`ZرB8]`.f1J|!\2Yc:H6c/Qۜvo^aL%	Y"#qwǛqW_i,2Kz=◧IdV^\Y.G+ǻ6Dў4S)UrCD
-7#lg]|H'bRNUt~i'
n+
f&ݍPr'Rd:4".;mi氧/=j훌wJc	neWCn/PA5^b6I#6O%2A|ǧ㽎ԛ^MN@e.z	1ۓ	ý6(-+j	6g);8)tKTn?pgN{砱
-L!KI 4aҎ:
-r \4l'qK)o5UY#/h16ƁB2dV
yK
-6F8"ƍSSK_dRh4*_?LѫVS[+?޺IzE=B"ҔH׸3n+4d(b"`W~D}Gerp{)/B"hMh@#j~4-f`IkLtؐΒԣI4&ݿ֬_7KR$hyhJw3Tu0W|P&L/Y50_d&ןZB.B4eӋPB?%
-xYŰt#m#C`Xhѩp'zOCd~7p?DQaBC%'Qn'w 9{JCitWq,YO֦0:෤'҉FF߹:iy* ~%k/aXxQT4	EZ%{sIb&EjMQw4붘.qT7P@j@LJ7AhY<3P`P(&ia8=n5'>t^B͗Ym\_p2]KN/*5^1@&OVJ`
-hN%:OwoF|r`oX"-[ATw	m0y(
ǂہ[-68*6_558$Cne;h҉1c;[jȱ&gיDvoc߮I|RZ>BO>rBWȉT!9Oe	0+:BcbBP9i{6mB'e>Ty#xOZH{tT<`	3ރә@2l-&@WA>KZumx(11f8RuJWD+(f_ld9qmx1A65nd3RL~
-\rt4z*yKo܀LlYwO/;LoXeBG4WOpWCx) aR$OB?* ʹdՌ]lI yсK|3ܺ60s]n@{)R]#*1ѢX5K,-j."n^`W.8ufR;=wT"a´㓽l(.~iX\kf=Uaۏ*{IcEa1ʹTsxXQ,~Qė@梽R,@UR-J))J$}aͣ/)UXQ3[e¶5KFNe`fEїi	Ċ|r|+de&4\!qaj)2Z[E]oz#HFR?2.beгPd
-1^a4S)]Hph^@7
+WmNIY]|9ŋK]@toYɱJmͯ7^Al2+XWZ0^߱]^025_f4#/UGCJG[<~7ݲԎۭ5bչk}y1Ԭ0r2euZQM/6Z,`|S2ʺMJ(83w	[n
-}~s<_t)OΎT+(Zd%I5|`T뉃
-*͑tb"F42/*>>/ԣ@ ;$X2wׂiM=agCʽFWXl˨+RT^Or

Cwp]"H:u"##ҘRN/_z#)Kj=r+Vy<+Xn\G!!B_8V'w#Ev·s`EPR7«À!!ZAuTLmV#"oƐlogc"c';oHok/WE0]IXF]!wE0Xy-lx3ōaQ
-
tjMnUBQf<_E	X4f.]A0FIϋ*> ;\DPp#AT ?7
-endstream
-endobj
-2 0 obj
-<<
-/ProcSet [/PDF /Text /ImageB /ImageC /ImageI]
-/Font <<
-/F1 6 0 R
-/F2 7 0 R
-/F3 9 0 R
->>
-/XObject <<
-/I0 10 0 R
->>
->>
-endobj
-12 0 obj
-<<
-/Producer (jsPDF 0.0.0)
-/CreationDate (D:20241217154849-00'00')
->>
-endobj
-13 0 obj
-<<
-/Type /Catalog
-/Pages 1 0 R
-/OpenAction [3 0 R /FitH null]
-/PageLayout /OneColumn
->>
-endobj
-xref
-0 14
-0000000000 65535 f 
-0000102899 00000 n 
-0000118853 00000 n 
-0000000015 00000 n 
-0000000125 00000 n 
-0000102956 00000 n 
-0000103126 00000 n 
-0000104180 00000 n 
-0000104307 00000 n 
-0000104476 00000 n 
-0000105520 00000 n 
-0000112030 00000 n 
-0000118988 00000 n 
-0000119074 00000 n 
-trailer
-<<
-/Size 14
-/Root 13 0 R
-/Info 12 0 R
-/ID [ <906A4C76C35816C42EB6FFD13B3B7D92> <906A4C76C35816C42EB6FFD13B3B7D92> ]
->>
-startxref
-119178
-%%EOF
\ No newline at end of file
diff --git a/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/Schematic_Pro-Micro_Pinouts.pdf b/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/Schematic_Pro-Micro_Pinouts.pdf
new file mode 100644
index 000000000..63a80dbbe
--- /dev/null
+++ b/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/Schematic_Pro-Micro_Pinouts.pdf
@@ -0,0 +1,33346 @@
+%PDF-1.4
+%߬
+3 0 obj
+<>
+endobj
+4 0 obj
+<<
+/Length 334350
+>>
+stream
+0.14 w
+0 G
+q
+2 J
+0 j
+72 M
+1.00 g
+[] 0 d
+0.00 1197.36 848.16 -1197.36 re
+f
+2 J
+0 j
+72 M
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+2.88 1194.48 842.40 -1191.60 re
+S
+2 J
+0 j
+72 M
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+10.08 1187.28 828.00 -1177.20 re
+S
+7.20 w
+BT
+/F1 7.199999999999999 Tf
+7.20 TL
+0.627 0.000 0.000 rg
+71.28 3.60 Td
+<0031> Tj
+ET
+7.20 w
+BT
+/F1 7.199999999999999 Tf
+7.20 TL
+0.627 0.000 0.000 rg
+71.28 1188.00 Td
+<0031> Tj
+ET
+7.20 w
+BT
+/F1 7.199999999999999 Tf
+7.20 TL
+0.627 0.000 0.000 rg
+211.68 3.60 Td
+<0032> Tj
+ET
+7.20 w
+BT
+/F1 7.199999999999999 Tf
+7.20 TL
+0.627 0.000 0.000 rg
+211.68 1188.00 Td
+<0032> Tj
+ET
+7.20 w
+BT
+/F1 7.199999999999999 Tf
+7.20 TL
+0.627 0.000 0.000 rg
+352.08 3.60 Td
+<0033> Tj
+ET
+7.20 w
+BT
+/F1 7.199999999999999 Tf
+7.20 TL
+0.627 0.000 0.000 rg
+352.08 1188.00 Td
+<0033> Tj
+ET
+7.20 w
+BT
+/F1 7.199999999999999 Tf
+7.20 TL
+0.627 0.000 0.000 rg
+492.48 3.60 Td
+<0034> Tj
+ET
+7.20 w
+BT
+/F1 7.199999999999999 Tf
+7.20 TL
+0.627 0.000 0.000 rg
+492.48 1188.00 Td
+<0034> Tj
+ET
+7.20 w
+BT
+/F1 7.199999999999999 Tf
+7.20 TL
+0.627 0.000 0.000 rg
+632.88 3.60 Td
+<0035> Tj
+ET
+7.20 w
+BT
+/F1 7.199999999999999 Tf
+7.20 TL
+0.627 0.000 0.000 rg
+632.88 1188.00 Td
+<0035> Tj
+ET
+7.20 w
+BT
+/F1 7.199999999999999 Tf
+7.20 TL
+0.627 0.000 0.000 rg
+773.28 3.60 Td
+<0036> Tj
+ET
+7.20 w
+BT
+/F1 7.199999999999999 Tf
+7.20 TL
+0.627 0.000 0.000 rg
+773.28 1188.00 Td
+<0036> Tj
+ET
+7.20 w
+BT
+/F1 7.199999999999999 Tf
+7.20 TL
+0.627 0.000 0.000 rg
+4.68 1042.65 Td
+<0041> Tj
+ET
+7.20 w
+BT
+/F1 7.199999999999999 Tf
+7.20 TL
+0.627 0.000 0.000 rg
+839.88 1042.65 Td
+<0041> Tj
+ET
+7.20 w
+BT
+/F1 7.199999999999999 Tf
+7.20 TL
+0.627 0.000 0.000 rg
+4.68 744.75 Td
+<0042> Tj
+ET
+7.20 w
+BT
+/F1 7.199999999999999 Tf
+7.20 TL
+0.627 0.000 0.000 rg
+839.88 744.75 Td
+<0042> Tj
+ET
+7.20 w
+BT
+/F1 7.199999999999999 Tf
+7.20 TL
+0.627 0.000 0.000 rg
+4.68 446.85 Td
+<0043> Tj
+ET
+7.20 w
+BT
+/F1 7.199999999999999 Tf
+7.20 TL
+0.627 0.000 0.000 rg
+839.88 446.85 Td
+<0043> Tj
+ET
+7.20 w
+BT
+/F1 7.199999999999999 Tf
+7.20 TL
+0.627 0.000 0.000 rg
+4.68 148.95 Td
+<0044> Tj
+ET
+7.20 w
+BT
+/F1 7.199999999999999 Tf
+7.20 TL
+0.627 0.000 0.000 rg
+839.88 148.95 Td
+<0044> Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+143.280 2.880 m 
+143.280 10.080 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+143.280 1194.480 m 
+143.280 1187.280 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+283.680 2.880 m 
+283.680 10.080 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+283.680 1194.480 m 
+283.680 1187.280 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+424.080 2.880 m 
+424.080 10.080 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+424.080 1194.480 m 
+424.080 1187.280 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+564.480 2.880 m 
+564.480 10.080 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+564.480 1194.480 m 
+564.480 1187.280 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+704.880 2.880 m 
+704.880 10.080 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+704.880 1194.480 m 
+704.880 1187.280 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+2.880 896.580 m 
+10.080 896.580 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+845.280 896.580 m 
+838.080 896.580 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+2.880 598.680 m 
+10.080 598.680 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+845.280 598.680 m 
+838.080 598.680 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+2.880 300.780 m 
+10.080 300.780 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+845.280 300.780 m 
+838.080 300.780 l
+S
+7.20 w
+BT
+/F2 13.090909090909088 Tf
+14.40 TL
+0.000 0.000 0.502 rg
+613.27 70.30 Td
+(Pro-micro Pinouts) Tj
+ET
+7.20 w
+BT
+/F2 9.818181818181817 Tf
+10.80 TL
+0.000 0.000 0.502 rg
+793.59 43.36 Td
+(1) Tj
+ET
+7.20 w
+BT
+/F2 9.818181818181817 Tf
+10.80 TL
+0.000 0.000 0.502 rg
+730.08 129.04 Td
+(2025-11-08) Tj
+ET
+7.20 w
+BT
+/F2 9.818181818181817 Tf
+10.80 TL
+0.000 0.000 0.502 rg
+730.08 114.64 Td
+(2025-11-07) Tj
+ET
+7.20 w
+BT
+/F2 13.090909090909088 Tf
+14.40 TL
+0.000 0.000 0.502 rg
+474.65 120.70 Td
+(Pro-micro_Pinouts) Tj
+ET
+7.20 w
+BT
+/F2 9.818181818181817 Tf
+10.80 TL
+0.000 0.000 0.502 rg
+709.35 43.36 Td
+(1) Tj
+ET
+7.20 w
+BT
+/F2 9.818181818181817 Tf
+10.80 TL
+0.000 0.000 0.502 rg
+510.19 100.24 Td
+(Sheet_1) Tj
+ET
+7.20 w
+BT
+/F2 9.818181818181817 Tf
+10.80 TL
+0.000 0.000 0.502 rg
+509.74 21.04 Td
+(V2.0) Tj
+ET
+7.20 w
+BT
+/F2 9.818181818181817 Tf
+10.80 TL
+0.000 0.000 0.502 rg
+572.88 21.04 Td
+(A3) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 0.502 rg
+2.88 -1170.85 Td
+(1) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 0.502 rg
+2.88 -1170.85 Td
+(1) Tj
+ET
+q
+1 0 0 1 0 0 cm
+7.20 w
+BT
+/F2 9.818181818181817 Tf
+10.80 TL
+0.627 0.000 0.000 rg
+339.84 71.44 Td
+(Reviewed) Tj
+ET
+7.20 w
+BT
+/F2 9.818181818181817 Tf
+10.80 TL
+0.627 0.000 0.000 rg
+339.84 85.84 Td
+(Drawn) Tj
+ET
+7.20 w
+BT
+/F2 9.818181818181817 Tf
+10.80 TL
+0.627 0.000 0.000 rg
+509.75 42.64 Td
+(VER) Tj
+ET
+7.20 w
+BT
+/F2 9.818181818181817 Tf
+10.80 TL
+0.627 0.000 0.000 rg
+656.64 114.64 Td
+(Create Date) Tj
+ET
+7.20 w
+BT
+/F2 9.818181818181817 Tf
+10.80 TL
+0.627 0.000 0.000 rg
+656.64 100.24 Td
+(Part Number) Tj
+ET
+7.20 w
+BT
+/F2 9.818181818181817 Tf
+10.80 TL
+0.627 0.000 0.000 rg
+656.64 43.36 Td
+(PAGE) Tj
+ET
+7.20 w
+BT
+/F2 9.818181818181817 Tf
+10.80 TL
+0.627 0.000 0.000 rg
+745.92 43.36 Td
+(OF) Tj
+ET
+7.20 w
+BT
+/F2 9.818181818181817 Tf
+10.80 TL
+0.627 0.000 0.000 rg
+339.84 121.84 Td
+(Schematic) Tj
+ET
+7.20 w
+BT
+/F2 9.818181818181817 Tf
+10.80 TL
+0.627 0.000 0.000 rg
+656.64 129.04 Td
+(Update Date) Tj
+ET
+7.20 w
+BT
+/F2 9.818181818181817 Tf
+10.80 TL
+0.627 0.000 0.000 rg
+566.53 42.64 Td
+(SIZE) Tj
+ET
+7.20 w
+BT
+/F2 9.818181818181817 Tf
+10.80 TL
+0.627 0.000 0.000 rg
+341.28 100.24 Td
+(Page) Tj
+ET
+2 J
+0 j
+72 M
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+332.64 139.68 505.44 -129.60 re
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+548.640 10.080 m 
+548.640 53.280 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+398.880 38.880 m 
+398.880 139.680 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+649.440 139.680 m 
+649.440 96.480 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+721.440 96.480 m 
+721.440 139.680 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+491.040 96.480 m 
+491.040 10.080 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+491.040 82.080 m 
+332.640 82.080 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+838.080 125.280 m 
+649.440 125.280 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+838.080 38.880 m 
+332.640 38.880 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+838.080 96.480 m 
+332.640 96.480 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+838.080 53.280 m 
+332.640 53.280 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+491.040 67.680 m 
+332.640 67.680 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+838.080 110.880 m 
+332.640 110.880 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+606.240 53.280 m 
+606.240 10.080 l
+S
+q
+0.33 0.53 1.00 rg
+[] 0 d
+414.402 23.650 m 
+414.554 23.764 414.738 23.821 414.949 23.821 c
+415.233 23.821 415.536 23.720 415.859 23.524 c
+416.182 23.328 416.492 23.018 416.789 22.594 c
+417.936 24.359 l
+417.580 24.852 417.138 25.238 416.604 25.510 c
+416.070 25.782 415.503 25.915 414.910 25.915 c
+414.066 25.915 413.328 25.668 412.708 25.175 c
+412.088 24.681 411.778 24.036 411.778 23.252 c
+411.778 22.689 411.976 22.151 412.372 21.645 c
+412.655 21.285 413.169 20.848 413.908 20.342 c
+414.547 19.906 414.943 19.602 415.088 19.432 c
+415.233 19.261 415.305 19.096 415.305 18.932 c
+415.305 18.729 415.213 18.559 415.022 18.407 c
+414.831 18.261 414.580 18.186 414.264 18.186 c
+413.466 18.186 412.728 18.609 412.055 19.463 c
+410.572 17.806 l
+411.231 17.136 411.831 16.668 412.365 16.415 c
+412.899 16.162 413.499 16.035 414.152 16.035 c
+415.286 16.035 416.149 16.345 416.749 16.965 c
+417.349 17.585 417.646 18.249 417.646 18.945 c
+417.646 19.476 417.501 19.963 417.211 20.418 c
+416.920 20.867 416.314 21.411 415.391 22.044 c
+414.811 22.442 414.468 22.708 414.356 22.847 c
+414.237 22.986 414.178 23.125 414.178 23.265 c
+414.171 23.410 414.251 23.537 414.402 23.650 c
+414.402 23.650 l
+f
+0.33 0.53 1.00 rg
+[] 0 d
+423.434 20.102 m 
+421.528 25.674 l
+419.168 25.674 l
+421.924 17.559 l
+419.142 12.841 l
+421.680 12.841 l
+429.373 25.674 l
+426.782 25.674 l
+423.434 20.102 l
+f
+0.33 0.53 1.00 rg
+[] 0 d
+397.908 24.093 m 
+393.267 24.093 l
+393.650 26.731 l
+393.650 26.731 398.574 26.737 398.601 26.737 c
+399.247 26.737 399.774 27.243 399.774 27.863 c
+399.774 28.483 399.247 28.989 398.601 28.989 c
+398.581 28.989 391.455 28.989 391.455 28.989 c
+389.635 16.275 l
+397.098 16.275 l
+397.098 16.275 l
+397.724 16.288 398.231 16.781 398.231 17.382 c
+398.231 17.996 397.711 18.495 397.071 18.495 c
+397.045 18.495 392.443 18.489 392.443 18.489 c
+392.931 21.860 l
+392.931 21.860 397.658 21.854 397.697 21.854 c
+398.344 21.854 398.871 22.360 398.871 22.980 c
+398.884 23.543 398.462 24.005 397.908 24.093 c
+397.908 24.093 l
+f
+0.33 0.53 1.00 rg
+[] 0 d
+438.576 28.982 m 
+438.556 28.982 431.430 28.982 431.430 28.982 c
+429.624 16.282 l
+437.093 16.282 l
+437.093 16.282 l
+437.719 16.294 438.227 16.788 438.227 17.389 c
+438.227 18.002 437.706 18.502 437.066 18.502 c
+437.040 18.502 432.439 18.495 432.439 18.495 c
+432.926 21.867 l
+432.926 21.867 437.653 21.860 437.699 21.860 c
+438.345 21.860 438.873 22.366 438.873 22.986 c
+438.873 23.543 438.451 24.005 437.897 24.093 c
+433.256 24.093 l
+433.638 26.731 l
+433.638 26.731 438.563 26.737 438.589 26.737 c
+439.235 26.737 439.763 27.243 439.763 27.863 c
+439.749 28.476 439.229 28.982 438.576 28.982 c
+438.576 28.982 l
+f
+0.33 0.53 1.00 rg
+[] 0 d
+451.912 22.898 m 
+451.912 24.144 451.609 25.251 451.009 26.206 c
+450.409 27.161 449.631 27.863 448.669 28.306 c
+447.706 28.755 446.249 28.976 444.278 28.976 c
+442.182 28.976 l
+440.376 16.275 l
+444.489 16.275 l
+446.216 16.275 447.568 16.522 448.537 17.015 c
+449.506 17.509 450.317 18.293 450.956 19.362 c
+451.596 20.437 451.912 21.614 451.912 22.898 c
+451.912 22.898 l
+448.655 20.273 m 
+448.174 19.577 447.541 19.084 446.750 18.799 c
+446.183 18.597 445.274 18.495 444.015 18.495 c
+443.197 18.495 l
+444.364 26.743 l
+444.990 26.743 l
+446.012 26.743 446.829 26.592 447.443 26.282 c
+448.056 25.972 448.530 25.535 448.873 24.966 c
+449.209 24.397 449.381 23.695 449.381 22.853 c
+449.374 21.835 449.137 20.969 448.655 20.273 c
+448.655 20.273 l
+f
+0.33 0.53 1.00 rg
+[] 0 d
+461.702 23.043 m 
+460.231 22.265 l
+460.113 21.241 459.203 20.450 458.109 20.450 c
+456.929 20.450 455.973 21.367 455.973 22.499 c
+455.973 23.631 456.929 24.549 458.109 24.549 c
+458.564 24.549 458.986 24.409 459.328 24.182 c
+461.154 25.149 l
+460.166 28.957 l
+458.076 28.957 l
+450.956 16.307 l
+453.619 16.307 l
+455.122 19.001 l
+460.357 19.001 l
+461.055 16.307 l
+463.455 16.307 l
+461.702 23.043 l
+461.702 23.043 l
+f
+0.33 0.53 1.00 rg
+[] 0 d
+457.324 22.550 m 
+457.324 22.113 457.693 21.759 458.148 21.759 c
+458.603 21.759 458.972 22.113 458.972 22.550 c
+458.972 22.986 458.603 23.340 458.148 23.340 c
+457.693 23.340 457.324 22.986 457.324 22.550 c
+457.324 22.550 l
+f
+0.33 0.53 1.00 rg
+[] 0 d
+408.674 24.194 m 
+408.674 24.194 408.674 24.201 408.674 24.194 c
+407.850 24.194 l
+407.764 24.346 l
+407.428 24.858 407.006 25.244 406.498 25.510 c
+405.984 25.776 405.173 25.908 404.567 25.908 c
+403.663 25.908 402.800 25.674 401.976 25.206 c
+401.152 24.738 400.493 24.087 400.005 23.246 c
+399.517 22.411 399.266 21.525 399.266 20.602 c
+399.266 19.387 399.649 18.325 400.413 17.408 c
+401.178 16.490 402.213 16.035 403.512 16.035 c
+404.079 16.035 404.586 16.124 405.041 16.313 c
+405.496 16.497 405.984 16.819 406.505 17.287 c
+406.505 17.287 407.118 16.775 407.124 16.781 c
+407.507 16.490 407.981 16.307 408.496 16.275 c
+408.733 16.275 l
+408.766 16.547 l
+409.603 23.309 l
+409.596 23.309 409.596 23.309 409.590 23.309 c
+409.590 23.796 409.181 24.188 408.674 24.194 c
+408.674 24.194 l
+406.749 19.659 m 
+406.452 19.122 406.083 18.723 405.641 18.470 c
+405.199 18.217 404.685 18.091 404.092 18.091 c
+403.380 18.091 402.800 18.312 402.345 18.767 c
+401.890 19.217 401.666 19.811 401.666 20.545 c
+401.666 21.500 401.963 22.278 402.563 22.885 c
+403.162 23.492 403.888 23.790 404.745 23.790 c
+405.483 23.790 406.076 23.562 406.524 23.113 c
+406.973 22.657 407.197 22.063 407.197 21.316 c
+407.197 20.753 407.045 20.197 406.749 19.659 c
+406.749 19.659 l
+f
+0.33 0.53 1.00 rg
+[] 0 d
+381.612 27.749 m 
+381.118 29.008 380.314 30.140 379.252 31.057 c
+377.624 32.461 375.515 33.239 373.319 33.239 c
+371.421 33.239 369.608 32.670 368.079 31.595 c
+367.340 31.076 366.701 30.462 366.173 29.767 c
+365.844 29.811 365.508 29.836 365.165 29.836 c
+363.273 29.836 361.486 29.128 360.148 27.844 c
+358.810 26.560 358.072 24.852 358.072 23.031 c
+358.072 21.342 358.724 19.723 359.904 18.470 c
+360.840 17.477 362.053 16.775 363.391 16.446 c
+363.972 14.789 365.606 13.594 367.525 13.594 c
+369.931 13.594 371.889 15.472 371.889 17.781 c
+371.889 17.914 371.882 18.053 371.869 18.186 c
+377.993 21.272 l
+376.655 23.499 l
+370.801 20.551 l
+370.003 21.424 368.830 21.974 367.525 21.974 c
+365.633 21.974 364.018 20.810 363.411 19.191 c
+361.869 19.843 360.788 21.316 360.788 23.037 c
+360.788 25.352 362.745 27.237 365.165 27.237 c
+366.015 27.237 366.813 27.003 367.485 26.598 c
+368.296 28.944 370.603 30.640 373.319 30.640 c
+376.484 30.640 379.081 28.350 379.430 25.409 c
+379.542 25.421 379.655 25.428 379.767 25.428 c
+381.659 25.428 383.195 23.954 383.195 22.139 c
+383.195 20.418 381.817 19.008 380.063 18.862 c
+378.105 18.862 l
+378.020 18.881 377.927 18.888 377.835 18.888 c
+377.077 18.888 376.464 18.299 376.464 17.572 c
+376.464 16.883 377.018 16.320 377.723 16.263 c
+377.723 16.250 l
+380.063 16.250 l
+380.182 16.250 l
+380.301 16.263 l
+381.830 16.389 383.247 17.053 384.289 18.141 c
+385.337 19.235 385.917 20.652 385.917 22.139 c
+385.917 24.757 384.104 26.996 381.612 27.749 c
+381.612 27.749 l
+367.525 19.400 m 
+368.454 19.400 369.212 18.673 369.212 17.781 c
+369.212 16.889 368.454 16.162 367.525 16.162 c
+366.595 16.162 365.837 16.889 365.837 17.781 c
+365.837 18.673 366.595 19.400 367.525 19.400 c
+367.525 19.400 l
+f
+Q
+Q
+2 J
+0 j
+72 M
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+110.88 1126.08 57.60 -115.20 re
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+147.04 1109.75 Td
+(BATIN) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+172.80 1112.63 Td
+(25) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+182.880 1111.680 m 
+168.480 1111.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+151.78 1102.55 Td
+(GND) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+172.80 1105.43 Td
+(24) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+182.880 1104.480 m 
+168.480 1104.480 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+153.23 1095.35 Td
+(RST) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+172.80 1098.23 Td
+(23) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+182.880 1097.280 m 
+168.480 1097.280 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+141.58 1088.15 Td
+(3.3v Out) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+172.80 1091.03 Td
+(22) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+182.880 1090.080 m 
+168.480 1090.080 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+149.22 1059.35 Td
+(P1.15) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+172.80 1062.23 Td
+(18) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+182.880 1061.280 m 
+168.480 1061.280 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+149.22 1080.95 Td
+(P0.31) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+172.80 1083.83 Td
+(21) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+182.880 1082.880 m 
+168.480 1082.880 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+113.04 1102.55 Td
+(P0.08) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+102.92 1105.43 Td
+(3) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+96.480 1104.480 m 
+110.880 1104.480 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+149.22 1052.15 Td
+(P1.13) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+172.80 1055.03 Td
+(17) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+182.880 1054.080 m 
+168.480 1054.080 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+149.22 1073.75 Td
+(P0.29) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+172.80 1076.63 Td
+(20) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+182.880 1075.680 m 
+168.480 1075.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+149.22 1044.95 Td
+(P1.11) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+172.80 1047.83 Td
+(16) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+182.880 1046.880 m 
+168.480 1046.880 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+149.22 1066.55 Td
+(P0.02) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+172.80 1069.43 Td
+(19) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+182.880 1068.480 m 
+168.480 1068.480 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+113.04 1095.35 Td
+(GND) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+102.92 1098.23 Td
+(4) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+96.480 1097.280 m 
+110.880 1097.280 l
+S
+0.72 w
+0.63 0.00 0.00 RG
+110.88 1111.68 m 110.88 1112.87 109.91 1113.84 108.72 1113.84 c
+107.53 1113.84 106.56 1112.87 106.56 1111.68 c
+106.56 1110.49 107.53 1109.52 108.72 1109.52 c
+109.91 1109.52 110.88 1110.49 110.88 1111.68 c
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+113.04 1109.75 Td
+(P0.06) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+102.92 1112.63 Td
+(2) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+96.480 1111.680 m 
+106.560 1111.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+149.22 1030.55 Td
+(P0.09) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+172.80 1033.43 Td
+(14) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+182.880 1032.480 m 
+168.480 1032.480 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+149.22 1037.75 Td
+(P0.10) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+172.80 1040.63 Td
+(15) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+182.880 1039.680 m 
+168.480 1039.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+113.04 1037.75 Td
+(P1.04) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+99.28 1040.63 Td
+(12) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+96.480 1039.680 m 
+110.880 1039.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+113.04 1088.15 Td
+(GND) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+102.92 1091.03 Td
+(5) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+96.480 1090.080 m 
+110.880 1090.080 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+113.04 1080.95 Td
+(P0.17) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+102.92 1083.83 Td
+(6) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+96.480 1082.880 m 
+110.880 1082.880 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+113.04 1073.75 Td
+(P0.20) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+102.92 1076.63 Td
+(7) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+96.480 1075.680 m 
+110.880 1075.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+113.04 1066.55 Td
+(P0.22) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+102.92 1069.43 Td
+(8) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+96.480 1068.480 m 
+110.880 1068.480 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+113.04 1059.35 Td
+(P0.24) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+102.92 1062.23 Td
+(9) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+96.480 1061.280 m 
+110.880 1061.280 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+113.04 1052.15 Td
+(P1.00) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+99.28 1055.03 Td
+(10) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+96.480 1054.080 m 
+110.880 1054.080 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+113.04 1044.95 Td
+(P0.11) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+99.28 1047.83 Td
+(11) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+96.480 1046.880 m 
+110.880 1046.880 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+113.04 1030.55 Td
+(P1.06) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+99.28 1033.43 Td
+(13) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+96.480 1032.480 m 
+110.880 1032.480 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+147.04 1116.95 Td
+(BATIN) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+172.80 1119.83 Td
+(26) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+182.880 1118.880 m 
+168.480 1118.880 l
+S
+1 J
+1 j
+0.72 w
+0.20 0.80 0.20 RG
+[] 0 d
+180.000 1121.760 m 
+185.760 1116.000 l
+180.000 1116.000 m 
+185.760 1121.760 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+113.04 1116.95 Td
+(GND) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+102.92 1119.83 Td
+(1) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+96.480 1118.880 m 
+110.880 1118.880 l
+S
+1 J
+1 j
+0.72 w
+0.20 0.80 0.20 RG
+[] 0 d
+93.600 1121.760 m 
+99.360 1116.000 l
+93.600 1116.000 m 
+99.360 1121.760 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+135.38 1128.78 Td
+(PRO_MICRO_NRF52840_26P) Tj
+ET
+7.20 w
+BT
+/F2 8.181818181818182 Tf
+9.00 TL
+0.000 g
+67.68 248.06 Td
+(Switches) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+118.080 154.080 m 
+118.080 161.280 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+111.600 154.080 m 
+124.560 154.080 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+113.760 152.640 m 
+122.400 152.640 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+115.920 151.200 m 
+120.240 151.200 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+117.360 149.760 m 
+118.800 149.760 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+118.080 161.280 m 
+118.080 161.280 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+108.72 142.09 Td
+(GND) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+118.080 204.480 m 
+118.080 211.680 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+111.600 204.480 m 
+124.560 204.480 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+113.760 203.040 m 
+122.400 203.040 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+115.920 201.600 m 
+120.240 201.600 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+117.360 200.160 m 
+118.800 200.160 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+118.080 211.680 m 
+118.080 211.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+108.72 192.49 Td
+(GND) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+74.880 161.280 m 
+71.280 157.680 l
+60.480 157.680 l
+60.480 164.880 l
+71.280 164.880 l
+74.880 161.280 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+74.880 161.280 m 
+74.880 161.280 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+42.93 158.92 Td
+(RBTN) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+74.880 211.680 m 
+71.280 208.080 l
+60.480 208.080 l
+60.480 215.280 l
+71.280 215.280 l
+74.880 211.680 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+74.880 211.680 m 
+74.880 211.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+41.22 208.96 Td
+(UBTN) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+96.480 165.600 m 
+96.480 169.200 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+107.28 161.93 Td
+(2) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+118.080 161.280 m 
+103.680 161.280 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+82.04 161.93 Td
+(1) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+74.880 161.280 m 
+89.280 161.280 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+88.560 165.600 m 
+104.400 165.600 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+105.12 161.28 m 105.12 162.08 104.48 162.72 103.68 162.72 c
+102.88 162.72 102.24 162.08 102.24 161.28 c
+102.24 160.48 102.88 159.84 103.68 159.84 c
+104.48 159.84 105.12 160.48 105.12 161.28 c
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+90.72 161.28 m 90.72 162.08 90.08 162.72 89.28 162.72 c
+88.48 162.72 87.84 162.08 87.84 161.28 c
+87.84 160.48 88.48 159.84 89.28 159.84 c
+90.08 159.84 90.72 160.48 90.72 161.28 c
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 0.502 rg
+89.20 178.06 Td
+(RS1) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+89.20 171.69 Td
+(434121043816) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+96.480 216.000 m 
+96.480 219.600 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+107.28 212.33 Td
+(2) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+118.080 211.680 m 
+103.680 211.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+82.04 212.33 Td
+(1) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+74.880 211.680 m 
+89.280 211.680 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+88.560 216.000 m 
+104.400 216.000 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+105.12 211.68 m 105.12 212.48 104.48 213.12 103.68 213.12 c
+102.88 213.12 102.24 212.48 102.24 211.68 c
+102.24 210.88 102.88 210.24 103.68 210.24 c
+104.48 210.24 105.12 210.88 105.12 211.68 c
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+90.72 211.68 m 90.72 212.48 90.08 213.12 89.28 213.12 c
+88.48 213.12 87.84 212.48 87.84 211.68 c
+87.84 210.88 88.48 210.24 89.28 210.24 c
+90.08 210.24 90.72 210.88 90.72 211.68 c
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 0.502 rg
+89.20 228.46 Td
+(US1) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+89.20 222.09 Td
+(434121043816) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+125.280 617.040 m 
+125.280 624.240 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+118.800 617.040 m 
+131.760 617.040 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+120.960 615.600 m 
+129.600 615.600 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+123.120 614.160 m 
+127.440 614.160 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+124.560 612.720 m 
+126.000 612.720 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+125.280 624.240 m 
+125.280 624.240 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+115.92 604.33 Td
+(GND) Tj
+ET
+2 J
+0 j
+72 M
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+103.68 631.44 14.40 -14.40 re
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+0.00 1.00 -1.00 0.00 108.85 608.42 Tm
+(1) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+110.880 602.640 m 
+110.880 617.040 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+100.04 619.01 Td
+(2) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+96.480 624.240 m 
+103.680 624.240 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+118.08 619.01 Td
+(3) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+125.280 624.240 m 
+118.080 624.240 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+0.00 1.00 -1.00 0.00 108.85 630.06 Tm
+(4) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+110.880 638.640 m 
+110.880 631.440 l
+S
+1 J
+1 j
+0.72 w
+0.20 0.80 0.20 RG
+[] 0 d
+108.000 641.520 m 
+113.760 635.760 l
+108.000 635.760 m 
+113.760 641.520 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+112.32 624.24 m 112.32 625.04 111.68 625.68 110.88 625.68 c
+110.08 625.68 109.44 625.04 109.44 624.24 c
+109.44 623.44 110.08 622.80 110.88 622.80 c
+111.68 622.80 112.32 623.44 112.32 624.24 c
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+110.880 622.800 m 
+110.880 617.040 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 0.502 rg
+95.72 648.44 Td
+(U11) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+95.72 641.89 Td
+(AMC-U_FL) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+96.480 617.040 m 
+96.480 624.240 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+90.000 617.040 m 
+102.960 617.040 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+92.160 615.600 m 
+100.800 615.600 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+94.320 614.160 m 
+98.640 614.160 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+95.760 612.720 m 
+97.200 612.720 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+96.480 624.240 m 
+96.480 624.240 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+87.12 605.05 Td
+(GND) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+146.880 573.840 m 
+154.080 573.840 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+146.880 570.240 m 
+146.880 577.440 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+154.080 573.840 m 
+154.080 573.840 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+128.76 570.43 Td
+(VCC) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+146.880 595.440 m 
+154.080 595.440 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+146.880 601.920 m 
+146.880 588.960 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+145.440 599.760 m 
+145.440 591.120 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+144.000 597.600 m 
+144.000 593.280 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+142.560 596.160 m 
+142.560 594.720 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+154.080 595.440 m 
+154.080 595.440 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+123.48 592.03 Td
+(GND) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+233.280 602.640 m 
+226.080 602.640 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+233.280 596.160 m 
+233.280 609.120 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+234.720 598.320 m 
+234.720 606.960 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+236.160 600.480 m 
+236.160 604.800 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+237.600 601.920 m 
+237.600 603.360 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+226.080 602.640 m 
+226.080 602.640 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+237.96 599.23 Td
+(GND) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+226.080 545.040 m 
+226.080 552.240 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+219.600 545.040 m 
+232.560 545.040 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+221.760 543.600 m 
+230.400 543.600 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+223.920 542.160 m 
+228.240 542.160 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+225.360 540.720 m 
+226.800 540.720 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+226.080 552.240 m 
+226.080 552.240 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+216.72 533.05 Td
+(GND) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+226.080 588.240 m 
+229.680 591.840 l
+240.480 591.840 l
+240.480 584.640 l
+229.680 584.640 l
+226.080 588.240 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+226.080 588.240 m 
+226.080 588.240 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+242.43 585.29 Td
+(NRST) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+154.080 559.440 m 
+150.480 555.840 l
+139.680 555.840 l
+139.680 563.040 l
+150.480 563.040 l
+154.080 559.440 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+154.080 559.440 m 
+154.080 559.440 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+124.96 556.28 Td
+(IRQ) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+154.080 552.240 m 
+150.480 548.640 l
+139.680 548.640 l
+139.680 555.840 l
+150.480 555.840 l
+154.080 552.240 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+154.080 552.240 m 
+154.080 552.240 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+120.42 549.37 Td
+(BUSY) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+226.080 581.040 m 
+229.680 584.640 l
+240.480 584.640 l
+240.480 577.440 l
+229.680 577.440 l
+226.080 581.040 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+226.080 581.040 m 
+226.080 581.040 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+241.92 578.24 Td
+(CS) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+226.080 573.840 m 
+229.680 577.440 l
+240.480 577.440 l
+240.480 570.240 l
+229.680 570.240 l
+226.080 573.840 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+226.080 573.840 m 
+226.080 573.840 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+241.60 571.04 Td
+(SCK) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+226.080 566.640 m 
+229.680 570.240 l
+240.480 570.240 l
+240.480 563.040 l
+229.680 563.040 l
+226.080 566.640 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+226.080 566.640 m 
+226.080 566.640 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+241.92 563.84 Td
+(MOSI) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+226.080 559.440 m 
+229.680 563.040 l
+240.480 563.040 l
+240.480 555.840 l
+229.680 555.840 l
+226.080 559.440 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+226.080 559.440 m 
+226.080 559.440 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+241.92 556.64 Td
+(MISO) Tj
+ET
+2 J
+0 j
+72 M
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+161.28 609.84 57.60 -64.80 re
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+165.96 606.24 m 165.96 606.84 165.48 607.32 164.88 607.32 c
+164.28 607.32 163.80 606.84 163.80 606.24 c
+163.80 605.64 164.28 605.16 164.88 605.16 c
+165.48 605.16 165.96 605.64 165.96 606.24 c
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+201.67 549.29 Td
+(GND) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+219.24 552.89 Td
+(1) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+226.080 552.240 m 
+218.880 552.240 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+199.49 556.49 Td
+(MISO) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+219.24 560.09 Td
+(2) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+226.080 559.440 m 
+218.880 559.440 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+199.49 563.69 Td
+(MOSI) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+219.24 567.29 Td
+(3) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+226.080 566.640 m 
+218.880 566.640 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+202.76 570.89 Td
+(SCK) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+219.24 574.49 Td
+(4) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+226.080 573.840 m 
+218.880 573.840 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+202.76 578.09 Td
+(NSS) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+219.24 581.69 Td
+(5) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+226.080 581.040 m 
+218.880 581.040 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+189.67 585.29 Td
+(NRESET) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+219.24 588.89 Td
+(6) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+226.080 588.240 m 
+218.880 588.240 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+206.76 592.49 Td
+(NC) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+219.24 596.09 Td
+(7) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+226.080 595.440 m 
+218.880 595.440 l
+S
+1 J
+1 j
+0.72 w
+0.20 0.80 0.20 RG
+[] 0 d
+223.200 598.320 m 
+228.960 592.560 l
+223.200 592.560 m 
+228.960 598.320 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+201.67 599.69 Td
+(GND) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+219.24 603.29 Td
+(8) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+226.080 602.640 m 
+218.880 602.640 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+163.94 599.69 Td
+(ANT) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+157.28 603.29 Td
+(9) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+154.080 602.640 m 
+161.280 602.640 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+163.94 592.49 Td
+(GND) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+153.64 596.09 Td
+(10) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+154.080 595.440 m 
+161.280 595.440 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+163.94 585.29 Td
+(DIO3) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+153.64 588.89 Td
+(11) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+154.080 588.240 m 
+161.280 588.240 l
+S
+1 J
+1 j
+0.72 w
+0.20 0.80 0.20 RG
+[] 0 d
+151.200 591.120 m 
+156.960 585.360 l
+151.200 585.360 m 
+156.960 591.120 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+163.94 578.09 Td
+(NC) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+153.64 581.69 Td
+(12) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+154.080 581.040 m 
+161.280 581.040 l
+S
+1 J
+1 j
+0.72 w
+0.20 0.80 0.20 RG
+[] 0 d
+151.200 583.920 m 
+156.960 578.160 l
+151.200 578.160 m 
+156.960 583.920 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+163.94 570.89 Td
+(VCC) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+153.64 574.49 Td
+(13) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+154.080 573.840 m 
+161.280 573.840 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+163.94 563.69 Td
+(NC) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+153.64 567.29 Td
+(14) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+154.080 566.640 m 
+161.280 566.640 l
+S
+1 J
+1 j
+0.72 w
+0.20 0.80 0.20 RG
+[] 0 d
+151.200 569.520 m 
+156.960 563.760 l
+151.200 563.760 m 
+156.960 569.520 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+163.94 556.49 Td
+(DIO1) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+153.64 560.09 Td
+(15) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+154.080 559.440 m 
+161.280 559.440 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+163.94 549.29 Td
+(BUSY) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+153.64 552.89 Td
+(16) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+154.080 552.240 m 
+161.280 552.240 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 0.502 rg
+185.64 618.81 Td
+(NICERF_LORA1262) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+185.64 612.33 Td
+(LoRa1262-868) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+409.680 996.480 m 
+413.280 992.880 l
+413.280 982.080 l
+406.080 982.080 l
+406.080 992.880 l
+409.680 996.480 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+409.680 996.480 m 
+409.680 996.480 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+0.00 1.00 -1.00 0.00 410.96 961.34 Tm
+(P1.02) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+416.880 996.480 m 
+420.480 992.880 l
+420.480 982.080 l
+413.280 982.080 l
+413.280 992.880 l
+416.880 996.480 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+416.880 996.480 m 
+416.880 996.480 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+0.00 1.00 -1.00 0.00 418.16 961.34 Tm
+(P1.07) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+402.480 996.480 m 
+406.080 992.880 l
+406.080 982.080 l
+398.880 982.080 l
+398.880 992.880 l
+402.480 996.480 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+402.480 996.480 m 
+402.480 996.480 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+0.00 1.00 -1.00 0.00 403.76 961.34 Tm
+(P1.01) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+647.280 218.880 m 
+654.480 218.880 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+647.280 215.280 m 
+647.280 222.480 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+654.480 218.880 m 
+654.480 218.880 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+629.15 215.53 Td
+(VCC) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+647.280 233.280 m 
+654.480 233.280 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+647.280 239.760 m 
+647.280 226.800 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+645.840 237.600 m 
+645.840 228.960 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+644.400 235.440 m 
+644.400 231.120 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+642.960 234.000 m 
+642.960 232.560 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+654.480 233.280 m 
+654.480 233.280 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+623.88 229.93 Td
+(GND) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+726.480 233.280 m 
+719.280 233.280 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+726.480 226.800 m 
+726.480 239.760 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+727.920 228.960 m 
+727.920 237.600 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+729.360 231.120 m 
+729.360 235.440 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+730.800 232.560 m 
+730.800 234.000 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+719.280 233.280 m 
+719.280 233.280 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+731.16 229.93 Td
+(GND) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+726.480 182.880 m 
+719.280 182.880 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+726.480 176.400 m 
+726.480 189.360 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+727.920 178.560 m 
+727.920 187.200 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+729.360 180.720 m 
+729.360 185.040 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+730.800 182.160 m 
+730.800 183.600 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+719.280 182.880 m 
+719.280 182.880 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+731.16 179.53 Td
+(GND) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+654.480 197.280 m 
+650.880 193.680 l
+640.080 193.680 l
+640.080 200.880 l
+650.880 200.880 l
+654.480 197.280 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+654.480 197.280 m 
+654.480 197.280 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+627.00 194.41 Td
+(IRQ) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+654.480 211.680 m 
+650.880 208.080 l
+640.080 208.080 l
+640.080 215.280 l
+650.880 215.280 l
+654.480 211.680 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+654.480 211.680 m 
+654.480 211.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+620.82 208.93 Td
+(NRST) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+719.280 211.680 m 
+722.880 215.280 l
+733.680 215.280 l
+733.680 208.080 l
+722.880 208.080 l
+719.280 211.680 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+719.280 211.680 m 
+719.280 211.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+735.12 208.96 Td
+(MISO) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+719.280 218.880 m 
+722.880 222.480 l
+733.680 222.480 l
+733.680 215.280 l
+722.880 215.280 l
+719.280 218.880 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+719.280 218.880 m 
+719.280 218.880 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+735.12 216.16 Td
+(MOSI) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+719.280 204.480 m 
+722.880 208.080 l
+733.680 208.080 l
+733.680 200.880 l
+722.880 200.880 l
+719.280 204.480 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+719.280 204.480 m 
+719.280 204.480 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+734.91 201.76 Td
+(SCK) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+719.280 226.080 m 
+722.880 229.680 l
+733.680 229.680 l
+733.680 222.480 l
+722.880 222.480 l
+719.280 226.080 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+719.280 226.080 m 
+719.280 226.080 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+735.12 223.36 Td
+(CS) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+654.480 204.480 m 
+650.880 200.880 l
+640.080 200.880 l
+640.080 208.080 l
+650.880 208.080 l
+654.480 204.480 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+654.480 204.480 m 
+654.480 204.480 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+620.82 201.61 Td
+(BUSY) Tj
+ET
+2 J
+0 j
+72 M
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+661.68 240.48 50.40 -64.80 re
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+666.36 236.88 m 666.36 237.48 665.88 237.96 665.28 237.96 c
+664.68 237.96 664.20 237.48 664.20 236.88 c
+664.20 236.28 664.68 235.80 665.28 235.80 c
+665.88 235.80 666.36 236.28 666.36 236.88 c
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+664.34 230.33 Td
+(GND) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+657.68 233.93 Td
+(1) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+654.480 233.280 m 
+661.680 233.280 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+664.34 223.13 Td
+(GND) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+657.68 226.73 Td
+(2) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+654.480 226.080 m 
+661.680 226.080 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+664.34 215.93 Td
+(3.3V) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+657.68 219.53 Td
+(3) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+654.480 218.880 m 
+661.680 218.880 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+664.34 208.73 Td
+(RESET) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+657.68 212.33 Td
+(4) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+654.480 211.680 m 
+661.680 211.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+664.34 201.53 Td
+(DIO0) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+657.68 205.13 Td
+(5) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+654.480 204.480 m 
+661.680 204.480 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+664.34 194.33 Td
+(DIO1) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+657.68 197.93 Td
+(6) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+654.480 197.280 m 
+661.680 197.280 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+664.34 187.13 Td
+(DIO2) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+657.68 190.73 Td
+(7) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+654.480 190.080 m 
+661.680 190.080 l
+S
+1 J
+1 j
+0.72 w
+0.20 0.80 0.20 RG
+[] 0 d
+651.600 192.960 m 
+657.360 187.200 l
+651.600 187.200 m 
+657.360 192.960 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+664.34 179.93 Td
+(DIO3) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+657.68 183.53 Td
+(8) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+654.480 182.880 m 
+661.680 182.880 l
+S
+1 J
+1 j
+0.72 w
+0.20 0.80 0.20 RG
+[] 0 d
+651.600 185.760 m 
+657.360 180.000 l
+651.600 180.000 m 
+657.360 185.760 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+694.87 179.93 Td
+(GND) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+712.44 183.53 Td
+(9) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+719.280 182.880 m 
+712.080 182.880 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+694.14 187.13 Td
+(DIO4) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+712.44 190.73 Td
+(10) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+719.280 190.080 m 
+712.080 190.080 l
+S
+1 J
+1 j
+0.72 w
+0.20 0.80 0.20 RG
+[] 0 d
+716.400 192.960 m 
+722.160 187.200 l
+716.400 187.200 m 
+722.160 192.960 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+694.14 194.33 Td
+(DIO5) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+712.44 197.93 Td
+(11) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+719.280 197.280 m 
+712.080 197.280 l
+S
+1 J
+1 j
+0.72 w
+0.20 0.80 0.20 RG
+[] 0 d
+716.400 200.160 m 
+722.160 194.400 l
+716.400 194.400 m 
+722.160 200.160 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+695.96 201.53 Td
+(SCK) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+712.44 205.13 Td
+(12) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+719.280 204.480 m 
+712.080 204.480 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+692.69 208.73 Td
+(MISO) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+712.44 212.33 Td
+(13) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+719.280 211.680 m 
+712.080 211.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+692.69 215.93 Td
+(MOSI) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+712.44 219.53 Td
+(14) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+719.280 218.880 m 
+712.080 218.880 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+695.96 223.13 Td
+(NSS) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+712.44 226.73 Td
+(15) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+719.280 226.080 m 
+712.080 226.080 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+694.87 230.33 Td
+(GND) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+712.44 233.93 Td
+(16) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+719.280 233.280 m 
+712.080 233.280 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 0.502 rg
+682.74 249.45 Td
+(RA-2) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+682.74 242.97 Td
+(RA-02_C9900010926) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+560.880 1082.880 m 
+564.480 1086.480 l
+575.280 1086.480 l
+575.280 1079.280 l
+564.480 1079.280 l
+560.880 1082.880 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+560.880 1082.880 m 
+560.880 1082.880 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+577.22 1079.93 Td
+(ADC) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+370.080 1039.680 m 
+366.480 1036.080 l
+355.680 1036.080 l
+355.680 1043.280 l
+366.480 1043.280 l
+370.080 1039.680 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+370.080 1039.680 m 
+370.080 1039.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+340.78 1036.81 Td
+(SDA) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+370.080 1046.880 m 
+366.480 1043.280 l
+355.680 1043.280 l
+355.680 1050.480 l
+366.480 1050.480 l
+370.080 1046.880 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+370.080 1046.880 m 
+370.080 1046.880 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+341.51 1044.01 Td
+(SCL) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+269.280 222.480 m 
+269.280 215.280 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+265.680 222.480 m 
+272.880 222.480 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+269.280 215.280 m 
+269.280 215.280 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+262.08 223.45 Td
+(+5V) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+233.280 179.280 m 
+233.280 186.480 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+226.800 179.280 m 
+239.760 179.280 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+228.960 177.840 m 
+237.600 177.840 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+231.120 176.400 m 
+235.440 176.400 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+232.560 174.960 m 
+234.000 174.960 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+233.280 186.480 m 
+233.280 186.480 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+223.92 166.39 Td
+(GND) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+233.280 222.480 m 
+233.280 215.280 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+229.680 222.480 m 
+236.880 222.480 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+233.280 215.280 m 
+233.280 215.280 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+224.64 223.45 Td
+(VCC) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+269.280 179.280 m 
+269.280 186.480 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+262.800 179.280 m 
+275.760 179.280 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+264.960 177.840 m 
+273.600 177.840 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+267.120 176.400 m 
+271.440 176.400 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+268.560 174.960 m 
+270.000 174.960 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+269.280 186.480 m 
+269.280 186.480 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+259.92 167.29 Td
+(GND) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+239.040 199.440 m 
+227.520 199.440 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+233.280 186.480 m 
+233.280 193.680 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+233.280 208.080 m 
+233.280 202.320 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+227.520 202.320 m 
+239.040 202.320 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+233.280 215.280 m 
+233.280 208.080 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+233.280 199.440 m 
+233.280 193.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 0.502 rg
+240.48 198.81 Td
+(C2) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+240.48 192.33 Td
+(100uF) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+275.040 199.440 m 
+263.520 199.440 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+269.280 186.480 m 
+269.280 193.680 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+269.280 208.080 m 
+269.280 202.320 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+263.520 202.320 m 
+275.040 202.320 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+269.280 215.280 m 
+269.280 208.080 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+269.280 199.440 m 
+269.280 193.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 0.502 rg
+276.48 198.81 Td
+(C1) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+276.48 192.33 Td
+(100uF) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+355.680 895.680 m 
+352.080 892.080 l
+341.280 892.080 l
+341.280 899.280 l
+352.080 899.280 l
+355.680 895.680 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+355.680 895.680 m 
+355.680 895.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+321.66 892.84 Td
+(RXEN) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+467.280 1111.680 m 
+470.880 1115.280 l
+481.680 1115.280 l
+481.680 1108.080 l
+470.880 1108.080 l
+467.280 1111.680 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+467.280 1111.680 m 
+467.280 1111.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+483.48 1108.74 Td
+(BATT) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+521.280 1104.480 m 
+514.080 1104.480 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+521.280 1098.000 m 
+521.280 1110.960 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+522.720 1100.160 m 
+522.720 1108.800 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+524.160 1102.320 m 
+524.160 1106.640 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+525.600 1103.760 m 
+525.600 1105.200 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+514.080 1104.480 m 
+514.080 1104.480 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+525.96 1101.13 Td
+(GND) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+474.480 1075.680 m 
+478.080 1079.280 l
+488.880 1079.280 l
+488.880 1072.080 l
+478.080 1072.080 l
+474.480 1075.680 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+474.480 1075.680 m 
+474.480 1075.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+490.10 1072.96 Td
+(BUSY) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+474.480 1046.880 m 
+478.080 1050.480 l
+488.880 1050.480 l
+488.880 1043.280 l
+478.080 1043.280 l
+474.480 1046.880 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+474.480 1046.880 m 
+474.480 1046.880 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+490.20 1044.16 Td
+(SCK) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+474.480 1068.480 m 
+478.080 1072.080 l
+488.880 1072.080 l
+488.880 1064.880 l
+478.080 1064.880 l
+474.480 1068.480 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+474.480 1068.480 m 
+474.480 1068.480 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+490.32 1065.76 Td
+(MISO) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+474.480 1061.280 m 
+478.080 1064.880 l
+488.880 1064.880 l
+488.880 1057.680 l
+478.080 1057.680 l
+474.480 1061.280 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+474.480 1061.280 m 
+474.480 1061.280 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+490.32 1058.56 Td
+(MOSI) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+474.480 1054.080 m 
+478.080 1057.680 l
+488.880 1057.680 l
+488.880 1050.480 l
+478.080 1050.480 l
+474.480 1054.080 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+474.480 1054.080 m 
+474.480 1054.080 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+490.32 1051.36 Td
+(CS) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+521.280 1090.080 m 
+514.080 1090.080 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+521.280 1093.680 m 
+521.280 1086.480 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+514.080 1090.080 m 
+514.080 1090.080 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+522.00 1086.73 Td
+(VCC) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+474.480 1032.480 m 
+478.080 1036.080 l
+488.880 1036.080 l
+488.880 1028.880 l
+478.080 1028.880 l
+474.480 1032.480 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+474.480 1032.480 m 
+474.480 1032.480 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+490.68 1029.76 Td
+(NRST) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+474.480 1039.680 m 
+478.080 1043.280 l
+488.880 1043.280 l
+488.880 1036.080 l
+478.080 1036.080 l
+474.480 1039.680 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+474.480 1039.680 m 
+474.480 1039.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+490.32 1036.96 Td
+(IRQ) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+370.080 1068.480 m 
+366.480 1064.880 l
+355.680 1064.880 l
+355.680 1072.080 l
+366.480 1072.080 l
+370.080 1068.480 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+370.080 1068.480 m 
+370.080 1068.480 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+332.05 1065.61 Td
+(GPSTX) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+370.080 1075.680 m 
+366.480 1072.080 l
+355.680 1072.080 l
+355.680 1079.280 l
+366.480 1079.280 l
+370.080 1075.680 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+370.080 1075.680 m 
+370.080 1075.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+331.32 1072.81 Td
+(GPSRX) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+370.080 1061.280 m 
+366.480 1057.680 l
+355.680 1057.680 l
+355.680 1064.880 l
+366.480 1064.880 l
+370.080 1061.280 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+370.080 1061.280 m 
+370.080 1061.280 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+331.32 1058.41 Td
+(GPSEN) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+373.680 1093.680 m 
+380.880 1093.680 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+373.680 1100.160 m 
+373.680 1087.200 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+372.240 1098.000 m 
+372.240 1089.360 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+370.800 1095.840 m 
+370.800 1091.520 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+369.360 1094.400 m 
+369.360 1092.960 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+380.880 1093.680 m 
+380.880 1093.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+350.28 1090.33 Td
+(GND) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+370.080 1054.080 m 
+366.480 1050.480 l
+355.680 1050.480 l
+355.680 1057.680 l
+366.480 1057.680 l
+370.080 1054.080 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+370.080 1054.080 m 
+370.080 1054.080 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+336.42 1051.36 Td
+(UBTN) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+474.480 1097.280 m 
+478.080 1100.880 l
+488.880 1100.880 l
+488.880 1093.680 l
+478.080 1093.680 l
+474.480 1097.280 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+474.480 1097.280 m 
+474.480 1097.280 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+490.33 1094.17 Td
+(RBTN) Tj
+ET
+2 J
+0 j
+72 M
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+557.28 1104.48 7.20 -14.40 re
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+560.880 1111.680 m 
+560.880 1104.480 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+560.880 1082.880 m 
+560.880 1090.080 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 0.502 rg
+565.92 1095.27 Td
+(R_ADC_T0) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+565.92 1088.79 Td
+(1M) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+560.880 1046.880 m 
+560.880 1054.080 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+554.400 1046.880 m 
+567.360 1046.880 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+556.560 1045.440 m 
+565.200 1045.440 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+558.720 1044.000 m 
+563.040 1044.000 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+560.160 1042.560 m 
+561.600 1042.560 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+560.880 1054.080 m 
+560.880 1054.080 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+551.52 1034.89 Td
+(GND) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+560.880 1111.680 m 
+557.280 1115.280 l
+557.280 1126.080 l
+564.480 1126.080 l
+564.480 1115.280 l
+560.880 1111.680 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+560.880 1111.680 m 
+560.880 1111.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+0.00 1.00 -1.00 0.00 562.23 1126.29 Tm
+(BATT) Tj
+ET
+2 J
+0 j
+72 M
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+557.28 1075.68 7.20 -14.40 re
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+560.880 1082.880 m 
+560.880 1075.680 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+560.880 1054.080 m 
+560.880 1061.280 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 0.502 rg
+565.92 1066.47 Td
+(R_ADC_B0) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+565.92 1059.99 Td
+(1.5M) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+380.880 1104.480 m 
+377.280 1100.880 l
+366.480 1100.880 l
+366.480 1108.080 l
+377.280 1108.080 l
+380.880 1104.480 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+380.880 1104.480 m 
+380.880 1104.480 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+329.75 1101.76 Td
+(SERIAL2TX) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+380.880 1111.680 m 
+377.280 1108.080 l
+366.480 1108.080 l
+366.480 1115.280 l
+377.280 1115.280 l
+380.880 1111.680 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+380.880 1111.680 m 
+380.880 1111.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+329.03 1108.96 Td
+(SERIAL2RX) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+380.880 1032.480 m 
+377.280 1028.880 l
+366.480 1028.880 l
+366.480 1036.080 l
+377.280 1036.080 l
+380.880 1032.480 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+380.880 1032.480 m 
+380.880 1032.480 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+347.94 1029.61 Td
+(P1.06) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+737.280 434.880 m 
+730.080 434.880 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+737.280 438.480 m 
+737.280 431.280 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+730.080 434.880 m 
+730.080 434.880 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+738.00 431.53 Td
+(VCC) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+622.080 362.880 m 
+629.280 362.880 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+622.080 369.360 m 
+622.080 356.400 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+620.640 367.200 m 
+620.640 358.560 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+619.200 365.040 m 
+619.200 360.720 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+617.760 363.600 m 
+617.760 362.160 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+629.280 362.880 m 
+629.280 362.880 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+598.68 359.53 Td
+(GND) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+622.080 377.280 m 
+629.280 377.280 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+622.080 383.760 m 
+622.080 370.800 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+620.640 381.600 m 
+620.640 372.960 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+619.200 379.440 m 
+619.200 375.120 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+617.760 378.000 m 
+617.760 376.560 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+629.280 377.280 m 
+629.280 377.280 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+598.68 373.93 Td
+(GND) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+622.080 391.680 m 
+629.280 391.680 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+622.080 398.160 m 
+622.080 385.200 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+620.640 396.000 m 
+620.640 387.360 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+619.200 393.840 m 
+619.200 389.520 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+617.760 392.400 m 
+617.760 390.960 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+629.280 391.680 m 
+629.280 391.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+598.68 388.33 Td
+(GND) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+622.080 442.080 m 
+629.280 442.080 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+622.080 448.560 m 
+622.080 435.600 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+620.640 446.400 m 
+620.640 437.760 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+619.200 444.240 m 
+619.200 439.920 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+617.760 442.800 m 
+617.760 441.360 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+629.280 442.080 m 
+629.280 442.080 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+598.68 438.73 Td
+(GND) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+737.280 442.080 m 
+730.080 442.080 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+737.280 435.600 m 
+737.280 448.560 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+738.720 437.760 m 
+738.720 446.400 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+740.160 439.920 m 
+740.160 444.240 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+741.600 441.360 m 
+741.600 442.800 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+730.080 442.080 m 
+730.080 442.080 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+741.96 438.73 Td
+(GND) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+737.280 391.680 m 
+730.080 391.680 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+737.280 385.200 m 
+737.280 398.160 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+738.720 387.360 m 
+738.720 396.000 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+740.160 389.520 m 
+740.160 393.840 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+741.600 390.960 m 
+741.600 392.400 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+730.080 391.680 m 
+730.080 391.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+741.96 388.33 Td
+(GND) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+737.280 377.280 m 
+730.080 377.280 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+737.280 370.800 m 
+737.280 383.760 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+738.720 372.960 m 
+738.720 381.600 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+740.160 375.120 m 
+740.160 379.440 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+741.600 376.560 m 
+741.600 378.000 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+730.080 377.280 m 
+730.080 377.280 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+741.96 373.93 Td
+(GND) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+730.080 355.680 m 
+730.080 362.880 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+723.600 355.680 m 
+736.560 355.680 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+725.760 354.240 m 
+734.400 354.240 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+727.920 352.800 m 
+732.240 352.800 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+729.360 351.360 m 
+730.800 351.360 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+730.080 362.880 m 
+730.080 362.880 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+720.72 342.97 Td
+(GND) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+645.12 360.65 Td
+(GND) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+632.80 363.53 Td
+(22) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+629.280 362.880 m 
+643.680 362.880 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+645.12 367.85 Td
+(ANT_2.4) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+632.80 370.73 Td
+(21) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+629.280 370.080 m 
+643.680 370.080 l
+S
+1 J
+1 j
+0.72 w
+0.20 0.80 0.20 RG
+[] 0 d
+626.400 372.960 m 
+632.160 367.200 l
+626.400 367.200 m 
+632.160 372.960 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+645.12 375.05 Td
+(GND) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+632.80 377.93 Td
+(20) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+629.280 377.280 m 
+643.680 377.280 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+645.12 389.45 Td
+(GND) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+632.80 392.33 Td
+(19) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+629.280 391.680 m 
+643.680 391.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+645.12 396.65 Td
+(BUSY) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+632.80 399.53 Td
+(18) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+629.280 398.880 m 
+643.680 398.880 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+645.12 403.85 Td
+(NSS) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+632.80 406.73 Td
+(17) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+629.280 406.080 m 
+643.680 406.080 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+645.12 411.05 Td
+(SCK) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+632.80 413.93 Td
+(16) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+629.280 413.280 m 
+643.680 413.280 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+645.12 418.25 Td
+(MOSI) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+632.80 421.13 Td
+(15) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+629.280 420.480 m 
+643.680 420.480 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+645.12 425.45 Td
+(MISO) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+632.80 428.33 Td
+(14) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+629.280 427.680 m 
+643.680 427.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+645.12 432.65 Td
+(NC) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+632.80 435.53 Td
+(13) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+629.280 434.880 m 
+643.680 434.880 l
+S
+1 J
+1 j
+0.72 w
+0.20 0.80 0.20 RG
+[] 0 d
+626.400 437.760 m 
+632.160 432.000 l
+626.400 432.000 m 
+632.160 437.760 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+645.12 439.85 Td
+(GND) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+632.80 442.73 Td
+(12) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+629.280 442.080 m 
+643.680 442.080 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+699.70 439.85 Td
+(GND) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+719.28 442.73 Td
+(11) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+730.080 442.080 m 
+715.680 442.080 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+700.42 432.65 Td
+(VCC) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+719.28 435.53 Td
+(10) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+730.080 434.880 m 
+715.680 434.880 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+698.96 425.45 Td
+(DIO7) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+719.28 428.33 Td
+(9) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+730.080 427.680 m 
+715.680 427.680 l
+S
+1 J
+1 j
+0.72 w
+0.20 0.80 0.20 RG
+[] 0 d
+727.200 430.560 m 
+732.960 424.800 l
+727.200 424.800 m 
+732.960 430.560 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+698.96 418.25 Td
+(DIO8) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+719.28 421.13 Td
+(8) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+730.080 420.480 m 
+715.680 420.480 l
+S
+1 J
+1 j
+0.72 w
+0.20 0.80 0.20 RG
+[] 0 d
+727.200 423.360 m 
+732.960 417.600 l
+727.200 417.600 m 
+732.960 423.360 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+698.96 411.05 Td
+(DIO9) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+719.28 413.93 Td
+(7) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+730.080 413.280 m 
+715.680 413.280 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+696.42 403.85 Td
+(NRST) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+719.28 406.73 Td
+(6) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+730.080 406.080 m 
+715.680 406.080 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+704.79 396.65 Td
+(NC) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+719.28 399.53 Td
+(5) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+730.080 398.880 m 
+715.680 398.880 l
+S
+1 J
+1 j
+0.72 w
+0.20 0.80 0.20 RG
+[] 0 d
+727.200 401.760 m 
+732.960 396.000 l
+727.200 396.000 m 
+732.960 401.760 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+699.70 389.45 Td
+(GND) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+719.28 392.33 Td
+(4) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+730.080 391.680 m 
+715.680 391.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+699.70 375.05 Td
+(GND) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+719.28 377.93 Td
+(3) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+730.080 377.280 m 
+715.680 377.280 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+686.59 367.85 Td
+(ANT_900) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+719.28 370.73 Td
+(2) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+730.080 370.080 m 
+715.680 370.080 l
+S
+1 J
+1 j
+0.72 w
+0.20 0.80 0.20 RG
+[] 0 d
+727.200 372.960 m 
+732.960 367.200 l
+727.200 367.200 m 
+732.960 372.960 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+699.70 360.65 Td
+(GND) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+719.28 363.53 Td
+(1) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+730.080 362.880 m 
+715.680 362.880 l
+S
+2 J
+0 j
+72 M
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+643.68 449.28 72.00 -100.80 re
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 0.502 rg
+675.33 458.49 Td
+(E80-900M2213S) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+675.33 452.01 Td
+(E80-900M2213S) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+629.280 427.680 m 
+625.680 424.080 l
+614.880 424.080 l
+614.880 431.280 l
+625.680 431.280 l
+629.280 427.680 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+629.280 427.680 m 
+629.280 427.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+596.71 424.81 Td
+(MISO) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+629.280 413.280 m 
+625.680 409.680 l
+614.880 409.680 l
+614.880 416.880 l
+625.680 416.880 l
+629.280 413.280 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+629.280 413.280 m 
+629.280 413.280 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+599.98 410.41 Td
+(SCK) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+629.280 420.480 m 
+625.680 416.880 l
+614.880 416.880 l
+614.880 424.080 l
+625.680 424.080 l
+629.280 420.480 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+629.280 420.480 m 
+629.280 420.480 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+596.71 417.61 Td
+(MOSI) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+629.280 406.080 m 
+625.680 402.480 l
+614.880 402.480 l
+614.880 409.680 l
+625.680 409.680 l
+629.280 406.080 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+629.280 406.080 m 
+629.280 406.080 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+604.35 403.21 Td
+(CS) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+629.280 398.880 m 
+625.680 395.280 l
+614.880 395.280 l
+614.880 402.480 l
+625.680 402.480 l
+629.280 398.880 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+629.280 398.880 m 
+629.280 398.880 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+595.62 396.01 Td
+(BUSY) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+730.080 413.280 m 
+733.680 416.880 l
+744.480 416.880 l
+744.480 409.680 l
+733.680 409.680 l
+730.080 413.280 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+730.080 413.280 m 
+730.080 413.280 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+745.92 410.56 Td
+(IRQ) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+730.080 406.080 m 
+733.680 409.680 l
+744.480 409.680 l
+744.480 402.480 l
+733.680 402.480 l
+730.080 406.080 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+730.080 406.080 m 
+730.080 406.080 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+746.43 403.13 Td
+(NRST) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+737.280 748.080 m 
+730.080 748.080 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+737.280 751.680 m 
+737.280 744.480 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+730.080 748.080 m 
+730.080 748.080 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+737.64 744.67 Td
+(+5V) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+114.480 726.480 m 
+114.480 733.680 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+108.000 726.480 m 
+120.960 726.480 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+110.160 725.040 m 
+118.800 725.040 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+112.320 723.600 m 
+116.640 723.600 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+113.760 722.160 m 
+115.200 722.160 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+114.480 733.680 m 
+114.480 733.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+105.12 713.77 Td
+(GND) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+488.880 744.480 m 
+481.680 744.480 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+488.880 748.080 m 
+488.880 740.880 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+481.680 744.480 m 
+481.680 744.480 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+489.24 741.07 Td
+(VCC) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+157.680 755.280 m 
+157.680 748.080 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+154.080 755.280 m 
+161.280 755.280 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+157.680 748.080 m 
+157.680 748.080 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+149.04 756.25 Td
+(VCC) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+730.080 667.440 m 
+730.080 674.640 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+723.600 667.440 m 
+736.560 667.440 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+725.760 666.000 m 
+734.400 666.000 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+727.920 664.560 m 
+732.240 664.560 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+729.360 663.120 m 
+730.800 663.120 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+730.080 674.640 m 
+730.080 674.640 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+720.72 654.55 Td
+(GND) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+629.280 737.280 m 
+625.680 733.680 l
+614.880 733.680 l
+614.880 740.880 l
+625.680 740.880 l
+629.280 737.280 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+629.280 737.280 m 
+629.280 737.280 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+595.62 734.53 Td
+(NRST) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+629.280 751.680 m 
+625.680 748.080 l
+614.880 748.080 l
+614.880 755.280 l
+625.680 755.280 l
+629.280 751.680 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+629.280 751.680 m 
+629.280 751.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+601.80 748.81 Td
+(IRQ) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+730.080 737.280 m 
+733.680 740.880 l
+744.480 740.880 l
+744.480 733.680 l
+733.680 733.680 l
+730.080 737.280 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+730.080 737.280 m 
+730.080 737.280 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+745.93 734.23 Td
+(DIO2) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+629.280 708.480 m 
+625.680 704.880 l
+614.880 704.880 l
+614.880 712.080 l
+625.680 712.080 l
+629.280 708.480 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+629.280 708.480 m 
+629.280 708.480 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+604.35 705.61 Td
+(CS) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+629.280 722.880 m 
+625.680 719.280 l
+614.880 719.280 l
+614.880 726.480 l
+625.680 726.480 l
+629.280 722.880 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+629.280 722.880 m 
+629.280 722.880 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+596.71 720.01 Td
+(MOSI) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+629.280 730.080 m 
+625.680 726.480 l
+614.880 726.480 l
+614.880 733.680 l
+625.680 733.680 l
+629.280 730.080 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+629.280 730.080 m 
+629.280 730.080 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+596.71 727.21 Td
+(MISO) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+629.280 715.680 m 
+625.680 712.080 l
+614.880 712.080 l
+614.880 719.280 l
+625.680 719.280 l
+629.280 715.680 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+629.280 715.680 m 
+629.280 715.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+599.98 712.81 Td
+(SCK) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+730.080 722.880 m 
+733.680 726.480 l
+744.480 726.480 l
+744.480 719.280 l
+733.680 719.280 l
+730.080 722.880 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+730.080 722.880 m 
+730.080 722.880 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+745.71 720.16 Td
+(RXEN) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+629.280 744.480 m 
+625.680 740.880 l
+614.880 740.880 l
+614.880 748.080 l
+625.680 748.080 l
+629.280 744.480 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+629.280 744.480 m 
+629.280 744.480 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+595.62 741.61 Td
+(BUSY) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+622.080 694.080 m 
+629.280 694.080 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+622.080 700.560 m 
+622.080 687.600 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+620.640 698.400 m 
+620.640 689.760 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+619.200 696.240 m 
+619.200 691.920 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+617.760 694.800 m 
+617.760 693.360 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+629.280 694.080 m 
+629.280 694.080 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+598.68 690.67 Td
+(GND) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+622.080 679.680 m 
+629.280 679.680 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+622.080 686.160 m 
+622.080 673.200 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+620.640 684.000 m 
+620.640 675.360 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+619.200 681.840 m 
+619.200 677.520 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+617.760 680.400 m 
+617.760 678.960 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+629.280 679.680 m 
+629.280 679.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+598.68 676.27 Td
+(GND) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+730.080 766.080 m 
+730.080 758.880 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+736.560 766.080 m 
+723.600 766.080 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+734.400 767.520 m 
+725.760 767.520 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+732.240 768.960 m 
+727.920 768.960 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+730.800 770.400 m 
+729.360 770.400 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+730.080 758.880 m 
+730.080 758.880 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+720.72 772.15 Td
+(GND) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+622.080 758.880 m 
+629.280 758.880 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+622.080 765.360 m 
+622.080 752.400 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+620.640 763.200 m 
+620.640 754.560 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+619.200 761.040 m 
+619.200 756.720 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+617.760 759.600 m 
+617.760 758.160 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+629.280 758.880 m 
+629.280 758.880 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+598.68 755.47 Td
+(GND) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+150.480 740.880 m 
+157.680 740.880 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+150.480 747.360 m 
+150.480 734.400 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+149.040 745.200 m 
+149.040 736.560 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+147.600 743.040 m 
+147.600 738.720 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+146.160 741.600 m 
+146.160 740.160 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+157.680 740.880 m 
+157.680 740.880 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+127.08 737.47 Td
+(GND) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+150.480 704.880 m 
+157.680 704.880 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+150.480 711.360 m 
+150.480 698.400 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+149.040 709.200 m 
+149.040 700.560 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+147.600 707.040 m 
+147.600 702.720 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+146.160 705.600 m 
+146.160 704.160 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+157.680 704.880 m 
+157.680 704.880 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+127.08 701.47 Td
+(GND) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+229.680 719.280 m 
+222.480 719.280 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+229.680 712.800 m 
+229.680 725.760 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+231.120 714.960 m 
+231.120 723.600 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+232.560 717.120 m 
+232.560 721.440 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+234.000 718.560 m 
+234.000 720.000 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+222.480 719.280 m 
+222.480 719.280 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+234.36 715.87 Td
+(GND) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+373.680 758.880 m 
+380.880 758.880 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+373.680 765.360 m 
+373.680 752.400 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+372.240 763.200 m 
+372.240 754.560 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+370.800 761.040 m 
+370.800 756.720 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+369.360 759.600 m 
+369.360 758.160 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+380.880 758.880 m 
+380.880 758.880 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+350.28 755.47 Td
+(GND) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+481.680 766.080 m 
+481.680 758.880 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+488.160 766.080 m 
+475.200 766.080 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+486.000 767.520 m 
+477.360 767.520 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+483.840 768.960 m 
+479.520 768.960 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+482.400 770.400 m 
+480.960 770.400 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+481.680 758.880 m 
+481.680 758.880 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+472.32 772.18 Td
+(GND) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+481.680 672.480 m 
+481.680 679.680 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+475.200 672.480 m 
+488.160 672.480 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+477.360 671.040 m 
+486.000 671.040 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+479.520 669.600 m 
+483.840 669.600 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+480.960 668.160 m 
+482.400 668.160 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+481.680 679.680 m 
+481.680 679.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+472.32 659.59 Td
+(GND) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+373.680 679.680 m 
+380.880 679.680 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+373.680 686.160 m 
+373.680 673.200 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+372.240 684.000 m 
+372.240 675.360 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+370.800 681.840 m 
+370.800 677.520 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+369.360 680.400 m 
+369.360 678.960 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+380.880 679.680 m 
+380.880 679.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+350.28 676.27 Td
+(GND) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+373.680 694.080 m 
+380.880 694.080 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+373.680 700.560 m 
+373.680 687.600 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+372.240 698.400 m 
+372.240 689.760 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+370.800 696.240 m 
+370.800 691.920 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+369.360 694.800 m 
+369.360 693.360 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+380.880 694.080 m 
+380.880 694.080 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+350.28 690.67 Td
+(GND) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+380.880 744.480 m 
+377.280 740.880 l
+366.480 740.880 l
+366.480 748.080 l
+377.280 748.080 l
+380.880 744.480 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+380.880 744.480 m 
+380.880 744.480 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+347.22 741.61 Td
+(BUSY) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+481.680 722.880 m 
+485.280 726.480 l
+496.080 726.480 l
+496.080 719.280 l
+485.280 719.280 l
+481.680 722.880 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+481.680 722.880 m 
+481.680 722.880 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+497.31 720.16 Td
+(RXEN) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+380.880 715.680 m 
+377.280 712.080 l
+366.480 712.080 l
+366.480 719.280 l
+377.280 719.280 l
+380.880 715.680 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+380.880 715.680 m 
+380.880 715.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+351.58 712.81 Td
+(SCK) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+380.880 730.080 m 
+377.280 726.480 l
+366.480 726.480 l
+366.480 733.680 l
+377.280 733.680 l
+380.880 730.080 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+380.880 730.080 m 
+380.880 730.080 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+348.31 727.21 Td
+(MISO) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+380.880 722.880 m 
+377.280 719.280 l
+366.480 719.280 l
+366.480 726.480 l
+377.280 726.480 l
+380.880 722.880 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+380.880 722.880 m 
+380.880 722.880 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+348.31 720.01 Td
+(MOSI) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+380.880 708.480 m 
+377.280 704.880 l
+366.480 704.880 l
+366.480 712.080 l
+377.280 712.080 l
+380.880 708.480 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+380.880 708.480 m 
+380.880 708.480 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+355.95 705.61 Td
+(CS) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+481.680 737.280 m 
+485.280 740.880 l
+496.080 740.880 l
+496.080 733.680 l
+485.280 733.680 l
+481.680 737.280 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+481.680 737.280 m 
+481.680 737.280 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+497.53 734.23 Td
+(DIO2) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+380.880 751.680 m 
+377.280 748.080 l
+366.480 748.080 l
+366.480 755.280 l
+377.280 755.280 l
+380.880 751.680 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+380.880 751.680 m 
+380.880 751.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+353.40 748.81 Td
+(IRQ) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+380.880 737.280 m 
+377.280 733.680 l
+366.480 733.680 l
+366.480 740.880 l
+377.280 740.880 l
+380.880 737.280 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+380.880 737.280 m 
+380.880 737.280 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+347.22 734.53 Td
+(NRST) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+222.480 733.680 m 
+226.080 737.280 l
+236.880 737.280 l
+236.880 730.080 l
+226.080 730.080 l
+222.480 733.680 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+222.480 733.680 m 
+222.480 733.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+238.33 730.63 Td
+(DIO3) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+157.680 733.680 m 
+154.080 730.080 l
+143.280 730.080 l
+143.280 737.280 l
+154.080 737.280 l
+157.680 733.680 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+157.680 733.680 m 
+157.680 733.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+124.02 730.93 Td
+(NRST) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+222.480 748.080 m 
+226.080 751.680 l
+236.880 751.680 l
+236.880 744.480 l
+226.080 744.480 l
+222.480 748.080 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+222.480 748.080 m 
+222.480 748.080 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+238.32 745.36 Td
+(IRQ) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+222.480 740.880 m 
+226.080 744.480 l
+236.880 744.480 l
+236.880 737.280 l
+226.080 737.280 l
+222.480 740.880 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+222.480 740.880 m 
+222.480 740.880 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+238.33 737.83 Td
+(DIO2) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+222.480 704.880 m 
+226.080 708.480 l
+236.880 708.480 l
+236.880 701.280 l
+226.080 701.280 l
+222.480 704.880 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+222.480 704.880 m 
+222.480 704.880 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+238.32 702.16 Td
+(CS) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+222.480 697.680 m 
+226.080 701.280 l
+236.880 701.280 l
+236.880 694.080 l
+226.080 694.080 l
+222.480 697.680 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+222.480 697.680 m 
+222.480 697.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+238.32 694.96 Td
+(MOSI) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+222.480 690.480 m 
+226.080 694.080 l
+236.880 694.080 l
+236.880 686.880 l
+226.080 686.880 l
+222.480 690.480 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+222.480 690.480 m 
+222.480 690.480 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+238.32 687.76 Td
+(MISO) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+222.480 712.080 m 
+226.080 715.680 l
+236.880 715.680 l
+236.880 708.480 l
+226.080 708.480 l
+222.480 712.080 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+222.480 712.080 m 
+222.480 712.080 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+238.11 709.36 Td
+(SCK) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+157.680 683.280 m 
+154.080 679.680 l
+143.280 679.680 l
+143.280 686.880 l
+154.080 686.880 l
+157.680 683.280 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+157.680 683.280 m 
+157.680 683.280 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+123.66 680.41 Td
+(RXEN) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+222.480 683.280 m 
+226.080 686.880 l
+236.880 686.880 l
+236.880 679.680 l
+226.080 679.680 l
+222.480 683.280 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+222.480 683.280 m 
+222.480 683.280 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+238.10 680.56 Td
+(BUSY) Tj
+ET
+2 J
+0 j
+72 M
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+164.88 755.28 50.40 -79.20 re
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+169.56 751.68 m 169.56 752.28 169.08 752.76 168.48 752.76 c
+167.88 752.76 167.40 752.28 167.40 751.68 c
+167.40 751.08 167.88 750.60 168.48 750.60 c
+169.08 750.60 169.56 751.08 169.56 751.68 c
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+167.54 745.13 Td
+(VCC) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+160.88 748.73 Td
+(1) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+157.680 748.080 m 
+164.880 748.080 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+167.54 737.93 Td
+(GND) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+160.88 741.53 Td
+(2) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+157.680 740.880 m 
+164.880 740.880 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+167.54 730.73 Td
+(NRST) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+160.88 734.33 Td
+(3) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+157.680 733.680 m 
+164.880 733.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+167.54 723.53 Td
+(NC) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+160.88 727.13 Td
+(4) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+157.680 726.480 m 
+164.880 726.480 l
+S
+1 J
+1 j
+0.72 w
+0.20 0.80 0.20 RG
+[] 0 d
+154.800 729.360 m 
+160.560 723.600 l
+154.800 723.600 m 
+160.560 729.360 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+167.54 716.33 Td
+(NC) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+160.88 719.93 Td
+(5) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+157.680 719.280 m 
+164.880 719.280 l
+S
+1 J
+1 j
+0.72 w
+0.20 0.80 0.20 RG
+[] 0 d
+154.800 722.160 m 
+160.560 716.400 l
+154.800 716.400 m 
+160.560 722.160 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+167.54 709.13 Td
+(ANT) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+160.88 712.73 Td
+(6) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+157.680 712.080 m 
+164.880 712.080 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+167.54 701.93 Td
+(GND) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+160.88 705.53 Td
+(7) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+157.680 704.880 m 
+164.880 704.880 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+167.54 694.73 Td
+(NC) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+160.88 698.33 Td
+(8) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+157.680 697.680 m 
+164.880 697.680 l
+S
+1 J
+1 j
+0.72 w
+0.20 0.80 0.20 RG
+[] 0 d
+154.800 700.560 m 
+160.560 694.800 l
+154.800 694.800 m 
+160.560 700.560 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+167.54 687.53 Td
+(TXEN) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+160.88 691.13 Td
+(9) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+157.680 690.480 m 
+164.880 690.480 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+167.54 680.33 Td
+(RXEN) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+157.24 683.93 Td
+(10) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+157.680 683.280 m 
+164.880 683.280 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+194.79 680.33 Td
+(BUSY) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+215.64 683.93 Td
+(11) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+222.480 683.280 m 
+215.280 683.280 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+195.89 687.53 Td
+(MISO) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+215.64 691.13 Td
+(12) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+222.480 690.480 m 
+215.280 690.480 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+195.89 694.73 Td
+(MOSI) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+215.64 698.33 Td
+(13) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+222.480 697.680 m 
+215.280 697.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+199.16 701.93 Td
+(NSS) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+215.64 705.53 Td
+(14) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+222.480 704.880 m 
+215.280 704.880 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+199.16 709.13 Td
+(SCK) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+215.64 712.73 Td
+(15) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+222.480 712.080 m 
+215.280 712.080 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+198.07 716.33 Td
+(GND) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+215.64 719.93 Td
+(16) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+222.480 719.280 m 
+215.280 719.280 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+203.16 723.53 Td
+(NC) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+215.64 727.13 Td
+(17) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+222.480 726.480 m 
+215.280 726.480 l
+S
+1 J
+1 j
+0.72 w
+0.20 0.80 0.20 RG
+[] 0 d
+219.600 729.360 m 
+225.360 723.600 l
+219.600 723.600 m 
+225.360 729.360 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+197.34 730.73 Td
+(DIO3) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+215.64 734.33 Td
+(18) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+222.480 733.680 m 
+215.280 733.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+197.34 737.93 Td
+(DIO2) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+215.64 741.53 Td
+(19) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+222.480 740.880 m 
+215.280 740.880 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+197.34 745.13 Td
+(DIO1) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+215.64 748.73 Td
+(20) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+222.480 748.080 m 
+215.280 748.080 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 0.502 rg
+185.75 764.49 Td
+(E22-900MM22S) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+185.75 758.01 Td
+(E22-400MM22S) Tj
+ET
+2 J
+0 j
+72 M
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+643.68 766.08 72.00 -100.80 re
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+699.70 677.45 Td
+(GND) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+719.28 680.33 Td
+(1) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+730.080 679.680 m 
+715.680 679.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+699.70 684.65 Td
+(GND) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+719.28 687.53 Td
+(2) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+730.080 686.880 m 
+715.680 686.880 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+699.70 691.85 Td
+(GND) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+719.28 694.73 Td
+(3) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+730.080 694.080 m 
+715.680 694.080 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+699.70 706.25 Td
+(GND) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+719.28 709.13 Td
+(4) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+730.080 708.480 m 
+715.680 708.480 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+699.70 713.45 Td
+(GND) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+719.28 716.33 Td
+(5) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+730.080 715.680 m 
+715.680 715.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+696.06 720.65 Td
+(RXEN) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+719.28 723.53 Td
+(6) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+730.080 722.880 m 
+715.680 722.880 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+696.78 727.85 Td
+(TXEN) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+719.28 730.73 Td
+(7) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+730.080 730.080 m 
+715.680 730.080 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+698.96 735.05 Td
+(DIO2) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+719.28 737.93 Td
+(8) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+730.080 737.280 m 
+715.680 737.280 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+700.42 742.25 Td
+(VCC) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+719.28 745.13 Td
+(9) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+730.080 744.480 m 
+715.680 744.480 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+700.42 749.45 Td
+(VCC) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+719.28 752.33 Td
+(10) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+730.080 751.680 m 
+715.680 751.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+699.70 756.65 Td
+(GND) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+719.28 759.53 Td
+(11) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+730.080 758.880 m 
+715.680 758.880 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+645.12 756.65 Td
+(GND) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+632.80 759.53 Td
+(12) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+629.280 758.880 m 
+643.680 758.880 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+645.12 749.45 Td
+(DIO1) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+632.80 752.33 Td
+(13) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+629.280 751.680 m 
+643.680 751.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+645.12 742.25 Td
+(BUSY) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+632.80 745.13 Td
+(14) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+629.280 744.480 m 
+643.680 744.480 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+645.12 735.05 Td
+(NRST) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+632.80 737.93 Td
+(15) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+629.280 737.280 m 
+643.680 737.280 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+645.12 727.85 Td
+(MISO) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+632.80 730.73 Td
+(16) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+629.280 730.080 m 
+643.680 730.080 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+645.12 720.65 Td
+(MOSI) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+632.80 723.53 Td
+(17) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+629.280 722.880 m 
+643.680 722.880 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+645.12 713.45 Td
+(SCK) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+632.80 716.33 Td
+(18) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+629.280 715.680 m 
+643.680 715.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+645.12 706.25 Td
+(NSS) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+632.80 709.13 Td
+(19) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+629.280 708.480 m 
+643.680 708.480 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+645.12 691.85 Td
+(GND) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+632.80 694.73 Td
+(20) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+629.280 694.080 m 
+643.680 694.080 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+645.12 684.65 Td
+(ANT) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+632.80 687.53 Td
+(21) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+629.280 686.880 m 
+643.680 686.880 l
+S
+1 J
+1 j
+0.72 w
+0.20 0.80 0.20 RG
+[] 0 d
+626.400 689.760 m 
+632.160 684.000 l
+626.400 684.000 m 
+632.160 689.760 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+645.12 677.45 Td
+(GND) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+632.80 680.33 Td
+(22) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+629.280 679.680 m 
+643.680 679.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 0.502 rg
+675.33 775.29 Td
+(E22-900M30S) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+675.33 768.81 Td
+(E22-900M30S) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+396.72 677.45 Td
+(GND) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+384.40 680.33 Td
+(22) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+380.880 679.680 m 
+395.280 679.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+396.72 684.65 Td
+(ANT) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+384.40 687.53 Td
+(21) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+380.880 686.880 m 
+395.280 686.880 l
+S
+1 J
+1 j
+0.72 w
+0.20 0.80 0.20 RG
+[] 0 d
+378.000 689.760 m 
+383.760 684.000 l
+378.000 684.000 m 
+383.760 689.760 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+396.72 691.85 Td
+(GND) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+384.40 694.73 Td
+(20) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+380.880 694.080 m 
+395.280 694.080 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+396.72 706.25 Td
+(NSS) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+384.40 709.13 Td
+(19) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+380.880 708.480 m 
+395.280 708.480 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+396.72 713.45 Td
+(SCK) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+384.40 716.33 Td
+(18) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+380.880 715.680 m 
+395.280 715.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+396.72 720.65 Td
+(MOSI) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+384.40 723.53 Td
+(17) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+380.880 722.880 m 
+395.280 722.880 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+396.72 727.85 Td
+(MISO) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+384.40 730.73 Td
+(16) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+380.880 730.080 m 
+395.280 730.080 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+396.72 735.05 Td
+(NRST) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+384.40 737.93 Td
+(15) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+380.880 737.280 m 
+395.280 737.280 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+396.72 742.25 Td
+(BUSY) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+384.40 745.13 Td
+(14) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+380.880 744.480 m 
+395.280 744.480 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+396.72 749.45 Td
+(DIO1) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+384.40 752.33 Td
+(13) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+380.880 751.680 m 
+395.280 751.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+396.72 756.65 Td
+(GND) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+384.40 759.53 Td
+(12) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+380.880 758.880 m 
+395.280 758.880 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+451.30 756.65 Td
+(GND) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+470.88 759.53 Td
+(11) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+481.680 758.880 m 
+467.280 758.880 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+451.30 749.45 Td
+(GND) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+470.88 752.33 Td
+(10) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+481.680 751.680 m 
+467.280 751.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+452.02 742.25 Td
+(VCC) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+470.88 745.13 Td
+(9) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+481.680 744.480 m 
+467.280 744.480 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+450.56 735.05 Td
+(DIO2) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+470.88 737.93 Td
+(8) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+481.680 737.280 m 
+467.280 737.280 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+448.38 727.85 Td
+(TXEN) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+470.88 730.73 Td
+(7) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+481.680 730.080 m 
+467.280 730.080 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+447.66 720.65 Td
+(RXEN) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+470.88 723.53 Td
+(6) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+481.680 722.880 m 
+467.280 722.880 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+451.30 713.45 Td
+(GND) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+470.88 716.33 Td
+(5) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+481.680 715.680 m 
+467.280 715.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+451.30 706.25 Td
+(GND) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+470.88 709.13 Td
+(4) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+481.680 708.480 m 
+467.280 708.480 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+451.30 691.85 Td
+(GND) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+470.88 694.73 Td
+(3) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+481.680 694.080 m 
+467.280 694.080 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+451.30 684.65 Td
+(GND) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+470.88 687.53 Td
+(2) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+481.680 686.880 m 
+467.280 686.880 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+451.30 677.45 Td
+(GND) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+470.88 680.33 Td
+(1) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+481.680 679.680 m 
+467.280 679.680 l
+S
+2 J
+0 j
+72 M
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+395.28 766.08 72.00 -100.80 re
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 0.502 rg
+426.93 775.29 Td
+(E22-900M22S) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+426.93 768.81 Td
+(E22-900M22S) Tj
+ET
+2 J
+0 j
+72 M
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+139.68 899.28 50.40 -64.80 re
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+144.36 895.68 m 144.36 896.28 143.88 896.76 143.28 896.76 c
+142.68 896.76 142.20 896.28 142.20 895.68 c
+142.20 895.08 142.68 894.60 143.28 894.60 c
+143.88 894.60 144.36 895.08 144.36 895.68 c
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+142.34 889.13 Td
+(ANT) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+135.68 892.73 Td
+(1) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+132.480 892.080 m 
+139.680 892.080 l
+S
+1 J
+1 j
+0.72 w
+0.20 0.80 0.20 RG
+[] 0 d
+129.600 894.960 m 
+135.360 889.200 l
+129.600 889.200 m 
+135.360 894.960 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+142.34 881.93 Td
+(GND) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+135.68 885.53 Td
+(2) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+132.480 884.880 m 
+139.680 884.880 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+142.34 874.73 Td
+(3.3V) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+135.68 878.33 Td
+(3) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+132.480 877.680 m 
+139.680 877.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+142.34 867.53 Td
+(RESET) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+135.68 871.13 Td
+(4) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+132.480 870.480 m 
+139.680 870.480 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+142.34 860.33 Td
+(TXEN) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+135.68 863.93 Td
+(5) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+132.480 863.280 m 
+139.680 863.280 l
+S
+1 J
+1 j
+0.72 w
+0.20 0.80 0.20 RG
+[] 0 d
+129.600 866.160 m 
+135.360 860.400 l
+129.600 860.400 m 
+135.360 866.160 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+142.34 853.13 Td
+(DIO1) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+135.68 856.73 Td
+(6) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+132.480 856.080 m 
+139.680 856.080 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+142.34 845.93 Td
+(DIO2) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+135.68 849.53 Td
+(7) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+132.480 848.880 m 
+139.680 848.880 l
+S
+1 J
+1 j
+0.72 w
+0.20 0.80 0.20 RG
+[] 0 d
+129.600 851.760 m 
+135.360 846.000 l
+129.600 846.000 m 
+135.360 851.760 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+142.34 838.73 Td
+(DIO3) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+135.68 842.33 Td
+(8) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+132.480 841.680 m 
+139.680 841.680 l
+S
+1 J
+1 j
+0.72 w
+0.20 0.80 0.20 RG
+[] 0 d
+129.600 844.560 m 
+135.360 838.800 l
+129.600 838.800 m 
+135.360 844.560 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+172.87 838.73 Td
+(GND) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+190.44 842.33 Td
+(9) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+197.280 841.680 m 
+190.080 841.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+169.59 845.93 Td
+(BUSY) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+190.44 849.53 Td
+(10) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+197.280 848.880 m 
+190.080 848.880 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+169.23 853.13 Td
+(RXEN) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+190.44 856.73 Td
+(11) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+197.280 856.080 m 
+190.080 856.080 l
+S
+1 J
+1 j
+0.72 w
+0.20 0.80 0.20 RG
+[] 0 d
+194.400 858.960 m 
+200.160 853.200 l
+194.400 853.200 m 
+200.160 858.960 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+173.96 860.33 Td
+(SCK) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+190.44 863.93 Td
+(12) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+197.280 863.280 m 
+190.080 863.280 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+170.69 867.53 Td
+(MISO) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+190.44 871.13 Td
+(13) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+197.280 870.480 m 
+190.080 870.480 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+170.69 874.73 Td
+(MOSI) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+190.44 878.33 Td
+(14) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+197.280 877.680 m 
+190.080 877.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+173.96 881.93 Td
+(NSS) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+190.44 885.53 Td
+(15) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+197.280 884.880 m 
+190.080 884.880 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+172.87 889.13 Td
+(GND) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+190.44 892.73 Td
+(16) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+197.280 892.080 m 
+190.080 892.080 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 0.502 rg
+161.24 908.49 Td
+(HT-RA62) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+161.24 902.01 Td
+(RA-01SH) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+132.480 870.480 m 
+128.880 866.880 l
+118.080 866.880 l
+118.080 874.080 l
+128.880 874.080 l
+132.480 870.480 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+132.480 870.480 m 
+132.480 870.480 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+98.82 867.73 Td
+(NRST) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+132.480 856.080 m 
+128.880 852.480 l
+118.080 852.480 l
+118.080 859.680 l
+128.880 859.680 l
+132.480 856.080 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+132.480 856.080 m 
+132.480 856.080 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+105.00 853.21 Td
+(IRQ) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+197.280 884.880 m 
+200.880 888.480 l
+211.680 888.480 l
+211.680 881.280 l
+200.880 881.280 l
+197.280 884.880 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+197.280 884.880 m 
+197.280 884.880 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+213.12 882.05 Td
+(CS) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+197.280 877.680 m 
+200.880 881.280 l
+211.680 881.280 l
+211.680 874.080 l
+200.880 874.080 l
+197.280 877.680 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+197.280 877.680 m 
+197.280 877.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+213.01 874.63 Td
+(MOSI) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+197.280 870.480 m 
+200.880 874.080 l
+211.680 874.080 l
+211.680 866.880 l
+200.880 866.880 l
+197.280 870.480 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+197.280 870.480 m 
+197.280 870.480 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+213.06 867.43 Td
+(MISO) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+197.280 863.280 m 
+200.880 866.880 l
+211.680 866.880 l
+211.680 859.680 l
+200.880 859.680 l
+197.280 863.280 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+197.280 863.280 m 
+197.280 863.280 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+212.80 860.23 Td
+(SCK) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+197.280 848.880 m 
+200.880 852.480 l
+211.680 852.480 l
+211.680 845.280 l
+200.880 845.280 l
+197.280 848.880 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+197.280 848.880 m 
+197.280 848.880 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+213.06 845.83 Td
+(BUSY) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+197.280 834.480 m 
+197.280 841.680 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+190.800 834.480 m 
+203.760 834.480 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+192.960 833.040 m 
+201.600 833.040 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+195.120 831.600 m 
+199.440 831.600 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+196.560 830.160 m 
+198.000 830.160 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+197.280 841.680 m 
+197.280 841.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+187.92 821.59 Td
+(GND) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+114.480 892.080 m 
+114.480 884.880 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+120.960 892.080 m 
+108.000 892.080 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+118.800 893.520 m 
+110.160 893.520 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+116.640 894.960 m 
+112.320 894.960 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+115.200 896.400 m 
+113.760 896.400 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+114.480 884.880 m 
+114.480 884.880 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+105.12 898.15 Td
+(GND) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+218.880 899.280 m 
+218.880 892.080 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+225.360 899.280 m 
+212.400 899.280 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+223.200 900.720 m 
+214.560 900.720 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+221.040 902.160 m 
+216.720 902.160 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+219.600 903.600 m 
+218.160 903.600 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+218.880 892.080 m 
+218.880 892.080 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+209.52 905.35 Td
+(GND) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+118.080 877.680 m 
+125.280 877.680 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+118.080 874.080 m 
+118.080 881.280 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+125.280 877.680 m 
+125.280 877.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+99.95 874.27 Td
+(VCC) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+385.92 893.45 Td
+(RF_SW) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+377.24 896.33 Td
+(1) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+370.080 895.680 m 
+384.480 895.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+385.92 886.25 Td
+(MISO) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+377.24 889.13 Td
+(2) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+370.080 888.480 m 
+384.480 888.480 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+385.92 879.05 Td
+(MOSI) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+377.24 881.93 Td
+(3) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+370.080 881.280 m 
+384.480 881.280 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+385.92 871.85 Td
+(CLK) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+377.24 874.73 Td
+(4) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+370.080 874.080 m 
+384.480 874.080 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+385.92 864.65 Td
+(RST) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+377.24 867.53 Td
+(5) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+370.080 866.880 m 
+384.480 866.880 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+385.92 857.45 Td
+(NSS) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+377.24 860.33 Td
+(6) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+370.080 859.680 m 
+384.480 859.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+385.92 850.25 Td
+(GND) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+377.24 853.13 Td
+(7) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+370.080 852.480 m 
+384.480 852.480 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+385.92 843.05 Td
+(VCC) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+377.24 845.93 Td
+(8) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+370.080 845.280 m 
+384.480 845.280 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+449.15 864.65 Td
+(ANT) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+467.28 867.53 Td
+(9) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+478.080 866.880 m 
+463.680 866.880 l
+S
+1 J
+1 j
+0.72 w
+0.20 0.80 0.20 RG
+[] 0 d
+475.200 869.760 m 
+480.960 864.000 l
+475.200 864.000 m 
+480.960 869.760 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+447.70 871.85 Td
+(GND) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+467.28 874.73 Td
+(10) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+478.080 874.080 m 
+463.680 874.080 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+444.42 879.05 Td
+(BUSY) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+467.28 881.93 Td
+(11) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+478.080 881.280 m 
+463.680 881.280 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+446.96 886.25 Td
+(DIO1) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+467.28 889.13 Td
+(12) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+478.080 888.480 m 
+463.680 888.480 l
+S
+2 J
+0 j
+72 M
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+384.48 910.08 79.20 -79.20 re
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+460.08 845.28 m 460.08 851.24 455.24 856.08 449.28 856.08 c
+443.32 856.08 438.48 851.24 438.48 845.28 c
+438.48 839.32 443.32 834.48 449.28 834.48 c
+455.24 834.48 460.08 839.32 460.08 845.28 c
+S
+2 J
+0 j
+72 M
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+434.88 859.68 28.80 -28.80 re
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 0.502 rg
+419.73 919.05 Td
+(SEEED_WIO-SX1262) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+419.73 912.57 Td
+(Seeed-wio-SX1262) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+478.080 881.280 m 
+481.680 884.880 l
+492.480 884.880 l
+492.480 877.680 l
+481.680 877.680 l
+478.080 881.280 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+478.080 881.280 m 
+478.080 881.280 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+493.86 878.23 Td
+(BUSY) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+370.080 866.880 m 
+366.480 863.280 l
+355.680 863.280 l
+355.680 870.480 l
+366.480 870.480 l
+370.080 866.880 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+370.080 866.880 m 
+370.080 866.880 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+336.42 864.13 Td
+(NRST) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+370.080 888.480 m 
+366.480 884.880 l
+355.680 884.880 l
+355.680 892.080 l
+366.480 892.080 l
+370.080 888.480 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+370.080 888.480 m 
+370.080 888.480 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+335.10 885.86 Td
+(MISO) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+370.080 881.280 m 
+366.480 877.680 l
+355.680 877.680 l
+355.680 884.880 l
+366.480 884.880 l
+370.080 881.280 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+370.080 881.280 m 
+370.080 881.280 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+335.15 878.66 Td
+(MOSI) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+370.080 859.680 m 
+366.480 856.080 l
+355.680 856.080 l
+355.680 863.280 l
+366.480 863.280 l
+370.080 859.680 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+370.080 859.680 m 
+370.080 859.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+345.15 856.85 Td
+(CS) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+370.080 874.080 m 
+366.480 870.480 l
+355.680 870.480 l
+355.680 877.680 l
+366.480 877.680 l
+370.080 874.080 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+370.080 874.080 m 
+370.080 874.080 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+339.30 871.46 Td
+(SCK) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+478.080 888.480 m 
+481.680 892.080 l
+492.480 892.080 l
+492.480 884.880 l
+481.680 884.880 l
+478.080 888.480 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+478.080 888.480 m 
+478.080 888.480 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+493.88 885.43 Td
+(IRQ) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+485.280 874.080 m 
+478.080 874.080 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+485.280 867.600 m 
+485.280 880.560 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+486.720 869.760 m 
+486.720 878.400 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+488.160 871.920 m 
+488.160 876.240 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+489.600 873.360 m 
+489.600 874.800 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+478.080 874.080 m 
+478.080 874.080 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+489.96 870.73 Td
+(GND) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+362.880 852.480 m 
+370.080 852.480 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+362.880 858.960 m 
+362.880 846.000 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+361.440 856.800 m 
+361.440 848.160 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+360.000 854.640 m 
+360.000 850.320 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+358.560 853.200 m 
+358.560 851.760 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+370.080 852.480 m 
+370.080 852.480 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+339.48 849.13 Td
+(GND) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+362.880 845.280 m 
+370.080 845.280 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+362.880 841.680 m 
+362.880 848.880 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+370.080 845.280 m 
+370.080 845.280 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+344.75 841.93 Td
+(VCC) Tj
+ET
+2 J
+0 j
+72 M
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+92.88 740.88 14.40 -14.40 re
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+0.00 1.00 -1.00 0.00 98.05 717.86 Tm
+(1) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+100.080 712.080 m 
+100.080 726.480 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+89.24 728.45 Td
+(2) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+85.680 733.680 m 
+92.880 733.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+107.28 728.45 Td
+(3) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+114.480 733.680 m 
+107.280 733.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+0.00 1.00 -1.00 0.00 98.05 739.50 Tm
+(4) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+100.080 748.080 m 
+100.080 740.880 l
+S
+1 J
+1 j
+0.72 w
+0.20 0.80 0.20 RG
+[] 0 d
+97.200 750.960 m 
+102.960 745.200 l
+97.200 745.200 m 
+102.960 750.960 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+101.52 733.68 m 101.52 734.48 100.88 735.12 100.08 735.12 c
+99.28 735.12 98.64 734.48 98.64 733.68 c
+98.64 732.88 99.28 732.24 100.08 732.24 c
+100.88 732.24 101.52 732.88 101.52 733.68 c
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+100.080 732.240 m 
+100.080 726.480 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 0.502 rg
+84.92 757.88 Td
+(U3) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+84.92 751.33 Td
+(AMC-U_FL) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+85.680 726.480 m 
+85.680 733.680 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+79.200 726.480 m 
+92.160 726.480 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+81.360 725.040 m 
+90.000 725.040 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+83.520 723.600 m 
+87.840 723.600 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+84.960 722.160 m 
+86.400 722.160 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+85.680 733.680 m 
+85.680 733.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+76.32 713.77 Td
+(GND) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+737.280 568.080 m 
+730.080 568.080 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+737.280 571.680 m 
+737.280 564.480 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+730.080 568.080 m 
+730.080 568.080 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+738.00 564.73 Td
+(VCC) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+730.080 575.280 m 
+733.680 578.880 l
+744.480 578.880 l
+744.480 571.680 l
+733.680 571.680 l
+730.080 575.280 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+730.080 575.280 m 
+730.080 575.280 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+745.93 572.23 Td
+(DIO2) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+737.280 593.280 m 
+730.080 593.280 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+737.280 596.880 m 
+737.280 589.680 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+730.080 593.280 m 
+730.080 593.280 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+737.64 589.87 Td
+(+5V) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+730.080 514.080 m 
+730.080 521.280 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+723.600 514.080 m 
+736.560 514.080 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+725.760 512.640 m 
+734.400 512.640 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+727.920 511.200 m 
+732.240 511.200 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+729.360 509.760 m 
+730.800 509.760 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+730.080 521.280 m 
+730.080 521.280 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+720.72 501.19 Td
+(GND) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+629.280 582.480 m 
+625.680 578.880 l
+614.880 578.880 l
+614.880 586.080 l
+625.680 586.080 l
+629.280 582.480 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+629.280 582.480 m 
+629.280 582.480 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+595.62 579.73 Td
+(NRST) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+629.280 596.880 m 
+625.680 593.280 l
+614.880 593.280 l
+614.880 600.480 l
+625.680 600.480 l
+629.280 596.880 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+629.280 596.880 m 
+629.280 596.880 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+601.80 594.01 Td
+(IRQ) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+730.080 582.480 m 
+733.680 586.080 l
+744.480 586.080 l
+744.480 578.880 l
+733.680 578.880 l
+730.080 582.480 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+730.080 582.480 m 
+730.080 582.480 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+745.93 579.43 Td
+(DIO2) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+629.280 553.680 m 
+625.680 550.080 l
+614.880 550.080 l
+614.880 557.280 l
+625.680 557.280 l
+629.280 553.680 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+629.280 553.680 m 
+629.280 553.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+604.35 550.81 Td
+(CS) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+629.280 568.080 m 
+625.680 564.480 l
+614.880 564.480 l
+614.880 571.680 l
+625.680 571.680 l
+629.280 568.080 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+629.280 568.080 m 
+629.280 568.080 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+596.71 565.21 Td
+(MOSI) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+629.280 575.280 m 
+625.680 571.680 l
+614.880 571.680 l
+614.880 578.880 l
+625.680 578.880 l
+629.280 575.280 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+629.280 575.280 m 
+629.280 575.280 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+596.71 572.41 Td
+(MISO) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+629.280 560.880 m 
+625.680 557.280 l
+614.880 557.280 l
+614.880 564.480 l
+625.680 564.480 l
+629.280 560.880 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+629.280 560.880 m 
+629.280 560.880 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+599.98 558.01 Td
+(SCK) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+629.280 589.680 m 
+625.680 586.080 l
+614.880 586.080 l
+614.880 593.280 l
+625.680 593.280 l
+629.280 589.680 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+629.280 589.680 m 
+629.280 589.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+595.62 586.81 Td
+(BUSY) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+622.080 539.280 m 
+629.280 539.280 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+622.080 545.760 m 
+622.080 532.800 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+620.640 543.600 m 
+620.640 534.960 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+619.200 541.440 m 
+619.200 537.120 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+617.760 540.000 m 
+617.760 538.560 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+629.280 539.280 m 
+629.280 539.280 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+598.68 535.87 Td
+(GND) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+622.080 524.880 m 
+629.280 524.880 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+622.080 531.360 m 
+622.080 518.400 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+620.640 529.200 m 
+620.640 520.560 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+619.200 527.040 m 
+619.200 522.720 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+617.760 525.600 m 
+617.760 524.160 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+629.280 524.880 m 
+629.280 524.880 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+598.68 521.47 Td
+(GND) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+730.080 611.280 m 
+730.080 604.080 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+736.560 611.280 m 
+723.600 611.280 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+734.400 612.720 m 
+725.760 612.720 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+732.240 614.160 m 
+727.920 614.160 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+730.800 615.600 m 
+729.360 615.600 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+730.080 604.080 m 
+730.080 604.080 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+720.72 617.35 Td
+(GND) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+622.080 604.080 m 
+629.280 604.080 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+622.080 610.560 m 
+622.080 597.600 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+620.640 608.400 m 
+620.640 599.760 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+619.200 606.240 m 
+619.200 601.920 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+617.760 604.800 m 
+617.760 603.360 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+629.280 604.080 m 
+629.280 604.080 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+598.68 600.67 Td
+(GND) Tj
+ET
+2 J
+0 j
+72 M
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+643.68 611.28 72.00 -100.80 re
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+699.70 522.65 Td
+(GND) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+719.28 525.53 Td
+(1) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+730.080 524.880 m 
+715.680 524.880 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+699.70 529.85 Td
+(GND) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+719.28 532.73 Td
+(2) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+730.080 532.080 m 
+715.680 532.080 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+699.70 537.05 Td
+(GND) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+719.28 539.93 Td
+(3) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+730.080 539.280 m 
+715.680 539.280 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+699.70 551.45 Td
+(GND) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+719.28 554.33 Td
+(4) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+730.080 553.680 m 
+715.680 553.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+699.70 558.65 Td
+(GND) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+719.28 561.53 Td
+(5) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+730.080 560.880 m 
+715.680 560.880 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+707.69 565.85 Td
+(IN) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+719.28 568.73 Td
+(6) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+730.080 568.080 m 
+715.680 568.080 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+684.79 573.05 Td
+(T/R CTRL) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+719.28 575.93 Td
+(7) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+730.080 575.280 m 
+715.680 575.280 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+698.96 580.25 Td
+(DIO2) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+719.28 583.13 Td
+(8) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+730.080 582.480 m 
+715.680 582.480 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+700.42 587.45 Td
+(VCC) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+719.28 590.33 Td
+(9) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+730.080 589.680 m 
+715.680 589.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+700.42 594.65 Td
+(VCC) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+719.28 597.53 Td
+(10) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+730.080 596.880 m 
+715.680 596.880 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+699.70 601.85 Td
+(GND) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+719.28 604.73 Td
+(11) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+730.080 604.080 m 
+715.680 604.080 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+645.12 601.85 Td
+(GND) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+632.80 604.73 Td
+(12) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+629.280 604.080 m 
+643.680 604.080 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+645.12 594.65 Td
+(DIO1) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+632.80 597.53 Td
+(13) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+629.280 596.880 m 
+643.680 596.880 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+645.12 587.45 Td
+(BUSY) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+632.80 590.33 Td
+(14) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+629.280 589.680 m 
+643.680 589.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+645.12 580.25 Td
+(NRST) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+632.80 583.13 Td
+(15) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+629.280 582.480 m 
+643.680 582.480 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+645.12 573.05 Td
+(MISO) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+632.80 575.93 Td
+(16) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+629.280 575.280 m 
+643.680 575.280 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+645.12 565.85 Td
+(MOSI) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+632.80 568.73 Td
+(17) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+629.280 568.080 m 
+643.680 568.080 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+645.12 558.65 Td
+(SCK) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+632.80 561.53 Td
+(18) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+629.280 560.880 m 
+643.680 560.880 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+645.12 551.45 Td
+(NSS) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+632.80 554.33 Td
+(19) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+629.280 553.680 m 
+643.680 553.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+645.12 537.05 Td
+(GND) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+632.80 539.93 Td
+(20) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+629.280 539.280 m 
+643.680 539.280 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+645.12 529.85 Td
+(ANT) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+632.80 532.73 Td
+(21) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+629.280 532.080 m 
+643.680 532.080 l
+S
+1 J
+1 j
+0.72 w
+0.20 0.80 0.20 RG
+[] 0 d
+626.400 534.960 m 
+632.160 529.200 l
+626.400 529.200 m 
+632.160 534.960 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+645.12 522.65 Td
+(GND) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+632.80 525.53 Td
+(22) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+629.280 524.880 m 
+643.680 524.880 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+672.35 613.83 Td
+(E22P-868M30S) Tj
+ET
+7.20 w
+BT
+/F2 6.363634909090909 Tf
+7.00 TL
+0.627 0.000 0.000 rg
+387.36 436.61 Td
+(ANT_Lora) Tj
+ET
+7.20 w
+BT
+/F2 6.363634909090909 Tf
+7.00 TL
+0.627 0.000 0.000 rg
+387.36 428.69 Td
+(GND) Tj
+ET
+7.20 w
+BT
+/F2 6.363634909090909 Tf
+7.00 TL
+0.627 0.000 0.000 rg
+387.36 421.49 Td
+(DIO9) Tj
+ET
+7.20 w
+BT
+/F2 6.363634909090909 Tf
+7.00 TL
+0.627 0.000 0.000 rg
+387.36 414.29 Td
+(DIO8) Tj
+ET
+7.20 w
+BT
+/F2 6.363634909090909 Tf
+7.00 TL
+0.627 0.000 0.000 rg
+387.36 407.81 Td
+(DIO7) Tj
+ET
+7.20 w
+BT
+/F2 6.363634909090909 Tf
+7.00 TL
+0.627 0.000 0.000 rg
+387.36 400.61 Td
+(NC) Tj
+ET
+7.20 w
+BT
+/F2 6.363634909090909 Tf
+7.00 TL
+0.627 0.000 0.000 rg
+387.36 392.69 Td
+(GND) Tj
+ET
+7.20 w
+BT
+/F2 6.363634909090909 Tf
+7.00 TL
+0.627 0.000 0.000 rg
+387.36 385.49 Td
+(3V3) Tj
+ET
+7.20 w
+BT
+/F2 6.363634909090909 Tf
+7.00 TL
+0.627 0.000 0.000 rg
+423.36 436.61 Td
+(ANT_2.4) Tj
+ET
+7.20 w
+BT
+/F2 6.363634909090909 Tf
+7.00 TL
+0.627 0.000 0.000 rg
+434.88 429.41 Td
+(GND) Tj
+ET
+7.20 w
+BT
+/F2 6.363634909090909 Tf
+7.00 TL
+0.627 0.000 0.000 rg
+440.64 422.21 Td
+(CS) Tj
+ET
+7.20 w
+BT
+/F2 6.363634909090909 Tf
+7.00 TL
+0.627 0.000 0.000 rg
+437.04 414.29 Td
+(CLK) Tj
+ET
+7.20 w
+BT
+/F2 6.363634909090909 Tf
+7.00 TL
+0.627 0.000 0.000 rg
+432.00 407.81 Td
+(MOSI) Tj
+ET
+7.20 w
+BT
+/F2 6.363634909090909 Tf
+7.00 TL
+0.627 0.000 0.000 rg
+432.00 400.61 Td
+(MISO) Tj
+ET
+7.20 w
+BT
+/F2 6.363634909090909 Tf
+7.00 TL
+0.627 0.000 0.000 rg
+428.40 392.69 Td
+(RESET) Tj
+ET
+7.20 w
+BT
+/F2 6.363634909090909 Tf
+7.00 TL
+0.627 0.000 0.000 rg
+432.00 385.49 Td
+(BUSY) Tj
+ET
+2 J
+0 j
+72 M
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+384.48 445.68 68.40 -64.80 re
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+389.16 442.08 m 389.16 442.68 388.68 443.16 388.08 443.16 c
+387.48 443.16 387.00 442.68 387.00 442.08 c
+387.00 441.48 387.48 441.00 388.08 441.00 c
+388.68 441.00 389.16 441.48 389.16 442.08 c
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+380.12 439.13 Td
+(1) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+377.280 438.480 m 
+384.480 438.480 l
+S
+1 J
+1 j
+0.72 w
+0.20 0.80 0.20 RG
+[] 0 d
+374.400 441.360 m 
+380.160 435.600 l
+374.400 435.600 m 
+380.160 441.360 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+380.12 431.93 Td
+(2) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+377.280 431.280 m 
+384.480 431.280 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+380.12 424.73 Td
+(3) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+377.280 424.080 m 
+384.480 424.080 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+380.12 417.53 Td
+(4) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+377.280 416.880 m 
+384.480 416.880 l
+S
+1 J
+1 j
+0.72 w
+0.20 0.80 0.20 RG
+[] 0 d
+374.400 419.760 m 
+380.160 414.000 l
+374.400 414.000 m 
+380.160 419.760 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+380.12 410.33 Td
+(5) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+377.280 409.680 m 
+384.480 409.680 l
+S
+1 J
+1 j
+0.72 w
+0.20 0.80 0.20 RG
+[] 0 d
+374.400 412.560 m 
+380.160 406.800 l
+374.400 406.800 m 
+380.160 412.560 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+380.12 403.13 Td
+(6) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+377.280 402.480 m 
+384.480 402.480 l
+S
+1 J
+1 j
+0.72 w
+0.20 0.80 0.20 RG
+[] 0 d
+374.400 405.360 m 
+380.160 399.600 l
+374.400 399.600 m 
+380.160 405.360 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+380.12 395.93 Td
+(7) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+377.280 395.280 m 
+384.480 395.280 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+380.12 388.73 Td
+(8) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+377.280 388.080 m 
+384.480 388.080 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+454.32 388.73 Td
+(9) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+460.080 388.080 m 
+452.880 388.080 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+452.88 395.93 Td
+(10) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+460.080 395.280 m 
+452.880 395.280 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+452.88 403.13 Td
+(11) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+460.080 402.480 m 
+452.880 402.480 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+452.88 410.33 Td
+(12) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+460.080 409.680 m 
+452.880 409.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+452.88 417.53 Td
+(13) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+460.080 416.880 m 
+452.880 416.880 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+452.88 424.73 Td
+(14) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+460.080 424.080 m 
+452.880 424.080 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+452.88 431.93 Td
+(15) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+460.080 431.280 m 
+452.880 431.280 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+452.88 439.13 Td
+(16) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+460.080 438.480 m 
+452.880 438.480 l
+S
+1 J
+1 j
+0.72 w
+0.20 0.80 0.20 RG
+[] 0 d
+457.200 441.360 m 
+462.960 435.600 l
+457.200 435.600 m 
+462.960 441.360 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 0.502 rg
+388.08 453.21 Td
+(WAVESHARE_LORA_CORE_1121) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+388.08 447.20 Td
+(LoRa Core1121-XF) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+377.280 424.080 m 
+373.680 427.680 l
+362.880 427.680 l
+362.880 420.480 l
+373.680 420.480 l
+377.280 424.080 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+377.280 424.080 m 
+377.280 424.080 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+348.16 420.93 Td
+(IRQ) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+460.080 388.080 m 
+463.680 384.480 l
+474.480 384.480 l
+474.480 391.680 l
+463.680 391.680 l
+460.080 388.080 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+460.080 388.080 m 
+460.080 388.080 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+475.55 385.64 Td
+(BUSY) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+460.080 395.280 m 
+463.680 391.680 l
+474.480 391.680 l
+474.480 398.880 l
+463.680 398.880 l
+460.080 395.280 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+460.080 395.280 m 
+460.080 395.280 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+476.44 392.12 Td
+(NRST) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+370.080 388.080 m 
+377.280 388.080 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+370.080 391.680 m 
+370.080 384.480 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+377.280 388.080 m 
+377.280 388.080 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+355.90 384.73 Td
+(VCC) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+460.080 424.080 m 
+463.680 427.680 l
+474.480 427.680 l
+474.480 420.480 l
+463.680 420.480 l
+460.080 424.080 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+460.080 424.080 m 
+460.080 424.080 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+475.92 421.36 Td
+(CS) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+460.080 416.880 m 
+463.680 420.480 l
+474.480 420.480 l
+474.480 413.280 l
+463.680 413.280 l
+460.080 416.880 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+460.080 416.880 m 
+460.080 416.880 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+475.71 414.16 Td
+(SCK) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+460.080 402.480 m 
+463.680 406.080 l
+474.480 406.080 l
+474.480 398.880 l
+463.680 398.880 l
+460.080 402.480 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+460.080 402.480 m 
+460.080 402.480 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+475.92 399.04 Td
+(MISO) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+460.080 409.680 m 
+463.680 413.280 l
+474.480 413.280 l
+474.480 406.080 l
+463.680 406.080 l
+460.080 409.680 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+460.080 409.680 m 
+460.080 409.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+475.92 406.24 Td
+(MOSI) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+370.080 431.280 m 
+377.280 431.280 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+370.080 437.760 m 
+370.080 424.800 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+368.640 435.600 m 
+368.640 426.960 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+367.200 433.440 m 
+367.200 429.120 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+365.760 432.000 m 
+365.760 430.560 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+377.280 431.280 m 
+377.280 431.280 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+346.68 427.87 Td
+(GND) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+467.280 431.280 m 
+460.080 431.280 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+467.280 424.800 m 
+467.280 437.760 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+468.720 426.960 m 
+468.720 435.600 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+470.160 429.120 m 
+470.160 433.440 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+471.600 430.560 m 
+471.600 432.000 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+460.080 431.280 m 
+460.080 431.280 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+471.96 427.87 Td
+(GND) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+370.080 395.280 m 
+377.280 395.280 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+370.080 401.760 m 
+370.080 388.800 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+368.640 399.600 m 
+368.640 390.960 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+367.200 397.440 m 
+367.200 393.120 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+365.760 396.000 m 
+365.760 394.560 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+377.280 395.280 m 
+377.280 395.280 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+346.68 391.87 Td
+(GND) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+380.880 557.280 m 
+388.080 557.280 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+380.880 563.760 m 
+380.880 550.800 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+379.440 561.600 m 
+379.440 552.960 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+378.000 559.440 m 
+378.000 555.120 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+376.560 558.000 m 
+376.560 556.560 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+388.080 557.280 m 
+388.080 557.280 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+357.48 553.87 Td
+(GND) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+388.080 586.080 m 
+384.480 582.480 l
+373.680 582.480 l
+373.680 589.680 l
+384.480 589.680 l
+388.080 586.080 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+388.080 586.080 m 
+388.080 586.080 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+354.06 583.25 Td
+(RXEN) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+456.480 593.280 m 
+449.280 593.280 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+456.480 586.800 m 
+456.480 599.760 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+457.920 588.960 m 
+457.920 597.600 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+459.360 591.120 m 
+459.360 595.440 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+460.800 592.560 m 
+460.800 594.000 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+449.280 593.280 m 
+449.280 593.280 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+461.16 589.87 Td
+(GND) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+380.880 600.480 m 
+388.080 600.480 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+380.880 606.960 m 
+380.880 594.000 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+379.440 604.800 m 
+379.440 596.160 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+378.000 602.640 m 
+378.000 598.320 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+376.560 601.200 m 
+376.560 599.760 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+388.080 600.480 m 
+388.080 600.480 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+357.48 597.07 Td
+(GND) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+380.880 593.280 m 
+388.080 593.280 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+380.880 599.760 m 
+380.880 586.800 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+379.440 597.600 m 
+379.440 588.960 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+378.000 595.440 m 
+378.000 591.120 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+376.560 594.000 m 
+376.560 592.560 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+388.080 593.280 m 
+388.080 593.280 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+357.48 589.87 Td
+(GND) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+449.280 571.680 m 
+452.880 575.280 l
+463.680 575.280 l
+463.680 568.080 l
+452.880 568.080 l
+449.280 571.680 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+449.280 571.680 m 
+449.280 571.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+465.12 568.24 Td
+(MOSI) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+449.280 564.480 m 
+452.880 568.080 l
+463.680 568.080 l
+463.680 560.880 l
+452.880 560.880 l
+449.280 564.480 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+449.280 564.480 m 
+449.280 564.480 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+465.12 561.04 Td
+(MISO) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+449.280 578.880 m 
+452.880 582.480 l
+463.680 582.480 l
+463.680 575.280 l
+452.880 575.280 l
+449.280 578.880 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+449.280 578.880 m 
+449.280 578.880 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+464.91 576.16 Td
+(SCK) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+449.280 586.080 m 
+452.880 589.680 l
+463.680 589.680 l
+463.680 582.480 l
+452.880 582.480 l
+449.280 586.080 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+449.280 586.080 m 
+449.280 586.080 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+465.12 583.36 Td
+(CS) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+380.880 550.080 m 
+388.080 550.080 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+380.880 553.680 m 
+380.880 546.480 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+388.080 550.080 m 
+388.080 550.080 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+366.70 546.73 Td
+(VCC) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+449.280 557.280 m 
+452.880 553.680 l
+463.680 553.680 l
+463.680 560.880 l
+452.880 560.880 l
+449.280 557.280 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+449.280 557.280 m 
+449.280 557.280 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+465.64 554.12 Td
+(NRST) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+449.280 550.080 m 
+452.880 546.480 l
+463.680 546.480 l
+463.680 553.680 l
+452.880 553.680 l
+449.280 550.080 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+449.280 550.080 m 
+449.280 550.080 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+464.75 547.64 Td
+(BUSY) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+388.080 564.480 m 
+384.480 568.080 l
+373.680 568.080 l
+373.680 560.880 l
+384.480 560.880 l
+388.080 564.480 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+388.080 564.480 m 
+388.080 564.480 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+358.96 561.33 Td
+(IRQ) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+449.280 600.480 m 
+452.880 604.080 l
+463.680 604.080 l
+463.680 596.880 l
+452.880 596.880 l
+449.280 600.480 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+449.280 600.480 m 
+449.280 600.480 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+465.65 597.65 Td
+(LORA_ANT) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+388.080 571.680 m 
+384.480 568.080 l
+373.680 568.080 l
+373.680 575.280 l
+384.480 575.280 l
+388.080 571.680 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+388.080 571.680 m 
+388.080 571.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+354.75 569.03 Td
+(DIO2) Tj
+ET
+7.20 w
+BT
+/F2 6.363634909090909 Tf
+7.00 TL
+0.627 0.000 0.000 rg
+398.16 597.89 Td
+(GND) Tj
+ET
+7.20 w
+BT
+/F2 6.363634909090909 Tf
+7.00 TL
+0.627 0.000 0.000 rg
+398.16 590.69 Td
+(GND) Tj
+ET
+7.20 w
+BT
+/F2 6.363634909090909 Tf
+7.00 TL
+0.627 0.000 0.000 rg
+398.16 583.49 Td
+(RXEN) Tj
+ET
+7.20 w
+BT
+/F2 6.363634909090909 Tf
+7.00 TL
+0.627 0.000 0.000 rg
+398.16 576.29 Td
+(TXEN) Tj
+ET
+7.20 w
+BT
+/F2 6.363634909090909 Tf
+7.00 TL
+0.627 0.000 0.000 rg
+398.16 569.81 Td
+(DIO2) Tj
+ET
+7.20 w
+BT
+/F2 6.363634909090909 Tf
+7.00 TL
+0.627 0.000 0.000 rg
+398.16 562.61 Td
+(DIO1) Tj
+ET
+7.20 w
+BT
+/F2 6.363634909090909 Tf
+7.00 TL
+0.627 0.000 0.000 rg
+398.16 554.69 Td
+(GND) Tj
+ET
+7.20 w
+BT
+/F2 6.363634909090909 Tf
+7.00 TL
+0.627 0.000 0.000 rg
+398.16 547.49 Td
+(3V3) Tj
+ET
+7.20 w
+BT
+/F2 6.363634909090909 Tf
+7.00 TL
+0.627 0.000 0.000 rg
+424.80 598.61 Td
+(ANT) Tj
+ET
+7.20 w
+BT
+/F2 6.363634909090909 Tf
+7.00 TL
+0.627 0.000 0.000 rg
+424.08 591.41 Td
+(GND) Tj
+ET
+7.20 w
+BT
+/F2 6.363634909090909 Tf
+7.00 TL
+0.627 0.000 0.000 rg
+429.84 584.21 Td
+(CS) Tj
+ET
+7.20 w
+BT
+/F2 6.363634909090909 Tf
+7.00 TL
+0.627 0.000 0.000 rg
+426.24 576.29 Td
+(CLK) Tj
+ET
+7.20 w
+BT
+/F2 6.363634909090909 Tf
+7.00 TL
+0.627 0.000 0.000 rg
+421.20 569.81 Td
+(MOSI) Tj
+ET
+7.20 w
+BT
+/F2 6.363634909090909 Tf
+7.00 TL
+0.627 0.000 0.000 rg
+421.20 562.61 Td
+(MISO) Tj
+ET
+7.20 w
+BT
+/F2 6.363634909090909 Tf
+7.00 TL
+0.627 0.000 0.000 rg
+417.60 554.69 Td
+(RESET) Tj
+ET
+7.20 w
+BT
+/F2 6.363634909090909 Tf
+7.00 TL
+0.627 0.000 0.000 rg
+421.20 547.49 Td
+(BUSY) Tj
+ET
+2 J
+0 j
+72 M
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+395.28 607.68 46.80 -64.80 re
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+399.96 604.08 m 399.96 604.68 399.48 605.16 398.88 605.16 c
+398.28 605.16 397.80 604.68 397.80 604.08 c
+397.80 603.48 398.28 603.00 398.88 603.00 c
+399.48 603.00 399.96 603.48 399.96 604.08 c
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+390.92 601.13 Td
+(1) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+388.080 600.480 m 
+395.280 600.480 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+390.92 593.93 Td
+(2) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+388.080 593.280 m 
+395.280 593.280 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+390.92 586.73 Td
+(3) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+388.080 586.080 m 
+395.280 586.080 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+390.92 579.53 Td
+(4) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+388.080 578.880 m 
+395.280 578.880 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+390.92 572.33 Td
+(5) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+388.080 571.680 m 
+395.280 571.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+390.92 565.13 Td
+(6) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+388.080 564.480 m 
+395.280 564.480 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+390.92 557.93 Td
+(7) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+388.080 557.280 m 
+395.280 557.280 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+390.92 550.73 Td
+(8) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+388.080 550.080 m 
+395.280 550.080 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+443.52 550.73 Td
+(9) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+449.280 550.080 m 
+442.080 550.080 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+442.08 557.93 Td
+(10) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+449.280 557.280 m 
+442.080 557.280 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+442.08 565.13 Td
+(11) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+449.280 564.480 m 
+442.080 564.480 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+442.08 572.33 Td
+(12) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+449.280 571.680 m 
+442.080 571.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+442.08 579.53 Td
+(13) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+449.280 578.880 m 
+442.080 578.880 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+442.08 586.73 Td
+(14) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+449.280 586.080 m 
+442.080 586.080 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+442.08 593.93 Td
+(15) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+449.280 593.280 m 
+442.080 593.280 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+442.08 601.13 Td
+(16) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+449.280 600.480 m 
+442.080 600.480 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 0.502 rg
+414.58 616.96 Td
+(WAVESHARE_LORA_CORE_1262) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+414.58 610.89 Td
+(LoRa Core1262-868M) Tj
+ET
+2 J
+0 j
+72 M
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+172.08 447.84 79.20 -108.00 re
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+248.76 433.44 m 248.76 434.04 248.28 434.52 247.68 434.52 c
+247.08 434.52 246.60 434.04 246.60 433.44 c
+246.60 432.84 247.08 432.36 247.68 432.36 c
+248.28 432.36 248.76 432.84 248.76 433.44 c
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+234.07 439.01 Td
+(GND) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+251.64 435.41 Td
+(1) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+258.480 440.640 m 
+251.280 440.640 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+234.43 431.81 Td
+(2.4G) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+251.64 428.21 Td
+(2) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+258.480 433.440 m 
+251.280 433.440 l
+S
+1 J
+1 j
+0.72 w
+0.20 0.80 0.20 RG
+[] 0 d
+255.600 436.320 m 
+261.360 430.560 l
+255.600 430.560 m 
+261.360 436.320 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+234.07 424.61 Td
+(GND) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+251.64 421.01 Td
+(3) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+258.480 426.240 m 
+251.280 426.240 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+223.15 406.61 Td
+(LR_NSS) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+251.64 403.01 Td
+(4) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+258.480 408.240 m 
+251.280 408.240 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+223.15 399.41 Td
+(LR_SCK) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+251.64 395.81 Td
+(5) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+258.480 401.040 m 
+251.280 401.040 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+219.88 392.21 Td
+(LR_MOSI) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+251.64 388.61 Td
+(6) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+258.480 393.840 m 
+251.280 393.840 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+219.88 385.01 Td
+(LR_MISO) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+251.64 381.41 Td
+(7) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+258.480 386.640 m 
+251.280 386.640 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+218.79 377.81 Td
+(LR_BUSY) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+251.64 374.21 Td
+(8) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+258.480 379.440 m 
+251.280 379.440 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+234.07 370.61 Td
+(GND) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+251.64 367.01 Td
+(9) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+258.480 372.240 m 
+251.280 372.240 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+0.00 1.00 -1.00 0.00 232.69 341.13 Tm
+(GND) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+0.00 1.00 -1.00 0.00 236.29 330.82 Tm
+(10) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+229.680 332.640 m 
+229.680 339.840 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+0.00 1.00 -1.00 0.00 225.49 341.13 Tm
+(DIO8) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+0.00 1.00 -1.00 0.00 229.09 330.82 Tm
+(11) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+222.480 332.640 m 
+222.480 339.840 l
+S
+1 J
+1 j
+0.72 w
+0.20 0.80 0.20 RG
+[] 0 d
+219.600 335.520 m 
+225.360 329.760 l
+219.600 329.760 m 
+225.360 335.520 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+0.00 1.00 -1.00 0.00 218.29 341.13 Tm
+(DIO9) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+0.00 1.00 -1.00 0.00 221.89 330.82 Tm
+(12) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+215.280 332.640 m 
+215.280 339.840 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+0.00 1.00 -1.00 0.00 211.09 341.13 Tm
+(GND) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+0.00 1.00 -1.00 0.00 214.69 330.82 Tm
+(13) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+208.080 332.640 m 
+208.080 339.840 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+0.00 1.00 -1.00 0.00 203.89 341.13 Tm
+(LR_nRST) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+0.00 1.00 -1.00 0.00 207.49 330.82 Tm
+(14) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+200.880 332.640 m 
+200.880 339.840 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+0.00 1.00 -1.00 0.00 196.69 341.13 Tm
+(GND) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+0.00 1.00 -1.00 0.00 200.29 330.82 Tm
+(15) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+193.680 332.640 m 
+193.680 339.840 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+174.74 370.61 Td
+(GND) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+164.44 367.01 Td
+(16) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+164.880 372.240 m 
+172.080 372.240 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+174.74 377.81 Td
+(GND) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+164.44 374.21 Td
+(17) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+164.880 379.440 m 
+172.080 379.440 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+174.74 385.01 Td
+(GND) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+164.44 381.41 Td
+(18) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+164.880 386.640 m 
+172.080 386.640 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+174.74 392.21 Td
+(GND) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+164.44 388.61 Td
+(19) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+164.880 393.840 m 
+172.080 393.840 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+174.74 399.41 Td
+(VDD_RF) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+164.44 395.81 Td
+(20) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+164.880 401.040 m 
+172.080 401.040 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+174.74 406.61 Td
+(VDD_RF) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+164.44 403.01 Td
+(21) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+164.880 408.240 m 
+172.080 408.240 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+174.74 424.61 Td
+(GND) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+164.44 421.01 Td
+(22) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+164.880 426.240 m 
+172.080 426.240 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+174.74 431.81 Td
+(SUBG_RF) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+164.44 428.21 Td
+(23) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+164.880 433.440 m 
+172.080 433.440 l
+S
+1 J
+1 j
+0.72 w
+0.20 0.80 0.20 RG
+[] 0 d
+162.000 436.320 m 
+167.760 430.560 l
+162.000 430.560 m 
+167.760 436.320 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+174.74 439.01 Td
+(GND) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+164.44 435.41 Td
+(24) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+164.880 440.640 m 
+172.080 440.640 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 0.502 rg
+205.52 456.81 Td
+(Seeed_WIO-LR1121) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+205.52 450.33 Td
+(Seeed_WIO-LR1121) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+265.680 426.240 m 
+258.480 426.240 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+265.680 419.760 m 
+265.680 432.720 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+267.120 421.920 m 
+267.120 430.560 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+268.560 424.080 m 
+268.560 428.400 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+270.000 425.520 m 
+270.000 426.960 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+258.480 426.240 m 
+258.480 426.240 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+270.36 422.89 Td
+(GND) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+265.680 440.640 m 
+258.480 440.640 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+265.680 434.160 m 
+265.680 447.120 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+267.120 436.320 m 
+267.120 444.960 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+268.560 438.480 m 
+268.560 442.800 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+270.000 439.920 m 
+270.000 441.360 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+258.480 440.640 m 
+258.480 440.640 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+270.36 437.29 Td
+(GND) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+157.680 440.640 m 
+164.880 440.640 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+157.680 447.120 m 
+157.680 434.160 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+156.240 444.960 m 
+156.240 436.320 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+154.800 442.800 m 
+154.800 438.480 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+153.360 441.360 m 
+153.360 439.920 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+164.880 440.640 m 
+164.880 440.640 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+134.04 437.29 Td
+(GND) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+157.680 426.240 m 
+164.880 426.240 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+157.680 432.720 m 
+157.680 419.760 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+156.240 430.560 m 
+156.240 421.920 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+154.800 428.400 m 
+154.800 424.080 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+153.360 426.960 m 
+153.360 425.520 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+164.880 426.240 m 
+164.880 426.240 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+134.04 422.89 Td
+(GND) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+154.080 408.240 m 
+161.280 408.240 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+154.080 404.640 m 
+154.080 411.840 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+161.280 408.240 m 
+161.280 408.240 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+135.94 404.89 Td
+(VCC) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+154.080 393.840 m 
+161.280 393.840 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+154.080 400.320 m 
+154.080 387.360 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+152.640 398.160 m 
+152.640 389.520 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+151.200 396.000 m 
+151.200 391.680 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+149.760 394.560 m 
+149.760 393.120 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+161.280 393.840 m 
+161.280 393.840 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+130.44 390.49 Td
+(GND) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+258.480 365.040 m 
+258.480 372.240 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+252.000 365.040 m 
+264.960 365.040 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+254.160 363.600 m 
+262.800 363.600 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+256.320 362.160 m 
+260.640 362.160 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+257.760 360.720 m 
+259.200 360.720 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+258.480 372.240 m 
+258.480 372.240 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+249.00 351.97 Td
+(GND) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+215.280 303.840 m 
+218.880 307.440 l
+229.680 307.440 l
+229.680 300.240 l
+218.880 300.240 l
+215.280 303.840 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+215.280 303.840 m 
+215.280 303.840 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+231.68 301.41 Td
+(IRQ) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+258.480 379.440 m 
+262.080 383.040 l
+272.880 383.040 l
+272.880 375.840 l
+262.080 375.840 l
+258.480 379.440 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+258.480 379.440 m 
+258.480 379.440 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+274.10 376.72 Td
+(BUSY) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+240.480 321.840 m 
+240.480 329.040 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+246.960 321.840 m 
+234.000 321.840 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+244.800 320.400 m 
+236.160 320.400 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+242.640 318.960 m 
+238.320 318.960 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+241.200 317.520 m 
+239.760 317.520 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+240.480 329.040 m 
+240.480 329.040 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+235.17 308.77 Td
+(GND) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+193.680 303.840 m 
+190.080 307.440 l
+179.280 307.440 l
+179.280 300.240 l
+190.080 300.240 l
+193.680 303.840 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+193.680 303.840 m 
+193.680 303.840 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+157.92 301.18 Td
+(NRST) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+258.480 408.240 m 
+262.080 404.640 l
+272.880 404.640 l
+272.880 411.840 l
+262.080 411.840 l
+258.480 408.240 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+258.480 408.240 m 
+258.480 408.240 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+274.28 405.44 Td
+(CS) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+258.480 401.040 m 
+262.080 397.440 l
+272.880 397.440 l
+272.880 404.640 l
+262.080 404.640 l
+258.480 401.040 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+258.480 401.040 m 
+258.480 401.040 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+273.07 398.24 Td
+(SCK) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+258.480 393.840 m 
+262.080 390.240 l
+272.880 390.240 l
+272.880 397.440 l
+262.080 397.440 l
+258.480 393.840 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+258.480 393.840 m 
+258.480 393.840 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+274.72 391.04 Td
+(MOSI) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+258.480 386.640 m 
+262.080 383.040 l
+272.880 383.040 l
+272.880 390.240 l
+262.080 390.240 l
+258.480 386.640 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+258.480 386.640 m 
+258.480 386.640 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+274.82 383.84 Td
+(MISO) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+96.480 1032.480 m 
+92.880 1028.880 l
+82.080 1028.880 l
+82.080 1036.080 l
+92.880 1036.080 l
+96.480 1032.480 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+96.480 1032.480 m 
+96.480 1032.480 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+63.54 1029.61 Td
+(P1.06) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+96.480 1111.680 m 
+92.880 1108.080 l
+82.080 1108.080 l
+82.080 1115.280 l
+92.880 1115.280 l
+96.480 1111.680 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+96.480 1111.680 m 
+96.480 1111.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+44.63 1108.96 Td
+(SERIAL2RX) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+96.480 1104.480 m 
+92.880 1100.880 l
+82.080 1100.880 l
+82.080 1108.080 l
+92.880 1108.080 l
+96.480 1104.480 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+96.480 1104.480 m 
+96.480 1104.480 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+45.35 1101.76 Td
+(SERIAL2TX) Tj
+ET
+2 J
+0 j
+72 M
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+272.88 1075.68 7.20 -14.40 re
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+276.480 1082.880 m 
+276.480 1075.680 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+276.480 1054.080 m 
+276.480 1061.280 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 0.502 rg
+281.52 1066.47 Td
+(R_ADC_B) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+281.52 1059.99 Td
+(1.5M) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+276.480 1111.680 m 
+272.880 1115.280 l
+272.880 1126.080 l
+280.080 1126.080 l
+280.080 1115.280 l
+276.480 1111.680 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+276.480 1111.680 m 
+276.480 1111.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+0.00 1.00 -1.00 0.00 277.83 1126.29 Tm
+(BATT) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+276.480 1046.880 m 
+276.480 1054.080 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+270.000 1046.880 m 
+282.960 1046.880 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+272.160 1045.440 m 
+280.800 1045.440 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+274.320 1044.000 m 
+278.640 1044.000 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+275.760 1042.560 m 
+277.200 1042.560 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+276.480 1054.080 m 
+276.480 1054.080 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+267.12 1034.89 Td
+(GND) Tj
+ET
+2 J
+0 j
+72 M
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+272.88 1104.48 7.20 -14.40 re
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+276.480 1111.680 m 
+276.480 1104.480 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+276.480 1082.880 m 
+276.480 1090.080 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 0.502 rg
+281.52 1095.27 Td
+(R_ADC_T) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+281.52 1088.79 Td
+(1M) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+190.080 1097.280 m 
+193.680 1100.880 l
+204.480 1100.880 l
+204.480 1093.680 l
+193.680 1093.680 l
+190.080 1097.280 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+190.080 1097.280 m 
+190.080 1097.280 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+205.93 1094.17 Td
+(RBTN) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+85.680 1054.080 m 
+82.080 1050.480 l
+71.280 1050.480 l
+71.280 1057.680 l
+82.080 1057.680 l
+85.680 1054.080 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+85.680 1054.080 m 
+85.680 1054.080 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+52.02 1051.36 Td
+(UBTN) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+89.280 1093.680 m 
+96.480 1093.680 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+89.280 1100.160 m 
+89.280 1087.200 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+87.840 1098.000 m 
+87.840 1089.360 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+86.400 1095.840 m 
+86.400 1091.520 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+84.960 1094.400 m 
+84.960 1092.960 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+96.480 1093.680 m 
+96.480 1093.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+65.88 1090.33 Td
+(GND) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+85.680 1061.280 m 
+82.080 1057.680 l
+71.280 1057.680 l
+71.280 1064.880 l
+82.080 1064.880 l
+85.680 1061.280 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+85.680 1061.280 m 
+85.680 1061.280 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+46.92 1058.41 Td
+(GPSEN) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+85.680 1075.680 m 
+82.080 1072.080 l
+71.280 1072.080 l
+71.280 1079.280 l
+82.080 1079.280 l
+85.680 1075.680 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+85.680 1075.680 m 
+85.680 1075.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+46.92 1072.81 Td
+(GPSRX) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+85.680 1068.480 m 
+82.080 1064.880 l
+71.280 1064.880 l
+71.280 1072.080 l
+82.080 1072.080 l
+85.680 1068.480 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+85.680 1068.480 m 
+85.680 1068.480 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+47.65 1065.61 Td
+(GPSTX) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+190.080 1039.680 m 
+193.680 1043.280 l
+204.480 1043.280 l
+204.480 1036.080 l
+193.680 1036.080 l
+190.080 1039.680 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+190.080 1039.680 m 
+190.080 1039.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+205.92 1036.96 Td
+(IRQ) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+190.080 1032.480 m 
+193.680 1036.080 l
+204.480 1036.080 l
+204.480 1028.880 l
+193.680 1028.880 l
+190.080 1032.480 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+190.080 1032.480 m 
+190.080 1032.480 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+206.28 1029.76 Td
+(NRST) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+236.880 1090.080 m 
+229.680 1090.080 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+236.880 1093.680 m 
+236.880 1086.480 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+229.680 1090.080 m 
+229.680 1090.080 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+237.60 1086.73 Td
+(VCC) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+190.080 1054.080 m 
+193.680 1057.680 l
+204.480 1057.680 l
+204.480 1050.480 l
+193.680 1050.480 l
+190.080 1054.080 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+190.080 1054.080 m 
+190.080 1054.080 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+205.92 1051.36 Td
+(CS) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+190.080 1061.280 m 
+193.680 1064.880 l
+204.480 1064.880 l
+204.480 1057.680 l
+193.680 1057.680 l
+190.080 1061.280 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+190.080 1061.280 m 
+190.080 1061.280 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+205.92 1058.56 Td
+(MOSI) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+190.080 1068.480 m 
+193.680 1072.080 l
+204.480 1072.080 l
+204.480 1064.880 l
+193.680 1064.880 l
+190.080 1068.480 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+190.080 1068.480 m 
+190.080 1068.480 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+205.92 1065.76 Td
+(MISO) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+190.080 1046.880 m 
+193.680 1050.480 l
+204.480 1050.480 l
+204.480 1043.280 l
+193.680 1043.280 l
+190.080 1046.880 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+190.080 1046.880 m 
+190.080 1046.880 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+205.80 1044.16 Td
+(SCK) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+190.080 1075.680 m 
+193.680 1079.280 l
+204.480 1079.280 l
+204.480 1072.080 l
+193.680 1072.080 l
+190.080 1075.680 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+190.080 1075.680 m 
+190.080 1075.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+205.70 1072.96 Td
+(BUSY) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+236.880 1104.480 m 
+229.680 1104.480 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+236.880 1098.000 m 
+236.880 1110.960 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+238.320 1100.160 m 
+238.320 1108.800 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+239.760 1102.320 m 
+239.760 1106.640 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+241.200 1103.760 m 
+241.200 1105.200 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+229.680 1104.480 m 
+229.680 1104.480 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+241.56 1101.13 Td
+(GND) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+182.880 1111.680 m 
+186.480 1115.280 l
+197.280 1115.280 l
+197.280 1108.080 l
+186.480 1108.080 l
+182.880 1111.680 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+182.880 1111.680 m 
+182.880 1111.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+199.08 1108.74 Td
+(BATT) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+85.680 1046.880 m 
+82.080 1043.280 l
+71.280 1043.280 l
+71.280 1050.480 l
+82.080 1050.480 l
+85.680 1046.880 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+85.680 1046.880 m 
+85.680 1046.880 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+57.11 1044.01 Td
+(SCL) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+85.680 1039.680 m 
+82.080 1036.080 l
+71.280 1036.080 l
+71.280 1043.280 l
+82.080 1043.280 l
+85.680 1039.680 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+85.680 1039.680 m 
+85.680 1039.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+56.38 1036.81 Td
+(SDA) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+276.480 1082.880 m 
+280.080 1086.480 l
+290.880 1086.480 l
+290.880 1079.280 l
+280.080 1079.280 l
+276.480 1082.880 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+276.480 1082.880 m 
+276.480 1082.880 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+292.82 1079.93 Td
+(ADC) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+661.680 1111.680 m 
+658.080 1115.280 l
+658.080 1126.080 l
+665.280 1126.080 l
+665.280 1115.280 l
+661.680 1111.680 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+661.680 1111.680 m 
+661.680 1111.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+0.00 1.00 -1.00 0.00 663.03 1126.29 Tm
+(BATT) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+661.680 1046.880 m 
+661.680 1054.080 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+655.200 1046.880 m 
+668.160 1046.880 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+657.360 1045.440 m 
+666.000 1045.440 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+659.520 1044.000 m 
+663.840 1044.000 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+660.960 1042.560 m 
+662.400 1042.560 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+661.680 1054.080 m 
+661.680 1054.080 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+652.32 1034.89 Td
+(GND) Tj
+ET
+2 J
+0 j
+72 M
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+658.08 1104.48 7.20 -14.40 re
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+661.680 1111.680 m 
+661.680 1104.480 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+661.680 1082.880 m 
+661.680 1090.080 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 0.502 rg
+666.72 1095.27 Td
+(R_ADC_T1) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+666.72 1088.79 Td
+(220k) Tj
+ET
+2 J
+0 j
+72 M
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+658.08 1075.68 7.20 -14.40 re
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+661.680 1082.880 m 
+661.680 1075.680 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+661.680 1054.080 m 
+661.680 1061.280 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 0.502 rg
+666.72 1066.47 Td
+(R_ADC_B1) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+666.72 1059.99 Td
+(330k) Tj
+ET
+2 J
+0 j
+72 M
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+712.08 1104.48 7.20 -14.40 re
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+715.680 1111.680 m 
+715.680 1104.480 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+715.680 1082.880 m 
+715.680 1090.080 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 0.502 rg
+720.72 1095.27 Td
+(R_ADC_T2) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+720.72 1088.79 Td
+(680k) Tj
+ET
+2 J
+0 j
+72 M
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+712.08 1075.68 7.20 -14.40 re
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+715.680 1082.880 m 
+715.680 1075.680 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+715.680 1054.080 m 
+715.680 1061.280 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 0.502 rg
+720.72 1066.47 Td
+(R_ADC_B2) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+720.72 1059.99 Td
+(1M) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+715.680 1082.880 m 
+719.280 1086.480 l
+730.080 1086.480 l
+730.080 1079.280 l
+719.280 1079.280 l
+715.680 1082.880 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+715.680 1082.880 m 
+715.680 1082.880 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+732.02 1079.93 Td
+(ADC) Tj
+ET
+7.20 w
+BT
+/F2 8.181818181818182 Tf
+9.00 TL
+0.000 g
+640.08 1151.66 Td
+(Alternative ADC resistors) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+715.680 1111.680 m 
+712.080 1115.280 l
+712.080 1126.080 l
+719.280 1126.080 l
+719.280 1115.280 l
+715.680 1111.680 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+715.680 1111.680 m 
+715.680 1111.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+0.00 1.00 -1.00 0.00 717.03 1126.29 Tm
+(BATT) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+715.680 1046.880 m 
+715.680 1054.080 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+709.200 1046.880 m 
+722.160 1046.880 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+711.360 1045.440 m 
+720.000 1045.440 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+713.520 1044.000 m 
+717.840 1044.000 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+714.960 1042.560 m 
+716.400 1042.560 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+715.680 1054.080 m 
+715.680 1054.080 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+706.32 1034.89 Td
+(GND) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+661.680 1082.880 m 
+665.280 1086.480 l
+676.080 1086.480 l
+676.080 1079.280 l
+665.280 1079.280 l
+661.680 1082.880 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+661.680 1082.880 m 
+661.680 1082.880 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+678.02 1079.93 Td
+(ADC) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+92.880 1082.880 m 
+89.280 1079.280 l
+78.480 1079.280 l
+78.480 1086.480 l
+89.280 1086.480 l
+92.880 1082.880 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+92.880 1082.880 m 
+92.880 1082.880 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+58.86 1080.01 Td
+(RXEN) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+377.280 1082.880 m 
+373.680 1079.280 l
+362.880 1079.280 l
+362.880 1086.480 l
+373.680 1086.480 l
+377.280 1082.880 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+377.280 1082.880 m 
+377.280 1082.880 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+343.26 1080.01 Td
+(RXEN) Tj
+ET
+7.20 w
+BT
+/F2 8.181818181818182 Tf
+9.00 TL
+0.000 g
+218.88 248.06 Td
+(Bulk Capacitors) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+157.680 690.480 m 
+154.080 686.880 l
+143.280 686.880 l
+143.280 694.080 l
+154.080 694.080 l
+157.680 690.480 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+157.680 690.480 m 
+157.680 690.480 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+124.34 687.64 Td
+(DIO2) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+481.680 730.080 m 
+485.280 733.680 l
+496.080 733.680 l
+496.080 726.480 l
+485.280 726.480 l
+481.680 730.080 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+481.680 730.080 m 
+481.680 730.080 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+497.53 727.03 Td
+(DIO2) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+730.080 730.080 m 
+733.680 733.680 l
+744.480 733.680 l
+744.480 726.480 l
+733.680 726.480 l
+730.080 730.080 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+730.080 730.080 m 
+730.080 730.080 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+745.93 727.03 Td
+(DIO2) Tj
+ET
+7.20 w
+BT
+/F2 8.181818181818182 Tf
+9.00 TL
+0.000 g
+366.48 820.46 Td
+(NB// Ant pin is not connected, ) Tj
+T* (except on non-ipex version) Tj
+ET
+7.20 w
+BT
+/F2 8.181818181818182 Tf
+9.00 TL
+0.000 g
+150.48 289.82 Td
+(NB// Ant pin is not connected, ) Tj
+T* (except on non-ipex version) Tj
+ET
+7.20 w
+BT
+/F2 8.181818181818182 Tf
+9.00 TL
+0.000 g
+157.68 665.66 Td
+(NB// Non-TCXO!) Tj
+ET
+7.20 w
+BT
+/F2 8.181818181818182 Tf
+9.00 TL
+0.000 g
+114.48 809.66 Td
+(NB// RA-01SH is Non-TCXO!) Tj
+ET
+7.20 w
+BT
+/F2 8.181818181818182 Tf
+9.00 TL
+0.000 g
+632.88 161.66 Td
+(NB// SX1276 - non-preferred!) Tj
+ET
+2 J
+0 j
+72 M
+0.72 w
+0.00 G
+[] 0 d
+35.28 476.64 777.60 -208.80 re
+S
+7.20 w
+BT
+/F2 8.181818181818182 Tf
+9.00 TL
+0.000 g
+46.08 459.02 Td
+(LR1121 Modules) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+388.080 578.880 m 
+384.480 575.280 l
+373.680 575.280 l
+373.680 582.480 l
+384.480 582.480 l
+388.080 578.880 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+388.080 578.880 m 
+388.080 578.880 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+354.75 576.23 Td
+(DIO2) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+730.080 859.680 m 
+722.880 859.680 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+730.080 863.280 m 
+730.080 856.080 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+722.880 859.680 m 
+722.880 859.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+730.44 856.27 Td
+(VCC) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+722.880 866.880 m 
+726.480 870.480 l
+737.280 870.480 l
+737.280 863.280 l
+726.480 863.280 l
+722.880 866.880 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+722.880 866.880 m 
+722.880 866.880 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+738.72 863.93 Td
+(DIO2) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+629.280 852.480 m 
+625.680 848.880 l
+614.880 848.880 l
+614.880 856.080 l
+625.680 856.080 l
+629.280 852.480 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+629.280 852.480 m 
+629.280 852.480 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+595.95 849.54 Td
+(DIO2) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+622.080 863.280 m 
+629.280 863.280 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+622.080 869.760 m 
+622.080 856.800 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+620.640 867.600 m 
+620.640 858.960 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+619.200 865.440 m 
+619.200 861.120 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+617.760 864.000 m 
+617.760 862.560 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+629.280 863.280 m 
+629.280 863.280 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+598.68 859.87 Td
+(GND) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+730.080 884.880 m 
+722.880 884.880 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+730.080 878.400 m 
+730.080 891.360 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+731.520 880.560 m 
+731.520 889.200 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+732.960 882.720 m 
+732.960 887.040 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+734.400 884.160 m 
+734.400 885.600 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+722.880 884.880 m 
+722.880 884.880 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+734.76 881.47 Td
+(GND) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+722.880 809.280 m 
+722.880 816.480 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+716.400 809.280 m 
+729.360 809.280 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+718.560 807.840 m 
+727.200 807.840 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+720.720 806.400 m 
+725.040 806.400 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+722.160 804.960 m 
+723.600 804.960 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+722.880 816.480 m 
+722.880 816.480 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+713.52 797.29 Td
+(GND) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+629.280 845.280 m 
+625.680 841.680 l
+614.880 841.680 l
+614.880 848.880 l
+625.680 848.880 l
+629.280 845.280 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+629.280 845.280 m 
+629.280 845.280 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+600.16 842.12 Td
+(IRQ) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+629.280 838.080 m 
+625.680 834.480 l
+614.880 834.480 l
+614.880 841.680 l
+625.680 841.680 l
+629.280 838.080 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+629.280 838.080 m 
+629.280 838.080 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+593.11 834.92 Td
+(BUSY) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+629.280 830.880 m 
+625.680 834.480 l
+614.880 834.480 l
+614.880 827.280 l
+625.680 827.280 l
+629.280 830.880 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+629.280 830.880 m 
+629.280 830.880 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+593.65 828.45 Td
+(NRST) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+622.080 902.880 m 
+629.280 902.880 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+622.080 899.280 m 
+622.080 906.480 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+629.280 902.880 m 
+629.280 902.880 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+602.58 899.53 Td
+(VCC) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+629.280 895.680 m 
+625.680 892.080 l
+614.880 892.080 l
+614.880 899.280 l
+625.680 899.280 l
+629.280 895.680 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+629.280 895.680 m 
+629.280 895.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+604.35 892.81 Td
+(CS) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+629.280 888.480 m 
+625.680 884.880 l
+614.880 884.880 l
+614.880 892.080 l
+625.680 892.080 l
+629.280 888.480 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+629.280 888.480 m 
+629.280 888.480 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+599.98 885.61 Td
+(SCK) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+629.280 874.080 m 
+625.680 877.680 l
+614.880 877.680 l
+614.880 870.480 l
+625.680 870.480 l
+629.280 874.080 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+629.280 874.080 m 
+629.280 874.080 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+596.71 870.64 Td
+(MISO) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+629.280 881.280 m 
+625.680 884.880 l
+614.880 884.880 l
+614.880 877.680 l
+625.680 877.680 l
+629.280 881.280 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+629.280 881.280 m 
+629.280 881.280 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+596.71 877.84 Td
+(MOSI) Tj
+ET
+2 J
+0 j
+72 M
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+636.48 910.08 79.20 -86.40 re
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+639.14 899.93 Td
+(VDD) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+632.48 903.53 Td
+(1) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+629.280 902.880 m 
+636.480 902.880 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+639.14 892.73 Td
+(CS) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+632.48 896.33 Td
+(2) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+629.280 895.680 m 
+636.480 895.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+639.14 885.53 Td
+(SCK) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+632.48 889.13 Td
+(3) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+629.280 888.480 m 
+636.480 888.480 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+639.14 878.33 Td
+(MOSI) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+632.48 881.93 Td
+(4) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+629.280 881.280 m 
+636.480 881.280 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+639.14 871.13 Td
+(MISO) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+632.48 874.73 Td
+(5) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+629.280 874.080 m 
+636.480 874.080 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+639.14 860.33 Td
+(GND) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+632.48 863.93 Td
+(6) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+629.280 863.280 m 
+636.480 863.280 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+638.64 850.25 Td
+(DIO2) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+633.20 853.13 Td
+(7) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+629.280 852.480 m 
+636.480 852.480 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+638.64 843.05 Td
+(DIO1) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+633.20 845.93 Td
+(8) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+629.280 845.280 m 
+636.480 845.280 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+638.64 835.85 Td
+(Busy) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+633.20 838.73 Td
+(9) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+629.280 838.080 m 
+636.480 838.080 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+638.64 828.65 Td
+(NRst) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+629.56 831.53 Td
+(10) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+629.280 830.880 m 
+636.480 830.880 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+703.70 900.65 Td
+(Ant) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+715.32 903.53 Td
+(11) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+722.880 902.880 m 
+715.680 902.880 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+698.98 889.85 Td
+(GND) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+715.32 892.73 Td
+(12) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+722.880 892.080 m 
+715.680 892.080 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+698.47 881.93 Td
+(GND) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+716.04 885.53 Td
+(13) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+722.880 884.880 m 
+715.680 884.880 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+698.47 874.73 Td
+(GND) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+716.04 878.33 Td
+(14) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+722.880 877.680 m 
+715.680 877.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+679.19 863.93 Td
+(TX_RX_EN) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+716.04 867.53 Td
+(15) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+722.880 866.880 m 
+715.680 866.880 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+685.01 856.73 Td
+(VDD_SW) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+716.04 860.33 Td
+(16) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+722.880 859.680 m 
+715.680 859.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+698.47 845.93 Td
+(GND) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+716.04 849.53 Td
+(17) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+722.880 848.880 m 
+715.680 848.880 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+698.47 838.73 Td
+(GND) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+716.04 842.33 Td
+(18) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+722.880 841.680 m 
+715.680 841.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+698.47 832.97 Td
+(GND) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+716.04 835.13 Td
+(19) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+722.880 834.480 m 
+715.680 834.480 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+698.47 825.77 Td
+(GND) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+716.04 827.93 Td
+(20) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+722.880 827.280 m 
+715.680 827.280 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+0.00 1.00 -1.00 0.00 665.41 824.97 Tm
+(GND) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+0.00 1.00 -1.00 0.00 663.25 814.66 Tm
+(21) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+665.280 816.480 m 
+665.280 823.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+0.00 1.00 -1.00 0.00 672.61 824.97 Tm
+(GND) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+0.00 1.00 -1.00 0.00 670.45 814.66 Tm
+(22) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+672.480 816.480 m 
+672.480 823.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+0.00 1.00 -1.00 0.00 679.81 824.97 Tm
+(GND) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+0.00 1.00 -1.00 0.00 677.65 814.66 Tm
+(23) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+679.680 816.480 m 
+679.680 823.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+0.00 1.00 -1.00 0.00 687.01 824.97 Tm
+(GND) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+0.00 1.00 -1.00 0.00 684.85 814.66 Tm
+(24) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+686.880 816.480 m 
+686.880 823.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 0.502 rg
+672.16 919.36 Td
+(ELECROW_LR1262) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+672.16 913.29 Td
+(LR1262 Transceiver) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+751.680 917.280 m 
+751.680 924.480 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+745.200 917.280 m 
+758.160 917.280 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+747.360 915.840 m 
+756.000 915.840 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+749.520 914.400 m 
+753.840 914.400 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+750.960 912.960 m 
+752.400 912.960 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+751.680 924.480 m 
+751.680 924.480 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+742.32 904.57 Td
+(GND) Tj
+ET
+2 J
+0 j
+72 M
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+758.88 931.68 14.40 -14.40 re
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+0.00 1.00 -1.00 0.00 764.05 908.66 Tm
+(1) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+766.080 902.880 m 
+766.080 917.280 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+755.24 919.25 Td
+(2) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+751.680 924.480 m 
+758.880 924.480 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+773.28 919.25 Td
+(3) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+780.480 924.480 m 
+773.280 924.480 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+0.00 1.00 -1.00 0.00 764.05 930.30 Tm
+(4) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+766.080 938.880 m 
+766.080 931.680 l
+S
+1 J
+1 j
+0.72 w
+0.20 0.80 0.20 RG
+[] 0 d
+763.200 941.760 m 
+768.960 936.000 l
+763.200 936.000 m 
+768.960 941.760 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+767.52 924.48 m 767.52 925.28 766.88 925.92 766.08 925.92 c
+765.28 925.92 764.64 925.28 764.64 924.48 c
+764.64 923.68 765.28 923.04 766.08 923.04 c
+766.88 923.04 767.52 923.68 767.52 924.48 c
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+766.080 923.040 m 
+766.080 917.280 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 0.502 rg
+750.92 948.68 Td
+(U4) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+750.92 942.13 Td
+(AMC-U_FL) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+780.480 917.280 m 
+780.480 924.480 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+774.000 917.280 m 
+786.960 917.280 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+776.160 915.840 m 
+784.800 915.840 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+778.320 914.400 m 
+782.640 914.400 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+779.760 912.960 m 
+781.200 912.960 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+780.480 924.480 m 
+780.480 924.480 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+771.12 904.57 Td
+(GND) Tj
+ET
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+110.880 602.640 m 
+154.080 602.640 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+110.880 602.640 m 
+154.080 602.640 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+654.480 233.280 m 
+654.480 226.080 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+654.480 233.280 m 
+654.480 226.080 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+370.080 895.680 m 
+355.680 895.680 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+370.080 895.680 m 
+355.680 895.680 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+467.280 1090.080 m 
+514.080 1090.080 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+467.280 1090.080 m 
+514.080 1090.080 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+467.280 1104.480 m 
+514.080 1104.480 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+467.280 1104.480 m 
+514.080 1104.480 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+467.280 1068.480 m 
+474.480 1068.480 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+467.280 1068.480 m 
+474.480 1068.480 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+467.280 1061.280 m 
+474.480 1061.280 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+467.280 1061.280 m 
+474.480 1061.280 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+467.280 1046.880 m 
+474.480 1046.880 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+467.280 1046.880 m 
+474.480 1046.880 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+467.280 1054.080 m 
+474.480 1054.080 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+467.280 1054.080 m 
+474.480 1054.080 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+467.280 1039.680 m 
+474.480 1039.680 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+467.280 1039.680 m 
+474.480 1039.680 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+467.280 1075.680 m 
+474.480 1075.680 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+467.280 1075.680 m 
+474.480 1075.680 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+467.280 1032.480 m 
+474.480 1032.480 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+467.280 1032.480 m 
+474.480 1032.480 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+467.280 1097.280 m 
+474.480 1097.280 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+467.280 1097.280 m 
+474.480 1097.280 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+467.280 1082.880 m 
+560.880 1082.880 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+467.280 1082.880 m 
+560.880 1082.880 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+380.880 1039.680 m 
+370.080 1039.680 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+380.880 1039.680 m 
+370.080 1039.680 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+380.880 1046.880 m 
+370.080 1046.880 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+380.880 1046.880 m 
+370.080 1046.880 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+380.880 1054.080 m 
+370.080 1054.080 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+380.880 1054.080 m 
+370.080 1054.080 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+380.880 1061.280 m 
+370.080 1061.280 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+380.880 1061.280 m 
+370.080 1061.280 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+380.880 1068.480 m 
+370.080 1068.480 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+380.880 1068.480 m 
+370.080 1068.480 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+380.880 1075.680 m 
+370.080 1075.680 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+380.880 1075.680 m 
+370.080 1075.680 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+380.880 1093.680 m 
+380.880 1090.080 l
+S
+380.880 1097.280 m 
+380.880 1093.680 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+380.880 1093.680 m 
+380.880 1090.080 l
+S
+380.880 1097.280 m 
+380.880 1093.680 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+730.080 748.080 m 
+730.080 751.680 l
+S
+730.080 748.080 m 
+730.080 744.480 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+730.080 748.080 m 
+730.080 751.680 l
+S
+730.080 748.080 m 
+730.080 744.480 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+481.680 758.880 m 
+481.680 751.680 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+481.680 758.880 m 
+481.680 751.680 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+157.680 712.080 m 
+100.080 712.080 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+157.680 712.080 m 
+100.080 712.080 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+730.080 679.680 m 
+730.080 674.640 l
+S
+730.080 715.680 m 
+730.080 708.480 l
+S
+730.080 694.080 m 
+730.080 708.480 l
+S
+730.080 686.880 m 
+730.080 694.080 l
+S
+730.080 679.680 m 
+730.080 686.880 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+730.080 679.680 m 
+730.080 674.640 l
+S
+730.080 715.680 m 
+730.080 708.480 l
+S
+730.080 694.080 m 
+730.080 708.480 l
+S
+730.080 686.880 m 
+730.080 694.080 l
+S
+730.080 679.680 m 
+730.080 686.880 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+481.680 679.680 m 
+481.680 686.880 l
+S
+481.680 694.080 m 
+481.680 686.880 l
+S
+481.680 694.080 m 
+481.680 708.480 l
+S
+481.680 715.680 m 
+481.680 708.480 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+481.680 679.680 m 
+481.680 686.880 l
+S
+481.680 694.080 m 
+481.680 686.880 l
+S
+481.680 694.080 m 
+481.680 708.480 l
+S
+481.680 715.680 m 
+481.680 708.480 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+132.480 884.880 m 
+114.480 884.880 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+132.480 884.880 m 
+114.480 884.880 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+132.480 877.680 m 
+125.280 877.680 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+132.480 877.680 m 
+125.280 877.680 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+197.280 892.080 m 
+218.880 892.080 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+197.280 892.080 m 
+218.880 892.080 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+730.080 589.680 m 
+730.080 593.280 l
+S
+730.080 593.280 m 
+730.080 596.880 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+730.080 589.680 m 
+730.080 593.280 l
+S
+730.080 593.280 m 
+730.080 596.880 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+730.080 524.880 m 
+730.080 532.080 l
+S
+730.080 532.080 m 
+730.080 539.280 l
+S
+730.080 539.280 m 
+730.080 553.680 l
+S
+730.080 560.880 m 
+730.080 553.680 l
+S
+730.080 524.880 m 
+730.080 521.280 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+730.080 524.880 m 
+730.080 532.080 l
+S
+730.080 532.080 m 
+730.080 539.280 l
+S
+730.080 539.280 m 
+730.080 553.680 l
+S
+730.080 560.880 m 
+730.080 553.680 l
+S
+730.080 524.880 m 
+730.080 521.280 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+215.280 303.840 m 
+215.280 332.640 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+215.280 303.840 m 
+215.280 332.640 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+193.680 303.840 m 
+200.880 303.840 l
+S
+200.880 303.840 m 
+200.880 332.640 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+193.680 303.840 m 
+200.880 303.840 l
+S
+200.880 303.840 m 
+200.880 332.640 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+164.880 401.040 m 
+161.280 401.040 l
+S
+161.280 401.040 m 
+161.280 408.240 l
+S
+161.280 408.240 m 
+164.880 408.240 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+164.880 401.040 m 
+161.280 401.040 l
+S
+161.280 401.040 m 
+161.280 408.240 l
+S
+161.280 408.240 m 
+164.880 408.240 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+161.280 386.640 m 
+161.280 393.840 l
+S
+161.280 393.840 m 
+164.880 393.840 l
+S
+161.280 379.440 m 
+161.280 386.640 l
+S
+164.880 386.640 m 
+161.280 386.640 l
+S
+164.880 372.240 m 
+161.280 372.240 l
+S
+161.280 372.240 m 
+161.280 379.440 l
+S
+164.880 379.440 m 
+161.280 379.440 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+161.280 386.640 m 
+161.280 393.840 l
+S
+161.280 393.840 m 
+164.880 393.840 l
+S
+161.280 379.440 m 
+161.280 386.640 l
+S
+164.880 386.640 m 
+161.280 386.640 l
+S
+164.880 372.240 m 
+161.280 372.240 l
+S
+161.280 372.240 m 
+161.280 379.440 l
+S
+164.880 379.440 m 
+161.280 379.440 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+96.480 1090.080 m 
+96.480 1093.680 l
+S
+96.480 1093.680 m 
+96.480 1097.280 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+96.480 1090.080 m 
+96.480 1093.680 l
+S
+96.480 1093.680 m 
+96.480 1097.280 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+85.680 1075.680 m 
+96.480 1075.680 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+85.680 1075.680 m 
+96.480 1075.680 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+85.680 1068.480 m 
+96.480 1068.480 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+85.680 1068.480 m 
+96.480 1068.480 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+85.680 1061.280 m 
+96.480 1061.280 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+85.680 1061.280 m 
+96.480 1061.280 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+85.680 1054.080 m 
+96.480 1054.080 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+85.680 1054.080 m 
+96.480 1054.080 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+85.680 1046.880 m 
+96.480 1046.880 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+85.680 1046.880 m 
+96.480 1046.880 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+85.680 1039.680 m 
+96.480 1039.680 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+85.680 1039.680 m 
+96.480 1039.680 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+276.480 1082.880 m 
+182.880 1082.880 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+276.480 1082.880 m 
+182.880 1082.880 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+190.080 1097.280 m 
+182.880 1097.280 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+190.080 1097.280 m 
+182.880 1097.280 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+190.080 1032.480 m 
+182.880 1032.480 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+190.080 1032.480 m 
+182.880 1032.480 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+190.080 1075.680 m 
+182.880 1075.680 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+190.080 1075.680 m 
+182.880 1075.680 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+190.080 1039.680 m 
+182.880 1039.680 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+190.080 1039.680 m 
+182.880 1039.680 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+190.080 1054.080 m 
+182.880 1054.080 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+190.080 1054.080 m 
+182.880 1054.080 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+190.080 1046.880 m 
+182.880 1046.880 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+190.080 1046.880 m 
+182.880 1046.880 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+190.080 1061.280 m 
+182.880 1061.280 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+190.080 1061.280 m 
+182.880 1061.280 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+190.080 1068.480 m 
+182.880 1068.480 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+190.080 1068.480 m 
+182.880 1068.480 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+229.680 1104.480 m 
+182.880 1104.480 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+229.680 1104.480 m 
+182.880 1104.480 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+229.680 1090.080 m 
+182.880 1090.080 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+229.680 1090.080 m 
+182.880 1090.080 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+92.880 1082.880 m 
+96.480 1082.880 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+92.880 1082.880 m 
+96.480 1082.880 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+380.880 1082.880 m 
+377.280 1082.880 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+380.880 1082.880 m 
+377.280 1082.880 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+722.880 841.680 m 
+722.880 848.880 l
+S
+722.880 834.480 m 
+722.880 841.680 l
+S
+722.880 827.280 m 
+722.880 834.480 l
+S
+722.880 827.280 m 
+722.880 816.480 l
+S
+722.880 816.480 m 
+686.880 816.480 l
+S
+686.880 816.480 m 
+679.680 816.480 l
+S
+679.680 816.480 m 
+672.480 816.480 l
+S
+672.480 816.480 m 
+665.280 816.480 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+722.880 841.680 m 
+722.880 848.880 l
+S
+722.880 834.480 m 
+722.880 841.680 l
+S
+722.880 827.280 m 
+722.880 834.480 l
+S
+722.880 827.280 m 
+722.880 816.480 l
+S
+722.880 816.480 m 
+686.880 816.480 l
+S
+686.880 816.480 m 
+679.680 816.480 l
+S
+679.680 816.480 m 
+672.480 816.480 l
+S
+672.480 816.480 m 
+665.280 816.480 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+722.880 884.880 m 
+722.880 892.080 l
+S
+722.880 877.680 m 
+722.880 884.880 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+722.880 884.880 m 
+722.880 892.080 l
+S
+722.880 877.680 m 
+722.880 884.880 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+766.080 902.880 m 
+722.880 902.880 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+766.080 902.880 m 
+722.880 902.880 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+229.680 329.040 m 
+229.680 332.640 l
+S
+193.680 332.640 m 
+193.680 329.040 l
+S
+193.680 329.040 m 
+208.080 329.040 l
+S
+208.080 332.640 m 
+208.080 329.040 l
+S
+208.080 329.040 m 
+229.680 329.040 l
+S
+240.480 329.040 m 
+229.680 329.040 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+229.680 329.040 m 
+229.680 332.640 l
+S
+193.680 332.640 m 
+193.680 329.040 l
+S
+193.680 329.040 m 
+208.080 329.040 l
+S
+208.080 332.640 m 
+208.080 329.040 l
+S
+208.080 329.040 m 
+229.680 329.040 l
+S
+240.480 329.040 m 
+229.680 329.040 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+467.280 1111.680 m 
+467.280 1111.680 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+467.280 1111.680 m 
+467.280 1111.680 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+380.880 1104.480 m 
+380.880 1104.480 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+380.880 1104.480 m 
+380.880 1104.480 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+380.880 1111.680 m 
+380.880 1111.680 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+380.880 1111.680 m 
+380.880 1111.680 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+380.880 1032.480 m 
+380.880 1032.480 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+380.880 1032.480 m 
+380.880 1032.480 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+402.480 996.480 m 
+402.480 996.480 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+402.480 996.480 m 
+402.480 996.480 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+409.680 996.480 m 
+409.680 996.480 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+409.680 996.480 m 
+409.680 996.480 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+416.880 996.480 m 
+416.880 996.480 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+416.880 996.480 m 
+416.880 996.480 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+182.880 1111.680 m 
+182.880 1111.680 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+182.880 1111.680 m 
+182.880 1111.680 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+96.480 1104.480 m 
+96.480 1104.480 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+96.480 1104.480 m 
+96.480 1104.480 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+96.480 1111.680 m 
+96.480 1111.680 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+96.480 1111.680 m 
+96.480 1111.680 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+96.480 1032.480 m 
+96.480 1032.480 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+96.480 1032.480 m 
+96.480 1032.480 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+118.080 161.280 m 
+118.080 161.280 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+118.080 161.280 m 
+118.080 161.280 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+118.080 211.680 m 
+118.080 211.680 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+118.080 211.680 m 
+118.080 211.680 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+74.880 161.280 m 
+74.880 161.280 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+74.880 161.280 m 
+74.880 161.280 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+74.880 211.680 m 
+74.880 211.680 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+74.880 211.680 m 
+74.880 211.680 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+125.280 624.240 m 
+125.280 624.240 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+125.280 624.240 m 
+125.280 624.240 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+96.480 624.240 m 
+96.480 624.240 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+96.480 624.240 m 
+96.480 624.240 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+154.080 573.840 m 
+154.080 573.840 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+154.080 573.840 m 
+154.080 573.840 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+154.080 595.440 m 
+154.080 595.440 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+154.080 595.440 m 
+154.080 595.440 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+226.080 602.640 m 
+226.080 602.640 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+226.080 602.640 m 
+226.080 602.640 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+226.080 552.240 m 
+226.080 552.240 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+226.080 552.240 m 
+226.080 552.240 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+226.080 588.240 m 
+226.080 588.240 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+226.080 588.240 m 
+226.080 588.240 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+154.080 559.440 m 
+154.080 559.440 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+154.080 559.440 m 
+154.080 559.440 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+154.080 552.240 m 
+154.080 552.240 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+154.080 552.240 m 
+154.080 552.240 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+226.080 581.040 m 
+226.080 581.040 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+226.080 581.040 m 
+226.080 581.040 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+226.080 573.840 m 
+226.080 573.840 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+226.080 573.840 m 
+226.080 573.840 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+226.080 566.640 m 
+226.080 566.640 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+226.080 566.640 m 
+226.080 566.640 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+226.080 559.440 m 
+226.080 559.440 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+226.080 559.440 m 
+226.080 559.440 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+654.480 218.880 m 
+654.480 218.880 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+654.480 218.880 m 
+654.480 218.880 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+719.280 233.280 m 
+719.280 233.280 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+719.280 233.280 m 
+719.280 233.280 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+719.280 182.880 m 
+719.280 182.880 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+719.280 182.880 m 
+719.280 182.880 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+654.480 197.280 m 
+654.480 197.280 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+654.480 197.280 m 
+654.480 197.280 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+654.480 211.680 m 
+654.480 211.680 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+654.480 211.680 m 
+654.480 211.680 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+719.280 211.680 m 
+719.280 211.680 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+719.280 211.680 m 
+719.280 211.680 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+719.280 218.880 m 
+719.280 218.880 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+719.280 218.880 m 
+719.280 218.880 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+719.280 204.480 m 
+719.280 204.480 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+719.280 204.480 m 
+719.280 204.480 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+719.280 226.080 m 
+719.280 226.080 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+719.280 226.080 m 
+719.280 226.080 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+654.480 204.480 m 
+654.480 204.480 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+654.480 204.480 m 
+654.480 204.480 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+269.280 215.280 m 
+269.280 215.280 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+269.280 215.280 m 
+269.280 215.280 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+233.280 186.480 m 
+233.280 186.480 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+233.280 186.480 m 
+233.280 186.480 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+233.280 215.280 m 
+233.280 215.280 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+233.280 215.280 m 
+233.280 215.280 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+269.280 186.480 m 
+269.280 186.480 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+269.280 186.480 m 
+269.280 186.480 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+560.880 1111.680 m 
+560.880 1111.680 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+560.880 1111.680 m 
+560.880 1111.680 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+560.880 1054.080 m 
+560.880 1054.080 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+560.880 1054.080 m 
+560.880 1054.080 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+730.080 434.880 m 
+730.080 434.880 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+730.080 434.880 m 
+730.080 434.880 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+629.280 362.880 m 
+629.280 362.880 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+629.280 362.880 m 
+629.280 362.880 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+629.280 377.280 m 
+629.280 377.280 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+629.280 377.280 m 
+629.280 377.280 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+629.280 391.680 m 
+629.280 391.680 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+629.280 391.680 m 
+629.280 391.680 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+629.280 442.080 m 
+629.280 442.080 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+629.280 442.080 m 
+629.280 442.080 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+730.080 442.080 m 
+730.080 442.080 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+730.080 442.080 m 
+730.080 442.080 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+730.080 391.680 m 
+730.080 391.680 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+730.080 391.680 m 
+730.080 391.680 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+730.080 377.280 m 
+730.080 377.280 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+730.080 377.280 m 
+730.080 377.280 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+730.080 362.880 m 
+730.080 362.880 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+730.080 362.880 m 
+730.080 362.880 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+629.280 398.880 m 
+629.280 398.880 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+629.280 398.880 m 
+629.280 398.880 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+629.280 406.080 m 
+629.280 406.080 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+629.280 406.080 m 
+629.280 406.080 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+629.280 413.280 m 
+629.280 413.280 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+629.280 413.280 m 
+629.280 413.280 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+629.280 420.480 m 
+629.280 420.480 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+629.280 420.480 m 
+629.280 420.480 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+629.280 427.680 m 
+629.280 427.680 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+629.280 427.680 m 
+629.280 427.680 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+730.080 413.280 m 
+730.080 413.280 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+730.080 413.280 m 
+730.080 413.280 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+730.080 406.080 m 
+730.080 406.080 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+730.080 406.080 m 
+730.080 406.080 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+114.480 733.680 m 
+114.480 733.680 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+114.480 733.680 m 
+114.480 733.680 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+481.680 744.480 m 
+481.680 744.480 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+481.680 744.480 m 
+481.680 744.480 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+157.680 748.080 m 
+157.680 748.080 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+157.680 748.080 m 
+157.680 748.080 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+629.280 737.280 m 
+629.280 737.280 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+629.280 737.280 m 
+629.280 737.280 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+629.280 751.680 m 
+629.280 751.680 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+629.280 751.680 m 
+629.280 751.680 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+730.080 737.280 m 
+730.080 737.280 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+730.080 737.280 m 
+730.080 737.280 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+629.280 708.480 m 
+629.280 708.480 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+629.280 708.480 m 
+629.280 708.480 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+629.280 722.880 m 
+629.280 722.880 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+629.280 722.880 m 
+629.280 722.880 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+629.280 730.080 m 
+629.280 730.080 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+629.280 730.080 m 
+629.280 730.080 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+629.280 715.680 m 
+629.280 715.680 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+629.280 715.680 m 
+629.280 715.680 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+730.080 722.880 m 
+730.080 722.880 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+730.080 722.880 m 
+730.080 722.880 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+629.280 744.480 m 
+629.280 744.480 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+629.280 744.480 m 
+629.280 744.480 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+629.280 694.080 m 
+629.280 694.080 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+629.280 694.080 m 
+629.280 694.080 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+629.280 679.680 m 
+629.280 679.680 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+629.280 679.680 m 
+629.280 679.680 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+730.080 758.880 m 
+730.080 758.880 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+730.080 758.880 m 
+730.080 758.880 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+629.280 758.880 m 
+629.280 758.880 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+629.280 758.880 m 
+629.280 758.880 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+157.680 740.880 m 
+157.680 740.880 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+157.680 740.880 m 
+157.680 740.880 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+157.680 704.880 m 
+157.680 704.880 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+157.680 704.880 m 
+157.680 704.880 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+222.480 719.280 m 
+222.480 719.280 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+222.480 719.280 m 
+222.480 719.280 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+380.880 758.880 m 
+380.880 758.880 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+380.880 758.880 m 
+380.880 758.880 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+380.880 679.680 m 
+380.880 679.680 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+380.880 679.680 m 
+380.880 679.680 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+380.880 694.080 m 
+380.880 694.080 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+380.880 694.080 m 
+380.880 694.080 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+380.880 744.480 m 
+380.880 744.480 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+380.880 744.480 m 
+380.880 744.480 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+481.680 722.880 m 
+481.680 722.880 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+481.680 722.880 m 
+481.680 722.880 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+380.880 715.680 m 
+380.880 715.680 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+380.880 715.680 m 
+380.880 715.680 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+380.880 730.080 m 
+380.880 730.080 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+380.880 730.080 m 
+380.880 730.080 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+380.880 722.880 m 
+380.880 722.880 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+380.880 722.880 m 
+380.880 722.880 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+380.880 708.480 m 
+380.880 708.480 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+380.880 708.480 m 
+380.880 708.480 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+481.680 737.280 m 
+481.680 737.280 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+481.680 737.280 m 
+481.680 737.280 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+380.880 751.680 m 
+380.880 751.680 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+380.880 751.680 m 
+380.880 751.680 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+380.880 737.280 m 
+380.880 737.280 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+380.880 737.280 m 
+380.880 737.280 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+222.480 733.680 m 
+222.480 733.680 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+222.480 733.680 m 
+222.480 733.680 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+157.680 733.680 m 
+157.680 733.680 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+157.680 733.680 m 
+157.680 733.680 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+222.480 748.080 m 
+222.480 748.080 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+222.480 748.080 m 
+222.480 748.080 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+222.480 740.880 m 
+222.480 740.880 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+222.480 740.880 m 
+222.480 740.880 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+222.480 704.880 m 
+222.480 704.880 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+222.480 704.880 m 
+222.480 704.880 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+222.480 697.680 m 
+222.480 697.680 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+222.480 697.680 m 
+222.480 697.680 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+222.480 690.480 m 
+222.480 690.480 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+222.480 690.480 m 
+222.480 690.480 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+222.480 712.080 m 
+222.480 712.080 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+222.480 712.080 m 
+222.480 712.080 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+157.680 683.280 m 
+157.680 683.280 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+157.680 683.280 m 
+157.680 683.280 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+222.480 683.280 m 
+222.480 683.280 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+222.480 683.280 m 
+222.480 683.280 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+157.680 690.480 m 
+157.680 690.480 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+157.680 690.480 m 
+157.680 690.480 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+730.080 730.080 m 
+730.080 730.080 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+730.080 730.080 m 
+730.080 730.080 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+481.680 730.080 m 
+481.680 730.080 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+481.680 730.080 m 
+481.680 730.080 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+132.480 870.480 m 
+132.480 870.480 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+132.480 870.480 m 
+132.480 870.480 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+132.480 856.080 m 
+132.480 856.080 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+132.480 856.080 m 
+132.480 856.080 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+197.280 841.680 m 
+197.280 841.680 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+197.280 841.680 m 
+197.280 841.680 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+197.280 848.880 m 
+197.280 848.880 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+197.280 848.880 m 
+197.280 848.880 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+197.280 863.280 m 
+197.280 863.280 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+197.280 863.280 m 
+197.280 863.280 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+197.280 870.480 m 
+197.280 870.480 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+197.280 870.480 m 
+197.280 870.480 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+197.280 877.680 m 
+197.280 877.680 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+197.280 877.680 m 
+197.280 877.680 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+197.280 884.880 m 
+197.280 884.880 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+197.280 884.880 m 
+197.280 884.880 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+370.080 888.480 m 
+370.080 888.480 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+370.080 888.480 m 
+370.080 888.480 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+370.080 881.280 m 
+370.080 881.280 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+370.080 881.280 m 
+370.080 881.280 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+370.080 874.080 m 
+370.080 874.080 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+370.080 874.080 m 
+370.080 874.080 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+370.080 866.880 m 
+370.080 866.880 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+370.080 866.880 m 
+370.080 866.880 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+370.080 859.680 m 
+370.080 859.680 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+370.080 859.680 m 
+370.080 859.680 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+370.080 852.480 m 
+370.080 852.480 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+370.080 852.480 m 
+370.080 852.480 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+370.080 845.280 m 
+370.080 845.280 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+370.080 845.280 m 
+370.080 845.280 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+478.080 874.080 m 
+478.080 874.080 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+478.080 874.080 m 
+478.080 874.080 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+478.080 881.280 m 
+478.080 881.280 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+478.080 881.280 m 
+478.080 881.280 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+478.080 888.480 m 
+478.080 888.480 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+478.080 888.480 m 
+478.080 888.480 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+85.680 733.680 m 
+85.680 733.680 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+85.680 733.680 m 
+85.680 733.680 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+730.080 568.080 m 
+730.080 568.080 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+730.080 568.080 m 
+730.080 568.080 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+730.080 575.280 m 
+730.080 575.280 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+730.080 575.280 m 
+730.080 575.280 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+629.280 582.480 m 
+629.280 582.480 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+629.280 582.480 m 
+629.280 582.480 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+629.280 596.880 m 
+629.280 596.880 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+629.280 596.880 m 
+629.280 596.880 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+730.080 582.480 m 
+730.080 582.480 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+730.080 582.480 m 
+730.080 582.480 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+629.280 553.680 m 
+629.280 553.680 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+629.280 553.680 m 
+629.280 553.680 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+629.280 568.080 m 
+629.280 568.080 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+629.280 568.080 m 
+629.280 568.080 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+629.280 575.280 m 
+629.280 575.280 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+629.280 575.280 m 
+629.280 575.280 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+629.280 560.880 m 
+629.280 560.880 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+629.280 560.880 m 
+629.280 560.880 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+629.280 589.680 m 
+629.280 589.680 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+629.280 589.680 m 
+629.280 589.680 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+629.280 539.280 m 
+629.280 539.280 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+629.280 539.280 m 
+629.280 539.280 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+629.280 524.880 m 
+629.280 524.880 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+629.280 524.880 m 
+629.280 524.880 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+730.080 604.080 m 
+730.080 604.080 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+730.080 604.080 m 
+730.080 604.080 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+629.280 604.080 m 
+629.280 604.080 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+629.280 604.080 m 
+629.280 604.080 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+377.280 431.280 m 
+377.280 431.280 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+377.280 431.280 m 
+377.280 431.280 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+377.280 424.080 m 
+377.280 424.080 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+377.280 424.080 m 
+377.280 424.080 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+377.280 395.280 m 
+377.280 395.280 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+377.280 395.280 m 
+377.280 395.280 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+377.280 388.080 m 
+377.280 388.080 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+377.280 388.080 m 
+377.280 388.080 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+460.080 388.080 m 
+460.080 388.080 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+460.080 388.080 m 
+460.080 388.080 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+460.080 395.280 m 
+460.080 395.280 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+460.080 395.280 m 
+460.080 395.280 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+460.080 402.480 m 
+460.080 402.480 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+460.080 402.480 m 
+460.080 402.480 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+460.080 409.680 m 
+460.080 409.680 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+460.080 409.680 m 
+460.080 409.680 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+460.080 416.880 m 
+460.080 416.880 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+460.080 416.880 m 
+460.080 416.880 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+460.080 424.080 m 
+460.080 424.080 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+460.080 424.080 m 
+460.080 424.080 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+460.080 431.280 m 
+460.080 431.280 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+460.080 431.280 m 
+460.080 431.280 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+388.080 557.280 m 
+388.080 557.280 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+388.080 557.280 m 
+388.080 557.280 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+388.080 586.080 m 
+388.080 586.080 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+388.080 586.080 m 
+388.080 586.080 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+449.280 593.280 m 
+449.280 593.280 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+449.280 593.280 m 
+449.280 593.280 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+388.080 600.480 m 
+388.080 600.480 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+388.080 600.480 m 
+388.080 600.480 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+388.080 593.280 m 
+388.080 593.280 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+388.080 593.280 m 
+388.080 593.280 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+449.280 571.680 m 
+449.280 571.680 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+449.280 571.680 m 
+449.280 571.680 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+449.280 564.480 m 
+449.280 564.480 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+449.280 564.480 m 
+449.280 564.480 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+449.280 578.880 m 
+449.280 578.880 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+449.280 578.880 m 
+449.280 578.880 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+449.280 586.080 m 
+449.280 586.080 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+449.280 586.080 m 
+449.280 586.080 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+388.080 550.080 m 
+388.080 550.080 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+388.080 550.080 m 
+388.080 550.080 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+449.280 557.280 m 
+449.280 557.280 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+449.280 557.280 m 
+449.280 557.280 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+449.280 550.080 m 
+449.280 550.080 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+449.280 550.080 m 
+449.280 550.080 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+388.080 564.480 m 
+388.080 564.480 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+388.080 564.480 m 
+388.080 564.480 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+449.280 600.480 m 
+449.280 600.480 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+449.280 600.480 m 
+449.280 600.480 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+388.080 571.680 m 
+388.080 571.680 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+388.080 571.680 m 
+388.080 571.680 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+388.080 578.880 m 
+388.080 578.880 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+388.080 578.880 m 
+388.080 578.880 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+258.480 440.640 m 
+258.480 440.640 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+258.480 440.640 m 
+258.480 440.640 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+258.480 426.240 m 
+258.480 426.240 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+258.480 426.240 m 
+258.480 426.240 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+258.480 408.240 m 
+258.480 408.240 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+258.480 408.240 m 
+258.480 408.240 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+258.480 401.040 m 
+258.480 401.040 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+258.480 401.040 m 
+258.480 401.040 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+258.480 393.840 m 
+258.480 393.840 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+258.480 393.840 m 
+258.480 393.840 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+258.480 386.640 m 
+258.480 386.640 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+258.480 386.640 m 
+258.480 386.640 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+258.480 379.440 m 
+258.480 379.440 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+258.480 379.440 m 
+258.480 379.440 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+258.480 372.240 m 
+258.480 372.240 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+258.480 372.240 m 
+258.480 372.240 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+164.880 426.240 m 
+164.880 426.240 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+164.880 426.240 m 
+164.880 426.240 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+164.880 440.640 m 
+164.880 440.640 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+164.880 440.640 m 
+164.880 440.640 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+276.480 1054.080 m 
+276.480 1054.080 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+276.480 1054.080 m 
+276.480 1054.080 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+276.480 1111.680 m 
+276.480 1111.680 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+276.480 1111.680 m 
+276.480 1111.680 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+661.680 1111.680 m 
+661.680 1111.680 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+661.680 1111.680 m 
+661.680 1111.680 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+661.680 1054.080 m 
+661.680 1054.080 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+661.680 1054.080 m 
+661.680 1054.080 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+661.680 1082.880 m 
+661.680 1082.880 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+661.680 1082.880 m 
+661.680 1082.880 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+715.680 1111.680 m 
+715.680 1111.680 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+715.680 1111.680 m 
+715.680 1111.680 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+715.680 1082.880 m 
+715.680 1082.880 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+715.680 1082.880 m 
+715.680 1082.880 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+715.680 1054.080 m 
+715.680 1054.080 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+715.680 1054.080 m 
+715.680 1054.080 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+722.880 859.680 m 
+722.880 859.680 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+722.880 859.680 m 
+722.880 859.680 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+722.880 866.880 m 
+722.880 866.880 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+722.880 866.880 m 
+722.880 866.880 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+629.280 852.480 m 
+629.280 852.480 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+629.280 852.480 m 
+629.280 852.480 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+629.280 863.280 m 
+629.280 863.280 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+629.280 863.280 m 
+629.280 863.280 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+629.280 845.280 m 
+629.280 845.280 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+629.280 845.280 m 
+629.280 845.280 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+629.280 838.080 m 
+629.280 838.080 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+629.280 838.080 m 
+629.280 838.080 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+629.280 830.880 m 
+629.280 830.880 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+629.280 830.880 m 
+629.280 830.880 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+629.280 902.880 m 
+629.280 902.880 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+629.280 902.880 m 
+629.280 902.880 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+629.280 895.680 m 
+629.280 895.680 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+629.280 895.680 m 
+629.280 895.680 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+629.280 888.480 m 
+629.280 888.480 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+629.280 888.480 m 
+629.280 888.480 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+629.280 874.080 m 
+629.280 874.080 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+629.280 874.080 m 
+629.280 874.080 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+629.280 881.280 m 
+629.280 881.280 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+629.280 881.280 m 
+629.280 881.280 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+751.680 924.480 m 
+751.680 924.480 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+751.680 924.480 m 
+751.680 924.480 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+780.480 924.480 m 
+780.480 924.480 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+780.480 924.480 m 
+780.480 924.480 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+424.080 226.080 m 
+420.480 222.480 l
+409.680 222.480 l
+409.680 229.680 l
+420.480 229.680 l
+424.080 226.080 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+424.080 226.080 m 
+424.080 226.080 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+365.70 223.79 Td
+(E_INK_BUSY) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+424.080 218.880 m 
+420.480 215.280 l
+409.680 215.280 l
+409.680 222.480 l
+420.480 222.480 l
+424.080 218.880 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+424.080 218.880 m 
+424.080 218.880 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+366.48 216.59 Td
+(E_INK_NRST) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+424.080 211.680 m 
+420.480 208.080 l
+409.680 208.080 l
+409.680 215.280 l
+420.480 215.280 l
+424.080 211.680 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+424.080 211.680 m 
+424.080 211.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+370.08 209.39 Td
+(E_INK_D/C) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+424.080 204.480 m 
+420.480 200.880 l
+409.680 200.880 l
+409.680 208.080 l
+420.480 208.080 l
+424.080 204.480 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+424.080 204.480 m 
+424.080 204.480 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+373.68 202.19 Td
+(E_INK_CS) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+424.080 197.280 m 
+420.480 193.680 l
+409.680 193.680 l
+409.680 200.880 l
+420.480 200.880 l
+424.080 197.280 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+424.080 197.280 m 
+424.080 197.280 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+391.68 194.99 Td
+(SCK) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+424.080 190.080 m 
+420.480 186.480 l
+409.680 186.480 l
+409.680 193.680 l
+420.480 193.680 l
+424.080 190.080 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+424.080 190.080 m 
+424.080 190.080 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+388.08 187.79 Td
+(MOSI) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+413.280 175.680 m 
+420.480 175.680 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+413.280 172.080 m 
+413.280 179.280 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+420.480 175.680 m 
+420.480 175.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+398.88 173.39 Td
+(3V3) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+416.880 182.880 m 
+424.080 182.880 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+416.880 189.360 m 
+416.880 176.400 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+415.440 187.200 m 
+415.440 178.560 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+414.000 185.040 m 
+414.000 180.720 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+412.560 183.600 m 
+412.560 182.160 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+424.080 182.880 m 
+424.080 182.880 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+391.68 180.59 Td
+(GND) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+438.480 211.680 m 
+442.080 215.280 l
+452.880 215.280 l
+452.880 208.080 l
+442.080 208.080 l
+438.480 211.680 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+438.480 211.680 m 
+438.480 211.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+457.38 209.39 Td
+(P1.02) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+438.480 204.480 m 
+442.080 208.080 l
+452.880 208.080 l
+452.880 200.880 l
+442.080 200.880 l
+438.480 204.480 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+438.480 204.480 m 
+438.480 204.480 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+457.38 202.19 Td
+(P1.07) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+438.480 218.880 m 
+442.080 222.480 l
+452.880 222.480 l
+452.880 215.280 l
+442.080 215.280 l
+438.480 218.880 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+438.480 218.880 m 
+438.480 218.880 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+457.38 216.59 Td
+(P1.01) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+438.480 226.080 m 
+442.080 229.680 l
+452.880 229.680 l
+452.880 222.480 l
+442.080 222.480 l
+438.480 226.080 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+438.480 226.080 m 
+438.480 226.080 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+456.48 223.79 Td
+(P1.06) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+438.480 226.080 m 
+434.880 226.080 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+424.080 226.080 m 
+427.680 226.080 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+427.680 226.080 m 
+431.280 228.240 l
+434.880 226.080 l
+431.280 223.920 l
+427.680 226.080 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+424.080 226.080 m 
+424.080 226.080 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+424.080 226.080 m 
+424.080 226.080 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+438.480 218.880 m 
+434.880 218.880 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+424.080 218.880 m 
+427.680 218.880 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+427.680 218.880 m 
+431.280 221.040 l
+434.880 218.880 l
+431.280 216.720 l
+427.680 218.880 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+424.080 218.880 m 
+424.080 218.880 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+424.080 218.880 m 
+424.080 218.880 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+438.480 211.680 m 
+434.880 211.680 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+424.080 211.680 m 
+427.680 211.680 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+427.680 211.680 m 
+431.280 213.840 l
+434.880 211.680 l
+431.280 209.520 l
+427.680 211.680 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+424.080 211.680 m 
+424.080 211.680 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+424.080 211.680 m 
+424.080 211.680 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+438.480 204.480 m 
+434.880 204.480 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+424.080 204.480 m 
+427.680 204.480 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+427.680 204.480 m 
+431.280 206.640 l
+434.880 204.480 l
+431.280 202.320 l
+427.680 204.480 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+424.080 204.480 m 
+424.080 204.480 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+424.080 204.480 m 
+424.080 204.480 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+438.480 197.280 m 
+434.880 197.280 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+424.080 197.280 m 
+427.680 197.280 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+427.680 197.280 m 
+431.280 199.440 l
+434.880 197.280 l
+431.280 195.120 l
+427.680 197.280 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+424.080 197.280 m 
+424.080 197.280 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+424.080 197.280 m 
+424.080 197.280 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+438.480 190.080 m 
+434.880 190.080 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+424.080 190.080 m 
+427.680 190.080 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+427.680 190.080 m 
+431.280 192.240 l
+434.880 190.080 l
+431.280 187.920 l
+427.680 190.080 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+424.080 190.080 m 
+424.080 190.080 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+424.080 190.080 m 
+424.080 190.080 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+438.480 182.880 m 
+434.880 182.880 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+424.080 182.880 m 
+427.680 182.880 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+427.680 182.880 m 
+431.280 185.040 l
+434.880 182.880 l
+431.280 180.720 l
+427.680 182.880 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+424.080 182.880 m 
+424.080 182.880 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+424.080 182.880 m 
+424.080 182.880 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+438.480 175.680 m 
+434.880 175.680 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+424.080 175.680 m 
+427.680 175.680 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+427.680 175.680 m 
+431.280 177.840 l
+434.880 175.680 l
+431.280 173.520 l
+427.680 175.680 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+424.080 175.680 m 
+420.480 175.680 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+424.080 175.680 m 
+420.480 175.680 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+449.280 175.680 m 
+442.080 175.680 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+449.280 179.280 m 
+449.280 172.080 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+442.080 175.680 m 
+442.080 175.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+452.88 173.39 Td
+(VCC) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+445.680 182.880 m 
+438.480 182.880 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+445.680 189.360 m 
+445.680 176.400 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+447.120 187.200 m 
+447.120 178.560 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+448.560 185.040 m 
+448.560 180.720 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+450.000 183.600 m 
+450.000 182.160 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+438.480 182.880 m 
+438.480 182.880 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+452.74 180.59 Td
+(GND) Tj
+ET
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+438.480 182.880 m 
+438.480 182.880 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+438.480 182.880 m 
+438.480 182.880 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+438.480 175.680 m 
+442.080 175.680 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+438.480 175.680 m 
+442.080 175.680 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+438.480 226.080 m 
+438.480 226.080 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+438.480 226.080 m 
+438.480 226.080 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+438.480 218.880 m 
+438.480 218.880 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+438.480 218.880 m 
+438.480 218.880 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+438.480 211.680 m 
+438.480 211.680 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+438.480 211.680 m 
+438.480 211.680 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+438.480 204.480 m 
+438.480 204.480 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+438.480 204.480 m 
+438.480 204.480 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+438.480 197.280 m 
+442.080 200.880 l
+452.880 200.880 l
+452.880 193.680 l
+442.080 193.680 l
+438.480 197.280 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+438.480 197.280 m 
+438.480 197.280 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+457.42 194.99 Td
+(SCK) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+0.00 g
+[] 0 d
+438.480 190.080 m 
+442.080 193.680 l
+452.880 193.680 l
+452.880 186.480 l
+442.080 186.480 l
+438.480 190.080 l
+S
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+438.480 190.080 m 
+438.480 190.080 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+456.36 188.47 Td
+(MOSI) Tj
+ET
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+438.480 197.280 m 
+438.480 197.280 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+438.480 197.280 m 
+438.480 197.280 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+438.480 190.080 m 
+438.480 190.080 l
+S
+1 J
+1 j
+0.72 w
+0.00 0.53 0.00 RG
+0.00 g
+[] 0 d
+438.480 190.080 m 
+438.480 190.080 l
+S
+7.20 w
+BT
+/F2 8.181818181818182 Tf
+9.00 TL
+0.000 g
+395.28 249.32 Td
+(E-Ink Connections) Tj
+ET
+2 J
+0 j
+72 M
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+395.28 1126.08 57.60 -115.20 re
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+431.44 1109.75 Td
+(BATIN) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+457.20 1112.63 Td
+(25) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+467.280 1111.680 m 
+452.880 1111.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+436.18 1102.55 Td
+(GND) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+457.20 1105.43 Td
+(24) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+467.280 1104.480 m 
+452.880 1104.480 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+437.63 1095.35 Td
+(RST) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+457.20 1098.23 Td
+(23) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+467.280 1097.280 m 
+452.880 1097.280 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+425.98 1088.15 Td
+(3.3v Out) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+457.20 1091.03 Td
+(22) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+467.280 1090.080 m 
+452.880 1090.080 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+433.62 1059.35 Td
+(P1.15) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+457.20 1062.23 Td
+(18) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+467.280 1061.280 m 
+452.880 1061.280 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+433.62 1080.95 Td
+(P0.31) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+457.20 1083.83 Td
+(21) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+467.280 1082.880 m 
+452.880 1082.880 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+397.44 1102.55 Td
+(P0.08) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+387.32 1105.43 Td
+(3) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+380.880 1104.480 m 
+395.280 1104.480 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+433.62 1052.15 Td
+(P1.13) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+457.20 1055.03 Td
+(17) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+467.280 1054.080 m 
+452.880 1054.080 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+433.62 1073.75 Td
+(P0.29) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+457.20 1076.63 Td
+(20) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+467.280 1075.680 m 
+452.880 1075.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+433.62 1044.95 Td
+(P1.11) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+457.20 1047.83 Td
+(16) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+467.280 1046.880 m 
+452.880 1046.880 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+433.62 1066.55 Td
+(P0.02) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+457.20 1069.43 Td
+(19) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+467.280 1068.480 m 
+452.880 1068.480 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+397.44 1095.35 Td
+(GND) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+387.32 1098.23 Td
+(4) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+380.880 1097.280 m 
+395.280 1097.280 l
+S
+0.72 w
+0.63 0.00 0.00 RG
+395.28 1111.68 m 395.28 1112.87 394.31 1113.84 393.12 1113.84 c
+391.93 1113.84 390.96 1112.87 390.96 1111.68 c
+390.96 1110.49 391.93 1109.52 393.12 1109.52 c
+394.31 1109.52 395.28 1110.49 395.28 1111.68 c
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+397.44 1109.75 Td
+(P0.06) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+387.32 1112.63 Td
+(2) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+380.880 1111.680 m 
+390.960 1111.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+433.62 1030.55 Td
+(P0.09) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+457.20 1033.43 Td
+(14) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+467.280 1032.480 m 
+452.880 1032.480 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+433.62 1037.75 Td
+(P0.10) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+457.20 1040.63 Td
+(15) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+467.280 1039.680 m 
+452.880 1039.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+397.44 1037.75 Td
+(P1.04) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+383.68 1040.63 Td
+(12) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+380.880 1039.680 m 
+395.280 1039.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+397.44 1088.15 Td
+(GND) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+387.32 1091.03 Td
+(5) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+380.880 1090.080 m 
+395.280 1090.080 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+397.44 1080.95 Td
+(P0.17) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+387.32 1083.83 Td
+(6) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+380.880 1082.880 m 
+395.280 1082.880 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+397.44 1073.75 Td
+(P0.20) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+387.32 1076.63 Td
+(7) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+380.880 1075.680 m 
+395.280 1075.680 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+397.44 1066.55 Td
+(P0.22) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+387.32 1069.43 Td
+(8) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+380.880 1068.480 m 
+395.280 1068.480 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+397.44 1059.35 Td
+(P0.24) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+387.32 1062.23 Td
+(9) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+380.880 1061.280 m 
+395.280 1061.280 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+397.44 1052.15 Td
+(P1.00) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+383.68 1055.03 Td
+(10) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+380.880 1054.080 m 
+395.280 1054.080 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+397.44 1044.95 Td
+(P0.11) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+383.68 1047.83 Td
+(11) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+380.880 1046.880 m 
+395.280 1046.880 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+397.44 1030.55 Td
+(P1.06) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+383.68 1033.43 Td
+(13) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+380.880 1032.480 m 
+395.280 1032.480 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+0.00 1.00 -1.00 0.00 403.33 1011.96 Tm
+(P1.01) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+0.00 1.00 -1.00 0.00 400.45 998.20 Tm
+(27) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+402.480 996.480 m 
+402.480 1010.880 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+0.00 1.00 -1.00 0.00 410.53 1011.96 Tm
+(P1.02) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+0.00 1.00 -1.00 0.00 407.65 998.20 Tm
+(28) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+409.680 996.480 m 
+409.680 1010.880 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+0.00 1.00 -1.00 0.00 417.73 1011.96 Tm
+(P1.07) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+0.00 1.00 -1.00 0.00 414.85 998.20 Tm
+(29) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+416.880 996.480 m 
+416.880 1010.880 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+431.44 1116.95 Td
+(BATIN) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+457.20 1119.83 Td
+(26) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+467.280 1118.880 m 
+452.880 1118.880 l
+S
+1 J
+1 j
+0.72 w
+0.20 0.80 0.20 RG
+[] 0 d
+464.400 1121.760 m 
+470.160 1116.000 l
+464.400 1116.000 m 
+470.160 1121.760 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+397.44 1116.95 Td
+(GND) Tj
+ET
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+387.32 1119.83 Td
+(1) Tj
+ET
+1 J
+1 j
+0.72 w
+0.63 0.00 0.00 RG
+[] 0 d
+380.880 1118.880 m 
+395.280 1118.880 l
+S
+1 J
+1 j
+0.72 w
+0.20 0.80 0.20 RG
+[] 0 d
+378.000 1121.760 m 
+383.760 1116.000 l
+378.000 1116.000 m 
+383.760 1121.760 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 0.000 1.000 rg
+419.78 1128.78 Td
+(PRO_MICRO_NRF52840_29P) Tj
+ET
+q
+1 0 0 1 762.48 1112.40 cm
+1.0000 0.0000 0.0000 1.0000 0 0 cm
+1 0 0 1 0.00 0.00 cm
+72.00 0 0 72.00 0 0 cm
+/I0 Do
+Q
+q
+0.16 0.16 0.23 rg
+[] 0 d
+348.612 35.359 m 
+470.980 35.359 l
+470.980 11.520 l
+348.612 11.520 l
+348.612 35.359 l
+f
+0.40 0.92 0.58 rg
+[] 0 d
+381.817 11.520 m 
+351.787 11.520 l
+349.935 11.520 348.480 12.977 348.480 14.831 c
+348.480 32.048 l
+348.480 33.902 349.935 35.359 351.787 35.359 c
+381.817 35.359 l
+381.817 11.520 l
+f
+0.16 0.16 0.23 rg
+[] 0 d
+368.720 30.591 m 
+368.720 30.591 l
+368.588 30.591 l
+368.588 30.591 l
+368.588 30.591 l
+368.456 30.591 l
+368.456 30.458 l
+368.456 30.458 l
+368.456 30.458 l
+368.323 30.458 l
+368.323 30.326 368.323 30.326 368.323 30.326 c
+368.191 30.326 l
+368.191 30.326 l
+368.191 30.326 368.191 30.326 368.191 30.326 c
+368.191 30.326 l
+368.059 30.194 l
+368.059 30.194 l
+368.059 30.194 368.059 30.194 368.059 30.194 c
+368.059 30.194 l
+368.059 30.061 l
+368.059 30.061 368.059 30.061 367.927 30.061 c
+367.927 30.061 l
+367.927 29.929 l
+367.927 29.929 l
+367.927 29.929 367.927 29.929 367.927 29.929 c
+367.794 29.796 l
+367.794 29.796 l
+367.794 29.664 l
+367.794 29.664 367.794 29.664 367.662 29.664 c
+367.662 29.664 l
+367.662 29.664 l
+367.662 29.531 367.662 29.531 367.662 29.531 c
+367.662 29.531 l
+367.662 29.531 l
+367.530 29.531 l
+367.530 29.399 367.530 29.399 367.530 29.399 c
+367.530 29.399 l
+367.397 29.266 l
+367.397 29.266 l
+367.397 29.134 367.397 29.134 367.397 29.266 c
+367.397 29.134 l
+367.397 29.134 l
+367.397 29.134 367.397 29.134 367.265 29.134 c
+367.265 29.002 l
+367.265 29.002 l
+367.265 29.002 l
+367.133 28.869 l
+367.133 28.869 l
+367.133 28.737 l
+367.133 28.737 367.133 28.737 367.133 28.737 c
+367.001 28.737 l
+367.001 28.604 l
+367.001 28.604 l
+367.001 28.604 367.001 28.604 367.001 28.604 c
+366.868 28.472 l
+366.868 28.472 l
+366.868 28.339 l
+366.868 28.339 l
+366.868 28.339 366.868 28.339 366.736 28.339 c
+366.736 28.339 l
+366.736 28.207 366.736 28.207 366.736 28.207 c
+366.736 28.207 l
+366.736 28.207 l
+366.736 28.075 l
+366.736 28.075 366.736 28.075 366.604 28.075 c
+366.604 28.075 l
+366.604 27.942 l
+366.471 27.942 l
+366.471 27.810 l
+366.471 27.810 366.471 27.810 366.471 27.810 c
+366.471 27.810 l
+366.471 27.810 366.471 27.810 366.471 27.810 c
+366.339 27.677 l
+366.339 27.677 l
+366.339 27.677 l
+366.339 27.545 366.339 27.545 366.339 27.545 c
+366.339 27.545 l
+366.207 27.545 l
+366.207 27.412 l
+366.207 27.412 366.207 27.412 366.207 27.412 c
+366.074 27.412 l
+366.074 27.280 l
+366.074 27.280 366.207 27.280 366.074 27.280 c
+366.074 27.280 l
+366.074 27.280 l
+366.074 27.148 l
+366.074 27.148 366.074 27.148 365.942 27.148 c
+365.942 27.015 l
+365.942 27.015 l
+365.942 26.883 l
+365.942 26.883 365.942 26.883 365.810 26.883 c
+365.810 26.883 l
+365.810 26.883 l
+365.810 26.750 365.810 26.750 365.810 26.750 c
+365.678 26.750 l
+365.678 26.750 l
+365.678 26.618 l
+365.678 26.618 l
+365.545 26.485 l
+365.545 26.485 l
+365.545 26.353 365.545 26.485 365.545 26.485 c
+365.545 26.353 l
+365.545 26.353 l
+365.413 26.353 l
+365.413 26.220 365.413 26.220 365.413 26.220 c
+365.413 26.220 l
+365.281 26.220 l
+365.281 26.088 l
+365.281 26.088 l
+365.281 25.956 365.281 25.956 365.281 25.956 c
+365.281 25.956 l
+365.281 25.956 365.281 25.956 365.148 25.956 c
+365.148 25.956 l
+365.148 25.823 l
+365.148 25.823 l
+365.148 25.823 365.148 25.823 365.148 25.823 c
+365.016 25.691 l
+365.016 25.691 l
+365.016 25.558 l
+364.884 25.558 l
+364.884 25.558 365.016 25.558 364.884 25.558 c
+364.884 25.558 l
+364.884 25.426 364.884 25.426 364.884 25.426 c
+364.884 25.426 l
+364.884 25.426 l
+364.752 25.293 l
+364.752 25.293 364.752 25.293 364.752 25.293 c
+364.752 25.293 l
+364.752 25.161 l
+364.619 25.161 l
+364.619 25.029 364.619 25.029 364.619 25.029 c
+364.619 25.029 l
+364.619 25.029 l
+364.619 25.029 364.619 25.029 364.487 25.029 c
+364.487 24.896 l
+364.487 24.896 l
+364.487 24.896 l
+364.487 24.764 364.487 24.896 364.487 24.896 c
+364.355 24.764 l
+364.355 24.764 l
+364.355 24.631 l
+364.355 24.631 364.355 24.631 364.355 24.631 c
+364.222 24.631 l
+364.222 24.499 l
+364.222 24.499 364.222 24.499 364.222 24.499 c
+364.222 24.499 l
+364.090 24.366 l
+364.090 24.366 l
+364.090 24.234 l
+364.090 24.234 l
+363.958 24.234 l
+363.958 24.101 364.090 24.101 363.958 24.101 c
+363.958 24.101 l
+363.958 24.101 l
+363.958 23.969 l
+363.958 23.969 363.958 23.969 363.826 23.969 c
+363.826 23.969 l
+363.826 23.837 l
+363.693 23.837 l
+363.693 23.704 l
+363.693 23.704 l
+363.693 23.572 363.693 23.704 363.693 23.704 c
+363.561 23.572 l
+363.561 23.572 l
+363.561 23.572 l
+363.561 23.439 363.561 23.439 363.561 23.439 c
+363.561 23.439 l
+363.429 23.439 l
+363.429 23.307 l
+363.429 23.307 l
+363.429 23.174 363.429 23.174 363.296 23.174 c
+363.296 23.174 l
+363.296 23.174 363.429 23.174 363.296 23.174 c
+363.296 23.174 l
+363.296 23.042 l
+363.296 23.042 l
+363.296 23.042 363.296 23.042 363.164 23.042 c
+363.164 22.910 l
+363.164 22.910 l
+363.164 22.777 l
+363.164 22.777 363.164 22.777 363.032 22.777 c
+363.032 22.777 l
+363.032 22.777 l
+363.032 22.645 363.032 22.645 363.032 22.645 c
+362.900 22.645 l
+362.900 22.645 l
+362.900 22.645 l
+362.900 22.512 362.900 22.512 362.900 22.512 c
+362.900 22.512 l
+362.767 22.380 l
+362.767 22.380 l
+362.767 22.247 362.767 22.247 362.767 22.380 c
+362.767 22.247 l
+362.767 22.247 l
+362.767 22.247 362.767 22.247 362.635 22.247 c
+362.635 22.115 l
+362.635 22.115 l
+362.503 22.115 l
+362.503 21.982 l
+362.503 21.982 l
+362.503 21.850 l
+362.503 21.850 362.503 21.850 362.370 21.850 c
+362.370 21.850 l
+362.370 21.718 l
+362.370 21.718 l
+362.370 21.718 362.370 21.718 362.370 21.718 c
+362.238 21.585 l
+362.238 21.585 l
+362.238 21.453 l
+362.106 21.453 l
+362.106 21.453 l
+362.106 21.320 362.106 21.320 362.106 21.320 c
+362.106 21.320 l
+362.106 21.320 l
+361.974 21.188 l
+361.974 21.188 362.106 21.188 361.974 21.188 c
+361.974 21.188 l
+361.974 21.055 l
+361.841 21.055 l
+361.841 20.923 l
+361.841 20.923 361.841 20.923 361.841 20.923 c
+361.841 20.923 l
+361.841 20.923 361.841 20.923 361.709 20.923 c
+361.709 20.791 l
+361.709 20.791 l
+361.709 20.791 l
+361.709 20.658 361.709 20.658 361.709 20.658 c
+361.577 20.658 l
+361.577 20.658 l
+361.577 20.526 l
+361.577 20.526 361.577 20.526 361.577 20.526 c
+361.444 20.526 l
+361.444 20.393 l
+361.444 20.393 361.444 20.393 361.444 20.393 c
+361.444 20.393 l
+361.444 20.393 l
+361.312 20.261 l
+361.444 20.261 361.444 20.261 361.312 20.261 c
+361.312 20.128 l
+361.312 20.128 l
+361.180 19.996 l
+361.180 19.996 361.312 19.996 361.180 19.996 c
+361.180 19.996 l
+361.180 19.996 l
+361.180 19.863 361.180 19.863 361.180 19.863 c
+361.047 19.863 l
+361.047 19.863 l
+361.047 19.731 l
+360.915 19.731 l
+360.915 19.599 l
+360.915 19.599 l
+360.915 19.466 360.915 19.599 360.915 19.599 c
+360.783 19.466 l
+360.783 19.466 l
+360.783 19.466 l
+360.783 19.334 360.783 19.334 360.783 19.334 c
+360.783 19.334 l
+360.651 19.334 l
+360.651 19.201 l
+360.651 19.201 l
+360.651 19.069 l
+360.651 19.069 360.651 19.069 360.518 19.069 c
+360.518 19.069 l
+360.518 18.936 l
+360.518 18.936 l
+360.518 18.936 360.518 18.936 360.386 18.936 c
+360.386 18.804 l
+360.386 18.804 l
+360.386 18.672 l
+360.254 18.672 l
+360.254 18.672 360.254 18.672 360.254 18.672 c
+360.254 18.672 l
+360.254 18.539 360.254 18.539 360.254 18.539 c
+360.121 18.539 l
+360.121 18.539 l
+360.121 18.407 l
+360.121 18.407 360.121 18.407 360.121 18.407 c
+360.121 18.407 l
+359.989 18.274 l
+359.989 18.274 l
+359.989 18.142 359.989 18.142 359.989 18.142 c
+359.989 18.142 l
+359.989 18.142 l
+359.989 18.142 359.989 18.142 359.857 18.142 c
+359.857 18.009 l
+359.857 18.009 l
+359.857 18.009 l
+359.857 17.877 359.857 18.009 359.725 18.009 c
+359.725 17.877 l
+359.725 17.877 l
+359.725 17.745 l
+359.725 17.745 359.725 17.745 359.592 17.745 c
+359.592 17.745 l
+359.592 17.612 l
+359.592 17.612 359.592 17.612 359.592 17.612 c
+359.592 17.612 l
+359.592 17.612 l
+359.592 17.480 l
+359.592 17.480 l
+359.725 17.480 359.592 17.480 359.592 17.347 c
+359.725 17.347 l
+359.725 17.347 l
+359.725 17.347 359.725 17.347 359.725 17.347 c
+359.725 17.215 l
+359.857 17.215 l
+359.857 17.215 l
+359.989 17.215 l
+359.989 17.082 l
+360.121 17.082 359.989 17.215 359.989 17.082 c
+360.121 17.082 l
+360.121 17.082 360.121 17.082 360.121 17.082 c
+360.121 17.082 l
+360.121 17.082 l
+360.254 16.950 l
+360.254 16.950 360.254 16.950 360.254 16.950 c
+360.386 16.950 l
+360.386 16.817 l
+360.386 16.817 l
+360.518 16.817 l
+360.518 16.817 360.518 16.817 360.518 16.817 c
+360.518 16.817 l
+360.651 16.817 360.651 16.817 360.518 16.685 c
+360.651 16.685 l
+360.651 16.685 l
+360.651 16.685 l
+360.783 16.685 360.783 16.685 360.783 16.685 c
+360.783 16.553 l
+360.783 16.553 l
+360.915 16.553 l
+360.915 16.420 l
+361.047 16.420 361.047 16.553 361.047 16.420 c
+361.047 16.420 l
+361.047 16.420 361.047 16.420 361.047 16.420 c
+361.047 16.420 l
+361.180 16.420 l
+361.180 16.288 l
+361.180 16.288 361.180 16.288 361.180 16.288 c
+361.312 16.288 l
+361.312 16.155 l
+361.312 16.155 l
+361.444 16.155 l
+361.444 16.288 l
+361.577 16.288 l
+361.577 16.420 l
+361.577 16.420 361.577 16.420 361.577 16.420 c
+361.577 16.420 l
+361.577 16.420 l
+361.577 16.553 361.577 16.553 361.709 16.553 c
+361.709 16.553 l
+361.709 16.685 l
+361.709 16.685 l
+361.841 16.685 l
+361.841 16.817 l
+361.841 16.817 361.841 16.817 361.841 16.817 c
+361.841 16.817 l
+361.841 16.950 361.841 16.950 361.974 16.817 c
+361.974 16.950 l
+361.974 16.950 l
+361.974 16.950 l
+361.974 17.082 361.974 17.082 361.974 17.082 c
+362.106 17.082 l
+362.106 17.082 l
+362.106 17.215 l
+362.106 17.215 l
+362.106 17.347 362.106 17.347 362.238 17.347 c
+362.238 17.347 l
+362.238 17.347 362.238 17.347 362.238 17.347 c
+362.238 17.347 l
+362.238 17.480 l
+362.370 17.480 l
+362.370 17.480 362.238 17.480 362.370 17.480 c
+362.370 17.612 l
+362.370 17.612 l
+362.503 17.745 l
+362.503 17.745 l
+362.503 17.745 362.503 17.745 362.503 17.745 c
+362.503 17.745 l
+362.503 17.877 362.503 17.877 362.503 17.877 c
+362.635 17.877 l
+362.635 17.877 l
+362.635 18.009 l
+362.635 18.009 362.635 18.009 362.635 18.009 c
+362.767 18.009 l
+362.767 18.142 l
+362.767 18.142 l
+362.767 18.142 l
+362.767 18.274 362.767 18.274 362.900 18.274 c
+362.900 18.274 l
+362.900 18.274 362.900 18.274 362.900 18.274 c
+362.900 18.407 l
+362.900 18.407 l
+363.032 18.539 l
+363.032 18.539 l
+363.032 18.539 l
+363.164 18.672 l
+363.032 18.672 363.032 18.672 363.164 18.672 c
+363.164 18.672 l
+363.164 18.804 l
+363.164 18.804 363.164 18.804 363.164 18.804 c
+363.296 18.804 l
+363.296 18.936 l
+363.296 18.936 l
+363.296 19.069 l
+363.429 19.069 l
+363.429 19.069 363.429 19.069 363.429 19.069 c
+363.429 19.201 l
+363.429 19.201 363.429 19.201 363.429 19.201 c
+363.561 19.201 l
+363.561 19.201 l
+363.561 19.334 l
+363.561 19.334 363.561 19.334 363.561 19.334 c
+363.561 19.334 l
+363.693 19.466 l
+363.693 19.466 l
+363.693 19.599 l
+363.693 19.599 363.693 19.599 363.693 19.599 c
+363.693 19.599 l
+363.693 19.731 363.693 19.599 363.826 19.599 c
+363.826 19.731 l
+363.826 19.731 l
+363.826 19.731 l
+363.826 19.863 363.826 19.863 363.958 19.863 c
+363.958 19.863 l
+363.958 19.863 l
+363.958 19.996 l
+364.090 19.996 l
+364.090 20.128 364.090 20.128 364.090 20.128 c
+364.090 20.128 l
+364.090 20.128 364.090 20.128 364.090 20.128 c
+364.090 20.128 l
+364.090 20.261 l
+364.222 20.261 l
+364.222 20.261 364.222 20.261 364.222 20.261 c
+364.222 20.393 l
+364.355 20.393 l
+364.355 20.393 l
+364.355 20.526 l
+364.355 20.526 364.355 20.526 364.355 20.526 c
+364.355 20.526 l
+364.355 20.658 364.355 20.658 364.487 20.658 c
+364.487 20.658 l
+364.487 20.791 l
+364.487 20.791 l
+364.619 20.791 l
+364.619 20.923 l
+364.619 20.923 l
+364.619 21.055 364.619 21.055 364.752 21.055 c
+364.752 21.055 l
+364.752 21.055 l
+364.752 21.055 364.752 21.055 364.752 21.055 c
+364.752 21.188 l
+364.884 21.188 l
+364.884 21.188 l
+364.884 21.320 l
+364.884 21.320 l
+364.884 21.453 364.884 21.453 365.016 21.453 c
+365.016 21.453 l
+365.016 21.453 365.016 21.453 365.016 21.453 c
+365.016 21.453 l
+365.016 21.585 l
+365.016 21.585 365.016 21.585 365.148 21.585 c
+365.148 21.585 l
+365.148 21.718 l
+365.148 21.718 l
+365.281 21.850 l
+365.281 21.850 l
+365.281 21.850 365.281 21.850 365.281 21.850 c
+365.281 21.850 l
+365.281 21.982 365.281 21.982 365.281 21.982 c
+365.413 21.982 l
+365.413 21.982 l
+365.413 22.115 l
+365.413 22.115 365.413 22.115 365.413 22.115 c
+365.545 22.115 l
+365.545 22.247 l
+365.545 22.247 l
+365.545 22.380 l
+365.545 22.380 365.545 22.380 365.678 22.380 c
+365.678 22.380 l
+365.678 22.512 365.545 22.380 365.678 22.380 c
+365.678 22.512 l
+365.678 22.512 l
+365.678 22.512 l
+365.678 22.645 365.678 22.645 365.810 22.645 c
+365.810 22.645 l
+365.810 22.645 l
+365.942 22.777 l
+365.942 22.777 l
+365.942 22.910 365.942 22.910 365.942 22.910 c
+365.942 22.910 l
+365.942 22.910 365.942 22.910 365.942 22.910 c
+366.074 22.910 l
+366.074 23.042 l
+366.074 23.042 l
+366.074 23.174 l
+366.207 23.174 l
+366.207 23.307 l
+366.207 23.307 366.207 23.307 366.207 23.307 c
+366.339 23.307 l
+366.339 23.307 l
+366.339 23.439 366.207 23.439 366.339 23.439 c
+366.339 23.439 l
+366.339 23.439 l
+366.471 23.572 l
+366.471 23.572 l
+366.471 23.704 l
+366.471 23.704 l
+366.471 23.837 366.471 23.837 366.604 23.704 c
+366.604 23.837 l
+366.604 23.837 l
+366.604 23.837 366.604 23.837 366.604 23.837 c
+366.736 23.969 l
+366.736 23.969 l
+366.736 23.969 l
+366.736 24.101 l
+366.868 24.101 l
+366.868 24.234 366.736 24.234 366.868 24.234 c
+366.868 24.234 l
+366.868 24.234 366.868 24.234 366.868 24.234 c
+366.868 24.234 l
+366.868 24.366 l
+367.001 24.366 l
+367.001 24.366 367.001 24.366 367.001 24.366 c
+367.001 24.499 l
+367.133 24.499 l
+367.133 24.631 l
+367.133 24.631 l
+367.133 24.631 367.133 24.631 367.133 24.631 c
+367.133 24.631 l
+367.133 24.764 367.133 24.764 367.265 24.764 c
+367.265 24.764 l
+367.265 24.764 l
+367.265 24.896 l
+367.265 24.896 367.265 24.896 367.265 24.896 c
+367.397 24.896 l
+367.397 25.029 l
+367.397 25.029 l
+367.530 25.029 l
+367.530 25.161 367.397 25.161 367.530 25.161 c
+367.530 25.161 l
+367.530 25.161 367.530 25.161 367.530 25.161 c
+367.530 25.293 l
+367.530 25.293 l
+367.662 25.293 l
+367.662 25.426 367.530 25.426 367.662 25.426 c
+367.662 25.426 l
+367.662 25.426 l
+367.794 25.558 l
+367.794 25.558 367.794 25.558 367.794 25.558 c
+367.794 25.558 l
+367.794 25.691 l
+367.794 25.691 367.794 25.691 367.927 25.691 c
+367.927 25.691 l
+367.927 25.823 l
+367.927 25.823 l
+368.059 25.956 l
+368.059 25.956 l
+368.059 26.088 l
+368.059 26.088 368.059 26.088 368.059 26.088 c
+368.191 26.088 l
+368.191 26.088 l
+368.191 26.220 368.191 26.220 368.191 26.220 c
+368.191 26.220 l
+368.191 26.220 l
+368.323 26.353 l
+368.323 26.353 l
+368.323 26.485 l
+368.323 26.485 368.323 26.485 368.456 26.485 c
+368.456 26.485 l
+368.456 26.618 368.323 26.485 368.456 26.485 c
+368.456 26.618 l
+368.456 26.618 l
+368.456 26.618 l
+368.456 26.750 368.456 26.750 368.588 26.750 c
+368.588 26.750 l
+368.588 26.750 l
+368.720 26.883 l
+368.720 26.883 l
+368.720 27.015 368.720 27.015 368.720 27.015 c
+368.720 27.015 l
+368.720 27.015 368.720 27.015 368.720 27.015 c
+368.853 27.015 l
+368.853 27.148 l
+368.853 27.148 l
+368.853 27.148 368.853 27.148 368.853 27.148 c
+368.853 27.280 l
+368.985 27.280 l
+368.985 27.280 l
+368.985 27.412 l
+368.985 27.412 368.985 27.412 369.117 27.412 c
+369.117 27.412 l
+369.117 27.545 l
+369.117 27.545 l
+369.117 27.545 l
+369.249 27.412 l
+369.249 27.412 l
+369.249 27.412 369.249 27.412 369.249 27.412 c
+369.249 27.280 l
+369.249 27.280 l
+369.382 27.280 369.382 27.280 369.382 27.280 c
+369.382 27.148 l
+369.382 27.148 l
+369.514 27.015 l
+369.514 27.015 369.514 27.015 369.514 27.015 c
+369.514 27.015 l
+369.514 27.015 l
+369.514 26.883 l
+369.646 26.883 369.646 26.883 369.646 26.883 c
+369.646 26.750 l
+369.646 26.750 l
+369.646 26.750 l
+369.779 26.750 369.779 26.750 369.779 26.618 c
+369.779 26.618 l
+369.779 26.618 l
+369.779 26.485 l
+369.911 26.485 369.911 26.618 369.911 26.485 c
+369.911 26.485 l
+369.911 26.485 369.911 26.485 369.911 26.485 c
+369.911 26.353 l
+369.911 26.353 l
+370.043 26.220 l
+370.043 26.220 l
+370.043 26.220 l
+370.175 26.220 370.043 26.220 370.043 26.088 c
+370.043 26.088 l
+370.175 26.088 l
+370.175 26.088 370.175 26.088 370.175 26.088 c
+370.175 25.956 l
+370.308 25.956 l
+370.308 25.823 l
+370.308 25.823 370.308 25.823 370.308 25.823 c
+370.308 25.691 l
+370.308 25.691 l
+370.440 25.691 l
+370.440 25.691 370.440 25.691 370.440 25.691 c
+370.440 25.558 l
+370.440 25.558 l
+370.572 25.426 l
+370.572 25.426 370.572 25.426 370.572 25.426 c
+370.572 25.426 l
+370.572 25.293 l
+370.705 25.293 l
+370.705 25.293 370.705 25.293 370.705 25.293 c
+370.705 25.293 l
+370.705 25.293 370.705 25.293 370.705 25.161 c
+370.705 25.161 l
+370.837 25.161 l
+370.837 25.029 l
+370.837 25.029 l
+370.969 25.029 370.837 25.029 370.837 24.896 c
+370.969 24.896 l
+370.969 24.896 l
+370.969 24.896 l
+370.969 24.896 370.969 24.896 370.969 24.764 c
+370.969 24.764 l
+371.101 24.631 l
+371.101 24.631 l
+371.101 24.631 371.101 24.631 371.101 24.631 c
+371.234 24.499 l
+371.234 24.499 l
+371.234 24.499 l
+371.234 24.499 371.234 24.499 371.234 24.366 c
+371.234 24.366 l
+371.366 24.366 l
+371.366 24.234 l
+371.366 24.234 l
+371.366 24.101 l
+371.498 24.101 l
+371.498 24.101 371.498 24.101 371.498 24.101 c
+371.498 23.969 l
+371.498 23.969 371.498 24.101 371.498 23.969 c
+371.631 23.969 l
+371.631 23.837 l
+371.631 23.837 l
+371.631 23.837 l
+371.763 23.837 371.763 23.837 371.763 23.704 c
+371.763 23.704 l
+371.763 23.704 l
+371.763 23.572 l
+371.895 23.572 371.895 23.704 371.895 23.572 c
+371.895 23.439 l
+371.895 23.439 l
+371.895 23.439 l
+372.027 23.439 372.027 23.439 372.027 23.307 c
+372.027 23.307 l
+372.027 23.307 l
+372.027 23.307 l
+372.160 23.307 372.027 23.307 372.027 23.174 c
+372.160 23.174 l
+372.160 23.042 l
+372.160 23.042 l
+372.292 23.042 l
+372.292 22.910 l
+372.292 22.910 l
+372.292 22.910 372.292 22.910 372.292 22.777 c
+372.292 22.777 l
+372.424 22.777 l
+372.424 22.777 372.424 22.777 372.424 22.777 c
+372.424 22.645 l
+372.424 22.645 l
+372.557 22.512 l
+372.557 22.512 372.557 22.512 372.557 22.512 c
+372.557 22.512 l
+372.557 22.380 l
+372.689 22.380 l
+372.689 22.380 372.689 22.380 372.689 22.380 c
+372.689 22.247 l
+372.689 22.247 l
+372.821 22.115 l
+372.821 22.115 372.821 22.247 372.821 22.115 c
+372.821 22.115 l
+372.821 22.115 l
+372.821 21.982 l
+372.954 21.982 372.954 21.982 372.954 21.982 c
+372.954 21.982 l
+372.954 21.982 372.954 21.982 372.954 21.850 c
+372.954 21.850 l
+372.954 21.850 l
+373.086 21.718 l
+373.086 21.718 l
+373.086 21.585 l
+373.218 21.718 373.218 21.718 373.218 21.585 c
+373.218 21.585 l
+373.218 21.585 l
+373.218 21.585 373.218 21.585 373.218 21.453 c
+373.218 21.453 l
+373.350 21.453 l
+373.350 21.320 l
+373.350 21.320 373.350 21.320 373.350 21.320 c
+373.350 21.188 l
+373.350 21.188 l
+373.483 21.188 l
+373.483 21.188 373.483 21.188 373.483 21.188 c
+373.483 21.055 l
+373.615 21.055 l
+373.615 20.923 l
+373.615 20.923 373.615 20.923 373.615 20.923 c
+373.615 20.923 l
+373.615 20.791 l
+373.747 20.791 l
+373.747 20.791 373.747 20.791 373.747 20.791 c
+373.747 20.791 l
+373.747 20.791 373.747 20.791 373.747 20.658 c
+373.747 20.658 l
+373.880 20.526 l
+373.880 20.526 l
+373.880 20.526 l
+374.012 20.393 l
+374.012 20.393 374.012 20.393 374.012 20.393 c
+374.012 20.393 l
+374.012 20.393 l
+374.144 20.393 374.012 20.393 374.012 20.261 c
+374.144 20.261 l
+374.144 20.128 l
+374.144 20.128 l
+374.276 20.128 374.276 20.128 374.144 19.996 c
+374.276 19.996 l
+374.276 19.996 l
+374.276 19.996 l
+374.276 19.996 374.276 19.996 374.276 19.863 c
+374.409 19.863 l
+374.409 19.731 l
+374.409 19.731 l
+374.541 19.731 374.409 19.731 374.409 19.731 c
+374.541 19.599 l
+374.541 19.599 l
+374.541 19.599 l
+374.541 19.599 374.541 19.599 374.541 19.599 c
+374.541 19.466 l
+374.673 19.466 374.673 19.466 374.541 19.466 c
+374.673 19.466 l
+374.673 19.334 l
+374.673 19.334 l
+374.806 19.201 l
+374.806 19.201 l
+374.806 19.201 374.806 19.201 374.806 19.201 c
+374.806 19.201 l
+374.806 19.069 l
+374.938 19.069 374.938 19.069 374.938 19.069 c
+374.938 18.936 l
+374.938 18.936 l
+374.938 18.936 l
+375.070 18.936 375.070 18.936 375.070 18.804 c
+375.070 18.804 l
+375.070 18.804 l
+375.070 18.672 l
+375.202 18.672 375.202 18.804 375.202 18.672 c
+375.202 18.672 l
+375.202 18.539 l
+375.202 18.539 l
+375.335 18.539 375.335 18.539 375.335 18.407 c
+375.335 18.407 l
+375.335 18.407 l
+375.335 18.407 l
+375.467 18.407 375.335 18.407 375.335 18.274 c
+375.335 18.274 l
+375.467 18.274 375.467 18.274 375.467 18.274 c
+375.467 18.142 l
+375.467 18.142 l
+375.599 18.142 l
+375.599 18.009 l
+375.599 18.009 375.599 18.009 375.599 18.009 c
+375.599 18.009 l
+375.599 17.877 l
+375.732 17.877 l
+375.732 17.877 375.732 17.877 375.732 17.877 c
+375.732 17.745 l
+375.732 17.745 l
+375.864 17.612 l
+375.864 17.612 375.864 17.612 375.864 17.612 c
+375.864 17.612 l
+375.864 17.480 l
+375.996 17.480 l
+375.996 17.480 375.996 17.480 375.996 17.480 c
+375.996 17.347 l
+375.996 17.347 l
+376.128 17.347 l
+376.128 17.215 l
+376.128 17.215 l
+376.128 17.082 l
+376.261 17.082 376.261 17.215 376.261 17.082 c
+376.261 17.082 l
+376.261 17.082 376.261 17.082 376.261 17.082 c
+376.261 16.950 l
+376.393 16.950 l
+376.393 16.817 l
+376.393 16.817 l
+376.525 16.817 376.393 16.817 376.393 16.817 c
+376.525 16.685 l
+376.525 16.685 l
+376.525 16.685 l
+376.525 16.685 376.525 16.685 376.525 16.685 c
+376.525 16.553 l
+376.658 16.553 l
+376.658 16.420 l
+376.658 16.420 376.658 16.420 376.658 16.420 c
+376.790 16.288 l
+376.790 16.288 l
+376.790 16.288 l
+376.790 16.288 376.790 16.288 376.790 16.288 c
+376.790 16.155 l
+376.922 16.155 l
+376.922 16.155 l
+376.922 16.288 376.922 16.288 376.922 16.288 c
+377.054 16.288 l
+377.054 16.288 l
+377.054 16.288 l
+377.054 16.420 377.054 16.420 377.187 16.420 c
+377.187 16.420 l
+377.187 16.420 l
+377.319 16.420 l
+377.319 16.553 377.319 16.553 377.319 16.553 c
+377.451 16.553 l
+377.451 16.553 l
+377.451 16.553 l
+377.451 16.685 377.451 16.685 377.451 16.685 c
+377.451 16.685 l
+377.451 16.685 377.451 16.685 377.584 16.685 c
+377.584 16.685 l
+377.716 16.685 l
+377.716 16.817 l
+377.716 16.817 l
+377.716 16.817 377.716 16.817 377.848 16.817 c
+377.848 16.817 l
+377.848 16.817 l
+377.981 16.950 l
+377.848 16.950 377.848 16.950 377.981 16.950 c
+377.981 16.950 l
+378.113 17.082 l
+378.113 17.082 l
+378.113 17.082 378.113 17.082 378.113 17.082 c
+378.245 17.082 l
+378.245 17.082 l
+378.245 17.215 l
+378.245 17.215 378.245 17.215 378.377 17.215 c
+378.377 17.215 l
+378.377 17.347 l
+378.510 17.347 l
+378.510 17.347 378.510 17.347 378.510 17.347 c
+378.642 17.347 l
+378.642 17.347 l
+378.642 17.480 l
+378.642 17.480 378.642 17.480 378.642 17.480 c
+378.642 17.480 l
+378.774 17.480 378.774 17.480 378.774 17.612 c
+378.774 17.612 l
+378.642 17.612 378.774 17.612 378.774 17.612 c
+378.642 17.612 l
+378.642 17.745 l
+378.642 17.745 l
+378.510 17.877 l
+378.510 17.877 l
+378.510 17.877 378.510 17.877 378.510 17.877 c
+378.510 18.009 l
+378.510 18.009 l
+378.377 18.009 378.377 18.009 378.377 18.009 c
+378.377 18.142 l
+378.377 18.142 l
+378.377 18.142 l
+378.245 18.142 378.245 18.142 378.245 18.274 c
+378.245 18.274 l
+378.245 18.274 l
+378.245 18.407 l
+378.113 18.407 378.113 18.274 378.113 18.407 c
+378.113 18.407 l
+378.113 18.407 378.113 18.407 378.113 18.407 c
+378.113 18.539 l
+378.113 18.539 l
+377.981 18.672 l
+377.981 18.672 l
+377.981 18.672 l
+377.848 18.672 377.981 18.672 377.981 18.804 c
+377.981 18.804 l
+377.848 18.804 377.848 18.804 377.848 18.804 c
+377.848 18.936 l
+377.848 18.936 l
+377.716 18.936 l
+377.716 19.069 l
+377.716 19.069 l
+377.716 19.069 377.716 19.069 377.716 19.201 c
+377.716 19.201 l
+377.584 19.201 l
+377.584 19.201 377.584 19.201 377.584 19.201 c
+377.584 19.334 l
+377.584 19.334 l
+377.451 19.466 l
+377.451 19.466 377.451 19.466 377.451 19.466 c
+377.451 19.466 l
+377.451 19.599 l
+377.319 19.599 l
+377.319 19.599 377.319 19.599 377.319 19.599 c
+377.319 19.599 l
+377.319 19.599 377.319 19.599 377.319 19.731 c
+377.319 19.731 l
+377.187 19.863 l
+377.187 19.863 l
+377.187 19.863 l
+377.187 19.996 l
+377.054 19.996 377.054 19.996 377.054 19.996 c
+377.054 19.996 l
+377.054 19.996 377.054 19.996 377.054 20.128 c
+377.054 20.128 l
+376.922 20.128 l
+376.922 20.261 l
+376.922 20.261 l
+376.922 20.261 l
+376.790 20.261 376.790 20.261 376.790 20.393 c
+376.790 20.393 l
+376.790 20.393 l
+376.790 20.393 376.790 20.393 376.790 20.526 c
+376.790 20.526 l
+376.658 20.526 l
+376.658 20.658 l
+376.658 20.658 376.658 20.658 376.658 20.658 c
+376.525 20.791 l
+376.525 20.791 l
+376.525 20.791 l
+376.525 20.791 376.525 20.791 376.525 20.791 c
+376.525 20.923 l
+376.525 20.923 376.525 20.791 376.525 20.923 c
+376.393 20.923 l
+376.393 21.055 l
+376.393 21.055 l
+376.393 21.188 l
+376.261 21.188 l
+376.261 21.188 376.261 21.188 376.261 21.188 c
+376.261 21.188 l
+376.261 21.188 376.261 21.188 376.261 21.320 c
+376.128 21.320 l
+376.128 21.453 l
+376.128 21.453 l
+376.128 21.453 l
+375.996 21.453 375.996 21.453 375.996 21.585 c
+375.996 21.585 l
+375.996 21.585 l
+375.996 21.585 l
+375.864 21.585 375.864 21.585 375.996 21.718 c
+375.864 21.718 l
+375.864 21.850 l
+375.864 21.850 l
+375.732 21.850 375.732 21.850 375.732 21.850 c
+375.732 21.982 l
+375.732 21.982 l
+375.732 21.982 l
+375.732 21.982 375.732 21.982 375.732 22.115 c
+375.732 22.115 l
+375.599 22.115 375.599 22.115 375.599 22.115 c
+375.599 22.115 l
+375.599 22.247 l
+375.599 22.247 l
+375.467 22.380 l
+375.467 22.380 l
+375.467 22.380 375.467 22.380 375.467 22.380 c
+375.467 22.512 l
+375.335 22.512 375.335 22.512 375.467 22.512 c
+375.335 22.512 l
+375.335 22.645 l
+375.335 22.645 l
+375.202 22.777 l
+375.202 22.777 375.202 22.645 375.202 22.777 c
+375.202 22.777 l
+375.202 22.777 l
+375.202 22.910 l
+375.070 22.910 375.070 22.910 375.070 22.910 c
+375.070 23.042 l
+375.070 23.042 l
+374.938 23.042 l
+374.938 23.042 374.938 23.042 374.938 23.174 c
+374.938 23.174 l
+374.938 23.174 l
+374.938 23.307 l
+374.806 23.174 374.806 23.174 374.806 23.307 c
+374.806 23.307 l
+374.806 23.307 374.806 23.307 374.806 23.307 c
+374.806 23.439 l
+374.806 23.439 l
+374.673 23.439 l
+374.673 23.572 l
+374.673 23.572 l
+374.541 23.572 374.541 23.572 374.541 23.704 c
+374.541 23.704 l
+374.541 23.704 374.541 23.704 374.541 23.704 c
+374.541 23.837 l
+374.541 23.837 l
+374.409 23.837 l
+374.409 23.969 l
+374.409 23.969 374.409 23.969 374.409 23.969 c
+374.409 23.969 l
+374.409 24.101 l
+374.276 24.101 l
+374.276 24.101 374.276 24.101 374.276 24.101 c
+374.276 24.234 l
+374.144 24.234 l
+374.144 24.366 l
+374.144 24.366 l
+374.144 24.366 l
+374.012 24.499 l
+374.012 24.499 374.012 24.499 374.012 24.499 c
+374.012 24.499 l
+374.012 24.499 374.012 24.499 374.012 24.631 c
+374.012 24.631 l
+373.880 24.631 l
+373.880 24.764 l
+373.880 24.764 l
+373.880 24.896 l
+373.747 24.896 373.747 24.764 373.747 24.896 c
+373.747 24.896 l
+373.747 24.896 l
+373.615 24.896 373.747 24.896 373.747 25.029 c
+373.615 25.029 l
+373.615 25.161 l
+373.615 25.161 l
+373.483 25.161 373.615 25.161 373.615 25.161 c
+373.483 25.293 l
+373.483 25.293 l
+373.483 25.293 l
+373.483 25.293 373.483 25.293 373.483 25.293 c
+373.483 25.426 l
+373.350 25.426 373.350 25.426 373.350 25.426 c
+373.350 25.426 l
+373.350 25.558 l
+373.350 25.558 l
+373.218 25.691 l
+373.218 25.691 l
+373.218 25.691 373.218 25.691 373.218 25.691 c
+373.218 25.691 l
+373.086 25.691 373.218 25.691 373.218 25.823 c
+373.086 25.823 l
+373.086 25.956 l
+373.086 25.956 l
+373.086 26.088 l
+372.954 26.088 l
+372.954 26.088 372.954 26.088 372.954 26.088 c
+372.954 26.088 l
+372.954 26.220 l
+372.821 26.220 372.821 26.088 372.821 26.220 c
+372.821 26.220 l
+372.821 26.353 l
+372.821 26.353 l
+372.689 26.353 372.689 26.353 372.689 26.485 c
+372.689 26.485 l
+372.689 26.485 l
+372.689 26.485 l
+372.557 26.485 372.557 26.485 372.557 26.618 c
+372.557 26.618 l
+372.557 26.618 372.557 26.618 372.557 26.618 c
+372.557 26.750 l
+372.557 26.750 l
+372.424 26.750 l
+372.424 26.883 l
+372.424 26.883 l
+372.292 26.883 372.424 26.883 372.424 27.015 c
+372.424 27.015 l
+372.292 27.015 372.292 27.015 372.292 27.015 c
+372.292 27.015 l
+372.292 27.148 l
+372.160 27.148 l
+372.160 27.280 l
+372.160 27.280 372.160 27.280 372.160 27.280 c
+372.160 27.280 l
+372.160 27.412 l
+372.027 27.412 l
+372.027 27.412 372.027 27.412 372.027 27.412 c
+372.027 27.545 l
+372.027 27.545 l
+371.895 27.545 l
+371.895 27.545 371.895 27.545 371.895 27.677 c
+371.895 27.677 l
+371.895 27.677 l
+371.763 27.810 l
+371.763 27.810 371.763 27.677 371.763 27.810 c
+371.763 27.810 l
+371.763 27.810 371.763 27.810 371.763 27.810 c
+371.763 27.942 l
+371.631 27.942 l
+371.631 28.075 l
+371.631 28.075 l
+371.631 28.075 l
+371.498 28.075 371.498 28.075 371.498 28.207 c
+371.498 28.207 l
+371.498 28.207 371.498 28.207 371.498 28.207 c
+371.498 28.339 l
+371.498 28.339 l
+371.366 28.339 l
+371.366 28.472 l
+371.366 28.472 371.366 28.472 371.366 28.472 c
+371.234 28.604 l
+371.234 28.604 l
+371.234 28.604 l
+371.234 28.604 371.234 28.604 371.234 28.604 c
+371.234 28.737 l
+371.101 28.737 l
+371.101 28.869 l
+371.101 28.869 371.101 28.869 371.101 28.869 c
+371.101 28.869 l
+371.101 29.002 l
+370.969 29.002 l
+370.969 29.002 370.969 29.002 370.969 29.002 c
+370.969 29.002 l
+370.969 29.002 370.969 29.002 370.969 29.134 c
+370.837 29.134 l
+370.837 29.266 l
+370.837 29.266 l
+370.837 29.266 l
+370.705 29.399 l
+370.705 29.399 370.705 29.399 370.705 29.399 c
+370.705 29.399 l
+370.705 29.399 370.705 29.399 370.705 29.531 c
+370.705 29.531 l
+370.572 29.531 l
+370.572 29.664 l
+370.572 29.664 l
+370.440 29.664 370.440 29.664 370.440 29.664 c
+370.440 29.796 l
+370.440 29.796 l
+370.440 29.796 l
+370.308 29.796 370.440 29.796 370.440 29.929 c
+370.308 29.929 l
+370.308 29.929 l
+370.308 30.061 l
+370.308 30.061 l
+370.175 30.061 370.175 30.061 370.175 30.194 c
+370.175 30.194 l
+370.043 30.326 l
+370.043 30.326 l
+369.911 30.326 369.911 30.326 369.911 30.326 c
+369.911 30.458 l
+369.911 30.458 l
+369.911 30.458 l
+369.779 30.458 l
+369.779 30.458 l
+369.646 30.458 369.779 30.458 369.779 30.591 c
+369.646 30.591 l
+369.646 30.591 l
+369.646 30.591 l
+369.514 30.591 l
+369.514 30.591 l
+369.382 30.591 l
+369.249 30.591 l
+368.985 30.591 l
+368.853 30.591 l
+368.720 30.591 l
+f
+0.16 0.16 0.23 rg
+[] 0 d
+360.254 30.723 m 
+360.254 30.723 360.254 30.723 360.254 30.723 c
+360.254 30.723 l
+360.121 30.591 l
+360.254 30.591 360.254 30.591 360.121 30.591 c
+360.121 30.591 l
+360.121 30.458 l
+359.989 30.458 l
+359.989 30.458 l
+359.989 30.326 l
+359.989 30.326 359.989 30.326 359.989 30.326 c
+359.989 30.326 l
+359.989 30.194 359.989 30.194 359.857 30.194 c
+359.857 30.194 l
+359.857 30.194 l
+359.857 30.194 l
+359.857 30.061 359.857 30.061 359.725 30.061 c
+359.725 30.061 l
+359.725 29.929 l
+359.725 29.929 l
+359.725 29.796 359.725 29.929 359.592 29.929 c
+359.592 29.796 l
+359.592 29.796 l
+359.592 29.796 l
+359.592 29.664 359.592 29.664 359.592 29.664 c
+359.460 29.664 l
+359.460 29.664 l
+359.460 29.531 l
+359.460 29.531 359.460 29.531 359.460 29.531 c
+359.328 29.399 l
+359.328 29.399 l
+359.328 29.399 l
+359.328 29.266 359.328 29.399 359.328 29.399 c
+359.195 29.266 l
+359.195 29.266 l
+359.195 29.134 l
+359.195 29.134 359.195 29.134 359.195 29.134 c
+359.063 29.134 l
+359.063 29.002 l
+359.063 29.002 l
+359.063 29.002 359.063 29.002 359.063 29.002 c
+359.063 28.869 l
+358.931 28.869 l
+358.931 28.869 l
+358.931 28.737 l
+358.931 28.737 358.931 28.737 358.799 28.737 c
+358.799 28.737 l
+358.799 28.604 358.931 28.604 358.799 28.604 c
+358.799 28.604 l
+358.799 28.604 l
+358.666 28.472 l
+358.666 28.472 l
+358.666 28.339 l
+358.666 28.339 358.666 28.339 358.666 28.339 c
+358.666 28.339 l
+358.666 28.207 358.666 28.339 358.534 28.339 c
+358.534 28.207 l
+358.534 28.207 l
+358.534 28.207 l
+358.534 28.075 358.534 28.075 358.402 28.075 c
+358.402 28.075 l
+358.402 28.075 l
+358.402 27.942 l
+358.402 27.942 358.402 27.942 358.269 27.942 c
+358.269 27.810 l
+358.269 27.810 l
+358.269 27.810 l
+358.269 27.677 358.269 27.810 358.137 27.810 c
+358.137 27.677 l
+358.137 27.677 l
+358.137 27.545 l
+358.137 27.545 358.137 27.545 358.005 27.545 c
+358.005 27.545 l
+358.005 27.412 l
+358.005 27.412 l
+358.005 27.412 358.005 27.412 358.005 27.412 c
+357.873 27.280 l
+357.873 27.280 l
+357.873 27.148 l
+357.873 27.148 357.873 27.148 357.873 27.148 c
+357.740 27.148 l
+357.740 27.148 l
+357.740 27.015 357.740 27.015 357.740 27.015 c
+357.740 27.015 l
+357.608 26.883 l
+357.608 26.883 l
+357.608 26.883 l
+357.608 26.750 l
+357.608 26.750 357.608 26.750 357.476 26.750 c
+357.476 26.750 l
+357.476 26.618 357.476 26.618 357.476 26.750 c
+357.476 26.618 l
+357.476 26.618 l
+357.343 26.485 l
+357.343 26.485 l
+357.343 26.485 l
+357.343 26.353 357.343 26.353 357.211 26.353 c
+357.211 26.353 l
+357.211 26.353 357.343 26.353 357.211 26.353 c
+357.211 26.220 l
+357.211 26.220 l
+357.211 26.220 l
+357.211 26.088 357.211 26.220 357.079 26.220 c
+357.079 26.088 l
+357.079 26.088 l
+357.079 25.956 l
+357.079 25.956 357.079 25.956 356.947 25.956 c
+356.947 25.956 l
+356.947 25.823 l
+356.947 25.823 l
+356.947 25.823 356.947 25.823 356.814 25.823 c
+356.814 25.691 l
+356.814 25.691 l
+356.814 25.558 l
+356.814 25.558 356.814 25.558 356.682 25.558 c
+356.682 25.558 l
+356.682 25.558 l
+356.682 25.426 l
+356.682 25.426 356.682 25.426 356.682 25.426 c
+356.550 25.293 l
+356.550 25.293 l
+356.550 25.293 l
+356.550 25.161 356.550 25.161 356.417 25.161 c
+356.417 25.161 l
+356.417 25.161 l
+356.417 25.029 356.417 25.029 356.417 25.161 c
+356.417 25.029 l
+356.285 25.029 l
+356.285 24.896 l
+356.285 24.896 l
+356.153 24.896 l
+356.285 24.764 356.285 24.764 356.153 24.764 c
+356.153 24.764 l
+356.153 24.764 356.153 24.764 356.153 24.764 c
+356.153 24.631 l
+356.153 24.631 l
+356.020 24.631 l
+356.020 24.499 356.153 24.631 356.020 24.631 c
+356.020 24.499 l
+356.020 24.499 l
+355.888 24.366 l
+355.888 24.366 355.888 24.366 355.888 24.366 c
+355.888 24.366 l
+355.888 24.234 l
+355.888 24.234 l
+355.888 24.234 355.888 24.234 355.756 24.234 c
+355.756 24.101 l
+355.756 24.101 l
+355.624 23.969 l
+355.624 23.969 355.756 23.969 355.624 23.969 c
+355.624 23.969 l
+355.624 23.969 l
+355.624 23.837 l
+355.624 23.837 355.624 23.837 355.491 23.837 c
+355.491 23.704 l
+355.491 23.704 l
+355.491 23.704 l
+355.491 23.572 355.491 23.572 355.359 23.572 c
+355.359 23.572 l
+355.359 23.572 l
+355.359 23.439 355.359 23.439 355.359 23.572 c
+355.227 23.439 l
+355.227 23.439 l
+355.227 23.307 l
+355.227 23.307 l
+355.094 23.307 l
+355.094 23.174 355.094 23.174 355.094 23.174 c
+355.094 23.174 l
+355.094 23.174 355.094 23.174 355.094 23.174 c
+354.962 23.042 l
+354.962 23.042 l
+354.962 23.042 l
+354.962 22.910 l
+354.830 22.910 l
+354.830 22.777 354.962 22.777 354.830 22.777 c
+354.830 22.777 l
+354.830 22.777 354.830 22.777 354.830 22.777 c
+354.830 22.777 l
+354.830 22.645 l
+354.698 22.645 l
+354.698 22.645 354.698 22.645 354.698 22.645 c
+354.698 22.512 l
+354.565 22.512 l
+354.565 22.380 l
+354.565 22.380 354.565 22.380 354.565 22.380 c
+354.565 22.380 l
+354.565 22.380 l
+354.433 22.247 l
+354.433 22.247 354.565 22.247 354.433 22.247 c
+354.433 22.115 l
+354.433 22.115 l
+354.301 22.115 l
+354.301 21.982 354.433 21.982 354.301 21.982 c
+354.301 21.982 l
+354.301 21.982 l
+354.301 21.850 l
+354.301 21.850 354.301 21.850 354.168 21.850 c
+354.168 21.850 l
+354.168 21.718 l
+354.036 21.718 l
+354.168 21.585 354.168 21.585 354.036 21.585 c
+354.036 21.585 l
+354.036 21.585 l
+354.036 21.585 354.036 21.585 354.036 21.585 c
+353.904 21.453 l
+353.904 21.453 l
+353.904 21.453 l
+353.904 21.320 l
+353.772 21.320 l
+353.772 21.188 353.772 21.188 353.772 21.188 c
+353.772 21.188 l
+353.772 21.188 353.772 21.188 353.772 21.188 c
+353.639 21.188 l
+353.639 21.055 l
+353.639 21.055 l
+353.639 20.923 l
+353.507 20.923 l
+353.507 20.791 l
+353.507 20.791 353.507 20.791 353.507 20.791 c
+353.507 20.791 l
+353.507 20.791 l
+353.375 20.658 l
+353.375 20.658 353.375 20.658 353.375 20.658 c
+353.375 20.526 l
+353.242 20.526 l
+353.242 20.526 l
+353.242 20.393 353.242 20.393 353.242 20.393 c
+353.242 20.393 l
+353.242 20.393 l
+353.110 20.261 l
+353.110 20.261 353.110 20.261 353.110 20.261 c
+353.110 20.261 l
+352.978 20.128 l
+352.978 20.128 l
+352.978 19.996 352.978 19.996 352.978 19.996 c
+352.978 19.996 l
+352.978 19.996 l
+352.846 19.996 l
+352.846 19.863 352.978 19.863 352.846 19.863 c
+352.846 19.863 l
+352.846 19.863 l
+352.713 19.731 l
+352.713 19.731 l
+352.713 19.599 352.713 19.599 352.713 19.599 c
+352.713 19.599 l
+352.713 19.599 352.713 19.599 352.713 19.599 c
+352.581 19.599 l
+352.581 19.466 l
+352.581 19.466 l
+352.449 19.334 l
+352.449 19.334 l
+352.449 19.201 352.449 19.334 352.449 19.334 c
+352.449 19.201 l
+352.449 19.201 352.449 19.201 352.449 19.201 c
+352.316 19.201 l
+352.316 19.201 l
+352.316 19.069 l
+352.316 19.069 352.316 19.069 352.316 19.069 c
+352.316 18.936 l
+352.184 18.936 l
+352.184 18.936 l
+352.184 18.804 352.184 18.804 352.184 18.804 c
+352.052 18.804 l
+352.052 18.804 l
+352.052 18.672 l
+352.052 18.672 352.052 18.672 352.052 18.672 c
+352.052 18.672 l
+351.920 18.539 l
+351.920 18.539 l
+351.920 18.407 351.920 18.407 351.920 18.407 c
+351.920 18.407 l
+351.920 18.407 l
+351.787 18.407 l
+351.787 18.274 351.787 18.274 351.787 18.274 c
+351.787 18.274 l
+351.655 18.274 l
+351.655 18.142 l
+351.655 18.009 351.655 18.142 351.655 18.142 c
+351.655 18.009 l
+351.655 18.009 l
+351.655 18.009 351.655 18.009 351.523 18.009 c
+351.523 18.009 l
+351.523 17.877 l
+351.523 17.877 l
+351.390 17.745 l
+351.390 17.745 l
+351.390 17.612 351.390 17.745 351.390 17.745 c
+351.390 17.612 l
+351.390 17.612 351.390 17.612 351.258 17.612 c
+351.258 17.612 l
+351.258 17.612 l
+351.258 17.480 351.258 17.480 351.258 17.480 c
+351.390 17.480 351.390 17.480 351.390 17.480 c
+351.390 17.347 l
+351.523 17.347 l
+351.523 17.347 351.523 17.347 351.523 17.347 c
+351.523 17.347 l
+351.655 17.347 351.523 17.347 351.523 17.347 c
+351.655 17.215 l
+351.655 17.215 l
+351.655 17.215 l
+351.787 17.215 351.787 17.215 351.787 17.215 c
+351.787 17.082 l
+351.787 17.082 l
+351.920 17.082 l
+351.920 17.082 351.920 17.082 351.920 17.082 c
+351.920 16.950 l
+352.052 16.950 l
+352.052 16.950 l
+352.052 16.950 352.052 16.950 352.052 16.950 c
+352.184 16.817 l
+352.184 16.817 l
+352.184 16.817 l
+352.316 16.817 l
+352.316 16.817 352.316 16.817 352.316 16.685 c
+352.316 16.685 l
+352.449 16.685 352.449 16.685 352.449 16.685 c
+352.449 16.685 l
+352.449 16.685 l
+352.581 16.553 l
+352.581 16.553 l
+352.713 16.553 l
+352.713 16.553 352.713 16.553 352.713 16.420 c
+352.713 16.420 l
+352.713 16.420 352.713 16.420 352.713 16.420 c
+352.846 16.420 l
+352.846 16.420 l
+352.846 16.288 l
+352.978 16.288 l
+353.110 16.288 352.978 16.155 353.110 16.288 c
+353.110 16.420 l
+353.110 16.420 l
+353.242 16.420 l
+353.242 16.553 353.110 16.553 353.242 16.553 c
+353.242 16.553 l
+353.242 16.553 l
+353.242 16.685 l
+353.242 16.685 353.242 16.685 353.375 16.685 c
+353.375 16.685 l
+353.375 16.817 l
+353.507 16.817 l
+353.507 16.817 l
+353.507 16.950 353.507 16.950 353.507 16.950 c
+353.507 16.950 l
+353.507 16.950 353.507 16.950 353.507 16.950 c
+353.639 17.082 l
+353.639 17.082 l
+353.639 17.082 l
+353.639 17.215 l
+353.772 17.215 l
+353.772 17.347 353.772 17.347 353.772 17.347 c
+353.772 17.347 l
+353.772 17.347 353.772 17.347 353.772 17.347 c
+353.904 17.347 l
+353.904 17.480 l
+353.904 17.480 l
+353.904 17.612 l
+354.036 17.612 l
+354.036 17.612 353.904 17.612 354.036 17.612 c
+354.036 17.745 l
+354.036 17.745 354.036 17.745 354.036 17.745 c
+354.036 17.745 l
+354.036 17.745 l
+354.168 17.877 l
+354.168 17.877 354.168 17.877 354.168 17.877 c
+354.168 18.009 l
+354.301 18.009 l
+354.301 18.009 l
+354.301 18.142 354.301 18.142 354.301 18.142 c
+354.301 18.142 l
+354.301 18.142 l
+354.433 18.274 l
+354.433 18.274 354.301 18.274 354.433 18.274 c
+354.433 18.274 l
+354.433 18.407 l
+354.565 18.407 l
+354.565 18.539 354.565 18.539 354.565 18.539 c
+354.565 18.539 l
+354.565 18.539 l
+354.698 18.539 l
+354.698 18.672 354.565 18.672 354.698 18.672 c
+354.698 18.672 l
+354.698 18.672 l
+354.830 18.804 l
+354.830 18.804 l
+354.830 18.936 354.830 18.936 354.830 18.936 c
+354.830 18.936 l
+354.830 18.936 354.830 18.936 354.830 18.936 c
+354.962 18.936 l
+354.962 19.069 l
+354.962 19.069 l
+355.094 19.201 l
+355.094 19.201 l
+355.094 19.201 355.094 19.201 355.094 19.201 c
+355.094 19.334 l
+355.094 19.334 355.094 19.334 355.094 19.334 c
+355.227 19.334 l
+355.227 19.466 l
+355.227 19.466 l
+355.227 19.599 l
+355.359 19.599 l
+355.359 19.599 355.359 19.599 355.359 19.599 c
+355.359 19.599 l
+355.359 19.731 355.359 19.731 355.359 19.731 c
+355.491 19.731 l
+355.491 19.731 l
+355.491 19.863 l
+355.491 19.863 355.491 19.863 355.491 19.863 c
+355.491 19.863 l
+355.624 19.996 l
+355.624 19.996 l
+355.624 20.128 355.624 20.128 355.624 20.128 c
+355.624 20.128 l
+355.624 20.128 l
+355.756 20.128 l
+355.756 20.261 355.756 20.261 355.756 20.261 c
+355.756 20.261 l
+355.888 20.261 l
+355.888 20.393 l
+355.888 20.393 355.888 20.393 355.888 20.393 c
+355.888 20.526 l
+355.888 20.526 l
+355.888 20.526 355.888 20.526 356.020 20.526 c
+356.020 20.526 l
+356.020 20.658 l
+356.020 20.658 l
+356.153 20.791 l
+356.153 20.791 l
+356.153 20.791 356.153 20.791 356.153 20.791 c
+356.153 20.923 l
+356.153 20.923 356.153 20.923 356.285 20.923 c
+356.285 20.923 l
+356.285 21.055 l
+356.285 21.055 l
+356.417 21.188 l
+356.417 21.188 l
+356.417 21.188 356.417 21.188 356.417 21.188 c
+356.417 21.188 l
+356.417 21.320 356.417 21.320 356.417 21.320 c
+356.550 21.320 l
+356.550 21.320 l
+356.550 21.453 l
+356.550 21.453 356.550 21.453 356.550 21.453 c
+356.682 21.453 l
+356.682 21.585 l
+356.682 21.585 356.682 21.585 356.682 21.585 c
+356.682 21.585 l
+356.682 21.718 356.682 21.718 356.682 21.718 c
+356.814 21.718 l
+356.814 21.718 l
+356.814 21.718 l
+356.814 21.850 356.814 21.850 356.814 21.850 c
+356.814 21.850 l
+356.947 21.850 l
+356.947 21.982 l
+356.947 21.982 356.947 21.982 356.947 21.982 c
+357.079 22.115 l
+357.079 22.115 l
+357.079 22.115 l
+357.079 22.247 357.079 22.115 357.079 22.115 c
+357.079 22.247 l
+357.211 22.247 l
+357.211 22.380 l
+357.211 22.380 l
+357.211 22.512 357.211 22.380 357.211 22.380 c
+357.211 22.512 l
+357.211 22.512 357.211 22.512 357.343 22.512 c
+357.343 22.512 l
+357.343 22.645 l
+357.476 22.645 l
+357.476 22.777 l
+357.476 22.777 l
+357.476 22.777 357.476 22.777 357.476 22.777 c
+357.476 22.777 l
+357.476 22.910 357.476 22.910 357.608 22.910 c
+357.608 22.910 l
+357.608 23.042 l
+357.608 23.042 l
+357.740 23.042 l
+357.740 23.174 l
+357.740 23.174 357.740 23.174 357.740 23.174 c
+357.740 23.174 l
+357.740 23.307 357.740 23.307 357.873 23.307 c
+357.873 23.307 l
+357.873 23.307 l
+357.873 23.307 l
+357.873 23.439 357.873 23.439 357.873 23.439 c
+358.005 23.439 l
+358.005 23.439 l
+358.005 23.572 l
+358.005 23.704 358.005 23.572 358.005 23.572 c
+358.137 23.704 l
+358.137 23.704 l
+358.137 23.704 l
+358.137 23.837 358.137 23.837 358.137 23.837 c
+358.137 23.837 l
+358.269 23.837 l
+358.269 23.969 l
+358.269 23.969 358.269 23.969 358.269 23.969 c
+358.402 23.969 l
+358.402 24.101 l
+358.402 24.101 358.269 24.101 358.402 24.101 c
+358.402 24.101 l
+358.402 24.234 l
+358.534 24.234 l
+358.534 24.366 l
+358.534 24.366 l
+358.534 24.366 358.534 24.366 358.534 24.366 c
+358.534 24.366 l
+358.534 24.499 358.534 24.499 358.666 24.499 c
+358.666 24.499 l
+358.666 24.631 l
+358.799 24.631 l
+358.799 24.631 l
+358.799 24.764 l
+358.799 24.764 358.799 24.764 358.799 24.764 c
+358.799 24.764 l
+358.799 24.896 358.799 24.896 358.931 24.896 c
+358.931 24.896 l
+358.931 24.896 l
+359.063 25.029 l
+359.063 25.029 l
+359.063 25.161 l
+359.063 25.161 359.063 25.161 359.063 25.161 c
+359.063 25.161 l
+359.063 25.293 359.063 25.161 359.195 25.161 c
+359.195 25.293 l
+359.195 25.293 l
+359.195 25.293 l
+359.195 25.426 359.195 25.426 359.195 25.426 c
+359.328 25.426 l
+359.328 25.426 l
+359.328 25.558 l
+359.328 25.558 359.328 25.558 359.328 25.558 c
+359.460 25.558 l
+359.460 25.691 l
+359.460 25.691 l
+359.460 25.691 359.460 25.691 359.460 25.691 c
+359.592 25.823 l
+359.592 25.823 l
+359.592 25.956 l
+359.592 25.956 l
+359.592 25.956 359.592 25.956 359.725 25.956 c
+359.725 25.956 l
+359.725 26.088 359.725 26.088 359.725 26.088 c
+359.725 26.088 l
+359.725 26.220 l
+359.857 26.220 l
+359.857 26.220 l
+359.857 26.353 l
+359.857 26.353 359.857 26.353 359.989 26.353 c
+359.989 26.353 l
+359.989 26.485 359.857 26.485 359.989 26.485 c
+359.989 26.485 l
+359.989 26.485 l
+360.121 26.618 l
+360.121 26.618 l
+360.121 26.750 l
+360.121 26.750 360.121 26.750 360.121 26.750 c
+360.121 26.750 l
+360.121 26.883 360.121 26.750 360.254 26.750 c
+360.254 26.883 l
+360.254 26.883 l
+360.254 26.883 l
+360.254 27.015 360.254 27.015 360.386 27.015 c
+360.386 27.015 l
+360.386 27.015 l
+360.386 27.148 l
+360.386 27.148 360.386 27.148 360.518 27.148 c
+360.518 27.148 l
+360.518 27.280 l
+360.518 27.280 l
+360.518 27.280 360.518 27.280 360.518 27.280 c
+360.651 27.412 l
+360.651 27.412 l
+360.651 27.545 l
+360.651 27.545 360.651 27.545 360.783 27.545 c
+360.783 27.545 l
+360.783 27.545 l
+360.783 27.677 360.783 27.677 360.783 27.677 c
+360.783 27.677 l
+360.915 27.810 l
+360.915 27.810 l
+360.915 27.810 l
+361.047 27.942 l
+360.915 27.942 360.915 27.942 361.047 27.942 c
+361.047 27.942 l
+361.047 28.075 361.047 28.075 361.047 28.075 c
+361.047 28.075 l
+361.180 28.075 l
+361.180 28.207 l
+361.180 28.207 l
+361.180 28.339 l
+361.180 28.339 361.180 28.339 361.312 28.339 c
+361.312 28.339 l
+361.312 28.339 361.312 28.339 361.312 28.339 c
+361.312 28.472 l
+361.312 28.472 l
+361.444 28.604 l
+361.444 28.604 l
+361.444 28.604 l
+361.444 28.737 361.444 28.737 361.577 28.737 c
+361.577 28.737 l
+361.577 28.737 361.444 28.737 361.577 28.737 c
+361.577 28.737 l
+361.577 28.869 l
+361.577 28.869 l
+361.577 28.869 361.577 28.869 361.709 28.869 c
+361.709 29.002 l
+361.709 29.002 l
+361.709 29.134 l
+361.709 29.134 361.709 29.134 361.841 29.134 c
+361.841 29.134 l
+361.841 29.134 l
+361.841 29.266 l
+361.841 29.266 361.841 29.266 361.974 29.266 c
+361.974 29.399 l
+361.974 29.399 l
+361.974 29.531 l
+361.974 29.531 361.974 29.531 362.106 29.531 c
+362.106 29.531 l
+362.106 29.531 l
+361.974 29.531 361.974 29.531 361.974 29.664 c
+361.974 29.664 l
+361.974 29.664 l
+361.974 29.664 l
+361.841 29.664 361.841 29.664 361.841 29.664 c
+361.841 29.796 l
+361.709 29.796 l
+361.709 29.796 l
+361.709 29.796 361.709 29.796 361.709 29.929 c
+361.577 29.929 l
+361.577 29.929 l
+361.577 29.929 l
+361.444 29.929 361.577 29.929 361.577 29.929 c
+361.444 30.061 l
+361.444 30.061 l
+361.312 30.061 l
+361.312 30.061 361.312 30.061 361.312 30.194 c
+361.312 30.194 l
+361.180 30.194 l
+361.180 30.194 l
+361.180 30.194 361.180 30.194 361.180 30.194 c
+361.047 30.326 l
+361.047 30.326 l
+360.915 30.326 l
+360.915 30.326 360.915 30.326 360.915 30.326 c
+360.915 30.458 l
+360.915 30.458 l
+360.783 30.458 360.783 30.458 360.783 30.458 c
+360.783 30.458 l
+360.783 30.458 l
+360.783 30.591 l
+360.651 30.591 360.651 30.458 360.651 30.591 c
+360.651 30.591 l
+360.651 30.591 l
+360.518 30.723 l
+360.518 30.723 360.518 30.723 360.518 30.723 c
+360.386 30.723 l
+360.386 30.723 l
+360.386 30.856 l
+360.254 30.856 360.386 30.723 360.386 30.856 c
+360.254 30.723 l
+f
+1.00 g
+[] 0 d
+389.093 30.591 m 
+391.077 30.591 392.665 30.194 393.855 29.399 c
+394.914 28.737 395.443 27.545 395.443 25.823 c
+395.443 25.029 395.310 24.234 395.046 23.704 c
+394.781 23.042 394.384 22.645 393.723 22.247 c
+393.194 21.850 392.532 21.585 391.739 21.320 c
+390.813 21.188 389.887 21.055 388.828 21.055 c
+387.638 21.055 l
+387.638 16.023 l
+384.992 16.023 l
+384.992 30.194 l
+385.521 30.326 386.182 30.458 386.976 30.458 c
+387.770 30.591 388.431 30.591 389.093 30.591 c
+389.093 30.591 l
+389.225 28.339 m 
+388.564 28.339 388.034 28.339 387.638 28.207 c
+387.638 23.307 l
+388.828 23.307 l
+390.151 23.307 391.077 23.572 391.739 23.837 c
+392.400 24.234 392.797 24.896 392.797 25.823 c
+392.797 26.353 392.665 26.750 392.532 27.015 c
+392.268 27.412 392.003 27.677 391.739 27.810 c
+391.474 27.942 391.077 28.075 390.680 28.207 c
+390.151 28.339 389.754 28.339 389.225 28.339 c
+389.225 28.339 l
+f
+1.00 g
+[] 0 d
+407.216 21.453 m 
+407.216 20.658 407.084 19.863 406.820 19.069 c
+406.555 18.407 406.290 17.877 405.761 17.347 c
+405.364 16.817 404.835 16.420 404.174 16.155 c
+403.512 15.890 402.719 15.758 402.057 15.758 c
+401.263 15.758 400.470 15.890 399.941 16.155 c
+399.279 16.420 398.750 16.817 398.221 17.347 c
+397.824 17.877 397.427 18.407 397.162 19.069 c
+396.898 19.863 396.766 20.658 396.766 21.453 c
+396.766 22.380 396.898 23.174 397.162 23.837 c
+397.427 24.499 397.824 25.161 398.221 25.558 c
+398.750 26.088 399.279 26.485 399.941 26.750 c
+400.602 27.015 401.263 27.148 402.057 27.148 c
+402.719 27.148 403.512 27.015 404.042 26.750 c
+404.703 26.485 405.364 26.088 405.761 25.558 c
+406.158 25.161 406.555 24.499 406.820 23.837 c
+407.084 23.174 407.216 22.380 407.216 21.453 c
+407.216 21.453 l
+404.571 21.453 m 
+404.571 22.512 404.438 23.439 403.909 24.101 c
+403.512 24.631 402.851 25.029 402.057 25.029 c
+401.131 25.029 400.470 24.631 400.073 24.101 c
+399.544 23.439 399.411 22.512 399.411 21.453 c
+399.411 20.393 399.544 19.466 400.073 18.936 c
+400.470 18.274 401.131 17.877 402.057 17.877 c
+402.851 17.877 403.512 18.274 403.909 18.936 c
+404.438 19.466 404.571 20.393 404.571 21.453 c
+404.571 21.453 l
+f
+1.00 g
+[] 0 d
+416.080 22.910 m 
+415.683 21.718 415.418 20.526 415.022 19.334 c
+414.625 18.142 414.360 17.082 413.963 16.023 c
+411.847 16.023 l
+411.582 16.685 411.317 17.347 411.053 18.274 c
+410.656 19.069 410.391 19.863 410.127 20.791 c
+409.730 21.718 409.465 22.777 409.201 23.704 c
+408.936 24.764 408.539 25.823 408.275 26.883 c
+410.921 26.883 l
+411.053 26.353 411.185 25.691 411.450 25.029 c
+411.582 24.366 411.714 23.704 411.847 22.910 c
+412.111 22.247 412.243 21.585 412.508 20.923 c
+412.640 20.261 412.905 19.599 413.037 19.069 c
+413.302 19.731 413.434 20.393 413.699 21.055 c
+413.831 21.718 414.095 22.380 414.228 23.042 c
+414.360 23.837 414.625 24.499 414.757 25.029 c
+414.889 25.691 415.022 26.353 415.154 26.883 c
+417.138 26.883 l
+417.270 26.353 417.403 25.691 417.535 25.029 c
+417.667 24.499 417.800 23.837 418.064 23.042 c
+418.196 22.380 418.329 21.718 418.593 21.055 c
+418.726 20.393 418.990 19.731 419.122 19.069 c
+419.387 19.599 419.519 20.261 419.784 20.923 c
+419.916 21.585 420.181 22.247 420.313 22.910 c
+420.445 23.704 420.710 24.366 420.842 25.029 c
+420.975 25.691 421.107 26.353 421.239 26.883 c
+423.885 26.883 l
+423.620 25.823 423.356 24.764 422.959 23.704 c
+422.694 22.777 422.430 21.718 422.033 20.791 c
+421.768 19.863 421.504 19.069 421.107 18.274 c
+420.842 17.347 420.578 16.685 420.313 16.023 c
+418.196 16.023 l
+417.800 17.082 417.535 18.142 417.138 19.334 c
+416.741 20.526 416.344 21.718 416.080 22.910 c
+416.080 22.910 l
+f
+1.00 g
+[] 0 d
+425.208 21.453 m 
+425.208 22.380 425.340 23.174 425.605 23.969 c
+425.869 24.631 426.266 25.293 426.663 25.691 c
+427.192 26.220 427.721 26.618 428.383 26.750 c
+428.912 27.015 429.573 27.148 430.235 27.148 c
+431.690 27.148 432.881 26.750 433.674 25.823 c
+434.600 24.896 434.997 23.439 434.997 21.585 c
+434.997 21.453 434.997 21.320 434.997 21.188 c
+434.997 20.923 434.997 20.791 434.865 20.658 c
+427.721 20.658 l
+427.854 19.863 428.118 19.201 428.647 18.672 c
+429.176 18.142 429.970 18.009 431.029 18.009 c
+431.690 18.009 432.219 18.009 432.748 18.142 c
+433.277 18.274 433.674 18.407 433.939 18.407 c
+434.203 16.420 l
+434.071 16.288 433.939 16.288 433.674 16.155 c
+433.410 16.155 433.145 16.023 432.881 16.023 c
+432.484 15.890 432.219 15.890 431.822 15.890 c
+431.425 15.758 431.161 15.758 430.764 15.758 c
+429.838 15.758 428.912 15.890 428.250 16.155 c
+427.589 16.420 426.928 16.817 426.531 17.347 c
+426.002 17.877 425.737 18.539 425.472 19.201 c
+425.340 19.863 425.208 20.658 425.208 21.453 c
+425.208 21.453 l
+432.484 22.512 m 
+432.484 22.910 432.351 23.174 432.351 23.572 c
+432.219 23.837 432.087 24.101 431.822 24.366 c
+431.690 24.631 431.425 24.764 431.161 24.896 c
+430.896 25.029 430.632 25.029 430.235 25.029 c
+429.838 25.029 429.441 25.029 429.176 24.896 c
+428.912 24.764 428.647 24.499 428.515 24.234 c
+428.250 24.101 428.118 23.837 427.986 23.439 c
+427.854 23.174 427.854 22.910 427.721 22.512 c
+432.484 22.512 l
+f
+1.00 g
+[] 0 d
+443.861 24.631 m 
+443.596 24.764 443.331 24.764 442.935 24.896 c
+442.538 24.896 442.141 25.029 441.612 25.029 c
+441.347 25.029 441.083 24.896 440.818 24.896 c
+440.421 24.896 440.289 24.764 440.156 24.764 c
+440.156 16.023 l
+437.643 16.023 l
+437.643 26.353 l
+438.040 26.618 438.701 26.750 439.495 26.883 c
+440.156 27.015 440.950 27.148 441.876 27.148 c
+442.009 27.148 442.273 27.148 442.405 27.148 c
+442.670 27.148 442.935 27.015 443.067 27.015 c
+443.331 27.015 443.596 26.883 443.728 26.883 c
+443.993 26.883 444.125 26.750 444.257 26.750 c
+443.861 24.631 l
+f
+1.00 g
+[] 0 d
+445.448 21.453 m 
+445.448 22.380 445.580 23.174 445.845 23.969 c
+446.109 24.631 446.506 25.293 446.903 25.691 c
+447.432 26.220 447.962 26.618 448.623 26.750 c
+449.152 27.015 449.814 27.148 450.475 27.148 c
+451.930 27.148 453.121 26.750 453.915 25.823 c
+454.708 24.896 455.237 23.439 455.237 21.585 c
+455.237 21.453 455.237 21.320 455.105 21.188 c
+455.105 20.923 455.105 20.791 455.105 20.658 c
+447.962 20.658 l
+448.094 19.863 448.358 19.201 448.888 18.672 c
+449.417 18.142 450.210 18.009 451.269 18.009 c
+451.930 18.009 452.459 18.009 452.989 18.142 c
+453.518 18.274 453.915 18.407 454.179 18.407 c
+454.444 16.420 l
+454.311 16.288 454.179 16.288 453.915 16.155 c
+453.650 16.155 453.385 16.023 453.121 16.023 c
+452.724 15.890 452.459 15.890 452.063 15.890 c
+451.666 15.758 451.401 15.758 451.004 15.758 c
+449.946 15.758 449.152 15.890 448.491 16.155 c
+447.829 16.420 447.168 16.817 446.771 17.347 c
+446.242 17.877 445.977 18.539 445.713 19.201 c
+445.448 19.863 445.448 20.658 445.448 21.453 c
+445.448 21.453 l
+452.724 22.512 m 
+452.724 22.910 452.592 23.174 452.459 23.572 c
+452.459 23.837 452.327 24.101 452.063 24.366 c
+451.930 24.631 451.666 24.764 451.401 24.896 c
+451.136 25.029 450.872 25.029 450.475 25.029 c
+450.078 25.029 449.681 25.029 449.417 24.896 c
+449.152 24.764 448.888 24.499 448.755 24.234 c
+448.491 24.101 448.358 23.837 448.226 23.439 c
+448.094 23.174 448.094 22.910 447.962 22.512 c
+452.724 22.512 l
+f
+1.00 g
+[] 0 d
+459.868 21.453 m 
+459.868 20.393 460.132 19.466 460.661 18.936 c
+461.058 18.274 461.852 18.009 462.778 18.009 c
+463.175 18.009 463.572 18.009 463.836 18.009 c
+464.101 18.009 464.365 18.009 464.498 18.142 c
+464.498 24.366 l
+464.233 24.499 463.969 24.631 463.572 24.764 c
+463.307 24.896 462.910 25.029 462.381 25.029 c
+461.587 25.029 460.926 24.631 460.397 24.101 c
+460.000 23.439 459.868 22.512 459.868 21.453 c
+459.868 21.453 l
+467.011 16.420 m 
+466.482 16.155 465.953 16.023 465.159 15.890 c
+464.365 15.890 463.572 15.758 462.778 15.758 c
+461.852 15.758 461.190 15.890 460.397 16.155 c
+459.735 16.420 459.206 16.817 458.677 17.347 c
+458.280 17.745 457.883 18.407 457.619 19.069 c
+457.354 19.731 457.222 20.526 457.222 21.453 c
+457.222 22.247 457.354 23.042 457.619 23.837 c
+457.751 24.499 458.148 25.029 458.545 25.558 c
+458.942 26.088 459.471 26.485 460.000 26.750 c
+460.661 27.015 461.323 27.148 462.117 27.148 c
+462.646 27.148 463.043 27.015 463.439 26.883 c
+463.836 26.883 464.233 26.750 464.498 26.485 c
+464.498 31.783 l
+467.011 32.180 l
+467.011 16.420 l
+f
+Q
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+1.000 0.000 0.000 rg
+784.08 720.59 Td
+(Note!) Tj
+ET
+1 J
+1 j
+0.72 w
+0.80 0.00 0.00 RG
+0.00 g
+[] 0 d
+780.480 722.880 m 
+766.080 722.880 l
+769.680 726.480 l
+S
+1 J
+1 j
+0.72 w
+0.80 0.00 0.00 RG
+0.00 g
+[] 0 d
+766.080 722.880 m 
+769.680 719.280 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+1.000 0.000 0.000 rg
+784.08 563.63 Td
+(Note!) Tj
+ET
+1 J
+1 j
+0.72 w
+0.80 0.00 0.00 RG
+0.00 g
+[] 0 d
+780.480 565.920 m 
+766.080 565.920 l
+769.680 569.520 l
+S
+1 J
+1 j
+0.72 w
+0.80 0.00 0.00 RG
+0.00 g
+[] 0 d
+766.080 565.920 m 
+769.680 562.320 l
+S
+7.20 w
+BT
+/F2 6.545454545454544 Tf
+7.20 TL
+0.000 g
+672.48 619.79 Td
+(E22P-915M30S) Tj
+ET
+0.80 0.00 0.00 rg
+656.28 233.28 m 656.28 234.27 655.47 235.08 654.48 235.08 c
+653.49 235.08 652.68 234.27 652.68 233.28 c
+652.68 232.29 653.49 231.48 654.48 231.48 c
+655.47 231.48 656.28 232.29 656.28 233.28 c
+f
+0.80 0.00 0.00 rg
+562.68 1082.88 m 562.68 1083.87 561.87 1084.68 560.88 1084.68 c
+559.89 1084.68 559.08 1083.87 559.08 1082.88 c
+559.08 1081.89 559.89 1081.08 560.88 1081.08 c
+561.87 1081.08 562.68 1081.89 562.68 1082.88 c
+f
+0.80 0.00 0.00 rg
+382.68 1093.68 m 382.68 1094.67 381.87 1095.48 380.88 1095.48 c
+379.89 1095.48 379.08 1094.67 379.08 1093.68 c
+379.08 1092.69 379.89 1091.88 380.88 1091.88 c
+381.87 1091.88 382.68 1092.69 382.68 1093.68 c
+f
+0.80 0.00 0.00 rg
+731.88 748.08 m 731.88 749.07 731.07 749.88 730.08 749.88 c
+729.09 749.88 728.28 749.07 728.28 748.08 c
+728.28 747.09 729.09 746.28 730.08 746.28 c
+731.07 746.28 731.88 747.09 731.88 748.08 c
+f
+0.80 0.00 0.00 rg
+483.48 758.88 m 483.48 759.87 482.67 760.68 481.68 760.68 c
+480.69 760.68 479.88 759.87 479.88 758.88 c
+479.88 757.89 480.69 757.08 481.68 757.08 c
+482.67 757.08 483.48 757.89 483.48 758.88 c
+f
+0.80 0.00 0.00 rg
+731.88 679.68 m 731.88 680.67 731.07 681.48 730.08 681.48 c
+729.09 681.48 728.28 680.67 728.28 679.68 c
+728.28 678.69 729.09 677.88 730.08 677.88 c
+731.07 677.88 731.88 678.69 731.88 679.68 c
+f
+0.80 0.00 0.00 rg
+731.88 708.48 m 731.88 709.47 731.07 710.28 730.08 710.28 c
+729.09 710.28 728.28 709.47 728.28 708.48 c
+728.28 707.49 729.09 706.68 730.08 706.68 c
+731.07 706.68 731.88 707.49 731.88 708.48 c
+f
+0.80 0.00 0.00 rg
+731.88 694.08 m 731.88 695.07 731.07 695.88 730.08 695.88 c
+729.09 695.88 728.28 695.07 728.28 694.08 c
+728.28 693.09 729.09 692.28 730.08 692.28 c
+731.07 692.28 731.88 693.09 731.88 694.08 c
+f
+0.80 0.00 0.00 rg
+731.88 686.88 m 731.88 687.87 731.07 688.68 730.08 688.68 c
+729.09 688.68 728.28 687.87 728.28 686.88 c
+728.28 685.89 729.09 685.08 730.08 685.08 c
+731.07 685.08 731.88 685.89 731.88 686.88 c
+f
+0.80 0.00 0.00 rg
+483.48 679.68 m 483.48 680.67 482.67 681.48 481.68 681.48 c
+480.69 681.48 479.88 680.67 479.88 679.68 c
+479.88 678.69 480.69 677.88 481.68 677.88 c
+482.67 677.88 483.48 678.69 483.48 679.68 c
+f
+0.80 0.00 0.00 rg
+483.48 686.88 m 483.48 687.87 482.67 688.68 481.68 688.68 c
+480.69 688.68 479.88 687.87 479.88 686.88 c
+479.88 685.89 480.69 685.08 481.68 685.08 c
+482.67 685.08 483.48 685.89 483.48 686.88 c
+f
+0.80 0.00 0.00 rg
+483.48 694.08 m 483.48 695.07 482.67 695.88 481.68 695.88 c
+480.69 695.88 479.88 695.07 479.88 694.08 c
+479.88 693.09 480.69 692.28 481.68 692.28 c
+482.67 692.28 483.48 693.09 483.48 694.08 c
+f
+0.80 0.00 0.00 rg
+483.48 708.48 m 483.48 709.47 482.67 710.28 481.68 710.28 c
+480.69 710.28 479.88 709.47 479.88 708.48 c
+479.88 707.49 480.69 706.68 481.68 706.68 c
+482.67 706.68 483.48 707.49 483.48 708.48 c
+f
+0.80 0.00 0.00 rg
+731.88 593.28 m 731.88 594.27 731.07 595.08 730.08 595.08 c
+729.09 595.08 728.28 594.27 728.28 593.28 c
+728.28 592.29 729.09 591.48 730.08 591.48 c
+731.07 591.48 731.88 592.29 731.88 593.28 c
+f
+0.80 0.00 0.00 rg
+731.88 524.88 m 731.88 525.87 731.07 526.68 730.08 526.68 c
+729.09 526.68 728.28 525.87 728.28 524.88 c
+728.28 523.89 729.09 523.08 730.08 523.08 c
+731.07 523.08 731.88 523.89 731.88 524.88 c
+f
+0.80 0.00 0.00 rg
+731.88 532.08 m 731.88 533.07 731.07 533.88 730.08 533.88 c
+729.09 533.88 728.28 533.07 728.28 532.08 c
+728.28 531.09 729.09 530.28 730.08 530.28 c
+731.07 530.28 731.88 531.09 731.88 532.08 c
+f
+0.80 0.00 0.00 rg
+731.88 539.28 m 731.88 540.27 731.07 541.08 730.08 541.08 c
+729.09 541.08 728.28 540.27 728.28 539.28 c
+728.28 538.29 729.09 537.48 730.08 537.48 c
+731.07 537.48 731.88 538.29 731.88 539.28 c
+f
+0.80 0.00 0.00 rg
+731.88 553.68 m 731.88 554.67 731.07 555.48 730.08 555.48 c
+729.09 555.48 728.28 554.67 728.28 553.68 c
+728.28 552.69 729.09 551.88 730.08 551.88 c
+731.07 551.88 731.88 552.69 731.88 553.68 c
+f
+0.80 0.00 0.00 rg
+163.08 408.24 m 163.08 409.23 162.27 410.04 161.28 410.04 c
+160.29 410.04 159.48 409.23 159.48 408.24 c
+159.48 407.25 160.29 406.44 161.28 406.44 c
+162.27 406.44 163.08 407.25 163.08 408.24 c
+f
+0.80 0.00 0.00 rg
+163.08 386.64 m 163.08 387.63 162.27 388.44 161.28 388.44 c
+160.29 388.44 159.48 387.63 159.48 386.64 c
+159.48 385.65 160.29 384.84 161.28 384.84 c
+162.27 384.84 163.08 385.65 163.08 386.64 c
+f
+0.80 0.00 0.00 rg
+163.08 393.84 m 163.08 394.83 162.27 395.64 161.28 395.64 c
+160.29 395.64 159.48 394.83 159.48 393.84 c
+159.48 392.85 160.29 392.04 161.28 392.04 c
+162.27 392.04 163.08 392.85 163.08 393.84 c
+f
+0.80 0.00 0.00 rg
+163.08 379.44 m 163.08 380.43 162.27 381.24 161.28 381.24 c
+160.29 381.24 159.48 380.43 159.48 379.44 c
+159.48 378.45 160.29 377.64 161.28 377.64 c
+162.27 377.64 163.08 378.45 163.08 379.44 c
+f
+0.80 0.00 0.00 rg
+98.28 1093.68 m 98.28 1094.67 97.47 1095.48 96.48 1095.48 c
+95.49 1095.48 94.68 1094.67 94.68 1093.68 c
+94.68 1092.69 95.49 1091.88 96.48 1091.88 c
+97.47 1091.88 98.28 1092.69 98.28 1093.68 c
+f
+0.80 0.00 0.00 rg
+278.28 1082.88 m 278.28 1083.87 277.47 1084.68 276.48 1084.68 c
+275.49 1084.68 274.68 1083.87 274.68 1082.88 c
+274.68 1081.89 275.49 1081.08 276.48 1081.08 c
+277.47 1081.08 278.28 1081.89 278.28 1082.88 c
+f
+0.80 0.00 0.00 rg
+724.68 841.68 m 724.68 842.67 723.87 843.48 722.88 843.48 c
+721.89 843.48 721.08 842.67 721.08 841.68 c
+721.08 840.69 721.89 839.88 722.88 839.88 c
+723.87 839.88 724.68 840.69 724.68 841.68 c
+f
+0.80 0.00 0.00 rg
+724.68 834.48 m 724.68 835.47 723.87 836.28 722.88 836.28 c
+721.89 836.28 721.08 835.47 721.08 834.48 c
+721.08 833.49 721.89 832.68 722.88 832.68 c
+723.87 832.68 724.68 833.49 724.68 834.48 c
+f
+0.80 0.00 0.00 rg
+724.68 827.28 m 724.68 828.27 723.87 829.08 722.88 829.08 c
+721.89 829.08 721.08 828.27 721.08 827.28 c
+721.08 826.29 721.89 825.48 722.88 825.48 c
+723.87 825.48 724.68 826.29 724.68 827.28 c
+f
+0.80 0.00 0.00 rg
+724.68 816.48 m 724.68 817.47 723.87 818.28 722.88 818.28 c
+721.89 818.28 721.08 817.47 721.08 816.48 c
+721.08 815.49 721.89 814.68 722.88 814.68 c
+723.87 814.68 724.68 815.49 724.68 816.48 c
+f
+0.80 0.00 0.00 rg
+688.68 816.48 m 688.68 817.47 687.87 818.28 686.88 818.28 c
+685.89 818.28 685.08 817.47 685.08 816.48 c
+685.08 815.49 685.89 814.68 686.88 814.68 c
+687.87 814.68 688.68 815.49 688.68 816.48 c
+f
+0.80 0.00 0.00 rg
+681.48 816.48 m 681.48 817.47 680.67 818.28 679.68 818.28 c
+678.69 818.28 677.88 817.47 677.88 816.48 c
+677.88 815.49 678.69 814.68 679.68 814.68 c
+680.67 814.68 681.48 815.49 681.48 816.48 c
+f
+0.80 0.00 0.00 rg
+674.28 816.48 m 674.28 817.47 673.47 818.28 672.48 818.28 c
+671.49 818.28 670.68 817.47 670.68 816.48 c
+670.68 815.49 671.49 814.68 672.48 814.68 c
+673.47 814.68 674.28 815.49 674.28 816.48 c
+f
+0.80 0.00 0.00 rg
+724.68 884.88 m 724.68 885.87 723.87 886.68 722.88 886.68 c
+721.89 886.68 721.08 885.87 721.08 884.88 c
+721.08 883.89 721.89 883.08 722.88 883.08 c
+723.87 883.08 724.68 883.89 724.68 884.88 c
+f
+0.80 0.00 0.00 rg
+231.48 329.04 m 231.48 330.03 230.67 330.84 229.68 330.84 c
+228.69 330.84 227.88 330.03 227.88 329.04 c
+227.88 328.05 228.69 327.24 229.68 327.24 c
+230.67 327.24 231.48 328.05 231.48 329.04 c
+f
+0.80 0.00 0.00 rg
+209.88 329.04 m 209.88 330.03 209.07 330.84 208.08 330.84 c
+207.09 330.84 206.28 330.03 206.28 329.04 c
+206.28 328.05 207.09 327.24 208.08 327.24 c
+209.07 327.24 209.88 328.05 209.88 329.04 c
+f
+0.80 0.00 0.00 rg
+663.48 1082.88 m 663.48 1083.87 662.67 1084.68 661.68 1084.68 c
+660.69 1084.68 659.88 1083.87 659.88 1082.88 c
+659.88 1081.89 660.69 1081.08 661.68 1081.08 c
+662.67 1081.08 663.48 1081.89 663.48 1082.88 c
+f
+0.80 0.00 0.00 rg
+717.48 1082.88 m 717.48 1083.87 716.67 1084.68 715.68 1084.68 c
+714.69 1084.68 713.88 1083.87 713.88 1082.88 c
+713.88 1081.89 714.69 1081.08 715.68 1081.08 c
+716.67 1081.08 717.48 1081.89 717.48 1082.88 c
+f
+Q
+endstream
+endobj
+1 0 obj
+<>
+endobj
+5 0 obj
+<<
+/Type /FontDescriptor
+/FontName /SimSun
+/FontBBox [-8 -145 1000 859]
+/Flags 32
+/StemV 0
+/ItalicAngle 0
+/Ascent 859
+/Descent -141
+/CapHeight 175
+>>
+endobj
+6 0 obj
+<<
+/Type /Font
+/BaseFont /SimSun
+/FontDescriptor 5 0 R
+/W [1 95 500]
+/Subtype /CIDFontType2
+/CIDSystemInfo
+<<
+/Ordering (GB1)
+/Registry (Adobe)
+/Supplement 2
+>>
+>>
+endobj
+7 0 obj
+<<
+/Type /Font
+/Subtype /Type0
+/BaseFont /SimSun
+/Encoding /UniGB-UCS2-H
+/DescendantFonts [6 0 R]
+>>
+endobj
+8 0 obj
+<<
+/Descent -325
+/CapHeight 500
+/StemV 80
+/Type /FontDescriptor
+/Flags 32
+/FontBBox [-665 -325 2000 1006]
+/FontName /Arial
+/ItalicAngle 0
+/Ascent 1006
+>>
+endobj
+9 0 obj
+<>
+endobj
+10 0 obj
+<<
+/Type /XObject
+/Subtype /Image
+/Width 1024
+/Height 1024
+/ColorSpace /DeviceRGB
+/BitsPerComponent 8
+/DecodeParms <>
+/SMask 11 0 R
+/Length 56607
+/Filter /FlateDecode
+>>
+stream
+x{\w]))D{ErNs!c9Ω-gCưp/&cMCJF4sHrӜ)6ms\B)onÕ>xwzs]+Ah!@C0
a4h!@C0
a4h!@C0
a4h!@C0
a4h!@C0
a4h!@C0
a4h!@C0
a4h!@C0
a4h!@C0
a4.<8wܙ3g._|IݻwFN…WdI%J899UPN`HJKKKѣGO:pBNNt/^\+V^*Udff&ݥE|u}ܹs݇tt…רQA5rwwwtt.
+@LJJڰamrrrvvt8995nܸe˖mڴ)Ut)csέ[.11q۶mqt:WWW//m6hЀ	.\W_ڵ[ꫯvСs01HOOV^)^rrrׯ_ʕ[esCIhK%0dȐҥKKܹv9*X`׮]jԨ!bLzvZhhhxx8P	N׶mI&IQ^+MvM<\vmc|(ҰaW^]:DEOؾ}N:%ðx&Mdii)ݢ
+?ܸqcذa111!0jժEFF֯__:D7vѣ0a#G2et$whA,YRre1?S.]:$ckk'"C`=z^0w\
H@W>}qrrr[ q+V(StHsNn֭['y_uzCŋ}||>,ڵtH>HMM_C.:nĉ!A+ 11K.!PϞ=L:$oibXGYYY!P5??ŋL,]w!0>>>qqqVVV!y@DD'^\f֮]kcc#'Ly̝;wС&/iiiUTyt?񉊊05K9s=Z7
0O7otә?~„	Fԁ~'鐧sttC鐧mmmCɓ'W›A,_PB!5`DT+:գGHZfC`ggwbŊI#ǏW^=''G:	Æ
#oo޺uK:	&M8qt?R?tƍGIW7kѢEzztȟΜ9cmm-tiiiNNNk^zEEEt:a֭m۶̔SDDD@@tөzO4IOڵ[rt۽{woJ*?:1믿JI&6lS4HOk׮x
+%KSUVݽ{tѨQ>?m6!!A);4i$]++={ԬYS:ϒݬY;vHvrr;'O*i[pa߾}+|ϟwuuztoUxbѣGϘ1C7]vJLLVërʝ>}@!OPvttT_{J@AAAٸqcV+iӦ֭[KWl{I@?YYYu=rt+}]pt8﯆ϙ3GsN׺ErJe3Ju@keʔvlFR?f h"Wڶm+]'
D5-^X=w!@.\v_"P޽dJuwߍmhܸqRRNKR^X*//e+knTPٳCժUKꚒ"sF6<pر5j6kn͚5
0˗6L0aɲ
k̘1cѲ
gxI>^'ԩ~Rod6o,[hQ߾}.] @fffѢE322mִiS\VVV*UΜ9#ذ|.]<{n4h@i@,0tٳg< O0y֭[eʔTہN+
:ZJt++K.q_SեK8(pƍ"EH<Pt+WHtR۵k'e˖-Z<//_^0`^^^Sٯ8O6m̘1R?#uzRΟ?onn.|0lذO?Tݻ/YD2N:n8Ӈ"KS;חl3^vȑ#R?Э[e˖IئMKv]…˔)#]0>,UԵkDNHOO/X鏩eԨQرc"G[YY)
+*$r:wׯ?,XSRRND ''Gy)r͛E-[nݺo}Ϟ=mllD&c…:=..SNR?prIKoFTbŘww|N۷SRRΞ={[n=hѢvvvʷ/Zҥtĉ_]QFI*;<<}<*3f͚QF>DxmҥWg*@LLL^N~zbŤNXfff_)qJ*P<_rիO<2_RJ۷СuH$$$~zS2eĉEvrr:sf̘1ӧOԩS
+J6mڮ]]]]_goܸqʓUVE~L 00p޼y"G+/>>^hcǎM>|PW֭###˖-GUв/O>9}tޝR|#F8P,[[n"G)R"G?ЩS+W=zGy֭[j_Ǖ,YRy־}{CGAӒ?ǽk~)w͑#G322,--NE%Yf~၁"Gxwދ/~гg>H"Jf]|yԨQK..]9::ݾ}[\/^*K)߬_KhZC/u*Tof%%%uUR_~e֭(^7D*@ٲe/]$r2<" ŋ...%{F5ydN
})φ~ѣdKt:]PPPHHl	`jժ"ם۶mkڴяb.\޽{"G_|TR"GP|||~54gg%K(°_&,##GRF{*__B
+I&e˖[l9zժU]S077233 eܹCɋlee|"Gϟ?"G?"?n޼YX1˖-{?~G}ou9""B5`HW<_nݾK
+uyÚ;wAD~D~988䔻xvz7?˗Te/jCLʄ	6bux*T7x޽zԩ?Z
2d_>|!/|˖-M4LLJ~8ydg̘1rHa0|տ
+ԨQ#&&Fw֭5k={V:D?KNII)Yt`"vYFǏpPի2+r7\zt`"F9;;_rE:͛7*WtժU:t ]{}L@__uIWQq|+{NNN\t`DDDO׹sy/^\:yKySV
+/ww۷(P@:0nIOjvҥ###C>JWԩS}
+1$1Ӗgt9sԽɑZVtðعsgݺuC#LۤI+^TjՖ,Y*C2U\0Id4o|!vZ___
+ŋׯ_oWݥCLFHH| ]aHЃ흘("VZ{-Xt`i8|p
w:0$1㒙Y^Ω…Φ1z-z+aI@G	ooDCZltp]WWSNIÇW\Y:Tn֬Y#F0=zHW@?ϗ0۷o/Pt  ]qW_MII)VtqF///',3mڴ1cHWbjeeeW6oܼysիΗ/_15vSt  رcM&]aHF
+

~t']aUvkkk@@P;w6iĔ9x𠕕thѢ}JW#FIW
+!ciiwޚ5kJ@gΜQ~n߾-bx_4 P>}+IiѢŷ~+bSRR/.+$6W~뭷+c۶m\иIWhEnݾK
+ _1$1UQ~K:;;+Jݑ#GCcǎթS'##C:DC; 뛐 bHK,޽tY~}eIhKѢEK GxxA+C+W~ƌ3}t
+-ܴiN@P5==]:`ʖ-boo/=gˬY
&]$ٍ7޻wtt///?kt,--,9$'N2et!
6l֬YO߾}-Z$]u۳gOC8pQFYYY!SjՃZ[[K@k֬i߾t~3nܸ>H[I@ݻwk׮}IعsgݺuC4gg+WH7fff[nmҤtKWRHHرc+;~SŊmmmCmذm۶}vnk\	Q
+]~,X ]$ %--!cccsʕ+K@p͚5ܹ#ԩt'yn٧O
+!''y۷oӕ(Q"55t!1$17|sԩSǍ']giݺubb"aHbU)!%%E\~H9͛;HWYNNNf͒CF{{{K@uMMM.\СCUT )S}Eu/VTe[t)"JX+vGDulDQ+*؂
HߵDckdwy~s<<w?g??;wr͖,^`BIaaa
+Gz[v->իW!D@ 2,///5q999!))h4CHEgd88L&??^rB2}O7R$"0aN$-СCك/HM88Lܹs]t?ē-QLLo,ջwXt@ǏߴiHHD&N ̄C˒X"_JCDMHH;vm-[HD&пC+uVLุ8t(+WmРA^^˯.VZABE88D۵k׈#Jҥ˩SxkR˲yǣ+ڳgϐ!C?$qzyQt@ _ӻw!Q`]6:dw8:D^z8޼yK.Eݻ *=$"qFcN!J:|?E>žQ+iV^n۶kPUUT)77Wz'E8+V={6BIG駟$n>g_~-??UѮ]2eʠCJHOOϢ"tb6l[bEtɐa	aaas{7e\rƌ
+@ `0Ch.]Ծ}{tPPP =_o%vӦ2%{{4'''tlHD"L2eÆ
+%͛7o
+g
+Qlmm{N{iLe˖ʕC@.
+ruuMII)[,:dP㰄,ZsɣG[tt(#Р+@ R֛7o{AW}.$"<~Xӽz
+UzCH'OhZ5=KܹsJ7JMfœǁ ,HD0/^D()::_~
+AWRJnnnK?/,,lӦ4k2?믿F3$"EYfڴi
+%
6l׮]
+Gp߸qݽ@$s%!!2%$/wMǏ_~qssGrȑ/:WVsK	"@ Bh4.CHvڥCDS`Vڗ)عsK.}2O[F$/4cƌիW+4s+V+H[	)=z^~47...y3$/q???5]XUV!33ӧOQ
6(5
4+s]d	?@ *5Zڵk:B2绺޾}"Jͳʕ+W߿CfB\t}@TjCUmw֭[7et3a„t(mڴ_~-maÆ+VD$9zh޽Jj߾K4
:d#̟?_WG駟D>>h4xt qIQV*zJۧR
+LpJg@ |s]lBI?Ú5k$Ϻuq
+g{=ggߛ[իWE3q q}&鍳}jo˖-322D_QIh%FEE{ddرcQ/^}uZ +@۵kQP;|0B
+*dgg7mTʕ+6hBֈ/-\pѢE
+%h׮]#F@W9ftQxM0B@ 233?}Qm=z^~%00666>ydt@W6mQ	...wA(DFSN			QjԨa0j׮g;unݤ'$ƍuVt,Y2w\tɳzjuߪ}AW|'OhW^CDiٲeff:џz|On޼VPPeĈ;v@WhڴiDW@ 7]o/B377"/*UmȐ!CWhΟ?߱cGtY$۷#GJڶmۨQ$̙3WZE:ʼp႟:4޾}i*7]]! @ 2%D(|YYY͚5C՟Yh!t(ҥK۷G
+q qu0`@tt4BI7o7nY|9s8q"88]!֋/t:QF999)PTT԰aJ
+<}4gY*OOO5]1cDFF+Lܹs]tc=*ɔ88ڨs{5j0kFTÆ
sss&L@Wd
!@ b4;w|%t:Էo_t#]]!8zmt(q21$*V9s&BI#Gܾ};IJJرeΜ9K.EWZffO!|wǏGWzp qqㆻ{AA:D1
6ɩT:dx!8;;Y-ZqOGFWJp qP)ŋ:t@<Æ
BWbooj!žQ*TݴiStY	rmgYXcǎ]!,:u*޽{*wRRR2e!d88HoW~~~FT>}@(Fќ?cǎg
+Qlmm!NzEj۶mzz::D^Jg֬Y+WDW(iVBW<.\ȋ`E+,í[;|}}/],H2;UVA}+555b1֯_?uTt@#*8H&>|pqqQ57]&JCH߿]!4G[n$}nN>/V$wލPҚ5kTv-#k.ݙd@ 8vX^JMHHh4ɓ'A˗/!^_B}&/aƌ*@ =Z':D1%RiUTGXA8p]!
+/YFT@zuԩS%۷O:P@W<7nT1ґMMK3q q
+lڴiĉ
+%暖ݻ...>|@rt\|cǎF"ʰavڅ s@_~qssG(nݺzZj]viiiQԩc0TԩSׯ_(::_~
+2kHdѤm^v
 tɳpBKzxxH
+"JժUz}z!d88Ȣ͟?ɒ%
+%M2eݺu
+'33ӧOQ&MPOOϢ"t(gϞ?@ ˕뫦hB:,W:dwuu}6:D͛KˣCThŊgFWq	&+Lq qsqq_!KNN@6))B,w}wItv9|ptɳo߾!C+CWX/^t"JFrrr*V!3@%22rر
+%ѣ
+ګW!wܹ.]
+đ^lق 3@޽{߿G(VZAB2H]v=s:D	%,,L݇'NFW@ KQ\\ܾ}tblllN<٭[7tɳ~S+ׯ5k427Hd)	]oڴ	]AܺuǏQBCCKwѣDZcd88"deeyyyMI&َ'=="Jݺu
CժU!Vm…h}SLHdkq[[+WxzzCHy-]]!FGX;igk.--
"J
+6m!0$2Ǐ߼y3BIK$OJJeԩk׮EWߜ?|˼@ 3K㹻_zwW,yyywAҢErʡCۼy-[l
+B@ sTP!++oA]!I3>|pqq{.:DujժC88-_\eg$KX*Z=zHӽ~"J``ӧyNKNNС/Aۻwtl//"tb5jSbEt`4;w|%t(5j0<'R̚5kʕ
+7h tBF#DoBHoӧOGWt>}+s}'##"Jʕz}!d:HdV&OPQ/pTj7otss+((@2bĈ;v+HaGt(			B&@~-I WWTײi&77"+*U!֭[?+ZvԩSd"Hd&޼ytbʗ/ټystɣ5ͅ!T1C׮]Ϝ9ڵk:B@fbDW(iӦMǏGW++YfR^hh޽{JZ~ɓ$>^˜1c"##d
+?-%%BJ@ 
		AW(SN}0]A\rOMA/a˖-CWxB>}"Jƍsrr!$2p(?x`}3%TٳAAACqƍyft)L֭[nnn?~D(nݺzZjgذaQQQ
+QxA*n݊ɓݻwGW88}||tb4M||?:9vX^]vԩ
+Bsuus:DZjzB88̙[J/*5^Z':D__߄iC,33ӧOQxku@ ђ;t蠦\l2##\r'88FWRr
C,Lcǎ#F@W@ 򜝝޽Q}ZZ:ٺuqEEE+\kNzB蘝ݤIt	5rȝ;w+jժӧ+H{ICQzyQt~Mz"J۶m˔)R@ q?.+Ԯ]X痔VZABf'""b„	
+/_>k,t	"=t:ݳg!9jɒ%GWbccsnݺCIqqqQ쒓=<w
:,͛7?~SNDѠCq q"Μ9ӵkWYA>>>/_M-޽S!֬Y3m4t@֭2e
+}/^hڧOCSBMCHM6M8]!Oq/g4.^>==]zWB?@/׷o_]{a#GDW<E	{AW<~X:>~5:D --lٲ;Hm6ftzq1tɣPPN˜R7dt@g^l}	ߠ!U^G=).\bnt$<E\xC88ԌFcǎ/_Qɓ'w y233tx7:͛7:ÇQ6lSR%t5$*%K̟?]qm޼]A绺޾}"JͥS|yt[8#Gܾ};NVVwQQ:D17qttD<'NܴiB[[6mڠCH&O/:to߾
+HT
+ׯ_G(F:̺r劧':䉏hѢ+Hz^B5Cڵ!T~pB]Af77;;;t\vv~[B``ӧyls@rg<̲PFWR|f͚C*,]t޼y
+6o>BgooO?###+ڻw/uٳgߧAqq~J-ىZr%u@sEEE!m۶w!:
+/BڼysPPuSSST*>|HŠcvvvVCmmm_~M
+p\o@0O9rDb=zp_љ>}zjj*u+FFF}K[lYp!uCׯ_|9u.RTݻS?~~~رc?ٳ!QJLSSѣ/_L"CyzzRW?UUU})u+ΝëR~g_~aܼ0JLll,w|||+n5*##:;.]P#ǎ`$:Ybg\.tFBK.N+:x3+t%keeu])J'77wذaRp'O{
t,ڌ	OףG__ߤ$
+!,^[CX177/--ūR vW^tFK@	wq!Cdffr'  `׮]ZZZR@銋8J:Nzw166VTxVtN<9i$
+6lذl2
+aHNb/..2JL7x߿_|A]TWWsCѣG!8;;_rUV!
+
+ZnM"Y0tٖ-[.\H]!)S>|x:u#G+Xi۶myyy޽CC]ʕ++$κ~ׯC{Ԯ]+ڽ{?u3f+X/_6lu4aPMEEE!dgϞuss~Ջ/CX4iǩ+XyR駟CXٳgEE%&5CBBbcc+#F\r:N:jc,fϰ3k֬;wRWH%7tP)kܼА:Y~}hh(uC2e
+us.ܲeuCSNJ7iCKh;v젮￧acǎjK.!@	@H^Kׯ_|9uS__RCXٳgeeevC4_aܹs=0(a'NxxxPW	W%KPW+nZvի+J!E*''gRJ3֭д&,VK"0tĉO:E]!{RW?/^P*ݣaʪwt[˗!QH%?w\
+!yxx;vxKLL`E__‚:LBB'ÝJN@	@HW2;wVC}
+ŋSW:u#G+XQ(999=J8dȐdӧOS?FTJS(phr:Xuu5#Vz]^^޶m[q yk֬҂}0aܨ+X166裏Cɓ''MD]~?JVZZ@"~qPmڴ~)f+ٳwI]
+^~0$6667nܠBupp~)ft?ލjuC
+͛7oB~$ip|$2D70*--͍L@ٸH2ԩSƍRaaaQQQ%&&PW%IQ*>Qyyy>}CڠA~WV͛m6
+\TTDŠqEEwJC">0$i驩BJHH~j5u+zĥ666uuu!<833/P@	@zv=k,
+!M8ĉۢE+XQ(ك
͛7SW0_/]Bd0(aHŋ!ԩZ*tҘ1cCXYz5Տ.vƍw9V---Cp'[#Fr
+uN<9a
+J!nݚ:@4~g/Pbnn^ZZj``@"0dݺuVR```||..Nf`{߾}#FP%ix捃.P(Cɿ?rHL&ϟ[ZZ޻w:333JejjJ"0!88xB
+~Ϟ=KŠIeee=CD,;;E·JNNJp1cƐ-	Ʀ@OO:ٺukPPuCϩ+Doɒ%111:tӓBaPgϞ)ʟ~:D0mڴ)++OC۷o[YYz:ɓ'9r@
+T*u+&&&?]ݩC%1cw}G]!m۶͛7ill}ݝ:%+kk[nQF__Ғ:Q*>afO:]T:PK]!%KPWo)))Ӈ:R^^СCzx3}ѣG+`PBǏB2dHFF~*:IIIᖜjժuQW0l+4
XXXpqeeG}DTUU)ʧORz9LFIJkF7PbP*Ä	Μ9C"d///
+৩iԨQ!tQVw҅:|Vٶmۂ+4y#GPWo111FJJʴiӨ+EQW0k=n߾mee+
D~oC]pLƍ;wu+EEEJ:DC0(ahRF&9sfر!OCCSII	u+fff.555~g'OP2`#7%###+G]IM._ti!BG2e
+uCK.믩+46澓SCCu`_ZZjhhHHzۺ@¼O]\.x񢋋us0ܼy:D0
+;~$MsssnksRy}Vt䝊0&$$PWiݺu[@@]+X/,,dgg>:D
+0(a:;\SW2'ON4
6,[ÝRW0t!OOO
+0(aV*=QEEE޽C)})6]
+ =*:{!`P 4uT$k۴i>L]J۶m˱Kիvvvo޼ae.\d!L`PH]!I&?~xf۬Y+.
6X-[,X	J$ܹceeKtYRQ}-Aq߃ܗ/CX.#GfffRb``PRR2`aPgϞڵ
Q*>aW^R:aPФH2{;Xt)w`E._|yذa!QiiinnngVSWƤ]^^u𓓓3|oRn:
+ 0o'ON0B04Cz/Mۗ:yⅥݻwCX*,,2x͍7CX0(ah>/??D
+VMdJKKtf&Nx	
+`P]ٳR9q?3<<<+Yx1uXf
uC;w1(av]KKK)icǎjK.!OuuǏCX2dHff\.b:##>}PJLI婩SN&Lpi
+V+++?#
+o߶~%u+NNNYYYZi9JL_>44BHHرcΜ9%%%y{{SWپ}y+Zr%uEaP`b+u`ps%s玕q1
+:~SPS%FIZM"\1tP৩%++:Ν;shC@HO/--544i	JlܸBHaaa[TTߎLvԩqQς+Z.]3fLSSu`lllqs%)//4hއܹs}Ox3gܳgu+2̙3cǎ
={fiiy}!އL^***xZ++[nQҵkWJաC~0(a.77wĉO<͛+7i_CPdee9::R>۷!L̜9sƍm%=zp=…qŋRoT?9@BCCׯ_O]!N:g!-@	νBBB^~Mrݻws玏Onn.uૡٹ:D0wڵ+uHaP`ڵk^^^eee!-wM>WcccLLLxx4>
@p?mmmCޗW_},1(aƝ{{롯޽{+@0I7CW\\cDiƍ!!!!99o߾!fp^"J255!EDDDGG#GLOO/w~yꐖP(ikddu0(ah˗/,Ycꐿ'/]4|p`ŋUUU!TVVу:DJRtիWRR3u`0(ahѣG̙SSSC.˗/ޥ=<((h߾}!8p?;rԩS+x޶m[۶mC@	@Ns())100RSSΝ+_M:::00:!J~@ի[5tЌ\N]]ZZJ'6oL]qFzM%mPTTĝ{ݼyWׯ7KڴiCT__oooVCCOOoʕqUV-aPuuu+Vؼy3՗ц9;B3p@
+ 
7J777߷o5u`P*.\𡆟wʔ)"m[~}hh(aL&={7|SvbP6Qĉ{nݺT*\_IKK9slmmqKfMMM#Fr
+ɳw=11Ņ	aPNH\.OOO|ٳg4mڴ)++O4++/^hyM}v%u]l)@RSS̙SM>֭[ϟggϞ3gj錍l奱g60۷opFN277/))144d %Ӎ=…2L3OӧjF
333
<stooׯ>,Ho		y5'211QTݻwg,TSST*^`͚5K.]w0(aB]]]DDDtt7,ܰaòe˄z4?7G=šC<===>;1:=طo1X<`PtA2xL]!**jڵo߾Xn޼y۷o1ryPPPttt֭}d .\ظ/P袂Q֭Z6551z͍7z@nbbСCz@i FyU0ĝ	:i&AM&={MGx


P[nm׮?`P{%}ѣGY$n:̙33s"-h5kDDD#tiǎB%I%x6mڴ|tURuЁiǏ_ZZ+hll*Ax߿QP۳h۷o[YYzehhW_f&@	@޼yf͚HhPP͛5V:eLZr%$+Crrr߾}%I%ɸt钟_UU՟+--mӦ@}gcvv6Hډ;M0̙3W*
+222ROOOa@	@J?p?^F;*:88TzWEEŻ2##>}h
+3%Rw5zJJJrvvX`PԹs>y?vUV&'d@8q.#fϞkdd* I?oFF;99eee-@"//\]]ϝ;(ݻٟΠv9~x" UܗVBBBXXXnn.b^xt;vرZҥU/^ufʔ)۷ofU@	@ڸV۶m+u9sL66	aÆ}}x{mfϞ}I??={PbŊ줤?E0(apG<1uo


Z!R@	4h%0Ja0@0(aaP
@	4h%0Ja0@0(aaP
@	4h%0Ja0@0(aaP
@	4h%0J}Eu \;-(n̵>F'؏X{vPX (s8ZC.g@0@0@0@0@0@0@0@0@0@0@0@0@0@0@0@0@0@0@0@0@0@0@0@0@0@0@0$+++ooo)MDIIIRFC@r(GA@r(GA@r(GA@r(GA@r(GA@r(GA@r(GA@r(GA@r(GA@r(Gd77sIi$Zjw)M#R׿o4                                                                    @9D{ݿٹo߾[G	襥p?&&gRRwA	Ȕ߹s'99c@0&%4! (O?\2##`
+9rӦMEC@P@
+@
+@
+@
+@
+@
+@
+@
+@n޽{GofBBBrrrJJʛ7o233gZZڛB|TTB
+UVRJŊmmm?9;;j~8::J7T
+  K(D}Oz'Ʀ~
6.ԨQ#)=@@8_~=&&~OMڵkҥZ@@fĈ۷o(TBvcРA>eP//ٽg̘1VVV| ("NNNÆ
Cv__033޽9sZj%/k(ҹsiӦuMvG@	1Ѱa9s@mI6met6*h4h 88]v;60{nO@P@dwAP@rL
+۰aÈ#dw>
+@ms0)\.`ƍÇ(ڔ`R=֭[(#P
+A0);wewJBP@
+*9rsβ; qppvZZdw ײeWPAvG (ֶ?mll̼gK?To.|@mx_cSӧO/]tcǎq_:uܹsBLsx(Ç[n]~˗/Umlذaj6e(hXlٝ;wkɓ'UTQ	ڔAӧiˎV5kL0A#)@ݻw[dI~~>mڴt)@HOO[ӧ	-zAP@#$::իW|.[lڴi|k^@@89߻w8lժ˗9}!(zɓ's,hiiammͱ&@m)Ж֭[Ǐsy͛s,gϞ%%%St,D^=^:mVVV::?}vZ*NNN..._U&.+++''=ݻw	1K$t
ŋM淚FE{-ut&֖v=e@@ƘI^^^yyy
+[nرEH._LZ335jԪUI&>>>;vӹv3g~q?͛7Ғv4HF7nܰaCjP@S?PRRRvv6ahV^No|AG/TNÿ@vNhk9w܃bccuRJ4h
hiX.ÈrLT	{[_€cӦM[jղe뛛ko@m,:44WѣGoذW5.?~gϞSN?''KM$ЧO/ԔKYhЦ{тN*uB=sR(rjG^3{,,,(y/N<^ei,۫PJKKwcbb藛7o({+yzz6+D#<988poẼ߿ٳ\jaE={RӐĉOYJFBa޺vڕzBP@4ر#jt0pj,GCU)77}Rj۶$0eʔUVסW>ym/~^z4S?ZǟXcRR.vϝ;wҮ^+SL>KVTS]xܹsqqq;[}*T߇\T;vՓRծ]{4T|NjzCBBB?A	E' :s1@>3:q)Eq#:gNނw%OOϙ3g:T'Nt҅K7o6nܘK));SzK4bvvvfӲe+W4
+Ypyh۽{U,???::4⧘D[ʕ+GFF6h@XYYY7nj?sĈ3f̨QFjժ05/Pݺu'LVX@m2~L#`WE@GVƍy.77Zj:޽^GwÇ{>&44믿fC~
+
+
+vEݻw5Zok֬{ձcÏ?&;377{222V\I^N"g
+
+v=3g1D@C'RF6FN>˫xUÇ'MwF#jբCI߾}W_рN``Ν;(//^YtQ‚wcx˗/jՊ.^JEdd洨Z%KF-.^q4hΝ;jBYq۶m4V0y{{oܸL|wT&j;
+Qt\MLLd hmx.]x\ask׮[~.W]]]E~x4ܩS3g-Zvc:
+xʂ>3fP~B)o˖-r'YHH?6oUpɓoܸj+zdڴiƍcv$ZXJEc2q)I?~GtTBs}v\J={ÃKOZbnŊis@VV=UAm߾K7|c7T^}ǎr'S|?a&v޽
+HNN2`4j(::K):8K)-h7YtYUB٣n#:۶m&//o;0773gb333vN{O:Fq^F\ҳgϔ@0	:tk˗O:K6l؁Tm/777hB&&Ozj:e^G.../^PrN8c>f޼yO=(_ŋUeJ*	n++5&8Xv@a,,,hp6i$)),,lРAP`zL6mٲe\Jɓ'ۇׄJ*Ve.3ɔ]ve =z
_vū?Z|'Od,B/**K>DZ9%@F<.27o~UeO#WUgnԨQt4ف3'Nϗ  A>}x]PWy??u4Fq:;::_233KII0MFRA#t7!sΛ7KJb=M...aaa~3VBj|#Gɓ'}bӮ]}U^]vG׏C@-@@rڵnݺ=
+QFhx:bTM\\\͚5cnܸѬY3:[fS±cWRJ/_\#8/^̱`
64r:p5JhMm! HP~.OtR.;{lϞ=ue.\Hae׮];a:fhc";wҟS *MzۗѿF˖-#""̎jR@tt4t|iӦ&z_"$$de}! }s)Ee::s)UѣGZz5߲r~-ߚ&ر#%.:ZZ}\}ւݻ8pǎ\S$116gʕbn([RJ&>rH^uzC^ڸqcM9sC]  ڻwlllx-}/޽{kkk/r8.+VHqʊKJwހ:ϥ]͚5!!!CoRNSP)h{^kYhQ@vxݜy֭͗"5*FÐW@0޽+
6=W޽{*hP<<z((5~'z
+w1+"QnݺYl
+lْk%}||жmدsɑc	FW͓Q
Xz:t.*Uh^?ܩ)~͍t6665QG>s"&hOJJb9(
+W@#Dȑ)
=4^N!M4a]*UpwwYe\\ܳgbcc+Tp*e2P:ӡ>P.]*ךe:ҠAO.]ԦM.iժJ+țݻS
7Ew޿_>̞={jTlmmͬ\r*U(ѧAjժ^bUhӭ[7OOOpKݻw۷~5fp7ٳ:t^{JիHRjjB޽
exY8.DϺu블yNlٲeƍKkt}add$
=5צM^|
+;`q3f8W,
Rc}P::;;3iiE^N@a@={^:--cO(Ӑk7P@kԨBgq I}x/}
+N:ҽ<,E>D>}=⢎/H}۶m!!!|GkT}ɓ/UI
[nUݽ(C&''߾}
+ѹI.  ߛ۷oy:tڣ}6ZjztPŋc\J=}F!uhlҍ-Z;u':qr,sN;iTMuu딆n{eC(mݺ@тWzӮ];#
GFF^,kRWZn

U;w7o;v,Gb6m4|p{]Æ
UmתUk|󍂩 h4җ! MgX^ᇅ2]s7
Pg͚r1賣#[iFw.>s,2Q1z>ڱdr,1z72ٲeN.߿?ߑV\XɓF5=۷UKPa嬬3g;vv@___u޼yCǁ/^XŊ̙3}toVZ5{l^>VK(9s&1g[ys>! hıٳg;vX$44u۶m:uR}:ǻ'N~Z˗/F#5{$'4TRƲ			#lB1Ypp1cT3)|JsSCu4?w
+5'''N4jԈWAa4uۺu!C*vzQ[A{5[FAAZg͚ű! o߾skccZB"tkҤ	giG;ǚo޼	<|0ǚ+W	hK=z{unܸA{&O}^;;;f8")3g7oT+:Bİ޻wˋT	$y\Q=y$lj
+4?ާO.VթS6)kqLJN|PDM6㏴WC@SN}WhՋEL^Nǚ޽A9֜6mڲeΤaÆw[\~iӦytȑExMѶm[:&OڵF#))M6Ϟ=c/5|pPNٳԨ]6/jժťZ	z:Thhh~ױcG.O	ܲeXL/2A@uW\>eppq4jԈOOO\]]y,2@@@Dž+Ws;;;Rݺu;vcJ|a2N.@}v"&۷/1~QFq)UӧO666({jj*{):"ݸq}--M|r:={{9ۍ7XݻwB	#F? #G43VVV			NS™3gO.!///^'{ൾ5kp8pcA777ڤY*|:up|~3+@Zh^GcܸqׯU8^7ӧY^="صk{gx-𞑑>)v}ցhRx[OLt:x	H
^eL!(M
^sȑnݺT+g:\JtU2(6===[
6RFtc,yfm߼yCd9HxxW_}^G4!cKKK)"]z^ёb07_.KiҥK\wrۗW5] (M4>s֭[CCC!JJwEΝqIM=zTR%.t{i.xMe2P:=z3&֬YKRYf„	\Jo߾},xMJhc-۷nnn^bq
FO4cSN1.99KhΛu[Pڔ@[QDDĮ]CDҠ=E7n|mqꇮ\Һuk.*VJ?pYr4c\G#$$ԓZA/_d)ҪU˗/w@D9R2qゃGømUT)66D:uzt655esƍf͚1Yp?X$??ٙ1HC+kiz1
6u]@øKڵk^gĉ˗O6̙3-ZX#@^^^111/ (ЙVQhwj]ٳ'  Ȇ
Ǝ˥?\nV͛sمyye
+ccchAںu2
'gϞa,Bh[bAoooi+.jw1cҥKP~	F.]XCΝc,n:^W6m4|p.6Qt7#???##~{=iğsݻwa{I:\pQƳgϸܯ}\ސZj=}ų3)ӐB5,Fѽ{w_~3:uʕ+x.>>^02d+bN:>d,qj}լYNm,*Vkoe.GGG爜
+ơRJ7odKʬ,.	Cwjp͛ի'"00pΝ,(i;88Pf)2cƌŋT`NZly(yf˟˖-c3yu?[Eh_>KJˉ0)\8S@mfdd$R?.l޺Rf~1cưd۷oRE5\\\\Mׯ_S0f}sa,ÇԩXdu;h :7nhҤ	{VX>;tP=<..Ã,@@Eʕ+N^[nGaÈm&Gp.oAb޼ysUrМ-7'4\f:~Raѣݻwg,Bn޼ٸqc:
+K.dz?Iŋ/40?kQVT@m4$zu[[[.(KpYlÆ
Gf\̩W޽{$''רQB2u׮]㸜vV4iwԉ!B[\V;2MFFIHߕw eytXf,k.]e<)x
%
JҀ2
+  h`R8ӧo/eҤ$ggg:x!mjjjooޥv1.iiiIR/X%vvv)@ݻwW|U{e/൐3(FDDwF6mDFFTuMX@VVVժUHa̤pzGGG"}aYŏ˚&^ `%}4jQ{^pѫWp:N!˖-
+
+b,²ŽBgqQm3.C)%%i›h<,2~W",}:MxL(xn{ek("4x捾U<gڵA4RAS4a˗/5sN
X)~odY{J,FF6m<)`̘1t `\]T.={6{:%ZI~;v`C~0QΈFi?_z`&MܸqCW=xqKKK:~VZFXXX޽K.q)x5׌iii^ժU+>&P1)_~J{.(_re˖uζaeY{
.WOKhXv-f7A&&&DULo[uڕq	"ӱLehd;֥K.5(Fnᵒ#b':!(Myݺu[|9zZ;,,NBBy^˗/Ԯ]}A}%W1ܹӰaC}:pf*nWm6x`^B[;
4U$88xܸq,̚5kѢE\J={p)eQFѲ{a(qF.sI:b
+  hS~hݺ+W}ښ/\sɸL0AsY]1[[[J5DDԊ?ݗ?lذ-[srrmi\\(yf.KHc
NNN))){a(O	ԃٓb
+  hS7Ν;zh	h~111EhH;.Yf
+nCP^^9{r)???"/^l۶_d%n|3foW811Q;8y?EY	ׯ\FtX&ssseP<~>h/9s%K;@:!(qz'OL㭊+l466#6|ٳu222/UiVxyw\Rb){*ۺ}4ee*hyG2&W2/zMYqƍ&M(x!5kְw@3i
+  hcQFAAArAk-0:t۶m,έ쉺++[F,]t:u>x@&~:uX*סC.VщuY#
{a@Ξ=lYѣGoܸ2b
+  hcdpљ]vPbŜ"`nݺť?2du'O0y
GTT({cǎݰa^5i$ex׮]=r
G	%nB
+vD":lj9:i`6A.  `޽u_Q	hc.)I&+'ׯUPP@ꤤ$
+~r$1c$[ӷfq`BƌTKɓ'}}}2JiVv/aX\b
+  hɓW^^<,]*cǎgϞeE͚5?pddo>h -Aw߿[nǎSPSrtX
+,o>qIc
\VY|dnn޸qce=tPϞ=0|p.+6-[,((Ax<
,KOHh3g.YDjtNNNvttה=1򱷑}Y4>`.K+c
\-e͛76m^C\&P@m۱c,ZƝuP쇦OtR:jѢŵkX*|l:u<|PR:u:s'ٳ,g֫W/
+s>}8q00z7סEk=|!(
w쳰kX<*?ܹsXbʔ)uJ>И1c>\uշ˧NɿFRo"2d{[[d=Wq	oE04C:Q4XG{iZ(6ܥV^}C-pt?_)U||;䔘Xbŋz.Soݟ?odyyy,G|77oVrcժUcCyu[6μy\(<==L@Ȑ!C(6juh2kjjJ@Uu/GΝСcͺui	֫I)x--ڑiwַNq޽;KxXJ*iiiܗ(5ܻwAuT),2j(y!(

uV:7n9r${FoS)uXj-F%MNNQFAA^Ethʖݽ~z&Mfu4(QTq~۷εkך5k^8kϷw999ц]<*748q"{$YOT#(
ݠ
{=zσn֬Y-b3tА:ZMzq򸸸ႲY/\Ю];ik}[;wyv,G\ԂךDjXiӦMdd${
^WpLԜ$~D@(w_޼ys:։3I֩SѣG֬Y3a:)[^W}|ݻwײnGevҫҨQϢ?Pgh;#3#4\3{ŋϘ1N{;;o߲ݻu@@@
t^zzz:{);'t¥T[VTvZƘ1k֬DÆ
۲e^/?e^Q4/4MժUKJJP\ANNN,h>yv\zUqضmСCԫWOjƤm۶uh׋l>~@@(^ÃyիWxx8{ggDwx-??_qOOOͬ4*0`/W0`ff&%
SZjҤI&˦;vݺu_;J7od3{1FʥԥK,m4͛O?q)%NR!(
J6m~q)4hЀe0]D)&K=}4KMÃޱc^/ze*Umk׮ǏU>>>&$$ԬYH{َ;*~M|r:+W~!̑%	@7muʨk׮hтK)[nկ_K5!(
J^xQF
.?jKkРA;wRr)I[loY*,X ((H_~q<	V899y,{;l` &L`hwOyڵ\J;v݌e5:wxEAP]I9=߿ۛsss:::Effsvv
+͚5[hb׭[7vX-*sO8Q~a…_,wޱB.1_\2۷/REGG˝SAÇs={UM
+  h]zyyݹsҒK5yׄm۶eG_]*rNbbF;w|}_w9!ݻwE~Oɓ\Jծ]͛\Q]\\`c"FDC#w
6ꡱ m߿RMee\_yxO>W_lѤpo:u+[sQӦMy}O#
+fxUڵkxx2&On۶W515k*(CEAPˣG*W̥v϶oҥK\?RJ\(//-99Yd!˗)S9v铖/_>uT-{Օ
+N8qռ9FΞ=ë}df%iDȫիW ̙3;wUּ
+vXQzΤpe˖;W<3]iQw'O;de:"yyy=~W*U\pApCXX@P@i&::W.]8p@}ojjOOO^uժU+aq5j6cǎ	kݻw9E٘BetRK_|Ņc2_~XNF!!!6AP%tm*TXSSbXP/u,%@pp0iii\&m|~g1dѨrŰa84|.\СߚW^-[ -??A\ֲ)ҷoߝ;wZYYqI)W5MMM7oα^ϟ?w\
qGGGaI666/^5\tw|gt,2i{5C0)\YGN)҈6s777
taPPoGP@o>d5۴i+7oތ7ɥZ>}Y_jp2NHH7|åvMnݺU@C⬾k֬[ŵKKK߳gϱc(YJS:Rqliiw͟?_|Ϟ=۽{͛
+
+|]y-ZqTlٲm>sMA@@:7nΝ;|˺Yܺuk|0)MOڵk׎tFZpG###W999\i!}aTL4RW娙3g֮][&3g{ɢ,TN̊+T*N;է$ ;|:ҦBCEoԸC+J*M8ۈ6bP_4K˖-ӧ۾};~KPceo3F&8dzƫZ(=
+AE;ښ^hPEo!Cu9BsΝ={~},Yķ	]&ik>>\TՁ4+ܬY/>>^[ WE6lxm-r_!;wx쎨ܟcdɒ3gQ_︗Φ.888P[ŤԩЄ9q℟M`"uP+ihĉŏeA͛'&&7n9r^"99f͚E3?ܵk&MZj֬YC}V^M,U(gϞbl1??wުNe8C@"""|||zw}w!a<ȷJh߿o߾޺uK3tm۶Ocǎ]nM@^zղeKA	c_E_mڴ{vCJ2b5&G>:Gtfݻ7ߚ*윞αyiaȑ[ln:tp9U(/ҘF/1cv>}J۪Q>S`P::2})޽[幻+T^/LzjZj)))6QIݺu>ĬQݻ~
+Y0>Lݷor3|ڬY-ZīZNΜ9ëZ:ԳgO^ժT@?yTUcz)pխ8fVׯϟ?/jC9}t@@P0eʔ˗=9/qqqkuەhիW\M0a͚5\J	@i:ucΝСzL
+g9rF#jNNСCCCCeu@U! '{ca|^^J^߯X,/JJxEPPжmۤ=h b;v޽;
`'OA;R
+.Zhذa;„	fb`nnsggg^]n̘1KZj=~X
+||rt)s3纹ǫWT׮]Rp)2Q]PP7}["chm6:jY!ClݺUv/z'CCCicT.Ѓ`ŋxBb7h8w\ԫoeYmJ֞ꚜUT+n޼r;v>>>cƌݻ7D5]AAÇiFQv_=tCm׮쾔.'''$$d=899Pϯk׮fn(@&-\VVڵk.]k.Hݙ77n4kLkmG _UkLb
+1111˖-۷o_FF/7i҄FrCs.hB1`ժUgϞ5hccӱcGSPݝO@FTzuR:tԩSF$ά eBNNɓ'nJG^SիWoذatN*Ou|ƹs]/Gj׮ͻG"//{ٻwoBB>ЛE
___::88l@/^:}txxd=V^R%J
}ܿtJ)1oܸ1g˖-ԩc  ȄERRehUfkk^KKK
w.\w`4mF<^,vvv_}kaaλ;ޢCѮqڵlUsssA?
5(NV3)ƆK
+BNx{TZZZAZرc-T+Ld
+TGvêGшiZ_2!ͥB7nP
stZZ*~Gb)htirګgݺu5P$###PddӧOXbvpp-6`4ߘFZ矚wʕ+wG#{1?)@&0ntzJJJٛ7o2/4wtt/t-+P#P$11%gVV)4Ȱ133/Κ Trr2m*ϟ?_4m'/_\+
+*FD"0C۸8ziKMMIOOΦ_ͤ]bED;=Ommm%le Ȅ!Ȅ!Ȅ!Ȅ!Ȅ!Ȅ!Ȅ!Ȅ!Ȅ!Ȅ!Ȅ!Ȅ!Ȅ!Ȅ!Ȅ!Ȅ!Ȅ!Ȅ!Ȅ!Ȅ!Ȅ!Ȅ!Ȅ!Ȕ$igϞIi$;w4|SJiZC~xkԨ ihƌK.ڵk{)Mk666RVZJJ@)SZJJ6l=z5|)MW\933SJ ッ4e˖aÆIiZC~ (Hi:77JJ K``ݻ4k׮HiZ Mvv4
>}ZJӧ5"&&&Ji:::[J K&Mnݺ%3gtIJ6lx)M߿wRY_z%i6h@J|||Ξ=+%KIi̴zbbM$|סR9rƍ4
Rܾ}qƲZɩPM$7nRn߾4
RܹsРAR6i
"o_*{⅔@Yf-ZHJuԹD		odKGGGY`z
+t׮]=*".\СCY8p_V ~Ji_4] @||֧Lb
+YH=ҥKO.u
*U͕zƍo޼)i?1b u
~111R633?ʃرCVnjԨ5%8p׮]ZzAAA,--߼ycee%".\8{lY+WnZVѲZ/b(СC={պcbb4:uʕ+e>xm۶j8ꫯ$vTWfM.^xƌZ/b(pQ^Yc;wjvȑ=zH'$v@À@>}8 ukkkvvv:;댹yZZZ*Udue˖I͛ddddgg@ӦM_.(DDDmVbZh%׏7Nb&Nzj(b@ 77jժ999pԩ/;׭[{K@
+sgΜSn֭Æ
SSDggg}(bX`ҥҧF/xnÆ
޽+{
8.U#,2P	
+Ԕ*oh2)X6Y2@HT25HMPS1F&b;Q
+=z0>z~sv.hPQQ~=
/r~~l%77744Taƌ͓mJ[@qww?t`hܶm[޽`ۧO۷fl޼900P*
tنK2C
ǎstt͸Js@	megg+h'O>-1nܸ˗6\Osl6?C%ѵkJ`Ҥ+ZhWin(111˖-ho,ZH-QVV6`"|Q'''ٌiq
6LIym6VǏŸ`6]]]?.c֭;v@3̘1#))INھo.^X+WJW~!C444H֭CC`Ϟ=ǐ=zȑ#!͞=ߗFX\\,]qd*++X,!C_^:"xwwwiwdeeEGGKW\hٴi,X ]qСC+pG}ĉ!W6lڵy:qDkVZ5zhFhw(f͚7ot5#"]憇k<<1cIW(!!Am ScǎN:UUUuY:l`ڵbH(:::55I:@wcbbftȍ̙3w\[DFFXB!:`ӧkUUU?߽Cᑗ#`Ξ=rJblc(qqq3Liii!laaai?w:H܆^zHThhhjj/`ϟ?/rSQQQg3@INNNHH=zddd8P:~SLϗgyL-
g޽!K/}gnnn!~ҥfs`ظqcPPtHP֮];rHk߾}BB{צM/L4L:"##+銦Wsss+?		%UUU|AVVMT;w{]J4
#Gx{{:uJ:z[oEDD888Hhځ>/RO[*JFFFlltE^k۶tTTT,\0''A~z 69T{-lݺuS122K@^MMMnnnvvMl\j2{)<69?w'Oi'|244TݻKk


?:;wN:&L ]l:q3ftqpp
+
+
+~}Y'''"ĉ?Caau;&sGFj*銖DGGgeeIWXGtuu.Sf~۱cGII?{n"eꨦ^T.]CZ¶ٳg~KX_ǎ=<)Sku=sk׮ԩt]a@IOO8q")BBBڵk'rPV\e6Ci#Goڴi#rb(W	&888H]zʎ;F_I@[ٳΝ+r/h(6Z!Њ6m,[,""B:PN>=v^:v횗 rn(%GӧO@zַo_uwss8.+((7nӧC  ..nɒ%NNN!~RYYV^^.{CK.7nt]…sINN@zЯ_1z_>**;18yd~z+?>_tW^_|EPPt<[W_=ttqҤIIII&IE7:w\bb"W??}Jhq-[C<@bbbllt0nJ3			n@Snzҥt1n699yѢEgΜnQF-XSEMrO?tm4hPRRR~CjP;wNW2dܹ!l555<8@h>|̙3/bK-t…O>d׮]-r}N:M0֭[rrr^֧OHb먩Q`奥#xyyI<>|8??o-))...Ç


		qttαek֬),,ܴiS]]t6l:_cu6lPK`ƍ;wX,E8x`u֭t=cS555[l))))..޾}Sd899yyy
0   @{!]I޹sgyyyEEž}>
+/Length 2967
+/Filter /FlateDecode
+>>
+stream
+x?#j]0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&	a0@ L&==
+endstream
+endobj
+2 0 obj
+<<
+/ProcSet [/PDF /Text /ImageB /ImageC /ImageI]
+/Font <<
+/F1 7 0 R
+/F2 9 0 R
+>>
+/XObject <<
+/I0 10 0 R
+>>
+>>
+endobj
+319 0 obj
+<> endobj
+320 0 obj
+<> endobj
+321 0 obj
+<> endobj
+322 0 obj
+<> endobj
+323 0 obj
+<> endobj
+324 0 obj
+<> endobj
+325 0 obj
+<> endobj
+326 0 obj
+<> endobj
+327 0 obj
+<> endobj
+328 0 obj
+<> endobj
+329 0 obj
+<> endobj
+330 0 obj
+<> endobj
+331 0 obj
+<> endobj
+332 0 obj
+<> endobj
+333 0 obj
+<> endobj
+334 0 obj
+<> endobj
+335 0 obj
+<> endobj
+336 0 obj
+<> endobj
+337 0 obj
+<> endobj
+338 0 obj
+<> endobj
+339 0 obj
+<> endobj
+340 0 obj
+<> endobj
+341 0 obj
+<> endobj
+342 0 obj
+<> endobj
+343 0 obj
+<> endobj
+344 0 obj
+<> endobj
+345 0 obj
+<> endobj
+346 0 obj
+<> endobj
+347 0 obj
+<> endobj
+348 0 obj
+<> endobj
+349 0 obj
+<> endobj
+350 0 obj
+<> endobj
+351 0 obj
+<> endobj
+352 0 obj
+<> endobj
+353 0 obj
+<> endobj
+354 0 obj
+<> endobj
+355 0 obj
+<> endobj
+356 0 obj
+<> endobj
+357 0 obj
+<> endobj
+358 0 obj
+<> endobj
+359 0 obj
+<> endobj
+360 0 obj
+<> endobj
+361 0 obj
+<> endobj
+362 0 obj
+<> endobj
+363 0 obj
+<> endobj
+364 0 obj
+<> endobj
+365 0 obj
+<> endobj
+366 0 obj
+<> endobj
+367 0 obj
+<> endobj
+368 0 obj
+<> endobj
+369 0 obj
+<> endobj
+370 0 obj
+<> endobj
+371 0 obj
+<> endobj
+372 0 obj
+<> endobj
+373 0 obj
+<> endobj
+374 0 obj
+<> endobj
+375 0 obj
+<> endobj
+376 0 obj
+<> endobj
+377 0 obj
+<> endobj
+378 0 obj
+<> endobj
+379 0 obj
+<> endobj
+380 0 obj
+<> endobj
+381 0 obj
+<> endobj
+382 0 obj
+<> endobj
+383 0 obj
+<> endobj
+384 0 obj
+<> endobj
+385 0 obj
+<> endobj
+386 0 obj
+<> endobj
+387 0 obj
+<> endobj
+388 0 obj
+<> endobj
+389 0 obj
+<> endobj
+390 0 obj
+<> endobj
+391 0 obj
+<> endobj
+392 0 obj
+<> endobj
+393 0 obj
+<> endobj
+394 0 obj
+<> endobj
+395 0 obj
+<> endobj
+396 0 obj
+<> endobj
+397 0 obj
+<> endobj
+398 0 obj
+<> endobj
+399 0 obj
+<> endobj
+400 0 obj
+<> endobj
+401 0 obj
+<> endobj
+402 0 obj
+<> endobj
+403 0 obj
+<> endobj
+404 0 obj
+<> endobj
+405 0 obj
+<> endobj
+406 0 obj
+<> endobj
+407 0 obj
+<> endobj
+408 0 obj
+<> endobj
+409 0 obj
+<> endobj
+410 0 obj
+<> endobj
+411 0 obj
+<> endobj
+412 0 obj
+<> endobj
+413 0 obj
+<> endobj
+414 0 obj
+<> endobj
+415 0 obj
+<> endobj
+416 0 obj
+<> endobj
+417 0 obj
+<> endobj
+418 0 obj
+<> endobj
+419 0 obj
+<> endobj
+420 0 obj
+<> endobj
+421 0 obj
+<> endobj
+422 0 obj
+<> endobj
+423 0 obj
+<> endobj
+424 0 obj
+<> endobj
+425 0 obj
+<> endobj
+426 0 obj
+<> endobj
+427 0 obj
+<> endobj
+428 0 obj
+<> endobj
+429 0 obj
+<> endobj
+430 0 obj
+<> endobj
+431 0 obj
+<> endobj
+432 0 obj
+<> endobj
+433 0 obj
+<> endobj
+434 0 obj
+<> endobj
+435 0 obj
+<> endobj
+436 0 obj
+<> endobj
+437 0 obj
+<> endobj
+438 0 obj
+<> endobj
+439 0 obj
+<> endobj
+440 0 obj
+<> endobj
+441 0 obj
+<> endobj
+442 0 obj
+<> endobj
+443 0 obj
+<> endobj
+444 0 obj
+<> endobj
+445 0 obj
+<> endobj
+446 0 obj
+<> endobj
+447 0 obj
+<> endobj
+448 0 obj
+<> endobj
+449 0 obj
+<> endobj
+450 0 obj
+<> endobj
+451 0 obj
+<> endobj
+452 0 obj
+<> endobj
+453 0 obj
+<> endobj
+454 0 obj
+<> endobj
+455 0 obj
+<> endobj
+456 0 obj
+<> endobj
+457 0 obj
+<> endobj
+458 0 obj
+<> endobj
+459 0 obj
+<> endobj
+460 0 obj
+<> endobj
+461 0 obj
+<> endobj
+462 0 obj
+<> endobj
+463 0 obj
+<> endobj
+464 0 obj
+<> endobj
+465 0 obj
+<> endobj
+466 0 obj
+<> endobj
+467 0 obj
+<> endobj
+468 0 obj
+<> endobj
+469 0 obj
+<> endobj
+470 0 obj
+<> endobj
+471 0 obj
+<> endobj
+472 0 obj
+<> endobj
+473 0 obj
+<> endobj
+474 0 obj
+<> endobj
+475 0 obj
+<> endobj
+476 0 obj
+<> endobj
+477 0 obj
+<> endobj
+478 0 obj
+<> endobj
+479 0 obj
+<> endobj
+480 0 obj
+<> endobj
+481 0 obj
+<> endobj
+482 0 obj
+<> endobj
+483 0 obj
+<> endobj
+484 0 obj
+<> endobj
+485 0 obj
+<> endobj
+486 0 obj
+<> endobj
+487 0 obj
+<> endobj
+488 0 obj
+<> endobj
+489 0 obj
+<> endobj
+490 0 obj
+<> endobj
+491 0 obj
+<> endobj
+492 0 obj
+<> endobj
+493 0 obj
+<> endobj
+494 0 obj
+<> endobj
+495 0 obj
+<> endobj
+496 0 obj
+<> endobj
+497 0 obj
+<> endobj
+498 0 obj
+<> endobj
+499 0 obj
+<> endobj
+500 0 obj
+<> endobj
+501 0 obj
+<> endobj
+502 0 obj
+<> endobj
+503 0 obj
+<> endobj
+504 0 obj
+<> endobj
+505 0 obj
+<> endobj
+506 0 obj
+<> endobj
+507 0 obj
+<> endobj
+508 0 obj
+<> endobj
+509 0 obj
+<> endobj
+510 0 obj
+<> endobj
+511 0 obj
+<> endobj
+512 0 obj
+<> endobj
+513 0 obj
+<> endobj
+514 0 obj
+<> endobj
+515 0 obj
+<> endobj
+516 0 obj
+<> endobj
+517 0 obj
+<> endobj
+518 0 obj
+<> endobj
+519 0 obj
+<> endobj
+520 0 obj
+<> endobj
+521 0 obj
+<> endobj
+522 0 obj
+<> endobj
+523 0 obj
+<> endobj
+524 0 obj
+<> endobj
+525 0 obj
+<> endobj
+526 0 obj
+<> endobj
+527 0 obj
+<> endobj
+528 0 obj
+<> endobj
+529 0 obj
+<> endobj
+530 0 obj
+<> endobj
+531 0 obj
+<> endobj
+532 0 obj
+<> endobj
+533 0 obj
+<> endobj
+534 0 obj
+<> endobj
+535 0 obj
+<> endobj
+536 0 obj
+<> endobj
+537 0 obj
+<> endobj
+538 0 obj
+<> endobj
+539 0 obj
+<> endobj
+540 0 obj
+<> endobj
+541 0 obj
+<> endobj
+542 0 obj
+<> endobj
+543 0 obj
+<> endobj
+544 0 obj
+<> endobj
+545 0 obj
+<> endobj
+546 0 obj
+<> endobj
+547 0 obj
+<> endobj
+548 0 obj
+<> endobj
+549 0 obj
+<> endobj
+550 0 obj
+<> endobj
+551 0 obj
+<> endobj
+552 0 obj
+<> endobj
+553 0 obj
+<> endobj
+554 0 obj
+<> endobj
+555 0 obj
+<> endobj
+556 0 obj
+<> endobj
+557 0 obj
+<> endobj
+558 0 obj
+<> endobj
+559 0 obj
+<> endobj
+560 0 obj
+<> endobj
+561 0 obj
+<> endobj
+562 0 obj
+<> endobj
+563 0 obj
+<> endobj
+564 0 obj
+<> endobj
+565 0 obj
+<> endobj
+566 0 obj
+<> endobj
+567 0 obj
+<> endobj
+568 0 obj
+<> endobj
+569 0 obj
+<> endobj
+570 0 obj
+<> endobj
+571 0 obj
+<> endobj
+572 0 obj
+<> endobj
+573 0 obj
+<> endobj
+574 0 obj
+<> endobj
+575 0 obj
+<> endobj
+576 0 obj
+<> endobj
+577 0 obj
+<> endobj
+578 0 obj
+<> endobj
+579 0 obj
+<> endobj
+580 0 obj
+<> endobj
+581 0 obj
+<> endobj
+582 0 obj
+<> endobj
+583 0 obj
+<> endobj
+584 0 obj
+<> endobj
+585 0 obj
+<> endobj
+586 0 obj
+<> endobj
+587 0 obj
+<> endobj
+588 0 obj
+<> endobj
+589 0 obj
+<> endobj
+590 0 obj
+<> endobj
+591 0 obj
+<> endobj
+592 0 obj
+<> endobj
+593 0 obj
+<> endobj
+594 0 obj
+<> endobj
+595 0 obj
+<> endobj
+596 0 obj
+<> endobj
+597 0 obj
+<> endobj
+598 0 obj
+<> endobj
+599 0 obj
+<> endobj
+600 0 obj
+<> endobj
+601 0 obj
+<> endobj
+602 0 obj
+<> endobj
+603 0 obj
+<> endobj
+604 0 obj
+<> endobj
+605 0 obj
+<> endobj
+606 0 obj
+<> endobj
+607 0 obj
+<> endobj
+608 0 obj
+<> endobj
+609 0 obj
+<> endobj
+610 0 obj
+<> endobj
+611 0 obj
+<> endobj
+612 0 obj
+<> endobj
+613 0 obj
+<> endobj
+614 0 obj
+<> endobj
+615 0 obj
+<> endobj
+616 0 obj
+<> endobj
+617 0 obj
+<> endobj
+618 0 obj
+<> endobj
+619 0 obj
+<> endobj
+620 0 obj
+<> endobj
+621 0 obj
+<> endobj
+622 0 obj
+<> endobj
+623 0 obj
+<> endobj
+624 0 obj
+<> endobj
+625 0 obj
+<> endobj
+626 0 obj
+<> endobj
+627 0 obj
+<> endobj
+628 0 obj
+<> endobj
+629 0 obj
+<> endobj
+630 0 obj
+<> endobj
+631 0 obj
+<> endobj
+632 0 obj
+<> endobj
+633 0 obj
+<> endobj
+634 0 obj
+<> endobj
+635 0 obj
+<> endobj
+636 0 obj
+<> endobj
+637 0 obj
+<> endobj
+638 0 obj
+<> endobj
+639 0 obj
+<> endobj
+640 0 obj
+<> endobj
+641 0 obj
+<> endobj
+642 0 obj
+<> endobj
+643 0 obj
+<> endobj
+644 0 obj
+<> endobj
+645 0 obj
+<> endobj
+646 0 obj
+<> endobj
+647 0 obj
+<> endobj
+648 0 obj
+<> endobj
+649 0 obj
+<> endobj
+650 0 obj
+<> endobj
+651 0 obj
+<> endobj
+652 0 obj
+<> endobj
+653 0 obj
+<> endobj
+654 0 obj
+<> endobj
+655 0 obj
+<> endobj
+656 0 obj
+<> endobj
+657 0 obj
+<> endobj
+658 0 obj
+<> endobj
+659 0 obj
+<> endobj
+660 0 obj
+<> endobj
+661 0 obj
+<> endobj
+662 0 obj
+<> endobj
+663 0 obj
+<> endobj
+664 0 obj
+<> endobj
+665 0 obj
+<> endobj
+666 0 obj
+<> endobj
+667 0 obj
+<> endobj
+668 0 obj
+<> endobj
+669 0 obj
+<> endobj
+670 0 obj
+<> endobj
+671 0 obj
+<> endobj
+672 0 obj
+<> endobj
+673 0 obj
+<> endobj
+674 0 obj
+<> endobj
+675 0 obj
+<> endobj
+676 0 obj
+<> endobj
+677 0 obj
+<> endobj
+678 0 obj
+<> endobj
+679 0 obj
+<> endobj
+680 0 obj
+<> endobj
+681 0 obj
+<> endobj
+682 0 obj
+<> endobj
+683 0 obj
+<> endobj
+684 0 obj
+<> endobj
+685 0 obj
+<> endobj
+686 0 obj
+<> endobj
+687 0 obj
+<> endobj
+688 0 obj
+<> endobj
+689 0 obj
+<> endobj
+690 0 obj
+<> endobj
+691 0 obj
+<> endobj
+692 0 obj
+<> endobj
+693 0 obj
+<> endobj
+694 0 obj
+<> endobj
+695 0 obj
+<> endobj
+696 0 obj
+<> endobj
+697 0 obj
+<> endobj
+698 0 obj
+<> endobj
+699 0 obj
+<> endobj
+700 0 obj
+<> endobj
+701 0 obj
+<> endobj
+702 0 obj
+<> endobj
+703 0 obj
+<> endobj
+704 0 obj
+<> endobj
+705 0 obj
+<> endobj
+706 0 obj
+<> endobj
+707 0 obj
+<> endobj
+708 0 obj
+<> endobj
+709 0 obj
+<> endobj
+710 0 obj
+<> endobj
+711 0 obj
+<> endobj
+712 0 obj
+<> endobj
+713 0 obj
+<> endobj
+714 0 obj
+<> endobj
+715 0 obj
+<> endobj
+716 0 obj
+<> endobj
+717 0 obj
+<> endobj
+718 0 obj
+<> endobj
+719 0 obj
+<> endobj
+720 0 obj
+<> endobj
+721 0 obj
+<> endobj
+722 0 obj
+<> endobj
+723 0 obj
+<> endobj
+724 0 obj
+<> endobj
+725 0 obj
+<> endobj
+726 0 obj
+<> endobj
+727 0 obj
+<> endobj
+728 0 obj
+<> endobj
+729 0 obj
+<> endobj
+730 0 obj
+<> endobj
+731 0 obj
+<> endobj
+732 0 obj
+<> endobj
+733 0 obj
+<> endobj
+734 0 obj
+<> endobj
+735 0 obj
+<> endobj
+736 0 obj
+<> endobj
+737 0 obj
+<> endobj
+738 0 obj
+<> endobj
+739 0 obj
+<> endobj
+740 0 obj
+<> endobj
+741 0 obj
+<> endobj
+742 0 obj
+<> endobj
+743 0 obj
+<> endobj
+744 0 obj
+<> endobj
+745 0 obj
+<> endobj
+746 0 obj
+<> endobj
+747 0 obj
+<> endobj
+748 0 obj
+<> endobj
+749 0 obj
+<> endobj
+750 0 obj
+<> endobj
+751 0 obj
+<> endobj
+752 0 obj
+<> endobj
+753 0 obj
+<> endobj
+754 0 obj
+<> endobj
+755 0 obj
+<> endobj
+756 0 obj
+<> endobj
+757 0 obj
+<> endobj
+758 0 obj
+<> endobj
+759 0 obj
+<> endobj
+760 0 obj
+<> endobj
+761 0 obj
+<> endobj
+762 0 obj
+<> endobj
+763 0 obj
+<> endobj
+764 0 obj
+<> endobj
+765 0 obj
+<> endobj
+766 0 obj
+<> endobj
+767 0 obj
+<> endobj
+768 0 obj
+<> endobj
+769 0 obj
+<> endobj
+770 0 obj
+<> endobj
+771 0 obj
+<> endobj
+772 0 obj
+<> endobj
+773 0 obj
+<> endobj
+774 0 obj
+<> endobj
+775 0 obj
+<> endobj
+776 0 obj
+<> endobj
+777 0 obj
+<> endobj
+778 0 obj
+<> endobj
+779 0 obj
+<> endobj
+780 0 obj
+<> endobj
+781 0 obj
+<> endobj
+782 0 obj
+<> endobj
+783 0 obj
+<> endobj
+784 0 obj
+<> endobj
+785 0 obj
+<> endobj
+786 0 obj
+<> endobj
+787 0 obj
+<> endobj
+788 0 obj
+<> endobj
+789 0 obj
+<> endobj
+790 0 obj
+<> endobj
+791 0 obj
+<> endobj
+792 0 obj
+<> endobj
+793 0 obj
+<> endobj
+794 0 obj
+<> endobj
+795 0 obj
+<> endobj
+796 0 obj
+<> endobj
+797 0 obj
+<> endobj
+798 0 obj
+<> endobj
+799 0 obj
+<> endobj
+800 0 obj
+<> endobj
+801 0 obj
+<> endobj
+802 0 obj
+<> endobj
+803 0 obj
+<> endobj
+804 0 obj
+<> endobj
+805 0 obj
+<> endobj
+806 0 obj
+<> endobj
+807 0 obj
+<> endobj
+808 0 obj
+<> endobj
+809 0 obj
+<> endobj
+810 0 obj
+<> endobj
+811 0 obj
+<> endobj
+812 0 obj
+<> endobj
+813 0 obj
+<> endobj
+814 0 obj
+<> endobj
+815 0 obj
+<> endobj
+816 0 obj
+<> endobj
+817 0 obj
+<> endobj
+818 0 obj
+<> endobj
+819 0 obj
+<> endobj
+820 0 obj
+<> endobj
+821 0 obj
+<> endobj
+822 0 obj
+<> endobj
+823 0 obj
+<> endobj
+824 0 obj
+<> endobj
+825 0 obj
+<> endobj
+826 0 obj
+<> endobj
+827 0 obj
+<> endobj
+828 0 obj
+<> endobj
+829 0 obj
+<> endobj
+830 0 obj
+<> endobj
+831 0 obj
+<> endobj
+832 0 obj
+<> endobj
+833 0 obj
+<> endobj
+834 0 obj
+<> endobj
+835 0 obj
+<> endobj
+836 0 obj
+<> endobj
+837 0 obj
+<> endobj
+838 0 obj
+<> endobj
+839 0 obj
+<> endobj
+840 0 obj
+<> endobj
+841 0 obj
+<> endobj
+842 0 obj
+<> endobj
+843 0 obj
+<> endobj
+844 0 obj
+<> endobj
+845 0 obj
+<> endobj
+846 0 obj
+<> endobj
+847 0 obj
+<> endobj
+848 0 obj
+<> endobj
+849 0 obj
+<> endobj
+850 0 obj
+<> endobj
+851 0 obj
+<> endobj
+852 0 obj
+<> endobj
+853 0 obj
+<> endobj
+854 0 obj
+<> endobj
+855 0 obj
+<> endobj
+856 0 obj
+<> endobj
+857 0 obj
+<> endobj
+858 0 obj
+<> endobj
+859 0 obj
+<> endobj
+860 0 obj
+<> endobj
+
+12 0 obj
+<<
+/Type /Outlines
+/First 13 0 R
+/Last 15 0 R
+/Count 306
+>> 
+endobj
+
+13 0 obj
+<<
+/Title (Pages)
+/Parent 12 0 R
+/Next 15 0 R
+/First 14 0 R
+/Last 14 0 R
+/Count 1
+>> 
+endobj
+
+15 0 obj
+<<
+/Title (Net)
+/Parent 12 0 R
+/Prev 13 0 R
+/First 16 0 R
+/Last 315 0 R
+/Count 303
+>> 
+endobj
+
+14 0 obj
+<<
+/Title (SCH_Pro-micro_Pinouts 1-Sheet_1)
+/Parent 13 0 R
+/Dest [3 0 R /XYZ 0 1197.36 0]
+>> 
+endobj
+
+16 0 obj
+<<
+/Title (3V3)
+/Parent 15 0 R
+/Next 35 0 R
+/First 17 0 R
+/Last 34 0 R
+/Count 18
+>> 
+endobj
+
+35 0 obj
+<<
+/Title (+5V)
+/Parent 15 0 R
+/Prev 16 0 R
+/Next 39 0 R
+/First 36 0 R
+/Last 38 0 R
+/Count 3
+>> 
+endobj
+
+39 0 obj
+<<
+/Title ($1N1)
+/Parent 15 0 R
+/Prev 35 0 R
+/Next 41 0 R
+/First 40 0 R
+/Last 40 0 R
+/Count 1
+>> 
+endobj
+
+41 0 obj
+<<
+/Title ($1N25)
+/Parent 15 0 R
+/Prev 39 0 R
+/Next 43 0 R
+/First 42 0 R
+/Last 42 0 R
+/Count 1
+>> 
+endobj
+
+43 0 obj
+<<
+/Title ($1N62)
+/Parent 15 0 R
+/Prev 41 0 R
+/Next 45 0 R
+/First 44 0 R
+/Last 44 0 R
+/Count 1
+>> 
+endobj
+
+45 0 obj
+<<
+/Title (ADC)
+/Parent 15 0 R
+/Prev 43 0 R
+/Next 50 0 R
+/First 46 0 R
+/Last 49 0 R
+/Count 4
+>> 
+endobj
+
+50 0 obj
+<<
+/Title (BATT)
+/Parent 15 0 R
+/Prev 45 0 R
+/Next 57 0 R
+/First 51 0 R
+/Last 56 0 R
+/Count 6
+>> 
+endobj
+
+57 0 obj
+<<
+/Title (BUSY)
+/Parent 15 0 R
+/Prev 50 0 R
+/Next 73 0 R
+/First 58 0 R
+/Last 72 0 R
+/Count 15
+>> 
+endobj
+
+73 0 obj
+<<
+/Title (CS)
+/Parent 15 0 R
+/Prev 57 0 R
+/Next 89 0 R
+/First 74 0 R
+/Last 88 0 R
+/Count 15
+>> 
+endobj
+
+89 0 obj
+<<
+/Title (DIO2)
+/Parent 15 0 R
+/Prev 73 0 R
+/Next 102 0 R
+/First 90 0 R
+/Last 101 0 R
+/Count 12
+>> 
+endobj
+
+102 0 obj
+<<
+/Title (DIO3)
+/Parent 15 0 R
+/Prev 89 0 R
+/Next 104 0 R
+/First 103 0 R
+/Last 103 0 R
+/Count 1
+>> 
+endobj
+
+104 0 obj
+<<
+/Title (E_INK_BUSY)
+/Parent 15 0 R
+/Prev 102 0 R
+/Next 109 0 R
+/First 105 0 R
+/Last 108 0 R
+/Count 4
+>> 
+endobj
+
+109 0 obj
+<<
+/Title (E_INK_CS)
+/Parent 15 0 R
+/Prev 104 0 R
+/Next 113 0 R
+/First 110 0 R
+/Last 112 0 R
+/Count 3
+>> 
+endobj
+
+113 0 obj
+<<
+/Title (E_INK_D/C)
+/Parent 15 0 R
+/Prev 109 0 R
+/Next 117 0 R
+/First 114 0 R
+/Last 116 0 R
+/Count 3
+>> 
+endobj
+
+117 0 obj
+<<
+/Title (E_INK_NRST)
+/Parent 15 0 R
+/Prev 113 0 R
+/Next 121 0 R
+/First 118 0 R
+/Last 120 0 R
+/Count 3
+>> 
+endobj
+
+121 0 obj
+<<
+/Title (GND)
+/Parent 15 0 R
+/Prev 117 0 R
+/Next 196 0 R
+/First 122 0 R
+/Last 195 0 R
+/Count 74
+>> 
+endobj
+
+196 0 obj
+<<
+/Title (GPSEN)
+/Parent 15 0 R
+/Prev 121 0 R
+/Next 199 0 R
+/First 197 0 R
+/Last 198 0 R
+/Count 2
+>> 
+endobj
+
+199 0 obj
+<<
+/Title (GPSRX)
+/Parent 15 0 R
+/Prev 196 0 R
+/Next 202 0 R
+/First 200 0 R
+/Last 201 0 R
+/Count 2
+>> 
+endobj
+
+202 0 obj
+<<
+/Title (GPSTX)
+/Parent 15 0 R
+/Prev 199 0 R
+/Next 205 0 R
+/First 203 0 R
+/Last 204 0 R
+/Count 2
+>> 
+endobj
+
+205 0 obj
+<<
+/Title (IRQ)
+/Parent 15 0 R
+/Prev 202 0 R
+/Next 221 0 R
+/First 206 0 R
+/Last 220 0 R
+/Count 15
+>> 
+endobj
+
+221 0 obj
+<<
+/Title (LORA_ANT)
+/Parent 15 0 R
+/Prev 205 0 R
+/Next 223 0 R
+/First 222 0 R
+/Last 222 0 R
+/Count 1
+>> 
+endobj
+
+223 0 obj
+<<
+/Title (MISO)
+/Parent 15 0 R
+/Prev 221 0 R
+/Next 239 0 R
+/First 224 0 R
+/Last 238 0 R
+/Count 15
+>> 
+endobj
+
+239 0 obj
+<<
+/Title (MOSI)
+/Parent 15 0 R
+/Prev 223 0 R
+/Next 257 0 R
+/First 240 0 R
+/Last 256 0 R
+/Count 17
+>> 
+endobj
+
+257 0 obj
+<<
+/Title (NRST)
+/Parent 15 0 R
+/Prev 239 0 R
+/Next 273 0 R
+/First 258 0 R
+/Last 272 0 R
+/Count 15
+>> 
+endobj
+
+273 0 obj
+<<
+/Title (RBTN)
+/Parent 15 0 R
+/Prev 257 0 R
+/Next 277 0 R
+/First 274 0 R
+/Last 276 0 R
+/Count 3
+>> 
+endobj
+
+277 0 obj
+<<
+/Title (RXEN)
+/Parent 15 0 R
+/Prev 273 0 R
+/Next 285 0 R
+/First 278 0 R
+/Last 284 0 R
+/Count 7
+>> 
+endobj
+
+285 0 obj
+<<
+/Title (SCK)
+/Parent 15 0 R
+/Prev 277 0 R
+/Next 303 0 R
+/First 286 0 R
+/Last 302 0 R
+/Count 17
+>> 
+endobj
+
+303 0 obj
+<<
+/Title (SCL)
+/Parent 15 0 R
+/Prev 285 0 R
+/Next 306 0 R
+/First 304 0 R
+/Last 305 0 R
+/Count 2
+>> 
+endobj
+
+306 0 obj
+<<
+/Title (SDA)
+/Parent 15 0 R
+/Prev 303 0 R
+/Next 309 0 R
+/First 307 0 R
+/Last 308 0 R
+/Count 2
+>> 
+endobj
+
+309 0 obj
+<<
+/Title (SERIAL2RX)
+/Parent 15 0 R
+/Prev 306 0 R
+/Next 312 0 R
+/First 310 0 R
+/Last 311 0 R
+/Count 2
+>> 
+endobj
+
+312 0 obj
+<<
+/Title (SERIAL2TX)
+/Parent 15 0 R
+/Prev 309 0 R
+/Next 315 0 R
+/First 313 0 R
+/Last 314 0 R
+/Count 2
+>> 
+endobj
+
+315 0 obj
+<<
+/Title (UBTN)
+/Parent 15 0 R
+/Prev 312 0 R
+/First 316 0 R
+/Last 318 0 R
+/Count 3
+>> 
+endobj
+
+17 0 obj
+<<
+/Title ($1N5)
+/Parent 16 0 R
+/Next 18 0 R
+/A 319 0 R
+>> 
+endobj
+
+18 0 obj
+<<
+/Title ($1N29)
+/Parent 16 0 R
+/Prev 17 0 R
+/Next 19 0 R
+/A 321 0 R
+>> 
+endobj
+
+19 0 obj
+<<
+/Title ($1N35)
+/Parent 16 0 R
+/Prev 18 0 R
+/Next 20 0 R
+/A 323 0 R
+>> 
+endobj
+
+20 0 obj
+<<
+/Title ($1N54)
+/Parent 16 0 R
+/Prev 19 0 R
+/Next 21 0 R
+/A 325 0 R
+>> 
+endobj
+
+21 0 obj
+<<
+/Title ($1N1528)
+/Parent 16 0 R
+/Prev 20 0 R
+/Next 22 0 R
+/A 327 0 R
+>> 
+endobj
+
+22 0 obj
+<<
+/Title ($1N1550)
+/Parent 16 0 R
+/Prev 21 0 R
+/Next 23 0 R
+/A 329 0 R
+>> 
+endobj
+
+23 0 obj
+<<
+/Title ($1N1574)
+/Parent 16 0 R
+/Prev 22 0 R
+/Next 24 0 R
+/A 331 0 R
+>> 
+endobj
+
+24 0 obj
+<<
+/Title ($1N1582)
+/Parent 16 0 R
+/Prev 23 0 R
+/Next 25 0 R
+/A 333 0 R
+>> 
+endobj
+
+25 0 obj
+<<
+/Title ($1N1616)
+/Parent 16 0 R
+/Prev 24 0 R
+/Next 26 0 R
+/A 335 0 R
+>> 
+endobj
+
+26 0 obj
+<<
+/Title ($1N1618)
+/Parent 16 0 R
+/Prev 25 0 R
+/Next 27 0 R
+/A 337 0 R
+>> 
+endobj
+
+27 0 obj
+<<
+/Title ($1N1732)
+/Parent 16 0 R
+/Prev 26 0 R
+/Next 28 0 R
+/A 339 0 R
+>> 
+endobj
+
+28 0 obj
+<<
+/Title ($1N1742)
+/Parent 16 0 R
+/Prev 27 0 R
+/Next 29 0 R
+/A 341 0 R
+>> 
+endobj
+
+29 0 obj
+<<
+/Title ($1N1776)
+/Parent 16 0 R
+/Prev 28 0 R
+/Next 30 0 R
+/A 343 0 R
+>> 
+endobj
+
+30 0 obj
+<<
+/Title ($1N1810)
+/Parent 16 0 R
+/Prev 29 0 R
+/Next 31 0 R
+/A 345 0 R
+>> 
+endobj
+
+31 0 obj
+<<
+/Title ($1N1860)
+/Parent 16 0 R
+/Prev 30 0 R
+/Next 32 0 R
+/A 347 0 R
+>> 
+endobj
+
+32 0 obj
+<<
+/Title ($1N1874)
+/Parent 16 0 R
+/Prev 31 0 R
+/Next 33 0 R
+/A 349 0 R
+>> 
+endobj
+
+33 0 obj
+<<
+/Title ($1N5406)
+/Parent 16 0 R
+/Prev 32 0 R
+/Next 34 0 R
+/A 351 0 R
+>> 
+endobj
+
+34 0 obj
+<<
+/Title ($1N5445)
+/Parent 16 0 R
+/Prev 33 0 R
+/A 353 0 R
+>> 
+endobj
+
+36 0 obj
+<<
+/Title ($1N23)
+/Parent 35 0 R
+/Next 37 0 R
+/A 355 0 R
+>> 
+endobj
+
+37 0 obj
+<<
+/Title ($1N31)
+/Parent 35 0 R
+/Prev 36 0 R
+/Next 38 0 R
+/A 357 0 R
+>> 
+endobj
+
+38 0 obj
+<<
+/Title ($1N1570)
+/Parent 35 0 R
+/Prev 37 0 R
+/A 359 0 R
+>> 
+endobj
+
+40 0 obj
+<<
+/Title ($1N1)
+/Parent 39 0 R
+/A 361 0 R
+>> 
+endobj
+
+42 0 obj
+<<
+/Title ($1N25)
+/Parent 41 0 R
+/A 363 0 R
+>> 
+endobj
+
+44 0 obj
+<<
+/Title ($1N62)
+/Parent 43 0 R
+/A 365 0 R
+>> 
+endobj
+
+46 0 obj
+<<
+/Title ($1N15)
+/Parent 45 0 R
+/Next 47 0 R
+/A 367 0 R
+>> 
+endobj
+
+47 0 obj
+<<
+/Title ($1N44)
+/Parent 45 0 R
+/Prev 46 0 R
+/Next 48 0 R
+/A 369 0 R
+>> 
+endobj
+
+48 0 obj
+<<
+/Title ($1N1852)
+/Parent 45 0 R
+/Prev 47 0 R
+/Next 49 0 R
+/A 371 0 R
+>> 
+endobj
+
+49 0 obj
+<<
+/Title ($1N1856)
+/Parent 45 0 R
+/Prev 48 0 R
+/A 373 0 R
+>> 
+endobj
+
+51 0 obj
+<<
+/Title ($1N1494)
+/Parent 50 0 R
+/Next 52 0 R
+/A 375 0 R
+>> 
+endobj
+
+52 0 obj
+<<
+/Title ($1N1508)
+/Parent 50 0 R
+/Prev 51 0 R
+/Next 53 0 R
+/A 377 0 R
+>> 
+endobj
+
+53 0 obj
+<<
+/Title ($1N1578)
+/Parent 50 0 R
+/Prev 52 0 R
+/Next 54 0 R
+/A 379 0 R
+>> 
+endobj
+
+54 0 obj
+<<
+/Title ($1N1846)
+/Parent 50 0 R
+/Prev 53 0 R
+/Next 55 0 R
+/A 381 0 R
+>> 
+endobj
+
+55 0 obj
+<<
+/Title ($1N1848)
+/Parent 50 0 R
+/Prev 54 0 R
+/Next 56 0 R
+/A 383 0 R
+>> 
+endobj
+
+56 0 obj
+<<
+/Title ($1N1854)
+/Parent 50 0 R
+/Prev 55 0 R
+/A 385 0 R
+>> 
+endobj
+
+58 0 obj
+<<
+/Title ($1N12)
+/Parent 57 0 R
+/Next 59 0 R
+/A 387 0 R
+>> 
+endobj
+
+59 0 obj
+<<
+/Title ($1N47)
+/Parent 57 0 R
+/Prev 58 0 R
+/Next 60 0 R
+/A 389 0 R
+>> 
+endobj
+
+60 0 obj
+<<
+/Title ($1N1540)
+/Parent 57 0 R
+/Prev 59 0 R
+/Next 61 0 R
+/A 391 0 R
+>> 
+endobj
+
+61 0 obj
+<<
+/Title ($1N1568)
+/Parent 57 0 R
+/Prev 60 0 R
+/Next 62 0 R
+/A 393 0 R
+>> 
+endobj
+
+62 0 obj
+<<
+/Title ($1N1600)
+/Parent 57 0 R
+/Prev 61 0 R
+/Next 63 0 R
+/A 395 0 R
+>> 
+endobj
+
+63 0 obj
+<<
+/Title ($1N1636)
+/Parent 57 0 R
+/Prev 62 0 R
+/Next 64 0 R
+/A 397 0 R
+>> 
+endobj
+
+64 0 obj
+<<
+/Title ($1N1660)
+/Parent 57 0 R
+/Prev 63 0 R
+/Next 65 0 R
+/A 399 0 R
+>> 
+endobj
+
+65 0 obj
+<<
+/Title ($1N1696)
+/Parent 57 0 R
+/Prev 64 0 R
+/Next 66 0 R
+/A 401 0 R
+>> 
+endobj
+
+66 0 obj
+<<
+/Title ($1N1710)
+/Parent 57 0 R
+/Prev 65 0 R
+/Next 67 0 R
+/A 403 0 R
+>> 
+endobj
+
+67 0 obj
+<<
+/Title ($1N1736)
+/Parent 57 0 R
+/Prev 66 0 R
+/Next 68 0 R
+/A 405 0 R
+>> 
+endobj
+
+68 0 obj
+<<
+/Title ($1N1760)
+/Parent 57 0 R
+/Prev 67 0 R
+/Next 69 0 R
+/A 407 0 R
+>> 
+endobj
+
+69 0 obj
+<<
+/Title ($1N1778)
+/Parent 57 0 R
+/Prev 68 0 R
+/Next 70 0 R
+/A 409 0 R
+>> 
+endobj
+
+70 0 obj
+<<
+/Title ($1N1814)
+/Parent 57 0 R
+/Prev 69 0 R
+/Next 71 0 R
+/A 411 0 R
+>> 
+endobj
+
+71 0 obj
+<<
+/Title ($1N1836)
+/Parent 57 0 R
+/Prev 70 0 R
+/Next 72 0 R
+/A 413 0 R
+>> 
+endobj
+
+72 0 obj
+<<
+/Title ($1N1870)
+/Parent 57 0 R
+/Prev 71 0 R
+/A 415 0 R
+>> 
+endobj
+
+74 0 obj
+<<
+/Title ($1N10)
+/Parent 73 0 R
+/Next 75 0 R
+/A 417 0 R
+>> 
+endobj
+
+75 0 obj
+<<
+/Title ($1N49)
+/Parent 73 0 R
+/Prev 74 0 R
+/Next 76 0 R
+/A 419 0 R
+>> 
+endobj
+
+76 0 obj
+<<
+/Title ($1N1542)
+/Parent 73 0 R
+/Prev 75 0 R
+/Next 77 0 R
+/A 421 0 R
+>> 
+endobj
+
+77 0 obj
+<<
+/Title ($1N1566)
+/Parent 73 0 R
+/Prev 76 0 R
+/Next 78 0 R
+/A 423 0 R
+>> 
+endobj
+
+78 0 obj
+<<
+/Title ($1N1602)
+/Parent 73 0 R
+/Prev 77 0 R
+/Next 79 0 R
+/A 425 0 R
+>> 
+endobj
+
+79 0 obj
+<<
+/Title ($1N1626)
+/Parent 73 0 R
+/Prev 78 0 R
+/Next 80 0 R
+/A 427 0 R
+>> 
+endobj
+
+80 0 obj
+<<
+/Title ($1N1670)
+/Parent 73 0 R
+/Prev 79 0 R
+/Next 81 0 R
+/A 429 0 R
+>> 
+endobj
+
+81 0 obj
+<<
+/Title ($1N1686)
+/Parent 73 0 R
+/Prev 80 0 R
+/Next 82 0 R
+/A 431 0 R
+>> 
+endobj
+
+82 0 obj
+<<
+/Title ($1N1718)
+/Parent 73 0 R
+/Prev 81 0 R
+/Next 83 0 R
+/A 433 0 R
+>> 
+endobj
+
+83 0 obj
+<<
+/Title ($1N1728)
+/Parent 73 0 R
+/Prev 82 0 R
+/Next 84 0 R
+/A 435 0 R
+>> 
+endobj
+
+84 0 obj
+<<
+/Title ($1N1752)
+/Parent 73 0 R
+/Prev 83 0 R
+/Next 85 0 R
+/A 437 0 R
+>> 
+endobj
+
+85 0 obj
+<<
+/Title ($1N1788)
+/Parent 73 0 R
+/Prev 84 0 R
+/Next 86 0 R
+/A 439 0 R
+>> 
+endobj
+
+86 0 obj
+<<
+/Title ($1N1808)
+/Parent 73 0 R
+/Prev 85 0 R
+/Next 87 0 R
+/A 441 0 R
+>> 
+endobj
+
+87 0 obj
+<<
+/Title ($1N1828)
+/Parent 73 0 R
+/Prev 86 0 R
+/Next 88 0 R
+/A 443 0 R
+>> 
+endobj
+
+88 0 obj
+<<
+/Title ($1N1876)
+/Parent 73 0 R
+/Prev 87 0 R
+/A 445 0 R
+>> 
+endobj
+
+90 0 obj
+<<
+/Title ($1N1624)
+/Parent 89 0 R
+/Next 91 0 R
+/A 447 0 R
+>> 
+endobj
+
+91 0 obj
+<<
+/Title ($1N1672)
+/Parent 89 0 R
+/Prev 90 0 R
+/Next 92 0 R
+/A 449 0 R
+>> 
+endobj
+
+92 0 obj
+<<
+/Title ($1N1684)
+/Parent 89 0 R
+/Prev 91 0 R
+/Next 93 0 R
+/A 451 0 R
+>> 
+endobj
+
+93 0 obj
+<<
+/Title ($1N1698)
+/Parent 89 0 R
+/Prev 92 0 R
+/Next 94 0 R
+/A 453 0 R
+>> 
+endobj
+
+94 0 obj
+<<
+/Title ($1N1700)
+/Parent 89 0 R
+/Prev 93 0 R
+/Next 95 0 R
+/A 455 0 R
+>> 
+endobj
+
+95 0 obj
+<<
+/Title ($1N1702)
+/Parent 89 0 R
+/Prev 94 0 R
+/Next 96 0 R
+/A 457 0 R
+>> 
+endobj
+
+96 0 obj
+<<
+/Title ($1N1744)
+/Parent 89 0 R
+/Prev 95 0 R
+/Next 97 0 R
+/A 459 0 R
+>> 
+endobj
+
+97 0 obj
+<<
+/Title ($1N1750)
+/Parent 89 0 R
+/Prev 96 0 R
+/Next 98 0 R
+/A 461 0 R
+>> 
+endobj
+
+98 0 obj
+<<
+/Title ($1N1820)
+/Parent 89 0 R
+/Prev 97 0 R
+/Next 99 0 R
+/A 463 0 R
+>> 
+endobj
+
+99 0 obj
+<<
+/Title ($1N1822)
+/Parent 89 0 R
+/Prev 98 0 R
+/Next 100 0 R
+/A 465 0 R
+>> 
+endobj
+
+100 0 obj
+<<
+/Title ($1N1862)
+/Parent 89 0 R
+/Prev 99 0 R
+/Next 101 0 R
+/A 467 0 R
+>> 
+endobj
+
+101 0 obj
+<<
+/Title ($1N1864)
+/Parent 89 0 R
+/Prev 100 0 R
+/A 469 0 R
+>> 
+endobj
+
+103 0 obj
+<<
+/Title ($1N1678)
+/Parent 102 0 R
+/A 471 0 R
+>> 
+endobj
+
+105 0 obj
+<<
+/Title ($1N1500)
+/Parent 104 0 R
+/Next 106 0 R
+/A 473 0 R
+>> 
+endobj
+
+106 0 obj
+<<
+/Title ($1N1514)
+/Parent 104 0 R
+/Prev 105 0 R
+/Next 107 0 R
+/A 475 0 R
+>> 
+endobj
+
+107 0 obj
+<<
+/Title ($1N5294)
+/Parent 104 0 R
+/Prev 106 0 R
+/Next 108 0 R
+/A 477 0 R
+>> 
+endobj
+
+108 0 obj
+<<
+/Title ($1N5448)
+/Parent 104 0 R
+/Prev 107 0 R
+/A 479 0 R
+>> 
+endobj
+
+110 0 obj
+<<
+/Title ($1N1506)
+/Parent 109 0 R
+/Next 111 0 R
+/A 481 0 R
+>> 
+endobj
+
+111 0 obj
+<<
+/Title ($1N5342)
+/Parent 109 0 R
+/Prev 110 0 R
+/Next 112 0 R
+/A 483 0 R
+>> 
+endobj
+
+112 0 obj
+<<
+/Title ($1N5457)
+/Parent 109 0 R
+/Prev 111 0 R
+/A 485 0 R
+>> 
+endobj
+
+114 0 obj
+<<
+/Title ($1N1504)
+/Parent 113 0 R
+/Next 115 0 R
+/A 487 0 R
+>> 
+endobj
+
+115 0 obj
+<<
+/Title ($1N5326)
+/Parent 113 0 R
+/Prev 114 0 R
+/Next 116 0 R
+/A 489 0 R
+>> 
+endobj
+
+116 0 obj
+<<
+/Title ($1N5454)
+/Parent 113 0 R
+/Prev 115 0 R
+/A 491 0 R
+>> 
+endobj
+
+118 0 obj
+<<
+/Title ($1N1502)
+/Parent 117 0 R
+/Next 119 0 R
+/A 493 0 R
+>> 
+endobj
+
+119 0 obj
+<<
+/Title ($1N5310)
+/Parent 117 0 R
+/Prev 118 0 R
+/Next 120 0 R
+/A 495 0 R
+>> 
+endobj
+
+120 0 obj
+<<
+/Title ($1N5451)
+/Parent 117 0 R
+/Prev 119 0 R
+/A 497 0 R
+>> 
+endobj
+
+122 0 obj
+<<
+/Title ($1N2)
+/Parent 121 0 R
+/Next 123 0 R
+/A 499 0 R
+>> 
+endobj
+
+123 0 obj
+<<
+/Title ($1N6)
+/Parent 121 0 R
+/Prev 122 0 R
+/Next 124 0 R
+/A 501 0 R
+>> 
+endobj
+
+124 0 obj
+<<
+/Title ($1N22)
+/Parent 121 0 R
+/Prev 123 0 R
+/Next 125 0 R
+/A 503 0 R
+>> 
+endobj
+
+125 0 obj
+<<
+/Title ($1N24)
+/Parent 121 0 R
+/Prev 124 0 R
+/Next 126 0 R
+/A 505 0 R
+>> 
+endobj
+
+126 0 obj
+<<
+/Title ($1N26)
+/Parent 121 0 R
+/Prev 125 0 R
+/Next 127 0 R
+/A 507 0 R
+>> 
+endobj
+
+127 0 obj
+<<
+/Title ($1N27)
+/Parent 121 0 R
+/Prev 126 0 R
+/Next 128 0 R
+/A 509 0 R
+>> 
+endobj
+
+128 0 obj
+<<
+/Title ($1N28)
+/Parent 121 0 R
+/Prev 127 0 R
+/Next 129 0 R
+/A 511 0 R
+>> 
+endobj
+
+129 0 obj
+<<
+/Title ($1N30)
+/Parent 121 0 R
+/Prev 128 0 R
+/Next 130 0 R
+/A 513 0 R
+>> 
+endobj
+
+130 0 obj
+<<
+/Title ($1N32)
+/Parent 121 0 R
+/Prev 129 0 R
+/Next 131 0 R
+/A 515 0 R
+>> 
+endobj
+
+131 0 obj
+<<
+/Title ($1N36)
+/Parent 121 0 R
+/Prev 130 0 R
+/Next 132 0 R
+/A 517 0 R
+>> 
+endobj
+
+132 0 obj
+<<
+/Title ($1N37)
+/Parent 121 0 R
+/Prev 131 0 R
+/Next 133 0 R
+/A 519 0 R
+>> 
+endobj
+
+133 0 obj
+<<
+/Title ($1N53)
+/Parent 121 0 R
+/Prev 132 0 R
+/Next 134 0 R
+/A 521 0 R
+>> 
+endobj
+
+134 0 obj
+<<
+/Title ($1N58)
+/Parent 121 0 R
+/Prev 133 0 R
+/Next 135 0 R
+/A 523 0 R
+>> 
+endobj
+
+135 0 obj
+<<
+/Title ($1N61)
+/Parent 121 0 R
+/Prev 134 0 R
+/Next 136 0 R
+/A 525 0 R
+>> 
+endobj
+
+136 0 obj
+<<
+/Title ($1N63)
+/Parent 121 0 R
+/Prev 135 0 R
+/Next 137 0 R
+/A 527 0 R
+>> 
+endobj
+
+137 0 obj
+<<
+/Title ($1N1516)
+/Parent 121 0 R
+/Prev 136 0 R
+/Next 138 0 R
+/A 529 0 R
+>> 
+endobj
+
+138 0 obj
+<<
+/Title ($1N1518)
+/Parent 121 0 R
+/Prev 137 0 R
+/Next 139 0 R
+/A 531 0 R
+>> 
+endobj
+
+139 0 obj
+<<
+/Title ($1N1524)
+/Parent 121 0 R
+/Prev 138 0 R
+/Next 140 0 R
+/A 533 0 R
+>> 
+endobj
+
+140 0 obj
+<<
+/Title ($1N1526)
+/Parent 121 0 R
+/Prev 139 0 R
+/Next 141 0 R
+/A 535 0 R
+>> 
+endobj
+
+141 0 obj
+<<
+/Title ($1N1530)
+/Parent 121 0 R
+/Prev 140 0 R
+/Next 142 0 R
+/A 537 0 R
+>> 
+endobj
+
+142 0 obj
+<<
+/Title ($1N1532)
+/Parent 121 0 R
+/Prev 141 0 R
+/Next 143 0 R
+/A 539 0 R
+>> 
+endobj
+
+143 0 obj
+<<
+/Title ($1N1534)
+/Parent 121 0 R
+/Prev 142 0 R
+/Next 144 0 R
+/A 541 0 R
+>> 
+endobj
+
+144 0 obj
+<<
+/Title ($1N1552)
+/Parent 121 0 R
+/Prev 143 0 R
+/Next 145 0 R
+/A 543 0 R
+>> 
+endobj
+
+145 0 obj
+<<
+/Title ($1N1554)
+/Parent 121 0 R
+/Prev 144 0 R
+/Next 146 0 R
+/A 545 0 R
+>> 
+endobj
+
+146 0 obj
+<<
+/Title ($1N1572)
+/Parent 121 0 R
+/Prev 145 0 R
+/Next 147 0 R
+/A 547 0 R
+>> 
+endobj
+
+147 0 obj
+<<
+/Title ($1N1576)
+/Parent 121 0 R
+/Prev 146 0 R
+/Next 148 0 R
+/A 549 0 R
+>> 
+endobj
+
+148 0 obj
+<<
+/Title ($1N1580)
+/Parent 121 0 R
+/Prev 147 0 R
+/Next 149 0 R
+/A 551 0 R
+>> 
+endobj
+
+149 0 obj
+<<
+/Title ($1N1584)
+/Parent 121 0 R
+/Prev 148 0 R
+/Next 150 0 R
+/A 553 0 R
+>> 
+endobj
+
+150 0 obj
+<<
+/Title ($1N1586)
+/Parent 121 0 R
+/Prev 149 0 R
+/Next 151 0 R
+/A 555 0 R
+>> 
+endobj
+
+151 0 obj
+<<
+/Title ($1N1588)
+/Parent 121 0 R
+/Prev 150 0 R
+/Next 152 0 R
+/A 557 0 R
+>> 
+endobj
+
+152 0 obj
+<<
+/Title ($1N1590)
+/Parent 121 0 R
+/Prev 151 0 R
+/Next 153 0 R
+/A 559 0 R
+>> 
+endobj
+
+153 0 obj
+<<
+/Title ($1N1592)
+/Parent 121 0 R
+/Prev 152 0 R
+/Next 154 0 R
+/A 561 0 R
+>> 
+endobj
+
+154 0 obj
+<<
+/Title ($1N1594)
+/Parent 121 0 R
+/Prev 153 0 R
+/Next 155 0 R
+/A 563 0 R
+>> 
+endobj
+
+155 0 obj
+<<
+/Title ($1N1596)
+/Parent 121 0 R
+/Prev 154 0 R
+/Next 156 0 R
+/A 565 0 R
+>> 
+endobj
+
+156 0 obj
+<<
+/Title ($1N1598)
+/Parent 121 0 R
+/Prev 155 0 R
+/Next 157 0 R
+/A 567 0 R
+>> 
+endobj
+
+157 0 obj
+<<
+/Title ($1N1614)
+/Parent 121 0 R
+/Prev 156 0 R
+/Next 158 0 R
+/A 569 0 R
+>> 
+endobj
+
+158 0 obj
+<<
+/Title ($1N1638)
+/Parent 121 0 R
+/Prev 157 0 R
+/Next 159 0 R
+/A 571 0 R
+>> 
+endobj
+
+159 0 obj
+<<
+/Title ($1N1640)
+/Parent 121 0 R
+/Prev 158 0 R
+/Next 160 0 R
+/A 573 0 R
+>> 
+endobj
+
+160 0 obj
+<<
+/Title ($1N1642)
+/Parent 121 0 R
+/Prev 159 0 R
+/Next 161 0 R
+/A 575 0 R
+>> 
+endobj
+
+161 0 obj
+<<
+/Title ($1N1644)
+/Parent 121 0 R
+/Prev 160 0 R
+/Next 162 0 R
+/A 577 0 R
+>> 
+endobj
+
+162 0 obj
+<<
+/Title ($1N1646)
+/Parent 121 0 R
+/Prev 161 0 R
+/Next 163 0 R
+/A 579 0 R
+>> 
+endobj
+
+163 0 obj
+<<
+/Title ($1N1648)
+/Parent 121 0 R
+/Prev 162 0 R
+/Next 164 0 R
+/A 581 0 R
+>> 
+endobj
+
+164 0 obj
+<<
+/Title ($1N1650)
+/Parent 121 0 R
+/Prev 163 0 R
+/Next 165 0 R
+/A 583 0 R
+>> 
+endobj
+
+165 0 obj
+<<
+/Title ($1N1652)
+/Parent 121 0 R
+/Prev 164 0 R
+/Next 166 0 R
+/A 585 0 R
+>> 
+endobj
+
+166 0 obj
+<<
+/Title ($1N1656)
+/Parent 121 0 R
+/Prev 165 0 R
+/Next 167 0 R
+/A 587 0 R
+>> 
+endobj
+
+167 0 obj
+<<
+/Title ($1N1658)
+/Parent 121 0 R
+/Prev 166 0 R
+/Next 168 0 R
+/A 589 0 R
+>> 
+endobj
+
+168 0 obj
+<<
+/Title ($1N1708)
+/Parent 121 0 R
+/Prev 167 0 R
+/Next 169 0 R
+/A 591 0 R
+>> 
+endobj
+
+169 0 obj
+<<
+/Title ($1N1730)
+/Parent 121 0 R
+/Prev 168 0 R
+/Next 170 0 R
+/A 593 0 R
+>> 
+endobj
+
+170 0 obj
+<<
+/Title ($1N1734)
+/Parent 121 0 R
+/Prev 169 0 R
+/Next 171 0 R
+/A 595 0 R
+>> 
+endobj
+
+171 0 obj
+<<
+/Title ($1N1740)
+/Parent 121 0 R
+/Prev 170 0 R
+/Next 172 0 R
+/A 597 0 R
+>> 
+endobj
+
+172 0 obj
+<<
+/Title ($1N1762)
+/Parent 121 0 R
+/Prev 171 0 R
+/Next 173 0 R
+/A 599 0 R
+>> 
+endobj
+
+173 0 obj
+<<
+/Title ($1N1764)
+/Parent 121 0 R
+/Prev 172 0 R
+/Next 174 0 R
+/A 601 0 R
+>> 
+endobj
+
+174 0 obj
+<<
+/Title ($1N1766)
+/Parent 121 0 R
+/Prev 173 0 R
+/Next 175 0 R
+/A 603 0 R
+>> 
+endobj
+
+175 0 obj
+<<
+/Title ($1N1768)
+/Parent 121 0 R
+/Prev 174 0 R
+/Next 176 0 R
+/A 605 0 R
+>> 
+endobj
+
+176 0 obj
+<<
+/Title ($1N1770)
+/Parent 121 0 R
+/Prev 175 0 R
+/Next 177 0 R
+/A 607 0 R
+>> 
+endobj
+
+177 0 obj
+<<
+/Title ($1N1774)
+/Parent 121 0 R
+/Prev 176 0 R
+/Next 178 0 R
+/A 609 0 R
+>> 
+endobj
+
+178 0 obj
+<<
+/Title ($1N1790)
+/Parent 121 0 R
+/Prev 177 0 R
+/Next 179 0 R
+/A 611 0 R
+>> 
+endobj
+
+179 0 obj
+<<
+/Title ($1N1792)
+/Parent 121 0 R
+/Prev 178 0 R
+/Next 180 0 R
+/A 613 0 R
+>> 
+endobj
+
+180 0 obj
+<<
+/Title ($1N1796)
+/Parent 121 0 R
+/Prev 179 0 R
+/Next 181 0 R
+/A 615 0 R
+>> 
+endobj
+
+181 0 obj
+<<
+/Title ($1N1798)
+/Parent 121 0 R
+/Prev 180 0 R
+/Next 182 0 R
+/A 617 0 R
+>> 
+endobj
+
+182 0 obj
+<<
+/Title ($1N1800)
+/Parent 121 0 R
+/Prev 181 0 R
+/Next 183 0 R
+/A 619 0 R
+>> 
+endobj
+
+183 0 obj
+<<
+/Title ($1N1824)
+/Parent 121 0 R
+/Prev 182 0 R
+/Next 184 0 R
+/A 621 0 R
+>> 
+endobj
+
+184 0 obj
+<<
+/Title ($1N1826)
+/Parent 121 0 R
+/Prev 183 0 R
+/Next 185 0 R
+/A 623 0 R
+>> 
+endobj
+
+185 0 obj
+<<
+/Title ($1N1838)
+/Parent 121 0 R
+/Prev 184 0 R
+/Next 186 0 R
+/A 625 0 R
+>> 
+endobj
+
+186 0 obj
+<<
+/Title ($1N1840)
+/Parent 121 0 R
+/Prev 185 0 R
+/Next 187 0 R
+/A 627 0 R
+>> 
+endobj
+
+187 0 obj
+<<
+/Title ($1N1842)
+/Parent 121 0 R
+/Prev 186 0 R
+/Next 188 0 R
+/A 629 0 R
+>> 
+endobj
+
+188 0 obj
+<<
+/Title ($1N1844)
+/Parent 121 0 R
+/Prev 187 0 R
+/Next 189 0 R
+/A 631 0 R
+>> 
+endobj
+
+189 0 obj
+<<
+/Title ($1N1850)
+/Parent 121 0 R
+/Prev 188 0 R
+/Next 190 0 R
+/A 633 0 R
+>> 
+endobj
+
+190 0 obj
+<<
+/Title ($1N1858)
+/Parent 121 0 R
+/Prev 189 0 R
+/Next 191 0 R
+/A 635 0 R
+>> 
+endobj
+
+191 0 obj
+<<
+/Title ($1N1866)
+/Parent 121 0 R
+/Prev 190 0 R
+/Next 192 0 R
+/A 637 0 R
+>> 
+endobj
+
+192 0 obj
+<<
+/Title ($1N1884)
+/Parent 121 0 R
+/Prev 191 0 R
+/Next 193 0 R
+/A 639 0 R
+>> 
+endobj
+
+193 0 obj
+<<
+/Title ($1N1886)
+/Parent 121 0 R
+/Prev 192 0 R
+/Next 194 0 R
+/A 641 0 R
+>> 
+endobj
+
+194 0 obj
+<<
+/Title ($1N5390)
+/Parent 121 0 R
+/Prev 193 0 R
+/Next 195 0 R
+/A 643 0 R
+>> 
+endobj
+
+195 0 obj
+<<
+/Title ($1N5442)
+/Parent 121 0 R
+/Prev 194 0 R
+/A 645 0 R
+>> 
+endobj
+
+197 0 obj
+<<
+/Title ($1N19)
+/Parent 196 0 R
+/Next 198 0 R
+/A 647 0 R
+>> 
+endobj
+
+198 0 obj
+<<
+/Title ($1N40)
+/Parent 196 0 R
+/Prev 197 0 R
+/A 649 0 R
+>> 
+endobj
+
+200 0 obj
+<<
+/Title ($1N21)
+/Parent 199 0 R
+/Next 201 0 R
+/A 651 0 R
+>> 
+endobj
+
+201 0 obj
+<<
+/Title ($1N38)
+/Parent 199 0 R
+/Prev 200 0 R
+/A 653 0 R
+>> 
+endobj
+
+203 0 obj
+<<
+/Title ($1N20)
+/Parent 202 0 R
+/Next 204 0 R
+/A 655 0 R
+>> 
+endobj
+
+204 0 obj
+<<
+/Title ($1N39)
+/Parent 202 0 R
+/Prev 203 0 R
+/A 657 0 R
+>> 
+endobj
+
+206 0 obj
+<<
+/Title ($1N11)
+/Parent 205 0 R
+/Next 207 0 R
+/A 659 0 R
+>> 
+endobj
+
+207 0 obj
+<<
+/Title ($1N33)
+/Parent 205 0 R
+/Prev 206 0 R
+/Next 208 0 R
+/A 661 0 R
+>> 
+endobj
+
+208 0 obj
+<<
+/Title ($1N48)
+/Parent 205 0 R
+/Prev 207 0 R
+/Next 209 0 R
+/A 663 0 R
+>> 
+endobj
+
+209 0 obj
+<<
+/Title ($1N1538)
+/Parent 205 0 R
+/Prev 208 0 R
+/Next 210 0 R
+/A 665 0 R
+>> 
+endobj
+
+210 0 obj
+<<
+/Title ($1N1556)
+/Parent 205 0 R
+/Prev 209 0 R
+/Next 211 0 R
+/A 667 0 R
+>> 
+endobj
+
+211 0 obj
+<<
+/Title ($1N1610)
+/Parent 205 0 R
+/Prev 210 0 R
+/Next 212 0 R
+/A 669 0 R
+>> 
+endobj
+
+212 0 obj
+<<
+/Title ($1N1622)
+/Parent 205 0 R
+/Prev 211 0 R
+/Next 213 0 R
+/A 671 0 R
+>> 
+endobj
+
+213 0 obj
+<<
+/Title ($1N1674)
+/Parent 205 0 R
+/Prev 212 0 R
+/Next 214 0 R
+/A 673 0 R
+>> 
+endobj
+
+214 0 obj
+<<
+/Title ($1N1682)
+/Parent 205 0 R
+/Prev 213 0 R
+/Next 215 0 R
+/A 675 0 R
+>> 
+endobj
+
+215 0 obj
+<<
+/Title ($1N1706)
+/Parent 205 0 R
+/Prev 214 0 R
+/Next 216 0 R
+/A 677 0 R
+>> 
+endobj
+
+216 0 obj
+<<
+/Title ($1N1738)
+/Parent 205 0 R
+/Prev 215 0 R
+/Next 217 0 R
+/A 679 0 R
+>> 
+endobj
+
+217 0 obj
+<<
+/Title ($1N1748)
+/Parent 205 0 R
+/Prev 216 0 R
+/Next 218 0 R
+/A 681 0 R
+>> 
+endobj
+
+218 0 obj
+<<
+/Title ($1N1772)
+/Parent 205 0 R
+/Prev 217 0 R
+/Next 219 0 R
+/A 683 0 R
+>> 
+endobj
+
+219 0 obj
+<<
+/Title ($1N1816)
+/Parent 205 0 R
+/Prev 218 0 R
+/Next 220 0 R
+/A 685 0 R
+>> 
+endobj
+
+220 0 obj
+<<
+/Title ($1N1868)
+/Parent 205 0 R
+/Prev 219 0 R
+/A 687 0 R
+>> 
+endobj
+
+222 0 obj
+<<
+/Title ($1N1818)
+/Parent 221 0 R
+/A 689 0 R
+>> 
+endobj
+
+224 0 obj
+<<
+/Title ($1N7)
+/Parent 223 0 R
+/Next 225 0 R
+/A 691 0 R
+>> 
+endobj
+
+225 0 obj
+<<
+/Title ($1N52)
+/Parent 223 0 R
+/Prev 224 0 R
+/Next 226 0 R
+/A 693 0 R
+>> 
+endobj
+
+226 0 obj
+<<
+/Title ($1N1548)
+/Parent 223 0 R
+/Prev 225 0 R
+/Next 227 0 R
+/A 695 0 R
+>> 
+endobj
+
+227 0 obj
+<<
+/Title ($1N1560)
+/Parent 223 0 R
+/Prev 226 0 R
+/Next 228 0 R
+/A 697 0 R
+>> 
+endobj
+
+228 0 obj
+<<
+/Title ($1N1608)
+/Parent 223 0 R
+/Prev 227 0 R
+/Next 229 0 R
+/A 699 0 R
+>> 
+endobj
+
+229 0 obj
+<<
+/Title ($1N1630)
+/Parent 223 0 R
+/Prev 228 0 R
+/Next 230 0 R
+/A 701 0 R
+>> 
+endobj
+
+230 0 obj
+<<
+/Title ($1N1666)
+/Parent 223 0 R
+/Prev 229 0 R
+/Next 231 0 R
+/A 703 0 R
+>> 
+endobj
+
+231 0 obj
+<<
+/Title ($1N1690)
+/Parent 223 0 R
+/Prev 230 0 R
+/Next 232 0 R
+/A 705 0 R
+>> 
+endobj
+
+232 0 obj
+<<
+/Title ($1N1714)
+/Parent 223 0 R
+/Prev 231 0 R
+/Next 233 0 R
+/A 707 0 R
+>> 
+endobj
+
+233 0 obj
+<<
+/Title ($1N1720)
+/Parent 223 0 R
+/Prev 232 0 R
+/Next 234 0 R
+/A 709 0 R
+>> 
+endobj
+
+234 0 obj
+<<
+/Title ($1N1756)
+/Parent 223 0 R
+/Prev 233 0 R
+/Next 235 0 R
+/A 711 0 R
+>> 
+endobj
+
+235 0 obj
+<<
+/Title ($1N1782)
+/Parent 223 0 R
+/Prev 234 0 R
+/Next 236 0 R
+/A 713 0 R
+>> 
+endobj
+
+236 0 obj
+<<
+/Title ($1N1804)
+/Parent 223 0 R
+/Prev 235 0 R
+/Next 237 0 R
+/A 715 0 R
+>> 
+endobj
+
+237 0 obj
+<<
+/Title ($1N1834)
+/Parent 223 0 R
+/Prev 236 0 R
+/Next 238 0 R
+/A 717 0 R
+>> 
+endobj
+
+238 0 obj
+<<
+/Title ($1N1880)
+/Parent 223 0 R
+/Prev 237 0 R
+/A 719 0 R
+>> 
+endobj
+
+240 0 obj
+<<
+/Title ($1N8)
+/Parent 239 0 R
+/Next 241 0 R
+/A 721 0 R
+>> 
+endobj
+
+241 0 obj
+<<
+/Title ($1N51)
+/Parent 239 0 R
+/Prev 240 0 R
+/Next 242 0 R
+/A 723 0 R
+>> 
+endobj
+
+242 0 obj
+<<
+/Title ($1N1546)
+/Parent 239 0 R
+/Prev 241 0 R
+/Next 243 0 R
+/A 725 0 R
+>> 
+endobj
+
+243 0 obj
+<<
+/Title ($1N1562)
+/Parent 239 0 R
+/Prev 242 0 R
+/Next 244 0 R
+/A 727 0 R
+>> 
+endobj
+
+244 0 obj
+<<
+/Title ($1N1606)
+/Parent 239 0 R
+/Prev 243 0 R
+/Next 245 0 R
+/A 729 0 R
+>> 
+endobj
+
+245 0 obj
+<<
+/Title ($1N1628)
+/Parent 239 0 R
+/Prev 244 0 R
+/Next 246 0 R
+/A 731 0 R
+>> 
+endobj
+
+246 0 obj
+<<
+/Title ($1N1668)
+/Parent 239 0 R
+/Prev 245 0 R
+/Next 247 0 R
+/A 733 0 R
+>> 
+endobj
+
+247 0 obj
+<<
+/Title ($1N1688)
+/Parent 239 0 R
+/Prev 246 0 R
+/Next 248 0 R
+/A 735 0 R
+>> 
+endobj
+
+248 0 obj
+<<
+/Title ($1N1716)
+/Parent 239 0 R
+/Prev 247 0 R
+/Next 249 0 R
+/A 737 0 R
+>> 
+endobj
+
+249 0 obj
+<<
+/Title ($1N1722)
+/Parent 239 0 R
+/Prev 248 0 R
+/Next 250 0 R
+/A 739 0 R
+>> 
+endobj
+
+250 0 obj
+<<
+/Title ($1N1754)
+/Parent 239 0 R
+/Prev 249 0 R
+/Next 251 0 R
+/A 741 0 R
+>> 
+endobj
+
+251 0 obj
+<<
+/Title ($1N1784)
+/Parent 239 0 R
+/Prev 250 0 R
+/Next 252 0 R
+/A 743 0 R
+>> 
+endobj
+
+252 0 obj
+<<
+/Title ($1N1802)
+/Parent 239 0 R
+/Prev 251 0 R
+/Next 253 0 R
+/A 745 0 R
+>> 
+endobj
+
+253 0 obj
+<<
+/Title ($1N1832)
+/Parent 239 0 R
+/Prev 252 0 R
+/Next 254 0 R
+/A 747 0 R
+>> 
+endobj
+
+254 0 obj
+<<
+/Title ($1N1882)
+/Parent 239 0 R
+/Prev 253 0 R
+/Next 255 0 R
+/A 749 0 R
+>> 
+endobj
+
+255 0 obj
+<<
+/Title ($1N5374)
+/Parent 239 0 R
+/Prev 254 0 R
+/Next 256 0 R
+/A 751 0 R
+>> 
+endobj
+
+256 0 obj
+<<
+/Title ($1N5481)
+/Parent 239 0 R
+/Prev 255 0 R
+/A 753 0 R
+>> 
+endobj
+
+258 0 obj
+<<
+/Title ($1N13)
+/Parent 257 0 R
+/Next 259 0 R
+/A 755 0 R
+>> 
+endobj
+
+259 0 obj
+<<
+/Title ($1N34)
+/Parent 257 0 R
+/Prev 258 0 R
+/Next 260 0 R
+/A 757 0 R
+>> 
+endobj
+
+260 0 obj
+<<
+/Title ($1N46)
+/Parent 257 0 R
+/Prev 259 0 R
+/Next 261 0 R
+/A 759 0 R
+>> 
+endobj
+
+261 0 obj
+<<
+/Title ($1N1536)
+/Parent 257 0 R
+/Prev 260 0 R
+/Next 262 0 R
+/A 761 0 R
+>> 
+endobj
+
+262 0 obj
+<<
+/Title ($1N1558)
+/Parent 257 0 R
+/Prev 261 0 R
+/Next 263 0 R
+/A 763 0 R
+>> 
+endobj
+
+263 0 obj
+<<
+/Title ($1N1612)
+/Parent 257 0 R
+/Prev 262 0 R
+/Next 264 0 R
+/A 765 0 R
+>> 
+endobj
+
+264 0 obj
+<<
+/Title ($1N1620)
+/Parent 257 0 R
+/Prev 263 0 R
+/Next 265 0 R
+/A 767 0 R
+>> 
+endobj
+
+265 0 obj
+<<
+/Title ($1N1676)
+/Parent 257 0 R
+/Prev 264 0 R
+/Next 266 0 R
+/A 769 0 R
+>> 
+endobj
+
+266 0 obj
+<<
+/Title ($1N1680)
+/Parent 257 0 R
+/Prev 265 0 R
+/Next 267 0 R
+/A 771 0 R
+>> 
+endobj
+
+267 0 obj
+<<
+/Title ($1N1704)
+/Parent 257 0 R
+/Prev 266 0 R
+/Next 268 0 R
+/A 773 0 R
+>> 
+endobj
+
+268 0 obj
+<<
+/Title ($1N1726)
+/Parent 257 0 R
+/Prev 267 0 R
+/Next 269 0 R
+/A 775 0 R
+>> 
+endobj
+
+269 0 obj
+<<
+/Title ($1N1746)
+/Parent 257 0 R
+/Prev 268 0 R
+/Next 270 0 R
+/A 777 0 R
+>> 
+endobj
+
+270 0 obj
+<<
+/Title ($1N1780)
+/Parent 257 0 R
+/Prev 269 0 R
+/Next 271 0 R
+/A 779 0 R
+>> 
+endobj
+
+271 0 obj
+<<
+/Title ($1N1812)
+/Parent 257 0 R
+/Prev 270 0 R
+/Next 272 0 R
+/A 781 0 R
+>> 
+endobj
+
+272 0 obj
+<<
+/Title ($1N1872)
+/Parent 257 0 R
+/Prev 271 0 R
+/A 783 0 R
+>> 
+endobj
+
+274 0 obj
+<<
+/Title ($1N14)
+/Parent 273 0 R
+/Next 275 0 R
+/A 785 0 R
+>> 
+endobj
+
+275 0 obj
+<<
+/Title ($1N45)
+/Parent 273 0 R
+/Prev 274 0 R
+/Next 276 0 R
+/A 787 0 R
+>> 
+endobj
+
+276 0 obj
+<<
+/Title ($1N1520)
+/Parent 273 0 R
+/Prev 275 0 R
+/A 789 0 R
+>> 
+endobj
+
+278 0 obj
+<<
+/Title ($1N4)
+/Parent 277 0 R
+/Next 279 0 R
+/A 791 0 R
+>> 
+endobj
+
+279 0 obj
+<<
+/Title ($1N56)
+/Parent 277 0 R
+/Prev 278 0 R
+/Next 280 0 R
+/A 793 0 R
+>> 
+endobj
+
+280 0 obj
+<<
+/Title ($1N57)
+/Parent 277 0 R
+/Prev 279 0 R
+/Next 281 0 R
+/A 795 0 R
+>> 
+endobj
+
+281 0 obj
+<<
+/Title ($1N1634)
+/Parent 277 0 R
+/Prev 280 0 R
+/Next 282 0 R
+/A 797 0 R
+>> 
+endobj
+
+282 0 obj
+<<
+/Title ($1N1662)
+/Parent 277 0 R
+/Prev 281 0 R
+/Next 283 0 R
+/A 799 0 R
+>> 
+endobj
+
+283 0 obj
+<<
+/Title ($1N1694)
+/Parent 277 0 R
+/Prev 282 0 R
+/Next 284 0 R
+/A 801 0 R
+>> 
+endobj
+
+284 0 obj
+<<
+/Title ($1N1794)
+/Parent 277 0 R
+/Prev 283 0 R
+/A 803 0 R
+>> 
+endobj
+
+286 0 obj
+<<
+/Title ($1N9)
+/Parent 285 0 R
+/Next 287 0 R
+/A 805 0 R
+>> 
+endobj
+
+287 0 obj
+<<
+/Title ($1N50)
+/Parent 285 0 R
+/Prev 286 0 R
+/Next 288 0 R
+/A 807 0 R
+>> 
+endobj
+
+288 0 obj
+<<
+/Title ($1N1544)
+/Parent 285 0 R
+/Prev 287 0 R
+/Next 289 0 R
+/A 809 0 R
+>> 
+endobj
+
+289 0 obj
+<<
+/Title ($1N1564)
+/Parent 285 0 R
+/Prev 288 0 R
+/Next 290 0 R
+/A 811 0 R
+>> 
+endobj
+
+290 0 obj
+<<
+/Title ($1N1604)
+/Parent 285 0 R
+/Prev 289 0 R
+/Next 291 0 R
+/A 813 0 R
+>> 
+endobj
+
+291 0 obj
+<<
+/Title ($1N1632)
+/Parent 285 0 R
+/Prev 290 0 R
+/Next 292 0 R
+/A 815 0 R
+>> 
+endobj
+
+292 0 obj
+<<
+/Title ($1N1664)
+/Parent 285 0 R
+/Prev 291 0 R
+/Next 293 0 R
+/A 817 0 R
+>> 
+endobj
+
+293 0 obj
+<<
+/Title ($1N1692)
+/Parent 285 0 R
+/Prev 292 0 R
+/Next 294 0 R
+/A 819 0 R
+>> 
+endobj
+
+294 0 obj
+<<
+/Title ($1N1712)
+/Parent 285 0 R
+/Prev 293 0 R
+/Next 295 0 R
+/A 821 0 R
+>> 
+endobj
+
+295 0 obj
+<<
+/Title ($1N1724)
+/Parent 285 0 R
+/Prev 294 0 R
+/Next 296 0 R
+/A 823 0 R
+>> 
+endobj
+
+296 0 obj
+<<
+/Title ($1N1758)
+/Parent 285 0 R
+/Prev 295 0 R
+/Next 297 0 R
+/A 825 0 R
+>> 
+endobj
+
+297 0 obj
+<<
+/Title ($1N1786)
+/Parent 285 0 R
+/Prev 296 0 R
+/Next 298 0 R
+/A 827 0 R
+>> 
+endobj
+
+298 0 obj
+<<
+/Title ($1N1806)
+/Parent 285 0 R
+/Prev 297 0 R
+/Next 299 0 R
+/A 829 0 R
+>> 
+endobj
+
+299 0 obj
+<<
+/Title ($1N1830)
+/Parent 285 0 R
+/Prev 298 0 R
+/Next 300 0 R
+/A 831 0 R
+>> 
+endobj
+
+300 0 obj
+<<
+/Title ($1N1878)
+/Parent 285 0 R
+/Prev 299 0 R
+/Next 301 0 R
+/A 833 0 R
+>> 
+endobj
+
+301 0 obj
+<<
+/Title ($1N5358)
+/Parent 285 0 R
+/Prev 300 0 R
+/Next 302 0 R
+/A 835 0 R
+>> 
+endobj
+
+302 0 obj
+<<
+/Title ($1N5478)
+/Parent 285 0 R
+/Prev 301 0 R
+/A 837 0 R
+>> 
+endobj
+
+304 0 obj
+<<
+/Title ($1N17)
+/Parent 303 0 R
+/Next 305 0 R
+/A 839 0 R
+>> 
+endobj
+
+305 0 obj
+<<
+/Title ($1N42)
+/Parent 303 0 R
+/Prev 304 0 R
+/A 841 0 R
+>> 
+endobj
+
+307 0 obj
+<<
+/Title ($1N16)
+/Parent 306 0 R
+/Next 308 0 R
+/A 843 0 R
+>> 
+endobj
+
+308 0 obj
+<<
+/Title ($1N43)
+/Parent 306 0 R
+/Prev 307 0 R
+/A 845 0 R
+>> 
+endobj
+
+310 0 obj
+<<
+/Title ($1N1498)
+/Parent 309 0 R
+/Next 311 0 R
+/A 847 0 R
+>> 
+endobj
+
+311 0 obj
+<<
+/Title ($1N1512)
+/Parent 309 0 R
+/Prev 310 0 R
+/A 849 0 R
+>> 
+endobj
+
+313 0 obj
+<<
+/Title ($1N1496)
+/Parent 312 0 R
+/Next 314 0 R
+/A 851 0 R
+>> 
+endobj
+
+314 0 obj
+<<
+/Title ($1N1510)
+/Parent 312 0 R
+/Prev 313 0 R
+/A 853 0 R
+>> 
+endobj
+
+316 0 obj
+<<
+/Title ($1N18)
+/Parent 315 0 R
+/Next 317 0 R
+/A 855 0 R
+>> 
+endobj
+
+317 0 obj
+<<
+/Title ($1N41)
+/Parent 315 0 R
+/Prev 316 0 R
+/Next 318 0 R
+/A 857 0 R
+>> 
+endobj
+
+318 0 obj
+<<
+/Title ($1N1522)
+/Parent 315 0 R
+/Prev 317 0 R
+/A 859 0 R
+>> 
+endobj
+
+861 0 obj
+<<
+/Producer (jsPDF 0.0.0)
+/CreationDate (D:20251108000128-00'00')
+>>
+endobj
+862 0 obj
+<<
+/Type /Catalog
+/Pages 1 0 R
+/OpenAction [3 0 R /FitH null]
+/PageLayout /OneColumn
+/Outlines 12 0 R
+>>
+endobj
+xref
+0 863
+0000000000 65535 f 
+0000334529 00000 n 
+0000396345 00000 n 
+0000000015 00000 n 
+0000000125 00000 n 
+0000334586 00000 n 
+0000334751 00000 n 
+0000334930 00000 n 
+0000335046 00000 n 
+0000335215 00000 n 
+0000336259 00000 n 
+0000393129 00000 n 
+0000527015 00000 n 
+0000527093 00000 n 
+0000527300 00000 n 
+0000527196 00000 n 
+0000527411 00000 n 
+0000531215 00000 n 
+0000531292 00000 n 
+0000531383 00000 n 
+0000531474 00000 n 
+0000531565 00000 n 
+0000531658 00000 n 
+0000531751 00000 n 
+0000531844 00000 n 
+0000531937 00000 n 
+0000532030 00000 n 
+0000532123 00000 n 
+0000532216 00000 n 
+0000532309 00000 n 
+0000532402 00000 n 
+0000532495 00000 n 
+0000532588 00000 n 
+0000532681 00000 n 
+0000532774 00000 n 
+0000527513 00000 n 
+0000532854 00000 n 
+0000532932 00000 n 
+0000533023 00000 n 
+0000527627 00000 n 
+0000533103 00000 n 
+0000527742 00000 n 
+0000533167 00000 n 
+0000527858 00000 n 
+0000533232 00000 n 
+0000527974 00000 n 
+0000533297 00000 n 
+0000533375 00000 n 
+0000533466 00000 n 
+0000533559 00000 n 
+0000528088 00000 n 
+0000533639 00000 n 
+0000533719 00000 n 
+0000533812 00000 n 
+0000533905 00000 n 
+0000533998 00000 n 
+0000534091 00000 n 
+0000528203 00000 n 
+0000534171 00000 n 
+0000534249 00000 n 
+0000534340 00000 n 
+0000534433 00000 n 
+0000534526 00000 n 
+0000534619 00000 n 
+0000534712 00000 n 
+0000534805 00000 n 
+0000534898 00000 n 
+0000534991 00000 n 
+0000535084 00000 n 
+0000535177 00000 n 
+0000535270 00000 n 
+0000535363 00000 n 
+0000535456 00000 n 
+0000528319 00000 n 
+0000535536 00000 n 
+0000535614 00000 n 
+0000535705 00000 n 
+0000535798 00000 n 
+0000535891 00000 n 
+0000535984 00000 n 
+0000536077 00000 n 
+0000536170 00000 n 
+0000536263 00000 n 
+0000536356 00000 n 
+0000536449 00000 n 
+0000536542 00000 n 
+0000536635 00000 n 
+0000536728 00000 n 
+0000536821 00000 n 
+0000528433 00000 n 
+0000536901 00000 n 
+0000536981 00000 n 
+0000537074 00000 n 
+0000537167 00000 n 
+0000537260 00000 n 
+0000537353 00000 n 
+0000537446 00000 n 
+0000537539 00000 n 
+0000537632 00000 n 
+0000537725 00000 n 
+0000537819 00000 n 
+0000537914 00000 n 
+0000528551 00000 n 
+0000537996 00000 n 
+0000528670 00000 n 
+0000538065 00000 n 
+0000538148 00000 n 
+0000538245 00000 n 
+0000538342 00000 n 
+0000528796 00000 n 
+0000538425 00000 n 
+0000538508 00000 n 
+0000538605 00000 n 
+0000528920 00000 n 
+0000538688 00000 n 
+0000538771 00000 n 
+0000538868 00000 n 
+0000529045 00000 n 
+0000538951 00000 n 
+0000539034 00000 n 
+0000539131 00000 n 
+0000529171 00000 n 
+0000539214 00000 n 
+0000539294 00000 n 
+0000539388 00000 n 
+0000539483 00000 n 
+0000539578 00000 n 
+0000539673 00000 n 
+0000539768 00000 n 
+0000539863 00000 n 
+0000539958 00000 n 
+0000540053 00000 n 
+0000540148 00000 n 
+0000540243 00000 n 
+0000540338 00000 n 
+0000540433 00000 n 
+0000540528 00000 n 
+0000540623 00000 n 
+0000540720 00000 n 
+0000540817 00000 n 
+0000540914 00000 n 
+0000541011 00000 n 
+0000541108 00000 n 
+0000541205 00000 n 
+0000541302 00000 n 
+0000541399 00000 n 
+0000541496 00000 n 
+0000541593 00000 n 
+0000541690 00000 n 
+0000541787 00000 n 
+0000541884 00000 n 
+0000541981 00000 n 
+0000542078 00000 n 
+0000542175 00000 n 
+0000542272 00000 n 
+0000542369 00000 n 
+0000542466 00000 n 
+0000542563 00000 n 
+0000542660 00000 n 
+0000542757 00000 n 
+0000542854 00000 n 
+0000542951 00000 n 
+0000543048 00000 n 
+0000543145 00000 n 
+0000543242 00000 n 
+0000543339 00000 n 
+0000543436 00000 n 
+0000543533 00000 n 
+0000543630 00000 n 
+0000543727 00000 n 
+0000543824 00000 n 
+0000543921 00000 n 
+0000544018 00000 n 
+0000544115 00000 n 
+0000544212 00000 n 
+0000544309 00000 n 
+0000544406 00000 n 
+0000544503 00000 n 
+0000544600 00000 n 
+0000544697 00000 n 
+0000544794 00000 n 
+0000544891 00000 n 
+0000544988 00000 n 
+0000545085 00000 n 
+0000545182 00000 n 
+0000545279 00000 n 
+0000545376 00000 n 
+0000545473 00000 n 
+0000545570 00000 n 
+0000545667 00000 n 
+0000545764 00000 n 
+0000545861 00000 n 
+0000545958 00000 n 
+0000546055 00000 n 
+0000546152 00000 n 
+0000546249 00000 n 
+0000529291 00000 n 
+0000546332 00000 n 
+0000546413 00000 n 
+0000529412 00000 n 
+0000546494 00000 n 
+0000546575 00000 n 
+0000529533 00000 n 
+0000546656 00000 n 
+0000546737 00000 n 
+0000529654 00000 n 
+0000546818 00000 n 
+0000546899 00000 n 
+0000546994 00000 n 
+0000547089 00000 n 
+0000547186 00000 n 
+0000547283 00000 n 
+0000547380 00000 n 
+0000547477 00000 n 
+0000547574 00000 n 
+0000547671 00000 n 
+0000547768 00000 n 
+0000547865 00000 n 
+0000547962 00000 n 
+0000548059 00000 n 
+0000548156 00000 n 
+0000529774 00000 n 
+0000548239 00000 n 
+0000529898 00000 n 
+0000548308 00000 n 
+0000548388 00000 n 
+0000548483 00000 n 
+0000548580 00000 n 
+0000548677 00000 n 
+0000548774 00000 n 
+0000548871 00000 n 
+0000548968 00000 n 
+0000549065 00000 n 
+0000549162 00000 n 
+0000549259 00000 n 
+0000549356 00000 n 
+0000549453 00000 n 
+0000549550 00000 n 
+0000549647 00000 n 
+0000530019 00000 n 
+0000549730 00000 n 
+0000549810 00000 n 
+0000549905 00000 n 
+0000550002 00000 n 
+0000550099 00000 n 
+0000550196 00000 n 
+0000550293 00000 n 
+0000550390 00000 n 
+0000550487 00000 n 
+0000550584 00000 n 
+0000550681 00000 n 
+0000550778 00000 n 
+0000550875 00000 n 
+0000550972 00000 n 
+0000551069 00000 n 
+0000551166 00000 n 
+0000551263 00000 n 
+0000530140 00000 n 
+0000551346 00000 n 
+0000551427 00000 n 
+0000551522 00000 n 
+0000551617 00000 n 
+0000551714 00000 n 
+0000551811 00000 n 
+0000551908 00000 n 
+0000552005 00000 n 
+0000552102 00000 n 
+0000552199 00000 n 
+0000552296 00000 n 
+0000552393 00000 n 
+0000552490 00000 n 
+0000552587 00000 n 
+0000552684 00000 n 
+0000530261 00000 n 
+0000552767 00000 n 
+0000552848 00000 n 
+0000552943 00000 n 
+0000530381 00000 n 
+0000553026 00000 n 
+0000553106 00000 n 
+0000553201 00000 n 
+0000553296 00000 n 
+0000553393 00000 n 
+0000553490 00000 n 
+0000553587 00000 n 
+0000530501 00000 n 
+0000553670 00000 n 
+0000553750 00000 n 
+0000553845 00000 n 
+0000553942 00000 n 
+0000554039 00000 n 
+0000554136 00000 n 
+0000554233 00000 n 
+0000554330 00000 n 
+0000554427 00000 n 
+0000554524 00000 n 
+0000554621 00000 n 
+0000554718 00000 n 
+0000554815 00000 n 
+0000554912 00000 n 
+0000555009 00000 n 
+0000555106 00000 n 
+0000555203 00000 n 
+0000530621 00000 n 
+0000555286 00000 n 
+0000555367 00000 n 
+0000530740 00000 n 
+0000555448 00000 n 
+0000555529 00000 n 
+0000530859 00000 n 
+0000555610 00000 n 
+0000555693 00000 n 
+0000530984 00000 n 
+0000555776 00000 n 
+0000555859 00000 n 
+0000531109 00000 n 
+0000555942 00000 n 
+0000556023 00000 n 
+0000556118 00000 n 
+0000396470 00000 n 
+0000396534 00000 n 
+0000396980 00000 n 
+0000397044 00000 n 
+0000397438 00000 n 
+0000397502 00000 n 
+0000397911 00000 n 
+0000397975 00000 n 
+0000398408 00000 n 
+0000398472 00000 n 
+0000398878 00000 n 
+0000398942 00000 n 
+0000399335 00000 n 
+0000399399 00000 n 
+0000399831 00000 n 
+0000399895 00000 n 
+0000400288 00000 n 
+0000400352 00000 n 
+0000400796 00000 n 
+0000400860 00000 n 
+0000401304 00000 n 
+0000401368 00000 n 
+0000401797 00000 n 
+0000401861 00000 n 
+0000402268 00000 n 
+0000402332 00000 n 
+0000402727 00000 n 
+0000402791 00000 n 
+0000403198 00000 n 
+0000403262 00000 n 
+0000403666 00000 n 
+0000403730 00000 n 
+0000404170 00000 n 
+0000404234 00000 n 
+0000404692 00000 n 
+0000404756 00000 n 
+0000405214 00000 n 
+0000405278 00000 n 
+0000405674 00000 n 
+0000405738 00000 n 
+0000406156 00000 n 
+0000406220 00000 n 
+0000406615 00000 n 
+0000406679 00000 n 
+0000407109 00000 n 
+0000407173 00000 n 
+0000407617 00000 n 
+0000407681 00000 n 
+0000408112 00000 n 
+0000408176 00000 n 
+0000408596 00000 n 
+0000408660 00000 n 
+0000409081 00000 n 
+0000409145 00000 n 
+0000409553 00000 n 
+0000409617 00000 n 
+0000410025 00000 n 
+0000410089 00000 n 
+0000410487 00000 n 
+0000410551 00000 n 
+0000410947 00000 n 
+0000411011 00000 n 
+0000411418 00000 n 
+0000411482 00000 n 
+0000411917 00000 n 
+0000411981 00000 n 
+0000412391 00000 n 
+0000412455 00000 n 
+0000412865 00000 n 
+0000412929 00000 n 
+0000413338 00000 n 
+0000413402 00000 n 
+0000413813 00000 n 
+0000413877 00000 n 
+0000414283 00000 n 
+0000414347 00000 n 
+0000414742 00000 n 
+0000414806 00000 n 
+0000415221 00000 n 
+0000415285 00000 n 
+0000415714 00000 n 
+0000415778 00000 n 
+0000416183 00000 n 
+0000416247 00000 n 
+0000416676 00000 n 
+0000416740 00000 n 
+0000417195 00000 n 
+0000417259 00000 n 
+0000417688 00000 n 
+0000417752 00000 n 
+0000418169 00000 n 
+0000418233 00000 n 
+0000418628 00000 n 
+0000418692 00000 n 
+0000419099 00000 n 
+0000419163 00000 n 
+0000419556 00000 n 
+0000419620 00000 n 
+0000420049 00000 n 
+0000420113 00000 n 
+0000420556 00000 n 
+0000420620 00000 n 
+0000421065 00000 n 
+0000421129 00000 n 
+0000421524 00000 n 
+0000421588 00000 n 
+0000422005 00000 n 
+0000422069 00000 n 
+0000422486 00000 n 
+0000422550 00000 n 
+0000422979 00000 n 
+0000423043 00000 n 
+0000423448 00000 n 
+0000423512 00000 n 
+0000423930 00000 n 
+0000423994 00000 n 
+0000424449 00000 n 
+0000424513 00000 n 
+0000424908 00000 n 
+0000424972 00000 n 
+0000425389 00000 n 
+0000425453 00000 n 
+0000425848 00000 n 
+0000425912 00000 n 
+0000426319 00000 n 
+0000426383 00000 n 
+0000426778 00000 n 
+0000426842 00000 n 
+0000427259 00000 n 
+0000427323 00000 n 
+0000427752 00000 n 
+0000427816 00000 n 
+0000428282 00000 n 
+0000428346 00000 n 
+0000428764 00000 n 
+0000428828 00000 n 
+0000429272 00000 n 
+0000429336 00000 n 
+0000429743 00000 n 
+0000429807 00000 n 
+0000430251 00000 n 
+0000430315 00000 n 
+0000430710 00000 n 
+0000430774 00000 n 
+0000431181 00000 n 
+0000431245 00000 n 
+0000431640 00000 n 
+0000431704 00000 n 
+0000432122 00000 n 
+0000432186 00000 n 
+0000432613 00000 n 
+0000432677 00000 n 
+0000433106 00000 n 
+0000433170 00000 n 
+0000433565 00000 n 
+0000433629 00000 n 
+0000434036 00000 n 
+0000434100 00000 n 
+0000434518 00000 n 
+0000434582 00000 n 
+0000434977 00000 n 
+0000435041 00000 n 
+0000435473 00000 n 
+0000435537 00000 n 
+0000435942 00000 n 
+0000436006 00000 n 
+0000436401 00000 n 
+0000436465 00000 n 
+0000436897 00000 n 
+0000436961 00000 n 
+0000437405 00000 n 
+0000437469 00000 n 
+0000437901 00000 n 
+0000437965 00000 n 
+0000438434 00000 n 
+0000438498 00000 n 
+0000438942 00000 n 
+0000439006 00000 n 
+0000439399 00000 n 
+0000439463 00000 n 
+0000439893 00000 n 
+0000439957 00000 n 
+0000440353 00000 n 
+0000440417 00000 n 
+0000440840 00000 n 
+0000440904 00000 n 
+0000441301 00000 n 
+0000441365 00000 n 
+0000441820 00000 n 
+0000441884 00000 n 
+0000442281 00000 n 
+0000442345 00000 n 
+0000442791 00000 n 
+0000442855 00000 n 
+0000443274 00000 n 
+0000443338 00000 n 
+0000443770 00000 n 
+0000443834 00000 n 
+0000444264 00000 n 
+0000444328 00000 n 
+0000444761 00000 n 
+0000444825 00000 n 
+0000445233 00000 n 
+0000445297 00000 n 
+0000445707 00000 n 
+0000445771 00000 n 
+0000446212 00000 n 
+0000446276 00000 n 
+0000446679 00000 n 
+0000446743 00000 n 
+0000447163 00000 n 
+0000447227 00000 n 
+0000447659 00000 n 
+0000447723 00000 n 
+0000448192 00000 n 
+0000448256 00000 n 
+0000448687 00000 n 
+0000448751 00000 n 
+0000449166 00000 n 
+0000449230 00000 n 
+0000449623 00000 n 
+0000449687 00000 n 
+0000450116 00000 n 
+0000450180 00000 n 
+0000450586 00000 n 
+0000450650 00000 n 
+0000451067 00000 n 
+0000451131 00000 n 
+0000451546 00000 n 
+0000451610 00000 n 
+0000452042 00000 n 
+0000452106 00000 n 
+0000452501 00000 n 
+0000452565 00000 n 
+0000453006 00000 n 
+0000453070 00000 n 
+0000453485 00000 n 
+0000453549 00000 n 
+0000453966 00000 n 
+0000454030 00000 n 
+0000454447 00000 n 
+0000454511 00000 n 
+0000454928 00000 n 
+0000454992 00000 n 
+0000455387 00000 n 
+0000455451 00000 n 
+0000455846 00000 n 
+0000455910 00000 n 
+0000456305 00000 n 
+0000456369 00000 n 
+0000456762 00000 n 
+0000456826 00000 n 
+0000457233 00000 n 
+0000457297 00000 n 
+0000457726 00000 n 
+0000457790 00000 n 
+0000458207 00000 n 
+0000458271 00000 n 
+0000458689 00000 n 
+0000458753 00000 n 
+0000459193 00000 n 
+0000459257 00000 n 
+0000459712 00000 n 
+0000459776 00000 n 
+0000460231 00000 n 
+0000460295 00000 n 
+0000460724 00000 n 
+0000460788 00000 n 
+0000461204 00000 n 
+0000461268 00000 n 
+0000461661 00000 n 
+0000461725 00000 n 
+0000462130 00000 n 
+0000462194 00000 n 
+0000462626 00000 n 
+0000462690 00000 n 
+0000463097 00000 n 
+0000463161 00000 n 
+0000463568 00000 n 
+0000463632 00000 n 
+0000464024 00000 n 
+0000464088 00000 n 
+0000464505 00000 n 
+0000464569 00000 n 
+0000465009 00000 n 
+0000465073 00000 n 
+0000465480 00000 n 
+0000465544 00000 n 
+0000465973 00000 n 
+0000466037 00000 n 
+0000466432 00000 n 
+0000466496 00000 n 
+0000466891 00000 n 
+0000466955 00000 n 
+0000467350 00000 n 
+0000467414 00000 n 
+0000467809 00000 n 
+0000467873 00000 n 
+0000468268 00000 n 
+0000468332 00000 n 
+0000468739 00000 n 
+0000468803 00000 n 
+0000469198 00000 n 
+0000469262 00000 n 
+0000469657 00000 n 
+0000469721 00000 n 
+0000470116 00000 n 
+0000470180 00000 n 
+0000470575 00000 n 
+0000470639 00000 n 
+0000471032 00000 n 
+0000471096 00000 n 
+0000471489 00000 n 
+0000471553 00000 n 
+0000472022 00000 n 
+0000472086 00000 n 
+0000472530 00000 n 
+0000472594 00000 n 
+0000473038 00000 n 
+0000473102 00000 n 
+0000473553 00000 n 
+0000473617 00000 n 
+0000474036 00000 n 
+0000474100 00000 n 
+0000474507 00000 n 
+0000474571 00000 n 
+0000474964 00000 n 
+0000475028 00000 n 
+0000475458 00000 n 
+0000475522 00000 n 
+0000475957 00000 n 
+0000476021 00000 n 
+0000476452 00000 n 
+0000476516 00000 n 
+0000476928 00000 n 
+0000476992 00000 n 
+0000477400 00000 n 
+0000477464 00000 n 
+0000477887 00000 n 
+0000477951 00000 n 
+0000478370 00000 n 
+0000478434 00000 n 
+0000478843 00000 n 
+0000478907 00000 n 
+0000479340 00000 n 
+0000479404 00000 n 
+0000479815 00000 n 
+0000479879 00000 n 
+0000480272 00000 n 
+0000480336 00000 n 
+0000480731 00000 n 
+0000480795 00000 n 
+0000481190 00000 n 
+0000481254 00000 n 
+0000481671 00000 n 
+0000481735 00000 n 
+0000482128 00000 n 
+0000482192 00000 n 
+0000482599 00000 n 
+0000482663 00000 n 
+0000483070 00000 n 
+0000483134 00000 n 
+0000483541 00000 n 
+0000483605 00000 n 
+0000484045 00000 n 
+0000484109 00000 n 
+0000484504 00000 n 
+0000484568 00000 n 
+0000484975 00000 n 
+0000485039 00000 n 
+0000485490 00000 n 
+0000485554 00000 n 
+0000485961 00000 n 
+0000486025 00000 n 
+0000486445 00000 n 
+0000486509 00000 n 
+0000486931 00000 n 
+0000486995 00000 n 
+0000487388 00000 n 
+0000487452 00000 n 
+0000487906 00000 n 
+0000487970 00000 n 
+0000488387 00000 n 
+0000488451 00000 n 
+0000488880 00000 n 
+0000488944 00000 n 
+0000489349 00000 n 
+0000489413 00000 n 
+0000489820 00000 n 
+0000489884 00000 n 
+0000490328 00000 n 
+0000490392 00000 n 
+0000490799 00000 n 
+0000490863 00000 n 
+0000491280 00000 n 
+0000491344 00000 n 
+0000491776 00000 n 
+0000491840 00000 n 
+0000492247 00000 n 
+0000492311 00000 n 
+0000492706 00000 n 
+0000492770 00000 n 
+0000493199 00000 n 
+0000493263 00000 n 
+0000493695 00000 n 
+0000493759 00000 n 
+0000494193 00000 n 
+0000494257 00000 n 
+0000494686 00000 n 
+0000494750 00000 n 
+0000495165 00000 n 
+0000495229 00000 n 
+0000495683 00000 n 
+0000495747 00000 n 
+0000496187 00000 n 
+0000496251 00000 n 
+0000496667 00000 n 
+0000496731 00000 n 
+0000497126 00000 n 
+0000497190 00000 n 
+0000497622 00000 n 
+0000497686 00000 n 
+0000498115 00000 n 
+0000498179 00000 n 
+0000498608 00000 n 
+0000498672 00000 n 
+0000499067 00000 n 
+0000499131 00000 n 
+0000499526 00000 n 
+0000499590 00000 n 
+0000499985 00000 n 
+0000500049 00000 n 
+0000500500 00000 n 
+0000500564 00000 n 
+0000500959 00000 n 
+0000501023 00000 n 
+0000501455 00000 n 
+0000501519 00000 n 
+0000501939 00000 n 
+0000502003 00000 n 
+0000502424 00000 n 
+0000502488 00000 n 
+0000502910 00000 n 
+0000502974 00000 n 
+0000503380 00000 n 
+0000503444 00000 n 
+0000503876 00000 n 
+0000503940 00000 n 
+0000504335 00000 n 
+0000504399 00000 n 
+0000504850 00000 n 
+0000504914 00000 n 
+0000505341 00000 n 
+0000505405 00000 n 
+0000505837 00000 n 
+0000505901 00000 n 
+0000506308 00000 n 
+0000506372 00000 n 
+0000506790 00000 n 
+0000506854 00000 n 
+0000507283 00000 n 
+0000507347 00000 n 
+0000507742 00000 n 
+0000507806 00000 n 
+0000508201 00000 n 
+0000508265 00000 n 
+0000508705 00000 n 
+0000508769 00000 n 
+0000509201 00000 n 
+0000509265 00000 n 
+0000509699 00000 n 
+0000509763 00000 n 
+0000510153 00000 n 
+0000510217 00000 n 
+0000510611 00000 n 
+0000510675 00000 n 
+0000511082 00000 n 
+0000511146 00000 n 
+0000511556 00000 n 
+0000511620 00000 n 
+0000512038 00000 n 
+0000512102 00000 n 
+0000512557 00000 n 
+0000512621 00000 n 
+0000513087 00000 n 
+0000513151 00000 n 
+0000513558 00000 n 
+0000513622 00000 n 
+0000514029 00000 n 
+0000514093 00000 n 
+0000514502 00000 n 
+0000514566 00000 n 
+0000514972 00000 n 
+0000515036 00000 n 
+0000515453 00000 n 
+0000515517 00000 n 
+0000515934 00000 n 
+0000515998 00000 n 
+0000516415 00000 n 
+0000516479 00000 n 
+0000516872 00000 n 
+0000516936 00000 n 
+0000517343 00000 n 
+0000517407 00000 n 
+0000517873 00000 n 
+0000517937 00000 n 
+0000518344 00000 n 
+0000518408 00000 n 
+0000518848 00000 n 
+0000518912 00000 n 
+0000519305 00000 n 
+0000519369 00000 n 
+0000519787 00000 n 
+0000519851 00000 n 
+0000520283 00000 n 
+0000520347 00000 n 
+0000520776 00000 n 
+0000520840 00000 n 
+0000521235 00000 n 
+0000521299 00000 n 
+0000521731 00000 n 
+0000521795 00000 n 
+0000522205 00000 n 
+0000522269 00000 n 
+0000522675 00000 n 
+0000522739 00000 n 
+0000523151 00000 n 
+0000523215 00000 n 
+0000523623 00000 n 
+0000523687 00000 n 
+0000524083 00000 n 
+0000524147 00000 n 
+0000524554 00000 n 
+0000524618 00000 n 
+0000525025 00000 n 
+0000525089 00000 n 
+0000525507 00000 n 
+0000525571 00000 n 
+0000526017 00000 n 
+0000526081 00000 n 
+0000526523 00000 n 
+0000526587 00000 n 
+0000556201 00000 n 
+0000556288 00000 n 
+trailer
+<<
+/Size 863
+/Root 862 0 R
+/Info 861 0 R
+/ID [   ]
+>>
+startxref
+556410
+%%EOF
diff --git a/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/readme.md b/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/readme.md
index 5a78103ee..de76286b2 100644
--- a/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/readme.md
+++ b/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/readme.md
@@ -4,7 +4,9 @@
 
 ## General
 
-The pinout is contained in the variant.h file, and a [generic schematic](./Schematic_Pro-Micro_Pinouts%202024-12-14.pdf) is located in this directory.
+The pinout is contained in the variant.h file, and a [generic schematic](./Schematic_Pro-Micro_Pinouts.pdf) is located in this directory.
+
+This variant is suitable for both TCXO and XTAL types of modules. The old XTAL variant has been removed to reduce confusion.
 
 ### Note on DIO2, RXEN, TXEN, and RF switching
 
@@ -17,9 +19,13 @@ Several modules require external switching between transmit (Tx) and receive (Rx
 RXEN is not required to be connected if the selected module already has internal RF switching, or if external RF switching logic is already applied.
 Also worth noting that the Seeed WIO SX1262 in particular only has RXEN exposed (marked RF_SW) and has the DIO2-TXEN link internally.
 
+## Making a node based on this variant
+
+Making your own node based on this design is straightforward. There are various open source and free to use PCB design files available, or you can solder wires directly from a module to the pro-micro.
+
 
- The table of known modules is at the bottom of the variant.h, and reproduced here for convenience. + < Click to expand > The table of known modules is at the bottom of the variant.h, and reproduced here for convenience. | Mfr | Module | TCXO | RF Switch | Notes | | ------------ | ---------------- | ---- | --------- | ------------------------------------- | @@ -34,6 +40,7 @@ Also worth noting that the Seeed WIO SX1262 in particular only has RXEN exposed | Waveshare | Core1262-HF | yes | Ext | | | Waveshare | LoRa Node Module | yes | Int | | | Seeed | Wio-SX1262 | yes | Ext | Cute! DIO2/TXEN are not exposed | +| Seeed | Wio-LR1121 | yes | Int | LR1121, needs alternate rfswitch.h | | AI-Thinker | RA-02 | No | Int | SX1278 **433mhz band only** | | RF Solutions | RFM95 | No | Int | Untested | | Ebyte | E80-900M2213S | Yes | Int | LR1121 radio | @@ -72,6 +79,10 @@ The Semtech default, the values are (taken from [here](https://github.com/Lora-n
+ < Click to expand > + + + ```cpp .rfswitch = { .enable = LR11XX_SYSTEM_RFSW0_HIGH | LR11XX_SYSTEM_RFSW1_HIGH | LR11XX_SYSTEM_RFSW2_HIGH, diff --git a/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/variant.h b/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/variant.h index 87342a02f..fee8ee88e 100644 --- a/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/variant.h +++ b/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/variant.h @@ -175,6 +175,7 @@ settings. | Waveshare | Core1262-HF | yes | Ext | | | Waveshare | LoRa Node Module | yes | Int | | | Seeed | Wio-SX1262 | yes | Ext | Cute! DIO2/TXEN are not exposed | +| Seeed | Wio-LR1121 | yes | Int | LR1121, needs alternate rfswitch.h | | AI-Thinker | RA-02 | No | Int | SX1278 **433mhz band only** | | RF Solutions | RFM95 | No | Int | Untested | | Ebyte | E80-900M2213S | Yes | Int | LR1121 radio | diff --git a/variants/nrf52840/diy/nrf52_promicro_diy_xtal/platformio.ini b/variants/nrf52840/diy/nrf52_promicro_diy_xtal/platformio.ini deleted file mode 100644 index 278f578c5..000000000 --- a/variants/nrf52840/diy/nrf52_promicro_diy_xtal/platformio.ini +++ /dev/null @@ -1,12 +0,0 @@ -; Promicro + E22(0)-xxxMM / RA-01SH modules board variant - DIY - without TCXO -[env:nrf52_promicro_diy_xtal] -extends = nrf52840_base -board = promicro-nrf52840 -board_level = extra -build_flags = ${nrf52840_base.build_flags} - -I variants/nrf52840/diy/nrf52_promicro_diy_xtal - -D NRF52_PROMICRO_DIY -build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/diy/nrf52_promicro_diy_xtal> -lib_deps = - ${nrf52840_base.lib_deps} -debug_tool = jlink diff --git a/variants/nrf52840/diy/nrf52_promicro_diy_xtal/variant.cpp b/variants/nrf52840/diy/nrf52_promicro_diy_xtal/variant.cpp deleted file mode 100644 index 5869ed1d4..000000000 --- a/variants/nrf52840/diy/nrf52_promicro_diy_xtal/variant.cpp +++ /dev/null @@ -1,38 +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() -{ - // 3V3 Power Rail - pinMode(PIN_3V3_EN, OUTPUT); - digitalWrite(PIN_3V3_EN, HIGH); -} diff --git a/variants/nrf52840/diy/nrf52_promicro_diy_xtal/variant.h b/variants/nrf52840/diy/nrf52_promicro_diy_xtal/variant.h deleted file mode 100644 index 6e208e79f..000000000 --- a/variants/nrf52840/diy/nrf52_promicro_diy_xtal/variant.h +++ /dev/null @@ -1,154 +0,0 @@ -#ifndef _VARIANT_PROMICRO_DIY_ -#define _VARIANT_PROMICRO_DIY_ - -/** Master clock frequency */ -#define VARIANT_MCK (64000000ul) - -// #define USE_LFXO // Board uses 32khz crystal for LF -#define USE_LFRC // Board uses RC for LF - -#define PROMICRO_DIY_XTAL -/*---------------------------------------------------------------------------- - * Headers - *----------------------------------------------------------------------------*/ - -#include "WVariant.h" - -#ifdef __cplusplus -extern "C" { -#endif // __cplusplus - -/* -NRF52 PRO MICRO PIN ASSIGNMENT - -| Pin | Function | | Pin | Function | -|-------|------------|---|---------|-------------| -| Gnd | | | vbat | | -| P0.06 | Serial2 RX | | vbat | | -| P0.08 | Serial2 TX | | Gnd | | -| Gnd | | | reset | | -| Gnd | | | ext_vcc | *see 0.13 | -| P0.17 | RXEN | | P0.31 | BATTERY_PIN | -| P0.20 | GPS_RX | | P0.29 | BUSY | -| P0.22 | GPS_TX | | P0.02 | MISO | -| P0.24 | GPS_EN | | P1.15 | MOSI | -| P1.00 | BUTTON_PIN | | P1.13 | CS | -| P0.11 | SCL | | P1.11 | SCK | -| P1.04 | SDA | | P0.10 | DIO1/IRQ | -| P1.06 | Free pin | | P0.09 | RESET | -| | | | | | -| | Mid board | | | Internal | -| P1.01 | Free pin | | 0.15 | LED | -| P1.02 | Free pin | | 0.13 | 3V3_EN | -| P1.07 | Free pin | | | | -*/ - -// Number of pins defined in PinDescription array -#define PINS_COUNT (48) -#define NUM_DIGITAL_PINS (48) -#define NUM_ANALOG_INPUTS (1) -#define NUM_ANALOG_OUTPUTS (0) - -// Pin 13 enables 3.3V periphery. If the Lora module is on this pin, then it should stay enabled at all times. -#define PIN_3V3_EN (0 + 13) // P0.13 - -// Analog pins -#define BATTERY_PIN (0 + 31) // P0.31 Battery ADC -#define ADC_CHANNEL ADC1_GPIO4_CHANNEL -#define ADC_RESOLUTION 14 -#define BATTERY_SENSE_RESOLUTION_BITS 12 -#define BATTERY_SENSE_RESOLUTION 4096.0 -// Definition of milliVolt per LSB => 3.0V ADC range and 12-bit ADC resolution = 3000mV/4096 -#define VBAT_MV_PER_LSB (0.73242188F) -// Voltage divider value => 1.5M + 1M voltage divider on VBAT = (1.5M / (1M + 1.5M)) -#define VBAT_DIVIDER (0.6F) -// Compensation factor for the VBAT divider -#define VBAT_DIVIDER_COMP (1.73) -// Fixed calculation of milliVolt from compensation value -#define REAL_VBAT_MV_PER_LSB (VBAT_DIVIDER_COMP * VBAT_MV_PER_LSB) -#undef AREF_VOLTAGE -#define AREF_VOLTAGE 3.0 -#define VBAT_AR_INTERNAL AR_INTERNAL_3_0 -#define ADC_MULTIPLIER VBAT_DIVIDER_COMP // REAL_VBAT_MV_PER_LSB -#define VBAT_RAW_TO_SCALED(x) (REAL_VBAT_MV_PER_LSB * x) - -// WIRE IC AND IIC PINS -#define WIRE_INTERFACES_COUNT 1 - -#define PIN_WIRE_SDA (32 + 4) // P1.04 -#define PIN_WIRE_SCL (0 + 11) // P0.11 - -// LED -#define PIN_LED1 (0 + 15) // P0.15 -#define LED_BUILTIN PIN_LED1 -// Actually red -#define LED_BLUE PIN_LED1 -#define LED_STATE_ON 1 // State when LED is lit - -// Button -#define BUTTON_PIN (32 + 0) // P1.00 - -// GPS -#define PIN_GPS_TX (0 + 22) // P0.22 -#define PIN_GPS_RX (0 + 20) // P0.20 - -#define PIN_GPS_EN (0 + 24) // P0.24 -#define GPS_UBLOX -// define GPS_DEBUG - -// UART interfaces -#define PIN_SERIAL1_RX PIN_GPS_TX -#define PIN_SERIAL1_TX PIN_GPS_RX - -#define PIN_SERIAL2_RX (0 + 6) // P0.06 -#define PIN_SERIAL2_TX (0 + 8) // P0.08 - -// Serial interfaces -#define SPI_INTERFACES_COUNT 1 - -#define PIN_SPI_MISO (0 + 2) // P0.02 -#define PIN_SPI_MOSI (32 + 15) // P1.15 -#define PIN_SPI_SCK (32 + 11) // P1.11 - -// LORA MODULES -#define USE_LLCC68 -#define USE_SX1262 -// #define USE_RF95 -#define USE_SX1268 - -// LORA CONFIG -#define SX126X_CS (32 + 13) // P1.13 FIXME - we really should define LORA_CS instead -#define SX126X_DIO1 (0 + 10) // P0.10 IRQ -#define SX126X_DIO2_AS_RF_SWITCH // Note for E22 modules: DIO2 is not attached internally to TXEN for automatic TX/RX switching, - // so it needs connecting externally if it is used in this way -#define SX126X_BUSY (0 + 29) // P0.29 -#define SX126X_RESET (0 + 9) // P0.09 -#define SX126X_RXEN (0 + 17) // P0.17 -#define SX126X_TXEN RADIOLIB_NC // Assuming that DIO2 is connected to TXEN pin. If not, TXEN must be connected. - -/* -On the SX1262, DIO3 sets the voltage for an external TCXO, if one is present. If one is not present, then this should not be used. - -Ebyte -e22-900mm22s has no TCXO -e22-900m22s has TCXO -e220-900mm22s has no TCXO, works with/without this definition, looks like DIO3 not connected at all - -AI-thinker -RA-01SH does not have TCXO - -Waveshare -Core1262 has TCXO - -*/ -// #define SX126X_DIO3_TCXO_VOLTAGE 1.8 - -#ifdef __cplusplus -} -#endif - -/*---------------------------------------------------------------------------- - * Arduino objects - C++ only - *----------------------------------------------------------------------------*/ - -#endif \ No newline at end of file From beaebda4debf07c845565697de8a7740408083e3 Mon Sep 17 00:00:00 2001 From: Chloe Bethel Date: Mon, 10 Nov 2025 15:08:04 +0000 Subject: [PATCH 606/691] stm32wl: Wrap and remove some functions that pull in large amounts of code/data to claw back even more flash space (#8609) --- arch/stm32/stm32.ini | 3 +++ src/platform/stm32wl/main-stm32wl.cpp | 28 +++++++++++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/arch/stm32/stm32.ini b/arch/stm32/stm32.ini index 8b7d256b3..7732533c9 100644 --- a/arch/stm32/stm32.ini +++ b/arch/stm32/stm32.ini @@ -37,6 +37,9 @@ build_flags = -DRADIOLIB_EXCLUDE_LR11X0=1 -DHAL_DAC_MODULE_ONLY -DHAL_RNG_MODULE_ENABLED + -Wl,--wrap=__assert_func + -Wl,--wrap=strerror + -Wl,--wrap=_tzset_unlocked_r build_src_filter = ${arduino_base.build_src_filter} - - - - - - - - - - - - - - diff --git a/src/platform/stm32wl/main-stm32wl.cpp b/src/platform/stm32wl/main-stm32wl.cpp index 3eddbb3cf..e841f8f29 100644 --- a/src/platform/stm32wl/main-stm32wl.cpp +++ b/src/platform/stm32wl/main-stm32wl.cpp @@ -26,3 +26,31 @@ void getMacAddr(uint8_t *dmac) } void cpuDeepSleep(uint32_t msecToWake) {} + +// Hacks to force more code and data out. + +// By default __assert_func uses fiprintf which pulls in stdio. +extern "C" void __wrap___assert_func(const char *, int, const char *, const char *) +{ + while (true) + ; + return; +} + +// By default strerror has a lot of strings we probably don't use. Make it return an empty string instead. +char empty = 0; +extern "C" char *__wrap_strerror(int) +{ + return ∅ +} + +#ifdef MESHTASTIC_EXCLUDE_TZ +struct _reent; + +// Even if you don't use timezones, mktime will try to set the timezone anyway with _tzset_unlocked(), which pulls in scanf and +// friends. The timezone is initialized to UTC by default. +extern "C" void __wrap__tzset_unlocked_r(struct _reent *reent_ptr) +{ + return; +} +#endif \ No newline at end of file From 7d2744fae06062d4a4518383d66f297947e4dc96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Mon, 10 Nov 2025 16:16:24 +0100 Subject: [PATCH 607/691] Change RadioLib to commit zip til 7.4.1+ is released fixes regression for SX127x chips per @GUVWAF --- platformio.ini | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index 09c924f47..f9e2dd898 100644 --- a/platformio.ini +++ b/platformio.ini @@ -115,7 +115,8 @@ lib_deps = [radiolib_base] lib_deps = # renovate: datasource=custom.pio depName=RadioLib packageName=jgromes/library/RadioLib - jgromes/RadioLib@7.4.0 + # jgromes/RadioLib@7.4.0 + https://github.com/jgromes/RadioLib/archive/536c7267362e2c1345be7054ba45e503252975ff.zip [device-ui_base] lib_deps = From e9590003f4709ca83e0586d59050483587ba194b Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Mon, 10 Nov 2025 11:58:39 -0600 Subject: [PATCH 608/691] Only call stopNow if we're nagging (#8601) --- src/input/InputBroker.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/input/InputBroker.cpp b/src/input/InputBroker.cpp index 7e3ff3de9..0aa78e2b8 100644 --- a/src/input/InputBroker.cpp +++ b/src/input/InputBroker.cpp @@ -52,7 +52,7 @@ int InputBroker::handleInputEvent(const InputEvent *event) powerFSM.trigger(EVENT_INPUT); // todo: not every input should wake, like long hold release if (event && event->inputEvent != INPUT_BROKER_NONE && externalNotificationModule && - moduleConfig.external_notification.enabled) { + moduleConfig.external_notification.enabled && externalNotificationModule->nagging()) { externalNotificationModule->stopNow(); } From 4118e1c0f6f41e9fda52f9eb6a84514af8bbf2ed Mon Sep 17 00:00:00 2001 From: "Jason B. Cox" Date: Mon, 10 Nov 2025 18:19:15 -0800 Subject: [PATCH 609/691] Cleanup unnecessary global dereferencing in CryptoEngine (#8611) Co-authored-by: Ben Meadors --- src/mesh/CryptoEngine.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/mesh/CryptoEngine.cpp b/src/mesh/CryptoEngine.cpp index 82d0a9f57..9ca16878d 100644 --- a/src/mesh/CryptoEngine.cpp +++ b/src/mesh/CryptoEngine.cpp @@ -92,10 +92,10 @@ bool CryptoEngine::encryptCurve25519(uint32_t toNode, uint32_t fromNode, meshtas LOG_DEBUG("Node %d or their public_key not found", toNode); return false; } - if (!crypto->setDHPublicKey(remotePublic.bytes)) { + if (!setDHPublicKey(remotePublic.bytes)) { return false; } - crypto->hash(shared_key, 32); + hash(shared_key, 32); initNonce(fromNode, packetNum, extraNonceTmp); // Calculate the shared secret with the destination node and encrypt @@ -134,10 +134,10 @@ bool CryptoEngine::decryptCurve25519(uint32_t fromNode, meshtastic_UserLite_publ } // Calculate the shared secret with the sending node and decrypt - if (!crypto->setDHPublicKey(remotePublic.bytes)) { + if (!setDHPublicKey(remotePublic.bytes)) { return false; } - crypto->hash(shared_key, 32); + hash(shared_key, 32); initNonce(fromNode, packetNum, extraNonce); printBytes("Attempt decrypt with nonce: ", nonce, 13); From 7212fb9caa15119c500edb3a074a5bc6b956605d Mon Sep 17 00:00:00 2001 From: Andrik45719 Date: Tue, 11 Nov 2025 18:00:08 +0200 Subject: [PATCH 610/691] Fix null pointer dereference in radio chip region check (#8613) --- src/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.cpp b/src/main.cpp index c65482292..8fec62953 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1401,7 +1401,7 @@ void setup() #endif // check if the radio chip matches the selected region - if ((config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_LORA_24) && (!rIf->wideLora())) { + if ((config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_LORA_24) && rIf && (!rIf->wideLora())) { LOG_WARN("LoRa chip does not support 2.4GHz. Revert to unset"); config.lora.region = meshtastic_Config_LoRaConfig_RegionCode_UNSET; nodeDB->saveToDisk(SEGMENT_CONFIG); From 4df6627ab153ef61a391fce92af00853e69920ea Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 12 Nov 2025 05:33:31 -0600 Subject: [PATCH 611/691] Upgrade trunk (#8606) Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com> --- .trunk/trunk.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml index 73baa5345..1fd8790f2 100644 --- a/.trunk/trunk.yaml +++ b/.trunk/trunk.yaml @@ -8,15 +8,15 @@ plugins: uri: https://github.com/trunk-io/plugins lint: enabled: - - checkov@3.2.490 - - renovate@41.173.1 + - checkov@3.2.492 + - renovate@42.5.4 - prettier@3.6.2 - trufflehog@3.90.13 - yamllint@1.37.1 - bandit@1.8.6 - trivy@0.67.2 - taplo@0.10.0 - - ruff@0.14.3 + - ruff@0.14.4 - isort@7.0.0 - markdownlint@0.45.0 - oxipng@9.1.5 @@ -26,7 +26,7 @@ lint: - hadolint@2.14.0 - shfmt@3.6.0 - shellcheck@0.11.0 - - black@25.9.0 + - black@25.11.0 - git-diff-check - gitleaks@8.29.0 - clang-format@16.0.3 From 0aa11d810c06ed87e70efc6bde1b4cc5f6d8d463 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Thu, 13 Nov 2025 11:20:17 -0600 Subject: [PATCH 612/691] Clean up GPS toggle logging Removed redundant log warnings for GPS toggle events. --- src/modules/SystemCommandsModule.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/modules/SystemCommandsModule.cpp b/src/modules/SystemCommandsModule.cpp index 74b9678f4..dc5d8b41f 100644 --- a/src/modules/SystemCommandsModule.cpp +++ b/src/modules/SystemCommandsModule.cpp @@ -85,10 +85,8 @@ int SystemCommandsModule::handleInputEvent(const InputEvent *event) switch (event->inputEvent) { // GPS case INPUT_BROKER_GPS_TOGGLE: - LOG_WARN("GPS Toggle"); #if !MESHTASTIC_EXCLUDE_GPS if (gps) { - LOG_WARN("GPS Toggle2"); if (config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_ENABLED && config.position.fixed_position == false) { nodeDB->clearLocalPosition(); From 034aaa376a8939dde47095e7697ce85d60cb34da Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 14 Nov 2025 06:09:25 -0600 Subject: [PATCH 613/691] Automated version bumps (#8626) Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> --- bin/org.meshtastic.meshtasticd.metainfo.xml | 3 +++ debian/changelog | 6 ++++++ version.properties | 2 +- 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/bin/org.meshtastic.meshtasticd.metainfo.xml b/bin/org.meshtastic.meshtasticd.metainfo.xml index 61ecf9fb5..b59bb6202 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.15 + https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.14 diff --git a/debian/changelog b/debian/changelog index a387cc3c5..437e1645b 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +meshtasticd (2.7.15.0) unstable; urgency=medium + + * Version 2.7.15 + + -- GitHub Actions Thu, 13 Nov 2025 12:31:57 +0000 + meshtasticd (2.7.14.0) unstable; urgency=medium * Version 2.7.14 diff --git a/version.properties b/version.properties index fe1a5b31b..165f476df 100644 --- a/version.properties +++ b/version.properties @@ -1,4 +1,4 @@ [VERSION] major = 2 minor = 7 -build = 14 +build = 15 From 4284fc2aecf2e5ce549bce3b4d71e35e26a79a0a Mon Sep 17 00:00:00 2001 From: Dane Evans Date: Sun, 16 Nov 2025 12:49:46 +1100 Subject: [PATCH 614/691] Feat/6704 neighbor info on demand (#8523) * full thing. works * works * minimal changes * roll back previous changes, move to using the alloc() overrride * clean up comments * format * run clang-format manually. Trunk may be the absolute worst formatter in existance * format on WSL to fix trunks awfulness * add a 3 minute cooldown to prevent messages going back and forth * add ignoring the dummy neighbor. * fix or. * fix spelling, increase logging --------- Co-authored-by: Ben Meadors --- src/modules/NeighborInfoModule.cpp | 67 +++++++++++++++++++++++------- src/modules/NeighborInfoModule.h | 7 ++++ 2 files changed, 59 insertions(+), 15 deletions(-) diff --git a/src/modules/NeighborInfoModule.cpp b/src/modules/NeighborInfoModule.cpp index 97dc17001..936a7b44a 100644 --- a/src/modules/NeighborInfoModule.cpp +++ b/src/modules/NeighborInfoModule.cpp @@ -34,7 +34,8 @@ void NeighborInfoModule::printNodeDBNeighbors() } } -/* Send our initial owner announcement 35 seconds after we start (to give network time to setup) */ +/* Send our initial owner announcement 35 seconds after we start (to give + * network time to setup) */ NeighborInfoModule::NeighborInfoModule() : ProtobufModule("neighborinfo", meshtastic_PortNum_NEIGHBORINFO_APP, &meshtastic_NeighborInfo_msg), concurrency::OSThread("NeighborInfo") @@ -53,8 +54,8 @@ NeighborInfoModule::NeighborInfoModule() } /* -Collect neighbor info from the nodeDB's history, capping at a maximum number of entries and max time -Assumes that the neighborInfo packet has been allocated +Collect neighbor info from the nodeDB's history, capping at a maximum number of +entries and max time Assumes that the neighborInfo packet has been allocated @returns the number of entries collected */ uint32_t NeighborInfoModule::collectNeighborInfo(meshtastic_NeighborInfo *neighborInfo) @@ -71,8 +72,8 @@ uint32_t NeighborInfoModule::collectNeighborInfo(meshtastic_NeighborInfo *neighb if ((neighborInfo->neighbors_count < MAX_NUM_NEIGHBORS) && (nbr.node_id != my_node_id)) { neighborInfo->neighbors[neighborInfo->neighbors_count].node_id = nbr.node_id; neighborInfo->neighbors[neighborInfo->neighbors_count].snr = nbr.snr; - // Note: we don't set the last_rx_time and node_broadcast_intervals_secs here, because we don't want to send this over - // the mesh + // Note: we don't set the last_rx_time and node_broadcast_intervals_secs + // here, because we don't want to send this over the mesh neighborInfo->neighbors_count++; } } @@ -88,8 +89,9 @@ void NeighborInfoModule::cleanUpNeighbors() uint32_t now = getTime(); NodeNum my_node_id = nodeDB->getNodeNum(); for (auto it = neighbors.rbegin(); it != neighbors.rend();) { - // We will remove a neighbor if we haven't heard from them in twice the broadcast interval - // cannot use isWithinTimespanMs() as it->last_rx_time is seconds since 1970 + // We will remove a neighbor if we haven't heard from them in twice the + // broadcast interval cannot use isWithinTimespanMs() as it->last_rx_time is + // seconds since 1970 if ((now - it->last_rx_time > it->node_broadcast_interval_secs * 2) && (it->node_id != my_node_id)) { LOG_DEBUG("Remove neighbor with node ID 0x%x", it->node_id); it = std::vector::reverse_iterator( @@ -132,25 +134,55 @@ int32_t NeighborInfoModule::runOnce() return Default::getConfiguredOrDefaultMs(moduleConfig.neighbor_info.update_interval, default_neighbor_info_broadcast_secs); } +meshtastic_MeshPacket *NeighborInfoModule::allocReply() +{ + LOG_INFO("NeighborInfoRequested."); + if (lastSentReply && Throttle::isWithinTimespanMs(lastSentReply, 3 * 60 * 1000)) { + LOG_DEBUG("Skip Neighbors reply since we sent a reply <3min ago"); + ignoreRequest = true; // Mark it as ignored for MeshModule + return nullptr; + } + + meshtastic_NeighborInfo neighborInfo = meshtastic_NeighborInfo_init_zero; + collectNeighborInfo(&neighborInfo); + + meshtastic_MeshPacket *reply = allocDataProtobuf(neighborInfo); + + if (reply) { + lastSentReply = millis(); // Track when we sent this reply + } + return reply; +} + /* Collect a received neighbor info packet from another node Pass it to an upper client; do not persist this data on the mesh */ bool NeighborInfoModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_NeighborInfo *np) { + LOG_DEBUG("NeighborInfo: handleReceivedProtobuf"); if (np) { printNeighborInfo("RECEIVED", np); - updateNeighbors(mp, np); + // Ignore dummy/interceptable packets: single neighbor with nodeId 0 and snr 0 + if (np->neighbors_count != 1 || np->neighbors[0].node_id != 0 || np->neighbors[0].snr != 0.0f) { + LOG_DEBUG(" Updating neighbours"); + updateNeighbors(mp, np); + } else { + LOG_DEBUG(" Ignoring dummy neighbor info packet (single neighbor with nodeId 0, snr 0)"); + } } else if (mp.hop_start != 0 && mp.hop_start == mp.hop_limit) { + LOG_DEBUG("Get or create neighbor: %u with snr %f", mp.from, mp.rx_snr); // If the hopLimit is the same as hopStart, then it is a neighbor - getOrCreateNeighbor(mp.from, mp.from, 0, mp.rx_snr); // Set the broadcast interval to 0, as we don't know it + getOrCreateNeighbor(mp.from, mp.from, 0, + mp.rx_snr); // Set the broadcast interval to 0, as we don't know it } // Allow others to handle this packet return false; } /* -Copy the content of a current NeighborInfo packet into a new one and update the last_sent_by_id to our NodeNum +Copy the content of a current NeighborInfo packet into a new one and update the +last_sent_by_id to our NodeNum */ void NeighborInfoModule::alterReceivedProtobuf(meshtastic_MeshPacket &p, meshtastic_NeighborInfo *n) { @@ -168,8 +200,10 @@ void NeighborInfoModule::resetNeighbors() void NeighborInfoModule::updateNeighbors(const meshtastic_MeshPacket &mp, const meshtastic_NeighborInfo *np) { - // The last sent ID will be 0 if the packet is from the phone, which we don't count as - // an edge. So we assume that if it's zero, then this packet is from our node. + LOG_DEBUG("updateNeighbors"); + // The last sent ID will be 0 if the packet is from the phone, which we don't + // count as an edge. So we assume that if it's zero, then this packet is from + // our node. if (mp.which_payload_variant == meshtastic_MeshPacket_decoded_tag && mp.from) { getOrCreateNeighbor(mp.from, np->last_sent_by_id, np->node_broadcast_interval_secs, mp.rx_snr); } @@ -188,7 +222,8 @@ meshtastic_Neighbor *NeighborInfoModule::getOrCreateNeighbor(NodeNum originalSen // if found, update it neighbors[i].snr = snr; neighbors[i].last_rx_time = getTime(); - // Only if this is the original sender, the broadcast interval corresponds to it + // Only if this is the original sender, the broadcast interval corresponds + // to it if (originalSender == n && node_broadcast_interval_secs != 0) neighbors[i].node_broadcast_interval_secs = node_broadcast_interval_secs; return &neighbors[i]; @@ -200,10 +235,12 @@ meshtastic_Neighbor *NeighborInfoModule::getOrCreateNeighbor(NodeNum originalSen new_nbr.node_id = n; new_nbr.snr = snr; new_nbr.last_rx_time = getTime(); - // Only if this is the original sender, the broadcast interval corresponds to it + // Only if this is the original sender, the broadcast interval corresponds to + // it if (originalSender == n && node_broadcast_interval_secs != 0) new_nbr.node_broadcast_interval_secs = node_broadcast_interval_secs; - else // Assume the same broadcast interval as us for the neighbor if we don't know it + else // Assume the same broadcast interval as us for the neighbor if we don't + // know it new_nbr.node_broadcast_interval_secs = moduleConfig.neighbor_info.update_interval; if (neighbors.size() < MAX_NUM_NEIGHBORS) { diff --git a/src/modules/NeighborInfoModule.h b/src/modules/NeighborInfoModule.h index aa76a2187..abb530329 100644 --- a/src/modules/NeighborInfoModule.h +++ b/src/modules/NeighborInfoModule.h @@ -28,6 +28,10 @@ class NeighborInfoModule : public ProtobufModule, priva */ virtual bool handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_NeighborInfo *nb) override; + /* Messages can be received that have the want_response bit set. If set, this callback will be invoked + * so that subclasses can (optionally) send a response back to the original sender. */ + virtual meshtastic_MeshPacket *allocReply() override; + /* * Collect neighbor info from the nodeDB's history, capping at a maximum number of entries and max time * @return the number of entries collected @@ -66,5 +70,8 @@ class NeighborInfoModule : public ProtobufModule, priva /* These are for debugging only */ void printNeighborInfo(const char *header, const meshtastic_NeighborInfo *np); void printNodeDBNeighbors(); + + private: + uint32_t lastSentReply = 0; // Last time we sent a position reply (used for reply throttling only) }; extern NeighborInfoModule *neighborInfoModule; \ No newline at end of file From 955347bf50b13168de188628d1003f1ed3699e90 Mon Sep 17 00:00:00 2001 From: Jason P Date: Sun, 16 Nov 2025 08:42:51 -0600 Subject: [PATCH 615/691] Remove fixed scaling in Digital Clock (#8620) * Update digital clock draw to auto scale to correct size; no more fixed scaling * Static scale calcuation to improve performance * Update src/graphics/draw/ClockRenderer.cpp Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Back off for width or height exceeds * Fixes for some calcuations --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/graphics/draw/ClockRenderer.cpp | 107 ++++++++++++++++------------ src/graphics/draw/ClockRenderer.h | 1 - 2 files changed, 61 insertions(+), 47 deletions(-) diff --git a/src/graphics/draw/ClockRenderer.cpp b/src/graphics/draw/ClockRenderer.cpp index 97417571b..cc6a70957 100644 --- a/src/graphics/draw/ClockRenderer.cpp +++ b/src/graphics/draw/ClockRenderer.cpp @@ -194,17 +194,12 @@ void drawDigitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int1 graphics::drawCommonHeader(display, x, y, titleStr, true, true); int line = 0; -#ifdef T_WATCH_S3 - if (nimbleBluetooth && nimbleBluetooth->isConnected()) { - graphics::ClockRenderer::drawBluetoothConnectedIcon(display, display->getWidth() - 18, display->getHeight() - 14); - } -#endif - uint32_t rtc_sec = getValidTime(RTCQuality::RTCQualityDevice, true); // Display local timezone char timeString[16]; int hour = 0; int minute = 0; int second = 0; + if (rtc_sec > 0) { long hms = rtc_sec % SEC_PER_DAY; hms = (hms + SEC_PER_DAY) % SEC_PER_DAY; @@ -215,11 +210,11 @@ void drawDigitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int1 } bool isPM = hour >= 12; - // hour = hour > 12 ? hour - 12 : hour; if (config.display.use_12h_clock) { hour %= 12; - if (hour == 0) + if (hour == 0) { hour = 12; + } snprintf(timeString, sizeof(timeString), "%d:%02d", hour, minute); } else { snprintf(timeString, sizeof(timeString), "%02d:%02d", hour, minute); @@ -229,24 +224,56 @@ void drawDigitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int1 char secondString[8]; snprintf(secondString, sizeof(secondString), "%02d", second); -#ifdef T_WATCH_S3 - float scale = 1.5; -#elif defined(CHATTER_2) - float scale = 1.1; -#else - float scale = 0.75; - if (isHighResolution) { - scale = 1.5; - } -#endif + static bool scaleInitialized = false; + static float scale = 0.75f; + static float segmentWidth = SEGMENT_WIDTH * 0.75f; + static float segmentHeight = SEGMENT_HEIGHT * 0.75f; - uint16_t segmentWidth = SEGMENT_WIDTH * scale; - uint16_t segmentHeight = SEGMENT_HEIGHT * scale; + if (!scaleInitialized) { + float screenwidth_target_ratio = 0.80f; // Target 80% of display width (adjustable) + float max_scale = 3.5f; // Safety limit to avoid runaway scaling + float step = 0.05f; // Step increment per iteration + + float target_width = display->getWidth() * screenwidth_target_ratio; + float target_height = + display->getHeight() - + (isHighResolution + ? 46 + : 33); // Be careful adjusting this number, we have to account for header and the text under the time + + float calculated_width_size = 0.0f; + float calculated_height_size = 0.0f; + + while (true) { + segmentWidth = SEGMENT_WIDTH * scale; + segmentHeight = SEGMENT_HEIGHT * scale; + + calculated_width_size = segmentHeight + ((segmentWidth + (segmentHeight * 2) + 4) * 4); + calculated_height_size = segmentHeight + ((segmentHeight + (segmentHeight * 2) + 4) * 2); + + if (calculated_width_size >= target_width || calculated_height_size >= target_height || scale >= max_scale) { + break; + } + + scale += step; + } + + // If we overshot width, back off one step and recompute segment sizes + if (calculated_width_size > target_width || calculated_height_size > target_height) { + scale -= step; + segmentWidth = SEGMENT_WIDTH * scale; + segmentHeight = SEGMENT_HEIGHT * scale; + } + + scaleInitialized = true; + } + + size_t len = strlen(timeString); // calculate hours:minutes string width - uint16_t timeStringWidth = strlen(timeString) * 5; + uint16_t timeStringWidth = len * 5; // base spacing between characters - for (uint8_t i = 0; i < strlen(timeString); i++) { + for (size_t i = 0; i < len; i++) { char character = timeString[i]; if (character == ':') { @@ -257,19 +284,21 @@ void drawDigitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int1 } uint16_t hourMinuteTextX = (display->getWidth() / 2) - (timeStringWidth / 2); - uint16_t startingHourMinuteTextX = hourMinuteTextX; - uint16_t hourMinuteTextY = (display->getHeight() / 2) - (((segmentWidth * 2) + (segmentHeight * 3) + 8) / 2); + uint16_t hourMinuteTextY = (display->getHeight() / 2) - (((segmentWidth * 2) + (segmentHeight * 3) + 8) / 2) + 2; // iterate over characters in hours:minutes string and draw segmented characters - for (uint8_t i = 0; i < strlen(timeString); i++) { + for (size_t i = 0; i < len; i++) { char character = timeString[i]; if (character == ':') { drawSegmentedDisplayColon(display, hourMinuteTextX, hourMinuteTextY, scale); hourMinuteTextX += segmentHeight + 6; + if (scale >= 2.0f) { + hourMinuteTextX += (uint16_t)(4.5f * scale); + } } else { drawSegmentedDisplayCharacter(display, hourMinuteTextX, hourMinuteTextY, character - '0', scale); @@ -279,38 +308,29 @@ void drawDigitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int1 hourMinuteTextX += 5; } - // draw seconds string + // draw seconds string + AM/PM display->setFont(FONT_SMALL); int xOffset = (isHighResolution) ? 0 : -1; if (hour >= 10) { xOffset += (isHighResolution) ? 32 : 18; } - int yOffset = (isHighResolution) ? 3 : 1; -#ifdef SENSECAP_INDICATOR - yOffset -= 3; -#endif -#ifdef T_DECK - yOffset -= 5; -#endif + if (config.display.use_12h_clock) { - display->drawString(startingHourMinuteTextX + xOffset, (display->getHeight() - hourMinuteTextY) - yOffset - 2, - isPM ? "pm" : "am"); + display->drawString(startingHourMinuteTextX + xOffset, (display->getHeight() - hourMinuteTextY) - 1, isPM ? "pm" : "am"); } #ifndef USE_EINK xOffset = (isHighResolution) ? 18 : 10; - display->drawString(startingHourMinuteTextX + timeStringWidth - xOffset, (display->getHeight() - hourMinuteTextY) - yOffset, + if (scale >= 2.0f) { + xOffset -= (int)(4.5f * scale); + } + display->drawString(startingHourMinuteTextX + timeStringWidth - xOffset, (display->getHeight() - hourMinuteTextY) - 1, secondString); #endif graphics::drawCommonFooter(display, x, y); } -void drawBluetoothConnectedIcon(OLEDDisplay *display, int16_t x, int16_t y) -{ - display->drawFastImage(x, y, 18, 14, bluetoothConnectedIcon); -} - // Draw an analog clock void drawAnalogClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) { @@ -321,11 +341,6 @@ void drawAnalogClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16 graphics::drawCommonHeader(display, x, y, titleStr, true, true); int line = 0; -#ifdef T_WATCH_S3 - if (nimbleBluetooth && nimbleBluetooth->isConnected()) { - drawBluetoothConnectedIcon(display, display->getWidth() - 18, display->getHeight() - 14); - } -#endif // clock face center coordinates int16_t centerX = display->getWidth() / 2; int16_t centerY = display->getHeight() / 2; diff --git a/src/graphics/draw/ClockRenderer.h b/src/graphics/draw/ClockRenderer.h index c8ba62868..eace26cf5 100644 --- a/src/graphics/draw/ClockRenderer.h +++ b/src/graphics/draw/ClockRenderer.h @@ -24,7 +24,6 @@ void drawVerticalSegment(OLEDDisplay *display, int x, int y, int width, int heig // UI elements for clock displays // void drawWatchFaceToggleButton(OLEDDisplay *display, int16_t x, int16_t y, bool digitalMode = true, float scale = 1); -void drawBluetoothConnectedIcon(OLEDDisplay *display, int16_t x, int16_t y); } // namespace ClockRenderer From 84bb1e33a6a857f342ebd8bfb97089a5144c5a60 Mon Sep 17 00:00:00 2001 From: Jason P Date: Sun, 16 Nov 2025 14:18:16 -0600 Subject: [PATCH 616/691] Add code for preserving favorites, also move to Home screen before reseting (#8647) --- src/graphics/draw/MenuHandler.cpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/graphics/draw/MenuHandler.cpp b/src/graphics/draw/MenuHandler.cpp index ddd4231f9..3162f07d3 100644 --- a/src/graphics/draw/MenuHandler.cpp +++ b/src/graphics/draw/MenuHandler.cpp @@ -785,17 +785,24 @@ void menuHandler::nodeNameLengthMenu() void menuHandler::resetNodeDBMenu() { - static const char *optionsArray[] = {"Back", "Confirm"}; + static const char *optionsArray[] = {"Back", "Reset All", "Preserve Favorites"}; BannerOverlayOptions bannerOptions; bannerOptions.message = "Confirm Reset NodeDB"; bannerOptions.optionsArrayPtr = optionsArray; - bannerOptions.optionsCount = 2; + bannerOptions.optionsCount = 3; bannerOptions.bannerCallback = [](int selected) -> void { - if (selected == 1) { + if (selected == 1 || selected == 2) { disableBluetooth(); + screen->setFrames(Screen::FOCUS_DEFAULT); + } + if (selected == 1) { LOG_INFO("Initiate node-db reset"); nodeDB->resetNodes(); rebootAtMsec = (millis() + DEFAULT_REBOOT_SECONDS * 1000); + } else if (selected == 2) { + LOG_INFO("Initiate node-db reset but keeping favorites"); + nodeDB->resetNodes(1); + rebootAtMsec = (millis() + DEFAULT_REBOOT_SECONDS * 1000); } }; screen->showOverlayBanner(bannerOptions); From 6e3be132f28c7601f5b9869d6917b2dec775ff75 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Sun, 16 Nov 2025 14:20:17 -0600 Subject: [PATCH 617/691] Reset the calibration data back to 0 when doing a compass calibration --- src/motion/BMX160Sensor.cpp | 3 ++- src/motion/ICM20948Sensor.cpp | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/motion/BMX160Sensor.cpp b/src/motion/BMX160Sensor.cpp index 003ee850c..56f794306 100755 --- a/src/motion/BMX160Sensor.cpp +++ b/src/motion/BMX160Sensor.cpp @@ -116,6 +116,7 @@ void BMX160Sensor::calibrate(uint16_t forSeconds) { #if !defined(MESHTASTIC_EXCLUDE_SCREEN) LOG_DEBUG("BMX160 calibration started for %is", forSeconds); + highestX = 0, lowestX = 0, highestY = 0, lowestY = 0, highestZ = 0, lowestZ = 0; doCalibration = true; uint16_t calibrateFor = forSeconds * 1000; // calibrate for seconds provided @@ -127,4 +128,4 @@ void BMX160Sensor::calibrate(uint16_t forSeconds) #endif -#endif \ No newline at end of file +#endif diff --git a/src/motion/ICM20948Sensor.cpp b/src/motion/ICM20948Sensor.cpp index 76ba8e8cf..ebb0f7b66 100755 --- a/src/motion/ICM20948Sensor.cpp +++ b/src/motion/ICM20948Sensor.cpp @@ -157,6 +157,7 @@ void ICM20948Sensor::calibrate(uint16_t forSeconds) { #if !defined(MESHTASTIC_EXCLUDE_SCREEN) && HAS_SCREEN LOG_DEBUG("BMX160 calibration started for %is", forSeconds); + highestX = 0, lowestX = 0, highestY = 0, lowestY = 0, highestZ = 0, lowestZ = 0; doCalibration = true; uint16_t calibrateFor = forSeconds * 1000; // calibrate for seconds provided @@ -295,4 +296,4 @@ bool ICM20948Singleton::setWakeOnMotion() return true; } -#endif \ No newline at end of file +#endif From df063f40ff85a3fbd916e26fbbe4ef14b88d4336 Mon Sep 17 00:00:00 2001 From: Chloe Bethel Date: Thu, 30 Oct 2025 22:35:56 +0000 Subject: [PATCH 618/691] Try to look for a config file based on the HAT vendor/product for autoconfig --- src/platform/portduino/PortduinoGlue.cpp | 55 +++++++++++++++++++++--- 1 file changed, 49 insertions(+), 6 deletions(-) diff --git a/src/platform/portduino/PortduinoGlue.cpp b/src/platform/portduino/PortduinoGlue.cpp index 6b3476b98..5d323d107 100644 --- a/src/platform/portduino/PortduinoGlue.cpp +++ b/src/platform/portduino/PortduinoGlue.cpp @@ -146,6 +146,20 @@ void getMacAddr(uint8_t *dmac) } } +std::string cleanupNameForAutoconf(std::string name) +{ + // Convert spaces -> dashes, lowercase + + std::transform(name.begin(), name.end(), name.begin(), [](unsigned char c) { + if (c == ' ') { + return '-'; + } + return (char)std::tolower(c); + }); + + return name; +} + /** apps run under portduino can optionally define a portduinoSetup() to * use portduino specific init code (such as gpioBind) to setup portduino on their host machine, * before running 'arduino' code. @@ -218,6 +232,11 @@ void portduinoSetup() // If LoRa `Module: auto` (default in config.yaml), // attempt to auto config based on Product Strings if (portduino_config.lora_module == use_autoconf) { + bool found_hat = false; + bool found_rak_eeprom = false; + bool found_ch341 = false; + + char hat_vendor[96] = {0}; char autoconf_product[96] = {0}; // Try CH341 try { @@ -227,21 +246,32 @@ void portduinoSetup() ch341Hal->getProductString(autoconf_product, 95); delete ch341Hal; std::cout << "autoconf: Found CH341 device " << autoconf_product << std::endl; + + found_ch341 = true; } catch (...) { std::cout << "autoconf: Could not locate CH341 device" << std::endl; } // Try Pi HAT+ if (strlen(autoconf_product) < 6) { std::cout << "autoconf: Looking for Pi HAT+..." << std::endl; + if (access("/proc/device-tree/hat/vendor", R_OK) == 0) { + std::ifstream hatVendorFile("/proc/device-tree/hat/vendor"); + if (hatVendorFile.is_open()) { + hatVendorFile.read(hat_vendor, 95); + hatVendorFile.close(); + } + } if (access("/proc/device-tree/hat/product", R_OK) == 0) { std::ifstream hatProductFile("/proc/device-tree/hat/product"); if (hatProductFile.is_open()) { hatProductFile.read(autoconf_product, 95); hatProductFile.close(); } - std::cout << "autoconf: Found Pi HAT+ " << autoconf_product << " at /proc/device-tree/hat/product" << std::endl; + std::cout << "autoconf: Found Pi HAT+ " << hat_vendor << " " << autoconf_product << " at /proc/device-tree/hat" + << std::endl; + found_hat = true; } else { - std::cout << "autoconf: Could not locate Pi HAT+ at /proc/device-tree/hat/product" << std::endl; + std::cout << "autoconf: Could not locate Pi HAT+ at /proc/device-tree/hat" << std::endl; } } // attempt to load autoconf data from an EEPROM on 0x50 @@ -297,6 +327,7 @@ void portduinoSetup() autoconf_product[0] = 0x0; } else { std::cout << "autoconf: Found eeprom data " << autoconf_raw << std::endl; + found_rak_eeprom = true; if (mac_start != nullptr) { std::cout << "autoconf: Found mac data " << mac_start << std::endl; if (strlen(mac_start) == 12) @@ -325,12 +356,24 @@ void portduinoSetup() if (strlen(autoconf_product) > 0) { // From configProducts map in PortduinoGlue.h std::string product_config = ""; - try { + + if (configProducts.find(autoconf_product) != configProducts.end()) { product_config = configProducts.at(autoconf_product); - } catch (std::out_of_range &e) { - std::cerr << "autoconf: Unable to find config for " << autoconf_product << std::endl; - exit(EXIT_FAILURE); + } else { + if (found_hat) { + product_config = + cleanupNameForAutoconf("lora-hat-" + std::string(hat_vendor) + "-" + autoconf_product + ".yaml"); + } else if (found_ch341) { + product_config = cleanupNameForAutoconf("lora-usb-" + std::string(autoconf_product) + ".yaml"); + } + + if (!access((portduino_config.available_directory + product_config).c_str(), R_OK)) { + std::cerr << "autoconf: Unable to find config for " << autoconf_product << "(tried " << product_config << ")" + << std::endl; + exit(EXIT_FAILURE); + } } + 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 { From b7bdcbe43e3cdcf94a5a64252237d6a9f75c5dfb Mon Sep 17 00:00:00 2001 From: Chloe Bethel Date: Mon, 10 Nov 2025 13:38:20 +0000 Subject: [PATCH 619/691] Address review comments --- src/platform/portduino/PortduinoGlue.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/platform/portduino/PortduinoGlue.cpp b/src/platform/portduino/PortduinoGlue.cpp index 5d323d107..10b3a7fe4 100644 --- a/src/platform/portduino/PortduinoGlue.cpp +++ b/src/platform/portduino/PortduinoGlue.cpp @@ -367,7 +367,12 @@ void portduinoSetup() product_config = cleanupNameForAutoconf("lora-usb-" + std::string(autoconf_product) + ".yaml"); } - if (!access((portduino_config.available_directory + product_config).c_str(), R_OK)) { + // Don't try to automatically find config for a device with RAK eeprom. + if (found_rak_eeprom) { + std::cerr << "autoconf: Found unknown RAK product " << autoconf_product << std::endl; + exit(EXIT_FAILURE); + } + if (access((portduino_config.available_directory + product_config).c_str(), R_OK) != 0) { std::cerr << "autoconf: Unable to find config for " << autoconf_product << "(tried " << product_config << ")" << std::endl; exit(EXIT_FAILURE); From 43e0c354668c76492c504256d90c3d656aa6c7ab Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 16 Nov 2025 20:00:05 -0600 Subject: [PATCH 620/691] chore(deps): update dorny/test-reporter action to v2.2.0 (#8637) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/test_native.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test_native.yml b/.github/workflows/test_native.yml index 3f3d02e4c..591d52bd0 100644 --- a/.github/workflows/test_native.yml +++ b/.github/workflows/test_native.yml @@ -143,7 +143,7 @@ jobs: merge-multiple: true - name: Test Report - uses: dorny/test-reporter@v2.1.1 + uses: dorny/test-reporter@v2.2.0 with: name: PlatformIO Tests path: testreport.xml From 17cd83085bdb8c548a3487085d3858253752d834 Mon Sep 17 00:00:00 2001 From: Jason P Date: Sun, 16 Nov 2025 22:05:24 -0600 Subject: [PATCH 621/691] Remove gating for Display Options (#8651) --- src/graphics/draw/MenuHandler.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/graphics/draw/MenuHandler.cpp b/src/graphics/draw/MenuHandler.cpp index 3162f07d3..1ac75c4d9 100644 --- a/src/graphics/draw/MenuHandler.cpp +++ b/src/graphics/draw/MenuHandler.cpp @@ -581,11 +581,8 @@ void menuHandler::systemBaseMenu() optionsArray[options] = "Notifications"; optionsEnumArray[options++] = Notifications; -#if defined(ST7789_CS) || defined(ST7796_CS) || defined(USE_OLED) || defined(USE_SSD1306) || defined(USE_SH1106) || \ - defined(USE_SH1107) || defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) || HAS_TFT optionsArray[options] = "Display Options"; optionsEnumArray[options++] = ScreenOptions; -#endif #if defined(M5STACK_UNITC6L) optionsArray[options] = "Bluetooth"; From 438e170b0345b6f35abebfd718af9229cdbbbfd0 Mon Sep 17 00:00:00 2001 From: Austin Date: Mon, 3 Nov 2025 18:56:31 -0500 Subject: [PATCH 622/691] Packaging: Add libbsd where needed (#8533) --- .devcontainer/Dockerfile | 2 +- alpine.Dockerfile | 7 ++++--- debian/control | 1 + meshtasticd.spec.rpkg | 1 + 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index a2c56fad9..f546d4cfd 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,7 +1,7 @@ # trunk-ignore-all(terrascan/AC_DOCKER_0002): Known terrascan issue # trunk-ignore-all(hadolint/DL3008): Do not pin apt package versions # trunk-ignore-all(hadolint/DL3013): Do not pin pip package versions -FROM mcr.microsoft.com/devcontainers/cpp:2-debian-12 +FROM mcr.microsoft.com/devcontainers/cpp:2-debian-13 USER root diff --git a/alpine.Dockerfile b/alpine.Dockerfile index 0469874e4..bdee57d79 100644 --- a/alpine.Dockerfile +++ b/alpine.Dockerfile @@ -8,7 +8,8 @@ ARG PIO_ENV=native ENV PIP_ROOT_USER_ACTION=ignore RUN apk --no-cache add \ - bash g++ libstdc++-dev linux-headers zip git ca-certificates libgpiod-dev yaml-cpp-dev bluez-dev \ + bash g++ libstdc++-dev linux-headers zip git ca-certificates libbsd-dev \ + libgpiod-dev yaml-cpp-dev bluez-dev \ libusb-dev i2c-tools-dev libuv-dev openssl-dev pkgconf argp-standalone \ libx11-dev libinput-dev libxkbcommon-dev \ && rm -rf /var/cache/apk/* \ @@ -40,8 +41,8 @@ LABEL org.opencontainers.image.title="Meshtastic" \ USER root RUN apk --no-cache add \ - shadow libstdc++ libgpiod yaml-cpp libusb i2c-tools libuv \ - libx11 libinput libxkbcommon \ + shadow libstdc++ libbsd libgpiod yaml-cpp libusb \ + i2c-tools libuv libx11 libinput libxkbcommon \ && rm -rf /var/cache/apk/* \ && mkdir -p /var/lib/meshtasticd \ && mkdir -p /etc/meshtasticd/config.d \ diff --git a/debian/control b/debian/control index 761383a5c..679a444c9 100644 --- a/debian/control +++ b/debian/control @@ -3,6 +3,7 @@ Section: misc Priority: optional Maintainer: Austin Lane Build-Depends: debhelper-compat (= 13), + libc6-dev (>= 2.38) | libbsd-dev, lsb-release, tar, gzip, diff --git a/meshtasticd.spec.rpkg b/meshtasticd.spec.rpkg index eb4ab5ae7..f3b2e6960 100644 --- a/meshtasticd.spec.rpkg +++ b/meshtasticd.spec.rpkg @@ -33,6 +33,7 @@ BuildRequires: python3dist(grpcio[protobuf]) BuildRequires: python3dist(grpcio-tools) BuildRequires: git-core BuildRequires: gcc-c++ +BuildRequires: (glibc-devel >= 2.38) or pkgconfig(libbsd-overlay) BuildRequires: pkgconfig(yaml-cpp) BuildRequires: pkgconfig(libgpiod) BuildRequires: pkgconfig(bluez) From ec5e79585bf2f39381425716991431aa4846539d Mon Sep 17 00:00:00 2001 From: Austin Date: Mon, 17 Nov 2025 17:40:19 -0500 Subject: [PATCH 623/691] Don't trust the AI! (#8659) Read the docs instead --- meshtasticd.spec.rpkg | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/meshtasticd.spec.rpkg b/meshtasticd.spec.rpkg index f3b2e6960..227f81574 100644 --- a/meshtasticd.spec.rpkg +++ b/meshtasticd.spec.rpkg @@ -33,7 +33,6 @@ BuildRequires: python3dist(grpcio[protobuf]) BuildRequires: python3dist(grpcio-tools) BuildRequires: git-core BuildRequires: gcc-c++ -BuildRequires: (glibc-devel >= 2.38) or pkgconfig(libbsd-overlay) BuildRequires: pkgconfig(yaml-cpp) BuildRequires: pkgconfig(libgpiod) BuildRequires: pkgconfig(bluez) @@ -50,6 +49,13 @@ BuildRequires: pkgconfig(x11) BuildRequires: pkgconfig(libinput) BuildRequires: pkgconfig(xkbcommon-x11) +# libbsd is needed on older Fedora/RHEL to provide 'strlcpy' +%if 0%{?fedora} >= 41 || 0%{?rhel} >= 9 +BuildRequires: glibc-devel >= 2.38 +%else +BuildRequires: pkgconfig(libbsd-overlay) +%endif + Requires: systemd-udev %description From 79a91578b7da810bf0a60167344af2749638ebef Mon Sep 17 00:00:00 2001 From: omgbebebe Date: Tue, 18 Nov 2025 02:54:02 +0400 Subject: [PATCH 624/691] mqtt: do not try to send packets when it disconnected (#8658) --- src/mqtt/MQTT.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp index f9f114039..babbcfd7c 100644 --- a/src/mqtt/MQTT.cpp +++ b/src/mqtt/MQTT.cpp @@ -51,6 +51,7 @@ constexpr int reconnectMax = 5; static uint8_t bytes[meshtastic_MqttClientProxyMessage_size + 30]; // 12 for channel name and 16 for nodeid static bool isMqttServerAddressPrivate = false; +static bool isConnected = false; inline void onReceiveProto(char *topic, byte *payload, size_t length) { @@ -320,8 +321,10 @@ bool connectPubSub(const PubSubConfig &config, PubSubClient &pubSub, Client &cli std::string nodeId = nodeDB->getNodeId(); const bool connected = pubSub.connect(nodeId.c_str(), config.mqttUsername, config.mqttPassword); if (connected) { + isConnected = true; LOG_INFO("MQTT connected"); } else { + isConnected = false; LOG_WARN("Failed to connect to MQTT server"); } return connected; @@ -507,6 +510,7 @@ bool MQTT::publish(const char *topic, const uint8_t *payload, size_t length, boo void MQTT::reconnect() { + isConnected = false; if (wantsLink()) { if (moduleConfig.mqtt.proxy_to_client_enabled) { LOG_INFO("MQTT connect via client proxy instead"); @@ -534,7 +538,7 @@ void MQTT::reconnect() runASAP = true; reconnectCount = 0; isMqttServerAddressPrivate = isPrivateIpAddress(clientConnection->remoteIP()); - + isConnected = true; publishNodeInfo(); sendSubscriptions(); } else { @@ -688,7 +692,7 @@ void MQTT::publishNodeInfo() } void MQTT::publishQueuedMessages() { - if (mqttQueue.isEmpty()) + if (mqttQueue.isEmpty() || !isConnected) return; LOG_DEBUG("Publish enqueued MQTT message"); @@ -895,4 +899,4 @@ void MQTT::perhapsReportToMap() // Update the last report time last_report_to_map = millis(); -} \ No newline at end of file +} From 501c296e758c624f7037c5010574f54ef50da129 Mon Sep 17 00:00:00 2001 From: Austin Date: Mon, 17 Nov 2025 18:39:52 -0500 Subject: [PATCH 625/691] Linux: Fix silly EPEL9 mistake (#8660) --- meshtasticd.spec.rpkg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meshtasticd.spec.rpkg b/meshtasticd.spec.rpkg index 227f81574..e2da172c3 100644 --- a/meshtasticd.spec.rpkg +++ b/meshtasticd.spec.rpkg @@ -50,7 +50,7 @@ BuildRequires: pkgconfig(libinput) BuildRequires: pkgconfig(xkbcommon-x11) # libbsd is needed on older Fedora/RHEL to provide 'strlcpy' -%if 0%{?fedora} >= 41 || 0%{?rhel} >= 9 +%if 0%{?fedora} >= 39 || 0%{?rhel} >= 10 BuildRequires: glibc-devel >= 2.38 %else BuildRequires: pkgconfig(libbsd-overlay) From a8d1a90e16ddc1defa29e1ac33d2808e1f069eee Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Mon, 17 Nov 2025 19:58:12 -0600 Subject: [PATCH 626/691] Fix ble rssi crash (#8661) * Fix BLE crash occuring when trying to get RSSI from Android with a bad connection handle * Cleanup --- src/nimble/NimbleBluetooth.cpp | 40 +++++++++++++++++++++++++--------- 1 file changed, 30 insertions(+), 10 deletions(-) diff --git a/src/nimble/NimbleBluetooth.cpp b/src/nimble/NimbleBluetooth.cpp index 6238031f6..b09777de3 100644 --- a/src/nimble/NimbleBluetooth.cpp +++ b/src/nimble/NimbleBluetooth.cpp @@ -20,13 +20,14 @@ #include "PowerStatus.h" #endif -#if defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C6) #if defined(CONFIG_NIMBLE_CPP_IDF) #include "host/ble_gap.h" #else #include "nimble/nimble/host/include/host/ble_gap.h" #endif +#if defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C6) + namespace { constexpr uint16_t kPreferredBleMtu = 517; @@ -776,16 +777,35 @@ bool NimbleBluetooth::isConnected() int NimbleBluetooth::getRssi() { - if (bleServer && isConnected()) { - auto service = bleServer->getServiceByUUID(MESH_SERVICE_UUID); - uint16_t handle = service->getHandle(); -#ifdef NIMBLE_TWO - return NimBLEDevice::getClientByHandle(handle)->getRssi(); -#else - return NimBLEDevice::getClientByID(handle)->getRssi(); -#endif +#if defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C6) + if (!bleServer || !isConnected()) { + return 0; // No active BLE connection } - return 0; // FIXME figure out where to source this + + uint16_t connHandle = nimbleBluetoothConnHandle.load(); + + if (connHandle == BLE_HS_CONN_HANDLE_NONE) { + const auto peers = bleServer->getPeerDevices(); + if (!peers.empty()) { + connHandle = peers.front(); + nimbleBluetoothConnHandle = connHandle; + } + } + + if (connHandle == BLE_HS_CONN_HANDLE_NONE) { + return 0; // Connection handle not available yet + } + + int8_t rssi = 0; + const int rc = ble_gap_conn_rssi(connHandle, &rssi); + + if (rc == 0) { + return rssi; + } + LOG_DEBUG("BLE RSSI read failed, rc=%d", rc); +#endif + + return 0; } void NimbleBluetooth::setup() From c34f94abda72e2bd850d5bc65f4a5da1220fbee3 Mon Sep 17 00:00:00 2001 From: omgbebebe Date: Tue, 18 Nov 2025 02:54:02 +0400 Subject: [PATCH 627/691] mqtt: do not try to send packets when it disconnected (#8658) --- src/mqtt/MQTT.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp index f9f114039..babbcfd7c 100644 --- a/src/mqtt/MQTT.cpp +++ b/src/mqtt/MQTT.cpp @@ -51,6 +51,7 @@ constexpr int reconnectMax = 5; static uint8_t bytes[meshtastic_MqttClientProxyMessage_size + 30]; // 12 for channel name and 16 for nodeid static bool isMqttServerAddressPrivate = false; +static bool isConnected = false; inline void onReceiveProto(char *topic, byte *payload, size_t length) { @@ -320,8 +321,10 @@ bool connectPubSub(const PubSubConfig &config, PubSubClient &pubSub, Client &cli std::string nodeId = nodeDB->getNodeId(); const bool connected = pubSub.connect(nodeId.c_str(), config.mqttUsername, config.mqttPassword); if (connected) { + isConnected = true; LOG_INFO("MQTT connected"); } else { + isConnected = false; LOG_WARN("Failed to connect to MQTT server"); } return connected; @@ -507,6 +510,7 @@ bool MQTT::publish(const char *topic, const uint8_t *payload, size_t length, boo void MQTT::reconnect() { + isConnected = false; if (wantsLink()) { if (moduleConfig.mqtt.proxy_to_client_enabled) { LOG_INFO("MQTT connect via client proxy instead"); @@ -534,7 +538,7 @@ void MQTT::reconnect() runASAP = true; reconnectCount = 0; isMqttServerAddressPrivate = isPrivateIpAddress(clientConnection->remoteIP()); - + isConnected = true; publishNodeInfo(); sendSubscriptions(); } else { @@ -688,7 +692,7 @@ void MQTT::publishNodeInfo() } void MQTT::publishQueuedMessages() { - if (mqttQueue.isEmpty()) + if (mqttQueue.isEmpty() || !isConnected) return; LOG_DEBUG("Publish enqueued MQTT message"); @@ -895,4 +899,4 @@ void MQTT::perhapsReportToMap() // Update the last report time last_report_to_map = millis(); -} \ No newline at end of file +} From ef4cb2abfb6cc23dacb860f342e7a8c677387a25 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Mon, 17 Nov 2025 20:05:42 -0600 Subject: [PATCH 628/691] If we're not client proxying and we are not connected, don't publish --- src/mqtt/MQTT.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp index babbcfd7c..ad35e152a 100644 --- a/src/mqtt/MQTT.cpp +++ b/src/mqtt/MQTT.cpp @@ -692,7 +692,10 @@ void MQTT::publishNodeInfo() } void MQTT::publishQueuedMessages() { - if (mqttQueue.isEmpty() || !isConnected) + if (mqttQueue.isEmpty()) + return; + + if (!moduleConfig.mqtt.proxy_to_client_enabled && !isConnected) return; LOG_DEBUG("Publish enqueued MQTT message"); From 6c09cf9d3d233b13cf12d86a81c612f130cf9926 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Tue, 18 Nov 2025 01:04:44 -0600 Subject: [PATCH 629/691] Gps reset detect (#8302) * Properly format timestamp in log message * Better formatting of GPS_DEBUG logging in gps probe * Reset GPS after serial speed change, and look for magic string to identify chip * Add UC6580 to boot message detection, for Heltec Tracker * Add L76K detect from boot string, for Heltec-v4 * Slightly more useful GPS debugging * Back out detection of L76K via startup messages. * Ignore PIN_GPS_RESET = -1 and rename passive_detect array. --------- Co-authored-by: Tom Fifield --- src/gps/GPS.cpp | 35 +++++++++++++++++++++++++++-------- src/gps/RTC.cpp | 4 ++-- 2 files changed, 29 insertions(+), 10 deletions(-) diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index 6c6cdba6d..0404ae5f8 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -240,6 +240,9 @@ GPS_RESPONSE GPS::getACK(const char *message, uint32_t waitMillis) buffer[bytesRead] = b; bytesRead++; if ((bytesRead == 767) || (b == '\r')) { +#ifdef GPS_DEBUG + LOG_DEBUG(debugmsg.c_str()); +#endif if (strnstr((char *)buffer, message, bytesRead) != nullptr) { #ifdef GPS_DEBUG LOG_DEBUG("Found: %s", message); // Log the found message @@ -247,9 +250,6 @@ GPS_RESPONSE GPS::getACK(const char *message, uint32_t waitMillis) return GNSS_RESPONSE_OK; } else { bytesRead = 0; -#ifdef GPS_DEBUG - LOG_DEBUG(debugmsg.c_str()); -#endif } } } @@ -1275,6 +1275,24 @@ GnssModel_t GPS::probe(int serialSpeed) memset(&ublox_info, 0, sizeof(ublox_info)); delay(100); +#if defined(PIN_GPS_RESET) && PIN_GPS_RESET != -1 + digitalWrite(PIN_GPS_RESET, GPS_RESET_MODE); // assert for 10ms + delay(10); + digitalWrite(PIN_GPS_RESET, !GPS_RESET_MODE); + + // attempt to detect the chip based on boot messages + std::vector passive_detect = { + {"AG3335", "$PAIR021,AG3335", GNSS_MODEL_AG3335}, + {"AG3352", "$PAIR021,AG3352", GNSS_MODEL_AG3352}, + {"RYS3520", "$PAIR021,REYAX_RYS3520_V2", GNSS_MODEL_AG3352}, + {"UC6580", "UC6580", GNSS_MODEL_UC6580}, + // as L76K is sort of a last ditch effort, we won't attempt to detect it by startup messages for now. + /*{"L76K", "SW=URANUS", GNSS_MODEL_MTK}*/}; + GnssModel_t detectedDriver = getProbeResponse(500, passive_detect, serialSpeed); + if (detectedDriver != GNSS_MODEL_UNKNOWN) { + return detectedDriver; + } +#endif // Close all NMEA sentences, valid for L76K, ATGM336H (and likely other AT6558 devices) _serial_gps->write("$PCAS03,0,0,0,0,0,0,0,0,0,0,,,0,0*02\r\n"); delay(20); @@ -1473,12 +1491,12 @@ GnssModel_t GPS::getProbeResponse(unsigned long timeout, const std::vector= 2 && response[responseLen - 2] == '\r' && response[responseLen - 1] == '\n')) { -#ifdef GPS_DEBUG - LOG_DEBUG(response); -#endif // check if we can see our chips for (const auto &chipInfo : responseMap) { if (strstr(response, chipInfo.detectionString.c_str()) != nullptr) { +#ifdef GPS_DEBUG + LOG_DEBUG(response); +#endif LOG_INFO("%s detected", chipInfo.chipName.c_str()); delete[] response; // Cleanup before return return chipInfo.driver; @@ -1486,6 +1504,9 @@ GnssModel_t GPS::getProbeResponse(unsigned long timeout, const std::vector= 2 && response[responseLen - 2] == '\r' && response[responseLen - 1] == '\n') { +#ifdef GPS_DEBUG + LOG_DEBUG(response); +#endif // Reset the response buffer for the next potential message responseLen = 0; response[0] = '\0'; @@ -1572,8 +1593,6 @@ GPS *GPS::createGps() #ifdef PIN_GPS_RESET pinMode(PIN_GPS_RESET, OUTPUT); - digitalWrite(PIN_GPS_RESET, GPS_RESET_MODE); // assert for 10ms - delay(10); digitalWrite(PIN_GPS_RESET, !GPS_RESET_MODE); #endif diff --git a/src/gps/RTC.cpp b/src/gps/RTC.cpp index 665a9aaa3..692f3c2d2 100644 --- a/src/gps/RTC.cpp +++ b/src/gps/RTC.cpp @@ -310,7 +310,7 @@ RTCSetResult perhapsSetRTC(RTCQuality q, struct tm &t) #ifdef BUILD_EPOCH if (tv.tv_sec < BUILD_EPOCH) { if (Throttle::isWithinTimespanMs(lastTimeValidationWarning, TIME_VALIDATION_WARNING_INTERVAL_MS) == false) { - LOG_WARN("Ignore time (%ld) before build epoch (%ld)!", printableEpoch, BUILD_EPOCH); + LOG_WARN("Ignore time (%lu) before build epoch (%lu)!", printableEpoch, BUILD_EPOCH); lastTimeValidationWarning = millis(); } return RTCSetResultInvalidTime; @@ -319,7 +319,7 @@ RTCSetResult perhapsSetRTC(RTCQuality q, struct tm &t) // Calculate max allowed time safely to avoid overflow in logging uint64_t maxAllowedTime = (uint64_t)BUILD_EPOCH + FORTY_YEARS; uint32_t maxAllowedPrintable = (maxAllowedTime > UINT32_MAX) ? UINT32_MAX : (uint32_t)maxAllowedTime; - LOG_WARN("Ignore time (%ld) too far in the future (build epoch: %ld, max allowed: %ld)!", printableEpoch, + LOG_WARN("Ignore time (%lu) too far in the future (build epoch: %lu, max allowed: %lu)!", printableEpoch, (uint32_t)BUILD_EPOCH, maxAllowedPrintable); lastTimeValidationWarning = millis(); } From edcdb2dcb22b804bc1e297ee1ada26bc3a9d5761 Mon Sep 17 00:00:00 2001 From: "Jason B. Cox" Date: Mon, 10 Nov 2025 18:19:15 -0800 Subject: [PATCH 630/691] Cleanup unnecessary global dereferencing in CryptoEngine (#8611) Co-authored-by: Ben Meadors --- src/mesh/CryptoEngine.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/mesh/CryptoEngine.cpp b/src/mesh/CryptoEngine.cpp index 82d0a9f57..9ca16878d 100644 --- a/src/mesh/CryptoEngine.cpp +++ b/src/mesh/CryptoEngine.cpp @@ -92,10 +92,10 @@ bool CryptoEngine::encryptCurve25519(uint32_t toNode, uint32_t fromNode, meshtas LOG_DEBUG("Node %d or their public_key not found", toNode); return false; } - if (!crypto->setDHPublicKey(remotePublic.bytes)) { + if (!setDHPublicKey(remotePublic.bytes)) { return false; } - crypto->hash(shared_key, 32); + hash(shared_key, 32); initNonce(fromNode, packetNum, extraNonceTmp); // Calculate the shared secret with the destination node and encrypt @@ -134,10 +134,10 @@ bool CryptoEngine::decryptCurve25519(uint32_t fromNode, meshtastic_UserLite_publ } // Calculate the shared secret with the sending node and decrypt - if (!crypto->setDHPublicKey(remotePublic.bytes)) { + if (!setDHPublicKey(remotePublic.bytes)) { return false; } - crypto->hash(shared_key, 32); + hash(shared_key, 32); initNonce(fromNode, packetNum, extraNonce); printBytes("Attempt decrypt with nonce: ", nonce, 13); From 59864dd09d7ccba3234e95de19087081507161fd Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Fri, 7 Nov 2025 15:03:56 -0600 Subject: [PATCH 631/691] Add API types, state, and log message in Debug screen. Added persistent "Connected" icon (#8576) * Add API types, state, and log message in Debug screen * un-goober the API state tracking * Set the SerialConsole api_type * Add api_type for Ethernet * Remove API state debugging code * Update wording for client connection states * Improve string width for smaller screen devices * Reserve space on navigation bar to fit link indicator * Add persistent Connected icon to screen * Connect System frame to ensure text doesn't overflow --------- Co-authored-by: Ben Meadors Co-authored-by: Jason P Co-authored-by: HarukiToreda <116696711+HarukiToreda@users.noreply.github.com> --- src/SerialConsole.cpp | 1 + src/graphics/SharedUIDisplay.cpp | 39 ++++++++++++++ src/graphics/SharedUIDisplay.h | 3 ++ src/graphics/draw/ClockRenderer.cpp | 3 ++ src/graphics/draw/DebugRenderer.cpp | 47 ++++++++++++++--- src/graphics/draw/MessageRenderer.cpp | 2 + src/graphics/draw/NodeListRenderer.cpp | 1 + src/graphics/draw/UIRenderer.cpp | 52 ++++++++++++++++++- src/graphics/images.h | 4 ++ src/mesh/MeshService.h | 12 +++++ src/mesh/PhoneAPI.cpp | 25 +++++++++ src/mesh/PhoneAPI.h | 12 +++++ src/mesh/api/PacketAPI.cpp | 1 + src/mesh/api/WiFiServerAPI.cpp | 1 + src/mesh/api/ethServerAPI.cpp | 1 + src/mesh/http/ContentHandler.h | 2 +- src/mesh/raspihttp/PiWebServer.h | 2 +- src/modules/SerialModule.cpp | 15 ++++-- .../Telemetry/EnvironmentTelemetry.cpp | 1 + src/modules/Telemetry/PowerTelemetry.cpp | 1 + src/modules/esp32/PaxcounterModule.cpp | 1 + src/nimble/NimbleBluetooth.cpp | 2 +- src/platform/nrf52/NRF52Bluetooth.cpp | 3 ++ 23 files changed, 218 insertions(+), 13 deletions(-) diff --git a/src/SerialConsole.cpp b/src/SerialConsole.cpp index fad0fb92f..dd2acb599 100644 --- a/src/SerialConsole.cpp +++ b/src/SerialConsole.cpp @@ -50,6 +50,7 @@ void consolePrintf(const char *format, ...) SerialConsole::SerialConsole() : StreamAPI(&Port), RedirectablePrint(&Port), concurrency::OSThread("SerialConsole") { + api_type = TYPE_SERIAL; assert(!console); console = this; canWrite = false; // We don't send packets to our port until it has talked to us first diff --git a/src/graphics/SharedUIDisplay.cpp b/src/graphics/SharedUIDisplay.cpp index 8e1299f51..1645789a7 100644 --- a/src/graphics/SharedUIDisplay.cpp +++ b/src/graphics/SharedUIDisplay.cpp @@ -1,6 +1,8 @@ #include "configuration.h" #if HAS_SCREEN +#include "MeshService.h" #include "RTC.h" +#include "draw/NodeListRenderer.h" #include "graphics/ScreenFonts.h" #include "graphics/SharedUIDisplay.h" #include "graphics/draw/UIRenderer.h" @@ -398,6 +400,43 @@ const int *getTextPositions(OLEDDisplay *display) return textPositions; } +// ************************* +// * Common Footer Drawing * +// ************************* +void drawCommonFooter(OLEDDisplay *display, int16_t x, int16_t y) +{ + bool drawConnectionState = false; + if (service->api_state == service->STATE_BLE || service->api_state == service->STATE_WIFI || + service->api_state == service->STATE_SERIAL || service->api_state == service->STATE_PACKET || + service->api_state == service->STATE_HTTP || service->api_state == service->STATE_ETH) { + drawConnectionState = true; + } + + if (drawConnectionState) { + if (isHighResolution) { + const int scale = 2; + const int bytesPerRow = (connection_icon_width + 7) / 8; + int iconX = 0; + int iconY = SCREEN_HEIGHT - (connection_icon_height * 2); + + for (int yy = 0; yy < connection_icon_height; ++yy) { + const uint8_t *rowPtr = connection_icon + yy * bytesPerRow; + for (int xx = 0; xx < connection_icon_width; ++xx) { + const uint8_t byteVal = pgm_read_byte(rowPtr + (xx >> 3)); + const uint8_t bitMask = 1U << (xx & 7); // XBM is LSB-first + if (byteVal & bitMask) { + display->fillRect(iconX + xx * scale, iconY + yy * scale, scale, scale); + } + } + } + + } else { + display->drawXbm(0, SCREEN_HEIGHT - connection_icon_height, connection_icon_width, connection_icon_height, + connection_icon); + } + } +} + bool isAllowedPunctuation(char c) { const std::string allowed = ".,!?;:-_()[]{}'\"@#$/\\&+=%~^ "; diff --git a/src/graphics/SharedUIDisplay.h b/src/graphics/SharedUIDisplay.h index e1a7c6383..b51dfea36 100644 --- a/src/graphics/SharedUIDisplay.h +++ b/src/graphics/SharedUIDisplay.h @@ -52,6 +52,9 @@ void drawRoundedHighlight(OLEDDisplay *display, int16_t x, int16_t y, int16_t w, void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *titleStr = "", bool force_no_invert = false, bool show_date = false); +// Shared battery/time/mail header +void drawCommonFooter(OLEDDisplay *display, int16_t x, int16_t y); + const int *getTextPositions(OLEDDisplay *display); bool isAllowedPunctuation(char c); diff --git a/src/graphics/draw/ClockRenderer.cpp b/src/graphics/draw/ClockRenderer.cpp index 751db8d88..97417571b 100644 --- a/src/graphics/draw/ClockRenderer.cpp +++ b/src/graphics/draw/ClockRenderer.cpp @@ -302,6 +302,8 @@ void drawDigitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int1 display->drawString(startingHourMinuteTextX + timeStringWidth - xOffset, (display->getHeight() - hourMinuteTextY) - yOffset, secondString); #endif + + graphics::drawCommonFooter(display, x, y); } void drawBluetoothConnectedIcon(OLEDDisplay *display, int16_t x, int16_t y) @@ -516,6 +518,7 @@ void drawAnalogClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16 display->drawLine(centerX, centerY, secondX, secondY); #endif } + graphics::drawCommonFooter(display, x, y); } } // namespace ClockRenderer diff --git a/src/graphics/draw/DebugRenderer.cpp b/src/graphics/draw/DebugRenderer.cpp index 60abd661e..d098fa304 100644 --- a/src/graphics/draw/DebugRenderer.cpp +++ b/src/graphics/draw/DebugRenderer.cpp @@ -3,6 +3,7 @@ #include "../Screen.h" #include "DebugRenderer.h" #include "FSCommon.h" +#include "MeshService.h" #include "NodeDB.h" #include "Throttle.h" #include "UIRenderer.h" @@ -223,6 +224,8 @@ void drawFrameWiFi(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, i display->drawString(x, getTextPositions(display)[line++], "URL: http://meshtastic.local"); + graphics::drawCommonFooter(display, x, y); + /* Display a heartbeat pixel that blinks every time the frame is redrawn */ #ifdef SHOW_REDRAWS if (heartbeat) @@ -503,6 +506,7 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, display->drawString(starting_position + chUtil_x + chutil_bar_width + extraoffset, getTextPositions(display)[line++], chUtilPercentage); #endif + graphics::drawCommonFooter(display, x, y); } // **************************** @@ -642,10 +646,9 @@ void drawSystemScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x int textWidth = display->getStringWidth(appversionstr); int nameX = (SCREEN_WIDTH - textWidth) / 2; - display->drawString(nameX, getTextPositions(display)[line], appversionstr); -#if !defined(M5STACK_UNITC6L) - if (SCREEN_HEIGHT > 64 || (SCREEN_HEIGHT <= 64 && line < 4)) { // Only show uptime if the screen can show it - line += 1; + display->drawString(nameX, getTextPositions(display)[line++], appversionstr); + + if (SCREEN_HEIGHT > 64 || (SCREEN_HEIGHT <= 64 && line <= 5)) { // Only show uptime if the screen can show it char uptimeStr[32] = ""; uint32_t uptime = millis() / 1000; uint32_t days = uptime / 86400; @@ -660,9 +663,41 @@ void drawSystemScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x snprintf(uptimeStr, sizeof(uptimeStr), " Uptime: %um", mins); textWidth = display->getStringWidth(uptimeStr); nameX = (SCREEN_WIDTH - textWidth) / 2; - display->drawString(nameX, getTextPositions(display)[line], uptimeStr); + display->drawString(nameX, getTextPositions(display)[line++], uptimeStr); } -#endif + + if (SCREEN_HEIGHT > 64 || (SCREEN_HEIGHT <= 64 && line <= 5)) { // Only show API state if the screen can show it + char api_state[32] = ""; + const char *clientWord = nullptr; + + // Determine if narrow or wide screen + if (isHighResolution) { + clientWord = "Client"; + } else { + clientWord = "App"; + } + snprintf(api_state, sizeof(api_state), "No %ss Connected", clientWord); + + if (service->api_state == service->STATE_BLE) { + snprintf(api_state, sizeof(api_state), "%s Connected (BLE)", clientWord); + } else if (service->api_state == service->STATE_WIFI) { + snprintf(api_state, sizeof(api_state), "%s Connected (WiFi)", clientWord); + } else if (service->api_state == service->STATE_SERIAL) { + snprintf(api_state, sizeof(api_state), "%s Connected (Serial)", clientWord); + } else if (service->api_state == service->STATE_PACKET) { + snprintf(api_state, sizeof(api_state), "%s Connected (Internal)", clientWord); + } else if (service->api_state == service->STATE_HTTP) { + snprintf(api_state, sizeof(api_state), "%s Connected (HTTP)", clientWord); + } else if (service->api_state == service->STATE_ETH) { + snprintf(api_state, sizeof(api_state), "%s Connected (Ethernet)", clientWord); + } + if (api_state[0] != '\0') { + display->drawString((SCREEN_WIDTH - display->getStringWidth(api_state)) / 2, getTextPositions(display)[line++], + api_state); + } + } + + graphics::drawCommonFooter(display, x, y); } // **************************** diff --git a/src/graphics/draw/MessageRenderer.cpp b/src/graphics/draw/MessageRenderer.cpp index 6971826de..da6ec7abc 100644 --- a/src/graphics/draw/MessageRenderer.cpp +++ b/src/graphics/draw/MessageRenderer.cpp @@ -213,6 +213,7 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16 #else display->drawString(center_text, getTextPositions(display)[2], messageString); #endif + graphics::drawCommonFooter(display, x, y); return; } @@ -423,6 +424,7 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16 // Draw header at the end to sort out overlapping elements graphics::drawCommonHeader(display, x, y, titleStr); #endif + graphics::drawCommonFooter(display, x, y); } std::vector generateLines(OLEDDisplay *display, const char *headerStr, const char *messageBuf, int textWidth) diff --git a/src/graphics/draw/NodeListRenderer.cpp b/src/graphics/draw/NodeListRenderer.cpp index 16bfa7066..2eed58d93 100644 --- a/src/graphics/draw/NodeListRenderer.cpp +++ b/src/graphics/draw/NodeListRenderer.cpp @@ -492,6 +492,7 @@ void drawNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t #endif const int scrollStartY = y + 3; drawScrollbar(display, visibleNodeRows, totalEntries, scrollIndex, 2, scrollStartY); + graphics::drawCommonFooter(display, x, y); } // ============================= diff --git a/src/graphics/draw/UIRenderer.cpp b/src/graphics/draw/UIRenderer.cpp index 1ff183779..538c32842 100644 --- a/src/graphics/draw/UIRenderer.cpp +++ b/src/graphics/draw/UIRenderer.cpp @@ -552,6 +552,7 @@ void UIRenderer::drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *st // else show nothing } #endif + graphics::drawCommonFooter(display, x, y); } // **************************** @@ -771,6 +772,7 @@ void UIRenderer::drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *sta display->drawString(nameX, getTextPositions(display)[line++], shortnameble); } #endif + graphics::drawCommonFooter(display, x, y); } // Start Functions to write date/time to the screen @@ -1183,6 +1185,7 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU } #endif #endif // HAS_GPS + graphics::drawCommonFooter(display, x, y); } #ifdef USERPREFS_OEM_TEXT @@ -1267,7 +1270,13 @@ void UIRenderer::drawNavigationBar(OLEDDisplay *display, OLEDDisplayUiState *sta if (totalIcons == 0) return; - const size_t iconsPerPage = (SCREEN_WIDTH + spacing) / (iconSize + spacing); + const int navPadding = isHighResolution ? 24 : 12; // padding per side + + int usableWidth = SCREEN_WIDTH - (navPadding * 2); + if (usableWidth < iconSize) + usableWidth = iconSize; + + const size_t iconsPerPage = usableWidth / (iconSize + spacing); const size_t currentPage = currentFrame / iconsPerPage; const size_t pageStart = currentPage * iconsPerPage; const size_t pageEnd = min(pageStart + iconsPerPage, totalIcons); @@ -1338,6 +1347,47 @@ void UIRenderer::drawNavigationBar(OLEDDisplay *display, OLEDDisplayUiState *sta display->setColor(WHITE); } } + + // Compact arrow drawer + auto drawArrow = [&](bool rightSide) { + display->setColor(WHITE); + + const int offset = isHighResolution ? 3 : 1; + const int halfH = rectHeight / 2; + + const int top = (y - 2) + (rectHeight - halfH) / 2; + const int bottom = top + halfH - 1; + const int midY = top + (halfH / 2); + + const int maxW = 4; + + // Determine left X coordinate + int baseX = rightSide ? (rectX + rectWidth + offset) : // right arrow + (rectX - offset - 1); // left arrow + + for (int yy = top; yy <= bottom; yy++) { + int dist = abs(yy - midY); + int lineW = maxW - (dist * maxW / (halfH / 2)); + if (lineW < 1) + lineW = 1; + + if (rightSide) { + display->drawHorizontalLine(baseX, yy, lineW); + } else { + display->drawHorizontalLine(baseX - lineW + 1, yy, lineW); + } + } + }; + // Right arrow + if (pageEnd < totalIcons) { + drawArrow(true); + } + + // Left arrow + if (pageStart > 0) { + drawArrow(false); + } + // Knock the corners off the square display->setColor(BLACK); display->drawRect(rectX, y - 2, 1, 1); diff --git a/src/graphics/images.h b/src/graphics/images.h index b5010b116..8670d78d9 100644 --- a/src/graphics/images.h +++ b/src/graphics/images.h @@ -360,6 +360,10 @@ const uint8_t chirpy_hirez[] = { #define chirpy_small_image_height 8 const uint8_t chirpy_small[] = {0x7f, 0x41, 0x55, 0x55, 0x55, 0x55, 0x41, 0x7f}; +#define connection_icon_width 7 +#define connection_icon_height 5 +const uint8_t connection_icon[] = {0x36, 0x41, 0x5D, 0x41, 0x36}; + #ifdef M5STACK_UNITC6L #include "img/icon_small.xbm" #else diff --git a/src/mesh/MeshService.h b/src/mesh/MeshService.h index 66d9d9679..71fb544a0 100644 --- a/src/mesh/MeshService.h +++ b/src/mesh/MeshService.h @@ -79,6 +79,18 @@ class MeshService uint32_t oldFromNum = 0; public: + enum APIState { + STATE_DISCONNECTED, // Initial state, no API is connected + STATE_BLE, + STATE_WIFI, + STATE_SERIAL, + STATE_PACKET, + STATE_HTTP, + STATE_ETH + }; + + APIState api_state = STATE_DISCONNECTED; + static bool isTextPayload(const meshtastic_MeshPacket *p) { if (moduleConfig.range_test.enabled && p->decoded.portnum == meshtastic_PortNum_RANGE_TEST_APP) { diff --git a/src/mesh/PhoneAPI.cpp b/src/mesh/PhoneAPI.cpp index d1e342c80..9050ee89d 100644 --- a/src/mesh/PhoneAPI.cpp +++ b/src/mesh/PhoneAPI.cpp @@ -87,6 +87,18 @@ void PhoneAPI::handleStartConfig() void PhoneAPI::close() { LOG_DEBUG("PhoneAPI::close()"); + if (service->api_state == service->STATE_BLE && api_type == TYPE_BLE) + service->api_state = service->STATE_DISCONNECTED; + else if (service->api_state == service->STATE_WIFI && api_type == TYPE_WIFI) + service->api_state = service->STATE_DISCONNECTED; + else if (service->api_state == service->STATE_SERIAL && api_type == TYPE_SERIAL) + service->api_state = service->STATE_DISCONNECTED; + else if (service->api_state == service->STATE_PACKET && api_type == TYPE_PACKET) + service->api_state = service->STATE_DISCONNECTED; + else if (service->api_state == service->STATE_HTTP && api_type == TYPE_HTTP) + service->api_state = service->STATE_DISCONNECTED; + else if (service->api_state == service->STATE_ETH && api_type == TYPE_ETH) + service->api_state = service->STATE_DISCONNECTED; if (state != STATE_SEND_NOTHING) { state = STATE_SEND_NOTHING; @@ -578,6 +590,19 @@ void PhoneAPI::sendConfigComplete() fromRadioScratch.config_complete_id = config_nonce; config_nonce = 0; state = STATE_SEND_PACKETS; + if (api_type == TYPE_BLE) { + service->api_state = service->STATE_BLE; + } else if (api_type == TYPE_WIFI) { + service->api_state = service->STATE_WIFI; + } else if (api_type == TYPE_SERIAL) { + service->api_state = service->STATE_SERIAL; + } else if (api_type == TYPE_PACKET) { + service->api_state = service->STATE_PACKET; + } else if (api_type == TYPE_HTTP) { + service->api_state = service->STATE_HTTP; + } else if (api_type == TYPE_ETH) { + service->api_state = service->STATE_ETH; + } // Allow subclasses to know we've entered steady-state so they can lower power consumption onConfigComplete(); diff --git a/src/mesh/PhoneAPI.h b/src/mesh/PhoneAPI.h index d6682684f..7f79b5792 100644 --- a/src/mesh/PhoneAPI.h +++ b/src/mesh/PhoneAPI.h @@ -167,6 +167,18 @@ class PhoneAPI /// begin a new connection void handleStartConfig(); + enum APIType { + TYPE_NONE, // Initial state, don't send anything until the client starts asking for config + TYPE_BLE, + TYPE_WIFI, + TYPE_SERIAL, + TYPE_PACKET, + TYPE_HTTP, + TYPE_ETH + }; + + APIType api_type = TYPE_NONE; + private: void releasePhonePacket(); diff --git a/src/mesh/api/PacketAPI.cpp b/src/mesh/api/PacketAPI.cpp index ab380d696..f4d5de540 100644 --- a/src/mesh/api/PacketAPI.cpp +++ b/src/mesh/api/PacketAPI.cpp @@ -19,6 +19,7 @@ PacketAPI *PacketAPI::create(PacketServer *_server) PacketAPI::PacketAPI(PacketServer *_server) : concurrency::OSThread("PacketAPI"), isConnected(false), programmingMode(false), server(_server) { + api_type = TYPE_PACKET; } int32_t PacketAPI::runOnce() diff --git a/src/mesh/api/WiFiServerAPI.cpp b/src/mesh/api/WiFiServerAPI.cpp index b19194f78..4d729f5c7 100644 --- a/src/mesh/api/WiFiServerAPI.cpp +++ b/src/mesh/api/WiFiServerAPI.cpp @@ -25,6 +25,7 @@ void deInitApiServer() WiFiServerAPI::WiFiServerAPI(WiFiClient &_client) : ServerAPI(_client) { + api_type = TYPE_WIFI; LOG_INFO("Incoming wifi connection"); } diff --git a/src/mesh/api/ethServerAPI.cpp b/src/mesh/api/ethServerAPI.cpp index 0ccf92df7..10ff06df2 100644 --- a/src/mesh/api/ethServerAPI.cpp +++ b/src/mesh/api/ethServerAPI.cpp @@ -20,6 +20,7 @@ void initApiServer(int port) ethServerAPI::ethServerAPI(EthernetClient &_client) : ServerAPI(_client) { LOG_INFO("Incoming ethernet connection"); + api_type = TYPE_ETH; } ethServerPort::ethServerPort(int port) : APIServerPort(port) {} diff --git a/src/mesh/http/ContentHandler.h b/src/mesh/http/ContentHandler.h index 2066a6d57..91cad3359 100644 --- a/src/mesh/http/ContentHandler.h +++ b/src/mesh/http/ContentHandler.h @@ -26,7 +26,7 @@ class HttpAPI : public PhoneAPI { public: - // Nothing here yet + HttpAPI() { api_type = TYPE_HTTP; } private: // Nothing here yet diff --git a/src/mesh/raspihttp/PiWebServer.h b/src/mesh/raspihttp/PiWebServer.h index b45348cf3..5a4adedaa 100644 --- a/src/mesh/raspihttp/PiWebServer.h +++ b/src/mesh/raspihttp/PiWebServer.h @@ -27,7 +27,7 @@ class HttpAPI : public PhoneAPI { public: - // Nothing here yet + HttpAPI() { api_type = TYPE_HTTP; } private: // Nothing here yet diff --git a/src/modules/SerialModule.cpp b/src/modules/SerialModule.cpp index a9ec8f6a8..575e9fa96 100644 --- a/src/modules/SerialModule.cpp +++ b/src/modules/SerialModule.cpp @@ -65,13 +65,22 @@ SerialModuleRadio *serialModuleRadio; #if defined(TTGO_T_ECHO) || defined(CANARYONE) || defined(MESHLINK) || defined(ELECROW_ThinkNode_M1) || \ defined(ELECROW_ThinkNode_M5) || defined(HELTEC_MESH_SOLAR) || defined(T_ECHO_LITE) -SerialModule::SerialModule() : StreamAPI(&Serial), concurrency::OSThread("Serial") {} +SerialModule::SerialModule() : StreamAPI(&Serial), concurrency::OSThread("Serial") +{ + api_type = TYPE_SERIAL; +} static Print *serialPrint = &Serial; #elif defined(CONFIG_IDF_TARGET_ESP32C6) || defined(RAK3172) || defined(EBYTE_E77_MBL) -SerialModule::SerialModule() : StreamAPI(&Serial1), concurrency::OSThread("Serial") {} +SerialModule::SerialModule() : StreamAPI(&Serial1), concurrency::OSThread("Serial") +{ + api_type = TYPE_SERIAL; +} static Print *serialPrint = &Serial1; #else -SerialModule::SerialModule() : StreamAPI(&Serial2), concurrency::OSThread("Serial") {} +SerialModule::SerialModule() : StreamAPI(&Serial2), concurrency::OSThread("Serial") +{ + api_type = TYPE_SERIAL; +} static Print *serialPrint = &Serial2; #endif diff --git a/src/modules/Telemetry/EnvironmentTelemetry.cpp b/src/modules/Telemetry/EnvironmentTelemetry.cpp index 2337af808..e8c8172cb 100644 --- a/src/modules/Telemetry/EnvironmentTelemetry.cpp +++ b/src/modules/Telemetry/EnvironmentTelemetry.cpp @@ -510,6 +510,7 @@ void EnvironmentTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiSt currentY += rowHeight; } + graphics::drawCommonFooter(display, x, y); } #endif diff --git a/src/modules/Telemetry/PowerTelemetry.cpp b/src/modules/Telemetry/PowerTelemetry.cpp index e69ee3931..29dd1def8 100644 --- a/src/modules/Telemetry/PowerTelemetry.cpp +++ b/src/modules/Telemetry/PowerTelemetry.cpp @@ -165,6 +165,7 @@ void PowerTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *s if (m.has_ch3_voltage || m.has_ch3_current) { drawLine("Ch3", m.ch3_voltage, m.ch3_current); } + graphics::drawCommonFooter(display, x, y); } #endif diff --git a/src/modules/esp32/PaxcounterModule.cpp b/src/modules/esp32/PaxcounterModule.cpp index 8b1fc5302..9c25177bc 100644 --- a/src/modules/esp32/PaxcounterModule.cpp +++ b/src/modules/esp32/PaxcounterModule.cpp @@ -141,6 +141,7 @@ void PaxcounterModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state display->drawStringf(display->getWidth() / 2 + x, graphics::getTextPositions(display)[line++], buffer, "WiFi: %d\nBLE: %d\nUptime: %ds", count_from_libpax.wifi_count, count_from_libpax.ble_count, millis() / 1000); + graphics::drawCommonFooter(display, x, y); } #endif // HAS_SCREEN diff --git a/src/nimble/NimbleBluetooth.cpp b/src/nimble/NimbleBluetooth.cpp index b09777de3..69da25884 100644 --- a/src/nimble/NimbleBluetooth.cpp +++ b/src/nimble/NimbleBluetooth.cpp @@ -119,7 +119,7 @@ class BluetoothPhoneAPI : public PhoneAPI, public concurrency::OSThread */ public: - BluetoothPhoneAPI() : concurrency::OSThread("NimbleBluetooth") {} + BluetoothPhoneAPI() : concurrency::OSThread("NimbleBluetooth") { api_type = TYPE_BLE; } /* Packets from phone (BLE onWrite callback) */ std::mutex fromPhoneMutex; diff --git a/src/platform/nrf52/NRF52Bluetooth.cpp b/src/platform/nrf52/NRF52Bluetooth.cpp index 79eef8f76..4f7fb4776 100644 --- a/src/platform/nrf52/NRF52Bluetooth.cpp +++ b/src/platform/nrf52/NRF52Bluetooth.cpp @@ -48,6 +48,9 @@ class BluetoothPhoneAPI : public PhoneAPI /// Check the current underlying physical link to see if the client is currently connected virtual bool checkIsConnected() override { return Bluefruit.connected(connectionHandle); } + + public: + BluetoothPhoneAPI() { api_type = TYPE_BLE; } }; static BluetoothPhoneAPI *bluetoothPhoneAPI; From 15257b017c570771c058ebec2fa6986d87bf1c76 Mon Sep 17 00:00:00 2001 From: Quency-D <55523105+Quency-D@users.noreply.github.com> Date: Sat, 8 Nov 2025 20:47:24 +0800 Subject: [PATCH 632/691] Add the Heltec v4 expansion box. (#8539) * Add the Heltec v4 expansion box. * Change heltec-v4-oled to heltec-v4. * Add touchscreen to I2C scanning. * Add reset and busy pins to the ST7789. * Ignore the touch interrupt pin and extend the sleep time to 1 hour. * Remove the default sleep function. --------- Co-authored-by: Ben Meadors --- src/graphics/TFTDisplay.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/graphics/TFTDisplay.cpp b/src/graphics/TFTDisplay.cpp index 87593b0d4..f7906c013 100644 --- a/src/graphics/TFTDisplay.cpp +++ b/src/graphics/TFTDisplay.cpp @@ -483,6 +483,8 @@ class LGFX : public lgfx::LGFX_Device lgfx::Touch_FT5x06 _touch_instance; #elif defined(HELTEC_V4_TFT) lgfx::TOUCH_CHSC6X _touch_instance; +#elif defined(HELTEC_V4_TFT) + lgfx::TOUCH_CHSC6X _touch_instance; #else lgfx::Touch_GT911 _touch_instance; #endif From 85ea22ac38e9c44b646f9c75013b02b9974b2db9 Mon Sep 17 00:00:00 2001 From: Tom <116762865+NomDeTom@users.noreply.github.com> Date: Sun, 9 Nov 2025 15:24:03 +0000 Subject: [PATCH 633/691] Update to Pro-micro variants (#8600) * Update to Pro-micro variants Schematic updated Xtal variant removed Extra module added to list Extra explanation added to readme. * Fix markdown formatting in readme.md * Fix formatting in readme.md for RF switch section --------- Co-authored-by: Tom <116762865+Nestpebble@users.noreply.github.com> --- ...Schematic_Pro-Micro_Pinouts 2024-12-14.pdf | 9836 ----- .../Schematic_Pro-Micro_Pinouts.pdf | 33346 ++++++++++++++++ .../diy/nrf52_promicro_diy_tcxo/readme.md | 15 +- .../diy/nrf52_promicro_diy_tcxo/variant.h | 1 + .../nrf52_promicro_diy_xtal/platformio.ini | 12 - .../diy/nrf52_promicro_diy_xtal/variant.cpp | 38 - .../diy/nrf52_promicro_diy_xtal/variant.h | 154 - 7 files changed, 33360 insertions(+), 10042 deletions(-) delete mode 100644 variants/nrf52840/diy/nrf52_promicro_diy_tcxo/Schematic_Pro-Micro_Pinouts 2024-12-14.pdf create mode 100644 variants/nrf52840/diy/nrf52_promicro_diy_tcxo/Schematic_Pro-Micro_Pinouts.pdf delete mode 100644 variants/nrf52840/diy/nrf52_promicro_diy_xtal/platformio.ini delete mode 100644 variants/nrf52840/diy/nrf52_promicro_diy_xtal/variant.cpp delete mode 100644 variants/nrf52840/diy/nrf52_promicro_diy_xtal/variant.h diff --git a/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/Schematic_Pro-Micro_Pinouts 2024-12-14.pdf b/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/Schematic_Pro-Micro_Pinouts 2024-12-14.pdf deleted file mode 100644 index de87af141..000000000 --- a/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/Schematic_Pro-Micro_Pinouts 2024-12-14.pdf +++ /dev/null @@ -1,9836 +0,0 @@ -%PDF-1.4 -%߬ -3 0 obj -<> -endobj -4 0 obj -<< -/Length 102720 ->> -stream -0.20 w -0 G -2 J -0 j -100 M -1.00 g -[] 0 d -0.00 826.80 1169.00 -826.80 re -f -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -216.000 806.000 m -216.000 816.000 l -216.000 20.000 m -216.000 10.000 l -412.000 806.000 m -412.000 816.000 l -412.000 20.000 m -412.000 10.000 l -608.000 806.000 m -608.000 816.000 l -608.000 20.000 m -608.000 10.000 l -804.000 806.000 m -804.000 816.000 l -804.000 20.000 m -804.000 10.000 l -1000.000 806.000 m -1000.000 816.000 l -1000.000 20.000 m -1000.000 10.000 l -20.000 610.000 m -10.000 610.000 l -1149.000 610.000 m -1159.000 610.000 l -20.000 414.000 m -10.000 414.000 l -1149.000 414.000 m -1159.000 414.000 l -20.000 218.000 m -10.000 218.000 l -1149.000 218.000 m -1159.000 218.000 l -20.000 22.000 m -10.000 22.000 l -1149.000 22.000 m -1159.000 22.000 l -S -10.00 w -BT -/F1 9 Tf -9.00 TL -0.533 0.000 0.000 rg -11.50 708.00 Td -(A) Tj -ET -10.00 w -BT -/F1 9 Tf -9.00 TL -0.533 0.000 0.000 rg -1150.50 708.00 Td -(A) Tj -ET -10.00 w -BT -/F1 9 Tf -9.00 TL -0.533 0.000 0.000 rg -11.50 512.00 Td -(B) Tj -ET -10.00 w -BT -/F1 9 Tf -9.00 TL -0.533 0.000 0.000 rg -1150.50 512.00 Td -(B) Tj -ET -10.00 w -BT -/F1 9 Tf -9.00 TL -0.533 0.000 0.000 rg -11.50 316.00 Td -(C) Tj -ET -10.00 w -BT -/F1 9 Tf -9.00 TL -0.533 0.000 0.000 rg -1150.50 316.00 Td -(C) Tj -ET -10.00 w -BT -/F1 9 Tf -9.00 TL -0.533 0.000 0.000 rg -11.50 120.00 Td -(D) Tj -ET -10.00 w -BT -/F1 9 Tf -9.00 TL -0.533 0.000 0.000 rg -1150.50 120.00 Td -(D) Tj -ET -10.00 w -BT -/F1 9 Tf -9.00 TL -0.533 0.000 0.000 rg -118.00 807.50 Td -(1) Tj -ET -10.00 w -BT -/F1 9 Tf -9.00 TL -0.533 0.000 0.000 rg -118.00 11.50 Td -(1) Tj -ET -10.00 w -BT -/F1 9 Tf -9.00 TL -0.533 0.000 0.000 rg -314.00 807.50 Td -(2) Tj -ET -10.00 w -BT -/F1 9 Tf -9.00 TL -0.533 0.000 0.000 rg -314.00 11.50 Td -(2) Tj -ET -10.00 w -BT -/F1 9 Tf -9.00 TL -0.533 0.000 0.000 rg -510.00 807.50 Td -(3) Tj -ET -10.00 w -BT -/F1 9 Tf -9.00 TL -0.533 0.000 0.000 rg -510.00 11.50 Td -(3) Tj -ET -10.00 w -BT -/F1 9 Tf -9.00 TL -0.533 0.000 0.000 rg -706.00 807.50 Td -(4) Tj -ET -10.00 w -BT -/F1 9 Tf -9.00 TL -0.533 0.000 0.000 rg -706.00 11.50 Td -(4) Tj -ET -10.00 w -BT -/F1 9 Tf -9.00 TL -0.533 0.000 0.000 rg -902.00 807.50 Td -(5) Tj -ET -10.00 w -BT -/F1 9 Tf -9.00 TL -0.533 0.000 0.000 rg -902.00 11.50 Td -(5) Tj -ET -2 J -0 j -100 M -1.00 w -0.53 0.00 0.00 RG -[] 0 d -20.00 806.00 1129.00 -786.00 re -S -2 J -0 j -100 M -1.00 w -0.53 0.00 0.00 RG -[] 0 d -10.00 816.00 1149.00 -806.00 re -S -2 J -0 j -100 M -1.00 w -0.53 0.00 0.00 RG -[] 0 d -705.00 100.00 444.00 -80.00 re -S -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -0.00 g -[] 0 d -705.100 60.750 m -1148.630 60.750 l -S -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -0.00 g -[] 0 d -809.630 40.750 m -1148.630 40.750 l -S -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -0.00 g -[] 0 d -1069.610 99.930 m -1069.630 60.750 l -S -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -0.00 g -[] 0 d -1069.630 60.750 m -1069.630 40.750 l -S -10.00 w -BT -/F1 11 Tf -11.00 TL -0.533 0.000 0.000 rg -710.00 87.00 Td -(TITLE:) Tj -ET -10.00 w -BT -/F1 13 Tf -13.00 TL -0.000 0.000 1.000 rg -767.62 74.41 Td -(Pro-Micro Pinouts) Tj -ET -10.00 w -BT -/F1 11 Tf -11.00 TL -0.533 0.000 0.000 rg -1074.62 73.75 Td -(REV:) Tj -ET -10.00 w -BT -/F1 12 Tf -12.00 TL -0.000 0.000 1.000 rg -1112.62 73.75 Td -(1.0) Tj -ET -10.00 w -BT -/F1 11 Tf -11.00 TL -0.533 0.000 0.000 rg -814.62 25.00 Td -(Date:) Tj -ET -10.00 w -BT -/F1 12 Tf -12.00 TL -0.000 0.000 1.000 rg -861.62 24.52 Td -(2024-12-17) Tj -ET -10.00 w -BT -/F1 11 Tf -11.00 TL -0.533 0.000 0.000 rg -1073.62 45.00 Td -(Sheet:) Tj -ET -10.00 w -BT -/F1 12 Tf -12.00 TL -0.000 0.000 1.000 rg -1118.62 44.52 Td -(1/1) Tj -ET -10.00 w -BT -/F1 11 Tf -11.00 TL -0.533 0.000 0.000 rg -953.62 24.75 Td -(Drawn By:) Tj -ET -10.00 w -BT -/F1 11 Tf -11.00 TL -0.533 0.000 0.000 rg -814.62 46.75 Td -(Company:) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -0.00 g -[] 0 d -809.630 60.750 m -809.630 20.750 l -S -1 J -1 j -1.00 w -0.20 0.80 0.20 RG -[] 0 d -541.000 684.000 m -549.000 676.000 l -549.000 684.000 m -541.000 676.000 l -S -1 J -1 j -1.00 w -0.00 0.53 0.00 RG -0.00 g -[] 0 d -665.000 680.000 m -635.000 680.000 l -S -1 J -1 j -1.00 w -0.20 0.80 0.20 RG -[] 0 d -986.000 649.000 m -994.000 641.000 l -994.000 649.000 m -986.000 641.000 l -S -1 J -1 j -1.00 w -0.20 0.80 0.20 RG -[] 0 d -541.000 614.000 m -549.000 606.000 l -549.000 614.000 m -541.000 606.000 l -S -1 J -1 j -1.00 w -0.20 0.80 0.20 RG -[] 0 d -541.000 624.000 m -549.000 616.000 l -549.000 624.000 m -541.000 616.000 l -S -1 J -1 j -1.00 w -0.20 0.80 0.20 RG -[] 0 d -541.000 644.000 m -549.000 636.000 l -549.000 644.000 m -541.000 636.000 l -S -1 J -1 j -1.00 w -0.20 0.80 0.20 RG -[] 0 d -631.000 634.000 m -639.000 626.000 l -639.000 634.000 m -631.000 626.000 l -S -BT -/F2 12 Tf -12.00 TL -0.000 g -1.00 -0.00 0.00 1.00 804.82 611.00 Tm -(VCC) Tj -ET -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -830.000 615.000 m -840.000 615.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -830.000 610.000 m -830.000 620.000 l -S -BT -/F2 12 Tf -12.00 TL -0.000 g -1.00 -0.00 0.00 1.00 797.50 621.00 Tm -(GND) Tj -ET -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -830.000 625.000 m -840.000 625.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -830.000 634.000 m -830.000 616.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -828.000 631.000 m -828.000 619.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -826.000 628.000 m -826.000 622.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -824.000 626.000 m -824.000 624.000 l -S -BT -/F2 12 Tf -12.00 TL -0.000 g -1.00 -0.00 0.00 1.00 1006.50 651.00 Tm -(GND) Tj -ET -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -1000.000 655.000 m -990.000 655.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -1000.000 646.000 m -1000.000 664.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -1002.000 649.000 m -1002.000 661.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -1004.000 652.000 m -1004.000 658.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -1006.000 654.000 m -1006.000 656.000 l -S -BT -/F2 11 Tf -11.00 TL -0.000 0.000 1.000 rg -1.00 -0.00 0.00 1.00 1011.94 671.15 Tm -(IRQ) Tj -ET -1 J -1 j -1.00 w -0.00 0.00 1.00 RG -0.00 g -[] 0 d -990.000 675.000 m -995.000 680.000 l -1010.000 680.000 l -1010.000 670.000 l -995.000 670.000 l -990.000 675.000 l -S -BT -/F2 11 Tf -11.00 TL -0.000 0.000 1.000 rg -1.00 -0.00 0.00 1.00 797.25 651.75 Tm -(SCK) Tj -ET -1 J -1 j -1.00 w -0.00 0.00 1.00 RG -0.00 g -[] 0 d -840.000 655.000 m -835.000 650.000 l -820.000 650.000 l -820.000 660.000 l -835.000 660.000 l -840.000 655.000 l -S -BT -/F2 11 Tf -11.00 TL -0.000 0.000 1.000 rg -1.00 -0.00 0.00 1.00 804.69 631.45 Tm -(CS) Tj -ET -1 J -1 j -1.00 w -0.00 0.00 1.00 RG -0.00 g -[] 0 d -840.000 635.000 m -835.000 630.000 l -820.000 630.000 l -820.000 640.000 l -835.000 640.000 l -840.000 635.000 l -S -BT -/F2 11 Tf -11.00 TL -0.000 0.000 1.000 rg -1.00 -0.00 0.00 1.00 791.48 661.75 Tm -(MOSI) Tj -ET -1 J -1 j -1.00 w -0.00 0.00 1.00 RG -0.00 g -[] 0 d -840.000 665.000 m -835.000 660.000 l -820.000 660.000 l -820.000 670.000 l -835.000 670.000 l -840.000 665.000 l -S -BT -/F2 11 Tf -11.00 TL -0.000 0.000 1.000 rg -1.00 -0.00 0.00 1.00 791.41 671.75 Tm -(MISO) Tj -ET -1 J -1 j -1.00 w -0.00 0.00 1.00 RG -0.00 g -[] 0 d -840.000 675.000 m -835.000 670.000 l -820.000 670.000 l -820.000 680.000 l -835.000 680.000 l -840.000 675.000 l -S -BT -/F2 11 Tf -11.00 TL -0.000 0.000 1.000 rg -1.00 -0.00 0.00 1.00 790.06 641.57 Tm -(NRST) Tj -ET -1 J -1 j -1.00 w -0.00 0.00 1.00 RG -0.00 g -[] 0 d -840.000 645.000 m -835.000 640.000 l -820.000 640.000 l -820.000 650.000 l -835.000 650.000 l -840.000 645.000 l -S -BT -/F2 11 Tf -11.00 TL -0.000 0.000 1.000 rg -1.00 -0.00 0.00 1.00 1011.92 661.15 Tm -(BUSY) Tj -ET -1 J -1 j -1.00 w -0.00 0.00 1.00 RG -0.00 g -[] 0 d -990.000 665.000 m -995.000 670.000 l -1010.000 670.000 l -1010.000 660.000 l -995.000 660.000 l -990.000 665.000 l -S -10.00 w -BT -/F3 9 Tf -9.00 TL -0.000 0.000 0.502 rg -908.96 708.00 Td -(Seeed-wio-SX1262) Tj -ET -10.00 w -BT -/F3 9 Tf -9.00 TL -0.000 0.000 0.502 rg -908.96 717.00 Td -(SEEED_WIO-SX1262) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -862.00 682.00 Td -(RF_SW) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -849.28 686.00 Td -(1) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -840.000 685.000 m -860.000 685.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -862.00 672.00 Td -(MISO) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -849.28 676.00 Td -(2) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -840.000 675.000 m -860.000 675.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -862.00 662.00 Td -(MOSI) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -849.28 666.00 Td -(3) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -840.000 665.000 m -860.000 665.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -862.00 652.00 Td -(CLK) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -849.28 656.00 Td -(4) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -840.000 655.000 m -860.000 655.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -862.00 642.00 Td -(RST) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -849.28 646.00 Td -(5) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -840.000 645.000 m -860.000 645.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -862.00 632.00 Td -(NSS) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -849.28 636.00 Td -(6) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -840.000 635.000 m -860.000 635.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -862.00 622.00 Td -(GND) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -849.28 626.00 Td -(7) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -840.000 625.000 m -860.000 625.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -862.00 612.00 Td -(VCC) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -849.28 616.00 Td -(8) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -840.000 615.000 m -860.000 615.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -949.58 642.00 Td -(ANT) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -975.00 646.00 Td -(9) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -990.000 645.000 m -970.000 645.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -947.36 652.00 Td -(GND) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -975.00 656.00 Td -(10) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -990.000 655.000 m -970.000 655.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -943.57 662.00 Td -(BUSY) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -975.00 666.00 Td -(11) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -990.000 665.000 m -970.000 665.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -944.49 672.00 Td -(DIO1) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -975.00 676.00 Td -(12) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -990.000 675.000 m -970.000 675.000 l -S -2 J -0 j -100 M -1.00 w -0.00 G -[] 0 d -860.00 705.00 110.00 -110.00 re -S -1.00 w -0.00 G -[] 0 d -965.00 615.00 m 965.00 623.28 958.28 630.00 950.00 630.00 c -941.72 630.00 935.00 623.28 935.00 615.00 c -935.00 606.72 941.72 600.00 950.00 600.00 c -958.28 600.00 965.00 606.72 965.00 615.00 c -S -2 J -0 j -100 M -1.00 w -0.00 G -[] 0 d -930.00 635.00 40.00 -40.00 re -S -1 J -1 j -1.00 w -0.00 0.53 0.00 RG -0.00 g -[] 0 d -535.000 660.000 m -545.000 660.000 l -S -BT -/F2 12 Tf -12.00 TL -0.000 g -1.00 -0.00 0.00 1.00 499.82 655.93 Tm -(VCC) Tj -ET -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -525.000 660.000 m -535.000 660.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -525.000 655.000 m -525.000 665.000 l -S -1 J -1 j -1.00 w -0.00 0.53 0.00 RG -0.00 g -[] 0 d -520.000 670.000 m -545.000 670.000 l -S -BT -/F2 12 Tf -12.00 TL -0.000 g -1.00 -0.00 0.00 1.00 652.00 699.09 Tm -(GND) Tj -ET -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -665.000 690.000 m -665.000 680.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -674.000 690.000 m -656.000 690.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -671.000 692.000 m -659.000 692.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -668.000 694.000 m -662.000 694.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -666.000 696.000 m -664.000 696.000 l -S -BT -/F2 12 Tf -12.00 TL -0.000 g -1.00 -0.00 0.00 1.00 507.00 689.09 Tm -(GND) Tj -ET -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -520.000 680.000 m -520.000 670.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -529.000 680.000 m -511.000 680.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -526.000 682.000 m -514.000 682.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -523.000 684.000 m -517.000 684.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -521.000 686.000 m -519.000 686.000 l -S -BT -/F2 12 Tf -12.00 TL -0.000 g -1.00 -0.00 0.00 1.00 622.00 582.76 Tm -(GND) Tj -ET -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -635.000 600.000 m -635.000 610.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -626.000 600.000 m -644.000 600.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -629.000 598.000 m -641.000 598.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -632.000 596.000 m -638.000 596.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -634.000 594.000 m -636.000 594.000 l -S -BT -/F2 11 Tf -11.00 TL -0.000 0.000 1.000 rg -1.00 -0.00 0.00 1.00 656.92 616.15 Tm -(BUSY) Tj -ET -1 J -1 j -1.00 w -0.00 0.00 1.00 RG -0.00 g -[] 0 d -635.000 620.000 m -640.000 625.000 l -655.000 625.000 l -655.000 615.000 l -640.000 615.000 l -635.000 620.000 l -S -BT -/F2 11 Tf -11.00 TL -0.000 0.000 1.000 rg -1.00 -0.00 0.00 1.00 656.55 636.15 Tm -(SCK) Tj -ET -1 J -1 j -1.00 w -0.00 0.00 1.00 RG -0.00 g -[] 0 d -635.000 640.000 m -640.000 645.000 l -655.000 645.000 l -655.000 635.000 l -640.000 635.000 l -635.000 640.000 l -S -BT -/F2 11 Tf -11.00 TL -0.000 0.000 1.000 rg -1.00 -0.00 0.00 1.00 656.92 646.15 Tm -(MISO) Tj -ET -1 J -1 j -1.00 w -0.00 0.00 1.00 RG -0.00 g -[] 0 d -635.000 650.000 m -640.000 655.000 l -655.000 655.000 l -655.000 645.000 l -640.000 645.000 l -635.000 650.000 l -S -BT -/F2 11 Tf -11.00 TL -0.000 0.000 1.000 rg -1.00 -0.00 0.00 1.00 656.85 656.15 Tm -(MOSI) Tj -ET -1 J -1 j -1.00 w -0.00 0.00 1.00 RG -0.00 g -[] 0 d -635.000 660.000 m -640.000 665.000 l -655.000 665.000 l -655.000 655.000 l -640.000 655.000 l -635.000 660.000 l -S -BT -/F2 11 Tf -11.00 TL -0.000 0.000 1.000 rg -1.00 -0.00 0.00 1.00 657.00 666.45 Tm -(CS) Tj -ET -1 J -1 j -1.00 w -0.00 0.00 1.00 RG -0.00 g -[] 0 d -635.000 670.000 m -640.000 675.000 l -655.000 675.000 l -655.000 665.000 l -640.000 665.000 l -635.000 670.000 l -S -BT -/F2 11 Tf -11.00 TL -0.000 0.000 1.000 rg -1.00 -0.00 0.00 1.00 504.19 626.40 Tm -(IRQ) Tj -ET -1 J -1 j -1.00 w -0.00 0.00 1.00 RG -0.00 g -[] 0 d -545.000 630.000 m -540.000 625.000 l -525.000 625.000 l -525.000 635.000 l -540.000 635.000 l -545.000 630.000 l -S -BT -/F2 11 Tf -11.00 TL -0.000 0.000 1.000 rg -1.00 -0.00 0.00 1.00 495.06 646.57 Tm -(NRST) Tj -ET -1 J -1 j -1.00 w -0.00 0.00 1.00 RG -0.00 g -[] 0 d -545.000 650.000 m -540.000 645.000 l -525.000 645.000 l -525.000 655.000 l -540.000 655.000 l -545.000 650.000 l -S -10.00 w -BT -/F3 9 Tf -9.00 TL -0.000 0.000 0.502 rg -584.95 693.33 Td -(RA-01SH) Tj -ET -10.00 w -BT -/F3 9 Tf -9.00 TL -0.000 0.000 0.502 rg -584.95 702.33 Td -(HT-RA62) Tj -ET -2 J -0 j -100 M -1.00 w -0.53 0.00 0.00 RG -[] 0 d -557.000 690.000 m -623.000 690.000 l -624.105 690.000 625.000 689.105 625.000 688.000 c -625.000 602.000 l -625.000 600.895 623.895 600.000 623.000 600.000 c -557.000 600.000 l -555.895 600.000 555.000 601.105 555.000 602.000 c -555.000 688.000 l -555.000 689.105 556.105 690.000 557.000 690.000 c -S -1.00 w -0.53 0.00 0.00 RG -0.53 0.00 0.00 rg -[] 0 d -561.50 685.00 m 561.50 685.83 560.83 686.50 560.00 686.50 c -559.17 686.50 558.50 685.83 558.50 685.00 c -558.50 684.17 559.17 683.50 560.00 683.50 c -560.83 683.50 561.50 684.17 561.50 685.00 c -B -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -558.70 676.00 Td -(ANT) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -548.78 681.00 Td -(1) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -545.000 680.000 m -555.000 680.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 g -558.70 666.00 Td -(GND) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 g -548.78 671.00 Td -(2) Tj -ET -1 J -1 j -1.00 w -0.00 G -[] 0 d -545.000 670.000 m -555.000 670.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -558.70 656.00 Td -(3.3V) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -548.78 661.00 Td -(3) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -545.000 660.000 m -555.000 660.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -558.70 646.00 Td -(RESET) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -548.78 651.00 Td -(4) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -545.000 650.000 m -555.000 650.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -558.70 636.00 Td -(TXEN) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -548.78 641.00 Td -(5) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -545.000 640.000 m -555.000 640.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -558.70 626.00 Td -(DIO1) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -548.78 631.00 Td -(6) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -545.000 630.000 m -555.000 630.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -558.70 616.00 Td -(DIO2) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -548.78 621.00 Td -(7) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -545.000 620.000 m -555.000 620.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -558.70 606.00 Td -(DIO3) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -548.78 611.00 Td -(8) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -545.000 610.000 m -555.000 610.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 g -600.66 606.00 Td -(GND) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 g -625.50 611.00 Td -(9) Tj -ET -1 J -1 j -1.00 w -0.00 G -[] 0 d -635.000 610.000 m -625.000 610.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -596.87 616.00 Td -(BUSY) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -625.50 621.00 Td -(10) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -635.000 620.000 m -625.000 620.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -596.46 626.00 Td -(RXEN) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -625.50 631.00 Td -(11) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -635.000 630.000 m -625.000 630.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -602.64 636.00 Td -(SCK) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -625.50 641.00 Td -(12) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -635.000 640.000 m -625.000 640.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -596.71 646.00 Td -(MISO) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -625.50 651.00 Td -(13) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -635.000 650.000 m -625.000 650.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -596.71 656.00 Td -(MOSI) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -625.50 661.00 Td -(14) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -635.000 660.000 m -625.000 660.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -602.27 666.00 Td -(NSS) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -625.50 671.00 Td -(15) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -635.000 670.000 m -625.000 670.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 g -600.66 676.00 Td -(GND) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 g -625.50 681.00 Td -(16) Tj -ET -1 J -1 j -1.00 w -0.00 G -[] 0 d -635.000 680.000 m -625.000 680.000 l -S -10.00 w -BT -/F3 9 Tf -9.00 TL -0.000 0.000 0.502 rg -153.95 479.05 Td -(AMC-U_FL) Tj -ET -10.00 w -BT -/F3 9 Tf -9.00 TL -0.000 0.000 0.502 rg -153.95 488.16 Td -(U6) Tj -ET -2 J -0 j -100 M -1.00 w -0.53 0.00 0.00 RG -[] 0 d -165.00 465.00 20.00 -20.00 re -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -0.00 1.00 -1.00 0.00 174.00 434.29 Tm -(1) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -175.000 425.000 m -175.000 445.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -159.28 456.00 Td -(2) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -155.000 455.000 m -165.000 455.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -185.00 456.00 Td -(3) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -195.000 455.000 m -185.000 455.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -0.00 1.00 -1.00 0.00 174.00 465.00 Tm -(4) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -175.000 475.000 m -175.000 465.000 l -S -1.00 w -0.53 0.00 0.00 RG -[] 0 d -177.00 455.00 m 177.00 456.10 176.10 457.00 175.00 457.00 c -173.90 457.00 173.00 456.10 173.00 455.00 c -173.00 453.90 173.90 453.00 175.00 453.00 c -176.10 453.00 177.00 453.90 177.00 455.00 c -S -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -0.00 g -[] 0 d -175.000 453.000 m -175.000 445.000 l -S -1 J -1 j -1.00 w -0.20 0.80 0.20 RG -[] 0 d -506.000 394.000 m -514.000 386.000 l -514.000 394.000 m -506.000 386.000 l -S -1 J -1 j -1.00 w -0.20 0.80 0.20 RG -[] 0 d -646.000 394.000 m -654.000 386.000 l -654.000 394.000 m -646.000 386.000 l -S -BT -/F2 12 Tf -12.00 TL -0.000 g -1.00 -0.00 0.00 1.00 666.50 415.93 Tm -(GND) Tj -ET -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -660.000 420.000 m -650.000 420.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -660.000 411.000 m -660.000 429.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -662.000 414.000 m -662.000 426.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -664.000 417.000 m -664.000 423.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -666.000 419.000 m -666.000 421.000 l -S -1 J -1 j -1.00 w -0.00 0.53 0.00 RG -0.00 g -[] 0 d -650.000 430.000 m -650.000 400.000 l -S -1 J -1 j -1.00 w -0.20 0.80 0.20 RG -[] 0 d -796.000 389.000 m -804.000 381.000 l -804.000 389.000 m -796.000 381.000 l -S -10.00 w -BT -/F3 9 Tf -9.00 TL -0.000 0.000 0.502 rg -573.96 503.33 Td -(E22-900M22S) Tj -ET -10.00 w -BT -/F3 9 Tf -9.00 TL -0.000 0.000 0.502 rg -573.96 512.33 Td -(E22-900M22S) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -532.00 377.00 Td -(GND) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -513.57 381.00 Td -(22) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -510.000 380.000 m -530.000 380.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -532.00 387.00 Td -(ANT) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -513.57 391.00 Td -(21) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -510.000 390.000 m -530.000 390.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -532.00 397.00 Td -(GND) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -513.57 401.00 Td -(20) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -510.000 400.000 m -530.000 400.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -532.00 417.00 Td -(NSS) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -513.57 421.00 Td -(19) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -510.000 420.000 m -530.000 420.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -532.00 427.00 Td -(SCK) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -513.57 431.00 Td -(18) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -510.000 430.000 m -530.000 430.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -532.00 437.00 Td -(MOSI) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -513.57 441.00 Td -(17) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -510.000 440.000 m -530.000 440.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -532.00 447.00 Td -(MISO) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -513.57 451.00 Td -(16) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -510.000 450.000 m -530.000 450.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -532.00 457.00 Td -(NRST) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -513.57 461.00 Td -(15) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -510.000 460.000 m -530.000 460.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -532.00 467.00 Td -(BUSY) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -513.57 471.00 Td -(14) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -510.000 470.000 m -530.000 470.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -532.00 477.00 Td -(DIO1) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -513.57 481.00 Td -(13) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -510.000 480.000 m -530.000 480.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -532.00 487.00 Td -(GND) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -513.57 491.00 Td -(12) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -510.000 490.000 m -530.000 490.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -607.36 487.00 Td -(GND) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -635.00 491.00 Td -(11) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -650.000 490.000 m -630.000 490.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -607.36 477.00 Td -(GND) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -635.00 481.00 Td -(10) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -650.000 480.000 m -630.000 480.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -609.29 467.00 Td -(VCC) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -635.00 471.00 Td -(9) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -650.000 470.000 m -630.000 470.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -604.49 457.00 Td -(DIO2) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -635.00 461.00 Td -(8) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -650.000 460.000 m -630.000 460.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -603.87 447.00 Td -(TXEN) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -635.00 451.00 Td -(7) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -650.000 450.000 m -630.000 450.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -603.16 437.00 Td -(RXEN) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -635.00 441.00 Td -(6) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -650.000 440.000 m -630.000 440.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -607.36 427.00 Td -(GND) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -635.00 431.00 Td -(5) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -650.000 430.000 m -630.000 430.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -607.36 417.00 Td -(GND) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -635.00 421.00 Td -(4) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -650.000 420.000 m -630.000 420.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -607.36 397.00 Td -(GND) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -635.00 401.00 Td -(3) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -650.000 400.000 m -630.000 400.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -607.36 387.00 Td -(GND) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -635.00 391.00 Td -(2) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -650.000 390.000 m -630.000 390.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -607.36 377.00 Td -(GND) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -635.00 381.00 Td -(1) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -650.000 380.000 m -630.000 380.000 l -S -2 J -0 j -100 M -1.00 w -0.53 0.00 0.00 RG -[] 0 d -532.000 500.000 m -628.000 500.000 l -629.105 500.000 630.000 499.105 630.000 498.000 c -630.000 362.000 l -630.000 360.895 628.895 360.000 628.000 360.000 c -532.000 360.000 l -530.895 360.000 530.000 361.105 530.000 362.000 c -530.000 498.000 l -530.000 499.105 531.105 500.000 532.000 500.000 c -S -10.00 w -BT -/F3 9 Tf -9.00 TL -0.000 0.000 0.502 rg -863.96 498.33 Td -(E22-900M30S) Tj -ET -10.00 w -BT -/F3 9 Tf -9.00 TL -0.000 0.000 0.502 rg -863.96 507.33 Td -(E22-900M30S) Tj -ET -2 J -0 j -100 M -1.00 w -0.53 0.00 0.00 RG -[] 0 d -822.000 495.000 m -918.000 495.000 l -919.105 495.000 920.000 494.105 920.000 493.000 c -920.000 357.000 l -920.000 355.895 918.895 355.000 918.000 355.000 c -822.000 355.000 l -820.895 355.000 820.000 356.105 820.000 357.000 c -820.000 493.000 l -820.000 494.105 821.105 495.000 822.000 495.000 c -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -897.36 372.00 Td -(GND) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -925.00 376.00 Td -(1) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -940.000 375.000 m -920.000 375.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -897.36 382.00 Td -(GND) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -925.00 386.00 Td -(2) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -940.000 385.000 m -920.000 385.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -897.36 392.00 Td -(GND) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -925.00 396.00 Td -(3) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -940.000 395.000 m -920.000 395.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -897.36 412.00 Td -(GND) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -925.00 416.00 Td -(4) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -940.000 415.000 m -920.000 415.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -897.36 422.00 Td -(GND) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -925.00 426.00 Td -(5) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -940.000 425.000 m -920.000 425.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -893.16 432.00 Td -(RXEN) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -925.00 436.00 Td -(6) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -940.000 435.000 m -920.000 435.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -893.87 442.00 Td -(TXEN) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -925.00 446.00 Td -(7) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -940.000 445.000 m -920.000 445.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -894.49 452.00 Td -(DIO2) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -925.00 456.00 Td -(8) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -940.000 455.000 m -920.000 455.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -899.29 462.00 Td -(VCC) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -925.00 466.00 Td -(9) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -940.000 465.000 m -920.000 465.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -899.29 472.00 Td -(VCC) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -925.00 476.00 Td -(10) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -940.000 475.000 m -920.000 475.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -897.36 482.00 Td -(GND) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -925.00 486.00 Td -(11) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -940.000 485.000 m -920.000 485.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -822.00 482.00 Td -(GND) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -803.57 486.00 Td -(12) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -800.000 485.000 m -820.000 485.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -822.00 472.00 Td -(DIO1) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -803.57 476.00 Td -(13) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -800.000 475.000 m -820.000 475.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -822.00 462.00 Td -(BUSY) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -803.57 466.00 Td -(14) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -800.000 465.000 m -820.000 465.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -822.00 452.00 Td -(NRST) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -803.57 456.00 Td -(15) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -800.000 455.000 m -820.000 455.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -822.00 442.00 Td -(MISO) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -803.57 446.00 Td -(16) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -800.000 445.000 m -820.000 445.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -822.00 432.00 Td -(MOSI) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -803.57 436.00 Td -(17) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -800.000 435.000 m -820.000 435.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -822.00 422.00 Td -(SCK) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -803.57 426.00 Td -(18) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -800.000 425.000 m -820.000 425.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -822.00 412.00 Td -(NSS) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -803.57 416.00 Td -(19) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -800.000 415.000 m -820.000 415.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -822.00 392.00 Td -(GND) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -803.57 396.00 Td -(20) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -800.000 395.000 m -820.000 395.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -822.00 382.00 Td -(ANT) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -803.57 386.00 Td -(21) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -800.000 385.000 m -820.000 385.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -822.00 372.00 Td -(GND) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -803.57 376.00 Td -(22) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -800.000 375.000 m -820.000 375.000 l -S -10.00 w -BT -/F3 9 Tf -9.00 TL -0.000 0.000 0.502 rg -293.99 488.33 Td -(E22-400MM22S) Tj -ET -10.00 w -BT -/F3 9 Tf -9.00 TL -0.000 0.000 0.502 rg -293.99 497.33 Td -(E22-900MM22S) Tj -ET -2 J -0 j -100 M -1.00 w -0.53 0.00 0.00 RG -[] 0 d -267.000 485.000 m -333.000 485.000 l -334.105 485.000 335.000 484.105 335.000 483.000 c -335.000 377.000 l -335.000 375.895 333.895 375.000 333.000 375.000 c -267.000 375.000 l -265.895 375.000 265.000 376.105 265.000 377.000 c -265.000 483.000 l -265.000 484.105 266.105 485.000 267.000 485.000 c -S -1.00 w -0.53 0.00 0.00 RG -0.53 0.00 0.00 rg -[] 0 d -271.50 480.00 m 271.50 480.83 270.83 481.50 270.00 481.50 c -269.17 481.50 268.50 480.83 268.50 480.00 c -268.50 479.17 269.17 478.50 270.00 478.50 c -270.83 478.50 271.50 479.17 271.50 480.00 c -B -BT -/F1 9 Tf -9.00 TL -1.000 0.000 0.000 rg -268.70 471.00 Td -(VCC) Tj -ET -BT -/F1 9 Tf -9.00 TL -1.000 0.000 0.000 rg -258.79 476.00 Td -(1) Tj -ET -1 J -1 j -1.00 w -1.00 0.00 0.00 RG -[] 0 d -255.000 475.000 m -265.000 475.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 g -268.70 461.00 Td -(GND) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 g -258.79 466.00 Td -(2) Tj -ET -1 J -1 j -1.00 w -0.00 G -[] 0 d -255.000 465.000 m -265.000 465.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -268.70 451.00 Td -(NRST) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -258.79 456.00 Td -(3) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -255.000 455.000 m -265.000 455.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -268.70 441.00 Td -(NC) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -258.79 446.00 Td -(4) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -255.000 445.000 m -265.000 445.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -268.70 431.00 Td -(NC) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -258.79 436.00 Td -(5) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -255.000 435.000 m -265.000 435.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -268.70 421.00 Td -(ANT) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -258.79 426.00 Td -(6) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -255.000 425.000 m -265.000 425.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 g -268.70 411.00 Td -(GND) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 g -258.79 416.00 Td -(7) Tj -ET -1 J -1 j -1.00 w -0.00 G -[] 0 d -255.000 415.000 m -265.000 415.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -268.70 401.00 Td -(NC) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -258.79 406.00 Td -(8) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -255.000 405.000 m -265.000 405.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -268.70 391.00 Td -(TXEN) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -258.79 396.00 Td -(9) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -255.000 395.000 m -265.000 395.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -268.70 381.00 Td -(RXEN) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -253.07 386.00 Td -(10) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -255.000 385.000 m -265.000 385.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -306.87 381.00 Td -(BUSY) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -335.50 386.00 Td -(11) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -345.000 385.000 m -335.000 385.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -306.71 391.00 Td -(MISO) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -335.50 396.00 Td -(12) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -345.000 395.000 m -335.000 395.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -306.71 401.00 Td -(MOSI) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -335.50 406.00 Td -(13) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -345.000 405.000 m -335.000 405.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -312.27 411.00 Td -(NSS) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -335.50 416.00 Td -(14) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -345.000 415.000 m -335.000 415.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -312.64 421.00 Td -(SCK) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -335.50 426.00 Td -(15) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -345.000 425.000 m -335.000 425.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 g -310.66 431.00 Td -(GND) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 g -335.50 436.00 Td -(16) Tj -ET -1 J -1 j -1.00 w -0.00 G -[] 0 d -345.000 435.000 m -335.000 435.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -318.29 441.00 Td -(NC) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -335.50 446.00 Td -(17) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -345.000 445.000 m -335.000 445.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -307.79 451.00 Td -(DIO3) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -335.50 456.00 Td -(18) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -345.000 455.000 m -335.000 455.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -307.79 461.00 Td -(DIO2) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -335.50 466.00 Td -(19) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -345.000 465.000 m -335.000 465.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -307.79 471.00 Td -(DIO1) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -335.50 476.00 Td -(20) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -345.000 475.000 m -335.000 475.000 l -S -BT -/F2 11 Tf -11.00 TL -0.000 0.000 1.000 rg -1.00 -0.00 0.00 1.00 366.70 381.60 Tm -(BUSY) Tj -ET -1 J -1 j -1.00 w -0.00 0.00 1.00 RG -0.00 g -[] 0 d -345.000 385.000 m -350.000 390.000 l -365.000 390.000 l -365.000 380.000 l -350.000 380.000 l -345.000 385.000 l -S -BT -/F2 11 Tf -11.00 TL -0.000 0.000 1.000 rg -1.00 -0.00 0.00 1.00 203.19 381.40 Tm -(RXEN) Tj -ET -1 J -1 j -1.00 w -0.00 0.00 1.00 RG -0.00 g -[] 0 d -255.000 385.000 m -250.000 380.000 l -235.000 380.000 l -235.000 390.000 l -250.000 390.000 l -255.000 385.000 l -S -BT -/F2 11 Tf -11.00 TL -0.000 0.000 1.000 rg -1.00 -0.00 0.00 1.00 366.71 421.60 Tm -(SCK) Tj -ET -1 J -1 j -1.00 w -0.00 0.00 1.00 RG -0.00 g -[] 0 d -345.000 425.000 m -350.000 430.000 l -365.000 430.000 l -365.000 420.000 l -350.000 420.000 l -345.000 425.000 l -S -BT -/F2 11 Tf -11.00 TL -0.000 0.000 1.000 rg -1.00 -0.00 0.00 1.00 367.00 391.60 Tm -(MISO) Tj -ET -1 J -1 j -1.00 w -0.00 0.00 1.00 RG -0.00 g -[] 0 d -345.000 395.000 m -350.000 400.000 l -365.000 400.000 l -365.000 390.000 l -350.000 390.000 l -345.000 395.000 l -S -BT -/F2 11 Tf -11.00 TL -0.000 0.000 1.000 rg -1.00 -0.00 0.00 1.00 367.00 401.60 Tm -(MOSI) Tj -ET -1 J -1 j -1.00 w -0.00 0.00 1.00 RG -0.00 g -[] 0 d -345.000 405.000 m -350.000 410.000 l -365.000 410.000 l -365.000 400.000 l -350.000 400.000 l -345.000 405.000 l -S -BT -/F2 11 Tf -11.00 TL -0.000 0.000 1.000 rg -1.00 -0.00 0.00 1.00 367.00 411.60 Tm -(CS) Tj -ET -1 J -1 j -1.00 w -0.00 0.00 1.00 RG -0.00 g -[] 0 d -345.000 415.000 m -350.000 420.000 l -365.000 420.000 l -365.000 410.000 l -350.000 410.000 l -345.000 415.000 l -S -BT -/F2 11 Tf -11.00 TL -0.000 0.000 1.000 rg -1.00 -0.00 0.00 1.00 367.01 461.15 Tm -(DIO2) Tj -ET -1 J -1 j -1.00 w -0.00 0.00 1.00 RG -0.00 g -[] 0 d -345.000 465.000 m -350.000 470.000 l -365.000 470.000 l -365.000 460.000 l -350.000 460.000 l -345.000 465.000 l -S -BT -/F2 11 Tf -11.00 TL -0.000 0.000 1.000 rg -1.00 -0.00 0.00 1.00 367.00 471.60 Tm -(IRQ) Tj -ET -1 J -1 j -1.00 w -0.00 0.00 1.00 RG -0.00 g -[] 0 d -345.000 475.000 m -350.000 480.000 l -365.000 480.000 l -365.000 470.000 l -350.000 470.000 l -345.000 475.000 l -S -BT -/F2 11 Tf -11.00 TL -0.000 0.000 1.000 rg -1.00 -0.00 0.00 1.00 203.74 391.40 Tm -(TXEN) Tj -ET -1 J -1 j -1.00 w -0.00 0.00 1.00 RG -0.00 g -[] 0 d -255.000 395.000 m -250.000 390.000 l -235.000 390.000 l -235.000 400.000 l -250.000 400.000 l -255.000 395.000 l -S -BT -/F2 11 Tf -11.00 TL -0.000 0.000 1.000 rg -1.00 -0.00 0.00 1.00 205.06 451.57 Tm -(NRST) Tj -ET -1 J -1 j -1.00 w -0.00 0.00 1.00 RG -0.00 g -[] 0 d -255.000 455.000 m -250.000 450.000 l -235.000 450.000 l -235.000 460.000 l -250.000 460.000 l -255.000 455.000 l -S -BT -/F2 11 Tf -11.00 TL -0.000 0.000 1.000 rg -1.00 -0.00 0.00 1.00 367.01 451.15 Tm -(DIO3) Tj -ET -1 J -1 j -1.00 w -0.00 0.00 1.00 RG -0.00 g -[] 0 d -345.000 455.000 m -350.000 460.000 l -365.000 460.000 l -365.000 450.000 l -350.000 450.000 l -345.000 455.000 l -S -BT -/F2 11 Tf -11.00 TL -0.000 0.000 1.000 rg -1.00 -0.00 0.00 1.00 460.06 456.57 Tm -(NRST) Tj -ET -1 J -1 j -1.00 w -0.00 0.00 1.00 RG -0.00 g -[] 0 d -510.000 460.000 m -505.000 455.000 l -490.000 455.000 l -490.000 465.000 l -505.000 465.000 l -510.000 460.000 l -S -BT -/F2 11 Tf -11.00 TL -0.000 0.000 1.000 rg -1.00 -0.00 0.00 1.00 671.71 446.60 Tm -(TXEN) Tj -ET -1 J -1 j -1.00 w -0.00 0.00 1.00 RG -0.00 g -[] 0 d -650.000 450.000 m -655.000 455.000 l -670.000 455.000 l -670.000 445.000 l -655.000 445.000 l -650.000 450.000 l -S -BT -/F2 11 Tf -11.00 TL -0.000 0.000 1.000 rg -1.00 -0.00 0.00 1.00 469.19 476.40 Tm -(IRQ) Tj -ET -1 J -1 j -1.00 w -0.00 0.00 1.00 RG -0.00 g -[] 0 d -510.000 480.000 m -505.000 475.000 l -490.000 475.000 l -490.000 485.000 l -505.000 485.000 l -510.000 480.000 l -S -BT -/F2 11 Tf -11.00 TL -0.000 0.000 1.000 rg -1.00 -0.00 0.00 1.00 672.01 456.15 Tm -(DIO2) Tj -ET -1 J -1 j -1.00 w -0.00 0.00 1.00 RG -0.00 g -[] 0 d -650.000 460.000 m -655.000 465.000 l -670.000 465.000 l -670.000 455.000 l -655.000 455.000 l -650.000 460.000 l -S -BT -/F2 11 Tf -11.00 TL -0.000 0.000 1.000 rg -1.00 -0.00 0.00 1.00 474.69 416.40 Tm -(CS) Tj -ET -1 J -1 j -1.00 w -0.00 0.00 1.00 RG -0.00 g -[] 0 d -510.000 420.000 m -505.000 415.000 l -490.000 415.000 l -490.000 425.000 l -505.000 425.000 l -510.000 420.000 l -S -BT -/F2 11 Tf -11.00 TL -0.000 0.000 1.000 rg -1.00 -0.00 0.00 1.00 460.61 436.40 Tm -(MOSI) Tj -ET -1 J -1 j -1.00 w -0.00 0.00 1.00 RG -0.00 g -[] 0 d -510.000 440.000 m -505.000 435.000 l -490.000 435.000 l -490.000 445.000 l -505.000 445.000 l -510.000 440.000 l -S -BT -/F2 11 Tf -11.00 TL -0.000 0.000 1.000 rg -1.00 -0.00 0.00 1.00 460.61 446.40 Tm -(MISO) Tj -ET -1 J -1 j -1.00 w -0.00 0.00 1.00 RG -0.00 g -[] 0 d -510.000 450.000 m -505.000 445.000 l -490.000 445.000 l -490.000 455.000 l -505.000 455.000 l -510.000 450.000 l -S -BT -/F2 11 Tf -11.00 TL -0.000 0.000 1.000 rg -1.00 -0.00 0.00 1.00 466.77 426.40 Tm -(SCK) Tj -ET -1 J -1 j -1.00 w -0.00 0.00 1.00 RG -0.00 g -[] 0 d -510.000 430.000 m -505.000 425.000 l -490.000 425.000 l -490.000 435.000 l -505.000 435.000 l -510.000 430.000 l -S -BT -/F2 11 Tf -11.00 TL -0.000 0.000 1.000 rg -1.00 -0.00 0.00 1.00 671.71 436.60 Tm -(RXEN) Tj -ET -1 J -1 j -1.00 w -0.00 0.00 1.00 RG -0.00 g -[] 0 d -650.000 440.000 m -655.000 445.000 l -670.000 445.000 l -670.000 435.000 l -655.000 435.000 l -650.000 440.000 l -S -BT -/F2 11 Tf -11.00 TL -0.000 0.000 1.000 rg -1.00 -0.00 0.00 1.00 458.85 466.40 Tm -(BUSY) Tj -ET -1 J -1 j -1.00 w -0.00 0.00 1.00 RG -0.00 g -[] 0 d -510.000 470.000 m -505.000 465.000 l -490.000 465.000 l -490.000 475.000 l -505.000 475.000 l -510.000 470.000 l -S -BT -/F2 12 Tf -12.00 TL -0.000 g -1.00 -0.00 0.00 1.00 467.50 395.92 Tm -(GND) Tj -ET -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -500.000 400.000 m -510.000 400.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -500.000 409.000 m -500.000 391.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -498.000 406.000 m -498.000 394.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -496.000 403.000 m -496.000 397.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -494.000 401.000 m -494.000 399.000 l -S -BT -/F2 12 Tf -12.00 TL -0.000 g -1.00 -0.00 0.00 1.00 467.50 375.92 Tm -(GND) Tj -ET -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -500.000 380.000 m -510.000 380.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -500.000 389.000 m -500.000 371.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -498.000 386.000 m -498.000 374.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -496.000 383.000 m -496.000 377.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -494.000 381.000 m -494.000 379.000 l -S -BT -/F2 12 Tf -12.00 TL -0.000 g -1.00 -0.00 0.00 1.00 637.00 352.76 Tm -(GND) Tj -ET -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -650.000 370.000 m -650.000 380.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -641.000 370.000 m -659.000 370.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -644.000 368.000 m -656.000 368.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -647.000 366.000 m -653.000 366.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -649.000 364.000 m -651.000 364.000 l -S -BT -/F2 12 Tf -12.00 TL -0.000 g -1.00 -0.00 0.00 1.00 637.00 509.13 Tm -(GND) Tj -ET -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -650.000 500.000 m -650.000 490.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -659.000 500.000 m -641.000 500.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -656.000 502.000 m -644.000 502.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -653.000 504.000 m -647.000 504.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -651.000 506.000 m -649.000 506.000 l -S -BT -/F2 12 Tf -12.00 TL -0.000 g -1.00 -0.00 0.00 1.00 467.50 485.92 Tm -(GND) Tj -ET -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -500.000 490.000 m -510.000 490.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -500.000 499.000 m -500.000 481.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -498.000 496.000 m -498.000 484.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -496.000 493.000 m -496.000 487.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -494.000 491.000 m -494.000 489.000 l -S -BT -/F2 12 Tf -12.00 TL -0.000 g -1.00 -0.00 0.00 1.00 361.50 430.92 Tm -(GND) Tj -ET -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -355.000 435.000 m -345.000 435.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -355.000 426.000 m -355.000 444.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -357.000 429.000 m -357.000 441.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -359.000 432.000 m -359.000 438.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -361.000 434.000 m -361.000 436.000 l -S -BT -/F2 12 Tf -12.00 TL -0.000 g -1.00 -0.00 0.00 1.00 212.50 410.93 Tm -(GND) Tj -ET -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -245.000 415.000 m -255.000 415.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -245.000 424.000 m -245.000 406.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -243.000 421.000 m -243.000 409.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -241.000 418.000 m -241.000 412.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -239.000 416.000 m -239.000 414.000 l -S -BT -/F2 12 Tf -12.00 TL -0.000 g -1.00 -0.00 0.00 1.00 212.50 460.93 Tm -(GND) Tj -ET -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -245.000 465.000 m -255.000 465.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -245.000 474.000 m -245.000 456.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -243.000 471.000 m -243.000 459.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -241.000 468.000 m -241.000 462.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -239.000 466.000 m -239.000 464.000 l -S -1 J -1 j -1.00 w -0.00 0.53 0.00 RG -0.00 g -[] 0 d -940.000 370.000 m -940.000 425.000 l -S -BT -/F2 12 Tf -12.00 TL -0.000 g -1.00 -0.00 0.00 1.00 757.50 480.92 Tm -(GND) Tj -ET -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -790.000 485.000 m -800.000 485.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -790.000 494.000 m -790.000 476.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -788.000 491.000 m -788.000 479.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -786.000 488.000 m -786.000 482.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -784.000 486.000 m -784.000 484.000 l -S -BT -/F2 12 Tf -12.00 TL -0.000 g -1.00 -0.00 0.00 1.00 927.00 504.09 Tm -(GND) Tj -ET -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -940.000 495.000 m -940.000 485.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -949.000 495.000 m -931.000 495.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -946.000 497.000 m -934.000 497.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -943.000 499.000 m -937.000 499.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -941.000 501.000 m -939.000 501.000 l -S -BT -/F2 12 Tf -12.00 TL -0.000 g -1.00 -0.00 0.00 1.00 757.50 370.92 Tm -(GND) Tj -ET -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -790.000 375.000 m -800.000 375.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -790.000 384.000 m -790.000 366.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -788.000 381.000 m -788.000 369.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -786.000 378.000 m -786.000 372.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -784.000 376.000 m -784.000 374.000 l -S -BT -/F2 12 Tf -12.00 TL -0.000 g -1.00 -0.00 0.00 1.00 757.50 390.92 Tm -(GND) Tj -ET -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -790.000 395.000 m -800.000 395.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -790.000 404.000 m -790.000 386.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -788.000 401.000 m -788.000 389.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -786.000 398.000 m -786.000 392.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -784.000 396.000 m -784.000 394.000 l -S -BT -/F2 11 Tf -11.00 TL -0.000 0.000 1.000 rg -1.00 -0.00 0.00 1.00 748.85 461.40 Tm -(BUSY) Tj -ET -1 J -1 j -1.00 w -0.00 0.00 1.00 RG -0.00 g -[] 0 d -800.000 465.000 m -795.000 460.000 l -780.000 460.000 l -780.000 470.000 l -795.000 470.000 l -800.000 465.000 l -S -BT -/F2 11 Tf -11.00 TL -0.000 0.000 1.000 rg -1.00 -0.00 0.00 1.00 961.71 431.60 Tm -(RXEN) Tj -ET -1 J -1 j -1.00 w -0.00 0.00 1.00 RG -0.00 g -[] 0 d -940.000 435.000 m -945.000 440.000 l -960.000 440.000 l -960.000 430.000 l -945.000 430.000 l -940.000 435.000 l -S -BT -/F2 11 Tf -11.00 TL -0.000 0.000 1.000 rg -1.00 -0.00 0.00 1.00 756.77 421.40 Tm -(SCK) Tj -ET -1 J -1 j -1.00 w -0.00 0.00 1.00 RG -0.00 g -[] 0 d -800.000 425.000 m -795.000 420.000 l -780.000 420.000 l -780.000 430.000 l -795.000 430.000 l -800.000 425.000 l -S -BT -/F2 11 Tf -11.00 TL -0.000 0.000 1.000 rg -1.00 -0.00 0.00 1.00 750.61 441.40 Tm -(MISO) Tj -ET -1 J -1 j -1.00 w -0.00 0.00 1.00 RG -0.00 g -[] 0 d -800.000 445.000 m -795.000 440.000 l -780.000 440.000 l -780.000 450.000 l -795.000 450.000 l -800.000 445.000 l -S -BT -/F2 11 Tf -11.00 TL -0.000 0.000 1.000 rg -1.00 -0.00 0.00 1.00 750.61 431.40 Tm -(MOSI) Tj -ET -1 J -1 j -1.00 w -0.00 0.00 1.00 RG -0.00 g -[] 0 d -800.000 435.000 m -795.000 430.000 l -780.000 430.000 l -780.000 440.000 l -795.000 440.000 l -800.000 435.000 l -S -BT -/F2 11 Tf -11.00 TL -0.000 0.000 1.000 rg -1.00 -0.00 0.00 1.00 764.69 411.40 Tm -(CS) Tj -ET -1 J -1 j -1.00 w -0.00 0.00 1.00 RG -0.00 g -[] 0 d -800.000 415.000 m -795.000 410.000 l -780.000 410.000 l -780.000 420.000 l -795.000 420.000 l -800.000 415.000 l -S -BT -/F2 11 Tf -11.00 TL -0.000 0.000 1.000 rg -1.00 -0.00 0.00 1.00 962.01 451.15 Tm -(DIO2) Tj -ET -1 J -1 j -1.00 w -0.00 0.00 1.00 RG -0.00 g -[] 0 d -940.000 455.000 m -945.000 460.000 l -960.000 460.000 l -960.000 450.000 l -945.000 450.000 l -940.000 455.000 l -S -BT -/F2 11 Tf -11.00 TL -0.000 0.000 1.000 rg -1.00 -0.00 0.00 1.00 759.19 471.40 Tm -(IRQ) Tj -ET -1 J -1 j -1.00 w -0.00 0.00 1.00 RG -0.00 g -[] 0 d -800.000 475.000 m -795.000 470.000 l -780.000 470.000 l -780.000 480.000 l -795.000 480.000 l -800.000 475.000 l -S -BT -/F2 11 Tf -11.00 TL -0.000 0.000 1.000 rg -1.00 -0.00 0.00 1.00 961.71 441.60 Tm -(TXEN) Tj -ET -1 J -1 j -1.00 w -0.00 0.00 1.00 RG -0.00 g -[] 0 d -940.000 445.000 m -945.000 450.000 l -960.000 450.000 l -960.000 440.000 l -945.000 440.000 l -940.000 445.000 l -S -BT -/F2 11 Tf -11.00 TL -0.000 0.000 1.000 rg -1.00 -0.00 0.00 1.00 750.06 451.57 Tm -(NRST) Tj -ET -1 J -1 j -1.00 w -0.00 0.00 1.00 RG -0.00 g -[] 0 d -800.000 455.000 m -795.000 450.000 l -780.000 450.000 l -780.000 460.000 l -795.000 460.000 l -800.000 455.000 l -S -BT -/F2 12 Tf -12.00 TL -0.000 g -1.00 -0.00 0.00 1.00 927.00 342.76 Tm -(GND) Tj -ET -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -940.000 360.000 m -940.000 370.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -931.000 360.000 m -949.000 360.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -934.000 358.000 m -946.000 358.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -937.000 356.000 m -943.000 356.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -939.000 354.000 m -941.000 354.000 l -S -BT -/F2 12 Tf -12.00 TL -0.000 g -1.00 -0.00 0.00 1.00 243.00 487.00 Tm -(VCC) Tj -ET -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -255.000 485.000 m -255.000 475.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -250.000 485.000 m -260.000 485.000 l -S -BT -/F2 12 Tf -12.00 TL -0.000 g -1.00 -0.00 0.00 1.00 660.50 465.93 Tm -(VCC) Tj -ET -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -660.000 470.000 m -650.000 470.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -660.000 475.000 m -660.000 465.000 l -S -1 J -1 j -1.00 w -0.00 0.53 0.00 RG -0.00 g -[] 0 d -175.000 425.000 m -210.000 425.000 l -255.000 425.000 l -S -BT -/F2 12 Tf -12.00 TL -0.000 g -1.00 -0.00 0.00 1.00 142.00 429.00 Tm -(GND) Tj -ET -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -155.000 445.000 m -155.000 455.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -146.000 445.000 m -164.000 445.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -149.000 443.000 m -161.000 443.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -152.000 441.000 m -158.000 441.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -154.000 439.000 m -156.000 439.000 l -S -BT -/F2 12 Tf -12.00 TL -0.000 g -1.00 -0.00 0.00 1.00 182.00 428.00 Tm -(GND) Tj -ET -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -195.000 445.000 m -195.000 455.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -186.000 445.000 m -204.000 445.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -189.000 443.000 m -201.000 443.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -192.000 441.000 m -198.000 441.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -194.000 439.000 m -196.000 439.000 l -S -BT -/F2 12 Tf -12.00 TL -0.000 g -1.00 -0.00 0.00 1.00 950.50 460.92 Tm -(+5V) Tj -ET -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -950.000 465.000 m -940.000 465.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -950.000 470.000 m -950.000 460.000 l -S -1 J -1 j -1.00 w -0.00 0.53 0.00 RG -0.00 g -[] 0 d -650.000 490.000 m -650.000 480.000 l -S -1 J -1 j -1.00 w -0.00 0.53 0.00 RG -0.00 g -[] 0 d -940.000 465.000 m -940.000 475.000 l -S -1 J -1 j -1.00 w -0.20 0.80 0.20 RG -[] 0 d -231.000 294.000 m -239.000 286.000 l -239.000 294.000 m -231.000 286.000 l -S -1 J -1 j -1.00 w -0.20 0.80 0.20 RG -[] 0 d -371.000 244.000 m -379.000 236.000 l -379.000 244.000 m -371.000 236.000 l -S -1 J -1 j -1.00 w -0.20 0.80 0.20 RG -[] 0 d -371.000 284.000 m -379.000 276.000 l -379.000 284.000 m -371.000 276.000 l -S -1 J -1 j -1.00 w -0.20 0.80 0.20 RG -[] 0 d -371.000 274.000 m -379.000 266.000 l -379.000 274.000 m -371.000 266.000 l -S -1 J -1 j -1.00 w -0.20 0.80 0.20 RG -[] 0 d -231.000 204.000 m -239.000 196.000 l -239.000 204.000 m -231.000 196.000 l -S -1 J -1 j -1.00 w -0.20 0.80 0.20 RG -[] 0 d -371.000 204.000 m -379.000 196.000 l -379.000 204.000 m -371.000 196.000 l -S -BT -/F2 11 Tf -11.00 TL -0.000 0.000 1.000 rg -1.00 -0.00 0.00 1.00 397.71 246.28 Tm -(NRST) Tj -ET -1 J -1 j -1.00 w -0.00 0.00 1.00 RG -0.00 g -[] 0 d -375.000 250.000 m -380.000 255.000 l -395.000 255.000 l -395.000 245.000 l -380.000 245.000 l -375.000 250.000 l -S -BT -/F2 11 Tf -11.00 TL -0.000 0.000 1.000 rg -1.00 -0.00 0.00 1.00 397.00 256.60 Tm -(IRQ) Tj -ET -1 J -1 j -1.00 w -0.00 0.00 1.00 RG -0.00 g -[] 0 d -375.000 260.000 m -380.000 265.000 l -395.000 265.000 l -395.000 255.000 l -380.000 255.000 l -375.000 260.000 l -S -BT -/F2 11 Tf -11.00 TL -0.000 0.000 1.000 rg -1.00 -0.00 0.00 1.00 183.85 236.40 Tm -(BUSY) Tj -ET -1 J -1 j -1.00 w -0.00 0.00 1.00 RG -0.00 g -[] 0 d -235.000 240.000 m -230.000 235.000 l -215.000 235.000 l -215.000 245.000 l -230.000 245.000 l -235.000 240.000 l -S -BT -/F2 11 Tf -11.00 TL -0.000 0.000 1.000 rg -1.00 -0.00 0.00 1.00 199.69 246.40 Tm -(CS) Tj -ET -1 J -1 j -1.00 w -0.00 0.00 1.00 RG -0.00 g -[] 0 d -235.000 250.000 m -230.000 245.000 l -215.000 245.000 l -215.000 255.000 l -230.000 255.000 l -235.000 250.000 l -S -BT -/F2 11 Tf -11.00 TL -0.000 0.000 1.000 rg -1.00 -0.00 0.00 1.00 185.61 266.40 Tm -(MOSI) Tj -ET -1 J -1 j -1.00 w -0.00 0.00 1.00 RG -0.00 g -[] 0 d -235.000 270.000 m -230.000 265.000 l -215.000 265.000 l -215.000 275.000 l -230.000 275.000 l -235.000 270.000 l -S -BT -/F2 11 Tf -11.00 TL -0.000 0.000 1.000 rg -1.00 -0.00 0.00 1.00 191.77 256.40 Tm -(SCK) Tj -ET -1 J -1 j -1.00 w -0.00 0.00 1.00 RG -0.00 g -[] 0 d -235.000 260.000 m -230.000 255.000 l -215.000 255.000 l -215.000 265.000 l -230.000 265.000 l -235.000 260.000 l -S -BT -/F2 11 Tf -11.00 TL -0.000 0.000 1.000 rg -1.00 -0.00 0.00 1.00 185.61 276.40 Tm -(MISO) Tj -ET -1 J -1 j -1.00 w -0.00 0.00 1.00 RG -0.00 g -[] 0 d -235.000 280.000 m -230.000 275.000 l -215.000 275.000 l -215.000 285.000 l -230.000 285.000 l -235.000 280.000 l -S -10.00 w -BT -/F3 9 Tf -9.00 TL -0.000 0.000 0.502 rg -298.96 313.33 Td -(E80-900M2213S) Tj -ET -10.00 w -BT -/F3 9 Tf -9.00 TL -0.000 0.000 0.502 rg -298.96 322.33 Td -(E80-900M2213S) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -257.00 187.00 Td -(GND) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -238.57 191.00 Td -(22) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -235.000 190.000 m -255.000 190.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -257.00 197.00 Td -(ANT_2.4) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -238.57 201.00 Td -(21) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -235.000 200.000 m -255.000 200.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -257.00 207.00 Td -(GND) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -238.57 211.00 Td -(20) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -235.000 210.000 m -255.000 210.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -257.00 227.00 Td -(GND) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -238.57 231.00 Td -(19) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -235.000 230.000 m -255.000 230.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -257.00 237.00 Td -(BUSY) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -238.57 241.00 Td -(18) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -235.000 240.000 m -255.000 240.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -257.00 247.00 Td -(NSS) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -238.57 251.00 Td -(17) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -235.000 250.000 m -255.000 250.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -257.00 257.00 Td -(SCK) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -238.57 261.00 Td -(16) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -235.000 260.000 m -255.000 260.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -257.00 267.00 Td -(MOSI) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -238.57 271.00 Td -(15) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -235.000 270.000 m -255.000 270.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -257.00 277.00 Td -(MISO) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -238.57 281.00 Td -(14) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -235.000 280.000 m -255.000 280.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -257.00 287.00 Td -(NC) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -238.57 291.00 Td -(13) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -235.000 290.000 m -255.000 290.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -257.00 297.00 Td -(GND) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -238.57 301.00 Td -(12) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -235.000 300.000 m -255.000 300.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -332.36 297.00 Td -(GND) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -360.00 301.00 Td -(11) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -375.000 300.000 m -355.000 300.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -334.29 287.00 Td -(VCC) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -360.00 291.00 Td -(10) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -375.000 290.000 m -355.000 290.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -329.49 277.00 Td -(DIO7) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -360.00 281.00 Td -(9) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -375.000 280.000 m -355.000 280.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -329.49 267.00 Td -(DIO8) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -360.00 271.00 Td -(8) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -375.000 270.000 m -355.000 270.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -329.49 257.00 Td -(DIO9) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -360.00 261.00 Td -(7) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -375.000 260.000 m -355.000 260.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -328.32 247.00 Td -(NRST) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -360.00 251.00 Td -(6) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -375.000 250.000 m -355.000 250.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -339.99 237.00 Td -(NC) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -360.00 241.00 Td -(5) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -375.000 240.000 m -355.000 240.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -332.36 227.00 Td -(GND) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -360.00 231.00 Td -(4) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -375.000 230.000 m -355.000 230.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -332.36 207.00 Td -(GND) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -360.00 211.00 Td -(3) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -375.000 210.000 m -355.000 210.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -311.72 197.00 Td -(ANT_900) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -360.00 201.00 Td -(2) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -375.000 200.000 m -355.000 200.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -332.36 187.00 Td -(GND) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -360.00 191.00 Td -(1) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -375.000 190.000 m -355.000 190.000 l -S -2 J -0 j -100 M -1.00 w -0.53 0.00 0.00 RG -[] 0 d -257.000 310.000 m -353.000 310.000 l -354.105 310.000 355.000 309.105 355.000 308.000 c -355.000 172.000 l -355.000 170.895 353.895 170.000 353.000 170.000 c -257.000 170.000 l -255.895 170.000 255.000 171.105 255.000 172.000 c -255.000 308.000 l -255.000 309.105 256.105 310.000 257.000 310.000 c -S -BT -/F2 12 Tf -12.00 TL -0.000 g -1.00 -0.00 0.00 1.00 362.00 163.00 Tm -(GND) Tj -ET -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -375.000 180.000 m -375.000 190.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -366.000 180.000 m -384.000 180.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -369.000 178.000 m -381.000 178.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -372.000 176.000 m -378.000 176.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -374.000 174.000 m -376.000 174.000 l -S -BT -/F2 12 Tf -12.00 TL -0.000 g -1.00 -0.00 0.00 1.00 391.50 206.00 Tm -(GND) Tj -ET -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -385.000 210.000 m -375.000 210.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -385.000 201.000 m -385.000 219.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -387.000 204.000 m -387.000 216.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -389.000 207.000 m -389.000 213.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -391.000 209.000 m -391.000 211.000 l -S -BT -/F2 12 Tf -12.00 TL -0.000 g -1.00 -0.00 0.00 1.00 391.50 226.00 Tm -(GND) Tj -ET -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -385.000 230.000 m -375.000 230.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -385.000 221.000 m -385.000 239.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -387.000 224.000 m -387.000 236.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -389.000 227.000 m -389.000 233.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -391.000 229.000 m -391.000 231.000 l -S -BT -/F2 12 Tf -12.00 TL -0.000 g -1.00 -0.00 0.00 1.00 391.50 296.00 Tm -(GND) Tj -ET -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -385.000 300.000 m -375.000 300.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -385.000 291.000 m -385.000 309.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -387.000 294.000 m -387.000 306.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -389.000 297.000 m -389.000 303.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -391.000 299.000 m -391.000 301.000 l -S -BT -/F2 12 Tf -12.00 TL -0.000 g -1.00 -0.00 0.00 1.00 192.50 296.00 Tm -(GND) Tj -ET -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -225.000 300.000 m -235.000 300.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -225.000 309.000 m -225.000 291.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -223.000 306.000 m -223.000 294.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -221.000 303.000 m -221.000 297.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -219.000 301.000 m -219.000 299.000 l -S -BT -/F2 12 Tf -12.00 TL -0.000 g -1.00 -0.00 0.00 1.00 192.50 226.00 Tm -(GND) Tj -ET -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -225.000 230.000 m -235.000 230.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -225.000 239.000 m -225.000 221.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -223.000 236.000 m -223.000 224.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -221.000 233.000 m -221.000 227.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -219.000 231.000 m -219.000 229.000 l -S -BT -/F2 12 Tf -12.00 TL -0.000 g -1.00 -0.00 0.00 1.00 192.50 206.00 Tm -(GND) Tj -ET -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -225.000 210.000 m -235.000 210.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -225.000 219.000 m -225.000 201.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -223.000 216.000 m -223.000 204.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -221.000 213.000 m -221.000 207.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -219.000 211.000 m -219.000 209.000 l -S -BT -/F2 12 Tf -12.00 TL -0.000 g -1.00 -0.00 0.00 1.00 192.50 186.00 Tm -(GND) Tj -ET -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -225.000 190.000 m -235.000 190.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -225.000 199.000 m -225.000 181.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -223.000 196.000 m -223.000 184.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -221.000 193.000 m -221.000 187.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -219.000 191.000 m -219.000 189.000 l -S -BT -/F2 12 Tf -12.00 TL -0.000 g -1.00 -0.00 0.00 1.00 386.00 286.00 Tm -(VCC) Tj -ET -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -385.000 290.000 m -375.000 290.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -385.000 295.000 m -385.000 285.000 l -S -1 J -1 j -1.00 w -0.00 0.53 0.00 RG -0.00 g -[] 0 d -160.000 675.000 m -160.000 680.000 l -160.000 685.000 l -S -1 J -1 j -1.00 w -0.00 0.53 0.00 RG -0.00 g -[] 0 d -145.000 655.000 m -160.000 655.000 l -S -1 J -1 j -1.00 w -0.00 0.53 0.00 RG -0.00 g -[] 0 d -145.000 645.000 m -160.000 645.000 l -S -1 J -1 j -1.00 w -0.00 0.53 0.00 RG -0.00 g -[] 0 d -145.000 635.000 m -160.000 635.000 l -S -1 J -1 j -1.00 w -0.00 0.53 0.00 RG -0.00 g -[] 0 d -145.000 625.000 m -160.000 625.000 l -S -1 J -1 j -1.00 w -0.00 0.53 0.00 RG -0.00 g -[] 0 d -145.000 615.000 m -160.000 615.000 l -S -1 J -1 j -1.00 w -0.00 0.53 0.00 RG -0.00 g -[] 0 d -145.000 605.000 m -160.000 605.000 l -S -BT -/F2 11 Tf -11.00 TL -0.000 0.000 1.000 rg -1.00 -0.00 0.00 1.00 112.70 591.40 Tm -(P1.06) Tj -ET -1 J -1 j -1.00 w -0.00 0.00 1.00 RG -0.00 g -[] 0 d -160.000 595.000 m -155.000 590.000 l -140.000 590.000 l -140.000 600.000 l -155.000 600.000 l -160.000 595.000 l -S -BT -/F2 11 Tf -11.00 TL -0.000 0.000 1.000 rg -1.00 -0.00 0.00 1.00 112.70 701.40 Tm -(P0.06) Tj -ET -1 J -1 j -1.00 w -0.00 0.00 1.00 RG -0.00 g -[] 0 d -160.000 705.000 m -155.000 700.000 l -140.000 700.000 l -140.000 710.000 l -155.000 710.000 l -160.000 705.000 l -S -BT -/F2 11 Tf -11.00 TL -0.000 0.000 1.000 rg -1.00 -0.00 0.00 1.00 112.70 691.40 Tm -(P0.08) Tj -ET -1 J -1 j -1.00 w -0.00 0.00 1.00 RG -0.00 g -[] 0 d -160.000 695.000 m -155.000 690.000 l -140.000 690.000 l -140.000 700.000 l -155.000 700.000 l -160.000 695.000 l -S -10.00 w -BT -/F3 9 Tf -9.00 TL -0.000 0.000 0.502 rg -417.00 632.75 Td -(1.5M) Tj -ET -10.00 w -BT -/F3 9 Tf -9.00 TL -0.000 0.000 0.502 rg -417.00 641.75 Td -(R2) Tj -ET -2 J -0 j -100 M -1.00 w -0.63 0.00 0.00 RG -[] 0 d -405.00 655.00 10.00 -20.00 re -S -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -410.000 655.000 m -410.000 665.000 l -S -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -410.000 635.000 m -410.000 625.000 l -S -BT -/F2 11 Tf -11.00 TL -0.000 0.000 1.000 rg -0.00 1.00 -1.00 0.00 413.70 727.50 Tm -(Batt) Tj -ET -1 J -1 j -1.00 w -0.00 0.00 1.00 RG -0.00 g -[] 0 d -410.000 705.000 m -405.000 710.000 l -405.000 725.000 l -415.000 725.000 l -415.000 710.000 l -410.000 705.000 l -S -1 J -1 j -1.00 w -0.20 0.80 0.20 RG -[] 0 d -186.000 549.000 m -194.000 541.000 l -194.000 549.000 m -186.000 541.000 l -S -1 J -1 j -1.00 w -0.20 0.80 0.20 RG -[] 0 d -196.000 549.000 m -204.000 541.000 l -204.000 549.000 m -196.000 541.000 l -S -1 J -1 j -1.00 w -0.20 0.80 0.20 RG -[] 0 d -206.000 549.000 m -214.000 541.000 l -214.000 549.000 m -206.000 541.000 l -S -1 J -1 j -1.00 w -0.20 0.80 0.20 RG -[] 0 d -276.000 719.000 m -284.000 711.000 l -284.000 719.000 m -276.000 711.000 l -S -1 J -1 j -1.00 w -0.20 0.80 0.20 RG -[] 0 d -156.000 719.000 m -164.000 711.000 l -164.000 719.000 m -156.000 711.000 l -S -BT -/F2 12 Tf -12.00 TL -0.000 g -1.00 -0.00 0.00 1.00 397.00 599.00 Tm -(GND) Tj -ET -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -410.000 615.000 m -410.000 625.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -401.000 615.000 m -419.000 615.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -404.000 613.000 m -416.000 613.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -407.000 611.000 m -413.000 611.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -409.000 609.000 m -411.000 609.000 l -S -1 J -1 j -1.00 w -0.00 0.53 0.00 RG -0.00 g -[] 0 d -410.000 665.000 m -280.000 665.000 l -S -10.00 w -BT -/F3 9 Tf -9.00 TL -0.000 0.000 0.502 rg -417.00 672.75 Td -(1M) Tj -ET -10.00 w -BT -/F3 9 Tf -9.00 TL -0.000 0.000 0.502 rg -417.00 681.75 Td -(R1) Tj -ET -2 J -0 j -100 M -1.00 w -0.63 0.00 0.00 RG -[] 0 d -405.00 695.00 10.00 -20.00 re -S -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -410.000 695.000 m -410.000 705.000 l -S -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -410.000 675.000 m -410.000 665.000 l -S -1 J -1 j -1.00 w -0.00 0.53 0.00 RG -0.00 g -[] 0 d -290.000 685.000 m -280.000 685.000 l -S -BT -/F2 11 Tf -11.00 TL -0.000 0.000 1.000 rg -1.00 -0.00 0.00 1.00 312.01 681.07 Tm -(RBtn) Tj -ET -1 J -1 j -1.00 w -0.00 0.00 1.00 RG -0.00 g -[] 0 d -290.000 685.000 m -295.000 690.000 l -310.000 690.000 l -310.000 680.000 l -295.000 680.000 l -290.000 685.000 l -S -BT -/F2 11 Tf -11.00 TL -0.000 0.000 1.000 rg -1.00 -0.00 0.00 1.00 99.24 621.60 Tm -(UBtn) Tj -ET -1 J -1 j -1.00 w -0.00 0.00 1.00 RG -0.00 g -[] 0 d -145.000 625.000 m -140.000 620.000 l -125.000 620.000 l -125.000 630.000 l -140.000 630.000 l -145.000 625.000 l -S -BT -/F2 12 Tf -12.00 TL -0.000 g -1.00 -0.00 0.00 1.00 117.50 676.00 Tm -(GND) Tj -ET -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -150.000 680.000 m -160.000 680.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -150.000 689.000 m -150.000 671.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -148.000 686.000 m -148.000 674.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -146.000 683.000 m -146.000 677.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -144.000 681.000 m -144.000 679.000 l -S -1 J -1 j -1.00 w -0.00 0.53 0.00 RG -0.00 g -[] 0 d -290.000 595.000 m -280.000 595.000 l -S -1 J -1 j -1.00 w -0.00 0.53 0.00 RG -0.00 g -[] 0 d -290.000 655.000 m -280.000 655.000 l -S -1 J -1 j -1.00 w -0.00 0.53 0.00 RG -0.00 g -[] 0 d -290.000 605.000 m -280.000 605.000 l -S -1 J -1 j -1.00 w -0.00 0.53 0.00 RG -0.00 g -[] 0 d -290.000 625.000 m -280.000 625.000 l -S -BT -/F2 11 Tf -11.00 TL -0.000 0.000 1.000 rg -1.00 -0.00 0.00 1.00 92.64 631.40 Tm -(GPSen) Tj -ET -1 J -1 j -1.00 w -0.00 0.00 1.00 RG -0.00 g -[] 0 d -145.000 635.000 m -140.000 630.000 l -125.000 630.000 l -125.000 640.000 l -140.000 640.000 l -145.000 635.000 l -S -BT -/F2 11 Tf -11.00 TL -0.000 0.000 1.000 rg -1.00 -0.00 0.00 1.00 93.85 651.40 Tm -(GPSrx) Tj -ET -1 J -1 j -1.00 w -0.00 0.00 1.00 RG -0.00 g -[] 0 d -145.000 655.000 m -140.000 650.000 l -125.000 650.000 l -125.000 660.000 l -140.000 660.000 l -145.000 655.000 l -S -BT -/F2 11 Tf -11.00 TL -0.000 0.000 1.000 rg -1.00 -0.00 0.00 1.00 94.40 641.40 Tm -(GPStx) Tj -ET -1 J -1 j -1.00 w -0.00 0.00 1.00 RG -0.00 g -[] 0 d -145.000 645.000 m -140.000 640.000 l -125.000 640.000 l -125.000 650.000 l -140.000 650.000 l -145.000 645.000 l -S -1 J -1 j -1.00 w -0.00 0.53 0.00 RG -0.00 g -[] 0 d -290.000 615.000 m -280.000 615.000 l -S -1 J -1 j -1.00 w -0.00 0.53 0.00 RG -0.00 g -[] 0 d -290.000 635.000 m -280.000 635.000 l -S -1 J -1 j -1.00 w -0.00 0.53 0.00 RG -0.00 g -[] 0 d -290.000 645.000 m -280.000 645.000 l -S -1 J -1 j -1.00 w -0.00 0.53 0.00 RG -0.00 g -[] 0 d -345.000 695.000 m -280.000 695.000 l -S -1 J -1 j -1.00 w -0.00 0.53 0.00 RG -0.00 g -[] 0 d -345.000 675.000 m -280.000 675.000 l -S -BT -/F2 11 Tf -11.00 TL -0.000 0.000 1.000 rg -1.00 -0.00 0.00 1.00 312.00 601.60 Tm -(IRQ) Tj -ET -1 J -1 j -1.00 w -0.00 0.00 1.00 RG -0.00 g -[] 0 d -290.000 605.000 m -295.000 610.000 l -310.000 610.000 l -310.000 600.000 l -295.000 600.000 l -290.000 605.000 l -S -BT -/F2 11 Tf -11.00 TL -0.000 0.000 1.000 rg -1.00 -0.00 0.00 1.00 312.50 591.60 Tm -(NRST) Tj -ET -1 J -1 j -1.00 w -0.00 0.00 1.00 RG -0.00 g -[] 0 d -290.000 595.000 m -295.000 600.000 l -310.000 600.000 l -310.000 590.000 l -295.000 590.000 l -290.000 595.000 l -S -BT -/F2 12 Tf -12.00 TL -0.000 g -1.00 -0.00 0.00 1.00 356.00 671.00 Tm -(VCC) Tj -ET -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -355.000 675.000 m -345.000 675.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -355.000 680.000 m -355.000 670.000 l -S -BT -/F2 11 Tf -11.00 TL -0.000 0.000 1.000 rg -1.00 -0.00 0.00 1.00 312.00 621.60 Tm -(CS) Tj -ET -1 J -1 j -1.00 w -0.00 0.00 1.00 RG -0.00 g -[] 0 d -290.000 625.000 m -295.000 630.000 l -310.000 630.000 l -310.000 620.000 l -295.000 620.000 l -290.000 625.000 l -S -BT -/F2 11 Tf -11.00 TL -0.000 0.000 1.000 rg -1.00 -0.00 0.00 1.00 312.00 631.60 Tm -(MOSI) Tj -ET -1 J -1 j -1.00 w -0.00 0.00 1.00 RG -0.00 g -[] 0 d -290.000 635.000 m -295.000 640.000 l -310.000 640.000 l -310.000 630.000 l -295.000 630.000 l -290.000 635.000 l -S -BT -/F2 11 Tf -11.00 TL -0.000 0.000 1.000 rg -1.00 -0.00 0.00 1.00 312.00 641.60 Tm -(MISO) Tj -ET -1 J -1 j -1.00 w -0.00 0.00 1.00 RG -0.00 g -[] 0 d -290.000 645.000 m -295.000 650.000 l -310.000 650.000 l -310.000 640.000 l -295.000 640.000 l -290.000 645.000 l -S -BT -/F2 11 Tf -11.00 TL -0.000 0.000 1.000 rg -1.00 -0.00 0.00 1.00 311.84 611.60 Tm -(SCk) Tj -ET -1 J -1 j -1.00 w -0.00 0.00 1.00 RG -0.00 g -[] 0 d -290.000 615.000 m -295.000 620.000 l -310.000 620.000 l -310.000 610.000 l -295.000 610.000 l -290.000 615.000 l -S -BT -/F2 11 Tf -11.00 TL -0.000 0.000 1.000 rg -1.00 -0.00 0.00 1.00 311.70 651.60 Tm -(BUSY) Tj -ET -1 J -1 j -1.00 w -0.00 0.00 1.00 RG -0.00 g -[] 0 d -290.000 655.000 m -295.000 660.000 l -310.000 660.000 l -310.000 650.000 l -295.000 650.000 l -290.000 655.000 l -S -BT -/F2 12 Tf -12.00 TL -0.000 g -1.00 -0.00 0.00 1.00 361.50 691.00 Tm -(GND) Tj -ET -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -355.000 695.000 m -345.000 695.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -355.000 686.000 m -355.000 704.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -357.000 689.000 m -357.000 701.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -359.000 692.000 m -359.000 698.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -361.000 694.000 m -361.000 696.000 l -S -BT -/F2 11 Tf -11.00 TL -0.000 0.000 1.000 rg -1.00 -0.00 0.00 1.00 302.50 701.30 Tm -(Batt) Tj -ET -1 J -1 j -1.00 w -0.00 0.00 1.00 RG -0.00 g -[] 0 d -280.000 705.000 m -285.000 710.000 l -300.000 710.000 l -300.000 700.000 l -285.000 700.000 l -280.000 705.000 l -S -10.00 w -BT -/F1 13 Tf -13.00 TL -0.000 0.000 1.000 rg -1015.00 25.00 Td -(Nom De Tom) Tj -ET -BT -/F2 11 Tf -11.00 TL -0.000 0.000 1.000 rg -1.00 -0.00 0.00 1.00 647.00 251.60 Tm -(IRQ) Tj -ET -1 J -1 j -1.00 w -0.00 0.00 1.00 RG -0.00 g -[] 0 d -625.000 255.000 m -630.000 260.000 l -645.000 260.000 l -645.000 250.000 l -630.000 250.000 l -625.000 255.000 l -S -BT -/F2 12 Tf -12.00 TL -0.000 g -1.00 -0.00 0.00 1.00 513.00 297.00 Tm -(VCC) Tj -ET -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -525.000 295.000 m -525.000 285.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -520.000 295.000 m -530.000 295.000 l -S -1 J -1 j -1.00 w -0.00 0.53 0.00 RG -0.00 g -[] 0 d -625.000 175.000 m -625.000 215.000 l -S -BT -/F2 12 Tf -12.00 TL -0.000 g -1.00 -0.00 0.00 1.00 612.00 149.00 Tm -(GND) Tj -ET -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -625.000 165.000 m -625.000 175.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -616.000 165.000 m -634.000 165.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -619.000 163.000 m -631.000 163.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -622.000 161.000 m -628.000 161.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -624.000 159.000 m -626.000 159.000 l -S -BT -/F2 11 Tf -11.00 TL -0.000 0.000 1.000 rg -1.00 -0.00 0.00 1.00 474.33 201.85 Tm -(BUSY) Tj -ET -1 J -1 j -1.00 w -0.00 0.00 1.00 RG -0.00 g -[] 0 d -525.000 205.000 m -520.000 200.000 l -505.000 200.000 l -505.000 210.000 l -520.000 210.000 l -525.000 205.000 l -S -BT -/F2 11 Tf -11.00 TL -0.000 0.000 1.000 rg -1.00 -0.00 0.00 1.00 473.74 191.85 Tm -(RXEN) Tj -ET -1 J -1 j -1.00 w -0.00 0.00 1.00 RG -0.00 g -[] 0 d -525.000 195.000 m -520.000 190.000 l -505.000 190.000 l -505.000 200.000 l -520.000 200.000 l -525.000 195.000 l -S -BT -/F2 11 Tf -11.00 TL -0.000 0.000 1.000 rg -1.00 -0.00 0.00 1.00 482.41 231.85 Tm -(SCK) Tj -ET -1 J -1 j -1.00 w -0.00 0.00 1.00 RG -0.00 g -[] 0 d -525.000 235.000 m -520.000 230.000 l -505.000 230.000 l -505.000 240.000 l -520.000 240.000 l -525.000 235.000 l -S -BT -/F2 11 Tf -11.00 TL -0.000 0.000 1.000 rg -1.00 -0.00 0.00 1.00 476.41 251.85 Tm -(MISO) Tj -ET -1 J -1 j -1.00 w -0.00 0.00 1.00 RG -0.00 g -[] 0 d -525.000 255.000 m -520.000 250.000 l -505.000 250.000 l -505.000 260.000 l -520.000 260.000 l -525.000 255.000 l -S -BT -/F2 11 Tf -11.00 TL -0.000 0.000 1.000 rg -1.00 -0.00 0.00 1.00 476.48 241.85 Tm -(MOSI) Tj -ET -1 J -1 j -1.00 w -0.00 0.00 1.00 RG -0.00 g -[] 0 d -525.000 245.000 m -520.000 240.000 l -505.000 240.000 l -505.000 250.000 l -520.000 250.000 l -525.000 245.000 l -S -BT -/F2 11 Tf -11.00 TL -0.000 0.000 1.000 rg -1.00 -0.00 0.00 1.00 489.69 221.45 Tm -(CS) Tj -ET -1 J -1 j -1.00 w -0.00 0.00 1.00 RG -0.00 g -[] 0 d -525.000 225.000 m -520.000 220.000 l -505.000 220.000 l -505.000 230.000 l -520.000 230.000 l -525.000 225.000 l -S -BT -/F2 11 Tf -11.00 TL -0.000 0.000 1.000 rg -1.00 -0.00 0.00 1.00 647.01 241.15 Tm -(DIO2) Tj -ET -1 J -1 j -1.00 w -0.00 0.00 1.00 RG -0.00 g -[] 0 d -625.000 245.000 m -630.000 250.000 l -645.000 250.000 l -645.000 240.000 l -630.000 240.000 l -625.000 245.000 l -S -BT -/F2 11 Tf -11.00 TL -0.000 0.000 1.000 rg -1.00 -0.00 0.00 1.00 474.33 181.85 Tm -(TXEN) Tj -ET -1 J -1 j -1.00 w -0.00 0.00 1.00 RG -0.00 g -[] 0 d -525.000 185.000 m -520.000 180.000 l -505.000 180.000 l -505.000 190.000 l -520.000 190.000 l -525.000 185.000 l -S -BT -/F2 11 Tf -11.00 TL -0.000 0.000 1.000 rg -1.00 -0.00 0.00 1.00 475.06 261.57 Tm -(NRST) Tj -ET -1 J -1 j -1.00 w -0.00 0.00 1.00 RG -0.00 g -[] 0 d -525.000 265.000 m -520.000 260.000 l -505.000 260.000 l -505.000 270.000 l -520.000 270.000 l -525.000 265.000 l -S -10.00 w -BT -/F3 9 Tf -9.00 TL -0.000 0.000 0.502 rg -568.96 298.33 Td -(SX1262_MOD) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -584.58 282.00 Td -(ANT) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -610.00 286.00 Td -(16) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -625.000 285.000 m -605.000 285.000 l -S -10.00 w -BT -/F3 9 Tf -9.00 TL -0.000 0.000 0.502 rg -568.96 307.33 Td -(CORE_SX1262) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -582.36 202.00 Td -(GND) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -610.00 206.00 Td -(2) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -625.000 205.000 m -605.000 205.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -547.00 192.00 Td -(RXEN) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -534.28 196.00 Td -(3) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -525.000 195.000 m -545.000 195.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -547.00 182.00 Td -(TXEN) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -534.28 186.00 Td -(4) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -525.000 185.000 m -545.000 185.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -579.49 242.00 Td -(DIO2) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -610.00 246.00 Td -(5) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -625.000 245.000 m -605.000 245.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -579.49 252.00 Td -(DIO1) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -610.00 256.00 Td -(6) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -625.000 255.000 m -605.000 255.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -582.36 192.00 Td -(GND) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -610.00 196.00 Td -(7) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -625.000 195.000 m -605.000 195.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -547.00 282.00 Td -(3V3) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -534.28 286.00 Td -(8) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -525.000 285.000 m -545.000 285.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -547.00 202.00 Td -(BUSY) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -534.28 206.00 Td -(9) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -525.000 205.000 m -545.000 205.000 l -S -1.00 w -0.53 0.00 0.00 RG -545.00 265.00 m 545.00 266.66 543.66 268.00 542.00 268.00 c -540.34 268.00 539.00 266.66 539.00 265.00 c -539.00 263.34 540.34 262.00 542.00 262.00 c -543.66 262.00 545.00 263.34 545.00 265.00 c -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -547.00 262.00 Td -(RST) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -528.57 266.00 Td -(10) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -525.000 265.000 m -539.000 265.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -547.00 252.00 Td -(MISO) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -528.57 256.00 Td -(11) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -525.000 255.000 m -545.000 255.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -547.00 242.00 Td -(MOSI) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -528.57 246.00 Td -(12) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -525.000 245.000 m -545.000 245.000 l -S -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -545.000 232.000 m -548.000 235.000 l -545.000 238.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -547.00 232.00 Td -(CLK) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -528.57 236.00 Td -(13) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -525.000 235.000 m -545.000 235.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -547.00 222.00 Td -(CS) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -528.57 226.00 Td -(14) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -525.000 225.000 m -545.000 225.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -582.36 182.00 Td -(GND) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -610.00 186.00 Td -(15) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -625.000 185.000 m -605.000 185.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -582.36 212.00 Td -(GND) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -610.00 216.00 Td -(1) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -625.000 215.000 m -605.000 215.000 l -S -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -0.00 g -[] 0 d -550.000 295.000 m -600.000 295.000 l -S -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -0.00 g -[] 0 d -605.000 290.000 m -605.000 180.000 l -S -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -0.00 g -[] 0 d -600.000 175.000 m -550.000 175.000 l -S -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -0.00 g -[] 0 d -545.000 180.000 m -545.000 290.000 l -S -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -545.000 290.000 m -545.000 295.000 545.000 295.000 550.000 295.000 c -S -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -600.000 295.000 m -605.000 295.000 605.000 295.000 605.000 290.000 c -S -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -605.000 180.000 m -605.000 175.000 605.000 175.000 600.000 175.000 c -S -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -545.000 180.000 m -545.000 175.000 545.000 175.000 550.000 175.000 c -S -BT -/F2 11 Tf -11.00 TL -0.000 0.000 1.000 rg -1.00 -0.00 0.00 1.00 737.72 681.45 Tm -(MCU_RXEN) Tj -ET -1 J -1 j -1.00 w -0.00 0.00 1.00 RG -0.00 g -[] 0 d -820.000 685.000 m -815.000 680.000 l -800.000 680.000 l -800.000 690.000 l -815.000 690.000 l -820.000 685.000 l -S -10.00 w -BT -/F3 9 Tf -9.00 TL -0.000 0.000 0.502 rg -1115.00 362.67 Td -(100uF) Tj -ET -10.00 w -BT -/F3 9 Tf -9.00 TL -0.000 0.000 0.502 rg -1115.00 371.67 Td -(C1) Tj -ET -1 J -1 j -1.00 w -0.63 0.00 0.00 RG -0.00 g -[] 0 d -1113.000 373.000 m -1097.000 373.000 l -S -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -1105.000 365.000 m -1105.000 355.000 l -S -1 J -1 j -1.00 w -0.63 0.00 0.00 RG -0.00 g -[] 0 d -1105.000 385.000 m -1105.000 377.000 l -S -1 J -1 j -1.00 w -0.63 0.00 0.00 RG -0.00 g -[] 0 d -1097.000 377.000 m -1113.000 377.000 l -S -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -1105.000 385.000 m -1105.000 395.000 l -S -1 J -1 j -1.00 w -0.63 0.00 0.00 RG -0.00 g -[] 0 d -1105.000 373.000 m -1105.000 365.000 l -S -10.00 w -BT -/F3 9 Tf -9.00 TL -0.000 0.000 0.502 rg -1065.00 362.67 Td -(100uF) Tj -ET -10.00 w -BT -/F3 9 Tf -9.00 TL -0.000 0.000 0.502 rg -1065.00 371.67 Td -(C2) Tj -ET -1 J -1 j -1.00 w -0.63 0.00 0.00 RG -0.00 g -[] 0 d -1063.000 373.000 m -1047.000 373.000 l -S -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -1055.000 365.000 m -1055.000 355.000 l -S -1 J -1 j -1.00 w -0.63 0.00 0.00 RG -0.00 g -[] 0 d -1055.000 385.000 m -1055.000 377.000 l -S -1 J -1 j -1.00 w -0.63 0.00 0.00 RG -0.00 g -[] 0 d -1047.000 377.000 m -1063.000 377.000 l -S -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -1055.000 385.000 m -1055.000 395.000 l -S -1 J -1 j -1.00 w -0.63 0.00 0.00 RG -0.00 g -[] 0 d -1055.000 373.000 m -1055.000 365.000 l -S -BT -/F2 12 Tf -12.00 TL -0.000 g -1.00 -0.00 0.00 1.00 1092.00 329.00 Tm -(GND) Tj -ET -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -1105.000 345.000 m -1105.000 355.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -1096.000 345.000 m -1114.000 345.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -1099.000 343.000 m -1111.000 343.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -1102.000 341.000 m -1108.000 341.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -1104.000 339.000 m -1106.000 339.000 l -S -BT -/F2 12 Tf -12.00 TL -0.000 g -1.00 -0.00 0.00 1.00 1043.00 407.00 Tm -(VCC) Tj -ET -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -1055.000 405.000 m -1055.000 395.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -1050.000 405.000 m -1060.000 405.000 l -S -BT -/F2 12 Tf -12.00 TL -0.000 g -1.00 -0.00 0.00 1.00 1042.00 327.76 Tm -(GND) Tj -ET -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -1055.000 345.000 m -1055.000 355.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -1046.000 345.000 m -1064.000 345.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -1049.000 343.000 m -1061.000 343.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -1052.000 341.000 m -1058.000 341.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -1054.000 339.000 m -1056.000 339.000 l -S -BT -/F2 12 Tf -12.00 TL -0.000 g -1.00 -0.00 0.00 1.00 1095.00 407.00 Tm -(+5V) Tj -ET -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -1105.000 405.000 m -1105.000 395.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -1100.000 405.000 m -1110.000 405.000 l -S -BT -/F2 11 Tf -11.00 TL -0.000 0.000 1.000 rg -1.00 -0.00 0.00 1.00 102.98 611.40 Tm -(SCL) Tj -ET -1 J -1 j -1.00 w -0.00 0.00 1.00 RG -0.00 g -[] 0 d -145.000 615.000 m -140.000 610.000 l -125.000 610.000 l -125.000 620.000 l -140.000 620.000 l -145.000 615.000 l -S -BT -/F2 11 Tf -11.00 TL -0.000 0.000 1.000 rg -1.00 -0.00 0.00 1.00 101.11 601.40 Tm -(SDA) Tj -ET -1 J -1 j -1.00 w -0.00 0.00 1.00 RG -0.00 g -[] 0 d -145.000 605.000 m -140.000 600.000 l -125.000 600.000 l -125.000 610.000 l -140.000 610.000 l -145.000 605.000 l -S -1 J -1 j -1.00 w -0.00 0.53 0.00 RG -0.00 g -[] 0 d -820.000 685.000 m -840.000 685.000 l -S -BT -/F2 11 Tf -11.00 TL -0.000 0.000 1.000 rg -1.00 -0.00 0.00 1.00 77.72 661.45 Tm -(MCU_RXEN) Tj -ET -1 J -1 j -1.00 w -0.00 0.00 1.00 RG -0.00 g -[] 0 d -160.000 665.000 m -155.000 660.000 l -140.000 660.000 l -140.000 670.000 l -155.000 670.000 l -160.000 665.000 l -S -1 J -1 j -1.00 w -0.00 0.53 0.00 RG -0.00 g -[] 0 d -160.000 715.000 m -165.000 715.000 l -S -1 J -1 j -1.00 w -0.20 0.80 0.20 RG -[] 0 d -621.000 289.000 m -629.000 281.000 l -629.000 289.000 m -621.000 281.000 l -S -1 J -1 j -1.00 w -0.20 0.80 0.20 RG -[] 0 d -251.000 439.000 m -259.000 431.000 l -259.000 439.000 m -251.000 431.000 l -S -1 J -1 j -1.00 w -0.20 0.80 0.20 RG -[] 0 d -251.000 449.000 m -259.000 441.000 l -259.000 449.000 m -251.000 441.000 l -S -1 J -1 j -1.00 w -0.20 0.80 0.20 RG -[] 0 d -341.000 449.000 m -349.000 441.000 l -349.000 449.000 m -341.000 441.000 l -S -1 J -1 j -1.00 w -0.20 0.80 0.20 RG -[] 0 d -251.000 409.000 m -259.000 401.000 l -259.000 409.000 m -251.000 401.000 l -S -1 J -1 j -1.00 w -0.20 0.80 0.20 RG -[] 0 d -171.000 479.000 m -179.000 471.000 l -179.000 479.000 m -171.000 471.000 l -S -BT -/F2 11 Tf -11.00 TL -0.000 0.000 1.000 rg -1.00 -0.00 0.00 1.00 432.69 661.28 Tm -(ADC) Tj -ET -1 J -1 j -1.00 w -0.00 0.00 1.00 RG -0.00 g -[] 0 d -410.000 665.000 m -415.000 670.000 l -430.000 670.000 l -430.000 660.000 l -415.000 660.000 l -410.000 665.000 l -S -10.00 w -BT -/F3 9 Tf -9.00 TL -0.000 0.000 0.502 rg -213.86 728.26 Td -(PRO_MICRO_NRF52840_29P) Tj -ET -10.00 w -BT -/F3 9 Tf -9.00 TL -0.000 0.000 0.502 rg -213.86 737.11 Td -(PRO-MICRO) Tj -ET -2 J -0 j -100 M -1.00 w -0.55 0.14 0.14 RG -[] 0 d -180.00 725.00 80.00 -160.00 re -S -BT -/F1 7 Tf -7.00 TL -0.553 0.137 0.137 rg -234.94 702.00 Td -(BATIN) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.553 0.137 0.137 rg -266.00 706.00 Td -(25) Tj -ET -1 J -1 j -1.00 w -0.55 0.14 0.14 RG -[] 0 d -280.000 705.000 m -260.000 705.000 l -S -BT -/F1 7 Tf -7.00 TL -0.553 0.137 0.137 rg -240.95 692.00 Td -(GND) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.553 0.137 0.137 rg -266.00 696.00 Td -(24) Tj -ET -1 J -1 j -1.00 w -0.55 0.14 0.14 RG -[] 0 d -280.000 695.000 m -260.000 695.000 l -S -BT -/F1 7 Tf -7.00 TL -0.553 0.137 0.137 rg -243.04 682.00 Td -(RST) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.553 0.137 0.137 rg -266.00 686.00 Td -(23) Tj -ET -1 J -1 j -1.00 w -0.55 0.14 0.14 RG -[] 0 d -280.000 685.000 m -260.000 685.000 l -S -BT -/F1 7 Tf -7.00 TL -0.553 0.137 0.137 rg -226.28 672.00 Td -(3.3v Out) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.553 0.137 0.137 rg -266.00 676.00 Td -(22) Tj -ET -1 J -1 j -1.00 w -0.55 0.14 0.14 RG -[] 0 d -280.000 675.000 m -260.000 675.000 l -S -BT -/F1 7 Tf -7.00 TL -0.553 0.137 0.137 rg -236.90 632.00 Td -(P1.15) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.553 0.137 0.137 rg -266.00 636.00 Td -(18) Tj -ET -1 J -1 j -1.00 w -0.55 0.14 0.14 RG -[] 0 d -280.000 635.000 m -260.000 635.000 l -S -BT -/F1 7 Tf -7.00 TL -0.553 0.137 0.137 rg -236.90 662.00 Td -(P0.31) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.553 0.137 0.137 rg -266.00 666.00 Td -(21) Tj -ET -1 J -1 j -1.00 w -0.55 0.14 0.14 RG -[] 0 d -280.000 665.000 m -260.000 665.000 l -S -BT -/F1 7 Tf -7.00 TL -0.553 0.137 0.137 rg -183.00 692.00 Td -(P0.08) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.553 0.137 0.137 rg -168.28 696.00 Td -(3) Tj -ET -1 J -1 j -1.00 w -0.55 0.14 0.14 RG -[] 0 d -160.000 695.000 m -180.000 695.000 l -S -BT -/F1 7 Tf -7.00 TL -0.553 0.137 0.137 rg -236.90 622.00 Td -(P1.13) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.553 0.137 0.137 rg -266.00 626.00 Td -(17) Tj -ET -1 J -1 j -1.00 w -0.55 0.14 0.14 RG -[] 0 d -280.000 625.000 m -260.000 625.000 l -S -BT -/F1 7 Tf -7.00 TL -0.553 0.137 0.137 rg -236.90 652.00 Td -(P0.29) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.553 0.137 0.137 rg -266.00 656.00 Td -(20) Tj -ET -1 J -1 j -1.00 w -0.55 0.14 0.14 RG -[] 0 d -280.000 655.000 m -260.000 655.000 l -S -BT -/F1 7 Tf -7.00 TL -0.553 0.137 0.137 rg -236.90 612.00 Td -(P1.11) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.553 0.137 0.137 rg -266.00 616.00 Td -(16) Tj -ET -1 J -1 j -1.00 w -0.55 0.14 0.14 RG -[] 0 d -280.000 615.000 m -260.000 615.000 l -S -BT -/F1 7 Tf -7.00 TL -0.553 0.137 0.137 rg -236.90 642.00 Td -(P0.02) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.553 0.137 0.137 rg -266.00 646.00 Td -(19) Tj -ET -1 J -1 j -1.00 w -0.55 0.14 0.14 RG -[] 0 d -280.000 645.000 m -260.000 645.000 l -S -BT -/F1 7 Tf -7.00 TL -0.553 0.137 0.137 rg -183.00 682.00 Td -(GND) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.553 0.137 0.137 rg -168.28 686.00 Td -(4) Tj -ET -1 J -1 j -1.00 w -0.55 0.14 0.14 RG -[] 0 d -160.000 685.000 m -180.000 685.000 l -S -1.00 w -0.55 0.14 0.14 RG -180.00 705.00 m 180.00 706.66 178.66 708.00 177.00 708.00 c -175.34 708.00 174.00 706.66 174.00 705.00 c -174.00 703.34 175.34 702.00 177.00 702.00 c -178.66 702.00 180.00 703.34 180.00 705.00 c -S -BT -/F1 7 Tf -7.00 TL -0.553 0.137 0.137 rg -183.00 702.00 Td -(P0.06) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.553 0.137 0.137 rg -168.28 706.00 Td -(2) Tj -ET -1 J -1 j -1.00 w -0.55 0.14 0.14 RG -[] 0 d -160.000 705.000 m -174.000 705.000 l -S -BT -/F1 7 Tf -7.00 TL -0.553 0.137 0.137 rg -236.90 592.00 Td -(P0.09) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.553 0.137 0.137 rg -266.00 596.00 Td -(14) Tj -ET -1 J -1 j -1.00 w -0.55 0.14 0.14 RG -[] 0 d -280.000 595.000 m -260.000 595.000 l -S -BT -/F1 7 Tf -7.00 TL -0.553 0.137 0.137 rg -236.90 602.00 Td -(P0.10) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.553 0.137 0.137 rg -266.00 606.00 Td -(15) Tj -ET -1 J -1 j -1.00 w -0.55 0.14 0.14 RG -[] 0 d -280.000 605.000 m -260.000 605.000 l -S -BT -/F1 7 Tf -7.00 TL -0.553 0.137 0.137 rg -183.00 602.00 Td -(P1.04) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.553 0.137 0.137 rg -162.57 606.00 Td -(12) Tj -ET -1 J -1 j -1.00 w -0.55 0.14 0.14 RG -[] 0 d -160.000 605.000 m -180.000 605.000 l -S -BT -/F1 7 Tf -7.00 TL -0.553 0.137 0.137 rg -183.00 672.00 Td -(GND) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.553 0.137 0.137 rg -168.28 676.00 Td -(5) Tj -ET -1 J -1 j -1.00 w -0.55 0.14 0.14 RG -[] 0 d -160.000 675.000 m -180.000 675.000 l -S -BT -/F1 7 Tf -7.00 TL -0.553 0.137 0.137 rg -183.00 662.00 Td -(P0.17) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.553 0.137 0.137 rg -168.28 666.00 Td -(6) Tj -ET -1 J -1 j -1.00 w -0.55 0.14 0.14 RG -[] 0 d -160.000 665.000 m -180.000 665.000 l -S -BT -/F1 7 Tf -7.00 TL -0.553 0.137 0.137 rg -183.00 652.00 Td -(P0.20) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.553 0.137 0.137 rg -168.28 656.00 Td -(7) Tj -ET -1 J -1 j -1.00 w -0.55 0.14 0.14 RG -[] 0 d -160.000 655.000 m -180.000 655.000 l -S -BT -/F1 7 Tf -7.00 TL -0.553 0.137 0.137 rg -183.00 642.00 Td -(P0.22) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.553 0.137 0.137 rg -168.28 646.00 Td -(8) Tj -ET -1 J -1 j -1.00 w -0.55 0.14 0.14 RG -[] 0 d -160.000 645.000 m -180.000 645.000 l -S -BT -/F1 7 Tf -7.00 TL -0.553 0.137 0.137 rg -183.00 632.00 Td -(P0.24) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.553 0.137 0.137 rg -168.28 636.00 Td -(9) Tj -ET -1 J -1 j -1.00 w -0.55 0.14 0.14 RG -[] 0 d -160.000 635.000 m -180.000 635.000 l -S -BT -/F1 7 Tf -7.00 TL -0.553 0.137 0.137 rg -183.00 622.00 Td -(P1.00) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.553 0.137 0.137 rg -162.57 626.00 Td -(10) Tj -ET -1 J -1 j -1.00 w -0.55 0.14 0.14 RG -[] 0 d -160.000 625.000 m -180.000 625.000 l -S -BT -/F1 7 Tf -7.00 TL -0.553 0.137 0.137 rg -183.00 612.00 Td -(P0.11) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.553 0.137 0.137 rg -162.57 616.00 Td -(11) Tj -ET -1 J -1 j -1.00 w -0.55 0.14 0.14 RG -[] 0 d -160.000 615.000 m -180.000 615.000 l -S -BT -/F1 7 Tf -7.00 TL -0.553 0.137 0.137 rg -183.00 592.00 Td -(P1.06) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.553 0.137 0.137 rg -162.57 596.00 Td -(13) Tj -ET -1 J -1 j -1.00 w -0.55 0.14 0.14 RG -[] 0 d -160.000 595.000 m -180.000 595.000 l -S -BT -/F1 7 Tf -7.00 TL -0.553 0.137 0.137 rg -0.00 1.00 -1.00 0.00 193.00 568.00 Tm -(P1.01) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.553 0.137 0.137 rg -0.00 1.00 -1.00 0.00 189.00 547.57 Tm -(27) Tj -ET -1 J -1 j -1.00 w -0.55 0.14 0.14 RG -[] 0 d -190.000 545.000 m -190.000 565.000 l -S -BT -/F1 7 Tf -7.00 TL -0.553 0.137 0.137 rg -0.00 1.00 -1.00 0.00 203.00 568.00 Tm -(P1.02) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.553 0.137 0.137 rg -0.00 1.00 -1.00 0.00 199.00 547.57 Tm -(28) Tj -ET -1 J -1 j -1.00 w -0.55 0.14 0.14 RG -[] 0 d -200.000 545.000 m -200.000 565.000 l -S -BT -/F1 7 Tf -7.00 TL -0.553 0.137 0.137 rg -0.00 1.00 -1.00 0.00 213.00 568.00 Tm -(P1.07) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.553 0.137 0.137 rg -0.00 1.00 -1.00 0.00 209.00 547.57 Tm -(29) Tj -ET -1 J -1 j -1.00 w -0.55 0.14 0.14 RG -[] 0 d -210.000 545.000 m -210.000 565.000 l -S -BT -/F1 7 Tf -7.00 TL -0.553 0.137 0.137 rg -234.94 712.00 Td -(BATIN) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.553 0.137 0.137 rg -266.00 716.00 Td -(26) Tj -ET -1 J -1 j -1.00 w -0.55 0.14 0.14 RG -[] 0 d -280.000 715.000 m -260.000 715.000 l -S -BT -/F1 7 Tf -7.00 TL -0.553 0.137 0.137 rg -183.00 712.00 Td -(GND) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.553 0.137 0.137 rg -168.28 716.00 Td -(1) Tj -ET -1 J -1 j -1.00 w -0.55 0.14 0.14 RG -[] 0 d -160.000 715.000 m -180.000 715.000 l -S -10.00 w -BT -/F3 9 Tf -9.00 TL -0.000 0.000 0.502 rg -864.25 253.00 Td -(RA-02_C9900010926) Tj -ET -10.00 w -BT -/F3 9 Tf -9.00 TL -0.000 0.000 0.502 rg -864.25 262.00 Td -(RA-02) Tj -ET -2 J -0 j -100 M -1.00 w -0.53 0.00 0.00 RG -[] 0 d -837.000 250.000 m -903.000 250.000 l -904.105 250.000 905.000 249.105 905.000 248.000 c -905.000 162.000 l -905.000 160.895 903.895 160.000 903.000 160.000 c -837.000 160.000 l -835.895 160.000 835.000 161.105 835.000 162.000 c -835.000 248.000 l -835.000 249.105 836.105 250.000 837.000 250.000 c -S -1.00 w -0.53 0.00 0.00 RG -0.53 0.00 0.00 rg -[] 0 d -841.50 245.00 m 841.50 245.83 840.83 246.50 840.00 246.50 c -839.17 246.50 838.50 245.83 838.50 245.00 c -838.50 244.17 839.17 243.50 840.00 243.50 c -840.83 243.50 841.50 244.17 841.50 245.00 c -B -BT -/F1 9 Tf -9.00 TL -0.000 g -838.70 236.00 Td -(GND) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 g -828.78 241.00 Td -(1) Tj -ET -1 J -1 j -1.00 w -0.00 G -[] 0 d -825.000 240.000 m -835.000 240.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 g -838.70 226.00 Td -(GND) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 g -828.78 231.00 Td -(2) Tj -ET -1 J -1 j -1.00 w -0.00 G -[] 0 d -825.000 230.000 m -835.000 230.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -838.70 216.00 Td -(3.3V) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -828.78 221.00 Td -(3) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -825.000 220.000 m -835.000 220.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -838.70 206.00 Td -(RESET) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -828.78 211.00 Td -(4) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -825.000 210.000 m -835.000 210.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -838.70 196.00 Td -(DIO0) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -828.78 201.00 Td -(5) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -825.000 200.000 m -835.000 200.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -838.70 186.00 Td -(DIO1) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -828.78 191.00 Td -(6) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -825.000 190.000 m -835.000 190.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -838.70 176.00 Td -(DIO2) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -828.78 181.00 Td -(7) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -825.000 180.000 m -835.000 180.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -838.70 166.00 Td -(DIO3) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -828.78 171.00 Td -(8) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -825.000 170.000 m -835.000 170.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 g -880.66 166.00 Td -(GND) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 g -905.50 171.00 Td -(9) Tj -ET -1 J -1 j -1.00 w -0.00 G -[] 0 d -915.000 170.000 m -905.000 170.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -877.79 176.00 Td -(DIO4) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -905.50 181.00 Td -(10) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -915.000 180.000 m -905.000 180.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -877.79 186.00 Td -(DIO5) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -905.50 191.00 Td -(11) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -915.000 190.000 m -905.000 190.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -882.64 196.00 Td -(SCK) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -905.50 201.00 Td -(12) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -915.000 200.000 m -905.000 200.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -876.71 206.00 Td -(MISO) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -905.50 211.00 Td -(13) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -915.000 210.000 m -905.000 210.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -876.71 216.00 Td -(MOSI) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -905.50 221.00 Td -(14) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -915.000 220.000 m -905.000 220.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -882.27 226.00 Td -(NSS) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 0.000 1.000 rg -905.50 231.00 Td -(15) Tj -ET -1 J -1 j -1.00 w -0.53 0.00 0.00 RG -[] 0 d -915.000 230.000 m -905.000 230.000 l -S -BT -/F1 9 Tf -9.00 TL -0.000 g -880.66 236.00 Td -(GND) Tj -ET -BT -/F1 9 Tf -9.00 TL -0.000 g -905.50 241.00 Td -(16) Tj -ET -1 J -1 j -1.00 w -0.00 G -[] 0 d -915.000 240.000 m -905.000 240.000 l -S -BT -/F2 11 Tf -11.00 TL -0.000 0.000 1.000 rg -1.00 -0.00 0.00 1.00 773.85 196.40 Tm -(BUSY) Tj -ET -1 J -1 j -1.00 w -0.00 0.00 1.00 RG -0.00 g -[] 0 d -825.000 200.000 m -820.000 195.000 l -805.000 195.000 l -805.000 205.000 l -820.000 205.000 l -825.000 200.000 l -S -BT -/F2 11 Tf -11.00 TL -0.000 0.000 1.000 rg -1.00 -0.00 0.00 1.00 937.00 226.60 Tm -(CS) Tj -ET -1 J -1 j -1.00 w -0.00 0.00 1.00 RG -0.00 g -[] 0 d -915.000 230.000 m -920.000 235.000 l -935.000 235.000 l -935.000 225.000 l -920.000 225.000 l -915.000 230.000 l -S -BT -/F2 11 Tf -11.00 TL -0.000 0.000 1.000 rg -1.00 -0.00 0.00 1.00 936.71 196.60 Tm -(SCK) Tj -ET -1 J -1 j -1.00 w -0.00 0.00 1.00 RG -0.00 g -[] 0 d -915.000 200.000 m -920.000 205.000 l -935.000 205.000 l -935.000 195.000 l -920.000 195.000 l -915.000 200.000 l -S -BT -/F2 11 Tf -11.00 TL -0.000 0.000 1.000 rg -1.00 -0.00 0.00 1.00 937.00 216.60 Tm -(MOSI) Tj -ET -1 J -1 j -1.00 w -0.00 0.00 1.00 RG -0.00 g -[] 0 d -915.000 220.000 m -920.000 225.000 l -935.000 225.000 l -935.000 215.000 l -920.000 215.000 l -915.000 220.000 l -S -BT -/F2 11 Tf -11.00 TL -0.000 0.000 1.000 rg -1.00 -0.00 0.00 1.00 937.00 206.60 Tm -(MISO) Tj -ET -1 J -1 j -1.00 w -0.00 0.00 1.00 RG -0.00 g -[] 0 d -915.000 210.000 m -920.000 215.000 l -935.000 215.000 l -935.000 205.000 l -920.000 205.000 l -915.000 210.000 l -S -BT -/F2 11 Tf -11.00 TL -0.000 0.000 1.000 rg -1.00 -0.00 0.00 1.00 775.06 206.57 Tm -(NRST) Tj -ET -1 J -1 j -1.00 w -0.00 0.00 1.00 RG -0.00 g -[] 0 d -825.000 210.000 m -820.000 205.000 l -805.000 205.000 l -805.000 215.000 l -820.000 215.000 l -825.000 210.000 l -S -BT -/F2 11 Tf -11.00 TL -0.000 0.000 1.000 rg -1.00 -0.00 0.00 1.00 784.19 186.40 Tm -(IRQ) Tj -ET -1 J -1 j -1.00 w -0.00 0.00 1.00 RG -0.00 g -[] 0 d -825.000 190.000 m -820.000 185.000 l -805.000 185.000 l -805.000 195.000 l -820.000 195.000 l -825.000 190.000 l -S -BT -/F2 12 Tf -12.00 TL -0.000 g -1.00 -0.00 0.00 1.00 931.50 166.00 Tm -(GND) Tj -ET -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -925.000 170.000 m -915.000 170.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -925.000 161.000 m -925.000 179.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -927.000 164.000 m -927.000 176.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -929.000 167.000 m -929.000 173.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -931.000 169.000 m -931.000 171.000 l -S -BT -/F2 12 Tf -12.00 TL -0.000 g -1.00 -0.00 0.00 1.00 931.50 236.00 Tm -(GND) Tj -ET -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -925.000 240.000 m -915.000 240.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -925.000 231.000 m -925.000 249.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -927.000 234.000 m -927.000 246.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -929.000 237.000 m -929.000 243.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -931.000 239.000 m -931.000 241.000 l -S -BT -/F2 12 Tf -12.00 TL -0.000 g -1.00 -0.00 0.00 1.00 782.50 236.00 Tm -(GND) Tj -ET -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -815.000 240.000 m -825.000 240.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -815.000 249.000 m -815.000 231.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -813.000 246.000 m -813.000 234.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -811.000 243.000 m -811.000 237.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -809.000 241.000 m -809.000 239.000 l -S -1 J -1 j -1.00 w -0.00 0.53 0.00 RG -0.00 g -[] 0 d -825.000 240.000 m -825.000 230.000 l -S -BT -/F2 12 Tf -12.00 TL -0.000 g -1.00 -0.00 0.00 1.00 789.82 216.00 Tm -(VCC) Tj -ET -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -815.000 220.000 m -825.000 220.000 l -S -1 J -1 j -1.00 w -0.00 G -0.00 g -[] 0 d -815.000 215.000 m -815.000 225.000 l -S -1 J -1 j -1.00 w -0.20 0.80 0.20 RG -[] 0 d -911.000 194.000 m -919.000 186.000 l -919.000 194.000 m -911.000 186.000 l -S -1 J -1 j -1.00 w -0.20 0.80 0.20 RG -[] 0 d -911.000 184.000 m -919.000 176.000 l -919.000 184.000 m -911.000 176.000 l -S -1 J -1 j -1.00 w -0.20 0.80 0.20 RG -[] 0 d -821.000 184.000 m -829.000 176.000 l -829.000 184.000 m -821.000 176.000 l -S -1 J -1 j -1.00 w -0.20 0.80 0.20 RG -[] 0 d -821.000 174.000 m -829.000 166.000 l -829.000 174.000 m -821.000 166.000 l -S -0.80 0.00 0.00 rg -652.50 420.00 m 652.50 421.38 651.38 422.50 650.00 422.50 c -648.62 422.50 647.50 421.38 647.50 420.00 c -647.50 418.62 648.62 417.50 650.00 417.50 c -651.38 417.50 652.50 418.62 652.50 420.00 c -f -0.80 0.00 0.00 rg -942.50 415.00 m 942.50 416.38 941.38 417.50 940.00 417.50 c -938.62 417.50 937.50 416.38 937.50 415.00 c -937.50 413.62 938.62 412.50 940.00 412.50 c -941.38 412.50 942.50 413.62 942.50 415.00 c -f -0.80 0.00 0.00 rg -942.50 395.00 m 942.50 396.38 941.38 397.50 940.00 397.50 c -938.62 397.50 937.50 396.38 937.50 395.00 c -937.50 393.62 938.62 392.50 940.00 392.50 c -941.38 392.50 942.50 393.62 942.50 395.00 c -f -0.80 0.00 0.00 rg -942.50 385.00 m 942.50 386.38 941.38 387.50 940.00 387.50 c -938.62 387.50 937.50 386.38 937.50 385.00 c -937.50 383.62 938.62 382.50 940.00 382.50 c -941.38 382.50 942.50 383.62 942.50 385.00 c -f -0.80 0.00 0.00 rg -942.50 375.00 m 942.50 376.38 941.38 377.50 940.00 377.50 c -938.62 377.50 937.50 376.38 937.50 375.00 c -937.50 373.62 938.62 372.50 940.00 372.50 c -941.38 372.50 942.50 373.62 942.50 375.00 c -f -0.80 0.00 0.00 rg -652.50 490.00 m 652.50 491.38 651.38 492.50 650.00 492.50 c -648.62 492.50 647.50 491.38 647.50 490.00 c -647.50 488.62 648.62 487.50 650.00 487.50 c -651.38 487.50 652.50 488.62 652.50 490.00 c -f -0.80 0.00 0.00 rg -942.50 465.00 m 942.50 466.38 941.38 467.50 940.00 467.50 c -938.62 467.50 937.50 466.38 937.50 465.00 c -937.50 463.62 938.62 462.50 940.00 462.50 c -941.38 462.50 942.50 463.62 942.50 465.00 c -f -0.80 0.00 0.00 rg -412.50 665.00 m 412.50 666.38 411.38 667.50 410.00 667.50 c -408.62 667.50 407.50 666.38 407.50 665.00 c -407.50 663.62 408.62 662.50 410.00 662.50 c -411.38 662.50 412.50 663.62 412.50 665.00 c -f -0.80 0.00 0.00 rg -162.50 680.00 m 162.50 681.38 161.38 682.50 160.00 682.50 c -158.62 682.50 157.50 681.38 157.50 680.00 c -157.50 678.62 158.62 677.50 160.00 677.50 c -161.38 677.50 162.50 678.62 162.50 680.00 c -f -0.80 0.00 0.00 rg -627.50 185.00 m 627.50 186.38 626.38 187.50 625.00 187.50 c -623.62 187.50 622.50 186.38 622.50 185.00 c -622.50 183.62 623.62 182.50 625.00 182.50 c -626.38 182.50 627.50 183.62 627.50 185.00 c -f -0.80 0.00 0.00 rg -627.50 195.00 m 627.50 196.38 626.38 197.50 625.00 197.50 c -623.62 197.50 622.50 196.38 622.50 195.00 c -622.50 193.62 623.62 192.50 625.00 192.50 c -626.38 192.50 627.50 193.62 627.50 195.00 c -f -0.80 0.00 0.00 rg -627.50 205.00 m 627.50 206.38 626.38 207.50 625.00 207.50 c -623.62 207.50 622.50 206.38 622.50 205.00 c -622.50 203.62 623.62 202.50 625.00 202.50 c -626.38 202.50 627.50 203.62 627.50 205.00 c -f -0.80 0.00 0.00 rg -827.50 240.00 m 827.50 241.38 826.38 242.50 825.00 242.50 c -823.62 242.50 822.50 241.38 822.50 240.00 c -822.50 238.62 823.62 237.50 825.00 237.50 c -826.38 237.50 827.50 238.62 827.50 240.00 c -f -q -102.00 0 0 20.00 706.00 30.50 cm -/I0 Do -Q -endstream -endobj -1 0 obj -<> -endobj -5 0 obj -<< -/Descent -209 -/CapHeight 727 -/StemV 0 -/Type /FontDescriptor -/Flags 32 -/FontBBox [-559 -303 1446 1050] -/FontName /Verdana -/ItalicAngle 0 -/Ascent 1005 ->> -endobj -6 0 obj -<> -endobj -7 0 obj -<< -/Type /Font -/BaseFont /Times-Roman -/Subtype /Type1 -/Encoding /WinAnsiEncoding -/FirstChar 32 -/LastChar 255 ->> -endobj -8 0 obj -<< -/Descent -325 -/CapHeight 500 -/StemV 80 -/Type /FontDescriptor -/Flags 32 -/FontBBox [-665 -325 2000 1006] -/FontName /Arial -/ItalicAngle 0 -/Ascent 1006 ->> -endobj -9 0 obj -<> -endobj -10 0 obj -<< -/Type /XObject -/Subtype /Image -/Width 520 -/Height 105 -/ColorSpace /DeviceRGB -/BitsPerComponent 8 -/DecodeParms <> -/SMask 11 0 R -/Length 6251 -/Filter /FlateDecode ->> -stream -xKqǣ{$V>:dm]uXbřZ>ـlhy-a$`!qMrN|G^_LUz8gfdf0~ SEQe"EQaAQEۢ&\gn_@QJk#o(sdAO%1GŘf!Eykn!׍&쯯),rlUӳR(p\DUnGo),m(.@Qҳ\9TEYdd#ܼsu.)rUXWqvkG<7yW(Jk)˶yns ?'ȶ섡RtOQ:oNsk(kȯ`U7(ƬjY8n+jRݠ(ʚK@+U೸֙ٚ4Bd^T7( -W ㊞g)}] 4TIQc2+D+H ]noh6<+S`?w~N*c{!q~j`z&VPRe 4hy4AQ5!vQo_}?cEQր*Q >yû(>?-hQV₞ɡ7ߦb~>HRǸ!<^}v~[TTaYUmAX$;9lφ"w9$3;yFQ<b~x-Q.}| ӱu -=9ԭ,jic>9R#bɪ|X_4eb'6~/v K!I"Nbejӎa?f򃔼b꧃1N 3ȺwErU4;q5A6<0tϤ"/R >X$%AZ1c{od6^Sr)k-䔎zD/⇺l,bzs&gGL1HXɲUS]ҋRBrēwLwX|楔]2CI2w,!S*Nf&Ybd]]9U`~qQ]e3}GؑJRYQ඾r֌.͌uNO79ĝ(0'OvJg{d ٥wq9 d+,өQBV=>qU6FB%1ڷD؈_gVPW)By24qaJԌ$pk8q]t q.!y-8́},wȷP%?C>4pTa߇8'ZNm19 m>Q eMi$- :1̱mt^"\˩ 84R!t "!W' }g>fd5hDuO3|m 0I?x]r fR4H4 8BKrYA1cL:{%o;gZpD1[{P|=`2(K^6gr_ 9Nو? 9,zYD4<*W)*/ws8y+<.E  嚞˹/u%NQA>9ϴch @93hvs~~I7k[|9)s&*þ[<#e -ٜӓ(m!èkzP\`\̞hOa^Q]Bvl:P]}~Ո~zyfduۅjz'> )o\n`i%4Nk4G RI]/[|M=.-uV'W3A̯ _:<`I-jFLP]KcH&Ϙ~ W178YnW=!l 3n4)ZPeS V5JR1=9ʾ0ɰAʛt1_d%gh l3%c -nŔZ!g?+sDNoz8'"L5(BY;q5~'ųZ"慊o?JaЛc\%\MsɔiLq]PE['Uʎ_z7(C3rt$@EBe ~9IoN@2?ԫ܍Q972ý.3@v^_}xZɶn\rR. ɳzz_MD I"t:V^Õ -2/;؁y 9 -©h  -5vWP޻H#n 6*8W$1{ eB*rb4$!&?˵bJŰr tǀYGȥI +~ǰװO -NRT62Oy\Yw񑭖C{kKl/,4_}5iCpQ ~"(Py.x1m0Mrڧ/ug]|&S_.=ę!V!t=)l7 t:ݤ'REgH&w 9Zr]3ܺrc;hH9yK *g\mcbG`M@Qj<|)~Et -8;N/$%4qI_KSp{pkx;Vi7u_N$C`F67ˣM-|2(/5)M*>] &%[aǫG$ +B=etHLEy0{oD̔KMpT 7FDA7?7nL aRq Q[^1@(R@lk7 \pRa [yC;e Dx{RSZ\;GɞJh^ipNbH݈x;^XRƼ\gbMBTK-Pp;mpP.v:j ;>*o -ĽIG1|%z((y}D -ć%O1$(wO _=Â7)^'*Bݲ#ULc 6p7nPȫN+\^y;PjG82ܚG?!Vne2doŭϼ -q1JO텘"ӻA n _5mE~qTiwu 8+i*x؆ Vu+^f|`(pp8ԔK} +Zq9= %&6紦uKe }FX\q5&HOhӴTGw;G*@ӡ`n]0ss}ZWhNѰt\\ JYEpN1'{im[/`5a!np)'6}ڟBUǭ~嗩op:Vf/fV F[ !t'<} S1ţc8}.4baΉ"+)!u(ofO| qB K?wCi!bѕ`+bjb1+ /miɭ g.X:rgUKK,3rڐf">[)˚+Dj bna 5[oL"pe|r(L^e̟gQYm5P]tݬĝ鐭xj1ՅDe\X[DV7TEF6/C@hbo1A`PoT:-݈}Vq!,J_j%D1#7;D$I(St(0'KK\SW}/;*b>)9]Jw+[CDi,ТˣRIᅖZe@l=Qbk tjAT`&wm3@] r28\ݸREMP+*zE❩{~|ԃMlwǶpZfo@3/M`cw$V LQ.%D6v]/p*4B )Ddb5~| -J -$ ۝zW3ovѨ}.R{L%o#4>݂q)C?.$a,nl&vJ,c >l*U4v)ɢ +iͿUM`娘lSƩP0؄Ha>"IUh+VQ7aJQr.6ҷǀf3@ŕ +z$e&SR#}[M"ڊPңey׀UA_bP1%2r JTd )J2&2M hIu*k -F\UQ kM}v(Fjb S(I"kc QO%)Ǧ(4L0gUwX^4'R_hXgmׯR9YiF|yLʕ.-%]g^\ϜTRSL1hAI6E  JZt)(PŠ(\@(rF- -endstream -endobj -11 0 obj -<< -/Type /XObject -/Subtype /Image -/Width 520 -/Height 105 -/ColorSpace /DeviceGray -/BitsPerComponent 8 -/DecodeParms <> -/Length 6577 -/Filter /FlateDecode ->> -stream -x] M?c=ȳJQj"*"xIJhGQ"o袌4(`B0_}{}3W3[g{{{k p*Bn"5 -߯Ds0uN\'!WfBKl5Gt\1g}vD]EԄ\᾽3珧/Sډ&B!ދIɻ:fVW&>ľ~Jz\z "`O>o/t[mGT]/u }EM4(uBW+I~ @SvXMq:~=|EP(BxF=hƕnWV/Ϗjpw,z~PvЬ]9ޕ,gv{NM[u)+++?DPɩ~^vJu? )2tGqL -Ï*ʋIȌ'#GR$$뵏[<ҡ䔊]]L'{KG9x)Lԉ -0G6.ݡ92P-b|N2NU#4}K&jG DԷc#By&ZdQ%ND#BݵԚv>)s\RPB>Wiк3k. -p5|aΈ ԈQQ5xO!XdAB"9g~2P<Z2s1v/tFtd/4Yx7wa鮋(:Bc7 E`~wa'S>{~ -0ㄠ)9Zm Δ>EE-# -_xEbeAvc]DeCӏOs@j=oj+58`~W/x} љ^VzMQv7GU| CEbTK0CEңU%ێ^NO{K-q"+W`No`VW~md1JwKf.*(c*s= -@qǭ/G|#A8 tf:Vo 2 g| <ʬ3i|r>V[%{gX7닅;%6&Z8(˰ؚ4+{> 3'HgvM=MBt:LnukTl4ӉHDuNşvGXX~!,!5 :M_cssQI>k#+0~ǎj'1\%P8ՠ* -C"hoW!;yTv9.:64lLd6cm)gӕzʅwLd`:kj/f5O'3w2^ $b^~ejS[z]*_~c#eI2[BӧC.⴨Q({z+WnݺB<=p2nCQ{w)[ב{\zZl[h.jyMt9]'pn7Fخ8Qp%| kN»3g2{ۜ#Հػ/r @`Six΀?_wijpo"9{sS]r61B)z<;h!/ eT  nGAQis79Wbp k=i~ݵ )KX[n -K3w̧$Apbz#D*J)2$)Wo&9VWeޱL\4@)69ֆՠH23`O@?zm~ GBQI ?=))Q,p1'nўXfb,̎ZY06 GYw1=n32 "0pyq0WH|,|WXm`R)^o;{ƌ&?Se * :D?d6G+RQsi9+v -EqwKmfP7JgNJ<ۀ3n-*:侟HvlwRs^0GjFy-NHu{T8xЪ̽ lmd`|8X~g.ty H/m' 4/8Nvor(dO9‹@ 1 bՄoA*e1Z쎕&L`- lR,l1xwݞ9t>D} p1~&)%,,0^j  4>N}춖1w6miW0crAI~aߊ?*{JIڢ &ѿ+qTnLdz.~vKDFyо\y%[6mjrg">tCrRY)S@ڂ`ZرB8]`.f1J|!\2Yc:H6c/Qۜvo^aL% Y"#qwǛqW_i,2Kz=◧IdV^\Y.G+ǻ6Dў4S)UrCD -7#lg]|H'bRNUt~i' n+ f&ݍPr'Rd:4".;mi氧/=j훌wJc neWCn/PA5^b6I#6O%2A|ǧ㽎ԛ^MN@e.z 1ۓ ý6(-+j 6g);8)tKTn?pgN{砱 -L!KI 4aҎ: -r \4l'qK)o5UY#/h 16ƁB2dV yK -6F8"ƍSSK_dRh4*_?LѫVS[+?޺ IzE=B"ҔH׸3n+4d(b"`W~D}Gerp{)/B"hMh@#j~4-f`IkLtؐΒԣI4&ݿ֬_7KR$hyhJw3Tu0W|P&L/Y50_d&ןZB. B4eӋPB?% -xYŰt# m#C`Xhѩp'zOCd~7 p?DQaBC%'Qn'w 9{JCitWq,YO֦ 0:෤'҉FF߹:iy* ~%k/aXxQT4 E Z%{sIb&EjMQw4붘.qT7P@j@LJ7AhY<3P`P(&ia8=n5'>t^B͗Ym\_p2]KN/*5^1@&OVJ` -hN%:OwoF|r`oX"-[ATw m0y( ǂہ[-68*6_558$Cne;h҉1c;[jȱ&gיDvoc߮I|RZ> BO>rBWȉT!9Oe 0+:BcbBP9i{6mB' e>Ty#xOZH{tT<` 3ރә@2l -&@WA>KZumx(11f8RuJWD+(f_ld9qmx1A65nd3RL~ -\rt4z*yKo܀LlYwO/;LoXeBG4WOpWCx) aR$O B?* ʹdՌ]lI yсK|3ܺ60s]n@{)R]#*1ѢX5K,-j."n^`W.8ufR;=w T"a´㓽l(.~iX\kf=Uaۏ*{IcEa1ʹTsxXQ,~Qė@梽R,@UR-J))J$}aͣ/)UXQ3[e¶5KFNe`fEїi Ċ |r|+de&4\!qaj)2Z[E] oz #HFR?2.beгPd -1^a4S)]Hph^@7 +WmNIY]|9ŋK]@toYɱJmͯ7^Al2+XWZ0^߱]^025_f4#/UGCJG[<~7ݲԎۭ5bչk}y1Ԭ0r2euZQM/6Z,`|S2ʺMJ(83w [n -}~s<_t)OΎT+(Zd%I5|`T뉃 -* ͑tb"F4 2/*>>/ԣ@ ;$X2wׂiM=agCʽFWXl˨+RT^Or Cwp]"H:u"##ҘRN/_z#)Kj=r+Vy<+Xn\G!!B_8V'w#Ev·s`EPR7«À!!ZAuTLmV#"oƐlogc"c';oHok/WE0]IXF]!wE0Xy-lx3ōaQ - tjMnUBQf<_E X4f.]A0FIϋ*> ;\DPp#AT ?7 -endstream -endobj -2 0 obj -<< -/ProcSet [/PDF /Text /ImageB /ImageC /ImageI] -/Font << -/F1 6 0 R -/F2 7 0 R -/F3 9 0 R ->> -/XObject << -/I0 10 0 R ->> ->> -endobj -12 0 obj -<< -/Producer (jsPDF 0.0.0) -/CreationDate (D:20241217154849-00'00') ->> -endobj -13 0 obj -<< -/Type /Catalog -/Pages 1 0 R -/OpenAction [3 0 R /FitH null] -/PageLayout /OneColumn ->> -endobj -xref -0 14 -0000000000 65535 f -0000102899 00000 n -0000118853 00000 n -0000000015 00000 n -0000000125 00000 n -0000102956 00000 n -0000103126 00000 n -0000104180 00000 n -0000104307 00000 n -0000104476 00000 n -0000105520 00000 n -0000112030 00000 n -0000118988 00000 n -0000119074 00000 n -trailer -<< -/Size 14 -/Root 13 0 R -/Info 12 0 R -/ID [ <906A4C76C35816C42EB6FFD13B3B7D92> <906A4C76C35816C42EB6FFD13B3B7D92> ] ->> -startxref -119178 -%%EOF \ No newline at end of file diff --git a/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/Schematic_Pro-Micro_Pinouts.pdf b/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/Schematic_Pro-Micro_Pinouts.pdf new file mode 100644 index 000000000..63a80dbbe --- /dev/null +++ b/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/Schematic_Pro-Micro_Pinouts.pdf @@ -0,0 +1,33346 @@ +%PDF-1.4 +%߬ +3 0 obj +<> +endobj +4 0 obj +<< +/Length 334350 +>> +stream +0.14 w +0 G +q +2 J +0 j +72 M +1.00 g +[] 0 d +0.00 1197.36 848.16 -1197.36 re +f +2 J +0 j +72 M +0.72 w +0.63 0.00 0.00 RG +[] 0 d +2.88 1194.48 842.40 -1191.60 re +S +2 J +0 j +72 M +0.72 w +0.63 0.00 0.00 RG +[] 0 d +10.08 1187.28 828.00 -1177.20 re +S +7.20 w +BT +/F1 7.199999999999999 Tf +7.20 TL +0.627 0.000 0.000 rg +71.28 3.60 Td +<0031> Tj +ET +7.20 w +BT +/F1 7.199999999999999 Tf +7.20 TL +0.627 0.000 0.000 rg +71.28 1188.00 Td +<0031> Tj +ET +7.20 w +BT +/F1 7.199999999999999 Tf +7.20 TL +0.627 0.000 0.000 rg +211.68 3.60 Td +<0032> Tj +ET +7.20 w +BT +/F1 7.199999999999999 Tf +7.20 TL +0.627 0.000 0.000 rg +211.68 1188.00 Td +<0032> Tj +ET +7.20 w +BT +/F1 7.199999999999999 Tf +7.20 TL +0.627 0.000 0.000 rg +352.08 3.60 Td +<0033> Tj +ET +7.20 w +BT +/F1 7.199999999999999 Tf +7.20 TL +0.627 0.000 0.000 rg +352.08 1188.00 Td +<0033> Tj +ET +7.20 w +BT +/F1 7.199999999999999 Tf +7.20 TL +0.627 0.000 0.000 rg +492.48 3.60 Td +<0034> Tj +ET +7.20 w +BT +/F1 7.199999999999999 Tf +7.20 TL +0.627 0.000 0.000 rg +492.48 1188.00 Td +<0034> Tj +ET +7.20 w +BT +/F1 7.199999999999999 Tf +7.20 TL +0.627 0.000 0.000 rg +632.88 3.60 Td +<0035> Tj +ET +7.20 w +BT +/F1 7.199999999999999 Tf +7.20 TL +0.627 0.000 0.000 rg +632.88 1188.00 Td +<0035> Tj +ET +7.20 w +BT +/F1 7.199999999999999 Tf +7.20 TL +0.627 0.000 0.000 rg +773.28 3.60 Td +<0036> Tj +ET +7.20 w +BT +/F1 7.199999999999999 Tf +7.20 TL +0.627 0.000 0.000 rg +773.28 1188.00 Td +<0036> Tj +ET +7.20 w +BT +/F1 7.199999999999999 Tf +7.20 TL +0.627 0.000 0.000 rg +4.68 1042.65 Td +<0041> Tj +ET +7.20 w +BT +/F1 7.199999999999999 Tf +7.20 TL +0.627 0.000 0.000 rg +839.88 1042.65 Td +<0041> Tj +ET +7.20 w +BT +/F1 7.199999999999999 Tf +7.20 TL +0.627 0.000 0.000 rg +4.68 744.75 Td +<0042> Tj +ET +7.20 w +BT +/F1 7.199999999999999 Tf +7.20 TL +0.627 0.000 0.000 rg +839.88 744.75 Td +<0042> Tj +ET +7.20 w +BT +/F1 7.199999999999999 Tf +7.20 TL +0.627 0.000 0.000 rg +4.68 446.85 Td +<0043> Tj +ET +7.20 w +BT +/F1 7.199999999999999 Tf +7.20 TL +0.627 0.000 0.000 rg +839.88 446.85 Td +<0043> Tj +ET +7.20 w +BT +/F1 7.199999999999999 Tf +7.20 TL +0.627 0.000 0.000 rg +4.68 148.95 Td +<0044> Tj +ET +7.20 w +BT +/F1 7.199999999999999 Tf +7.20 TL +0.627 0.000 0.000 rg +839.88 148.95 Td +<0044> Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +143.280 2.880 m +143.280 10.080 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +143.280 1194.480 m +143.280 1187.280 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +283.680 2.880 m +283.680 10.080 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +283.680 1194.480 m +283.680 1187.280 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +424.080 2.880 m +424.080 10.080 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +424.080 1194.480 m +424.080 1187.280 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +564.480 2.880 m +564.480 10.080 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +564.480 1194.480 m +564.480 1187.280 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +704.880 2.880 m +704.880 10.080 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +704.880 1194.480 m +704.880 1187.280 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +2.880 896.580 m +10.080 896.580 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +845.280 896.580 m +838.080 896.580 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +2.880 598.680 m +10.080 598.680 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +845.280 598.680 m +838.080 598.680 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +2.880 300.780 m +10.080 300.780 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +845.280 300.780 m +838.080 300.780 l +S +7.20 w +BT +/F2 13.090909090909088 Tf +14.40 TL +0.000 0.000 0.502 rg +613.27 70.30 Td +(Pro-micro Pinouts) Tj +ET +7.20 w +BT +/F2 9.818181818181817 Tf +10.80 TL +0.000 0.000 0.502 rg +793.59 43.36 Td +(1) Tj +ET +7.20 w +BT +/F2 9.818181818181817 Tf +10.80 TL +0.000 0.000 0.502 rg +730.08 129.04 Td +(2025-11-08) Tj +ET +7.20 w +BT +/F2 9.818181818181817 Tf +10.80 TL +0.000 0.000 0.502 rg +730.08 114.64 Td +(2025-11-07) Tj +ET +7.20 w +BT +/F2 13.090909090909088 Tf +14.40 TL +0.000 0.000 0.502 rg +474.65 120.70 Td +(Pro-micro_Pinouts) Tj +ET +7.20 w +BT +/F2 9.818181818181817 Tf +10.80 TL +0.000 0.000 0.502 rg +709.35 43.36 Td +(1) Tj +ET +7.20 w +BT +/F2 9.818181818181817 Tf +10.80 TL +0.000 0.000 0.502 rg +510.19 100.24 Td +(Sheet_1) Tj +ET +7.20 w +BT +/F2 9.818181818181817 Tf +10.80 TL +0.000 0.000 0.502 rg +509.74 21.04 Td +(V2.0) Tj +ET +7.20 w +BT +/F2 9.818181818181817 Tf +10.80 TL +0.000 0.000 0.502 rg +572.88 21.04 Td +(A3) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 0.502 rg +2.88 -1170.85 Td +(1) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 0.502 rg +2.88 -1170.85 Td +(1) Tj +ET +q +1 0 0 1 0 0 cm +7.20 w +BT +/F2 9.818181818181817 Tf +10.80 TL +0.627 0.000 0.000 rg +339.84 71.44 Td +(Reviewed) Tj +ET +7.20 w +BT +/F2 9.818181818181817 Tf +10.80 TL +0.627 0.000 0.000 rg +339.84 85.84 Td +(Drawn) Tj +ET +7.20 w +BT +/F2 9.818181818181817 Tf +10.80 TL +0.627 0.000 0.000 rg +509.75 42.64 Td +(VER) Tj +ET +7.20 w +BT +/F2 9.818181818181817 Tf +10.80 TL +0.627 0.000 0.000 rg +656.64 114.64 Td +(Create Date) Tj +ET +7.20 w +BT +/F2 9.818181818181817 Tf +10.80 TL +0.627 0.000 0.000 rg +656.64 100.24 Td +(Part Number) Tj +ET +7.20 w +BT +/F2 9.818181818181817 Tf +10.80 TL +0.627 0.000 0.000 rg +656.64 43.36 Td +(PAGE) Tj +ET +7.20 w +BT +/F2 9.818181818181817 Tf +10.80 TL +0.627 0.000 0.000 rg +745.92 43.36 Td +(OF) Tj +ET +7.20 w +BT +/F2 9.818181818181817 Tf +10.80 TL +0.627 0.000 0.000 rg +339.84 121.84 Td +(Schematic) Tj +ET +7.20 w +BT +/F2 9.818181818181817 Tf +10.80 TL +0.627 0.000 0.000 rg +656.64 129.04 Td +(Update Date) Tj +ET +7.20 w +BT +/F2 9.818181818181817 Tf +10.80 TL +0.627 0.000 0.000 rg +566.53 42.64 Td +(SIZE) Tj +ET +7.20 w +BT +/F2 9.818181818181817 Tf +10.80 TL +0.627 0.000 0.000 rg +341.28 100.24 Td +(Page) Tj +ET +2 J +0 j +72 M +0.72 w +0.63 0.00 0.00 RG +[] 0 d +332.64 139.68 505.44 -129.60 re +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +548.640 10.080 m +548.640 53.280 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +398.880 38.880 m +398.880 139.680 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +649.440 139.680 m +649.440 96.480 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +721.440 96.480 m +721.440 139.680 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +491.040 96.480 m +491.040 10.080 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +491.040 82.080 m +332.640 82.080 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +838.080 125.280 m +649.440 125.280 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +838.080 38.880 m +332.640 38.880 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +838.080 96.480 m +332.640 96.480 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +838.080 53.280 m +332.640 53.280 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +491.040 67.680 m +332.640 67.680 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +838.080 110.880 m +332.640 110.880 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +606.240 53.280 m +606.240 10.080 l +S +q +0.33 0.53 1.00 rg +[] 0 d +414.402 23.650 m +414.554 23.764 414.738 23.821 414.949 23.821 c +415.233 23.821 415.536 23.720 415.859 23.524 c +416.182 23.328 416.492 23.018 416.789 22.594 c +417.936 24.359 l +417.580 24.852 417.138 25.238 416.604 25.510 c +416.070 25.782 415.503 25.915 414.910 25.915 c +414.066 25.915 413.328 25.668 412.708 25.175 c +412.088 24.681 411.778 24.036 411.778 23.252 c +411.778 22.689 411.976 22.151 412.372 21.645 c +412.655 21.285 413.169 20.848 413.908 20.342 c +414.547 19.906 414.943 19.602 415.088 19.432 c +415.233 19.261 415.305 19.096 415.305 18.932 c +415.305 18.729 415.213 18.559 415.022 18.407 c +414.831 18.261 414.580 18.186 414.264 18.186 c +413.466 18.186 412.728 18.609 412.055 19.463 c +410.572 17.806 l +411.231 17.136 411.831 16.668 412.365 16.415 c +412.899 16.162 413.499 16.035 414.152 16.035 c +415.286 16.035 416.149 16.345 416.749 16.965 c +417.349 17.585 417.646 18.249 417.646 18.945 c +417.646 19.476 417.501 19.963 417.211 20.418 c +416.920 20.867 416.314 21.411 415.391 22.044 c +414.811 22.442 414.468 22.708 414.356 22.847 c +414.237 22.986 414.178 23.125 414.178 23.265 c +414.171 23.410 414.251 23.537 414.402 23.650 c +414.402 23.650 l +f +0.33 0.53 1.00 rg +[] 0 d +423.434 20.102 m +421.528 25.674 l +419.168 25.674 l +421.924 17.559 l +419.142 12.841 l +421.680 12.841 l +429.373 25.674 l +426.782 25.674 l +423.434 20.102 l +f +0.33 0.53 1.00 rg +[] 0 d +397.908 24.093 m +393.267 24.093 l +393.650 26.731 l +393.650 26.731 398.574 26.737 398.601 26.737 c +399.247 26.737 399.774 27.243 399.774 27.863 c +399.774 28.483 399.247 28.989 398.601 28.989 c +398.581 28.989 391.455 28.989 391.455 28.989 c +389.635 16.275 l +397.098 16.275 l +397.098 16.275 l +397.724 16.288 398.231 16.781 398.231 17.382 c +398.231 17.996 397.711 18.495 397.071 18.495 c +397.045 18.495 392.443 18.489 392.443 18.489 c +392.931 21.860 l +392.931 21.860 397.658 21.854 397.697 21.854 c +398.344 21.854 398.871 22.360 398.871 22.980 c +398.884 23.543 398.462 24.005 397.908 24.093 c +397.908 24.093 l +f +0.33 0.53 1.00 rg +[] 0 d +438.576 28.982 m +438.556 28.982 431.430 28.982 431.430 28.982 c +429.624 16.282 l +437.093 16.282 l +437.093 16.282 l +437.719 16.294 438.227 16.788 438.227 17.389 c +438.227 18.002 437.706 18.502 437.066 18.502 c +437.040 18.502 432.439 18.495 432.439 18.495 c +432.926 21.867 l +432.926 21.867 437.653 21.860 437.699 21.860 c +438.345 21.860 438.873 22.366 438.873 22.986 c +438.873 23.543 438.451 24.005 437.897 24.093 c +433.256 24.093 l +433.638 26.731 l +433.638 26.731 438.563 26.737 438.589 26.737 c +439.235 26.737 439.763 27.243 439.763 27.863 c +439.749 28.476 439.229 28.982 438.576 28.982 c +438.576 28.982 l +f +0.33 0.53 1.00 rg +[] 0 d +451.912 22.898 m +451.912 24.144 451.609 25.251 451.009 26.206 c +450.409 27.161 449.631 27.863 448.669 28.306 c +447.706 28.755 446.249 28.976 444.278 28.976 c +442.182 28.976 l +440.376 16.275 l +444.489 16.275 l +446.216 16.275 447.568 16.522 448.537 17.015 c +449.506 17.509 450.317 18.293 450.956 19.362 c +451.596 20.437 451.912 21.614 451.912 22.898 c +451.912 22.898 l +448.655 20.273 m +448.174 19.577 447.541 19.084 446.750 18.799 c +446.183 18.597 445.274 18.495 444.015 18.495 c +443.197 18.495 l +444.364 26.743 l +444.990 26.743 l +446.012 26.743 446.829 26.592 447.443 26.282 c +448.056 25.972 448.530 25.535 448.873 24.966 c +449.209 24.397 449.381 23.695 449.381 22.853 c +449.374 21.835 449.137 20.969 448.655 20.273 c +448.655 20.273 l +f +0.33 0.53 1.00 rg +[] 0 d +461.702 23.043 m +460.231 22.265 l +460.113 21.241 459.203 20.450 458.109 20.450 c +456.929 20.450 455.973 21.367 455.973 22.499 c +455.973 23.631 456.929 24.549 458.109 24.549 c +458.564 24.549 458.986 24.409 459.328 24.182 c +461.154 25.149 l +460.166 28.957 l +458.076 28.957 l +450.956 16.307 l +453.619 16.307 l +455.122 19.001 l +460.357 19.001 l +461.055 16.307 l +463.455 16.307 l +461.702 23.043 l +461.702 23.043 l +f +0.33 0.53 1.00 rg +[] 0 d +457.324 22.550 m +457.324 22.113 457.693 21.759 458.148 21.759 c +458.603 21.759 458.972 22.113 458.972 22.550 c +458.972 22.986 458.603 23.340 458.148 23.340 c +457.693 23.340 457.324 22.986 457.324 22.550 c +457.324 22.550 l +f +0.33 0.53 1.00 rg +[] 0 d +408.674 24.194 m +408.674 24.194 408.674 24.201 408.674 24.194 c +407.850 24.194 l +407.764 24.346 l +407.428 24.858 407.006 25.244 406.498 25.510 c +405.984 25.776 405.173 25.908 404.567 25.908 c +403.663 25.908 402.800 25.674 401.976 25.206 c +401.152 24.738 400.493 24.087 400.005 23.246 c +399.517 22.411 399.266 21.525 399.266 20.602 c +399.266 19.387 399.649 18.325 400.413 17.408 c +401.178 16.490 402.213 16.035 403.512 16.035 c +404.079 16.035 404.586 16.124 405.041 16.313 c +405.496 16.497 405.984 16.819 406.505 17.287 c +406.505 17.287 407.118 16.775 407.124 16.781 c +407.507 16.490 407.981 16.307 408.496 16.275 c +408.733 16.275 l +408.766 16.547 l +409.603 23.309 l +409.596 23.309 409.596 23.309 409.590 23.309 c +409.590 23.796 409.181 24.188 408.674 24.194 c +408.674 24.194 l +406.749 19.659 m +406.452 19.122 406.083 18.723 405.641 18.470 c +405.199 18.217 404.685 18.091 404.092 18.091 c +403.380 18.091 402.800 18.312 402.345 18.767 c +401.890 19.217 401.666 19.811 401.666 20.545 c +401.666 21.500 401.963 22.278 402.563 22.885 c +403.162 23.492 403.888 23.790 404.745 23.790 c +405.483 23.790 406.076 23.562 406.524 23.113 c +406.973 22.657 407.197 22.063 407.197 21.316 c +407.197 20.753 407.045 20.197 406.749 19.659 c +406.749 19.659 l +f +0.33 0.53 1.00 rg +[] 0 d +381.612 27.749 m +381.118 29.008 380.314 30.140 379.252 31.057 c +377.624 32.461 375.515 33.239 373.319 33.239 c +371.421 33.239 369.608 32.670 368.079 31.595 c +367.340 31.076 366.701 30.462 366.173 29.767 c +365.844 29.811 365.508 29.836 365.165 29.836 c +363.273 29.836 361.486 29.128 360.148 27.844 c +358.810 26.560 358.072 24.852 358.072 23.031 c +358.072 21.342 358.724 19.723 359.904 18.470 c +360.840 17.477 362.053 16.775 363.391 16.446 c +363.972 14.789 365.606 13.594 367.525 13.594 c +369.931 13.594 371.889 15.472 371.889 17.781 c +371.889 17.914 371.882 18.053 371.869 18.186 c +377.993 21.272 l +376.655 23.499 l +370.801 20.551 l +370.003 21.424 368.830 21.974 367.525 21.974 c +365.633 21.974 364.018 20.810 363.411 19.191 c +361.869 19.843 360.788 21.316 360.788 23.037 c +360.788 25.352 362.745 27.237 365.165 27.237 c +366.015 27.237 366.813 27.003 367.485 26.598 c +368.296 28.944 370.603 30.640 373.319 30.640 c +376.484 30.640 379.081 28.350 379.430 25.409 c +379.542 25.421 379.655 25.428 379.767 25.428 c +381.659 25.428 383.195 23.954 383.195 22.139 c +383.195 20.418 381.817 19.008 380.063 18.862 c +378.105 18.862 l +378.020 18.881 377.927 18.888 377.835 18.888 c +377.077 18.888 376.464 18.299 376.464 17.572 c +376.464 16.883 377.018 16.320 377.723 16.263 c +377.723 16.250 l +380.063 16.250 l +380.182 16.250 l +380.301 16.263 l +381.830 16.389 383.247 17.053 384.289 18.141 c +385.337 19.235 385.917 20.652 385.917 22.139 c +385.917 24.757 384.104 26.996 381.612 27.749 c +381.612 27.749 l +367.525 19.400 m +368.454 19.400 369.212 18.673 369.212 17.781 c +369.212 16.889 368.454 16.162 367.525 16.162 c +366.595 16.162 365.837 16.889 365.837 17.781 c +365.837 18.673 366.595 19.400 367.525 19.400 c +367.525 19.400 l +f +Q +Q +2 J +0 j +72 M +0.72 w +0.63 0.00 0.00 RG +[] 0 d +110.88 1126.08 57.60 -115.20 re +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +147.04 1109.75 Td +(BATIN) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +172.80 1112.63 Td +(25) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +182.880 1111.680 m +168.480 1111.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +151.78 1102.55 Td +(GND) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +172.80 1105.43 Td +(24) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +182.880 1104.480 m +168.480 1104.480 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +153.23 1095.35 Td +(RST) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +172.80 1098.23 Td +(23) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +182.880 1097.280 m +168.480 1097.280 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +141.58 1088.15 Td +(3.3v Out) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +172.80 1091.03 Td +(22) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +182.880 1090.080 m +168.480 1090.080 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +149.22 1059.35 Td +(P1.15) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +172.80 1062.23 Td +(18) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +182.880 1061.280 m +168.480 1061.280 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +149.22 1080.95 Td +(P0.31) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +172.80 1083.83 Td +(21) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +182.880 1082.880 m +168.480 1082.880 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +113.04 1102.55 Td +(P0.08) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +102.92 1105.43 Td +(3) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +96.480 1104.480 m +110.880 1104.480 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +149.22 1052.15 Td +(P1.13) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +172.80 1055.03 Td +(17) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +182.880 1054.080 m +168.480 1054.080 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +149.22 1073.75 Td +(P0.29) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +172.80 1076.63 Td +(20) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +182.880 1075.680 m +168.480 1075.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +149.22 1044.95 Td +(P1.11) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +172.80 1047.83 Td +(16) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +182.880 1046.880 m +168.480 1046.880 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +149.22 1066.55 Td +(P0.02) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +172.80 1069.43 Td +(19) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +182.880 1068.480 m +168.480 1068.480 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +113.04 1095.35 Td +(GND) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +102.92 1098.23 Td +(4) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +96.480 1097.280 m +110.880 1097.280 l +S +0.72 w +0.63 0.00 0.00 RG +110.88 1111.68 m 110.88 1112.87 109.91 1113.84 108.72 1113.84 c +107.53 1113.84 106.56 1112.87 106.56 1111.68 c +106.56 1110.49 107.53 1109.52 108.72 1109.52 c +109.91 1109.52 110.88 1110.49 110.88 1111.68 c +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +113.04 1109.75 Td +(P0.06) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +102.92 1112.63 Td +(2) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +96.480 1111.680 m +106.560 1111.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +149.22 1030.55 Td +(P0.09) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +172.80 1033.43 Td +(14) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +182.880 1032.480 m +168.480 1032.480 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +149.22 1037.75 Td +(P0.10) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +172.80 1040.63 Td +(15) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +182.880 1039.680 m +168.480 1039.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +113.04 1037.75 Td +(P1.04) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +99.28 1040.63 Td +(12) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +96.480 1039.680 m +110.880 1039.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +113.04 1088.15 Td +(GND) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +102.92 1091.03 Td +(5) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +96.480 1090.080 m +110.880 1090.080 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +113.04 1080.95 Td +(P0.17) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +102.92 1083.83 Td +(6) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +96.480 1082.880 m +110.880 1082.880 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +113.04 1073.75 Td +(P0.20) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +102.92 1076.63 Td +(7) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +96.480 1075.680 m +110.880 1075.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +113.04 1066.55 Td +(P0.22) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +102.92 1069.43 Td +(8) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +96.480 1068.480 m +110.880 1068.480 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +113.04 1059.35 Td +(P0.24) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +102.92 1062.23 Td +(9) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +96.480 1061.280 m +110.880 1061.280 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +113.04 1052.15 Td +(P1.00) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +99.28 1055.03 Td +(10) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +96.480 1054.080 m +110.880 1054.080 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +113.04 1044.95 Td +(P0.11) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +99.28 1047.83 Td +(11) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +96.480 1046.880 m +110.880 1046.880 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +113.04 1030.55 Td +(P1.06) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +99.28 1033.43 Td +(13) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +96.480 1032.480 m +110.880 1032.480 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +147.04 1116.95 Td +(BATIN) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +172.80 1119.83 Td +(26) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +182.880 1118.880 m +168.480 1118.880 l +S +1 J +1 j +0.72 w +0.20 0.80 0.20 RG +[] 0 d +180.000 1121.760 m +185.760 1116.000 l +180.000 1116.000 m +185.760 1121.760 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +113.04 1116.95 Td +(GND) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +102.92 1119.83 Td +(1) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +96.480 1118.880 m +110.880 1118.880 l +S +1 J +1 j +0.72 w +0.20 0.80 0.20 RG +[] 0 d +93.600 1121.760 m +99.360 1116.000 l +93.600 1116.000 m +99.360 1121.760 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +135.38 1128.78 Td +(PRO_MICRO_NRF52840_26P) Tj +ET +7.20 w +BT +/F2 8.181818181818182 Tf +9.00 TL +0.000 g +67.68 248.06 Td +(Switches) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +118.080 154.080 m +118.080 161.280 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +111.600 154.080 m +124.560 154.080 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +113.760 152.640 m +122.400 152.640 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +115.920 151.200 m +120.240 151.200 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +117.360 149.760 m +118.800 149.760 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +118.080 161.280 m +118.080 161.280 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +108.72 142.09 Td +(GND) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +118.080 204.480 m +118.080 211.680 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +111.600 204.480 m +124.560 204.480 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +113.760 203.040 m +122.400 203.040 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +115.920 201.600 m +120.240 201.600 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +117.360 200.160 m +118.800 200.160 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +118.080 211.680 m +118.080 211.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +108.72 192.49 Td +(GND) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +74.880 161.280 m +71.280 157.680 l +60.480 157.680 l +60.480 164.880 l +71.280 164.880 l +74.880 161.280 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +74.880 161.280 m +74.880 161.280 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +42.93 158.92 Td +(RBTN) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +74.880 211.680 m +71.280 208.080 l +60.480 208.080 l +60.480 215.280 l +71.280 215.280 l +74.880 211.680 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +74.880 211.680 m +74.880 211.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +41.22 208.96 Td +(UBTN) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +96.480 165.600 m +96.480 169.200 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +107.28 161.93 Td +(2) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +118.080 161.280 m +103.680 161.280 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +82.04 161.93 Td +(1) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +74.880 161.280 m +89.280 161.280 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +88.560 165.600 m +104.400 165.600 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +105.12 161.28 m 105.12 162.08 104.48 162.72 103.68 162.72 c +102.88 162.72 102.24 162.08 102.24 161.28 c +102.24 160.48 102.88 159.84 103.68 159.84 c +104.48 159.84 105.12 160.48 105.12 161.28 c +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +90.72 161.28 m 90.72 162.08 90.08 162.72 89.28 162.72 c +88.48 162.72 87.84 162.08 87.84 161.28 c +87.84 160.48 88.48 159.84 89.28 159.84 c +90.08 159.84 90.72 160.48 90.72 161.28 c +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 0.502 rg +89.20 178.06 Td +(RS1) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +89.20 171.69 Td +(434121043816) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +96.480 216.000 m +96.480 219.600 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +107.28 212.33 Td +(2) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +118.080 211.680 m +103.680 211.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +82.04 212.33 Td +(1) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +74.880 211.680 m +89.280 211.680 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +88.560 216.000 m +104.400 216.000 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +105.12 211.68 m 105.12 212.48 104.48 213.12 103.68 213.12 c +102.88 213.12 102.24 212.48 102.24 211.68 c +102.24 210.88 102.88 210.24 103.68 210.24 c +104.48 210.24 105.12 210.88 105.12 211.68 c +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +90.72 211.68 m 90.72 212.48 90.08 213.12 89.28 213.12 c +88.48 213.12 87.84 212.48 87.84 211.68 c +87.84 210.88 88.48 210.24 89.28 210.24 c +90.08 210.24 90.72 210.88 90.72 211.68 c +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 0.502 rg +89.20 228.46 Td +(US1) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +89.20 222.09 Td +(434121043816) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +125.280 617.040 m +125.280 624.240 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +118.800 617.040 m +131.760 617.040 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +120.960 615.600 m +129.600 615.600 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +123.120 614.160 m +127.440 614.160 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +124.560 612.720 m +126.000 612.720 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +125.280 624.240 m +125.280 624.240 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +115.92 604.33 Td +(GND) Tj +ET +2 J +0 j +72 M +0.72 w +0.63 0.00 0.00 RG +[] 0 d +103.68 631.44 14.40 -14.40 re +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +0.00 1.00 -1.00 0.00 108.85 608.42 Tm +(1) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +110.880 602.640 m +110.880 617.040 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +100.04 619.01 Td +(2) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +96.480 624.240 m +103.680 624.240 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +118.08 619.01 Td +(3) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +125.280 624.240 m +118.080 624.240 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +0.00 1.00 -1.00 0.00 108.85 630.06 Tm +(4) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +110.880 638.640 m +110.880 631.440 l +S +1 J +1 j +0.72 w +0.20 0.80 0.20 RG +[] 0 d +108.000 641.520 m +113.760 635.760 l +108.000 635.760 m +113.760 641.520 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +112.32 624.24 m 112.32 625.04 111.68 625.68 110.88 625.68 c +110.08 625.68 109.44 625.04 109.44 624.24 c +109.44 623.44 110.08 622.80 110.88 622.80 c +111.68 622.80 112.32 623.44 112.32 624.24 c +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +110.880 622.800 m +110.880 617.040 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 0.502 rg +95.72 648.44 Td +(U11) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +95.72 641.89 Td +(AMC-U_FL) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +96.480 617.040 m +96.480 624.240 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +90.000 617.040 m +102.960 617.040 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +92.160 615.600 m +100.800 615.600 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +94.320 614.160 m +98.640 614.160 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +95.760 612.720 m +97.200 612.720 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +96.480 624.240 m +96.480 624.240 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +87.12 605.05 Td +(GND) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +146.880 573.840 m +154.080 573.840 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +146.880 570.240 m +146.880 577.440 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +154.080 573.840 m +154.080 573.840 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +128.76 570.43 Td +(VCC) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +146.880 595.440 m +154.080 595.440 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +146.880 601.920 m +146.880 588.960 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +145.440 599.760 m +145.440 591.120 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +144.000 597.600 m +144.000 593.280 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +142.560 596.160 m +142.560 594.720 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +154.080 595.440 m +154.080 595.440 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +123.48 592.03 Td +(GND) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +233.280 602.640 m +226.080 602.640 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +233.280 596.160 m +233.280 609.120 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +234.720 598.320 m +234.720 606.960 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +236.160 600.480 m +236.160 604.800 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +237.600 601.920 m +237.600 603.360 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +226.080 602.640 m +226.080 602.640 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +237.96 599.23 Td +(GND) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +226.080 545.040 m +226.080 552.240 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +219.600 545.040 m +232.560 545.040 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +221.760 543.600 m +230.400 543.600 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +223.920 542.160 m +228.240 542.160 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +225.360 540.720 m +226.800 540.720 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +226.080 552.240 m +226.080 552.240 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +216.72 533.05 Td +(GND) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +226.080 588.240 m +229.680 591.840 l +240.480 591.840 l +240.480 584.640 l +229.680 584.640 l +226.080 588.240 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +226.080 588.240 m +226.080 588.240 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +242.43 585.29 Td +(NRST) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +154.080 559.440 m +150.480 555.840 l +139.680 555.840 l +139.680 563.040 l +150.480 563.040 l +154.080 559.440 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +154.080 559.440 m +154.080 559.440 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +124.96 556.28 Td +(IRQ) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +154.080 552.240 m +150.480 548.640 l +139.680 548.640 l +139.680 555.840 l +150.480 555.840 l +154.080 552.240 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +154.080 552.240 m +154.080 552.240 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +120.42 549.37 Td +(BUSY) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +226.080 581.040 m +229.680 584.640 l +240.480 584.640 l +240.480 577.440 l +229.680 577.440 l +226.080 581.040 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +226.080 581.040 m +226.080 581.040 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +241.92 578.24 Td +(CS) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +226.080 573.840 m +229.680 577.440 l +240.480 577.440 l +240.480 570.240 l +229.680 570.240 l +226.080 573.840 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +226.080 573.840 m +226.080 573.840 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +241.60 571.04 Td +(SCK) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +226.080 566.640 m +229.680 570.240 l +240.480 570.240 l +240.480 563.040 l +229.680 563.040 l +226.080 566.640 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +226.080 566.640 m +226.080 566.640 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +241.92 563.84 Td +(MOSI) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +226.080 559.440 m +229.680 563.040 l +240.480 563.040 l +240.480 555.840 l +229.680 555.840 l +226.080 559.440 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +226.080 559.440 m +226.080 559.440 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +241.92 556.64 Td +(MISO) Tj +ET +2 J +0 j +72 M +0.72 w +0.63 0.00 0.00 RG +[] 0 d +161.28 609.84 57.60 -64.80 re +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +165.96 606.24 m 165.96 606.84 165.48 607.32 164.88 607.32 c +164.28 607.32 163.80 606.84 163.80 606.24 c +163.80 605.64 164.28 605.16 164.88 605.16 c +165.48 605.16 165.96 605.64 165.96 606.24 c +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +201.67 549.29 Td +(GND) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +219.24 552.89 Td +(1) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +226.080 552.240 m +218.880 552.240 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +199.49 556.49 Td +(MISO) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +219.24 560.09 Td +(2) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +226.080 559.440 m +218.880 559.440 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +199.49 563.69 Td +(MOSI) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +219.24 567.29 Td +(3) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +226.080 566.640 m +218.880 566.640 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +202.76 570.89 Td +(SCK) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +219.24 574.49 Td +(4) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +226.080 573.840 m +218.880 573.840 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +202.76 578.09 Td +(NSS) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +219.24 581.69 Td +(5) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +226.080 581.040 m +218.880 581.040 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +189.67 585.29 Td +(NRESET) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +219.24 588.89 Td +(6) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +226.080 588.240 m +218.880 588.240 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +206.76 592.49 Td +(NC) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +219.24 596.09 Td +(7) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +226.080 595.440 m +218.880 595.440 l +S +1 J +1 j +0.72 w +0.20 0.80 0.20 RG +[] 0 d +223.200 598.320 m +228.960 592.560 l +223.200 592.560 m +228.960 598.320 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +201.67 599.69 Td +(GND) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +219.24 603.29 Td +(8) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +226.080 602.640 m +218.880 602.640 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +163.94 599.69 Td +(ANT) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +157.28 603.29 Td +(9) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +154.080 602.640 m +161.280 602.640 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +163.94 592.49 Td +(GND) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +153.64 596.09 Td +(10) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +154.080 595.440 m +161.280 595.440 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +163.94 585.29 Td +(DIO3) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +153.64 588.89 Td +(11) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +154.080 588.240 m +161.280 588.240 l +S +1 J +1 j +0.72 w +0.20 0.80 0.20 RG +[] 0 d +151.200 591.120 m +156.960 585.360 l +151.200 585.360 m +156.960 591.120 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +163.94 578.09 Td +(NC) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +153.64 581.69 Td +(12) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +154.080 581.040 m +161.280 581.040 l +S +1 J +1 j +0.72 w +0.20 0.80 0.20 RG +[] 0 d +151.200 583.920 m +156.960 578.160 l +151.200 578.160 m +156.960 583.920 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +163.94 570.89 Td +(VCC) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +153.64 574.49 Td +(13) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +154.080 573.840 m +161.280 573.840 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +163.94 563.69 Td +(NC) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +153.64 567.29 Td +(14) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +154.080 566.640 m +161.280 566.640 l +S +1 J +1 j +0.72 w +0.20 0.80 0.20 RG +[] 0 d +151.200 569.520 m +156.960 563.760 l +151.200 563.760 m +156.960 569.520 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +163.94 556.49 Td +(DIO1) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +153.64 560.09 Td +(15) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +154.080 559.440 m +161.280 559.440 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +163.94 549.29 Td +(BUSY) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +153.64 552.89 Td +(16) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +154.080 552.240 m +161.280 552.240 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 0.502 rg +185.64 618.81 Td +(NICERF_LORA1262) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +185.64 612.33 Td +(LoRa1262-868) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +409.680 996.480 m +413.280 992.880 l +413.280 982.080 l +406.080 982.080 l +406.080 992.880 l +409.680 996.480 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +409.680 996.480 m +409.680 996.480 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +0.00 1.00 -1.00 0.00 410.96 961.34 Tm +(P1.02) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +416.880 996.480 m +420.480 992.880 l +420.480 982.080 l +413.280 982.080 l +413.280 992.880 l +416.880 996.480 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +416.880 996.480 m +416.880 996.480 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +0.00 1.00 -1.00 0.00 418.16 961.34 Tm +(P1.07) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +402.480 996.480 m +406.080 992.880 l +406.080 982.080 l +398.880 982.080 l +398.880 992.880 l +402.480 996.480 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +402.480 996.480 m +402.480 996.480 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +0.00 1.00 -1.00 0.00 403.76 961.34 Tm +(P1.01) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +647.280 218.880 m +654.480 218.880 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +647.280 215.280 m +647.280 222.480 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +654.480 218.880 m +654.480 218.880 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +629.15 215.53 Td +(VCC) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +647.280 233.280 m +654.480 233.280 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +647.280 239.760 m +647.280 226.800 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +645.840 237.600 m +645.840 228.960 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +644.400 235.440 m +644.400 231.120 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +642.960 234.000 m +642.960 232.560 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +654.480 233.280 m +654.480 233.280 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +623.88 229.93 Td +(GND) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +726.480 233.280 m +719.280 233.280 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +726.480 226.800 m +726.480 239.760 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +727.920 228.960 m +727.920 237.600 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +729.360 231.120 m +729.360 235.440 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +730.800 232.560 m +730.800 234.000 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +719.280 233.280 m +719.280 233.280 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +731.16 229.93 Td +(GND) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +726.480 182.880 m +719.280 182.880 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +726.480 176.400 m +726.480 189.360 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +727.920 178.560 m +727.920 187.200 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +729.360 180.720 m +729.360 185.040 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +730.800 182.160 m +730.800 183.600 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +719.280 182.880 m +719.280 182.880 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +731.16 179.53 Td +(GND) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +654.480 197.280 m +650.880 193.680 l +640.080 193.680 l +640.080 200.880 l +650.880 200.880 l +654.480 197.280 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +654.480 197.280 m +654.480 197.280 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +627.00 194.41 Td +(IRQ) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +654.480 211.680 m +650.880 208.080 l +640.080 208.080 l +640.080 215.280 l +650.880 215.280 l +654.480 211.680 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +654.480 211.680 m +654.480 211.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +620.82 208.93 Td +(NRST) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +719.280 211.680 m +722.880 215.280 l +733.680 215.280 l +733.680 208.080 l +722.880 208.080 l +719.280 211.680 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +719.280 211.680 m +719.280 211.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +735.12 208.96 Td +(MISO) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +719.280 218.880 m +722.880 222.480 l +733.680 222.480 l +733.680 215.280 l +722.880 215.280 l +719.280 218.880 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +719.280 218.880 m +719.280 218.880 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +735.12 216.16 Td +(MOSI) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +719.280 204.480 m +722.880 208.080 l +733.680 208.080 l +733.680 200.880 l +722.880 200.880 l +719.280 204.480 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +719.280 204.480 m +719.280 204.480 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +734.91 201.76 Td +(SCK) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +719.280 226.080 m +722.880 229.680 l +733.680 229.680 l +733.680 222.480 l +722.880 222.480 l +719.280 226.080 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +719.280 226.080 m +719.280 226.080 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +735.12 223.36 Td +(CS) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +654.480 204.480 m +650.880 200.880 l +640.080 200.880 l +640.080 208.080 l +650.880 208.080 l +654.480 204.480 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +654.480 204.480 m +654.480 204.480 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +620.82 201.61 Td +(BUSY) Tj +ET +2 J +0 j +72 M +0.72 w +0.63 0.00 0.00 RG +[] 0 d +661.68 240.48 50.40 -64.80 re +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +666.36 236.88 m 666.36 237.48 665.88 237.96 665.28 237.96 c +664.68 237.96 664.20 237.48 664.20 236.88 c +664.20 236.28 664.68 235.80 665.28 235.80 c +665.88 235.80 666.36 236.28 666.36 236.88 c +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +664.34 230.33 Td +(GND) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +657.68 233.93 Td +(1) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +654.480 233.280 m +661.680 233.280 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +664.34 223.13 Td +(GND) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +657.68 226.73 Td +(2) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +654.480 226.080 m +661.680 226.080 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +664.34 215.93 Td +(3.3V) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +657.68 219.53 Td +(3) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +654.480 218.880 m +661.680 218.880 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +664.34 208.73 Td +(RESET) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +657.68 212.33 Td +(4) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +654.480 211.680 m +661.680 211.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +664.34 201.53 Td +(DIO0) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +657.68 205.13 Td +(5) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +654.480 204.480 m +661.680 204.480 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +664.34 194.33 Td +(DIO1) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +657.68 197.93 Td +(6) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +654.480 197.280 m +661.680 197.280 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +664.34 187.13 Td +(DIO2) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +657.68 190.73 Td +(7) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +654.480 190.080 m +661.680 190.080 l +S +1 J +1 j +0.72 w +0.20 0.80 0.20 RG +[] 0 d +651.600 192.960 m +657.360 187.200 l +651.600 187.200 m +657.360 192.960 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +664.34 179.93 Td +(DIO3) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +657.68 183.53 Td +(8) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +654.480 182.880 m +661.680 182.880 l +S +1 J +1 j +0.72 w +0.20 0.80 0.20 RG +[] 0 d +651.600 185.760 m +657.360 180.000 l +651.600 180.000 m +657.360 185.760 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +694.87 179.93 Td +(GND) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +712.44 183.53 Td +(9) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +719.280 182.880 m +712.080 182.880 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +694.14 187.13 Td +(DIO4) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +712.44 190.73 Td +(10) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +719.280 190.080 m +712.080 190.080 l +S +1 J +1 j +0.72 w +0.20 0.80 0.20 RG +[] 0 d +716.400 192.960 m +722.160 187.200 l +716.400 187.200 m +722.160 192.960 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +694.14 194.33 Td +(DIO5) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +712.44 197.93 Td +(11) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +719.280 197.280 m +712.080 197.280 l +S +1 J +1 j +0.72 w +0.20 0.80 0.20 RG +[] 0 d +716.400 200.160 m +722.160 194.400 l +716.400 194.400 m +722.160 200.160 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +695.96 201.53 Td +(SCK) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +712.44 205.13 Td +(12) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +719.280 204.480 m +712.080 204.480 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +692.69 208.73 Td +(MISO) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +712.44 212.33 Td +(13) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +719.280 211.680 m +712.080 211.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +692.69 215.93 Td +(MOSI) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +712.44 219.53 Td +(14) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +719.280 218.880 m +712.080 218.880 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +695.96 223.13 Td +(NSS) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +712.44 226.73 Td +(15) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +719.280 226.080 m +712.080 226.080 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +694.87 230.33 Td +(GND) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +712.44 233.93 Td +(16) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +719.280 233.280 m +712.080 233.280 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 0.502 rg +682.74 249.45 Td +(RA-2) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +682.74 242.97 Td +(RA-02_C9900010926) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +560.880 1082.880 m +564.480 1086.480 l +575.280 1086.480 l +575.280 1079.280 l +564.480 1079.280 l +560.880 1082.880 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +560.880 1082.880 m +560.880 1082.880 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +577.22 1079.93 Td +(ADC) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +370.080 1039.680 m +366.480 1036.080 l +355.680 1036.080 l +355.680 1043.280 l +366.480 1043.280 l +370.080 1039.680 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +370.080 1039.680 m +370.080 1039.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +340.78 1036.81 Td +(SDA) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +370.080 1046.880 m +366.480 1043.280 l +355.680 1043.280 l +355.680 1050.480 l +366.480 1050.480 l +370.080 1046.880 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +370.080 1046.880 m +370.080 1046.880 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +341.51 1044.01 Td +(SCL) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +269.280 222.480 m +269.280 215.280 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +265.680 222.480 m +272.880 222.480 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +269.280 215.280 m +269.280 215.280 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +262.08 223.45 Td +(+5V) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +233.280 179.280 m +233.280 186.480 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +226.800 179.280 m +239.760 179.280 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +228.960 177.840 m +237.600 177.840 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +231.120 176.400 m +235.440 176.400 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +232.560 174.960 m +234.000 174.960 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +233.280 186.480 m +233.280 186.480 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +223.92 166.39 Td +(GND) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +233.280 222.480 m +233.280 215.280 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +229.680 222.480 m +236.880 222.480 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +233.280 215.280 m +233.280 215.280 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +224.64 223.45 Td +(VCC) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +269.280 179.280 m +269.280 186.480 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +262.800 179.280 m +275.760 179.280 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +264.960 177.840 m +273.600 177.840 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +267.120 176.400 m +271.440 176.400 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +268.560 174.960 m +270.000 174.960 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +269.280 186.480 m +269.280 186.480 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +259.92 167.29 Td +(GND) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +239.040 199.440 m +227.520 199.440 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +233.280 186.480 m +233.280 193.680 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +233.280 208.080 m +233.280 202.320 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +227.520 202.320 m +239.040 202.320 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +233.280 215.280 m +233.280 208.080 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +233.280 199.440 m +233.280 193.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 0.502 rg +240.48 198.81 Td +(C2) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +240.48 192.33 Td +(100uF) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +275.040 199.440 m +263.520 199.440 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +269.280 186.480 m +269.280 193.680 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +269.280 208.080 m +269.280 202.320 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +263.520 202.320 m +275.040 202.320 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +269.280 215.280 m +269.280 208.080 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +269.280 199.440 m +269.280 193.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 0.502 rg +276.48 198.81 Td +(C1) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +276.48 192.33 Td +(100uF) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +355.680 895.680 m +352.080 892.080 l +341.280 892.080 l +341.280 899.280 l +352.080 899.280 l +355.680 895.680 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +355.680 895.680 m +355.680 895.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +321.66 892.84 Td +(RXEN) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +467.280 1111.680 m +470.880 1115.280 l +481.680 1115.280 l +481.680 1108.080 l +470.880 1108.080 l +467.280 1111.680 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +467.280 1111.680 m +467.280 1111.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +483.48 1108.74 Td +(BATT) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +521.280 1104.480 m +514.080 1104.480 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +521.280 1098.000 m +521.280 1110.960 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +522.720 1100.160 m +522.720 1108.800 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +524.160 1102.320 m +524.160 1106.640 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +525.600 1103.760 m +525.600 1105.200 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +514.080 1104.480 m +514.080 1104.480 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +525.96 1101.13 Td +(GND) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +474.480 1075.680 m +478.080 1079.280 l +488.880 1079.280 l +488.880 1072.080 l +478.080 1072.080 l +474.480 1075.680 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +474.480 1075.680 m +474.480 1075.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +490.10 1072.96 Td +(BUSY) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +474.480 1046.880 m +478.080 1050.480 l +488.880 1050.480 l +488.880 1043.280 l +478.080 1043.280 l +474.480 1046.880 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +474.480 1046.880 m +474.480 1046.880 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +490.20 1044.16 Td +(SCK) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +474.480 1068.480 m +478.080 1072.080 l +488.880 1072.080 l +488.880 1064.880 l +478.080 1064.880 l +474.480 1068.480 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +474.480 1068.480 m +474.480 1068.480 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +490.32 1065.76 Td +(MISO) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +474.480 1061.280 m +478.080 1064.880 l +488.880 1064.880 l +488.880 1057.680 l +478.080 1057.680 l +474.480 1061.280 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +474.480 1061.280 m +474.480 1061.280 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +490.32 1058.56 Td +(MOSI) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +474.480 1054.080 m +478.080 1057.680 l +488.880 1057.680 l +488.880 1050.480 l +478.080 1050.480 l +474.480 1054.080 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +474.480 1054.080 m +474.480 1054.080 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +490.32 1051.36 Td +(CS) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +521.280 1090.080 m +514.080 1090.080 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +521.280 1093.680 m +521.280 1086.480 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +514.080 1090.080 m +514.080 1090.080 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +522.00 1086.73 Td +(VCC) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +474.480 1032.480 m +478.080 1036.080 l +488.880 1036.080 l +488.880 1028.880 l +478.080 1028.880 l +474.480 1032.480 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +474.480 1032.480 m +474.480 1032.480 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +490.68 1029.76 Td +(NRST) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +474.480 1039.680 m +478.080 1043.280 l +488.880 1043.280 l +488.880 1036.080 l +478.080 1036.080 l +474.480 1039.680 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +474.480 1039.680 m +474.480 1039.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +490.32 1036.96 Td +(IRQ) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +370.080 1068.480 m +366.480 1064.880 l +355.680 1064.880 l +355.680 1072.080 l +366.480 1072.080 l +370.080 1068.480 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +370.080 1068.480 m +370.080 1068.480 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +332.05 1065.61 Td +(GPSTX) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +370.080 1075.680 m +366.480 1072.080 l +355.680 1072.080 l +355.680 1079.280 l +366.480 1079.280 l +370.080 1075.680 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +370.080 1075.680 m +370.080 1075.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +331.32 1072.81 Td +(GPSRX) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +370.080 1061.280 m +366.480 1057.680 l +355.680 1057.680 l +355.680 1064.880 l +366.480 1064.880 l +370.080 1061.280 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +370.080 1061.280 m +370.080 1061.280 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +331.32 1058.41 Td +(GPSEN) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +373.680 1093.680 m +380.880 1093.680 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +373.680 1100.160 m +373.680 1087.200 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +372.240 1098.000 m +372.240 1089.360 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +370.800 1095.840 m +370.800 1091.520 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +369.360 1094.400 m +369.360 1092.960 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +380.880 1093.680 m +380.880 1093.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +350.28 1090.33 Td +(GND) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +370.080 1054.080 m +366.480 1050.480 l +355.680 1050.480 l +355.680 1057.680 l +366.480 1057.680 l +370.080 1054.080 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +370.080 1054.080 m +370.080 1054.080 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +336.42 1051.36 Td +(UBTN) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +474.480 1097.280 m +478.080 1100.880 l +488.880 1100.880 l +488.880 1093.680 l +478.080 1093.680 l +474.480 1097.280 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +474.480 1097.280 m +474.480 1097.280 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +490.33 1094.17 Td +(RBTN) Tj +ET +2 J +0 j +72 M +0.72 w +0.63 0.00 0.00 RG +[] 0 d +557.28 1104.48 7.20 -14.40 re +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +560.880 1111.680 m +560.880 1104.480 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +560.880 1082.880 m +560.880 1090.080 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 0.502 rg +565.92 1095.27 Td +(R_ADC_T0) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +565.92 1088.79 Td +(1M) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +560.880 1046.880 m +560.880 1054.080 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +554.400 1046.880 m +567.360 1046.880 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +556.560 1045.440 m +565.200 1045.440 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +558.720 1044.000 m +563.040 1044.000 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +560.160 1042.560 m +561.600 1042.560 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +560.880 1054.080 m +560.880 1054.080 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +551.52 1034.89 Td +(GND) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +560.880 1111.680 m +557.280 1115.280 l +557.280 1126.080 l +564.480 1126.080 l +564.480 1115.280 l +560.880 1111.680 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +560.880 1111.680 m +560.880 1111.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +0.00 1.00 -1.00 0.00 562.23 1126.29 Tm +(BATT) Tj +ET +2 J +0 j +72 M +0.72 w +0.63 0.00 0.00 RG +[] 0 d +557.28 1075.68 7.20 -14.40 re +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +560.880 1082.880 m +560.880 1075.680 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +560.880 1054.080 m +560.880 1061.280 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 0.502 rg +565.92 1066.47 Td +(R_ADC_B0) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +565.92 1059.99 Td +(1.5M) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +380.880 1104.480 m +377.280 1100.880 l +366.480 1100.880 l +366.480 1108.080 l +377.280 1108.080 l +380.880 1104.480 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +380.880 1104.480 m +380.880 1104.480 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +329.75 1101.76 Td +(SERIAL2TX) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +380.880 1111.680 m +377.280 1108.080 l +366.480 1108.080 l +366.480 1115.280 l +377.280 1115.280 l +380.880 1111.680 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +380.880 1111.680 m +380.880 1111.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +329.03 1108.96 Td +(SERIAL2RX) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +380.880 1032.480 m +377.280 1028.880 l +366.480 1028.880 l +366.480 1036.080 l +377.280 1036.080 l +380.880 1032.480 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +380.880 1032.480 m +380.880 1032.480 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +347.94 1029.61 Td +(P1.06) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +737.280 434.880 m +730.080 434.880 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +737.280 438.480 m +737.280 431.280 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +730.080 434.880 m +730.080 434.880 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +738.00 431.53 Td +(VCC) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +622.080 362.880 m +629.280 362.880 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +622.080 369.360 m +622.080 356.400 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +620.640 367.200 m +620.640 358.560 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +619.200 365.040 m +619.200 360.720 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +617.760 363.600 m +617.760 362.160 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +629.280 362.880 m +629.280 362.880 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +598.68 359.53 Td +(GND) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +622.080 377.280 m +629.280 377.280 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +622.080 383.760 m +622.080 370.800 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +620.640 381.600 m +620.640 372.960 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +619.200 379.440 m +619.200 375.120 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +617.760 378.000 m +617.760 376.560 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +629.280 377.280 m +629.280 377.280 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +598.68 373.93 Td +(GND) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +622.080 391.680 m +629.280 391.680 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +622.080 398.160 m +622.080 385.200 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +620.640 396.000 m +620.640 387.360 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +619.200 393.840 m +619.200 389.520 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +617.760 392.400 m +617.760 390.960 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +629.280 391.680 m +629.280 391.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +598.68 388.33 Td +(GND) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +622.080 442.080 m +629.280 442.080 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +622.080 448.560 m +622.080 435.600 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +620.640 446.400 m +620.640 437.760 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +619.200 444.240 m +619.200 439.920 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +617.760 442.800 m +617.760 441.360 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +629.280 442.080 m +629.280 442.080 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +598.68 438.73 Td +(GND) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +737.280 442.080 m +730.080 442.080 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +737.280 435.600 m +737.280 448.560 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +738.720 437.760 m +738.720 446.400 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +740.160 439.920 m +740.160 444.240 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +741.600 441.360 m +741.600 442.800 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +730.080 442.080 m +730.080 442.080 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +741.96 438.73 Td +(GND) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +737.280 391.680 m +730.080 391.680 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +737.280 385.200 m +737.280 398.160 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +738.720 387.360 m +738.720 396.000 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +740.160 389.520 m +740.160 393.840 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +741.600 390.960 m +741.600 392.400 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +730.080 391.680 m +730.080 391.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +741.96 388.33 Td +(GND) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +737.280 377.280 m +730.080 377.280 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +737.280 370.800 m +737.280 383.760 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +738.720 372.960 m +738.720 381.600 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +740.160 375.120 m +740.160 379.440 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +741.600 376.560 m +741.600 378.000 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +730.080 377.280 m +730.080 377.280 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +741.96 373.93 Td +(GND) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +730.080 355.680 m +730.080 362.880 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +723.600 355.680 m +736.560 355.680 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +725.760 354.240 m +734.400 354.240 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +727.920 352.800 m +732.240 352.800 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +729.360 351.360 m +730.800 351.360 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +730.080 362.880 m +730.080 362.880 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +720.72 342.97 Td +(GND) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +645.12 360.65 Td +(GND) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +632.80 363.53 Td +(22) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +629.280 362.880 m +643.680 362.880 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +645.12 367.85 Td +(ANT_2.4) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +632.80 370.73 Td +(21) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +629.280 370.080 m +643.680 370.080 l +S +1 J +1 j +0.72 w +0.20 0.80 0.20 RG +[] 0 d +626.400 372.960 m +632.160 367.200 l +626.400 367.200 m +632.160 372.960 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +645.12 375.05 Td +(GND) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +632.80 377.93 Td +(20) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +629.280 377.280 m +643.680 377.280 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +645.12 389.45 Td +(GND) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +632.80 392.33 Td +(19) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +629.280 391.680 m +643.680 391.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +645.12 396.65 Td +(BUSY) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +632.80 399.53 Td +(18) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +629.280 398.880 m +643.680 398.880 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +645.12 403.85 Td +(NSS) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +632.80 406.73 Td +(17) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +629.280 406.080 m +643.680 406.080 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +645.12 411.05 Td +(SCK) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +632.80 413.93 Td +(16) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +629.280 413.280 m +643.680 413.280 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +645.12 418.25 Td +(MOSI) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +632.80 421.13 Td +(15) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +629.280 420.480 m +643.680 420.480 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +645.12 425.45 Td +(MISO) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +632.80 428.33 Td +(14) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +629.280 427.680 m +643.680 427.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +645.12 432.65 Td +(NC) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +632.80 435.53 Td +(13) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +629.280 434.880 m +643.680 434.880 l +S +1 J +1 j +0.72 w +0.20 0.80 0.20 RG +[] 0 d +626.400 437.760 m +632.160 432.000 l +626.400 432.000 m +632.160 437.760 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +645.12 439.85 Td +(GND) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +632.80 442.73 Td +(12) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +629.280 442.080 m +643.680 442.080 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +699.70 439.85 Td +(GND) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +719.28 442.73 Td +(11) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +730.080 442.080 m +715.680 442.080 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +700.42 432.65 Td +(VCC) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +719.28 435.53 Td +(10) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +730.080 434.880 m +715.680 434.880 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +698.96 425.45 Td +(DIO7) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +719.28 428.33 Td +(9) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +730.080 427.680 m +715.680 427.680 l +S +1 J +1 j +0.72 w +0.20 0.80 0.20 RG +[] 0 d +727.200 430.560 m +732.960 424.800 l +727.200 424.800 m +732.960 430.560 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +698.96 418.25 Td +(DIO8) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +719.28 421.13 Td +(8) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +730.080 420.480 m +715.680 420.480 l +S +1 J +1 j +0.72 w +0.20 0.80 0.20 RG +[] 0 d +727.200 423.360 m +732.960 417.600 l +727.200 417.600 m +732.960 423.360 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +698.96 411.05 Td +(DIO9) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +719.28 413.93 Td +(7) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +730.080 413.280 m +715.680 413.280 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +696.42 403.85 Td +(NRST) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +719.28 406.73 Td +(6) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +730.080 406.080 m +715.680 406.080 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +704.79 396.65 Td +(NC) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +719.28 399.53 Td +(5) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +730.080 398.880 m +715.680 398.880 l +S +1 J +1 j +0.72 w +0.20 0.80 0.20 RG +[] 0 d +727.200 401.760 m +732.960 396.000 l +727.200 396.000 m +732.960 401.760 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +699.70 389.45 Td +(GND) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +719.28 392.33 Td +(4) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +730.080 391.680 m +715.680 391.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +699.70 375.05 Td +(GND) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +719.28 377.93 Td +(3) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +730.080 377.280 m +715.680 377.280 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +686.59 367.85 Td +(ANT_900) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +719.28 370.73 Td +(2) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +730.080 370.080 m +715.680 370.080 l +S +1 J +1 j +0.72 w +0.20 0.80 0.20 RG +[] 0 d +727.200 372.960 m +732.960 367.200 l +727.200 367.200 m +732.960 372.960 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +699.70 360.65 Td +(GND) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +719.28 363.53 Td +(1) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +730.080 362.880 m +715.680 362.880 l +S +2 J +0 j +72 M +0.72 w +0.63 0.00 0.00 RG +[] 0 d +643.68 449.28 72.00 -100.80 re +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 0.502 rg +675.33 458.49 Td +(E80-900M2213S) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +675.33 452.01 Td +(E80-900M2213S) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +629.280 427.680 m +625.680 424.080 l +614.880 424.080 l +614.880 431.280 l +625.680 431.280 l +629.280 427.680 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +629.280 427.680 m +629.280 427.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +596.71 424.81 Td +(MISO) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +629.280 413.280 m +625.680 409.680 l +614.880 409.680 l +614.880 416.880 l +625.680 416.880 l +629.280 413.280 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +629.280 413.280 m +629.280 413.280 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +599.98 410.41 Td +(SCK) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +629.280 420.480 m +625.680 416.880 l +614.880 416.880 l +614.880 424.080 l +625.680 424.080 l +629.280 420.480 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +629.280 420.480 m +629.280 420.480 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +596.71 417.61 Td +(MOSI) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +629.280 406.080 m +625.680 402.480 l +614.880 402.480 l +614.880 409.680 l +625.680 409.680 l +629.280 406.080 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +629.280 406.080 m +629.280 406.080 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +604.35 403.21 Td +(CS) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +629.280 398.880 m +625.680 395.280 l +614.880 395.280 l +614.880 402.480 l +625.680 402.480 l +629.280 398.880 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +629.280 398.880 m +629.280 398.880 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +595.62 396.01 Td +(BUSY) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +730.080 413.280 m +733.680 416.880 l +744.480 416.880 l +744.480 409.680 l +733.680 409.680 l +730.080 413.280 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +730.080 413.280 m +730.080 413.280 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +745.92 410.56 Td +(IRQ) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +730.080 406.080 m +733.680 409.680 l +744.480 409.680 l +744.480 402.480 l +733.680 402.480 l +730.080 406.080 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +730.080 406.080 m +730.080 406.080 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +746.43 403.13 Td +(NRST) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +737.280 748.080 m +730.080 748.080 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +737.280 751.680 m +737.280 744.480 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +730.080 748.080 m +730.080 748.080 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +737.64 744.67 Td +(+5V) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +114.480 726.480 m +114.480 733.680 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +108.000 726.480 m +120.960 726.480 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +110.160 725.040 m +118.800 725.040 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +112.320 723.600 m +116.640 723.600 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +113.760 722.160 m +115.200 722.160 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +114.480 733.680 m +114.480 733.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +105.12 713.77 Td +(GND) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +488.880 744.480 m +481.680 744.480 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +488.880 748.080 m +488.880 740.880 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +481.680 744.480 m +481.680 744.480 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +489.24 741.07 Td +(VCC) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +157.680 755.280 m +157.680 748.080 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +154.080 755.280 m +161.280 755.280 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +157.680 748.080 m +157.680 748.080 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +149.04 756.25 Td +(VCC) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +730.080 667.440 m +730.080 674.640 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +723.600 667.440 m +736.560 667.440 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +725.760 666.000 m +734.400 666.000 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +727.920 664.560 m +732.240 664.560 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +729.360 663.120 m +730.800 663.120 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +730.080 674.640 m +730.080 674.640 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +720.72 654.55 Td +(GND) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +629.280 737.280 m +625.680 733.680 l +614.880 733.680 l +614.880 740.880 l +625.680 740.880 l +629.280 737.280 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +629.280 737.280 m +629.280 737.280 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +595.62 734.53 Td +(NRST) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +629.280 751.680 m +625.680 748.080 l +614.880 748.080 l +614.880 755.280 l +625.680 755.280 l +629.280 751.680 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +629.280 751.680 m +629.280 751.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +601.80 748.81 Td +(IRQ) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +730.080 737.280 m +733.680 740.880 l +744.480 740.880 l +744.480 733.680 l +733.680 733.680 l +730.080 737.280 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +730.080 737.280 m +730.080 737.280 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +745.93 734.23 Td +(DIO2) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +629.280 708.480 m +625.680 704.880 l +614.880 704.880 l +614.880 712.080 l +625.680 712.080 l +629.280 708.480 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +629.280 708.480 m +629.280 708.480 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +604.35 705.61 Td +(CS) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +629.280 722.880 m +625.680 719.280 l +614.880 719.280 l +614.880 726.480 l +625.680 726.480 l +629.280 722.880 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +629.280 722.880 m +629.280 722.880 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +596.71 720.01 Td +(MOSI) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +629.280 730.080 m +625.680 726.480 l +614.880 726.480 l +614.880 733.680 l +625.680 733.680 l +629.280 730.080 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +629.280 730.080 m +629.280 730.080 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +596.71 727.21 Td +(MISO) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +629.280 715.680 m +625.680 712.080 l +614.880 712.080 l +614.880 719.280 l +625.680 719.280 l +629.280 715.680 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +629.280 715.680 m +629.280 715.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +599.98 712.81 Td +(SCK) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +730.080 722.880 m +733.680 726.480 l +744.480 726.480 l +744.480 719.280 l +733.680 719.280 l +730.080 722.880 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +730.080 722.880 m +730.080 722.880 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +745.71 720.16 Td +(RXEN) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +629.280 744.480 m +625.680 740.880 l +614.880 740.880 l +614.880 748.080 l +625.680 748.080 l +629.280 744.480 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +629.280 744.480 m +629.280 744.480 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +595.62 741.61 Td +(BUSY) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +622.080 694.080 m +629.280 694.080 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +622.080 700.560 m +622.080 687.600 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +620.640 698.400 m +620.640 689.760 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +619.200 696.240 m +619.200 691.920 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +617.760 694.800 m +617.760 693.360 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +629.280 694.080 m +629.280 694.080 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +598.68 690.67 Td +(GND) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +622.080 679.680 m +629.280 679.680 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +622.080 686.160 m +622.080 673.200 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +620.640 684.000 m +620.640 675.360 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +619.200 681.840 m +619.200 677.520 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +617.760 680.400 m +617.760 678.960 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +629.280 679.680 m +629.280 679.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +598.68 676.27 Td +(GND) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +730.080 766.080 m +730.080 758.880 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +736.560 766.080 m +723.600 766.080 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +734.400 767.520 m +725.760 767.520 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +732.240 768.960 m +727.920 768.960 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +730.800 770.400 m +729.360 770.400 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +730.080 758.880 m +730.080 758.880 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +720.72 772.15 Td +(GND) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +622.080 758.880 m +629.280 758.880 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +622.080 765.360 m +622.080 752.400 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +620.640 763.200 m +620.640 754.560 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +619.200 761.040 m +619.200 756.720 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +617.760 759.600 m +617.760 758.160 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +629.280 758.880 m +629.280 758.880 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +598.68 755.47 Td +(GND) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +150.480 740.880 m +157.680 740.880 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +150.480 747.360 m +150.480 734.400 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +149.040 745.200 m +149.040 736.560 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +147.600 743.040 m +147.600 738.720 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +146.160 741.600 m +146.160 740.160 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +157.680 740.880 m +157.680 740.880 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +127.08 737.47 Td +(GND) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +150.480 704.880 m +157.680 704.880 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +150.480 711.360 m +150.480 698.400 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +149.040 709.200 m +149.040 700.560 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +147.600 707.040 m +147.600 702.720 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +146.160 705.600 m +146.160 704.160 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +157.680 704.880 m +157.680 704.880 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +127.08 701.47 Td +(GND) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +229.680 719.280 m +222.480 719.280 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +229.680 712.800 m +229.680 725.760 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +231.120 714.960 m +231.120 723.600 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +232.560 717.120 m +232.560 721.440 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +234.000 718.560 m +234.000 720.000 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +222.480 719.280 m +222.480 719.280 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +234.36 715.87 Td +(GND) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +373.680 758.880 m +380.880 758.880 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +373.680 765.360 m +373.680 752.400 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +372.240 763.200 m +372.240 754.560 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +370.800 761.040 m +370.800 756.720 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +369.360 759.600 m +369.360 758.160 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +380.880 758.880 m +380.880 758.880 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +350.28 755.47 Td +(GND) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +481.680 766.080 m +481.680 758.880 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +488.160 766.080 m +475.200 766.080 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +486.000 767.520 m +477.360 767.520 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +483.840 768.960 m +479.520 768.960 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +482.400 770.400 m +480.960 770.400 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +481.680 758.880 m +481.680 758.880 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +472.32 772.18 Td +(GND) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +481.680 672.480 m +481.680 679.680 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +475.200 672.480 m +488.160 672.480 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +477.360 671.040 m +486.000 671.040 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +479.520 669.600 m +483.840 669.600 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +480.960 668.160 m +482.400 668.160 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +481.680 679.680 m +481.680 679.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +472.32 659.59 Td +(GND) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +373.680 679.680 m +380.880 679.680 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +373.680 686.160 m +373.680 673.200 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +372.240 684.000 m +372.240 675.360 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +370.800 681.840 m +370.800 677.520 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +369.360 680.400 m +369.360 678.960 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +380.880 679.680 m +380.880 679.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +350.28 676.27 Td +(GND) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +373.680 694.080 m +380.880 694.080 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +373.680 700.560 m +373.680 687.600 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +372.240 698.400 m +372.240 689.760 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +370.800 696.240 m +370.800 691.920 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +369.360 694.800 m +369.360 693.360 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +380.880 694.080 m +380.880 694.080 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +350.28 690.67 Td +(GND) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +380.880 744.480 m +377.280 740.880 l +366.480 740.880 l +366.480 748.080 l +377.280 748.080 l +380.880 744.480 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +380.880 744.480 m +380.880 744.480 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +347.22 741.61 Td +(BUSY) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +481.680 722.880 m +485.280 726.480 l +496.080 726.480 l +496.080 719.280 l +485.280 719.280 l +481.680 722.880 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +481.680 722.880 m +481.680 722.880 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +497.31 720.16 Td +(RXEN) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +380.880 715.680 m +377.280 712.080 l +366.480 712.080 l +366.480 719.280 l +377.280 719.280 l +380.880 715.680 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +380.880 715.680 m +380.880 715.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +351.58 712.81 Td +(SCK) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +380.880 730.080 m +377.280 726.480 l +366.480 726.480 l +366.480 733.680 l +377.280 733.680 l +380.880 730.080 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +380.880 730.080 m +380.880 730.080 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +348.31 727.21 Td +(MISO) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +380.880 722.880 m +377.280 719.280 l +366.480 719.280 l +366.480 726.480 l +377.280 726.480 l +380.880 722.880 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +380.880 722.880 m +380.880 722.880 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +348.31 720.01 Td +(MOSI) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +380.880 708.480 m +377.280 704.880 l +366.480 704.880 l +366.480 712.080 l +377.280 712.080 l +380.880 708.480 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +380.880 708.480 m +380.880 708.480 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +355.95 705.61 Td +(CS) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +481.680 737.280 m +485.280 740.880 l +496.080 740.880 l +496.080 733.680 l +485.280 733.680 l +481.680 737.280 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +481.680 737.280 m +481.680 737.280 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +497.53 734.23 Td +(DIO2) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +380.880 751.680 m +377.280 748.080 l +366.480 748.080 l +366.480 755.280 l +377.280 755.280 l +380.880 751.680 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +380.880 751.680 m +380.880 751.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +353.40 748.81 Td +(IRQ) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +380.880 737.280 m +377.280 733.680 l +366.480 733.680 l +366.480 740.880 l +377.280 740.880 l +380.880 737.280 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +380.880 737.280 m +380.880 737.280 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +347.22 734.53 Td +(NRST) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +222.480 733.680 m +226.080 737.280 l +236.880 737.280 l +236.880 730.080 l +226.080 730.080 l +222.480 733.680 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +222.480 733.680 m +222.480 733.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +238.33 730.63 Td +(DIO3) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +157.680 733.680 m +154.080 730.080 l +143.280 730.080 l +143.280 737.280 l +154.080 737.280 l +157.680 733.680 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +157.680 733.680 m +157.680 733.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +124.02 730.93 Td +(NRST) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +222.480 748.080 m +226.080 751.680 l +236.880 751.680 l +236.880 744.480 l +226.080 744.480 l +222.480 748.080 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +222.480 748.080 m +222.480 748.080 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +238.32 745.36 Td +(IRQ) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +222.480 740.880 m +226.080 744.480 l +236.880 744.480 l +236.880 737.280 l +226.080 737.280 l +222.480 740.880 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +222.480 740.880 m +222.480 740.880 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +238.33 737.83 Td +(DIO2) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +222.480 704.880 m +226.080 708.480 l +236.880 708.480 l +236.880 701.280 l +226.080 701.280 l +222.480 704.880 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +222.480 704.880 m +222.480 704.880 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +238.32 702.16 Td +(CS) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +222.480 697.680 m +226.080 701.280 l +236.880 701.280 l +236.880 694.080 l +226.080 694.080 l +222.480 697.680 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +222.480 697.680 m +222.480 697.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +238.32 694.96 Td +(MOSI) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +222.480 690.480 m +226.080 694.080 l +236.880 694.080 l +236.880 686.880 l +226.080 686.880 l +222.480 690.480 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +222.480 690.480 m +222.480 690.480 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +238.32 687.76 Td +(MISO) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +222.480 712.080 m +226.080 715.680 l +236.880 715.680 l +236.880 708.480 l +226.080 708.480 l +222.480 712.080 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +222.480 712.080 m +222.480 712.080 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +238.11 709.36 Td +(SCK) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +157.680 683.280 m +154.080 679.680 l +143.280 679.680 l +143.280 686.880 l +154.080 686.880 l +157.680 683.280 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +157.680 683.280 m +157.680 683.280 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +123.66 680.41 Td +(RXEN) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +222.480 683.280 m +226.080 686.880 l +236.880 686.880 l +236.880 679.680 l +226.080 679.680 l +222.480 683.280 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +222.480 683.280 m +222.480 683.280 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +238.10 680.56 Td +(BUSY) Tj +ET +2 J +0 j +72 M +0.72 w +0.63 0.00 0.00 RG +[] 0 d +164.88 755.28 50.40 -79.20 re +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +169.56 751.68 m 169.56 752.28 169.08 752.76 168.48 752.76 c +167.88 752.76 167.40 752.28 167.40 751.68 c +167.40 751.08 167.88 750.60 168.48 750.60 c +169.08 750.60 169.56 751.08 169.56 751.68 c +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +167.54 745.13 Td +(VCC) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +160.88 748.73 Td +(1) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +157.680 748.080 m +164.880 748.080 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +167.54 737.93 Td +(GND) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +160.88 741.53 Td +(2) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +157.680 740.880 m +164.880 740.880 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +167.54 730.73 Td +(NRST) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +160.88 734.33 Td +(3) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +157.680 733.680 m +164.880 733.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +167.54 723.53 Td +(NC) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +160.88 727.13 Td +(4) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +157.680 726.480 m +164.880 726.480 l +S +1 J +1 j +0.72 w +0.20 0.80 0.20 RG +[] 0 d +154.800 729.360 m +160.560 723.600 l +154.800 723.600 m +160.560 729.360 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +167.54 716.33 Td +(NC) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +160.88 719.93 Td +(5) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +157.680 719.280 m +164.880 719.280 l +S +1 J +1 j +0.72 w +0.20 0.80 0.20 RG +[] 0 d +154.800 722.160 m +160.560 716.400 l +154.800 716.400 m +160.560 722.160 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +167.54 709.13 Td +(ANT) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +160.88 712.73 Td +(6) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +157.680 712.080 m +164.880 712.080 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +167.54 701.93 Td +(GND) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +160.88 705.53 Td +(7) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +157.680 704.880 m +164.880 704.880 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +167.54 694.73 Td +(NC) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +160.88 698.33 Td +(8) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +157.680 697.680 m +164.880 697.680 l +S +1 J +1 j +0.72 w +0.20 0.80 0.20 RG +[] 0 d +154.800 700.560 m +160.560 694.800 l +154.800 694.800 m +160.560 700.560 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +167.54 687.53 Td +(TXEN) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +160.88 691.13 Td +(9) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +157.680 690.480 m +164.880 690.480 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +167.54 680.33 Td +(RXEN) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +157.24 683.93 Td +(10) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +157.680 683.280 m +164.880 683.280 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +194.79 680.33 Td +(BUSY) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +215.64 683.93 Td +(11) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +222.480 683.280 m +215.280 683.280 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +195.89 687.53 Td +(MISO) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +215.64 691.13 Td +(12) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +222.480 690.480 m +215.280 690.480 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +195.89 694.73 Td +(MOSI) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +215.64 698.33 Td +(13) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +222.480 697.680 m +215.280 697.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +199.16 701.93 Td +(NSS) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +215.64 705.53 Td +(14) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +222.480 704.880 m +215.280 704.880 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +199.16 709.13 Td +(SCK) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +215.64 712.73 Td +(15) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +222.480 712.080 m +215.280 712.080 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +198.07 716.33 Td +(GND) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +215.64 719.93 Td +(16) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +222.480 719.280 m +215.280 719.280 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +203.16 723.53 Td +(NC) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +215.64 727.13 Td +(17) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +222.480 726.480 m +215.280 726.480 l +S +1 J +1 j +0.72 w +0.20 0.80 0.20 RG +[] 0 d +219.600 729.360 m +225.360 723.600 l +219.600 723.600 m +225.360 729.360 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +197.34 730.73 Td +(DIO3) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +215.64 734.33 Td +(18) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +222.480 733.680 m +215.280 733.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +197.34 737.93 Td +(DIO2) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +215.64 741.53 Td +(19) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +222.480 740.880 m +215.280 740.880 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +197.34 745.13 Td +(DIO1) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +215.64 748.73 Td +(20) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +222.480 748.080 m +215.280 748.080 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 0.502 rg +185.75 764.49 Td +(E22-900MM22S) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +185.75 758.01 Td +(E22-400MM22S) Tj +ET +2 J +0 j +72 M +0.72 w +0.63 0.00 0.00 RG +[] 0 d +643.68 766.08 72.00 -100.80 re +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +699.70 677.45 Td +(GND) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +719.28 680.33 Td +(1) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +730.080 679.680 m +715.680 679.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +699.70 684.65 Td +(GND) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +719.28 687.53 Td +(2) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +730.080 686.880 m +715.680 686.880 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +699.70 691.85 Td +(GND) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +719.28 694.73 Td +(3) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +730.080 694.080 m +715.680 694.080 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +699.70 706.25 Td +(GND) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +719.28 709.13 Td +(4) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +730.080 708.480 m +715.680 708.480 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +699.70 713.45 Td +(GND) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +719.28 716.33 Td +(5) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +730.080 715.680 m +715.680 715.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +696.06 720.65 Td +(RXEN) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +719.28 723.53 Td +(6) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +730.080 722.880 m +715.680 722.880 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +696.78 727.85 Td +(TXEN) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +719.28 730.73 Td +(7) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +730.080 730.080 m +715.680 730.080 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +698.96 735.05 Td +(DIO2) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +719.28 737.93 Td +(8) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +730.080 737.280 m +715.680 737.280 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +700.42 742.25 Td +(VCC) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +719.28 745.13 Td +(9) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +730.080 744.480 m +715.680 744.480 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +700.42 749.45 Td +(VCC) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +719.28 752.33 Td +(10) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +730.080 751.680 m +715.680 751.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +699.70 756.65 Td +(GND) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +719.28 759.53 Td +(11) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +730.080 758.880 m +715.680 758.880 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +645.12 756.65 Td +(GND) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +632.80 759.53 Td +(12) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +629.280 758.880 m +643.680 758.880 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +645.12 749.45 Td +(DIO1) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +632.80 752.33 Td +(13) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +629.280 751.680 m +643.680 751.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +645.12 742.25 Td +(BUSY) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +632.80 745.13 Td +(14) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +629.280 744.480 m +643.680 744.480 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +645.12 735.05 Td +(NRST) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +632.80 737.93 Td +(15) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +629.280 737.280 m +643.680 737.280 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +645.12 727.85 Td +(MISO) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +632.80 730.73 Td +(16) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +629.280 730.080 m +643.680 730.080 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +645.12 720.65 Td +(MOSI) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +632.80 723.53 Td +(17) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +629.280 722.880 m +643.680 722.880 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +645.12 713.45 Td +(SCK) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +632.80 716.33 Td +(18) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +629.280 715.680 m +643.680 715.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +645.12 706.25 Td +(NSS) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +632.80 709.13 Td +(19) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +629.280 708.480 m +643.680 708.480 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +645.12 691.85 Td +(GND) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +632.80 694.73 Td +(20) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +629.280 694.080 m +643.680 694.080 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +645.12 684.65 Td +(ANT) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +632.80 687.53 Td +(21) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +629.280 686.880 m +643.680 686.880 l +S +1 J +1 j +0.72 w +0.20 0.80 0.20 RG +[] 0 d +626.400 689.760 m +632.160 684.000 l +626.400 684.000 m +632.160 689.760 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +645.12 677.45 Td +(GND) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +632.80 680.33 Td +(22) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +629.280 679.680 m +643.680 679.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 0.502 rg +675.33 775.29 Td +(E22-900M30S) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +675.33 768.81 Td +(E22-900M30S) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +396.72 677.45 Td +(GND) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +384.40 680.33 Td +(22) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +380.880 679.680 m +395.280 679.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +396.72 684.65 Td +(ANT) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +384.40 687.53 Td +(21) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +380.880 686.880 m +395.280 686.880 l +S +1 J +1 j +0.72 w +0.20 0.80 0.20 RG +[] 0 d +378.000 689.760 m +383.760 684.000 l +378.000 684.000 m +383.760 689.760 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +396.72 691.85 Td +(GND) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +384.40 694.73 Td +(20) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +380.880 694.080 m +395.280 694.080 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +396.72 706.25 Td +(NSS) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +384.40 709.13 Td +(19) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +380.880 708.480 m +395.280 708.480 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +396.72 713.45 Td +(SCK) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +384.40 716.33 Td +(18) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +380.880 715.680 m +395.280 715.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +396.72 720.65 Td +(MOSI) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +384.40 723.53 Td +(17) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +380.880 722.880 m +395.280 722.880 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +396.72 727.85 Td +(MISO) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +384.40 730.73 Td +(16) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +380.880 730.080 m +395.280 730.080 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +396.72 735.05 Td +(NRST) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +384.40 737.93 Td +(15) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +380.880 737.280 m +395.280 737.280 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +396.72 742.25 Td +(BUSY) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +384.40 745.13 Td +(14) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +380.880 744.480 m +395.280 744.480 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +396.72 749.45 Td +(DIO1) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +384.40 752.33 Td +(13) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +380.880 751.680 m +395.280 751.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +396.72 756.65 Td +(GND) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +384.40 759.53 Td +(12) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +380.880 758.880 m +395.280 758.880 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +451.30 756.65 Td +(GND) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +470.88 759.53 Td +(11) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +481.680 758.880 m +467.280 758.880 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +451.30 749.45 Td +(GND) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +470.88 752.33 Td +(10) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +481.680 751.680 m +467.280 751.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +452.02 742.25 Td +(VCC) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +470.88 745.13 Td +(9) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +481.680 744.480 m +467.280 744.480 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +450.56 735.05 Td +(DIO2) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +470.88 737.93 Td +(8) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +481.680 737.280 m +467.280 737.280 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +448.38 727.85 Td +(TXEN) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +470.88 730.73 Td +(7) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +481.680 730.080 m +467.280 730.080 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +447.66 720.65 Td +(RXEN) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +470.88 723.53 Td +(6) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +481.680 722.880 m +467.280 722.880 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +451.30 713.45 Td +(GND) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +470.88 716.33 Td +(5) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +481.680 715.680 m +467.280 715.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +451.30 706.25 Td +(GND) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +470.88 709.13 Td +(4) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +481.680 708.480 m +467.280 708.480 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +451.30 691.85 Td +(GND) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +470.88 694.73 Td +(3) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +481.680 694.080 m +467.280 694.080 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +451.30 684.65 Td +(GND) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +470.88 687.53 Td +(2) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +481.680 686.880 m +467.280 686.880 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +451.30 677.45 Td +(GND) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +470.88 680.33 Td +(1) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +481.680 679.680 m +467.280 679.680 l +S +2 J +0 j +72 M +0.72 w +0.63 0.00 0.00 RG +[] 0 d +395.28 766.08 72.00 -100.80 re +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 0.502 rg +426.93 775.29 Td +(E22-900M22S) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +426.93 768.81 Td +(E22-900M22S) Tj +ET +2 J +0 j +72 M +0.72 w +0.63 0.00 0.00 RG +[] 0 d +139.68 899.28 50.40 -64.80 re +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +144.36 895.68 m 144.36 896.28 143.88 896.76 143.28 896.76 c +142.68 896.76 142.20 896.28 142.20 895.68 c +142.20 895.08 142.68 894.60 143.28 894.60 c +143.88 894.60 144.36 895.08 144.36 895.68 c +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +142.34 889.13 Td +(ANT) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +135.68 892.73 Td +(1) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +132.480 892.080 m +139.680 892.080 l +S +1 J +1 j +0.72 w +0.20 0.80 0.20 RG +[] 0 d +129.600 894.960 m +135.360 889.200 l +129.600 889.200 m +135.360 894.960 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +142.34 881.93 Td +(GND) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +135.68 885.53 Td +(2) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +132.480 884.880 m +139.680 884.880 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +142.34 874.73 Td +(3.3V) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +135.68 878.33 Td +(3) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +132.480 877.680 m +139.680 877.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +142.34 867.53 Td +(RESET) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +135.68 871.13 Td +(4) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +132.480 870.480 m +139.680 870.480 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +142.34 860.33 Td +(TXEN) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +135.68 863.93 Td +(5) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +132.480 863.280 m +139.680 863.280 l +S +1 J +1 j +0.72 w +0.20 0.80 0.20 RG +[] 0 d +129.600 866.160 m +135.360 860.400 l +129.600 860.400 m +135.360 866.160 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +142.34 853.13 Td +(DIO1) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +135.68 856.73 Td +(6) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +132.480 856.080 m +139.680 856.080 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +142.34 845.93 Td +(DIO2) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +135.68 849.53 Td +(7) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +132.480 848.880 m +139.680 848.880 l +S +1 J +1 j +0.72 w +0.20 0.80 0.20 RG +[] 0 d +129.600 851.760 m +135.360 846.000 l +129.600 846.000 m +135.360 851.760 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +142.34 838.73 Td +(DIO3) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +135.68 842.33 Td +(8) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +132.480 841.680 m +139.680 841.680 l +S +1 J +1 j +0.72 w +0.20 0.80 0.20 RG +[] 0 d +129.600 844.560 m +135.360 838.800 l +129.600 838.800 m +135.360 844.560 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +172.87 838.73 Td +(GND) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +190.44 842.33 Td +(9) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +197.280 841.680 m +190.080 841.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +169.59 845.93 Td +(BUSY) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +190.44 849.53 Td +(10) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +197.280 848.880 m +190.080 848.880 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +169.23 853.13 Td +(RXEN) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +190.44 856.73 Td +(11) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +197.280 856.080 m +190.080 856.080 l +S +1 J +1 j +0.72 w +0.20 0.80 0.20 RG +[] 0 d +194.400 858.960 m +200.160 853.200 l +194.400 853.200 m +200.160 858.960 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +173.96 860.33 Td +(SCK) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +190.44 863.93 Td +(12) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +197.280 863.280 m +190.080 863.280 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +170.69 867.53 Td +(MISO) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +190.44 871.13 Td +(13) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +197.280 870.480 m +190.080 870.480 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +170.69 874.73 Td +(MOSI) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +190.44 878.33 Td +(14) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +197.280 877.680 m +190.080 877.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +173.96 881.93 Td +(NSS) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +190.44 885.53 Td +(15) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +197.280 884.880 m +190.080 884.880 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +172.87 889.13 Td +(GND) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +190.44 892.73 Td +(16) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +197.280 892.080 m +190.080 892.080 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 0.502 rg +161.24 908.49 Td +(HT-RA62) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +161.24 902.01 Td +(RA-01SH) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +132.480 870.480 m +128.880 866.880 l +118.080 866.880 l +118.080 874.080 l +128.880 874.080 l +132.480 870.480 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +132.480 870.480 m +132.480 870.480 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +98.82 867.73 Td +(NRST) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +132.480 856.080 m +128.880 852.480 l +118.080 852.480 l +118.080 859.680 l +128.880 859.680 l +132.480 856.080 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +132.480 856.080 m +132.480 856.080 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +105.00 853.21 Td +(IRQ) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +197.280 884.880 m +200.880 888.480 l +211.680 888.480 l +211.680 881.280 l +200.880 881.280 l +197.280 884.880 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +197.280 884.880 m +197.280 884.880 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +213.12 882.05 Td +(CS) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +197.280 877.680 m +200.880 881.280 l +211.680 881.280 l +211.680 874.080 l +200.880 874.080 l +197.280 877.680 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +197.280 877.680 m +197.280 877.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +213.01 874.63 Td +(MOSI) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +197.280 870.480 m +200.880 874.080 l +211.680 874.080 l +211.680 866.880 l +200.880 866.880 l +197.280 870.480 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +197.280 870.480 m +197.280 870.480 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +213.06 867.43 Td +(MISO) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +197.280 863.280 m +200.880 866.880 l +211.680 866.880 l +211.680 859.680 l +200.880 859.680 l +197.280 863.280 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +197.280 863.280 m +197.280 863.280 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +212.80 860.23 Td +(SCK) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +197.280 848.880 m +200.880 852.480 l +211.680 852.480 l +211.680 845.280 l +200.880 845.280 l +197.280 848.880 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +197.280 848.880 m +197.280 848.880 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +213.06 845.83 Td +(BUSY) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +197.280 834.480 m +197.280 841.680 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +190.800 834.480 m +203.760 834.480 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +192.960 833.040 m +201.600 833.040 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +195.120 831.600 m +199.440 831.600 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +196.560 830.160 m +198.000 830.160 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +197.280 841.680 m +197.280 841.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +187.92 821.59 Td +(GND) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +114.480 892.080 m +114.480 884.880 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +120.960 892.080 m +108.000 892.080 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +118.800 893.520 m +110.160 893.520 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +116.640 894.960 m +112.320 894.960 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +115.200 896.400 m +113.760 896.400 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +114.480 884.880 m +114.480 884.880 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +105.12 898.15 Td +(GND) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +218.880 899.280 m +218.880 892.080 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +225.360 899.280 m +212.400 899.280 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +223.200 900.720 m +214.560 900.720 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +221.040 902.160 m +216.720 902.160 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +219.600 903.600 m +218.160 903.600 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +218.880 892.080 m +218.880 892.080 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +209.52 905.35 Td +(GND) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +118.080 877.680 m +125.280 877.680 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +118.080 874.080 m +118.080 881.280 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +125.280 877.680 m +125.280 877.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +99.95 874.27 Td +(VCC) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +385.92 893.45 Td +(RF_SW) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +377.24 896.33 Td +(1) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +370.080 895.680 m +384.480 895.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +385.92 886.25 Td +(MISO) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +377.24 889.13 Td +(2) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +370.080 888.480 m +384.480 888.480 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +385.92 879.05 Td +(MOSI) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +377.24 881.93 Td +(3) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +370.080 881.280 m +384.480 881.280 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +385.92 871.85 Td +(CLK) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +377.24 874.73 Td +(4) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +370.080 874.080 m +384.480 874.080 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +385.92 864.65 Td +(RST) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +377.24 867.53 Td +(5) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +370.080 866.880 m +384.480 866.880 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +385.92 857.45 Td +(NSS) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +377.24 860.33 Td +(6) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +370.080 859.680 m +384.480 859.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +385.92 850.25 Td +(GND) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +377.24 853.13 Td +(7) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +370.080 852.480 m +384.480 852.480 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +385.92 843.05 Td +(VCC) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +377.24 845.93 Td +(8) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +370.080 845.280 m +384.480 845.280 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +449.15 864.65 Td +(ANT) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +467.28 867.53 Td +(9) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +478.080 866.880 m +463.680 866.880 l +S +1 J +1 j +0.72 w +0.20 0.80 0.20 RG +[] 0 d +475.200 869.760 m +480.960 864.000 l +475.200 864.000 m +480.960 869.760 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +447.70 871.85 Td +(GND) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +467.28 874.73 Td +(10) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +478.080 874.080 m +463.680 874.080 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +444.42 879.05 Td +(BUSY) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +467.28 881.93 Td +(11) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +478.080 881.280 m +463.680 881.280 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +446.96 886.25 Td +(DIO1) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +467.28 889.13 Td +(12) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +478.080 888.480 m +463.680 888.480 l +S +2 J +0 j +72 M +0.72 w +0.63 0.00 0.00 RG +[] 0 d +384.48 910.08 79.20 -79.20 re +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +460.08 845.28 m 460.08 851.24 455.24 856.08 449.28 856.08 c +443.32 856.08 438.48 851.24 438.48 845.28 c +438.48 839.32 443.32 834.48 449.28 834.48 c +455.24 834.48 460.08 839.32 460.08 845.28 c +S +2 J +0 j +72 M +0.72 w +0.63 0.00 0.00 RG +[] 0 d +434.88 859.68 28.80 -28.80 re +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 0.502 rg +419.73 919.05 Td +(SEEED_WIO-SX1262) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +419.73 912.57 Td +(Seeed-wio-SX1262) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +478.080 881.280 m +481.680 884.880 l +492.480 884.880 l +492.480 877.680 l +481.680 877.680 l +478.080 881.280 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +478.080 881.280 m +478.080 881.280 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +493.86 878.23 Td +(BUSY) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +370.080 866.880 m +366.480 863.280 l +355.680 863.280 l +355.680 870.480 l +366.480 870.480 l +370.080 866.880 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +370.080 866.880 m +370.080 866.880 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +336.42 864.13 Td +(NRST) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +370.080 888.480 m +366.480 884.880 l +355.680 884.880 l +355.680 892.080 l +366.480 892.080 l +370.080 888.480 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +370.080 888.480 m +370.080 888.480 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +335.10 885.86 Td +(MISO) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +370.080 881.280 m +366.480 877.680 l +355.680 877.680 l +355.680 884.880 l +366.480 884.880 l +370.080 881.280 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +370.080 881.280 m +370.080 881.280 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +335.15 878.66 Td +(MOSI) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +370.080 859.680 m +366.480 856.080 l +355.680 856.080 l +355.680 863.280 l +366.480 863.280 l +370.080 859.680 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +370.080 859.680 m +370.080 859.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +345.15 856.85 Td +(CS) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +370.080 874.080 m +366.480 870.480 l +355.680 870.480 l +355.680 877.680 l +366.480 877.680 l +370.080 874.080 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +370.080 874.080 m +370.080 874.080 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +339.30 871.46 Td +(SCK) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +478.080 888.480 m +481.680 892.080 l +492.480 892.080 l +492.480 884.880 l +481.680 884.880 l +478.080 888.480 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +478.080 888.480 m +478.080 888.480 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +493.88 885.43 Td +(IRQ) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +485.280 874.080 m +478.080 874.080 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +485.280 867.600 m +485.280 880.560 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +486.720 869.760 m +486.720 878.400 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +488.160 871.920 m +488.160 876.240 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +489.600 873.360 m +489.600 874.800 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +478.080 874.080 m +478.080 874.080 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +489.96 870.73 Td +(GND) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +362.880 852.480 m +370.080 852.480 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +362.880 858.960 m +362.880 846.000 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +361.440 856.800 m +361.440 848.160 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +360.000 854.640 m +360.000 850.320 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +358.560 853.200 m +358.560 851.760 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +370.080 852.480 m +370.080 852.480 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +339.48 849.13 Td +(GND) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +362.880 845.280 m +370.080 845.280 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +362.880 841.680 m +362.880 848.880 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +370.080 845.280 m +370.080 845.280 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +344.75 841.93 Td +(VCC) Tj +ET +2 J +0 j +72 M +0.72 w +0.63 0.00 0.00 RG +[] 0 d +92.88 740.88 14.40 -14.40 re +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +0.00 1.00 -1.00 0.00 98.05 717.86 Tm +(1) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +100.080 712.080 m +100.080 726.480 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +89.24 728.45 Td +(2) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +85.680 733.680 m +92.880 733.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +107.28 728.45 Td +(3) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +114.480 733.680 m +107.280 733.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +0.00 1.00 -1.00 0.00 98.05 739.50 Tm +(4) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +100.080 748.080 m +100.080 740.880 l +S +1 J +1 j +0.72 w +0.20 0.80 0.20 RG +[] 0 d +97.200 750.960 m +102.960 745.200 l +97.200 745.200 m +102.960 750.960 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +101.52 733.68 m 101.52 734.48 100.88 735.12 100.08 735.12 c +99.28 735.12 98.64 734.48 98.64 733.68 c +98.64 732.88 99.28 732.24 100.08 732.24 c +100.88 732.24 101.52 732.88 101.52 733.68 c +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +100.080 732.240 m +100.080 726.480 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 0.502 rg +84.92 757.88 Td +(U3) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +84.92 751.33 Td +(AMC-U_FL) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +85.680 726.480 m +85.680 733.680 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +79.200 726.480 m +92.160 726.480 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +81.360 725.040 m +90.000 725.040 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +83.520 723.600 m +87.840 723.600 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +84.960 722.160 m +86.400 722.160 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +85.680 733.680 m +85.680 733.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +76.32 713.77 Td +(GND) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +737.280 568.080 m +730.080 568.080 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +737.280 571.680 m +737.280 564.480 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +730.080 568.080 m +730.080 568.080 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +738.00 564.73 Td +(VCC) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +730.080 575.280 m +733.680 578.880 l +744.480 578.880 l +744.480 571.680 l +733.680 571.680 l +730.080 575.280 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +730.080 575.280 m +730.080 575.280 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +745.93 572.23 Td +(DIO2) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +737.280 593.280 m +730.080 593.280 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +737.280 596.880 m +737.280 589.680 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +730.080 593.280 m +730.080 593.280 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +737.64 589.87 Td +(+5V) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +730.080 514.080 m +730.080 521.280 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +723.600 514.080 m +736.560 514.080 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +725.760 512.640 m +734.400 512.640 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +727.920 511.200 m +732.240 511.200 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +729.360 509.760 m +730.800 509.760 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +730.080 521.280 m +730.080 521.280 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +720.72 501.19 Td +(GND) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +629.280 582.480 m +625.680 578.880 l +614.880 578.880 l +614.880 586.080 l +625.680 586.080 l +629.280 582.480 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +629.280 582.480 m +629.280 582.480 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +595.62 579.73 Td +(NRST) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +629.280 596.880 m +625.680 593.280 l +614.880 593.280 l +614.880 600.480 l +625.680 600.480 l +629.280 596.880 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +629.280 596.880 m +629.280 596.880 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +601.80 594.01 Td +(IRQ) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +730.080 582.480 m +733.680 586.080 l +744.480 586.080 l +744.480 578.880 l +733.680 578.880 l +730.080 582.480 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +730.080 582.480 m +730.080 582.480 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +745.93 579.43 Td +(DIO2) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +629.280 553.680 m +625.680 550.080 l +614.880 550.080 l +614.880 557.280 l +625.680 557.280 l +629.280 553.680 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +629.280 553.680 m +629.280 553.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +604.35 550.81 Td +(CS) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +629.280 568.080 m +625.680 564.480 l +614.880 564.480 l +614.880 571.680 l +625.680 571.680 l +629.280 568.080 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +629.280 568.080 m +629.280 568.080 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +596.71 565.21 Td +(MOSI) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +629.280 575.280 m +625.680 571.680 l +614.880 571.680 l +614.880 578.880 l +625.680 578.880 l +629.280 575.280 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +629.280 575.280 m +629.280 575.280 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +596.71 572.41 Td +(MISO) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +629.280 560.880 m +625.680 557.280 l +614.880 557.280 l +614.880 564.480 l +625.680 564.480 l +629.280 560.880 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +629.280 560.880 m +629.280 560.880 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +599.98 558.01 Td +(SCK) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +629.280 589.680 m +625.680 586.080 l +614.880 586.080 l +614.880 593.280 l +625.680 593.280 l +629.280 589.680 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +629.280 589.680 m +629.280 589.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +595.62 586.81 Td +(BUSY) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +622.080 539.280 m +629.280 539.280 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +622.080 545.760 m +622.080 532.800 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +620.640 543.600 m +620.640 534.960 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +619.200 541.440 m +619.200 537.120 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +617.760 540.000 m +617.760 538.560 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +629.280 539.280 m +629.280 539.280 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +598.68 535.87 Td +(GND) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +622.080 524.880 m +629.280 524.880 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +622.080 531.360 m +622.080 518.400 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +620.640 529.200 m +620.640 520.560 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +619.200 527.040 m +619.200 522.720 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +617.760 525.600 m +617.760 524.160 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +629.280 524.880 m +629.280 524.880 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +598.68 521.47 Td +(GND) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +730.080 611.280 m +730.080 604.080 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +736.560 611.280 m +723.600 611.280 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +734.400 612.720 m +725.760 612.720 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +732.240 614.160 m +727.920 614.160 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +730.800 615.600 m +729.360 615.600 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +730.080 604.080 m +730.080 604.080 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +720.72 617.35 Td +(GND) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +622.080 604.080 m +629.280 604.080 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +622.080 610.560 m +622.080 597.600 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +620.640 608.400 m +620.640 599.760 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +619.200 606.240 m +619.200 601.920 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +617.760 604.800 m +617.760 603.360 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +629.280 604.080 m +629.280 604.080 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +598.68 600.67 Td +(GND) Tj +ET +2 J +0 j +72 M +0.72 w +0.63 0.00 0.00 RG +[] 0 d +643.68 611.28 72.00 -100.80 re +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +699.70 522.65 Td +(GND) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +719.28 525.53 Td +(1) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +730.080 524.880 m +715.680 524.880 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +699.70 529.85 Td +(GND) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +719.28 532.73 Td +(2) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +730.080 532.080 m +715.680 532.080 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +699.70 537.05 Td +(GND) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +719.28 539.93 Td +(3) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +730.080 539.280 m +715.680 539.280 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +699.70 551.45 Td +(GND) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +719.28 554.33 Td +(4) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +730.080 553.680 m +715.680 553.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +699.70 558.65 Td +(GND) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +719.28 561.53 Td +(5) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +730.080 560.880 m +715.680 560.880 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +707.69 565.85 Td +(IN) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +719.28 568.73 Td +(6) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +730.080 568.080 m +715.680 568.080 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +684.79 573.05 Td +(T/R CTRL) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +719.28 575.93 Td +(7) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +730.080 575.280 m +715.680 575.280 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +698.96 580.25 Td +(DIO2) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +719.28 583.13 Td +(8) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +730.080 582.480 m +715.680 582.480 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +700.42 587.45 Td +(VCC) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +719.28 590.33 Td +(9) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +730.080 589.680 m +715.680 589.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +700.42 594.65 Td +(VCC) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +719.28 597.53 Td +(10) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +730.080 596.880 m +715.680 596.880 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +699.70 601.85 Td +(GND) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +719.28 604.73 Td +(11) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +730.080 604.080 m +715.680 604.080 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +645.12 601.85 Td +(GND) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +632.80 604.73 Td +(12) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +629.280 604.080 m +643.680 604.080 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +645.12 594.65 Td +(DIO1) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +632.80 597.53 Td +(13) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +629.280 596.880 m +643.680 596.880 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +645.12 587.45 Td +(BUSY) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +632.80 590.33 Td +(14) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +629.280 589.680 m +643.680 589.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +645.12 580.25 Td +(NRST) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +632.80 583.13 Td +(15) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +629.280 582.480 m +643.680 582.480 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +645.12 573.05 Td +(MISO) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +632.80 575.93 Td +(16) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +629.280 575.280 m +643.680 575.280 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +645.12 565.85 Td +(MOSI) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +632.80 568.73 Td +(17) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +629.280 568.080 m +643.680 568.080 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +645.12 558.65 Td +(SCK) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +632.80 561.53 Td +(18) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +629.280 560.880 m +643.680 560.880 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +645.12 551.45 Td +(NSS) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +632.80 554.33 Td +(19) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +629.280 553.680 m +643.680 553.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +645.12 537.05 Td +(GND) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +632.80 539.93 Td +(20) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +629.280 539.280 m +643.680 539.280 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +645.12 529.85 Td +(ANT) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +632.80 532.73 Td +(21) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +629.280 532.080 m +643.680 532.080 l +S +1 J +1 j +0.72 w +0.20 0.80 0.20 RG +[] 0 d +626.400 534.960 m +632.160 529.200 l +626.400 529.200 m +632.160 534.960 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +645.12 522.65 Td +(GND) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +632.80 525.53 Td +(22) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +629.280 524.880 m +643.680 524.880 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +672.35 613.83 Td +(E22P-868M30S) Tj +ET +7.20 w +BT +/F2 6.363634909090909 Tf +7.00 TL +0.627 0.000 0.000 rg +387.36 436.61 Td +(ANT_Lora) Tj +ET +7.20 w +BT +/F2 6.363634909090909 Tf +7.00 TL +0.627 0.000 0.000 rg +387.36 428.69 Td +(GND) Tj +ET +7.20 w +BT +/F2 6.363634909090909 Tf +7.00 TL +0.627 0.000 0.000 rg +387.36 421.49 Td +(DIO9) Tj +ET +7.20 w +BT +/F2 6.363634909090909 Tf +7.00 TL +0.627 0.000 0.000 rg +387.36 414.29 Td +(DIO8) Tj +ET +7.20 w +BT +/F2 6.363634909090909 Tf +7.00 TL +0.627 0.000 0.000 rg +387.36 407.81 Td +(DIO7) Tj +ET +7.20 w +BT +/F2 6.363634909090909 Tf +7.00 TL +0.627 0.000 0.000 rg +387.36 400.61 Td +(NC) Tj +ET +7.20 w +BT +/F2 6.363634909090909 Tf +7.00 TL +0.627 0.000 0.000 rg +387.36 392.69 Td +(GND) Tj +ET +7.20 w +BT +/F2 6.363634909090909 Tf +7.00 TL +0.627 0.000 0.000 rg +387.36 385.49 Td +(3V3) Tj +ET +7.20 w +BT +/F2 6.363634909090909 Tf +7.00 TL +0.627 0.000 0.000 rg +423.36 436.61 Td +(ANT_2.4) Tj +ET +7.20 w +BT +/F2 6.363634909090909 Tf +7.00 TL +0.627 0.000 0.000 rg +434.88 429.41 Td +(GND) Tj +ET +7.20 w +BT +/F2 6.363634909090909 Tf +7.00 TL +0.627 0.000 0.000 rg +440.64 422.21 Td +(CS) Tj +ET +7.20 w +BT +/F2 6.363634909090909 Tf +7.00 TL +0.627 0.000 0.000 rg +437.04 414.29 Td +(CLK) Tj +ET +7.20 w +BT +/F2 6.363634909090909 Tf +7.00 TL +0.627 0.000 0.000 rg +432.00 407.81 Td +(MOSI) Tj +ET +7.20 w +BT +/F2 6.363634909090909 Tf +7.00 TL +0.627 0.000 0.000 rg +432.00 400.61 Td +(MISO) Tj +ET +7.20 w +BT +/F2 6.363634909090909 Tf +7.00 TL +0.627 0.000 0.000 rg +428.40 392.69 Td +(RESET) Tj +ET +7.20 w +BT +/F2 6.363634909090909 Tf +7.00 TL +0.627 0.000 0.000 rg +432.00 385.49 Td +(BUSY) Tj +ET +2 J +0 j +72 M +0.72 w +0.63 0.00 0.00 RG +[] 0 d +384.48 445.68 68.40 -64.80 re +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +389.16 442.08 m 389.16 442.68 388.68 443.16 388.08 443.16 c +387.48 443.16 387.00 442.68 387.00 442.08 c +387.00 441.48 387.48 441.00 388.08 441.00 c +388.68 441.00 389.16 441.48 389.16 442.08 c +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +380.12 439.13 Td +(1) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +377.280 438.480 m +384.480 438.480 l +S +1 J +1 j +0.72 w +0.20 0.80 0.20 RG +[] 0 d +374.400 441.360 m +380.160 435.600 l +374.400 435.600 m +380.160 441.360 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +380.12 431.93 Td +(2) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +377.280 431.280 m +384.480 431.280 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +380.12 424.73 Td +(3) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +377.280 424.080 m +384.480 424.080 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +380.12 417.53 Td +(4) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +377.280 416.880 m +384.480 416.880 l +S +1 J +1 j +0.72 w +0.20 0.80 0.20 RG +[] 0 d +374.400 419.760 m +380.160 414.000 l +374.400 414.000 m +380.160 419.760 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +380.12 410.33 Td +(5) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +377.280 409.680 m +384.480 409.680 l +S +1 J +1 j +0.72 w +0.20 0.80 0.20 RG +[] 0 d +374.400 412.560 m +380.160 406.800 l +374.400 406.800 m +380.160 412.560 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +380.12 403.13 Td +(6) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +377.280 402.480 m +384.480 402.480 l +S +1 J +1 j +0.72 w +0.20 0.80 0.20 RG +[] 0 d +374.400 405.360 m +380.160 399.600 l +374.400 399.600 m +380.160 405.360 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +380.12 395.93 Td +(7) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +377.280 395.280 m +384.480 395.280 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +380.12 388.73 Td +(8) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +377.280 388.080 m +384.480 388.080 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +454.32 388.73 Td +(9) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +460.080 388.080 m +452.880 388.080 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +452.88 395.93 Td +(10) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +460.080 395.280 m +452.880 395.280 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +452.88 403.13 Td +(11) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +460.080 402.480 m +452.880 402.480 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +452.88 410.33 Td +(12) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +460.080 409.680 m +452.880 409.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +452.88 417.53 Td +(13) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +460.080 416.880 m +452.880 416.880 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +452.88 424.73 Td +(14) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +460.080 424.080 m +452.880 424.080 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +452.88 431.93 Td +(15) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +460.080 431.280 m +452.880 431.280 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +452.88 439.13 Td +(16) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +460.080 438.480 m +452.880 438.480 l +S +1 J +1 j +0.72 w +0.20 0.80 0.20 RG +[] 0 d +457.200 441.360 m +462.960 435.600 l +457.200 435.600 m +462.960 441.360 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 0.502 rg +388.08 453.21 Td +(WAVESHARE_LORA_CORE_1121) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +388.08 447.20 Td +(LoRa Core1121-XF) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +377.280 424.080 m +373.680 427.680 l +362.880 427.680 l +362.880 420.480 l +373.680 420.480 l +377.280 424.080 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +377.280 424.080 m +377.280 424.080 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +348.16 420.93 Td +(IRQ) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +460.080 388.080 m +463.680 384.480 l +474.480 384.480 l +474.480 391.680 l +463.680 391.680 l +460.080 388.080 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +460.080 388.080 m +460.080 388.080 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +475.55 385.64 Td +(BUSY) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +460.080 395.280 m +463.680 391.680 l +474.480 391.680 l +474.480 398.880 l +463.680 398.880 l +460.080 395.280 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +460.080 395.280 m +460.080 395.280 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +476.44 392.12 Td +(NRST) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +370.080 388.080 m +377.280 388.080 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +370.080 391.680 m +370.080 384.480 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +377.280 388.080 m +377.280 388.080 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +355.90 384.73 Td +(VCC) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +460.080 424.080 m +463.680 427.680 l +474.480 427.680 l +474.480 420.480 l +463.680 420.480 l +460.080 424.080 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +460.080 424.080 m +460.080 424.080 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +475.92 421.36 Td +(CS) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +460.080 416.880 m +463.680 420.480 l +474.480 420.480 l +474.480 413.280 l +463.680 413.280 l +460.080 416.880 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +460.080 416.880 m +460.080 416.880 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +475.71 414.16 Td +(SCK) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +460.080 402.480 m +463.680 406.080 l +474.480 406.080 l +474.480 398.880 l +463.680 398.880 l +460.080 402.480 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +460.080 402.480 m +460.080 402.480 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +475.92 399.04 Td +(MISO) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +460.080 409.680 m +463.680 413.280 l +474.480 413.280 l +474.480 406.080 l +463.680 406.080 l +460.080 409.680 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +460.080 409.680 m +460.080 409.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +475.92 406.24 Td +(MOSI) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +370.080 431.280 m +377.280 431.280 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +370.080 437.760 m +370.080 424.800 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +368.640 435.600 m +368.640 426.960 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +367.200 433.440 m +367.200 429.120 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +365.760 432.000 m +365.760 430.560 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +377.280 431.280 m +377.280 431.280 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +346.68 427.87 Td +(GND) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +467.280 431.280 m +460.080 431.280 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +467.280 424.800 m +467.280 437.760 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +468.720 426.960 m +468.720 435.600 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +470.160 429.120 m +470.160 433.440 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +471.600 430.560 m +471.600 432.000 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +460.080 431.280 m +460.080 431.280 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +471.96 427.87 Td +(GND) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +370.080 395.280 m +377.280 395.280 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +370.080 401.760 m +370.080 388.800 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +368.640 399.600 m +368.640 390.960 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +367.200 397.440 m +367.200 393.120 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +365.760 396.000 m +365.760 394.560 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +377.280 395.280 m +377.280 395.280 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +346.68 391.87 Td +(GND) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +380.880 557.280 m +388.080 557.280 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +380.880 563.760 m +380.880 550.800 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +379.440 561.600 m +379.440 552.960 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +378.000 559.440 m +378.000 555.120 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +376.560 558.000 m +376.560 556.560 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +388.080 557.280 m +388.080 557.280 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +357.48 553.87 Td +(GND) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +388.080 586.080 m +384.480 582.480 l +373.680 582.480 l +373.680 589.680 l +384.480 589.680 l +388.080 586.080 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +388.080 586.080 m +388.080 586.080 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +354.06 583.25 Td +(RXEN) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +456.480 593.280 m +449.280 593.280 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +456.480 586.800 m +456.480 599.760 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +457.920 588.960 m +457.920 597.600 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +459.360 591.120 m +459.360 595.440 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +460.800 592.560 m +460.800 594.000 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +449.280 593.280 m +449.280 593.280 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +461.16 589.87 Td +(GND) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +380.880 600.480 m +388.080 600.480 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +380.880 606.960 m +380.880 594.000 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +379.440 604.800 m +379.440 596.160 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +378.000 602.640 m +378.000 598.320 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +376.560 601.200 m +376.560 599.760 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +388.080 600.480 m +388.080 600.480 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +357.48 597.07 Td +(GND) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +380.880 593.280 m +388.080 593.280 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +380.880 599.760 m +380.880 586.800 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +379.440 597.600 m +379.440 588.960 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +378.000 595.440 m +378.000 591.120 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +376.560 594.000 m +376.560 592.560 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +388.080 593.280 m +388.080 593.280 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +357.48 589.87 Td +(GND) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +449.280 571.680 m +452.880 575.280 l +463.680 575.280 l +463.680 568.080 l +452.880 568.080 l +449.280 571.680 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +449.280 571.680 m +449.280 571.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +465.12 568.24 Td +(MOSI) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +449.280 564.480 m +452.880 568.080 l +463.680 568.080 l +463.680 560.880 l +452.880 560.880 l +449.280 564.480 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +449.280 564.480 m +449.280 564.480 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +465.12 561.04 Td +(MISO) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +449.280 578.880 m +452.880 582.480 l +463.680 582.480 l +463.680 575.280 l +452.880 575.280 l +449.280 578.880 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +449.280 578.880 m +449.280 578.880 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +464.91 576.16 Td +(SCK) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +449.280 586.080 m +452.880 589.680 l +463.680 589.680 l +463.680 582.480 l +452.880 582.480 l +449.280 586.080 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +449.280 586.080 m +449.280 586.080 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +465.12 583.36 Td +(CS) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +380.880 550.080 m +388.080 550.080 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +380.880 553.680 m +380.880 546.480 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +388.080 550.080 m +388.080 550.080 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +366.70 546.73 Td +(VCC) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +449.280 557.280 m +452.880 553.680 l +463.680 553.680 l +463.680 560.880 l +452.880 560.880 l +449.280 557.280 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +449.280 557.280 m +449.280 557.280 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +465.64 554.12 Td +(NRST) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +449.280 550.080 m +452.880 546.480 l +463.680 546.480 l +463.680 553.680 l +452.880 553.680 l +449.280 550.080 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +449.280 550.080 m +449.280 550.080 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +464.75 547.64 Td +(BUSY) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +388.080 564.480 m +384.480 568.080 l +373.680 568.080 l +373.680 560.880 l +384.480 560.880 l +388.080 564.480 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +388.080 564.480 m +388.080 564.480 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +358.96 561.33 Td +(IRQ) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +449.280 600.480 m +452.880 604.080 l +463.680 604.080 l +463.680 596.880 l +452.880 596.880 l +449.280 600.480 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +449.280 600.480 m +449.280 600.480 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +465.65 597.65 Td +(LORA_ANT) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +388.080 571.680 m +384.480 568.080 l +373.680 568.080 l +373.680 575.280 l +384.480 575.280 l +388.080 571.680 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +388.080 571.680 m +388.080 571.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +354.75 569.03 Td +(DIO2) Tj +ET +7.20 w +BT +/F2 6.363634909090909 Tf +7.00 TL +0.627 0.000 0.000 rg +398.16 597.89 Td +(GND) Tj +ET +7.20 w +BT +/F2 6.363634909090909 Tf +7.00 TL +0.627 0.000 0.000 rg +398.16 590.69 Td +(GND) Tj +ET +7.20 w +BT +/F2 6.363634909090909 Tf +7.00 TL +0.627 0.000 0.000 rg +398.16 583.49 Td +(RXEN) Tj +ET +7.20 w +BT +/F2 6.363634909090909 Tf +7.00 TL +0.627 0.000 0.000 rg +398.16 576.29 Td +(TXEN) Tj +ET +7.20 w +BT +/F2 6.363634909090909 Tf +7.00 TL +0.627 0.000 0.000 rg +398.16 569.81 Td +(DIO2) Tj +ET +7.20 w +BT +/F2 6.363634909090909 Tf +7.00 TL +0.627 0.000 0.000 rg +398.16 562.61 Td +(DIO1) Tj +ET +7.20 w +BT +/F2 6.363634909090909 Tf +7.00 TL +0.627 0.000 0.000 rg +398.16 554.69 Td +(GND) Tj +ET +7.20 w +BT +/F2 6.363634909090909 Tf +7.00 TL +0.627 0.000 0.000 rg +398.16 547.49 Td +(3V3) Tj +ET +7.20 w +BT +/F2 6.363634909090909 Tf +7.00 TL +0.627 0.000 0.000 rg +424.80 598.61 Td +(ANT) Tj +ET +7.20 w +BT +/F2 6.363634909090909 Tf +7.00 TL +0.627 0.000 0.000 rg +424.08 591.41 Td +(GND) Tj +ET +7.20 w +BT +/F2 6.363634909090909 Tf +7.00 TL +0.627 0.000 0.000 rg +429.84 584.21 Td +(CS) Tj +ET +7.20 w +BT +/F2 6.363634909090909 Tf +7.00 TL +0.627 0.000 0.000 rg +426.24 576.29 Td +(CLK) Tj +ET +7.20 w +BT +/F2 6.363634909090909 Tf +7.00 TL +0.627 0.000 0.000 rg +421.20 569.81 Td +(MOSI) Tj +ET +7.20 w +BT +/F2 6.363634909090909 Tf +7.00 TL +0.627 0.000 0.000 rg +421.20 562.61 Td +(MISO) Tj +ET +7.20 w +BT +/F2 6.363634909090909 Tf +7.00 TL +0.627 0.000 0.000 rg +417.60 554.69 Td +(RESET) Tj +ET +7.20 w +BT +/F2 6.363634909090909 Tf +7.00 TL +0.627 0.000 0.000 rg +421.20 547.49 Td +(BUSY) Tj +ET +2 J +0 j +72 M +0.72 w +0.63 0.00 0.00 RG +[] 0 d +395.28 607.68 46.80 -64.80 re +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +399.96 604.08 m 399.96 604.68 399.48 605.16 398.88 605.16 c +398.28 605.16 397.80 604.68 397.80 604.08 c +397.80 603.48 398.28 603.00 398.88 603.00 c +399.48 603.00 399.96 603.48 399.96 604.08 c +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +390.92 601.13 Td +(1) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +388.080 600.480 m +395.280 600.480 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +390.92 593.93 Td +(2) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +388.080 593.280 m +395.280 593.280 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +390.92 586.73 Td +(3) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +388.080 586.080 m +395.280 586.080 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +390.92 579.53 Td +(4) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +388.080 578.880 m +395.280 578.880 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +390.92 572.33 Td +(5) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +388.080 571.680 m +395.280 571.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +390.92 565.13 Td +(6) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +388.080 564.480 m +395.280 564.480 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +390.92 557.93 Td +(7) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +388.080 557.280 m +395.280 557.280 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +390.92 550.73 Td +(8) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +388.080 550.080 m +395.280 550.080 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +443.52 550.73 Td +(9) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +449.280 550.080 m +442.080 550.080 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +442.08 557.93 Td +(10) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +449.280 557.280 m +442.080 557.280 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +442.08 565.13 Td +(11) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +449.280 564.480 m +442.080 564.480 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +442.08 572.33 Td +(12) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +449.280 571.680 m +442.080 571.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +442.08 579.53 Td +(13) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +449.280 578.880 m +442.080 578.880 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +442.08 586.73 Td +(14) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +449.280 586.080 m +442.080 586.080 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +442.08 593.93 Td +(15) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +449.280 593.280 m +442.080 593.280 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +442.08 601.13 Td +(16) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +449.280 600.480 m +442.080 600.480 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 0.502 rg +414.58 616.96 Td +(WAVESHARE_LORA_CORE_1262) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +414.58 610.89 Td +(LoRa Core1262-868M) Tj +ET +2 J +0 j +72 M +0.72 w +0.63 0.00 0.00 RG +[] 0 d +172.08 447.84 79.20 -108.00 re +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +248.76 433.44 m 248.76 434.04 248.28 434.52 247.68 434.52 c +247.08 434.52 246.60 434.04 246.60 433.44 c +246.60 432.84 247.08 432.36 247.68 432.36 c +248.28 432.36 248.76 432.84 248.76 433.44 c +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +234.07 439.01 Td +(GND) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +251.64 435.41 Td +(1) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +258.480 440.640 m +251.280 440.640 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +234.43 431.81 Td +(2.4G) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +251.64 428.21 Td +(2) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +258.480 433.440 m +251.280 433.440 l +S +1 J +1 j +0.72 w +0.20 0.80 0.20 RG +[] 0 d +255.600 436.320 m +261.360 430.560 l +255.600 430.560 m +261.360 436.320 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +234.07 424.61 Td +(GND) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +251.64 421.01 Td +(3) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +258.480 426.240 m +251.280 426.240 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +223.15 406.61 Td +(LR_NSS) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +251.64 403.01 Td +(4) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +258.480 408.240 m +251.280 408.240 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +223.15 399.41 Td +(LR_SCK) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +251.64 395.81 Td +(5) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +258.480 401.040 m +251.280 401.040 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +219.88 392.21 Td +(LR_MOSI) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +251.64 388.61 Td +(6) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +258.480 393.840 m +251.280 393.840 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +219.88 385.01 Td +(LR_MISO) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +251.64 381.41 Td +(7) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +258.480 386.640 m +251.280 386.640 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +218.79 377.81 Td +(LR_BUSY) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +251.64 374.21 Td +(8) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +258.480 379.440 m +251.280 379.440 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +234.07 370.61 Td +(GND) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +251.64 367.01 Td +(9) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +258.480 372.240 m +251.280 372.240 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +0.00 1.00 -1.00 0.00 232.69 341.13 Tm +(GND) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +0.00 1.00 -1.00 0.00 236.29 330.82 Tm +(10) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +229.680 332.640 m +229.680 339.840 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +0.00 1.00 -1.00 0.00 225.49 341.13 Tm +(DIO8) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +0.00 1.00 -1.00 0.00 229.09 330.82 Tm +(11) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +222.480 332.640 m +222.480 339.840 l +S +1 J +1 j +0.72 w +0.20 0.80 0.20 RG +[] 0 d +219.600 335.520 m +225.360 329.760 l +219.600 329.760 m +225.360 335.520 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +0.00 1.00 -1.00 0.00 218.29 341.13 Tm +(DIO9) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +0.00 1.00 -1.00 0.00 221.89 330.82 Tm +(12) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +215.280 332.640 m +215.280 339.840 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +0.00 1.00 -1.00 0.00 211.09 341.13 Tm +(GND) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +0.00 1.00 -1.00 0.00 214.69 330.82 Tm +(13) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +208.080 332.640 m +208.080 339.840 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +0.00 1.00 -1.00 0.00 203.89 341.13 Tm +(LR_nRST) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +0.00 1.00 -1.00 0.00 207.49 330.82 Tm +(14) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +200.880 332.640 m +200.880 339.840 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +0.00 1.00 -1.00 0.00 196.69 341.13 Tm +(GND) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +0.00 1.00 -1.00 0.00 200.29 330.82 Tm +(15) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +193.680 332.640 m +193.680 339.840 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +174.74 370.61 Td +(GND) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +164.44 367.01 Td +(16) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +164.880 372.240 m +172.080 372.240 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +174.74 377.81 Td +(GND) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +164.44 374.21 Td +(17) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +164.880 379.440 m +172.080 379.440 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +174.74 385.01 Td +(GND) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +164.44 381.41 Td +(18) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +164.880 386.640 m +172.080 386.640 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +174.74 392.21 Td +(GND) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +164.44 388.61 Td +(19) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +164.880 393.840 m +172.080 393.840 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +174.74 399.41 Td +(VDD_RF) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +164.44 395.81 Td +(20) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +164.880 401.040 m +172.080 401.040 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +174.74 406.61 Td +(VDD_RF) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +164.44 403.01 Td +(21) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +164.880 408.240 m +172.080 408.240 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +174.74 424.61 Td +(GND) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +164.44 421.01 Td +(22) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +164.880 426.240 m +172.080 426.240 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +174.74 431.81 Td +(SUBG_RF) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +164.44 428.21 Td +(23) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +164.880 433.440 m +172.080 433.440 l +S +1 J +1 j +0.72 w +0.20 0.80 0.20 RG +[] 0 d +162.000 436.320 m +167.760 430.560 l +162.000 430.560 m +167.760 436.320 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +174.74 439.01 Td +(GND) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +164.44 435.41 Td +(24) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +164.880 440.640 m +172.080 440.640 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 0.502 rg +205.52 456.81 Td +(Seeed_WIO-LR1121) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +205.52 450.33 Td +(Seeed_WIO-LR1121) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +265.680 426.240 m +258.480 426.240 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +265.680 419.760 m +265.680 432.720 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +267.120 421.920 m +267.120 430.560 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +268.560 424.080 m +268.560 428.400 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +270.000 425.520 m +270.000 426.960 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +258.480 426.240 m +258.480 426.240 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +270.36 422.89 Td +(GND) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +265.680 440.640 m +258.480 440.640 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +265.680 434.160 m +265.680 447.120 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +267.120 436.320 m +267.120 444.960 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +268.560 438.480 m +268.560 442.800 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +270.000 439.920 m +270.000 441.360 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +258.480 440.640 m +258.480 440.640 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +270.36 437.29 Td +(GND) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +157.680 440.640 m +164.880 440.640 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +157.680 447.120 m +157.680 434.160 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +156.240 444.960 m +156.240 436.320 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +154.800 442.800 m +154.800 438.480 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +153.360 441.360 m +153.360 439.920 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +164.880 440.640 m +164.880 440.640 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +134.04 437.29 Td +(GND) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +157.680 426.240 m +164.880 426.240 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +157.680 432.720 m +157.680 419.760 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +156.240 430.560 m +156.240 421.920 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +154.800 428.400 m +154.800 424.080 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +153.360 426.960 m +153.360 425.520 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +164.880 426.240 m +164.880 426.240 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +134.04 422.89 Td +(GND) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +154.080 408.240 m +161.280 408.240 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +154.080 404.640 m +154.080 411.840 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +161.280 408.240 m +161.280 408.240 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +135.94 404.89 Td +(VCC) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +154.080 393.840 m +161.280 393.840 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +154.080 400.320 m +154.080 387.360 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +152.640 398.160 m +152.640 389.520 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +151.200 396.000 m +151.200 391.680 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +149.760 394.560 m +149.760 393.120 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +161.280 393.840 m +161.280 393.840 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +130.44 390.49 Td +(GND) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +258.480 365.040 m +258.480 372.240 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +252.000 365.040 m +264.960 365.040 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +254.160 363.600 m +262.800 363.600 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +256.320 362.160 m +260.640 362.160 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +257.760 360.720 m +259.200 360.720 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +258.480 372.240 m +258.480 372.240 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +249.00 351.97 Td +(GND) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +215.280 303.840 m +218.880 307.440 l +229.680 307.440 l +229.680 300.240 l +218.880 300.240 l +215.280 303.840 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +215.280 303.840 m +215.280 303.840 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +231.68 301.41 Td +(IRQ) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +258.480 379.440 m +262.080 383.040 l +272.880 383.040 l +272.880 375.840 l +262.080 375.840 l +258.480 379.440 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +258.480 379.440 m +258.480 379.440 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +274.10 376.72 Td +(BUSY) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +240.480 321.840 m +240.480 329.040 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +246.960 321.840 m +234.000 321.840 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +244.800 320.400 m +236.160 320.400 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +242.640 318.960 m +238.320 318.960 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +241.200 317.520 m +239.760 317.520 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +240.480 329.040 m +240.480 329.040 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +235.17 308.77 Td +(GND) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +193.680 303.840 m +190.080 307.440 l +179.280 307.440 l +179.280 300.240 l +190.080 300.240 l +193.680 303.840 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +193.680 303.840 m +193.680 303.840 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +157.92 301.18 Td +(NRST) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +258.480 408.240 m +262.080 404.640 l +272.880 404.640 l +272.880 411.840 l +262.080 411.840 l +258.480 408.240 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +258.480 408.240 m +258.480 408.240 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +274.28 405.44 Td +(CS) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +258.480 401.040 m +262.080 397.440 l +272.880 397.440 l +272.880 404.640 l +262.080 404.640 l +258.480 401.040 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +258.480 401.040 m +258.480 401.040 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +273.07 398.24 Td +(SCK) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +258.480 393.840 m +262.080 390.240 l +272.880 390.240 l +272.880 397.440 l +262.080 397.440 l +258.480 393.840 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +258.480 393.840 m +258.480 393.840 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +274.72 391.04 Td +(MOSI) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +258.480 386.640 m +262.080 383.040 l +272.880 383.040 l +272.880 390.240 l +262.080 390.240 l +258.480 386.640 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +258.480 386.640 m +258.480 386.640 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +274.82 383.84 Td +(MISO) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +96.480 1032.480 m +92.880 1028.880 l +82.080 1028.880 l +82.080 1036.080 l +92.880 1036.080 l +96.480 1032.480 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +96.480 1032.480 m +96.480 1032.480 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +63.54 1029.61 Td +(P1.06) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +96.480 1111.680 m +92.880 1108.080 l +82.080 1108.080 l +82.080 1115.280 l +92.880 1115.280 l +96.480 1111.680 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +96.480 1111.680 m +96.480 1111.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +44.63 1108.96 Td +(SERIAL2RX) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +96.480 1104.480 m +92.880 1100.880 l +82.080 1100.880 l +82.080 1108.080 l +92.880 1108.080 l +96.480 1104.480 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +96.480 1104.480 m +96.480 1104.480 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +45.35 1101.76 Td +(SERIAL2TX) Tj +ET +2 J +0 j +72 M +0.72 w +0.63 0.00 0.00 RG +[] 0 d +272.88 1075.68 7.20 -14.40 re +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +276.480 1082.880 m +276.480 1075.680 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +276.480 1054.080 m +276.480 1061.280 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 0.502 rg +281.52 1066.47 Td +(R_ADC_B) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +281.52 1059.99 Td +(1.5M) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +276.480 1111.680 m +272.880 1115.280 l +272.880 1126.080 l +280.080 1126.080 l +280.080 1115.280 l +276.480 1111.680 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +276.480 1111.680 m +276.480 1111.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +0.00 1.00 -1.00 0.00 277.83 1126.29 Tm +(BATT) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +276.480 1046.880 m +276.480 1054.080 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +270.000 1046.880 m +282.960 1046.880 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +272.160 1045.440 m +280.800 1045.440 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +274.320 1044.000 m +278.640 1044.000 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +275.760 1042.560 m +277.200 1042.560 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +276.480 1054.080 m +276.480 1054.080 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +267.12 1034.89 Td +(GND) Tj +ET +2 J +0 j +72 M +0.72 w +0.63 0.00 0.00 RG +[] 0 d +272.88 1104.48 7.20 -14.40 re +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +276.480 1111.680 m +276.480 1104.480 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +276.480 1082.880 m +276.480 1090.080 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 0.502 rg +281.52 1095.27 Td +(R_ADC_T) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +281.52 1088.79 Td +(1M) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +190.080 1097.280 m +193.680 1100.880 l +204.480 1100.880 l +204.480 1093.680 l +193.680 1093.680 l +190.080 1097.280 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +190.080 1097.280 m +190.080 1097.280 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +205.93 1094.17 Td +(RBTN) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +85.680 1054.080 m +82.080 1050.480 l +71.280 1050.480 l +71.280 1057.680 l +82.080 1057.680 l +85.680 1054.080 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +85.680 1054.080 m +85.680 1054.080 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +52.02 1051.36 Td +(UBTN) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +89.280 1093.680 m +96.480 1093.680 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +89.280 1100.160 m +89.280 1087.200 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +87.840 1098.000 m +87.840 1089.360 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +86.400 1095.840 m +86.400 1091.520 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +84.960 1094.400 m +84.960 1092.960 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +96.480 1093.680 m +96.480 1093.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +65.88 1090.33 Td +(GND) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +85.680 1061.280 m +82.080 1057.680 l +71.280 1057.680 l +71.280 1064.880 l +82.080 1064.880 l +85.680 1061.280 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +85.680 1061.280 m +85.680 1061.280 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +46.92 1058.41 Td +(GPSEN) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +85.680 1075.680 m +82.080 1072.080 l +71.280 1072.080 l +71.280 1079.280 l +82.080 1079.280 l +85.680 1075.680 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +85.680 1075.680 m +85.680 1075.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +46.92 1072.81 Td +(GPSRX) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +85.680 1068.480 m +82.080 1064.880 l +71.280 1064.880 l +71.280 1072.080 l +82.080 1072.080 l +85.680 1068.480 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +85.680 1068.480 m +85.680 1068.480 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +47.65 1065.61 Td +(GPSTX) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +190.080 1039.680 m +193.680 1043.280 l +204.480 1043.280 l +204.480 1036.080 l +193.680 1036.080 l +190.080 1039.680 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +190.080 1039.680 m +190.080 1039.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +205.92 1036.96 Td +(IRQ) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +190.080 1032.480 m +193.680 1036.080 l +204.480 1036.080 l +204.480 1028.880 l +193.680 1028.880 l +190.080 1032.480 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +190.080 1032.480 m +190.080 1032.480 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +206.28 1029.76 Td +(NRST) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +236.880 1090.080 m +229.680 1090.080 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +236.880 1093.680 m +236.880 1086.480 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +229.680 1090.080 m +229.680 1090.080 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +237.60 1086.73 Td +(VCC) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +190.080 1054.080 m +193.680 1057.680 l +204.480 1057.680 l +204.480 1050.480 l +193.680 1050.480 l +190.080 1054.080 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +190.080 1054.080 m +190.080 1054.080 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +205.92 1051.36 Td +(CS) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +190.080 1061.280 m +193.680 1064.880 l +204.480 1064.880 l +204.480 1057.680 l +193.680 1057.680 l +190.080 1061.280 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +190.080 1061.280 m +190.080 1061.280 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +205.92 1058.56 Td +(MOSI) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +190.080 1068.480 m +193.680 1072.080 l +204.480 1072.080 l +204.480 1064.880 l +193.680 1064.880 l +190.080 1068.480 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +190.080 1068.480 m +190.080 1068.480 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +205.92 1065.76 Td +(MISO) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +190.080 1046.880 m +193.680 1050.480 l +204.480 1050.480 l +204.480 1043.280 l +193.680 1043.280 l +190.080 1046.880 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +190.080 1046.880 m +190.080 1046.880 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +205.80 1044.16 Td +(SCK) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +190.080 1075.680 m +193.680 1079.280 l +204.480 1079.280 l +204.480 1072.080 l +193.680 1072.080 l +190.080 1075.680 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +190.080 1075.680 m +190.080 1075.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +205.70 1072.96 Td +(BUSY) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +236.880 1104.480 m +229.680 1104.480 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +236.880 1098.000 m +236.880 1110.960 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +238.320 1100.160 m +238.320 1108.800 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +239.760 1102.320 m +239.760 1106.640 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +241.200 1103.760 m +241.200 1105.200 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +229.680 1104.480 m +229.680 1104.480 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +241.56 1101.13 Td +(GND) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +182.880 1111.680 m +186.480 1115.280 l +197.280 1115.280 l +197.280 1108.080 l +186.480 1108.080 l +182.880 1111.680 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +182.880 1111.680 m +182.880 1111.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +199.08 1108.74 Td +(BATT) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +85.680 1046.880 m +82.080 1043.280 l +71.280 1043.280 l +71.280 1050.480 l +82.080 1050.480 l +85.680 1046.880 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +85.680 1046.880 m +85.680 1046.880 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +57.11 1044.01 Td +(SCL) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +85.680 1039.680 m +82.080 1036.080 l +71.280 1036.080 l +71.280 1043.280 l +82.080 1043.280 l +85.680 1039.680 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +85.680 1039.680 m +85.680 1039.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +56.38 1036.81 Td +(SDA) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +276.480 1082.880 m +280.080 1086.480 l +290.880 1086.480 l +290.880 1079.280 l +280.080 1079.280 l +276.480 1082.880 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +276.480 1082.880 m +276.480 1082.880 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +292.82 1079.93 Td +(ADC) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +661.680 1111.680 m +658.080 1115.280 l +658.080 1126.080 l +665.280 1126.080 l +665.280 1115.280 l +661.680 1111.680 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +661.680 1111.680 m +661.680 1111.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +0.00 1.00 -1.00 0.00 663.03 1126.29 Tm +(BATT) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +661.680 1046.880 m +661.680 1054.080 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +655.200 1046.880 m +668.160 1046.880 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +657.360 1045.440 m +666.000 1045.440 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +659.520 1044.000 m +663.840 1044.000 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +660.960 1042.560 m +662.400 1042.560 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +661.680 1054.080 m +661.680 1054.080 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +652.32 1034.89 Td +(GND) Tj +ET +2 J +0 j +72 M +0.72 w +0.63 0.00 0.00 RG +[] 0 d +658.08 1104.48 7.20 -14.40 re +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +661.680 1111.680 m +661.680 1104.480 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +661.680 1082.880 m +661.680 1090.080 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 0.502 rg +666.72 1095.27 Td +(R_ADC_T1) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +666.72 1088.79 Td +(220k) Tj +ET +2 J +0 j +72 M +0.72 w +0.63 0.00 0.00 RG +[] 0 d +658.08 1075.68 7.20 -14.40 re +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +661.680 1082.880 m +661.680 1075.680 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +661.680 1054.080 m +661.680 1061.280 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 0.502 rg +666.72 1066.47 Td +(R_ADC_B1) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +666.72 1059.99 Td +(330k) Tj +ET +2 J +0 j +72 M +0.72 w +0.63 0.00 0.00 RG +[] 0 d +712.08 1104.48 7.20 -14.40 re +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +715.680 1111.680 m +715.680 1104.480 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +715.680 1082.880 m +715.680 1090.080 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 0.502 rg +720.72 1095.27 Td +(R_ADC_T2) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +720.72 1088.79 Td +(680k) Tj +ET +2 J +0 j +72 M +0.72 w +0.63 0.00 0.00 RG +[] 0 d +712.08 1075.68 7.20 -14.40 re +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +715.680 1082.880 m +715.680 1075.680 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +715.680 1054.080 m +715.680 1061.280 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 0.502 rg +720.72 1066.47 Td +(R_ADC_B2) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +720.72 1059.99 Td +(1M) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +715.680 1082.880 m +719.280 1086.480 l +730.080 1086.480 l +730.080 1079.280 l +719.280 1079.280 l +715.680 1082.880 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +715.680 1082.880 m +715.680 1082.880 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +732.02 1079.93 Td +(ADC) Tj +ET +7.20 w +BT +/F2 8.181818181818182 Tf +9.00 TL +0.000 g +640.08 1151.66 Td +(Alternative ADC resistors) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +715.680 1111.680 m +712.080 1115.280 l +712.080 1126.080 l +719.280 1126.080 l +719.280 1115.280 l +715.680 1111.680 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +715.680 1111.680 m +715.680 1111.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +0.00 1.00 -1.00 0.00 717.03 1126.29 Tm +(BATT) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +715.680 1046.880 m +715.680 1054.080 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +709.200 1046.880 m +722.160 1046.880 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +711.360 1045.440 m +720.000 1045.440 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +713.520 1044.000 m +717.840 1044.000 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +714.960 1042.560 m +716.400 1042.560 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +715.680 1054.080 m +715.680 1054.080 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +706.32 1034.89 Td +(GND) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +661.680 1082.880 m +665.280 1086.480 l +676.080 1086.480 l +676.080 1079.280 l +665.280 1079.280 l +661.680 1082.880 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +661.680 1082.880 m +661.680 1082.880 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +678.02 1079.93 Td +(ADC) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +92.880 1082.880 m +89.280 1079.280 l +78.480 1079.280 l +78.480 1086.480 l +89.280 1086.480 l +92.880 1082.880 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +92.880 1082.880 m +92.880 1082.880 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +58.86 1080.01 Td +(RXEN) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +377.280 1082.880 m +373.680 1079.280 l +362.880 1079.280 l +362.880 1086.480 l +373.680 1086.480 l +377.280 1082.880 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +377.280 1082.880 m +377.280 1082.880 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +343.26 1080.01 Td +(RXEN) Tj +ET +7.20 w +BT +/F2 8.181818181818182 Tf +9.00 TL +0.000 g +218.88 248.06 Td +(Bulk Capacitors) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +157.680 690.480 m +154.080 686.880 l +143.280 686.880 l +143.280 694.080 l +154.080 694.080 l +157.680 690.480 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +157.680 690.480 m +157.680 690.480 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +124.34 687.64 Td +(DIO2) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +481.680 730.080 m +485.280 733.680 l +496.080 733.680 l +496.080 726.480 l +485.280 726.480 l +481.680 730.080 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +481.680 730.080 m +481.680 730.080 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +497.53 727.03 Td +(DIO2) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +730.080 730.080 m +733.680 733.680 l +744.480 733.680 l +744.480 726.480 l +733.680 726.480 l +730.080 730.080 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +730.080 730.080 m +730.080 730.080 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +745.93 727.03 Td +(DIO2) Tj +ET +7.20 w +BT +/F2 8.181818181818182 Tf +9.00 TL +0.000 g +366.48 820.46 Td +(NB// Ant pin is not connected, ) Tj +T* (except on non-ipex version) Tj +ET +7.20 w +BT +/F2 8.181818181818182 Tf +9.00 TL +0.000 g +150.48 289.82 Td +(NB// Ant pin is not connected, ) Tj +T* (except on non-ipex version) Tj +ET +7.20 w +BT +/F2 8.181818181818182 Tf +9.00 TL +0.000 g +157.68 665.66 Td +(NB// Non-TCXO!) Tj +ET +7.20 w +BT +/F2 8.181818181818182 Tf +9.00 TL +0.000 g +114.48 809.66 Td +(NB// RA-01SH is Non-TCXO!) Tj +ET +7.20 w +BT +/F2 8.181818181818182 Tf +9.00 TL +0.000 g +632.88 161.66 Td +(NB// SX1276 - non-preferred!) Tj +ET +2 J +0 j +72 M +0.72 w +0.00 G +[] 0 d +35.28 476.64 777.60 -208.80 re +S +7.20 w +BT +/F2 8.181818181818182 Tf +9.00 TL +0.000 g +46.08 459.02 Td +(LR1121 Modules) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +388.080 578.880 m +384.480 575.280 l +373.680 575.280 l +373.680 582.480 l +384.480 582.480 l +388.080 578.880 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +388.080 578.880 m +388.080 578.880 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +354.75 576.23 Td +(DIO2) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +730.080 859.680 m +722.880 859.680 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +730.080 863.280 m +730.080 856.080 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +722.880 859.680 m +722.880 859.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +730.44 856.27 Td +(VCC) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +722.880 866.880 m +726.480 870.480 l +737.280 870.480 l +737.280 863.280 l +726.480 863.280 l +722.880 866.880 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +722.880 866.880 m +722.880 866.880 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +738.72 863.93 Td +(DIO2) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +629.280 852.480 m +625.680 848.880 l +614.880 848.880 l +614.880 856.080 l +625.680 856.080 l +629.280 852.480 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +629.280 852.480 m +629.280 852.480 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +595.95 849.54 Td +(DIO2) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +622.080 863.280 m +629.280 863.280 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +622.080 869.760 m +622.080 856.800 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +620.640 867.600 m +620.640 858.960 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +619.200 865.440 m +619.200 861.120 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +617.760 864.000 m +617.760 862.560 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +629.280 863.280 m +629.280 863.280 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +598.68 859.87 Td +(GND) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +730.080 884.880 m +722.880 884.880 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +730.080 878.400 m +730.080 891.360 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +731.520 880.560 m +731.520 889.200 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +732.960 882.720 m +732.960 887.040 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +734.400 884.160 m +734.400 885.600 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +722.880 884.880 m +722.880 884.880 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +734.76 881.47 Td +(GND) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +722.880 809.280 m +722.880 816.480 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +716.400 809.280 m +729.360 809.280 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +718.560 807.840 m +727.200 807.840 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +720.720 806.400 m +725.040 806.400 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +722.160 804.960 m +723.600 804.960 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +722.880 816.480 m +722.880 816.480 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +713.52 797.29 Td +(GND) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +629.280 845.280 m +625.680 841.680 l +614.880 841.680 l +614.880 848.880 l +625.680 848.880 l +629.280 845.280 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +629.280 845.280 m +629.280 845.280 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +600.16 842.12 Td +(IRQ) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +629.280 838.080 m +625.680 834.480 l +614.880 834.480 l +614.880 841.680 l +625.680 841.680 l +629.280 838.080 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +629.280 838.080 m +629.280 838.080 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +593.11 834.92 Td +(BUSY) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +629.280 830.880 m +625.680 834.480 l +614.880 834.480 l +614.880 827.280 l +625.680 827.280 l +629.280 830.880 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +629.280 830.880 m +629.280 830.880 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +593.65 828.45 Td +(NRST) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +622.080 902.880 m +629.280 902.880 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +622.080 899.280 m +622.080 906.480 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +629.280 902.880 m +629.280 902.880 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +602.58 899.53 Td +(VCC) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +629.280 895.680 m +625.680 892.080 l +614.880 892.080 l +614.880 899.280 l +625.680 899.280 l +629.280 895.680 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +629.280 895.680 m +629.280 895.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +604.35 892.81 Td +(CS) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +629.280 888.480 m +625.680 884.880 l +614.880 884.880 l +614.880 892.080 l +625.680 892.080 l +629.280 888.480 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +629.280 888.480 m +629.280 888.480 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +599.98 885.61 Td +(SCK) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +629.280 874.080 m +625.680 877.680 l +614.880 877.680 l +614.880 870.480 l +625.680 870.480 l +629.280 874.080 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +629.280 874.080 m +629.280 874.080 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +596.71 870.64 Td +(MISO) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +629.280 881.280 m +625.680 884.880 l +614.880 884.880 l +614.880 877.680 l +625.680 877.680 l +629.280 881.280 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +629.280 881.280 m +629.280 881.280 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +596.71 877.84 Td +(MOSI) Tj +ET +2 J +0 j +72 M +0.72 w +0.63 0.00 0.00 RG +[] 0 d +636.48 910.08 79.20 -86.40 re +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +639.14 899.93 Td +(VDD) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +632.48 903.53 Td +(1) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +629.280 902.880 m +636.480 902.880 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +639.14 892.73 Td +(CS) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +632.48 896.33 Td +(2) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +629.280 895.680 m +636.480 895.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +639.14 885.53 Td +(SCK) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +632.48 889.13 Td +(3) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +629.280 888.480 m +636.480 888.480 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +639.14 878.33 Td +(MOSI) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +632.48 881.93 Td +(4) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +629.280 881.280 m +636.480 881.280 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +639.14 871.13 Td +(MISO) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +632.48 874.73 Td +(5) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +629.280 874.080 m +636.480 874.080 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +639.14 860.33 Td +(GND) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +632.48 863.93 Td +(6) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +629.280 863.280 m +636.480 863.280 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +638.64 850.25 Td +(DIO2) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +633.20 853.13 Td +(7) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +629.280 852.480 m +636.480 852.480 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +638.64 843.05 Td +(DIO1) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +633.20 845.93 Td +(8) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +629.280 845.280 m +636.480 845.280 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +638.64 835.85 Td +(Busy) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +633.20 838.73 Td +(9) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +629.280 838.080 m +636.480 838.080 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +638.64 828.65 Td +(NRst) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +629.56 831.53 Td +(10) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +629.280 830.880 m +636.480 830.880 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +703.70 900.65 Td +(Ant) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +715.32 903.53 Td +(11) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +722.880 902.880 m +715.680 902.880 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +698.98 889.85 Td +(GND) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +715.32 892.73 Td +(12) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +722.880 892.080 m +715.680 892.080 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +698.47 881.93 Td +(GND) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +716.04 885.53 Td +(13) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +722.880 884.880 m +715.680 884.880 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +698.47 874.73 Td +(GND) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +716.04 878.33 Td +(14) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +722.880 877.680 m +715.680 877.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +679.19 863.93 Td +(TX_RX_EN) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +716.04 867.53 Td +(15) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +722.880 866.880 m +715.680 866.880 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +685.01 856.73 Td +(VDD_SW) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +716.04 860.33 Td +(16) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +722.880 859.680 m +715.680 859.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +698.47 845.93 Td +(GND) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +716.04 849.53 Td +(17) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +722.880 848.880 m +715.680 848.880 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +698.47 838.73 Td +(GND) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +716.04 842.33 Td +(18) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +722.880 841.680 m +715.680 841.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +698.47 832.97 Td +(GND) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +716.04 835.13 Td +(19) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +722.880 834.480 m +715.680 834.480 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +698.47 825.77 Td +(GND) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +716.04 827.93 Td +(20) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +722.880 827.280 m +715.680 827.280 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +0.00 1.00 -1.00 0.00 665.41 824.97 Tm +(GND) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +0.00 1.00 -1.00 0.00 663.25 814.66 Tm +(21) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +665.280 816.480 m +665.280 823.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +0.00 1.00 -1.00 0.00 672.61 824.97 Tm +(GND) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +0.00 1.00 -1.00 0.00 670.45 814.66 Tm +(22) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +672.480 816.480 m +672.480 823.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +0.00 1.00 -1.00 0.00 679.81 824.97 Tm +(GND) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +0.00 1.00 -1.00 0.00 677.65 814.66 Tm +(23) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +679.680 816.480 m +679.680 823.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +0.00 1.00 -1.00 0.00 687.01 824.97 Tm +(GND) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +0.00 1.00 -1.00 0.00 684.85 814.66 Tm +(24) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +686.880 816.480 m +686.880 823.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 0.502 rg +672.16 919.36 Td +(ELECROW_LR1262) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +672.16 913.29 Td +(LR1262 Transceiver) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +751.680 917.280 m +751.680 924.480 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +745.200 917.280 m +758.160 917.280 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +747.360 915.840 m +756.000 915.840 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +749.520 914.400 m +753.840 914.400 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +750.960 912.960 m +752.400 912.960 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +751.680 924.480 m +751.680 924.480 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +742.32 904.57 Td +(GND) Tj +ET +2 J +0 j +72 M +0.72 w +0.63 0.00 0.00 RG +[] 0 d +758.88 931.68 14.40 -14.40 re +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +0.00 1.00 -1.00 0.00 764.05 908.66 Tm +(1) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +766.080 902.880 m +766.080 917.280 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +755.24 919.25 Td +(2) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +751.680 924.480 m +758.880 924.480 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +773.28 919.25 Td +(3) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +780.480 924.480 m +773.280 924.480 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +0.00 1.00 -1.00 0.00 764.05 930.30 Tm +(4) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +766.080 938.880 m +766.080 931.680 l +S +1 J +1 j +0.72 w +0.20 0.80 0.20 RG +[] 0 d +763.200 941.760 m +768.960 936.000 l +763.200 936.000 m +768.960 941.760 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +767.52 924.48 m 767.52 925.28 766.88 925.92 766.08 925.92 c +765.28 925.92 764.64 925.28 764.64 924.48 c +764.64 923.68 765.28 923.04 766.08 923.04 c +766.88 923.04 767.52 923.68 767.52 924.48 c +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +766.080 923.040 m +766.080 917.280 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 0.502 rg +750.92 948.68 Td +(U4) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +750.92 942.13 Td +(AMC-U_FL) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +780.480 917.280 m +780.480 924.480 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +774.000 917.280 m +786.960 917.280 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +776.160 915.840 m +784.800 915.840 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +778.320 914.400 m +782.640 914.400 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +779.760 912.960 m +781.200 912.960 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +780.480 924.480 m +780.480 924.480 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +771.12 904.57 Td +(GND) Tj +ET +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +110.880 602.640 m +154.080 602.640 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +110.880 602.640 m +154.080 602.640 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +654.480 233.280 m +654.480 226.080 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +654.480 233.280 m +654.480 226.080 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +370.080 895.680 m +355.680 895.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +370.080 895.680 m +355.680 895.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +467.280 1090.080 m +514.080 1090.080 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +467.280 1090.080 m +514.080 1090.080 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +467.280 1104.480 m +514.080 1104.480 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +467.280 1104.480 m +514.080 1104.480 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +467.280 1068.480 m +474.480 1068.480 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +467.280 1068.480 m +474.480 1068.480 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +467.280 1061.280 m +474.480 1061.280 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +467.280 1061.280 m +474.480 1061.280 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +467.280 1046.880 m +474.480 1046.880 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +467.280 1046.880 m +474.480 1046.880 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +467.280 1054.080 m +474.480 1054.080 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +467.280 1054.080 m +474.480 1054.080 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +467.280 1039.680 m +474.480 1039.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +467.280 1039.680 m +474.480 1039.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +467.280 1075.680 m +474.480 1075.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +467.280 1075.680 m +474.480 1075.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +467.280 1032.480 m +474.480 1032.480 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +467.280 1032.480 m +474.480 1032.480 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +467.280 1097.280 m +474.480 1097.280 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +467.280 1097.280 m +474.480 1097.280 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +467.280 1082.880 m +560.880 1082.880 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +467.280 1082.880 m +560.880 1082.880 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +380.880 1039.680 m +370.080 1039.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +380.880 1039.680 m +370.080 1039.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +380.880 1046.880 m +370.080 1046.880 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +380.880 1046.880 m +370.080 1046.880 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +380.880 1054.080 m +370.080 1054.080 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +380.880 1054.080 m +370.080 1054.080 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +380.880 1061.280 m +370.080 1061.280 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +380.880 1061.280 m +370.080 1061.280 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +380.880 1068.480 m +370.080 1068.480 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +380.880 1068.480 m +370.080 1068.480 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +380.880 1075.680 m +370.080 1075.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +380.880 1075.680 m +370.080 1075.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +380.880 1093.680 m +380.880 1090.080 l +S +380.880 1097.280 m +380.880 1093.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +380.880 1093.680 m +380.880 1090.080 l +S +380.880 1097.280 m +380.880 1093.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +730.080 748.080 m +730.080 751.680 l +S +730.080 748.080 m +730.080 744.480 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +730.080 748.080 m +730.080 751.680 l +S +730.080 748.080 m +730.080 744.480 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +481.680 758.880 m +481.680 751.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +481.680 758.880 m +481.680 751.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +157.680 712.080 m +100.080 712.080 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +157.680 712.080 m +100.080 712.080 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +730.080 679.680 m +730.080 674.640 l +S +730.080 715.680 m +730.080 708.480 l +S +730.080 694.080 m +730.080 708.480 l +S +730.080 686.880 m +730.080 694.080 l +S +730.080 679.680 m +730.080 686.880 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +730.080 679.680 m +730.080 674.640 l +S +730.080 715.680 m +730.080 708.480 l +S +730.080 694.080 m +730.080 708.480 l +S +730.080 686.880 m +730.080 694.080 l +S +730.080 679.680 m +730.080 686.880 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +481.680 679.680 m +481.680 686.880 l +S +481.680 694.080 m +481.680 686.880 l +S +481.680 694.080 m +481.680 708.480 l +S +481.680 715.680 m +481.680 708.480 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +481.680 679.680 m +481.680 686.880 l +S +481.680 694.080 m +481.680 686.880 l +S +481.680 694.080 m +481.680 708.480 l +S +481.680 715.680 m +481.680 708.480 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +132.480 884.880 m +114.480 884.880 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +132.480 884.880 m +114.480 884.880 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +132.480 877.680 m +125.280 877.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +132.480 877.680 m +125.280 877.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +197.280 892.080 m +218.880 892.080 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +197.280 892.080 m +218.880 892.080 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +730.080 589.680 m +730.080 593.280 l +S +730.080 593.280 m +730.080 596.880 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +730.080 589.680 m +730.080 593.280 l +S +730.080 593.280 m +730.080 596.880 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +730.080 524.880 m +730.080 532.080 l +S +730.080 532.080 m +730.080 539.280 l +S +730.080 539.280 m +730.080 553.680 l +S +730.080 560.880 m +730.080 553.680 l +S +730.080 524.880 m +730.080 521.280 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +730.080 524.880 m +730.080 532.080 l +S +730.080 532.080 m +730.080 539.280 l +S +730.080 539.280 m +730.080 553.680 l +S +730.080 560.880 m +730.080 553.680 l +S +730.080 524.880 m +730.080 521.280 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +215.280 303.840 m +215.280 332.640 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +215.280 303.840 m +215.280 332.640 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +193.680 303.840 m +200.880 303.840 l +S +200.880 303.840 m +200.880 332.640 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +193.680 303.840 m +200.880 303.840 l +S +200.880 303.840 m +200.880 332.640 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +164.880 401.040 m +161.280 401.040 l +S +161.280 401.040 m +161.280 408.240 l +S +161.280 408.240 m +164.880 408.240 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +164.880 401.040 m +161.280 401.040 l +S +161.280 401.040 m +161.280 408.240 l +S +161.280 408.240 m +164.880 408.240 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +161.280 386.640 m +161.280 393.840 l +S +161.280 393.840 m +164.880 393.840 l +S +161.280 379.440 m +161.280 386.640 l +S +164.880 386.640 m +161.280 386.640 l +S +164.880 372.240 m +161.280 372.240 l +S +161.280 372.240 m +161.280 379.440 l +S +164.880 379.440 m +161.280 379.440 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +161.280 386.640 m +161.280 393.840 l +S +161.280 393.840 m +164.880 393.840 l +S +161.280 379.440 m +161.280 386.640 l +S +164.880 386.640 m +161.280 386.640 l +S +164.880 372.240 m +161.280 372.240 l +S +161.280 372.240 m +161.280 379.440 l +S +164.880 379.440 m +161.280 379.440 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +96.480 1090.080 m +96.480 1093.680 l +S +96.480 1093.680 m +96.480 1097.280 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +96.480 1090.080 m +96.480 1093.680 l +S +96.480 1093.680 m +96.480 1097.280 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +85.680 1075.680 m +96.480 1075.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +85.680 1075.680 m +96.480 1075.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +85.680 1068.480 m +96.480 1068.480 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +85.680 1068.480 m +96.480 1068.480 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +85.680 1061.280 m +96.480 1061.280 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +85.680 1061.280 m +96.480 1061.280 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +85.680 1054.080 m +96.480 1054.080 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +85.680 1054.080 m +96.480 1054.080 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +85.680 1046.880 m +96.480 1046.880 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +85.680 1046.880 m +96.480 1046.880 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +85.680 1039.680 m +96.480 1039.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +85.680 1039.680 m +96.480 1039.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +276.480 1082.880 m +182.880 1082.880 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +276.480 1082.880 m +182.880 1082.880 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +190.080 1097.280 m +182.880 1097.280 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +190.080 1097.280 m +182.880 1097.280 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +190.080 1032.480 m +182.880 1032.480 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +190.080 1032.480 m +182.880 1032.480 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +190.080 1075.680 m +182.880 1075.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +190.080 1075.680 m +182.880 1075.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +190.080 1039.680 m +182.880 1039.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +190.080 1039.680 m +182.880 1039.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +190.080 1054.080 m +182.880 1054.080 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +190.080 1054.080 m +182.880 1054.080 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +190.080 1046.880 m +182.880 1046.880 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +190.080 1046.880 m +182.880 1046.880 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +190.080 1061.280 m +182.880 1061.280 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +190.080 1061.280 m +182.880 1061.280 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +190.080 1068.480 m +182.880 1068.480 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +190.080 1068.480 m +182.880 1068.480 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +229.680 1104.480 m +182.880 1104.480 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +229.680 1104.480 m +182.880 1104.480 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +229.680 1090.080 m +182.880 1090.080 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +229.680 1090.080 m +182.880 1090.080 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +92.880 1082.880 m +96.480 1082.880 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +92.880 1082.880 m +96.480 1082.880 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +380.880 1082.880 m +377.280 1082.880 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +380.880 1082.880 m +377.280 1082.880 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +722.880 841.680 m +722.880 848.880 l +S +722.880 834.480 m +722.880 841.680 l +S +722.880 827.280 m +722.880 834.480 l +S +722.880 827.280 m +722.880 816.480 l +S +722.880 816.480 m +686.880 816.480 l +S +686.880 816.480 m +679.680 816.480 l +S +679.680 816.480 m +672.480 816.480 l +S +672.480 816.480 m +665.280 816.480 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +722.880 841.680 m +722.880 848.880 l +S +722.880 834.480 m +722.880 841.680 l +S +722.880 827.280 m +722.880 834.480 l +S +722.880 827.280 m +722.880 816.480 l +S +722.880 816.480 m +686.880 816.480 l +S +686.880 816.480 m +679.680 816.480 l +S +679.680 816.480 m +672.480 816.480 l +S +672.480 816.480 m +665.280 816.480 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +722.880 884.880 m +722.880 892.080 l +S +722.880 877.680 m +722.880 884.880 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +722.880 884.880 m +722.880 892.080 l +S +722.880 877.680 m +722.880 884.880 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +766.080 902.880 m +722.880 902.880 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +766.080 902.880 m +722.880 902.880 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +229.680 329.040 m +229.680 332.640 l +S +193.680 332.640 m +193.680 329.040 l +S +193.680 329.040 m +208.080 329.040 l +S +208.080 332.640 m +208.080 329.040 l +S +208.080 329.040 m +229.680 329.040 l +S +240.480 329.040 m +229.680 329.040 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +229.680 329.040 m +229.680 332.640 l +S +193.680 332.640 m +193.680 329.040 l +S +193.680 329.040 m +208.080 329.040 l +S +208.080 332.640 m +208.080 329.040 l +S +208.080 329.040 m +229.680 329.040 l +S +240.480 329.040 m +229.680 329.040 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +467.280 1111.680 m +467.280 1111.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +467.280 1111.680 m +467.280 1111.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +380.880 1104.480 m +380.880 1104.480 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +380.880 1104.480 m +380.880 1104.480 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +380.880 1111.680 m +380.880 1111.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +380.880 1111.680 m +380.880 1111.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +380.880 1032.480 m +380.880 1032.480 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +380.880 1032.480 m +380.880 1032.480 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +402.480 996.480 m +402.480 996.480 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +402.480 996.480 m +402.480 996.480 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +409.680 996.480 m +409.680 996.480 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +409.680 996.480 m +409.680 996.480 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +416.880 996.480 m +416.880 996.480 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +416.880 996.480 m +416.880 996.480 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +182.880 1111.680 m +182.880 1111.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +182.880 1111.680 m +182.880 1111.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +96.480 1104.480 m +96.480 1104.480 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +96.480 1104.480 m +96.480 1104.480 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +96.480 1111.680 m +96.480 1111.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +96.480 1111.680 m +96.480 1111.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +96.480 1032.480 m +96.480 1032.480 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +96.480 1032.480 m +96.480 1032.480 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +118.080 161.280 m +118.080 161.280 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +118.080 161.280 m +118.080 161.280 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +118.080 211.680 m +118.080 211.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +118.080 211.680 m +118.080 211.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +74.880 161.280 m +74.880 161.280 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +74.880 161.280 m +74.880 161.280 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +74.880 211.680 m +74.880 211.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +74.880 211.680 m +74.880 211.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +125.280 624.240 m +125.280 624.240 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +125.280 624.240 m +125.280 624.240 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +96.480 624.240 m +96.480 624.240 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +96.480 624.240 m +96.480 624.240 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +154.080 573.840 m +154.080 573.840 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +154.080 573.840 m +154.080 573.840 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +154.080 595.440 m +154.080 595.440 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +154.080 595.440 m +154.080 595.440 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +226.080 602.640 m +226.080 602.640 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +226.080 602.640 m +226.080 602.640 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +226.080 552.240 m +226.080 552.240 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +226.080 552.240 m +226.080 552.240 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +226.080 588.240 m +226.080 588.240 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +226.080 588.240 m +226.080 588.240 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +154.080 559.440 m +154.080 559.440 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +154.080 559.440 m +154.080 559.440 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +154.080 552.240 m +154.080 552.240 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +154.080 552.240 m +154.080 552.240 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +226.080 581.040 m +226.080 581.040 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +226.080 581.040 m +226.080 581.040 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +226.080 573.840 m +226.080 573.840 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +226.080 573.840 m +226.080 573.840 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +226.080 566.640 m +226.080 566.640 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +226.080 566.640 m +226.080 566.640 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +226.080 559.440 m +226.080 559.440 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +226.080 559.440 m +226.080 559.440 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +654.480 218.880 m +654.480 218.880 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +654.480 218.880 m +654.480 218.880 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +719.280 233.280 m +719.280 233.280 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +719.280 233.280 m +719.280 233.280 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +719.280 182.880 m +719.280 182.880 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +719.280 182.880 m +719.280 182.880 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +654.480 197.280 m +654.480 197.280 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +654.480 197.280 m +654.480 197.280 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +654.480 211.680 m +654.480 211.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +654.480 211.680 m +654.480 211.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +719.280 211.680 m +719.280 211.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +719.280 211.680 m +719.280 211.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +719.280 218.880 m +719.280 218.880 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +719.280 218.880 m +719.280 218.880 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +719.280 204.480 m +719.280 204.480 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +719.280 204.480 m +719.280 204.480 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +719.280 226.080 m +719.280 226.080 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +719.280 226.080 m +719.280 226.080 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +654.480 204.480 m +654.480 204.480 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +654.480 204.480 m +654.480 204.480 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +269.280 215.280 m +269.280 215.280 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +269.280 215.280 m +269.280 215.280 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +233.280 186.480 m +233.280 186.480 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +233.280 186.480 m +233.280 186.480 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +233.280 215.280 m +233.280 215.280 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +233.280 215.280 m +233.280 215.280 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +269.280 186.480 m +269.280 186.480 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +269.280 186.480 m +269.280 186.480 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +560.880 1111.680 m +560.880 1111.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +560.880 1111.680 m +560.880 1111.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +560.880 1054.080 m +560.880 1054.080 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +560.880 1054.080 m +560.880 1054.080 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +730.080 434.880 m +730.080 434.880 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +730.080 434.880 m +730.080 434.880 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +629.280 362.880 m +629.280 362.880 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +629.280 362.880 m +629.280 362.880 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +629.280 377.280 m +629.280 377.280 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +629.280 377.280 m +629.280 377.280 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +629.280 391.680 m +629.280 391.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +629.280 391.680 m +629.280 391.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +629.280 442.080 m +629.280 442.080 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +629.280 442.080 m +629.280 442.080 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +730.080 442.080 m +730.080 442.080 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +730.080 442.080 m +730.080 442.080 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +730.080 391.680 m +730.080 391.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +730.080 391.680 m +730.080 391.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +730.080 377.280 m +730.080 377.280 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +730.080 377.280 m +730.080 377.280 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +730.080 362.880 m +730.080 362.880 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +730.080 362.880 m +730.080 362.880 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +629.280 398.880 m +629.280 398.880 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +629.280 398.880 m +629.280 398.880 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +629.280 406.080 m +629.280 406.080 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +629.280 406.080 m +629.280 406.080 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +629.280 413.280 m +629.280 413.280 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +629.280 413.280 m +629.280 413.280 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +629.280 420.480 m +629.280 420.480 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +629.280 420.480 m +629.280 420.480 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +629.280 427.680 m +629.280 427.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +629.280 427.680 m +629.280 427.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +730.080 413.280 m +730.080 413.280 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +730.080 413.280 m +730.080 413.280 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +730.080 406.080 m +730.080 406.080 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +730.080 406.080 m +730.080 406.080 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +114.480 733.680 m +114.480 733.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +114.480 733.680 m +114.480 733.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +481.680 744.480 m +481.680 744.480 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +481.680 744.480 m +481.680 744.480 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +157.680 748.080 m +157.680 748.080 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +157.680 748.080 m +157.680 748.080 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +629.280 737.280 m +629.280 737.280 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +629.280 737.280 m +629.280 737.280 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +629.280 751.680 m +629.280 751.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +629.280 751.680 m +629.280 751.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +730.080 737.280 m +730.080 737.280 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +730.080 737.280 m +730.080 737.280 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +629.280 708.480 m +629.280 708.480 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +629.280 708.480 m +629.280 708.480 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +629.280 722.880 m +629.280 722.880 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +629.280 722.880 m +629.280 722.880 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +629.280 730.080 m +629.280 730.080 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +629.280 730.080 m +629.280 730.080 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +629.280 715.680 m +629.280 715.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +629.280 715.680 m +629.280 715.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +730.080 722.880 m +730.080 722.880 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +730.080 722.880 m +730.080 722.880 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +629.280 744.480 m +629.280 744.480 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +629.280 744.480 m +629.280 744.480 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +629.280 694.080 m +629.280 694.080 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +629.280 694.080 m +629.280 694.080 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +629.280 679.680 m +629.280 679.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +629.280 679.680 m +629.280 679.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +730.080 758.880 m +730.080 758.880 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +730.080 758.880 m +730.080 758.880 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +629.280 758.880 m +629.280 758.880 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +629.280 758.880 m +629.280 758.880 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +157.680 740.880 m +157.680 740.880 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +157.680 740.880 m +157.680 740.880 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +157.680 704.880 m +157.680 704.880 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +157.680 704.880 m +157.680 704.880 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +222.480 719.280 m +222.480 719.280 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +222.480 719.280 m +222.480 719.280 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +380.880 758.880 m +380.880 758.880 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +380.880 758.880 m +380.880 758.880 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +380.880 679.680 m +380.880 679.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +380.880 679.680 m +380.880 679.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +380.880 694.080 m +380.880 694.080 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +380.880 694.080 m +380.880 694.080 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +380.880 744.480 m +380.880 744.480 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +380.880 744.480 m +380.880 744.480 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +481.680 722.880 m +481.680 722.880 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +481.680 722.880 m +481.680 722.880 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +380.880 715.680 m +380.880 715.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +380.880 715.680 m +380.880 715.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +380.880 730.080 m +380.880 730.080 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +380.880 730.080 m +380.880 730.080 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +380.880 722.880 m +380.880 722.880 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +380.880 722.880 m +380.880 722.880 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +380.880 708.480 m +380.880 708.480 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +380.880 708.480 m +380.880 708.480 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +481.680 737.280 m +481.680 737.280 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +481.680 737.280 m +481.680 737.280 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +380.880 751.680 m +380.880 751.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +380.880 751.680 m +380.880 751.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +380.880 737.280 m +380.880 737.280 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +380.880 737.280 m +380.880 737.280 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +222.480 733.680 m +222.480 733.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +222.480 733.680 m +222.480 733.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +157.680 733.680 m +157.680 733.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +157.680 733.680 m +157.680 733.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +222.480 748.080 m +222.480 748.080 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +222.480 748.080 m +222.480 748.080 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +222.480 740.880 m +222.480 740.880 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +222.480 740.880 m +222.480 740.880 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +222.480 704.880 m +222.480 704.880 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +222.480 704.880 m +222.480 704.880 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +222.480 697.680 m +222.480 697.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +222.480 697.680 m +222.480 697.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +222.480 690.480 m +222.480 690.480 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +222.480 690.480 m +222.480 690.480 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +222.480 712.080 m +222.480 712.080 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +222.480 712.080 m +222.480 712.080 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +157.680 683.280 m +157.680 683.280 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +157.680 683.280 m +157.680 683.280 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +222.480 683.280 m +222.480 683.280 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +222.480 683.280 m +222.480 683.280 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +157.680 690.480 m +157.680 690.480 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +157.680 690.480 m +157.680 690.480 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +730.080 730.080 m +730.080 730.080 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +730.080 730.080 m +730.080 730.080 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +481.680 730.080 m +481.680 730.080 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +481.680 730.080 m +481.680 730.080 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +132.480 870.480 m +132.480 870.480 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +132.480 870.480 m +132.480 870.480 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +132.480 856.080 m +132.480 856.080 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +132.480 856.080 m +132.480 856.080 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +197.280 841.680 m +197.280 841.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +197.280 841.680 m +197.280 841.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +197.280 848.880 m +197.280 848.880 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +197.280 848.880 m +197.280 848.880 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +197.280 863.280 m +197.280 863.280 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +197.280 863.280 m +197.280 863.280 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +197.280 870.480 m +197.280 870.480 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +197.280 870.480 m +197.280 870.480 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +197.280 877.680 m +197.280 877.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +197.280 877.680 m +197.280 877.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +197.280 884.880 m +197.280 884.880 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +197.280 884.880 m +197.280 884.880 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +370.080 888.480 m +370.080 888.480 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +370.080 888.480 m +370.080 888.480 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +370.080 881.280 m +370.080 881.280 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +370.080 881.280 m +370.080 881.280 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +370.080 874.080 m +370.080 874.080 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +370.080 874.080 m +370.080 874.080 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +370.080 866.880 m +370.080 866.880 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +370.080 866.880 m +370.080 866.880 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +370.080 859.680 m +370.080 859.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +370.080 859.680 m +370.080 859.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +370.080 852.480 m +370.080 852.480 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +370.080 852.480 m +370.080 852.480 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +370.080 845.280 m +370.080 845.280 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +370.080 845.280 m +370.080 845.280 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +478.080 874.080 m +478.080 874.080 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +478.080 874.080 m +478.080 874.080 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +478.080 881.280 m +478.080 881.280 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +478.080 881.280 m +478.080 881.280 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +478.080 888.480 m +478.080 888.480 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +478.080 888.480 m +478.080 888.480 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +85.680 733.680 m +85.680 733.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +85.680 733.680 m +85.680 733.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +730.080 568.080 m +730.080 568.080 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +730.080 568.080 m +730.080 568.080 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +730.080 575.280 m +730.080 575.280 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +730.080 575.280 m +730.080 575.280 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +629.280 582.480 m +629.280 582.480 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +629.280 582.480 m +629.280 582.480 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +629.280 596.880 m +629.280 596.880 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +629.280 596.880 m +629.280 596.880 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +730.080 582.480 m +730.080 582.480 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +730.080 582.480 m +730.080 582.480 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +629.280 553.680 m +629.280 553.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +629.280 553.680 m +629.280 553.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +629.280 568.080 m +629.280 568.080 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +629.280 568.080 m +629.280 568.080 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +629.280 575.280 m +629.280 575.280 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +629.280 575.280 m +629.280 575.280 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +629.280 560.880 m +629.280 560.880 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +629.280 560.880 m +629.280 560.880 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +629.280 589.680 m +629.280 589.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +629.280 589.680 m +629.280 589.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +629.280 539.280 m +629.280 539.280 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +629.280 539.280 m +629.280 539.280 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +629.280 524.880 m +629.280 524.880 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +629.280 524.880 m +629.280 524.880 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +730.080 604.080 m +730.080 604.080 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +730.080 604.080 m +730.080 604.080 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +629.280 604.080 m +629.280 604.080 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +629.280 604.080 m +629.280 604.080 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +377.280 431.280 m +377.280 431.280 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +377.280 431.280 m +377.280 431.280 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +377.280 424.080 m +377.280 424.080 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +377.280 424.080 m +377.280 424.080 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +377.280 395.280 m +377.280 395.280 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +377.280 395.280 m +377.280 395.280 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +377.280 388.080 m +377.280 388.080 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +377.280 388.080 m +377.280 388.080 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +460.080 388.080 m +460.080 388.080 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +460.080 388.080 m +460.080 388.080 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +460.080 395.280 m +460.080 395.280 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +460.080 395.280 m +460.080 395.280 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +460.080 402.480 m +460.080 402.480 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +460.080 402.480 m +460.080 402.480 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +460.080 409.680 m +460.080 409.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +460.080 409.680 m +460.080 409.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +460.080 416.880 m +460.080 416.880 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +460.080 416.880 m +460.080 416.880 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +460.080 424.080 m +460.080 424.080 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +460.080 424.080 m +460.080 424.080 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +460.080 431.280 m +460.080 431.280 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +460.080 431.280 m +460.080 431.280 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +388.080 557.280 m +388.080 557.280 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +388.080 557.280 m +388.080 557.280 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +388.080 586.080 m +388.080 586.080 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +388.080 586.080 m +388.080 586.080 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +449.280 593.280 m +449.280 593.280 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +449.280 593.280 m +449.280 593.280 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +388.080 600.480 m +388.080 600.480 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +388.080 600.480 m +388.080 600.480 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +388.080 593.280 m +388.080 593.280 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +388.080 593.280 m +388.080 593.280 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +449.280 571.680 m +449.280 571.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +449.280 571.680 m +449.280 571.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +449.280 564.480 m +449.280 564.480 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +449.280 564.480 m +449.280 564.480 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +449.280 578.880 m +449.280 578.880 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +449.280 578.880 m +449.280 578.880 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +449.280 586.080 m +449.280 586.080 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +449.280 586.080 m +449.280 586.080 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +388.080 550.080 m +388.080 550.080 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +388.080 550.080 m +388.080 550.080 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +449.280 557.280 m +449.280 557.280 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +449.280 557.280 m +449.280 557.280 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +449.280 550.080 m +449.280 550.080 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +449.280 550.080 m +449.280 550.080 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +388.080 564.480 m +388.080 564.480 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +388.080 564.480 m +388.080 564.480 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +449.280 600.480 m +449.280 600.480 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +449.280 600.480 m +449.280 600.480 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +388.080 571.680 m +388.080 571.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +388.080 571.680 m +388.080 571.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +388.080 578.880 m +388.080 578.880 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +388.080 578.880 m +388.080 578.880 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +258.480 440.640 m +258.480 440.640 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +258.480 440.640 m +258.480 440.640 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +258.480 426.240 m +258.480 426.240 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +258.480 426.240 m +258.480 426.240 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +258.480 408.240 m +258.480 408.240 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +258.480 408.240 m +258.480 408.240 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +258.480 401.040 m +258.480 401.040 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +258.480 401.040 m +258.480 401.040 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +258.480 393.840 m +258.480 393.840 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +258.480 393.840 m +258.480 393.840 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +258.480 386.640 m +258.480 386.640 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +258.480 386.640 m +258.480 386.640 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +258.480 379.440 m +258.480 379.440 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +258.480 379.440 m +258.480 379.440 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +258.480 372.240 m +258.480 372.240 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +258.480 372.240 m +258.480 372.240 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +164.880 426.240 m +164.880 426.240 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +164.880 426.240 m +164.880 426.240 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +164.880 440.640 m +164.880 440.640 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +164.880 440.640 m +164.880 440.640 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +276.480 1054.080 m +276.480 1054.080 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +276.480 1054.080 m +276.480 1054.080 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +276.480 1111.680 m +276.480 1111.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +276.480 1111.680 m +276.480 1111.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +661.680 1111.680 m +661.680 1111.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +661.680 1111.680 m +661.680 1111.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +661.680 1054.080 m +661.680 1054.080 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +661.680 1054.080 m +661.680 1054.080 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +661.680 1082.880 m +661.680 1082.880 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +661.680 1082.880 m +661.680 1082.880 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +715.680 1111.680 m +715.680 1111.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +715.680 1111.680 m +715.680 1111.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +715.680 1082.880 m +715.680 1082.880 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +715.680 1082.880 m +715.680 1082.880 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +715.680 1054.080 m +715.680 1054.080 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +715.680 1054.080 m +715.680 1054.080 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +722.880 859.680 m +722.880 859.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +722.880 859.680 m +722.880 859.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +722.880 866.880 m +722.880 866.880 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +722.880 866.880 m +722.880 866.880 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +629.280 852.480 m +629.280 852.480 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +629.280 852.480 m +629.280 852.480 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +629.280 863.280 m +629.280 863.280 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +629.280 863.280 m +629.280 863.280 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +629.280 845.280 m +629.280 845.280 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +629.280 845.280 m +629.280 845.280 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +629.280 838.080 m +629.280 838.080 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +629.280 838.080 m +629.280 838.080 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +629.280 830.880 m +629.280 830.880 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +629.280 830.880 m +629.280 830.880 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +629.280 902.880 m +629.280 902.880 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +629.280 902.880 m +629.280 902.880 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +629.280 895.680 m +629.280 895.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +629.280 895.680 m +629.280 895.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +629.280 888.480 m +629.280 888.480 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +629.280 888.480 m +629.280 888.480 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +629.280 874.080 m +629.280 874.080 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +629.280 874.080 m +629.280 874.080 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +629.280 881.280 m +629.280 881.280 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +629.280 881.280 m +629.280 881.280 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +751.680 924.480 m +751.680 924.480 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +751.680 924.480 m +751.680 924.480 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +780.480 924.480 m +780.480 924.480 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +780.480 924.480 m +780.480 924.480 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +424.080 226.080 m +420.480 222.480 l +409.680 222.480 l +409.680 229.680 l +420.480 229.680 l +424.080 226.080 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +424.080 226.080 m +424.080 226.080 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +365.70 223.79 Td +(E_INK_BUSY) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +424.080 218.880 m +420.480 215.280 l +409.680 215.280 l +409.680 222.480 l +420.480 222.480 l +424.080 218.880 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +424.080 218.880 m +424.080 218.880 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +366.48 216.59 Td +(E_INK_NRST) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +424.080 211.680 m +420.480 208.080 l +409.680 208.080 l +409.680 215.280 l +420.480 215.280 l +424.080 211.680 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +424.080 211.680 m +424.080 211.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +370.08 209.39 Td +(E_INK_D/C) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +424.080 204.480 m +420.480 200.880 l +409.680 200.880 l +409.680 208.080 l +420.480 208.080 l +424.080 204.480 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +424.080 204.480 m +424.080 204.480 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +373.68 202.19 Td +(E_INK_CS) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +424.080 197.280 m +420.480 193.680 l +409.680 193.680 l +409.680 200.880 l +420.480 200.880 l +424.080 197.280 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +424.080 197.280 m +424.080 197.280 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +391.68 194.99 Td +(SCK) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +424.080 190.080 m +420.480 186.480 l +409.680 186.480 l +409.680 193.680 l +420.480 193.680 l +424.080 190.080 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +424.080 190.080 m +424.080 190.080 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +388.08 187.79 Td +(MOSI) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +413.280 175.680 m +420.480 175.680 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +413.280 172.080 m +413.280 179.280 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +420.480 175.680 m +420.480 175.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +398.88 173.39 Td +(3V3) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +416.880 182.880 m +424.080 182.880 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +416.880 189.360 m +416.880 176.400 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +415.440 187.200 m +415.440 178.560 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +414.000 185.040 m +414.000 180.720 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +412.560 183.600 m +412.560 182.160 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +424.080 182.880 m +424.080 182.880 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +391.68 180.59 Td +(GND) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +438.480 211.680 m +442.080 215.280 l +452.880 215.280 l +452.880 208.080 l +442.080 208.080 l +438.480 211.680 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +438.480 211.680 m +438.480 211.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +457.38 209.39 Td +(P1.02) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +438.480 204.480 m +442.080 208.080 l +452.880 208.080 l +452.880 200.880 l +442.080 200.880 l +438.480 204.480 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +438.480 204.480 m +438.480 204.480 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +457.38 202.19 Td +(P1.07) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +438.480 218.880 m +442.080 222.480 l +452.880 222.480 l +452.880 215.280 l +442.080 215.280 l +438.480 218.880 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +438.480 218.880 m +438.480 218.880 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +457.38 216.59 Td +(P1.01) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +438.480 226.080 m +442.080 229.680 l +452.880 229.680 l +452.880 222.480 l +442.080 222.480 l +438.480 226.080 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +438.480 226.080 m +438.480 226.080 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +456.48 223.79 Td +(P1.06) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +438.480 226.080 m +434.880 226.080 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +424.080 226.080 m +427.680 226.080 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +427.680 226.080 m +431.280 228.240 l +434.880 226.080 l +431.280 223.920 l +427.680 226.080 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +424.080 226.080 m +424.080 226.080 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +424.080 226.080 m +424.080 226.080 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +438.480 218.880 m +434.880 218.880 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +424.080 218.880 m +427.680 218.880 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +427.680 218.880 m +431.280 221.040 l +434.880 218.880 l +431.280 216.720 l +427.680 218.880 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +424.080 218.880 m +424.080 218.880 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +424.080 218.880 m +424.080 218.880 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +438.480 211.680 m +434.880 211.680 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +424.080 211.680 m +427.680 211.680 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +427.680 211.680 m +431.280 213.840 l +434.880 211.680 l +431.280 209.520 l +427.680 211.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +424.080 211.680 m +424.080 211.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +424.080 211.680 m +424.080 211.680 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +438.480 204.480 m +434.880 204.480 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +424.080 204.480 m +427.680 204.480 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +427.680 204.480 m +431.280 206.640 l +434.880 204.480 l +431.280 202.320 l +427.680 204.480 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +424.080 204.480 m +424.080 204.480 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +424.080 204.480 m +424.080 204.480 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +438.480 197.280 m +434.880 197.280 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +424.080 197.280 m +427.680 197.280 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +427.680 197.280 m +431.280 199.440 l +434.880 197.280 l +431.280 195.120 l +427.680 197.280 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +424.080 197.280 m +424.080 197.280 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +424.080 197.280 m +424.080 197.280 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +438.480 190.080 m +434.880 190.080 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +424.080 190.080 m +427.680 190.080 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +427.680 190.080 m +431.280 192.240 l +434.880 190.080 l +431.280 187.920 l +427.680 190.080 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +424.080 190.080 m +424.080 190.080 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +424.080 190.080 m +424.080 190.080 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +438.480 182.880 m +434.880 182.880 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +424.080 182.880 m +427.680 182.880 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +427.680 182.880 m +431.280 185.040 l +434.880 182.880 l +431.280 180.720 l +427.680 182.880 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +424.080 182.880 m +424.080 182.880 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +424.080 182.880 m +424.080 182.880 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +438.480 175.680 m +434.880 175.680 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +424.080 175.680 m +427.680 175.680 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +427.680 175.680 m +431.280 177.840 l +434.880 175.680 l +431.280 173.520 l +427.680 175.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +424.080 175.680 m +420.480 175.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +424.080 175.680 m +420.480 175.680 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +449.280 175.680 m +442.080 175.680 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +449.280 179.280 m +449.280 172.080 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +442.080 175.680 m +442.080 175.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +452.88 173.39 Td +(VCC) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +445.680 182.880 m +438.480 182.880 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +445.680 189.360 m +445.680 176.400 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +447.120 187.200 m +447.120 178.560 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +448.560 185.040 m +448.560 180.720 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +450.000 183.600 m +450.000 182.160 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +438.480 182.880 m +438.480 182.880 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +452.74 180.59 Td +(GND) Tj +ET +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +438.480 182.880 m +438.480 182.880 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +438.480 182.880 m +438.480 182.880 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +438.480 175.680 m +442.080 175.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +438.480 175.680 m +442.080 175.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +438.480 226.080 m +438.480 226.080 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +438.480 226.080 m +438.480 226.080 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +438.480 218.880 m +438.480 218.880 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +438.480 218.880 m +438.480 218.880 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +438.480 211.680 m +438.480 211.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +438.480 211.680 m +438.480 211.680 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +438.480 204.480 m +438.480 204.480 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +438.480 204.480 m +438.480 204.480 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +438.480 197.280 m +442.080 200.880 l +452.880 200.880 l +452.880 193.680 l +442.080 193.680 l +438.480 197.280 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +438.480 197.280 m +438.480 197.280 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +457.42 194.99 Td +(SCK) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +0.00 g +[] 0 d +438.480 190.080 m +442.080 193.680 l +452.880 193.680 l +452.880 186.480 l +442.080 186.480 l +438.480 190.080 l +S +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +438.480 190.080 m +438.480 190.080 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +456.36 188.47 Td +(MOSI) Tj +ET +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +438.480 197.280 m +438.480 197.280 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +438.480 197.280 m +438.480 197.280 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +438.480 190.080 m +438.480 190.080 l +S +1 J +1 j +0.72 w +0.00 0.53 0.00 RG +0.00 g +[] 0 d +438.480 190.080 m +438.480 190.080 l +S +7.20 w +BT +/F2 8.181818181818182 Tf +9.00 TL +0.000 g +395.28 249.32 Td +(E-Ink Connections) Tj +ET +2 J +0 j +72 M +0.72 w +0.63 0.00 0.00 RG +[] 0 d +395.28 1126.08 57.60 -115.20 re +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +431.44 1109.75 Td +(BATIN) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +457.20 1112.63 Td +(25) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +467.280 1111.680 m +452.880 1111.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +436.18 1102.55 Td +(GND) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +457.20 1105.43 Td +(24) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +467.280 1104.480 m +452.880 1104.480 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +437.63 1095.35 Td +(RST) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +457.20 1098.23 Td +(23) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +467.280 1097.280 m +452.880 1097.280 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +425.98 1088.15 Td +(3.3v Out) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +457.20 1091.03 Td +(22) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +467.280 1090.080 m +452.880 1090.080 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +433.62 1059.35 Td +(P1.15) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +457.20 1062.23 Td +(18) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +467.280 1061.280 m +452.880 1061.280 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +433.62 1080.95 Td +(P0.31) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +457.20 1083.83 Td +(21) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +467.280 1082.880 m +452.880 1082.880 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +397.44 1102.55 Td +(P0.08) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +387.32 1105.43 Td +(3) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +380.880 1104.480 m +395.280 1104.480 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +433.62 1052.15 Td +(P1.13) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +457.20 1055.03 Td +(17) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +467.280 1054.080 m +452.880 1054.080 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +433.62 1073.75 Td +(P0.29) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +457.20 1076.63 Td +(20) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +467.280 1075.680 m +452.880 1075.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +433.62 1044.95 Td +(P1.11) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +457.20 1047.83 Td +(16) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +467.280 1046.880 m +452.880 1046.880 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +433.62 1066.55 Td +(P0.02) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +457.20 1069.43 Td +(19) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +467.280 1068.480 m +452.880 1068.480 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +397.44 1095.35 Td +(GND) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +387.32 1098.23 Td +(4) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +380.880 1097.280 m +395.280 1097.280 l +S +0.72 w +0.63 0.00 0.00 RG +395.28 1111.68 m 395.28 1112.87 394.31 1113.84 393.12 1113.84 c +391.93 1113.84 390.96 1112.87 390.96 1111.68 c +390.96 1110.49 391.93 1109.52 393.12 1109.52 c +394.31 1109.52 395.28 1110.49 395.28 1111.68 c +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +397.44 1109.75 Td +(P0.06) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +387.32 1112.63 Td +(2) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +380.880 1111.680 m +390.960 1111.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +433.62 1030.55 Td +(P0.09) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +457.20 1033.43 Td +(14) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +467.280 1032.480 m +452.880 1032.480 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +433.62 1037.75 Td +(P0.10) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +457.20 1040.63 Td +(15) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +467.280 1039.680 m +452.880 1039.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +397.44 1037.75 Td +(P1.04) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +383.68 1040.63 Td +(12) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +380.880 1039.680 m +395.280 1039.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +397.44 1088.15 Td +(GND) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +387.32 1091.03 Td +(5) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +380.880 1090.080 m +395.280 1090.080 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +397.44 1080.95 Td +(P0.17) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +387.32 1083.83 Td +(6) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +380.880 1082.880 m +395.280 1082.880 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +397.44 1073.75 Td +(P0.20) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +387.32 1076.63 Td +(7) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +380.880 1075.680 m +395.280 1075.680 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +397.44 1066.55 Td +(P0.22) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +387.32 1069.43 Td +(8) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +380.880 1068.480 m +395.280 1068.480 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +397.44 1059.35 Td +(P0.24) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +387.32 1062.23 Td +(9) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +380.880 1061.280 m +395.280 1061.280 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +397.44 1052.15 Td +(P1.00) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +383.68 1055.03 Td +(10) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +380.880 1054.080 m +395.280 1054.080 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +397.44 1044.95 Td +(P0.11) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +383.68 1047.83 Td +(11) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +380.880 1046.880 m +395.280 1046.880 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +397.44 1030.55 Td +(P1.06) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +383.68 1033.43 Td +(13) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +380.880 1032.480 m +395.280 1032.480 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +0.00 1.00 -1.00 0.00 403.33 1011.96 Tm +(P1.01) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +0.00 1.00 -1.00 0.00 400.45 998.20 Tm +(27) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +402.480 996.480 m +402.480 1010.880 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +0.00 1.00 -1.00 0.00 410.53 1011.96 Tm +(P1.02) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +0.00 1.00 -1.00 0.00 407.65 998.20 Tm +(28) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +409.680 996.480 m +409.680 1010.880 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +0.00 1.00 -1.00 0.00 417.73 1011.96 Tm +(P1.07) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +0.00 1.00 -1.00 0.00 414.85 998.20 Tm +(29) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +416.880 996.480 m +416.880 1010.880 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +431.44 1116.95 Td +(BATIN) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +457.20 1119.83 Td +(26) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +467.280 1118.880 m +452.880 1118.880 l +S +1 J +1 j +0.72 w +0.20 0.80 0.20 RG +[] 0 d +464.400 1121.760 m +470.160 1116.000 l +464.400 1116.000 m +470.160 1121.760 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +397.44 1116.95 Td +(GND) Tj +ET +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +387.32 1119.83 Td +(1) Tj +ET +1 J +1 j +0.72 w +0.63 0.00 0.00 RG +[] 0 d +380.880 1118.880 m +395.280 1118.880 l +S +1 J +1 j +0.72 w +0.20 0.80 0.20 RG +[] 0 d +378.000 1121.760 m +383.760 1116.000 l +378.000 1116.000 m +383.760 1121.760 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 0.000 1.000 rg +419.78 1128.78 Td +(PRO_MICRO_NRF52840_29P) Tj +ET +q +1 0 0 1 762.48 1112.40 cm +1.0000 0.0000 0.0000 1.0000 0 0 cm +1 0 0 1 0.00 0.00 cm +72.00 0 0 72.00 0 0 cm +/I0 Do +Q +q +0.16 0.16 0.23 rg +[] 0 d +348.612 35.359 m +470.980 35.359 l +470.980 11.520 l +348.612 11.520 l +348.612 35.359 l +f +0.40 0.92 0.58 rg +[] 0 d +381.817 11.520 m +351.787 11.520 l +349.935 11.520 348.480 12.977 348.480 14.831 c +348.480 32.048 l +348.480 33.902 349.935 35.359 351.787 35.359 c +381.817 35.359 l +381.817 11.520 l +f +0.16 0.16 0.23 rg +[] 0 d +368.720 30.591 m +368.720 30.591 l +368.588 30.591 l +368.588 30.591 l +368.588 30.591 l +368.456 30.591 l +368.456 30.458 l +368.456 30.458 l +368.456 30.458 l +368.323 30.458 l +368.323 30.326 368.323 30.326 368.323 30.326 c +368.191 30.326 l +368.191 30.326 l +368.191 30.326 368.191 30.326 368.191 30.326 c +368.191 30.326 l +368.059 30.194 l +368.059 30.194 l +368.059 30.194 368.059 30.194 368.059 30.194 c +368.059 30.194 l +368.059 30.061 l +368.059 30.061 368.059 30.061 367.927 30.061 c +367.927 30.061 l +367.927 29.929 l +367.927 29.929 l +367.927 29.929 367.927 29.929 367.927 29.929 c +367.794 29.796 l +367.794 29.796 l +367.794 29.664 l +367.794 29.664 367.794 29.664 367.662 29.664 c +367.662 29.664 l +367.662 29.664 l +367.662 29.531 367.662 29.531 367.662 29.531 c +367.662 29.531 l +367.662 29.531 l +367.530 29.531 l +367.530 29.399 367.530 29.399 367.530 29.399 c +367.530 29.399 l +367.397 29.266 l +367.397 29.266 l +367.397 29.134 367.397 29.134 367.397 29.266 c +367.397 29.134 l +367.397 29.134 l +367.397 29.134 367.397 29.134 367.265 29.134 c +367.265 29.002 l +367.265 29.002 l +367.265 29.002 l +367.133 28.869 l +367.133 28.869 l +367.133 28.737 l +367.133 28.737 367.133 28.737 367.133 28.737 c +367.001 28.737 l +367.001 28.604 l +367.001 28.604 l +367.001 28.604 367.001 28.604 367.001 28.604 c +366.868 28.472 l +366.868 28.472 l +366.868 28.339 l +366.868 28.339 l +366.868 28.339 366.868 28.339 366.736 28.339 c +366.736 28.339 l +366.736 28.207 366.736 28.207 366.736 28.207 c +366.736 28.207 l +366.736 28.207 l +366.736 28.075 l +366.736 28.075 366.736 28.075 366.604 28.075 c +366.604 28.075 l +366.604 27.942 l +366.471 27.942 l +366.471 27.810 l +366.471 27.810 366.471 27.810 366.471 27.810 c +366.471 27.810 l +366.471 27.810 366.471 27.810 366.471 27.810 c +366.339 27.677 l +366.339 27.677 l +366.339 27.677 l +366.339 27.545 366.339 27.545 366.339 27.545 c +366.339 27.545 l +366.207 27.545 l +366.207 27.412 l +366.207 27.412 366.207 27.412 366.207 27.412 c +366.074 27.412 l +366.074 27.280 l +366.074 27.280 366.207 27.280 366.074 27.280 c +366.074 27.280 l +366.074 27.280 l +366.074 27.148 l +366.074 27.148 366.074 27.148 365.942 27.148 c +365.942 27.015 l +365.942 27.015 l +365.942 26.883 l +365.942 26.883 365.942 26.883 365.810 26.883 c +365.810 26.883 l +365.810 26.883 l +365.810 26.750 365.810 26.750 365.810 26.750 c +365.678 26.750 l +365.678 26.750 l +365.678 26.618 l +365.678 26.618 l +365.545 26.485 l +365.545 26.485 l +365.545 26.353 365.545 26.485 365.545 26.485 c +365.545 26.353 l +365.545 26.353 l +365.413 26.353 l +365.413 26.220 365.413 26.220 365.413 26.220 c +365.413 26.220 l +365.281 26.220 l +365.281 26.088 l +365.281 26.088 l +365.281 25.956 365.281 25.956 365.281 25.956 c +365.281 25.956 l +365.281 25.956 365.281 25.956 365.148 25.956 c +365.148 25.956 l +365.148 25.823 l +365.148 25.823 l +365.148 25.823 365.148 25.823 365.148 25.823 c +365.016 25.691 l +365.016 25.691 l +365.016 25.558 l +364.884 25.558 l +364.884 25.558 365.016 25.558 364.884 25.558 c +364.884 25.558 l +364.884 25.426 364.884 25.426 364.884 25.426 c +364.884 25.426 l +364.884 25.426 l +364.752 25.293 l +364.752 25.293 364.752 25.293 364.752 25.293 c +364.752 25.293 l +364.752 25.161 l +364.619 25.161 l +364.619 25.029 364.619 25.029 364.619 25.029 c +364.619 25.029 l +364.619 25.029 l +364.619 25.029 364.619 25.029 364.487 25.029 c +364.487 24.896 l +364.487 24.896 l +364.487 24.896 l +364.487 24.764 364.487 24.896 364.487 24.896 c +364.355 24.764 l +364.355 24.764 l +364.355 24.631 l +364.355 24.631 364.355 24.631 364.355 24.631 c +364.222 24.631 l +364.222 24.499 l +364.222 24.499 364.222 24.499 364.222 24.499 c +364.222 24.499 l +364.090 24.366 l +364.090 24.366 l +364.090 24.234 l +364.090 24.234 l +363.958 24.234 l +363.958 24.101 364.090 24.101 363.958 24.101 c +363.958 24.101 l +363.958 24.101 l +363.958 23.969 l +363.958 23.969 363.958 23.969 363.826 23.969 c +363.826 23.969 l +363.826 23.837 l +363.693 23.837 l +363.693 23.704 l +363.693 23.704 l +363.693 23.572 363.693 23.704 363.693 23.704 c +363.561 23.572 l +363.561 23.572 l +363.561 23.572 l +363.561 23.439 363.561 23.439 363.561 23.439 c +363.561 23.439 l +363.429 23.439 l +363.429 23.307 l +363.429 23.307 l +363.429 23.174 363.429 23.174 363.296 23.174 c +363.296 23.174 l +363.296 23.174 363.429 23.174 363.296 23.174 c +363.296 23.174 l +363.296 23.042 l +363.296 23.042 l +363.296 23.042 363.296 23.042 363.164 23.042 c +363.164 22.910 l +363.164 22.910 l +363.164 22.777 l +363.164 22.777 363.164 22.777 363.032 22.777 c +363.032 22.777 l +363.032 22.777 l +363.032 22.645 363.032 22.645 363.032 22.645 c +362.900 22.645 l +362.900 22.645 l +362.900 22.645 l +362.900 22.512 362.900 22.512 362.900 22.512 c +362.900 22.512 l +362.767 22.380 l +362.767 22.380 l +362.767 22.247 362.767 22.247 362.767 22.380 c +362.767 22.247 l +362.767 22.247 l +362.767 22.247 362.767 22.247 362.635 22.247 c +362.635 22.115 l +362.635 22.115 l +362.503 22.115 l +362.503 21.982 l +362.503 21.982 l +362.503 21.850 l +362.503 21.850 362.503 21.850 362.370 21.850 c +362.370 21.850 l +362.370 21.718 l +362.370 21.718 l +362.370 21.718 362.370 21.718 362.370 21.718 c +362.238 21.585 l +362.238 21.585 l +362.238 21.453 l +362.106 21.453 l +362.106 21.453 l +362.106 21.320 362.106 21.320 362.106 21.320 c +362.106 21.320 l +362.106 21.320 l +361.974 21.188 l +361.974 21.188 362.106 21.188 361.974 21.188 c +361.974 21.188 l +361.974 21.055 l +361.841 21.055 l +361.841 20.923 l +361.841 20.923 361.841 20.923 361.841 20.923 c +361.841 20.923 l +361.841 20.923 361.841 20.923 361.709 20.923 c +361.709 20.791 l +361.709 20.791 l +361.709 20.791 l +361.709 20.658 361.709 20.658 361.709 20.658 c +361.577 20.658 l +361.577 20.658 l +361.577 20.526 l +361.577 20.526 361.577 20.526 361.577 20.526 c +361.444 20.526 l +361.444 20.393 l +361.444 20.393 361.444 20.393 361.444 20.393 c +361.444 20.393 l +361.444 20.393 l +361.312 20.261 l +361.444 20.261 361.444 20.261 361.312 20.261 c +361.312 20.128 l +361.312 20.128 l +361.180 19.996 l +361.180 19.996 361.312 19.996 361.180 19.996 c +361.180 19.996 l +361.180 19.996 l +361.180 19.863 361.180 19.863 361.180 19.863 c +361.047 19.863 l +361.047 19.863 l +361.047 19.731 l +360.915 19.731 l +360.915 19.599 l +360.915 19.599 l +360.915 19.466 360.915 19.599 360.915 19.599 c +360.783 19.466 l +360.783 19.466 l +360.783 19.466 l +360.783 19.334 360.783 19.334 360.783 19.334 c +360.783 19.334 l +360.651 19.334 l +360.651 19.201 l +360.651 19.201 l +360.651 19.069 l +360.651 19.069 360.651 19.069 360.518 19.069 c +360.518 19.069 l +360.518 18.936 l +360.518 18.936 l +360.518 18.936 360.518 18.936 360.386 18.936 c +360.386 18.804 l +360.386 18.804 l +360.386 18.672 l +360.254 18.672 l +360.254 18.672 360.254 18.672 360.254 18.672 c +360.254 18.672 l +360.254 18.539 360.254 18.539 360.254 18.539 c +360.121 18.539 l +360.121 18.539 l +360.121 18.407 l +360.121 18.407 360.121 18.407 360.121 18.407 c +360.121 18.407 l +359.989 18.274 l +359.989 18.274 l +359.989 18.142 359.989 18.142 359.989 18.142 c +359.989 18.142 l +359.989 18.142 l +359.989 18.142 359.989 18.142 359.857 18.142 c +359.857 18.009 l +359.857 18.009 l +359.857 18.009 l +359.857 17.877 359.857 18.009 359.725 18.009 c +359.725 17.877 l +359.725 17.877 l +359.725 17.745 l +359.725 17.745 359.725 17.745 359.592 17.745 c +359.592 17.745 l +359.592 17.612 l +359.592 17.612 359.592 17.612 359.592 17.612 c +359.592 17.612 l +359.592 17.612 l +359.592 17.480 l +359.592 17.480 l +359.725 17.480 359.592 17.480 359.592 17.347 c +359.725 17.347 l +359.725 17.347 l +359.725 17.347 359.725 17.347 359.725 17.347 c +359.725 17.215 l +359.857 17.215 l +359.857 17.215 l +359.989 17.215 l +359.989 17.082 l +360.121 17.082 359.989 17.215 359.989 17.082 c +360.121 17.082 l +360.121 17.082 360.121 17.082 360.121 17.082 c +360.121 17.082 l +360.121 17.082 l +360.254 16.950 l +360.254 16.950 360.254 16.950 360.254 16.950 c +360.386 16.950 l +360.386 16.817 l +360.386 16.817 l +360.518 16.817 l +360.518 16.817 360.518 16.817 360.518 16.817 c +360.518 16.817 l +360.651 16.817 360.651 16.817 360.518 16.685 c +360.651 16.685 l +360.651 16.685 l +360.651 16.685 l +360.783 16.685 360.783 16.685 360.783 16.685 c +360.783 16.553 l +360.783 16.553 l +360.915 16.553 l +360.915 16.420 l +361.047 16.420 361.047 16.553 361.047 16.420 c +361.047 16.420 l +361.047 16.420 361.047 16.420 361.047 16.420 c +361.047 16.420 l +361.180 16.420 l +361.180 16.288 l +361.180 16.288 361.180 16.288 361.180 16.288 c +361.312 16.288 l +361.312 16.155 l +361.312 16.155 l +361.444 16.155 l +361.444 16.288 l +361.577 16.288 l +361.577 16.420 l +361.577 16.420 361.577 16.420 361.577 16.420 c +361.577 16.420 l +361.577 16.420 l +361.577 16.553 361.577 16.553 361.709 16.553 c +361.709 16.553 l +361.709 16.685 l +361.709 16.685 l +361.841 16.685 l +361.841 16.817 l +361.841 16.817 361.841 16.817 361.841 16.817 c +361.841 16.817 l +361.841 16.950 361.841 16.950 361.974 16.817 c +361.974 16.950 l +361.974 16.950 l +361.974 16.950 l +361.974 17.082 361.974 17.082 361.974 17.082 c +362.106 17.082 l +362.106 17.082 l +362.106 17.215 l +362.106 17.215 l +362.106 17.347 362.106 17.347 362.238 17.347 c +362.238 17.347 l +362.238 17.347 362.238 17.347 362.238 17.347 c +362.238 17.347 l +362.238 17.480 l +362.370 17.480 l +362.370 17.480 362.238 17.480 362.370 17.480 c +362.370 17.612 l +362.370 17.612 l +362.503 17.745 l +362.503 17.745 l +362.503 17.745 362.503 17.745 362.503 17.745 c +362.503 17.745 l +362.503 17.877 362.503 17.877 362.503 17.877 c +362.635 17.877 l +362.635 17.877 l +362.635 18.009 l +362.635 18.009 362.635 18.009 362.635 18.009 c +362.767 18.009 l +362.767 18.142 l +362.767 18.142 l +362.767 18.142 l +362.767 18.274 362.767 18.274 362.900 18.274 c +362.900 18.274 l +362.900 18.274 362.900 18.274 362.900 18.274 c +362.900 18.407 l +362.900 18.407 l +363.032 18.539 l +363.032 18.539 l +363.032 18.539 l +363.164 18.672 l +363.032 18.672 363.032 18.672 363.164 18.672 c +363.164 18.672 l +363.164 18.804 l +363.164 18.804 363.164 18.804 363.164 18.804 c +363.296 18.804 l +363.296 18.936 l +363.296 18.936 l +363.296 19.069 l +363.429 19.069 l +363.429 19.069 363.429 19.069 363.429 19.069 c +363.429 19.201 l +363.429 19.201 363.429 19.201 363.429 19.201 c +363.561 19.201 l +363.561 19.201 l +363.561 19.334 l +363.561 19.334 363.561 19.334 363.561 19.334 c +363.561 19.334 l +363.693 19.466 l +363.693 19.466 l +363.693 19.599 l +363.693 19.599 363.693 19.599 363.693 19.599 c +363.693 19.599 l +363.693 19.731 363.693 19.599 363.826 19.599 c +363.826 19.731 l +363.826 19.731 l +363.826 19.731 l +363.826 19.863 363.826 19.863 363.958 19.863 c +363.958 19.863 l +363.958 19.863 l +363.958 19.996 l +364.090 19.996 l +364.090 20.128 364.090 20.128 364.090 20.128 c +364.090 20.128 l +364.090 20.128 364.090 20.128 364.090 20.128 c +364.090 20.128 l +364.090 20.261 l +364.222 20.261 l +364.222 20.261 364.222 20.261 364.222 20.261 c +364.222 20.393 l +364.355 20.393 l +364.355 20.393 l +364.355 20.526 l +364.355 20.526 364.355 20.526 364.355 20.526 c +364.355 20.526 l +364.355 20.658 364.355 20.658 364.487 20.658 c +364.487 20.658 l +364.487 20.791 l +364.487 20.791 l +364.619 20.791 l +364.619 20.923 l +364.619 20.923 l +364.619 21.055 364.619 21.055 364.752 21.055 c +364.752 21.055 l +364.752 21.055 l +364.752 21.055 364.752 21.055 364.752 21.055 c +364.752 21.188 l +364.884 21.188 l +364.884 21.188 l +364.884 21.320 l +364.884 21.320 l +364.884 21.453 364.884 21.453 365.016 21.453 c +365.016 21.453 l +365.016 21.453 365.016 21.453 365.016 21.453 c +365.016 21.453 l +365.016 21.585 l +365.016 21.585 365.016 21.585 365.148 21.585 c +365.148 21.585 l +365.148 21.718 l +365.148 21.718 l +365.281 21.850 l +365.281 21.850 l +365.281 21.850 365.281 21.850 365.281 21.850 c +365.281 21.850 l +365.281 21.982 365.281 21.982 365.281 21.982 c +365.413 21.982 l +365.413 21.982 l +365.413 22.115 l +365.413 22.115 365.413 22.115 365.413 22.115 c +365.545 22.115 l +365.545 22.247 l +365.545 22.247 l +365.545 22.380 l +365.545 22.380 365.545 22.380 365.678 22.380 c +365.678 22.380 l +365.678 22.512 365.545 22.380 365.678 22.380 c +365.678 22.512 l +365.678 22.512 l +365.678 22.512 l +365.678 22.645 365.678 22.645 365.810 22.645 c +365.810 22.645 l +365.810 22.645 l +365.942 22.777 l +365.942 22.777 l +365.942 22.910 365.942 22.910 365.942 22.910 c +365.942 22.910 l +365.942 22.910 365.942 22.910 365.942 22.910 c +366.074 22.910 l +366.074 23.042 l +366.074 23.042 l +366.074 23.174 l +366.207 23.174 l +366.207 23.307 l +366.207 23.307 366.207 23.307 366.207 23.307 c +366.339 23.307 l +366.339 23.307 l +366.339 23.439 366.207 23.439 366.339 23.439 c +366.339 23.439 l +366.339 23.439 l +366.471 23.572 l +366.471 23.572 l +366.471 23.704 l +366.471 23.704 l +366.471 23.837 366.471 23.837 366.604 23.704 c +366.604 23.837 l +366.604 23.837 l +366.604 23.837 366.604 23.837 366.604 23.837 c +366.736 23.969 l +366.736 23.969 l +366.736 23.969 l +366.736 24.101 l +366.868 24.101 l +366.868 24.234 366.736 24.234 366.868 24.234 c +366.868 24.234 l +366.868 24.234 366.868 24.234 366.868 24.234 c +366.868 24.234 l +366.868 24.366 l +367.001 24.366 l +367.001 24.366 367.001 24.366 367.001 24.366 c +367.001 24.499 l +367.133 24.499 l +367.133 24.631 l +367.133 24.631 l +367.133 24.631 367.133 24.631 367.133 24.631 c +367.133 24.631 l +367.133 24.764 367.133 24.764 367.265 24.764 c +367.265 24.764 l +367.265 24.764 l +367.265 24.896 l +367.265 24.896 367.265 24.896 367.265 24.896 c +367.397 24.896 l +367.397 25.029 l +367.397 25.029 l +367.530 25.029 l +367.530 25.161 367.397 25.161 367.530 25.161 c +367.530 25.161 l +367.530 25.161 367.530 25.161 367.530 25.161 c +367.530 25.293 l +367.530 25.293 l +367.662 25.293 l +367.662 25.426 367.530 25.426 367.662 25.426 c +367.662 25.426 l +367.662 25.426 l +367.794 25.558 l +367.794 25.558 367.794 25.558 367.794 25.558 c +367.794 25.558 l +367.794 25.691 l +367.794 25.691 367.794 25.691 367.927 25.691 c +367.927 25.691 l +367.927 25.823 l +367.927 25.823 l +368.059 25.956 l +368.059 25.956 l +368.059 26.088 l +368.059 26.088 368.059 26.088 368.059 26.088 c +368.191 26.088 l +368.191 26.088 l +368.191 26.220 368.191 26.220 368.191 26.220 c +368.191 26.220 l +368.191 26.220 l +368.323 26.353 l +368.323 26.353 l +368.323 26.485 l +368.323 26.485 368.323 26.485 368.456 26.485 c +368.456 26.485 l +368.456 26.618 368.323 26.485 368.456 26.485 c +368.456 26.618 l +368.456 26.618 l +368.456 26.618 l +368.456 26.750 368.456 26.750 368.588 26.750 c +368.588 26.750 l +368.588 26.750 l +368.720 26.883 l +368.720 26.883 l +368.720 27.015 368.720 27.015 368.720 27.015 c +368.720 27.015 l +368.720 27.015 368.720 27.015 368.720 27.015 c +368.853 27.015 l +368.853 27.148 l +368.853 27.148 l +368.853 27.148 368.853 27.148 368.853 27.148 c +368.853 27.280 l +368.985 27.280 l +368.985 27.280 l +368.985 27.412 l +368.985 27.412 368.985 27.412 369.117 27.412 c +369.117 27.412 l +369.117 27.545 l +369.117 27.545 l +369.117 27.545 l +369.249 27.412 l +369.249 27.412 l +369.249 27.412 369.249 27.412 369.249 27.412 c +369.249 27.280 l +369.249 27.280 l +369.382 27.280 369.382 27.280 369.382 27.280 c +369.382 27.148 l +369.382 27.148 l +369.514 27.015 l +369.514 27.015 369.514 27.015 369.514 27.015 c +369.514 27.015 l +369.514 27.015 l +369.514 26.883 l +369.646 26.883 369.646 26.883 369.646 26.883 c +369.646 26.750 l +369.646 26.750 l +369.646 26.750 l +369.779 26.750 369.779 26.750 369.779 26.618 c +369.779 26.618 l +369.779 26.618 l +369.779 26.485 l +369.911 26.485 369.911 26.618 369.911 26.485 c +369.911 26.485 l +369.911 26.485 369.911 26.485 369.911 26.485 c +369.911 26.353 l +369.911 26.353 l +370.043 26.220 l +370.043 26.220 l +370.043 26.220 l +370.175 26.220 370.043 26.220 370.043 26.088 c +370.043 26.088 l +370.175 26.088 l +370.175 26.088 370.175 26.088 370.175 26.088 c +370.175 25.956 l +370.308 25.956 l +370.308 25.823 l +370.308 25.823 370.308 25.823 370.308 25.823 c +370.308 25.691 l +370.308 25.691 l +370.440 25.691 l +370.440 25.691 370.440 25.691 370.440 25.691 c +370.440 25.558 l +370.440 25.558 l +370.572 25.426 l +370.572 25.426 370.572 25.426 370.572 25.426 c +370.572 25.426 l +370.572 25.293 l +370.705 25.293 l +370.705 25.293 370.705 25.293 370.705 25.293 c +370.705 25.293 l +370.705 25.293 370.705 25.293 370.705 25.161 c +370.705 25.161 l +370.837 25.161 l +370.837 25.029 l +370.837 25.029 l +370.969 25.029 370.837 25.029 370.837 24.896 c +370.969 24.896 l +370.969 24.896 l +370.969 24.896 l +370.969 24.896 370.969 24.896 370.969 24.764 c +370.969 24.764 l +371.101 24.631 l +371.101 24.631 l +371.101 24.631 371.101 24.631 371.101 24.631 c +371.234 24.499 l +371.234 24.499 l +371.234 24.499 l +371.234 24.499 371.234 24.499 371.234 24.366 c +371.234 24.366 l +371.366 24.366 l +371.366 24.234 l +371.366 24.234 l +371.366 24.101 l +371.498 24.101 l +371.498 24.101 371.498 24.101 371.498 24.101 c +371.498 23.969 l +371.498 23.969 371.498 24.101 371.498 23.969 c +371.631 23.969 l +371.631 23.837 l +371.631 23.837 l +371.631 23.837 l +371.763 23.837 371.763 23.837 371.763 23.704 c +371.763 23.704 l +371.763 23.704 l +371.763 23.572 l +371.895 23.572 371.895 23.704 371.895 23.572 c +371.895 23.439 l +371.895 23.439 l +371.895 23.439 l +372.027 23.439 372.027 23.439 372.027 23.307 c +372.027 23.307 l +372.027 23.307 l +372.027 23.307 l +372.160 23.307 372.027 23.307 372.027 23.174 c +372.160 23.174 l +372.160 23.042 l +372.160 23.042 l +372.292 23.042 l +372.292 22.910 l +372.292 22.910 l +372.292 22.910 372.292 22.910 372.292 22.777 c +372.292 22.777 l +372.424 22.777 l +372.424 22.777 372.424 22.777 372.424 22.777 c +372.424 22.645 l +372.424 22.645 l +372.557 22.512 l +372.557 22.512 372.557 22.512 372.557 22.512 c +372.557 22.512 l +372.557 22.380 l +372.689 22.380 l +372.689 22.380 372.689 22.380 372.689 22.380 c +372.689 22.247 l +372.689 22.247 l +372.821 22.115 l +372.821 22.115 372.821 22.247 372.821 22.115 c +372.821 22.115 l +372.821 22.115 l +372.821 21.982 l +372.954 21.982 372.954 21.982 372.954 21.982 c +372.954 21.982 l +372.954 21.982 372.954 21.982 372.954 21.850 c +372.954 21.850 l +372.954 21.850 l +373.086 21.718 l +373.086 21.718 l +373.086 21.585 l +373.218 21.718 373.218 21.718 373.218 21.585 c +373.218 21.585 l +373.218 21.585 l +373.218 21.585 373.218 21.585 373.218 21.453 c +373.218 21.453 l +373.350 21.453 l +373.350 21.320 l +373.350 21.320 373.350 21.320 373.350 21.320 c +373.350 21.188 l +373.350 21.188 l +373.483 21.188 l +373.483 21.188 373.483 21.188 373.483 21.188 c +373.483 21.055 l +373.615 21.055 l +373.615 20.923 l +373.615 20.923 373.615 20.923 373.615 20.923 c +373.615 20.923 l +373.615 20.791 l +373.747 20.791 l +373.747 20.791 373.747 20.791 373.747 20.791 c +373.747 20.791 l +373.747 20.791 373.747 20.791 373.747 20.658 c +373.747 20.658 l +373.880 20.526 l +373.880 20.526 l +373.880 20.526 l +374.012 20.393 l +374.012 20.393 374.012 20.393 374.012 20.393 c +374.012 20.393 l +374.012 20.393 l +374.144 20.393 374.012 20.393 374.012 20.261 c +374.144 20.261 l +374.144 20.128 l +374.144 20.128 l +374.276 20.128 374.276 20.128 374.144 19.996 c +374.276 19.996 l +374.276 19.996 l +374.276 19.996 l +374.276 19.996 374.276 19.996 374.276 19.863 c +374.409 19.863 l +374.409 19.731 l +374.409 19.731 l +374.541 19.731 374.409 19.731 374.409 19.731 c +374.541 19.599 l +374.541 19.599 l +374.541 19.599 l +374.541 19.599 374.541 19.599 374.541 19.599 c +374.541 19.466 l +374.673 19.466 374.673 19.466 374.541 19.466 c +374.673 19.466 l +374.673 19.334 l +374.673 19.334 l +374.806 19.201 l +374.806 19.201 l +374.806 19.201 374.806 19.201 374.806 19.201 c +374.806 19.201 l +374.806 19.069 l +374.938 19.069 374.938 19.069 374.938 19.069 c +374.938 18.936 l +374.938 18.936 l +374.938 18.936 l +375.070 18.936 375.070 18.936 375.070 18.804 c +375.070 18.804 l +375.070 18.804 l +375.070 18.672 l +375.202 18.672 375.202 18.804 375.202 18.672 c +375.202 18.672 l +375.202 18.539 l +375.202 18.539 l +375.335 18.539 375.335 18.539 375.335 18.407 c +375.335 18.407 l +375.335 18.407 l +375.335 18.407 l +375.467 18.407 375.335 18.407 375.335 18.274 c +375.335 18.274 l +375.467 18.274 375.467 18.274 375.467 18.274 c +375.467 18.142 l +375.467 18.142 l +375.599 18.142 l +375.599 18.009 l +375.599 18.009 375.599 18.009 375.599 18.009 c +375.599 18.009 l +375.599 17.877 l +375.732 17.877 l +375.732 17.877 375.732 17.877 375.732 17.877 c +375.732 17.745 l +375.732 17.745 l +375.864 17.612 l +375.864 17.612 375.864 17.612 375.864 17.612 c +375.864 17.612 l +375.864 17.480 l +375.996 17.480 l +375.996 17.480 375.996 17.480 375.996 17.480 c +375.996 17.347 l +375.996 17.347 l +376.128 17.347 l +376.128 17.215 l +376.128 17.215 l +376.128 17.082 l +376.261 17.082 376.261 17.215 376.261 17.082 c +376.261 17.082 l +376.261 17.082 376.261 17.082 376.261 17.082 c +376.261 16.950 l +376.393 16.950 l +376.393 16.817 l +376.393 16.817 l +376.525 16.817 376.393 16.817 376.393 16.817 c +376.525 16.685 l +376.525 16.685 l +376.525 16.685 l +376.525 16.685 376.525 16.685 376.525 16.685 c +376.525 16.553 l +376.658 16.553 l +376.658 16.420 l +376.658 16.420 376.658 16.420 376.658 16.420 c +376.790 16.288 l +376.790 16.288 l +376.790 16.288 l +376.790 16.288 376.790 16.288 376.790 16.288 c +376.790 16.155 l +376.922 16.155 l +376.922 16.155 l +376.922 16.288 376.922 16.288 376.922 16.288 c +377.054 16.288 l +377.054 16.288 l +377.054 16.288 l +377.054 16.420 377.054 16.420 377.187 16.420 c +377.187 16.420 l +377.187 16.420 l +377.319 16.420 l +377.319 16.553 377.319 16.553 377.319 16.553 c +377.451 16.553 l +377.451 16.553 l +377.451 16.553 l +377.451 16.685 377.451 16.685 377.451 16.685 c +377.451 16.685 l +377.451 16.685 377.451 16.685 377.584 16.685 c +377.584 16.685 l +377.716 16.685 l +377.716 16.817 l +377.716 16.817 l +377.716 16.817 377.716 16.817 377.848 16.817 c +377.848 16.817 l +377.848 16.817 l +377.981 16.950 l +377.848 16.950 377.848 16.950 377.981 16.950 c +377.981 16.950 l +378.113 17.082 l +378.113 17.082 l +378.113 17.082 378.113 17.082 378.113 17.082 c +378.245 17.082 l +378.245 17.082 l +378.245 17.215 l +378.245 17.215 378.245 17.215 378.377 17.215 c +378.377 17.215 l +378.377 17.347 l +378.510 17.347 l +378.510 17.347 378.510 17.347 378.510 17.347 c +378.642 17.347 l +378.642 17.347 l +378.642 17.480 l +378.642 17.480 378.642 17.480 378.642 17.480 c +378.642 17.480 l +378.774 17.480 378.774 17.480 378.774 17.612 c +378.774 17.612 l +378.642 17.612 378.774 17.612 378.774 17.612 c +378.642 17.612 l +378.642 17.745 l +378.642 17.745 l +378.510 17.877 l +378.510 17.877 l +378.510 17.877 378.510 17.877 378.510 17.877 c +378.510 18.009 l +378.510 18.009 l +378.377 18.009 378.377 18.009 378.377 18.009 c +378.377 18.142 l +378.377 18.142 l +378.377 18.142 l +378.245 18.142 378.245 18.142 378.245 18.274 c +378.245 18.274 l +378.245 18.274 l +378.245 18.407 l +378.113 18.407 378.113 18.274 378.113 18.407 c +378.113 18.407 l +378.113 18.407 378.113 18.407 378.113 18.407 c +378.113 18.539 l +378.113 18.539 l +377.981 18.672 l +377.981 18.672 l +377.981 18.672 l +377.848 18.672 377.981 18.672 377.981 18.804 c +377.981 18.804 l +377.848 18.804 377.848 18.804 377.848 18.804 c +377.848 18.936 l +377.848 18.936 l +377.716 18.936 l +377.716 19.069 l +377.716 19.069 l +377.716 19.069 377.716 19.069 377.716 19.201 c +377.716 19.201 l +377.584 19.201 l +377.584 19.201 377.584 19.201 377.584 19.201 c +377.584 19.334 l +377.584 19.334 l +377.451 19.466 l +377.451 19.466 377.451 19.466 377.451 19.466 c +377.451 19.466 l +377.451 19.599 l +377.319 19.599 l +377.319 19.599 377.319 19.599 377.319 19.599 c +377.319 19.599 l +377.319 19.599 377.319 19.599 377.319 19.731 c +377.319 19.731 l +377.187 19.863 l +377.187 19.863 l +377.187 19.863 l +377.187 19.996 l +377.054 19.996 377.054 19.996 377.054 19.996 c +377.054 19.996 l +377.054 19.996 377.054 19.996 377.054 20.128 c +377.054 20.128 l +376.922 20.128 l +376.922 20.261 l +376.922 20.261 l +376.922 20.261 l +376.790 20.261 376.790 20.261 376.790 20.393 c +376.790 20.393 l +376.790 20.393 l +376.790 20.393 376.790 20.393 376.790 20.526 c +376.790 20.526 l +376.658 20.526 l +376.658 20.658 l +376.658 20.658 376.658 20.658 376.658 20.658 c +376.525 20.791 l +376.525 20.791 l +376.525 20.791 l +376.525 20.791 376.525 20.791 376.525 20.791 c +376.525 20.923 l +376.525 20.923 376.525 20.791 376.525 20.923 c +376.393 20.923 l +376.393 21.055 l +376.393 21.055 l +376.393 21.188 l +376.261 21.188 l +376.261 21.188 376.261 21.188 376.261 21.188 c +376.261 21.188 l +376.261 21.188 376.261 21.188 376.261 21.320 c +376.128 21.320 l +376.128 21.453 l +376.128 21.453 l +376.128 21.453 l +375.996 21.453 375.996 21.453 375.996 21.585 c +375.996 21.585 l +375.996 21.585 l +375.996 21.585 l +375.864 21.585 375.864 21.585 375.996 21.718 c +375.864 21.718 l +375.864 21.850 l +375.864 21.850 l +375.732 21.850 375.732 21.850 375.732 21.850 c +375.732 21.982 l +375.732 21.982 l +375.732 21.982 l +375.732 21.982 375.732 21.982 375.732 22.115 c +375.732 22.115 l +375.599 22.115 375.599 22.115 375.599 22.115 c +375.599 22.115 l +375.599 22.247 l +375.599 22.247 l +375.467 22.380 l +375.467 22.380 l +375.467 22.380 375.467 22.380 375.467 22.380 c +375.467 22.512 l +375.335 22.512 375.335 22.512 375.467 22.512 c +375.335 22.512 l +375.335 22.645 l +375.335 22.645 l +375.202 22.777 l +375.202 22.777 375.202 22.645 375.202 22.777 c +375.202 22.777 l +375.202 22.777 l +375.202 22.910 l +375.070 22.910 375.070 22.910 375.070 22.910 c +375.070 23.042 l +375.070 23.042 l +374.938 23.042 l +374.938 23.042 374.938 23.042 374.938 23.174 c +374.938 23.174 l +374.938 23.174 l +374.938 23.307 l +374.806 23.174 374.806 23.174 374.806 23.307 c +374.806 23.307 l +374.806 23.307 374.806 23.307 374.806 23.307 c +374.806 23.439 l +374.806 23.439 l +374.673 23.439 l +374.673 23.572 l +374.673 23.572 l +374.541 23.572 374.541 23.572 374.541 23.704 c +374.541 23.704 l +374.541 23.704 374.541 23.704 374.541 23.704 c +374.541 23.837 l +374.541 23.837 l +374.409 23.837 l +374.409 23.969 l +374.409 23.969 374.409 23.969 374.409 23.969 c +374.409 23.969 l +374.409 24.101 l +374.276 24.101 l +374.276 24.101 374.276 24.101 374.276 24.101 c +374.276 24.234 l +374.144 24.234 l +374.144 24.366 l +374.144 24.366 l +374.144 24.366 l +374.012 24.499 l +374.012 24.499 374.012 24.499 374.012 24.499 c +374.012 24.499 l +374.012 24.499 374.012 24.499 374.012 24.631 c +374.012 24.631 l +373.880 24.631 l +373.880 24.764 l +373.880 24.764 l +373.880 24.896 l +373.747 24.896 373.747 24.764 373.747 24.896 c +373.747 24.896 l +373.747 24.896 l +373.615 24.896 373.747 24.896 373.747 25.029 c +373.615 25.029 l +373.615 25.161 l +373.615 25.161 l +373.483 25.161 373.615 25.161 373.615 25.161 c +373.483 25.293 l +373.483 25.293 l +373.483 25.293 l +373.483 25.293 373.483 25.293 373.483 25.293 c +373.483 25.426 l +373.350 25.426 373.350 25.426 373.350 25.426 c +373.350 25.426 l +373.350 25.558 l +373.350 25.558 l +373.218 25.691 l +373.218 25.691 l +373.218 25.691 373.218 25.691 373.218 25.691 c +373.218 25.691 l +373.086 25.691 373.218 25.691 373.218 25.823 c +373.086 25.823 l +373.086 25.956 l +373.086 25.956 l +373.086 26.088 l +372.954 26.088 l +372.954 26.088 372.954 26.088 372.954 26.088 c +372.954 26.088 l +372.954 26.220 l +372.821 26.220 372.821 26.088 372.821 26.220 c +372.821 26.220 l +372.821 26.353 l +372.821 26.353 l +372.689 26.353 372.689 26.353 372.689 26.485 c +372.689 26.485 l +372.689 26.485 l +372.689 26.485 l +372.557 26.485 372.557 26.485 372.557 26.618 c +372.557 26.618 l +372.557 26.618 372.557 26.618 372.557 26.618 c +372.557 26.750 l +372.557 26.750 l +372.424 26.750 l +372.424 26.883 l +372.424 26.883 l +372.292 26.883 372.424 26.883 372.424 27.015 c +372.424 27.015 l +372.292 27.015 372.292 27.015 372.292 27.015 c +372.292 27.015 l +372.292 27.148 l +372.160 27.148 l +372.160 27.280 l +372.160 27.280 372.160 27.280 372.160 27.280 c +372.160 27.280 l +372.160 27.412 l +372.027 27.412 l +372.027 27.412 372.027 27.412 372.027 27.412 c +372.027 27.545 l +372.027 27.545 l +371.895 27.545 l +371.895 27.545 371.895 27.545 371.895 27.677 c +371.895 27.677 l +371.895 27.677 l +371.763 27.810 l +371.763 27.810 371.763 27.677 371.763 27.810 c +371.763 27.810 l +371.763 27.810 371.763 27.810 371.763 27.810 c +371.763 27.942 l +371.631 27.942 l +371.631 28.075 l +371.631 28.075 l +371.631 28.075 l +371.498 28.075 371.498 28.075 371.498 28.207 c +371.498 28.207 l +371.498 28.207 371.498 28.207 371.498 28.207 c +371.498 28.339 l +371.498 28.339 l +371.366 28.339 l +371.366 28.472 l +371.366 28.472 371.366 28.472 371.366 28.472 c +371.234 28.604 l +371.234 28.604 l +371.234 28.604 l +371.234 28.604 371.234 28.604 371.234 28.604 c +371.234 28.737 l +371.101 28.737 l +371.101 28.869 l +371.101 28.869 371.101 28.869 371.101 28.869 c +371.101 28.869 l +371.101 29.002 l +370.969 29.002 l +370.969 29.002 370.969 29.002 370.969 29.002 c +370.969 29.002 l +370.969 29.002 370.969 29.002 370.969 29.134 c +370.837 29.134 l +370.837 29.266 l +370.837 29.266 l +370.837 29.266 l +370.705 29.399 l +370.705 29.399 370.705 29.399 370.705 29.399 c +370.705 29.399 l +370.705 29.399 370.705 29.399 370.705 29.531 c +370.705 29.531 l +370.572 29.531 l +370.572 29.664 l +370.572 29.664 l +370.440 29.664 370.440 29.664 370.440 29.664 c +370.440 29.796 l +370.440 29.796 l +370.440 29.796 l +370.308 29.796 370.440 29.796 370.440 29.929 c +370.308 29.929 l +370.308 29.929 l +370.308 30.061 l +370.308 30.061 l +370.175 30.061 370.175 30.061 370.175 30.194 c +370.175 30.194 l +370.043 30.326 l +370.043 30.326 l +369.911 30.326 369.911 30.326 369.911 30.326 c +369.911 30.458 l +369.911 30.458 l +369.911 30.458 l +369.779 30.458 l +369.779 30.458 l +369.646 30.458 369.779 30.458 369.779 30.591 c +369.646 30.591 l +369.646 30.591 l +369.646 30.591 l +369.514 30.591 l +369.514 30.591 l +369.382 30.591 l +369.249 30.591 l +368.985 30.591 l +368.853 30.591 l +368.720 30.591 l +f +0.16 0.16 0.23 rg +[] 0 d +360.254 30.723 m +360.254 30.723 360.254 30.723 360.254 30.723 c +360.254 30.723 l +360.121 30.591 l +360.254 30.591 360.254 30.591 360.121 30.591 c +360.121 30.591 l +360.121 30.458 l +359.989 30.458 l +359.989 30.458 l +359.989 30.326 l +359.989 30.326 359.989 30.326 359.989 30.326 c +359.989 30.326 l +359.989 30.194 359.989 30.194 359.857 30.194 c +359.857 30.194 l +359.857 30.194 l +359.857 30.194 l +359.857 30.061 359.857 30.061 359.725 30.061 c +359.725 30.061 l +359.725 29.929 l +359.725 29.929 l +359.725 29.796 359.725 29.929 359.592 29.929 c +359.592 29.796 l +359.592 29.796 l +359.592 29.796 l +359.592 29.664 359.592 29.664 359.592 29.664 c +359.460 29.664 l +359.460 29.664 l +359.460 29.531 l +359.460 29.531 359.460 29.531 359.460 29.531 c +359.328 29.399 l +359.328 29.399 l +359.328 29.399 l +359.328 29.266 359.328 29.399 359.328 29.399 c +359.195 29.266 l +359.195 29.266 l +359.195 29.134 l +359.195 29.134 359.195 29.134 359.195 29.134 c +359.063 29.134 l +359.063 29.002 l +359.063 29.002 l +359.063 29.002 359.063 29.002 359.063 29.002 c +359.063 28.869 l +358.931 28.869 l +358.931 28.869 l +358.931 28.737 l +358.931 28.737 358.931 28.737 358.799 28.737 c +358.799 28.737 l +358.799 28.604 358.931 28.604 358.799 28.604 c +358.799 28.604 l +358.799 28.604 l +358.666 28.472 l +358.666 28.472 l +358.666 28.339 l +358.666 28.339 358.666 28.339 358.666 28.339 c +358.666 28.339 l +358.666 28.207 358.666 28.339 358.534 28.339 c +358.534 28.207 l +358.534 28.207 l +358.534 28.207 l +358.534 28.075 358.534 28.075 358.402 28.075 c +358.402 28.075 l +358.402 28.075 l +358.402 27.942 l +358.402 27.942 358.402 27.942 358.269 27.942 c +358.269 27.810 l +358.269 27.810 l +358.269 27.810 l +358.269 27.677 358.269 27.810 358.137 27.810 c +358.137 27.677 l +358.137 27.677 l +358.137 27.545 l +358.137 27.545 358.137 27.545 358.005 27.545 c +358.005 27.545 l +358.005 27.412 l +358.005 27.412 l +358.005 27.412 358.005 27.412 358.005 27.412 c +357.873 27.280 l +357.873 27.280 l +357.873 27.148 l +357.873 27.148 357.873 27.148 357.873 27.148 c +357.740 27.148 l +357.740 27.148 l +357.740 27.015 357.740 27.015 357.740 27.015 c +357.740 27.015 l +357.608 26.883 l +357.608 26.883 l +357.608 26.883 l +357.608 26.750 l +357.608 26.750 357.608 26.750 357.476 26.750 c +357.476 26.750 l +357.476 26.618 357.476 26.618 357.476 26.750 c +357.476 26.618 l +357.476 26.618 l +357.343 26.485 l +357.343 26.485 l +357.343 26.485 l +357.343 26.353 357.343 26.353 357.211 26.353 c +357.211 26.353 l +357.211 26.353 357.343 26.353 357.211 26.353 c +357.211 26.220 l +357.211 26.220 l +357.211 26.220 l +357.211 26.088 357.211 26.220 357.079 26.220 c +357.079 26.088 l +357.079 26.088 l +357.079 25.956 l +357.079 25.956 357.079 25.956 356.947 25.956 c +356.947 25.956 l +356.947 25.823 l +356.947 25.823 l +356.947 25.823 356.947 25.823 356.814 25.823 c +356.814 25.691 l +356.814 25.691 l +356.814 25.558 l +356.814 25.558 356.814 25.558 356.682 25.558 c +356.682 25.558 l +356.682 25.558 l +356.682 25.426 l +356.682 25.426 356.682 25.426 356.682 25.426 c +356.550 25.293 l +356.550 25.293 l +356.550 25.293 l +356.550 25.161 356.550 25.161 356.417 25.161 c +356.417 25.161 l +356.417 25.161 l +356.417 25.029 356.417 25.029 356.417 25.161 c +356.417 25.029 l +356.285 25.029 l +356.285 24.896 l +356.285 24.896 l +356.153 24.896 l +356.285 24.764 356.285 24.764 356.153 24.764 c +356.153 24.764 l +356.153 24.764 356.153 24.764 356.153 24.764 c +356.153 24.631 l +356.153 24.631 l +356.020 24.631 l +356.020 24.499 356.153 24.631 356.020 24.631 c +356.020 24.499 l +356.020 24.499 l +355.888 24.366 l +355.888 24.366 355.888 24.366 355.888 24.366 c +355.888 24.366 l +355.888 24.234 l +355.888 24.234 l +355.888 24.234 355.888 24.234 355.756 24.234 c +355.756 24.101 l +355.756 24.101 l +355.624 23.969 l +355.624 23.969 355.756 23.969 355.624 23.969 c +355.624 23.969 l +355.624 23.969 l +355.624 23.837 l +355.624 23.837 355.624 23.837 355.491 23.837 c +355.491 23.704 l +355.491 23.704 l +355.491 23.704 l +355.491 23.572 355.491 23.572 355.359 23.572 c +355.359 23.572 l +355.359 23.572 l +355.359 23.439 355.359 23.439 355.359 23.572 c +355.227 23.439 l +355.227 23.439 l +355.227 23.307 l +355.227 23.307 l +355.094 23.307 l +355.094 23.174 355.094 23.174 355.094 23.174 c +355.094 23.174 l +355.094 23.174 355.094 23.174 355.094 23.174 c +354.962 23.042 l +354.962 23.042 l +354.962 23.042 l +354.962 22.910 l +354.830 22.910 l +354.830 22.777 354.962 22.777 354.830 22.777 c +354.830 22.777 l +354.830 22.777 354.830 22.777 354.830 22.777 c +354.830 22.777 l +354.830 22.645 l +354.698 22.645 l +354.698 22.645 354.698 22.645 354.698 22.645 c +354.698 22.512 l +354.565 22.512 l +354.565 22.380 l +354.565 22.380 354.565 22.380 354.565 22.380 c +354.565 22.380 l +354.565 22.380 l +354.433 22.247 l +354.433 22.247 354.565 22.247 354.433 22.247 c +354.433 22.115 l +354.433 22.115 l +354.301 22.115 l +354.301 21.982 354.433 21.982 354.301 21.982 c +354.301 21.982 l +354.301 21.982 l +354.301 21.850 l +354.301 21.850 354.301 21.850 354.168 21.850 c +354.168 21.850 l +354.168 21.718 l +354.036 21.718 l +354.168 21.585 354.168 21.585 354.036 21.585 c +354.036 21.585 l +354.036 21.585 l +354.036 21.585 354.036 21.585 354.036 21.585 c +353.904 21.453 l +353.904 21.453 l +353.904 21.453 l +353.904 21.320 l +353.772 21.320 l +353.772 21.188 353.772 21.188 353.772 21.188 c +353.772 21.188 l +353.772 21.188 353.772 21.188 353.772 21.188 c +353.639 21.188 l +353.639 21.055 l +353.639 21.055 l +353.639 20.923 l +353.507 20.923 l +353.507 20.791 l +353.507 20.791 353.507 20.791 353.507 20.791 c +353.507 20.791 l +353.507 20.791 l +353.375 20.658 l +353.375 20.658 353.375 20.658 353.375 20.658 c +353.375 20.526 l +353.242 20.526 l +353.242 20.526 l +353.242 20.393 353.242 20.393 353.242 20.393 c +353.242 20.393 l +353.242 20.393 l +353.110 20.261 l +353.110 20.261 353.110 20.261 353.110 20.261 c +353.110 20.261 l +352.978 20.128 l +352.978 20.128 l +352.978 19.996 352.978 19.996 352.978 19.996 c +352.978 19.996 l +352.978 19.996 l +352.846 19.996 l +352.846 19.863 352.978 19.863 352.846 19.863 c +352.846 19.863 l +352.846 19.863 l +352.713 19.731 l +352.713 19.731 l +352.713 19.599 352.713 19.599 352.713 19.599 c +352.713 19.599 l +352.713 19.599 352.713 19.599 352.713 19.599 c +352.581 19.599 l +352.581 19.466 l +352.581 19.466 l +352.449 19.334 l +352.449 19.334 l +352.449 19.201 352.449 19.334 352.449 19.334 c +352.449 19.201 l +352.449 19.201 352.449 19.201 352.449 19.201 c +352.316 19.201 l +352.316 19.201 l +352.316 19.069 l +352.316 19.069 352.316 19.069 352.316 19.069 c +352.316 18.936 l +352.184 18.936 l +352.184 18.936 l +352.184 18.804 352.184 18.804 352.184 18.804 c +352.052 18.804 l +352.052 18.804 l +352.052 18.672 l +352.052 18.672 352.052 18.672 352.052 18.672 c +352.052 18.672 l +351.920 18.539 l +351.920 18.539 l +351.920 18.407 351.920 18.407 351.920 18.407 c +351.920 18.407 l +351.920 18.407 l +351.787 18.407 l +351.787 18.274 351.787 18.274 351.787 18.274 c +351.787 18.274 l +351.655 18.274 l +351.655 18.142 l +351.655 18.009 351.655 18.142 351.655 18.142 c +351.655 18.009 l +351.655 18.009 l +351.655 18.009 351.655 18.009 351.523 18.009 c +351.523 18.009 l +351.523 17.877 l +351.523 17.877 l +351.390 17.745 l +351.390 17.745 l +351.390 17.612 351.390 17.745 351.390 17.745 c +351.390 17.612 l +351.390 17.612 351.390 17.612 351.258 17.612 c +351.258 17.612 l +351.258 17.612 l +351.258 17.480 351.258 17.480 351.258 17.480 c +351.390 17.480 351.390 17.480 351.390 17.480 c +351.390 17.347 l +351.523 17.347 l +351.523 17.347 351.523 17.347 351.523 17.347 c +351.523 17.347 l +351.655 17.347 351.523 17.347 351.523 17.347 c +351.655 17.215 l +351.655 17.215 l +351.655 17.215 l +351.787 17.215 351.787 17.215 351.787 17.215 c +351.787 17.082 l +351.787 17.082 l +351.920 17.082 l +351.920 17.082 351.920 17.082 351.920 17.082 c +351.920 16.950 l +352.052 16.950 l +352.052 16.950 l +352.052 16.950 352.052 16.950 352.052 16.950 c +352.184 16.817 l +352.184 16.817 l +352.184 16.817 l +352.316 16.817 l +352.316 16.817 352.316 16.817 352.316 16.685 c +352.316 16.685 l +352.449 16.685 352.449 16.685 352.449 16.685 c +352.449 16.685 l +352.449 16.685 l +352.581 16.553 l +352.581 16.553 l +352.713 16.553 l +352.713 16.553 352.713 16.553 352.713 16.420 c +352.713 16.420 l +352.713 16.420 352.713 16.420 352.713 16.420 c +352.846 16.420 l +352.846 16.420 l +352.846 16.288 l +352.978 16.288 l +353.110 16.288 352.978 16.155 353.110 16.288 c +353.110 16.420 l +353.110 16.420 l +353.242 16.420 l +353.242 16.553 353.110 16.553 353.242 16.553 c +353.242 16.553 l +353.242 16.553 l +353.242 16.685 l +353.242 16.685 353.242 16.685 353.375 16.685 c +353.375 16.685 l +353.375 16.817 l +353.507 16.817 l +353.507 16.817 l +353.507 16.950 353.507 16.950 353.507 16.950 c +353.507 16.950 l +353.507 16.950 353.507 16.950 353.507 16.950 c +353.639 17.082 l +353.639 17.082 l +353.639 17.082 l +353.639 17.215 l +353.772 17.215 l +353.772 17.347 353.772 17.347 353.772 17.347 c +353.772 17.347 l +353.772 17.347 353.772 17.347 353.772 17.347 c +353.904 17.347 l +353.904 17.480 l +353.904 17.480 l +353.904 17.612 l +354.036 17.612 l +354.036 17.612 353.904 17.612 354.036 17.612 c +354.036 17.745 l +354.036 17.745 354.036 17.745 354.036 17.745 c +354.036 17.745 l +354.036 17.745 l +354.168 17.877 l +354.168 17.877 354.168 17.877 354.168 17.877 c +354.168 18.009 l +354.301 18.009 l +354.301 18.009 l +354.301 18.142 354.301 18.142 354.301 18.142 c +354.301 18.142 l +354.301 18.142 l +354.433 18.274 l +354.433 18.274 354.301 18.274 354.433 18.274 c +354.433 18.274 l +354.433 18.407 l +354.565 18.407 l +354.565 18.539 354.565 18.539 354.565 18.539 c +354.565 18.539 l +354.565 18.539 l +354.698 18.539 l +354.698 18.672 354.565 18.672 354.698 18.672 c +354.698 18.672 l +354.698 18.672 l +354.830 18.804 l +354.830 18.804 l +354.830 18.936 354.830 18.936 354.830 18.936 c +354.830 18.936 l +354.830 18.936 354.830 18.936 354.830 18.936 c +354.962 18.936 l +354.962 19.069 l +354.962 19.069 l +355.094 19.201 l +355.094 19.201 l +355.094 19.201 355.094 19.201 355.094 19.201 c +355.094 19.334 l +355.094 19.334 355.094 19.334 355.094 19.334 c +355.227 19.334 l +355.227 19.466 l +355.227 19.466 l +355.227 19.599 l +355.359 19.599 l +355.359 19.599 355.359 19.599 355.359 19.599 c +355.359 19.599 l +355.359 19.731 355.359 19.731 355.359 19.731 c +355.491 19.731 l +355.491 19.731 l +355.491 19.863 l +355.491 19.863 355.491 19.863 355.491 19.863 c +355.491 19.863 l +355.624 19.996 l +355.624 19.996 l +355.624 20.128 355.624 20.128 355.624 20.128 c +355.624 20.128 l +355.624 20.128 l +355.756 20.128 l +355.756 20.261 355.756 20.261 355.756 20.261 c +355.756 20.261 l +355.888 20.261 l +355.888 20.393 l +355.888 20.393 355.888 20.393 355.888 20.393 c +355.888 20.526 l +355.888 20.526 l +355.888 20.526 355.888 20.526 356.020 20.526 c +356.020 20.526 l +356.020 20.658 l +356.020 20.658 l +356.153 20.791 l +356.153 20.791 l +356.153 20.791 356.153 20.791 356.153 20.791 c +356.153 20.923 l +356.153 20.923 356.153 20.923 356.285 20.923 c +356.285 20.923 l +356.285 21.055 l +356.285 21.055 l +356.417 21.188 l +356.417 21.188 l +356.417 21.188 356.417 21.188 356.417 21.188 c +356.417 21.188 l +356.417 21.320 356.417 21.320 356.417 21.320 c +356.550 21.320 l +356.550 21.320 l +356.550 21.453 l +356.550 21.453 356.550 21.453 356.550 21.453 c +356.682 21.453 l +356.682 21.585 l +356.682 21.585 356.682 21.585 356.682 21.585 c +356.682 21.585 l +356.682 21.718 356.682 21.718 356.682 21.718 c +356.814 21.718 l +356.814 21.718 l +356.814 21.718 l +356.814 21.850 356.814 21.850 356.814 21.850 c +356.814 21.850 l +356.947 21.850 l +356.947 21.982 l +356.947 21.982 356.947 21.982 356.947 21.982 c +357.079 22.115 l +357.079 22.115 l +357.079 22.115 l +357.079 22.247 357.079 22.115 357.079 22.115 c +357.079 22.247 l +357.211 22.247 l +357.211 22.380 l +357.211 22.380 l +357.211 22.512 357.211 22.380 357.211 22.380 c +357.211 22.512 l +357.211 22.512 357.211 22.512 357.343 22.512 c +357.343 22.512 l +357.343 22.645 l +357.476 22.645 l +357.476 22.777 l +357.476 22.777 l +357.476 22.777 357.476 22.777 357.476 22.777 c +357.476 22.777 l +357.476 22.910 357.476 22.910 357.608 22.910 c +357.608 22.910 l +357.608 23.042 l +357.608 23.042 l +357.740 23.042 l +357.740 23.174 l +357.740 23.174 357.740 23.174 357.740 23.174 c +357.740 23.174 l +357.740 23.307 357.740 23.307 357.873 23.307 c +357.873 23.307 l +357.873 23.307 l +357.873 23.307 l +357.873 23.439 357.873 23.439 357.873 23.439 c +358.005 23.439 l +358.005 23.439 l +358.005 23.572 l +358.005 23.704 358.005 23.572 358.005 23.572 c +358.137 23.704 l +358.137 23.704 l +358.137 23.704 l +358.137 23.837 358.137 23.837 358.137 23.837 c +358.137 23.837 l +358.269 23.837 l +358.269 23.969 l +358.269 23.969 358.269 23.969 358.269 23.969 c +358.402 23.969 l +358.402 24.101 l +358.402 24.101 358.269 24.101 358.402 24.101 c +358.402 24.101 l +358.402 24.234 l +358.534 24.234 l +358.534 24.366 l +358.534 24.366 l +358.534 24.366 358.534 24.366 358.534 24.366 c +358.534 24.366 l +358.534 24.499 358.534 24.499 358.666 24.499 c +358.666 24.499 l +358.666 24.631 l +358.799 24.631 l +358.799 24.631 l +358.799 24.764 l +358.799 24.764 358.799 24.764 358.799 24.764 c +358.799 24.764 l +358.799 24.896 358.799 24.896 358.931 24.896 c +358.931 24.896 l +358.931 24.896 l +359.063 25.029 l +359.063 25.029 l +359.063 25.161 l +359.063 25.161 359.063 25.161 359.063 25.161 c +359.063 25.161 l +359.063 25.293 359.063 25.161 359.195 25.161 c +359.195 25.293 l +359.195 25.293 l +359.195 25.293 l +359.195 25.426 359.195 25.426 359.195 25.426 c +359.328 25.426 l +359.328 25.426 l +359.328 25.558 l +359.328 25.558 359.328 25.558 359.328 25.558 c +359.460 25.558 l +359.460 25.691 l +359.460 25.691 l +359.460 25.691 359.460 25.691 359.460 25.691 c +359.592 25.823 l +359.592 25.823 l +359.592 25.956 l +359.592 25.956 l +359.592 25.956 359.592 25.956 359.725 25.956 c +359.725 25.956 l +359.725 26.088 359.725 26.088 359.725 26.088 c +359.725 26.088 l +359.725 26.220 l +359.857 26.220 l +359.857 26.220 l +359.857 26.353 l +359.857 26.353 359.857 26.353 359.989 26.353 c +359.989 26.353 l +359.989 26.485 359.857 26.485 359.989 26.485 c +359.989 26.485 l +359.989 26.485 l +360.121 26.618 l +360.121 26.618 l +360.121 26.750 l +360.121 26.750 360.121 26.750 360.121 26.750 c +360.121 26.750 l +360.121 26.883 360.121 26.750 360.254 26.750 c +360.254 26.883 l +360.254 26.883 l +360.254 26.883 l +360.254 27.015 360.254 27.015 360.386 27.015 c +360.386 27.015 l +360.386 27.015 l +360.386 27.148 l +360.386 27.148 360.386 27.148 360.518 27.148 c +360.518 27.148 l +360.518 27.280 l +360.518 27.280 l +360.518 27.280 360.518 27.280 360.518 27.280 c +360.651 27.412 l +360.651 27.412 l +360.651 27.545 l +360.651 27.545 360.651 27.545 360.783 27.545 c +360.783 27.545 l +360.783 27.545 l +360.783 27.677 360.783 27.677 360.783 27.677 c +360.783 27.677 l +360.915 27.810 l +360.915 27.810 l +360.915 27.810 l +361.047 27.942 l +360.915 27.942 360.915 27.942 361.047 27.942 c +361.047 27.942 l +361.047 28.075 361.047 28.075 361.047 28.075 c +361.047 28.075 l +361.180 28.075 l +361.180 28.207 l +361.180 28.207 l +361.180 28.339 l +361.180 28.339 361.180 28.339 361.312 28.339 c +361.312 28.339 l +361.312 28.339 361.312 28.339 361.312 28.339 c +361.312 28.472 l +361.312 28.472 l +361.444 28.604 l +361.444 28.604 l +361.444 28.604 l +361.444 28.737 361.444 28.737 361.577 28.737 c +361.577 28.737 l +361.577 28.737 361.444 28.737 361.577 28.737 c +361.577 28.737 l +361.577 28.869 l +361.577 28.869 l +361.577 28.869 361.577 28.869 361.709 28.869 c +361.709 29.002 l +361.709 29.002 l +361.709 29.134 l +361.709 29.134 361.709 29.134 361.841 29.134 c +361.841 29.134 l +361.841 29.134 l +361.841 29.266 l +361.841 29.266 361.841 29.266 361.974 29.266 c +361.974 29.399 l +361.974 29.399 l +361.974 29.531 l +361.974 29.531 361.974 29.531 362.106 29.531 c +362.106 29.531 l +362.106 29.531 l +361.974 29.531 361.974 29.531 361.974 29.664 c +361.974 29.664 l +361.974 29.664 l +361.974 29.664 l +361.841 29.664 361.841 29.664 361.841 29.664 c +361.841 29.796 l +361.709 29.796 l +361.709 29.796 l +361.709 29.796 361.709 29.796 361.709 29.929 c +361.577 29.929 l +361.577 29.929 l +361.577 29.929 l +361.444 29.929 361.577 29.929 361.577 29.929 c +361.444 30.061 l +361.444 30.061 l +361.312 30.061 l +361.312 30.061 361.312 30.061 361.312 30.194 c +361.312 30.194 l +361.180 30.194 l +361.180 30.194 l +361.180 30.194 361.180 30.194 361.180 30.194 c +361.047 30.326 l +361.047 30.326 l +360.915 30.326 l +360.915 30.326 360.915 30.326 360.915 30.326 c +360.915 30.458 l +360.915 30.458 l +360.783 30.458 360.783 30.458 360.783 30.458 c +360.783 30.458 l +360.783 30.458 l +360.783 30.591 l +360.651 30.591 360.651 30.458 360.651 30.591 c +360.651 30.591 l +360.651 30.591 l +360.518 30.723 l +360.518 30.723 360.518 30.723 360.518 30.723 c +360.386 30.723 l +360.386 30.723 l +360.386 30.856 l +360.254 30.856 360.386 30.723 360.386 30.856 c +360.254 30.723 l +f +1.00 g +[] 0 d +389.093 30.591 m +391.077 30.591 392.665 30.194 393.855 29.399 c +394.914 28.737 395.443 27.545 395.443 25.823 c +395.443 25.029 395.310 24.234 395.046 23.704 c +394.781 23.042 394.384 22.645 393.723 22.247 c +393.194 21.850 392.532 21.585 391.739 21.320 c +390.813 21.188 389.887 21.055 388.828 21.055 c +387.638 21.055 l +387.638 16.023 l +384.992 16.023 l +384.992 30.194 l +385.521 30.326 386.182 30.458 386.976 30.458 c +387.770 30.591 388.431 30.591 389.093 30.591 c +389.093 30.591 l +389.225 28.339 m +388.564 28.339 388.034 28.339 387.638 28.207 c +387.638 23.307 l +388.828 23.307 l +390.151 23.307 391.077 23.572 391.739 23.837 c +392.400 24.234 392.797 24.896 392.797 25.823 c +392.797 26.353 392.665 26.750 392.532 27.015 c +392.268 27.412 392.003 27.677 391.739 27.810 c +391.474 27.942 391.077 28.075 390.680 28.207 c +390.151 28.339 389.754 28.339 389.225 28.339 c +389.225 28.339 l +f +1.00 g +[] 0 d +407.216 21.453 m +407.216 20.658 407.084 19.863 406.820 19.069 c +406.555 18.407 406.290 17.877 405.761 17.347 c +405.364 16.817 404.835 16.420 404.174 16.155 c +403.512 15.890 402.719 15.758 402.057 15.758 c +401.263 15.758 400.470 15.890 399.941 16.155 c +399.279 16.420 398.750 16.817 398.221 17.347 c +397.824 17.877 397.427 18.407 397.162 19.069 c +396.898 19.863 396.766 20.658 396.766 21.453 c +396.766 22.380 396.898 23.174 397.162 23.837 c +397.427 24.499 397.824 25.161 398.221 25.558 c +398.750 26.088 399.279 26.485 399.941 26.750 c +400.602 27.015 401.263 27.148 402.057 27.148 c +402.719 27.148 403.512 27.015 404.042 26.750 c +404.703 26.485 405.364 26.088 405.761 25.558 c +406.158 25.161 406.555 24.499 406.820 23.837 c +407.084 23.174 407.216 22.380 407.216 21.453 c +407.216 21.453 l +404.571 21.453 m +404.571 22.512 404.438 23.439 403.909 24.101 c +403.512 24.631 402.851 25.029 402.057 25.029 c +401.131 25.029 400.470 24.631 400.073 24.101 c +399.544 23.439 399.411 22.512 399.411 21.453 c +399.411 20.393 399.544 19.466 400.073 18.936 c +400.470 18.274 401.131 17.877 402.057 17.877 c +402.851 17.877 403.512 18.274 403.909 18.936 c +404.438 19.466 404.571 20.393 404.571 21.453 c +404.571 21.453 l +f +1.00 g +[] 0 d +416.080 22.910 m +415.683 21.718 415.418 20.526 415.022 19.334 c +414.625 18.142 414.360 17.082 413.963 16.023 c +411.847 16.023 l +411.582 16.685 411.317 17.347 411.053 18.274 c +410.656 19.069 410.391 19.863 410.127 20.791 c +409.730 21.718 409.465 22.777 409.201 23.704 c +408.936 24.764 408.539 25.823 408.275 26.883 c +410.921 26.883 l +411.053 26.353 411.185 25.691 411.450 25.029 c +411.582 24.366 411.714 23.704 411.847 22.910 c +412.111 22.247 412.243 21.585 412.508 20.923 c +412.640 20.261 412.905 19.599 413.037 19.069 c +413.302 19.731 413.434 20.393 413.699 21.055 c +413.831 21.718 414.095 22.380 414.228 23.042 c +414.360 23.837 414.625 24.499 414.757 25.029 c +414.889 25.691 415.022 26.353 415.154 26.883 c +417.138 26.883 l +417.270 26.353 417.403 25.691 417.535 25.029 c +417.667 24.499 417.800 23.837 418.064 23.042 c +418.196 22.380 418.329 21.718 418.593 21.055 c +418.726 20.393 418.990 19.731 419.122 19.069 c +419.387 19.599 419.519 20.261 419.784 20.923 c +419.916 21.585 420.181 22.247 420.313 22.910 c +420.445 23.704 420.710 24.366 420.842 25.029 c +420.975 25.691 421.107 26.353 421.239 26.883 c +423.885 26.883 l +423.620 25.823 423.356 24.764 422.959 23.704 c +422.694 22.777 422.430 21.718 422.033 20.791 c +421.768 19.863 421.504 19.069 421.107 18.274 c +420.842 17.347 420.578 16.685 420.313 16.023 c +418.196 16.023 l +417.800 17.082 417.535 18.142 417.138 19.334 c +416.741 20.526 416.344 21.718 416.080 22.910 c +416.080 22.910 l +f +1.00 g +[] 0 d +425.208 21.453 m +425.208 22.380 425.340 23.174 425.605 23.969 c +425.869 24.631 426.266 25.293 426.663 25.691 c +427.192 26.220 427.721 26.618 428.383 26.750 c +428.912 27.015 429.573 27.148 430.235 27.148 c +431.690 27.148 432.881 26.750 433.674 25.823 c +434.600 24.896 434.997 23.439 434.997 21.585 c +434.997 21.453 434.997 21.320 434.997 21.188 c +434.997 20.923 434.997 20.791 434.865 20.658 c +427.721 20.658 l +427.854 19.863 428.118 19.201 428.647 18.672 c +429.176 18.142 429.970 18.009 431.029 18.009 c +431.690 18.009 432.219 18.009 432.748 18.142 c +433.277 18.274 433.674 18.407 433.939 18.407 c +434.203 16.420 l +434.071 16.288 433.939 16.288 433.674 16.155 c +433.410 16.155 433.145 16.023 432.881 16.023 c +432.484 15.890 432.219 15.890 431.822 15.890 c +431.425 15.758 431.161 15.758 430.764 15.758 c +429.838 15.758 428.912 15.890 428.250 16.155 c +427.589 16.420 426.928 16.817 426.531 17.347 c +426.002 17.877 425.737 18.539 425.472 19.201 c +425.340 19.863 425.208 20.658 425.208 21.453 c +425.208 21.453 l +432.484 22.512 m +432.484 22.910 432.351 23.174 432.351 23.572 c +432.219 23.837 432.087 24.101 431.822 24.366 c +431.690 24.631 431.425 24.764 431.161 24.896 c +430.896 25.029 430.632 25.029 430.235 25.029 c +429.838 25.029 429.441 25.029 429.176 24.896 c +428.912 24.764 428.647 24.499 428.515 24.234 c +428.250 24.101 428.118 23.837 427.986 23.439 c +427.854 23.174 427.854 22.910 427.721 22.512 c +432.484 22.512 l +f +1.00 g +[] 0 d +443.861 24.631 m +443.596 24.764 443.331 24.764 442.935 24.896 c +442.538 24.896 442.141 25.029 441.612 25.029 c +441.347 25.029 441.083 24.896 440.818 24.896 c +440.421 24.896 440.289 24.764 440.156 24.764 c +440.156 16.023 l +437.643 16.023 l +437.643 26.353 l +438.040 26.618 438.701 26.750 439.495 26.883 c +440.156 27.015 440.950 27.148 441.876 27.148 c +442.009 27.148 442.273 27.148 442.405 27.148 c +442.670 27.148 442.935 27.015 443.067 27.015 c +443.331 27.015 443.596 26.883 443.728 26.883 c +443.993 26.883 444.125 26.750 444.257 26.750 c +443.861 24.631 l +f +1.00 g +[] 0 d +445.448 21.453 m +445.448 22.380 445.580 23.174 445.845 23.969 c +446.109 24.631 446.506 25.293 446.903 25.691 c +447.432 26.220 447.962 26.618 448.623 26.750 c +449.152 27.015 449.814 27.148 450.475 27.148 c +451.930 27.148 453.121 26.750 453.915 25.823 c +454.708 24.896 455.237 23.439 455.237 21.585 c +455.237 21.453 455.237 21.320 455.105 21.188 c +455.105 20.923 455.105 20.791 455.105 20.658 c +447.962 20.658 l +448.094 19.863 448.358 19.201 448.888 18.672 c +449.417 18.142 450.210 18.009 451.269 18.009 c +451.930 18.009 452.459 18.009 452.989 18.142 c +453.518 18.274 453.915 18.407 454.179 18.407 c +454.444 16.420 l +454.311 16.288 454.179 16.288 453.915 16.155 c +453.650 16.155 453.385 16.023 453.121 16.023 c +452.724 15.890 452.459 15.890 452.063 15.890 c +451.666 15.758 451.401 15.758 451.004 15.758 c +449.946 15.758 449.152 15.890 448.491 16.155 c +447.829 16.420 447.168 16.817 446.771 17.347 c +446.242 17.877 445.977 18.539 445.713 19.201 c +445.448 19.863 445.448 20.658 445.448 21.453 c +445.448 21.453 l +452.724 22.512 m +452.724 22.910 452.592 23.174 452.459 23.572 c +452.459 23.837 452.327 24.101 452.063 24.366 c +451.930 24.631 451.666 24.764 451.401 24.896 c +451.136 25.029 450.872 25.029 450.475 25.029 c +450.078 25.029 449.681 25.029 449.417 24.896 c +449.152 24.764 448.888 24.499 448.755 24.234 c +448.491 24.101 448.358 23.837 448.226 23.439 c +448.094 23.174 448.094 22.910 447.962 22.512 c +452.724 22.512 l +f +1.00 g +[] 0 d +459.868 21.453 m +459.868 20.393 460.132 19.466 460.661 18.936 c +461.058 18.274 461.852 18.009 462.778 18.009 c +463.175 18.009 463.572 18.009 463.836 18.009 c +464.101 18.009 464.365 18.009 464.498 18.142 c +464.498 24.366 l +464.233 24.499 463.969 24.631 463.572 24.764 c +463.307 24.896 462.910 25.029 462.381 25.029 c +461.587 25.029 460.926 24.631 460.397 24.101 c +460.000 23.439 459.868 22.512 459.868 21.453 c +459.868 21.453 l +467.011 16.420 m +466.482 16.155 465.953 16.023 465.159 15.890 c +464.365 15.890 463.572 15.758 462.778 15.758 c +461.852 15.758 461.190 15.890 460.397 16.155 c +459.735 16.420 459.206 16.817 458.677 17.347 c +458.280 17.745 457.883 18.407 457.619 19.069 c +457.354 19.731 457.222 20.526 457.222 21.453 c +457.222 22.247 457.354 23.042 457.619 23.837 c +457.751 24.499 458.148 25.029 458.545 25.558 c +458.942 26.088 459.471 26.485 460.000 26.750 c +460.661 27.015 461.323 27.148 462.117 27.148 c +462.646 27.148 463.043 27.015 463.439 26.883 c +463.836 26.883 464.233 26.750 464.498 26.485 c +464.498 31.783 l +467.011 32.180 l +467.011 16.420 l +f +Q +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +1.000 0.000 0.000 rg +784.08 720.59 Td +(Note!) Tj +ET +1 J +1 j +0.72 w +0.80 0.00 0.00 RG +0.00 g +[] 0 d +780.480 722.880 m +766.080 722.880 l +769.680 726.480 l +S +1 J +1 j +0.72 w +0.80 0.00 0.00 RG +0.00 g +[] 0 d +766.080 722.880 m +769.680 719.280 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +1.000 0.000 0.000 rg +784.08 563.63 Td +(Note!) Tj +ET +1 J +1 j +0.72 w +0.80 0.00 0.00 RG +0.00 g +[] 0 d +780.480 565.920 m +766.080 565.920 l +769.680 569.520 l +S +1 J +1 j +0.72 w +0.80 0.00 0.00 RG +0.00 g +[] 0 d +766.080 565.920 m +769.680 562.320 l +S +7.20 w +BT +/F2 6.545454545454544 Tf +7.20 TL +0.000 g +672.48 619.79 Td +(E22P-915M30S) Tj +ET +0.80 0.00 0.00 rg +656.28 233.28 m 656.28 234.27 655.47 235.08 654.48 235.08 c +653.49 235.08 652.68 234.27 652.68 233.28 c +652.68 232.29 653.49 231.48 654.48 231.48 c +655.47 231.48 656.28 232.29 656.28 233.28 c +f +0.80 0.00 0.00 rg +562.68 1082.88 m 562.68 1083.87 561.87 1084.68 560.88 1084.68 c +559.89 1084.68 559.08 1083.87 559.08 1082.88 c +559.08 1081.89 559.89 1081.08 560.88 1081.08 c +561.87 1081.08 562.68 1081.89 562.68 1082.88 c +f +0.80 0.00 0.00 rg +382.68 1093.68 m 382.68 1094.67 381.87 1095.48 380.88 1095.48 c +379.89 1095.48 379.08 1094.67 379.08 1093.68 c +379.08 1092.69 379.89 1091.88 380.88 1091.88 c +381.87 1091.88 382.68 1092.69 382.68 1093.68 c +f +0.80 0.00 0.00 rg +731.88 748.08 m 731.88 749.07 731.07 749.88 730.08 749.88 c +729.09 749.88 728.28 749.07 728.28 748.08 c +728.28 747.09 729.09 746.28 730.08 746.28 c +731.07 746.28 731.88 747.09 731.88 748.08 c +f +0.80 0.00 0.00 rg +483.48 758.88 m 483.48 759.87 482.67 760.68 481.68 760.68 c +480.69 760.68 479.88 759.87 479.88 758.88 c +479.88 757.89 480.69 757.08 481.68 757.08 c +482.67 757.08 483.48 757.89 483.48 758.88 c +f +0.80 0.00 0.00 rg +731.88 679.68 m 731.88 680.67 731.07 681.48 730.08 681.48 c +729.09 681.48 728.28 680.67 728.28 679.68 c +728.28 678.69 729.09 677.88 730.08 677.88 c +731.07 677.88 731.88 678.69 731.88 679.68 c +f +0.80 0.00 0.00 rg +731.88 708.48 m 731.88 709.47 731.07 710.28 730.08 710.28 c +729.09 710.28 728.28 709.47 728.28 708.48 c +728.28 707.49 729.09 706.68 730.08 706.68 c +731.07 706.68 731.88 707.49 731.88 708.48 c +f +0.80 0.00 0.00 rg +731.88 694.08 m 731.88 695.07 731.07 695.88 730.08 695.88 c +729.09 695.88 728.28 695.07 728.28 694.08 c +728.28 693.09 729.09 692.28 730.08 692.28 c +731.07 692.28 731.88 693.09 731.88 694.08 c +f +0.80 0.00 0.00 rg +731.88 686.88 m 731.88 687.87 731.07 688.68 730.08 688.68 c +729.09 688.68 728.28 687.87 728.28 686.88 c +728.28 685.89 729.09 685.08 730.08 685.08 c +731.07 685.08 731.88 685.89 731.88 686.88 c +f +0.80 0.00 0.00 rg +483.48 679.68 m 483.48 680.67 482.67 681.48 481.68 681.48 c +480.69 681.48 479.88 680.67 479.88 679.68 c +479.88 678.69 480.69 677.88 481.68 677.88 c +482.67 677.88 483.48 678.69 483.48 679.68 c +f +0.80 0.00 0.00 rg +483.48 686.88 m 483.48 687.87 482.67 688.68 481.68 688.68 c +480.69 688.68 479.88 687.87 479.88 686.88 c +479.88 685.89 480.69 685.08 481.68 685.08 c +482.67 685.08 483.48 685.89 483.48 686.88 c +f +0.80 0.00 0.00 rg +483.48 694.08 m 483.48 695.07 482.67 695.88 481.68 695.88 c +480.69 695.88 479.88 695.07 479.88 694.08 c +479.88 693.09 480.69 692.28 481.68 692.28 c +482.67 692.28 483.48 693.09 483.48 694.08 c +f +0.80 0.00 0.00 rg +483.48 708.48 m 483.48 709.47 482.67 710.28 481.68 710.28 c +480.69 710.28 479.88 709.47 479.88 708.48 c +479.88 707.49 480.69 706.68 481.68 706.68 c +482.67 706.68 483.48 707.49 483.48 708.48 c +f +0.80 0.00 0.00 rg +731.88 593.28 m 731.88 594.27 731.07 595.08 730.08 595.08 c +729.09 595.08 728.28 594.27 728.28 593.28 c +728.28 592.29 729.09 591.48 730.08 591.48 c +731.07 591.48 731.88 592.29 731.88 593.28 c +f +0.80 0.00 0.00 rg +731.88 524.88 m 731.88 525.87 731.07 526.68 730.08 526.68 c +729.09 526.68 728.28 525.87 728.28 524.88 c +728.28 523.89 729.09 523.08 730.08 523.08 c +731.07 523.08 731.88 523.89 731.88 524.88 c +f +0.80 0.00 0.00 rg +731.88 532.08 m 731.88 533.07 731.07 533.88 730.08 533.88 c +729.09 533.88 728.28 533.07 728.28 532.08 c +728.28 531.09 729.09 530.28 730.08 530.28 c +731.07 530.28 731.88 531.09 731.88 532.08 c +f +0.80 0.00 0.00 rg +731.88 539.28 m 731.88 540.27 731.07 541.08 730.08 541.08 c +729.09 541.08 728.28 540.27 728.28 539.28 c +728.28 538.29 729.09 537.48 730.08 537.48 c +731.07 537.48 731.88 538.29 731.88 539.28 c +f +0.80 0.00 0.00 rg +731.88 553.68 m 731.88 554.67 731.07 555.48 730.08 555.48 c +729.09 555.48 728.28 554.67 728.28 553.68 c +728.28 552.69 729.09 551.88 730.08 551.88 c +731.07 551.88 731.88 552.69 731.88 553.68 c +f +0.80 0.00 0.00 rg +163.08 408.24 m 163.08 409.23 162.27 410.04 161.28 410.04 c +160.29 410.04 159.48 409.23 159.48 408.24 c +159.48 407.25 160.29 406.44 161.28 406.44 c +162.27 406.44 163.08 407.25 163.08 408.24 c +f +0.80 0.00 0.00 rg +163.08 386.64 m 163.08 387.63 162.27 388.44 161.28 388.44 c +160.29 388.44 159.48 387.63 159.48 386.64 c +159.48 385.65 160.29 384.84 161.28 384.84 c +162.27 384.84 163.08 385.65 163.08 386.64 c +f +0.80 0.00 0.00 rg +163.08 393.84 m 163.08 394.83 162.27 395.64 161.28 395.64 c +160.29 395.64 159.48 394.83 159.48 393.84 c +159.48 392.85 160.29 392.04 161.28 392.04 c +162.27 392.04 163.08 392.85 163.08 393.84 c +f +0.80 0.00 0.00 rg +163.08 379.44 m 163.08 380.43 162.27 381.24 161.28 381.24 c +160.29 381.24 159.48 380.43 159.48 379.44 c +159.48 378.45 160.29 377.64 161.28 377.64 c +162.27 377.64 163.08 378.45 163.08 379.44 c +f +0.80 0.00 0.00 rg +98.28 1093.68 m 98.28 1094.67 97.47 1095.48 96.48 1095.48 c +95.49 1095.48 94.68 1094.67 94.68 1093.68 c +94.68 1092.69 95.49 1091.88 96.48 1091.88 c +97.47 1091.88 98.28 1092.69 98.28 1093.68 c +f +0.80 0.00 0.00 rg +278.28 1082.88 m 278.28 1083.87 277.47 1084.68 276.48 1084.68 c +275.49 1084.68 274.68 1083.87 274.68 1082.88 c +274.68 1081.89 275.49 1081.08 276.48 1081.08 c +277.47 1081.08 278.28 1081.89 278.28 1082.88 c +f +0.80 0.00 0.00 rg +724.68 841.68 m 724.68 842.67 723.87 843.48 722.88 843.48 c +721.89 843.48 721.08 842.67 721.08 841.68 c +721.08 840.69 721.89 839.88 722.88 839.88 c +723.87 839.88 724.68 840.69 724.68 841.68 c +f +0.80 0.00 0.00 rg +724.68 834.48 m 724.68 835.47 723.87 836.28 722.88 836.28 c +721.89 836.28 721.08 835.47 721.08 834.48 c +721.08 833.49 721.89 832.68 722.88 832.68 c +723.87 832.68 724.68 833.49 724.68 834.48 c +f +0.80 0.00 0.00 rg +724.68 827.28 m 724.68 828.27 723.87 829.08 722.88 829.08 c +721.89 829.08 721.08 828.27 721.08 827.28 c +721.08 826.29 721.89 825.48 722.88 825.48 c +723.87 825.48 724.68 826.29 724.68 827.28 c +f +0.80 0.00 0.00 rg +724.68 816.48 m 724.68 817.47 723.87 818.28 722.88 818.28 c +721.89 818.28 721.08 817.47 721.08 816.48 c +721.08 815.49 721.89 814.68 722.88 814.68 c +723.87 814.68 724.68 815.49 724.68 816.48 c +f +0.80 0.00 0.00 rg +688.68 816.48 m 688.68 817.47 687.87 818.28 686.88 818.28 c +685.89 818.28 685.08 817.47 685.08 816.48 c +685.08 815.49 685.89 814.68 686.88 814.68 c +687.87 814.68 688.68 815.49 688.68 816.48 c +f +0.80 0.00 0.00 rg +681.48 816.48 m 681.48 817.47 680.67 818.28 679.68 818.28 c +678.69 818.28 677.88 817.47 677.88 816.48 c +677.88 815.49 678.69 814.68 679.68 814.68 c +680.67 814.68 681.48 815.49 681.48 816.48 c +f +0.80 0.00 0.00 rg +674.28 816.48 m 674.28 817.47 673.47 818.28 672.48 818.28 c +671.49 818.28 670.68 817.47 670.68 816.48 c +670.68 815.49 671.49 814.68 672.48 814.68 c +673.47 814.68 674.28 815.49 674.28 816.48 c +f +0.80 0.00 0.00 rg +724.68 884.88 m 724.68 885.87 723.87 886.68 722.88 886.68 c +721.89 886.68 721.08 885.87 721.08 884.88 c +721.08 883.89 721.89 883.08 722.88 883.08 c +723.87 883.08 724.68 883.89 724.68 884.88 c +f +0.80 0.00 0.00 rg +231.48 329.04 m 231.48 330.03 230.67 330.84 229.68 330.84 c +228.69 330.84 227.88 330.03 227.88 329.04 c +227.88 328.05 228.69 327.24 229.68 327.24 c +230.67 327.24 231.48 328.05 231.48 329.04 c +f +0.80 0.00 0.00 rg +209.88 329.04 m 209.88 330.03 209.07 330.84 208.08 330.84 c +207.09 330.84 206.28 330.03 206.28 329.04 c +206.28 328.05 207.09 327.24 208.08 327.24 c +209.07 327.24 209.88 328.05 209.88 329.04 c +f +0.80 0.00 0.00 rg +663.48 1082.88 m 663.48 1083.87 662.67 1084.68 661.68 1084.68 c +660.69 1084.68 659.88 1083.87 659.88 1082.88 c +659.88 1081.89 660.69 1081.08 661.68 1081.08 c +662.67 1081.08 663.48 1081.89 663.48 1082.88 c +f +0.80 0.00 0.00 rg +717.48 1082.88 m 717.48 1083.87 716.67 1084.68 715.68 1084.68 c +714.69 1084.68 713.88 1083.87 713.88 1082.88 c +713.88 1081.89 714.69 1081.08 715.68 1081.08 c +716.67 1081.08 717.48 1081.89 717.48 1082.88 c +f +Q +endstream +endobj +1 0 obj +<> +endobj +5 0 obj +<< +/Type /FontDescriptor +/FontName /SimSun +/FontBBox [-8 -145 1000 859] +/Flags 32 +/StemV 0 +/ItalicAngle 0 +/Ascent 859 +/Descent -141 +/CapHeight 175 +>> +endobj +6 0 obj +<< +/Type /Font +/BaseFont /SimSun +/FontDescriptor 5 0 R +/W [1 95 500] +/Subtype /CIDFontType2 +/CIDSystemInfo +<< +/Ordering (GB1) +/Registry (Adobe) +/Supplement 2 +>> +>> +endobj +7 0 obj +<< +/Type /Font +/Subtype /Type0 +/BaseFont /SimSun +/Encoding /UniGB-UCS2-H +/DescendantFonts [6 0 R] +>> +endobj +8 0 obj +<< +/Descent -325 +/CapHeight 500 +/StemV 80 +/Type /FontDescriptor +/Flags 32 +/FontBBox [-665 -325 2000 1006] +/FontName /Arial +/ItalicAngle 0 +/Ascent 1006 +>> +endobj +9 0 obj +<> +endobj +10 0 obj +<< +/Type /XObject +/Subtype /Image +/Width 1024 +/Height 1024 +/ColorSpace /DeviceRGB +/BitsPerComponent 8 +/DecodeParms <> +/SMask 11 0 R +/Length 56607 +/Filter /FlateDecode +>> +stream +x{\w]))D{ErNs!c9Ω-gCưp/& cMCJF4sHrӜ)6ms\B)onÕ>xwzs]+Ah! @C0 a4h! @C0 a4h! @C0 a4h! @C0 a4h! @C0 a4h! @C0 a4h! @C0 a4h! @C0 a4h! @C0 a4.<8wܙ3g._|IݻwFN…WdI%J899UPN`HJKKKѣGO:pBNNt/^\+V^*Udff&ݥE |u}ܹs݇tt… רQA5rwwwtt. +@LJJڰamrrrvvt8995nܸe˖mڴ)Ut)csέ[.11q۶mqt:WWW//m6hЀ .\W_ڵ[ꫯvСs01 HOOV^)^rrrׯ_ʕ[esCIhK%0dȐҥKKܹv9*X`׮]jԨ!bLzvZhhhxx8P N׶mI&I Q^+MvM<\vmc|(ҰaW^]:DEOؾ}N:%ðx&Mdii)ݢ + ?ܸqcذa111!0jժEFF֯__:D7vѣ0a#G2et$whA,YRre1?S.]:$ckk'"C`=z^0w\ H@W>}qrrr[ q+V(StHsNn֭['y_uzCŋ}||>,ڵtH>HMM_C.:nĉ!A+ 11K.!PϞ=L:$oibXGYYY!P5??ŋL,]w!0>>>qqqVVV!y@DD'^\f֮]kcc#'Ly̝;wС&/iiiUTyt?񉊊05K9s=Z7 0O 7otә?~„ Fԁ~'鐧sttC鐧mmmCɓ'W›A,_PB!5`DT+:գGH ZfC`ggwbŊI#ǏW^=''G: Æ #oo޺uK: &M8qt?R?tƍGIW7kѢEzztȟΜ9cmm-tiiiNNNk^zEEEt:a֭m۶̔SDDD@@tөzO4IOڵ[rt۽{woJ*?:1믿JI&6lS4HOk׮x +%KSUVݽ{tѨQ>?m6!!A);4i$]++={ԬYS:ϒݬY;vHvrr;'O*i[pa߾}+|ϟwuuztoUxbѣGϘ1C7]vJLLVërʝ>}@!OPvttT_{J@AAAٸqcV+iӦ֭[KWl{I@?YYYu=rt+}]pt8﯆ϙ3GsN׺ErJe3Ju@keʔvlFR?f h"Wڶm+]' D5-^X=w!@.\v_"P޽dJuwߍmhܸqRRNKR^X*//e+knTPٳCժUKꚒ"sF6<pر5j6kn͚5 0˗6L0aɲ k̘1cѲ gxI>^'ԩ~Rod6o,[hQ߾}.] @fffѢE322mִiS\VVV*UΜ9#ذ|.]<{n4h@i @,0tٳg <  O0y֭[eʔTہN+ :ZJt++K.q_SեK8 (pƍ"EH<Pt+WHtR۵k'e˖-Z<//_^0`^^^Sٯ8O6m̘1R?#uzRΟ?onn.|0lذO?Tݻ/YD2N:n8Ӈ "KS;חl3^vȑ#R?Э[e˖IئMKv]… ˔)#]0>,UԵkDNHOO/X鏩eԨQرc"G[YY) +*$r:wׯ? ,XSRRND ''Gy)r͛E-[nݺo}Ϟ=mllD&c…:=..SNR?prIKoFTbŘww|N۷SRRΞ={[n=hѢvvvʷ/Zҥtĉ_]QFI*;<<}<*3f͚QF>DxmҥWg*@LLL^N~zbŤNXfff_ )qJ*P<_rիO<2_RJ۷СuH$$$~zS2eĉEvrr:sf̘1ӧOԩS +J6mڮ] ]]]_goܸqʓUVE~L 00p޼y"G+/>>^hcǎM>|PW֭###˖-GUв/O>9}tޝR|#F 8P,[[n"G)R"G?ЩS+W=zGy֭[j_Ǖ,YRy־}{CGAӒ?ǽk~)w͑#G322,--NE%Yf~၁"Gxwދ/~гg>H"Jf]|yԨQK..]9::ݾ}[\/^*K)߬_KhZC/u*Tof%%%uUR_~e֭(^7D*@ٲe/]$r2<" ŋ...%{F5ydN })φ~ѣdKt:]PPPHHl `jժ"ם ۶mkڴяb.\޽{"G_|TR"GP||| ~54gg%K(°_&,##GRF{*__B +I&e˖[l9zժU]S077233 eܹC ɋlee|"Gϟ?"G?"?n޼YX1˖-{?~G}ou9""B5` HW<_nݾK +uyÚ;wAD~D~988䔻xvz7?˗Te/jCLʄ 6bux*T7x޽zԩ?Z 2d_>|!/|˖-M4LLJ~8ydg̘1rHa0|տ  +ԨQ#&&Fw֭5k={V:D?KNII)Yt`"vYFǏpPի2+r7\zt`"F9;;_rE:͛7*WtժU:t ]{}L@__uIWQq|+{ NNN\t`DDDO׹sy/^\:yKySV +/ww۷(P@:0n I Ojvҥ###C>JWԩS} +1$1Ӗgt 9sԽɑZVtðعsgݺuC#LۤI+^TjՖ,Y*C2U\0 I d4o|!vZ___ +ŋׯ_oWݥCLFHH| ]aHЃ흘("VZ{-Xt`i8|p w:0$1㒙Y^Ω… Φ1z-z+a I @G ooDCZltp]WWSNIÇW\Y:Tn֬Y#F0=zHW@?ϗ0۷o/Pt  ]qW_MII)VtqF///',3mڴ1cHWbjeeeW6oܼysիΗ/_15vSt  رcM&]aHF + ~t']aUvkkk@@P;w6iĔ9x𠕕thѢ}JW#FIW +!ciiwޚ5kJ@gΜQ~n߾-bx_4 P>}+ IiѢŷ~+bSRR/.+$6W~뭷+ c۶m\иIWhEnݾK + _1$1UQ~K:;;+Jݑ#GCcǎթS'##C:DC; 뛐 bHK,޽tY~}eIhKѢEK GxxA+ C+W~ƌ3}t +-ܴiN@P5==]:`ʖ-boo/=gˬY &]$ٍ7޻wtt///?kt,--,9$'N2et! 6l֬YO߾}-Z$]u۳gOC8pQFYYY!SjՃZ[[K@k֬i߾t~3nܸ>H[ I @ݻwk׮}IعsgݺuC4gg+WH7fff[nmҤtKWRHHرc+;~SŊmmmCmذm۶}vnk\ Q +]~,X ]$ %--!cccsʕ+K@p͚5ܹ#ԩt'yn٧O +!''y۷oӕ(Q"55t!1$17|sԩSǍ']giݺubb"aHbU)!%%E\~H9͛;HWYNNNf͒C F{{{K@uMMM.\СCUT  )S}Eu/VTe[t)"JX+vGDulDQ+*؂ HߵDckdwy~s<<w?g??;wr͖,^`BIaaa +Gz[v->իW!D@ 2,///5q999!))h4CHEgd88L&??^rB2}O7R$"0aN$-СCك/HM88Lܹs]t?ē-QLLo,ջwXt@ǏߴiHHD& N ̄C˒X"_JCDMHH;vm-[HD&пC+uVLุ8t(+WmРA^^˯.VZABE88D۵k׈#Jҥ˩SxkR˲yǣ+ڳgϐ!C?$qzyQt@ _ӻw!Q`]6:dw8:D^z8޼yK.Eݻ *=$"qFcN!J:|?E>žQ+iV^n۶kPUUT)77Wz'E8+V={6BIG駟$n>g_~-??UѮ]2eʠCJHOOϢ"tb6l[bEtɐa aaas{7e\rƌ +@ `0Ch.]Ծ}{tPPP =_o%vӦ2%{{4'''tlHD"L2eÆ +%͛7o +g +Qlmm{N{iLe˖ʕC@ . +ruuMII)[,:dP㰄 ,ZsɣG[tt(#Р+@ R֛7o{AW}.$"<~Xӽz +UzCH'OhZ5=KܹsJ7JMfœǁ  ,HD0/^D()::_~ +AWRJnnnK?/,,lӦ4k2?믿F3$"EYfڴi +% 6l׮] +Gp߸qݽ@$s%!!2%$/wMǏ_~qssGrȑ/:WVsK "@ Bh4 .CHvڥCDS`Vڗ)عsK.}2O[F$/4cƌիW+4s+V+H[ )=z^~47...y3$/q???5]XUV!33ӧOQ 6(5 4+s]d ?@ *5Zڵk:B2绺޾}"Jͳʕ+W߿CfB\t}@TjCUmw֭[7et3a„t(mڴ_~-maÆ+VD$9zh޽Jj߾K4 :d #̟?_WG駟D>>h4xt qIQV*zJۧR +LpJg@ |s]lBI?Ú5k$Ϻuq +g{=ggߛ[իWE3q q}&鍳}jo˖-322D_QIh%FEE{ddرcQ/^}uZ +@۵kQ P;|0B +*dgg7mTʕ+6hBֈ/-\pѢE +%h׮]#F@W9ftQxM0B@ 233?}Q m=z^~%00666>ydt@W6mQ ...wA(DFSN QjԨa0j׮g;unݤ'$ƍuVt,Y2w\tɳzjuߪ}AW|'OhW^CDiٲeff:џz|On޼VPPeĈ;v@WhڴiDW@ 7]o/B377"/*UmȐ!CWhΟ?߱cGtY $۷#GJڶmۨQ$̙3WZE:ʼp႟:4޾}i*7]]! @ 2%D(|YYY͚5C ՟Yh!t(ҥK۷G +q qu0`@tt4BI7o7nY|9s8q"88]!֋/t:QF999)PTT԰aJ + <}4gY*OOO5]1cDFF+Lܹs]tc=*ɔ88ڨs{5j0 kF TÆ sss&L@Wd !@ b4;w|%t:Էo_t#]]!8zmt(q21$*V9s&BI#Gܾ};IJJرeΜ9K.EWZffO!|wǏGWzp qqㆻ{AA:D1 6ɩT:dx!8;;Y-ZqOGFWJp qP)ŋ:t@<Æ BWbooj!žQ*TݴiStY rmgYXcǎ ]!,:u*޽{*wRRR2e!d88HoW~~~FT>}@(Fќ?cǎg +Qlmm!NzEj۶mzz::D^Jg֬Y+WDW(iVBW<.\ȋ`E+,í[;|}}/],H2;UVA}+555b1֯_?uTt@#*8H&>|pqqQ57]&JCH߿]!4G[n$}nN>/V$wލPҚ5kTv-#k.ݙd@ 8vX^JMHHh4ɓ'A˗/!^_B}&/aƌ*@ =Z':D1%RiUTGXA8p]! +/YFT@zuԩS%۷O:P@W<7nT1ґMMK3q q +lڴiĉ +%暖ݻ...>|@rt\|cǎF"ʰavڅ s@_~qssG(nݺzZj]viiiQԩc0TԩSׯ_(::_~ +2kHdѤm^v  tɳpBKzxxH +"JժUz}z!d88Ȣ͟?ɒ% +%M2eݺu +'33ӧOQ&MPOOϢ"t(gϞ?@ ˕뫦hB:,W:dwuu}6:D͛KˣCThŊgFWq &+Lq qsqq_!KNN@6)) B,w}wItv9|ptɳo߾!C+ CWX/^t"JFrrr*V!3@%22rر +%ѣ +ګW!wܹ.] +đ^lق 3@޽{߿G(VZAB2H]v=s :D %,,L݇'NFW@ KQ\\ܾ}tblllN<٭[7tɳ~S+ׯ5k4 27Hd) ]oڴ ]AܺuǏQBCCKwѣDZcd88"deeyyyMI&َ'=="Jݺu CժU!Vm…h}SLHdkq[[+WxzzCHy-]]!FGX;igk.-- "J +6m!0$2Ǐ߼y3BIK$OJJeԩk׮EWߜ?|˼@ 3K㹻_zwW,yyywAҢErʡCۼy-[l +B@ sTP!++oA]!I3>|pqq{.:DujժC88 -_\eg$KX*Z=zHӽ~"J``ӧyNKNNС/Aۻwtl//"tb5jSbEt`4;w|%t(5j0 <'R̚5kʕ +7h tBF#DoBHoӧOGWt>}+s}'##"Jʕz}!d:HdV&OPQ/pTj7otss+((@2bĈ;v+HaGt( B&@~-I WWTײi&77"+*U!֭[?+ZvԩSd"Hd&޼ytbʗ/ټystɣ5ͅ !T1C׮]Ϝ9ڵk:B@fbDW(iӦMǏGW++YfR^hh޽{JZ~ɓ$>^˜1c"##d +?-%%BJ@  AW(SN} 0]A\rOMA/a˖-CWxB>}"Jƍsrr! $2p(?x`}3%TٳAAAC qƍyft)L֭[nnn?~D(nݺzZjgذaQQQ +QxA*n݊ɓݻwGW88}||tb4M||?:9vX^]vԩ +Bsuus:DZjzB88̙[J/*5^Z':D__߄iC,33ӧOQxku@ ђ;t蠦\l2##\r'88FWRr C,Lcǎ#F@W@ 򜝝޽Q}ZZ:ٺuqEEE+\kNzB蘝ݤIt 5rȝ;w+jժӧ+H{ICQzyQt~Mz"J۶m˔)R@ q?.+Ԯ]X痔VZABf'""b„ +/_>k,t "=t:ݳg!9jɒ%GWbccsnݺCIqqqQ쒓=<w :,͛7?~SNDѠCq q"Μ9ӵkWYA>>>/_M-޽S!֬Y3m4t@֭2e +}/^hڧOCSBMCHM6M8]!Oq/g4.^>==]zWB?@/׷o_]{a#GDW<E {AW<~X:>~5:D --lٲ;Hm6ftzq1tɣPPN˜R7dt@g^l} ߠ!U^G=).\bnt$<E\xC88ԌFcǎ/_Qɓ'w y233tx7:͛7:ÇQ6lSR%t5$*%K̟?]qm޼]A绺޾}"JͥS|yt[8#Gܾ};NVVwQQ:D17qttD<'NܴiB[[6mڠCH&O/:to߾ + HT +ׯ_G(F:̺r劧':䉏 hѢ +Hz^B5 Cڵ!T~pB]A f77;;;t\vv~[B``ӧyls@rg<̲P FWR|f͚C*,]t޼y +6o>BgooO?###+ڻw/uٳgߧAqq~J-ىZr%u@sEEE!m۶w!: +/BڼysPPuSSST*>|HŠcvvvVCmmm_~M +p\o@0O9rDb=z p_љ>}zjj*u+FFF}K[lYp!uCׯ_|9u.RTݻS?~~~ رc?ٳ!Q JLSSѣ/_L"CyzzRW?UUU})u+ΝëR~g_~aܼ0 JLll,w|||+n5*##:;.]P#ǎ`$:Ybg\.tFBK.N+:x3+t% keeu])J'77wذaRp'O{ t,ڌ OףG__ߤ$ +!,^[CX177/--ūR vW^tFK@ wq!Cdffr' `׮]ZZZR@銋 8 J:Nzw166VTxVtN<9i$ +6lذl2 +aHNb/..2 JL7x߿_|A]TWWsCѣG!8;;_rUV! + +ZnM"Y0tٖ-[.\H]!)S>|x:u#G+Xi۶myyy޽CC]ʕ++$ κ~ׯC{Ԯ]+ڽ{?u3f+X/_6lu4aPMEEE!dgϞuss~Ջ/CX4iǩ+XyR駟CXٳgEE% &5CBBbcc+#F\r:N:jc,fϰ3k֬;wRWH% 7tP)kܼА:Y~}hh(uC2e +us .ܲe uCSN J7iCKh;v젮￧acǎjK.!@ @H^Kׯ_|9uS__RCXٳgeeevC4_aܹs=0(a'NxxxPW W%KPW+nZvի+  J!E*''gRJ3֭д&,VK"0tĉO:E]!{RW?/^P*ݣaʪwt[˗!QH% ?w\ +!yxx;vxKLL`E__‚:LBB'ÝJN@ @HW2;wVC} +ŋSW:u#G+XQ(999= J8dȐdӧOS?FTJS(phr:Xuu5#Vz]^^޶m[q yk֬҂ }0aܨ+X166裏Cɓ''MD]~? JVZZ@"~qPmڴ~)f+ٳwI] +^~0$6667nܠBupp~)ft?ލjuC +͛7oB ~$ip|$2D70*--͍ L@ٸH2ԩSƍRaaaQQQ %&&PW% IQ*>Qyyy>}CڠA~WV͛m6 +\TTDŠqEEwJC">0$i驩BJHH~j5u+zĥ666uuu! <833/P@ @zv=k, +!M8ĉۢE+XQ(ك ͛7SW0_/]Bd0(aH ŋ!ԩZ*tҘ1cCXYz5Տ.vƍw9V ---Cp'[#Fr +uN<9a +J!nݚ:@4~g/Pbnn^ZZj``@"0dݺuVR```||..Nf`{߾}#FP% ix捃.P(Cɿ?rHL&ϟ[ZZ޻w:333JejjJ"0!88xB +~Ϟ=KŠIeee=CD,;;E·JNN Jp1cƐ- Ʀ@OO:ٺukPPuCϩ+Doɒ%111 :tӓBaPgϞ)ʟ~:D0mڴ)++OC۷o[YYz:ɓ'9r@ +T*u+&&&?]ݩC% 1cw}G]!m۶͛7ill}ݝ:% +kk[nQF__Ғ:Q*>afO:]T:PK]!%KPWo)))Ӈ:R^^СCzx3}ѣG+`PBǏB2dHFF~*:III ᖜjժuQW0l+4 XXXpqeeG}DTUU)ʧORz9LFIJkF7PbP*Ä Μ9C"d/// +৩iԨQ!tQVw҅:|Vٶmۂ +4y#GPWo111FJJʴiӨ+EQW0k=n߾mee+ D~oC]pLƍ;wu+EEEJ:DC0(ahRF&9sfر!OCCSII u+fff.555~g 'OP2`#7%###+G]IM._ti!BG2e +uCK.믩+46澓SCCu`_ZZjhhHHzۺ@¼O]\.x񢋋 us0ܼy:D0 +;~$MsssnksRy}Vt䝊0&$$PWiݺu[@@]+X/,,dgg>:D +0(a:;\SW2'ON4 6,[ÝRW0t!OOO +0(aV*=QEEE޽C )})6] + =*:{!`P 4uT$k۴i>L]J۶m˱Kիvvvo޼ae.\d!L`PH]!I&?~xf۬Y+. 6X-[,X J$ܹceeKtYRQ}-Aq߃ܗ/CX.#GfffRb``PRR2`aPgϞڵ Q*>aW^R:aPФ H2{;Xt)w`E._|yذa!QiiinnngVSWƤ]^^u𓓓3|oRn: + 0o'ON0B04Cz/Mۗ:yⅥݻwCX*,,2x͍7CX0(ah>/??D +VMdJKKtf&Nx +`P]ٳR9q?3<<<+Yx1uXf uC;w1(av]KKK)icǎjK.!OuuǏCX2dHff\.b:##>}P JLI婩SN&Lpi +V+++?# +o߶~%u+NNNYYYZi9 JL_>44BHHرcΜ9 %%%y{{SWپ}y+Zr%uEaP`b+u`ps%s玕q1 +:~SPS% FIZM"\1tP৩%++:Ν;shC@HO/--544i JlܸBHaaa[TTߎLvԩqQς+Z .]3fLSSu`lllqs%)//4hއܹs}Ox3gܳgu+2̙3cǎ ={fiiy}!އL^***xZ++[nQҵkWJաC~0(a.77wĉO<͛+7i_CPdee9::R >۷!L̜9sƍm% =zp=… qŋRoT?9@BCCׯ_O]!N:g!-@ νBBB^~Mrݻws玏Onn.uૡٹ:D0wڵ+uH aP`ڵk^^^eee!-wM>WcccLLLxx4> @p?mmmCޗW_},1(aƝ{{롯޽{+@0I7CW\\cDiƍ!!!!99o߾! fp^"J255!EDDDGG#GLOO/w~yꐖP(ikddu0(ah˗/,Ycꐿ'/]4|p`ŋUUU!TVVу:DJRtիWRR3u`0(ahѣG̙SSSC.˗/ޥ=<((h߾}!8p?;rԩS+x޶m[۶mC@ @Ns ())100RSSΝ+_M:::00:! J~@ի[5tЌ \N]]ZZJ'6oL]qFzM% mPTTĝ{ݼyWׯ7KڴiCT__oooVCCOOoʕqUV-aPuuu+Vؼy3՗ц9;B3p@ + 7J777߷o5u`P*.\𡆟wʔ)"m[~}hh(aL&={7|SvbP6Qĉ{nݺT*\_IKK9slmmqKfMMM#Fr +ɳw=11Ņ aPNH\.OOO|ٳg4mڴ)++O4++/^hyM}v% u]l)@RSS̙SM>֭[ϟggϞ3gj錍l奱g60۷opFN277/))144d %Ӎ=… 2L3OӧjF 333 <stooׯ >,Ho y5'211QTݻwg,TSST*^`͚5K.]w0(aB]]]DDDtt7,ܰaòe˄z4?7G=šC<===>;1:=طo1X<`PtA2xL]!**jڵo߾Xn޼y۷o1ryPPPttt֭}d .\ظ/P袂Q֭Z6551z͍7z@nbbСCz@i FyU0ĝ :i&AM&={MGx P[nm׮?`P{%}ѣGY$n:̙33s"-h5kDDD#tiǎB%I % x6mڴ|tURuЁiǏ_ZZ+hll*A x߿QP۳h۷o[YYzehhW_f&@ @޼yf͚HhPP͛5V:eLZr%$+Crrr߾}%I% ɸt钟_UU՟+--mӦ@}gcvv6H ډ;M0̙3W* +222ROOOa@ @J?p?^F;*:88TzWEEŻ2##>}h +3%Rw5zJJJrvvX`PԹs>y?vUV&'  d@ 8q.#fϞkdd* I?oFF;99eee-@"//\]]ϝ;(ݻٟΠv9~x" UܗVBBBXXXnn.b^xt;vرZҥ U/^ufʔ)۷ofU@ @ڸV۶m+u9sL66 aÆ}}x{mfϞ}I??={PbŊ줤?E0(apG<1u o Z!R@ 4 h% 0 Ja0@0(aaP @ 4 h% 0 Ja0@0(aaP @ 4 h% 0 Ja0@0(aaP @ 4 h% 0 J}Eu \;-(n̵>F'؏X{vPX (s8ZC.g@0@0@0@0@0@0@0@0@0@0@0@0@0@0@0@0@0@0@0@0@0@0@0@0@0@0@0$+++ooo)MDIIIRFC@r(GA@r(GA@r(GA@r(GA@r(GA@r(GA@r(GA@r(GA@r(GA@r(Gd77sIi$Zjw)M# R׿o4                                                                    @9D{ݿٹo߾[G 襥p?&&gRRwA Ȕ߹s'99c@0&%4! (O?\2##` +9rӦMEC@P@ +@ +@ +@ +@ +@ +@ +@ +@n޽{GofBBBrrrJJʛ7o233gZZڛB| TTB +UVRJŊmmm?9;;j~8::J7T + K(D}Oz'Ʀ~ 6.ԨQ#)=@@8_~=&&~OMڵkҥZ@@fĈ۷o(TBvcРA>eP//ٽg̘1VVV| ("NNNÆ Cv__033޽9sZj%/k(ҹsiӦuMvG@ 1Ѱa9s @mI6met6*h4h 88]v;60{nO@P@dwAP@rL +۰aÈ#dw> +@ms0)\.`ƍÇ(ڔ`R=֭[(#P +A0);wewJBP@ +*9rsβ; qppvZZdw ײeWPAvG (ֶ?mll̼gK?To.|@mx_cSӧO/]tcǎq_:uܹsBLsx(Ç[n]~˗/Umlذaj6e(hXlٝ;wkɓ'UTQ ڔAӧiˎV5kL0A#)@ݻw[dI~~>mڴt)@ HOO[ӧ - zAP@#$::իW|.[lڴi|k^@@89߻w8lժ˗9}!(zɓ's,hiiammͱ&@m)Ж֭[Ǐsy͛s,gϞ%%%St,D^=^:mVVV ::?}vZ*NNN..._U&.+++''=ݻw 1K$t ŋM淚FE{-ut&֖v=e@@ƘI^^^yyy +[nرEH._LZ335jԪUI&>>>;vӹv3g~q?͛7Ғv4HF7nܰaCjP@S?PRRRvv6ahV^No|AG/TNÿ@vNhk9w܃bccuRJ4h hiX.ÈrLT {[_€cӦM[jղe뛛ko@m,:44WѣGoذW5.?~gϞSN?''KM$ЧO/ԔKYhЦ{тN*uB=sR(rjG ^3{,,,(y/N<^ei,۫PJKKwcbb藛7o({+yzz6+D#<988poẼ߿ٳ\jaE={RӐĉOYJFBa޺vڕzBP@ 4ر#jt0pj,GCU)77}Rj۶$0eʔUVסW>ym/~^z4S?ZǟXcRR.vϝ;wҮ^+SL>KVTS]xܹsqqq;[}*T߇\T;vՓRծ]{4T|NjzCBBB?A E' :s1@>3:q)Eq#:gNނw%OOϙ3g:T'Nt҅K7o6nܘK));SzK4bvvvfӲe+W 4 +Ypyh۽{U,???::4⧘D[ʕ+GFF6h@XYYY7nj?sĈ3f̨QFjժ05/Pݺu'LVX@m2~L#`WE@GVƍy.77Zj:޽^GwÇ{>&44믿fC~ + + +vEݻw5Zok֬{ձcÏ?&;377{222V\I^N"g + +v=3g1D@C'RF6FN>˫xUÇ'MwF#jբCI߾}W_рN``Ν;(//^YtQ‚ wcx˗/jՊ.^JEdd洨Z%KF-.^q4hΝ;jBYq۶m4V0y{{oܸL|wT&j; +Qt\MLLd hmx.]x\ask׮[~.W]]]E~x4ܩS3g-Zvc: +xʂ>3fP~B)o˖-r' YHH?6oUpɓoܸj+zdڴiƍcv$ZXJEc2q)I?~GtTBs}v\J={ÃKOZbnŊis@VV=UAm߾K7|c7T^}ǎr'S|?a&v޽ +HNN2 `4j(::K):8K)-h7YtY UB٣n#:۶m&//o;0773gb333vN{O:Fq^F\ҳgϔ@0 :tk˗O:K6l؁Tm/777hB&&Ozj:e^G.../^PrN8c>f޼yO=(_ŋUeJ* n++ 5&8Xv@a,,,hp6i$)),,lРAP`zL6mٲe\Jɓ'ۇׄJ*Ve.3ɔ]ve =z _vū?Z|'Od,B/**K>DZ9%@F<.27o~UeO#WUgnԨQt4ف3'Nϗ  A>}x]PWy??u4Fq:;::_233KII0MFRA#t7!sΛ7KJb=M...aaa~3VB j|#Gɓ'}bӮ]}U^]vG׏C@-@@rڵnݺ= +QFhx:bTM\\\͚5cnܸѬY3:[fS±cWRJ/_\ #8/^̱` 64r:p5JhMm! HP~.OtR.;{lϞ=ue.\Hae׮];a:fhc";w ҟS *MzۗѿF˖-#""̎jR@tt4t|iӦ&z_"$$de}! }s)Ee::s)UѣGZz5߲r~-ߚ&ر#%.:ZZ}\}ւݻ 8pǎ\S$11 6gʕbn([RJ&>rH^uzC^ڸqcM9sC]  ڻwlllx-}/޽{kkk/r8.+VHqʊKJwހ:ϥ]͚5!!!CoRNSP)h{^kYhQ@vxݜy֭͗"5*FÐW @0޽+ 6=W޽{*hP<<z((5~'z +w1+"QnݺYl +lْk%}||жmدsɑc FW͓Q Xz:t.*Uh^?ܩ)~͍t6665QG>s"&hOJJb9( +W@#Dȑ) =4^N!M4a]*UpwwYe\\ܳgbcc+Tp*e2P:ӡ>P.]*ךe:ҠAO.]ԦM.iժJ+țݻS 7Ew޿_>̞={jTlmmͬ\r*U(ѧAjժ^bUhӭ[7OOOpKݻw۷~5fp7ٳ:t^{J իHRjjB޽ exY8.DϺu블yNlٲeƍKkt}add$ =5צM^| +;`q3f8W, Rc}P::;;3iiE^N@a@={^:--cO(Ӑk7P@kԨBgq I}x/ } +N:ҽ<,E>D>}=⢎/H}۶m!!!|GkT}ɓ/UI [nUݽ(C&''߾} +ѹI.  ߛ۷oy:tڣ}6ZjztPŋc\J=}F!uhlҍ-Z;u':qr,sN;iTMuu딆n{eC(mݺ@тWzӮ];# GFF^,kRWZn U;w7o;v,Gb6m4|p{]Æ UmתUk|󍂩 h4җ! MgX^ᇅ 2]s7 Pg͚r1賣#[iFw.>s,2Q1z>ڱdr,1z72ٲe N.߿?ߑV\XɓF5=۷UKPa嬬3g;vv@___u޼yCǁ/^XŊ̙3}toVZ5{l^>VK(9s&1g[ys>! hıٳg;vX$44u۶m:uR}:ǻ'N~Z˗/F#5{$'4TRƲ #lB1Ypp1cT3)|JsSCu4 ?w +5'''N4jԈWAa4uۺu!C*vzQ[A{5[FAAZg͚ű! o߾skccZB"tkҤ giG;ǚo޼ <|0ǚ+W hK=z{unܸA{&O}^;;;f8")3g7oT+ :Bİ޻wˋT $y\Q=y$lj +4?ާO.VթS6)kqLJN|PDM6㏴WC @SN}WhՋEL^Nǚ޽A9֜6mڲe ΤaÆw[\~iӦ ytȑExMѶm[:&OڵF#))M6Ϟ=c/5|pPNٳԨ]6/jժťZ z:Thhh~ױcG.O ܲeXL/2A @uW\>eppq4jԈOOO\]]y,2@@@Dž+Ws;;;Rݺu;vcJ|a2N.@ }v"&۷/1~QFq)UӧO666({jj*{):"ݸq}--M|r:={{9ۍ7XݻwB #F? #G 43VVV NS™3gO.!///^'{ൾ5kp8pcA777ڤY*|:up|~3+@Zh^GcܸqׯU8^7ӧY^=" صk{gx-𞑑>)v}ցhRx[OLt:x H ^eL!(M ^sȑnݺT+g:\JtU2(6===[ 6RFtc,yfm߼yCd9HxxW_}^G4!cKKK)"] z^ёb07_ .KiҥK\wrۗW5] (M 4>s֭[CCC!JJwEΝqIM=zTR%.t{i.xMe2P: =z3& ֬YKRYf„ \Jo߾},xMJhc-۷nnn^bq FO4cSN1.99KhΛu[Pڔ@[QDDĮ]CDҠ=E7n|mqꇮ\Һuk.*VJ?pYr4c\G#$$ԓZA/_d)ҪU˗/w@D9R2qゃGømUT)66D:uzt655esƍf͚1Yp?X$??ٙ1HC+kiz1 6u]@øKڵk^gĉ˗O6̙3-ZX#@^^^111/ (ЙVQhwj]ٳ' Ȇ Ǝ˥?\nV͛sمyye +ccchAںu2 'gϞa,Bh[bAoooi+.jw1cҥKP~ F.]XCΝc,n:^W6m4|p.6Qt7#???##~{= iğsݻwa{I:\pQƳgϸܯ }\ސZj=}ų3)ӐB5,Fѽ{w_~3:uʕ+x.>>^02d+bN:>d,qj}լYNm,*Vkoe.GGG爜 +ơRJ7odKʬ,. Cwjp͛ի' "00pΝ,(i;88Pf)2cƌŋT`NZly(yf˟˖-c3yu?[Eh_>KJˉ0) \8S@mfdd$R?.l޺Rf~1cưd۷oRE5\\\\Mׯ_S0f}sa,ÇԩXd u;h :7nhҤ {VX>;tP=<..Ã,@@Eʕ+N^[nGaÈm&Gp.oAb޼ysUrМ-7'4\f:~Raѣݻwg,Bn޼ٸqc: + K.dz?Iŋ/40?kQVT@m4$zu[[[.(KpYlÆ Gf\̩W޽{$''רQB2u׮]㸜vV4iwԉ!B[\V;2MFFIHߕw eytXf,k.]e<)x % JҀ2 + h`R8ӧo/eҤ$ggg:x!mjjjooޥv1.iiiIR/X%vvv)@ݻwW|U{e/൐3(FDDwF6mDFFTuMX@VVVժUHa̤pzGGG"}aYŏ˚&^ `%}4jQ{^pѫWp:N!˖- + +b,²ŽBgqQm3.C)%%i›h<,2~W",}:MxL(xn{ek("4x捾U< gڵA4 RAS4a˗/5sN X)~odY{J,FF6m< )`̘1t `\]T .={6{:%ZI~;v`C~ 0QΈFi?_z`&MܸqCW=xqKKK:~VZFXXX޽K.q)x5׌iii^ժU+>&P1)_~J{.(_re˖uζaeY{ .WOKhXv-f7A&&&DULo[uڕq "ӱLehd;֥K.5(Fnᵒ#b':!(My ݺu[|9zZ;,,NBBy^˗/Ԯ]}A}%W1ܹӰaC}:pf*nWm6x`^B[; 4U$88xܸq,̚5kѢE\J={p)eQFѲ{a(qF.sI :b + hS~ hݺ+W}ښ/\sɸL0AsY]1[[[J5DDԊ?ݗ?lذ-[srrmi\\(yf.KHc NNN))){a(O ԃٓb + hS7Ν;zh h~111EhH;.Yf +nCP^^9{r)???"/^l۶_d%n|3foW811Q;8y?EY ׯ\FtX&ssseP<~>h/9s%K;@:!(qz'OL㭊+l466#6|ٳu222/UiVxyw\Rb){*ۺ}4ee*hyG2&W2/zMYqƍ&M(x!5kְw@3i + hcQFAAArAk-0:t۶m,έ쉺++[F,]t:u>x@&~:uX*סC .VщuY# {a@Ξ=lYѣGoܸ2b + hcdpљ]vPbŜ"`nݺť? 2du'O0y GTT({ cǎݰa^5i$ex׮]=r G %n B +vD":lj9:i`6A. `޽u_Q hc.)I&+'ׯUPP@ꤤ$ +~r$ 1c$[ӷfq`BƌTKɓ'}}}2JiVv/ aX\b + hɓW^^<,]*cǎgϞeE͚5?pddo>h -Aw߿[nǎSPSrtX +,o> qIc \VY|dnn޸qce=tPϞ=0|p.+6-[,((Ax< ,KOHh3g.YDjtNNNvttה=1򱷑}Y4>`.K+c \-e͛76m^C\&P @m۱c,ZƝuP쇦OtR:jѢŵkX*|l:u<|PR:u:s'ٳ,g֫W/ +s>}8q00z7סEk=|!( w쳰kX<*?ܹsXbʔ)uJ>И1c>\uշ˧NɿFRo"2d{[[d=Wq oE04C:Q4XG{iZ(6ܥV^}C-pt?_)U||;䔘Xbŋz.Soݟ?odyyy,G|77oVrcժUcCyu[6μy\(<==L@Ȑ!C(6juh2 kjjJ@Uu/GΝСcͺui ֫I)x--ڑiwַNq޽;KxXJ*iiiܗ(5ܻwAu T),2j(y!(  uV:7n9r${FoS)uXj-F%MNNQFAA^Ethʖݽ~z&Mfu4(QTq~۷εkך5k^8kϷw999ц]<*748q"{$YOT#( ݠ {=zσn֬Y-b3tА:ZMzq򸸸ႲY/\Ю];ik}[;wyv,G\ԂךDjXiӦMdd${ ^WpLԜ$~D@(w_޼ys:։3I֩SѣG֬Y3a:)[^W}|ݻwײnGevҫҨQϢ?Pgh;#3#4\3 {ŋϘ1N{;;o߲ݻu@@@ t^zzz:{);'t¥T[VTvZƘ1k֬DÆ ۲e^/?e^Q4/4MժUKJJP\ANNN,h>yv\zUqضmСCԫWOjƤm۶uh׋l>~@@(^ÃyիWxx8{ggDwx-??_qOOOͬ4*0`/W0`ff&% SZjҤI&˦;vݺu_;J7od3{ 1FʥԥK,m4͛O?q)%NR!( J6m~q)4hЀe0]D)&K=}4KMÃޱc^/ze*Umk׮ǏU>>>&$$ԬYH{َ;*~M|r:+W~!̑% @7muʨk׮hтK) [nկ_K5!( J^xQF .?jKkРA;wRr)I[loY*,X ((H_~q< V899y,{;l` &L`hwOyڵ\J;v݌e5:wxEAP]I9=߿ۛsss:::Effsvv +͚5[hb׭[7vX-*sO8Q~a…_,wޱB.1_\2۷/REGG˝SAÇs={UM + h]zyyݹsҒK5yׄm۶eG_]*rNbbF;w|}_w9!ݻwE~Oɓ\Jծ]͛\Q]\\`c"FDC#w 6ꡱ m߿RMee\_yxO>W_lѤpo:u+[sQӦMy}O# +fxUڵkxx2&On۶W515k*(CEAPˣG*W̥v϶oҥK\?RJ\(//-99Yd!˗)S9v铖/_>uT-{Օ +N8qռ9FΞ=ë}df%iDȫիW ̙3;wUּ +vXQzΤpe˖;W<3]iQw'O;de:"yyy=~W*U\pApCXX@P@i&::W.]8p@ }ojjOOO^uժU+aq5j6cǎ kݻw9E٘BetRK_|Ņc2_~XNF!!!6AP%tm*TXSSbXP/u,%@pp0iii\&m|~g1dѨrŰa84|.\СߚW^-[ -??A\ֲ)ҷoߝ;wZYYqI)W5MMM7oα^ϟ?w\ qGGGaI666/^5\tw|gt,2i{5C0)\YGN)҈6s777 taPPoGP@o>d5۴i+7oތ7ɥZ>}Y_jp2NHH7|åvMnݺU@C⬾k֬[ŵKKK ߳gϱc( YJS:Rqliiw͟?_|Ϟ=۽{͛ + +|]y-ZqTlٲm>sMA@@:7nΝ;|˺Yܺuk|0)MOڵk׎tFZpG###W999\i!}aTL4RW娙3g֮][&3g{ɢ,TN̊+T*N;է$ ;|:ҦBCEoԸC+J*M8ۈ6bP_4K˖-ӧ۾};~KPceo3F&8dzƫZ(= +AE;ښ^hPEo!Cu9BsΝ={~},Yķ ]&ik>>\TՁ4+ܬY/>>^[ WE6lxm-r_!;wx쎨ܟcdɒ3gQ_︗Φ.888P[ŤԩЄ9q℟M`"uP+ihĉŏeA ͛'&&7n9r^"99f͚E3?ܵk&MZj֬YC} V^M,U(gϞbl1??wުNe8C@"""|||zw}w!a<ȷJh߿o߾޺uK 3tm۶Ocǎ]nM@^zղeKA c_E_mڴ{vC J2b5&G>:Gtfݻ7ߚ*윞αyiaȑ[ln:tp9U(/ҘF/1cv>}J۪Q>S`P::2})޽[幻+T^/LzjZj)))6QIݺu>ĬQݻ ~ +Y0>Lݷor3|ڬY-ZīZNΜ9ëZ:ԳgO^ժT@?yTUcz)pխ8fVׯϟ?/jC 9}t@@P0eʔ˗=9/qqqkuەhիW\M0a͚5\J @i:ucΝСzL +g9r F#jNNСCCCCeu@U! '{ca|^^J^߯X,/JJxEPPжmۤ=h b;v޽; `'OA;R +.Zhذa;„ fb`nnsggg^]n̘1KZj=~X +||rt)s3纹ǫWT׮]Rp)2Q]PP7}["chm6:jY!ClݺUv/z'CCCicT. Ѓ`ŋxBb7h8w\ ԫoeYmJ֞ꚜUT+n޼r;v>>>cƌݻ7D5]AAÇiFQv_=tCm׮쾔.'''$$d=899Pϯk׮fn(@&- \VVڵk.]k.Hݙ77n4kLkmG _UkLb +1111˖-۷o_FF/7i҄FrC s.hB1`ժUgϞ5hccӱcGSPݝO@FTzuR:tԩSF$ά eBNNɓ'nJG^SիWoذatN*Ou|ƹs]/Gj׮ͻG"//{ٻwoBB>ЛE ___::88l@/^:}txxd=V^R%J }ܿtJ)1oܸ1g˖-ԩc ȄERRehUfkk^KKK w.\w`4mF<^,vvv_}kaaλ;ޢCѮqڵlUsssA? 5(NV3)ƆK +BNx{TZZZAZرc-T+Ld +TGvêGшiZ_2!ͥB7nP stZZ*~Gb)htirګgݺu5P$###PddӧOXbvpp-6`4ߘFZ矚wʕ+wG#{1?)@&0ntzJJJٛ7o2 /4wtt/t-+P#P $11%gVV)4Ȱ133/Κ Trr2m*ϟ?_4 m'/_\+ +*FD "0C۸8ziKMMIOOΦ_ͤ]bED;=Ommm %le Ȅ!Ȅ!Ȅ!Ȅ!Ȅ!Ȅ!Ȅ!Ȅ!Ȅ!Ȅ!Ȅ!Ȅ!Ȅ!Ȅ!Ȅ!Ȅ!Ȅ!Ȅ!Ȅ!Ȅ!Ȅ!Ȅ!Ȅ!Ȕ$igϞIi$;w4|SJiZC~xkԨ ihƌK.ڵk{)Mk666RVZJJ@)SZJJ6l=z5| )MW\933SJ ッ4e˖aÆIiZC~ (Hi:77JJ K``ݻ4k׮HiZ Mvv4 >}ZJӧ5 "&&&Ji:::[J K&Mnݺ%3gtIJ6lx)M߿wRY_z%i6h@J|||Ξ=+%KIi̴zbbM $|סR9rƍ4 Rܾ}qƲZɩPM $7nRn߾4 RܹsРAR6i "o_*{⅔@Yf-ZHJuԹD odKGGGY`z +t׮]=*".\СCY8p_V ~Ji_4] @||֧Lb +YH=ҥKO.u *U͕zƍo޼)i?1b u ~111R633?ʃرCVnjԨ5 % 8p׮]ZzAAA,--߼ycee%".\8{lY+WnZVѲZ/b(СC={պcbb4:uʕ+e>xm۶j8ꫯ$vTWfM.^xƌZ/b(pQ^Yc;wjvȑ=zH'$v@À@>}8 ukkkvvv:;댹yZZZ*Udue˖I͛ddddgg@ӦM_. (DDDmVbZh%׏7Nb&Nzj(b@ 77jժ999pԩ/;׭[{K@ +sgΜSn֭Æ SSDggg}(bX`ҥҧF/xnÆ ޽+{ 8.U#,2P +Ԕ*oh2)X6Y2@HT25HMPS1F&b ;Q +=z0>z~sv.hPQQ~= /r~~l%77744Taƌ͓mJ[@qww?t`hܶm[޽`ۧO۷fl޼900P* tنK2 C ǎstt͸Js@ megg+h'O>-1nܸ˗6\Osl6?C%ѵkJ `Ҥ+ZhWin(111˖-ho,ZH-QVV6`"|Q'''ٌiq 6LIym6VǏŸ`6]]]?.c֭;v@3̘1#))IN ھo.^X+WJW~!C444H֭CC`Ϟ=ǐ=zȑ#!͞=ߗFX\\,]qd*++X,!C_^:"xwwwiwdeeEGGKW\hٴi,X ]qСC +pG}ĉ!W 6lڵy:qDkVZ5zhFhw(f͚7ot5#"]憇k<<1cIW(!!Am ScǎN:UUUuY:l`ڵbH(:::55I:@wcbbftȍ̙3w\[DFFXB!:`ӧkUUU?߽Cᑗ#`Ξ=rJblc(qqq3Liii!laaai?w:H܆ ^zHThhhjj/`ϟ?/rSQQQg3@INNNHH=zddd 8P:~SLϗgyL- g޽!K/}gnnn!~ҥfs`ظqcPPtHP֮];rHk߾}BB{צM/L4L:"##+銦Wsss+? %UUU|AVVMT;w{]J4 #Gx{{:uJ:z[oEDD888Hhځ>/RO[*JFFFlltE^k۶t TTT,\0''A~z  69T{-lݺuS122K@^MMMnnnvvMl\j2{)<69?w'Oi'|244TݻKk ?:;wN:&L ]l:q3ftqpp + + +~}Y'''"ĉ?Caau;&sGFj*銖DGGgeeIWXGtuu.Sf~۱cGII?{n"eꨦ^T.]CZ¶ٳg~KX_ǎ=<)Sku=sk׮ԩt]a@IOO8q")BBBڵk'rPV\e6Ci#Goڴi#rb(W &888H]zʎ;F_I@[ ٳΝ+r/h(6Z!Њ6m,[,""B:PN>=v^:v횗 rn(%GӧO@zַo_uwss8.+((7nӧC ..nɒ%NNN!~RYYV^^.{CK.7nt ]… sINN@zЯ_1z_>**;18yd~z +?>_tW^_|EPPt<[W_=ttqҤIIII&IE7:w\bb"W??}Jhq-[C<@bbbllt0nJ3 n@Snzҥt1n699yѢEgΜn QF-XSEMrO?tm4hPRRR~C jP;wNW 2dܹ!l555<8 @h>|̙3/bK-t…O>d׮]-r}N:M0֭[rrr^֧OHb 먩Q`奥#xyyI<>|8??o-))...Ç qttα ek֬),,ܴiS]]t6l:_cu6lPK`ƍ;wX,E8x`u֭t=cS555[l))))..޾}Sd899yyy 0 @{!] I޹sgyyyEEž}> +/Length 2967 +/Filter /FlateDecode +>> +stream +x?#j]0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L& a0@ L&== +endstream +endobj +2 0 obj +<< +/ProcSet [/PDF /Text /ImageB /ImageC /ImageI] +/Font << +/F1 7 0 R +/F2 9 0 R +>> +/XObject << +/I0 10 0 R +>> +>> +endobj +319 0 obj +<> endobj +320 0 obj +<> endobj +321 0 obj +<> endobj +322 0 obj +<> endobj +323 0 obj +<> endobj +324 0 obj +<> endobj +325 0 obj +<> endobj +326 0 obj +<> endobj +327 0 obj +<> endobj +328 0 obj +<> endobj +329 0 obj +<> endobj +330 0 obj +<> endobj +331 0 obj +<> endobj +332 0 obj +<> endobj +333 0 obj +<> endobj +334 0 obj +<> endobj +335 0 obj +<> endobj +336 0 obj +<> endobj +337 0 obj +<> endobj +338 0 obj +<> endobj +339 0 obj +<> endobj +340 0 obj +<> endobj +341 0 obj +<> endobj +342 0 obj +<> endobj +343 0 obj +<> endobj +344 0 obj +<> endobj +345 0 obj +<> endobj +346 0 obj +<> endobj +347 0 obj +<> endobj +348 0 obj +<> endobj +349 0 obj +<> endobj +350 0 obj +<> endobj +351 0 obj +<> endobj +352 0 obj +<> endobj +353 0 obj +<> endobj +354 0 obj +<> endobj +355 0 obj +<> endobj +356 0 obj +<> endobj +357 0 obj +<> endobj +358 0 obj +<> endobj +359 0 obj +<> endobj +360 0 obj +<> endobj +361 0 obj +<> endobj +362 0 obj +<> endobj +363 0 obj +<> endobj +364 0 obj +<> endobj +365 0 obj +<> endobj +366 0 obj +<> endobj +367 0 obj +<> endobj +368 0 obj +<> endobj +369 0 obj +<> endobj +370 0 obj +<> endobj +371 0 obj +<> endobj +372 0 obj +<> endobj +373 0 obj +<> endobj +374 0 obj +<> endobj +375 0 obj +<> endobj +376 0 obj +<> endobj +377 0 obj +<> endobj +378 0 obj +<> endobj +379 0 obj +<> endobj +380 0 obj +<> endobj +381 0 obj +<> endobj +382 0 obj +<> endobj +383 0 obj +<> endobj +384 0 obj +<> endobj +385 0 obj +<> endobj +386 0 obj +<> endobj +387 0 obj +<> endobj +388 0 obj +<> endobj +389 0 obj +<> endobj +390 0 obj +<> endobj +391 0 obj +<> endobj +392 0 obj +<> endobj +393 0 obj +<> endobj +394 0 obj +<> endobj +395 0 obj +<> endobj +396 0 obj +<> endobj +397 0 obj +<> endobj +398 0 obj +<> endobj +399 0 obj +<> endobj +400 0 obj +<> endobj +401 0 obj +<> endobj +402 0 obj +<> endobj +403 0 obj +<> endobj +404 0 obj +<> endobj +405 0 obj +<> endobj +406 0 obj +<> endobj +407 0 obj +<> endobj +408 0 obj +<> endobj +409 0 obj +<> endobj +410 0 obj +<> endobj +411 0 obj +<> endobj +412 0 obj +<> endobj +413 0 obj +<> endobj +414 0 obj +<> endobj +415 0 obj +<> endobj +416 0 obj +<> endobj +417 0 obj +<> endobj +418 0 obj +<> endobj +419 0 obj +<> endobj +420 0 obj +<> endobj +421 0 obj +<> endobj +422 0 obj +<> endobj +423 0 obj +<> endobj +424 0 obj +<> endobj +425 0 obj +<> endobj +426 0 obj +<> endobj +427 0 obj +<> endobj +428 0 obj +<> endobj +429 0 obj +<> endobj +430 0 obj +<> endobj +431 0 obj +<> endobj +432 0 obj +<> endobj +433 0 obj +<> endobj +434 0 obj +<> endobj +435 0 obj +<> endobj +436 0 obj +<> endobj +437 0 obj +<> endobj +438 0 obj +<> endobj +439 0 obj +<> endobj +440 0 obj +<> endobj +441 0 obj +<> endobj +442 0 obj +<> endobj +443 0 obj +<> endobj +444 0 obj +<> endobj +445 0 obj +<> endobj +446 0 obj +<> endobj +447 0 obj +<> endobj +448 0 obj +<> endobj +449 0 obj +<> endobj +450 0 obj +<> endobj +451 0 obj +<> endobj +452 0 obj +<> endobj +453 0 obj +<> endobj +454 0 obj +<> endobj +455 0 obj +<> endobj +456 0 obj +<> endobj +457 0 obj +<> endobj +458 0 obj +<> endobj +459 0 obj +<> endobj +460 0 obj +<> endobj +461 0 obj +<> endobj +462 0 obj +<> endobj +463 0 obj +<> endobj +464 0 obj +<> endobj +465 0 obj +<> endobj +466 0 obj +<> endobj +467 0 obj +<> endobj +468 0 obj +<> endobj +469 0 obj +<> endobj +470 0 obj +<> endobj +471 0 obj +<> endobj +472 0 obj +<> endobj +473 0 obj +<> endobj +474 0 obj +<> endobj +475 0 obj +<> endobj +476 0 obj +<> endobj +477 0 obj +<> endobj +478 0 obj +<> endobj +479 0 obj +<> endobj +480 0 obj +<> endobj +481 0 obj +<> endobj +482 0 obj +<> endobj +483 0 obj +<> endobj +484 0 obj +<> endobj +485 0 obj +<> endobj +486 0 obj +<> endobj +487 0 obj +<> endobj +488 0 obj +<> endobj +489 0 obj +<> endobj +490 0 obj +<> endobj +491 0 obj +<> endobj +492 0 obj +<> endobj +493 0 obj +<> endobj +494 0 obj +<> endobj +495 0 obj +<> endobj +496 0 obj +<> endobj +497 0 obj +<> endobj +498 0 obj +<> endobj +499 0 obj +<> endobj +500 0 obj +<> endobj +501 0 obj +<> endobj +502 0 obj +<> endobj +503 0 obj +<> endobj +504 0 obj +<> endobj +505 0 obj +<> endobj +506 0 obj +<> endobj +507 0 obj +<> endobj +508 0 obj +<> endobj +509 0 obj +<> endobj +510 0 obj +<> endobj +511 0 obj +<> endobj +512 0 obj +<> endobj +513 0 obj +<> endobj +514 0 obj +<> endobj +515 0 obj +<> endobj +516 0 obj +<> endobj +517 0 obj +<> endobj +518 0 obj +<> endobj +519 0 obj +<> endobj +520 0 obj +<> endobj +521 0 obj +<> endobj +522 0 obj +<> endobj +523 0 obj +<> endobj +524 0 obj +<> endobj +525 0 obj +<> endobj +526 0 obj +<> endobj +527 0 obj +<> endobj +528 0 obj +<> endobj +529 0 obj +<> endobj +530 0 obj +<> endobj +531 0 obj +<> endobj +532 0 obj +<> endobj +533 0 obj +<> endobj +534 0 obj +<> endobj +535 0 obj +<> endobj +536 0 obj +<> endobj +537 0 obj +<> endobj +538 0 obj +<> endobj +539 0 obj +<> endobj +540 0 obj +<> endobj +541 0 obj +<> endobj +542 0 obj +<> endobj +543 0 obj +<> endobj +544 0 obj +<> endobj +545 0 obj +<> endobj +546 0 obj +<> endobj +547 0 obj +<> endobj +548 0 obj +<> endobj +549 0 obj +<> endobj +550 0 obj +<> endobj +551 0 obj +<> endobj +552 0 obj +<> endobj +553 0 obj +<> endobj +554 0 obj +<> endobj +555 0 obj +<> endobj +556 0 obj +<> endobj +557 0 obj +<> endobj +558 0 obj +<> endobj +559 0 obj +<> endobj +560 0 obj +<> endobj +561 0 obj +<> endobj +562 0 obj +<> endobj +563 0 obj +<> endobj +564 0 obj +<> endobj +565 0 obj +<> endobj +566 0 obj +<> endobj +567 0 obj +<> endobj +568 0 obj +<> endobj +569 0 obj +<> endobj +570 0 obj +<> endobj +571 0 obj +<> endobj +572 0 obj +<> endobj +573 0 obj +<> endobj +574 0 obj +<> endobj +575 0 obj +<> endobj +576 0 obj +<> endobj +577 0 obj +<> endobj +578 0 obj +<> endobj +579 0 obj +<> endobj +580 0 obj +<> endobj +581 0 obj +<> endobj +582 0 obj +<> endobj +583 0 obj +<> endobj +584 0 obj +<> endobj +585 0 obj +<> endobj +586 0 obj +<> endobj +587 0 obj +<> endobj +588 0 obj +<> endobj +589 0 obj +<> endobj +590 0 obj +<> endobj +591 0 obj +<> endobj +592 0 obj +<> endobj +593 0 obj +<> endobj +594 0 obj +<> endobj +595 0 obj +<> endobj +596 0 obj +<> endobj +597 0 obj +<> endobj +598 0 obj +<> endobj +599 0 obj +<> endobj +600 0 obj +<> endobj +601 0 obj +<> endobj +602 0 obj +<> endobj +603 0 obj +<> endobj +604 0 obj +<> endobj +605 0 obj +<> endobj +606 0 obj +<> endobj +607 0 obj +<> endobj +608 0 obj +<> endobj +609 0 obj +<> endobj +610 0 obj +<> endobj +611 0 obj +<> endobj +612 0 obj +<> endobj +613 0 obj +<> endobj +614 0 obj +<> endobj +615 0 obj +<> endobj +616 0 obj +<> endobj +617 0 obj +<> endobj +618 0 obj +<> endobj +619 0 obj +<> endobj +620 0 obj +<> endobj +621 0 obj +<> endobj +622 0 obj +<> endobj +623 0 obj +<> endobj +624 0 obj +<> endobj +625 0 obj +<> endobj +626 0 obj +<> endobj +627 0 obj +<> endobj +628 0 obj +<> endobj +629 0 obj +<> endobj +630 0 obj +<> endobj +631 0 obj +<> endobj +632 0 obj +<> endobj +633 0 obj +<> endobj +634 0 obj +<> endobj +635 0 obj +<> endobj +636 0 obj +<> endobj +637 0 obj +<> endobj +638 0 obj +<> endobj +639 0 obj +<> endobj +640 0 obj +<> endobj +641 0 obj +<> endobj +642 0 obj +<> endobj +643 0 obj +<> endobj +644 0 obj +<> endobj +645 0 obj +<> endobj +646 0 obj +<> endobj +647 0 obj +<> endobj +648 0 obj +<> endobj +649 0 obj +<> endobj +650 0 obj +<> endobj +651 0 obj +<> endobj +652 0 obj +<> endobj +653 0 obj +<> endobj +654 0 obj +<> endobj +655 0 obj +<> endobj +656 0 obj +<> endobj +657 0 obj +<> endobj +658 0 obj +<> endobj +659 0 obj +<> endobj +660 0 obj +<> endobj +661 0 obj +<> endobj +662 0 obj +<> endobj +663 0 obj +<> endobj +664 0 obj +<> endobj +665 0 obj +<> endobj +666 0 obj +<> endobj +667 0 obj +<> endobj +668 0 obj +<> endobj +669 0 obj +<> endobj +670 0 obj +<> endobj +671 0 obj +<> endobj +672 0 obj +<> endobj +673 0 obj +<> endobj +674 0 obj +<> endobj +675 0 obj +<> endobj +676 0 obj +<> endobj +677 0 obj +<> endobj +678 0 obj +<> endobj +679 0 obj +<> endobj +680 0 obj +<> endobj +681 0 obj +<> endobj +682 0 obj +<> endobj +683 0 obj +<> endobj +684 0 obj +<> endobj +685 0 obj +<> endobj +686 0 obj +<> endobj +687 0 obj +<> endobj +688 0 obj +<> endobj +689 0 obj +<> endobj +690 0 obj +<> endobj +691 0 obj +<> endobj +692 0 obj +<> endobj +693 0 obj +<> endobj +694 0 obj +<> endobj +695 0 obj +<> endobj +696 0 obj +<> endobj +697 0 obj +<> endobj +698 0 obj +<> endobj +699 0 obj +<> endobj +700 0 obj +<> endobj +701 0 obj +<> endobj +702 0 obj +<> endobj +703 0 obj +<> endobj +704 0 obj +<> endobj +705 0 obj +<> endobj +706 0 obj +<> endobj +707 0 obj +<> endobj +708 0 obj +<> endobj +709 0 obj +<> endobj +710 0 obj +<> endobj +711 0 obj +<> endobj +712 0 obj +<> endobj +713 0 obj +<> endobj +714 0 obj +<> endobj +715 0 obj +<> endobj +716 0 obj +<> endobj +717 0 obj +<> endobj +718 0 obj +<> endobj +719 0 obj +<> endobj +720 0 obj +<> endobj +721 0 obj +<> endobj +722 0 obj +<> endobj +723 0 obj +<> endobj +724 0 obj +<> endobj +725 0 obj +<> endobj +726 0 obj +<> endobj +727 0 obj +<> endobj +728 0 obj +<> endobj +729 0 obj +<> endobj +730 0 obj +<> endobj +731 0 obj +<> endobj +732 0 obj +<> endobj +733 0 obj +<> endobj +734 0 obj +<> endobj +735 0 obj +<> endobj +736 0 obj +<> endobj +737 0 obj +<> endobj +738 0 obj +<> endobj +739 0 obj +<> endobj +740 0 obj +<> endobj +741 0 obj +<> endobj +742 0 obj +<> endobj +743 0 obj +<> endobj +744 0 obj +<> endobj +745 0 obj +<> endobj +746 0 obj +<> endobj +747 0 obj +<> endobj +748 0 obj +<> endobj +749 0 obj +<> endobj +750 0 obj +<> endobj +751 0 obj +<> endobj +752 0 obj +<> endobj +753 0 obj +<> endobj +754 0 obj +<> endobj +755 0 obj +<> endobj +756 0 obj +<> endobj +757 0 obj +<> endobj +758 0 obj +<> endobj +759 0 obj +<> endobj +760 0 obj +<> endobj +761 0 obj +<> endobj +762 0 obj +<> endobj +763 0 obj +<> endobj +764 0 obj +<> endobj +765 0 obj +<> endobj +766 0 obj +<> endobj +767 0 obj +<> endobj +768 0 obj +<> endobj +769 0 obj +<> endobj +770 0 obj +<> endobj +771 0 obj +<> endobj +772 0 obj +<> endobj +773 0 obj +<> endobj +774 0 obj +<> endobj +775 0 obj +<> endobj +776 0 obj +<> endobj +777 0 obj +<> endobj +778 0 obj +<> endobj +779 0 obj +<> endobj +780 0 obj +<> endobj +781 0 obj +<> endobj +782 0 obj +<> endobj +783 0 obj +<> endobj +784 0 obj +<> endobj +785 0 obj +<> endobj +786 0 obj +<> endobj +787 0 obj +<> endobj +788 0 obj +<> endobj +789 0 obj +<> endobj +790 0 obj +<> endobj +791 0 obj +<> endobj +792 0 obj +<> endobj +793 0 obj +<> endobj +794 0 obj +<> endobj +795 0 obj +<> endobj +796 0 obj +<> endobj +797 0 obj +<> endobj +798 0 obj +<> endobj +799 0 obj +<> endobj +800 0 obj +<> endobj +801 0 obj +<> endobj +802 0 obj +<> endobj +803 0 obj +<> endobj +804 0 obj +<> endobj +805 0 obj +<> endobj +806 0 obj +<> endobj +807 0 obj +<> endobj +808 0 obj +<> endobj +809 0 obj +<> endobj +810 0 obj +<> endobj +811 0 obj +<> endobj +812 0 obj +<> endobj +813 0 obj +<> endobj +814 0 obj +<> endobj +815 0 obj +<> endobj +816 0 obj +<> endobj +817 0 obj +<> endobj +818 0 obj +<> endobj +819 0 obj +<> endobj +820 0 obj +<> endobj +821 0 obj +<> endobj +822 0 obj +<> endobj +823 0 obj +<> endobj +824 0 obj +<> endobj +825 0 obj +<> endobj +826 0 obj +<> endobj +827 0 obj +<> endobj +828 0 obj +<> endobj +829 0 obj +<> endobj +830 0 obj +<> endobj +831 0 obj +<> endobj +832 0 obj +<> endobj +833 0 obj +<> endobj +834 0 obj +<> endobj +835 0 obj +<> endobj +836 0 obj +<> endobj +837 0 obj +<> endobj +838 0 obj +<> endobj +839 0 obj +<> endobj +840 0 obj +<> endobj +841 0 obj +<> endobj +842 0 obj +<> endobj +843 0 obj +<> endobj +844 0 obj +<> endobj +845 0 obj +<> endobj +846 0 obj +<> endobj +847 0 obj +<> endobj +848 0 obj +<> endobj +849 0 obj +<> endobj +850 0 obj +<> endobj +851 0 obj +<> endobj +852 0 obj +<> endobj +853 0 obj +<> endobj +854 0 obj +<> endobj +855 0 obj +<> endobj +856 0 obj +<> endobj +857 0 obj +<> endobj +858 0 obj +<> endobj +859 0 obj +<> endobj +860 0 obj +<> endobj + +12 0 obj +<< +/Type /Outlines +/First 13 0 R +/Last 15 0 R +/Count 306 +>> +endobj + +13 0 obj +<< +/Title (Pages) +/Parent 12 0 R +/Next 15 0 R +/First 14 0 R +/Last 14 0 R +/Count 1 +>> +endobj + +15 0 obj +<< +/Title (Net) +/Parent 12 0 R +/Prev 13 0 R +/First 16 0 R +/Last 315 0 R +/Count 303 +>> +endobj + +14 0 obj +<< +/Title (SCH_Pro-micro_Pinouts 1-Sheet_1) +/Parent 13 0 R +/Dest [3 0 R /XYZ 0 1197.36 0] +>> +endobj + +16 0 obj +<< +/Title (3V3) +/Parent 15 0 R +/Next 35 0 R +/First 17 0 R +/Last 34 0 R +/Count 18 +>> +endobj + +35 0 obj +<< +/Title (+5V) +/Parent 15 0 R +/Prev 16 0 R +/Next 39 0 R +/First 36 0 R +/Last 38 0 R +/Count 3 +>> +endobj + +39 0 obj +<< +/Title ($1N1) +/Parent 15 0 R +/Prev 35 0 R +/Next 41 0 R +/First 40 0 R +/Last 40 0 R +/Count 1 +>> +endobj + +41 0 obj +<< +/Title ($1N25) +/Parent 15 0 R +/Prev 39 0 R +/Next 43 0 R +/First 42 0 R +/Last 42 0 R +/Count 1 +>> +endobj + +43 0 obj +<< +/Title ($1N62) +/Parent 15 0 R +/Prev 41 0 R +/Next 45 0 R +/First 44 0 R +/Last 44 0 R +/Count 1 +>> +endobj + +45 0 obj +<< +/Title (ADC) +/Parent 15 0 R +/Prev 43 0 R +/Next 50 0 R +/First 46 0 R +/Last 49 0 R +/Count 4 +>> +endobj + +50 0 obj +<< +/Title (BATT) +/Parent 15 0 R +/Prev 45 0 R +/Next 57 0 R +/First 51 0 R +/Last 56 0 R +/Count 6 +>> +endobj + +57 0 obj +<< +/Title (BUSY) +/Parent 15 0 R +/Prev 50 0 R +/Next 73 0 R +/First 58 0 R +/Last 72 0 R +/Count 15 +>> +endobj + +73 0 obj +<< +/Title (CS) +/Parent 15 0 R +/Prev 57 0 R +/Next 89 0 R +/First 74 0 R +/Last 88 0 R +/Count 15 +>> +endobj + +89 0 obj +<< +/Title (DIO2) +/Parent 15 0 R +/Prev 73 0 R +/Next 102 0 R +/First 90 0 R +/Last 101 0 R +/Count 12 +>> +endobj + +102 0 obj +<< +/Title (DIO3) +/Parent 15 0 R +/Prev 89 0 R +/Next 104 0 R +/First 103 0 R +/Last 103 0 R +/Count 1 +>> +endobj + +104 0 obj +<< +/Title (E_INK_BUSY) +/Parent 15 0 R +/Prev 102 0 R +/Next 109 0 R +/First 105 0 R +/Last 108 0 R +/Count 4 +>> +endobj + +109 0 obj +<< +/Title (E_INK_CS) +/Parent 15 0 R +/Prev 104 0 R +/Next 113 0 R +/First 110 0 R +/Last 112 0 R +/Count 3 +>> +endobj + +113 0 obj +<< +/Title (E_INK_D/C) +/Parent 15 0 R +/Prev 109 0 R +/Next 117 0 R +/First 114 0 R +/Last 116 0 R +/Count 3 +>> +endobj + +117 0 obj +<< +/Title (E_INK_NRST) +/Parent 15 0 R +/Prev 113 0 R +/Next 121 0 R +/First 118 0 R +/Last 120 0 R +/Count 3 +>> +endobj + +121 0 obj +<< +/Title (GND) +/Parent 15 0 R +/Prev 117 0 R +/Next 196 0 R +/First 122 0 R +/Last 195 0 R +/Count 74 +>> +endobj + +196 0 obj +<< +/Title (GPSEN) +/Parent 15 0 R +/Prev 121 0 R +/Next 199 0 R +/First 197 0 R +/Last 198 0 R +/Count 2 +>> +endobj + +199 0 obj +<< +/Title (GPSRX) +/Parent 15 0 R +/Prev 196 0 R +/Next 202 0 R +/First 200 0 R +/Last 201 0 R +/Count 2 +>> +endobj + +202 0 obj +<< +/Title (GPSTX) +/Parent 15 0 R +/Prev 199 0 R +/Next 205 0 R +/First 203 0 R +/Last 204 0 R +/Count 2 +>> +endobj + +205 0 obj +<< +/Title (IRQ) +/Parent 15 0 R +/Prev 202 0 R +/Next 221 0 R +/First 206 0 R +/Last 220 0 R +/Count 15 +>> +endobj + +221 0 obj +<< +/Title (LORA_ANT) +/Parent 15 0 R +/Prev 205 0 R +/Next 223 0 R +/First 222 0 R +/Last 222 0 R +/Count 1 +>> +endobj + +223 0 obj +<< +/Title (MISO) +/Parent 15 0 R +/Prev 221 0 R +/Next 239 0 R +/First 224 0 R +/Last 238 0 R +/Count 15 +>> +endobj + +239 0 obj +<< +/Title (MOSI) +/Parent 15 0 R +/Prev 223 0 R +/Next 257 0 R +/First 240 0 R +/Last 256 0 R +/Count 17 +>> +endobj + +257 0 obj +<< +/Title (NRST) +/Parent 15 0 R +/Prev 239 0 R +/Next 273 0 R +/First 258 0 R +/Last 272 0 R +/Count 15 +>> +endobj + +273 0 obj +<< +/Title (RBTN) +/Parent 15 0 R +/Prev 257 0 R +/Next 277 0 R +/First 274 0 R +/Last 276 0 R +/Count 3 +>> +endobj + +277 0 obj +<< +/Title (RXEN) +/Parent 15 0 R +/Prev 273 0 R +/Next 285 0 R +/First 278 0 R +/Last 284 0 R +/Count 7 +>> +endobj + +285 0 obj +<< +/Title (SCK) +/Parent 15 0 R +/Prev 277 0 R +/Next 303 0 R +/First 286 0 R +/Last 302 0 R +/Count 17 +>> +endobj + +303 0 obj +<< +/Title (SCL) +/Parent 15 0 R +/Prev 285 0 R +/Next 306 0 R +/First 304 0 R +/Last 305 0 R +/Count 2 +>> +endobj + +306 0 obj +<< +/Title (SDA) +/Parent 15 0 R +/Prev 303 0 R +/Next 309 0 R +/First 307 0 R +/Last 308 0 R +/Count 2 +>> +endobj + +309 0 obj +<< +/Title (SERIAL2RX) +/Parent 15 0 R +/Prev 306 0 R +/Next 312 0 R +/First 310 0 R +/Last 311 0 R +/Count 2 +>> +endobj + +312 0 obj +<< +/Title (SERIAL2TX) +/Parent 15 0 R +/Prev 309 0 R +/Next 315 0 R +/First 313 0 R +/Last 314 0 R +/Count 2 +>> +endobj + +315 0 obj +<< +/Title (UBTN) +/Parent 15 0 R +/Prev 312 0 R +/First 316 0 R +/Last 318 0 R +/Count 3 +>> +endobj + +17 0 obj +<< +/Title ($1N5) +/Parent 16 0 R +/Next 18 0 R +/A 319 0 R +>> +endobj + +18 0 obj +<< +/Title ($1N29) +/Parent 16 0 R +/Prev 17 0 R +/Next 19 0 R +/A 321 0 R +>> +endobj + +19 0 obj +<< +/Title ($1N35) +/Parent 16 0 R +/Prev 18 0 R +/Next 20 0 R +/A 323 0 R +>> +endobj + +20 0 obj +<< +/Title ($1N54) +/Parent 16 0 R +/Prev 19 0 R +/Next 21 0 R +/A 325 0 R +>> +endobj + +21 0 obj +<< +/Title ($1N1528) +/Parent 16 0 R +/Prev 20 0 R +/Next 22 0 R +/A 327 0 R +>> +endobj + +22 0 obj +<< +/Title ($1N1550) +/Parent 16 0 R +/Prev 21 0 R +/Next 23 0 R +/A 329 0 R +>> +endobj + +23 0 obj +<< +/Title ($1N1574) +/Parent 16 0 R +/Prev 22 0 R +/Next 24 0 R +/A 331 0 R +>> +endobj + +24 0 obj +<< +/Title ($1N1582) +/Parent 16 0 R +/Prev 23 0 R +/Next 25 0 R +/A 333 0 R +>> +endobj + +25 0 obj +<< +/Title ($1N1616) +/Parent 16 0 R +/Prev 24 0 R +/Next 26 0 R +/A 335 0 R +>> +endobj + +26 0 obj +<< +/Title ($1N1618) +/Parent 16 0 R +/Prev 25 0 R +/Next 27 0 R +/A 337 0 R +>> +endobj + +27 0 obj +<< +/Title ($1N1732) +/Parent 16 0 R +/Prev 26 0 R +/Next 28 0 R +/A 339 0 R +>> +endobj + +28 0 obj +<< +/Title ($1N1742) +/Parent 16 0 R +/Prev 27 0 R +/Next 29 0 R +/A 341 0 R +>> +endobj + +29 0 obj +<< +/Title ($1N1776) +/Parent 16 0 R +/Prev 28 0 R +/Next 30 0 R +/A 343 0 R +>> +endobj + +30 0 obj +<< +/Title ($1N1810) +/Parent 16 0 R +/Prev 29 0 R +/Next 31 0 R +/A 345 0 R +>> +endobj + +31 0 obj +<< +/Title ($1N1860) +/Parent 16 0 R +/Prev 30 0 R +/Next 32 0 R +/A 347 0 R +>> +endobj + +32 0 obj +<< +/Title ($1N1874) +/Parent 16 0 R +/Prev 31 0 R +/Next 33 0 R +/A 349 0 R +>> +endobj + +33 0 obj +<< +/Title ($1N5406) +/Parent 16 0 R +/Prev 32 0 R +/Next 34 0 R +/A 351 0 R +>> +endobj + +34 0 obj +<< +/Title ($1N5445) +/Parent 16 0 R +/Prev 33 0 R +/A 353 0 R +>> +endobj + +36 0 obj +<< +/Title ($1N23) +/Parent 35 0 R +/Next 37 0 R +/A 355 0 R +>> +endobj + +37 0 obj +<< +/Title ($1N31) +/Parent 35 0 R +/Prev 36 0 R +/Next 38 0 R +/A 357 0 R +>> +endobj + +38 0 obj +<< +/Title ($1N1570) +/Parent 35 0 R +/Prev 37 0 R +/A 359 0 R +>> +endobj + +40 0 obj +<< +/Title ($1N1) +/Parent 39 0 R +/A 361 0 R +>> +endobj + +42 0 obj +<< +/Title ($1N25) +/Parent 41 0 R +/A 363 0 R +>> +endobj + +44 0 obj +<< +/Title ($1N62) +/Parent 43 0 R +/A 365 0 R +>> +endobj + +46 0 obj +<< +/Title ($1N15) +/Parent 45 0 R +/Next 47 0 R +/A 367 0 R +>> +endobj + +47 0 obj +<< +/Title ($1N44) +/Parent 45 0 R +/Prev 46 0 R +/Next 48 0 R +/A 369 0 R +>> +endobj + +48 0 obj +<< +/Title ($1N1852) +/Parent 45 0 R +/Prev 47 0 R +/Next 49 0 R +/A 371 0 R +>> +endobj + +49 0 obj +<< +/Title ($1N1856) +/Parent 45 0 R +/Prev 48 0 R +/A 373 0 R +>> +endobj + +51 0 obj +<< +/Title ($1N1494) +/Parent 50 0 R +/Next 52 0 R +/A 375 0 R +>> +endobj + +52 0 obj +<< +/Title ($1N1508) +/Parent 50 0 R +/Prev 51 0 R +/Next 53 0 R +/A 377 0 R +>> +endobj + +53 0 obj +<< +/Title ($1N1578) +/Parent 50 0 R +/Prev 52 0 R +/Next 54 0 R +/A 379 0 R +>> +endobj + +54 0 obj +<< +/Title ($1N1846) +/Parent 50 0 R +/Prev 53 0 R +/Next 55 0 R +/A 381 0 R +>> +endobj + +55 0 obj +<< +/Title ($1N1848) +/Parent 50 0 R +/Prev 54 0 R +/Next 56 0 R +/A 383 0 R +>> +endobj + +56 0 obj +<< +/Title ($1N1854) +/Parent 50 0 R +/Prev 55 0 R +/A 385 0 R +>> +endobj + +58 0 obj +<< +/Title ($1N12) +/Parent 57 0 R +/Next 59 0 R +/A 387 0 R +>> +endobj + +59 0 obj +<< +/Title ($1N47) +/Parent 57 0 R +/Prev 58 0 R +/Next 60 0 R +/A 389 0 R +>> +endobj + +60 0 obj +<< +/Title ($1N1540) +/Parent 57 0 R +/Prev 59 0 R +/Next 61 0 R +/A 391 0 R +>> +endobj + +61 0 obj +<< +/Title ($1N1568) +/Parent 57 0 R +/Prev 60 0 R +/Next 62 0 R +/A 393 0 R +>> +endobj + +62 0 obj +<< +/Title ($1N1600) +/Parent 57 0 R +/Prev 61 0 R +/Next 63 0 R +/A 395 0 R +>> +endobj + +63 0 obj +<< +/Title ($1N1636) +/Parent 57 0 R +/Prev 62 0 R +/Next 64 0 R +/A 397 0 R +>> +endobj + +64 0 obj +<< +/Title ($1N1660) +/Parent 57 0 R +/Prev 63 0 R +/Next 65 0 R +/A 399 0 R +>> +endobj + +65 0 obj +<< +/Title ($1N1696) +/Parent 57 0 R +/Prev 64 0 R +/Next 66 0 R +/A 401 0 R +>> +endobj + +66 0 obj +<< +/Title ($1N1710) +/Parent 57 0 R +/Prev 65 0 R +/Next 67 0 R +/A 403 0 R +>> +endobj + +67 0 obj +<< +/Title ($1N1736) +/Parent 57 0 R +/Prev 66 0 R +/Next 68 0 R +/A 405 0 R +>> +endobj + +68 0 obj +<< +/Title ($1N1760) +/Parent 57 0 R +/Prev 67 0 R +/Next 69 0 R +/A 407 0 R +>> +endobj + +69 0 obj +<< +/Title ($1N1778) +/Parent 57 0 R +/Prev 68 0 R +/Next 70 0 R +/A 409 0 R +>> +endobj + +70 0 obj +<< +/Title ($1N1814) +/Parent 57 0 R +/Prev 69 0 R +/Next 71 0 R +/A 411 0 R +>> +endobj + +71 0 obj +<< +/Title ($1N1836) +/Parent 57 0 R +/Prev 70 0 R +/Next 72 0 R +/A 413 0 R +>> +endobj + +72 0 obj +<< +/Title ($1N1870) +/Parent 57 0 R +/Prev 71 0 R +/A 415 0 R +>> +endobj + +74 0 obj +<< +/Title ($1N10) +/Parent 73 0 R +/Next 75 0 R +/A 417 0 R +>> +endobj + +75 0 obj +<< +/Title ($1N49) +/Parent 73 0 R +/Prev 74 0 R +/Next 76 0 R +/A 419 0 R +>> +endobj + +76 0 obj +<< +/Title ($1N1542) +/Parent 73 0 R +/Prev 75 0 R +/Next 77 0 R +/A 421 0 R +>> +endobj + +77 0 obj +<< +/Title ($1N1566) +/Parent 73 0 R +/Prev 76 0 R +/Next 78 0 R +/A 423 0 R +>> +endobj + +78 0 obj +<< +/Title ($1N1602) +/Parent 73 0 R +/Prev 77 0 R +/Next 79 0 R +/A 425 0 R +>> +endobj + +79 0 obj +<< +/Title ($1N1626) +/Parent 73 0 R +/Prev 78 0 R +/Next 80 0 R +/A 427 0 R +>> +endobj + +80 0 obj +<< +/Title ($1N1670) +/Parent 73 0 R +/Prev 79 0 R +/Next 81 0 R +/A 429 0 R +>> +endobj + +81 0 obj +<< +/Title ($1N1686) +/Parent 73 0 R +/Prev 80 0 R +/Next 82 0 R +/A 431 0 R +>> +endobj + +82 0 obj +<< +/Title ($1N1718) +/Parent 73 0 R +/Prev 81 0 R +/Next 83 0 R +/A 433 0 R +>> +endobj + +83 0 obj +<< +/Title ($1N1728) +/Parent 73 0 R +/Prev 82 0 R +/Next 84 0 R +/A 435 0 R +>> +endobj + +84 0 obj +<< +/Title ($1N1752) +/Parent 73 0 R +/Prev 83 0 R +/Next 85 0 R +/A 437 0 R +>> +endobj + +85 0 obj +<< +/Title ($1N1788) +/Parent 73 0 R +/Prev 84 0 R +/Next 86 0 R +/A 439 0 R +>> +endobj + +86 0 obj +<< +/Title ($1N1808) +/Parent 73 0 R +/Prev 85 0 R +/Next 87 0 R +/A 441 0 R +>> +endobj + +87 0 obj +<< +/Title ($1N1828) +/Parent 73 0 R +/Prev 86 0 R +/Next 88 0 R +/A 443 0 R +>> +endobj + +88 0 obj +<< +/Title ($1N1876) +/Parent 73 0 R +/Prev 87 0 R +/A 445 0 R +>> +endobj + +90 0 obj +<< +/Title ($1N1624) +/Parent 89 0 R +/Next 91 0 R +/A 447 0 R +>> +endobj + +91 0 obj +<< +/Title ($1N1672) +/Parent 89 0 R +/Prev 90 0 R +/Next 92 0 R +/A 449 0 R +>> +endobj + +92 0 obj +<< +/Title ($1N1684) +/Parent 89 0 R +/Prev 91 0 R +/Next 93 0 R +/A 451 0 R +>> +endobj + +93 0 obj +<< +/Title ($1N1698) +/Parent 89 0 R +/Prev 92 0 R +/Next 94 0 R +/A 453 0 R +>> +endobj + +94 0 obj +<< +/Title ($1N1700) +/Parent 89 0 R +/Prev 93 0 R +/Next 95 0 R +/A 455 0 R +>> +endobj + +95 0 obj +<< +/Title ($1N1702) +/Parent 89 0 R +/Prev 94 0 R +/Next 96 0 R +/A 457 0 R +>> +endobj + +96 0 obj +<< +/Title ($1N1744) +/Parent 89 0 R +/Prev 95 0 R +/Next 97 0 R +/A 459 0 R +>> +endobj + +97 0 obj +<< +/Title ($1N1750) +/Parent 89 0 R +/Prev 96 0 R +/Next 98 0 R +/A 461 0 R +>> +endobj + +98 0 obj +<< +/Title ($1N1820) +/Parent 89 0 R +/Prev 97 0 R +/Next 99 0 R +/A 463 0 R +>> +endobj + +99 0 obj +<< +/Title ($1N1822) +/Parent 89 0 R +/Prev 98 0 R +/Next 100 0 R +/A 465 0 R +>> +endobj + +100 0 obj +<< +/Title ($1N1862) +/Parent 89 0 R +/Prev 99 0 R +/Next 101 0 R +/A 467 0 R +>> +endobj + +101 0 obj +<< +/Title ($1N1864) +/Parent 89 0 R +/Prev 100 0 R +/A 469 0 R +>> +endobj + +103 0 obj +<< +/Title ($1N1678) +/Parent 102 0 R +/A 471 0 R +>> +endobj + +105 0 obj +<< +/Title ($1N1500) +/Parent 104 0 R +/Next 106 0 R +/A 473 0 R +>> +endobj + +106 0 obj +<< +/Title ($1N1514) +/Parent 104 0 R +/Prev 105 0 R +/Next 107 0 R +/A 475 0 R +>> +endobj + +107 0 obj +<< +/Title ($1N5294) +/Parent 104 0 R +/Prev 106 0 R +/Next 108 0 R +/A 477 0 R +>> +endobj + +108 0 obj +<< +/Title ($1N5448) +/Parent 104 0 R +/Prev 107 0 R +/A 479 0 R +>> +endobj + +110 0 obj +<< +/Title ($1N1506) +/Parent 109 0 R +/Next 111 0 R +/A 481 0 R +>> +endobj + +111 0 obj +<< +/Title ($1N5342) +/Parent 109 0 R +/Prev 110 0 R +/Next 112 0 R +/A 483 0 R +>> +endobj + +112 0 obj +<< +/Title ($1N5457) +/Parent 109 0 R +/Prev 111 0 R +/A 485 0 R +>> +endobj + +114 0 obj +<< +/Title ($1N1504) +/Parent 113 0 R +/Next 115 0 R +/A 487 0 R +>> +endobj + +115 0 obj +<< +/Title ($1N5326) +/Parent 113 0 R +/Prev 114 0 R +/Next 116 0 R +/A 489 0 R +>> +endobj + +116 0 obj +<< +/Title ($1N5454) +/Parent 113 0 R +/Prev 115 0 R +/A 491 0 R +>> +endobj + +118 0 obj +<< +/Title ($1N1502) +/Parent 117 0 R +/Next 119 0 R +/A 493 0 R +>> +endobj + +119 0 obj +<< +/Title ($1N5310) +/Parent 117 0 R +/Prev 118 0 R +/Next 120 0 R +/A 495 0 R +>> +endobj + +120 0 obj +<< +/Title ($1N5451) +/Parent 117 0 R +/Prev 119 0 R +/A 497 0 R +>> +endobj + +122 0 obj +<< +/Title ($1N2) +/Parent 121 0 R +/Next 123 0 R +/A 499 0 R +>> +endobj + +123 0 obj +<< +/Title ($1N6) +/Parent 121 0 R +/Prev 122 0 R +/Next 124 0 R +/A 501 0 R +>> +endobj + +124 0 obj +<< +/Title ($1N22) +/Parent 121 0 R +/Prev 123 0 R +/Next 125 0 R +/A 503 0 R +>> +endobj + +125 0 obj +<< +/Title ($1N24) +/Parent 121 0 R +/Prev 124 0 R +/Next 126 0 R +/A 505 0 R +>> +endobj + +126 0 obj +<< +/Title ($1N26) +/Parent 121 0 R +/Prev 125 0 R +/Next 127 0 R +/A 507 0 R +>> +endobj + +127 0 obj +<< +/Title ($1N27) +/Parent 121 0 R +/Prev 126 0 R +/Next 128 0 R +/A 509 0 R +>> +endobj + +128 0 obj +<< +/Title ($1N28) +/Parent 121 0 R +/Prev 127 0 R +/Next 129 0 R +/A 511 0 R +>> +endobj + +129 0 obj +<< +/Title ($1N30) +/Parent 121 0 R +/Prev 128 0 R +/Next 130 0 R +/A 513 0 R +>> +endobj + +130 0 obj +<< +/Title ($1N32) +/Parent 121 0 R +/Prev 129 0 R +/Next 131 0 R +/A 515 0 R +>> +endobj + +131 0 obj +<< +/Title ($1N36) +/Parent 121 0 R +/Prev 130 0 R +/Next 132 0 R +/A 517 0 R +>> +endobj + +132 0 obj +<< +/Title ($1N37) +/Parent 121 0 R +/Prev 131 0 R +/Next 133 0 R +/A 519 0 R +>> +endobj + +133 0 obj +<< +/Title ($1N53) +/Parent 121 0 R +/Prev 132 0 R +/Next 134 0 R +/A 521 0 R +>> +endobj + +134 0 obj +<< +/Title ($1N58) +/Parent 121 0 R +/Prev 133 0 R +/Next 135 0 R +/A 523 0 R +>> +endobj + +135 0 obj +<< +/Title ($1N61) +/Parent 121 0 R +/Prev 134 0 R +/Next 136 0 R +/A 525 0 R +>> +endobj + +136 0 obj +<< +/Title ($1N63) +/Parent 121 0 R +/Prev 135 0 R +/Next 137 0 R +/A 527 0 R +>> +endobj + +137 0 obj +<< +/Title ($1N1516) +/Parent 121 0 R +/Prev 136 0 R +/Next 138 0 R +/A 529 0 R +>> +endobj + +138 0 obj +<< +/Title ($1N1518) +/Parent 121 0 R +/Prev 137 0 R +/Next 139 0 R +/A 531 0 R +>> +endobj + +139 0 obj +<< +/Title ($1N1524) +/Parent 121 0 R +/Prev 138 0 R +/Next 140 0 R +/A 533 0 R +>> +endobj + +140 0 obj +<< +/Title ($1N1526) +/Parent 121 0 R +/Prev 139 0 R +/Next 141 0 R +/A 535 0 R +>> +endobj + +141 0 obj +<< +/Title ($1N1530) +/Parent 121 0 R +/Prev 140 0 R +/Next 142 0 R +/A 537 0 R +>> +endobj + +142 0 obj +<< +/Title ($1N1532) +/Parent 121 0 R +/Prev 141 0 R +/Next 143 0 R +/A 539 0 R +>> +endobj + +143 0 obj +<< +/Title ($1N1534) +/Parent 121 0 R +/Prev 142 0 R +/Next 144 0 R +/A 541 0 R +>> +endobj + +144 0 obj +<< +/Title ($1N1552) +/Parent 121 0 R +/Prev 143 0 R +/Next 145 0 R +/A 543 0 R +>> +endobj + +145 0 obj +<< +/Title ($1N1554) +/Parent 121 0 R +/Prev 144 0 R +/Next 146 0 R +/A 545 0 R +>> +endobj + +146 0 obj +<< +/Title ($1N1572) +/Parent 121 0 R +/Prev 145 0 R +/Next 147 0 R +/A 547 0 R +>> +endobj + +147 0 obj +<< +/Title ($1N1576) +/Parent 121 0 R +/Prev 146 0 R +/Next 148 0 R +/A 549 0 R +>> +endobj + +148 0 obj +<< +/Title ($1N1580) +/Parent 121 0 R +/Prev 147 0 R +/Next 149 0 R +/A 551 0 R +>> +endobj + +149 0 obj +<< +/Title ($1N1584) +/Parent 121 0 R +/Prev 148 0 R +/Next 150 0 R +/A 553 0 R +>> +endobj + +150 0 obj +<< +/Title ($1N1586) +/Parent 121 0 R +/Prev 149 0 R +/Next 151 0 R +/A 555 0 R +>> +endobj + +151 0 obj +<< +/Title ($1N1588) +/Parent 121 0 R +/Prev 150 0 R +/Next 152 0 R +/A 557 0 R +>> +endobj + +152 0 obj +<< +/Title ($1N1590) +/Parent 121 0 R +/Prev 151 0 R +/Next 153 0 R +/A 559 0 R +>> +endobj + +153 0 obj +<< +/Title ($1N1592) +/Parent 121 0 R +/Prev 152 0 R +/Next 154 0 R +/A 561 0 R +>> +endobj + +154 0 obj +<< +/Title ($1N1594) +/Parent 121 0 R +/Prev 153 0 R +/Next 155 0 R +/A 563 0 R +>> +endobj + +155 0 obj +<< +/Title ($1N1596) +/Parent 121 0 R +/Prev 154 0 R +/Next 156 0 R +/A 565 0 R +>> +endobj + +156 0 obj +<< +/Title ($1N1598) +/Parent 121 0 R +/Prev 155 0 R +/Next 157 0 R +/A 567 0 R +>> +endobj + +157 0 obj +<< +/Title ($1N1614) +/Parent 121 0 R +/Prev 156 0 R +/Next 158 0 R +/A 569 0 R +>> +endobj + +158 0 obj +<< +/Title ($1N1638) +/Parent 121 0 R +/Prev 157 0 R +/Next 159 0 R +/A 571 0 R +>> +endobj + +159 0 obj +<< +/Title ($1N1640) +/Parent 121 0 R +/Prev 158 0 R +/Next 160 0 R +/A 573 0 R +>> +endobj + +160 0 obj +<< +/Title ($1N1642) +/Parent 121 0 R +/Prev 159 0 R +/Next 161 0 R +/A 575 0 R +>> +endobj + +161 0 obj +<< +/Title ($1N1644) +/Parent 121 0 R +/Prev 160 0 R +/Next 162 0 R +/A 577 0 R +>> +endobj + +162 0 obj +<< +/Title ($1N1646) +/Parent 121 0 R +/Prev 161 0 R +/Next 163 0 R +/A 579 0 R +>> +endobj + +163 0 obj +<< +/Title ($1N1648) +/Parent 121 0 R +/Prev 162 0 R +/Next 164 0 R +/A 581 0 R +>> +endobj + +164 0 obj +<< +/Title ($1N1650) +/Parent 121 0 R +/Prev 163 0 R +/Next 165 0 R +/A 583 0 R +>> +endobj + +165 0 obj +<< +/Title ($1N1652) +/Parent 121 0 R +/Prev 164 0 R +/Next 166 0 R +/A 585 0 R +>> +endobj + +166 0 obj +<< +/Title ($1N1656) +/Parent 121 0 R +/Prev 165 0 R +/Next 167 0 R +/A 587 0 R +>> +endobj + +167 0 obj +<< +/Title ($1N1658) +/Parent 121 0 R +/Prev 166 0 R +/Next 168 0 R +/A 589 0 R +>> +endobj + +168 0 obj +<< +/Title ($1N1708) +/Parent 121 0 R +/Prev 167 0 R +/Next 169 0 R +/A 591 0 R +>> +endobj + +169 0 obj +<< +/Title ($1N1730) +/Parent 121 0 R +/Prev 168 0 R +/Next 170 0 R +/A 593 0 R +>> +endobj + +170 0 obj +<< +/Title ($1N1734) +/Parent 121 0 R +/Prev 169 0 R +/Next 171 0 R +/A 595 0 R +>> +endobj + +171 0 obj +<< +/Title ($1N1740) +/Parent 121 0 R +/Prev 170 0 R +/Next 172 0 R +/A 597 0 R +>> +endobj + +172 0 obj +<< +/Title ($1N1762) +/Parent 121 0 R +/Prev 171 0 R +/Next 173 0 R +/A 599 0 R +>> +endobj + +173 0 obj +<< +/Title ($1N1764) +/Parent 121 0 R +/Prev 172 0 R +/Next 174 0 R +/A 601 0 R +>> +endobj + +174 0 obj +<< +/Title ($1N1766) +/Parent 121 0 R +/Prev 173 0 R +/Next 175 0 R +/A 603 0 R +>> +endobj + +175 0 obj +<< +/Title ($1N1768) +/Parent 121 0 R +/Prev 174 0 R +/Next 176 0 R +/A 605 0 R +>> +endobj + +176 0 obj +<< +/Title ($1N1770) +/Parent 121 0 R +/Prev 175 0 R +/Next 177 0 R +/A 607 0 R +>> +endobj + +177 0 obj +<< +/Title ($1N1774) +/Parent 121 0 R +/Prev 176 0 R +/Next 178 0 R +/A 609 0 R +>> +endobj + +178 0 obj +<< +/Title ($1N1790) +/Parent 121 0 R +/Prev 177 0 R +/Next 179 0 R +/A 611 0 R +>> +endobj + +179 0 obj +<< +/Title ($1N1792) +/Parent 121 0 R +/Prev 178 0 R +/Next 180 0 R +/A 613 0 R +>> +endobj + +180 0 obj +<< +/Title ($1N1796) +/Parent 121 0 R +/Prev 179 0 R +/Next 181 0 R +/A 615 0 R +>> +endobj + +181 0 obj +<< +/Title ($1N1798) +/Parent 121 0 R +/Prev 180 0 R +/Next 182 0 R +/A 617 0 R +>> +endobj + +182 0 obj +<< +/Title ($1N1800) +/Parent 121 0 R +/Prev 181 0 R +/Next 183 0 R +/A 619 0 R +>> +endobj + +183 0 obj +<< +/Title ($1N1824) +/Parent 121 0 R +/Prev 182 0 R +/Next 184 0 R +/A 621 0 R +>> +endobj + +184 0 obj +<< +/Title ($1N1826) +/Parent 121 0 R +/Prev 183 0 R +/Next 185 0 R +/A 623 0 R +>> +endobj + +185 0 obj +<< +/Title ($1N1838) +/Parent 121 0 R +/Prev 184 0 R +/Next 186 0 R +/A 625 0 R +>> +endobj + +186 0 obj +<< +/Title ($1N1840) +/Parent 121 0 R +/Prev 185 0 R +/Next 187 0 R +/A 627 0 R +>> +endobj + +187 0 obj +<< +/Title ($1N1842) +/Parent 121 0 R +/Prev 186 0 R +/Next 188 0 R +/A 629 0 R +>> +endobj + +188 0 obj +<< +/Title ($1N1844) +/Parent 121 0 R +/Prev 187 0 R +/Next 189 0 R +/A 631 0 R +>> +endobj + +189 0 obj +<< +/Title ($1N1850) +/Parent 121 0 R +/Prev 188 0 R +/Next 190 0 R +/A 633 0 R +>> +endobj + +190 0 obj +<< +/Title ($1N1858) +/Parent 121 0 R +/Prev 189 0 R +/Next 191 0 R +/A 635 0 R +>> +endobj + +191 0 obj +<< +/Title ($1N1866) +/Parent 121 0 R +/Prev 190 0 R +/Next 192 0 R +/A 637 0 R +>> +endobj + +192 0 obj +<< +/Title ($1N1884) +/Parent 121 0 R +/Prev 191 0 R +/Next 193 0 R +/A 639 0 R +>> +endobj + +193 0 obj +<< +/Title ($1N1886) +/Parent 121 0 R +/Prev 192 0 R +/Next 194 0 R +/A 641 0 R +>> +endobj + +194 0 obj +<< +/Title ($1N5390) +/Parent 121 0 R +/Prev 193 0 R +/Next 195 0 R +/A 643 0 R +>> +endobj + +195 0 obj +<< +/Title ($1N5442) +/Parent 121 0 R +/Prev 194 0 R +/A 645 0 R +>> +endobj + +197 0 obj +<< +/Title ($1N19) +/Parent 196 0 R +/Next 198 0 R +/A 647 0 R +>> +endobj + +198 0 obj +<< +/Title ($1N40) +/Parent 196 0 R +/Prev 197 0 R +/A 649 0 R +>> +endobj + +200 0 obj +<< +/Title ($1N21) +/Parent 199 0 R +/Next 201 0 R +/A 651 0 R +>> +endobj + +201 0 obj +<< +/Title ($1N38) +/Parent 199 0 R +/Prev 200 0 R +/A 653 0 R +>> +endobj + +203 0 obj +<< +/Title ($1N20) +/Parent 202 0 R +/Next 204 0 R +/A 655 0 R +>> +endobj + +204 0 obj +<< +/Title ($1N39) +/Parent 202 0 R +/Prev 203 0 R +/A 657 0 R +>> +endobj + +206 0 obj +<< +/Title ($1N11) +/Parent 205 0 R +/Next 207 0 R +/A 659 0 R +>> +endobj + +207 0 obj +<< +/Title ($1N33) +/Parent 205 0 R +/Prev 206 0 R +/Next 208 0 R +/A 661 0 R +>> +endobj + +208 0 obj +<< +/Title ($1N48) +/Parent 205 0 R +/Prev 207 0 R +/Next 209 0 R +/A 663 0 R +>> +endobj + +209 0 obj +<< +/Title ($1N1538) +/Parent 205 0 R +/Prev 208 0 R +/Next 210 0 R +/A 665 0 R +>> +endobj + +210 0 obj +<< +/Title ($1N1556) +/Parent 205 0 R +/Prev 209 0 R +/Next 211 0 R +/A 667 0 R +>> +endobj + +211 0 obj +<< +/Title ($1N1610) +/Parent 205 0 R +/Prev 210 0 R +/Next 212 0 R +/A 669 0 R +>> +endobj + +212 0 obj +<< +/Title ($1N1622) +/Parent 205 0 R +/Prev 211 0 R +/Next 213 0 R +/A 671 0 R +>> +endobj + +213 0 obj +<< +/Title ($1N1674) +/Parent 205 0 R +/Prev 212 0 R +/Next 214 0 R +/A 673 0 R +>> +endobj + +214 0 obj +<< +/Title ($1N1682) +/Parent 205 0 R +/Prev 213 0 R +/Next 215 0 R +/A 675 0 R +>> +endobj + +215 0 obj +<< +/Title ($1N1706) +/Parent 205 0 R +/Prev 214 0 R +/Next 216 0 R +/A 677 0 R +>> +endobj + +216 0 obj +<< +/Title ($1N1738) +/Parent 205 0 R +/Prev 215 0 R +/Next 217 0 R +/A 679 0 R +>> +endobj + +217 0 obj +<< +/Title ($1N1748) +/Parent 205 0 R +/Prev 216 0 R +/Next 218 0 R +/A 681 0 R +>> +endobj + +218 0 obj +<< +/Title ($1N1772) +/Parent 205 0 R +/Prev 217 0 R +/Next 219 0 R +/A 683 0 R +>> +endobj + +219 0 obj +<< +/Title ($1N1816) +/Parent 205 0 R +/Prev 218 0 R +/Next 220 0 R +/A 685 0 R +>> +endobj + +220 0 obj +<< +/Title ($1N1868) +/Parent 205 0 R +/Prev 219 0 R +/A 687 0 R +>> +endobj + +222 0 obj +<< +/Title ($1N1818) +/Parent 221 0 R +/A 689 0 R +>> +endobj + +224 0 obj +<< +/Title ($1N7) +/Parent 223 0 R +/Next 225 0 R +/A 691 0 R +>> +endobj + +225 0 obj +<< +/Title ($1N52) +/Parent 223 0 R +/Prev 224 0 R +/Next 226 0 R +/A 693 0 R +>> +endobj + +226 0 obj +<< +/Title ($1N1548) +/Parent 223 0 R +/Prev 225 0 R +/Next 227 0 R +/A 695 0 R +>> +endobj + +227 0 obj +<< +/Title ($1N1560) +/Parent 223 0 R +/Prev 226 0 R +/Next 228 0 R +/A 697 0 R +>> +endobj + +228 0 obj +<< +/Title ($1N1608) +/Parent 223 0 R +/Prev 227 0 R +/Next 229 0 R +/A 699 0 R +>> +endobj + +229 0 obj +<< +/Title ($1N1630) +/Parent 223 0 R +/Prev 228 0 R +/Next 230 0 R +/A 701 0 R +>> +endobj + +230 0 obj +<< +/Title ($1N1666) +/Parent 223 0 R +/Prev 229 0 R +/Next 231 0 R +/A 703 0 R +>> +endobj + +231 0 obj +<< +/Title ($1N1690) +/Parent 223 0 R +/Prev 230 0 R +/Next 232 0 R +/A 705 0 R +>> +endobj + +232 0 obj +<< +/Title ($1N1714) +/Parent 223 0 R +/Prev 231 0 R +/Next 233 0 R +/A 707 0 R +>> +endobj + +233 0 obj +<< +/Title ($1N1720) +/Parent 223 0 R +/Prev 232 0 R +/Next 234 0 R +/A 709 0 R +>> +endobj + +234 0 obj +<< +/Title ($1N1756) +/Parent 223 0 R +/Prev 233 0 R +/Next 235 0 R +/A 711 0 R +>> +endobj + +235 0 obj +<< +/Title ($1N1782) +/Parent 223 0 R +/Prev 234 0 R +/Next 236 0 R +/A 713 0 R +>> +endobj + +236 0 obj +<< +/Title ($1N1804) +/Parent 223 0 R +/Prev 235 0 R +/Next 237 0 R +/A 715 0 R +>> +endobj + +237 0 obj +<< +/Title ($1N1834) +/Parent 223 0 R +/Prev 236 0 R +/Next 238 0 R +/A 717 0 R +>> +endobj + +238 0 obj +<< +/Title ($1N1880) +/Parent 223 0 R +/Prev 237 0 R +/A 719 0 R +>> +endobj + +240 0 obj +<< +/Title ($1N8) +/Parent 239 0 R +/Next 241 0 R +/A 721 0 R +>> +endobj + +241 0 obj +<< +/Title ($1N51) +/Parent 239 0 R +/Prev 240 0 R +/Next 242 0 R +/A 723 0 R +>> +endobj + +242 0 obj +<< +/Title ($1N1546) +/Parent 239 0 R +/Prev 241 0 R +/Next 243 0 R +/A 725 0 R +>> +endobj + +243 0 obj +<< +/Title ($1N1562) +/Parent 239 0 R +/Prev 242 0 R +/Next 244 0 R +/A 727 0 R +>> +endobj + +244 0 obj +<< +/Title ($1N1606) +/Parent 239 0 R +/Prev 243 0 R +/Next 245 0 R +/A 729 0 R +>> +endobj + +245 0 obj +<< +/Title ($1N1628) +/Parent 239 0 R +/Prev 244 0 R +/Next 246 0 R +/A 731 0 R +>> +endobj + +246 0 obj +<< +/Title ($1N1668) +/Parent 239 0 R +/Prev 245 0 R +/Next 247 0 R +/A 733 0 R +>> +endobj + +247 0 obj +<< +/Title ($1N1688) +/Parent 239 0 R +/Prev 246 0 R +/Next 248 0 R +/A 735 0 R +>> +endobj + +248 0 obj +<< +/Title ($1N1716) +/Parent 239 0 R +/Prev 247 0 R +/Next 249 0 R +/A 737 0 R +>> +endobj + +249 0 obj +<< +/Title ($1N1722) +/Parent 239 0 R +/Prev 248 0 R +/Next 250 0 R +/A 739 0 R +>> +endobj + +250 0 obj +<< +/Title ($1N1754) +/Parent 239 0 R +/Prev 249 0 R +/Next 251 0 R +/A 741 0 R +>> +endobj + +251 0 obj +<< +/Title ($1N1784) +/Parent 239 0 R +/Prev 250 0 R +/Next 252 0 R +/A 743 0 R +>> +endobj + +252 0 obj +<< +/Title ($1N1802) +/Parent 239 0 R +/Prev 251 0 R +/Next 253 0 R +/A 745 0 R +>> +endobj + +253 0 obj +<< +/Title ($1N1832) +/Parent 239 0 R +/Prev 252 0 R +/Next 254 0 R +/A 747 0 R +>> +endobj + +254 0 obj +<< +/Title ($1N1882) +/Parent 239 0 R +/Prev 253 0 R +/Next 255 0 R +/A 749 0 R +>> +endobj + +255 0 obj +<< +/Title ($1N5374) +/Parent 239 0 R +/Prev 254 0 R +/Next 256 0 R +/A 751 0 R +>> +endobj + +256 0 obj +<< +/Title ($1N5481) +/Parent 239 0 R +/Prev 255 0 R +/A 753 0 R +>> +endobj + +258 0 obj +<< +/Title ($1N13) +/Parent 257 0 R +/Next 259 0 R +/A 755 0 R +>> +endobj + +259 0 obj +<< +/Title ($1N34) +/Parent 257 0 R +/Prev 258 0 R +/Next 260 0 R +/A 757 0 R +>> +endobj + +260 0 obj +<< +/Title ($1N46) +/Parent 257 0 R +/Prev 259 0 R +/Next 261 0 R +/A 759 0 R +>> +endobj + +261 0 obj +<< +/Title ($1N1536) +/Parent 257 0 R +/Prev 260 0 R +/Next 262 0 R +/A 761 0 R +>> +endobj + +262 0 obj +<< +/Title ($1N1558) +/Parent 257 0 R +/Prev 261 0 R +/Next 263 0 R +/A 763 0 R +>> +endobj + +263 0 obj +<< +/Title ($1N1612) +/Parent 257 0 R +/Prev 262 0 R +/Next 264 0 R +/A 765 0 R +>> +endobj + +264 0 obj +<< +/Title ($1N1620) +/Parent 257 0 R +/Prev 263 0 R +/Next 265 0 R +/A 767 0 R +>> +endobj + +265 0 obj +<< +/Title ($1N1676) +/Parent 257 0 R +/Prev 264 0 R +/Next 266 0 R +/A 769 0 R +>> +endobj + +266 0 obj +<< +/Title ($1N1680) +/Parent 257 0 R +/Prev 265 0 R +/Next 267 0 R +/A 771 0 R +>> +endobj + +267 0 obj +<< +/Title ($1N1704) +/Parent 257 0 R +/Prev 266 0 R +/Next 268 0 R +/A 773 0 R +>> +endobj + +268 0 obj +<< +/Title ($1N1726) +/Parent 257 0 R +/Prev 267 0 R +/Next 269 0 R +/A 775 0 R +>> +endobj + +269 0 obj +<< +/Title ($1N1746) +/Parent 257 0 R +/Prev 268 0 R +/Next 270 0 R +/A 777 0 R +>> +endobj + +270 0 obj +<< +/Title ($1N1780) +/Parent 257 0 R +/Prev 269 0 R +/Next 271 0 R +/A 779 0 R +>> +endobj + +271 0 obj +<< +/Title ($1N1812) +/Parent 257 0 R +/Prev 270 0 R +/Next 272 0 R +/A 781 0 R +>> +endobj + +272 0 obj +<< +/Title ($1N1872) +/Parent 257 0 R +/Prev 271 0 R +/A 783 0 R +>> +endobj + +274 0 obj +<< +/Title ($1N14) +/Parent 273 0 R +/Next 275 0 R +/A 785 0 R +>> +endobj + +275 0 obj +<< +/Title ($1N45) +/Parent 273 0 R +/Prev 274 0 R +/Next 276 0 R +/A 787 0 R +>> +endobj + +276 0 obj +<< +/Title ($1N1520) +/Parent 273 0 R +/Prev 275 0 R +/A 789 0 R +>> +endobj + +278 0 obj +<< +/Title ($1N4) +/Parent 277 0 R +/Next 279 0 R +/A 791 0 R +>> +endobj + +279 0 obj +<< +/Title ($1N56) +/Parent 277 0 R +/Prev 278 0 R +/Next 280 0 R +/A 793 0 R +>> +endobj + +280 0 obj +<< +/Title ($1N57) +/Parent 277 0 R +/Prev 279 0 R +/Next 281 0 R +/A 795 0 R +>> +endobj + +281 0 obj +<< +/Title ($1N1634) +/Parent 277 0 R +/Prev 280 0 R +/Next 282 0 R +/A 797 0 R +>> +endobj + +282 0 obj +<< +/Title ($1N1662) +/Parent 277 0 R +/Prev 281 0 R +/Next 283 0 R +/A 799 0 R +>> +endobj + +283 0 obj +<< +/Title ($1N1694) +/Parent 277 0 R +/Prev 282 0 R +/Next 284 0 R +/A 801 0 R +>> +endobj + +284 0 obj +<< +/Title ($1N1794) +/Parent 277 0 R +/Prev 283 0 R +/A 803 0 R +>> +endobj + +286 0 obj +<< +/Title ($1N9) +/Parent 285 0 R +/Next 287 0 R +/A 805 0 R +>> +endobj + +287 0 obj +<< +/Title ($1N50) +/Parent 285 0 R +/Prev 286 0 R +/Next 288 0 R +/A 807 0 R +>> +endobj + +288 0 obj +<< +/Title ($1N1544) +/Parent 285 0 R +/Prev 287 0 R +/Next 289 0 R +/A 809 0 R +>> +endobj + +289 0 obj +<< +/Title ($1N1564) +/Parent 285 0 R +/Prev 288 0 R +/Next 290 0 R +/A 811 0 R +>> +endobj + +290 0 obj +<< +/Title ($1N1604) +/Parent 285 0 R +/Prev 289 0 R +/Next 291 0 R +/A 813 0 R +>> +endobj + +291 0 obj +<< +/Title ($1N1632) +/Parent 285 0 R +/Prev 290 0 R +/Next 292 0 R +/A 815 0 R +>> +endobj + +292 0 obj +<< +/Title ($1N1664) +/Parent 285 0 R +/Prev 291 0 R +/Next 293 0 R +/A 817 0 R +>> +endobj + +293 0 obj +<< +/Title ($1N1692) +/Parent 285 0 R +/Prev 292 0 R +/Next 294 0 R +/A 819 0 R +>> +endobj + +294 0 obj +<< +/Title ($1N1712) +/Parent 285 0 R +/Prev 293 0 R +/Next 295 0 R +/A 821 0 R +>> +endobj + +295 0 obj +<< +/Title ($1N1724) +/Parent 285 0 R +/Prev 294 0 R +/Next 296 0 R +/A 823 0 R +>> +endobj + +296 0 obj +<< +/Title ($1N1758) +/Parent 285 0 R +/Prev 295 0 R +/Next 297 0 R +/A 825 0 R +>> +endobj + +297 0 obj +<< +/Title ($1N1786) +/Parent 285 0 R +/Prev 296 0 R +/Next 298 0 R +/A 827 0 R +>> +endobj + +298 0 obj +<< +/Title ($1N1806) +/Parent 285 0 R +/Prev 297 0 R +/Next 299 0 R +/A 829 0 R +>> +endobj + +299 0 obj +<< +/Title ($1N1830) +/Parent 285 0 R +/Prev 298 0 R +/Next 300 0 R +/A 831 0 R +>> +endobj + +300 0 obj +<< +/Title ($1N1878) +/Parent 285 0 R +/Prev 299 0 R +/Next 301 0 R +/A 833 0 R +>> +endobj + +301 0 obj +<< +/Title ($1N5358) +/Parent 285 0 R +/Prev 300 0 R +/Next 302 0 R +/A 835 0 R +>> +endobj + +302 0 obj +<< +/Title ($1N5478) +/Parent 285 0 R +/Prev 301 0 R +/A 837 0 R +>> +endobj + +304 0 obj +<< +/Title ($1N17) +/Parent 303 0 R +/Next 305 0 R +/A 839 0 R +>> +endobj + +305 0 obj +<< +/Title ($1N42) +/Parent 303 0 R +/Prev 304 0 R +/A 841 0 R +>> +endobj + +307 0 obj +<< +/Title ($1N16) +/Parent 306 0 R +/Next 308 0 R +/A 843 0 R +>> +endobj + +308 0 obj +<< +/Title ($1N43) +/Parent 306 0 R +/Prev 307 0 R +/A 845 0 R +>> +endobj + +310 0 obj +<< +/Title ($1N1498) +/Parent 309 0 R +/Next 311 0 R +/A 847 0 R +>> +endobj + +311 0 obj +<< +/Title ($1N1512) +/Parent 309 0 R +/Prev 310 0 R +/A 849 0 R +>> +endobj + +313 0 obj +<< +/Title ($1N1496) +/Parent 312 0 R +/Next 314 0 R +/A 851 0 R +>> +endobj + +314 0 obj +<< +/Title ($1N1510) +/Parent 312 0 R +/Prev 313 0 R +/A 853 0 R +>> +endobj + +316 0 obj +<< +/Title ($1N18) +/Parent 315 0 R +/Next 317 0 R +/A 855 0 R +>> +endobj + +317 0 obj +<< +/Title ($1N41) +/Parent 315 0 R +/Prev 316 0 R +/Next 318 0 R +/A 857 0 R +>> +endobj + +318 0 obj +<< +/Title ($1N1522) +/Parent 315 0 R +/Prev 317 0 R +/A 859 0 R +>> +endobj + +861 0 obj +<< +/Producer (jsPDF 0.0.0) +/CreationDate (D:20251108000128-00'00') +>> +endobj +862 0 obj +<< +/Type /Catalog +/Pages 1 0 R +/OpenAction [3 0 R /FitH null] +/PageLayout /OneColumn +/Outlines 12 0 R +>> +endobj +xref +0 863 +0000000000 65535 f +0000334529 00000 n +0000396345 00000 n +0000000015 00000 n +0000000125 00000 n +0000334586 00000 n +0000334751 00000 n +0000334930 00000 n +0000335046 00000 n +0000335215 00000 n +0000336259 00000 n +0000393129 00000 n +0000527015 00000 n +0000527093 00000 n +0000527300 00000 n +0000527196 00000 n +0000527411 00000 n +0000531215 00000 n +0000531292 00000 n +0000531383 00000 n +0000531474 00000 n +0000531565 00000 n +0000531658 00000 n +0000531751 00000 n +0000531844 00000 n +0000531937 00000 n +0000532030 00000 n +0000532123 00000 n +0000532216 00000 n +0000532309 00000 n +0000532402 00000 n +0000532495 00000 n +0000532588 00000 n +0000532681 00000 n +0000532774 00000 n +0000527513 00000 n +0000532854 00000 n +0000532932 00000 n +0000533023 00000 n +0000527627 00000 n +0000533103 00000 n +0000527742 00000 n +0000533167 00000 n +0000527858 00000 n +0000533232 00000 n +0000527974 00000 n +0000533297 00000 n +0000533375 00000 n +0000533466 00000 n +0000533559 00000 n +0000528088 00000 n +0000533639 00000 n +0000533719 00000 n +0000533812 00000 n +0000533905 00000 n +0000533998 00000 n +0000534091 00000 n +0000528203 00000 n +0000534171 00000 n +0000534249 00000 n +0000534340 00000 n +0000534433 00000 n +0000534526 00000 n +0000534619 00000 n +0000534712 00000 n +0000534805 00000 n +0000534898 00000 n +0000534991 00000 n +0000535084 00000 n +0000535177 00000 n +0000535270 00000 n +0000535363 00000 n +0000535456 00000 n +0000528319 00000 n +0000535536 00000 n +0000535614 00000 n +0000535705 00000 n +0000535798 00000 n +0000535891 00000 n +0000535984 00000 n +0000536077 00000 n +0000536170 00000 n +0000536263 00000 n +0000536356 00000 n +0000536449 00000 n +0000536542 00000 n +0000536635 00000 n +0000536728 00000 n +0000536821 00000 n +0000528433 00000 n +0000536901 00000 n +0000536981 00000 n +0000537074 00000 n +0000537167 00000 n +0000537260 00000 n +0000537353 00000 n +0000537446 00000 n +0000537539 00000 n +0000537632 00000 n +0000537725 00000 n +0000537819 00000 n +0000537914 00000 n +0000528551 00000 n +0000537996 00000 n +0000528670 00000 n +0000538065 00000 n +0000538148 00000 n +0000538245 00000 n +0000538342 00000 n +0000528796 00000 n +0000538425 00000 n +0000538508 00000 n +0000538605 00000 n +0000528920 00000 n +0000538688 00000 n +0000538771 00000 n +0000538868 00000 n +0000529045 00000 n +0000538951 00000 n +0000539034 00000 n +0000539131 00000 n +0000529171 00000 n +0000539214 00000 n +0000539294 00000 n +0000539388 00000 n +0000539483 00000 n +0000539578 00000 n +0000539673 00000 n +0000539768 00000 n +0000539863 00000 n +0000539958 00000 n +0000540053 00000 n +0000540148 00000 n +0000540243 00000 n +0000540338 00000 n +0000540433 00000 n +0000540528 00000 n +0000540623 00000 n +0000540720 00000 n +0000540817 00000 n +0000540914 00000 n +0000541011 00000 n +0000541108 00000 n +0000541205 00000 n +0000541302 00000 n +0000541399 00000 n +0000541496 00000 n +0000541593 00000 n +0000541690 00000 n +0000541787 00000 n +0000541884 00000 n +0000541981 00000 n +0000542078 00000 n +0000542175 00000 n +0000542272 00000 n +0000542369 00000 n +0000542466 00000 n +0000542563 00000 n +0000542660 00000 n +0000542757 00000 n +0000542854 00000 n +0000542951 00000 n +0000543048 00000 n +0000543145 00000 n +0000543242 00000 n +0000543339 00000 n +0000543436 00000 n +0000543533 00000 n +0000543630 00000 n +0000543727 00000 n +0000543824 00000 n +0000543921 00000 n +0000544018 00000 n +0000544115 00000 n +0000544212 00000 n +0000544309 00000 n +0000544406 00000 n +0000544503 00000 n +0000544600 00000 n +0000544697 00000 n +0000544794 00000 n +0000544891 00000 n +0000544988 00000 n +0000545085 00000 n +0000545182 00000 n +0000545279 00000 n +0000545376 00000 n +0000545473 00000 n +0000545570 00000 n +0000545667 00000 n +0000545764 00000 n +0000545861 00000 n +0000545958 00000 n +0000546055 00000 n +0000546152 00000 n +0000546249 00000 n +0000529291 00000 n +0000546332 00000 n +0000546413 00000 n +0000529412 00000 n +0000546494 00000 n +0000546575 00000 n +0000529533 00000 n +0000546656 00000 n +0000546737 00000 n +0000529654 00000 n +0000546818 00000 n +0000546899 00000 n +0000546994 00000 n +0000547089 00000 n +0000547186 00000 n +0000547283 00000 n +0000547380 00000 n +0000547477 00000 n +0000547574 00000 n +0000547671 00000 n +0000547768 00000 n +0000547865 00000 n +0000547962 00000 n +0000548059 00000 n +0000548156 00000 n +0000529774 00000 n +0000548239 00000 n +0000529898 00000 n +0000548308 00000 n +0000548388 00000 n +0000548483 00000 n +0000548580 00000 n +0000548677 00000 n +0000548774 00000 n +0000548871 00000 n +0000548968 00000 n +0000549065 00000 n +0000549162 00000 n +0000549259 00000 n +0000549356 00000 n +0000549453 00000 n +0000549550 00000 n +0000549647 00000 n +0000530019 00000 n +0000549730 00000 n +0000549810 00000 n +0000549905 00000 n +0000550002 00000 n +0000550099 00000 n +0000550196 00000 n +0000550293 00000 n +0000550390 00000 n +0000550487 00000 n +0000550584 00000 n +0000550681 00000 n +0000550778 00000 n +0000550875 00000 n +0000550972 00000 n +0000551069 00000 n +0000551166 00000 n +0000551263 00000 n +0000530140 00000 n +0000551346 00000 n +0000551427 00000 n +0000551522 00000 n +0000551617 00000 n +0000551714 00000 n +0000551811 00000 n +0000551908 00000 n +0000552005 00000 n +0000552102 00000 n +0000552199 00000 n +0000552296 00000 n +0000552393 00000 n +0000552490 00000 n +0000552587 00000 n +0000552684 00000 n +0000530261 00000 n +0000552767 00000 n +0000552848 00000 n +0000552943 00000 n +0000530381 00000 n +0000553026 00000 n +0000553106 00000 n +0000553201 00000 n +0000553296 00000 n +0000553393 00000 n +0000553490 00000 n +0000553587 00000 n +0000530501 00000 n +0000553670 00000 n +0000553750 00000 n +0000553845 00000 n +0000553942 00000 n +0000554039 00000 n +0000554136 00000 n +0000554233 00000 n +0000554330 00000 n +0000554427 00000 n +0000554524 00000 n +0000554621 00000 n +0000554718 00000 n +0000554815 00000 n +0000554912 00000 n +0000555009 00000 n +0000555106 00000 n +0000555203 00000 n +0000530621 00000 n +0000555286 00000 n +0000555367 00000 n +0000530740 00000 n +0000555448 00000 n +0000555529 00000 n +0000530859 00000 n +0000555610 00000 n +0000555693 00000 n +0000530984 00000 n +0000555776 00000 n +0000555859 00000 n +0000531109 00000 n +0000555942 00000 n +0000556023 00000 n +0000556118 00000 n +0000396470 00000 n +0000396534 00000 n +0000396980 00000 n +0000397044 00000 n +0000397438 00000 n +0000397502 00000 n +0000397911 00000 n +0000397975 00000 n +0000398408 00000 n +0000398472 00000 n +0000398878 00000 n +0000398942 00000 n +0000399335 00000 n +0000399399 00000 n +0000399831 00000 n +0000399895 00000 n +0000400288 00000 n +0000400352 00000 n +0000400796 00000 n +0000400860 00000 n +0000401304 00000 n +0000401368 00000 n +0000401797 00000 n +0000401861 00000 n +0000402268 00000 n +0000402332 00000 n +0000402727 00000 n +0000402791 00000 n +0000403198 00000 n +0000403262 00000 n +0000403666 00000 n +0000403730 00000 n +0000404170 00000 n +0000404234 00000 n +0000404692 00000 n +0000404756 00000 n +0000405214 00000 n +0000405278 00000 n +0000405674 00000 n +0000405738 00000 n +0000406156 00000 n +0000406220 00000 n +0000406615 00000 n +0000406679 00000 n +0000407109 00000 n +0000407173 00000 n +0000407617 00000 n +0000407681 00000 n +0000408112 00000 n +0000408176 00000 n +0000408596 00000 n +0000408660 00000 n +0000409081 00000 n +0000409145 00000 n +0000409553 00000 n +0000409617 00000 n +0000410025 00000 n +0000410089 00000 n +0000410487 00000 n +0000410551 00000 n +0000410947 00000 n +0000411011 00000 n +0000411418 00000 n +0000411482 00000 n +0000411917 00000 n +0000411981 00000 n +0000412391 00000 n +0000412455 00000 n +0000412865 00000 n +0000412929 00000 n +0000413338 00000 n +0000413402 00000 n +0000413813 00000 n +0000413877 00000 n +0000414283 00000 n +0000414347 00000 n +0000414742 00000 n +0000414806 00000 n +0000415221 00000 n +0000415285 00000 n +0000415714 00000 n +0000415778 00000 n +0000416183 00000 n +0000416247 00000 n +0000416676 00000 n +0000416740 00000 n +0000417195 00000 n +0000417259 00000 n +0000417688 00000 n +0000417752 00000 n +0000418169 00000 n +0000418233 00000 n +0000418628 00000 n +0000418692 00000 n +0000419099 00000 n +0000419163 00000 n +0000419556 00000 n +0000419620 00000 n +0000420049 00000 n +0000420113 00000 n +0000420556 00000 n +0000420620 00000 n +0000421065 00000 n +0000421129 00000 n +0000421524 00000 n +0000421588 00000 n +0000422005 00000 n +0000422069 00000 n +0000422486 00000 n +0000422550 00000 n +0000422979 00000 n +0000423043 00000 n +0000423448 00000 n +0000423512 00000 n +0000423930 00000 n +0000423994 00000 n +0000424449 00000 n +0000424513 00000 n +0000424908 00000 n +0000424972 00000 n +0000425389 00000 n +0000425453 00000 n +0000425848 00000 n +0000425912 00000 n +0000426319 00000 n +0000426383 00000 n +0000426778 00000 n +0000426842 00000 n +0000427259 00000 n +0000427323 00000 n +0000427752 00000 n +0000427816 00000 n +0000428282 00000 n +0000428346 00000 n +0000428764 00000 n +0000428828 00000 n +0000429272 00000 n +0000429336 00000 n +0000429743 00000 n +0000429807 00000 n +0000430251 00000 n +0000430315 00000 n +0000430710 00000 n +0000430774 00000 n +0000431181 00000 n +0000431245 00000 n +0000431640 00000 n +0000431704 00000 n +0000432122 00000 n +0000432186 00000 n +0000432613 00000 n +0000432677 00000 n +0000433106 00000 n +0000433170 00000 n +0000433565 00000 n +0000433629 00000 n +0000434036 00000 n +0000434100 00000 n +0000434518 00000 n +0000434582 00000 n +0000434977 00000 n +0000435041 00000 n +0000435473 00000 n +0000435537 00000 n +0000435942 00000 n +0000436006 00000 n +0000436401 00000 n +0000436465 00000 n +0000436897 00000 n +0000436961 00000 n +0000437405 00000 n +0000437469 00000 n +0000437901 00000 n +0000437965 00000 n +0000438434 00000 n +0000438498 00000 n +0000438942 00000 n +0000439006 00000 n +0000439399 00000 n +0000439463 00000 n +0000439893 00000 n +0000439957 00000 n +0000440353 00000 n +0000440417 00000 n +0000440840 00000 n +0000440904 00000 n +0000441301 00000 n +0000441365 00000 n +0000441820 00000 n +0000441884 00000 n +0000442281 00000 n +0000442345 00000 n +0000442791 00000 n +0000442855 00000 n +0000443274 00000 n +0000443338 00000 n +0000443770 00000 n +0000443834 00000 n +0000444264 00000 n +0000444328 00000 n +0000444761 00000 n +0000444825 00000 n +0000445233 00000 n +0000445297 00000 n +0000445707 00000 n +0000445771 00000 n +0000446212 00000 n +0000446276 00000 n +0000446679 00000 n +0000446743 00000 n +0000447163 00000 n +0000447227 00000 n +0000447659 00000 n +0000447723 00000 n +0000448192 00000 n +0000448256 00000 n +0000448687 00000 n +0000448751 00000 n +0000449166 00000 n +0000449230 00000 n +0000449623 00000 n +0000449687 00000 n +0000450116 00000 n +0000450180 00000 n +0000450586 00000 n +0000450650 00000 n +0000451067 00000 n +0000451131 00000 n +0000451546 00000 n +0000451610 00000 n +0000452042 00000 n +0000452106 00000 n +0000452501 00000 n +0000452565 00000 n +0000453006 00000 n +0000453070 00000 n +0000453485 00000 n +0000453549 00000 n +0000453966 00000 n +0000454030 00000 n +0000454447 00000 n +0000454511 00000 n +0000454928 00000 n +0000454992 00000 n +0000455387 00000 n +0000455451 00000 n +0000455846 00000 n +0000455910 00000 n +0000456305 00000 n +0000456369 00000 n +0000456762 00000 n +0000456826 00000 n +0000457233 00000 n +0000457297 00000 n +0000457726 00000 n +0000457790 00000 n +0000458207 00000 n +0000458271 00000 n +0000458689 00000 n +0000458753 00000 n +0000459193 00000 n +0000459257 00000 n +0000459712 00000 n +0000459776 00000 n +0000460231 00000 n +0000460295 00000 n +0000460724 00000 n +0000460788 00000 n +0000461204 00000 n +0000461268 00000 n +0000461661 00000 n +0000461725 00000 n +0000462130 00000 n +0000462194 00000 n +0000462626 00000 n +0000462690 00000 n +0000463097 00000 n +0000463161 00000 n +0000463568 00000 n +0000463632 00000 n +0000464024 00000 n +0000464088 00000 n +0000464505 00000 n +0000464569 00000 n +0000465009 00000 n +0000465073 00000 n +0000465480 00000 n +0000465544 00000 n +0000465973 00000 n +0000466037 00000 n +0000466432 00000 n +0000466496 00000 n +0000466891 00000 n +0000466955 00000 n +0000467350 00000 n +0000467414 00000 n +0000467809 00000 n +0000467873 00000 n +0000468268 00000 n +0000468332 00000 n +0000468739 00000 n +0000468803 00000 n +0000469198 00000 n +0000469262 00000 n +0000469657 00000 n +0000469721 00000 n +0000470116 00000 n +0000470180 00000 n +0000470575 00000 n +0000470639 00000 n +0000471032 00000 n +0000471096 00000 n +0000471489 00000 n +0000471553 00000 n +0000472022 00000 n +0000472086 00000 n +0000472530 00000 n +0000472594 00000 n +0000473038 00000 n +0000473102 00000 n +0000473553 00000 n +0000473617 00000 n +0000474036 00000 n +0000474100 00000 n +0000474507 00000 n +0000474571 00000 n +0000474964 00000 n +0000475028 00000 n +0000475458 00000 n +0000475522 00000 n +0000475957 00000 n +0000476021 00000 n +0000476452 00000 n +0000476516 00000 n +0000476928 00000 n +0000476992 00000 n +0000477400 00000 n +0000477464 00000 n +0000477887 00000 n +0000477951 00000 n +0000478370 00000 n +0000478434 00000 n +0000478843 00000 n +0000478907 00000 n +0000479340 00000 n +0000479404 00000 n +0000479815 00000 n +0000479879 00000 n +0000480272 00000 n +0000480336 00000 n +0000480731 00000 n +0000480795 00000 n +0000481190 00000 n +0000481254 00000 n +0000481671 00000 n +0000481735 00000 n +0000482128 00000 n +0000482192 00000 n +0000482599 00000 n +0000482663 00000 n +0000483070 00000 n +0000483134 00000 n +0000483541 00000 n +0000483605 00000 n +0000484045 00000 n +0000484109 00000 n +0000484504 00000 n +0000484568 00000 n +0000484975 00000 n +0000485039 00000 n +0000485490 00000 n +0000485554 00000 n +0000485961 00000 n +0000486025 00000 n +0000486445 00000 n +0000486509 00000 n +0000486931 00000 n +0000486995 00000 n +0000487388 00000 n +0000487452 00000 n +0000487906 00000 n +0000487970 00000 n +0000488387 00000 n +0000488451 00000 n +0000488880 00000 n +0000488944 00000 n +0000489349 00000 n +0000489413 00000 n +0000489820 00000 n +0000489884 00000 n +0000490328 00000 n +0000490392 00000 n +0000490799 00000 n +0000490863 00000 n +0000491280 00000 n +0000491344 00000 n +0000491776 00000 n +0000491840 00000 n +0000492247 00000 n +0000492311 00000 n +0000492706 00000 n +0000492770 00000 n +0000493199 00000 n +0000493263 00000 n +0000493695 00000 n +0000493759 00000 n +0000494193 00000 n +0000494257 00000 n +0000494686 00000 n +0000494750 00000 n +0000495165 00000 n +0000495229 00000 n +0000495683 00000 n +0000495747 00000 n +0000496187 00000 n +0000496251 00000 n +0000496667 00000 n +0000496731 00000 n +0000497126 00000 n +0000497190 00000 n +0000497622 00000 n +0000497686 00000 n +0000498115 00000 n +0000498179 00000 n +0000498608 00000 n +0000498672 00000 n +0000499067 00000 n +0000499131 00000 n +0000499526 00000 n +0000499590 00000 n +0000499985 00000 n +0000500049 00000 n +0000500500 00000 n +0000500564 00000 n +0000500959 00000 n +0000501023 00000 n +0000501455 00000 n +0000501519 00000 n +0000501939 00000 n +0000502003 00000 n +0000502424 00000 n +0000502488 00000 n +0000502910 00000 n +0000502974 00000 n +0000503380 00000 n +0000503444 00000 n +0000503876 00000 n +0000503940 00000 n +0000504335 00000 n +0000504399 00000 n +0000504850 00000 n +0000504914 00000 n +0000505341 00000 n +0000505405 00000 n +0000505837 00000 n +0000505901 00000 n +0000506308 00000 n +0000506372 00000 n +0000506790 00000 n +0000506854 00000 n +0000507283 00000 n +0000507347 00000 n +0000507742 00000 n +0000507806 00000 n +0000508201 00000 n +0000508265 00000 n +0000508705 00000 n +0000508769 00000 n +0000509201 00000 n +0000509265 00000 n +0000509699 00000 n +0000509763 00000 n +0000510153 00000 n +0000510217 00000 n +0000510611 00000 n +0000510675 00000 n +0000511082 00000 n +0000511146 00000 n +0000511556 00000 n +0000511620 00000 n +0000512038 00000 n +0000512102 00000 n +0000512557 00000 n +0000512621 00000 n +0000513087 00000 n +0000513151 00000 n +0000513558 00000 n +0000513622 00000 n +0000514029 00000 n +0000514093 00000 n +0000514502 00000 n +0000514566 00000 n +0000514972 00000 n +0000515036 00000 n +0000515453 00000 n +0000515517 00000 n +0000515934 00000 n +0000515998 00000 n +0000516415 00000 n +0000516479 00000 n +0000516872 00000 n +0000516936 00000 n +0000517343 00000 n +0000517407 00000 n +0000517873 00000 n +0000517937 00000 n +0000518344 00000 n +0000518408 00000 n +0000518848 00000 n +0000518912 00000 n +0000519305 00000 n +0000519369 00000 n +0000519787 00000 n +0000519851 00000 n +0000520283 00000 n +0000520347 00000 n +0000520776 00000 n +0000520840 00000 n +0000521235 00000 n +0000521299 00000 n +0000521731 00000 n +0000521795 00000 n +0000522205 00000 n +0000522269 00000 n +0000522675 00000 n +0000522739 00000 n +0000523151 00000 n +0000523215 00000 n +0000523623 00000 n +0000523687 00000 n +0000524083 00000 n +0000524147 00000 n +0000524554 00000 n +0000524618 00000 n +0000525025 00000 n +0000525089 00000 n +0000525507 00000 n +0000525571 00000 n +0000526017 00000 n +0000526081 00000 n +0000526523 00000 n +0000526587 00000 n +0000556201 00000 n +0000556288 00000 n +trailer +<< +/Size 863 +/Root 862 0 R +/Info 861 0 R +/ID [ ] +>> +startxref +556410 +%%EOF diff --git a/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/readme.md b/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/readme.md index 5a78103ee..de76286b2 100644 --- a/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/readme.md +++ b/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/readme.md @@ -4,7 +4,9 @@ ## General -The pinout is contained in the variant.h file, and a [generic schematic](./Schematic_Pro-Micro_Pinouts%202024-12-14.pdf) is located in this directory. +The pinout is contained in the variant.h file, and a [generic schematic](./Schematic_Pro-Micro_Pinouts.pdf) is located in this directory. + +This variant is suitable for both TCXO and XTAL types of modules. The old XTAL variant has been removed to reduce confusion. ### Note on DIO2, RXEN, TXEN, and RF switching @@ -17,9 +19,13 @@ Several modules require external switching between transmit (Tx) and receive (Rx RXEN is not required to be connected if the selected module already has internal RF switching, or if external RF switching logic is already applied. Also worth noting that the Seeed WIO SX1262 in particular only has RXEN exposed (marked RF_SW) and has the DIO2-TXEN link internally. +## Making a node based on this variant + +Making your own node based on this design is straightforward. There are various open source and free to use PCB design files available, or you can solder wires directly from a module to the pro-micro. +
- The table of known modules is at the bottom of the variant.h, and reproduced here for convenience. + < Click to expand > The table of known modules is at the bottom of the variant.h, and reproduced here for convenience. | Mfr | Module | TCXO | RF Switch | Notes | | ------------ | ---------------- | ---- | --------- | ------------------------------------- | @@ -34,6 +40,7 @@ Also worth noting that the Seeed WIO SX1262 in particular only has RXEN exposed | Waveshare | Core1262-HF | yes | Ext | | | Waveshare | LoRa Node Module | yes | Int | | | Seeed | Wio-SX1262 | yes | Ext | Cute! DIO2/TXEN are not exposed | +| Seeed | Wio-LR1121 | yes | Int | LR1121, needs alternate rfswitch.h | | AI-Thinker | RA-02 | No | Int | SX1278 **433mhz band only** | | RF Solutions | RFM95 | No | Int | Untested | | Ebyte | E80-900M2213S | Yes | Int | LR1121 radio | @@ -72,6 +79,10 @@ The Semtech default, the values are (taken from [here](https://github.com/Lora-n
+ < Click to expand > + + + ```cpp .rfswitch = { .enable = LR11XX_SYSTEM_RFSW0_HIGH | LR11XX_SYSTEM_RFSW1_HIGH | LR11XX_SYSTEM_RFSW2_HIGH, diff --git a/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/variant.h b/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/variant.h index 87342a02f..fee8ee88e 100644 --- a/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/variant.h +++ b/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/variant.h @@ -175,6 +175,7 @@ settings. | Waveshare | Core1262-HF | yes | Ext | | | Waveshare | LoRa Node Module | yes | Int | | | Seeed | Wio-SX1262 | yes | Ext | Cute! DIO2/TXEN are not exposed | +| Seeed | Wio-LR1121 | yes | Int | LR1121, needs alternate rfswitch.h | | AI-Thinker | RA-02 | No | Int | SX1278 **433mhz band only** | | RF Solutions | RFM95 | No | Int | Untested | | Ebyte | E80-900M2213S | Yes | Int | LR1121 radio | diff --git a/variants/nrf52840/diy/nrf52_promicro_diy_xtal/platformio.ini b/variants/nrf52840/diy/nrf52_promicro_diy_xtal/platformio.ini deleted file mode 100644 index 278f578c5..000000000 --- a/variants/nrf52840/diy/nrf52_promicro_diy_xtal/platformio.ini +++ /dev/null @@ -1,12 +0,0 @@ -; Promicro + E22(0)-xxxMM / RA-01SH modules board variant - DIY - without TCXO -[env:nrf52_promicro_diy_xtal] -extends = nrf52840_base -board = promicro-nrf52840 -board_level = extra -build_flags = ${nrf52840_base.build_flags} - -I variants/nrf52840/diy/nrf52_promicro_diy_xtal - -D NRF52_PROMICRO_DIY -build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/diy/nrf52_promicro_diy_xtal> -lib_deps = - ${nrf52840_base.lib_deps} -debug_tool = jlink diff --git a/variants/nrf52840/diy/nrf52_promicro_diy_xtal/variant.cpp b/variants/nrf52840/diy/nrf52_promicro_diy_xtal/variant.cpp deleted file mode 100644 index 5869ed1d4..000000000 --- a/variants/nrf52840/diy/nrf52_promicro_diy_xtal/variant.cpp +++ /dev/null @@ -1,38 +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() -{ - // 3V3 Power Rail - pinMode(PIN_3V3_EN, OUTPUT); - digitalWrite(PIN_3V3_EN, HIGH); -} diff --git a/variants/nrf52840/diy/nrf52_promicro_diy_xtal/variant.h b/variants/nrf52840/diy/nrf52_promicro_diy_xtal/variant.h deleted file mode 100644 index 6e208e79f..000000000 --- a/variants/nrf52840/diy/nrf52_promicro_diy_xtal/variant.h +++ /dev/null @@ -1,154 +0,0 @@ -#ifndef _VARIANT_PROMICRO_DIY_ -#define _VARIANT_PROMICRO_DIY_ - -/** Master clock frequency */ -#define VARIANT_MCK (64000000ul) - -// #define USE_LFXO // Board uses 32khz crystal for LF -#define USE_LFRC // Board uses RC for LF - -#define PROMICRO_DIY_XTAL -/*---------------------------------------------------------------------------- - * Headers - *----------------------------------------------------------------------------*/ - -#include "WVariant.h" - -#ifdef __cplusplus -extern "C" { -#endif // __cplusplus - -/* -NRF52 PRO MICRO PIN ASSIGNMENT - -| Pin | Function | | Pin | Function | -|-------|------------|---|---------|-------------| -| Gnd | | | vbat | | -| P0.06 | Serial2 RX | | vbat | | -| P0.08 | Serial2 TX | | Gnd | | -| Gnd | | | reset | | -| Gnd | | | ext_vcc | *see 0.13 | -| P0.17 | RXEN | | P0.31 | BATTERY_PIN | -| P0.20 | GPS_RX | | P0.29 | BUSY | -| P0.22 | GPS_TX | | P0.02 | MISO | -| P0.24 | GPS_EN | | P1.15 | MOSI | -| P1.00 | BUTTON_PIN | | P1.13 | CS | -| P0.11 | SCL | | P1.11 | SCK | -| P1.04 | SDA | | P0.10 | DIO1/IRQ | -| P1.06 | Free pin | | P0.09 | RESET | -| | | | | | -| | Mid board | | | Internal | -| P1.01 | Free pin | | 0.15 | LED | -| P1.02 | Free pin | | 0.13 | 3V3_EN | -| P1.07 | Free pin | | | | -*/ - -// Number of pins defined in PinDescription array -#define PINS_COUNT (48) -#define NUM_DIGITAL_PINS (48) -#define NUM_ANALOG_INPUTS (1) -#define NUM_ANALOG_OUTPUTS (0) - -// Pin 13 enables 3.3V periphery. If the Lora module is on this pin, then it should stay enabled at all times. -#define PIN_3V3_EN (0 + 13) // P0.13 - -// Analog pins -#define BATTERY_PIN (0 + 31) // P0.31 Battery ADC -#define ADC_CHANNEL ADC1_GPIO4_CHANNEL -#define ADC_RESOLUTION 14 -#define BATTERY_SENSE_RESOLUTION_BITS 12 -#define BATTERY_SENSE_RESOLUTION 4096.0 -// Definition of milliVolt per LSB => 3.0V ADC range and 12-bit ADC resolution = 3000mV/4096 -#define VBAT_MV_PER_LSB (0.73242188F) -// Voltage divider value => 1.5M + 1M voltage divider on VBAT = (1.5M / (1M + 1.5M)) -#define VBAT_DIVIDER (0.6F) -// Compensation factor for the VBAT divider -#define VBAT_DIVIDER_COMP (1.73) -// Fixed calculation of milliVolt from compensation value -#define REAL_VBAT_MV_PER_LSB (VBAT_DIVIDER_COMP * VBAT_MV_PER_LSB) -#undef AREF_VOLTAGE -#define AREF_VOLTAGE 3.0 -#define VBAT_AR_INTERNAL AR_INTERNAL_3_0 -#define ADC_MULTIPLIER VBAT_DIVIDER_COMP // REAL_VBAT_MV_PER_LSB -#define VBAT_RAW_TO_SCALED(x) (REAL_VBAT_MV_PER_LSB * x) - -// WIRE IC AND IIC PINS -#define WIRE_INTERFACES_COUNT 1 - -#define PIN_WIRE_SDA (32 + 4) // P1.04 -#define PIN_WIRE_SCL (0 + 11) // P0.11 - -// LED -#define PIN_LED1 (0 + 15) // P0.15 -#define LED_BUILTIN PIN_LED1 -// Actually red -#define LED_BLUE PIN_LED1 -#define LED_STATE_ON 1 // State when LED is lit - -// Button -#define BUTTON_PIN (32 + 0) // P1.00 - -// GPS -#define PIN_GPS_TX (0 + 22) // P0.22 -#define PIN_GPS_RX (0 + 20) // P0.20 - -#define PIN_GPS_EN (0 + 24) // P0.24 -#define GPS_UBLOX -// define GPS_DEBUG - -// UART interfaces -#define PIN_SERIAL1_RX PIN_GPS_TX -#define PIN_SERIAL1_TX PIN_GPS_RX - -#define PIN_SERIAL2_RX (0 + 6) // P0.06 -#define PIN_SERIAL2_TX (0 + 8) // P0.08 - -// Serial interfaces -#define SPI_INTERFACES_COUNT 1 - -#define PIN_SPI_MISO (0 + 2) // P0.02 -#define PIN_SPI_MOSI (32 + 15) // P1.15 -#define PIN_SPI_SCK (32 + 11) // P1.11 - -// LORA MODULES -#define USE_LLCC68 -#define USE_SX1262 -// #define USE_RF95 -#define USE_SX1268 - -// LORA CONFIG -#define SX126X_CS (32 + 13) // P1.13 FIXME - we really should define LORA_CS instead -#define SX126X_DIO1 (0 + 10) // P0.10 IRQ -#define SX126X_DIO2_AS_RF_SWITCH // Note for E22 modules: DIO2 is not attached internally to TXEN for automatic TX/RX switching, - // so it needs connecting externally if it is used in this way -#define SX126X_BUSY (0 + 29) // P0.29 -#define SX126X_RESET (0 + 9) // P0.09 -#define SX126X_RXEN (0 + 17) // P0.17 -#define SX126X_TXEN RADIOLIB_NC // Assuming that DIO2 is connected to TXEN pin. If not, TXEN must be connected. - -/* -On the SX1262, DIO3 sets the voltage for an external TCXO, if one is present. If one is not present, then this should not be used. - -Ebyte -e22-900mm22s has no TCXO -e22-900m22s has TCXO -e220-900mm22s has no TCXO, works with/without this definition, looks like DIO3 not connected at all - -AI-thinker -RA-01SH does not have TCXO - -Waveshare -Core1262 has TCXO - -*/ -// #define SX126X_DIO3_TCXO_VOLTAGE 1.8 - -#ifdef __cplusplus -} -#endif - -/*---------------------------------------------------------------------------- - * Arduino objects - C++ only - *----------------------------------------------------------------------------*/ - -#endif \ No newline at end of file From b202559d3712c1b6e08731c620b10d97fa2ac7b4 Mon Sep 17 00:00:00 2001 From: Jason P Date: Sun, 16 Nov 2025 14:18:16 -0600 Subject: [PATCH 634/691] Add code for preserving favorites, also move to Home screen before reseting (#8647) --- src/graphics/draw/MenuHandler.cpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/graphics/draw/MenuHandler.cpp b/src/graphics/draw/MenuHandler.cpp index e1d309a10..7d70ec35a 100644 --- a/src/graphics/draw/MenuHandler.cpp +++ b/src/graphics/draw/MenuHandler.cpp @@ -790,17 +790,24 @@ void menuHandler::nodeNameLengthMenu() void menuHandler::resetNodeDBMenu() { - static const char *optionsArray[] = {"Back", "Confirm"}; + static const char *optionsArray[] = {"Back", "Reset All", "Preserve Favorites"}; BannerOverlayOptions bannerOptions; bannerOptions.message = "Confirm Reset NodeDB"; bannerOptions.optionsArrayPtr = optionsArray; - bannerOptions.optionsCount = 2; + bannerOptions.optionsCount = 3; bannerOptions.bannerCallback = [](int selected) -> void { - if (selected == 1) { + if (selected == 1 || selected == 2) { disableBluetooth(); + screen->setFrames(Screen::FOCUS_DEFAULT); + } + if (selected == 1) { LOG_INFO("Initiate node-db reset"); nodeDB->resetNodes(); rebootAtMsec = (millis() + DEFAULT_REBOOT_SECONDS * 1000); + } else if (selected == 2) { + LOG_INFO("Initiate node-db reset but keeping favorites"); + nodeDB->resetNodes(1); + rebootAtMsec = (millis() + DEFAULT_REBOOT_SECONDS * 1000); } }; screen->showOverlayBanner(bannerOptions); From d39d1917adf5fecdc59cbf5deb1548b55a0b9c6c Mon Sep 17 00:00:00 2001 From: omgbebebe Date: Tue, 18 Nov 2025 02:54:02 +0400 Subject: [PATCH 635/691] mqtt: do not try to send packets when it disconnected (#8658) --- src/mqtt/MQTT.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp index ad35e152a..2dcfd80e1 100644 --- a/src/mqtt/MQTT.cpp +++ b/src/mqtt/MQTT.cpp @@ -692,7 +692,7 @@ void MQTT::publishNodeInfo() } void MQTT::publishQueuedMessages() { - if (mqttQueue.isEmpty()) + if (mqttQueue.isEmpty() || !isConnected) return; if (!moduleConfig.mqtt.proxy_to_client_enabled && !isConnected) From 567b8ea1c2b2d100c24b0d6cbc437ec89fae0a56 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 14 Nov 2025 06:09:25 -0600 Subject: [PATCH 636/691] Automated version bumps (#8626) Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> --- bin/org.meshtastic.meshtasticd.metainfo.xml | 3 +++ debian/changelog | 6 ++++++ version.properties | 2 +- 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/bin/org.meshtastic.meshtasticd.metainfo.xml b/bin/org.meshtastic.meshtasticd.metainfo.xml index 61ecf9fb5..b59bb6202 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.15 + https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.14 diff --git a/debian/changelog b/debian/changelog index a387cc3c5..437e1645b 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +meshtasticd (2.7.15.0) unstable; urgency=medium + + * Version 2.7.15 + + -- GitHub Actions Thu, 13 Nov 2025 12:31:57 +0000 + meshtasticd (2.7.14.0) unstable; urgency=medium * Version 2.7.14 diff --git a/version.properties b/version.properties index fe1a5b31b..165f476df 100644 --- a/version.properties +++ b/version.properties @@ -1,4 +1,4 @@ [VERSION] major = 2 minor = 7 -build = 14 +build = 15 From d18f3f7a658817b35a3ab746d8522c1136890785 Mon Sep 17 00:00:00 2001 From: viric Date: Tue, 18 Nov 2025 18:23:39 +0100 Subject: [PATCH 637/691] Allow deepsleep in rak4630 and make it restart well when power comes back (#7882) * Make RAK4631 nodes power back on deep sleep The devices will hang if the VBAT goes under 1.7V (Brown-out reset) and they will never come back unless power supply goes completely off. This kills unattended nodes. Using the SystemOff the LPCOMP we can get the nodes back again when power comes back, even if VBAT goes under 1.7V, which moreover is more unlikely because the device is off. * Adding support for heltec t114 And moved particularities to variant.h * Remove old cpp comment that belongs to variant.h It was a leftover. * Trunk fix --------- Co-authored-by: Tom Fifield --- src/Power.cpp | 15 +++++-------- src/platform/nrf52/main-nrf52.cpp | 22 ++++++++++++++++++- src/power.h | 2 ++ .../nrf52840/heltec_mesh_node_t114/variant.h | 10 +++++++++ variants/nrf52840/rak4631/variant.h | 14 ++++++++++++ 5 files changed, 52 insertions(+), 11 deletions(-) diff --git a/src/Power.cpp b/src/Power.cpp index d7fd5b33b..fa8661d01 100644 --- a/src/Power.cpp +++ b/src/Power.cpp @@ -194,7 +194,7 @@ static HasBatteryLevel *batteryLevel; // Default to NULL for no battery level se #ifdef BATTERY_PIN -static void adcEnable() +void battery_adcEnable() { #ifdef ADC_CTRL // enable adc voltage divider when we need to read #ifdef ADC_USE_PULLUP @@ -214,7 +214,7 @@ static void adcEnable() #endif } -static void adcDisable() +static void battery_adcDisable() { #ifdef ADC_CTRL // disable adc voltage divider when we need to read #ifdef ADC_USE_PULLUP @@ -320,7 +320,7 @@ class AnalogBatteryLevel : public HasBatteryLevel uint32_t raw = 0; float scaled = 0; - adcEnable(); + battery_adcEnable(); #ifdef ARCH_ESP32 // ADC block for espressif platforms raw = espAdcRead(); scaled = esp_adc_cal_raw_to_voltage(raw, adc_characs); @@ -332,7 +332,7 @@ class AnalogBatteryLevel : public HasBatteryLevel raw = raw / BATTERY_SENSE_SAMPLES; scaled = operativeAdcMultiplier * ((1000 * AREF_VOLTAGE) / pow(2, BATTERY_SENSE_RESOLUTION_BITS)) * raw; #endif - adcDisable(); + battery_adcDisable(); if (!initial_read_done) { // Flush the smoothing filter with an ADC reading, if the reading is plausibly correct @@ -906,13 +906,8 @@ void Power::readPowerStatus() low_voltage_counter++; LOG_DEBUG("Low voltage counter: %d/10", low_voltage_counter); if (low_voltage_counter > 10) { -#ifdef ARCH_NRF52 - // We can't trigger deep sleep on NRF52, it's freezing the board - LOG_DEBUG("Low voltage detected, but not trigger deep sleep"); -#else LOG_INFO("Low voltage detected, trigger deep sleep"); powerFSM.trigger(EVENT_LOW_BATTERY); -#endif } } else { low_voltage_counter = 0; @@ -1552,4 +1547,4 @@ bool Power::meshSolarInit() { return false; } -#endif \ No newline at end of file +#endif diff --git a/src/platform/nrf52/main-nrf52.cpp b/src/platform/nrf52/main-nrf52.cpp index 8ce74d5f7..f29def72e 100644 --- a/src/platform/nrf52/main-nrf52.cpp +++ b/src/platform/nrf52/main-nrf52.cpp @@ -14,6 +14,9 @@ #include "error.h" #include "main.h" #include "meshUtils.h" +#include "power.h" + +#include #ifdef BQ25703A_ADDR #include "BQ25713.h" @@ -389,6 +392,23 @@ void cpuDeepSleep(uint32_t msecToWake) nrf_gpio_cfg_sense_set(BUTTON_PIN, sense); // Apply SENSE to wake up the device from the deep sleep #endif +#ifdef BATTERY_LPCOMP_INPUT + // Wake up if power rises again + nrf_lpcomp_config_t c; + c.reference = BATTERY_LPCOMP_THRESHOLD; + c.detection = NRF_LPCOMP_DETECT_UP; + c.hyst = NRF_LPCOMP_HYST_NOHYST; + nrf_lpcomp_configure(NRF_LPCOMP, &c); + nrf_lpcomp_input_select(NRF_LPCOMP, BATTERY_LPCOMP_INPUT); + nrf_lpcomp_enable(NRF_LPCOMP); + + battery_adcEnable(); + + nrf_lpcomp_task_trigger(NRF_LPCOMP, NRF_LPCOMP_TASK_START); + while (!nrf_lpcomp_event_check(NRF_LPCOMP, NRF_LPCOMP_EVENT_READY)) + ; +#endif + auto ok = sd_power_system_off(); if (ok != NRF_SUCCESS) { LOG_ERROR("FIXME: Ignoring soft device (EasyDMA pending?) and forcing system-off!"); @@ -420,4 +440,4 @@ void enterDfuMode() #else enterUf2Dfu(); #endif -} \ No newline at end of file +} diff --git a/src/power.h b/src/power.h index cdbdd3ea0..f9ccb08aa 100644 --- a/src/power.h +++ b/src/power.h @@ -144,4 +144,6 @@ class Power : private concurrency::OSThread #endif }; +void battery_adcEnable(); + extern Power *power; diff --git a/variants/nrf52840/heltec_mesh_node_t114/variant.h b/variants/nrf52840/heltec_mesh_node_t114/variant.h index 7e82733aa..b6082fdc6 100644 --- a/variants/nrf52840/heltec_mesh_node_t114/variant.h +++ b/variants/nrf52840/heltec_mesh_node_t114/variant.h @@ -210,6 +210,16 @@ No longer populated on PCB #define VBAT_AR_INTERNAL AR_INTERNAL_3_0 #define ADC_MULTIPLIER (4.916F) +// rf52840 AIN2 = Pin 4 +#define BATTERY_LPCOMP_INPUT NRF_LPCOMP_INPUT_2 + +// We have AIN2 with a VBAT divider so AIN2 = VBAT * (100/490) +// We have the device going deep sleep under 3.1V, which is AIN2 = 0.63V +// So we can wake up when VBAT>=VDD is restored to 3.3V, where AIN2 = 0.67V +// Ratio 0.67/3.3 = 0.20, so we can pick a bit higher, 2/8 VDD, which means +// VBAT=4.04V +#define BATTERY_LPCOMP_THRESHOLD NRF_LPCOMP_REF_SUPPLY_2_8 + #define HAS_RTC 0 #ifdef __cplusplus } diff --git a/variants/nrf52840/rak4631/variant.h b/variants/nrf52840/rak4631/variant.h index f5ec11ef2..302e531d5 100644 --- a/variants/nrf52840/rak4631/variant.h +++ b/variants/nrf52840/rak4631/variant.h @@ -267,6 +267,20 @@ SO GPIO 39/TXEN MAY NOT BE DEFINED FOR SUCCESSFUL OPERATION OF THE SX1262 - TG #define VBAT_AR_INTERNAL AR_INTERNAL_3_0 #define ADC_MULTIPLIER 1.73 +// RAK4630 AIN0 = nrf52840 AIN3 = Pin 5 +#define BATTERY_LPCOMP_INPUT NRF_LPCOMP_INPUT_3 + +// We have AIN3 with a VBAT divider so AIN3 = VBAT * (1.5/2.5) +// We have the device going deep sleep under 3.1V, which is AIN3 = 1.86V +// So we can wake up when VBAT>=VDD is restored to 3.3V, where AIN3 = 1.98V +// 1.98/3.3 = 6/10, but that's close to the VBAT divider, so we +// pick 6/8VDD, which means VBAT=4.1V. +// Reference: +// VDD=3.3V AIN3=5/8*VDD=2.06V VBAT=1.66*AIN3=3.41V +// VDD=3.3V AIN3=11/16*VDD=2.26V VBAT=1.66*AIN3=3.76V +// VDD=3.3V AIN3=6/8*VDD=2.47V VBAT=1.66*AIN3=4.1V +#define BATTERY_LPCOMP_THRESHOLD NRF_LPCOMP_REF_SUPPLY_11_16 + #define HAS_RTC 1 #define HAS_ETHERNET 1 From 10de230dba820776f87fbfe18429a3486e43db53 Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Tue, 4 Nov 2025 04:35:44 -0700 Subject: [PATCH 638/691] nrf52: add watchdog (#8485) * nrf52: add watchdog Main thread only for now. * bump framework-arduinoadafruitnrf52 to pick up new wdt support * clang-format the new parts of main-nrf52.cpp --------- Co-authored-by: Ben Meadors (cherry picked from commit 83954293d8b52068750f40ae633ae7ccaf39b9c0) --- arch/nrf52/nrf52.ini | 2 +- src/platform/nrf52/main-nrf52.cpp | 33 +++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/arch/nrf52/nrf52.ini b/arch/nrf52/nrf52.ini index 36effe017..e60d47ce7 100644 --- a/arch/nrf52/nrf52.ini +++ b/arch/nrf52/nrf52.ini @@ -7,7 +7,7 @@ extends = arduino_base platform_packages = ; our custom Git version until they merge our PR # TODO renovate - platformio/framework-arduinoadafruitnrf52 @ https://github.com/meshtastic/Adafruit_nRF52_Arduino#e13f5820002a4fb2a5e6754b42ace185277e5adf + platformio/framework-arduinoadafruitnrf52 @ https://github.com/meshtastic/Adafruit_nRF52_Arduino#c770c8a16a351b55b86e347a3d9d7b74ad0bbf39 ; Don't renovate toolchain-gccarmnoneeabi platformio/toolchain-gccarmnoneeabi@~1.90301.0 diff --git a/src/platform/nrf52/main-nrf52.cpp b/src/platform/nrf52/main-nrf52.cpp index f29def72e..2ec122062 100644 --- a/src/platform/nrf52/main-nrf52.cpp +++ b/src/platform/nrf52/main-nrf52.cpp @@ -4,6 +4,13 @@ #include #include #include + +#define NRFX_WDT_ENABLED 1 +#define NRFX_WDT0_ENABLED 1 +#define NRFX_WDT_CONFIG_NO_IRQ 1 +#include +#include + #include #include #include @@ -22,6 +29,9 @@ #include "BQ25713.h" #endif +static nrfx_wdt_t nrfx_wdt = NRFX_WDT_INSTANCE(0); +static nrfx_wdt_channel_id nrfx_wdt_channel_id_nrf52_main; + static inline void debugger_break(void) { __asm volatile("bkpt #0x01\n\t" @@ -205,6 +215,15 @@ void checkSDEvents() void nrf52Loop() { + { + static bool watchdog_running = false; + if (!watchdog_running) { + nrfx_wdt_enable(&nrfx_wdt); + watchdog_running = true; + } + } + nrfx_wdt_channel_feed(&nrfx_wdt, nrfx_wdt_channel_id_nrf52_main); + checkSDEvents(); reportLittleFSCorruptionOnce(); } @@ -272,6 +291,20 @@ void nrf52Setup() LOG_DEBUG("Set random seed %u", seed.seed32); randomSeed(seed.seed32); nRFCrypto.end(); + + // Set up nrfx watchdog. Do not enable the watchdog yet (we do that + // the first time through the main loop), so that other threads can + // allocate their own wdt channel to protect themselves from hangs. + nrfx_wdt_config_t wdt0_config = { + .behaviour = NRF_WDT_BEHAVIOUR_RUN_SLEEP, .reload_value = 2000, + // Note: Not using wdt interrupts. + // .interrupt_priority = NRFX_WDT_DEFAULT_CONFIG_IRQ_PRIORITY + }; + nrfx_err_t r = nrfx_wdt_init(&nrfx_wdt, &wdt0_config, + nullptr // Watchdog event handler, not used, we just reset. + ); + + r = nrfx_wdt_channel_alloc(&nrfx_wdt, &nrfx_wdt_channel_id_nrf52_main); } void cpuDeepSleep(uint32_t msecToWake) From 7232dddd691fc8a92cfc91bbc0e52147e1defe2d Mon Sep 17 00:00:00 2001 From: Sebastian Kuzminsky Date: Thu, 6 Nov 2025 22:22:58 -0700 Subject: [PATCH 639/691] nrf52 wdt: pause wdt in Sleep and Halt, set timeout to 90 s The 90 seconds wdt timeout matches the esp32 wdt timeout. --- src/platform/nrf52/main-nrf52.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/platform/nrf52/main-nrf52.cpp b/src/platform/nrf52/main-nrf52.cpp index 2ec122062..827863f33 100644 --- a/src/platform/nrf52/main-nrf52.cpp +++ b/src/platform/nrf52/main-nrf52.cpp @@ -5,6 +5,7 @@ #include #include +#define APP_WATCHDOG_SECS 90 #define NRFX_WDT_ENABLED 1 #define NRFX_WDT0_ENABLED 1 #define NRFX_WDT_CONFIG_NO_IRQ 1 @@ -296,15 +297,17 @@ void nrf52Setup() // the first time through the main loop), so that other threads can // allocate their own wdt channel to protect themselves from hangs. nrfx_wdt_config_t wdt0_config = { - .behaviour = NRF_WDT_BEHAVIOUR_RUN_SLEEP, .reload_value = 2000, + .behaviour = NRF_WDT_BEHAVIOUR_PAUSE_SLEEP_HALT, .reload_value = APP_WATCHDOG_SECS * 1000, // Note: Not using wdt interrupts. // .interrupt_priority = NRFX_WDT_DEFAULT_CONFIG_IRQ_PRIORITY }; nrfx_err_t r = nrfx_wdt_init(&nrfx_wdt, &wdt0_config, nullptr // Watchdog event handler, not used, we just reset. ); + assert(r == NRFX_SUCCESS); r = nrfx_wdt_channel_alloc(&nrfx_wdt, &nrfx_wdt_channel_id_nrf52_main); + assert(r == NRFX_SUCCESS); } void cpuDeepSleep(uint32_t msecToWake) From f9433a31d168abe982abdf556246b76a5afe390d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 19 Nov 2025 12:13:28 -0600 Subject: [PATCH 640/691] Automated version bumps (#8684) Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> --- bin/org.meshtastic.meshtasticd.metainfo.xml | 3 +++ debian/changelog | 6 ++++++ version.properties | 2 +- 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/bin/org.meshtastic.meshtasticd.metainfo.xml b/bin/org.meshtastic.meshtasticd.metainfo.xml index b59bb6202..243edca0c 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.16 + https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.15 diff --git a/debian/changelog b/debian/changelog index 437e1645b..e5b84d134 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +meshtasticd (2.7.16.0) unstable; urgency=medium + + * Version 2.7.16 + + -- GitHub Actions Wed, 19 Nov 2025 16:12:32 +0000 + meshtasticd (2.7.15.0) unstable; urgency=medium * Version 2.7.15 diff --git a/version.properties b/version.properties index 165f476df..3dde9b1a3 100644 --- a/version.properties +++ b/version.properties @@ -1,4 +1,4 @@ [VERSION] major = 2 minor = 7 -build = 15 +build = 16 From 8d31fc5ec6a7a40927accd45ecc40b63e7e53944 Mon Sep 17 00:00:00 2001 From: "Jason B. Cox" Date: Wed, 19 Nov 2025 13:59:45 -0800 Subject: [PATCH 641/691] Unify uptime formatting (#8677) * Unify uptime formatting * Fix small label alignment item --------- Co-authored-by: Ben Meadors Co-authored-by: Jason P --- src/graphics/TimeFormatters.cpp | 20 +++++++++++ src/graphics/TimeFormatters.h | 7 ++++ src/graphics/draw/DebugRenderer.cpp | 15 ++------ src/graphics/draw/UIRenderer.cpp | 56 +++++------------------------ 4 files changed, 39 insertions(+), 59 deletions(-) diff --git a/src/graphics/TimeFormatters.cpp b/src/graphics/TimeFormatters.cpp index 47036078b..0a1c23341 100644 --- a/src/graphics/TimeFormatters.cpp +++ b/src/graphics/TimeFormatters.cpp @@ -101,3 +101,23 @@ void getTimeAgoStr(uint32_t agoSecs, char *timeStr, uint8_t maxLength) else snprintf(timeStr, maxLength, "unknown age"); } + +void getUptimeStr(uint32_t uptimeMillis, const char *prefix, char *uptimeStr, uint8_t maxLength, bool includeSecs) +{ + uint32_t days = uptimeMillis / 86400000; + uint32_t hours = (uptimeMillis % 86400000) / 3600000; + uint32_t mins = (uptimeMillis % 3600000) / 60000; + uint32_t secs = (uptimeMillis % 60000) / 1000; + + if (days) { + snprintf(uptimeStr, maxLength, "%s: %ud %uh", prefix, days, hours); + } else if (hours) { + snprintf(uptimeStr, maxLength, "%s: %uh %um", prefix, hours, mins); + } else if (!includeSecs) { + snprintf(uptimeStr, maxLength, "%s: %um", prefix, mins); + } else if (mins) { + snprintf(uptimeStr, maxLength, "%s: %um %us", prefix, mins, secs); + } else { + snprintf(uptimeStr, maxLength, "%s: %us", prefix, secs); + } +} diff --git a/src/graphics/TimeFormatters.h b/src/graphics/TimeFormatters.h index b3d8413a2..f86c6725c 100644 --- a/src/graphics/TimeFormatters.h +++ b/src/graphics/TimeFormatters.h @@ -24,3 +24,10 @@ bool deltaToTimestamp(uint32_t secondsAgo, uint8_t *hours, uint8_t *minutes, int * @param maxLength Maximum length of the resulting string buffer */ void getTimeAgoStr(uint32_t agoSecs, char *timeStr, uint8_t maxLength); + +/** + * Get a compact human-readable string that only shows the largest non-zero time components. + * For example, 0 days 1 hour 2 minutes will display as "1h 2m" but 1 day 2 hours 3 minutes + * will display as "1d 2h". + */ +void getUptimeStr(uint32_t uptimeMillis, const char *prefix, char *uptimeStr, uint8_t maxLength, bool includeSecs = false); diff --git a/src/graphics/draw/DebugRenderer.cpp b/src/graphics/draw/DebugRenderer.cpp index d098fa304..79c1e7e61 100644 --- a/src/graphics/draw/DebugRenderer.cpp +++ b/src/graphics/draw/DebugRenderer.cpp @@ -11,6 +11,7 @@ #include "gps/RTC.h" #include "graphics/ScreenFonts.h" #include "graphics/SharedUIDisplay.h" +#include "graphics/TimeFormatters.h" #include "graphics/images.h" #include "main.h" #include "mesh/Channels.h" @@ -650,17 +651,7 @@ void drawSystemScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x if (SCREEN_HEIGHT > 64 || (SCREEN_HEIGHT <= 64 && line <= 5)) { // Only show uptime if the screen can show it char uptimeStr[32] = ""; - uint32_t uptime = millis() / 1000; - uint32_t days = uptime / 86400; - uint32_t hours = (uptime % 86400) / 3600; - uint32_t mins = (uptime % 3600) / 60; - // Show as "Up: 2d 3h", "Up: 5h 14m", or "Up: 37m" - if (days) - snprintf(uptimeStr, sizeof(uptimeStr), " Up: %ud %uh", days, hours); - else if (hours) - snprintf(uptimeStr, sizeof(uptimeStr), " Up: %uh %um", hours, mins); - else - snprintf(uptimeStr, sizeof(uptimeStr), " Uptime: %um", mins); + getUptimeStr(millis(), "Up", uptimeStr, sizeof(uptimeStr)); textWidth = display->getStringWidth(uptimeStr); nameX = (SCREEN_WIDTH - textWidth) / 2; display->drawString(nameX, getTextPositions(display)[line++], uptimeStr); @@ -729,4 +720,4 @@ void drawChirpy(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int1 } // namespace DebugRenderer } // namespace graphics -#endif \ No newline at end of file +#endif diff --git a/src/graphics/draw/UIRenderer.cpp b/src/graphics/draw/UIRenderer.cpp index 538c32842..c50fe5cf1 100644 --- a/src/graphics/draw/UIRenderer.cpp +++ b/src/graphics/draw/UIRenderer.cpp @@ -11,6 +11,7 @@ #include "graphics/Screen.h" #include "graphics/ScreenFonts.h" #include "graphics/SharedUIDisplay.h" +#include "graphics/TimeFormatters.h" #include "graphics/images.h" #include "main.h" #include "target_specific.h" @@ -383,17 +384,7 @@ void UIRenderer::drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *st // === 4. Uptime (only show if metric is present) === char uptimeStr[32] = ""; if (node->has_device_metrics && node->device_metrics.has_uptime_seconds) { - uint32_t uptime = node->device_metrics.uptime_seconds; - uint32_t days = uptime / 86400; - uint32_t hours = (uptime % 86400) / 3600; - uint32_t mins = (uptime % 3600) / 60; - // Show as "Up: 2d 3h", "Up: 5h 14m", or "Up: 37m" - if (days) - snprintf(uptimeStr, sizeof(uptimeStr), " Uptime: %ud %uh", days, hours); - else if (hours) - snprintf(uptimeStr, sizeof(uptimeStr), " Uptime: %uh %um", hours, mins); - else - snprintf(uptimeStr, sizeof(uptimeStr), " Uptime: %um", mins); + getUptimeStr(node->device_metrics.uptime_seconds * 1000, " Up", uptimeStr, sizeof(uptimeStr)); } if (uptimeStr[0] && line < 5) { display->drawString(x, getTextPositions(display)[line++], uptimeStr); @@ -592,18 +583,8 @@ void UIRenderer::drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *sta drawNodes(display, x + 1, getTextPositions(display)[line] + 2, nodeStatus, -1, false, "online"); #endif char uptimeStr[32] = ""; - uint32_t uptime = millis() / 1000; - uint32_t days = uptime / 86400; - uint32_t hours = (uptime % 86400) / 3600; - uint32_t mins = (uptime % 3600) / 60; - // Show as "Up: 2d 3h", "Up: 5h 14m", or "Up: 37m" #if !defined(M5STACK_UNITC6L) - if (days) - snprintf(uptimeStr, sizeof(uptimeStr), "Up: %ud %uh", days, hours); - else if (hours) - snprintf(uptimeStr, sizeof(uptimeStr), "Up: %uh %um", hours, mins); - else - snprintf(uptimeStr, sizeof(uptimeStr), "Up: %um", mins); + getUptimeStr(millis(), "Up", uptimeStr, sizeof(uptimeStr)); #endif display->drawString(SCREEN_WIDTH - display->getStringWidth(uptimeStr), getTextPositions(display)[line++], uptimeStr); @@ -1048,36 +1029,17 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU if (strcmp(displayLine, "GPS off") != 0 && strcmp(displayLine, "No GPS") != 0) { // === 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]; + uint32_t delta = millis() - gpsStatus->getLastFixMillis(); + char uptimeStr[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); - } + getUptimeStr(delta, "Last", uptimeStr, sizeof(uptimeStr), false); #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); - } + getUptimeStr(delta, "Last", uptimeStr, sizeof(uptimeStr), true); #endif - display->drawString(0, getTextPositions(display)[line++], buf); + display->drawString(0, getTextPositions(display)[line++], uptimeStr); } else { display->drawString(0, getTextPositions(display)[line++], "Last: ?"); } @@ -1422,4 +1384,4 @@ std::string UIRenderer::drawTimeDelta(uint32_t days, uint32_t hours, uint32_t mi } // namespace graphics -#endif // HAS_SCREEN \ No newline at end of file +#endif // HAS_SCREEN From ef298814f2da32d53c3236545ba83b00f27c45c1 Mon Sep 17 00:00:00 2001 From: Austin Date: Wed, 19 Nov 2025 17:00:13 -0500 Subject: [PATCH 642/691] CI: Submit Bump Version PR against master (#8668) --- .github/workflows/release_channels.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/release_channels.yml b/.github/workflows/release_channels.yml index d5d642db4..4e5a48dfe 100644 --- a/.github/workflows/release_channels.yml +++ b/.github/workflows/release_channels.yml @@ -61,6 +61,9 @@ jobs: steps: - name: Checkout uses: actions/checkout@v5 + with: + # Always use master branch for version bumps + ref: master - name: Setup Python uses: actions/setup-python@v6 From 2ca03fbf4bccad55e2f9e8d66f84bc008113b2ac Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 19 Nov 2025 16:02:33 -0600 Subject: [PATCH 643/691] chore(deps): update meshtastic-esp8266-oled-ssd1306 digest to 2887bf4 (#8688) 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 d62504ae3..4bd047170 100644 --- a/platformio.ini +++ b/platformio.ini @@ -62,7 +62,7 @@ monitor_speed = 115200 monitor_filters = direct lib_deps = # renovate: datasource=git-refs depName=meshtastic-esp8266-oled-ssd1306 packageName=https://github.com/meshtastic/esp8266-oled-ssd1306 gitBranch=master - https://github.com/meshtastic/esp8266-oled-ssd1306/archive/0cbc26b1f8f61957af0475f486b362eafe7cc4e2.zip + https://github.com/meshtastic/esp8266-oled-ssd1306/archive/2887bf4a19f64d92c984dcc8fd5ca7429e425e4a.zip # renovate: datasource=git-refs depName=meshtastic-OneButton packageName=https://github.com/meshtastic/OneButton gitBranch=master https://github.com/meshtastic/OneButton/archive/fa352d668c53f290cfa480a5f79ad422cd828c70.zip # renovate: datasource=git-refs depName=meshtastic-arduino-fsm packageName=https://github.com/meshtastic/arduino-fsm gitBranch=master From 9cf369c5d0ed27d631bc3f18ba58a02f18416947 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Thu, 20 Nov 2025 05:41:32 -0600 Subject: [PATCH 644/691] actually respect wake_on_motion setting (#8690) --- src/motion/MotionSensor.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/motion/MotionSensor.cpp b/src/motion/MotionSensor.cpp index b00460aff..d0bfe4e2c 100755 --- a/src/motion/MotionSensor.cpp +++ b/src/motion/MotionSensor.cpp @@ -69,7 +69,8 @@ void MotionSensor::wakeScreen() { if (powerFSM.getState() == &stateDARK) { LOG_DEBUG("Motion wakeScreen detected"); - powerFSM.trigger(EVENT_INPUT); + if (config.display.wake_on_tap_or_motion) + powerFSM.trigger(EVENT_INPUT); } } @@ -87,4 +88,4 @@ void MotionSensor::buttonPress() {} #endif -#endif \ No newline at end of file +#endif From a2a0150ee83e734d6359f85882382a97601764cd Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Thu, 20 Nov 2025 06:14:29 -0600 Subject: [PATCH 645/691] Trunk fmt --- README.md | 1 - src/graphics/TFTDisplay.cpp | 53 +++++++++++++++++++----------------- src/graphics/niche/README.md | 1 - src/mesh/NodeDB.cpp | 2 +- 4 files changed, 29 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index a53fe9646..f34bf1839 100644 --- a/README.md +++ b/README.md @@ -37,4 +37,3 @@ Join our community and help improve Meshtastic! 🚀 ## Stats ![Alt](https://repobeats.axiom.co/api/embed/8025e56c482ec63541593cc5bd322c19d5c0bdcf.svg "Repobeats analytics image") - diff --git a/src/graphics/TFTDisplay.cpp b/src/graphics/TFTDisplay.cpp index b662869dd..87593b0d4 100644 --- a/src/graphics/TFTDisplay.cpp +++ b/src/graphics/TFTDisplay.cpp @@ -427,33 +427,35 @@ static LGFX *tft = nullptr; #include "lgfx/v1/Touch.hpp" namespace lgfx { - inline namespace v1 - { +inline namespace v1 +{ class TOUCH_CHSC6X : public ITouch { -public: + public: TOUCH_CHSC6X(void) { - _cfg.i2c_addr = TOUCH_SLAVE_ADDRESS; - _cfg.x_min = 0; - _cfg.x_max = 240; - _cfg.y_min = 0; - _cfg.y_max = 320; + _cfg.i2c_addr = TOUCH_SLAVE_ADDRESS; + _cfg.x_min = 0; + _cfg.x_max = 240; + _cfg.y_min = 0; + _cfg.y_max = 320; }; - bool init(void) override { - if(chsc6xTouch==nullptr) { - chsc6xTouch=new chsc6x(&Wire1,TOUCH_SDA_PIN,TOUCH_SCL_PIN,TOUCH_INT_PIN,TOUCH_RST_PIN); + bool init(void) override + { + if (chsc6xTouch == nullptr) { + chsc6xTouch = new chsc6x(&Wire1, TOUCH_SDA_PIN, TOUCH_SCL_PIN, TOUCH_INT_PIN, TOUCH_RST_PIN); } chsc6xTouch->chsc6x_init(); return true; }; - uint_fast8_t getTouchRaw(touch_point_t* tp, uint_fast8_t count) override { - uint16_t raw_x,raw_y; - if (chsc6xTouch->chsc6x_read_touch_info(&raw_x, &raw_y)==0) { - tp[0].x = 320-1-raw_y; - tp[0].y = 240-1-raw_x ; + uint_fast8_t getTouchRaw(touch_point_t *tp, uint_fast8_t count) override + { + uint16_t raw_x, raw_y; + if (chsc6xTouch->chsc6x_read_touch_info(&raw_x, &raw_y) == 0) { + tp[0].x = 320 - 1 - raw_y; + tp[0].y = 240 - 1 - raw_x; tp[0].size = 1; tp[0].id = 1; return 1; @@ -462,13 +464,14 @@ public: return 0; }; - void wakeup(void) override {}; - void sleep(void) override {}; + void wakeup(void) override{}; + void sleep(void) override{}; + private: - chsc6x *chsc6xTouch=nullptr; - }; -} -} + chsc6x *chsc6xTouch = nullptr; +}; +} // namespace v1 +} // namespace lgfx #endif class LGFX : public lgfx::LGFX_Device { @@ -513,9 +516,9 @@ class LGFX : public lgfx::LGFX_Device { // Set the display panel control. auto cfg = _panel_instance.config(); // Gets a structure for display panel settings. - cfg.pin_cs = ST7789_CS; // Pin number where CS is connected (-1 = disable) - cfg.pin_rst = ST7789_RESET; // Pin number where RST is connected (-1 = disable) - cfg.pin_busy = ST7789_BUSY; // Pin number where BUSY is connected (-1 = disable) + cfg.pin_cs = ST7789_CS; // Pin number where CS is connected (-1 = disable) + cfg.pin_rst = ST7789_RESET; // Pin number where RST is connected (-1 = disable) + cfg.pin_busy = ST7789_BUSY; // Pin number where BUSY is connected (-1 = disable) // The following setting values ​​are general initial values ​​for each panel, so please comment out any // unknown items and try them. diff --git a/src/graphics/niche/README.md b/src/graphics/niche/README.md index e87464abc..e58578f6b 100644 --- a/src/graphics/niche/README.md +++ b/src/graphics/niche/README.md @@ -5,7 +5,6 @@ A pattern / collection of resources for creating custom UIs, to target small gro For an example, see the `heltec-vision-master-e290-inkhud` platformio env. - platformio.ini - - suppress default Meshtastic components (Screen, ButtonThread, etc) - define `MESHTASTIC_INCLUDE_NICHE_GRAPHICS` - (possibly) Edit `build_src_filter` to include our new nicheGraphics.h file diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 6291fa4cc..d8146c4a3 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -653,7 +653,7 @@ void NodeDB::installDefaultConfig(bool preserveKey = false) strncpy(config.network.ntp_server, "meshtastic.pool.ntp.org", 32); #if (defined(T_DECK) || defined(T_WATCH_S3) || defined(UNPHONE) || defined(PICOMPUTER_S3) || defined(SENSECAP_INDICATOR) || \ - defined(ELECROW_PANEL)||defined(HELTEC_V4_TFT)) && \ + defined(ELECROW_PANEL) || defined(HELTEC_V4_TFT)) && \ HAS_TFT // switch BT off by default; use TFT programming mode or hotkey to enable config.bluetooth.enabled = false; From b09fa31492e60cc5eec002d0aa4a0d6257693d57 Mon Sep 17 00:00:00 2001 From: Jason P Date: Thu, 20 Nov 2025 08:07:11 -0600 Subject: [PATCH 646/691] Update src/graphics/draw/MenuHandler.cpp Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/graphics/draw/MenuHandler.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/graphics/draw/MenuHandler.cpp b/src/graphics/draw/MenuHandler.cpp index 11499805b..c22ff23f9 100644 --- a/src/graphics/draw/MenuHandler.cpp +++ b/src/graphics/draw/MenuHandler.cpp @@ -1353,7 +1353,7 @@ void menuHandler::screenOptionsMenu() optionsEnumArray[options++] = ScreenColor; #endif - optionsArray[options] = "Frame Visiblity Toggle"; + optionsArray[options] = "Frame Visibility Toggle"; optionsEnumArray[options++] = FrameToggles; optionsArray[options] = "Display Units"; From f329de04c47c6a57dbe218b7845704d2bbeec070 Mon Sep 17 00:00:00 2001 From: Quency-D <55523105+Quency-D@users.noreply.github.com> Date: Thu, 20 Nov 2025 22:47:41 +0800 Subject: [PATCH 647/691] Add a reset pulse signal to the OLED. (#8691) * Add a reset pulse signal to the OLED. * The modification time is the same as that of the Adafruit_SSD1306 library. --- src/main.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main.cpp b/src/main.cpp index 8fec62953..fd376ea51 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -477,6 +477,10 @@ void setup() #ifdef RESET_OLED pinMode(RESET_OLED, OUTPUT); digitalWrite(RESET_OLED, 1); + delay(2); + digitalWrite(RESET_OLED, 0); + delay(10); + digitalWrite(RESET_OLED, 1); #endif #ifdef SENSOR_POWER_CTRL_PIN From 066da492d5b7a1f070c3ee78076e5ea1c624908f Mon Sep 17 00:00:00 2001 From: "Jason B. Cox" Date: Thu, 20 Nov 2025 12:26:07 -0800 Subject: [PATCH 648/691] Fix build when MESHTASTIC_EXCLUDE_PKI is defined --- src/main.cpp | 2 ++ src/mesh/NodeDB.cpp | 2 ++ src/mesh/NodeDB.h | 4 +++- src/modules/AdminModule.cpp | 2 ++ 4 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/main.cpp b/src/main.cpp index fd376ea51..8fc2c097b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -963,6 +963,7 @@ void setup() i2cScanner.reset(); #endif +#if !defined(MESHTASTIC_EXCLUDE_PKI) // warn the user about a low entropy key if (nodeDB->keyIsLowEntropy && !nodeDB->hasWarned) { LOG_WARN(LOW_ENTROPY_WARNING); @@ -973,6 +974,7 @@ void setup() service->sendClientNotification(cn); nodeDB->hasWarned = true; } +#endif // buttons are now inputBroker, so have to come after setupModules #if HAS_BUTTON diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index d8146c4a3..bb3fc6dca 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -2008,6 +2008,7 @@ UserLicenseStatus NodeDB::getLicenseStatus(uint32_t nodeNum) return info->user.is_licensed ? UserLicenseStatus::Licensed : UserLicenseStatus::NotLicensed; } +#if !defined(MESHTASTIC_EXCLUDE_PKI) bool NodeDB::checkLowEntropyPublicKey(const meshtastic_Config_SecurityConfig_public_key_t &keyToTest) { if (keyToTest.size == 32) { @@ -2022,6 +2023,7 @@ bool NodeDB::checkLowEntropyPublicKey(const meshtastic_Config_SecurityConfig_pub } return false; } +#endif bool NodeDB::backupPreferences(meshtastic_AdminMessage_BackupLocation location) { diff --git a/src/mesh/NodeDB.h b/src/mesh/NodeDB.h index 444ac13e4..306acc0a5 100644 --- a/src/mesh/NodeDB.h +++ b/src/mesh/NodeDB.h @@ -283,7 +283,9 @@ class NodeDB bool hasValidPosition(const meshtastic_NodeInfoLite *n); +#if !defined(MESHTASTIC_EXCLUDE_PKI) bool checkLowEntropyPublicKey(const meshtastic_Config_SecurityConfig_public_key_t &keyToTest); +#endif bool backupPreferences(meshtastic_AdminMessage_BackupLocation location); bool restorePreferences(meshtastic_AdminMessage_BackupLocation location, @@ -373,4 +375,4 @@ extern uint32_t error_address; ModuleConfig_RangeTestConfig_size + ModuleConfig_SerialConfig_size + ModuleConfig_StoreForwardConfig_size + \ ModuleConfig_TelemetryConfig_size + ModuleConfig_size) -// Please do not remove this comment, it makes trunk and compiler happy at the same time. \ No newline at end of file +// Please do not remove this comment, it makes trunk and compiler happy at the same time. diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index a98515059..aa510a86d 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -773,6 +773,7 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c) config.lora = validatedLora; // If we're setting region for the first time, init the region and regenerate the keys if (isRegionUnset && config.lora.region > meshtastic_Config_LoRaConfig_RegionCode_UNSET) { +#if !(MESHTASTIC_EXCLUDE_PKI_KEYGEN || MESHTASTIC_EXCLUDE_PKI) if (!owner.is_licensed) { bool keygenSuccess = false; if (config.security.private_key.size == 32) { @@ -791,6 +792,7 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c) memcpy(owner.public_key.bytes, config.security.public_key.bytes, 32); } } +#endif config.lora.tx_enabled = true; initRegion(); if (myRegion->dutyCycle < 100) { From 0100eeea67c9ef8d4d495151e54fa5df5b84bd29 Mon Sep 17 00:00:00 2001 From: "Jason B. Cox" Date: Thu, 20 Nov 2025 14:20:18 -0800 Subject: [PATCH 649/691] Fix MenuHandler when MESHTASTIC_EXCLUDE_PKI is defined --- src/graphics/draw/MenuHandler.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/graphics/draw/MenuHandler.cpp b/src/graphics/draw/MenuHandler.cpp index c22ff23f9..bd647c3d8 100644 --- a/src/graphics/draw/MenuHandler.cpp +++ b/src/graphics/draw/MenuHandler.cpp @@ -119,6 +119,7 @@ void menuHandler::LoraRegionPicker(uint32_t duration) auto changes = SEGMENT_CONFIG; // This is needed as we wait til picking the LoRa region to generate keys for the first time. +#if !(MESHTASTIC_EXCLUDE_PKI_KEYGEN || MESHTASTIC_EXCLUDE_PKI) if (!owner.is_licensed) { bool keygenSuccess = false; if (config.security.private_key.size == 32) { @@ -139,6 +140,7 @@ void menuHandler::LoraRegionPicker(uint32_t duration) memcpy(owner.public_key.bytes, config.security.public_key.bytes, 32); } } +#endif config.lora.tx_enabled = true; initRegion(); if (myRegion->dutyCycle < 100) { @@ -1750,4 +1752,4 @@ void menuHandler::saveUIConfig() } // namespace graphics -#endif \ No newline at end of file +#endif From d743ba8e75967eb947a1e2467b8bc663884a727f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Fri, 21 Nov 2025 10:14:06 +0100 Subject: [PATCH 650/691] Add Thinknode M6 --- boards/ThinkNode-M6.json | 53 +++++++ src/platform/nrf52/architecture.h | 2 + .../ELECROW-ThinkNode-M6/platformio.ini | 15 ++ .../nrf52840/ELECROW-ThinkNode-M6/variant.cpp | 43 ++++++ .../nrf52840/ELECROW-ThinkNode-M6/variant.h | 143 ++++++++++++++++++ 5 files changed, 256 insertions(+) create mode 100644 boards/ThinkNode-M6.json create mode 100644 variants/nrf52840/ELECROW-ThinkNode-M6/platformio.ini create mode 100644 variants/nrf52840/ELECROW-ThinkNode-M6/variant.cpp create mode 100644 variants/nrf52840/ELECROW-ThinkNode-M6/variant.h diff --git a/boards/ThinkNode-M6.json b/boards/ThinkNode-M6.json new file mode 100644 index 000000000..9fe324ec2 --- /dev/null +++ b/boards/ThinkNode-M6.json @@ -0,0 +1,53 @@ +{ + "build": { + "arduino": { + "ldscript": "nrf52840_s140_v6.ld" + }, + "core": "nRF5", + "cpu": "cortex-m4", + "extra_flags": "-DARDUINO_NRF52840_ELECROW_M6 -DNRF52840_XXAA", + "f_cpu": "64000000L", + "hwids": [ + ["0x239A", "0x4405"], + ["0x239A", "0x0029"], + ["0x239A", "0x002A"] + ], + "usb_product": "elecrow_thinknode_m6", + "mcu": "nrf52840", + "variant": "ELECROW-ThinkNode-M6", + "variants_dir": "variants", + "bsp": { + "name": "adafruit" + }, + "softdevice": { + "sd_flags": "-DS140", + "sd_name": "s140", + "sd_version": "6.1.1", + "sd_fwid": "0x00B6" + }, + "bootloader": { + "settings_addr": "0xFF000" + } + }, + "connectivity": ["bluetooth"], + "debug": { + "jlink_device": "nRF52840_xxAA", + "onboard_tools": ["jlink"], + "svd_path": "nrf52840.svd", + "openocd_target": "nrf52840-mdk-rs" + }, + "frameworks": ["arduino"], + "name": "ELECROW ThinkNode M6", + "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": "https://www.elecrow.com/thinknode-m6-outdoor-solar-power-for-lora-powered-by-nrf52840-supports-gps.html", + "vendor": "ELECROW" +} diff --git a/src/platform/nrf52/architecture.h b/src/platform/nrf52/architecture.h index c74f02c44..6ddb41b16 100644 --- a/src/platform/nrf52/architecture.h +++ b/src/platform/nrf52/architecture.h @@ -68,6 +68,8 @@ #define HW_VENDOR meshtastic_HardwareModel_T_ECHO_LITE #elif defined(ELECROW_ThinkNode_M1) #define HW_VENDOR meshtastic_HardwareModel_THINKNODE_M1 +#elif defined(ELECROW_ThinkNode_M6) +#define HW_VENDOR meshtastic_HardwareModel_THINKNODE_M6 #elif defined(NANO_G2_ULTRA) #define HW_VENDOR meshtastic_HardwareModel_NANO_G2_ULTRA #elif defined(CANARYONE) diff --git a/variants/nrf52840/ELECROW-ThinkNode-M6/platformio.ini b/variants/nrf52840/ELECROW-ThinkNode-M6/platformio.ini new file mode 100644 index 000000000..2bf227791 --- /dev/null +++ b/variants/nrf52840/ELECROW-ThinkNode-M6/platformio.ini @@ -0,0 +1,15 @@ +; ThinkNode M6 - Outdoor Solar Power nrf52840/sx1262 device +[env:thinknode_m6] +extends = nrf52840_base +board = ThinkNode-M6 +board_check = true +debug_tool = jlink + +build_flags = ${nrf52840_base.build_flags} + -Ivariants/nrf52840/ELECROW-ThinkNode-M6 + -DELECROW_ThinkNode_M6 + +build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/ELECROW-ThinkNode-M6> +lib_deps = + ${nrf52840_base.lib_deps} + lewisxhe/PCF8563_Library@^1.0.1 diff --git a/variants/nrf52840/ELECROW-ThinkNode-M6/variant.cpp b/variants/nrf52840/ELECROW-ThinkNode-M6/variant.cpp new file mode 100644 index 000000000..b84079e66 --- /dev/null +++ b/variants/nrf52840/ELECROW-ThinkNode-M6/variant.cpp @@ -0,0 +1,43 @@ +/* + 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 - pins 0 and 1 are hardwired for xtal and should never be enabled + 0xff, 0xff, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + + // P1 + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47}; + +void initVariant() +{ + pinMode(PIN_LED1, OUTPUT); + ledOff(PIN_LED1); + + pinMode(PIN_LED2, OUTPUT); + ledOff(PIN_LED2); + + pinMode(VDD_FLASH_EN, OUTPUT); + digitalWrite(VDD_FLASH_EN, HIGH); +} diff --git a/variants/nrf52840/ELECROW-ThinkNode-M6/variant.h b/variants/nrf52840/ELECROW-ThinkNode-M6/variant.h new file mode 100644 index 000000000..98c654df2 --- /dev/null +++ b/variants/nrf52840/ELECROW-ThinkNode-M6/variant.h @@ -0,0 +1,143 @@ +/* + 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_ELECROW_THINKNODE_M6_ +#define _VARIANT_ELECROW_THINKNODE_M6_ + +/** Master clock frequency */ +#define VARIANT_MCK (64000000ul) + +#define USE_LFXO + +/*---------------------------------------------------------------------------- + * Headers + *----------------------------------------------------------------------------*/ + +#include "WVariant.h" + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +#define PINS_COUNT (48) +#define NUM_DIGITAL_PINS (48) +#define NUM_ANALOG_INPUTS (1) +#define NUM_ANALOG_OUTPUTS (0) + +// LEDs +#define PIN_LED1 (12) +#define PIN_LED2 (7) +#define LED_BUILTIN PIN_LED1 +#define LED_BLUE PIN_LED2 +#define LED_STATE_ON 1 + +// USB power detection +#define EXT_PWR_DETECT (13) + +// Button +#define PIN_BUTTON1 (17) + +// Battery ADC +#define PIN_A0 (28) +#define BATTERY_PIN PIN_A0 +#define ADC_CTRL (11) +#define ADC_CTRL_ENABLED 1 + +static const uint8_t A0 = PIN_A0; + +#define ADC_RESOLUTION 14 +#define BATTERY_SENSE_SAMPLES 30 + +#define PIN_NFC1 (9) +#define PIN_NFC2 (10) + +// I2C +#define WIRE_INTERFACES_COUNT 1 +#define PIN_WIRE_SDA (32 + 9) +#define PIN_WIRE_SCL (8) + +// Peripheral power enable +#define PIN_POWER_EN (27) + +// Solar charger status +#define EXT_CHRG_DETECT (15) +#define EXT_CHRG_DETECT_VALUE LOW + +// QSPI Flash +#define PIN_QSPI_SCK (32 + 3) +#define PIN_QSPI_CS (23) +#define PIN_QSPI_IO0 (32 + 1) +#define PIN_QSPI_IO1 (32 + 2) +#define PIN_QSPI_IO2 (32 + 4) +#define PIN_QSPI_IO3 (32 + 5) + +#define EXTERNAL_FLASH_DEVICES MX25R1635F +#define EXTERNAL_FLASH_USE_QSPI +#define VDD_FLASH_EN (21) + +// LoRa SX1262 +#define USE_SX1262 +#define SX126X_CS (32 + 12) +#define SX126X_DIO1 (32 + 6) +#define SX126X_BUSY (32 + 11) +#define SX126X_RESET (32 + 10) +#define SX126X_DIO2_AS_RF_SWITCH +#define SX126X_DIO3_TCXO_VOLTAGE 3.3 + +// GPS L76K +#define GPS_L76K +#define GPS_BAUDRATE 9600 +#define PIN_GPS_EN (6) +#define PIN_GPS_REINIT (29) +#define PIN_GPS_STANDBY (30) +#define PIN_GPS_PPS (31) +#define GPS_TX_PIN (3) +#define GPS_RX_PIN (2) +#define GPS_THREAD_INTERVAL 50 + +#define PIN_SERIAL1_RX GPS_TX_PIN +#define PIN_SERIAL1_TX GPS_RX_PIN + +// Secondary UART +#define PIN_SERIAL2_RX (22) +#define PIN_SERIAL2_TX (24) + +// PCF8563 RTC Module +#define PCF8563_RTC 0x51 + +// SPI +#define SPI_INTERFACES_COUNT 1 +#define PIN_SPI_MISO (32 + 15) +#define PIN_SPI_MOSI (32 + 14) +#define PIN_SPI_SCK (32 + 13) + +// Battery +#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.75F) + +#define HAS_SOLAR + +#ifdef __cplusplus +} +#endif + +#endif From 451e52b54141251bee3f3882fe2012003667670f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Fri, 21 Nov 2025 10:42:15 +0100 Subject: [PATCH 651/691] fix some minor compiler warnings. Note: The 'delete' is actually safe, so we suppress the warning. --- src/modules/Telemetry/Sensor/DFRobotGravitySensor.cpp | 3 +++ src/power.h | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/modules/Telemetry/Sensor/DFRobotGravitySensor.cpp b/src/modules/Telemetry/Sensor/DFRobotGravitySensor.cpp index 59a98e291..101b01f8f 100644 --- a/src/modules/Telemetry/Sensor/DFRobotGravitySensor.cpp +++ b/src/modules/Telemetry/Sensor/DFRobotGravitySensor.cpp @@ -13,7 +13,10 @@ DFRobotGravitySensor::DFRobotGravitySensor() : TelemetrySensor(meshtastic_Teleme DFRobotGravitySensor::~DFRobotGravitySensor() { if (gravity) { +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdelete-non-virtual-dtor" delete gravity; +#pragma GCC diagnostic pop gravity = nullptr; } } diff --git a/src/power.h b/src/power.h index f9ccb08aa..8fc7c8f45 100644 --- a/src/power.h +++ b/src/power.h @@ -138,7 +138,7 @@ class Power : private concurrency::OSThread void reboot(); // open circuit voltage lookup table uint8_t low_voltage_counter; - int32_t lastLogTime = 0; + uint32_t lastLogTime = 0; #ifdef DEBUG_HEAP uint32_t lastheap; #endif From 0e3e8b7607ffdeeabc34a3a349e108e0c3a1363d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 21 Nov 2025 11:02:13 +0100 Subject: [PATCH 652/691] Update protobufs (#8707) Co-authored-by: caveman99 <25002+caveman99@users.noreply.github.com> --- protobufs | 2 +- src/mesh/generated/meshtastic/mesh.pb.h | 12 +++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/protobufs b/protobufs index 7654db2e2..52fa252f1 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit 7654db2e2d1834aebde40090a9b74162ad1048ae +Subproject commit 52fa252f1e01be87ad2f7ab17ceef7882b2a4a93 diff --git a/src/mesh/generated/meshtastic/mesh.pb.h b/src/mesh/generated/meshtastic/mesh.pb.h index 0da44cce0..46de1dee0 100644 --- a/src/mesh/generated/meshtastic/mesh.pb.h +++ b/src/mesh/generated/meshtastic/mesh.pb.h @@ -288,6 +288,12 @@ typedef enum _meshtastic_HardwareModel { meshtastic_HardwareModel_WISMESH_TAP_V2 = 116, /* RAK3401 */ meshtastic_HardwareModel_RAK3401 = 117, + /* RAK6421 Hat+ */ + meshtastic_HardwareModel_RAK6421 = 118, + /* Elecrow ThinkNode M4 */ + meshtastic_HardwareModel_THINKNODE_M4 = 119, + /* Elecrow ThinkNode M6 */ + meshtastic_HardwareModel_THINKNODE_M6 = 120, /* ------------------------------------------------------------------------------------------------------------------------------------------ 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. ------------------------------------------------------------------------------------------------------------------------------------------ */ @@ -825,7 +831,11 @@ typedef struct _meshtastic_MeshPacket { Note: Our crypto implementation uses this field as well. See [crypto](/docs/overview/encryption) for details. */ uint32_t from; - /* The (immediate) destination for this packet */ + /* The (immediate) destination for this packet + If the value is 4,294,967,295 (maximum value of an unsigned 32bit integer), this indicates that the packet was + not destined for a specific node, but for a channel as indicated by the value of `channel` below. + If the value is another, this indicates that the packet was destined for a specific + node (i.e. a kind of "Direct Message" to this node) and not broadcast on a channel. */ uint32_t to; /* (Usually) If set, this indicates the index in the secondary_channels table that this packet was sent/received on. If unset, packet was on the primary channel. From a4c92d9fd5f55ac5042c84aa41f3f74fae1784e5 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 21 Nov 2025 11:02:13 +0100 Subject: [PATCH 653/691] Update protobufs (#8707) Co-authored-by: caveman99 <25002+caveman99@users.noreply.github.com> --- protobufs | 2 +- src/mesh/generated/meshtastic/mesh.pb.h | 12 +++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/protobufs b/protobufs index 7654db2e2..52fa252f1 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit 7654db2e2d1834aebde40090a9b74162ad1048ae +Subproject commit 52fa252f1e01be87ad2f7ab17ceef7882b2a4a93 diff --git a/src/mesh/generated/meshtastic/mesh.pb.h b/src/mesh/generated/meshtastic/mesh.pb.h index 0da44cce0..46de1dee0 100644 --- a/src/mesh/generated/meshtastic/mesh.pb.h +++ b/src/mesh/generated/meshtastic/mesh.pb.h @@ -288,6 +288,12 @@ typedef enum _meshtastic_HardwareModel { meshtastic_HardwareModel_WISMESH_TAP_V2 = 116, /* RAK3401 */ meshtastic_HardwareModel_RAK3401 = 117, + /* RAK6421 Hat+ */ + meshtastic_HardwareModel_RAK6421 = 118, + /* Elecrow ThinkNode M4 */ + meshtastic_HardwareModel_THINKNODE_M4 = 119, + /* Elecrow ThinkNode M6 */ + meshtastic_HardwareModel_THINKNODE_M6 = 120, /* ------------------------------------------------------------------------------------------------------------------------------------------ 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. ------------------------------------------------------------------------------------------------------------------------------------------ */ @@ -825,7 +831,11 @@ typedef struct _meshtastic_MeshPacket { Note: Our crypto implementation uses this field as well. See [crypto](/docs/overview/encryption) for details. */ uint32_t from; - /* The (immediate) destination for this packet */ + /* The (immediate) destination for this packet + If the value is 4,294,967,295 (maximum value of an unsigned 32bit integer), this indicates that the packet was + not destined for a specific node, but for a channel as indicated by the value of `channel` below. + If the value is another, this indicates that the packet was destined for a specific + node (i.e. a kind of "Direct Message" to this node) and not broadcast on a channel. */ uint32_t to; /* (Usually) If set, this indicates the index in the secondary_channels table that this packet was sent/received on. If unset, packet was on the primary channel. From c051c56544e4faedd7c9b5e75ecf11ec2cd88b54 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 22 Nov 2025 08:26:39 -0600 Subject: [PATCH 654/691] Update Kongduino-Adafruit_nRFCrypto digest to 8cde718 (#8708) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- arch/nrf52/nrf52840.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/nrf52/nrf52840.ini b/arch/nrf52/nrf52840.ini index 5e846b3b7..e13443152 100644 --- a/arch/nrf52/nrf52840.ini +++ b/arch/nrf52/nrf52840.ini @@ -8,7 +8,7 @@ lib_deps = ${environmental_base.lib_deps} ${environmental_extra.lib_deps} # renovate: datasource=git-refs depName=Kongduino-Adafruit_nRFCrypto packageName=https://github.com/Kongduino/Adafruit_nRFCrypto gitBranch=master - https://github.com/Kongduino/Adafruit_nRFCrypto/archive/5f838d2709461a2c981f642917aa50254a25c14c.zip + https://github.com/Kongduino/Adafruit_nRFCrypto/archive/8cde7189b5ead9dcd49f72601b43b969c0bbc06e.zip ; Common NRF52 debugging settings follow. See the Meshtastic developer docs for how to connect SWD debugging probes to your board. From 376dc7ef3a184113ff8060c5a4c6870209f55690 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 22 Nov 2025 08:27:00 -0600 Subject: [PATCH 655/691] Update actions/checkout action to v6 (#8695) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/actions/setup-base/action.yml | 2 +- .github/workflows/build_debian_src.yml | 2 +- .github/workflows/build_firmware.yml | 2 +- .github/workflows/build_one_arch.yml | 6 +++--- .github/workflows/build_one_target.yml | 6 +++--- .github/workflows/docker_build.yml | 2 +- .github/workflows/docker_manifest.yml | 2 +- .github/workflows/hook_copr.yml | 2 +- .github/workflows/main_matrix.yml | 14 +++++++------- .github/workflows/merge_queue.yml | 14 +++++++------- .github/workflows/nightly.yml | 4 ++-- .github/workflows/package_obs.yml | 2 +- .github/workflows/package_pio_deps.yml | 2 +- .github/workflows/package_ppa.yml | 2 +- .github/workflows/pr_tests.yml | 2 +- .github/workflows/release_channels.yml | 2 +- .github/workflows/sec_sast_semgrep_cron.yml | 2 +- .github/workflows/sec_sast_semgrep_pull.yml | 2 +- .github/workflows/test_native.yml | 6 +++--- .github/workflows/tests.yml | 2 +- .github/workflows/trunk_annotate_pr.yml | 2 +- .github/workflows/trunk_check.yml | 2 +- .github/workflows/trunk_format_pr.yml | 2 +- .github/workflows/update_protobufs.yml | 2 +- 24 files changed, 43 insertions(+), 43 deletions(-) diff --git a/.github/actions/setup-base/action.yml b/.github/actions/setup-base/action.yml index f6c1fd80c..80f5c6855 100644 --- a/.github/actions/setup-base/action.yml +++ b/.github/actions/setup-base/action.yml @@ -5,7 +5,7 @@ runs: using: composite steps: - name: Checkout code - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: submodules: recursive ref: ${{github.event.pull_request.head.ref}} diff --git a/.github/workflows/build_debian_src.yml b/.github/workflows/build_debian_src.yml index d36e7fea1..d7d26f0e8 100644 --- a/.github/workflows/build_debian_src.yml +++ b/.github/workflows/build_debian_src.yml @@ -24,7 +24,7 @@ jobs: runs-on: ubuntu-24.04 steps: - name: Checkout code - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: submodules: recursive path: meshtasticd diff --git a/.github/workflows/build_firmware.yml b/.github/workflows/build_firmware.yml index 57c1e72c7..9ac84c23e 100644 --- a/.github/workflows/build_firmware.yml +++ b/.github/workflows/build_firmware.yml @@ -22,7 +22,7 @@ jobs: outputs: artifact-id: ${{ steps.upload.outputs.artifact-id }} steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: submodules: recursive ref: ${{github.event.pull_request.head.ref}} diff --git a/.github/workflows/build_one_arch.yml b/.github/workflows/build_one_arch.yml index 6d5462c93..5673f8cb6 100644 --- a/.github/workflows/build_one_arch.yml +++ b/.github/workflows/build_one_arch.yml @@ -26,7 +26,7 @@ jobs: setup: runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: actions/setup-python@v6 with: python-version: 3.x @@ -44,7 +44,7 @@ jobs: version: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Get release version string run: | echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT @@ -108,7 +108,7 @@ jobs: needs: [version, build] steps: - name: Checkout code - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: ref: ${{github.event.pull_request.head.ref}} repository: ${{github.event.pull_request.head.repo.full_name}} diff --git a/.github/workflows/build_one_target.yml b/.github/workflows/build_one_target.yml index 46362a629..343e5be64 100644 --- a/.github/workflows/build_one_target.yml +++ b/.github/workflows/build_one_target.yml @@ -45,7 +45,7 @@ jobs: runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: actions/setup-python@v6 with: python-version: 3.x @@ -66,7 +66,7 @@ jobs: if: ${{ inputs.target != '' }} runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Get release version string run: | echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT @@ -114,7 +114,7 @@ jobs: needs: [version, build] steps: - name: Checkout code - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: ref: ${{github.event.pull_request.head.ref}} repository: ${{github.event.pull_request.head.repo.full_name}} diff --git a/.github/workflows/docker_build.yml b/.github/workflows/docker_build.yml index 26a9cff18..8d19af894 100644 --- a/.github/workflows/docker_build.yml +++ b/.github/workflows/docker_build.yml @@ -47,7 +47,7 @@ jobs: runs-on: ${{ inputs.runs-on }} steps: - name: Checkout code - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: submodules: recursive ref: ${{github.event.pull_request.head.ref}} diff --git a/.github/workflows/docker_manifest.yml b/.github/workflows/docker_manifest.yml index 20b9ceee6..396ddb68e 100644 --- a/.github/workflows/docker_manifest.yml +++ b/.github/workflows/docker_manifest.yml @@ -83,7 +83,7 @@ jobs: runs-on: ubuntu-24.04 steps: - name: Checkout code - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: submodules: recursive ref: ${{github.event.pull_request.head.ref}} diff --git a/.github/workflows/hook_copr.yml b/.github/workflows/hook_copr.yml index 2204cc02c..eb4ebc57b 100644 --- a/.github/workflows/hook_copr.yml +++ b/.github/workflows/hook_copr.yml @@ -19,7 +19,7 @@ jobs: runs-on: ubuntu-24.04 steps: - name: Checkout code - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: submodules: recursive ref: ${{ github.ref }} diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml index 7ea033d55..38373a2fc 100644 --- a/.github/workflows/main_matrix.yml +++ b/.github/workflows/main_matrix.yml @@ -35,7 +35,7 @@ jobs: - check runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: actions/setup-python@v6 with: python-version: 3.x @@ -59,7 +59,7 @@ jobs: version: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Get release version string run: | echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT @@ -81,7 +81,7 @@ jobs: runs-on: ubuntu-latest if: ${{ github.event_name != 'workflow_dispatch' && github.repository == 'meshtastic/firmware' }} steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Build base id: base uses: ./.github/actions/setup-base @@ -163,7 +163,7 @@ jobs: needs: [version, build] steps: - name: Checkout code - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: ref: ${{github.event.pull_request.head.ref}} repository: ${{github.event.pull_request.head.repo.full_name}} @@ -242,7 +242,7 @@ jobs: - package-pio-deps-native-tft steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Setup Python uses: actions/setup-python@v6 @@ -311,7 +311,7 @@ jobs: needs: [release-artifacts, version] steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Setup Python uses: actions/setup-python@v6 @@ -366,7 +366,7 @@ jobs: esp32,esp32s3,esp32c3,esp32c6,nrf52840,rp2040,rp2350,stm32 steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Setup Python uses: actions/setup-python@v6 diff --git a/.github/workflows/merge_queue.yml b/.github/workflows/merge_queue.yml index 6d69258c9..154b230c7 100644 --- a/.github/workflows/merge_queue.yml +++ b/.github/workflows/merge_queue.yml @@ -17,7 +17,7 @@ jobs: - check runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: actions/setup-python@v6 with: python-version: 3.x @@ -40,7 +40,7 @@ jobs: version: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Get release version string run: | echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT @@ -62,7 +62,7 @@ jobs: runs-on: ubuntu-latest if: ${{ github.event_name != 'workflow_dispatch' }} steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Build base id: base uses: ./.github/actions/setup-base @@ -142,7 +142,7 @@ jobs: needs: [version, build] steps: - name: Checkout code - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: ref: ${{github.event.pull_request.head.ref}} repository: ${{github.event.pull_request.head.repo.full_name}} @@ -221,7 +221,7 @@ jobs: - package-pio-deps-native-tft steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Setup Python uses: actions/setup-python@v6 @@ -290,7 +290,7 @@ jobs: needs: [release-artifacts, version] steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Setup Python uses: actions/setup-python@v6 @@ -345,7 +345,7 @@ jobs: esp32,esp32s3,esp32c3,esp32c6,nrf52840,rp2040,rp2350,stm32 steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Setup Python uses: actions/setup-python@v6 diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index f26073ec4..045e94895 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -14,7 +14,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Trunk Check uses: trunk-io/trunk-action@v1 @@ -31,7 +31,7 @@ jobs: pull-requests: write # For trunk to create PRs steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Trunk Upgrade uses: trunk-io/trunk-action/upgrade@v1 diff --git a/.github/workflows/package_obs.yml b/.github/workflows/package_obs.yml index b8a829d9a..2b202ed95 100644 --- a/.github/workflows/package_obs.yml +++ b/.github/workflows/package_obs.yml @@ -34,7 +34,7 @@ jobs: needs: build-debian-src steps: - name: Checkout code - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: submodules: recursive path: meshtasticd diff --git a/.github/workflows/package_pio_deps.yml b/.github/workflows/package_pio_deps.yml index c52dfe348..cb10a79f3 100644 --- a/.github/workflows/package_pio_deps.yml +++ b/.github/workflows/package_pio_deps.yml @@ -24,7 +24,7 @@ jobs: runs-on: ubuntu-24.04 steps: - name: Checkout code - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: submodules: recursive ref: ${{github.event.pull_request.head.ref}} diff --git a/.github/workflows/package_ppa.yml b/.github/workflows/package_ppa.yml index 2d6c257e6..2e3278041 100644 --- a/.github/workflows/package_ppa.yml +++ b/.github/workflows/package_ppa.yml @@ -32,7 +32,7 @@ jobs: needs: build-debian-src steps: - name: Checkout code - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: submodules: recursive path: meshtasticd diff --git a/.github/workflows/pr_tests.yml b/.github/workflows/pr_tests.yml index c3a964e04..048186538 100644 --- a/.github/workflows/pr_tests.yml +++ b/.github/workflows/pr_tests.yml @@ -40,7 +40,7 @@ jobs: checks: write pull-requests: write steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: submodules: recursive diff --git a/.github/workflows/release_channels.yml b/.github/workflows/release_channels.yml index 4e5a48dfe..f21b13ee1 100644 --- a/.github/workflows/release_channels.yml +++ b/.github/workflows/release_channels.yml @@ -60,7 +60,7 @@ jobs: shell: bash steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: # Always use master branch for version bumps ref: master diff --git a/.github/workflows/sec_sast_semgrep_cron.yml b/.github/workflows/sec_sast_semgrep_cron.yml index dfb828bf6..d044f9038 100644 --- a/.github/workflows/sec_sast_semgrep_cron.yml +++ b/.github/workflows/sec_sast_semgrep_cron.yml @@ -21,7 +21,7 @@ jobs: steps: # step 1 - name: clone application source code - uses: actions/checkout@v5 + uses: actions/checkout@v6 # step 2 - name: full scan diff --git a/.github/workflows/sec_sast_semgrep_pull.yml b/.github/workflows/sec_sast_semgrep_pull.yml index e93b2ae8b..e9b4108a1 100644 --- a/.github/workflows/sec_sast_semgrep_pull.yml +++ b/.github/workflows/sec_sast_semgrep_pull.yml @@ -13,7 +13,7 @@ jobs: steps: # step 1 - name: clone application source code - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: fetch-depth: 0 diff --git a/.github/workflows/test_native.yml b/.github/workflows/test_native.yml index 591d52bd0..a2328022e 100644 --- a/.github/workflows/test_native.yml +++ b/.github/workflows/test_native.yml @@ -14,7 +14,7 @@ jobs: name: Native Simulator Tests runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: ref: ${{github.event.pull_request.head.ref}} repository: ${{github.event.pull_request.head.repo.full_name}} @@ -70,7 +70,7 @@ jobs: name: Native PlatformIO Tests runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: ref: ${{github.event.pull_request.head.ref}} repository: ${{github.event.pull_request.head.repo.full_name}} @@ -127,7 +127,7 @@ jobs: - platformio-tests if: always() steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: ref: ${{github.event.pull_request.head.ref}} repository: ${{github.event.pull_request.head.repo.full_name}} diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 1ec435512..4a97853e2 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -20,7 +20,7 @@ jobs: runs-on: test-runner steps: - name: Checkout code - uses: actions/checkout@v5 + uses: actions/checkout@v6 # - uses: actions/setup-python@v5 # with: diff --git a/.github/workflows/trunk_annotate_pr.yml b/.github/workflows/trunk_annotate_pr.yml index 23dcf8d09..59ab25c28 100644 --- a/.github/workflows/trunk_annotate_pr.yml +++ b/.github/workflows/trunk_annotate_pr.yml @@ -18,7 +18,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Trunk Check uses: trunk-io/trunk-action@v1 diff --git a/.github/workflows/trunk_check.yml b/.github/workflows/trunk_check.yml index 41731d491..874374fe0 100644 --- a/.github/workflows/trunk_check.yml +++ b/.github/workflows/trunk_check.yml @@ -16,7 +16,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Trunk Check uses: trunk-io/trunk-action@v1 diff --git a/.github/workflows/trunk_format_pr.yml b/.github/workflows/trunk_format_pr.yml index 51082fc5f..8fa0cc1eb 100644 --- a/.github/workflows/trunk_format_pr.yml +++ b/.github/workflows/trunk_format_pr.yml @@ -15,7 +15,7 @@ jobs: pull-requests: write steps: - name: Checkout repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: ref: ${{github.event.pull_request.head.ref}} repository: ${{github.event.pull_request.head.repo.full_name}} diff --git a/.github/workflows/update_protobufs.yml b/.github/workflows/update_protobufs.yml index c06e06b0a..af0557fda 100644 --- a/.github/workflows/update_protobufs.yml +++ b/.github/workflows/update_protobufs.yml @@ -11,7 +11,7 @@ jobs: pull-requests: write steps: - name: Checkout code - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: submodules: true From 14463043bd3c4d01f79ea7d5c70d1279ae22ef15 Mon Sep 17 00:00:00 2001 From: Avi0n <14863961+Avi0n@users.noreply.github.com> Date: Sat, 22 Nov 2025 10:03:47 -0800 Subject: [PATCH 656/691] Add WisMesh Tag OCV array (#8646) * Add WisMesh Tag OCV array * Update 10% to 3650 --------- Co-authored-by: Jonathan Bennett --- src/power.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/power.h b/src/power.h index 8fc7c8f45..84efbeb9e 100644 --- a/src/power.h +++ b/src/power.h @@ -36,6 +36,8 @@ #define OCV_ARRAY 4200, 3986, 3922, 3812, 3734, 3645, 3527, 3420, 3281, 3087, 2786 #elif defined(R1_NEO) #define OCV_ARRAY 4330, 4292, 4254, 4216, 4178, 4140, 4102, 4064, 4026, 3988, 3950 +#elif defined(WISMESH_TAG) +#define OCV_ARRAY 4240, 4112, 4029, 3970, 3906, 3846, 3824, 3802, 3776, 3650, 3072 #else // LiIon #define OCV_ARRAY 4190, 4050, 3990, 3890, 3800, 3720, 3630, 3530, 3420, 3300, 3100 #endif From f4e260e0f1dc45b08d25827deb53b50f24d907e7 Mon Sep 17 00:00:00 2001 From: simon-muzi Date: Sat, 22 Nov 2025 14:54:10 -0500 Subject: [PATCH 657/691] R1 Neo - Added OCV_ARRAY from measured discharge curve testing + update ADC multiplier (#8716) * Added OCV_ARRAY from measured discharge curve testing and update ADC multiplier The ADC resistor divider ratio is 0.6 -> multiplier should be 1/0.6 ~=1.667 We data logged a full discharge curve at constant 100mA draw over 15hours to get a realistic voltage curve for battery SoC measurements. * Remove power.h in favor of variant.h --------- Co-authored-by: Jason P Co-authored-by: Jonathan Bennett --- src/power.h | 2 -- variants/nrf52840/r1-neo/variant.h | 3 ++- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/power.h b/src/power.h index 84efbeb9e..3f28dedb2 100644 --- a/src/power.h +++ b/src/power.h @@ -34,8 +34,6 @@ #define OCV_ARRAY 4200, 3876, 3826, 3763, 3713, 3660, 3573, 3485, 3422, 3359, 3300 #elif defined(SEEED_SOLAR_NODE) #define OCV_ARRAY 4200, 3986, 3922, 3812, 3734, 3645, 3527, 3420, 3281, 3087, 2786 -#elif defined(R1_NEO) -#define OCV_ARRAY 4330, 4292, 4254, 4216, 4178, 4140, 4102, 4064, 4026, 3988, 3950 #elif defined(WISMESH_TAG) #define OCV_ARRAY 4240, 4112, 4029, 3970, 3906, 3846, 3824, 3802, 3776, 3650, 3072 #else // LiIon diff --git a/variants/nrf52840/r1-neo/variant.h b/variants/nrf52840/r1-neo/variant.h index 901e993e3..ef975433a 100644 --- a/variants/nrf52840/r1-neo/variant.h +++ b/variants/nrf52840/r1-neo/variant.h @@ -132,7 +132,8 @@ static const uint8_t SCK = PIN_SPI_SCK; #undef AREF_VOLTAGE #define AREF_VOLTAGE 3.0 #define VBAT_AR_INTERNAL AR_INTERNAL_3_0 -#define ADC_MULTIPLIER 1.73 +#define ADC_MULTIPLIER 1.667 +#define OCV_ARRAY 4200, 4020, 4000, 3940, 3870, 3820, 3750, 3630, 3550, 3450, 3100 #define HAS_RTC 1 From b18794e98da1f346e0290747ca97744a79632d20 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Sat, 22 Nov 2025 13:54:24 -0600 Subject: [PATCH 658/691] Log error if startReceive fails in LR11x0Interface (#8718) --- src/mesh/LR11x0Interface.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/mesh/LR11x0Interface.cpp b/src/mesh/LR11x0Interface.cpp index 3831a384d..af6dd92e9 100644 --- a/src/mesh/LR11x0Interface.cpp +++ b/src/mesh/LR11x0Interface.cpp @@ -244,6 +244,8 @@ template void LR11x0Interface::startReceive() // We use a 16 bit preamble so this should save some power by letting radio sit in standby mostly. int err = lora.startReceive(RADIOLIB_LR11X0_RX_TIMEOUT_INF, MESHTASTIC_RADIOLIB_IRQ_RX_FLAGS, RADIOLIB_IRQ_RX_DEFAULT_MASK, 0); + if (err) + LOG_ERROR("StartReceive error: %d", err); assert(err == RADIOLIB_ERR_NONE); RadioLibInterface::startReceive(); @@ -304,4 +306,4 @@ template bool LR11x0Interface::sleep() return true; } -#endif \ No newline at end of file +#endif From 1bfa9ed4c4d166e3141a392dc601aa10b5a917e0 Mon Sep 17 00:00:00 2001 From: simon-muzi Date: Sat, 22 Nov 2025 17:35:10 -0500 Subject: [PATCH 659/691] Tweak OCV_ARRAY 100% voltage to take into account charger hysteresis and voltage sag after charge (#8720) Measured voltage of fully charged battery after a few minutes of rest --- variants/nrf52840/r1-neo/variant.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/variants/nrf52840/r1-neo/variant.h b/variants/nrf52840/r1-neo/variant.h index ef975433a..b1d96ebd0 100644 --- a/variants/nrf52840/r1-neo/variant.h +++ b/variants/nrf52840/r1-neo/variant.h @@ -133,7 +133,7 @@ static const uint8_t SCK = PIN_SPI_SCK; #define AREF_VOLTAGE 3.0 #define VBAT_AR_INTERNAL AR_INTERNAL_3_0 #define ADC_MULTIPLIER 1.667 -#define OCV_ARRAY 4200, 4020, 4000, 3940, 3870, 3820, 3750, 3630, 3550, 3450, 3100 +#define OCV_ARRAY 4120, 4020, 4000, 3940, 3870, 3820, 3750, 3630, 3550, 3450, 3100 #define HAS_RTC 1 From 5d7da6868e02521dc32d9df69c140abd2cdfd030 Mon Sep 17 00:00:00 2001 From: Chloe Bethel Date: Mon, 24 Nov 2025 01:40:27 +0000 Subject: [PATCH 660/691] Support overriding GPS serial pins on all architectures (#8486) --- src/gps/GPS.cpp | 37 ++++++++++++++++++++++++------------- src/gps/GPS.h | 2 ++ 2 files changed, 26 insertions(+), 13 deletions(-) diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index 0404ae5f8..a61a71dde 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -38,14 +38,16 @@ template std::size_t array_count(const T (&)[N]) return N; } -#if defined(NRF52840_XXAA) || defined(NRF52833_XXAA) || defined(ARCH_ESP32) || defined(ARCH_PORTDUINO) || defined(ARCH_STM32WL) -#if defined(GPS_SERIAL_PORT) -HardwareSerial *GPS::_serial_gps = &GPS_SERIAL_PORT; -#else -HardwareSerial *GPS::_serial_gps = &Serial1; +#ifndef GPS_SERIAL_PORT +#define GPS_SERIAL_PORT Serial1 #endif + +#if defined(ARCH_NRF52) +Uart *GPS::_serial_gps = &GPS_SERIAL_PORT; +#elif defined(ARCH_ESP32) || defined(ARCH_PORTDUINO) || defined(ARCH_STM32WL) +HardwareSerial *GPS::_serial_gps = &GPS_SERIAL_PORT; #elif defined(ARCH_RP2040) -SerialUART *GPS::_serial_gps = &Serial1; +SerialUART *GPS::_serial_gps = &GPS_SERIAL_PORT; #else HardwareSerial *GPS::_serial_gps = nullptr; #endif @@ -1525,10 +1527,7 @@ GPS *GPS::createGps() int8_t _rx_gpio = config.position.rx_gpio; int8_t _tx_gpio = config.position.tx_gpio; int8_t _en_gpio = config.position.gps_en_gpio; -#if HAS_GPS && !defined(ARCH_ESP32) - _rx_gpio = 1; // We only specify GPS serial ports on ESP32. Otherwise, these are just flags. - _tx_gpio = 1; -#endif + #if defined(GPS_RX_PIN) if (!_rx_gpio) _rx_gpio = GPS_RX_PIN; @@ -1602,16 +1601,28 @@ GPS *GPS::createGps() _serial_gps->setRxBufferSize(SERIAL_BUFFER_SIZE); // the default is 256 #endif -// ESP32 has a special set of parameters vs other arduino ports -#if defined(ARCH_ESP32) LOG_DEBUG("Use GPIO%d for GPS RX", new_gps->rx_gpio); LOG_DEBUG("Use GPIO%d for GPS TX", new_gps->tx_gpio); + +// ESP32 has a special set of parameters vs other arduino ports +#if defined(ARCH_ESP32) _serial_gps->begin(GPS_BAUDRATE, SERIAL_8N1, new_gps->rx_gpio, new_gps->tx_gpio); #elif defined(ARCH_RP2040) + _serial_gps->setPinout(new_gps->tx_gpio, new_gps->rx_gpio); _serial_gps->setFIFOSize(256); _serial_gps->begin(GPS_BAUDRATE); -#else +#elif defined(ARCH_NRF52) + _serial_gps->setPins(new_gps->rx_gpio, new_gps->tx_gpio); _serial_gps->begin(GPS_BAUDRATE); +#elif defined(ARCH_STM32WL) + _serial_gps->setTx(new_gps->tx_gpio); + _serial_gps->setRx(new_gps->rx_gpio); + _serial_gps->begin(GPS_BAUDRATE); +#elif defined(ARCH_PORTDUINO) + // Portduino can't set the GPS pins directly. + _serial_gps->begin(GPS_BAUDRATE); +#else +#error Unsupported architecture! #endif } return new_gps; diff --git a/src/gps/GPS.h b/src/gps/GPS.h index 8ba1ce0a6..59cee7113 100644 --- a/src/gps/GPS.h +++ b/src/gps/GPS.h @@ -194,6 +194,8 @@ class GPS : private concurrency::OSThread /** If !NULL we will use this serial port to construct our GPS */ #if defined(ARCH_RP2040) static SerialUART *_serial_gps; +#elif defined(ARCH_NRF52) + static Uart *_serial_gps; #else static HardwareSerial *_serial_gps; #endif From ed4a798c60f8d25e9f7037d69b4b001f6b1a6d81 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Mon, 24 Nov 2025 16:35:54 -0600 Subject: [PATCH 661/691] Thinknode M3 support against master (#8630) * Add variant_shutdown() as a week function in main-nrf52.cpp * Add Status LED module * Add Thinknode M3 support * Catch case of BLE disabled * Update src/modules/StatusLEDModule.cpp Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update src/modules/StatusLEDModule.cpp Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update variants/nrf52840/ELECROW-ThinkNode-M3/rfswitch.h Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Remove unused pin * M3 pairing LED only active for 30 seconds after state change * Thinknode M3 shutdown work --------- Co-authored-by: Ben Meadors Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- boards/ThinkNode-M3.json | 53 ++++++++ src/configuration.h | 7 + src/modules/Modules.cpp | 6 + src/modules/SerialModule.cpp | 9 +- src/modules/StatusLEDModule.cpp | 94 ++++++++++++++ src/modules/StatusLEDModule.h | 44 +++++++ src/modules/Telemetry/Sensor/AHT10.cpp | 2 +- src/modules/Telemetry/Sensor/AHT10.h | 4 + src/platform/nrf52/architecture.h | 4 + src/platform/nrf52/main-nrf52.cpp | 6 + .../ELECROW-ThinkNode-M3/platformio.ini | 17 +++ .../nrf52840/ELECROW-ThinkNode-M3/rfswitch.h | 15 +++ .../nrf52840/ELECROW-ThinkNode-M3/variant.cpp | 93 +++++++++++++ .../nrf52840/ELECROW-ThinkNode-M3/variant.h | 122 ++++++++++++++++++ 14 files changed, 471 insertions(+), 5 deletions(-) create mode 100644 boards/ThinkNode-M3.json create mode 100644 src/modules/StatusLEDModule.cpp create mode 100644 src/modules/StatusLEDModule.h create mode 100644 variants/nrf52840/ELECROW-ThinkNode-M3/platformio.ini create mode 100644 variants/nrf52840/ELECROW-ThinkNode-M3/rfswitch.h create mode 100644 variants/nrf52840/ELECROW-ThinkNode-M3/variant.cpp create mode 100644 variants/nrf52840/ELECROW-ThinkNode-M3/variant.h diff --git a/boards/ThinkNode-M3.json b/boards/ThinkNode-M3.json new file mode 100644 index 000000000..ff21e046a --- /dev/null +++ b/boards/ThinkNode-M3.json @@ -0,0 +1,53 @@ +{ + "build": { + "arduino": { + "ldscript": "nrf52840_s140_v6.ld" + }, + "core": "nRF5", + "cpu": "cortex-m4", + "extra_flags": "-DNRF52840_XXAA", + "f_cpu": "64000000L", + "hwids": [ + ["0x239A", "0x4405"], + ["0x239A", "0x0029"], + ["0x239A", "0x002A"] + ], + "usb_product": "elecrow_eink", + "mcu": "nrf52840", + "variant": "ELECROW-ThinkNode-M3", + "variants_dir": "variants", + "bsp": { + "name": "adafruit" + }, + "softdevice": { + "sd_flags": "-DS140", + "sd_name": "s140", + "sd_version": "6.1.1", + "sd_fwid": "0x00B6" + }, + "bootloader": { + "settings_addr": "0xFF000" + } + }, + "connectivity": ["bluetooth"], + "debug": { + "jlink_device": "nRF52840_xxAA", + "onboard_tools": ["jlink"], + "svd_path": "nrf52840.svd", + "openocd_target": "nrf52840-mdk-rs" + }, + "frameworks": ["arduino"], + "name": "elecrow nrf", + "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": "", + "vendor": "ELECROW" +} diff --git a/src/configuration.h b/src/configuration.h index 8ec3b2211..d37269995 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -396,6 +396,13 @@ along with this program. If not, see . #define HAS_RGB_LED #endif +#ifndef LED_STATE_OFF +#define LED_STATE_OFF 0 +#endif +#ifndef LED_STATE_ON +#define LED_STATE_ON 1 +#endif + // default mapping of pins #if defined(PIN_BUTTON2) && !defined(CANCEL_BUTTON_PIN) #define ALT_BUTTON_PIN PIN_BUTTON2 diff --git a/src/modules/Modules.cpp b/src/modules/Modules.cpp index e477574dd..9e4401e05 100644 --- a/src/modules/Modules.cpp +++ b/src/modules/Modules.cpp @@ -13,6 +13,8 @@ #include "input/TrackballInterruptImpl1.h" #endif +#include "modules/StatusLEDModule.h" + #if !MESHTASTIC_EXCLUDE_I2C #include "input/cardKbI2cImpl.h" #endif @@ -119,6 +121,10 @@ void setupModules() buzzerFeedbackThread = new BuzzerFeedbackThread(); } #endif +#if defined(LED_CHARGE) || defined(LED_PAIRING) + statusLEDModule = new StatusLEDModule(); +#endif + #if !MESHTASTIC_EXCLUDE_ADMIN adminModule = new AdminModule(); #endif diff --git a/src/modules/SerialModule.cpp b/src/modules/SerialModule.cpp index 575e9fa96..d04daf594 100644 --- a/src/modules/SerialModule.cpp +++ b/src/modules/SerialModule.cpp @@ -64,7 +64,7 @@ SerialModule *serialModule; SerialModuleRadio *serialModuleRadio; #if defined(TTGO_T_ECHO) || defined(CANARYONE) || defined(MESHLINK) || defined(ELECROW_ThinkNode_M1) || \ - defined(ELECROW_ThinkNode_M5) || defined(HELTEC_MESH_SOLAR) || defined(T_ECHO_LITE) + defined(ELECROW_ThinkNode_M5) || defined(HELTEC_MESH_SOLAR) || defined(T_ECHO_LITE) || defined(ELECROW_ThinkNode_M3) SerialModule::SerialModule() : StreamAPI(&Serial), concurrency::OSThread("Serial") { api_type = TYPE_SERIAL; @@ -204,7 +204,7 @@ int32_t SerialModule::runOnce() Serial.setTimeout(moduleConfig.serial.timeout > 0 ? moduleConfig.serial.timeout : TIMEOUT); } #elif !defined(TTGO_T_ECHO) && !defined(T_ECHO_LITE) && !defined(CANARYONE) && !defined(MESHLINK) && \ - !defined(ELECROW_ThinkNode_M1) && !defined(ELECROW_ThinkNode_M5) + !defined(ELECROW_ThinkNode_M1) && !defined(ELECROW_ThinkNode_M3) && !defined(ELECROW_ThinkNode_M5) if (moduleConfig.serial.rxd && moduleConfig.serial.txd) { #ifdef ARCH_RP2040 Serial2.setFIFOSize(RX_BUFFER); @@ -261,7 +261,7 @@ int32_t SerialModule::runOnce() } #if !defined(TTGO_T_ECHO) && !defined(T_ECHO_LITE) && !defined(CANARYONE) && !defined(MESHLINK) && \ - !defined(ELECROW_ThinkNode_M1) && !defined(ELECROW_ThinkNode_M5) + !defined(ELECROW_ThinkNode_M1) && !defined(ELECROW_ThinkNode_M3) && !defined(ELECROW_ThinkNode_M5) else if ((moduleConfig.serial.mode == meshtastic_ModuleConfig_SerialConfig_Serial_Mode_WS85)) { processWXSerial(); @@ -536,7 +536,8 @@ 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(ARCH_STM32WL) + !defined(MESHLINK) && !defined(ELECROW_ThinkNode_M1) && !defined(ELECROW_ThinkNode_M3) && !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/StatusLEDModule.cpp b/src/modules/StatusLEDModule.cpp new file mode 100644 index 000000000..fc9ed310e --- /dev/null +++ b/src/modules/StatusLEDModule.cpp @@ -0,0 +1,94 @@ +#include "StatusLEDModule.h" +#include "MeshService.h" +#include "configuration.h" +#include + +/* +StatusLEDModule manages the device's status LEDs, updating their states based on power and Bluetooth status. +It reflects charging, charged, discharging, and Bluetooth connection states using the appropriate LEDs. +*/ +StatusLEDModule *statusLEDModule; + +StatusLEDModule::StatusLEDModule() : concurrency::OSThread("StatusLEDModule") +{ + bluetoothStatusObserver.observe(&bluetoothStatus->onNewStatus); + powerStatusObserver.observe(&powerStatus->onNewStatus); +} + +int StatusLEDModule::handleStatusUpdate(const meshtastic::Status *arg) +{ + switch (arg->getStatusType()) { + case STATUS_TYPE_POWER: { + meshtastic::PowerStatus *powerStatus = (meshtastic::PowerStatus *)arg; + if (powerStatus->getHasUSB()) { + power_state = charging; + if (powerStatus->getBatteryChargePercent() >= 100) { + power_state = charged; + } + } else { + power_state = discharging; + } + break; + } + case STATUS_TYPE_BLUETOOTH: { + meshtastic::BluetoothStatus *bluetoothStatus = (meshtastic::BluetoothStatus *)arg; + switch (bluetoothStatus->getConnectionState()) { + case meshtastic::BluetoothStatus::ConnectionState::DISCONNECTED: { + ble_state = unpaired; + PAIRING_LED_starttime = millis(); + break; + } + case meshtastic::BluetoothStatus::ConnectionState::PAIRING: { + ble_state = pairing; + PAIRING_LED_starttime = millis(); + break; + } + case meshtastic::BluetoothStatus::ConnectionState::CONNECTED: { + ble_state = connected; + PAIRING_LED_starttime = millis(); + break; + } + } + + break; + } + } + return 0; +}; + +int32_t StatusLEDModule::runOnce() +{ + + if (power_state == charging) { + CHARGE_LED_state = !CHARGE_LED_state; + } else if (power_state == charged) { + CHARGE_LED_state = LED_STATE_ON; + } else { + CHARGE_LED_state = LED_STATE_OFF; + } + + if (!config.bluetooth.enabled || PAIRING_LED_starttime + 30 * 1000 < millis()) { + PAIRING_LED_state = LED_STATE_OFF; + } else if (ble_state == unpaired) { + if (slowTrack) { + PAIRING_LED_state = !PAIRING_LED_state; + slowTrack = false; + } else { + slowTrack = true; + } + } else if (ble_state == pairing) { + PAIRING_LED_state = !PAIRING_LED_state; + } else { + PAIRING_LED_state = LED_STATE_ON; + } + +#ifdef LED_CHARGE + digitalWrite(LED_CHARGE, CHARGE_LED_state); +#endif + // digitalWrite(green_LED_PIN, LED_STATE_OFF); +#ifdef LED_PAIRING + digitalWrite(LED_PAIRING, PAIRING_LED_state); +#endif + + return (my_interval); +} diff --git a/src/modules/StatusLEDModule.h b/src/modules/StatusLEDModule.h new file mode 100644 index 000000000..d9e3a4f33 --- /dev/null +++ b/src/modules/StatusLEDModule.h @@ -0,0 +1,44 @@ +#pragma once + +#include "BluetoothStatus.h" +#include "MeshModule.h" +#include "PowerStatus.h" +#include "concurrency/OSThread.h" +#include "configuration.h" +#include +#include + +class StatusLEDModule : private concurrency::OSThread +{ + bool slowTrack = false; + + public: + StatusLEDModule(); + + int handleStatusUpdate(const meshtastic::Status *); + + protected: + unsigned int my_interval = 1000; // interval in millisconds + virtual int32_t runOnce() override; + + CallbackObserver bluetoothStatusObserver = + CallbackObserver(this, &StatusLEDModule::handleStatusUpdate); + CallbackObserver powerStatusObserver = + CallbackObserver(this, &StatusLEDModule::handleStatusUpdate); + + private: + bool CHARGE_LED_state = LED_STATE_OFF; + bool PAIRING_LED_state = LED_STATE_OFF; + + uint32_t PAIRING_LED_starttime = 0; + + enum PowerState { discharging, charging, charged }; + + PowerState power_state = discharging; + + enum BLEState { unpaired, pairing, connected }; + + BLEState ble_state = unpaired; +}; + +extern StatusLEDModule *statusLEDModule; diff --git a/src/modules/Telemetry/Sensor/AHT10.cpp b/src/modules/Telemetry/Sensor/AHT10.cpp index 52fdc05c0..c38fd2a92 100644 --- a/src/modules/Telemetry/Sensor/AHT10.cpp +++ b/src/modules/Telemetry/Sensor/AHT10.cpp @@ -35,7 +35,7 @@ bool AHT10Sensor::getMetrics(meshtastic_Telemetry *measurement) // prefer other sensors like bmp280, bmp3xx if (!measurement->variant.environment_metrics.has_temperature) { measurement->variant.environment_metrics.has_temperature = true; - measurement->variant.environment_metrics.temperature = temp.temperature; + measurement->variant.environment_metrics.temperature = temp.temperature + AHT10_TEMP_OFFSET; } if (!measurement->variant.environment_metrics.has_relative_humidity) { diff --git a/src/modules/Telemetry/Sensor/AHT10.h b/src/modules/Telemetry/Sensor/AHT10.h index ab3f5806c..f85f04aa0 100644 --- a/src/modules/Telemetry/Sensor/AHT10.h +++ b/src/modules/Telemetry/Sensor/AHT10.h @@ -6,6 +6,10 @@ #if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && __has_include() +#ifndef AHT10_TEMP_OFFSET +#define AHT10_TEMP_OFFSET 0 +#endif + #include "../mesh/generated/meshtastic/telemetry.pb.h" #include "TelemetrySensor.h" #include diff --git a/src/platform/nrf52/architecture.h b/src/platform/nrf52/architecture.h index 6ddb41b16..75ca7567e 100644 --- a/src/platform/nrf52/architecture.h +++ b/src/platform/nrf52/architecture.h @@ -68,6 +68,8 @@ #define HW_VENDOR meshtastic_HardwareModel_T_ECHO_LITE #elif defined(ELECROW_ThinkNode_M1) #define HW_VENDOR meshtastic_HardwareModel_THINKNODE_M1 +#elif defined(ELECROW_ThinkNode_M3) +#define HW_VENDOR meshtastic_HardwareModel_THINKNODE_M3 #elif defined(ELECROW_ThinkNode_M6) #define HW_VENDOR meshtastic_HardwareModel_THINKNODE_M6 #elif defined(NANO_G2_ULTRA) @@ -130,7 +132,9 @@ #endif +#ifdef PIN_LED1 #define LED_PIN PIN_LED1 // LED1 on nrf52840-DK +#endif #ifdef PIN_BUTTON1 #define BUTTON_PIN PIN_BUTTON1 diff --git a/src/platform/nrf52/main-nrf52.cpp b/src/platform/nrf52/main-nrf52.cpp index 827863f33..c03cc4454 100644 --- a/src/platform/nrf52/main-nrf52.cpp +++ b/src/platform/nrf52/main-nrf52.cpp @@ -30,6 +30,11 @@ #include "BQ25713.h" #endif +// Weak empty variant initialization function. +// May be redefined by variant files. +void variant_shutdown() __attribute__((weak)); +void variant_shutdown() {} + static nrfx_wdt_t nrfx_wdt = NRFX_WDT_INSTANCE(0); static nrfx_wdt_channel_id nrfx_wdt_channel_id_nrf52_main; @@ -391,6 +396,7 @@ void cpuDeepSleep(uint32_t msecToWake) NRF_GPIO->DIRCLR = (1 << pin); } #endif + variant_shutdown(); // Sleepy trackers or sensors can low power "sleep" // Don't enter this if we're sleeping portMAX_DELAY, since that's a shutdown event diff --git a/variants/nrf52840/ELECROW-ThinkNode-M3/platformio.ini b/variants/nrf52840/ELECROW-ThinkNode-M3/platformio.ini new file mode 100644 index 000000000..958e48e48 --- /dev/null +++ b/variants/nrf52840/ELECROW-ThinkNode-M3/platformio.ini @@ -0,0 +1,17 @@ +[env:thinknode_m3] +extends = nrf52840_base +board = ThinkNode-M3 +board_check = true +debug_tool = jlink +build_flags = + ${nrf52840_base.build_flags} + -Ivariants/nrf52840/ELECROW-ThinkNode-M3 + -DELECROW_ThinkNode_M3 + -DGPS_POWER_TOGGLE + -D CONFIG_NFCT_PINS_AS_GPIOS=1 + -L "${platformio.libdeps_dir}/${this.__env__}/bsec2/src/cortex-m4/fpv4-sp-d16-hard" +build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/ELECROW-ThinkNode-M3> +lib_deps = + ${nrf52840_base.lib_deps} + khoih-prog/nRF52_PWM@^1.0.1 + lewisxhe/PCF8563_Library@^1.0.1 diff --git a/variants/nrf52840/ELECROW-ThinkNode-M3/rfswitch.h b/variants/nrf52840/ELECROW-ThinkNode-M3/rfswitch.h new file mode 100644 index 000000000..77ae9ef73 --- /dev/null +++ b/variants/nrf52840/ELECROW-ThinkNode-M3/rfswitch.h @@ -0,0 +1,15 @@ +#include "RadioLib.h" +#include "nrf.h" + +// set RF switch configuration for ELECROW ThinkNode M3 +// ELECROW ThinkNode M3 uses DIO5 and DIO6 for RF switching + +static const uint32_t rfswitch_dio_pins[] = {RADIOLIB_LR11X0_DIO5, RADIOLIB_LR11X0_DIO6, RADIOLIB_NC, RADIOLIB_NC, RADIOLIB_NC}; + +static const Module::RfSwitchMode_t rfswitch_table[] = { + // mode DIO5 DIO6 + {LR11x0::MODE_STBY, {LOW, LOW}}, {LR11x0::MODE_RX, {HIGH, LOW}}, + {LR11x0::MODE_TX, {HIGH, HIGH}}, {LR11x0::MODE_TX_HP, {LOW, HIGH}}, + {LR11x0::MODE_TX_HF, {LOW, LOW}}, {LR11x0::MODE_GNSS, {LOW, LOW}}, + {LR11x0::MODE_WIFI, {LOW, LOW}}, END_OF_MODE_TABLE, +}; diff --git a/variants/nrf52840/ELECROW-ThinkNode-M3/variant.cpp b/variants/nrf52840/ELECROW-ThinkNode-M3/variant.cpp new file mode 100644 index 000000000..b7a7b7342 --- /dev/null +++ b/variants/nrf52840/ELECROW-ThinkNode-M3/variant.cpp @@ -0,0 +1,93 @@ +/* + 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 "meshUtils.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() +{ + pinMode(KEY_POWER, OUTPUT); + digitalWrite(KEY_POWER, HIGH); + pinMode(RGB_POWER, OUTPUT); + digitalWrite(RGB_POWER, HIGH); + pinMode(green_LED_PIN, OUTPUT); + digitalWrite(green_LED_PIN, LED_STATE_OFF); + pinMode(LED_BLUE, OUTPUT); + pinMode(PIN_POWER_USB, INPUT); + pinMode(PIN_POWER_DONE, INPUT); + pinMode(PIN_POWER_CHRG, INPUT); + pinMode(BUTTON_PIN, INPUT_PULLUP); + pinMode(EEPROM_POWER, OUTPUT); + digitalWrite(EEPROM_POWER, HIGH); + pinMode(PIN_EN1, OUTPUT); + digitalWrite(PIN_EN1, HIGH); + pinMode(PIN_EN2, OUTPUT); + digitalWrite(PIN_EN2, HIGH); + pinMode(ACC_POWER, OUTPUT); + digitalWrite(ACC_POWER, LOW); + pinMode(DHT_POWER, OUTPUT); + digitalWrite(DHT_POWER, HIGH); + pinMode(Battery_POWER, OUTPUT); + digitalWrite(Battery_POWER, HIGH); + pinMode(GPS_POWER, OUTPUT); + digitalWrite(GPS_POWER, HIGH); +} + +// called from main-nrf52.cpp during the cpuDeepSleep() function +void variant_shutdown() +{ + digitalWrite(EEPROM_POWER, LOW); + digitalWrite(KEY_POWER, LOW); + + for (int pin = 0; pin < 48; pin++) { + if (pin == PIN_POWER_USB || pin == BUTTON_PIN || pin == PIN_EN1 || pin == PIN_EN2 || pin == DHT_POWER || + pin == ACC_POWER || pin == Battery_POWER || pin == GPS_POWER || pin == LR1110_SPI_MISO_PIN || + pin == LR1110_SPI_MOSI_PIN || pin == LR1110_SPI_SCK_PIN || pin == LR1110_SPI_NSS_PIN || pin == LR1110_BUSY_PIN || + pin == LR1110_NRESET_PIN || pin == LR1110_IRQ_PIN || pin == GPS_TX_PIN || pin == GPS_RX_PIN || pin == green_LED_PIN || + pin == red_LED_PIN || pin == LED_BLUE) { + continue; + } + pinMode(pin, OUTPUT); + digitalWrite(pin, LOW); + if (pin >= 32) { + NRF_P1->DIRCLR = (1 << (pin - 32)); + } else { + NRF_GPIO->DIRCLR = (1 << pin); + } + } + + nrf_gpio_cfg_input(BUTTON_PIN, NRF_GPIO_PIN_PULLUP); // Configure the pin to be woken up as an input + nrf_gpio_pin_sense_t sense1 = NRF_GPIO_PIN_SENSE_LOW; + nrf_gpio_cfg_sense_set(BUTTON_PIN, sense1); + + nrf_gpio_cfg_input(PIN_POWER_USB, NRF_GPIO_PIN_PULLDOWN); // Configure the pin to be woken up as an input + nrf_gpio_pin_sense_t sense2 = NRF_GPIO_PIN_SENSE_HIGH; + nrf_gpio_cfg_sense_set(PIN_POWER_USB, sense2); +} \ No newline at end of file diff --git a/variants/nrf52840/ELECROW-ThinkNode-M3/variant.h b/variants/nrf52840/ELECROW-ThinkNode-M3/variant.h new file mode 100644 index 000000000..cf940172b --- /dev/null +++ b/variants/nrf52840/ELECROW-ThinkNode-M3/variant.h @@ -0,0 +1,122 @@ +/* + 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_ELECROW_EINK_V1_0_ +#define _VARIANT_ELECROW_EINK_V1_0_ + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +#include "WVariant.h" + +#define VARIANT_MCK (64000000ul) +#define USE_LFXO // Board uses 32khz crystal for LF + +#define ELECROW_ThinkNode_M3 1 +// Number of pins defined in PinDescription array +#define PINS_COUNT (48) +#define NUM_DIGITAL_PINS (48) +#define NUM_ANALOG_INPUTS (1) +#define NUM_ANALOG_OUTPUTS (0) + +// Power Pin +#define NRF_APM +#define GPS_POWER 14 +#define PIN_POWER_USB 31 +#define EXT_PWR_DETECT PIN_POWER_USB +#define PIN_POWER_DONE 24 +#define PIN_POWER_CHRG 32 +#define KEY_POWER 16 +#define ACC_POWER 2 +#define DHT_POWER 3 +#define Battery_POWER 17 +#define RGB_POWER 29 +#define EEPROM_POWER 7 + +// LED +#define red_LED_PIN 33 +#define LED_POWER red_LED_PIN +#define LED_CHARGE LED_POWER // Signals the Status LED Module to handle this LED +#define green_LED_PIN 35 +#define LED_BLUE 37 +#define LED_PAIRING LED_BLUE // Signals the Status LED Module to handle this LED + +#define LED_BUILTIN -1 +#define LED_STATE_ON LOW +#define LED_STATE_OFF HIGH + +// BUZZER +#define PIN_BUZZER 23 +#define PIN_EN1 36 +#define PIN_EN2 34 +/*Wire Interfaces*/ +#define WIRE_INTERFACES_COUNT 1 +#define PIN_WIRE_SDA 26 +#define PIN_WIRE_SCL 27 + +// Temperature correction for sensor +#define AHT10_TEMP_OFFSET -5.0 + +/*GPS pins*/ +#define HAS_GPS 1 +#define GPS_BAUDRATE 9600 +#define PIN_GPS_RESET 25 +#define PIN_GPS_STANDBY 21 +#define GPS_TX_PIN 20 +#define GPS_RX_PIN 22 +#define GPS_THREAD_INTERVAL 50 +#define PIN_SERIAL1_RX GPS_TX_PIN +#define PIN_SERIAL1_TX GPS_RX_PIN +// Button +#define BUTTON_PIN 12 +#define BUTTON_PIN_ALT (0 + 12) +// Battery +#define BATTERY_PIN 5 +#define BATTERY_SENSE_RESOLUTION_BITS 12 +#define BATTERY_SENSE_RESOLUTION 4096.0 +#undef AREF_VOLTAGE +#define AREF_VOLTAGE 2.4 +#define VBAT_AR_INTERNAL AR_INTERNAL_2_4 +#define ADC_MULTIPLIER (1.75) +/*SPI Interfaces*/ +#define SPI_INTERFACES_COUNT 1 +#define PIN_SPI_MISO (32 + 15) // P1.15 47 +#define PIN_SPI_MOSI (32 + 14) // P1.14 46 +#define PIN_SPI_SCK (32 + 13) // P1.13 45 +#define PIN_SPI_NSS (32 + 12) // P1.12 44 +/*LORA Interfaces*/ +#define USE_LR1110 +#define LR1110_IRQ_PIN 40 +#define LR1110_NRESET_PIN 42 +#define LR1110_BUSY_PIN 43 +#define LR1110_SPI_NSS_PIN 44 +#define LR1110_SPI_SCK_PIN 45 +#define LR1110_SPI_MOSI_PIN 46 +#define LR1110_SPI_MISO_PIN 47 +#define LR11X0_DIO3_TCXO_VOLTAGE 3.3 +#define LR11X0_DIO_AS_RF_SWITCH + +// PCF8563 RTC Module +#define PCF8563_RTC 0x51 + +#ifdef __cplusplus +} +#endif + +#endif From 0336331411f864ef0ea81c5ada391b56cd06101b Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Tue, 25 Nov 2025 02:29:35 -0600 Subject: [PATCH 662/691] Use LED_CHARGE and LED_PAIRING for M6 led control (#8742) --- variants/nrf52840/ELECROW-ThinkNode-M6/variant.cpp | 8 ++++---- variants/nrf52840/ELECROW-ThinkNode-M6/variant.h | 9 +++++---- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/variants/nrf52840/ELECROW-ThinkNode-M6/variant.cpp b/variants/nrf52840/ELECROW-ThinkNode-M6/variant.cpp index b84079e66..09872d409 100644 --- a/variants/nrf52840/ELECROW-ThinkNode-M6/variant.cpp +++ b/variants/nrf52840/ELECROW-ThinkNode-M6/variant.cpp @@ -32,11 +32,11 @@ const uint32_t g_ADigitalPinMap[] = { void initVariant() { - pinMode(PIN_LED1, OUTPUT); - ledOff(PIN_LED1); + pinMode(LED_CHARGE, OUTPUT); + ledOff(LED_CHARGE); - pinMode(PIN_LED2, OUTPUT); - ledOff(PIN_LED2); + pinMode(LED_PAIRING, OUTPUT); + ledOff(LED_PAIRING); pinMode(VDD_FLASH_EN, OUTPUT); digitalWrite(VDD_FLASH_EN, HIGH); diff --git a/variants/nrf52840/ELECROW-ThinkNode-M6/variant.h b/variants/nrf52840/ELECROW-ThinkNode-M6/variant.h index 98c654df2..5e543b21f 100644 --- a/variants/nrf52840/ELECROW-ThinkNode-M6/variant.h +++ b/variants/nrf52840/ELECROW-ThinkNode-M6/variant.h @@ -40,10 +40,11 @@ extern "C" { #define NUM_ANALOG_OUTPUTS (0) // LEDs -#define PIN_LED1 (12) -#define PIN_LED2 (7) -#define LED_BUILTIN PIN_LED1 -#define LED_BLUE PIN_LED2 +#define LED_BUILTIN -1 +#define LED_BLUE -1 +#define LED_CHARGE (12) +#define LED_PAIRING (7) + #define LED_STATE_ON 1 // USB power detection From 592a8f23db76d75f61c4848665f61ed7df297431 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Tue, 25 Nov 2025 06:10:20 -0600 Subject: [PATCH 663/691] Further fix compass calibration (#8740) * Update calibration logic for ICM20948 sensor Initialize highest and lowest magnetic values based on sensor data readiness during calibration. * Refactor BMX160 calibration to use magnetometer data Update calibration logic to initialize highest and lowest values using magnetometer data. * Add missed viable defines in ::calibrate() --- src/motion/BMX160Sensor.cpp | 7 ++++++- src/motion/ICM20948Sensor.cpp | 12 +++++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/motion/BMX160Sensor.cpp b/src/motion/BMX160Sensor.cpp index 56f794306..5888c20be 100755 --- a/src/motion/BMX160Sensor.cpp +++ b/src/motion/BMX160Sensor.cpp @@ -115,8 +115,13 @@ int32_t BMX160Sensor::runOnce() void BMX160Sensor::calibrate(uint16_t forSeconds) { #if !defined(MESHTASTIC_EXCLUDE_SCREEN) + sBmx160SensorData_t magAccel; + sBmx160SensorData_t gAccel; LOG_DEBUG("BMX160 calibration started for %is", forSeconds); - highestX = 0, lowestX = 0, highestY = 0, lowestY = 0, highestZ = 0, lowestZ = 0; + sensor.getAllData(&magAccel, NULL, &gAccel); + highestX = magAccel.x, lowestX = magAccel.x; + highestY = magAccel.y, lowestY = magAccel.y; + highestZ = magAccel.z, lowestZ = magAccel.z; doCalibration = true; uint16_t calibrateFor = forSeconds * 1000; // calibrate for seconds provided diff --git a/src/motion/ICM20948Sensor.cpp b/src/motion/ICM20948Sensor.cpp index ebb0f7b66..10918eb7d 100755 --- a/src/motion/ICM20948Sensor.cpp +++ b/src/motion/ICM20948Sensor.cpp @@ -157,7 +157,17 @@ void ICM20948Sensor::calibrate(uint16_t forSeconds) { #if !defined(MESHTASTIC_EXCLUDE_SCREEN) && HAS_SCREEN LOG_DEBUG("BMX160 calibration started for %is", forSeconds); - highestX = 0, lowestX = 0, highestY = 0, lowestY = 0, highestZ = 0, lowestZ = 0; + if (sensor->dataReady()) { + sensor->getAGMT(); + highestX = sensor->agmt.mag.axes.x; + lowestX = sensor->agmt.mag.axes.x; + highestY = sensor->agmt.mag.axes.y; + lowestY = sensor->agmt.mag.axes.y; + highestZ = sensor->agmt.mag.axes.z; + lowestZ = sensor->agmt.mag.axes.z; + } else { + highestX = 0, lowestX = 0, highestY = 0, lowestY = 0, highestZ = 0, lowestZ = 0; + } doCalibration = true; uint16_t calibrateFor = forSeconds * 1000; // calibrate for seconds provided From 81439f16d026675398313682bf347338300ded97 Mon Sep 17 00:00:00 2001 From: Jason P Date: Tue, 25 Nov 2025 08:59:11 -0600 Subject: [PATCH 664/691] More quickly hide "Shutting Down" to prevent it showing on Eink sleep screen (#8749) --- src/Power.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Power.cpp b/src/Power.cpp index fa8661d01..75fd32202 100644 --- a/src/Power.cpp +++ b/src/Power.cpp @@ -759,6 +759,8 @@ void Power::shutdown() if (screen) { #ifdef T_DECK_PRO screen->showSimpleBanner("Device is powered off.\nConnect USB to start!", 0); // T-Deck Pro has no power button +#elif USE_EINK + screen->showSimpleBanner("Shutting Down...", 2250); // dismiss after 3 seconds to avoid the banner on the sleep screen #else screen->showSimpleBanner("Shutting Down...", 0); // stays on screen #endif From faa6af74afbbdf5cf418fc3a7d64f68a9d3be0aa Mon Sep 17 00:00:00 2001 From: Jason P Date: Tue, 25 Nov 2025 13:55:28 -0600 Subject: [PATCH 665/691] Swapping GPS pins for GPS TX/RX (#8751) --- variants/nrf52840/heltec_mesh_node_t114/variant.h | 4 ++-- variants/nrf52840/t-echo/variant.h | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/variants/nrf52840/heltec_mesh_node_t114/variant.h b/variants/nrf52840/heltec_mesh_node_t114/variant.h index b6082fdc6..de89d2d07 100644 --- a/variants/nrf52840/heltec_mesh_node_t114/variant.h +++ b/variants/nrf52840/heltec_mesh_node_t114/variant.h @@ -167,8 +167,8 @@ No longer populated on PCB #define PIN_GPS_PPS (32 + 4) // Seems to be missing on this new board // #define PIN_GPS_PPS (32 + 4) // Pulse per second input from the GPS -#define GPS_TX_PIN (32 + 5) // This is for bits going TOWARDS the CPU -#define GPS_RX_PIN (32 + 7) // This is for bits going TOWARDS the GPS +#define GPS_TX_PIN (32 + 7) // This is for bits going TOWARDS the CPU +#define GPS_RX_PIN (32 + 5) // This is for bits going TOWARDS the GPS #define GPS_THREAD_INTERVAL 50 diff --git a/variants/nrf52840/t-echo/variant.h b/variants/nrf52840/t-echo/variant.h index 4f3a53ebf..8ddb1c263 100644 --- a/variants/nrf52840/t-echo/variant.h +++ b/variants/nrf52840/t-echo/variant.h @@ -182,8 +182,8 @@ External serial flash WP25R1635FZUIL0 #define PIN_GPS_STANDBY (32 + 2) // An output to wake GPS, low means allow sleep, high means force wake // Seems to be missing on this new board // #define PIN_GPS_PPS (32 + 4) // Pulse per second input from the GPS -#define GPS_TX_PIN (32 + 9) // This is for bits going TOWARDS the CPU -#define GPS_RX_PIN (32 + 8) // This is for bits going TOWARDS the GPS +#define GPS_TX_PIN (32 + 8) // This is for bits going TOWARDS the CPU +#define GPS_RX_PIN (32 + 9) // This is for bits going TOWARDS the GPS #define GPS_THREAD_INTERVAL 50 From bacff5c1f0a7f966dc1ff1762eb52bb52935b178 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Tue, 25 Nov 2025 05:38:00 -0600 Subject: [PATCH 666/691] Reduce noise --- src/modules/ExternalNotificationModule.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/modules/ExternalNotificationModule.cpp b/src/modules/ExternalNotificationModule.cpp index 4fe49cc1b..91e96b8d4 100644 --- a/src/modules/ExternalNotificationModule.cpp +++ b/src/modules/ExternalNotificationModule.cpp @@ -314,11 +314,10 @@ void ExternalNotificationModule::stopNow() audioThread->stop(); #endif // Turn off all outputs - LOG_INFO("Turning off setExternalStates: "); + LOG_INFO("Turning off setExternalStates"); for (int i = 0; i < 3; i++) { setExternalState(i, false); externalTurnedOn[i] = 0; - LOG_INFO("%d ", i); } setIntervalFromNow(0); #ifdef T_WATCH_S3 From 66193e17766b4b4a5e3d7c7a67aa7360f4dfc306 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Tue, 25 Nov 2025 14:34:55 -0600 Subject: [PATCH 667/691] Prevent double-registering of Rotary Encoder on TLora Pager (#8746) * Reduce noise * Prevent double registering of rotary encoder broker --- src/modules/Modules.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/modules/Modules.cpp b/src/modules/Modules.cpp index 9e4401e05..827524fc3 100644 --- a/src/modules/Modules.cpp +++ b/src/modules/Modules.cpp @@ -181,12 +181,13 @@ void setupModules() // new ReplyModule(); #if (HAS_BUTTON || ARCH_PORTDUINO) && !MESHTASTIC_EXCLUDE_INPUTBROKER if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) { +#ifndef T_LORA_PAGER rotaryEncoderInterruptImpl1 = new RotaryEncoderInterruptImpl1(); if (!rotaryEncoderInterruptImpl1->init()) { delete rotaryEncoderInterruptImpl1; rotaryEncoderInterruptImpl1 = nullptr; } -#ifdef T_LORA_PAGER +#elif defined(T_LORA_PAGER) // use a special FSM based rotary encoder version for T-LoRa Pager rotaryEncoderImpl = new RotaryEncoderImpl(); if (!rotaryEncoderImpl->init()) { From 486fa74549bacd44a24cffa140d160da56977920 Mon Sep 17 00:00:00 2001 From: Tom <116762865+NomDeTom@users.noreply.github.com> Date: Tue, 25 Nov 2025 21:18:55 +0000 Subject: [PATCH 668/691] Actions: Remove native from build_one (#8685) * Remove native from the build, and remove the required permissions * Delete .github/workflows/build_one_arch.yml Its borken and not really needed. one_target is the goal. --- .github/workflows/build_one_arch.yml | 176 ------------------------- .github/workflows/build_one_target.yml | 23 +--- 2 files changed, 1 insertion(+), 198 deletions(-) delete mode 100644 .github/workflows/build_one_arch.yml diff --git a/.github/workflows/build_one_arch.yml b/.github/workflows/build_one_arch.yml deleted file mode 100644 index 5673f8cb6..000000000 --- a/.github/workflows/build_one_arch.yml +++ /dev/null @@ -1,176 +0,0 @@ -name: Build One Arch - -on: - workflow_dispatch: - inputs: - # trunk-ignore(checkov/CKV_GHA_7) - arch: - type: choice - options: - - esp32 - - esp32s3 - - esp32c3 - - esp32c6 - - nrf52840 - - rp2040 - - rp2350 - - stm32 - - native - -permissions: read-all - -env: - INPUT_ARCH: ${{ github.event.inputs.arch }} - -jobs: - setup: - runs-on: ubuntu-24.04 - steps: - - uses: actions/checkout@v6 - - uses: actions/setup-python@v6 - with: - python-version: 3.x - cache: pip - - run: pip install -U platformio - - name: Generate matrix - id: jsonStep - run: | - TARGETS=$(./bin/generate_ci_matrix.py $INPUT_ARCH --level extra) - echo "Name: $GITHUB_REF_NAME Base: $GITHUB_BASE_REF Ref: $GITHUB_REF" - echo "selected_arch=$TARGETS" >> $GITHUB_OUTPUT - outputs: - selected_arch: ${{ steps.jsonStep.outputs.selected_arch }} - - version: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - - name: Get release version string - run: | - echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT - echo "deb=$(./bin/buildinfo.py deb)" >> $GITHUB_OUTPUT - id: version - env: - BUILD_LOCATION: local - outputs: - long: ${{ steps.version.outputs.long }} - deb: ${{ steps.version.outputs.deb }} - - build: - if: ${{ github.event_name != 'workflow_dispatch' }} - needs: [setup, version] - strategy: - fail-fast: false - matrix: - build: ${{ fromJson(needs.setup.outputs.selected_arch) }} - uses: ./.github/workflows/build_firmware.yml - with: - version: ${{ needs.version.outputs.long }} - pio_env: ${{ matrix.build.board }} - platform: ${{ matrix.build.arch }} - - build-debian-src: - if: ${{ github.repository == 'meshtastic/firmware' && github.event_name != 'workflow_dispatch' || inputs.arch == 'native' }} - uses: ./.github/workflows/build_debian_src.yml - with: - series: UNRELEASED - build_location: local - secrets: inherit - - package-pio-deps-native-tft: - if: ${{ inputs.arch == 'native' }} - uses: ./.github/workflows/package_pio_deps.yml - with: - pio_env: native-tft - secrets: inherit - - test-native: - if: ${{ !contains(github.ref_name, 'event/') && github.event_name != 'workflow_dispatch' || !contains(github.ref_name, 'event/') && inputs.arch == 'native' }} - uses: ./.github/workflows/test_native.yml - - gather-artifacts: - permissions: - contents: write - pull-requests: write - strategy: - fail-fast: false - matrix: - arch: - - esp32 - - esp32s3 - - esp32c3 - - esp32c6 - - nrf52840 - - rp2040 - - rp2350 - - stm32 - runs-on: ubuntu-latest - needs: [version, build] - steps: - - name: Checkout code - uses: actions/checkout@v6 - with: - ref: ${{github.event.pull_request.head.ref}} - repository: ${{github.event.pull_request.head.repo.full_name}} - - - uses: actions/download-artifact@v6 - with: - path: ./ - pattern: firmware-${{inputs.arch}}-* - merge-multiple: true - - - name: Display structure of downloaded files - run: ls -R - - - name: Move files up - run: mv -b -t ./ ./bin/device-*.sh ./bin/device-*.bat - - - name: Repackage in single firmware zip - uses: actions/upload-artifact@v5 - with: - name: firmware-${{inputs.arch}}-${{ needs.version.outputs.long }} - overwrite: true - path: | - ./firmware-*.bin - ./firmware-*.uf2 - ./firmware-*.hex - ./firmware-*-ota.zip - ./device-*.sh - ./device-*.bat - ./littlefs-*.bin - ./bleota*bin - ./Meshtastic_nRF52_factory_erase*.uf2 - retention-days: 30 - - - uses: actions/download-artifact@v6 - with: - name: firmware-${{inputs.arch}}-${{ needs.version.outputs.long }} - merge-multiple: true - path: ./output - - # For diagnostics - - name: Show artifacts - run: ls -lR - - - name: Device scripts permissions - run: | - chmod +x ./output/device-install.sh - chmod +x ./output/device-update.sh - - - name: Zip firmware - run: zip -j -9 -r ./firmware-${{inputs.arch}}-${{ needs.version.outputs.long }}.zip ./output - - - name: Repackage in single elfs zip - uses: actions/upload-artifact@v5 - with: - name: debug-elfs-${{inputs.arch}}-${{ needs.version.outputs.long }}.zip - overwrite: true - path: ./*.elf - retention-days: 30 - - - uses: scruplelesswizard/comment-artifact@main - if: ${{ github.event_name == 'pull_request' }} - with: - name: firmware-${{inputs.arch}}-${{ needs.version.outputs.long }} - description: "Download firmware-${{inputs.arch}}-${{ needs.version.outputs.long }}.zip. This artifact will be available for 90 days from creation" - github-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/build_one_target.yml b/.github/workflows/build_one_target.yml index 343e5be64..e4b332a06 100644 --- a/.github/workflows/build_one_target.yml +++ b/.github/workflows/build_one_target.yml @@ -15,7 +15,6 @@ on: - rp2040 - rp2350 - stm32 - - native target: type: string required: false @@ -42,7 +41,6 @@ jobs: - rp2040 - rp2350 - stm32 - runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v6 @@ -60,7 +58,7 @@ jobs: echo "Arch: ${{matrix.arch}}" >> $GITHUB_STEP_SUMMARY echo "Ref: $GITHUB_REF" >> $GITHUB_STEP_SUMMARY echo "Targets:" >> $GITHUB_STEP_SUMMARY - echo $TARGETS >> $GITHUB_STEP_SUMMARY + echo $TARGETS | jq -r 'sort_by(.board) |.[] | "- " + .board' >> $GITHUB_STEP_SUMMARY version: if: ${{ inputs.target != '' }} @@ -87,25 +85,6 @@ jobs: pio_env: ${{ inputs.target }} platform: ${{ inputs.arch }} - build-debian-src: - if: ${{ github.repository == 'meshtastic/firmware' && inputs.arch == 'native' }} - uses: ./.github/workflows/build_debian_src.yml - with: - series: UNRELEASED - build_location: local - secrets: inherit - - package-pio-deps-native-tft: - if: ${{ inputs.arch == 'native' }} - uses: ./.github/workflows/package_pio_deps.yml - with: - pio_env: native-tft - secrets: inherit - - test-native: - if: ${{ !contains(github.ref_name, 'event/') && github.event_name != 'workflow_dispatch' || !contains(github.ref_name, 'event/') && inputs.arch == 'native' && inputs.target != '' }} - uses: ./.github/workflows/test_native.yml - gather-artifacts: permissions: contents: write From 79e8fc94bce22c125a843c778b0e341aabd1f59e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Tue, 25 Nov 2025 23:35:17 +0100 Subject: [PATCH 669/691] 3401 fix (#8755) * Preliminary Thinknode M4 Support * fix 3401 detection * don't push unrelated work --- src/platform/nrf52/architecture.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/platform/nrf52/architecture.h b/src/platform/nrf52/architecture.h index 75ca7567e..dc3930d64 100644 --- a/src/platform/nrf52/architecture.h +++ b/src/platform/nrf52/architecture.h @@ -57,11 +57,11 @@ #define HW_VENDOR meshtastic_HardwareModel_NOMADSTAR_METEOR_PRO #elif defined(R1_NEO) #define HW_VENDOR meshtastic_HardwareModel_MUZI_R1_NEO +#elif defined(RAK3401) +#define HW_VENDOR meshtastic_HardwareModel_RAK3401 // MAke sure all custom RAK4630 boards are defined before the generic RAK4630 #elif defined(RAK4630) #define HW_VENDOR meshtastic_HardwareModel_RAK4631 -#elif defined(RAK3401) -#define HW_VENDOR meshtastic_HardwareModel_RAK3401 #elif defined(TTGO_T_ECHO) #define HW_VENDOR meshtastic_HardwareModel_T_ECHO #elif defined(T_ECHO_LITE) From 654abe5b2cb6faa960181227223a2effd4d68fbd Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Tue, 25 Nov 2025 18:28:06 -0600 Subject: [PATCH 670/691] Add support for muzi-base (#8753) --- boards/muzi-base.json | 56 ++++++ src/Power.cpp | 20 ++- src/detect/ScanI2CTwoWire.cpp | 5 + src/gps/RTC.cpp | 8 + src/graphics/Screen.cpp | 17 +- src/graphics/Screen.h | 2 + src/mesh/NodeDB.cpp | 3 + src/modules/SerialModule.cpp | 9 +- src/motion/ICM20948Sensor.cpp | 17 ++ src/motion/ICM20948Sensor.h | 6 + src/platform/nrf52/architecture.h | 2 + variants/nrf52840/muzi_base/platformio.ini | 15 ++ variants/nrf52840/muzi_base/rfswitch.h | 11 ++ variants/nrf52840/muzi_base/variant.cpp | 83 +++++++++ variants/nrf52840/muzi_base/variant.h | 192 +++++++++++++++++++++ 15 files changed, 440 insertions(+), 6 deletions(-) create mode 100644 boards/muzi-base.json create mode 100644 variants/nrf52840/muzi_base/platformio.ini create mode 100644 variants/nrf52840/muzi_base/rfswitch.h create mode 100644 variants/nrf52840/muzi_base/variant.cpp create mode 100644 variants/nrf52840/muzi_base/variant.h diff --git a/boards/muzi-base.json b/boards/muzi-base.json new file mode 100644 index 000000000..5f65c0dc8 --- /dev/null +++ b/boards/muzi-base.json @@ -0,0 +1,56 @@ +{ + "build": { + "arduino": { + "ldscript": "nrf52840_s140_v6.ld" + }, + "core": "nRF5", + "cpu": "cortex-m4", + "extra_flags": "-DARDUINO_NRF52840_MUZI_BASE -DNRF52840_XXAA", + "f_cpu": "64000000L", + "hwids": [["0x239A", "0xcafe"]], + "mcu": "nrf52840", + "variant": "muzi-base", + "variants_dir": "variants", + "bsp": { + "name": "adafruit" + }, + "softdevice": { + "sd_flags": "-DS140", + "sd_name": "s140", + "sd_version": "6.1.1", + "sd_fwid": "0x00B6" + }, + "bootloader": { + "settings_addr": "0xFF000" + } + }, + "connectivity": ["bluetooth"], + "debug": { + "jlink_device": "nRF52840_xxAA", + "onboard_tools": ["jlink"], + "svd_path": "nrf52840.svd", + "openocd_target": "nrf52840-mdk-rs" + }, + "frameworks": ["arduino"], + "name": "Muzi Base", + "url": "https://muzi.works/", + "vendor": "MuziWorks", + "upload": { + "maximum_ram_size": 248832, + "maximum_size": 815104, + "speed": 115200, + "protocol": "nrfutil", + "protocols": [ + "jlink", + "nrfjprog", + "nrfutil", + "blackmagic", + "cmsis-dap", + "mbed", + "stlink" + ], + "use_1200bps_touch": true, + "require_upload_port": true, + "wait_for_upload_port": true + } +} diff --git a/src/Power.cpp b/src/Power.cpp index 75fd32202..0ac89670f 100644 --- a/src/Power.cpp +++ b/src/Power.cpp @@ -278,6 +278,11 @@ class AnalogBatteryLevel : public HasBatteryLevel break; } } +#if defined(BATTERY_CHARGING_INV) + // bit of trickery to show 99% up until the charge finishes + if (!digitalRead(BATTERY_CHARGING_INV) && battery_SOC > 99) + battery_SOC = 99; +#endif return clamp((int)(battery_SOC), 0, 100); } @@ -455,6 +460,8 @@ class AnalogBatteryLevel : public HasBatteryLevel } // if it's not HIGH - check the battery #endif +#elif defined(MUZI_BASE) + return NRF_POWER->USBREGSTATUS & POWER_USBREGSTATUS_VBUSDETECT_Msk; #endif return getBattVoltage() > chargingVolt; } @@ -470,6 +477,8 @@ class AnalogBatteryLevel : public HasBatteryLevel #endif #ifdef EXT_CHRG_DETECT return digitalRead(EXT_CHRG_DETECT) == ext_chrg_detect_value; +#elif defined(BATTERY_CHARGING_INV) + return !digitalRead(BATTERY_CHARGING_INV); #else #if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && !defined(DISABLE_INA_CHARGING_DETECTION) if (hasINA()) { @@ -702,7 +711,16 @@ bool Power::setup() }, CHANGE); #endif - +#ifdef BATTERY_CHARGING_INV + attachInterrupt( + BATTERY_CHARGING_INV, + []() { + power->setIntervalFromNow(0); + runASAP = true; + BaseType_t higherWake = 0; + }, + CHANGE); +#endif enabled = found; low_voltage_counter = 0; diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp index 167728ad3..bcf49286e 100644 --- a/src/detect/ScanI2CTwoWire.cpp +++ b/src/detect/ScanI2CTwoWire.cpp @@ -547,6 +547,11 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize) case ICM20948_ADDR: // same as BMX160_ADDR case ICM20948_ADDR_ALT: // same as MPU6050_ADDR registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x00), 1); +#ifdef HAS_ICM20948 + type = ICM20948; + logFoundDevice("ICM20948", (uint8_t)addr.address); + break; +#endif if (registerValue == 0xEA) { type = ICM20948; logFoundDevice("ICM20948", (uint8_t)addr.address); diff --git a/src/gps/RTC.cpp b/src/gps/RTC.cpp index 692f3c2d2..1122f0a51 100644 --- a/src/gps/RTC.cpp +++ b/src/gps/RTC.cpp @@ -112,7 +112,11 @@ RTCSetResult readFromRTC() #elif defined(RX8130CE_RTC) if (rtc_found.address == RX8130CE_RTC) { uint32_t now = millis(); +#ifdef MUZI_BASE + ArtronShop_RX8130CE rtc(&Wire1); +#else ArtronShop_RX8130CE rtc(&Wire); +#endif tm t; if (rtc.getTime(&t)) { tv.tv_sec = gm_mktime(&t); @@ -245,7 +249,11 @@ RTCSetResult perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpd } #elif defined(RX8130CE_RTC) if (rtc_found.address == RX8130CE_RTC) { +#ifdef MUZI_BASE + ArtronShop_RX8130CE rtc(&Wire1); +#else ArtronShop_RX8130CE rtc(&Wire); +#endif tm *t = gmtime(&tv->tv_sec); if (rtc.setTime(*t)) { LOG_DEBUG("RX8130CE setDateTime %02d-%02d-%02d %02d:%02d:%02d (%ld)", t->tm_year + 1900, t->tm_mon + 1, diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index 86599d5b3..dc9806156 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -435,6 +435,14 @@ void Screen::handleSetOn(bool on, FrameCallback einkScreensaver) PMU->enablePowerOutput(XPOWERS_ALDO2); #endif +#if defined(MUZI_BASE) + dispdev->init(); + dispdev->setBrightness(brightness); + dispdev->flipScreenVertically(); + dispdev->resetDisplay(); + digitalWrite(SCREEN_12V_ENABLE, HIGH); + delay(100); +#endif #if !ARCH_PORTDUINO dispdev->displayOn(); #endif @@ -484,6 +492,10 @@ void Screen::handleSetOn(bool on, FrameCallback einkScreensaver) #endif dispdev->displayOff(); + +#ifdef SCREEN_12V_ENABLE + digitalWrite(SCREEN_12V_ENABLE, LOW); +#endif #ifdef USE_ST7789 SPI1.end(); #if defined(ARCH_ESP32) @@ -534,7 +546,7 @@ void Screen::setup() static_cast(dispdev)->setDetected(model); #endif -#ifdef USE_SH1107_128_64 +#if defined(USE_SH1107_128_64) || defined(USE_SH1107) static_cast(dispdev)->setSubtype(7); #endif @@ -542,6 +554,9 @@ void Screen::setup() // Apply custom RGB color (e.g. Heltec T114/T190) static_cast(dispdev)->setRGB(TFT_MESH); #endif +#if defined(MUZI_BASE) + dispdev->delayPoweron = true; +#endif // === Initialize display and UI system === ui->init(); diff --git a/src/graphics/Screen.h b/src/graphics/Screen.h index 74b8d7c5d..375bc2805 100644 --- a/src/graphics/Screen.h +++ b/src/graphics/Screen.h @@ -249,6 +249,8 @@ class Screen : public concurrency::OSThread bool isOverlayBannerShowing(); + bool isScreenOn() { return screenOn; } + // Stores the last 4 of our hardware ID, to make finding the device for pairing easier // FIXME: Needs refactoring and getMacAddr needs to be moved to a utility class char ourId[5]; diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index bb3fc6dca..ff76baaa1 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -734,6 +734,9 @@ void NodeDB::installDefaultConfig(bool preserveKey = false) config.display.screen_on_secs = 30; config.display.wake_on_tap_or_motion = true; #endif +#ifdef COMPASS_ORIENTATION + config.display.compass_orientation = COMPASS_ORIENTATION; +#endif #if defined(ARCH_ESP32) && !MESHTASTIC_EXCLUDE_WIFI if (WiFiOTA::isUpdated()) { WiFiOTA::recoverConfig(&config.network); diff --git a/src/modules/SerialModule.cpp b/src/modules/SerialModule.cpp index d04daf594..719e342b1 100644 --- a/src/modules/SerialModule.cpp +++ b/src/modules/SerialModule.cpp @@ -64,7 +64,8 @@ SerialModule *serialModule; SerialModuleRadio *serialModuleRadio; #if defined(TTGO_T_ECHO) || defined(CANARYONE) || defined(MESHLINK) || defined(ELECROW_ThinkNode_M1) || \ - defined(ELECROW_ThinkNode_M5) || defined(HELTEC_MESH_SOLAR) || defined(T_ECHO_LITE) || defined(ELECROW_ThinkNode_M3) + defined(ELECROW_ThinkNode_M5) || defined(HELTEC_MESH_SOLAR) || defined(T_ECHO_LITE) || defined(ELECROW_ThinkNode_M3) || \ + defined(MUZI_BASE) SerialModule::SerialModule() : StreamAPI(&Serial), concurrency::OSThread("Serial") { api_type = TYPE_SERIAL; @@ -204,7 +205,7 @@ int32_t SerialModule::runOnce() Serial.setTimeout(moduleConfig.serial.timeout > 0 ? moduleConfig.serial.timeout : TIMEOUT); } #elif !defined(TTGO_T_ECHO) && !defined(T_ECHO_LITE) && !defined(CANARYONE) && !defined(MESHLINK) && \ - !defined(ELECROW_ThinkNode_M1) && !defined(ELECROW_ThinkNode_M3) && !defined(ELECROW_ThinkNode_M5) + !defined(ELECROW_ThinkNode_M1) && !defined(ELECROW_ThinkNode_M3) && !defined(ELECROW_ThinkNode_M5) && !defined(MUZI_BASE) if (moduleConfig.serial.rxd && moduleConfig.serial.txd) { #ifdef ARCH_RP2040 Serial2.setFIFOSize(RX_BUFFER); @@ -261,7 +262,7 @@ int32_t SerialModule::runOnce() } #if !defined(TTGO_T_ECHO) && !defined(T_ECHO_LITE) && !defined(CANARYONE) && !defined(MESHLINK) && \ - !defined(ELECROW_ThinkNode_M1) && !defined(ELECROW_ThinkNode_M3) && !defined(ELECROW_ThinkNode_M5) + !defined(ELECROW_ThinkNode_M1) && !defined(ELECROW_ThinkNode_M3) && !defined(ELECROW_ThinkNode_M5) && !defined(MUZI_BASE) else if ((moduleConfig.serial.mode == meshtastic_ModuleConfig_SerialConfig_Serial_Mode_WS85)) { processWXSerial(); @@ -537,7 +538,7 @@ 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_M3) && !defined(ELECROW_ThinkNode_M5) && \ - !defined(ARCH_STM32WL) + !defined(ARCH_STM32WL) && !defined(MUZI_BASE) static unsigned int lastAveraged = 0; static unsigned int averageIntervalMillis = 300000; // 5 minutes hard coded. static double dir_sum_sin = 0; diff --git a/src/motion/ICM20948Sensor.cpp b/src/motion/ICM20948Sensor.cpp index 10918eb7d..9455eafe0 100755 --- a/src/motion/ICM20948Sensor.cpp +++ b/src/motion/ICM20948Sensor.cpp @@ -47,6 +47,21 @@ int32_t ICM20948Sensor::runOnce() int32_t ICM20948Sensor::runOnce() { #if !defined(MESHTASTIC_EXCLUDE_SCREEN) && HAS_SCREEN +#if defined(MUZI_BASE) // temporarily gated to single device due to feature freeze + if (screen && !screen->isScreenOn() && !config.display.wake_on_tap_or_motion && !config.device.double_tap_as_button_press) { + if (!isAsleep) { + LOG_DEBUG("sleeping IMU"); + sensor->sleep(true); + isAsleep = true; + } + return MOTION_SENSOR_CHECK_INTERVAL_MS; + } + if (isAsleep) { + sensor->sleep(false); + isAsleep = false; + } +#endif + float magX = 0, magY = 0, magZ = 0; if (sensor->dataReady()) { sensor->getAGMT(); @@ -156,6 +171,8 @@ int32_t ICM20948Sensor::runOnce() void ICM20948Sensor::calibrate(uint16_t forSeconds) { #if !defined(MESHTASTIC_EXCLUDE_SCREEN) && HAS_SCREEN + LOG_DEBUG("Old calibration data: highestX = %f, lowestX = %f, highestY = %f, lowestY = %f, highestZ = %f, lowestZ = %f", + highestX, lowestX, highestY, lowestY, highestZ, lowestZ); LOG_DEBUG("BMX160 calibration started for %is", forSeconds); if (sensor->dataReady()) { sensor->getAGMT(); diff --git a/src/motion/ICM20948Sensor.h b/src/motion/ICM20948Sensor.h index 27ce4f451..a9b7b69d0 100755 --- a/src/motion/ICM20948Sensor.h +++ b/src/motion/ICM20948Sensor.h @@ -82,7 +82,13 @@ class ICM20948Sensor : public MotionSensor private: ICM20948Singleton *sensor = nullptr; bool showingScreen = false; +#ifdef MUZI_BASE + bool isAsleep = false; + float highestX = 449.000000, lowestX = -140.000000, highestY = 422.000000, lowestY = -232.000000, highestZ = 749.000000, + lowestZ = 98.000000; +#else float highestX = 0, lowestX = 0, highestY = 0, lowestY = 0, highestZ = 0, lowestZ = 0; +#endif public: explicit ICM20948Sensor(ScanI2C::FoundDevice foundDevice); diff --git a/src/platform/nrf52/architecture.h b/src/platform/nrf52/architecture.h index dc3930d64..1568e1790 100644 --- a/src/platform/nrf52/architecture.h +++ b/src/platform/nrf52/architecture.h @@ -108,6 +108,8 @@ #define HW_VENDOR meshtastic_HardwareModel_SEEED_WIO_TRACKER_L1 #elif defined(HELTEC_MESH_SOLAR) #define HW_VENDOR meshtastic_HardwareModel_HELTEC_MESH_SOLAR +#elif defined(MUZI_BASE) +#define HW_VENDOR meshtastic_HardwareModel_RESERVED_FRIED_CHICKEN #else #define HW_VENDOR meshtastic_HardwareModel_NRF52_UNKNOWN #endif diff --git a/variants/nrf52840/muzi_base/platformio.ini b/variants/nrf52840/muzi_base/platformio.ini new file mode 100644 index 000000000..49393f4e0 --- /dev/null +++ b/variants/nrf52840/muzi_base/platformio.ini @@ -0,0 +1,15 @@ +[env:muzi-base] +extends = nrf52840_base +board = muzi-base +build_flags = ${nrf52840_base.build_flags} + -I variants/nrf52840/muzi_base + -D MUZI_BASE + -D CONFIG_NFCT_PINS_AS_GPIOS=1 + -L "${platformio.libdeps_dir}/${this.__env__}/bsec2/src/cortex-m4/fpv4-sp-d16-hard" + +build_src_filter = ${nrf52840_base.build_src_filter} +<../variants/nrf52840/muzi_base> +lib_deps = + ${nrf52840_base.lib_deps} + artronshop/ArtronShop_RX8130CE@1.0.0 + + diff --git a/variants/nrf52840/muzi_base/rfswitch.h b/variants/nrf52840/muzi_base/rfswitch.h new file mode 100644 index 000000000..589f24767 --- /dev/null +++ b/variants/nrf52840/muzi_base/rfswitch.h @@ -0,0 +1,11 @@ +#include "RadioLib.h" + +static const uint32_t rfswitch_dio_pins[] = {RADIOLIB_LR11X0_DIO5, RADIOLIB_LR11X0_DIO6, RADIOLIB_NC, RADIOLIB_NC, RADIOLIB_NC}; + +static const Module::RfSwitchMode_t rfswitch_table[] = { + // mode DIO5 DIO6 + {LR11x0::MODE_STBY, {LOW, LOW}}, {LR11x0::MODE_RX, {HIGH, LOW}}, + {LR11x0::MODE_TX, {LOW, HIGH}}, {LR11x0::MODE_TX_HP, {LOW, HIGH}}, + {LR11x0::MODE_TX_HF, {LOW, LOW}}, {LR11x0::MODE_GNSS, {LOW, LOW}}, + {LR11x0::MODE_WIFI, {LOW, LOW}}, END_OF_MODE_TABLE, +}; diff --git a/variants/nrf52840/muzi_base/variant.cpp b/variants/nrf52840/muzi_base/variant.cpp new file mode 100644 index 000000000..da01de974 --- /dev/null +++ b/variants/nrf52840/muzi_base/variant.cpp @@ -0,0 +1,83 @@ +#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() +{ + // Initialize the digital pins as inputs or outputs + pinMode(PIN_LED1, OUTPUT); + digitalWrite(PIN_LED1, HIGH); + + pinMode(PIN_LED2, OUTPUT); + digitalWrite(PIN_LED2, HIGH); + + // Initialize LoRa pins + pinMode(SX126X_RESET, OUTPUT); + digitalWrite(SX126X_RESET, HIGH); + + pinMode(SX126X_CS, OUTPUT); + digitalWrite(SX126X_CS, HIGH); + + pinMode(GPS_EN_GPIO, OUTPUT); + digitalWrite(GPS_EN_GPIO, HIGH); // GPS on initially + + pinMode(SCREEN_12V_ENABLE, OUTPUT); + digitalWrite(SCREEN_12V_ENABLE, LOW); // + + pinMode(BATTERY_CHARGING_INV, INPUT); +} diff --git a/variants/nrf52840/muzi_base/variant.h b/variants/nrf52840/muzi_base/variant.h new file mode 100644 index 000000000..d3e315f8b --- /dev/null +++ b/variants/nrf52840/muzi_base/variant.h @@ -0,0 +1,192 @@ +#pragma once + +#ifndef _VARIANT_MUZI_BASE_ +#define _VARIANT_MUZI_BASE_ + +/** Master clock frequency */ +#define VARIANT_MCK (64000000ul) + +#define USE_LFXO // Board uses 32khz crystal for LF + +/*---------------------------------------------------------------------------- + * Headers + *----------------------------------------------------------------------------*/ + +#include "WVariant.h" + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +// 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) + +// Define I2C Peripherals +#define WIRE_INTERFACES_COUNT 2 + +// this is the OLED bus +#define PIN_WIRE_SDA (0 + 24) // P0.24 +#define PIN_WIRE_SCL (0 + 25) // P0.25 + +// IMU bus +#define PIN_WIRE1_SDA (0 + 04) // P0.04 +#define PIN_WIRE1_SCL (0 + 06) // P0.06 + +#define COMPASS_ORIENTATION meshtastic_Config_DisplayConfig_CompassOrientation_DEGREES_270 +#define HAS_ICM20948 // forces the i2c address to be seen as this sensor + +#define HAS_RTC 1 +#define RX8130CE_RTC 0x32 + +// LEDs +#define PIN_LED1 (32 + 3) // P1.03, Green +#define PIN_LED2 (32 + 4) // P1.04, Blue + +#define LED_BUILTIN -1 // PIN_LED1 +#define LED_BLUE PIN_LED2 +#define LED_STATE_ON 0 // State when LED is lit + +// Buttons +#define HAS_TRACKBALL 1 +#define TB_UP (0 + 21) +#define TB_DOWN (0 + 17) +#define TB_LEFT (32 + 05) +#define TB_RIGHT (0 + 16) +#define TB_PRESS (0 + 10) +#define TB_DIRECTION FALLING + +#define CANCEL_BUTTON_PIN (0 + 15) // P0.15 +#define CANCEL_BUTTON_ACTIVE_LOW true +#define CANCEL_BUTTON_ACTIVE_PULLUP false + +// Switch +#define SWITCH_MODE1 (32 + 9) // P1.09, Top Position +#define SWITCH_MODE2 (0 + 12) // P0.12, Middle Position +#define PIN_GPS_SWITCH SWITCH_MODE2 + +/* + * SPI Interfaces + */ + +#define SPI_INTERFACES_COUNT 1 + +// For LORA, spi 0 +#define PIN_SPI_MISO (32 + 15) // P1.15 +#define PIN_SPI_MOSI (32 + 14) // P1.14 +#define PIN_SPI_SCK (32 + 13) // P1.13 + +#define LORA_SCK PIN_SPI_SCK +#define LORA_MISO PIN_SPI_MISO +#define LORA_MOSI PIN_SPI_MOSI +#define LORA_CS (32 + 12) // P1.12 + +#define USE_SX1262 +#define SX126X_CS LORA_CS +#define SX126X_DIO1 (32 + 6) // P1.06 +#define SX126X_BUSY (32 + 11) // P1.11 +#define SX126X_RESET (32 + 10) // P1.10 +#define SX126X_DIO2_AS_RF_SWITCH +#define SX126X_DIO3_TCXO_VOLTAGE 3.3 + +#define USE_LR1121 +#define LR1121_IRQ_PIN (32 + 8) // P1.08 +#define LR1121_NRESET_PIN (32 + 10) // P1.10 +#define LR1121_BUSY_PIN (32 + 11) // P1.11 +#define LR1121_SPI_NSS_PIN LORA_CS +#define LR1121_SPI_SCK_PIN LORA_SCK +#define LR1121_SPI_MOSI_PIN LORA_MOSI +#define LR1121_SPI_MISO_PIN LORA_MISO +#define LR11X0_DIO3_TCXO_VOLTAGE 3.0 +#define LR11X0_DIO_AS_RF_SWITCH + +// GPS +#define GPS_RX_PIN (0 + 19) // P0.19 +#define GPS_TX_PIN (0 + 20) // P0.20 +#define GPS_EN_GPIO (32 + 1) // P1.01 + +#define PIN_SERIAL1_RX GPS_TX_PIN +#define PIN_SERIAL1_TX GPS_RX_PIN + +#define PIN_BUZZER (0 + 22) // P0.22 + +// Battery monitoring +#define BATTERY_PIN (0 + 31) // P0.31 + +// #define CHARGER_FAULT (0 + 27) // P0.27 +#define BATTERY_CHARGING_INV (32 + 02) // P1.02 +#define BATTERY_SENSE_RESOLUTION_BITS 12 +#define BATTERY_SENSE_RESOLUTION 4096.0 +#define ADC_MULTIPLIER 1.537 + +#define OCV_ARRAY 4050, 4010, 3990, 3930, 3870, 3820, 3740, 3630, 3550, 3450, 3100 + +// Display - I2C display +#define HAS_SCREEN 1 +#define SCREEN_12V_ENABLE (0 + 23) // P0.23 +#define USE_SH1107 + +#define USERPREFS_OEM_TEXT "muzi_works_logo" +#define USERPREFS_OEM_FONT_SIZE 0 +#define USERPREFS_OEM_IMAGE_WIDTH 88 // 11 bytes wide +#define USERPREFS_OEM_IMAGE_HEIGHT 47 // 517 bytes total +#define USERPREFS_OEM_IMAGE_DATA \ + { \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0xFF, 0xFF, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0xFF, \ + 0xFF, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0xFF, 0xFF, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x03, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0xF7, 0x0F, 0xFF, 0x00, 0xF0, 0xFF, 0x0F, 0xC0, 0xFF, 0x07, 0x78, 0xFF, 0x9F, 0xFF, 0x01, 0xF0, 0xFF, 0x0F, 0xC0, \ + 0xFF, 0x03, 0x78, 0x3F, 0xFE, 0xF3, 0x01, 0xF0, 0xFF, 0x0F, 0x00, 0xE0, 0x03, 0x78, 0x1F, 0xFC, 0xC0, 0x03, 0xF0, \ + 0xFF, 0x0F, 0x00, 0xE0, 0x01, 0x78, 0x0F, 0xF8, 0x80, 0x03, 0xF0, 0xFF, 0x0F, 0x00, 0xF0, 0x00, 0x78, 0x0F, 0x78, \ + 0x80, 0x03, 0xF0, 0xFF, 0x0F, 0x00, 0x70, 0x00, 0x78, 0x07, 0x70, 0x80, 0x03, 0xF0, 0xFF, 0x0F, 0x00, 0x78, 0x00, \ + 0x78, 0x07, 0x70, 0x80, 0x03, 0xF0, 0xFF, 0x0F, 0x00, 0x3C, 0x00, 0x78, 0x07, 0x70, 0x80, 0x03, 0xF0, 0xFF, 0x0F, \ + 0x00, 0x1C, 0x00, 0x78, 0x07, 0x70, 0x80, 0x03, 0xF0, 0xFF, 0x0F, 0x00, 0x1E, 0x00, 0x78, 0x07, 0x70, 0x80, 0x03, \ + 0xE0, 0xFF, 0x0F, 0x00, 0x0F, 0x00, 0x78, 0x07, 0x70, 0x80, 0x03, 0xE0, 0xFF, 0x07, 0x80, 0x07, 0x00, 0x78, 0x07, \ + 0x70, 0x80, 0x03, 0xC0, 0xFF, 0x07, 0x80, 0x07, 0x00, 0x78, 0x07, 0x70, 0x80, 0x03, 0xC0, 0xFF, 0x03, 0xC0, 0xFF, \ + 0x07, 0x78, 0x07, 0x70, 0x80, 0x03, 0x00, 0xFF, 0x01, 0xE0, 0xFF, 0x07, 0x78, 0x07, 0x70, 0x80, 0x03, 0x00, 0x7C, \ + 0x00, 0xF0, 0xFF, 0x07, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, \ + 0xFF, 0xFF, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, \ + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0xE3, 0xE7, 0xC7, 0x1F, 0xF8, 0x0F, 0xF0, 0xE7, 0xE3, 0x07, 0x7C, 0xC7, 0xE7, \ + 0xC3, 0x0F, 0xE0, 0x07, 0xE0, 0xC7, 0xE1, 0x03, 0x70, 0xC7, 0xC3, 0xE3, 0x87, 0xC1, 0x07, 0xC0, 0xC7, 0xF8, 0xE3, \ + 0x71, 0xC7, 0xC3, 0xE3, 0xE3, 0xC7, 0xC7, 0xC7, 0x47, 0xF8, 0xF3, 0x7F, 0x8F, 0xC3, 0xF1, 0xE3, 0x8F, 0xC7, 0x8F, \ + 0x27, 0xFC, 0xE3, 0x7F, 0x8F, 0x81, 0xF1, 0xF1, 0x8F, 0xC7, 0xCF, 0x07, 0xFE, 0x03, 0x7E, 0x8F, 0x99, 0xF1, 0xF1, \ + 0x8F, 0x07, 0xC0, 0x07, 0xFF, 0x07, 0x78, 0x9F, 0x99, 0xF9, 0xF1, 0x8F, 0x07, 0xE0, 0x07, 0xFE, 0x3F, 0x70, 0x1F, \ + 0x18, 0xF8, 0xF3, 0x8F, 0x07, 0xF0, 0x27, 0xFC, 0xFF, 0x71, 0x3F, 0x18, 0xF8, 0xE3, 0xC7, 0xC7, 0xF1, 0x47, 0xF8, \ + 0xF3, 0x63, 0x3F, 0x3C, 0xFC, 0xC3, 0xC3, 0xC7, 0xE3, 0xC7, 0xF0, 0xE1, 0x71, 0x3F, 0x3C, 0xFC, 0x07, 0xE0, 0xC7, \ + 0xC7, 0xC7, 0xE1, 0x03, 0x70, 0x7F, 0x7E, 0xFE, 0x0F, 0xF0, 0xC7, 0x87, 0xC7, 0xC3, 0x07, 0x78, 0xFF, 0xFF, 0xFF, \ + 0x7F, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0x1F, 0x7E, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, \ + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, \ + 0xFF, 0xFF, 0x7F \ + } + +// QSPI Pins +#define PIN_QSPI_SCK (0 + 3) +#define PIN_QSPI_CS (0 + 26) +#define PIN_QSPI_IO0 (0 + 30) +#define PIN_QSPI_IO1 (0 + 29) +#define PIN_QSPI_IO2 (0 + 28) +#define PIN_QSPI_IO3 (0 + 2) + +// On-board QSPI Flash +#define EXTERNAL_FLASH_DEVICES W25Q32JVSS +#define EXTERNAL_FLASH_USE_QSPI + +// NFC is disabled via CONFIG_NFCT_PINS_AS_GPIOS=1 build flag +// This configures P0.09 and P0.10 as regular GPIO pins instead of NFC pins + +#ifdef __cplusplus +} +#endif + +/*---------------------------------------------------------------------------- + * Arduino objects - C++ only + *----------------------------------------------------------------------------*/ +#ifdef __cplusplus +#endif + +#endif // _VARIANT_MUZI_BASE_ \ No newline at end of file From 06dac12a738e6fa5dc82aedf73954b768f2306ec Mon Sep 17 00:00:00 2001 From: Quency-D <55523105+Quency-D@users.noreply.github.com> Date: Thu, 27 Nov 2025 01:10:21 +0800 Subject: [PATCH 671/691] Swap the GPS serial port pins. (#8756) * Swap the GPS serial port pins. * Trunk fixes --------- Co-authored-by: Jason P Co-authored-by: Ben Meadors --- variants/nrf52840/heltec_mesh_node_t114-inkhud/variant.h | 8 ++++---- variants/nrf52840/heltec_mesh_node_t114/variant.h | 4 ++-- variants/nrf52840/heltec_mesh_solar/variant.h | 8 ++++---- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/variants/nrf52840/heltec_mesh_node_t114-inkhud/variant.h b/variants/nrf52840/heltec_mesh_node_t114-inkhud/variant.h index 39cbc8f01..143d20459 100644 --- a/variants/nrf52840/heltec_mesh_node_t114-inkhud/variant.h +++ b/variants/nrf52840/heltec_mesh_node_t114-inkhud/variant.h @@ -116,13 +116,13 @@ No longer populated on PCB #define PIN_GPS_PPS (32 + 4) // Seems to be missing on this new board // #define PIN_GPS_PPS (32 + 4) // Pulse per second input from the GPS -#define GPS_TX_PIN (32 + 5) // This is for bits going TOWARDS the CPU -#define GPS_RX_PIN (32 + 7) // This is for bits going TOWARDS the GPS +#define GPS_TX_PIN (32 + 7) // This is for bits going TOWARDS the CPU +#define GPS_RX_PIN (32 + 5) // This is for bits going TOWARDS the GPS #define GPS_THREAD_INTERVAL 50 -#define PIN_SERIAL1_RX GPS_TX_PIN -#define PIN_SERIAL1_TX GPS_RX_PIN +#define PIN_SERIAL1_RX GPS_RX_PIN +#define PIN_SERIAL1_TX GPS_TX_PIN // PCF8563 RTC Module #define PCF8563_RTC 0x51 diff --git a/variants/nrf52840/heltec_mesh_node_t114/variant.h b/variants/nrf52840/heltec_mesh_node_t114/variant.h index de89d2d07..3493577bc 100644 --- a/variants/nrf52840/heltec_mesh_node_t114/variant.h +++ b/variants/nrf52840/heltec_mesh_node_t114/variant.h @@ -172,8 +172,8 @@ No longer populated on PCB #define GPS_THREAD_INTERVAL 50 -#define PIN_SERIAL1_RX GPS_TX_PIN -#define PIN_SERIAL1_TX GPS_RX_PIN +#define PIN_SERIAL1_RX GPS_RX_PIN +#define PIN_SERIAL1_TX GPS_TX_PIN // PCF8563 RTC Module #define PCF8563_RTC 0x51 diff --git a/variants/nrf52840/heltec_mesh_solar/variant.h b/variants/nrf52840/heltec_mesh_solar/variant.h index 7c43d8ba7..7a8fc579f 100644 --- a/variants/nrf52840/heltec_mesh_solar/variant.h +++ b/variants/nrf52840/heltec_mesh_solar/variant.h @@ -116,13 +116,13 @@ No longer populated on PCB #define PIN_GPS_PPS (32 + 4) // Seems to be missing on this new board // #define PIN_GPS_PPS (32 + 4) // Pulse per second input from the GPS -#define GPS_TX_PIN (32 + 5) // This is for bits going TOWARDS the CPU -#define GPS_RX_PIN (32 + 7) // This is for bits going TOWARDS the GPS +#define GPS_TX_PIN (32 + 7) // This is for bits going TOWARDS the CPU +#define GPS_RX_PIN (32 + 5) // This is for bits going TOWARDS the GPS #define GPS_THREAD_INTERVAL 50 -#define PIN_SERIAL1_RX GPS_TX_PIN -#define PIN_SERIAL1_TX GPS_RX_PIN +#define PIN_SERIAL1_RX GPS_RX_PIN +#define PIN_SERIAL1_TX GPS_TX_PIN /* * SPI Interfaces From f10aa3daa250a9f733888448e6c6f8b650b2cbb9 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Wed, 26 Nov 2025 11:30:34 -0600 Subject: [PATCH 672/691] Fixes --- src/Power.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Power.cpp b/src/Power.cpp index 0ac89670f..a2c559d91 100644 --- a/src/Power.cpp +++ b/src/Power.cpp @@ -707,7 +707,6 @@ bool Power::setup() []() { power->setIntervalFromNow(0); runASAP = true; - BaseType_t higherWake = 0; }, CHANGE); #endif @@ -717,7 +716,6 @@ bool Power::setup() []() { power->setIntervalFromNow(0); runASAP = true; - BaseType_t higherWake = 0; }, CHANGE); #endif @@ -777,7 +775,7 @@ void Power::shutdown() if (screen) { #ifdef T_DECK_PRO screen->showSimpleBanner("Device is powered off.\nConnect USB to start!", 0); // T-Deck Pro has no power button -#elif USE_EINK +#elif defined(USE_EINK) screen->showSimpleBanner("Shutting Down...", 2250); // dismiss after 3 seconds to avoid the banner on the sleep screen #else screen->showSimpleBanner("Shutting Down...", 0); // stays on screen From c3a7ad28656a255c41054b1f00a19392123e879d Mon Sep 17 00:00:00 2001 From: Jason P Date: Wed, 26 Nov 2025 13:53:26 -0600 Subject: [PATCH 673/691] More GPS pin flips for devices (#8760) --- variants/nrf52840/muzi_base/variant.h | 8 ++++---- variants/nrf52840/t-echo/variant.h | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/variants/nrf52840/muzi_base/variant.h b/variants/nrf52840/muzi_base/variant.h index d3e315f8b..96604c400 100644 --- a/variants/nrf52840/muzi_base/variant.h +++ b/variants/nrf52840/muzi_base/variant.h @@ -103,12 +103,12 @@ extern "C" { #define LR11X0_DIO_AS_RF_SWITCH // GPS -#define GPS_RX_PIN (0 + 19) // P0.19 -#define GPS_TX_PIN (0 + 20) // P0.20 +#define GPS_RX_PIN (0 + 20) // P0.20 +#define GPS_TX_PIN (0 + 19) // P0.19 #define GPS_EN_GPIO (32 + 1) // P1.01 -#define PIN_SERIAL1_RX GPS_TX_PIN -#define PIN_SERIAL1_TX GPS_RX_PIN +#define PIN_SERIAL1_RX GPS_RX_PIN +#define PIN_SERIAL1_TX GPS_TX_PIN #define PIN_BUZZER (0 + 22) // P0.22 diff --git a/variants/nrf52840/t-echo/variant.h b/variants/nrf52840/t-echo/variant.h index 8ddb1c263..b2692e448 100644 --- a/variants/nrf52840/t-echo/variant.h +++ b/variants/nrf52840/t-echo/variant.h @@ -187,8 +187,8 @@ External serial flash WP25R1635FZUIL0 #define GPS_THREAD_INTERVAL 50 -#define PIN_SERIAL1_RX GPS_TX_PIN -#define PIN_SERIAL1_TX GPS_RX_PIN +#define PIN_SERIAL1_RX GPS_RX_PIN +#define PIN_SERIAL1_TX GPS_TX_PIN // PCF8563 RTC Module #define PCF8563_RTC 0x51 From 9bfef80e308b9010c54765cb2352dd88c3d35135 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Thu, 27 Nov 2025 06:01:03 -0600 Subject: [PATCH 674/691] Add requestFocus() in CannedMessages (#8770) Certain actions in CannedMessages can trigger the module losing the requestFocus bit, which puts the UI into a slightly frozen state. --- src/modules/CannedMessageModule.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/modules/CannedMessageModule.cpp b/src/modules/CannedMessageModule.cpp index f435f6060..9cbacc877 100644 --- a/src/modules/CannedMessageModule.cpp +++ b/src/modules/CannedMessageModule.cpp @@ -836,6 +836,7 @@ bool CannedMessageModule::handleFreeTextInput(const InputEvent *event) if (event->inputEvent == INPUT_BROKER_BACK && this->freetext.length() > 0) { payload = 0x08; lastTouchMillis = millis(); + requestFocus(); runOnce(); return true; } @@ -844,6 +845,7 @@ bool CannedMessageModule::handleFreeTextInput(const InputEvent *event) if (event->inputEvent == INPUT_BROKER_LEFT) { payload = INPUT_BROKER_LEFT; lastTouchMillis = millis(); + requestFocus(); runOnce(); return true; } @@ -851,6 +853,7 @@ bool CannedMessageModule::handleFreeTextInput(const InputEvent *event) if (event->inputEvent == INPUT_BROKER_RIGHT) { payload = INPUT_BROKER_RIGHT; lastTouchMillis = millis(); + requestFocus(); runOnce(); return true; } From f7ae7aa2c13473d5971bb3f2a2a4992ae80ec190 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 27 Nov 2025 06:11:14 -0600 Subject: [PATCH 675/691] Upgrade trunk (#8623) Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com> --- .trunk/trunk.yaml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml index 1fd8790f2..ccb426745 100644 --- a/.trunk/trunk.yaml +++ b/.trunk/trunk.yaml @@ -4,31 +4,31 @@ cli: plugins: sources: - id: trunk - ref: v1.7.3 + ref: v1.7.4 uri: https://github.com/trunk-io/plugins lint: enabled: - - checkov@3.2.492 - - renovate@42.5.4 + - checkov@3.2.495 + - renovate@42.24.1 - prettier@3.6.2 - - trufflehog@3.90.13 + - trufflehog@3.91.1 - yamllint@1.37.1 - - bandit@1.8.6 + - bandit@1.9.2 - trivy@0.67.2 - taplo@0.10.0 - - ruff@0.14.4 + - ruff@0.14.6 - isort@7.0.0 - - markdownlint@0.45.0 + - markdownlint@0.46.0 - oxipng@9.1.5 - svgo@4.0.0 - - actionlint@1.7.8 + - actionlint@1.7.9 - flake8@7.3.0 - hadolint@2.14.0 - shfmt@3.6.0 - shellcheck@0.11.0 - black@25.11.0 - git-diff-check - - gitleaks@8.29.0 + - gitleaks@8.30.0 - clang-format@16.0.3 ignore: - linters: [ALL] From a6d1ce2048fae6a2f98837a8893186ad6e8deb0b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 27 Nov 2025 06:14:49 -0600 Subject: [PATCH 676/691] Update Sensirion Core to v0.7.2 (#8551) 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 d6ff155e4..9f7faeca5 100644 --- a/platformio.ini +++ b/platformio.ini @@ -213,6 +213,6 @@ lib_deps = # renovate: datasource=git-refs depName=meshtastic-DFRobot_LarkWeatherStation packageName=https://github.com/meshtastic/DFRobot_LarkWeatherStation gitBranch=master https://github.com/meshtastic/DFRobot_LarkWeatherStation/archive/4de3a9cadef0f6a5220a8a906cf9775b02b0040d.zip # renovate: datasource=custom.pio depName=Sensirion Core packageName=sensirion/library/Sensirion Core - sensirion/Sensirion Core@0.7.1 + sensirion/Sensirion Core@0.7.2 # renovate: datasource=custom.pio depName=Sensirion I2C SCD4x packageName=sensirion/library/Sensirion I2C SCD4x sensirion/Sensirion I2C SCD4x@1.1.0 From d0c6ec28dbae79d8dd9c8ec78e97b61aeddfdc48 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 27 Nov 2025 06:15:36 -0600 Subject: [PATCH 677/691] Update INA226 to v0.6.5 (#8645) 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 9f7faeca5..217ab0af8 100644 --- a/platformio.ini +++ b/platformio.ini @@ -169,7 +169,7 @@ lib_deps = # renovate: datasource=git-refs depName=DFRobot_RainfallSensor packageName=https://github.com/DFRobot/DFRobot_RainfallSensor gitBranch=master https://github.com/DFRobot/DFRobot_RainfallSensor/archive/38fea5e02b40a5430be6dab39a99a6f6347d667e.zip # renovate: datasource=custom.pio depName=INA226 packageName=robtillaart/library/INA226 - robtillaart/INA226@0.6.4 + robtillaart/INA226@0.6.5 # renovate: datasource=custom.pio depName=SparkFun MAX3010x packageName=sparkfun/library/SparkFun MAX3010x Pulse and Proximity Sensor Library sparkfun/SparkFun MAX3010x Pulse and Proximity Sensor Library@1.1.2 # renovate: datasource=custom.pio depName=SparkFun 9DoF IMU Breakout ICM 20948 packageName=sparkfun/library/SparkFun 9DoF IMU Breakout - ICM 20948 - Arduino Library From 7cb7a6cd3ee7705dcf9eb1ba881beb8fd5d2bc2f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 27 Nov 2025 06:16:12 -0600 Subject: [PATCH 678/691] Update NonBlockingRTTTL to v1.4.0 (#8541) 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 217ab0af8..1363a63fc 100644 --- a/platformio.ini +++ b/platformio.ini @@ -90,7 +90,7 @@ framework = arduino lib_deps = ${env.lib_deps} # renovate: datasource=custom.pio depName=NonBlockingRTTTL packageName=end2endzone/library/NonBlockingRTTTL - end2endzone/NonBlockingRTTTL@1.3.0 + end2endzone/NonBlockingRTTTL@1.4.0 build_flags = ${env.build_flags} -Os build_src_filter = ${env.build_src_filter} - - From bc3ed4a7f34ea0f1b9a69eb081093e2aa45edbc4 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 27 Nov 2025 06:16:50 -0600 Subject: [PATCH 679/691] Update platformio/ststm32 to v19.4.0 (#8433) 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 7732533c9..1a9fd10ce 100644 --- a/arch/stm32/stm32.ini +++ b/arch/stm32/stm32.ini @@ -2,7 +2,7 @@ extends = arduino_base platform = # renovate: datasource=custom.pio depName=platformio/ststm32 packageName=platformio/platform/ststm32 - platformio/ststm32@19.3.0 + platformio/ststm32@19.4.0 platform_packages = # TODO renovate platformio/framework-arduinoststm32@https://github.com/stm32duino/Arduino_Core_STM32/archive/2.10.1.zip From 1523368c53f9f0a04c6813ba7c95ba1095aa41c1 Mon Sep 17 00:00:00 2001 From: Nasimovy Date: Thu, 27 Nov 2025 13:18:52 +0100 Subject: [PATCH 680/691] adding support for the ST7796 + creating a new variant of the T-beam (#6575) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * remove duplicate HAS_LP5562 introduced by #6422 * add ST7796 * changes to get display centered+lib update * seperated from tbeam * forgot the simple scan case * lowered speeds to 1/4 * added SPI Speed to constructor+ cleaned up variant.h * even slower speeds.... * add ST7796 * changes to get display centered+lib update * seperated from tbeam * forgot the simple scan case * lowered speeds to 1/4 * added SPI Speed to constructor+ cleaned up variant.h * even slower speeds.... * changed variant name to tbeam-displayshield * modified variant.h and merged ini file+testing on lower spi frequency for the lora module, display shield pumps out EMI? * try higher speeds + HSPI * cleanup of redundant code * refelct changes? * trunk fmt * testing touchscreen code * further testing * changed to sensorlib 0.3.1 * i broke it , dont know how to fix at the moment will investigate * add -1 functionality for touch IRQ * revert to working example? * it works.... is pressed was not working properly * working touchscreen but gestures not moving display * swap XY+ mirror X * cleanup + addition of defines for on screen keyboard and canned message module * removed debug lines, disabled bluetooth for now because of stack smashing protect failure * reverted the revert #6640 + increased speed, bleutooth is stable now on reconnection cold booth etc , GPS is still not working though * remove debug + add fixed baudrate for gps * fmt * revert NIMble * changed display library to meshtastic org * removed baudrate of 115200 and some commented out code * Correct spelling Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Typo Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * display speed x10 * resolve conflicts * undo * revert speed increase CPU * add SCREEN_TRANSITION_FRAMERATE 5 * spi speed increase of the display * using the original touchscreen implementation * removal of H file line * add USE_ST7796 to missing places * removed is pressed + interrupt * revert changes of settings.json * update to screen.cpp * test identification of CST226 and CST328 * Update src/configuration.h typo Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * made changes to detection because it was completely wrong, CST226SE has 2 posible adresses * add merge queue * try vars * kerning in yaml. * update comment * lint etc * touching to check grandfathering * explicit ignores * add WIP for Unit C6L (#7433) * add WIP for Unit C6L * adapt to new config structure * Add c6l BLE and screen support (#7991) * Minor c6l fix * Move out of PRIVATE_HW --------- Co-authored-by: Austin Co-authored-by: Ben Meadors Co-authored-by: Jonathan Bennett Co-authored-by: Jason P Co-authored-by: Markus * Update Adafruit BusIO to v1.17.3 (#8018) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * Update actions/checkout action to v5 (#8020) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * Update actions/setup-python action to v6 (#8023) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * Upgrade trunk (#8025) Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com> * Update actions/download-artifact action to v5 (#8021) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * Fix init for InputEvent (#8015) * Automated version bumps (#8028) Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> * Allow Left / Right Events for selection and improve encoder responsives (#8016) * Allow Left / Right Events for selection and improve encoder responsives * add define for ROTARY_DELAY * T-Lora Pager: Support LR1121 and SX1280 models (#7956) * T-Lora Pager: Support LR1121 and SX1280 models * Remove ifdefs * (resubmission) Manual GitHub actions to allow building one target or arch (#7997) * Reset the modified files * Fix some changes * Fix some changes * Trunk. That is all. --------- Co-authored-by: Tom <116762865+Nestpebble@users.noreply.github.com> * BaseUI Show/Hide Frame Functionality (#7382) * Rename System Frame (from Memory) in code base * Create menu options to Show/Hide frames: Node Lists, Bearings, Position, LoRa, Clock and Favorites frames * Move Region Picker into submenu * Tweak wording for Send Position vs Node Info if the device has GPS * Update actions/checkout action to v5 (#8031) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * Update actions/download-artifact action to v5 (#8032) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * Update actions/setup-python action to v6 (#8033) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * 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. * Merge pull request #8004 from compumike/compumike/debug-heap-add-free-heap-debugging-to-all-log-lines When `DEBUG_HEAP` is defined, add free heap bytes to every log line in `RedirectablePrint::log_to_serial` * Feature: Seamless Cross-Preset Communication via UDP Multicast Bridging (#7753) * Added compatibility between nodes on different Presets through `Mesh via UDP` * Optimize multicast handling and channel mapping - FloodingRouter: remove redundant UDP-encrypted rebroadcast suppression. - Router: guard multicast fallback with HAS_UDP_MULTICAST and map fallback-decoded packets to the local default channel via isDefaultChannel() - UdpMulticastHandler: set transport_mechanism only after successful decode * trunk fmt * Move setting transport mechanism. --------- Co-authored-by: GUVWAF * Auto-favorite remote admin node * Merge pull request #7873 from compumike/compumike/client-base-role Add `CLIENT_BASE` role: `ROUTER` for favorites, `CLIENT` otherwise (for attic/roof nodes!) * Fixes * 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 * 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. * Fix * 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 * updated shebang to use a more standard path for bash (#7922) Signed-off-by: Trenton VanderWert * Show GPS Date properly in drawCommonHeader (#7887) * Commit good code that is sustainable * Fix new build errors * 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 * Add formatting and menu picking for other GPS format options (#7974) * Add back options for other GPS format options * Rename variables and don't overlap elements * Fix default value * Should probably add a menu while I'm here! * Shorten names just a bit to fit on screens * Fix off by one * Labels try to make things better * Missed a label * Update protobufs (#8038) Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> * Add formatting and menu picking for other GPS format options (#7974) * Add back options for other GPS format options * Rename variables and don't overlap elements * Fix default value * Should probably add a menu while I'm here! * Shorten names just a bit to fit on screens * Fix off by one * Labels try to make things better * Missed a label * 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 * (resubmission) Manual GitHub actions to allow building one target or arch (#7997) * Reset the modified files * Fix some changes * Fix some changes * Trunk. That is all. --------- Co-authored-by: Tom <116762865+Nestpebble@users.noreply.github.com> * PPA: Enable Ubuntu 25.10 (questing) (#7940) * Update Protobuf usage, add MLS, fix clock (#8041) * Update protobufs (#8045) Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> * Fix icon * C6l fixes (#8047) * fix build with HAS_TELEMETRY 0 (#8051) * Make sure to ACK ACKs/replies if next-hop routing is used (#8052) * Make sure to ACK ACKs/replies if next-hop routing is used To stop their retransmissions; hop limit of 0 is enough * Update src/mesh/ReliableRouter.cpp Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Ben Meadors Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * move HTTP contentTypes to Flash - saves 768 Bytes of RAM (#8055) * Use `lora.use_preset` config to get name (#8057) * Update RadioLib to v7.3.0 (#8065) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * Fix Rotary Encoder Button (#8001) this fixes the Rotary Encoder Button, currenlty its not working at all. Currently the action `ROTARY_ACTION_PRESSED` is only triggerd with a IRQ on RISING, which results in nothing since the function detects the "not longer" pressed button --> no action. the `ROTARY_ACTION_PRESSED` implementation needs to be called on both edges (on press and release of the button) changing the interupt setting to `CHANGE` fixes the problem. * Add another seeed_xiao_nrf52840_kit build environment for I2C pinout (#8036) * Update platformio.ini * Remove some more extraneous lines * Add heltec_v4 board. (#7845) * add heltec_v4 board. * Update variants/esp32s3/heltec_v4/platformio.ini Co-authored-by: Austin * Limit the maximum output power. * Trunk fixes Fixes formatting to match meshtastic trunk linter. * Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Ben Meadors Co-authored-by: Austin Co-authored-by: Tom Fifield Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Upgrade trunk (#8078) Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com> * portduino bump to fix gpiod bug (#8083) An earlier portduino causes problems with initializing gpiod lines. This pulls in the fix. * Handle ext. notification module things even if not enabled (#8089) * tlora-pager wake on button, and kb backlight toggling (#8090) * Try-fix: Unstick that PhoneAPI state (#8091) Co-authored-by: Jonathan Bennett * Also pull a deviceID from esp32c6 devices (#8092) * Remove line from BLE pin screen, to make pin readible on tiny screens * Fix build errors (#8067) * Heltec V4 is 16mb * Clear lasttoradio on BLE disconnect (#8095) * On disconnect, clear the lastToRadio buffer * Move it, bucko! * Revert "Fix build errors (#8067)" This reverts commit d998f70b5633e8b2f88823cfb73761625bbc3423. * Automated version bumps (#8100) Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> * Upgrade trunk (#8094) Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com> * Update Adafruit BusIO to v1.17.4 (#8098) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * Add three expansion screens for heltec mesh solar. (#7995) * Add three expansion screens for heltec mesh solar. * delete whitespace Update variants/nrf52840/heltec_mesh_solar/variant.h Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * delete whitespace Update variants/nrf52840/heltec_mesh_solar/platformio.ini Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Ben Meadors * Trunk --------- Signed-off-by: Trenton VanderWert Co-authored-by: Thomas Göttgens Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Ben Meadors Co-authored-by: Dane Evans Co-authored-by: Austin Co-authored-by: Jonathan Bennett Co-authored-by: Jason P Co-authored-by: Markus Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com> Co-authored-by: Markus <974709+Links2004@users.noreply.github.com> Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> Co-authored-by: WillyJL Co-authored-by: Tom <116762865+NomDeTom@users.noreply.github.com> Co-authored-by: Tom <116762865+Nestpebble@users.noreply.github.com> Co-authored-by: Jason P Co-authored-by: HarukiToreda <116696711+HarukiToreda@users.noreply.github.com> Co-authored-by: Michael Co-authored-by: GUVWAF Co-authored-by: Trent V. Co-authored-by: Quency-D <55523105+Quency-D@users.noreply.github.com> Co-authored-by: Tom Fifield Co-authored-by: GUVWAF <78759985+GUVWAF@users.noreply.github.com> --- src/configuration.h | 3 +- src/detect/ScanI2C.h | 3 +- src/detect/ScanI2CTwoWire.cpp | 21 +++++++-- src/graphics/Screen.cpp | 45 ++++++++++++++++++- src/graphics/Screen.h | 2 + src/graphics/ScreenFonts.h | 2 +- src/graphics/draw/DebugRenderer.cpp | 8 ++-- src/graphics/draw/UIRenderer.cpp | 2 +- src/graphics/images.h | 3 +- src/main.cpp | 4 +- src/mesh/NodeDB.cpp | 2 +- .../tbeam_displayshield/variant.cpp | 43 ++++++++++++++++++ variants/esp32/tbeam/platformio.ini | 18 ++++++-- variants/esp32/tbeam/variant.h | 33 +++++++++++++- 14 files changed, 167 insertions(+), 22 deletions(-) create mode 100644 src/platform/extra_variants/tbeam_displayshield/variant.cpp diff --git a/src/configuration.h b/src/configuration.h index d37269995..d30280d8b 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -250,8 +250,9 @@ along with this program. If not, see . // Touchscreen // ----------------------------------------------------------------------------- #define FT6336U_ADDR 0x48 -#define CST328_ADDR 0x1A +#define CST328_ADDR 0x1A // same address as CST226SE #define CHSC6X_ADDR 0x2E +#define CST226SE_ADDR_ALT 0x5A // ----------------------------------------------------------------------------- // RAK12035VB Soil Monitor (using RAK12023 up to 3 RAK12035 monitors can be connected) diff --git a/src/detect/ScanI2C.h b/src/detect/ScanI2C.h index 55980face..cced980a6 100644 --- a/src/detect/ScanI2C.h +++ b/src/detect/ScanI2C.h @@ -85,7 +85,8 @@ class ScanI2C DRV2605, BH1750, DA217, - CHSC6X + CHSC6X, + CST226SE } DeviceType; // typedef uint8_t DeviceAddress; diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp index bcf49286e..db269ac64 100644 --- a/src/detect/ScanI2CTwoWire.cpp +++ b/src/detect/ScanI2CTwoWire.cpp @@ -499,7 +499,18 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize) SCAN_SIMPLE_CASE(DFROBOT_RAIN_ADDR, DFROBOT_RAIN, "DFRobot Rain Gauge", (uint8_t)addr.address); SCAN_SIMPLE_CASE(LTR390UV_ADDR, LTR390UV, "LTR390UV", (uint8_t)addr.address); SCAN_SIMPLE_CASE(PCT2075_ADDR, PCT2075, "PCT2075", (uint8_t)addr.address); - SCAN_SIMPLE_CASE(CST328_ADDR, CST328, "CST328", (uint8_t)addr.address); + case CST328_ADDR: + // Do we have the CST328 or the CST226SE + registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0xAB), 1); + if (registerValue == 0xA9) { + type = CST226SE; + logFoundDevice("CST226SE", (uint8_t)addr.address); + } else { + type = CST328; + logFoundDevice("CST328", (uint8_t)addr.address); + } + break; + SCAN_SIMPLE_CASE(CHSC6X_ADDR, CHSC6X, "CHSC6X", (uint8_t)addr.address); case LTR553ALS_ADDR: registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x86), 1); // Part ID register @@ -528,8 +539,12 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize) #endif case MLX90614_ADDR_DEF: - registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x0e), 1); - if (registerValue == 0x5a) { + // Do we have the MLX90614 or the MPR121KB or the CST226SE + registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x06), 1); + if (registerValue == 0xAB) { + type = CST226SE; + logFoundDevice("CST226SE", (uint8_t)addr.address); + } else if (getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x0e), 1) == 0x5a) { type = MLX90614; logFoundDevice("MLX90614", (uint8_t)addr.address); } else { diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index dc9806156..e8c2e4b88 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -324,7 +324,7 @@ static int8_t prevFrame = -1; // Combined dynamic node list frame cycling through LastHeard, HopSignal, and Distance modes // Uses a single frame and changes data every few seconds (E-Ink variant is separate) -#if defined(ESP_PLATFORM) && defined(USE_ST7789) +#if defined(ESP_PLATFORM) && (defined(USE_ST7789) || defined(USE_ST7796)) SPIClass SPI1(HSPI); #endif @@ -356,7 +356,18 @@ Screen::Screen(ScanI2C::DeviceAddress address, meshtastic_Config_DisplayConfig_O #else dispdev = new ST7789Spi(&SPI1, ST7789_RESET, ST7789_RS, ST7789_NSS, GEOMETRY_RAWMODE, TFT_WIDTH, TFT_HEIGHT); #endif +#elif defined(USE_ST7796) +#ifdef ESP_PLATFORM + dispdev = new ST7796Spi(&SPI1, ST7796_RESET, ST7796_RS, ST7796_NSS, GEOMETRY_RAWMODE, TFT_WIDTH, TFT_HEIGHT, ST7796_SDA, + ST7796_MISO, ST7796_SCK, TFT_SPI_FREQUENCY); +#else + dispdev = new ST7796Spi(&SPI1, ST7796_RESET, ST7796_RS, ST7796_NSS, GEOMETRY_RAWMODE, TFT_WIDTH, TFT_HEIGHT); +#endif +#if defined(USE_ST7789) static_cast(dispdev)->setRGB(TFT_MESH); +#elif defined(USE_ST7796) + static_cast(dispdev)->setRGB(TFT_MESH); +#endif #elif defined(USE_SSD1306) dispdev = new SSD1306Wire(address.address, -1, -1, geometry, (address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE); @@ -474,6 +485,15 @@ void Screen::handleSetOn(bool on, FrameCallback einkScreensaver) pinMode(VTFT_LEDA, OUTPUT); digitalWrite(VTFT_LEDA, TFT_BACKLIGHT_ON); #endif +#endif +#ifdef USE_ST7796 + ui->init(); +#ifdef ESP_PLATFORM + analogWrite(VTFT_LEDA, BRIGHTNESS_DEFAULT); +#else + pinMode(VTFT_LEDA, OUTPUT); + digitalWrite(VTFT_LEDA, TFT_BACKLIGHT_ON); +#endif #endif enabled = true; setInterval(0); // Draw ASAP @@ -512,6 +532,21 @@ void Screen::handleSetOn(bool on, FrameCallback einkScreensaver) nrf_gpio_cfg_default(ST7789_NSS); #endif #endif +#ifdef USE_ST7796 + SPI1.end(); +#if defined(ARCH_ESP32) + pinMode(VTFT_LEDA, OUTPUT); + digitalWrite(VTFT_LEDA, LOW); + pinMode(ST7796_RESET, ANALOG); + pinMode(ST7796_RS, ANALOG); + pinMode(ST7796_NSS, ANALOG); +#else + nrf_gpio_cfg_default(VTFT_LEDA); + nrf_gpio_cfg_default(ST7796_RESET); + nrf_gpio_cfg_default(ST7796_RS); + nrf_gpio_cfg_default(ST7796_NSS); +#endif +#endif #ifdef T_WATCH_S3 PMU->disablePowerOutput(XPOWERS_ALDO2); @@ -557,6 +592,10 @@ void Screen::setup() #if defined(MUZI_BASE) dispdev->delayPoweron = true; #endif +#if defined(USE_ST7796) && defined(TFT_MESH) + // Custom text color, if defined in variant.h + static_cast(dispdev)->setRGB(TFT_MESH); +#endif // === Initialize display and UI system === ui->init(); @@ -620,6 +659,8 @@ void Screen::setup() static_cast(dispdev)->flipScreenVertically(); #elif defined(USE_ST7789) static_cast(dispdev)->flipScreenVertically(); +#elif defined(USE_ST7796) + static_cast(dispdev)->mirrorScreen(); #elif !defined(M5STACK_UNITC6L) dispdev->flipScreenVertically(); #endif @@ -652,7 +693,7 @@ void Screen::setup() touchScreenImpl1->init(); } } -#elif HAS_TOUCHSCREEN && !defined(USE_EINK) +#elif HAS_TOUCHSCREEN && !defined(USE_EINK) && !HAS_CST226SE touchScreenImpl1 = new TouchScreenImpl1(dispdev->getWidth(), dispdev->getHeight(), static_cast(dispdev)->getTouch); touchScreenImpl1->init(); diff --git a/src/graphics/Screen.h b/src/graphics/Screen.h index 375bc2805..a40579ff5 100644 --- a/src/graphics/Screen.h +++ b/src/graphics/Screen.h @@ -83,6 +83,8 @@ class Screen #include #elif defined(USE_SPISSD1306) #include +#elif defined(USE_ST7796) +#include #else // the SH1106/SSD1306 variant is auto-detected #include diff --git a/src/graphics/ScreenFonts.h b/src/graphics/ScreenFonts.h index c497a27b2..bcb4c4987 100644 --- a/src/graphics/ScreenFonts.h +++ b/src/graphics/ScreenFonts.h @@ -73,7 +73,7 @@ #endif #if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \ - defined(ST7789_CS) || defined(USE_ST7789) || defined(HX8357_CS) || defined(ILI9488_CS) || defined(ST7796_CS)) && \ + defined(ST7789_CS) || defined(USE_ST7789) || defined(HX8357_CS) || defined(ILI9488_CS) || defined(ST7796_CS) || defined(USE_ST7796)) && \ !defined(DISPLAY_FORCE_SMALL_FONTS) // The screen is bigger so use bigger fonts #define FONT_SMALL FONT_MEDIUM_LOCAL // Height: 19 diff --git a/src/graphics/draw/DebugRenderer.cpp b/src/graphics/draw/DebugRenderer.cpp index 79c1e7e61..6bccb1653 100644 --- a/src/graphics/draw/DebugRenderer.cpp +++ b/src/graphics/draw/DebugRenderer.cpp @@ -97,7 +97,8 @@ void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16 (storeForwardModule->heartbeatInterval * 1200))) { // no heartbeat, overlap a bit #if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \ defined(ST7789_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(HX8357_CS) || defined(ST7796_CS) || \ - ARCH_PORTDUINO) && \ + defined(USE_ST7796) || \ + ARCH_PORTDUINO) && \ !defined(DISPLAY_FORCE_SMALL_FONTS) display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(screen->ourId), y + 3 + FONT_HEIGHT_SMALL, 12, 8, imgQuestionL1); @@ -109,7 +110,7 @@ void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16 #endif } else { #if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \ - defined(ST7789_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(HX8357_CS) || defined(ST7796_CS)) && \ + defined(ST7789_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(HX8357_CS) || defined(ST7796_CS) || defined(USE_ST7796)) && \ !defined(DISPLAY_FORCE_SMALL_FONTS) display->drawFastImage(x + SCREEN_WIDTH - 18 - display->getStringWidth(screen->ourId), y + 3 + FONT_HEIGHT_SMALL, 16, 8, imgSFL1); @@ -125,7 +126,8 @@ void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16 // TODO: Raspberry Pi supports more than just the one screen size #if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \ defined(ST7789_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(HX8357_CS) || defined(ST7796_CS) || \ - ARCH_PORTDUINO) && \ + defined(USE_ST7796) || \ + ARCH_PORTDUINO) && \ !defined(DISPLAY_FORCE_SMALL_FONTS) display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(screen->ourId), y + 3 + FONT_HEIGHT_SMALL, 12, 8, imgInfoL1); diff --git a/src/graphics/draw/UIRenderer.cpp b/src/graphics/draw/UIRenderer.cpp index c50fe5cf1..3d23acc9f 100644 --- a/src/graphics/draw/UIRenderer.cpp +++ b/src/graphics/draw/UIRenderer.cpp @@ -257,7 +257,7 @@ void UIRenderer::drawNodes(OLEDDisplay *display, int16_t x, int16_t y, const mes } #if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \ - defined(ST7789_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(HX8357_CS) || defined(ST7796_CS)) && \ + defined(ST7789_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(HX8357_CS) || defined(ST7796_CS) || defined(USE_ST7796)) && \ !defined(DISPLAY_FORCE_SMALL_FONTS) if (isHighResolution) { diff --git a/src/graphics/images.h b/src/graphics/images.h index 8670d78d9..998fe8e2a 100644 --- a/src/graphics/images.h +++ b/src/graphics/images.h @@ -27,8 +27,7 @@ const uint8_t bluetoothConnectedIcon[36] PROGMEM = {0xfe, 0x01, 0xff, 0x03, 0x03 0xfe, 0x31, 0x00, 0x30, 0x30, 0x30, 0x30, 0x30, 0xf0, 0x3f, 0xe0, 0x1f}; #if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \ - defined(ST7789_CS) || defined(USE_ST7789) || defined(HX8357_CS) || defined(ILI9488_CS) || defined(ST7796_CS) || \ - ARCH_PORTDUINO) && \ + defined(ST7789_CS) || defined(USE_ST7789) || defined(HX8357_CS) || defined(ILI9488_CS) || defined(USE_ST7796) || defined(ST7796_CS) || ARCH_PORTDUINO) && \ !defined(DISPLAY_FORCE_SMALL_FONTS) const uint8_t imgQuestionL1[] PROGMEM = {0xff, 0x01, 0x01, 0x32, 0x7b, 0x49, 0x49, 0x6f, 0x26, 0x01, 0x01, 0xff}; const uint8_t imgQuestionL2[] PROGMEM = {0x0f, 0x08, 0x08, 0x08, 0x06, 0x0f, 0x0f, 0x06, 0x08, 0x08, 0x08, 0x0f}; diff --git a/src/main.cpp b/src/main.cpp index 8fc2c097b..da2e39604 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -877,7 +877,7 @@ void setup() if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) { #if defined(ST7701_CS) || defined(ST7735_CS) || defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || \ - defined(ST7789_CS) || defined(HX8357_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(ST7796_CS) || \ + defined(ST7789_CS) || defined(HX8357_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(ST7796_CS) || defined(USE_ST7796) || \ defined(USE_SPISSD1306) screen = new graphics::Screen(screen_found, screen_model, screen_geometry); #elif defined(ARCH_PORTDUINO) @@ -1154,7 +1154,7 @@ void setup() // Don't call screen setup until after nodedb is setup (because we need // the current region name) #if defined(ST7701_CS) || defined(ST7735_CS) || defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || \ - defined(ST7789_CS) || defined(HX8357_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(ST7796_CS) || \ + defined(ST7789_CS) || defined(HX8357_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(ST7796_CS) || defined(USE_ST7796) || \ defined(USE_SPISSD1306) if (screen) screen->setup(); diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index ff76baaa1..4e99a22ef 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -664,7 +664,7 @@ void NodeDB::installDefaultConfig(bool preserveKey = false) config.bluetooth.fixed_pin = defaultBLEPin; #if defined(ST7735_CS) || defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7789_CS) || \ - defined(HX8357_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(ST7796_CS) || defined(USE_SPISSD1306) + defined(HX8357_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(ST7796_CS) || defined(USE_SPISSD1306) || defined(USE_ST7796) bool hasScreen = true; #ifdef HELTEC_MESH_NODE_T114 uint32_t st7789_id = get_st7789_id(ST7789_NSS, ST7789_SCK, ST7789_SDA, ST7789_RS, ST7789_RESET); diff --git a/src/platform/extra_variants/tbeam_displayshield/variant.cpp b/src/platform/extra_variants/tbeam_displayshield/variant.cpp new file mode 100644 index 000000000..7beac2293 --- /dev/null +++ b/src/platform/extra_variants/tbeam_displayshield/variant.cpp @@ -0,0 +1,43 @@ +#include "configuration.h" + +#ifdef HAS_CST226SE + +#include "TouchDrvCSTXXX.hpp" +#include "input/TouchScreenImpl1.h" +#include + +TouchDrvCSTXXX tsPanel; +static constexpr uint8_t PossibleAddresses[2] = {CST328_ADDR, CST226SE_ADDR_ALT}; +uint8_t i2cAddress = 0; + +bool readTouch(int16_t *x, int16_t *y) +{ + int16_t x_array[1], y_array[1]; + uint8_t touched = tsPanel.getPoint(x_array, y_array, 1); + if (touched > 0) { + *y = x_array[0]; + *x = (TFT_WIDTH - y_array[0]); + // Check bounds + if (*x < 0 || *x >= TFT_WIDTH || *y < 0 || *y >= TFT_HEIGHT) { + return false; + } + return true; // Valid touch detected + } + return false; // No valid touch data +} + +void lateInitVariant() +{ + tsPanel.setTouchDrvModel(TouchDrv_CST226); + for (uint8_t addr : PossibleAddresses) { + if (tsPanel.begin(Wire, addr, I2C_SDA, I2C_SCL)) { + i2cAddress = addr; + LOG_DEBUG("CST226SE init OK at address 0x%02X", addr); + touchScreenImpl1 = new TouchScreenImpl1(TFT_WIDTH, TFT_HEIGHT, readTouch); + touchScreenImpl1->init(); + return; + } + } + LOG_ERROR("CST226SE init failed at all known addresses"); +} +#endif diff --git a/variants/esp32/tbeam/platformio.ini b/variants/esp32/tbeam/platformio.ini index e53f22d30..c635081ff 100644 --- a/variants/esp32/tbeam/platformio.ini +++ b/variants/esp32/tbeam/platformio.ini @@ -4,12 +4,22 @@ extends = esp32_base board = ttgo-t-beam board_level = pr board_check = true -lib_deps = - ${esp32_base.lib_deps} -build_flags = - ${esp32_base.build_flags} +lib_deps = ${esp32_base.lib_deps} +build_flags = ${esp32_base.build_flags} -D TBEAM_V10 -I variants/esp32/tbeam -DBOARD_HAS_PSRAM -mfix-esp32-psram-cache-issue upload_speed = 921600 + +[env:tbeam-displayshield] +extends = env:tbeam + +build_flags = + ${env:tbeam.build_flags} + -D USE_ST7796 + +lib_deps = + ${env:tbeam.lib_deps} + https://github.com/meshtastic/st7796/archive/refs/tags/1.0.5.zip ; display addon + lewisxhe/SensorLib@0.3.1 ; touchscreen addon \ No newline at end of file diff --git a/variants/esp32/tbeam/variant.h b/variants/esp32/tbeam/variant.h index 5b521a2de..2d144a888 100644 --- a/variants/esp32/tbeam/variant.h +++ b/variants/esp32/tbeam/variant.h @@ -42,4 +42,35 @@ #define GPS_UBLOX #define GPS_RX_PIN 34 #define GPS_TX_PIN 12 -// #define GPS_DEBUG \ No newline at end of file +// #define GPS_DEBUG + +// Used when the display shield is chosen +#ifdef USE_ST7796 + +#undef EXT_NOTIFY_OUT +#undef LED_STATE_ON +#undef LED_PIN + +#define HAS_CST226SE 1 +#define HAS_TOUCHSCREEN 1 +// #define TOUCH_IRQ 35 // broken in this version of the lib 0.3.1 +#ifndef TOUCH_IRQ +#define TOUCH_IRQ -1 +#endif +#define CANNED_MESSAGE_MODULE_ENABLE 1 +#define USE_VIRTUAL_KEYBOARD 1 + +#define ST7796_NSS 25 +#define ST7796_RS 13 // DC +#define ST7796_SDA 14 // MOSI +#define ST7796_SCK 15 +#define ST7796_RESET 2 +#define ST7796_MISO -1 +#define ST7796_BUSY -1 +#define VTFT_LEDA 4 +#define TFT_SPI_FREQUENCY 60000000 +#define TFT_HEIGHT 222 +#define TFT_WIDTH 480 +#define BRIGHTNESS_DEFAULT 100 // Medium Low Brightness +#define SCREEN_TRANSITION_FRAMERATE 5 // fps +#endif \ No newline at end of file From de26dfe46837367addb35bd163363179d62a55f3 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Fri, 28 Nov 2025 05:23:07 -0600 Subject: [PATCH 681/691] Remove screen activation in powerExit function (#8779) This seems to be a potential source of unintended screen wakes. --- src/PowerFSM.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/PowerFSM.cpp b/src/PowerFSM.cpp index 322b877ff..b4906cd60 100644 --- a/src/PowerFSM.cpp +++ b/src/PowerFSM.cpp @@ -219,8 +219,6 @@ static void powerIdle() static void powerExit() { - if (screen) - screen->setOn(true); setBluetoothEnable(true); } From 2f0fe4e5da61e86c7833e9fa0c23a81a5d799452 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Fri, 28 Nov 2025 16:42:14 -0600 Subject: [PATCH 682/691] Use the dedicated isVbusIn() function for detecting USB plug --- src/Power.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Power.cpp b/src/Power.cpp index a2c559d91..7bb8896ce 100644 --- a/src/Power.cpp +++ b/src/Power.cpp @@ -1453,7 +1453,7 @@ class LipoCharger : public HasBatteryLevel /** * return true if there is an external power source detected */ - virtual bool isVbusIn() override { return PPM->getVbusVoltage() > 0; } + virtual bool isVbusIn() override { return PPM->isVbusIn(); } /** * return true if the battery is currently charging From 94db3506bdd23b39c0cbf72acad43948893f2ec8 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Fri, 28 Nov 2025 19:58:52 -0600 Subject: [PATCH 683/691] Add LOG_POWERFSM and LOG_INPUT debug macros (#8791) --- src/PowerFSM.cpp | 30 ++++++++++++++-------------- src/PowerFSM.h | 6 ++++++ src/graphics/Screen.cpp | 1 + src/input/InputBroker.h | 6 ++++++ src/modules/SystemCommandsModule.cpp | 3 ++- 5 files changed, 30 insertions(+), 16 deletions(-) diff --git a/src/PowerFSM.cpp b/src/PowerFSM.cpp index b4906cd60..67b680233 100644 --- a/src/PowerFSM.cpp +++ b/src/PowerFSM.cpp @@ -57,21 +57,21 @@ static bool isPowered() static void sdsEnter() { - LOG_DEBUG("State: SDS"); + LOG_POWERFSM("State: SDS"); // FIXME - make sure GPS and LORA radio are off first - because we want close to zero current draw doDeepSleep(Default::getConfiguredOrDefaultMs(config.power.sds_secs), false, false); } static void lowBattSDSEnter() { - LOG_DEBUG("State: Lower batt SDS"); + LOG_POWERFSM("State: Lower batt SDS"); doDeepSleep(Default::getConfiguredOrDefaultMs(config.power.sds_secs), false, true); } extern Power *power; static void shutdownEnter() { - LOG_DEBUG("State: SHUTDOWN"); + LOG_POWERFSM("State: SHUTDOWN"); shutdownAtMsec = millis(); } @@ -81,7 +81,7 @@ static uint32_t secsSlept; static void lsEnter() { - LOG_INFO("lsEnter begin, ls_secs=%u", config.power.ls_secs); + LOG_POWERFSM("lsEnter begin, ls_secs=%u", config.power.ls_secs); if (screen) screen->setOn(false); secsSlept = 0; // How long have we been sleeping this time @@ -155,12 +155,12 @@ static void lsIdle() static void lsExit() { - LOG_INFO("Exit state: LS"); + LOG_POWERFSM("State: lsExit"); } static void nbEnter() { - LOG_DEBUG("State: NB"); + LOG_POWERFSM("State: nbEnter"); if (screen) screen->setOn(false); #ifdef ARCH_ESP32 @@ -173,6 +173,7 @@ static void nbEnter() static void darkEnter() { + LOG_POWERFSM("State: darkEnter"); setBluetoothEnable(true); if (screen) screen->setOn(false); @@ -180,7 +181,7 @@ static void darkEnter() static void serialEnter() { - LOG_DEBUG("State: SERIAL"); + LOG_POWERFSM("State: serialEnter"); setBluetoothEnable(false); if (screen) { screen->setOn(true); @@ -189,13 +190,14 @@ static void serialEnter() static void serialExit() { + LOG_POWERFSM("State: serialExit"); // Turn bluetooth back on when we leave serial stream API setBluetoothEnable(true); } static void powerEnter() { - // LOG_DEBUG("State: POWER"); + LOG_POWERFSM("State: powerEnter"); if (!isPowered()) { // If we got here, we are in the wrong state - we should be in powered, let that state handle things LOG_INFO("Loss of power in Powered"); @@ -210,6 +212,7 @@ static void powerEnter() static void powerIdle() { + // LOG_POWERFSM("State: powerIdle"); // very chatty if (!isPowered()) { // If we got here, we are in the wrong state LOG_INFO("Loss of power in Powered"); @@ -219,12 +222,13 @@ static void powerIdle() static void powerExit() { + LOG_POWERFSM("State: powerExit"); setBluetoothEnable(true); } static void onEnter() { - LOG_DEBUG("State: ON"); + LOG_POWERFSM("State: onEnter"); if (screen) screen->setOn(true); setBluetoothEnable(true); @@ -232,6 +236,7 @@ static void onEnter() static void onIdle() { + LOG_POWERFSM("State: onIdle"); if (isPowered()) { // If we got here, we are in the wrong state - we should be in powered, let that state handle things powerFSM.trigger(EVENT_POWER_CONNECTED); @@ -240,7 +245,7 @@ static void onIdle() static void bootEnter() { - LOG_DEBUG("State: BOOT"); + LOG_POWERFSM("State: bootEnter"); } State stateSHUTDOWN(shutdownEnter, NULL, NULL, "SHUTDOWN"); @@ -317,11 +322,6 @@ void PowerFSM_setup() // if any packet destined for phone arrives, turn on bluetooth at least powerFSM.add_transition(&stateNB, &stateDARK, EVENT_PACKET_FOR_PHONE, NULL, "Packet for phone"); - // Removed 2.7: we don't show the nodes individually for every node on the screen anymore - // powerFSM.add_transition(&stateNB, &stateON, EVENT_NODEDB_UPDATED, NULL, "NodeDB update"); - // powerFSM.add_transition(&stateDARK, &stateON, EVENT_NODEDB_UPDATED, NULL, "NodeDB update"); - // powerFSM.add_transition(&stateON, &stateON, EVENT_NODEDB_UPDATED, NULL, "NodeDB update"); - // Show the received text message powerFSM.add_transition(&stateLS, &stateON, EVENT_RECEIVED_MSG, NULL, "Received text"); powerFSM.add_transition(&stateNB, &stateON, EVENT_RECEIVED_MSG, NULL, "Received text"); diff --git a/src/PowerFSM.h b/src/PowerFSM.h index 6330a5fc6..182ac082a 100644 --- a/src/PowerFSM.h +++ b/src/PowerFSM.h @@ -2,6 +2,12 @@ #include "configuration.h" +#ifdef PowerFSMDebug +#define LOG_POWERFSM(...) LOG_DEBUG(__VA_ARGS__) +#else +#define LOG_POWERFSM(...) +#endif + // See sw-design.md for documentation #define EVENT_PRESS 1 diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index e8c2e4b88..d58927f1e 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -1605,6 +1605,7 @@ int Screen::handleUIFrameEvent(const UIFrameEvent *event) int Screen::handleInputEvent(const InputEvent *event) { + LOG_INPUT("Screen Input event %u! kb %u", event->inputEvent, event->kbchar); if (!screenOn) return 0; diff --git a/src/input/InputBroker.h b/src/input/InputBroker.h index 36328ca64..022101f7d 100644 --- a/src/input/InputBroker.h +++ b/src/input/InputBroker.h @@ -3,6 +3,12 @@ #include "Observer.h" #include "freertosinc.h" +#ifdef InputBrokerDebug +#define LOG_INPUT(...) LOG_DEBUG(__VA_ARGS__) +#else +#define LOG_INPUT(...) +#endif + enum input_broker_event { INPUT_BROKER_NONE = 0, INPUT_BROKER_SELECT = 10, diff --git a/src/modules/SystemCommandsModule.cpp b/src/modules/SystemCommandsModule.cpp index dc5d8b41f..7fa4485c8 100644 --- a/src/modules/SystemCommandsModule.cpp +++ b/src/modules/SystemCommandsModule.cpp @@ -1,4 +1,5 @@ #include "SystemCommandsModule.h" +#include "input/InputBroker.h" #include "meshUtils.h" #if HAS_SCREEN #include "graphics/Screen.h" @@ -22,7 +23,7 @@ SystemCommandsModule::SystemCommandsModule() int SystemCommandsModule::handleInputEvent(const InputEvent *event) { - LOG_INFO("Input event %u! kb %u", event->inputEvent, event->kbchar); + LOG_INPUT("SystemCommands Input event %u! kb %u", event->inputEvent, event->kbchar); // System commands (all others fall through) switch (event->kbchar) { // Fn key symbols From 0081cec2073614494fb23d9f9dbb3823bacde9b7 Mon Sep 17 00:00:00 2001 From: Jason P Date: Fri, 28 Nov 2025 20:24:39 -0600 Subject: [PATCH 684/691] Fix ifdef statement after ST7796 merge to resolve screen color issues (#8796) --- src/graphics/Screen.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index d58927f1e..0864e5ae1 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -356,13 +356,14 @@ Screen::Screen(ScanI2C::DeviceAddress address, meshtastic_Config_DisplayConfig_O #else dispdev = new ST7789Spi(&SPI1, ST7789_RESET, ST7789_RS, ST7789_NSS, GEOMETRY_RAWMODE, TFT_WIDTH, TFT_HEIGHT); #endif -#elif defined(USE_ST7796) +#if defined(USE_ST7796) #ifdef ESP_PLATFORM dispdev = new ST7796Spi(&SPI1, ST7796_RESET, ST7796_RS, ST7796_NSS, GEOMETRY_RAWMODE, TFT_WIDTH, TFT_HEIGHT, ST7796_SDA, ST7796_MISO, ST7796_SCK, TFT_SPI_FREQUENCY); #else dispdev = new ST7796Spi(&SPI1, ST7796_RESET, ST7796_RS, ST7796_NSS, GEOMETRY_RAWMODE, TFT_WIDTH, TFT_HEIGHT); #endif +#endif #if defined(USE_ST7789) static_cast(dispdev)->setRGB(TFT_MESH); #elif defined(USE_ST7796) From 5ef3ff7116ea6a1de837dcfb577577e9a32375b6 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Sun, 30 Nov 2025 15:33:29 -0600 Subject: [PATCH 685/691] rework screen.cpp ifdefs (#8816) --- src/graphics/Screen.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index 0864e5ae1..aed73deb0 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -356,19 +356,13 @@ Screen::Screen(ScanI2C::DeviceAddress address, meshtastic_Config_DisplayConfig_O #else dispdev = new ST7789Spi(&SPI1, ST7789_RESET, ST7789_RS, ST7789_NSS, GEOMETRY_RAWMODE, TFT_WIDTH, TFT_HEIGHT); #endif -#if defined(USE_ST7796) +#elif defined(USE_ST7796) #ifdef ESP_PLATFORM dispdev = new ST7796Spi(&SPI1, ST7796_RESET, ST7796_RS, ST7796_NSS, GEOMETRY_RAWMODE, TFT_WIDTH, TFT_HEIGHT, ST7796_SDA, ST7796_MISO, ST7796_SCK, TFT_SPI_FREQUENCY); #else dispdev = new ST7796Spi(&SPI1, ST7796_RESET, ST7796_RS, ST7796_NSS, GEOMETRY_RAWMODE, TFT_WIDTH, TFT_HEIGHT); #endif -#endif -#if defined(USE_ST7789) - static_cast(dispdev)->setRGB(TFT_MESH); -#elif defined(USE_ST7796) - static_cast(dispdev)->setRGB(TFT_MESH); -#endif #elif defined(USE_SSD1306) dispdev = new SSD1306Wire(address.address, -1, -1, geometry, (address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE); @@ -411,6 +405,12 @@ Screen::Screen(ScanI2C::DeviceAddress address, meshtastic_Config_DisplayConfig_O isAUTOOled = true; #endif +#if defined(USE_ST7789) + static_cast(dispdev)->setRGB(TFT_MESH); +#elif defined(USE_ST7796) + static_cast(dispdev)->setRGB(TFT_MESH); +#endif + ui = new OLEDDisplayUi(dispdev); cmdQueue.setReader(this); } From 430d55e5e8eaf19a99d8101a38a1d243bb5a5e3f Mon Sep 17 00:00:00 2001 From: Jason P Date: Sun, 30 Nov 2025 17:17:00 -0600 Subject: [PATCH 686/691] Add WiFi Toggle to System frame to re-enable (#8802) Co-authored-by: Jonathan Bennett --- src/graphics/draw/MenuHandler.cpp | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/src/graphics/draw/MenuHandler.cpp b/src/graphics/draw/MenuHandler.cpp index bd647c3d8..e17c7c3d8 100644 --- a/src/graphics/draw/MenuHandler.cpp +++ b/src/graphics/draw/MenuHandler.cpp @@ -576,7 +576,7 @@ void menuHandler::textMessageBaseMenu() void menuHandler::systemBaseMenu() { - enum optionsNumbers { Back, Notifications, ScreenOptions, Bluetooth, PowerMenu, Test, enumEnd }; + enum optionsNumbers { Back, Notifications, ScreenOptions, Bluetooth, WiFiToggle, PowerMenu, Test, enumEnd }; static const char *optionsArray[enumEnd] = {"Back"}; static int optionsEnumArray[enumEnd] = {Back}; int options = 1; @@ -592,6 +592,10 @@ void menuHandler::systemBaseMenu() optionsArray[options] = "Bluetooth Toggle"; #endif optionsEnumArray[options++] = Bluetooth; +#if HAS_WIFI && !defined(ARCH_PORTDUINO) + optionsArray[options] = "WiFi Toggle"; + optionsEnumArray[options++] = WiFiToggle; +#endif #if defined(M5STACK_UNITC6L) optionsArray[options] = "Power"; #else @@ -629,6 +633,11 @@ void menuHandler::systemBaseMenu() } else if (selected == Bluetooth) { menuQueue = bluetooth_toggle_menu; screen->runNow(); +#if HAS_WIFI && !defined(ARCH_PORTDUINO) + } else if (selected == WiFiToggle) { + menuQueue = wifi_toggle_menu; + screen->runNow(); +#endif } else if (selected == Back && !test_enabled) { test_count++; if (test_count > 4) { @@ -1278,19 +1287,28 @@ void menuHandler::wifiBaseMenu() void menuHandler::wifiToggleMenu() { - enum optionsNumbers { Back, Wifi_toggle }; + enum optionsNumbers { Back, Wifi_disable, Wifi_enable }; - static const char *optionsArray[] = {"Back", "Disable"}; + static const char *optionsArray[] = {"Back", "WiFi Disabled", "WiFi Enabled"}; BannerOverlayOptions bannerOptions; - bannerOptions.message = "Disable Wifi and\nEnable Bluetooth?"; + bannerOptions.message = "WiFi Actions"; bannerOptions.optionsArrayPtr = optionsArray; - bannerOptions.optionsCount = 2; + bannerOptions.optionsCount = 3; + if (config.network.wifi_enabled == true) + bannerOptions.InitialSelected = 2; + else + bannerOptions.InitialSelected = 1; bannerOptions.bannerCallback = [](int selected) -> void { - if (selected == Wifi_toggle) { + if (selected == Wifi_disable) { config.network.wifi_enabled = false; config.bluetooth.enabled = true; service->reloadConfig(SEGMENT_CONFIG); rebootAtMsec = (millis() + DEFAULT_REBOOT_SECONDS * 1000); + } else if (selected == Wifi_enable) { + config.network.wifi_enabled = true; + config.bluetooth.enabled = false; + service->reloadConfig(SEGMENT_CONFIG); + rebootAtMsec = (millis() + DEFAULT_REBOOT_SECONDS * 1000); } }; screen->showOverlayBanner(bannerOptions); From 8899487c2f2b29d484c98012d8fa9ea8013e637c Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Sun, 30 Nov 2025 17:18:03 -0600 Subject: [PATCH 687/691] Modify power saving condition for WiFi (#8815) Update preprocessor directive to require both HAS_WIFI and MESHTASTIC_EXCLUDE_WIFI conditions. --- src/PowerFSM.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PowerFSM.cpp b/src/PowerFSM.cpp index 67b680233..9f8097b84 100644 --- a/src/PowerFSM.cpp +++ b/src/PowerFSM.cpp @@ -370,7 +370,7 @@ void PowerFSM_setup() // Don't add power saving transitions if we are a power saving tracker or sensor or have Wifi enabled. Sleep will be initiated // through the modules -#if HAS_WIFI || !defined(MESHTASTIC_EXCLUDE_WIFI) +#if HAS_WIFI && !defined(MESHTASTIC_EXCLUDE_WIFI) bool isTrackerOrSensor = config.device.role == meshtastic_Config_DeviceConfig_Role_TRACKER || config.device.role == meshtastic_Config_DeviceConfig_Role_TAK_TRACKER || config.device.role == meshtastic_Config_DeviceConfig_Role_SENSOR; From 5b1b420cad41559121fd06c49edd673e1dc7c98a Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Sun, 30 Nov 2025 17:21:10 -0600 Subject: [PATCH 688/691] Add initial support for Hackaday Communicator (#8771) * Add initial support for Hackaday Communicator * Fork it! * Trunk * Remove unused elements from the HackadayCommunicatorKeyboard * Don't divide by zero. --- boards/hackaday-communicator.json | 41 ++++ src/graphics/Screen.cpp | 4 +- src/graphics/ScreenFonts.h | 3 +- src/graphics/TFTDisplay.cpp | 44 +++- src/graphics/draw/DebugRenderer.cpp | 9 +- src/graphics/draw/MenuHandler.cpp | 8 +- src/graphics/draw/UIRenderer.cpp | 3 +- src/graphics/images.h | 3 +- src/input/HackadayCommunicatorKeyboard.cpp | 217 ++++++++++++++++++ src/input/HackadayCommunicatorKeyboard.h | 26 +++ src/input/kbI2cBase.cpp | 6 +- src/main.cpp | 11 +- src/mesh/NodeDB.cpp | 3 +- src/platform/esp32/architecture.h | 4 +- .../hackaday-communicator/pins_arduino.h | 59 +++++ .../hackaday-communicator/platformio.ini | 15 ++ .../esp32s3/hackaday-communicator/variant.h | 60 +++++ 17 files changed, 486 insertions(+), 30 deletions(-) create mode 100644 boards/hackaday-communicator.json create mode 100644 src/input/HackadayCommunicatorKeyboard.cpp create mode 100644 src/input/HackadayCommunicatorKeyboard.h create mode 100644 variants/esp32s3/hackaday-communicator/pins_arduino.h create mode 100644 variants/esp32s3/hackaday-communicator/platformio.ini create mode 100644 variants/esp32s3/hackaday-communicator/variant.h diff --git a/boards/hackaday-communicator.json b/boards/hackaday-communicator.json new file mode 100644 index 000000000..6e6c1ad2d --- /dev/null +++ b/boards/hackaday-communicator.json @@ -0,0 +1,41 @@ +{ + "build": { + "arduino": { + "ldscript": "esp32s3_out.ld", + "memory_type": "qio_opi" + }, + "core": "esp32", + "extra_flags": [ + "-DBOARD_HAS_PSRAM", + "-DARDUINO_USB_CDC_ON_BOOT=1", + "-DARDUINO_USB_MODE=0", + "-DARDUINO_RUNNING_CORE=1", + "-DARDUINO_EVENT_RUNNING_CORE=1" + ], + "f_cpu": "240000000L", + "f_flash": "80000000L", + "flash_mode": "qio", + "hwids": [["0x303A", "0x1001"]], + "mcu": "esp32s3", + "variant": "hackaday-communicator" + }, + "connectivity": ["wifi", "bluetooth", "lora"], + "debug": { + "default_tool": "esp-builtin", + "onboard_tools": ["esp-builtin"], + "openocd_target": "esp32s3.cfg" + }, + "frameworks": ["arduino", "espidf"], + "name": "hackaday-communicator (16 MB FLASH, 8 MB PSRAM)", + "upload": { + "flash_size": "16MB", + "maximum_ram_size": 327680, + "maximum_size": 16777216, + "use_1200bps_touch": true, + "wait_for_upload_port": true, + "require_upload_port": true, + "speed": 1500000 + }, + "url": "hackaday.com", + "vendor": "hackaday" +} diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index aed73deb0..351419289 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -375,7 +375,7 @@ Screen::Screen(ScanI2C::DeviceAddress address, meshtastic_Config_DisplayConfig_O LOG_INFO("SSD1306 init success"); } #elif defined(ST7735_CS) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7789_CS) || \ - defined(RAK14014) || defined(HX8357_CS) || defined(ILI9488_CS) || defined(ST7796_CS) + defined(RAK14014) || defined(HX8357_CS) || defined(ILI9488_CS) || defined(ST7796_CS) || defined(HACKADAY_COMMUNICATOR) dispdev = new TFTDisplay(address.address, -1, -1, geometry, (address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE); #elif defined(USE_EINK) && !defined(USE_EINK_DYNAMICDISPLAY) @@ -656,7 +656,7 @@ void Screen::setup() #else if (!config.display.flip_screen) { #if defined(ST7701_CS) || defined(ST7735_CS) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7789_CS) || \ - defined(RAK14014) || defined(HX8357_CS) || defined(ILI9488_CS) || defined(ST7796_CS) + defined(RAK14014) || defined(HX8357_CS) || defined(ILI9488_CS) || defined(ST7796_CS) || defined(HACKADAY_COMMUNICATOR) static_cast(dispdev)->flipScreenVertically(); #elif defined(USE_ST7789) static_cast(dispdev)->flipScreenVertically(); diff --git a/src/graphics/ScreenFonts.h b/src/graphics/ScreenFonts.h index bcb4c4987..d54fc9958 100644 --- a/src/graphics/ScreenFonts.h +++ b/src/graphics/ScreenFonts.h @@ -73,7 +73,8 @@ #endif #if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \ - defined(ST7789_CS) || defined(USE_ST7789) || defined(HX8357_CS) || defined(ILI9488_CS) || defined(ST7796_CS) || defined(USE_ST7796)) && \ + defined(ST7789_CS) || defined(USE_ST7789) || defined(HX8357_CS) || defined(ILI9488_CS) || defined(ST7796_CS) || \ + defined(HACKADAY_COMMUNICATOR) || defined(USE_ST7796)) && \ !defined(DISPLAY_FORCE_SMALL_FONTS) // The screen is bigger so use bigger fonts #define FONT_SMALL FONT_MEDIUM_LOCAL // Height: 19 diff --git a/src/graphics/TFTDisplay.cpp b/src/graphics/TFTDisplay.cpp index 87593b0d4..4445a7c5e 100644 --- a/src/graphics/TFTDisplay.cpp +++ b/src/graphics/TFTDisplay.cpp @@ -123,6 +123,11 @@ static void rak14014_tpIntHandle(void) _rak14014_touch_int = true; } +#elif defined(HACKADAY_COMMUNICATOR) +#include +Arduino_DataBus *bus = nullptr; +Arduino_GFX *tft = nullptr; + #elif defined(ST72xx_DE) #include #include @@ -1135,7 +1140,7 @@ static LGFX *tft = nullptr; #if defined(ST7701_CS) || defined(ST7735_CS) || defined(ST7789_CS) || defined(ST7796_CS) || defined(ILI9341_DRIVER) || \ defined(ILI9342_DRIVER) || defined(RAK14014) || defined(HX8357_CS) || defined(ILI9488_CS) || defined(ST72xx_DE) || \ - (ARCH_PORTDUINO && HAS_SCREEN != 0) + (ARCH_PORTDUINO && HAS_SCREEN != 0) || defined(HACKADAY_COMMUNICATOR) #include "SPILock.h" #include "TFTDisplay.h" #include @@ -1271,12 +1276,15 @@ void TFTDisplay::display(bool fromBlank) x_LastPixelUpdate = x; } } - +#if defined(HACKADAY_COMMUNICATOR) + tft->draw16bitBeRGBBitmap(x_FirstPixelUpdate, y, &linePixelBuffer[x_FirstPixelUpdate], + (x_LastPixelUpdate - x_FirstPixelUpdate + 1), 1); +#else // Step 4: Send the changed pixels on this line to the screen as a single block transfer. // This function accepts pixel data MSB first so it can dump the memory straight out the SPI port. tft->pushRect(x_FirstPixelUpdate, y, (x_LastPixelUpdate - x_FirstPixelUpdate + 1), 1, &linePixelBuffer[x_FirstPixelUpdate]); - +#endif somethingChanged = true; } y++; @@ -1340,6 +1348,8 @@ void TFTDisplay::sendCommand(uint8_t com) display(true); if (portduino_config.displayBacklight.pin > 0) digitalWrite(portduino_config.displayBacklight.pin, TFT_BACKLIGHT_ON); +#elif defined(HACKADAY_COMMUNICATOR) + tft->displayOn(); #elif !defined(RAK14014) && !defined(M5STACK) && !defined(UNPHONE) tft->wakeup(); tft->powerSaveOff(); @@ -1352,7 +1362,8 @@ void TFTDisplay::sendCommand(uint8_t com) unphone.backlight(true); // using unPhone library #endif #ifdef RAK14014 -#elif !defined(M5STACK) && !defined(ST7789_CS) // T-Deck gets brightness set in Screen.cpp in the handleSetOn function +#elif !defined(M5STACK) && !defined(ST7789_CS) && \ + !defined(HACKADAY_COMMUNICATOR) // T-Deck gets brightness set in Screen.cpp in the handleSetOn function tft->setBrightness(172); #endif break; @@ -1364,6 +1375,8 @@ void TFTDisplay::sendCommand(uint8_t com) tft->clear(); if (portduino_config.displayBacklight.pin > 0) digitalWrite(portduino_config.displayBacklight.pin, !TFT_BACKLIGHT_ON); +#elif defined(HACKADAY_COMMUNICATOR) + tft->displayOff(); #elif !defined(RAK14014) && !defined(M5STACK) && !defined(UNPHONE) tft->sleep(); tft->powerSaveOn(); @@ -1376,7 +1389,7 @@ void TFTDisplay::sendCommand(uint8_t com) unphone.backlight(false); // using unPhone library #endif #ifdef RAK14014 -#elif !defined(M5STACK) +#elif !defined(M5STACK) && !defined(HACKADAY_COMMUNICATOR) tft->setBrightness(0); #endif break; @@ -1392,7 +1405,7 @@ void TFTDisplay::setDisplayBrightness(uint8_t _brightness) { #ifdef RAK14014 // todo -#else +#elif !defined(HACKADAY_COMMUNICATOR) tft->setBrightness(_brightness); LOG_DEBUG("Brightness is set to value: %i ", _brightness); #endif @@ -1410,7 +1423,7 @@ bool TFTDisplay::hasTouch(void) { #ifdef RAK14014 return true; -#elif !defined(M5STACK) +#elif !defined(M5STACK) && !defined(HACKADAY_COMMUNICATOR) return tft->touch() != nullptr; #else return false; @@ -1429,7 +1442,7 @@ bool TFTDisplay::getTouch(int16_t *x, int16_t *y) } else { return false; } -#elif !defined(M5STACK) +#elif !defined(M5STACK) && !defined(HACKADAY_COMMUNICATOR) return tft->getTouch(x, y); #else return false; @@ -1448,6 +1461,12 @@ bool TFTDisplay::connect() LOG_INFO("Do TFT init"); #ifdef RAK14014 tft = new TFT_eSPI; +#elif defined(HACKADAY_COMMUNICATOR) + bus = new Arduino_ESP32SPI(TFT_DC, TFT_CS, 38 /* SCK */, 21 /* MOSI */, GFX_NOT_DEFINED /* MISO */, HSPI /* spi_num */); + tft = new Arduino_NV3007(bus, 40, 0 /* rotation */, false /* IPS */, 142 /* width */, 428 /* height */, 12 /* col offset 1 */, + 0 /* row offset 1 */, 14 /* col offset 2 */, 0 /* row offset 2 */, nv3007_279_init_operations, + sizeof(nv3007_279_init_operations)); + #else tft = new LGFX; #endif @@ -1458,8 +1477,15 @@ bool TFTDisplay::connect() #ifdef UNPHONE unphone.backlight(true); // using unPhone library #endif - +#ifdef HACKADAY_COMMUNICATOR + bool beginStatus = tft->begin(); + if (beginStatus) + LOG_DEBUG("TFT Success!"); + else + LOG_ERROR("TFT Fail!"); +#else tft->init(); +#endif #if defined(M5STACK) tft->setRotation(0); diff --git a/src/graphics/draw/DebugRenderer.cpp b/src/graphics/draw/DebugRenderer.cpp index 6bccb1653..1b3a148d6 100644 --- a/src/graphics/draw/DebugRenderer.cpp +++ b/src/graphics/draw/DebugRenderer.cpp @@ -97,8 +97,7 @@ void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16 (storeForwardModule->heartbeatInterval * 1200))) { // no heartbeat, overlap a bit #if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \ defined(ST7789_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(HX8357_CS) || defined(ST7796_CS) || \ - defined(USE_ST7796) || \ - ARCH_PORTDUINO) && \ + defined(HACKADAY_COMMUNICATOR) || defined(USE_ST7796) || ARCH_PORTDUINO) && \ !defined(DISPLAY_FORCE_SMALL_FONTS) display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(screen->ourId), y + 3 + FONT_HEIGHT_SMALL, 12, 8, imgQuestionL1); @@ -110,7 +109,8 @@ void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16 #endif } else { #if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \ - defined(ST7789_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(HX8357_CS) || defined(ST7796_CS) || defined(USE_ST7796)) && \ + defined(ST7789_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(HX8357_CS) || defined(ST7796_CS) || \ + defined(HACKADAY_COMMUNICATOR) || defined(USE_ST7796)) && \ !defined(DISPLAY_FORCE_SMALL_FONTS) display->drawFastImage(x + SCREEN_WIDTH - 18 - display->getStringWidth(screen->ourId), y + 3 + FONT_HEIGHT_SMALL, 16, 8, imgSFL1); @@ -126,8 +126,7 @@ void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16 // TODO: Raspberry Pi supports more than just the one screen size #if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \ defined(ST7789_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(HX8357_CS) || defined(ST7796_CS) || \ - defined(USE_ST7796) || \ - ARCH_PORTDUINO) && \ + defined(HACKADAY_COMMUNICATOR) || defined(USE_ST7796) || ARCH_PORTDUINO) && \ !defined(DISPLAY_FORCE_SMALL_FONTS) display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(screen->ourId), y + 3 + FONT_HEIGHT_SMALL, 12, 8, imgInfoL1); diff --git a/src/graphics/draw/MenuHandler.cpp b/src/graphics/draw/MenuHandler.cpp index e17c7c3d8..bfe3656ce 100644 --- a/src/graphics/draw/MenuHandler.cpp +++ b/src/graphics/draw/MenuHandler.cpp @@ -1047,7 +1047,8 @@ void menuHandler::TFTColorPickerMenu(OLEDDisplay *display) bannerOptions.optionsArrayPtr = optionsArray; bannerOptions.optionsCount = 10; bannerOptions.bannerCallback = [display](int selected) -> void { -#if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) || defined(T_DECK) || defined(T_LORA_PAGER) || HAS_TFT +#if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) || defined(T_DECK) || defined(T_LORA_PAGER) || \ + HAS_TFT || defined(HACKADAY_COMMUNICATOR) uint8_t TFT_MESH_r = 0; uint8_t TFT_MESH_g = 0; uint8_t TFT_MESH_b = 0; @@ -1356,7 +1357,7 @@ void menuHandler::screenOptionsMenu() static int optionsEnumArray[5] = {Back}; int options = 1; -#if defined(T_DECK) || defined(T_LORA_PAGER) +#if defined(T_DECK) || defined(T_LORA_PAGER) || defined(HACKADAY_COMMUNICATOR) optionsArray[options] = "Show Long/Short Name"; optionsEnumArray[options++] = NodeNameLength; #endif @@ -1368,7 +1369,8 @@ void menuHandler::screenOptionsMenu() } // Only show screen color for TFT displays -#if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) || defined(T_DECK) || defined(T_LORA_PAGER) || HAS_TFT +#if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) || defined(T_DECK) || defined(T_LORA_PAGER) || \ + HAS_TFT || defined(HACKADAY_COMMUNICATOR) optionsArray[options] = "Screen Color"; optionsEnumArray[options++] = ScreenColor; #endif diff --git a/src/graphics/draw/UIRenderer.cpp b/src/graphics/draw/UIRenderer.cpp index 3d23acc9f..1f01640bf 100644 --- a/src/graphics/draw/UIRenderer.cpp +++ b/src/graphics/draw/UIRenderer.cpp @@ -257,7 +257,8 @@ void UIRenderer::drawNodes(OLEDDisplay *display, int16_t x, int16_t y, const mes } #if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \ - defined(ST7789_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(HX8357_CS) || defined(ST7796_CS) || defined(USE_ST7796)) && \ + defined(ST7789_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(HX8357_CS) || defined(ST7796_CS) || \ + defined(HACKADAY_COMMUNICATOR) || defined(USE_ST7796)) && \ !defined(DISPLAY_FORCE_SMALL_FONTS) if (isHighResolution) { diff --git a/src/graphics/images.h b/src/graphics/images.h index 998fe8e2a..c268b3269 100644 --- a/src/graphics/images.h +++ b/src/graphics/images.h @@ -27,7 +27,8 @@ const uint8_t bluetoothConnectedIcon[36] PROGMEM = {0xfe, 0x01, 0xff, 0x03, 0x03 0xfe, 0x31, 0x00, 0x30, 0x30, 0x30, 0x30, 0x30, 0xf0, 0x3f, 0xe0, 0x1f}; #if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \ - defined(ST7789_CS) || defined(USE_ST7789) || defined(HX8357_CS) || defined(ILI9488_CS) || defined(USE_ST7796) || defined(ST7796_CS) || ARCH_PORTDUINO) && \ + defined(ST7789_CS) || defined(USE_ST7789) || defined(HX8357_CS) || defined(ILI9488_CS) || defined(ST7796_CS) || \ + defined(USE_ST7796) || defined(HACKADAY_COMMUNICATOR) || ARCH_PORTDUINO) && \ !defined(DISPLAY_FORCE_SMALL_FONTS) const uint8_t imgQuestionL1[] PROGMEM = {0xff, 0x01, 0x01, 0x32, 0x7b, 0x49, 0x49, 0x6f, 0x26, 0x01, 0x01, 0xff}; const uint8_t imgQuestionL2[] PROGMEM = {0x0f, 0x08, 0x08, 0x08, 0x06, 0x0f, 0x0f, 0x06, 0x08, 0x08, 0x08, 0x0f}; diff --git a/src/input/HackadayCommunicatorKeyboard.cpp b/src/input/HackadayCommunicatorKeyboard.cpp new file mode 100644 index 000000000..87c8a24ae --- /dev/null +++ b/src/input/HackadayCommunicatorKeyboard.cpp @@ -0,0 +1,217 @@ +#if defined(HACKADAY_COMMUNICATOR) + +#include "HackadayCommunicatorKeyboard.h" +#include "main.h" + +#define _TCA8418_COLS 10 +#define _TCA8418_ROWS 8 +#define _TCA8418_NUM_KEYS 80 + +#define _TCA8418_MULTI_TAP_THRESHOLD 1500 + +using Key = TCA8418KeyboardBase::TCA8418Key; + +constexpr uint8_t modifierRightShiftKey = 30; +constexpr uint8_t modifierRightShift = 0b0001; +constexpr uint8_t modifierLeftShiftKey = 76; // keynum -1 +constexpr uint8_t modifierLeftShift = 0b0001; +// constexpr uint8_t modifierSymKey = 42; +// constexpr uint8_t modifierSym = 0b0010; + +// Num chars per key, Modulus for rotating through characters +static uint8_t HackadayCommunicatorTapMod[_TCA8418_NUM_KEYS] = { + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 2, 2, 2, 2, 2, 2, 0, 0, 0, 1, 2, 2, 2, 1, 2, 2, 0, 0, 0, 2, 1, 2, 2, 0, 1, 1, 0, +}; + +static unsigned char HackadayCommunicatorTapMap[_TCA8418_NUM_KEYS][2] = {{}, + {}, + {'+'}, + {'9'}, + {'8'}, + {'7'}, + {'2'}, + {'3'}, + {'4'}, + {'5'}, + {Key::ESC}, + {'q', 'Q'}, + {'w', 'W'}, + {'e', 'E'}, + {'r', 'R'}, + {'t', 'T'}, + {'y', 'Y'}, + {'u', 'U'}, + {'i', 'I'}, + {'o', 'O'}, + {Key::TAB}, + {'a', 'A'}, + {'s', 'S'}, + {'d', 'D'}, + {'f', 'F'}, + {'g', 'G'}, + {'h', 'H'}, + {'j', 'J'}, + {'k', 'K'}, + {'l', 'L'}, + {}, + {'z', 'Z'}, + {'x', 'X'}, + {'c', 'C'}, + {'v', 'V'}, + {'b', 'B'}, + {'n', 'N'}, + {'m', 'M'}, + {',', '<'}, + {'.', '>'}, + {}, + {}, + {}, + {'\\'}, + {' '}, + {}, + {Key::RIGHT}, + {Key::DOWN}, + {Key::LEFT}, + {}, + {}, + {}, + {'-'}, + {'6', '^'}, + {'5', '%'}, + {'4', '$'}, + {'[', '{'}, + {']', '}'}, + {'p', 'P'}, + {}, + {}, + {}, + {'*'}, + {'3', '#'}, + {'2', '@'}, + {'1', '!'}, + {Key::SELECT}, + {'\'', '"'}, + {';', ':'}, + {}, + {}, + {}, + {'/', '?'}, + {'='}, + {'.', '>'}, + {'0', ')'}, + {}, + {Key::UP}, + {Key::BSP}, + {}}; + +HackadayCommunicatorKeyboard::HackadayCommunicatorKeyboard() + : TCA8418KeyboardBase(_TCA8418_ROWS, _TCA8418_COLS), modifierFlag(0), last_modifier_time(0), last_key(-1), next_key(-1), + last_tap(0L), char_idx(0), tap_interval(0) +{ + reset(); +} + +void HackadayCommunicatorKeyboard::reset(void) +{ + TCA8418KeyboardBase::reset(); + enableInterrupts(); +} + +// handle multi-key presses (shift and alt) +void HackadayCommunicatorKeyboard::trigger() +{ + uint8_t count = keyCount(); + if (count == 0) + return; + for (uint8_t i = 0; i < count; ++i) { + uint8_t k = readRegister(TCA8418_REG_KEY_EVENT_A + i); + uint8_t key = k & 0x7F; + if (k & 0x80) { + pressed(key); + } else { + released(); + state = Idle; + } + } +} + +void HackadayCommunicatorKeyboard::pressed(uint8_t key) +{ + if (state == Init || state == Busy) { + return; + } + + if (modifierFlag && (millis() - last_modifier_time > _TCA8418_MULTI_TAP_THRESHOLD)) { + modifierFlag = 0; + } + + uint8_t next_key = 0; + int row = (key - 1) / 10; + int col = (key - 1) % 10; + if (row >= _TCA8418_ROWS || col >= _TCA8418_COLS) { + return; // Invalid key + } + + next_key = row * _TCA8418_COLS + col; + state = Held; + + uint32_t now = millis(); + tap_interval = now - last_tap; + + updateModifierFlag(next_key); + if (isModifierKey(next_key)) { + last_modifier_time = now; + } + + if (tap_interval < 0) { + last_tap = 0; + state = Busy; + return; + } + + if (next_key != last_key || tap_interval > _TCA8418_MULTI_TAP_THRESHOLD) { + char_idx = 0; + } else { + char_idx += 1; + } + + last_key = next_key; + last_tap = now; +} + +void HackadayCommunicatorKeyboard::released() +{ + if (state != Held) { + return; + } + + if (last_key < 0 || last_key >= _TCA8418_NUM_KEYS) { + last_key = -1; + state = Idle; + return; + } + + uint32_t now = millis(); + last_tap = now; + if (HackadayCommunicatorTapMod[last_key]) + queueEvent(HackadayCommunicatorTapMap[last_key][modifierFlag % HackadayCommunicatorTapMod[last_key]]); + if (isModifierKey(last_key) == false) + modifierFlag = 0; +} + +void HackadayCommunicatorKeyboard::updateModifierFlag(uint8_t key) +{ + if (key == modifierRightShiftKey) { + modifierFlag ^= modifierRightShift; + } else if (key == modifierLeftShiftKey) { + modifierFlag ^= modifierLeftShift; + } +} + +bool HackadayCommunicatorKeyboard::isModifierKey(uint8_t key) +{ + return (key == modifierRightShiftKey || key == modifierLeftShiftKey); +} + +#endif \ No newline at end of file diff --git a/src/input/HackadayCommunicatorKeyboard.h b/src/input/HackadayCommunicatorKeyboard.h new file mode 100644 index 000000000..8316bed72 --- /dev/null +++ b/src/input/HackadayCommunicatorKeyboard.h @@ -0,0 +1,26 @@ +#include "TCA8418KeyboardBase.h" + +class HackadayCommunicatorKeyboard : public TCA8418KeyboardBase +{ + public: + HackadayCommunicatorKeyboard(); + void reset(void); + void trigger(void) override; + virtual ~HackadayCommunicatorKeyboard() {} + + protected: + void pressed(uint8_t key) override; + void released(void) override; + + void updateModifierFlag(uint8_t key); + bool isModifierKey(uint8_t key); + + private: + uint8_t modifierFlag; // Flag to indicate if a modifier key is pressed + uint32_t last_modifier_time; // Timestamp of the last modifier key press + int8_t last_key; + int8_t next_key; + uint32_t last_tap; + uint8_t char_idx; + int32_t tap_interval; +}; diff --git a/src/input/kbI2cBase.cpp b/src/input/kbI2cBase.cpp index 0ed2df116..0085c806b 100644 --- a/src/input/kbI2cBase.cpp +++ b/src/input/kbI2cBase.cpp @@ -7,6 +7,8 @@ #include "TDeckProKeyboard.h" #elif defined(T_LORA_PAGER) #include "TLoraPagerKeyboard.h" +#elif defined(HACKADAY_COMMUNICATOR) +#include "HackadayCommunicatorKeyboard.h" #else #include "TCA8418Keyboard.h" #endif @@ -20,6 +22,8 @@ KbI2cBase::KbI2cBase(const char *name) TCAKeyboard(*(new TDeckProKeyboard())) #elif defined(T_LORA_PAGER) TCAKeyboard(*(new TLoraPagerKeyboard())) +#elif defined(HACKADAY_COMMUNICATOR) + TCAKeyboard(*(new HackadayCommunicatorKeyboard())) #else TCAKeyboard(*(new TCA8418Keyboard())) #endif @@ -328,7 +332,7 @@ int32_t KbI2cBase::runOnce() break; } if (e.inputEvent != INPUT_BROKER_NONE) { - LOG_DEBUG("TCA8418 Notifying: %i Char: %c", e.inputEvent, e.kbchar); + // LOG_DEBUG("TCA8418 Notifying: %i Char: %c", e.inputEvent, e.kbchar); this->notifyObservers(&e); } TCAKeyboard.trigger(); diff --git a/src/main.cpp b/src/main.cpp index da2e39604..f8d89e1ba 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -394,7 +394,10 @@ void setup() io.pinMode(EXPANDS_GPIO_EN, OUTPUT); io.digitalWrite(EXPANDS_GPIO_EN, HIGH); io.pinMode(EXPANDS_SD_PULLEN, INPUT); +#elif defined(HACKADAY_COMMUNICATOR) + pinMode(KB_INT, INPUT); #endif + concurrency::hasBeenSetup = true; #if ARCH_PORTDUINO SPISettings spiSettings(portduino_config.spiSpeed, MSBFIRST, SPI_MODE0); @@ -877,8 +880,8 @@ void setup() if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) { #if defined(ST7701_CS) || defined(ST7735_CS) || defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || \ - defined(ST7789_CS) || defined(HX8357_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(ST7796_CS) || defined(USE_ST7796) || \ - defined(USE_SPISSD1306) + defined(ST7789_CS) || defined(HX8357_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(ST7796_CS) || \ + defined(USE_SPISSD1306) || defined(USE_ST7796) || defined(HACKADAY_COMMUNICATOR) screen = new graphics::Screen(screen_found, screen_model, screen_geometry); #elif defined(ARCH_PORTDUINO) if ((screen_found.port != ScanI2C::I2CPort::NO_I2C || portduino_config.displayPanel) && @@ -1154,8 +1157,8 @@ void setup() // Don't call screen setup until after nodedb is setup (because we need // the current region name) #if defined(ST7701_CS) || defined(ST7735_CS) || defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || \ - defined(ST7789_CS) || defined(HX8357_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(ST7796_CS) || defined(USE_ST7796) || \ - defined(USE_SPISSD1306) + defined(ST7789_CS) || defined(HX8357_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(ST7796_CS) || \ + defined(USE_ST7796) || defined(USE_SPISSD1306) || defined(HACKADAY_COMMUNICATOR) if (screen) screen->setup(); #elif defined(ARCH_PORTDUINO) diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 4e99a22ef..d3000c500 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -664,7 +664,8 @@ void NodeDB::installDefaultConfig(bool preserveKey = false) config.bluetooth.fixed_pin = defaultBLEPin; #if defined(ST7735_CS) || defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7789_CS) || \ - defined(HX8357_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(ST7796_CS) || defined(USE_SPISSD1306) || defined(USE_ST7796) + defined(HX8357_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(ST7796_CS) || defined(USE_SPISSD1306) || \ + defined(USE_ST7796) || defined(HACKADAY_COMMUNICATOR) bool hasScreen = true; #ifdef HELTEC_MESH_NODE_T114 uint32_t st7789_id = get_st7789_id(ST7789_NSS, ST7789_SCK, ST7789_SDA, ST7789_RS, ST7789_RESET); diff --git a/src/platform/esp32/architecture.h b/src/platform/esp32/architecture.h index 9b5abfba0..085692f96 100644 --- a/src/platform/esp32/architecture.h +++ b/src/platform/esp32/architecture.h @@ -101,8 +101,6 @@ #define HW_VENDOR meshtastic_HardwareModel_T_WATCH_S3 #elif defined(GENIEBLOCKS) #define HW_VENDOR meshtastic_HardwareModel_GENIEBLOCKS -#elif defined(PRIVATE_HW) -#define HW_VENDOR meshtastic_HardwareModel_PRIVATE_HW #elif defined(NANO_G1) #define HW_VENDOR meshtastic_HardwareModel_NANO_G1 #elif defined(M5STACK) @@ -205,6 +203,8 @@ #define HW_VENDOR meshtastic_HardwareModel_M5STACK_C6L #elif defined(HELTEC_WIRELESS_TRACKER_V2) #define HW_VENDOR meshtastic_HardwareModel_HELTEC_WIRELESS_TRACKER_V2 +#else +#define HW_VENDOR meshtastic_HardwareModel_PRIVATE_HW #endif // ----------------------------------------------------------------------------- diff --git a/variants/esp32s3/hackaday-communicator/pins_arduino.h b/variants/esp32s3/hackaday-communicator/pins_arduino.h new file mode 100644 index 000000000..65d4e1751 --- /dev/null +++ b/variants/esp32s3/hackaday-communicator/pins_arduino.h @@ -0,0 +1,59 @@ +#ifndef Pins_Arduino_h +#define Pins_Arduino_h + +#include + +#define USB_VID 0x303a +#define USB_PID 0x1001 + +// static const uint8_t TX = 43; +// static const uint8_t RX = 44; + +static const uint8_t SDA = 47; +static const uint8_t SCL = 14; + +// Default SPI will be mapped to Radio +static const uint8_t SS = 17; +static const uint8_t MOSI = 3; +static const uint8_t MISO = 9; +static const uint8_t SCK = 8; + +static const uint8_t A0 = 1; +static const uint8_t A1 = 2; +static const uint8_t A2 = 3; +static const uint8_t A3 = 4; +static const uint8_t A4 = 5; +static const uint8_t A5 = 6; +static const uint8_t A6 = 7; +static const uint8_t A7 = 8; +static const uint8_t A8 = 9; +static const uint8_t A9 = 10; +static const uint8_t A10 = 11; +static const uint8_t A11 = 12; +static const uint8_t A12 = 13; +static const uint8_t A13 = 14; +static const uint8_t A14 = 15; +static const uint8_t A15 = 16; +static const uint8_t A16 = 17; +static const uint8_t A17 = 18; +static const uint8_t A18 = 19; +static const uint8_t A19 = 20; + +static const uint8_t T1 = 1; +static const uint8_t T2 = 2; +static const uint8_t T3 = 3; +static const uint8_t T4 = 4; +static const uint8_t T5 = 5; +static const uint8_t T6 = 6; +static const uint8_t T7 = 7; +static const uint8_t T8 = 8; +static const uint8_t T9 = 9; +static const uint8_t T10 = 10; +static const uint8_t T11 = 11; +static const uint8_t T12 = 12; +static const uint8_t T13 = 13; +static const uint8_t T14 = 14; + +// static const uint8_t BAT_ADC_PIN = 4; + +#endif /* Pins_Arduino_h */ diff --git a/variants/esp32s3/hackaday-communicator/platformio.ini b/variants/esp32s3/hackaday-communicator/platformio.ini new file mode 100644 index 000000000..970215045 --- /dev/null +++ b/variants/esp32s3/hackaday-communicator/platformio.ini @@ -0,0 +1,15 @@ +; Hackaday Communicator +[env:hackaday-communicator] +extends = esp32s3_base +board = hackaday-communicator +board_check = true +board_build.partitions = default_16MB.csv +upload_protocol = esptool + +build_flags = ${esp32s3_base.build_flags} + -D HACKADAY_COMMUNICATOR + -D BOARD_HAS_PSRAM + -I variants/esp32s3/hackaday-communicator + +lib_deps = ${esp32s3_base.lib_deps} + https://github.com/meshtastic/Arduino_GFX/archive/054e81ffaf23784830a734e3c184346789349406.zip \ No newline at end of file diff --git a/variants/esp32s3/hackaday-communicator/variant.h b/variants/esp32s3/hackaday-communicator/variant.h new file mode 100644 index 000000000..ccd9d3edb --- /dev/null +++ b/variants/esp32s3/hackaday-communicator/variant.h @@ -0,0 +1,60 @@ +#define TFT_BL 2 +#define SPI_FREQUENCY 2000000 +#define SPI_READ_FREQUENCY 16000000 +#define TFT_HEIGHT 142 +#define TFT_WIDTH 428 +#define TFT_OFFSET_X 0 +#define TFT_OFFSET_Y 0 +#define TFT_OFFSET_ROTATION 0 +#define SCREEN_TRANSITION_FRAMERATE 5 +#define HAS_SCREEN 1 +#define TFT_BLACK 0 +#define BRIGHTNESS_DEFAULT 130 // Medium Low Brightness + +#define USE_POWERSAVE +#define SLEEP_TIME 120 + +#define GPS_DEFAULT_NOT_PRESENT 1 +// #define GPS_RX_PIN 44 +// #define GPS_TX_PIN 43 + +// #define BATTERY_PIN 4 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage +// ratio of voltage divider = 2.0 (RD2=100k, RD3=100k) +// #define ADC_MULTIPLIER 2.11 // 2.0 + 10% for correction of display undervoltage. +// #define ADC_CHANNEL ADC1_GPIO4_CHANNEL + +// keyboard +#define I2C_SDA 47 // I2C pins for this board +#define I2C_SCL 14 +// #define KB_POWERON -1 // must be set to HIGH +// #define KB_SLAVE_ADDRESS TDECK_KB_ADDR // 0x55 +// #define KB_BL_PIN 46 // not used for now +#define KB_INT 13 +#define CANNED_MESSAGE_MODULE_ENABLE 1 + +#define TFT_DC 39 +#define TFT_CS 41 + +// LoRa +#define USE_SX1262 + +#define LORA_SCK 8 +#define LORA_MISO 9 +#define LORA_MOSI 3 +#define LORA_CS 17 + +// #define LORA_DIO0 -1 // a No connect on the SX1262 module +#define LORA_RESET 18 +#define LORA_DIO1 16 // SX1262 IRQ +#define LORA_DIO2 15 // SX1262 BUSY +// #define LORA_DIO3 // Not connected on PCB, but internally on the TTGO SX1262, if DIO3 is high the TXCO is enabled + +#define SX126X_CS LORA_CS +#define SX126X_DIO1 LORA_DIO1 +#define SX126X_BUSY LORA_DIO2 +#define SX126X_RESET LORA_RESET + +#define SX126X_DIO2_AS_RF_SWITCH +#define SX126X_DIO3_TCXO_VOLTAGE 1.8 + +// #define LED_PIN 1 \ No newline at end of file From 09bbfce625f1ded9663618d282916bcf0936cf71 Mon Sep 17 00:00:00 2001 From: Riker Date: Mon, 1 Dec 2025 10:27:45 +0800 Subject: [PATCH 689/691] Enabled MQTT and WEBSERVER by default (#8679) Signed-off-by: kur1k0 Co-authored-by: Ben Meadors Co-authored-by: Jonathan Bennett --- variants/esp32c6/m5stack_unitc6l/platformio.ini | 2 -- 1 file changed, 2 deletions(-) diff --git a/variants/esp32c6/m5stack_unitc6l/platformio.ini b/variants/esp32c6/m5stack_unitc6l/platformio.ini index da1c70c0a..9992ab2bf 100644 --- a/variants/esp32c6/m5stack_unitc6l/platformio.ini +++ b/variants/esp32c6/m5stack_unitc6l/platformio.ini @@ -22,8 +22,6 @@ build_flags = -DARDUINO_USB_CDC_ON_BOOT=1 -DARDUINO_USB_MODE=1 -D HAS_BLUETOOTH=1 - -D MESHTASTIC_EXCLUDE_WEBSERVER - -D MESHTASTIC_EXCLUDE_MQTT -DCONFIG_BT_NIMBLE_EXT_ADV=1 -DCONFIG_BT_NIMBLE_MAX_EXT_ADV_INSTANCES=2 -D NIMBLE_TWO From 34f8300288b06348b8358dff4376f75a0ce1b3cb Mon Sep 17 00:00:00 2001 From: HarukiToreda <116696711+HarukiToreda@users.noreply.github.com> Date: Sun, 30 Nov 2025 22:32:51 -0500 Subject: [PATCH 690/691] Initial Chatter 2.0 fix for baseUI (#8615) * Initial Chatter 2.0 fix for baseUI * trunk fix --------- Co-authored-by: Jason P --- src/graphics/SharedUIDisplay.cpp | 11 +-- src/input/SerialKeyboard.cpp | 20 ++++- src/input/SerialKeyboard.h | 6 +- src/modules/CannedMessageModule.cpp | 84 ++++++++++++++++++- variants/esp32/chatter2/variant.h | 1 + .../heltec_wireless_tracker_V1_0/variant.h | 1 + 6 files changed, 114 insertions(+), 9 deletions(-) diff --git a/src/graphics/SharedUIDisplay.cpp b/src/graphics/SharedUIDisplay.cpp index 1645789a7..892285dcb 100644 --- a/src/graphics/SharedUIDisplay.cpp +++ b/src/graphics/SharedUIDisplay.cpp @@ -17,6 +17,12 @@ namespace graphics void determineResolution(int16_t screenheight, int16_t screenwidth) { + +#ifdef FORCE_LOW_RES + isHighResolution = false; + return; +#endif + if (screenwidth > 128) { isHighResolution = true; } @@ -24,11 +30,6 @@ void determineResolution(int16_t screenheight, int16_t screenwidth) if (screenwidth > 128 && screenheight <= 64) { isHighResolution = false; } - - // Special case for Heltec Wireless Tracker v1.1 - if (screenwidth == 160 && screenheight == 80) { - isHighResolution = false; - } } // === Shared External State === diff --git a/src/input/SerialKeyboard.cpp b/src/input/SerialKeyboard.cpp index 2df1ace70..a5d2c614f 100644 --- a/src/input/SerialKeyboard.cpp +++ b/src/input/SerialKeyboard.cpp @@ -2,6 +2,8 @@ #include "configuration.h" #include +SerialKeyboard *globalSerialKeyboard = nullptr; + #ifdef INPUTBROKER_SERIAL_TYPE #define CANNED_MESSAGE_MODULE_ENABLE 1 // in case it's not set in the variant file @@ -25,6 +27,8 @@ unsigned char KeyMap[3][4][10] = {{{'.', 'a', 'd', 'g', 'j', 'm', 'p', 't', 'w', SerialKeyboard::SerialKeyboard(const char *name) : concurrency::OSThread(name) { this->_originName = name; + + globalSerialKeyboard = this; } void SerialKeyboard::erase() @@ -85,9 +89,21 @@ int32_t SerialKeyboard::runOnce() e.source = this->_originName; // SELECT OR SEND OR CANCEL EVENT if (!(shiftRegister2 & (1 << 3))) { - e.inputEvent = INPUT_BROKER_UP; + if (shift > 0) { + e.inputEvent = INPUT_BROKER_ANYKEY; // REQUIRED + e.kbchar = 0x09; // TAB + shift = 0; // reset shift after TAB + } else { + e.inputEvent = INPUT_BROKER_LEFT; + } } else if (!(shiftRegister2 & (1 << 2))) { - e.inputEvent = INPUT_BROKER_RIGHT; + if (shift > 0) { + e.inputEvent = INPUT_BROKER_ANYKEY; // REQUIRED + e.kbchar = 0x09; // TAB + shift = 0; // reset shift after TAB + } else { + e.inputEvent = INPUT_BROKER_RIGHT; + } e.kbchar = 0; } else if (!(shiftRegister2 & (1 << 1))) { e.inputEvent = INPUT_BROKER_SELECT; diff --git a/src/input/SerialKeyboard.h b/src/input/SerialKeyboard.h index 1480c4d58..f25eb2630 100644 --- a/src/input/SerialKeyboard.h +++ b/src/input/SerialKeyboard.h @@ -8,6 +8,8 @@ class SerialKeyboard : public Observable, public concurrency public: explicit SerialKeyboard(const char *name); + uint8_t getShift() const { return shift; } + protected: virtual int32_t runOnce() override; void erase(); @@ -22,4 +24,6 @@ class SerialKeyboard : public Observable, public concurrency int lastKeyPressed = 13; int quickPress = 0; unsigned long lastPressTime = 0; -}; \ No newline at end of file +}; + +extern SerialKeyboard *globalSerialKeyboard; \ No newline at end of file diff --git a/src/modules/CannedMessageModule.cpp b/src/modules/CannedMessageModule.cpp index 9cbacc877..9433c0a9e 100644 --- a/src/modules/CannedMessageModule.cpp +++ b/src/modules/CannedMessageModule.cpp @@ -16,6 +16,7 @@ #include "graphics/draw/NotificationRenderer.h" #include "graphics/emotes.h" #include "graphics/images.h" +#include "input/SerialKeyboard.h" #include "main.h" // for cardkb_found #include "mesh/generated/meshtastic/cannedmessages.pb.h" #include "modules/AdminModule.h" @@ -1848,7 +1849,88 @@ void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *st display->drawString(x + display->getWidth() - display->getStringWidth(buffer), y + 0, buffer); } - // --- Draw Free Text input with multi-emote support and proper line wrapping --- +#if INPUTBROKER_SERIAL_TYPE == 1 + // Chatter Modifier key mode label (right side) + { + uint8_t mode = globalSerialKeyboard ? globalSerialKeyboard->getShift() : 0; + const char *label = (mode == 0) ? "a" : (mode == 1) ? "A" : "#"; + + display->setFont(FONT_SMALL); + display->setTextAlignment(TEXT_ALIGN_LEFT); + + const int16_t th = FONT_HEIGHT_SMALL; + const int16_t tw = display->getStringWidth(label); + const int16_t padX = 3; + const int16_t padY = 2; + const int16_t r = 3; + + const int16_t bw = tw + padX * 2; + const int16_t bh = th + padY * 2; + + const int16_t bx = x + display->getWidth() - bw - 2; + const int16_t by = y + display->getHeight() - bh - 2; + + display->setColor(WHITE); + display->fillRect(bx + r, by, bw - r * 2, bh); + display->fillRect(bx, by + r, r, bh - r * 2); + display->fillRect(bx + bw - r, by + r, r, bh - r * 2); + display->fillCircle(bx + r, by + r, r); + display->fillCircle(bx + bw - r - 1, by + r, r); + display->fillCircle(bx + r, by + bh - r - 1, r); + display->fillCircle(bx + bw - r - 1, by + bh - r - 1, r); + + display->setColor(BLACK); + display->drawString(bx + padX, by + padY, label); + } + + // LEFT-SIDE DESTINATION-HINT BOX (“Dest: Shift + ◄”) + { + display->setFont(FONT_SMALL); + display->setTextAlignment(TEXT_ALIGN_LEFT); + + const char *label = "Dest: Shift + "; + int16_t labelW = display->getStringWidth(label); + + // triangle size visually matches glyph height, not full line height + const int triH = FONT_HEIGHT_SMALL - 3; + const int triW = triH * 0.7; + + const int16_t padX = 3; + const int16_t padY = 2; + const int16_t r = 3; + + const int16_t bw = labelW + triW + padX * 2 + 2; + const int16_t bh = FONT_HEIGHT_SMALL + padY * 2; + + const int16_t bx = x + 2; + const int16_t by = y + display->getHeight() - bh - 2; + + // Rounded white box + display->setColor(WHITE); + display->fillRect(bx + r, by, bw - (r * 2), bh); + display->fillRect(bx, by + r, r, bh - (r * 2)); + display->fillRect(bx + bw - r, by + r, r, bh - (r * 2)); + display->fillCircle(bx + r, by + r, r); + display->fillCircle(bx + bw - r - 1, by + r, r); + display->fillCircle(bx + r, by + bh - r - 1, r); + display->fillCircle(bx + bw - r - 1, by + bh - r - 1, r); + + // Draw text + display->setColor(BLACK); + display->drawString(bx + padX, by + padY, label); + + // Perfectly center triangle on text baseline + int16_t tx = bx + padX + labelW; + int16_t ty = by + padY + (FONT_HEIGHT_SMALL / 2) - (triH / 2) - 1; // -1 for optical centering + + // ◄ Left-pointing triangle + display->fillTriangle(tx + triW, ty, // top-right + tx, ty + triH / 2, // left center + tx + triW, ty + triH // bottom-right + ); + } +#endif + // Draw Free Text input with multi-emote support and proper line wrapping display->setColor(WHITE); { int inputY = 0 + y + FONT_HEIGHT_SMALL; diff --git a/variants/esp32/chatter2/variant.h b/variants/esp32/chatter2/variant.h index ff4f87bbe..b3e06de48 100644 --- a/variants/esp32/chatter2/variant.h +++ b/variants/esp32/chatter2/variant.h @@ -62,6 +62,7 @@ #define TFT_OFFSET_X 0 #define TFT_OFFSET_Y 0 #define TFT_INVERT false +#define FORCE_LOW_RES 1 #define SCREEN_ROTATE #define SCREEN_TRANSITION_FRAMERATE 5 // fps #define DISPLAY_FORCE_SMALL_FONTS diff --git a/variants/esp32s3/heltec_wireless_tracker_V1_0/variant.h b/variants/esp32s3/heltec_wireless_tracker_V1_0/variant.h index 876ff1146..cd76bb604 100644 --- a/variants/esp32s3/heltec_wireless_tracker_V1_0/variant.h +++ b/variants/esp32s3/heltec_wireless_tracker_V1_0/variant.h @@ -27,6 +27,7 @@ #define VTFT_CTRL 46 // Heltec Tracker needs this pulled low for TFT #define SCREEN_TRANSITION_FRAMERATE 3 // fps #define DISPLAY_FORCE_SMALL_FONTS +#define FORCE_LOW_RES 1 #define VEXT_ENABLE Vext // active low, powers the oled display and the lora antenna boost #define VEXT_ON_VALUE LOW From ee6c9101c70c84c7fe26c89cc1000c21afc5fb54 Mon Sep 17 00:00:00 2001 From: Chloe Bethel Date: Mon, 1 Dec 2025 03:57:25 +0000 Subject: [PATCH 691/691] Make GPS_TX_PIN the serial TX and GPS_RX_PIN the serial RX for all NRF variants (#8772) --- variants/nrf52840/ELECROW-ThinkNode-M1/variant.h | 8 ++++---- variants/nrf52840/ELECROW-ThinkNode-M3/variant.h | 8 ++++---- variants/nrf52840/ELECROW-ThinkNode-M6/variant.h | 8 ++++---- variants/nrf52840/canaryone/variant.h | 8 ++++---- variants/nrf52840/diy/nrf52_promicro_diy_tcxo/variant.h | 8 ++++---- variants/nrf52840/meshlink/variant.h | 4 ++-- variants/nrf52840/meshlink_eink/variant.h | 4 ++-- variants/nrf52840/nano-g2-ultra/variant.h | 8 ++++---- variants/nrf52840/seeed_solar_node/variant.h | 8 ++++---- variants/nrf52840/seeed_xiao_nrf52840_kit/variant.h | 8 ++++---- 10 files changed, 36 insertions(+), 36 deletions(-) diff --git a/variants/nrf52840/ELECROW-ThinkNode-M1/variant.h b/variants/nrf52840/ELECROW-ThinkNode-M1/variant.h index 79e31c54a..b8cd8da63 100644 --- a/variants/nrf52840/ELECROW-ThinkNode-M1/variant.h +++ b/variants/nrf52840/ELECROW-ThinkNode-M1/variant.h @@ -157,15 +157,15 @@ External serial flash WP25R1635FZUIL0 #define PIN_GPS_STANDBY (32 + 2) // An output to wake GPS, low means allow sleep, high means force wake // Seems to be missing on this new board // #define PIN_GPS_PPS (32 + 4) // Pulse per second input from the GPS -#define GPS_TX_PIN (32 + 9) // This is for bits going TOWARDS the CPU -#define GPS_RX_PIN (32 + 8) // This is for bits going TOWARDS the GPS +#define GPS_TX_PIN (32 + 8) // This is for bits going TOWARDS the GPS +#define GPS_RX_PIN (32 + 9) // This is for bits going TOWARDS the CPU #define GPS_THREAD_INTERVAL 50 #define PIN_GPS_SWITCH (32 + 1) // GPS开关判断 -#define PIN_SERIAL1_RX GPS_TX_PIN -#define PIN_SERIAL1_TX GPS_RX_PIN +#define PIN_SERIAL1_TX GPS_TX_PIN +#define PIN_SERIAL1_RX GPS_RX_PIN // PCF8563 RTC Module #define PCF8563_RTC 0x51 diff --git a/variants/nrf52840/ELECROW-ThinkNode-M3/variant.h b/variants/nrf52840/ELECROW-ThinkNode-M3/variant.h index cf940172b..2ad3efa27 100644 --- a/variants/nrf52840/ELECROW-ThinkNode-M3/variant.h +++ b/variants/nrf52840/ELECROW-ThinkNode-M3/variant.h @@ -78,11 +78,11 @@ extern "C" { #define GPS_BAUDRATE 9600 #define PIN_GPS_RESET 25 #define PIN_GPS_STANDBY 21 -#define GPS_TX_PIN 20 -#define GPS_RX_PIN 22 +#define GPS_TX_PIN 22 +#define GPS_RX_PIN 20 #define GPS_THREAD_INTERVAL 50 -#define PIN_SERIAL1_RX GPS_TX_PIN -#define PIN_SERIAL1_TX GPS_RX_PIN +#define PIN_SERIAL1_TX GPS_TX_PIN +#define PIN_SERIAL1_RX GPS_RX_PIN // Button #define BUTTON_PIN 12 #define BUTTON_PIN_ALT (0 + 12) diff --git a/variants/nrf52840/ELECROW-ThinkNode-M6/variant.h b/variants/nrf52840/ELECROW-ThinkNode-M6/variant.h index 5e543b21f..d30b88d66 100644 --- a/variants/nrf52840/ELECROW-ThinkNode-M6/variant.h +++ b/variants/nrf52840/ELECROW-ThinkNode-M6/variant.h @@ -107,12 +107,12 @@ static const uint8_t A0 = PIN_A0; #define PIN_GPS_REINIT (29) #define PIN_GPS_STANDBY (30) #define PIN_GPS_PPS (31) -#define GPS_TX_PIN (3) -#define GPS_RX_PIN (2) +#define GPS_TX_PIN (2) +#define GPS_RX_PIN (3) #define GPS_THREAD_INTERVAL 50 -#define PIN_SERIAL1_RX GPS_TX_PIN -#define PIN_SERIAL1_TX GPS_RX_PIN +#define PIN_SERIAL1_TX GPS_TX_PIN +#define PIN_SERIAL1_RX GPS_RX_PIN // Secondary UART #define PIN_SERIAL2_RX (22) diff --git a/variants/nrf52840/canaryone/variant.h b/variants/nrf52840/canaryone/variant.h index 836fa74a3..204ca6306 100644 --- a/variants/nrf52840/canaryone/variant.h +++ b/variants/nrf52840/canaryone/variant.h @@ -128,13 +128,13 @@ static const uint8_t A0 = PIN_A0; // #define PIN_GPS_WAKE (GPIO_PORT1 + 2) // An output to wake GPS, low means allow sleep, high means force wake // Seems to be missing on this new board #define PIN_GPS_PPS (GPIO_PORT1 + 4) // Pulse per second input from the GPS -#define GPS_TX_PIN (GPIO_PORT1 + 9) // This is for bits going TOWARDS the CPU -#define GPS_RX_PIN (GPIO_PORT1 + 8) // This is for bits going TOWARDS the GPS +#define GPS_TX_PIN (GPIO_PORT1 + 8) // This is for bits going TOWARDS the GPS +#define GPS_RX_PIN (GPIO_PORT1 + 9) // This is for bits going TOWARDS the CPU #define GPS_THREAD_INTERVAL 50 -#define PIN_SERIAL1_RX GPS_TX_PIN -#define PIN_SERIAL1_TX GPS_RX_PIN +#define PIN_SERIAL1_RX GPS_RX_PIN +#define PIN_SERIAL1_TX GPS_TX_PIN #define GPS_RESET_PIN (GPIO_PORT1 + 5) // GPS reset pin diff --git a/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/variant.h b/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/variant.h index fee8ee88e..b52d0e57e 100644 --- a/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/variant.h +++ b/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/variant.h @@ -90,16 +90,16 @@ NRF52 PRO MICRO PIN ASSIGNMENT #define BUTTON_PIN (32 + 0) // P1.00 // GPS -#define PIN_GPS_TX (0 + 22) // P0.22 -#define PIN_GPS_RX (0 + 20) // P0.20 +#define PIN_GPS_TX (0 + 20) // P0.20 +#define PIN_GPS_RX (0 + 22) // P0.22 #define PIN_GPS_EN (0 + 24) // P0.24 #define GPS_UBLOX // define GPS_DEBUG // UART interfaces -#define PIN_SERIAL1_RX PIN_GPS_TX -#define PIN_SERIAL1_TX PIN_GPS_RX +#define PIN_SERIAL1_TX PIN_GPS_TX +#define PIN_SERIAL1_RX PIN_GPS_RX #define PIN_SERIAL2_RX (0 + 6) // P0.06 #define PIN_SERIAL2_TX (0 + 8) // P0.08 diff --git a/variants/nrf52840/meshlink/variant.h b/variants/nrf52840/meshlink/variant.h index 54df03691..d1dba574f 100644 --- a/variants/nrf52840/meshlink/variant.h +++ b/variants/nrf52840/meshlink/variant.h @@ -121,8 +121,8 @@ static const uint8_t SCK = PIN_SPI_SCK; #define PIN_GPS_PPS (26) // Pulse per second input from the GPS -#define GPS_TX_PIN PIN_SERIAL1_RX // This is for bits going TOWARDS the CPU -#define GPS_RX_PIN PIN_SERIAL1_TX // This is for bits going TOWARDS the GPS +#define GPS_TX_PIN PIN_SERIAL1_TX // This is for bits going TOWARDS the CPU +#define GPS_RX_PIN PIN_SERIAL1_RX // This is for bits going TOWARDS the GPS // #define GPS_THREAD_INTERVAL 50 diff --git a/variants/nrf52840/meshlink_eink/variant.h b/variants/nrf52840/meshlink_eink/variant.h index b605d7082..e82163ca7 100644 --- a/variants/nrf52840/meshlink_eink/variant.h +++ b/variants/nrf52840/meshlink_eink/variant.h @@ -121,8 +121,8 @@ static const uint8_t SCK = PIN_SPI_SCK; #define PIN_GPS_PPS (26) // Pulse per second input from the GPS -#define GPS_TX_PIN PIN_SERIAL1_RX // This is for bits going TOWARDS the CPU -#define GPS_RX_PIN PIN_SERIAL1_TX // This is for bits going TOWARDS the GPS +#define GPS_TX_PIN PIN_SERIAL1_TX // This is for bits going TOWARDS the CPU +#define GPS_RX_PIN PIN_SERIAL1_RX // This is for bits going TOWARDS the GPS // #define GPS_THREAD_INTERVAL 50 diff --git a/variants/nrf52840/nano-g2-ultra/variant.h b/variants/nrf52840/nano-g2-ultra/variant.h index fd51cf9a1..fd837f66e 100644 --- a/variants/nrf52840/nano-g2-ultra/variant.h +++ b/variants/nrf52840/nano-g2-ultra/variant.h @@ -132,13 +132,13 @@ External serial flash W25Q16JV_IQ #define GPS_L76K #define PIN_GPS_STANDBY (0 + 13) // An output to wake GPS, low means allow sleep, high means force wake STANDBY -#define PIN_GPS_TX (0 + 9) // This is for bits going TOWARDS the CPU -#define PIN_GPS_RX (0 + 10) // This is for bits going TOWARDS the GPS +#define PIN_GPS_TX (0 + 10) // This is for bits going TOWARDS the CPU +#define PIN_GPS_RX (0 + 9) // This is for bits going TOWARDS the GPS // #define GPS_THREAD_INTERVAL 50 -#define PIN_SERIAL1_RX PIN_GPS_TX -#define PIN_SERIAL1_TX PIN_GPS_RX +#define PIN_SERIAL1_TX PIN_GPS_TX +#define PIN_SERIAL1_RX PIN_GPS_RX // PCF8563 RTC Module #define PCF8563_RTC 0x51 diff --git a/variants/nrf52840/seeed_solar_node/variant.h b/variants/nrf52840/seeed_solar_node/variant.h index 30d5c5888..da89fcfa5 100644 --- a/variants/nrf52840/seeed_solar_node/variant.h +++ b/variants/nrf52840/seeed_solar_node/variant.h @@ -115,13 +115,13 @@ static const uint8_t SCL = PIN_WIRE_SCL; // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ #define GPS_L76K #ifdef GPS_L76K -#define PIN_GPS_RX D6 // 44 -#define PIN_GPS_TX D7 // 43 +#define PIN_GPS_TX D6 // 44 +#define PIN_GPS_RX D7 // 43 #define HAS_GPS 1 #define GPS_BAUDRATE 9600 #define GPS_THREAD_INTERVAL 50 -#define PIN_SERIAL1_RX PIN_GPS_TX -#define PIN_SERIAL1_TX PIN_GPS_RX +#define PIN_SERIAL1_TX PIN_GPS_TX +#define PIN_SERIAL1_RX PIN_GPS_RX #define PIN_GPS_STANDBY D0 #define GPS_EN D18 // P1.05 #endif diff --git a/variants/nrf52840/seeed_xiao_nrf52840_kit/variant.h b/variants/nrf52840/seeed_xiao_nrf52840_kit/variant.h index a65500612..fb112a302 100644 --- a/variants/nrf52840/seeed_xiao_nrf52840_kit/variant.h +++ b/variants/nrf52840/seeed_xiao_nrf52840_kit/variant.h @@ -147,12 +147,12 @@ static const uint8_t SCK = PIN_SPI_SCK; */ // GPS L76K #ifdef GPS_L76K -#define PIN_GPS_RX D6 -#define PIN_GPS_TX D7 +#define PIN_GPS_TX D6 +#define PIN_GPS_RX D7 #define HAS_GPS 1 #define GPS_THREAD_INTERVAL 50 -#define PIN_SERIAL1_RX PIN_GPS_TX -#define PIN_SERIAL1_TX PIN_GPS_RX +#define PIN_SERIAL1_TX PIN_GPS_TX +#define PIN_SERIAL1_RX PIN_GPS_RX #define PIN_GPS_STANDBY D0 #else #define PIN_SERIAL1_RX (-1)