Skip to main content

MeshCore Python API

The MeshCore Python library (meshcore_py) provides an async interface for building applications and scripts that communicate with MeshCore companion radio nodes. It is theone primaryof several programmatic access methodmethods, alongside meshcore.js (NodeJS/JavaScript) and the meshcore-cli command-line tool.

Canonical source: github.com/meshcore-dev/meshcore_py. The API moves quickly — always check the repository README for automation,the networkauthoritative, monitoring,up-to-date andmethod customsignatures integrations.before relying on the examples below.

Requirements

  • A reasonably modern Python 3.10 or newer3 (requiredcheck -the usesrepository's structuralpyproject.toml patternfor matching)the exact minimum version)
  • A MeshCore node connected via USB serialserial, BLE, or TCP

Installation

pip install meshcore

Source: github.com/MeshCore-meshcore-dev/MeshCore_pymeshcore_py

Connecting to a node

The MeshCore class is created with async factory methods (create_serial, create_tcp, create_ble), not a constructor. Commands are issued through the mc.commands namespace and return an Event whose .payload is a dict.

import asyncio
from meshcore import MeshCoreMeshCore, EventType

async def main():
 # Connect via USB serial (most common). Port and baud are positional.
 mc = await MeshCore.connect_serial(create_serial("/dev/ttyUSB0", baudrate=115200)

 # Or via TCP (for nodes with a WiFi/TCP bridge); port is positional.
 # The MeshCore TCP default is port 5000.
 # mc = await MeshCore.connect_tcp(create_tcp("192.168.1.100", port=5000)

 # Or via BLE by address:
 # mc = await MeshCore.create_ble("AA:BB:CC:DD:EE:FF")

 # Fetch device self-info (returns an Event; data is in .payload)
 result = await mc.commands.send_appstart()
 print(f"Connected to: {mc.self_info.name}result.payload}")

 await mc.disconnect()

asyncio.run(main())

Listing nodes and contacts

async

The defdevice's main():stored mccontact = await MeshCore.connect_serial("/dev/ttyUSB0") # Get all known contactslist (nodesoptionally heardfiltered by thislast-modification device)time) contactsis =returned awaitin mc.get_contacts()the forevent payload as a dict keyed by public key. Each contact inis contacts: print(f"{contact.name} | RSSI: {contact.last_rssi} dBm | SNR: {contact.last_snr} dB") await mc.disconnect()

Sendingitself a message

dict accessed by string keys (e.g. contact['adv_name']).
async def main():
 mc = await MeshCore.connect_serial(create_serial("/dev/ttyUSB0", 115200)

 # Get the device's stored contact list
 result = await mc.commands.get_contacts()
 contacts = result.payload  # dict keyed by public key

 for contact_id, contact in contacts.items():
 print(f"{contact['adv_name']} ({contact_id})")

 await mc.disconnect()

Note: RSSI and SNR are not stored on the contact record — they arrive on incoming message / raw-data events, not in the contact database.

Sending a message

Channel (broadcast) messages use send_chan_msg(channel_index, msg). Direct messages use send_msg(dst, msg), where dst is a contact object, a hex public-key string, or bytes — contacts are addressed by their public key, not a "node ID".

async def main():
 mc = await MeshCore.create_serial("/dev/ttyUSB0", 115200)

 # Send to a channel (broadcast); channel index is positional
 await mc.send_channel_message(commands.send_chan_msg(0, "Hello mesh!", channel=0))

 # Send a direct message to a contact identified by nodeits IDpublic contactskey
 result = await mc.commands.get_contacts()
 contacts = result.payload
 target = next(c for c in contactscontacts.values() if c.namec['adv_name'] == "Base Station")
 await mc.send_direct_message(target.node_id,commands.send_msg(target, "Hello from Python!")

 await mc.disconnect()

Monitoring incoming messages

Incoming messages are handled by subscribing to an EventType (e.g. CONTACT_MSG_RECV for direct messages, CHANNEL_MSG_RECV for channel messages). The handler receives an Event whose .payload is a dict.

import asyncio
from meshcore import MeshCore, MessageEventEventType

async def main():
 mc = await MeshCore.connect_serial(create_serial("/dev/ttyUSB0"), 115200)

 async def on_message(event:event):
 MessageEvent):data = event.payload
 print(f"[{event.sender_name}data['pubkey_prefix']}] {event.text}data['text']}")

 mc.on_message(subscribe(EventType.CONTACT_MSG_RECV, on_message)

 # Keep running and receiving events
 print("Monitoring... press Ctrl+C to stop")
 try:
 await asyncio.sleep(float('inf'))
 except KeyboardInterrupt:
 pass
 finally:
 await mc.disconnect()

asyncio.run(main())

Getting node telemetry

Self-info comes from send_appstart() (SELF_INFO). Core statistics — battery voltage, uptime, error counts, queue length — come from get_stats_core(). All return an Event whose .payload is a dict. TX power is set with set_tx_power(val); it is not exposed as a read-only attribute.

async def main():
 mc = await MeshCore.connect_serial(create_serial("/dev/ttyUSB0", 115200)

 # Device self-info (name, public key, coordinates, etc.)
 infoself_info = (await mc.self_infocommands.send_appstart()).payload
 print(f"Node:Self info: {info.name}self_info}")

 # Core statistics
 stats = (await mc.commands.get_stats_core()).payload
 print(f"Battery: {info.battery_mv}stats['battery_mv']} mV")
 print(f"Uptime: {info.uptime_s}stats['uptime_secs']} seconds")
 print(f"TX power: {info.tx_power_dbm} dBm")

 await mc.disconnect()

Use cases

  • Network monitoring dashboards - log all messages and node activity to a database
  • Gateway integrations - bridge MeshCore messages to Discord, MQTT, or other platforms
  • Automated alerts - notify via SMS or email when specific keywords are detected
  • Repeater health monitoring - check uptime, battery level, and contact count on a schedule
  • Coverage mapping - record signal reports from automated messages during a walking survey

Error handling notes

MeshCore over serial can occasionally miss bytes or timeout. The library includessupports opt-in automatic reconnect logic,(pass butauto_reconnect=True, e.g. with max_reconnect_attempts, to the create_* factory method) — it is not enabled by default. For long-running scriptsscripts, shouldhandle wrap operations in try/except blockserrors and handledisconnects by subscribing to MeshCoreConnectionErrorEventType.ERROR gracefully.and EventType.DISCONNECTED rather than relying on a specific exception class. Check the repository README for the current error-handling surface.