Merge branch 'develop' into boot-status

This commit is contained in:
Jason P
2026-04-28 13:47:23 -05:00
committed by GitHub
32 changed files with 548 additions and 81 deletions
+10 -21
View File
@@ -781,8 +781,10 @@ void Power::reboot()
rp2040.reboot();
#elif defined(ARCH_PORTDUINO)
deInitApiServer();
#ifdef __linux__
if (aLinuxInputImpl)
aLinuxInputImpl->deInit();
#endif
SPI.end();
Wire.end();
Serial1.end();
@@ -1000,11 +1002,8 @@ int32_t Power::runOnce()
powerFSM.trigger(EVENT_POWER_CONNECTED);
}
#ifdef T_WATCH_S3
/*
In the T-Watch S3 this code fragment reacts to the short press of the button by switching the
display on and off
*/
#ifdef PMU_POWER_BUTTON_IS_CANCEL
// cancel action also turns the screen on and off.
if (PMU->isPekeyShortPressIrq()) {
LOG_INFO("Input: Corona Button Click");
InputEvent event = {.inputEvent = (input_broker_event)INPUT_BROKER_CANCEL, .kbchar = 0, .touchX = 0, .touchY = 0};
@@ -1027,13 +1026,6 @@ int32_t Power::runOnce()
LOG_DEBUG("Battery removed");
}
*/
#ifndef T_WATCH_S3 // FIXME - why is this triggering on the T-Watch S3?
if (PMU->isPekeyLongPressIrq()) {
LOG_DEBUG("PEK long button press");
if (screen)
screen->setOn(false);
}
#endif
PMU->clearIrqStatus();
}
@@ -1102,7 +1094,7 @@ void Power::attachPowerInterrupts()
if (PMU) {
attachInterrupt(
PMU_IRQ,
[] {
[]() {
pmu_irq = true;
power->setIntervalFromNow(0);
runASAP = true;
@@ -1405,19 +1397,16 @@ bool Power::axpChipInit()
uint64_t pmuIrqMask = 0;
if (PMU->getChipModel() == XPOWERS_AXP192) {
pmuIrqMask = XPOWERS_AXP192_VBUS_INSERT_IRQ | XPOWERS_AXP192_BAT_INSERT_IRQ | XPOWERS_AXP192_PKEY_SHORT_IRQ;
pmuIrqMask = XPOWERS_AXP192_VBUS_INSERT_IRQ | XPOWERS_AXP192_VBUS_REMOVE_IRQ | XPOWERS_AXP192_PKEY_SHORT_IRQ;
} else if (PMU->getChipModel() == XPOWERS_AXP2101) {
pmuIrqMask = XPOWERS_AXP2101_VBUS_INSERT_IRQ | XPOWERS_AXP2101_BAT_INSERT_IRQ | XPOWERS_AXP2101_PKEY_SHORT_IRQ;
pmuIrqMask = XPOWERS_AXP2101_VBUS_INSERT_IRQ | XPOWERS_AXP2101_VBUS_REMOVE_IRQ | XPOWERS_AXP2101_PKEY_SHORT_IRQ;
}
pinMode(PMU_IRQ, INPUT);
// 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 wake on IRQ, so only enable the IRQs that we care about.
// we want USB plug and unplug to update the screen and LED status,
// and short press on the power button to trigger the "cancel" action in the UI (which also turns the screen on and off).
PMU->enableIRQ(pmuIrqMask);
PMU->clearIrqStatus();
+1 -1
View File
@@ -1,8 +1,8 @@
#pragma once
#include "../freertosinc.h"
#include "Print.h"
#include "mesh/generated/meshtastic/mesh.pb.h"
#include <Print.h>
#include <stdarg.h>
#include <string>
+3
View File
@@ -162,6 +162,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#elif defined(HELTEC_MESH_NODE_T096)
#define NUM_PA_POINTS 22
#define TX_GAIN_LORA 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 13, 13, 13, 12, 11, 10, 9, 8, 7
#elif defined(HELTEC_V4_R8)
#define NUM_PA_POINTS 22
#define TX_GAIN_LORA 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 12, 12, 11, 11, 10, 9, 8, 7
#else
// If a board enables USE_KCT8103L_PA but does not match a known variant and has
// not already provided a PA curve, fail at compile time to avoid unsafe defaults.
+11 -6
View File
@@ -70,20 +70,25 @@ bool GPSUpdateScheduling::isUpdateDue()
// Have we been searching for a GPS position for too long?
bool GPSUpdateScheduling::searchedTooLong()
{
constexpr uint32_t oneMinuteMs = 60UL * 1000UL;
constexpr uint32_t maxSearchClampMs = 15UL * oneMinuteMs; // Hard cap: 15 minutes is always too long
uint32_t elapsed = elapsedSearchMs();
// Anything over 15 minutes is too long, regardless of the broadcast interval.
// TODO: Make a smarter algorithm that backs off the search dwell time when not getting a lock.
if (elapsed > maxSearchClampMs)
return true;
uint32_t minimumOrConfiguredSecs =
Default::getConfiguredOrMinimumValue(config.position.position_broadcast_secs, default_broadcast_interval_secs);
uint32_t maxSearchMs = Default::getConfiguredOrDefaultMs(minimumOrConfiguredSecs, default_broadcast_interval_secs);
// If broadcast interval set to max, no such thing as "too long"
if (maxSearchMs == UINT32_MAX)
return false;
// If we've been searching longer than our position broadcast interval: that's too long
else if (elapsedSearchMs() > maxSearchMs)
if (elapsed > maxSearchMs)
return true;
// Otherwise, not too long yet!
else
return false;
return false;
}
// Updates the predicted time-to-get-lock, by exponentially smoothing the latest observation
+15 -4
View File
@@ -422,7 +422,7 @@ static LGFX *tft = nullptr;
#elif defined(ST7789_CS)
#include <LovyanGFX.hpp> // Graphics and font library for ST7735 driver chip
#ifdef HELTEC_V4_TFT
#if defined(HELTEC_V4_TFT) || defined(HELTEC_V4_R8_TFT)
#include "chsc6x.h"
#include "lgfx/v1/Touch.hpp"
namespace lgfx
@@ -444,7 +444,11 @@ class TOUCH_CHSC6X : public ITouch
bool init(void) override
{
if (chsc6xTouch == nullptr) {
#if (TOUCH_I2C_PORT == 1)
chsc6xTouch = new chsc6x(&Wire1, TOUCH_SDA_PIN, TOUCH_SCL_PIN, TOUCH_INT_PIN, TOUCH_RST_PIN);
#else
chsc6xTouch = new chsc6x(&Wire, TOUCH_SDA_PIN, TOUCH_SCL_PIN, TOUCH_INT_PIN, TOUCH_RST_PIN);
#endif
}
chsc6xTouch->chsc6x_init();
return true;
@@ -481,7 +485,7 @@ class LGFX : public lgfx::LGFX_Device
#if HAS_TOUCHSCREEN
#if defined(T_WATCH_S3) || defined(ELECROW)
lgfx::Touch_FT5x06 _touch_instance;
#elif defined(HELTEC_V4_TFT)
#elif defined(HELTEC_V4_TFT) || defined(HELTEC_V4_R8_TFT)
lgfx::TOUCH_CHSC6X _touch_instance;
#else
lgfx::Touch_GT911 _touch_instance;
@@ -500,7 +504,11 @@ class LGFX : public lgfx::LGFX_Device
cfg.freq_write = SPI_FREQUENCY; // SPI clock for transmission (up to 80MHz, rounded to the value obtained by dividing
// 80MHz by an integer)
cfg.freq_read = SPI_READ_FREQUENCY; // SPI clock when receiving
cfg.spi_3wire = false;
#ifdef SPI_3_WIRE
cfg.spi_3wire = SPI_3_WIRE;
#else
cfg.spi_3wire = true; // Set to true if reception is done on the MOSI pin
#endif
cfg.use_lock = true; // Set to true to use transaction locking
cfg.dma_channel = SPI_DMA_CH_AUTO; // SPI_DMA_CH_AUTO; // Set DMA channel to use (0=not use DMA / 1=1ch / 2=ch /
// SPI_DMA_CH_AUTO=auto setting)
@@ -550,8 +558,11 @@ class LGFX : public lgfx::LGFX_Device
cfg.rgb_order = false; // Set to true if the panel's red and blue are swapped
cfg.dlen_16bit =
false; // Set to true for panels that transmit data length in 16-bit units with 16-bit parallel or SPI
#if defined(HAS_SDCARD)
cfg.bus_shared = true; // If the bus is shared with the SD card, set to true (bus control with drawJpgFile etc.)
#else
cfg.bus_shared = false;
#endif
// Set the following only when the display is shifted with a driver with a variable number of pixels, such as the
// ST7735 or ILI9163.
// cfg.memory_width = TFT_WIDTH; // Maximum width supported by the driver IC
+3
View File
@@ -390,8 +390,11 @@ void InputBroker::Init()
seesawRotary = nullptr;
}
}
#ifdef __linux__
// Linux evdev keyboard input only — macOS has no <linux/input.h>.
aLinuxInputImpl = new LinuxInputImpl();
aLinuxInputImpl->init();
#endif
}
#endif
#if !MESHTASTIC_EXCLUDE_INPUTBROKER && HAS_TRACKBALL
+4 -1
View File
@@ -1,5 +1,8 @@
#pragma once
#if ARCH_PORTDUINO
// Linux evdev keyboard input. Only compiled on Linux portduino targets;
// macOS / non-Linux builds have no <linux/input.h> or epoll, and the
// headless build doesn't need real keyboards anyway.
#if ARCH_PORTDUINO && defined(__linux__)
#include "InputBroker.h"
#include "concurrency/OSThread.h"
#include <assert.h>
+2 -1
View File
@@ -1,4 +1,5 @@
#ifdef ARCH_PORTDUINO
// Linux evdev impl. Same Linux-only gating as LinuxInput.h.
#if defined(ARCH_PORTDUINO) && defined(__linux__)
#pragma once
#include "LinuxInput.h"
#include "main.h"
+11 -1
View File
@@ -19,8 +19,12 @@ extern Adafruit_nRFCrypto nRFCrypto;
#include <Arduino.h>
#elif defined(ARCH_PORTDUINO)
#include <random>
#include <sys/random.h>
#include <unistd.h>
#ifdef __linux__
#include <sys/random.h> // getrandom()
#else
#include <stdlib.h> // arc4random_buf() on Darwin/BSD
#endif
#endif
namespace HardwareRNG
@@ -119,10 +123,16 @@ bool fill(uint8_t *buffer, size_t length, bool useRadioEntropy)
filled = true;
#elif defined(ARCH_PORTDUINO)
// Prefer the host OS RNG first when running under Portduino.
#ifdef __linux__
ssize_t generated = ::getrandom(buffer, length, 0);
if (generated == static_cast<ssize_t>(length)) {
filled = true;
}
#else
// arc4random_buf is available on Darwin/BSD and cannot fail.
::arc4random_buf(buffer, length);
filled = true;
#endif
if (!filled) {
fillWithRandomDevice(buffer, length);
+5 -2
View File
@@ -6,8 +6,11 @@
#include "configuration.h"
#include "detect/LoRaRadioType.h"
// Sentinel marking the end of a modem preset array
static constexpr meshtastic_Config_LoRaConfig_ModemPreset MODEM_PRESET_END =
// Sentinel marking the end of a modem preset array. Declared `const` rather
// than `constexpr` because the cast from 0xFF to the enum is out-of-range and
// therefore not a valid constant expression on Clang 16+ (Apple Clang on
// macOS). The value is only ever compared at runtime, so static-init is fine.
static const meshtastic_Config_LoRaConfig_ModemPreset MODEM_PRESET_END =
static_cast<meshtastic_Config_LoRaConfig_ModemPreset>(0xFF);
// Region profile: bundles the preset list with regulatory parameters shared across regions
+1 -1
View File
@@ -688,7 +688,7 @@ void NodeDB::installDefaultConfig(bool preserveKey = false)
strncpy(config.network.ntp_server, "meshtastic.pool.ntp.org", 32);
#if (defined(T_DECK) || defined(T_WATCH_S3) || defined(UNPHONE) || defined(PICOMPUTER_S3) || defined(SENSECAP_INDICATOR) || \
defined(ELECROW_PANEL) || defined(HELTEC_V4_TFT)) && \
defined(ELECROW_PANEL) || defined(HELTEC_V4_TFT) || defined(HELTEC_V4_R8_TFT)) && \
HAS_TFT
// switch BT off by default; use TFT programming mode or hotkey to enable
config.bluetooth.enabled = false;
+1 -1
View File
@@ -119,7 +119,7 @@ bool RadioLibInterface::canSendImmediately()
return true;
}
bool RadioLibInterface::receiveDetected(uint16_t irq, ulong syncWordHeaderValidFlag, ulong preambleDetectedFlag)
bool RadioLibInterface::receiveDetected(uint16_t irq, unsigned long syncWordHeaderValidFlag, unsigned long preambleDetectedFlag)
{
bool detected = (irq & (syncWordHeaderValidFlag | preambleDetectedFlag));
// Handle false detections
+1 -1
View File
@@ -220,7 +220,7 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified
protected:
uint32_t activeReceiveStart = 0;
bool receiveDetected(uint16_t irq, ulong syncWordHeaderValidFlag, ulong preambleDetectedFlag);
bool receiveDetected(uint16_t irq, unsigned long syncWordHeaderValidFlag, unsigned long preambleDetectedFlag);
/** Do any hardware setup needed on entry into send configuration for the radio.
* Subclasses can customize, but must also call this base method */
+7 -4
View File
@@ -736,9 +736,13 @@ void Router::handleReceived(meshtastic_MeshPacket *p, RxSource src)
// Also, we should set the time from the ISR and it should have msec level resolution
p->rx_time = getValidTime(RTCQualityFromNet); // store the arrival timestamp for the phone
// Store a copy of encrypted packet for MQTT
// Store a copy of the encrypted packet for MQTT.
// Local, not a class member: handleReceived re-enters itself when a module
// reply broadcast goes through MeshService::sendToMesh -> Router::sendLocal,
// and a member would be silently overwritten without release on the inner
// call. Each invocation now owns its own copy (issue #9632, #10101, #8729).
DEBUG_HEAP_BEFORE;
p_encrypted = packetPool.allocCopy(*p);
meshtastic_MeshPacket *p_encrypted = packetPool.allocCopy(*p);
DEBUG_HEAP_AFTER("Router::handleReceived", p_encrypted);
// Take those raw bytes and convert them back into a well structured protobuf we can understand
@@ -832,8 +836,7 @@ void Router::handleReceived(meshtastic_MeshPacket *p, RxSource src)
#endif
}
packetPool.release(p_encrypted); // Release the encrypted packet
p_encrypted = nullptr;
packetPool.release(p_encrypted); // Release the encrypted packet (release() handles nullptr)
}
void Router::perhapsHandleReceived(meshtastic_MeshPacket *p)
-3
View File
@@ -92,9 +92,6 @@ class Router : protected concurrency::OSThread, protected PacketHistory
before us */
uint32_t rxDupe = 0, txRelayCanceled = 0;
// pointer to the encrypted packet
meshtastic_MeshPacket *p_encrypted = nullptr;
protected:
friend class RoutingModule;
+1 -1
View File
@@ -32,7 +32,7 @@
#include <assert.h>
#include <utility>
#include <IPAddress.h>
#include "IPAddress.h"
#if defined(ARCH_PORTDUINO)
#include <netinet/in.h>
#elif !defined(ntohl)
+22 -7
View File
@@ -9,13 +9,10 @@
#include "PortduinoGlue.h"
#include "SHA256.h"
#include "api/ServerAPI.h"
#include "linux/gpio/LinuxGPIOPin.h"
#include "meshUtils.h"
#include <ErriezCRC32.h>
#include <Utility.h>
#include <assert.h>
#include <bluetooth/bluetooth.h>
#include <bluetooth/hci.h>
#include <filesystem>
#include <fstream>
#include <iostream>
@@ -25,6 +22,12 @@
#include <sys/ioctl.h>
#include <unistd.h>
#ifdef PORTDUINO_LINUX_HARDWARE
#include "linux/gpio/LinuxGPIOPin.h"
#include <bluetooth/bluetooth.h>
#include <bluetooth/hci.h>
#endif
#ifdef PORTDUINO_LINUX_HARDWARE
#include <cxxabi.h>
#endif
@@ -130,9 +133,9 @@ void getMacAddr(uint8_t *dmac)
}
} else if (portduino_config.mac_address.length() > 11) {
MAC_from_string(portduino_config.mac_address, dmac);
exit;
return;
} else {
#ifdef PORTDUINO_LINUX_HARDWARE
struct hci_dev_info di = {0};
di.dev_id = 0;
bdaddr_t bdaddr;
@@ -152,6 +155,11 @@ void getMacAddr(uint8_t *dmac)
dmac[3] = di.bdaddr.b[2];
dmac[4] = di.bdaddr.b[1];
dmac[5] = di.bdaddr.b[0];
#else
// No BlueZ on non-Linux hosts (e.g. macOS). Leave dmac at its default;
// the caller can override via the --hwid CLI flag or the YAML config.
(void)dmac;
#endif
}
}
@@ -486,10 +494,17 @@ void portduinoSetup()
exit(EXIT_FAILURE);
}
char serial[9] = {0};
ch341Hal->getSerialString(serial, 8);
// Pass the full buffer size (9 = 8 chars + null) to getSerialString,
// not 8. The function treats `len` as buffer size and reserves one
// slot for the null terminator, so passing 8 produced a 7-char serial
// and broke the `strlen(serial) == 8` check below — masked on Linux
// by the BlueZ HCI MAC fallback in getMacAddr(), but on macOS (where
// the BlueZ path is __linux__-guarded) it left mac_address empty and
// meshtasticd refused to start.
ch341Hal->getSerialString(serial, sizeof(serial));
std::cout << "CH341 Serial " << serial << std::endl;
char product_string[96] = {0};
ch341Hal->getProductString(product_string, 95);
ch341Hal->getProductString(product_string, sizeof(product_string));
std::cout << "CH341 Product " << product_string << std::endl;
if (strlen(serial) == 8 && portduino_config.mac_address.length() < 12) {
std::cout << "Deriving MAC address from Serial and Product String" << std::endl;
+7 -3
View File
@@ -5,6 +5,7 @@
#include "platform/portduino/PortduinoGlue.h"
#include <RadioLib.h>
#include <csignal>
#include <cstring>
#include <iostream>
#include <libpinedio-usb.h>
#include <unistd.h>
@@ -34,7 +35,7 @@ class Ch341Hal : public RadioLibHal
: RadioLibHal(PI_INPUT, PI_OUTPUT, PI_LOW, PI_HIGH, PI_RISING, PI_FALLING)
{
if (serial != "") {
strncpy(pinedio.serial_number, serial.c_str(), 8);
std::strncpy(pinedio.serial_number, serial.c_str(), 8);
pinedio_set_option(&pinedio, PINEDIO_OPTION_SEARCH_SERIAL, 1);
}
// LOG_INFO("USB Serial: %s", pinedio.serial_number);
@@ -59,8 +60,11 @@ class Ch341Hal : public RadioLibHal
void getSerialString(char *_serial, size_t len)
{
len = len > 8 ? 8 : len;
strncpy(_serial, pinedio.serial_number, len);
if (len == 0)
return;
size_t bytesCopied = (len - 1) < 8 ? (len - 1) : 8;
std::strncpy(_serial, pinedio.serial_number, bytesCopied);
_serial[bytesCopied] = '\0';
}
void getProductString(char *_product_string, size_t len)