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
This commit is contained in:
Jonathan Bennett
2026-01-27 18:00:20 -06:00
committed by GitHub
parent fd498bebad
commit 69a42e1fd2
8 changed files with 129 additions and 11 deletions

View File

@@ -1401,7 +1401,43 @@ void loop()
if (inputBroker) if (inputBroker)
inputBroker->processInputEventQueue(); inputBroker->processInputEventQueue();
#endif #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 && if (screen && portduino_config.displayPanel == x11 &&
config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) { config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
auto dispdev = screen->getDisplayDevice(); auto dispdev = screen->getDisplayDevice();
@@ -1409,6 +1445,7 @@ void loop()
static_cast<TFTDisplay *>(dispdev)->sdlLoop(); static_cast<TFTDisplay *>(dispdev)->sdlLoop();
} }
#endif #endif
#endif
#if HAS_SCREEN && ENABLE_MESSAGE_PERSISTENCE #if HAS_SCREEN && ENABLE_MESSAGE_PERSISTENCE
messageStoreAutosaveTick(); messageStoreAutosaveTick();
#endif #endif

View File

@@ -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 // Currently portuino is mostly used for simulation. Make sure the user notices something really bad happened
#ifdef ARCH_PORTDUINO #ifdef ARCH_PORTDUINO
LOG_ERROR("A critical failure occurred, portduino is exiting"); 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); exit(2);
#endif #endif
} }

View File

@@ -269,8 +269,12 @@ template <typename T> void SX126xInterface<T>::setStandby()
if (err != RADIOLIB_ERR_NONE) if (err != RADIOLIB_ERR_NONE)
LOG_DEBUG("SX126x standby %s%d", radioLibErr, err); 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); assert(err == RADIOLIB_ERR_NONE);
#endif
isReceiving = false; // If we were receiving, not any more isReceiving = false; // If we were receiving, not any more
activeReceiveStart = 0; activeReceiveStart = 0;
disableInterrupt(); disableInterrupt();
@@ -313,7 +317,12 @@ template <typename T> void SX126xInterface<T>::startReceive()
int err = lora.startReceiveDutyCycleAuto(preambleLength, 8, MESHTASTIC_RADIOLIB_IRQ_RX_FLAGS); int err = lora.startReceiveDutyCycleAuto(preambleLength, 8, MESHTASTIC_RADIOLIB_IRQ_RX_FLAGS);
if (err != RADIOLIB_ERR_NONE) if (err != RADIOLIB_ERR_NONE)
LOG_ERROR("SX126X startReceiveDutyCycleAuto %s%d", radioLibErr, err); 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); assert(err == RADIOLIB_ERR_NONE);
#endif
RadioLibInterface::startReceive(); RadioLibInterface::startReceive();
@@ -341,7 +350,12 @@ template <typename T> bool SX126xInterface<T>::isChannelActive()
return true; return true;
if (result != RADIOLIB_CHANNEL_FREE) if (result != RADIOLIB_CHANNEL_FREE)
LOG_ERROR("SX126X scanChannel %s%d", radioLibErr, result); 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); assert(result != RADIOLIB_ERR_WRONG_MODEM);
#endif
return false; return false;
} }

View File

@@ -19,6 +19,7 @@
#include <fstream> #include <fstream>
#include <iostream> #include <iostream>
#include <map> #include <map>
#include <stdexcept>
#include <sys/ioctl.h> #include <sys/ioctl.h>
#include <unistd.h> #include <unistd.h>
@@ -29,6 +30,7 @@
#include "platform/portduino/USBHal.h" #include "platform/portduino/USBHal.h"
portduino_config_struct portduino_config; portduino_config_struct portduino_config;
portduino_status_struct portduino_status;
std::ofstream traceFile; std::ofstream traceFile;
std::ofstream JSONFile; std::ofstream JSONFile;
Ch341Hal *ch341Hal = nullptr; Ch341Hal *ch341Hal = nullptr;
@@ -400,6 +402,11 @@ void portduinoSetup()
if (found_hat) { if (found_hat) {
product_config = product_config =
cleanupNameForAutoconf("lora-hat-" + std::string(hat_vendor) + "-" + autoconf_product + ".yaml"); 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) { } else if (found_ch341) {
product_config = cleanupNameForAutoconf("lora-usb-" + std::string(autoconf_product) + ".yaml"); product_config = cleanupNameForAutoconf("lora-usb-" + std::string(autoconf_product) + ".yaml");
// look for more data after the null terminator // look for more data after the null terminator
@@ -408,6 +415,10 @@ void portduinoSetup()
memcpy(portduino_config.device_id, autoconf_product + len + 1, 16); 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)) { if (!memfll(portduino_config.device_id, '\0', 16) && !memfll(portduino_config.device_id, 0xff, 16)) {
portduino_config.has_device_id = true; 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;
}
} }
} }
} }

View File

@@ -1,15 +1,22 @@
#pragma once #pragma once
#include <fstream> #include <fstream>
#include <map> #include <map>
#include <unistd.h>
#include <unordered_map> #include <unordered_map>
#include <vector> #include <vector>
#include "LR11x0Interface.h" #include "LR11x0Interface.h"
#include "Module.h" #include "Module.h"
#include "mesh/generated/meshtastic/mesh.pb.h" #include "mesh/generated/meshtastic/mesh.pb.h"
#include "platform/portduino/USBHal.h"
#include "yaml-cpp/yaml.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 strings for auto-configuration
// {"PRODUCT_STRING", "CONFIG.YAML"} // {"PRODUCT_STRING", "CONFIG.YAML"}
// YAML paths are relative to `meshtastic/available.d` // YAML paths are relative to `meshtastic/available.d`

View File

@@ -9,6 +9,8 @@
#include <libpinedio-usb.h> #include <libpinedio-usb.h>
#include <unistd.h> #include <unistd.h>
extern uint32_t rebootAtMsec;
// include the library for Raspberry GPIO pins // include the library for Raspberry GPIO pins
#define PI_RISING (PINEDIO_INT_MODE_RISING) #define PI_RISING (PINEDIO_INT_MODE_RISING)
@@ -45,7 +47,7 @@ class Ch341Hal : public RadioLibHal
int32_t ret = pinedio_init(&pinedio, NULL); int32_t ret = pinedio_init(&pinedio, NULL);
if (ret != 0) { if (ret != 0) {
std::string s = "Could not open SPI: "; 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); 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 // RADIOLIB_NC as an alias for non-connected pins
void pinMode(uint32_t pin, uint32_t mode) override void pinMode(uint32_t pin, uint32_t mode) override
{ {
if (checkError()) {
return;
}
if (pin == RADIOLIB_NC) { if (pin == RADIOLIB_NC) {
return; 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 void digitalWrite(uint32_t pin, uint32_t value) override
{ {
if (checkError()) {
return;
}
if (pin == RADIOLIB_NC) { if (pin == RADIOLIB_NC) {
return; 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 uint32_t digitalRead(uint32_t pin) override
{ {
if (checkError()) {
return 0;
}
if (pin == RADIOLIB_NC) { if (pin == RADIOLIB_NC) {
return 0; 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 void attachInterrupt(uint32_t interruptNum, void (*interruptCb)(void), uint32_t mode) override
{ {
if (checkError()) {
return;
}
if (interruptNum == RADIOLIB_NC) { if (interruptNum == RADIOLIB_NC) {
return; return;
} }
@@ -107,6 +134,9 @@ class Ch341Hal : public RadioLibHal
void detachInterrupt(uint32_t interruptNum) override void detachInterrupt(uint32_t interruptNum) override
{ {
if (checkError()) {
return;
}
if (interruptNum == RADIOLIB_NC) { if (interruptNum == RADIOLIB_NC) {
return; return;
} }
@@ -152,6 +182,9 @@ class Ch341Hal : public RadioLibHal
void spiTransfer(uint8_t *out, size_t len, uint8_t *in) void spiTransfer(uint8_t *out, size_t len, uint8_t *in)
{ {
if (checkError()) {
return;
}
int32_t ret = pinedio_transceive(&this->pinedio, out, in, len); int32_t ret = pinedio_transceive(&this->pinedio, out, in, len);
if (ret < 0) { if (ret < 0) {
std::cerr << "Could not perform SPI transfer: " << ret << std::endl; std::cerr << "Could not perform SPI transfer: " << ret << std::endl;
@@ -160,9 +193,22 @@ class Ch341Hal : public RadioLibHal
void spiEndTransaction() {} void spiEndTransaction() {}
void spiEnd() {} 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: private:
pinedio_inst pinedio = {0}; pinedio_inst pinedio = {0};
bool has_warned = false;
}; };
#endif #endif

View File

@@ -6,7 +6,7 @@
// set HW_VENDOR // set HW_VENDOR
// //
#define HW_VENDOR meshtastic_HardwareModel_PORTDUINO #define HW_VENDOR portduino_status.hardwareModel
#ifndef HAS_BUTTON #ifndef HAS_BUTTON
#define HAS_BUTTON 1 #define HAS_BUTTON 1

View File

@@ -29,7 +29,7 @@ lib_deps =
# renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX # renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX
lovyan03/LovyanGFX@1.2.7 lovyan03/LovyanGFX@1.2.7
# renovate: datasource=git-refs depName=libch341-spi-userspace packageName=https://github.com/pine64/libch341-spi-userspace gitBranch=main # renovate: datasource=git-refs depName=libch341-spi-userspace packageName=https://github.com/pine64/libch341-spi-userspace gitBranch=main
https://github.com/pine64/libch341-spi-userspace/archive/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 # renovate: datasource=custom.pio depName=adafruit/Adafruit seesaw Library packageName=adafruit/library/Adafruit seesaw Library
adafruit/Adafruit seesaw Library@1.7.9 adafruit/Adafruit seesaw Library@1.7.9
# renovate: datasource=git-refs depName=RAK12034-BMX160 packageName=https://github.com/RAKWireless/RAK12034-BMX160 gitBranch=main # renovate: datasource=git-refs depName=RAK12034-BMX160 packageName=https://github.com/RAKWireless/RAK12034-BMX160 gitBranch=main