Skip to main content

Meshtastic Python API Reference

Meshtastic Python API Reference

This page documents the key classes, methods, and patterns of the Meshtastic Python library. It covers the four interface classes, the event system, protobuf message types, and error handling. Refer to the library’s source on GitHub for the full method signatures as the API evolves with each firmware release.

Interface Class Hierarchy

MeshInterface  (base class, meshtastic.mesh_interface)
├── SerialInterface   (meshtastic.serial_interface)
├── TCPInterface      (meshtastic.tcp_interface)
└── BLEInterface      (meshtastic.ble_interface)

All four classes expose identical public methods for sending messages, reading node state, and changing configuration. The difference is only in the underlying transport.

MeshInterface (Base Class)

Constructor Parameters

ParameterTypeDescription
noProtoboolSkip protocol handshake (testing only). Default False.
debugOutfile-likeStream for debug output. Default None.
noNodesboolSkip downloading node database on connect. Default False.
configTimeoutintSeconds to wait for initial config download. Default 15.

Key Properties

PropertyTypeDescription
nodes dict[str, dict] All known nodes keyed by "!<hex_id>". Each value is a dict with num, user, position, snr, lastHeard, deviceMetrics.
myInfo dict Basic info about the local node: myNodeNum, hasGps.
metadata protobuf DeviceMetadata Firmware version, hardware model, capability flags.
localConfig protobuf LocalConfig Full device configuration: .lora, .device, .position, .power, .network, .display, .bluetooth.
moduleConfig protobuf LocalModuleConfig Module configs: .mqtt, .serial, .telemetry, .neighbor_info, .ambient_lighting, etc.
localChannels list[protobuf Channel] List of channel objects. Each has .index, .role, .settings (name, PSK, etc.).

Key Methods

# Send a text message
iface.sendText(
    text: str,
    destinationId: Union[int, str] = BROADCAST_ADDR,  # "^all" or "!aabbccdd"
    wantAck: bool = False,
    wantResponse: bool = False,
    channelIndex: int = 0,
    hopLimit: Optional[int] = None,
) -> Optional[Packet]

# Send raw data (any port number)
iface.sendData(
    data: bytes,
    destinationId: Union[int, str] = BROADCAST_ADDR,
    portNum: int = portnums_pb2.PortNum.PRIVATE_APP,
    wantAck: bool = False,
    wantResponse: bool = False,
    hopLimit: Optional[int] = None,
    channelIndex: int = 0,
    priority: MeshPacket.Priority = MeshPacket.Priority.RELIABLE,
) -> Optional[Packet]

# Get my node info dict
iface.getMyNodeInfo() -> dict

# Get my node number (integer)
iface.getMyNodeNum() -> int

# Remove a node from the local database
iface.removeNode(nodeId: str) -> None  # nodeId: "!aabbccdd"

# Send a traceroute request
iface.sendTraceRoute(
    dest: Union[int, str],
    hopLimit: int = 3,
    channelIndex: int = 0,
) -> None

# Write a config change to the device
# (after modifying iface.localConfig protobuf object)
iface.writeConfig(config_name: str) -> None
# config_name is one of: "device", "position", "power", "network",
#                         "display", "lora", "bluetooth"

# Write module config
iface.writeModuleConfig(config_name: str) -> None
# config_name is one of: "mqtt", "serial", "external_notification",
#                         "store_forward", "range_test", "telemetry",
#                         "canned_message", "audio", "remote_hardware",
#                         "neighbor_info", "ambient_lighting", "detection_sensor"

# Cleanly close the connection
iface.close() -> None

SerialInterface

class meshtastic.serial_interface.SerialInterface(
    devPath: Optional[str] = None,   # e.g. "/dev/ttyUSB0", "COM4"; None = auto-detect
    debugOut=None,
    noProto: bool = False,
    noNodes: bool = False,
)

Auto-detection scans /dev/ttyUSB*, /dev/ttyACM*, /dev/cu.usbserial-*, and Windows COM ports for a device that responds to the Meshtastic handshake.

TCPInterface

class meshtastic.tcp_interface.TCPInterface(
    hostname: str,                   # IP address or mDNS hostname
    portNumber: int = 4403,          # TCP port; default is 4403
    debugOut=None,
    noProto: bool = False,
    noNodes: bool = False,
    tls: bool = False,               # Enable TLS (requires server cert)
)

BLEInterface

class meshtastic.ble_interface.BLEInterface(
    address: str,                    # Device name or MAC address
    noProto: bool = False,
    debugOut=None,
    noNodes: bool = False,
)

# Class method: scan for devices
await BLEInterface.scan() -> list[BLEDevice]

Event System (pypubsub Topics)

The library uses pypubsub for all asynchronous notifications. Subscribe before or after creating the interface; the subscription takes effect for all future events.

from pubsub import pub

# All received packets
pub.subscribe(callback, "meshtastic.receive")

# Filtered by port number (portnum string from portnums.proto)
pub.subscribe(callback, "meshtastic.receive.text")       # TEXT_MESSAGE_APP
pub.subscribe(callback, "meshtastic.receive.position")   # POSITION_APP
pub.subscribe(callback, "meshtastic.receive.user")       # NODEINFO_APP
pub.subscribe(callback, "meshtastic.receive.telemetry")  # TELEMETRY_APP
pub.subscribe(callback, "meshtastic.receive.routing")    # ROUTING_APP (ACKs/NACKs)
pub.subscribe(callback, "meshtastic.receive.data.portnum_<N>")  # any port by number

# Connection lifecycle
pub.subscribe(callback, "meshtastic.connection.established")   # on connect + data download
pub.subscribe(callback, "meshtastic.connection.lost")          # on disconnect
pub.subscribe(callback, "meshtastic.node.updated")             # when node DB entry changes

Callback Signatures

# For meshtastic.receive.*
def on_receive(packet: dict, interface: MeshInterface) -> None:
    ...

# For meshtastic.connection.established
def on_connect(interface: MeshInterface, topic=pub.AUTO_TOPIC) -> None:
    ...

# For meshtastic.connection.lost
def on_lost(interface: MeshInterface, topic=pub.AUTO_TOPIC) -> None:
    ...

Packet Dictionary Structure

{
    "id":        4012345678,          # uint32 packet ID
    "from":      2864434397,          # sender node number (integer)
    "fromId":    "!aabbccdd",         # sender node ID (hex string)
    "to":        4294967295,          # destination (4294967295 = broadcast)
    "toId":      "^all",              # destination as string
    "hopLimit":  3,                   # remaining hops
    "hopStart":  3,                   # original hop limit
    "rxSnr":     4.25,                # SNR at receiver (dB)
    "rxRssi":    -98,                 # RSSI at receiver (dBm)
    "rxTime":    1714010000,          # Unix time packet was received
    "viaMqtt":   False,               # True if packet came via MQTT gateway
    "channel":   0,                   # channel index
    "decoded": {
        "portnum":  "TEXT_MESSAGE_APP",
        "text":     "Hello mesh!",    # present for TEXT_MESSAGE_APP
        "position": { ... },          # present for POSITION_APP
        "telemetry": { ... },         # present for TELEMETRY_APP
        "user":     { ... },          # present for NODEINFO_APP
        "routing":  { ... },          # present for ROUTING_APP
    },
    "raw":  <protobuf MeshPacket object>
}

Protobuf Message Types

The library exposes raw protobuf objects for advanced use. The most important generated modules in the package are:

ModuleKey Message Types
meshtastic.mesh_pb2 MeshPacket, NodeInfo, User, Position, Data, Routing
meshtastic.config_pb2 Config, Config.DeviceConfig, Config.LoRaConfig, Config.PositionConfig, Config.NetworkConfig
meshtastic.module_config_pb2 ModuleConfig, ModuleConfig.MQTTConfig, ModuleConfig.TelemetryConfig
meshtastic.telemetry_pb2 Telemetry, DeviceMetrics, EnvironmentMetrics, PowerMetrics
meshtastic.portnums_pb2 PortNum enum (TEXT_MESSAGE_APP, POSITION_APP, TELEMETRY_APP, etc.)
meshtastic.channel_pb2 Channel, ChannelSettings

Example: Modifying a Config Setting

import meshtastic
import meshtastic.serial_interface

iface = meshtastic.serial_interface.SerialInterface()

# Read current value
print("Current hop limit:", iface.localConfig.lora.hop_limit)

# Modify the protobuf object directly
iface.localConfig.lora.hop_limit = 2
iface.localConfig.lora.tx_power  = 20  # dBm

# Write back to the device (triggers a reboot if radio settings changed)
iface.writeConfig("lora")

iface.close()

Error Handling Patterns

import meshtastic
import meshtastic.serial_interface
from meshtastic.mesh_interface import MeshInterface

# Handle connection failures
try:
    iface = meshtastic.serial_interface.SerialInterface()
except Exception as exc:
    print(f"Failed to connect: {exc}")
    # Common causes:
    #   - No Meshtastic device found on any serial port
    #   - Permission denied (Linux: add user to dialout group)
    #   - Device busy (app connected via BLE using same serial port implicitly)
    raise

# Handle send timeout (device not responding)
import meshtastic.mesh_interface as mi
try:
    iface.sendText("test", destinationId="!aabbccdd", wantAck=True)
except Exception as exc:
    print(f"Send failed: {exc}")

# Handle TCP connection refused
try:
    import meshtastic.tcp_interface
    iface = meshtastic.tcp_interface.TCPInterface("192.168.1.99")
except ConnectionRefusedError:
    print("TCP connection refused. Is the node on Wi-Fi with TCP API enabled?")

# Handle device disconnect mid-session
from pubsub import pub

def on_lost(interface, topic=pub.AUTO_TOPIC):
    print("Connection to device lost. Attempting reconnect in 5 seconds...")
    import time, threading
    def reconnect():
        time.sleep(5)
        try:
            new_iface = meshtastic.serial_interface.SerialInterface()
            print("Reconnected successfully.")
        except Exception as e:
            print(f"Reconnect failed: {e}")
    threading.Thread(target=reconnect, daemon=True).start()

pub.subscribe(on_lost, "meshtastic.connection.lost")

Thread Safety

The Meshtastic Python library spawns a background reader thread on connection. All pubsub callbacks are invoked from this thread. If your callback modifies shared state, use a threading.Lock to prevent race conditions. The sendText and sendData methods acquire an internal send lock and are safe to call from any thread.

import threading

lock = threading.Lock()
message_log = []

def on_receive(packet, interface):
    decoded = packet.get("decoded", {})
    if decoded.get("portnum") == "TEXT_MESSAGE_APP":
        with lock:
            message_log.append({
                "time":  packet.get("rxTime"),
                "from":  packet.get("fromId"),
                "text":  decoded.get("text"),
            })

Version Compatibility

Library VersionFirmware CompatibilityNotes
2.3.x Firmware 2.3.x Introduced traceroute, neighbor-info module support.
2.4.x Firmware 2.4.x PKI / public-key authentication, BLE improvements.
2.5.x Firmware 2.5.x New config fields, detection sensor module, power telemetry.

Always match the library version to your firmware major/minor version. Mismatches can cause silent config field drops or protobuf decode errors. Run pip install --upgrade meshtastic after each firmware update.