Operations Guide

Mesh Node Monitoring & Data Capture

How to interface with Meshtastic and MeshCore nodes to capture, store, and relay mesh data

Affiliate Disclosure: As an Amazon Associate, MilkMesh earns from qualifying purchases. Some links on this page are affiliate links β€” clicking them and making a purchase supports this project at no extra cost to you. Affiliate relationships do not influence our recommendations. See our Privacy Policy and Terms of Use.

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

MethodMeshtasticMeshCoreBest For
USB Serialβœ… CLI, Python API, serial logβœ… CLI commands, KISS modemDirect wired access, initial setup
Bluetooth (BLE)βœ… Mobile apps, Python CLIβœ… Companion firmware + appsPhone-to-node, wireless monitoring
WiFi / TCPβœ… --host CLI, web clientβœ… WiFi companion firmwareHeadless 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 insteadSensor 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

ModeDescription
SIMPLEDumb UART tunnel β€” raw bytes in/out on a channel named “serial”
TEXTMSGSend/receive text messages as plain strings over serial
PROTOFull protobuf API β€” programmatic control from another device
NMEAOutput GPS data as NMEA 0183 sentences
LOGFormatted 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 PinConnects To
TX (GPIO 14)RX on Pi/Arduino
RX (GPIO 13)TX on Pi/Arduino
GNDGND 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:

CapabilityCommandData Returned
Send/receive raw packetsStandard KISS data framesRaw LoRa packets
Node identityGetIdentity (0x01)32-byte Ed25519 public key
Radio statsGetStats (0x12)RX count, TX count, error count
Battery voltageGetBattery (0x13)Millivolts (16-bit)
MCU temperatureGetMCUTemp (0x14)Temperature in 0.1Β°C units
Noise floorGetNoiseFloor (0x10)dBm (signed 16-bit)
Channel busyIsChannelBusy (0x0E)Clear or busy flag
Signal qualityRxMeta (auto)SNR + RSSI after each received packet
Sensor dataGetSensors (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:

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:

DestinationToolData Format
Flat filesCLI redirection, Python scriptsJSON lines, CSV, raw text
SQLitePython + sqlite3Structured tables
InfluxDBTelegraf MQTT consumerTime-series metrics
Home AssistantMQTT integrationAutomations, dashboards
GrafanaInfluxDB/Prometheus backendVisual dashboards
Node-REDMQTT nodesFlows, 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

ComponentUse CaseEst. PriceLink
USB-C Cable (6ft)Connect node to computer~$7Amazon
USB-C Cable (10ft)Reach nodes mounted higher~$9Amazon
Raspberry Pi 5Dedicated monitoring server~$60Amazon
Raspberry Pi Zero 2 WLightweight MQTT gateway~$20Amazon
USB Hub (powered)Monitor multiple nodes~$12Amazon

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.