Initial release: MeshCore Stats monitoring system

A Python-based monitoring system for MeshCore LoRa mesh networks.
Collects metrics from companion and repeater nodes, stores them in
a SQLite database, and generates a static website with interactive
SVG charts and statistics.

Features:
- Data collection from local companion and remote repeater nodes
- SQLite database with EAV schema for flexible metric storage
- Interactive SVG chart generation with matplotlib
- Static HTML site with day/week/month/year views
- Monthly and yearly statistics reports (HTML, TXT, JSON)
- Light and dark theme support
- Circuit breaker for unreliable LoRa connections
- Battery percentage calculation from 18650 discharge curves
- Automated releases via release-please

Live demo: https://meshcore.jorijn.com
This commit is contained in:
Jorijn Schrijvershof
2026-01-04 19:37:57 +01:00
commit 0f8b0a3492
45 changed files with 9252 additions and 0 deletions

120
docs/firmware-responses.md Normal file
View File

@@ -0,0 +1,120 @@
# 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.
```python
{
'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).
```python
{
'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.
```python
{
'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).
```python
{
'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.
```python
{
'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)
}
```
---
## 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