docs/uconsole.md walks a uConsole with the AIO LoRa board from requirements to a running bridge and a connected MeshTerm: the system prep, the radio runtime (and the bookworm/trixie split), the preflight, a foreground run, the user service, the profile, updating, troubleshooting, and what has been tested. Plain language, one step at a time; docs/hardware.md stays as the reference behind it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
13 KiB
MeshTerm on the uConsole
A ClockworkPi uConsole with the hackergadgets AIO LoRa board has no companion microcontroller and no firmware to flash — the LoRa chip sits straight on the host's SPI bus. So a small bridge runs the mesh node in software on the uConsole itself and puts the standard MeshCore companion protocol in front of it on a local TCP port. MeshTerm then connects to that port like it would any network companion. By the end of this guide you'll have the bridge running as a background service and MeshTerm talking to it.
- What you need
- Step 1 — Prepare the system
- Step 2 — Install the radio runtime
- Step 3 — Get the bridge
- Step 4 — Run the preflight
- Step 5 — Run it once in the foreground
- Step 6 — Install it as a service
- Step 7 — Install MeshTerm and connect
- Updating and uninstalling
- Troubleshooting
- What this has been tested on
What you need
Hardware — one of:
| Board | Notes |
|---|---|
| ClockworkPi uConsole (CM4 module) + hackergadgets AIO LoRa board | SX1262 on SPI bus 1. This guide's defaults are tuned for it. |
| A Raspberry Pi + Waveshare SX1262 LoRa HAT | Also supported. Its pins differ from the AIO's, so set them through the MESHCORE_* variables in the Hardware knobs table of docs/hardware.md. |
| An antenna for your region's LoRa band | Required either way. |
Installing the AIO board into the uConsole is out of scope here — follow hackergadgets' own setup guide for that part.
The bridge assumes the AIO's wiring by default:
| Setting | Default |
|---|---|
| SPI bus | 1 |
| Reset pin | 25 |
| Busy pin | 24 |
| IRQ pin | 26 |
| TX-enable pin | none |
| DIO2 (RF switch) | on |
| DIO3 (TCXO) | on |
| Frequency | 910.525 MHz |
| TX power | 22 dBm |
| Spreading factor | 7 |
| Bandwidth | 62.5 kHz |
| Coding rate | 4/5 |
Every one of these is overridable with a MESHCORE_* environment variable if your board or
region differs — see the Hardware knobs table in docs/hardware.md.
Software:
- Raspberry Pi OS / Debian bookworm or trixie on the uConsole, 64-bit.
- Python 3 (the system one is fine — the bridge finds whichever interpreter has the radio library, and re-execs itself under it).
- A
/dev/spidev*node. This needsdtoverlay=spi1-1csin/boot/firmware/config.txt. /dev/gpiochip0, and membership of thespiandgpiogroups so you can read and write both without root.- Optionally, the
meshcore-uconsolepackage (it brings themeshcore-consoleGUI and a radio runtime). Whether you can use it depends on your Debian release — see Step 2.
The radio library that actually drives the chip was renamed in 2026: pymc_core became
openhop_core. PyPI's pymc-core stopped at 1.0.12; openhop-core carries on from 1.1.x.
The bridge supports both and prefers the newer one when it finds both installed.
MeshTerm itself — either:
- the Linux ARM64 one-file release from the latest release page, or
- an install from the repository with
pipx(Python 3.10 or newer). Plainpip installinto the system Python is refused on bookworm and trixie;pipx(sudo apt install pipx) gives MeshTerm a virtual environment of its own.
Step 1 — Prepare the system
Enable the SPI overlay. Edit /boot/firmware/config.txt and add:
dtoverlay=spi1-1cs
Add yourself to the groups that own the SPI and GPIO device nodes:
sudo usermod -aG spi,gpio $USER
Reboot so both the overlay and the group membership take effect:
sudo reboot
After it comes back, check the device nodes exist and that you can use them:
ls -l /dev/spidev* /dev/gpiochip0
groups
You should see /dev/spidev1.0 (or another spidev* node) and /dev/gpiochip0 listed,
and spi and gpio in your groups output.
Step 2 — Install the radio runtime
Which route you take depends on your Debian release:
- trixie, with
meshcore-uconsoleinstalled from the vendor's package: you already have a working runtime (Python 3.13-based). Skip to Step 3. - bookworm: the
meshcore-uconsole1.12.0+ package is built for Python 3.13 and will not run — its virtualenv points at an interpreter bookworm doesn't have. Don't take the upgrade if apt offers it. Instead give the bridge a runtime of its own:
python3 -m venv ~/.local/share/meshterm-spi-bridge/venv
~/.local/share/meshterm-spi-bridge/venv/bin/pip install "openhop-core[hardware]"
This is also the route to take on any Debian-family host that isn't running the
meshcore-uconsole package at all (a Waveshare HAT on a plain Raspberry Pi OS install, for
example).
You'll confirm which runtime the bridge actually found in Step 4 — its first check line names the interpreter and the runtime.
Step 3 — Get the bridge
scripts/meshterm-spi-bridge is a single, self-contained Python file — no install step of
its own. Either clone the MeshTerm repo:
git clone https://github.com/jpmartineau/MeshTerm
cd MeshTerm/scripts
or download just the one file:
curl -L -o meshterm-spi-bridge \
https://raw.githubusercontent.com/jpmartineau/MeshTerm/main/scripts/meshterm-spi-bridge
Make it executable:
chmod +x meshterm-spi-bridge
Step 4 — Run the preflight
./meshterm-spi-bridge --check
This prints a Prerequisite check: block with one line per check. What each line means and
what to do if it fails:
| Check | Meaning if it fails | Fix |
|---|---|---|
node runtime |
Neither openhop_core nor pymc_core is importable by any interpreter the bridge tried. |
Build the venv from Step 2, or set MESHTERM_PYMC_PYTHON to a python that has one. |
SPI device |
No /dev/spidev* node exists, or it exists but you can't read/write it. |
No node at all: add dtoverlay=spi1-1cs to /boot/firmware/config.txt and reboot. Node present but no permission: sudo usermod -aG spi $USER, then log out and back in. |
GPIO chip |
/dev/gpiochip0 is missing or not read/write for you. |
sudo usermod -aG gpio $USER, then log out and back in. |
bridge service / radio in use / port 5000 busy / radio is free |
Tells you whether something already holds the radio: the bridge's own boot service, a running meshcore-console, or something else already listening on the TCP port. |
Only one program may use the radio at a time — see Step 7. If meshcore-console is running, close it before running the bridge. |
If the first three checks all pass, you're ready to run it.
Step 5 — Run it once in the foreground
./meshterm-spi-bridge --run
This runs the preflight again, prints it, then starts the bridge in this terminal. A healthy startup logs something like:
node runtime openhop_core 1.1.3 (/home/you/.local/share/meshterm-spi-bridge/venv/bin/python)
radio config bus_id=1 reset_pin=25 busy_pin=24 irq_pin=26 frequency=910525000 tx_power=22
radio.begin() attempt 1/4
radio ready
node identity ab12cd34ef567890… (hash 0xab, name 'uConsole')
READY — connect with: meshterm --tcp 127.0.0.1:5000
The radio config line lists every setting as key=value, so the one above is
shortened; yours is longer.
Leave it running, and in another terminal on the same machine try:
meshterm --tcp 127.0.0.1:5000
Back in the bridge's terminal, press Ctrl+C to stop it.
Step 6 — Install it as a service
Run the bridge with no flags to get the interactive menu:
./meshterm-spi-bridge
Choose 2) Install as a service. This:
- copies the script to
~/.local/bin/meshterm-spi-bridge, - writes a
systemd --userunit at~/.config/systemd/user/meshterm-spi-bridge.servicethat restarts on failure and starts with your session, - enables and starts it now,
- tries to enable lingering for your user, so the service comes up at boot even before you log in. If it can't, it prints the command to run yourself:
sudo loginctl enable-linger $USER
It also offers to add a [profiles.bridge] block to your MeshTerm config.toml — say yes,
it's the profile you'll use in Step 7.
Check on it any time:
systemctl --user status meshterm-spi-bridge.service
journalctl --user -u meshterm-spi-bridge.service -f
Menu option 4) Show status prints the same information from inside the script.
Step 7 — Install MeshTerm and connect
Install MeshTerm on the uConsole. The simplest way is the Linux ARM64 one-file release:
chmod +x meshterm-*-linux-arm64
sudo mv meshterm-*-linux-arm64 /usr/local/bin/meshterm
Or install it from the repository with pipx:
sudo apt install pipx
pipx install git+https://github.com/jpmartineau/MeshTerm
pipx ensurepath
If you accepted the profile in Step 6, your config.toml now has:
[profiles.bridge]
host = "127.0.0.1"
tcp_port = 5000
description = "Local SPI radio via meshterm-spi-bridge"
Connect with:
meshterm -p bridge
or, without a profile:
meshterm --tcp 127.0.0.1:5000
You're connected when the device page or dashboard shows a node name (uConsole by
default) instead of a connection error. MeshTerm reports the device model as
pyMC-spi-bridge — that's how you tell a bridged node from a real companion.
Identity. If the meshcore-console GUI's package is present, the bridge loads that
package's identity key, so MeshTerm and the GUI are the same node — same contacts, same
public key. On a library-only install, the bridge mints and keeps its own identity key
under ~/.local/share/meshterm-spi-bridge/identity.key.
Only one program may use the radio at a time. Never run the bridge and the
meshcore-console GUI at the same time — whichever started second will fail to open the
radio.
Updating and uninstalling
To update, pull or re-download meshterm-spi-bridge and reinstall the service (menu option
2 again — it overwrites the copy in ~/.local/bin/).
To remove the bridge, run the menu and choose 3) Uninstall the service. This stops and
removes the systemd --user unit and the installed script copy. It does not touch
meshcore-console, the radio library, your node identity, or your saved contacts — those
stay put. If you'd added [profiles.bridge] to your MeshTerm config, it tells you to remove
that block by hand if you no longer want it.
Troubleshooting
| Symptom | Check |
|---|---|
node runtime fails in the preflight |
Did you build the venv in Step 2? Is MESHTERM_PYMC_PYTHON pointing at the right interpreter, if set? |
SPI device shows "no /dev/spidev* found" |
Is dtoverlay=spi1-1cs in /boot/firmware/config.txt? Did you reboot after adding it? |
SPI device or GPIO chip shows a permission error |
Are you in the spi and gpio groups (groups)? Did you log out and back in after usermod? |
| Preflight warns "radio in use — meshcore-console is running" | Close the GUI before running or installing the bridge. |
| Preflight warns "port 5000 busy" | Something else is already listening there — check systemctl --user status meshterm-spi-bridge.service for an already-running instance. |
Bridge starts but radio.begin() fails after 4 attempts |
GPIO lines may still be held by a session that just closed — wait a few seconds and try again. |
| MeshTerm connects but direct messages to an old contact say "not found" | Restart the bridge — contacts are restored from its snapshot file at startup; if the snapshot is missing, the contact needs to be heard again. |
| Reboot, factory reset, or setting a device PIN silently does nothing | Expected — the radio library has no handler for these three, whatever runtime you're on. |
| Traces or the live feed don't show up | These rely on compatibility shims the bridge installs (or, under openhop_core, on the library's own equivalents). Check the bridge's log for lines starting compat: to confirm they're wired. |
What this has been tested on
This has been verified on a bookworm uConsole with openhop-core 1.1.3: the radio comes up,
contacts are restored across a restart, and info and contacts answer correctly over the
TCP connection. Tracing, sending messages, and the live packet feed under that same runtime
have not yet been exercised end-to-end, though the runtime's own trace-push support is what
the bridge relies on for the first of those. The older pymc-core 1.0.x path, with all
three of the bridge's compatibility shims active, has been exercised in full, including
traces and the live feed. For the complete, honest list of what is and isn't verified —
including the on-air trace and acknowledgement behaviour — see
"What is verified and what is not" in
docs/hardware.md.