mirror of
https://github.com/agessaman/MeshCore.git
synced 2026-10-23 12:52:43 +00:00
Add MQTT Bridge implementation with WiFi CLI configuration
- Implement MQTT bridge for packet uplink to multiple brokers - Add JSON message formatting for status, packets, and raw data - Add WiFi SSID/password CLI configuration (get/set wifi.ssid, wifi.pwd) - Add persistent storage for WiFi credentials - Add NTP time synchronization for accurate MQTT timestamps - Add CLI commands for MQTT configuration (origin, iata, status, packets, raw) - Support multiple MQTT brokers with automatic reconnection - Integrate with existing MeshCore bridge architecture
This commit is contained in:
1 parent
0cd0c36ab9
commit
efc15eb643
8 files changed
+376
-2701
No files matched your search
+17
-405
@@ -2,53 +2,6 @@
|
||||
|
||||
This document describes the MQTT bridge implementation that allows MeshCore repeaters to uplink packet data to multiple MQTT brokers.
|
||||
|
||||
## Quick Start Guide
|
||||
|
||||
### Essential Commands to Get MQTT Repeater Running
|
||||
|
||||
**1. Connect to device console via repeater login or serial console (115200 baud)**
|
||||
```bash
|
||||
# Connect to device via serial
|
||||
```
|
||||
|
||||
**2. Configure WiFi Credentials**
|
||||
```bash
|
||||
set wifi.ssid YourWiFiNetwork
|
||||
set wifi.pwd YourWiFiPassword
|
||||
```
|
||||
|
||||
**3. Reboot to Connect to WiFi**
|
||||
```bash
|
||||
reboot
|
||||
```
|
||||
|
||||
**4. Verify Configuration**
|
||||
```bash
|
||||
get wifi.ssid
|
||||
get bridge.enabled
|
||||
get mqtt.origin
|
||||
get mqtt.iata
|
||||
```
|
||||
If you wish to change mqtt.iata, use `set mqtt.iata XXX`
|
||||
|
||||
**5. Restart Bridge (if needed)**
|
||||
```bash
|
||||
# Option A: Toggle bridge off then on
|
||||
set bridge.enabled off
|
||||
set bridge.enabled on
|
||||
|
||||
# Option B: Full device reboot
|
||||
reboot
|
||||
```
|
||||
|
||||
**That's it!** The device will now:
|
||||
- Connect to WiFi automatically
|
||||
- Start uplinking mesh packets to Let's Mesh Analyzer
|
||||
- Publish to both custom MQTT broker and Let's Mesh Analyzer servers
|
||||
- Use device name as MQTT origin (set automatically)
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
The MQTT bridge implementation provides:
|
||||
@@ -68,47 +21,19 @@ The MQTT bridge implementation provides:
|
||||
|
||||
### Integration
|
||||
- Updated `examples/simple_repeater/MyMesh.h` - Added MQTT bridge support
|
||||
- Updated `examples/simple_repeater/MyMesh.cpp` - Added MQTT bridge integration and raw radio data capture
|
||||
- Updated `src/helpers/CommonCLI.h` - Added MQTT, WiFi, and timezone configuration fields
|
||||
- Updated `src/helpers/CommonCLI.cpp` - Added MQTT, WiFi, and timezone CLI commands
|
||||
- Updated `examples/simple_repeater/MyMesh.cpp` - Added MQTT bridge integration
|
||||
- Updated `src/helpers/CommonCLI.h` - Added MQTT configuration fields
|
||||
- Updated `src/helpers/CommonCLI.cpp` - Added MQTT CLI commands
|
||||
- Updated `variants/heltec_v3/platformio.ini` - Added MQTT build configuration
|
||||
- Updated `variants/station_g2/platformio.ini` - Added MQTT build configuration for Station G2
|
||||
|
||||
## Build Configuration
|
||||
|
||||
To build the MQTT bridge firmware:
|
||||
|
||||
### Heltec V3
|
||||
```bash
|
||||
pio run -e Heltec_v3_repeater_observer_mqtt
|
||||
pio run -e Heltec_v3_repeater_bridge_mqtt
|
||||
```
|
||||
|
||||
### Station G2
|
||||
```bash
|
||||
pio run -e Station_G2_repeater_observer_mqtt
|
||||
```
|
||||
|
||||
### Custom MQTT Server Configuration
|
||||
|
||||
You can configure a custom MQTT server using build flags in `platformio.ini`:
|
||||
|
||||
```ini
|
||||
[env:Heltec_v3_repeater_observer_mqtt]
|
||||
build_flags =
|
||||
${Heltec_lora32_v3.build_flags}
|
||||
-D WITH_MQTT_BRIDGE=1
|
||||
-D MQTT_SERVER='"your-mqtt-broker.com"'
|
||||
-D MQTT_PORT=1883
|
||||
-D MQTT_USERNAME='"your-username"'
|
||||
-D MQTT_PASSWORD='"your-password"'
|
||||
```
|
||||
|
||||
**Build Flags:**
|
||||
- `MQTT_SERVER` - MQTT broker hostname
|
||||
- `MQTT_PORT` - MQTT broker port (default: 1883)
|
||||
- `MQTT_USERNAME` - MQTT username
|
||||
- `MQTT_PASSWORD` - MQTT password
|
||||
|
||||
## Default Configuration
|
||||
|
||||
The MQTT bridge comes with the following defaults:
|
||||
@@ -117,117 +42,40 @@ The MQTT bridge comes with the following defaults:
|
||||
- **Status Messages**: Enabled
|
||||
- **Packet Messages**: Enabled
|
||||
- **Raw Messages**: Disabled
|
||||
- **TX Messages**: Disabled (RX only by default)
|
||||
- **Status Interval**: 5 minutes (300000 ms)
|
||||
- **Default Broker**: meshtastic.pugetmesh.org:1883 (username: meshdev, password: large4cats)
|
||||
- **WiFi SSID**: "ssid_here" (must be configured)
|
||||
- **WiFi Password**: "password_here" (must be configured)
|
||||
- **Timezone**: "America/Los_Angeles" (Pacific Time with DST support)
|
||||
- **Timezone Offset**: -8 hours (fallback)
|
||||
- **Let's Mesh Analyzer US**: Enabled (mqtt-us-v1.letsmesh.net:443)
|
||||
- **Let's Mesh Analyzer EU**: Enabled (mqtt-eu-v1.letsmesh.net:443)
|
||||
|
||||
## CLI Commands
|
||||
|
||||
### MQTT Commands
|
||||
|
||||
#### Get Commands
|
||||
### Get Commands
|
||||
- `get mqtt.origin` - Get device origin name
|
||||
- `get mqtt.iata` - Get IATA code
|
||||
- `get mqtt.status` - Get status message setting (on/off)
|
||||
- `get mqtt.packets` - Get packet message setting (on/off)
|
||||
- `get mqtt.raw` - Get raw message setting (on/off)
|
||||
- `get mqtt.tx` - Get TX message setting (on/off)
|
||||
- `get mqtt.interval` - Get status publish interval (ms)
|
||||
- `get mqtt.server` - Get MQTT server hostname
|
||||
- `get mqtt.port` - Get MQTT server port
|
||||
- `get mqtt.username` - Get MQTT username
|
||||
- `get mqtt.password` - Get MQTT password
|
||||
- `get mqtt.analyzer.us` - Get US Let's Mesh Analyzer server setting (on/off)
|
||||
- `get mqtt.analyzer.eu` - Get EU Let's Mesh Analyzer server setting (on/off)
|
||||
|
||||
#### Set Commands
|
||||
### Set Commands
|
||||
- `set mqtt.origin <name>` - Set device origin name
|
||||
- `set mqtt.iata <code>` - Set IATA code
|
||||
- `set mqtt.status on|off` - Enable/disable status messages
|
||||
- `set mqtt.packets on|off` - Enable/disable packet messages
|
||||
- `set mqtt.raw on|off` - Enable/disable raw messages
|
||||
- `set mqtt.tx on|off` - Enable/disable TX packet messages
|
||||
- `set mqtt.interval <ms>` - Set status publish interval (1000-3600000 ms)
|
||||
- `set mqtt.server <hostname>` - Set MQTT server hostname
|
||||
- `set mqtt.port <port>` - Set MQTT server port (1-65535)
|
||||
- `set mqtt.username <username>` - Set MQTT username
|
||||
- `set mqtt.password <password>` - Set MQTT password
|
||||
- `set mqtt.analyzer.us on|off` - Enable/disable US Let's Mesh Analyzer server
|
||||
- `set mqtt.analyzer.eu on|off` - Enable/disable EU Let's Mesh Analyzer server
|
||||
|
||||
### WiFi Commands
|
||||
|
||||
#### Get Commands
|
||||
- `get wifi.ssid` - Get WiFi SSID
|
||||
- `get wifi.pwd` - Get WiFi password
|
||||
|
||||
#### Set Commands
|
||||
- `set wifi.ssid <ssid>` - Set WiFi SSID
|
||||
- `set wifi.pwd <password>` - Set WiFi password
|
||||
|
||||
### Timezone Commands
|
||||
|
||||
#### Get Commands
|
||||
- `get timezone` - Get timezone string (e.g., "America/Los_Angeles")
|
||||
- `get timezone.offset` - Get timezone offset in hours (-12 to +14)
|
||||
|
||||
#### Set Commands
|
||||
- `set timezone <string>` - Set timezone string (IANA format or abbreviation)
|
||||
- `set timezone.offset <offset>` - Set timezone offset in hours (-12 to +14)
|
||||
|
||||
#### Supported Timezone Formats
|
||||
- **IANA strings**: `America/Los_Angeles`, `Europe/London`, `Asia/Tokyo`, etc.
|
||||
- **Common abbreviations**: `PDT`, `PST`, `MDT`, `MST`, `CDT`, `CST`, `EDT`, `EST`, `BST`, `GMT`, `CEST`, `CET`
|
||||
- **UTC offsets**: `UTC-8`, `UTC+5`, `+5`, `-8`, etc.
|
||||
|
||||
### Bridge Commands
|
||||
|
||||
#### Get Commands
|
||||
- `get bridge.source` - Get packet source (rx/tx)
|
||||
- `get bridge.enabled` - Get bridge enabled status (on/off)
|
||||
|
||||
#### Set Commands
|
||||
- `set bridge.source rx|tx` - Set packet source (rx for received, tx for transmitted)
|
||||
- `set bridge.enabled on|off` - Enable/disable bridge
|
||||
|
||||
## Command Architecture
|
||||
|
||||
The CLI commands are organized into two levels:
|
||||
|
||||
### Bridge Commands (`bridge.*`)
|
||||
**Low-level bridge control** - These settings apply to all bridge types (MQTT, RS232, ESP-NOW, etc.):
|
||||
- `bridge.enabled` - Master switch for the entire bridge system
|
||||
- `bridge.source` - Controls which packet events to capture (RX vs TX)
|
||||
|
||||
### Bridge-Specific Commands (`mqtt.*`, `wifi.*`, `timezone.*`)
|
||||
**Implementation-specific settings** - These only apply to the MQTT bridge:
|
||||
- `mqtt.*` - MQTT broker configuration, message types, and formatting
|
||||
- `wifi.*` - WiFi connection settings for MQTT connectivity
|
||||
- `timezone.*` - Timezone configuration for accurate timestamps
|
||||
|
||||
This design allows MeshCore to support multiple bridge types simultaneously while keeping configuration clean and logical.
|
||||
|
||||
## MQTT Topics
|
||||
|
||||
The bridge publishes to three main topics with the following structure:
|
||||
The bridge publishes to three main topics:
|
||||
|
||||
### Status Topic: `meshcore/{IATA}/{DEVICE_PUBLIC_KEY}/status`
|
||||
### Status Topic: `meshcore/{IATA}/status`
|
||||
Device connection status and metadata (retained messages).
|
||||
|
||||
### Packets Topic: `meshcore/{IATA}/{DEVICE_PUBLIC_KEY}/packets`
|
||||
### Packets Topic: `meshcore/{IATA}/packets`
|
||||
Full packet data with RF characteristics and metadata.
|
||||
|
||||
### Raw Topic: `meshcore/{IATA}/{DEVICE_PUBLIC_KEY}/raw`
|
||||
### Raw Topic: `meshcore/{IATA}/raw`
|
||||
Minimal raw packet data for map integration.
|
||||
|
||||
**Note**: `{DEVICE_PUBLIC_KEY}` is the device's public key in hexadecimal format (64 characters).
|
||||
|
||||
## JSON Message Formats
|
||||
|
||||
### Status Message
|
||||
@@ -240,7 +88,7 @@ Minimal raw packet data for map integration.
|
||||
"model": "device_model",
|
||||
"firmware_version": "firmware_version",
|
||||
"radio": "radio_info",
|
||||
"client_version": "meshcore-custom-repeater/{build_date}"
|
||||
"client_version": "packet_capture_version"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -277,260 +125,24 @@ Minimal raw packet data for map integration.
|
||||
}
|
||||
```
|
||||
|
||||
## Key Features
|
||||
|
||||
### Raw Radio Data Capture
|
||||
- Captures actual raw radio transmission data (including radio headers)
|
||||
- Uses proper MeshCore packet hashing (SHA256-based)
|
||||
- Provides accurate SNR/RSSI values from actual radio reception
|
||||
- Supports both RX and TX packet uplinking (configurable)
|
||||
|
||||
### Timezone Support
|
||||
- Full timezone support with automatic DST handling
|
||||
- Supports IANA timezone strings, common abbreviations, and UTC offsets
|
||||
- Separates local time (for timestamps) and UTC time (for time/date fields)
|
||||
- Uses JChristensen/Timezone library for accurate timezone conversions
|
||||
|
||||
### WiFi Configuration
|
||||
- Runtime WiFi credential management via CLI
|
||||
- Persistent storage across reboots
|
||||
- Automatic reconnection with exponential backoff
|
||||
|
||||
### NTP Time Synchronization
|
||||
- Automatic time synchronization with NTP servers
|
||||
- Periodic time updates (every hour)
|
||||
- Proper UTC system time handling
|
||||
|
||||
### Let's Mesh Analyzer Integration
|
||||
- **JWT Authentication**: Ed25519-signed tokens for secure MQTT authentication
|
||||
- **WebSocket MQTT**: Support for MQTT over WebSocket connections (TLS/SSL)
|
||||
- **Dual Server Support**: Both US and EU servers enabled by default
|
||||
- **Automatic Token Generation**: Creates authentication tokens using device's Ed25519 keys
|
||||
- **Username Format**: `v1_{UPPERCASE_PUBLIC_KEY}` (e.g., `v1_7E7662676F7F0850A8A355BAAFBFC1EB7B4174C340442D7D7161C9474A2C9400`)
|
||||
- **Server Configuration**:
|
||||
- US Server: `mqtt-us-v1.letsmesh.net:443` (WebSocket with TLS)
|
||||
- EU Server: `mqtt-eu-v1.letsmesh.net:443` (WebSocket with TLS)
|
||||
|
||||
## First-Time Setup
|
||||
|
||||
### Prerequisites
|
||||
- MeshCore device with MQTT bridge firmware flashed
|
||||
- WiFi network credentials
|
||||
- MQTT broker (optional - default broker is provided)
|
||||
- LoRa-capable device for configuration (repeater console)
|
||||
- MeshCore network access
|
||||
|
||||
### Step 1: Initial Boot and Network Connection
|
||||
1. **Flash the firmware** to your device using PlatformIO or the build script
|
||||
2. **Deploy the device** in your mesh network location
|
||||
3. **Ensure WiFi connectivity** - the device will automatically connect to WiFi if credentials are pre-configured
|
||||
4. **Verify mesh network access** - device should be discoverable by other mesh nodes
|
||||
|
||||
### Step 2: Connect via LoRa Repeater Console
|
||||
Use a MeshCore companion device to configure the Repeater's MQTT bridge.
|
||||
|
||||
1. **Connect to the mesh** using your companion
|
||||
2. **Locate the MQTT bridge device** in your contacts
|
||||
3. **Log into your Repeater** using the default password (password) or whatever you configured via serial console
|
||||
3. **Tap on the repeater console** on your repeater's settings
|
||||
4. **Send configuration commands** via LoRa to the MQTT bridge device
|
||||
|
||||
### Step 3: Configure WiFi Connection
|
||||
The device needs internet connectivity to publish to MQTT brokers.
|
||||
|
||||
**Via LoRa Repeater Console:**
|
||||
```
|
||||
# Set your WiFi credentials
|
||||
set wifi.ssid "YourWiFiNetwork"
|
||||
set wifi.pwd "YourWiFiPassword"
|
||||
|
||||
# Verify WiFi settings
|
||||
get wifi.ssid
|
||||
get wifi.pwd
|
||||
```
|
||||
|
||||
### Step 4: Configure Device Identity
|
||||
Set up your device's identity for MQTT topics and status messages.
|
||||
|
||||
**Via LoRa Repeater Console:**
|
||||
```
|
||||
# Set IATA code for topic structure (e.g., airport code)
|
||||
set mqtt.iata "SEA"
|
||||
|
||||
# Verify settings (origin is set automatically to device name)
|
||||
get mqtt.origin
|
||||
get mqtt.iata
|
||||
```
|
||||
|
||||
### Step 5: Configure Timezone
|
||||
Set your local timezone for accurate timestamps.
|
||||
|
||||
**Via LoRa Repeater Console:**
|
||||
```
|
||||
# Set timezone (choose one method)
|
||||
set timezone "America/Los_Angeles" # IANA format
|
||||
set timezone "PDT" # Abbreviation
|
||||
set timezone "UTC-8" # UTC offset
|
||||
|
||||
# Verify timezone
|
||||
get timezone
|
||||
```
|
||||
|
||||
### Step 6: Configure MQTT Settings
|
||||
Customize which messages to publish and how often.
|
||||
|
||||
**Via LoRa Repeater Console:**
|
||||
```
|
||||
# Configure MQTT server (optional - uses defaults if not set)
|
||||
set mqtt.server "your-mqtt-broker.com"
|
||||
set mqtt.port 1883
|
||||
set mqtt.username "your-username"
|
||||
set mqtt.password "your-password"
|
||||
|
||||
# Enable/disable message types
|
||||
set mqtt.status on # Device status messages
|
||||
set mqtt.packets on # Packet data messages
|
||||
set mqtt.raw off # Raw packet data (optional)
|
||||
set mqtt.tx off # Transmitted packets (optional)
|
||||
|
||||
# Set status publish interval (default: 5 minutes)
|
||||
set mqtt.interval 300000
|
||||
|
||||
# Verify settings
|
||||
get mqtt.server
|
||||
get mqtt.port
|
||||
get mqtt.username
|
||||
get mqtt.status
|
||||
get mqtt.packets
|
||||
get mqtt.interval
|
||||
```
|
||||
|
||||
### Step 7: Verify MQTT Broker Connection
|
||||
Check that the device can connect to MQTT brokers.
|
||||
|
||||
**Via LoRa Repeater Console:**
|
||||
```
|
||||
# Check bridge status
|
||||
get bridge.enabled
|
||||
|
||||
# If disabled, enable it
|
||||
set bridge.enabled on
|
||||
|
||||
# Check MQTT analyzer servers (optional)
|
||||
get mqtt.analyzer.us
|
||||
get mqtt.analyzer.eu
|
||||
```
|
||||
|
||||
### Step 8: Monitor MQTT Messages
|
||||
Once configured, the device will automatically publish messages to MQTT brokers.
|
||||
|
||||
**Default MQTT Broker**: `meshtastic.pugetmesh.org:1883`
|
||||
- Username: `meshdev`
|
||||
- Password: `large4cats`
|
||||
|
||||
**Topic Structure**:
|
||||
- Status: `meshcore/{IATA}/{DEVICE_PUBLIC_KEY}/status`
|
||||
- Packets: `meshcore/{IATA}/{DEVICE_PUBLIC_KEY}/packets`
|
||||
- Raw: `meshcore/{IATA}/{DEVICE_PUBLIC_KEY}/raw`
|
||||
|
||||
**Example Topics**:
|
||||
- `meshcore/SEA/7E7662676F7F0850A8A355BAAFBFC1EB7B4174C340442D7D7161C9474A2C9400/status`
|
||||
- `meshcore/SEA/7E7662676F7F0850A8A355BAAFBFC1EB7B4174C340442D7D7161C9474A2C9400/packets`
|
||||
|
||||
### Step 9: Troubleshooting
|
||||
|
||||
#### Device Won't Connect to WiFi
|
||||
**Via LoRa Repeater Console:**
|
||||
```
|
||||
# Check WiFi settings
|
||||
get wifi.ssid
|
||||
get wifi.pwd
|
||||
|
||||
# Reset WiFi settings
|
||||
set wifi.ssid ""
|
||||
set wifi.pwd ""
|
||||
|
||||
# Reconfigure with correct credentials
|
||||
set wifi.ssid "YourWiFiNetwork"
|
||||
set wifi.pwd "YourWiFiPassword"
|
||||
```
|
||||
|
||||
#### No MQTT Messages Appearing
|
||||
**Via LoRa Repeater Console:**
|
||||
```
|
||||
# Check bridge status
|
||||
get bridge.enabled
|
||||
|
||||
# Check message types
|
||||
get mqtt.status
|
||||
get mqtt.packets
|
||||
|
||||
# Check device identity (origin is set automatically)
|
||||
get mqtt.origin
|
||||
get mqtt.iata
|
||||
|
||||
# Enable bridge if needed
|
||||
set bridge.enabled on
|
||||
```
|
||||
|
||||
#### Timezone Issues
|
||||
**Via LoRa Repeater Console:**
|
||||
```
|
||||
# Check current timezone
|
||||
get timezone
|
||||
|
||||
# Try different timezone formats
|
||||
set timezone "America/New_York" # IANA format
|
||||
set timezone "EST" # Abbreviation
|
||||
set timezone "UTC-5" # UTC offset
|
||||
```
|
||||
|
||||
#### LoRa Configuration Issues
|
||||
- **Device not responding**: Ensure both devices are on the same mesh network
|
||||
- **Commands not working**: Check that the target device is reachable via LoRa
|
||||
- **No response to get commands**: Verify the device is powered and in range
|
||||
|
||||
### Step 10: Advanced Configuration (Optional)
|
||||
|
||||
#### Custom MQTT Broker
|
||||
If you want to use your own MQTT broker instead of the default:
|
||||
|
||||
```
|
||||
# Note: Custom broker configuration requires code modification
|
||||
# The default broker is: meshtastic.pugetmesh.org:1883
|
||||
# Username: meshdev, Password: large4cats
|
||||
```
|
||||
|
||||
#### Let's Mesh Analyzer Servers
|
||||
The device automatically connects to Let's Mesh Analyzer servers for additional monitoring:
|
||||
|
||||
- **US Server**: `mqtt-us-v1.letsmesh.net:443` (WebSocket with TLS)
|
||||
- **EU Server**: `mqtt-eu-v1.letsmesh.net:443` (WebSocket with TLS)
|
||||
|
||||
These are enabled by default and use JWT authentication with your device's Ed25519 keys.
|
||||
|
||||
## Testing
|
||||
|
||||
1. Flash the MQTT bridge firmware to your device
|
||||
2. Follow the first-time setup instructions above
|
||||
3. Monitor MQTT broker for incoming messages
|
||||
4. Verify message formats match the JSON schemas in this document
|
||||
2. Connect to the device via serial console
|
||||
3. Configure WiFi connection (if not already connected)
|
||||
4. Check MQTT settings: `get mqtt.origin`
|
||||
5. Monitor MQTT broker for incoming messages
|
||||
|
||||
## Dependencies
|
||||
|
||||
- **PubSubClient**: MQTT client library
|
||||
- **ArduinoJson**: JSON message formatting (v6.17.3)
|
||||
- **NTPClient**: Network time protocol client
|
||||
- **Timezone**: Timezone conversion library (JChristensen/Timezone)
|
||||
- **ArduinoJson**: JSON message formatting
|
||||
- **WiFi**: ESP32 WiFi functionality
|
||||
- **Ed25519**: Cryptographic library for JWT token signing
|
||||
- **JWTHelper**: Custom JWT token generation for Let's Mesh Analyzer authentication
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
- Full WebSocket MQTT implementation (currently JWT tokens are generated but WebSocket publishing is pending)
|
||||
- Device-signed JWTs for WebSocket MQTT servers
|
||||
- Multiple broker configuration via CLI
|
||||
- Advanced packet filtering
|
||||
- Custom topic templates
|
||||
- TLS/SSL support for secure connections
|
||||
- Real-time WebSocket MQTT publishing to Let's Mesh Analyzer servers
|
||||
@@ -343,19 +343,12 @@ void MyMesh::logRxRaw(float snr, float rssi, const uint8_t raw[], int len) {
|
||||
mesh::Utils::printHex(Serial, raw, len);
|
||||
Serial.println();
|
||||
#endif
|
||||
|
||||
#ifdef WITH_BRIDGE
|
||||
if (_prefs.bridge_enabled) {
|
||||
// Store raw radio data for MQTT messages
|
||||
bridge.storeRawRadioData(raw, len, snr, rssi);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void MyMesh::logRx(mesh::Packet *pkt, int len, float score) {
|
||||
#ifdef WITH_BRIDGE
|
||||
if (_prefs.bridge_pkt_src == 1) {
|
||||
bridge.onPacketReceived(pkt);
|
||||
bridge.sendPacket(pkt);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -681,7 +674,7 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc
|
||||
#elif defined(WITH_ESPNOW_BRIDGE)
|
||||
, bridge(&_prefs, _mgr, &rtc)
|
||||
#elif defined(WITH_MQTT_BRIDGE)
|
||||
, bridge(&_prefs, _mgr, &rtc, &self_id)
|
||||
, bridge(&_prefs, _mgr, &rtc)
|
||||
#endif
|
||||
{
|
||||
last_millis = 0;
|
||||
@@ -719,7 +712,7 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc
|
||||
// bridge defaults
|
||||
_prefs.bridge_enabled = 1; // enabled
|
||||
_prefs.bridge_delay = 500; // milliseconds
|
||||
_prefs.bridge_pkt_src = 1; // logRx (RX packets)
|
||||
_prefs.bridge_pkt_src = 0; // logTx
|
||||
_prefs.bridge_baud = 115200; // baud rate
|
||||
_prefs.bridge_channel = 1; // channel 1
|
||||
|
||||
@@ -736,20 +729,11 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc
|
||||
_prefs.mqtt_status_enabled = 1; // enabled
|
||||
_prefs.mqtt_packets_enabled = 1; // enabled
|
||||
_prefs.mqtt_raw_enabled = 0; // disabled
|
||||
_prefs.mqtt_tx_enabled = 0; // disabled (RX only for now)
|
||||
_prefs.mqtt_status_interval = 300000; // 5 minutes
|
||||
|
||||
// WiFi defaults
|
||||
StrHelper::strncpy(_prefs.wifi_ssid, "ssid_here", sizeof(_prefs.wifi_ssid));
|
||||
StrHelper::strncpy(_prefs.wifi_password, "password_here", sizeof(_prefs.wifi_password));
|
||||
|
||||
// Timezone defaults (Pacific Time with DST support)
|
||||
StrHelper::strncpy(_prefs.timezone_string, "America/Los_Angeles", sizeof(_prefs.timezone_string));
|
||||
_prefs.timezone_offset = -8; // fallback
|
||||
|
||||
// Let's Mesh Analyzer defaults (both enabled by default)
|
||||
_prefs.mqtt_analyzer_us_enabled = 1; // enabled
|
||||
_prefs.mqtt_analyzer_eu_enabled = 1; // enabled
|
||||
}
|
||||
|
||||
void MyMesh::begin(FILESYSTEM *fs) {
|
||||
@@ -757,18 +741,6 @@ void MyMesh::begin(FILESYSTEM *fs) {
|
||||
_fs = fs;
|
||||
// load persisted prefs
|
||||
_cli.loadPrefs(_fs);
|
||||
|
||||
// Ensure analyzer servers are enabled by default (in case no prefs were loaded)
|
||||
if (_prefs.mqtt_analyzer_us_enabled == 0 && _prefs.mqtt_analyzer_eu_enabled == 0) {
|
||||
_prefs.mqtt_analyzer_us_enabled = 1; // enabled
|
||||
_prefs.mqtt_analyzer_eu_enabled = 1; // enabled
|
||||
MESH_DEBUG_PRINTLN("Setting analyzer servers to enabled by default");
|
||||
}
|
||||
|
||||
// Set MQTT origin to actual device name (not build-time ADVERT_NAME)
|
||||
StrHelper::strncpy(_prefs.mqtt_origin, _prefs.node_name, sizeof(_prefs.mqtt_origin));
|
||||
MESH_DEBUG_PRINTLN("MQTT origin set to device name: %s", _prefs.mqtt_origin);
|
||||
|
||||
acl.load(_fs);
|
||||
// TODO: key_store.begin();
|
||||
region_map.load(_fs);
|
||||
@@ -788,16 +760,6 @@ void MyMesh::begin(FILESYSTEM *fs) {
|
||||
// Set board model
|
||||
bridge.setBoardModel(_cli.getBoard()->getManufacturerName());
|
||||
|
||||
// Set build date
|
||||
bridge.setBuildDate(getBuildDate());
|
||||
|
||||
#ifdef WITH_MQTT_BRIDGE
|
||||
// Set stats sources for automatic stats collection (optional - can be done in custom initialization)
|
||||
// This enables stats to be included in status messages automatically
|
||||
// this (Mesh*) inherits from Dispatcher, so it can be passed as Dispatcher*
|
||||
bridge.setStatsSources(this, _radio, _cli.getBoard(), _ms);
|
||||
#endif
|
||||
|
||||
bridge.begin();
|
||||
}
|
||||
#endif
|
||||
|
||||
+180
-336
@@ -3,24 +3,6 @@
|
||||
#include "TxtDataHelpers.h"
|
||||
#include "AdvertDataHelpers.h"
|
||||
#include <RTClib.h>
|
||||
#ifdef WITH_MQTT_BRIDGE
|
||||
#include "bridges/MQTTBridge.h"
|
||||
|
||||
// Helper function to calculate total size of MQTT fields for file format compatibility
|
||||
// Uses NodePrefs struct to get accurate field sizes
|
||||
static size_t getMQTTFieldsSize(const NodePrefs* prefs) {
|
||||
return sizeof(prefs->mqtt_origin) + sizeof(prefs->mqtt_iata) +
|
||||
sizeof(prefs->mqtt_status_enabled) + sizeof(prefs->mqtt_packets_enabled) +
|
||||
sizeof(prefs->mqtt_raw_enabled) + sizeof(prefs->mqtt_tx_enabled) +
|
||||
sizeof(prefs->mqtt_status_interval) + sizeof(prefs->wifi_ssid) +
|
||||
sizeof(prefs->wifi_password) + sizeof(prefs->timezone_string) +
|
||||
sizeof(prefs->timezone_offset) + sizeof(prefs->mqtt_server) +
|
||||
sizeof(prefs->mqtt_port) + sizeof(prefs->mqtt_username) +
|
||||
sizeof(prefs->mqtt_password) + sizeof(prefs->mqtt_analyzer_us_enabled) +
|
||||
sizeof(prefs->mqtt_analyzer_eu_enabled) + sizeof(prefs->mqtt_owner_public_key) +
|
||||
sizeof(prefs->mqtt_email);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Believe it or not, this std C function is busted on some platforms!
|
||||
static uint32_t _atoi(const char* sp) {
|
||||
@@ -40,12 +22,6 @@ void CommonCLI::loadPrefs(FILESYSTEM* fs) {
|
||||
savePrefs(fs); // save to new filename
|
||||
fs->remove("/node_prefs"); // remove old
|
||||
}
|
||||
#ifdef WITH_MQTT_BRIDGE
|
||||
// Load MQTT preferences from separate file
|
||||
loadMQTTPrefs(fs);
|
||||
// Sync MQTT prefs to NodePrefs so existing code (like MQTTBridge) can access them
|
||||
syncMQTTPrefsToNodePrefs();
|
||||
#endif
|
||||
}
|
||||
|
||||
void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) {
|
||||
@@ -227,33 +203,6 @@ void CommonCLI::savePrefs(FILESYSTEM* fs) {
|
||||
file.write(pad, to_write);
|
||||
remaining -= to_write;
|
||||
}
|
||||
file.write((uint8_t *)&_prefs->mqtt_iata, sizeof(_prefs->mqtt_iata)); // 194
|
||||
file.write((uint8_t *)&_prefs->mqtt_status_enabled, sizeof(_prefs->mqtt_status_enabled)); // 202
|
||||
file.write((uint8_t *)&_prefs->mqtt_packets_enabled, sizeof(_prefs->mqtt_packets_enabled)); // 203
|
||||
file.write((uint8_t *)&_prefs->mqtt_raw_enabled, sizeof(_prefs->mqtt_raw_enabled)); // 204
|
||||
file.write((uint8_t *)&_prefs->mqtt_tx_enabled, sizeof(_prefs->mqtt_tx_enabled)); // 205
|
||||
file.write((uint8_t *)&_prefs->mqtt_status_interval, sizeof(_prefs->mqtt_status_interval)); // 206
|
||||
|
||||
// WiFi settings
|
||||
file.write((uint8_t *)&_prefs->wifi_ssid, sizeof(_prefs->wifi_ssid)); // 209
|
||||
file.write((uint8_t *)&_prefs->wifi_password, sizeof(_prefs->wifi_password)); // 241
|
||||
|
||||
// Timezone settings
|
||||
file.write((uint8_t *)&_prefs->timezone_string, sizeof(_prefs->timezone_string)); // 305
|
||||
file.write((uint8_t *)&_prefs->timezone_offset, sizeof(_prefs->timezone_offset)); // 337
|
||||
|
||||
// MQTT server settings
|
||||
file.write((uint8_t *)&_prefs->mqtt_server, sizeof(_prefs->mqtt_server)); // 340
|
||||
file.write((uint8_t *)&_prefs->mqtt_port, sizeof(_prefs->mqtt_port)); // 341
|
||||
file.write((uint8_t *)&_prefs->mqtt_username, sizeof(_prefs->mqtt_username)); // 342
|
||||
file.write((uint8_t *)&_prefs->mqtt_password, sizeof(_prefs->mqtt_password)); // 343
|
||||
|
||||
// Let's Mesh Analyzer settings
|
||||
file.write((uint8_t *)&_prefs->mqtt_analyzer_us_enabled, sizeof(_prefs->mqtt_analyzer_us_enabled)); // 344
|
||||
file.write((uint8_t *)&_prefs->mqtt_analyzer_eu_enabled, sizeof(_prefs->mqtt_analyzer_eu_enabled)); // 345
|
||||
file.write((uint8_t *)&_prefs->mqtt_owner_public_key, sizeof(_prefs->mqtt_owner_public_key)); // 346
|
||||
file.write((uint8_t *)&_prefs->mqtt_email, sizeof(_prefs->mqtt_email)); // 347
|
||||
// 209
|
||||
|
||||
file.close();
|
||||
}
|
||||
@@ -264,162 +213,6 @@ void CommonCLI::savePrefs(FILESYSTEM* fs) {
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef WITH_MQTT_BRIDGE
|
||||
// Set default values for MQTT preferences (used when file doesn't exist or is corrupted)
|
||||
static void setMQTTPrefsDefaults(MQTTPrefs* prefs) {
|
||||
memset(prefs, 0, sizeof(MQTTPrefs));
|
||||
// Set sensible defaults matching MQTTBridge expectations
|
||||
prefs->mqtt_status_enabled = 1; // enabled by default
|
||||
prefs->mqtt_packets_enabled = 1; // enabled by default
|
||||
prefs->mqtt_raw_enabled = 0; // disabled by default
|
||||
prefs->mqtt_tx_enabled = 0; // disabled by default (RX only)
|
||||
prefs->mqtt_status_interval = 300000; // 5 minutes default
|
||||
prefs->mqtt_analyzer_us_enabled = 1; // enabled by default
|
||||
prefs->mqtt_analyzer_eu_enabled = 1; // enabled by default
|
||||
// String fields are already zero-initialized by memset
|
||||
}
|
||||
|
||||
void CommonCLI::loadMQTTPrefs(FILESYSTEM* fs) {
|
||||
// Initialize with defaults first
|
||||
setMQTTPrefsDefaults(&_mqtt_prefs);
|
||||
|
||||
if (fs->exists("/mqtt_prefs")) {
|
||||
// Load from separate MQTT prefs file
|
||||
#if defined(RP2040_PLATFORM)
|
||||
File file = fs->open("/mqtt_prefs", "r");
|
||||
#else
|
||||
File file = fs->open("/mqtt_prefs");
|
||||
#endif
|
||||
if (file) {
|
||||
// Verify file size is correct before reading
|
||||
if (file.size() >= sizeof(_mqtt_prefs)) {
|
||||
size_t bytes_read = file.read((uint8_t *)&_mqtt_prefs, sizeof(_mqtt_prefs));
|
||||
if (bytes_read != sizeof(_mqtt_prefs)) {
|
||||
// File read incomplete - reinitialize to defaults
|
||||
setMQTTPrefsDefaults(&_mqtt_prefs);
|
||||
}
|
||||
} else {
|
||||
// File too small - reinitialize to defaults
|
||||
setMQTTPrefsDefaults(&_mqtt_prefs);
|
||||
}
|
||||
file.close();
|
||||
}
|
||||
} else {
|
||||
// Migration: Try to read from old /com_prefs file if it exists
|
||||
// This handles the case where MQTT settings were previously stored in /com_prefs
|
||||
if (fs->exists("/com_prefs")) {
|
||||
#if defined(RP2040_PLATFORM)
|
||||
File file = fs->open("/com_prefs", "r");
|
||||
#else
|
||||
File file = fs->open("/com_prefs");
|
||||
#endif
|
||||
if (file) {
|
||||
// Skip to MQTT section (after advert_loc_policy at offset 161)
|
||||
// Calculate offset: we need to skip everything up to and including advert_loc_policy
|
||||
size_t offset_to_mqtt =
|
||||
sizeof(_prefs->airtime_factor) + sizeof(_prefs->node_name) + 4 + // pad
|
||||
sizeof(_prefs->node_lat) + sizeof(_prefs->node_lon) +
|
||||
sizeof(_prefs->password) + sizeof(_prefs->freq) +
|
||||
sizeof(_prefs->tx_power_dbm) + sizeof(_prefs->disable_fwd) +
|
||||
sizeof(_prefs->advert_interval) + 1 + // pad
|
||||
sizeof(_prefs->rx_delay_base) + sizeof(_prefs->tx_delay_factor) +
|
||||
sizeof(_prefs->guest_password) + sizeof(_prefs->direct_tx_delay_factor) + 4 + // pad
|
||||
sizeof(_prefs->sf) + sizeof(_prefs->cr) +
|
||||
sizeof(_prefs->allow_read_only) + sizeof(_prefs->multi_acks) +
|
||||
sizeof(_prefs->bw) + sizeof(_prefs->agc_reset_interval) + 3 + // pad
|
||||
sizeof(_prefs->flood_max) + sizeof(_prefs->flood_advert_interval) +
|
||||
sizeof(_prefs->interference_threshold) + sizeof(_prefs->bridge_enabled) +
|
||||
sizeof(_prefs->bridge_delay) + sizeof(_prefs->bridge_pkt_src) +
|
||||
sizeof(_prefs->bridge_baud) + sizeof(_prefs->bridge_channel) +
|
||||
sizeof(_prefs->bridge_secret) + 4 + // pad
|
||||
sizeof(_prefs->gps_enabled) + sizeof(_prefs->gps_interval) +
|
||||
sizeof(_prefs->advert_loc_policy);
|
||||
|
||||
// Check if file is large enough and seek succeeded
|
||||
if (file.size() >= offset_to_mqtt + sizeof(_mqtt_prefs)) {
|
||||
if (file.seek(offset_to_mqtt)) {
|
||||
size_t bytes_read = file.read((uint8_t *)&_mqtt_prefs, sizeof(_mqtt_prefs));
|
||||
if (bytes_read == sizeof(_mqtt_prefs)) {
|
||||
// Successfully migrated - save to new location for future use
|
||||
file.close();
|
||||
saveMQTTPrefs(fs);
|
||||
return; // Migration successful
|
||||
}
|
||||
}
|
||||
}
|
||||
file.close();
|
||||
// Migration failed - defaults already set, just return
|
||||
return;
|
||||
}
|
||||
}
|
||||
// No file exists and migration didn't happen - defaults already set
|
||||
}
|
||||
}
|
||||
|
||||
void CommonCLI::saveMQTTPrefs(FILESYSTEM* fs) {
|
||||
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
|
||||
fs->remove("/mqtt_prefs");
|
||||
File file = fs->open("/mqtt_prefs", FILE_O_WRITE);
|
||||
#elif defined(RP2040_PLATFORM)
|
||||
File file = fs->open("/mqtt_prefs", "w");
|
||||
#else
|
||||
File file = fs->open("/mqtt_prefs", "w", true);
|
||||
#endif
|
||||
if (file) {
|
||||
file.write((uint8_t *)&_mqtt_prefs, sizeof(_mqtt_prefs));
|
||||
file.close();
|
||||
}
|
||||
}
|
||||
|
||||
void CommonCLI::syncMQTTPrefsToNodePrefs() {
|
||||
// Copy MQTT prefs to NodePrefs so existing code can access them
|
||||
// Use StrHelper::strncpy to ensure proper null termination
|
||||
StrHelper::strncpy(_prefs->mqtt_origin, _mqtt_prefs.mqtt_origin, sizeof(_prefs->mqtt_origin));
|
||||
StrHelper::strncpy(_prefs->mqtt_iata, _mqtt_prefs.mqtt_iata, sizeof(_prefs->mqtt_iata));
|
||||
_prefs->mqtt_status_enabled = _mqtt_prefs.mqtt_status_enabled;
|
||||
_prefs->mqtt_packets_enabled = _mqtt_prefs.mqtt_packets_enabled;
|
||||
_prefs->mqtt_raw_enabled = _mqtt_prefs.mqtt_raw_enabled;
|
||||
_prefs->mqtt_tx_enabled = _mqtt_prefs.mqtt_tx_enabled;
|
||||
_prefs->mqtt_status_interval = _mqtt_prefs.mqtt_status_interval;
|
||||
StrHelper::strncpy(_prefs->wifi_ssid, _mqtt_prefs.wifi_ssid, sizeof(_prefs->wifi_ssid));
|
||||
StrHelper::strncpy(_prefs->wifi_password, _mqtt_prefs.wifi_password, sizeof(_prefs->wifi_password));
|
||||
StrHelper::strncpy(_prefs->timezone_string, _mqtt_prefs.timezone_string, sizeof(_prefs->timezone_string));
|
||||
_prefs->timezone_offset = _mqtt_prefs.timezone_offset;
|
||||
StrHelper::strncpy(_prefs->mqtt_server, _mqtt_prefs.mqtt_server, sizeof(_prefs->mqtt_server));
|
||||
_prefs->mqtt_port = _mqtt_prefs.mqtt_port;
|
||||
StrHelper::strncpy(_prefs->mqtt_username, _mqtt_prefs.mqtt_username, sizeof(_prefs->mqtt_username));
|
||||
StrHelper::strncpy(_prefs->mqtt_password, _mqtt_prefs.mqtt_password, sizeof(_prefs->mqtt_password));
|
||||
_prefs->mqtt_analyzer_us_enabled = _mqtt_prefs.mqtt_analyzer_us_enabled;
|
||||
_prefs->mqtt_analyzer_eu_enabled = _mqtt_prefs.mqtt_analyzer_eu_enabled;
|
||||
StrHelper::strncpy(_prefs->mqtt_owner_public_key, _mqtt_prefs.mqtt_owner_public_key, sizeof(_prefs->mqtt_owner_public_key));
|
||||
StrHelper::strncpy(_prefs->mqtt_email, _mqtt_prefs.mqtt_email, sizeof(_prefs->mqtt_email));
|
||||
}
|
||||
|
||||
void CommonCLI::syncNodePrefsToMQTTPrefs() {
|
||||
// Copy NodePrefs to MQTT prefs (used when saving after changes via CLI)
|
||||
// Use StrHelper::strncpy to ensure proper null termination
|
||||
StrHelper::strncpy(_mqtt_prefs.mqtt_origin, _prefs->mqtt_origin, sizeof(_mqtt_prefs.mqtt_origin));
|
||||
StrHelper::strncpy(_mqtt_prefs.mqtt_iata, _prefs->mqtt_iata, sizeof(_mqtt_prefs.mqtt_iata));
|
||||
_mqtt_prefs.mqtt_status_enabled = _prefs->mqtt_status_enabled;
|
||||
_mqtt_prefs.mqtt_packets_enabled = _prefs->mqtt_packets_enabled;
|
||||
_mqtt_prefs.mqtt_raw_enabled = _prefs->mqtt_raw_enabled;
|
||||
_mqtt_prefs.mqtt_tx_enabled = _prefs->mqtt_tx_enabled;
|
||||
_mqtt_prefs.mqtt_status_interval = _prefs->mqtt_status_interval;
|
||||
StrHelper::strncpy(_mqtt_prefs.wifi_ssid, _prefs->wifi_ssid, sizeof(_mqtt_prefs.wifi_ssid));
|
||||
StrHelper::strncpy(_mqtt_prefs.wifi_password, _prefs->wifi_password, sizeof(_mqtt_prefs.wifi_password));
|
||||
StrHelper::strncpy(_mqtt_prefs.timezone_string, _prefs->timezone_string, sizeof(_mqtt_prefs.timezone_string));
|
||||
_mqtt_prefs.timezone_offset = _prefs->timezone_offset;
|
||||
StrHelper::strncpy(_mqtt_prefs.mqtt_server, _prefs->mqtt_server, sizeof(_mqtt_prefs.mqtt_server));
|
||||
_mqtt_prefs.mqtt_port = _prefs->mqtt_port;
|
||||
StrHelper::strncpy(_mqtt_prefs.mqtt_username, _prefs->mqtt_username, sizeof(_mqtt_prefs.mqtt_username));
|
||||
StrHelper::strncpy(_mqtt_prefs.mqtt_password, _prefs->mqtt_password, sizeof(_mqtt_prefs.mqtt_password));
|
||||
_mqtt_prefs.mqtt_analyzer_us_enabled = _prefs->mqtt_analyzer_us_enabled;
|
||||
_mqtt_prefs.mqtt_analyzer_eu_enabled = _prefs->mqtt_analyzer_eu_enabled;
|
||||
StrHelper::strncpy(_mqtt_prefs.mqtt_owner_public_key, _prefs->mqtt_owner_public_key, sizeof(_mqtt_prefs.mqtt_owner_public_key));
|
||||
StrHelper::strncpy(_mqtt_prefs.mqtt_email, _prefs->mqtt_email, sizeof(_mqtt_prefs.mqtt_email));
|
||||
}
|
||||
#endif
|
||||
|
||||
#define MIN_LOCAL_ADVERT_INTERVAL 60
|
||||
|
||||
void CommonCLI::savePrefs() {
|
||||
@@ -458,10 +251,6 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
|
||||
} else {
|
||||
strcpy(reply, "ERR: clock cannot go backwards");
|
||||
}
|
||||
} else if (memcmp(command, "memory", 6) == 0) {
|
||||
sprintf(reply, "Free: %d, Min: %d, Max: %d, Queue: %d",
|
||||
ESP.getFreeHeap(), ESP.getMinFreeHeap(), ESP.getMaxAllocHeap(),
|
||||
_callbacks->getQueueSize());
|
||||
} else if (memcmp(command, "start ota", 9) == 0) {
|
||||
if (!_board->startOTAUpdate(_prefs->node_name, reply)) {
|
||||
strcpy(reply, "Error");
|
||||
@@ -610,49 +399,14 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
|
||||
sprintf(reply, "> %s", _prefs->mqtt_status_enabled ? "on" : "off");
|
||||
} else if (memcmp(config, "mqtt.packets", 12) == 0) {
|
||||
sprintf(reply, "> %s", _prefs->mqtt_packets_enabled ? "on" : "off");
|
||||
} else if (memcmp(config, "mqtt.raw", 8) == 0) {
|
||||
sprintf(reply, "> %s", _prefs->mqtt_raw_enabled ? "on" : "off");
|
||||
} else if (memcmp(config, "mqtt.tx", 7) == 0) {
|
||||
sprintf(reply, "> %s", _prefs->mqtt_tx_enabled ? "on" : "off");
|
||||
} else if (memcmp(config, "mqtt.raw", 8) == 0) {
|
||||
sprintf(reply, "> %s", _prefs->mqtt_raw_enabled ? "on" : "off");
|
||||
} else if (memcmp(config, "mqtt.interval", 13) == 0) {
|
||||
// Display interval in minutes (rounded)
|
||||
uint32_t minutes = (_prefs->mqtt_status_interval + 29999) / 60000; // Round up
|
||||
sprintf(reply, "> %u minutes (%lu ms)", minutes, _prefs->mqtt_status_interval);
|
||||
} else if (memcmp(config, "mqtt.server", 11) == 0) {
|
||||
sprintf(reply, "> %s", _prefs->mqtt_server);
|
||||
} else if (memcmp(config, "mqtt.port", 9) == 0) {
|
||||
sprintf(reply, "> %d", _prefs->mqtt_port);
|
||||
} else if (memcmp(config, "mqtt.username", 13) == 0) {
|
||||
sprintf(reply, "> %s", _prefs->mqtt_username);
|
||||
} else if (memcmp(config, "mqtt.password", 13) == 0) {
|
||||
sprintf(reply, "> %s", _prefs->mqtt_password);
|
||||
sprintf(reply, "> %d", (uint32_t)_prefs->mqtt_status_interval);
|
||||
} else if (memcmp(config, "wifi.ssid", 9) == 0) {
|
||||
sprintf(reply, "> %s", _prefs->wifi_ssid);
|
||||
} else if (memcmp(config, "wifi.pwd", 8) == 0) {
|
||||
sprintf(reply, "> %s", _prefs->wifi_password);
|
||||
} else if (memcmp(config, "timezone", 8) == 0) {
|
||||
sprintf(reply, "> %s", _prefs->timezone_string);
|
||||
} else if (memcmp(config, "timezone.offset", 15) == 0) {
|
||||
sprintf(reply, "> %d", _prefs->timezone_offset);
|
||||
} else if (memcmp(config, "mqtt.analyzer.us", 17) == 0) {
|
||||
sprintf(reply, "> %s", _prefs->mqtt_analyzer_us_enabled ? "on" : "off");
|
||||
} else if (memcmp(config, "mqtt.analyzer.eu", 17) == 0) {
|
||||
sprintf(reply, "> %s", _prefs->mqtt_analyzer_eu_enabled ? "on" : "off");
|
||||
} else if (sender_timestamp == 0 && memcmp(config, "mqtt.owner", 10) == 0) { // from serial command line only
|
||||
if (_prefs->mqtt_owner_public_key[0] != '\0') {
|
||||
sprintf(reply, "> %s", _prefs->mqtt_owner_public_key);
|
||||
} else {
|
||||
strcpy(reply, "> (not set)");
|
||||
}
|
||||
} else if (sender_timestamp == 0 && memcmp(config, "mqtt.email", 10) == 0) { // from serial command line only
|
||||
if (_prefs->mqtt_email[0] != '\0') {
|
||||
sprintf(reply, "> %s", _prefs->mqtt_email);
|
||||
} else {
|
||||
strcpy(reply, "> (not set)");
|
||||
}
|
||||
} else if (memcmp(config, "mqtt.config.valid", 17) == 0) {
|
||||
bool valid = MQTTBridge::isConfigValid(_prefs);
|
||||
sprintf(reply, "> %s", valid ? "valid" : "invalid");
|
||||
#endif
|
||||
} else {
|
||||
sprintf(reply, "??: %s", config);
|
||||
@@ -863,24 +617,18 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
|
||||
_prefs->mqtt_packets_enabled = memcmp(&config[13], "on", 2) == 0;
|
||||
savePrefs();
|
||||
strcpy(reply, "OK");
|
||||
} else if (memcmp(config, "mqtt.raw ", 9) == 0) {
|
||||
_prefs->mqtt_raw_enabled = memcmp(&config[9], "on", 2) == 0;
|
||||
savePrefs();
|
||||
strcpy(reply, "OK");
|
||||
} else if (memcmp(config, "mqtt.tx ", 8) == 0) {
|
||||
_prefs->mqtt_tx_enabled = memcmp(&config[8], "on", 2) == 0;
|
||||
savePrefs();
|
||||
strcpy(reply, "OK");
|
||||
} else if (memcmp(config, "mqtt.interval ", 14) == 0) {
|
||||
uint32_t minutes = _atoi(&config[14]);
|
||||
if (minutes >= 1 && minutes <= 60) { // 1 minute to 60 minutes
|
||||
_prefs->mqtt_status_interval = minutes * 60000; // Convert minutes to milliseconds
|
||||
} else if (memcmp(config, "mqtt.raw ", 9) == 0) {
|
||||
_prefs->mqtt_raw_enabled = memcmp(&config[9], "on", 2) == 0;
|
||||
savePrefs();
|
||||
strcpy(reply, "OK");
|
||||
} else if (memcmp(config, "mqtt.interval ", 15) == 0) {
|
||||
uint32_t interval = _atoi(&config[15]);
|
||||
if (interval >= 1000 && interval <= 3600000) { // 1 second to 1 hour
|
||||
_prefs->mqtt_status_interval = interval;
|
||||
savePrefs();
|
||||
// Restart bridge to pick up new interval value
|
||||
_callbacks->restartBridge();
|
||||
sprintf(reply, "OK - interval set to %u minutes (%lu ms), bridge restarted", minutes, _prefs->mqtt_status_interval);
|
||||
strcpy(reply, "OK");
|
||||
} else {
|
||||
strcpy(reply, "Error: interval must be between 1-60 minutes");
|
||||
strcpy(reply, "Error: interval must be between 1000-3600000 ms");
|
||||
}
|
||||
} else if (memcmp(config, "wifi.ssid ", 10) == 0) {
|
||||
StrHelper::strncpy(_prefs->wifi_ssid, &config[10], sizeof(_prefs->wifi_ssid));
|
||||
@@ -890,77 +638,6 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
|
||||
StrHelper::strncpy(_prefs->wifi_password, &config[9], sizeof(_prefs->wifi_password));
|
||||
savePrefs();
|
||||
strcpy(reply, "OK");
|
||||
} else if (memcmp(config, "timezone ", 9) == 0) {
|
||||
StrHelper::strncpy(_prefs->timezone_string, &config[9], sizeof(_prefs->timezone_string));
|
||||
savePrefs();
|
||||
strcpy(reply, "OK");
|
||||
} else if (memcmp(config, "timezone.offset ", 16) == 0) {
|
||||
int8_t offset = _atoi(&config[16]);
|
||||
if (offset >= -12 && offset <= 14) {
|
||||
_prefs->timezone_offset = offset;
|
||||
savePrefs();
|
||||
strcpy(reply, "OK");
|
||||
} else {
|
||||
strcpy(reply, "Error: timezone offset must be between -12 and +14");
|
||||
}
|
||||
} else if (memcmp(config, "mqtt.server ", 12) == 0) {
|
||||
StrHelper::strncpy(_prefs->mqtt_server, &config[12], sizeof(_prefs->mqtt_server));
|
||||
savePrefs();
|
||||
strcpy(reply, "OK");
|
||||
} else if (memcmp(config, "mqtt.port ", 10) == 0) {
|
||||
int port = atoi(&config[10]);
|
||||
if (port > 0 && port <= 65535) {
|
||||
_prefs->mqtt_port = port;
|
||||
savePrefs();
|
||||
strcpy(reply, "OK");
|
||||
} else {
|
||||
strcpy(reply, "Error: port must be between 1 and 65535");
|
||||
}
|
||||
} else if (memcmp(config, "mqtt.username ", 14) == 0) {
|
||||
StrHelper::strncpy(_prefs->mqtt_username, &config[14], sizeof(_prefs->mqtt_username));
|
||||
savePrefs();
|
||||
strcpy(reply, "OK");
|
||||
} else if (memcmp(config, "mqtt.password ", 14) == 0) {
|
||||
StrHelper::strncpy(_prefs->mqtt_password, &config[14], sizeof(_prefs->mqtt_password));
|
||||
savePrefs();
|
||||
strcpy(reply, "OK");
|
||||
} else if (memcmp(config, "mqtt.analyzer.us ", 17) == 0) {
|
||||
_prefs->mqtt_analyzer_us_enabled = memcmp(&config[17], "on", 2) == 0;
|
||||
savePrefs();
|
||||
strcpy(reply, "OK");
|
||||
} else if (memcmp(config, "mqtt.analyzer.eu ", 17) == 0) {
|
||||
_prefs->mqtt_analyzer_eu_enabled = memcmp(&config[17], "on", 2) == 0;
|
||||
savePrefs();
|
||||
strcpy(reply, "OK");
|
||||
} else if (memcmp(config, "mqtt.owner ", 11) == 0) {
|
||||
// Validate that it's a valid hex string of the correct length (64 hex chars = 32 bytes)
|
||||
const char* owner_key = &config[11];
|
||||
int key_len = strlen(owner_key);
|
||||
if (key_len == 64) {
|
||||
// Validate hex characters
|
||||
bool valid = true;
|
||||
for (int i = 0; i < key_len; i++) {
|
||||
if (!((owner_key[i] >= '0' && owner_key[i] <= '9') ||
|
||||
(owner_key[i] >= 'A' && owner_key[i] <= 'F') ||
|
||||
(owner_key[i] >= 'a' && owner_key[i] <= 'f'))) {
|
||||
valid = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (valid) {
|
||||
StrHelper::strncpy(_prefs->mqtt_owner_public_key, owner_key, sizeof(_prefs->mqtt_owner_public_key));
|
||||
savePrefs();
|
||||
strcpy(reply, "OK");
|
||||
} else {
|
||||
strcpy(reply, "Error: invalid hex characters in public key");
|
||||
}
|
||||
} else {
|
||||
strcpy(reply, "Error: public key must be 64 hex characters (32 bytes)");
|
||||
}
|
||||
} else if (memcmp(config, "mqtt.email ", 11) == 0) {
|
||||
StrHelper::strncpy(_prefs->mqtt_email, &config[11], sizeof(_prefs->mqtt_email));
|
||||
savePrefs();
|
||||
strcpy(reply, "OK");
|
||||
#endif
|
||||
} else {
|
||||
sprintf(reply, "unknown config: %s", config);
|
||||
@@ -1128,3 +805,170 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
|
||||
strcpy(reply, "Unknown command");
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef WITH_MQTT_BRIDGE
|
||||
// Set default values for MQTT preferences (used when file doesn't exist or is corrupted)
|
||||
static void setMQTTPrefsDefaults(MQTTPrefs* prefs) {
|
||||
memset(prefs, 0, sizeof(MQTTPrefs));
|
||||
// Set sensible defaults matching MQTTBridge expectations
|
||||
StrHelper::strncpy(prefs->mqtt_origin, "MeshCore-Repeater", sizeof(prefs->mqtt_origin));
|
||||
StrHelper::strncpy(prefs->mqtt_iata, "XXX", sizeof(prefs->mqtt_iata));
|
||||
prefs->mqtt_status_enabled = 1;
|
||||
prefs->mqtt_packets_enabled = 1;
|
||||
prefs->mqtt_raw_enabled = 0;
|
||||
prefs->mqtt_tx_enabled = 0;
|
||||
prefs->mqtt_status_interval = 300000; // 5 minutes
|
||||
StrHelper::strncpy(prefs->wifi_ssid, "ssid_here", sizeof(prefs->wifi_ssid));
|
||||
StrHelper::strncpy(prefs->wifi_password, "password_here", sizeof(prefs->wifi_password));
|
||||
StrHelper::strncpy(prefs->timezone_string, "America/Los_Angeles", sizeof(prefs->timezone_string));
|
||||
prefs->timezone_offset = -8;
|
||||
prefs->mqtt_analyzer_us_enabled = 1;
|
||||
prefs->mqtt_analyzer_eu_enabled = 1;
|
||||
}
|
||||
|
||||
void CommonCLI::loadMQTTPrefs(FILESYSTEM* fs) {
|
||||
// Initialize with zeros
|
||||
memset(&_mqtt_prefs, 0, sizeof(_mqtt_prefs));
|
||||
|
||||
if (fs->exists("/mqtt_prefs")) {
|
||||
// Load from separate MQTT prefs file
|
||||
#if defined(RP2040_PLATFORM)
|
||||
File file = fs->open("/mqtt_prefs", "r");
|
||||
#else
|
||||
File file = fs->open("/mqtt_prefs");
|
||||
#endif
|
||||
if (file) {
|
||||
// Verify file size is correct before reading
|
||||
if (file.size() >= sizeof(_mqtt_prefs)) {
|
||||
size_t bytes_read = file.read((uint8_t *)&_mqtt_prefs, sizeof(_mqtt_prefs));
|
||||
if (bytes_read != sizeof(_mqtt_prefs)) {
|
||||
// File read incomplete - reinitialize to defaults
|
||||
setMQTTPrefsDefaults(&_mqtt_prefs);
|
||||
}
|
||||
} else {
|
||||
// File too small - reinitialize to defaults
|
||||
setMQTTPrefsDefaults(&_mqtt_prefs);
|
||||
}
|
||||
file.close();
|
||||
} else {
|
||||
// File open failed - use defaults
|
||||
setMQTTPrefsDefaults(&_mqtt_prefs);
|
||||
}
|
||||
} else {
|
||||
// Migration: Try to read from old /com_prefs file if it exists
|
||||
// This handles the case where MQTT settings were previously stored in /com_prefs
|
||||
if (fs->exists("/com_prefs")) {
|
||||
#if defined(RP2040_PLATFORM)
|
||||
File file = fs->open("/com_prefs", "r");
|
||||
#else
|
||||
File file = fs->open("/com_prefs");
|
||||
#endif
|
||||
if (file) {
|
||||
// Skip to MQTT section (after discovery_mod_timestamp)
|
||||
// Calculate offset: we need to skip everything up to and including discovery_mod_timestamp
|
||||
// Note: discovery_mod_timestamp was added in upstream dev branch
|
||||
size_t offset_to_mqtt =
|
||||
sizeof(_prefs->airtime_factor) + sizeof(_prefs->node_name) + 4 + // pad
|
||||
sizeof(_prefs->node_lat) + sizeof(_prefs->node_lon) +
|
||||
sizeof(_prefs->password) + sizeof(_prefs->freq) +
|
||||
sizeof(_prefs->tx_power_dbm) + sizeof(_prefs->disable_fwd) +
|
||||
sizeof(_prefs->advert_interval) + 1 + // pad
|
||||
sizeof(_prefs->rx_delay_base) + sizeof(_prefs->tx_delay_factor) +
|
||||
sizeof(_prefs->guest_password) + sizeof(_prefs->direct_tx_delay_factor) + 4 + // pad
|
||||
sizeof(_prefs->sf) + sizeof(_prefs->cr) +
|
||||
sizeof(_prefs->allow_read_only) + sizeof(_prefs->multi_acks) +
|
||||
sizeof(_prefs->bw) + sizeof(_prefs->agc_reset_interval) + 3 + // pad
|
||||
sizeof(_prefs->flood_max) + sizeof(_prefs->flood_advert_interval) +
|
||||
sizeof(_prefs->interference_threshold) + sizeof(_prefs->bridge_enabled) +
|
||||
sizeof(_prefs->bridge_delay) + sizeof(_prefs->bridge_pkt_src) +
|
||||
sizeof(_prefs->bridge_baud) + sizeof(_prefs->bridge_channel) +
|
||||
sizeof(_prefs->bridge_secret) + 4 + // pad
|
||||
sizeof(_prefs->gps_enabled) + sizeof(_prefs->gps_interval) +
|
||||
sizeof(_prefs->advert_loc_policy) +
|
||||
sizeof(_prefs->discovery_mod_timestamp); // Added in upstream dev branch
|
||||
|
||||
// Check if file is large enough and seek succeeded
|
||||
if (file.size() >= offset_to_mqtt + sizeof(_mqtt_prefs)) {
|
||||
if (file.seek(offset_to_mqtt)) {
|
||||
size_t bytes_read = file.read((uint8_t *)&_mqtt_prefs, sizeof(_mqtt_prefs));
|
||||
if (bytes_read == sizeof(_mqtt_prefs)) {
|
||||
// Successfully migrated - save to new location for future use
|
||||
file.close();
|
||||
saveMQTTPrefs(fs);
|
||||
return; // Migration successful
|
||||
}
|
||||
}
|
||||
}
|
||||
file.close();
|
||||
// Migration failed - defaults already set, just return
|
||||
return;
|
||||
}
|
||||
}
|
||||
// No file exists and migration didn't happen - defaults already set
|
||||
setMQTTPrefsDefaults(&_mqtt_prefs);
|
||||
}
|
||||
}
|
||||
|
||||
void CommonCLI::saveMQTTPrefs(FILESYSTEM* fs) {
|
||||
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
|
||||
fs->remove("/mqtt_prefs");
|
||||
File file = fs->open("/mqtt_prefs", FILE_O_WRITE);
|
||||
#elif defined(RP2040_PLATFORM)
|
||||
File file = fs->open("/mqtt_prefs", "w");
|
||||
#else
|
||||
File file = fs->open("/mqtt_prefs", "w", true);
|
||||
#endif
|
||||
if (file) {
|
||||
file.write((uint8_t *)&_mqtt_prefs, sizeof(_mqtt_prefs));
|
||||
file.close();
|
||||
}
|
||||
}
|
||||
|
||||
void CommonCLI::syncMQTTPrefsToNodePrefs() {
|
||||
// Copy MQTT prefs to NodePrefs so existing code can access them
|
||||
// Use StrHelper::strncpy to ensure proper null termination
|
||||
StrHelper::strncpy(_prefs->mqtt_origin, _mqtt_prefs.mqtt_origin, sizeof(_prefs->mqtt_origin));
|
||||
StrHelper::strncpy(_prefs->mqtt_iata, _mqtt_prefs.mqtt_iata, sizeof(_prefs->mqtt_iata));
|
||||
_prefs->mqtt_status_enabled = _mqtt_prefs.mqtt_status_enabled;
|
||||
_prefs->mqtt_packets_enabled = _mqtt_prefs.mqtt_packets_enabled;
|
||||
_prefs->mqtt_raw_enabled = _mqtt_prefs.mqtt_raw_enabled;
|
||||
_prefs->mqtt_tx_enabled = _mqtt_prefs.mqtt_tx_enabled;
|
||||
_prefs->mqtt_status_interval = _mqtt_prefs.mqtt_status_interval;
|
||||
StrHelper::strncpy(_prefs->wifi_ssid, _mqtt_prefs.wifi_ssid, sizeof(_prefs->wifi_ssid));
|
||||
StrHelper::strncpy(_prefs->wifi_password, _mqtt_prefs.wifi_password, sizeof(_prefs->wifi_password));
|
||||
StrHelper::strncpy(_prefs->timezone_string, _mqtt_prefs.timezone_string, sizeof(_prefs->timezone_string));
|
||||
_prefs->timezone_offset = _mqtt_prefs.timezone_offset;
|
||||
StrHelper::strncpy(_prefs->mqtt_server, _mqtt_prefs.mqtt_server, sizeof(_prefs->mqtt_server));
|
||||
_prefs->mqtt_port = _mqtt_prefs.mqtt_port;
|
||||
StrHelper::strncpy(_prefs->mqtt_username, _mqtt_prefs.mqtt_username, sizeof(_prefs->mqtt_username));
|
||||
StrHelper::strncpy(_prefs->mqtt_password, _mqtt_prefs.mqtt_password, sizeof(_prefs->mqtt_password));
|
||||
_prefs->mqtt_analyzer_us_enabled = _mqtt_prefs.mqtt_analyzer_us_enabled;
|
||||
_prefs->mqtt_analyzer_eu_enabled = _mqtt_prefs.mqtt_analyzer_eu_enabled;
|
||||
StrHelper::strncpy(_prefs->mqtt_owner_public_key, _mqtt_prefs.mqtt_owner_public_key, sizeof(_prefs->mqtt_owner_public_key));
|
||||
StrHelper::strncpy(_prefs->mqtt_email, _mqtt_prefs.mqtt_email, sizeof(_prefs->mqtt_email));
|
||||
}
|
||||
|
||||
void CommonCLI::syncNodePrefsToMQTTPrefs() {
|
||||
// Copy NodePrefs to MQTT prefs (used when saving after changes via CLI)
|
||||
// Use StrHelper::strncpy to ensure proper null termination
|
||||
StrHelper::strncpy(_mqtt_prefs.mqtt_origin, _prefs->mqtt_origin, sizeof(_mqtt_prefs.mqtt_origin));
|
||||
StrHelper::strncpy(_mqtt_prefs.mqtt_iata, _prefs->mqtt_iata, sizeof(_mqtt_prefs.mqtt_iata));
|
||||
_mqtt_prefs.mqtt_status_enabled = _prefs->mqtt_status_enabled;
|
||||
_mqtt_prefs.mqtt_packets_enabled = _prefs->mqtt_packets_enabled;
|
||||
_mqtt_prefs.mqtt_raw_enabled = _prefs->mqtt_raw_enabled;
|
||||
_mqtt_prefs.mqtt_tx_enabled = _prefs->mqtt_tx_enabled;
|
||||
_mqtt_prefs.mqtt_status_interval = _prefs->mqtt_status_interval;
|
||||
StrHelper::strncpy(_mqtt_prefs.wifi_ssid, _prefs->wifi_ssid, sizeof(_mqtt_prefs.wifi_ssid));
|
||||
StrHelper::strncpy(_mqtt_prefs.wifi_password, _prefs->wifi_password, sizeof(_mqtt_prefs.wifi_password));
|
||||
StrHelper::strncpy(_mqtt_prefs.timezone_string, _prefs->timezone_string, sizeof(_mqtt_prefs.timezone_string));
|
||||
_mqtt_prefs.timezone_offset = _prefs->timezone_offset;
|
||||
StrHelper::strncpy(_mqtt_prefs.mqtt_server, _prefs->mqtt_server, sizeof(_mqtt_prefs.mqtt_server));
|
||||
_mqtt_prefs.mqtt_port = _prefs->mqtt_port;
|
||||
StrHelper::strncpy(_mqtt_prefs.mqtt_username, _prefs->mqtt_username, sizeof(_mqtt_prefs.mqtt_username));
|
||||
StrHelper::strncpy(_mqtt_prefs.mqtt_password, _prefs->mqtt_password, sizeof(_mqtt_prefs.mqtt_password));
|
||||
_mqtt_prefs.mqtt_analyzer_us_enabled = _prefs->mqtt_analyzer_us_enabled;
|
||||
_mqtt_prefs.mqtt_analyzer_eu_enabled = _prefs->mqtt_analyzer_eu_enabled;
|
||||
StrHelper::strncpy(_mqtt_prefs.mqtt_owner_public_key, _prefs->mqtt_owner_public_key, sizeof(_mqtt_prefs.mqtt_owner_public_key));
|
||||
StrHelper::strncpy(_mqtt_prefs.mqtt_email, _prefs->mqtt_email, sizeof(_mqtt_prefs.mqtt_email));
|
||||
}
|
||||
#endif
|
||||
@@ -1,8 +1,6 @@
|
||||
#include "MQTTMessageBuilder.h"
|
||||
#include <ArduinoJson.h>
|
||||
#include <time.h>
|
||||
#include <Timezone.h>
|
||||
#include "MeshCore.h"
|
||||
|
||||
int MQTTMessageBuilder::buildStatusMessage(
|
||||
const char* origin,
|
||||
@@ -14,16 +12,9 @@ int MQTTMessageBuilder::buildStatusMessage(
|
||||
const char* status,
|
||||
const char* timestamp,
|
||||
char* buffer,
|
||||
size_t buffer_size,
|
||||
int battery_mv,
|
||||
int uptime_secs,
|
||||
int errors,
|
||||
int queue_len,
|
||||
int noise_floor,
|
||||
int tx_air_secs,
|
||||
int rx_air_secs
|
||||
size_t buffer_size
|
||||
) {
|
||||
DynamicJsonDocument doc(768); // Increased size to accommodate stats
|
||||
DynamicJsonDocument doc(512);
|
||||
JsonObject root = doc.to<JsonObject>();
|
||||
|
||||
root["status"] = status;
|
||||
@@ -35,34 +26,6 @@ int MQTTMessageBuilder::buildStatusMessage(
|
||||
root["radio"] = radio;
|
||||
root["client_version"] = client_version;
|
||||
|
||||
// Add stats object if any stats are provided
|
||||
if (battery_mv >= 0 || uptime_secs >= 0 || errors >= 0 || queue_len >= 0 ||
|
||||
noise_floor > -999 || tx_air_secs >= 0 || rx_air_secs >= 0) {
|
||||
JsonObject stats = root.createNestedObject("stats");
|
||||
|
||||
if (battery_mv >= 0) {
|
||||
stats["battery_mv"] = battery_mv;
|
||||
}
|
||||
if (uptime_secs >= 0) {
|
||||
stats["uptime_secs"] = uptime_secs;
|
||||
}
|
||||
if (errors >= 0) {
|
||||
stats["errors"] = errors;
|
||||
}
|
||||
if (queue_len >= 0) {
|
||||
stats["queue_len"] = queue_len;
|
||||
}
|
||||
if (noise_floor > -999) {
|
||||
stats["noise_floor"] = noise_floor;
|
||||
}
|
||||
if (tx_air_secs >= 0) {
|
||||
stats["tx_air_secs"] = tx_air_secs;
|
||||
}
|
||||
if (rx_air_secs >= 0) {
|
||||
stats["rx_air_secs"] = rx_air_secs;
|
||||
}
|
||||
}
|
||||
|
||||
size_t len = serializeJson(root, buffer, buffer_size);
|
||||
return (len > 0 && len < buffer_size) ? len : 0;
|
||||
}
|
||||
@@ -86,15 +49,7 @@ int MQTTMessageBuilder::buildPacketMessage(
|
||||
char* buffer,
|
||||
size_t buffer_size
|
||||
) {
|
||||
// Size-adaptive JSON document: estimate needed size based on raw hex string length
|
||||
// Base JSON overhead ~200 bytes, raw hex can be up to 510 chars (255 bytes packet)
|
||||
// Use minimum needed to reduce memory fragmentation
|
||||
size_t raw_len = raw ? strlen(raw) : 0;
|
||||
size_t doc_size = 256 + raw_len + 128; // Base + raw + overhead, but cap at buffer_size
|
||||
if (doc_size > buffer_size) doc_size = buffer_size;
|
||||
if (doc_size < 512) doc_size = 512; // Minimum for small packets
|
||||
if (doc_size > 2048) doc_size = 2048; // Maximum cap
|
||||
DynamicJsonDocument doc(doc_size);
|
||||
DynamicJsonDocument doc(1024);
|
||||
JsonObject root = doc.to<JsonObject>();
|
||||
|
||||
root["origin"] = origin;
|
||||
@@ -147,55 +102,45 @@ int MQTTMessageBuilder::buildPacketJSON(
|
||||
bool is_tx,
|
||||
const char* origin,
|
||||
const char* origin_id,
|
||||
Timezone* timezone,
|
||||
char* buffer,
|
||||
size_t buffer_size
|
||||
) {
|
||||
if (!packet) return 0;
|
||||
|
||||
// Get current device time (should be UTC since system timezone is set to UTC)
|
||||
// Get current device time
|
||||
time_t now = time(nullptr);
|
||||
struct tm* timeinfo = localtime(&now);
|
||||
|
||||
// Convert to local time using timezone library (for timestamp field only)
|
||||
time_t local_time = timezone ? timezone->toLocal(now) : now;
|
||||
struct tm* local_timeinfo = localtime(&local_time);
|
||||
|
||||
// Format timestamp in ISO 8601 format (LOCAL TIME)
|
||||
// Format timestamp in ISO 8601 format
|
||||
char timestamp[32];
|
||||
if (local_timeinfo) {
|
||||
strftime(timestamp, sizeof(timestamp), "%Y-%m-%dT%H:%M:%S.000000", local_timeinfo);
|
||||
if (timeinfo) {
|
||||
strftime(timestamp, sizeof(timestamp), "%Y-%m-%dT%H:%M:%S.000000", timeinfo);
|
||||
} else {
|
||||
strcpy(timestamp, "2024-01-01T12:00:00.000000");
|
||||
}
|
||||
|
||||
// Get UTC time (since system timezone is UTC, time() returns UTC)
|
||||
struct tm* utc_timeinfo = gmtime(&now);
|
||||
|
||||
// Format time and date (ALWAYS UTC)
|
||||
// Format time and date
|
||||
char time_str[16];
|
||||
char date_str[16];
|
||||
if (utc_timeinfo) {
|
||||
strftime(time_str, sizeof(time_str), "%H:%M:%S", utc_timeinfo);
|
||||
strftime(date_str, sizeof(date_str), "%d/%m/%Y", utc_timeinfo);
|
||||
if (timeinfo) {
|
||||
strftime(time_str, sizeof(time_str), "%H:%M:%S", timeinfo);
|
||||
strftime(date_str, sizeof(date_str), "%d/%m/%Y", timeinfo);
|
||||
} else {
|
||||
strcpy(time_str, "12:00:00");
|
||||
strcpy(date_str, "01/01/2024");
|
||||
}
|
||||
|
||||
// Convert packet to hex
|
||||
// MAX_TRANS_UNIT is 255 bytes, hex = 510 chars, but allow for larger with headers
|
||||
char raw_hex[1024];
|
||||
char raw_hex[512];
|
||||
packetToHex(packet, raw_hex, sizeof(raw_hex));
|
||||
|
||||
// Get packet characteristics
|
||||
int packet_type = packet->getPayloadType();
|
||||
const char* route_str = getRouteTypeString(packet->isRouteDirect() ? 1 : 0);
|
||||
|
||||
// Create proper packet hash using MeshCore's calculatePacketHash method
|
||||
// Create hash (simplified - use first 8 bytes of packet)
|
||||
char hash_str[17];
|
||||
uint8_t packet_hash[MAX_HASH_SIZE];
|
||||
packet->calculatePacketHash(packet_hash);
|
||||
bytesToHex(packet_hash, MAX_HASH_SIZE, hash_str, sizeof(hash_str));
|
||||
bytesToHex(packet->payload, min(8, (int)packet->payload_len), hash_str, sizeof(hash_str));
|
||||
|
||||
// Build path string for direct packets
|
||||
char path_str[128] = "";
|
||||
@@ -220,93 +165,10 @@ int MQTTMessageBuilder::buildPacketJSON(
|
||||
);
|
||||
}
|
||||
|
||||
int MQTTMessageBuilder::buildPacketJSONFromRaw(
|
||||
const uint8_t* raw_data,
|
||||
int raw_len,
|
||||
mesh::Packet* packet,
|
||||
bool is_tx,
|
||||
const char* origin,
|
||||
const char* origin_id,
|
||||
float snr,
|
||||
float rssi,
|
||||
Timezone* timezone,
|
||||
char* buffer,
|
||||
size_t buffer_size
|
||||
) {
|
||||
if (!packet || !raw_data || raw_len <= 0) return 0;
|
||||
|
||||
// Get current device time (should be UTC since system timezone is set to UTC)
|
||||
time_t now = time(nullptr);
|
||||
|
||||
// Convert to local time using timezone library (for timestamp field only)
|
||||
time_t local_time = timezone ? timezone->toLocal(now) : now;
|
||||
struct tm* local_timeinfo = localtime(&local_time);
|
||||
|
||||
// Format timestamp in ISO 8601 format (LOCAL TIME)
|
||||
char timestamp[32];
|
||||
if (local_timeinfo) {
|
||||
strftime(timestamp, sizeof(timestamp), "%Y-%m-%dT%H:%M:%S.000000", local_timeinfo);
|
||||
} else {
|
||||
strcpy(timestamp, "2024-01-01T12:00:00.000000");
|
||||
}
|
||||
|
||||
// Get UTC time (since system timezone is UTC, time() returns UTC)
|
||||
struct tm* utc_timeinfo = gmtime(&now);
|
||||
|
||||
// Format time and date (ALWAYS UTC)
|
||||
char time_str[16];
|
||||
char date_str[16];
|
||||
if (utc_timeinfo) {
|
||||
strftime(time_str, sizeof(time_str), "%H:%M:%S", utc_timeinfo);
|
||||
strftime(date_str, sizeof(date_str), "%d/%m/%Y", utc_timeinfo);
|
||||
} else {
|
||||
strcpy(time_str, "12:00:00");
|
||||
strcpy(date_str, "01/01/2024");
|
||||
}
|
||||
|
||||
// Convert raw radio data to hex (this includes radio headers)
|
||||
// MAX_TRANS_UNIT is 255 bytes, hex = 510 chars, but allow for larger with headers
|
||||
char raw_hex[1024];
|
||||
bytesToHex(raw_data, raw_len, raw_hex, sizeof(raw_hex));
|
||||
|
||||
// Get packet characteristics from the parsed packet
|
||||
int packet_type = packet->getPayloadType();
|
||||
const char* route_str = getRouteTypeString(packet->isRouteDirect() ? 1 : 0);
|
||||
|
||||
// Create proper packet hash using MeshCore's calculatePacketHash method
|
||||
char hash_str[17];
|
||||
uint8_t packet_hash[MAX_HASH_SIZE];
|
||||
packet->calculatePacketHash(packet_hash);
|
||||
bytesToHex(packet_hash, MAX_HASH_SIZE, hash_str, sizeof(hash_str));
|
||||
|
||||
// Build path string for direct packets
|
||||
char path_str[128] = "";
|
||||
if (packet->isRouteDirect() && packet->path_len > 0) {
|
||||
// Simplified path representation
|
||||
snprintf(path_str, sizeof(path_str), "path_len_%d", packet->path_len);
|
||||
}
|
||||
|
||||
return buildPacketMessage(
|
||||
origin, origin_id, timestamp,
|
||||
is_tx ? "tx" : "rx",
|
||||
time_str, date_str,
|
||||
raw_len, // Use actual raw radio data length
|
||||
packet_type, route_str,
|
||||
packet->payload_len,
|
||||
raw_hex,
|
||||
snr, // Use actual SNR from radio
|
||||
rssi, // Use actual RSSI from radio
|
||||
hash_str,
|
||||
packet->isRouteDirect() ? path_str : nullptr,
|
||||
buffer, buffer_size
|
||||
);
|
||||
}
|
||||
|
||||
int MQTTMessageBuilder::buildRawJSON(
|
||||
mesh::Packet* packet,
|
||||
const char* origin,
|
||||
const char* origin_id,
|
||||
Timezone* timezone,
|
||||
char* buffer,
|
||||
size_t buffer_size
|
||||
) {
|
||||
@@ -314,10 +176,7 @@ int MQTTMessageBuilder::buildRawJSON(
|
||||
|
||||
// Get current device time
|
||||
time_t now = time(nullptr);
|
||||
|
||||
// Convert to local time using timezone library
|
||||
time_t local_time = timezone ? timezone->toLocal(now) : now;
|
||||
struct tm* timeinfo = localtime(&local_time);
|
||||
struct tm* timeinfo = localtime(&now);
|
||||
|
||||
// Format timestamp in ISO 8601 format
|
||||
char timestamp[32];
|
||||
@@ -328,8 +187,7 @@ int MQTTMessageBuilder::buildRawJSON(
|
||||
}
|
||||
|
||||
// Convert packet to hex
|
||||
// MAX_TRANS_UNIT is 255, so max hex size is 510 chars + null = 511 bytes
|
||||
char raw_hex[1024];
|
||||
char raw_hex[512];
|
||||
packetToHex(packet, raw_hex, sizeof(raw_hex));
|
||||
|
||||
return buildRawMessage(origin, origin_id, timestamp, raw_hex, buffer, buffer_size);
|
||||
@@ -391,15 +249,21 @@ void MQTTMessageBuilder::bytesToHex(const uint8_t* data, size_t len, char* hex,
|
||||
}
|
||||
|
||||
void MQTTMessageBuilder::packetToHex(mesh::Packet* packet, char* hex, size_t hex_size) {
|
||||
// Serialize full on-air/wire format using Packet::writeTo()
|
||||
// This includes header, transport codes (if present), path_len, path, and payload
|
||||
uint8_t raw_buf[512];
|
||||
uint8_t raw_len = packet->writeTo(raw_buf);
|
||||
if (raw_len == 0 || raw_len > sizeof(raw_buf)) return;
|
||||
// Convert entire packet to hex string
|
||||
size_t total_len = packet->path_len + packet->payload_len + 2;
|
||||
if (hex_size < total_len * 2 + 1) return;
|
||||
|
||||
// Check if hex buffer is large enough (2 hex chars per byte + null terminator)
|
||||
if (hex_size < (size_t)raw_len * 2 + 1) return;
|
||||
size_t offset = 0;
|
||||
|
||||
// Convert serialized packet to hex
|
||||
bytesToHex(raw_buf, raw_len, hex, hex_size);
|
||||
// Add path data
|
||||
if (packet->path_len > 0) {
|
||||
bytesToHex(packet->path, packet->path_len, hex + offset, hex_size - offset);
|
||||
offset += packet->path_len * 2;
|
||||
}
|
||||
|
||||
// Add payload data
|
||||
if (packet->payload_len > 0) {
|
||||
bytesToHex(packet->payload, packet->payload_len, hex + offset, hex_size - offset);
|
||||
offset += packet->payload_len * 2;
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,6 @@
|
||||
#include "MeshCore.h"
|
||||
#include <ArduinoJson.h>
|
||||
#include <Mesh.h>
|
||||
#include <Timezone.h>
|
||||
|
||||
/**
|
||||
* @brief Utility class for building MQTT JSON messages
|
||||
@@ -30,13 +29,6 @@ public:
|
||||
* @param timestamp ISO 8601 timestamp
|
||||
* @param buffer Output buffer for JSON string
|
||||
* @param buffer_size Size of output buffer
|
||||
* @param battery_mv Battery voltage in millivolts (optional, -1 to omit)
|
||||
* @param uptime_secs Uptime in seconds (optional, -1 to omit)
|
||||
* @param errors Error flags (optional, -1 to omit)
|
||||
* @param queue_len Queue length (optional, -1 to omit)
|
||||
* @param noise_floor Noise floor in dBm (optional, -999 to omit)
|
||||
* @param tx_air_secs TX air time in seconds (optional, -1 to omit)
|
||||
* @param rx_air_secs RX air time in seconds (optional, -1 to omit)
|
||||
* @return Length of JSON string, or 0 on error
|
||||
*/
|
||||
static int buildStatusMessage(
|
||||
@@ -49,14 +41,7 @@ public:
|
||||
const char* status,
|
||||
const char* timestamp,
|
||||
char* buffer,
|
||||
size_t buffer_size,
|
||||
int battery_mv = -1,
|
||||
int uptime_secs = -1,
|
||||
int errors = -1,
|
||||
int queue_len = -1,
|
||||
int noise_floor = -999,
|
||||
int tx_air_secs = -1,
|
||||
int rx_air_secs = -1
|
||||
size_t buffer_size
|
||||
);
|
||||
|
||||
/**
|
||||
@@ -137,21 +122,6 @@ public:
|
||||
bool is_tx,
|
||||
const char* origin,
|
||||
const char* origin_id,
|
||||
Timezone* timezone,
|
||||
char* buffer,
|
||||
size_t buffer_size
|
||||
);
|
||||
|
||||
static int buildPacketJSONFromRaw(
|
||||
const uint8_t* raw_data,
|
||||
int raw_len,
|
||||
mesh::Packet* packet,
|
||||
bool is_tx,
|
||||
const char* origin,
|
||||
const char* origin_id,
|
||||
float snr,
|
||||
float rssi,
|
||||
Timezone* timezone,
|
||||
char* buffer,
|
||||
size_t buffer_size
|
||||
);
|
||||
@@ -170,7 +140,6 @@ public:
|
||||
mesh::Packet* packet,
|
||||
const char* origin,
|
||||
const char* origin_id,
|
||||
Timezone* timezone,
|
||||
char* buffer,
|
||||
size_t buffer_size
|
||||
);
|
||||
|
||||
+126
-1510
File diff suppressed because it is too large.
Load diff
@@ -2,12 +2,10 @@
|
||||
|
||||
#include "MeshCore.h"
|
||||
#include "helpers/bridges/BridgeBase.h"
|
||||
#include <PsychicMqttClient.h>
|
||||
#include <PubSubClient.h>
|
||||
#include <WiFi.h>
|
||||
#include <NTPClient.h>
|
||||
#include <WiFiUdp.h>
|
||||
#include <Timezone.h>
|
||||
#include "helpers/JWTHelper.h"
|
||||
|
||||
#if defined(MQTT_DEBUG) && defined(ARDUINO)
|
||||
#include <Arduino.h>
|
||||
@@ -45,7 +43,8 @@
|
||||
*/
|
||||
class MQTTBridge : public BridgeBase {
|
||||
private:
|
||||
PsychicMqttClient* _mqtt_client;
|
||||
PubSubClient* _mqtt_client;
|
||||
WiFiClient* _wifi_client;
|
||||
|
||||
// MQTT broker configuration
|
||||
struct MQTTBroker {
|
||||
@@ -71,11 +70,9 @@ private:
|
||||
char _device_id[65]; // Device public key (hex string)
|
||||
char _firmware_version[64]; // Firmware version string
|
||||
char _board_model[64]; // Board model string
|
||||
char _build_date[32]; // Build date string
|
||||
bool _status_enabled;
|
||||
bool _packets_enabled;
|
||||
bool _raw_enabled;
|
||||
bool _tx_enabled;
|
||||
unsigned long _last_status_publish;
|
||||
unsigned long _status_interval;
|
||||
|
||||
@@ -84,15 +81,9 @@ private:
|
||||
mesh::Packet* packet;
|
||||
unsigned long timestamp;
|
||||
bool is_tx;
|
||||
// Store raw radio data with each packet to avoid it being overwritten
|
||||
uint8_t raw_data[256];
|
||||
int raw_len;
|
||||
float snr;
|
||||
float rssi;
|
||||
bool has_raw_data;
|
||||
};
|
||||
|
||||
static const int MAX_QUEUE_SIZE = 10;
|
||||
static const int MAX_QUEUE_SIZE = 50;
|
||||
QueuedPacket _packet_queue[MAX_QUEUE_SIZE];
|
||||
int _queue_head;
|
||||
int _queue_tail;
|
||||
@@ -104,76 +95,17 @@ private:
|
||||
unsigned long _last_ntp_sync;
|
||||
bool _ntp_synced;
|
||||
|
||||
// Timezone handling
|
||||
Timezone* _timezone;
|
||||
|
||||
// Raw radio data storage
|
||||
uint8_t _last_raw_data[256];
|
||||
int _last_raw_len;
|
||||
float _last_snr;
|
||||
float _last_rssi;
|
||||
unsigned long _last_raw_timestamp;
|
||||
|
||||
// Let's Mesh Analyzer support
|
||||
bool _analyzer_us_enabled;
|
||||
bool _analyzer_eu_enabled;
|
||||
char _auth_token_us[768]; // JWT token for US server authentication (increased for owner/client fields)
|
||||
char _auth_token_eu[768]; // JWT token for EU server authentication (increased for owner/client fields)
|
||||
char _analyzer_username[70]; // Username in format v1_{UPPERCASE_PUBLIC_KEY}
|
||||
|
||||
// Token expiration tracking
|
||||
unsigned long _token_us_expires_at;
|
||||
unsigned long _token_eu_expires_at;
|
||||
unsigned long _last_token_renewal_attempt_us;
|
||||
unsigned long _last_token_renewal_attempt_eu;
|
||||
unsigned long _last_reconnect_attempt_us;
|
||||
unsigned long _last_reconnect_attempt_eu;
|
||||
|
||||
// Status publish retry tracking
|
||||
unsigned long _last_status_retry; // Track last retry attempt (separate from successful publish)
|
||||
static const unsigned long STATUS_RETRY_INTERVAL = 30000; // Retry every 30 seconds if failed
|
||||
|
||||
// Device identity for JWT token creation
|
||||
mesh::LocalIdentity *_identity;
|
||||
|
||||
// PsychicMqttClient instances for different brokers
|
||||
PsychicMqttClient* _analyzer_us_client;
|
||||
PsychicMqttClient* _analyzer_eu_client;
|
||||
|
||||
// Configuration validation state
|
||||
bool _config_valid;
|
||||
|
||||
// Throttle logging for disconnected broker messages
|
||||
unsigned long _last_no_broker_log;
|
||||
static const unsigned long NO_BROKER_LOG_INTERVAL = 30000; // Log every 30 seconds max
|
||||
|
||||
// Throttle logging for analyzer client disconnected messages
|
||||
unsigned long _last_analyzer_us_log;
|
||||
unsigned long _last_analyzer_eu_log;
|
||||
static const unsigned long ANALYZER_LOG_INTERVAL = 30000; // Log every 30 seconds max
|
||||
|
||||
// Optional pointers for collecting stats internally (set by mesh if available)
|
||||
mesh::Dispatcher* _dispatcher; // For air times and errors
|
||||
mesh::Radio* _radio; // For noise floor
|
||||
mesh::MainBoard* _board; // For battery voltage
|
||||
mesh::MillisecondClock* _ms; // For uptime
|
||||
|
||||
// Internal methods
|
||||
void connectToBrokers();
|
||||
void processPacketQueue();
|
||||
bool publishStatus(); // Returns true if status was successfully published
|
||||
void publishPacket(mesh::Packet* packet, bool is_tx,
|
||||
const uint8_t* raw_data = nullptr, int raw_len = 0,
|
||||
float snr = 0.0f, float rssi = 0.0f);
|
||||
void publishStatus();
|
||||
void publishPacket(mesh::Packet* packet, bool is_tx);
|
||||
void publishRaw(mesh::Packet* packet);
|
||||
void queuePacket(mesh::Packet* packet, bool is_tx);
|
||||
void dequeuePacket();
|
||||
bool isAnyBrokerConnected();
|
||||
void setBrokerDefaults();
|
||||
void syncTimeWithNTP();
|
||||
Timezone* createTimezoneFromString(const char* tz_string);
|
||||
bool isMQTTConfigValid();
|
||||
bool isIATAValid() const; // Check if IATA code is configured
|
||||
|
||||
public:
|
||||
/**
|
||||
@@ -182,9 +114,8 @@ public:
|
||||
* @param prefs Node preferences for configuration settings
|
||||
* @param mgr PacketManager for allocating and queuing packets
|
||||
* @param rtc RTCClock for timestamping debug messages
|
||||
* @param identity Device identity for JWT token creation
|
||||
*/
|
||||
MQTTBridge(NodePrefs *prefs, mesh::PacketManager *mgr, mesh::RTCClock *rtc, mesh::LocalIdentity *identity);
|
||||
MQTTBridge(NodePrefs *prefs, mesh::PacketManager *mgr, mesh::RTCClock *rtc);
|
||||
|
||||
/**
|
||||
* Initializes the MQTT bridge
|
||||
@@ -204,28 +135,6 @@ public:
|
||||
*/
|
||||
void end() override;
|
||||
|
||||
/**
|
||||
* Checks if MQTT configuration is valid
|
||||
*
|
||||
* @return true if all required MQTT settings are properly configured
|
||||
*/
|
||||
bool isConfigValid() const;
|
||||
|
||||
/**
|
||||
* Static method to validate MQTT configuration from preferences
|
||||
*
|
||||
* @param prefs Node preferences containing MQTT settings
|
||||
* @return true if all required MQTT settings are properly configured
|
||||
*/
|
||||
static bool isConfigValid(const NodePrefs* prefs);
|
||||
|
||||
/**
|
||||
* Check if MQTT bridge is ready to operate (has WiFi credentials)
|
||||
*
|
||||
* @return true if WiFi credentials are configured and bridge can connect
|
||||
*/
|
||||
bool isReady() const;
|
||||
|
||||
/**
|
||||
* Main loop handler
|
||||
* - Maintains broker connections
|
||||
@@ -298,34 +207,6 @@ public:
|
||||
*/
|
||||
void setBoardModel(const char* board_model);
|
||||
|
||||
/**
|
||||
* Set build date for client version
|
||||
*
|
||||
* @param build_date Build date string
|
||||
*/
|
||||
void setBuildDate(const char* build_date);
|
||||
|
||||
/**
|
||||
* Stores raw radio data for MQTT messages
|
||||
*
|
||||
* @param raw_data Raw radio transmission data
|
||||
* @param len Length of raw data
|
||||
* @param snr Signal-to-noise ratio
|
||||
* @param rssi Received signal strength indicator
|
||||
*/
|
||||
void storeRawRadioData(const uint8_t* raw_data, int len, float snr, float rssi);
|
||||
|
||||
// Let's Mesh Analyzer methods
|
||||
void setupAnalyzerServers();
|
||||
bool createAuthToken();
|
||||
void publishToAnalyzerServers(const char* topic, const char* payload, bool retained = false);
|
||||
|
||||
// PsychicMqttClient WebSocket methods
|
||||
void setupAnalyzerClients();
|
||||
void maintainAnalyzerConnections();
|
||||
void publishToAnalyzerClient(PsychicMqttClient* client, const char* topic, const char* payload, bool retained = false);
|
||||
void publishStatusToAnalyzerClient(PsychicMqttClient* client, const char* server_name);
|
||||
|
||||
/**
|
||||
* Enable/disable message types
|
||||
*
|
||||
@@ -348,33 +229,6 @@ public:
|
||||
* @return Number of queued packets
|
||||
*/
|
||||
int getQueueSize() const;
|
||||
|
||||
/**
|
||||
* Set optional pointers for stats collection.
|
||||
* If these are set, stats will be collected automatically when publishing status.
|
||||
*
|
||||
* @param dispatcher Dispatcher (or Mesh*) for air times and errors
|
||||
* @param radio Radio for noise floor
|
||||
* @param board MainBoard for battery voltage
|
||||
* @param ms MillisecondClock for uptime
|
||||
*/
|
||||
void setStatsSources(mesh::Dispatcher* dispatcher, mesh::Radio* radio,
|
||||
mesh::MainBoard* board, mesh::MillisecondClock* ms);
|
||||
|
||||
private:
|
||||
/**
|
||||
* Generate client version string in format "meshcore/{firmware_version}"
|
||||
* Memory-efficient: writes to provided buffer, no dynamic allocation
|
||||
*
|
||||
* @param buffer Buffer to write the client version string to
|
||||
* @param buffer_size Size of the buffer (must be at least 64 bytes)
|
||||
*/
|
||||
void getClientVersion(char* buffer, size_t buffer_size) const;
|
||||
|
||||
/**
|
||||
* Log memory status for debugging
|
||||
*/
|
||||
void logMemoryStatus();
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -51,9 +51,6 @@ build_flags =
|
||||
build_src_filter = ${Heltec_lora32_v3.build_src_filter}
|
||||
+<helpers/ui/SSD1306Display.cpp>
|
||||
+<../examples/simple_repeater>
|
||||
-<helpers/bridges/MQTTBridge.cpp>
|
||||
-<helpers/MQTTMessageBuilder.cpp>
|
||||
-<helpers/JWTHelper.cpp>
|
||||
lib_deps =
|
||||
${Heltec_lora32_v3.lib_deps}
|
||||
${esp32_ota.lib_deps}
|
||||
@@ -105,13 +102,13 @@ lib_deps =
|
||||
${Heltec_lora32_v3.lib_deps}
|
||||
${esp32_ota.lib_deps}
|
||||
|
||||
[env:Heltec_v3_repeater_observer_mqtt]
|
||||
[env:Heltec_v3_repeater_bridge_mqtt]
|
||||
extends = Heltec_lora32_v3
|
||||
upload_port = /dev/cu.usbserial-4
|
||||
upload_port = /dev/cu.usbserial-2
|
||||
build_flags =
|
||||
${Heltec_lora32_v3.build_flags}
|
||||
-D DISPLAY_CLASS=SSD1306Display
|
||||
-D ADVERT_NAME='"MQTT Observer"'
|
||||
-D ADVERT_NAME='"MQTT Bridge"'
|
||||
-D ADVERT_LAT=0.0
|
||||
-D ADVERT_LON=0.0
|
||||
-D ADMIN_PASSWORD='"password"'
|
||||
@@ -122,27 +119,19 @@ build_flags =
|
||||
-D MQTT_DEBUG=1
|
||||
-D MESH_PACKET_LOGGING=1
|
||||
-D MESH_DEBUG=1
|
||||
-D CONFIG_MBEDTLS_CERTIFICATE_BUNDLE=y
|
||||
# -D WIFI_SSID='"ssid"'
|
||||
# -D WIFI_PWD='"password"'
|
||||
# -D MQTT_SERVER='"your-mqtt-broker.com"'
|
||||
# -D MQTT_PORT=1883
|
||||
# -D MQTT_USERNAME='"your-username"'
|
||||
# -D MQTT_PASSWORD='"your-password"'
|
||||
build_src_filter = ${Heltec_lora32_v3.build_src_filter}
|
||||
+<helpers/bridges/MQTTBridge.cpp>
|
||||
+<helpers/MQTTMessageBuilder.cpp>
|
||||
+<helpers/JWTHelper.cpp>
|
||||
+<helpers/ui/SSD1306Display.cpp>
|
||||
+<../examples/simple_repeater>
|
||||
lib_deps =
|
||||
${Heltec_lora32_v3.lib_deps}
|
||||
${esp32_ota.lib_deps}
|
||||
elims/PsychicMqttClient@^0.2.4
|
||||
bblanchon/ArduinoJson
|
||||
arduino-libraries/NTPClient
|
||||
JChristensen/Timezone
|
||||
paulstoffregen/Time
|
||||
${Heltec_lora32_v3.lib_deps}
|
||||
${esp32_ota.lib_deps}
|
||||
knolleary/PubSubClient @ ^2.8
|
||||
bblanchon/ArduinoJson @ 6.17.3
|
||||
arduino-libraries/NTPClient
|
||||
|
||||
[env:Heltec_v3_room_server]
|
||||
extends = Heltec_lora32_v3
|
||||
@@ -163,38 +152,6 @@ lib_deps =
|
||||
${Heltec_lora32_v3.lib_deps}
|
||||
${esp32_ota.lib_deps}
|
||||
|
||||
[env:Heltec_v3_room_server_observer_mqtt]
|
||||
extends = Heltec_lora32_v3
|
||||
build_flags =
|
||||
${Heltec_lora32_v3.build_flags}
|
||||
-D DISPLAY_CLASS=SSD1306Display
|
||||
-D ADVERT_NAME='"Heltec Room Observer"'
|
||||
-D ADVERT_LAT=0.0
|
||||
-D ADVERT_LON=0.0
|
||||
-D ADMIN_PASSWORD='"password"'
|
||||
-D ROOM_PASSWORD='"hello"'
|
||||
-D WITH_MQTT_BRIDGE=1
|
||||
-D MAX_MQTT_BROKERS=3
|
||||
-D MQTT_MAX_PACKET_SIZE=1024
|
||||
; -D MQTT_DEBUG=1
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
-D CONFIG_MBEDTLS_CERTIFICATE_BUNDLE=y
|
||||
build_src_filter = ${Heltec_lora32_v3.build_src_filter}
|
||||
+<helpers/ui/SSD1306Display.cpp>
|
||||
+<helpers/bridges/MQTTBridge.cpp>
|
||||
+<helpers/MQTTMessageBuilder.cpp>
|
||||
+<helpers/JWTHelper.cpp>
|
||||
+<../examples/simple_room_server>
|
||||
lib_deps =
|
||||
${Heltec_lora32_v3.lib_deps}
|
||||
${esp32_ota.lib_deps}
|
||||
elims/PsychicMqttClient@^0.2.4
|
||||
bblanchon/ArduinoJson
|
||||
arduino-libraries/NTPClient
|
||||
JChristensen/Timezone
|
||||
paulstoffregen/Time
|
||||
|
||||
[env:Heltec_v3_terminal_chat]
|
||||
extends = Heltec_lora32_v3
|
||||
build_flags =
|
||||
@@ -261,8 +218,8 @@ build_flags =
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D DISPLAY_CLASS=SSD1306Display
|
||||
-D WIFI_DEBUG_LOGGING=1
|
||||
# -D WIFI_SSID='"ssid"'
|
||||
# -D WIFI_PWD='"password"'
|
||||
-D WIFI_SSID='"REMOVED_SSID"'
|
||||
-D WIFI_PWD='"REMOVED_PASSWORD"'
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${Heltec_lora32_v3.build_src_filter}
|
||||
@@ -308,9 +265,6 @@ build_flags =
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${Heltec_lora32_v3.build_src_filter}
|
||||
+<../examples/simple_repeater>
|
||||
-<helpers/bridges/MQTTBridge.cpp>
|
||||
-<helpers/MQTTMessageBuilder.cpp>
|
||||
-<helpers/JWTHelper.cpp>
|
||||
lib_deps =
|
||||
${Heltec_lora32_v3.lib_deps}
|
||||
${esp32_ota.lib_deps}
|
||||
|
||||
Reference in new issue
Block a user