From 64116cd0d3466d0540d2b4b213c7493748a77914 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Thu, 15 Jan 2026 14:36:36 -0600 Subject: [PATCH 01/99] Meshtastic OTA (moar) (#9327) * Initial commit of combined BLE and WiFi OTA * Incorporate ota_hash in AdminMessage protobuf * OTA protobuf changes * Trunk fmt * Partition header check for OTA type * Guards * Guards * Derp * Missed one --------- Co-authored-by: Jake-B --- src/main.cpp | 43 ++++++++++++- src/modules/AdminModule.cpp | 64 +++++++++++++++++-- src/modules/AdminModule.h | 9 ++- src/platform/esp32/MeshtasticOTA.cpp | 43 ++++++++++--- src/platform/esp32/MeshtasticOTA.h | 10 ++- .../esp32c6/m5stack_unitc6l/platformio.ini | 2 + 6 files changed, 152 insertions(+), 19 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 88282c837..c1096a240 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -105,6 +105,43 @@ NRF52Bluetooth *nrf52Bluetooth = nullptr; #include #endif +#ifdef ARCH_ESP32 +#ifdef DEBUG_PARTITION_TABLE +#include "esp_partition.h" + +void printPartitionTable() +{ + printf("\n--- Partition Table ---\n"); + // Print Column Headers + printf("| %-16s | %-4s | %-7s | %-10s | %-10s |\n", "Label", "Type", "Subtype", "Offset", "Size"); + printf("|------------------|------|---------|------------|------------|\n"); + + // Create an iterator to find ALL partitions (Type ANY, Subtype ANY) + esp_partition_iterator_t it = esp_partition_find(ESP_PARTITION_TYPE_ANY, ESP_PARTITION_SUBTYPE_ANY, NULL); + + // Loop through the iterator + if (it != NULL) { + do { + const esp_partition_t *part = esp_partition_get(it); + + // Print details: Label, Type (Hex), Subtype (Hex), Offset (Hex), Size (Hex) + printf("| %-16s | 0x%02x | 0x%02x | 0x%08x | 0x%08x |\n", part->label, part->type, part->subtype, part->address, + part->size); + + // Move to next partition + it = esp_partition_next(it); + } while (it != NULL); + + // Release the iterator memory + esp_partition_iterator_release(it); + } else { + printf("No partitions found.\n"); + } + printf("-----------------------\n"); +} +#endif // DEBUG_PARTITION_TABLE +#endif // ARCH_ESP32 + #if HAS_BUTTON || defined(ARCH_PORTDUINO) #include "input/ButtonThread.h" @@ -648,7 +685,11 @@ void setup() sensor_detected = true; #endif } - +#ifdef ARCH_ESP32 +#ifdef DEBUG_PARTITION_TABLE + printPartitionTable(); +#endif +#endif // ARCH_ESP32 #ifdef ARCH_ESP32 // Don't init display if we don't have one or we are waking headless due to a timer event if (wakeCause == ESP_SLEEP_WAKEUP_TIMER) { diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index 990ca0f46..1fda9bf13 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -235,22 +235,46 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta } case meshtastic_AdminMessage_ota_request_tag: { #if defined(ARCH_ESP32) + LOG_INFO("OTA Requested"); + if (r->ota_request.ota_hash.size != 32) { suppressRebootBanner = true; - LOG_INFO("OTA Failed: Invalid `ota_hash` provided"); + sendWarningAndLog("Cannot start OTA: Invalid `ota_hash` provided."); break; } meshtastic_OTAMode mode = r->ota_request.reboot_ota_mode; + const char *mode_name = (mode == METHOD_OTA_BLE ? "BLE" : "WiFi"); + + // Check that we have an OTA partition + const esp_partition_t *part = MeshtasticOTA::getAppPartition(); + if (part == NULL) { + suppressRebootBanner = true; + sendWarningAndLog("Cannot start OTA: Cannot find OTA Loader partition."); + break; + } + + static esp_app_desc_t app_desc; + if (!MeshtasticOTA::getAppDesc(part, &app_desc)) { + suppressRebootBanner = true; + sendWarningAndLog("Cannot start OTA: Device does have a valid OTA Loader."); + break; + } + + if (!MeshtasticOTA::checkOTACapability(&app_desc, mode)) { + suppressRebootBanner = true; + sendWarningAndLog("OTA Loader does not support %s", mode_name); + break; + } + if (MeshtasticOTA::trySwitchToOTA()) { - LOG_INFO("OTA Requested"); suppressRebootBanner = true; if (screen) screen->startFirmwareUpdateScreen(); MeshtasticOTA::saveConfig(&config.network, mode, r->ota_request.ota_hash.bytes); - LOG_INFO("Rebooting to WiFi OTA"); + sendWarningAndLog("Rebooting to %s OTA", mode_name); } else { - LOG_INFO("WIFI OTA Failed"); + sendWarningAndLog("Unable to switch to the OTA partition."); } #endif int s = 1; // Reboot in 1 second, hard coded @@ -1472,15 +1496,43 @@ void AdminModule::handleSendInputEvent(const meshtastic_AdminMessage_InputEvent #endif } -void AdminModule::sendWarning(const char *message) +void AdminModule::sendWarning(const char *format, ...) { meshtastic_ClientNotification *cn = clientNotificationPool.allocZeroed(); + if (!cn) + return; + cn->level = meshtastic_LogRecord_Level_WARNING; cn->time = getValidTime(RTCQualityFromNet); - strncpy(cn->message, message, sizeof(cn->message)); + + va_list args; + va_start(args, format); + // Format the arguments directly into the notification object + vsnprintf(cn->message, sizeof(cn->message), format, args); + va_end(args); + service->sendClientNotification(cn); } +void AdminModule::sendWarningAndLog(const char *format, ...) +{ + // We need a temporary buffer to hold the formatted text so we can log it + // Using 250 bytes as a safe upper limit for typical text notifications + char buf[250]; + + va_list args; + va_start(args, format); + vsnprintf(buf, sizeof(buf), format, args); + va_end(args); + + LOG_WARN(buf); + // 2. Call sendWarning + // SECURITY NOTE: We pass "%s", buf instead of just 'buf'. + // If 'buf' contained a % symbol (e.g. "Battery 50%"), passing it directly + // would crash sendWarning. "%s" treats it purely as text. + sendWarning("%s", buf); +} + void disableBluetooth() { #if HAS_BLUETOOTH diff --git a/src/modules/AdminModule.h b/src/modules/AdminModule.h index 867751f49..c446887b3 100644 --- a/src/modules/AdminModule.h +++ b/src/modules/AdminModule.h @@ -1,7 +1,9 @@ -#include - #pragma once +#ifdef ESP_PLATFORM +#include +#endif #include "ProtobufModule.h" +#include #if HAS_WIFI #include "mesh/wifi/WiFiAPClient.h" #endif @@ -71,7 +73,8 @@ class AdminModule : public ProtobufModule, public Obser bool messageIsResponse(const meshtastic_AdminMessage *r); bool messageIsRequest(const meshtastic_AdminMessage *r); - void sendWarning(const char *message); + void sendWarning(const char *format, ...) __attribute__((format(printf, 2, 3))); + void sendWarningAndLog(const char *format, ...) __attribute__((format(printf, 2, 3))); }; static constexpr const char *licensedModeMessage = diff --git a/src/platform/esp32/MeshtasticOTA.cpp b/src/platform/esp32/MeshtasticOTA.cpp index b8cb052ef..4ca074723 100644 --- a/src/platform/esp32/MeshtasticOTA.cpp +++ b/src/platform/esp32/MeshtasticOTA.cpp @@ -1,13 +1,17 @@ #include "MeshtasticOTA.h" #include "configuration.h" +#ifdef ESP_PLATFORM #include #include +#endif namespace MeshtasticOTA { static const char *nvsNamespace = "MeshtasticOTA"; -static const char *appProjectName = "MeshtasticOTA"; +static const char *combinedAppProjectName = "MeshtasticOTA"; +static const char *bleOnlyAppProjectName = "MeshtasticOTA-BLE"; +static const char *wifiOnlyAppProjectName = "MeshtasticOTA-WiFi"; static bool updated = false; @@ -68,21 +72,44 @@ bool getAppDesc(const esp_partition_t *part, esp_app_desc_t *app_desc) LOG_INFO("esp_ota_get_partition_description failed"); return false; } - if (strcmp(app_desc->project_name, appProjectName) != 0) { - LOG_INFO("app_desc->project_name == 0"); - return false; - } return true; } +bool checkOTACapability(esp_app_desc_t *app_desc, uint8_t method) +{ + // Combined loader supports all (both) transports, BLE and WiFi + if (strcmp(app_desc->project_name, combinedAppProjectName) == 0) { + LOG_INFO("OTA partition contains combined BLE/WiFi OTA Loader"); + return true; + } + if (method == METHOD_OTA_BLE && strcmp(app_desc->project_name, bleOnlyAppProjectName) == 0) { + LOG_INFO("OTA partition contains BLE-only OTA Loader"); + return true; + } + if (method == METHOD_OTA_WIFI && strcmp(app_desc->project_name, wifiOnlyAppProjectName) == 0) { + LOG_INFO("OTA partition contains WiFi-only OTA Loader"); + return true; + } + LOG_INFO("OTA partition does not contain a known OTA loader"); + return false; +} + bool trySwitchToOTA() { const esp_partition_t *part = getAppPartition(); - esp_app_desc_t app_desc; - if (!getAppDesc(part, &app_desc)) + + if (part == NULL) { + LOG_WARN("Unable to get app partition in preparation of OTA reboot"); return false; - if (esp_ota_set_boot_partition(part) != ESP_OK) + } + + uint8_t result = esp_ota_set_boot_partition(part); + // Partition and app checks should now be done in the AdminModule before this is called + if (result != ESP_OK) { + LOG_WARN("Unable to switch to OTA partiton. (Reason %d)", result); return false; + } + return true; } diff --git a/src/platform/esp32/MeshtasticOTA.h b/src/platform/esp32/MeshtasticOTA.h index 001eba039..7c158775f 100644 --- a/src/platform/esp32/MeshtasticOTA.h +++ b/src/platform/esp32/MeshtasticOTA.h @@ -3,12 +3,20 @@ #include "mesh-pb-constants.h" #include +#ifdef ESP_PLATFORM +#include +#endif + +#define METHOD_OTA_BLE 1 +#define METHOD_OTA_WIFI 2 namespace MeshtasticOTA { void initialize(); bool isUpdated(); - +const esp_partition_t *getAppPartition(); +bool getAppDesc(const esp_partition_t *part, esp_app_desc_t *app_desc); +bool checkOTACapability(esp_app_desc_t *app_desc, uint8_t method); void recoverConfig(meshtastic_Config_NetworkConfig *network); void saveConfig(meshtastic_Config_NetworkConfig *network, meshtastic_OTAMode method, uint8_t *ota_hash); bool trySwitchToOTA(); diff --git a/variants/esp32c6/m5stack_unitc6l/platformio.ini b/variants/esp32c6/m5stack_unitc6l/platformio.ini index 3054d342a..ed26598d2 100644 --- a/variants/esp32c6/m5stack_unitc6l/platformio.ini +++ b/variants/esp32c6/m5stack_unitc6l/platformio.ini @@ -10,6 +10,8 @@ custom_meshtastic_tags = M5Stack extends = esp32c6_base board = esp32-c6-devkitc-1 +board_upload.flash_size = 16MB +board_build.partitions = default_16MB.csv ;OpenOCD flash method ;upload_protocol = esp-builtin ;Normal method From afbd9e21802a8f5145f55d09d9a1eb445455b8a0 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Fri, 16 Jan 2026 13:52:04 -0600 Subject: [PATCH 02/99] Filter BLE updates that don't change pairing status (#9333) --- src/modules/StatusLEDModule.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/modules/StatusLEDModule.cpp b/src/modules/StatusLEDModule.cpp index 33aa58127..fed035513 100644 --- a/src/modules/StatusLEDModule.cpp +++ b/src/modules/StatusLEDModule.cpp @@ -50,9 +50,10 @@ int StatusLEDModule::handleStatusUpdate(const meshtastic::Status *arg) break; } case meshtastic::BluetoothStatus::ConnectionState::CONNECTED: { - ble_state = connected; - PAIRING_LED_starttime = millis(); - break; + if (ble_state != connected) { + ble_state = connected; + PAIRING_LED_starttime = millis(); + } } } From 021106dfe58a0b2b481c355ad318289aff592e7c Mon Sep 17 00:00:00 2001 From: "Ted W." Date: Sat, 17 Jan 2026 16:23:16 -0500 Subject: [PATCH 03/99] Add support for setting API port from the config file (#8435) * Add support for setting API port from the config file * Update PortduinoGlue.cpp Fix typo in var identifier --------- Co-authored-by: Ben Meadors --- src/platform/portduino/PortduinoGlue.cpp | 10 ++++++++++ src/platform/portduino/PortduinoGlue.h | 5 ++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/platform/portduino/PortduinoGlue.cpp b/src/platform/portduino/PortduinoGlue.cpp index 7430c2eae..ec9bbedca 100644 --- a/src/platform/portduino/PortduinoGlue.cpp +++ b/src/platform/portduino/PortduinoGlue.cpp @@ -55,6 +55,7 @@ void cpuDeepSleep(uint32_t msecs) void updateBatteryLevel(uint8_t level) NOT_IMPLEMENTED("updateBatteryLevel"); int TCPPort = SERVER_API_DEFAULT_PORT; +bool checkConfigPort = true; static error_t parse_opt(int key, char *arg, struct argp_state *state) { @@ -63,6 +64,7 @@ static error_t parse_opt(int key, char *arg, struct argp_state *state) if (sscanf(arg, "%d", &TCPPort) < 1) return ARGP_ERR_UNKNOWN; else + checkConfigPort = false; printf("Using config file %d\n", TCPPort); break; case 'c': @@ -870,6 +872,14 @@ bool loadConfig(const char *configPath) std::cout << "Cannot set both MACAddress and MACAddressSource!" << std::endl; exit(EXIT_FAILURE); } + if (checkConfigPort) { + portduino_config.api_port = (yamlConfig["General"]["APIPort"]).as(-1); + if (portduino_config.api_port != -1 && + portduino_config.api_port > 1023 && + portduino_config.api_port < 65536) { + TCPPort = (portduino_config.api_port); + } + } portduino_config.mac_address = (yamlConfig["General"]["MACAddress"]).as(""); if (portduino_config.mac_address != "") { portduino_config.mac_address_explicit = true; diff --git a/src/platform/portduino/PortduinoGlue.h b/src/platform/portduino/PortduinoGlue.h index 8992f5f1a..3a6887421 100644 --- a/src/platform/portduino/PortduinoGlue.h +++ b/src/platform/portduino/PortduinoGlue.h @@ -175,6 +175,7 @@ extern struct portduino_config_struct { std::string mac_address = ""; bool mac_address_explicit = false; std::string mac_address_source = ""; + int api_port = -1; std::string config_directory = ""; std::string available_directory = "/etc/meshtasticd/available.d/"; int maxtophone = 100; @@ -508,6 +509,8 @@ extern struct portduino_config_struct { out << YAML::Key << "General" << YAML::Value << YAML::BeginMap; if (config_directory != "") out << YAML::Key << "ConfigDirectory" << YAML::Value << config_directory; + if (api_port != -1) + out << YAML::Key << "TCPPort" << YAML::Value << api_port; if (mac_address_explicit) out << YAML::Key << "MACAddress" << YAML::Value << mac_address; if (mac_address_source != "") @@ -519,4 +522,4 @@ extern struct portduino_config_struct { out << YAML::EndMap; // General return out.c_str(); } -} portduino_config; \ No newline at end of file +} portduino_config; From 33ae3777a377cf564eec2fc9acf1f21088201221 Mon Sep 17 00:00:00 2001 From: Catalin Patulea Date: Sun, 18 Jan 2026 08:41:24 -0500 Subject: [PATCH 04/99] toradio, fromradio OPTIONS handler: fix sending proper HTTP response. (#9322) Before this (missing response): $ curl -v -X OPTIONS http://meshtastic.local/api/v1/fromradio * Host meshtastic.local:80 was resolved. * IPv6: (none) * IPv4: 192.168.0.19 * Trying 192.168.0.19:80... * Connected to meshtastic.local (192.168.0.19) port 80 * using HTTP/1.x > OPTIONS /api/v1/fromradio HTTP/1.1 > Host: meshtastic.local > User-Agent: curl/8.14.1 > Accept: */* > * Request completely sent off * Empty reply from server * shutting down connection #0 curl: (52) Empty reply from server After this (proper HTTP 204 response): $ curl -v -X OPTIONS http://meshtastic.local/api/v1/fromradio * Host meshtastic.local:80 was resolved. * IPv6: (none) * IPv4: 192.168.0.19 * Trying 192.168.0.19:80... * Connected to meshtastic.local (192.168.0.19) port 80 * using HTTP/1.x > OPTIONS /api/v1/fromradio HTTP/1.1 > Host: meshtastic.local > User-Agent: curl/8.14.1 > Accept: */* > * Request completely sent off < HTTP/1.1 204 OK < Content-Type: application/x-protobuf < Access-Control-Allow-Origin: * < Access-Control-Allow-Methods: GET < X-Protobuf-Schema: https://raw.githubusercontent.com/meshtastic/protobufs/master/meshtastic/mesh.proto < * Connection #0 to host meshtastic.local left intact This is related to https://github.com/meshtastic/firmware/issues/5385. Co-authored-by: Ben Meadors --- src/mesh/http/ContentHandler.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mesh/http/ContentHandler.cpp b/src/mesh/http/ContentHandler.cpp index 7b7ebb595..ea8d6af8e 100644 --- a/src/mesh/http/ContentHandler.cpp +++ b/src/mesh/http/ContentHandler.cpp @@ -173,7 +173,7 @@ void handleAPIv1FromRadio(HTTPRequest *req, HTTPResponse *res) if (req->getMethod() == "OPTIONS") { res->setStatusCode(204); // Success with no content - // res->print(""); @todo remove + res->print(""); return; } @@ -223,7 +223,7 @@ void handleAPIv1ToRadio(HTTPRequest *req, HTTPResponse *res) if (req->getMethod() == "OPTIONS") { res->setStatusCode(204); // Success with no content - // res->print(""); @todo remove + res->print(""); return; } From 02f24b90151e4f5a5e634aae20b321fb0d035c46 Mon Sep 17 00:00:00 2001 From: Jason P Date: Sun, 18 Jan 2026 15:38:46 -0600 Subject: [PATCH 05/99] Improve BaseUI Preset Change Flow (#9343) * Reset Channel Number to 0 on Preset Change * Add Channel Picker to LoRa Options * Change Channel to Frequency Slot * Catch comparison issue * Reset override_frequency to ensure we correctly move to new Radio Preset * CoPilot Suggestions --- src/graphics/draw/DebugRenderer.cpp | 2 +- src/graphics/draw/MenuHandler.cpp | 120 +++++++++++++++++++++++++++- src/graphics/draw/MenuHandler.h | 2 + 3 files changed, 120 insertions(+), 4 deletions(-) diff --git a/src/graphics/draw/DebugRenderer.cpp b/src/graphics/draw/DebugRenderer.cpp index 75b65c65f..2dca38d66 100644 --- a/src/graphics/draw/DebugRenderer.cpp +++ b/src/graphics/draw/DebugRenderer.cpp @@ -438,7 +438,7 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, if (currentResolution == ScreenResolution::UltraLow) { 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); + snprintf(frequencyslot, sizeof(frequencyslot), "Freq: %sMHz (%d)", freqStr, config.lora.channel_num); } } size_t len = strlen(frequencyslot); diff --git a/src/graphics/draw/MenuHandler.cpp b/src/graphics/draw/MenuHandler.cpp index 5c459d984..c5a4106e7 100644 --- a/src/graphics/draw/MenuHandler.cpp +++ b/src/graphics/draw/MenuHandler.cpp @@ -65,12 +65,12 @@ uint8_t test_count = 0; void menuHandler::loraMenu() { - 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 }; + static const char *optionsArray[] = {"Back", "Device Role", "Radio Preset", "Frequency Slot", "LoRa Region"}; + enum optionsNumbers { Back = 0, device_role_picker = 1, radio_preset_picker = 2, frequency_slot = 3, lora_picker = 4 }; BannerOverlayOptions bannerOptions; bannerOptions.message = "LoRa Actions"; bannerOptions.optionsArrayPtr = optionsArray; - bannerOptions.optionsCount = 4; + bannerOptions.optionsCount = 5; bannerOptions.bannerCallback = [](int selected) -> void { if (selected == Back) { // No action @@ -78,6 +78,8 @@ void menuHandler::loraMenu() menuHandler::menuQueue = menuHandler::device_role_picker; } else if (selected == radio_preset_picker) { menuHandler::menuQueue = menuHandler::radio_preset_picker; + } else if (selected == frequency_slot) { + menuHandler::menuQueue = menuHandler::frequency_slot; } else if (selected == lora_picker) { menuHandler::menuQueue = menuHandler::lora_picker; } @@ -248,6 +250,113 @@ void menuHandler::DeviceRolePicker() screen->showOverlayBanner(bannerOptions); } +void menuHandler::FrequencySlotPicker() +{ + + enum ReplyOptions : int { Back = -1 }; + constexpr int MAX_CHANNEL_OPTIONS = 202; + static const char *optionsArray[MAX_CHANNEL_OPTIONS]; + static int optionsEnumArray[MAX_CHANNEL_OPTIONS]; + static char channelText[MAX_CHANNEL_OPTIONS - 1][12]; + int options = 0; + optionsArray[options] = "Back"; + optionsEnumArray[options++] = Back; + optionsArray[options] = "Slot 0 (Auto)"; + optionsEnumArray[options++] = 0; + + // Calculate number of channels (copied from RadioInterface::applyModemConfig()) + meshtastic_Config_LoRaConfig &loraConfig = config.lora; + double bw = loraConfig.bandwidth; + if (loraConfig.use_preset) { + switch (loraConfig.modem_preset) { + case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO: + bw = (myRegion->wideLora) ? 1625.0 : 500; + break; + case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_FAST: + bw = (myRegion->wideLora) ? 812.5 : 250; + break; + case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_SLOW: + bw = (myRegion->wideLora) ? 812.5 : 250; + break; + case meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_FAST: + bw = (myRegion->wideLora) ? 812.5 : 250; + break; + case meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_SLOW: + bw = (myRegion->wideLora) ? 812.5 : 250; + break; + case meshtastic_Config_LoRaConfig_ModemPreset_LONG_TURBO: + bw = (myRegion->wideLora) ? 1625.0 : 500; + break; + default: + bw = (myRegion->wideLora) ? 812.5 : 250; + break; + case meshtastic_Config_LoRaConfig_ModemPreset_LONG_MODERATE: + bw = (myRegion->wideLora) ? 406.25 : 125; + break; + case meshtastic_Config_LoRaConfig_ModemPreset_LONG_SLOW: + bw = (myRegion->wideLora) ? 406.25 : 125; + break; + } + } else { + bw = loraConfig.bandwidth; + if (bw == 31) // This parameter is not an integer + bw = 31.25; + if (bw == 62) // Fix for 62.5Khz bandwidth + bw = 62.5; + if (bw == 200) + bw = 203.125; + if (bw == 400) + bw = 406.25; + if (bw == 800) + bw = 812.5; + if (bw == 1600) + bw = 1625.0; + } + + uint32_t numChannels = 0; + if (myRegion) { + numChannels = (uint32_t)floor((myRegion->freqEnd - myRegion->freqStart) / (myRegion->spacing + (bw / 1000.0))); + } else { + LOG_WARN("Region not set, cannot calculate number of channels"); + return; + } + + if (numChannels > (uint32_t)(MAX_CHANNEL_OPTIONS - 2)) + numChannels = (uint32_t)(MAX_CHANNEL_OPTIONS - 2); + + for (uint32_t ch = 1; ch <= numChannels; ch++) { + snprintf(channelText[ch - 1], sizeof(channelText[ch - 1]), "Slot %lu", (unsigned long)ch); + optionsArray[options] = channelText[ch - 1]; + optionsEnumArray[options++] = (int)ch; + } + + BannerOverlayOptions bannerOptions; + bannerOptions.message = "Frequency Slot"; + bannerOptions.optionsArrayPtr = optionsArray; + bannerOptions.optionsEnumPtr = optionsEnumArray; + bannerOptions.optionsCount = options; + + // Start highlight on current channel if possible, otherwise on "1" + int initial = (int)config.lora.channel_num + 1; + if (initial < 2 || initial > (int)numChannels + 1) + initial = 1; + bannerOptions.InitialSelected = initial; + + bannerOptions.bannerCallback = [](int selected) -> void { + if (selected == Back) { + menuHandler::menuQueue = menuHandler::lora_Menu; + screen->runNow(); + return; + } + + config.lora.channel_num = selected; + service->reloadConfig(SEGMENT_CONFIG); + rebootAtMsec = (millis() + DEFAULT_REBOOT_SECONDS * 1000); + }; + + screen->showOverlayBanner(bannerOptions); +} + void menuHandler::RadioPresetPicker() { static const RadioPresetOption presetOptions[] = { @@ -278,6 +387,8 @@ void menuHandler::RadioPresetPicker() } config.lora.modem_preset = option.value; + config.lora.channel_num = 0; // Reset to default channel for the preset + config.lora.override_frequency = 0; // Clear any custom frequency service->reloadConfig(SEGMENT_CONFIG); rebootAtMsec = (millis() + DEFAULT_REBOOT_SECONDS * 1000); }); @@ -2551,6 +2662,9 @@ void menuHandler::handleMenuSwitch(OLEDDisplay *display) case radio_preset_picker: RadioPresetPicker(); break; + case frequency_slot: + FrequencySlotPicker(); + break; case no_timeout_lora_picker: LoraRegionPicker(0); break; diff --git a/src/graphics/draw/MenuHandler.h b/src/graphics/draw/MenuHandler.h index 121b6dfc9..45fd0bf5f 100644 --- a/src/graphics/draw/MenuHandler.h +++ b/src/graphics/draw/MenuHandler.h @@ -13,6 +13,7 @@ class menuHandler lora_picker, device_role_picker, radio_preset_picker, + frequency_slot, no_timeout_lora_picker, TZ_picker, twelve_hour_picker, @@ -63,6 +64,7 @@ class menuHandler static void loraMenu(); static void DeviceRolePicker(); static void RadioPresetPicker(); + static void FrequencySlotPicker(); static void handleMenuSwitch(OLEDDisplay *display); static void showConfirmationBanner(const char *message, std::function onConfirm); static void clockMenu(); From 49accefd8bc1a4507187d98a464c94fb74926d08 Mon Sep 17 00:00:00 2001 From: Jason P Date: Sun, 18 Jan 2026 15:39:23 -0600 Subject: [PATCH 06/99] Don't Mute DMs just because we mute a channel (#9348) * Don't Mute DMs just because we mute a channel * Updated code to consolidate muting --- src/modules/ExternalNotificationModule.cpp | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/modules/ExternalNotificationModule.cpp b/src/modules/ExternalNotificationModule.cpp index 04fcd8e73..8b7ce700a 100644 --- a/src/modules/ExternalNotificationModule.cpp +++ b/src/modules/ExternalNotificationModule.cpp @@ -460,12 +460,15 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP } meshtastic_NodeInfoLite *sender = nodeDB->getMeshNode(mp.from); - bool mutedNode = false; - if (sender) { - mutedNode = (sender->bitfield & NODEINFO_BITFIELD_IS_MUTED_MASK); - } meshtastic_Channel ch = channels.getByIndex(mp.channel ? mp.channel : channels.getPrimaryIndex()); + // If we receive a broadcast message, apply channel mute setting + // If we receive a direct message and the receipent is us, apply DM mute setting + // Else we just handle it as not muted. + const bool directToUs = !isBroadcast(mp.to) && isToUs(&mp); + bool is_muted = directToUs ? (sender && ((sender->bitfield & NODEINFO_BITFIELD_IS_MUTED_MASK) != 0)) + : (ch.settings.has_module_settings && ch.settings.module_settings.is_muted); + if (moduleConfig.external_notification.alert_bell) { if (containsBell) { LOG_INFO("externalNotificationModule - Notification Bell"); @@ -516,8 +519,7 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP } } - if (moduleConfig.external_notification.alert_message && !mutedNode && - (!ch.settings.has_module_settings || !ch.settings.module_settings.is_muted)) { + if (moduleConfig.external_notification.alert_message && !is_muted) { LOG_INFO("externalNotificationModule - Notification Module"); isNagging = true; setExternalState(0, true); @@ -528,8 +530,7 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP } } - if (moduleConfig.external_notification.alert_message_vibra && !mutedNode && - (!ch.settings.has_module_settings || !ch.settings.module_settings.is_muted)) { + if (moduleConfig.external_notification.alert_message_vibra && !is_muted) { LOG_INFO("externalNotificationModule - Notification Module (Vibra)"); isNagging = true; setExternalState(1, true); @@ -540,8 +541,7 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP } } - if (moduleConfig.external_notification.alert_message_buzzer && !mutedNode && - (!ch.settings.has_module_settings || !ch.settings.module_settings.is_muted)) { + if (moduleConfig.external_notification.alert_message_buzzer && !is_muted) { LOG_INFO("externalNotificationModule - Notification Module (Buzzer)"); if (config.device.buzzer_mode != meshtastic_Config_DeviceConfig_BuzzerMode_DIRECT_MSG_ONLY || (!isBroadcast(mp.to) && isToUs(&mp))) { From caa6ec0e8a68bc805e8e513774d7fafe820dfc22 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 19 Jan 2026 14:28:05 +1100 Subject: [PATCH 07/99] Update meshtastic/device-ui digest to 3480b73 (#9353) 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 4c19136af..8daad1fb3 100644 --- a/platformio.ini +++ b/platformio.ini @@ -119,7 +119,7 @@ lib_deps = [device-ui_base] lib_deps = # renovate: datasource=git-refs depName=meshtastic/device-ui packageName=https://github.com/meshtastic/device-ui gitBranch=master - https://github.com/meshtastic/device-ui/archive/5a870c623a4e9ab7a7abe3d02950536f107d1a31.zip + https://github.com/meshtastic/device-ui/archive/3480b731d28b10d73414cf0dd7975bff745de8cf.zip ; Common libs for environmental measurements in telemetry module [environmental_base] From e545897d4ebb190a23001914b2aaf7ed4e386b55 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Sun, 18 Jan 2026 21:25:39 -0600 Subject: [PATCH 08/99] Untangle some BME680 ifdef spaghetti --- src/configuration.h | 12 ---------- .../Telemetry/EnvironmentTelemetry.cpp | 4 ++-- src/modules/Telemetry/Sensor/BME680Sensor.cpp | 18 +++++++-------- src/modules/Telemetry/Sensor/BME680Sensor.h | 22 +++++++++---------- 4 files changed, 22 insertions(+), 34 deletions(-) diff --git a/src/configuration.h b/src/configuration.h index eb258651c..59bffe7be 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -445,18 +445,6 @@ along with this program. If not, see . #endif #endif -// BME680 BSEC2 support detection -#if !defined(MESHTASTIC_BME680_BSEC2_SUPPORTED) -#if defined(RAK_4631) || defined(TBEAM_V10) - -#define MESHTASTIC_BME680_BSEC2_SUPPORTED 1 -#define MESHTASTIC_BME680_HEADER -#else -#define MESHTASTIC_BME680_BSEC2_SUPPORTED 0 -#define MESHTASTIC_BME680_HEADER -#endif // defined(RAK_4631) -#endif // !defined(MESHTASTIC_BME680_BSEC2_SUPPORTED) - // ----------------------------------------------------------------------------- // Global switches to turn off features for a minimized build // ----------------------------------------------------------------------------- diff --git a/src/modules/Telemetry/EnvironmentTelemetry.cpp b/src/modules/Telemetry/EnvironmentTelemetry.cpp index ec6fe4799..86a8606c2 100644 --- a/src/modules/Telemetry/EnvironmentTelemetry.cpp +++ b/src/modules/Telemetry/EnvironmentTelemetry.cpp @@ -53,7 +53,7 @@ extern void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const c #include "Sensor/LTR390UVSensor.h" #endif -#if __has_include(MESHTASTIC_BME680_HEADER) +#if __has_include() || __has_include() #include "Sensor/BME680Sensor.h" #endif @@ -187,7 +187,7 @@ void EnvironmentTelemetryModule::i2cScanFinished(ScanI2C *i2cScanner) #if __has_include() addSensor(i2cScanner, ScanI2C::DeviceType::LTR390UV); #endif -#if __has_include(MESHTASTIC_BME680_HEADER) +#if __has_include() || __has_include() addSensor(i2cScanner, ScanI2C::DeviceType::BME_680); #endif #if __has_include() diff --git a/src/modules/Telemetry/Sensor/BME680Sensor.cpp b/src/modules/Telemetry/Sensor/BME680Sensor.cpp index 22330ca75..3a1eb9532 100644 --- a/src/modules/Telemetry/Sensor/BME680Sensor.cpp +++ b/src/modules/Telemetry/Sensor/BME680Sensor.cpp @@ -1,6 +1,6 @@ #include "configuration.h" -#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && __has_include(MESHTASTIC_BME680_HEADER) +#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && (__has_include() || __has_include()) #include "../mesh/generated/meshtastic/telemetry.pb.h" #include "BME680Sensor.h" @@ -10,7 +10,7 @@ BME680Sensor::BME680Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_BME680, "BME680") {} -#if MESHTASTIC_BME680_BSEC2_SUPPORTED == 1 +#if __has_include() int32_t BME680Sensor::runOnce() { if (!bme680.run()) { @@ -18,13 +18,13 @@ int32_t BME680Sensor::runOnce() } return 35; } -#endif // defined(MESHTASTIC_BME680_BSEC2_SUPPORTED) +#endif bool BME680Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) { status = 0; -#if MESHTASTIC_BME680_BSEC2_SUPPORTED == 1 +#if __has_include() if (!bme680.begin(dev->address.address, *bus)) checkStatus("begin"); @@ -56,7 +56,7 @@ bool BME680Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) status = 1; -#endif // MESHTASTIC_BME680_BSEC2_SUPPORTED +#endif initI2CSensor(); return status; @@ -64,7 +64,7 @@ bool BME680Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) bool BME680Sensor::getMetrics(meshtastic_Telemetry *measurement) { -#if MESHTASTIC_BME680_BSEC2_SUPPORTED == 1 +#if __has_include() if (bme680.getData(BSEC_OUTPUT_RAW_PRESSURE).signal == 0) return false; @@ -98,11 +98,11 @@ bool BME680Sensor::getMetrics(meshtastic_Telemetry *measurement) measurement->variant.environment_metrics.barometric_pressure = bme680->readPressure() / 100.0F; measurement->variant.environment_metrics.gas_resistance = bme680->readGas() / 1000.0; -#endif // MESHTASTIC_BME680_BSEC2_SUPPORTED +#endif return true; } -#if MESHTASTIC_BME680_BSEC2_SUPPORTED == 1 +#if __has_include() void BME680Sensor::loadState() { #ifdef FSCom @@ -179,6 +179,6 @@ void BME680Sensor::checkStatus(const char *functionName) else if (bme680.sensor.status > BME68X_OK) LOG_WARN("%s BME68X code: %d", functionName, bme680.sensor.status); } -#endif // MESHTASTIC_BME680_BSEC2_SUPPORTED +#endif #endif diff --git a/src/modules/Telemetry/Sensor/BME680Sensor.h b/src/modules/Telemetry/Sensor/BME680Sensor.h index 9bef56e1e..eaeceb848 100644 --- a/src/modules/Telemetry/Sensor/BME680Sensor.h +++ b/src/modules/Telemetry/Sensor/BME680Sensor.h @@ -1,29 +1,29 @@ #include "configuration.h" -#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && __has_include(MESHTASTIC_BME680_HEADER) +#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && (__has_include() || __has_include()) #include "../mesh/generated/meshtastic/telemetry.pb.h" #include "TelemetrySensor.h" -#if MESHTASTIC_BME680_BSEC2_SUPPORTED == 1 +#if __has_include() #include #include #else #include #include -#endif // MESHTASTIC_BME680_BSEC2_SUPPORTED +#endif #define STATE_SAVE_PERIOD UINT32_C(360 * 60 * 1000) // That's 6 hours worth of millis() -#if MESHTASTIC_BME680_BSEC2_SUPPORTED == 1 +#if __has_include() const uint8_t bsec_config[] = { #include "config/bme680/bme680_iaq_33v_3s_4d/bsec_iaq.txt" }; -#endif // MESHTASTIC_BME680_BSEC2_SUPPORTED +#endif class BME680Sensor : public TelemetrySensor { private: -#if MESHTASTIC_BME680_BSEC2_SUPPORTED == 1 +#if __has_include() Bsec2 bme680; #else using BME680Ptr = std::unique_ptr; @@ -31,10 +31,10 @@ class BME680Sensor : public TelemetrySensor static BME680Ptr makeBME680(TwoWire *bus) { return std::make_unique(bus); } BME680Ptr bme680; -#endif // MESHTASTIC_BME680_BSEC2_SUPPORTED +#endif protected: -#if MESHTASTIC_BME680_BSEC2_SUPPORTED == 1 +#if __has_include() const char *bsecConfigFileName = "/prefs/bsec.dat"; uint8_t bsecState[BSEC_MAX_STATE_BLOB_SIZE] = {0}; uint8_t accuracy = 0; @@ -51,13 +51,13 @@ class BME680Sensor : public TelemetrySensor void loadState(); void updateState(); void checkStatus(const char *functionName); -#endif // MESHTASTIC_BME680_BSEC2_SUPPORTED +#endif public: BME680Sensor(); -#if MESHTASTIC_BME680_BSEC2_SUPPORTED == 1 +#if __has_include() virtual int32_t runOnce() override; -#endif // MESHTASTIC_BME680_BSEC2_SUPPORTED +#endif virtual bool getMetrics(meshtastic_Telemetry *measurement) override; virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override; }; From d34d694731c38ebd241f60ab436f67f0dc604a7d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 19 Jan 2026 06:21:01 -0600 Subject: [PATCH 09/99] Update protobufs (#9360) Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> --- protobufs | 2 +- src/mesh/generated/meshtastic/mesh.pb.cpp | 3 +++ src/mesh/generated/meshtastic/mesh.pb.h | 17 +++++++++++++ .../generated/meshtastic/module_config.pb.cpp | 3 +++ .../generated/meshtastic/module_config.pb.h | 25 ++++++++++++++++++- src/mesh/generated/meshtastic/portnums.pb.h | 5 ++++ 6 files changed, 53 insertions(+), 2 deletions(-) diff --git a/protobufs b/protobufs index 4b9f104a1..bbde30a0b 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit 4b9f104a18ea43b1b2091ee2b48899fe43ad8a0b +Subproject commit bbde30a0b98b485df8ccc1058facc7d31477f16d diff --git a/src/mesh/generated/meshtastic/mesh.pb.cpp b/src/mesh/generated/meshtastic/mesh.pb.cpp index d8eee1203..7f1a738c6 100644 --- a/src/mesh/generated/meshtastic/mesh.pb.cpp +++ b/src/mesh/generated/meshtastic/mesh.pb.cpp @@ -30,6 +30,9 @@ PB_BIND(meshtastic_StoreForwardPlusPlus, meshtastic_StoreForwardPlusPlus, 2) PB_BIND(meshtastic_Waypoint, meshtastic_Waypoint, AUTO) +PB_BIND(meshtastic_StatusMessage, meshtastic_StatusMessage, AUTO) + + PB_BIND(meshtastic_MqttClientProxyMessage, meshtastic_MqttClientProxyMessage, 2) diff --git a/src/mesh/generated/meshtastic/mesh.pb.h b/src/mesh/generated/meshtastic/mesh.pb.h index 68552ede5..aeae4bd84 100644 --- a/src/mesh/generated/meshtastic/mesh.pb.h +++ b/src/mesh/generated/meshtastic/mesh.pb.h @@ -858,6 +858,11 @@ typedef struct _meshtastic_Waypoint { uint32_t icon; } meshtastic_Waypoint; +/* Message for node status */ +typedef struct _meshtastic_StatusMessage { + char status[80]; +} meshtastic_StatusMessage; + typedef PB_BYTES_ARRAY_T(435) meshtastic_MqttClientProxyMessage_data_t; /* This message will be proxied over the PhoneAPI for the client to deliver to the MQTT server */ typedef struct _meshtastic_MqttClientProxyMessage { @@ -1402,6 +1407,7 @@ extern "C" { + #define meshtastic_MeshPacket_priority_ENUMTYPE meshtastic_MeshPacket_Priority #define meshtastic_MeshPacket_delayed_ENUMTYPE meshtastic_MeshPacket_Delayed #define meshtastic_MeshPacket_transport_mechanism_ENUMTYPE meshtastic_MeshPacket_TransportMechanism @@ -1444,6 +1450,7 @@ extern "C" { #define meshtastic_KeyVerification_init_default {0, {0, {0}}, {0, {0}}} #define meshtastic_StoreForwardPlusPlus_init_default {_meshtastic_StoreForwardPlusPlus_SFPP_message_type_MIN, {0, {0}}, {0, {0}}, {0, {0}}, {0, {0}}, 0, 0, 0, 0, 0} #define meshtastic_Waypoint_init_default {0, false, 0, false, 0, 0, 0, "", "", 0} +#define meshtastic_StatusMessage_init_default {""} #define meshtastic_MqttClientProxyMessage_init_default {"", 0, {{0, {0}}}, 0} #define meshtastic_MeshPacket_init_default {0, 0, 0, 0, {meshtastic_Data_init_default}, 0, 0, 0, 0, 0, _meshtastic_MeshPacket_Priority_MIN, 0, _meshtastic_MeshPacket_Delayed_MIN, 0, 0, {0, {0}}, 0, 0, 0, 0, _meshtastic_MeshPacket_TransportMechanism_MIN} #define meshtastic_NodeInfo_init_default {0, false, meshtastic_User_init_default, false, meshtastic_Position_init_default, 0, 0, false, meshtastic_DeviceMetrics_init_default, 0, 0, false, 0, 0, 0, 0, 0} @@ -1476,6 +1483,7 @@ extern "C" { #define meshtastic_KeyVerification_init_zero {0, {0, {0}}, {0, {0}}} #define meshtastic_StoreForwardPlusPlus_init_zero {_meshtastic_StoreForwardPlusPlus_SFPP_message_type_MIN, {0, {0}}, {0, {0}}, {0, {0}}, {0, {0}}, 0, 0, 0, 0, 0} #define meshtastic_Waypoint_init_zero {0, false, 0, false, 0, 0, 0, "", "", 0} +#define meshtastic_StatusMessage_init_zero {""} #define meshtastic_MqttClientProxyMessage_init_zero {"", 0, {{0, {0}}}, 0} #define meshtastic_MeshPacket_init_zero {0, 0, 0, 0, {meshtastic_Data_init_zero}, 0, 0, 0, 0, 0, _meshtastic_MeshPacket_Priority_MIN, 0, _meshtastic_MeshPacket_Delayed_MIN, 0, 0, {0, {0}}, 0, 0, 0, 0, _meshtastic_MeshPacket_TransportMechanism_MIN} #define meshtastic_NodeInfo_init_zero {0, false, meshtastic_User_init_zero, false, meshtastic_Position_init_zero, 0, 0, false, meshtastic_DeviceMetrics_init_zero, 0, 0, false, 0, 0, 0, 0, 0} @@ -1571,6 +1579,7 @@ extern "C" { #define meshtastic_Waypoint_name_tag 6 #define meshtastic_Waypoint_description_tag 7 #define meshtastic_Waypoint_icon_tag 8 +#define meshtastic_StatusMessage_status_tag 1 #define meshtastic_MqttClientProxyMessage_topic_tag 1 #define meshtastic_MqttClientProxyMessage_data_tag 2 #define meshtastic_MqttClientProxyMessage_text_tag 3 @@ -1806,6 +1815,11 @@ X(a, STATIC, SINGULAR, FIXED32, icon, 8) #define meshtastic_Waypoint_CALLBACK NULL #define meshtastic_Waypoint_DEFAULT NULL +#define meshtastic_StatusMessage_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, STRING, status, 1) +#define meshtastic_StatusMessage_CALLBACK NULL +#define meshtastic_StatusMessage_DEFAULT NULL + #define meshtastic_MqttClientProxyMessage_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, STRING, topic, 1) \ X(a, STATIC, ONEOF, BYTES, (payload_variant,data,payload_variant.data), 2) \ @@ -2072,6 +2086,7 @@ extern const pb_msgdesc_t meshtastic_Data_msg; extern const pb_msgdesc_t meshtastic_KeyVerification_msg; extern const pb_msgdesc_t meshtastic_StoreForwardPlusPlus_msg; extern const pb_msgdesc_t meshtastic_Waypoint_msg; +extern const pb_msgdesc_t meshtastic_StatusMessage_msg; extern const pb_msgdesc_t meshtastic_MqttClientProxyMessage_msg; extern const pb_msgdesc_t meshtastic_MeshPacket_msg; extern const pb_msgdesc_t meshtastic_NodeInfo_msg; @@ -2106,6 +2121,7 @@ extern const pb_msgdesc_t meshtastic_ChunkedPayloadResponse_msg; #define meshtastic_KeyVerification_fields &meshtastic_KeyVerification_msg #define meshtastic_StoreForwardPlusPlus_fields &meshtastic_StoreForwardPlusPlus_msg #define meshtastic_Waypoint_fields &meshtastic_Waypoint_msg +#define meshtastic_StatusMessage_fields &meshtastic_StatusMessage_msg #define meshtastic_MqttClientProxyMessage_fields &meshtastic_MqttClientProxyMessage_msg #define meshtastic_MeshPacket_fields &meshtastic_MeshPacket_msg #define meshtastic_NodeInfo_fields &meshtastic_NodeInfo_msg @@ -2161,6 +2177,7 @@ extern const pb_msgdesc_t meshtastic_ChunkedPayloadResponse_msg; #define meshtastic_QueueStatus_size 23 #define meshtastic_RouteDiscovery_size 256 #define meshtastic_Routing_size 259 +#define meshtastic_StatusMessage_size 81 #define meshtastic_StoreForwardPlusPlus_size 377 #define meshtastic_ToRadio_size 504 #define meshtastic_User_size 115 diff --git a/src/mesh/generated/meshtastic/module_config.pb.cpp b/src/mesh/generated/meshtastic/module_config.pb.cpp index f262df6a3..bb57c3f2d 100644 --- a/src/mesh/generated/meshtastic/module_config.pb.cpp +++ b/src/mesh/generated/meshtastic/module_config.pb.cpp @@ -51,6 +51,9 @@ PB_BIND(meshtastic_ModuleConfig_CannedMessageConfig, meshtastic_ModuleConfig_Can PB_BIND(meshtastic_ModuleConfig_AmbientLightingConfig, meshtastic_ModuleConfig_AmbientLightingConfig, AUTO) +PB_BIND(meshtastic_ModuleConfig_StatusMessageConfig, meshtastic_ModuleConfig_StatusMessageConfig, AUTO) + + PB_BIND(meshtastic_RemoteHardwarePin, meshtastic_RemoteHardwarePin, AUTO) diff --git a/src/mesh/generated/meshtastic/module_config.pb.h b/src/mesh/generated/meshtastic/module_config.pb.h index dd0151e3f..46a7164d2 100644 --- a/src/mesh/generated/meshtastic/module_config.pb.h +++ b/src/mesh/generated/meshtastic/module_config.pb.h @@ -409,6 +409,12 @@ typedef struct _meshtastic_ModuleConfig_AmbientLightingConfig { uint8_t blue; } meshtastic_ModuleConfig_AmbientLightingConfig; +/* StatusMessage config - Allows setting a status message for a node to periodically rebroadcast */ +typedef struct _meshtastic_ModuleConfig_StatusMessageConfig { + /* The actual status string */ + char node_status[80]; +} meshtastic_ModuleConfig_StatusMessageConfig; + /* A GPIO pin definition for remote hardware module */ typedef struct _meshtastic_RemoteHardwarePin { /* GPIO Pin number (must match Arduino) */ @@ -460,6 +466,8 @@ typedef struct _meshtastic_ModuleConfig { meshtastic_ModuleConfig_DetectionSensorConfig detection_sensor; /* TODO: REPLACE */ meshtastic_ModuleConfig_PaxcounterConfig paxcounter; + /* TODO: REPLACE */ + meshtastic_ModuleConfig_StatusMessageConfig statusmessage; } payload_variant; } meshtastic_ModuleConfig; @@ -515,6 +523,7 @@ extern "C" { #define meshtastic_ModuleConfig_CannedMessageConfig_inputbroker_event_press_ENUMTYPE meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar + #define meshtastic_RemoteHardwarePin_type_ENUMTYPE meshtastic_RemoteHardwarePinType @@ -534,6 +543,7 @@ extern "C" { #define meshtastic_ModuleConfig_TelemetryConfig_init_default {0, 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_ModuleConfig_StatusMessageConfig_init_default {""} #define meshtastic_RemoteHardwarePin_init_default {0, "", _meshtastic_RemoteHardwarePinType_MIN} #define meshtastic_ModuleConfig_init_zero {0, {meshtastic_ModuleConfig_MQTTConfig_init_zero}} #define meshtastic_ModuleConfig_MQTTConfig_init_zero {0, "", "", "", 0, 0, 0, "", 0, 0, false, meshtastic_ModuleConfig_MapReportSettings_init_zero} @@ -550,6 +560,7 @@ extern "C" { #define meshtastic_ModuleConfig_TelemetryConfig_init_zero {0, 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_ModuleConfig_StatusMessageConfig_init_zero {""} #define meshtastic_RemoteHardwarePin_init_zero {0, "", _meshtastic_RemoteHardwarePinType_MIN} /* Field tags (for use in manual encoding/decoding) */ @@ -653,6 +664,7 @@ extern "C" { #define meshtastic_ModuleConfig_AmbientLightingConfig_red_tag 3 #define meshtastic_ModuleConfig_AmbientLightingConfig_green_tag 4 #define meshtastic_ModuleConfig_AmbientLightingConfig_blue_tag 5 +#define meshtastic_ModuleConfig_StatusMessageConfig_node_status_tag 1 #define meshtastic_RemoteHardwarePin_gpio_pin_tag 1 #define meshtastic_RemoteHardwarePin_name_tag 2 #define meshtastic_RemoteHardwarePin_type_tag 3 @@ -672,6 +684,7 @@ extern "C" { #define meshtastic_ModuleConfig_ambient_lighting_tag 11 #define meshtastic_ModuleConfig_detection_sensor_tag 12 #define meshtastic_ModuleConfig_paxcounter_tag 13 +#define meshtastic_ModuleConfig_statusmessage_tag 14 /* Struct field encoding specification for nanopb */ #define meshtastic_ModuleConfig_FIELDLIST(X, a) \ @@ -687,7 +700,8 @@ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,remote_hardware,payload_vari X(a, STATIC, ONEOF, MESSAGE, (payload_variant,neighbor_info,payload_variant.neighbor_info), 10) \ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,ambient_lighting,payload_variant.ambient_lighting), 11) \ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,detection_sensor,payload_variant.detection_sensor), 12) \ -X(a, STATIC, ONEOF, MESSAGE, (payload_variant,paxcounter,payload_variant.paxcounter), 13) +X(a, STATIC, ONEOF, MESSAGE, (payload_variant,paxcounter,payload_variant.paxcounter), 13) \ +X(a, STATIC, ONEOF, MESSAGE, (payload_variant,statusmessage,payload_variant.statusmessage), 14) #define meshtastic_ModuleConfig_CALLBACK NULL #define meshtastic_ModuleConfig_DEFAULT NULL #define meshtastic_ModuleConfig_payload_variant_mqtt_MSGTYPE meshtastic_ModuleConfig_MQTTConfig @@ -703,6 +717,7 @@ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,paxcounter,payload_variant.p #define meshtastic_ModuleConfig_payload_variant_ambient_lighting_MSGTYPE meshtastic_ModuleConfig_AmbientLightingConfig #define meshtastic_ModuleConfig_payload_variant_detection_sensor_MSGTYPE meshtastic_ModuleConfig_DetectionSensorConfig #define meshtastic_ModuleConfig_payload_variant_paxcounter_MSGTYPE meshtastic_ModuleConfig_PaxcounterConfig +#define meshtastic_ModuleConfig_payload_variant_statusmessage_MSGTYPE meshtastic_ModuleConfig_StatusMessageConfig #define meshtastic_ModuleConfig_MQTTConfig_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, BOOL, enabled, 1) \ @@ -865,6 +880,11 @@ X(a, STATIC, SINGULAR, UINT32, blue, 5) #define meshtastic_ModuleConfig_AmbientLightingConfig_CALLBACK NULL #define meshtastic_ModuleConfig_AmbientLightingConfig_DEFAULT NULL +#define meshtastic_ModuleConfig_StatusMessageConfig_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, STRING, node_status, 1) +#define meshtastic_ModuleConfig_StatusMessageConfig_CALLBACK NULL +#define meshtastic_ModuleConfig_StatusMessageConfig_DEFAULT NULL + #define meshtastic_RemoteHardwarePin_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, UINT32, gpio_pin, 1) \ X(a, STATIC, SINGULAR, STRING, name, 2) \ @@ -887,6 +907,7 @@ extern const pb_msgdesc_t meshtastic_ModuleConfig_RangeTestConfig_msg; extern const pb_msgdesc_t meshtastic_ModuleConfig_TelemetryConfig_msg; extern const pb_msgdesc_t meshtastic_ModuleConfig_CannedMessageConfig_msg; extern const pb_msgdesc_t meshtastic_ModuleConfig_AmbientLightingConfig_msg; +extern const pb_msgdesc_t meshtastic_ModuleConfig_StatusMessageConfig_msg; extern const pb_msgdesc_t meshtastic_RemoteHardwarePin_msg; /* Defines for backwards compatibility with code written before nanopb-0.4.0 */ @@ -905,6 +926,7 @@ extern const pb_msgdesc_t meshtastic_RemoteHardwarePin_msg; #define meshtastic_ModuleConfig_TelemetryConfig_fields &meshtastic_ModuleConfig_TelemetryConfig_msg #define meshtastic_ModuleConfig_CannedMessageConfig_fields &meshtastic_ModuleConfig_CannedMessageConfig_msg #define meshtastic_ModuleConfig_AmbientLightingConfig_fields &meshtastic_ModuleConfig_AmbientLightingConfig_msg +#define meshtastic_ModuleConfig_StatusMessageConfig_fields &meshtastic_ModuleConfig_StatusMessageConfig_msg #define meshtastic_RemoteHardwarePin_fields &meshtastic_RemoteHardwarePin_msg /* Maximum encoded size of messages (where known) */ @@ -921,6 +943,7 @@ extern const pb_msgdesc_t meshtastic_RemoteHardwarePin_msg; #define meshtastic_ModuleConfig_RangeTestConfig_size 12 #define meshtastic_ModuleConfig_RemoteHardwareConfig_size 96 #define meshtastic_ModuleConfig_SerialConfig_size 28 +#define meshtastic_ModuleConfig_StatusMessageConfig_size 81 #define meshtastic_ModuleConfig_StoreForwardConfig_size 24 #define meshtastic_ModuleConfig_TelemetryConfig_size 50 #define meshtastic_ModuleConfig_size 227 diff --git a/src/mesh/generated/meshtastic/portnums.pb.h b/src/mesh/generated/meshtastic/portnums.pb.h index 6b89c6a37..d31daa4b2 100644 --- a/src/mesh/generated/meshtastic/portnums.pb.h +++ b/src/mesh/generated/meshtastic/portnums.pb.h @@ -91,6 +91,11 @@ typedef enum _meshtastic_PortNum { This module is specifically for Native Linux nodes, and provides a Git-style chain of messages. */ meshtastic_PortNum_STORE_FORWARD_PLUSPLUS_APP = 35, + /* Node Status module + ENCODING: protobuf + This module allows setting an extra string of status for a node. + Broadcasts on change and on a timer, possibly once a day. */ + meshtastic_PortNum_NODE_STATUS_APP = 36, /* Provides a hardware serial interface to send and receive from the Meshtastic network. Connect to the RX/TX pins of a device with 38400 8N1. Packets received from the Meshtastic network is forwarded to the RX pin while sending a packet to TX will go out to the Mesh network. From 3e4239daf86808331ef413fc91e51ab8b0ac83b4 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 19 Jan 2026 07:22:27 -0600 Subject: [PATCH 10/99] Upgrade trunk (#9330) 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 12e6696c0..9d563a39a 100644 --- a/.trunk/trunk.yaml +++ b/.trunk/trunk.yaml @@ -9,14 +9,14 @@ plugins: lint: enabled: - checkov@3.2.497 - - renovate@42.81.8 + - renovate@42.84.2 - prettier@3.8.0 - - trufflehog@3.92.4 + - trufflehog@3.92.5 - yamllint@1.38.0 - - bandit@1.9.2 + - bandit@1.9.3 - trivy@0.68.2 - taplo@0.10.0 - - ruff@0.14.11 + - ruff@0.14.13 - isort@7.0.0 - markdownlint@0.47.0 - oxipng@10.0.0 @@ -26,7 +26,7 @@ lint: - hadolint@2.14.0 - shfmt@3.6.0 - shellcheck@0.11.0 - - black@25.12.0 + - black@26.1.0 - git-diff-check - gitleaks@8.30.0 - clang-format@16.0.3 From 5c401b8e349e085f47835397d04d37d7667fcd56 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 19 Jan 2026 12:11:13 -0600 Subject: [PATCH 11/99] Update protobufs (#9362) 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 bbde30a0b..d9003b2b6 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit bbde30a0b98b485df8ccc1058facc7d31477f16d +Subproject commit d9003b2b6c9f378066979ec83b013aca441cf240 diff --git a/src/mesh/generated/meshtastic/telemetry.pb.h b/src/mesh/generated/meshtastic/telemetry.pb.h index dec89ba15..131dd9949 100644 --- a/src/mesh/generated/meshtastic/telemetry.pb.h +++ b/src/mesh/generated/meshtastic/telemetry.pb.h @@ -361,6 +361,8 @@ typedef struct _meshtastic_LocalStats { uint32_t heap_free_bytes; /* Number of packets that were dropped because the transmit queue was full. */ uint16_t num_tx_dropped; + /* Noise floor value measured in dBm */ + int32_t noise_floor; } meshtastic_LocalStats; /* Health telemetry metrics */ @@ -458,7 +460,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, 0} +#define meshtastic_LocalStats_init_default {0, 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}} @@ -467,7 +469,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, 0} +#define meshtastic_LocalStats_init_zero {0, 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}} @@ -556,6 +558,7 @@ extern "C" { #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_LocalStats_noise_floor_tag 15 #define meshtastic_HealthMetrics_heart_bpm_tag 1 #define meshtastic_HealthMetrics_spO2_tag 2 #define meshtastic_HealthMetrics_temperature_tag 3 @@ -678,7 +681,8 @@ 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, num_tx_dropped, 14) +X(a, STATIC, SINGULAR, UINT32, num_tx_dropped, 14) \ +X(a, STATIC, SINGULAR, INT32, noise_floor, 15) #define meshtastic_LocalStats_CALLBACK NULL #define meshtastic_LocalStats_DEFAULT NULL @@ -755,7 +759,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 76 +#define meshtastic_LocalStats_size 87 #define meshtastic_Nau7802Config_size 16 #define meshtastic_PowerMetrics_size 81 #define meshtastic_Telemetry_size 272 From ff50ba4002be7bf57110feb4045508672a3e0d82 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Mon, 19 Jan 2026 12:12:01 -0600 Subject: [PATCH 12/99] Remove bsec from OG ESP32 to fix DRAM overflow --- platformio.ini | 27 +++++++++++++++++++++++++++ variants/esp32/esp32.ini | 19 +++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/platformio.ini b/platformio.ini index 8daad1fb3..7ba2c4166 100644 --- a/platformio.ini +++ b/platformio.ini @@ -212,3 +212,30 @@ lib_deps = sensirion/Sensirion Core@0.7.2 # renovate: datasource=custom.pio depName=Sensirion I2C SCD4x packageName=sensirion/library/Sensirion I2C SCD4x sensirion/Sensirion I2C SCD4x@1.1.0 +; Same as environmental_extra but without BSEC (saves ~3.5KB DRAM for original ESP32 targets) +[environmental_extra_no_bsec] +lib_deps = + # renovate: datasource=custom.pio depName=Adafruit BMP3XX packageName=adafruit/library/Adafruit BMP3XX Library + adafruit/Adafruit BMP3XX Library@2.1.6 + # renovate: datasource=custom.pio depName=Adafruit MAX1704X packageName=adafruit/library/Adafruit MAX1704X + adafruit/Adafruit MAX1704X@1.0.3 + # renovate: datasource=custom.pio depName=Adafruit SHTC3 packageName=adafruit/library/Adafruit SHTC3 Library + adafruit/Adafruit SHTC3 Library@1.0.2 + # renovate: datasource=custom.pio depName=Adafruit LPS2X packageName=adafruit/library/Adafruit LPS2X + adafruit/Adafruit LPS2X@2.0.6 + # renovate: datasource=custom.pio depName=Adafruit SHT31 packageName=adafruit/library/Adafruit SHT31 Library + adafruit/Adafruit SHT31 Library@2.2.2 + # renovate: datasource=custom.pio depName=Adafruit VEML7700 packageName=adafruit/library/Adafruit VEML7700 Library + adafruit/Adafruit VEML7700 Library@2.1.6 + # renovate: datasource=custom.pio depName=Adafruit SHT4x packageName=adafruit/library/Adafruit SHT4x Library + adafruit/Adafruit SHT4x Library@1.0.5 + # renovate: datasource=custom.pio depName=SparkFun Qwiic Scale NAU7802 packageName=sparkfun/library/SparkFun Qwiic Scale NAU7802 Arduino Library + sparkfun/SparkFun Qwiic Scale NAU7802 Arduino Library@1.0.6 + # renovate: datasource=custom.pio depName=ClosedCube OPT3001 packageName=closedcube/library/ClosedCube OPT3001 + closedcube/ClosedCube OPT3001@1.1.2 + # renovate: datasource=git-refs depName=meshtastic-DFRobot_LarkWeatherStation packageName=https://github.com/meshtastic/DFRobot_LarkWeatherStation gitBranch=master + https://github.com/meshtastic/DFRobot_LarkWeatherStation/archive/4de3a9cadef0f6a5220a8a906cf9775b02b0040d.zip + # renovate: datasource=custom.pio depName=Sensirion Core packageName=sensirion/library/Sensirion Core + sensirion/Sensirion Core@0.7.2 + # renovate: datasource=custom.pio depName=Sensirion I2C SCD4x packageName=sensirion/library/Sensirion I2C SCD4x + sensirion/Sensirion I2C SCD4x@1.1.0 \ No newline at end of file diff --git a/variants/esp32/esp32.ini b/variants/esp32/esp32.ini index 5999bc098..502d937b0 100644 --- a/variants/esp32/esp32.ini +++ b/variants/esp32/esp32.ini @@ -7,3 +7,22 @@ custom_esp32_kind = esp32 build_flags = ${esp32_common.build_flags} -DMESHTASTIC_EXCLUDE_AUDIO=1 +; Override lib_deps to use environmental_extra_no_bsec instead of environmental_extra +; BSEC library uses ~3.5KB DRAM which causes overflow on original ESP32 targets +lib_deps = + ${arduino_base.lib_deps} + ${networking_base.lib_deps} + ${networking_extra.lib_deps} + ${environmental_base.lib_deps} + ${environmental_extra_no_bsec.lib_deps} + ${radiolib_base.lib_deps} + # renovate: datasource=git-refs depName=meshtastic-esp32_https_server packageName=https://github.com/meshtastic/esp32_https_server gitBranch=master + https://github.com/meshtastic/esp32_https_server/archive/3223704846752e6d545139204837bdb2a55459ca.zip + # renovate: datasource=custom.pio depName=NimBLE-Arduino packageName=h2zero/library/NimBLE-Arduino + h2zero/NimBLE-Arduino@^1.4.3 + # renovate: datasource=git-refs depName=libpax packageName=https://github.com/dbinfrago/libpax gitBranch=master + https://github.com/dbinfrago/libpax/archive/3cdc0371c375676a97967547f4065607d4c53fd1.zip + # renovate: datasource=github-tags depName=XPowersLib packageName=lewisxhe/XPowersLib + https://github.com/lewisxhe/XPowersLib/archive/v0.3.2.zip + # renovate: datasource=custom.pio depName=rweather/Crypto packageName=rweather/library/Crypto + rweather/Crypto@0.4.0 \ No newline at end of file From c38aff7e521c88805cde46d1ec176a87992beca6 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Mon, 19 Jan 2026 15:39:24 -0600 Subject: [PATCH 13/99] Add interrupt for external charge detection (#9332) Tested on Thinknode m4, m6, and T1000-e --- src/Power.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/Power.cpp b/src/Power.cpp index e320f0557..cec881f83 100644 --- a/src/Power.cpp +++ b/src/Power.cpp @@ -723,6 +723,16 @@ bool Power::setup() runASAP = true; }, CHANGE); +#endif +#ifdef EXT_CHRG_DETECT + attachInterrupt( + EXT_CHRG_DETECT, + []() { + power->setIntervalFromNow(0); + runASAP = true; + BaseType_t higherWake = 0; + }, + CHANGE); #endif enabled = found; low_voltage_counter = 0; From fc268d43d04bb81857250fc0cfc38bcd7a4ddfd3 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Mon, 19 Jan 2026 16:57:21 -0600 Subject: [PATCH 14/99] Add Meshtastic exclusion flags for webserver and paxcounter in platformio.ini --- variants/esp32/chatter2/platformio.ini | 2 ++ variants/esp32/m5stack_core/platformio.ini | 2 ++ 2 files changed, 4 insertions(+) diff --git a/variants/esp32/chatter2/platformio.ini b/variants/esp32/chatter2/platformio.ini index 94a846bc9..4218e8503 100644 --- a/variants/esp32/chatter2/platformio.ini +++ b/variants/esp32/chatter2/platformio.ini @@ -6,6 +6,8 @@ build_flags = ${esp32_base.build_flags} -D CHATTER_2 -I variants/esp32/chatter2 + -DMESHTASTIC_EXCLUDE_WEBSERVER=1 + -DMESHTASTIC_EXCLUDE_PAXCOUNTER=1 lib_deps = ${esp32_base.lib_deps} diff --git a/variants/esp32/m5stack_core/platformio.ini b/variants/esp32/m5stack_core/platformio.ini index d6c5c5b4c..4544d73b5 100644 --- a/variants/esp32/m5stack_core/platformio.ini +++ b/variants/esp32/m5stack_core/platformio.ini @@ -16,6 +16,8 @@ build_flags = ${esp32_base.build_flags} -I variants/esp32/m5stack_core -DM5STACK + -DMESHTASTIC_EXCLUDE_WEBSERVER=1 + -DMESHTASTIC_EXCLUDE_PAXCOUNTER=1 -DUSER_SETUP_LOADED -DTFT_SDA_READ -DTFT_DRIVER=0x9341 From fb3bf783dd971cb1ac33ef6fcaa0eacb0a010880 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Mon, 19 Jan 2026 20:00:41 -0600 Subject: [PATCH 15/99] Implement graduated scaling for NodeInfo send timeout based on active mesh size (#9364) * Implement graduated scaling for NodeInfo send timeout based on active mesh size * Shorter timeout still needed for pubkey unkown and ad-hoc send * Update src/modules/NodeInfoModule.cpp Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/modules/NodeInfoModule.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/modules/NodeInfoModule.cpp b/src/modules/NodeInfoModule.cpp index 7db8b66cc..a568505ae 100644 --- a/src/modules/NodeInfoModule.cpp +++ b/src/modules/NodeInfoModule.cpp @@ -2,6 +2,7 @@ #include "Default.h" #include "MeshService.h" #include "NodeDB.h" +#include "NodeStatus.h" #include "RTC.h" #include "Router.h" #include "configuration.h" @@ -129,14 +130,17 @@ meshtastic_MeshPacket *NodeInfoModule::allocReply() LOG_DEBUG("Skip send NodeInfo > 40%% ch. util"); return NULL; } - // If we sent our NodeInfo less than 5 min. ago, don't send it again as it may be still underway. - if (!shorterTimeout && lastSentToMesh && Throttle::isWithinTimespanMs(lastSentToMesh, 5 * 60 * 1000)) { - LOG_DEBUG("Skip send NodeInfo since we sent it <5min ago"); + + // Use graduated scaling based on active mesh size (10 minute base, scales with congestion coefficient) + uint32_t timeoutMs = Default::getConfiguredOrDefaultMsScaled(0, 10 * 60, nodeStatus->getNumOnline()); + if (!shorterTimeout && lastSentToMesh && Throttle::isWithinTimespanMs(lastSentToMesh, timeoutMs)) { + LOG_DEBUG("Skip send NodeInfo since we sent it <%us ago", timeoutMs / 1000); ignoreRequest = true; // Mark it as ignored for MeshModule return NULL; } else if (shorterTimeout && lastSentToMesh && Throttle::isWithinTimespanMs(lastSentToMesh, 60 * 1000)) { + // For interactive/urgent requests (e.g., user-triggered or implicit requests), use a shorter 60s timeout LOG_DEBUG("Skip send NodeInfo since we sent it <60s ago"); - ignoreRequest = true; // Mark it as ignored for MeshModule + ignoreRequest = true; return NULL; } else { ignoreRequest = false; // Don't ignore requests anymore From fb6d199d366629aff081c8fee526ca704302f325 Mon Sep 17 00:00:00 2001 From: Andrew Yong Date: Tue, 20 Jan 2026 20:38:04 +0800 Subject: [PATCH 16/99] feat: Add Russell, a board designed to go Up! on a balloon (#9079) Hardware repository: https://github.com/Meshtastic-Malaysia/russell - Designed to mount on an ER34615/IFR32700 cell - RAK3172 STM32WLE5CCU6 MCU + integrated SX1262 LoRa - CDtop CD-PA1010D GPS - Bosch Sensortec BME280 sensor - Consonance CN3158 LiFePO4 solar charger Signed-off-by: Andrew Yong --- variants/stm32/russell/platformio.ini | 21 ++++++++++++++ variants/stm32/russell/rfswitch.h | 7 +++++ variants/stm32/russell/variant.h | 41 +++++++++++++++++++++++++++ 3 files changed, 69 insertions(+) create mode 100644 variants/stm32/russell/platformio.ini create mode 100644 variants/stm32/russell/rfswitch.h create mode 100644 variants/stm32/russell/variant.h diff --git a/variants/stm32/russell/platformio.ini b/variants/stm32/russell/platformio.ini new file mode 100644 index 000000000..0dd57a2c7 --- /dev/null +++ b/variants/stm32/russell/platformio.ini @@ -0,0 +1,21 @@ +; Russell is a board designed to mount on an ER34615/IFR32700 cell and go Up! on a balloon +; Hardware repository: https://github.com/Meshtastic-Malaysia/russell +; - RAK3172 STM32WLE5CCU6 MCU + integrated SX1262 LoRa +; - CDtop CD-PA1010D GPS +; - Bosch Sensortec BME280 sensor +; - Consonance CN3158 LiFePO4 solar charger +[env:russell] +extends = stm32_base +board = wiscore_rak3172 +board_level = extra +board_upload.maximum_size = 233472 ; reserve the last 28KB for filesystem +build_flags = + ${stm32_base.build_flags} + -Ivariants/stm32/russell + -DPRIVATE_HW +lib_deps = + ${stm32_base.lib_deps} + # renovate: datasource=custom.pio depName=Adafruit BME280 packageName=adafruit/library/Adafruit BME280 Library + adafruit/Adafruit BME280 Library@2.3.0 + +upload_port = stlink diff --git a/variants/stm32/russell/rfswitch.h b/variants/stm32/russell/rfswitch.h new file mode 100644 index 000000000..ec4829de6 --- /dev/null +++ b/variants/stm32/russell/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}; diff --git a/variants/stm32/russell/variant.h b/variants/stm32/russell/variant.h new file mode 100644 index 000000000..796302d34 --- /dev/null +++ b/variants/stm32/russell/variant.h @@ -0,0 +1,41 @@ +#ifndef _VARIANT_RUSSELL_ +#define _VARIANT_RUSSELL_ + +#define USE_STM32WLx + +// I/O +#define LED_PIN PA0 // Red LED +#define LED_STATE_ON 1 +#define BUTTON_PIN PH3 // Shared with BOOT0 +#define BUTTON_NEED_PULLUP +// Charger IC charge/standby pins are open-drain with no hardware pull-up: +// Internal pull-up is needed on STM32 (TODO) +// #define EXT_CHRG_DETECT PA5 +// #define EXT_PWR_DETECT PA4 + +// Bosch Sensortec BME280 +#define HAS_SENSOR 1 + +// CDtop CD-PA1010D +#define ENABLE_HWSERIAL1 +#define PIN_SERIAL1_RX PB7 +#define PIN_SERIAL1_TX PB6 +#define HAS_GPS 1 +#define PIN_GPS_STANDBY PA15 +#define GPS_RX_PIN PB7 +#define GPS_TX_PIN PB6 + +// LoRa +/* + * RAK3172 (-20–85°C) -> No TCXO + * RAK3172-T (-40–85°C) -> 3.0V TCXO + * https://github.com/RAKWireless/RAK-STM32-RUI/blob/e5a28be8fab1a492bd9223dd425ca33a8a297d90/variants/WisDuo_RAK3172-T_Board/radio_conf.h#L91 + */ +#define TCXO_OPTIONAL +#define SX126X_DIO3_TCXO_VOLTAGE 3.0 + +// Required to avoid Serial1 conflicts due to board definition here: +// https://github.com/stm32duino/Arduino_Core_STM32/blob/main/variants/STM32WLxx/WL54CCU_WL55CCU_WLE4C(8-B-C)U_WLE5C(8-B-C)U/variant_RAK3172_MODULE.h +#define RAK3172 + +#endif From eefc08087d66b3b4298b76d9f8d8803ae372fe30 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 20 Jan 2026 10:29:11 -0600 Subject: [PATCH 17/99] Update protobufs (#9371) Co-authored-by: jp-bennett <5630967+jp-bennett@users.noreply.github.com> --- protobufs | 2 +- src/mesh/generated/meshtastic/admin.pb.h | 8 +++++--- src/mesh/generated/meshtastic/deviceonly.pb.h | 2 +- src/mesh/generated/meshtastic/localonly.pb.h | 16 +++++++++++----- 4 files changed, 18 insertions(+), 10 deletions(-) diff --git a/protobufs b/protobufs index d9003b2b6..77c8329a5 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit d9003b2b6c9f378066979ec83b013aca441cf240 +Subproject commit 77c8329a59a9c96a61c447b5d5f1a52ca583e4f2 diff --git a/src/mesh/generated/meshtastic/admin.pb.h b/src/mesh/generated/meshtastic/admin.pb.h index 26b4343e9..efdead91b 100644 --- a/src/mesh/generated/meshtastic/admin.pb.h +++ b/src/mesh/generated/meshtastic/admin.pb.h @@ -77,7 +77,9 @@ typedef enum _meshtastic_AdminMessage_ModuleConfigType { /* TODO: REPLACE */ meshtastic_AdminMessage_ModuleConfigType_DETECTIONSENSOR_CONFIG = 11, /* TODO: REPLACE */ - meshtastic_AdminMessage_ModuleConfigType_PAXCOUNTER_CONFIG = 12 + meshtastic_AdminMessage_ModuleConfigType_PAXCOUNTER_CONFIG = 12, + /* TODO: REPLACE */ + meshtastic_AdminMessage_ModuleConfigType_STATUSMESSAGE_CONFIG = 13 } meshtastic_AdminMessage_ModuleConfigType; typedef enum _meshtastic_AdminMessage_BackupLocation { @@ -323,8 +325,8 @@ extern "C" { #define _meshtastic_AdminMessage_ConfigType_ARRAYSIZE ((meshtastic_AdminMessage_ConfigType)(meshtastic_AdminMessage_ConfigType_DEVICEUI_CONFIG+1)) #define _meshtastic_AdminMessage_ModuleConfigType_MIN meshtastic_AdminMessage_ModuleConfigType_MQTT_CONFIG -#define _meshtastic_AdminMessage_ModuleConfigType_MAX meshtastic_AdminMessage_ModuleConfigType_PAXCOUNTER_CONFIG -#define _meshtastic_AdminMessage_ModuleConfigType_ARRAYSIZE ((meshtastic_AdminMessage_ModuleConfigType)(meshtastic_AdminMessage_ModuleConfigType_PAXCOUNTER_CONFIG+1)) +#define _meshtastic_AdminMessage_ModuleConfigType_MAX meshtastic_AdminMessage_ModuleConfigType_STATUSMESSAGE_CONFIG +#define _meshtastic_AdminMessage_ModuleConfigType_ARRAYSIZE ((meshtastic_AdminMessage_ModuleConfigType)(meshtastic_AdminMessage_ModuleConfigType_STATUSMESSAGE_CONFIG+1)) #define _meshtastic_AdminMessage_BackupLocation_MIN meshtastic_AdminMessage_BackupLocation_FLASH #define _meshtastic_AdminMessage_BackupLocation_MAX meshtastic_AdminMessage_BackupLocation_SD diff --git a/src/mesh/generated/meshtastic/deviceonly.pb.h b/src/mesh/generated/meshtastic/deviceonly.pb.h index 409805d24..57e7df8fc 100644 --- a/src/mesh/generated/meshtastic/deviceonly.pb.h +++ b/src/mesh/generated/meshtastic/deviceonly.pb.h @@ -361,7 +361,7 @@ extern const pb_msgdesc_t meshtastic_BackupPreferences_msg; /* Maximum encoded size of messages (where known) */ /* meshtastic_NodeDatabase_size depends on runtime parameters */ #define MESHTASTIC_MESHTASTIC_DEVICEONLY_PB_H_MAX_SIZE meshtastic_BackupPreferences_size -#define meshtastic_BackupPreferences_size 2279 +#define meshtastic_BackupPreferences_size 2362 #define meshtastic_ChannelFile_size 718 #define meshtastic_DeviceState_size 1737 #define meshtastic_NodeInfoLite_size 196 diff --git a/src/mesh/generated/meshtastic/localonly.pb.h b/src/mesh/generated/meshtastic/localonly.pb.h index 2b44d0c9a..f11b13419 100644 --- a/src/mesh/generated/meshtastic/localonly.pb.h +++ b/src/mesh/generated/meshtastic/localonly.pb.h @@ -87,6 +87,9 @@ typedef struct _meshtastic_LocalModuleConfig { /* Paxcounter Config */ bool has_paxcounter; meshtastic_ModuleConfig_PaxcounterConfig paxcounter; + /* StatusMessage Config */ + bool has_statusmessage; + meshtastic_ModuleConfig_StatusMessageConfig statusmessage; } meshtastic_LocalModuleConfig; @@ -96,9 +99,9 @@ extern "C" { /* Initializer values for message structs */ #define meshtastic_LocalConfig_init_default {false, meshtastic_Config_DeviceConfig_init_default, false, meshtastic_Config_PositionConfig_init_default, false, meshtastic_Config_PowerConfig_init_default, false, meshtastic_Config_NetworkConfig_init_default, false, meshtastic_Config_DisplayConfig_init_default, false, meshtastic_Config_LoRaConfig_init_default, false, meshtastic_Config_BluetoothConfig_init_default, 0, false, meshtastic_Config_SecurityConfig_init_default} -#define meshtastic_LocalModuleConfig_init_default {false, meshtastic_ModuleConfig_MQTTConfig_init_default, false, meshtastic_ModuleConfig_SerialConfig_init_default, false, meshtastic_ModuleConfig_ExternalNotificationConfig_init_default, false, meshtastic_ModuleConfig_StoreForwardConfig_init_default, false, meshtastic_ModuleConfig_RangeTestConfig_init_default, false, meshtastic_ModuleConfig_TelemetryConfig_init_default, false, meshtastic_ModuleConfig_CannedMessageConfig_init_default, 0, false, meshtastic_ModuleConfig_AudioConfig_init_default, false, meshtastic_ModuleConfig_RemoteHardwareConfig_init_default, false, meshtastic_ModuleConfig_NeighborInfoConfig_init_default, false, meshtastic_ModuleConfig_AmbientLightingConfig_init_default, false, meshtastic_ModuleConfig_DetectionSensorConfig_init_default, false, meshtastic_ModuleConfig_PaxcounterConfig_init_default} +#define meshtastic_LocalModuleConfig_init_default {false, meshtastic_ModuleConfig_MQTTConfig_init_default, false, meshtastic_ModuleConfig_SerialConfig_init_default, false, meshtastic_ModuleConfig_ExternalNotificationConfig_init_default, false, meshtastic_ModuleConfig_StoreForwardConfig_init_default, false, meshtastic_ModuleConfig_RangeTestConfig_init_default, false, meshtastic_ModuleConfig_TelemetryConfig_init_default, false, meshtastic_ModuleConfig_CannedMessageConfig_init_default, 0, false, meshtastic_ModuleConfig_AudioConfig_init_default, false, meshtastic_ModuleConfig_RemoteHardwareConfig_init_default, false, meshtastic_ModuleConfig_NeighborInfoConfig_init_default, false, meshtastic_ModuleConfig_AmbientLightingConfig_init_default, false, meshtastic_ModuleConfig_DetectionSensorConfig_init_default, false, meshtastic_ModuleConfig_PaxcounterConfig_init_default, false, meshtastic_ModuleConfig_StatusMessageConfig_init_default} #define meshtastic_LocalConfig_init_zero {false, meshtastic_Config_DeviceConfig_init_zero, false, meshtastic_Config_PositionConfig_init_zero, false, meshtastic_Config_PowerConfig_init_zero, false, meshtastic_Config_NetworkConfig_init_zero, false, meshtastic_Config_DisplayConfig_init_zero, false, meshtastic_Config_LoRaConfig_init_zero, false, meshtastic_Config_BluetoothConfig_init_zero, 0, false, meshtastic_Config_SecurityConfig_init_zero} -#define meshtastic_LocalModuleConfig_init_zero {false, meshtastic_ModuleConfig_MQTTConfig_init_zero, false, meshtastic_ModuleConfig_SerialConfig_init_zero, false, meshtastic_ModuleConfig_ExternalNotificationConfig_init_zero, false, meshtastic_ModuleConfig_StoreForwardConfig_init_zero, false, meshtastic_ModuleConfig_RangeTestConfig_init_zero, false, meshtastic_ModuleConfig_TelemetryConfig_init_zero, false, meshtastic_ModuleConfig_CannedMessageConfig_init_zero, 0, false, meshtastic_ModuleConfig_AudioConfig_init_zero, false, meshtastic_ModuleConfig_RemoteHardwareConfig_init_zero, false, meshtastic_ModuleConfig_NeighborInfoConfig_init_zero, false, meshtastic_ModuleConfig_AmbientLightingConfig_init_zero, false, meshtastic_ModuleConfig_DetectionSensorConfig_init_zero, false, meshtastic_ModuleConfig_PaxcounterConfig_init_zero} +#define meshtastic_LocalModuleConfig_init_zero {false, meshtastic_ModuleConfig_MQTTConfig_init_zero, false, meshtastic_ModuleConfig_SerialConfig_init_zero, false, meshtastic_ModuleConfig_ExternalNotificationConfig_init_zero, false, meshtastic_ModuleConfig_StoreForwardConfig_init_zero, false, meshtastic_ModuleConfig_RangeTestConfig_init_zero, false, meshtastic_ModuleConfig_TelemetryConfig_init_zero, false, meshtastic_ModuleConfig_CannedMessageConfig_init_zero, 0, false, meshtastic_ModuleConfig_AudioConfig_init_zero, false, meshtastic_ModuleConfig_RemoteHardwareConfig_init_zero, false, meshtastic_ModuleConfig_NeighborInfoConfig_init_zero, false, meshtastic_ModuleConfig_AmbientLightingConfig_init_zero, false, meshtastic_ModuleConfig_DetectionSensorConfig_init_zero, false, meshtastic_ModuleConfig_PaxcounterConfig_init_zero, false, meshtastic_ModuleConfig_StatusMessageConfig_init_zero} /* Field tags (for use in manual encoding/decoding) */ #define meshtastic_LocalConfig_device_tag 1 @@ -124,6 +127,7 @@ extern "C" { #define meshtastic_LocalModuleConfig_ambient_lighting_tag 12 #define meshtastic_LocalModuleConfig_detection_sensor_tag 13 #define meshtastic_LocalModuleConfig_paxcounter_tag 14 +#define meshtastic_LocalModuleConfig_statusmessage_tag 15 /* Struct field encoding specification for nanopb */ #define meshtastic_LocalConfig_FIELDLIST(X, a) \ @@ -161,7 +165,8 @@ X(a, STATIC, OPTIONAL, MESSAGE, remote_hardware, 10) \ X(a, STATIC, OPTIONAL, MESSAGE, neighbor_info, 11) \ X(a, STATIC, OPTIONAL, MESSAGE, ambient_lighting, 12) \ X(a, STATIC, OPTIONAL, MESSAGE, detection_sensor, 13) \ -X(a, STATIC, OPTIONAL, MESSAGE, paxcounter, 14) +X(a, STATIC, OPTIONAL, MESSAGE, paxcounter, 14) \ +X(a, STATIC, OPTIONAL, MESSAGE, statusmessage, 15) #define meshtastic_LocalModuleConfig_CALLBACK NULL #define meshtastic_LocalModuleConfig_DEFAULT NULL #define meshtastic_LocalModuleConfig_mqtt_MSGTYPE meshtastic_ModuleConfig_MQTTConfig @@ -177,6 +182,7 @@ X(a, STATIC, OPTIONAL, MESSAGE, paxcounter, 14) #define meshtastic_LocalModuleConfig_ambient_lighting_MSGTYPE meshtastic_ModuleConfig_AmbientLightingConfig #define meshtastic_LocalModuleConfig_detection_sensor_MSGTYPE meshtastic_ModuleConfig_DetectionSensorConfig #define meshtastic_LocalModuleConfig_paxcounter_MSGTYPE meshtastic_ModuleConfig_PaxcounterConfig +#define meshtastic_LocalModuleConfig_statusmessage_MSGTYPE meshtastic_ModuleConfig_StatusMessageConfig extern const pb_msgdesc_t meshtastic_LocalConfig_msg; extern const pb_msgdesc_t meshtastic_LocalModuleConfig_msg; @@ -186,9 +192,9 @@ extern const pb_msgdesc_t meshtastic_LocalModuleConfig_msg; #define meshtastic_LocalModuleConfig_fields &meshtastic_LocalModuleConfig_msg /* Maximum encoded size of messages (where known) */ -#define MESHTASTIC_MESHTASTIC_LOCALONLY_PB_H_MAX_SIZE meshtastic_LocalConfig_size +#define MESHTASTIC_MESHTASTIC_LOCALONLY_PB_H_MAX_SIZE meshtastic_LocalModuleConfig_size #define meshtastic_LocalConfig_size 749 -#define meshtastic_LocalModuleConfig_size 675 +#define meshtastic_LocalModuleConfig_size 758 #ifdef __cplusplus } /* extern "C" */ From 3e3299f5491008732ee5568d967a2531b12ee383 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 22 Jan 2026 09:07:23 +1100 Subject: [PATCH 18/99] Update meshtastic/device-ui digest to 613c095 (#9383) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index 7ba2c4166..016bc1b2d 100644 --- a/platformio.ini +++ b/platformio.ini @@ -119,7 +119,7 @@ lib_deps = [device-ui_base] lib_deps = # renovate: datasource=git-refs depName=meshtastic/device-ui packageName=https://github.com/meshtastic/device-ui gitBranch=master - https://github.com/meshtastic/device-ui/archive/3480b731d28b10d73414cf0dd7975bff745de8cf.zip + https://github.com/meshtastic/device-ui/archive/613c0953313bbd236f4ddc5ede447e9edf8e890a.zip ; Common libs for environmental measurements in telemetry module [environmental_base] From d8d02cd6ec0c18beac6a256464ac5a30cf368e77 Mon Sep 17 00:00:00 2001 From: Chloe Bethel Date: Thu, 22 Jan 2026 13:08:43 +0000 Subject: [PATCH 19/99] Implement setting TX_GAIN_LORA for portduino (#8501) * Implement setting TX_GAIN_LORA for portduino * use std::size instead of sizeof --------- Co-authored-by: Ben Meadors --- src/configuration.h | 4 ++++ src/mesh/RadioInterface.cpp | 26 +++++++++++++++--------- src/platform/portduino/PortduinoGlue.cpp | 13 ++++++++++++ src/platform/portduino/PortduinoGlue.h | 13 ++++++++++++ 4 files changed, 46 insertions(+), 10 deletions(-) diff --git a/src/configuration.h b/src/configuration.h index 59bffe7be..f7b438272 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -155,6 +155,10 @@ along with this program. If not, see . #endif // Default system gain to 0 if not defined +#ifndef NUM_PA_POINTS +#define NUM_PA_POINTS 1 +#endif + #ifndef TX_GAIN_LORA #define TX_GAIN_LORA 0 #endif diff --git a/src/mesh/RadioInterface.cpp b/src/mesh/RadioInterface.cpp index aaaca719e..d0097a46a 100644 --- a/src/mesh/RadioInterface.cpp +++ b/src/mesh/RadioInterface.cpp @@ -666,18 +666,24 @@ void RadioInterface::limitPower(int8_t loraMaxPower) power = maxPower; } -#ifndef NUM_PA_POINTS - 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; - } +#ifdef ARCH_PORTDUINO + size_t num_pa_points = portduino_config.num_pa_points; + const uint16_t *tx_gain = portduino_config.tx_gain_lora; #else - if (!devicestate.owner.is_licensed) { + size_t num_pa_points = NUM_PA_POINTS; + const uint16_t tx_gain[NUM_PA_POINTS] = {TX_GAIN_LORA}; +#endif + + if (num_pa_points == 1) { + if (tx_gain[0] > 0 && !devicestate.owner.is_licensed) { + LOG_INFO("Requested Tx power: %d dBm; Device LoRa Tx gain: %d dB", power, tx_gain[0]); + power -= tx_gain[0]; + } + } else 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++) { + 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))) { + ((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]; @@ -685,7 +691,7 @@ void RadioInterface::limitPower(int8_t loraMaxPower) } } } -#endif + if (power > loraMaxPower) // Clamp power to maximum defined level power = loraMaxPower; diff --git a/src/platform/portduino/PortduinoGlue.cpp b/src/platform/portduino/PortduinoGlue.cpp index ec9bbedca..530cbe13e 100644 --- a/src/platform/portduino/PortduinoGlue.cpp +++ b/src/platform/portduino/PortduinoGlue.cpp @@ -649,6 +649,19 @@ bool loadConfig(const char *configPath) if (yamlConfig["Lora"]["RF95_MAX_POWER"]) portduino_config.rf95_max_power = yamlConfig["Lora"]["RF95_MAX_POWER"].as(20); + if (yamlConfig["Lora"]["TX_GAIN_LORA"]) { + YAML::Node tx_gain_node = yamlConfig["Lora"]["TX_GAIN_LORA"]; + if (tx_gain_node.IsSequence() && tx_gain_node.size() != 0) { + portduino_config.num_pa_points = min(tx_gain_node.size(), std::size(portduino_config.tx_gain_lora)); + for (int i = 0; i < portduino_config.num_pa_points; i++) { + portduino_config.tx_gain_lora[i] = tx_gain_node[i].as(); + } + } else { + portduino_config.num_pa_points = 1; + portduino_config.tx_gain_lora[0] = tx_gain_node.as(0); + } + } + 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); diff --git a/src/platform/portduino/PortduinoGlue.h b/src/platform/portduino/PortduinoGlue.h index 3a6887421..eb4eb8925 100644 --- a/src/platform/portduino/PortduinoGlue.h +++ b/src/platform/portduino/PortduinoGlue.h @@ -91,6 +91,8 @@ extern struct portduino_config_struct { int lora_usb_pid = 0x5512; int lora_usb_vid = 0x1A86; int spiSpeed = 2000000; + int num_pa_points = 1; // default to 1 point, with 0 gain + uint16_t tx_gain_lora[22] = {0}; pinMapping lora_cs_pin = {"Lora", "CS"}; pinMapping lora_irq_pin = {"Lora", "IRQ"}; pinMapping lora_busy_pin = {"Lora", "Busy"}; @@ -231,6 +233,17 @@ extern struct portduino_config_struct { 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; + + if (num_pa_points > 1) { + out << YAML::Key << "TX_GAIN_LORA" << YAML::Value << YAML::Flow << YAML::BeginSeq; + for (int i = 0; i < num_pa_points; i++) { + out << YAML::Value << tx_gain_lora[i]; + } + out << YAML::EndSeq; + } else if (tx_gain_lora[0] != 0) { + out << YAML::Key << "TX_GAIN_LORA" << YAML::Value << tx_gain_lora[0]; + } + out << YAML::Key << "DIO2_AS_RF_SWITCH" << YAML::Value << dio2_as_rf_switch; if (dio3_tcxo_voltage != 0) out << YAML::Key << "DIO3_TCXO_VOLTAGE" << YAML::Value << YAML::Precision(3) << (float)dio3_tcxo_voltage / 1000; From 4744010295130e5ff5232f091c34db8954a84524 Mon Sep 17 00:00:00 2001 From: Jorropo Date: Thu, 22 Jan 2026 22:46:37 +0100 Subject: [PATCH 20/99] run trunk fmt -a (#9400) * run trunk fmt -a * fix bracket bug This was introduced by @tedwardd and @thebentern in 021106dfe58a0b2b481c355ad318289aff592e7c. See this diff: else + checkConfigPort = false; printf("Using config file %d\n", TCPPort); --- monitor/filter_c3_exception_decoder.py | 6 ++---- src/platform/portduino/PortduinoGlue.cpp | 11 +++++------ variants/esp32s3/CDEBYTE_EoRa-Hub/rfswitch.h | 3 ++- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/monitor/filter_c3_exception_decoder.py b/monitor/filter_c3_exception_decoder.py index 5e74dc2b9..fbc372bcf 100644 --- a/monitor/filter_c3_exception_decoder.py +++ b/monitor/filter_c3_exception_decoder.py @@ -43,13 +43,11 @@ class Esp32C3ExceptionDecoder(DeviceMonitorFilterBase): self.enabled = self.setup_paths() if self.config.get("env:" + self.environment, "build_type") != "debug": - print( - """ + print(""" Please build project in debug configuration to get more details about an exception. See https://docs.platformio.org/page/projectconf/build_configurations.html -""" - ) +""") return self diff --git a/src/platform/portduino/PortduinoGlue.cpp b/src/platform/portduino/PortduinoGlue.cpp index 530cbe13e..9159d5954 100644 --- a/src/platform/portduino/PortduinoGlue.cpp +++ b/src/platform/portduino/PortduinoGlue.cpp @@ -61,11 +61,12 @@ static error_t parse_opt(int key, char *arg, struct argp_state *state) { switch (key) { case 'p': - if (sscanf(arg, "%d", &TCPPort) < 1) + if (sscanf(arg, "%d", &TCPPort) < 1) { return ARGP_ERR_UNKNOWN; - else + } else { checkConfigPort = false; printf("Using config file %d\n", TCPPort); + } break; case 'c': configPath = arg; @@ -887,10 +888,8 @@ bool loadConfig(const char *configPath) } if (checkConfigPort) { portduino_config.api_port = (yamlConfig["General"]["APIPort"]).as(-1); - if (portduino_config.api_port != -1 && - portduino_config.api_port > 1023 && - portduino_config.api_port < 65536) { - TCPPort = (portduino_config.api_port); + if (portduino_config.api_port != -1 && portduino_config.api_port > 1023 && portduino_config.api_port < 65536) { + TCPPort = (portduino_config.api_port); } } portduino_config.mac_address = (yamlConfig["General"]["MACAddress"]).as(""); diff --git a/variants/esp32s3/CDEBYTE_EoRa-Hub/rfswitch.h b/variants/esp32s3/CDEBYTE_EoRa-Hub/rfswitch.h index 1448b1d74..9bb3af45a 100644 --- a/variants/esp32s3/CDEBYTE_EoRa-Hub/rfswitch.h +++ b/variants/esp32s3/CDEBYTE_EoRa-Hub/rfswitch.h @@ -8,7 +8,8 @@ // DIO6 -> RFSW1_V2 // DIO7 -> not connected on E80 module - note that GNSS and Wifi scanning are not possible. -static const uint32_t rfswitch_dio_pins[] = {RADIOLIB_LR11X0_DIO5, RADIOLIB_LR11X0_DIO6, RADIOLIB_LR11X0_DIO7, RADIOLIB_NC, RADIOLIB_NC}; +static const uint32_t rfswitch_dio_pins[] = {RADIOLIB_LR11X0_DIO5, RADIOLIB_LR11X0_DIO6, RADIOLIB_LR11X0_DIO7, RADIOLIB_NC, + RADIOLIB_NC}; static const Module::RfSwitchMode_t rfswitch_table[] = { // mode DIO5 DIO6 DIO7 From 073eb2c672076157c772da0a5e49ad1a9e7b9638 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 22 Jan 2026 16:19:35 -0600 Subject: [PATCH 21/99] Automated version bumps (#9402) 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 cb8985ee6..6ad8962d1 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.19 + https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.18 diff --git a/debian/changelog b/debian/changelog index 5f25d53ad..38489b074 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +meshtasticd (2.7.19.0) unstable; urgency=medium + + * Version 2.7.19 + + -- GitHub Actions Thu, 22 Jan 2026 22:17:40 +0000 + meshtasticd (2.7.18.0) unstable; urgency=medium * Version 2.7.18 diff --git a/version.properties b/version.properties index 0a028eff0..62145da14 100644 --- a/version.properties +++ b/version.properties @@ -1,4 +1,4 @@ [VERSION] major = 2 minor = 7 -build = 18 +build = 19 From bc2abf3db4f2e1650816fde2a6e1ed17babd4f27 Mon Sep 17 00:00:00 2001 From: HarukiToreda <116696711+HarukiToreda@users.noreply.github.com> Date: Thu, 22 Jan 2026 18:00:21 -0500 Subject: [PATCH 22/99] BaseUI: Bubbles for messages (#9365) * Message Bubbles * Angled edges * Proper indent for messages inside the bubble * Fix message header line width * Correctly calculate text width for the header and shrink Channel Name is on OLED --------- Co-authored-by: Jason P --- src/graphics/draw/MessageRenderer.cpp | 315 ++++++++++++++++++++++---- 1 file changed, 270 insertions(+), 45 deletions(-) diff --git a/src/graphics/draw/MessageRenderer.cpp b/src/graphics/draw/MessageRenderer.cpp index 09b798e06..72746e415 100644 --- a/src/graphics/draw/MessageRenderer.cpp +++ b/src/graphics/draw/MessageRenderer.cpp @@ -6,7 +6,6 @@ #include "MessageStore.h" #include "NodeDB.h" #include "UIRenderer.h" -#include "configuration.h" #include "gps/RTC.h" #include "graphics/Screen.h" #include "graphics/ScreenFonts.h" @@ -20,7 +19,6 @@ // External declarations extern bool hasUnreadMessage; -extern meshtastic_DeviceState devicestate; extern graphics::Screen *screen; using graphics::Emote; @@ -49,7 +47,7 @@ static inline size_t utf8CharLen(uint8_t c) } // Remove variation selectors (FE0F) and skin tone modifiers from emoji so they match your labels -std::string normalizeEmoji(const std::string &s) +static std::string normalizeEmoji(const std::string &s) { std::string out; for (size_t i = 0; i < s.size();) { @@ -82,6 +80,7 @@ uint32_t pauseStart = 0; bool waitingToReset = false; bool scrollStarted = false; static bool didReset = false; +static constexpr int MESSAGE_BLOCK_GAP = 6; void scrollUp() { @@ -111,22 +110,6 @@ void scrollDown() void drawStringWithEmotes(OLEDDisplay *display, int x, int y, const std::string &line, const Emote *emotes, int emoteCount) { - std::string renderLine; - for (size_t i = 0; i < line.size();) { - uint8_t c = (uint8_t)line[i]; - size_t len = utf8CharLen(c); - if (c == 0xEF && i + 2 < line.size() && (uint8_t)line[i + 1] == 0xB8 && (uint8_t)line[i + 2] == 0x8F) { - i += 3; - continue; - } - if (c == 0xF0 && i + 3 < line.size() && (uint8_t)line[i + 1] == 0x9F && (uint8_t)line[i + 2] == 0x8F && - ((uint8_t)line[i + 3] >= 0xBB && (uint8_t)line[i + 3] <= 0xBF)) { - i += 4; - continue; - } - renderLine.append(line, i, len); - i += len; - } int cursorX = x; const int fontHeight = FONT_HEIGHT_SMALL; @@ -203,8 +186,7 @@ void drawStringWithEmotes(OLEDDisplay *display, int x, int y, const std::string // Render the emote (if found) if (matchedEmote && i == nextEmotePos) { - // Vertically center emote relative to font baseline (not just midline) - int iconY = fontY + (fontHeight - matchedEmote->height) / 2; + int iconY = y + (lineHeight - matchedEmote->height) / 2; display->drawXbm(cursorX, iconY, matchedEmote->width, matchedEmote->height, matchedEmote->bitmap); cursorX += matchedEmote->width + 1; i += emojiLen; @@ -423,6 +405,102 @@ static inline int getRenderedLineWidth(OLEDDisplay *display, const std::string & return totalWidth; } +struct MessageBlock { + size_t start; + size_t end; + bool mine; +}; + +static int getDrawnLinePixelBottom(int lineTopY, const std::string &line, bool isHeaderLine) +{ + if (isHeaderLine) { + return lineTopY + (FONT_HEIGHT_SMALL - 1); + } + + int tallest = FONT_HEIGHT_SMALL; + for (int e = 0; e < numEmotes; ++e) { + if (line.find(emotes[e].label) != std::string::npos) { + if (emotes[e].height > tallest) + tallest = emotes[e].height; + } + } + + const int lineHeight = std::max(FONT_HEIGHT_SMALL, tallest); + const int iconTop = lineTopY + (lineHeight - tallest) / 2; + + return iconTop + tallest - 1; +} + +static void drawRoundedRectOutline(OLEDDisplay *display, int x, int y, int w, int h, int r) +{ + if (w <= 1 || h <= 1) + return; + + if (r < 0) + r = 0; + + int maxR = (std::min(w, h) / 2) - 1; + if (r > maxR) + r = maxR; + + if (r == 0) { + display->drawRect(x, y, w, h); + return; + } + + const int x0 = x; + const int y0 = y; + const int x1 = x + w - 1; + const int y1 = y + h - 1; + + // sides + if (x0 + r <= x1 - r) { + display->drawLine(x0 + r, y0, x1 - r, y0); // top + display->drawLine(x0 + r, y1, x1 - r, y1); // bottom + } + if (y0 + r <= y1 - r) { + display->drawLine(x0, y0 + r, x0, y1 - r); // left + display->drawLine(x1, y0 + r, x1, y1 - r); // right + } + + // corner arcs + display->drawCircleQuads(x0 + r, y0 + r, r, 2); // top left + display->drawCircleQuads(x1 - r, y0 + r, r, 1); // top right + display->drawCircleQuads(x1 - r, y1 - r, r, 8); // bottom right + display->drawCircleQuads(x0 + r, y1 - r, r, 4); // bottom left +} + +static std::vector buildMessageBlocks(const std::vector &isHeaderVec, const std::vector &isMineVec) +{ + std::vector blocks; + if (isHeaderVec.empty()) + return blocks; + + size_t start = 0; + bool mine = isMineVec[0]; + + for (size_t i = 1; i < isHeaderVec.size(); ++i) { + if (isHeaderVec[i]) { + MessageBlock b; + b.start = start; + b.end = i - 1; + b.mine = mine; + blocks.push_back(b); + + start = i; + mine = isMineVec[i]; + } + } + + MessageBlock last; + last.start = start; + last.end = isHeaderVec.size() - 1; + last.mine = mine; + blocks.push_back(last); + + return blocks; +} + static void drawMessageScrollbar(OLEDDisplay *display, int visibleHeight, int totalHeight, int scrollOffset, int startY) { if (totalHeight <= visibleHeight) @@ -482,9 +560,14 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16 constexpr int LEFT_MARGIN = 2; constexpr int RIGHT_MARGIN = 2; constexpr int SCROLLBAR_WIDTH = 3; + constexpr int BUBBLE_PAD_X = 3; + constexpr int BUBBLE_PAD_Y = 4; + constexpr int BUBBLE_RADIUS = 4; + constexpr int BUBBLE_MIN_W = 24; + constexpr int BUBBLE_TEXT_INDENT = 2; - const int leftTextWidth = SCREEN_WIDTH - LEFT_MARGIN - RIGHT_MARGIN; - + // Derived widths + const int leftTextWidth = SCREEN_WIDTH - LEFT_MARGIN - RIGHT_MARGIN - (BUBBLE_PAD_X * 2); const int rightTextWidth = SCREEN_WIDTH - LEFT_MARGIN - RIGHT_MARGIN - SCROLLBAR_WIDTH; // Title string depending on mode @@ -547,7 +630,28 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16 char chanType[32] = ""; if (currentMode == ThreadMode::ALL) { if (m.dest == NODENUM_BROADCAST) { - snprintf(chanType, sizeof(chanType), "#%s", channels.getName(m.channelIndex)); + const char *name = channels.getName(m.channelIndex); + if (currentResolution == ScreenResolution::Low || currentResolution == ScreenResolution::UltraLow) { + if (strcmp(name, "ShortTurbo") == 0) + name = "ShortT"; + else if (strcmp(name, "ShortSlow") == 0) + name = "ShortS"; + else if (strcmp(name, "ShortFast") == 0) + name = "ShortF"; + else if (strcmp(name, "MediumSlow") == 0) + name = "MedS"; + else if (strcmp(name, "MediumFast") == 0) + name = "MedF"; + else if (strcmp(name, "LongSlow") == 0) + name = "LongS"; + else if (strcmp(name, "LongFast") == 0) + name = "LongF"; + else if (strcmp(name, "LongTurbo") == 0) + name = "LongT"; + else if (strcmp(name, "LongMod") == 0) + name = "LongM"; + } + snprintf(chanType, sizeof(chanType), "#%s", name); } else { snprintf(chanType, sizeof(chanType), "(DM)"); } @@ -614,8 +718,8 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16 } // Shrink Sender name if needed - int availWidth = SCREEN_WIDTH - display->getStringWidth(timeBuf) - display->getStringWidth(chanType) - - display->getStringWidth(" @...") - 10; + int availWidth = (mine ? rightTextWidth : leftTextWidth) - display->getStringWidth(timeBuf) - + display->getStringWidth(chanType) - display->getStringWidth(" @..."); if (availWidth < 0) availWidth = 0; @@ -667,6 +771,8 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16 cachedLines = allLines; cachedHeights = calculateLineHeights(cachedLines, emotes, isHeader); + std::vector blocks = buildMessageBlocks(isHeader, isMine); + // Scrolling logic (unchanged) int totalHeight = 0; for (size_t i = 0; i < cachedHeights.size(); ++i) @@ -714,12 +820,123 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16 int finalScroll = (int)scrollY; int yOffset = -finalScroll + getTextPositions(display)[1]; + const int contentTop = getTextPositions(display)[1]; + const int contentBottom = scrollBottom; // already excludes nav line + const int rightEdge = SCREEN_WIDTH - SCROLLBAR_WIDTH - RIGHT_MARGIN; + const int bubbleGapY = std::max(1, MESSAGE_BLOCK_GAP / 2); + + std::vector lineTop; + lineTop.resize(cachedLines.size()); + { + int acc = 0; + for (size_t i = 0; i < cachedLines.size(); ++i) { + lineTop[i] = yOffset + acc; + acc += cachedHeights[i]; + } + } + + // Draw bubbles + for (size_t bi = 0; bi < blocks.size(); ++bi) { + const auto &b = blocks[bi]; + if (b.start >= cachedLines.size() || b.end >= cachedLines.size() || b.start > b.end) + continue; + + int visualTop = lineTop[b.start]; + + int topY; + if (isHeader[b.start]) { + // Header start + constexpr int BUBBLE_PAD_TOP_HEADER = 1; // try 1 or 2 + topY = visualTop - BUBBLE_PAD_TOP_HEADER; + } else { + // Body start + bool thisLineHasEmote = false; + for (int e = 0; e < numEmotes; ++e) { + if (cachedLines[b.start].find(emotes[e].label) != std::string::npos) { + thisLineHasEmote = true; + break; + } + } + if (thisLineHasEmote) { + constexpr int EMOTE_PADDING_ABOVE = 4; + visualTop -= EMOTE_PADDING_ABOVE; + } + topY = visualTop - BUBBLE_PAD_Y; + } + int visualBottom = getDrawnLinePixelBottom(lineTop[b.end], cachedLines[b.end], isHeader[b.end]); + int bottomY = visualBottom + BUBBLE_PAD_Y; + + if (bi + 1 < blocks.size()) { + int nextHeaderIndex = (int)blocks[bi + 1].start; + int nextTop = lineTop[nextHeaderIndex]; + int maxBottom = nextTop - 1 - bubbleGapY; + if (bottomY > maxBottom) + bottomY = maxBottom; + } + + if (bottomY <= topY + 2) + continue; + + if (bottomY < contentTop || topY > contentBottom - 1) + continue; + + int maxLineW = 0; + + for (size_t i = b.start; i <= b.end; ++i) { + int w = 0; + if (isHeader[i]) { + w = display->getStringWidth(cachedLines[i].c_str()); + if (b.mine) + w += 12; // room for ACK/NACK/relay mark + } else { + w = getRenderedLineWidth(display, cachedLines[i], emotes, numEmotes); + } + if (w > maxLineW) + maxLineW = w; + } + + int bubbleW = std::max(BUBBLE_MIN_W, maxLineW + (BUBBLE_PAD_X * 2)); + int bubbleH = (bottomY - topY) + 1; + int bubbleX = 0; + if (b.mine) { + bubbleX = rightEdge - bubbleW; + } else { + bubbleX = x; + } + if (bubbleX < x) + bubbleX = x; + if (bubbleX + bubbleW > rightEdge) + bubbleW = std::max(1, rightEdge - bubbleX); + + if (bubbleW > 1 && bubbleH > 1) { + int r = BUBBLE_RADIUS; + int maxR = (std::min(bubbleW, bubbleH) / 2) - 1; + if (maxR < 0) + maxR = 0; + if (r > maxR) + r = maxR; + + drawRoundedRectOutline(display, bubbleX, topY, bubbleW, bubbleH, r); + const int extra = 3; + const int rr = r + extra; + int x1 = bubbleX + bubbleW - 1; + int y1 = topY + bubbleH - 1; + + if (!b.mine) { + // top-left corner square + display->drawLine(bubbleX, topY, bubbleX + rr, topY); + display->drawLine(bubbleX, topY, bubbleX, topY + rr); + } else { + // bottom-right corner square + display->drawLine(x1 - rr, y1, x1, y1); + display->drawLine(x1, y1 - rr, x1, y1); + } + } + } // Render visible lines + int lineY = yOffset; for (size_t i = 0; i < cachedLines.size(); ++i) { - int lineY = yOffset; - for (size_t j = 0; j < i; ++j) - lineY += cachedHeights[j]; if (lineY > -cachedHeights[i] && lineY < scrollBottom) { if (isHeader[i]) { @@ -728,14 +945,28 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16 int headerX; if (isMine[i]) { // push header left to avoid overlap with scrollbar - headerX = SCREEN_WIDTH - w - SCROLLBAR_WIDTH - RIGHT_MARGIN; + headerX = (SCREEN_WIDTH - SCROLLBAR_WIDTH - RIGHT_MARGIN) - w - BUBBLE_TEXT_INDENT; if (headerX < LEFT_MARGIN) headerX = LEFT_MARGIN; } else { - headerX = x; + headerX = x + BUBBLE_PAD_X + BUBBLE_TEXT_INDENT; } display->drawString(headerX, lineY, cachedLines[i].c_str()); + // Draw underline just under header text + int underlineY = lineY + FONT_HEIGHT_SMALL; + + int underlineW = w; + int maxW = rightEdge - headerX; + if (maxW < 0) + maxW = 0; + if (underlineW > maxW) + underlineW = maxW; + + for (int px = 0; px < underlineW; ++px) { + display->setPixel(headerX + px, underlineY); + } + // Draw ACK/NACK mark for our own messages if (isMine[i]) { int markX = headerX - 10; @@ -753,32 +984,28 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16 // AckStatus::NONE → show nothing } - // Draw underline just under header text - int underlineY = lineY + FONT_HEIGHT_SMALL; - for (int px = 0; px < w; ++px) { - display->setPixel(headerX + px, underlineY); - } } else { // Render message line if (isMine[i]) { // Calculate actual rendered width including emotes int renderedWidth = getRenderedLineWidth(display, cachedLines[i], emotes, numEmotes); - int rightX = SCREEN_WIDTH - renderedWidth - SCROLLBAR_WIDTH - RIGHT_MARGIN; + int rightX = (SCREEN_WIDTH - SCROLLBAR_WIDTH - RIGHT_MARGIN) - renderedWidth - BUBBLE_TEXT_INDENT; if (rightX < LEFT_MARGIN) rightX = LEFT_MARGIN; drawStringWithEmotes(display, rightX, lineY, cachedLines[i], emotes, numEmotes); } else { - drawStringWithEmotes(display, x, lineY, cachedLines[i], emotes, numEmotes); + drawStringWithEmotes(display, x + BUBBLE_PAD_X + BUBBLE_TEXT_INDENT, lineY, cachedLines[i], emotes, + numEmotes); } } } + + lineY += cachedHeights[i]; } - int totalContentHeight = totalHeight; - int visibleHeight = usableHeight; // Draw scrollbar - drawMessageScrollbar(display, visibleHeight, totalContentHeight, finalScroll, getTextPositions(display)[1]); + drawMessageScrollbar(display, usableHeight, totalHeight, finalScroll, getTextPositions(display)[1]); graphics::drawCommonHeader(display, x, y, titleStr); graphics::drawCommonFooter(display, x, y); } @@ -841,7 +1068,6 @@ std::vector calculateLineHeights(const std::vector &lines, con constexpr int HEADER_UNDERLINE_GAP = 0; // space between underline and first body line constexpr int HEADER_UNDERLINE_PIX = 1; // underline thickness (1px row drawn) constexpr int BODY_LINE_LEADING = -4; // default vertical leading for normal body lines - constexpr int MESSAGE_BLOCK_GAP = 4; // gap after a message block before a new header constexpr int EMOTE_PADDING_ABOVE = 4; // space above emote line (added to line above) constexpr int EMOTE_PADDING_BELOW = 3; // space below emote line (added to emote line) @@ -851,6 +1077,7 @@ std::vector calculateLineHeights(const std::vector &lines, con for (size_t idx = 0; idx < lines.size(); ++idx) { const auto &line = lines[idx]; const int baseHeight = FONT_HEIGHT_SMALL; + int lineHeight = baseHeight; // Detect if THIS line or NEXT line contains an emote bool hasEmote = false; @@ -872,8 +1099,6 @@ std::vector calculateLineHeights(const std::vector &lines, con } } - int lineHeight = baseHeight; - if (isHeaderVec[idx]) { // Header line spacing lineHeight = baseHeight + HEADER_UNDERLINE_PIX + HEADER_UNDERLINE_GAP; @@ -922,7 +1147,7 @@ void handleNewMessage(OLEDDisplay *display, const StoredMessage &sm, const mesht // Banner logic const meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(packet.from); - char longName[48] = "???"; + char longName[48] = "?"; if (node && node->user.long_name) { strncpy(longName, node->user.long_name, sizeof(longName) - 1); longName[sizeof(longName) - 1] = '\0'; From 73932dd1c38d1a044aad1c456c629b82834d64b2 Mon Sep 17 00:00:00 2001 From: Till Maas Date: Fri, 23 Jan 2026 13:05:29 +0100 Subject: [PATCH 23/99] device-install: Consistently use write-flash (#8868) * flash scripts: Unify indentation * flash scripts: Support esptool v4 and v5 esptool v5 supports commands with dashes and deprecates commands with underscores. Prior versions only support commands with underscores. --- bin/device-install.sh | 25 +++++++++++++++++++------ bin/device-update.sh | 15 +++++++++++++-- 2 files changed, 32 insertions(+), 8 deletions(-) diff --git a/bin/device-install.sh b/bin/device-install.sh index 1778a952d..49427524e 100755 --- a/bin/device-install.sh +++ b/bin/device-install.sh @@ -32,6 +32,19 @@ if ! command -v jq >/dev/null 2>&1; then exit 1 fi +# esptool v5 supports commands with dashes and deprecates commands with +# underscores. Prior versions only support commands with underscores +if ${ESPTOOL_CMD} | grep --quiet write-flash +then + ESPTOOL_WRITE_FLASH=write-flash + ESPTOOL_ERASE_FLASH=erase-flash + ESPTOOL_READ_FLASH_STATUS=read-flash-status +else + ESPTOOL_WRITE_FLASH=write_flash + ESPTOOL_ERASE_FLASH=erase_flash + ESPTOOL_READ_FLASH_STATUS=read_flash_status +fi + set -e # Usage info @@ -83,8 +96,8 @@ while [ $# -gt 0 ]; do done if [[ $BPS_RESET == true ]]; then - $ESPTOOL_CMD --baud $RESET_BAUD --after no_reset read_flash_status - exit 0 + $ESPTOOL_CMD --baud $RESET_BAUD --after no_reset ${ESPTOOL_READ_FLASH_STATUS} + exit 0 fi [ -z "$FILENAME" ] && [ -n "$1" ] && { @@ -144,12 +157,12 @@ if [[ -f "$FILENAME" && "$FILENAME" == *.factory.bin ]]; then fi echo "Trying to flash ${FILENAME}, but first erasing and writing system information" - $ESPTOOL_CMD erase-flash - $ESPTOOL_CMD write-flash $FIRMWARE_OFFSET "${FILENAME}" + $ESPTOOL_CMD ${ESPTOOL_ERASE_FLASH} + $ESPTOOL_CMD ${ESPTOOL_WRITE_FLASH} $FIRMWARE_OFFSET "${FILENAME}" echo "Trying to flash ${OTAFILE} at offset ${OTA_OFFSET}" - $ESPTOOL_CMD write_flash $OTA_OFFSET "${OTAFILE}" + $ESPTOOL_CMD ${ESPTOOL_WRITE_FLASH} $OTA_OFFSET "${OTAFILE}" echo "Trying to flash ${SPIFFSFILE}, at offset ${OFFSET}" - $ESPTOOL_CMD write_flash $OFFSET "${SPIFFSFILE}" + $ESPTOOL_CMD ${ESPTOOL_WRITE_FLASH} $OFFSET "${SPIFFSFILE}" else show_help diff --git a/bin/device-update.sh b/bin/device-update.sh index 1c3d6be70..10eb5eedd 100755 --- a/bin/device-update.sh +++ b/bin/device-update.sh @@ -20,6 +20,17 @@ else exit 1 fi +# esptool v5 supports commands with dashes and deprecates commands with +# underscores. Prior versions only support commands with underscores +if ${ESPTOOL_CMD} | grep --quiet write-flash +then + ESPTOOL_WRITE_FLASH=write-flash + ESPTOOL_READ_FLASH_STATUS=read-flash-status +else + ESPTOOL_WRITE_FLASH=write_flash + ESPTOOL_READ_FLASH_STATUS=read_flash_status +fi + # Usage info show_help() { cat << EOF @@ -69,7 +80,7 @@ done shift "$((OPTIND-1))" if [ "$CHANGE_MODE" = true ]; then - $ESPTOOL_CMD --baud $RESET_BAUD --after no_reset read_flash_status + $ESPTOOL_CMD --baud $RESET_BAUD --after no_reset ${ESPTOOL_READ_FLASH_STATUS} exit 0 fi @@ -80,7 +91,7 @@ fi if [[ -f "$FILENAME" && "$FILENAME" != *.factory.bin ]]; then echo "Trying to flash update ${FILENAME}" - $ESPTOOL_CMD --baud $FLASH_BAUD write-flash $UPDATE_OFFSET "${FILENAME}" + $ESPTOOL_CMD --baud $FLASH_BAUD ${ESPTOOL_WRITE_FLASH} $UPDATE_OFFSET "${FILENAME}" else show_help echo "Invalid file: ${FILENAME}" From 0157a769c350a7430079f5a8000b0008b1b11053 Mon Sep 17 00:00:00 2001 From: Mattijs Date: Fri, 23 Jan 2026 18:26:01 +0100 Subject: [PATCH 24/99] Make BLE TX power configurable for nRF52 variants (#9232) * Make BLE TX power configurable for nRF52 variants * Include BLE TX power setting in T114 variant.h as tested --------- Co-authored-by: Ben Meadors --- src/platform/nrf52/NRF52Bluetooth.cpp | 11 +++++++++++ variants/nrf52840/heltec_mesh_node_t114/variant.h | 8 ++++++++ 2 files changed, 19 insertions(+) diff --git a/src/platform/nrf52/NRF52Bluetooth.cpp b/src/platform/nrf52/NRF52Bluetooth.cpp index 4f7fb4776..d41c7ba0e 100644 --- a/src/platform/nrf52/NRF52Bluetooth.cpp +++ b/src/platform/nrf52/NRF52Bluetooth.cpp @@ -240,6 +240,14 @@ int NRF52Bluetooth::getRssi() { return 0; // FIXME figure out where to source this } + +// Valid BLE TX power levels as per nRF52840 Product Specification are: "-20 to +8 dBm TX power, configurable in 4 dB steps". +// See https://docs.nordicsemi.com/bundle/ps_nrf52840/page/keyfeatures_html5.html +#define VALID_BLE_TX_POWER(x) \ + ((x) == -20 || (x) == -16 || (x) == -12 || \ + (x) == -8 || (x) == -4 || (x) == 0 || \ + (x) == 4 || (x) == 8) + void NRF52Bluetooth::setup() { // Initialise the Bluefruit module @@ -251,6 +259,9 @@ void NRF52Bluetooth::setup() Bluefruit.Advertising.stop(); Bluefruit.Advertising.clearData(); Bluefruit.ScanResponse.clearData(); +#if defined(NRF52_BLE_TX_POWER) && VALID_BLE_TX_POWER(NRF52_BLE_TX_POWER) + Bluefruit.setTxPower(NRF52_BLE_TX_POWER); +#endif if (config.bluetooth.mode != meshtastic_Config_BluetoothConfig_PairingMode_NO_PIN) { configuredPasskey = config.bluetooth.mode == meshtastic_Config_BluetoothConfig_PairingMode_FIXED_PIN ? config.bluetooth.fixed_pin diff --git a/variants/nrf52840/heltec_mesh_node_t114/variant.h b/variants/nrf52840/heltec_mesh_node_t114/variant.h index bad488b35..fb7f61ac7 100644 --- a/variants/nrf52840/heltec_mesh_node_t114/variant.h +++ b/variants/nrf52840/heltec_mesh_node_t114/variant.h @@ -150,6 +150,14 @@ No longer populated on PCB #define PIN_SPI1_MOSI ST7789_SDA #define PIN_SPI1_SCK ST7789_SCK +/* + * Bluetooth + */ + +// The bluetooth transmit power on the nRF52840 is adjustable from -20dB to +8dB in steps of 4dB +// so NRF52_BLE_TX_POWER can be set to -20, -16, -12, -8, -4, 0 (default), 4, and 8. +//#define NRF52_BLE_TX_POWER 8 + /* * GPS pins */ From 6d6a0734b04cff50bf9e1836f0704dc971c8ea7b Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Fri, 23 Jan 2026 15:37:16 -0600 Subject: [PATCH 25/99] Add pin sense to wake M6 on Solar Charge (#9416) --- variants/nrf52840/ELECROW-ThinkNode-M6/variant.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/variants/nrf52840/ELECROW-ThinkNode-M6/variant.cpp b/variants/nrf52840/ELECROW-ThinkNode-M6/variant.cpp index 9c7b521ef..4ce8ecdf0 100644 --- a/variants/nrf52840/ELECROW-ThinkNode-M6/variant.cpp +++ b/variants/nrf52840/ELECROW-ThinkNode-M6/variant.cpp @@ -67,4 +67,8 @@ void variant_shutdown() nrf_gpio_cfg_input(PIN_BUTTON1, NRF_GPIO_PIN_PULLUP); // Configure the pin to be woken up as an input nrf_gpio_pin_sense_t sense1 = NRF_GPIO_PIN_SENSE_LOW; nrf_gpio_cfg_sense_set(PIN_BUTTON1, sense1); + + nrf_gpio_cfg_input(EXT_CHRG_DETECT, NRF_GPIO_PIN_PULLUP); // Configure the pin to be woken up as an input + nrf_gpio_pin_sense_t sense2 = NRF_GPIO_PIN_SENSE_LOW; + nrf_gpio_cfg_sense_set(EXT_CHRG_DETECT, sense2); } From 6b88d37b73e83c595b92179154b13faa67dd5af3 Mon Sep 17 00:00:00 2001 From: "Justin E. Mann" Date: Sat, 24 Jan 2026 04:16:36 -0700 Subject: [PATCH 26/99] To fix the gps power rail issue on RAK 19007 when RAK12023+RAK12035 is installed (#9409) * asked claude to fix the gps power rail issue when the io slot is in use.. this fixes the gps when both the RAK12500 GPS module and the RAK12035 soil sensor modules are being used. * remove do { ... } while(0) from RESTORE_3V3_POWER() Macro * remove some comments * cleaner macro * removed more excessive comments --------- Co-authored-by: Ben Meadors --- .../Telemetry/Sensor/RAK12035Sensor.cpp | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/modules/Telemetry/Sensor/RAK12035Sensor.cpp b/src/modules/Telemetry/Sensor/RAK12035Sensor.cpp index ff0628cc3..626cc0e87 100644 --- a/src/modules/Telemetry/Sensor/RAK12035Sensor.cpp +++ b/src/modules/Telemetry/Sensor/RAK12035Sensor.cpp @@ -4,6 +4,15 @@ #include "../mesh/generated/meshtastic/telemetry.pb.h" #include "RAK12035Sensor.h" +// The RAK12035 library's sensor_sleep() sets WB_IO2 (GPIO 34) LOW, which controls +// the 3.3V switched power rail (PIN_3V3_EN). This turns off power to ALL peripherals +// including GPS. We need to restore power after the library turns it off. +#ifdef PIN_3V3_EN +#define RESTORE_3V3_POWER() digitalWrite(PIN_3V3_EN, HIGH) +#else +#define RESTORE_3V3_POWER() +#endif + RAK12035Sensor::RAK12035Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_RAK12035, "RAK12035") {} bool RAK12035Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) @@ -13,7 +22,6 @@ bool RAK12035Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) delay(100); sensor.begin(dev->address.address); - // Get sensor firmware version uint8_t data = 0; sensor.get_sensor_version(&data); if (data != 0) { @@ -21,8 +29,8 @@ bool RAK12035Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) LOG_INFO("RAK12035Sensor Init Succeed \nSensor1 Firmware version: %i, Sensor Name: %s", data, sensorName); status = true; sensor.sensor_sleep(); + RESTORE_3V3_POWER(); } else { - // If we reach here, it means the sensor did not initialize correctly. LOG_INFO("Init sensor: %s", sensorName); LOG_ERROR("RAK12035Sensor Init Failed"); status = false; @@ -38,8 +46,6 @@ bool RAK12035Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) void RAK12035Sensor::setup() { - // Set the calibration values - // Reading the saved calibration values from the sensor. // TODO:: Check for and run calibration check for up to 2 additional sensors if present. uint16_t zero_val = 0; uint16_t hundred_val = 0; @@ -71,6 +77,7 @@ void RAK12035Sensor::setup() LOG_INFO("Wet calibration reset complete. New value is %d", hundred_val); } sensor.sensor_sleep(); + RESTORE_3V3_POWER(); delay(200); LOG_INFO("Dry calibration value is %d", zero_val); LOG_INFO("Wet calibration value is %d", hundred_val); @@ -79,10 +86,6 @@ void RAK12035Sensor::setup() bool RAK12035Sensor::getMetrics(meshtastic_Telemetry *measurement) { // TODO:: read and send metrics for up to 2 additional soil monitors if present. - // -- how to do this.. this could get a little complex.. - // ie - 1> we combine them into an average and send that, 2> we send them as separate metrics - // ^-- these scenarios would require different handling of the metrics in the receiving end and maybe a setting in the - // device ui and an additional proto for that? measurement->variant.environment_metrics.has_soil_temperature = true; measurement->variant.environment_metrics.has_soil_moisture = true; @@ -97,6 +100,7 @@ bool RAK12035Sensor::getMetrics(meshtastic_Telemetry *measurement) success &= sensor.get_sensor_temperature(&temp); delay(200); sensor.sensor_sleep(); + RESTORE_3V3_POWER(); if (success == false) { LOG_ERROR("Failed to read sensor data"); From 04d2dd3b1c330c3b0dbedd7d36eb5be465b399ab Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 24 Jan 2026 05:16:47 -0600 Subject: [PATCH 27/99] Update GxEPD2 to v1.6.6 (#9412) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- variants/esp32/m5stack_coreink/platformio.ini | 2 +- variants/esp32s3/diy/my_esp32s3_diy_eink/platformio.ini | 2 +- variants/esp32s3/esp32-s3-pico/platformio.ini | 2 +- variants/esp32s3/t-deck-pro/platformio.ini | 2 +- variants/nrf52840/Dongle_nRF52840-pca10059-v1/platformio.ini | 2 +- variants/nrf52840/ME25LS01-4Y10TD_e-ink/platformio.ini | 2 +- variants/nrf52840/MakePython_nRF52840_eink/platformio.ini | 2 +- variants/nrf52840/TWC_mesh_v4/platformio.ini | 2 +- variants/nrf52840/rak4631_epaper/platformio.ini | 2 +- variants/nrf52840/rak4631_epaper_onrxtx/platformio.ini | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/variants/esp32/m5stack_coreink/platformio.ini b/variants/esp32/m5stack_coreink/platformio.ini index a4d44a15e..0a79cab28 100644 --- a/variants/esp32/m5stack_coreink/platformio.ini +++ b/variants/esp32/m5stack_coreink/platformio.ini @@ -19,7 +19,7 @@ build_flags = lib_deps = ${esp32_base.lib_deps} # renovate: datasource=custom.pio depName=GxEPD2 packageName=zinggjm/library/GxEPD2 - zinggjm/GxEPD2@1.6.5 + zinggjm/GxEPD2@1.6.6 # renovate: datasource=custom.pio depName=SensorLib packageName=lewisxhe/library/SensorLib lewisxhe/SensorLib@0.3.3 lib_ignore = diff --git a/variants/esp32s3/diy/my_esp32s3_diy_eink/platformio.ini b/variants/esp32s3/diy/my_esp32s3_diy_eink/platformio.ini index 95e909b91..f44f26006 100644 --- a/variants/esp32s3/diy/my_esp32s3_diy_eink/platformio.ini +++ b/variants/esp32s3/diy/my_esp32s3_diy_eink/platformio.ini @@ -11,7 +11,7 @@ upload_speed = 921600 lib_deps = ${esp32s3_base.lib_deps} # renovate: datasource=custom.pio depName=GxEPD2 packageName=zinggjm/library/GxEPD2 - zinggjm/GxEPD2@1.6.5 + zinggjm/GxEPD2@1.6.6 # renovate: datasource=custom.pio depName=NeoPixel packageName=adafruit/library/Adafruit NeoPixel adafruit/Adafruit NeoPixel@1.15.2 build_unflags = diff --git a/variants/esp32s3/esp32-s3-pico/platformio.ini b/variants/esp32s3/esp32-s3-pico/platformio.ini index 8b65c3ca3..db0c038e6 100644 --- a/variants/esp32s3/esp32-s3-pico/platformio.ini +++ b/variants/esp32s3/esp32-s3-pico/platformio.ini @@ -23,6 +23,6 @@ build_flags = ${esp32s3_base.build_flags} lib_deps = ${esp32s3_base.lib_deps} # renovate: datasource=custom.pio depName=GxEPD2 packageName=zinggjm/library/GxEPD2 - zinggjm/GxEPD2@1.6.5 + zinggjm/GxEPD2@1.6.6 # renovate: datasource=custom.pio depName=NeoPixel packageName=adafruit/library/Adafruit NeoPixel adafruit/Adafruit NeoPixel@1.15.2 diff --git a/variants/esp32s3/t-deck-pro/platformio.ini b/variants/esp32s3/t-deck-pro/platformio.ini index b2c91dcf5..aca31b599 100644 --- a/variants/esp32s3/t-deck-pro/platformio.ini +++ b/variants/esp32s3/t-deck-pro/platformio.ini @@ -29,7 +29,7 @@ build_flags = lib_deps = ${esp32s3_base.lib_deps} # renovate: datasource=custom.pio depName=GxEPD2 packageName=zinggjm/library/GxEPD2 - zinggjm/GxEPD2@1.6.5 + zinggjm/GxEPD2@1.6.6 # renovate: datasource=git-refs depName=CSE_Touch packageName=https://github.com/CIRCUITSTATE/CSE_Touch gitBranch=main https://github.com/CIRCUITSTATE/CSE_Touch/archive/b44f23b6f870b848f1fbe453c190879bc6cfaafa.zip # renovate: datasource=github-tags depName=CSE_CST328 packageName=CIRCUITSTATE/CSE_CST328 diff --git a/variants/nrf52840/Dongle_nRF52840-pca10059-v1/platformio.ini b/variants/nrf52840/Dongle_nRF52840-pca10059-v1/platformio.ini index 880871e13..093c3732d 100644 --- a/variants/nrf52840/Dongle_nRF52840-pca10059-v1/platformio.ini +++ b/variants/nrf52840/Dongle_nRF52840-pca10059-v1/platformio.ini @@ -12,5 +12,5 @@ build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/Dongle_ lib_deps = ${nrf52840_base.lib_deps} # renovate: datasource=custom.pio depName=GxEPD2 packageName=zinggjm/library/GxEPD2 - zinggjm/GxEPD2@1.6.5 + zinggjm/GxEPD2@1.6.6 debug_tool = jlink diff --git a/variants/nrf52840/ME25LS01-4Y10TD_e-ink/platformio.ini b/variants/nrf52840/ME25LS01-4Y10TD_e-ink/platformio.ini index 025e28078..5951a37a3 100644 --- a/variants/nrf52840/ME25LS01-4Y10TD_e-ink/platformio.ini +++ b/variants/nrf52840/ME25LS01-4Y10TD_e-ink/platformio.ini @@ -16,7 +16,7 @@ build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/ME25LS0 lib_deps = ${nrf52840_base.lib_deps} # renovate: datasource=custom.pio depName=GxEPD2 packageName=zinggjm/library/GxEPD2 - zinggjm/GxEPD2@1.6.5 + zinggjm/GxEPD2@1.6.6 ; If not set we will default to uploading over serial (first it forces bootloader entry by talking 1200bps to cdcacm) upload_protocol = nrfutil ;upload_port = /dev/ttyACM1 diff --git a/variants/nrf52840/MakePython_nRF52840_eink/platformio.ini b/variants/nrf52840/MakePython_nRF52840_eink/platformio.ini index d823a7230..ecb630bb7 100644 --- a/variants/nrf52840/MakePython_nRF52840_eink/platformio.ini +++ b/variants/nrf52840/MakePython_nRF52840_eink/platformio.ini @@ -15,6 +15,6 @@ lib_deps = # renovate: datasource=git-refs depName=meshtastic-ESP32_Codec2 packageName=https://github.com/meshtastic/ESP32_Codec2 gitBranch=master https://github.com/meshtastic/ESP32_Codec2/archive/633326c78ac251c059ab3a8c430fcdf25b41672f.zip # renovate: datasource=custom.pio depName=GxEPD2 packageName=zinggjm/library/GxEPD2 - zinggjm/GxEPD2@1.6.5 + zinggjm/GxEPD2@1.6.6 debug_tool = jlink ;upload_port = /dev/ttyACM4 \ No newline at end of file diff --git a/variants/nrf52840/TWC_mesh_v4/platformio.ini b/variants/nrf52840/TWC_mesh_v4/platformio.ini index e196029ec..d93f179c2 100644 --- a/variants/nrf52840/TWC_mesh_v4/platformio.ini +++ b/variants/nrf52840/TWC_mesh_v4/platformio.ini @@ -9,5 +9,5 @@ build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/TWC_mes lib_deps = ${nrf52840_base.lib_deps} # renovate: datasource=custom.pio depName=GxEPD2 packageName=zinggjm/library/GxEPD2 - zinggjm/GxEPD2@1.6.5 + zinggjm/GxEPD2@1.6.6 debug_tool = jlink diff --git a/variants/nrf52840/rak4631_epaper/platformio.ini b/variants/nrf52840/rak4631_epaper/platformio.ini index bc21b7912..f0da832cb 100644 --- a/variants/nrf52840/rak4631_epaper/platformio.ini +++ b/variants/nrf52840/rak4631_epaper/platformio.ini @@ -15,7 +15,7 @@ build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/rak4631 lib_deps = ${nrf52840_base.lib_deps} # renovate: datasource=custom.pio depName=GxEPD2 packageName=zinggjm/library/GxEPD2 - zinggjm/GxEPD2@1.6.5 + zinggjm/GxEPD2@1.6.6 # renovate: datasource=custom.pio depName=Melopero RV3028 packageName=melopero/library/Melopero RV3028 melopero/Melopero RV3028@1.2.0 # renovate: datasource=custom.pio depName=RAK NCP5623 RGB LED packageName=rakwireless/library/RAKwireless NCP5623 RGB LED library diff --git a/variants/nrf52840/rak4631_epaper_onrxtx/platformio.ini b/variants/nrf52840/rak4631_epaper_onrxtx/platformio.ini index d0bca377b..112ddfc29 100644 --- a/variants/nrf52840/rak4631_epaper_onrxtx/platformio.ini +++ b/variants/nrf52840/rak4631_epaper_onrxtx/platformio.ini @@ -17,7 +17,7 @@ build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/rak4631 lib_deps = ${nrf52840_base.lib_deps} # renovate: datasource=custom.pio depName=GxEPD2 packageName=zinggjm/library/GxEPD2 - zinggjm/GxEPD2@1.6.5 + zinggjm/GxEPD2@1.6.6 # renovate: datasource=custom.pio depName=Melopero RV3028 packageName=melopero/library/Melopero RV3028 melopero/Melopero RV3028@1.2.0 # renovate: datasource=custom.pio depName=RAK NCP5623 RGB LED packageName=rakwireless/library/RAKwireless NCP5623 RGB LED library From a417760887cbc25bddc5e2bb9204ae1ea45ababf Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 24 Jan 2026 05:18:48 -0600 Subject: [PATCH 28/99] Update meshtastic/device-ui digest to 37ad715 (#9403) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index 016bc1b2d..ee51d24ec 100644 --- a/platformio.ini +++ b/platformio.ini @@ -119,7 +119,7 @@ lib_deps = [device-ui_base] lib_deps = # renovate: datasource=git-refs depName=meshtastic/device-ui packageName=https://github.com/meshtastic/device-ui gitBranch=master - https://github.com/meshtastic/device-ui/archive/613c0953313bbd236f4ddc5ede447e9edf8e890a.zip + https://github.com/meshtastic/device-ui/archive/37ad715b76cd6ca4aa500a4a4d9740e3cdf3e3cb.zip ; Common libs for environmental measurements in telemetry module [environmental_base] From 7221fc4d4b1f66dd1bc1f52bcbeec888b59b8ddd Mon Sep 17 00:00:00 2001 From: Eric Sesterhenn Date: Sat, 24 Jan 2026 12:19:19 +0100 Subject: [PATCH 29/99] Delete unused code (#9350) * Delete unused code CryptoEngine::clearKeys() is not used in the code base, therefore this cleanup removes the code. It might give casual reviewers the impression, that keys are wiped. Since the code uses memset() which might be optimized away by the compiler, using the code might not even cause the memory to be wiped. * Update CryptoEngine.cpp Fix stray newline, this is the only thing that I can come up with that might confuse the linter. --------- Co-authored-by: Jason P --- src/mesh/CryptoEngine.cpp | 5 ----- src/mesh/CryptoEngine.h | 1 - 2 files changed, 6 deletions(-) diff --git a/src/mesh/CryptoEngine.cpp b/src/mesh/CryptoEngine.cpp index 9ca16878d..0f4d64113 100644 --- a/src/mesh/CryptoEngine.cpp +++ b/src/mesh/CryptoEngine.cpp @@ -61,11 +61,6 @@ bool CryptoEngine::regeneratePublicKey(uint8_t *pubKey, uint8_t *privKey) return true; } #endif -void CryptoEngine::clearKeys() -{ - memset(public_key, 0, sizeof(public_key)); - memset(private_key, 0, sizeof(private_key)); -} /** * Encrypt a packet's payload using a key generated with Curve25519 and SHA256 diff --git a/src/mesh/CryptoEngine.h b/src/mesh/CryptoEngine.h index 6bbcb3b8a..7689006ab 100644 --- a/src/mesh/CryptoEngine.h +++ b/src/mesh/CryptoEngine.h @@ -37,7 +37,6 @@ class CryptoEngine virtual bool regeneratePublicKey(uint8_t *pubKey, uint8_t *privKey); #endif - void clearKeys(); void setDHPrivateKey(uint8_t *_private_key); virtual bool encryptCurve25519(uint32_t toNode, uint32_t fromNode, meshtastic_UserLite_public_key_t remotePublic, uint64_t packetNum, size_t numBytes, const uint8_t *bytes, uint8_t *bytesOut); From 5838b26d90c37702a95f08f4f59e14a38b2bd5b5 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 24 Jan 2026 05:39:05 -0600 Subject: [PATCH 30/99] Update lewisxhe-SensorLib to v0.3.4 (#9395) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- variants/esp32/tbeam/platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/variants/esp32/tbeam/platformio.ini b/variants/esp32/tbeam/platformio.ini index 51952457a..16a3d1845 100644 --- a/variants/esp32/tbeam/platformio.ini +++ b/variants/esp32/tbeam/platformio.ini @@ -32,4 +32,4 @@ lib_deps = # renovate: datasource=github-tags depName=meshtastic-st7796 packageName=meshtastic/st7796 https://github.com/meshtastic/st7796/archive/1.0.5.zip # renovate: datasource=custom.pio depName=lewisxhe-SensorLib packageName=lewisxhe/library/SensorLib - lewisxhe/SensorLib@0.3.3 + lewisxhe/SensorLib@0.3.4 From c98f134b403e4fd8c523b8e106befc8ddec7ee28 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 24 Jan 2026 05:39:22 -0600 Subject: [PATCH 31/99] Update meshtastic-esp32_https_server digest to b0f3960 (#9393) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- variants/esp32/esp32-common.ini | 2 +- variants/esp32/esp32.ini | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/variants/esp32/esp32-common.ini b/variants/esp32/esp32-common.ini index 3ee2b9516..1ff4ce4d8 100644 --- a/variants/esp32/esp32-common.ini +++ b/variants/esp32/esp32-common.ini @@ -59,7 +59,7 @@ lib_deps = ${environmental_extra.lib_deps} ${radiolib_base.lib_deps} # renovate: datasource=git-refs depName=meshtastic-esp32_https_server packageName=https://github.com/meshtastic/esp32_https_server gitBranch=master - https://github.com/meshtastic/esp32_https_server/archive/3223704846752e6d545139204837bdb2a55459ca.zip + https://github.com/meshtastic/esp32_https_server/archive/b0f3960b3e8444563280656d88e22b5899481884.zip # renovate: datasource=custom.pio depName=NimBLE-Arduino packageName=h2zero/library/NimBLE-Arduino h2zero/NimBLE-Arduino@^1.4.3 # renovate: datasource=git-refs depName=libpax packageName=https://github.com/dbinfrago/libpax gitBranch=master diff --git a/variants/esp32/esp32.ini b/variants/esp32/esp32.ini index 502d937b0..2ce9f4d22 100644 --- a/variants/esp32/esp32.ini +++ b/variants/esp32/esp32.ini @@ -17,7 +17,7 @@ lib_deps = ${environmental_extra_no_bsec.lib_deps} ${radiolib_base.lib_deps} # renovate: datasource=git-refs depName=meshtastic-esp32_https_server packageName=https://github.com/meshtastic/esp32_https_server gitBranch=master - https://github.com/meshtastic/esp32_https_server/archive/3223704846752e6d545139204837bdb2a55459ca.zip + https://github.com/meshtastic/esp32_https_server/archive/b0f3960b3e8444563280656d88e22b5899481884.zip # renovate: datasource=custom.pio depName=NimBLE-Arduino packageName=h2zero/library/NimBLE-Arduino h2zero/NimBLE-Arduino@^1.4.3 # renovate: datasource=git-refs depName=libpax packageName=https://github.com/dbinfrago/libpax gitBranch=master From 9faf178bdcaef725f50f3a109031a12441e468be Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 24 Jan 2026 05:45:26 -0600 Subject: [PATCH 32/99] Update XPowersLib to v0.3.3 (#9354) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- variants/esp32/esp32-common.ini | 2 +- variants/esp32/esp32.ini | 2 +- variants/esp32c6/esp32c6.ini | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/variants/esp32/esp32-common.ini b/variants/esp32/esp32-common.ini index 1ff4ce4d8..bbbcd3cbe 100644 --- a/variants/esp32/esp32-common.ini +++ b/variants/esp32/esp32-common.ini @@ -65,7 +65,7 @@ lib_deps = # renovate: datasource=git-refs depName=libpax packageName=https://github.com/dbinfrago/libpax gitBranch=master https://github.com/dbinfrago/libpax/archive/3cdc0371c375676a97967547f4065607d4c53fd1.zip # renovate: datasource=github-tags depName=XPowersLib packageName=lewisxhe/XPowersLib - https://github.com/lewisxhe/XPowersLib/archive/v0.3.2.zip + https://github.com/lewisxhe/XPowersLib/archive/v0.3.3.zip # renovate: datasource=custom.pio depName=rweather/Crypto packageName=rweather/library/Crypto rweather/Crypto@0.4.0 diff --git a/variants/esp32/esp32.ini b/variants/esp32/esp32.ini index 2ce9f4d22..20ce38fae 100644 --- a/variants/esp32/esp32.ini +++ b/variants/esp32/esp32.ini @@ -23,6 +23,6 @@ lib_deps = # renovate: datasource=git-refs depName=libpax packageName=https://github.com/dbinfrago/libpax gitBranch=master https://github.com/dbinfrago/libpax/archive/3cdc0371c375676a97967547f4065607d4c53fd1.zip # renovate: datasource=github-tags depName=XPowersLib packageName=lewisxhe/XPowersLib - https://github.com/lewisxhe/XPowersLib/archive/v0.3.2.zip + https://github.com/lewisxhe/XPowersLib/archive/v0.3.3.zip # renovate: datasource=custom.pio depName=rweather/Crypto packageName=rweather/library/Crypto rweather/Crypto@0.4.0 \ No newline at end of file diff --git a/variants/esp32c6/esp32c6.ini b/variants/esp32c6/esp32c6.ini index c1dfa4d28..9ee8591be 100644 --- a/variants/esp32c6/esp32c6.ini +++ b/variants/esp32c6/esp32c6.ini @@ -28,7 +28,7 @@ lib_deps = ${environmental_extra.lib_deps} ${radiolib_base.lib_deps} # renovate: datasource=custom.pio depName=XPowersLib packageName=lewisxhe/library/XPowersLib - lewisxhe/XPowersLib@0.3.2 + lewisxhe/XPowersLib@0.3.3 # renovate: datasource=git-refs depName=meshtastic-ESP32_Codec2 packageName=https://github.com/meshtastic/ESP32_Codec2 gitBranch=master https://github.com/meshtastic/ESP32_Codec2/archive/633326c78ac251c059ab3a8c430fcdf25b41672f.zip # renovate: datasource=custom.pio depName=rweather/Crypto packageName=rweather/library/Crypto From b627fa720ba5a259bdc0b9254faf4f9f679f31d9 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 24 Jan 2026 05:46:46 -0600 Subject: [PATCH 33/99] Update SensorLib to v0.3.4 (#9396) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- variants/esp32/m5stack_coreink/platformio.ini | 2 +- variants/esp32s3/t-watch-s3/platformio.ini | 2 +- variants/esp32s3/tbeam-s3-core/platformio.ini | 2 +- variants/esp32s3/tlora-pager/platformio.ini | 2 +- variants/nrf52840/ELECROW-ThinkNode-M3/platformio.ini | 2 +- variants/nrf52840/ELECROW-ThinkNode-M6/platformio.ini | 2 +- variants/nrf52840/nano-g2-ultra/platformio.ini | 2 +- variants/nrf52840/t-echo/platformio.ini | 4 ++-- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/variants/esp32/m5stack_coreink/platformio.ini b/variants/esp32/m5stack_coreink/platformio.ini index 0a79cab28..af1535f59 100644 --- a/variants/esp32/m5stack_coreink/platformio.ini +++ b/variants/esp32/m5stack_coreink/platformio.ini @@ -21,7 +21,7 @@ lib_deps = # renovate: datasource=custom.pio depName=GxEPD2 packageName=zinggjm/library/GxEPD2 zinggjm/GxEPD2@1.6.6 # renovate: datasource=custom.pio depName=SensorLib packageName=lewisxhe/library/SensorLib - lewisxhe/SensorLib@0.3.3 + lewisxhe/SensorLib@0.3.4 lib_ignore = m5stack-coreink monitor_filters = esp32_exception_decoder diff --git a/variants/esp32s3/t-watch-s3/platformio.ini b/variants/esp32s3/t-watch-s3/platformio.ini index 7d7b07ff6..9c7a642b2 100644 --- a/variants/esp32s3/t-watch-s3/platformio.ini +++ b/variants/esp32s3/t-watch-s3/platformio.ini @@ -24,7 +24,7 @@ lib_deps = ${esp32s3_base.lib_deps} # renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX lovyan03/LovyanGFX@1.2.7 # renovate: datasource=custom.pio depName=SensorLib packageName=lewisxhe/library/SensorLib - lewisxhe/SensorLib@0.3.3 + lewisxhe/SensorLib@0.3.4 # renovate: datasource=custom.pio depName=Adafruit DRV2605 packageName=adafruit/library/Adafruit DRV2605 Library adafruit/Adafruit DRV2605 Library@1.2.4 # renovate: datasource=custom.pio depName=ESP8266Audio packageName=earlephilhower/library/ESP8266Audio diff --git a/variants/esp32s3/tbeam-s3-core/platformio.ini b/variants/esp32s3/tbeam-s3-core/platformio.ini index c0a32c49c..512cf3202 100644 --- a/variants/esp32s3/tbeam-s3-core/platformio.ini +++ b/variants/esp32s3/tbeam-s3-core/platformio.ini @@ -19,7 +19,7 @@ board_check = true lib_deps = ${esp32s3_base.lib_deps} # renovate: datasource=custom.pio depName=SensorLib packageName=lewisxhe/library/SensorLib - lewisxhe/SensorLib@0.3.3 + lewisxhe/SensorLib@0.3.4 build_flags = ${esp32s3_base.build_flags} diff --git a/variants/esp32s3/tlora-pager/platformio.ini b/variants/esp32s3/tlora-pager/platformio.ini index 5973db1d0..ea6369f29 100644 --- a/variants/esp32s3/tlora-pager/platformio.ini +++ b/variants/esp32s3/tlora-pager/platformio.ini @@ -39,7 +39,7 @@ lib_deps = ${esp32s3_base.lib_deps} # renovate: datasource=custom.pio depName=PCF8563 packageName=lewisxhe/library/PCF8563_Library lewisxhe/PCF8563_Library@1.0.1 # renovate: datasource=custom.pio depName=SensorLib packageName=lewisxhe/library/SensorLib - lewisxhe/SensorLib@0.3.3 + lewisxhe/SensorLib@0.3.4 # renovate: datasource=github-tags depName=pschatzmann_arduino-audio-driver packageName=pschatzmann/arduino-audio-driver https://github.com/pschatzmann/arduino-audio-driver/archive/v0.2.0.zip # TODO renovate diff --git a/variants/nrf52840/ELECROW-ThinkNode-M3/platformio.ini b/variants/nrf52840/ELECROW-ThinkNode-M3/platformio.ini index bf9492075..6751dd4ef 100644 --- a/variants/nrf52840/ELECROW-ThinkNode-M3/platformio.ini +++ b/variants/nrf52840/ELECROW-ThinkNode-M3/platformio.ini @@ -25,4 +25,4 @@ lib_deps = # renovate: datasource=custom.pio depName=nRF52_PWM packageName=khoih-prog/library/nRF52_PWM khoih-prog/nRF52_PWM@1.0.1 ; # renovate: datasource=custom.pio depName=SensorLib packageName=lewisxhe/library/SensorLib - ; lewisxhe/SensorLib@0.3.3 + ; lewisxhe/SensorLib@0.3.4 diff --git a/variants/nrf52840/ELECROW-ThinkNode-M6/platformio.ini b/variants/nrf52840/ELECROW-ThinkNode-M6/platformio.ini index 413eb4fab..a31615545 100644 --- a/variants/nrf52840/ELECROW-ThinkNode-M6/platformio.ini +++ b/variants/nrf52840/ELECROW-ThinkNode-M6/platformio.ini @@ -22,4 +22,4 @@ build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/ELECROW lib_deps = ${nrf52840_base.lib_deps} ; # renovate: datasource=custom.pio depName=SensorLib packageName=lewisxhe/library/SensorLib - ; lewisxhe/SensorLib@0.3.3 + ; lewisxhe/SensorLib@0.3.4 diff --git a/variants/nrf52840/nano-g2-ultra/platformio.ini b/variants/nrf52840/nano-g2-ultra/platformio.ini index 0748b7e38..7e593a49a 100644 --- a/variants/nrf52840/nano-g2-ultra/platformio.ini +++ b/variants/nrf52840/nano-g2-ultra/platformio.ini @@ -20,5 +20,5 @@ build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/nano-g2 lib_deps = ${nrf52840_base.lib_deps} # renovate: datasource=custom.pio depName=SensorLib packageName=lewisxhe/library/SensorLib - lewisxhe/SensorLib@0.3.3 + lewisxhe/SensorLib@0.3.4 ;upload_protocol = fs diff --git a/variants/nrf52840/t-echo/platformio.ini b/variants/nrf52840/t-echo/platformio.ini index 9a66890a7..4acd70b02 100644 --- a/variants/nrf52840/t-echo/platformio.ini +++ b/variants/nrf52840/t-echo/platformio.ini @@ -32,7 +32,7 @@ lib_deps = # renovate: datasource=git-refs depName=meshtastic-GxEPD2 packageName=https://github.com/meshtastic/GxEPD2 gitBranch=master https://github.com/meshtastic/GxEPD2/archive/a05c11c02862624266b61599b0d6ba93e33c6f24.zip # renovate: datasource=custom.pio depName=SensorLib packageName=lewisxhe/library/SensorLib - lewisxhe/SensorLib@0.3.3 + lewisxhe/SensorLib@0.3.4 ;upload_protocol = fs [env:t-echo-inkhud] @@ -53,4 +53,4 @@ lib_deps = ${inkhud.lib_deps} ; InkHUD libs first, so we get GFXRoot instead of AdafruitGFX ${nrf52840_base.lib_deps} # renovate: datasource=custom.pio depName=SensorLib packageName=lewisxhe/library/SensorLib - lewisxhe/SensorLib@0.3.3 + lewisxhe/SensorLib@0.3.4 From b312f226b4cdf279e296d3958aac60b23ae82301 Mon Sep 17 00:00:00 2001 From: phaseloop <90922095+phaseloop@users.noreply.github.com> Date: Sat, 24 Jan 2026 14:22:01 +0200 Subject: [PATCH 34/99] Cut NRF52 bluetooth power usage (#8992) * Improve NRF52 bluetooth power efficiency * test T114 bad LFXO * T1000 test * force BLE param negotiation * stash * NRF52 bluetooth small cleanup * fix potential connectivity issues * lower BLE min interval to make iOS happy * remove slave latency negotation * add BLE issue comment * code format * Revert "code format" This reverts commit 1f92b09d08cde52cf61f115a6335dfce12ad9ffd. * remove LFCLK debug info * Fix --------- Co-authored-by: Ben Meadors --- src/platform/nrf52/NRF52Bluetooth.cpp | 35 +++++++++++++++++++++------ 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/src/platform/nrf52/NRF52Bluetooth.cpp b/src/platform/nrf52/NRF52Bluetooth.cpp index d41c7ba0e..6a552f236 100644 --- a/src/platform/nrf52/NRF52Bluetooth.cpp +++ b/src/platform/nrf52/NRF52Bluetooth.cpp @@ -119,7 +119,7 @@ void startAdv(void) Bluefruit.Advertising.addService(meshBleService); /* Start Advertising * - Enable auto advertising if disconnected - * - Interval: fast mode = 20 ms, slow mode = 152.5 ms + * - Interval: fast mode = 20 ms, slow mode = 417,5 ms * - Timeout for fast mode is 30 seconds * - Start(timeout) with timeout = 0 will advertise forever (until connected) * @@ -127,7 +127,7 @@ void startAdv(void) * https://developer.apple.com/library/content/qa/qa1931/_index.html */ Bluefruit.Advertising.restartOnDisconnect(true); - Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms + Bluefruit.Advertising.setInterval(32, 668); // in unit of 0.625 ms Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode Bluefruit.Advertising.start(0); // 0 = Don't stop advertising after n seconds. FIXME, we should stop advertising after X } @@ -243,10 +243,8 @@ int NRF52Bluetooth::getRssi() // Valid BLE TX power levels as per nRF52840 Product Specification are: "-20 to +8 dBm TX power, configurable in 4 dB steps". // See https://docs.nordicsemi.com/bundle/ps_nrf52840/page/keyfeatures_html5.html -#define VALID_BLE_TX_POWER(x) \ - ((x) == -20 || (x) == -16 || (x) == -12 || \ - (x) == -8 || (x) == -4 || (x) == 0 || \ - (x) == 4 || (x) == 8) +#define VALID_BLE_TX_POWER(x) \ + ((x) == -20 || (x) == -16 || (x) == -12 || (x) == -8 || (x) == -4 || (x) == 0 || (x) == 4 || (x) == 8) void NRF52Bluetooth::setup() { @@ -283,6 +281,29 @@ void NRF52Bluetooth::setup() // Set the connect/disconnect callback handlers Bluefruit.Periph.setConnectCallback(onConnect); Bluefruit.Periph.setDisconnectCallback(onDisconnect); + + // Do not change Slave Latency to value other than 0 !!! + // There is probably a bug in SoftDevice + certain Apple iOS versions being + // brain damaged causing connectivity problems. + + // On one side it seems SoftDevice is using SlaveLatency value even + // if connection parameter negotation failed and phone sees it as connectivity errors. + + // On the other hand Apple can randomly refuse any parameter negotiation and shutdown connection + // even if you meet Apple Developer Guidelines for BLE devices. Because f* you, that's why. + + // While this API call sets preferred connection parameters (PPCP) - many phones ignore it (yeah) and it seems SoftDevice + // will try to renegotiate connection parameters based on those values after phone connection. + // So those are relatively safe values so Apple braindead firmware won't get angry and at least we may try + // to negotiate some longer connection interval to save battery. + + // See https://github.com/meshtastic/firmware/pull/8858 for measurements. We are dealing with microamp savings anyway so not + // worth dying on a hill here. + + Bluefruit.Periph.setConnSlaveLatency(0); + // 1.25 ms units - so min, max is 15, 100 ms range. + Bluefruit.Periph.setConnInterval(12, 80); + #ifndef BLE_DFU_SECURE bledfu.setPermission(SECMODE_ENC_WITH_MITM, SECMODE_ENC_WITH_MITM); bledfu.begin(); // Install the DFU helper @@ -311,7 +332,7 @@ void NRF52Bluetooth::setup() void NRF52Bluetooth::resumeAdvertising() { Bluefruit.Advertising.restartOnDisconnect(true); - Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms + Bluefruit.Advertising.setInterval(32, 668); // in unit of 0.625 ms Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode Bluefruit.Advertising.start(0); } From 6cff13623f93880e4cd5537a406b77f69bf8cbe5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?U=C4=9Fur=20ALTINSOY?= <48494791+uguraltinsoy@users.noreply.github.com> Date: Sat, 24 Jan 2026 17:38:07 +0300 Subject: [PATCH 35/99] Added Minimesh variant (#9289) * Minimesh Lite Added * Add Minimesh Lite NRF * Added board_level = extra * Fix formatting and optimize image for Minimesh Lite * Change image * The image has been deleted. --------- Co-authored-by: Ben Meadors --- boards/minimesh_lite.json | 50 +++++++++ .../nrf52840/dls_Minimesh_Lite/platformio.ini | 9 ++ .../nrf52840/dls_Minimesh_Lite/variant.cpp | 38 +++++++ variants/nrf52840/dls_Minimesh_Lite/variant.h | 104 ++++++++++++++++++ 4 files changed, 201 insertions(+) create mode 100644 boards/minimesh_lite.json create mode 100644 variants/nrf52840/dls_Minimesh_Lite/platformio.ini create mode 100644 variants/nrf52840/dls_Minimesh_Lite/variant.cpp create mode 100644 variants/nrf52840/dls_Minimesh_Lite/variant.h diff --git a/boards/minimesh_lite.json b/boards/minimesh_lite.json new file mode 100644 index 000000000..0b8f0b909 --- /dev/null +++ b/boards/minimesh_lite.json @@ -0,0 +1,50 @@ +{ + "build": { + "arduino": { + "ldscript": "nrf52840_s140_v6.ld" + }, + "core": "nRF5", + "cpu": "cortex-m4", + "extra_flags": "-DMINIMESH_LITE -DNRF52840_XXAA", + "f_cpu": "64000000L", + "hwids": [ + ["0x239A", "0x8029"], + ["0x239A", "0x0029"], + ["0x239A", "0x002A"] + ], + "usb_product": "Minimesh Lite", + "mcu": "nrf52840", + "variant": "dls_Minimesh_Lite", + "bsp": { + "name": "adafruit" + }, + "softdevice": { + "sd_flags": "-DS140", + "sd_name": "s140", + "sd_version": "6.1.1", + "sd_fwid": "0x00B6" + }, + "bootloader": { + "settings_addr": "0xFF000" + } + }, + "connectivity": ["bluetooth"], + "debug": { + "jlink_device": "nRF52840_xxAA", + "svd_path": "nrf52840.svd" + }, + "frameworks": ["arduino"], + "name": "Minimesh Lite", + "upload": { + "maximum_ram_size": 248832, + "maximum_size": 815104, + "speed": 115200, + "protocol": "nrfutil", + "protocols": ["nrfutil", "jlink", "nrfjprog", "stlink"], + "use_1200bps_touch": true, + "require_upload_port": true, + "wait_for_upload_port": true + }, + "url": "https://deeplabstudio.com", + "vendor": "Deeplab Studio" +} diff --git a/variants/nrf52840/dls_Minimesh_Lite/platformio.ini b/variants/nrf52840/dls_Minimesh_Lite/platformio.ini new file mode 100644 index 000000000..763ff5477 --- /dev/null +++ b/variants/nrf52840/dls_Minimesh_Lite/platformio.ini @@ -0,0 +1,9 @@ +[env:minimesh_lite] +extends = nrf52840_base +board = minimesh_lite +board_level = extra +build_flags = ${nrf52840_base.build_flags} + -Ivariants/nrf52840/dls_Minimesh_Lite + -DPRIVATE_HW +build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/dls_Minimesh_Lite> +debug_tool = jlink diff --git a/variants/nrf52840/dls_Minimesh_Lite/variant.cpp b/variants/nrf52840/dls_Minimesh_Lite/variant.cpp new file mode 100644 index 000000000..5869ed1d4 --- /dev/null +++ b/variants/nrf52840/dls_Minimesh_Lite/variant.cpp @@ -0,0 +1,38 @@ +/* + Copyright (c) 2014-2015 Arduino LLC. All right reserved. + Copyright (c) 2016 Sandeep Mistry All right reserved. + Copyright (c) 2018, Adafruit Industries (adafruit.com) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "variant.h" +#include "nrf.h" +#include "wiring_constants.h" +#include "wiring_digital.h" + +const uint32_t g_ADigitalPinMap[] = { + // P0 + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + + // P1 + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47}; + +void initVariant() +{ + // 3V3 Power Rail + pinMode(PIN_3V3_EN, OUTPUT); + digitalWrite(PIN_3V3_EN, HIGH); +} diff --git a/variants/nrf52840/dls_Minimesh_Lite/variant.h b/variants/nrf52840/dls_Minimesh_Lite/variant.h new file mode 100644 index 000000000..f5163619b --- /dev/null +++ b/variants/nrf52840/dls_Minimesh_Lite/variant.h @@ -0,0 +1,104 @@ +#ifndef _VARIANT_MINIMESH_LITE_ +#define _VARIANT_MINIMESH_LITE_ + +#define VARIANT_MCK (64000000ul) +#define USE_LFRC + +#include "WVariant.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define MINIMESH_LITE + +// Number of pins defined in PinDescription array +#define PINS_COUNT (48) +#define NUM_DIGITAL_PINS (48) +#define NUM_ANALOG_INPUTS (1) +#define NUM_ANALOG_OUTPUTS (0) + +#define PIN_3V3_EN (0 + 13) // P0.13 + +// Analog pins +#define BATTERY_PIN (0 + 31) // P0.31 Battery ADC +#define ADC_CHANNEL ADC1_GPIO4_CHANNEL +#define ADC_RESOLUTION 14 +#define BATTERY_SENSE_RESOLUTION_BITS 12 +#define BATTERY_SENSE_RESOLUTION 4096.0 +#define VBAT_MV_PER_LSB (0.73242188F) +#define VBAT_DIVIDER (0.6F) +#define VBAT_DIVIDER_COMP (1.73) +#define REAL_VBAT_MV_PER_LSB (VBAT_DIVIDER_COMP * VBAT_MV_PER_LSB) +#undef AREF_VOLTAGE +#define AREF_VOLTAGE 3.0 +#define VBAT_AR_INTERNAL AR_INTERNAL_3_0 +#define ADC_MULTIPLIER VBAT_DIVIDER_COMP +#define VBAT_RAW_TO_SCALED(x) (REAL_VBAT_MV_PER_LSB * x) + +// WIRE IC AND IIC PINS +#define WIRE_INTERFACES_COUNT 1 + +#define PIN_WIRE_SDA (32 + 4) +#define PIN_WIRE_SCL (0 + 11) + +// LED +#define PIN_LED1 (0 + 15) +#define LED_BUILTIN PIN_LED1 +// Actually red +#define LED_BLUE PIN_LED1 +#define LED_STATE_ON 1 + +// Button +#define BUTTON_PIN (32 + 0) + +// GPS +#define GPS_TX_PIN (0 + 20) +#define GPS_RX_PIN (0 + 22) + +#define PIN_GPS_EN (0 + 24) +#define GPS_UBLOX +// define GPS_DEBUG + +// UART interfaces +#define PIN_SERIAL1_TX GPS_TX_PIN +#define PIN_SERIAL1_RX GPS_RX_PIN + +#define PIN_SERIAL2_RX (0 + 6) +#define PIN_SERIAL2_TX (0 + 8) + +// Serial interfaces +#define SPI_INTERFACES_COUNT 1 + +#define PIN_SPI_MISO (0 + 2) +#define PIN_SPI_MOSI (32 + 15) +#define PIN_SPI_SCK (32 + 11) + +#define LORA_MISO PIN_SPI_MISO +#define LORA_MOSI PIN_SPI_MOSI +#define LORA_SCK PIN_SPI_SCK +#define LORA_CS (32 + 13) + +// LORA MODULES +#define USE_LLCC68 +#define USE_SX1262 +#define USE_SX1268 + +// SX126X CONFIG +#define SX126X_CS (32 + 13) +#define SX126X_DIO1 (0 + 10) +#define SX126X_DIO2_AS_RF_SWITCH + +#define SX126X_BUSY (0 + 29) +#define SX126X_RESET (0 + 9) +#define SX126X_RXEN (0 + 17) +#define SX126X_TXEN RADIOLIB_NC + +#define SX126X_DIO3_TCXO_VOLTAGE 1.8 +#define TCXO_OPTIONAL // make it so that the firmware can try both TCXO and XTAL + +#ifdef __cplusplus +} +#endif + +#endif // _VARIANT_MINIMESH_LITE_ From 57a3ff8dfcc7b2b4f766de224cc80376e7332564 Mon Sep 17 00:00:00 2001 From: phaseloop <90922095+phaseloop@users.noreply.github.com> Date: Sat, 24 Jan 2026 16:39:03 +0200 Subject: [PATCH 36/99] NRF52 - power management improvements (#9211) * minor NRF52 test cleanup * detect USB power input on ProMicro boards * prevent booting on power failure detection * introduce PowerHAL layer * powerHAL basic implementation for NRF52 * prevent data saves on low power * remove comment * Update src/platform/nrf52/main-nrf52.cpp Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update src/power/PowerHAL.h Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update src/main.cpp Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Merge missing voltage threshold comparison * add missing variable * add missing function declaration * remove debug strings --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Ben Meadors --- src/Power.cpp | 194 ++++++++++++++++-------------- src/main.cpp | 46 +++++++ src/mesh/NodeDB.cpp | 49 ++++++++ src/platform/nrf52/architecture.h | 19 +++ src/platform/nrf52/main-nrf52.cpp | 144 ++++++++++++++++++---- src/power/PowerHAL.cpp | 19 +++ src/power/PowerHAL.h | 26 ++++ 7 files changed, 384 insertions(+), 113 deletions(-) create mode 100644 src/power/PowerHAL.cpp create mode 100644 src/power/PowerHAL.h diff --git a/src/Power.cpp b/src/Power.cpp index cec881f83..b2a4ddaaf 100644 --- a/src/Power.cpp +++ b/src/Power.cpp @@ -1,11 +1,14 @@ /** * @file Power.cpp - * @brief This file contains the implementation of the Power class, which is responsible for managing power-related functionality - * of the device. It includes battery level sensing, power management unit (PMU) control, and power state machine management. The - * Power class is used by the main device class to manage power-related functionality. + * @brief This file contains the implementation of the Power class, which is + * responsible for managing power-related functionality of the device. It + * includes battery level sensing, power management unit (PMU) control, and + * power state machine management. The Power class is used by the main device + * class to manage power-related functionality. * - * The file also includes implementations of various battery level sensors, such as the AnalogBatteryLevel class, which assumes - * the battery voltage is attached via a voltage-divider to an analog input. + * The file also includes implementations of various battery level sensors, such + * as the AnalogBatteryLevel class, which assumes the battery voltage is + * attached via a voltage-divider to an analog input. * * This file is part of the Meshtastic project. * For more information, see: https://meshtastic.org/ @@ -19,6 +22,7 @@ #include "configuration.h" #include "main.h" #include "meshUtils.h" +#include "power/PowerHAL.h" #include "sleep.h" #if defined(ARCH_PORTDUINO) @@ -171,22 +175,12 @@ Power *power; using namespace meshtastic; -#ifndef AREF_VOLTAGE -#if defined(ARCH_NRF52) -/* - * Internal Reference is +/-0.6V, with an adjustable gain of 1/6, 1/5, 1/4, - * 1/3, 1/2 or 1, meaning 3.6, 3.0, 2.4, 1.8, 1.2 or 0.6V for the ADC levels. - * - * External Reference is VDD/4, with an adjustable gain of 1, 2 or 4, meaning - * VDD/4, VDD/2 or VDD for the ADC levels. - * - * Default settings are internal reference with 1/6 gain (GND..3.6V ADC range) - */ -#define AREF_VOLTAGE 3.6 -#else +// NRF52 has AREF_VOLTAGE defined in architecture.h but +// make sure it's included. If something is wrong with NRF52 +// definition - compilation will fail on missing definition +#if !defined(AREF_VOLTAGE) && !defined(ARCH_NRF52) #define AREF_VOLTAGE 3.3 #endif -#endif /** * If this board has a battery level sensor, set this to a valid implementation @@ -233,7 +227,8 @@ static void battery_adcDisable() #endif /** - * A simple battery level sensor that assumes the battery voltage is attached via a voltage-divider to an analog input + * A simple battery level sensor that assumes the battery voltage is attached + * via a voltage-divider to an analog input */ class AnalogBatteryLevel : public HasBatteryLevel { @@ -311,7 +306,8 @@ class AnalogBatteryLevel : public HasBatteryLevel #ifndef BATTERY_SENSE_SAMPLES #define BATTERY_SENSE_SAMPLES \ - 15 // Set the number of samples, it has an effect of increasing sensitivity in complex electromagnetic environment. + 15 // Set the number of samples, it has an effect of increasing sensitivity in + // complex electromagnetic environment. #endif #ifdef BATTERY_PIN @@ -341,7 +337,8 @@ class AnalogBatteryLevel : public HasBatteryLevel battery_adcDisable(); if (!initial_read_done) { - // Flush the smoothing filter with an ADC reading, if the reading is plausibly correct + // Flush the smoothing filter with an ADC reading, if the reading is + // plausibly correct if (scaled > last_read_value) last_read_value = scaled; initial_read_done = true; @@ -350,8 +347,8 @@ class AnalogBatteryLevel : public HasBatteryLevel last_read_value += (scaled - last_read_value) * 0.5; // Virtual LPF } - // LOG_DEBUG("battery gpio %d raw val=%u scaled=%u filtered=%u", BATTERY_PIN, raw, (uint32_t)(scaled), (uint32_t) - // (last_read_value)); + // LOG_DEBUG("battery gpio %d raw val=%u scaled=%u filtered=%u", + // BATTERY_PIN, raw, (uint32_t)(scaled), (uint32_t) (last_read_value)); } return last_read_value; #endif // BATTERY_PIN @@ -420,7 +417,8 @@ class AnalogBatteryLevel : public HasBatteryLevel /** * return true if there is a battery installed in this unit */ - // if we have a integrated device with a battery, we can assume that the battery is always connected + // if we have a integrated device with a battery, we can assume that the + // battery is always connected #ifdef BATTERY_IMMUTABLE virtual bool isBatteryConnect() override { return true; } #elif defined(ADC_V) @@ -441,10 +439,10 @@ class AnalogBatteryLevel : public HasBatteryLevel virtual bool isBatteryConnect() override { return getBatteryPercent() != -1; } #endif - /// If we see a battery voltage higher than physics allows - assume charger is pumping - /// in power - /// On some boards we don't have the power management chip (like AXPxxxx) - /// so we use EXT_PWR_DETECT GPIO pin to detect external power source + /// If we see a battery voltage higher than physics allows - assume charger is + /// pumping in power On some boards we don't have the power management chip + /// (like AXPxxxx) so we use EXT_PWR_DETECT GPIO pin to detect external power + /// source virtual bool isVbusIn() override { #ifdef EXT_PWR_DETECT @@ -461,8 +459,12 @@ class AnalogBatteryLevel : public HasBatteryLevel } // if it's not HIGH - check the battery #endif -#elif defined(MUZI_BASE) - return NRF_POWER->USBREGSTATUS & POWER_USBREGSTATUS_VBUSDETECT_Msk; + +// technically speaking this should work for all(?) NRF52 boards +// but needs testing across multiple devices. NRF52 USB would not even work if +// VBUS was not properly connected and detected by the CPU +#elif defined(MUZI_BASE) || defined(PROMICRO_DIY_TCXO) + return powerHAL_isVBUSConnected(); #endif return getBattVoltage() > chargingVolt; } @@ -485,8 +487,9 @@ class AnalogBatteryLevel : public HasBatteryLevel #else #if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && !defined(DISABLE_INA_CHARGING_DETECTION) if (hasINA()) { - // get current flow from INA sensor - negative value means power flowing into the battery - // default assuming BATTERY+ <--> INA_VIN+ <--> SHUNT RESISTOR <--> INA_VIN- <--> LOAD + // get current flow from INA sensor - negative value means power flowing + // into the battery default assuming BATTERY+ <--> INA_VIN+ <--> SHUNT + // RESISTOR <--> INA_VIN- <--> LOAD LOG_DEBUG("Using INA on I2C addr 0x%x for charging detection", config.power.device_battery_ina_address); #if defined(INA_CHARGING_DETECTION_INVERT) return getINACurrent() > 0; @@ -502,8 +505,8 @@ class AnalogBatteryLevel : public HasBatteryLevel } private: - /// If we see a battery voltage higher than physics allows - assume charger is pumping - /// in power + /// If we see a battery voltage higher than physics allows - assume charger is + /// pumping in power /// For heltecs with no battery connected, the measured voltage is 2204, so // need to be higher than that, in this case is 2500mV (3000-500) @@ -512,7 +515,8 @@ class AnalogBatteryLevel : public HasBatteryLevel const float noBatVolt = (OCV[NUM_OCV_POINTS - 1] - 500) * NUM_CELLS; // Start value from minimum voltage for the filter to not start from 0 // that could trigger some events. - // This value is over-written by the first ADC reading, it the voltage seems reasonable. + // This value is over-written by the first ADC reading, it the voltage seems + // reasonable. bool initial_read_done = false; float last_read_value = (OCV[NUM_OCV_POINTS - 1] * NUM_CELLS); uint32_t last_read_time_ms = 0; @@ -654,7 +658,8 @@ bool Power::analogInit() #ifdef CONFIG_IDF_TARGET_ESP32S3 // ESP32S3 else if (val_type == ESP_ADC_CAL_VAL_EFUSE_TP_FIT) { - LOG_INFO("ADC config based on Two Point values and fitting curve coefficients stored in eFuse"); + LOG_INFO("ADC config based on Two Point values and fitting curve " + "coefficients stored in eFuse"); } #endif else { @@ -662,13 +667,7 @@ bool Power::analogInit() } #endif // ARCH_ESP32 -#ifdef ARCH_NRF52 -#ifdef VBAT_AR_INTERNAL - analogReference(VBAT_AR_INTERNAL); -#else - analogReference(AR_INTERNAL); // 3.6V -#endif -#endif // ARCH_NRF52 + // NRF52 ADC init moved to powerHAL_init in nrf52 platform #ifndef ARCH_ESP32 analogReadResolution(BATTERY_SENSE_RESOLUTION_BITS); @@ -779,7 +778,8 @@ void Power::reboot() HAL_NVIC_SystemReset(); #else rebootAtMsec = -1; - LOG_WARN("FIXME implement reboot for this platform. Note that some settings require a restart to be applied"); + LOG_WARN("FIXME implement reboot for this platform. Note that some settings " + "require a restart to be applied"); #endif } @@ -789,9 +789,12 @@ void Power::shutdown() #if HAS_SCREEN if (screen) { #ifdef T_DECK_PRO - screen->showSimpleBanner("Device is powered off.\nConnect USB to start!", 0); // T-Deck Pro has no power button + screen->showSimpleBanner("Device is powered off.\nConnect USB to start!", + 0); // T-Deck Pro has no power button #elif defined(USE_EINK) - screen->showSimpleBanner("Shutting Down...", 2250); // dismiss after 3 seconds to avoid the banner on the sleep screen + screen->showSimpleBanner("Shutting Down...", + 2250); // dismiss after 3 seconds to avoid the + // banner on the sleep screen #else screen->showSimpleBanner("Shutting Down...", 0); // stays on screen #endif @@ -830,7 +833,8 @@ void Power::readPowerStatus() int32_t batteryVoltageMv = -1; // Assume unknown int8_t batteryChargePercent = -1; OptionalBool usbPowered = OptUnknown; - OptionalBool hasBattery = OptUnknown; // These must be static because NRF_APM code doesn't run every time + OptionalBool hasBattery = OptUnknown; // These must be static because NRF_APM + // code doesn't run every time OptionalBool isChargingNow = OptUnknown; if (batteryLevel) { @@ -843,9 +847,10 @@ void Power::readPowerStatus() if (batteryLevel->getBatteryPercent() >= 0) { batteryChargePercent = batteryLevel->getBatteryPercent(); } else { - // If the AXP192 returns a percentage less than 0, the feature is either not supported or there is an error - // In that case, we compute an estimate of the charge percent based on open circuit voltage table defined - // in power.h + // If the AXP192 returns a percentage less than 0, the feature is either + // not supported or there is an error In that case, we compute an + // estimate of the charge percent based on open circuit voltage table + // defined in power.h batteryChargePercent = clamp((int)(((batteryVoltageMv - (OCV[NUM_OCV_POINTS - 1] * NUM_CELLS)) * 1e2) / ((OCV[0] * NUM_CELLS) - (OCV[NUM_OCV_POINTS - 1] * NUM_CELLS))), 0, 100); @@ -853,12 +858,12 @@ void Power::readPowerStatus() } } -// FIXME: IMO we shouldn't be littering our code with all these ifdefs. Way better instead to make a Nrf52IsUsbPowered subclass -// (which shares a superclass with the BatteryLevel stuff) -// that just provides a few methods. But in the interest of fixing this bug I'm going to follow current -// practice. -#ifdef NRF_APM // Section of code detects USB power on the RAK4631 and updates the power states. Takes 20 seconds or so to detect - // changes. +// FIXME: IMO we shouldn't be littering our code with all these ifdefs. Way +// better instead to make a Nrf52IsUsbPowered subclass (which shares a +// superclass with the BatteryLevel stuff) that just provides a few methods. But +// in the interest of fixing this bug I'm going to follow current practice. +#ifdef NRF_APM // Section of code detects USB power on the RAK4631 and updates + // the power states. Takes 20 seconds or so to detect changes. nrfx_power_usb_state_t nrf_usb_state = nrfx_power_usbstatus_get(); // LOG_DEBUG("NRF Power %d", nrf_usb_state); @@ -932,8 +937,9 @@ void Power::readPowerStatus() #endif - // If we have a battery at all and it is less than 0%, force deep sleep if we have more than 10 low readings in - // a row. NOTE: min LiIon/LiPo voltage is 2.0 to 2.5V, current OCV min is set to 3100 that is large enough. + // If we have a battery at all and it is less than 0%, force deep sleep if we + // have more than 10 low readings in a row. NOTE: min LiIon/LiPo voltage + // is 2.0 to 2.5V, current OCV min is set to 3100 that is large enough. // if (batteryLevel && powerStatus2.getHasBattery() && !powerStatus2.getHasUSB()) { @@ -955,8 +961,8 @@ int32_t Power::runOnce() readPowerStatus(); #ifdef HAS_PMU - // WE no longer use the IRQ line to wake the CPU (due to false wakes from sleep), but we do poll - // the IRQ status by reading the registers over I2C + // WE no longer use the IRQ line to wake the CPU (due to false wakes from + // sleep), but we do poll the IRQ status by reading the registers over I2C if (PMU) { PMU->getIrqStatus(); @@ -998,7 +1004,8 @@ int32_t Power::runOnce() PMU->clearIrqStatus(); } #endif - // Only read once every 20 seconds once the power status for the app has been initialized + // Only read once every 20 seconds once the power status for the app has been + // initialized return (statusHandler && statusHandler->isInitialized()) ? (1000 * 20) : RUN_SAME; } @@ -1006,10 +1013,12 @@ int32_t Power::runOnce() * Init the power manager chip * * axp192 power - DCDC1 0.7-3.5V @ 1200mA max -> OLED // If you turn this off you'll lose comms to the axp192 because the OLED and the - axp192 share the same i2c bus, instead use ssd1306 sleep mode DCDC2 -> unused DCDC3 0.7-3.5V @ 700mA max -> ESP32 (keep this - on!) LDO1 30mA -> charges GPS backup battery // charges the tiny J13 battery by the GPS to power the GPS ram (for a couple of - days), can not be turned off LDO2 200mA -> LORA LDO3 200mA -> GPS + DCDC1 0.7-3.5V @ 1200mA max -> OLED // If you turn this off you'll lose + comms to the axp192 because the OLED and the axp192 share the same i2c bus, + instead use ssd1306 sleep mode DCDC2 -> unused DCDC3 0.7-3.5V @ 700mA max -> + ESP32 (keep this on!) LDO1 30mA -> charges GPS backup battery // charges the + tiny J13 battery by the GPS to power the GPS ram (for a couple of days), can + not be turned off LDO2 200mA -> LORA LDO3 200mA -> GPS * */ bool Power::axpChipInit() @@ -1054,9 +1063,10 @@ bool Power::axpChipInit() if (!PMU) { /* - * In XPowersLib, if the XPowersAXPxxx object is released, Wire.end() will be called at the same time. - * In order not to affect other devices, if the initialization of the PMU fails, Wire needs to be re-initialized once, - * if there are multiple devices sharing the bus. + * In XPowersLib, if the XPowersAXPxxx object is released, Wire.end() will + * be called at the same time. In order not to affect other devices, if the + * initialization of the PMU fails, Wire needs to be re-initialized once, if + * there are multiple devices sharing the bus. * * */ #ifndef PMU_USE_WIRE1 w->begin(I2C_SDA, I2C_SCL); @@ -1073,8 +1083,8 @@ bool Power::axpChipInit() PMU->enablePowerOutput(XPOWERS_LDO2); // oled module power channel, - // disable it will cause abnormal communication between boot and AXP power supply, - // do not turn it off + // disable it will cause abnormal communication between boot and AXP power + // supply, do not turn it off PMU->setPowerChannelVoltage(XPOWERS_DCDC1, 3300); // enable oled power PMU->enablePowerOutput(XPOWERS_DCDC1); @@ -1101,7 +1111,8 @@ bool Power::axpChipInit() PMU->setChargeTargetVoltage(XPOWERS_AXP192_CHG_VOL_4V2); } else if (PMU->getChipModel() == XPOWERS_AXP2101) { - /*The alternative version of T-Beam 1.1 differs from T-Beam V1.1 in that it uses an AXP2101 power chip*/ + /*The alternative version of T-Beam 1.1 differs from T-Beam V1.1 in that it + * uses an AXP2101 power chip*/ if (HW_VENDOR == meshtastic_HardwareModel_TBEAM) { // Unuse power channel PMU->disablePowerOutput(XPOWERS_DCDC2); @@ -1136,8 +1147,8 @@ bool Power::axpChipInit() // t-beam s3 core /** * gnss module power channel - * The default ALDO4 is off, you need to turn on the GNSS power first, otherwise it will be invalid during - * initialization + * The default ALDO4 is off, you need to turn on the GNSS power first, + * otherwise it will be invalid during initialization */ PMU->setPowerChannelVoltage(XPOWERS_ALDO4, 3300); PMU->enablePowerOutput(XPOWERS_ALDO4); @@ -1187,7 +1198,8 @@ bool Power::axpChipInit() // disable all axp chip interrupt PMU->disableIRQ(XPOWERS_AXP2101_ALL_IRQ); - // Set the constant current charging current of AXP2101, temporarily use 500mA by default + // Set the constant current charging current of AXP2101, temporarily use + // 500mA by default PMU->setChargerConstantCurr(XPOWERS_AXP2101_CHG_CUR_500MA); // Set up the charging voltage @@ -1253,11 +1265,12 @@ bool Power::axpChipInit() PMU->getPowerChannelVoltage(XPOWERS_BLDO2)); } -// We can safely ignore this approach for most (or all) boards because MCU turned off -// earlier than battery discharged to 2.6V. +// We can safely ignore this approach for most (or all) boards because MCU +// turned off earlier than battery discharged to 2.6V. // -// Unfortanly for now we can't use this killswitch for RAK4630-based boards because they have a bug with -// battery voltage measurement. Probably it sometimes drops to low values. +// Unfortunately for now we can't use this killswitch for RAK4630-based boards +// because they have a bug with battery voltage measurement. Probably it +// sometimes drops to low values. #ifndef RAK4630 // Set PMU shutdown voltage at 2.6V to maximize battery utilization PMU->setSysPowerDownVoltage(2600); @@ -1276,10 +1289,12 @@ bool Power::axpChipInit() attachInterrupt( PMU_IRQ, [] { pmu_irq = true; }, FALLING); - // we do not look for AXPXXX_CHARGING_FINISHED_IRQ & AXPXXX_CHARGING_IRQ because it occurs repeatedly while there is - // no battery also it could cause inadvertent waking from light sleep just because the battery filled - // we don't look for AXPXXX_BATT_REMOVED_IRQ because it occurs repeatedly while no battery installed - // we don't look at AXPXXX_VBUS_REMOVED_IRQ because we don't have anything hooked to vbus + // we do not look for AXPXXX_CHARGING_FINISHED_IRQ & AXPXXX_CHARGING_IRQ + // because it occurs repeatedly while there is no battery also it could cause + // inadvertent waking from light sleep just because the battery filled we + // don't look for AXPXXX_BATT_REMOVED_IRQ because it occurs repeatedly while + // no battery installed we don't look at AXPXXX_VBUS_REMOVED_IRQ because we + // don't have anything hooked to vbus PMU->enableIRQ(pmuIrqMask); PMU->clearIrqStatus(); @@ -1395,8 +1410,8 @@ class LipoCharger : public HasBatteryLevel bool result = PPM->init(Wire, I2C_SDA, I2C_SCL, BQ25896_ADDR); if (result) { LOG_INFO("PPM BQ25896 init succeeded"); - // Set the minimum operating voltage. Below this voltage, the PPM will protect - // PPM->setSysPowerDownVoltage(3100); + // Set the minimum operating voltage. Below this voltage, the PPM will + // protect PPM->setSysPowerDownVoltage(3100); // Set input current limit, default is 500mA // PPM->setInputCurrentLimit(800); @@ -1419,7 +1434,8 @@ class LipoCharger : public HasBatteryLevel PPM->enableMeasure(); // Turn on charging function - // If there is no battery connected, do not turn on the charging function + // If there is no battery connected, do not turn on the charging + // function PPM->enableCharge(); } else { LOG_WARN("PPM BQ25896 init failed"); @@ -1454,7 +1470,8 @@ class LipoCharger : public HasBatteryLevel virtual int getBatteryPercent() override { return -1; - // return bq->getChargePercent(); // don't use BQ27220 for battery percent, it is not calibrated + // return bq->getChargePercent(); // don't use BQ27220 for battery percent, + // it is not calibrated } /** @@ -1576,7 +1593,8 @@ bool Power::meshSolarInit() #else /** - * The meshSolar battery level sensor is unavailable - default to AnalogBatteryLevel + * The meshSolar battery level sensor is unavailable - default to + * AnalogBatteryLevel */ bool Power::meshSolarInit() { diff --git a/src/main.cpp b/src/main.cpp index c1096a240..ea114ea34 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -10,6 +10,7 @@ #include "ReliableRouter.h" #include "airtime.h" #include "buzz.h" +#include "power/PowerHAL.h" #include "FSCommon.h" #include "Led.h" @@ -332,6 +333,43 @@ __attribute__((weak, noinline)) bool loopCanSleep() void lateInitVariant() __attribute__((weak)); void lateInitVariant() {} +// NRF52 (and probably other platforms) can report when system is in power failure mode +// (eg. too low battery voltage) and operating it is unsafe (data corruption, bootloops, etc). +// For example NRF52 will prevent any flash writes in that case automatically +// (but it causes issues we need to handle). +// This detection is independent from whatever ADC or dividers used in Meshtastic +// boards and is internal to chip. + +// we use powerHAL layer to get this info and delay booting until power level is safe + +// wait until power level is safe to continue booting (to avoid bootloops) +// blink user led in 3 flashes sequence to indicate what is happening +void waitUntilPowerLevelSafe() +{ + +#ifdef LED_PIN + pinMode(LED_PIN, OUTPUT); +#endif + + while (powerHAL_isPowerLevelSafe() == false) { + +#ifdef LED_PIN + + // 3x: blink for 300 ms, pause for 300 ms + + for (int i = 0; i < 3; i++) { + digitalWrite(LED_PIN, LED_STATE_ON); + delay(300); + digitalWrite(LED_PIN, LED_STATE_OFF); + delay(300); + } +#endif + + // sleep for 2s + delay(2000); + } +} + /** * Print info as a structured log message (for automated log processing) */ @@ -342,6 +380,14 @@ void printInfo() #ifndef PIO_UNIT_TESTING void setup() { + + // initialize power HAL layer as early as possible + powerHAL_init(); + + // prevent booting if device is in power failure mode + // boot sequence will follow when battery level raises to safe mode + waitUntilPowerLevelSafe(); + #if defined(R1_NEO) pinMode(DCDC_EN_HOLD, OUTPUT); digitalWrite(DCDC_EN_HOLD, HIGH); diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 375bc76e3..2a3294086 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #ifdef ARCH_ESP32 @@ -1418,6 +1419,14 @@ void NodeDB::loadFromDisk() bool NodeDB::saveProto(const char *filename, size_t protoSize, const pb_msgdesc_t *fields, const void *dest_struct, bool fullAtomic) { + + // do not try to save anything if power level is not safe. In many cases flash will be lock-protected + // and all writes will fail anyway. Device should be sleeping at this point anyway. + if (!powerHAL_isPowerLevelSafe()) { + LOG_ERROR("Error: trying to saveProto() on unsafe device power level."); + return false; + } + bool okay = false; #ifdef FSCom auto f = SafeFile(filename, fullAtomic); @@ -1444,6 +1453,14 @@ bool NodeDB::saveProto(const char *filename, size_t protoSize, const pb_msgdesc_ bool NodeDB::saveChannelsToDisk() { + + // do not try to save anything if power level is not safe. In many cases flash will be lock-protected + // and all writes will fail anyway. + if (!powerHAL_isPowerLevelSafe()) { + LOG_ERROR("Error: trying to saveChannelsToDisk() on unsafe device power level."); + return false; + } + #ifdef FSCom spiLock->lock(); FSCom.mkdir("/prefs"); @@ -1454,6 +1471,14 @@ bool NodeDB::saveChannelsToDisk() bool NodeDB::saveDeviceStateToDisk() { + + // do not try to save anything if power level is not safe. In many cases flash will be lock-protected + // and all writes will fail anyway. Device should be sleeping at this point anyway. + if (!powerHAL_isPowerLevelSafe()) { + LOG_ERROR("Error: trying to saveDeviceStateToDisk() on unsafe device power level."); + return false; + } + #ifdef FSCom spiLock->lock(); FSCom.mkdir("/prefs"); @@ -1466,6 +1491,14 @@ bool NodeDB::saveDeviceStateToDisk() bool NodeDB::saveNodeDatabaseToDisk() { + + // do not try to save anything if power level is not safe. In many cases flash will be lock-protected + // and all writes will fail anyway. Device should be sleeping at this point anyway. + if (!powerHAL_isPowerLevelSafe()) { + LOG_ERROR("Error: trying to saveNodeDatabaseToDisk() on unsafe device power level."); + return false; + } + #ifdef FSCom spiLock->lock(); FSCom.mkdir("/prefs"); @@ -1478,6 +1511,14 @@ bool NodeDB::saveNodeDatabaseToDisk() bool NodeDB::saveToDiskNoRetry(int saveWhat) { + + // do not try to save anything if power level is not safe. In many cases flash will be lock-protected + // and all writes will fail anyway. Device should be sleeping at this point anyway. + if (!powerHAL_isPowerLevelSafe()) { + LOG_ERROR("Error: trying to saveToDiskNoRetry() on unsafe device power level."); + return false; + } + bool success = true; #ifdef FSCom spiLock->lock(); @@ -1533,6 +1574,14 @@ bool NodeDB::saveToDiskNoRetry(int saveWhat) bool NodeDB::saveToDisk(int saveWhat) { LOG_DEBUG("Save to disk %d", saveWhat); + + // do not try to save anything if power level is not safe. In many cases flash will be lock-protected + // and all writes will fail anyway. Device should be sleeping at this point anyway. + if (!powerHAL_isPowerLevelSafe()) { + LOG_ERROR("Error: trying to saveToDisk() on unsafe device power level."); + return false; + } + bool success = saveToDiskNoRetry(saveWhat); if (!success) { diff --git a/src/platform/nrf52/architecture.h b/src/platform/nrf52/architecture.h index 7734c0020..d1965f03e 100644 --- a/src/platform/nrf52/architecture.h +++ b/src/platform/nrf52/architecture.h @@ -5,6 +5,25 @@ // // defaults for NRF52 architecture // + +/* + * Internal Reference is +/-0.6V, with an adjustable gain of 1/6, 1/5, 1/4, + * 1/3, 1/2 or 1, meaning 3.6, 3.0, 2.4, 1.8, 1.2 or 0.6V for the ADC levels. + * + * External Reference is VDD/4, with an adjustable gain of 1, 2 or 4, meaning + * VDD/4, VDD/2 or VDD for the ADC levels. + * + * Default settings are internal reference with 1/6 gain (GND..3.6V ADC range) + * Some variants overwrite it. + */ +#ifndef AREF_VOLTAGE +#define AREF_VOLTAGE 3.6 +#endif + +#ifndef BATTERY_SENSE_RESOLUTION_BITS +#define BATTERY_SENSE_RESOLUTION_BITS 10 +#endif + #ifndef HAS_BLUETOOTH #define HAS_BLUETOOTH 1 #endif diff --git a/src/platform/nrf52/main-nrf52.cpp b/src/platform/nrf52/main-nrf52.cpp index 472107229..2068fe2a7 100644 --- a/src/platform/nrf52/main-nrf52.cpp +++ b/src/platform/nrf52/main-nrf52.cpp @@ -9,12 +9,12 @@ #define NRFX_WDT_ENABLED 1 #define NRFX_WDT0_ENABLED 1 #define NRFX_WDT_CONFIG_NO_IRQ 1 -#include -#include - +#include "nrfx_power.h" #include #include #include +#include +#include #include // #include #include "NodeDB.h" @@ -23,6 +23,7 @@ #include "main.h" #include "meshUtils.h" #include "power.h" +#include #include @@ -30,6 +31,21 @@ #include "BQ25713.h" #endif +// WARNING! THRESHOLD + HYSTERESIS should be less than regulated VDD voltage - which depends on board +// and is 3.0 or 3.3V. Also VDD likes to read values like 2.9999 so make sure you account for that +// otherwise board will not boot at all. Before you modify this part - please triple read NRF52840 power design +// section in datasheet and you understand how REG0 and REG1 regulators work together. +#ifndef SAFE_VDD_VOLTAGE_THRESHOLD +#define SAFE_VDD_VOLTAGE_THRESHOLD 2.7 +#endif + +// hysteresis value +#ifndef SAFE_VDD_VOLTAGE_THRESHOLD_HYST +#define SAFE_VDD_VOLTAGE_THRESHOLD_HYST 0.2 +#endif + +uint16_t getVDDVoltage(); + // Weak empty variant initialization function. // May be redefined by variant files. void variant_shutdown() __attribute__((weak)); @@ -38,12 +54,95 @@ void variant_shutdown() {} static nrfx_wdt_t nrfx_wdt = NRFX_WDT_INSTANCE(0); static nrfx_wdt_channel_id nrfx_wdt_channel_id_nrf52_main; +// This is a public global so that the debugger can set it to false automatically from our gdbinit +// @phaseloop comment: most part of codebase, including filesystem flash driver depend on softdevice +// methods so disabling it may actually crash thing. Proceed with caution. + +bool useSoftDevice = true; // Set to false for easier debugging + static inline void debugger_break(void) { __asm volatile("bkpt #0x01\n\t" "mov pc, lr\n\t"); } +// PowerHAL NRF52 specific function implementations +bool powerHAL_isVBUSConnected() +{ + return NRF_POWER->USBREGSTATUS & POWER_USBREGSTATUS_VBUSDETECT_Msk; +} + +bool powerHAL_isPowerLevelSafe() +{ + + static bool powerLevelSafe = true; + + uint16_t threshold = SAFE_VDD_VOLTAGE_THRESHOLD * 1000; // convert V to mV + uint16_t hysteresis = SAFE_VDD_VOLTAGE_THRESHOLD_HYST * 1000; + + if (powerLevelSafe) { + if (getVDDVoltage() < threshold) { + powerLevelSafe = false; + } + } else { + // power level is only safe again when it raises above threshold + hysteresis + if (getVDDVoltage() >= (threshold + hysteresis)) { + powerLevelSafe = true; + } + } + + return powerLevelSafe; +} + +void powerHAL_platformInit() +{ + + // Enable POF power failure comparator. It will prevent writing to NVMC flash when supply voltage is too low. + // Set to some low value as last resort - powerHAL_isPowerLevelSafe uses different method and should manage proper node + // behaviour on its own. + + // POFWARN is pretty useless for node power management because it triggers only once and clearing this event will not + // re-trigger it again until voltage rises to safe level and drops again. So we will use SAADC routed to VDD to read safely + // voltage. + + // @phaseloop: I disable POFCON for now because it seems to be unreliable or buggy. Even when set at 2.0V it + // triggers below 2.8V and corrupts data when pairing bluetooth - because it prevents filesystem writes and + // adafruit BLE library triggers lfs_assert which reboots node and formats filesystem. + // I did experiments with bench power supply and no matter what is set to POFCON, it always triggers right below + // 2.8V. I compared raw registry values with datasheet. + + NRF_POWER->POFCON = + ((POWER_POFCON_THRESHOLD_V22 << POWER_POFCON_THRESHOLD_Pos) | (POWER_POFCON_POF_Enabled << POWER_POFCON_POF_Pos)); + + // remember to always match VBAT_AR_INTERNAL with AREF_VALUE in variant definition file +#ifdef VBAT_AR_INTERNAL + analogReference(VBAT_AR_INTERNAL); +#else + analogReference(AR_INTERNAL); // 3.6V +#endif +} + +// get VDD voltage (in millivolts) +uint16_t getVDDVoltage() +{ + // we use the same values as regular battery read so there is no conflict on SAADC + analogReadResolution(BATTERY_SENSE_RESOLUTION_BITS); + + // VDD range on NRF52840 is 1.8-3.3V so we need to remap analog reference to 3.6V + // let's hope battery reading runs in same task and we don't have race condition + analogReference(AR_INTERNAL); + + uint16_t vddADCRead = analogReadVDD(); + float voltage = ((1000 * 3.6) / pow(2, BATTERY_SENSE_RESOLUTION_BITS)) * vddADCRead; + +// restore default battery reading reference +#ifdef VBAT_AR_INTERNAL + analogReference(VBAT_AR_INTERNAL); +#endif + + return voltage; +} + bool loopCanSleep() { // turn off sleep only while connected via USB @@ -72,22 +171,6 @@ void getMacAddr(uint8_t *dmac) dmac[0] = src[5] | 0xc0; // MSB high two bits get set elsewhere in the bluetooth stack } -static void initBrownout() -{ - auto vccthresh = POWER_POFCON_THRESHOLD_V24; - - auto err_code = sd_power_pof_enable(POWER_POFCON_POF_Enabled); - assert(err_code == NRF_SUCCESS); - - err_code = sd_power_pof_threshold_set(vccthresh); - assert(err_code == NRF_SUCCESS); - - // We don't bother with setting up brownout if soft device is disabled - because during production we always use softdevice -} - -// This is a public global so that the debugger can set it to false automatically from our gdbinit -bool useSoftDevice = true; // Set to false for easier debugging - #if !MESHTASTIC_EXCLUDE_BLUETOOTH void setBluetoothEnable(bool enable) { @@ -106,7 +189,6 @@ void setBluetoothEnable(bool enable) if (!initialized) { nrf52Bluetooth = new NRF52Bluetooth(); nrf52Bluetooth->startDisabled(); - initBrownout(); initialized = true; } return; @@ -120,9 +202,6 @@ void setBluetoothEnable(bool enable) LOG_DEBUG("Init NRF52 Bluetooth"); nrf52Bluetooth = new NRF52Bluetooth(); nrf52Bluetooth->setup(); - - // We delay brownout init until after BLE because BLE starts soft device - initBrownout(); } // Already setup, apparently else @@ -192,9 +271,24 @@ extern "C" void lfs_assert(const char *reason) delay(500); // Give the serial port a bit of time to output that last message. // Try setting GPREGRET with the SoftDevice first. If that fails (perhaps because the SD hasn't been initialize yet) then set // NRF_POWER->GPREGRET directly. - if (!(sd_power_gpregret_clr(0, 0xFF) == NRF_SUCCESS && sd_power_gpregret_set(0, NRF52_MAGIC_LFS_IS_CORRUPT) == NRF_SUCCESS)) { - NRF_POWER->GPREGRET = NRF52_MAGIC_LFS_IS_CORRUPT; + + // TODO: this will/can crash CPU if bluetooth stack is not compiled in or bluetooth is not initialized + // (regardless if enabled or disabled) - as there is no live SoftDevice stack + // implement "safe" functions detecting softdevice stack state and using proper method to set registers + + // do not set GPREGRET if POFWARN is triggered because it means lfs_assert reports flash undervoltage protection + // and not data corruption. Reboot is fine as boot procedure will wait until power level is safe again + + if (!NRF_POWER->EVENTS_POFWARN) { + if (!(sd_power_gpregret_clr(0, 0xFF) == NRF_SUCCESS && + sd_power_gpregret_set(0, NRF52_MAGIC_LFS_IS_CORRUPT) == NRF_SUCCESS)) { + NRF_POWER->GPREGRET = NRF52_MAGIC_LFS_IS_CORRUPT; + } } + + // TODO: this should not be done when SoftDevice is enabled as device will not boot back on soft reset + // as some data is retained in RAM which will prevent re-enabling bluetooth stack + // Google what Nordic has to say about NVIC_* + SoftDevice NVIC_SystemReset(); } diff --git a/src/power/PowerHAL.cpp b/src/power/PowerHAL.cpp new file mode 100644 index 000000000..0a8d5f10b --- /dev/null +++ b/src/power/PowerHAL.cpp @@ -0,0 +1,19 @@ + +#include "PowerHAL.h" + +void powerHAL_init() +{ + return powerHAL_platformInit(); +} + +__attribute__((weak, noinline)) void powerHAL_platformInit() {} + +__attribute__((weak, noinline)) bool powerHAL_isPowerLevelSafe() +{ + return true; +} + +__attribute__((weak, noinline)) bool powerHAL_isVBUSConnected() +{ + return false; +} diff --git a/src/power/PowerHAL.h b/src/power/PowerHAL.h new file mode 100644 index 000000000..318b06810 --- /dev/null +++ b/src/power/PowerHAL.h @@ -0,0 +1,26 @@ + +/* + +Power Hardware Abstraction Layer. Set of API calls to offload power management, measurements, reboots, etc +to the platform and variant code to avoid #ifdef spaghetti hell and limitless device-based edge cases +in the main firmware code + +Functions declared here (with exception of powerHAL_init) should be defined in platform specific codebase. +Default function body does usually nothing. + +*/ + +// Initialize HAL layer. Call it as early as possible during device boot +// do not overwrite it as it's not declared with "weak" attribute. +void powerHAL_init(); + +// platform specific init code if needed to be run early on boot +void powerHAL_platformInit(); + +// Return true if current battery level is safe for device operation (for example flash writes). +// This should be reported by power failure comparator (NRF52) or similar circuits on other platforms. +// Do not use battery ADC as improper ADC configuration may prevent device from booting. +bool powerHAL_isPowerLevelSafe(); + +// return if USB voltage is connected +bool powerHAL_isVBUSConnected(); From 8894a0b71169f980e06b3131b290483a1bcf5f47 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sun, 25 Jan 2026 18:02:26 -0600 Subject: [PATCH 37/99] Consolidate LoRa params / preset logic and fix display of preset values (#9413) * Consolidate LoRa params / preset logic and fix display of preset values * Move preset coercion logic to RadioInterface * Fix some warnings * Fix warnings * STM32 fix * Add unit tests * Update src/mesh/RadioInterface.h Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/gps/RTC.cpp | 9 +-- src/gps/RTC.h | 2 +- src/graphics/draw/MenuHandler.cpp | 48 +------------- src/graphics/draw/MenuHandler.h | 2 +- src/mesh/MeshRadio.h | 97 ++++++++++++++++++++++++++++- src/mesh/NodeDB.cpp | 8 +++ src/mesh/RadioInterface.cpp | 92 +++++++++------------------ src/mesh/RadioInterface.h | 9 +++ test/test_radio/test_main.cpp | 100 ++++++++++++++++++++++++++++++ 9 files changed, 252 insertions(+), 115 deletions(-) create mode 100644 test/test_radio/test_main.cpp diff --git a/src/gps/RTC.cpp b/src/gps/RTC.cpp index 25cd3ceff..ad26b55a4 100644 --- a/src/gps/RTC.cpp +++ b/src/gps/RTC.cpp @@ -397,7 +397,7 @@ uint32_t getValidTime(RTCQuality minQuality, bool local) return (currentQuality >= minQuality) ? getTime(local) : 0; } -time_t gm_mktime(struct tm *tm) +time_t gm_mktime(const struct tm *tm) { #if !MESHTASTIC_EXCLUDE_TZ time_t result = 0; @@ -413,8 +413,8 @@ time_t gm_mktime(struct tm *tm) days_before_this_year -= 719162; // (1969 * 365 + 1969 / 4 - 1969 / 100 + 1969 / 400); // Now, within this tm->year, compute the days *before* this tm->month starts. - int days_before_month[12] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334}; // non-leap year - int days_this_year_before_this_month = days_before_month[tm->tm_mon]; // tm->tm_mon is 0..11 + static const int days_before_month[12] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334}; // non-leap year + int days_this_year_before_this_month = days_before_month[tm->tm_mon]; // tm->tm_mon is 0..11 // If this is a leap year, and we're past February, add a day: if (tm->tm_mon >= 2 && (year % 4) == 0 && ((year % 100) != 0 || (year % 400) == 0)) { @@ -435,6 +435,7 @@ time_t gm_mktime(struct tm *tm) return result; #else - return mktime(tm); + struct tm tmCopy = *tm; + return mktime(&tmCopy); #endif } diff --git a/src/gps/RTC.h b/src/gps/RTC.h index 06dd34c16..cf6db0239 100644 --- a/src/gps/RTC.h +++ b/src/gps/RTC.h @@ -54,7 +54,7 @@ uint32_t getValidTime(RTCQuality minQuality, bool local = false); RTCSetResult readFromRTC(); -time_t gm_mktime(struct tm *tm); +time_t gm_mktime(const struct tm *tm); #define SEC_PER_DAY 86400 #define SEC_PER_HOUR 3600 diff --git a/src/graphics/draw/MenuHandler.cpp b/src/graphics/draw/MenuHandler.cpp index c5a4106e7..6d29e9f7f 100644 --- a/src/graphics/draw/MenuHandler.cpp +++ b/src/graphics/draw/MenuHandler.cpp @@ -266,52 +266,8 @@ void menuHandler::FrequencySlotPicker() // Calculate number of channels (copied from RadioInterface::applyModemConfig()) meshtastic_Config_LoRaConfig &loraConfig = config.lora; - double bw = loraConfig.bandwidth; - if (loraConfig.use_preset) { - switch (loraConfig.modem_preset) { - case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO: - bw = (myRegion->wideLora) ? 1625.0 : 500; - break; - case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_FAST: - bw = (myRegion->wideLora) ? 812.5 : 250; - break; - case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_SLOW: - bw = (myRegion->wideLora) ? 812.5 : 250; - break; - case meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_FAST: - bw = (myRegion->wideLora) ? 812.5 : 250; - break; - case meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_SLOW: - bw = (myRegion->wideLora) ? 812.5 : 250; - break; - case meshtastic_Config_LoRaConfig_ModemPreset_LONG_TURBO: - bw = (myRegion->wideLora) ? 1625.0 : 500; - break; - default: - bw = (myRegion->wideLora) ? 812.5 : 250; - break; - case meshtastic_Config_LoRaConfig_ModemPreset_LONG_MODERATE: - bw = (myRegion->wideLora) ? 406.25 : 125; - break; - case meshtastic_Config_LoRaConfig_ModemPreset_LONG_SLOW: - bw = (myRegion->wideLora) ? 406.25 : 125; - break; - } - } else { - bw = loraConfig.bandwidth; - if (bw == 31) // This parameter is not an integer - bw = 31.25; - if (bw == 62) // Fix for 62.5Khz bandwidth - bw = 62.5; - if (bw == 200) - bw = 203.125; - if (bw == 400) - bw = 406.25; - if (bw == 800) - bw = 812.5; - if (bw == 1600) - bw = 1625.0; - } + double bw = loraConfig.use_preset ? modemPresetToBwKHz(loraConfig.modem_preset, myRegion->wideLora) + : bwCodeToKHz(loraConfig.bandwidth); uint32_t numChannels = 0; if (myRegion) { diff --git a/src/graphics/draw/MenuHandler.h b/src/graphics/draw/MenuHandler.h index 45fd0bf5f..1b964678b 100644 --- a/src/graphics/draw/MenuHandler.h +++ b/src/graphics/draw/MenuHandler.h @@ -140,7 +140,7 @@ struct ScreenColor { uint8_t b; bool useVariant; - ScreenColor(uint8_t rIn = 0, uint8_t gIn = 0, uint8_t bIn = 0, bool variantIn = false) + explicit ScreenColor(uint8_t rIn = 0, uint8_t gIn = 0, uint8_t bIn = 0, bool variantIn = false) : r(rIn), g(gIn), b(bIn), useVariant(variantIn) { } diff --git a/src/mesh/MeshRadio.h b/src/mesh/MeshRadio.h index f2514eea1..bbb0ee00f 100644 --- a/src/mesh/MeshRadio.h +++ b/src/mesh/MeshRadio.h @@ -22,4 +22,99 @@ struct RegionInfo { extern const RegionInfo regions[]; extern const RegionInfo *myRegion; -extern void initRegion(); \ No newline at end of file +extern void initRegion(); + +static inline float bwCodeToKHz(uint16_t bwCode) +{ + if (bwCode == 31) + return 31.25f; + if (bwCode == 62) + return 62.5f; + if (bwCode == 200) + return 203.125f; + if (bwCode == 400) + return 406.25f; + if (bwCode == 800) + return 812.5f; + if (bwCode == 1600) + return 1625.0f; + return (float)bwCode; +} + +static inline uint16_t bwKHzToCode(float bwKHz) +{ + if (bwKHz > 31.24f && bwKHz < 31.26f) + return 31; + if (bwKHz > 62.49f && bwKHz < 62.51f) + return 62; + if (bwKHz > 203.12f && bwKHz < 203.13f) + return 200; + if (bwKHz > 406.24f && bwKHz < 406.26f) + return 400; + if (bwKHz > 812.49f && bwKHz < 812.51f) + return 800; + if (bwKHz > 1624.99f && bwKHz < 1625.01f) + return 1600; + return (uint16_t)(bwKHz + 0.5f); +} + +static inline void modemPresetToParams(meshtastic_Config_LoRaConfig_ModemPreset preset, bool wideLora, float &bwKHz, uint8_t &sf, + uint8_t &cr) +{ + switch (preset) { + case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO: + bwKHz = wideLora ? 1625.0f : 500.0f; + cr = 5; + sf = 7; + break; + case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_FAST: + bwKHz = wideLora ? 812.5f : 250.0f; + cr = 5; + sf = 7; + break; + case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_SLOW: + bwKHz = wideLora ? 812.5f : 250.0f; + cr = 5; + sf = 8; + break; + case meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_FAST: + bwKHz = wideLora ? 812.5f : 250.0f; + cr = 5; + sf = 9; + break; + case meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_SLOW: + bwKHz = wideLora ? 812.5f : 250.0f; + cr = 5; + sf = 10; + break; + case meshtastic_Config_LoRaConfig_ModemPreset_LONG_TURBO: + bwKHz = wideLora ? 1625.0f : 500.0f; + cr = 8; + sf = 11; + break; + case meshtastic_Config_LoRaConfig_ModemPreset_LONG_MODERATE: + bwKHz = wideLora ? 406.25f : 125.0f; + cr = 8; + sf = 11; + break; + case meshtastic_Config_LoRaConfig_ModemPreset_LONG_SLOW: + bwKHz = wideLora ? 406.25f : 125.0f; + cr = 8; + sf = 12; + break; + default: // LONG_FAST (or illegal) + bwKHz = wideLora ? 812.5f : 250.0f; + cr = 5; + sf = 11; + break; + } +} + +static inline float modemPresetToBwKHz(meshtastic_Config_LoRaConfig_ModemPreset preset, bool wideLora) +{ + float bwKHz = 0; + uint8_t sf = 0; + uint8_t cr = 0; + modemPresetToParams(preset, wideLora, bwKHz, sf, cr); + return bwKHz; +} \ No newline at end of file diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 375bc76e3..f87fa58b1 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -13,6 +13,7 @@ #include "PacketHistory.h" #include "PowerFSM.h" #include "RTC.h" +#include "RadioInterface.h" #include "Router.h" #include "SPILock.h" #include "SafeFile.h" @@ -1297,6 +1298,13 @@ void NodeDB::loadFromDisk() LOG_INFO("Loaded saved config version %d", config.version); } } + + // Coerce LoRa config fields derived from presets while bootstrapping. + // Some clients/UI components display bandwidth/spread_factor directly from config even in preset mode. + if (config.has_lora && config.lora.use_preset) { + RadioInterface::bootstrapLoRaConfigFromPreset(config.lora); + } + if (backupSecurity.private_key.size > 0) { LOG_DEBUG("Restoring backup of security config"); config.security = backupSecurity; diff --git a/src/mesh/RadioInterface.cpp b/src/mesh/RadioInterface.cpp index aaaca719e..26ef162b9 100644 --- a/src/mesh/RadioInterface.cpp +++ b/src/mesh/RadioInterface.cpp @@ -220,6 +220,34 @@ void initRegion() myRegion = r; } +void RadioInterface::bootstrapLoRaConfigFromPreset(meshtastic_Config_LoRaConfig &loraConfig) +{ + if (!loraConfig.use_preset) { + return; + } + + // Find region info to determine whether "wide" LoRa is permitted (2.4 GHz uses wider bandwidth codes). + const RegionInfo *r = regions; + for (; r->code != meshtastic_Config_LoRaConfig_RegionCode_UNSET && r->code != loraConfig.region; r++) + ; + + const bool regionWideLora = r->wideLora; + + float bwKHz = 0; + uint8_t sf = 0; + uint8_t cr = 0; + modemPresetToParams(loraConfig.modem_preset, regionWideLora, bwKHz, sf, cr); + + // If selected preset requests a bandwidth larger than the region span, fall back to LONG_FAST. + if (r->code != meshtastic_Config_LoRaConfig_RegionCode_UNSET && (r->freqEnd - r->freqStart) < (bwKHz / 1000.0f)) { + loraConfig.modem_preset = meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST; + modemPresetToParams(loraConfig.modem_preset, regionWideLora, bwKHz, sf, cr); + } + + loraConfig.bandwidth = bwKHzToCode(bwKHz); + loraConfig.spread_factor = sf; +} + /** * ## LoRaWAN for North America @@ -474,54 +502,7 @@ void RadioInterface::applyModemConfig() bool validConfig = false; // We need to check for a valid configuration while (!validConfig) { if (loraConfig.use_preset) { - - switch (loraConfig.modem_preset) { - case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO: - bw = (myRegion->wideLora) ? 1625.0 : 500; - cr = 5; - sf = 7; - break; - case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_FAST: - bw = (myRegion->wideLora) ? 812.5 : 250; - cr = 5; - sf = 7; - break; - case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_SLOW: - bw = (myRegion->wideLora) ? 812.5 : 250; - cr = 5; - sf = 8; - break; - case meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_FAST: - bw = (myRegion->wideLora) ? 812.5 : 250; - cr = 5; - sf = 9; - break; - case meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_SLOW: - bw = (myRegion->wideLora) ? 812.5 : 250; - cr = 5; - sf = 10; - break; - case meshtastic_Config_LoRaConfig_ModemPreset_LONG_TURBO: - bw = (myRegion->wideLora) ? 1625.0 : 500; - cr = 8; - sf = 11; - break; - default: // Config_LoRaConfig_ModemPreset_LONG_FAST is default. Gracefully use this is preset is something illegal. - bw = (myRegion->wideLora) ? 812.5 : 250; - cr = 5; - sf = 11; - break; - case meshtastic_Config_LoRaConfig_ModemPreset_LONG_MODERATE: - bw = (myRegion->wideLora) ? 406.25 : 125; - cr = 8; - sf = 11; - break; - case meshtastic_Config_LoRaConfig_ModemPreset_LONG_SLOW: - bw = (myRegion->wideLora) ? 406.25 : 125; - cr = 8; - sf = 12; - break; - } + modemPresetToParams(loraConfig.modem_preset, myRegion->wideLora, bw, sf, cr); if (loraConfig.coding_rate >= 5 && loraConfig.coding_rate <= 8 && loraConfig.coding_rate != cr) { cr = loraConfig.coding_rate; LOG_INFO("Using custom Coding Rate %u", cr); @@ -529,20 +510,7 @@ void RadioInterface::applyModemConfig() } else { sf = loraConfig.spread_factor; cr = loraConfig.coding_rate; - bw = loraConfig.bandwidth; - - if (bw == 31) // This parameter is not an integer - bw = 31.25; - if (bw == 62) // Fix for 62.5Khz bandwidth - bw = 62.5; - if (bw == 200) - bw = 203.125; - if (bw == 400) - bw = 406.25; - if (bw == 800) - bw = 812.5; - if (bw == 1600) - bw = 1625.0; + bw = bwCodeToKHz(loraConfig.bandwidth); } if ((myRegion->freqEnd - myRegion->freqStart) < bw / 1000) { diff --git a/src/mesh/RadioInterface.h b/src/mesh/RadioInterface.h index 6049a11cc..e4dc02de5 100644 --- a/src/mesh/RadioInterface.h +++ b/src/mesh/RadioInterface.h @@ -7,6 +7,9 @@ #include "airtime.h" #include "error.h" +// Forward decl to avoid a direct include of generated config headers / full LoRaConfig definition in this widely-included file. +typedef struct _meshtastic_Config_LoRaConfig meshtastic_Config_LoRaConfig; + #define MAX_TX_QUEUE 16 // max number of packets which can be waiting for transmission #define MAX_LORA_PAYLOAD_LEN 255 // max length of 255 per Semtech's datasheets on SX12xx @@ -115,6 +118,12 @@ class RadioInterface virtual ~RadioInterface() {} + /** + * Coerce LoRa config fields (bandwidth/spread_factor) derived from presets. + * This is used during early bootstrapping so UIs that display these fields directly remain consistent. + */ + static void bootstrapLoRaConfigFromPreset(meshtastic_Config_LoRaConfig &loraConfig); + /** * Return true if we think the board can go to sleep (i.e. our tx queue is empty, we are not sending or receiving) * diff --git a/test/test_radio/test_main.cpp b/test/test_radio/test_main.cpp new file mode 100644 index 000000000..fbe2b1b13 --- /dev/null +++ b/test/test_radio/test_main.cpp @@ -0,0 +1,100 @@ +#include "MeshRadio.h" +#include "RadioInterface.h" +#include "TestUtil.h" +#include + +#include "meshtastic/config.pb.h" + +static void test_bwCodeToKHz_specialMappings() +{ + TEST_ASSERT_FLOAT_WITHIN(0.0001f, 31.25f, bwCodeToKHz(31)); + TEST_ASSERT_FLOAT_WITHIN(0.0001f, 62.5f, bwCodeToKHz(62)); + TEST_ASSERT_FLOAT_WITHIN(0.0001f, 203.125f, bwCodeToKHz(200)); + TEST_ASSERT_FLOAT_WITHIN(0.0001f, 406.25f, bwCodeToKHz(400)); + TEST_ASSERT_FLOAT_WITHIN(0.0001f, 812.5f, bwCodeToKHz(800)); + TEST_ASSERT_FLOAT_WITHIN(0.0001f, 1625.0f, bwCodeToKHz(1600)); +} + +static void test_bwCodeToKHz_passthrough() +{ + TEST_ASSERT_FLOAT_WITHIN(0.0001f, 125.0f, bwCodeToKHz(125)); + TEST_ASSERT_FLOAT_WITHIN(0.0001f, 250.0f, bwCodeToKHz(250)); +} + +static void test_bootstrapLoRaConfigFromPreset_noopWhenUsePresetFalse() +{ + meshtastic_Config_LoRaConfig cfg = meshtastic_Config_LoRaConfig_init_zero; + cfg.use_preset = false; + cfg.region = meshtastic_Config_LoRaConfig_RegionCode_US; + cfg.modem_preset = meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_FAST; + cfg.bandwidth = 123; + cfg.spread_factor = 8; + + RadioInterface::bootstrapLoRaConfigFromPreset(cfg); + + TEST_ASSERT_EQUAL_UINT16(123, cfg.bandwidth); + TEST_ASSERT_EQUAL_UINT32(8, cfg.spread_factor); + TEST_ASSERT_EQUAL(meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_FAST, cfg.modem_preset); +} + +static void test_bootstrapLoRaConfigFromPreset_setsDerivedFields_nonWideRegion() +{ + meshtastic_Config_LoRaConfig cfg = meshtastic_Config_LoRaConfig_init_zero; + cfg.use_preset = true; + cfg.region = meshtastic_Config_LoRaConfig_RegionCode_US; + cfg.modem_preset = meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_FAST; + + RadioInterface::bootstrapLoRaConfigFromPreset(cfg); + + TEST_ASSERT_EQUAL_UINT16(250, cfg.bandwidth); + TEST_ASSERT_EQUAL_UINT32(9, cfg.spread_factor); +} + +static void test_bootstrapLoRaConfigFromPreset_setsDerivedFields_wideRegion() +{ + meshtastic_Config_LoRaConfig cfg = meshtastic_Config_LoRaConfig_init_zero; + cfg.use_preset = true; + cfg.region = meshtastic_Config_LoRaConfig_RegionCode_LORA_24; + cfg.modem_preset = meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_FAST; + + RadioInterface::bootstrapLoRaConfigFromPreset(cfg); + + TEST_ASSERT_EQUAL_UINT16(800, cfg.bandwidth); + TEST_ASSERT_EQUAL_UINT32(9, cfg.spread_factor); +} + +static void test_bootstrapLoRaConfigFromPreset_fallsBackIfBandwidthExceedsRegionSpan() +{ + meshtastic_Config_LoRaConfig cfg = meshtastic_Config_LoRaConfig_init_zero; + cfg.use_preset = true; + cfg.region = meshtastic_Config_LoRaConfig_RegionCode_EU_868; + cfg.modem_preset = meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO; + + RadioInterface::bootstrapLoRaConfigFromPreset(cfg); + + TEST_ASSERT_EQUAL(meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST, cfg.modem_preset); + TEST_ASSERT_EQUAL_UINT16(250, cfg.bandwidth); + TEST_ASSERT_EQUAL_UINT32(11, cfg.spread_factor); +} + +void setUp(void) {} +void tearDown(void) {} + +void setup() +{ + delay(10); + delay(2000); + + initializeTestEnvironment(); + + UNITY_BEGIN(); + RUN_TEST(test_bwCodeToKHz_specialMappings); + RUN_TEST(test_bwCodeToKHz_passthrough); + RUN_TEST(test_bootstrapLoRaConfigFromPreset_noopWhenUsePresetFalse); + RUN_TEST(test_bootstrapLoRaConfigFromPreset_setsDerivedFields_nonWideRegion); + RUN_TEST(test_bootstrapLoRaConfigFromPreset_setsDerivedFields_wideRegion); + RUN_TEST(test_bootstrapLoRaConfigFromPreset_fallsBackIfBandwidthExceedsRegionSpan); + exit(UNITY_END()); +} + +void loop() {} From 8a9830282ab48ab52bbf68e46a4af52161535120 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Sun, 25 Jan 2026 20:54:17 -0600 Subject: [PATCH 38/99] Move Lora Init code into LoraInit.cpp/h (#9435) * Move Lora Init code into LoraInit.cpp/h * Add missed extern definition * More carefully check for nullptr in LoraInit.cpp * STM32 fixes * Add extern SPI1 for HW_SPI1_DEVICE * Move initLora to RadioInterface.h --- src/main.cpp | 279 +--------------------------------- src/mesh/RadioInterface.cpp | 294 ++++++++++++++++++++++++++++++++++++ src/mesh/RadioInterface.h | 2 + src/mesh/Router.cpp | 1 - 4 files changed, 302 insertions(+), 274 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index ea114ea34..c063aaa6a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -77,29 +77,10 @@ NRF52Bluetooth *nrf52Bluetooth = nullptr; #include "mqtt/MQTT.h" #endif -#include "LLCC68Interface.h" -#include "LR1110Interface.h" -#include "LR1120Interface.h" -#include "LR1121Interface.h" -#include "RF95Interface.h" -#include "SX1262Interface.h" -#include "SX1268Interface.h" -#include "SX1280Interface.h" -#include "detect/LoRaRadioType.h" - -#ifdef ARCH_STM32WL -#include "STM32WLE5JCInterface.h" -#endif - -#if defined(ARCH_PORTDUINO) -#include "platform/portduino/SimRadio.h" -#endif - #ifdef ARCH_PORTDUINO #include "linux/LinuxHardwareI2C.h" #include "mesh/raspihttp/PiWebServer.h" #include "platform/portduino/PortduinoGlue.h" -#include "platform/portduino/USBHal.h" #include #include #include @@ -254,9 +235,6 @@ ScanI2C::DeviceAddress aqi_found = ScanI2C::ADDRESS_NONE; Adafruit_DRV2605 drv; #endif -// Global LoRa radio type -LoRaRadioType radioType = NO_RADIO; - bool isVibrating = false; bool eink_found = true; @@ -293,6 +271,7 @@ const char *getDeviceName() return name; } +// TODO remove from main.cpp static int32_t ledBlinker() { // Still set up the blinking (heartbeat) interval but skip code path below, so LED will blink if @@ -388,6 +367,7 @@ void setup() // boot sequence will follow when battery level raises to safe mode waitUntilPowerLevelSafe(); + // TODO remove all device-specific setup code to variant.cpp #if defined(R1_NEO) pinMode(DCDC_EN_HOLD, OUTPUT); digitalWrite(DCDC_EN_HOLD, HIGH); @@ -489,11 +469,6 @@ void setup() #endif concurrency::hasBeenSetup = true; -#if ARCH_PORTDUINO - SPISettings spiSettings(portduino_config.spiSpeed, MSBFIRST, SPI_MODE0); -#else - SPISettings spiSettings(4000000, MSBFIRST, SPI_MODE0); -#endif meshtastic_Config_DisplayConfig_OledType screen_model = meshtastic_Config_DisplayConfig_OledType::meshtastic_Config_DisplayConfig_OledType_OLED_AUTO; @@ -632,6 +607,7 @@ void setup() OSThread::setup(); + // TODO make this ifdef based on defined pins and move from main.cpp #if defined(ELECROW_ThinkNode_M1) || defined(ELECROW_ThinkNode_M2) // The ThinkNodes have their own blink logic // ledPeriodic = new Periodic("Blink", elecrowLedBlinker); @@ -1009,6 +985,7 @@ void setup() } #endif // HAS_SCREEN + // TODO Remove magic string // setup TZ prior to time actions. #if !MESHTASTIC_EXCLUDE_TZ LOG_DEBUG("Use compiled/slipstreamed %s", slipstreamTZString); // important, removing this clobbers our magic string @@ -1304,252 +1281,7 @@ void setup() #endif #endif -#ifdef ARCH_PORTDUINO - // as one can't use a function pointer to the class constructor: - auto loraModuleInterface = [](LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, - RADIOLIB_PIN_TYPE busy) { - switch (portduino_config.lora_module) { - case use_rf95: - return (RadioInterface *)new RF95Interface(hal, cs, irq, rst, busy); - case use_sx1262: - return (RadioInterface *)new SX1262Interface(hal, cs, irq, rst, busy); - case use_sx1268: - return (RadioInterface *)new SX1268Interface(hal, cs, irq, rst, busy); - case use_sx1280: - return (RadioInterface *)new SX1280Interface(hal, cs, irq, rst, busy); - case use_lr1110: - return (RadioInterface *)new LR1110Interface(hal, cs, irq, rst, busy); - case use_lr1120: - return (RadioInterface *)new LR1120Interface(hal, cs, irq, rst, busy); - case use_lr1121: - return (RadioInterface *)new LR1121Interface(hal, cs, irq, rst, busy); - case use_llcc68: - return (RadioInterface *)new LLCC68Interface(hal, cs, irq, rst, busy); - case use_simradio: - return (RadioInterface *)new SimRadio; - default: - assert(0); // shouldn't happen - return (RadioInterface *)nullptr; - } - }; - - LOG_DEBUG("Activate %s radio on SPI port %s", portduino_config.loraModules[portduino_config.lora_module].c_str(), - portduino_config.lora_spi_dev.c_str()); - if (portduino_config.lora_spi_dev == "ch341") { - RadioLibHAL = ch341Hal; - } else { - RadioLibHAL = new LockingArduinoHal(SPI, spiSettings); - } - rIf = - loraModuleInterface((LockingArduinoHal *)RadioLibHAL, portduino_config.lora_cs_pin.pin, portduino_config.lora_irq_pin.pin, - portduino_config.lora_reset_pin.pin, portduino_config.lora_busy_pin.pin); - - if (!rIf->init()) { - LOG_WARN("No %s radio", portduino_config.loraModules[portduino_config.lora_module].c_str()); - delete rIf; - rIf = NULL; - exit(EXIT_FAILURE); - } else { - LOG_INFO("%s init success", portduino_config.loraModules[portduino_config.lora_module].c_str()); - } - -#elif defined(HW_SPI1_DEVICE) - LockingArduinoHal *RadioLibHAL = new LockingArduinoHal(SPI1, spiSettings); -#else // HW_SPI1_DEVICE - LockingArduinoHal *RadioLibHAL = new LockingArduinoHal(SPI, spiSettings); -#endif - - // radio init MUST BE AFTER service.init, so we have our radio config settings (from nodedb init) -#if defined(USE_STM32WLx) - if (!rIf) { - rIf = new STM32WLE5JCInterface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY); - if (!rIf->init()) { - LOG_WARN("No STM32WL radio"); - delete rIf; - rIf = NULL; - } else { - LOG_INFO("STM32WL init success"); - radioType = STM32WLx_RADIO; - } - } -#endif - -#if defined(RF95_IRQ) && RADIOLIB_EXCLUDE_SX127X != 1 - if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) { - rIf = new RF95Interface(RadioLibHAL, LORA_CS, RF95_IRQ, RF95_RESET, RF95_DIO1); - if (!rIf->init()) { - LOG_WARN("No RF95 radio"); - delete rIf; - rIf = NULL; - } else { - LOG_INFO("RF95 init success"); - radioType = RF95_RADIO; - } - } -#endif - -#if defined(USE_SX1262) && !defined(ARCH_PORTDUINO) && !defined(TCXO_OPTIONAL) && RADIOLIB_EXCLUDE_SX126X != 1 - if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) { - auto *sxIf = new SX1262Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY); -#ifdef SX126X_DIO3_TCXO_VOLTAGE - sxIf->setTCXOVoltage(SX126X_DIO3_TCXO_VOLTAGE); -#endif - if (!sxIf->init()) { - LOG_WARN("No SX1262 radio"); - delete sxIf; - rIf = NULL; - } else { - LOG_INFO("SX1262 init success"); - rIf = sxIf; - radioType = SX1262_RADIO; - } - } -#endif - -#if defined(USE_SX1262) && !defined(ARCH_PORTDUINO) && defined(TCXO_OPTIONAL) - if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) { - // try using the specified TCXO voltage - auto *sxIf = new SX1262Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY); - sxIf->setTCXOVoltage(SX126X_DIO3_TCXO_VOLTAGE); - if (!sxIf->init()) { - LOG_WARN("No SX1262 radio with TCXO, Vref %fV", SX126X_DIO3_TCXO_VOLTAGE); - delete sxIf; - rIf = NULL; - } else { - LOG_INFO("SX1262 init success, TCXO, Vref %fV", SX126X_DIO3_TCXO_VOLTAGE); - rIf = sxIf; - radioType = SX1262_RADIO; - } - } - - if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) { - // If specified TCXO voltage fails, attempt to use DIO3 as a reference instead - rIf = new SX1262Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY); - if (!rIf->init()) { - LOG_WARN("No SX1262 radio with XTAL, Vref 0.0V"); - delete rIf; - rIf = NULL; - } else { - LOG_INFO("SX1262 init success, XTAL, Vref 0.0V"); - radioType = SX1262_RADIO; - } - } -#endif - -#if defined(USE_SX1268) -#if defined(SX126X_DIO3_TCXO_VOLTAGE) && defined(TCXO_OPTIONAL) - if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) { - // try using the specified TCXO voltage - auto *sxIf = new SX1268Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY); - sxIf->setTCXOVoltage(SX126X_DIO3_TCXO_VOLTAGE); - if (!sxIf->init()) { - LOG_WARN("No SX1268 radio with TCXO, Vref %fV", SX126X_DIO3_TCXO_VOLTAGE); - delete sxIf; - rIf = NULL; - } else { - LOG_INFO("SX1268 init success, TCXO, Vref %fV", SX126X_DIO3_TCXO_VOLTAGE); - rIf = sxIf; - radioType = SX1268_RADIO; - } - } -#endif - if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) { - rIf = new SX1268Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY); - if (!rIf->init()) { - LOG_WARN("No SX1268 radio"); - delete rIf; - rIf = NULL; - } else { - LOG_INFO("SX1268 init success"); - radioType = SX1268_RADIO; - } - } -#endif - -#if defined(USE_LLCC68) - if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) { - rIf = new LLCC68Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY); - if (!rIf->init()) { - LOG_WARN("No LLCC68 radio"); - delete rIf; - rIf = NULL; - } else { - LOG_INFO("LLCC68 init success"); - radioType = LLCC68_RADIO; - } - } -#endif - -#if defined(USE_LR1110) && RADIOLIB_EXCLUDE_LR11X0 != 1 - if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) { - rIf = new LR1110Interface(RadioLibHAL, LR1110_SPI_NSS_PIN, LR1110_IRQ_PIN, LR1110_NRESET_PIN, LR1110_BUSY_PIN); - if (!rIf->init()) { - LOG_WARN("No LR1110 radio"); - delete rIf; - rIf = NULL; - } else { - LOG_INFO("LR1110 init success"); - radioType = LR1110_RADIO; - } - } -#endif - -#if defined(USE_LR1120) && RADIOLIB_EXCLUDE_LR11X0 != 1 - if (!rIf) { - rIf = new LR1120Interface(RadioLibHAL, LR1120_SPI_NSS_PIN, LR1120_IRQ_PIN, LR1120_NRESET_PIN, LR1120_BUSY_PIN); - if (!rIf->init()) { - LOG_WARN("No LR1120 radio"); - delete rIf; - rIf = NULL; - } else { - LOG_INFO("LR1120 init success"); - radioType = LR1120_RADIO; - } - } -#endif - -#if defined(USE_LR1121) && RADIOLIB_EXCLUDE_LR11X0 != 1 - if (!rIf) { - rIf = new LR1121Interface(RadioLibHAL, LR1121_SPI_NSS_PIN, LR1121_IRQ_PIN, LR1121_NRESET_PIN, LR1121_BUSY_PIN); - if (!rIf->init()) { - LOG_WARN("No LR1121 radio"); - delete rIf; - rIf = NULL; - } else { - LOG_INFO("LR1121 init success"); - radioType = LR1121_RADIO; - } - } -#endif - -#if defined(USE_SX1280) && RADIOLIB_EXCLUDE_SX128X != 1 - if (!rIf) { - rIf = new SX1280Interface(RadioLibHAL, SX128X_CS, SX128X_DIO1, SX128X_RESET, SX128X_BUSY); - if (!rIf->init()) { - LOG_WARN("No SX1280 radio"); - delete rIf; - rIf = NULL; - } else { - LOG_INFO("SX1280 init success"); - radioType = SX1280_RADIO; - } - } -#endif - - // check if the radio chip matches the selected region - if ((config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_LORA_24) && rIf && (!rIf->wideLora())) { - LOG_WARN("LoRa chip does not support 2.4GHz. Revert to unset"); - config.lora.region = meshtastic_Config_LoRaConfig_RegionCode_UNSET; - nodeDB->saveToDisk(SEGMENT_CONFIG); - - if (!rIf->reconfigure()) { - LOG_WARN("Reconfigure failed, rebooting"); - if (screen) { - screen->showSimpleBanner("Rebooting..."); - } - rebootAtMsec = millis() + 5000; - } - } + initLoRa(); lateInitVariant(); // Do board specific init (see extra_variants/README.md for documentation) @@ -1638,6 +1370,7 @@ bool suppressRebootBanner; // If true, suppress "Rebooting..." overlay (used for // This will suppress the current delay and instead try to run ASAP. bool runASAP; +// TODO find better home than main.cpp extern meshtastic_DeviceMetadata getDeviceMetadata() { meshtastic_DeviceMetadata deviceMetadata; diff --git a/src/mesh/RadioInterface.cpp b/src/mesh/RadioInterface.cpp index d0097a46a..b489f7939 100644 --- a/src/mesh/RadioInterface.cpp +++ b/src/mesh/RadioInterface.cpp @@ -1,17 +1,36 @@ #include "RadioInterface.h" #include "Channels.h" #include "DisplayFormatters.h" +#include "LLCC68Interface.h" +#include "LR1110Interface.h" +#include "LR1120Interface.h" +#include "LR1121Interface.h" #include "MeshRadio.h" #include "MeshService.h" #include "NodeDB.h" +#include "RF95Interface.h" #include "Router.h" +#include "SX1262Interface.h" +#include "SX1268Interface.h" +#include "SX1280Interface.h" #include "configuration.h" +#include "detect/LoRaRadioType.h" #include "main.h" #include "sleep.h" #include #include #include +#ifdef ARCH_PORTDUINO +#include "platform/portduino/PortduinoGlue.h" +#include "platform/portduino/SimRadio.h" +#include "platform/portduino/USBHal.h" +#endif + +#ifdef ARCH_STM32WL> +#include "STM32WLE5JCInterface.h" +#endif + // Calculate 2^n without calling pow() uint32_t pow_of_2(uint32_t n) { @@ -205,6 +224,281 @@ bool RadioInterface::uses_default_frequency_slot = true; static uint8_t bytes[MAX_LORA_PAYLOAD_LEN + 1]; +// Global LoRa radio type +LoRaRadioType radioType = NO_RADIO; + +extern RadioInterface *rIf; +extern RadioLibHal *RadioLibHAL; +#if defined(HW_SPI1_DEVICE) && defined(ARCH_ESP32) +extern SPIClass SPI1; +#endif + +bool initLoRa() +{ + if (rIf != nullptr) { + delete rIf; + rIf = nullptr; + } + +#if ARCH_PORTDUINO + SPISettings spiSettings(portduino_config.spiSpeed, MSBFIRST, SPI_MODE0); +#else + SPISettings spiSettings(4000000, MSBFIRST, SPI_MODE0); +#endif + +#ifdef ARCH_PORTDUINO + // as one can't use a function pointer to the class constructor: + auto loraModuleInterface = [](LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, + RADIOLIB_PIN_TYPE busy) { + switch (portduino_config.lora_module) { + case use_rf95: + return (RadioInterface *)new RF95Interface(hal, cs, irq, rst, busy); + case use_sx1262: + return (RadioInterface *)new SX1262Interface(hal, cs, irq, rst, busy); + case use_sx1268: + return (RadioInterface *)new SX1268Interface(hal, cs, irq, rst, busy); + case use_sx1280: + return (RadioInterface *)new SX1280Interface(hal, cs, irq, rst, busy); + case use_lr1110: + return (RadioInterface *)new LR1110Interface(hal, cs, irq, rst, busy); + case use_lr1120: + return (RadioInterface *)new LR1120Interface(hal, cs, irq, rst, busy); + case use_lr1121: + return (RadioInterface *)new LR1121Interface(hal, cs, irq, rst, busy); + case use_llcc68: + return (RadioInterface *)new LLCC68Interface(hal, cs, irq, rst, busy); + case use_simradio: + return (RadioInterface *)new SimRadio; + default: + assert(0); // shouldn't happen + return (RadioInterface *)nullptr; + } + }; + + LOG_DEBUG("Activate %s radio on SPI port %s", portduino_config.loraModules[portduino_config.lora_module].c_str(), + portduino_config.lora_spi_dev.c_str()); + if (portduino_config.lora_spi_dev == "ch341") { + RadioLibHAL = ch341Hal; + } else { + if (RadioLibHAL != nullptr) { + delete RadioLibHAL; + RadioLibHAL = nullptr; + } + RadioLibHAL = new LockingArduinoHal(SPI, spiSettings); + } + rIf = + loraModuleInterface((LockingArduinoHal *)RadioLibHAL, portduino_config.lora_cs_pin.pin, portduino_config.lora_irq_pin.pin, + portduino_config.lora_reset_pin.pin, portduino_config.lora_busy_pin.pin); + + if (!rIf->init()) { + LOG_WARN("No %s radio", portduino_config.loraModules[portduino_config.lora_module].c_str()); + delete rIf; + rIf = NULL; + exit(EXIT_FAILURE); + } else { + LOG_INFO("%s init success", portduino_config.loraModules[portduino_config.lora_module].c_str()); + } + +#elif defined(HW_SPI1_DEVICE) + LockingArduinoHal *RadioLibHAL = new LockingArduinoHal(SPI1, spiSettings); +#else // HW_SPI1_DEVICE + LockingArduinoHal *RadioLibHAL = new LockingArduinoHal(SPI, spiSettings); +#endif + +// radio init MUST BE AFTER service.init, so we have our radio config settings (from nodedb init) +#if defined(USE_STM32WLx) + if (!rIf) { + rIf = new STM32WLE5JCInterface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY); + if (!rIf->init()) { + LOG_WARN("No STM32WL radio"); + delete rIf; + rIf = NULL; + } else { + LOG_INFO("STM32WL init success"); + radioType = STM32WLx_RADIO; + } + } +#endif + +#if defined(RF95_IRQ) && RADIOLIB_EXCLUDE_SX127X != 1 + if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) { + rIf = new RF95Interface(RadioLibHAL, LORA_CS, RF95_IRQ, RF95_RESET, RF95_DIO1); + if (!rIf->init()) { + LOG_WARN("No RF95 radio"); + delete rIf; + rIf = NULL; + } else { + LOG_INFO("RF95 init success"); + radioType = RF95_RADIO; + } + } +#endif + +#if defined(USE_SX1262) && !defined(ARCH_PORTDUINO) && !defined(TCXO_OPTIONAL) && RADIOLIB_EXCLUDE_SX126X != 1 + if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) { + auto *sxIf = new SX1262Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY); +#ifdef SX126X_DIO3_TCXO_VOLTAGE + sxIf->setTCXOVoltage(SX126X_DIO3_TCXO_VOLTAGE); +#endif + if (!sxIf->init()) { + LOG_WARN("No SX1262 radio"); + delete sxIf; + rIf = NULL; + } else { + LOG_INFO("SX1262 init success"); + rIf = sxIf; + radioType = SX1262_RADIO; + } + } +#endif + +#if defined(USE_SX1262) && !defined(ARCH_PORTDUINO) && defined(TCXO_OPTIONAL) + if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) { + // try using the specified TCXO voltage + auto *sxIf = new SX1262Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY); + sxIf->setTCXOVoltage(SX126X_DIO3_TCXO_VOLTAGE); + if (!sxIf->init()) { + LOG_WARN("No SX1262 radio with TCXO, Vref %fV", SX126X_DIO3_TCXO_VOLTAGE); + delete sxIf; + rIf = NULL; + } else { + LOG_INFO("SX1262 init success, TCXO, Vref %fV", SX126X_DIO3_TCXO_VOLTAGE); + rIf = sxIf; + radioType = SX1262_RADIO; + } + } + + if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) { + // If specified TCXO voltage fails, attempt to use DIO3 as a reference instead + rIf = new SX1262Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY); + if (!rIf->init()) { + LOG_WARN("No SX1262 radio with XTAL, Vref 0.0V"); + delete rIf; + rIf = NULL; + } else { + LOG_INFO("SX1262 init success, XTAL, Vref 0.0V"); + radioType = SX1262_RADIO; + } + } +#endif + +#if defined(USE_SX1268) +#if defined(SX126X_DIO3_TCXO_VOLTAGE) && defined(TCXO_OPTIONAL) + if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) { + // try using the specified TCXO voltage + auto *sxIf = new SX1268Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY); + sxIf->setTCXOVoltage(SX126X_DIO3_TCXO_VOLTAGE); + if (!sxIf->init()) { + LOG_WARN("No SX1268 radio with TCXO, Vref %fV", SX126X_DIO3_TCXO_VOLTAGE); + delete sxIf; + rIf = NULL; + } else { + LOG_INFO("SX1268 init success, TCXO, Vref %fV", SX126X_DIO3_TCXO_VOLTAGE); + rIf = sxIf; + radioType = SX1268_RADIO; + } + } +#endif + if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) { + rIf = new SX1268Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY); + if (!rIf->init()) { + LOG_WARN("No SX1268 radio"); + delete rIf; + rIf = NULL; + } else { + LOG_INFO("SX1268 init success"); + radioType = SX1268_RADIO; + } + } +#endif + +#if defined(USE_LLCC68) + if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) { + rIf = new LLCC68Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY); + if (!rIf->init()) { + LOG_WARN("No LLCC68 radio"); + delete rIf; + rIf = NULL; + } else { + LOG_INFO("LLCC68 init success"); + radioType = LLCC68_RADIO; + } + } +#endif + +#if defined(USE_LR1110) && RADIOLIB_EXCLUDE_LR11X0 != 1 + if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) { + rIf = new LR1110Interface(RadioLibHAL, LR1110_SPI_NSS_PIN, LR1110_IRQ_PIN, LR1110_NRESET_PIN, LR1110_BUSY_PIN); + if (!rIf->init()) { + LOG_WARN("No LR1110 radio"); + delete rIf; + rIf = NULL; + } else { + LOG_INFO("LR1110 init success"); + radioType = LR1110_RADIO; + } + } +#endif + +#if defined(USE_LR1120) && RADIOLIB_EXCLUDE_LR11X0 != 1 + if (!rIf) { + rIf = new LR1120Interface(RadioLibHAL, LR1120_SPI_NSS_PIN, LR1120_IRQ_PIN, LR1120_NRESET_PIN, LR1120_BUSY_PIN); + if (!rIf->init()) { + LOG_WARN("No LR1120 radio"); + delete rIf; + rIf = NULL; + } else { + LOG_INFO("LR1120 init success"); + radioType = LR1120_RADIO; + } + } +#endif + +#if defined(USE_LR1121) && RADIOLIB_EXCLUDE_LR11X0 != 1 + if (!rIf) { + rIf = new LR1121Interface(RadioLibHAL, LR1121_SPI_NSS_PIN, LR1121_IRQ_PIN, LR1121_NRESET_PIN, LR1121_BUSY_PIN); + if (!rIf->init()) { + LOG_WARN("No LR1121 radio"); + delete rIf; + rIf = NULL; + } else { + LOG_INFO("LR1121 init success"); + radioType = LR1121_RADIO; + } + } +#endif + +#if defined(USE_SX1280) && RADIOLIB_EXCLUDE_SX128X != 1 + if (!rIf) { + rIf = new SX1280Interface(RadioLibHAL, SX128X_CS, SX128X_DIO1, SX128X_RESET, SX128X_BUSY); + if (!rIf->init()) { + LOG_WARN("No SX1280 radio"); + delete rIf; + rIf = NULL; + } else { + LOG_INFO("SX1280 init success"); + radioType = SX1280_RADIO; + } + } +#endif + + // check if the radio chip matches the selected region + if ((config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_LORA_24) && rIf && (!rIf->wideLora())) { + LOG_WARN("LoRa chip does not support 2.4GHz. Revert to unset"); + config.lora.region = meshtastic_Config_LoRaConfig_RegionCode_UNSET; + nodeDB->saveToDisk(SEGMENT_CONFIG); + + if (rIf && !rIf->reconfigure()) { + LOG_WARN("Reconfigure failed, rebooting"); + if (screen) { + screen->showSimpleBanner("Rebooting..."); + } + rebootAtMsec = millis() + 5000; + } + } + return rIf != nullptr; +} + void initRegion() { const RegionInfo *r = regions; diff --git a/src/mesh/RadioInterface.h b/src/mesh/RadioInterface.h index 6049a11cc..ddacf2c5d 100644 --- a/src/mesh/RadioInterface.h +++ b/src/mesh/RadioInterface.h @@ -270,5 +270,7 @@ class RadioInterface } }; +bool initLoRa(); + /// Debug printing for packets void printPacket(const char *prefix, const meshtastic_MeshPacket *p); diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index a3861521a..1a3270040 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -7,7 +7,6 @@ #include "RTC.h" #include "configuration.h" -#include "detect/LoRaRadioType.h" #include "main.h" #include "mesh-pb-constants.h" #include "meshUtils.h" From c038cfe69a17d5d4ab9aa22a5437a378b01063e1 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Mon, 26 Jan 2026 11:54:14 -0600 Subject: [PATCH 39/99] Move device code from main.cpp to earlyInitVariant (#9438) --- src/input/TrackballInterruptBase.cpp | 6 +- src/main.cpp | 80 ++----------------- src/main.h | 12 +-- src/platform/extra_variants/README.md | 2 +- src/platform/nrf52/main-nrf52.cpp | 60 +------------- .../ELECROW-ThinkNode-M5/platformio.ini | 4 + .../esp32s3/ELECROW-ThinkNode-M5/variant.cpp | 12 +++ .../esp32s3/ELECROW-ThinkNode-M5/variant.h | 2 + .../hackaday-communicator/platformio.ini | 6 +- .../esp32s3/hackaday-communicator/variant.cpp | 6 ++ variants/esp32s3/t-deck-pro/platformio.ini | 4 + variants/esp32s3/t-deck-pro/variant.cpp | 14 ++++ variants/esp32s3/t-deck/platformio.ini | 4 + variants/esp32s3/t-deck/variant.cpp | 23 ++++++ variants/esp32s3/tlora-pager/platformio.ini | 4 + variants/esp32s3/tlora-pager/variant.cpp | 31 +++++++ .../nrf52840/ELECROW-ThinkNode-M1/variant.cpp | 18 +++++ .../heltec_mesh_node_t114-inkhud/variant.cpp | 8 ++ .../heltec_mesh_node_t114/variant.cpp | 8 ++ .../nrf52840/heltec_mesh_node_t114/variant.h | 2 +- .../nrf52840/heltec_mesh_solar/variant.cpp | 8 ++ variants/nrf52840/meshlink/variant.cpp | 7 ++ variants/nrf52840/r1-neo/variant.cpp | 8 ++ variants/nrf52840/rak_wismeshtap/variant.cpp | 17 ++++ variants/nrf52840/t-echo/variant.cpp | 10 +++ 25 files changed, 208 insertions(+), 148 deletions(-) create mode 100644 variants/esp32s3/ELECROW-ThinkNode-M5/variant.cpp create mode 100644 variants/esp32s3/hackaday-communicator/variant.cpp create mode 100644 variants/esp32s3/t-deck-pro/variant.cpp create mode 100644 variants/esp32s3/t-deck/variant.cpp create mode 100644 variants/esp32s3/tlora-pager/variant.cpp diff --git a/src/input/TrackballInterruptBase.cpp b/src/input/TrackballInterruptBase.cpp index bbd07e199..bce62caaf 100644 --- a/src/input/TrackballInterruptBase.cpp +++ b/src/input/TrackballInterruptBase.cpp @@ -61,11 +61,7 @@ int32_t TrackballInterruptBase::runOnce() uint32_t pressDuration = millis() - pressStartTime; bool buttonStillPressed = false; -#if defined(T_DECK) - buttonStillPressed = (this->action == TB_ACTION_PRESSED); -#else buttonStillPressed = !digitalRead(_pinPress); -#endif if (!buttonStillPressed) { // Button released @@ -135,7 +131,7 @@ int32_t TrackballInterruptBase::runOnce() } #if defined(T_DECK) // T-deck gets a super-simple debounce on trackball - if (this->action == TB_ACTION_PRESSED && !pressDetected) { + if (this->action == TB_ACTION_PRESSED && (!pressDetected || pressStartTime == 0)) { // Start long press detection pressDetected = true; pressStartTime = millis(); diff --git a/src/main.cpp b/src/main.cpp index c063aaa6a..93cc89296 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -43,10 +43,6 @@ #include "MessageStore.h" #endif -#ifdef ELECROW_ThinkNode_M5 -PCA9557 io(0x18, &Wire); -#endif - #ifdef ARCH_ESP32 #include "freertosinc.h" #if !MESHTASTIC_EXCLUDE_WEBSERVER @@ -312,6 +308,9 @@ __attribute__((weak, noinline)) bool loopCanSleep() void lateInitVariant() __attribute__((weak)); void lateInitVariant() {} +void earlyInitVariant() __attribute__((weak)); +void earlyInitVariant() {} + // NRF52 (and probably other platforms) can report when system is in power failure mode // (eg. too low battery voltage) and operating it is unsafe (data corruption, bootloops, etc). // For example NRF52 will prevent any flash writes in that case automatically @@ -367,27 +366,14 @@ void setup() // boot sequence will follow when battery level raises to safe mode waitUntilPowerLevelSafe(); - // TODO remove all device-specific setup code to variant.cpp -#if defined(R1_NEO) - pinMode(DCDC_EN_HOLD, OUTPUT); - digitalWrite(DCDC_EN_HOLD, HIGH); - pinMode(NRF_ON, OUTPUT); - digitalWrite(NRF_ON, HIGH); -#endif + // Defined in variant.cpp for early init code + earlyInitVariant(); #if defined(PIN_POWER_EN) pinMode(PIN_POWER_EN, OUTPUT); digitalWrite(PIN_POWER_EN, HIGH); #endif -#if defined(ELECROW_ThinkNode_M5) - Wire.begin(48, 47); - io.pinMode(PCA_PIN_EINK_EN, OUTPUT); - io.pinMode(PCA_PIN_POWER_EN, OUTPUT); - io.digitalWrite(PCA_PIN_POWER_EN, HIGH); - // io.pinMode(C2_PIN, OUTPUT); -#endif - #ifdef LED_POWER pinMode(LED_POWER, OUTPUT); digitalWrite(LED_POWER, LED_STATE_ON); @@ -412,62 +398,6 @@ void setup() #endif #endif -#if defined(T_DECK) - // GPIO10 manages all peripheral power supplies - // Turn on peripheral power immediately after MUC starts. - // If some boards are turned on late, ESP32 will reset due to low voltage. - // ESP32-C3(Keyboard) , MAX98357A(Audio Power Amplifier) , - // TF Card , Display backlight(AW9364DNR) , AN48841B(Trackball) , ES7210(Decoder) - pinMode(KB_POWERON, OUTPUT); - digitalWrite(KB_POWERON, HIGH); - // T-Deck has all three SPI peripherals (TFT, SD, LoRa) attached to the same SPI bus - // We need to initialize all CS pins in advance otherwise there will be SPI communication issues - // e.g. when detecting the SD card - pinMode(LORA_CS, OUTPUT); - digitalWrite(LORA_CS, HIGH); - pinMode(SDCARD_CS, OUTPUT); - digitalWrite(SDCARD_CS, HIGH); - pinMode(TFT_CS, OUTPUT); - digitalWrite(TFT_CS, HIGH); - delay(100); -#elif defined(T_DECK_PRO) - pinMode(LORA_EN, OUTPUT); - digitalWrite(LORA_EN, HIGH); - pinMode(LORA_CS, OUTPUT); - digitalWrite(LORA_CS, HIGH); - pinMode(SDCARD_CS, OUTPUT); - digitalWrite(SDCARD_CS, HIGH); - pinMode(PIN_EINK_CS, OUTPUT); - digitalWrite(PIN_EINK_CS, HIGH); -#elif defined(T_LORA_PAGER) - pinMode(LORA_CS, OUTPUT); - digitalWrite(LORA_CS, HIGH); - pinMode(SDCARD_CS, OUTPUT); - digitalWrite(SDCARD_CS, HIGH); - pinMode(TFT_CS, OUTPUT); - digitalWrite(TFT_CS, HIGH); - pinMode(KB_INT, INPUT_PULLUP); - // io expander - io.begin(Wire, XL9555_SLAVE_ADDRESS0, SDA, SCL); - io.pinMode(EXPANDS_DRV_EN, OUTPUT); - io.digitalWrite(EXPANDS_DRV_EN, HIGH); - io.pinMode(EXPANDS_AMP_EN, OUTPUT); - io.digitalWrite(EXPANDS_AMP_EN, LOW); - io.pinMode(EXPANDS_LORA_EN, OUTPUT); - io.digitalWrite(EXPANDS_LORA_EN, HIGH); - io.pinMode(EXPANDS_GPS_EN, OUTPUT); - io.digitalWrite(EXPANDS_GPS_EN, HIGH); - io.pinMode(EXPANDS_KB_EN, OUTPUT); - io.digitalWrite(EXPANDS_KB_EN, HIGH); - io.pinMode(EXPANDS_SD_EN, OUTPUT); - io.digitalWrite(EXPANDS_SD_EN, HIGH); - io.pinMode(EXPANDS_GPIO_EN, OUTPUT); - io.digitalWrite(EXPANDS_GPIO_EN, HIGH); - io.pinMode(EXPANDS_SD_PULLEN, INPUT); -#elif defined(HACKADAY_COMMUNICATOR) - pinMode(KB_INT, INPUT); -#endif - concurrency::hasBeenSetup = true; meshtastic_Config_DisplayConfig_OledType screen_model = diff --git a/src/main.h b/src/main.h index c3528a63d..91e27951f 100644 --- a/src/main.h +++ b/src/main.h @@ -26,8 +26,8 @@ extern NRF52Bluetooth *nrf52Bluetooth; #if ARCH_PORTDUINO extern HardwareSPI *DisplaySPI; extern HardwareSPI *LoraSPI; - #endif + extern ScanI2C::DeviceAddress screen_found; extern ScanI2C::DeviceAddress cardkb_found; extern uint8_t kb_model; @@ -47,16 +47,16 @@ extern bool isUSBPowered; extern Adafruit_DRV2605 drv; #endif +#ifdef HAS_PCA9557 +#include +extern PCA9557 io; +#endif + #ifdef HAS_I2S #include "AudioThread.h" extern AudioThread *audioThread; #endif -#ifdef ELECROW_ThinkNode_M5 -#include -extern PCA9557 io; -#endif - #ifdef HAS_UDP_MULTICAST #include "mesh/udp/UdpMulticastHandler.h" extern UdpMulticastHandler *udpHandler; diff --git a/src/platform/extra_variants/README.md b/src/platform/extra_variants/README.md index e558502f0..838014c4f 100644 --- a/src/platform/extra_variants/README.md +++ b/src/platform/extra_variants/README.md @@ -5,7 +5,7 @@ This directory tree is designed to solve two problems. - The ESP32 arduino/platformio project doesn't support the nice "if initVariant() is found, call that after init" behavior of the nrf52 builds (they use initVariant() internally). - Over the years a lot of 'board specific' init code has been added to init() in main.cpp. It would be great to have a general/clean mechanism to allow developers to specify board specific/unique code in a clean fashion without mucking in main. -So we are borrowing the initVariant() ideas here (by using weak gcc references). You can now define lateInitVariant() if your board needs it. +So we are borrowing the initVariant() ideas here (by using weak gcc references). You can now define earlyInitVariant() and lateInitVariant() if your board needs them. earlyInitVariant() runs at the beginning of setup() directly after waitUntilPowerLevelSafe(); while lateInitVariant() runs after the LoRa radio is initialized. If you'd like a board specific variant to be run, add the variant.cpp file to an appropriately named subdirectory and check for \_VARIANT_boardname in the cpp file (so that your code is only built for your board). diff --git a/src/platform/nrf52/main-nrf52.cpp b/src/platform/nrf52/main-nrf52.cpp index 2068fe2a7..11b05165c 100644 --- a/src/platform/nrf52/main-nrf52.cpp +++ b/src/platform/nrf52/main-nrf52.cpp @@ -430,15 +430,6 @@ void cpuDeepSleep(uint32_t msecToWake) Serial1.end(); #endif -#ifdef TTGO_T_ECHO - // To power off the T-Echo, the display must be set - // as an input pin; otherwise, there will be leakage current. - pinMode(PIN_EINK_CS, INPUT); - pinMode(PIN_EINK_DC, INPUT); - pinMode(PIN_EINK_RES, INPUT); - pinMode(PIN_EINK_BUSY, INPUT); -#endif - setBluetoothEnable(false); #ifdef RAK4630 @@ -449,57 +440,8 @@ void cpuDeepSleep(uint32_t msecToWake) // RAK-12039 set pin for Air quality sensor digitalWrite(AQ_SET_PIN, LOW); #endif -#ifdef RAK14014 - // GPIO restores input status, otherwise there will be leakage current - nrf_gpio_cfg_default(TFT_BL); - nrf_gpio_cfg_default(TFT_DC); - nrf_gpio_cfg_default(TFT_CS); - nrf_gpio_cfg_default(TFT_SCLK); - nrf_gpio_cfg_default(TFT_MOSI); - nrf_gpio_cfg_default(TFT_MISO); - nrf_gpio_cfg_default(SCREEN_TOUCH_INT); - nrf_gpio_cfg_default(WB_I2C1_SCL); - nrf_gpio_cfg_default(WB_I2C1_SDA); - - // nrf_gpio_cfg_default(WB_I2C2_SCL); - // nrf_gpio_cfg_default(WB_I2C2_SDA); -#endif -#endif -#ifdef MESHLINK -#ifdef PIN_WD_EN - digitalWrite(PIN_WD_EN, LOW); -#endif -#endif - -#if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_MESH_SOLAR) - nrf_gpio_cfg_default(PIN_GPS_PPS); - detachInterrupt(PIN_GPS_PPS); - detachInterrupt(PIN_BUTTON1); -#endif - -#ifdef ELECROW_ThinkNode_M1 - for (int pin = 0; pin < 48; pin++) { - if (pin == 17 || pin == 19 || pin == 20 || pin == 22 || pin == 23 || pin == 24 || pin == 25 || pin == 9 || pin == 10 || - pin == PIN_BUTTON1 || pin == PIN_BUTTON2) { - continue; - } - pinMode(pin, OUTPUT); - } - for (int pin = 0; pin < 48; pin++) { - if (pin == 17 || pin == 19 || pin == 20 || pin == 22 || pin == 23 || pin == 24 || pin == 25 || pin == 9 || pin == 10 || - pin == PIN_BUTTON1 || pin == PIN_BUTTON2) { - continue; - } - digitalWrite(pin, LOW); - } - for (int pin = 0; pin < 48; pin++) { - if (pin == 17 || pin == 19 || pin == 20 || pin == 22 || pin == 23 || pin == 24 || pin == 25 || pin == 9 || pin == 10 || - pin == PIN_BUTTON1 || pin == PIN_BUTTON2) { - continue; - } - NRF_GPIO->DIRCLR = (1 << pin); - } #endif + // Run shutdown code if specified in variant.cpp variant_shutdown(); // Sleepy trackers or sensors can low power "sleep" diff --git a/variants/esp32s3/ELECROW-ThinkNode-M5/platformio.ini b/variants/esp32s3/ELECROW-ThinkNode-M5/platformio.ini index 9994cf665..ee51018d4 100644 --- a/variants/esp32s3/ELECROW-ThinkNode-M5/platformio.ini +++ b/variants/esp32s3/ELECROW-ThinkNode-M5/platformio.ini @@ -11,6 +11,10 @@ custom_meshtastic_requires_dfu = false extends = esp32s3_base board = ESP32-S3-WROOM-1-N4 +build_src_filter = + ${esp32s3_base.build_src_filter} + +<../variants/esp32s3/ELECROW-ThinkNode-M5> + build_flags = ${esp32s3_base.build_flags} -D ELECROW_ThinkNode_M5 diff --git a/variants/esp32s3/ELECROW-ThinkNode-M5/variant.cpp b/variants/esp32s3/ELECROW-ThinkNode-M5/variant.cpp new file mode 100644 index 000000000..4b485a1a3 --- /dev/null +++ b/variants/esp32s3/ELECROW-ThinkNode-M5/variant.cpp @@ -0,0 +1,12 @@ +#include "variant.h" +#include + +PCA9557 io(0x18, &Wire); + +void earlyInitVariant() +{ + Wire.begin(48, 47); + io.pinMode(PCA_PIN_EINK_EN, OUTPUT); + io.pinMode(PCA_PIN_POWER_EN, OUTPUT); + io.digitalWrite(PCA_PIN_POWER_EN, HIGH); +} diff --git a/variants/esp32s3/ELECROW-ThinkNode-M5/variant.h b/variants/esp32s3/ELECROW-ThinkNode-M5/variant.h index 5f5133e61..77a64f717 100644 --- a/variants/esp32s3/ELECROW-ThinkNode-M5/variant.h +++ b/variants/esp32s3/ELECROW-ThinkNode-M5/variant.h @@ -4,6 +4,8 @@ #define UART_TX 43 #define UART_RX 44 +#define HAS_PCA9557 + // LED // Both of these are on the GPIO expander #define PCA_LED_USER 1 // the Blue LED diff --git a/variants/esp32s3/hackaday-communicator/platformio.ini b/variants/esp32s3/hackaday-communicator/platformio.ini index 29b2c2305..8fd275c0e 100644 --- a/variants/esp32s3/hackaday-communicator/platformio.ini +++ b/variants/esp32s3/hackaday-communicator/platformio.ini @@ -6,6 +6,10 @@ board_check = true board_build.partitions = default_16MB.csv upload_protocol = esptool +build_src_filter = + ${esp32s3_base.build_src_filter} + +<../variants/esp32s3/hackaday-communicator> + build_flags = ${esp32s3_base.build_flags} -D HACKADAY_COMMUNICATOR -D BOARD_HAS_PSRAM @@ -13,4 +17,4 @@ build_flags = ${esp32s3_base.build_flags} lib_deps = ${esp32s3_base.lib_deps} # renovate: datasource=git-refs depName=meshtastic-Arduino_GFX packageName=https://github.com/meshtastic/Arduino_GFX gitBranch=master - https://github.com/meshtastic/Arduino_GFX/archive/054e81ffaf23784830a734e3c184346789349406.zip + https://github.com/meshtastic/Arduino_GFX/archive/054e81ffaf23784830a734e3c184346789349406.zip \ No newline at end of file diff --git a/variants/esp32s3/hackaday-communicator/variant.cpp b/variants/esp32s3/hackaday-communicator/variant.cpp new file mode 100644 index 000000000..d85b2abb5 --- /dev/null +++ b/variants/esp32s3/hackaday-communicator/variant.cpp @@ -0,0 +1,6 @@ +#include "variant.h" +#include "Arduino.h" +void earlyInitVariant() +{ + pinMode(KB_INT, INPUT); +} \ No newline at end of file diff --git a/variants/esp32s3/t-deck-pro/platformio.ini b/variants/esp32s3/t-deck-pro/platformio.ini index aca31b599..5ba82d045 100644 --- a/variants/esp32s3/t-deck-pro/platformio.ini +++ b/variants/esp32s3/t-deck-pro/platformio.ini @@ -15,6 +15,10 @@ board = t-deck-pro board_check = true upload_protocol = esptool +build_src_filter = + ${esp32s3_base.build_src_filter} + +<../variants/esp32s3/t-deck-pro> + build_flags = ${esp32s3_base.build_flags} -I variants/esp32s3/t-deck-pro -D T_DECK_PRO diff --git a/variants/esp32s3/t-deck-pro/variant.cpp b/variants/esp32s3/t-deck-pro/variant.cpp new file mode 100644 index 000000000..509726c52 --- /dev/null +++ b/variants/esp32s3/t-deck-pro/variant.cpp @@ -0,0 +1,14 @@ +#include "variant.h" +#include "Arduino.h" + +void earlyInitVariant() +{ + pinMode(LORA_EN, OUTPUT); + digitalWrite(LORA_EN, HIGH); + pinMode(LORA_CS, OUTPUT); + digitalWrite(LORA_CS, HIGH); + pinMode(SDCARD_CS, OUTPUT); + digitalWrite(SDCARD_CS, HIGH); + pinMode(PIN_EINK_CS, OUTPUT); + digitalWrite(PIN_EINK_CS, HIGH); +} \ No newline at end of file diff --git a/variants/esp32s3/t-deck/platformio.ini b/variants/esp32s3/t-deck/platformio.ini index 58335796a..54ffe43fe 100644 --- a/variants/esp32s3/t-deck/platformio.ini +++ b/variants/esp32s3/t-deck/platformio.ini @@ -17,6 +17,10 @@ board_check = true board_build.partitions = default_16MB.csv upload_protocol = esptool +build_src_filter = + ${esp32s3_base.build_src_filter} + +<../variants/esp32s3/t-deck> + build_flags = ${esp32s3_base.build_flags} -D T_DECK -D BOARD_HAS_PSRAM diff --git a/variants/esp32s3/t-deck/variant.cpp b/variants/esp32s3/t-deck/variant.cpp new file mode 100644 index 000000000..6b68f142c --- /dev/null +++ b/variants/esp32s3/t-deck/variant.cpp @@ -0,0 +1,23 @@ +#include "variant.h" +#include "Arduino.h" + +void earlyInitVariant() +{ + // GPIO10 manages all peripheral power supplies + // Turn on peripheral power immediately after MUC starts. + // If some boards are turned on late, ESP32 will reset due to low voltage. + // ESP32-C3(Keyboard) , MAX98357A(Audio Power Amplifier) , + // TF Card , Display backlight(AW9364DNR) , AN48841B(Trackball) , ES7210(Decoder) + pinMode(KB_POWERON, OUTPUT); + digitalWrite(KB_POWERON, HIGH); + // T-Deck has all three SPI peripherals (TFT, SD, LoRa) attached to the same SPI bus + // We need to initialize all CS pins in advance otherwise there will be SPI communication issues + // e.g. when detecting the SD card + pinMode(LORA_CS, OUTPUT); + digitalWrite(LORA_CS, HIGH); + pinMode(SDCARD_CS, OUTPUT); + digitalWrite(SDCARD_CS, HIGH); + pinMode(TFT_CS, OUTPUT); + digitalWrite(TFT_CS, HIGH); + delay(100); +} \ No newline at end of file diff --git a/variants/esp32s3/tlora-pager/platformio.ini b/variants/esp32s3/tlora-pager/platformio.ini index ea6369f29..7b4fc5312 100644 --- a/variants/esp32s3/tlora-pager/platformio.ini +++ b/variants/esp32s3/tlora-pager/platformio.ini @@ -17,6 +17,10 @@ board_check = true board_build.partitions = default_16MB.csv upload_protocol = esptool +build_src_filter = + ${esp32s3_base.build_src_filter} + +<../variants/esp32s3/tlora-pager> + build_flags = ${esp32s3_base.build_flags} -I variants/esp32s3/tlora-pager -D T_LORA_PAGER diff --git a/variants/esp32s3/tlora-pager/variant.cpp b/variants/esp32s3/tlora-pager/variant.cpp new file mode 100644 index 000000000..7b0cbdfec --- /dev/null +++ b/variants/esp32s3/tlora-pager/variant.cpp @@ -0,0 +1,31 @@ +#include "variant.h" +#include "ExtensionIOXL9555.hpp" +extern ExtensionIOXL9555 io; + +void earlyInitVariant() +{ + pinMode(LORA_CS, OUTPUT); + digitalWrite(LORA_CS, HIGH); + pinMode(SDCARD_CS, OUTPUT); + digitalWrite(SDCARD_CS, HIGH); + pinMode(TFT_CS, OUTPUT); + digitalWrite(TFT_CS, HIGH); + pinMode(KB_INT, INPUT_PULLUP); + // io expander + io.begin(Wire, XL9555_SLAVE_ADDRESS0, SDA, SCL); + io.pinMode(EXPANDS_DRV_EN, OUTPUT); + io.digitalWrite(EXPANDS_DRV_EN, HIGH); + io.pinMode(EXPANDS_AMP_EN, OUTPUT); + io.digitalWrite(EXPANDS_AMP_EN, LOW); + io.pinMode(EXPANDS_LORA_EN, OUTPUT); + io.digitalWrite(EXPANDS_LORA_EN, HIGH); + io.pinMode(EXPANDS_GPS_EN, OUTPUT); + io.digitalWrite(EXPANDS_GPS_EN, HIGH); + io.pinMode(EXPANDS_KB_EN, OUTPUT); + io.digitalWrite(EXPANDS_KB_EN, HIGH); + io.pinMode(EXPANDS_SD_EN, OUTPUT); + io.digitalWrite(EXPANDS_SD_EN, HIGH); + io.pinMode(EXPANDS_GPIO_EN, OUTPUT); + io.digitalWrite(EXPANDS_GPIO_EN, HIGH); + io.pinMode(EXPANDS_SD_PULLEN, INPUT); +} \ No newline at end of file diff --git a/variants/nrf52840/ELECROW-ThinkNode-M1/variant.cpp b/variants/nrf52840/ELECROW-ThinkNode-M1/variant.cpp index cae079b74..2ce84bc57 100644 --- a/variants/nrf52840/ELECROW-ThinkNode-M1/variant.cpp +++ b/variants/nrf52840/ELECROW-ThinkNode-M1/variant.cpp @@ -42,3 +42,21 @@ void initVariant() pinMode(PIN_LED3, OUTPUT); ledOff(PIN_LED3); } + +void variant_shutdown() +{ + for (int pin = 0; pin < 48; pin++) { + if (pin == SX126X_BUSY || pin == PIN_SPI_SCK || pin == SX126X_DIO1 || pin == PIN_SPI_MOSI || pin == PIN_SPI_MISO || + pin == SX126X_CS || pin == SX126X_RESET || pin == PIN_NFC1 || pin == PIN_NFC2 || pin == PIN_BUTTON1 || + pin == PIN_BUTTON2) { + continue; + } + pinMode(pin, OUTPUT); + digitalWrite(pin, LOW); + if (pin >= 32) { + NRF_P1->DIRCLR = (1 << (pin - 32)); + } else { + NRF_GPIO->DIRCLR = (1 << pin); + } + } +} \ No newline at end of file diff --git a/variants/nrf52840/heltec_mesh_node_t114-inkhud/variant.cpp b/variants/nrf52840/heltec_mesh_node_t114-inkhud/variant.cpp index 85c9f4a72..b8b0e21c5 100644 --- a/variants/nrf52840/heltec_mesh_node_t114-inkhud/variant.cpp +++ b/variants/nrf52840/heltec_mesh_node_t114-inkhud/variant.cpp @@ -19,6 +19,7 @@ */ #include "variant.h" +#include "Arduino.h" #include "nrf.h" #include "wiring_constants.h" #include "wiring_digital.h" @@ -36,3 +37,10 @@ void initVariant() pinMode(PIN_LED1, OUTPUT); ledOff(PIN_LED1); } + +void variant_shutdown() +{ + nrf_gpio_cfg_default(PIN_GPS_PPS); + detachInterrupt(PIN_GPS_PPS); + detachInterrupt(PIN_BUTTON1); +} \ No newline at end of file diff --git a/variants/nrf52840/heltec_mesh_node_t114/variant.cpp b/variants/nrf52840/heltec_mesh_node_t114/variant.cpp index 85c9f4a72..b8b0e21c5 100644 --- a/variants/nrf52840/heltec_mesh_node_t114/variant.cpp +++ b/variants/nrf52840/heltec_mesh_node_t114/variant.cpp @@ -19,6 +19,7 @@ */ #include "variant.h" +#include "Arduino.h" #include "nrf.h" #include "wiring_constants.h" #include "wiring_digital.h" @@ -36,3 +37,10 @@ void initVariant() pinMode(PIN_LED1, OUTPUT); ledOff(PIN_LED1); } + +void variant_shutdown() +{ + nrf_gpio_cfg_default(PIN_GPS_PPS); + detachInterrupt(PIN_GPS_PPS); + detachInterrupt(PIN_BUTTON1); +} \ No newline at end of file diff --git a/variants/nrf52840/heltec_mesh_node_t114/variant.h b/variants/nrf52840/heltec_mesh_node_t114/variant.h index fb7f61ac7..cf14b5e04 100644 --- a/variants/nrf52840/heltec_mesh_node_t114/variant.h +++ b/variants/nrf52840/heltec_mesh_node_t114/variant.h @@ -156,7 +156,7 @@ No longer populated on PCB // The bluetooth transmit power on the nRF52840 is adjustable from -20dB to +8dB in steps of 4dB // so NRF52_BLE_TX_POWER can be set to -20, -16, -12, -8, -4, 0 (default), 4, and 8. -//#define NRF52_BLE_TX_POWER 8 +// #define NRF52_BLE_TX_POWER 8 /* * GPS pins diff --git a/variants/nrf52840/heltec_mesh_solar/variant.cpp b/variants/nrf52840/heltec_mesh_solar/variant.cpp index c13f006d7..3b2612da8 100644 --- a/variants/nrf52840/heltec_mesh_solar/variant.cpp +++ b/variants/nrf52840/heltec_mesh_solar/variant.cpp @@ -19,6 +19,7 @@ */ #include "variant.h" +#include "Arduino.h" #include "nrf.h" #include "wiring_constants.h" #include "wiring_digital.h" @@ -38,3 +39,10 @@ void initVariant() digitalWrite(PIN_SCREEN_VDD_CTL, LOW); // Start with power on #endif } + +void variant_shutdown() +{ + nrf_gpio_cfg_default(PIN_GPS_PPS); + detachInterrupt(PIN_GPS_PPS); + detachInterrupt(PIN_BUTTON1); +} \ No newline at end of file diff --git a/variants/nrf52840/meshlink/variant.cpp b/variants/nrf52840/meshlink/variant.cpp index 81a5097c4..f35bbd1af 100644 --- a/variants/nrf52840/meshlink/variant.cpp +++ b/variants/nrf52840/meshlink/variant.cpp @@ -20,4 +20,11 @@ void initVariant() pinMode(PIN_WD_EN, OUTPUT); digitalWrite(PIN_WD_EN, HIGH); // Enable the Watchdog at boot #endif +} + +void variant_shutdown() +{ +#ifdef PIN_WD_EN + digitalWrite(PIN_WD_EN, LOW); +#endif } \ No newline at end of file diff --git a/variants/nrf52840/r1-neo/variant.cpp b/variants/nrf52840/r1-neo/variant.cpp index f87c041aa..d87b88c85 100644 --- a/variants/nrf52840/r1-neo/variant.cpp +++ b/variants/nrf52840/r1-neo/variant.cpp @@ -43,3 +43,11 @@ void initVariant() // pinMode(PIN_3V3_EN, OUTPUT); // digitalWrite(PIN_3V3_EN, HIGH); } + +void earlyInitVariant() +{ + pinMode(DCDC_EN_HOLD, OUTPUT); + digitalWrite(DCDC_EN_HOLD, HIGH); + pinMode(NRF_ON, OUTPUT); + digitalWrite(NRF_ON, HIGH); +} diff --git a/variants/nrf52840/rak_wismeshtap/variant.cpp b/variants/nrf52840/rak_wismeshtap/variant.cpp index 5a3587982..36572b074 100644 --- a/variants/nrf52840/rak_wismeshtap/variant.cpp +++ b/variants/nrf52840/rak_wismeshtap/variant.cpp @@ -42,4 +42,21 @@ void initVariant() // 3V3 Power Rail pinMode(PIN_3V3_EN, OUTPUT); digitalWrite(PIN_3V3_EN, HIGH); +} + +void variant_shutdown() +{ + // GPIO restores input status, otherwise there will be leakage current + nrf_gpio_cfg_default(TFT_BL); + nrf_gpio_cfg_default(TFT_DC); + nrf_gpio_cfg_default(TFT_CS); + nrf_gpio_cfg_default(TFT_SCLK); + nrf_gpio_cfg_default(TFT_MOSI); + nrf_gpio_cfg_default(TFT_MISO); + nrf_gpio_cfg_default(SCREEN_TOUCH_INT); + nrf_gpio_cfg_default(WB_I2C1_SCL); + nrf_gpio_cfg_default(WB_I2C1_SDA); + + // nrf_gpio_cfg_default(WB_I2C2_SCL); + // nrf_gpio_cfg_default(WB_I2C2_SDA); } \ No newline at end of file diff --git a/variants/nrf52840/t-echo/variant.cpp b/variants/nrf52840/t-echo/variant.cpp index cae079b74..cb64530f6 100644 --- a/variants/nrf52840/t-echo/variant.cpp +++ b/variants/nrf52840/t-echo/variant.cpp @@ -42,3 +42,13 @@ void initVariant() pinMode(PIN_LED3, OUTPUT); ledOff(PIN_LED3); } + +void variant_shutdown() +{ + // To power off the T-Echo, the display must be set + // as an input pin; otherwise, there will be leakage current. + pinMode(PIN_EINK_CS, INPUT); + pinMode(PIN_EINK_DC, INPUT); + pinMode(PIN_EINK_RES, INPUT); + pinMode(PIN_EINK_BUSY, INPUT); +} \ No newline at end of file From 3d58c6e91676f36ee518a3f99d6b825a146091e4 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Mon, 26 Jan 2026 14:28:05 -0600 Subject: [PATCH 40/99] Trackball revamp (#9440) * Trackball revamp * Use Throttle * Volatile! --- src/input/TrackballInterruptBase.cpp | 82 ++++++++++++++++++++++----- src/input/TrackballInterruptBase.h | 14 ++++- src/input/TrackballInterruptImpl1.cpp | 35 ++++-------- variants/esp32s3/t-deck/variant.h | 1 + 4 files changed, 91 insertions(+), 41 deletions(-) diff --git a/src/input/TrackballInterruptBase.cpp b/src/input/TrackballInterruptBase.cpp index bce62caaf..1bbe75629 100644 --- a/src/input/TrackballInterruptBase.cpp +++ b/src/input/TrackballInterruptBase.cpp @@ -1,5 +1,7 @@ #include "TrackballInterruptBase.h" +#include "Throttle.h" #include "configuration.h" + extern bool osk_found; TrackballInterruptBase::TrackballInterruptBase(const char *name) : concurrency::OSThread(name), _originName(name) {} @@ -55,6 +57,20 @@ int32_t TrackballInterruptBase::runOnce() { InputEvent e = {}; e.inputEvent = INPUT_BROKER_NONE; +#if TB_THRESHOLD + if (lastInterruptTime && !Throttle::isWithinTimespanMs(lastInterruptTime, 1000)) { + left_counter = 0; + right_counter = 0; + up_counter = 0; + down_counter = 0; + lastInterruptTime = 0; + } +#ifdef INPUT_DEBUG + if (left_counter > 0 || right_counter > 0 || up_counter > 0 || down_counter > 0) { + LOG_DEBUG("L %u R %u U %u D %u, time %u", left_counter, right_counter, up_counter, down_counter, millis()); + } +#endif +#endif // Handle long press detection for press button if (pressDetected && pressStartTime > 0) { @@ -130,23 +146,31 @@ int32_t TrackballInterruptBase::runOnce() } } -#if defined(T_DECK) // T-deck gets a super-simple debounce on trackball +#if TB_THRESHOLD if (this->action == TB_ACTION_PRESSED && (!pressDetected || pressStartTime == 0)) { // Start long press detection pressDetected = true; pressStartTime = millis(); // Don't send event yet, wait to see if it's a long press - } else if (this->action == TB_ACTION_UP && lastEvent == TB_ACTION_UP) { - // LOG_DEBUG("Trackball event UP"); + } else if (up_counter >= TB_THRESHOLD) { +#ifdef INPUT_DEBUG + LOG_DEBUG("Trackball event UP %u", millis()); +#endif e.inputEvent = this->_eventUp; - } else if (this->action == TB_ACTION_DOWN && lastEvent == TB_ACTION_DOWN) { - // LOG_DEBUG("Trackball event DOWN"); + } else if (down_counter >= TB_THRESHOLD) { +#ifdef INPUT_DEBUG + LOG_DEBUG("Trackball event DOWN %u", millis()); +#endif e.inputEvent = this->_eventDown; - } else if (this->action == TB_ACTION_LEFT && lastEvent == TB_ACTION_LEFT) { - // LOG_DEBUG("Trackball event LEFT"); + } else if (left_counter >= TB_THRESHOLD) { +#ifdef INPUT_DEBUG + LOG_DEBUG("Trackball event LEFT %u", millis()); +#endif e.inputEvent = this->_eventLeft; - } else if (this->action == TB_ACTION_RIGHT && lastEvent == TB_ACTION_RIGHT) { - // LOG_DEBUG("Trackball event RIGHT"); + } else if (right_counter >= TB_THRESHOLD) { +#ifdef INPUT_DEBUG + LOG_DEBUG("Trackball event RIGHT %u", millis()); +#endif e.inputEvent = this->_eventRight; } #else @@ -179,6 +203,12 @@ int32_t TrackballInterruptBase::runOnce() e.source = this->_originName; e.kbchar = 0x00; this->notifyObservers(&e); +#if TB_THRESHOLD + left_counter = 0; + right_counter = 0; + up_counter = 0; + down_counter = 0; +#endif } // Only update lastEvent for non-press actions or completed press actions @@ -194,25 +224,49 @@ int32_t TrackballInterruptBase::runOnce() void TrackballInterruptBase::intPressHandler() { - this->action = TB_ACTION_PRESSED; + if (!Throttle::isWithinTimespanMs(lastInterruptTime, 10)) + this->action = TB_ACTION_PRESSED; + lastInterruptTime = millis(); } void TrackballInterruptBase::intDownHandler() { - this->action = TB_ACTION_DOWN; + if (TB_THRESHOLD || !Throttle::isWithinTimespanMs(lastInterruptTime, 10)) + this->action = TB_ACTION_DOWN; + lastInterruptTime = millis(); + +#if TB_THRESHOLD + down_counter++; +#endif } void TrackballInterruptBase::intUpHandler() { - this->action = TB_ACTION_UP; + if (TB_THRESHOLD || !Throttle::isWithinTimespanMs(lastInterruptTime, 10)) + this->action = TB_ACTION_UP; + lastInterruptTime = millis(); + +#if TB_THRESHOLD + up_counter++; +#endif } void TrackballInterruptBase::intLeftHandler() { - this->action = TB_ACTION_LEFT; + if (TB_THRESHOLD || !Throttle::isWithinTimespanMs(lastInterruptTime, 10)) + this->action = TB_ACTION_LEFT; + lastInterruptTime = millis(); +#if TB_THRESHOLD + left_counter++; +#endif } void TrackballInterruptBase::intRightHandler() { - this->action = TB_ACTION_RIGHT; + if (TB_THRESHOLD || !Throttle::isWithinTimespanMs(lastInterruptTime, 10)) + this->action = TB_ACTION_RIGHT; + lastInterruptTime = millis(); +#if TB_THRESHOLD + right_counter++; +#endif } diff --git a/src/input/TrackballInterruptBase.h b/src/input/TrackballInterruptBase.h index 67d4ee449..908f62769 100644 --- a/src/input/TrackballInterruptBase.h +++ b/src/input/TrackballInterruptBase.h @@ -12,6 +12,10 @@ #endif #endif +#ifndef TB_THRESHOLD +#define TB_THRESHOLD 0 +#endif + class TrackballInterruptBase : public Observable, public concurrency::OSThread { public: @@ -25,8 +29,6 @@ class TrackballInterruptBase : public Observable, public con void intUpHandler(); void intLeftHandler(); void intRightHandler(); - uint32_t lastTime = 0; - virtual int32_t runOnce() override; protected: @@ -67,4 +69,12 @@ class TrackballInterruptBase : public Observable, public con input_broker_event _eventPressedLong = INPUT_BROKER_NONE; const char *_originName; TrackballInterruptBaseActionType lastEvent = TB_ACTION_NONE; + volatile uint32_t lastInterruptTime = 0; + +#if TB_THRESHOLD + volatile uint8_t left_counter = 0; + volatile uint8_t right_counter = 0; + volatile uint8_t up_counter = 0; + volatile uint8_t down_counter = 0; +#endif }; diff --git a/src/input/TrackballInterruptImpl1.cpp b/src/input/TrackballInterruptImpl1.cpp index 594facdeb..fd126913a 100644 --- a/src/input/TrackballInterruptImpl1.cpp +++ b/src/input/TrackballInterruptImpl1.cpp @@ -24,41 +24,26 @@ void TrackballInterruptImpl1::init(uint8_t pinDown, uint8_t pinUp, uint8_t pinLe void TrackballInterruptImpl1::handleIntDown() { - if (TB_DIRECTION == RISING || millis() > trackballInterruptImpl1->lastTime + 10) { - trackballInterruptImpl1->lastTime = millis(); - trackballInterruptImpl1->intDownHandler(); - trackballInterruptImpl1->setIntervalFromNow(20); - } + trackballInterruptImpl1->intDownHandler(); + trackballInterruptImpl1->setIntervalFromNow(20); } void TrackballInterruptImpl1::handleIntUp() { - if (TB_DIRECTION == RISING || millis() > trackballInterruptImpl1->lastTime + 10) { - trackballInterruptImpl1->lastTime = millis(); - trackballInterruptImpl1->intUpHandler(); - trackballInterruptImpl1->setIntervalFromNow(20); - } + trackballInterruptImpl1->intUpHandler(); + trackballInterruptImpl1->setIntervalFromNow(20); } void TrackballInterruptImpl1::handleIntLeft() { - if (TB_DIRECTION == RISING || millis() > trackballInterruptImpl1->lastTime + 10) { - trackballInterruptImpl1->lastTime = millis(); - trackballInterruptImpl1->intLeftHandler(); - trackballInterruptImpl1->setIntervalFromNow(20); - } + trackballInterruptImpl1->intLeftHandler(); + trackballInterruptImpl1->setIntervalFromNow(20); } void TrackballInterruptImpl1::handleIntRight() { - if (TB_DIRECTION == RISING || millis() > trackballInterruptImpl1->lastTime + 10) { - trackballInterruptImpl1->lastTime = millis(); - trackballInterruptImpl1->intRightHandler(); - trackballInterruptImpl1->setIntervalFromNow(20); - } + trackballInterruptImpl1->intRightHandler(); + trackballInterruptImpl1->setIntervalFromNow(20); } void TrackballInterruptImpl1::handleIntPressed() { - if (TB_DIRECTION == RISING || millis() > trackballInterruptImpl1->lastTime + 10) { - trackballInterruptImpl1->lastTime = millis(); - trackballInterruptImpl1->intPressHandler(); - trackballInterruptImpl1->setIntervalFromNow(20); - } + trackballInterruptImpl1->intPressHandler(); + trackballInterruptImpl1->setIntervalFromNow(20); } diff --git a/variants/esp32s3/t-deck/variant.h b/variants/esp32s3/t-deck/variant.h index 8d2996131..ab5b74870 100644 --- a/variants/esp32s3/t-deck/variant.h +++ b/variants/esp32s3/t-deck/variant.h @@ -71,6 +71,7 @@ #define TB_RIGHT 2 #define TB_PRESS 0 // BUTTON_PIN #define TB_DIRECTION FALLING +#define TB_THRESHOLD 3 // microphone #define ES7210_SCK 47 From 7efc3e37708cdaa0762baec12bd178b7760fa938 Mon Sep 17 00:00:00 2001 From: Keane <2617725+k3an3@users.noreply.github.com> Date: Mon, 26 Jan 2026 15:32:03 -0500 Subject: [PATCH 41/99] Replace strcpy with strncpy and null termination (#9436) Co-authored-by: Jonathan Bennett --- src/mqtt/MQTT.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp index 4c2c0fe1b..18a4f913e 100644 --- a/src/mqtt/MQTT.cpp +++ b/src/mqtt/MQTT.cpp @@ -475,8 +475,10 @@ bool MQTT::publish(const char *topic, const char *payload, bool retained) if (moduleConfig.mqtt.proxy_to_client_enabled) { meshtastic_MqttClientProxyMessage *msg = mqttClientProxyMessagePool.allocZeroed(); msg->which_payload_variant = meshtastic_MqttClientProxyMessage_text_tag; - strcpy(msg->topic, topic); - strcpy(msg->payload_variant.text, payload); + strncpy(msg->topic, topic, sizeof(msg->topic)); + msg->topic[sizeof(msg->topic) - 1] = '\0'; + strncpy(msg->payload_variant.text, payload, sizeof(msg->payload_variant.text)); + msg->payload_variant.text[sizeof(msg->payload_variant.text) - 1] = '\0'; msg->retained = retained; service->sendMqttMessageToClientProxy(msg); return true; From a2e8e232f11ee7660a5e92c8fd0d6c72439369d7 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Mon, 26 Jan 2026 16:58:16 -0600 Subject: [PATCH 42/99] Remove the unused OCV_ARRAYs and move one to a variant.h (#9442) --- src/power.h | 12 ------------ variants/esp32/chatter2/variant.h | 2 +- 2 files changed, 1 insertion(+), 13 deletions(-) diff --git a/src/power.h b/src/power.h index 5f887c36b..e4b456d3b 100644 --- a/src/power.h +++ b/src/power.h @@ -15,20 +15,8 @@ // Device specific curves go in variant.h #ifndef OCV_ARRAY -#ifdef CELL_TYPE_LIFEPO4 -#define OCV_ARRAY 3400, 3350, 3320, 3290, 3270, 3260, 3250, 3230, 3200, 3120, 3000 -#elif defined(CELL_TYPE_LEADACID) -#define OCV_ARRAY 2120, 2090, 2070, 2050, 2030, 2010, 1990, 1980, 1970, 1960, 1950 -#elif defined(CELL_TYPE_ALKALINE) -#define OCV_ARRAY 1580, 1400, 1350, 1300, 1280, 1250, 1230, 1190, 1150, 1100, 1000 -#elif defined(CELL_TYPE_NIMH) -#define OCV_ARRAY 1400, 1300, 1280, 1270, 1260, 1250, 1240, 1230, 1210, 1150, 1000 -#elif defined(CELL_TYPE_LTO) -#define OCV_ARRAY 2700, 2560, 2540, 2520, 2500, 2460, 2420, 2400, 2380, 2320, 1500 -#else // LiIon #define OCV_ARRAY 4190, 4050, 3990, 3890, 3800, 3720, 3630, 3530, 3420, 3300, 3100 #endif -#endif /*Note: 12V lead acid is 6 cells, most board accept only 1 cell LiIon/LiPo*/ #ifndef NUM_CELLS diff --git a/variants/esp32/chatter2/variant.h b/variants/esp32/chatter2/variant.h index 0c1ef6967..abcb1ce4d 100644 --- a/variants/esp32/chatter2/variant.h +++ b/variants/esp32/chatter2/variant.h @@ -79,7 +79,7 @@ // lower dB for lower voltage rnage #define ADC_MULTIPLIER 5.0 // VBATT---10k--pin34---2.5K---GND // Chatter2 uses 3 AAA cells -#define CELL_TYPE_ALKALINE +#define OCV_ARRAY 1580, 1400, 1350, 1300, 1280, 1250, 1230, 1190, 1150, 1100, 1000 #define NUM_CELLS 3 #undef EXT_PWR_DETECT From 63a97a54e110bd1d22f39812229becba0555da68 Mon Sep 17 00:00:00 2001 From: Colby Dillion Date: Mon, 26 Jan 2026 18:45:24 -0600 Subject: [PATCH 43/99] Fix retry_delay calculation for error responses (#9443) Co-authored-by: Ben Meadors --- src/modules/StoreForwardModule.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/StoreForwardModule.cpp b/src/modules/StoreForwardModule.cpp index b8a710bf5..023a1c798 100644 --- a/src/modules/StoreForwardModule.cpp +++ b/src/modules/StoreForwardModule.cpp @@ -513,7 +513,7 @@ bool StoreForwardModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, LOG_DEBUG("StoreAndForward_RequestResponse_ROUTER_BUSY"); // retry in messages_saved * packetTimeMax ms retry_delay = millis() + getNumAvailablePackets(this->busyTo, this->last_time) * packetTimeMax * - (meshtastic_StoreAndForward_RequestResponse_ROUTER_ERROR ? 2 : 1); + (p->rr == meshtastic_StoreAndForward_RequestResponse_ROUTER_ERROR ? 2 : 1); } break; From 90778a4e788b70442a705f43dd92916b9b028248 Mon Sep 17 00:00:00 2001 From: Andrew Yong Date: Tue, 27 Jan 2026 20:03:31 +0800 Subject: [PATCH 44/99] feat(GPS): Support Softsleep with WAKE-UP pin on PA1010D (#9078) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Support softsleep by defining PIN_GPS_STANDBY on CDTop CD-PA1010D. This differs from existing MTK GPS e.g. L76K, where pulling PIN_GPS_STANDBY (WAKE-UP pin) low is not sufficient to put the GPS module in standby. An additional `$PMTK225,4*2F` must be sent to enter "Backup Mode", which is exited by bringing PIN_GPS_STANDBY (WAKE-UP pin) high. Refer to datasheet[0] §1.9.3 "Backup Mode". 0: https://cdn-learn.adafruit.com/assets/assets/000/084/295/original/CD_PA1010D_Datasheet_v.03.pdf Signed-off-by: Andrew Yong --- src/gps/GPS.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index fd121861c..13e5c32d1 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -905,6 +905,12 @@ void GPS::writePinStandby(bool standby) // Write and log pinMode(PIN_GPS_STANDBY, OUTPUT); digitalWrite(PIN_GPS_STANDBY, val); + + // Enter backup mode on PA1010D; TODO: may be applicable to other MTK GPS too + if (IS_ONE_OF(gnssModel, GNSS_MODEL_MTK_PA1010D)) { + _serial_gps->write("$PMTK225,4*2F\r\n"); + } + #ifdef GPS_DEBUG LOG_DEBUG("Pin STANDBY %s", val == HIGH ? "HI" : "LOW"); #endif From c8079d4115abd5e4a3e2902e95e7de9224e0280c Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Tue, 27 Jan 2026 08:05:36 -0600 Subject: [PATCH 45/99] Metadata for heltec tracker v2 --- .../esp32s3/heltec_wireless_tracker_v2/platformio.ini | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/variants/esp32s3/heltec_wireless_tracker_v2/platformio.ini b/variants/esp32s3/heltec_wireless_tracker_v2/platformio.ini index 8bdb71541..0b486618b 100644 --- a/variants/esp32s3/heltec_wireless_tracker_v2/platformio.ini +++ b/variants/esp32s3/heltec_wireless_tracker_v2/platformio.ini @@ -1,8 +1,17 @@ [env:heltec-wireless-tracker-v2] +custom_meshtastic_support_level = 1 +custom_meshtastic_images = heltec_wireless_tracker_v2.svg +custom_meshtastic_tags = Heltec + extends = esp32s3_base board = heltec_wireless_tracker_v2 board_build.partitions = default_8MB.csv upload_protocol = esptool +custom_meshtastic_hw_model = 113 +custom_meshtastic_hw_model_slug = HELTEC_WIRELESS_TRACKER_V2 +custom_meshtastic_architecture = esp32s3 +custom_meshtastic_display_name = Heltec Wireless Tracker V2 +custom_meshtastic_actively_supported = true build_flags = ${esp32s3_base.build_flags} From d54ae5dad8c84eab3b42e2b98d2c15d3c7788256 Mon Sep 17 00:00:00 2001 From: HarukiToreda <116696711+HarukiToreda@users.noreply.github.com> Date: Tue, 27 Jan 2026 10:11:11 -0500 Subject: [PATCH 46/99] InkHUD Menu improvements (#8975) * InkHUD: Region Picker on initial setup * Added Node Config menu with Lora Region Picker * Role picker * Preset Picker * Timezone picker added * Power save mode and bluetooth configs * Config section Headers * Channel Config * Cleaning some behavior * Add back to all Options * Display config added * Position Toggle added * Network Config for ESP32 * Wifi details * Reduce line spacing to fit more content * Recent list with checkboxes * Timezone labels easier to understand * Trunk fix * Added "Saving Changes" screen when reboot is needed * Trunk fix * Make Tips show after first boot if the region is Unset * Added ResetDB and keep only favorite commands * quick fix to joystick * Trunk Fix * Fix to tips to work with new joystick input * Added ADC multiplier value display on power config * added ADC calibration feature * Fixed missing stray endiff * GPS toggle now is aware if gps is present. --- .../InkHUD/Applets/System/Logo/LogoApplet.cpp | 12 + .../InkHUD/Applets/System/Logo/LogoApplet.h | 1 + .../InkHUD/Applets/System/Menu/MenuAction.h | 79 ++ .../InkHUD/Applets/System/Menu/MenuApplet.cpp | 1127 ++++++++++++++++- .../InkHUD/Applets/System/Menu/MenuApplet.h | 12 +- .../InkHUD/Applets/System/Menu/MenuItem.h | 7 + .../InkHUD/Applets/System/Menu/MenuPage.h | 19 +- .../InkHUD/Applets/System/Tips/TipsApplet.cpp | 192 ++- .../InkHUD/Applets/System/Tips/TipsApplet.h | 1 + src/graphics/niche/InkHUD/Events.cpp | 9 + src/graphics/niche/InkHUD/Events.h | 13 +- src/graphics/niche/InkHUD/InkHUD.cpp | 7 + src/graphics/niche/InkHUD/InkHUD.h | 4 + src/graphics/niche/InkHUD/SystemApplet.h | 1 + 14 files changed, 1344 insertions(+), 140 deletions(-) diff --git a/src/graphics/niche/InkHUD/Applets/System/Logo/LogoApplet.cpp b/src/graphics/niche/InkHUD/Applets/System/Logo/LogoApplet.cpp index ecaa7cea3..4b55529bb 100644 --- a/src/graphics/niche/InkHUD/Applets/System/Logo/LogoApplet.cpp +++ b/src/graphics/niche/InkHUD/Applets/System/Logo/LogoApplet.cpp @@ -155,6 +155,18 @@ void InkHUD::LogoApplet::onShutdown() // This is then drawn by InkHUD::Events::onShutdown, with a blocking FULL update, after InkHUD's flash write is complete } +void InkHUD::LogoApplet::onApplyingChanges() +{ + bringToForeground(); + + textLeft = ""; + textRight = ""; + textTitle = "Applying changes"; + fontTitle = fontSmall; + + inkhud->forceUpdate(Drivers::EInk::FAST, false); +} + void InkHUD::LogoApplet::onReboot() { bringToForeground(); diff --git a/src/graphics/niche/InkHUD/Applets/System/Logo/LogoApplet.h b/src/graphics/niche/InkHUD/Applets/System/Logo/LogoApplet.h index 3f604baed..37f940453 100644 --- a/src/graphics/niche/InkHUD/Applets/System/Logo/LogoApplet.h +++ b/src/graphics/niche/InkHUD/Applets/System/Logo/LogoApplet.h @@ -26,6 +26,7 @@ class LogoApplet : public SystemApplet, public concurrency::OSThread void onBackground() override; void onShutdown() override; void onReboot() override; + void onApplyingChanges(); protected: int32_t runOnce() override; diff --git a/src/graphics/niche/InkHUD/Applets/System/Menu/MenuAction.h b/src/graphics/niche/InkHUD/Applets/System/Menu/MenuAction.h index debe2b719..74ad5c85f 100644 --- a/src/graphics/niche/InkHUD/Applets/System/Menu/MenuAction.h +++ b/src/graphics/niche/InkHUD/Applets/System/Menu/MenuAction.h @@ -22,6 +22,7 @@ enum MenuAction { STORE_CANNEDMESSAGE_SELECTION, SEND_CANNEDMESSAGE, SHUTDOWN, + BACK, NEXT_TILE, TOGGLE_BACKLIGHT, TOGGLE_GPS, @@ -36,6 +37,84 @@ enum MenuAction { TOGGLE_NOTIFICATIONS, TOGGLE_INVERT_COLOR, TOGGLE_12H_CLOCK, + // Regions + SET_REGION_US, + SET_REGION_EU_868, + SET_REGION_EU_433, + SET_REGION_CN, + SET_REGION_JP, + SET_REGION_ANZ, + SET_REGION_KR, + SET_REGION_TW, + SET_REGION_RU, + SET_REGION_IN, + SET_REGION_NZ_865, + SET_REGION_TH, + SET_REGION_LORA_24, + SET_REGION_UA_433, + SET_REGION_UA_868, + SET_REGION_MY_433, + SET_REGION_MY_919, + SET_REGION_SG_923, + SET_REGION_PH_433, + SET_REGION_PH_868, + SET_REGION_PH_915, + SET_REGION_ANZ_433, + SET_REGION_KZ_433, + SET_REGION_KZ_863, + SET_REGION_NP_865, + SET_REGION_BR_902, + // Device Roles + SET_ROLE_CLIENT, + SET_ROLE_CLIENT_MUTE, + SET_ROLE_ROUTER, + SET_ROLE_REPEATER, + // Presets + SET_PRESET_LONG_SLOW, + SET_PRESET_LONG_MODERATE, + SET_PRESET_LONG_FAST, + SET_PRESET_MEDIUM_SLOW, + SET_PRESET_MEDIUM_FAST, + SET_PRESET_SHORT_SLOW, + SET_PRESET_SHORT_FAST, + SET_PRESET_SHORT_TURBO, + // Timezones + SET_TZ_US_HAWAII, + SET_TZ_US_ALASKA, + SET_TZ_US_PACIFIC, + SET_TZ_US_ARIZONA, + SET_TZ_US_MOUNTAIN, + SET_TZ_US_CENTRAL, + SET_TZ_US_EASTERN, + SET_TZ_BR_BRAZILIA, + SET_TZ_UTC, + SET_TZ_EU_WESTERN, + SET_TZ_EU_CENTRAL, + SET_TZ_EU_EASTERN, + SET_TZ_ASIA_KOLKATA, + SET_TZ_ASIA_HONG_KONG, + SET_TZ_AU_AWST, + SET_TZ_AU_ACST, + SET_TZ_AU_AEST, + SET_TZ_PACIFIC_NZ, + // Power + TOGGLE_POWER_SAVE, + CALIBRATE_ADC, + // Bluetooth + TOGGLE_BLUETOOTH, + TOGGLE_BLUETOOTH_PAIR_MODE, + // Channel + TOGGLE_CHANNEL_UPLINK, + TOGGLE_CHANNEL_DOWNLINK, + TOGGLE_CHANNEL_POSITION, + SET_CHANNEL_PRECISION, + // Display + TOGGLE_DISPLAY_UNITS, + // Network + TOGGLE_WIFI, + // Administration + RESET_NODEDB_ALL, + RESET_NODEDB_KEEP_FAVORITES, }; } // namespace NicheGraphics::InkHUD diff --git a/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.cpp b/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.cpp index 7e7093857..93d2c6b83 100644 --- a/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.cpp +++ b/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.cpp @@ -2,16 +2,21 @@ #include "./MenuApplet.h" -#include "RTC.h" - +#include "DisplayFormatters.h" +#include "GPS.h" #include "MeshService.h" +#include "RTC.h" #include "Router.h" #include "airtime.h" #include "main.h" +#include "mesh/generated/meshtastic/deviceonly.pb.h" #include "power.h" - -#if !MESHTASTIC_EXCLUDE_GPS -#include "GPS.h" +#include +#include +#if defined(ARCH_ESP32) && HAS_WIFI +#include "mesh/wifi/WiFiAPClient.h" +#include +#include #endif using namespace NicheGraphics; @@ -22,6 +27,18 @@ static constexpr uint8_t MENU_TIMEOUT_SEC = 60; // How many seconds before menu // These are offered to users as possible values for settings.recentlyActiveSeconds static constexpr uint8_t RECENTS_OPTIONS_MINUTES[] = {2, 5, 10, 30, 60, 120}; +struct PositionPrecisionOption { + uint8_t value; // proto value + const char *metric; + const char *imperial; +}; + +static constexpr PositionPrecisionOption POSITION_PRECISION_OPTIONS[] = { + {32, "Precise", "Precise"}, {19, "50 m", "150 ft"}, {18, "90 m", "300 ft"}, {17, "200 m", "600 ft"}, + {16, "350 m", "0.2 mi"}, {15, "700 m", "0.5 mi"}, {14, "1.5 km", "0.9 mi"}, {13, "2.9 km", "1.8 mi"}, + {12, "5.8 km", "3.6 mi"}, {11, "12 km", "7.3 mi"}, {10, "23 km", "15 mi"}, +}; + InkHUD::MenuApplet::MenuApplet() : concurrency::OSThread("MenuApplet") { // No timer tasks at boot @@ -45,8 +62,15 @@ void InkHUD::MenuApplet::onForeground() // We do need this before we render, but we can optimize by just calculating it once now systemInfoPanelHeight = getSystemInfoPanelHeight(); - // Display initial menu page - showPage(MenuPage::ROOT); + // Force Region page ONLY when explicitly requested (one-shot) + if (inkhud->forceRegionMenu) { + + inkhud->forceRegionMenu = false; // consume one-shot flag + showPage(MenuPage::REGION); + + } else { + showPage(MenuPage::ROOT); + } // If device has a backlight which isn't controlled by aux button: // backlight on always when menu opens. @@ -139,6 +163,150 @@ int32_t InkHUD::MenuApplet::runOnce() return OSThread::disable(); } +static void applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode region) +{ + if (config.lora.region == region) + return; + + config.lora.region = region; + + auto changes = SEGMENT_CONFIG; + +#if !(MESHTASTIC_EXCLUDE_PKI_KEYGEN || MESHTASTIC_EXCLUDE_PKI) + if (!owner.is_licensed) { + bool keygenSuccess = false; + + if (config.security.private_key.size == 32) { + if (crypto->regeneratePublicKey(config.security.public_key.bytes, config.security.private_key.bytes)) { + keygenSuccess = true; + } + } else { + crypto->generateKeyPair(config.security.public_key.bytes, config.security.private_key.bytes); + keygenSuccess = true; + } + + if (keygenSuccess) { + config.security.public_key.size = 32; + config.security.private_key.size = 32; + owner.public_key.size = 32; + memcpy(owner.public_key.bytes, config.security.public_key.bytes, 32); + } + } +#endif + + config.lora.tx_enabled = true; + + initRegion(); + + if (myRegion && myRegion->dutyCycle < 100) { + config.lora.ignore_mqtt = true; + } + + if (strncmp(moduleConfig.mqtt.root, default_mqtt_root, strlen(default_mqtt_root)) == 0) { + sprintf(moduleConfig.mqtt.root, "%s/%s", default_mqtt_root, myRegion->name); + changes |= SEGMENT_MODULECONFIG; + } + // Notify UI that changes are being applied + InkHUD::InkHUD::getInstance()->notifyApplyingChanges(); + service->reloadConfig(changes); + + rebootAtMsec = millis() + DEFAULT_REBOOT_SECONDS * 1000; +} + +static void applyDeviceRole(meshtastic_Config_DeviceConfig_Role role) +{ + if (config.device.role == role) + return; + + config.device.role = role; + + nodeDB->saveToDisk(SEGMENT_CONFIG); + + service->reloadConfig(SEGMENT_CONFIG); + + // Notify UI that changes are being applied + InkHUD::InkHUD::getInstance()->notifyApplyingChanges(); + + rebootAtMsec = millis() + DEFAULT_REBOOT_SECONDS * 1000; +} + +static void applyLoRaPreset(meshtastic_Config_LoRaConfig_ModemPreset preset) +{ + if (config.lora.modem_preset == preset) + return; + + config.lora.use_preset = true; + config.lora.modem_preset = preset; + + nodeDB->saveToDisk(SEGMENT_CONFIG); + service->reloadConfig(SEGMENT_CONFIG); + + // Notify UI that changes are being applied + InkHUD::InkHUD::getInstance()->notifyApplyingChanges(); + + rebootAtMsec = millis() + DEFAULT_REBOOT_SECONDS * 1000; +} + +static const char *getTimezoneLabelFromValue(const char *tzdef) +{ + if (!tzdef || !*tzdef) + return "Unset"; + + // Must match TIMEZONE menu entries + if (strcmp(tzdef, "HST10") == 0) + return "US/Hawaii"; + if (strcmp(tzdef, "AKST9AKDT,M3.2.0,M11.1.0") == 0) + return "US/Alaska"; + if (strcmp(tzdef, "PST8PDT,M3.2.0,M11.1.0") == 0) + return "US/Pacific"; + if (strcmp(tzdef, "MST7") == 0) + return "US/Arizona"; + if (strcmp(tzdef, "MST7MDT,M3.2.0,M11.1.0") == 0) + return "US/Mountain"; + if (strcmp(tzdef, "CST6CDT,M3.2.0,M11.1.0") == 0) + return "US/Central"; + if (strcmp(tzdef, "EST5EDT,M3.2.0,M11.1.0") == 0) + return "US/Eastern"; + if (strcmp(tzdef, "BRT3") == 0) + return "BR/Brasilia"; + if (strcmp(tzdef, "UTC0") == 0) + return "UTC"; + if (strcmp(tzdef, "GMT0BST,M3.5.0/1,M10.5.0") == 0) + return "EU/Western"; + if (strcmp(tzdef, "CET-1CEST,M3.5.0,M10.5.0/3") == 0) + return "EU/Central"; + if (strcmp(tzdef, "EET-2EEST,M3.5.0/3,M10.5.0/4") == 0) + return "EU/Eastern"; + if (strcmp(tzdef, "IST-5:30") == 0) + return "Asia/Kolkata"; + if (strcmp(tzdef, "HKT-8") == 0) + return "Asia/Hong Kong"; + if (strcmp(tzdef, "AWST-8") == 0) + return "AU/AWST"; + if (strcmp(tzdef, "ACST-9:30ACDT,M10.1.0,M4.1.0/3") == 0) + return "AU/ACST"; + if (strcmp(tzdef, "AEST-10AEDT,M10.1.0,M4.1.0/3") == 0) + return "AU/AEST"; + if (strcmp(tzdef, "NZST-12NZDT,M9.5.0,M4.1.0/3") == 0) + return "Pacific/NZ"; + + return tzdef; // fallback for unknown/custom values +} + +static void applyTimezone(const char *tz) +{ + if (!tz || strcmp(config.device.tzdef, tz) == 0) + return; + + strncpy(config.device.tzdef, tz, sizeof(config.device.tzdef)); + config.device.tzdef[sizeof(config.device.tzdef) - 1] = '\0'; + + setenv("TZ", config.device.tzdef, 1); + + nodeDB->saveToDisk(SEGMENT_CONFIG); + service->reloadConfig(SEGMENT_CONFIG); +} + // Perform action for a menu item, then change page // Behaviors for MenuActions are defined here void InkHUD::MenuApplet::execute(MenuItem item) @@ -150,10 +318,22 @@ void InkHUD::MenuApplet::execute(MenuItem item) // Open a submenu without performing any action // Also handles exit case NO_ACTION: + if (currentPage == MenuPage::NODE_CONFIG_CHANNELS && item.nextPage == MenuPage::NODE_CONFIG_CHANNEL_DETAIL) { + + // cursor - 1 because index 0 is "Back" + selectedChannelIndex = cursor - 1; + } break; + case BACK: + showPage(item.nextPage); + return; + case NEXT_TILE: inkhud->nextTile(); + // Unselect menu item after tile change + cursorShown = false; + cursor = 0; break; case SEND_PING: @@ -196,17 +376,23 @@ void InkHUD::MenuApplet::execute(MenuItem item) break; case TOGGLE_APPLET: - settings->userApplets.active[cursor] = !settings->userApplets.active[cursor]; - inkhud->updateAppletSelection(); + if (item.checkState) { + *item.checkState = !(*item.checkState); + inkhud->updateAppletSelection(); + } break; case TOGGLE_AUTOSHOW_APPLET: // Toggle settings.userApplets.autoshow[] value, via MenuItem::checkState pointer set in populateAutoshowPage() - *items.at(cursor).checkState = !(*items.at(cursor).checkState); + if (item.checkState) { + *item.checkState = !(*item.checkState); + } break; case TOGGLE_NOTIFICATIONS: - settings->optionalFeatures.notifications = !settings->optionalFeatures.notifications; + if (item.checkState) { + *item.checkState = !(*item.checkState); + } break; case TOGGLE_INVERT_COLOR: @@ -218,12 +404,14 @@ void InkHUD::MenuApplet::execute(MenuItem item) nodeDB->saveToDisk(SEGMENT_CONFIG); break; - case SET_RECENTS: - // Set value of settings.recentlyActiveSeconds - // Uses menu cursor to read RECENTS_OPTIONS_MINUTES array (defined at top of this file) - assert(cursor < sizeof(RECENTS_OPTIONS_MINUTES) / sizeof(RECENTS_OPTIONS_MINUTES[0])); - settings->recentlyActiveSeconds = RECENTS_OPTIONS_MINUTES[cursor] * 60; // Menu items are in minutes + case SET_RECENTS: { + // cursor - 1 because index 0 is "Back" + const uint8_t index = cursor - 1; + constexpr uint8_t optionCount = sizeof(RECENTS_OPTIONS_MINUTES) / sizeof(RECENTS_OPTIONS_MINUTES[0]); + assert(index < optionCount); + settings->recentlyActiveSeconds = RECENTS_OPTIONS_MINUTES[index] * 60; break; + } case SHUTDOWN: LOG_INFO("Shutting down from menu"); @@ -251,8 +439,18 @@ void InkHUD::MenuApplet::execute(MenuItem item) break; case TOGGLE_GPS: - gps->toggleGpsMode(); +#if !MESHTASTIC_EXCLUDE_GPS && HAS_GPS + if (config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_DISABLED) { + config.position.gps_mode = meshtastic_Config_PositionConfig_GpsMode_ENABLED; + } else if (config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_ENABLED) { + config.position.gps_mode = meshtastic_Config_PositionConfig_GpsMode_DISABLED; + } else { + // NOT_PRESENT do nothing + break; + } nodeDB->saveToDisk(SEGMENT_CONFIG); + service->reloadConfig(SEGMENT_CONFIG); +#endif break; case ENABLE_BLUETOOTH: @@ -260,10 +458,397 @@ void InkHUD::MenuApplet::execute(MenuItem item) LOG_INFO("Enabling Bluetooth"); config.network.wifi_enabled = false; config.bluetooth.enabled = true; - nodeDB->saveToDisk(); + nodeDB->saveToDisk(SEGMENT_CONFIG); + InkHUD::InkHUD::getInstance()->notifyApplyingChanges(); rebootAtMsec = millis() + 2000; break; + // Power / Network (ESP32-only) +#if defined(ARCH_ESP32) + case TOGGLE_POWER_SAVE: + config.power.is_power_saving = !config.power.is_power_saving; + nodeDB->saveToDisk(SEGMENT_CONFIG); + InkHUD::InkHUD::getInstance()->notifyApplyingChanges(); + rebootAtMsec = millis() + DEFAULT_REBOOT_SECONDS * 1000; + break; + + case TOGGLE_WIFI: + config.network.wifi_enabled = !config.network.wifi_enabled; + + if (config.network.wifi_enabled) { + // Switch behavior: WiFi ON forces Bluetooth OFF + config.bluetooth.enabled = false; + } + + nodeDB->saveToDisk(SEGMENT_CONFIG); + InkHUD::InkHUD::getInstance()->notifyApplyingChanges(); + rebootAtMsec = millis() + DEFAULT_REBOOT_SECONDS * 1000; + break; +#endif + // ADC Calibration + case CALIBRATE_ADC: { + // Read current measured voltage + float measuredV = powerStatus->getBatteryVoltageMv() / 1000.0f; + + // Sanity check + if (measuredV < 3.0f || measuredV > 4.5f) { + LOG_WARN("ADC calibration aborted, unreasonable voltage: %.2fV", measuredV); + break; + } + + // Determine the base multiplier currently in effect + float baseMult = 0.0f; + + if (config.power.adc_multiplier_override > 0.0f) { + baseMult = config.power.adc_multiplier_override; + } +#ifdef ADC_MULTIPLIER + else { + baseMult = ADC_MULTIPLIER; + } +#endif + + if (baseMult <= 0.0f) { + LOG_WARN("ADC calibration failed: no base multiplier"); + break; + } + + // Target voltage considered 100% by UI + constexpr float TARGET_VOLTAGE = 4.19f; + + // Calculate new multiplier + float newMult = baseMult * (TARGET_VOLTAGE / measuredV); + + config.power.adc_multiplier_override = newMult; + + nodeDB->saveToDisk(SEGMENT_CONFIG); + + LOG_INFO("ADC calibrated: measured=%.3fV base=%.4f new=%.4f", measuredV, baseMult, newMult); + + break; + } + + // Display + case TOGGLE_DISPLAY_UNITS: + if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) + config.display.units = meshtastic_Config_DisplayConfig_DisplayUnits_METRIC; + else + config.display.units = meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL; + + nodeDB->saveToDisk(SEGMENT_CONFIG); + break; + + // Bluetooth + case TOGGLE_BLUETOOTH: + config.bluetooth.enabled = !config.bluetooth.enabled; + + if (config.bluetooth.enabled) { + // Switch behavior: Bluetooth ON forces WiFi OFF + config.network.wifi_enabled = false; + } + + nodeDB->saveToDisk(SEGMENT_CONFIG); + InkHUD::InkHUD::getInstance()->notifyApplyingChanges(); + rebootAtMsec = millis() + DEFAULT_REBOOT_SECONDS * 1000; + break; + + case TOGGLE_BLUETOOTH_PAIR_MODE: + config.bluetooth.fixed_pin = !config.bluetooth.fixed_pin; + nodeDB->saveToDisk(SEGMENT_CONFIG); + break; + + // Regions + case SET_REGION_US: + applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_US); + break; + + case SET_REGION_EU_868: + applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_EU_868); + break; + + case SET_REGION_EU_433: + applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_EU_433); + break; + + case SET_REGION_CN: + applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_CN); + break; + + case SET_REGION_JP: + applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_JP); + break; + + case SET_REGION_ANZ: + applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_ANZ); + break; + case SET_REGION_KR: + applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_KR); + break; + + case SET_REGION_TW: + applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_TW); + break; + + case SET_REGION_RU: + applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_RU); + break; + + case SET_REGION_IN: + applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_IN); + break; + + case SET_REGION_NZ_865: + applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_NZ_865); + break; + + case SET_REGION_TH: + applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_TH); + break; + + case SET_REGION_LORA_24: + applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_LORA_24); + break; + + case SET_REGION_UA_433: + applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_UA_433); + break; + + case SET_REGION_UA_868: + applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_UA_868); + break; + + case SET_REGION_MY_433: + applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_MY_433); + break; + + case SET_REGION_MY_919: + applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_MY_919); + break; + + case SET_REGION_SG_923: + applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_SG_923); + break; + + case SET_REGION_PH_433: + applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_PH_433); + break; + + case SET_REGION_PH_868: + applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_PH_868); + break; + + case SET_REGION_PH_915: + applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_PH_915); + break; + + case SET_REGION_ANZ_433: + applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_ANZ_433); + break; + + case SET_REGION_KZ_433: + applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_KZ_433); + break; + + case SET_REGION_KZ_863: + applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_KZ_863); + break; + + case SET_REGION_NP_865: + applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_NP_865); + break; + + case SET_REGION_BR_902: + applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_BR_902); + break; + + // Roles + case SET_ROLE_CLIENT: + applyDeviceRole(meshtastic_Config_DeviceConfig_Role_CLIENT); + break; + + case SET_ROLE_CLIENT_MUTE: + applyDeviceRole(meshtastic_Config_DeviceConfig_Role_CLIENT_MUTE); + break; + + case SET_ROLE_ROUTER: + applyDeviceRole(meshtastic_Config_DeviceConfig_Role_ROUTER); + break; + + case SET_ROLE_REPEATER: + applyDeviceRole(meshtastic_Config_DeviceConfig_Role_REPEATER); + break; + + // Presets + case SET_PRESET_LONG_SLOW: + applyLoRaPreset(meshtastic_Config_LoRaConfig_ModemPreset_LONG_SLOW); + break; + + case SET_PRESET_LONG_MODERATE: + applyLoRaPreset(meshtastic_Config_LoRaConfig_ModemPreset_LONG_MODERATE); + break; + + case SET_PRESET_LONG_FAST: + applyLoRaPreset(meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST); + break; + + case SET_PRESET_MEDIUM_SLOW: + applyLoRaPreset(meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_SLOW); + break; + + case SET_PRESET_MEDIUM_FAST: + applyLoRaPreset(meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_FAST); + break; + + case SET_PRESET_SHORT_SLOW: + applyLoRaPreset(meshtastic_Config_LoRaConfig_ModemPreset_SHORT_SLOW); + break; + + case SET_PRESET_SHORT_FAST: + applyLoRaPreset(meshtastic_Config_LoRaConfig_ModemPreset_SHORT_FAST); + break; + + case SET_PRESET_SHORT_TURBO: + applyLoRaPreset(meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO); + break; + + // Timezones + case SET_TZ_US_HAWAII: + applyTimezone("HST10"); + break; + + case SET_TZ_US_ALASKA: + applyTimezone("AKST9AKDT,M3.2.0,M11.1.0"); + break; + + case SET_TZ_US_PACIFIC: + applyTimezone("PST8PDT,M3.2.0,M11.1.0"); + break; + + case SET_TZ_US_ARIZONA: + applyTimezone("MST7"); + break; + + case SET_TZ_US_MOUNTAIN: + applyTimezone("MST7MDT,M3.2.0,M11.1.0"); + break; + + case SET_TZ_US_CENTRAL: + applyTimezone("CST6CDT,M3.2.0,M11.1.0"); + break; + + case SET_TZ_US_EASTERN: + applyTimezone("EST5EDT,M3.2.0,M11.1.0"); + break; + + case SET_TZ_BR_BRAZILIA: + applyTimezone("BRT3"); + break; + + case SET_TZ_UTC: + applyTimezone("UTC0"); + break; + + case SET_TZ_EU_WESTERN: + applyTimezone("GMT0BST,M3.5.0/1,M10.5.0"); + break; + + case SET_TZ_EU_CENTRAL: + applyTimezone("CET-1CEST,M3.5.0,M10.5.0/3"); + break; + + case SET_TZ_EU_EASTERN: + applyTimezone("EET-2EEST,M3.5.0/3,M10.5.0/4"); + break; + + case SET_TZ_ASIA_KOLKATA: + applyTimezone("IST-5:30"); + break; + + case SET_TZ_ASIA_HONG_KONG: + applyTimezone("HKT-8"); + break; + + case SET_TZ_AU_AWST: + applyTimezone("AWST-8"); + break; + + case SET_TZ_AU_ACST: + applyTimezone("ACST-9:30ACDT,M10.1.0,M4.1.0/3"); + break; + + case SET_TZ_AU_AEST: + applyTimezone("AEST-10AEDT,M10.1.0,M4.1.0/3"); + break; + + case SET_TZ_PACIFIC_NZ: + applyTimezone("NZST-12NZDT,M9.5.0,M4.1.0/3"); + break; + + // Channels + case TOGGLE_CHANNEL_UPLINK: { + auto &ch = channels.getByIndex(selectedChannelIndex); + ch.settings.uplink_enabled = !ch.settings.uplink_enabled; + nodeDB->saveToDisk(SEGMENT_CHANNELS); + service->reloadConfig(SEGMENT_CHANNELS); + break; + } + + case TOGGLE_CHANNEL_DOWNLINK: { + auto &ch = channels.getByIndex(selectedChannelIndex); + ch.settings.downlink_enabled = !ch.settings.downlink_enabled; + nodeDB->saveToDisk(SEGMENT_CHANNELS); + service->reloadConfig(SEGMENT_CHANNELS); + break; + } + + case TOGGLE_CHANNEL_POSITION: { + auto &ch = channels.getByIndex(selectedChannelIndex); + + if (!ch.settings.has_module_settings) + ch.settings.has_module_settings = true; + + if (ch.settings.module_settings.position_precision > 0) + ch.settings.module_settings.position_precision = 0; + else + ch.settings.module_settings.position_precision = 13; // default + + nodeDB->saveToDisk(SEGMENT_CHANNELS); + service->reloadConfig(SEGMENT_CHANNELS); + break; + } + + case SET_CHANNEL_PRECISION: { + auto &ch = channels.getByIndex(selectedChannelIndex); + + if (!ch.settings.has_module_settings) + ch.settings.has_module_settings = true; + + // Cursor - 1 because of "Back" + uint8_t index = cursor - 1; + + constexpr uint8_t optionCount = sizeof(POSITION_PRECISION_OPTIONS) / sizeof(POSITION_PRECISION_OPTIONS[0]); + + if (index < optionCount) { + ch.settings.module_settings.position_precision = POSITION_PRECISION_OPTIONS[index].value; + } + + nodeDB->saveToDisk(SEGMENT_CHANNELS); + service->reloadConfig(SEGMENT_CHANNELS); + break; + } + + case RESET_NODEDB_ALL: + InkHUD::getInstance()->notifyApplyingChanges(); + nodeDB->resetNodes(); + rebootAtMsec = millis() + DEFAULT_REBOOT_SECONDS * 1000; + break; + + case RESET_NODEDB_KEEP_FAVORITES: + InkHUD::getInstance()->notifyApplyingChanges(); + nodeDB->resetNodes(1); + rebootAtMsec = millis() + DEFAULT_REBOOT_SECONDS * 1000; + break; + default: LOG_WARN("Action not implemented"); } @@ -279,6 +864,7 @@ void InkHUD::MenuApplet::showPage(MenuPage page) { items.clear(); items.shrink_to_fit(); + nodeConfigLabels.clear(); switch (page) { case ROOT: @@ -289,6 +875,7 @@ void InkHUD::MenuApplet::showPage(MenuPage page) items.push_back(MenuItem("Send", MenuPage::SEND)); items.push_back(MenuItem("Options", MenuPage::OPTIONS)); // items.push_back(MenuItem("Display Off", MenuPage::EXIT)); // TODO + items.push_back(MenuItem("Node Config", MenuPage::NODE_CONFIG)); items.push_back(MenuItem("Save & Shut Down", MenuAction::SHUTDOWN)); items.push_back(MenuItem("Exit", MenuPage::EXIT)); previousPage = MenuPage::EXIT; @@ -305,6 +892,7 @@ void InkHUD::MenuApplet::showPage(MenuPage page) break; case OPTIONS: + items.push_back(MenuItem("Back", MenuAction::BACK, MenuPage::ROOT)); // Optional: backlight if (settings->optionalMenuItems.backlight) items.push_back(MenuItem(backlight->isLatched() ? "Backlight Off" : "Keep Backlight On", // Label @@ -312,16 +900,7 @@ void InkHUD::MenuApplet::showPage(MenuPage page) MenuPage::EXIT // Exit once complete )); - // Optional: GPS - if (config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_DISABLED) - items.push_back(MenuItem("Enable GPS", MenuAction::TOGGLE_GPS, MenuPage::EXIT)); - if (config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_ENABLED) - items.push_back(MenuItem("Disable GPS", MenuAction::TOGGLE_GPS, MenuPage::EXIT)); - - // Optional: Enable Bluetooth, in case of lost wifi connection - if (!config.bluetooth.enabled || config.network.wifi_enabled) - items.push_back(MenuItem("Enable Bluetooth", MenuAction::ENABLE_BLUETOOTH, MenuPage::EXIT)); - + // Options Toggles items.push_back(MenuItem("Applets", MenuPage::APPLETS)); items.push_back(MenuItem("Auto-show", MenuPage::AUTOSHOW)); items.push_back(MenuItem("Recents Duration", MenuPage::RECENTS)); @@ -334,33 +913,423 @@ void InkHUD::MenuApplet::showPage(MenuPage page) &settings->optionalFeatures.notifications)); items.push_back(MenuItem("Battery Icon", MenuAction::TOGGLE_BATTERY_ICON, MenuPage::OPTIONS, &settings->optionalFeatures.batteryIcon)); - invertedColors = (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_INVERTED); items.push_back(MenuItem("Invert Color", MenuAction::TOGGLE_INVERT_COLOR, MenuPage::OPTIONS, &invertedColors)); - - items.push_back( - MenuItem("12-Hour Clock", MenuAction::TOGGLE_12H_CLOCK, MenuPage::OPTIONS, &config.display.use_12h_clock)); items.push_back(MenuItem("Exit", MenuPage::EXIT)); previousPage = MenuPage::ROOT; break; case APPLETS: - populateAppletPage(); + populateAppletPage(); // must be first + items.insert(items.begin(), MenuItem("Back", MenuAction::BACK, MenuPage::OPTIONS)); items.push_back(MenuItem("Exit", MenuPage::EXIT)); previousPage = MenuPage::OPTIONS; break; case AUTOSHOW: - populateAutoshowPage(); + populateAutoshowPage(); // must be first + items.insert(items.begin(), MenuItem("Back", MenuAction::BACK, MenuPage::OPTIONS)); items.push_back(MenuItem("Exit", MenuPage::EXIT)); previousPage = MenuPage::OPTIONS; break; case RECENTS: - populateRecentsPage(); - previousPage = MenuPage::OPTIONS; + populateRecentsPage(); // builds only the options + items.insert(items.begin(), MenuItem("Back", MenuAction::BACK, MenuPage::OPTIONS)); + items.push_back(MenuItem("Exit", MenuPage::EXIT)); break; + case NODE_CONFIG: + items.push_back(MenuItem("Back", MenuAction::BACK, MenuPage::ROOT)); + // Radio Config Section + items.push_back(MenuItem::Header("Radio Config")); + items.push_back(MenuItem("LoRa", MenuPage::NODE_CONFIG_LORA)); + items.push_back(MenuItem("Channel", MenuPage::NODE_CONFIG_CHANNELS)); + // Device Config Section + items.push_back(MenuItem::Header("Device Config")); + items.push_back(MenuItem("Device", MenuPage::NODE_CONFIG_DEVICE)); + items.push_back(MenuItem("Position", MenuPage::NODE_CONFIG_POSITION)); + items.push_back(MenuItem("Power", MenuPage::NODE_CONFIG_POWER)); +#if defined(ARCH_ESP32) + items.push_back(MenuItem("Network", MenuPage::NODE_CONFIG_NETWORK)); +#endif + items.push_back(MenuItem("Display", MenuPage::NODE_CONFIG_DISPLAY)); + items.push_back(MenuItem("Bluetooth", MenuPage::NODE_CONFIG_BLUETOOTH)); + + // Administration Section + items.push_back(MenuItem::Header("Administration")); + items.push_back(MenuItem("Reset NodeDB", MenuPage::NODE_CONFIG_ADMIN_RESET)); + + // Exit + items.push_back(MenuItem("Exit", MenuPage::EXIT)); + break; + + case NODE_CONFIG_DEVICE: { + + items.push_back(MenuItem("Back", MenuAction::BACK, MenuPage::NODE_CONFIG)); + + const char *role = DisplayFormatters::getDeviceRole(config.device.role); + nodeConfigLabels.emplace_back("Role: " + std::string(role)); + items.push_back(MenuItem(nodeConfigLabels.back().c_str(), MenuAction::NO_ACTION, MenuPage::NODE_CONFIG_DEVICE_ROLE)); + + const char *tzLabel = getTimezoneLabelFromValue(config.device.tzdef); + nodeConfigLabels.emplace_back("Timezone: " + std::string(tzLabel)); + items.push_back(MenuItem(nodeConfigLabels.back().c_str(), MenuAction::NO_ACTION, MenuPage::TIMEZONE)); + + items.push_back(MenuItem("Exit", MenuPage::EXIT)); + break; + } + + case NODE_CONFIG_POSITION: { + items.push_back(MenuItem("Back", MenuAction::BACK, MenuPage::NODE_CONFIG)); +#if !MESHTASTIC_EXCLUDE_GPS && HAS_GPS + const auto mode = config.position.gps_mode; + if (mode == meshtastic_Config_PositionConfig_GpsMode_NOT_PRESENT) { + items.push_back(MenuItem("GPS None", MenuAction::NO_ACTION, MenuPage::NODE_CONFIG_POSITION)); + } else { + gpsEnabled = (mode == meshtastic_Config_PositionConfig_GpsMode_ENABLED); + items.push_back(MenuItem("GPS", MenuAction::TOGGLE_GPS, MenuPage::NODE_CONFIG_POSITION, &gpsEnabled)); + } +#endif + items.push_back(MenuItem("Exit", MenuPage::EXIT)); + break; + } + + case NODE_CONFIG_POWER: { + items.push_back(MenuItem("Back", MenuAction::BACK, MenuPage::NODE_CONFIG)); +#if defined(ARCH_ESP32) + items.push_back(MenuItem("Powersave", MenuAction::TOGGLE_POWER_SAVE, MenuPage::EXIT, &config.power.is_power_saving)); +#endif + // ADC Multiplier + float effectiveMult = 0.0f; + + // User override always shows if it exists + if (config.power.adc_multiplier_override > 0.0f) { + effectiveMult = config.power.adc_multiplier_override; + } +#ifdef ADC_MULTIPLIER + else { + // Fallback to variant defined + effectiveMult = ADC_MULTIPLIER; + } +#endif + + // Only show if we actually have a value + if (effectiveMult > 0.0f) { + char buf[32]; + snprintf(buf, sizeof(buf), "ADC Mult: %.3f", effectiveMult); + nodeConfigLabels.emplace_back(buf); + + items.push_back( + MenuItem(nodeConfigLabels.back().c_str(), MenuAction::NO_ACTION, MenuPage::NODE_CONFIG_POWER_ADC_CAL)); + } + + items.push_back(MenuItem("Exit", MenuPage::EXIT)); + break; + } + + case NODE_CONFIG_POWER_ADC_CAL: { + items.push_back(MenuItem("Back", MenuAction::BACK, MenuPage::NODE_CONFIG_POWER)); + + // Instruction text (header-style, non-selectable) + items.push_back(MenuItem::Header("Run on full charge Only")); + + // Action + items.push_back(MenuItem("Calibrate ADC", MenuAction::CALIBRATE_ADC, MenuPage::NODE_CONFIG_POWER)); + + items.push_back(MenuItem("Exit", MenuPage::EXIT)); + break; + } + + case NODE_CONFIG_NETWORK: { + items.push_back(MenuItem("Back", MenuAction::BACK, MenuPage::NODE_CONFIG)); + + const char *wifiLabel = config.network.wifi_enabled ? "WiFi: On" : "WiFi: Off"; + + items.push_back(MenuItem(wifiLabel, MenuAction::TOGGLE_WIFI, MenuPage::EXIT)); + +#if HAS_WIFI && defined(ARCH_ESP32) + if (config.network.wifi_enabled) { + + // Status + if (WiFi.status() == WL_CONNECTED) { + nodeConfigLabels.emplace_back("Status: Connected"); + } else { + nodeConfigLabels.emplace_back("Status: Not Connected"); + } + items.push_back(MenuItem(nodeConfigLabels.back().c_str(), MenuAction::NO_ACTION, MenuPage::NODE_CONFIG_NETWORK)); + + // Signal + if (WiFi.status() == WL_CONNECTED) { + int rssi = WiFi.RSSI(); + int quality = constrain(2 * (rssi + 100), 0, 100); + + char sigBuf[32]; + snprintf(sigBuf, sizeof(sigBuf), "Signal: %d%%", quality); + nodeConfigLabels.emplace_back(sigBuf); + items.push_back(MenuItem(nodeConfigLabels.back().c_str(), MenuAction::NO_ACTION, MenuPage::NODE_CONFIG_NETWORK)); + + char ipBuf[64]; + snprintf(ipBuf, sizeof(ipBuf), "IP: %s", WiFi.localIP().toString().c_str()); + nodeConfigLabels.emplace_back(ipBuf); + items.push_back(MenuItem(nodeConfigLabels.back().c_str(), MenuAction::NO_ACTION, MenuPage::NODE_CONFIG_NETWORK)); + } + + // SSID + if (config.network.wifi_ssid && strlen(config.network.wifi_ssid) > 0) { + std::string ssidLabel = "SSID: "; + ssidLabel += config.network.wifi_ssid; + nodeConfigLabels.emplace_back(ssidLabel); + items.push_back(MenuItem(nodeConfigLabels.back().c_str(), MenuAction::NO_ACTION, MenuPage::NODE_CONFIG_NETWORK)); + } + + // Hostname + const char *host = WiFi.getHostname(); + if (host && strlen(host) > 0) { + std::string hostLabel = "Host: "; + hostLabel += host; + nodeConfigLabels.emplace_back(hostLabel); + items.push_back(MenuItem(nodeConfigLabels.back().c_str(), MenuAction::NO_ACTION, MenuPage::NODE_CONFIG_NETWORK)); + } + } +#endif + + items.push_back(MenuItem("Exit", MenuPage::EXIT)); + break; + } + + case NODE_CONFIG_DISPLAY: { + items.push_back(MenuItem("Back", MenuAction::BACK, MenuPage::NODE_CONFIG)); + + items.push_back(MenuItem("12-Hour Clock", MenuAction::TOGGLE_12H_CLOCK, MenuPage::NODE_CONFIG_DISPLAY, + &config.display.use_12h_clock)); + + const char *unitsLabel = + (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) ? "Units: Imperial" : "Units: Metric"; + + items.push_back(MenuItem(unitsLabel, MenuAction::TOGGLE_DISPLAY_UNITS, MenuPage::NODE_CONFIG_DISPLAY)); + + items.push_back(MenuItem("Exit", MenuPage::EXIT)); + break; + } + + case NODE_CONFIG_BLUETOOTH: { + items.push_back(MenuItem("Back", MenuAction::BACK, MenuPage::NODE_CONFIG)); + + const char *btLabel = config.bluetooth.enabled ? "Bluetooth: On" : "Bluetooth: Off"; + items.push_back(MenuItem(btLabel, MenuAction::TOGGLE_BLUETOOTH, MenuPage::EXIT)); + + const char *pairLabel = config.bluetooth.fixed_pin ? "Pair Mode: Fixed" : "Pair Mode: Random"; + items.push_back(MenuItem(pairLabel, MenuAction::TOGGLE_BLUETOOTH_PAIR_MODE, MenuPage::NODE_CONFIG_BLUETOOTH)); + + items.push_back(MenuItem("Exit", MenuPage::EXIT)); + break; + } + + case NODE_CONFIG_LORA: { + + items.push_back(MenuItem("Back", MenuAction::BACK, MenuPage::NODE_CONFIG)); + + const char *region = myRegion ? myRegion->name : "Unset"; + nodeConfigLabels.emplace_back("Region: " + std::string(region)); + items.push_back(MenuItem(nodeConfigLabels.back().c_str(), MenuAction::NO_ACTION, MenuPage::REGION)); + + const char *preset = + DisplayFormatters::getModemPresetDisplayName(config.lora.modem_preset, false, config.lora.use_preset); + nodeConfigLabels.emplace_back("Preset: " + std::string(preset)); + items.push_back(MenuItem(nodeConfigLabels.back().c_str(), MenuAction::NO_ACTION, MenuPage::NODE_CONFIG_PRESET)); + + char freqBuf[32]; + float freq = RadioLibInterface::instance->getFreq(); + snprintf(freqBuf, sizeof(freqBuf), "Freq: %.3f MHz", freq); + nodeConfigLabels.emplace_back(freqBuf); + items.push_back(MenuItem(nodeConfigLabels.back().c_str(), MenuAction::NO_ACTION, MenuPage::NODE_CONFIG_LORA)); + + items.push_back(MenuItem("Exit", MenuPage::EXIT)); + break; + } + + case NODE_CONFIG_CHANNELS: { + items.push_back(MenuItem("Back", MenuAction::BACK, MenuPage::NODE_CONFIG)); + + for (uint8_t i = 0; i < MAX_NUM_CHANNELS; i++) { + meshtastic_Channel &ch = channels.getByIndex(i); + + if (!ch.has_settings) + continue; + + if (ch.role == meshtastic_Channel_Role_DISABLED) + continue; + + std::string label = "#"; + + if (ch.role == meshtastic_Channel_Role_PRIMARY) { + label += "Primary"; + } else if (strlen(ch.settings.name) > 0) { + label += parse(ch.settings.name); + } else { + label += "Channel" + to_string(i + 1); + } + + nodeConfigLabels.push_back(label); + items.push_back( + MenuItem(nodeConfigLabels.back().c_str(), MenuAction::NO_ACTION, MenuPage::NODE_CONFIG_CHANNEL_DETAIL)); + } + + items.push_back(MenuItem("Exit", MenuPage::EXIT)); + break; + } + + case NODE_CONFIG_CHANNEL_DETAIL: { + items.push_back(MenuItem("Back", MenuAction::BACK, MenuPage::NODE_CONFIG_CHANNELS)); + + meshtastic_Channel &ch = channels.getByIndex(selectedChannelIndex); + + // Name (read-only) + const char *name = strlen(ch.settings.name) > 0 ? ch.settings.name : "Unnamed"; + nodeConfigLabels.emplace_back("Ch: " + parse(name)); + items.push_back(MenuItem(nodeConfigLabels.back().c_str(), MenuAction::NO_ACTION, MenuPage::NODE_CONFIG_CHANNEL_DETAIL)); + + // Uplink + items.push_back(MenuItem("Uplink", MenuAction::TOGGLE_CHANNEL_UPLINK, MenuPage::NODE_CONFIG_CHANNEL_DETAIL, + &ch.settings.uplink_enabled)); + + items.push_back(MenuItem("Downlink", MenuAction::TOGGLE_CHANNEL_DOWNLINK, MenuPage::NODE_CONFIG_CHANNEL_DETAIL, + &ch.settings.downlink_enabled)); + + // Position + channelPositionEnabled = ch.settings.has_module_settings && ch.settings.module_settings.position_precision > 0; + + items.push_back(MenuItem("Position", MenuAction::TOGGLE_CHANNEL_POSITION, MenuPage::NODE_CONFIG_CHANNEL_DETAIL, + &channelPositionEnabled)); + + // Precision + if (channelPositionEnabled) { + + std::string precisionLabel = "Unknown"; + + for (const auto &opt : POSITION_PRECISION_OPTIONS) { + if (opt.value == ch.settings.module_settings.position_precision) { + precisionLabel = (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) + ? opt.imperial + : opt.metric; + break; + } + } + nodeConfigLabels.emplace_back("Precision: " + precisionLabel); + items.push_back( + MenuItem(nodeConfigLabels.back().c_str(), MenuAction::NO_ACTION, MenuPage::NODE_CONFIG_CHANNEL_PRECISION)); + } + + items.push_back(MenuItem("Exit", MenuPage::EXIT)); + break; + } + + case NODE_CONFIG_CHANNEL_PRECISION: { + items.push_back(MenuItem("Back", MenuAction::BACK, MenuPage::NODE_CONFIG_CHANNEL_DETAIL)); + meshtastic_Channel &ch = channels.getByIndex(selectedChannelIndex); + if (!ch.settings.has_module_settings || ch.settings.module_settings.position_precision == 0) { + items.push_back(MenuItem("Position is Off", MenuPage::NODE_CONFIG_CHANNEL_DETAIL)); + break; + } + constexpr uint8_t optionCount = sizeof(POSITION_PRECISION_OPTIONS) / sizeof(POSITION_PRECISION_OPTIONS[0]); + for (uint8_t i = 0; i < optionCount; i++) { + const auto &opt = POSITION_PRECISION_OPTIONS[i]; + const char *label = + (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) ? opt.imperial : opt.metric; + nodeConfigLabels.emplace_back(label); + + items.push_back(MenuItem(nodeConfigLabels.back().c_str(), MenuAction::SET_CHANNEL_PRECISION, + MenuPage::NODE_CONFIG_CHANNEL_DETAIL)); + } + items.push_back(MenuItem("Exit", MenuPage::EXIT)); + break; + } + + case NODE_CONFIG_DEVICE_ROLE: { + items.push_back(MenuItem("Back", MenuAction::BACK, MenuPage::NODE_CONFIG_DEVICE)); + items.push_back(MenuItem("Client", MenuAction::SET_ROLE_CLIENT, MenuPage::EXIT)); + items.push_back(MenuItem("Client Mute", MenuAction::SET_ROLE_CLIENT_MUTE, MenuPage::EXIT)); + items.push_back(MenuItem("Router", MenuAction::SET_ROLE_ROUTER, MenuPage::EXIT)); + items.push_back(MenuItem("Repeater", MenuAction::SET_ROLE_REPEATER, MenuPage::EXIT)); + items.push_back(MenuItem("Exit", MenuPage::EXIT)); + break; + } + + case TIMEZONE: + items.push_back(MenuItem("Back", MenuAction::BACK, MenuPage::NODE_CONFIG_DEVICE)); + items.push_back(MenuItem("US/Hawaii", SET_TZ_US_HAWAII, MenuPage::NODE_CONFIG_DEVICE)); + items.push_back(MenuItem("US/Alaska", SET_TZ_US_ALASKA, MenuPage::NODE_CONFIG_DEVICE)); + items.push_back(MenuItem("US/Pacific", SET_TZ_US_PACIFIC, MenuPage::NODE_CONFIG_DEVICE)); + items.push_back(MenuItem("US/Arizona", SET_TZ_US_ARIZONA, MenuPage::NODE_CONFIG_DEVICE)); + items.push_back(MenuItem("US/Mountain", SET_TZ_US_MOUNTAIN, MenuPage::NODE_CONFIG_DEVICE)); + items.push_back(MenuItem("US/Central", SET_TZ_US_CENTRAL, MenuPage::NODE_CONFIG_DEVICE)); + items.push_back(MenuItem("US/Eastern", SET_TZ_US_EASTERN, MenuPage::NODE_CONFIG_DEVICE)); + items.push_back(MenuItem("BR/Brasilia", SET_TZ_BR_BRAZILIA, MenuPage::NODE_CONFIG_DEVICE)); + items.push_back(MenuItem("UTC", SET_TZ_UTC, MenuPage::NODE_CONFIG_DEVICE)); + items.push_back(MenuItem("EU/Western", SET_TZ_EU_WESTERN, MenuPage::NODE_CONFIG_DEVICE)); + items.push_back(MenuItem("EU/Central", SET_TZ_EU_CENTRAL, MenuPage::NODE_CONFIG_DEVICE)); + items.push_back(MenuItem("EU/Eastern", SET_TZ_EU_EASTERN, MenuPage::NODE_CONFIG_DEVICE)); + items.push_back(MenuItem("Asia/Kolkata", SET_TZ_ASIA_KOLKATA, MenuPage::NODE_CONFIG_DEVICE)); + items.push_back(MenuItem("Asia/Hong Kong", SET_TZ_ASIA_HONG_KONG, MenuPage::NODE_CONFIG_DEVICE)); + items.push_back(MenuItem("AU/AWST", SET_TZ_AU_AWST, MenuPage::NODE_CONFIG_DEVICE)); + items.push_back(MenuItem("AU/ACST", SET_TZ_AU_ACST, MenuPage::NODE_CONFIG_DEVICE)); + items.push_back(MenuItem("AU/AEST", SET_TZ_AU_AEST, MenuPage::NODE_CONFIG_DEVICE)); + items.push_back(MenuItem("Exit", MenuPage::EXIT)); + break; + + case REGION: + items.push_back(MenuItem("Back", MenuAction::BACK, MenuPage::NODE_CONFIG_LORA)); + items.push_back(MenuItem("US", MenuAction::SET_REGION_US, MenuPage::EXIT)); + items.push_back(MenuItem("EU 868", MenuAction::SET_REGION_EU_868, MenuPage::EXIT)); + items.push_back(MenuItem("EU 433", MenuAction::SET_REGION_EU_433, MenuPage::EXIT)); + items.push_back(MenuItem("CN", MenuAction::SET_REGION_CN, MenuPage::EXIT)); + items.push_back(MenuItem("JP", MenuAction::SET_REGION_JP, MenuPage::EXIT)); + items.push_back(MenuItem("ANZ", MenuAction::SET_REGION_ANZ, MenuPage::EXIT)); + items.push_back(MenuItem("KR", MenuAction::SET_REGION_KR, MenuPage::EXIT)); + items.push_back(MenuItem("TW", MenuAction::SET_REGION_TW, MenuPage::EXIT)); + items.push_back(MenuItem("RU", MenuAction::SET_REGION_RU, MenuPage::EXIT)); + items.push_back(MenuItem("IN", MenuAction::SET_REGION_IN, MenuPage::EXIT)); + items.push_back(MenuItem("NZ 865", MenuAction::SET_REGION_NZ_865, MenuPage::EXIT)); + items.push_back(MenuItem("TH", MenuAction::SET_REGION_TH, MenuPage::EXIT)); + items.push_back(MenuItem("LoRa 2.4", MenuAction::SET_REGION_LORA_24, MenuPage::EXIT)); + items.push_back(MenuItem("UA 433", MenuAction::SET_REGION_UA_433, MenuPage::EXIT)); + items.push_back(MenuItem("UA 868", MenuAction::SET_REGION_UA_868, MenuPage::EXIT)); + items.push_back(MenuItem("MY 433", MenuAction::SET_REGION_MY_433, MenuPage::EXIT)); + items.push_back(MenuItem("MY 919", MenuAction::SET_REGION_MY_919, MenuPage::EXIT)); + items.push_back(MenuItem("SG 923", MenuAction::SET_REGION_SG_923, MenuPage::EXIT)); + items.push_back(MenuItem("PH 433", MenuAction::SET_REGION_PH_433, MenuPage::EXIT)); + items.push_back(MenuItem("PH 868", MenuAction::SET_REGION_PH_868, MenuPage::EXIT)); + items.push_back(MenuItem("PH 915", MenuAction::SET_REGION_PH_915, MenuPage::EXIT)); + items.push_back(MenuItem("ANZ 433", MenuAction::SET_REGION_ANZ_433, MenuPage::EXIT)); + items.push_back(MenuItem("KZ 433", MenuAction::SET_REGION_KZ_433, MenuPage::EXIT)); + items.push_back(MenuItem("KZ 863", MenuAction::SET_REGION_KZ_863, MenuPage::EXIT)); + items.push_back(MenuItem("NP 865", MenuAction::SET_REGION_NP_865, MenuPage::EXIT)); + items.push_back(MenuItem("BR 902", MenuAction::SET_REGION_BR_902, MenuPage::EXIT)); + items.push_back(MenuItem("Exit", MenuPage::EXIT)); + break; + + case NODE_CONFIG_PRESET: { + items.push_back(MenuItem("Back", MenuAction::BACK, MenuPage::NODE_CONFIG_LORA)); + items.push_back(MenuItem("Long Moderate", MenuAction::SET_PRESET_LONG_MODERATE, MenuPage::EXIT)); + items.push_back(MenuItem("Long Fast", MenuAction::SET_PRESET_LONG_FAST, MenuPage::EXIT)); + items.push_back(MenuItem("Medium Slow", MenuAction::SET_PRESET_MEDIUM_SLOW, MenuPage::EXIT)); + items.push_back(MenuItem("Medium Fast", MenuAction::SET_PRESET_MEDIUM_FAST, MenuPage::EXIT)); + items.push_back(MenuItem("Short Slow", MenuAction::SET_PRESET_SHORT_SLOW, MenuPage::EXIT)); + items.push_back(MenuItem("Short Fast", MenuAction::SET_PRESET_SHORT_FAST, MenuPage::EXIT)); + items.push_back(MenuItem("Short Turbo", MenuAction::SET_PRESET_SHORT_TURBO, MenuPage::EXIT)); + items.push_back(MenuItem("Exit", MenuPage::EXIT)); + break; + } + // Administration Section + case NODE_CONFIG_ADMIN_RESET: + items.push_back(MenuItem("Back", MenuAction::BACK, MenuPage::NODE_CONFIG)); + items.push_back(MenuItem("Reset All", MenuAction::RESET_NODEDB_ALL, MenuPage::EXIT)); + items.push_back(MenuItem("Keep Favorites Only", MenuAction::RESET_NODEDB_KEEP_FAVORITES, MenuPage::EXIT)); + items.push_back(MenuItem("Exit", MenuPage::EXIT)); + break; + + // Exit case EXIT: sendToBackground(); // Menu applet dismissed, allow normal behavior to resume break; @@ -379,6 +1348,15 @@ void InkHUD::MenuApplet::showPage(MenuPage page) cursorShown = false; } + // Ensure cursor never rests on a header + if (cursorShown) { + while (cursor < items.size() && items.at(cursor).isHeader) { + cursor++; + } + if (cursor >= items.size()) + cursor = 0; + } + // Remember which page we are on now currentPage = page; } @@ -390,7 +1368,8 @@ void InkHUD::MenuApplet::onRender() // Dimensions for the slots where we will draw menuItems const float padding = 0.05; - const uint16_t itemH = fontSmall.lineHeight() * 2; + const uint16_t itemH = fontSmall.lineHeight() * 1.6; + const int16_t selectInsetY = 2; const int16_t itemW = width() - X(padding) - X(padding); const int16_t itemL = X(padding); const int16_t itemR = X(1 - padding); @@ -441,18 +1420,31 @@ void InkHUD::MenuApplet::onRender() // -- Loop: draw each (visible) menu item -- for (uint8_t i = firstItem; i <= lastItem; i++) { - // Grab the menuItem - MenuItem item = items.at(i); - // Center-line for the text + // Grab the menu item + MenuItem &item = items.at(i); + + // Vertical center of this slot int16_t center = itemT + (itemH / 2); - // Box, if currently selected - if (cursorShown && i == cursor) - drawRect(itemL, itemT, itemW, itemH, BLACK); + // Header (non-selectable section label) + if (item.isHeader) { + setFont(fontSmall); - // Item's text - printAt(itemL + X(padding), center, item.label, LEFT, MIDDLE); + // Header text (flush left) + printAt(itemL + X(padding), center, item.label, LEFT, MIDDLE); + + // Subtle underline + int16_t underlineY = itemT + itemH - 2; + drawLine(itemL + X(padding), underlineY, itemR - X(padding), underlineY, BLACK); + } else { + // Box, if currently selected + if (cursorShown && i == cursor) + drawRect(itemL, itemT + selectInsetY, itemW, itemH - (selectInsetY * 2), BLACK); + + // Indented normal item text + printAt(itemL + X(padding * 2), center, item.label, LEFT, MIDDLE); + } // Checkbox, if relevant if (item.checkState) { @@ -493,11 +1485,14 @@ void InkHUD::MenuApplet::onButtonShortPress() OSThread::setIntervalFromNow(MENU_TIMEOUT_SEC * 1000UL); if (!settings->joystick.enabled) { - // Move menu cursor to next entry, then update - if (cursorShown) - cursor = (cursor + 1) % items.size(); - else + if (!cursorShown) { cursorShown = true; + cursor = 0; + } else { + do { + cursor = (cursor + 1) % items.size(); + } while (items.at(cursor).isHeader); + } requestUpdate(Drivers::EInk::UpdateTypes::FAST); } else { if (cursorShown) @@ -538,14 +1533,17 @@ void InkHUD::MenuApplet::onNavUp() { OSThread::setIntervalFromNow(MENU_TIMEOUT_SEC * 1000UL); - // Move menu cursor to previous entry, then update - if (cursor == 0) - cursor = items.size() - 1; - else - cursor--; - - if (!cursorShown) + if (!cursorShown) { cursorShown = true; + cursor = 0; + } else { + do { + if (cursor == 0) + cursor = items.size() - 1; + else + cursor--; + } while (items.at(cursor).isHeader); + } requestUpdate(Drivers::EInk::UpdateTypes::FAST); } @@ -554,11 +1552,14 @@ void InkHUD::MenuApplet::onNavDown() { OSThread::setIntervalFromNow(MENU_TIMEOUT_SEC * 1000UL); - // Move menu cursor to next entry, then update - if (cursorShown) - cursor = (cursor + 1) % items.size(); - else + if (!cursorShown) { cursorShown = true; + cursor = 0; + } else { + do { + cursor = (cursor + 1) % items.size(); + } while (items.at(cursor).isHeader); + } requestUpdate(Drivers::EInk::UpdateTypes::FAST); } @@ -622,7 +1623,8 @@ void InkHUD::MenuApplet::populateRecentsPage() // (Defined at top of this file) for (uint8_t i = 0; i < optionCount; i++) { std::string label = to_string(RECENTS_OPTIONS_MINUTES[i]) + " mins"; - items.push_back(MenuItem(label.c_str(), MenuAction::SET_RECENTS, MenuPage::EXIT)); + recentsSelected[i] = (settings->recentlyActiveSeconds == RECENTS_OPTIONS_MINUTES[i] * 60); + items.push_back(MenuItem(label.c_str(), MenuAction::SET_RECENTS, MenuPage::OPTIONS, &recentsSelected[i])); } } @@ -873,5 +1875,4 @@ void InkHUD::MenuApplet::freeCannedMessageResources() cm.messageItems.clear(); cm.recipientItems.clear(); } - -#endif +#endif // MESHTASTIC_INCLUDE_INKHUD \ No newline at end of file diff --git a/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.h b/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.h index 4f9f92227..82ccc8f45 100644 --- a/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.h +++ b/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.h @@ -35,6 +35,7 @@ class MenuApplet : public SystemApplet, public concurrency::OSThread void onRender() override; void show(Tile *t); // Open the menu, onto a user tile + void setStartPage(MenuPage page); protected: Drivers::LatchingBacklight *backlight = nullptr; // Convenient access to the backlight singleton @@ -56,6 +57,7 @@ class MenuApplet : public SystemApplet, public concurrency::OSThread void sendText(NodeNum dest, ChannelIndex channel, const char *message); // Send a text message to mesh void freeCannedMessageResources(); // Clear MenuApplet's canned message processing data + MenuPage startPageOverride = MenuPage::ROOT; MenuPage currentPage = MenuPage::ROOT; MenuPage previousPage = MenuPage::EXIT; uint8_t cursor = 0; // Which menu item is currently highlighted @@ -63,7 +65,15 @@ class MenuApplet : public SystemApplet, public concurrency::OSThread uint16_t systemInfoPanelHeight = 0; // Need to know before we render - std::vector items; // MenuItems for the current page. Filled by ShowPage + std::vector items; // MenuItems for the current page. Filled by ShowPage + std::vector nodeConfigLabels; // Persistent labels for Node Config pages + uint8_t selectedChannelIndex = 0; // Currently selected LoRa channel (Node Config → Radio → Channel) + bool channelPositionEnabled = false; + bool gpsEnabled = false; + + // Recents menu checkbox state (derived from settings.recentlyActiveSeconds) + static constexpr uint8_t RECENTS_COUNT = 6; + bool recentsSelected[RECENTS_COUNT] = {}; // Data for selecting and sending canned messages via the menu // Placed into a sub-class for organization only diff --git a/src/graphics/niche/InkHUD/Applets/System/Menu/MenuItem.h b/src/graphics/niche/InkHUD/Applets/System/Menu/MenuItem.h index c74fe3d8a..51c9161a7 100644 --- a/src/graphics/niche/InkHUD/Applets/System/Menu/MenuItem.h +++ b/src/graphics/niche/InkHUD/Applets/System/Menu/MenuItem.h @@ -30,6 +30,7 @@ class MenuItem MenuAction action = NO_ACTION; MenuPage nextPage = EXIT; bool *checkState = nullptr; + bool isHeader = false; // Non-selectable section label // Various constructors, depending on the intended function of the item @@ -40,6 +41,12 @@ class MenuItem : label(label), action(action), nextPage(nextPage), checkState(checkState) { } + static MenuItem Header(const char *label) + { + MenuItem item(label, NO_ACTION, EXIT); + item.isHeader = true; + return item; + } }; } // namespace NicheGraphics::InkHUD diff --git a/src/graphics/niche/InkHUD/Applets/System/Menu/MenuPage.h b/src/graphics/niche/InkHUD/Applets/System/Menu/MenuPage.h index 389e411c3..138c389be 100644 --- a/src/graphics/niche/InkHUD/Applets/System/Menu/MenuPage.h +++ b/src/graphics/niche/InkHUD/Applets/System/Menu/MenuPage.h @@ -20,10 +20,27 @@ enum MenuPage : uint8_t { SEND, CANNEDMESSAGE_RECIPIENT, // Select destination for a canned message OPTIONS, + NODE_CONFIG, + NODE_CONFIG_LORA, + NODE_CONFIG_CHANNELS, // List of channels + NODE_CONFIG_CHANNEL_DETAIL, // Per-channel options + NODE_CONFIG_CHANNEL_PRECISION, + NODE_CONFIG_PRESET, + NODE_CONFIG_DEVICE, + NODE_CONFIG_DEVICE_ROLE, + NODE_CONFIG_POWER, + NODE_CONFIG_POWER_ADC_CAL, + NODE_CONFIG_NETWORK, + NODE_CONFIG_DISPLAY, + NODE_CONFIG_BLUETOOTH, + NODE_CONFIG_POSITION, + NODE_CONFIG_ADMIN_RESET, + TIMEZONE, APPLETS, AUTOSHOW, RECENTS, // Select length of "recentlyActiveSeconds" - EXIT, // Dismiss the menu applet + REGION, + EXIT, // Dismiss the menu applet }; } // namespace NicheGraphics::InkHUD diff --git a/src/graphics/niche/InkHUD/Applets/System/Tips/TipsApplet.cpp b/src/graphics/niche/InkHUD/Applets/System/Tips/TipsApplet.cpp index a9d579873..7869319fe 100644 --- a/src/graphics/niche/InkHUD/Applets/System/Tips/TipsApplet.cpp +++ b/src/graphics/niche/InkHUD/Applets/System/Tips/TipsApplet.cpp @@ -10,34 +10,37 @@ using namespace NicheGraphics; InkHUD::TipsApplet::TipsApplet() { - // Decide which tips (if any) should be shown to user after the boot screen + bool needsRegion = (config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_UNSET); + + bool showTutorialTips = (settings->tips.firstBoot || needsRegion); // Welcome screen - if (settings->tips.firstBoot) + if (showTutorialTips) tipQueue.push_back(Tip::WELCOME); - // Antenna, region, timezone - // Shown at boot if region not yet set - if (config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_UNSET) + // Finish setup + if (needsRegion) tipQueue.push_back(Tip::FINISH_SETUP); + // Using the UI + if (showTutorialTips) { + tipQueue.push_back(Tip::CUSTOMIZATION); + tipQueue.push_back(Tip::BUTTONS); + } + // Shutdown info // Shown until user performs one valid shutdown if (!settings->tips.safeShutdownSeen) tipQueue.push_back(Tip::SAFE_SHUTDOWN); - // Using the UI - if (settings->tips.firstBoot) { - tipQueue.push_back(Tip::CUSTOMIZATION); - tipQueue.push_back(Tip::BUTTONS); - } - // Catch an incorrect attempt at rotating display if (config.display.flip_screen) tipQueue.push_back(Tip::ROTATION); - // Applet is foreground immediately at boot, but is obscured by LogoApplet, which is also foreground - // LogoApplet can be considered to have a higher Z-index, because it is placed before TipsApplet in the systemApplets vector + // Region picker + if (needsRegion) + tipQueue.push_back(Tip::PICK_REGION); + if (!tipQueue.empty()) bringToForeground(); } @@ -51,81 +54,109 @@ void InkHUD::TipsApplet::onRender() case Tip::FINISH_SETUP: { setFont(fontMedium); - printAt(0, 0, "Tip: Finish Setup"); + const char *title = "Tip: Finish Setup"; + uint16_t h = getWrappedTextHeight(0, width(), title); + printWrapped(0, 0, width(), title); setFont(fontSmall); - int16_t cursorY = fontMedium.lineHeight() * 1.5; - printAt(0, cursorY, "- connect antenna"); + int16_t cursorY = h + fontSmall.lineHeight(); - cursorY += fontSmall.lineHeight() * 1.2; - printAt(0, cursorY, "- connect a client app"); + auto drawBullet = [&](const char *text) { + uint16_t bh = getWrappedTextHeight(0, width(), text); + printWrapped(0, cursorY, width(), text); + cursorY += bh + (fontSmall.lineHeight() / 3); + }; - // Only if region not set - if (config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_UNSET) { - cursorY += fontSmall.lineHeight() * 1.2; - printAt(0, cursorY, "- set region"); - } + drawBullet("- connect antenna"); + drawBullet("- connect a client app"); - // Only if tz not set - if (!(*config.device.tzdef && config.device.tzdef[0] != 0)) { - cursorY += fontSmall.lineHeight() * 1.2; - printAt(0, cursorY, "- set timezone"); - } + if (config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_UNSET) + drawBullet("- set region"); - cursorY += fontSmall.lineHeight() * 1.5; - printAt(0, cursorY, "More info at meshtastic.org"); + if (!(*config.device.tzdef && config.device.tzdef[0] != 0)) + drawBullet("- set timezone"); + + cursorY += fontSmall.lineHeight() / 2; + drawBullet("More info at meshtastic.org"); - setFont(fontSmall); printAt(0, Y(1.0), "Press button to continue", LEFT, BOTTOM); } break; + case Tip::PICK_REGION: { + setFont(fontMedium); + printAt(0, 0, "Set Region"); + + setFont(fontSmall); + printWrapped(0, fontMedium.lineHeight() * 1.5, width(), "Please select your LoRa region to complete setup."); + + printAt(0, Y(1.0), "Press button to choose", LEFT, BOTTOM); + } break; + case Tip::SAFE_SHUTDOWN: { setFont(fontMedium); - printAt(0, 0, "Tip: Shutdown"); + + const char *title = "Tip: Shutdown"; + uint16_t h = getWrappedTextHeight(0, width(), title); + printWrapped(0, 0, width(), title); setFont(fontSmall); - std::string shutdown; - shutdown += "Before removing power, please shut down from InkHUD menu, or a client app. \n"; - shutdown += "\n"; - shutdown += "This ensures data is saved."; - printWrapped(0, fontMedium.lineHeight() * 1.5, width(), shutdown); + int16_t cursorY = h + fontSmall.lineHeight(); + + const char *body = "Before removing power, please shut down from InkHUD menu, or a client app.\n\n" + "This ensures data is saved."; + + uint16_t bodyH = getWrappedTextHeight(0, width(), body); + printWrapped(0, cursorY, width(), body); + cursorY += bodyH + (fontSmall.lineHeight() / 2); printAt(0, Y(1.0), "Press button to continue", LEFT, BOTTOM); - } break; case Tip::CUSTOMIZATION: { setFont(fontMedium); - printAt(0, 0, "Tip: Customization"); + + const char *title = "Tip: Customization"; + uint16_t h = getWrappedTextHeight(0, width(), title); + printWrapped(0, 0, width(), title); setFont(fontSmall); - printWrapped(0, fontMedium.lineHeight() * 1.5, width(), - "Configure & control display with the InkHUD menu. Optional features, layout, rotation, and more."); + int16_t cursorY = h + fontSmall.lineHeight(); + + const char *body = "Configure & control display with the InkHUD menu. " + "Optional features, layout, rotation, and more."; + + uint16_t bodyH = getWrappedTextHeight(0, width(), body); + printWrapped(0, cursorY, width(), body); + cursorY += bodyH + (fontSmall.lineHeight() / 2); printAt(0, Y(1.0), "Press button to continue", LEFT, BOTTOM); } break; case Tip::BUTTONS: { setFont(fontMedium); - printAt(0, 0, "Tip: Buttons"); + + const char *title = "Tip: Buttons"; + uint16_t h = getWrappedTextHeight(0, width(), title); + printWrapped(0, 0, width(), title); setFont(fontSmall); - int16_t cursorY = fontMedium.lineHeight() * 1.5; + int16_t cursorY = h + fontSmall.lineHeight(); + + auto drawBullet = [&](const char *text) { + uint16_t bh = getWrappedTextHeight(0, width(), text); + printWrapped(0, cursorY, width(), text); + cursorY += bh + (fontSmall.lineHeight() / 3); + }; if (!settings->joystick.enabled) { - printAt(0, cursorY, "User Button"); - cursorY += fontSmall.lineHeight() * 1.2; - printAt(0, cursorY, "- short press: next"); - cursorY += fontSmall.lineHeight() * 1.2; - printAt(0, cursorY, "- long press: select / open menu"); + drawBullet("User Button"); + drawBullet("- short press: next"); + drawBullet("- long press: select or open menu"); } else { - printAt(0, cursorY, "Joystick"); - cursorY += fontSmall.lineHeight() * 1.2; - printAt(0, cursorY, "- open menu / select"); - cursorY += fontSmall.lineHeight() * 1.5; - printAt(0, cursorY, "Exit Button"); - cursorY += fontSmall.lineHeight() * 1.2; - printAt(0, cursorY, "- switch tile / close menu"); + drawBullet("Joystick"); + drawBullet("- press: open menu or select"); + drawBullet("Exit Button"); + drawBullet("- press: switch tile or close menu"); } printAt(0, Y(1.0), "Press button to continue", LEFT, BOTTOM); @@ -133,12 +164,21 @@ void InkHUD::TipsApplet::onRender() case Tip::ROTATION: { setFont(fontMedium); - printAt(0, 0, "Tip: Rotation"); + + const char *title = "Tip: Rotation"; + uint16_t h = getWrappedTextHeight(0, width(), title); + printWrapped(0, 0, width(), title); setFont(fontSmall); if (!settings->joystick.enabled) { - printWrapped(0, fontMedium.lineHeight() * 1.5, width(), - "To rotate the display, use the InkHUD menu. Long-press the user button > Options > Rotate."); + int16_t cursorY = h + fontSmall.lineHeight(); + + const char *body = "To rotate the display, use the InkHUD menu. " + "Long-press the user button > Options > Rotate."; + + uint16_t bh = getWrappedTextHeight(0, width(), body); + printWrapped(0, cursorY, width(), body); + cursorY += bh + (fontSmall.lineHeight() / 2); } else { printWrapped(0, fontMedium.lineHeight() * 1.5, width(), "To rotate the display, use the InkHUD menu. Press the user button > Options > Rotate."); @@ -159,12 +199,15 @@ void InkHUD::TipsApplet::renderWelcome() { uint16_t padW = X(0.05); + // Detect portrait orientation + bool portrait = height() > width(); + // Block 1 - logo & title // ======================== // Logo size - uint16_t logoWLimit = X(0.3); - uint16_t logoHLimit = Y(0.3); + uint16_t logoWLimit = portrait ? X(0.5) : X(0.3); + uint16_t logoHLimit = portrait ? Y(0.25) : Y(0.3); uint16_t logoW = getLogoWidth(logoWLimit, logoHLimit); uint16_t logoH = getLogoHeight(logoWLimit, logoHLimit); @@ -177,7 +220,7 @@ void InkHUD::TipsApplet::renderWelcome() // Center the block // Desired effect: equal margin from display edge for logo left and title right - int16_t block1Y = Y(0.3); + int16_t block1Y = portrait ? Y(0.2) : Y(0.3); int16_t block1CX = X(0.5) + (logoW / 2) - (titleW / 2); int16_t logoCX = block1CX - (logoW / 2) - (padW / 2); int16_t titleCX = block1CX + (titleW / 2) + (padW / 2); @@ -192,7 +235,7 @@ void InkHUD::TipsApplet::renderWelcome() std::string subtitle = "InkHUD"; if (width() >= 200) subtitle += " - A Heads-Up Display"; // Future proofing: narrower for tiny display - printAt(X(0.5), Y(0.6), subtitle, CENTER, MIDDLE); + printAt(X(0.5), portrait ? Y(0.45) : Y(0.6), subtitle, CENTER, MIDDLE); // Block 3 - press to continue // ============================ @@ -224,26 +267,37 @@ void InkHUD::TipsApplet::onBackground() // While our SystemApplet::handleInput flag is true void InkHUD::TipsApplet::onButtonShortPress() { + bool needsRegion = (config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_UNSET); + // If we're prompting the user to pick a region, hand off to the menu + if (!tipQueue.empty() && tipQueue.front() == Tip::PICK_REGION) { + tipQueue.pop_front(); + + // Signal InkHUD to open the menu on Region page + inkhud->forceRegionMenu = true; + + // Close tips and open menu + sendToBackground(); + inkhud->openMenu(); + return; + } + // Consume current tip tipQueue.pop_front(); // All tips done if (tipQueue.empty()) { // Record that user has now seen the "tutorial" set of tips // Don't show them on subsequent boots - if (settings->tips.firstBoot) { + if (settings->tips.firstBoot && !needsRegion) { settings->tips.firstBoot = false; inkhud->persistence->saveSettings(); } - // Close applet, and full refresh to clean the screen - // Need to force update, because our request would be ignored otherwise, as we are now background + // Close applet and clean the screen sendToBackground(); inkhud->forceUpdate(EInk::UpdateTypes::FULL); - } - - // More tips left - else + } else { requestUpdate(); + } } // Functions the same as the user button in this instance diff --git a/src/graphics/niche/InkHUD/Applets/System/Tips/TipsApplet.h b/src/graphics/niche/InkHUD/Applets/System/Tips/TipsApplet.h index 159e6f58f..ff7eea046 100644 --- a/src/graphics/niche/InkHUD/Applets/System/Tips/TipsApplet.h +++ b/src/graphics/niche/InkHUD/Applets/System/Tips/TipsApplet.h @@ -23,6 +23,7 @@ class TipsApplet : public SystemApplet enum class Tip { WELCOME, FINISH_SETUP, + PICK_REGION, SAFE_SHUTDOWN, CUSTOMIZATION, BUTTONS, diff --git a/src/graphics/niche/InkHUD/Events.cpp b/src/graphics/niche/InkHUD/Events.cpp index 5382d2391..fa45a49ed 100644 --- a/src/graphics/niche/InkHUD/Events.cpp +++ b/src/graphics/niche/InkHUD/Events.cpp @@ -276,6 +276,15 @@ int InkHUD::Events::beforeDeepSleep(void *unused) return 0; // We agree: deep sleep now } +// Display an intermediate screen while configuration changes are applied +void InkHUD::Events::applyingChanges() +{ + // Bring the logo applet forward with a temporary message + for (SystemApplet *sa : inkhud->systemApplets) { + sa->onApplyingChanges(); + } +} + // Callback for rebootObserver // Same as shutdown, without drawing the logoApplet // Makes sure we don't lose message history / InkHUD config diff --git a/src/graphics/niche/InkHUD/Events.h b/src/graphics/niche/InkHUD/Events.h index 664ca19f0..1916cf78e 100644 --- a/src/graphics/niche/InkHUD/Events.h +++ b/src/graphics/niche/InkHUD/Events.h @@ -29,12 +29,13 @@ class Events void onButtonShort(); // User button: short press void onButtonLong(); // User button: long press - void onExitShort(); // Exit button: short press - void onExitLong(); // Exit button: long press - void onNavUp(); // Navigate up - void onNavDown(); // Navigate down - void onNavLeft(); // Navigate left - void onNavRight(); // Navigate right + void applyingChanges(); + void onExitShort(); // Exit button: short press + void onExitLong(); // Exit button: long press + void onNavUp(); // Navigate up + void onNavDown(); // Navigate down + void onNavLeft(); // Navigate left + void onNavRight(); // Navigate right int beforeDeepSleep(void *unused); // Prepare for shutdown int beforeReboot(void *unused); // Prepare for reboot diff --git a/src/graphics/niche/InkHUD/InkHUD.cpp b/src/graphics/niche/InkHUD/InkHUD.cpp index 9f05ae5bb..13b15b7e8 100644 --- a/src/graphics/niche/InkHUD/InkHUD.cpp +++ b/src/graphics/niche/InkHUD/InkHUD.cpp @@ -53,6 +53,13 @@ void InkHUD::InkHUD::addApplet(const char *name, Applet *a, bool defaultActive, windowManager->addApplet(name, a, defaultActive, defaultAutoshow, onTile); } +void InkHUD::InkHUD::notifyApplyingChanges() +{ + if (events) { + events->applyingChanges(); + } +} + // Start InkHUD! // Call this only after you have configured InkHUD void InkHUD::InkHUD::begin() diff --git a/src/graphics/niche/InkHUD/InkHUD.h b/src/graphics/niche/InkHUD/InkHUD.h index 7325d8262..5280d9ac7 100644 --- a/src/graphics/niche/InkHUD/InkHUD.h +++ b/src/graphics/niche/InkHUD/InkHUD.h @@ -47,6 +47,7 @@ class InkHUD void setDriver(Drivers::EInk *driver); void setDisplayResilience(uint8_t fastPerFull = 5, float stressMultiplier = 2.0); void addApplet(const char *name, Applet *a, bool defaultActive = false, bool defaultAutoshow = false, uint8_t onTile = -1); + void notifyApplyingChanges(); void begin(); @@ -76,6 +77,9 @@ class InkHUD void rotateJoystick(uint8_t angle = 1); // rotate 90 deg by default void toggleBatteryIcon(); + // Used by TipsApplet to force menu to start on Region selection + bool forceRegionMenu = false; + // Updating the display // - called by various InkHUD components diff --git a/src/graphics/niche/InkHUD/SystemApplet.h b/src/graphics/niche/InkHUD/SystemApplet.h index 7ee47eeb9..fb5b06e51 100644 --- a/src/graphics/niche/InkHUD/SystemApplet.h +++ b/src/graphics/niche/InkHUD/SystemApplet.h @@ -27,6 +27,7 @@ class SystemApplet : public Applet bool lockRequests = false; // - prevent other applets from triggering display updates virtual void onReboot() { onShutdown(); } // - handle reboot specially + virtual void onApplyingChanges() {} // Other system applets may take precedence over our own system applet though // The order an applet is passed to WindowManager::addSystemApplet determines this hierarchy (added earlier = higher rank) From 91ad861b26edcd0382190b64ac49371d87860843 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Tue, 27 Jan 2026 09:56:56 -0600 Subject: [PATCH 47/99] Add Thinknode M4 variant_shutdown() (#9449) --- .../nrf52840/ELECROW-ThinkNode-M4/variant.cpp | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/variants/nrf52840/ELECROW-ThinkNode-M4/variant.cpp b/variants/nrf52840/ELECROW-ThinkNode-M4/variant.cpp index af9bed998..5c4b6215b 100644 --- a/variants/nrf52840/ELECROW-ThinkNode-M4/variant.cpp +++ b/variants/nrf52840/ELECROW-ThinkNode-M4/variant.cpp @@ -49,3 +49,21 @@ void initVariant() pinMode(Battery_LED_4, OUTPUT); ledOff(Battery_LED_4); } + +/// called from main-nrf52.cpp during the cpuDeepSleep() function +void variant_shutdown() +{ + for (int pin = 0; pin < 48; pin++) { + if (pin == PIN_GPS_EN || pin == PIN_BUTTON1) { + continue; + } + pinMode(pin, OUTPUT); + digitalWrite(pin, LOW); + if (pin >= 32) { + NRF_P1->DIRCLR = (1 << (pin - 32)); + } else { + NRF_GPIO->DIRCLR = (1 << pin); + } + } + digitalWrite(PIN_GPS_EN, HIGH); +} From b6a1020fc522fc07ec7c39b5325206f21bf84dc8 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Tue, 27 Jan 2026 13:06:50 -0600 Subject: [PATCH 48/99] Add error handling for SPI command failures in LR11x0, RF95, and SX128x interfaces (#9447) --- src/mesh/LR11x0Interface.cpp | 13 ++++++++++++- src/mesh/RF95Interface.cpp | 3 +++ src/mesh/SX128xInterface.cpp | 2 ++ 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/mesh/LR11x0Interface.cpp b/src/mesh/LR11x0Interface.cpp index 341afe78d..a8a2780ed 100644 --- a/src/mesh/LR11x0Interface.cpp +++ b/src/mesh/LR11x0Interface.cpp @@ -91,10 +91,21 @@ template bool LR11x0Interface::init() LOG_DEBUG("Set RF1 switch to %s", getFreq() < 1e9 ? "SubGHz" : "2.4GHz"); #endif + // Allow extra time for TCXO to stabilize after power-on + delay(10); + int res = lora.begin(getFreq(), bw, sf, cr, syncWord, power, preambleLength, tcxoVoltage); + + // Retry if we get SPI command failed - some units need extra TCXO stabilization time + if (res == RADIOLIB_ERR_SPI_CMD_FAILED) { + LOG_WARN("LR11x0 init failed with %d (SPI_CMD_FAILED), retrying after delay...", res); + delay(100); + res = lora.begin(getFreq(), bw, sf, cr, syncWord, power, preambleLength, tcxoVoltage); + } + // \todo Display actual typename of the adapter, not just `LR11x0` LOG_INFO("LR11x0 init result %d", res); - if (res == RADIOLIB_ERR_CHIP_NOT_FOUND) + if (res == RADIOLIB_ERR_CHIP_NOT_FOUND || res == RADIOLIB_ERR_SPI_CMD_FAILED) return false; LR11x0VersionInfo_t version; diff --git a/src/mesh/RF95Interface.cpp b/src/mesh/RF95Interface.cpp index 5588fc348..0c12401ca 100644 --- a/src/mesh/RF95Interface.cpp +++ b/src/mesh/RF95Interface.cpp @@ -177,6 +177,9 @@ bool RF95Interface::init() int res = lora->begin(getFreq(), bw, sf, cr, syncWord, power, preambleLength); LOG_INFO("RF95 init result %d", res); + if (res == RADIOLIB_ERR_CHIP_NOT_FOUND || res == RADIOLIB_ERR_SPI_CMD_FAILED) + return false; + LOG_INFO("Frequency set to %f", getFreq()); LOG_INFO("Bandwidth set to %f", bw); LOG_INFO("Power output set to %d", power); diff --git a/src/mesh/SX128xInterface.cpp b/src/mesh/SX128xInterface.cpp index b4278c636..3ab63df3d 100644 --- a/src/mesh/SX128xInterface.cpp +++ b/src/mesh/SX128xInterface.cpp @@ -69,6 +69,8 @@ template bool SX128xInterface::init() int res = lora.begin(getFreq(), bw, sf, cr, syncWord, power, preambleLength); // \todo Display actual typename of the adapter, not just `SX128x` LOG_INFO("SX128x init result %d", res); + if (res == RADIOLIB_ERR_CHIP_NOT_FOUND || res == RADIOLIB_ERR_SPI_CMD_FAILED) + return false; if ((config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24) && (res == RADIOLIB_ERR_INVALID_FREQUENCY)) { LOG_WARN("Radio only supports 2.4GHz LoRa. Adjusting Region and rebooting"); From d1edd386b65e297c1a96d78e9f06ecc3807db323 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 27 Jan 2026 13:08:10 -0600 Subject: [PATCH 49/99] Update meshtastic/device-ui digest to 69739b8 (#9448) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index ee51d24ec..b7c46a6e1 100644 --- a/platformio.ini +++ b/platformio.ini @@ -119,7 +119,7 @@ lib_deps = [device-ui_base] lib_deps = # renovate: datasource=git-refs depName=meshtastic/device-ui packageName=https://github.com/meshtastic/device-ui gitBranch=master - https://github.com/meshtastic/device-ui/archive/37ad715b76cd6ca4aa500a4a4d9740e3cdf3e3cb.zip + https://github.com/meshtastic/device-ui/archive/69739b84f87a91568d3c421498bc89977937a141.zip ; Common libs for environmental measurements in telemetry module [environmental_base] From cfda9bb8ef7026d8936d97799e5c53eeb6525613 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 27 Jan 2026 13:12:03 -0600 Subject: [PATCH 50/99] Update protobufs (#9453) Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> --- protobufs | 2 +- src/mesh/generated/meshtastic/admin.pb.cpp | 9 ++ src/mesh/generated/meshtastic/admin.pb.h | 111 +++++++++++++++++- src/mesh/generated/meshtastic/deviceonly.pb.h | 2 +- src/mesh/generated/meshtastic/localonly.pb.h | 16 ++- .../generated/meshtastic/telemetry.pb.cpp | 3 + src/mesh/generated/meshtastic/telemetry.pb.h | 41 +++++++ 7 files changed, 173 insertions(+), 11 deletions(-) diff --git a/protobufs b/protobufs index d9003b2b6..bc63a57f9 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit d9003b2b6c9f378066979ec83b013aca441cf240 +Subproject commit bc63a57f9e5dba8a7c90ee0bd4a9840862d61f6d diff --git a/src/mesh/generated/meshtastic/admin.pb.cpp b/src/mesh/generated/meshtastic/admin.pb.cpp index e358bc96d..01d3fa910 100644 --- a/src/mesh/generated/meshtastic/admin.pb.cpp +++ b/src/mesh/generated/meshtastic/admin.pb.cpp @@ -27,6 +27,15 @@ PB_BIND(meshtastic_SharedContact, meshtastic_SharedContact, AUTO) PB_BIND(meshtastic_KeyVerificationAdmin, meshtastic_KeyVerificationAdmin, AUTO) +PB_BIND(meshtastic_SensorConfig, meshtastic_SensorConfig, AUTO) + + +PB_BIND(meshtastic_SCD4X_config, meshtastic_SCD4X_config, AUTO) + + +PB_BIND(meshtastic_SEN5X_config, meshtastic_SEN5X_config, AUTO) + + diff --git a/src/mesh/generated/meshtastic/admin.pb.h b/src/mesh/generated/meshtastic/admin.pb.h index 26b4343e9..f545ed9bf 100644 --- a/src/mesh/generated/meshtastic/admin.pb.h +++ b/src/mesh/generated/meshtastic/admin.pb.h @@ -77,7 +77,9 @@ typedef enum _meshtastic_AdminMessage_ModuleConfigType { /* TODO: REPLACE */ meshtastic_AdminMessage_ModuleConfigType_DETECTIONSENSOR_CONFIG = 11, /* TODO: REPLACE */ - meshtastic_AdminMessage_ModuleConfigType_PAXCOUNTER_CONFIG = 12 + meshtastic_AdminMessage_ModuleConfigType_PAXCOUNTER_CONFIG = 12, + /* TODO: REPLACE */ + meshtastic_AdminMessage_ModuleConfigType_STATUSMESSAGE_CONFIG = 13 } meshtastic_AdminMessage_ModuleConfigType; typedef enum _meshtastic_AdminMessage_BackupLocation { @@ -169,6 +171,48 @@ typedef struct _meshtastic_KeyVerificationAdmin { uint32_t security_number; } meshtastic_KeyVerificationAdmin; +typedef struct _meshtastic_SCD4X_config { + /* Set Automatic self-calibration enabled */ + bool has_set_asc; + bool set_asc; + /* Recalibration target CO2 concentration in ppm (FRC or ASC) */ + bool has_set_target_co2_conc; + uint32_t set_target_co2_conc; + /* Reference temperature in degC */ + bool has_set_temperature; + float set_temperature; + /* Altitude of sensor in meters above sea level. 0 - 3000m (overrides ambient pressure) */ + bool has_set_altitude; + uint32_t set_altitude; + /* Sensor ambient pressure in Pa. 70000 - 120000 Pa (overrides altitude) */ + bool has_set_ambient_pressure; + uint32_t set_ambient_pressure; + /* Perform a factory reset of the sensor */ + bool has_factory_reset; + bool factory_reset; + /* Power mode for sensor (true for low power, false for normal) */ + bool has_set_power_mode; + bool set_power_mode; +} meshtastic_SCD4X_config; + +typedef struct _meshtastic_SEN5X_config { + /* Reference temperature in degC */ + bool has_set_temperature; + float set_temperature; + /* One-shot mode (true for low power - one-shot mode, false for normal - continuous mode) */ + bool has_set_one_shot_mode; + bool set_one_shot_mode; +} meshtastic_SEN5X_config; + +typedef struct _meshtastic_SensorConfig { + /* SCD4X CO2 Sensor configuration */ + bool has_scd4x_config; + meshtastic_SCD4X_config scd4x_config; + /* SEN5X PM Sensor configuration */ + bool has_sen5x_config; + meshtastic_SEN5X_config sen5x_config; +} meshtastic_SensorConfig; + typedef PB_BYTES_ARRAY_T(8) meshtastic_AdminMessage_session_passkey_t; /* This message is handled by the Admin module and is responsible for all settings/channel read/write operations. This message is used to do settings operations to both remote AND local nodes. @@ -301,6 +345,8 @@ typedef struct _meshtastic_AdminMessage { bool nodedb_reset; /* Tell the node to reset into the OTA Loader */ meshtastic_AdminMessage_OTAEvent ota_request; + /* Parameters and sensor configuration */ + meshtastic_SensorConfig sensor_config; }; /* The node generates this key and sends it with any get_x_response packets. The client MUST include the same key with any set_x commands. Key expires after 300 seconds. @@ -323,8 +369,8 @@ extern "C" { #define _meshtastic_AdminMessage_ConfigType_ARRAYSIZE ((meshtastic_AdminMessage_ConfigType)(meshtastic_AdminMessage_ConfigType_DEVICEUI_CONFIG+1)) #define _meshtastic_AdminMessage_ModuleConfigType_MIN meshtastic_AdminMessage_ModuleConfigType_MQTT_CONFIG -#define _meshtastic_AdminMessage_ModuleConfigType_MAX meshtastic_AdminMessage_ModuleConfigType_PAXCOUNTER_CONFIG -#define _meshtastic_AdminMessage_ModuleConfigType_ARRAYSIZE ((meshtastic_AdminMessage_ModuleConfigType)(meshtastic_AdminMessage_ModuleConfigType_PAXCOUNTER_CONFIG+1)) +#define _meshtastic_AdminMessage_ModuleConfigType_MAX meshtastic_AdminMessage_ModuleConfigType_STATUSMESSAGE_CONFIG +#define _meshtastic_AdminMessage_ModuleConfigType_ARRAYSIZE ((meshtastic_AdminMessage_ModuleConfigType)(meshtastic_AdminMessage_ModuleConfigType_STATUSMESSAGE_CONFIG+1)) #define _meshtastic_AdminMessage_BackupLocation_MIN meshtastic_AdminMessage_BackupLocation_FLASH #define _meshtastic_AdminMessage_BackupLocation_MAX meshtastic_AdminMessage_BackupLocation_SD @@ -349,6 +395,9 @@ extern "C" { #define meshtastic_KeyVerificationAdmin_message_type_ENUMTYPE meshtastic_KeyVerificationAdmin_MessageType + + + /* Initializer values for message structs */ #define meshtastic_AdminMessage_init_default {0, {0}, {0, {0}}} #define meshtastic_AdminMessage_InputEvent_init_default {0, 0, 0, 0} @@ -357,6 +406,9 @@ extern "C" { #define meshtastic_NodeRemoteHardwarePinsResponse_init_default {0, {meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default}} #define meshtastic_SharedContact_init_default {0, false, meshtastic_User_init_default, 0, 0} #define meshtastic_KeyVerificationAdmin_init_default {_meshtastic_KeyVerificationAdmin_MessageType_MIN, 0, 0, false, 0} +#define meshtastic_SensorConfig_init_default {false, meshtastic_SCD4X_config_init_default, false, meshtastic_SEN5X_config_init_default} +#define meshtastic_SCD4X_config_init_default {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} +#define meshtastic_SEN5X_config_init_default {false, 0, false, 0} #define meshtastic_AdminMessage_init_zero {0, {0}, {0, {0}}} #define meshtastic_AdminMessage_InputEvent_init_zero {0, 0, 0, 0} #define meshtastic_AdminMessage_OTAEvent_init_zero {_meshtastic_OTAMode_MIN, {0, {0}}} @@ -364,6 +416,9 @@ extern "C" { #define meshtastic_NodeRemoteHardwarePinsResponse_init_zero {0, {meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero}} #define meshtastic_SharedContact_init_zero {0, false, meshtastic_User_init_zero, 0, 0} #define meshtastic_KeyVerificationAdmin_init_zero {_meshtastic_KeyVerificationAdmin_MessageType_MIN, 0, 0, false, 0} +#define meshtastic_SensorConfig_init_zero {false, meshtastic_SCD4X_config_init_zero, false, meshtastic_SEN5X_config_init_zero} +#define meshtastic_SCD4X_config_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} +#define meshtastic_SEN5X_config_init_zero {false, 0, false, 0} /* Field tags (for use in manual encoding/decoding) */ #define meshtastic_AdminMessage_InputEvent_event_code_tag 1 @@ -385,6 +440,17 @@ extern "C" { #define meshtastic_KeyVerificationAdmin_remote_nodenum_tag 2 #define meshtastic_KeyVerificationAdmin_nonce_tag 3 #define meshtastic_KeyVerificationAdmin_security_number_tag 4 +#define meshtastic_SCD4X_config_set_asc_tag 1 +#define meshtastic_SCD4X_config_set_target_co2_conc_tag 2 +#define meshtastic_SCD4X_config_set_temperature_tag 3 +#define meshtastic_SCD4X_config_set_altitude_tag 4 +#define meshtastic_SCD4X_config_set_ambient_pressure_tag 5 +#define meshtastic_SCD4X_config_factory_reset_tag 6 +#define meshtastic_SCD4X_config_set_power_mode_tag 7 +#define meshtastic_SEN5X_config_set_temperature_tag 1 +#define meshtastic_SEN5X_config_set_one_shot_mode_tag 2 +#define meshtastic_SensorConfig_scd4x_config_tag 1 +#define meshtastic_SensorConfig_sen5x_config_tag 2 #define meshtastic_AdminMessage_get_channel_request_tag 1 #define meshtastic_AdminMessage_get_channel_response_tag 2 #define meshtastic_AdminMessage_get_owner_request_tag 3 @@ -441,6 +507,7 @@ extern "C" { #define meshtastic_AdminMessage_factory_reset_config_tag 99 #define meshtastic_AdminMessage_nodedb_reset_tag 100 #define meshtastic_AdminMessage_ota_request_tag 102 +#define meshtastic_AdminMessage_sensor_config_tag 103 #define meshtastic_AdminMessage_session_passkey_tag 101 /* Struct field encoding specification for nanopb */ @@ -501,7 +568,8 @@ X(a, STATIC, ONEOF, INT32, (payload_variant,shutdown_seconds,shutdown_se X(a, STATIC, ONEOF, INT32, (payload_variant,factory_reset_config,factory_reset_config), 99) \ X(a, STATIC, ONEOF, BOOL, (payload_variant,nodedb_reset,nodedb_reset), 100) \ X(a, STATIC, SINGULAR, BYTES, session_passkey, 101) \ -X(a, STATIC, ONEOF, MESSAGE, (payload_variant,ota_request,ota_request), 102) +X(a, STATIC, ONEOF, MESSAGE, (payload_variant,ota_request,ota_request), 102) \ +X(a, STATIC, ONEOF, MESSAGE, (payload_variant,sensor_config,sensor_config), 103) #define meshtastic_AdminMessage_CALLBACK NULL #define meshtastic_AdminMessage_DEFAULT NULL #define meshtastic_AdminMessage_payload_variant_get_channel_response_MSGTYPE meshtastic_Channel @@ -523,6 +591,7 @@ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,ota_request,ota_request), 10 #define meshtastic_AdminMessage_payload_variant_add_contact_MSGTYPE meshtastic_SharedContact #define meshtastic_AdminMessage_payload_variant_key_verification_MSGTYPE meshtastic_KeyVerificationAdmin #define meshtastic_AdminMessage_payload_variant_ota_request_MSGTYPE meshtastic_AdminMessage_OTAEvent +#define meshtastic_AdminMessage_payload_variant_sensor_config_MSGTYPE meshtastic_SensorConfig #define meshtastic_AdminMessage_InputEvent_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, UINT32, event_code, 1) \ @@ -569,6 +638,31 @@ X(a, STATIC, OPTIONAL, UINT32, security_number, 4) #define meshtastic_KeyVerificationAdmin_CALLBACK NULL #define meshtastic_KeyVerificationAdmin_DEFAULT NULL +#define meshtastic_SensorConfig_FIELDLIST(X, a) \ +X(a, STATIC, OPTIONAL, MESSAGE, scd4x_config, 1) \ +X(a, STATIC, OPTIONAL, MESSAGE, sen5x_config, 2) +#define meshtastic_SensorConfig_CALLBACK NULL +#define meshtastic_SensorConfig_DEFAULT NULL +#define meshtastic_SensorConfig_scd4x_config_MSGTYPE meshtastic_SCD4X_config +#define meshtastic_SensorConfig_sen5x_config_MSGTYPE meshtastic_SEN5X_config + +#define meshtastic_SCD4X_config_FIELDLIST(X, a) \ +X(a, STATIC, OPTIONAL, BOOL, set_asc, 1) \ +X(a, STATIC, OPTIONAL, UINT32, set_target_co2_conc, 2) \ +X(a, STATIC, OPTIONAL, FLOAT, set_temperature, 3) \ +X(a, STATIC, OPTIONAL, UINT32, set_altitude, 4) \ +X(a, STATIC, OPTIONAL, UINT32, set_ambient_pressure, 5) \ +X(a, STATIC, OPTIONAL, BOOL, factory_reset, 6) \ +X(a, STATIC, OPTIONAL, BOOL, set_power_mode, 7) +#define meshtastic_SCD4X_config_CALLBACK NULL +#define meshtastic_SCD4X_config_DEFAULT NULL + +#define meshtastic_SEN5X_config_FIELDLIST(X, a) \ +X(a, STATIC, OPTIONAL, FLOAT, set_temperature, 1) \ +X(a, STATIC, OPTIONAL, BOOL, set_one_shot_mode, 2) +#define meshtastic_SEN5X_config_CALLBACK NULL +#define meshtastic_SEN5X_config_DEFAULT NULL + extern const pb_msgdesc_t meshtastic_AdminMessage_msg; extern const pb_msgdesc_t meshtastic_AdminMessage_InputEvent_msg; extern const pb_msgdesc_t meshtastic_AdminMessage_OTAEvent_msg; @@ -576,6 +670,9 @@ extern const pb_msgdesc_t meshtastic_HamParameters_msg; extern const pb_msgdesc_t meshtastic_NodeRemoteHardwarePinsResponse_msg; extern const pb_msgdesc_t meshtastic_SharedContact_msg; extern const pb_msgdesc_t meshtastic_KeyVerificationAdmin_msg; +extern const pb_msgdesc_t meshtastic_SensorConfig_msg; +extern const pb_msgdesc_t meshtastic_SCD4X_config_msg; +extern const pb_msgdesc_t meshtastic_SEN5X_config_msg; /* Defines for backwards compatibility with code written before nanopb-0.4.0 */ #define meshtastic_AdminMessage_fields &meshtastic_AdminMessage_msg @@ -585,6 +682,9 @@ extern const pb_msgdesc_t meshtastic_KeyVerificationAdmin_msg; #define meshtastic_NodeRemoteHardwarePinsResponse_fields &meshtastic_NodeRemoteHardwarePinsResponse_msg #define meshtastic_SharedContact_fields &meshtastic_SharedContact_msg #define meshtastic_KeyVerificationAdmin_fields &meshtastic_KeyVerificationAdmin_msg +#define meshtastic_SensorConfig_fields &meshtastic_SensorConfig_msg +#define meshtastic_SCD4X_config_fields &meshtastic_SCD4X_config_msg +#define meshtastic_SEN5X_config_fields &meshtastic_SEN5X_config_msg /* Maximum encoded size of messages (where known) */ #define MESHTASTIC_MESHTASTIC_ADMIN_PB_H_MAX_SIZE meshtastic_AdminMessage_size @@ -594,6 +694,9 @@ extern const pb_msgdesc_t meshtastic_KeyVerificationAdmin_msg; #define meshtastic_HamParameters_size 31 #define meshtastic_KeyVerificationAdmin_size 25 #define meshtastic_NodeRemoteHardwarePinsResponse_size 496 +#define meshtastic_SCD4X_config_size 29 +#define meshtastic_SEN5X_config_size 7 +#define meshtastic_SensorConfig_size 40 #define meshtastic_SharedContact_size 127 #ifdef __cplusplus diff --git a/src/mesh/generated/meshtastic/deviceonly.pb.h b/src/mesh/generated/meshtastic/deviceonly.pb.h index 409805d24..57e7df8fc 100644 --- a/src/mesh/generated/meshtastic/deviceonly.pb.h +++ b/src/mesh/generated/meshtastic/deviceonly.pb.h @@ -361,7 +361,7 @@ extern const pb_msgdesc_t meshtastic_BackupPreferences_msg; /* Maximum encoded size of messages (where known) */ /* meshtastic_NodeDatabase_size depends on runtime parameters */ #define MESHTASTIC_MESHTASTIC_DEVICEONLY_PB_H_MAX_SIZE meshtastic_BackupPreferences_size -#define meshtastic_BackupPreferences_size 2279 +#define meshtastic_BackupPreferences_size 2362 #define meshtastic_ChannelFile_size 718 #define meshtastic_DeviceState_size 1737 #define meshtastic_NodeInfoLite_size 196 diff --git a/src/mesh/generated/meshtastic/localonly.pb.h b/src/mesh/generated/meshtastic/localonly.pb.h index 2b44d0c9a..f11b13419 100644 --- a/src/mesh/generated/meshtastic/localonly.pb.h +++ b/src/mesh/generated/meshtastic/localonly.pb.h @@ -87,6 +87,9 @@ typedef struct _meshtastic_LocalModuleConfig { /* Paxcounter Config */ bool has_paxcounter; meshtastic_ModuleConfig_PaxcounterConfig paxcounter; + /* StatusMessage Config */ + bool has_statusmessage; + meshtastic_ModuleConfig_StatusMessageConfig statusmessage; } meshtastic_LocalModuleConfig; @@ -96,9 +99,9 @@ extern "C" { /* Initializer values for message structs */ #define meshtastic_LocalConfig_init_default {false, meshtastic_Config_DeviceConfig_init_default, false, meshtastic_Config_PositionConfig_init_default, false, meshtastic_Config_PowerConfig_init_default, false, meshtastic_Config_NetworkConfig_init_default, false, meshtastic_Config_DisplayConfig_init_default, false, meshtastic_Config_LoRaConfig_init_default, false, meshtastic_Config_BluetoothConfig_init_default, 0, false, meshtastic_Config_SecurityConfig_init_default} -#define meshtastic_LocalModuleConfig_init_default {false, meshtastic_ModuleConfig_MQTTConfig_init_default, false, meshtastic_ModuleConfig_SerialConfig_init_default, false, meshtastic_ModuleConfig_ExternalNotificationConfig_init_default, false, meshtastic_ModuleConfig_StoreForwardConfig_init_default, false, meshtastic_ModuleConfig_RangeTestConfig_init_default, false, meshtastic_ModuleConfig_TelemetryConfig_init_default, false, meshtastic_ModuleConfig_CannedMessageConfig_init_default, 0, false, meshtastic_ModuleConfig_AudioConfig_init_default, false, meshtastic_ModuleConfig_RemoteHardwareConfig_init_default, false, meshtastic_ModuleConfig_NeighborInfoConfig_init_default, false, meshtastic_ModuleConfig_AmbientLightingConfig_init_default, false, meshtastic_ModuleConfig_DetectionSensorConfig_init_default, false, meshtastic_ModuleConfig_PaxcounterConfig_init_default} +#define meshtastic_LocalModuleConfig_init_default {false, meshtastic_ModuleConfig_MQTTConfig_init_default, false, meshtastic_ModuleConfig_SerialConfig_init_default, false, meshtastic_ModuleConfig_ExternalNotificationConfig_init_default, false, meshtastic_ModuleConfig_StoreForwardConfig_init_default, false, meshtastic_ModuleConfig_RangeTestConfig_init_default, false, meshtastic_ModuleConfig_TelemetryConfig_init_default, false, meshtastic_ModuleConfig_CannedMessageConfig_init_default, 0, false, meshtastic_ModuleConfig_AudioConfig_init_default, false, meshtastic_ModuleConfig_RemoteHardwareConfig_init_default, false, meshtastic_ModuleConfig_NeighborInfoConfig_init_default, false, meshtastic_ModuleConfig_AmbientLightingConfig_init_default, false, meshtastic_ModuleConfig_DetectionSensorConfig_init_default, false, meshtastic_ModuleConfig_PaxcounterConfig_init_default, false, meshtastic_ModuleConfig_StatusMessageConfig_init_default} #define meshtastic_LocalConfig_init_zero {false, meshtastic_Config_DeviceConfig_init_zero, false, meshtastic_Config_PositionConfig_init_zero, false, meshtastic_Config_PowerConfig_init_zero, false, meshtastic_Config_NetworkConfig_init_zero, false, meshtastic_Config_DisplayConfig_init_zero, false, meshtastic_Config_LoRaConfig_init_zero, false, meshtastic_Config_BluetoothConfig_init_zero, 0, false, meshtastic_Config_SecurityConfig_init_zero} -#define meshtastic_LocalModuleConfig_init_zero {false, meshtastic_ModuleConfig_MQTTConfig_init_zero, false, meshtastic_ModuleConfig_SerialConfig_init_zero, false, meshtastic_ModuleConfig_ExternalNotificationConfig_init_zero, false, meshtastic_ModuleConfig_StoreForwardConfig_init_zero, false, meshtastic_ModuleConfig_RangeTestConfig_init_zero, false, meshtastic_ModuleConfig_TelemetryConfig_init_zero, false, meshtastic_ModuleConfig_CannedMessageConfig_init_zero, 0, false, meshtastic_ModuleConfig_AudioConfig_init_zero, false, meshtastic_ModuleConfig_RemoteHardwareConfig_init_zero, false, meshtastic_ModuleConfig_NeighborInfoConfig_init_zero, false, meshtastic_ModuleConfig_AmbientLightingConfig_init_zero, false, meshtastic_ModuleConfig_DetectionSensorConfig_init_zero, false, meshtastic_ModuleConfig_PaxcounterConfig_init_zero} +#define meshtastic_LocalModuleConfig_init_zero {false, meshtastic_ModuleConfig_MQTTConfig_init_zero, false, meshtastic_ModuleConfig_SerialConfig_init_zero, false, meshtastic_ModuleConfig_ExternalNotificationConfig_init_zero, false, meshtastic_ModuleConfig_StoreForwardConfig_init_zero, false, meshtastic_ModuleConfig_RangeTestConfig_init_zero, false, meshtastic_ModuleConfig_TelemetryConfig_init_zero, false, meshtastic_ModuleConfig_CannedMessageConfig_init_zero, 0, false, meshtastic_ModuleConfig_AudioConfig_init_zero, false, meshtastic_ModuleConfig_RemoteHardwareConfig_init_zero, false, meshtastic_ModuleConfig_NeighborInfoConfig_init_zero, false, meshtastic_ModuleConfig_AmbientLightingConfig_init_zero, false, meshtastic_ModuleConfig_DetectionSensorConfig_init_zero, false, meshtastic_ModuleConfig_PaxcounterConfig_init_zero, false, meshtastic_ModuleConfig_StatusMessageConfig_init_zero} /* Field tags (for use in manual encoding/decoding) */ #define meshtastic_LocalConfig_device_tag 1 @@ -124,6 +127,7 @@ extern "C" { #define meshtastic_LocalModuleConfig_ambient_lighting_tag 12 #define meshtastic_LocalModuleConfig_detection_sensor_tag 13 #define meshtastic_LocalModuleConfig_paxcounter_tag 14 +#define meshtastic_LocalModuleConfig_statusmessage_tag 15 /* Struct field encoding specification for nanopb */ #define meshtastic_LocalConfig_FIELDLIST(X, a) \ @@ -161,7 +165,8 @@ X(a, STATIC, OPTIONAL, MESSAGE, remote_hardware, 10) \ X(a, STATIC, OPTIONAL, MESSAGE, neighbor_info, 11) \ X(a, STATIC, OPTIONAL, MESSAGE, ambient_lighting, 12) \ X(a, STATIC, OPTIONAL, MESSAGE, detection_sensor, 13) \ -X(a, STATIC, OPTIONAL, MESSAGE, paxcounter, 14) +X(a, STATIC, OPTIONAL, MESSAGE, paxcounter, 14) \ +X(a, STATIC, OPTIONAL, MESSAGE, statusmessage, 15) #define meshtastic_LocalModuleConfig_CALLBACK NULL #define meshtastic_LocalModuleConfig_DEFAULT NULL #define meshtastic_LocalModuleConfig_mqtt_MSGTYPE meshtastic_ModuleConfig_MQTTConfig @@ -177,6 +182,7 @@ X(a, STATIC, OPTIONAL, MESSAGE, paxcounter, 14) #define meshtastic_LocalModuleConfig_ambient_lighting_MSGTYPE meshtastic_ModuleConfig_AmbientLightingConfig #define meshtastic_LocalModuleConfig_detection_sensor_MSGTYPE meshtastic_ModuleConfig_DetectionSensorConfig #define meshtastic_LocalModuleConfig_paxcounter_MSGTYPE meshtastic_ModuleConfig_PaxcounterConfig +#define meshtastic_LocalModuleConfig_statusmessage_MSGTYPE meshtastic_ModuleConfig_StatusMessageConfig extern const pb_msgdesc_t meshtastic_LocalConfig_msg; extern const pb_msgdesc_t meshtastic_LocalModuleConfig_msg; @@ -186,9 +192,9 @@ extern const pb_msgdesc_t meshtastic_LocalModuleConfig_msg; #define meshtastic_LocalModuleConfig_fields &meshtastic_LocalModuleConfig_msg /* Maximum encoded size of messages (where known) */ -#define MESHTASTIC_MESHTASTIC_LOCALONLY_PB_H_MAX_SIZE meshtastic_LocalConfig_size +#define MESHTASTIC_MESHTASTIC_LOCALONLY_PB_H_MAX_SIZE meshtastic_LocalModuleConfig_size #define meshtastic_LocalConfig_size 749 -#define meshtastic_LocalModuleConfig_size 675 +#define meshtastic_LocalModuleConfig_size 758 #ifdef __cplusplus } /* extern "C" */ diff --git a/src/mesh/generated/meshtastic/telemetry.pb.cpp b/src/mesh/generated/meshtastic/telemetry.pb.cpp index 345d7a157..fff75ebc1 100644 --- a/src/mesh/generated/meshtastic/telemetry.pb.cpp +++ b/src/mesh/generated/meshtastic/telemetry.pb.cpp @@ -33,6 +33,9 @@ PB_BIND(meshtastic_Telemetry, meshtastic_Telemetry, 2) PB_BIND(meshtastic_Nau7802Config, meshtastic_Nau7802Config, AUTO) +PB_BIND(meshtastic_SEN5XState, meshtastic_SEN5XState, AUTO) + + diff --git a/src/mesh/generated/meshtastic/telemetry.pb.h b/src/mesh/generated/meshtastic/telemetry.pb.h index 131dd9949..dc9d876dc 100644 --- a/src/mesh/generated/meshtastic/telemetry.pb.h +++ b/src/mesh/generated/meshtastic/telemetry.pb.h @@ -435,6 +435,25 @@ typedef struct _meshtastic_Nau7802Config { float calibrationFactor; } meshtastic_Nau7802Config; +/* SEN5X State, for saving to flash */ +typedef struct _meshtastic_SEN5XState { + /* Last cleaning time for SEN5X */ + uint32_t last_cleaning_time; + /* Last cleaning time for SEN5X - valid flag */ + bool last_cleaning_valid; + /* Config flag for one-shot mode (see admin.proto) */ + bool one_shot_mode; + /* Last VOC state time for SEN55 */ + bool has_voc_state_time; + uint32_t voc_state_time; + /* Last VOC state validity flag for SEN55 */ + bool has_voc_state_valid; + bool voc_state_valid; + /* VOC state array (8x uint8t) for SEN55 */ + bool has_voc_state_array; + uint64_t voc_state_array; +} meshtastic_SEN5XState; + #ifdef __cplusplus extern "C" { @@ -455,6 +474,7 @@ extern "C" { + /* Initializer values for message structs */ #define meshtastic_DeviceMetrics_init_default {false, 0, false, 0, false, 0, false, 0, false, 0} #define meshtastic_EnvironmentMetrics_init_default {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} @@ -465,6 +485,7 @@ extern "C" { #define meshtastic_HostMetrics_init_default {0, 0, 0, false, 0, false, 0, 0, 0, 0, false, ""} #define meshtastic_Telemetry_init_default {0, 0, {meshtastic_DeviceMetrics_init_default}} #define meshtastic_Nau7802Config_init_default {0, 0} +#define meshtastic_SEN5XState_init_default {0, 0, 0, false, 0, false, 0, false, 0} #define meshtastic_DeviceMetrics_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0} #define meshtastic_EnvironmentMetrics_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} #define meshtastic_PowerMetrics_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} @@ -474,6 +495,7 @@ extern "C" { #define meshtastic_HostMetrics_init_zero {0, 0, 0, false, 0, false, 0, 0, 0, 0, false, ""} #define meshtastic_Telemetry_init_zero {0, 0, {meshtastic_DeviceMetrics_init_zero}} #define meshtastic_Nau7802Config_init_zero {0, 0} +#define meshtastic_SEN5XState_init_zero {0, 0, 0, false, 0, false, 0, false, 0} /* Field tags (for use in manual encoding/decoding) */ #define meshtastic_DeviceMetrics_battery_level_tag 1 @@ -581,6 +603,12 @@ extern "C" { #define meshtastic_Telemetry_host_metrics_tag 8 #define meshtastic_Nau7802Config_zeroOffset_tag 1 #define meshtastic_Nau7802Config_calibrationFactor_tag 2 +#define meshtastic_SEN5XState_last_cleaning_time_tag 1 +#define meshtastic_SEN5XState_last_cleaning_valid_tag 2 +#define meshtastic_SEN5XState_one_shot_mode_tag 3 +#define meshtastic_SEN5XState_voc_state_time_tag 4 +#define meshtastic_SEN5XState_voc_state_valid_tag 5 +#define meshtastic_SEN5XState_voc_state_array_tag 6 /* Struct field encoding specification for nanopb */ #define meshtastic_DeviceMetrics_FIELDLIST(X, a) \ @@ -731,6 +759,16 @@ X(a, STATIC, SINGULAR, FLOAT, calibrationFactor, 2) #define meshtastic_Nau7802Config_CALLBACK NULL #define meshtastic_Nau7802Config_DEFAULT NULL +#define meshtastic_SEN5XState_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, UINT32, last_cleaning_time, 1) \ +X(a, STATIC, SINGULAR, BOOL, last_cleaning_valid, 2) \ +X(a, STATIC, SINGULAR, BOOL, one_shot_mode, 3) \ +X(a, STATIC, OPTIONAL, UINT32, voc_state_time, 4) \ +X(a, STATIC, OPTIONAL, BOOL, voc_state_valid, 5) \ +X(a, STATIC, OPTIONAL, FIXED64, voc_state_array, 6) +#define meshtastic_SEN5XState_CALLBACK NULL +#define meshtastic_SEN5XState_DEFAULT NULL + extern const pb_msgdesc_t meshtastic_DeviceMetrics_msg; extern const pb_msgdesc_t meshtastic_EnvironmentMetrics_msg; extern const pb_msgdesc_t meshtastic_PowerMetrics_msg; @@ -740,6 +778,7 @@ extern const pb_msgdesc_t meshtastic_HealthMetrics_msg; extern const pb_msgdesc_t meshtastic_HostMetrics_msg; extern const pb_msgdesc_t meshtastic_Telemetry_msg; extern const pb_msgdesc_t meshtastic_Nau7802Config_msg; +extern const pb_msgdesc_t meshtastic_SEN5XState_msg; /* Defines for backwards compatibility with code written before nanopb-0.4.0 */ #define meshtastic_DeviceMetrics_fields &meshtastic_DeviceMetrics_msg @@ -751,6 +790,7 @@ extern const pb_msgdesc_t meshtastic_Nau7802Config_msg; #define meshtastic_HostMetrics_fields &meshtastic_HostMetrics_msg #define meshtastic_Telemetry_fields &meshtastic_Telemetry_msg #define meshtastic_Nau7802Config_fields &meshtastic_Nau7802Config_msg +#define meshtastic_SEN5XState_fields &meshtastic_SEN5XState_msg /* Maximum encoded size of messages (where known) */ #define MESHTASTIC_MESHTASTIC_TELEMETRY_PB_H_MAX_SIZE meshtastic_Telemetry_size @@ -762,6 +802,7 @@ extern const pb_msgdesc_t meshtastic_Nau7802Config_msg; #define meshtastic_LocalStats_size 87 #define meshtastic_Nau7802Config_size 16 #define meshtastic_PowerMetrics_size 81 +#define meshtastic_SEN5XState_size 27 #define meshtastic_Telemetry_size 272 #ifdef __cplusplus From 10b2eae70c7f36895892bbbd3094f2cb4544026d Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Tue, 27 Jan 2026 13:56:32 -0600 Subject: [PATCH 51/99] Move more code out of main-nrf52 into variant.cpp (#9450) --- src/platform/nrf52/main-nrf52.cpp | 16 ---------------- .../nrf52840/ELECROW-ThinkNode-M1/variant.cpp | 7 +++++++ .../diy/nrf52_promicro_diy_tcxo/variant.cpp | 7 +++++++ 3 files changed, 14 insertions(+), 16 deletions(-) diff --git a/src/platform/nrf52/main-nrf52.cpp b/src/platform/nrf52/main-nrf52.cpp index 11b05165c..0376a1dad 100644 --- a/src/platform/nrf52/main-nrf52.cpp +++ b/src/platform/nrf52/main-nrf52.cpp @@ -464,22 +464,6 @@ void cpuDeepSleep(uint32_t msecToWake) // FIXME, use non-init RAM per // https://devzone.nordicsemi.com/f/nordic-q-a/48919/ram-retention-settings-with-softdevice-enabled -#ifdef ELECROW_ThinkNode_M1 - nrf_gpio_cfg_input(PIN_BUTTON1, NRF_GPIO_PIN_PULLUP); // Configure the pin to be woken up as an input - nrf_gpio_pin_sense_t sense = NRF_GPIO_PIN_SENSE_LOW; - nrf_gpio_cfg_sense_set(PIN_BUTTON1, sense); - - nrf_gpio_cfg_input(PIN_BUTTON2, NRF_GPIO_PIN_PULLUP); - nrf_gpio_pin_sense_t sense1 = NRF_GPIO_PIN_SENSE_LOW; - nrf_gpio_cfg_sense_set(PIN_BUTTON2, sense1); -#endif - -#ifdef PROMICRO_DIY_TCXO - nrf_gpio_cfg_input(BUTTON_PIN, NRF_GPIO_PIN_PULLUP); // Enable internal pull-up on the button pin - nrf_gpio_pin_sense_t sense = NRF_GPIO_PIN_SENSE_LOW; // Configure SENSE signal on low edge - nrf_gpio_cfg_sense_set(BUTTON_PIN, sense); // Apply SENSE to wake up the device from the deep sleep -#endif - #ifdef BATTERY_LPCOMP_INPUT // Wake up if power rises again nrf_lpcomp_config_t c; diff --git a/variants/nrf52840/ELECROW-ThinkNode-M1/variant.cpp b/variants/nrf52840/ELECROW-ThinkNode-M1/variant.cpp index 2ce84bc57..1560cde73 100644 --- a/variants/nrf52840/ELECROW-ThinkNode-M1/variant.cpp +++ b/variants/nrf52840/ELECROW-ThinkNode-M1/variant.cpp @@ -59,4 +59,11 @@ void variant_shutdown() NRF_GPIO->DIRCLR = (1 << pin); } } + nrf_gpio_cfg_input(PIN_BUTTON1, NRF_GPIO_PIN_PULLUP); // Configure the pin to be woken up as an input + nrf_gpio_pin_sense_t sense = NRF_GPIO_PIN_SENSE_LOW; + nrf_gpio_cfg_sense_set(PIN_BUTTON1, sense); + + nrf_gpio_cfg_input(PIN_BUTTON2, NRF_GPIO_PIN_PULLUP); + nrf_gpio_pin_sense_t sense1 = NRF_GPIO_PIN_SENSE_LOW; + nrf_gpio_cfg_sense_set(PIN_BUTTON2, sense1); } \ No newline at end of file diff --git a/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/variant.cpp b/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/variant.cpp index 5869ed1d4..384c618e2 100644 --- a/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/variant.cpp +++ b/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/variant.cpp @@ -36,3 +36,10 @@ void initVariant() pinMode(PIN_3V3_EN, OUTPUT); digitalWrite(PIN_3V3_EN, HIGH); } + +void variant_shutdown() +{ + nrf_gpio_cfg_input(BUTTON_PIN, NRF_GPIO_PIN_PULLUP); // Enable internal pull-up on the button pin + nrf_gpio_pin_sense_t sense = NRF_GPIO_PIN_SENSE_LOW; // Configure SENSE signal on low edge + nrf_gpio_cfg_sense_set(BUTTON_PIN, sense); // Apply SENSE to wake up the device from the deep sleep +} From 23a8b5a66f9ad9f5176bd768beef2200c2f3066c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E6=9E=97?= <47050377+linser233@users.noreply.github.com> Date: Wed, 28 Jan 2026 04:50:50 +0800 Subject: [PATCH 52/99] Fix uMesh RF POWER configuration error (#9326) * fix issue https://github.com/linser233/uMesh/issues/1 * fix issue https://github.com/linser233/uMesh/issues/1 * Update and rename lora-usb-umesh-1262.yaml to lora-usb-umesh-1262-30dbm.yaml * Update and rename lora-usb-umesh-1268.yaml to lora-usb-umesh-1268-30dbm.yaml --- bin/config.d/lora-usb-umesh-1262-30dbm.yaml | 23 +++++++++++++++++++++ bin/config.d/lora-usb-umesh-1262.yaml | 15 -------------- bin/config.d/lora-usb-umesh-1268-30dbm.yaml | 23 +++++++++++++++++++++ bin/config.d/lora-usb-umesh-1268.yaml | 15 -------------- 4 files changed, 46 insertions(+), 30 deletions(-) create mode 100644 bin/config.d/lora-usb-umesh-1262-30dbm.yaml delete mode 100644 bin/config.d/lora-usb-umesh-1262.yaml create mode 100644 bin/config.d/lora-usb-umesh-1268-30dbm.yaml delete mode 100644 bin/config.d/lora-usb-umesh-1268.yaml diff --git a/bin/config.d/lora-usb-umesh-1262-30dbm.yaml b/bin/config.d/lora-usb-umesh-1262-30dbm.yaml new file mode 100644 index 000000000..7726eccd1 --- /dev/null +++ b/bin/config.d/lora-usb-umesh-1262-30dbm.yaml @@ -0,0 +1,23 @@ +Lora: + Module: sx1262 + CS: 0 + IRQ: 6 + Reset: 1 + Busy: 4 + RXen: 2 + DIO2_AS_RF_SWITCH: true + spidev: ch341 + USB_PID: 0x5512 + USB_VID: 0x1A86 + DIO3_TCXO_VOLTAGE: true +# USB_Serialnum: 12345678 + SX126X_MAX_POWER: 22 +# Reduce output power to improve EMI + NUM_PA_POINTS: 22 + TX_GAIN_LORA: 12, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 10, 10, 9, 8, 8, 7 +# Note: This module integrates an additional PA to achieve higher output power. +# The 'power' parameter here does not represent the actual RF output. +# TX_GAIN_LORA defines the gain offset applied at each SX1262 input power step (1–22 dBm). +# Each array element corresponds to the additional gain when that input level is set, +# The effective RF output is: Pout ≈ Pset + TX_GAIN_LORA[index]. +# Please refer to https://github.com/linser233/uMesh/blob/main/RF_Power.md for detailed information. diff --git a/bin/config.d/lora-usb-umesh-1262.yaml b/bin/config.d/lora-usb-umesh-1262.yaml deleted file mode 100644 index 6008e63b7..000000000 --- a/bin/config.d/lora-usb-umesh-1262.yaml +++ /dev/null @@ -1,15 +0,0 @@ -Lora: - Module: sx1262 - CS: 0 - IRQ: 6 - Reset: 1 - Busy: 4 - RXen: 2 - DIO2_AS_RF_SWITCH: true - spidev: ch341 - USB_PID: 0x5512 - USB_VID: 0x1A86 - DIO3_TCXO_VOLTAGE: true -# USB_Serialnum: 12345678 - SX126X_MAX_POWER: 30 -# Reduce output power to improve EMI diff --git a/bin/config.d/lora-usb-umesh-1268-30dbm.yaml b/bin/config.d/lora-usb-umesh-1268-30dbm.yaml new file mode 100644 index 000000000..c054a92f9 --- /dev/null +++ b/bin/config.d/lora-usb-umesh-1268-30dbm.yaml @@ -0,0 +1,23 @@ +Lora: + Module: sx1268 + CS: 0 + IRQ: 6 + Reset: 1 + Busy: 4 + RXen: 2 + DIO2_AS_RF_SWITCH: true + spidev: ch341 + USB_PID: 0x5512 + USB_VID: 0x1A86 + DIO3_TCXO_VOLTAGE: true +# USB_Serialnum: 12345678 + SX126X_MAX_POWER: 22 +# Reduce output power to improve EMI + NUM_PA_POINTS: 22 + TX_GAIN_LORA: 12, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 10, 10, 9, 8, 8, 7 +# Note: This module integrates an additional PA to achieve higher output power. +# The 'power' parameter here does not represent the actual RF output. +# TX_GAIN_LORA defines the gain offset applied at each SX1262 input power step (1–22 dBm). +# Each array element corresponds to the additional gain when that input level is set, +# The effective RF output is: Pout ≈ Pset + TX_GAIN_LORA[index]. +# Please refer to https://github.com/linser233/uMesh/blob/main/RF_Power.md for detailed information. diff --git a/bin/config.d/lora-usb-umesh-1268.yaml b/bin/config.d/lora-usb-umesh-1268.yaml deleted file mode 100644 index 637472966..000000000 --- a/bin/config.d/lora-usb-umesh-1268.yaml +++ /dev/null @@ -1,15 +0,0 @@ -Lora: - Module: sx1268 - CS: 0 - IRQ: 6 - Reset: 1 - Busy: 4 - RXen: 2 - DIO2_AS_RF_SWITCH: true - spidev: ch341 - USB_PID: 0x5512 - USB_VID: 0x1A86 - DIO3_TCXO_VOLTAGE: true -# USB_Serialnum: 12345678 - SX126X_MAX_POWER: 30 -# Reduce output power to improve EMI From fd498bebad72f6e69b2bf65ea6b9057f62667a48 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Tue, 27 Jan 2026 16:09:18 -0600 Subject: [PATCH 53/99] Add support for Hackaday Communicator function keys (#9444) --- src/graphics/Screen.cpp | 20 ++++++++++++++++++++ src/input/HackadayCommunicatorKeyboard.cpp | 13 +++++++------ src/input/InputBroker.h | 5 +++++ src/input/TCA8418KeyboardBase.h | 7 ++++++- src/input/kbI2cBase.cpp | 20 ++++++++++++++++++++ 5 files changed, 58 insertions(+), 7 deletions(-) diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index 8bf69b7a0..111a47f7c 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -1731,6 +1731,26 @@ int Screen::handleInputEvent(const InputEvent *event) showFrame(FrameDirection::PREVIOUS); } else if (event->inputEvent == INPUT_BROKER_RIGHT || event->inputEvent == INPUT_BROKER_USER_PRESS) { showFrame(FrameDirection::NEXT); + } else if (event->inputEvent == INPUT_BROKER_FN_F1) { + this->ui->switchToFrame(0); + lastScreenTransition = millis(); + setFastFramerate(); + } else if (event->inputEvent == INPUT_BROKER_FN_F2) { + this->ui->switchToFrame(1); + lastScreenTransition = millis(); + setFastFramerate(); + } else if (event->inputEvent == INPUT_BROKER_FN_F3) { + this->ui->switchToFrame(2); + lastScreenTransition = millis(); + setFastFramerate(); + } else if (event->inputEvent == INPUT_BROKER_FN_F4) { + this->ui->switchToFrame(3); + lastScreenTransition = millis(); + setFastFramerate(); + } else if (event->inputEvent == INPUT_BROKER_FN_F5) { + this->ui->switchToFrame(4); + lastScreenTransition = millis(); + setFastFramerate(); } else if (event->inputEvent == INPUT_BROKER_UP_LONG) { // Long press up button for fast frame switching showPrevFrame(); diff --git a/src/input/HackadayCommunicatorKeyboard.cpp b/src/input/HackadayCommunicatorKeyboard.cpp index 87c8a24ae..c6a9e0ae8 100644 --- a/src/input/HackadayCommunicatorKeyboard.cpp +++ b/src/input/HackadayCommunicatorKeyboard.cpp @@ -20,20 +20,20 @@ constexpr uint8_t modifierLeftShift = 0b0001; // Num chars per key, Modulus for rotating through characters static uint8_t HackadayCommunicatorTapMod[_TCA8418_NUM_KEYS] = { - 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 2, 2, 2, 2, 2, 2, 0, 0, 0, 1, 2, 2, 2, 1, 2, 2, 0, 0, 0, 2, 1, 2, 2, 0, 1, 1, 0, }; static unsigned char HackadayCommunicatorTapMap[_TCA8418_NUM_KEYS][2] = {{}, - {}, + {Key::FUNCTION_F1}, {'+'}, {'9'}, {'8'}, {'7'}, - {'2'}, - {'3'}, - {'4'}, - {'5'}, + {Key::FUNCTION_F2}, + {Key::FUNCTION_F3}, + {Key::FUNCTION_F4}, + {Key::FUNCTION_F5}, {Key::ESC}, {'q', 'Q'}, {'w', 'W'}, @@ -141,6 +141,7 @@ void HackadayCommunicatorKeyboard::pressed(uint8_t key) if (state == Init || state == Busy) { return; } + LOG_DEBUG("Key pressed: %u", key); if (modifierFlag && (millis() - last_modifier_time > _TCA8418_MULTI_TAP_THRESHOLD)) { modifierFlag = 0; diff --git a/src/input/InputBroker.h b/src/input/InputBroker.h index c55d7fa53..970e9969a 100644 --- a/src/input/InputBroker.h +++ b/src/input/InputBroker.h @@ -27,6 +27,11 @@ enum input_broker_event { INPUT_BROKER_SHUTDOWN = 0x9b, INPUT_BROKER_GPS_TOGGLE = 0x9e, INPUT_BROKER_SEND_PING = 0xaf, + INPUT_BROKER_FN_F1 = 0xf1, + INPUT_BROKER_FN_F2 = 0xf2, + INPUT_BROKER_FN_F3 = 0xf3, + INPUT_BROKER_FN_F4 = 0xf4, + INPUT_BROKER_FN_F5 = 0xf5, INPUT_BROKER_MATRIXKEY = 0xFE, INPUT_BROKER_ANYKEY = 0xff diff --git a/src/input/TCA8418KeyboardBase.h b/src/input/TCA8418KeyboardBase.h index 8e509ac7e..e608c6da5 100644 --- a/src/input/TCA8418KeyboardBase.h +++ b/src/input/TCA8418KeyboardBase.h @@ -26,7 +26,12 @@ class TCA8418KeyboardBase GPS_TOGGLE = 0x9E, MUTE_TOGGLE = 0xAC, SEND_PING = 0xAF, - BL_TOGGLE = 0xAB + BL_TOGGLE = 0xAB, + FUNCTION_F1 = 0xF1, + FUNCTION_F2 = 0xF2, + FUNCTION_F3 = 0xF3, + FUNCTION_F4 = 0xF4, + FUNCTION_F5 = 0xF5 }; typedef uint8_t (*i2c_com_fptr_t)(uint8_t dev_addr, uint8_t reg_addr, uint8_t *data, uint8_t len); diff --git a/src/input/kbI2cBase.cpp b/src/input/kbI2cBase.cpp index 12d0822f6..d744ee2ca 100644 --- a/src/input/kbI2cBase.cpp +++ b/src/input/kbI2cBase.cpp @@ -321,6 +321,26 @@ int32_t KbI2cBase::runOnce() e.inputEvent = INPUT_BROKER_ANYKEY; e.kbchar = INPUT_BROKER_MSG_TAB; break; + case TCA8418KeyboardBase::FUNCTION_F1: + e.inputEvent = INPUT_BROKER_FN_F1; + e.kbchar = 0x00; + break; + case TCA8418KeyboardBase::FUNCTION_F2: + e.inputEvent = INPUT_BROKER_FN_F2; + e.kbchar = 0x00; + break; + case TCA8418KeyboardBase::FUNCTION_F3: + e.inputEvent = INPUT_BROKER_FN_F3; + e.kbchar = 0x00; + break; + case TCA8418KeyboardBase::FUNCTION_F4: + e.inputEvent = INPUT_BROKER_FN_F4; + e.kbchar = 0x00; + break; + case TCA8418KeyboardBase::FUNCTION_F5: + e.inputEvent = INPUT_BROKER_FN_F5; + e.kbchar = 0x00; + break; default: if (nextEvent > 127) { e.inputEvent = INPUT_BROKER_NONE; From 69a42e1fd2a65e34ca87cd2e9c9f8c3d89ccf627 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Tue, 27 Jan 2026 18:00:20 -0600 Subject: [PATCH 54/99] Add portduino_status, assign hardware device IDs... (#9441) * Add portduino_status, assign hardware device IDs, and try to recover a CH341 device on a USB error * Minor fixes suggested by Copilot --- src/main.cpp | 39 ++++++++++++++++- src/mesh/NodeDB.cpp | 7 ++- src/mesh/SX126xInterface.cpp | 16 ++++++- src/platform/portduino/PortduinoGlue.cpp | 11 +++++ src/platform/portduino/PortduinoGlue.h | 9 +++- src/platform/portduino/USBHal.h | 54 ++++++++++++++++++++++-- src/platform/portduino/architecture.h | 2 +- variants/native/portduino.ini | 2 +- 8 files changed, 129 insertions(+), 11 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 93cc89296..3ccc06afb 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1401,7 +1401,43 @@ void loop() if (inputBroker) inputBroker->processInputEventQueue(); #endif -#if ARCH_PORTDUINO && HAS_TFT +#if ARCH_PORTDUINO + if (portduino_config.lora_spi_dev == "ch341" && ch341Hal != nullptr) { + ch341Hal->checkError(); + } + if (portduino_status.LoRa_in_error && rebootAtMsec == 0) { + LOG_ERROR("LoRa in error detected, attempting to recover"); + if (rIf != nullptr) { + delete rIf; + rIf = nullptr; + } + if (portduino_config.lora_spi_dev == "ch341") { + if (ch341Hal != nullptr) { + delete ch341Hal; + ch341Hal = nullptr; + sleep(3); + } + try { + ch341Hal = new Ch341Hal(0, portduino_config.lora_usb_serial_num, portduino_config.lora_usb_vid, + portduino_config.lora_usb_pid); + } catch (std::exception &e) { + std::cerr << e.what() << std::endl; + std::cerr << "Could not initialize CH341 device!" << std::endl; + exit(EXIT_FAILURE); + } + } + if (initLoRa()) { + router->addInterface(rIf); + portduino_status.LoRa_in_error = false; + } else { + LOG_WARN("Reconfigure failed, rebooting"); + if (screen) { + screen->showSimpleBanner("Rebooting..."); + } + rebootAtMsec = millis() + 25; + } + } +#if HAS_TFT if (screen && portduino_config.displayPanel == x11 && config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) { auto dispdev = screen->getDisplayDevice(); @@ -1409,6 +1445,7 @@ void loop() static_cast(dispdev)->sdlLoop(); } #endif +#endif #if HAS_SCREEN && ENABLE_MESSAGE_PERSISTENCE messageStoreAutosaveTick(); #endif diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index ca6d49ca2..30d1633d5 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -2247,7 +2247,10 @@ void recordCriticalError(meshtastic_CriticalErrorCode code, uint32_t address, co // Currently portuino is mostly used for simulation. Make sure the user notices something really bad happened #ifdef ARCH_PORTDUINO - LOG_ERROR("A critical failure occurred, portduino is exiting"); - exit(2); + LOG_ERROR("A critical failure occurred"); + // TODO: Determine if other critical errors should also cause an immediate exit + if (code == meshtastic_CriticalErrorCode_FLASH_CORRUPTION_RECOVERABLE || + code == meshtastic_CriticalErrorCode_FLASH_CORRUPTION_UNRECOVERABLE) + exit(2); #endif } diff --git a/src/mesh/SX126xInterface.cpp b/src/mesh/SX126xInterface.cpp index 498496a3b..9dfc46bee 100644 --- a/src/mesh/SX126xInterface.cpp +++ b/src/mesh/SX126xInterface.cpp @@ -269,8 +269,12 @@ template void SX126xInterface::setStandby() if (err != RADIOLIB_ERR_NONE) LOG_DEBUG("SX126x standby %s%d", radioLibErr, err); +#ifdef ARCH_PORTDUINO + if (err != RADIOLIB_ERR_NONE) + portduino_status.LoRa_in_error = true; +#else assert(err == RADIOLIB_ERR_NONE); - +#endif isReceiving = false; // If we were receiving, not any more activeReceiveStart = 0; disableInterrupt(); @@ -313,7 +317,12 @@ template void SX126xInterface::startReceive() int err = lora.startReceiveDutyCycleAuto(preambleLength, 8, MESHTASTIC_RADIOLIB_IRQ_RX_FLAGS); if (err != RADIOLIB_ERR_NONE) LOG_ERROR("SX126X startReceiveDutyCycleAuto %s%d", radioLibErr, err); +#ifdef ARCH_PORTDUINO + if (err != RADIOLIB_ERR_NONE) + portduino_status.LoRa_in_error = true; +#else assert(err == RADIOLIB_ERR_NONE); +#endif RadioLibInterface::startReceive(); @@ -341,7 +350,12 @@ template bool SX126xInterface::isChannelActive() return true; if (result != RADIOLIB_CHANNEL_FREE) LOG_ERROR("SX126X scanChannel %s%d", radioLibErr, result); +#ifdef ARCH_PORTDUINO + if (result == RADIOLIB_ERR_WRONG_MODEM) + portduino_status.LoRa_in_error = true; +#else assert(result != RADIOLIB_ERR_WRONG_MODEM); +#endif return false; } diff --git a/src/platform/portduino/PortduinoGlue.cpp b/src/platform/portduino/PortduinoGlue.cpp index 9159d5954..d0d8ba40f 100644 --- a/src/platform/portduino/PortduinoGlue.cpp +++ b/src/platform/portduino/PortduinoGlue.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include @@ -29,6 +30,7 @@ #include "platform/portduino/USBHal.h" portduino_config_struct portduino_config; +portduino_status_struct portduino_status; std::ofstream traceFile; std::ofstream JSONFile; Ch341Hal *ch341Hal = nullptr; @@ -400,6 +402,11 @@ void portduinoSetup() if (found_hat) { product_config = cleanupNameForAutoconf("lora-hat-" + std::string(hat_vendor) + "-" + autoconf_product + ".yaml"); + if (strncmp(hat_vendor, "RAK", strlen("RAK")) == 0 && + strncmp(autoconf_product, "6421 Pi Hat", strlen("6421 Pi Hat")) == 0) { + std::cout << "autoconf: Setting hardwareModel to RAK6421" << std::endl; + portduino_status.hardwareModel = meshtastic_HardwareModel_RAK6421; + } } else if (found_ch341) { product_config = cleanupNameForAutoconf("lora-usb-" + std::string(autoconf_product) + ".yaml"); // look for more data after the null terminator @@ -408,6 +415,10 @@ void portduinoSetup() memcpy(portduino_config.device_id, autoconf_product + len + 1, 16); if (!memfll(portduino_config.device_id, '\0', 16) && !memfll(portduino_config.device_id, 0xff, 16)) { portduino_config.has_device_id = true; + if (strncmp(autoconf_product, "MESHSTICK 1262", strlen("MESHSTICK 1262")) == 0) { + std::cout << "autoconf: Setting hardwareModel to Meshstick 1262" << std::endl; + portduino_status.hardwareModel = meshtastic_HardwareModel_MESHSTICK_1262; + } } } } diff --git a/src/platform/portduino/PortduinoGlue.h b/src/platform/portduino/PortduinoGlue.h index eb4eb8925..af511be6e 100644 --- a/src/platform/portduino/PortduinoGlue.h +++ b/src/platform/portduino/PortduinoGlue.h @@ -1,15 +1,22 @@ #pragma once #include #include +#include #include #include #include "LR11x0Interface.h" #include "Module.h" #include "mesh/generated/meshtastic/mesh.pb.h" -#include "platform/portduino/USBHal.h" #include "yaml-cpp/yaml.h" +extern struct portduino_status_struct { + bool LoRa_in_error = false; + _meshtastic_HardwareModel hardwareModel = meshtastic_HardwareModel_PORTDUINO; +} portduino_status; + +#include "platform/portduino/USBHal.h" + // Product strings for auto-configuration // {"PRODUCT_STRING", "CONFIG.YAML"} // YAML paths are relative to `meshtastic/available.d` diff --git a/src/platform/portduino/USBHal.h b/src/platform/portduino/USBHal.h index ecc292430..441f75b10 100644 --- a/src/platform/portduino/USBHal.h +++ b/src/platform/portduino/USBHal.h @@ -9,6 +9,8 @@ #include #include +extern uint32_t rebootAtMsec; + // include the library for Raspberry GPIO pins #define PI_RISING (PINEDIO_INT_MODE_RISING) @@ -45,7 +47,7 @@ class Ch341Hal : public RadioLibHal int32_t ret = pinedio_init(&pinedio, NULL); if (ret != 0) { std::string s = "Could not open SPI: "; - throw(s + std::to_string(ret)); + throw std::runtime_error(s + std::to_string(ret)); } pinedio_set_option(&pinedio, PINEDIO_OPTION_AUTO_CS, 0); @@ -74,30 +76,55 @@ class Ch341Hal : public RadioLibHal // RADIOLIB_NC as an alias for non-connected pins void pinMode(uint32_t pin, uint32_t mode) override { + if (checkError()) { + return; + } if (pin == RADIOLIB_NC) { return; } - pinedio_set_pin_mode(&pinedio, pin, mode); + auto res = pinedio_set_pin_mode(&pinedio, pin, mode); + if (res < 0 && rebootAtMsec == 0) { + LOG_ERROR("USBHal pinMode: Could not set pin %u mode to %u: %d", pin, mode, res); + } } void digitalWrite(uint32_t pin, uint32_t value) override { + if (checkError()) { + return; + } if (pin == RADIOLIB_NC) { return; } - pinedio_digital_write(&pinedio, pin, value); + auto res = pinedio_digital_write(&pinedio, pin, value); + if (res < 0 && rebootAtMsec == 0) { + LOG_ERROR("USBHal digitalWrite: Could not write pin %u: %d", pin, res); + portduino_status.LoRa_in_error = true; + } } uint32_t digitalRead(uint32_t pin) override { + if (checkError()) { + return 0; + } if (pin == RADIOLIB_NC) { return 0; } - return pinedio_digital_read(&pinedio, pin); + auto res = pinedio_digital_read(&pinedio, pin); + if (res < 0 && rebootAtMsec == 0) { + LOG_ERROR("USBHal digitalRead: Could not read pin %u: %d", pin, res); + portduino_status.LoRa_in_error = true; + return 0; + } + return res; } void attachInterrupt(uint32_t interruptNum, void (*interruptCb)(void), uint32_t mode) override { + if (checkError()) { + return; + } if (interruptNum == RADIOLIB_NC) { return; } @@ -107,6 +134,9 @@ class Ch341Hal : public RadioLibHal void detachInterrupt(uint32_t interruptNum) override { + if (checkError()) { + return; + } if (interruptNum == RADIOLIB_NC) { return; } @@ -152,6 +182,9 @@ class Ch341Hal : public RadioLibHal void spiTransfer(uint8_t *out, size_t len, uint8_t *in) { + if (checkError()) { + return; + } int32_t ret = pinedio_transceive(&this->pinedio, out, in, len); if (ret < 0) { std::cerr << "Could not perform SPI transfer: " << ret << std::endl; @@ -160,9 +193,22 @@ class Ch341Hal : public RadioLibHal void spiEndTransaction() {} void spiEnd() {} + bool checkError() + { + if (pinedio.in_error) { + if (!has_warned) + LOG_ERROR("USBHal: libch341 in_error detected"); + portduino_status.LoRa_in_error = true; + has_warned = true; + return true; + } + has_warned = false; + return false; + } private: pinedio_inst pinedio = {0}; + bool has_warned = false; }; #endif diff --git a/src/platform/portduino/architecture.h b/src/platform/portduino/architecture.h index e10519d21..9ee8ad366 100644 --- a/src/platform/portduino/architecture.h +++ b/src/platform/portduino/architecture.h @@ -6,7 +6,7 @@ // set HW_VENDOR // -#define HW_VENDOR meshtastic_HardwareModel_PORTDUINO +#define HW_VENDOR portduino_status.hardwareModel #ifndef HAS_BUTTON #define HAS_BUTTON 1 diff --git a/variants/native/portduino.ini b/variants/native/portduino.ini index cc6c39aa2..55905481a 100644 --- a/variants/native/portduino.ini +++ b/variants/native/portduino.ini @@ -29,7 +29,7 @@ lib_deps = # renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX lovyan03/LovyanGFX@1.2.7 # renovate: datasource=git-refs depName=libch341-spi-userspace packageName=https://github.com/pine64/libch341-spi-userspace gitBranch=main - https://github.com/pine64/libch341-spi-userspace/archive/af9bc27c9c30fa90772279925b7c5913dff789b4.zip + https://github.com/pine64/libch341-spi-userspace/archive/23c42319a69cffcb65868e3c72e6bed83974a393.zip # renovate: datasource=custom.pio depName=adafruit/Adafruit seesaw Library packageName=adafruit/library/Adafruit seesaw Library adafruit/Adafruit seesaw Library@1.7.9 # renovate: datasource=git-refs depName=RAK12034-BMX160 packageName=https://github.com/RAKWireless/RAK12034-BMX160 gitBranch=main From 4eb4c4b584f529fd55c796bbb6aae9d2377fd470 Mon Sep 17 00:00:00 2001 From: Jason P Date: Tue, 27 Jan 2026 20:11:56 -0600 Subject: [PATCH 55/99] BaseUI Message Bubble Improvements (#9452) * Improve Message bubbles for more distinct markers and improved layout * Tune message bubble size and corner markers * Finish message bubble tuning --------- Co-authored-by: HarukiToreda <116696711+HarukiToreda@users.noreply.github.com> --- src/graphics/draw/MessageRenderer.cpp | 83 +++++++++------------------ 1 file changed, 27 insertions(+), 56 deletions(-) diff --git a/src/graphics/draw/MessageRenderer.cpp b/src/graphics/draw/MessageRenderer.cpp index 72746e415..193164439 100644 --- a/src/graphics/draw/MessageRenderer.cpp +++ b/src/graphics/draw/MessageRenderer.cpp @@ -431,45 +431,6 @@ static int getDrawnLinePixelBottom(int lineTopY, const std::string &line, bool i return iconTop + tallest - 1; } -static void drawRoundedRectOutline(OLEDDisplay *display, int x, int y, int w, int h, int r) -{ - if (w <= 1 || h <= 1) - return; - - if (r < 0) - r = 0; - - int maxR = (std::min(w, h) / 2) - 1; - if (r > maxR) - r = maxR; - - if (r == 0) { - display->drawRect(x, y, w, h); - return; - } - - const int x0 = x; - const int y0 = y; - const int x1 = x + w - 1; - const int y1 = y + h - 1; - - // sides - if (x0 + r <= x1 - r) { - display->drawLine(x0 + r, y0, x1 - r, y0); // top - display->drawLine(x0 + r, y1, x1 - r, y1); // bottom - } - if (y0 + r <= y1 - r) { - display->drawLine(x0, y0 + r, x0, y1 - r); // left - display->drawLine(x1, y0 + r, x1, y1 - r); // right - } - - // corner arcs - display->drawCircleQuads(x0 + r, y0 + r, r, 2); // top left - display->drawCircleQuads(x1 - r, y0 + r, r, 1); // top right - display->drawCircleQuads(x1 - r, y1 - r, r, 8); // bottom right - display->drawCircleQuads(x0 + r, y1 - r, r, 4); // bottom left -} - static std::vector buildMessageBlocks(const std::vector &isHeaderVec, const std::vector &isMineVec) { std::vector blocks; @@ -909,27 +870,37 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16 bubbleW = std::max(1, rightEdge - bubbleX); if (bubbleW > 1 && bubbleH > 1) { - int r = BUBBLE_RADIUS; - int maxR = (std::min(bubbleW, bubbleH) / 2) - 1; - if (maxR < 0) - maxR = 0; - if (r > maxR) - r = maxR; - - drawRoundedRectOutline(display, bubbleX, topY, bubbleW, bubbleH, r); - const int extra = 3; - const int rr = r + extra; int x1 = bubbleX + bubbleW - 1; int y1 = topY + bubbleH - 1; - if (!b.mine) { - // top-left corner square - display->drawLine(bubbleX, topY, bubbleX + rr, topY); - display->drawLine(bubbleX, topY, bubbleX, topY + rr); + if (b.mine) { + // Send Message (Right side) + display->drawRect(x1 + 2 - bubbleW, y1 - bubbleH, bubbleW, bubbleH); + // Top Right Corner + display->drawRect(x1, topY, 2, 1); + display->drawRect(x1, topY, 1, 2); + // Bottom Right Corner + display->drawRect(x1 - 1, bottomY - 2, 2, 1); + display->drawRect(x1, bottomY - 3, 1, 2); + // Knock the corners off to make a bubble + display->setColor(BLACK); + display->drawRect(x1 - bubbleW, topY - 1, 1, 1); + display->drawRect(x1 - bubbleW, bottomY - 1, 1, 1); + display->setColor(WHITE); } else { - // bottom-right corner square - display->drawLine(x1 - rr, y1, x1, y1); - display->drawLine(x1, y1 - rr, x1, y1); + // Received Message (Left Side) + display->drawRect(bubbleX, topY, bubbleW + 1, bubbleH); + // Top Left Corner + display->drawRect(bubbleX + 1, topY + 1, 2, 1); + display->drawRect(bubbleX + 1, topY + 1, 1, 2); + // Bottom Left Corner + display->drawRect(bubbleX + 1, bottomY - 1, 2, 1); + display->drawRect(bubbleX + 1, bottomY - 2, 1, 2); + // Knock the corners off to make a bubble + display->setColor(BLACK); + display->drawRect(bubbleX + bubbleW, topY, 1, 1); + display->drawRect(bubbleX + bubbleW, bottomY, 1, 1); + display->setColor(WHITE); } } } From d0562e1ee657dec366751a10ff8b6955b2b12d13 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Wed, 28 Jan 2026 10:50:56 -0600 Subject: [PATCH 56/99] Add model workflows (#9462) * Add GitHub workflows for issue completeness, duplicate detection, onboarding, and contribution quality checks * Fix indentation * Refactor GitHub workflows for issue handling * Consolidate to two triage workflows * Update .github/workflows/models_pr_triage.yml Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .github/workflows/models_issue_triage.yml | 204 ++++++++++++++++++++++ .github/workflows/models_pr_triage.yml | 138 +++++++++++++++ 2 files changed, 342 insertions(+) create mode 100644 .github/workflows/models_issue_triage.yml create mode 100644 .github/workflows/models_pr_triage.yml diff --git a/.github/workflows/models_issue_triage.yml b/.github/workflows/models_issue_triage.yml new file mode 100644 index 000000000..f61a15fe6 --- /dev/null +++ b/.github/workflows/models_issue_triage.yml @@ -0,0 +1,204 @@ +name: Issue Triage (Models) + +on: + issues: + types: [opened] + +permissions: + issues: write + models: read + +concurrency: + group: ${{ github.workflow }}-${{ github.event.issue.number }} + cancel-in-progress: true + +jobs: + triage: + if: ${{ github.repository == 'meshtastic/firmware' && github.event.issue.user.type != 'Bot' }} + runs-on: ubuntu-latest + steps: + # ───────────────────────────────────────────────────────────────────────── + # Step 1: Quality check (spam/AI-slop detection) - runs first, exits early if spam + # ───────────────────────────────────────────────────────────────────────── + - name: Detect spam or low-quality content + uses: actions/ai-inference@v2 + id: quality + continue-on-error: true + with: + max-tokens: 20 + prompt: | + Is this GitHub issue spam, AI-generated slop, or low quality? + + Title: ${{ github.event.issue.title }} + Body: ${{ github.event.issue.body }} + + Respond with exactly one of: spam, ai-generated, needs-review, ok + system-prompt: You detect spam and low-quality contributions. Be conservative - only flag obvious spam or AI slop. + model: openai/gpt-4o-mini + + - name: Apply quality label if needed + if: steps.quality.outputs.response != '' && steps.quality.outputs.response != 'ok' + uses: actions/github-script@v8 + env: + QUALITY_LABEL: ${{ steps.quality.outputs.response }} + with: + script: | + const label = (process.env.QUALITY_LABEL || '').trim().toLowerCase(); + const labelMeta = { + 'spam': { color: 'd73a4a', description: 'Possible spam' }, + 'ai-generated': { color: 'fbca04', description: 'Possible AI-generated low-quality content' }, + 'needs-review': { color: 'f9d0c4', description: 'Needs human review' }, + }; + const meta = labelMeta[label]; + if (!meta) return; + + // Ensure label exists + try { + await github.rest.issues.getLabel({ owner: context.repo.owner, repo: context.repo.repo, name: label }); + } catch (e) { + if (e.status !== 404) throw e; + await github.rest.issues.createLabel({ owner: context.repo.owner, repo: context.repo.repo, name: label, color: meta.color, description: meta.description }); + } + + // Apply label + await github.rest.issues.addLabels({ owner: context.repo.owner, repo: context.repo.repo, issue_number: context.payload.issue.number, labels: [label] }); + + // Set output to skip remaining steps + core.setOutput('is_spam', 'true'); + + # ───────────────────────────────────────────────────────────────────────── + # Step 2: Duplicate detection - only if not spam + # ───────────────────────────────────────────────────────────────────────── + - name: Detect duplicate issues + if: steps.quality.outputs.response == 'ok' || steps.quality.outputs.response == '' + uses: pelikhan/action-genai-issue-dedup@bdb3b5d9451c1090ffcdf123d7447a5e7c7a2528 # v0.0.19 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + + # ───────────────────────────────────────────────────────────────────────── + # Step 3: Completeness check + auto-labeling (combined into one AI call) + # ───────────────────────────────────────────────────────────────────────── + - name: Determine if completeness check should be skipped + if: steps.quality.outputs.response == 'ok' || steps.quality.outputs.response == '' + uses: actions/github-script@v8 + id: check-skip + with: + script: | + const title = (context.payload.issue.title || '').toLowerCase(); + const labels = (context.payload.issue.labels || []).map(label => label.name); + const hasFeatureRequest = title.includes('feature request'); + const hasEnhancement = labels.includes('enhancement'); + const shouldSkip = hasFeatureRequest && hasEnhancement; + core.setOutput('should_skip', shouldSkip ? 'true' : 'false'); + + - name: Analyze issue completeness and determine labels + if: (steps.quality.outputs.response == 'ok' || steps.quality.outputs.response == '') && steps.check-skip.outputs.should_skip != 'true' + uses: actions/ai-inference@v2 + id: analysis + continue-on-error: true + with: + prompt: | + Analyze this GitHub issue for completeness and determine if it needs labels. + + If this looks like a bug on the device/firmware (crash, reboot, lockup, radio issues, GPS issues, display issues, power/sleep issues), request device logs and explain how to get them: + + Web Flasher logs: + - Go to https://flasher.meshtastic.org + - Connect the device via USB and click Connect + - Open the device console/log output, reproduce the problem, then copy/download and attach/paste the logs + + Meshtastic CLI logs: + - Run: meshtastic --port --noproto + - Reproduce the problem, then copy/paste the terminal output + + Also request key context if missing: device model/variant, firmware version, region, steps to reproduce, expected vs actual. + + Respond ONLY with JSON: + { + "complete": true|false, + "comment": "Your helpful comment requesting missing info, or empty string if complete", + "label": "needs-logs" | "needs-info" | "none" + } + + Use "needs-logs" if this is a device bug AND no logs are attached. + Use "needs-info" if basic info like firmware version or steps to reproduce are missing. + Use "none" if the issue is complete or is a feature request. + + Title: ${{ github.event.issue.title }} + Body: ${{ github.event.issue.body }} + system-prompt: You are a helpful assistant that triages GitHub issues. Be conservative with labels. + model: openai/gpt-4o-mini + + - name: Process analysis result + if: (steps.quality.outputs.response == 'ok' || steps.quality.outputs.response == '') && steps.check-skip.outputs.should_skip != 'true' && steps.analysis.outputs.response != '' + uses: actions/github-script@v8 + id: process + env: + AI_RESPONSE: ${{ steps.analysis.outputs.response }} + with: + script: | + const raw = (process.env.AI_RESPONSE || '').trim(); + + let complete = false; + let comment = ''; + let label = 'none'; + + try { + const parsed = JSON.parse(raw); + complete = !!parsed.complete; + comment = (parsed.comment ?? '').toString().trim(); + label = (parsed.label ?? 'none').toString().trim().toLowerCase(); + } catch { + // If JSON parse fails, treat as incomplete with raw response as comment + complete = false; + comment = raw; + label = 'none'; + } + + // Validate label + const allowedLabels = new Set(['needs-logs', 'needs-info', 'none']); + if (!allowedLabels.has(label)) label = 'none'; + + core.setOutput('should_comment', (!complete && comment.length > 0) ? 'true' : 'false'); + core.setOutput('comment_body', comment); + core.setOutput('label', label); + + - name: Apply triage label + if: steps.process.outputs.label != '' && steps.process.outputs.label != 'none' + uses: actions/github-script@v8 + env: + LABEL_NAME: ${{ steps.process.outputs.label }} + with: + script: | + const label = process.env.LABEL_NAME; + const labelMeta = { + 'needs-logs': { color: 'cfd3d7', description: 'Device logs requested for triage' }, + 'needs-info': { color: 'f9d0c4', description: 'More information requested for triage' }, + }; + const meta = labelMeta[label]; + if (!meta) return; + + // Ensure label exists + try { + await github.rest.issues.getLabel({ owner: context.repo.owner, repo: context.repo.repo, name: label }); + } catch (e) { + if (e.status !== 404) throw e; + await github.rest.issues.createLabel({ owner: context.repo.owner, repo: context.repo.repo, name: label, color: meta.color, description: meta.description }); + } + + // Apply label + await github.rest.issues.addLabels({ owner: context.repo.owner, repo: context.repo.repo, issue_number: context.payload.issue.number, labels: [label] }); + + - name: Comment on issue + if: steps.process.outputs.should_comment == 'true' + uses: actions/github-script@v8 + env: + COMMENT_BODY: ${{ steps.process.outputs.comment_body }} + with: + script: | + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.payload.issue.number, + body: process.env.COMMENT_BODY + }); diff --git a/.github/workflows/models_pr_triage.yml b/.github/workflows/models_pr_triage.yml new file mode 100644 index 000000000..ef303c02a --- /dev/null +++ b/.github/workflows/models_pr_triage.yml @@ -0,0 +1,138 @@ +name: PR Triage (Models) + +on: + pull_request_target: + types: [opened] + +permissions: + pull-requests: write + issues: write + models: read + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number }} + cancel-in-progress: true + +jobs: + triage: + if: ${{ github.repository == 'meshtastic/firmware' && github.event.pull_request.user.type != 'Bot' }} + runs-on: ubuntu-latest + steps: + # ───────────────────────────────────────────────────────────────────────── + # Step 1: Check if PR already has automation/type labels (skip if so) + # ───────────────────────────────────────────────────────────────────────── + - name: Check existing labels + uses: actions/github-script@v8 + id: check-labels + with: + script: | + const skipLabels = new Set(['automation']); + const typeLabels = new Set(['bugfix', 'hardware-support', 'enhancement', 'dependencies', 'submodules', 'github_actions', 'trunk', 'cleanup']); + const prLabels = context.payload.pull_request.labels.map(l => l.name); + + const shouldSkipAll = prLabels.some(l => skipLabels.has(l)); + const hasTypeLabel = prLabels.some(l => typeLabels.has(l)); + + core.setOutput('skip_all', shouldSkipAll ? 'true' : 'false'); + core.setOutput('has_type_label', hasTypeLabel ? 'true' : 'false'); + + # ───────────────────────────────────────────────────────────────────────── + # Step 2: Quality check (spam/AI-slop detection) + # ───────────────────────────────────────────────────────────────────────── + - name: Detect spam or low-quality content + if: steps.check-labels.outputs.skip_all != 'true' + uses: actions/ai-inference@v2 + id: quality + continue-on-error: true + with: + max-tokens: 20 + prompt: | + Is this GitHub pull request spam, AI-generated slop, or low quality? + + Title: ${{ github.event.pull_request.title }} + Body: ${{ github.event.pull_request.body }} + + Respond with exactly one of: spam, ai-generated, needs-review, ok + system-prompt: You detect spam and low-quality contributions. Be conservative - only flag obvious spam or AI slop. + model: openai/gpt-4o-mini + + - name: Apply quality label if needed + if: steps.check-labels.outputs.skip_all != 'true' && steps.quality.outputs.response != '' && steps.quality.outputs.response != 'ok' + uses: actions/github-script@v8 + id: quality-label + env: + QUALITY_LABEL: ${{ steps.quality.outputs.response }} + with: + script: | + const label = (process.env.QUALITY_LABEL || '').trim().toLowerCase(); + const labelMeta = { + 'spam': { color: 'd73a4a', description: 'Possible spam' }, + 'ai-generated': { color: 'fbca04', description: 'Possible AI-generated low-quality content' }, + 'needs-review': { color: 'f9d0c4', description: 'Needs human review' }, + }; + const meta = labelMeta[label]; + if (!meta) return; + + // Ensure label exists + try { + await github.rest.issues.getLabel({ owner: context.repo.owner, repo: context.repo.repo, name: label }); + } catch (e) { + if (e.status !== 404) throw e; + await github.rest.issues.createLabel({ owner: context.repo.owner, repo: context.repo.repo, name: label, color: meta.color, description: meta.description }); + } + + // Apply label + await github.rest.issues.addLabels({ owner: context.repo.owner, repo: context.repo.repo, issue_number: context.payload.pull_request.number, labels: [label] }); + + core.setOutput('is_spam', 'true'); + + # ───────────────────────────────────────────────────────────────────────── + # Step 3: Auto-label PR type (bugfix/hardware-support/enhancement) + # ───────────────────────────────────────────────────────────────────────── + - name: Classify PR for labeling + if: steps.check-labels.outputs.skip_all != 'true' && steps.check-labels.outputs.has_type_label != 'true' && (steps.quality.outputs.response == 'ok' || steps.quality.outputs.response == '') + uses: actions/ai-inference@v2 + id: classify + continue-on-error: true + with: + max-tokens: 30 + prompt: | + Classify this pull request into exactly one category. + + Return exactly one of: bugfix, hardware-support, enhancement + + Use bugfix if it fixes a bug, crash, or incorrect behavior. + Use hardware-support if it adds or improves support for a specific hardware device/variant. + Use enhancement if it adds a new feature, improves performance, or refactors code. + + Title: ${{ github.event.pull_request.title }} + Body: ${{ github.event.pull_request.body }} + system-prompt: You classify pull requests into categories. Be conservative and pick the most appropriate single label. + model: openai/gpt-4o-mini + + - name: Apply type label + if: steps.check-labels.outputs.skip_all != 'true' && steps.check-labels.outputs.has_type_label != 'true' && steps.classify.outputs.response != '' + uses: actions/github-script@v8 + env: + TYPE_LABEL: ${{ steps.classify.outputs.response }} + with: + script: | + const label = (process.env.TYPE_LABEL || '').trim().toLowerCase(); + const labelMeta = { + 'bugfix': { color: 'd73a4a', description: 'Bug fix' }, + 'hardware-support': { color: '0e8a16', description: 'Hardware support addition or improvement' }, + 'enhancement': { color: 'a2eeef', description: 'New feature or enhancement' }, + }; + const meta = labelMeta[label]; + if (!meta) return; + + // Ensure label exists + try { + await github.rest.issues.getLabel({ owner: context.repo.owner, repo: context.repo.repo, name: label }); + } catch (e) { + if (e.status !== 404) throw e; + await github.rest.issues.createLabel({ owner: context.repo.owner, repo: context.repo.repo, name: label, color: meta.color, description: meta.description }); + } + + // Apply label + await github.rest.issues.addLabels({ owner: context.repo.owner, repo: context.repo.repo, issue_number: context.payload.pull_request.number, labels: [label] }); From c1e3f56324eeb2d64c2818ee6377af697575fbc0 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 28 Jan 2026 11:08:07 -0600 Subject: [PATCH 57/99] Update LovyanGFX to v1.2.19 (#9405) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- variants/esp32/chatter2/platformio.ini | 2 +- variants/esp32/m5stack_core/platformio.ini | 2 +- variants/esp32/wiphone/platformio.ini | 2 +- variants/esp32s3/heltec_v4/platformio.ini | 2 +- variants/esp32s3/heltec_wireless_tracker/platformio.ini | 2 +- variants/esp32s3/heltec_wireless_tracker_V1_0/platformio.ini | 2 +- variants/esp32s3/heltec_wireless_tracker_v2/platformio.ini | 2 +- variants/esp32s3/mesh-tab/platformio.ini | 2 +- variants/esp32s3/picomputer-s3/platformio.ini | 2 +- variants/esp32s3/rak_wismesh_tap_v2/platformio.ini | 2 +- variants/esp32s3/t-deck/platformio.ini | 2 +- variants/esp32s3/t-watch-s3/platformio.ini | 2 +- variants/esp32s3/tlora-pager/platformio.ini | 2 +- variants/esp32s3/tracksenger/platformio.ini | 4 ++-- variants/esp32s3/unphone/platformio.ini | 2 +- variants/native/portduino.ini | 2 +- 16 files changed, 17 insertions(+), 17 deletions(-) diff --git a/variants/esp32/chatter2/platformio.ini b/variants/esp32/chatter2/platformio.ini index 4218e8503..a14e407a1 100644 --- a/variants/esp32/chatter2/platformio.ini +++ b/variants/esp32/chatter2/platformio.ini @@ -12,4 +12,4 @@ build_flags = lib_deps = ${esp32_base.lib_deps} # renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX - lovyan03/LovyanGFX@1.2.7 + lovyan03/LovyanGFX@1.2.19 diff --git a/variants/esp32/m5stack_core/platformio.ini b/variants/esp32/m5stack_core/platformio.ini index 4544d73b5..5e41d5f2b 100644 --- a/variants/esp32/m5stack_core/platformio.ini +++ b/variants/esp32/m5stack_core/platformio.ini @@ -36,4 +36,4 @@ lib_ignore = lib_deps = ${esp32_base.lib_deps} # renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX - lovyan03/LovyanGFX@1.2.7 + lovyan03/LovyanGFX@1.2.19 diff --git a/variants/esp32/wiphone/platformio.ini b/variants/esp32/wiphone/platformio.ini index fcf36a23c..fbd77be75 100644 --- a/variants/esp32/wiphone/platformio.ini +++ b/variants/esp32/wiphone/platformio.ini @@ -11,7 +11,7 @@ build_flags = lib_deps = ${esp32_base.lib_deps} # renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX - lovyan03/LovyanGFX@1.2.7 + lovyan03/LovyanGFX@1.2.19 # renovate: datasource=custom.pio depName=SX1509 IO Expander packageName=sparkfun/library/SX1509 IO Expander sparkfun/SX1509 IO Expander@3.0.6 # renovate: datasource=custom.pio depName=APA102 packageName=pololu/library/APA102 diff --git a/variants/esp32s3/heltec_v4/platformio.ini b/variants/esp32s3/heltec_v4/platformio.ini index 4495a409f..20b3b4606 100644 --- a/variants/esp32s3/heltec_v4/platformio.ini +++ b/variants/esp32s3/heltec_v4/platformio.ini @@ -126,7 +126,7 @@ build_flags = lib_deps = ${heltec_v4_base.lib_deps} ; ${device-ui_base.lib_deps} # renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX - lovyan03/LovyanGFX@1.2.0 + lovyan03/LovyanGFX@1.2.19 # renovate: datasource=git-refs depName=Quency-D_chsc6x packageName=https://github.com/Quency-D/chsc6x gitBranch=master https://github.com/Quency-D/chsc6x/archive/5cbead829d6b432a8d621ed1aafd4eb474fd4f27.zip ; TODO revert to official device-ui (when merged) diff --git a/variants/esp32s3/heltec_wireless_tracker/platformio.ini b/variants/esp32s3/heltec_wireless_tracker/platformio.ini index c2dab0c93..33643c541 100644 --- a/variants/esp32s3/heltec_wireless_tracker/platformio.ini +++ b/variants/esp32s3/heltec_wireless_tracker/platformio.ini @@ -24,4 +24,4 @@ build_flags = lib_deps = ${esp32s3_base.lib_deps} # renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX - lovyan03/LovyanGFX@1.2.7 + lovyan03/LovyanGFX@1.2.19 diff --git a/variants/esp32s3/heltec_wireless_tracker_V1_0/platformio.ini b/variants/esp32s3/heltec_wireless_tracker_V1_0/platformio.ini index efeb63b8e..ab6592afb 100644 --- a/variants/esp32s3/heltec_wireless_tracker_V1_0/platformio.ini +++ b/variants/esp32s3/heltec_wireless_tracker_V1_0/platformio.ini @@ -22,4 +22,4 @@ build_flags = lib_deps = ${esp32s3_base.lib_deps} # renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX - lovyan03/LovyanGFX@1.2.7 + lovyan03/LovyanGFX@1.2.19 diff --git a/variants/esp32s3/heltec_wireless_tracker_v2/platformio.ini b/variants/esp32s3/heltec_wireless_tracker_v2/platformio.ini index 0b486618b..a5277ba19 100644 --- a/variants/esp32s3/heltec_wireless_tracker_v2/platformio.ini +++ b/variants/esp32s3/heltec_wireless_tracker_v2/platformio.ini @@ -20,4 +20,4 @@ build_flags = lib_deps = ${esp32s3_base.lib_deps} # renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX - lovyan03/LovyanGFX@1.2.7 + lovyan03/LovyanGFX@1.2.19 diff --git a/variants/esp32s3/mesh-tab/platformio.ini b/variants/esp32s3/mesh-tab/platformio.ini index 21d8fb432..ecf5498b3 100644 --- a/variants/esp32s3/mesh-tab/platformio.ini +++ b/variants/esp32s3/mesh-tab/platformio.ini @@ -51,7 +51,7 @@ lib_deps = ${esp32s3_base.lib_deps} ${device-ui_base.lib_deps} # renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX - lovyan03/LovyanGFX@1.2.7 + lovyan03/LovyanGFX@1.2.19 [mesh_tab_xpt2046] extends = mesh_tab_base diff --git a/variants/esp32s3/picomputer-s3/platformio.ini b/variants/esp32s3/picomputer-s3/platformio.ini index bef7b19a0..82147f222 100644 --- a/variants/esp32s3/picomputer-s3/platformio.ini +++ b/variants/esp32s3/picomputer-s3/platformio.ini @@ -24,7 +24,7 @@ build_flags = lib_deps = ${esp32s3_base.lib_deps} # renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX - lovyan03/LovyanGFX@1.2.7 + lovyan03/LovyanGFX@1.2.19 build_src_filter = ${esp32s3_base.build_src_filter} diff --git a/variants/esp32s3/rak_wismesh_tap_v2/platformio.ini b/variants/esp32s3/rak_wismesh_tap_v2/platformio.ini index e4ff9812c..8423bb4df 100644 --- a/variants/esp32s3/rak_wismesh_tap_v2/platformio.ini +++ b/variants/esp32s3/rak_wismesh_tap_v2/platformio.ini @@ -16,7 +16,7 @@ build_flags = lib_deps = ${esp32s3_base.lib_deps} # renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX - lovyan03/LovyanGFX@1.2.7 + lovyan03/LovyanGFX@1.2.19 [ft5x06] extends = mesh_tab_base diff --git a/variants/esp32s3/t-deck/platformio.ini b/variants/esp32s3/t-deck/platformio.ini index 58335796a..5a96a3839 100644 --- a/variants/esp32s3/t-deck/platformio.ini +++ b/variants/esp32s3/t-deck/platformio.ini @@ -24,7 +24,7 @@ build_flags = ${esp32s3_base.build_flags} lib_deps = ${esp32s3_base.lib_deps} # renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX - lovyan03/LovyanGFX@1.2.7 + lovyan03/LovyanGFX@1.2.19 # renovate: datasource=custom.pio depName=ESP8266Audio packageName=earlephilhower/library/ESP8266Audio earlephilhower/ESP8266Audio@1.9.9 # renovate: datasource=custom.pio depName=ESP8266SAM packageName=earlephilhower/library/ESP8266SAM diff --git a/variants/esp32s3/t-watch-s3/platformio.ini b/variants/esp32s3/t-watch-s3/platformio.ini index 9c7a642b2..8c79ca097 100644 --- a/variants/esp32s3/t-watch-s3/platformio.ini +++ b/variants/esp32s3/t-watch-s3/platformio.ini @@ -22,7 +22,7 @@ build_flags = ${esp32s3_base.build_flags} lib_deps = ${esp32s3_base.lib_deps} # renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX - lovyan03/LovyanGFX@1.2.7 + lovyan03/LovyanGFX@1.2.19 # renovate: datasource=custom.pio depName=SensorLib packageName=lewisxhe/library/SensorLib lewisxhe/SensorLib@0.3.4 # renovate: datasource=custom.pio depName=Adafruit DRV2605 packageName=adafruit/library/Adafruit DRV2605 Library diff --git a/variants/esp32s3/tlora-pager/platformio.ini b/variants/esp32s3/tlora-pager/platformio.ini index ea6369f29..cf8be67fb 100644 --- a/variants/esp32s3/tlora-pager/platformio.ini +++ b/variants/esp32s3/tlora-pager/platformio.ini @@ -29,7 +29,7 @@ build_flags = ${esp32s3_base.build_flags} lib_deps = ${esp32s3_base.lib_deps} # renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX - lovyan03/LovyanGFX@1.2.7 + lovyan03/LovyanGFX@1.2.19 # renovate: datasource=custom.pio depName=ESP8266Audio packageName=earlephilhower/library/ESP8266Audio earlephilhower/ESP8266Audio@1.9.9 # renovate: datasource=custom.pio depName=ESP8266SAM packageName=earlephilhower/library/ESP8266SAM diff --git a/variants/esp32s3/tracksenger/platformio.ini b/variants/esp32s3/tracksenger/platformio.ini index 419a3539b..c006cf835 100644 --- a/variants/esp32s3/tracksenger/platformio.ini +++ b/variants/esp32s3/tracksenger/platformio.ini @@ -22,7 +22,7 @@ build_flags = lib_deps = ${esp32s3_base.lib_deps} # renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX - lovyan03/LovyanGFX@1.2.7 + lovyan03/LovyanGFX@1.2.19 [env:tracksenger-lcd] custom_meshtastic_hw_model = 48 @@ -48,7 +48,7 @@ build_flags = lib_deps = ${esp32s3_base.lib_deps} # renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX - lovyan03/LovyanGFX@1.2.7 + lovyan03/LovyanGFX@1.2.19 [env:tracksenger-oled] custom_meshtastic_hw_model = 48 diff --git a/variants/esp32s3/unphone/platformio.ini b/variants/esp32s3/unphone/platformio.ini index 28be1f3e1..924dfa74f 100644 --- a/variants/esp32s3/unphone/platformio.ini +++ b/variants/esp32s3/unphone/platformio.ini @@ -37,7 +37,7 @@ build_src_filter = lib_deps = ${esp32s3_base.lib_deps} # renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX - lovyan03/LovyanGFX@1.2.0 + lovyan03/LovyanGFX@1.2.19 # TODO renovate https://gitlab.com/hamishcunningham/unphonelibrary#meshtastic@9.0.0 # renovate: datasource=custom.pio depName=NeoPixel packageName=adafruit/library/Adafruit NeoPixel diff --git a/variants/native/portduino.ini b/variants/native/portduino.ini index cc6c39aa2..f99ee9a6f 100644 --- a/variants/native/portduino.ini +++ b/variants/native/portduino.ini @@ -27,7 +27,7 @@ lib_deps = # renovate: datasource=custom.pio depName=rweather/Crypto packageName=rweather/library/Crypto rweather/Crypto@0.4.0 # renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX - lovyan03/LovyanGFX@1.2.7 + lovyan03/LovyanGFX@1.2.19 # renovate: datasource=git-refs depName=libch341-spi-userspace packageName=https://github.com/pine64/libch341-spi-userspace gitBranch=main https://github.com/pine64/libch341-spi-userspace/archive/af9bc27c9c30fa90772279925b7c5913dff789b4.zip # renovate: datasource=custom.pio depName=adafruit/Adafruit seesaw Library packageName=adafruit/library/Adafruit seesaw Library From a922751afc1b87a4e8c21ff94440a221e580233c Mon Sep 17 00:00:00 2001 From: Jason P Date: Wed, 28 Jan 2026 11:12:02 -0600 Subject: [PATCH 58/99] External Notification - handleReceived Rewrite (#9454) * First steps in consolidating code and minimizing rewrite * Continuing code cleanup * Merge containsBell and !isMuted to a single code path * Forgot about alert_message_buzzer in the cleanup * More code refinements and cleanup * Fix nagCycleCutoff * CoPilot Updates --- src/modules/ExternalNotificationModule.cpp | 126 +++++++-------------- 1 file changed, 38 insertions(+), 88 deletions(-) diff --git a/src/modules/ExternalNotificationModule.cpp b/src/modules/ExternalNotificationModule.cpp index 8b7ce700a..5e6985bdf 100644 --- a/src/modules/ExternalNotificationModule.cpp +++ b/src/modules/ExternalNotificationModule.cpp @@ -442,6 +442,7 @@ ExternalNotificationModule::ExternalNotificationModule() ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshPacket &mp) { + // Trigger external notification if enabled and not muted; isSilenced is from temporary mute toggles if (moduleConfig.external_notification.enabled && !isSilenced) { #ifdef T_WATCH_S3 drv.setWaveform(0, 75); @@ -456,6 +457,7 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP for (size_t i = 0; i < p.payload.size; i++) { if (p.payload.bytes[i] == ASCII_BELL) { containsBell = true; + break; } } @@ -465,90 +467,47 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP // If we receive a broadcast message, apply channel mute setting // If we receive a direct message and the receipent is us, apply DM mute setting // Else we just handle it as not muted. - const bool directToUs = !isBroadcast(mp.to) && isToUs(&mp); - bool is_muted = directToUs ? (sender && ((sender->bitfield & NODEINFO_BITFIELD_IS_MUTED_MASK) != 0)) - : (ch.settings.has_module_settings && ch.settings.module_settings.is_muted); + const bool isDmToUs = !isBroadcast(mp.to) && isToUs(&mp); + bool is_muted = isDmToUs ? (sender && ((sender->bitfield & NODEINFO_BITFIELD_IS_MUTED_MASK) != 0)) + : (ch.settings.has_module_settings && ch.settings.module_settings.is_muted); - if (moduleConfig.external_notification.alert_bell) { - if (containsBell) { - LOG_INFO("externalNotificationModule - Notification Bell"); + const bool buzzerModeIsDirectOnly = + (config.device.buzzer_mode == meshtastic_Config_DeviceConfig_BuzzerMode_DIRECT_MSG_ONLY); + + if (containsBell || !is_muted) { + if (moduleConfig.external_notification.alert_bell || moduleConfig.external_notification.alert_message || + moduleConfig.external_notification.alert_bell_vibra || + moduleConfig.external_notification.alert_message_vibra || + ((moduleConfig.external_notification.alert_bell_buzzer || + moduleConfig.external_notification.alert_message_buzzer) && + canBuzz())) { + nagCycleCutoff = millis() + (moduleConfig.external_notification.nag_timeout + ? (moduleConfig.external_notification.nag_timeout * 1000) + : moduleConfig.external_notification.output_ms); + LOG_INFO("Toggling nagCycleCutoff to %lu", nagCycleCutoff); isNagging = true; + } + + if (moduleConfig.external_notification.alert_bell || moduleConfig.external_notification.alert_message) { + LOG_INFO("externalNotificationModule - Notification Module or Bell"); setExternalState(0, true); - if (moduleConfig.external_notification.nag_timeout) { - nagCycleCutoff = millis() + moduleConfig.external_notification.nag_timeout * 1000; - } else { - nagCycleCutoff = millis() + moduleConfig.external_notification.output_ms; - } } - } - if (moduleConfig.external_notification.alert_bell_vibra) { - if (containsBell) { - LOG_INFO("externalNotificationModule - Notification Bell (Vibra)"); - isNagging = true; + if (moduleConfig.external_notification.alert_bell_vibra || + moduleConfig.external_notification.alert_message_vibra) { + LOG_INFO("externalNotificationModule - Notification Module or Bell (Vibra)"); setExternalState(1, true); - if (moduleConfig.external_notification.nag_timeout) { - nagCycleCutoff = millis() + moduleConfig.external_notification.nag_timeout * 1000; + } + + if ((moduleConfig.external_notification.alert_bell_buzzer || + moduleConfig.external_notification.alert_message_buzzer) && + canBuzz()) { + LOG_INFO("externalNotificationModule - Notification Module or Bell (Buzzer)"); + if (buzzerModeIsDirectOnly && !isDmToUs && !containsBell) { + LOG_INFO("Message buzzer was suppressed because buzzer mode DIRECT_MSG_ONLY"); } else { - nagCycleCutoff = millis() + moduleConfig.external_notification.output_ms; - } - } - } - - if (moduleConfig.external_notification.alert_bell_buzzer && canBuzz()) { - if (containsBell) { - LOG_INFO("externalNotificationModule - Notification Bell (Buzzer)"); - isNagging = true; - if (!moduleConfig.external_notification.use_pwm && !moduleConfig.external_notification.use_i2s_as_buzzer) { - setExternalState(2, true); - } else { -#ifdef HAS_I2S - if (moduleConfig.external_notification.use_i2s_as_buzzer) { - audioThread->beginRttl(rtttlConfig.ringtone, strlen_P(rtttlConfig.ringtone)); - } else -#endif - if (moduleConfig.external_notification.use_pwm) { - rtttl::begin(config.device.buzzer_gpio, rtttlConfig.ringtone); - } - } - if (moduleConfig.external_notification.nag_timeout) { - nagCycleCutoff = millis() + moduleConfig.external_notification.nag_timeout * 1000; - } else { - nagCycleCutoff = millis() + moduleConfig.external_notification.output_ms; - } - } - } - - if (moduleConfig.external_notification.alert_message && !is_muted) { - LOG_INFO("externalNotificationModule - Notification Module"); - isNagging = true; - setExternalState(0, true); - if (moduleConfig.external_notification.nag_timeout) { - nagCycleCutoff = millis() + moduleConfig.external_notification.nag_timeout * 1000; - } else { - nagCycleCutoff = millis() + moduleConfig.external_notification.output_ms; - } - } - - if (moduleConfig.external_notification.alert_message_vibra && !is_muted) { - LOG_INFO("externalNotificationModule - Notification Module (Vibra)"); - isNagging = true; - setExternalState(1, true); - if (moduleConfig.external_notification.nag_timeout) { - nagCycleCutoff = millis() + moduleConfig.external_notification.nag_timeout * 1000; - } else { - nagCycleCutoff = millis() + moduleConfig.external_notification.output_ms; - } - } - - if (moduleConfig.external_notification.alert_message_buzzer && !is_muted) { - LOG_INFO("externalNotificationModule - Notification Module (Buzzer)"); - if (config.device.buzzer_mode != meshtastic_Config_DeviceConfig_BuzzerMode_DIRECT_MSG_ONLY || - (!isBroadcast(mp.to) && isToUs(&mp))) { - // Buzz if buzzer mode is not in DIRECT_MSG_ONLY or is DM to us - isNagging = true; + // Buzz if buzzer mode is not in DIRECT_MSG_ONLY or is DM to us #ifdef T_LORA_PAGER - if (canBuzz()) { drv.setWaveform(0, 16); // Long buzzer 100% drv.setWaveform(1, 0); // Pause drv.setWaveform(2, 16); @@ -558,11 +517,7 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP drv.setWaveform(6, 16); drv.setWaveform(7, 0); drv.go(); - } #endif - if (!moduleConfig.external_notification.use_pwm && !moduleConfig.external_notification.use_i2s_as_buzzer) { - setExternalState(2, true); - } else { #ifdef HAS_I2S if (moduleConfig.external_notification.use_i2s_as_buzzer) { audioThread->beginRttl(rtttlConfig.ringtone, strlen_P(rtttlConfig.ringtone)); @@ -570,18 +525,13 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP #endif if (moduleConfig.external_notification.use_pwm) { rtttl::begin(config.device.buzzer_gpio, rtttlConfig.ringtone); + } else { + setExternalState(2, true); } } - if (moduleConfig.external_notification.nag_timeout) { - nagCycleCutoff = millis() + moduleConfig.external_notification.nag_timeout * 1000; - } else { - nagCycleCutoff = millis() + moduleConfig.external_notification.output_ms; - } - } else { - // Don't beep if buzzer mode is "direct messages only" and it is no direct message - LOG_INFO("Message buzzer was suppressed because buzzer mode DIRECT_MSG_ONLY"); } } + setIntervalFromNow(0); // run once so we know if we should do something } } else { From 571c1ac34c19461740236a1906b82e8c87f35f84 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Wed, 28 Jan 2026 14:08:32 -0600 Subject: [PATCH 59/99] Initial serialModule cleanup (#9465) * Initial serialModule cleanup * Move SERIAL_PRINT_PORT definition to variant.h * Add missed c6 check * Update src/modules/SerialModule.cpp Compile error for invalid SERIAL_PRINT_OBJECT value Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/modules/SerialModule.cpp | 53 ++++++++----------- variants/esp32c6/m5stack_unitc6l/variant.h | 2 + variants/esp32c6/tlora_c6/variant.h | 2 + .../esp32s3/ELECROW-ThinkNode-M5/variant.h | 2 + .../nrf52840/ELECROW-ThinkNode-M1/variant.h | 2 + .../nrf52840/ELECROW-ThinkNode-M3/variant.h | 2 + .../nrf52840/ELECROW-ThinkNode-M4/variant.h | 2 + variants/nrf52840/canaryone/variant.h | 2 + variants/nrf52840/heltec_mesh_solar/variant.h | 2 + variants/nrf52840/meshlink/variant.h | 1 + variants/nrf52840/muzi_base/variant.h | 2 + variants/nrf52840/t-echo-lite/variant.h | 2 + variants/nrf52840/t-echo-plus/variant.h | 2 + variants/nrf52840/t-echo/variant.h | 1 + variants/stm32/CDEBYTE_E77-MBL/variant.h | 2 + variants/stm32/rak3172/variant.h | 1 + 16 files changed, 48 insertions(+), 32 deletions(-) diff --git a/src/modules/SerialModule.cpp b/src/modules/SerialModule.cpp index 5699f3be6..7a969343e 100644 --- a/src/modules/SerialModule.cpp +++ b/src/modules/SerialModule.cpp @@ -63,29 +63,26 @@ SerialModule *serialModule; SerialModuleRadio *serialModuleRadio; -#if defined(TTGO_T_ECHO) || defined(TTGO_T_ECHO_PLUS) || defined(CANARYONE) || defined(MESHLINK) || \ - defined(ELECROW_ThinkNode_M1) || defined(ELECROW_ThinkNode_M4) || defined(ELECROW_ThinkNode_M5) || \ - defined(HELTEC_MESH_SOLAR) || defined(T_ECHO_LITE) || defined(ELECROW_ThinkNode_M3) || defined(MUZI_BASE) - -SerialModule::SerialModule() : StreamAPI(&Serial), concurrency::OSThread("Serial") -{ - api_type = TYPE_SERIAL; -} -static Print *serialPrint = &Serial; -#elif defined(CONFIG_IDF_TARGET_ESP32C6) || defined(RAK3172) || defined(EBYTE_E77_MBL) -SerialModule::SerialModule() : StreamAPI(&Serial1), concurrency::OSThread("Serial") -{ - api_type = TYPE_SERIAL; -} -static Print *serialPrint = &Serial1; -#else -SerialModule::SerialModule() : StreamAPI(&Serial2), concurrency::OSThread("Serial") -{ - api_type = TYPE_SERIAL; -} -static Print *serialPrint = &Serial2; +#ifndef SERIAL_PRINT_PORT +#define SERIAL_PRINT_PORT 2 #endif +#if SERIAL_PRINT_PORT == 0 +#define SERIAL_PRINT_OBJECT Serial +#elif SERIAL_PRINT_PORT == 1 +#define SERIAL_PRINT_OBJECT Serial1 +#elif SERIAL_PRINT_PORT == 2 +#define SERIAL_PRINT_OBJECT Serial2 +#else +#error "Unsupported SERIAL_PRINT_PORT value. Allowed values are 0, 1, or 2." +#endif + +SerialModule::SerialModule() : StreamAPI(&SERIAL_PRINT_OBJECT), concurrency::OSThread("Serial") +{ + api_type = TYPE_SERIAL; +} +static Print *serialPrint = &SERIAL_PRINT_OBJECT; + char serialBytes[512]; size_t serialPayloadSize; @@ -205,9 +202,7 @@ int32_t SerialModule::runOnce() Serial.begin(baud); Serial.setTimeout(moduleConfig.serial.timeout > 0 ? moduleConfig.serial.timeout : TIMEOUT); } -#elif !defined(TTGO_T_ECHO) && !defined(TTGO_T_ECHO_PLUS) && !defined(T_ECHO_LITE) && !defined(CANARYONE) && \ - !defined(MESHLINK) && !defined(ELECROW_ThinkNode_M1) && !defined(ELECROW_ThinkNode_M3) && !defined(ELECROW_ThinkNode_M4) && \ - !defined(ELECROW_ThinkNode_M5) && !defined(MUZI_BASE) +#elif SERIAL_PRINT_PORT != 0 if (moduleConfig.serial.rxd && moduleConfig.serial.txd) { #ifdef ARCH_RP2040 @@ -264,9 +259,7 @@ int32_t SerialModule::runOnce() } } -#if !defined(TTGO_T_ECHO) && !defined(TTGO_T_ECHO_PLUS) && !defined(T_ECHO_LITE) && !defined(CANARYONE) && !defined(MESHLINK) && \ - !defined(ELECROW_ThinkNode_M1) && !defined(ELECROW_ThinkNode_M3) && !defined(ELECROW_ThinkNode_M4) && \ - !defined(ELECROW_ThinkNode_M5) && !defined(MUZI_BASE) +#if SERIAL_PRINT_PORT != 0 else if ((moduleConfig.serial.mode == meshtastic_ModuleConfig_SerialConfig_Serial_Mode_WS85)) { processWXSerial(); @@ -540,11 +533,7 @@ ParsedLine parseLine(const char *line) */ void SerialModule::processWXSerial() { -#if !defined(TTGO_T_ECHO) && !defined(TTGO_T_ECHO_PLUS) && !defined(T_ECHO_LITE) && !defined(CANARYONE) && \ - !defined(CONFIG_IDF_TARGET_ESP32C6) && !defined(MESHLINK) && !defined(ELECROW_ThinkNode_M1) && \ - !defined(ELECROW_ThinkNode_M3) && \ - !defined(ELECROW_ThinkNode_M4) && \ - !defined(ELECROW_ThinkNode_M5) && !defined(ARCH_STM32WL) && !defined(MUZI_BASE) +#if SERIAL_PRINT_PORT != 0 && !defined(ARCH_STM32WL) && !defined(CONFIG_IDF_TARGET_ESP32C6) static unsigned int lastAveraged = 0; static unsigned int averageIntervalMillis = 300000; // 5 minutes hard coded. diff --git a/variants/esp32c6/m5stack_unitc6l/variant.h b/variants/esp32c6/m5stack_unitc6l/variant.h index d973aa281..1654ee590 100644 --- a/variants/esp32c6/m5stack_unitc6l/variant.h +++ b/variants/esp32c6/m5stack_unitc6l/variant.h @@ -50,3 +50,5 @@ void c6l_init(); #endif #define SCREEN_TRANSITION_FRAMERATE 10 #define BRIGHTNESS_DEFAULT 130 // Medium Low Brightness + +#define SERIAL_PRINT_PORT 1 diff --git a/variants/esp32c6/tlora_c6/variant.h b/variants/esp32c6/tlora_c6/variant.h index 55635fe13..4a0d40232 100644 --- a/variants/esp32c6/tlora_c6/variant.h +++ b/variants/esp32c6/tlora_c6/variant.h @@ -19,3 +19,5 @@ #define SX126X_TXEN 14 #define SX126X_DIO2_AS_RF_SWITCH #define SX126X_DIO3_TCXO_VOLTAGE 1.8 + +#define SERIAL_PRINT_PORT 1 diff --git a/variants/esp32s3/ELECROW-ThinkNode-M5/variant.h b/variants/esp32s3/ELECROW-ThinkNode-M5/variant.h index 77a64f717..353741d91 100644 --- a/variants/esp32s3/ELECROW-ThinkNode-M5/variant.h +++ b/variants/esp32s3/ELECROW-ThinkNode-M5/variant.h @@ -81,4 +81,6 @@ #define BUTTON_PIN PIN_BUTTON1 #define BUTTON_PIN_ALT PIN_BUTTON2 + +#define SERIAL_PRINT_PORT 0 #endif diff --git a/variants/nrf52840/ELECROW-ThinkNode-M1/variant.h b/variants/nrf52840/ELECROW-ThinkNode-M1/variant.h index cde0f49c1..6f7056d27 100644 --- a/variants/nrf52840/ELECROW-ThinkNode-M1/variant.h +++ b/variants/nrf52840/ELECROW-ThinkNode-M1/variant.h @@ -159,6 +159,8 @@ External serial flash WP25R1635FZUIL0 #define PIN_SERIAL1_TX GPS_TX_PIN #define PIN_SERIAL1_RX GPS_RX_PIN +#define SERIAL_PRINT_PORT 0 + /* * SPI Interfaces */ diff --git a/variants/nrf52840/ELECROW-ThinkNode-M3/variant.h b/variants/nrf52840/ELECROW-ThinkNode-M3/variant.h index 29a6c85fd..decdd6ed0 100644 --- a/variants/nrf52840/ELECROW-ThinkNode-M3/variant.h +++ b/variants/nrf52840/ELECROW-ThinkNode-M3/variant.h @@ -113,6 +113,8 @@ extern "C" { #define LR11X0_DIO3_TCXO_VOLTAGE 3.3 #define LR11X0_DIO_AS_RF_SWITCH +#define SERIAL_PRINT_PORT 0 + // PCF8563 RTC Module // REVISIT https://github.com/meshtastic/firmware/pull/9084 // #define PCF8563_RTC 0x51 diff --git a/variants/nrf52840/ELECROW-ThinkNode-M4/variant.h b/variants/nrf52840/ELECROW-ThinkNode-M4/variant.h index faca5b075..1aff32838 100644 --- a/variants/nrf52840/ELECROW-ThinkNode-M4/variant.h +++ b/variants/nrf52840/ELECROW-ThinkNode-M4/variant.h @@ -135,6 +135,8 @@ static const uint8_t A0 = PIN_A0; #define PIN_SERIAL1_RX GPS_RX_PIN #define PIN_SERIAL1_TX GPS_TX_PIN +#define SERIAL_PRINT_PORT 0 + #ifdef __cplusplus } #endif diff --git a/variants/nrf52840/canaryone/variant.h b/variants/nrf52840/canaryone/variant.h index 61d1e8df9..537f4e26c 100644 --- a/variants/nrf52840/canaryone/variant.h +++ b/variants/nrf52840/canaryone/variant.h @@ -170,6 +170,8 @@ static const uint8_t A0 = PIN_A0; #define VBAT_AR_INTERNAL AR_INTERNAL_3_0 #define ADC_MULTIPLIER (2.0F) +#define SERIAL_PRINT_PORT 0 + #ifdef __cplusplus } #endif diff --git a/variants/nrf52840/heltec_mesh_solar/variant.h b/variants/nrf52840/heltec_mesh_solar/variant.h index 112bcd8b3..c80b81da4 100644 --- a/variants/nrf52840/heltec_mesh_solar/variant.h +++ b/variants/nrf52840/heltec_mesh_solar/variant.h @@ -142,6 +142,8 @@ No longer populated on PCB #define BQ4050_SCL_PIN (32 + 0) // I2C clock line pin #define BQ4050_EMERGENCY_SHUTDOWN_PIN (32 + 3) // Emergency shutdown pin +#define SERIAL_PRINT_PORT 0 + #ifdef __cplusplus } #endif diff --git a/variants/nrf52840/meshlink/variant.h b/variants/nrf52840/meshlink/variant.h index d1dba574f..2e252ff95 100644 --- a/variants/nrf52840/meshlink/variant.h +++ b/variants/nrf52840/meshlink/variant.h @@ -54,6 +54,7 @@ extern "C" { */ #define PIN_SERIAL1_RX (32 + 8) #define PIN_SERIAL1_TX (7) +#define SERIAL_PRINT_PORT 0 /* * SPI Interfaces diff --git a/variants/nrf52840/muzi_base/variant.h b/variants/nrf52840/muzi_base/variant.h index 96604c400..8a3532b77 100644 --- a/variants/nrf52840/muzi_base/variant.h +++ b/variants/nrf52840/muzi_base/variant.h @@ -176,6 +176,8 @@ extern "C" { #define EXTERNAL_FLASH_DEVICES W25Q32JVSS #define EXTERNAL_FLASH_USE_QSPI +#define SERIAL_PRINT_PORT 0 + // NFC is disabled via CONFIG_NFCT_PINS_AS_GPIOS=1 build flag // This configures P0.09 and P0.10 as regular GPIO pins instead of NFC pins diff --git a/variants/nrf52840/t-echo-lite/variant.h b/variants/nrf52840/t-echo-lite/variant.h index 0748f6d48..12dcb8949 100644 --- a/variants/nrf52840/t-echo-lite/variant.h +++ b/variants/nrf52840/t-echo-lite/variant.h @@ -171,6 +171,8 @@ static const uint8_t A0 = PIN_A0; #define VBAT_AR_INTERNAL AR_INTERNAL_3_0 #define ADC_MULTIPLIER (2.0F) +#define SERIAL_PRINT_PORT 0 + // #define NO_EXT_GPIO 1 // PINs back side // Batt & solar connector left up corner diff --git a/variants/nrf52840/t-echo-plus/variant.h b/variants/nrf52840/t-echo-plus/variant.h index 226f6d6fe..2d00f8c1f 100644 --- a/variants/nrf52840/t-echo-plus/variant.h +++ b/variants/nrf52840/t-echo-plus/variant.h @@ -138,6 +138,8 @@ static const uint8_t A0 = PIN_A0; // Battery / ADC already defined above #define HAS_RTC 1 +#define SERIAL_PRINT_PORT 0 + #ifdef __cplusplus } #endif diff --git a/variants/nrf52840/t-echo/variant.h b/variants/nrf52840/t-echo/variant.h index 9244fc6c3..8e72fd84b 100644 --- a/variants/nrf52840/t-echo/variant.h +++ b/variants/nrf52840/t-echo/variant.h @@ -88,6 +88,7 @@ static const uint8_t A0 = PIN_A0; /* * Serial interfaces */ +#define SERIAL_PRINT_PORT 0 /* No longer populated on PCB diff --git a/variants/stm32/CDEBYTE_E77-MBL/variant.h b/variants/stm32/CDEBYTE_E77-MBL/variant.h index e3d111a33..2cf355a61 100644 --- a/variants/stm32/CDEBYTE_E77-MBL/variant.h +++ b/variants/stm32/CDEBYTE_E77-MBL/variant.h @@ -19,5 +19,7 @@ Do not expect a working Meshtastic device with this target. // #define LED_PIN PB3 // LED2 #define LED_STATE_ON 1 +#define SERIAL_PRINT_PORT 1 + #define EBYTE_E77_MBL #endif diff --git a/variants/stm32/rak3172/variant.h b/variants/stm32/rak3172/variant.h index 30d2b57b4..b3f6cbcda 100644 --- a/variants/stm32/rak3172/variant.h +++ b/variants/stm32/rak3172/variant.h @@ -17,5 +17,6 @@ Do not expect a working Meshtastic device with this target. #define LED_STATE_ON 1 #define RAK3172 +#define SERIAL_PRINT_PORT 1 #endif From f710cd6ecbd415d9e1c1fc9ec9a028f4b0bd3003 Mon Sep 17 00:00:00 2001 From: Eric Severance Date: Wed, 28 Jan 2026 12:14:01 -0800 Subject: [PATCH 60/99] Support fully direct request/responses (#9455) Co-authored-by: Ben Meadors --- src/mesh/ReliableRouter.cpp | 6 ------ src/mesh/Router.cpp | 7 +++++++ src/modules/RoutingModule.cpp | 2 ++ 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/mesh/ReliableRouter.cpp b/src/mesh/ReliableRouter.cpp index 2b9b17183..42c24c783 100644 --- a/src/mesh/ReliableRouter.cpp +++ b/src/mesh/ReliableRouter.cpp @@ -17,12 +17,6 @@ ErrorCode ReliableRouter::send(meshtastic_MeshPacket *p) { if (p->want_ack) { - // If someone asks for acks on broadcast, we need the hop limit to be at least one, so that first node that receives our - // message will rebroadcast. But asking for hop_limit 0 in that context means the client app has no preference on hop - // counts and we want this message to get through the whole mesh, so use the default. - if (p->hop_limit == 0) { - p->hop_limit = Default::getConfiguredOrDefaultHopLimit(config.lora.hop_limit); - } DEBUG_HEAP_BEFORE; auto copy = packetPool.allocCopy(*p); DEBUG_HEAP_AFTER("ReliableRouter::send", copy); diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index 1a3270040..287fbcf60 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -266,6 +266,13 @@ ErrorCode Router::sendLocal(meshtastic_MeshPacket *p, RxSource src) } } + // If someone asks for acks on broadcast, we need the hop limit to be at least one, so that first node that receives our + // message will rebroadcast. But asking for hop_limit 0 in that context means the client app has no preference on hop + // counts and we want this message to get through the whole mesh, so use the default. + if (src == RX_SRC_USER && p->want_ack && p->hop_limit == 0) { + p->hop_limit = Default::getConfiguredOrDefaultHopLimit(config.lora.hop_limit); + } + return send(p); } } diff --git a/src/modules/RoutingModule.cpp b/src/modules/RoutingModule.cpp index e9e1fc786..d87cf3a44 100644 --- a/src/modules/RoutingModule.cpp +++ b/src/modules/RoutingModule.cpp @@ -67,6 +67,8 @@ uint8_t RoutingModule::getHopLimitForResponse(const meshtastic_MeshPacket &mp) #if !(EVENTMODE) // This falls through to the default. return hopsUsed; // If the request used more hops than the limit, use the same amount of hops #endif + } else if (mp.hop_start == 0) { + return 0; // The requesting node wanted 0 hops, so the response also uses a direct/local path. } else if ((uint8_t)(hopsUsed + 2) < config.lora.hop_limit) { return hopsUsed + 2; // Use only the amount of hops needed with some margin as the way back may be different } From 1d219a93abd2b819619d7918dc7bad912be687d9 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Wed, 28 Jan 2026 14:58:05 -0600 Subject: [PATCH 61/99] Move input init to an init function in InputBroker (#9463) * Move input init to an init function in iInputBroker * Unbreak targets with EXCLUDE_INPUTBROKER * Update src/input/InputBroker.cpp Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Fix conditional compilation for input broker * Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Trunk --------- Co-authored-by: Ben Meadors Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/input/InputBroker.cpp | 310 ++++++++++++++++++++++++++++++++++++++ src/input/InputBroker.h | 5 +- src/main.cpp | 226 +-------------------------- src/modules/Modules.cpp | 76 ---------- 4 files changed, 317 insertions(+), 300 deletions(-) diff --git a/src/input/InputBroker.cpp b/src/input/InputBroker.cpp index 0aa78e2b8..c0a56233f 100644 --- a/src/input/InputBroker.cpp +++ b/src/input/InputBroker.cpp @@ -1,8 +1,59 @@ #include "InputBroker.h" #include "PowerFSM.h" // needed for event trigger #include "configuration.h" +#include "graphics/Screen.h" #include "modules/ExternalNotificationModule.h" +#if ARCH_PORTDUINO +#include "input/LinuxInputImpl.h" +#include "input/SeesawRotary.h" +#include "platform/portduino/PortduinoGlue.h" +#endif + +#if !MESHTASTIC_EXCLUDE_INPUTBROKER +#include "input/ExpressLRSFiveWay.h" +#include "input/RotaryEncoderImpl.h" +#include "input/RotaryEncoderInterruptImpl1.h" +#include "input/SerialKeyboardImpl.h" +#include "input/UpDownInterruptImpl1.h" +#include "input/i2cButton.h" +#if HAS_TRACKBALL +#include "input/TrackballInterruptImpl1.h" +#endif + +#include "modules/StatusLEDModule.h" + +#if !MESHTASTIC_EXCLUDE_I2C +#include "input/cardKbI2cImpl.h" +#endif +#include "input/kbMatrixImpl.h" +#endif + +#if HAS_BUTTON || defined(ARCH_PORTDUINO) +#include "input/ButtonThread.h" + +#if defined(BUTTON_PIN_TOUCH) +ButtonThread *TouchButtonThread = nullptr; +#if defined(TTGO_T_ECHO_PLUS) && defined(PIN_EINK_EN) +static bool touchBacklightWasOn = false; +static bool touchBacklightActive = false; +#endif +#endif + +#if defined(BUTTON_PIN) || defined(ARCH_PORTDUINO) +ButtonThread *UserButtonThread = nullptr; +#endif + +#if defined(ALT_BUTTON_PIN) +ButtonThread *BackButtonThread = nullptr; +#endif + +#if defined(CANCEL_BUTTON_PIN) +ButtonThread *CancelButtonThread = nullptr; +#endif + +#endif + InputBroker *inputBroker = nullptr; InputBroker::InputBroker() @@ -74,3 +125,262 @@ void InputBroker::pollSoonWorker(void *p) vTaskDelete(NULL); } #endif + +void InputBroker::Init() +{ + +#ifdef BUTTON_PIN +#ifdef ARCH_ESP32 + +#if ESP_ARDUINO_VERSION_MAJOR >= 3 +#ifdef BUTTON_NEED_PULLUP + pinMode(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN, INPUT_PULLUP); +#else + pinMode(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN, INPUT); // default to BUTTON_PIN +#endif +#else + pinMode(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN, INPUT); // default to BUTTON_PIN +#ifdef BUTTON_NEED_PULLUP + gpio_pullup_en((gpio_num_t)(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN)); + delay(10); +#endif +#ifdef BUTTON_NEED_PULLUP2 + gpio_pullup_en((gpio_num_t)BUTTON_NEED_PULLUP2); + delay(10); +#endif +#endif +#endif +#endif + +// buttons are now inputBroker, so have to come after setupModules +#if HAS_BUTTON + int pullup_sense = 0; +#ifdef INPUT_PULLUP_SENSE + // Some platforms (nrf52) have a SENSE variant which allows wake from sleep - override what OneButton did +#ifdef BUTTON_SENSE_TYPE + pullup_sense = BUTTON_SENSE_TYPE; +#else + pullup_sense = INPUT_PULLUP_SENSE; +#endif +#endif +#if defined(ARCH_PORTDUINO) + + if (portduino_config.userButtonPin.enabled) { + + LOG_DEBUG("Use GPIO%02d for button", portduino_config.userButtonPin.pin); + UserButtonThread = new ButtonThread("UserButton"); + if (screen) { + ButtonConfig config; + config.pinNumber = (uint8_t)portduino_config.userButtonPin.pin; + config.activeLow = true; + config.activePullup = true; + config.pullupSense = INPUT_PULLUP; + config.intRoutine = []() { + UserButtonThread->userButton.tick(); + UserButtonThread->setIntervalFromNow(0); + runASAP = true; + BaseType_t higherWake = 0; + concurrency::mainDelay.interruptFromISR(&higherWake); + }; + config.singlePress = INPUT_BROKER_USER_PRESS; + config.longPress = INPUT_BROKER_SELECT; + UserButtonThread->initButton(config); + } + } +#endif + +#ifdef BUTTON_PIN_TOUCH + TouchButtonThread = new ButtonThread("BackButton"); + ButtonConfig touchConfig; + touchConfig.pinNumber = BUTTON_PIN_TOUCH; + touchConfig.activeLow = true; + touchConfig.activePullup = true; + touchConfig.pullupSense = pullup_sense; + touchConfig.intRoutine = []() { + TouchButtonThread->userButton.tick(); + TouchButtonThread->setIntervalFromNow(0); + runASAP = true; + BaseType_t higherWake = 0; + concurrency::mainDelay.interruptFromISR(&higherWake); + }; + touchConfig.singlePress = INPUT_BROKER_NONE; + touchConfig.longPress = INPUT_BROKER_BACK; +#if defined(TTGO_T_ECHO_PLUS) && defined(PIN_EINK_EN) + // On T-Echo Plus the touch pad should only drive the backlight, not UI navigation/sounds + touchConfig.longPress = INPUT_BROKER_NONE; + touchConfig.suppressLeadUpSound = true; + touchConfig.onPress = []() { + touchBacklightWasOn = uiconfig.screen_brightness == 1; + if (!touchBacklightWasOn) { + digitalWrite(PIN_EINK_EN, HIGH); + } + touchBacklightActive = true; + }; + touchConfig.onRelease = []() { + if (touchBacklightActive && !touchBacklightWasOn) { + digitalWrite(PIN_EINK_EN, LOW); + } + touchBacklightActive = false; + }; +#endif + TouchButtonThread->initButton(touchConfig); +#endif + +#if defined(CANCEL_BUTTON_PIN) + // Buttons. Moved here cause we need NodeDB to be initialized + CancelButtonThread = new ButtonThread("CancelButton"); + ButtonConfig cancelConfig; + cancelConfig.pinNumber = CANCEL_BUTTON_PIN; + cancelConfig.activeLow = CANCEL_BUTTON_ACTIVE_LOW; + cancelConfig.activePullup = CANCEL_BUTTON_ACTIVE_PULLUP; + cancelConfig.pullupSense = pullup_sense; + cancelConfig.intRoutine = []() { + CancelButtonThread->userButton.tick(); + CancelButtonThread->setIntervalFromNow(0); + runASAP = true; + BaseType_t higherWake = 0; + concurrency::mainDelay.interruptFromISR(&higherWake); + }; + cancelConfig.singlePress = INPUT_BROKER_CANCEL; + cancelConfig.longPress = INPUT_BROKER_SHUTDOWN; + cancelConfig.longPressTime = 4000; + CancelButtonThread->initButton(cancelConfig); +#endif + +#if defined(ALT_BUTTON_PIN) + // Buttons. Moved here cause we need NodeDB to be initialized + BackButtonThread = new ButtonThread("BackButton"); + ButtonConfig backConfig; + backConfig.pinNumber = ALT_BUTTON_PIN; + backConfig.activeLow = ALT_BUTTON_ACTIVE_LOW; + backConfig.activePullup = ALT_BUTTON_ACTIVE_PULLUP; + backConfig.pullupSense = pullup_sense; + backConfig.intRoutine = []() { + BackButtonThread->userButton.tick(); + BackButtonThread->setIntervalFromNow(0); + runASAP = true; + BaseType_t higherWake = 0; + concurrency::mainDelay.interruptFromISR(&higherWake); + }; + backConfig.singlePress = INPUT_BROKER_ALT_PRESS; + backConfig.longPress = INPUT_BROKER_ALT_LONG; + backConfig.longPressTime = 500; + BackButtonThread->initButton(backConfig); +#endif + +#if defined(BUTTON_PIN) +#if defined(USERPREFS_BUTTON_PIN) + int _pinNum = config.device.button_gpio ? config.device.button_gpio : USERPREFS_BUTTON_PIN; +#else + int _pinNum = config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN; +#endif +#ifndef BUTTON_ACTIVE_LOW +#define BUTTON_ACTIVE_LOW true +#endif +#ifndef BUTTON_ACTIVE_PULLUP +#define BUTTON_ACTIVE_PULLUP true +#endif + + // Buttons. Moved here cause we need NodeDB to be initialized + // If your variant.h has a BUTTON_PIN defined, go ahead and define BUTTON_ACTIVE_LOW and BUTTON_ACTIVE_PULLUP + UserButtonThread = new ButtonThread("UserButton"); + if (screen) { + ButtonConfig userConfig; + userConfig.pinNumber = (uint8_t)_pinNum; + userConfig.activeLow = BUTTON_ACTIVE_LOW; + userConfig.activePullup = BUTTON_ACTIVE_PULLUP; + userConfig.pullupSense = pullup_sense; + userConfig.intRoutine = []() { + UserButtonThread->userButton.tick(); + UserButtonThread->setIntervalFromNow(0); + runASAP = true; + BaseType_t higherWake = 0; + concurrency::mainDelay.interruptFromISR(&higherWake); + }; + userConfig.singlePress = INPUT_BROKER_USER_PRESS; + userConfig.longPress = INPUT_BROKER_SELECT; + userConfig.longPressTime = 500; + userConfig.longLongPress = INPUT_BROKER_SHUTDOWN; + UserButtonThread->initButton(userConfig); + } else { + ButtonConfig userConfigNoScreen; + userConfigNoScreen.pinNumber = (uint8_t)_pinNum; + userConfigNoScreen.activeLow = BUTTON_ACTIVE_LOW; + userConfigNoScreen.activePullup = BUTTON_ACTIVE_PULLUP; + userConfigNoScreen.pullupSense = pullup_sense; + userConfigNoScreen.intRoutine = []() { + UserButtonThread->userButton.tick(); + UserButtonThread->setIntervalFromNow(0); + runASAP = true; + BaseType_t higherWake = 0; + concurrency::mainDelay.interruptFromISR(&higherWake); + }; + userConfigNoScreen.singlePress = INPUT_BROKER_USER_PRESS; + userConfigNoScreen.longPress = INPUT_BROKER_NONE; + userConfigNoScreen.longPressTime = 500; + userConfigNoScreen.longLongPress = INPUT_BROKER_SHUTDOWN; + userConfigNoScreen.doublePress = INPUT_BROKER_SEND_PING; + userConfigNoScreen.triplePress = INPUT_BROKER_GPS_TOGGLE; + UserButtonThread->initButton(userConfigNoScreen); + } +#endif +#endif + +#if (HAS_BUTTON || ARCH_PORTDUINO) && !MESHTASTIC_EXCLUDE_INPUTBROKER + if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) { +#if defined(T_LORA_PAGER) + // use a special FSM based rotary encoder version for T-LoRa Pager + rotaryEncoderImpl = new RotaryEncoderImpl(); + if (!rotaryEncoderImpl->init()) { + delete rotaryEncoderImpl; + rotaryEncoderImpl = nullptr; + } +#elif defined(INPUTDRIVER_ENCODER_TYPE) && (INPUTDRIVER_ENCODER_TYPE == 2) + upDownInterruptImpl1 = new UpDownInterruptImpl1(); + if (!upDownInterruptImpl1->init()) { + delete upDownInterruptImpl1; + upDownInterruptImpl1 = nullptr; + } +#else + rotaryEncoderInterruptImpl1 = new RotaryEncoderInterruptImpl1(); + if (!rotaryEncoderInterruptImpl1->init()) { + delete rotaryEncoderInterruptImpl1; + rotaryEncoderInterruptImpl1 = nullptr; + } +#endif + cardKbI2cImpl = new CardKbI2cImpl(); + cardKbI2cImpl->init(); +#if defined(M5STACK_UNITC6L) + i2cButton = new i2cButtonThread("i2cButtonThread"); +#endif +#ifdef INPUTBROKER_MATRIX_TYPE + kbMatrixImpl = new KbMatrixImpl(); + kbMatrixImpl->init(); +#endif // INPUTBROKER_MATRIX_TYPE +#ifdef INPUTBROKER_SERIAL_TYPE + aSerialKeyboardImpl = new SerialKeyboardImpl(); + aSerialKeyboardImpl->init(); +#endif // INPUTBROKER_MATRIX_TYPE + } +#endif // HAS_BUTTON +#if ARCH_PORTDUINO + if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR && portduino_config.i2cdev != "") { + seesawRotary = new SeesawRotary("SeesawRotary"); + if (!seesawRotary->init()) { + delete seesawRotary; + seesawRotary = nullptr; + } + aLinuxInputImpl = new LinuxInputImpl(); + aLinuxInputImpl->init(); + } +#endif +#if !MESHTASTIC_EXCLUDE_INPUTBROKER && HAS_TRACKBALL + if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) { + trackballInterruptImpl1 = new TrackballInterruptImpl1(); + trackballInterruptImpl1->init(TB_DOWN, TB_UP, TB_LEFT, TB_RIGHT, TB_PRESS); + } +#endif +#ifdef INPUTBROKER_EXPRESSLRSFIVEWAY_TYPE + expressLRSFiveWayInput = new ExpressLRSFiveWay(); +#endif +} diff --git a/src/input/InputBroker.h b/src/input/InputBroker.h index 970e9969a..9fcdd845f 100644 --- a/src/input/InputBroker.h +++ b/src/input/InputBroker.h @@ -1,6 +1,7 @@ #pragma once #include "Observer.h" +#include "concurrency/OSThread.h" #include "freertosinc.h" #ifdef InputBrokerDebug @@ -76,6 +77,7 @@ class InputBroker : public Observable void queueInputEvent(const InputEvent *event); void processInputEventQueue(); #endif + void Init(); protected: int handleInputEvent(const InputEvent *event); @@ -89,4 +91,5 @@ class InputBroker : public Observable #endif }; -extern InputBroker *inputBroker; \ No newline at end of file +extern InputBroker *inputBroker; +extern bool runASAP; \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 3ccc06afb..68eda2d0d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -120,31 +120,6 @@ void printPartitionTable() #endif // DEBUG_PARTITION_TABLE #endif // ARCH_ESP32 -#if HAS_BUTTON || defined(ARCH_PORTDUINO) -#include "input/ButtonThread.h" - -#if defined(BUTTON_PIN_TOUCH) -ButtonThread *TouchButtonThread = nullptr; -#if defined(TTGO_T_ECHO_PLUS) && defined(PIN_EINK_EN) -static bool touchBacklightWasOn = false; -static bool touchBacklightActive = false; -#endif -#endif - -#if defined(BUTTON_PIN) || defined(ARCH_PORTDUINO) -ButtonThread *UserButtonThread = nullptr; -#endif - -#if defined(ALT_BUTTON_PIN) -ButtonThread *BackButtonThread = nullptr; -#endif - -#if defined(CANCEL_BUTTON_PIN) -ButtonThread *CancelButtonThread = nullptr; -#endif - -#endif - #include "AmbientLightingThread.h" #include "PowerFSMThread.h" @@ -509,30 +484,6 @@ void setup() LOG_INFO("Wait for peripherals to stabilize"); delay(PERIPHERAL_WARMUP_MS); #endif - -#ifdef BUTTON_PIN -#ifdef ARCH_ESP32 - -#if ESP_ARDUINO_VERSION_MAJOR >= 3 -#ifdef BUTTON_NEED_PULLUP - pinMode(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN, INPUT_PULLUP); -#else - pinMode(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN, INPUT); // default to BUTTON_PIN -#endif -#else - pinMode(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN, INPUT); // default to BUTTON_PIN -#ifdef BUTTON_NEED_PULLUP - gpio_pullup_en((gpio_num_t)(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN)); - delay(10); -#endif -#ifdef BUTTON_NEED_PULLUP2 - gpio_pullup_en((gpio_num_t)BUTTON_NEED_PULLUP2); - delay(10); -#endif -#endif -#endif -#endif - initSPI(); OSThread::setup(); @@ -999,180 +950,9 @@ void setup() nodeDB->hasWarned = true; } #endif - -// buttons are now inputBroker, so have to come after setupModules -#if HAS_BUTTON - int pullup_sense = 0; -#ifdef INPUT_PULLUP_SENSE - // Some platforms (nrf52) have a SENSE variant which allows wake from sleep - override what OneButton did -#ifdef BUTTON_SENSE_TYPE - pullup_sense = BUTTON_SENSE_TYPE; -#else - pullup_sense = INPUT_PULLUP_SENSE; -#endif -#endif -#if defined(ARCH_PORTDUINO) - - if (portduino_config.userButtonPin.enabled) { - - LOG_DEBUG("Use GPIO%02d for button", portduino_config.userButtonPin.pin); - UserButtonThread = new ButtonThread("UserButton"); - if (screen) { - ButtonConfig config; - config.pinNumber = (uint8_t)portduino_config.userButtonPin.pin; - config.activeLow = true; - config.activePullup = true; - config.pullupSense = INPUT_PULLUP; - config.intRoutine = []() { - UserButtonThread->userButton.tick(); - UserButtonThread->setIntervalFromNow(0); - runASAP = true; - BaseType_t higherWake = 0; - mainDelay.interruptFromISR(&higherWake); - }; - config.singlePress = INPUT_BROKER_USER_PRESS; - config.longPress = INPUT_BROKER_SELECT; - UserButtonThread->initButton(config); - } - } -#endif - -#ifdef BUTTON_PIN_TOUCH - TouchButtonThread = new ButtonThread("BackButton"); - ButtonConfig touchConfig; - touchConfig.pinNumber = BUTTON_PIN_TOUCH; - touchConfig.activeLow = true; - touchConfig.activePullup = true; - touchConfig.pullupSense = pullup_sense; - touchConfig.intRoutine = []() { - TouchButtonThread->userButton.tick(); - TouchButtonThread->setIntervalFromNow(0); - runASAP = true; - BaseType_t higherWake = 0; - mainDelay.interruptFromISR(&higherWake); - }; - touchConfig.singlePress = INPUT_BROKER_NONE; - touchConfig.longPress = INPUT_BROKER_BACK; -#if defined(TTGO_T_ECHO_PLUS) && defined(PIN_EINK_EN) - // On T-Echo Plus the touch pad should only drive the backlight, not UI navigation/sounds - touchConfig.longPress = INPUT_BROKER_NONE; - touchConfig.suppressLeadUpSound = true; - touchConfig.onPress = []() { - touchBacklightWasOn = uiconfig.screen_brightness == 1; - if (!touchBacklightWasOn) { - digitalWrite(PIN_EINK_EN, HIGH); - } - touchBacklightActive = true; - }; - touchConfig.onRelease = []() { - if (touchBacklightActive && !touchBacklightWasOn) { - digitalWrite(PIN_EINK_EN, LOW); - } - touchBacklightActive = false; - }; -#endif - TouchButtonThread->initButton(touchConfig); -#endif - -#if defined(CANCEL_BUTTON_PIN) - // Buttons. Moved here cause we need NodeDB to be initialized - CancelButtonThread = new ButtonThread("CancelButton"); - ButtonConfig cancelConfig; - cancelConfig.pinNumber = CANCEL_BUTTON_PIN; - cancelConfig.activeLow = CANCEL_BUTTON_ACTIVE_LOW; - cancelConfig.activePullup = CANCEL_BUTTON_ACTIVE_PULLUP; - cancelConfig.pullupSense = pullup_sense; - cancelConfig.intRoutine = []() { - CancelButtonThread->userButton.tick(); - CancelButtonThread->setIntervalFromNow(0); - runASAP = true; - BaseType_t higherWake = 0; - mainDelay.interruptFromISR(&higherWake); - }; - cancelConfig.singlePress = INPUT_BROKER_CANCEL; - cancelConfig.longPress = INPUT_BROKER_SHUTDOWN; - cancelConfig.longPressTime = 4000; - CancelButtonThread->initButton(cancelConfig); -#endif - -#if defined(ALT_BUTTON_PIN) - // Buttons. Moved here cause we need NodeDB to be initialized - BackButtonThread = new ButtonThread("BackButton"); - ButtonConfig backConfig; - backConfig.pinNumber = ALT_BUTTON_PIN; - backConfig.activeLow = ALT_BUTTON_ACTIVE_LOW; - backConfig.activePullup = ALT_BUTTON_ACTIVE_PULLUP; - backConfig.pullupSense = pullup_sense; - backConfig.intRoutine = []() { - BackButtonThread->userButton.tick(); - BackButtonThread->setIntervalFromNow(0); - runASAP = true; - BaseType_t higherWake = 0; - mainDelay.interruptFromISR(&higherWake); - }; - backConfig.singlePress = INPUT_BROKER_ALT_PRESS; - backConfig.longPress = INPUT_BROKER_ALT_LONG; - backConfig.longPressTime = 500; - BackButtonThread->initButton(backConfig); -#endif - -#if defined(BUTTON_PIN) -#if defined(USERPREFS_BUTTON_PIN) - int _pinNum = config.device.button_gpio ? config.device.button_gpio : USERPREFS_BUTTON_PIN; -#else - int _pinNum = config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN; -#endif -#ifndef BUTTON_ACTIVE_LOW -#define BUTTON_ACTIVE_LOW true -#endif -#ifndef BUTTON_ACTIVE_PULLUP -#define BUTTON_ACTIVE_PULLUP true -#endif - - // Buttons. Moved here cause we need NodeDB to be initialized - // If your variant.h has a BUTTON_PIN defined, go ahead and define BUTTON_ACTIVE_LOW and BUTTON_ACTIVE_PULLUP - UserButtonThread = new ButtonThread("UserButton"); - if (screen) { - ButtonConfig userConfig; - userConfig.pinNumber = (uint8_t)_pinNum; - userConfig.activeLow = BUTTON_ACTIVE_LOW; - userConfig.activePullup = BUTTON_ACTIVE_PULLUP; - userConfig.pullupSense = pullup_sense; - userConfig.intRoutine = []() { - UserButtonThread->userButton.tick(); - UserButtonThread->setIntervalFromNow(0); - runASAP = true; - BaseType_t higherWake = 0; - mainDelay.interruptFromISR(&higherWake); - }; - userConfig.singlePress = INPUT_BROKER_USER_PRESS; - userConfig.longPress = INPUT_BROKER_SELECT; - userConfig.longPressTime = 500; - userConfig.longLongPress = INPUT_BROKER_SHUTDOWN; - UserButtonThread->initButton(userConfig); - } else { - ButtonConfig userConfigNoScreen; - userConfigNoScreen.pinNumber = (uint8_t)_pinNum; - userConfigNoScreen.activeLow = BUTTON_ACTIVE_LOW; - userConfigNoScreen.activePullup = BUTTON_ACTIVE_PULLUP; - userConfigNoScreen.pullupSense = pullup_sense; - userConfigNoScreen.intRoutine = []() { - UserButtonThread->userButton.tick(); - UserButtonThread->setIntervalFromNow(0); - runASAP = true; - BaseType_t higherWake = 0; - mainDelay.interruptFromISR(&higherWake); - }; - userConfigNoScreen.singlePress = INPUT_BROKER_USER_PRESS; - userConfigNoScreen.longPress = INPUT_BROKER_NONE; - userConfigNoScreen.longPressTime = 500; - userConfigNoScreen.longLongPress = INPUT_BROKER_SHUTDOWN; - userConfigNoScreen.doublePress = INPUT_BROKER_SEND_PING; - userConfigNoScreen.triplePress = INPUT_BROKER_GPS_TOGGLE; - UserButtonThread->initButton(userConfigNoScreen); - } -#endif - +#if !MESHTASTIC_EXCLUDE_INPUTBROKER + if (inputBroker) + inputBroker->Init(); #endif #ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS diff --git a/src/modules/Modules.cpp b/src/modules/Modules.cpp index e17868baf..c3463a07e 100644 --- a/src/modules/Modules.cpp +++ b/src/modules/Modules.cpp @@ -1,24 +1,7 @@ #include "configuration.h" #if !MESHTASTIC_EXCLUDE_INPUTBROKER #include "buzz/BuzzerFeedbackThread.h" -#include "input/ExpressLRSFiveWay.h" -#include "input/InputBroker.h" -#include "input/RotaryEncoderImpl.h" -#include "input/RotaryEncoderInterruptImpl1.h" -#include "input/SerialKeyboardImpl.h" -#include "input/UpDownInterruptImpl1.h" -#include "input/i2cButton.h" #include "modules/SystemCommandsModule.h" -#if HAS_TRACKBALL -#include "input/TrackballInterruptImpl1.h" -#endif - -#include "modules/StatusLEDModule.h" - -#if !MESHTASTIC_EXCLUDE_I2C -#include "input/cardKbI2cImpl.h" -#endif -#include "input/kbMatrixImpl.h" #endif #if !MESHTASTIC_EXCLUDE_PKI #include "KeyVerificationModule.h" @@ -59,8 +42,6 @@ #include "modules/WaypointModule.h" #endif #if ARCH_PORTDUINO -#include "input/LinuxInputImpl.h" -#include "input/SeesawRotary.h" #include "modules/Telemetry/HostMetrics.h" #if !MESHTASTIC_EXCLUDE_STOREFORWARD #include "modules/StoreForwardModule.h" @@ -179,63 +160,6 @@ void setupModules() #endif // Example: Put your module here // new ReplyModule(); -#if (HAS_BUTTON || ARCH_PORTDUINO) && !MESHTASTIC_EXCLUDE_INPUTBROKER - if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) { -#if defined(T_LORA_PAGER) - // use a special FSM based rotary encoder version for T-LoRa Pager - rotaryEncoderImpl = new RotaryEncoderImpl(); - if (!rotaryEncoderImpl->init()) { - delete rotaryEncoderImpl; - rotaryEncoderImpl = nullptr; - } -#elif defined(INPUTDRIVER_ENCODER_TYPE) && (INPUTDRIVER_ENCODER_TYPE == 2) - upDownInterruptImpl1 = new UpDownInterruptImpl1(); - if (!upDownInterruptImpl1->init()) { - delete upDownInterruptImpl1; - upDownInterruptImpl1 = nullptr; - } -#else - rotaryEncoderInterruptImpl1 = new RotaryEncoderInterruptImpl1(); - if (!rotaryEncoderInterruptImpl1->init()) { - delete rotaryEncoderInterruptImpl1; - rotaryEncoderInterruptImpl1 = nullptr; - } -#endif - cardKbI2cImpl = new CardKbI2cImpl(); - cardKbI2cImpl->init(); -#if defined(M5STACK_UNITC6L) - i2cButton = new i2cButtonThread("i2cButtonThread"); -#endif -#ifdef INPUTBROKER_MATRIX_TYPE - kbMatrixImpl = new KbMatrixImpl(); - kbMatrixImpl->init(); -#endif // INPUTBROKER_MATRIX_TYPE -#ifdef INPUTBROKER_SERIAL_TYPE - aSerialKeyboardImpl = new SerialKeyboardImpl(); - aSerialKeyboardImpl->init(); -#endif // INPUTBROKER_MATRIX_TYPE - } -#endif // HAS_BUTTON -#if ARCH_PORTDUINO - if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR && portduino_config.i2cdev != "") { - seesawRotary = new SeesawRotary("SeesawRotary"); - if (!seesawRotary->init()) { - delete seesawRotary; - seesawRotary = nullptr; - } - aLinuxInputImpl = new LinuxInputImpl(); - aLinuxInputImpl->init(); - } -#endif -#if !MESHTASTIC_EXCLUDE_INPUTBROKER && HAS_TRACKBALL - if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) { - trackballInterruptImpl1 = new TrackballInterruptImpl1(); - trackballInterruptImpl1->init(TB_DOWN, TB_UP, TB_LEFT, TB_RIGHT, TB_PRESS); - } -#endif -#ifdef INPUTBROKER_EXPRESSLRSFIVEWAY_TYPE - expressLRSFiveWayInput = new ExpressLRSFiveWay(); -#endif #if HAS_SCREEN && !MESHTASTIC_EXCLUDE_CANNEDMESSAGES if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) { cannedMessageModule = new CannedMessageModule(); From 4fd0a8276bc8f64220ad42734c9315edd22e0395 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Wed, 28 Jan 2026 17:09:13 -0600 Subject: [PATCH 62/99] Just set LED_BUILTIN universally to -1, as we don't use it. (#8830) * Just set LED_BUILTIN universally to -1, as we don't use it. * LUD_BUILTIN workarounds * Squash the LED_BUILTINs that sneaked in * Don't kill valid pin derfine --- platformio.ini | 1 + variants/esp32/rak11200/pins_arduino.h | 2 -- variants/esp32/rak11200/variant.h | 2 -- variants/esp32/tbeam/platformio.ini | 1 + variants/esp32c6/tlora_c6/platformio.ini | 1 + variants/esp32s3/heltec_v3/platformio.ini | 1 + variants/esp32s3/heltec_v4/pins_arduino.h | 4 ---- variants/esp32s3/heltec_vision_master_e213/pins_arduino.h | 4 ---- variants/esp32s3/heltec_vision_master_e290/pins_arduino.h | 4 ---- variants/esp32s3/heltec_vision_master_t190/pins_arduino.h | 4 ---- variants/esp32s3/heltec_wireless_paper/pins_arduino.h | 4 ---- variants/esp32s3/heltec_wireless_paper_v1/pins_arduino.h | 4 ---- variants/esp32s3/heltec_wireless_tracker/pins_arduino.h | 4 ---- variants/esp32s3/heltec_wireless_tracker_V1_0/pins_arduino.h | 4 ---- variants/esp32s3/heltec_wireless_tracker_v2/pins_arduino.h | 4 ---- variants/esp32s3/m5stack_cores3/pins_arduino.h | 5 +---- variants/esp32s3/mesh-tab/pins_arduino.h | 4 ---- variants/esp32s3/rak3312/pins_arduino.h | 3 --- variants/esp32s3/rak_wismesh_tap_v2/pins_arduino.h | 3 --- variants/esp32s3/seeed-sensecap-indicator/pins_arduino.h | 2 -- variants/esp32s3/t-deck/pins_arduino.h | 2 -- variants/esp32s3/t-watch-s3/pins_arduino.h | 2 -- variants/esp32s3/tracksenger/internal/pins_arduino.h | 4 ---- variants/esp32s3/tracksenger/lcd/pins_arduino.h | 4 ---- variants/esp32s3/tracksenger/oled/pins_arduino.h | 4 ---- variants/esp32s3/unphone/pins_arduino.h | 3 --- variants/nrf52840/Dongle_nRF52840-pca10059-v1/variant.h | 1 - variants/nrf52840/ELECROW-ThinkNode-M1/variant.h | 1 - variants/nrf52840/ELECROW-ThinkNode-M3/variant.h | 1 - variants/nrf52840/ELECROW-ThinkNode-M4/variant.h | 1 - variants/nrf52840/ELECROW-ThinkNode-M6/variant.h | 1 - variants/nrf52840/ME25LS01-4Y10TD/variant.h | 1 - variants/nrf52840/ME25LS01-4Y10TD_e-ink/variant.h | 1 - variants/nrf52840/MS24SF1/variant.h | 1 - variants/nrf52840/MakePython_nRF52840_eink/variant.h | 1 - variants/nrf52840/MakePython_nRF52840_oled/variant.h | 1 - variants/nrf52840/TWC_mesh_v4/variant.h | 1 - variants/nrf52840/canaryone/variant.h | 1 - variants/nrf52840/diy/nrf52_promicro_diy_tcxo/variant.h | 1 - .../nrf52840/diy/seeed-xiao-nrf52840-wio-sx1262/variant.h | 1 - variants/nrf52840/dls_Minimesh_Lite/variant.h | 1 - variants/nrf52840/feather_diy/variant.h | 2 -- variants/nrf52840/gat562_mesh_trial_tracker/variant.h | 1 - variants/nrf52840/heltec_mesh_node_t114-inkhud/variant.h | 1 - variants/nrf52840/heltec_mesh_node_t114/variant.h | 1 - variants/nrf52840/heltec_mesh_pocket/variant.h | 1 - variants/nrf52840/heltec_mesh_solar/variant.h | 1 - variants/nrf52840/meshlink/variant.h | 1 - variants/nrf52840/meshtiny/variant.h | 1 - variants/nrf52840/monteops_hw1/variant.h | 1 - variants/nrf52840/muzi_base/variant.h | 1 - variants/nrf52840/nano-g2-ultra/variant.h | 1 - variants/nrf52840/r1-neo/variant.h | 1 - variants/nrf52840/rak2560/variant.h | 1 - variants/nrf52840/rak3401_1watt/variant.h | 1 - variants/nrf52840/rak4631/variant.h | 1 - variants/nrf52840/rak4631_epaper/variant.h | 1 - variants/nrf52840/rak4631_epaper_onrxtx/variant.h | 1 - variants/nrf52840/rak4631_eth_gw/variant.h | 1 - variants/nrf52840/rak4631_nomadstar_meteor_pro/variant.h | 1 - variants/nrf52840/rak_wismeshtag/variant.h | 1 - variants/nrf52840/rak_wismeshtap/variant.h | 1 - variants/nrf52840/seeed_solar_node/variant.h | 1 - variants/nrf52840/seeed_wio_tracker_L1/variant.h | 1 - variants/nrf52840/seeed_wio_tracker_L1_eink/variant.h | 1 - variants/nrf52840/seeed_xiao_nrf52840_kit/variant.h | 2 -- variants/nrf52840/t-echo-lite/variant.h | 1 - variants/nrf52840/t-echo-plus/variant.h | 1 - variants/nrf52840/t-echo/variant.h | 1 - variants/nrf52840/tracker-t1000-e/variant.h | 1 - variants/nrf52840/wio-sdk-wm1110/variant.h | 2 -- variants/nrf52840/wio-t1000-s/variant.h | 1 - variants/nrf52840/wio-tracker-wm1110/variant.h | 2 -- variants/rp2040/challenger_2040_lora/pins_arduino.h | 2 -- variants/rp2040/rak11310/pins_arduino.h | 1 - variants/rp2040/rak11310/variant.h | 2 +- variants/rp2040/rpipicow/platformio.ini | 1 + variants/rp2040/rpipicow/variant.h | 2 +- variants/rp2040/senselora_rp2040/pins_arduino.h | 1 - variants/stm32/wio-e5/variant.h | 5 ----- 80 files changed, 8 insertions(+), 137 deletions(-) diff --git a/platformio.ini b/platformio.ini index b7c46a6e1..25fc8829f 100644 --- a/platformio.ini +++ b/platformio.ini @@ -56,6 +56,7 @@ build_flags = -Wno-missing-field-initializers -DMESHTASTIC_EXCLUDE_GENERIC_THREAD_MODULE=1 -DMESHTASTIC_EXCLUDE_POWERMON=1 -D MAX_THREADS=40 ; As we've split modules, we have more threads to manage + -DLED_BUILTIN=-1 #-DBUILD_EPOCH=$UNIX_TIME ; set in platformio-custom.py now #-D OLED_PL=1 #-D DEBUG_HEAP=1 ; uncomment to add free heap space / memory leak debugging logs diff --git a/variants/esp32/rak11200/pins_arduino.h b/variants/esp32/rak11200/pins_arduino.h index f383d54a7..263fbd5a0 100644 --- a/variants/esp32/rak11200/pins_arduino.h +++ b/variants/esp32/rak11200/pins_arduino.h @@ -6,8 +6,6 @@ #define LED_GREEN 12 #define LED_BLUE 2 -#define LED_BUILTIN LED_GREEN - static const uint8_t TX = 1; static const uint8_t RX = 3; diff --git a/variants/esp32/rak11200/variant.h b/variants/esp32/rak11200/variant.h index 01edb8b73..fe7d05676 100644 --- a/variants/esp32/rak11200/variant.h +++ b/variants/esp32/rak11200/variant.h @@ -6,8 +6,6 @@ #define LED_GREEN 12 #define LED_BLUE 2 -#define LED_BUILTIN LED_GREEN - static const uint8_t TX = 1; static const uint8_t RX = 3; diff --git a/variants/esp32/tbeam/platformio.ini b/variants/esp32/tbeam/platformio.ini index 16a3d1845..dbaccee8f 100644 --- a/variants/esp32/tbeam/platformio.ini +++ b/variants/esp32/tbeam/platformio.ini @@ -18,6 +18,7 @@ build_flags = ${esp32_base.build_flags} -I variants/esp32/tbeam -DBOARD_HAS_PSRAM -mfix-esp32-psram-cache-issue + -ULED_BUILTIN upload_speed = 921600 [env:tbeam-displayshield] diff --git a/variants/esp32c6/tlora_c6/platformio.ini b/variants/esp32c6/tlora_c6/platformio.ini index 6b402d7c5..174e5e297 100644 --- a/variants/esp32c6/tlora_c6/platformio.ini +++ b/variants/esp32c6/tlora_c6/platformio.ini @@ -8,3 +8,4 @@ build_flags = -I variants/esp32c6/tlora_c6 -DARDUINO_USB_CDC_ON_BOOT=1 -DARDUINO_USB_MODE=1 + -ULED_BUILTIN diff --git a/variants/esp32s3/heltec_v3/platformio.ini b/variants/esp32s3/heltec_v3/platformio.ini index 2f53c8756..fe31df094 100644 --- a/variants/esp32s3/heltec_v3/platformio.ini +++ b/variants/esp32s3/heltec_v3/platformio.ini @@ -18,3 +18,4 @@ build_flags = ${esp32s3_base.build_flags} -D HELTEC_V3 -I variants/esp32s3/heltec_v3 + -ULED_BUILTIN diff --git a/variants/esp32s3/heltec_v4/pins_arduino.h b/variants/esp32s3/heltec_v4/pins_arduino.h index d4485016d..32fd8a8e4 100644 --- a/variants/esp32s3/heltec_v4/pins_arduino.h +++ b/variants/esp32s3/heltec_v4/pins_arduino.h @@ -6,10 +6,6 @@ #define USB_VID 0x303a #define USB_PID 0x1001 -static const uint8_t LED_BUILTIN = 35; -#define BUILTIN_LED LED_BUILTIN // backward compatibility -#define LED_BUILTIN LED_BUILTIN // allow testing #ifdef LED_BUILTIN - static const uint8_t TX = 43; static const uint8_t RX = 44; diff --git a/variants/esp32s3/heltec_vision_master_e213/pins_arduino.h b/variants/esp32s3/heltec_vision_master_e213/pins_arduino.h index 56f5ef157..5cf3c6453 100644 --- a/variants/esp32s3/heltec_vision_master_e213/pins_arduino.h +++ b/variants/esp32s3/heltec_vision_master_e213/pins_arduino.h @@ -3,10 +3,6 @@ #include -static const uint8_t LED_BUILTIN = 45; // LED is not populated on earliest board variant -#define BUILTIN_LED LED_BUILTIN // Backward compatibility -#define LED_BUILTIN LED_BUILTIN - static const uint8_t TX = 43; static const uint8_t RX = 44; diff --git a/variants/esp32s3/heltec_vision_master_e290/pins_arduino.h b/variants/esp32s3/heltec_vision_master_e290/pins_arduino.h index 56f5ef157..5cf3c6453 100644 --- a/variants/esp32s3/heltec_vision_master_e290/pins_arduino.h +++ b/variants/esp32s3/heltec_vision_master_e290/pins_arduino.h @@ -3,10 +3,6 @@ #include -static const uint8_t LED_BUILTIN = 45; // LED is not populated on earliest board variant -#define BUILTIN_LED LED_BUILTIN // Backward compatibility -#define LED_BUILTIN LED_BUILTIN - static const uint8_t TX = 43; static const uint8_t RX = 44; diff --git a/variants/esp32s3/heltec_vision_master_t190/pins_arduino.h b/variants/esp32s3/heltec_vision_master_t190/pins_arduino.h index eeef95ff1..b8a33f721 100644 --- a/variants/esp32s3/heltec_vision_master_t190/pins_arduino.h +++ b/variants/esp32s3/heltec_vision_master_t190/pins_arduino.h @@ -3,10 +3,6 @@ #include -static const uint8_t LED_BUILTIN = 35; -#define BUILTIN_LED LED_BUILTIN // backward compatibility -#define LED_BUILTIN LED_BUILTIN - static const uint8_t TX = 43; static const uint8_t RX = 44; diff --git a/variants/esp32s3/heltec_wireless_paper/pins_arduino.h b/variants/esp32s3/heltec_wireless_paper/pins_arduino.h index 3e36d98f5..886cab254 100644 --- a/variants/esp32s3/heltec_wireless_paper/pins_arduino.h +++ b/variants/esp32s3/heltec_wireless_paper/pins_arduino.h @@ -3,10 +3,6 @@ #include -static const uint8_t LED_BUILTIN = 18; -#define BUILTIN_LED LED_BUILTIN // backward compatibility -#define LED_BUILTIN LED_BUILTIN - static const uint8_t TX = 43; static const uint8_t RX = 44; diff --git a/variants/esp32s3/heltec_wireless_paper_v1/pins_arduino.h b/variants/esp32s3/heltec_wireless_paper_v1/pins_arduino.h index 2bb44161a..0c486eebc 100644 --- a/variants/esp32s3/heltec_wireless_paper_v1/pins_arduino.h +++ b/variants/esp32s3/heltec_wireless_paper_v1/pins_arduino.h @@ -3,10 +3,6 @@ #include -static const uint8_t LED_BUILTIN = 18; -#define BUILTIN_LED LED_BUILTIN // backward compatibility -#define LED_BUILTIN LED_BUILTIN - static const uint8_t KEY_BUILTIN = 0; static const uint8_t TX = 43; diff --git a/variants/esp32s3/heltec_wireless_tracker/pins_arduino.h b/variants/esp32s3/heltec_wireless_tracker/pins_arduino.h index 1052af961..93fd5d9c2 100644 --- a/variants/esp32s3/heltec_wireless_tracker/pins_arduino.h +++ b/variants/esp32s3/heltec_wireless_tracker/pins_arduino.h @@ -11,10 +11,6 @@ #define USB_VID 0x303a #define USB_PID 0x1001 -static const uint8_t LED_BUILTIN = 18; -#define BUILTIN_LED LED_BUILTIN // backward compatibility -#define LED_BUILTIN LED_BUILTIN - static const uint8_t TX = 43; static const uint8_t RX = 44; diff --git a/variants/esp32s3/heltec_wireless_tracker_V1_0/pins_arduino.h b/variants/esp32s3/heltec_wireless_tracker_V1_0/pins_arduino.h index 28b982012..7a1f43eee 100644 --- a/variants/esp32s3/heltec_wireless_tracker_V1_0/pins_arduino.h +++ b/variants/esp32s3/heltec_wireless_tracker_V1_0/pins_arduino.h @@ -11,10 +11,6 @@ #define USB_VID 0x303a #define USB_PID 0x1001 -static const uint8_t LED_BUILTIN = 18; -#define BUILTIN_LED LED_BUILTIN // backward compatibility -#define LED_BUILTIN LED_BUILTIN - static const uint8_t TX = 43; static const uint8_t RX = 44; diff --git a/variants/esp32s3/heltec_wireless_tracker_v2/pins_arduino.h b/variants/esp32s3/heltec_wireless_tracker_v2/pins_arduino.h index 61c319109..9fb825002 100644 --- a/variants/esp32s3/heltec_wireless_tracker_v2/pins_arduino.h +++ b/variants/esp32s3/heltec_wireless_tracker_v2/pins_arduino.h @@ -10,10 +10,6 @@ #define USB_VID 0x303a #define USB_PID 0x1001 -static const uint8_t LED_BUILTIN = 18; -#define BUILTIN_LED LED_BUILTIN // backward compatibility -#define LED_BUILTIN LED_BUILTIN - static const uint8_t TX = 43; static const uint8_t RX = 44; diff --git a/variants/esp32s3/m5stack_cores3/pins_arduino.h b/variants/esp32s3/m5stack_cores3/pins_arduino.h index 78e936990..ff7d35993 100644 --- a/variants/esp32s3/m5stack_cores3/pins_arduino.h +++ b/variants/esp32s3/m5stack_cores3/pins_arduino.h @@ -10,10 +10,7 @@ // Some boards have too low voltage on this pin (board design bug) // Use different pin with 3V and connect with 48 // and change this setup for the chosen pin (for example 38) -static const uint8_t LED_BUILTIN = SOC_GPIO_PIN_COUNT + 48; -#define BUILTIN_LED LED_BUILTIN // backward compatibility -#define LED_BUILTIN LED_BUILTIN -#define RGB_BUILTIN LED_BUILTIN +#define RGB_BUILTIN SOC_GPIO_PIN_COUNT + 48 #define RGB_BRIGHTNESS 64 static const uint8_t TX = 43; diff --git a/variants/esp32s3/mesh-tab/pins_arduino.h b/variants/esp32s3/mesh-tab/pins_arduino.h index c995f638c..d980e1a49 100644 --- a/variants/esp32s3/mesh-tab/pins_arduino.h +++ b/variants/esp32s3/mesh-tab/pins_arduino.h @@ -49,10 +49,6 @@ static const uint8_t T14 = 14; static const uint8_t VBAT_SENSE = 2; static const uint8_t VBUS_SENSE = 34; -// User LED -#define LED_BUILTIN 13 -#define BUILTIN_LED LED_BUILTIN // backward compatibility - static const uint8_t RGB_DATA = 40; // RGB_BUILTIN and RGB_BRIGHTNESS can be used in new Arduino API neopixelWrite() #define RGB_BUILTIN (RGB_DATA + SOC_GPIO_PIN_COUNT) diff --git a/variants/esp32s3/rak3312/pins_arduino.h b/variants/esp32s3/rak3312/pins_arduino.h index dc5d30d61..600c619cf 100644 --- a/variants/esp32s3/rak3312/pins_arduino.h +++ b/variants/esp32s3/rak3312/pins_arduino.h @@ -24,9 +24,6 @@ static const uint8_t SCK = 13; #define SPI_MISO (10) #define SPI_CS (12) -// LEDs -#define LED_BUILTIN LED_GREEN - #ifdef _VARIANT_RAK3112_ /* * Serial interfaces diff --git a/variants/esp32s3/rak_wismesh_tap_v2/pins_arduino.h b/variants/esp32s3/rak_wismesh_tap_v2/pins_arduino.h index 15a26e991..b51cd214e 100644 --- a/variants/esp32s3/rak_wismesh_tap_v2/pins_arduino.h +++ b/variants/esp32s3/rak_wismesh_tap_v2/pins_arduino.h @@ -22,7 +22,4 @@ static const uint8_t SCK = 13; #define SPI_MISO (10) #define SPI_CS (12) -// LEDs -#define LED_BUILTIN LED_GREEN - #endif /* Pins_Arduino_h */ diff --git a/variants/esp32s3/seeed-sensecap-indicator/pins_arduino.h b/variants/esp32s3/seeed-sensecap-indicator/pins_arduino.h index 300f0e0f5..88c233491 100644 --- a/variants/esp32s3/seeed-sensecap-indicator/pins_arduino.h +++ b/variants/esp32s3/seeed-sensecap-indicator/pins_arduino.h @@ -3,8 +3,6 @@ #include -// static const uint8_t LED_BUILTIN = -1; - // static const uint8_t TX = 43; // static const uint8_t RX = 44; diff --git a/variants/esp32s3/t-deck/pins_arduino.h b/variants/esp32s3/t-deck/pins_arduino.h index cb429d776..c358b988e 100644 --- a/variants/esp32s3/t-deck/pins_arduino.h +++ b/variants/esp32s3/t-deck/pins_arduino.h @@ -6,8 +6,6 @@ #define USB_VID 0x303a #define USB_PID 0x1001 -// static const uint8_t LED_BUILTIN = -1; - static const uint8_t TX = 43; static const uint8_t RX = 44; diff --git a/variants/esp32s3/t-watch-s3/pins_arduino.h b/variants/esp32s3/t-watch-s3/pins_arduino.h index 35f0e933e..f4585ace8 100644 --- a/variants/esp32s3/t-watch-s3/pins_arduino.h +++ b/variants/esp32s3/t-watch-s3/pins_arduino.h @@ -3,8 +3,6 @@ #include -// static const uint8_t LED_BUILTIN = -1; - // static const uint8_t TX = 43; // static const uint8_t RX = 44; diff --git a/variants/esp32s3/tracksenger/internal/pins_arduino.h b/variants/esp32s3/tracksenger/internal/pins_arduino.h index 1052af961..93fd5d9c2 100644 --- a/variants/esp32s3/tracksenger/internal/pins_arduino.h +++ b/variants/esp32s3/tracksenger/internal/pins_arduino.h @@ -11,10 +11,6 @@ #define USB_VID 0x303a #define USB_PID 0x1001 -static const uint8_t LED_BUILTIN = 18; -#define BUILTIN_LED LED_BUILTIN // backward compatibility -#define LED_BUILTIN LED_BUILTIN - static const uint8_t TX = 43; static const uint8_t RX = 44; diff --git a/variants/esp32s3/tracksenger/lcd/pins_arduino.h b/variants/esp32s3/tracksenger/lcd/pins_arduino.h index 1052af961..93fd5d9c2 100644 --- a/variants/esp32s3/tracksenger/lcd/pins_arduino.h +++ b/variants/esp32s3/tracksenger/lcd/pins_arduino.h @@ -11,10 +11,6 @@ #define USB_VID 0x303a #define USB_PID 0x1001 -static const uint8_t LED_BUILTIN = 18; -#define BUILTIN_LED LED_BUILTIN // backward compatibility -#define LED_BUILTIN LED_BUILTIN - static const uint8_t TX = 43; static const uint8_t RX = 44; diff --git a/variants/esp32s3/tracksenger/oled/pins_arduino.h b/variants/esp32s3/tracksenger/oled/pins_arduino.h index 1052af961..93fd5d9c2 100644 --- a/variants/esp32s3/tracksenger/oled/pins_arduino.h +++ b/variants/esp32s3/tracksenger/oled/pins_arduino.h @@ -11,10 +11,6 @@ #define USB_VID 0x303a #define USB_PID 0x1001 -static const uint8_t LED_BUILTIN = 18; -#define BUILTIN_LED LED_BUILTIN // backward compatibility -#define LED_BUILTIN LED_BUILTIN - static const uint8_t TX = 43; static const uint8_t RX = 44; diff --git a/variants/esp32s3/unphone/pins_arduino.h b/variants/esp32s3/unphone/pins_arduino.h index 74067359f..8a62e3d42 100644 --- a/variants/esp32s3/unphone/pins_arduino.h +++ b/variants/esp32s3/unphone/pins_arduino.h @@ -6,9 +6,6 @@ #define USB_VID 0x16D0 #define USB_PID 0x1178 -#define LED_BUILTIN 13 -#define BUILTIN_LED LED_BUILTIN // backward compatibility - static const uint8_t TX = 43; static const uint8_t RX = 44; diff --git a/variants/nrf52840/Dongle_nRF52840-pca10059-v1/variant.h b/variants/nrf52840/Dongle_nRF52840-pca10059-v1/variant.h index 2318450eb..d120e485c 100644 --- a/variants/nrf52840/Dongle_nRF52840-pca10059-v1/variant.h +++ b/variants/nrf52840/Dongle_nRF52840-pca10059-v1/variant.h @@ -50,7 +50,6 @@ extern "C" { #define RGBLED_BLUE (0 + 12) // Blue of RGB P0.12 #define RGBLED_CA // comment out this line if you have a common cathode type, as defined use common anode logic -#define LED_BUILTIN PIN_LED1 #define LED_CONN PIN_LED2 #define LED_GREEN PIN_LED1 diff --git a/variants/nrf52840/ELECROW-ThinkNode-M1/variant.h b/variants/nrf52840/ELECROW-ThinkNode-M1/variant.h index 6f7056d27..c36211a63 100644 --- a/variants/nrf52840/ELECROW-ThinkNode-M1/variant.h +++ b/variants/nrf52840/ELECROW-ThinkNode-M1/variant.h @@ -55,7 +55,6 @@ extern "C" { #define LED_RED PIN_LED3 #define LED_BLUE PIN_LED1 #define LED_GREEN PIN_LED2 -#define LED_BUILTIN LED_BLUE #define LED_CONN PIN_GREEN #define LED_STATE_ON 0 // State when LED is lit // LED灯亮时的状态 #define PIN_BUZZER (0 + 6) diff --git a/variants/nrf52840/ELECROW-ThinkNode-M3/variant.h b/variants/nrf52840/ELECROW-ThinkNode-M3/variant.h index decdd6ed0..acbcf6ae5 100644 --- a/variants/nrf52840/ELECROW-ThinkNode-M3/variant.h +++ b/variants/nrf52840/ELECROW-ThinkNode-M3/variant.h @@ -58,7 +58,6 @@ extern "C" { #define LED_BLUE 37 #define LED_PAIRING LED_BLUE // Signals the Status LED Module to handle this LED -#define LED_BUILTIN -1 #define LED_STATE_ON LOW #define LED_STATE_OFF HIGH diff --git a/variants/nrf52840/ELECROW-ThinkNode-M4/variant.h b/variants/nrf52840/ELECROW-ThinkNode-M4/variant.h index 1aff32838..0d905c2c1 100644 --- a/variants/nrf52840/ELECROW-ThinkNode-M4/variant.h +++ b/variants/nrf52840/ELECROW-ThinkNode-M4/variant.h @@ -40,7 +40,6 @@ extern "C" { #define NUM_ANALOG_OUTPUTS (0) // LEDs -#define LED_BUILTIN -1 #define LED_BLUE -1 #define PIN_LED2 (32 + 9) #define LED_PAIRING (13) diff --git a/variants/nrf52840/ELECROW-ThinkNode-M6/variant.h b/variants/nrf52840/ELECROW-ThinkNode-M6/variant.h index e46391207..2461d73d2 100644 --- a/variants/nrf52840/ELECROW-ThinkNode-M6/variant.h +++ b/variants/nrf52840/ELECROW-ThinkNode-M6/variant.h @@ -40,7 +40,6 @@ extern "C" { #define NUM_ANALOG_OUTPUTS (0) // LEDs -#define LED_BUILTIN -1 #define LED_BLUE -1 #define LED_CHARGE (12) #define LED_PAIRING (7) diff --git a/variants/nrf52840/ME25LS01-4Y10TD/variant.h b/variants/nrf52840/ME25LS01-4Y10TD/variant.h index e772069da..a920b02e5 100644 --- a/variants/nrf52840/ME25LS01-4Y10TD/variant.h +++ b/variants/nrf52840/ME25LS01-4Y10TD/variant.h @@ -51,7 +51,6 @@ extern "C" { #define PIN_LED1 (32 + 7) // P1.07 Blue D2 #define LED_PIN PIN_LED1 -#define LED_BUILTIN -1 #define LED_BLUE -1 #define LED_STATE_ON 1 // State when LED is lit diff --git a/variants/nrf52840/ME25LS01-4Y10TD_e-ink/variant.h b/variants/nrf52840/ME25LS01-4Y10TD_e-ink/variant.h index 797394ce6..683669160 100644 --- a/variants/nrf52840/ME25LS01-4Y10TD_e-ink/variant.h +++ b/variants/nrf52840/ME25LS01-4Y10TD_e-ink/variant.h @@ -51,7 +51,6 @@ extern "C" { #define PIN_LED1 (32 + 7) // P1.07 Blue D2 #define LED_PIN PIN_LED1 -#define LED_BUILTIN -1 #define LED_BLUE -1 #define LED_STATE_ON 1 // State when LED is lit diff --git a/variants/nrf52840/MS24SF1/variant.h b/variants/nrf52840/MS24SF1/variant.h index d26dcebc2..a71be99fd 100644 --- a/variants/nrf52840/MS24SF1/variant.h +++ b/variants/nrf52840/MS24SF1/variant.h @@ -51,7 +51,6 @@ extern "C" { #define PIN_LED1 (-1) #define LED_PIN PIN_LED1 -#define LED_BUILTIN -1 #define LED_BLUE -1 #define LED_STATE_ON 1 // State when LED is lit diff --git a/variants/nrf52840/MakePython_nRF52840_eink/variant.h b/variants/nrf52840/MakePython_nRF52840_eink/variant.h index 00c8dc199..91ca39d1d 100644 --- a/variants/nrf52840/MakePython_nRF52840_eink/variant.h +++ b/variants/nrf52840/MakePython_nRF52840_eink/variant.h @@ -29,7 +29,6 @@ extern "C" { #define PIN_LED1 (32 + 10) // LED P1.15 #define PIN_LED2 (-1) // -#define LED_BUILTIN PIN_LED1 #define LED_CONN PIN_LED2 #define LED_GREEN PIN_LED1 diff --git a/variants/nrf52840/MakePython_nRF52840_oled/variant.h b/variants/nrf52840/MakePython_nRF52840_oled/variant.h index 28d941171..286c5c251 100644 --- a/variants/nrf52840/MakePython_nRF52840_oled/variant.h +++ b/variants/nrf52840/MakePython_nRF52840_oled/variant.h @@ -29,7 +29,6 @@ extern "C" { #define PIN_LED1 (32 + 10) // LED P1.15 #define PIN_LED2 (-1) // -#define LED_BUILTIN PIN_LED1 #define LED_CONN PIN_LED2 #define LED_GREEN PIN_LED1 diff --git a/variants/nrf52840/TWC_mesh_v4/variant.h b/variants/nrf52840/TWC_mesh_v4/variant.h index 6a6f541e6..5b4623210 100644 --- a/variants/nrf52840/TWC_mesh_v4/variant.h +++ b/variants/nrf52840/TWC_mesh_v4/variant.h @@ -34,7 +34,6 @@ extern "C" { // #define PIN_LED1 (32 + 9) Green // #define PIN_LED1 (0 + 12) Blue -#define LED_BUILTIN PIN_LED1 #define LED_CONN PIN_LED2 #define LED_GREEN PIN_LED1 diff --git a/variants/nrf52840/canaryone/variant.h b/variants/nrf52840/canaryone/variant.h index 537f4e26c..4d388c376 100644 --- a/variants/nrf52840/canaryone/variant.h +++ b/variants/nrf52840/canaryone/variant.h @@ -52,7 +52,6 @@ extern "C" { #define LED_BLUE PIN_LED1 -#define LED_BUILTIN PIN_LED1 #define LED_CONN PIN_LED3 #define LED_STATE_ON 0 // State when LED is lit diff --git a/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/variant.h b/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/variant.h index 63af1fe79..8e10141f5 100644 --- a/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/variant.h +++ b/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/variant.h @@ -81,7 +81,6 @@ NRF52 PRO MICRO PIN ASSIGNMENT // LED #define PIN_LED1 (0 + 15) // P0.15 -#define LED_BUILTIN PIN_LED1 // Actually red #define LED_BLUE PIN_LED1 #define LED_STATE_ON 1 // State when LED is lit diff --git a/variants/nrf52840/diy/seeed-xiao-nrf52840-wio-sx1262/variant.h b/variants/nrf52840/diy/seeed-xiao-nrf52840-wio-sx1262/variant.h index 277377d71..407bc1541 100644 --- a/variants/nrf52840/diy/seeed-xiao-nrf52840-wio-sx1262/variant.h +++ b/variants/nrf52840/diy/seeed-xiao-nrf52840-wio-sx1262/variant.h @@ -38,7 +38,6 @@ extern "C" { #define PIN_LED PIN_LED1 #define LED_PWR (PINS_COUNT) -#define LED_BUILTIN PIN_LED #define LED_STATE_ON 1 // State when LED is lit // XIAO Wio-SX1262 Shield User button diff --git a/variants/nrf52840/dls_Minimesh_Lite/variant.h b/variants/nrf52840/dls_Minimesh_Lite/variant.h index f5163619b..32c16f06d 100644 --- a/variants/nrf52840/dls_Minimesh_Lite/variant.h +++ b/variants/nrf52840/dls_Minimesh_Lite/variant.h @@ -44,7 +44,6 @@ extern "C" { // LED #define PIN_LED1 (0 + 15) -#define LED_BUILTIN PIN_LED1 // Actually red #define LED_BLUE PIN_LED1 #define LED_STATE_ON 1 diff --git a/variants/nrf52840/feather_diy/variant.h b/variants/nrf52840/feather_diy/variant.h index 1c0979f82..a9782a8ee 100644 --- a/variants/nrf52840/feather_diy/variant.h +++ b/variants/nrf52840/feather_diy/variant.h @@ -49,8 +49,6 @@ extern "C" { #define PIN_LED1 (32 + 15) // P1.15 3 #define PIN_LED2 (32 + 10) // P1.10 4 -#define LED_BUILTIN PIN_LED1 - #define LED_GREEN PIN_LED2 // Actually red #define LED_BLUE PIN_LED1 diff --git a/variants/nrf52840/gat562_mesh_trial_tracker/variant.h b/variants/nrf52840/gat562_mesh_trial_tracker/variant.h index 6337ac70c..282953cd9 100644 --- a/variants/nrf52840/gat562_mesh_trial_tracker/variant.h +++ b/variants/nrf52840/gat562_mesh_trial_tracker/variant.h @@ -48,7 +48,6 @@ extern "C" { #define PIN_LED1 (35) #define PIN_LED2 (36) -#define LED_BUILTIN PIN_LED1 #define LED_CONN PIN_LED2 #define LED_GREEN PIN_LED1 diff --git a/variants/nrf52840/heltec_mesh_node_t114-inkhud/variant.h b/variants/nrf52840/heltec_mesh_node_t114-inkhud/variant.h index 14170d5f3..96fa35869 100644 --- a/variants/nrf52840/heltec_mesh_node_t114-inkhud/variant.h +++ b/variants/nrf52840/heltec_mesh_node_t114-inkhud/variant.h @@ -30,7 +30,6 @@ extern "C" { #define PIN_LED1 (32 + 3) // green (confirmed on 1.0 board) #define LED_BLUE PIN_LED1 // fake for bluefruit library #define LED_GREEN PIN_LED1 -#define LED_BUILTIN LED_GREEN #define LED_STATE_ON 0 // State when LED is lit #define HAS_NEOPIXEL // Enable the use of neopixels diff --git a/variants/nrf52840/heltec_mesh_node_t114/variant.h b/variants/nrf52840/heltec_mesh_node_t114/variant.h index cf14b5e04..b3aa942ff 100644 --- a/variants/nrf52840/heltec_mesh_node_t114/variant.h +++ b/variants/nrf52840/heltec_mesh_node_t114/variant.h @@ -74,7 +74,6 @@ extern "C" { #define PIN_LED1 (32 + 3) // green (confirmed on 1.0 board) #define LED_BLUE PIN_LED1 // fake for bluefruit library #define LED_GREEN PIN_LED1 -#define LED_BUILTIN LED_GREEN #define LED_STATE_ON 0 // State when LED is lit #define HAS_NEOPIXEL // Enable the use of neopixels diff --git a/variants/nrf52840/heltec_mesh_pocket/variant.h b/variants/nrf52840/heltec_mesh_pocket/variant.h index 7ec9b88ea..c80fefd58 100644 --- a/variants/nrf52840/heltec_mesh_pocket/variant.h +++ b/variants/nrf52840/heltec_mesh_pocket/variant.h @@ -26,7 +26,6 @@ extern "C" { #define LED_RED PIN_LED1 #define LED_BLUE PIN_LED1 #define LED_GREEN PIN_LED1 -#define LED_BUILTIN LED_BLUE #define LED_CONN LED_BLUE #define LED_STATE_ON 0 // State when LED is lit diff --git a/variants/nrf52840/heltec_mesh_solar/variant.h b/variants/nrf52840/heltec_mesh_solar/variant.h index c80b81da4..f05668e56 100644 --- a/variants/nrf52840/heltec_mesh_solar/variant.h +++ b/variants/nrf52840/heltec_mesh_solar/variant.h @@ -42,7 +42,6 @@ extern "C" { #define PIN_LED1 (0 + 4) // green (confirmed on 1.0 board) #define LED_BLUE PIN_LED1 // fake for bluefruit library #define LED_GREEN PIN_LED1 -#define LED_BUILTIN LED_GREEN #define LED_STATE_ON 0 // State when LED is lit #define HAS_NEOPIXEL // Enable the use of neopixels diff --git a/variants/nrf52840/meshlink/variant.h b/variants/nrf52840/meshlink/variant.h index 2e252ff95..9075d0467 100644 --- a/variants/nrf52840/meshlink/variant.h +++ b/variants/nrf52840/meshlink/variant.h @@ -31,7 +31,6 @@ extern "C" { // LEDs #define PIN_LED1 (24) // Built in white led for status #define LED_BLUE PIN_LED1 -#define LED_BUILTIN PIN_LED1 #define LED_STATE_ON 0 // State when LED is litted #define LED_INVERTED 1 diff --git a/variants/nrf52840/meshtiny/variant.h b/variants/nrf52840/meshtiny/variant.h index 8d634ba60..d2cffd169 100644 --- a/variants/nrf52840/meshtiny/variant.h +++ b/variants/nrf52840/meshtiny/variant.h @@ -49,7 +49,6 @@ extern "C" { #define PIN_LED1 (35) #define PIN_LED2 (36) -#define LED_BUILTIN PIN_LED1 #define LED_CONN PIN_LED2 #define LED_GREEN PIN_LED1 diff --git a/variants/nrf52840/monteops_hw1/variant.h b/variants/nrf52840/monteops_hw1/variant.h index 97536b169..e5896091b 100644 --- a/variants/nrf52840/monteops_hw1/variant.h +++ b/variants/nrf52840/monteops_hw1/variant.h @@ -52,7 +52,6 @@ extern "C" { #define PIN_LED1 (35) #define PIN_LED2 (36) // Connected to WWAN host LED (if present) -#define LED_BUILTIN PIN_LED1 #define LED_CONN PIN_LED2 #define LED_GREEN PIN_LED1 diff --git a/variants/nrf52840/muzi_base/variant.h b/variants/nrf52840/muzi_base/variant.h index 8a3532b77..1ac8d9c27 100644 --- a/variants/nrf52840/muzi_base/variant.h +++ b/variants/nrf52840/muzi_base/variant.h @@ -45,7 +45,6 @@ extern "C" { #define PIN_LED1 (32 + 3) // P1.03, Green #define PIN_LED2 (32 + 4) // P1.04, Blue -#define LED_BUILTIN -1 // PIN_LED1 #define LED_BLUE PIN_LED2 #define LED_STATE_ON 0 // State when LED is lit diff --git a/variants/nrf52840/nano-g2-ultra/variant.h b/variants/nrf52840/nano-g2-ultra/variant.h index d8f41a68c..8c3451835 100644 --- a/variants/nrf52840/nano-g2-ultra/variant.h +++ b/variants/nrf52840/nano-g2-ultra/variant.h @@ -50,7 +50,6 @@ extern "C" { #define LED_BLUE PIN_LED1 #define LED_GREEN PIN_LED2 -#define LED_BUILTIN LED_BLUE #define LED_CONN PIN_GREEN #define LED_STATE_ON 0 // State when LED is lit diff --git a/variants/nrf52840/r1-neo/variant.h b/variants/nrf52840/r1-neo/variant.h index b1d96ebd0..830126812 100644 --- a/variants/nrf52840/r1-neo/variant.h +++ b/variants/nrf52840/r1-neo/variant.h @@ -47,7 +47,6 @@ extern "C" { #define PIN_LED1 (32 + 4) // P1.04 Controls Green LED #define PIN_LED2 (28) // P0.28 Controls Blue LED -#define LED_BUILTIN PIN_LED1 #define LED_CONN PIN_LED2 #define LED_GREEN PIN_LED1 diff --git a/variants/nrf52840/rak2560/variant.h b/variants/nrf52840/rak2560/variant.h index f922e8a61..2fa0bd1e7 100644 --- a/variants/nrf52840/rak2560/variant.h +++ b/variants/nrf52840/rak2560/variant.h @@ -48,7 +48,6 @@ extern "C" { #define PIN_LED1 (35) #define PIN_LED2 (36) -#define LED_BUILTIN PIN_LED1 #define LED_CONN PIN_LED2 #define LED_GREEN PIN_LED1 diff --git a/variants/nrf52840/rak3401_1watt/variant.h b/variants/nrf52840/rak3401_1watt/variant.h index d4bb1a175..4cc565d98 100644 --- a/variants/nrf52840/rak3401_1watt/variant.h +++ b/variants/nrf52840/rak3401_1watt/variant.h @@ -47,7 +47,6 @@ extern "C" { #define PIN_LED1 (35) #define PIN_LED2 (36) -#define LED_BUILTIN PIN_LED1 #define LED_CONN PIN_LED2 #define LED_GREEN PIN_LED1 diff --git a/variants/nrf52840/rak4631/variant.h b/variants/nrf52840/rak4631/variant.h index 302e531d5..b0215ad28 100644 --- a/variants/nrf52840/rak4631/variant.h +++ b/variants/nrf52840/rak4631/variant.h @@ -47,7 +47,6 @@ extern "C" { #define PIN_LED1 (35) #define PIN_LED2 (36) -#define LED_BUILTIN PIN_LED1 #define LED_CONN PIN_LED2 #define LED_GREEN PIN_LED1 diff --git a/variants/nrf52840/rak4631_epaper/variant.h b/variants/nrf52840/rak4631_epaper/variant.h index c1e11bee5..574a0e68f 100644 --- a/variants/nrf52840/rak4631_epaper/variant.h +++ b/variants/nrf52840/rak4631_epaper/variant.h @@ -47,7 +47,6 @@ extern "C" { #define PIN_LED1 (35) #define PIN_LED2 (36) -#define LED_BUILTIN PIN_LED1 #define LED_CONN PIN_LED2 #define LED_GREEN PIN_LED1 diff --git a/variants/nrf52840/rak4631_epaper_onrxtx/variant.h b/variants/nrf52840/rak4631_epaper_onrxtx/variant.h index 1f8257e8e..7f1a5d112 100644 --- a/variants/nrf52840/rak4631_epaper_onrxtx/variant.h +++ b/variants/nrf52840/rak4631_epaper_onrxtx/variant.h @@ -29,7 +29,6 @@ extern "C" { #define PIN_LED1 (35) #define PIN_LED2 (36) -#define LED_BUILTIN PIN_LED1 #define LED_CONN PIN_LED2 #define LED_GREEN PIN_LED1 diff --git a/variants/nrf52840/rak4631_eth_gw/variant.h b/variants/nrf52840/rak4631_eth_gw/variant.h index c8a2f83ae..2e8ae129c 100644 --- a/variants/nrf52840/rak4631_eth_gw/variant.h +++ b/variants/nrf52840/rak4631_eth_gw/variant.h @@ -47,7 +47,6 @@ extern "C" { #define PIN_LED1 (35) #define PIN_LED2 (36) -#define LED_BUILTIN PIN_LED1 #define LED_CONN PIN_LED2 #define LED_GREEN PIN_LED1 diff --git a/variants/nrf52840/rak4631_nomadstar_meteor_pro/variant.h b/variants/nrf52840/rak4631_nomadstar_meteor_pro/variant.h index 51baf3ada..84793bfb3 100644 --- a/variants/nrf52840/rak4631_nomadstar_meteor_pro/variant.h +++ b/variants/nrf52840/rak4631_nomadstar_meteor_pro/variant.h @@ -47,7 +47,6 @@ extern "C" { #define PIN_LED1 (35) #define PIN_LED2 (36) -#define LED_BUILTIN PIN_LED1 #define LED_CONN PIN_LED2 #define LED_GREEN PIN_LED1 diff --git a/variants/nrf52840/rak_wismeshtag/variant.h b/variants/nrf52840/rak_wismeshtag/variant.h index fa3e252ab..c31443371 100644 --- a/variants/nrf52840/rak_wismeshtag/variant.h +++ b/variants/nrf52840/rak_wismeshtag/variant.h @@ -47,7 +47,6 @@ extern "C" { #define PIN_LED1 (35) #define PIN_LED2 (36) -#define LED_BUILTIN PIN_LED1 #define LED_CONN PIN_LED2 #define LED_GREEN PIN_LED1 diff --git a/variants/nrf52840/rak_wismeshtap/variant.h b/variants/nrf52840/rak_wismeshtap/variant.h index a7b9290a5..a6eccc3c8 100644 --- a/variants/nrf52840/rak_wismeshtap/variant.h +++ b/variants/nrf52840/rak_wismeshtap/variant.h @@ -47,7 +47,6 @@ extern "C" { #define PIN_LED1 (35) #define PIN_LED2 (36) -#define LED_BUILTIN PIN_LED1 #define LED_CONN PIN_LED2 #define LED_GREEN PIN_LED1 diff --git a/variants/nrf52840/seeed_solar_node/variant.h b/variants/nrf52840/seeed_solar_node/variant.h index b2a1e6dff..7473fd073 100644 --- a/variants/nrf52840/seeed_solar_node/variant.h +++ b/variants/nrf52840/seeed_solar_node/variant.h @@ -23,7 +23,6 @@ #define PIN_LED1 (12) // LED P1.15 #define PIN_LED2 (11) // -#define LED_BUILTIN PIN_LED1 #define LED_CONN PIN_LED2 #define LED_GREEN PIN_LED1 diff --git a/variants/nrf52840/seeed_wio_tracker_L1/variant.h b/variants/nrf52840/seeed_wio_tracker_L1/variant.h index b62b65161..70b35336b 100644 --- a/variants/nrf52840/seeed_wio_tracker_L1/variant.h +++ b/variants/nrf52840/seeed_wio_tracker_L1/variant.h @@ -23,7 +23,6 @@ #define PIN_LED1 (11) // LED P1.15 #define PIN_LED2 (12) // -#define LED_BUILTIN PIN_LED1 #define LED_CONN PIN_LED2 #define LED_GREEN PIN_LED1 diff --git a/variants/nrf52840/seeed_wio_tracker_L1_eink/variant.h b/variants/nrf52840/seeed_wio_tracker_L1_eink/variant.h index ae20f3c36..87aa5bb80 100644 --- a/variants/nrf52840/seeed_wio_tracker_L1_eink/variant.h +++ b/variants/nrf52840/seeed_wio_tracker_L1_eink/variant.h @@ -23,7 +23,6 @@ #define PIN_LED1 (11) // LED P1.15 #define PIN_LED2 (12) // -#define LED_BUILTIN PIN_LED1 #define LED_CONN PIN_LED2 #define LED_GREEN PIN_LED1 diff --git a/variants/nrf52840/seeed_xiao_nrf52840_kit/variant.h b/variants/nrf52840/seeed_xiao_nrf52840_kit/variant.h index 0844595da..e4305a57a 100644 --- a/variants/nrf52840/seeed_xiao_nrf52840_kit/variant.h +++ b/variants/nrf52840/seeed_xiao_nrf52840_kit/variant.h @@ -69,8 +69,6 @@ static const uint8_t A5 = PIN_A5; #define PIN_LED2 LED_BLUE #define PIN_LED3 LED_RED -#define LED_BUILTIN LED_RED // LED_BUILTIN is used by framework-arduinoadafruitnrf52 to indicate flash writes - #define LED_PWR LED_RED #define USER_LED LED_BLUE diff --git a/variants/nrf52840/t-echo-lite/variant.h b/variants/nrf52840/t-echo-lite/variant.h index 12dcb8949..de93b10f4 100644 --- a/variants/nrf52840/t-echo-lite/variant.h +++ b/variants/nrf52840/t-echo-lite/variant.h @@ -52,7 +52,6 @@ extern "C" { #define BLE_LED LED_BLUE #define BLE_LED_INVERTED 1 -#define LED_BUILTIN LED_GREEN #define LED_CONN LED_GREEN #define LED_STATE_ON 0 // State when LED is lit diff --git a/variants/nrf52840/t-echo-plus/variant.h b/variants/nrf52840/t-echo-plus/variant.h index 2d00f8c1f..7e5235902 100644 --- a/variants/nrf52840/t-echo-plus/variant.h +++ b/variants/nrf52840/t-echo-plus/variant.h @@ -25,7 +25,6 @@ extern "C" { #define LED_BLUE PIN_LED1 #define LED_GREEN PIN_LED2 -#define LED_BUILTIN LED_BLUE #define LED_CONN LED_GREEN #define LED_STATE_ON 0 diff --git a/variants/nrf52840/t-echo/variant.h b/variants/nrf52840/t-echo/variant.h index 8e72fd84b..7714f5ce3 100644 --- a/variants/nrf52840/t-echo/variant.h +++ b/variants/nrf52840/t-echo/variant.h @@ -52,7 +52,6 @@ extern "C" { #define LED_BLUE PIN_LED1 #define LED_GREEN PIN_LED2 -#define LED_BUILTIN LED_BLUE #define LED_CONN PIN_GREEN #define LED_STATE_ON 0 // State when LED is lit diff --git a/variants/nrf52840/tracker-t1000-e/variant.h b/variants/nrf52840/tracker-t1000-e/variant.h index 5b6719e12..143b7d4ad 100644 --- a/variants/nrf52840/tracker-t1000-e/variant.h +++ b/variants/nrf52840/tracker-t1000-e/variant.h @@ -48,7 +48,6 @@ extern "C" { #define PIN_LED1 (0 + 24) // P0.24 #define LED_PIN PIN_LED1 -#define LED_BUILTIN -1 #define LED_BLUE -1 // Actually green #define LED_STATE_ON 1 // State when LED is lit diff --git a/variants/nrf52840/wio-sdk-wm1110/variant.h b/variants/nrf52840/wio-sdk-wm1110/variant.h index b6e5c79df..d802d20f6 100644 --- a/variants/nrf52840/wio-sdk-wm1110/variant.h +++ b/variants/nrf52840/wio-sdk-wm1110/variant.h @@ -58,8 +58,6 @@ extern "C" { #define PIN_LED1 (0 + 13) // P0.13 #define PIN_LED2 (0 + 14) // P0.14 -#define LED_BUILTIN PIN_LED1 - #define LED_GREEN PIN_LED1 #define LED_BLUE PIN_LED2 // Actually red diff --git a/variants/nrf52840/wio-t1000-s/variant.h b/variants/nrf52840/wio-t1000-s/variant.h index 02f8a20b2..3b8103d85 100644 --- a/variants/nrf52840/wio-t1000-s/variant.h +++ b/variants/nrf52840/wio-t1000-s/variant.h @@ -48,7 +48,6 @@ extern "C" { #define PIN_LED1 (0 + 24) // P0.24 #define LED_PIN PIN_LED1 -#define LED_BUILTIN -1 #define LED_BLUE -1 // Actually green #define LED_STATE_ON 1 // State when LED is lit diff --git a/variants/nrf52840/wio-tracker-wm1110/variant.h b/variants/nrf52840/wio-tracker-wm1110/variant.h index 807ca8dbb..145750da5 100644 --- a/variants/nrf52840/wio-tracker-wm1110/variant.h +++ b/variants/nrf52840/wio-tracker-wm1110/variant.h @@ -54,8 +54,6 @@ extern "C" { #define PIN_LED1 (0 + 6) // P0.06 #define PIN_LED2 (PINS_COUNT) // P0.14 -#define LED_BUILTIN PIN_LED1 - #define LED_GREEN PIN_LED1 #define LED_BLUE PIN_LED2 diff --git a/variants/rp2040/challenger_2040_lora/pins_arduino.h b/variants/rp2040/challenger_2040_lora/pins_arduino.h index ac472c07e..5e7311413 100644 --- a/variants/rp2040/challenger_2040_lora/pins_arduino.h +++ b/variants/rp2040/challenger_2040_lora/pins_arduino.h @@ -45,8 +45,6 @@ #define SPI_HOWMANY (2u) #define WIRE_HOWMANY (1u) -#define LED_BUILTIN PIN_LED - static const uint8_t D0 = (16u); static const uint8_t D1 = (17u); static const uint8_t D2 = (20u); diff --git a/variants/rp2040/rak11310/pins_arduino.h b/variants/rp2040/rak11310/pins_arduino.h index 0e2808b19..95ee308bc 100644 --- a/variants/rp2040/rak11310/pins_arduino.h +++ b/variants/rp2040/rak11310/pins_arduino.h @@ -26,7 +26,6 @@ static const uint8_t A3 = PIN_A3; #define PIN_LED (23u) #define PIN_LED1 PIN_LED #define PIN_LED2 (24u) -#define LED_BUILTIN PIN_LED #define ADC_RESOLUTION 12 diff --git a/variants/rp2040/rak11310/variant.h b/variants/rp2040/rak11310/variant.h index 2400d56a7..36c77d209 100644 --- a/variants/rp2040/rak11310/variant.h +++ b/variants/rp2040/rak11310/variant.h @@ -11,7 +11,7 @@ #define I2C_SCL1 3 #define LED_CONN PIN_LED2 -#define LED_PIN LED_BUILTIN +#define LED_PIN PIN_LED #define ledOff(pin) pinMode(pin, INPUT) #define BUTTON_PIN 9 diff --git a/variants/rp2040/rpipicow/platformio.ini b/variants/rp2040/rpipicow/platformio.ini index 99e02a1aa..9b4b29a5b 100644 --- a/variants/rp2040/rpipicow/platformio.ini +++ b/variants/rp2040/rpipicow/platformio.ini @@ -22,6 +22,7 @@ build_flags = -D HW_SPI1_DEVICE -D HAS_UDP_MULTICAST=1 -fexceptions # for exception handling in MQTT + -ULED_BUILTIN build_src_filter = ${rp2040_base.build_src_filter} + lib_deps = ${rp2040_base.lib_deps} diff --git a/variants/rp2040/rpipicow/variant.h b/variants/rp2040/rpipicow/variant.h index 24da8f932..fe94e615d 100644 --- a/variants/rp2040/rpipicow/variant.h +++ b/variants/rp2040/rpipicow/variant.h @@ -19,7 +19,7 @@ #define EXT_NOTIFY_OUT 22 #define BUTTON_PIN 17 -#define LED_PIN LED_BUILTIN +#define LED_PIN PIN_LED #define BATTERY_PIN 26 // ratio of voltage divider = 3.0 (R17=200k, R18=100k) diff --git a/variants/rp2040/senselora_rp2040/pins_arduino.h b/variants/rp2040/senselora_rp2040/pins_arduino.h index bb0ee637e..61705c8d9 100644 --- a/variants/rp2040/senselora_rp2040/pins_arduino.h +++ b/variants/rp2040/senselora_rp2040/pins_arduino.h @@ -13,7 +13,6 @@ static const uint8_t A3 = PIN_A3; // LEDs #define PIN_LED (23u) #define PIN_LED1 PIN_LED -#define LED_BUILTIN PIN_LED #define ADC_RESOLUTION 12 diff --git a/variants/stm32/wio-e5/variant.h b/variants/stm32/wio-e5/variant.h index a312b31bd..2b20eb2a6 100644 --- a/variants/stm32/wio-e5/variant.h +++ b/variants/stm32/wio-e5/variant.h @@ -19,9 +19,4 @@ Do not expect a working Meshtastic device with this target. #define WIO_E5 -#if (defined(LED_BUILTIN) && LED_BUILTIN == PNUM_NOT_DEFINED) -#undef LED_BUILTIN -#define LED_BUILTIN (LED_PIN) -#endif - #endif From d44ceb6eb2d6cec2369e398a27ed286be4284242 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Wed, 28 Jan 2026 17:17:35 -0600 Subject: [PATCH 63/99] Fix NimBLE deinit null check --- src/nimble/NimbleBluetooth.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/nimble/NimbleBluetooth.cpp b/src/nimble/NimbleBluetooth.cpp index fc1f27ea2..2a59c0aab 100644 --- a/src/nimble/NimbleBluetooth.cpp +++ b/src/nimble/NimbleBluetooth.cpp @@ -686,6 +686,9 @@ class NimbleBluetoothServerCallback : public NimBLEServerCallbacks #ifdef NIMBLE_TWO if (ble->isDeInit) return; +#else + if (nimbleBluetooth && nimbleBluetooth->isDeInit) + return; #endif meshtastic::BluetoothStatus newStatus(meshtastic::BluetoothStatus::ConnectionState::DISCONNECTED); From d7d6fe7f0f1306d2c53e8782b4bc1da7c59e6fcf Mon Sep 17 00:00:00 2001 From: oscgonfer Date: Thu, 29 Jan 2026 00:18:42 +0100 Subject: [PATCH 64/99] Avoid short-circuit evaluation issues in Telemetry (#9467) * Make sensors in telemetry non-definitory Co-authored-by: Ben Meadors --- .../Telemetry/EnvironmentTelemetry.cpp | 23 +++++++++++++------ src/modules/Telemetry/HealthTelemetry.cpp | 9 +++++--- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/src/modules/Telemetry/EnvironmentTelemetry.cpp b/src/modules/Telemetry/EnvironmentTelemetry.cpp index 86a8606c2..140c2c17e 100644 --- a/src/modules/Telemetry/EnvironmentTelemetry.cpp +++ b/src/modules/Telemetry/EnvironmentTelemetry.cpp @@ -529,37 +529,46 @@ bool EnvironmentTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPac bool EnvironmentTelemetryModule::getEnvironmentTelemetry(meshtastic_Telemetry *m) { - bool valid = true; + bool valid = false; bool hasSensor = false; + // getMetrics() doesn't always get evaluated because of + // short-circuit evaluation rules in c++ + bool get_metrics; m->time = getTime(); m->which_variant = meshtastic_Telemetry_environment_metrics_tag; m->variant.environment_metrics = meshtastic_EnvironmentMetrics_init_zero; for (TelemetrySensor *sensor : sensors) { - valid = valid && sensor->getMetrics(m); + get_metrics = sensor->getMetrics(m); // avoid short-circuit evaluation rules + valid = valid || get_metrics; hasSensor = true; } #ifndef T1000X_SENSOR_EN if (ina219Sensor.hasSensor()) { - valid = valid && ina219Sensor.getMetrics(m); + get_metrics = ina219Sensor.getMetrics(m); + valid = valid || get_metrics; hasSensor = true; } if (ina260Sensor.hasSensor()) { - valid = valid && ina260Sensor.getMetrics(m); + get_metrics = ina260Sensor.getMetrics(m); + valid = valid || get_metrics; hasSensor = true; } if (ina3221Sensor.hasSensor()) { - valid = valid && ina3221Sensor.getMetrics(m); + get_metrics = ina3221Sensor.getMetrics(m); + valid = valid || get_metrics; hasSensor = true; } if (max17048Sensor.hasSensor()) { - valid = valid && max17048Sensor.getMetrics(m); + get_metrics = max17048Sensor.getMetrics(m); + valid = valid || get_metrics; hasSensor = true; } #endif #ifdef HAS_RAKPROT - valid = valid && rak9154Sensor.getMetrics(m); + get_metrics = rak9154Sensor.getMetrics(m); + valid = valid || get_metrics; hasSensor = true; #endif return valid && hasSensor; diff --git a/src/modules/Telemetry/HealthTelemetry.cpp b/src/modules/Telemetry/HealthTelemetry.cpp index 572f0281a..bb3555062 100644 --- a/src/modules/Telemetry/HealthTelemetry.cpp +++ b/src/modules/Telemetry/HealthTelemetry.cpp @@ -168,18 +168,21 @@ bool HealthTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPacket & bool HealthTelemetryModule::getHealthTelemetry(meshtastic_Telemetry *m) { - bool valid = true; + bool valid = false; bool hasSensor = false; + bool get_metrics; m->time = getTime(); m->which_variant = meshtastic_Telemetry_health_metrics_tag; m->variant.health_metrics = meshtastic_HealthMetrics_init_zero; if (max30102Sensor.hasSensor()) { - valid = valid && max30102Sensor.getMetrics(m); + get_metrics = max30102Sensor.getMetrics(m); + valid = valid || get_metrics; // avoid short-circuit evaluation rules hasSensor = true; } if (mlx90614Sensor.hasSensor()) { - valid = valid && mlx90614Sensor.getMetrics(m); + get_metrics = mlx90614Sensor.getMetrics(m); + valid = valid || get_metrics; hasSensor = true; } From 6ab2f02dbc3537461dbb1b39dd5cb2deabb2c726 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Wed, 28 Jan 2026 17:53:12 -0600 Subject: [PATCH 65/99] re-add unintentionally removed include --- src/modules/Modules.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/modules/Modules.cpp b/src/modules/Modules.cpp index c3463a07e..a24bd95b5 100644 --- a/src/modules/Modules.cpp +++ b/src/modules/Modules.cpp @@ -1,6 +1,7 @@ #include "configuration.h" #if !MESHTASTIC_EXCLUDE_INPUTBROKER #include "buzz/BuzzerFeedbackThread.h" +#include "modules/StatusLEDModule.h" #include "modules/SystemCommandsModule.h" #endif #if !MESHTASTIC_EXCLUDE_PKI From df400850c1dbb3062b4091ff82fcce0583ba568d Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Wed, 28 Jan 2026 18:56:57 -0600 Subject: [PATCH 66/99] Undefine LED_BUILTIN where needed --- variants/esp32/chatter2/platformio.ini | 1 + variants/esp32/diy/hydra/platformio.ini | 1 + variants/esp32/diy/v1/platformio.ini | 1 + variants/esp32/nano-g1-explorer/platformio.ini | 1 + variants/esp32/nano-g1/platformio.ini | 1 + variants/esp32/radiomaster_900_bandit/platformio.ini | 1 + variants/esp32/radiomaster_900_bandit_micro/platformio.ini | 1 + variants/esp32/radiomaster_900_bandit_nano/platformio.ini | 1 + variants/esp32/station-g1/platformio.ini | 1 + variants/esp32/tlora_v2_1_16/platformio.ini | 2 +- variants/esp32/tlora_v3_3_0_tcxo/platformio.ini | 3 ++- variants/esp32s3/heltec_capsule_sensor_v3/platformio.ini | 1 + variants/esp32s3/heltec_sensor_hub/platformio.ini | 1 + variants/esp32s3/heltec_wsl_v3/platformio.ini | 1 + 14 files changed, 15 insertions(+), 2 deletions(-) diff --git a/variants/esp32/chatter2/platformio.ini b/variants/esp32/chatter2/platformio.ini index a14e407a1..62d23b1e6 100644 --- a/variants/esp32/chatter2/platformio.ini +++ b/variants/esp32/chatter2/platformio.ini @@ -8,6 +8,7 @@ build_flags = -I variants/esp32/chatter2 -DMESHTASTIC_EXCLUDE_WEBSERVER=1 -DMESHTASTIC_EXCLUDE_PAXCOUNTER=1 + -ULED_BUILTIN lib_deps = ${esp32_base.lib_deps} diff --git a/variants/esp32/diy/hydra/platformio.ini b/variants/esp32/diy/hydra/platformio.ini index 3afd17e01..f23224f0b 100644 --- a/variants/esp32/diy/hydra/platformio.ini +++ b/variants/esp32/diy/hydra/platformio.ini @@ -14,3 +14,4 @@ build_flags = ${esp32_base.build_flags} -D DIY_V1 -I variants/esp32/diy/hydra + -ULED_BUILTIN diff --git a/variants/esp32/diy/v1/platformio.ini b/variants/esp32/diy/v1/platformio.ini index 3d31fc24a..6be2bfd09 100644 --- a/variants/esp32/diy/v1/platformio.ini +++ b/variants/esp32/diy/v1/platformio.ini @@ -17,3 +17,4 @@ build_flags = -D DIY_V1 -D EBYTE_E22 -I variants/esp32/diy/v1 + -ULED_BUILTIN diff --git a/variants/esp32/nano-g1-explorer/platformio.ini b/variants/esp32/nano-g1-explorer/platformio.ini index 703bb9d09..16ecb99cc 100644 --- a/variants/esp32/nano-g1-explorer/platformio.ini +++ b/variants/esp32/nano-g1-explorer/platformio.ini @@ -14,3 +14,4 @@ build_flags = ${esp32_base.build_flags} -D NANO_G1_EXPLORER -I variants/esp32/nano-g1-explorer + -ULED_BUILTIN diff --git a/variants/esp32/nano-g1/platformio.ini b/variants/esp32/nano-g1/platformio.ini index b0ebd191c..724e008c7 100644 --- a/variants/esp32/nano-g1/platformio.ini +++ b/variants/esp32/nano-g1/platformio.ini @@ -14,3 +14,4 @@ build_flags = ${esp32_base.build_flags} -D NANO_G1 -I variants/esp32/nano-g1 + -ULED_BUILTIN diff --git a/variants/esp32/radiomaster_900_bandit/platformio.ini b/variants/esp32/radiomaster_900_bandit/platformio.ini index 6729235ed..0012f49d3 100644 --- a/variants/esp32/radiomaster_900_bandit/platformio.ini +++ b/variants/esp32/radiomaster_900_bandit/platformio.ini @@ -9,6 +9,7 @@ build_flags = -DHAS_STK8XXX=1 -O2 -I variants/esp32/radiomaster_900_bandit + -ULED_BUILTIN board_build.f_cpu = 240000000L upload_protocol = esptool lib_deps = diff --git a/variants/esp32/radiomaster_900_bandit_micro/platformio.ini b/variants/esp32/radiomaster_900_bandit_micro/platformio.ini index 32e9280e1..e58d06f1e 100644 --- a/variants/esp32/radiomaster_900_bandit_micro/platformio.ini +++ b/variants/esp32/radiomaster_900_bandit_micro/platformio.ini @@ -13,5 +13,6 @@ build_flags = -DCONFIG_DISABLE_HAL_LOCKS=1 -O2 -I variants/esp32/radiomaster_900_bandit_nano + -ULED_BUILTIN board_build.f_cpu = 240000000L upload_protocol = esptool diff --git a/variants/esp32/radiomaster_900_bandit_nano/platformio.ini b/variants/esp32/radiomaster_900_bandit_nano/platformio.ini index 924447ee4..7b3d187bf 100644 --- a/variants/esp32/radiomaster_900_bandit_nano/platformio.ini +++ b/variants/esp32/radiomaster_900_bandit_nano/platformio.ini @@ -16,5 +16,6 @@ build_flags = -DCONFIG_DISABLE_HAL_LOCKS=1 -O2 -I variants/esp32/radiomaster_900_bandit_nano + -ULED_BUILTIN board_build.f_cpu = 240000000L upload_protocol = esptool diff --git a/variants/esp32/station-g1/platformio.ini b/variants/esp32/station-g1/platformio.ini index ab7fcac2b..fad003b20 100644 --- a/variants/esp32/station-g1/platformio.ini +++ b/variants/esp32/station-g1/platformio.ini @@ -14,3 +14,4 @@ build_flags = ${esp32_base.build_flags} -D STATION_G1 -I variants/esp32/station-g1 + -ULED_BUILTIN diff --git a/variants/esp32/tlora_v2_1_16/platformio.ini b/variants/esp32/tlora_v2_1_16/platformio.ini index d9cb8ed3b..dfdbcb152 100644 --- a/variants/esp32/tlora_v2_1_16/platformio.ini +++ b/variants/esp32/tlora_v2_1_16/platformio.ini @@ -12,7 +12,7 @@ extends = esp32_base board = ttgo-lora32-v21 board_check = true build_flags = - ${esp32_base.build_flags} -D TLORA_V2_1_16 -I variants/esp32/tlora_v2_1_16 + ${esp32_base.build_flags} -D TLORA_V2_1_16 -I variants/esp32/tlora_v2_1_16 -ULED_BUILTIN upload_speed = 115200 [env:sugarcube] diff --git a/variants/esp32/tlora_v3_3_0_tcxo/platformio.ini b/variants/esp32/tlora_v3_3_0_tcxo/platformio.ini index 1258fd8b7..38f14ffc5 100644 --- a/variants/esp32/tlora_v3_3_0_tcxo/platformio.ini +++ b/variants/esp32/tlora_v3_3_0_tcxo/platformio.ini @@ -6,4 +6,5 @@ build_flags = -D TLORA_V2_1_16 -I variants/esp32/tlora_v2_1_16 -D LORA_TCXO_GPIO=12 - -D BUTTON_PIN=0 \ No newline at end of file + -D BUTTON_PIN=0 + -ULED_BUILTIN \ No newline at end of file diff --git a/variants/esp32s3/heltec_capsule_sensor_v3/platformio.ini b/variants/esp32s3/heltec_capsule_sensor_v3/platformio.ini index 0bb21581a..6dd828433 100644 --- a/variants/esp32s3/heltec_capsule_sensor_v3/platformio.ini +++ b/variants/esp32s3/heltec_capsule_sensor_v3/platformio.ini @@ -6,4 +6,5 @@ board_build.partitions = default_8MB.csv build_flags = ${esp32s3_base.build_flags} -I variants/esp32s3/heltec_capsule_sensor_v3 -D HELTEC_CAPSULE_SENSOR_V3 + -ULED_BUILTIN ;-D DEBUG_DISABLED ; uncomment this line to disable DEBUG output diff --git a/variants/esp32s3/heltec_sensor_hub/platformio.ini b/variants/esp32s3/heltec_sensor_hub/platformio.ini index ded0c22fe..3f93c7ad2 100644 --- a/variants/esp32s3/heltec_sensor_hub/platformio.ini +++ b/variants/esp32s3/heltec_sensor_hub/platformio.ini @@ -7,6 +7,7 @@ build_flags = ${esp32s3_base.build_flags} -I variants/esp32s3/heltec_sensor_hub -D HELTEC_SENSOR_HUB + -ULED_BUILTIN lib_deps = ${esp32s3_base.lib_deps} # renovate: datasource=custom.pio depName=NeoPixel packageName=adafruit/library/Adafruit NeoPixel diff --git a/variants/esp32s3/heltec_wsl_v3/platformio.ini b/variants/esp32s3/heltec_wsl_v3/platformio.ini index 0903a6bc7..873300c3c 100644 --- a/variants/esp32s3/heltec_wsl_v3/platformio.ini +++ b/variants/esp32s3/heltec_wsl_v3/platformio.ini @@ -17,3 +17,4 @@ build_flags = ${esp32s3_base.build_flags} -D HELTEC_WSL_V3 -I variants/esp32s3/heltec_wsl_v3 + -ULED_BUILTIN From b2f2f6b305e9cdc66fdb129fd6795b642fd96c2f Mon Sep 17 00:00:00 2001 From: Quency-D <55523105+Quency-D@users.noreply.github.com> Date: Thu, 29 Jan 2026 10:41:27 +0800 Subject: [PATCH 67/99] Add a watchdog module to meshsolar. (#9337) * add watchdog module * Restore the code in power.h --------- Co-authored-by: Ben Meadors --- src/modules/Modules.cpp | 6 +++ src/watchdog/watchdogThread.cpp | 37 +++++++++++++++++++ src/watchdog/watchdogThread.h | 19 ++++++++++ variants/nrf52840/heltec_mesh_solar/variant.h | 20 ++++++---- 4 files changed, 75 insertions(+), 7 deletions(-) create mode 100644 src/watchdog/watchdogThread.cpp create mode 100644 src/watchdog/watchdogThread.h diff --git a/src/modules/Modules.cpp b/src/modules/Modules.cpp index a24bd95b5..e8da8e983 100644 --- a/src/modules/Modules.cpp +++ b/src/modules/Modules.cpp @@ -91,6 +91,9 @@ #include "modules/DropzoneModule.h" #endif +#if defined(HAS_HARDWARE_WATCHDOG) +#include "watchdog/watchdogThread.h" +#endif /** * Create module instances here. If you are adding a new module, you must 'new' it here (or somewhere else) */ @@ -229,6 +232,9 @@ void setupModules() #if !MESHTASTIC_EXCLUDE_RANGETEST && !MESHTASTIC_EXCLUDE_GPS if (moduleConfig.has_range_test && moduleConfig.range_test.enabled) new RangeTestModule(); +#endif +#if defined(HAS_HARDWARE_WATCHDOG) + watchdogThread = new WatchdogThread(); #endif // NOTE! This module must be added LAST because it likes to check for replies from other modules and avoid sending extra // acks diff --git a/src/watchdog/watchdogThread.cpp b/src/watchdog/watchdogThread.cpp new file mode 100644 index 000000000..ff329f62b --- /dev/null +++ b/src/watchdog/watchdogThread.cpp @@ -0,0 +1,37 @@ +#include "watchdogThread.h" +#include "configuration.h" + +#ifdef HAS_HARDWARE_WATCHDOG +WatchdogThread *watchdogThread; + +WatchdogThread::WatchdogThread() : OSThread("Watchdog") +{ + setup(); +} + +void WatchdogThread::feedDog(void) +{ + digitalWrite(HARDWARE_WATCHDOG_DONE, HIGH); + delay(1); + digitalWrite(HARDWARE_WATCHDOG_DONE, LOW); +} + +int32_t WatchdogThread::runOnce() +{ + LOG_DEBUG("Feeding hardware watchdog"); + feedDog(); + return HARDWARE_WATCHDOG_TIMEOUT_MS; +} + +bool WatchdogThread::setup() +{ + LOG_DEBUG("init hardware watchdog"); + pinMode(HARDWARE_WATCHDOG_WAKE, INPUT); + pinMode(HARDWARE_WATCHDOG_DONE, OUTPUT); + delay(1); + digitalWrite(HARDWARE_WATCHDOG_DONE, LOW); + delay(1); + feedDog(); + return true; +} +#endif \ No newline at end of file diff --git a/src/watchdog/watchdogThread.h b/src/watchdog/watchdogThread.h new file mode 100644 index 000000000..5756afa46 --- /dev/null +++ b/src/watchdog/watchdogThread.h @@ -0,0 +1,19 @@ +#pragma once + +#include +#include "concurrency/OSThread.h" + +#ifdef HAS_HARDWARE_WATCHDOG +class WatchdogThread : private concurrency::OSThread +{ + public: + + WatchdogThread(); + void feedDog(void); + virtual bool setup(); + virtual int32_t runOnce() override; + +}; + +extern WatchdogThread *watchdogThread; +#endif diff --git a/variants/nrf52840/heltec_mesh_solar/variant.h b/variants/nrf52840/heltec_mesh_solar/variant.h index f05668e56..02cf724c3 100644 --- a/variants/nrf52840/heltec_mesh_solar/variant.h +++ b/variants/nrf52840/heltec_mesh_solar/variant.h @@ -39,15 +39,15 @@ extern "C" { #define NUM_ANALOG_INPUTS (1) #define NUM_ANALOG_OUTPUTS (0) -#define PIN_LED1 (0 + 4) // green (confirmed on 1.0 board) +#define PIN_LED1 (32 + 15) // green (confirmed on 1.0 board) #define LED_BLUE PIN_LED1 // fake for bluefruit library #define LED_GREEN PIN_LED1 #define LED_STATE_ON 0 // State when LED is lit -#define HAS_NEOPIXEL // Enable the use of neopixels -#define NEOPIXEL_COUNT 1 // How many neopixels are connected -#define NEOPIXEL_DATA (32 + 15) // gpio pin used to send data to the neopixels -#define NEOPIXEL_TYPE (NEO_GRB + NEO_KHZ800) // type of neopixels in use +// #define HAS_NEOPIXEL // Enable the use of neopixels +// #define NEOPIXEL_COUNT 1 // How many neopixels are connected +// #define NEOPIXEL_DATA (32 + 15) // gpio pin used to send data to the neopixels +// #define NEOPIXEL_TYPE (NEO_GRB + NEO_KHZ800) // type of neopixels in use /* * Buttons @@ -59,8 +59,8 @@ extern "C" { /* No longer populated on PCB */ -#define PIN_SERIAL2_RX (0 + 9) -#define PIN_SERIAL2_TX (0 + 10) +#define PIN_SERIAL2_RX (-1) +#define PIN_SERIAL2_TX (-1) /* * I2C @@ -137,6 +137,12 @@ No longer populated on PCB // To debug via the segger JLINK console rather than the CDC-ACM serial device // #define USE_SEGGER +// Hardware watchdog +#define HAS_HARDWARE_WATCHDOG +#define HARDWARE_WATCHDOG_DONE (0 + 9) +#define HARDWARE_WATCHDOG_WAKE (0 + 10) +#define HARDWARE_WATCHDOG_TIMEOUT_MS (6*60*1000) // 6 minute watchdog + #define BQ4050_SDA_PIN (32 + 1) // I2C data line pin #define BQ4050_SCL_PIN (32 + 0) // I2C clock line pin #define BQ4050_EMERGENCY_SHUTDOWN_PIN (32 + 3) // Emergency shutdown pin From 415686dd067f06d2f6df3e044ec20240e2126e48 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Thu, 29 Jan 2026 05:56:19 -0600 Subject: [PATCH 68/99] Trunk --- src/watchdog/watchdogThread.cpp | 2 +- src/watchdog/watchdogThread.h | 4 +--- variants/nrf52840/heltec_mesh_solar/variant.h | 6 +++--- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/watchdog/watchdogThread.cpp b/src/watchdog/watchdogThread.cpp index ff329f62b..3e8a5466f 100644 --- a/src/watchdog/watchdogThread.cpp +++ b/src/watchdog/watchdogThread.cpp @@ -10,7 +10,7 @@ WatchdogThread::WatchdogThread() : OSThread("Watchdog") } void WatchdogThread::feedDog(void) -{ +{ digitalWrite(HARDWARE_WATCHDOG_DONE, HIGH); delay(1); digitalWrite(HARDWARE_WATCHDOG_DONE, LOW); diff --git a/src/watchdog/watchdogThread.h b/src/watchdog/watchdogThread.h index 5756afa46..3a3830aa4 100644 --- a/src/watchdog/watchdogThread.h +++ b/src/watchdog/watchdogThread.h @@ -1,18 +1,16 @@ #pragma once -#include #include "concurrency/OSThread.h" +#include #ifdef HAS_HARDWARE_WATCHDOG class WatchdogThread : private concurrency::OSThread { public: - WatchdogThread(); void feedDog(void); virtual bool setup(); virtual int32_t runOnce() override; - }; extern WatchdogThread *watchdogThread; diff --git a/variants/nrf52840/heltec_mesh_solar/variant.h b/variants/nrf52840/heltec_mesh_solar/variant.h index 02cf724c3..298475401 100644 --- a/variants/nrf52840/heltec_mesh_solar/variant.h +++ b/variants/nrf52840/heltec_mesh_solar/variant.h @@ -39,8 +39,8 @@ extern "C" { #define NUM_ANALOG_INPUTS (1) #define NUM_ANALOG_OUTPUTS (0) -#define PIN_LED1 (32 + 15) // green (confirmed on 1.0 board) -#define LED_BLUE PIN_LED1 // fake for bluefruit library +#define PIN_LED1 (32 + 15) // green (confirmed on 1.0 board) +#define LED_BLUE PIN_LED1 // fake for bluefruit library #define LED_GREEN PIN_LED1 #define LED_STATE_ON 0 // State when LED is lit @@ -141,7 +141,7 @@ No longer populated on PCB #define HAS_HARDWARE_WATCHDOG #define HARDWARE_WATCHDOG_DONE (0 + 9) #define HARDWARE_WATCHDOG_WAKE (0 + 10) -#define HARDWARE_WATCHDOG_TIMEOUT_MS (6*60*1000) // 6 minute watchdog +#define HARDWARE_WATCHDOG_TIMEOUT_MS (6 * 60 * 1000) // 6 minute watchdog #define BQ4050_SDA_PIN (32 + 1) // I2C data line pin #define BQ4050_SCL_PIN (32 + 0) // I2C clock line pin From 03084f6d3bbf20fcac7421f7bbff516b126e46cb Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Thu, 29 Jan 2026 06:23:52 -0600 Subject: [PATCH 69/99] PRs with needs-review still should get bot labeled --- .github/workflows/models_pr_triage.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/models_pr_triage.yml b/.github/workflows/models_pr_triage.yml index ef303c02a..d4c8509d2 100644 --- a/.github/workflows/models_pr_triage.yml +++ b/.github/workflows/models_pr_triage.yml @@ -88,9 +88,10 @@ jobs: # ───────────────────────────────────────────────────────────────────────── # Step 3: Auto-label PR type (bugfix/hardware-support/enhancement) + # Only skip for spam/ai-generated; still classify needs-review PRs # ───────────────────────────────────────────────────────────────────────── - name: Classify PR for labeling - if: steps.check-labels.outputs.skip_all != 'true' && steps.check-labels.outputs.has_type_label != 'true' && (steps.quality.outputs.response == 'ok' || steps.quality.outputs.response == '') + if: steps.check-labels.outputs.skip_all != 'true' && steps.check-labels.outputs.has_type_label != 'true' && steps.quality.outputs.response != 'spam' && steps.quality.outputs.response != 'ai-generated' uses: actions/ai-inference@v2 id: classify continue-on-error: true From b18742c2112309199150f39cb5a85a718290690e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 29 Jan 2026 06:25:07 -0600 Subject: [PATCH 70/99] Update libch341-spi-userspace digest to af9bc27 (#9472) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- variants/native/portduino.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/variants/native/portduino.ini b/variants/native/portduino.ini index b86420291..f99ee9a6f 100644 --- a/variants/native/portduino.ini +++ b/variants/native/portduino.ini @@ -29,7 +29,7 @@ lib_deps = # renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX lovyan03/LovyanGFX@1.2.19 # renovate: datasource=git-refs depName=libch341-spi-userspace packageName=https://github.com/pine64/libch341-spi-userspace gitBranch=main - https://github.com/pine64/libch341-spi-userspace/archive/23c42319a69cffcb65868e3c72e6bed83974a393.zip + https://github.com/pine64/libch341-spi-userspace/archive/af9bc27c9c30fa90772279925b7c5913dff789b4.zip # renovate: datasource=custom.pio depName=adafruit/Adafruit seesaw Library packageName=adafruit/library/Adafruit seesaw Library adafruit/Adafruit seesaw Library@1.7.9 # renovate: datasource=git-refs depName=RAK12034-BMX160 packageName=https://github.com/RAKWireless/RAK12034-BMX160 gitBranch=main From 334a4f04cdd236214d1cd6cceb75a2c6b5a4bfd4 Mon Sep 17 00:00:00 2001 From: "Justin E. Mann" Date: Thu, 29 Jan 2026 05:29:15 -0700 Subject: [PATCH 71/99] Fix logic for rak12035 sensor default config and improve messaging (#9414) * better logic to check if the RAK12035 soil sensor is calibrated, better log messaging if either of the default values were used. * . * changes to how default calibration is done and a message it the default calibration is used pointing to the actual calibration sketch so the user can find it and use it to improve accuracy. --------- Co-authored-by: Ben Meadors --- .../Telemetry/Sensor/RAK12035Sensor.cpp | 42 +++++++++++-------- 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/src/modules/Telemetry/Sensor/RAK12035Sensor.cpp b/src/modules/Telemetry/Sensor/RAK12035Sensor.cpp index 626cc0e87..4f3150b25 100644 --- a/src/modules/Telemetry/Sensor/RAK12035Sensor.cpp +++ b/src/modules/Telemetry/Sensor/RAK12035Sensor.cpp @@ -26,7 +26,7 @@ bool RAK12035Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) sensor.get_sensor_version(&data); if (data != 0) { LOG_INFO("Init sensor: %s", sensorName); - LOG_INFO("RAK12035Sensor Init Succeed \nSensor1 Firmware version: %i, Sensor Name: %s", data, sensorName); + LOG_INFO("RAK12035Sensor Init Succeed \nSensor Firmware version: %i, Sensor Name: %s", data, sensorName); status = true; sensor.sensor_sleep(); RESTORE_3V3_POWER(); @@ -49,33 +49,39 @@ void RAK12035Sensor::setup() // TODO:: Check for and run calibration check for up to 2 additional sensors if present. uint16_t zero_val = 0; uint16_t hundred_val = 0; - uint16_t default_zero_val = 550; - uint16_t default_hundred_val = 420; + const uint16_t default_zero_val = 510; + const uint16_t default_hundred_val = 390; + sensor.sensor_on(); + sensor.begin(); delay(200); sensor.get_dry_cal(&zero_val); + delay(200); sensor.get_wet_cal(&hundred_val); delay(200); - if (zero_val == 0 || zero_val <= hundred_val) { - LOG_INFO("Dry calibration value is %d", zero_val); - LOG_INFO("Wet calibration value is %d", hundred_val); - LOG_INFO("This does not make sense. You can recalibrate this sensor using the calibration sketch included here: " - "https://github.com/RAKWireless/RAK12035_SoilMoisture."); - LOG_INFO("For now, setting default calibration value for Dry Calibration: %d", default_zero_val); + + bool calibrationReset = false; + + if (zero_val == 0) { + LOG_INFO("Dry calibration not set, using default: %d", default_zero_val); sensor.set_dry_cal(default_zero_val); - sensor.get_dry_cal(&zero_val); - LOG_INFO("Dry calibration reset complete. New value is %d", zero_val); + delay(200); + zero_val = default_zero_val; + calibrationReset = true; } if (hundred_val == 0 || hundred_val >= zero_val) { - LOG_INFO("Dry calibration value is %d", zero_val); - LOG_INFO("Wet calibration value is %d", hundred_val); - LOG_INFO("This does not make sense. You can recalibrate this sensor using the calibration sketch included here: " - "https://github.com/RAKWireless/RAK12035_SoilMoisture."); - LOG_INFO("For now, setting default calibration value for Wet Calibration: %d", default_hundred_val); + LOG_INFO("Wet calibration not set, using default: %d", default_hundred_val); sensor.set_wet_cal(default_hundred_val); - sensor.get_wet_cal(&hundred_val); - LOG_INFO("Wet calibration reset complete. New value is %d", hundred_val); + delay(200); + hundred_val = default_hundred_val; + calibrationReset = true; } + if (calibrationReset) { + LOG_INFO("Default calibration values applied. Consider running the calibration sketch for better accuracy: " + "https://github.com/RAKWireless/RAK12035_SoilMoisture"); + } + + LOG_INFO("Dry calibration value: %d, Wet calibration value: %d", zero_val, hundred_val); sensor.sensor_sleep(); RESTORE_3V3_POWER(); delay(200); From 31bf51b3f2c72627d67499b2d8b942cbe8372da2 Mon Sep 17 00:00:00 2001 From: treysis Date: Thu, 29 Jan 2026 13:38:49 +0100 Subject: [PATCH 72/99] Add support for the hardware buttons on Bluetooth Nugget device (#9468) Co-authored-by: Ben Meadors --- variants/esp32s3/nugget_s3_lora/variant.h | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/variants/esp32s3/nugget_s3_lora/variant.h b/variants/esp32s3/nugget_s3_lora/variant.h index 8e6057d5b..1354d0837 100644 --- a/variants/esp32s3/nugget_s3_lora/variant.h +++ b/variants/esp32s3/nugget_s3_lora/variant.h @@ -12,7 +12,7 @@ #define NEOPIXEL_TYPE (NEO_GRB + NEO_KHZ800) // type of neopixels in use // Button A (44), B (43), R (12), U (13), L (11), D (18) -#define BUTTON_PIN 44 // If defined, this will be used for user button presses +#define BUTTON_PIN 43 // If defined, this will be used for user button presses #define BUTTON_NEED_PULLUP #define USE_RF95 @@ -20,8 +20,19 @@ #define LORA_MISO 7 #define LORA_MOSI 8 #define LORA_CS 9 -#define LORA_DIO0 16 // a No connect on the SX1262 module +#define LORA_DIO0 16 #define LORA_RESET 4 #define LORA_DIO1 RADIOLIB_NC -#define LORA_DIO2 RADIOLIB_NC \ No newline at end of file +#define LORA_DIO2 RADIOLIB_NC + +// jk, its not really a trackball but we're gonna pretend! +#define HAS_TRACKBALL 1 +#define TB_UP 13 +#define TB_DOWN 18 +#define TB_LEFT 11 +#define TB_RIGHT 12 +#define TB_PRESS 44 // BUTTON_PIN +#define TB_DIRECTION FALLING + +#define ENABLE_AMBIENTLIGHTING From 1f7ed6888a2cd7dcf058958c73ef2fc3d1eadccb Mon Sep 17 00:00:00 2001 From: Andrew Yong Date: Thu, 29 Jan 2026 20:43:48 +0800 Subject: [PATCH 73/99] feat(stm32): Add Milesight GS301 Bathroom Odor Detector (#9359) - STM32WLE5CCU6 - NFC (unsupported): NXP NT3H2211W0FTTJ (NTAG I2C plus: NFC Forum T2T with I2C interface, password protection and energy harvesting) - Sensor (unsupported): Analog ADuCM355 (SHTC3 is connected to ADuCM355 and not directly accessible) - Bicolor LED - User button (presently not functional in STM32 variants) The definitions for sensor voltage control are present but commented out to save power, due to lack of sensor support. Powered by 4x 4000mAh RAMWAY ER18505 Li-SOCl2 batteries. Flashing: 1. Power down device (remove batteries) 2. Connect USB-UART to J1 (USART2), pinout is below, do not connect +3V3 pin yet 3. Short BOOT pins next to J1 4. Connect +3V3 pin or insert batteries while BOOT pins are shorted 5. Use STM32CubeProgrammer, connect by UART mode 6. Load firmware .hex and download J1 (USART2); Molex Picoblade (P=1.25mm * 4) 1. +3V3 2. PA3_USART2_RX_J1 3. PA2_USART2_TX_J1 4. GND Signed-off-by: Andrew Yong Co-authored-by: Ben Meadors --- variants/stm32/milesight_gs301/platformio.ini | 21 ++++++++++ variants/stm32/milesight_gs301/rfswitch.h | 7 ++++ variants/stm32/milesight_gs301/variant.h | 41 +++++++++++++++++++ 3 files changed, 69 insertions(+) create mode 100644 variants/stm32/milesight_gs301/platformio.ini create mode 100644 variants/stm32/milesight_gs301/rfswitch.h create mode 100644 variants/stm32/milesight_gs301/variant.h diff --git a/variants/stm32/milesight_gs301/platformio.ini b/variants/stm32/milesight_gs301/platformio.ini new file mode 100644 index 000000000..a1d3fb34e --- /dev/null +++ b/variants/stm32/milesight_gs301/platformio.ini @@ -0,0 +1,21 @@ +; Milesight GS301 Bathroom Odor Detector +; https://www.milesight.com/iot/product/lorawan-sensor/gs301 +[env:milesight_gs301] +extends = stm32_base +board = wiscore_rak3172 ; Convenient choice as the same USART is used for programming/debug +board_level = extra +board_upload.maximum_size = 233472 ; reserve the last 28KB for filesystem +build_flags = + ${stm32_base.build_flags} + -Ivariants/stm32/milesight_gs301 + -DPRIVATE_HW + -DMESHTASTIC_EXCLUDE_GPS=1 + -DMESHTASTIC_EXCLUDE_I2C=1 # Analog ADuCM355 (unsupported) so no point building support for I2C in + -DMESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR=1 + -DMESHTASTIC_EXCLUDE_AIR_QUALITY_SENSOR=1 +build_unflags = + -DDEBUG_MUTE # We have space for debug output until sensor support is added +lib_deps = + ${stm32_base.lib_deps} + +upload_port = stlink diff --git a/variants/stm32/milesight_gs301/rfswitch.h b/variants/stm32/milesight_gs301/rfswitch.h new file mode 100644 index 000000000..d9f60038a --- /dev/null +++ b/variants/stm32/milesight_gs301/rfswitch.h @@ -0,0 +1,7 @@ +// Seems to use the same RF switch pins as RAK3172… getting Tx/Rx SNR +11dB with a nearby node +// PB8, PC13 + +static const RADIOLIB_PIN_TYPE rfswitch_pins[5] = {PB8, PC13, RADIOLIB_NC, RADIOLIB_NC, RADIOLIB_NC}; + +static const Module::RfSwitchMode_t rfswitch_table[4] = { + {STM32WLx::MODE_IDLE, {LOW, LOW}}, {STM32WLx::MODE_RX, {HIGH, LOW}}, {STM32WLx::MODE_TX_HP, {LOW, HIGH}}, END_OF_MODE_TABLE}; diff --git a/variants/stm32/milesight_gs301/variant.h b/variants/stm32/milesight_gs301/variant.h new file mode 100644 index 000000000..e86e93fc4 --- /dev/null +++ b/variants/stm32/milesight_gs301/variant.h @@ -0,0 +1,41 @@ +#ifndef _VARIANT_MILESIGHT_GS301_ +#define _VARIANT_MILESIGHT_GS301_ + +#define USE_STM32WLx + +// I/O +#define LED_STATE_ON 1 +#define PIN_LED1 PA0 // Green LED +#define LED_PIN PIN_LED1 +#define PIN_LED2 PA0 // Red LED +#define USER_LED PIN_LED2 +#define BUTTON_PIN PC13 +#define BUTTON_ACTIVE_LOW true +#define BUTTON_ACTIVE_PULLUP false +#define PIN_BUZZER PA6 + +// EC Sense DGM10 Double Gas Module +// Analog ADuCM355 (unsupported); SHTC3 is connected to ADuCM355 and not directly accessible +#define PIN_WIRE_SDA PB7 +#define PIN_WIRE_SCL PB8 +// Commented out to keep sensor powered down due to lack of support +/* +#define VEXT_ENABLE PB12 // TI TPS61291DRV VSEL - set LOW before ENable for Vout = 3.3V +#define VEXT_ON_VALUE LOW +#define SENSOR_POWER_CTRL_PIN PB2 // TI TPS61291DRV EN pin +#define SENSOR_POWER_ON HIGH +#define HAS_SENSOR 1 +*/ + +#define ENABLE_HWSERIAL1 +#define PIN_SERIAL1_RX NC +#define PIN_SERIAL1_TX PB6 + +// LoRa +#define SX126X_DIO3_TCXO_VOLTAGE 3.0 + +// Required to avoid Serial1 conflicts due to board definition here: +// https://github.com/stm32duino/Arduino_Core_STM32/blob/main/variants/STM32WLxx/WL54CCU_WL55CCU_WLE4C(8-B-C)U_WLE5C(8-B-C)U/variant_RAK3172_MODULE.h +#define RAK3172 + +#endif From 8af9e7fbdc912ba5b423e4c7885c3380f5d73b88 Mon Sep 17 00:00:00 2001 From: Jorropo Date: Thu, 29 Jan 2026 13:44:17 +0100 Subject: [PATCH 74/99] enable long interleaving mode for LR11x0 and SX128x (#9399) Using long interleaving is not a breaking change, the receiver node is able to use the lora header to know if LI encoding is used or not and will decode LI packets correctly. However the problem is SX127x and other first generation LoRa IP which do not support LI at all, for theses it is a breaking change. HOWEVER due to the sync word bug the LR11x0 already can't talk with SX127x, so if we enable LI on theses no one would be able to tell. Same for SX128x altho this is because it works on 2.4Ghz which is incompatible with SX127x. Co-authored-by: Ben Meadors --- src/mesh/LR11x0Interface.cpp | 2 +- src/mesh/SX128xInterface.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mesh/LR11x0Interface.cpp b/src/mesh/LR11x0Interface.cpp index a8a2780ed..7c73b56cd 100644 --- a/src/mesh/LR11x0Interface.cpp +++ b/src/mesh/LR11x0Interface.cpp @@ -170,7 +170,7 @@ template bool LR11x0Interface::reconfigure() if (err != RADIOLIB_ERR_NONE) RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING); - err = lora.setCodingRate(cr); + err = lora.setCodingRate(cr, cr != 7); // use long interleaving except if CR is 4/7 which doesn't support it if (err != RADIOLIB_ERR_NONE) RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING); diff --git a/src/mesh/SX128xInterface.cpp b/src/mesh/SX128xInterface.cpp index 3ab63df3d..9fcedfe49 100644 --- a/src/mesh/SX128xInterface.cpp +++ b/src/mesh/SX128xInterface.cpp @@ -126,7 +126,7 @@ template bool SX128xInterface::reconfigure() if (err != RADIOLIB_ERR_NONE) RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING); - err = lora.setCodingRate(cr); + err = lora.setCodingRate(cr, cr != 7); // use long interleaving except if CR is 4/7 which doesn't support it if (err != RADIOLIB_ERR_NONE) RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING); From 61b39acc7d1d2c15317b2100c960df1d5e69b3d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Agust=C3=ADn=20Mista?= Date: Thu, 29 Jan 2026 17:06:58 +0100 Subject: [PATCH 75/99] Add initial Nix shell (#8530) * Add initial Nix shell * Update flake.nix Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Ben Meadors Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .envrc | 1 + .gitignore | 3 +++ flake.lock | 44 ++++++++++++++++++++++++++++++++++++ flake.nix | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ shell.nix | 12 ++++++++++ 5 files changed, 126 insertions(+) create mode 100644 .envrc create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 shell.nix diff --git a/.envrc b/.envrc new file mode 100644 index 000000000..65326bb6d --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use nix \ No newline at end of file diff --git a/.gitignore b/.gitignore index 769603202..d6d97c6c4 100644 --- a/.gitignore +++ b/.gitignore @@ -50,3 +50,6 @@ idf_component.yml CMakeLists.txt /sdkconfig.* .dummy/* + +# PYTHONPATH used by the Nix shell +.python3 diff --git a/flake.lock b/flake.lock new file mode 100644 index 000000000..e7a4c7ff7 --- /dev/null +++ b/flake.lock @@ -0,0 +1,44 @@ +{ + "nodes": { + "flake-compat": { + "flake": false, + "locked": { + "lastModified": 1767039857, + "narHash": "sha256-vNpUSpF5Nuw8xvDLj2KCwwksIbjua2LZCqhV1LNRDns=", + "owner": "NixOS", + "repo": "flake-compat", + "rev": "5edf11c44bc78a0d334f6334cdaf7d60d732daab", + "type": "github" + }, + "original": { + "owner": "NixOS", + "repo": "flake-compat", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1766314097, + "narHash": "sha256-laJftWbghBehazn/zxVJ8NdENVgjccsWAdAqKXhErrM=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "306ea70f9eb0fb4e040f8540e2deab32ed7e2055", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-compat": "flake-compat", + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 000000000..1af493c6d --- /dev/null +++ b/flake.nix @@ -0,0 +1,66 @@ +{ + description = "Nix flake to compile Meshtastic firmware"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; + + # Shim to make flake.nix work with stable Nix. + flake-compat = { + url = "github:NixOS/flake-compat"; + flake = false; + }; + }; + + outputs = + inputs: + let + lib = inputs.nixpkgs.lib; + + forAllSystems = + fn: + lib.genAttrs lib.systems.flakeExposed ( + system: + fn { + pkgs = import inputs.nixpkgs { + inherit system; + }; + inherit system; + } + ); + in + { + devShells = forAllSystems ( + { pkgs, ... }: + let + python3 = pkgs.python312.withPackages ( + ps: with ps; [ + google + ] + ); + in + { + default = pkgs.mkShell { + buildInputs = with pkgs; [ + python3 + platformio + ]; + + shellHook = '' + # Set up PlatformIO to use a local core directory. + export PLATFORMIO_CORE_DIR=$PWD/.platformio + # Tell pip to put packages into $PIP_PREFIX instead of the usual + # location. This is especially necessary under NixOS to avoid having + # pip trying to write to the read-only Nix store. For more info, + # see https://wiki.nixos.org/wiki/Python + export PIP_PREFIX=$PWD/.python3 + export PYTHONPATH="$PIP_PREFIX/${python3.sitePackages}" + export PATH="$PIP_PREFIX/bin:$PATH" + # Avoids reproducibility issues with some Python packages + # See https://nixos.org/manual/nixpkgs/stable/#python-setup.py-bdist_wheel-cannot-create-.whl + unset SOURCE_DATE_EPOCH + ''; + }; + } + ); + }; +} diff --git a/shell.nix b/shell.nix new file mode 100644 index 000000000..692cd4df8 --- /dev/null +++ b/shell.nix @@ -0,0 +1,12 @@ +(import ( + let + lock = builtins.fromJSON (builtins.readFile ./flake.lock); + nodeName = lock.nodes.root.inputs.flake-compat; + in + fetchTarball { + url = + lock.nodes.${nodeName}.locked.url + or "https://github.com/NixOS/flake-compat/archive/${lock.nodes.${nodeName}.locked.rev}.tar.gz"; + sha256 = lock.nodes.${nodeName}.locked.narHash; + } +) { src = ./.; }).shellNix From 45fbc0f9d330bced8d23b1f97e9d3d8191f2538c Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Thu, 29 Jan 2026 10:58:06 -0600 Subject: [PATCH 76/99] Remove stale variant.h defines (#9470) * Remove noop CANNED_MESSAGE_MODULE_ENABLE define * Remove over-eager warning removal * Remove unused LED_CONN * Dead defines removal * Rename oddball LED pin name * Rename second oddball LED pin name * Remove another dead define --- src/input/SerialKeyboard.cpp | 1 - src/modules/CannedMessageModule.cpp | 3 +-- src/modules/CannedMessageModule.h | 4 ---- src/modules/StatusLEDModule.cpp | 1 - src/sleep.cpp | 1 - variants/esp32/chatter2/variant.h | 1 - variants/esp32/m5stack_core/platformio.ini | 1 - variants/esp32/tbeam/variant.h | 1 - variants/esp32/trackerd/variant.h | 1 - variants/esp32s3/hackaday-communicator/variant.h | 12 ------------ variants/esp32s3/picomputer-s3/variant.h | 2 -- variants/esp32s3/rak3312/variant.h | 1 - variants/esp32s3/rak_wismesh_tap_v2/variant.h | 3 --- variants/esp32s3/t-deck-pro/variant.h | 1 - variants/esp32s3/t-deck/variant.h | 1 - variants/esp32s3/tlora-pager/variant.h | 1 - variants/esp32s3/tracksenger/internal/variant.h | 1 - variants/esp32s3/tracksenger/lcd/variant.h | 1 - variants/esp32s3/tracksenger/oled/variant.h | 1 - variants/native/portduino-buildroot/variant.h | 1 - variants/native/portduino/variant.h | 1 - .../nrf52840/Dongle_nRF52840-pca10059-v1/variant.h | 2 -- variants/nrf52840/ELECROW-ThinkNode-M1/variant.h | 3 --- variants/nrf52840/ELECROW-ThinkNode-M3/variant.cpp | 12 ++++++------ variants/nrf52840/ELECROW-ThinkNode-M3/variant.h | 8 ++++---- variants/nrf52840/MakePython_nRF52840_eink/variant.h | 2 -- variants/nrf52840/MakePython_nRF52840_oled/variant.h | 2 -- variants/nrf52840/TWC_mesh_v4/variant.h | 2 -- variants/nrf52840/canaryone/variant.h | 2 -- .../diy/seeed-xiao-nrf52840-wio-sx1262/variant.h | 1 - .../nrf52840/gat562_mesh_trial_tracker/variant.h | 4 ---- .../nrf52840/heltec_mesh_node_t114-inkhud/variant.h | 2 -- variants/nrf52840/heltec_mesh_node_t114/variant.h | 2 -- variants/nrf52840/heltec_mesh_pocket/variant.h | 3 --- variants/nrf52840/heltec_mesh_solar/variant.h | 2 -- variants/nrf52840/meshtiny/variant.h | 4 ---- variants/nrf52840/monteops_hw1/variant.h | 4 ---- variants/nrf52840/nano-g2-ultra/variant.h | 4 ---- variants/nrf52840/r1-neo/variant.h | 2 -- variants/nrf52840/rak2560/variant.h | 4 ---- variants/nrf52840/rak3401_1watt/variant.h | 2 -- variants/nrf52840/rak4631/variant.h | 4 ---- variants/nrf52840/rak4631_epaper/variant.h | 4 ---- variants/nrf52840/rak4631_epaper_onrxtx/variant.h | 2 -- variants/nrf52840/rak4631_eth_gw/variant.h | 4 ---- .../nrf52840/rak4631_nomadstar_meteor_pro/variant.h | 4 ---- variants/nrf52840/rak_wismeshtag/variant.h | 4 ---- variants/nrf52840/rak_wismeshtap/variant.h | 6 ------ variants/nrf52840/seeed_solar_node/variant.h | 2 -- variants/nrf52840/seeed_wio_tracker_L1/variant.h | 4 ---- .../nrf52840/seeed_wio_tracker_L1_eink/variant.h | 3 --- variants/nrf52840/seeed_xiao_nrf52840_kit/variant.h | 1 - variants/nrf52840/t-echo-lite/variant.h | 1 - variants/nrf52840/t-echo-plus/variant.h | 2 -- variants/nrf52840/t-echo/variant.h | 3 --- variants/rp2040/rak11310/variant.h | 1 - 56 files changed, 11 insertions(+), 141 deletions(-) diff --git a/src/input/SerialKeyboard.cpp b/src/input/SerialKeyboard.cpp index a5d2c614f..8037b0d57 100644 --- a/src/input/SerialKeyboard.cpp +++ b/src/input/SerialKeyboard.cpp @@ -5,7 +5,6 @@ SerialKeyboard *globalSerialKeyboard = nullptr; #ifdef INPUTBROKER_SERIAL_TYPE -#define CANNED_MESSAGE_MODULE_ENABLE 1 // in case it's not set in the variant file #if INPUTBROKER_SERIAL_TYPE == 1 // It's a Chatter // 3 SHIFT level (lower case, upper case, numbers), up to 4 repeated presses, button number diff --git a/src/modules/CannedMessageModule.cpp b/src/modules/CannedMessageModule.cpp index 8d1ba6346..7d7b3cdb1 100644 --- a/src/modules/CannedMessageModule.cpp +++ b/src/modules/CannedMessageModule.cpp @@ -130,8 +130,7 @@ CannedMessageModule::CannedMessageModule() : SinglePortModule("canned", meshtastic_PortNum_TEXT_MESSAGE_APP), concurrency::OSThread("CannedMessage") { this->loadProtoForModule(); - if ((this->splitConfiguredMessages() <= 0) && (cardkb_found.address == 0x00) && !INPUTBROKER_MATRIX_TYPE && - !CANNED_MESSAGE_MODULE_ENABLE) { + if ((this->splitConfiguredMessages() <= 0) && (cardkb_found.address == 0x00) && !INPUTBROKER_MATRIX_TYPE) { LOG_INFO("CannedMessageModule: No messages are configured. Module is disabled"); this->runState = CANNED_MESSAGE_RUN_STATE_DISABLED; disable(); diff --git a/src/modules/CannedMessageModule.h b/src/modules/CannedMessageModule.h index 3d7c09d87..65715dd22 100644 --- a/src/modules/CannedMessageModule.h +++ b/src/modules/CannedMessageModule.h @@ -27,10 +27,6 @@ enum CannedMessageModuleIconType { shift, backspace, space, enter }; #define CANNED_MESSAGE_MODULE_MESSAGE_MAX_COUNT 50 #define CANNED_MESSAGE_MODULE_MESSAGES_SIZE 800 -#ifndef CANNED_MESSAGE_MODULE_ENABLE -#define CANNED_MESSAGE_MODULE_ENABLE 0 -#endif - // ============================ // Data Structures // ============================ diff --git a/src/modules/StatusLEDModule.cpp b/src/modules/StatusLEDModule.cpp index fed035513..e7a405bdf 100644 --- a/src/modules/StatusLEDModule.cpp +++ b/src/modules/StatusLEDModule.cpp @@ -130,7 +130,6 @@ int32_t StatusLEDModule::runOnce() #ifdef LED_CHARGE digitalWrite(LED_CHARGE, CHARGE_LED_state); #endif - // digitalWrite(green_LED_PIN, LED_STATE_OFF); #ifdef LED_PAIRING digitalWrite(LED_PAIRING, PAIRING_LED_state); #endif diff --git a/src/sleep.cpp b/src/sleep.cpp index 756582c74..8b30a5352 100644 --- a/src/sleep.cpp +++ b/src/sleep.cpp @@ -241,7 +241,6 @@ void doDeepSleep(uint32_t msecToWake, bool skipPreflight = false, bool skipSaveN #ifdef PIN_POWER_EN digitalWrite(PIN_POWER_EN, LOW); pinMode(PIN_POWER_EN, INPUT); // power off peripherals - // pinMode(PIN_POWER_EN1, INPUT_PULLDOWN); #endif #ifdef RAK_WISMESH_TAP_V2 diff --git a/variants/esp32/chatter2/variant.h b/variants/esp32/chatter2/variant.h index abcb1ce4d..28ce64f91 100644 --- a/variants/esp32/chatter2/variant.h +++ b/variants/esp32/chatter2/variant.h @@ -98,7 +98,6 @@ #define KB_LOAD 21 // load values from the switch and store in shift register #define KB_CLK 22 // clock pin for serial data out #define KB_DATA 23 // data pin -#define CANNED_MESSAGE_MODULE_ENABLE 1 ///////////////////////////////////////////////////////////////////////////////// // // diff --git a/variants/esp32/m5stack_core/platformio.ini b/variants/esp32/m5stack_core/platformio.ini index 5e41d5f2b..8fbbae895 100644 --- a/variants/esp32/m5stack_core/platformio.ini +++ b/variants/esp32/m5stack_core/platformio.ini @@ -30,7 +30,6 @@ build_flags = -DTFT_BL=32 -DSPI_FREQUENCY=40000000 -DSPI_READ_FREQUENCY=16000000 - -DDISABLE_ALL_LIBRARY_WARNINGS lib_ignore = m5stack-core lib_deps = diff --git a/variants/esp32/tbeam/variant.h b/variants/esp32/tbeam/variant.h index 2d144a888..2c1e61c49 100644 --- a/variants/esp32/tbeam/variant.h +++ b/variants/esp32/tbeam/variant.h @@ -57,7 +57,6 @@ #ifndef TOUCH_IRQ #define TOUCH_IRQ -1 #endif -#define CANNED_MESSAGE_MODULE_ENABLE 1 #define USE_VIRTUAL_KEYBOARD 1 #define ST7796_NSS 25 diff --git a/variants/esp32/trackerd/variant.h b/variants/esp32/trackerd/variant.h index c4dfb9e93..0996e85ac 100644 --- a/variants/esp32/trackerd/variant.h +++ b/variants/esp32/trackerd/variant.h @@ -10,7 +10,6 @@ #define LED_PIN 13 // 13 red, 2 blue, 15 red -// #define HAS_BUTTON 0 #define BUTTON_PIN 0 #define BUTTON_NEED_PULLUP diff --git a/variants/esp32s3/hackaday-communicator/variant.h b/variants/esp32s3/hackaday-communicator/variant.h index a127f548f..7e048d72c 100644 --- a/variants/esp32s3/hackaday-communicator/variant.h +++ b/variants/esp32s3/hackaday-communicator/variant.h @@ -16,22 +16,12 @@ #define SLEEP_TIME 120 #define GPS_DEFAULT_NOT_PRESENT 1 -// #define GPS_RX_PIN 44 -// #define GPS_TX_PIN 43 - -// #define BATTERY_PIN 4 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage -// ratio of voltage divider = 2.0 (RD2=100k, RD3=100k) -// #define ADC_MULTIPLIER 2.11 // 2.0 + 10% for correction of display undervoltage. -// #define ADC_CHANNEL ADC1_GPIO4_CHANNEL // keyboard #define I2C_SDA 47 // I2C pins for this board #define I2C_SCL 14 -// #define KB_POWERON -1 // must be set to HIGH -// #define KB_SLAVE_ADDRESS TDECK_KB_ADDR // 0x55 // #define KB_BL_PIN 46 // not used for now #define KB_INT 13 -#define CANNED_MESSAGE_MODULE_ENABLE 1 #define TFT_DC 39 #define TFT_CS 41 @@ -44,11 +34,9 @@ #define LORA_MOSI 3 #define LORA_CS 17 -// #define LORA_DIO0 -1 // a No connect on the SX1262 module #define LORA_RESET 18 #define LORA_DIO1 16 // SX1262 IRQ #define LORA_DIO2 15 // SX1262 BUSY -// #define LORA_DIO3 // Not connected on PCB, but internally on the TTGO SX1262, if DIO3 is high the TXCO is enabled #define SX126X_CS LORA_CS #define SX126X_DIO1 LORA_DIO1 diff --git a/variants/esp32s3/picomputer-s3/variant.h b/variants/esp32s3/picomputer-s3/variant.h index 275da1b61..7b6218f87 100644 --- a/variants/esp32s3/picomputer-s3/variant.h +++ b/variants/esp32s3/picomputer-s3/variant.h @@ -52,8 +52,6 @@ // Picomputer gets a white on black display #define TFT_MESH_OVERRIDE COLOR565(255, 255, 255) -#define CANNED_MESSAGE_MODULE_ENABLE 1 - #define INPUTBROKER_MATRIX_TYPE 1 #define KEYS_COLS \ diff --git a/variants/esp32s3/rak3312/variant.h b/variants/esp32s3/rak3312/variant.h index 1f8eb9e39..4d860e476 100644 --- a/variants/esp32s3/rak3312/variant.h +++ b/variants/esp32s3/rak3312/variant.h @@ -24,7 +24,6 @@ #define PIN_LED1 LED_GREEN #define PIN_LED2 LED_BLUE -#define LED_CONN LED_BLUE #define LED_PIN LED_GREEN #define ledOff(pin) pinMode(pin, INPUT) diff --git a/variants/esp32s3/rak_wismesh_tap_v2/variant.h b/variants/esp32s3/rak_wismesh_tap_v2/variant.h index 2fc056557..f94b7ca4e 100644 --- a/variants/esp32s3/rak_wismesh_tap_v2/variant.h +++ b/variants/esp32s3/rak_wismesh_tap_v2/variant.h @@ -32,7 +32,6 @@ #define PIN_LED1 LED_GREEN #define PIN_LED2 LED_BLUE -#define LED_CONN LED_BLUE #define LED_PIN LED_GREEN #define ledOff(pin) pinMode(pin, INPUT) @@ -47,10 +46,8 @@ #define SPI_MISO (10) #define SPI_CS (12) -#define HAS_BUTTON 1 #define BUTTON_PIN 0 -#define CANNED_MESSAGE_MODULE_ENABLE 1 #define USE_VIRTUAL_KEYBOARD 1 #define BATTERY_PIN 1 diff --git a/variants/esp32s3/t-deck-pro/variant.h b/variants/esp32s3/t-deck-pro/variant.h index 36a1310f1..d95f07f3a 100644 --- a/variants/esp32s3/t-deck-pro/variant.h +++ b/variants/esp32s3/t-deck-pro/variant.h @@ -43,7 +43,6 @@ // TCA8418 keyboard #define KB_BL_PIN 42 -#define CANNED_MESSAGE_MODULE_ENABLE 1 // microphone PCM5102A #define PCM5102A_SCK 47 diff --git a/variants/esp32s3/t-deck/variant.h b/variants/esp32s3/t-deck/variant.h index ab5b74870..5d885579a 100644 --- a/variants/esp32s3/t-deck/variant.h +++ b/variants/esp32s3/t-deck/variant.h @@ -61,7 +61,6 @@ #define KB_POWERON 10 // must be set to HIGH #define KB_SLAVE_ADDRESS TDECK_KB_ADDR // 0x55 #define KB_BL_PIN 46 // not used for now -#define CANNED_MESSAGE_MODULE_ENABLE 1 // trackball #define HAS_TRACKBALL 1 diff --git a/variants/esp32s3/tlora-pager/variant.h b/variants/esp32s3/tlora-pager/variant.h index 565f4f580..e2d4029be 100644 --- a/variants/esp32s3/tlora-pager/variant.h +++ b/variants/esp32s3/tlora-pager/variant.h @@ -61,7 +61,6 @@ #define I2C_NO_RESCAN #define KB_BL_PIN 46 #define KB_INT 6 -#define CANNED_MESSAGE_MODULE_ENABLE 1 // audio codec ES8311 #define HAS_I2S diff --git a/variants/esp32s3/tracksenger/internal/variant.h b/variants/esp32s3/tracksenger/internal/variant.h index 2287dfe0b..ba3e281c8 100644 --- a/variants/esp32s3/tracksenger/internal/variant.h +++ b/variants/esp32s3/tracksenger/internal/variant.h @@ -78,7 +78,6 @@ // keyboard changes #define PIN_BUZZER 43 -#define CANNED_MESSAGE_MODULE_ENABLE 1 #define INPUTBROKER_MATRIX_TYPE 1 diff --git a/variants/esp32s3/tracksenger/lcd/variant.h b/variants/esp32s3/tracksenger/lcd/variant.h index f42a5b19f..a9bb89d68 100644 --- a/variants/esp32s3/tracksenger/lcd/variant.h +++ b/variants/esp32s3/tracksenger/lcd/variant.h @@ -102,7 +102,6 @@ // keyboard changes #define PIN_BUZZER 43 -#define CANNED_MESSAGE_MODULE_ENABLE 1 #define INPUTBROKER_MATRIX_TYPE 1 diff --git a/variants/esp32s3/tracksenger/oled/variant.h b/variants/esp32s3/tracksenger/oled/variant.h index 85cc019c4..689864b32 100644 --- a/variants/esp32s3/tracksenger/oled/variant.h +++ b/variants/esp32s3/tracksenger/oled/variant.h @@ -79,7 +79,6 @@ // keyboard changes #define PIN_BUZZER 43 -#define CANNED_MESSAGE_MODULE_ENABLE 1 #define INPUTBROKER_MATRIX_TYPE 1 diff --git a/variants/native/portduino-buildroot/variant.h b/variants/native/portduino-buildroot/variant.h index affd83051..ac6421b14 100644 --- a/variants/native/portduino-buildroot/variant.h +++ b/variants/native/portduino-buildroot/variant.h @@ -1,6 +1,5 @@ #define HAS_SCREEN 1 #define USE_TFTDISPLAY 1 -#define CANNED_MESSAGE_MODULE_ENABLE 1 #define HAS_GPS 1 #define MAX_RX_TOPHONE portduino_config.maxtophone #define MAX_NUM_NODES portduino_config.MaxNodes diff --git a/variants/native/portduino/variant.h b/variants/native/portduino/variant.h index 972443450..cba4aaedd 100644 --- a/variants/native/portduino/variant.h +++ b/variants/native/portduino/variant.h @@ -2,7 +2,6 @@ #define HAS_SCREEN 1 #endif #define USE_TFTDISPLAY 1 -#define CANNED_MESSAGE_MODULE_ENABLE 1 #define HAS_GPS 1 #define MAX_RX_TOPHONE portduino_config.maxtophone #define MAX_NUM_NODES portduino_config.MaxNodes diff --git a/variants/nrf52840/Dongle_nRF52840-pca10059-v1/variant.h b/variants/nrf52840/Dongle_nRF52840-pca10059-v1/variant.h index d120e485c..8baf25e87 100644 --- a/variants/nrf52840/Dongle_nRF52840-pca10059-v1/variant.h +++ b/variants/nrf52840/Dongle_nRF52840-pca10059-v1/variant.h @@ -50,8 +50,6 @@ extern "C" { #define RGBLED_BLUE (0 + 12) // Blue of RGB P0.12 #define RGBLED_CA // comment out this line if you have a common cathode type, as defined use common anode logic -#define LED_CONN PIN_LED2 - #define LED_GREEN PIN_LED1 #define LED_BLUE PIN_LED2 diff --git a/variants/nrf52840/ELECROW-ThinkNode-M1/variant.h b/variants/nrf52840/ELECROW-ThinkNode-M1/variant.h index c36211a63..a8c7b42b5 100644 --- a/variants/nrf52840/ELECROW-ThinkNode-M1/variant.h +++ b/variants/nrf52840/ELECROW-ThinkNode-M1/variant.h @@ -55,7 +55,6 @@ extern "C" { #define LED_RED PIN_LED3 #define LED_BLUE PIN_LED1 #define LED_GREEN PIN_LED2 -#define LED_CONN PIN_GREEN #define LED_STATE_ON 0 // State when LED is lit // LED灯亮时的状态 #define PIN_BUZZER (0 + 6) /* @@ -170,8 +169,6 @@ External serial flash WP25R1635FZUIL0 #define PIN_SPI_MOSI (0 + 22) #define PIN_SPI_SCK (0 + 19) -#define PIN_PWR_EN (0 + 6) - // To debug via the segger JLINK console rather than the CDC-ACM serial device // #define USE_SEGGER diff --git a/variants/nrf52840/ELECROW-ThinkNode-M3/variant.cpp b/variants/nrf52840/ELECROW-ThinkNode-M3/variant.cpp index 9769e3edd..45a64ad3b 100644 --- a/variants/nrf52840/ELECROW-ThinkNode-M3/variant.cpp +++ b/variants/nrf52840/ELECROW-ThinkNode-M3/variant.cpp @@ -37,8 +37,8 @@ void initVariant() digitalWrite(KEY_POWER, HIGH); pinMode(RGB_POWER, OUTPUT); digitalWrite(RGB_POWER, HIGH); - pinMode(green_LED_PIN, OUTPUT); - digitalWrite(green_LED_PIN, LED_STATE_OFF); + pinMode(LED_GREEN, OUTPUT); + digitalWrite(LED_GREEN, LED_STATE_OFF); pinMode(LED_BLUE, OUTPUT); pinMode(PIN_POWER_USB, INPUT); pinMode(PIN_POWER_DONE, INPUT); @@ -63,8 +63,8 @@ void initVariant() // called from main-nrf52.cpp during the cpuDeepSleep() function void variant_shutdown() { - digitalWrite(red_LED_PIN, HIGH); - digitalWrite(green_LED_PIN, HIGH); + digitalWrite(LED_RED, HIGH); + digitalWrite(LED_GREEN, HIGH); digitalWrite(LED_BLUE, HIGH); digitalWrite(PIN_EN1, LOW); @@ -81,8 +81,8 @@ void variant_shutdown() if (pin == PIN_POWER_USB || pin == BUTTON_PIN || pin == PIN_EN1 || pin == PIN_EN2 || pin == DHT_POWER || pin == ACC_POWER || pin == Battery_POWER || pin == GPS_POWER || pin == LR1110_SPI_MISO_PIN || pin == LR1110_SPI_MOSI_PIN || pin == LR1110_SPI_SCK_PIN || pin == LR1110_SPI_NSS_PIN || pin == LR1110_BUSY_PIN || - pin == LR1110_NRESET_PIN || pin == LR1110_IRQ_PIN || pin == GPS_TX_PIN || pin == GPS_RX_PIN || pin == green_LED_PIN || - pin == red_LED_PIN || pin == LED_BLUE) { + pin == LR1110_NRESET_PIN || pin == LR1110_IRQ_PIN || pin == GPS_TX_PIN || pin == GPS_RX_PIN || pin == LED_GREEN || + pin == LED_RED || pin == LED_BLUE) { continue; } pinMode(pin, OUTPUT); diff --git a/variants/nrf52840/ELECROW-ThinkNode-M3/variant.h b/variants/nrf52840/ELECROW-ThinkNode-M3/variant.h index acbcf6ae5..955a16a88 100644 --- a/variants/nrf52840/ELECROW-ThinkNode-M3/variant.h +++ b/variants/nrf52840/ELECROW-ThinkNode-M3/variant.h @@ -50,11 +50,11 @@ extern "C" { #define EEPROM_POWER 7 // LED -#define red_LED_PIN 33 -#define LED_POWER red_LED_PIN +#define LED_RED 33 +#define LED_POWER LED_RED #define LED_CHARGE LED_POWER // Signals the Status LED Module to handle this LED -#define green_LED_PIN 35 -#define PIN_LED2 green_LED_PIN +#define LED_GREEN 35 +#define PIN_LED2 LED_GREEN #define LED_BLUE 37 #define LED_PAIRING LED_BLUE // Signals the Status LED Module to handle this LED diff --git a/variants/nrf52840/MakePython_nRF52840_eink/variant.h b/variants/nrf52840/MakePython_nRF52840_eink/variant.h index 91ca39d1d..48256e65f 100644 --- a/variants/nrf52840/MakePython_nRF52840_eink/variant.h +++ b/variants/nrf52840/MakePython_nRF52840_eink/variant.h @@ -29,8 +29,6 @@ extern "C" { #define PIN_LED1 (32 + 10) // LED P1.15 #define PIN_LED2 (-1) // -#define LED_CONN PIN_LED2 - #define LED_GREEN PIN_LED1 #define LED_BLUE PIN_LED2 diff --git a/variants/nrf52840/MakePython_nRF52840_oled/variant.h b/variants/nrf52840/MakePython_nRF52840_oled/variant.h index 286c5c251..025324037 100644 --- a/variants/nrf52840/MakePython_nRF52840_oled/variant.h +++ b/variants/nrf52840/MakePython_nRF52840_oled/variant.h @@ -29,8 +29,6 @@ extern "C" { #define PIN_LED1 (32 + 10) // LED P1.15 #define PIN_LED2 (-1) // -#define LED_CONN PIN_LED2 - #define LED_GREEN PIN_LED1 #define LED_BLUE PIN_LED2 diff --git a/variants/nrf52840/TWC_mesh_v4/variant.h b/variants/nrf52840/TWC_mesh_v4/variant.h index 5b4623210..875040b5f 100644 --- a/variants/nrf52840/TWC_mesh_v4/variant.h +++ b/variants/nrf52840/TWC_mesh_v4/variant.h @@ -34,8 +34,6 @@ extern "C" { // #define PIN_LED1 (32 + 9) Green // #define PIN_LED1 (0 + 12) Blue -#define LED_CONN PIN_LED2 - #define LED_GREEN PIN_LED1 #define LED_BLUE PIN_LED2 diff --git a/variants/nrf52840/canaryone/variant.h b/variants/nrf52840/canaryone/variant.h index 4d388c376..3f45b618a 100644 --- a/variants/nrf52840/canaryone/variant.h +++ b/variants/nrf52840/canaryone/variant.h @@ -52,8 +52,6 @@ extern "C" { #define LED_BLUE PIN_LED1 -#define LED_CONN PIN_LED3 - #define LED_STATE_ON 0 // State when LED is lit /* diff --git a/variants/nrf52840/diy/seeed-xiao-nrf52840-wio-sx1262/variant.h b/variants/nrf52840/diy/seeed-xiao-nrf52840-wio-sx1262/variant.h index 407bc1541..6927f1295 100644 --- a/variants/nrf52840/diy/seeed-xiao-nrf52840-wio-sx1262/variant.h +++ b/variants/nrf52840/diy/seeed-xiao-nrf52840-wio-sx1262/variant.h @@ -36,7 +36,6 @@ extern "C" { #define PIN_LED3 LED_RED #define PIN_LED PIN_LED1 -#define LED_PWR (PINS_COUNT) #define LED_STATE_ON 1 // State when LED is lit diff --git a/variants/nrf52840/gat562_mesh_trial_tracker/variant.h b/variants/nrf52840/gat562_mesh_trial_tracker/variant.h index 282953cd9..0e37eeeff 100644 --- a/variants/nrf52840/gat562_mesh_trial_tracker/variant.h +++ b/variants/nrf52840/gat562_mesh_trial_tracker/variant.h @@ -48,8 +48,6 @@ extern "C" { #define PIN_LED1 (35) #define PIN_LED2 (36) -#define LED_CONN PIN_LED2 - #define LED_GREEN PIN_LED1 #define LED_BLUE PIN_LED2 @@ -62,8 +60,6 @@ extern "C" { #define PIN_BUTTON1 9 // Pin for button on E-ink button module or IO expansion #define BUTTON_NEED_PULLUP #define PIN_BUTTON2 12 -#define PIN_BUTTON3 24 -#define PIN_BUTTON4 25 /* * Analog pins diff --git a/variants/nrf52840/heltec_mesh_node_t114-inkhud/variant.h b/variants/nrf52840/heltec_mesh_node_t114-inkhud/variant.h index 96fa35869..2802e4c1d 100644 --- a/variants/nrf52840/heltec_mesh_node_t114-inkhud/variant.h +++ b/variants/nrf52840/heltec_mesh_node_t114-inkhud/variant.h @@ -137,8 +137,6 @@ No longer populated on PCB #define PIN_SPI1_MOSI PIN_EINK_MOSI #define PIN_SPI1_SCK PIN_EINK_SCLK -// #define PIN_PWR_EN (0 + 6) - // To debug via the segger JLINK console rather than the CDC-ACM serial device // #define USE_SEGGER diff --git a/variants/nrf52840/heltec_mesh_node_t114/variant.h b/variants/nrf52840/heltec_mesh_node_t114/variant.h index b3aa942ff..fe825089e 100644 --- a/variants/nrf52840/heltec_mesh_node_t114/variant.h +++ b/variants/nrf52840/heltec_mesh_node_t114/variant.h @@ -192,8 +192,6 @@ No longer populated on PCB #define PIN_SPI_MOSI (0 + 22) #define PIN_SPI_SCK (0 + 19) -// #define PIN_PWR_EN (0 + 6) - // To debug via the segger JLINK console rather than the CDC-ACM serial device // #define USE_SEGGER diff --git a/variants/nrf52840/heltec_mesh_pocket/variant.h b/variants/nrf52840/heltec_mesh_pocket/variant.h index c80fefd58..a86faf575 100644 --- a/variants/nrf52840/heltec_mesh_pocket/variant.h +++ b/variants/nrf52840/heltec_mesh_pocket/variant.h @@ -26,7 +26,6 @@ extern "C" { #define LED_RED PIN_LED1 #define LED_BLUE PIN_LED1 #define LED_GREEN PIN_LED1 -#define LED_CONN LED_BLUE #define LED_STATE_ON 0 // State when LED is lit /* @@ -99,8 +98,6 @@ No longer populated on PCB #define PIN_SPI_MOSI (0 + 5) #define PIN_SPI_SCK (0 + 4) -// #define PIN_PWR_EN (0 + 6) - // To debug via the segger JLINK console rather than the CDC-ACM serial device // #define USE_SEGGER diff --git a/variants/nrf52840/heltec_mesh_solar/variant.h b/variants/nrf52840/heltec_mesh_solar/variant.h index 298475401..8d4db4bea 100644 --- a/variants/nrf52840/heltec_mesh_solar/variant.h +++ b/variants/nrf52840/heltec_mesh_solar/variant.h @@ -132,8 +132,6 @@ No longer populated on PCB #define PIN_SPI_MOSI (0 + 22) #define PIN_SPI_SCK (0 + 19) -// #define PIN_PWR_EN (0 + 6) - // To debug via the segger JLINK console rather than the CDC-ACM serial device // #define USE_SEGGER diff --git a/variants/nrf52840/meshtiny/variant.h b/variants/nrf52840/meshtiny/variant.h index d2cffd169..4a99def7d 100644 --- a/variants/nrf52840/meshtiny/variant.h +++ b/variants/nrf52840/meshtiny/variant.h @@ -49,8 +49,6 @@ extern "C" { #define PIN_LED1 (35) #define PIN_LED2 (36) -#define LED_CONN PIN_LED2 - #define LED_GREEN PIN_LED1 #define LED_BLUE PIN_LED2 @@ -65,8 +63,6 @@ extern "C" { #define INPUTDRIVER_ENCODER_BTN 28 #define UPDOWN_LONG_PRESS_REPEAT_INTERVAL 150 -#define CANNED_MESSAGE_MODULE_ENABLE 1 - /* * Buzzer - PWM */ diff --git a/variants/nrf52840/monteops_hw1/variant.h b/variants/nrf52840/monteops_hw1/variant.h index e5896091b..9560d076b 100644 --- a/variants/nrf52840/monteops_hw1/variant.h +++ b/variants/nrf52840/monteops_hw1/variant.h @@ -52,8 +52,6 @@ extern "C" { #define PIN_LED1 (35) #define PIN_LED2 (36) // Connected to WWAN host LED (if present) -#define LED_CONN PIN_LED2 - #define LED_GREEN PIN_LED1 #define LED_BLUE PIN_LED2 @@ -66,8 +64,6 @@ extern "C" { // #define PIN_BUTTON1 9 // Pin for button on E-ink button module or IO expansion #define BUTTON_NEED_PULLUP #define PIN_BUTTON2 12 -#define PIN_BUTTON3 24 -#define PIN_BUTTON4 25 /* * Analog pins diff --git a/variants/nrf52840/nano-g2-ultra/variant.h b/variants/nrf52840/nano-g2-ultra/variant.h index 8c3451835..6e7cbdc45 100644 --- a/variants/nrf52840/nano-g2-ultra/variant.h +++ b/variants/nrf52840/nano-g2-ultra/variant.h @@ -50,8 +50,6 @@ extern "C" { #define LED_BLUE PIN_LED1 #define LED_GREEN PIN_LED2 -#define LED_CONN PIN_GREEN - #define LED_STATE_ON 0 // State when LED is lit /* @@ -152,8 +150,6 @@ External serial flash W25Q16JV_IQ #define PIN_SPI_MOSI (0 + 11) #define PIN_SPI_SCK (0 + 12) -// #define PIN_PWR_EN (0 + 6) - // To debug via the segger JLINK console rather than the CDC-ACM serial device // #define USE_SEGGER diff --git a/variants/nrf52840/r1-neo/variant.h b/variants/nrf52840/r1-neo/variant.h index 830126812..acad61a51 100644 --- a/variants/nrf52840/r1-neo/variant.h +++ b/variants/nrf52840/r1-neo/variant.h @@ -47,8 +47,6 @@ extern "C" { #define PIN_LED1 (32 + 4) // P1.04 Controls Green LED #define PIN_LED2 (28) // P0.28 Controls Blue LED -#define LED_CONN PIN_LED2 - #define LED_GREEN PIN_LED1 #define LED_BLUE PIN_LED2 diff --git a/variants/nrf52840/rak2560/variant.h b/variants/nrf52840/rak2560/variant.h index 2fa0bd1e7..b6b8f0d6d 100644 --- a/variants/nrf52840/rak2560/variant.h +++ b/variants/nrf52840/rak2560/variant.h @@ -48,8 +48,6 @@ extern "C" { #define PIN_LED1 (35) #define PIN_LED2 (36) -#define LED_CONN PIN_LED2 - #define LED_GREEN PIN_LED1 #define LED_BLUE PIN_LED2 @@ -62,8 +60,6 @@ extern "C" { #define PIN_BUTTON1 9 // Pin for button on E-ink button module or IO expansion #define BUTTON_NEED_PULLUP #define PIN_BUTTON2 12 -#define PIN_BUTTON3 24 -#define PIN_BUTTON4 25 /* * Analog pins diff --git a/variants/nrf52840/rak3401_1watt/variant.h b/variants/nrf52840/rak3401_1watt/variant.h index 4cc565d98..f98f7c400 100644 --- a/variants/nrf52840/rak3401_1watt/variant.h +++ b/variants/nrf52840/rak3401_1watt/variant.h @@ -47,8 +47,6 @@ extern "C" { #define PIN_LED1 (35) #define PIN_LED2 (36) -#define LED_CONN PIN_LED2 - #define LED_GREEN PIN_LED1 #define LED_BLUE PIN_LED2 diff --git a/variants/nrf52840/rak4631/variant.h b/variants/nrf52840/rak4631/variant.h index b0215ad28..fd1e7cc4b 100644 --- a/variants/nrf52840/rak4631/variant.h +++ b/variants/nrf52840/rak4631/variant.h @@ -47,8 +47,6 @@ extern "C" { #define PIN_LED1 (35) #define PIN_LED2 (36) -#define LED_CONN PIN_LED2 - #define LED_GREEN PIN_LED1 #define LED_BLUE PIN_LED2 @@ -61,8 +59,6 @@ extern "C" { #define PIN_BUTTON1 9 // Pin for button on E-ink button module or IO expansion #define BUTTON_NEED_PULLUP #define PIN_BUTTON2 12 -#define PIN_BUTTON3 24 -#define PIN_BUTTON4 25 /* * Analog pins diff --git a/variants/nrf52840/rak4631_epaper/variant.h b/variants/nrf52840/rak4631_epaper/variant.h index 574a0e68f..8029baf81 100644 --- a/variants/nrf52840/rak4631_epaper/variant.h +++ b/variants/nrf52840/rak4631_epaper/variant.h @@ -47,8 +47,6 @@ extern "C" { #define PIN_LED1 (35) #define PIN_LED2 (36) -#define LED_CONN PIN_LED2 - #define LED_GREEN PIN_LED1 #define LED_BLUE PIN_LED2 @@ -61,8 +59,6 @@ extern "C" { #define PIN_BUTTON1 9 // Pin for button on E-ink button module or IO expansion #define BUTTON_NEED_PULLUP #define PIN_BUTTON2 12 -#define PIN_BUTTON3 24 -#define PIN_BUTTON4 25 /* * Analog pins diff --git a/variants/nrf52840/rak4631_epaper_onrxtx/variant.h b/variants/nrf52840/rak4631_epaper_onrxtx/variant.h index 7f1a5d112..9e57f9503 100644 --- a/variants/nrf52840/rak4631_epaper_onrxtx/variant.h +++ b/variants/nrf52840/rak4631_epaper_onrxtx/variant.h @@ -29,8 +29,6 @@ extern "C" { #define PIN_LED1 (35) #define PIN_LED2 (36) -#define LED_CONN PIN_LED2 - #define LED_GREEN PIN_LED1 #define LED_BLUE PIN_LED2 diff --git a/variants/nrf52840/rak4631_eth_gw/variant.h b/variants/nrf52840/rak4631_eth_gw/variant.h index 2e8ae129c..0fcffe5b6 100644 --- a/variants/nrf52840/rak4631_eth_gw/variant.h +++ b/variants/nrf52840/rak4631_eth_gw/variant.h @@ -47,8 +47,6 @@ extern "C" { #define PIN_LED1 (35) #define PIN_LED2 (36) -#define LED_CONN PIN_LED2 - #define LED_GREEN PIN_LED1 #define LED_BLUE PIN_LED2 @@ -61,8 +59,6 @@ extern "C" { #define PIN_BUTTON1 9 // Pin for button on E-ink button module or IO expansion #define BUTTON_NEED_PULLUP #define PIN_BUTTON2 12 -#define PIN_BUTTON3 24 -#define PIN_BUTTON4 25 /* * Analog pins diff --git a/variants/nrf52840/rak4631_nomadstar_meteor_pro/variant.h b/variants/nrf52840/rak4631_nomadstar_meteor_pro/variant.h index 84793bfb3..50c6ab573 100644 --- a/variants/nrf52840/rak4631_nomadstar_meteor_pro/variant.h +++ b/variants/nrf52840/rak4631_nomadstar_meteor_pro/variant.h @@ -47,8 +47,6 @@ extern "C" { #define PIN_LED1 (35) #define PIN_LED2 (36) -#define LED_CONN PIN_LED2 - #define LED_GREEN PIN_LED1 #define LED_BLUE PIN_LED2 @@ -61,8 +59,6 @@ extern "C" { #define PIN_BUTTON1 9 // Pin for button on E-ink button module or IO expansion #define BUTTON_NEED_PULLUP #define PIN_BUTTON2 12 -#define PIN_BUTTON3 24 -#define PIN_BUTTON4 25 /* * Analog pins diff --git a/variants/nrf52840/rak_wismeshtag/variant.h b/variants/nrf52840/rak_wismeshtag/variant.h index c31443371..354772e66 100644 --- a/variants/nrf52840/rak_wismeshtag/variant.h +++ b/variants/nrf52840/rak_wismeshtag/variant.h @@ -47,8 +47,6 @@ extern "C" { #define PIN_LED1 (35) #define PIN_LED2 (36) -#define LED_CONN PIN_LED2 - #define LED_GREEN PIN_LED1 #define LED_BLUE PIN_LED2 @@ -61,8 +59,6 @@ extern "C" { #define PIN_BUTTON1 9 // Pin for button on E-ink button module or IO expansion #define BUTTON_NEED_PULLUP #define PIN_BUTTON2 12 -#define PIN_BUTTON3 24 -#define PIN_BUTTON4 25 /* * Analog pins diff --git a/variants/nrf52840/rak_wismeshtap/variant.h b/variants/nrf52840/rak_wismeshtap/variant.h index a6eccc3c8..2cd7279f9 100644 --- a/variants/nrf52840/rak_wismeshtap/variant.h +++ b/variants/nrf52840/rak_wismeshtap/variant.h @@ -47,8 +47,6 @@ extern "C" { #define PIN_LED1 (35) #define PIN_LED2 (36) -#define LED_CONN PIN_LED2 - #define LED_GREEN PIN_LED1 #define LED_BLUE PIN_LED2 @@ -61,8 +59,6 @@ extern "C" { #define PIN_BUTTON1 9 // Pin for button on E-ink button module or IO expansion such as the RAK14014 or RAK14015 TFT modules #define BUTTON_NEED_PULLUP #define PIN_BUTTON2 12 -#define PIN_BUTTON3 24 -#define PIN_BUTTON4 25 /* * Analog pins @@ -282,7 +278,6 @@ SO GPIO 39/TXEN MAY NOT BE DEFINED FOR SUCCESSFUL OPERATION OF THE SX1262 - TG #define RAK14014 // Tell it we have a RAK14014 #define USER_SETUP_LOADED 1 -#define DISABLE_ALL_LIBRARY_WARNINGS 1 #define ST7789_DRIVER 1 #define TFT_WIDTH 240 #define TFT_HEIGHT 320 @@ -310,7 +305,6 @@ SO GPIO 39/TXEN MAY NOT BE DEFINED FOR SUCCESSFUL OPERATION OF THE SX1262 - TG #define USE_POWERSAVE #define SLEEP_TIME 120 -#define CANNED_MESSAGE_MODULE_ENABLE 1 #define USE_VIRTUAL_KEYBOARD 1 /*---------------------------------------------------------------------------- * Arduino objects - C++ only diff --git a/variants/nrf52840/seeed_solar_node/variant.h b/variants/nrf52840/seeed_solar_node/variant.h index 7473fd073..cbc8181a9 100644 --- a/variants/nrf52840/seeed_solar_node/variant.h +++ b/variants/nrf52840/seeed_solar_node/variant.h @@ -23,8 +23,6 @@ #define PIN_LED1 (12) // LED P1.15 #define PIN_LED2 (11) // -#define LED_CONN PIN_LED2 - #define LED_GREEN PIN_LED1 #define LED_BLUE PIN_LED2 // #define LED_PIN PIN_LED2 diff --git a/variants/nrf52840/seeed_wio_tracker_L1/variant.h b/variants/nrf52840/seeed_wio_tracker_L1/variant.h index 70b35336b..2d0ff2020 100644 --- a/variants/nrf52840/seeed_wio_tracker_L1/variant.h +++ b/variants/nrf52840/seeed_wio_tracker_L1/variant.h @@ -23,8 +23,6 @@ #define PIN_LED1 (11) // LED P1.15 #define PIN_LED2 (12) // -#define LED_CONN PIN_LED2 - #define LED_GREEN PIN_LED1 #define LED_BLUE PIN_LED2 // #define LED_PIN PIN_LED2 @@ -157,8 +155,6 @@ static const uint8_t SCL = PIN_WIRE_SCL; // joystick // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ -#define CANNED_MESSAGE_MODULE_ENABLE 1 - #define CANNED_MESSAGE_ADD_CONFIRMATION 1 // trackball diff --git a/variants/nrf52840/seeed_wio_tracker_L1_eink/variant.h b/variants/nrf52840/seeed_wio_tracker_L1_eink/variant.h index 87aa5bb80..3dd1ca21b 100644 --- a/variants/nrf52840/seeed_wio_tracker_L1_eink/variant.h +++ b/variants/nrf52840/seeed_wio_tracker_L1_eink/variant.h @@ -23,8 +23,6 @@ #define PIN_LED1 (11) // LED P1.15 #define PIN_LED2 (12) // -#define LED_CONN PIN_LED2 - #define LED_GREEN PIN_LED1 #define LED_BLUE PIN_LED2 // #define LED_PIN PIN_LED2 @@ -176,7 +174,6 @@ static const uint8_t SCL = PIN_WIRE_SCL; #define TB_PRESS 29 #define TB_DIRECTION FALLING -#define CANNED_MESSAGE_MODULE_ENABLE 1 #define CANNED_MESSAGE_ADD_CONFIRMATION 1 // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ diff --git a/variants/nrf52840/seeed_xiao_nrf52840_kit/variant.h b/variants/nrf52840/seeed_xiao_nrf52840_kit/variant.h index e4305a57a..a9ccb2aae 100644 --- a/variants/nrf52840/seeed_xiao_nrf52840_kit/variant.h +++ b/variants/nrf52840/seeed_xiao_nrf52840_kit/variant.h @@ -69,7 +69,6 @@ static const uint8_t A5 = PIN_A5; #define PIN_LED2 LED_BLUE #define PIN_LED3 LED_RED -#define LED_PWR LED_RED #define USER_LED LED_BLUE /* diff --git a/variants/nrf52840/t-echo-lite/variant.h b/variants/nrf52840/t-echo-lite/variant.h index de93b10f4..7dc1a3ef5 100644 --- a/variants/nrf52840/t-echo-lite/variant.h +++ b/variants/nrf52840/t-echo-lite/variant.h @@ -52,7 +52,6 @@ extern "C" { #define BLE_LED LED_BLUE #define BLE_LED_INVERTED 1 -#define LED_CONN LED_GREEN #define LED_STATE_ON 0 // State when LED is lit // Buttons diff --git a/variants/nrf52840/t-echo-plus/variant.h b/variants/nrf52840/t-echo-plus/variant.h index 7e5235902..4038ce6ef 100644 --- a/variants/nrf52840/t-echo-plus/variant.h +++ b/variants/nrf52840/t-echo-plus/variant.h @@ -25,8 +25,6 @@ extern "C" { #define LED_BLUE PIN_LED1 #define LED_GREEN PIN_LED2 -#define LED_CONN LED_GREEN - #define LED_STATE_ON 0 // Buttons / touch diff --git a/variants/nrf52840/t-echo/variant.h b/variants/nrf52840/t-echo/variant.h index 7714f5ce3..f6f650315 100644 --- a/variants/nrf52840/t-echo/variant.h +++ b/variants/nrf52840/t-echo/variant.h @@ -52,8 +52,6 @@ extern "C" { #define LED_BLUE PIN_LED1 #define LED_GREEN PIN_LED2 -#define LED_CONN PIN_GREEN - #define LED_STATE_ON 0 // State when LED is lit /* @@ -163,7 +161,6 @@ External serial flash WP25R1635FZUIL0 // Controls power for all peripherals (eink + GPS + LoRa + Sensor) #define PIN_POWER_EN (0 + 12) -// #define PIN_POWER_EN1 (0 + 13) #define PIN_SPI1_MISO \ (32 + 7) // FIXME not really needed, but for now the SPI code requires something to be defined, pick an used GPIO diff --git a/variants/rp2040/rak11310/variant.h b/variants/rp2040/rak11310/variant.h index 36c77d209..4d2b9ca3a 100644 --- a/variants/rp2040/rak11310/variant.h +++ b/variants/rp2040/rak11310/variant.h @@ -10,7 +10,6 @@ #define I2C_SDA1 2 #define I2C_SCL1 3 -#define LED_CONN PIN_LED2 #define LED_PIN PIN_LED #define ledOff(pin) pinMode(pin, INPUT) From dbded86dcb6647b06e6a19a2a9656472f1488989 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Thu, 29 Jan 2026 12:51:48 -0600 Subject: [PATCH 77/99] More variant.h cleanup. LED_NOTIFICATION, remove dead code, etc (#9477) --- src/Power.cpp | 3 +++ src/configuration.h | 6 +++--- src/main.cpp | 6 +++--- src/mesh/NodeDB.cpp | 21 +++---------------- .../9m2ibr_aprs_lora_tracker/platformio.ini | 3 +++ .../diy/9m2ibr_aprs_lora_tracker/variant.cpp | 8 +++++++ .../esp32s3/hackaday-communicator/variant.h | 3 ++- variants/esp32s3/rak3312/variant.h | 2 +- variants/esp32s3/rak_wismesh_tap_v2/variant.h | 2 +- .../nrf52840/ELECROW-ThinkNode-M1/variant.cpp | 7 ------- .../nrf52840/ELECROW-ThinkNode-M1/variant.h | 7 +------ .../nrf52840/ELECROW-ThinkNode-M3/variant.h | 2 +- .../nrf52840/ELECROW-ThinkNode-M4/variant.cpp | 3 --- .../nrf52840/ELECROW-ThinkNode-M4/variant.h | 2 +- .../nrf52840/ELECROW-ThinkNode-M6/variant.h | 2 +- .../MakePython_nRF52840_eink/variant.cpp | 3 --- .../MakePython_nRF52840_eink/variant.h | 4 +--- .../MakePython_nRF52840_oled/variant.cpp | 3 --- .../MakePython_nRF52840_oled/variant.h | 4 +--- variants/nrf52840/feather_diy/variant.h | 2 +- .../gat562_mesh_trial_tracker/variant.cpp | 3 --- .../gat562_mesh_trial_tracker/variant.h | 4 ++-- variants/nrf52840/meshtiny/variant.cpp | 6 +++--- variants/nrf52840/meshtiny/variant.h | 3 +-- variants/nrf52840/monteops_hw1/variant.cpp | 3 --- variants/nrf52840/monteops_hw1/variant.h | 4 ++-- variants/nrf52840/muzi_base/variant.cpp | 4 ++-- variants/nrf52840/muzi_base/variant.h | 4 ++-- variants/nrf52840/nano-g2-ultra/variant.h | 9 -------- variants/nrf52840/nrf52.ini | 2 +- variants/nrf52840/r1-neo/variant.cpp | 3 --- variants/nrf52840/r1-neo/variant.h | 4 ++-- variants/nrf52840/rak2560/variant.cpp | 3 --- variants/nrf52840/rak2560/variant.h | 4 ++-- variants/nrf52840/rak3401_1watt/variant.cpp | 3 --- variants/nrf52840/rak3401_1watt/variant.h | 4 ++-- variants/nrf52840/rak4631/variant.cpp | 3 --- variants/nrf52840/rak4631/variant.h | 4 ++-- variants/nrf52840/rak4631_epaper/variant.cpp | 3 --- variants/nrf52840/rak4631_epaper/variant.h | 4 ++-- .../rak4631_epaper_onrxtx/variant.cpp | 3 --- .../nrf52840/rak4631_epaper_onrxtx/variant.h | 4 ++-- variants/nrf52840/rak4631_eth_gw/variant.cpp | 3 --- variants/nrf52840/rak4631_eth_gw/variant.h | 4 ++-- .../rak4631_nomadstar_meteor_pro/variant.cpp | 3 --- .../rak4631_nomadstar_meteor_pro/variant.h | 4 ++-- variants/nrf52840/rak_wismeshtag/variant.cpp | 3 --- variants/nrf52840/rak_wismeshtag/variant.h | 4 ++-- variants/nrf52840/rak_wismeshtap/variant.cpp | 3 --- variants/nrf52840/rak_wismeshtap/variant.h | 4 ++-- variants/nrf52840/seeed_solar_node/variant.h | 1 - .../nrf52840/seeed_wio_tracker_L1/variant.h | 3 +-- .../seeed_wio_tracker_L1_eink/variant.h | 3 +-- .../seeed_xiao_nrf52840_kit/variant.h | 2 -- .../nrf52840/wio-tracker-wm1110/variant.cpp | 3 --- .../nrf52840/wio-tracker-wm1110/variant.h | 5 +---- variants/rp2040/rak11310/pins_arduino.h | 2 +- variants/stm32/milesight_gs301/platformio.ini | 3 +++ variants/stm32/milesight_gs301/variant.cpp | 8 +++++++ 59 files changed, 82 insertions(+), 153 deletions(-) create mode 100644 variants/esp32/diy/9m2ibr_aprs_lora_tracker/variant.cpp create mode 100644 variants/stm32/milesight_gs301/variant.cpp diff --git a/src/Power.cpp b/src/Power.cpp index b2a4ddaaf..b211d760e 100644 --- a/src/Power.cpp +++ b/src/Power.cpp @@ -816,6 +816,9 @@ void Power::shutdown() #endif #ifdef PIN_LED3 ledOff(PIN_LED3); +#endif +#ifdef LED_NOTIFICATION + ledOff(LED_NOTIFICATION); #endif doDeepSleep(DELAY_FOREVER, true, true); #elif defined(ARCH_PORTDUINO) diff --git a/src/configuration.h b/src/configuration.h index f7b438272..0a0525fe5 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -428,12 +428,12 @@ along with this program. If not, see . #define HAS_RGB_LED #endif -#ifndef LED_STATE_OFF -#define LED_STATE_OFF 0 -#endif #ifndef LED_STATE_ON #define LED_STATE_ON 1 #endif +#ifndef LED_STATE_OFF +#define LED_STATE_OFF (LED_STATE_ON ^ 1) +#endif // default mapping of pins #if defined(PIN_BUTTON2) && !defined(CANCEL_BUTTON_PIN) diff --git a/src/main.cpp b/src/main.cpp index 68eda2d0d..d9773dfb9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -354,9 +354,9 @@ void setup() digitalWrite(LED_POWER, LED_STATE_ON); #endif -#ifdef USER_LED - pinMode(USER_LED, OUTPUT); - digitalWrite(USER_LED, HIGH ^ LED_STATE_ON); +#ifdef LED_NOTIFICATION + pinMode(LED_NOTIFICATION, OUTPUT); + digitalWrite(LED_NOTIFICATION, HIGH ^ LED_STATE_ON); #endif #ifdef WIFI_LED diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 30d1633d5..0ba60f1e2 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -824,16 +824,10 @@ void NodeDB::installDefaultModuleConfig() moduleConfig.external_notification.output_ms = 500; moduleConfig.external_notification.nag_timeout = 2; #endif -#if defined(RAK4630) || defined(RAK11310) || defined(RAK3312) || defined(MUZI_BASE) || defined(ELECROW_ThinkNode_M3) || \ - defined(ELECROW_ThinkNode_M4) || defined(ELECROW_ThinkNode_M6) - // Default to PIN_LED2 for external notification output (LED color depends on device variant) +#if defined(LED_NOTIFICATION) moduleConfig.external_notification.enabled = true; - moduleConfig.external_notification.output = PIN_LED2; -#if defined(MUZI_BASE) || defined(ELECROW_ThinkNode_M3) - moduleConfig.external_notification.active = false; -#else - moduleConfig.external_notification.active = true; -#endif + moduleConfig.external_notification.output = LED_NOTIFICATION; + moduleConfig.external_notification.active = LED_STATE_ON; moduleConfig.external_notification.alert_message = true; moduleConfig.external_notification.output_ms = 1000; moduleConfig.external_notification.nag_timeout = default_ringtone_nag_secs; @@ -857,15 +851,6 @@ void NodeDB::installDefaultModuleConfig() moduleConfig.external_notification.output_ms = 100; moduleConfig.external_notification.active = true; #endif -#ifdef ELECROW_ThinkNode_M1 - // Default to Elecrow USER_LED (blue) - moduleConfig.external_notification.enabled = true; - moduleConfig.external_notification.output = USER_LED; - moduleConfig.external_notification.active = true; - moduleConfig.external_notification.alert_message = true; - moduleConfig.external_notification.output_ms = 1000; - moduleConfig.external_notification.nag_timeout = 60; -#endif #ifdef T_LORA_PAGER moduleConfig.canned_message.updown1_enabled = true; moduleConfig.canned_message.inputbroker_pin_a = ROTARY_A; diff --git a/variants/esp32/diy/9m2ibr_aprs_lora_tracker/platformio.ini b/variants/esp32/diy/9m2ibr_aprs_lora_tracker/platformio.ini index 809599212..2ddc5a2db 100644 --- a/variants/esp32/diy/9m2ibr_aprs_lora_tracker/platformio.ini +++ b/variants/esp32/diy/9m2ibr_aprs_lora_tracker/platformio.ini @@ -10,3 +10,6 @@ build_flags = -D EBYTE_E22 -D EBYTE_E22_900M30S ; Assume Tx power curve is identical to 900M30S as there is no documentation -I variants/esp32/diy/9m2ibr_aprs_lora_tracker +build_src_filter = + ${esp32_base.build_src_filter} + +<../variants/esp32/diy/9m2ibr_aprs_lora_tracker> \ No newline at end of file diff --git a/variants/esp32/diy/9m2ibr_aprs_lora_tracker/variant.cpp b/variants/esp32/diy/9m2ibr_aprs_lora_tracker/variant.cpp new file mode 100644 index 000000000..ef90d5a54 --- /dev/null +++ b/variants/esp32/diy/9m2ibr_aprs_lora_tracker/variant.cpp @@ -0,0 +1,8 @@ +#include "variant.h" +#include "Arduino.h" + +void earlyInitVariant() +{ + pinMode(USER_LED, OUTPUT); + digitalWrite(USER_LED, HIGH ^ LED_STATE_ON); +} \ No newline at end of file diff --git a/variants/esp32s3/hackaday-communicator/variant.h b/variants/esp32s3/hackaday-communicator/variant.h index 7e048d72c..cca408622 100644 --- a/variants/esp32s3/hackaday-communicator/variant.h +++ b/variants/esp32s3/hackaday-communicator/variant.h @@ -46,4 +46,5 @@ #define SX126X_DIO2_AS_RF_SWITCH #define SX126X_DIO3_TCXO_VOLTAGE 1.8 -// #define LED_PIN 1 \ No newline at end of file +#define LED_NOTIFICATION 1 +#define LED_STATE_ON 0 diff --git a/variants/esp32s3/rak3312/variant.h b/variants/esp32s3/rak3312/variant.h index 4d860e476..6431f1fd0 100644 --- a/variants/esp32s3/rak3312/variant.h +++ b/variants/esp32s3/rak3312/variant.h @@ -22,7 +22,7 @@ #define LED_BLUE 45 #define PIN_LED1 LED_GREEN -#define PIN_LED2 LED_BLUE +#define LED_NOTIFICATION LED_BLUE #define LED_PIN LED_GREEN #define ledOff(pin) pinMode(pin, INPUT) diff --git a/variants/esp32s3/rak_wismesh_tap_v2/variant.h b/variants/esp32s3/rak_wismesh_tap_v2/variant.h index f94b7ca4e..7d263165c 100644 --- a/variants/esp32s3/rak_wismesh_tap_v2/variant.h +++ b/variants/esp32s3/rak_wismesh_tap_v2/variant.h @@ -30,7 +30,7 @@ #define LED_BLUE 45 #define PIN_LED1 LED_GREEN -#define PIN_LED2 LED_BLUE +#define LED_NOTIFICATION LED_BLUE #define LED_PIN LED_GREEN #define ledOff(pin) pinMode(pin, INPUT) diff --git a/variants/nrf52840/ELECROW-ThinkNode-M1/variant.cpp b/variants/nrf52840/ELECROW-ThinkNode-M1/variant.cpp index 1560cde73..04f86e2d4 100644 --- a/variants/nrf52840/ELECROW-ThinkNode-M1/variant.cpp +++ b/variants/nrf52840/ELECROW-ThinkNode-M1/variant.cpp @@ -32,15 +32,8 @@ const uint32_t g_ADigitalPinMap[] = { void initVariant() { - // LED1 & LED2 pinMode(PIN_LED1, OUTPUT); ledOff(PIN_LED1); - - pinMode(PIN_LED2, OUTPUT); - ledOff(PIN_LED2); - - pinMode(PIN_LED3, OUTPUT); - ledOff(PIN_LED3); } void variant_shutdown() diff --git a/variants/nrf52840/ELECROW-ThinkNode-M1/variant.h b/variants/nrf52840/ELECROW-ThinkNode-M1/variant.h index a8c7b42b5..e00e56785 100644 --- a/variants/nrf52840/ELECROW-ThinkNode-M1/variant.h +++ b/variants/nrf52840/ELECROW-ThinkNode-M1/variant.h @@ -41,20 +41,15 @@ extern "C" { #define NUM_ANALOG_INPUTS (1) #define NUM_ANALOG_OUTPUTS (0) -#define PIN_LED2 -1 -#define PIN_LED3 -1 - // LED #define PIN_LED1 (32 + 6) // red #define LED_POWER (32 + 4) -#define USER_LED (0 + 13) // green +#define LED_NOTIFICATION (0 + 13) // green // USB_CHECK #define EXT_PWR_DETECT (32 + 3) #define ADC_V (0 + 8) -#define LED_RED PIN_LED3 #define LED_BLUE PIN_LED1 -#define LED_GREEN PIN_LED2 #define LED_STATE_ON 0 // State when LED is lit // LED灯亮时的状态 #define PIN_BUZZER (0 + 6) /* diff --git a/variants/nrf52840/ELECROW-ThinkNode-M3/variant.h b/variants/nrf52840/ELECROW-ThinkNode-M3/variant.h index 955a16a88..197b72cc9 100644 --- a/variants/nrf52840/ELECROW-ThinkNode-M3/variant.h +++ b/variants/nrf52840/ELECROW-ThinkNode-M3/variant.h @@ -54,7 +54,7 @@ extern "C" { #define LED_POWER LED_RED #define LED_CHARGE LED_POWER // Signals the Status LED Module to handle this LED #define LED_GREEN 35 -#define PIN_LED2 LED_GREEN +#define LED_NOTIFICATION LED_GREEN #define LED_BLUE 37 #define LED_PAIRING LED_BLUE // Signals the Status LED Module to handle this LED diff --git a/variants/nrf52840/ELECROW-ThinkNode-M4/variant.cpp b/variants/nrf52840/ELECROW-ThinkNode-M4/variant.cpp index 5c4b6215b..999f326db 100644 --- a/variants/nrf52840/ELECROW-ThinkNode-M4/variant.cpp +++ b/variants/nrf52840/ELECROW-ThinkNode-M4/variant.cpp @@ -32,9 +32,6 @@ const uint32_t g_ADigitalPinMap[] = { void initVariant() { - pinMode(PIN_LED2, OUTPUT); - ledOff(PIN_LED2); - pinMode(LED_PAIRING, OUTPUT); ledOff(LED_PAIRING); diff --git a/variants/nrf52840/ELECROW-ThinkNode-M4/variant.h b/variants/nrf52840/ELECROW-ThinkNode-M4/variant.h index 0d905c2c1..2cfe948e3 100644 --- a/variants/nrf52840/ELECROW-ThinkNode-M4/variant.h +++ b/variants/nrf52840/ELECROW-ThinkNode-M4/variant.h @@ -41,7 +41,7 @@ extern "C" { // LEDs #define LED_BLUE -1 -#define PIN_LED2 (32 + 9) +#define LED_NOTIFICATION (32 + 9) #define LED_PAIRING (13) #define Battery_LED_1 (15) diff --git a/variants/nrf52840/ELECROW-ThinkNode-M6/variant.h b/variants/nrf52840/ELECROW-ThinkNode-M6/variant.h index 2461d73d2..387f4122b 100644 --- a/variants/nrf52840/ELECROW-ThinkNode-M6/variant.h +++ b/variants/nrf52840/ELECROW-ThinkNode-M6/variant.h @@ -43,7 +43,7 @@ extern "C" { #define LED_BLUE -1 #define LED_CHARGE (12) #define LED_PAIRING (7) -#define PIN_LED2 LED_PAIRING +#define LED_NOTIFICATION LED_PAIRING #define LED_STATE_ON HIGH #define LED_STATE_OFF LOW diff --git a/variants/nrf52840/MakePython_nRF52840_eink/variant.cpp b/variants/nrf52840/MakePython_nRF52840_eink/variant.cpp index 8c6bf039c..04cda84ac 100644 --- a/variants/nrf52840/MakePython_nRF52840_eink/variant.cpp +++ b/variants/nrf52840/MakePython_nRF52840_eink/variant.cpp @@ -32,7 +32,4 @@ void initVariant() // LED1 & LED2 pinMode(PIN_LED1, OUTPUT); ledOff(PIN_LED1); - - pinMode(PIN_LED2, OUTPUT); - ledOff(PIN_LED2); } diff --git a/variants/nrf52840/MakePython_nRF52840_eink/variant.h b/variants/nrf52840/MakePython_nRF52840_eink/variant.h index 48256e65f..64cca4916 100644 --- a/variants/nrf52840/MakePython_nRF52840_eink/variant.h +++ b/variants/nrf52840/MakePython_nRF52840_eink/variant.h @@ -27,12 +27,10 @@ extern "C" { // LEDs #define PIN_LED1 (32 + 10) // LED P1.15 -#define PIN_LED2 (-1) // #define LED_GREEN PIN_LED1 -#define LED_BLUE PIN_LED2 -#define LED_STATE_ON 0 // State when LED is litted +#define LED_STATE_ON 0 // State when LED is lit /* * Buttons diff --git a/variants/nrf52840/MakePython_nRF52840_oled/variant.cpp b/variants/nrf52840/MakePython_nRF52840_oled/variant.cpp index 8c6bf039c..04cda84ac 100644 --- a/variants/nrf52840/MakePython_nRF52840_oled/variant.cpp +++ b/variants/nrf52840/MakePython_nRF52840_oled/variant.cpp @@ -32,7 +32,4 @@ void initVariant() // LED1 & LED2 pinMode(PIN_LED1, OUTPUT); ledOff(PIN_LED1); - - pinMode(PIN_LED2, OUTPUT); - ledOff(PIN_LED2); } diff --git a/variants/nrf52840/MakePython_nRF52840_oled/variant.h b/variants/nrf52840/MakePython_nRF52840_oled/variant.h index 025324037..2f035e7da 100644 --- a/variants/nrf52840/MakePython_nRF52840_oled/variant.h +++ b/variants/nrf52840/MakePython_nRF52840_oled/variant.h @@ -27,12 +27,10 @@ extern "C" { // LEDs #define PIN_LED1 (32 + 10) // LED P1.15 -#define PIN_LED2 (-1) // #define LED_GREEN PIN_LED1 -#define LED_BLUE PIN_LED2 -#define LED_STATE_ON 0 // State when LED is litted +#define LED_STATE_ON 0 // State when LED is lit /* * Buttons diff --git a/variants/nrf52840/feather_diy/variant.h b/variants/nrf52840/feather_diy/variant.h index a9782a8ee..a816e7867 100644 --- a/variants/nrf52840/feather_diy/variant.h +++ b/variants/nrf52840/feather_diy/variant.h @@ -52,7 +52,7 @@ extern "C" { #define LED_GREEN PIN_LED2 // Actually red #define LED_BLUE PIN_LED1 -#define LED_STATE_ON 1 // State when LED is litted +#define LED_STATE_ON 1 // State when LED is lit #define BUTTON_PIN (32 + 2) // P1.02 7 diff --git a/variants/nrf52840/gat562_mesh_trial_tracker/variant.cpp b/variants/nrf52840/gat562_mesh_trial_tracker/variant.cpp index e84b60b3b..a035fbaf0 100644 --- a/variants/nrf52840/gat562_mesh_trial_tracker/variant.cpp +++ b/variants/nrf52840/gat562_mesh_trial_tracker/variant.cpp @@ -36,9 +36,6 @@ void initVariant() pinMode(PIN_LED1, OUTPUT); ledOff(PIN_LED1); - pinMode(PIN_LED2, OUTPUT); - ledOff(PIN_LED2); - // 3V3 Power Rail pinMode(PIN_3V3_EN, OUTPUT); digitalWrite(PIN_3V3_EN, HIGH); diff --git a/variants/nrf52840/gat562_mesh_trial_tracker/variant.h b/variants/nrf52840/gat562_mesh_trial_tracker/variant.h index 0e37eeeff..2795a6141 100644 --- a/variants/nrf52840/gat562_mesh_trial_tracker/variant.h +++ b/variants/nrf52840/gat562_mesh_trial_tracker/variant.h @@ -46,10 +46,10 @@ extern "C" { // LEDs #define PIN_LED1 (35) -#define PIN_LED2 (36) +#define LED_BLUE (36) #define LED_GREEN PIN_LED1 -#define LED_BLUE PIN_LED2 +#define LED_NOTIFICATION LED_BLUE #define LED_STATE_ON 1 // State when LED is litted diff --git a/variants/nrf52840/meshtiny/variant.cpp b/variants/nrf52840/meshtiny/variant.cpp index 2e8b00e4b..aae3da9f2 100644 --- a/variants/nrf52840/meshtiny/variant.cpp +++ b/variants/nrf52840/meshtiny/variant.cpp @@ -32,12 +32,12 @@ const uint32_t g_ADigitalPinMap[] = { void initVariant() { - // LED1 & LED2 + // LED1 & LED_BLUE pinMode(PIN_LED1, OUTPUT); ledOff(PIN_LED1); - pinMode(PIN_LED2, OUTPUT); - ledOff(PIN_LED2); + pinMode(LED_BLUE, OUTPUT); + ledOff(LED_BLUE); // 3V3 Power Rail pinMode(PIN_3V3_EN, OUTPUT); diff --git a/variants/nrf52840/meshtiny/variant.h b/variants/nrf52840/meshtiny/variant.h index 4a99def7d..4289fef4c 100644 --- a/variants/nrf52840/meshtiny/variant.h +++ b/variants/nrf52840/meshtiny/variant.h @@ -47,10 +47,9 @@ extern "C" { // LEDs #define PIN_LED1 (35) -#define PIN_LED2 (36) +#define LED_BLUE (36) #define LED_GREEN PIN_LED1 -#define LED_BLUE PIN_LED2 #define LED_STATE_ON 1 // State when LED is litted diff --git a/variants/nrf52840/monteops_hw1/variant.cpp b/variants/nrf52840/monteops_hw1/variant.cpp index 75cca1dc3..81ae9f482 100644 --- a/variants/nrf52840/monteops_hw1/variant.cpp +++ b/variants/nrf52840/monteops_hw1/variant.cpp @@ -35,7 +35,4 @@ void initVariant() // LED1 & LED2 pinMode(PIN_LED1, OUTPUT); ledOff(PIN_LED1); - - pinMode(PIN_LED2, OUTPUT); - ledOff(PIN_LED2); } diff --git a/variants/nrf52840/monteops_hw1/variant.h b/variants/nrf52840/monteops_hw1/variant.h index 9560d076b..b5411bf04 100644 --- a/variants/nrf52840/monteops_hw1/variant.h +++ b/variants/nrf52840/monteops_hw1/variant.h @@ -50,10 +50,10 @@ extern "C" { // LEDs #define PIN_LED1 (35) -#define PIN_LED2 (36) // Connected to WWAN host LED (if present) +#define LED_BLUE (36) // Connected to WWAN host LED (if present) #define LED_GREEN PIN_LED1 -#define LED_BLUE PIN_LED2 +#define LED_NOTIFICATION LED_BLUE #define LED_STATE_ON 1 // State when LED is litted diff --git a/variants/nrf52840/muzi_base/variant.cpp b/variants/nrf52840/muzi_base/variant.cpp index da01de974..e6178a968 100644 --- a/variants/nrf52840/muzi_base/variant.cpp +++ b/variants/nrf52840/muzi_base/variant.cpp @@ -63,8 +63,8 @@ void initVariant() pinMode(PIN_LED1, OUTPUT); digitalWrite(PIN_LED1, HIGH); - pinMode(PIN_LED2, OUTPUT); - digitalWrite(PIN_LED2, HIGH); + pinMode(LED_BLUE, OUTPUT); + digitalWrite(LED_BLUE, HIGH); // Initialize LoRa pins pinMode(SX126X_RESET, OUTPUT); diff --git a/variants/nrf52840/muzi_base/variant.h b/variants/nrf52840/muzi_base/variant.h index 1ac8d9c27..b82382314 100644 --- a/variants/nrf52840/muzi_base/variant.h +++ b/variants/nrf52840/muzi_base/variant.h @@ -43,9 +43,9 @@ extern "C" { // LEDs #define PIN_LED1 (32 + 3) // P1.03, Green -#define PIN_LED2 (32 + 4) // P1.04, Blue +#define LED_BLUE (32 + 4) // P1.04, Blue -#define LED_BLUE PIN_LED2 +#define LED_NOTIFICATION LED_BLUE #define LED_STATE_ON 0 // State when LED is lit // Buttons diff --git a/variants/nrf52840/nano-g2-ultra/variant.h b/variants/nrf52840/nano-g2-ultra/variant.h index 6e7cbdc45..5fa6ed9dd 100644 --- a/variants/nrf52840/nano-g2-ultra/variant.h +++ b/variants/nrf52840/nano-g2-ultra/variant.h @@ -41,15 +41,6 @@ extern "C" { #define NUM_ANALOG_INPUTS (1) #define NUM_ANALOG_OUTPUTS (0) -// LEDs -#define PIN_LED1 (-1) -#define PIN_LED2 (-1) -#define PIN_LED3 (-1) - -#define LED_RED PIN_LED3 -#define LED_BLUE PIN_LED1 -#define LED_GREEN PIN_LED2 - #define LED_STATE_ON 0 // State when LED is lit /* diff --git a/variants/nrf52840/nrf52.ini b/variants/nrf52840/nrf52.ini index a07fefb2f..727d2c741 100644 --- a/variants/nrf52840/nrf52.ini +++ b/variants/nrf52840/nrf52.ini @@ -7,7 +7,7 @@ extends = arduino_base platform_packages = ; our custom Git version until they merge our PR # TODO renovate - platformio/framework-arduinoadafruitnrf52 @ https://github.com/meshtastic/Adafruit_nRF52_Arduino#c770c8a16a351b55b86e347a3d9d7b74ad0bbf39 + platformio/framework-arduinoadafruitnrf52 @ https://github.com/meshtastic/Adafruit_nRF52_Arduino#74096746e5f167a2ff22e483d8e79bb1aef00591 ; Don't renovate toolchain-gccarmnoneeabi platformio/toolchain-gccarmnoneeabi@~1.90301.0 diff --git a/variants/nrf52840/r1-neo/variant.cpp b/variants/nrf52840/r1-neo/variant.cpp index d87b88c85..c36e88602 100644 --- a/variants/nrf52840/r1-neo/variant.cpp +++ b/variants/nrf52840/r1-neo/variant.cpp @@ -36,9 +36,6 @@ void initVariant() pinMode(PIN_LED1, OUTPUT); ledOff(PIN_LED1); - pinMode(PIN_LED2, OUTPUT); - ledOff(PIN_LED2); - // 3V3 Power Rail // pinMode(PIN_3V3_EN, OUTPUT); // digitalWrite(PIN_3V3_EN, HIGH); diff --git a/variants/nrf52840/r1-neo/variant.h b/variants/nrf52840/r1-neo/variant.h index acad61a51..1d2bc9bab 100644 --- a/variants/nrf52840/r1-neo/variant.h +++ b/variants/nrf52840/r1-neo/variant.h @@ -45,10 +45,10 @@ extern "C" { // LEDs #define PIN_LED1 (32 + 4) // P1.04 Controls Green LED -#define PIN_LED2 (28) // P0.28 Controls Blue LED +#define LED_BLUE (28) // P0.28 Controls Blue LED #define LED_GREEN PIN_LED1 -#define LED_BLUE PIN_LED2 +#define LED_NOTIFICATION LED_BLUE #define LED_STATE_ON 1 // State when LED is litted diff --git a/variants/nrf52840/rak2560/variant.cpp b/variants/nrf52840/rak2560/variant.cpp index e84b60b3b..a035fbaf0 100644 --- a/variants/nrf52840/rak2560/variant.cpp +++ b/variants/nrf52840/rak2560/variant.cpp @@ -36,9 +36,6 @@ void initVariant() pinMode(PIN_LED1, OUTPUT); ledOff(PIN_LED1); - pinMode(PIN_LED2, OUTPUT); - ledOff(PIN_LED2); - // 3V3 Power Rail pinMode(PIN_3V3_EN, OUTPUT); digitalWrite(PIN_3V3_EN, HIGH); diff --git a/variants/nrf52840/rak2560/variant.h b/variants/nrf52840/rak2560/variant.h index b6b8f0d6d..0038f295d 100644 --- a/variants/nrf52840/rak2560/variant.h +++ b/variants/nrf52840/rak2560/variant.h @@ -46,10 +46,10 @@ extern "C" { // LEDs #define PIN_LED1 (35) -#define PIN_LED2 (36) +#define LED_BLUE (36) #define LED_GREEN PIN_LED1 -#define LED_BLUE PIN_LED2 +#define LED_NOTIFICATION LED_BLUE #define LED_STATE_ON 1 // State when LED is litted diff --git a/variants/nrf52840/rak3401_1watt/variant.cpp b/variants/nrf52840/rak3401_1watt/variant.cpp index e84b60b3b..a035fbaf0 100644 --- a/variants/nrf52840/rak3401_1watt/variant.cpp +++ b/variants/nrf52840/rak3401_1watt/variant.cpp @@ -36,9 +36,6 @@ void initVariant() pinMode(PIN_LED1, OUTPUT); ledOff(PIN_LED1); - pinMode(PIN_LED2, OUTPUT); - ledOff(PIN_LED2); - // 3V3 Power Rail pinMode(PIN_3V3_EN, OUTPUT); digitalWrite(PIN_3V3_EN, HIGH); diff --git a/variants/nrf52840/rak3401_1watt/variant.h b/variants/nrf52840/rak3401_1watt/variant.h index f98f7c400..8df0faccb 100644 --- a/variants/nrf52840/rak3401_1watt/variant.h +++ b/variants/nrf52840/rak3401_1watt/variant.h @@ -45,10 +45,10 @@ extern "C" { // LEDs #define PIN_LED1 (35) -#define PIN_LED2 (36) +#define LED_BLUE (36) #define LED_GREEN PIN_LED1 -#define LED_BLUE PIN_LED2 +#define LED_NOTIFICATION LED_BLUE #define LED_STATE_ON 1 // State when LED is litted diff --git a/variants/nrf52840/rak4631/variant.cpp b/variants/nrf52840/rak4631/variant.cpp index e84b60b3b..a035fbaf0 100644 --- a/variants/nrf52840/rak4631/variant.cpp +++ b/variants/nrf52840/rak4631/variant.cpp @@ -36,9 +36,6 @@ void initVariant() pinMode(PIN_LED1, OUTPUT); ledOff(PIN_LED1); - pinMode(PIN_LED2, OUTPUT); - ledOff(PIN_LED2); - // 3V3 Power Rail pinMode(PIN_3V3_EN, OUTPUT); digitalWrite(PIN_3V3_EN, HIGH); diff --git a/variants/nrf52840/rak4631/variant.h b/variants/nrf52840/rak4631/variant.h index fd1e7cc4b..edf4205f7 100644 --- a/variants/nrf52840/rak4631/variant.h +++ b/variants/nrf52840/rak4631/variant.h @@ -45,10 +45,10 @@ extern "C" { // LEDs #define PIN_LED1 (35) -#define PIN_LED2 (36) +#define LED_BLUE (36) #define LED_GREEN PIN_LED1 -#define LED_BLUE PIN_LED2 +#define LED_NOTIFICATION LED_BLUE #define LED_STATE_ON 1 // State when LED is litted diff --git a/variants/nrf52840/rak4631_epaper/variant.cpp b/variants/nrf52840/rak4631_epaper/variant.cpp index e84b60b3b..a035fbaf0 100644 --- a/variants/nrf52840/rak4631_epaper/variant.cpp +++ b/variants/nrf52840/rak4631_epaper/variant.cpp @@ -36,9 +36,6 @@ void initVariant() pinMode(PIN_LED1, OUTPUT); ledOff(PIN_LED1); - pinMode(PIN_LED2, OUTPUT); - ledOff(PIN_LED2); - // 3V3 Power Rail pinMode(PIN_3V3_EN, OUTPUT); digitalWrite(PIN_3V3_EN, HIGH); diff --git a/variants/nrf52840/rak4631_epaper/variant.h b/variants/nrf52840/rak4631_epaper/variant.h index 8029baf81..19a2615f9 100644 --- a/variants/nrf52840/rak4631_epaper/variant.h +++ b/variants/nrf52840/rak4631_epaper/variant.h @@ -45,10 +45,10 @@ extern "C" { // LEDs #define PIN_LED1 (35) -#define PIN_LED2 (36) +#define LED_BLUE (36) #define LED_GREEN PIN_LED1 -#define LED_BLUE PIN_LED2 +#define LED_NOTIFICATION LED_BLUE #define LED_STATE_ON 1 // State when LED is litted diff --git a/variants/nrf52840/rak4631_epaper_onrxtx/variant.cpp b/variants/nrf52840/rak4631_epaper_onrxtx/variant.cpp index e84b60b3b..a035fbaf0 100644 --- a/variants/nrf52840/rak4631_epaper_onrxtx/variant.cpp +++ b/variants/nrf52840/rak4631_epaper_onrxtx/variant.cpp @@ -36,9 +36,6 @@ void initVariant() pinMode(PIN_LED1, OUTPUT); ledOff(PIN_LED1); - pinMode(PIN_LED2, OUTPUT); - ledOff(PIN_LED2); - // 3V3 Power Rail pinMode(PIN_3V3_EN, OUTPUT); digitalWrite(PIN_3V3_EN, HIGH); diff --git a/variants/nrf52840/rak4631_epaper_onrxtx/variant.h b/variants/nrf52840/rak4631_epaper_onrxtx/variant.h index 9e57f9503..f1a1c58eb 100644 --- a/variants/nrf52840/rak4631_epaper_onrxtx/variant.h +++ b/variants/nrf52840/rak4631_epaper_onrxtx/variant.h @@ -27,10 +27,10 @@ extern "C" { // LEDs #define PIN_LED1 (35) -#define PIN_LED2 (36) +#define LED_BLUE (36) #define LED_GREEN PIN_LED1 -#define LED_BLUE PIN_LED2 +#define LED_NOTIFICATION LED_BLUE #define LED_STATE_ON 1 // State when LED is litted diff --git a/variants/nrf52840/rak4631_eth_gw/variant.cpp b/variants/nrf52840/rak4631_eth_gw/variant.cpp index e84b60b3b..a035fbaf0 100644 --- a/variants/nrf52840/rak4631_eth_gw/variant.cpp +++ b/variants/nrf52840/rak4631_eth_gw/variant.cpp @@ -36,9 +36,6 @@ void initVariant() pinMode(PIN_LED1, OUTPUT); ledOff(PIN_LED1); - pinMode(PIN_LED2, OUTPUT); - ledOff(PIN_LED2); - // 3V3 Power Rail pinMode(PIN_3V3_EN, OUTPUT); digitalWrite(PIN_3V3_EN, HIGH); diff --git a/variants/nrf52840/rak4631_eth_gw/variant.h b/variants/nrf52840/rak4631_eth_gw/variant.h index 0fcffe5b6..24e0c0cd3 100644 --- a/variants/nrf52840/rak4631_eth_gw/variant.h +++ b/variants/nrf52840/rak4631_eth_gw/variant.h @@ -45,10 +45,10 @@ extern "C" { // LEDs #define PIN_LED1 (35) -#define PIN_LED2 (36) +#define LED_BLUE (36) #define LED_GREEN PIN_LED1 -#define LED_BLUE PIN_LED2 +#define LED_NOTIFICATION LED_BLUE #define LED_STATE_ON 1 // State when LED is litted diff --git a/variants/nrf52840/rak4631_nomadstar_meteor_pro/variant.cpp b/variants/nrf52840/rak4631_nomadstar_meteor_pro/variant.cpp index e84b60b3b..a035fbaf0 100644 --- a/variants/nrf52840/rak4631_nomadstar_meteor_pro/variant.cpp +++ b/variants/nrf52840/rak4631_nomadstar_meteor_pro/variant.cpp @@ -36,9 +36,6 @@ void initVariant() pinMode(PIN_LED1, OUTPUT); ledOff(PIN_LED1); - pinMode(PIN_LED2, OUTPUT); - ledOff(PIN_LED2); - // 3V3 Power Rail pinMode(PIN_3V3_EN, OUTPUT); digitalWrite(PIN_3V3_EN, HIGH); diff --git a/variants/nrf52840/rak4631_nomadstar_meteor_pro/variant.h b/variants/nrf52840/rak4631_nomadstar_meteor_pro/variant.h index 50c6ab573..655cea6e4 100644 --- a/variants/nrf52840/rak4631_nomadstar_meteor_pro/variant.h +++ b/variants/nrf52840/rak4631_nomadstar_meteor_pro/variant.h @@ -45,10 +45,10 @@ extern "C" { // LEDs #define PIN_LED1 (35) -#define PIN_LED2 (36) +#define LED_BLUE (36) #define LED_GREEN PIN_LED1 -#define LED_BLUE PIN_LED2 +#define LED_NOTIFICATION LED_BLUE #define LED_STATE_ON 1 // State when LED is litted diff --git a/variants/nrf52840/rak_wismeshtag/variant.cpp b/variants/nrf52840/rak_wismeshtag/variant.cpp index e84b60b3b..a035fbaf0 100644 --- a/variants/nrf52840/rak_wismeshtag/variant.cpp +++ b/variants/nrf52840/rak_wismeshtag/variant.cpp @@ -36,9 +36,6 @@ void initVariant() pinMode(PIN_LED1, OUTPUT); ledOff(PIN_LED1); - pinMode(PIN_LED2, OUTPUT); - ledOff(PIN_LED2); - // 3V3 Power Rail pinMode(PIN_3V3_EN, OUTPUT); digitalWrite(PIN_3V3_EN, HIGH); diff --git a/variants/nrf52840/rak_wismeshtag/variant.h b/variants/nrf52840/rak_wismeshtag/variant.h index 354772e66..5b20e4d93 100644 --- a/variants/nrf52840/rak_wismeshtag/variant.h +++ b/variants/nrf52840/rak_wismeshtag/variant.h @@ -45,10 +45,10 @@ extern "C" { // LEDs #define PIN_LED1 (35) -#define PIN_LED2 (36) +#define LED_BLUE (36) #define LED_GREEN PIN_LED1 -#define LED_BLUE PIN_LED2 +#define LED_NOTIFICATION LED_BLUE #define LED_STATE_ON 1 // State when LED is litted diff --git a/variants/nrf52840/rak_wismeshtap/variant.cpp b/variants/nrf52840/rak_wismeshtap/variant.cpp index 36572b074..73d2fac04 100644 --- a/variants/nrf52840/rak_wismeshtap/variant.cpp +++ b/variants/nrf52840/rak_wismeshtap/variant.cpp @@ -36,9 +36,6 @@ void initVariant() pinMode(PIN_LED1, OUTPUT); ledOff(PIN_LED1); - pinMode(PIN_LED2, OUTPUT); - ledOff(PIN_LED2); - // 3V3 Power Rail pinMode(PIN_3V3_EN, OUTPUT); digitalWrite(PIN_3V3_EN, HIGH); diff --git a/variants/nrf52840/rak_wismeshtap/variant.h b/variants/nrf52840/rak_wismeshtap/variant.h index 2cd7279f9..a31bc7b15 100644 --- a/variants/nrf52840/rak_wismeshtap/variant.h +++ b/variants/nrf52840/rak_wismeshtap/variant.h @@ -45,10 +45,10 @@ extern "C" { // LEDs #define PIN_LED1 (35) -#define PIN_LED2 (36) +#define LED_BLUE (36) #define LED_GREEN PIN_LED1 -#define LED_BLUE PIN_LED2 +#define LED_NOTIFICATION LED_BLUE #define LED_STATE_ON 1 // State when LED is litted diff --git a/variants/nrf52840/seeed_solar_node/variant.h b/variants/nrf52840/seeed_solar_node/variant.h index cbc8181a9..69736a9e0 100644 --- a/variants/nrf52840/seeed_solar_node/variant.h +++ b/variants/nrf52840/seeed_solar_node/variant.h @@ -25,7 +25,6 @@ #define LED_GREEN PIN_LED1 #define LED_BLUE PIN_LED2 -// #define LED_PIN PIN_LED2 #define LED_STATE_ON 1 // State when LED is litted // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ // Button Configuration diff --git a/variants/nrf52840/seeed_wio_tracker_L1/variant.h b/variants/nrf52840/seeed_wio_tracker_L1/variant.h index 2d0ff2020..a1ec2508a 100644 --- a/variants/nrf52840/seeed_wio_tracker_L1/variant.h +++ b/variants/nrf52840/seeed_wio_tracker_L1/variant.h @@ -25,8 +25,7 @@ #define LED_GREEN PIN_LED1 #define LED_BLUE PIN_LED2 -// #define LED_PIN PIN_LED2 -#define LED_STATE_ON 1 // State when LED is litted +#define LED_STATE_ON 1 // State when LED is lit // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ // Button Configuration // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ diff --git a/variants/nrf52840/seeed_wio_tracker_L1_eink/variant.h b/variants/nrf52840/seeed_wio_tracker_L1_eink/variant.h index 3dd1ca21b..495c4ace8 100644 --- a/variants/nrf52840/seeed_wio_tracker_L1_eink/variant.h +++ b/variants/nrf52840/seeed_wio_tracker_L1_eink/variant.h @@ -25,8 +25,7 @@ #define LED_GREEN PIN_LED1 #define LED_BLUE PIN_LED2 -// #define LED_PIN PIN_LED2 -#define LED_STATE_ON 1 // State when LED is litted +#define LED_STATE_ON 1 // State when LED is lit // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ // Button Configuration // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ diff --git a/variants/nrf52840/seeed_xiao_nrf52840_kit/variant.h b/variants/nrf52840/seeed_xiao_nrf52840_kit/variant.h index a9ccb2aae..0d599d313 100644 --- a/variants/nrf52840/seeed_xiao_nrf52840_kit/variant.h +++ b/variants/nrf52840/seeed_xiao_nrf52840_kit/variant.h @@ -69,8 +69,6 @@ static const uint8_t A5 = PIN_A5; #define PIN_LED2 LED_BLUE #define PIN_LED3 LED_RED -#define USER_LED LED_BLUE - /* * Buttons */ diff --git a/variants/nrf52840/wio-tracker-wm1110/variant.cpp b/variants/nrf52840/wio-tracker-wm1110/variant.cpp index 5a3587982..0d0856773 100644 --- a/variants/nrf52840/wio-tracker-wm1110/variant.cpp +++ b/variants/nrf52840/wio-tracker-wm1110/variant.cpp @@ -36,9 +36,6 @@ void initVariant() pinMode(PIN_LED1, OUTPUT); ledOff(PIN_LED1); - pinMode(PIN_LED2, OUTPUT); - ledOff(PIN_LED2); - // 3V3 Power Rail pinMode(PIN_3V3_EN, OUTPUT); digitalWrite(PIN_3V3_EN, HIGH); diff --git a/variants/nrf52840/wio-tracker-wm1110/variant.h b/variants/nrf52840/wio-tracker-wm1110/variant.h index 145750da5..647bd47d8 100644 --- a/variants/nrf52840/wio-tracker-wm1110/variant.h +++ b/variants/nrf52840/wio-tracker-wm1110/variant.h @@ -51,11 +51,8 @@ extern "C" { #define PIN_WIRE_SDA (0 + 5) // P0.05 #define PIN_WIRE_SCL (0 + 4) // P0.04 -#define PIN_LED1 (0 + 6) // P0.06 -#define PIN_LED2 (PINS_COUNT) // P0.14 - +#define PIN_LED1 (0 + 6) // P0.06 #define LED_GREEN PIN_LED1 -#define LED_BLUE PIN_LED2 #define LED_STATE_ON 0 diff --git a/variants/rp2040/rak11310/pins_arduino.h b/variants/rp2040/rak11310/pins_arduino.h index 95ee308bc..56214a947 100644 --- a/variants/rp2040/rak11310/pins_arduino.h +++ b/variants/rp2040/rak11310/pins_arduino.h @@ -25,7 +25,7 @@ static const uint8_t A3 = PIN_A3; // LEDs #define PIN_LED (23u) #define PIN_LED1 PIN_LED -#define PIN_LED2 (24u) +#define LED_NOTIFICATION (24u) #define ADC_RESOLUTION 12 diff --git a/variants/stm32/milesight_gs301/platformio.ini b/variants/stm32/milesight_gs301/platformio.ini index a1d3fb34e..73b9cf7ea 100644 --- a/variants/stm32/milesight_gs301/platformio.ini +++ b/variants/stm32/milesight_gs301/platformio.ini @@ -15,6 +15,9 @@ build_flags = -DMESHTASTIC_EXCLUDE_AIR_QUALITY_SENSOR=1 build_unflags = -DDEBUG_MUTE # We have space for debug output until sensor support is added +build_src_filter = + ${stm32_base.build_src_filter} + +<../variants/stm32/milesight_gs301> lib_deps = ${stm32_base.lib_deps} diff --git a/variants/stm32/milesight_gs301/variant.cpp b/variants/stm32/milesight_gs301/variant.cpp new file mode 100644 index 000000000..ef90d5a54 --- /dev/null +++ b/variants/stm32/milesight_gs301/variant.cpp @@ -0,0 +1,8 @@ +#include "variant.h" +#include "Arduino.h" + +void earlyInitVariant() +{ + pinMode(USER_LED, OUTPUT); + digitalWrite(USER_LED, HIGH ^ LED_STATE_ON); +} \ No newline at end of file From eeb7373043f748d67a37ed54a91628413b02c3b3 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Thu, 29 Jan 2026 13:23:49 -0600 Subject: [PATCH 78/99] Remove errant symbol --- src/mesh/RadioInterface.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mesh/RadioInterface.cpp b/src/mesh/RadioInterface.cpp index 2a9b4e8f3..bbd766329 100644 --- a/src/mesh/RadioInterface.cpp +++ b/src/mesh/RadioInterface.cpp @@ -27,7 +27,7 @@ #include "platform/portduino/USBHal.h" #endif -#ifdef ARCH_STM32WL> +#ifdef ARCH_STM32WL #include "STM32WLE5JCInterface.h" #endif From 5dd06edd007b870744e4da930d6c953be0e5a538 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Thu, 29 Jan 2026 13:24:10 -0600 Subject: [PATCH 79/99] Add ledOff if not defined --- src/configuration.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/configuration.h b/src/configuration.h index 0a0525fe5..45054e421 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -435,6 +435,10 @@ along with this program. If not, see . #define LED_STATE_OFF (LED_STATE_ON ^ 1) #endif +#ifndef ledOff +#define ledOff(pin) pinMode(pin, INPUT) +#endif + // default mapping of pins #if defined(PIN_BUTTON2) && !defined(CANCEL_BUTTON_PIN) #define ALT_BUTTON_PIN PIN_BUTTON2 From 28b4f37a93af63f8895adc56c295b6885fc5211b Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Thu, 29 Jan 2026 20:14:50 -0600 Subject: [PATCH 80/99] Revert "Update libch341-spi-userspace digest to af9bc27 (#9472)" (#9483) This reverts commit b18742c2112309199150f39cb5a85a718290690e. --- variants/native/portduino.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/variants/native/portduino.ini b/variants/native/portduino.ini index f99ee9a6f..b86420291 100644 --- a/variants/native/portduino.ini +++ b/variants/native/portduino.ini @@ -29,7 +29,7 @@ lib_deps = # renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX lovyan03/LovyanGFX@1.2.19 # renovate: datasource=git-refs depName=libch341-spi-userspace packageName=https://github.com/pine64/libch341-spi-userspace gitBranch=main - https://github.com/pine64/libch341-spi-userspace/archive/af9bc27c9c30fa90772279925b7c5913dff789b4.zip + https://github.com/pine64/libch341-spi-userspace/archive/23c42319a69cffcb65868e3c72e6bed83974a393.zip # renovate: datasource=custom.pio depName=adafruit/Adafruit seesaw Library packageName=adafruit/library/Adafruit seesaw Library adafruit/Adafruit seesaw Library@1.7.9 # renovate: datasource=git-refs depName=RAK12034-BMX160 packageName=https://github.com/RAKWireless/RAK12034-BMX160 gitBranch=main From e08c0507206b3efc8aad90e17ff6b6b83560b8f8 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Thu, 29 Jan 2026 20:15:08 -0600 Subject: [PATCH 81/99] Add custom ringtone definition for RAK4631 and enable buzzer pin (#9481) --- variants/nrf52840/rak4631/variant.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/variants/nrf52840/rak4631/variant.h b/variants/nrf52840/rak4631/variant.h index b0215ad28..8b149a4fc 100644 --- a/variants/nrf52840/rak4631/variant.h +++ b/variants/nrf52840/rak4631/variant.h @@ -252,9 +252,13 @@ SO GPIO 39/TXEN MAY NOT BE DEFINED FOR SUCCESSFUL OPERATION OF THE SX1262 - TG #define RV3028_RTC (uint8_t)0b1010010 // RAK18001 Buzzer in Slot C -// #define PIN_BUZZER 21 // IO3 is PWM2 +#define PIN_BUZZER 21 // IO3 is PWM2 // NEW: set this via protobuf instead! +// RAK4631 custom ringtone +#undef USERPREFS_RINGTONE_RTTTL +#define USERPREFS_RINGTONE_RTTTL "Rak:d=32,o=5,b=200:b7,p,b7,4p,p" + // Battery // The battery sense is hooked to pin A0 (5) #define BATTERY_PIN PIN_A0 From 6f5a7672b483fe97b2e1341ea5ae2a9be11cd393 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 29 Jan 2026 20:18:34 -0600 Subject: [PATCH 82/99] Update pschatzmann_arduino-audio-driver to v0.2.1 (#9398) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- variants/esp32s3/tlora-pager/platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/variants/esp32s3/tlora-pager/platformio.ini b/variants/esp32s3/tlora-pager/platformio.ini index 63cb2a614..b5c9fd579 100644 --- a/variants/esp32s3/tlora-pager/platformio.ini +++ b/variants/esp32s3/tlora-pager/platformio.ini @@ -45,7 +45,7 @@ lib_deps = ${esp32s3_base.lib_deps} # renovate: datasource=custom.pio depName=SensorLib packageName=lewisxhe/library/SensorLib lewisxhe/SensorLib@0.3.4 # renovate: datasource=github-tags depName=pschatzmann_arduino-audio-driver packageName=pschatzmann/arduino-audio-driver - https://github.com/pschatzmann/arduino-audio-driver/archive/v0.2.0.zip + https://github.com/pschatzmann/arduino-audio-driver/archive/v0.2.1.zip # TODO renovate https://github.com/mverch67/BQ27220/archive/07d92be846abd8a0258a50c23198dac0858b22ed.zip # TODO renovate From 22617076f8f82f61c28ab97d1c4b2bda30413a23 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 29 Jan 2026 20:19:12 -0600 Subject: [PATCH 83/99] Update meshtastic/device-ui digest to 63967a4 (#9475) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index 25fc8829f..77e9cf214 100644 --- a/platformio.ini +++ b/platformio.ini @@ -120,7 +120,7 @@ lib_deps = [device-ui_base] lib_deps = # renovate: datasource=git-refs depName=meshtastic/device-ui packageName=https://github.com/meshtastic/device-ui gitBranch=master - https://github.com/meshtastic/device-ui/archive/69739b84f87a91568d3c421498bc89977937a141.zip + https://github.com/meshtastic/device-ui/archive/63967a4a557d33d56fc5746f9128200dde2d88c5.zip ; Common libs for environmental measurements in telemetry module [environmental_base] From 68733a6c51289f80649269d5db6edaae894561ba Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Fri, 30 Jan 2026 06:32:08 -0600 Subject: [PATCH 84/99] Fix issue triage workflow by clarifying device log requirements and improving JSON response handling --- .github/workflows/models_issue_triage.yml | 41 ++++++++++++++--------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/.github/workflows/models_issue_triage.yml b/.github/workflows/models_issue_triage.yml index f61a15fe6..ef12885f8 100644 --- a/.github/workflows/models_issue_triage.yml +++ b/.github/workflows/models_issue_triage.yml @@ -100,7 +100,12 @@ jobs: prompt: | Analyze this GitHub issue for completeness and determine if it needs labels. - If this looks like a bug on the device/firmware (crash, reboot, lockup, radio issues, GPS issues, display issues, power/sleep issues), request device logs and explain how to get them: + IMPORTANT: Distinguish between: + - Device/firmware bugs (crashes, reboots, lockups, radio/GPS/display/power issues) - these need device logs + - Build/release/packaging issues (missing files, CI failures, download problems) - these do NOT need device logs + - Documentation or website issues - these do NOT need device logs + + If this is a device/firmware bug, request device logs and explain how to get them: Web Flasher logs: - Go to https://flasher.meshtastic.org @@ -113,20 +118,18 @@ jobs: Also request key context if missing: device model/variant, firmware version, region, steps to reproduce, expected vs actual. - Respond ONLY with JSON: - { - "complete": true|false, - "comment": "Your helpful comment requesting missing info, or empty string if complete", - "label": "needs-logs" | "needs-info" | "none" - } + Respond ONLY with valid JSON (no markdown, no code fences): + {"complete": true, "comment": "", "label": "none"} + OR + {"complete": false, "comment": "Your helpful comment", "label": "needs-logs"} - Use "needs-logs" if this is a device bug AND no logs are attached. + Use "needs-logs" ONLY if this is a device/firmware bug AND no logs are attached. Use "needs-info" if basic info like firmware version or steps to reproduce are missing. - Use "none" if the issue is complete or is a feature request. + Use "none" if the issue is complete, is a feature request, or is a build/CI/packaging issue. Title: ${{ github.event.issue.title }} Body: ${{ github.event.issue.body }} - system-prompt: You are a helpful assistant that triages GitHub issues. Be conservative with labels. + system-prompt: You are a helpful assistant that triages GitHub issues. Be conservative with labels. Only request device logs for actual device/firmware bugs, not for build/release/CI issues. model: openai/gpt-4o-mini - name: Process analysis result @@ -137,9 +140,12 @@ jobs: AI_RESPONSE: ${{ steps.analysis.outputs.response }} with: script: | - const raw = (process.env.AI_RESPONSE || '').trim(); + let raw = (process.env.AI_RESPONSE || '').trim(); - let complete = false; + // Strip markdown code fences if present + raw = raw.replace(/^```(?:json)?\s*/i, '').replace(/\s*```$/i, '').trim(); + + let complete = true; let comment = ''; let label = 'none'; @@ -149,9 +155,10 @@ jobs: comment = (parsed.comment ?? '').toString().trim(); label = (parsed.label ?? 'none').toString().trim().toLowerCase(); } catch { - // If JSON parse fails, treat as incomplete with raw response as comment - complete = false; - comment = raw; + // If JSON parse fails, log warning and don't comment (avoid posting raw JSON) + console.log('Failed to parse AI response as JSON:', raw); + complete = true; + comment = ''; label = 'none'; } @@ -159,7 +166,9 @@ jobs: const allowedLabels = new Set(['needs-logs', 'needs-info', 'none']); if (!allowedLabels.has(label)) label = 'none'; - core.setOutput('should_comment', (!complete && comment.length > 0) ? 'true' : 'false'); + // Only comment if we have a valid parsed comment (not raw JSON) + const shouldComment = !complete && comment.length > 0 && !comment.startsWith('{'); + core.setOutput('should_comment', shouldComment ? 'true' : 'false'); core.setOutput('comment_body', comment); core.setOutput('label', label); From ad4b1d9c2ba3232e4098eef0ea2bbcbd963216d4 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Fri, 30 Jan 2026 09:46:41 -0600 Subject: [PATCH 85/99] re-enable RTC support on THINKNODE M3 and M6 --- src/gps/RTC.cpp | 1 - variants/nrf52840/ELECROW-ThinkNode-M3/platformio.ini | 2 +- variants/nrf52840/ELECROW-ThinkNode-M3/variant.h | 4 ++-- variants/nrf52840/ELECROW-ThinkNode-M6/platformio.ini | 2 +- variants/nrf52840/ELECROW-ThinkNode-M6/variant.h | 4 ++-- 5 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/gps/RTC.cpp b/src/gps/RTC.cpp index ad26b55a4..b13406e96 100644 --- a/src/gps/RTC.cpp +++ b/src/gps/RTC.cpp @@ -276,7 +276,6 @@ RTCSetResult perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpd settimeofday(tv, NULL); #endif - // nrf52 doesn't have a readable RTC (yet - software not written) #if HAS_RTC readFromRTC(); #endif diff --git a/variants/nrf52840/ELECROW-ThinkNode-M3/platformio.ini b/variants/nrf52840/ELECROW-ThinkNode-M3/platformio.ini index 6751dd4ef..7a87c2a21 100644 --- a/variants/nrf52840/ELECROW-ThinkNode-M3/platformio.ini +++ b/variants/nrf52840/ELECROW-ThinkNode-M3/platformio.ini @@ -25,4 +25,4 @@ lib_deps = # renovate: datasource=custom.pio depName=nRF52_PWM packageName=khoih-prog/library/nRF52_PWM khoih-prog/nRF52_PWM@1.0.1 ; # renovate: datasource=custom.pio depName=SensorLib packageName=lewisxhe/library/SensorLib - ; lewisxhe/SensorLib@0.3.4 + lewisxhe/SensorLib@0.3.4 diff --git a/variants/nrf52840/ELECROW-ThinkNode-M3/variant.h b/variants/nrf52840/ELECROW-ThinkNode-M3/variant.h index acbcf6ae5..74440e3c3 100644 --- a/variants/nrf52840/ELECROW-ThinkNode-M3/variant.h +++ b/variants/nrf52840/ELECROW-ThinkNode-M3/variant.h @@ -115,8 +115,8 @@ extern "C" { #define SERIAL_PRINT_PORT 0 // PCF8563 RTC Module -// REVISIT https://github.com/meshtastic/firmware/pull/9084 -// #define PCF8563_RTC 0x51 +#define PCF8563_RTC 0x51 +#define HAS_RTC 1 #ifdef __cplusplus } diff --git a/variants/nrf52840/ELECROW-ThinkNode-M6/platformio.ini b/variants/nrf52840/ELECROW-ThinkNode-M6/platformio.ini index a31615545..329111ce6 100644 --- a/variants/nrf52840/ELECROW-ThinkNode-M6/platformio.ini +++ b/variants/nrf52840/ELECROW-ThinkNode-M6/platformio.ini @@ -22,4 +22,4 @@ build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/ELECROW lib_deps = ${nrf52840_base.lib_deps} ; # renovate: datasource=custom.pio depName=SensorLib packageName=lewisxhe/library/SensorLib - ; lewisxhe/SensorLib@0.3.4 + lewisxhe/SensorLib@0.3.4 diff --git a/variants/nrf52840/ELECROW-ThinkNode-M6/variant.h b/variants/nrf52840/ELECROW-ThinkNode-M6/variant.h index 2461d73d2..780919a1f 100644 --- a/variants/nrf52840/ELECROW-ThinkNode-M6/variant.h +++ b/variants/nrf52840/ELECROW-ThinkNode-M6/variant.h @@ -120,8 +120,8 @@ static const uint8_t A0 = PIN_A0; #define PIN_SERIAL2_TX (24) // PCF8563 RTC Module -// REVISIT https://github.com/meshtastic/firmware/pull/9084 -// #define PCF8563_RTC 0x51 +#define PCF8563_RTC 0x51 +#define HAS_RTC 1 // SPI #define SPI_INTERFACES_COUNT 1 From 4cf01e7e53e3158f604d62b8de30c884cbc30b2b Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Fri, 30 Jan 2026 10:05:26 -0600 Subject: [PATCH 86/99] Adjust pin poweroff for Thinknode M6 --- variants/nrf52840/ELECROW-ThinkNode-M6/variant.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/variants/nrf52840/ELECROW-ThinkNode-M6/variant.cpp b/variants/nrf52840/ELECROW-ThinkNode-M6/variant.cpp index 4ce8ecdf0..bc0381a48 100644 --- a/variants/nrf52840/ELECROW-ThinkNode-M6/variant.cpp +++ b/variants/nrf52840/ELECROW-ThinkNode-M6/variant.cpp @@ -48,7 +48,7 @@ void variant_shutdown() // This sets the pin to OUTPUT and LOW for the pins *not* in the if block. for (int pin = 0; pin < 48; pin++) { if (pin == PIN_GPS_EN || pin == ADC_CTRL || pin == PIN_BUTTON1 || pin == PIN_SPI_MISO || pin == PIN_SPI_MOSI || - pin == PIN_SPI_SCK) { + pin == PIN_SPI_SCK || pin == SX126X_CS || pin == SX126X_RESET || pin == SX126X_BUSY || pin == SX126X_DIO1) { continue; } pinMode(pin, OUTPUT); From 200e79e80065d34a3a63abc8848b242ee559ab3b Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Fri, 30 Jan 2026 11:54:49 -0600 Subject: [PATCH 87/99] You get an RTC, and you get an RTC! (delete HAS_RTC as it wasn't actually doing much) (#9493) --- src/configuration.h | 3 --- src/gps/RTC.cpp | 3 --- src/platform/esp32/architecture.h | 3 --- src/platform/portduino/architecture.h | 3 --- variants/esp32/m5stack_coreink/variant.h | 1 - variants/esp32s3/t-watch-s3/variant.h | 1 - variants/esp32s3/tbeam-s3-core/variant.h | 1 - variants/esp32s3/tlora-pager/variant.h | 1 - variants/nrf52840/gat562_mesh_trial_tracker/variant.h | 2 -- variants/nrf52840/heltec_mesh_node_t114/variant.h | 1 - variants/nrf52840/monteops_hw1/variant.h | 2 -- variants/nrf52840/muzi_base/variant.h | 1 - variants/nrf52840/nano-g2-ultra/variant.h | 1 - variants/nrf52840/r1-neo/variant.h | 2 -- variants/nrf52840/rak2560/variant.h | 2 -- variants/nrf52840/rak3401_1watt/variant.h | 2 -- variants/nrf52840/rak4631/variant.h | 2 -- variants/nrf52840/rak4631_epaper/variant.h | 2 -- variants/nrf52840/rak4631_epaper_onrxtx/variant.h | 2 -- variants/nrf52840/rak4631_eth_gw/variant.h | 2 -- variants/nrf52840/rak4631_nomadstar_meteor_pro/variant.h | 2 -- variants/nrf52840/rak_wismeshtap/variant.h | 2 -- variants/nrf52840/t-echo/variant.h | 1 - 23 files changed, 42 deletions(-) diff --git a/src/configuration.h b/src/configuration.h index 45054e421..66fa4492d 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -390,9 +390,6 @@ along with this program. If not, see . #ifndef HAS_RADIO #define HAS_RADIO 0 #endif -#ifndef HAS_RTC -#define HAS_RTC 0 -#endif #ifndef HAS_CPU_SHUTDOWN #define HAS_CPU_SHUTDOWN 0 #endif diff --git a/src/gps/RTC.cpp b/src/gps/RTC.cpp index b13406e96..3bca6f6ec 100644 --- a/src/gps/RTC.cpp +++ b/src/gps/RTC.cpp @@ -276,10 +276,7 @@ RTCSetResult perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpd settimeofday(tv, NULL); #endif -#if HAS_RTC readFromRTC(); -#endif - return RTCSetResultSuccess; } else { return RTCSetResultNotSet; // RTC was already set with a higher quality time diff --git a/src/platform/esp32/architecture.h b/src/platform/esp32/architecture.h index f34f1fc65..7aee45f81 100644 --- a/src/platform/esp32/architecture.h +++ b/src/platform/esp32/architecture.h @@ -33,9 +33,6 @@ #ifndef HAS_RADIO #define HAS_RADIO 1 #endif -#ifndef HAS_RTC -#define HAS_RTC 1 -#endif #ifndef HAS_CPU_SHUTDOWN #define HAS_CPU_SHUTDOWN 1 #endif diff --git a/src/platform/portduino/architecture.h b/src/platform/portduino/architecture.h index 9ee8ad366..b1698a4eb 100644 --- a/src/platform/portduino/architecture.h +++ b/src/platform/portduino/architecture.h @@ -17,9 +17,6 @@ #ifndef HAS_RADIO #define HAS_RADIO 1 #endif -#ifndef HAS_RTC -#define HAS_RTC 1 -#endif #ifndef HAS_TELEMETRY #define HAS_TELEMETRY 1 #endif diff --git a/variants/esp32/m5stack_coreink/variant.h b/variants/esp32/m5stack_coreink/variant.h index b1708352b..9bf45f2ff 100644 --- a/variants/esp32/m5stack_coreink/variant.h +++ b/variants/esp32/m5stack_coreink/variant.h @@ -15,7 +15,6 @@ // PCF8563 RTC Module #define PCF8563_RTC 0x51 -#define HAS_RTC 1 // Wheel // Down 37 diff --git a/variants/esp32s3/t-watch-s3/variant.h b/variants/esp32s3/t-watch-s3/variant.h index 216dda589..df275c31d 100644 --- a/variants/esp32s3/t-watch-s3/variant.h +++ b/variants/esp32s3/t-watch-s3/variant.h @@ -45,7 +45,6 @@ // PCF8563 RTC Module #define PCF8563_RTC 0x51 -#define HAS_RTC 1 #define I2C_SDA 10 // For QMC6310 sensors and screens #define I2C_SCL 11 // For QMC6310 sensors and screens diff --git a/variants/esp32s3/tbeam-s3-core/variant.h b/variants/esp32s3/tbeam-s3-core/variant.h index 1f900fcae..9ce4aade9 100644 --- a/variants/esp32s3/tbeam-s3-core/variant.h +++ b/variants/esp32s3/tbeam-s3-core/variant.h @@ -55,7 +55,6 @@ // PCF8563 RTC Module #define PCF8563_RTC 0x51 -#define HAS_RTC 1 // Specify the PMU as Wire1. In the t-beam-s3 core, PCF8563 and PMU share the bus #define PMU_USE_WIRE1 diff --git a/variants/esp32s3/tlora-pager/variant.h b/variants/esp32s3/tlora-pager/variant.h index e2d4029be..d97f864c3 100644 --- a/variants/esp32s3/tlora-pager/variant.h +++ b/variants/esp32s3/tlora-pager/variant.h @@ -40,7 +40,6 @@ // PCF85063 RTC Module #define PCF85063_RTC 0x51 -#define HAS_RTC 1 // Rotary #define ROTARY_A (40) diff --git a/variants/nrf52840/gat562_mesh_trial_tracker/variant.h b/variants/nrf52840/gat562_mesh_trial_tracker/variant.h index 2795a6141..b5632a7fb 100644 --- a/variants/nrf52840/gat562_mesh_trial_tracker/variant.h +++ b/variants/nrf52840/gat562_mesh_trial_tracker/variant.h @@ -259,8 +259,6 @@ SO GPIO 39/TXEN MAY NOT BE DEFINED FOR SUCCESSFUL OPERATION OF THE SX1262 - TG #define VBAT_AR_INTERNAL AR_INTERNAL_3_0 #define ADC_MULTIPLIER 1.73 -// #define HAS_RTC 1 - // #define HAS_ETHERNET 1 // #define RAK_4631 1 diff --git a/variants/nrf52840/heltec_mesh_node_t114/variant.h b/variants/nrf52840/heltec_mesh_node_t114/variant.h index fe825089e..e7385c4bb 100644 --- a/variants/nrf52840/heltec_mesh_node_t114/variant.h +++ b/variants/nrf52840/heltec_mesh_node_t114/variant.h @@ -223,7 +223,6 @@ No longer populated on PCB // VBAT=4.04V #define BATTERY_LPCOMP_THRESHOLD NRF_LPCOMP_REF_SUPPLY_2_8 -#define HAS_RTC 0 #ifdef __cplusplus } #endif diff --git a/variants/nrf52840/monteops_hw1/variant.h b/variants/nrf52840/monteops_hw1/variant.h index b5411bf04..a7fa7125b 100644 --- a/variants/nrf52840/monteops_hw1/variant.h +++ b/variants/nrf52840/monteops_hw1/variant.h @@ -208,8 +208,6 @@ SO GPIO 39/TXEN MAY NOT BE DEFINED FOR SUCCESSFUL OPERATION OF THE SX1262 - TG #define VBAT_AR_INTERNAL AR_INTERNAL_3_0 #define ADC_MULTIPLIER (1.73F) -// #define HAS_RTC 1 - #define HAS_ETHERNET 1 #define PIN_ETHERNET_RESET 21 diff --git a/variants/nrf52840/muzi_base/variant.h b/variants/nrf52840/muzi_base/variant.h index b82382314..0cbd0e3ef 100644 --- a/variants/nrf52840/muzi_base/variant.h +++ b/variants/nrf52840/muzi_base/variant.h @@ -38,7 +38,6 @@ extern "C" { #define COMPASS_ORIENTATION meshtastic_Config_DisplayConfig_CompassOrientation_DEGREES_270 #define HAS_ICM20948 // forces the i2c address to be seen as this sensor -#define HAS_RTC 1 #define RX8130CE_RTC 0x32 // LEDs diff --git a/variants/nrf52840/nano-g2-ultra/variant.h b/variants/nrf52840/nano-g2-ultra/variant.h index 5fa6ed9dd..631af72d8 100644 --- a/variants/nrf52840/nano-g2-ultra/variant.h +++ b/variants/nrf52840/nano-g2-ultra/variant.h @@ -129,7 +129,6 @@ External serial flash W25Q16JV_IQ // PCF8563 RTC Module #define PIN_RTC_INT (0 + 14) // Interrupt from the PCF8563 RTC #define PCF8563_RTC 0x51 -#define HAS_RTC 1 /* * SPI Interfaces diff --git a/variants/nrf52840/r1-neo/variant.h b/variants/nrf52840/r1-neo/variant.h index 1d2bc9bab..42d44d673 100644 --- a/variants/nrf52840/r1-neo/variant.h +++ b/variants/nrf52840/r1-neo/variant.h @@ -132,8 +132,6 @@ static const uint8_t SCK = PIN_SPI_SCK; #define ADC_MULTIPLIER 1.667 #define OCV_ARRAY 4120, 4020, 4000, 3940, 3870, 3820, 3750, 3630, 3550, 3450, 3100 -#define HAS_RTC 1 - #define RX8130CE_RTC 0x32 #ifdef __cplusplus diff --git a/variants/nrf52840/rak2560/variant.h b/variants/nrf52840/rak2560/variant.h index 0038f295d..acd1ae60e 100644 --- a/variants/nrf52840/rak2560/variant.h +++ b/variants/nrf52840/rak2560/variant.h @@ -244,8 +244,6 @@ SO GPIO 39/TXEN MAY NOT BE DEFINED FOR SUCCESSFUL OPERATION OF THE SX1262 - TG #define VBAT_AR_INTERNAL AR_INTERNAL_3_0 #define ADC_MULTIPLIER 1.73 -#define HAS_RTC 1 - #define RAK_4631 1 #define HALF_UART_PIN PIN_SERIAL1_RX diff --git a/variants/nrf52840/rak3401_1watt/variant.h b/variants/nrf52840/rak3401_1watt/variant.h index 8df0faccb..80b09cf69 100644 --- a/variants/nrf52840/rak3401_1watt/variant.h +++ b/variants/nrf52840/rak3401_1watt/variant.h @@ -208,8 +208,6 @@ static const uint8_t SCK = PIN_SPI_SCK; #define VBAT_AR_INTERNAL AR_INTERNAL_3_0 #define ADC_MULTIPLIER 1.73 -#define HAS_RTC 1 - #define RAK_4631 1 #ifdef __cplusplus diff --git a/variants/nrf52840/rak4631/variant.h b/variants/nrf52840/rak4631/variant.h index 37ae0f12a..6a6b32f27 100644 --- a/variants/nrf52840/rak4631/variant.h +++ b/variants/nrf52840/rak4631/variant.h @@ -280,8 +280,6 @@ SO GPIO 39/TXEN MAY NOT BE DEFINED FOR SUCCESSFUL OPERATION OF THE SX1262 - TG // VDD=3.3V AIN3=6/8*VDD=2.47V VBAT=1.66*AIN3=4.1V #define BATTERY_LPCOMP_THRESHOLD NRF_LPCOMP_REF_SUPPLY_11_16 -#define HAS_RTC 1 - #define HAS_ETHERNET 1 #define RAK_4631 1 diff --git a/variants/nrf52840/rak4631_epaper/variant.h b/variants/nrf52840/rak4631_epaper/variant.h index 19a2615f9..82c26af7b 100644 --- a/variants/nrf52840/rak4631_epaper/variant.h +++ b/variants/nrf52840/rak4631_epaper/variant.h @@ -217,8 +217,6 @@ static const uint8_t SCK = PIN_SPI_SCK; #define VBAT_AR_INTERNAL AR_INTERNAL_3_0 #define ADC_MULTIPLIER 1.73 -#define HAS_RTC 1 - #define RAK_4631 1 #ifdef __cplusplus diff --git a/variants/nrf52840/rak4631_epaper_onrxtx/variant.h b/variants/nrf52840/rak4631_epaper_onrxtx/variant.h index f1a1c58eb..2d34ab84c 100644 --- a/variants/nrf52840/rak4631_epaper_onrxtx/variant.h +++ b/variants/nrf52840/rak4631_epaper_onrxtx/variant.h @@ -192,8 +192,6 @@ static const uint8_t SCK = PIN_SPI_SCK; // #define VBAT_AR_INTERNAL AR_INTERNAL_3_0 // #define ADC_MULTIPLIER 1.73 -// #define HAS_RTC 1 - #ifdef __cplusplus } #endif diff --git a/variants/nrf52840/rak4631_eth_gw/variant.h b/variants/nrf52840/rak4631_eth_gw/variant.h index 24e0c0cd3..eb1d558ea 100644 --- a/variants/nrf52840/rak4631_eth_gw/variant.h +++ b/variants/nrf52840/rak4631_eth_gw/variant.h @@ -249,8 +249,6 @@ SO GPIO 39/TXEN MAY NOT BE DEFINED FOR SUCCESSFUL OPERATION OF THE SX1262 - TG #define VBAT_AR_INTERNAL AR_INTERNAL_3_0 #define ADC_MULTIPLIER 1.73 -#define HAS_RTC 1 - #define HAS_ETHERNET 1 #define RAK_4631 1 diff --git a/variants/nrf52840/rak4631_nomadstar_meteor_pro/variant.h b/variants/nrf52840/rak4631_nomadstar_meteor_pro/variant.h index 655cea6e4..aea497305 100644 --- a/variants/nrf52840/rak4631_nomadstar_meteor_pro/variant.h +++ b/variants/nrf52840/rak4631_nomadstar_meteor_pro/variant.h @@ -244,8 +244,6 @@ SO GPIO 39/TXEN MAY NOT BE DEFINED FOR SUCCESSFUL OPERATION OF THE SX1262 - TG #define VBAT_AR_INTERNAL AR_INTERNAL_3_0 #define ADC_MULTIPLIER 1.73 -#define HAS_RTC 0 - #define HAS_ETHERNET 0 #define RAK_4631 1 diff --git a/variants/nrf52840/rak_wismeshtap/variant.h b/variants/nrf52840/rak_wismeshtap/variant.h index a31bc7b15..358117cd5 100644 --- a/variants/nrf52840/rak_wismeshtap/variant.h +++ b/variants/nrf52840/rak_wismeshtap/variant.h @@ -266,8 +266,6 @@ SO GPIO 39/TXEN MAY NOT BE DEFINED FOR SUCCESSFUL OPERATION OF THE SX1262 - TG #define VBAT_AR_INTERNAL AR_INTERNAL_3_0 #define ADC_MULTIPLIER (1.73F) -#define HAS_RTC 1 - #define RAK_4631 1 #define AQ_SET_PIN 10 diff --git a/variants/nrf52840/t-echo/variant.h b/variants/nrf52840/t-echo/variant.h index f6f650315..f4644c6de 100644 --- a/variants/nrf52840/t-echo/variant.h +++ b/variants/nrf52840/t-echo/variant.h @@ -188,7 +188,6 @@ External serial flash WP25R1635FZUIL0 // PCF8563 RTC Module #define PIN_RTC_INT (0 + 16) // Interrupt from the PCF8563 RTC #define PCF8563_RTC 0x51 -#define HAS_RTC 1 /* * SPI Interfaces From caae6bc5970a8fe30c2802c58565c6cefea63566 Mon Sep 17 00:00:00 2001 From: scobert969 Date: Fri, 30 Jan 2026 10:31:26 -0800 Subject: [PATCH 88/99] Change canned message recipient's previous page to send page (#9227) * Change canned message recipient's previous page to send page * Set previousPage for new menu pages Set nextPage in back MenuPages to previousPage Removed back MenuAction --------- Co-authored-by: zeropt Co-authored-by: HarukiToreda <116696711+HarukiToreda@users.noreply.github.com> --- .../InkHUD/Applets/System/Menu/MenuAction.h | 1 - .../InkHUD/Applets/System/Menu/MenuApplet.cpp | 80 +++++++++++-------- 2 files changed, 46 insertions(+), 35 deletions(-) diff --git a/src/graphics/niche/InkHUD/Applets/System/Menu/MenuAction.h b/src/graphics/niche/InkHUD/Applets/System/Menu/MenuAction.h index 74ad5c85f..7cfb33e9d 100644 --- a/src/graphics/niche/InkHUD/Applets/System/Menu/MenuAction.h +++ b/src/graphics/niche/InkHUD/Applets/System/Menu/MenuAction.h @@ -22,7 +22,6 @@ enum MenuAction { STORE_CANNEDMESSAGE_SELECTION, SEND_CANNEDMESSAGE, SHUTDOWN, - BACK, NEXT_TILE, TOGGLE_BACKLIGHT, TOGGLE_GPS, diff --git a/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.cpp b/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.cpp index 93d2c6b83..b69e31e9a 100644 --- a/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.cpp +++ b/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.cpp @@ -325,10 +325,6 @@ void InkHUD::MenuApplet::execute(MenuItem item) } break; - case BACK: - showPage(item.nextPage); - return; - case NEXT_TILE: inkhud->nextTile(); // Unselect menu item after tile change @@ -868,6 +864,7 @@ void InkHUD::MenuApplet::showPage(MenuPage page) switch (page) { case ROOT: + previousPage = MenuPage::EXIT; // Optional: next applet if (settings->optionalMenuItems.nextTile && settings->userTiles.count > 1) items.push_back(MenuItem("Next Tile", MenuAction::NEXT_TILE, MenuPage::ROOT)); // Only if multiple applets shown @@ -878,7 +875,6 @@ void InkHUD::MenuApplet::showPage(MenuPage page) items.push_back(MenuItem("Node Config", MenuPage::NODE_CONFIG)); items.push_back(MenuItem("Save & Shut Down", MenuAction::SHUTDOWN)); items.push_back(MenuItem("Exit", MenuPage::EXIT)); - previousPage = MenuPage::EXIT; break; case SEND: @@ -888,11 +884,12 @@ void InkHUD::MenuApplet::showPage(MenuPage page) case CANNEDMESSAGE_RECIPIENT: populateRecipientPage(); - previousPage = MenuPage::OPTIONS; + previousPage = MenuPage::SEND; break; case OPTIONS: - items.push_back(MenuItem("Back", MenuAction::BACK, MenuPage::ROOT)); + previousPage = MenuPage::ROOT; + items.push_back(MenuItem("Back", previousPage)); // Optional: backlight if (settings->optionalMenuItems.backlight) items.push_back(MenuItem(backlight->isLatched() ? "Backlight Off" : "Keep Backlight On", // Label @@ -916,31 +913,32 @@ void InkHUD::MenuApplet::showPage(MenuPage page) invertedColors = (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_INVERTED); items.push_back(MenuItem("Invert Color", MenuAction::TOGGLE_INVERT_COLOR, MenuPage::OPTIONS, &invertedColors)); items.push_back(MenuItem("Exit", MenuPage::EXIT)); - previousPage = MenuPage::ROOT; break; case APPLETS: - populateAppletPage(); // must be first - items.insert(items.begin(), MenuItem("Back", MenuAction::BACK, MenuPage::OPTIONS)); - items.push_back(MenuItem("Exit", MenuPage::EXIT)); previousPage = MenuPage::OPTIONS; + populateAppletPage(); // must be first + items.insert(items.begin(), MenuItem("Back", previousPage)); + items.push_back(MenuItem("Exit", MenuPage::EXIT)); break; case AUTOSHOW: - populateAutoshowPage(); // must be first - items.insert(items.begin(), MenuItem("Back", MenuAction::BACK, MenuPage::OPTIONS)); - items.push_back(MenuItem("Exit", MenuPage::EXIT)); previousPage = MenuPage::OPTIONS; + populateAutoshowPage(); // must be first + items.insert(items.begin(), MenuItem("Back", previousPage)); + items.push_back(MenuItem("Exit", MenuPage::EXIT)); break; case RECENTS: + previousPage = MenuPage::OPTIONS; populateRecentsPage(); // builds only the options - items.insert(items.begin(), MenuItem("Back", MenuAction::BACK, MenuPage::OPTIONS)); + items.insert(items.begin(), MenuItem("Back", previousPage)); items.push_back(MenuItem("Exit", MenuPage::EXIT)); break; case NODE_CONFIG: - items.push_back(MenuItem("Back", MenuAction::BACK, MenuPage::ROOT)); + previousPage = MenuPage::ROOT; + items.push_back(MenuItem("Back", previousPage)); // Radio Config Section items.push_back(MenuItem::Header("Radio Config")); items.push_back(MenuItem("LoRa", MenuPage::NODE_CONFIG_LORA)); @@ -965,8 +963,8 @@ void InkHUD::MenuApplet::showPage(MenuPage page) break; case NODE_CONFIG_DEVICE: { - - items.push_back(MenuItem("Back", MenuAction::BACK, MenuPage::NODE_CONFIG)); + previousPage = MenuPage::NODE_CONFIG; + items.push_back(MenuItem("Back", previousPage)); const char *role = DisplayFormatters::getDeviceRole(config.device.role); nodeConfigLabels.emplace_back("Role: " + std::string(role)); @@ -981,7 +979,8 @@ void InkHUD::MenuApplet::showPage(MenuPage page) } case NODE_CONFIG_POSITION: { - items.push_back(MenuItem("Back", MenuAction::BACK, MenuPage::NODE_CONFIG)); + previousPage = MenuPage::NODE_CONFIG; + items.push_back(MenuItem("Back", previousPage)); #if !MESHTASTIC_EXCLUDE_GPS && HAS_GPS const auto mode = config.position.gps_mode; if (mode == meshtastic_Config_PositionConfig_GpsMode_NOT_PRESENT) { @@ -996,7 +995,8 @@ void InkHUD::MenuApplet::showPage(MenuPage page) } case NODE_CONFIG_POWER: { - items.push_back(MenuItem("Back", MenuAction::BACK, MenuPage::NODE_CONFIG)); + previousPage = MenuPage::NODE_CONFIG; + items.push_back(MenuItem("Back", previousPage)); #if defined(ARCH_ESP32) items.push_back(MenuItem("Powersave", MenuAction::TOGGLE_POWER_SAVE, MenuPage::EXIT, &config.power.is_power_saving)); #endif @@ -1029,7 +1029,8 @@ void InkHUD::MenuApplet::showPage(MenuPage page) } case NODE_CONFIG_POWER_ADC_CAL: { - items.push_back(MenuItem("Back", MenuAction::BACK, MenuPage::NODE_CONFIG_POWER)); + previousPage = MenuPage::NODE_CONFIG_POWER; + items.push_back(MenuItem("Back", previousPage)); // Instruction text (header-style, non-selectable) items.push_back(MenuItem::Header("Run on full charge Only")); @@ -1042,7 +1043,8 @@ void InkHUD::MenuApplet::showPage(MenuPage page) } case NODE_CONFIG_NETWORK: { - items.push_back(MenuItem("Back", MenuAction::BACK, MenuPage::NODE_CONFIG)); + previousPage = MenuPage::NODE_CONFIG; + items.push_back(MenuItem("Back", previousPage)); const char *wifiLabel = config.network.wifi_enabled ? "WiFi: On" : "WiFi: Off"; @@ -1099,7 +1101,8 @@ void InkHUD::MenuApplet::showPage(MenuPage page) } case NODE_CONFIG_DISPLAY: { - items.push_back(MenuItem("Back", MenuAction::BACK, MenuPage::NODE_CONFIG)); + previousPage = MenuPage::NODE_CONFIG; + items.push_back(MenuItem("Back", previousPage)); items.push_back(MenuItem("12-Hour Clock", MenuAction::TOGGLE_12H_CLOCK, MenuPage::NODE_CONFIG_DISPLAY, &config.display.use_12h_clock)); @@ -1114,7 +1117,8 @@ void InkHUD::MenuApplet::showPage(MenuPage page) } case NODE_CONFIG_BLUETOOTH: { - items.push_back(MenuItem("Back", MenuAction::BACK, MenuPage::NODE_CONFIG)); + previousPage = MenuPage::NODE_CONFIG; + items.push_back(MenuItem("Back", previousPage)); const char *btLabel = config.bluetooth.enabled ? "Bluetooth: On" : "Bluetooth: Off"; items.push_back(MenuItem(btLabel, MenuAction::TOGGLE_BLUETOOTH, MenuPage::EXIT)); @@ -1127,8 +1131,8 @@ void InkHUD::MenuApplet::showPage(MenuPage page) } case NODE_CONFIG_LORA: { - - items.push_back(MenuItem("Back", MenuAction::BACK, MenuPage::NODE_CONFIG)); + previousPage = MenuPage::NODE_CONFIG; + items.push_back(MenuItem("Back", previousPage)); const char *region = myRegion ? myRegion->name : "Unset"; nodeConfigLabels.emplace_back("Region: " + std::string(region)); @@ -1150,7 +1154,8 @@ void InkHUD::MenuApplet::showPage(MenuPage page) } case NODE_CONFIG_CHANNELS: { - items.push_back(MenuItem("Back", MenuAction::BACK, MenuPage::NODE_CONFIG)); + previousPage = MenuPage::NODE_CONFIG; + items.push_back(MenuItem("Back", previousPage)); for (uint8_t i = 0; i < MAX_NUM_CHANNELS; i++) { meshtastic_Channel &ch = channels.getByIndex(i); @@ -1181,7 +1186,8 @@ void InkHUD::MenuApplet::showPage(MenuPage page) } case NODE_CONFIG_CHANNEL_DETAIL: { - items.push_back(MenuItem("Back", MenuAction::BACK, MenuPage::NODE_CONFIG_CHANNELS)); + previousPage = MenuPage::NODE_CONFIG_CHANNELS; + items.push_back(MenuItem("Back", previousPage)); meshtastic_Channel &ch = channels.getByIndex(selectedChannelIndex); @@ -1226,7 +1232,8 @@ void InkHUD::MenuApplet::showPage(MenuPage page) } case NODE_CONFIG_CHANNEL_PRECISION: { - items.push_back(MenuItem("Back", MenuAction::BACK, MenuPage::NODE_CONFIG_CHANNEL_DETAIL)); + previousPage = MenuPage::NODE_CONFIG_CHANNEL_DETAIL; + items.push_back(MenuItem("Back", previousPage)); meshtastic_Channel &ch = channels.getByIndex(selectedChannelIndex); if (!ch.settings.has_module_settings || ch.settings.module_settings.position_precision == 0) { items.push_back(MenuItem("Position is Off", MenuPage::NODE_CONFIG_CHANNEL_DETAIL)); @@ -1247,7 +1254,8 @@ void InkHUD::MenuApplet::showPage(MenuPage page) } case NODE_CONFIG_DEVICE_ROLE: { - items.push_back(MenuItem("Back", MenuAction::BACK, MenuPage::NODE_CONFIG_DEVICE)); + previousPage = MenuPage::NODE_CONFIG_DEVICE; + items.push_back(MenuItem("Back", previousPage)); items.push_back(MenuItem("Client", MenuAction::SET_ROLE_CLIENT, MenuPage::EXIT)); items.push_back(MenuItem("Client Mute", MenuAction::SET_ROLE_CLIENT_MUTE, MenuPage::EXIT)); items.push_back(MenuItem("Router", MenuAction::SET_ROLE_ROUTER, MenuPage::EXIT)); @@ -1257,7 +1265,8 @@ void InkHUD::MenuApplet::showPage(MenuPage page) } case TIMEZONE: - items.push_back(MenuItem("Back", MenuAction::BACK, MenuPage::NODE_CONFIG_DEVICE)); + previousPage = MenuPage::NODE_CONFIG_DEVICE; + items.push_back(MenuItem("Back", previousPage)); items.push_back(MenuItem("US/Hawaii", SET_TZ_US_HAWAII, MenuPage::NODE_CONFIG_DEVICE)); items.push_back(MenuItem("US/Alaska", SET_TZ_US_ALASKA, MenuPage::NODE_CONFIG_DEVICE)); items.push_back(MenuItem("US/Pacific", SET_TZ_US_PACIFIC, MenuPage::NODE_CONFIG_DEVICE)); @@ -1279,7 +1288,8 @@ void InkHUD::MenuApplet::showPage(MenuPage page) break; case REGION: - items.push_back(MenuItem("Back", MenuAction::BACK, MenuPage::NODE_CONFIG_LORA)); + previousPage = MenuPage::NODE_CONFIG_LORA; + items.push_back(MenuItem("Back", previousPage)); items.push_back(MenuItem("US", MenuAction::SET_REGION_US, MenuPage::EXIT)); items.push_back(MenuItem("EU 868", MenuAction::SET_REGION_EU_868, MenuPage::EXIT)); items.push_back(MenuItem("EU 433", MenuAction::SET_REGION_EU_433, MenuPage::EXIT)); @@ -1310,7 +1320,8 @@ void InkHUD::MenuApplet::showPage(MenuPage page) break; case NODE_CONFIG_PRESET: { - items.push_back(MenuItem("Back", MenuAction::BACK, MenuPage::NODE_CONFIG_LORA)); + previousPage = MenuPage::NODE_CONFIG_LORA; + items.push_back(MenuItem("Back", previousPage)); items.push_back(MenuItem("Long Moderate", MenuAction::SET_PRESET_LONG_MODERATE, MenuPage::EXIT)); items.push_back(MenuItem("Long Fast", MenuAction::SET_PRESET_LONG_FAST, MenuPage::EXIT)); items.push_back(MenuItem("Medium Slow", MenuAction::SET_PRESET_MEDIUM_SLOW, MenuPage::EXIT)); @@ -1323,7 +1334,8 @@ void InkHUD::MenuApplet::showPage(MenuPage page) } // Administration Section case NODE_CONFIG_ADMIN_RESET: - items.push_back(MenuItem("Back", MenuAction::BACK, MenuPage::NODE_CONFIG)); + previousPage = MenuPage::NODE_CONFIG; + items.push_back(MenuItem("Back", previousPage)); items.push_back(MenuItem("Reset All", MenuAction::RESET_NODEDB_ALL, MenuPage::EXIT)); items.push_back(MenuItem("Keep Favorites Only", MenuAction::RESET_NODEDB_KEEP_FAVORITES, MenuPage::EXIT)); items.push_back(MenuItem("Exit", MenuPage::EXIT)); From 7bbfe99fbef6d2f9302462662518295fe151a9fd Mon Sep 17 00:00:00 2001 From: scobert969 Date: Fri, 30 Jan 2026 10:35:10 -0800 Subject: [PATCH 89/99] Add on-screen keyboard to InkHUD (#9445) * Added keyboard option to menu. Shows a keyboard layout but does not type. * Keyboard types into text box and wraps. * send FreeText messages from the send submenu - renamed `KEYBOARD` action to `FREE_TEXT` and moved its menu location to the send submenu - opening the FreeText applet from the menu keeps the menu open and disabled the timeout - the FreeText applet writes to inkhud->freetext - the sending a canned message checks inkhud->freetext and if it isn't empty, sends and clears the inkhud->freetext * Text scrolls along with input * handle free text message completion as an event implements `handleFreeText` and `OnFreeText()` for system applets to interface with the FreeText Applet The FreeText Applet generates an `OnFreeText` event when completing a message which is handled by the first system applet with the `handleFreeText` flag set to true. The Menu Applet now handles this event. * call `onFreeText` whenever the FreeText Applet exits allows the menu to consistently restart its auto-close timeout * Add text cursor * Change UI to remove the header and make text box longer Keyboard displays captial letters for legibility Keyboard types captial letters with long press * center FreeText keys and draw symbolic buttons Move input field and keyboard drawing to their own functions: - `drawInputField()` - `drawKeyboard()` Store the keys in a 1-dimensional array Implement a matching array, `keyWidths`, to set key widths relative to the font size * Add character limit and counter * Fix softlock when hitting character limit * Move text box as its own menu page * rework FreeTextApplet into KeyboardApplet - The Keyboard Applet renders an on-screen keyboard at the lower portion of the screen. - Calling `inkhud->openKeyboard()` sends all the user applets to the background and resizes the first system applet with `handleFreeText` set to True to fit above the on-screen keyboard - `inkhud->closeKeyboard()` reverses this layout change * Fix input box rendering and add character limit to menu free text * remove FREE_TEXT menu page and use the FREE_TEXT menu action solely * force update when changing the free text message * reorganize KeyboardApplet - add comments after each row of `key[]` and `keyWidths[]` to preserve formatting - The selected key is now set using the key index directly - rowWidths are pre-calculated in the KeyboardApplet constructor - removed `drawKeyboard()` and implemented `drawKeyLabel()` * implement `Renderer::clearTile()` to clear the region below a tile * add parameter to forceUpdate() for re-rendering the full screen setting the `all` parameter to true in `inkhud->forceUpdate()` now causes the full screen buffer to clear an re-render. This is helpful for when sending applets to the background and the UI needs a clean canvas. System Applets can now set the `alwaysRender` flag true which causes it to re-render on every screen update. This is set to true in the Battery Icon Applet. * clean up tile clearing loops * implement dirty rendering to let applets draw over their previous render - `Applet::requestUpdate()` now has an optional flag to keep the old canvas - If honored, the renderer calls `render(true)` which runs `onDirtyRender()` instead of `onRender()` for said applet - The renderer will not call a dirty render if the full screen is getting re-rendered * simplify arithmetic in clearTile for better understanding * combine Applet::onRender() and Applet::onDirtyRender() into Applet::onRender(bool full) - add new `full` parameter to onRender() in every applet. This parameter can be ignored by most applets. - `Applet::requestUpdate()` has an optional flag that requests a full render by default * implement tile and partial rendering in KeyboardApplet * add comment for drawKeyLabel() * improve clarity of byte operations in clearTile() * remove typo and commented code * fix inaccurate comments * add null check to openKeyboard() and closeKeyboard() --------- Co-authored-by: zeropt Co-authored-by: HarukiToreda <116696711+HarukiToreda@users.noreply.github.com> --- src/graphics/niche/InkHUD/Applet.cpp | 13 +- src/graphics/niche/InkHUD/Applet.h | 14 +- .../InkHUD/Applets/Bases/Map/MapApplet.cpp | 2 +- .../InkHUD/Applets/Bases/Map/MapApplet.h | 2 +- .../Applets/Bases/NodeList/NodeListApplet.cpp | 2 +- .../Applets/Bases/NodeList/NodeListApplet.h | 2 +- .../BasicExample/BasicExampleApplet.cpp | 2 +- .../BasicExample/BasicExampleApplet.h | 2 +- .../NewMsgExample/NewMsgExampleApplet.cpp | 2 +- .../NewMsgExample/NewMsgExampleApplet.h | 2 +- .../System/AlignStick/AlignStickApplet.cpp | 10 +- .../System/AlignStick/AlignStickApplet.h | 2 +- .../System/BatteryIcon/BatteryIconApplet.cpp | 4 +- .../System/BatteryIcon/BatteryIconApplet.h | 2 +- .../System/Keyboard/KeyboardApplet.cpp | 257 ++++++++++++++++++ .../Applets/System/Keyboard/KeyboardApplet.h | 66 +++++ .../InkHUD/Applets/System/Logo/LogoApplet.cpp | 10 +- .../InkHUD/Applets/System/Logo/LogoApplet.h | 2 +- .../InkHUD/Applets/System/Menu/MenuAction.h | 1 + .../InkHUD/Applets/System/Menu/MenuApplet.cpp | 253 ++++++++++++----- .../InkHUD/Applets/System/Menu/MenuApplet.h | 12 +- .../Notification/NotificationApplet.cpp | 11 +- .../System/Notification/NotificationApplet.h | 2 +- .../Applets/System/Pairing/PairingApplet.cpp | 4 +- .../Applets/System/Pairing/PairingApplet.h | 2 +- .../System/Placeholder/PlaceholderApplet.cpp | 2 +- .../System/Placeholder/PlaceholderApplet.h | 2 +- .../InkHUD/Applets/System/Tips/TipsApplet.cpp | 9 +- .../InkHUD/Applets/System/Tips/TipsApplet.h | 2 +- .../User/AllMessage/AllMessageApplet.cpp | 2 +- .../User/AllMessage/AllMessageApplet.h | 2 +- .../niche/InkHUD/Applets/User/DM/DMApplet.cpp | 2 +- .../niche/InkHUD/Applets/User/DM/DMApplet.h | 2 +- .../User/Positions/PositionsApplet.cpp | 4 +- .../Applets/User/Positions/PositionsApplet.h | 2 +- .../ThreadedMessage/ThreadedMessageApplet.cpp | 2 +- .../ThreadedMessage/ThreadedMessageApplet.h | 2 +- src/graphics/niche/InkHUD/Events.cpp | 35 ++- src/graphics/niche/InkHUD/Events.h | 5 + src/graphics/niche/InkHUD/InkHUD.cpp | 36 ++- src/graphics/niche/InkHUD/InkHUD.h | 10 +- src/graphics/niche/InkHUD/Renderer.cpp | 113 +++++++- src/graphics/niche/InkHUD/Renderer.h | 6 +- src/graphics/niche/InkHUD/SystemApplet.h | 10 +- src/graphics/niche/InkHUD/Tile.cpp | 16 +- src/graphics/niche/InkHUD/Tile.h | 2 + src/graphics/niche/InkHUD/WindowManager.cpp | 56 +++- src/graphics/niche/InkHUD/WindowManager.h | 3 + src/graphics/niche/InkHUD/docs/README.md | 4 +- 49 files changed, 852 insertions(+), 158 deletions(-) create mode 100644 src/graphics/niche/InkHUD/Applets/System/Keyboard/KeyboardApplet.cpp create mode 100644 src/graphics/niche/InkHUD/Applets/System/Keyboard/KeyboardApplet.h diff --git a/src/graphics/niche/InkHUD/Applet.cpp b/src/graphics/niche/InkHUD/Applet.cpp index 1e89ebe1b..ccdd76f97 100644 --- a/src/graphics/niche/InkHUD/Applet.cpp +++ b/src/graphics/niche/InkHUD/Applet.cpp @@ -55,7 +55,7 @@ InkHUD::Tile *InkHUD::Applet::getTile() } // Draw the applet -void InkHUD::Applet::render() +void InkHUD::Applet::render(bool full) { assert(assignedTile); // Ensure that we have a tile assert(assignedTile->getAssignedApplet() == this); // Ensure that we have a reciprocal link with the tile @@ -65,10 +65,11 @@ void InkHUD::Applet::render() wantRender = false; // Flag set by requestUpdate wantAutoshow = false; // Flag set by requestAutoShow. May or may not have been honored. wantUpdateType = Drivers::EInk::UpdateTypes::UNSPECIFIED; // Update type we wanted. May on may not have been granted. + wantFullRender = true; // Default to a full render updateDimensions(); resetDrawingSpace(); - onRender(); // Derived applet's drawing takes place here + onRender(full); // Draw the applet // Handle "Tile Highlighting" // Some devices may use an auxiliary button to switch between tiles @@ -115,6 +116,11 @@ Drivers::EInk::UpdateTypes InkHUD::Applet::wantsUpdateType() return wantUpdateType; } +bool InkHUD::Applet::wantsFullRender() +{ + return wantFullRender; +} + // Get size of the applet's drawing space from its tile // Performed immediately before derived applet's drawing code runs void InkHUD::Applet::updateDimensions() @@ -142,10 +148,11 @@ void InkHUD::Applet::resetDrawingSpace() // Once the renderer has given other applets a chance to process whatever event we just detected, // it will run Applet::render(), which may draw our applet to screen, if it is shown (foreground) // We should requestUpdate even if our applet is currently background, because this might be changed by autoshow -void InkHUD::Applet::requestUpdate(Drivers::EInk::UpdateTypes type) +void InkHUD::Applet::requestUpdate(Drivers::EInk::UpdateTypes type, bool full) { wantRender = true; wantUpdateType = type; + wantFullRender = full; inkhud->requestUpdate(); } diff --git a/src/graphics/niche/InkHUD/Applet.h b/src/graphics/niche/InkHUD/Applet.h index b35ca5cc0..69d35a234 100644 --- a/src/graphics/niche/InkHUD/Applet.h +++ b/src/graphics/niche/InkHUD/Applet.h @@ -64,10 +64,11 @@ class Applet : public GFX // Rendering - void render(); // Draw the applet + void render(bool full); // Draw the applet bool wantsToRender(); // Check whether applet wants to render bool wantsToAutoshow(); // Check whether applet wants to become foreground Drivers::EInk::UpdateTypes wantsUpdateType(); // Check which display update type the applet would prefer + bool wantsFullRender(); // Check whether applet wants to render over its previous render void updateDimensions(); // Get current size from tile void resetDrawingSpace(); // Makes sure every render starts with same parameters @@ -82,7 +83,7 @@ class Applet : public GFX // Event handlers - virtual void onRender() = 0; // All drawing happens here + virtual void onRender(bool full) = 0; // For drawing the applet virtual void onActivate() {} virtual void onDeactivate() {} virtual void onForeground() {} @@ -96,6 +97,9 @@ class Applet : public GFX virtual void onNavDown() {} virtual void onNavLeft() {} virtual void onNavRight() {} + virtual void onFreeText(char c) {} + virtual void onFreeTextDone() {} + virtual void onFreeTextCancel() {} virtual bool approveNotification(Notification &n); // Allow an applet to veto a notification @@ -108,8 +112,9 @@ class Applet : public GFX protected: void drawPixel(int16_t x, int16_t y, uint16_t color) override; // Place a single pixel. All drawing output passes through here - void requestUpdate(EInk::UpdateTypes type = EInk::UpdateTypes::UNSPECIFIED); // Ask WindowManager to schedule a display update - void requestAutoshow(); // Ask for applet to be moved to foreground + void requestUpdate(EInk::UpdateTypes type = EInk::UpdateTypes::UNSPECIFIED, + bool full = true); // Ask WindowManager to schedule a display update + void requestAutoshow(); // Ask for applet to be moved to foreground uint16_t X(float f); // Map applet width, mapped from 0 to 1.0 uint16_t Y(float f); // Map applet height, mapped from 0 to 1.0 @@ -164,6 +169,7 @@ class Applet : public GFX bool wantAutoshow = false; // Does the applet have new data it would like to display in foreground? NicheGraphics::Drivers::EInk::UpdateTypes wantUpdateType = NicheGraphics::Drivers::EInk::UpdateTypes::UNSPECIFIED; // Which update method we'd prefer when redrawing the display + bool wantFullRender = true; // Render with a fresh canvas using GFX::setFont; // Make sure derived classes use AppletFont instead of AdafruitGFX fonts directly using GFX::setRotation; // Block setRotation calls. Rotation is handled globally by WindowManager. diff --git a/src/graphics/niche/InkHUD/Applets/Bases/Map/MapApplet.cpp b/src/graphics/niche/InkHUD/Applets/Bases/Map/MapApplet.cpp index d383a11e4..4cf83966b 100644 --- a/src/graphics/niche/InkHUD/Applets/Bases/Map/MapApplet.cpp +++ b/src/graphics/niche/InkHUD/Applets/Bases/Map/MapApplet.cpp @@ -4,7 +4,7 @@ using namespace NicheGraphics; -void InkHUD::MapApplet::onRender() +void InkHUD::MapApplet::onRender(bool full) { // Abort if no markers to render if (!enoughMarkers()) { diff --git a/src/graphics/niche/InkHUD/Applets/Bases/Map/MapApplet.h b/src/graphics/niche/InkHUD/Applets/Bases/Map/MapApplet.h index f45a36071..11dfb39d9 100644 --- a/src/graphics/niche/InkHUD/Applets/Bases/Map/MapApplet.h +++ b/src/graphics/niche/InkHUD/Applets/Bases/Map/MapApplet.h @@ -27,7 +27,7 @@ namespace NicheGraphics::InkHUD class MapApplet : public Applet { public: - void onRender() override; + void onRender(bool full) override; protected: virtual bool shouldDrawNode(meshtastic_NodeInfoLite *node) { return true; } // Allow derived applets to filter the nodes diff --git a/src/graphics/niche/InkHUD/Applets/Bases/NodeList/NodeListApplet.cpp b/src/graphics/niche/InkHUD/Applets/Bases/NodeList/NodeListApplet.cpp index 5c9906fba..9794c3efb 100644 --- a/src/graphics/niche/InkHUD/Applets/Bases/NodeList/NodeListApplet.cpp +++ b/src/graphics/niche/InkHUD/Applets/Bases/NodeList/NodeListApplet.cpp @@ -103,7 +103,7 @@ uint8_t InkHUD::NodeListApplet::maxCards() } // Draw, using info which derived applet placed into NodeListApplet::cards for us -void InkHUD::NodeListApplet::onRender() +void InkHUD::NodeListApplet::onRender(bool full) { // ================================ diff --git a/src/graphics/niche/InkHUD/Applets/Bases/NodeList/NodeListApplet.h b/src/graphics/niche/InkHUD/Applets/Bases/NodeList/NodeListApplet.h index c2340027b..8babdba03 100644 --- a/src/graphics/niche/InkHUD/Applets/Bases/NodeList/NodeListApplet.h +++ b/src/graphics/niche/InkHUD/Applets/Bases/NodeList/NodeListApplet.h @@ -46,7 +46,7 @@ class NodeListApplet : public Applet, public MeshModule public: NodeListApplet(const char *name); - void onRender() override; + void onRender(bool full) override; bool wantPacket(const meshtastic_MeshPacket *p) override; ProcessMessage handleReceived(const meshtastic_MeshPacket &mp) override; diff --git a/src/graphics/niche/InkHUD/Applets/Examples/BasicExample/BasicExampleApplet.cpp b/src/graphics/niche/InkHUD/Applets/Examples/BasicExample/BasicExampleApplet.cpp index c52719e55..71b6d9a7a 100644 --- a/src/graphics/niche/InkHUD/Applets/Examples/BasicExample/BasicExampleApplet.cpp +++ b/src/graphics/niche/InkHUD/Applets/Examples/BasicExample/BasicExampleApplet.cpp @@ -6,7 +6,7 @@ using namespace NicheGraphics; // All drawing happens here // Our basic example doesn't do anything useful. It just passively prints some text. -void InkHUD::BasicExampleApplet::onRender() +void InkHUD::BasicExampleApplet::onRender(bool full) { printAt(0, 0, "Hello, World!"); diff --git a/src/graphics/niche/InkHUD/Applets/Examples/BasicExample/BasicExampleApplet.h b/src/graphics/niche/InkHUD/Applets/Examples/BasicExample/BasicExampleApplet.h index aed63cdc8..a36f6e8d5 100644 --- a/src/graphics/niche/InkHUD/Applets/Examples/BasicExample/BasicExampleApplet.h +++ b/src/graphics/niche/InkHUD/Applets/Examples/BasicExample/BasicExampleApplet.h @@ -28,7 +28,7 @@ class BasicExampleApplet : public Applet // You must have an onRender() method // All drawing happens here - void onRender() override; + void onRender(bool full) override; }; } // namespace NicheGraphics::InkHUD diff --git a/src/graphics/niche/InkHUD/Applets/Examples/NewMsgExample/NewMsgExampleApplet.cpp b/src/graphics/niche/InkHUD/Applets/Examples/NewMsgExample/NewMsgExampleApplet.cpp index 6b02f4c92..cf3fd7714 100644 --- a/src/graphics/niche/InkHUD/Applets/Examples/NewMsgExample/NewMsgExampleApplet.cpp +++ b/src/graphics/niche/InkHUD/Applets/Examples/NewMsgExample/NewMsgExampleApplet.cpp @@ -35,7 +35,7 @@ ProcessMessage InkHUD::NewMsgExampleApplet::handleReceived(const meshtastic_Mesh // We can trigger a render by calling requestUpdate() // Render might be called by some external source // We should always be ready to draw -void InkHUD::NewMsgExampleApplet::onRender() +void InkHUD::NewMsgExampleApplet::onRender(bool full) { printAt(0, 0, "Example: NewMsg", LEFT, TOP); // Print top-left corner of text at (0,0) diff --git a/src/graphics/niche/InkHUD/Applets/Examples/NewMsgExample/NewMsgExampleApplet.h b/src/graphics/niche/InkHUD/Applets/Examples/NewMsgExample/NewMsgExampleApplet.h index 22670a0f0..599f08a7a 100644 --- a/src/graphics/niche/InkHUD/Applets/Examples/NewMsgExample/NewMsgExampleApplet.h +++ b/src/graphics/niche/InkHUD/Applets/Examples/NewMsgExample/NewMsgExampleApplet.h @@ -34,7 +34,7 @@ class NewMsgExampleApplet : public Applet, public SinglePortModule NewMsgExampleApplet() : SinglePortModule("NewMsgExampleApplet", meshtastic_PortNum_TEXT_MESSAGE_APP) {} // All drawing happens here - void onRender() override; + void onRender(bool full) override; // Your applet might also want to use some of these // Useful for setting up or tidying up diff --git a/src/graphics/niche/InkHUD/Applets/System/AlignStick/AlignStickApplet.cpp b/src/graphics/niche/InkHUD/Applets/System/AlignStick/AlignStickApplet.cpp index 67ef87f41..3afa80149 100644 --- a/src/graphics/niche/InkHUD/Applets/System/AlignStick/AlignStickApplet.cpp +++ b/src/graphics/niche/InkHUD/Applets/System/AlignStick/AlignStickApplet.cpp @@ -10,7 +10,7 @@ InkHUD::AlignStickApplet::AlignStickApplet() bringToForeground(); } -void InkHUD::AlignStickApplet::onRender() +void InkHUD::AlignStickApplet::onRender(bool full) { setFont(fontMedium); printAt(0, 0, "Align Joystick:"); @@ -152,19 +152,17 @@ void InkHUD::AlignStickApplet::onBackground() // Need to force an update, as a polite request wouldn't be honored, seeing how we are now in the background // Usually, onBackground is followed by another applet's onForeground (which requests update), but not in this case - inkhud->forceUpdate(EInk::UpdateTypes::FULL); + inkhud->forceUpdate(EInk::UpdateTypes::FULL, true); } void InkHUD::AlignStickApplet::onButtonLongPress() { sendToBackground(); - inkhud->forceUpdate(EInk::UpdateTypes::FULL); } void InkHUD::AlignStickApplet::onExitLong() { sendToBackground(); - inkhud->forceUpdate(EInk::UpdateTypes::FULL); } void InkHUD::AlignStickApplet::onNavUp() @@ -172,7 +170,6 @@ void InkHUD::AlignStickApplet::onNavUp() settings->joystick.aligned = true; sendToBackground(); - inkhud->forceUpdate(EInk::UpdateTypes::FULL); } void InkHUD::AlignStickApplet::onNavDown() @@ -181,7 +178,6 @@ void InkHUD::AlignStickApplet::onNavDown() settings->joystick.aligned = true; sendToBackground(); - inkhud->forceUpdate(EInk::UpdateTypes::FULL); } void InkHUD::AlignStickApplet::onNavLeft() @@ -190,7 +186,6 @@ void InkHUD::AlignStickApplet::onNavLeft() settings->joystick.aligned = true; sendToBackground(); - inkhud->forceUpdate(EInk::UpdateTypes::FULL); } void InkHUD::AlignStickApplet::onNavRight() @@ -199,7 +194,6 @@ void InkHUD::AlignStickApplet::onNavRight() settings->joystick.aligned = true; sendToBackground(); - inkhud->forceUpdate(EInk::UpdateTypes::FULL); } #endif \ No newline at end of file diff --git a/src/graphics/niche/InkHUD/Applets/System/AlignStick/AlignStickApplet.h b/src/graphics/niche/InkHUD/Applets/System/AlignStick/AlignStickApplet.h index 8dba33165..7c8d00155 100644 --- a/src/graphics/niche/InkHUD/Applets/System/AlignStick/AlignStickApplet.h +++ b/src/graphics/niche/InkHUD/Applets/System/AlignStick/AlignStickApplet.h @@ -23,7 +23,7 @@ class AlignStickApplet : public SystemApplet public: AlignStickApplet(); - void onRender() override; + void onRender(bool full) override; void onForeground() override; void onBackground() override; void onButtonLongPress() override; diff --git a/src/graphics/niche/InkHUD/Applets/System/BatteryIcon/BatteryIconApplet.cpp b/src/graphics/niche/InkHUD/Applets/System/BatteryIcon/BatteryIconApplet.cpp index 4f99d99ee..0cc6f50ed 100644 --- a/src/graphics/niche/InkHUD/Applets/System/BatteryIcon/BatteryIconApplet.cpp +++ b/src/graphics/niche/InkHUD/Applets/System/BatteryIcon/BatteryIconApplet.cpp @@ -6,6 +6,8 @@ using namespace NicheGraphics; InkHUD::BatteryIconApplet::BatteryIconApplet() { + alwaysRender = true; // render everytime the screen is updated + // Show at boot, if user has previously enabled the feature if (settings->optionalFeatures.batteryIcon) bringToForeground(); @@ -44,7 +46,7 @@ int InkHUD::BatteryIconApplet::onPowerStatusUpdate(const meshtastic::Status *sta return 0; // Tell Observable to continue informing other observers } -void InkHUD::BatteryIconApplet::onRender() +void InkHUD::BatteryIconApplet::onRender(bool full) { // Fill entire tile // - size of icon controlled by size of tile diff --git a/src/graphics/niche/InkHUD/Applets/System/BatteryIcon/BatteryIconApplet.h b/src/graphics/niche/InkHUD/Applets/System/BatteryIcon/BatteryIconApplet.h index e5b4172be..ceaf88d7f 100644 --- a/src/graphics/niche/InkHUD/Applets/System/BatteryIcon/BatteryIconApplet.h +++ b/src/graphics/niche/InkHUD/Applets/System/BatteryIcon/BatteryIconApplet.h @@ -23,7 +23,7 @@ class BatteryIconApplet : public SystemApplet public: BatteryIconApplet(); - void onRender() override; + void onRender(bool full) override; int onPowerStatusUpdate(const meshtastic::Status *status); // Called when new info about battery is available private: diff --git a/src/graphics/niche/InkHUD/Applets/System/Keyboard/KeyboardApplet.cpp b/src/graphics/niche/InkHUD/Applets/System/Keyboard/KeyboardApplet.cpp new file mode 100644 index 000000000..57581d56b --- /dev/null +++ b/src/graphics/niche/InkHUD/Applets/System/Keyboard/KeyboardApplet.cpp @@ -0,0 +1,257 @@ +#ifdef MESHTASTIC_INCLUDE_INKHUD +#include "./KeyboardApplet.h" + +using namespace NicheGraphics; + +InkHUD::KeyboardApplet::KeyboardApplet() +{ + // Calculate row widths + for (uint8_t row = 0; row < KBD_ROWS; row++) { + rowWidths[row] = 0; + for (uint8_t col = 0; col < KBD_COLS; col++) + rowWidths[row] += keyWidths[row * KBD_COLS + col]; + } +} + +void InkHUD::KeyboardApplet::onRender(bool full) +{ + uint16_t em = fontSmall.lineHeight(); // 16 pt + uint16_t keyH = Y(1.0) / KBD_ROWS; + int16_t keyTopPadding = (keyH - fontSmall.lineHeight()) / 2; + + if (full) { // Draw full keyboard + for (uint8_t row = 0; row < KBD_ROWS; row++) { + + // Calculate the remaining space to be used as padding + int16_t keyXPadding = X(1.0) - ((rowWidths[row] * em) >> 4); + + // Draw keys + uint16_t xPos = 0; + for (uint8_t col = 0; col < KBD_COLS; col++) { + Color fgcolor = BLACK; + uint8_t index = row * KBD_COLS + col; + uint16_t keyX = ((xPos * em) >> 4) + ((col * keyXPadding) / (KBD_COLS - 1)); + uint16_t keyY = row * keyH; + uint16_t keyW = (keyWidths[index] * em) >> 4; + if (index == selectedKey) { + fgcolor = WHITE; + fillRect(keyX, keyY, keyW, keyH, BLACK); + } + drawKeyLabel(keyX, keyY + keyTopPadding, keyW, keys[index], fgcolor); + xPos += keyWidths[index]; + } + } + } else { // Only draw the difference + if (selectedKey != prevSelectedKey) { + // Draw previously selected key + uint8_t row = prevSelectedKey / KBD_COLS; + int16_t keyXPadding = X(1.0) - ((rowWidths[row] * em) >> 4); + uint16_t xPos = 0; + for (uint8_t i = prevSelectedKey - (prevSelectedKey % KBD_COLS); i < prevSelectedKey; i++) + xPos += keyWidths[i]; + uint16_t keyX = ((xPos * em) >> 4) + (((prevSelectedKey % KBD_COLS) * keyXPadding) / (KBD_COLS - 1)); + uint16_t keyY = row * keyH; + uint16_t keyW = (keyWidths[prevSelectedKey] * em) >> 4; + fillRect(keyX, keyY, keyW, keyH, WHITE); + drawKeyLabel(keyX, keyY + keyTopPadding, keyW, keys[prevSelectedKey], BLACK); + + // Draw newly selected key + row = selectedKey / KBD_COLS; + keyXPadding = X(1.0) - ((rowWidths[row] * em) >> 4); + xPos = 0; + for (uint8_t i = selectedKey - (selectedKey % KBD_COLS); i < selectedKey; i++) + xPos += keyWidths[i]; + keyX = ((xPos * em) >> 4) + (((selectedKey % KBD_COLS) * keyXPadding) / (KBD_COLS - 1)); + keyY = row * keyH; + keyW = (keyWidths[selectedKey] * em) >> 4; + fillRect(keyX, keyY, keyW, keyH, BLACK); + drawKeyLabel(keyX, keyY + keyTopPadding, keyW, keys[selectedKey], WHITE); + } + } + + prevSelectedKey = selectedKey; +} + +// Draw the key label corresponding to the char +// for most keys it draws the character itself +// for ['\b', '\n', ' ', '\x1b'] it draws special glyphs +void InkHUD::KeyboardApplet::drawKeyLabel(uint16_t left, uint16_t top, uint16_t width, char key, Color color) +{ + if (key == '\b') { + // Draw backspace glyph: 13 x 9 px + /** + * [][][][][][][][][] + * [][] [] + * [][] [] [] [] + * [][] [] [] [] + * [][] [] [] + * [][] [] [] [] + * [][] [] [] [] + * [][] [] + * [][][][][][][][][] + */ + const uint8_t bsBitmap[] = {0x0f, 0xf8, 0x18, 0x08, 0x32, 0x28, 0x61, 0x48, 0xc0, + 0x88, 0x61, 0x48, 0x32, 0x28, 0x18, 0x08, 0x0f, 0xf8}; + uint16_t leftPadding = (width - 13) >> 1; + drawBitmap(left + leftPadding, top + 1, bsBitmap, 13, 9, color); + } else if (key == '\n') { + // Draw done glyph: 12 x 9 px + /** + * [][] + * [][] + * [][] + * [][] + * [][] + * [][] [][] + * [][] [][] + * [][][] + * [] + */ + const uint8_t doneBitmap[] = {0x00, 0x30, 0x00, 0x60, 0x00, 0xc0, 0x01, 0x80, 0x03, + 0x00, 0xc6, 0x00, 0x6c, 0x00, 0x38, 0x00, 0x10, 0x00}; + uint16_t leftPadding = (width - 12) >> 1; + drawBitmap(left + leftPadding, top + 1, doneBitmap, 12, 9, color); + } else if (key == ' ') { + // Draw space glyph: 13 x 9 px + /** + * + * + * + * + * [] [] + * [] [] + * [][][][][][][][][][][][][] + * + * + */ + const uint8_t spaceBitmap[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, + 0x08, 0x80, 0x08, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00}; + uint16_t leftPadding = (width - 13) >> 1; + drawBitmap(left + leftPadding, top + 1, spaceBitmap, 13, 9, color); + } else if (key == '\x1b') { + setTextColor(color); + std::string keyText = "ESC"; + uint16_t leftPadding = (width - getTextWidth(keyText)) >> 1; + printAt(left + leftPadding, top, keyText); + } else { + setTextColor(color); + if (key >= 0x61) + key -= 32; // capitalize + std::string keyText = std::string(1, key); + uint16_t leftPadding = (width - getTextWidth(keyText)) >> 1; + printAt(left + leftPadding, top, keyText); + } +} + +void InkHUD::KeyboardApplet::onForeground() +{ + handleInput = true; // Intercept the button input for our applet + + // Select the first key + selectedKey = 0; + prevSelectedKey = 0; +} + +void InkHUD::KeyboardApplet::onBackground() +{ + handleInput = false; +} + +void InkHUD::KeyboardApplet::onButtonShortPress() +{ + char key = keys[selectedKey]; + if (key == '\n') { + inkhud->freeTextDone(); + inkhud->closeKeyboard(); + } else if (key == '\x1b') { + inkhud->freeTextCancel(); + inkhud->closeKeyboard(); + } else { + inkhud->freeText(key); + } +} + +void InkHUD::KeyboardApplet::onButtonLongPress() +{ + char key = keys[selectedKey]; + if (key == '\n') { + inkhud->freeTextDone(); + inkhud->closeKeyboard(); + } else if (key == '\x1b') { + inkhud->freeTextCancel(); + inkhud->closeKeyboard(); + } else { + if (key >= 0x61) + key -= 32; // capitalize + inkhud->freeText(key); + } +} + +void InkHUD::KeyboardApplet::onExitShort() +{ + inkhud->freeTextCancel(); + inkhud->closeKeyboard(); +} + +void InkHUD::KeyboardApplet::onExitLong() +{ + inkhud->freeTextCancel(); + inkhud->closeKeyboard(); +} + +void InkHUD::KeyboardApplet::onNavUp() +{ + if (selectedKey < KBD_COLS) // wrap + selectedKey += KBD_COLS * (KBD_ROWS - 1); + else // move 1 row back + selectedKey -= KBD_COLS; + + // Request rendering over the previously drawn render + requestUpdate(EInk::UpdateTypes::FAST, false); + // Force an update to bypass lockRequests + inkhud->forceUpdate(EInk::UpdateTypes::FAST); +} + +void InkHUD::KeyboardApplet::onNavDown() +{ + selectedKey += KBD_COLS; + selectedKey %= (KBD_COLS * KBD_ROWS); + + // Request rendering over the previously drawn render + requestUpdate(EInk::UpdateTypes::FAST, false); + // Force an update to bypass lockRequests + inkhud->forceUpdate(EInk::UpdateTypes::FAST); +} + +void InkHUD::KeyboardApplet::onNavLeft() +{ + if (selectedKey % KBD_COLS == 0) // wrap + selectedKey += KBD_COLS - 1; + else // move 1 column back + selectedKey--; + + // Request rendering over the previously drawn render + requestUpdate(EInk::UpdateTypes::FAST, false); + // Force an update to bypass lockRequests + inkhud->forceUpdate(EInk::UpdateTypes::FAST); +} + +void InkHUD::KeyboardApplet::onNavRight() +{ + if (selectedKey % KBD_COLS == KBD_COLS - 1) // wrap + selectedKey -= KBD_COLS - 1; + else // move 1 column forward + selectedKey++; + + // Request rendering over the previously drawn render + requestUpdate(EInk::UpdateTypes::FAST, false); + // Force an update to bypass lockRequests + inkhud->forceUpdate(EInk::UpdateTypes::FAST); +} + +uint16_t InkHUD::KeyboardApplet::getKeyboardHeight() +{ + const uint16_t keyH = fontSmall.lineHeight() * 1.2; + return keyH * KBD_ROWS; +} +#endif diff --git a/src/graphics/niche/InkHUD/Applets/System/Keyboard/KeyboardApplet.h b/src/graphics/niche/InkHUD/Applets/System/Keyboard/KeyboardApplet.h new file mode 100644 index 000000000..306a8d8e3 --- /dev/null +++ b/src/graphics/niche/InkHUD/Applets/System/Keyboard/KeyboardApplet.h @@ -0,0 +1,66 @@ +#ifdef MESHTASTIC_INCLUDE_INKHUD + +/* + +System Applet to render an on-screeen keyboard + +*/ + +#pragma once + +#include "configuration.h" +#include "graphics/niche/InkHUD/InkHUD.h" +#include "graphics/niche/InkHUD/SystemApplet.h" +#include +namespace NicheGraphics::InkHUD +{ + +class KeyboardApplet : public SystemApplet +{ + public: + KeyboardApplet(); + + void onRender(bool full) override; + void onForeground() override; + void onBackground() override; + void onButtonShortPress() override; + void onButtonLongPress() override; + void onExitShort() override; + void onExitLong() override; + void onNavUp() override; + void onNavDown() override; + void onNavLeft() override; + void onNavRight() override; + + static uint16_t getKeyboardHeight(); // used to set the keyboard tile height + + private: + void drawKeyLabel(uint16_t left, uint16_t top, uint16_t width, char key, Color color); + + static const uint8_t KBD_COLS = 11; + static const uint8_t KBD_ROWS = 4; + + const char keys[KBD_COLS * KBD_ROWS] = { + '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '\b', // row 0 + 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '\n', // row 1 + 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', '!', ' ', // row 2 + 'z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.', '?', '\x1b' // row 3 + }; + + // This array represents the widths of each key in points + // 16 pt = line height of the text + const uint16_t keyWidths[KBD_COLS * KBD_ROWS] = { + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 24, // row 0 + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 24, // row 1 + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 24, // row 2 + 16, 16, 16, 16, 16, 16, 16, 10, 10, 12, 40 // row 3 + }; + + uint16_t rowWidths[KBD_ROWS]; + uint8_t selectedKey = 0; // selected key index + uint8_t prevSelectedKey = 0; +}; + +} // namespace NicheGraphics::InkHUD + +#endif diff --git a/src/graphics/niche/InkHUD/Applets/System/Logo/LogoApplet.cpp b/src/graphics/niche/InkHUD/Applets/System/Logo/LogoApplet.cpp index 4b55529bb..b2c58fc60 100644 --- a/src/graphics/niche/InkHUD/Applets/System/Logo/LogoApplet.cpp +++ b/src/graphics/niche/InkHUD/Applets/System/Logo/LogoApplet.cpp @@ -30,7 +30,7 @@ InkHUD::LogoApplet::LogoApplet() : concurrency::OSThread("LogoApplet") // This is then drawn with a FULL refresh by Renderer::begin } -void InkHUD::LogoApplet::onRender() +void InkHUD::LogoApplet::onRender(bool full) { // Size of the region which the logo should "scale to fit" uint16_t logoWLimit = X(0.8); @@ -120,7 +120,7 @@ void InkHUD::LogoApplet::onBackground() // Need to force an update, as a polite request wouldn't be honored, seeing how we are now in the background // Usually, onBackground is followed by another applet's onForeground (which requests update), but not in this case - inkhud->forceUpdate(EInk::UpdateTypes::FULL); + inkhud->forceUpdate(EInk::UpdateTypes::FULL, true); } // Begin displaying the screen which is shown at shutdown @@ -138,10 +138,10 @@ void InkHUD::LogoApplet::onShutdown() // Intention is to restore display health. inverted = true; - inkhud->forceUpdate(Drivers::EInk::FULL, false); + inkhud->forceUpdate(Drivers::EInk::FULL, true, false); delay(1000); // Cooldown. Back to back updates aren't great for health. inverted = false; - inkhud->forceUpdate(Drivers::EInk::FULL, false); + inkhud->forceUpdate(Drivers::EInk::FULL, true, false); delay(1000); // Cooldown // Prepare for the powered-off screen now @@ -176,7 +176,7 @@ void InkHUD::LogoApplet::onReboot() textTitle = "Rebooting..."; fontTitle = fontSmall; - inkhud->forceUpdate(Drivers::EInk::FULL, false); + inkhud->forceUpdate(Drivers::EInk::FULL, true, false); // Perform the update right now, waiting here until complete } diff --git a/src/graphics/niche/InkHUD/Applets/System/Logo/LogoApplet.h b/src/graphics/niche/InkHUD/Applets/System/Logo/LogoApplet.h index 37f940453..d70dcc7b2 100644 --- a/src/graphics/niche/InkHUD/Applets/System/Logo/LogoApplet.h +++ b/src/graphics/niche/InkHUD/Applets/System/Logo/LogoApplet.h @@ -21,7 +21,7 @@ class LogoApplet : public SystemApplet, public concurrency::OSThread { public: LogoApplet(); - void onRender() override; + void onRender(bool full) override; void onForeground() override; void onBackground() override; void onShutdown() override; diff --git a/src/graphics/niche/InkHUD/Applets/System/Menu/MenuAction.h b/src/graphics/niche/InkHUD/Applets/System/Menu/MenuAction.h index 7cfb33e9d..7ec76292b 100644 --- a/src/graphics/niche/InkHUD/Applets/System/Menu/MenuAction.h +++ b/src/graphics/niche/InkHUD/Applets/System/Menu/MenuAction.h @@ -19,6 +19,7 @@ namespace NicheGraphics::InkHUD enum MenuAction { NO_ACTION, SEND_PING, + FREE_TEXT, STORE_CANNEDMESSAGE_SELECTION, SEND_CANNEDMESSAGE, SHUTDOWN, diff --git a/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.cpp b/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.cpp index b69e31e9a..6a141f73e 100644 --- a/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.cpp +++ b/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.cpp @@ -90,6 +90,8 @@ void InkHUD::MenuApplet::onForeground() OSThread::setIntervalFromNow(MENU_TIMEOUT_SEC * 1000UL); OSThread::enabled = true; + freeTextMode = false; + // Upgrade the refresh to FAST, for guaranteed responsiveness inkhud->forceUpdate(EInk::UpdateTypes::FAST); } @@ -116,6 +118,8 @@ void InkHUD::MenuApplet::onBackground() SystemApplet::lockRequests = false; SystemApplet::handleInput = false; + handleFreeText = false; + // Restore the user applet whose tile we borrowed if (borrowedTileOwner) borrowedTileOwner->bringToForeground(); @@ -340,12 +344,26 @@ void InkHUD::MenuApplet::execute(MenuItem item) inkhud->forceUpdate(Drivers::EInk::UpdateTypes::FULL); break; + case FREE_TEXT: + OSThread::enabled = false; + handleFreeText = true; + cm.freeTextItem.rawText.erase(); // clear the previous freetext message + freeTextMode = true; // render input field instead of normal menu + // Open the on-screen keyboard if the joystick is enabled + if (settings->joystick.enabled) + inkhud->openKeyboard(); + break; + case STORE_CANNEDMESSAGE_SELECTION: - cm.selectedMessageItem = &cm.messageItems.at(cursor - 1); // Minus one: offset for the initial "Send Ping" entry + if (!settings->joystick.enabled) + cm.selectedMessageItem = &cm.messageItems.at(cursor - 1); // Minus one: offset for the initial "Send Ping" entry + else + cm.selectedMessageItem = &cm.messageItems.at(cursor - 2); // Minus two: offset for the "Send Ping" and free text entry break; case SEND_CANNEDMESSAGE: cm.selectedRecipientItem = &cm.recipientItems.at(cursor); + // send selected message sendText(cm.selectedRecipientItem->dest, cm.selectedRecipientItem->channelIndex, cm.selectedMessageItem->rawText.c_str()); inkhud->forceUpdate(Drivers::EInk::UpdateTypes::FULL); // Next refresh should be FULL. Lots of button pressing to get here break; @@ -1373,8 +1391,14 @@ void InkHUD::MenuApplet::showPage(MenuPage page) currentPage = page; } -void InkHUD::MenuApplet::onRender() +void InkHUD::MenuApplet::onRender(bool full) { + // Free text mode draws a text input field and skips the normal rendering + if (freeTextMode) { + drawInputField(0, fontSmall.lineHeight(), X(1.0), Y(1.0) - fontSmall.lineHeight() - 1, cm.freeTextItem.rawText); + return; + } + if (items.size() == 0) LOG_ERROR("Empty Menu"); @@ -1493,44 +1517,48 @@ void InkHUD::MenuApplet::onRender() void InkHUD::MenuApplet::onButtonShortPress() { - // Push the auto-close timer back - OSThread::setIntervalFromNow(MENU_TIMEOUT_SEC * 1000UL); + if (!freeTextMode) { + // Push the auto-close timer back + OSThread::setIntervalFromNow(MENU_TIMEOUT_SEC * 1000UL); - if (!settings->joystick.enabled) { - if (!cursorShown) { - cursorShown = true; - cursor = 0; - } else { - do { - cursor = (cursor + 1) % items.size(); - } while (items.at(cursor).isHeader); - } - requestUpdate(Drivers::EInk::UpdateTypes::FAST); - } else { - if (cursorShown) - execute(items.at(cursor)); - else - showPage(MenuPage::EXIT); - if (!wantsToRender()) + if (!settings->joystick.enabled) { + if (!cursorShown) { + cursorShown = true; + cursor = 0; + } else { + do { + cursor = (cursor + 1) % items.size(); + } while (items.at(cursor).isHeader); + } requestUpdate(Drivers::EInk::UpdateTypes::FAST); + } else { + if (cursorShown) + execute(items.at(cursor)); + else + showPage(MenuPage::EXIT); + if (!wantsToRender()) + requestUpdate(Drivers::EInk::UpdateTypes::FAST); + } } } void InkHUD::MenuApplet::onButtonLongPress() { - // Push the auto-close timer back - OSThread::setIntervalFromNow(MENU_TIMEOUT_SEC * 1000UL); + if (!freeTextMode) { + // Push the auto-close timer back + OSThread::setIntervalFromNow(MENU_TIMEOUT_SEC * 1000UL); - if (cursorShown) - execute(items.at(cursor)); - else - showPage(MenuPage::EXIT); // Special case: Peek at root-menu; longpress again to close + if (cursorShown) + execute(items.at(cursor)); + else + showPage(MenuPage::EXIT); // Special case: Peek at root-menu; longpress again to close - // If we didn't already request a specialized update, when handling a menu action, - // then perform the usual fast update. - // FAST keeps things responsive: important because we're dealing with user input - if (!wantsToRender()) - requestUpdate(Drivers::EInk::UpdateTypes::FAST); + // If we didn't already request a specialized update, when handling a menu action, + // then perform the usual fast update. + // FAST keeps things responsive: important because we're dealing with user input + if (!wantsToRender()) + requestUpdate(Drivers::EInk::UpdateTypes::FAST); + } } void InkHUD::MenuApplet::onExitShort() @@ -1543,56 +1571,107 @@ void InkHUD::MenuApplet::onExitShort() void InkHUD::MenuApplet::onNavUp() { - OSThread::setIntervalFromNow(MENU_TIMEOUT_SEC * 1000UL); + if (!freeTextMode) { + OSThread::setIntervalFromNow(MENU_TIMEOUT_SEC * 1000UL); - if (!cursorShown) { - cursorShown = true; - cursor = 0; - } else { - do { - if (cursor == 0) - cursor = items.size() - 1; - else - cursor--; - } while (items.at(cursor).isHeader); + if (!cursorShown) { + cursorShown = true; + cursor = 0; + } else { + do { + if (cursor == 0) + cursor = items.size() - 1; + else + cursor--; + } while (items.at(cursor).isHeader); + } + + requestUpdate(Drivers::EInk::UpdateTypes::FAST); } - - requestUpdate(Drivers::EInk::UpdateTypes::FAST); } void InkHUD::MenuApplet::onNavDown() { - OSThread::setIntervalFromNow(MENU_TIMEOUT_SEC * 1000UL); + if (!freeTextMode) { + OSThread::setIntervalFromNow(MENU_TIMEOUT_SEC * 1000UL); - if (!cursorShown) { - cursorShown = true; - cursor = 0; - } else { - do { - cursor = (cursor + 1) % items.size(); - } while (items.at(cursor).isHeader); + if (!cursorShown) { + cursorShown = true; + cursor = 0; + } else { + do { + cursor = (cursor + 1) % items.size(); + } while (items.at(cursor).isHeader); + } + + requestUpdate(Drivers::EInk::UpdateTypes::FAST); } - - requestUpdate(Drivers::EInk::UpdateTypes::FAST); } void InkHUD::MenuApplet::onNavLeft() { - OSThread::setIntervalFromNow(MENU_TIMEOUT_SEC * 1000UL); + if (!freeTextMode) { + OSThread::setIntervalFromNow(MENU_TIMEOUT_SEC * 1000UL); - // Go to the previous menu page - showPage(previousPage); - requestUpdate(Drivers::EInk::UpdateTypes::FAST); + // Go to the previous menu page + showPage(previousPage); + requestUpdate(Drivers::EInk::UpdateTypes::FAST); + } } void InkHUD::MenuApplet::onNavRight() { - OSThread::setIntervalFromNow(MENU_TIMEOUT_SEC * 1000UL); + if (!freeTextMode) { + OSThread::setIntervalFromNow(MENU_TIMEOUT_SEC * 1000UL); + if (cursorShown) + execute(items.at(cursor)); + if (!wantsToRender()) + requestUpdate(Drivers::EInk::UpdateTypes::FAST); + } +} - if (cursorShown) - execute(items.at(cursor)); - if (!wantsToRender()) - requestUpdate(Drivers::EInk::UpdateTypes::FAST); +void InkHUD::MenuApplet::onFreeText(char c) +{ + if (cm.freeTextItem.rawText.length() >= menuTextLimit && c != '\b') + return; + if (c == '\b') { + if (!cm.freeTextItem.rawText.empty()) + cm.freeTextItem.rawText.pop_back(); + } else { + cm.freeTextItem.rawText += c; + } + requestUpdate(Drivers::EInk::UpdateTypes::FAST); +} + +void InkHUD::MenuApplet::onFreeTextDone() +{ + // Restart the auto-close timeout + OSThread::setIntervalFromNow(MENU_TIMEOUT_SEC * 1000UL); + OSThread::enabled = true; + + handleFreeText = false; + freeTextMode = false; + + if (!cm.freeTextItem.rawText.empty()) { + cm.selectedMessageItem = &cm.freeTextItem; + showPage(MenuPage::CANNEDMESSAGE_RECIPIENT); + } + requestUpdate(Drivers::EInk::UpdateTypes::FAST); +} + +void InkHUD::MenuApplet::onFreeTextCancel() +{ + // Restart the auto-close timeout + OSThread::setIntervalFromNow(MENU_TIMEOUT_SEC * 1000UL); + OSThread::enabled = true; + + handleFreeText = false; + freeTextMode = false; + + // Clear the free text message + cm.freeTextItem.rawText.erase(); + + requestUpdate(Drivers::EInk::UpdateTypes::FAST); } // Dynamically create MenuItem entries for activating / deactivating Applets, for the "Applet Selection" submenu @@ -1647,6 +1726,10 @@ void InkHUD::MenuApplet::populateSendPage() // Position / NodeInfo packet items.push_back(MenuItem("Ping", MenuAction::SEND_PING, MenuPage::EXIT)); + // If joystick is available, include the Free Text option + if (settings->joystick.enabled) + items.push_back(MenuItem("Free Text", MenuAction::FREE_TEXT, MenuPage::SEND)); + // One menu item for each canned message uint8_t count = cm.store->size(); for (uint8_t i = 0; i < count; i++) { @@ -1746,6 +1829,48 @@ void InkHUD::MenuApplet::populateRecipientPage() items.push_back(MenuItem("Exit", MenuPage::EXIT)); } +void InkHUD::MenuApplet::drawInputField(uint16_t left, uint16_t top, uint16_t width, uint16_t height, std::string text) +{ + setFont(fontSmall); + uint16_t wrapMaxH = 0; + + // Draw the text, input box, and cursor + // Adjusting the box for screen height + while (wrapMaxH < height - fontSmall.lineHeight()) { + wrapMaxH += fontSmall.lineHeight(); + } + + // If the text is so long that it goes outside of the input box, the text is actually rendered off screen. + uint32_t textHeight = getWrappedTextHeight(0, width - 5, text); + if (!text.empty()) { + uint16_t textPadding = X(1.0) > Y(1.0) ? wrapMaxH - textHeight : wrapMaxH - textHeight + 1; + if (textHeight > wrapMaxH) + printWrapped(2, textPadding, width - 5, text); + else + printWrapped(2, top + 2, width - 5, text); + } + + uint16_t textCursorX = text.empty() ? 1 : getCursorX(); + uint16_t textCursorY = text.empty() ? fontSmall.lineHeight() + 2 : getCursorY() - fontSmall.lineHeight() + 3; + + if (textCursorX + 1 > width - 5) { + textCursorX = getCursorX() - width + 5; + textCursorY += fontSmall.lineHeight(); + } + + fillRect(textCursorX + 1, textCursorY, 1, fontSmall.lineHeight(), BLACK); + + // A white rectangle clears the top part of the screen for any text that's printed beyond the input box + fillRect(0, 0, X(1.0), top, WHITE); + + // Draw character limit + std::string ftlen = std::to_string(text.length()) + "/" + to_string(menuTextLimit); + uint16_t textLen = getTextWidth(ftlen); + printAt(X(1.0) - textLen - 2, 0, ftlen); + + // Draw the border + drawRect(0, top, width, wrapMaxH + 5, BLACK); +} // Renders the panel shown at the top of the root menu. // Displays the clock, and several other pieces of instantaneous system info, // which we'd prefer not to have displayed in a normal applet, as they update too frequently. @@ -1887,4 +2012,4 @@ void InkHUD::MenuApplet::freeCannedMessageResources() cm.messageItems.clear(); cm.recipientItems.clear(); } -#endif // MESHTASTIC_INCLUDE_INKHUD \ No newline at end of file +#endif // MESHTASTIC_INCLUDE_INKHUD diff --git a/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.h b/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.h index 82ccc8f45..7b092153b 100644 --- a/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.h +++ b/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.h @@ -32,7 +32,10 @@ class MenuApplet : public SystemApplet, public concurrency::OSThread void onNavDown() override; void onNavLeft() override; void onNavRight() override; - void onRender() override; + void onFreeText(char c) override; + void onFreeTextDone() override; + void onFreeTextCancel() override; + void onRender(bool full) override; void show(Tile *t); // Open the menu, onto a user tile void setStartPage(MenuPage page); @@ -51,6 +54,8 @@ class MenuApplet : public SystemApplet, public concurrency::OSThread void populateAutoshowPage(); // Dynamically create MenuItems for selecting which applets can autoshow void populateRecentsPage(); // Create menu items: a choice of values for settings.recentlyActiveSeconds + void drawInputField(uint16_t left, uint16_t top, uint16_t width, uint16_t height, + std::string text); // Draw input field for free text uint16_t getSystemInfoPanelHeight(); void drawSystemInfoPanel(int16_t left, int16_t top, uint16_t width, uint16_t *height = nullptr); // Info panel at top of root menu @@ -62,8 +67,9 @@ class MenuApplet : public SystemApplet, public concurrency::OSThread MenuPage previousPage = MenuPage::EXIT; uint8_t cursor = 0; // Which menu item is currently highlighted bool cursorShown = false; // Is *any* item highlighted? (Root menu: no initial selection) - + bool freeTextMode = false; uint16_t systemInfoPanelHeight = 0; // Need to know before we render + uint16_t menuTextLimit = 200; std::vector items; // MenuItems for the current page. Filled by ShowPage std::vector nodeConfigLabels; // Persistent labels for Node Config pages @@ -104,6 +110,8 @@ class MenuApplet : public SystemApplet, public concurrency::OSThread // Cleared onBackground (when MenuApplet closes) std::vector messageItems; std::vector recipientItems; + + MessageItem freeTextItem; } cm; Applet *borrowedTileOwner = nullptr; // Which applet we have temporarily replaced while displaying menu diff --git a/src/graphics/niche/InkHUD/Applets/System/Notification/NotificationApplet.cpp b/src/graphics/niche/InkHUD/Applets/System/Notification/NotificationApplet.cpp index 2ea9c7fe0..19cef4fbd 100644 --- a/src/graphics/niche/InkHUD/Applets/System/Notification/NotificationApplet.cpp +++ b/src/graphics/niche/InkHUD/Applets/System/Notification/NotificationApplet.cpp @@ -65,7 +65,7 @@ int InkHUD::NotificationApplet::onReceiveTextMessage(const meshtastic_MeshPacket return 0; } -void InkHUD::NotificationApplet::onRender() +void InkHUD::NotificationApplet::onRender(bool full) { // Clear the region beneath the tile // Most applets are drawing onto an empty frame buffer and don't need to do this @@ -139,54 +139,47 @@ void InkHUD::NotificationApplet::onForeground() void InkHUD::NotificationApplet::onBackground() { handleInput = false; + inkhud->forceUpdate(EInk::UpdateTypes::FULL, true); } void InkHUD::NotificationApplet::onButtonShortPress() { dismiss(); - inkhud->forceUpdate(EInk::UpdateTypes::FULL); } void InkHUD::NotificationApplet::onButtonLongPress() { dismiss(); - inkhud->forceUpdate(EInk::UpdateTypes::FULL); } void InkHUD::NotificationApplet::onExitShort() { dismiss(); - inkhud->forceUpdate(EInk::UpdateTypes::FULL); } void InkHUD::NotificationApplet::onExitLong() { dismiss(); - inkhud->forceUpdate(EInk::UpdateTypes::FULL); } void InkHUD::NotificationApplet::onNavUp() { dismiss(); - inkhud->forceUpdate(EInk::UpdateTypes::FULL); } void InkHUD::NotificationApplet::onNavDown() { dismiss(); - inkhud->forceUpdate(EInk::UpdateTypes::FULL); } void InkHUD::NotificationApplet::onNavLeft() { dismiss(); - inkhud->forceUpdate(EInk::UpdateTypes::FULL); } void InkHUD::NotificationApplet::onNavRight() { dismiss(); - inkhud->forceUpdate(EInk::UpdateTypes::FULL); } // Ask the WindowManager to check whether any displayed applets are already displaying the info from this notification diff --git a/src/graphics/niche/InkHUD/Applets/System/Notification/NotificationApplet.h b/src/graphics/niche/InkHUD/Applets/System/Notification/NotificationApplet.h index 16ea13407..d398a36f3 100644 --- a/src/graphics/niche/InkHUD/Applets/System/Notification/NotificationApplet.h +++ b/src/graphics/niche/InkHUD/Applets/System/Notification/NotificationApplet.h @@ -26,7 +26,7 @@ class NotificationApplet : public SystemApplet public: NotificationApplet(); - void onRender() override; + void onRender(bool full) override; void onForeground() override; void onBackground() override; void onButtonShortPress() override; diff --git a/src/graphics/niche/InkHUD/Applets/System/Pairing/PairingApplet.cpp b/src/graphics/niche/InkHUD/Applets/System/Pairing/PairingApplet.cpp index 09931f109..a09ff55d5 100644 --- a/src/graphics/niche/InkHUD/Applets/System/Pairing/PairingApplet.cpp +++ b/src/graphics/niche/InkHUD/Applets/System/Pairing/PairingApplet.cpp @@ -9,7 +9,7 @@ InkHUD::PairingApplet::PairingApplet() bluetoothStatusObserver.observe(&bluetoothStatus->onNewStatus); } -void InkHUD::PairingApplet::onRender() +void InkHUD::PairingApplet::onRender(bool full) { // Header setFont(fontMedium); @@ -45,7 +45,7 @@ void InkHUD::PairingApplet::onBackground() // Need to force an update, as a polite request wouldn't be honored, seeing how we are now in the background // Usually, onBackground is followed by another applet's onForeground (which requests update), but not in this case - inkhud->forceUpdate(EInk::UpdateTypes::FULL); + inkhud->forceUpdate(EInk::UpdateTypes::FULL, true); } int InkHUD::PairingApplet::onBluetoothStatusUpdate(const meshtastic::Status *status) diff --git a/src/graphics/niche/InkHUD/Applets/System/Pairing/PairingApplet.h b/src/graphics/niche/InkHUD/Applets/System/Pairing/PairingApplet.h index b89783a25..4c2e95321 100644 --- a/src/graphics/niche/InkHUD/Applets/System/Pairing/PairingApplet.h +++ b/src/graphics/niche/InkHUD/Applets/System/Pairing/PairingApplet.h @@ -22,7 +22,7 @@ class PairingApplet : public SystemApplet public: PairingApplet(); - void onRender() override; + void onRender(bool full) override; void onForeground() override; void onBackground() override; diff --git a/src/graphics/niche/InkHUD/Applets/System/Placeholder/PlaceholderApplet.cpp b/src/graphics/niche/InkHUD/Applets/System/Placeholder/PlaceholderApplet.cpp index 99cdeb0ac..228c8b2ca 100644 --- a/src/graphics/niche/InkHUD/Applets/System/Placeholder/PlaceholderApplet.cpp +++ b/src/graphics/niche/InkHUD/Applets/System/Placeholder/PlaceholderApplet.cpp @@ -4,7 +4,7 @@ using namespace NicheGraphics; -void InkHUD::PlaceholderApplet::onRender() +void InkHUD::PlaceholderApplet::onRender(bool full) { // This placeholder applet fills its area with sparse diagonal lines hatchRegion(0, 0, width(), height(), 8, BLACK); diff --git a/src/graphics/niche/InkHUD/Applets/System/Placeholder/PlaceholderApplet.h b/src/graphics/niche/InkHUD/Applets/System/Placeholder/PlaceholderApplet.h index 78ba5cd89..fa40913e0 100644 --- a/src/graphics/niche/InkHUD/Applets/System/Placeholder/PlaceholderApplet.h +++ b/src/graphics/niche/InkHUD/Applets/System/Placeholder/PlaceholderApplet.h @@ -17,7 +17,7 @@ namespace NicheGraphics::InkHUD class PlaceholderApplet : public SystemApplet { public: - void onRender() override; + void onRender(bool full) override; // Note: onForeground, onBackground, and wantsToRender are not meaningful for this applet. // The window manager decides when and where it should be rendered diff --git a/src/graphics/niche/InkHUD/Applets/System/Tips/TipsApplet.cpp b/src/graphics/niche/InkHUD/Applets/System/Tips/TipsApplet.cpp index 7869319fe..6cac2644b 100644 --- a/src/graphics/niche/InkHUD/Applets/System/Tips/TipsApplet.cpp +++ b/src/graphics/niche/InkHUD/Applets/System/Tips/TipsApplet.cpp @@ -45,7 +45,7 @@ InkHUD::TipsApplet::TipsApplet() bringToForeground(); } -void InkHUD::TipsApplet::onRender() +void InkHUD::TipsApplet::onRender(bool full) { switch (tipQueue.front()) { case Tip::WELCOME: @@ -261,7 +261,7 @@ void InkHUD::TipsApplet::onBackground() // Need to force an update, as a polite request wouldn't be honored, seeing how we are now in the background // Usually, onBackground is followed by another applet's onForeground (which requests update), but not in this case - inkhud->forceUpdate(EInk::UpdateTypes::FULL); + inkhud->forceUpdate(EInk::UpdateTypes::FULL, true); } // While our SystemApplet::handleInput flag is true @@ -292,9 +292,8 @@ void InkHUD::TipsApplet::onButtonShortPress() inkhud->persistence->saveSettings(); } - // Close applet and clean the screen + // Close applet sendToBackground(); - inkhud->forceUpdate(EInk::UpdateTypes::FULL); } else { requestUpdate(); } @@ -306,4 +305,4 @@ void InkHUD::TipsApplet::onExitShort() onButtonShortPress(); } -#endif \ No newline at end of file +#endif diff --git a/src/graphics/niche/InkHUD/Applets/System/Tips/TipsApplet.h b/src/graphics/niche/InkHUD/Applets/System/Tips/TipsApplet.h index ff7eea046..2e81d678b 100644 --- a/src/graphics/niche/InkHUD/Applets/System/Tips/TipsApplet.h +++ b/src/graphics/niche/InkHUD/Applets/System/Tips/TipsApplet.h @@ -33,7 +33,7 @@ class TipsApplet : public SystemApplet public: TipsApplet(); - void onRender() override; + void onRender(bool full) override; void onForeground() override; void onBackground() override; void onButtonShortPress() override; diff --git a/src/graphics/niche/InkHUD/Applets/User/AllMessage/AllMessageApplet.cpp b/src/graphics/niche/InkHUD/Applets/User/AllMessage/AllMessageApplet.cpp index 7c6232f3b..96c519599 100644 --- a/src/graphics/niche/InkHUD/Applets/User/AllMessage/AllMessageApplet.cpp +++ b/src/graphics/niche/InkHUD/Applets/User/AllMessage/AllMessageApplet.cpp @@ -34,7 +34,7 @@ int InkHUD::AllMessageApplet::onReceiveTextMessage(const meshtastic_MeshPacket * return 0; } -void InkHUD::AllMessageApplet::onRender() +void InkHUD::AllMessageApplet::onRender(bool full) { // Find newest message, regardless of whether DM or broadcast MessageStore::Message *message; diff --git a/src/graphics/niche/InkHUD/Applets/User/AllMessage/AllMessageApplet.h b/src/graphics/niche/InkHUD/Applets/User/AllMessage/AllMessageApplet.h index c74e16196..4aa97e4f1 100644 --- a/src/graphics/niche/InkHUD/Applets/User/AllMessage/AllMessageApplet.h +++ b/src/graphics/niche/InkHUD/Applets/User/AllMessage/AllMessageApplet.h @@ -30,7 +30,7 @@ class Applet; class AllMessageApplet : public Applet { public: - void onRender() override; + void onRender(bool full) override; void onActivate() override; void onDeactivate() override; diff --git a/src/graphics/niche/InkHUD/Applets/User/DM/DMApplet.cpp b/src/graphics/niche/InkHUD/Applets/User/DM/DMApplet.cpp index a3b9615a5..189a56cab 100644 --- a/src/graphics/niche/InkHUD/Applets/User/DM/DMApplet.cpp +++ b/src/graphics/niche/InkHUD/Applets/User/DM/DMApplet.cpp @@ -37,7 +37,7 @@ int InkHUD::DMApplet::onReceiveTextMessage(const meshtastic_MeshPacket *p) return 0; } -void InkHUD::DMApplet::onRender() +void InkHUD::DMApplet::onRender(bool full) { // Abort if no text message if (!latestMessage->dm.sender) { diff --git a/src/graphics/niche/InkHUD/Applets/User/DM/DMApplet.h b/src/graphics/niche/InkHUD/Applets/User/DM/DMApplet.h index b3dc36e66..4eb0ec704 100644 --- a/src/graphics/niche/InkHUD/Applets/User/DM/DMApplet.h +++ b/src/graphics/niche/InkHUD/Applets/User/DM/DMApplet.h @@ -30,7 +30,7 @@ class Applet; class DMApplet : public Applet { public: - void onRender() override; + void onRender(bool full) override; void onActivate() override; void onDeactivate() override; diff --git a/src/graphics/niche/InkHUD/Applets/User/Positions/PositionsApplet.cpp b/src/graphics/niche/InkHUD/Applets/User/Positions/PositionsApplet.cpp index ad0f9fc47..ae7679962 100644 --- a/src/graphics/niche/InkHUD/Applets/User/Positions/PositionsApplet.cpp +++ b/src/graphics/niche/InkHUD/Applets/User/Positions/PositionsApplet.cpp @@ -5,10 +5,10 @@ using namespace NicheGraphics; -void InkHUD::PositionsApplet::onRender() +void InkHUD::PositionsApplet::onRender(bool full) { // Draw the usual map applet first - MapApplet::onRender(); + MapApplet::onRender(full); // Draw our latest "node of interest" as a special marker // ------------------------------------------------------- diff --git a/src/graphics/niche/InkHUD/Applets/User/Positions/PositionsApplet.h b/src/graphics/niche/InkHUD/Applets/User/Positions/PositionsApplet.h index 28a53cb0f..d0d3e5f07 100644 --- a/src/graphics/niche/InkHUD/Applets/User/Positions/PositionsApplet.h +++ b/src/graphics/niche/InkHUD/Applets/User/Positions/PositionsApplet.h @@ -24,7 +24,7 @@ class PositionsApplet : public MapApplet, public SinglePortModule { public: PositionsApplet() : SinglePortModule("PositionsApplet", meshtastic_PortNum_POSITION_APP) {} - void onRender() override; + void onRender(bool full) override; protected: ProcessMessage handleReceived(const meshtastic_MeshPacket &mp) override; diff --git a/src/graphics/niche/InkHUD/Applets/User/ThreadedMessage/ThreadedMessageApplet.cpp b/src/graphics/niche/InkHUD/Applets/User/ThreadedMessage/ThreadedMessageApplet.cpp index fdb5a168d..f16721357 100644 --- a/src/graphics/niche/InkHUD/Applets/User/ThreadedMessage/ThreadedMessageApplet.cpp +++ b/src/graphics/niche/InkHUD/Applets/User/ThreadedMessage/ThreadedMessageApplet.cpp @@ -22,7 +22,7 @@ InkHUD::ThreadedMessageApplet::ThreadedMessageApplet(uint8_t channelIndex) store = new MessageStore("ch" + to_string(channelIndex)); } -void InkHUD::ThreadedMessageApplet::onRender() +void InkHUD::ThreadedMessageApplet::onRender(bool full) { // ============= // Draw a header diff --git a/src/graphics/niche/InkHUD/Applets/User/ThreadedMessage/ThreadedMessageApplet.h b/src/graphics/niche/InkHUD/Applets/User/ThreadedMessage/ThreadedMessageApplet.h index c986539b3..045e2a6fc 100644 --- a/src/graphics/niche/InkHUD/Applets/User/ThreadedMessage/ThreadedMessageApplet.h +++ b/src/graphics/niche/InkHUD/Applets/User/ThreadedMessage/ThreadedMessageApplet.h @@ -36,7 +36,7 @@ class ThreadedMessageApplet : public Applet, public SinglePortModule explicit ThreadedMessageApplet(uint8_t channelIndex); ThreadedMessageApplet() = delete; - void onRender() override; + void onRender(bool full) override; void onActivate() override; void onDeactivate() override; diff --git a/src/graphics/niche/InkHUD/Events.cpp b/src/graphics/niche/InkHUD/Events.cpp index fa45a49ed..e6c16d350 100644 --- a/src/graphics/niche/InkHUD/Events.cpp +++ b/src/graphics/niche/InkHUD/Events.cpp @@ -238,6 +238,39 @@ void InkHUD::Events::onNavRight() } } +void InkHUD::Events::onFreeText(char c) +{ + // Trigger the first system applet that wants to handle the new character + for (SystemApplet *sa : inkhud->systemApplets) { + if (sa->handleFreeText) { + sa->onFreeText(c); + break; + } + } +} + +void InkHUD::Events::onFreeTextDone() +{ + // Trigger the first system applet that wants to handle it + for (SystemApplet *sa : inkhud->systemApplets) { + if (sa->handleFreeText) { + sa->onFreeTextDone(); + break; + } + } +} + +void InkHUD::Events::onFreeTextCancel() +{ + // Trigger the first system applet that wants to handle it + for (SystemApplet *sa : inkhud->systemApplets) { + if (sa->handleFreeText) { + sa->onFreeTextCancel(); + break; + } + } +} + // Callback for deepSleepObserver // Returns 0 to signal that we agree to sleep now int InkHUD::Events::beforeDeepSleep(void *unused) @@ -266,7 +299,7 @@ int InkHUD::Events::beforeDeepSleep(void *unused) // then prepared a final powered-off screen for us, which shows device shortname. // We're updating to show that one now. - inkhud->forceUpdate(Drivers::EInk::UpdateTypes::FULL, false); + inkhud->forceUpdate(Drivers::EInk::UpdateTypes::FULL, true, false); delay(1000); // Cooldown, before potentially yanking display power // InkHUD shutdown complete diff --git a/src/graphics/niche/InkHUD/Events.h b/src/graphics/niche/InkHUD/Events.h index 1916cf78e..873f53fd5 100644 --- a/src/graphics/niche/InkHUD/Events.h +++ b/src/graphics/niche/InkHUD/Events.h @@ -37,6 +37,11 @@ class Events void onNavLeft(); // Navigate left void onNavRight(); // Navigate right + // Free text typing events + void onFreeText(char c); // New freetext character input + void onFreeTextDone(); + void onFreeTextCancel(); + int beforeDeepSleep(void *unused); // Prepare for shutdown int beforeReboot(void *unused); // Prepare for reboot int onReceiveTextMessage(const meshtastic_MeshPacket *packet); // Store most recent text message diff --git a/src/graphics/niche/InkHUD/InkHUD.cpp b/src/graphics/niche/InkHUD/InkHUD.cpp index 13b15b7e8..5fab67639 100644 --- a/src/graphics/niche/InkHUD/InkHUD.cpp +++ b/src/graphics/niche/InkHUD/InkHUD.cpp @@ -175,6 +175,25 @@ void InkHUD::InkHUD::navRight() } } +// Call this for keyboard input +// The Keyboard Applet also calls this +void InkHUD::InkHUD::freeText(char c) +{ + events->onFreeText(c); +} + +// Call this to complete a freetext input +void InkHUD::InkHUD::freeTextDone() +{ + events->onFreeTextDone(); +} + +// Call this to cancel a freetext input +void InkHUD::InkHUD::freeTextCancel() +{ + events->onFreeTextCancel(); +} + // Cycle the next user applet to the foreground // Only activated applets are cycled // If user has a multi-applet layout, the applets will cycle on the "focused tile" @@ -204,6 +223,18 @@ void InkHUD::InkHUD::openAlignStick() windowManager->openAlignStick(); } +// Open the on-screen keyboard +void InkHUD::InkHUD::openKeyboard() +{ + windowManager->openKeyboard(); +} + +// Close the on-screen keyboard +void InkHUD::InkHUD::closeKeyboard() +{ + windowManager->closeKeyboard(); +} + // In layouts where multiple applets are shown at once, change which tile is focused // The focused tile in the one which cycles applets on button short press, and displays menu on long press void InkHUD::InkHUD::nextTile() @@ -252,10 +283,11 @@ void InkHUD::InkHUD::requestUpdate() // Ignores all diplomacy: // - the display *will* update // - the specified update type *will* be used +// If the all parameter is true, the whole screen buffer is cleared and re-rendered // If the async parameter is false, code flow is blocked while the update takes place -void InkHUD::InkHUD::forceUpdate(EInk::UpdateTypes type, bool async) +void InkHUD::InkHUD::forceUpdate(EInk::UpdateTypes type, bool all, bool async) { - renderer->forceUpdate(type, async); + renderer->forceUpdate(type, all, async); } // Wait for any in-progress display update to complete before continuing diff --git a/src/graphics/niche/InkHUD/InkHUD.h b/src/graphics/niche/InkHUD/InkHUD.h index 5280d9ac7..ae029137e 100644 --- a/src/graphics/niche/InkHUD/InkHUD.h +++ b/src/graphics/niche/InkHUD/InkHUD.h @@ -63,6 +63,11 @@ class InkHUD void navLeft(); void navRight(); + // Freetext handlers + void freeText(char c); + void freeTextDone(); + void freeTextCancel(); + // Trigger UI changes // - called by various InkHUD components // - suitable(?) for use by aux button, connected in variant nicheGraphics.h @@ -71,6 +76,8 @@ class InkHUD void prevApplet(); void openMenu(); void openAlignStick(); + void openKeyboard(); + void closeKeyboard(); void nextTile(); void prevTile(); void rotate(); @@ -84,7 +91,8 @@ class InkHUD // - called by various InkHUD components void requestUpdate(); - void forceUpdate(Drivers::EInk::UpdateTypes type = Drivers::EInk::UpdateTypes::UNSPECIFIED, bool async = true); + void forceUpdate(Drivers::EInk::UpdateTypes type = Drivers::EInk::UpdateTypes::UNSPECIFIED, bool all = false, + bool async = true); void awaitUpdate(); // (Re)configuring WindowManager diff --git a/src/graphics/niche/InkHUD/Renderer.cpp b/src/graphics/niche/InkHUD/Renderer.cpp index 072e9dbd6..89a83c932 100644 --- a/src/graphics/niche/InkHUD/Renderer.cpp +++ b/src/graphics/niche/InkHUD/Renderer.cpp @@ -56,15 +56,16 @@ void InkHUD::Renderer::setDisplayResilience(uint8_t fastPerFull, float stressMul void InkHUD::Renderer::begin() { - forceUpdate(Drivers::EInk::UpdateTypes::FULL, false); + forceUpdate(Drivers::EInk::UpdateTypes::FULL, true, false); } // Set a flag, which will be picked up by runOnce, ASAP. // Quite likely, multiple applets will all want to respond to one event (Observable, etc) // Each affected applet can independently call requestUpdate(), and all share the one opportunity to render, at next runOnce -void InkHUD::Renderer::requestUpdate() +void InkHUD::Renderer::requestUpdate(bool all) { requested = true; + renderAll |= all; // We will run the thread as soon as we loop(), // after all Applets have had a chance to observe whatever event set this off @@ -79,10 +80,11 @@ void InkHUD::Renderer::requestUpdate() // Sometimes, however, we will want to trigger a display update manually, in the absence of any sort of applet event // Display health, for example. // In these situations, we use forceUpdate -void InkHUD::Renderer::forceUpdate(Drivers::EInk::UpdateTypes type, bool async) +void InkHUD::Renderer::forceUpdate(Drivers::EInk::UpdateTypes type, bool all, bool async) { requested = true; forced = true; + renderAll |= all; displayHealth.forceUpdateType(type); // Normally, we need to start the timer, in case the display is busy and we briefly defer the update @@ -219,7 +221,8 @@ void InkHUD::Renderer::render(bool async) Drivers::EInk::UpdateTypes updateType = decideUpdateType(); // Render the new image - clearBuffer(); + if (renderAll) + clearBuffer(); renderUserApplets(); renderPlaceholders(); renderSystemApplets(); @@ -247,6 +250,7 @@ void InkHUD::Renderer::render(bool async) // Tidy up, ready for a new request requested = false; forced = false; + renderAll = false; } // Manually fill the image buffer with WHITE @@ -259,6 +263,76 @@ void InkHUD::Renderer::clearBuffer() memset(imageBuffer, 0xFF, imageBufferHeight * imageBufferWidth); } +// Manually clear the pixels below a tile +void InkHUD::Renderer::clearTile(Tile *t) +{ + // Rotate the tile dimensions + int16_t left = 0; + int16_t top = 0; + uint16_t width = 0; + uint16_t height = 0; + switch (settings->rotation) { + case 0: + left = t->getLeft(); + top = t->getTop(); + width = t->getWidth(); + height = t->getHeight(); + break; + case 1: + left = driver->width - (t->getTop() + t->getHeight()); + top = t->getLeft(); + width = t->getHeight(); + height = t->getWidth(); + break; + case 2: + left = driver->width - (t->getLeft() + t->getWidth()); + top = driver->height - (t->getTop() + t->getHeight()); + width = t->getWidth(); + height = t->getHeight(); + break; + case 3: + left = t->getTop(); + top = driver->height - (t->getLeft() + t->getWidth()); + width = t->getHeight(); + height = t->getWidth(); + break; + } + + // Calculate the bounds to clear + uint16_t xStart = (left < 0) ? 0 : left; + uint16_t yStart = (top < 0) ? 0 : top; + if (xStart >= driver->width || yStart >= driver->height || left + width < 0 || top + height < 0) + return; // the box is completely off the screen + uint16_t xEnd = left + width; + uint16_t yEnd = top + height; + if (xEnd > driver->width) + xEnd = driver->width; + if (yEnd > driver->height) + yEnd = driver->height; + + // Clear the pixels + if (xStart == 0 && xEnd == driver->width) { // full width box is easier to clear + memset(imageBuffer + (yStart * imageBufferWidth), 0xFF, (yEnd - yStart) * imageBufferWidth); + } else { + const uint16_t byteStart = (xStart / 8) + 1; + const uint16_t byteEnd = xEnd / 8; + const uint8_t leadingByte = 0xFF >> (xStart - ((byteStart - 1) * 8)); + const uint8_t trailingByte = (0xFF00 >> (xEnd - (byteEnd * 8))) & 0xFF; + for (uint16_t i = yStart * imageBufferWidth; i < yEnd * imageBufferWidth; i += imageBufferWidth) { + // Set the leading byte + imageBuffer[i + byteStart - 1] |= leadingByte; + + // Set the continuous bytes + if (byteStart < byteEnd) + memset(imageBuffer + i + byteStart, 0xFF, byteEnd - byteStart); + + // Set the trailing byte + if (byteEnd != imageBufferWidth) + imageBuffer[i + byteEnd] |= trailingByte; + } + } +} + void InkHUD::Renderer::checkLocks() { lockRendering = nullptr; @@ -323,12 +397,12 @@ Drivers::EInk::UpdateTypes InkHUD::Renderer::decideUpdateType() if (!forced) { // User applets for (Applet *ua : inkhud->userApplets) { - if (ua && ua->isForeground()) + if (ua && ua->isForeground() && (ua->wantsToRender() || renderAll)) displayHealth.requestUpdateType(ua->wantsUpdateType()); } // System Applets for (SystemApplet *sa : inkhud->systemApplets) { - if (sa && sa->isForeground()) + if (sa && sa->isForeground() && (sa->wantsToRender() || sa->alwaysRender || renderAll)) displayHealth.requestUpdateType(sa->wantsUpdateType()); } } @@ -346,9 +420,16 @@ void InkHUD::Renderer::renderUserApplets() // Render any user applets which are currently visible for (Applet *ua : inkhud->userApplets) { - if (ua && ua->isActive() && ua->isForeground()) { + if (ua && ua->isActive() && ua->isForeground() && (ua->wantsToRender() || renderAll)) { + + // Clear the tile unless the applet wants to draw over its previous render + // or everything is getting re-rendered anyways + if (ua->wantsFullRender() && !renderAll) + clearTile(ua->getTile()); + uint32_t start = millis(); - ua->render(); // Draw! + bool full = ua->wantsFullRender() || renderAll; + ua->render(full); // Draw! uint32_t stop = millis(); LOG_DEBUG("%s took %dms to render", ua->name, stop - start); } @@ -370,6 +451,9 @@ void InkHUD::Renderer::renderSystemApplets() if (!sa->isForeground()) continue; + if (!sa->wantsToRender() && !sa->alwaysRender && !renderAll) + continue; + // Skip if locked by another applet if (lockRendering && lockRendering != sa) continue; @@ -381,8 +465,14 @@ void InkHUD::Renderer::renderSystemApplets() assert(sa->getTile()); + // Clear the tile unless the applet wants to draw over its previous render + // or everything is getting re-rendered anyways + if (sa->wantsFullRender() && !renderAll) + clearTile(sa->getTile()); + // uint32_t start = millis(); - sa->render(); // Draw! + bool full = sa->wantsFullRender() || renderAll; + sa->render(full); // Draw! // uint32_t stop = millis(); // LOG_DEBUG("%s took %dms to render", sa->name, stop - start); } @@ -409,7 +499,10 @@ void InkHUD::Renderer::renderPlaceholders() // uint32_t start = millis(); for (Tile *t : emptyTiles) { t->assignApplet(placeholder); - placeholder->render(); + // Clear the tile unless everything is getting re-rendered + if (!renderAll) + clearTile(t); + placeholder->render(true); // full render t->assignApplet(nullptr); } // uint32_t stop = millis(); diff --git a/src/graphics/niche/InkHUD/Renderer.h b/src/graphics/niche/InkHUD/Renderer.h index b6cf9e215..5cfb79277 100644 --- a/src/graphics/niche/InkHUD/Renderer.h +++ b/src/graphics/niche/InkHUD/Renderer.h @@ -37,8 +37,8 @@ class Renderer : protected concurrency::OSThread // Call these to make the image change - void requestUpdate(); // Update display, if a foreground applet has info it wants to show - void forceUpdate(Drivers::EInk::UpdateTypes type = Drivers::EInk::UpdateTypes::UNSPECIFIED, + void requestUpdate(bool all = false); // Update display, if a foreground applet has info it wants to show + void forceUpdate(Drivers::EInk::UpdateTypes type = Drivers::EInk::UpdateTypes::UNSPECIFIED, bool all = false, bool async = true); // Update display, regardless of whether any applets requested this // Wait for an update to complete @@ -65,6 +65,7 @@ class Renderer : protected concurrency::OSThread // Steps of the rendering process void clearBuffer(); + void clearTile(Tile *t); void checkLocks(); bool shouldUpdate(); Drivers::EInk::UpdateTypes decideUpdateType(); @@ -85,6 +86,7 @@ class Renderer : protected concurrency::OSThread bool requested = false; bool forced = false; + bool renderAll = false; // For convenience InkHUD *inkhud = nullptr; diff --git a/src/graphics/niche/InkHUD/SystemApplet.h b/src/graphics/niche/InkHUD/SystemApplet.h index fb5b06e51..32e0e58bb 100644 --- a/src/graphics/niche/InkHUD/SystemApplet.h +++ b/src/graphics/niche/InkHUD/SystemApplet.h @@ -22,9 +22,11 @@ class SystemApplet : public Applet public: // System applets have the right to: - bool handleInput = false; // - respond to input from the user button - bool lockRendering = false; // - prevent other applets from being rendered during an update - bool lockRequests = false; // - prevent other applets from triggering display updates + bool handleInput = false; // - respond to input from the user button + bool handleFreeText = false; // - respond to free text input + bool lockRendering = false; // - prevent other applets from being rendered during an update + bool lockRequests = false; // - prevent other applets from triggering display updates + bool alwaysRender = false; // - render every time the screen is updated virtual void onReboot() { onShutdown(); } // - handle reboot specially virtual void onApplyingChanges() {} @@ -41,4 +43,4 @@ class SystemApplet : public Applet }; // namespace NicheGraphics::InkHUD -#endif \ No newline at end of file +#endif diff --git a/src/graphics/niche/InkHUD/Tile.cpp b/src/graphics/niche/InkHUD/Tile.cpp index 5e548de74..8beb25f39 100644 --- a/src/graphics/niche/InkHUD/Tile.cpp +++ b/src/graphics/niche/InkHUD/Tile.cpp @@ -18,7 +18,7 @@ static int32_t runtaskHighlight() LOG_DEBUG("Dismissing Highlight"); InkHUD::Tile::highlightShown = false; InkHUD::Tile::highlightTarget = nullptr; - InkHUD::InkHUD::getInstance()->forceUpdate(Drivers::EInk::UpdateTypes::FAST); // Re-render, clearing the highlighting + InkHUD::InkHUD::getInstance()->forceUpdate(Drivers::EInk::UpdateTypes::FAST, true); // Re-render, clearing the highlighting return taskHighlight->disable(); } static void inittaskHighlight() @@ -190,6 +190,18 @@ void InkHUD::Tile::handleAppletPixel(int16_t x, int16_t y, Color c) } } +// Used in Renderer for clearing the tile +int16_t InkHUD::Tile::getLeft() +{ + return left; +} + +// Used in Renderer for clearing the tile +int16_t InkHUD::Tile::getTop() +{ + return top; +} + // Called by Applet base class, when setting applet dimensions, immediately before render uint16_t InkHUD::Tile::getWidth() { @@ -220,7 +232,7 @@ void InkHUD::Tile::requestHighlight() { Tile::highlightTarget = this; Tile::highlightShown = false; - inkhud->forceUpdate(Drivers::EInk::UpdateTypes::FAST); + inkhud->forceUpdate(Drivers::EInk::UpdateTypes::FAST, true); } // Starts the timer which will automatically dismiss the highlighting, if the tile doesn't organically redraw first diff --git a/src/graphics/niche/InkHUD/Tile.h b/src/graphics/niche/InkHUD/Tile.h index 0f5444f17..0c09e4704 100644 --- a/src/graphics/niche/InkHUD/Tile.h +++ b/src/graphics/niche/InkHUD/Tile.h @@ -29,6 +29,8 @@ class Tile void setRegion(uint8_t layoutSize, uint8_t tileIndex); // Assign region automatically, based on layout void setRegion(int16_t left, int16_t top, uint16_t width, uint16_t height); // Assign region manually void handleAppletPixel(int16_t x, int16_t y, Color c); // Receive px output from assigned applet + int16_t getLeft(); + int16_t getTop(); uint16_t getWidth(); uint16_t getHeight(); static uint16_t maxDisplayDimension(); // Largest possible width / height any tile may ever encounter diff --git a/src/graphics/niche/InkHUD/WindowManager.cpp b/src/graphics/niche/InkHUD/WindowManager.cpp index 0548de1eb..9c18fbd48 100644 --- a/src/graphics/niche/InkHUD/WindowManager.cpp +++ b/src/graphics/niche/InkHUD/WindowManager.cpp @@ -4,6 +4,7 @@ #include "./Applets/System/AlignStick/AlignStickApplet.h" #include "./Applets/System/BatteryIcon/BatteryIconApplet.h" +#include "./Applets/System/Keyboard/KeyboardApplet.h" #include "./Applets/System/Logo/LogoApplet.h" #include "./Applets/System/Menu/MenuApplet.h" #include "./Applets/System/Notification/NotificationApplet.h" @@ -148,6 +149,28 @@ void InkHUD::WindowManager::openAlignStick() } } +void InkHUD::WindowManager::openKeyboard() +{ + KeyboardApplet *keyboard = (KeyboardApplet *)inkhud->getSystemApplet("Keyboard"); + + if (keyboard) { + keyboard->bringToForeground(); + keyboardOpen = true; + changeLayout(); + } +} + +void InkHUD::WindowManager::closeKeyboard() +{ + KeyboardApplet *keyboard = (KeyboardApplet *)inkhud->getSystemApplet("Keyboard"); + + if (keyboard) { + keyboard->sendToBackground(); + keyboardOpen = false; + changeLayout(); + } +} + // On the currently focussed tile: cycle to the next available user applet // Applets available for this must be activated, and not already displayed on another tile void InkHUD::WindowManager::nextApplet() @@ -272,7 +295,6 @@ void InkHUD::WindowManager::toggleBatteryIcon() batteryIcon->sendToBackground(); // Force-render - // - redraw all applets inkhud->forceUpdate(EInk::UpdateTypes::FAST); } @@ -311,9 +333,25 @@ void InkHUD::WindowManager::changeLayout() menu->show(ft); } + // Resize for the on-screen keyboard + if (keyboardOpen) { + // Send all user applets to the background + // User applets currently don't handle free text input + for (uint8_t i = 0; i < inkhud->userApplets.size(); i++) + inkhud->userApplets.at(i)->sendToBackground(); + // Find the first system applet that can handle freetext and resize it + for (SystemApplet *sa : inkhud->systemApplets) { + if (sa->handleFreeText) { + const uint16_t keyboardHeight = KeyboardApplet::getKeyboardHeight(); + sa->getTile()->setRegion(0, 0, inkhud->width(), inkhud->height() - keyboardHeight - 1); + break; + } + } + } + // Force-render // - redraw all applets - inkhud->forceUpdate(EInk::UpdateTypes::FAST); + inkhud->forceUpdate(EInk::UpdateTypes::FAST, true); } // Perform necessary reconfiguration when user activates or deactivates applets at run-time @@ -347,7 +385,7 @@ void InkHUD::WindowManager::changeActivatedApplets() // Force-render // - redraw all applets - inkhud->forceUpdate(EInk::UpdateTypes::FAST); + inkhud->forceUpdate(EInk::UpdateTypes::FAST, true); } // Some applets may be permitted to bring themselves to foreground, to show new data @@ -433,8 +471,10 @@ void InkHUD::WindowManager::createSystemApplets() addSystemApplet("Logo", new LogoApplet, new Tile); addSystemApplet("Pairing", new PairingApplet, new Tile); addSystemApplet("Tips", new TipsApplet, new Tile); - if (settings->joystick.enabled) + if (settings->joystick.enabled) { addSystemApplet("AlignStick", new AlignStickApplet, new Tile); + addSystemApplet("Keyboard", new KeyboardApplet, new Tile); + } addSystemApplet("Menu", new MenuApplet, nullptr); @@ -457,9 +497,13 @@ void InkHUD::WindowManager::placeSystemTiles() inkhud->getSystemApplet("Logo")->getTile()->setRegion(0, 0, inkhud->width(), inkhud->height()); inkhud->getSystemApplet("Pairing")->getTile()->setRegion(0, 0, inkhud->width(), inkhud->height()); inkhud->getSystemApplet("Tips")->getTile()->setRegion(0, 0, inkhud->width(), inkhud->height()); - if (settings->joystick.enabled) + if (settings->joystick.enabled) { inkhud->getSystemApplet("AlignStick")->getTile()->setRegion(0, 0, inkhud->width(), inkhud->height()); - + const uint16_t keyboardHeight = KeyboardApplet::getKeyboardHeight(); + inkhud->getSystemApplet("Keyboard") + ->getTile() + ->setRegion(0, inkhud->height() - keyboardHeight, inkhud->width(), keyboardHeight); + } inkhud->getSystemApplet("Notification")->getTile()->setRegion(0, 0, inkhud->width(), 20); const uint16_t batteryIconHeight = Applet::getHeaderHeight() - 2 - 2; diff --git a/src/graphics/niche/InkHUD/WindowManager.h b/src/graphics/niche/InkHUD/WindowManager.h index 5def48f8c..948ef6131 100644 --- a/src/graphics/niche/InkHUD/WindowManager.h +++ b/src/graphics/niche/InkHUD/WindowManager.h @@ -31,6 +31,8 @@ class WindowManager void prevTile(); void openMenu(); void openAlignStick(); + void openKeyboard(); + void closeKeyboard(); void nextApplet(); void prevApplet(); void rotate(); @@ -64,6 +66,7 @@ class WindowManager void findOrphanApplets(); // Find any applets left-behind when layout changes std::vector userTiles; // Tiles which can host user applets + bool keyboardOpen = false; // For convenience InkHUD *inkhud = nullptr; diff --git a/src/graphics/niche/InkHUD/docs/README.md b/src/graphics/niche/InkHUD/docs/README.md index aa23f065f..8c30aba58 100644 --- a/src/graphics/niche/InkHUD/docs/README.md +++ b/src/graphics/niche/InkHUD/docs/README.md @@ -174,7 +174,7 @@ class BasicExampleApplet : public Applet // You must have an onRender() method // All drawing happens here - void onRender() override; + void onRender(bool full) override; }; ``` @@ -183,7 +183,7 @@ The `onRender` method is called when the display image is redrawn. This can happ ```cpp // All drawing happens here // Our basic example doesn't do anything useful. It just passively prints some text. -void InkHUD::BasicExampleApplet::onRender() +void InkHUD::BasicExampleApplet::onRender(bool full) { printAt(0, 0, "Hello, world!"); } From 8f630bfcf3f38fdc1e13a2c6f36f1331c4feabfa Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Fri, 30 Jan 2026 12:52:17 -0600 Subject: [PATCH 90/99] Fix typo in LED state comment And removed unused define --- variants/nrf52840/meshlink/variant.h | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/variants/nrf52840/meshlink/variant.h b/variants/nrf52840/meshlink/variant.h index 9075d0467..00107ac34 100644 --- a/variants/nrf52840/meshlink/variant.h +++ b/variants/nrf52840/meshlink/variant.h @@ -32,8 +32,7 @@ extern "C" { #define PIN_LED1 (24) // Built in white led for status #define LED_BLUE PIN_LED1 -#define LED_STATE_ON 0 // State when LED is litted -#define LED_INVERTED 1 +#define LED_STATE_ON 0 // State when LED is lit // Testing USB detection // #define NRF_APM @@ -150,4 +149,4 @@ static const uint8_t SCK = PIN_SPI_SCK; /*---------------------------------------------------------------------------- * Arduino objects - C++ only *----------------------------------------------------------------------------*/ -#endif \ No newline at end of file +#endif From 7b03980e0a02f55aaf041159eb6d7649ffc8e967 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Fri, 30 Jan 2026 13:46:24 -0600 Subject: [PATCH 91/99] Refuse to send legacy DMs simply because the remote public key is unknown (#9485) * Refuse to send legacy DMs simply because the remote public key is unknown * Update src/mesh/Router.cpp - use more specific error Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update src/mesh/Router.cpp - more detail in warning message Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/mesh/Router.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index 287fbcf60..32544a051 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -620,15 +620,19 @@ meshtastic_Routing_Error perhapsEncode(meshtastic_MeshPacket *p) !(p->pki_encrypted != true && (strcasecmp(channels.getName(chIndex), Channels::serialChannel) == 0 || strcasecmp(channels.getName(chIndex), Channels::gpioChannel) == 0)) && // Check for valid keys and single node destination - config.security.private_key.size == 32 && !isBroadcast(p->to) && node != nullptr && - // Check for a known public key for the destination - (node->user.public_key.size == 32) && + config.security.private_key.size == 32 && !isBroadcast(p->to) && // Some portnums either make no sense to send with PKC p->decoded.portnum != meshtastic_PortNum_TRACEROUTE_APP && p->decoded.portnum != meshtastic_PortNum_NODEINFO_APP && p->decoded.portnum != meshtastic_PortNum_ROUTING_APP && p->decoded.portnum != meshtastic_PortNum_POSITION_APP) { LOG_DEBUG("Use PKI!"); if (numbytes + MESHTASTIC_HEADER_LENGTH + MESHTASTIC_PKC_OVERHEAD > MAX_LORA_PAYLOAD_LEN) return meshtastic_Routing_Error_TOO_LARGE; + // Check for a known public key for the destination + if (node == nullptr || node->user.public_key.size != 32) { + LOG_WARN("Unknown public key for destination node 0x%08x (portnum %d), refusing to send legacy DM", p->to, + p->decoded.portnum); + return meshtastic_Routing_Error_PKI_SEND_FAIL_PUBLIC_KEY; + } if (p->pki_encrypted && !memfll(p->public_key.bytes, 0, 32) && memcmp(p->public_key.bytes, node->user.public_key.bytes, 32) != 0) { LOG_WARN("Client public key differs from requested: 0x%02x, stored key begins 0x%02x", *p->public_key.bytes, From 1d30342c0035eb03da99e65abe312c7682c2e2d8 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Sat, 31 Jan 2026 12:15:06 -0600 Subject: [PATCH 92/99] Don't ever define PIN_LED or BLE_LED_INVERTED (#9494) * Don't ever define PIN_LED * Deprecate BLE_LED_INVERTED --- src/BluetoothStatus.h | 12 ++---------- src/main.cpp | 10 ++++------ src/nimble/NimbleBluetooth.cpp | 6 +----- variants/esp32/wiphone/variant.h | 1 - variants/esp32s3/esp32-s3-pico/variant.h | 1 - .../diy/seeed-xiao-nrf52840-wio-sx1262/variant.h | 2 -- variants/nrf52840/t-echo-lite/variant.h | 1 - variants/rp2040/challenger_2040_lora/pins_arduino.h | 2 +- variants/rp2040/challenger_2040_lora/variant.h | 2 -- variants/rp2040/rak11310/pins_arduino.h | 3 +-- variants/rp2040/rak11310/variant.h | 2 +- variants/rp2040/senselora_rp2040/pins_arduino.h | 3 +-- variants/rp2040/senselora_rp2040/variant.h | 2 +- 13 files changed, 12 insertions(+), 35 deletions(-) diff --git a/src/BluetoothStatus.h b/src/BluetoothStatus.h index 680aec929..4ea4a95ac 100644 --- a/src/BluetoothStatus.h +++ b/src/BluetoothStatus.h @@ -89,22 +89,14 @@ class BluetoothStatus : public Status case ConnectionState::CONNECTED: LOG_DEBUG("BluetoothStatus CONNECTED"); #ifdef BLE_LED -#ifdef BLE_LED_INVERTED - digitalWrite(BLE_LED, LOW); -#else - digitalWrite(BLE_LED, HIGH); -#endif + digitalWrite(BLE_LED, LED_STATE_ON); #endif break; case ConnectionState::DISCONNECTED: LOG_DEBUG("BluetoothStatus DISCONNECTED"); #ifdef BLE_LED -#ifdef BLE_LED_INVERTED - digitalWrite(BLE_LED, HIGH); -#else - digitalWrite(BLE_LED, LOW); -#endif + digitalWrite(BLE_LED, LED_STATE_OFF); #endif break; } diff --git a/src/main.cpp b/src/main.cpp index d9773dfb9..1e0ec041e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -366,11 +366,7 @@ void setup() #ifdef BLE_LED pinMode(BLE_LED, OUTPUT); -#ifdef BLE_LED_INVERTED - digitalWrite(BLE_LED, HIGH); -#else - digitalWrite(BLE_LED, LOW); -#endif + digitalWrite(BLE_LED, LED_STATE_OFF); #endif concurrency::hasBeenSetup = true; @@ -493,7 +489,9 @@ void setup() // The ThinkNodes have their own blink logic // ledPeriodic = new Periodic("Blink", elecrowLedBlinker); #else + ledPeriodic = new Periodic("Blink", ledBlinker); + #endif fsInit(); @@ -834,7 +832,7 @@ void setup() SPI.begin(); #endif #else -// ESP32 + // ESP32 #if defined(HW_SPI1_DEVICE) SPI1.begin(LORA_SCK, LORA_MISO, LORA_MOSI, LORA_CS); LOG_DEBUG("SPI1.begin(SCK=%d, MISO=%d, MOSI=%d, NSS=%d)", LORA_SCK, LORA_MISO, LORA_MOSI, LORA_CS); diff --git a/src/nimble/NimbleBluetooth.cpp b/src/nimble/NimbleBluetooth.cpp index 2a59c0aab..89d74dbd7 100644 --- a/src/nimble/NimbleBluetooth.cpp +++ b/src/nimble/NimbleBluetooth.cpp @@ -757,11 +757,7 @@ void NimbleBluetooth::deinit() isDeInit = true; #ifdef BLE_LED -#ifdef BLE_LED_INVERTED - digitalWrite(BLE_LED, HIGH); -#else - digitalWrite(BLE_LED, LOW); -#endif + digitalWrite(BLE_LED, LED_STATE_OFF); #endif #ifndef NIMBLE_TWO NimBLEDevice::deinit(); diff --git a/variants/esp32/wiphone/variant.h b/variants/esp32/wiphone/variant.h index 619ac622a..5baeb3936 100644 --- a/variants/esp32/wiphone/variant.h +++ b/variants/esp32/wiphone/variant.h @@ -26,7 +26,6 @@ #undef GPS_TX_PIN #define NO_GPS 1 #define HAS_GPS 0 -#define NO_SCREEN #define HAS_SCREEN 0 // Default SPI1 will be mapped to the display diff --git a/variants/esp32s3/esp32-s3-pico/variant.h b/variants/esp32s3/esp32-s3-pico/variant.h index bfcb6059d..65732171a 100644 --- a/variants/esp32s3/esp32-s3-pico/variant.h +++ b/variants/esp32s3/esp32-s3-pico/variant.h @@ -8,7 +8,6 @@ #define EXT_NOTIFY_OUT 22 #define BUTTON_PIN 0 // 17 -// #define LED_PIN PIN_LED // Board has RGB LED 21 #define HAS_NEOPIXEL // Enable the use of neopixels #define NEOPIXEL_COUNT 1 // How many neopixels are connected diff --git a/variants/nrf52840/diy/seeed-xiao-nrf52840-wio-sx1262/variant.h b/variants/nrf52840/diy/seeed-xiao-nrf52840-wio-sx1262/variant.h index 6927f1295..c132eba5b 100644 --- a/variants/nrf52840/diy/seeed-xiao-nrf52840-wio-sx1262/variant.h +++ b/variants/nrf52840/diy/seeed-xiao-nrf52840-wio-sx1262/variant.h @@ -35,8 +35,6 @@ extern "C" { #define PIN_LED2 LED_BLUE #define PIN_LED3 LED_RED -#define PIN_LED PIN_LED1 - #define LED_STATE_ON 1 // State when LED is lit // XIAO Wio-SX1262 Shield User button diff --git a/variants/nrf52840/t-echo-lite/variant.h b/variants/nrf52840/t-echo-lite/variant.h index 7dc1a3ef5..54c7bdfb5 100644 --- a/variants/nrf52840/t-echo-lite/variant.h +++ b/variants/nrf52840/t-echo-lite/variant.h @@ -51,7 +51,6 @@ extern "C" { #define LED_GREEN PIN_LED1 #define BLE_LED LED_BLUE -#define BLE_LED_INVERTED 1 #define LED_STATE_ON 0 // State when LED is lit // Buttons diff --git a/variants/rp2040/challenger_2040_lora/pins_arduino.h b/variants/rp2040/challenger_2040_lora/pins_arduino.h index 5e7311413..b27cc0297 100644 --- a/variants/rp2040/challenger_2040_lora/pins_arduino.h +++ b/variants/rp2040/challenger_2040_lora/pins_arduino.h @@ -7,7 +7,7 @@ #define ADC_RESOLUTION (12u) // LEDs -#define PIN_LED (24u) +#define LED_PIN (24u) // Serial #define PIN_SERIAL1_TX (16u) diff --git a/variants/rp2040/challenger_2040_lora/variant.h b/variants/rp2040/challenger_2040_lora/variant.h index 552f90720..f5126cfff 100644 --- a/variants/rp2040/challenger_2040_lora/variant.h +++ b/variants/rp2040/challenger_2040_lora/variant.h @@ -5,8 +5,6 @@ #define EXT_NOTIFY_OUT 0xFFFFFFFF #define BUTTON_PIN 0xFFFFFFFF -#define LED_PIN PIN_LED - #define USE_RF95 // RFM95/SX127x #undef LORA_SCK diff --git a/variants/rp2040/rak11310/pins_arduino.h b/variants/rp2040/rak11310/pins_arduino.h index 56214a947..59290bbdb 100644 --- a/variants/rp2040/rak11310/pins_arduino.h +++ b/variants/rp2040/rak11310/pins_arduino.h @@ -23,8 +23,7 @@ static const uint8_t A2 = PIN_A2; static const uint8_t A3 = PIN_A3; // LEDs -#define PIN_LED (23u) -#define PIN_LED1 PIN_LED +#define PIN_LED1 (23u) #define LED_NOTIFICATION (24u) #define ADC_RESOLUTION 12 diff --git a/variants/rp2040/rak11310/variant.h b/variants/rp2040/rak11310/variant.h index 4d2b9ca3a..cf49ff491 100644 --- a/variants/rp2040/rak11310/variant.h +++ b/variants/rp2040/rak11310/variant.h @@ -10,7 +10,7 @@ #define I2C_SDA1 2 #define I2C_SCL1 3 -#define LED_PIN PIN_LED +#define LED_PIN PIN_LED1 #define ledOff(pin) pinMode(pin, INPUT) #define BUTTON_PIN 9 diff --git a/variants/rp2040/senselora_rp2040/pins_arduino.h b/variants/rp2040/senselora_rp2040/pins_arduino.h index 61705c8d9..575839cbc 100644 --- a/variants/rp2040/senselora_rp2040/pins_arduino.h +++ b/variants/rp2040/senselora_rp2040/pins_arduino.h @@ -11,8 +11,7 @@ static const uint8_t A2 = PIN_A2; static const uint8_t A3 = PIN_A3; // LEDs -#define PIN_LED (23u) -#define PIN_LED1 PIN_LED +#define PIN_LED1 (23u) #define ADC_RESOLUTION 12 diff --git a/variants/rp2040/senselora_rp2040/variant.h b/variants/rp2040/senselora_rp2040/variant.h index cc90284b7..04e21e073 100644 --- a/variants/rp2040/senselora_rp2040/variant.h +++ b/variants/rp2040/senselora_rp2040/variant.h @@ -5,7 +5,7 @@ #define BUTTON_PIN 2 #define BUTTON_NEED_PULLUP -#define LED_PIN PIN_LED +#define LED_PIN PIN_LED1 #define ledOff(pin) pinMode(pin, INPUT) #undef BATTERY_PIN From 9d06c1bf34d7b6a3727a319d0f5fa38777ee5297 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Sat, 31 Jan 2026 14:55:51 -0600 Subject: [PATCH 93/99] Add StatusMessage module and config overrides (#9351) * Add StatusMessage module and config overrides * Trunk * Don't reboot node simply for a StatusMessage config update --- src/mesh/NodeDB.cpp | 10 ++++++ src/modules/AdminModule.cpp | 18 +++++++++-- src/modules/Modules.cpp | 6 ++++ src/modules/StatusMessageModule.cpp | 41 ++++++++++++++++++++++++ src/modules/StatusMessageModule.h | 35 ++++++++++++++++++++ src/platform/portduino/PortduinoGlue.cpp | 8 +++++ src/platform/portduino/PortduinoGlue.h | 41 ++++++++++++++++-------- 7 files changed, 142 insertions(+), 17 deletions(-) create mode 100644 src/modules/StatusMessageModule.cpp create mode 100644 src/modules/StatusMessageModule.h diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 0ba60f1e2..0da4261b9 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -1404,6 +1404,15 @@ void NodeDB::loadFromDisk() if (portduino_config.has_configDisplayMode) { config.display.displaymode = (_meshtastic_Config_DisplayConfig_DisplayMode)portduino_config.configDisplayMode; } + if (portduino_config.has_statusMessage) { + moduleConfig.has_statusmessage = true; + strncpy(moduleConfig.statusmessage.node_status, portduino_config.statusMessage.c_str(), + sizeof(moduleConfig.statusmessage.node_status)); + moduleConfig.statusmessage.node_status[sizeof(moduleConfig.statusmessage.node_status) - 1] = '\0'; + } + if (portduino_config.enable_UDP) { + config.network.enabled_protocols = true; + } #endif } @@ -1544,6 +1553,7 @@ bool NodeDB::saveToDiskNoRetry(int saveWhat) moduleConfig.has_ambient_lighting = true; moduleConfig.has_audio = true; moduleConfig.has_paxcounter = true; + moduleConfig.has_statusmessage = true; success &= saveProto(moduleConfigFileName, meshtastic_LocalModuleConfig_size, &meshtastic_LocalModuleConfig_msg, &moduleConfig); diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index 1fda9bf13..61dbc0b92 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -905,10 +905,11 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c) bool AdminModule::handleSetModuleConfig(const meshtastic_ModuleConfig &c) { + bool shouldReboot = true; // If we are in an open transaction or configuring MQTT or Serial (which have validation), defer disabling Bluetooth // Otherwise, disable Bluetooth to prevent the phone from interfering with the config - if (!hasOpenEditTransaction && - !IS_ONE_OF(c.which_payload_variant, meshtastic_ModuleConfig_mqtt_tag, meshtastic_ModuleConfig_serial_tag)) { + if (!hasOpenEditTransaction && !IS_ONE_OF(c.which_payload_variant, meshtastic_ModuleConfig_mqtt_tag, + meshtastic_ModuleConfig_serial_tag, meshtastic_ModuleConfig_statusmessage_tag)) { disableBluetooth(); } @@ -1000,8 +1001,14 @@ bool AdminModule::handleSetModuleConfig(const meshtastic_ModuleConfig &c) moduleConfig.has_paxcounter = true; moduleConfig.paxcounter = c.payload_variant.paxcounter; break; + case meshtastic_ModuleConfig_statusmessage_tag: + LOG_INFO("Set module config: StatusMessage"); + moduleConfig.has_statusmessage = true; + moduleConfig.statusmessage = c.payload_variant.statusmessage; + shouldReboot = false; + break; } - saveChanges(SEGMENT_MODULECONFIG); + saveChanges(SEGMENT_MODULECONFIG, shouldReboot); return true; } @@ -1180,6 +1187,11 @@ void AdminModule::handleGetModuleConfig(const meshtastic_MeshPacket &req, const res.get_module_config_response.which_payload_variant = meshtastic_ModuleConfig_paxcounter_tag; res.get_module_config_response.payload_variant.paxcounter = moduleConfig.paxcounter; break; + case meshtastic_AdminMessage_ModuleConfigType_STATUSMESSAGE_CONFIG: + LOG_INFO("Get module config: StatusMessage"); + res.get_module_config_response.which_payload_variant = meshtastic_ModuleConfig_statusmessage_tag; + res.get_module_config_response.payload_variant.statusmessage = moduleConfig.statusmessage; + break; } // NOTE: The phone app needs to know the ls_secsvalue so it can properly expect sleep behavior. diff --git a/src/modules/Modules.cpp b/src/modules/Modules.cpp index e8da8e983..e8fa4775a 100644 --- a/src/modules/Modules.cpp +++ b/src/modules/Modules.cpp @@ -90,6 +90,9 @@ #if !MESHTASTIC_EXCLUDE_DROPZONE #include "modules/DropzoneModule.h" #endif +#if !MESHTASTIC_EXCLUDE_STATUS +#include "modules/StatusMessageModule.h" +#endif #if defined(HAS_HARDWARE_WATCHDOG) #include "watchdog/watchdogThread.h" @@ -150,6 +153,9 @@ void setupModules() #if !MESHTASTIC_EXCLUDE_DROPZONE dropzoneModule = new DropzoneModule(); #endif +#if !MESHTASTIC_EXCLUDE_STATUS + statusMessageModule = new StatusMessageModule(); +#endif #if !MESHTASTIC_EXCLUDE_GENERIC_THREAD_MODULE new GenericThreadModule(); #endif diff --git a/src/modules/StatusMessageModule.cpp b/src/modules/StatusMessageModule.cpp new file mode 100644 index 000000000..139a74d8e --- /dev/null +++ b/src/modules/StatusMessageModule.cpp @@ -0,0 +1,41 @@ +#if !MESHTASTIC_EXCLUDE_STATUS + +#include "StatusMessageModule.h" +#include "MeshService.h" +#include "ProtobufModule.h" + +StatusMessageModule *statusMessageModule; + +int32_t StatusMessageModule::runOnce() +{ + if (moduleConfig.has_statusmessage && moduleConfig.statusmessage.node_status[0] != '\0') { + // create and send message with the status message set + meshtastic_StatusMessage ourStatus = meshtastic_StatusMessage_init_zero; + strncpy(ourStatus.status, moduleConfig.statusmessage.node_status, sizeof(ourStatus.status)); + ourStatus.status[sizeof(ourStatus.status) - 1] = '\0'; // ensure null termination + meshtastic_MeshPacket *p = allocDataPacket(); + p->decoded.payload.size = pb_encode_to_bytes(p->decoded.payload.bytes, sizeof(p->decoded.payload.bytes), + meshtastic_StatusMessage_fields, &ourStatus); + p->to = NODENUM_BROADCAST; + p->decoded.want_response = false; + p->priority = meshtastic_MeshPacket_Priority_BACKGROUND; + p->channel = 0; + service->sendToMesh(p); + } + + return 1000 * 12 * 60 * 60; +} + +ProcessMessage StatusMessageModule::handleReceived(const meshtastic_MeshPacket &mp) +{ + if (mp.which_payload_variant == meshtastic_MeshPacket_decoded_tag) { + meshtastic_StatusMessage incomingMessage; + if (pb_decode_from_bytes(mp.decoded.payload.bytes, mp.decoded.payload.size, meshtastic_StatusMessage_fields, + &incomingMessage)) { + LOG_INFO("Received a NodeStatus message %s", incomingMessage.status); + } + } + return ProcessMessage::CONTINUE; +} + +#endif \ No newline at end of file diff --git a/src/modules/StatusMessageModule.h b/src/modules/StatusMessageModule.h new file mode 100644 index 000000000..c9ff54018 --- /dev/null +++ b/src/modules/StatusMessageModule.h @@ -0,0 +1,35 @@ +#pragma once +#if !MESHTASTIC_EXCLUDE_STATUS +#include "SinglePortModule.h" +#include "configuration.h" + +class StatusMessageModule : public SinglePortModule, private concurrency::OSThread +{ + + public: + /** Constructor + * name is for debugging output + */ + StatusMessageModule() + : SinglePortModule("statusMessage", meshtastic_PortNum_NODE_STATUS_APP), concurrency::OSThread("StatusMessage") + { + if (moduleConfig.has_statusmessage && moduleConfig.statusmessage.node_status[0] != '\0') { + this->setInterval(2 * 60 * 1000); + } else { + this->setInterval(1000 * 12 * 60 * 60); + } + // TODO: If we have a string, set the initial delay (15 minutes maybe) + } + + virtual int32_t runOnce() override; + + protected: + /** Called to handle a particular incoming message + */ + virtual ProcessMessage handleReceived(const meshtastic_MeshPacket &mp) override; + + private: +}; + +extern StatusMessageModule *statusMessageModule; +#endif \ No newline at end of file diff --git a/src/platform/portduino/PortduinoGlue.cpp b/src/platform/portduino/PortduinoGlue.cpp index d0d8ba40f..9579bef45 100644 --- a/src/platform/portduino/PortduinoGlue.cpp +++ b/src/platform/portduino/PortduinoGlue.cpp @@ -872,6 +872,7 @@ bool loadConfig(const char *configPath) } if (yamlConfig["Config"]) { + portduino_config.has_config_overrides = true; if (yamlConfig["Config"]["DisplayMode"]) { portduino_config.has_configDisplayMode = true; if ((yamlConfig["Config"]["DisplayMode"]).as("") == "TWOCOLOR") { @@ -884,6 +885,13 @@ bool loadConfig(const char *configPath) portduino_config.configDisplayMode = meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT; } } + if (yamlConfig["Config"]["StatusMessage"]) { + portduino_config.has_statusMessage = true; + portduino_config.statusMessage = (yamlConfig["Config"]["StatusMessage"]).as(""); + } + if ((yamlConfig["Config"]["EnableUDP"]).as(false)) { + portduino_config.enable_UDP = true; + } } if (yamlConfig["General"]) { diff --git a/src/platform/portduino/PortduinoGlue.h b/src/platform/portduino/PortduinoGlue.h index af511be6e..990803e79 100644 --- a/src/platform/portduino/PortduinoGlue.h +++ b/src/platform/portduino/PortduinoGlue.h @@ -177,8 +177,12 @@ extern struct portduino_config_struct { int hostMetrics_channel = 0; // config + bool has_config_overrides = false; int configDisplayMode = 0; bool has_configDisplayMode = false; + std::string statusMessage = ""; + bool has_statusMessage = false; + bool enable_UDP = false; // General std::string mac_address = ""; @@ -505,21 +509,30 @@ extern struct portduino_config_struct { } // config - if (has_configDisplayMode) { + if (has_config_overrides) { out << YAML::Key << "Config" << YAML::Value << YAML::BeginMap; - switch (configDisplayMode) { - case meshtastic_Config_DisplayConfig_DisplayMode_TWOCOLOR: - out << YAML::Key << "DisplayMode" << YAML::Value << "TWOCOLOR"; - break; - case meshtastic_Config_DisplayConfig_DisplayMode_INVERTED: - out << YAML::Key << "DisplayMode" << YAML::Value << "INVERTED"; - break; - case meshtastic_Config_DisplayConfig_DisplayMode_COLOR: - out << YAML::Key << "DisplayMode" << YAML::Value << "COLOR"; - break; - case meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT: - out << YAML::Key << "DisplayMode" << YAML::Value << "DEFAULT"; - break; + if (has_configDisplayMode) { + + switch (configDisplayMode) { + case meshtastic_Config_DisplayConfig_DisplayMode_TWOCOLOR: + out << YAML::Key << "DisplayMode" << YAML::Value << "TWOCOLOR"; + break; + case meshtastic_Config_DisplayConfig_DisplayMode_INVERTED: + out << YAML::Key << "DisplayMode" << YAML::Value << "INVERTED"; + break; + case meshtastic_Config_DisplayConfig_DisplayMode_COLOR: + out << YAML::Key << "DisplayMode" << YAML::Value << "COLOR"; + break; + case meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT: + out << YAML::Key << "DisplayMode" << YAML::Value << "DEFAULT"; + break; + } + } + if (has_statusMessage) { + out << YAML::Key << "StatusMessage" << YAML::Value << statusMessage; + } + if (enable_UDP) { + out << YAML::Key << "EnableUDP" << YAML::Value << true; } out << YAML::EndMap; // Config From 002214832387ace3ba448cb4e432d6908642bd27 Mon Sep 17 00:00:00 2001 From: Jason P Date: Sun, 1 Feb 2026 19:10:00 -0600 Subject: [PATCH 94/99] Missed in reviews - fixing send bubble (#9505) --- src/graphics/draw/MessageRenderer.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/graphics/draw/MessageRenderer.cpp b/src/graphics/draw/MessageRenderer.cpp index 193164439..01fdbb966 100644 --- a/src/graphics/draw/MessageRenderer.cpp +++ b/src/graphics/draw/MessageRenderer.cpp @@ -877,15 +877,15 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16 // Send Message (Right side) display->drawRect(x1 + 2 - bubbleW, y1 - bubbleH, bubbleW, bubbleH); // Top Right Corner - display->drawRect(x1, topY, 2, 1); + display->drawRect(x1 - 1, topY, 2, 1); display->drawRect(x1, topY, 1, 2); // Bottom Right Corner display->drawRect(x1 - 1, bottomY - 2, 2, 1); display->drawRect(x1, bottomY - 3, 1, 2); // Knock the corners off to make a bubble display->setColor(BLACK); - display->drawRect(x1 - bubbleW, topY - 1, 1, 1); - display->drawRect(x1 - bubbleW, bottomY - 1, 1, 1); + display->drawRect(x1 - bubbleW + 2, topY - 1, 1, 1); + display->drawRect(x1 - bubbleW + 2, bottomY - 1, 1, 1); display->setColor(WHITE); } else { // Received Message (Left Side) From f514bc230b86e462c8678d158e942b662bbbb399 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Tue, 3 Feb 2026 00:13:49 -0600 Subject: [PATCH 95/99] Prefer EXT_PWR_DETECT pin over chargingVolt to detect power unplugged (#9511) --- src/Power.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Power.cpp b/src/Power.cpp index b211d760e..a7da6f7a9 100644 --- a/src/Power.cpp +++ b/src/Power.cpp @@ -459,6 +459,8 @@ class AnalogBatteryLevel : public HasBatteryLevel } // if it's not HIGH - check the battery #endif + // If we have an EXT_PWR_DETECT pin and it indicates no external power, believe it. + return false; // technically speaking this should work for all(?) NRF52 boards // but needs testing across multiple devices. NRF52 USB would not even work if From 0703e0e6d797621eae32cff355a973ed519bbf16 Mon Sep 17 00:00:00 2001 From: Eric Sesterhenn Date: Tue, 3 Feb 2026 13:22:33 +0100 Subject: [PATCH 96/99] Make sure we always return a value in NodeDB::restorePreferences() (#9516) In case FScom is not defined there is no return statement. This moves the return outside of the ifdef to make sure a defined value is returned. --- src/mesh/NodeDB.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 0da4261b9..f23d6490b 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -2223,8 +2223,8 @@ bool NodeDB::restorePreferences(meshtastic_AdminMessage_BackupLocation location, } else if (location == meshtastic_AdminMessage_BackupLocation_SD) { // TODO: After more mainline SD card support } - return success; #endif + return success; } /// Record an error that should be reported via analytics From b7db22055d3a964d188ccbffce4c4ab3a3df6f0e Mon Sep 17 00:00:00 2001 From: Vortetty <33466216+Vortetty@users.noreply.github.com> Date: Tue, 3 Feb 2026 17:02:54 -0800 Subject: [PATCH 97/99] Inkhud battery icon improvements. (#9513) * Inkhud battery icon improvements. Fixes the battery icon draining from the flat side towards the bump, which is backwards from general design language seen on most devices By request of kr0n05_ on discord, adds the ability to mirror the battery icon which fixes that issue in another way, and is also a common design seen on other devices. * Remove option for icon mirroring * Add border + dither to battery to prevent font overlap * Fix trunk format * Code cleanup, courtesy of Xaositek. --- src/graphics/niche/InkHUD/Applet.cpp | 12 +++++++ .../System/BatteryIcon/BatteryIconApplet.cpp | 33 +++++++------------ src/graphics/niche/InkHUD/WindowManager.cpp | 8 ++--- 3 files changed, 28 insertions(+), 25 deletions(-) diff --git a/src/graphics/niche/InkHUD/Applet.cpp b/src/graphics/niche/InkHUD/Applet.cpp index ccdd76f97..0616d03cd 100644 --- a/src/graphics/niche/InkHUD/Applet.cpp +++ b/src/graphics/niche/InkHUD/Applet.cpp @@ -1,3 +1,5 @@ +#include "graphics/niche/InkHUD/Tile.h" +#include #ifdef MESHTASTIC_INCLUDE_INKHUD #include "./Applet.h" @@ -785,6 +787,16 @@ void InkHUD::Applet::drawHeader(std::string text) drawPixel(x, 0, BLACK); drawPixel(x, headerDivY, BLACK); // Dotted 50% } + + // Dither near battery + if (settings->optionalFeatures.batteryIcon) { + constexpr uint16_t ditherSizePx = 4; + Tile *batteryTile = ((Applet *)inkhud->getSystemApplet("BatteryIcon"))->getTile(); + const uint16_t batteryTileLeft = batteryTile->getLeft(); + const uint16_t batteryTileTop = batteryTile->getTop(); + const uint16_t batteryTileHeight = batteryTile->getHeight(); + hatchRegion(batteryTileLeft - ditherSizePx, batteryTileTop, ditherSizePx, batteryTileHeight, 2, WHITE); + } } // Get the height of the standard applet header diff --git a/src/graphics/niche/InkHUD/Applets/System/BatteryIcon/BatteryIconApplet.cpp b/src/graphics/niche/InkHUD/Applets/System/BatteryIcon/BatteryIconApplet.cpp index 0cc6f50ed..4fd01c348 100644 --- a/src/graphics/niche/InkHUD/Applets/System/BatteryIcon/BatteryIconApplet.cpp +++ b/src/graphics/niche/InkHUD/Applets/System/BatteryIcon/BatteryIconApplet.cpp @@ -48,37 +48,27 @@ int InkHUD::BatteryIconApplet::onPowerStatusUpdate(const meshtastic::Status *sta void InkHUD::BatteryIconApplet::onRender(bool full) { - // Fill entire tile - // - size of icon controlled by size of tile - int16_t l = 0; - int16_t t = 0; - uint16_t w = width(); - int16_t h = height(); - - // Clear the region beneath the tile + // Clear the region beneath the tile, including the border // Most applets are drawing onto an empty frame buffer and don't need to do this // We do need to do this with the battery though, as it is an "overlay" - fillRect(l, t, w, h, WHITE); - - // Vertical centerline - const int16_t m = t + (h / 2); + fillRect(0, 0, width(), height(), WHITE); // ===================== // Draw battery outline // ===================== // Positive terminal "bump" - const int16_t &bumpL = l; - const uint16_t bumpH = h / 2; - const int16_t bumpT = m - (bumpH / 2); constexpr uint16_t bumpW = 2; + const int16_t &bumpL = 1; + const uint16_t bumpH = (height() - 2) / 2; + const int16_t bumpT = (1 + ((height() - 2) / 2)) - (bumpH / 2); fillRect(bumpL, bumpT, bumpW, bumpH, BLACK); // Main body of battery - const int16_t bodyL = bumpL + bumpW; - const int16_t &bodyT = t; - const int16_t &bodyH = h; - const int16_t bodyW = w - bumpW; + const int16_t bodyL = 1 + bumpW; + const int16_t &bodyT = 1; + const int16_t &bodyH = height() - 2; // Handle top/bottom padding + const int16_t bodyW = (width() - 1) - bumpW; // Handle 1px left pad drawRect(bodyL, bodyT, bodyW, bodyH, BLACK); // Erase join between bump and body @@ -89,12 +79,13 @@ void InkHUD::BatteryIconApplet::onRender(bool full) // =================== constexpr int16_t slicePad = 2; - const int16_t sliceL = bodyL + slicePad; + int16_t sliceL = bodyL + slicePad; const int16_t sliceT = bodyT + slicePad; const uint16_t sliceH = bodyH - (slicePad * 2); uint16_t sliceW = bodyW - (slicePad * 2); - sliceW = (sliceW * socRounded) / 100; // Apply percentage + sliceW = (sliceW * socRounded) / 100; // Apply percentage + sliceL += ((bodyW - (slicePad * 2)) - sliceW); // Shift slice to the battery's negative terminal, correcting drain direction hatchRegion(sliceL, sliceT, sliceW, sliceH, 2, BLACK); drawRect(sliceL, sliceT, sliceW, sliceH, BLACK); diff --git a/src/graphics/niche/InkHUD/WindowManager.cpp b/src/graphics/niche/InkHUD/WindowManager.cpp index 9c18fbd48..cec72ce8f 100644 --- a/src/graphics/niche/InkHUD/WindowManager.cpp +++ b/src/graphics/niche/InkHUD/WindowManager.cpp @@ -510,10 +510,10 @@ void InkHUD::WindowManager::placeSystemTiles() const uint16_t batteryIconWidth = batteryIconHeight * 1.8; inkhud->getSystemApplet("BatteryIcon") ->getTile() - ->setRegion(inkhud->width() - batteryIconWidth, // x - 2, // y - batteryIconWidth, // width - batteryIconHeight); // height + ->setRegion(inkhud->width() - batteryIconWidth - 1, // x + 1, // y + batteryIconWidth + 1, // width + batteryIconHeight + 2); // height // Note: the tiles of placeholder and menu applets are manipulated specially // - menuApplet borrows user tiles From 538a5f0dfc9f6cd587b076f6c80a801b51a5f064 Mon Sep 17 00:00:00 2001 From: Mattatat25 <108779801+mattatat25@users.noreply.github.com> Date: Tue, 3 Feb 2026 20:45:02 -0600 Subject: [PATCH 98/99] Add reply bot module with DM-only responses and rate limiting (#9456) * Implement Meshtastic reply bot module with ping and status features Adds a reply bot module that listens for /ping, /hello, and /test commands received via direct messages or broadcasts on the primary channel. The module always replies via direct message to the sender only, reporting hop count, RSSI, and SNR. Per-sender cooldowns are enforced to reduce network spam, and the module can be excluded at build time via a compile flag. Updates include the new module source files and required build configuration changes. * Update ReplyBotModule.cpp Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update src/modules/ReplyBotModule.h Match the existing MESHTASTIC_EXCLUDE_* guard pattern so the module is excluded by default. Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update src/modules/ReplyBotModule.cpp Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Tidying up --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Ben Meadors --- platformio.ini | 1 + src/modules/ReplyBotModule.cpp | 183 +++++++++++++++++++++++++++++++++ src/modules/ReplyBotModule.h | 18 ++++ 3 files changed, 202 insertions(+) create mode 100644 src/modules/ReplyBotModule.cpp create mode 100644 src/modules/ReplyBotModule.h diff --git a/platformio.ini b/platformio.ini index 77e9cf214..291915d3c 100644 --- a/platformio.ini +++ b/platformio.ini @@ -50,6 +50,7 @@ build_flags = -Wno-missing-field-initializers -DRADIOLIB_EXCLUDE_APRS=1 -DRADIOLIB_EXCLUDE_LORAWAN=1 -DMESHTASTIC_EXCLUDE_DROPZONE=1 + -DMESHTASTIC_EXCLUDE_REPLYBOT=1 -DMESHTASTIC_EXCLUDE_REMOTEHARDWARE=1 -DMESHTASTIC_EXCLUDE_HEALTH_TELEMETRY=1 -DMESHTASTIC_EXCLUDE_POWERSTRESS=1 ; exclude power stress test module from main firmware diff --git a/src/modules/ReplyBotModule.cpp b/src/modules/ReplyBotModule.cpp new file mode 100644 index 000000000..90223cacb --- /dev/null +++ b/src/modules/ReplyBotModule.cpp @@ -0,0 +1,183 @@ +#if !MESHTASTIC_EXCLUDE_REPLYBOT +/* + * ReplyBotModule.cpp + * + * This module implements a simple reply bot for the Meshtastic firmware. It listens for + * specific text commands ("/ping", "/hello" and "/test") delivered either via a direct + * message (DM) or a broadcast on the primary channel. When a supported command is + * received the bot responds with a short status message that includes the hop count + * (minimum number of relays), RSSI and SNR of the received packet. To avoid spamming + * the network it enforces a per‑sender cooldown between responses. By default the + * module is enabled; define MESHTASTIC_EXCLUDE_REPLYBOT at build time to exclude it + * entirely. See the official firmware documentation for guidance on adding modules. + */ + +#include "ReplyBotModule.h" +#include "Channels.h" +#include "MeshService.h" +#include "NodeDB.h" +#include "configuration.h" +#include "mesh/MeshTypes.h" + +#include +#include +#include + +// +// Rate limiting data structures +// +// Each sender is tracked in a small ring buffer. When a message arrives from a +// sender we check the last time we responded to them. If the difference is +// less than the configured cooldown (different values for DM vs broadcast) +// the message is ignored; otherwise we update the last response time and +// proceed with replying. + +struct ReplyBotCooldownEntry { + uint32_t from = 0; + uint32_t lastMs = 0; +}; + +static constexpr uint8_t REPLYBOT_COOLDOWN_SLOTS = 8; // ring buffer size +static constexpr uint32_t REPLYBOT_DM_COOLDOWN_MS = 15 * 1000; // 15 seconds for DMs +static constexpr uint32_t REPLYBOT_LF_COOLDOWN_MS = 60 * 1000; // 60 seconds for LongFast broadcasts + +static ReplyBotCooldownEntry replybotCooldown[REPLYBOT_COOLDOWN_SLOTS]; +static uint8_t replybotCooldownIdx = 0; + +// Return true if a reply should be rate‑limited for this sender, updating the +// entry table as needed. +static bool replybotRateLimited(uint32_t from, uint32_t cooldownMs) +{ + const uint32_t now = millis(); + for (auto &e : replybotCooldown) { + if (e.from == from) { + // Found existing entry; check if cooldown expired + if ((uint32_t)(now - e.lastMs) < cooldownMs) { + return true; + } + e.lastMs = now; + return false; + } + } + // No entry found – insert new sender into the ring + replybotCooldown[replybotCooldownIdx].from = from; + replybotCooldown[replybotCooldownIdx].lastMs = now; + replybotCooldownIdx = (replybotCooldownIdx + 1) % REPLYBOT_COOLDOWN_SLOTS; + return false; +} + +// Constructor – registers a single text port and marks the module promiscuous +// so that broadcast messages on the primary channel are visible. +ReplyBotModule::ReplyBotModule() : SinglePortModule("replybot", meshtastic_PortNum_TEXT_MESSAGE_APP) +{ + isPromiscuous = true; +} + +void ReplyBotModule::setup() +{ + // In future we may add a protobuf configuration; for now the module is + // always enabled when compiled in. +} + +// Determine whether we want to process this packet. We only care about +// plain text messages addressed to our port. +bool ReplyBotModule::wantPacket(const meshtastic_MeshPacket *p) +{ + return (p && p->decoded.portnum == ourPortNum); +} + +ProcessMessage ReplyBotModule::handleReceived(const meshtastic_MeshPacket &mp) +{ + // Accept only direct messages to us or broadcasts on the Primary channel + // (regardless of modem preset: LongFast, MediumFast, etc). + + const uint32_t ourNode = nodeDB->getNodeNum(); + const bool isDM = (mp.to == ourNode); + const bool isPrimaryChannel = (mp.channel == channels.getPrimaryIndex()) && isBroadcast(mp.to); + if (!isDM && !isPrimaryChannel) { + return ProcessMessage::CONTINUE; + } + + // Ignore empty payloads + if (mp.decoded.payload.size == 0) { + return ProcessMessage::CONTINUE; + } + + // Copy payload into a null‑terminated buffer + char buf[260]; + memset(buf, 0, sizeof(buf)); + size_t n = mp.decoded.payload.size; + if (n > sizeof(buf) - 1) + n = sizeof(buf) - 1; + memcpy(buf, mp.decoded.payload.bytes, n); + + // React only to supported slash commands + if (!isCommand(buf)) { + return ProcessMessage::CONTINUE; + } + + // Apply rate limiting per sender depending on DM/broadcast + const uint32_t cooldownMs = isDM ? REPLYBOT_DM_COOLDOWN_MS : REPLYBOT_LF_COOLDOWN_MS; + if (replybotRateLimited(mp.from, cooldownMs)) { + return ProcessMessage::CONTINUE; + } + + // Compute hop count indicator – if the relay_node is non‑zero we know + // there was at least one relay. Some firmware builds support a hop_start + // field which could be used for more accurate counts, but here we use + // the available relay_node flag only. + // int hopsAway = mp.hop_start - mp.hop_limit; + int hopsAway = getHopsAway(mp); + + // Normalize RSSI: if positive adjust down by 200 to align with typical values + int rssi = mp.rx_rssi; + if (rssi > 0) { + rssi -= 200; + } + float snr = mp.rx_snr; + + // Build the reply message and send it back via DM + char reply[96]; + snprintf(reply, sizeof(reply), "🎙️ Mic Check : %d Hops away | RSSI %d | SNR %.1f", hopsAway, rssi, snr); + sendDm(mp, reply); + return ProcessMessage::CONTINUE; +} + +// Check if the message starts with one of the supported commands. Leading +// whitespace is skipped and commands must be followed by end‑of‑string or +// whitespace. +bool ReplyBotModule::isCommand(const char *msg) const +{ + if (!msg) + return false; + while (*msg == ' ' || *msg == '\t') + msg++; + auto isEndOrSpace = [](char c) { return c == '\0' || std::isspace(static_cast(c)); }; + if (strncmp(msg, "/ping", 5) == 0 && isEndOrSpace(msg[5])) + return true; + if (strncmp(msg, "/hello", 6) == 0 && isEndOrSpace(msg[6])) + return true; + if (strncmp(msg, "/test", 5) == 0 && isEndOrSpace(msg[5])) + return true; + return false; +} + +// Send a direct message back to the originating node. +void ReplyBotModule::sendDm(const meshtastic_MeshPacket &rx, const char *text) +{ + if (!text) + return; + meshtastic_MeshPacket *p = allocDataPacket(); + p->to = rx.from; + p->channel = rx.channel; + p->want_ack = false; + p->decoded.want_response = false; + size_t len = strlen(text); + if (len > sizeof(p->decoded.payload.bytes)) { + len = sizeof(p->decoded.payload.bytes); + } + p->decoded.payload.size = len; + memcpy(p->decoded.payload.bytes, text, len); + service->sendToMesh(p); +} +#endif // MESHTASTIC_EXCLUDE_REPLYBOT \ No newline at end of file diff --git a/src/modules/ReplyBotModule.h b/src/modules/ReplyBotModule.h new file mode 100644 index 000000000..7413667ca --- /dev/null +++ b/src/modules/ReplyBotModule.h @@ -0,0 +1,18 @@ +#pragma once +#if !MESHTASTIC_EXCLUDE_REPLYBOT +#include "SinglePortModule.h" +#include "mesh/generated/meshtastic/mesh.pb.h" + +class ReplyBotModule : public SinglePortModule +{ + public: + ReplyBotModule(); + void setup() override; + bool wantPacket(const meshtastic_MeshPacket *p) override; + ProcessMessage handleReceived(const meshtastic_MeshPacket &mp) override; + + protected: + bool isCommand(const char *msg) const; + void sendDm(const meshtastic_MeshPacket &rx, const char *text); +}; +#endif // MESHTASTIC_EXCLUDE_REPLYBOT \ No newline at end of file From e65ce4d21c443ad8ea2546333dbaf697e14fd107 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Mon, 22 Sep 2025 09:28:25 +0200 Subject: [PATCH 99/99] ThinkNode G3, ETH support WIP --- src/DebugConfiguration.h | 9 ++++- src/main.cpp | 2 +- src/mesh/InterfacesTemplates.cpp | 2 +- src/mesh/api/ethServerAPI.cpp | 2 +- src/mesh/api/ethServerAPI.h | 2 +- src/mesh/wifi/WiFiAPClient.cpp | 8 ++--- src/modules/AdminModule.cpp | 2 +- src/mqtt/MQTT.h | 2 +- .../ELECROW-ThinkNode-G3/pins_arduino.h | 26 ++++++++++++++ .../ELECROW-ThinkNode-G3/platformio.ini | 22 ++++++++++++ .../esp32s3/ELECROW-ThinkNode-G3/variant.h | 36 +++++++++++++++++++ 11 files changed, 102 insertions(+), 11 deletions(-) create mode 100644 variants/esp32s3/ELECROW-ThinkNode-G3/pins_arduino.h create mode 100644 variants/esp32s3/ELECROW-ThinkNode-G3/platformio.ini create mode 100644 variants/esp32s3/ELECROW-ThinkNode-G3/variant.h diff --git a/src/DebugConfiguration.h b/src/DebugConfiguration.h index eac6260fc..a78dde78e 100644 --- a/src/DebugConfiguration.h +++ b/src/DebugConfiguration.h @@ -13,6 +13,11 @@ extern MemGet memGet; #define LED_STATE_ON 1 #endif +// WIFI LED +#ifndef WIFI_STATE_ON +#define WIFI_STATE_ON 1 +#endif + // ----------------------------------------------------------------------------- // DEBUG // ----------------------------------------------------------------------------- @@ -147,7 +152,9 @@ extern "C" void logLegacy(const char *level, const char *fmt, ...); // Default Bluetooth PIN #define defaultBLEPin 123456 -#if HAS_ETHERNET && !defined(USE_WS5500) +#if HAS_ETHERNET && defined(USE_CH390D) +#include +#elif HAS_ETHERNET && !defined(USE_WS5500) #include #endif // HAS_ETHERNET diff --git a/src/main.cpp b/src/main.cpp index 1e0ec041e..20f81bd9f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -361,7 +361,7 @@ void setup() #ifdef WIFI_LED pinMode(WIFI_LED, OUTPUT); - digitalWrite(WIFI_LED, LOW); + digitalWrite(WIFI_LED, HIGH ^ WIFI_STATE_ON); #endif #ifdef BLE_LED diff --git a/src/mesh/InterfacesTemplates.cpp b/src/mesh/InterfacesTemplates.cpp index 57abbf0ee..c246141dc 100644 --- a/src/mesh/InterfacesTemplates.cpp +++ b/src/mesh/InterfacesTemplates.cpp @@ -25,7 +25,7 @@ template class LR11x0Interface; template class SX126xInterface; #endif -#if HAS_ETHERNET && !defined(USE_WS5500) +#if HAS_ETHERNET && !defined(USE_WS5500) && !defined(USE_CH390D) #include "api/ethServerAPI.h" template class ServerAPI; template class APIServerPort; diff --git a/src/mesh/api/ethServerAPI.cpp b/src/mesh/api/ethServerAPI.cpp index 10ff06df2..892718e04 100644 --- a/src/mesh/api/ethServerAPI.cpp +++ b/src/mesh/api/ethServerAPI.cpp @@ -1,7 +1,7 @@ #include "configuration.h" #include -#if HAS_ETHERNET && !defined(USE_WS5500) +#if HAS_ETHERNET && !defined(USE_WS5500) && !defined(USE_CH390D) #include "ethServerAPI.h" diff --git a/src/mesh/api/ethServerAPI.h b/src/mesh/api/ethServerAPI.h index c616c87be..5667dea15 100644 --- a/src/mesh/api/ethServerAPI.h +++ b/src/mesh/api/ethServerAPI.h @@ -1,7 +1,7 @@ #pragma once #include "ServerAPI.h" -#ifndef USE_WS5500 +#if !defined(USE_WS5500) && !defined(USE_CH390D) #include /** diff --git a/src/mesh/wifi/WiFiAPClient.cpp b/src/mesh/wifi/WiFiAPClient.cpp index a95dfa58f..7bb5220e5 100644 --- a/src/mesh/wifi/WiFiAPClient.cpp +++ b/src/mesh/wifi/WiFiAPClient.cpp @@ -384,13 +384,13 @@ static void WiFiEvent(WiFiEvent_t event) #endif } #ifdef WIFI_LED - digitalWrite(WIFI_LED, HIGH); + digitalWrite(WIFI_LED, LOW ^ WIFI_STATE_ON); #endif break; case ARDUINO_EVENT_WIFI_STA_DISCONNECTED: LOG_INFO("Disconnected from WiFi access point"); #ifdef WIFI_LED - digitalWrite(WIFI_LED, LOW); + digitalWrite(WIFI_LED, HIGH ^ WIFI_STATE_ON); #endif if (!isReconnecting) { WiFi.disconnect(false, true); @@ -442,13 +442,13 @@ static void WiFiEvent(WiFiEvent_t event) case ARDUINO_EVENT_WIFI_AP_START: LOG_INFO("WiFi access point started"); #ifdef WIFI_LED - digitalWrite(WIFI_LED, HIGH); + digitalWrite(WIFI_LED, LOW ^ WIFI_STATE_ON); #endif break; case ARDUINO_EVENT_WIFI_AP_STOP: LOG_INFO("WiFi access point stopped"); #ifdef WIFI_LED - digitalWrite(WIFI_LED, LOW); + digitalWrite(WIFI_LED, HIGH ^ WIFI_STATE_ON); #endif break; case ARDUINO_EVENT_WIFI_AP_STACONNECTED: diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index 61dbc0b92..6d3d616ac 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -1274,7 +1274,7 @@ void AdminModule::handleGetDeviceConnectionStatus(const meshtastic_MeshPacket &r } #endif -#if HAS_ETHERNET && !defined(USE_WS5500) +#if HAS_ETHERNET && !defined(USE_WS5500) && !defined(USE_CH390D) conn.has_ethernet = true; conn.ethernet.has_status = true; if (Ethernet.linkStatus() == LinkON) { diff --git a/src/mqtt/MQTT.h b/src/mqtt/MQTT.h index 7d5715602..b1b143f04 100644 --- a/src/mqtt/MQTT.h +++ b/src/mqtt/MQTT.h @@ -15,7 +15,7 @@ #include #endif #endif -#if HAS_ETHERNET && !defined(USE_WS5500) +#if HAS_ETHERNET && !defined(USE_WS5500) && !defined(USE_CH390D) #include #endif diff --git a/variants/esp32s3/ELECROW-ThinkNode-G3/pins_arduino.h b/variants/esp32s3/ELECROW-ThinkNode-G3/pins_arduino.h new file mode 100644 index 000000000..359355772 --- /dev/null +++ b/variants/esp32s3/ELECROW-ThinkNode-G3/pins_arduino.h @@ -0,0 +1,26 @@ +#ifndef Pins_Arduino_h +#define Pins_Arduino_h + +#include + +#define USB_VID 0x303a +#define USB_PID 0x1001 + +// The default Wire will be mapped to PMU and RTC +static const uint8_t SDA = 17; +static const uint8_t SCL = 18; + +// Default SPI will be mapped to Radio +static const uint8_t SS = 39; +static const uint8_t MOSI = 40; +static const uint8_t MISO = 41; +static const uint8_t SCK = 42; + +// #define SPI_MOSI (11) +// #define SPI_SCK (10) +// #define SPI_MISO (9) +// #define SPI_CS (12) + +// #define SDCARD_CS SPI_CS + +#endif /* Pins_Arduino_h */ \ No newline at end of file diff --git a/variants/esp32s3/ELECROW-ThinkNode-G3/platformio.ini b/variants/esp32s3/ELECROW-ThinkNode-G3/platformio.ini new file mode 100644 index 000000000..6271b99ac --- /dev/null +++ b/variants/esp32s3/ELECROW-ThinkNode-G3/platformio.ini @@ -0,0 +1,22 @@ +[env:thinknode_g3] +extends = esp32s3_base +board = ESP32-S3-WROOM-1-N4 +board_build.psram_type = opi + +build_flags = + ${esp32s3_base.build_flags} + -D ELECROW_ThinkNode_G3 + -D HAS_UDP_MULTICAST=1 + -D BOARD_HAS_PSRAM + -D PRIVATE_HW +# -D CONFIG_ETH_ENABLED=1 +# -D CONFIG_ETH_USE_ESP32_EMAC=1 + -I variants/esp32s3/ELECROW-ThinkNode-G3 + -mfix-esp32-psram-cache-issue + +lib_ignore = + Ethernet + +lib_deps = + ${esp32s3_base.lib_deps} +# file://../ESP32-CH390/ESP32-CH390-1.0.1.tar.gz diff --git a/variants/esp32s3/ELECROW-ThinkNode-G3/variant.h b/variants/esp32s3/ELECROW-ThinkNode-G3/variant.h new file mode 100644 index 000000000..4ec352d23 --- /dev/null +++ b/variants/esp32s3/ELECROW-ThinkNode-G3/variant.h @@ -0,0 +1,36 @@ +#define HAS_GPS 0 +#define HAS_WIRE 0 +#define I2C_NO_RESCAN + +#define WIFI_LED 5 +#define WIFI_STATE_ON 0 + +#define LED_PIN 6 // The blue LORA LED +#define LED_STATE_ON 0 +#define BUTTON_PIN 4 // the external user button of the device, BOOT and RESET are not accessible without opening it up. + +#define USE_SX1262 +#define LORA_SCK 42 +#define LORA_MISO 41 +#define LORA_MOSI 40 +#define LORA_CS 39 +#define LORA_RESET 21 + +#define SX126X_CS LORA_CS +#define SX126X_DIO1 15 +#define SX126X_BUSY 47 +#define SX126X_RESET LORA_RESET +#define SX126X_DIO2_AS_RF_SWITCH +#define SX126X_DIO3_TCXO_VOLTAGE 1.8 +#define PIN_POWER_EN 45 + +// #define HAS_ETHERNET 1 +// #define USE_CH390D 1 // this driver uses the same stack as the ESP32 Wifi driver + +// #define ETH_MISO_PIN 47 +// #define ETH_MOSI_PIN 21 +// #define ETH_SCLK_PIN 48 +// #define ETH_CS_PIN 45 +// #define ETH_INT_PIN 14 +// #define ETH_RST_PIN -1 +// #define ETH_ADDR 1 \ No newline at end of file