Skip to content

Latest commit

 

History

6 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

MicroMesh 📻

A tiny, dependency-free Meshtastic client for MicroPython.

PyPI

MicroMesh lets a small MicroPython board talk to a Meshtastic radio over UART. Use it to receive messages, send text and position packets, inspect the node list, or power a full-screen terminal dashboard.

No protobuf compiler runs on the board. No threads. No google.protobuf. Just a UART, three wires, and regular Python.

MicroMesh intentionally provides a focused, lightweight client for embedded projects. For the complete desktop feature set, use the official meshtastic package.

What can it do?

  • Connect to a Meshtastic radio using its framed serial API
  • Download and track the radio's node database
  • Receive and send text messages
  • Send binary data and positions
  • Read user, position, signal, battery, and hop information
  • Preserve unknown protobuf fields for forward compatibility
  • Recover from serial noise and optionally report and skip undecodable frames
  • Run a curses dashboard with live stats, nodes, and messages
  • Work on MicroPython without third-party runtime dependencies

Pick your setup

I want to… Use this
Run a simple listener on a XIAO RP2040 XIAO quick start
View nodes and messages through my XIAO Curses dashboard
Connect my computer directly to the Meshtastic radio Direct desktop dashboard
Use a Pico, Pico W, or ESP32 Other boards
Write my own program Python API

How the XIAO setup works

The XIAO is the computer running MicroMesh. The Meshtastic device is the LoRa radio.

 Mac / PC                    XIAO RP2040                Meshtastic radio
┌─────────┐   USB/REPL    ┌────────────────┐   UART   ┌─────────────────┐
│ terminal│◀─────────────▶│ MicroPython +  │◀────────▶│ Meshtastic      │◀──▶ LoRa
│ or UI   │               │ MicroMesh      │ 3 wires  │ firmware        │
└─────────┘               └────────────────┘          └─────────────────┘

The XIAO does not contain a LoRa radio. It controls a separate Meshtastic device over UART.

Before you begin

You need:

  • A Seeed Studio XIAO RP2040 with MicroPython installed
  • A separate Meshtastic-compatible radio running Meshtastic firmware
  • Three jumper wires for TX, RX, and GND
  • A data-capable USB cable for the XIAO
  • Python 3.8 or newer on your Mac, Linux computer, or Windows PC

For the UART-through-microcontroller setups, configure the Meshtastic radio's serial module for:

Enabled: yes
Mode:    PROTO
Baud:    115200 (must match the MicroPython UART)

Meshtastic's serial-module default is 38400 baud; these examples deliberately use 115200. Set both ends to the same value. A direct USB connection to the radio does not use this external-UART setup.

The radio's UART pin names depend on its model. Consult that board's pinout before connecting wires.

Caution

Use 3.3 V UART logic and always connect the grounds. Do not connect the boards' power pins unless you have verified their voltage and current requirements.

XIAO RP2040 quick start

1. Wire the boards

UART wires cross: TX goes to RX, and RX goes to TX.

XIAO RP2040                         Meshtastic radio
────────────────────────────────────────────────────
D6 / GPIO0 / UART0 TX  ──────────▶ UART RX
D7 / GPIO1 / UART0 RX  ◀────────── UART TX
GND                    ─────────── GND

The XIAO D6/D7 mapping comes from the XIAO RP2040 pinout.

2. Prepare a Python environment

Run these commands from the MicroMesh repository directory:

python3 -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade mpremote

Windows PowerShell uses this activation command instead:

.venv\Scripts\Activate.ps1

3. Find the XIAO serial port

Connect the XIAO over USB, then run:

mpremote connect list

On macOS it will usually look similar to /dev/cu.usbmodem2101. Linux commonly uses /dev/ttyACM0; Windows uses a name such as COM4.

The commands below use /dev/cu.usbmodem2101. Replace it with your port.

4. Install MicroMesh on the XIAO

mpremote connect /dev/cu.usbmodem2101 mip install package.json

This installs the micromesh package under /lib on the board.

5. Run the example

The example broadcasts one hello from XIAO RP2040 message after each successful configuration download. Comment out its mesh.sendText(...) line first if you want a receive-only test.

mpremote connect /dev/cu.usbmodem2101 run examples/xiao_rp2040.py

You should immediately see:

MicroMesh starting on XIAO RP2040
UART0: D6/GPIO0 TX -> radio RX
UART0: D7/GPIO1 RX <- radio TX
UART0: 115200 baud; common GND required
configuration requested; id=...

When the radio finishes sending its configuration and node database:

radio configuration complete; sending greeting
connected; 17 nodes known

The number of nodes will be different for your radio. Press Ctrl-C to stop.

6. Start it automatically

Once the temporary run works, save it as the XIAO boot program:

mpremote connect /dev/cu.usbmodem2101 fs cp examples/xiao_rp2040.py :main.py
mpremote connect /dev/cu.usbmodem2101 reset

To replace an automatically running script, upload a different main.py; mpremote stops the running program before filesystem commands. To disable automatic startup without replacing it:

mpremote connect /dev/cu.usbmodem2101 rm :main.py
mpremote connect /dev/cu.usbmodem2101 reset

Curses dashboard through the XIAO

The desktop curses dashboard provides keyboard message composition and a resizable UI. It displays live connection stats, known nodes, battery levels, SNR, hops, last-seen times, and incoming messages. The XIAO runs a small bridge between the radio UART and the dashboard over USB.

The header shows Config: complete after a matching configuration handshake. Some radios do not return the matching completion ID; once local and node data are usable, the dashboard shows Config: ready instead.

The dashboard needs a small bridge program on the XIAO. The regular xiao_rp2040.py example prints human-readable logs; it does not expose structured data to desktop applications.

Complete the XIAO quick-start installation through step 4 first, so the micromesh package is present on the board.

1. Install the desktop dependencies

source .venv/bin/activate
python -m pip install micromesh pyserial

Windows users also need:

python -m pip install windows-curses

2. Install the bridge on the XIAO

This replaces the current main.py:

mpremote connect /dev/cu.usbmodem2101 fs cp examples/xiao_dashboard_bridge.py :main.py
mpremote connect /dev/cu.usbmodem2101 reset

3. Close mpremote, then launch the dashboard

python examples/curses_dashboard.py --xiao-bridge /dev/cu.usbmodem2101

Only one application can own a serial port. Close mpremote, Arduino Serial Monitor, screen, minicom, and other serial tools before launching the dashboard.

The message field is always active. Type a message and press Enter—there is no separate compose mode. message queued means the radio accepted the packet for transmission; it is not a delivery receipt.

Key Action
Type + Enter Broadcast the text in the message field
Escape Clear the message field
F5 (or Ctrl-R) Request the configuration and node database again
F10 (or Ctrl-Q) Quit cleanly

To switch back to the regular logging example:

mpremote connect /dev/cu.usbmodem2101 fs cp examples/xiao_rp2040.py :main.py
mpremote connect /dev/cu.usbmodem2101 reset

Direct-to-radio dashboard

If the Meshtastic radio itself is connected to your computer over USB, the desktop can talk directly to it. Do not use --xiao-bridge in this mode.

python -m pip install micromesh pyserial
python -m serial.tools.list_ports -v
python examples/curses_dashboard.py /dev/cu.YOUR_RADIO_PORT

The XIAO's USB port identifies as a MicroPython board. Direct mode will not work with that port; use --xiao-bridge when the radio is wired through the XIAO.

Other boards

Ready-to-run examples are included for common MicroPython boards:

Board Example UART TX RX
Seeed Studio XIAO RP2040 xiao_rp2040.py UART0 D6 / GPIO0 D7 / GPIO1
Raspberry Pi Pico / Pico W raspberry_pi_pico.py UART0 GP0 GP1
Generic ESP32 esp32.py UART2 GPIO17 GPIO16

Change the UART and pin numbers if your board uses a different mapping. See the complete examples guide for wiring notes.

Troubleshooting

could not enter raw repl

Use the exact port instead of connect auto:

mpremote connect list
mpremote connect /dev/cu.usbmodem2101 exec "print('MicroPython REPL OK')"

If it still fails, close every other serial application, unplug and reconnect the XIAO, and try again.

It repeatedly says waiting for radio data

The XIAO is running, but no complete Meshtastic frame has arrived. Check:

  1. XIAO D6/TX goes to radio RX—not radio TX.
  2. XIAO D7/RX goes to radio TX.
  3. Both boards share GND.
  4. The radio UART is enabled in PROTO mode at 115200 baud.
  5. The configured radio UART pins match the pins you physically connected.

The desktop curses dashboard stays at configuration requested

If the radio is wired through the XIAO, install xiao_dashboard_bridge.py as main.py and include --xiao-bridge in the dashboard command. Without the bridge, the desktop sends Meshtastic data to the MicroPython REPL instead of UART0.

Also confirm that mpremote or another serial monitor is not holding the port.

The node count appears, but no messages appear

The node list is stored data downloaded from the radio. It is not a message history. Send a new text from another Meshtastic node while MicroMesh is running.

A frame is skipped or reports a decode error

Meshtastic's protobuf schema evolves. Unknown fields are retained automatically. When an on_error callback is configured—as it is in the board examples—MicroMesh reports and skips a frame if it is malformed or conflicts with the compact schema; later frames continue processing. Without that callback, poll() raises the decoding error. Update the installed files after pulling a newer MicroMesh version:

mpremote connect /dev/cu.usbmodem2101 mip install package.json

The serial port is busy

One port can have only one owner. Quit the dashboard before using mpremote, and close mpremote before restarting the dashboard.

Python API

Here is the smallest useful program:

from machine import Pin, UART
from time import sleep_ms

from micromesh import PortNum, SerialInterface


def received(packet):
    if packet.WhichOneof("payload_variant") != "decoded":
        return
    if packet.decoded.portnum == PortNum.TEXT_MESSAGE_APP:
        try:
            text = packet.decoded.payload.decode("utf-8")
        except UnicodeError:
            text = repr(packet.decoded.payload)
        print("from !%08x: %s" % (packet.from_, text))


def decode_error(error, payload):
    print("skipping undecodable frame:", error, "(%d bytes)" % len(payload))


uart = UART(0, 115200, tx=Pin(0), rx=Pin(1), timeout=0, rxbuf=1024)
mesh = SerialInterface(uart, on_packet=received, on_error=decode_error)
mesh.connect()

while True:
    mesh.poll()
    sleep_ms(10)

Send packets

# Broadcast text on the primary channel
mesh.sendText("hello mesh")

# Direct message with an acknowledgement
mesh.sendText("hello", destinationId="!a1b2c3d4", wantAck=True)

# Position packet
mesh.sendPosition(45.5152, -122.6784, altitude=15)

# Custom application data
mesh.sendData(b"custom", portNum=PortNum.PRIVATE_APP)

Data payloads may be at most 233 bytes, including UTF-8 encoded text bytes rather than characters. poll() is non-blocking when the UART uses timeout=0, so call it frequently from your main loop.

Inspect connection state

if mesh.config_complete:
    print("connected to", mesh.my_info.my_node_num)
    print("known nodes:", len(mesh.nodes))

for number, info in mesh.nodes.items():
    name = info.user.long_name if info.HasField("user") else "unknown"
    print("!%08x %s" % (number, name))

Use the lightweight protobuf codec

from micromesh import Data, PortNum

message = Data(portnum=PortNum.TEXT_MESSAGE_APP, payload=b"hello")
encoded = message.SerializeToString()
decoded = Data().ParseFromString(encoded)
print(decoded.payload)

Generated-module-style imports are available as micromesh.mesh_pb2 and micromesh.portnums_pb2. A protobuf field named from is accessed as from_ because from is a Python keyword.

Installation alternatives

Install from a local checkout with MicroPython's package installer:

mpremote mip install package.json

Or copy the package directory manually:

mpremote fs cp -r micromesh :lib/

Install the published package from PyPI:

python -m pip install micromesh

For editable CPython development from a repository checkout:

python -m pip install -e .

Supported scope

MicroMesh currently models the most useful portions of the Meshtastic API:

  • ToRadio / FromRadio framing and configuration handshake
  • MeshPacket, Data, Position, User, and NodeInfo
  • Device metrics, waypoints, routing, queue status, and log records
  • Unknown-field preservation and stream resynchronization

Large configuration, admin, metadata, and module-configuration messages are retained as encoded bytes rather than fully interpreted.

The protocol definitions come from meshtastic/protobufs.

Development

Set up the project and run the tests:

python3 -m venv .venv
source .venv/bin/activate
python -m pip install -e .
python -m unittest discover -s tests

The test suite covers encoding, decoding, field presence, framing, serial recovery, sending, input validation, and configuration state.

Releasing to PyPI

MicroMesh publishes with GitHub Trusted Publishing, so no long-lived PyPI token is stored in the repository. The publisher configuration is:

Setting Value
PyPI project micromesh
GitHub owner pdxlocations
Repository micromesh
Workflow release.yaml
Environment pypi

Keep the version in pyproject.toml, package.json, and micromesh/__init__.py in sync. Publishing a matching GitHub release, such as 0.1.0, builds, validates, and uploads the wheel and source distribution.

License

MicroMesh is released under the GNU General Public License v3.0 or later.

About

No description, website, or topics provided.

Resources

Stars

3 stars

Watchers

2 watching

Forks

Releases

Packages

Contributors

Languages