2026-03-27 19:16:49 +01:00
2026-03-27 19:16:49 +01:00

MeshCore Stats

A monitoring system for MeshCore LoRa mesh networks. Collects metrics from companion and repeater nodes, stores them in SQLite, and generates a static dashboard with interactive charts.

Live demo: meshcore.jorijn.com

MeshCore Stats Dashboard MeshCore Stats Reports

Quick Start

Linux only - macOS and Windows users see Platform Notes first.

# Clone and configure
git clone https://github.com/jorijn/meshcore-stats.git
cd meshcore-stats
cp meshcore.conf.example meshcore.conf
# Edit meshcore.conf with your repeater name and password

# Create data directories (container runs as UID 1000)
mkdir -p data/state out
sudo chown -R 1000:1000 data out

# Add your serial device
cat > docker-compose.override.yml << 'EOF'
services:
  meshcore-stats:
    devices:
      - /dev/ttyACM0:/dev/ttyACM0
EOF

# Start
docker compose up -d

# Verify it's working. The various collection and render jobs will trigger after a few minutes.
docker compose ps
docker compose logs meshcore-stats | head -20

# View dashboard at http://localhost:8080

Features

  • Data Collection - Metrics from local companion and remote repeater nodes
  • Interactive Charts - SVG charts with day/week/month/year views and tooltips
  • Auto Telemetry Charts - Repeater telemetry.* metrics are charted automatically when telemetry is enabled (telemetry.voltage.* excluded)
  • Statistics Reports - Monthly and yearly report generation
  • Light/Dark Theme - Automatic theme switching based on system preference

Prerequisites

  • Docker and Docker Compose V2
  • MeshCore companion node connected via USB serial
  • Remote repeater node reachable via LoRa from the companion

Resource requirements: ~100MB memory, ~100MB disk per year of data.

Installation

1. Clone the Repository

git clone https://github.com/jorijn/meshcore-stats.git
cd meshcore-stats

2. Configure

Copy the example configuration and edit it:

cp meshcore.conf.example meshcore.conf

Minimal required settings:

# Repeater identity (required)
REPEATER_NAME=Your Repeater Name
REPEATER_PASSWORD=your-admin-password

# Display names
REPEATER_DISPLAY_NAME=My Repeater
COMPANION_DISPLAY_NAME=My Companion

See meshcore.conf.example for all available options.

Optional telemetry display settings:

# Enable environmental telemetry collection from repeater
TELEMETRY_ENABLED=1

# Telemetry display units only (DB values stay unchanged)
DISPLAY_UNIT_SYSTEM=metric   # or imperial

3. Create Data Directories

mkdir -p data/state out
sudo chown -R 1000:1000 data out

The container runs as UID 1000, so directories must be writable by this user. If sudo is not available, you can relaxed the permissions using chmod 777 data out, but this is less secure.

4. Configure Serial Device

Create docker-compose.override.yml to specify your serial device:

services:
  meshcore-stats:
    devices:
      - /dev/ttyACM0:/dev/ttyACM0

Ensure your user has serial port access:

sudo usermod -aG dialout $USER
# Log out and back in for changes to take effect

5. Start the Containers

docker compose up -d

After the various collection and render jobs has run, the dashboard will be available at http://localhost:8080.

Verify Installation

# Check container status
docker compose ps

# View logs
docker compose logs -f meshcore-stats

Common Docker Commands

# View real-time logs
docker compose logs -f meshcore-stats

# Restart after configuration changes
docker compose restart meshcore-stats

# Update to latest version (database migrations are automatic)
docker compose pull && docker compose up -d

# Stop all containers
docker compose down

# Backup database
cp data/state/metrics.db data/state/metrics.db.backup

Note

: docker compose down preserves your data. Use docker compose down -v only if you want to delete everything.

Manual Installation (Alternative)

For environments where Docker is not available.

Requirements

  • Python 3.11+ (3.14 recommended)
  • SQLite3
  • uv

Setup

cd meshcore-stats
uv venv
source .venv/bin/activate
uv sync
cp meshcore.conf.example meshcore.conf
# Edit meshcore.conf with your settings

Cron Setup

Add to your crontab (crontab -e):

MESHCORE=/path/to/meshcore-stats

# Companion: every minute
* * * * * cd $MESHCORE && .venv/bin/python scripts/collect_companion.py

# Repeater: every 15 minutes
1,16,31,46 * * * * cd $MESHCORE && .venv/bin/python scripts/collect_repeater.py

# Charts: every 5 minutes
*/5 * * * * cd $MESHCORE && .venv/bin/python scripts/render_charts.py

# Site: every 5 minutes
*/5 * * * * cd $MESHCORE && .venv/bin/python scripts/render_site.py

# Reports: daily at midnight
0 0 * * * cd $MESHCORE && .venv/bin/python scripts/render_reports.py

Serve the out/ directory with any web server.

Platform Notes

Linux

Docker can access USB serial devices directly. Add your device to docker-compose.override.yml:

services:
  meshcore-stats:
    devices:
      - /dev/ttyACM0:/dev/ttyACM0

Common device paths:

  • /dev/ttyACM0 - Arduino/native USB
  • /dev/ttyUSB0 - USB-to-serial adapters
macOS

Docker Desktop for macOS runs in a Linux VM and cannot directly access USB serial devices.

Option 1: TCP Bridge (Recommended)

Expose the serial port over TCP using socat:

# Install socat
brew install socat

# Bridge serial to TCP (run in background)
socat TCP-LISTEN:5000,fork,reuseaddr OPEN:/dev/cu.usbserial-0001,rawer,nonblock,ispeed=115200,ospeed=115200

Configure in meshcore.conf:

MESH_TRANSPORT=tcp
MESH_TCP_HOST=host.docker.internal
MESH_TCP_PORT=5000

Option 2: Native Installation

Use the manual installation method with cron instead of Docker.

Windows (WSL2)

WSL2 and Docker Desktop for Windows cannot directly access COM ports.

Use the TCP bridge approach (similar to macOS) or native installation.

Configuration Reference

Variable Default Description
Repeater Identity
REPEATER_NAME required Advertised name to find in contacts
REPEATER_PASSWORD required Admin password for repeater
REPEATER_KEY_PREFIX - Alternative to REPEATER_NAME: hex prefix of public key
Connection
MESH_TRANSPORT serial Transport type: serial, tcp, or ble
MESH_SERIAL_PORT auto Serial port path
MESH_TCP_HOST localhost TCP host (for TCP transport)
MESH_TCP_PORT 5000 TCP port (for TCP transport)
Display
REPEATER_DISPLAY_NAME Repeater Node Name shown in UI
COMPANION_DISPLAY_NAME Companion Node Name shown in UI
REPEATER_HARDWARE LoRa Repeater Hardware model for sidebar
COMPANION_HARDWARE LoRa Node Hardware model for sidebar
Location
REPORT_LOCATION_NAME Your Location Full location for reports
REPORT_LAT 0.0 Latitude
REPORT_LON 0.0 Longitude
REPORT_ELEV 0.0 Elevation
Radio (display only)
RADIO_FREQUENCY 869.618 MHz Frequency shown in sidebar
RADIO_BANDWIDTH 62.5 kHz Bandwidth
RADIO_SPREAD_FACTOR SF8 Spread factor

See meshcore.conf.example for all options with regional radio presets.

Troubleshooting

Symptom Cause Solution
"Permission denied" on serial port User not in dialout group sudo usermod -aG dialout $USER then re-login
Repeater shows "offline" status No data or circuit breaker tripped Check logs; delete data/state/repeater_circuit.json to reset
Empty charts Not enough data collected Wait for 2+ collection cycles
Container exits immediately Missing or invalid configuration Verify meshcore.conf exists and has required values
"No serial ports found" Device not connected/detected Check ls /dev/tty* and device permissions
Device path changed after reboot USB enumeration order changed Update path in docker-compose.override.yml or use udev rules
"database is locked" errors Maintenance script running Wait for completion; check if VACUUM is running

Debug Logging

# Enable debug mode in meshcore.conf
MESH_DEBUG=1

# View detailed logs
docker compose logs -f meshcore-stats

Circuit Breaker

The repeater collector uses a circuit breaker to avoid spamming LoRa when the repeater is unreachable. After multiple failures, it enters a cooldown period (default: 1 hour).

To reset manually:

rm data/state/repeater_circuit.json
docker compose restart meshcore-stats

Architecture

┌─────────────────┐     LoRa      ┌─────────────────┐
│   Companion     │◄─────────────►│    Repeater     │
│  (USB Serial)   │               │   (Remote)      │
└────────┬────────┘               └─────────────────┘
         │
         │ Serial/TCP
         ▼
┌─────────────────┐
│   Docker Host   │
│  ┌───────────┐  │
│  │ meshcore- │  │     ┌─────────┐
│  │   stats   │──┼────►│  nginx  │──► :8080
│  └───────────┘  │     └─────────┘
│        │        │
│        ▼        │
│   SQLite + SVG  │
└─────────────────┘

The system runs two containers:

  • meshcore-stats: Collects data on schedule (Ofelia) and generates charts
  • nginx: Serves the static dashboard

Documentation

License

MIT

Public Instances

Public MeshCore Stats installations. Want to add yours? Open a pull request!

URL Hardware Location
meshcore.jorijn.com SenseCAP Solar Node P1 Pro + 6.5dBi Mikrotik antenna Oosterhout, The Netherlands
Description
No description provided
Readme MIT 5.6 MiB
Languages
Python 93.3%
CSS 2.6%
JavaScript 2%
HTML 1.4%
Dockerfile 0.6%
Other 0.1%