From 80341326fe3363ea188a05650a8f86c0c54743da Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Mon, 26 Jan 2026 15:48:44 -0600 Subject: [PATCH] Add portduino_status, assign hardware device IDs, and try to recover a CH341 device on a USB error --- src/main.cpp | 35 ++++++++++++++- src/mesh/NodeDB.cpp | 7 ++- src/mesh/SX126xInterface.cpp | 16 ++++++- src/platform/portduino/PortduinoGlue.cpp | 9 ++++ 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, 123 insertions(+), 11 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 93cc89296..94c51d823 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1401,7 +1401,39 @@ 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 (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 +1441,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..64efcb550 100644 --- a/src/platform/portduino/PortduinoGlue.cpp +++ b/src/platform/portduino/PortduinoGlue.cpp @@ -29,6 +29,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 +401,10 @@ void portduinoSetup() if (found_hat) { product_config = cleanupNameForAutoconf("lora-hat-" + std::string(hat_vendor) + "-" + autoconf_product + ".yaml"); + if (strncmp(hat_vendor, "RAK", 14) == 0 && strncmp(autoconf_product, "6421 Pi Hat", 14) == 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 +413,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", 14) == 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..4952630b6 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 %d mode to %d: %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 %d: %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 %d: %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