A Python command-line tool for working with Meshtastic networks, providing packet capture, filtering, and analysis capabilities.
This project uses uv as the package manager. To install dependencies:
uv sync# Run with default serial port (/dev/ttyACM0)
uv run meshcap
# Run with custom serial port
uv run meshcap --port /dev/ttyUSB0
uv run meshcap -p /dev/ttyUSB0
# Connect via TCP/IP to a network-enabled device
uv run meshcap --host 192.168.1.50
uv run meshcap --host myradio.local
# Connect via TCP/IP with custom port (default: 4403)
uv run meshcap --host 192.168.1.50 --tcp-port 1234
# Run with verbose output (shows JSON details for unknown packet types)
uv run meshcap --verbose
uv run meshcap -vmeshcap supports advanced packet filtering using logical expressions:
# Filter text messages only
uv run meshcap port text
# Messages from specific node and text port
uv run meshcap src node A and port text
# Complex filtering with parentheses
uv run meshcap '(' src node A or dst node A ')' and port position# Write packets to JSON file (recommended - secure format)
uv run meshcap --write-file packets.json --format json
# Write packets to file with format auto-detected from extension
uv run meshcap --write-file packets.json # Uses JSON format
uv run meshcap --write-file packets.pkl # Uses pickle format (deprecated)
# TCP connection with JSON output
uv run meshcap --host 192.168.1.50 --write-file packets.json --format json# Read from JSON file (secure format)
uv run meshcap -r packets.json
# Read from file with auto-format detection
uv run meshcap -r packets.json encrypted and hop_limit '>' 5
uv run meshcap -r packets.pkl encrypted and hop_limit '>' 5 # Shows deprecation warning
# Limit packet count (works with both serial and TCP)
uv run meshcap -c 10 priority HIGH or want_ack
uv run meshcap --host myradio.local -c 10 port textmeshcap displays captured packets in a structured format showing timestamp, channel, signal strength, hop information, optional flags, addressing, packet type, and payload:
[timestamp] Ch:hash signal Hop:X [A|M|AM] [NH:<label|0xHH>] address_fields packet_type: payload
# or, when hop_start is present and valid
[timestamp] Ch:hash signal Hops:U/S [A|M|AM] [NH:<label|0xHH>] address_fields packet_type: payload
meshcap uses a flexible node identity model:
- Canonical NodeNum: Each Meshtastic node has a canonical node number (e.g.,
3735928559) - User ID view: Node IDs can be displayed as hex strings (e.g.,
!deadbeef) - Labels optional: User names/labels are optional and displayed as
Name(!deadbeef)when available
Node identity with new display form:
[2023-10-19 16:02:15] Ch:3 -85dBm/10.0dB Hop:0 from:TestNode (!deadbeef) to:Relay (!cafebabe) Text: New identity format
Hop usage with hop_start provided:
[2023-10-19 16:00:00] Ch:5 -85dBm/12.5dB Hops:4/7 from:Alice Node (!a1b2c3d4) to:Bob Node (!e5f6a7b8) Text: Test message
Directed next hop (NH) shown when present:
[2023-10-19 16:05:00] Ch:5 -85dBm/12.5dB Hop:3 NH:NH Node (!a1b2c315) from:Alice Node (!a1b2c3d4) to:Bob Node (!e5f6a7b8) Text: Directed hop
When name resolution is disabled or ambiguous, NH falls back to the last byte in hex:
[2023-10-19 16:05:00] Ch:5 -85dBm/12.5dB Hop:3 NH:0x15 from:!a1b2c3d4 to:!e5f6a7b8 Text: Directed hop
Flag examples (ack/MQTT):
[2023-10-19 16:10:00] Ch:2 -92dBm/9.0dB Hop:2 [A] from:Alice (!deadbeef) to:Bob (!cafebabe) Text: Ack requested
[2023-10-19 16:11:00] Ch:2 -92dBm/9.0dB Hop:2 [M] from:Relay (!00112233) to:All (!ffffffff) Text: Via MQTT
[2023-10-19 16:12:00] Ch:2 -92dBm/9.0dB Hop:2 [AM] from:Alice (!deadbeef) to:Bob (!cafebabe) Text: Ack via MQTT
- Hop:X: Remaining hop limit when
hop_startis missing/zero or whenhop_limit>hop_start. - Hops:U/S: Hop usage format when both
hop_startandhop_limitare present andhop_limit <= hop_start. HereSis the starting TTL (hop_start), andU = S - hop_limitis the number of hops already used. - from/to: End-to-end message routing (ultimate origin/destination)
- NH:<label|0xHH>: Next hop shown only when
nextHop/next_hopis non-zero. If name resolution is enabled and there is a unique node whose user ID last byte matches, the label is shown; otherwise raw hex0xHHis shown. - [A|M|AM]: Optional flags shown when present —
Aindicates the packet requested an acknowledgement (wantAck),Mindicates the packet arrived via MQTT (viaMqtt). When both apply, they are shown together asAM. If no flags are active, this section is omitted.
- Node numbers: Raw integers (e.g.,
123456789) - preferred for readability - Node IDs: Hex format (e.g.,
!075bcd15) - fallback when node numbers unavailable - Resolved names:
Name (identifier)format - shows user name with node number/ID
The tool only shows address fields that are present and differ from each other, keeping output clean while providing comprehensive routing information when available. Hop information is always displayed when available: it defaults to Hop:0 when no values are provided, or switches to Hops:U/S when hop_start is provided along with a valid hop_limit.
--port/-p: Serial device path (default:/dev/ttyACM0) - mutually exclusive with--host--host: TCP/IP hostname or IP address for network connection - mutually exclusive with--port--tcp-port: TCP port number for network connection (default:4403) - used with--host
--test-mode: Run in test mode (exit after setup)--no-resolve/-n: Disable node name resolution (show raw node numbers/IDs without names)--write-file/-w: Write packets to file (format determined by --format or file extension)--read-file/-r: Read packets from file (auto-detects format)--count/-c: Exit after N packets--verbose/-v: Enable verbose output (show JSON details for unknown packet types)--format: File format for writing/reading packets (json,auto- default: auto)--cache-size: Maximum size of the NodeBook cache for node name resolutionfilter: Filter expression
# Serial connection (default)
uv run meshcap --port /dev/ttyUSB0
# TCP connection to IP address
uv run meshcap --host 192.168.1.50
# TCP connection to hostname with custom port
uv run meshcap --host myradio.local --tcp-port 1234
# Invalid: cannot use both serial and TCP
uv run meshcap --port /dev/ttyUSB0 --host 192.168.1.50 # Error!node <value>: Match packets from or to nodesrc node <value>: Match source nodedst node <value>: Match destination nodeuser <name>: Match packets from or to a user by long or short namesrc user <name>: Match source userdst user <name>: Match destination userport <value>: Match port (supports: text, position, nodeinfo, routing, admin, telemetry)hop_limit <op> <value>: Match hop limit (<,>,=)priority <value>: Match priority levelwant_ack: Match packets requiring acknowledgmentencrypted/is encrypted: Match encrypted packetsplaintext/is plaintext: Match plaintext packets
and: Logical AND (higher precedence)or: Logical OR (lower precedence)not: Logical NOT (highest precedence)( ): Parentheses for grouping
# Text messages from specific node (serial connection)
uv run meshcap src node !12345678 and port text
# Text messages from specific node (TCP connection)
uv run meshcap --host 192.168.1.50 src node !12345678 and port text
# Messages from the user named "Alice"
uv run meshcap src user Alice
# High priority or ack-required packets (TCP with custom port)
uv run meshcap --host myradio.local --tcp-port 1234 priority HIGH or want_ack
# Complex expression
uv run meshcap '(' src node A or dst node A ')' and '(' port text or port position ')'
# Encrypted high hop-limit packets
uv run meshcap encrypted and hop_limit '>' 5
# Filtering with mixed node ID forms (hex and decimal)
uv run meshcap node src == !deadbeef
uv run meshcap node src == 3735928559
# Show verbose output for unknown packet types (TCP connection)
uv run meshcap --host 192.168.1.50 --verbose port admin# Run all tests
uv run pytest
# Run specific test file
uv run pytest tests/test_filtering.py
# Verbose output
uv run pytest -v# Build package
uv buildsrc/meshcap/main.py: Main CLI applicationsrc/meshcap/filter.py: Packet filtering enginetests/: Test suitepyproject.toml: Project configuration
meshtastic: Core Meshtastic library (supports both serial and TCP connections)pytest: Testing framework