Operations Guide
Mesh Node Monitoring & Data Capture
How to interface with Meshtastic and MeshCore nodes to capture, store, and relay mesh data
Every mesh node is a sensor. It knows who’s online, how strong their signal is, what the battery voltage reads, and where packets came from. This guide covers how to tap into that data β over a cable, over the air, or over your local network β and pipe it into a computer for logging, dashboards, or integration with other systems.
We cover both Meshtastic and MeshCore protocols, since each has different tooling and data interfaces.
Quick Reference: Interface Methods
| Method | Meshtastic | MeshCore | Best For |
|---|---|---|---|
| USB Serial | β CLI, Python API, serial log | β CLI commands, KISS modem | Direct wired access, initial setup |
| Bluetooth (BLE) | β Mobile apps, Python CLI | β Companion firmware + apps | Phone-to-node, wireless monitoring |
| WiFi / TCP | β
--host CLI, web client | β WiFi companion firmware | Headless nodes on LAN |
| MQTT | β Built-in gateway module | β Not built-in (use bridge) | Multi-node fleet monitoring |
| Serial Module | β Dedicated TX/RX GPIO | β Use KISS modem instead | Sensor integration, microcontroller bridges |
Part 1: Meshtastic Monitoring
USB Serial β Direct Cable Connection
The simplest monitoring method. Plug your node into a computer via USB-C, and the Meshtastic Python CLI gives you full access to device state, telemetry, and real-time packet streams.
Setup
# Install the Meshtastic CLI
pip3 install --upgrade "meshtastic[cli]"
# Verify connection (auto-detects port)
meshtastic --info
# Or specify port explicitly
meshtastic --port /dev/ttyUSB0 --info # Linux
meshtastic --port /dev/cu.usbmodem* --info # macOS
meshtastic --port COM4 --info # Windows
Live Packet Stream
The --listen flag keeps the connection open and streams all received packets as JSON to your terminal:
# Stream all packets (enables debug output)
meshtastic --listen
# Log raw serial output to a file
meshtastic --seriallog mesh-capture.txt
# Stream to file while watching in terminal
meshtastic --listen 2>&1 | tee mesh-log-$(date +%Y%m%d).txt
Query Node State
# Show all nodes the device has heard
meshtastic --nodes
# Request telemetry from a specific node
meshtastic --request-telemetry --dest '!ba4bf9d0'
# Request position from a node
meshtastic --request-position --dest '!ba4bf9d0'
# Run a traceroute to see the path
meshtastic --traceroute '!ba4bf9d0'
# Export full device config as YAML
meshtastic --export-config > node-config.yaml
Python API for Custom Monitoring
For automated data collection, use the Meshtastic Python library directly:
import meshtastic
import meshtastic.serial_interface
from pubsub import pub
def on_receive(packet, interface):
"""Called for every received packet."""
print(f"From: {packet.get('fromId', 'unknown')}")
print(f"Type: {packet.get('decoded', {}).get('portnum', 'unknown')}")
# Extract telemetry
decoded = packet.get('decoded', {})
if decoded.get('portnum') == 'TELEMETRY_APP':
telemetry = decoded.get('telemetry', {})
env = telemetry.get('environmentMetrics', {})
if env:
print(f" Temp: {env.get('temperature')}Β°C")
print(f" Humidity: {env.get('relativeHumidity')}%")
print(f" Pressure: {env.get('barometricPressure')} hPa")
device = telemetry.get('deviceMetrics', {})
if device:
print(f" Battery: {device.get('batteryLevel')}%")
print(f" Voltage: {device.get('voltage')}V")
print(f" Uptime: {device.get('uptimeSeconds')}s")
# Extract position
if decoded.get('portnum') == 'POSITION_APP':
pos = decoded.get('position', {})
print(f" Lat: {pos.get('latitude')}")
print(f" Lon: {pos.get('longitude')}")
print(f" Alt: {pos.get('altitude')}m")
# Subscribe to receive events
pub.subscribe(on_receive, "meshtastic.receive")
# Connect via USB
interface = meshtastic.serial_interface.SerialInterface()
# Keep running
import time
while True:
time.sleep(1)
Bluetooth (BLE) Monitoring
Connect wirelessly from a computer with Bluetooth:
# Scan for nearby Meshtastic devices
meshtastic --ble-scan
# Connect by name or address
meshtastic --ble "device_name" --info
meshtastic --ble "AA:BB:CC:DD:EE:FF" --nodes
# Stream packets over BLE
meshtastic --ble "device_name" --listen
This is useful for monitoring a node that’s mounted outdoors or on a rooftop where running a USB cable isn’t practical. BLE range is typically 10β30 meters through walls.
WiFi / TCP Monitoring
For nodes with WiFi enabled (ESP32-based devices):
# Connect to node on your local network
meshtastic --host meshtastic.local --info
meshtastic --host 192.168.1.50 --nodes
# Stream packets over WiFi
meshtastic --host 192.168.1.50 --listen
# Log to file over network
meshtastic --host 192.168.1.50 --seriallog ~/mesh-logs/log.txt
Enable WiFi on your node first:
meshtastic --set network.wifi_ssid "YourSSID"
meshtastic --set network.wifi_psk "YourPassword"
meshtastic --set network.wifi_enabled true
The node also runs a web client on port 80 that you can access from a browser.
MQTT β Fleet Monitoring at Scale
MQTT is the most powerful monitoring method. A WiFi-connected node acts as a gateway, publishing all mesh traffic to an MQTT broker. From there, any subscriber can consume the data β Home Assistant, Node-RED, Grafana, custom scripts, or anything that speaks MQTT.
How It Works
[Mesh Node] --LoRa--> [Gateway Node] --WiFi--> [MQTT Broker] ---> [Subscribers]
βββ Home Assistant
βββ Node-RED
βββ Grafana
βββ Python script
βββ Database
Gateway Setup
# Enable WiFi
meshtastic --set network.wifi_ssid "YourSSID"
meshtastic --set network.wifi_psk "YourPassword"
meshtastic --set network.wifi_enabled true
# Point to your MQTT broker (or use the public one)
meshtastic --set mqtt.address "mqtt://your-broker:1883"
meshtastic --set mqtt.username "meshuser"
meshtastic --set mqtt.password "meshpass"
meshtastic --set mqtt.enabled true
# Enable JSON output (easier to consume than protobuf)
meshtastic --set mqtt.json_enabled true
# Enable uplink on your primary channel
meshtastic --ch-index 0 --ch-set uplink_enabled true
MQTT Topics
Messages arrive on topics following this pattern:
msh/US/2/json/LongFast/!abcd1234 # JSON messages
msh/US/2/e/LongFast/!abcd1234 # Protobuf (raw) messages
Data types published via MQTT JSON:
- Text messages β chat messages between nodes
- Position β GPS coordinates, altitude, speed
- Telemetry β battery, temperature, humidity, pressure
- Node info β device name, hardware model
- Traceroute β hop path between nodes
Subscribe with mosquitto
# Subscribe to all JSON messages from your region
mosquitto_sub -h your-broker -t "msh/US/2/json/#" -v
# Example JSON output for a position packet:
# {
# "from": 2130636288,
# "to": -1,
# "channel": 0,
# "type": "position",
# "payload": {
# "latitude_i": 285123456,
# "longitude_i": -814567890,
# "altitude": 25,
# "time": 1646832724
# },
# "timestamp": 1646832724
# }
Python MQTT Consumer
import paho.mqtt.client as mqtt
import json
def on_message(client, userdata, msg):
data = json.loads(msg.payload)
msg_type = data.get('type', 'unknown')
node_id = hex(data.get('from', 0))
if msg_type == 'telemetry':
| **USB-C Cable (6ft)** | Connect node to computer | ~$7 | [Amazon](https://www.amazon.com/s?k=USB-C+to+USB-A+cable+6ft&tag=v00ko-20) |
| **USB-C Cable (10ft)** | Reach nodes mounted higher | ~$9 | [Amazon](https://www.amazon.com/s?k=USB-C+to+USB-A+cable+10ft&tag=v00ko-20) |
| **Raspberry Pi 5** | Dedicated monitoring server | ~$60 | [Amazon](https://www.amazon.com/s?k=Raspberry+Pi+5+8GB&tag=v00ko-20) |
| **Raspberry Pi Zero 2 W** | Lightweight MQTT gateway | ~$20 | [Amazon](https://www.amazon.com/s?k=Raspberry+Pi+Zero+2+W&tag=v00ko-20) |
| **USB Hub (powered)** | Monitor multiple nodes | ~$12 | [Amazon](https://www.amazon.com/s?k=powered+USB+hub+4+port&tag=v00ko-20) |
payload = data.get('payload', {})
lat = payload.get('latitude_i', 0) / 1e7
lon = payload.get('longitude_i', 0) / 1e7
print(f"[{node_id}] Position: {lat}, {lon}")
elif msg_type == 'text':
print(f"[{node_id}] Message: {data.get('payload')}")
except json.JSONDecodeError:
pass # Skip protobuf messages
client = mqtt.Client()
client.on_message = on_message
client.connect("your-broker", 1883, 60)
client.subscribe("msh/US/2/json/#")
client.loop_forever()
Serial Module β Dedicated Hardware Interface
For connecting a Meshtastic node to another microcontroller (Raspberry Pi, Arduino, ESP32), the Serial Module provides a dedicated UART interface on GPIO pins.
Modes
| Mode | Description |
|---|---|
SIMPLE | Dumb UART tunnel β raw bytes in/out on a channel named “serial” |
TEXTMSG | Send/receive text messages as plain strings over serial |
PROTO | Full protobuf API β programmatic control from another device |
NMEA | Output GPS data as NMEA 0183 sentences |
LOG | Formatted log of all packets (text, telemetry, etc.) |
Setup
# Enable serial module in TEXTMSG mode
meshtastic --set serial.enabled true
meshtastic --set serial.mode TEXTMSG
meshtastic --set serial.rxd 13 # RX pin (connect to other device TX)
meshtastic --set serial.txd 14 # TX pin (connect to other device RX)
meshtastic --set serial.baud BAUD_38400
Wiring
Connect your Meshtastic device to a Raspberry Pi or other microcontroller:
| Meshtastic Pin | Connects To |
|---|---|
| TX (GPIO 14) | RX on Pi/Arduino |
| RX (GPIO 13) | TX on Pi/Arduino |
| GND | GND on Pi/Arduino |
Important: Cross the TX/RX lines β TX goes to RX and vice versa. Connect grounds together.
Part 2: MeshCore Monitoring
MeshCore takes a different approach from Meshtastic. Instead of a single firmware with modules, MeshCore has distinct firmware types for different roles: Companion (for app connections), Repeater (for relaying), and Room Server (for BBS-style posts). Monitoring uses different tools depending on what firmware you’re running.
USB Serial β CLI Monitoring
Every MeshCore firmware type supports serial CLI commands over USB. Connect via a serial terminal at 115200 baud, 8N1.
# macOS/Linux β use screen, minicom, or tio
tio /dev/cu.usbmodem* -b 115200
# Or use PlatformIO Serial Monitor in VS Code
Key Monitoring Commands
# System health
stats-core # Battery voltage, uptime, queue length
stats-radio # Noise floor, last RSSI/SNR, airtime, errors
stats-packets # Packet counters: received, sent
# Network state
neighbors # List nearby nodes (repeater only)
get role # Show configured role
ver # Firmware version
board # Hardware name
# Radio diagnostics
get radio # Current frequency, bandwidth, SF, CR
get tx # Current TX power in dBm
Packet Logging
MeshCore repeaters can capture a full receive log:
log start # Begin capturing received packets to storage
log stop # Stop capture
log # Print captured log to serial terminal (serial only)
log erase # Clear captured log
This is invaluable for diagnosing routing issues or measuring traffic patterns.
KISS Modem β Programmatic Interface
The MeshCore KISS Modem firmware turns a LoRa device into a standard KISS TNC (Terminal Node Controller). This is the most powerful interface for building custom monitoring tools β your computer handles all the protocol logic while the device handles the radio.
Serial config: 115200 baud, 8N1, no flow control.
The KISS modem exposes:
| Capability | Command | Data Returned |
|---|---|---|
| Send/receive raw packets | Standard KISS data frames | Raw LoRa packets |
| Node identity | GetIdentity (0x01) | 32-byte Ed25519 public key |
| Radio stats | GetStats (0x12) | RX count, TX count, error count |
| Battery voltage | GetBattery (0x13) | Millivolts (16-bit) |
| MCU temperature | GetMCUTemp (0x14) | Temperature in 0.1Β°C units |
| Noise floor | GetNoiseFloor (0x10) | dBm (signed 16-bit) |
| Channel busy | IsChannelBusy (0x0E) | Clear or busy flag |
| Signal quality | RxMeta (auto) | SNR + RSSI after each received packet |
| Sensor data | GetSensors (0x15) | CayenneLPP encoded telemetry |
All extended commands use the KISS SetHardware (0x06) frame with a sub-command byte. Standard KISS clients ignore these frames, so the modem is fully compatible with existing tools like Direwolf, APRSdroid, or YAAC.
BLE β Companion Protocol
MeshCore’s Companion firmware exposes a BLE GATT service that mobile apps and desktop tools connect to. The protocol is binary and documented at docs.meshcore.io/companion_protocol.
Client libraries are available for programmatic access:
- JavaScript: meshcore.js (Node.js)
- Python: meshcore-cli
The data you can pull via the companion protocol:
- Contact list and message history
- Channel configuration
- Battery voltage and storage usage
- Device info (firmware version, model, radio settings)
- Real-time received messages with SNR metadata
WiFi Companion
Some MeshCore devices support WiFi companion firmware, which serves the same companion protocol over a TCP socket instead of BLE. Useful for always-on monitoring from a computer on the same network.
Bridge Mode β Serial Packet Forwarding
MeshCore repeaters support a Bridge mode that forwards packet data to an external interface (RS-232 serial or ESP-NOW wireless):
set bridge.enabled on
set bridge.source logTx # Bridge transmitted packets
set bridge.baud 115200 # Serial speed (RS-232 bridge)
set bridge.delay 500 # Delay between bridged packets (ms)
This lets you feed mesh traffic into another system β a Raspberry Pi running a logger, a second radio on a different frequency, or an ESP-NOW mesh.
Data Storage & Integration Ideas
Once you’re capturing mesh data, here are common ways to store and use it:
| Destination | Tool | Data Format |
|---|---|---|
| Flat files | CLI redirection, Python scripts | JSON lines, CSV, raw text |
| SQLite | Python + sqlite3 | Structured tables |
| InfluxDB | Telegraf MQTT consumer | Time-series metrics |
| Home Assistant | MQTT integration | Automations, dashboards |
| Grafana | InfluxDB/Prometheus backend | Visual dashboards |
| Node-RED | MQTT nodes | Flows, alerts, webhooks |
Example: Log Meshtastic Telemetry to SQLite
import meshtastic.serial_interface
from pubsub import pub
import sqlite3
import time
db = sqlite3.connect('mesh_telemetry.db')
db.execute('''CREATE TABLE IF NOT EXISTS telemetry (
timestamp INTEGER, node_id TEXT, battery_level REAL,
voltage REAL, temperature REAL, humidity REAL
)''')
def on_receive(packet, interface):
decoded = packet.get('decoded', {})
if decoded.get('portnum') != 'TELEMETRY_APP':
return
t = decoded.get('telemetry', {})
dev = t.get('deviceMetrics', {})
env = t.get('environmentMetrics', {})
db.execute('INSERT INTO telemetry VALUES (?,?,?,?,?,?)', (
int(time.time()),
packet.get('fromId', ''),
dev.get('batteryLevel'),
dev.get('voltage'),
env.get('temperature'),
env.get('relativeHumidity'),
))
db.commit()
pub.subscribe(on_receive, "meshtastic.receive")
interface = meshtastic.serial_interface.SerialInterface()
while True:
time.sleep(1)
Hardware for Monitoring Setups
| Component | Use Case | Est. Price | Link |
|---|---|---|---|
| USB-C Cable (6ft) | Connect node to computer | ~$7 | Amazon |
| USB-C Cable (10ft) | Reach nodes mounted higher | ~$9 | Amazon |
| Raspberry Pi 5 | Dedicated monitoring server | ~$60 | Amazon |
| Raspberry Pi Zero 2 W | Lightweight MQTT gateway | ~$20 | Amazon |
| USB Hub (powered) | Monitor multiple nodes | ~$12 | Amazon |
MilkMesh monitoring guides are maintained by the community. If you find a better tool, a broken workflow, or want to suggest a new integration, let us know.