Compare commits

..
217 changed files with 3593 additions and 5674 deletions
+1 -1
View File
@@ -18,7 +18,7 @@ ENV PIP_ROOT_USER_ACTION=ignore
# trunk-ignore(hadolint/DL3008): apt packages are not pinned.
# trunk-ignore(terrascan/AC_DOCKER_0002): apt packages are not pinned.
RUN apt-get update && apt-get install --no-install-recommends -y \
cmake git zip libgpiod-dev libjsoncpp-dev libbluetooth-dev libi2c-dev \
cmake git zip libgpiod-dev libbluetooth-dev libi2c-dev \
libunistring-dev libmicrohttpd-dev libgnutls28-dev libgcrypt20-dev \
libusb-1.0-0-dev libssl-dev pkg-config libsqlite3-dev libsdl2-dev && \
apt-get clean && rm -rf /var/lib/apt/lists/* && \
+1 -1
View File
@@ -31,7 +31,7 @@ cmake --install "$WORK/ulfius/$SANITIZER" --prefix /usr
cd "$SRC/firmware"
PLATFORMIO_EXTRA_SCRIPTS=$(echo -e "pre:.clusterfuzzlite/platformio-clusterfuzzlite-pre.py\npost:.clusterfuzzlite/platformio-clusterfuzzlite-post.py")
STATIC_LIBS=$(pkg-config --libs --static libulfius openssl libgpiod yaml-cpp jsoncpp bluez --silence-errors)
STATIC_LIBS=$(pkg-config --libs --static libulfius openssl libgpiod yaml-cpp bluez --silence-errors)
export PLATFORMIO_EXTRA_SCRIPTS
export STATIC_LIBS
export PLATFORMIO_WORKSPACE_DIR="$WORK/pio/$SANITIZER"
-1
View File
@@ -16,7 +16,6 @@ RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
libssl-dev \
libulfius-dev \
libyaml-cpp-dev \
libjsoncpp-dev \
pipx \
pkg-config \
python3 \
+1 -1
View File
@@ -13,7 +13,7 @@ runs:
shell: bash
run: |
sudo apt-get -y update --fix-missing
sudo apt-get install -y cppcheck libbluetooth-dev libgpiod-dev libyaml-cpp-dev libjsoncpp-dev lsb-release
sudo apt-get install -y cppcheck libbluetooth-dev libgpiod-dev libyaml-cpp-dev lsb-release
- name: Setup Python
uses: actions/setup-python@v6
+1 -1
View File
@@ -11,4 +11,4 @@ runs:
- name: Install libs needed for native build
shell: bash
run: |
sudo apt-get install -y libbluetooth-dev libgpiod-dev libyaml-cpp-dev libjsoncpp-dev openssl libssl-dev libulfius-dev liborcania-dev libusb-1.0-0-dev libi2c-dev libuv1-dev
sudo apt-get install -y libbluetooth-dev libgpiod-dev libyaml-cpp-dev openssl libssl-dev libulfius-dev liborcania-dev libusb-1.0-0-dev libi2c-dev libuv1-dev
+27 -164
View File
@@ -4,11 +4,11 @@ This document provides context and guidelines for AI assistants working with the
## Project Overview
Meshtastic is an open-source LoRa mesh networking project for long-range, low-power communication without relying on internet or cellular infrastructure. The firmware enables text messaging, location sharing, and telemetry over a decentralized mesh network. The project uses **C++17** as its language standard across all platforms.
Meshtastic is an open-source LoRa mesh networking project for long-range, low-power communication without relying on internet or cellular infrastructure. The firmware enables text messaging, location sharing, and telemetry over a decentralized mesh network.
### Supported Hardware Platforms
- **ESP32** (ESP32, ESP32-S3, ESP32-C3, ESP32-C6) - Most common platform
- **ESP32** (ESP32, ESP32-S3, ESP32-C3) - Most common platform
- **nRF52** (nRF52840, nRF52833) - Low power Nordic chips
- **RP2040/RP2350** - Raspberry Pi Pico variants
- **STM32WL** - STM32 with integrated LoRa
@@ -80,46 +80,21 @@ firmware/
│ │ ├── NodeDB.* # Node database management
│ │ ├── Router.* # Packet routing
│ │ ├── Channels.* # Channel management
│ │ ├── CryptoEngine.* # AES-CCM encryption
│ │ ├── *Interface.* # Radio interface implementations
│ │ ├── api/ # WiFi/Ethernet server APIs (ServerAPI, PacketAPI)
│ │ ├── http/ # HTTP server (WebServer, ContentHandler)
│ │ ├── wifi/ # WiFi support (WiFiAPClient)
│ │ ├── eth/ # Ethernet support (ethClient)
│ │ ├── udp/ # UDP multicast
│ │ ├── compression/ # Message compression (unishox2)
│ │ └── generated/ # Protobuf generated code
│ ├── modules/ # Feature modules (Position, Telemetry, etc.)
│ │ └── Telemetry/ # Telemetry subsystem
│ │ └── Sensor/ # 50+ I2C sensor drivers
│ ├── gps/ # GPS handling
│ ├── graphics/ # Display drivers and UI
│ │ └── niche/ # Specialized UIs (InkHUD e-ink framework)
│ ├── platform/ # Platform-specific code (esp32, nrf52, rp2xx0, stm32wl, portduino)
│ ├── input/ # Input device handling (InputBroker, keyboards, buttons)
│ ├── detect/ # I2C hardware auto-detection (80+ device types)
│ ├── motion/ # Accelerometer drivers (BMA423, BMI270, MPU6050, etc.)
│ ├── mqtt/ # MQTT bridge client
│ ├── power/ # Power HAL
│ ├── nimble/ # BLE via NimBLE
│ ├── buzz/ # Audio/notification (buzzer, RTTTL)
│ ├── serialization/ # JSON serialization, COBS encoding
│ ├── watchdog/ # Hardware watchdog thread
│ ├── concurrency/ # Threading utilities (OSThread, Lock)
│ ├── PowerFSM.* # Power finite state machine
│ └── Observer.h # Observer/Observable event pattern
│ ├── platform/ # Platform-specific code
│ ├── input/ # Input device handling
│ └── concurrency/ # Threading utilities
├── variants/ # Hardware variant definitions
│ ├── esp32/ # ESP32 variants
│ ├── esp32s3/ # ESP32-S3 variants
│ ├── esp32c3/ # ESP32-C3 variants
│ ├── esp32c6/ # ESP32-C6 variants
│ ├── nrf52840/ # nRF52 variants
│ ├── rp2040/ # RP2040/RP2350 variants
│ ├── stm32/ # STM32WL variants
│ └── native/ # Linux/Portduino variants
│ ├── nrf52/ # nRF52 variants
│ └── rp2xxx/ # RP2040/RP2350 variants
├── protobufs/ # Protocol buffer definitions
├── boards/ # Custom PlatformIO board definitions
├── test/ # Unit tests (12 test suites)
└── bin/ # Build and utility scripts
```
@@ -130,7 +105,6 @@ firmware/
- Follow existing code style - run `trunk fmt` before commits
- Prefer `LOG_DEBUG`, `LOG_INFO`, `LOG_WARN`, `LOG_ERROR` for logging
- Use `assert()` for invariants that should never fail
- C++17 features are available (`std::optional`, structured bindings, `if constexpr`, etc.)
### Naming Conventions
@@ -144,151 +118,70 @@ firmware/
#### Module System
Modules use a three-tier class hierarchy:
Modules inherit from `MeshModule` or `ProtobufModule<T>` and implement:
1. **`MeshModule`** - Base class. Implement `wantPacket()` and `handleReceived()`. Returns `ProcessMessage::STOP` or `ProcessMessage::CONTINUE`.
2. **`SinglePortModule`** - Handles a single portnum. Simplified `wantPacket()` that checks `decoded.portnum`.
3. **`ProtobufModule<T>`** - Template for protobuf-based modules. Handles encoding/decoding automatically.
Most modules also inherit from **`OSThread`** for periodic tasks (the "mixin" pattern):
- `handleReceivedProtobuf()` - Process incoming packets
- `allocReply()` - Generate response packets
- `runOnce()` - Periodic task execution (returns next run interval in ms)
```cpp
class MyModule : public ProtobufModule<meshtastic_MyMessage>, private concurrency::OSThread
class MyModule : public ProtobufModule<meshtastic_MyMessage>
{
public:
MyModule();
protected:
virtual bool handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_MyMessage *msg) override;
virtual meshtastic_MeshPacket *allocReply() override; // Generate response packets
virtual int32_t runOnce() override; // Periodic task (returns next interval in ms)
virtual bool alterReceivedProtobuf(meshtastic_MeshPacket &mp, meshtastic_MyMessage *msg); // Modify in-flight
virtual bool wantUIFrame(); // Request a UI display frame
virtual int32_t runOnce() override;
};
```
Modules are registered in `src/modules/Modules.cpp` guarded by `MESHTASTIC_EXCLUDE_*` flags.
#### Observer/Observable Pattern
Event-driven communication between subsystems uses `src/Observer.h`:
```cpp
// Observable emits events
Observable<const meshtastic::Status *> newStatus;
newStatus.notifyObservers(&status);
// Observer receives events via callback
CallbackObserver<MyClass, const meshtastic::Status *> statusObserver =
CallbackObserver<MyClass, const meshtastic::Status *>(this, &MyClass::handleStatusUpdate);
```
#### Configuration Access
- `config.*` - Device configuration (LoRa, position, power, etc.)
- `moduleConfig.*` - Module-specific configuration
- `channels.*` - Channel configuration and management
- `owner` - Device owner info
- `myNodeInfo` - Local node info
#### Default Values
Use the `Default` class helpers in `src/mesh/Default.h`:
- `Default::getConfiguredOrDefaultMs(configured, default)` - Returns ms, using default if configured is 0
- `Default::getConfiguredOrDefault(configured, default)` - Generic configured/default getter
- `Default::getConfiguredOrMinimumValue(configured, min)` - Enforces minimum values
- `Default::getConfiguredOrDefaultMsScaled(configured, default, numNodes)` - Scales based on network size
#### Thread Safety
- Use `concurrency::Lock` and `concurrency::LockGuard` for mutex protection
- Use `concurrency::Lock` for mutex protection
- Radio SPI access uses `SPILock`
- Prefer `OSThread` for background tasks
### Hardware Detection
`src/detect/ScanI2C` automatically enumerates 80+ I2C device types at boot including displays, sensors, RTCs, keyboards, PMUs, and touch controllers. This drives automatic initialization of the correct drivers.
### Graphics/UI System
Multiple display driver families in `src/graphics/`:
- **OLED**: SSD1306, SH1106, ST7567
- **TFT**: TFTDisplay (LovyanGFX-based)
- **E-Ink**: EInkDisplay2, EInkDynamicDisplay, EInkParallelDisplay
**InkHUD** (`src/graphics/niche/InkHUD/`) is an event-driven e-ink UI framework:
- Applet-based architecture — modular display tiles
- Read-only, static display optimized for minimal refreshes and low power
- Configured per-variant via `nicheGraphics.h`
- Separate PlatformIO config: `src/graphics/niche/InkHUD/PlatformioConfig.ini`
### Input System
`src/input/InputBroker` is the centralized input event dispatcher. Supports multiple input sources: buttons, keyboards (BBQ10, Cardputer, TCA8418), touch screens, rotary encoders, and matrix keyboards.
### Power Management
`src/PowerFSM.*` implements a finite state machine with states: `stateON`, `statePOWER`, `stateSERIAL`, `stateDARK`. Key events: `EVENT_PRESS`, `EVENT_WAKE_TIMER`, `EVENT_LOW_BATTERY`, `EVENT_RECEIVED_MSG`, `EVENT_SHUTDOWN`. Conditionally excluded with `MESHTASTIC_EXCLUDE_POWER_FSM` (falls back to `FakeFsm`).
### Motion Sensors
`src/motion/AccelerometerThread` provides background motion monitoring with automatic screen wake and double-tap button press detection. Supports 10+ accelerometer/gyroscope chips (BMA423, BMI270, MPU6050, LIS3DH, LSM6DS3, STK8XXX, QMA6100P, ICM20948, BMX160).
### Telemetry Sensor Library
`src/modules/Telemetry/Sensor/` contains 50+ I2C sensor drivers organized by category:
- **Power monitoring**: INA219/226/260/3221, MAX17048
- **Environmental**: BME280/680, SCD4X (CO₂), SEN5X (particulate)
- **Humidity/Temperature**: SHT3X/4X, AHT10, MCP9808, MLX90614
- **Light**: BH1750, TSL2561/2591, VEML7700, LTR390UV, OPT3001
- **Air quality**: PMSA003I, SFA30
- **Specialized**: CGRadSens (radiation), NAU7802 (weight scale)
### API/Networking
`src/mesh/api/` provides a template-based `ServerAPI` for client communication over WiFi (`WiFiServerAPI`) and Ethernet (`ethServerAPI`). Default port: **4403**. HTTP server in `src/mesh/http/`. JSON serialization in `src/serialization/MeshPacketSerializer`.
### Hardware Variants
Each hardware variant has:
- `variant.h` - Pin definitions and hardware capabilities
- `platformio.ini` - Build configuration
- Optional: `pins_arduino.h`, `rfswitch.h`, `nicheGraphics.h` (for InkHUD variants)
- Optional: `pins_arduino.h`, `rfswitch.h`
Key defines in variant.h:
```cpp
#define USE_SX1262 // Radio chip selection
#define HAS_GPS 1 // Hardware capabilities
#define HAS_SCREEN 1 // Display present
#define LORA_CS 36 // Pin assignments
#define SX126X_DIO1 14 // Radio-specific pins
```
### Protobuf Messages
- Defined in `protobufs/meshtastic/*.proto` (~32 proto files)
- Generated code in `src/mesh/generated/meshtastic/`
- Defined in `protobufs/meshtastic/*.proto`
- Generated code in `src/mesh/generated/`
- Regenerate with `bin/regen-protos.sh`
- Message types prefixed with `meshtastic_`
- Nanopb `.options` files control field sizes and encoding
### Conditional Compilation
```cpp
#if !MESHTASTIC_EXCLUDE_GPS // Feature exclusion
#if !MESHTASTIC_EXCLUDE_WIFI // Network feature exclusion
#if !MESHTASTIC_EXCLUDE_BLUETOOTH // BLE exclusion
#if !MESHTASTIC_EXCLUDE_POWER_FSM // Power FSM exclusion
#ifdef ARCH_ESP32 // Architecture-specific
#ifdef ARCH_NRF52 // Nordic platform
#ifdef ARCH_RP2040 // Raspberry Pi Pico
#ifdef ARCH_PORTDUINO // Linux native
#if defined(USE_SX1262) // Radio-specific
#ifdef HAS_SCREEN // Hardware capability
#if USERPREFS_EVENT_MODE // User preferences
@@ -299,7 +192,7 @@ Key defines in variant.h:
Uses **PlatformIO** with custom scripts:
- `bin/platformio-pre.py` - Pre-build script
- `bin/platformio-custom.py` - Custom build logic, manifest generation
- `bin/platformio-custom.py` - Custom build logic
Build commands:
@@ -309,38 +202,21 @@ pio run -e tbeam -t upload # Build and upload
pio run -e native # Build native/Linux version
```
### Build Manifest
`bin/platformio-custom.py` emits a build manifest with metadata:
- `hasMui`, `hasInkHud` - UI capability flags (overridable via `custom_meshtastic_has_mui`, `custom_meshtastic_has_ink_hud`)
- Architecture normalization (e.g., `esp32s3` → `esp32-s3` for API compatibility)
## Common Tasks
### Adding a New Module
1. Create `src/modules/MyModule.cpp` and `.h`
2. Inherit from appropriate base class (`MeshModule`, `SinglePortModule`, or `ProtobufModule<T>`)
3. Mix in `concurrency::OSThread` if periodic work is needed
4. Register in `src/modules/Modules.cpp` guarded by `#if !MESHTASTIC_EXCLUDE_MYMODULE`
5. Add protobuf messages if needed in `protobufs/meshtastic/`
6. Add test suite in `test/test_mymodule/` if applicable
2. Inherit from appropriate base class
3. Register in `src/modules/Modules.cpp`
4. Add protobuf messages if needed in `protobufs/`
### Adding a New Hardware Variant
1. Create directory under `variants/<arch>/<name>/`
2. Add `variant.h` with pin definitions and hardware capability defines
3. Add `platformio.ini` with build config — use `extends` to reference common base (e.g., `esp32s3_base`)
4. Set `custom_meshtastic_support_level = 1` (PR builds) or `2` (merge builds)
5. For e-ink displays, add `nicheGraphics.h` for InkHUD configuration
### Adding a New Telemetry Sensor
1. Create driver in `src/modules/Telemetry/Sensor/` following existing sensor pattern
2. Register I2C address in `src/detect/ScanI2C` for auto-detection
3. Integrate with the appropriate telemetry module (Environment, Health, Power, AirQuality)
4. Add proto fields in `protobufs/meshtastic/telemetry.proto` if new data types are needed
2. Add `variant.h` with pin definitions
3. Add `platformio.ini` with build config
4. Reference common configs with `extends`
### Modifying Configuration Defaults
@@ -429,22 +305,9 @@ Most workflows can be triggered manually via `workflow_dispatch` for testing.
## Testing
Unit tests in `test/` directory with 12 test suites:
- `test_crypto/` - Cryptography
- `test_mqtt/` - MQTT integration
- `test_radio/` - Radio interface
- `test_mesh_module/` - Module framework
- `test_meshpacket_serializer/` - Packet serialization
- `test_transmit_history/` - Retransmission tracking
- `test_atak/` - ATAK integration
- `test_default/` - Default configuration
- `test_http_content_handler/` - HTTP handling
- `test_serial/` - Serial communication
Run with: `pio test -e native`
Simulation testing: `bin/test-simulator.sh`
- Unit tests in `test/` directory
- Run with `pio test -e native`
- Use `bin/test-simulator.sh` for simulation testing
## Resources
-138
View File
@@ -1,138 +0,0 @@
# New Meshtastic Module
Guide for developing a new Meshtastic firmware module.
## Module Hierarchy
Choose the appropriate base class:
1. **`MeshModule`** — Raw base class. Override `wantPacket()` and `handleReceived()`. Returns `ProcessMessage::STOP` or `ProcessMessage::CONTINUE`.
2. **`SinglePortModule`** — Handles a single `meshtastic_PortNum`. Constructor takes `(name, portNum)`. Simplified `wantPacket()` checking `decoded.portnum`. Use `allocDataPacket()` to create outgoing packets.
3. **`ProtobufModule<T>`** — Template for protobuf-encoded modules. Constructor takes `(name, portNum, fields)`. Override `handleReceivedProtobuf()`. Use `allocDataProtobuf(payload)` to create outgoing packets.
Most modules also mix in `concurrency::OSThread` for periodic background tasks.
## Implementation Pattern
```cpp
// src/modules/MyModule.h
#pragma once
#include "ProtobufModule.h"
#include "concurrency/OSThread.h"
class MyModule : public ProtobufModule<meshtastic_MyMessage>, private concurrency::OSThread
{
public:
MyModule();
protected:
// Process incoming protobuf packet. Return true to stop further processing.
virtual bool handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_MyMessage *msg) override;
// Generate response packet (optional)
virtual meshtastic_MeshPacket *allocReply() override;
// Periodic task — return next run interval in ms, or disable()
virtual int32_t runOnce() override;
// Modify packet in-flight before delivery (optional)
virtual bool alterReceivedProtobuf(meshtastic_MeshPacket &mp, meshtastic_MyMessage *msg);
// Request a UI display frame (optional)
virtual bool wantUIFrame();
};
```
## Registration
Register in `src/modules/Modules.cpp` inside `setupModules()`:
```cpp
#if !MESHTASTIC_EXCLUDE_MYMODULE
new MyModule();
#endif
```
If other code needs to reference the module instance:
```cpp
#if !MESHTASTIC_EXCLUDE_MYMODULE
myModule = new MyModule();
#endif
```
And declare the global in the header:
```cpp
extern MyModule *myModule;
```
Some modules also conditionally instantiate based on `moduleConfig`:
```cpp
#if !MESHTASTIC_EXCLUDE_MYMODULE
if (moduleConfig.has_my_module && moduleConfig.my_module.enabled) {
new MyModule();
}
#endif
```
## Conditional Compilation
Add a `MESHTASTIC_EXCLUDE_MYMODULE` guard. This allows the module to be excluded from constrained builds. The flag name must follow the pattern: `MESHTASTIC_EXCLUDE_` + uppercase module name.
## Protobuf Messages (if needed)
1. Define messages in `protobufs/meshtastic/` (e.g., `mymodule.proto`)
2. Add a `.options` file for nanopb field size constraints
3. Regenerate with `bin/regen-protos.sh`
4. Generated code appears in `src/mesh/generated/meshtastic/`
5. Assign a `meshtastic_PortNum` if the module uses a new port number
## Timing and Defaults
Use `Default` class helpers for configurable intervals:
```cpp
int32_t MyModule::runOnce()
{
uint32_t interval = Default::getConfiguredOrDefaultMs(moduleConfig.my_module.update_interval,
default_my_module_interval);
// ... do work ...
return interval;
}
```
On public/default channels, enforce minimums with `Default::getConfiguredOrMinimumValue()`.
## Observer Pattern
Subscribe to system events:
```cpp
CallbackObserver<MyModule, const meshtastic::Status *> statusObserver =
CallbackObserver<MyModule, const meshtastic::Status *>(this, &MyModule::handleStatusUpdate);
```
## Testing
Add test suite in `test/test_mymodule/`:
```
test/
└── test_mymodule/
└── test_main.cpp
```
Run with: `pio test -e native`
## Checklist
- [ ] Header and implementation files in `src/modules/`
- [ ] Inherit from appropriate base class (MeshModule / SinglePortModule / ProtobufModule)
- [ ] Mix in OSThread if periodic work is needed
- [ ] Register in `src/modules/Modules.cpp` with `MESHTASTIC_EXCLUDE_` guard
- [ ] Add protobuf definitions if needed (`protobufs/meshtastic/`)
- [ ] Use `Default::getConfiguredOrDefaultMs()` for timing
- [ ] Respect bandwidth limits on public channels
- [ ] Add test suite in `test/`
-149
View File
@@ -1,149 +0,0 @@
# New Telemetry Sensor
Guide for adding a new I2C telemetry sensor driver to Meshtastic firmware.
## Overview
Telemetry sensors live in `src/modules/Telemetry/Sensor/`. There are 50+ existing drivers organized by measurement type. Each sensor integrates with one of the telemetry modules:
- **EnvironmentTelemetryModule** — Temperature, humidity, pressure, gas, light
- **AirQualityTelemetryModule** — Particulate matter, VOCs
- **PowerTelemetryModule** — Voltage, current, power monitoring
- **HealthTelemetryModule** — Heart rate, SpO2, body temperature
## Sensor Driver Pattern
Each sensor has a `.h` and `.cpp` file pair following this pattern:
```cpp
// src/modules/Telemetry/Sensor/MySensor.h
#pragma once
#include "TelemetrySensor.h"
#include <MySensorLibrary.h> // Arduino/PlatformIO library
class MySensor : virtual public TelemetrySensor
{
private:
MySensorLibrary sensor;
public:
MySensor() : TelemetrySensor(meshtastic_TelemetrySensorType_MY_SENSOR, "MySensor") {}
// Initialize sensor hardware. Return true on success.
virtual void setup() override;
// Read sensor data into the telemetry protobuf. Return true on success.
virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
};
```
```cpp
// src/modules/Telemetry/Sensor/MySensor.cpp
#include "MySensor.h"
#include "TelemetrySensor.h"
void MySensor::setup()
{
sensor.begin();
// Configure sensor parameters...
}
bool MySensor::getMetrics(meshtastic_Telemetry *measurement)
{
// Read from hardware
float value = sensor.readValue();
// Populate the appropriate protobuf variant
measurement->variant.environment_metrics.temperature = value;
// ... other fields ...
return true;
}
```
## I2C Address Registration
Register the sensor's I2C address(es) in `src/detect/ScanI2C` so it's auto-detected at boot:
1. Add a `DeviceType` enum entry in `src/detect/ScanI2C.h`
2. Add the I2C address mapping in `src/detect/ScanI2CTwoWire.cpp`
The scan runs at boot and populates a device map that telemetry modules use to decide which sensors to initialize.
## Protobuf Fields
If the sensor provides data not covered by existing telemetry fields:
1. Add fields to the appropriate message in `protobufs/meshtastic/telemetry.proto`:
- `EnvironmentMetrics` — Environmental measurements
- `AirQualityMetrics` — Air quality data
- `PowerMetrics` — Power/energy data
- `HealthMetrics` — Health/biometric data
2. Add a `.options` constraint if needed (field sizes for nanopb)
3. Regenerate: `bin/regen-protos.sh`
## Sensor Type Enum
Add the sensor to `meshtastic_TelemetrySensorType` enum in `protobufs/meshtastic/telemetry.proto`:
```protobuf
enum TelemetrySensorType {
// ... existing entries ...
MY_SENSOR = XX;
}
```
## Integration with Telemetry Module
Wire the sensor into the appropriate telemetry module. For environment sensors, this is typically in `src/modules/Telemetry/EnvironmentTelemetry.cpp`:
1. Include the sensor header
2. Add initialization in `setupSensor()` guarded by detection results
3. Call `getMetrics()` in the measurement collection path
Example pattern from existing sensors:
```cpp
#include "Sensor/MySensor.h"
MySensor mySensor;
// In setup:
if (nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_MY_SENSOR].first > 0) {
mySensor.setup();
}
// In measurement collection:
if (nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_MY_SENSOR].first > 0) {
mySensor.getMetrics(&measurement);
}
```
## Library Dependencies
If the sensor needs an external library, add it to the `lib_deps` in the relevant base platformio.ini configs:
```ini
lib_deps =
${env.lib_deps}
mysensorlibrary@^1.0.0
```
Or use a conditional dependency if it's platform-specific.
## Unit Conversions
If the sensor reports values in non-standard units, use `src/modules/Telemetry/UnitConversions.h` for conversion helpers (e.g., Celsius ↔ Fahrenheit, hPa ↔ inHg).
## Checklist
- [ ] Create `src/modules/Telemetry/Sensor/MySensor.h` and `.cpp`
- [ ] Inherit from `TelemetrySensor` base class
- [ ] Implement `setup()` and `getMetrics()` methods
- [ ] Add `meshtastic_TelemetrySensorType` enum entry in `telemetry.proto`
- [ ] Add I2C address to `src/detect/ScanI2C` for auto-detection
- [ ] Add protobuf fields in `telemetry.proto` if new data types needed
- [ ] Regenerate protos: `bin/regen-protos.sh`
- [ ] Wire into the appropriate telemetry module (Environment/AirQuality/Power/Health)
- [ ] Add library dependency if external library required
- [ ] Test on hardware or native build
-178
View File
@@ -1,178 +0,0 @@
# New Hardware Variant
Guide for adding a new Meshtastic hardware variant to the firmware.
## Directory Structure
Create under `variants/<arch>/<name>/`:
```
variants/
├── esp32/ # ESP32
├── esp32s3/ # ESP32-S3
├── esp32c3/ # ESP32-C3
├── esp32c6/ # ESP32-C6
├── nrf52840/ # nRF52840
├── rp2040/ # RP2040/RP2350
├── stm32/ # STM32WL
└── native/ # Linux/Portduino
```
Each variant needs at minimum:
- `variant.h` — Pin definitions and hardware capabilities
- `platformio.ini` — Build configuration
Optional files:
- `pins_arduino.h` — Arduino pin mapping overrides
- `rfswitch.h` — RF switch control for multi-band radios
- `nicheGraphics.h` — InkHUD e-ink configuration
## variant.h Template
```cpp
// Pin definitions
#define I2C_SDA 21
#define I2C_SCL 22
// LoRa radio
#define USE_SX1262 // Radio chip: USE_SX1262, USE_SX1268, USE_SX1280, USE_RF95, USE_LLCC68, USE_LR1110, USE_LR1120, USE_LR1121
#define LORA_CS 18
#define LORA_SCK 5
#define LORA_MOSI 27
#define LORA_MISO 19
#define LORA_DIO1 33 // SX126x: DIO1, SX128x: DIO1, RF95: IRQ
#define LORA_RESET 23
#define LORA_BUSY 32 // SX126x/SX128x only
#define SX126X_DIO2_AS_RF_SWITCH // Common for SX1262 boards
// GPS
#define HAS_GPS 1
#define GPS_RX_PIN 34
#define GPS_TX_PIN 12
// #define PIN_GPS_EN 47 // Optional GPS enable pin
// #define GPS_BAUDRATE 9600 // Override default 9600
// Display
#define HAS_SCREEN 1
// #define USE_SSD1306 // OLED type
// #define USE_SH1106 // Alternative OLED
// #define USE_ST7789 // TFT type
// #define SCREEN_WIDTH 128
// #define SCREEN_HEIGHT 64
// LEDs
#define LED_PIN 2 // Status LED (optional)
// #define HAS_NEOPIXEL 1 // WS2812 support
// Buttons
#define BUTTON_PIN 38
// #define BUTTON_PIN_ALT 0 // Secondary button
// Power management
// #define HAS_AXP192 1 // AXP192 PMU (T-Beam v1.0)
// #define HAS_AXP2101 1 // AXP2101 PMU (T-Beam v1.2+)
// #define BATTERY_PIN 35 // ADC battery voltage pin
// #define ADC_MULTIPLIER 2.0 // Voltage divider ratio
// Optional I2C devices
// #define HAS_RTC 1 // Real-time clock
// #define HAS_TELEMETRY 1 // Enable telemetry sensor support
// #define HAS_SENSOR 1 // I2C sensors present
```
## platformio.ini Template
```ini
[env:my_variant]
extends = esp32s3_base ; Use architecture-specific base
board = esp32-s3-devkitc-1 ; PlatformIO board definition (or custom in boards/)
board_level = extra ; Build level: extra, or omit for default
custom_meshtastic_support_level = 1 ; 1 = PR builds, 2 = merge builds only
build_flags =
${esp32s3_base.build_flags}
-D MY_VARIANT_SPECIFIC_FLAG=1
-I variants/esp32s3/my_variant ; Include path for variant.h
upload_speed = 921600
```
### Common Base Configs
- `esp32_base` / `esp32-common.ini` — ESP32
- `esp32s3_base` — ESP32-S3
- `esp32c3_base` — ESP32-C3
- `esp32c6_base` — ESP32-C6
- `nrf52840_base` / `nrf52.ini` — nRF52840
- `rp2040_base` — RP2040/RP2350
### Support Levels
- `custom_meshtastic_support_level = 1` — Built on every PR (actively supported)
- `custom_meshtastic_support_level = 2` — Built only on merge to main branches
- `board_level = extra` — Only built on full releases
## Build Manifest Metadata
`bin/platformio-custom.py` emits UI capability flags in the build manifest:
- `custom_meshtastic_has_mui = true/false` — Override MUI detection
- `custom_meshtastic_has_ink_hud = true/false` — Override InkHUD detection
- Architecture names are normalized (e.g., `esp32s3` → `esp32-s3`)
## InkHUD E-Ink Variants
For e-ink display variants using the InkHUD framework, add `nicheGraphics.h`:
```cpp
// nicheGraphics.h — InkHUD configuration for this variant
#define INKHUD // Enable InkHUD
// Configure display, applets, and refresh behavior per device
```
InkHUD has its own PlatformIO config: `src/graphics/niche/InkHUD/PlatformioConfig.ini`
## I2C Device Detection
If the variant has I2C devices, ensure `src/detect/ScanI2C` will detect them. The auto-detection system handles 80+ device types including displays, sensors, RTCs, keyboards, PMUs, and touch controllers at boot.
## Custom Board Definitions
If the PlatformIO board doesn't exist, create a custom board JSON in `boards/`:
```json
{
"build": {
"arduino": { "ldscript": "esp32s3_out.ld" },
"core": "esp32",
"f_cpu": "240000000L",
"f_flash": "80000000L",
"flash_mode": "qio",
"mcu": "esp32s3",
"variant": "esp32s3"
},
"connectivity": ["wifi", "bluetooth"],
"frameworks": ["arduino", "espidf"],
"name": "My Custom Board",
"upload": {
"flash_size": "8MB",
"maximum_ram_size": 327680,
"maximum_size": 8388608
},
"url": "https://example.com",
"vendor": "MyVendor"
}
```
## Checklist
- [ ] Create `variants/<arch>/<name>/variant.h` with pin definitions
- [ ] Create `variants/<arch>/<name>/platformio.ini` extending correct base
- [ ] Set `custom_meshtastic_support_level` (1 or 2)
- [ ] Verify radio chip define matches hardware (`USE_SX1262`, etc.)
- [ ] Set hardware capability flags (`HAS_GPS`, `HAS_SCREEN`, etc.)
- [ ] Add custom board JSON in `boards/` if needed
- [ ] Test build: `pio run -e my_variant`
- [ ] For e-ink: add `nicheGraphics.h` with InkHUD config
+11 -22
View File
@@ -56,25 +56,6 @@ jobs:
echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
id: version
- name: Set up QEMU
uses: docker/setup-qemu-action@v4
with:
cache-image: true
- name: Docker setup
uses: docker/setup-buildx-action@v4
with:
# Add Google/Amazon DockerHub mirrors to buildkit config
# https://docs.docker.com/build/ci/github-actions/configure-builder/#registry-mirror
buildkitd-config-inline: |
[registry."docker.io"]
mirrors = ["mirror.gcr.io", "public.ecr.aws"]
- name: Sanitize platform string
id: sanitize_platform
# Replace slashes with underscores
run: echo "cleaned_platform=${{ inputs.platform }}" | sed 's/\//_/g' >> $GITHUB_OUTPUT
- name: Docker login
if: ${{ inputs.push }}
uses: docker/login-action@v4
@@ -82,6 +63,17 @@ jobs:
username: meshtastic
password: ${{ secrets.DOCKER_FIRMWARE_TOKEN }}
- name: Set up QEMU
uses: docker/setup-qemu-action@v4
- name: Docker setup
uses: docker/setup-buildx-action@v4
- name: Sanitize platform string
id: sanitize_platform
# Replace slashes with underscores
run: echo "cleaned_platform=${{ inputs.platform }}" | sed 's/\//_/g' >> $GITHUB_OUTPUT
- name: Docker tag
id: meta
uses: docker/metadata-action@v6
@@ -103,6 +95,3 @@ jobs:
platforms: ${{ inputs.platform }}
build-args: |
PIO_ENV=${{ inputs.pio_env }}
# Cache image layers in GitHub Actions cache to speed up subsequent builds.
cache-from: type=gha
cache-to: type=gha,mode=max
+3 -5
View File
@@ -301,17 +301,15 @@ jobs:
id: release_notes
run: |
chmod +x ./bin/generate_release_notes.py
NOTES=$(./bin/generate_release_notes.py ${{ needs.version.outputs.long }} --compare-ref HEAD 2>release_notes.log)
NOTES=$(./bin/generate_release_notes.py ${{ needs.version.outputs.long }})
echo "notes<<EOF" >> $GITHUB_OUTPUT
echo "$NOTES" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
echo "### Release note range" >> $GITHUB_STEP_SUMMARY
cat release_notes.log >> $GITHUB_STEP_SUMMARY
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Create release
uses: softprops/action-gh-release@v3
uses: softprops/action-gh-release@v2
id: create_release
with:
draft: true
@@ -468,7 +466,7 @@ jobs:
- name: Generate release notes
run: |
chmod +x ./bin/generate_release_notes.py
./bin/generate_release_notes.py ${{ needs.version.outputs.long }} --compare-ref HEAD > ./publish/release_notes.md
./bin/generate_release_notes.py ${{ needs.version.outputs.long }} > ./publish/release_notes.md
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+5 -5
View File
@@ -38,7 +38,7 @@ jobs:
- name: Apply quality label if needed
if: steps.quality.outputs.response != '' && steps.quality.outputs.response != 'ok'
uses: actions/github-script@v9
uses: actions/github-script@v8
env:
QUALITY_LABEL: ${{ steps.quality.outputs.response }}
with:
@@ -80,7 +80,7 @@ jobs:
# ─────────────────────────────────────────────────────────────────────────
- name: Determine if completeness check should be skipped
if: steps.quality.outputs.response == 'ok' || steps.quality.outputs.response == ''
uses: actions/github-script@v9
uses: actions/github-script@v8
id: check-skip
with:
script: |
@@ -134,7 +134,7 @@ jobs:
- 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@v9
uses: actions/github-script@v8
id: process
env:
AI_RESPONSE: ${{ steps.analysis.outputs.response }}
@@ -174,7 +174,7 @@ jobs:
- name: Apply triage label
if: steps.process.outputs.label != '' && steps.process.outputs.label != 'none'
uses: actions/github-script@v9
uses: actions/github-script@v8
env:
LABEL_NAME: ${{ steps.process.outputs.label }}
with:
@@ -200,7 +200,7 @@ jobs:
- name: Comment on issue
if: steps.process.outputs.should_comment == 'true'
uses: actions/github-script@v9
uses: actions/github-script@v8
env:
COMMENT_BODY: ${{ steps.process.outputs.comment_body }}
with:
+3 -3
View File
@@ -22,7 +22,7 @@ jobs:
# Step 1: Check if PR already has automation/type labels (skip if so)
# ─────────────────────────────────────────────────────────────────────────
- name: Check existing labels
uses: actions/github-script@v9
uses: actions/github-script@v8
id: check-labels
with:
script: |
@@ -58,7 +58,7 @@ jobs:
- 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@v9
uses: actions/github-script@v8
id: quality-label
env:
QUALITY_LABEL: ${{ steps.quality.outputs.response }}
@@ -113,7 +113,7 @@ jobs:
- 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@v9
uses: actions/github-script@v8
env:
TYPE_LABEL: ${{ steps.classify.outputs.response }}
with:
+5 -42
View File
@@ -5,8 +5,6 @@ on:
secrets:
PPA_GPG_PRIVATE_KEY:
required: true
PPA_SFTP_PRIVATE_KEY:
required: true
inputs:
ppa_repo:
description: Meshtastic PPA to target
@@ -29,7 +27,6 @@ jobs:
build_location: ppa
package-ppa:
if: ${{ github.event_name != 'pull_request_target' && github.event_name != 'pull_request' }}
runs-on: ubuntu-24.04
needs: build-debian-src
steps:
@@ -43,7 +40,7 @@ jobs:
shell: bash
run: |
sudo apt-get update -y --fix-missing
sudo apt-get install -y dput openssh-client
sudo apt-get install -y dput
- name: Import GPG key
uses: crazy-max/ghaction-import-gpg@v7
@@ -68,42 +65,8 @@ jobs:
- name: Display structure of downloaded files
run: ls -lah
- name: Trust Launchpad's SSH key
- name: Publish with dput
if: ${{ github.event_name != 'pull_request_target' && github.event_name != 'pull_request' }}
timeout-minutes: 15 # dput is terrible, sometimes runs 'forever'
run: |
mkdir -p ~/.ssh
ssh-keyscan -H ppa.launchpad.net >> ~/.ssh/known_hosts
- name: Setup dput config
env:
ppa_login: meshtasticorg
run: |
sudo tee /etc/meshtastic-dput.cf >/dev/null <<EOF
[ppa]
fqdn = ppa.launchpad.net
method = ftp
incoming = ~%(ppa)s
login = anonymous
[ssh-ppa]
fqdn = ppa.launchpad.net
method = sftp
incoming = ~%(ssh-ppa)s
login = ${ppa_login}
EOF
- name: Import SSH key
uses: webfactory/ssh-agent@v0.10.0
with:
ssh-private-key: ${{ secrets.PPA_SFTP_PRIVATE_KEY }}
id: ssh
- name: Publish with dput (sftp)
timeout-minutes: 30 # dput is terrible, sometimes runs 'forever'
env:
up_ppa_repo: ${{ inputs.ppa_repo }}
up_series: ${{ inputs.series }}
up_version: ${{ steps.version.outputs.deb }}
run: >
dput -c /etc/meshtastic-dput.cf
ssh-${up_ppa_repo}
meshtasticd_${up_version}~${up_series}_source.changes
dput ${{ inputs.ppa_repo }} meshtasticd_${{ steps.version.outputs.deb }}~${{ inputs.series }}_source.changes
+1 -1
View File
@@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check for PR labels
uses: actions/github-script@v9
uses: actions/github-script@v8
with:
script: |
const labels = context.payload.pull_request.labels.map(label => label.name);
+1 -1
View File
@@ -177,7 +177,7 @@ jobs:
- name: Comment test results on PR
if: github.event_name == 'pull_request' && needs.native-tests.result != 'skipped'
uses: actions/github-script@v9
uses: actions/github-script@v8
with:
script: |
const fs = require('fs');
+1 -1
View File
@@ -52,7 +52,7 @@ jobs:
node-version: 24
- name: Setup pnpm
uses: pnpm/action-setup@v6
uses: pnpm/action-setup@v5
with:
version: latest
+5 -5
View File
@@ -8,20 +8,20 @@ plugins:
uri: https://github.com/trunk-io/plugins
lint:
enabled:
- checkov@3.2.517
- renovate@43.110.9
- checkov@3.2.513
- renovate@43.100.0
- prettier@3.8.1
- trufflehog@3.94.3
- trufflehog@3.94.1
- yamllint@1.38.0
- bandit@1.9.4
- trivy@0.69.3
- taplo@0.10.0
- ruff@0.15.9
- ruff@0.15.8
- isort@8.0.1
- markdownlint@0.48.0
- oxipng@10.1.0
- svgo@4.0.1
- actionlint@1.7.12
- actionlint@1.7.11
- flake8@7.3.0
- hadolint@2.14.0
- shfmt@3.6.0
+2 -2
View File
@@ -12,7 +12,7 @@ ENV TZ=Etc/UTC
ENV PIP_ROOT_USER_ACTION=ignore
RUN apt-get update && apt-get install --no-install-recommends -y \
curl wget g++ zip git ca-certificates pkg-config \
libgpiod-dev libyaml-cpp-dev libjsoncpp-dev libbluetooth-dev libi2c-dev libuv1-dev \
libgpiod-dev libyaml-cpp-dev libbluetooth-dev libi2c-dev libuv1-dev \
libusb-1.0-0-dev libulfius-dev liborcania-dev libssl-dev \
libx11-dev libinput-dev libxkbcommon-x11-dev libsqlite3-dev libsdl2-dev \
&& apt-get clean && rm -rf /var/lib/apt/lists/* \
@@ -51,7 +51,7 @@ ENV TZ=Etc/UTC
USER root
RUN apt-get update && apt-get --no-install-recommends -y install \
libc-bin libc6 libgpiod3 libyaml-cpp0.8 libjsoncpp26 libi2c0 libuv1t64 libusb-1.0-0-dev \
libc-bin libc6 libgpiod3 libyaml-cpp0.8 libi2c0 libuv1t64 libusb-1.0-0-dev \
liborcania2.3 libulfius2.7t64 libssl3t64 \
libx11-6 libinput10 libxkbcommon-x11-0 libsdl2-2.0-0 \
&& apt-get clean && rm -rf /var/lib/apt/lists/* \
+1 -1
View File
@@ -7,7 +7,7 @@ ENV PIP_ROOT_USER_ACTION=ignore
# hadolint ignore=DL3008
RUN apt-get update && apt-get install --no-install-recommends -y \
g++ git ca-certificates pkg-config \
libgpiod-dev libyaml-cpp-dev libjsoncpp-dev libbluetooth-dev libi2c-dev libuv1-dev \
libgpiod-dev libyaml-cpp-dev libbluetooth-dev libi2c-dev libuv1-dev \
libusb-1.0-0-dev libulfius-dev liborcania-dev libssl-dev \
libx11-dev libinput-dev libxkbcommon-x11-dev libsqlite3-dev libsdl2-dev \
&& apt-get clean && rm -rf /var/lib/apt/lists/* \
+2 -2
View File
@@ -9,7 +9,7 @@ ENV PIP_ROOT_USER_ACTION=ignore
RUN apk --no-cache add \
bash g++ libstdc++-dev linux-headers zip git ca-certificates libbsd-dev \
libgpiod-dev yaml-cpp-dev jsoncpp-dev bluez-dev \
libgpiod-dev yaml-cpp-dev bluez-dev \
libusb-dev i2c-tools-dev libuv-dev openssl-dev pkgconf argp-standalone \
libx11-dev libinput-dev libxkbcommon-dev sqlite-dev sdl2-dev \
&& rm -rf /var/cache/apk/* \
@@ -41,7 +41,7 @@ LABEL org.opencontainers.image.title="Meshtastic" \
USER root
RUN apk --no-cache add \
shadow libstdc++ libbsd libgpiod yaml-cpp jsoncpp libusb \
shadow libstdc++ libbsd libgpiod yaml-cpp libusb \
i2c-tools libuv libx11 libinput libxkbcommon sdl2 \
&& rm -rf /var/cache/apk/* \
&& mkdir -p /var/lib/meshtasticd \
@@ -1,39 +0,0 @@
# MeshAdv-Pi E22-900M30S
# https://github.com/chrismyers2000/MeshAdv-Pi-Hat
Meta:
name: MeshAdv-Pi E22-900M30S
support: community
compatible:
- ebyte-ecb41-pge # Armbian
Lora:
Module: sx1262
CS: # GPIO0_A1 (physical 40)
pin: 1
gpiochip: 0
line: 1
IRQ: # GPIO0_A3 (physical 36)
pin: 3
gpiochip: 0
line: 3
Busy: # GPIO0_A0 (physical 38)
pin: 0
gpiochip: 0
line: 0
Reset: # GPIO0_B4 (physical 12)
pin: 12
gpiochip: 0
line: 12
TXen: # GPIO1_D1 (physical 33)
pin: 57
gpiochip: 1
line: 25
RXen: # GPIO1_B3 (physical 32)
pin: 43
gpiochip: 1
line: 11
DIO3_TCXO_VOLTAGE: true
# Only for E22-900M33S:
# Limit the output power to 8 dBm
# SX126X_MAX_POWER: 8
spidev: spidev0.0
@@ -1,33 +0,0 @@
# MeshAdv Mini E22-900M22S
# https://github.com/chrismyers2000/MeshAdv-Mini
Meta:
name: MeshAdv Mini E22-900M22S
support: community
compatible:
- ebyte-ecb41-pge # Armbian
Lora:
Module: sx1262 # Ebyte E22-900M22S
CS: # GPIO0_B6 (physical 24, SPI1_CSN0)
pin: 14
gpiochip: 0
line: 14
IRQ: # GPIO0_A3 (physical 36)
pin: 3
gpiochip: 0
line: 3
Busy: # GPIO0_A0 (physical 38)
pin: 0
gpiochip: 0
line: 0
Reset: # GPIO1_C3 (physical 18)
pin: 51
gpiochip: 1
line: 19
RXen: # GPIO1_B3 (physical 32)
pin: 43
gpiochip: 1
line: 11
DIO2_AS_RF_SWITCH: true
DIO3_TCXO_VOLTAGE: true
spidev: spidev0.0
@@ -1,38 +0,0 @@
Meta:
name: RAK6421 + RAK13300 Slot 1
support: community # Promote when tested
compatible:
- ebyte-ecb41-pge # Armbian
Lora:
Module: sx1262
IRQ: # GPIO0_A5 (IO6, physical 15)
pin: 5
gpiochip: 0
line: 5
Reset: # GPIO0_A3 (IO4, physical 36)
pin: 3
gpiochip: 0
line: 3
Busy: # GPIO1_C3 (IO5, physical 18)
pin: 51
gpiochip: 1
line: 19
# Ant_sw: # GPIO1_C2 (IO3, physical 16)
# pin: 50
# gpiochip: 1
# line: 18
Enable_Pins:
- pin: 43 # GPIO1_B3 (physical 32)
gpiochip: 1
line: 11
- pin: 57 # GPIO1_D1 (physical 33)
gpiochip: 1
line: 25
DIO3_TCXO_VOLTAGE: true
DIO2_AS_RF_SWITCH: true
spidev: spidev0.0
# CS: # GPIO0_B6 (SPI1_CSN0, physical 24)
# pin: 14
# gpiochip: 0
# line: 14
@@ -1,36 +0,0 @@
Meta:
name: RAK6421 + RAK13300 Slot 2
support: community # Promote when tested
compatible:
- ebyte-ecb41-pge # Armbian
Lora:
Module: sx1262
IRQ: # GPIO0_B4 (IO6, physical 12)
pin: 12
gpiochip: 0
line: 12
Reset: # GPIO1_C3 (IO4, physical 18)
pin: 51
gpiochip: 1
line: 19
Busy: # GPIO0_B3 (IO5, physical 35)
pin: 11
gpiochip: 0
line: 11
# Ant_sw: # GPIO1_C2 (IO3, physical 16)
# pin: 50
# gpiochip: 1
# line: 18
Enable_Pins:
- pin: 2 # GPIO0_A2 (physical 37)
gpiochip: 0
line: 2
- pin: 50 # GPIO1_C2 (physical 16)
gpiochip: 1
line: 18
spidev: spidev0.1
# CS: # GPIO0_A7 (SPI1_CSN1, physical 26)
# pin: 7
# gpiochip: 0
# line: 7
@@ -1,39 +0,0 @@
Meta:
name: RAK6421 + RAK13302 Slot 1
support: community # Promote when tested
compatible:
- ebyte-ecb41-pge # Armbian
Lora:
Module: sx1262
IRQ: # GPIO0_A5 (IO6, physical 15)
pin: 5
gpiochip: 0
line: 5
Reset: # GPIO0_A3 (IO4, physical 36)
pin: 3
gpiochip: 0
line: 3
Busy: # GPIO1_C3 (IO5, physical 18)
pin: 51
gpiochip: 1
line: 19
# Ant_sw: # GPIO1_C2 (IO3, physical 16)
# pin: 50
# gpiochip: 1
# line: 18
Enable_Pins:
- pin: 43 # GPIO1_B3 (physical 32)
gpiochip: 1
line: 11
- pin: 57 # GPIO1_D1 (physical 33)
gpiochip: 1
line: 25
DIO3_TCXO_VOLTAGE: true
DIO2_AS_RF_SWITCH: true
spidev: spidev0.0
# CS: # GPIO0_B6 (SPI1_CSN0, physical 24)
# pin: 14
# gpiochip: 0
# line: 14
TX_GAIN_LORA: [9, 9, 10, 11, 9, 8, 9, 10, 10, 10, 11, 12, 12, 12, 12, 12, 12, 12, 12, 10, 9, 8]
@@ -1,37 +0,0 @@
Meta:
name: RAK6421 + RAK13302 Slot 2
support: community # Promote when tested
compatible:
- ebyte-ecb41-pge # Armbian
Lora:
Module: sx1262
IRQ: # GPIO0_B4 (IO6, physical 12)
pin: 12
gpiochip: 0
line: 12
Reset: # GPIO1_C3 (IO4, physical 18)
pin: 51
gpiochip: 1
line: 19
Busy: # GPIO0_B3 (IO5, physical 35)
pin: 11
gpiochip: 0
line: 11
# Ant_sw: # GPIO1_C2 (IO3, physical 16)
# pin: 50
# gpiochip: 1
# line: 18
Enable_Pins:
- pin: 2 # GPIO0_A2 (physical 37)
gpiochip: 0
line: 2
- pin: 50 # GPIO1_C2 (physical 16)
gpiochip: 1
line: 18
spidev: spidev0.1
# CS: # GPIO0_A7 (SPI1_CSN1, physical 26)
# pin: 7
# gpiochip: 0
# line: 7
TX_GAIN_LORA: [9, 9, 10, 11, 9, 8, 9, 10, 10, 10, 11, 12, 12, 12, 12, 12, 12, 12, 12, 10, 9, 8]
@@ -1,30 +0,0 @@
Meta:
name: Waveshare SX1262
support: deprecated
compatible:
- ebyte-ecb41-pge # Armbian
Lora:
Module: sx1262 # Waveshare SX126X XXXM
DIO2_AS_RF_SWITCH: true
CS: # GPIO0_A1 (physical 40)
pin: 1
gpiochip: 0
line: 1
IRQ: # GPIO0_A3 (physical 36)
pin: 3
gpiochip: 0
line: 3
Busy: # GPIO0_A0 (physical 38)
pin: 0
gpiochip: 0
line: 0
Reset: # GPIO0_B4 (physical 12)
pin: 12
gpiochip: 0
line: 12
SX126X_ANT_SW: # GPIO1_B2 (physical 31)
pin: 42
gpiochip: 1
line: 10
spidev: spidev0.0
@@ -1,31 +0,0 @@
# For use with Armbian luckfox-pico-max
# Waveshare LoRa HAT for Raspberry Pi Pico
# https://www.waveshare.com/wiki/Pico-LoRa-SX1262
Meta:
name: luckfox-pico-max-ws-raspberry-pi-pico-hat
support: community
compatible:
- luckfox-pico-max # Armbian
Lora:
Module: sx1262
DIO2_AS_RF_SWITCH: true
DIO3_TCXO_VOLTAGE: true
spidev: spidev0.0
Busy: # GPIO1_C7 / GP2
pin: 55
gpiochip: 1
line: 23
CS: # GPIO1_C6 / GP3
pin: 54
gpiochip: 1
line: 22
Reset: # GPIO1_D1 / GP15
pin: 57
gpiochip: 1
line: 25
IRQ: # GPIO2_A2 / GP20
pin: 66
gpiochip: 2
line: 2
@@ -1,39 +0,0 @@
# MeshAdv-Pi E22-900M30S
# https://github.com/chrismyers2000/MeshAdv-Pi-Hat
Meta:
name: MeshAdv-Pi E22-900M30S
support: community
compatible:
- luckfox-lyra-zero-w # Armbian
Lora:
Module: sx1262
CS: # GPIO0_C2 (physical 40)
pin: 18
gpiochip: 0
line: 18
IRQ: # GPIO1_D1 (physical 36)
pin: 57
gpiochip: 1
line: 25
Busy: # GPIO0_C1 (physical 38)
pin: 17
gpiochip: 0
line: 17
Reset: # GPIO0_B6 (physical 12)
pin: 14
gpiochip: 0
line: 14
TXen: # GPIO1_C2 (physical 33)
pin: 50
gpiochip: 1
line: 18
RXen: # GPIO1_D2 (physical 32)
pin: 58
gpiochip: 1
line: 26
DIO3_TCXO_VOLTAGE: true
# Only for E22-900M33S:
# Limit the output power to 8 dBm
# SX126X_MAX_POWER: 8
spidev: spidev0.0
@@ -1,33 +0,0 @@
# MeshAdv Mini E22-900M22S
# https://github.com/chrismyers2000/MeshAdv-Mini
Meta:
name: MeshAdv Mini E22-900M22S
support: community
compatible:
- luckfox-lyra-zero-w # Armbian
Lora:
Module: sx1262 # Ebyte E22-900M22S
CS: # GPIO0_B2_d (phys 24, RPi CE0)
pin: 10
gpiochip: 0
line: 10
IRQ: # GPIO1_D1_d (phys 36, RPi GPIO16)
pin: 57
gpiochip: 1
line: 25
Busy: # GPIO0_C1_d (phys 38, RPi GPIO20)
pin: 17
gpiochip: 0
line: 17
Reset: # GPIO0_B4_d (phys 18, RPi GPIO24)
pin: 12
gpiochip: 0
line: 12
RXen: # GPIO1_D2_d (phys 32, RPi GPIO12)
pin: 58
gpiochip: 1
line: 26
DIO2_AS_RF_SWITCH: true
DIO3_TCXO_VOLTAGE: true
spidev: spidev0.0
@@ -1,38 +0,0 @@
Meta:
name: RAK6421 + RAK13300 Slot 1
support: community # Promote when tested
compatible:
- luckfox-lyra-zero-w # Armbian
Lora:
Module: sx1262
IRQ: # GPIO0_A5 (IO6)
pin: 5
gpiochip: 0
line: 5
Reset: # GPIO1_D1 (IO4)
pin: 57
gpiochip: 1
line: 25
Busy: # GPIO0_B4 (IO5)
pin: 12
gpiochip: 0
line: 12
# Ant_sw: # GPIO1_C2 (IO3)
# pin: 50
# gpiochip: 1
# line: 18
Enable_Pins:
- pin: 58 # GPIO1_D2
gpiochip: 1
line: 26
- pin: 50 # GPIO1_C2
gpiochip: 1
line: 18
DIO3_TCXO_VOLTAGE: true
DIO2_AS_RF_SWITCH: true
spidev: spidev0.0
# CS: # GPIO0_B2
# pin: 10
# gpiochip: 0
# line: 10
@@ -1,36 +0,0 @@
Meta:
name: RAK6421 + RAK13300 Slot 2
support: community # Promote when tested
compatible:
- luckfox-lyra-zero-w # Armbian
Lora:
Module: sx1262
IRQ: # GPIO0_B6 (IO6)
pin: 14
gpiochip: 0
line: 14
Reset: # GPIO0_B4 (IO4)
pin: 12
gpiochip: 0
line: 12
Busy: # GPIO1_C0 (IO5)
pin: 48
gpiochip: 1
line: 16
# Ant_sw: # GPIO0_B5 (IO3)
# pin: 13
# gpiochip: 0
# line: 13
Enable_Pins:
- pin: 51 # GPIO1_C3
gpiochip: 1
line: 19
- pin: 13 # GPIO0_B5
gpiochip: 0
line: 13
spidev: spidev0.1
# CS: # GPIO0_B1
# pin: 9
# gpiochip: 0
# line: 9
@@ -1,39 +0,0 @@
Meta:
name: RAK6421 + RAK13300 Slot 1
support: community # Promote when tested
compatible:
- luckfox-lyra-zero-w # Armbian
Lora:
Module: sx1262
IRQ: # GPIO0_A5 (IO6)
pin: 5
gpiochip: 0
line: 5
Reset: # GPIO1_D1 (IO4)
pin: 57
gpiochip: 1
line: 25
Busy: # GPIO0_B4 (IO5)
pin: 12
gpiochip: 0
line: 12
# Ant_sw: # GPIO1_C2 (IO3)
# pin: 50
# gpiochip: 1
# line: 18
Enable_Pins:
- pin: 58 # GPIO1_D2
gpiochip: 1
line: 26
- pin: 50 # GPIO1_C2
gpiochip: 1
line: 18
DIO3_TCXO_VOLTAGE: true
DIO2_AS_RF_SWITCH: true
spidev: spidev0.0
# CS: # GPIO0_B2
# pin: 10
# gpiochip: 0
# line: 10
TX_GAIN_LORA: [9, 9, 10, 11, 9, 8, 9, 10, 10, 10, 11, 12, 12, 12, 12, 12, 12, 12, 12, 10, 9, 8]
@@ -1,37 +0,0 @@
Meta:
name: RAK6421 + RAK13300 Slot 2
support: community # Promote when tested
compatible:
- luckfox-lyra-zero-w # Armbian
Lora:
Module: sx1262
IRQ: # GPIO0_B6 (IO6)
pin: 14
gpiochip: 0
line: 14
Reset: # GPIO0_B4 (IO4)
pin: 12
gpiochip: 0
line: 12
Busy: # GPIO1_C0 (IO5)
pin: 48
gpiochip: 1
line: 16
# Ant_sw: # GPIO0_B5 (IO3)
# pin: 13
# gpiochip: 0
# line: 13
Enable_Pins:
- pin: 51 # GPIO1_C3
gpiochip: 1
line: 19
- pin: 13 # GPIO0_B5
gpiochip: 0
line: 13
spidev: spidev0.1
# CS: # GPIO0_B1
# pin: 9
# gpiochip: 0
# line: 9
TX_GAIN_LORA: [9, 9, 10, 11, 9, 8, 9, 10, 10, 10, 11, 12, 12, 12, 12, 12, 12, 12, 12, 10, 9, 8]
@@ -1,30 +0,0 @@
Meta:
name: Waveshare SX1262
support: deprecated
compatible:
- luckfox-lyra-zero-w # Armbian
Lora:
Module: sx1262 # Waveshare SX126X XXXM
DIO2_AS_RF_SWITCH: true
CS: # GPIO0_C2 (physical 40)
pin: 18
gpiochip: 0
line: 18
IRQ: # GPIO1_D1 (physical 36)
pin: 57
gpiochip: 1
line: 25
Busy: # GPIO0_C1 (physical 38)
pin: 17
gpiochip: 0
line: 17
Reset: # GPIO0_B6 (physical 12)
pin: 14
gpiochip: 0
line: 14
SX126X_ANT_SW: # GPIO1_B3 (physical 31)
pin: 43
gpiochip: 1
line: 11
spidev: spidev0.0
@@ -1,41 +0,0 @@
# !! WARNING: Hats on the OK3506 board are installed "backwards" (facing outwards)
# MeshAdv-Pi E22-900M30S
# https://github.com/chrismyers2000/MeshAdv-Pi-Hat
Meta:
name: MeshAdv-Pi E22-900M30S
support: community
compatible:
- forlinx-ok3506-s12 # Armbian
Lora:
Module: sx1262
CS: # GPIO0_B0 (physical 40)
pin: 8
gpiochip: 0
line: 8
IRQ: # GPIO3_B0 (physical 36)
pin: 104
gpiochip: 3
line: 8
Busy: # GPIO0_B1 (physical 38)
pin: 9
gpiochip: 0
line: 9
Reset: # GPIO0_B6 (physical 12)
pin: 14
gpiochip: 0
line: 14
TXen: # GPIO0_A3 (physical 33)
pin: 3
gpiochip: 0
line: 3
RXen: # GPIO0_A2 (physical 32)
pin: 2
gpiochip: 0
line: 2
DIO3_TCXO_VOLTAGE: true
# Only for E22-900M33S:
# Limit the output power to 8 dBm
# SX126X_MAX_POWER: 8
spidev: spidev0.0
@@ -1,35 +0,0 @@
# !! WARNING: Hats on the OK3506 board are installed "backwards" (facing outwards)
# MeshAdv Mini E22-900M22S
# https://github.com/chrismyers2000/MeshAdv-Mini
Meta:
name: MeshAdv Mini E22-900M22S
support: community
compatible:
- forlinx-ok3506-s12 # Armbian
Lora:
Module: sx1262 # Ebyte E22-900M22S
CS: # GPIO0_C3 (physical 24, SPI0_CSN0)
pin: 19
gpiochip: 0
line: 19
IRQ: # GPIO3_B0 (physical 36)
pin: 104
gpiochip: 3
line: 8
Busy: # GPIO0_B1 (physical 38)
pin: 9
gpiochip: 0
line: 9
Reset: # GPIO3_A6 (physical 18)
pin: 102
gpiochip: 3
line: 6
RXen: # GPIO0_A2 (physical 32)
pin: 2
gpiochip: 0
line: 2
DIO2_AS_RF_SWITCH: true
DIO3_TCXO_VOLTAGE: true
spidev: spidev0.0
@@ -1,40 +0,0 @@
# !! WARNING: Hats on the OK3506 board are installed "backwards" (facing outwards)
Meta:
name: RAK6421 + RAK13300 Slot 1
support: community # Promote when tested
compatible:
- forlinx-ok3506-s12 # Armbian
Lora:
Module: sx1262
IRQ: # GPIO3_B5 (IO6, physical 15)
pin: 109
gpiochip: 3
line: 13
Reset: # GPIO3_B0 (IO4, physical 36)
pin: 104
gpiochip: 3
line: 8
Busy: # GPIO3_A6 (IO5, physical 18)
pin: 102
gpiochip: 3
line: 6
# Ant_sw: # GPIO0_A3 (IO3, physical 33)
# pin: 3
# gpiochip: 0
# line: 3
Enable_Pins:
- pin: 2 # GPIO0_A2 (physical 32)
gpiochip: 0
line: 2
- pin: 3 # GPIO0_A3 (physical 33)
gpiochip: 0
line: 3
DIO3_TCXO_VOLTAGE: true
DIO2_AS_RF_SWITCH: true
spidev: spidev0.0
# CS: # GPIO0_C3 (SPI0_CSN0, physical 24)
# pin: 19
# gpiochip: 0
# line: 19
@@ -1,38 +0,0 @@
# !! WARNING: Hats on the OK3506 board are installed "backwards" (facing outwards)
Meta:
name: RAK6421 + RAK13300 Slot 2
support: community # Promote when tested
compatible:
- forlinx-ok3506-s12 # Armbian
Lora:
Module: sx1262
IRQ: # GPIO0_B6 (IO6, physical 12)
pin: 14
gpiochip: 0
line: 14
Reset: # GPIO3_A6 (IO4, physical 18)
pin: 102
gpiochip: 3
line: 6
Busy: # GPIO0_B2 (IO5, physical 35)
pin: 10
gpiochip: 0
line: 10
# Ant_sw: # GPIO3_A7 (IO3, physical 16)
# pin: 103
# gpiochip: 3
# line: 7
Enable_Pins:
- pin: 106 # GPIO3_B2 (physical 37)
gpiochip: 3
line: 10
- pin: 103 # GPIO3_A7 (physical 16)
gpiochip: 3
line: 7
spidev: spidev0.1
# CS: # GPIO0_B7 (SPI0_CSN1, physical 26)
# pin: 15
# gpiochip: 0
# line: 15
@@ -1,41 +0,0 @@
# !! WARNING: Hats on the OK3506 board are installed "backwards" (facing outwards)
Meta:
name: RAK6421 + RAK13302 Slot 1
support: community # Promote when tested
compatible:
- forlinx-ok3506-s12 # Armbian
Lora:
Module: sx1262
IRQ: # GPIO3_B5 (IO6, physical 15)
pin: 109
gpiochip: 3
line: 13
Reset: # GPIO3_B0 (IO4, physical 36)
pin: 104
gpiochip: 3
line: 8
Busy: # GPIO3_A6 (IO5, physical 18)
pin: 102
gpiochip: 3
line: 6
# Ant_sw: # GPIO0_A3 (IO3, physical 33)
# pin: 3
# gpiochip: 0
# line: 3
Enable_Pins:
- pin: 2 # GPIO0_A2 (physical 32)
gpiochip: 0
line: 2
- pin: 3 # GPIO0_A3 (physical 33)
gpiochip: 0
line: 3
DIO3_TCXO_VOLTAGE: true
DIO2_AS_RF_SWITCH: true
spidev: spidev0.0
# CS: # GPIO0_C3 (SPI0_CSN0, physical 24)
# pin: 19
# gpiochip: 0
# line: 19
TX_GAIN_LORA: [9, 9, 10, 11, 9, 8, 9, 10, 10, 10, 11, 12, 12, 12, 12, 12, 12, 12, 12, 10, 9, 8]
@@ -1,39 +0,0 @@
# !! WARNING: Hats on the OK3506 board are installed "backwards" (facing outwards)
Meta:
name: RAK6421 + RAK13302 Slot 2
support: community # Promote when tested
compatible:
- forlinx-ok3506-s12 # Armbian
Lora:
Module: sx1262
IRQ: # GPIO0_B6 (IO6, physical 12)
pin: 14
gpiochip: 0
line: 14
Reset: # GPIO3_A6 (IO4, physical 18)
pin: 102
gpiochip: 3
line: 6
Busy: # GPIO0_B2 (IO5, physical 35)
pin: 10
gpiochip: 0
line: 10
# Ant_sw: # GPIO3_A7 (IO3, physical 16)
# pin: 103
# gpiochip: 3
# line: 7
Enable_Pins:
- pin: 106 # GPIO3_B2 (physical 37)
gpiochip: 3
line: 10
- pin: 103 # GPIO3_A7 (physical 16)
gpiochip: 3
line: 7
spidev: spidev0.1
# CS: # GPIO0_B7 (SPI0_CSN1, physical 26)
# pin: 15
# gpiochip: 0
# line: 15
TX_GAIN_LORA: [9, 9, 10, 11, 9, 8, 9, 10, 10, 10, 11, 12, 12, 12, 12, 12, 12, 12, 12, 10, 9, 8]
@@ -1,32 +0,0 @@
# !! WARNING: Hats on the OK3506 board are installed "backwards" (facing outwards)
Meta:
name: Waveshare SX1262
support: deprecated
compatible:
- forlinx-ok3506-s12 # Armbian
Lora:
Module: sx1262 # Waveshare SX126X XXXM
DIO2_AS_RF_SWITCH: true
CS: # GPIO0_B0 (physical 40)
pin: 8
gpiochip: 0
line: 8
IRQ: # GPIO3_B0 (physical 36)
pin: 104
gpiochip: 3
line: 8
Busy: # GPIO0_B1 (physical 38)
pin: 9
gpiochip: 0
line: 9
Reset: # GPIO0_B6 (physical 12)
pin: 14
gpiochip: 0
line: 14
SX126X_ANT_SW: # GPIO3_B3 (physical 31)
pin: 107
gpiochip: 3
line: 11
spidev: spidev0.0
+29 -54
View File
@@ -1,31 +1,25 @@
#!/usr/bin/env python3
"""Generate release notes from the actual release commit range."""
"""
Generate release notes from merged PRs on develop and master branches.
Categorizes PRs into Enhancements and Bug Fixes/Maintenance sections.
"""
import argparse
import json
import re
import subprocess
import re
import json
import sys
from datetime import datetime
def get_last_release_tag(compare_ref, exclude_tag=None):
"""Get the most recent version tag merged into compare_ref."""
def get_last_release_tag():
"""Get the most recent release tag."""
result = subprocess.run(
["git", "tag", "--merged", compare_ref, "--sort=-version:refname", "v*"],
["git", "describe", "--tags", "--abbrev=0"],
capture_output=True,
text=True,
check=True,
)
for line in result.stdout.splitlines():
candidate = line.strip()
if not candidate:
continue
if exclude_tag and candidate == exclude_tag:
continue
return candidate
raise subprocess.CalledProcessError(result.returncode, result.args, output=result.stdout, stderr=result.stderr)
return result.stdout.strip()
def get_tag_date(tag):
@@ -39,18 +33,18 @@ def get_tag_date(tag):
return result.stdout.strip()
def get_merged_prs_in_range(tag, compare_ref):
"""Get all merged PRs in the git range between tag and compare_ref."""
def get_merged_prs_since_tag(tag, branch):
"""Get all merged PRs since the given tag on the specified branch."""
# Get commits since tag on the branch - look for PR numbers in parentheses
result = subprocess.run(
[
"git",
"log",
f"{tag}..{compare_ref}",
f"{tag}..origin/{branch}",
"--oneline",
],
capture_output=True,
text=True,
check=True,
)
prs = []
@@ -71,25 +65,6 @@ def get_merged_prs_in_range(tag, compare_ref):
return prs
def parse_args():
"""Parse CLI arguments."""
parser = argparse.ArgumentParser(
description="Generate release notes from the actual release commit range."
)
parser.add_argument("new_version", help="Version that will be tagged for this release")
parser.add_argument(
"--base-tag",
dest="base_tag",
help="Existing version tag to diff from. Defaults to the latest version tag merged into the compare ref.",
)
parser.add_argument(
"--compare-ref",
default="HEAD",
help="Git ref to diff to. Defaults to HEAD.",
)
return parser.parse_args()
def get_pr_details(pr_number):
"""Get PR details from GitHub API via gh CLI."""
try:
@@ -293,28 +268,28 @@ def get_new_contributors(pr_details_list, tag, repo="meshtastic/firmware"):
def main():
args = parse_args()
new_version = args.new_version
compare_ref = args.compare_ref
current_tag = f"v{new_version}"
if len(sys.argv) < 2:
print("Usage: generate_release_notes.py <new_version>", file=sys.stderr)
sys.exit(1)
new_version = sys.argv[1]
# Get last release tag
try:
last_tag = args.base_tag or get_last_release_tag(compare_ref, exclude_tag=current_tag)
last_tag = get_last_release_tag()
except subprocess.CalledProcessError:
print("Error: Could not find last release tag", file=sys.stderr)
sys.exit(1)
print(
f"Resolved release note range: {last_tag}..{compare_ref}",
file=sys.stderr,
)
# Collect PRs from both branches
all_pr_numbers = set()
try:
all_pr_numbers = set(get_merged_prs_in_range(last_tag, compare_ref))
except subprocess.CalledProcessError as e:
print(f"Error: Could not get PRs for range {last_tag}..{compare_ref}: {e}", file=sys.stderr)
sys.exit(1)
for branch in ["develop", "master"]:
try:
prs = get_merged_prs_since_tag(last_tag, branch)
all_pr_numbers.update(prs)
except Exception as e:
print(f"Warning: Could not get PRs from {branch}: {e}", file=sys.stderr)
# Get details for all PRs
enhancements = []
@@ -87,12 +87,6 @@
</screenshots>
<releases>
<release version="2.7.23" date="2026-04-14">
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.23</url>
</release>
<release version="2.7.22" date="2026-04-06">
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.22</url>
</release>
<release version="2.7.21" date="2026-03-11">
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.21</url>
</release>
-12
View File
@@ -1,15 +1,3 @@
meshtasticd (2.7.23.0) unstable; urgency=medium
* Version 2.7.23
-- GitHub Actions <github-actions[bot]@users.noreply.github.com> Tue, 14 Apr 2026 12:29:48 +0000
meshtasticd (2.7.22.0) unstable; urgency=medium
* Version 2.7.22
-- GitHub Actions <github-actions[bot]@users.noreply.github.com> Mon, 06 Apr 2026 11:34:12 +0000
meshtasticd (2.7.21.0) unstable; urgency=medium
* Version 2.7.21
-1
View File
@@ -14,7 +14,6 @@ Build-Depends: debhelper-compat (= 13),
g++,
pkg-config,
libyaml-cpp-dev,
libjsoncpp-dev,
libgpiod-dev,
libbluetooth-dev,
libusb-1.0-0-dev,
-1
View File
@@ -34,7 +34,6 @@ BuildRequires: python3dist(grpcio-tools)
BuildRequires: git-core
BuildRequires: gcc-c++
BuildRequires: pkgconfig(yaml-cpp)
BuildRequires: pkgconfig(jsoncpp)
BuildRequires: pkgconfig(libgpiod)
BuildRequires: pkgconfig(bluez)
BuildRequires: pkgconfig(libusb-1.0)
+85 -93
View File
@@ -56,7 +56,9 @@ build_flags = -Wno-missing-field-initializers
-DMESHTASTIC_EXCLUDE_POWERSTRESS=1 ; exclude power stress test module from main firmware
-DMESHTASTIC_EXCLUDE_GENERIC_THREAD_MODULE=1
-DMESHTASTIC_EXCLUDE_POWERMON=1
-DMESHTASTIC_EXCLUDE_STATUS=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
@@ -124,122 +126,112 @@ 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/5305670b68eb5b92d14e62b5b536969ca4bb441f.zip
https://github.com/meshtastic/device-ui/archive/7b1485b86c8d55a40e5226749097767e8b87f396.zip
; Common libs for environmental measurements in telemetry module
[environmental_base]
lib_deps =
# renovate: datasource=github-tags depName=Adafruit BusIO packageName=adafruit/Adafruit_BusIO
https://github.com/adafruit/Adafruit_BusIO/archive/refs/tags/1.17.4.zip
# renovate: datasource=github-tags depName=Adafruit Unified Sensor packageName=adafruit/Adafruit_Sensor
https://github.com/adafruit/Adafruit_Sensor/archive/refs/tags/1.1.15.zip
# renovate: datasource=github-tags depName=Adafruit GFX packageName=adafruit/Adafruit-GFX-Library
https://github.com/adafruit/Adafruit-GFX-Library/archive/refs/tags/1.12.6.zip
# renovate: datasource=github-tags depName=NeoPixel packageName=adafruit/Adafruit_NeoPixel
https://github.com/adafruit/Adafruit_NeoPixel/archive/refs/tags/1.15.4.zip
# renovate: datasource=github-tags depName=Adafruit SSD1306 packageName=adafruit/Adafruit_SSD1306
https://github.com/adafruit/Adafruit_SSD1306/archive/refs/tags/2.5.16.zip
# renovate: datasource=github-tags depName=Adafruit BMP280 packageName=adafruit/Adafruit_BMP280_Library
https://github.com/adafruit/Adafruit_BMP280_Library/archive/refs/tags/3.0.0.zip
# renovate: datasource=github-tags depName=Adafruit BMP085 packageName=adafruit/Adafruit-BMP085-Library
https://github.com/adafruit/Adafruit-BMP085-Library/archive/refs/tags/1.2.4.zip
# renovate: datasource=github-tags depName=Adafruit BME280 packageName=adafruit/Adafruit_BME280_Library
https://github.com/adafruit/Adafruit_BME280_Library/archive/refs/tags/2.3.0.zip
# renovate: datasource=github-tags depName=Adafruit DPS310 packageName=adafruit/Adafruit_DPS310
https://github.com/adafruit/Adafruit_DPS310/archive/refs/tags/1.1.6.zip
# renovate: datasource=github-tags depName=Adafruit SH110x packageName=adafruit/Adafruit_SH110x
https://github.com/adafruit/Adafruit_SH110x/archive/refs/tags/2.1.14.zip
# renovate: datasource=github-tags depName=Adafruit MCP9808 packageName=adafruit/Adafruit_MCP9808_Library
https://github.com/adafruit/Adafruit_MCP9808_Library/archive/refs/tags/2.0.2.zip
# renovate: datasource=github-tags depName=Adafruit INA260 packageName=adafruit/Adafruit_INA260
https://github.com/adafruit/Adafruit_INA260/archive/refs/tags/1.5.3.zip
# renovate: datasource=github-tags depName=Adafruit INA219 packageName=adafruit/Adafruit_INA219
https://github.com/adafruit/Adafruit_INA219/archive/refs/tags/1.2.3.zip
# renovate: datasource=github-tags depName=Adafruit MPU6050 packageName=adafruit/Adafruit_MPU6050
https://github.com/adafruit/Adafruit_MPU6050/archive/refs/tags/2.2.9.zip
# renovate: datasource=github-tags depName=Adafruit LIS3DH packageName=adafruit/Adafruit_LIS3DH
https://github.com/adafruit/Adafruit_LIS3DH/archive/refs/tags/1.3.0.zip
# renovate: datasource=github-tags depName=Adafruit AHTX0 packageName=adafruit/Adafruit_AHTX0
https://github.com/adafruit/Adafruit_AHTX0/archive/refs/tags/2.0.6.zip
# renovate: datasource=github-tags depName=Adafruit LSM6DS packageName=adafruit/Adafruit_LSM6DS
https://github.com/adafruit/Adafruit_LSM6DS/archive/refs/tags/4.7.4.zip
# renovate: datasource=github-tags depName=Adafruit TSL2591 packageName=adafruit/Adafruit_TSL2591_Library
https://github.com/adafruit/Adafruit_TSL2591_Library/archive/refs/tags/1.4.5.zip
# renovate: datasource=github-tags depName=EmotiBit MLX90632 packageName=emotibit/EmotiBit_MLX90632
https://github.com/EmotiBit/EmotiBit_MLX90632/archive/refs/tags/v1.0.8.zip
# renovate: datasource=github-tags depName=Adafruit MLX90614 packageName=adafruit/Adafruit_MLX90614
https://github.com/adafruit/Adafruit-MLX90614-Library/archive/refs/tags/2.1.6.zip
# renovate: datasource=custom.pio depName=Adafruit BusIO packageName=adafruit/library/Adafruit BusIO
adafruit/Adafruit BusIO@1.17.4
# renovate: datasource=custom.pio depName=Adafruit Unified Sensor packageName=adafruit/library/Adafruit Unified Sensor
adafruit/Adafruit Unified Sensor@1.1.15
# renovate: datasource=custom.pio depName=Adafruit BMP280 packageName=adafruit/library/Adafruit BMP280 Library
adafruit/Adafruit BMP280 Library@3.0.0
# renovate: datasource=custom.pio depName=Adafruit BMP085 packageName=adafruit/library/Adafruit BMP085 Library
adafruit/Adafruit BMP085 Library@1.2.4
# renovate: datasource=custom.pio depName=Adafruit BME280 packageName=adafruit/library/Adafruit BME280 Library
adafruit/Adafruit BME280 Library@2.3.0
# renovate: datasource=custom.pio depName=Adafruit DPS310 packageName=adafruit/library/Adafruit DPS310
adafruit/Adafruit DPS310@1.1.6
# renovate: datasource=custom.pio depName=Adafruit MCP9808 packageName=adafruit/library/Adafruit MCP9808 Library
adafruit/Adafruit MCP9808 Library@2.0.2
# renovate: datasource=custom.pio depName=Adafruit INA260 packageName=adafruit/library/Adafruit INA260 Library
adafruit/Adafruit INA260 Library@1.5.3
# renovate: datasource=custom.pio depName=Adafruit INA219 packageName=adafruit/library/Adafruit INA219
adafruit/Adafruit INA219@1.2.3
# renovate: datasource=custom.pio depName=Adafruit MPU6050 packageName=adafruit/library/Adafruit MPU6050
adafruit/Adafruit MPU6050@2.2.9
# renovate: datasource=custom.pio depName=Adafruit LIS3DH packageName=adafruit/library/Adafruit LIS3DH
adafruit/Adafruit LIS3DH@1.3.0
# renovate: datasource=custom.pio depName=Adafruit AHTX0 packageName=adafruit/library/Adafruit AHTX0
adafruit/Adafruit AHTX0@2.0.6
# renovate: datasource=custom.pio depName=Adafruit LSM6DS packageName=adafruit/library/Adafruit LSM6DS
adafruit/Adafruit LSM6DS@4.7.4
# renovate: datasource=custom.pio depName=Adafruit TSL2591 packageName=adafruit/library/Adafruit TSL2591 Library
adafruit/Adafruit TSL2591 Library@1.4.5
# renovate: datasource=custom.pio depName=EmotiBit MLX90632 packageName=emotibit/library/EmotiBit MLX90632
emotibit/EmotiBit MLX90632@1.0.8
# renovate: datasource=custom.pio depName=Adafruit MLX90614 packageName=adafruit/library/Adafruit MLX90614 Library
adafruit/Adafruit MLX90614 Library@2.1.6
# renovate: datasource=git-refs depName=INA3221 packageName=https://github.com/sgtwilko/INA3221 gitBranch=FixOverflow
https://github.com/sgtwilko/INA3221/archive/bb03d7e9bfcc74fc798838a54f4f99738f29fc6a.zip
# renovate: datasource=github-tags depName=QMC5883L Compass packageName=mprograms/QMC5883LCompass
https://github.com/mprograms/QMC5883LCompass/archive/refs/tags/v1.2.3.zip
# renovate: datasource=github-tags depName=DFRobot_RTU packageName=dfrobot/DFRobot_RTU
https://github.com/DFRobot/DFRobot_RTU/archive/refs/tags/V1.0.6.zip
# renovate: datasource=custom.pio depName=QMC5883L Compass packageName=mprograms/library/QMC5883LCompass
mprograms/QMC5883LCompass@1.2.3
# renovate: datasource=custom.pio depName=DFRobot_RTU packageName=dfrobot/library/DFRobot_RTU
dfrobot/DFRobot_RTU@1.0.6
# renovate: datasource=git-refs depName=DFRobot_RainfallSensor packageName=https://github.com/DFRobot/DFRobot_RainfallSensor gitBranch=master
https://github.com/DFRobot/DFRobot_RainfallSensor/archive/38fea5e02b40a5430be6dab39a99a6f6347d667e.zip
# renovate: datasource=github-tags depName=INA226 packageName=robtillaart/INA226
https://github.com/RobTillaart/INA226/archive/refs/tags/0.6.6.zip
# renovate: datasource=github-tags depName=SparkFun MAX3010x packageName=sparkfun/SparkFun_MAX3010x_Sensor_Library
https://github.com/sparkfun/SparkFun_MAX3010x_Sensor_Library/archive/refs/tags/v1.1.2.zip
# renovate: datasource=github-tags depName=SparkFun 9DoF IMU Breakout ICM 20948 packageName=sparkfun/SparkFun_ICM-20948_ArduinoLibrary
https://github.com/sparkfun/SparkFun_ICM-20948_ArduinoLibrary/archive/refs/tags/v1.3.2.zip
# renovate: datasource=github-tags depName=Adafruit LTR390 Library packageName=adafruit/Adafruit_LTR390
https://github.com/adafruit/Adafruit_LTR390/archive/refs/tags/1.1.2.zip
# renovate: datasource=github-tags depName=Adafruit PCT2075 packageName=adafruit/Adafruit_PCT2075
https://github.com/adafruit/Adafruit_PCT2075/archive/refs/tags/1.0.6.zip
# renovate: datasource=github-tags depName=DFRobot_BMM150 packageName=dfrobot/DFRobot_BMM150
https://github.com/DFRobot/DFRobot_BMM150/archive/refs/tags/V1.0.0.zip
# renovate: datasource=github-tags depName=Adafruit_TSL2561 packageName=adafruit/Adafruit_TSL2561
https://github.com/adafruit/Adafruit_TSL2561/archive/refs/tags/1.1.3.zip
# renovate: datasource=github-tags depName=BH1750_WE packageName=wollewald/BH1750_WE
https://github.com/wollewald/BH1750_WE/archive/refs/tags/1.1.10.zip
# renovate: datasource=custom.pio depName=INA226 packageName=robtillaart/library/INA226
robtillaart/INA226@0.6.6
# renovate: datasource=custom.pio depName=SparkFun MAX3010x packageName=sparkfun/library/SparkFun MAX3010x Pulse and Proximity Sensor Library
sparkfun/SparkFun MAX3010x Pulse and Proximity Sensor Library@1.1.2
# renovate: datasource=custom.pio depName=SparkFun 9DoF IMU Breakout ICM 20948 packageName=sparkfun/library/SparkFun 9DoF IMU Breakout - ICM 20948 - Arduino Library
sparkfun/SparkFun 9DoF IMU Breakout - ICM 20948 - Arduino Library@1.3.2
# renovate: datasource=custom.pio depName=Adafruit LTR390 Library packageName=adafruit/library/Adafruit LTR390 Library
adafruit/Adafruit LTR390 Library@1.1.2
# renovate: datasource=custom.pio depName=Adafruit PCT2075 packageName=adafruit/library/Adafruit PCT2075
adafruit/Adafruit PCT2075@1.0.6
# renovate: datasource=custom.pio depName=DFRobot_BMM150 packageName=dfrobot/library/DFRobot_BMM150
dfrobot/DFRobot_BMM150@1.0.0
# renovate: datasource=custom.pio depName=Adafruit_TSL2561 packageName=adafruit/library/Adafruit TSL2561
adafruit/Adafruit TSL2561@1.1.3
# renovate: datasource=custom.pio depName=BH1750_WE packageName=wollewald/library/BH1750_WE
wollewald/BH1750_WE@1.1.10
; Common environmental sensor libraries (not included in native / portduino)
[environmental_extra_common]
lib_deps =
# renovate: datasource=github-tags depName=Adafruit BMP3XX packageName=adafruit/Adafruit_BMP3XX
https://github.com/adafruit/Adafruit_BMP3XX/archive/refs/tags/2.1.6.zip
# renovate: datasource=github-tags depName=Adafruit MAX1704X packageName=adafruit/Adafruit_MAX1704X
https://github.com/adafruit/Adafruit_MAX1704X/archive/refs/tags/1.0.3.zip
# renovate: datasource=github-tags depName=Adafruit SHTC3 packageName=adafruit/Adafruit_SHTC3
https://github.com/adafruit/Adafruit_SHTC3/archive/refs/tags/1.0.2.zip
# renovate: datasource=github-tags depName=Adafruit LPS2X packageName=adafruit/Adafruit_LPS2X
https://github.com/adafruit/Adafruit_LPS2X/archive/refs/tags/2.0.6.zip
# renovate: datasource=github-tags depName=Adafruit SHT31 packageName=adafruit/Adafruit_SHT31
https://github.com/adafruit/Adafruit_SHT31/archive/refs/tags/2.2.2.zip
# renovate: datasource=github-tags depName=Adafruit VEML7700 packageName=adafruit/Adafruit_VEML7700
https://github.com/adafruit/Adafruit_VEML7700/archive/refs/tags/2.1.6.zip
# renovate: datasource=github-tags depName=Adafruit SHT4x packageName=adafruit/Adafruit_SHT4X
https://github.com/adafruit/Adafruit_SHT4X/archive/refs/tags/1.0.5.zip
# renovate: datasource=github-tags depName=SparkFun Qwiic Scale NAU7802 packageName=sparkfun/SparkFun_Qwiic_Scale_NAU7802_Arduino_Library
https://github.com/sparkfun/SparkFun_Qwiic_Scale_NAU7802_Arduino_Library/archive/refs/tags/v1.0.6.zip
# 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=github-tags depName=Sensirion Core packageName=sensirion/arduino-core
https://github.com/Sensirion/arduino-core/archive/refs/tags/0.7.3.zip
# renovate: datasource=github-tags depName=Sensirion I2C SCD4x packageName=sensirion/arduino-i2c-scd4x
https://github.com/Sensirion/arduino-i2c-scd4x/archive/refs/tags/1.1.0.zip
# renovate: datasource=github-tags depName=Sensirion I2C SFA3x packageName=sensirion/arduino-i2c-sfa3x
https://github.com/Sensirion/arduino-i2c-sfa3x/archive/refs/tags/1.0.0.zip
# renovate: datasource=github-tags depName=Sensirion I2C SCD30 packageName=sensirion/arduino-i2c-scd30
https://github.com/Sensirion/arduino-i2c-scd30/archive/refs/tags/1.0.0.zip
# renovate: datasource=github-tags depName=arduino-sht packageName=sensirion/arduino-sht
https://github.com/Sensirion/arduino-sht/archive/refs/tags/v1.2.6.zip
# renovate: datasource=custom.pio depName=Sensirion Core packageName=sensirion/library/Sensirion Core
sensirion/Sensirion Core@0.7.3
# renovate: datasource=custom.pio depName=Sensirion I2C SCD4x packageName=sensirion/library/Sensirion I2C SCD4x
sensirion/Sensirion I2C SCD4x@1.1.0
# renovate: datasource=custom.pio depName=Sensirion I2C SFA3x packageName=sensirion/library/Sensirion I2C SFA3x
sensirion/Sensirion I2C SFA3x@1.0.0
# renovate: datasource=custom.pio depName=Sensirion I2C SCD30 packageName=sensirion/library/Sensirion I2C SCD30
sensirion/Sensirion I2C SCD30@1.0.0
; Environmental sensors with BSEC2 (Bosch proprietary IAQ)
[environmental_extra]
lib_deps =
${environmental_extra_common.lib_deps}
# renovate: datasource=github-tags depName=Bosch BSEC2 packageName=boschsensortec/Bosch-BSEC2-Library
https://github.com/boschsensortec/Bosch-BSEC2-Library/archive/refs/tags/1.10.2610.zip
# renovate: datasource=github-tags depName=Bosch BME68x packageName=boschsensortec/Bosch-BME68x-Library
https://github.com/boschsensortec/Bosch-BME68x-Library/archive/refs/tags/v1.3.40408.zip
# renovate: datasource=custom.pio depName=Bosch BSEC2 packageName=boschsensortec/library/bsec2
boschsensortec/bsec2@1.10.2610
# renovate: datasource=custom.pio depName=Bosch BME68x packageName=boschsensortec/library/BME68x Sensor Library
boschsensortec/BME68x Sensor Library@1.3.40408
; Environmental sensors without BSEC (saves ~3.5KB DRAM for original ESP32 targets)
[environmental_extra_no_bsec]
lib_deps =
${environmental_extra_common.lib_deps}
# renovate: datasource=github-tags depName=Adafruit_BME680 packageName=adafruit/Adafruit_BME680
https://github.com/adafruit/Adafruit_BME680/archive/refs/tags/2.0.6.zip
# renovate: datasource=custom.pio depName=Adafruit_BME680 packageName=adafruit/library/Adafruit BME680 Library
adafruit/Adafruit BME680 Library@2.0.6
-7
View File
@@ -21,15 +21,8 @@
// How many messages are stored (RAM + flash).
// Define -DMESSAGE_HISTORY_LIMIT=N in build_flags to control memory usage.
#ifndef MESSAGE_HISTORY_LIMIT
#if defined(ARCH_ESP32) && \
!(defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32S2))
// Baseline ESP32 (non-PSRAM variants) has limited heap; reduce message history on resource-constrained builds.
// Override with -DMESSAGE_HISTORY_LIMIT=N if needed.
#define MESSAGE_HISTORY_LIMIT 10
#else
#define MESSAGE_HISTORY_LIMIT 20
#endif
#endif
// Internal alias used everywhere in code – do NOT redefine elsewhere.
#define MAX_MESSAGES_SAVED MESSAGE_HISTORY_LIMIT
+5 -34
View File
@@ -40,22 +40,6 @@
#include "concurrency/LockGuard.h"
#endif
#if defined(ARCH_STM32WL) && defined(BATTERY_PIN)
#include "stm32yyxx_ll_adc.h"
/* Analog read resolution */
#if defined(LL_ADC_RESOLUTION_12B)
#define LL_ADC_RESOLUTION LL_ADC_RESOLUTION_12B
#define BATTERY_SENSE_RESOLUTION_BITS 12
#elif defined(LL_ADC_DS_DATA_WIDTH_12_BIT)
#define LL_ADC_RESOLUTION LL_ADC_DS_DATA_WIDTH_12_BIT
#define BATTERY_SENSE_RESOLUTION_BITS 12
#else
#error "ADC resolution could not be defined!"
#endif
#define ADC_RANGE (1 << BATTERY_SENSE_RESOLUTION_BITS)
#endif
#if defined(DEBUG_HEAP_MQTT) && !MESHTASTIC_EXCLUDE_MQTT
#include "mqtt/MQTT.h"
#include "target_specific.h"
@@ -344,17 +328,11 @@ class AnalogBatteryLevel : public HasBatteryLevel
float scaled = 0;
battery_adcEnable();
#ifdef ARCH_STM32WL
// STM32 ADC with VREFINT runtime calibration
Vref = __LL_ADC_CALC_VREFANALOG_VOLTAGE(analogRead(AVREF), LL_ADC_RESOLUTION);
raw = analogRead(BATTERY_PIN);
scaled = __LL_ADC_CALC_DATA_TO_VOLTAGE(Vref, raw, LL_ADC_RESOLUTION);
scaled *= operativeAdcMultiplier;
#elif defined(ARCH_ESP32) // ADC block for espressif platforms
#ifdef ARCH_ESP32 // ADC block for espressif platforms
raw = espAdcRead();
scaled = esp_adc_cal_raw_to_voltage(raw, adc_characs);
scaled *= operativeAdcMultiplier;
#else // block for all other platforms
#else // block for all other platforms
#ifdef ARCH_NRF52
concurrency::LockGuard saadcGuard(concurrency::nrf52SaadcLock);
#endif
@@ -552,11 +530,6 @@ class AnalogBatteryLevel : public HasBatteryLevel
bool initial_read_done = false;
float last_read_value = (OCV[NUM_OCV_POINTS - 1] * NUM_CELLS);
uint32_t last_read_time_ms = 0;
#ifdef ARCH_STM32WL
// 3300mV placeholder for STM32 errata where VREFINT factory calibration may be missing
// (e.g. STM32U0, see DS14756 Rev 3 §2.4.1 "VREFINT offset")
uint32_t Vref = 3300;
#endif
#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && defined(HAS_RAKPROT)
@@ -666,9 +639,7 @@ bool Power::analogInit()
#define BATTERY_SENSE_RESOLUTION_BITS 10
#endif
#ifdef ARCH_STM32WL
analogReadResolution(BATTERY_SENSE_RESOLUTION_BITS);
#elif defined(ARCH_ESP32) // ESP32 needs special analog stuff
#ifdef ARCH_ESP32 // ESP32 needs special analog stuff
#ifndef ADC_WIDTH // max resolution by default
static const adc_bits_width_t width = ADC_WIDTH_BIT_12;
@@ -678,7 +649,7 @@ bool Power::analogInit()
#ifndef BAT_MEASURE_ADC_UNIT // ADC1
adc1_config_width(width);
adc1_config_channel_atten(adc_channel, atten);
#else // ADC2
#else // ADC2
adc2_config_channel_atten(adc_channel, atten);
#ifndef CONFIG_IDF_TARGET_ESP32S3
// ADC2 wifi bug workaround
@@ -708,7 +679,7 @@ bool Power::analogInit()
// NRF52 ADC init moved to powerHAL_init in nrf52 platform
#if !defined(ARCH_ESP32) && !defined(ARCH_STM32WL)
#ifndef ARCH_ESP32
analogReadResolution(BATTERY_SENSE_RESOLUTION_BITS);
#endif
+3 -3
View File
@@ -51,7 +51,7 @@ size_t RedirectablePrint::write(uint8_t c)
size_t RedirectablePrint::vprintf(const char *logLevel, const char *format, va_list arg)
{
va_list copy;
#if ARCH_PORTDUINO
#if ENABLE_JSON_LOGGING || ARCH_PORTDUINO
static char printBuf[512];
#else
static char printBuf[160];
@@ -137,7 +137,7 @@ void RedirectablePrint::log_to_serial(const char *logLevel, const char *format,
if (color) {
::printf("\u001b[0m");
}
::printf("| %02d:%02d:%02d %u.%03u ", hour, min, sec, millis() / 1000, millis() % 1000);
::printf("| %02d:%02d:%02d %u ", hour, min, sec, millis() / 1000);
#else
printf("%s ", logLevel);
if (color) {
@@ -151,7 +151,7 @@ void RedirectablePrint::log_to_serial(const char *logLevel, const char *format,
if (color) {
::printf("\u001b[0m");
}
::printf("| ??:??:?? %u.%03u ", millis() / 1000, millis() % 1000);
::printf("| ??:??:?? %u ", millis() / 1000);
#else
printf("%s ", logLevel);
if (color) {
-3
View File
@@ -30,9 +30,6 @@ SerialConsole *console;
void consoleInit()
{
if (console) {
return;
}
auto sc = new SerialConsole(); // Must be dynamically allocated because we are now inheriting from thread
#if defined(SERIAL_HAS_ON_RECEIVE)
+1 -77
View File
@@ -1,85 +1,10 @@
#include "concurrency/BinarySemaphorePosix.h"
#include "configuration.h"
#include <errno.h>
#include <sys/time.h>
#ifndef HAS_FREE_RTOS
namespace concurrency
{
#ifdef ARCH_PORTDUINO
BinarySemaphorePosix::BinarySemaphorePosix()
{
if (pthread_mutex_init(&mutex, NULL) != 0) {
throw std::runtime_error("pthread_mutex_init failed");
}
if (pthread_cond_init(&cond, NULL) != 0) {
pthread_mutex_destroy(&mutex);
throw std::runtime_error("pthread_cond_init failed");
}
signaled = false;
}
BinarySemaphorePosix::~BinarySemaphorePosix()
{
pthread_cond_destroy(&cond);
pthread_mutex_destroy(&mutex);
}
/**
* Returns false if we timed out
*/
bool BinarySemaphorePosix::take(uint32_t msec)
{
pthread_mutex_lock(&mutex);
if (!signaled) {
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
ts.tv_sec += msec / 1000;
ts.tv_nsec += (msec % 1000) * 1000000L;
if (ts.tv_nsec >= 1000000000L) {
ts.tv_sec += 1;
ts.tv_nsec -= 1000000000L;
}
while (!signaled) {
int rc = pthread_cond_timedwait(&cond, &mutex, &ts);
if (rc == ETIMEDOUT)
break;
if (rc != 0) {
// Some other error occurred
pthread_mutex_unlock(&mutex);
throw std::runtime_error("pthread_cond_timedwait failed: " + std::to_string(rc));
}
}
}
bool wasSignaled = signaled;
signaled = false; // consume the signal (binary semaphore)
pthread_mutex_unlock(&mutex);
return wasSignaled;
}
void BinarySemaphorePosix::give()
{
pthread_mutex_lock(&mutex);
signaled = true;
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);
}
IRAM_ATTR void BinarySemaphorePosix::giveFromISR(BaseType_t *pxHigherPriorityTaskWoken)
{
give();
if (pxHigherPriorityTaskWoken)
*pxHigherPriorityTaskWoken = true;
}
#else
BinarySemaphorePosix::BinarySemaphorePosix() {}
@@ -97,8 +22,7 @@ bool BinarySemaphorePosix::take(uint32_t msec)
void BinarySemaphorePosix::give() {}
IRAM_ATTR void BinarySemaphorePosix::giveFromISR(BaseType_t *pxHigherPriorityTaskWoken) {}
#endif
} // namespace concurrency
#endif
#endif
+2 -11
View File
@@ -2,10 +2,6 @@
#include "../freertosinc.h"
#ifdef ARCH_PORTDUINO
#include <pthread.h>
#endif
namespace concurrency
{
@@ -13,12 +9,7 @@ namespace concurrency
class BinarySemaphorePosix
{
#ifdef ARCH_PORTDUINO
pthread_mutex_t mutex;
pthread_cond_t cond;
bool signaled;
#endif
// SemaphoreHandle_t semaphore;
public:
BinarySemaphorePosix();
@@ -36,4 +27,4 @@ class BinarySemaphorePosix
#endif
} // namespace concurrency
} // namespace concurrency
+3 -3
View File
@@ -231,7 +231,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define BME_ADDR 0x76
#define BME_ADDR_ALTERNATE 0x77
#define MCP9808_ADDR 0x18
#define INA_ADDR 0x40 // same as SHT2X
#define INA_ADDR 0x40
#define INA_ADDR_ALTERNATE 0x41
#define INA_ADDR_WAVESHARE_UPS 0x43
#define INA3221_ADDR 0x42
@@ -244,8 +244,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define LPS22HB_ADDR 0x5C
#define LPS22HB_ADDR_ALT 0x5D
#define SFA30_ADDR 0x5D
#define SHTXX_ADDR 0x44
#define SHTXX_ADDR_ALT 0x45
#define SHT31_4x_ADDR 0x44
#define SHT31_4x_ADDR_ALT 0x45
#define PMSA003I_ADDR 0x12
#define QMA6100P_ADDR 0x12
#define AHT10_ADDR 0x38
+4 -2
View File
@@ -31,6 +31,9 @@ class ScanI2C
INA3221,
MAX17048,
MCP9808,
SHT31,
SHT4X,
SHTC3,
LPS22HB,
QMC6310U,
QMC6310N,
@@ -91,8 +94,7 @@ class ScanI2C
SFA30,
CW2015,
SCD30,
ADS1115,
SHTXX
ADS1115
} DeviceType;
// typedef uint8_t DeviceAddress;
+12 -58
View File
@@ -136,9 +136,7 @@ bool ScanI2CTwoWire::i2cCommandResponseLength(ScanI2C::DeviceAddress addr, uint1
return match;
}
#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_AIR_QUALITY_SENSOR
// FIXME Move to a separate file for detection of sensors that require more complex interactions?
// For SEN5X detection
/// for SEN5X detection
// Note, this code needs to be called before setting the I2C bus speed
// for the screen at high speed. The speed needs to be at 100kHz, otherwise
// detection will not work
@@ -176,46 +174,6 @@ String readSEN5xProductName(TwoWire *i2cBus, uint8_t address)
return String(productName);
}
#endif
#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
bool detectSHT21SerialNumber(TwoWire *i2cBus, uint8_t address)
{
i2cBus->beginTransmission(address);
i2cBus->write(0xFA);
i2cBus->write(0x0F);
if (i2cBus->endTransmission() != 0)
return false;
if (i2cBus->requestFrom(address, (uint8_t)8) != 8)
return false;
// Just flush the data
while (i2cBus->available() < 8) {
i2cBus->read();
}
i2cBus->beginTransmission(address);
i2cBus->write(0xFC);
i2cBus->write(0xC9);
if (i2cBus->endTransmission() != 0)
return false;
if (i2cBus->requestFrom(address, (uint8_t)6) != 6)
return false;
// Just flush the data
while (i2cBus->available() < 6) {
i2cBus->read();
}
// Assume we detect the SHT21 if something came back from the request
return true;
}
#endif
#define SCAN_SIMPLE_CASE(ADDR, T, ...) \
case ADDR: \
@@ -413,7 +371,7 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
break;
#endif
#if !defined(M5STACK_UNITC6L)
case INA_ADDR: // Same as SHT2X
case INA_ADDR:
case INA_ADDR_ALTERNATE:
case INA_ADDR_WAVESHARE_UPS:
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0xFE), 2);
@@ -429,12 +387,7 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
logFoundDevice("INA260", (uint8_t)addr.address);
type = INA260;
}
#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
} else if (detectSHT21SerialNumber(i2cBus, (uint8_t)addr.address)) {
logFoundDevice("SHTXX (SHT2X)", (uint8_t)addr.address);
type = SHTXX;
#endif
} else { // Assume INA219 if none of the above ones are found
} else { // Assume INA219 if INA260 ID is not found
logFoundDevice("INA219", (uint8_t)addr.address);
type = INA219;
}
@@ -495,19 +448,22 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
}
break;
}
case SHTXX_ADDR: // same as OPT3001_ADDR_ALT
case SHTXX_ADDR_ALT: // same as OPT3001_ADDR
case SHT31_4x_ADDR: // same as OPT3001_ADDR_ALT
case SHT31_4x_ADDR_ALT: // same as OPT3001_ADDR
if (getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x7E), 2) == 0x5449) {
type = OPT3001;
logFoundDevice("OPT3001", (uint8_t)addr.address);
} else { // SHTXX
type = SHTXX;
logFoundDevice("SHTXX", (uint8_t)addr.address);
} else if (i2cCommandResponseLength(addr, 0x89, 6)) { // SHT4x serial number (6 bytes inc. CRC)
type = SHT4X;
logFoundDevice("SHT4X", (uint8_t)addr.address);
} else {
type = SHT31;
logFoundDevice("SHT31", (uint8_t)addr.address);
}
break;
SCAN_SIMPLE_CASE(SHTC3_ADDR, SHTXX, "SHTXX", (uint8_t)addr.address)
SCAN_SIMPLE_CASE(SHTC3_ADDR, SHTC3, "SHTC3", (uint8_t)addr.address)
case RCWL9620_ADDR:
// get MAX30102 PARTID
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0xFF), 1);
@@ -719,7 +675,6 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
logFoundDevice("BMX160", (uint8_t)addr.address);
break;
} else {
#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_AIR_QUALITY_SENSOR
String prod = "";
prod = readSEN5xProductName(i2cBus, addr.address);
if (prod.startsWith("SEN55")) {
@@ -735,7 +690,6 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
logFoundDevice("Sensirion SEN50", addr.address);
break;
}
#endif
if (addr.address == BMX160_ADDR) {
type = BMX160;
logFoundDevice("BMX160", (uint8_t)addr.address);
+1 -1
View File
@@ -202,7 +202,7 @@ void EInkParallelDisplay::display(void)
// Get pointers to internal buffers
uint8_t *cur = epaper->currentBuffer();
const uint8_t *prev = epaper->previousBuffer(); // may be NULL on first init
uint8_t *prev = epaper->previousBuffer(); // may be NULL on first init
// Track changed row range while converting
int newTop = h; // min changed row (initialized to out-of-range)
+2 -2
View File
@@ -1197,7 +1197,7 @@ void Screen::setFrames(FrameFocus focus)
for (size_t i = 0; i < nodeDB->getNumMeshNodes(); i++) {
const meshtastic_NodeInfoLite *n = nodeDB->getMeshNodeByIndex(i);
if (n && n->num != nodeDB->getNodeNum() && n->is_favorite) {
favoriteFrames.push_back(graphics::UIRenderer::drawFavoriteNode);
favoriteFrames.push_back(graphics::UIRenderer::drawNodeInfo);
}
}
@@ -1226,7 +1226,7 @@ void Screen::setFrames(FrameFocus focus)
static OverlayCallback overlays[] = {graphics::UIRenderer::drawNavigationBar, NotificationRenderer::drawBannercallback};
ui->setOverlays(overlays, sizeof(overlays) / sizeof(overlays[0]));
prevFrame = -1; // Force drawFavoriteNode to pick a new node (because our list just changed)
prevFrame = -1; // Force drawNodeInfo to pick a new node (because our list just changed)
// Focus on a specific frame, in the frame set we just created
switch (focus) {
+7 -15
View File
@@ -1254,14 +1254,14 @@ void TFTDisplay::display(bool fromBlank)
// Did we find a pixel that needs updating on this row?
if (x_FirstPixelUpdate < displayWidth) {
// Align the first pixel for update to an even number so the total alignment of
// the data will be at 32-bit boundary, which is required by GDMA SPI transfers.
x_FirstPixelUpdate &= ~1;
// Step 3a: copy rest of the pixels in this row into the pixel line buffer,
// while also recording the last pixel in the row that needs updating.
// Since the first changed pixel will be looked up, the x_LastPixelUpdate will be set.
for (x = x_FirstPixelUpdate; x < displayWidth; x++) {
// Quickly write out the first changed pixel (saves another array lookup)
linePixelBuffer[x_FirstPixelUpdate] = isset ? colorTftMesh : colorTftBlack;
x_LastPixelUpdate = x_FirstPixelUpdate;
// Step 3: copy all remaining pixels in this row into the pixel line buffer,
// while also recording the last pixel in the row that needs updating
for (x = x_FirstPixelUpdate + 1; x < displayWidth; x++) {
isset = buffer[x + y_byteIndex] & y_byteMask;
linePixelBuffer[x] = isset ? colorTftMesh : colorTftBlack;
@@ -1274,14 +1274,6 @@ void TFTDisplay::display(bool fromBlank)
x_LastPixelUpdate = x;
}
}
// Step 3b: Round up the last pixel to odd number to maintain 32-bit alignment for SPIs.
// Most displays will have even number of pixels in a row -- this will be in bounds
// of the displayWidth. (Hopefully odd displays will just ignore that extra pixel.)
x_LastPixelUpdate |= 1;
// Ensure the last pixel index does not exceed the display width.
if (x_LastPixelUpdate >= displayWidth) {
x_LastPixelUpdate = displayWidth - 1;
}
#if defined(HACKADAY_COMMUNICATOR)
tft->draw16bitBeRGBBitmap(x_FirstPixelUpdate, y, &linePixelBuffer[x_FirstPixelUpdate],
(x_LastPixelUpdate - x_FirstPixelUpdate + 1), 1);
+1 -3
View File
@@ -163,11 +163,9 @@ void menuHandler::LoraRegionPicker(uint32_t duration)
// Guard: without a reboot, reconfigure() applies the region directly.
// Reject LORA_24 on sub-GHz-only hardware — getRadio() used to catch this post-reboot.
// TODO: change this to either use the validateLoraConfig() logic or at least check the region for wideLora
// rather than a hardcoded check for LORA_24.
if (selectedRegion == meshtastic_Config_LoRaConfig_RegionCode_LORA_24 &&
!(RadioLibInterface::instance && RadioLibInterface::instance->wideLora())) {
LOG_WARN("Radio hardware does not support 2.4 GHz; ignoring region selection");
LOG_WARN("Radio hardware does not support 2.4 GHz; ignoring LORA_24 selection");
return;
}
+2 -20
View File
@@ -422,17 +422,6 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
std::vector<bool> isMine; // track alignment
std::vector<bool> isHeader; // track header lines
std::vector<AckStatus> ackForLine;
// Hard limit on total cached lines to prevent unbounded growth from a single long message.
// Reserve to the actual cache cap up front, because a single message can expand to many more
// wrapped display lines than a small per-message estimate would predict. For a display
// rendering only ~5-30 lines at a time, caching more than this limit wastes heap. Stop
// appending once we reach MAX_CACHED_LINES to prevent a single message from blowing out the
// heap.
constexpr size_t MAX_CACHED_LINES = 100U; // ~5-6KB for std::string overhead on 32-bit (if each ~50-60 bytes avg)
allLines.reserve(MAX_CACHED_LINES);
isMine.reserve(MAX_CACHED_LINES);
isHeader.reserve(MAX_CACHED_LINES);
ackForLine.reserve(MAX_CACHED_LINES);
for (auto it = filtered.rbegin(); it != filtered.rend(); ++it) {
const auto &m = *it;
@@ -576,23 +565,16 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
int wrapWidth = mine ? rightTextWidth : leftTextWidth;
std::vector<std::string> wrapped = generateLines(display, "", msgText, wrapWidth);
// Per-message wrap-line limit: even if wrapping produces many lines, cap them to prevent
// a single long message from consuming most or all of the cache.
constexpr size_t MAX_WRAPPED_LINES_PER_MSG = 20U;
size_t wrappedCount = 0;
for (auto &ln : wrapped) {
if (allLines.size() >= MAX_CACHED_LINES || wrappedCount >= MAX_WRAPPED_LINES_PER_MSG)
break; // Cache limit or per-message limit reached; stop adding lines from this message
allLines.emplace_back(std::move(ln));
allLines.push_back(ln);
isMine.push_back(mine);
isHeader.push_back(false);
ackForLine.push_back(AckStatus::NONE);
++wrappedCount;
}
}
// Cache lines and heights
cachedLines.swap(allLines);
cachedLines = allLines;
cachedHeights = calculateLineHeights(cachedLines, emotes, isHeader);
std::vector<MessageBlock> blocks = buildMessageBlocks(isHeader, isMine);
+2 -38
View File
@@ -3,9 +3,6 @@
#include "CompassRenderer.h"
#include "NodeDB.h"
#include "NodeListRenderer.h"
#if !MESHTASTIC_EXCLUDE_STATUS
#include "modules/StatusMessageModule.h"
#endif
#include "UIRenderer.h"
#include "gps/GeoCoord.h"
#include "gps/RTC.h" // for getTime() function
@@ -95,41 +92,8 @@ std::string getSafeNodeName(OLEDDisplay *display, meshtastic_NodeInfoLite *node,
// 1) Choose target candidate (long vs short) only if present
const char *raw = nullptr;
#if !MESHTASTIC_EXCLUDE_STATUS
// If long-name mode is enabled, and we have a recent status for this node,
// prefer "(short_name) statusText" as the raw candidate.
std::string composedFromStatus;
if (config.display.use_long_node_name && node && node->has_user && statusMessageModule) {
const auto &recent = statusMessageModule->getRecentReceived();
const StatusMessageModule::RecentStatus *found = nullptr;
for (auto it = recent.rbegin(); it != recent.rend(); ++it) {
if (it->fromNodeId == node->num && !it->statusText.empty()) {
found = &(*it);
break;
}
}
if (found) {
const char *shortName = node->user.short_name;
composedFromStatus.reserve(4 + (shortName ? std::strlen(shortName) : 0) + 1 + found->statusText.size());
composedFromStatus += "(";
if (shortName && *shortName) {
composedFromStatus += shortName;
}
composedFromStatus += ") ";
composedFromStatus += found->statusText;
raw = composedFromStatus.c_str(); // safe for now; we'll sanitize immediately into std::string
}
}
#endif
// If we didn't compose from status, use normal long/short selection
if (!raw) {
if (node && node->has_user) {
raw = config.display.use_long_node_name ? node->user.long_name : node->user.short_name;
}
if (node && node->has_user) {
raw = config.display.use_long_node_name ? node->user.long_name : node->user.short_name;
}
// 2) Preserve UTF-8 names so emotes can be detected and rendered.
+1 -55
View File
@@ -5,9 +5,6 @@
#include "MeshService.h"
#include "NodeDB.h"
#include "NodeListRenderer.h"
#if !MESHTASTIC_EXCLUDE_STATUS
#include "modules/StatusMessageModule.h"
#endif
#include "UIRenderer.h"
#include "airtime.h"
#include "gps/GeoCoord.h"
@@ -293,7 +290,7 @@ void UIRenderer::drawNodes(OLEDDisplay *display, int16_t x, int16_t y, const mes
// * Favorite Node Info *
// **********************
// cppcheck-suppress constParameterPointer; signature must match FrameCallback typedef from OLEDDisplayUi library
void UIRenderer::drawFavoriteNode(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
void UIRenderer::drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{
if (favoritedNodes.empty())
return;
@@ -345,57 +342,6 @@ void UIRenderer::drawFavoriteNode(OLEDDisplay *display, OLEDDisplayUiState *stat
UIRenderer::drawStringWithEmotes(display, x, getTextPositions(display)[line++], username, FONT_HEIGHT_SMALL, 1, false);
}
#if !MESHTASTIC_EXCLUDE_STATUS
// === Optional: Last received StatusMessage line for this node ===
// Display it directly under the username line (if we have one).
if (statusMessageModule) {
const auto &recent = statusMessageModule->getRecentReceived();
const StatusMessageModule::RecentStatus *found = nullptr;
// Search newest-to-oldest
for (auto it = recent.rbegin(); it != recent.rend(); ++it) {
if (it->fromNodeId == node->num && !it->statusText.empty()) {
found = &(*it);
break;
}
}
if (found) {
std::string statusLine = std::string(" Status: ") + found->statusText;
{
const int screenW = display->getWidth();
const int ellipseW = display->getStringWidth("...");
int w = display->getStringWidth(statusLine.c_str());
// Only do work if it overflows
if (w > screenW) {
bool truncated = false;
if (ellipseW > screenW) {
statusLine.clear();
} else {
while (!statusLine.empty()) {
// remove one char (byte) at a time
statusLine.pop_back();
truncated = true;
// Measure candidate with ellipsis appended
std::string candidate = statusLine + "...";
if (display->getStringWidth(candidate.c_str()) <= screenW) {
statusLine = std::move(candidate);
break;
}
}
if (statusLine.empty() && ellipseW <= screenW) {
statusLine = "...";
}
}
}
}
display->drawString(x, getTextPositions(display)[line++], statusLine.c_str());
}
}
#endif
// === 2. Signal and Hops (combined on one line, if available) ===
char signalHopsStr[32] = "";
bool haveSignal = false;
+1 -1
View File
@@ -50,7 +50,7 @@ class UIRenderer
// Navigation bar overlay
static void drawNavigationBar(OLEDDisplay *display, OLEDDisplayUiState *state);
static void drawFavoriteNode(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
static void drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
+1
View File
@@ -121,6 +121,7 @@ void CardputerKeyboard::pressed(uint8_t key)
modifierFlag = 0;
}
uint8_t next_key = 0;
int row = (key - 1) / 10;
int col = (key - 1) % 10;
+6 -11
View File
@@ -299,7 +299,6 @@ void InputBroker::Init()
// 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 !MESHTASTIC_EXCLUDE_SCREEN
if (screen) {
ButtonConfig userConfig;
userConfig.pinNumber = (uint8_t)_pinNum;
@@ -318,9 +317,7 @@ void InputBroker::Init()
userConfig.longPressTime = 500;
userConfig.longLongPress = INPUT_BROKER_SHUTDOWN;
UserButtonThread->initButton(userConfig);
} else
#endif
{
} else {
ButtonConfig userConfigNoScreen;
userConfigNoScreen.pinNumber = (uint8_t)_pinNum;
userConfigNoScreen.activeLow = BUTTON_ACTIVE_LOW;
@@ -382,13 +379,11 @@ void InputBroker::Init()
}
#endif // HAS_BUTTON
#if ARCH_PORTDUINO
if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
if (portduino_config.i2cdev != "") {
seesawRotary = new SeesawRotary("SeesawRotary");
if (!seesawRotary->init()) {
delete seesawRotary;
seesawRotary = nullptr;
}
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();
+5 -23
View File
@@ -10,26 +10,8 @@
#include "memGet.h"
#include "configuration.h"
#if defined(MESHTASTIC_DYNAMIC_SBRK_HEAP)
#include <malloc.h>
#include <unistd.h> // sbrk
#ifdef ARCH_STM32WL
// Returns the uncommitted sbrk headroom: addressable space between the current heap
// break and the stack pointer that has not yet been committed to the arena.
static uint32_t sbrkHeadroom()
{
// defined in STM32 linker script
extern char _estack;
extern char _Min_Stack_Size;
uint32_t max_sp = (uint32_t)(&_estack - &_Min_Stack_Size);
uint32_t heap_end = (uint32_t)sbrk(0);
return (max_sp > heap_end) ? (max_sp - heap_end) : 0;
}
#else
#error Unsupported architecture!
#endif
#include <malloc.h>
#endif
MemGet memGet;
@@ -46,9 +28,9 @@ uint32_t MemGet::getFreeHeap()
return dbgHeapFree();
#elif defined(ARCH_RP2040)
return rp2040.getFreeHeap();
#elif defined(MESHTASTIC_DYNAMIC_SBRK_HEAP) // Currently: ARCH_STM32WL
#elif defined(ARCH_STM32WL)
struct mallinfo m = mallinfo();
return m.fordblks + sbrkHeadroom(); // Free space within arena + uncommitted sbrk headroom
return m.fordblks; // Total free space (bytes)
#else
// this platform does not have heap management function implemented
return UINT32_MAX;
@@ -67,9 +49,9 @@ uint32_t MemGet::getHeapSize()
return dbgHeapTotal();
#elif defined(ARCH_RP2040)
return rp2040.getTotalHeap();
#elif defined(MESHTASTIC_DYNAMIC_SBRK_HEAP) // Currently: ARCH_STM32WL
#elif defined(ARCH_STM32WL)
struct mallinfo m = mallinfo();
return m.arena + sbrkHeadroom(); // Non-mmapped space allocated + uncommitted sbrk headroom
return m.arena; // Non-mmapped space allocated (bytes)
#else
// this platform does not have heap management function implemented
return UINT32_MAX;
-10
View File
@@ -4,7 +4,6 @@
#include <memory>
#if !(MESHTASTIC_EXCLUDE_PKI)
#include "HardwareRNG.h"
#include "NodeDB.h"
#include "aes-ccm.h"
#include "meshUtils.h"
@@ -27,15 +26,6 @@ void CryptoEngine::generateKeyPair(uint8_t *pubKey, uint8_t *privKey)
{
// Mix in any randomness we can, to make key generation stronger.
CryptRNG.begin(optstr(APP_VERSION));
uint8_t hardwareEntropy[64] = {0};
if (HardwareRNG::fill(hardwareEntropy, sizeof(hardwareEntropy), true)) {
CryptRNG.stir(hardwareEntropy, sizeof(hardwareEntropy));
} else {
LOG_WARN("Hardware entropy unavailable, falling back to software RNG");
}
memset(hardwareEntropy, 0, sizeof(hardwareEntropy));
if (myNodeInfo.device_id.size == 16) {
CryptRNG.stir(myNodeInfo.device_id.bytes, myNodeInfo.device_id.size);
}
+2 -2
View File
@@ -32,8 +32,8 @@
#define default_map_publish_interval_secs 60 * 60
// Traffic management defaults
#define default_traffic_mgmt_position_precision_bits 24 // ~10m grid cells
#define default_traffic_mgmt_position_min_interval_secs (ONE_DAY / 2) // 12 hours between identical positions
#define default_traffic_mgmt_position_precision_bits 24 // ~10m grid cells
#define default_traffic_mgmt_position_min_interval_secs ONE_DAY // 1 day between identical positions
#ifdef USERPREFS_RINGTONE_NAG_SECS
#define default_ringtone_nag_secs USERPREFS_RINGTONE_NAG_SECS
-159
View File
@@ -1,159 +0,0 @@
#include "HardwareRNG.h"
#include <algorithm>
#include <cstring>
#include <random>
#include "configuration.h"
#if HAS_RADIO
#include "RadioLibInterface.h"
#endif
#if defined(ARCH_NRF52)
#include <Adafruit_nRFCrypto.h>
extern Adafruit_nRFCrypto nRFCrypto;
#elif defined(ARCH_ESP32)
#include <esp_system.h>
#elif defined(ARCH_RP2040)
#include <Arduino.h>
#elif defined(ARCH_PORTDUINO)
#include <random>
#include <sys/random.h>
#include <unistd.h>
#endif
namespace HardwareRNG
{
namespace
{
void fillWithRandomDevice(uint8_t *buffer, size_t length)
{
std::random_device rd;
size_t offset = 0;
while (offset < length) {
uint32_t value = rd();
size_t toCopy = std::min(length - offset, sizeof(value));
memcpy(buffer + offset, &value, toCopy);
offset += toCopy;
}
}
#if HAS_RADIO
bool mixWithLoRaEntropy(uint8_t *buffer, size_t length)
{
// Only attempt to pull entropy from the modem if it is initialized and exposes the helper.
// When the radio stack is disabled or has not yet been configured, we simply skip this step
// and return false so callers know no extra mixing occurred.
RadioLibInterface *radio = RadioLibInterface::instance;
if (!radio) {
LOG_ERROR("No radio instance available to provide entropy");
return false;
}
constexpr size_t chunkSize = 16;
uint8_t scratch[chunkSize];
size_t offset = 0;
bool mixed = false;
while (offset < length) {
size_t toCopy = std::min(length - offset, chunkSize);
// randomBytes() returns false if the modem does not support it or is not ready
// (for instance, when the radio is powered down). We break immediately to avoid
// blocking or returning partially-filled entropy and simply report failure.
if (!radio->randomBytes(scratch, toCopy)) {
break;
}
for (size_t i = 0; i < toCopy; ++i) {
buffer[offset + i] ^= scratch[i];
}
mixed = true;
offset += toCopy;
}
// Avoid leaving the modem-sourced bytes sitting on the stack longer than needed.
if (mixed) {
memset(scratch, 0, sizeof(scratch));
}
return mixed;
}
#endif
} // namespace
bool fill(uint8_t *buffer, size_t length, bool useRadioEntropy)
{
if (!buffer || length == 0) {
return false;
}
bool filled = false;
#if defined(ARCH_NRF52)
// The Nordic SDK RNG provides cryptographic-quality randomness backed by hardware.
nRFCrypto.begin();
auto result = nRFCrypto.Random.generate(buffer, length);
nRFCrypto.end();
filled = result;
#elif defined(ARCH_ESP32)
// ESP32 exposes a true RNG via esp_fill_random().
esp_fill_random(buffer, length);
filled = true;
#elif defined(ARCH_RP2040)
// RP2040 has a hardware random number generator accessible through the Arduino core.
size_t offset = 0;
while (offset < length) {
uint32_t value = rp2040.hwrand32();
size_t toCopy = std::min(length - offset, sizeof(value));
memcpy(buffer + offset, &value, toCopy);
offset += toCopy;
}
filled = true;
#elif defined(ARCH_PORTDUINO)
// Prefer the host OS RNG first when running under Portduino.
ssize_t generated = ::getrandom(buffer, length, 0);
if (generated == static_cast<ssize_t>(length)) {
filled = true;
}
if (!filled) {
fillWithRandomDevice(buffer, length);
filled = true;
}
#endif
if (!filled) {
// As a last resort, fall back to std::random_device. This should only be reached
// if a platform-specific source was unavailable.
fillWithRandomDevice(buffer, length);
filled = true;
}
#if HAS_RADIO
if (useRadioEntropy) {
// Best-effort: if the radio is active and can provide modem entropy, XOR it over the
// buffer to improve overall quality. We consider the filling a success if either a
// good platform RNG or the modem RNG provided data, so we return true as long as at
// least one of those steps succeeded.
filled = mixWithLoRaEntropy(buffer, length) || filled;
}
#endif
return filled;
}
bool seed(uint32_t &seedOut)
{
uint32_t candidate = 0;
if (!fill(reinterpret_cast<uint8_t *>(&candidate), sizeof(candidate), true)) {
return false;
}
seedOut = candidate;
return true;
}
} // namespace HardwareRNG
-28
View File
@@ -1,28 +0,0 @@
#pragma once
#include <cstddef>
#include <cstdint>
namespace HardwareRNG
{
/**
* Fill the provided buffer with random bytes sourced from the most
* appropriate hardware-backed RNG available on the current platform.
*
* @param buffer Destination buffer for random bytes
* @param length Number of bytes to write
* @param useRadioEntropy If true, attempt to mix radio entropy into the output as well.
* @return true if the buffer was fully populated with entropy, false on failure
*/
bool fill(uint8_t *buffer, size_t length, bool useRadioEntropy = false);
/**
* Populate a 32-bit seed value with hardware-backed randomness where possible.
*
* @param seedOut Destination for the generated seed value
* @return true if a seed was produced from a reliable entropy source
*/
bool seed(uint32_t &seedOut);
} // namespace HardwareRNG
+11 -10
View File
@@ -71,10 +71,12 @@ template <typename T> bool LR11x0Interface<T>::init()
RadioLibInterface::init();
if (config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_LORA_24) { // clamp if wide freq range
limitPower(LR1120_MAX_POWER);
} else {
limitPower(LR1110_MAX_POWER); // default clamp for non-wide freq range
limitPower(LR1110_MAX_POWER);
if ((power > LR1120_MAX_POWER) &&
(config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) { // clamp again if wide freq range
power = LR1120_MAX_POWER;
preambleLength = 12; // 12 is the default for operation above 2GHz
}
#ifdef LR11X0_RF_SWITCH_SUBGHZ
@@ -175,12 +177,6 @@ template <typename T> bool LR11x0Interface<T>::reconfigure()
err = lora.setSyncWord(syncWord);
assert(err == RADIOLIB_ERR_NONE);
if (config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_LORA_24) { // clamp if wide freq range
limitPower(LR1120_MAX_POWER);
} else {
limitPower(LR1110_MAX_POWER); // default clamp for non-wide freq range
}
err = lora.setPreambleLength(preambleLength);
assert(err == RADIOLIB_ERR_NONE);
@@ -188,6 +184,11 @@ template <typename T> bool LR11x0Interface<T>::reconfigure()
if (err != RADIOLIB_ERR_NONE)
RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING);
if (power > LR1110_MAX_POWER) // This chip has lower power limits than some
power = LR1110_MAX_POWER;
if ((power > LR1120_MAX_POWER) && (config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) // 2.4G power limit
power = LR1120_MAX_POWER;
err = lora.setOutputPower(power);
assert(err == RADIOLIB_ERR_NONE);
+2 -7
View File
@@ -4,7 +4,6 @@
#include "MeshTypes.h"
#include "PointerQueue.h"
#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 =
@@ -60,7 +59,7 @@ extern const RegionInfo *myRegion;
extern void initRegion();
// Valid LoRa spread factor range and defaults
constexpr uint8_t LORA_SF_MIN = 5;
constexpr uint8_t LORA_SF_MIN = 7;
constexpr uint8_t LORA_SF_MAX = 12;
constexpr uint8_t LORA_SF_DEFAULT = 11; // LONG_FAST default
@@ -72,14 +71,10 @@ constexpr uint8_t LORA_CR_DEFAULT = 5; // LONG_FAST default
// Default bandwidth in kHz (LONG_FAST)
constexpr float LORA_BW_DEFAULT_KHZ = 250.0f;
/// Clamp spread factor to the valid LoRa range [5, 12].
/// Clamp spread factor to the valid LoRa range [7, 12].
/// Out-of-range values (including 0 from unset preset mode) return LORA_SF_DEFAULT.
static inline uint8_t clampSpreadFactor(uint8_t sf)
{
// We check for RF95 radios that are incompatible with Spreading Factors 5 and 6.
if (radioType == RF95_RADIO && (sf == 5 || sf == 6))
return LORA_SF_DEFAULT;
if (sf < LORA_SF_MIN || sf > LORA_SF_MAX)
return LORA_SF_DEFAULT;
return sf;
+1 -1
View File
@@ -819,7 +819,7 @@ void NodeDB::installDefaultModuleConfig()
moduleConfig.has_store_forward = true;
moduleConfig.has_telemetry = true;
moduleConfig.has_external_notification = true;
#if defined(PIN_BUZZER) || defined(PIN_VIBRATION) || defined(LED_NOTIFICATION) || defined(PCA_LED_NOTIFICATION)
#if defined(PIN_BUZZER) || defined(PIN_VIBRATION) || defined(LED_NOTIFICATION)
moduleConfig.external_notification.enabled = true;
#endif
#if defined(PIN_BUZZER)
+125 -15
View File
@@ -18,10 +18,7 @@
#include "concurrency/LockGuard.h"
#include "main.h"
#include "xmodem.h"
#if FromRadio_size > MAX_TO_FROM_RADIO_SIZE
#error FromRadio is too big
#endif
#include <pb_encode.h>
#if ToRadio_size > MAX_TO_FROM_RADIO_SIZE
#error ToRadio is too big
@@ -61,13 +58,14 @@ void PhoneAPI::handleStartConfig()
onConfigStart();
// even if we were already connected - restart our state machine
if (config_nonce == SPECIAL_NONCE_ONLY_NODES) {
if (config_nonce == SPECIAL_NONCE_ONLY_NODES || config_nonce == SPECIAL_NONCE_BATCH_ONLY_NODES) {
// If client only wants node info, jump directly to sending nodes
state = STATE_SEND_OWN_NODEINFO;
LOG_INFO("Client only wants node info, skipping other config");
} else {
state = STATE_SEND_MY_INFO;
}
batchNodeInfo = (config_nonce == SPECIAL_NONCE_BATCH || config_nonce == SPECIAL_NONCE_BATCH_ONLY_NODES);
pauseBluetoothLogging = true;
spiLock->lock();
filesManifest = getFiles("/", 10);
@@ -223,6 +221,48 @@ bool PhoneAPI::handleToRadio(const uint8_t *buf, size_t bufLength)
STATE_SEND_PACKETS // send packets or debug strings
*/
/// Context passed to the nanopb encoding callback for batched NodeInfo
struct NodeInfoBatchEncodeContext {
std::deque<meshtastic_NodeInfo> *items;
size_t itemCount; // How many items from the front of the deque to encode
};
/// Nanopb encoding callback for repeated NodeInfo items in a NodeInfoBatch.
/// Called by pb_encode during both sizing and actual encoding passes.
static bool nodeInfoBatchEncodeCallback(pb_ostream_t *stream, const pb_field_iter_t *field, void *const *arg)
{
auto *ctx = static_cast<NodeInfoBatchEncodeContext *>(*arg);
for (size_t i = 0; i < ctx->itemCount && i < ctx->items->size(); i++) {
if (!pb_encode_tag_for_field(stream, field))
return false;
if (!pb_encode_submessage(stream, meshtastic_NodeInfo_fields, &(*ctx->items)[i]))
return false;
}
return true;
}
/// Calculate how many NodeInfos from the front of the deque fit within maxPayloadBytes.
/// maxPayloadBytes is the budget for the inner NodeInfoBatch content (repeated field entries).
static size_t calculateBatchCount(std::deque<meshtastic_NodeInfo> &items, size_t maxPayloadBytes)
{
size_t totalSize = 0;
size_t count = 0;
for (const auto &info : items) {
pb_ostream_t sizestream = PB_OSTREAM_SIZING;
if (!pb_encode(&sizestream, meshtastic_NodeInfo_fields, &info))
break;
size_t itemSize = sizestream.bytes_written;
// Per-item overhead: 1 byte tag (field 1, wire type 2) + varint length prefix
size_t overhead = 1 + (itemSize < 128 ? 1 : 2);
if (totalSize + overhead + itemSize > maxPayloadBytes)
break;
totalSize += overhead + itemSize;
count++;
}
// Always include at least 1 item if available (even if it slightly exceeds budget)
return count > 0 ? count : (items.empty() ? 0 : 1);
}
size_t PhoneAPI::getFromRadio(uint8_t *buf)
{
// Respond to heartbeat by sending queue status
@@ -231,7 +271,7 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
fromRadioScratch.which_payload_variant = meshtastic_FromRadio_queueStatus_tag;
fromRadioScratch.queueStatus = router->getQueueStatus();
heartbeatReceived = false;
size_t numbytes = pb_encode_to_bytes(buf, meshtastic_FromRadio_size, &meshtastic_FromRadio_msg, &fromRadioScratch);
size_t numbytes = pb_encode_to_bytes(buf, MAX_TO_FROM_RADIO_SIZE, &meshtastic_FromRadio_msg, &fromRadioScratch);
LOG_DEBUG("FromRadio=STATE_SEND_QUEUE_STATUS, numbytes=%u", numbytes);
return numbytes;
}
@@ -286,7 +326,7 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
nodeInfoForPhone.num = 0;
}
}
if (config_nonce == SPECIAL_NONCE_ONLY_NODES) {
if (config_nonce == SPECIAL_NONCE_ONLY_NODES || config_nonce == SPECIAL_NONCE_BATCH_ONLY_NODES) {
// If client only wants node info, jump directly to sending nodes
state = STATE_SEND_OTHER_NODEINFOS;
onNowHasData(0);
@@ -479,6 +519,81 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
LOG_INFO("Start sending nodeinfos millis=%u", millis());
}
if (batchNodeInfo) {
// Batched path: pack multiple NodeInfos into a single FromRadio packet.
// Consolidate any pending nodeInfoForPhone into the queue, then top it up.
{
concurrency::LockGuard guard(&nodeInfoMutex);
if (nodeInfoForPhone.num != 0) {
nodeInfoQueue.push_front(nodeInfoForPhone);
nodeInfoForPhone = {};
}
}
prefetchNodeInfos();
bool queueEmpty;
{
concurrency::LockGuard guard(&nodeInfoMutex);
queueEmpty = nodeInfoQueue.empty();
}
if (queueEmpty) {
LOG_DEBUG("Done sending %d of %d nodeinfos (batched) millis=%u", readIndex, nodeDB->getNumMeshNodes(), millis());
concurrency::LockGuard guard(&nodeInfoMutex);
nodeInfoQueue.clear();
state = STATE_SEND_FILEMANIFEST;
return getFromRadio(buf);
}
// Fix up user IDs and log progress
{
concurrency::LockGuard guard(&nodeInfoMutex);
for (auto &info : nodeInfoQueue) {
sprintf(info.user.id, "!%08x", info.num);
}
}
// Estimate overhead: FromRadio id (~2B) + oneof tag 18 (~2B) + NodeInfoBatch length varint (~2B) = ~6 bytes
constexpr size_t kFromRadioOverhead = 10; // conservative
size_t batchCount;
{
concurrency::LockGuard guard(&nodeInfoMutex);
batchCount = calculateBatchCount(nodeInfoQueue, MAX_TO_FROM_RADIO_SIZE - kFromRadioOverhead);
}
if (readIndex == 2 || readIndex % 20 == 0) {
concurrency::LockGuard guard(&nodeInfoMutex);
LOG_DEBUG("nodeinfo batch: %d items, %d/%d total", batchCount, readIndex, nodeDB->getNumMeshNodes());
}
// Use a local FromRadio so the callback context pointer doesn't outlive its scope.
meshtastic_FromRadio batchRadio = {};
NodeInfoBatchEncodeContext ctx;
{
concurrency::LockGuard guard(&nodeInfoMutex);
ctx.items = &nodeInfoQueue;
ctx.itemCount = batchCount;
}
batchRadio.which_payload_variant = meshtastic_FromRadio_node_info_batch_tag;
batchRadio.node_info_batch.items.funcs.encode = nodeInfoBatchEncodeCallback;
batchRadio.node_info_batch.items.arg = &ctx;
size_t numbytes = pb_encode_to_bytes(buf, MAX_TO_FROM_RADIO_SIZE, &meshtastic_FromRadio_msg, &batchRadio);
// Remove consumed items from the queue
{
concurrency::LockGuard guard(&nodeInfoMutex);
for (size_t i = 0; i < batchCount && !nodeInfoQueue.empty(); i++) {
nodeInfoQueue.pop_front();
}
}
prefetchNodeInfos();
return numbytes;
}
// Non-batched path: send one NodeInfo per FromRadio packet (legacy behavior)
meshtastic_NodeInfo infoToSend = {};
{
concurrency::LockGuard guard(&nodeInfoMutex);
@@ -496,11 +611,6 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
// Just in case we stored a different user.id in the past, but should never happen going forward
sprintf(infoToSend.user.id, "!%08x", infoToSend.num);
// Logging this really slows down sending nodes on initial connection because the serial console is so slow, so only
// uncomment if you really need to:
// LOG_INFO("nodeinfo: num=0x%x, lastseen=%u, id=%s, name=%s", nodeInfoForPhone.num, nodeInfoForPhone.last_heard,
// nodeInfoForPhone.user.id, nodeInfoForPhone.user.long_name);
// Occasional progress logging. (readIndex==2 will be true for the first non-us node)
if (readIndex == 2 || readIndex % 20 == 0) {
LOG_DEBUG("nodeinfo: %d/%d", readIndex, nodeDB->getNumMeshNodes());
@@ -523,8 +633,8 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
case STATE_SEND_FILEMANIFEST: {
LOG_DEBUG("FromRadio=STATE_SEND_FILEMANIFEST");
// last element
if (config_state == filesManifest.size() ||
config_nonce == SPECIAL_NONCE_ONLY_NODES) { // also handles an empty filesManifest
if (config_state == filesManifest.size() || config_nonce == SPECIAL_NONCE_ONLY_NODES ||
config_nonce == SPECIAL_NONCE_BATCH_ONLY_NODES) { // also handles an empty filesManifest
config_state = 0;
filesManifest.clear();
// Skip to complete packet
@@ -579,7 +689,7 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
// Do we have a message from the mesh?
if (fromRadioScratch.which_payload_variant != 0) {
// Encapsulate as a FromRadio packet
size_t numbytes = pb_encode_to_bytes(buf, meshtastic_FromRadio_size, &meshtastic_FromRadio_msg, &fromRadioScratch);
size_t numbytes = pb_encode_to_bytes(buf, MAX_TO_FROM_RADIO_SIZE, &meshtastic_FromRadio_msg, &fromRadioScratch);
// VERY IMPORTANT to not print debug messages while writing to fromRadioScratch - because we use that same buffer
// for logging (when we are encapsulating with protobufs)
+5 -3
View File
@@ -13,15 +13,14 @@
// Make sure that we never let our packets grow too large for one BLE packet
#define MAX_TO_FROM_RADIO_SIZE 512
#if meshtastic_FromRadio_size > MAX_TO_FROM_RADIO_SIZE
#error "meshtastic_FromRadio_size is too large for our BLE packets"
#endif
#if meshtastic_ToRadio_size > MAX_TO_FROM_RADIO_SIZE
#error "meshtastic_ToRadio_size is too large for our BLE packets"
#endif
#define SPECIAL_NONCE_ONLY_CONFIG 69420
#define SPECIAL_NONCE_ONLY_NODES 69421 // ( ͡° ͜ʖ ͡°)
#define SPECIAL_NONCE_BATCH 69422
#define SPECIAL_NONCE_BATCH_ONLY_NODES 69423
/**
* Provides our protobuf based API which phone/PC clients can use to talk to our device
@@ -88,6 +87,9 @@ class PhoneAPI
// Protect nodeInfoForPhone + nodeInfoQueue because NimBLE callbacks run in a separate FreeRTOS task.
concurrency::Lock nodeInfoMutex;
// When true, bundle multiple NodeInfos per FromRadio packet (opt-in via batch nonce)
bool batchNodeInfo = false;
meshtastic_ToRadio toRadioScratch = {
0}; // this is a static scratch object, any data must be copied elsewhere before returning
+10
View File
@@ -17,4 +17,14 @@ template <class T> class PointerQueue : public TypedQueue<T *>
return this->dequeue(&p, maxWait) ? p : nullptr;
}
#ifdef HAS_FREE_RTOS
// returns a ptr or null if the queue was empty
T *dequeuePtrFromISR(BaseType_t *higherPriWoken)
{
T *p;
return this->dequeueFromISR(&p, higherPriWoken) ? p : nullptr;
}
#endif
};
+2 -1
View File
@@ -240,7 +240,8 @@ bool RF95Interface::reconfigure()
if (err != RADIOLIB_ERR_NONE)
RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING);
limitPower(RF95_MAX_POWER);
if (power > RF95_MAX_POWER) // This chip has lower power limits than some
power = RF95_MAX_POWER;
#ifdef USE_RF95_RFO
err = lora->setOutputPower(power, true);
+2 -11
View File
@@ -152,10 +152,8 @@ const RegionInfo regions[] = {
/*
https://lora-alliance.org/wp-content/uploads/2020/11/lorawan_regional_parameters_v1.0.3reva_0.pdf
https://standard.nbtc.go.th/getattachment/Standards/%E0%B8%A1%E0%B8%B2%E0%B8%95%E0%B8%A3%E0%B8%90%E0%B8%B2%E0%B8%99%E0%B8%97%E0%B8%B2%E0%B8%87%E0%B9%80%E0%B8%97%E0%B8%84%E0%B8%99%E0%B8%B4%E0%B8%84%E0%B8%82%E0%B8%AD%E0%B8%87%E0%B9%80%E0%B8%84%E0%B8%A3%E0%B8%B7%E0%B9%88%E0%B8%AD%E0%B8%87%E0%B9%82%E0%B8%97%E0%B8%A3%E0%B8%84%E0%B8%A1%E0%B8%99%E0%B8%B2%E0%B8%84%E0%B8%A1/1033-2565.pdf.aspx?lang=th-TH
Thailand 920–925 MHz set max TX power to 27 dBm and enforce 10% duty cycle, aligned with NBTC regulations.
*/
RDEF(TH, 920.0f, 925.0f, 10, 27, false, false, PROFILE_STD),
RDEF(TH, 920.0f, 925.0f, 100, 16, false, false, PROFILE_STD),
/*
433,05-434,7 Mhz 10 mW
@@ -963,7 +961,7 @@ void RadioInterface::applyModemConfig()
cr = loraConfig.coding_rate;
LOG_INFO("Using custom Coding Rate %u", cr);
} else {
cr = newcr;
cr = loraConfig.coding_rate;
}
} else { // if not using preset, then just use the custom settings
@@ -1038,13 +1036,6 @@ void RadioInterface::applyModemConfig()
saveFreq(freq + loraConfig.frequency_offset);
const char *channelName = channels.getName(channels.getPrimaryIndex());
if (newRegion->wideLora) { // clamp if wide freq range
preambleLength = wideLoraPreambleLengthDefault; // 12 is the default for operation above 2GHz
} else {
preambleLength =
preambleLengthDefault; // 8 is default, but we use longer to increase the amount of sleep time when receiving
}
slotTimeMsec = computeSlotTimeMsec();
preambleTimeMsec = preambleLength * (pow_of_2(sf) / bw);
+8 -13
View File
@@ -92,20 +92,15 @@ class RadioInterface
uint8_t sf = 9;
uint8_t cr = 5;
static constexpr uint8_t NUM_SYM_CAD =
2; // Number of symbols used for CAD, 2 is the default since RadioLib 6.3.0 as per AN1200.48
static constexpr uint8_t NUM_SYM_CAD_24GHZ =
4; // Number of symbols used for CAD in 2.4 GHz, 4 is recommended in AN1200.22 of SX1280
const uint8_t NUM_SYM_CAD = 2; // Number of symbols used for CAD, 2 is the default since RadioLib 6.3.0 as per AN1200.48
const uint8_t NUM_SYM_CAD_24GHZ = 4; // Number of symbols used for CAD in 2.4 GHz, 4 is recommended in AN1200.22 of SX1280
uint32_t slotTimeMsec = computeSlotTimeMsec();
uint16_t preambleLength = 16; // 8 is default, but we use longer to increase the amount of sleep time when receiving
static constexpr uint16_t preambleLengthDefault =
16; // 8 is default, but we use longer to increase the amount of sleep time when receiving
static constexpr uint16_t wideLoraPreambleLengthDefault = 12; // 12 is default for wide Lora
uint32_t preambleTimeMsec = 165; // calculated on startup, this is the default for LongFast
static constexpr uint32_t PROCESSING_TIME_MSEC =
4500; // time to construct, process and construct a packet again (empirically determined)
static constexpr uint8_t CWmin = 3; // minimum CWsize
static constexpr uint8_t CWmax = 8; // maximum CWsize
uint16_t preambleLength = 16; // 8 is default, but we use longer to increase the amount of sleep time when receiving
uint32_t preambleTimeMsec = 165; // calculated on startup, this is the default for LongFast
const uint32_t PROCESSING_TIME_MSEC =
4500; // time to construct, process and construct a packet again (empirically determined)
const uint8_t CWmin = 3; // minimum CWsize
const uint8_t CWmax = 8; // maximum CWsize
meshtastic_MeshPacket *sendingPacket = NULL; // The packet we are currently sending
uint32_t lastTxStart = 0L;
+1 -19
View File
@@ -246,24 +246,6 @@ bool RadioLibInterface::findInTxQueue(NodeNum from, PacketId id)
return txQueue.find(from, id);
}
bool RadioLibInterface::randomBytes(uint8_t *buffer, size_t length)
{
if (!buffer || length == 0 || !iface) {
return false;
}
// Older RadioLib versions only expose random(min, max), so fill the buffer byte-by-byte.
for (size_t i = 0; i < length; ++i) {
int32_t value = iface->random(0, 255);
if (value < 0) {
return false;
}
buffer[i] = static_cast<uint8_t>(value & 0xFF);
}
return true;
}
/** radio helper thread callback.
We never immediately transmit after any operation (either Rx or Tx). Instead we should wait a random multiple of
'slotTimes' (see definition in RadioInterface.h) taken from a contention window (CW) to lower the chance of collision.
@@ -605,4 +587,4 @@ bool RadioLibInterface::startSend(meshtastic_MeshPacket *txp)
return res == RADIOLIB_ERR_NONE;
}
}
}
-6
View File
@@ -172,12 +172,6 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified
/** Attempt to find a packet in the TxQueue. Returns true if the packet was found. */
virtual bool findInTxQueue(NodeNum from, PacketId id) override;
/**
* Request randomness sourced from the LoRa modem, if supported by the active RadioLib interface.
* @return true if len bytes were produced, false otherwise.
*/
bool randomBytes(uint8_t *buffer, size_t length);
private:
/** if we have something waiting to send, start a short (random) timer so we can come check for collision before actually
* doing the transmit */
+10 -2
View File
@@ -21,6 +21,8 @@
#if ARCH_PORTDUINO
#include "Throttle.h"
#include "platform/portduino/PortduinoGlue.h"
#endif
#if ENABLE_JSON_LOGGING || ARCH_PORTDUINO
#include "serialization/MeshPacketSerializer.h"
#endif
@@ -542,7 +544,9 @@ DecodeState perhapsDecode(meshtastic_MeshPacket *p)
} */
printPacket("decoded message", p);
#if ARCH_PORTDUINO
#if ENABLE_JSON_LOGGING
LOG_TRACE("%s", MeshPacketSerializer::JsonSerialize(p, false).c_str());
#elif ARCH_PORTDUINO
if (portduino_config.traceFilename != "" || portduino_config.logoutputlevel == level_trace) {
LOG_TRACE("%s", MeshPacketSerializer::JsonSerialize(p, false).c_str());
} else if (portduino_config.JSONFilename != "") {
@@ -834,7 +838,11 @@ void Router::handleReceived(meshtastic_MeshPacket *p, RxSource src)
void Router::perhapsHandleReceived(meshtastic_MeshPacket *p)
{
#if ARCH_PORTDUINO
#if ENABLE_JSON_LOGGING
// Even ignored packets get logged in the trace
p->rx_time = getValidTime(RTCQualityFromNet); // store the arrival timestamp for the phone
LOG_TRACE("%s", MeshPacketSerializer::JsonSerializeEncrypted(p).c_str());
#elif ARCH_PORTDUINO
// Even ignored packets get logged in the trace
if (portduino_config.traceFilename != "" || portduino_config.logoutputlevel == level_trace) {
p->rx_time = getValidTime(RTCQualityFromNet); // store the arrival timestamp for the phone
+2 -4
View File
@@ -245,10 +245,8 @@ template <typename T> bool SX126xInterface<T>::reconfigure()
if (err != RADIOLIB_ERR_NONE)
RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING);
limitPower(SX126X_MAX_POWER);
// Make sure we reach the minimum power supported to turn the chip on (-9dBm)
if (power < -9)
power = -9;
if (power > SX126X_MAX_POWER) // This chip has lower power limits than some
power = SX126X_MAX_POWER;
err = lora.setOutputPower(power);
if (err != RADIOLIB_ERR_NONE)
+2 -1
View File
@@ -144,7 +144,8 @@ template <typename T> bool SX128xInterface<T>::reconfigure()
if (err != RADIOLIB_ERR_NONE)
RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING);
limitPower(SX128X_MAX_POWER);
if (power > SX128X_MAX_POWER) // This chip has lower power limits than some
power = SX128X_MAX_POWER;
err = lora.setOutputPower(power);
if (err != RADIOLIB_ERR_NONE)
+2 -2
View File
@@ -190,7 +190,7 @@ void StreamAPI::emitRebooted()
fromRadioScratch.rebooted = true;
// LOG_DEBUG("Emitting reboot packet for serial shell");
emitTxBuffer(pb_encode_to_bytes(txBuf + HEADER_LEN, meshtastic_FromRadio_size, &meshtastic_FromRadio_msg, &fromRadioScratch));
emitTxBuffer(pb_encode_to_bytes(txBuf + HEADER_LEN, MAX_TO_FROM_RADIO_SIZE, &meshtastic_FromRadio_msg, &fromRadioScratch));
}
void StreamAPI::emitLogRecord(meshtastic_LogRecord_Level level, const char *src, const char *format, va_list arg)
@@ -209,7 +209,7 @@ void StreamAPI::emitLogRecord(meshtastic_LogRecord_Level level, const char *src,
if (num_printed > 0 && fromRadioScratch.log_record.message[num_printed - 1] ==
'\n') // Strip any ending newline, because we have records for framing instead.
fromRadioScratch.log_record.message[num_printed - 1] = '\0';
emitTxBuffer(pb_encode_to_bytes(txBuf + HEADER_LEN, meshtastic_FromRadio_size, &meshtastic_FromRadio_msg, &fromRadioScratch));
emitTxBuffer(pb_encode_to_bytes(txBuf + HEADER_LEN, MAX_TO_FROM_RADIO_SIZE, &meshtastic_FromRadio_msg, &fromRadioScratch));
}
/// Hookable to find out when connection changes
-74
View File
@@ -22,80 +22,6 @@ PB_BIND(meshtastic_Contact, meshtastic_Contact, AUTO)
PB_BIND(meshtastic_PLI, meshtastic_PLI, AUTO)
PB_BIND(meshtastic_AircraftTrack, meshtastic_AircraftTrack, AUTO)
PB_BIND(meshtastic_CotGeoPoint, meshtastic_CotGeoPoint, AUTO)
PB_BIND(meshtastic_DrawnShape, meshtastic_DrawnShape, 2)
PB_BIND(meshtastic_Marker, meshtastic_Marker, AUTO)
PB_BIND(meshtastic_RangeAndBearing, meshtastic_RangeAndBearing, AUTO)
PB_BIND(meshtastic_Route, meshtastic_Route, 2)
PB_BIND(meshtastic_Route_Link, meshtastic_Route_Link, AUTO)
PB_BIND(meshtastic_CasevacReport, meshtastic_CasevacReport, 2)
PB_BIND(meshtastic_ZMistEntry, meshtastic_ZMistEntry, AUTO)
PB_BIND(meshtastic_EmergencyAlert, meshtastic_EmergencyAlert, AUTO)
PB_BIND(meshtastic_TaskRequest, meshtastic_TaskRequest, AUTO)
PB_BIND(meshtastic_TAKEnvironment, meshtastic_TAKEnvironment, AUTO)
PB_BIND(meshtastic_SensorFov, meshtastic_SensorFov, AUTO)
PB_BIND(meshtastic_TAKPacketV2, meshtastic_TAKPacketV2, 2)
File diff suppressed because it is too large Load Diff
+3 -5
View File
@@ -27,9 +27,6 @@ PB_BIND(meshtastic_KeyVerification, meshtastic_KeyVerification, AUTO)
PB_BIND(meshtastic_StoreForwardPlusPlus, meshtastic_StoreForwardPlusPlus, 2)
PB_BIND(meshtastic_RemoteShell, meshtastic_RemoteShell, AUTO)
PB_BIND(meshtastic_Waypoint, meshtastic_Waypoint, AUTO)
@@ -45,6 +42,9 @@ PB_BIND(meshtastic_MeshPacket, meshtastic_MeshPacket, 2)
PB_BIND(meshtastic_NodeInfo, meshtastic_NodeInfo, 2)
PB_BIND(meshtastic_NodeInfoBatch, meshtastic_NodeInfoBatch, AUTO)
PB_BIND(meshtastic_MyNodeInfo, meshtastic_MyNodeInfo, AUTO)
@@ -132,8 +132,6 @@ PB_BIND(meshtastic_ChunkedPayloadResponse, meshtastic_ChunkedPayloadResponse, AU
+28 -91
View File
@@ -308,13 +308,6 @@ typedef enum _meshtastic_HardwareModel {
meshtastic_HardwareModel_TDISPLAY_S3_PRO = 126,
/* Heltec Mesh Node T096 board features an nRF52840 CPU and a TFT screen. */
meshtastic_HardwareModel_HELTEC_MESH_NODE_T096 = 127,
/* Seeed studio T1000-E Pro tracker card. NRF52840 w/ LR2021 radio,
GPS, button, buzzer, and sensors. */
meshtastic_HardwareModel_TRACKER_T1000_E_PRO = 128,
/* Elecrow ThinkNode M7, M8 and M9 */
meshtastic_HardwareModel_THINKNODE_M7 = 129,
meshtastic_HardwareModel_THINKNODE_M8 = 130,
meshtastic_HardwareModel_THINKNODE_M9 = 131,
/* ------------------------------------------------------------------------------------------------------------------------------------------
Reserved ID For developing private Ports. These will show up in live traffic sparsely, so we can use a high number. Keep it within 8 bits.
------------------------------------------------------------------------------------------------------------------------------------------ */
@@ -518,27 +511,6 @@ typedef enum _meshtastic_StoreForwardPlusPlus_SFPP_message_type {
meshtastic_StoreForwardPlusPlus_SFPP_message_type_LINK_PROVIDE_SECONDHALF = 6
} meshtastic_StoreForwardPlusPlus_SFPP_message_type;
/* Frame op code for PTY session control and stream transport.
Values 1-63 are client->server requests.
Values 64-127 are server->client responses/events. */
typedef enum _meshtastic_RemoteShell_OpCode {
meshtastic_RemoteShell_OpCode_OP_UNSET = 0,
/* Client -> server */
meshtastic_RemoteShell_OpCode_OPEN = 1,
meshtastic_RemoteShell_OpCode_INPUT = 2,
meshtastic_RemoteShell_OpCode_RESIZE = 3,
meshtastic_RemoteShell_OpCode_CLOSE = 4,
meshtastic_RemoteShell_OpCode_PING = 5,
meshtastic_RemoteShell_OpCode_ACK = 6,
/* Server -> client */
meshtastic_RemoteShell_OpCode_OPEN_OK = 64,
meshtastic_RemoteShell_OpCode_OUTPUT = 65,
meshtastic_RemoteShell_OpCode_CLOSED = 66,
meshtastic_RemoteShell_OpCode_ERROR = 67,
meshtastic_RemoteShell_OpCode_PONG = 68
} meshtastic_RemoteShell_OpCode;
/* The priority of this message for sending.
Higher priorities are sent first (when managing the transmit queue).
This field is never sent over the air, it is only used internally inside of a local device node.
@@ -871,31 +843,6 @@ typedef struct _meshtastic_StoreForwardPlusPlus {
uint32_t chain_count;
} meshtastic_StoreForwardPlusPlus;
typedef PB_BYTES_ARRAY_T(200) meshtastic_RemoteShell_payload_t;
/* The actual over-the-mesh message doing RemoteShell */
typedef struct _meshtastic_RemoteShell {
/* Structured frame operation. */
meshtastic_RemoteShell_OpCode op;
/* Logical PTY session identifier. */
uint32_t session_id;
/* Monotonic sequence number for this frame. */
uint32_t seq;
/* Cumulative ack sequence number. */
uint32_t ack_seq;
/* Opaque bytes payload for INPUT/OUTPUT/ERROR and other frame bodies. */
meshtastic_RemoteShell_payload_t payload;
/* Terminal size columns used for OPEN/RESIZE signaling. */
uint32_t cols;
/* Terminal size rows used for OPEN/RESIZE signaling. */
uint32_t rows;
/* Bit flags for protocol extensions. */
uint32_t flags;
/* The last sequence number TX'd. */
uint32_t last_tx_seq;
/* The last sequence number RX'd. */
uint32_t last_rx_seq;
} meshtastic_RemoteShell;
/* Waypoint message, used to share arbitrary locations across the mesh */
typedef struct _meshtastic_Waypoint {
/* Id of the waypoint */
@@ -1090,6 +1037,12 @@ typedef struct _meshtastic_NodeInfo {
bool is_muted;
} meshtastic_NodeInfo;
/* Batched NodeInfo wrapper for efficient bulk transfer during want_config flow.
Allows multiple NodeInfo messages to be packed into a single FromRadio packet. */
typedef struct _meshtastic_NodeInfoBatch {
pb_callback_t items;
} meshtastic_NodeInfoBatch;
typedef PB_BYTES_ARRAY_T(16) meshtastic_MyNodeInfo_device_id_t;
/* Unique local debugging info for this node
Note: we don't include position or the user info, because that will come in the
@@ -1315,6 +1268,9 @@ typedef struct _meshtastic_FromRadio {
meshtastic_ClientNotification clientNotification;
/* Persistent data for device-ui */
meshtastic_DeviceUIConfig deviceuiConfig;
/* Batched NodeInfo messages for efficient bulk transfer.
Used when client opts in via special nonce value. */
meshtastic_NodeInfoBatch node_info_batch;
};
} meshtastic_FromRadio;
@@ -1436,10 +1392,6 @@ extern "C" {
#define _meshtastic_StoreForwardPlusPlus_SFPP_message_type_MAX meshtastic_StoreForwardPlusPlus_SFPP_message_type_LINK_PROVIDE_SECONDHALF
#define _meshtastic_StoreForwardPlusPlus_SFPP_message_type_ARRAYSIZE ((meshtastic_StoreForwardPlusPlus_SFPP_message_type)(meshtastic_StoreForwardPlusPlus_SFPP_message_type_LINK_PROVIDE_SECONDHALF+1))
#define _meshtastic_RemoteShell_OpCode_MIN meshtastic_RemoteShell_OpCode_OP_UNSET
#define _meshtastic_RemoteShell_OpCode_MAX meshtastic_RemoteShell_OpCode_PONG
#define _meshtastic_RemoteShell_OpCode_ARRAYSIZE ((meshtastic_RemoteShell_OpCode)(meshtastic_RemoteShell_OpCode_PONG+1))
#define _meshtastic_MeshPacket_Priority_MIN meshtastic_MeshPacket_Priority_UNSET
#define _meshtastic_MeshPacket_Priority_MAX meshtastic_MeshPacket_Priority_MAX
#define _meshtastic_MeshPacket_Priority_ARRAYSIZE ((meshtastic_MeshPacket_Priority)(meshtastic_MeshPacket_Priority_MAX+1))
@@ -1470,8 +1422,6 @@ extern "C" {
#define meshtastic_StoreForwardPlusPlus_sfpp_message_type_ENUMTYPE meshtastic_StoreForwardPlusPlus_SFPP_message_type
#define meshtastic_RemoteShell_op_ENUMTYPE meshtastic_RemoteShell_OpCode
@@ -1480,6 +1430,7 @@ extern "C" {
#define meshtastic_MeshPacket_transport_mechanism_ENUMTYPE meshtastic_MeshPacket_TransportMechanism
#define meshtastic_MyNodeInfo_firmware_edition_ENUMTYPE meshtastic_FirmwareEdition
#define meshtastic_LogRecord_level_ENUMTYPE meshtastic_LogRecord_Level
@@ -1516,12 +1467,12 @@ extern "C" {
#define meshtastic_Data_init_default {_meshtastic_PortNum_MIN, {0, {0}}, 0, 0, 0, 0, 0, 0, false, 0}
#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_RemoteShell_init_default {_meshtastic_RemoteShell_OpCode_MIN, 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}
#define meshtastic_NodeInfoBatch_init_default {{{NULL}, NULL}}
#define meshtastic_MyNodeInfo_init_default {0, 0, 0, {0, {0}}, "", _meshtastic_FirmwareEdition_MIN, 0}
#define meshtastic_LogRecord_init_default {"", 0, "", _meshtastic_LogRecord_Level_MIN}
#define meshtastic_QueueStatus_init_default {0, 0, 0, 0}
@@ -1550,12 +1501,12 @@ extern "C" {
#define meshtastic_Data_init_zero {_meshtastic_PortNum_MIN, {0, {0}}, 0, 0, 0, 0, 0, 0, false, 0}
#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_RemoteShell_init_zero {_meshtastic_RemoteShell_OpCode_MIN, 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}
#define meshtastic_NodeInfoBatch_init_zero {{{NULL}, NULL}}
#define meshtastic_MyNodeInfo_init_zero {0, 0, 0, {0, {0}}, "", _meshtastic_FirmwareEdition_MIN, 0}
#define meshtastic_LogRecord_init_zero {"", 0, "", _meshtastic_LogRecord_Level_MIN}
#define meshtastic_QueueStatus_init_zero {0, 0, 0, 0}
@@ -1640,16 +1591,6 @@ extern "C" {
#define meshtastic_StoreForwardPlusPlus_encapsulated_from_tag 8
#define meshtastic_StoreForwardPlusPlus_encapsulated_rxtime_tag 9
#define meshtastic_StoreForwardPlusPlus_chain_count_tag 10
#define meshtastic_RemoteShell_op_tag 1
#define meshtastic_RemoteShell_session_id_tag 2
#define meshtastic_RemoteShell_seq_tag 3
#define meshtastic_RemoteShell_ack_seq_tag 4
#define meshtastic_RemoteShell_payload_tag 5
#define meshtastic_RemoteShell_cols_tag 6
#define meshtastic_RemoteShell_rows_tag 7
#define meshtastic_RemoteShell_flags_tag 8
#define meshtastic_RemoteShell_last_tx_seq_tag 9
#define meshtastic_RemoteShell_last_rx_seq_tag 10
#define meshtastic_Waypoint_id_tag 1
#define meshtastic_Waypoint_latitude_i_tag 2
#define meshtastic_Waypoint_longitude_i_tag 3
@@ -1697,6 +1638,7 @@ extern "C" {
#define meshtastic_NodeInfo_is_ignored_tag 11
#define meshtastic_NodeInfo_is_key_manually_verified_tag 12
#define meshtastic_NodeInfo_is_muted_tag 13
#define meshtastic_NodeInfoBatch_items_tag 1
#define meshtastic_MyNodeInfo_my_node_num_tag 1
#define meshtastic_MyNodeInfo_reboot_count_tag 8
#define meshtastic_MyNodeInfo_min_app_version_tag 11
@@ -1771,6 +1713,7 @@ extern "C" {
#define meshtastic_FromRadio_fileInfo_tag 15
#define meshtastic_FromRadio_clientNotification_tag 16
#define meshtastic_FromRadio_deviceuiConfig_tag 17
#define meshtastic_FromRadio_node_info_batch_tag 18
#define meshtastic_Heartbeat_nonce_tag 1
#define meshtastic_ToRadio_packet_tag 1
#define meshtastic_ToRadio_want_config_id_tag 3
@@ -1882,20 +1825,6 @@ X(a, STATIC, SINGULAR, UINT32, chain_count, 10)
#define meshtastic_StoreForwardPlusPlus_CALLBACK NULL
#define meshtastic_StoreForwardPlusPlus_DEFAULT NULL
#define meshtastic_RemoteShell_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, UENUM, op, 1) \
X(a, STATIC, SINGULAR, UINT32, session_id, 2) \
X(a, STATIC, SINGULAR, UINT32, seq, 3) \
X(a, STATIC, SINGULAR, UINT32, ack_seq, 4) \
X(a, STATIC, SINGULAR, BYTES, payload, 5) \
X(a, STATIC, SINGULAR, UINT32, cols, 6) \
X(a, STATIC, SINGULAR, UINT32, rows, 7) \
X(a, STATIC, SINGULAR, UINT32, flags, 8) \
X(a, STATIC, SINGULAR, UINT32, last_tx_seq, 9) \
X(a, STATIC, SINGULAR, UINT32, last_rx_seq, 10)
#define meshtastic_RemoteShell_CALLBACK NULL
#define meshtastic_RemoteShell_DEFAULT NULL
#define meshtastic_Waypoint_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, UINT32, id, 1) \
X(a, STATIC, OPTIONAL, SFIXED32, latitude_i, 2) \
@@ -1967,6 +1896,12 @@ X(a, STATIC, SINGULAR, BOOL, is_muted, 13)
#define meshtastic_NodeInfo_position_MSGTYPE meshtastic_Position
#define meshtastic_NodeInfo_device_metrics_MSGTYPE meshtastic_DeviceMetrics
#define meshtastic_NodeInfoBatch_FIELDLIST(X, a) \
X(a, CALLBACK, REPEATED, MESSAGE, items, 1)
#define meshtastic_NodeInfoBatch_CALLBACK pb_default_field_callback
#define meshtastic_NodeInfoBatch_DEFAULT NULL
#define meshtastic_NodeInfoBatch_items_MSGTYPE meshtastic_NodeInfo
#define meshtastic_MyNodeInfo_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, UINT32, my_node_num, 1) \
X(a, STATIC, SINGULAR, UINT32, reboot_count, 8) \
@@ -2011,7 +1946,8 @@ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,metadata,metadata), 13) \
X(a, STATIC, ONEOF, MESSAGE, (payload_variant,mqttClientProxyMessage,mqttClientProxyMessage), 14) \
X(a, STATIC, ONEOF, MESSAGE, (payload_variant,fileInfo,fileInfo), 15) \
X(a, STATIC, ONEOF, MESSAGE, (payload_variant,clientNotification,clientNotification), 16) \
X(a, STATIC, ONEOF, MESSAGE, (payload_variant,deviceuiConfig,deviceuiConfig), 17)
X(a, STATIC, ONEOF, MESSAGE, (payload_variant,deviceuiConfig,deviceuiConfig), 17) \
X(a, STATIC, ONEOF, MESSAGE, (payload_variant,node_info_batch,node_info_batch), 18)
#define meshtastic_FromRadio_CALLBACK NULL
#define meshtastic_FromRadio_DEFAULT NULL
#define meshtastic_FromRadio_payload_variant_packet_MSGTYPE meshtastic_MeshPacket
@@ -2028,6 +1964,7 @@ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,deviceuiConfig,deviceuiConfi
#define meshtastic_FromRadio_payload_variant_fileInfo_MSGTYPE meshtastic_FileInfo
#define meshtastic_FromRadio_payload_variant_clientNotification_MSGTYPE meshtastic_ClientNotification
#define meshtastic_FromRadio_payload_variant_deviceuiConfig_MSGTYPE meshtastic_DeviceUIConfig
#define meshtastic_FromRadio_payload_variant_node_info_batch_MSGTYPE meshtastic_NodeInfoBatch
#define meshtastic_ClientNotification_FIELDLIST(X, a) \
X(a, STATIC, OPTIONAL, UINT32, reply_id, 1) \
@@ -2178,12 +2115,12 @@ extern const pb_msgdesc_t meshtastic_Routing_msg;
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_RemoteShell_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;
extern const pb_msgdesc_t meshtastic_NodeInfoBatch_msg;
extern const pb_msgdesc_t meshtastic_MyNodeInfo_msg;
extern const pb_msgdesc_t meshtastic_LogRecord_msg;
extern const pb_msgdesc_t meshtastic_QueueStatus_msg;
@@ -2214,12 +2151,12 @@ extern const pb_msgdesc_t meshtastic_ChunkedPayloadResponse_msg;
#define meshtastic_Data_fields &meshtastic_Data_msg
#define meshtastic_KeyVerification_fields &meshtastic_KeyVerification_msg
#define meshtastic_StoreForwardPlusPlus_fields &meshtastic_StoreForwardPlusPlus_msg
#define meshtastic_RemoteShell_fields &meshtastic_RemoteShell_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
#define meshtastic_NodeInfoBatch_fields &meshtastic_NodeInfoBatch_msg
#define meshtastic_MyNodeInfo_fields &meshtastic_MyNodeInfo_msg
#define meshtastic_LogRecord_fields &meshtastic_LogRecord_msg
#define meshtastic_QueueStatus_fields &meshtastic_QueueStatus_msg
@@ -2243,9 +2180,11 @@ extern const pb_msgdesc_t meshtastic_ChunkedPayloadResponse_msg;
#define meshtastic_ChunkedPayloadResponse_fields &meshtastic_ChunkedPayloadResponse_msg
/* Maximum encoded size of messages (where known) */
/* meshtastic_NodeInfoBatch_size depends on runtime parameters */
/* meshtastic_FromRadio_size depends on runtime parameters */
/* meshtastic_resend_chunks_size depends on runtime parameters */
/* meshtastic_ChunkedPayloadResponse_size depends on runtime parameters */
#define MESHTASTIC_MESHTASTIC_MESH_PB_H_MAX_SIZE meshtastic_FromRadio_size
#define MESHTASTIC_MESHTASTIC_MESH_PB_H_MAX_SIZE meshtastic_ToRadio_size
#define meshtastic_ChunkedPayload_size 245
#define meshtastic_ClientNotification_size 482
#define meshtastic_Compressed_size 239
@@ -2253,7 +2192,6 @@ extern const pb_msgdesc_t meshtastic_ChunkedPayloadResponse_msg;
#define meshtastic_DeviceMetadata_size 54
#define meshtastic_DuplicatedPublicKey_size 0
#define meshtastic_FileInfo_size 236
#define meshtastic_FromRadio_size 510
#define meshtastic_Heartbeat_size 6
#define meshtastic_KeyVerificationFinal_size 65
#define meshtastic_KeyVerificationNumberInform_size 58
@@ -2270,7 +2208,6 @@ extern const pb_msgdesc_t meshtastic_ChunkedPayloadResponse_msg;
#define meshtastic_NodeRemoteHardwarePin_size 29
#define meshtastic_Position_size 144
#define meshtastic_QueueStatus_size 23
#define meshtastic_RemoteShell_size 253
#define meshtastic_RouteDiscovery_size 256
#define meshtastic_Routing_size 259
#define meshtastic_StatusMessage_size 81
@@ -76,8 +76,6 @@ typedef enum _meshtastic_PortNum {
meshtastic_PortNum_ALERT_APP = 11,
/* Module/port for handling key verification requests. */
meshtastic_PortNum_KEY_VERIFICATION_APP = 12,
/* Module/port for handling primitive remote shell access. */
meshtastic_PortNum_REMOTE_SHELL_APP = 13,
/* Provides a 'ping' service that replies to any packet it receives.
Also serves as a small example module.
ENCODING: ASCII Plaintext */
@@ -152,10 +150,6 @@ typedef enum _meshtastic_PortNum {
arbitrary telemetry over meshtastic that is not covered by telemetry.proto
ENCODING: CayenneLLP */
meshtastic_PortNum_CAYENNE_APP = 77,
/* ATAK Plugin V2
Portnum for payloads from the official Meshtastic ATAK plugin using
TAKPacketV2 with zstd dictionary compression. */
meshtastic_PortNum_ATAK_PLUGIN_V2 = 78,
/* GroupAlarm integration
Used for transporting GroupAlarm-related messages between Meshtastic nodes
and companion applications/services. */
+7 -14
View File
@@ -113,9 +113,7 @@ typedef enum _meshtastic_TelemetrySensorType {
/* SCD30 CO2, humidity, temperature sensor */
meshtastic_TelemetrySensorType_SCD30 = 49,
/* SHT family of sensors for temperature and humidity */
meshtastic_TelemetrySensorType_SHTXX = 50,
/* DS248X Bridge for one-wire temperature sensors */
meshtastic_TelemetrySensorType_DS248X = 51
meshtastic_TelemetrySensorType_SHTXX = 50
} meshtastic_TelemetrySensorType;
/* Struct definitions */
@@ -208,9 +206,6 @@ typedef struct _meshtastic_EnvironmentMetrics {
/* Soil temperature measured (*C) */
bool has_soil_temperature;
float soil_temperature;
/* One-wire temperature (*C) */
pb_size_t one_wire_temperature_count;
float one_wire_temperature[8];
} meshtastic_EnvironmentMetrics;
/* Power Metrics (voltage / current / etc) */
@@ -496,8 +491,8 @@ extern "C" {
/* Helper constants for enums */
#define _meshtastic_TelemetrySensorType_MIN meshtastic_TelemetrySensorType_SENSOR_UNSET
#define _meshtastic_TelemetrySensorType_MAX meshtastic_TelemetrySensorType_DS248X
#define _meshtastic_TelemetrySensorType_ARRAYSIZE ((meshtastic_TelemetrySensorType)(meshtastic_TelemetrySensorType_DS248X+1))
#define _meshtastic_TelemetrySensorType_MAX meshtastic_TelemetrySensorType_SHTXX
#define _meshtastic_TelemetrySensorType_ARRAYSIZE ((meshtastic_TelemetrySensorType)(meshtastic_TelemetrySensorType_SHTXX+1))
@@ -513,7 +508,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, 0, {0, 0, 0, 0, 0, 0, 0, 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}
#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, 0}
@@ -524,7 +519,7 @@ extern "C" {
#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, 0, {0, 0, 0, 0, 0, 0, 0, 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}
#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, 0}
@@ -563,7 +558,6 @@ extern "C" {
#define meshtastic_EnvironmentMetrics_rainfall_24h_tag 20
#define meshtastic_EnvironmentMetrics_soil_moisture_tag 21
#define meshtastic_EnvironmentMetrics_soil_temperature_tag 22
#define meshtastic_EnvironmentMetrics_one_wire_temperature_tag 23
#define meshtastic_PowerMetrics_ch1_voltage_tag 1
#define meshtastic_PowerMetrics_ch1_current_tag 2
#define meshtastic_PowerMetrics_ch2_voltage_tag 3
@@ -689,8 +683,7 @@ X(a, STATIC, OPTIONAL, FLOAT, radiation, 18) \
X(a, STATIC, OPTIONAL, FLOAT, rainfall_1h, 19) \
X(a, STATIC, OPTIONAL, FLOAT, rainfall_24h, 20) \
X(a, STATIC, OPTIONAL, UINT32, soil_moisture, 21) \
X(a, STATIC, OPTIONAL, FLOAT, soil_temperature, 22) \
X(a, STATIC, REPEATED, FLOAT, one_wire_temperature, 23)
X(a, STATIC, OPTIONAL, FLOAT, soil_temperature, 22)
#define meshtastic_EnvironmentMetrics_CALLBACK NULL
#define meshtastic_EnvironmentMetrics_DEFAULT NULL
@@ -859,7 +852,7 @@ extern const pb_msgdesc_t meshtastic_SEN5XState_msg;
#define MESHTASTIC_MESHTASTIC_TELEMETRY_PB_H_MAX_SIZE meshtastic_Telemetry_size
#define meshtastic_AirQualityMetrics_size 150
#define meshtastic_DeviceMetrics_size 27
#define meshtastic_EnvironmentMetrics_size 161
#define meshtastic_EnvironmentMetrics_size 113
#define meshtastic_HealthMetrics_size 11
#define meshtastic_HostMetrics_size 264
#define meshtastic_LocalStats_size 87
+179 -257
View File
@@ -11,12 +11,11 @@
#endif
#include "SPILock.h"
#include "power.h"
#include "serialization/JSON.h"
#include <FSCommon.h>
#include <HTTPBodyParser.hpp>
#include <HTTPMultipartBodyParser.hpp>
#include <HTTPURLEncodedBodyParser.hpp>
#include <cmath>
#include <sstream>
#ifdef ARCH_ESP32
#include "esp_task_wdt.h"
@@ -260,95 +259,40 @@ void htmlDeleteDir(const char *dirname)
root.close();
}
// Escape a string into a JSON double-quoted literal. Matches the previous
// SimpleJSON StringifyString behavior (0x00-0x1F and 0x7F -> \u00xx lowercase,
// escapes " \ / \b \f \n \r \t, UTF-8 passes through unchanged).
static std::string jsonEscape(const std::string &str)
{
std::string out = "\"";
for (size_t i = 0; i < str.size(); ++i) {
char chr = str[i];
if (chr == '"' || chr == '\\' || chr == '/') {
out += '\\';
out += chr;
} else if (chr == '\b') {
out += "\\b";
} else if (chr == '\f') {
out += "\\f";
} else if (chr == '\n') {
out += "\\n";
} else if (chr == '\r') {
out += "\\r";
} else if (chr == '\t') {
out += "\\t";
} else if ((unsigned char)chr < 0x20 || chr == 0x7F) {
char buf[8];
snprintf(buf, sizeof(buf), "\\u%04x", (unsigned char)chr);
out += buf;
} else {
out += chr;
}
}
out += "\"";
return out;
}
// Format a numeric value the way the previous SimpleJSON serializer did
// (std::stringstream with precision 15, NaN/Inf -> "null").
static std::string jsonNum(double v)
{
if (std::isinf(v) || std::isnan(v))
return "null";
std::ostringstream ss;
ss.precision(15);
ss << v;
return ss.str();
}
// Build a serialized JSON array string listing files in `dirname`.
// Subdirectories recurse as nested arrays (up to `levels` deep).
std::string htmlListDir(const char *dirname, uint8_t levels)
JSONArray htmlListDir(const char *dirname, uint8_t levels)
{
File root = FSCom.open(dirname, FILE_O_READ);
std::string out = "[";
bool first = true;
JSONArray fileList;
if (!root) {
out += "]";
return out;
return fileList;
}
if (!root.isDirectory()) {
out += "]";
return out;
return fileList;
}
// iterate over the file list
File file = root.openNextFile();
while (file) {
std::string element;
bool haveElement = false;
if (file.isDirectory() && !String(file.name()).endsWith(".")) {
if (levels) {
#ifdef ARCH_ESP32
element = htmlListDir(file.path(), levels - 1);
fileList.push_back(new JSONValue(htmlListDir(file.path(), levels - 1)));
#else
element = htmlListDir(file.name(), levels - 1);
fileList.push_back(new JSONValue(htmlListDir(file.name(), levels - 1)));
#endif
haveElement = true;
file.close();
}
} else {
JSONObject thisFileMap;
thisFileMap["size"] = new JSONValue((int)file.size());
#ifdef ARCH_ESP32
String fileName = String(file.path()).substring(1);
thisFileMap["name"] = new JSONValue(fileName.c_str());
#else
String fileName = String(file.name()).substring(1);
thisFileMap["name"] = new JSONValue(fileName.c_str());
#endif
String tempName = String(file.name()).substring(1);
// Keys in the previous std::map<string,...> were emitted in
// alphabetical order: name, nameModified, size.
element = "{";
element += jsonEscape("name");
element += ":";
element += jsonEscape(fileName.c_str());
if (tempName.endsWith(".gz")) {
#ifdef ARCH_ESP32
String modifiedFile = String(file.path()).substring(1);
@@ -356,30 +300,15 @@ std::string htmlListDir(const char *dirname, uint8_t levels)
String modifiedFile = String(file.name()).substring(1);
#endif
modifiedFile.remove((modifiedFile.length() - 3), 3);
element += ",";
element += jsonEscape("nameModified");
element += ":";
element += jsonEscape(modifiedFile.c_str());
thisFileMap["nameModified"] = new JSONValue(modifiedFile.c_str());
}
element += ",";
element += jsonEscape("size");
element += ":";
element += jsonNum((int)file.size());
element += "}";
haveElement = true;
}
if (haveElement) {
if (!first)
out += ",";
out += element;
first = false;
fileList.push_back(new JSONValue(thisFileMap));
}
file.close();
file = root.openNextFile();
}
root.close();
out += "]";
return out;
return fileList;
}
void handleFsBrowseStatic(HTTPRequest *req, HTTPResponse *res)
@@ -389,25 +318,28 @@ void handleFsBrowseStatic(HTTPRequest *req, HTTPResponse *res)
res->setHeader("Access-Control-Allow-Methods", "GET");
concurrency::LockGuard g(spiLock);
std::string fileList = htmlListDir("/static", 10);
auto fileList = htmlListDir("/static", 10);
uint64_t total = FSCom.totalBytes();
uint64_t used = FSCom.usedBytes();
// create json output structure
JSONObject filesystemObj;
filesystemObj["total"] = new JSONValue((int)FSCom.totalBytes());
filesystemObj["used"] = new JSONValue((int)FSCom.usedBytes());
filesystemObj["free"] = new JSONValue(int(FSCom.totalBytes() - FSCom.usedBytes()));
// Key order matches the previous std::map-based emission (alphabetical).
std::string out;
out.reserve(fileList.size() + 128);
out += "{\"data\":{\"files\":";
out += fileList;
out += ",\"filesystem\":{\"free\":";
out += jsonNum((int)(total - used));
out += ",\"total\":";
out += jsonNum((int)total);
out += ",\"used\":";
out += jsonNum((int)used);
out += "}},\"status\":\"ok\"}";
JSONObject jsonObjInner;
jsonObjInner["files"] = new JSONValue(fileList);
jsonObjInner["filesystem"] = new JSONValue(filesystemObj);
res->print(out.c_str());
JSONObject jsonObjOuter;
jsonObjOuter["data"] = new JSONValue(jsonObjInner);
jsonObjOuter["status"] = new JSONValue("ok");
JSONValue *value = new JSONValue(jsonObjOuter);
std::string jsonString = value->Stringify();
res->print(jsonString.c_str());
delete value;
}
void handleFsDeleteStatic(HTTPRequest *req, HTTPResponse *res)
@@ -422,13 +354,27 @@ void handleFsDeleteStatic(HTTPRequest *req, HTTPResponse *res)
if (params->getQueryParameter("delete", paramValDelete)) {
std::string pathDelete = "/" + paramValDelete;
concurrency::LockGuard g(spiLock);
const char *status = FSCom.remove(pathDelete.c_str()) ? "ok" : "Error";
LOG_INFO("%s", pathDelete.c_str());
std::string out = "{\"status\":";
out += jsonEscape(status);
out += "}";
res->print(out.c_str());
return;
if (FSCom.remove(pathDelete.c_str())) {
LOG_INFO("%s", pathDelete.c_str());
JSONObject jsonObjOuter;
jsonObjOuter["status"] = new JSONValue("ok");
JSONValue *value = new JSONValue(jsonObjOuter);
std::string jsonString = value->Stringify();
res->print(jsonString.c_str());
delete value;
return;
} else {
LOG_INFO("%s", pathDelete.c_str());
JSONObject jsonObjOuter;
jsonObjOuter["status"] = new JSONValue("Error");
JSONValue *value = new JSONValue(jsonObjOuter);
std::string jsonString = value->Stringify();
res->print(jsonString.c_str());
delete value;
return;
}
}
}
@@ -669,113 +615,95 @@ void handleReport(HTTPRequest *req, HTTPResponse *res)
res->println("<pre>");
}
auto arrayFromLog = [](const uint32_t *logArray, int count) -> std::string {
std::string s = "[";
// Helper lambda to create JSON array and clean up memory properly
auto createJSONArrayFromLog = [](const uint32_t *logArray, int count) -> JSONValue * {
JSONArray tempArray;
for (int i = 0; i < count; i++) {
if (i)
s += ",";
s += jsonNum((int)logArray[i]);
tempArray.push_back(new JSONValue((int)logArray[i]));
}
s += "]";
return s;
JSONValue *result = new JSONValue(tempArray);
// Note: Don't delete tempArray elements here - JSONValue now owns them
return result;
};
// data->airtime->tx_log
uint32_t *logArray;
logArray = airTime->airtimeReport(TX_LOG);
std::string txLog = arrayFromLog(logArray, airTime->getPeriodsToLog());
logArray = airTime->airtimeReport(RX_LOG);
std::string rxLog = arrayFromLog(logArray, airTime->getPeriodsToLog());
logArray = airTime->airtimeReport(RX_ALL_LOG);
std::string rxAllLog = arrayFromLog(logArray, airTime->getPeriodsToLog());
JSONValue *txLogJsonValue = createJSONArrayFromLog(logArray, airTime->getPeriodsToLog());
// data->airtime->rx_log
logArray = airTime->airtimeReport(RX_LOG);
JSONValue *rxLogJsonValue = createJSONArrayFromLog(logArray, airTime->getPeriodsToLog());
// data->airtime->rx_all_log
logArray = airTime->airtimeReport(RX_ALL_LOG);
JSONValue *rxAllLogJsonValue = createJSONArrayFromLog(logArray, airTime->getPeriodsToLog());
// data->airtime
JSONObject jsonObjAirtime;
jsonObjAirtime["tx_log"] = txLogJsonValue;
jsonObjAirtime["rx_log"] = rxLogJsonValue;
jsonObjAirtime["rx_all_log"] = rxAllLogJsonValue;
jsonObjAirtime["channel_utilization"] = new JSONValue(airTime->channelUtilizationPercent());
jsonObjAirtime["utilization_tx"] = new JSONValue(airTime->utilizationTXPercent());
jsonObjAirtime["seconds_since_boot"] = new JSONValue(int(airTime->getSecondsSinceBoot()));
jsonObjAirtime["seconds_per_period"] = new JSONValue(int(airTime->getSecondsPerPeriod()));
jsonObjAirtime["periods_to_log"] = new JSONValue(airTime->getPeriodsToLog());
// data->wifi
JSONObject jsonObjWifi;
jsonObjWifi["rssi"] = new JSONValue(WiFi.RSSI());
String wifiIPString = WiFi.localIP().toString();
std::string wifiIP = wifiIPString.c_str();
jsonObjWifi["ip"] = new JSONValue(wifiIP.c_str());
// data->memory
JSONObject jsonObjMemory;
jsonObjMemory["heap_total"] = new JSONValue((int)memGet.getHeapSize());
jsonObjMemory["heap_free"] = new JSONValue((int)memGet.getFreeHeap());
jsonObjMemory["psram_total"] = new JSONValue((int)memGet.getPsramSize());
jsonObjMemory["psram_free"] = new JSONValue((int)memGet.getFreePsram());
spiLock->lock();
uint64_t fsTotal = FSCom.totalBytes();
uint64_t fsUsed = FSCom.usedBytes();
jsonObjMemory["fs_total"] = new JSONValue((int)FSCom.totalBytes());
jsonObjMemory["fs_used"] = new JSONValue((int)FSCom.usedBytes());
jsonObjMemory["fs_free"] = new JSONValue(int(FSCom.totalBytes() - FSCom.usedBytes()));
spiLock->unlock();
// Emit keys in the same alphabetical order as the previous
// std::map-based JSON output to keep responses byte-compatible.
std::string out;
out.reserve(1024);
out += "{\"data\":{";
// data->power
JSONObject jsonObjPower;
jsonObjPower["battery_percent"] = new JSONValue(powerStatus->getBatteryChargePercent());
jsonObjPower["battery_voltage_mv"] = new JSONValue(powerStatus->getBatteryVoltageMv());
jsonObjPower["has_battery"] = new JSONValue(BoolToString(powerStatus->getHasBattery()));
jsonObjPower["has_usb"] = new JSONValue(BoolToString(powerStatus->getHasUSB()));
jsonObjPower["is_charging"] = new JSONValue(BoolToString(powerStatus->getIsCharging()));
// airtime
out += "\"airtime\":{";
out += "\"channel_utilization\":";
out += jsonNum(airTime->channelUtilizationPercent());
out += ",\"periods_to_log\":";
out += jsonNum(airTime->getPeriodsToLog());
out += ",\"rx_all_log\":";
out += rxAllLog;
out += ",\"rx_log\":";
out += rxLog;
out += ",\"seconds_per_period\":";
out += jsonNum((int)airTime->getSecondsPerPeriod());
out += ",\"seconds_since_boot\":";
out += jsonNum((int)airTime->getSecondsSinceBoot());
out += ",\"tx_log\":";
out += txLog;
out += ",\"utilization_tx\":";
out += jsonNum(airTime->utilizationTXPercent());
out += "}";
// data->device
JSONObject jsonObjDevice;
jsonObjDevice["reboot_counter"] = new JSONValue((int)myNodeInfo.reboot_count);
// device
out += ",\"device\":{\"reboot_counter\":";
out += jsonNum((int)myNodeInfo.reboot_count);
out += "}";
// data->radio
JSONObject jsonObjRadio;
jsonObjRadio["frequency"] = new JSONValue(RadioLibInterface::instance->getFreq());
jsonObjRadio["lora_channel"] = new JSONValue((int)RadioLibInterface::instance->getChannelNum() + 1);
// memory
out += ",\"memory\":{";
out += "\"fs_free\":";
out += jsonNum((int)(fsTotal - fsUsed));
out += ",\"fs_total\":";
out += jsonNum((int)fsTotal);
out += ",\"fs_used\":";
out += jsonNum((int)fsUsed);
out += ",\"heap_free\":";
out += jsonNum((int)memGet.getFreeHeap());
out += ",\"heap_total\":";
out += jsonNum((int)memGet.getHeapSize());
out += ",\"psram_free\":";
out += jsonNum((int)memGet.getFreePsram());
out += ",\"psram_total\":";
out += jsonNum((int)memGet.getPsramSize());
out += "}";
// collect data to inner data object
JSONObject jsonObjInner;
jsonObjInner["airtime"] = new JSONValue(jsonObjAirtime);
jsonObjInner["wifi"] = new JSONValue(jsonObjWifi);
jsonObjInner["memory"] = new JSONValue(jsonObjMemory);
jsonObjInner["power"] = new JSONValue(jsonObjPower);
jsonObjInner["device"] = new JSONValue(jsonObjDevice);
jsonObjInner["radio"] = new JSONValue(jsonObjRadio);
// power (has_* / is_charging were serialized as the strings "true"/"false")
out += ",\"power\":{";
out += "\"battery_percent\":";
out += jsonNum(powerStatus->getBatteryChargePercent());
out += ",\"battery_voltage_mv\":";
out += jsonNum(powerStatus->getBatteryVoltageMv());
out += ",\"has_battery\":";
out += jsonEscape(BoolToString(powerStatus->getHasBattery()));
out += ",\"has_usb\":";
out += jsonEscape(BoolToString(powerStatus->getHasUSB()));
out += ",\"is_charging\":";
out += jsonEscape(BoolToString(powerStatus->getIsCharging()));
out += "}";
// radio
out += ",\"radio\":{\"frequency\":";
out += jsonNum(RadioLibInterface::instance->getFreq());
out += ",\"lora_channel\":";
out += jsonNum((int)RadioLibInterface::instance->getChannelNum() + 1);
out += "}";
// wifi
out += ",\"wifi\":{\"ip\":";
out += jsonEscape(wifiIP);
out += ",\"rssi\":";
out += jsonNum(WiFi.RSSI());
out += "}";
out += "},\"status\":\"ok\"}";
res->print(out.c_str());
// create json output structure
JSONObject jsonObjOuter;
jsonObjOuter["data"] = new JSONValue(jsonObjInner);
jsonObjOuter["status"] = new JSONValue("ok");
// serialize and write it to the stream
JSONValue *value = new JSONValue(jsonObjOuter);
std::string jsonString = value->Stringify();
res->print(jsonString.c_str());
delete value;
}
void handleNodes(HTTPRequest *req, HTTPResponse *res)
@@ -796,65 +724,58 @@ void handleNodes(HTTPRequest *req, HTTPResponse *res)
res->println("<pre>");
}
std::string out;
out.reserve(2048);
out += "{\"data\":{\"nodes\":[";
JSONArray nodesArray;
bool firstNode = true;
uint32_t readIndex = 0;
const meshtastic_NodeInfoLite *tempNodeInfo = nodeDB->readNextMeshNode(readIndex);
while (tempNodeInfo != NULL) {
if (tempNodeInfo->has_user) {
JSONObject node;
char id[16];
snprintf(id, sizeof(id), "!%08x", tempNodeInfo->num);
node["id"] = new JSONValue(id);
node["snr"] = new JSONValue(tempNodeInfo->snr);
node["via_mqtt"] = new JSONValue(BoolToString(tempNodeInfo->via_mqtt));
node["last_heard"] = new JSONValue((int)tempNodeInfo->last_heard);
node["position"] = new JSONValue();
if (nodeDB->hasValidPosition(tempNodeInfo)) {
JSONObject position;
position["latitude"] = new JSONValue((float)tempNodeInfo->position.latitude_i * 1e-7);
position["longitude"] = new JSONValue((float)tempNodeInfo->position.longitude_i * 1e-7);
position["altitude"] = new JSONValue((int)tempNodeInfo->position.altitude);
node["position"] = new JSONValue(position);
}
node["long_name"] = new JSONValue(tempNodeInfo->user.long_name);
node["short_name"] = new JSONValue(tempNodeInfo->user.short_name);
char macStr[18];
snprintf(macStr, sizeof(macStr), "%02X:%02X:%02X:%02X:%02X:%02X", tempNodeInfo->user.macaddr[0],
tempNodeInfo->user.macaddr[1], tempNodeInfo->user.macaddr[2], tempNodeInfo->user.macaddr[3],
tempNodeInfo->user.macaddr[4], tempNodeInfo->user.macaddr[5]);
node["mac_address"] = new JSONValue(macStr);
node["hw_model"] = new JSONValue(tempNodeInfo->user.hw_model);
std::string position;
if (nodeDB->hasValidPosition(tempNodeInfo)) {
position = "{\"altitude\":";
position += jsonNum((int)tempNodeInfo->position.altitude);
position += ",\"latitude\":";
position += jsonNum((float)tempNodeInfo->position.latitude_i * 1e-7);
position += ",\"longitude\":";
position += jsonNum((float)tempNodeInfo->position.longitude_i * 1e-7);
position += "}";
} else {
position = "null";
}
if (!firstNode)
out += ",";
firstNode = false;
// Alphabetical key order matches previous std::map-based output.
out += "{\"hw_model\":";
out += jsonNum(tempNodeInfo->user.hw_model);
out += ",\"id\":";
out += jsonEscape(id);
out += ",\"last_heard\":";
out += jsonNum((int)tempNodeInfo->last_heard);
out += ",\"long_name\":";
out += jsonEscape(tempNodeInfo->user.long_name);
out += ",\"mac_address\":";
out += jsonEscape(macStr);
out += ",\"position\":";
out += position;
out += ",\"short_name\":";
out += jsonEscape(tempNodeInfo->user.short_name);
out += ",\"snr\":";
out += jsonNum(tempNodeInfo->snr);
out += ",\"via_mqtt\":";
out += jsonEscape(BoolToString(tempNodeInfo->via_mqtt));
out += "}";
nodesArray.push_back(new JSONValue(node));
}
tempNodeInfo = nodeDB->readNextMeshNode(readIndex);
}
out += "]},\"status\":\"ok\"}";
res->print(out.c_str());
// collect data to inner data object
JSONObject jsonObjInner;
jsonObjInner["nodes"] = new JSONValue(nodesArray);
// create json output structure
JSONObject jsonObjOuter;
jsonObjOuter["data"] = new JSONValue(jsonObjInner);
jsonObjOuter["status"] = new JSONValue("ok");
// serialize and write it to the stream
JSONValue *value = new JSONValue(jsonObjOuter);
std::string jsonString = value->Stringify();
res->print(jsonString.c_str());
delete value;
}
/*
@@ -976,28 +897,20 @@ void handleScanNetworks(HTTPRequest *req, HTTPResponse *res)
int n = WiFi.scanNetworks();
std::string out = "{\"data\":[";
bool firstNet = true;
// build list of network objects
JSONArray networkObjs;
if (n > 0) {
for (int i = 0; i < n; ++i) {
char ssidArray[50];
// The previous implementation pre-escaped quotes before handing
// the value to the JSON serializer; preserve that (byte-compatible
// even if it double-encodes a quote) so existing clients are not
// affected by this refactor.
String ssidString = String(WiFi.SSID(i));
ssidString.replace("\"", "\\\"");
ssidString.toCharArray(ssidArray, 50);
if (WiFi.encryptionType(i) != WIFI_AUTH_OPEN) {
if (!firstNet)
out += ",";
firstNet = false;
out += "{\"rssi\":";
out += jsonNum((int)WiFi.RSSI(i));
out += ",\"ssid\":";
out += jsonEscape(ssidArray);
out += "}";
JSONObject thisNetwork;
thisNetwork["ssid"] = new JSONValue(ssidArray);
thisNetwork["rssi"] = new JSONValue(int(WiFi.RSSI(i)));
networkObjs.push_back(new JSONValue(thisNetwork));
}
// Yield some cpu cycles to IP stack.
// This is important in case the list is large and it takes us time to return
@@ -1005,7 +918,16 @@ void handleScanNetworks(HTTPRequest *req, HTTPResponse *res)
yield();
}
}
out += "],\"status\":\"ok\"}";
res->print(out.c_str());
// build output structure
JSONObject jsonObjOuter;
jsonObjOuter["data"] = new JSONValue(networkObjs);
jsonObjOuter["status"] = new JSONValue("ok");
// serialize and write it to the stream
JSONValue *value = new JSONValue(jsonObjOuter);
std::string jsonString = value->Stringify();
res->print(jsonString.c_str());
delete value;
}
#endif
+1 -1
View File
@@ -480,7 +480,7 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta
#if HAS_SCREEN
IF_SCREEN(screen->showSimpleBanner("Device is rebooting\ninto DFU mode.", 0));
#endif
#if defined(ARCH_NRF52) || defined(ARCH_RP2040) || defined(ARCH_STM32WL)
#if defined(ARCH_NRF52) || defined(ARCH_RP2040)
enterDfuMode();
#endif
break;
@@ -203,10 +203,6 @@ void ExternalNotificationModule::setExternalState(uint8_t index, bool on)
default:
if (output > 0)
digitalWrite(output, (moduleConfig.external_notification.active ? on : !on));
#ifdef PCA_LED_NOTIFICATION
io.digitalWrite(PCA_LED_NOTIFICATION, on);
#endif
break;
}
+1 -1
View File
@@ -651,7 +651,7 @@ void SerialModule::processWXSerial()
LOG_INFO("WS8X : %i %.1fg%.1f %.1fv %.1fv %.1fC rain: %.1f, %i sum", atoi(windDir), strtof(windVel, nullptr),
strtof(windGust, nullptr), batVoltageF, capVoltageF, temperatureF, rain, rainSum);
}
if (gotwind && !Throttle::isWithinTimespanMs(lastAveraged, averageIntervalMillis) && velCount > 0 && dirCount > 0) {
if (gotwind && !Throttle::isWithinTimespanMs(lastAveraged, averageIntervalMillis)) {
// calculate averages and send to the mesh
float velAvg = 1.0 * velSum / velCount;
+1 -14
View File
@@ -29,23 +29,10 @@ int32_t StatusMessageModule::runOnce()
ProcessMessage StatusMessageModule::handleReceived(const meshtastic_MeshPacket &mp)
{
if (mp.which_payload_variant == meshtastic_MeshPacket_decoded_tag) {
meshtastic_StatusMessage incomingMessage = meshtastic_StatusMessage_init_zero;
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);
RecentStatus entry;
entry.fromNodeId = mp.from;
entry.statusText = incomingMessage.status;
recentReceived.push_back(std::move(entry));
// Keep only last MAX_RECENT_STATUSMESSAGES
if (recentReceived.size() > MAX_RECENT_STATUSMESSAGES) {
recentReceived.erase(recentReceived.begin()); // drop oldest
}
}
}
return ProcessMessage::CONTINUE;

Some files were not shown because too many files have changed in this diff Show More