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-theusesrepository'sstructuralpyproject.tomlpatternformatching)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
asyncThe 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.ERRORgracefully.and EventType.DISCONNECTED rather than relying on a specific exception class. Check the repository README for the current error-handling surface.