Compare commits

..
Author SHA1 Message Date
Manuel 9785369da9 Merge branch 'master' into crowpanelV2 2026-03-28 07:54:33 +01:00
Ben Meadors 8390b65d24 Merge branch 'master' into crowpanelV2 2026-03-06 16:05:24 -06:00
Ben Meadors e87215b1ed Merge branch 'master' into crowpanelV2 2026-03-06 05:55:46 -06:00
Ben Meadors ab54e5ffc1 Merge branch 'master' into crowpanelV2 2026-03-04 08:25:49 -06:00
Manuel 31794dc7bd Merge branch 'master' into crowpanelV2 2026-03-03 20:00:10 +01:00
Manuel 4570163190 Merge branch 'master' into crowpanelV2 2026-02-02 13:01:04 +01:00
Manuel 86fa917362 Merge branch 'master' into crowpanelV2 2026-01-13 12:36:34 +01:00
Manuel 3575c48e36 Merge branch 'master' into crowpanelV2 2025-12-31 13:35:53 +01:00
Manuel 469abe8dec Merge branch 'master' into crowpanelV2 2025-12-10 13:38:41 +01:00
Manuel fefce15b1a Merge branch 'master' into crowpanelV2 2025-12-05 10:59:34 +01:00
Manuel 50ea7ace98 Merge branch 'master' into crowpanelV2 2025-10-11 23:32:09 +02:00
Manuel 96f371eafd Merge branch 'master' into crowpanelV2 2025-10-07 14:22:46 +02:00
Manuel dbc52c7576 update device-ui commit 2025-10-07 14:21:55 +02:00
Manuel 232e0412ff cosmetic print when STC8H1K28 is found 2025-10-07 14:19:45 +02:00
Manuel b94659ff6a Update device-ui commit reference 2025-10-05 22:32:08 +02:00
Manuel 059e17b3e2 Update device-ui commit reference 2025-10-05 11:12:25 +02:00
Manuel b09f6db9e2 Merge branch 'master' into crowpanelV2 2025-10-05 11:00:02 +02:00
Manuel 64706153e0 Merge branch 'master' into crowpanelV2 2025-10-02 16:35:36 +02:00
Manuel f94043e4f2 Merge branch 'master' into crowpanelV2 2025-10-01 18:06:45 +02:00
Manuel bc82cd803c Merge branch 'master' into crowpanelV2 2025-09-29 10:49:38 +02:00
Manuel c15b6de138 Merge branch 'master' into crowpanelV2 2025-09-28 00:30:03 +02:00
Manuel fbf32b93b6 fix lora radio detection 2025-09-22 22:23:03 +02:00
Manuel b7289f7f2d Merge branch 'master' into crowpanelV2 2025-09-22 10:40:10 +02:00
Manuel d3d932ccee Merge branch 'master' into crowpanelV2 2025-09-21 14:11:29 +02:00
Manuel 3a267b440d Merge branch 'master' into crowpanelV2 2025-09-21 09:39:49 +02:00
Manuel b1821bfd4f CrowPanel Advanced V2 support 2025-09-18 16:00:49 +02:00
254 changed files with 3829 additions and 10627 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
+25 -39
View File
@@ -4,14 +4,9 @@ on:
workflow_dispatch:
inputs:
# trunk-ignore(checkov/CKV_GHA_7)
target:
type: string
required: false
description: Choose the target board, e.g. nrf52_promicro_diy_tcxo. If blank, will find available targets.
arch:
type: choice
options:
- all
- esp32
- esp32s3
- esp32c3
@@ -20,18 +15,32 @@ on:
- rp2040
- rp2350
- stm32
description: Choose an arch to limit the search, or 'all' to search all architectures.
default: all
target:
type: string
required: false
description: Choose the target board, e.g. nrf52_promicro_diy_tcxo. If blank, will find available targets.
# find-target:
# type: boolean
# default: true
# description: 'Find the available targets'
permissions: read-all
jobs:
find-targets:
if: ${{ inputs.target == '' }}
strategy:
fail-fast: false
matrix:
arch:
- all
- esp32
- esp32s3
- esp32c3
- esp32c6
- nrf52840
- rp2040
- rp2350
- stm32
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v6
@@ -42,37 +51,14 @@ jobs:
- run: pip install -U platformio
- name: Generate matrix
id: jsonStep
env:
BUILDTARGET: ${{ inputs.target }}
MATRIXARCH: ${{ inputs.arch }}
run: |
TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}} --level extra)
if [ "$BUILDTARGET" = "" ]; then
echo "Name: $GITHUB_REF_NAME" >> $GITHUB_STEP_SUMMARY
echo "Base: $GITHUB_BASE_REF" >> $GITHUB_STEP_SUMMARY
echo "Arch: $MATRIXARCH" >> $GITHUB_STEP_SUMMARY
echo "Ref: $GITHUB_REF" >> $GITHUB_STEP_SUMMARY
echo "## 🎯 The following target boards are available to build:" >> $GITHUB_STEP_SUMMARY
echo "| Platform | Board |" >> $GITHUB_STEP_SUMMARY
echo "| -------- | ----- |" >> $GITHUB_STEP_SUMMARY
echo $TARGETS | jq -r 'sort_by(.board) | sort_by(.platform) |.[] | "| " + .platform + " | " + .board + " |" ' >> $GITHUB_STEP_SUMMARY
else
echo "We build this one:" >> $GITHUB_STEP_SUMMARY
ARCH=$(echo "$TARGETS" | jq --arg BUILDTARGET "$BUILDTARGET" -r '.[] | select(.board==$BUILDTARGET) | .platform')
echo "| Platform | Board |" >> $GITHUB_STEP_SUMMARY
echo "| -------- | ----- |" >> $GITHUB_STEP_SUMMARY
echo "| $ARCH | "$BUILDTARGET" |" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
if [[ "$ARCH" == "" ]]; then
echo "## ❌ Error: Target "$BUILDTARGET" not found!" >> $GITHUB_STEP_SUMMARY
else
echo "## ✅ Target "$BUILDTARGET" found, proceeding to build." >> $GITHUB_STEP_SUMMARY
fi
echo "You may need to refresh this page to make the built firmware appear below." >> $GITHUB_STEP_SUMMARY
echo "arch=$ARCH" >> $GITHUB_OUTPUT
fi
outputs:
arch: ${{ steps.jsonStep.outputs.arch }}
echo "Name: $GITHUB_REF_NAME" >> $GITHUB_STEP_SUMMARY
echo "Base: $GITHUB_BASE_REF" >> $GITHUB_STEP_SUMMARY
echo "Arch: ${{matrix.arch}}" >> $GITHUB_STEP_SUMMARY
echo "Ref: $GITHUB_REF" >> $GITHUB_STEP_SUMMARY
echo "Targets:" >> $GITHUB_STEP_SUMMARY
echo $TARGETS | jq -r 'sort_by(.board) |.[] | "- " + .board' >> $GITHUB_STEP_SUMMARY
version:
if: ${{ inputs.target != '' }}
@@ -92,12 +78,12 @@ jobs:
build:
if: ${{ inputs.target != '' && inputs.arch != 'native' }}
needs: [version, find-targets]
needs: [version]
uses: ./.github/workflows/build_firmware.yml
with:
version: ${{ needs.version.outputs.long }}
pio_env: ${{ inputs.target }}
platform: ${{ needs.find-targets.outputs.arch }}
platform: ${{ inputs.arch }}
gather-artifacts:
permissions:
+1 -1
View File
@@ -5,7 +5,7 @@ on:
workflow_dispatch:
push:
branches:
- develop # Default branch, same as 'cron' above
- master
paths:
- debian/**
- "*.rpkg"
+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:
+4 -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,7 @@ 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' }}
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.511
- renovate@43.92.1
- 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,30 +0,0 @@
---
Lora:
## Ebyte E80-900M22S
## This is a bit experimental
##
##
Module: lr1121
gpiochip: 1 # subtract 32 from the gpio numbers
DIO3_TCXO_VOLTAGE: 1.8
CS: 16 #pin6 / GPIO48 1C0
IRQ: 23 #pin17 / GPIO55 1C7
Busy: 22 #pin16 / GPIO54 1C6
Reset: 25 #pin13 / GPIO57 1D1
spidev: spidev0.0 #pins are (CS=16, CLK=17, MOSI=18, MISO=19)
spiSpeed: 2000000
rfswitch_table:
pins: [DIO5, DIO6, DIO7]
MODE_STBY: [LOW, LOW, LOW]
MODE_RX: [LOW, HIGH, LOW]
MODE_TX: [HIGH, HIGH, LOW]
MODE_TX_HP: [HIGH, LOW, LOW]
MODE_TX_HF: [LOW, LOW, LOW]
MODE_GNSS: [LOW, LOW, HIGH]
MODE_WIFI: [LOW, LOW, LOW]
General:
MACAddressSource: eth0
@@ -1,46 +0,0 @@
---
Lora:
## Ebyte E80-900M22S
## This is a bit experimental
##
##
Module: lr1121
gpiochip: 1 # subtract 32 from the gpio numbers
DIO3_TCXO_VOLTAGE: 1.8
CS: 16 #pin6 / GPIO48 1C0
IRQ: 23 #pin17 / GPIO55 1C7
Busy: 22 #pin16 / GPIO54 1C6
Reset: 25 #pin13 / GPIO57 1D1
spidev: spidev0.0 #pins are (CS=16, CLK=17, MOSI=18, MISO=19)
spiSpeed: 2000000
rfswitch_table:
pins:
- DIO5
- DIO6
MODE_STBY:
- LOW
- LOW
MODE_RX:
- HIGH
- LOW
MODE_TX:
- HIGH
- HIGH
MODE_TX_HP:
- LOW
- HIGH
MODE_TX_HF:
- LOW
- LOW
MODE_GNSS:
- LOW
- LOW
MODE_WIFI:
- LOW
- LOW
General:
MACAddressSource: eth0
@@ -1,30 +0,0 @@
---
Lora:
## Ebyte E80-900M22S
## This is a bit experimental
##
##
Module: lr1121
gpiochip: 1 # subtract 32 from the gpio numbers
DIO3_TCXO_VOLTAGE: 1.8
CS: 16 #pin6 / GPIO48 1C0
IRQ: 23 #pin17 / GPIO55 1C7
Busy: 22 #pin16 / GPIO54 1C6
Reset: 25 #pin13 / GPIO57 1D1
spidev: spidev0.0 #pins are (CS=16, CLK=17, MOSI=18, MISO=19)
spiSpeed: 2000000
rfswitch_table:
pins: [DIO5, DIO6, DIO7]
MODE_STBY: [LOW, LOW, LOW]
MODE_RX: [LOW, LOW, LOW]
MODE_TX: [LOW, HIGH, LOW]
MODE_TX_HP: [HIGH, LOW, LOW]
# MODE_TX_HF: []
# MODE_GNSS: []
MODE_WIFI: [LOW, LOW, LOW]
General:
MACAddressSource: eth0
@@ -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
+1 -1
View File
@@ -43,7 +43,7 @@ for pio_env in pio_envs:
env = {
"ci": {"board": pio_env, "platform": env_platform},
"board_level": cfg.get(f"env:{pio_env}", "board_level", default=None),
"board_check": cfg.get(f"env:{pio_env}", "board_check", default="false").strip().lower() == "true",
"board_check": bool(cfg.get(f"env:{pio_env}", "board_check", default=False)),
}
all_envs.append(env)
+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>
-29
View File
@@ -1,29 +0,0 @@
{
"build": {
"arduino": {
"ldscript": "esp32_out.ld"
},
"core": "esp32",
"extra_flags": "-DARDUINO_T_Beam",
"f_cpu": "240000000L",
"f_flash": "40000000L",
"flash_mode": "dio",
"mcu": "esp32",
"variant": "tbeam"
},
"connectivity": ["wifi", "bluetooth", "can", "ethernet"],
"debug": {
"openocd_board": "esp-wroom-32.cfg"
},
"frameworks": ["arduino", "espidf"],
"name": "TTGO T-Beam (PSRAM Disabled)",
"upload": {
"flash_size": "4MB",
"maximum_ram_size": 1310720,
"maximum_size": 4194304,
"require_upload_port": true,
"speed": 460800
},
"url": "https://github.com/LilyGO/TTGO-T-Beam",
"vendor": "TTGO"
}
-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)
+99 -107
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
@@ -75,10 +77,10 @@ lib_deps =
https://github.com/meshtastic/TinyGPSPlus/archive/71a82db35f3b973440044c476d4bcdc673b104f4.zip
# renovate: datasource=git-refs depName=meshtastic-ArduinoThread packageName=https://github.com/meshtastic/ArduinoThread gitBranch=master
https://github.com/meshtastic/ArduinoThread/archive/b841b0415721f1341ea41cccfb4adccfaf951567.zip
# renovate: datasource=github-tags depName=Nanopb packageName=nanopb/nanopb
https://github.com/nanopb/nanopb/archive/refs/tags/nanopb-0.4.9.1.zip
# renovate: datasource=github-tags depName=ErriezCRC32 packageName=Erriez/ErriezCRC32
https://github.com/Erriez/ErriezCRC32/archive/refs/tags/1.0.1.zip
# renovate: datasource=custom.pio depName=Nanopb packageName=nanopb/library/Nanopb
nanopb/Nanopb@0.4.91
# renovate: datasource=custom.pio depName=ErriezCRC32 packageName=erriez/library/ErriezCRC32
erriez/ErriezCRC32@1.0.1
; Used for the code analysis in PIO Home / Inspect
check_tool = cppcheck
@@ -93,8 +95,8 @@ check_flags =
framework = arduino
lib_deps =
${env.lib_deps}
# renovate: datasource=github-tags depName=NonBlockingRTTTL packageName=end2endzone/NonBlockingRTTTL
https://github.com/end2endzone/NonBlockingRTTTL/archive/refs/tags/1.4.0.zip
# renovate: datasource=custom.pio depName=NonBlockingRTTTL packageName=end2endzone/library/NonBlockingRTTTL
end2endzone/NonBlockingRTTTL@1.4.0
build_unflags =
-std=c++11
-std=gnu++11
@@ -105,141 +107,131 @@ build_src_filter = ${env.build_src_filter} -<platform/portduino/> -<graphics/nic
; Common libs for communicating over TCP/IP networks such as MQTT
[networking_base]
lib_deps =
# renovate: datasource=github-tags depName=TBPubSubClient packageName=thingsboard/pubsubclient
https://github.com/thingsboard/pubsubclient/archive/refs/tags/v2.12.1.zip
# renovate: datasource=github-tags depName=NTPClient packageName=arduino-libraries/NTPClient
https://github.com/arduino-libraries/NTPClient/archive/refs/tags/3.2.1.zip
# renovate: datasource=custom.pio depName=TBPubSubClient packageName=thingsboard/library/TBPubSubClient
thingsboard/TBPubSubClient@2.12.1
# renovate: datasource=custom.pio depName=NTPClient packageName=arduino-libraries/library/NTPClient
arduino-libraries/NTPClient@3.2.1
; Extra TCP/IP networking libs for supported devices
[networking_extra]
lib_deps =
# renovate: datasource=github-tags depName=Syslog packageName=arcao/Syslog
https://github.com/arcao/Syslog/archive/refs/tags/v2.0.zip
# renovate: datasource=custom.pio depName=Syslog packageName=arcao/library/Syslog
arcao/Syslog@2.0.0
[radiolib_base]
lib_deps =
# renovate: datasource=github-tags depName=RadioLib packageName=jgromes/RadioLib
https://github.com/jgromes/RadioLib/archive/refs/tags/7.6.0.zip
# renovate: datasource=custom.pio depName=RadioLib packageName=jgromes/library/RadioLib
jgromes/RadioLib@7.6.0
[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/499f3a806d729e49ea5148a1633e9b64ae8c2d2e.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
-2
View File
@@ -4,8 +4,6 @@
":dependencyDashboard",
":semanticCommitTypeAll(chore)",
":ignoreModulesAndTests",
":noUnscheduledUpdates",
"schedule:daily",
"group:recommended",
"replacements:all",
"workarounds:all"
+2 -10
View File
@@ -98,6 +98,7 @@ Syslog &Syslog::logMask(uint8_t priMask)
void Syslog::enable()
{
this->_client->begin(this->_port);
this->_enabled = true;
}
@@ -165,21 +166,14 @@ inline bool Syslog::_sendLog(uint16_t pri, const char *appName, const char *mess
if ((pri & LOG_FACMASK) == 0)
pri = LOG_MAKEPRI(LOG_FAC(this->_priDefault), pri);
// W5100S: acquire UDP socket on-demand to avoid permanent socket consumption
if (!this->_client->begin(this->_port)) {
return false;
}
if (this->_server != NULL) {
result = this->_client->beginPacket(this->_server, this->_port);
} else {
result = this->_client->beginPacket(this->_ip, this->_port);
}
if (result != 1) {
this->_client->stop();
if (result != 1)
return false;
}
this->_client->print('<');
this->_client->print(pri);
@@ -199,8 +193,6 @@ inline bool Syslog::_sendLog(uint16_t pri, const char *appName, const char *mess
this->_client->print(message);
this->_client->endPacket();
this->_client->stop(); // W5100S: release UDP socket for other services
return true;
}
+1 -2
View File
@@ -4,8 +4,7 @@ const char *DisplayFormatters::getModemPresetDisplayName(meshtastic_Config_LoRaC
bool usePreset)
{
// If use_preset is false, always return "Custom" — callers such as RadioInterface and Channels
// rely on this being a stable literal for channel-name hashing and default-channel detection.
// If use_preset is false, always return "Custom"
if (!usePreset) {
return "Custom";
}
-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 -42
View File
@@ -35,27 +35,6 @@
#include "nrfx_power.h"
#endif
#if defined(ARCH_NRF52)
#include "Nrf52SaadcLock.h"
#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,20 +323,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
#ifdef ARCH_NRF52
concurrency::LockGuard saadcGuard(concurrency::nrf52SaadcLock);
#endif
#else // block for all other platforms
for (uint32_t i = 0; i < BATTERY_SENSE_SAMPLES; i++) {
raw += analogRead(BATTERY_PIN);
}
@@ -552,11 +522,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 +631,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 +641,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 +671,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
+6 -9
View File
@@ -78,11 +78,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
// Configuration
// -----------------------------------------------------------------------------
// Pre-hop drop handling (compile-time flag).
#ifndef MESHTASTIC_PREHOP_DROP
#define MESHTASTIC_PREHOP_DROP 0
#endif
/// Convert a preprocessor name into a quoted string
#define xstr(s) ystr(s)
#define ystr(s) #s
@@ -231,7 +226,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 +239,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
@@ -306,6 +301,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define TCA9535_ADDR 0x20
#define TCA9555_ADDR 0x26
// used for display brightness control
#define STC8H1K28_ADDR 0x30
// -----------------------------------------------------------------------------
// Touchscreen
// -----------------------------------------------------------------------------
@@ -515,7 +513,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define MESHTASTIC_EXCLUDE_REMOTEHARDWARE 1
#define MESHTASTIC_EXCLUDE_STOREFORWARD 1
#define MESHTASTIC_EXCLUDE_TEXTMESSAGE 1
#define MESHTASTIC_EXCLUDE_TRAFFIC_MANAGEMENT 1
#define MESHTASTIC_EXCLUDE_ATAK 1
#define MESHTASTIC_EXCLUDE_CANNEDMESSAGES 1
#define MESHTASTIC_EXCLUDE_NEIGHBORINFO 1
+5 -2
View File
@@ -31,6 +31,9 @@ class ScanI2C
INA3221,
MAX17048,
MCP9808,
SHT31,
SHT4X,
SHTC3,
LPS22HB,
QMC6310U,
QMC6310N,
@@ -60,6 +63,7 @@ class ScanI2C
NAU7802,
FT6336U,
STK8BAXX,
STC8H1K28,
ICM20948,
SCD4X,
MAX30102,
@@ -91,8 +95,7 @@ class ScanI2C
SFA30,
CW2015,
SCD30,
ADS1115,
SHTXX
ADS1115
} DeviceType;
// typedef uint8_t DeviceAddress;
+14 -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: \
@@ -346,6 +304,8 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
#endif
#ifdef HAS_LP5562
SCAN_SIMPLE_CASE(LP5562_ADDR, LP5562, "LP5562", (uint8_t)addr.address);
#else
SCAN_SIMPLE_CASE(STC8H1K28_ADDR, LP5562, "STC8H1K28", (uint8_t)addr.address);
#endif
case XPOWERS_AXP192_AXP2101_ADDRESS:
// Do we have the axp2101/192 or the TCA8418
@@ -413,7 +373,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 +389,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 +450,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 +677,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 +692,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);
-8
View File
@@ -103,14 +103,6 @@ static int32_t gpsSwitch()
if (gps) {
int currentState = digitalRead(PIN_GPS_SWITCH);
// Respect explicit NOT_PRESENT mode and do not let the hardware switch re-enable GPS.
if (config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_NOT_PRESENT) {
gps->disable();
lastState = currentState;
firstrun = false;
return 1000;
}
// if the switch is set to zero, disable the GPS Thread
if (firstrun)
if (currentState == LOW)
+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);
+2 -11
View File
@@ -408,16 +408,7 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
display->drawString(nameX, getTextPositions(display)[line++], device_role);
// === Third Row: Radio Preset ===
// For custom modem settings show the actual parameters; for presets use the preset name.
char modeStr[16];
if (!config.lora.use_preset) {
snprintf(modeStr, sizeof(modeStr), "BW%u-SF%u-CR%u", static_cast<unsigned>(config.lora.bandwidth),
static_cast<unsigned>(config.lora.spread_factor), static_cast<unsigned>(config.lora.coding_rate));
} else {
strncpy(modeStr, DisplayFormatters::getModemPresetDisplayName(config.lora.modem_preset, false, true),
sizeof(modeStr) - 1);
modeStr[sizeof(modeStr) - 1] = '\0';
}
auto mode = DisplayFormatters::getModemPresetDisplayName(config.lora.modem_preset, false, config.lora.use_preset);
char regionradiopreset[25];
const char *region = myRegion ? myRegion->name : NULL;
@@ -425,7 +416,7 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
if (currentResolution == ScreenResolution::UltraLow) {
snprintf(regionradiopreset, sizeof(regionradiopreset), "%s", region);
} else {
snprintf(regionradiopreset, sizeof(regionradiopreset), "%s/%s", region, modeStr);
snprintf(regionradiopreset, sizeof(regionradiopreset), "%s/%s", region, mode);
}
}
textWidth = display->getStringWidth(regionradiopreset);
+25 -26
View File
@@ -18,7 +18,6 @@
#include "main.h"
#include "mesh/Default.h"
#include "mesh/MeshTypes.h"
#include "mesh/RadioLibInterface.h"
#include "modules/AdminModule.h"
#include "modules/CannedMessageModule.h"
#include "modules/ExternalNotificationModule.h"
@@ -26,7 +25,6 @@
#include "modules/TraceRouteModule.h"
#include <algorithm>
#include <array>
#include <cmath>
#include <functional>
#include <utility>
@@ -161,22 +159,31 @@ void menuHandler::LoraRegionPicker(uint32_t duration)
return;
}
// 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");
return;
}
config.lora.region = selectedRegion;
auto changes = SEGMENT_CONFIG;
// FIXME: This should be a method consolidated with the same logic in the admin message as well
// This is needed as we wait til picking the LoRa region to generate keys for the first time.
#if !(MESHTASTIC_EXCLUDE_PKI_KEYGEN || MESHTASTIC_EXCLUDE_PKI)
if (crypto) {
crypto->ensurePkiKeys(config.security, owner);
if (!owner.is_licensed) {
bool keygenSuccess = false;
if (config.security.private_key.size == 32) {
// public key is derived from private, so this will always have the same result.
if (crypto->regeneratePublicKey(config.security.public_key.bytes, config.security.private_key.bytes)) {
keygenSuccess = true;
}
} else {
LOG_INFO("Generate new PKI keys");
crypto->generateKeyPair(config.security.public_key.bytes, config.security.private_key.bytes);
keygenSuccess = true;
}
if (keygenSuccess) {
config.security.public_key.size = 32;
config.security.private_key.size = 32;
owner.public_key.size = 32;
memcpy(owner.public_key.bytes, config.security.public_key.bytes, 32);
}
}
#endif
config.lora.tx_enabled = true;
@@ -192,6 +199,7 @@ void menuHandler::LoraRegionPicker(uint32_t duration)
}
service->reloadConfig(changes);
rebootAtMsec = (millis() + DEFAULT_REBOOT_SECONDS * 1000);
});
bannerOptions.durationMs = duration;
@@ -257,24 +265,13 @@ void menuHandler::FrequencySlotPicker()
optionsEnumArray[options++] = 0;
// Calculate number of channels (copied from RadioInterface::applyModemConfig())
meshtastic_Config_LoRaConfig &loraConfig = config.lora;
double bw = loraConfig.use_preset ? modemPresetToBwKHz(loraConfig.modem_preset, myRegion->wideLora)
: bwCodeToKHz(loraConfig.bandwidth);
uint32_t numChannels = 0;
if (myRegion) {
// Match RadioInterface::applyModemConfig(): include padding, add spacing in numerator, and use round()
const double spacing = myRegion->profile->spacing;
const double padding = myRegion->profile->padding;
const double channelBandwidthMHz = bw / 1000.0;
const double numerator = (myRegion->freqEnd - myRegion->freqStart) + spacing;
const double denominator = spacing + (padding * 2) + channelBandwidthMHz;
if (denominator > 0.0) {
numChannels = static_cast<uint32_t>(round(numerator / denominator));
} else {
LOG_WARN("Invalid region configuration: non-positive channel spacing/width");
}
numChannels = (uint32_t)floor((myRegion->freqEnd - myRegion->freqStart) / (myRegion->spacing + (bw / 1000.0)));
} else {
LOG_WARN("Region not set, cannot calculate number of channels");
return;
@@ -310,6 +307,7 @@ void menuHandler::FrequencySlotPicker()
config.lora.channel_num = selected;
service->reloadConfig(SEGMENT_CONFIG);
rebootAtMsec = (millis() + DEFAULT_REBOOT_SECONDS * 1000);
};
screen->showOverlayBanner(bannerOptions);
@@ -348,6 +346,7 @@ void menuHandler::radioPresetPicker()
config.lora.channel_num = 0; // Reset to default channel for the preset
config.lora.override_frequency = 0; // Clear any custom frequency
service->reloadConfig(SEGMENT_CONFIG);
rebootAtMsec = (millis() + DEFAULT_REBOOT_SECONDS * 1000);
});
screen->showOverlayBanner(bannerOptions);
+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);
@@ -177,8 +177,24 @@ static void applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode region)
auto changes = SEGMENT_CONFIG;
#if !(MESHTASTIC_EXCLUDE_PKI_KEYGEN || MESHTASTIC_EXCLUDE_PKI)
if (crypto) {
crypto->ensurePkiKeys(config.security, owner);
if (!owner.is_licensed) {
bool keygenSuccess = false;
if (config.security.private_key.size == 32) {
if (crypto->regeneratePublicKey(config.security.public_key.bytes, config.security.private_key.bytes)) {
keygenSuccess = true;
}
} else {
crypto->generateKeyPair(config.security.public_key.bytes, config.security.private_key.bytes);
keygenSuccess = true;
}
if (keygenSuccess) {
config.security.public_key.size = 32;
config.security.private_key.size = 32;
owner.public_key.size = 32;
memcpy(owner.public_key.bytes, config.security.public_key.bytes, 32);
}
}
#endif
+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;
-37
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);
}
@@ -71,33 +61,6 @@ bool CryptoEngine::regeneratePublicKey(uint8_t *pubKey, uint8_t *privKey)
}
return true;
}
bool CryptoEngine::ensurePkiKeys(meshtastic_Config_SecurityConfig &security, meshtastic_User &user)
{
if (user.is_licensed) {
return false;
}
bool keygenSuccess = false;
if (security.private_key.size == 32) {
if (regeneratePublicKey(security.public_key.bytes, security.private_key.bytes)) {
keygenSuccess = true;
}
} else {
LOG_INFO("Generate new PKI keys");
generateKeyPair(security.public_key.bytes, security.private_key.bytes);
keygenSuccess = true;
}
if (keygenSuccess) {
security.public_key.size = 32;
security.private_key.size = 32;
user.public_key.size = 32;
memcpy(user.public_key.bytes, security.public_key.bytes, 32);
}
return keygenSuccess;
}
#endif
/**
-1
View File
@@ -36,7 +36,6 @@ class CryptoEngine
#if !(MESHTASTIC_EXCLUDE_PKI_KEYGEN)
virtual void generateKeyPair(uint8_t *pubKey, uint8_t *privKey);
virtual bool regeneratePublicKey(uint8_t *pubKey, uint8_t *privKey);
virtual bool ensurePkiKeys(meshtastic_Config_SecurityConfig &security, meshtastic_User &user);
#endif
void setDHPrivateKey(uint8_t *_private_key);
-5
View File
@@ -30,11 +30,6 @@
#define min_node_info_broadcast_secs 60 * 60 // No regular broadcasts of more than once an hour
#define min_neighbor_info_broadcast_secs 4 * 60 * 60
#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
#ifdef USERPREFS_RINGTONE_NAG_SECS
#define default_ringtone_nag_secs USERPREFS_RINGTONE_NAG_SECS
#else
-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 -15
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,14 +184,14 @@ 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);
// Apply RX gain mode — valid in STDBY, matches resetAGC() pattern
err = lora.setRxBoostedGainMode(config.lora.sx126x_rx_boosted_gain);
if (err != RADIOLIB_ERR_NONE)
LOG_WARN("LR11x0 setRxBoostedGainMode %s%d", radioLibErr, err);
startReceive(); // restart receiving
return RADIOLIB_ERR_NONE;
+5 -44
View File
@@ -4,54 +4,19 @@
#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 =
static_cast<meshtastic_Config_LoRaConfig_ModemPreset>(0xFF);
// Region profile: bundles the preset list with regulatory parameters shared across regions
struct RegionProfile {
const meshtastic_Config_LoRaConfig_ModemPreset *presets; // sentinel-terminated; first entry is the default
float spacing; // gaps between radio channels
float padding; // padding at each side of the "operating channel"
bool audioPermitted;
bool licensedOnly; // a region profile for licensed operators only
int8_t textThrottle; // throttle for text - future expansion
int8_t positionThrottle; // throttle for location data - future expansion
int8_t telemetryThrottle; // throttle for telemetry - future expansion
uint8_t overrideSlot; // a per-region override slot for if we need to fix it in place
};
extern const RegionProfile PROFILE_STD;
extern const RegionProfile PROFILE_EU868;
extern const RegionProfile PROFILE_UNDEF;
// extern const RegionProfile PROFILE_LITE;
// extern const RegionProfile PROFILE_NARROW;
// extern const RegionProfile PROFILE_HAM;
// Map from old region names to new region enums
struct RegionInfo {
meshtastic_Config_LoRaConfig_RegionCode code;
float freqStart;
float freqEnd;
float dutyCycle; // modified by getEffectiveDutyCycle
float dutyCycle;
float spacing;
uint8_t powerLimit; // Or zero for not set
bool audioPermitted;
bool freqSwitching;
bool wideLora;
const RegionProfile *profile;
const char *name; // EU433 etc
// Preset accessors (delegate through profile)
meshtastic_Config_LoRaConfig_ModemPreset getDefaultPreset() const { return profile->presets[0]; }
const meshtastic_Config_LoRaConfig_ModemPreset *getAvailablePresets() const { return profile->presets; }
size_t getNumPresets() const
{
size_t n = 0;
while (profile->presets[n] != MODEM_PRESET_END)
n++;
return n;
}
};
extern const RegionInfo regions[];
@@ -60,7 +25,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 +37,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;
+3 -19
View File
@@ -4,9 +4,6 @@
#if !MESHTASTIC_EXCLUDE_TRACEROUTE
#include "modules/TraceRouteModule.h"
#endif
#if HAS_TRAFFIC_MANAGEMENT
#include "modules/TrafficManagementModule.h"
#endif
#include "NodeDB.h"
NextHopRouter::NextHopRouter() {}
@@ -129,28 +126,15 @@ void NextHopRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtast
/* Check if we should be rebroadcasting this packet if so, do so. */
bool NextHopRouter::perhapsRebroadcast(const meshtastic_MeshPacket *p)
{
// Check if traffic management wants to exhaust this packet's hops
bool exhaustHops = false;
#if HAS_TRAFFIC_MANAGEMENT
if (trafficManagementModule && trafficManagementModule->shouldExhaustHops(*p)) {
exhaustHops = true;
}
#endif
// Allow rebroadcast if hop_limit > 0 OR if we're exhausting hops (which sets hop_limit = 0 but still needs one relay)
if (!isToUs(p) && !isFromUs(p) && (p->hop_limit > 0 || exhaustHops)) {
if (!isToUs(p) && !isFromUs(p) && p->hop_limit > 0) {
if (p->id != 0) {
if (isRebroadcaster()) {
if (p->next_hop == NO_NEXT_HOP_PREFERENCE || p->next_hop == nodeDB->getLastByteOfNodeNum(getNodeNum())) {
meshtastic_MeshPacket *tosend = packetPool.allocCopy(*p); // keep a copy because we will be sending it
LOG_INFO("Rebroadcast received message coming from %x", p->relay_node);
// If exhausting hops, force hop_limit = 0 regardless of other logic
if (exhaustHops) {
tosend->hop_limit = 0;
LOG_INFO("Traffic management: exhausting hops for 0x%08x, setting hop_limit=0", getFrom(p));
} else if (shouldDecrementHopLimit(p)) {
// Use shared logic to determine if hop_limit should be decremented
// Use shared logic to determine if hop_limit should be decremented
if (shouldDecrementHopLimit(p)) {
tosend->hop_limit--; // bump down the hop count
} else {
LOG_INFO("favorite-ROUTER/CLIENT_BASE-to-ROUTER/CLIENT_BASE rebroadcast: preserving hop_limit");
+2 -37
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)
@@ -1299,7 +1299,7 @@ void NodeDB::loadFromDisk()
// Coerce LoRa config fields derived from presets while bootstrapping.
// Some clients/UI components display bandwidth/spread_factor directly from config even in preset mode.
if (config.has_lora && config.lora.use_preset) {
RadioInterface::clampConfigLora(config.lora);
RadioInterface::bootstrapLoRaConfigFromPreset(config.lora);
}
#if defined(USERPREFS_LORA_TX_DISABLED) && USERPREFS_LORA_TX_DISABLED
@@ -1650,25 +1650,6 @@ uint32_t sinceReceived(const meshtastic_MeshPacket *p)
return delta;
}
HopStartStatus classifyHopStart(const meshtastic_MeshPacket &p)
{
// Guard against invalid values.
if (p.hop_start < p.hop_limit)
return HopStartStatus::INVALID;
if (p.hop_start == 0) {
// Firmware prior to 2.3.0 (585805c) lacked a hop_start field. Firmware version 2.5.0 (bf34329) introduced a
// bitfield that is always present. Use the presence of the bitfield to determine if the origin's firmware
// version is guaranteed to have hop_start populated. Note that this can only be done for decoded packets as
// the bitfield is encrypted under the channel encryption key.
if (p.which_payload_variant == meshtastic_MeshPacket_decoded_tag && p.decoded.has_bitfield)
return HopStartStatus::VALID;
return HopStartStatus::MISSING_OR_UNKNOWN;
}
return HopStartStatus::VALID;
}
int8_t getHopsAway(const meshtastic_MeshPacket &p, int8_t defaultIfUnknown)
{
// Firmware prior to 2.3.0 (585805c) lacked a hop_start field. Firmware version 2.5.0 (bf34329) introduced a
@@ -1706,22 +1687,6 @@ size_t NodeDB::getNumOnlineMeshNodes(bool localOnly)
#include "MeshModule.h"
#include "Throttle.h"
static constexpr uint32_t HOPSTART_DROP_LOG_INTERVAL_MS = 15000;
void logHopStartDrop(const meshtastic_MeshPacket &p, const char *context)
{
static uint32_t lastLogMs = 0;
if (Throttle::isWithinTimespanMs(lastLogMs, HOPSTART_DROP_LOG_INTERVAL_MS)) {
return;
}
lastLogMs = millis();
const bool decoded = (p.which_payload_variant == meshtastic_MeshPacket_decoded_tag);
const bool hasBitfield = decoded && p.decoded.has_bitfield;
LOG_DEBUG(
"Drop packet (%s): hop_start invalid/missing (from=0x%x id=%u hop_start=%u hop_limit=%u decoded=%d has_bitfield=%d)",
context ? context : "unknown", p.from, p.id, p.hop_start, p.hop_limit, decoded, hasBitfield);
}
/** Update position info for this node based on received position data
*/
void NodeDB::updatePosition(uint32_t nodeId, const meshtastic_Position &p, RxSource src)
-21
View File
@@ -114,27 +114,6 @@ uint32_t sinceReceived(const meshtastic_MeshPacket *p);
/// Returns defaultIfUnknown if the number of hops couldn't be determined.
int8_t getHopsAway(const meshtastic_MeshPacket &p, int8_t defaultIfUnknown = -1);
enum class HopStartStatus : uint8_t { VALID = 0, MISSING_OR_UNKNOWN, INVALID };
/// Classify hop_start validity for forwarding decisions.
HopStartStatus classifyHopStart(const meshtastic_MeshPacket &p);
inline bool shouldDropPacketForPreHop(const meshtastic_MeshPacket &p)
{
#if !MESHTASTIC_PREHOP_DROP
(void)p;
return false;
#else
if (isFromUs(&p)) {
return false; // local-originated packets should never be dropped by pre-hop drop policy
}
return classifyHopStart(p) != HopStartStatus::VALID;
#endif
}
/// Rate-limited debug log when hop_start is invalid/missing and packet is dropped.
void logHopStartDrop(const meshtastic_MeshPacket &p, const char *context);
enum LoadFileResult {
// Successfully opened the file
LOAD_SUCCESS = 1,
-5
View File
@@ -449,11 +449,6 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
fromRadioScratch.moduleConfig.which_payload_variant = meshtastic_ModuleConfig_paxcounter_tag;
fromRadioScratch.moduleConfig.payload_variant.paxcounter = moduleConfig.paxcounter;
break;
case meshtastic_ModuleConfig_traffic_management_tag:
LOG_DEBUG("Send module config: traffic management");
fromRadioScratch.moduleConfig.which_payload_variant = meshtastic_ModuleConfig_traffic_management_tag;
fromRadioScratch.moduleConfig.payload_variant.traffic_management = moduleConfig.traffic_management;
break;
default:
LOG_ERROR("Unknown module config type %d", config_state);
}
+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);
+132 -314
View File
@@ -21,7 +21,6 @@
#include <assert.h>
#include <pb_decode.h>
#include <pb_encode.h>
#include <string.h>
#ifdef ARCH_PORTDUINO
#include "platform/portduino/PortduinoGlue.h"
@@ -33,32 +32,10 @@
#include "STM32WLE5JCInterface.h"
#endif
static const meshtastic_Config_LoRaConfig_ModemPreset PRESETS_STD[] = {
meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST, meshtastic_Config_LoRaConfig_ModemPreset_LONG_SLOW,
meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_SLOW, meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_FAST,
meshtastic_Config_LoRaConfig_ModemPreset_SHORT_SLOW, meshtastic_Config_LoRaConfig_ModemPreset_SHORT_FAST,
meshtastic_Config_LoRaConfig_ModemPreset_LONG_MODERATE, meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO,
meshtastic_Config_LoRaConfig_ModemPreset_LONG_TURBO, MODEM_PRESET_END};
static const meshtastic_Config_LoRaConfig_ModemPreset PRESETS_EU_868[] = {
meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST, meshtastic_Config_LoRaConfig_ModemPreset_LONG_SLOW,
meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_SLOW, meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_FAST,
meshtastic_Config_LoRaConfig_ModemPreset_SHORT_SLOW, meshtastic_Config_LoRaConfig_ModemPreset_SHORT_FAST,
meshtastic_Config_LoRaConfig_ModemPreset_LONG_MODERATE, MODEM_PRESET_END};
static const meshtastic_Config_LoRaConfig_ModemPreset PRESETS_UNDEF[] = {meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST,
MODEM_PRESET_END};
// Region profiles: bundle preset list + regulatory parameters shared across regions
// presets, spacing, padding, audio, licensed, text throttle, position throttle, telemetry throttle, override slot
const RegionProfile PROFILE_STD = {PRESETS_STD, 0, 0, true, false, 0, 0, 0, 0};
const RegionProfile PROFILE_EU868 = {PRESETS_EU_868, 0, 0, false, false, 0, 0, 0, 0};
const RegionProfile PROFILE_UNDEF = {PRESETS_UNDEF, 0, 0, true, false, 0, 0, 0, 0};
#define RDEF(name, freq_start, freq_end, duty_cycle, power_limit, frequency_switching, wide_lora, profile_ptr) \
#define RDEF(name, freq_start, freq_end, duty_cycle, spacing, power_limit, audio_permitted, frequency_switching, wide_lora) \
{ \
meshtastic_Config_LoRaConfig_RegionCode_##name, freq_start, freq_end, duty_cycle, power_limit, frequency_switching, \
wide_lora, &profile_ptr, #name \
meshtastic_Config_LoRaConfig_RegionCode_##name, freq_start, freq_end, duty_cycle, spacing, power_limit, audio_permitted, \
frequency_switching, wide_lora, #name \
}
const RegionInfo regions[] = {
@@ -66,7 +43,7 @@ const RegionInfo regions[] = {
https://link.springer.com/content/pdf/bbm%3A978-1-4842-4357-2%2F1.pdf
https://www.thethingsnetwork.org/docs/lorawan/regional-parameters/
*/
RDEF(US, 902.0f, 928.0f, 100, 30, false, false, PROFILE_STD),
RDEF(US, 902.0f, 928.0f, 100, 0, 30, true, false, false),
/*
EN300220 ETSI V3.2.1 [Table B.1, Item H, p. 21]
@@ -74,7 +51,8 @@ const RegionInfo regions[] = {
https://www.etsi.org/deliver/etsi_en/300200_300299/30022002/03.02.01_60/en_30022002v030201p.pdf
FIXME: https://github.com/meshtastic/firmware/issues/3371
*/
RDEF(EU_433, 433.0f, 434.0f, 10, 10, false, false, PROFILE_STD),
RDEF(EU_433, 433.0f, 434.0f, 10, 0, 10, true, false, false),
/*
https://www.thethingsnetwork.org/docs/lorawan/duty-cycle/
https://www.thethingsnetwork.org/docs/lorawan/regional-parameters/
@@ -89,33 +67,33 @@ const RegionInfo regions[] = {
AFA) to avoid a duty cycle. (Please refer to line P page 22 of this document.)
https://www.etsi.org/deliver/etsi_en/300200_300299/30022002/03.01.01_60/en_30022002v030101p.pdf
*/
RDEF(EU_868, 869.4f, 869.65f, 10, 27, false, false, PROFILE_EU868),
RDEF(EU_868, 869.4f, 869.65f, 10, 0, 27, false, false, false),
/*
https://lora-alliance.org/wp-content/uploads/2020/11/lorawan_regional_parameters_v1.0.3reva_0.pdf
*/
RDEF(CN, 470.0f, 510.0f, 100, 19, false, false, PROFILE_STD),
RDEF(CN, 470.0f, 510.0f, 100, 0, 19, true, false, false),
/*
https://lora-alliance.org/wp-content/uploads/2020/11/lorawan_regional_parameters_v1.0.3reva_0.pdf
https://www.arib.or.jp/english/html/overview/doc/5-STD-T108v1_5-E1.pdf
https://qiita.com/ammo0613/items/d952154f1195b64dc29f
*/
RDEF(JP, 920.5f, 923.5f, 100, 13, false, false, PROFILE_STD),
RDEF(JP, 920.5f, 923.5f, 100, 0, 13, true, false, false),
/*
https://www.iot.org.au/wp/wp-content/uploads/2016/12/IoTSpectrumFactSheet.pdf
https://iotalliance.org.nz/wp-content/uploads/sites/4/2019/05/IoT-Spectrum-in-NZ-Briefing-Paper.pdf
Also used in Brazil.
*/
RDEF(ANZ, 915.0f, 928.0f, 100, 30, false, false, PROFILE_STD),
RDEF(ANZ, 915.0f, 928.0f, 100, 0, 30, true, false, false),
/*
433.05 - 434.79 MHz, 25mW EIRP max, No duty cycle restrictions
AU Low Interference Potential https://www.acma.gov.au/licences/low-interference-potential-devices-lipd-class-licence
NZ General User Radio Licence for Short Range Devices https://gazette.govt.nz/notice/id/2022-go3100
*/
RDEF(ANZ_433, 433.05f, 434.79f, 100, 14, false, false, PROFILE_STD),
RDEF(ANZ_433, 433.05f, 434.79f, 100, 0, 14, true, false, false),
/*
https://digital.gov.ru/uploaded/files/prilozhenie-12-k-reshenyu-gkrch-18-46-03-1.pdf
@@ -123,13 +101,13 @@ const RegionInfo regions[] = {
Note:
- We do LBT, so 100% is allowed.
*/
RDEF(RU, 868.7f, 869.2f, 100, 20, false, false, PROFILE_STD),
RDEF(RU, 868.7f, 869.2f, 100, 0, 20, true, false, false),
/*
https://www.law.go.kr/LSW/admRulLsInfoP.do?admRulId=53943&efYd=0
https://resources.lora-alliance.org/technical-specifications/rp002-1-0-4-regional-parameters
*/
RDEF(KR, 920.0f, 923.0f, 100, 23, false, false, PROFILE_STD),
RDEF(KR, 920.0f, 923.0f, 100, 0, 23, true, false, false),
/*
Taiwan, 920-925Mhz, limited to 0.5W indoor or coastal, 1.0W outdoor.
@@ -137,40 +115,42 @@ const RegionInfo regions[] = {
https://www.ncc.gov.tw/english/files/23070/102_5190_230703_1_doc_C.PDF
https://gazette.nat.gov.tw/egFront/e_detail.do?metaid=147283
*/
RDEF(TW, 920.0f, 925.0f, 100, 27, false, false, PROFILE_STD),
RDEF(TW, 920.0f, 925.0f, 100, 0, 27, true, false, false),
/*
https://lora-alliance.org/wp-content/uploads/2020/11/lorawan_regional_parameters_v1.0.3reva_0.pdf
*/
RDEF(IN, 865.0f, 867.0f, 100, 30, false, false, PROFILE_STD),
RDEF(IN, 865.0f, 867.0f, 100, 0, 30, true, false, false),
/*
https://rrf.rsm.govt.nz/smart-web/smart/page/-smart/domain/licence/LicenceSummary.wdk?id=219752
https://iotalliance.org.nz/wp-content/uploads/sites/4/2019/05/IoT-Spectrum-in-NZ-Briefing-Paper.pdf
*/
RDEF(NZ_865, 864.0f, 868.0f, 100, 36, false, false, PROFILE_STD),
RDEF(NZ_865, 864.0f, 868.0f, 100, 0, 36, true, false, false),
/*
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, 0, 16, true, false, false),
/*
433,05-434,7 Mhz 10 mW
https://nkrzi.gov.ua/images/upload/256/5810/PDF_UUZ_19_01_2016.pdf
*/
RDEF(UA_433, 433.0f, 434.7f, 10, 0, 10, true, false, false),
/*
868,0-868,6 Mhz 25 mW
https://nkrzi.gov.ua/images/upload/256/5810/PDF_UUZ_19_01_2016.pdf
*/
RDEF(UA_433, 433.0f, 434.7f, 10, 10, false, false, PROFILE_STD),
RDEF(UA_868, 868.0f, 868.6f, 1, 14, false, false, PROFILE_STD),
RDEF(UA_868, 868.0f, 868.6f, 1, 0, 14, true, false, false),
/*
Malaysia
433 - 435 MHz at 100mW, no restrictions.
https://www.mcmc.gov.my/skmmgovmy/media/General/pdf/Short-Range-Devices-Specification.pdf
*/
RDEF(MY_433, 433.0f, 435.0f, 100, 20, false, false, PROFILE_STD),
RDEF(MY_433, 433.0f, 435.0f, 100, 0, 20, true, false, false),
/*
Malaysia
@@ -179,14 +159,14 @@ const RegionInfo regions[] = {
Frequency hopping is used for 919 - 923 MHz.
https://www.mcmc.gov.my/skmmgovmy/media/General/pdf/Short-Range-Devices-Specification.pdf
*/
RDEF(MY_919, 919.0f, 924.0f, 100, 27, true, false, PROFILE_STD),
RDEF(MY_919, 919.0f, 924.0f, 100, 0, 27, true, true, false),
/*
Singapore
SG_923 Band 30d: 917 - 925 MHz at 100mW, no restrictions.
https://www.imda.gov.sg/-/media/imda/files/regulation-licensing-and-consultations/ict-standards/telecommunication-standards/radio-comms/imdatssrd.pdf
*/
RDEF(SG_923, 917.0f, 925.0f, 100, 20, false, false, PROFILE_STD),
RDEF(SG_923, 917.0f, 925.0f, 100, 0, 20, true, false, false),
/*
Philippines
@@ -196,9 +176,8 @@ const RegionInfo regions[] = {
https://github.com/meshtastic/firmware/issues/4948#issuecomment-2394926135
*/
RDEF(PH_433, 433.0f, 434.7f, 100, 10, false, false, PROFILE_STD),
RDEF(PH_868, 868.0f, 869.4f, 100, 14, false, false, PROFILE_STD),
RDEF(PH_915, 915.0f, 918.0f, 100, 24, false, false, PROFILE_STD),
RDEF(PH_433, 433.0f, 434.7f, 100, 0, 10, true, false, false), RDEF(PH_868, 868.0f, 869.4f, 100, 0, 14, true, false, false),
RDEF(PH_915, 915.0f, 918.0f, 100, 0, 24, true, false, false),
/*
Kazakhstan
@@ -206,38 +185,37 @@ const RegionInfo regions[] = {
863 - 868 MHz <25 mW EIRP, 500kHz channels allowed, must not be used at airfields
https://github.com/meshtastic/firmware/issues/7204
*/
RDEF(KZ_433, 433.075f, 434.775f, 100, 10, false, false, PROFILE_STD),
RDEF(KZ_863, 863.0f, 868.0f, 100, 30, false, false, PROFILE_STD),
RDEF(KZ_433, 433.075f, 434.775f, 100, 0, 10, true, false, false),
RDEF(KZ_863, 863.0f, 868.0f, 100, 0, 30, true, false, false),
/*
Nepal
865 MHz to 868 MHz frequency band for IoT (Internet of Things), M2M (Machine-to-Machine), and smart metering use,
specifically in non-cellular mode. https://www.nta.gov.np/uploads/contents/Radio-Frequency-Policy-2080-English.pdf
*/
RDEF(NP_865, 865.0f, 868.0f, 100, 30, false, false, PROFILE_STD),
RDEF(NP_865, 865.0f, 868.0f, 100, 0, 30, true, false, false),
/*
Brazil
902 - 907.5 MHz , 1W power limit, no duty cycle restrictions
https://github.com/meshtastic/firmware/issues/3741
*/
RDEF(BR_902, 902.0f, 907.5f, 100, 30, false, false, PROFILE_STD),
RDEF(BR_902, 902.0f, 907.5f, 100, 0, 30, true, false, false),
/*
2.4 GHZ WLAN Band equivalent. Only for SX128x chips.
*/
RDEF(LORA_24, 2400.0f, 2483.5f, 100, 10, false, true, PROFILE_STD),
RDEF(LORA_24, 2400.0f, 2483.5f, 100, 0, 10, true, false, true),
/*
This needs to be last. Same as US.
*/
RDEF(UNSET, 902.0f, 928.0f, 100, 30, false, false, PROFILE_UNDEF)
RDEF(UNSET, 902.0f, 928.0f, 100, 0, 30, true, false, false)
};
const RegionInfo *myRegion;
bool RadioInterface::uses_default_frequency_slot = true;
bool RadioInterface::uses_custom_channel_name = false;
static uint8_t bytes[MAX_LORA_PAYLOAD_LEN + 1];
@@ -523,14 +501,45 @@ void initRegion()
myRegion = r;
}
const RegionInfo *getRegion(meshtastic_Config_LoRaConfig_RegionCode code)
void RadioInterface::bootstrapLoRaConfigFromPreset(meshtastic_Config_LoRaConfig &loraConfig)
{
if (!loraConfig.use_preset) {
return;
}
// Find region info to determine whether "wide" LoRa is permitted (2.4 GHz uses wider bandwidth codes).
const RegionInfo *r = regions;
for (; r->code != meshtastic_Config_LoRaConfig_RegionCode_UNSET && r->code != code; r++)
for (; r->code != meshtastic_Config_LoRaConfig_RegionCode_UNSET && r->code != loraConfig.region; r++)
;
return r;
const bool regionWideLora = r->wideLora;
float bwKHz = 0;
uint8_t sf = 0;
uint8_t cr = 0;
modemPresetToParams(loraConfig.modem_preset, regionWideLora, bwKHz, sf, cr);
// If selected preset requests a bandwidth larger than the region span, fall back to LONG_FAST.
if (r->code != meshtastic_Config_LoRaConfig_RegionCode_UNSET && (r->freqEnd - r->freqStart) < (bwKHz / 1000.0f)) {
loraConfig.modem_preset = meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST;
modemPresetToParams(loraConfig.modem_preset, regionWideLora, bwKHz, sf, cr);
}
loraConfig.bandwidth = bwKHzToCode(bwKHz);
loraConfig.spread_factor = sf;
}
/**
* ## LoRaWAN for North America
LoRaWAN defines 64, 125 kHz channels from 902.3 to 914.9 MHz increments.
The maximum output power for North America is +30 dBM.
The band is from 902 to 928 MHz. It mentions channel number and its respective channel frequency. All the 13 channels are
separated by 2.16 MHz with respect to the adjacent channels. Channel zero starts at 903.08 MHz center frequency.
*/
uint32_t RadioInterface::getPacketTime(const meshtastic_MeshPacket *p, bool received)
{
uint32_t pl = 0;
@@ -740,7 +749,7 @@ void RadioInterface::saveFreq(float freq)
}
/**
* Save our frequency slot (aka channel) for later reuse.
* Save our channel for later reuse.
*/
void RadioInterface::saveChannelNum(uint32_t channel_num)
{
@@ -763,304 +772,113 @@ uint32_t RadioInterface::getChannelNum()
return savedChannelNum;
}
/**
* Send an error-level client notification. Safe to call when service is null (e.g. in tests).
*/
static void sendErrorNotification(const char *msg)
{
if (!service)
return;
meshtastic_ClientNotification *cn = clientNotificationPool.allocZeroed();
if (!cn)
return;
cn->level = meshtastic_LogRecord_Level_ERROR;
snprintf(cn->message, sizeof(cn->message), "%s", msg);
service->sendClientNotification(cn);
}
/**
* Checks if a region is valid for the current settings.
* Returns false if not compatible.
*/
bool RadioInterface::validateConfigRegion(const meshtastic_Config_LoRaConfig &loraConfig)
{
const RegionInfo *newRegion = getRegion(loraConfig.region);
// If you are not licensed, you can't use ham regions.
if (newRegion->profile->licensedOnly && !devicestate.owner.is_licensed) {
char err_string[160];
snprintf(err_string, sizeof(err_string), "Region %s requires licensed mode", newRegion->name);
LOG_ERROR("%s", err_string);
RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING);
sendErrorNotification(err_string);
return false;
}
return true;
}
/**
* Internal helper: validate or clamp a LoRa config against its region.
* When clamp==false, returns false on first error (pure validation).
* When clamp==true, fixes invalid settings in-place and returns true.
*/
bool RadioInterface::checkOrClampConfigLora(meshtastic_Config_LoRaConfig &loraConfig, bool clamp)
{
char err_string[160];
float check_bw;
const RegionInfo *newRegion = getRegion(loraConfig.region);
const char *presetName = DisplayFormatters::getModemPresetDisplayName(loraConfig.modem_preset, false, loraConfig.use_preset);
// Check preset validity (only when use_preset is true)
if (loraConfig.use_preset) {
check_bw = modemPresetToBwKHz(loraConfig.modem_preset, newRegion->wideLora);
bool preset_valid = false;
for (size_t i = 0; i < newRegion->getNumPresets(); i++) {
if (loraConfig.modem_preset == newRegion->getAvailablePresets()[i]) {
preset_valid = true;
break;
}
}
if (!preset_valid) {
const char *defaultName = DisplayFormatters::getModemPresetDisplayName(newRegion->getDefaultPreset(), false, true);
if (clamp) {
snprintf(err_string, sizeof(err_string), "Preset %s invalid for %s, using %s", presetName, newRegion->name,
defaultName);
} else {
snprintf(err_string, sizeof(err_string), "Preset %s invalid for %s", presetName, newRegion->name);
}
LOG_ERROR("%s", err_string);
RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING);
sendErrorNotification(err_string);
if (clamp) {
loraConfig.modem_preset = newRegion->getDefaultPreset();
check_bw = modemPresetToBwKHz(loraConfig.modem_preset, newRegion->wideLora);
} else {
return false;
}
}
} else {
check_bw = bwCodeToKHz(loraConfig.bandwidth);
}
// Calculate width of slots (aka channels) based on bandwidth and any spacing or padding required by the region:
// spacing = gap between slots (0 for continuous spectrum) and at the beginning of the band
// padding = gap at the beginning and end of the slots (0 for no padding)
float freqSlotWidth = newRegion->profile->spacing + (newRegion->profile->padding * 2) + (check_bw / 1000); // in MHz
uint32_t numFreqSlots = round((newRegion->freqEnd - newRegion->freqStart + newRegion->profile->spacing) / freqSlotWidth);
// Check if the region supports the requested bandwidth
if ((newRegion->freqEnd - newRegion->freqStart) < freqSlotWidth) {
const float regionSpanKHz = (newRegion->freqEnd - newRegion->freqStart) * 1000.0f;
snprintf(err_string, sizeof(err_string), "%s span %.0fkHz < requested %.0fkHz", newRegion->name, regionSpanKHz, check_bw);
LOG_ERROR("%s", err_string);
RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING);
sendErrorNotification(err_string);
if (clamp) {
loraConfig.bandwidth = bwKHzToCode(modemPresetToBwKHz(newRegion->getDefaultPreset(), newRegion->wideLora));
check_bw = bwCodeToKHz(loraConfig.bandwidth);
// Recompute slot width and number of slots based on the new bandwidth
freqSlotWidth = newRegion->profile->spacing + (newRegion->profile->padding * 2) + (check_bw / 1000); // in MHz
numFreqSlots = round((newRegion->freqEnd - newRegion->freqStart + newRegion->profile->spacing) / freqSlotWidth);
} else {
return false;
}
}
const char *channelName = channels.getName(channels.getPrimaryIndex());
const char *presetNameDisplay =
DisplayFormatters::getModemPresetDisplayName(loraConfig.modem_preset, false, loraConfig.use_preset);
uint32_t channelNameHashSlot = hash(channelName) % numFreqSlots;
uint32_t presetNameHashSlot = hash(presetNameDisplay) % numFreqSlots;
if (loraConfig.override_frequency == 0) {
// Check if we use the default frequency slot
uses_default_frequency_slot =
(loraConfig.channel_num == 0) || // user choice unset, no frequency override, so use default
(newRegion->profile->overrideSlot != 0 &&
loraConfig.channel_num == newRegion->profile->overrideSlot) || // user setting matches override
((newRegion->profile->overrideSlot == 0) &&
((uint32_t)(loraConfig.channel_num - 1) == presetNameHashSlot)); // user setting matches preset hash, no override
// check if user setting different to preset name
uses_custom_channel_name = (strcmp(channelName, presetNameDisplay) != 0);
if (loraConfig.channel_num > numFreqSlots) {
snprintf(err_string, sizeof(err_string), "Channel number %u invalid for %s, max is %u", loraConfig.channel_num,
newRegion->name, numFreqSlots);
LOG_ERROR("%s", err_string);
RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING);
sendErrorNotification(err_string);
if (clamp) {
if (uses_custom_channel_name) { // clamp to channel name hash
loraConfig.channel_num =
channelNameHashSlot + 1; // channel_num is 1-based, but hash slot is 0-based, so add 1
} else if ((loraConfig.use_preset) && (newRegion->profile->overrideSlot != 0)) { // clamp to preset override slot
loraConfig.channel_num =
newRegion->profile->overrideSlot; // use the override slot specified by the region profile
uses_default_frequency_slot = true;
} else if (loraConfig.use_preset) { // clamp to preset slot
loraConfig.channel_num = presetNameHashSlot + 1; // channel_num is 1-based, but hash slot is 0-based, so add 1
uses_default_frequency_slot = true;
} else { // if not using preset, and no custom channel name, just clamp to default anyway
uses_default_frequency_slot = true;
};
} else {
return false;
}
} // end of channel number check
} else {
// if we have a frequency override, we ignore the channel number and just use the override frequency
snprintf(err_string, sizeof(err_string), "Frequency override in place, using %.3f", loraConfig.override_frequency);
}
return true;
}
bool RadioInterface::validateConfigLora(const meshtastic_Config_LoRaConfig &loraConfig)
{
auto copy = loraConfig;
return checkOrClampConfigLora(copy, false);
}
void RadioInterface::clampConfigLora(meshtastic_Config_LoRaConfig &loraConfig)
{
checkOrClampConfigLora(loraConfig, true);
}
/**
* Pull our channel settings etc... from protobufs to the dumb interface settings
* Note: this must be given only settings which have been validated or clamped!
*/
void RadioInterface::applyModemConfig()
{
// Set up default configuration
// No Sync Words in LORA mode
meshtastic_Config_LoRaConfig &loraConfig = config.lora;
const RegionInfo *newRegion = getRegion(loraConfig.region);
myRegion = newRegion;
if (loraConfig.use_preset) {
if (!validateConfigLora(loraConfig)) {
loraConfig.modem_preset = newRegion->getDefaultPreset();
}
uint8_t newcr;
modemPresetToParams(loraConfig.modem_preset, newRegion->wideLora, bw, sf, newcr);
// If custom CR is being used already, check if the new preset is higher
if (loraConfig.coding_rate >= 5 && loraConfig.coding_rate <= 8 && loraConfig.coding_rate < newcr) {
cr = newcr;
LOG_INFO("Default Coding Rate is higher than custom setting, using %u", cr);
}
// If the custom CR is higher than the preset, use it
else if (loraConfig.coding_rate >= 5 && loraConfig.coding_rate <= 8 && loraConfig.coding_rate > newcr) {
bool validConfig = false; // We need to check for a valid configuration
while (!validConfig) {
if (loraConfig.use_preset) {
modemPresetToParams(loraConfig.modem_preset, myRegion->wideLora, bw, sf, cr);
if (loraConfig.coding_rate >= 5 && loraConfig.coding_rate <= 8 && loraConfig.coding_rate != cr) {
cr = loraConfig.coding_rate;
LOG_INFO("Using custom Coding Rate %u", cr);
}
} else {
sf = loraConfig.spread_factor;
cr = loraConfig.coding_rate;
LOG_INFO("Using custom Coding Rate %u", cr);
} else {
cr = newcr;
bw = bwCodeToKHz(loraConfig.bandwidth);
}
} else { // if not using preset, then just use the custom settings
if (validateConfigLora(loraConfig)) {
if ((myRegion->freqEnd - myRegion->freqStart) < bw / 1000) {
const float regionSpanKHz = (myRegion->freqEnd - myRegion->freqStart) * 1000.0f;
const float requestedBwKHz = bw;
const bool isWideRequest = requestedBwKHz >= 499.5f; // treat as 500 kHz preset
const char *presetName =
DisplayFormatters::getModemPresetDisplayName(loraConfig.modem_preset, false, loraConfig.use_preset);
char err_string[160];
if (isWideRequest) {
snprintf(err_string, sizeof(err_string), "%s region too narrow for 500kHz preset (%s). Falling back to LongFast.",
myRegion->name, presetName);
} else {
snprintf(err_string, sizeof(err_string), "%s region span %.0fkHz < requested %.0fkHz. Falling back to LongFast.",
myRegion->name, regionSpanKHz, requestedBwKHz);
}
LOG_ERROR("%s", err_string);
RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING);
meshtastic_ClientNotification *cn = clientNotificationPool.allocZeroed();
cn->level = meshtastic_LogRecord_Level_ERROR;
snprintf(cn->message, sizeof(cn->message), "%s", err_string);
service->sendClientNotification(cn);
// Set to default modem preset
loraConfig.use_preset = true;
loraConfig.modem_preset = meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST;
} else {
LOG_WARN("Invalid LoRa config settings, cannot apply requested modem config - falling back to %s defaults",
newRegion->name);
clampConfigLora(loraConfig);
validConfig = true;
}
bw = bwCodeToKHz(loraConfig.bandwidth);
sf = loraConfig.spread_factor;
cr = loraConfig.coding_rate;
}
power = loraConfig.tx_power;
if ((power == 0) || ((power > newRegion->powerLimit) && !devicestate.owner.is_licensed))
power = newRegion->powerLimit;
if ((power == 0) || ((power > myRegion->powerLimit) && !devicestate.owner.is_licensed))
power = myRegion->powerLimit;
if (power == 0)
power = 17; // Default to this power level if we don't have a valid regional power limit (powerLimit of newRegion defaults
power = 17; // Default to this power level if we don't have a valid regional power limit (powerLimit of myRegion defaults
// to 0, currently no region has an actual power limit of 0 [dBm] so we can assume regions which have this
// variable set to 0 don't have a valid power limit)
// Set final tx_power back onto config
loraConfig.tx_power = (int8_t)power; // cppcheck-suppress assignmentAddressToInteger
uint32_t channel_num;
float freq;
// Calculate the number of channels
uint32_t numChannels = floor((myRegion->freqEnd - myRegion->freqStart) / (myRegion->spacing + (bw / 1000)));
// Calculate number of frequency slots (aka Channels):
// spacing = gap between channels (0 for continuous spectrum) and at the beginning of the band
// padding = gap at the beginning and end of the channel (0 for no padding)
float freqSlotWidth = newRegion->profile->spacing + (newRegion->profile->padding * 2) + (bw / 1000); // in MHz
uint32_t numFreqSlots = round((newRegion->freqEnd - newRegion->freqStart + newRegion->profile->spacing) / freqSlotWidth);
// If user has manually specified a channel num, then use that, otherwise generate one by hashing the name
const char *channelName = channels.getName(channels.getPrimaryIndex());
// channel_num is actually (channel_num - 1), since modulus (%) returns values from 0 to (numChannels - 1)
uint32_t channel_num = (loraConfig.channel_num ? loraConfig.channel_num - 1 : hash(channelName)) % numChannels;
// Calculate hash of channel name and preset name to pick a default frequency slot if user has not specified one.
// Note that channel_num is actually (channel_num - 1), i.e. zero-based, since modulus (%) returns values from 0 to
// (numFreqSlots - 1).
uint32_t presetNameHashSlot =
hash(DisplayFormatters::getModemPresetDisplayName(loraConfig.modem_preset, false, loraConfig.use_preset)) % numFreqSlots;
// Check if we use the default frequency slot
RadioInterface::uses_default_frequency_slot =
channel_num ==
hash(DisplayFormatters::getModemPresetDisplayName(config.lora.modem_preset, false, config.lora.use_preset)) % numChannels;
// Old frequency selection formula
// float freq = myRegion->freqStart + ((((myRegion->freqEnd - myRegion->freqStart) / numChannels) / 2) * channel_num);
// New frequency selection formula
float freq = myRegion->freqStart + (bw / 2000) + (channel_num * (bw / 1000));
// override if we have a verbatim frequency
if (loraConfig.override_frequency) {
freq = loraConfig.override_frequency;
channel_num = -1;
uses_default_frequency_slot = false;
} else {
// If user has not manually specified a frequency slot, or has not specified one that is different than the default or the
// override for the new region, then use the default or override. If the user has not specified one, but has specified a
// custom channel name, then use the hash of that channel name to pick a frequency slot. Note that channel_num is actually
// (channel_num - 1), i.e. zero-based, since modulus (%) returns values from 0 to (numFreqSlots - 1).
// NB: channel_num is also know as frequency slot but it's too late to fix now.
if (uses_default_frequency_slot) {
// if there's an override slot, use that
if (newRegion->profile->overrideSlot != 0) {
channel_num = newRegion->profile->overrideSlot - 1;
} else {
channel_num = presetNameHashSlot;
}
} else { // use the manually defined one
channel_num = loraConfig.channel_num - 1;
}
// Calculate frequency: freqStart is band edge, add half bandwidth (plus optional padding) to get middle of first channel
// subsequent channels are spaced by freqSlotWidth
freq = newRegion->freqStart + (bw / 2000) + newRegion->profile->padding + (channel_num * freqSlotWidth); // in MHz
}
saveChannelNum(channel_num);
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);
LOG_INFO("Radio freq=%.3f, config.lora.frequency_offset=%.3f", freq, loraConfig.frequency_offset);
LOG_INFO("Set radio: region=%s, name=%s, config=%u, ch=%d, power=%d", newRegion->name, channelName, loraConfig.modem_preset,
LOG_INFO("Set radio: region=%s, name=%s, config=%u, ch=%d, power=%d", myRegion->name, channelName, loraConfig.modem_preset,
channel_num, power);
LOG_INFO("newRegion->freqStart -> newRegion->freqEnd: %f -> %f (%f MHz)", newRegion->freqStart, newRegion->freqEnd,
newRegion->freqEnd - newRegion->freqStart);
LOG_INFO("numFreqSlots: %d x %.3fkHz", numFreqSlots, bw);
if (newRegion->profile->overrideSlot != 0) {
LOG_INFO("Using region override slot: %d", newRegion->profile->overrideSlot);
}
LOG_INFO("myRegion->freqStart -> myRegion->freqEnd: %f -> %f (%f MHz)", myRegion->freqStart, myRegion->freqEnd,
myRegion->freqEnd - myRegion->freqStart);
LOG_INFO("numChannels: %d x %.3fkHz", numChannels, bw);
LOG_INFO("channel_num: %d", channel_num + 1);
LOG_INFO("frequency: %f", getFreq());
LOG_INFO("Slot time: %u msec, preamble time: %u msec", slotTimeMsec, preambleTimeMsec);
} // end of applyModemConfig
}
/** Slottime is the time to detect a transmission has started, consisting of:
- CAD duration;
@@ -1174,4 +992,4 @@ size_t RadioInterface::beginSending(meshtastic_MeshPacket *p)
sendingPacket = p;
return p->encrypted.size + sizeof(PacketHeader);
}
}
+9 -28
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;
@@ -132,7 +127,7 @@ class RadioInterface
* Coerce LoRa config fields (bandwidth/spread_factor) derived from presets.
* This is used during early bootstrapping so UIs that display these fields directly remain consistent.
*/
// static void bootstrapLoRaConfigFromPreset(meshtastic_Config_LoRaConfig &loraConfig); // maybe superseded?
static void bootstrapLoRaConfigFromPreset(meshtastic_Config_LoRaConfig &loraConfig);
/**
* Return true if we think the board can go to sleep (i.e. our tx queue is empty, we are not sending or receiving)
@@ -239,20 +234,6 @@ class RadioInterface
// Whether we use the default frequency slot given our LoRa config (region and modem preset)
static bool uses_default_frequency_slot;
// Whether we have a custom channel name
static bool uses_custom_channel_name;
static bool checkOrClampConfigLora(meshtastic_Config_LoRaConfig &loraConfig, bool clamp);
// Check if a candidate region is compatible and valid.
static bool validateConfigRegion(const meshtastic_Config_LoRaConfig &loraConfig);
// Check if a candidate radio configuration is valid.
static bool validateConfigLora(const meshtastic_Config_LoRaConfig &loraConfig);
// Make a candidate radio configuration valid, even if it isn't.
static void clampConfigLora(meshtastic_Config_LoRaConfig &loraConfig);
protected:
int8_t power = 17; // Set by applyModemConfig()
+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 */

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