# MicroMesh πŸ“» **A tiny, dependency-free Meshtastic client for MicroPython.** 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. > [!NOTE] > MicroMesh is young and intentionally small. It supports the most useful Meshtastic > client operations, but it is not yet a complete replacement for the official desktop > [`meshtastic`](https://github.com/meshtastic/python) 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 skip newer messages it cannot decode - 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](#xiao-rp2040-quick-start) | | View nodes and messages through my XIAO | [Curses dashboard](#curses-dashboard-through-the-xiao) | | Connect my computer directly to the Meshtastic radio | [Direct desktop dashboard](#direct-to-radio-dashboard) | | Use a Pico, Pico W, or ESP32 | [Other boards](#other-boards) | | Write my own program | [Python API](#python-api) | ## How the XIAO setup works The XIAO is the computer running MicroMesh. The Meshtastic device is the LoRa radio. ```text 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 on your Mac, Linux computer, or Windows PC Configure the Meshtastic radio's serial interface for: ```text Enabled: yes Mode: PROTO Baud: 115200 ``` 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. ```text 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`](https://wiki.seeedstudio.com/XIAO-RP2040/). ### 2. Prepare a Python environment Run these commands from the MicroMesh repository directory: ```sh python3 -m venv .venv source .venv/bin/activate python -m pip install --upgrade mpremote ``` Windows PowerShell uses this activation command instead: ```powershell .venv\Scripts\Activate.ps1 ``` ### 3. Find the XIAO serial port Connect the XIAO over USB, then run: ```sh 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 ```sh 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. ```sh mpremote connect /dev/cu.usbmodem2101 run examples/xiao_rp2040.py ``` You should immediately see: ```text 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: ```text 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: ```sh mpremote connect /dev/cu.usbmodem2101 fs cp examples/xiao_rp2040.py :main.py mpremote connect /dev/cu.usbmodem2101 reset ``` To stop or replace an automatically running script, connect the XIAO, press `Ctrl-C` in `mpremote repl`, and upload a different `main.py`. ## 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 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. ### 1. Install the desktop dependencies ```sh source .venv/bin/activate python -m pip install -e . pyserial ``` Windows users also need: ```powershell python -m pip install windows-curses ``` ### 2. Install the bridge on the XIAO This replaces the current `main.py`: ```sh 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 ```sh 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. | 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: ```sh 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. ```sh python -m pip install -e . 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`](examples/xiao_rp2040.py) | UART0 | D6 / GPIO0 | D7 / GPIO1 | | Raspberry Pi Pico / Pico W | [`raspberry_pi_pico.py`](examples/raspberry_pi_pico.py) | UART0 | GP0 | GP1 | | Generic ESP32 | [`esp32.py`](examples/esp32.py) | UART2 | GPIO17 | GPIO16 | Change the UART and pin numbers if your board uses a different mapping. See the complete [examples guide](examples/README.md) for wiring notes. ## Troubleshooting ### `could not enter raw repl` Use the exact port instead of `connect auto`: ```sh 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. MicroMesh reports and skips newer messages that its compact schema does not yet understand. Text and other supported packets continue processing. Update the installed files after pulling a newer MicroMesh version: ```sh 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: ```python 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: print("from !%08x: %s" % ( packet.from_, packet.decoded.payload.decode("utf-8"), )) uart = UART(0, 115200, tx=Pin(0), rx=Pin(1), timeout=0, rxbuf=1024) mesh = SerialInterface(uart, on_packet=received) mesh.connect() while True: mesh.poll() sleep_ms(10) ``` ### Send packets ```python # 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) ``` Payloads may be at most 233 bytes. `poll()` is non-blocking when the UART uses `timeout=0`, so call it frequently from your main loop. ### Inspect connection state ```python 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 ```python 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: ```sh mpremote mip install package.json ``` Or copy the package directory manually: ```sh mpremote fs cp -r micromesh :lib/ ``` For CPython development: ```sh python -m pip install -e . ``` After the first PyPI release: ```sh python -m pip install micromesh ``` ## 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`](https://github.com/meshtastic/protobufs). ## Development Set up the project and run the tests: ```sh 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, framing, serial recovery, sending, and configuration state. ## Releasing to PyPI Maintainers publish with GitHub Trusted Publishing; no long-lived PyPI token is stored in the repository. Configure the pending publisher with: | 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 `v0.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](LICENSE).