Meshtastic Python Scripting
The meshtastic Python Library
The official meshtastic Python library provides programmatic access to any Meshtastic device. It exposes the full device API: sending and receiving messages, reading/writing configuration, querying node lists, requesting telemetry, and subscribing to real-time events.
Install:
pip install meshtastic
Official documentation: python.meshtastic.org
Source and examples: github.com/meshtastic/python
Connection Types
| Interface class | Transport | Typical use |
|---|---|---|
meshtastic.SerialInterface |
USB serial (CDC ACM) | Direct connection, most reliable; default port auto-detected or specify /dev/ttyUSB0 |
meshtastic.TCPInterface |
TCP/IP over WiFi | Wireless connection to nodes with WiFi enabled (ESP32 devices); specify host IP (default port 4403) |
meshtastic.BLEInterface |
Bluetooth Low Energy | Short-range wireless; requires BlueZ on Linux or appropriate BLE stack |
Basic Usage Examples
The snippets below assume you have already created an iface object as shown in the first example. When copying a single snippet, include that connection setup line first.
Connect and print node info
import meshtastic import meshtastic.serial_interface iface = meshtastic.serial_interface.SerialInterface() print(iface.nodes) # dict of all known nodes iface.close()
Send a message to a channel
iface.sendText("Hello mesh", channelIndex=0)
# channelIndex=0 is the primary channel; 1 - 7 are secondary channels
Subscribe to received messages
import meshtastic
import meshtastic.serial_interface
from pubsub import pub
def on_receive(packet, interface):
print(f"Received: {packet}")
iface = meshtastic.serial_interface.SerialInterface()
pub.subscribe(on_receive, "meshtastic.receive")
# Keep the script alive while the interface thread processes events
import time
try:
while True:
time.sleep(1)
except KeyboardInterrupt:
iface.close()
Get node list with last-heard timestamps
for node_id, node in iface.nodes.items():
last_heard = node.get("lastHeard", "unknown")
name = node.get("user", {}).get("longName", node_id)
print(f"{name}: last heard {last_heard}")
Read and write device config
local = iface.getNode('^local')
# Read
print(local.localConfig.lora.hop_limit)
# Write
local.localConfig.lora.hop_limit = 5
local.writeConfig("lora")
Note: getNode('^local') returns the local Node object. localConfig, writeConfig(), writeChannel(), getMyNodeNum() and removeNode() are methods/attributes of the Node object (reached via getNode(...)), not of the base interface.
Request position from local node
iface.getNode('^local').requestPosition()
To set a fixed position programmatically (including altitude), use iface.localNode.setFixedPosition(lat, lon, alt).
Automation Use Cases
- Automated mesh announcements - scheduled weather, news, or system status broadcasts on a channel.
- Message bridges - relay messages between the mesh and Telegram, Discord, Matrix, or SMS (via Twilio). Bidirectional bridging is straightforward with the pub/sub event model.
- Telemetry logging - write incoming telemetry packets to CSV, SQLite, or InfluxDB for Grafana dashboards.
- Position mapping - forward GPS positions to a self-hosted map (e.g. Traccar, OwnTracks, or a custom Leaflet map).
- Alert systems - trigger SMS or email alerts when specific nodes go offline (last-heard threshold exceeded).
- Config management - script bulk configuration changes across many nodes connected via TCP.
Privacy note: the bridging and position-mapping patterns export mesh content and node positions to external services. Make sure that exposure is acceptable for your deployment before forwarding messages or locations off the RF mesh.
Common Patterns
# Connect (serial, auto-detect port)
iface = meshtastic.serial_interface.SerialInterface()
# Connect (TCP)
iface = meshtastic.tcp_interface.TCPInterface("192.168.1.50")
# Send text
iface.sendText("Hello mesh", channelIndex=0)
# Send to specific node (DM)
iface.sendText("Private message", destinationId="!a1b2c3d4", channelIndex=0)
# Request position
iface.getNode('^local').requestPosition()
# Write channel config
ch = iface.getNode('^local').channels[0]
ch.settings.name = "MyNet"
iface.getNode('^local').writeChannel(0)
Event Loop and Threading
The library spawns a background thread that reads from the serial/TCP connection and dispatches events via the PyPubSub pub/sub system. Your on_receive callback is called in that background thread - use thread-safe data structures (queues, locks) if you share state with your main thread.
Available topics:
meshtastic.receive- any incoming packetmeshtastic.receive.text- text messages onlymeshtastic.receive.position- position updatesmeshtastic.receive.user- node user/info packetsmeshtastic.receive.data.portnum- data packets by port number (telemetry arrives here under theTELEMETRY_APPportnum; there is no separatemeshtastic.receive.telemetrytopic)meshtastic.connection.established- fired after successful connectmeshtastic.connection.lost- fired on disconnect
Error Handling
- Serial port busy: only one process can hold the serial port. Close the Meshtastic app or CLI before running your script. Use
try/finally: iface.close()to release cleanly. - Reconnection on disconnect: subscribe to
meshtastic.connection.lostand re-instantiate the interface with exponential backoff. - Timeout handling:
SerialInterfacehas anoProtoparameter - set it toTruefor raw serial access without waiting for a Meshtastic handshake, useful for debugging hardware. - Packet validation: always guard against missing keys in the packet dict; not all fields are present in every packet type.
No comments to display
No comments to display