Compare commits

...
12 Commits
Author SHA1 Message Date
Thomas Göttgens 8a412e5436 Park unused LNA_CTRL_MCU net with pulldown on makerfabs_pucky 2026-05-20 21:22:05 +02:00
Thomas Göttgens 83c16d93a3 Enable LR11X0_DIO_AS_RF_SWITCH for makerfabs_pucky 2026-05-20 20:30:31 +02:00
Kongduino 449bb537c5 Update variant.h (#10405)
Added TX Power curve.
2026-05-19 08:34:54 +02:00
Jonathan Bennett 6238c32d77 Fixes 2026-05-06 11:53:34 -05:00
Jonathan Bennett 7cc404890f Initial support for Pucky 2026-04-16 11:39:48 -05:00
Ben Meadors 23f92c1cbd Add comprehensive guides for new module, sensor, and hardware variant development 2026-04-16 08:01:03 -05:00
renovate[bot] c1bee82baf Update platform-native digest to 71ed55b (#10165)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-14 21:07:15 -05:00
github-actions[bot]andthebentern 47e129f4bd Automated version bumps (#10159)
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
2026-04-14 13:13:45 -05:00
Ben MeadorsandCopilot 96dd647882 Fix heap blowout on TBeams (#10155)
* Fix heap blowout on TBeams

* Update src/graphics/draw/MessageRenderer.cpp

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Set MESSAGE_HISTORY_LIMIT to 10 for original ESP32 to optimize RAM usage

* Optimize message frame allocation to prevent excessive memory usage

* Refine message history limits for resource-constrained builds and cap cached lines to prevent heap overflow

* Update src/graphics/draw/MessageRenderer.cpp

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-13 14:50:51 -05:00
Thomas Göttgens e42ff3590c fix last cppcheck issue (#10154) 2026-04-13 15:48:30 +02:00
Ben Meadors 7527233130 Enhance release notes generation with commit range comparison 2026-04-13 06:43:11 -05:00
197226365b fix(native): implement BinarySemaphorePosix with proper pthread synchronization (#9895)
* fix(native): implement BinarySemaphorePosix with proper pthread synchronization

The BinarySemaphorePosix class (used on all Linux/portduino/native builds)
had stub implementations: give() was a no-op and take() just called
delay(msec) and returned false. This broke the cooperative thread scheduler
on native platforms — threads could not wake the main loop, radio RX
interrupts were missed, and telemetry never transmitted over the mesh.

Replace the stubs with a proper binary semaphore using pthread_mutex_t +
pthread_cond_t + bool signaled:

- take(msec): pthread_cond_timedwait with CLOCK_REALTIME timeout, consumes
  signal atomically (binary semaphore semantics)
- give(): sets signaled=true, signals condition variable
- giveFromISR(): delegates to give(), sets pxHigherPriorityTaskWoken

Tested on Raspberry Pi 3 Model B (ARM64, Debian Bookworm) with Adafruit
LoRa Radio Bonnet (SX1276). Before fix: no radio TX/RX, no telemetry on
mesh. After fix: bidirectional LoRa, MQTT gateway, telemetry all working.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* ARCH_PORTDUINO

* Refactor BinarySemaphorePosix header for ARCH_PORTDUINO

* Change preprocessor directive from ifndef to ifdef

* Gate new Semaphore code to Portduino and fix STM compilation

* Binary Semaphore Posix better error handling

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
Co-authored-by: Jonathan Bennett <jbennett@incomsystems.biz>
2026-04-13 06:31:38 -05:00
22 changed files with 1178 additions and 67 deletions
+164 -27
View File
@@ -4,11 +4,11 @@ This document provides context and guidelines for AI assistants working with the
## Project Overview ## 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. 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.
### Supported Hardware Platforms ### Supported Hardware Platforms
- **ESP32** (ESP32, ESP32-S3, ESP32-C3) - Most common platform - **ESP32** (ESP32, ESP32-S3, ESP32-C3, ESP32-C6) - Most common platform
- **nRF52** (nRF52840, nRF52833) - Low power Nordic chips - **nRF52** (nRF52840, nRF52833) - Low power Nordic chips
- **RP2040/RP2350** - Raspberry Pi Pico variants - **RP2040/RP2350** - Raspberry Pi Pico variants
- **STM32WL** - STM32 with integrated LoRa - **STM32WL** - STM32 with integrated LoRa
@@ -80,21 +80,46 @@ firmware/
│ │ ├── NodeDB.* # Node database management │ │ ├── NodeDB.* # Node database management
│ │ ├── Router.* # Packet routing │ │ ├── Router.* # Packet routing
│ │ ├── Channels.* # Channel management │ │ ├── Channels.* # Channel management
│ │ ├── CryptoEngine.* # AES-CCM encryption
│ │ ├── *Interface.* # Radio interface implementations │ │ ├── *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 │ │ └── generated/ # Protobuf generated code
│ ├── modules/ # Feature modules (Position, Telemetry, etc.) │ ├── modules/ # Feature modules (Position, Telemetry, etc.)
│ │ └── Telemetry/ # Telemetry subsystem
│ │ └── Sensor/ # 50+ I2C sensor drivers
│ ├── gps/ # GPS handling │ ├── gps/ # GPS handling
│ ├── graphics/ # Display drivers and UI │ ├── graphics/ # Display drivers and UI
│ ├── platform/ # Platform-specific code │ │ └── niche/ # Specialized UIs (InkHUD e-ink framework)
│ ├── input/ # Input device handling │ ├── platform/ # Platform-specific code (esp32, nrf52, rp2xx0, stm32wl, portduino)
│ └── concurrency/ # Threading utilities │ ├── 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
├── variants/ # Hardware variant definitions ├── variants/ # Hardware variant definitions
│ ├── esp32/ # ESP32 variants │ ├── esp32/ # ESP32 variants
│ ├── esp32s3/ # ESP32-S3 variants │ ├── esp32s3/ # ESP32-S3 variants
│ ├── nrf52/ # nRF52 variants │ ├── esp32c3/ # ESP32-C3 variants
│ └── rp2xxx/ # RP2040/RP2350 variants │ ├── esp32c6/ # ESP32-C6 variants
│ ├── nrf52840/ # nRF52 variants
│ ├── rp2040/ # RP2040/RP2350 variants
│ ├── stm32/ # STM32WL variants
│ └── native/ # Linux/Portduino variants
├── protobufs/ # Protocol buffer definitions ├── protobufs/ # Protocol buffer definitions
├── boards/ # Custom PlatformIO board definitions ├── boards/ # Custom PlatformIO board definitions
├── test/ # Unit tests (12 test suites)
└── bin/ # Build and utility scripts └── bin/ # Build and utility scripts
``` ```
@@ -105,6 +130,7 @@ firmware/
- Follow existing code style - run `trunk fmt` before commits - Follow existing code style - run `trunk fmt` before commits
- Prefer `LOG_DEBUG`, `LOG_INFO`, `LOG_WARN`, `LOG_ERROR` for logging - Prefer `LOG_DEBUG`, `LOG_INFO`, `LOG_WARN`, `LOG_ERROR` for logging
- Use `assert()` for invariants that should never fail - Use `assert()` for invariants that should never fail
- C++17 features are available (`std::optional`, structured bindings, `if constexpr`, etc.)
### Naming Conventions ### Naming Conventions
@@ -118,70 +144,151 @@ firmware/
#### Module System #### Module System
Modules inherit from `MeshModule` or `ProtobufModule<T>` and implement: Modules use a three-tier class hierarchy:
- `handleReceivedProtobuf()` - Process incoming packets 1. **`MeshModule`** - Base class. Implement `wantPacket()` and `handleReceived()`. Returns `ProcessMessage::STOP` or `ProcessMessage::CONTINUE`.
- `allocReply()` - Generate response packets 2. **`SinglePortModule`** - Handles a single portnum. Simplified `wantPacket()` that checks `decoded.portnum`.
- `runOnce()` - Periodic task execution (returns next run interval in ms) 3. **`ProtobufModule<T>`** - Template for protobuf-based modules. Handles encoding/decoding automatically.
Most modules also inherit from **`OSThread`** for periodic tasks (the "mixin" pattern):
```cpp ```cpp
class MyModule : public ProtobufModule<meshtastic_MyMessage> class MyModule : public ProtobufModule<meshtastic_MyMessage>, private concurrency::OSThread
{ {
public:
MyModule();
protected: protected:
virtual bool handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_MyMessage *msg) override; virtual bool handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_MyMessage *msg) override;
virtual int32_t runOnce() 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
}; };
``` ```
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 #### Configuration Access
- `config.*` - Device configuration (LoRa, position, power, etc.) - `config.*` - Device configuration (LoRa, position, power, etc.)
- `moduleConfig.*` - Module-specific configuration - `moduleConfig.*` - Module-specific configuration
- `channels.*` - Channel configuration and management - `channels.*` - Channel configuration and management
- `owner` - Device owner info
- `myNodeInfo` - Local node info
#### Default Values #### Default Values
Use the `Default` class helpers in `src/mesh/Default.h`: Use the `Default` class helpers in `src/mesh/Default.h`:
- `Default::getConfiguredOrDefaultMs(configured, default)` - Returns ms, using default if configured is 0 - `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::getConfiguredOrMinimumValue(configured, min)` - Enforces minimum values
- `Default::getConfiguredOrDefaultMsScaled(configured, default, numNodes)` - Scales based on network size - `Default::getConfiguredOrDefaultMsScaled(configured, default, numNodes)` - Scales based on network size
#### Thread Safety #### Thread Safety
- Use `concurrency::Lock` for mutex protection - Use `concurrency::Lock` and `concurrency::LockGuard` for mutex protection
- Radio SPI access uses `SPILock` - Radio SPI access uses `SPILock`
- Prefer `OSThread` for background tasks - 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 ### Hardware Variants
Each hardware variant has: Each hardware variant has:
- `variant.h` - Pin definitions and hardware capabilities - `variant.h` - Pin definitions and hardware capabilities
- `platformio.ini` - Build configuration - `platformio.ini` - Build configuration
- Optional: `pins_arduino.h`, `rfswitch.h` - Optional: `pins_arduino.h`, `rfswitch.h`, `nicheGraphics.h` (for InkHUD variants)
Key defines in variant.h: Key defines in variant.h:
```cpp ```cpp
#define USE_SX1262 // Radio chip selection #define USE_SX1262 // Radio chip selection
#define HAS_GPS 1 // Hardware capabilities #define HAS_GPS 1 // Hardware capabilities
#define HAS_SCREEN 1 // Display present
#define LORA_CS 36 // Pin assignments #define LORA_CS 36 // Pin assignments
#define SX126X_DIO1 14 // Radio-specific pins #define SX126X_DIO1 14 // Radio-specific pins
``` ```
### Protobuf Messages ### Protobuf Messages
- Defined in `protobufs/meshtastic/*.proto` - Defined in `protobufs/meshtastic/*.proto` (~32 proto files)
- Generated code in `src/mesh/generated/` - Generated code in `src/mesh/generated/meshtastic/`
- Regenerate with `bin/regen-protos.sh` - Regenerate with `bin/regen-protos.sh`
- Message types prefixed with `meshtastic_` - Message types prefixed with `meshtastic_`
- Nanopb `.options` files control field sizes and encoding
### Conditional Compilation ### Conditional Compilation
```cpp ```cpp
#if !MESHTASTIC_EXCLUDE_GPS // Feature exclusion #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_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 #if defined(USE_SX1262) // Radio-specific
#ifdef HAS_SCREEN // Hardware capability #ifdef HAS_SCREEN // Hardware capability
#if USERPREFS_EVENT_MODE // User preferences #if USERPREFS_EVENT_MODE // User preferences
@@ -192,7 +299,7 @@ Key defines in variant.h:
Uses **PlatformIO** with custom scripts: Uses **PlatformIO** with custom scripts:
- `bin/platformio-pre.py` - Pre-build script - `bin/platformio-pre.py` - Pre-build script
- `bin/platformio-custom.py` - Custom build logic - `bin/platformio-custom.py` - Custom build logic, manifest generation
Build commands: Build commands:
@@ -202,21 +309,38 @@ pio run -e tbeam -t upload # Build and upload
pio run -e native # Build native/Linux version 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 ## Common Tasks
### Adding a New Module ### Adding a New Module
1. Create `src/modules/MyModule.cpp` and `.h` 1. Create `src/modules/MyModule.cpp` and `.h`
2. Inherit from appropriate base class 2. Inherit from appropriate base class (`MeshModule`, `SinglePortModule`, or `ProtobufModule<T>`)
3. Register in `src/modules/Modules.cpp` 3. Mix in `concurrency::OSThread` if periodic work is needed
4. Add protobuf messages if needed in `protobufs/` 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
### Adding a New Hardware Variant ### Adding a New Hardware Variant
1. Create directory under `variants/<arch>/<name>/` 1. Create directory under `variants/<arch>/<name>/`
2. Add `variant.h` with pin definitions 2. Add `variant.h` with pin definitions and hardware capability defines
3. Add `platformio.ini` with build config 3. Add `platformio.ini` with build config — use `extends` to reference common base (e.g., `esp32s3_base`)
4. Reference common configs with `extends` 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
### Modifying Configuration Defaults ### Modifying Configuration Defaults
@@ -305,9 +429,22 @@ Most workflows can be triggered manually via `workflow_dispatch` for testing.
## Testing ## Testing
- Unit tests in `test/` directory Unit tests in `test/` directory with 12 test suites:
- Run with `pio test -e native`
- Use `bin/test-simulator.sh` for simulation testing - `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`
## Resources ## Resources
+138
View File
@@ -0,0 +1,138 @@
# 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
@@ -0,0 +1,149 @@
# 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
@@ -0,0 +1,178 @@
# 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
+4 -2
View File
@@ -301,10 +301,12 @@ jobs:
id: release_notes id: release_notes
run: | run: |
chmod +x ./bin/generate_release_notes.py chmod +x ./bin/generate_release_notes.py
NOTES=$(./bin/generate_release_notes.py ${{ needs.version.outputs.long }}) NOTES=$(./bin/generate_release_notes.py ${{ needs.version.outputs.long }} --compare-ref HEAD 2>release_notes.log)
echo "notes<<EOF" >> $GITHUB_OUTPUT echo "notes<<EOF" >> $GITHUB_OUTPUT
echo "$NOTES" >> $GITHUB_OUTPUT echo "$NOTES" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT echo "EOF" >> $GITHUB_OUTPUT
echo "### Release note range" >> $GITHUB_STEP_SUMMARY
cat release_notes.log >> $GITHUB_STEP_SUMMARY
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -466,7 +468,7 @@ jobs:
- name: Generate release notes - name: Generate release notes
run: | run: |
chmod +x ./bin/generate_release_notes.py chmod +x ./bin/generate_release_notes.py
./bin/generate_release_notes.py ${{ needs.version.outputs.long }} > ./publish/release_notes.md ./bin/generate_release_notes.py ${{ needs.version.outputs.long }} --compare-ref HEAD > ./publish/release_notes.md
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+54 -29
View File
@@ -1,25 +1,31 @@
#!/usr/bin/env python3 #!/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 subprocess import argparse
import re
import json import json
import re
import subprocess
import sys import sys
from datetime import datetime
def get_last_release_tag(): def get_last_release_tag(compare_ref, exclude_tag=None):
"""Get the most recent release tag.""" """Get the most recent version tag merged into compare_ref."""
result = subprocess.run( result = subprocess.run(
["git", "describe", "--tags", "--abbrev=0"], ["git", "tag", "--merged", compare_ref, "--sort=-version:refname", "v*"],
capture_output=True, capture_output=True,
text=True, text=True,
check=True, check=True,
) )
return result.stdout.strip()
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)
def get_tag_date(tag): def get_tag_date(tag):
@@ -33,18 +39,18 @@ def get_tag_date(tag):
return result.stdout.strip() return result.stdout.strip()
def get_merged_prs_since_tag(tag, branch): def get_merged_prs_in_range(tag, compare_ref):
"""Get all merged PRs since the given tag on the specified branch.""" """Get all merged PRs in the git range between tag and compare_ref."""
# Get commits since tag on the branch - look for PR numbers in parentheses
result = subprocess.run( result = subprocess.run(
[ [
"git", "git",
"log", "log",
f"{tag}..origin/{branch}", f"{tag}..{compare_ref}",
"--oneline", "--oneline",
], ],
capture_output=True, capture_output=True,
text=True, text=True,
check=True,
) )
prs = [] prs = []
@@ -65,6 +71,25 @@ def get_merged_prs_since_tag(tag, branch):
return prs 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): def get_pr_details(pr_number):
"""Get PR details from GitHub API via gh CLI.""" """Get PR details from GitHub API via gh CLI."""
try: try:
@@ -268,28 +293,28 @@ def get_new_contributors(pr_details_list, tag, repo="meshtastic/firmware"):
def main(): def main():
if len(sys.argv) < 2: args = parse_args()
print("Usage: generate_release_notes.py <new_version>", file=sys.stderr) new_version = args.new_version
sys.exit(1) compare_ref = args.compare_ref
current_tag = f"v{new_version}"
new_version = sys.argv[1]
# Get last release tag # Get last release tag
try: try:
last_tag = get_last_release_tag() last_tag = args.base_tag or get_last_release_tag(compare_ref, exclude_tag=current_tag)
except subprocess.CalledProcessError: except subprocess.CalledProcessError:
print("Error: Could not find last release tag", file=sys.stderr) print("Error: Could not find last release tag", file=sys.stderr)
sys.exit(1) sys.exit(1)
# Collect PRs from both branches print(
all_pr_numbers = set() f"Resolved release note range: {last_tag}..{compare_ref}",
file=sys.stderr,
)
for branch in ["develop", "master"]: try:
try: all_pr_numbers = set(get_merged_prs_in_range(last_tag, compare_ref))
prs = get_merged_prs_since_tag(last_tag, branch) except subprocess.CalledProcessError as e:
all_pr_numbers.update(prs) print(f"Error: Could not get PRs for range {last_tag}..{compare_ref}: {e}", file=sys.stderr)
except Exception as e: sys.exit(1)
print(f"Warning: Could not get PRs from {branch}: {e}", file=sys.stderr)
# Get details for all PRs # Get details for all PRs
enhancements = [] enhancements = []
@@ -87,6 +87,9 @@
</screenshots> </screenshots>
<releases> <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"> <release version="2.7.22" date="2026-04-06">
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.22</url> <url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.22</url>
</release> </release>
+52
View File
@@ -0,0 +1,52 @@
{
"build": {
"arduino": {
"ldscript": "nrf52840_s140_v6.ld"
},
"core": "nRF5",
"cpu": "cortex-m4",
"extra_flags": "-DARDUINO_NRF52840_FEATHER -DNRF52840_XXAA",
"f_cpu": "64000000L",
"hwids": [
["0x239A", "0x8029"],
["0x239A", "0x0029"],
["0x239A", "0x002A"],
["0x239A", "0x802A"]
],
"usb_product": "makerfabs_pucky",
"mcu": "nrf52840",
"variant": "makerfabs_pucky",
"bsp": {
"name": "adafruit"
},
"softdevice": {
"sd_flags": "-DS140",
"sd_name": "s140",
"sd_version": "6.1.1",
"sd_fwid": "0x00B6"
},
"bootloader": {
"settings_addr": "0xFF000"
}
},
"connectivity": ["bluetooth"],
"debug": {
"jlink_device": "nRF52840_xxAA",
"svd_path": "nrf52840.svd",
"openocd_target": "nrf52840-mdk-rs"
},
"frameworks": ["arduino", "freertos"],
"name": "makerfabs_pucky",
"upload": {
"maximum_ram_size": 248832,
"maximum_size": 815104,
"speed": 115200,
"protocol": "nrfutil",
"protocols": ["jlink", "nrfjprog", "nrfutil", "stlink"],
"use_1200bps_touch": true,
"require_upload_port": true,
"wait_for_upload_port": true
},
"url": "https://www.makerfabs.com",
"vendor": "Makerfabs"
}
+6
View File
@@ -1,3 +1,9 @@
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 meshtasticd (2.7.22.0) unstable; urgency=medium
* Version 2.7.22 * Version 2.7.22
+7
View File
@@ -21,8 +21,15 @@
// How many messages are stored (RAM + flash). // How many messages are stored (RAM + flash).
// Define -DMESSAGE_HISTORY_LIMIT=N in build_flags to control memory usage. // Define -DMESSAGE_HISTORY_LIMIT=N in build_flags to control memory usage.
#ifndef MESSAGE_HISTORY_LIMIT #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 #define MESSAGE_HISTORY_LIMIT 20
#endif #endif
#endif
// Internal alias used everywhere in code – do NOT redefine elsewhere. // Internal alias used everywhere in code – do NOT redefine elsewhere.
#define MAX_MESSAGES_SAVED MESSAGE_HISTORY_LIMIT #define MAX_MESSAGES_SAVED MESSAGE_HISTORY_LIMIT
+1 -1
View File
@@ -473,7 +473,7 @@ class AnalogBatteryLevel : public HasBatteryLevel
// technically speaking this should work for all(?) NRF52 boards // technically speaking this should work for all(?) NRF52 boards
// but needs testing across multiple devices. NRF52 USB would not even work if // but needs testing across multiple devices. NRF52 USB would not even work if
// VBUS was not properly connected and detected by the CPU // VBUS was not properly connected and detected by the CPU
#elif defined(MUZI_BASE) || defined(PROMICRO_DIY_TCXO) #elif defined(MUZI_BASE) || defined(PROMICRO_DIY_TCXO) || defined(HOCKEY_PUCK)
return powerHAL_isVBUSConnected(); return powerHAL_isVBUSConnected();
#endif #endif
return getBattVoltage() > chargingVolt; return getBattVoltage() > chargingVolt;
+76
View File
@@ -1,10 +1,85 @@
#include "concurrency/BinarySemaphorePosix.h" #include "concurrency/BinarySemaphorePosix.h"
#include "configuration.h" #include "configuration.h"
#include <errno.h>
#include <sys/time.h>
#ifndef HAS_FREE_RTOS #ifndef HAS_FREE_RTOS
namespace concurrency 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() {} BinarySemaphorePosix::BinarySemaphorePosix() {}
@@ -22,6 +97,7 @@ bool BinarySemaphorePosix::take(uint32_t msec)
void BinarySemaphorePosix::give() {} void BinarySemaphorePosix::give() {}
IRAM_ATTR void BinarySemaphorePosix::giveFromISR(BaseType_t *pxHigherPriorityTaskWoken) {} IRAM_ATTR void BinarySemaphorePosix::giveFromISR(BaseType_t *pxHigherPriorityTaskWoken) {}
#endif
} // namespace concurrency } // namespace concurrency
+10 -1
View File
@@ -2,6 +2,10 @@
#include "../freertosinc.h" #include "../freertosinc.h"
#ifdef ARCH_PORTDUINO
#include <pthread.h>
#endif
namespace concurrency namespace concurrency
{ {
@@ -9,7 +13,12 @@ namespace concurrency
class BinarySemaphorePosix class BinarySemaphorePosix
{ {
// SemaphoreHandle_t semaphore;
#ifdef ARCH_PORTDUINO
pthread_mutex_t mutex;
pthread_cond_t cond;
bool signaled;
#endif
public: public:
BinarySemaphorePosix(); BinarySemaphorePosix();
+20 -2
View File
@@ -422,6 +422,17 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
std::vector<bool> isMine; // track alignment std::vector<bool> isMine; // track alignment
std::vector<bool> isHeader; // track header lines std::vector<bool> isHeader; // track header lines
std::vector<AckStatus> ackForLine; 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) { for (auto it = filtered.rbegin(); it != filtered.rend(); ++it) {
const auto &m = *it; const auto &m = *it;
@@ -565,16 +576,23 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
int wrapWidth = mine ? rightTextWidth : leftTextWidth; int wrapWidth = mine ? rightTextWidth : leftTextWidth;
std::vector<std::string> wrapped = generateLines(display, "", msgText, wrapWidth); 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) { for (auto &ln : wrapped) {
allLines.push_back(ln); 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));
isMine.push_back(mine); isMine.push_back(mine);
isHeader.push_back(false); isHeader.push_back(false);
ackForLine.push_back(AckStatus::NONE); ackForLine.push_back(AckStatus::NONE);
++wrappedCount;
} }
} }
// Cache lines and heights // Cache lines and heights
cachedLines = allLines; cachedLines.swap(allLines);
cachedHeights = calculateLineHeights(cachedLines, emotes, isHeader); cachedHeights = calculateLineHeights(cachedLines, emotes, isHeader);
std::vector<MessageBlock> blocks = buildMessageBlocks(isHeader, isMine); std::vector<MessageBlock> blocks = buildMessageBlocks(isHeader, isMine);
-1
View File
@@ -121,7 +121,6 @@ void CardputerKeyboard::pressed(uint8_t key)
modifierFlag = 0; modifierFlag = 0;
} }
uint8_t next_key = 0;
int row = (key - 1) / 10; int row = (key - 1) / 10;
int col = (key - 1) % 10; int col = (key - 1) % 10;
+3
View File
@@ -497,6 +497,9 @@ void cpuDeepSleep(uint32_t msecToWake)
; ;
#endif #endif
#ifdef POWER_ON_OFF
digitalWrite(POWER_ON_OFF, HIGH);
#endif
auto ok = sd_power_system_off(); auto ok = sd_power_system_off();
if (ok != NRF_SUCCESS) { if (ok != NRF_SUCCESS) {
LOG_ERROR("FIXME: Ignoring soft device (EasyDMA pending?) and forcing system-off!"); LOG_ERROR("FIXME: Ignoring soft device (EasyDMA pending?) and forcing system-off!");
+1 -1
View File
@@ -2,7 +2,7 @@
[portduino_base] [portduino_base]
platform = platform =
# renovate: datasource=git-refs depName=platform-native packageName=https://github.com/meshtastic/platform-native gitBranch=develop # renovate: datasource=git-refs depName=platform-native packageName=https://github.com/meshtastic/platform-native gitBranch=develop
https://github.com/meshtastic/platform-native/archive/f566d364204416cdbf298e349213f7d551f793d9.zip https://github.com/meshtastic/platform-native/archive/71ed55bb95feb3c43ebde1ec1e2e17643a424c04.zip
framework = arduino framework = arduino
build_src_filter = build_src_filter =
@@ -0,0 +1,68 @@
[env:makerfabs_pucky]
custom_meshtastic_hw_model = 124
custom_meshtastic_hw_model_slug = Hockey_Puck
custom_meshtastic_architecture = nrf52840
custom_meshtastic_actively_supported = true
custom_meshtastic_support_level = 1
custom_meshtastic_display_name = Makerfabs Hockey Puck
custom_meshtastic_images = rak4631.svg, rak4631_case.svg
custom_meshtastic_tags = Makerfabs
extends = nrf52840_base
board = makerfabs_pucky
board_level = pr
board_check = true
build_type = release
build_flags = ${nrf52840_base.build_flags}
-I variants/nrf52840/makerfabs_pucky
-D Hockey_Puck
-DMESHTASTIC_USE_EINK_UI=0
-DMESHTASTIC_HAS_SCREEN=0
-DMESHTASTIC_HAS_GPS=1
-DRADIOLIB_EXCLUDE_SX128X=1
-DRADIOLIB_EXCLUDE_SX127X=1
-DRADIOLIB_EXCLUDE_SX126X=1
-DRADIOLIB_EXCLUDE_LR11X0=0
build_src_filter = ${nrf52_base.build_src_filter} \
+<../variants/nrf52840/makerfabs_pucky> \
+<mesh/api/> \
+<mqtt/> \
lib_deps =
${nrf52840_base.lib_deps}
${networking_base.lib_deps}
# TODO renovate
https://github.com/meshtastic/QMA6100P_Arduino_Library/archive/14c900b8b2e4feaac5007a7e41e0c1b7f0841136.zip
; If not set we will default to uploading over serial (first it forces bootloader entry by talking 1200bps to cdcacm)
; Note: as of 6/2013 the serial/bootloader based programming takes approximately 30 seconds
;upload_protocol = jlink
; Allows programming and debug via the RAK NanoDAP as the default debugger tool for the RAK4631 (it is only $10!)
; programming time is about the same as the bootloader version.
; For information on this see the meshtastic developers documentation for "Development on the NRF52"
[env:makerfabs_pucky_dbg]
extends = env:makerfabs_pucky
board_level = extra
; if the builtin version of openocd has a buggy version of semihosting, so use the external version
; platform_packages = platformio/tool-openocd@3.1200.0
build_flags =
${env:makerfabs_pucky.build_flags}
-D USE_SEMIHOSTING
lib_deps =
${env:makerfabs_pucky.lib_deps}
# TODO renovate
https://github.com/geeksville/Armduino-Semihosting/archive/35b538fdf208c3530c1434cd099a08e486672ee4.zip
; NOTE: the pyocd support for semihosting is buggy. So I switched to using the builtin platformio support for the stlink adapter which worked much better.
; However the built in openocd version in platformio has buggy support for TCP to semihosting.
;
; So I'm now trying the external openocd - but the openocd scripts for nrf52.cfg assume you are using a DAP adapter not an STLINK adapter.
; In theory I could change those scripts. But for now I'm trying going back to a DAP adapter but with the external openocd.
upload_protocol = stlink
; eventually use platformio/tool-pyocd@2.3600.0 instad
;upload_protocol = custom
;upload_command = pyocd flash -t nrf52840 $UPLOADERFLAGS $SOURCE
@@ -0,0 +1,18 @@
#include "RadioLib.h"
#include "nrf.h"
static const uint32_t rfswitch_dio_pins[] = {
RADIOLIB_LR11X0_DIO5, RADIOLIB_LR11X0_DIO6, RADIOLIB_NC, RADIOLIB_NC, RADIOLIB_NC // RADIOLIB_LR11X0_DIO7
};
static const Module::RfSwitchMode_t rfswitch_table[] = {
// mode DIO5 DIO6 // DIO7
{LR11x0::MODE_STBY, {LOW, LOW}}, // { LR11x0::MODE_STBY, { LOW, LOW, LOW } },
{LR11x0::MODE_RX, {HIGH, LOW}}, // { LR11x0::MODE_RX, { HIGH, LOW, LOW } },
{LR11x0::MODE_TX, {HIGH, HIGH}}, // { LR11x0::MODE_TX, { HIGH, HIGH, LOW } },
{LR11x0::MODE_TX_HP, {LOW, HIGH}}, // { LR11x0::MODE_TX_HP, { LOW, HIGH, HIGH } },
{LR11x0::MODE_TX_HF, {LOW, LOW}}, // { LR11x0::MODE_TX_HF, { LOW, LOW, HIGH } },
{LR11x0::MODE_GNSS, {LOW, LOW}}, // { LR11x0::MODE_GNSS, { LOW, LOW, LOW } },
{LR11x0::MODE_WIFI, {LOW, LOW}}, // { LR11x0::MODE_WIFI, { LOW, LOW, LOW } },
END_OF_MODE_TABLE,
};
@@ -0,0 +1,46 @@
/*
Copyright (c) 2014-2015 Arduino LLC. All right reserved.
Copyright (c) 2016 Sandeep Mistry All right reserved.
Copyright (c) 2018, Adafruit Industries (adafruit.com)
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "variant.h"
#include "nrf.h"
#include "wiring_constants.h"
#include "wiring_digital.h"
const uint32_t g_ADigitalPinMap[] = {
// P0
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
// P1
32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47};
void initVariant()
{
// 3V3 Power Rail
pinMode(PIN_3V3_EN, OUTPUT);
digitalWrite(PIN_3V3_EN, HIGH);
// Power latch
pinMode(POWER_ON_OFF, OUTPUT);
digitalWrite(POWER_ON_OFF, HIGH);
// Park the unconnected LNA_CTRL_MCU net so it does not float
pinMode(LNA_CTRL_PIN, INPUT_PULLDOWN);
}
+177
View File
@@ -0,0 +1,177 @@
/*
Copyright (c) 2014-2015 Arduino LLC. All right reserved.
Copyright (c) 2016 Sandeep Mistry All right reserved.
Copyright (c) 2018, Adafruit Industries (adafruit.com)
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef _VARIANT_RAK4630_
#define _VARIANT_RAK4630_
// #define RAK4630
/** Master clock frequency */
#define VARIANT_MCK (64000000ul)
#define USE_LFXO // Board uses 32khz crystal for LF
/*----------------------------------------------------------------------------
* Headers
*----------------------------------------------------------------------------*/
#include "WVariant.h"
#ifdef __cplusplus
extern "C" {
#endif // __cplusplus
// Number of pins defined in PinDescription array
#define PINS_COUNT (48)
#define NUM_DIGITAL_PINS (48)
#define NUM_ANALOG_INPUTS (6)
#define NUM_ANALOG_OUTPUTS (0)
// #define LED_PIN 16 // This is a LED_WS2812 not a standard LED
#define HAS_NEOPIXEL // Enable the use of neopixels
#define NEOPIXEL_COUNT 1 // How many neopixels are connected
#define NEOPIXEL_DATA 25 // gpio pin used to send data to the neopixels
#define NEOPIXEL_TYPE (NEO_GRB + NEO_KHZ800) // type of neopixels in use
#define RGB_LED_POWER
#define LED_STATE_ON 1
/*
* Buttons
*/
#define BUTTON_PIN 5
#define PIN_BUZZER 22
/*
* Analog pins
*/
#define PIN_A0 (2)
#define PIN_A1 (3)
#define PIN_A2 (4)
#define PIN_A3 (5)
#define PIN_A4 (28)
#define PIN_A5 (29)
#define PIN_A6 (30)
#define PIN_A7 (31)
static const uint8_t A0 = PIN_A0;
static const uint8_t A1 = PIN_A1;
static const uint8_t A2 = PIN_A2;
static const uint8_t A3 = PIN_A3;
static const uint8_t A4 = PIN_A4;
static const uint8_t A5 = PIN_A5;
static const uint8_t A6 = PIN_A6;
static const uint8_t A7 = PIN_A7;
#define ADC_RESOLUTION 14
// Other pins
#define PIN_AREF (2)
static const uint8_t AREF = PIN_AREF;
/*
* Serial interfaces
*/
// GPS on Base Board
#define PIN_SERIAL1_RX (8)
#define PIN_SERIAL1_TX (6)
// DEBUG on Base Board
#define PIN_SERIAL2_RX (38)
#define PIN_SERIAL2_TX (37)
/*
* SPI Interfaces
*/
#define SPI_INTERFACES_COUNT 1
#define PIN_SPI_MISO (47)
#define PIN_SPI_MOSI (46)
#define PIN_SPI_SCK (45)
static const uint8_t SS = 44;
static const uint8_t MOSI = PIN_SPI_MOSI;
static const uint8_t MISO = PIN_SPI_MISO;
static const uint8_t SCK = PIN_SPI_SCK;
#define USE_LR1121
#define LR11X0_POWER_EN (13)
#define LR1121_IRQ_PIN 14
#define LR1121_NRESET_PIN (42)
#define LR1121_BUSY_PIN (43)
#define LR1121_SPI_NSS_PIN SS
#define LR1121_SPI_SCK_PIN PIN_SPI_SCK
#define LR1121_SPI_MOSI_PIN PIN_SPI_MOSI
#define LR1121_SPI_MISO_PIN PIN_SPI_MISO
#define LR11X0_DIO3_TCXO_VOLTAGE 3.3
#define LR11X0_DIO_AS_RF_SWITCH
/*
* Wire Interfaces
*/
#define WIRE_INTERFACES_COUNT 1
#define PIN_WIRE_SDA (26)
#define PIN_WIRE_SCL (27)
#define HAS_QMA6100P
/*
* Power Switches
*/
#define POWER_ON_OFF (30) // P0.30 Power latch control, set high to keep power on, set low to turn off
#define PIN_3V3_EN (16)
#define HAS_GPS 1
#define PIN_GPS_PPS (11) // Pulse per second input from the GPS
#define GPS_RX_PIN PIN_SERIAL1_RX
#define GPS_TX_PIN PIN_SERIAL1_TX
// Battery
#define EXT_CHRG_DETECT (32 + 7) // P1.07
#define HAS_CW2015 1
// RAK4630 AIN0 = nrf52840 AIN3 = Pin 5
// #define BATTERY_LPCOMP_INPUT NRF_LPCOMP_INPUT_3
// We have AIN3 with a VBAT divider so AIN3 = VBAT * (1.5/2.5)
// We have the device going deep sleep under 3.1V, which is AIN3 = 1.86V
// So we can wake up when VBAT>=VDD is restored to 3.3V, where AIN3 = 1.98V
// 1.98/3.3 = 6/10, but that's close to the VBAT divider, so we
// pick 6/8VDD, which means VBAT=4.1V.
// Reference:
// VDD=3.3V AIN3=5/8*VDD=2.06V VBAT=1.66*AIN3=3.41V
// VDD=3.3V AIN3=11/16*VDD=2.26V VBAT=1.66*AIN3=3.76V
// VDD=3.3V AIN3=6/8*VDD=2.47V VBAT=1.66*AIN3=4.1V
// #define BATTERY_LPCOMP_THRESHOLD NRF_LPCOMP_REF_SUPPLY_11_16
#define LNA_CTRL_PIN 4 // P0.04
#define HOCKEY_PUCK 1
#define NUM_PA_POINTS 22
#define TX_GAIN_LORA 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 13, 13, 13, 12, 12, 11, 10, 9, 8, 7
#ifdef __cplusplus
}
#endif
/*----------------------------------------------------------------------------
* Arduino objects - C++ only
*----------------------------------------------------------------------------*/
#endif
+1 -1
View File
@@ -1,4 +1,4 @@
[VERSION] [VERSION]
major = 2 major = 2
minor = 7 minor = 7
build = 22 build = 23