Add environmental telemetry collection (temperature, humidity, barometric
pressure, voltage) from both the repeater node (over LoRa) and companion
node (local serial). Telemetry is stored in the same EAV metrics table
with `telemetry.` prefix.
Key changes:
- Add TELEMETRY_ENABLED feature flag (defaults to OFF)
- Add telemetry-specific timeout/retry settings
- Create shared telemetry.py module with extract_lpp_from_payload()
and extract_telemetry_metrics() helpers
- Handle MeshCore API dict payload format: {'pubkey_pre': '...', 'lpp': [...]}
- Repeater: store status metrics BEFORE attempting telemetry (LoRa reliability)
- Companion: merge telemetry into single DB write (serial is reliable)
- Telemetry failures do NOT affect circuit breaker state
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
5.7 KiB
MeshCore Firmware Response Reference
This document captures the actual response structures from MeshCore firmware commands. Use this as a reference when updating collectors or adding new metrics.
Last verified: 2025-12-29
Companion Node
The companion node is queried via USB serial using multiple commands.
get_stats_core()
Core system statistics.
{
'battery_mv': 3895, # Battery voltage in millivolts
'uptime_secs': 126, # Uptime in seconds
'errors': 0, # Error count
'queue_len': 0 # Message queue length
}
get_stats_packets()
Packet counters (cumulative since boot).
{
'recv': 20, # Total packets received
'sent': 0, # Total packets sent
'flood_tx': 0, # Flood packets transmitted
'direct_tx': 0, # Direct packets transmitted
'flood_rx': 20, # Flood packets received
'direct_rx': 0 # Direct packets received
}
get_stats_radio()
Radio/RF statistics.
{
'noise_floor': -113, # Noise floor in dBm
'last_rssi': -123, # RSSI of last received packet (dBm)
'last_snr': -8.5, # SNR of last received packet (dB)
'tx_air_secs': 0, # Cumulative TX airtime in seconds
'rx_air_secs': 11 # Cumulative RX airtime in seconds
}
get_bat()
Battery/storage info (note: not the main battery source for metrics).
{
'level': 4436, # Battery level (unclear unit)
'used_kb': 256, # Used storage in KB
'total_kb': 1404 # Total storage in KB
}
get_contacts()
Returns a dict keyed by public key. Count via len(payload).
Repeater Node
The repeater is queried over LoRa using the binary protocol command req_status_sync().
req_status_sync(contact)
Returns a single dict with all status fields.
{
'bat': 4047, # Battery voltage in millivolts
'uptime': 1441998, # Uptime in seconds
'last_rssi': -63, # RSSI of last received packet (dBm)
'last_snr': 12.5, # SNR of last received packet (dB)
'noise_floor': -118, # Noise floor in dBm
'tx_queue_len': 0, # TX queue depth
'nb_recv': 221311, # Total packets received (counter)
'nb_sent': 93993, # Total packets sent (counter)
'airtime': 64461, # TX airtime in seconds (counter)
'rx_airtime': 146626, # RX airtime in seconds (counter)
'flood_dups': 59799, # Duplicate flood packets (counter)
'direct_dups': 8, # Duplicate direct packets (counter)
'sent_flood': 92207, # Flood packets transmitted (counter)
'recv_flood': 216960, # Flood packets received (counter)
'sent_direct': 1786, # Direct packets transmitted (counter)
'recv_direct': 4328 # Direct packets received (counter)
}
Telemetry Data
Environmental telemetry is requested via req_telemetry_sync(contact) and returns
Cayenne LPP formatted sensor data. This requires TELEMETRY_ENABLED=1 and a sensor
board attached to the repeater.
Payload Format
Both req_telemetry_sync() and get_self_telemetry() return a dict containing the
LPP data list and a public key prefix:
{
'pubkey_pre': 'a5c14f5244d6',
'lpp': [
{'channel': 0, 'type': 'temperature', 'value': 23.5},
{'channel': 0, 'type': 'humidity', 'value': 45.2},
]
}
The extract_lpp_from_payload() helper in src/meshmon/telemetry.py handles
extracting the lpp list from this wrapper format.
req_telemetry_sync(contact)
Returns sensor readings from a remote node in Cayenne LPP format:
[
{'channel': 0, 'type': 'temperature', 'value': 23.5},
{'channel': 0, 'type': 'humidity', 'value': 45.2},
{'channel': 0, 'type': 'barometer', 'value': 1013.25},
{'channel': 1, 'type': 'gps', 'value': {'latitude': 51.5, 'longitude': -0.1, 'altitude': 10}},
]
Common sensor types:
| Type | Unit | Description |
|---|---|---|
temperature |
Celsius | Temperature reading |
humidity |
% | Relative humidity |
barometer |
hPa/mbar | Barometric pressure |
voltage |
V | Voltage reading |
gps |
compound | GPS with latitude, longitude, altitude |
Stored as:
telemetry.temperature.0- Temperature on channel 0telemetry.humidity.0- Humidity on channel 0telemetry.gps.1.latitude- GPS latitude on channel 1
Notes:
- Requires environmental sensor board (BME280, BME680, etc.) on repeater
- Channel number distinguishes multiple sensors of the same type
- Not all repeaters have environmental sensors attached
- Telemetry collection does not affect circuit breaker state
- Telemetry failures are logged as warnings and do not block status collection
get_self_telemetry()
Returns self telemetry from the companion node's attached sensors.
Same Cayenne LPP format as req_telemetry_sync().
[
{'channel': 0, 'type': 'temperature', 'value': 23.5},
{'channel': 0, 'type': 'humidity', 'value': 45.2},
]
Notes:
- Requires environmental sensor board attached to companion
- Returns empty list if no sensors attached
- Uses same format as repeater telemetry
Derived Metrics
These are computed at query time, not stored:
| Metric | Source | Computation |
|---|---|---|
bat_pct |
bat or battery_mv |
voltage_to_percentage(mv / 1000) using 18650 Li-ion discharge curve |
Notes
- Counters reset to 0 on device reboot
- Millivolts to volts: Divide by 1000 (e.g.,
bat: 4047→4.047V) - Repeater fields come from a single
req_status_sync()call - Companion fields are spread across multiple
get_stats_*()calls