Skip to main content

Using the Meshtastic Python CLI for Diagnostics

The Meshtastic Python package ships both a library and a command-line interface (CLI). The CLI is the fastest way to interrogate a connected node, export its configuration, watch live packet traffic, and run one-off diagnostic commands from a laptop. This page covers installation, every useful diagnostic command, how to read the output, and the difference between serial and TCP connections.

Installation

# Requires Python 3
pip3 install --upgrade "meshtastic[cli]"

# Verify installation
meshtastic --version
# Output: meshtastic v2.x.x

On Linux you may need to add your user to the dialout group to access serial ports without sudo:

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

Connecting to a Node

Serial (USB)

The most common connection method. Plug the device in via USB and run any command without extra flags - the CLI auto-detects the first available serial port:

meshtastic --info

To specify a port explicitly:

meshtastic --port /dev/ttyUSB0 --info # Linux
meshtastic --port /dev/cu.usbserial-* --info # macOS
meshtastic --port COM4 --info # Windows

TCP (Wi-Fi)

Nodes running ESP32 with Wi-Fi enabled expose a TCP port (default 4403). Use --host instead of --port:

meshtastic --host 192.168.1.42 --info
meshtastic --host meshtastic.local --info # mDNS name on the local LAN

BLE

BLE support is included by default — bleak is now a core dependency of the meshtastic package, so the old [ble] extra is no longer required:

pip3 install --upgrade "meshtastic[cli]"
meshtastic --ble-scan # lists visible Meshtastic BLE devices
meshtastic --ble <MAC-or-name> --info

--info: Device Summary

meshtastic --info

This is the single most useful diagnostic command. It prints a comprehensive summary of the connected node's radio configuration and node database (largely human-readable text with some structured sections), including:

Connected to radio
Owner: WB5ABC (4 char id: !aabbccdd)
My info:
{
 "myNodeNum": 2864434397,
 "hasGps": true,
 ...
}
Metadata: {
 "firmwareVersion": "2.5.3.abcdef",
 "deviceStateVersion": 23,
 "hasWifi": true,
 "hasBluetooth": true,
 "hasEthernet": false,
 "hwModel": "TLORA_V2_1_1P6",
 "hasRemoteHardware": false,
 "canShutdown": false
}
Nodes in mesh: {
 "!aabbccdd": {
 "num": 2864434397,
 "user": { "id": "!aabbccdd", "longName": "WB5ABC", "shortName": "W5", "hwModel": "TLORA_V2_1_1P6", "role": "CLIENT" },
 "position": { "latitudeI": 323456789, "longitudeI": -970123456, "altitude": 312, "time": 1714000000 },
 "snr": 6.5,
 "lastHeard": 1714010000,
 "deviceMetrics": { "batteryLevel": 87, "voltage": 3.98, "channelUtilization": 4.25, "airUtilTx": 0.81 }
 },
 ...
}
Preferences: { ... full radio config (role, positionFlags, etc. live here) ... }
Channels: [ { "index": 0, "role": "PRIMARY", "settings": { ... } }, ... ]

Note that the fields live in different objects: device capabilities and firmware version are under Metadata, while the node's role and positionFlags live in the radio config / Preferences (localConfig), not in Metadata. Script your lookups against the correct object.

Key fields to check during diagnostics:

  • channelUtilization: percentage of channel airtime used over the last ~1 minute (the firmware sums on-air time across six 10-second sub-windows).
  • airUtilTx: TX duty cycle percentage.
  • snr: last-heard SNR from each remote node (dB).
  • lastHeard: Unix timestamp. Subtract from current time to see how stale a node entry is.
  • firmwareVersion: compare against the latest release to check if updates are needed.

--nodes: Node Table

meshtastic --nodes

Prints a compact tabular summary of all nodes in the mesh database:

 N Num User AKA Hardware Latitude Longitude Altitude Battery SNR LastHeard
 1 2864434397 WB5ABC W5 TLORA_V2 32.3456 -97.0123 312m 87% 6.5 2024-04-25 10:31:22
 2 3109276812 K5ZZZ K5 RAK4631 32.3512 -97.0099 289m 72% 2.1 2024-04-25 10:29:55
 3 4012345678 N5XYZ N5 HELTEC_V3 32.3390 -97.0210 278m -- -4.7 2024-04-25 09:15:10

The LastHeard column quickly shows stale nodes (entries in the node DB that haven't been heard in hours or days). These are candidates for manual removal if you are troubleshooting ghost-node problems. The SNR column shows the signal quality of the last packet heard from that node (not necessarily a direct link - the packet may have been relayed).

--export-config: Full Configuration Export

meshtastic --export-config > node_backup_$(date +%Y%m%d).yaml

Exports the complete device configuration as YAML. This includes radio settings, channel definitions, module configs (telemetry intervals, MQTT settings, serial module, etc.), and device metadata. Use this to:

  • Back up a node before a firmware update.
  • Clone configuration from one device to another with meshtastic --configure config.yaml.
  • Audit configuration differences between nodes in a community mesh.

Treat the exported file as secret. The YAML contains channel PSKs and any MQTT username/password in recoverable form. Store the backup encrypted and do not commit it to a public repository.

--listen: Live Packet Watch

meshtastic --listen

Subscribes to all decoded packets from the connected node and prints them as they arrive. This is the equivalent of a packet sniffer for the mesh. Press Ctrl-C to stop. The CLI prints library log lines and packet dictionaries; the simplified example below illustrates the key fields you will see rather than the literal output format:

TEXT_MESSAGE_APP: from=!aabbccdd, to=^all, id=0x12345678, hop_limit=3, want_ack=False
 payload: b'Hello from WB5ABC'

POSITION_APP: from=!aabbccdd, to=^all, id=0xdeadbeef, hop_limit=3, want_ack=False
 payload: Position(latitudeI=323456789, longitudeI=-970123456, altitude=312, time=1714010500)

TELEMETRY_APP: from=!ccccdddd, to=^all, id=0x87654321, hop_limit=1, want_ack=False
 payload: Telemetry(deviceMetrics=DeviceMetrics(batteryLevel=72, voltage=3.91,
 channelUtilization=11.3, airUtilTx=2.1))

During a --listen session, watch for:

  • Packets arriving with hop_limit=0. hop_limit is the number of remaining hops: it is set to the configured hop count when the packet is sent and is decremented by one at each relay. A hop_limit of 0 on arrival means the packet can no longer be rebroadcast — it used all of its allowed hops, so the hop budget may be too low.
  • Repeated identical packet IDs arriving within seconds (duplicate storm).
  • High-frequency position packets from a single node ID indicating that node's smart position interval is too aggressive.

Reading Telemetry from --listen

Every 30 minutes (1800 s) by default each node broadcasts device telemetry. While listening you can redirect output and grep for telemetry to build a quick health log:

meshtastic --listen 2>&1 | grep TELEMETRY_APP | tee mesh_telemetry.log

Practical Diagnostic Workflow

  1. Start with --info to get baseline node count and channelUtilization. If utilization > 25%, immediately check individual node position intervals.
  2. Run --nodes and flag any node where LastHeard is more than 2 hours old during an active session. These are likely ghost nodes.
  3. Run --listen for 5 - 10 minutes during peak mesh activity to count duplicate packet IDs and identify high-frequency transmitters.
  4. Export config with --export-config and compare hop_limit across nodes. A community mesh should generally use a maximum hop limit of 3 (the default).

Serial vs TCP: When to Use Each

MethodBest ForLimitations
Serial (USB) Local bench testing, initial setup, firmware examination Requires physical access; occupies USB port preventing simultaneous app connection
TCP (Wi-Fi) Remote diagnostics on deployed nodes, scripted automation, Raspberry Pi gateways Requires node to have Wi-Fi enabled and be on local network or accessible via port forward
BLE Temporary field diagnostics without Wi-Fi Short range, platform-specific BLE stack issues, slower data rate