Bridge a repeater with meshcoretomqtt
The other route to an observer. Instead of the node uplinking over its own WiFi, a repeater stays wired to a small always-on computer, and that machine publishes what the radio hears to our collector.
Is this the method you want?
For most people it is not, and that is fine. The observer firmware turns one ESP32 board into a repeater and an observer at once, with nothing else to run and nothing else to maintain. If your board is on that list and the site has WiFi, start there.
This page is for the cases that method cannot reach. It costs you a second device to look after, and it earns you a node that would otherwise never report at all.
What it actually does
The repeater is flashed with a build that prints every packet it hears to its serial console. The bridge reads that stream over USB, parses it, and publishes it to MQTT. The radio itself never touches the network.
One consequence worth understanding up front: because the packets are read off the wire and forwarded as raw data, everything the repeater hears is uplinked. That is equally true of the firmware method, and neither one can read message contents, MeshCore's encryption is unaffected either way.
Your board has no WiFi
This is the big one. The observer firmware is ESP32-only, so a RAK WisBlock 4631, a XIAO nRF52, or any other nRF52 repeater simply cannot run it. Bridged over USB, those boards make perfectly good observers.
You already have a Pi at the site
If something is already running at the install anyway, adding the bridge costs one USB cable. It also keeps the radio's own firmware stock, which is one less thing to reflash when MeshCore updates.
You want the host's diagnostics
Logs land in journalctl, the process is restartable without touching the radio, and you can watch the serial stream directly when something looks wrong. Harder to do when the node uplinks on its own.
What this needs
Two pieces of hardware and one thing that trips people up, which is the firmware on the radio.
A MeshCore repeater
Any board, including the nRF52 ones the firmware method rules out. It has to be running a build with packet logging compiled in, which stock repeater firmware does not have. Step 01 covers that.
An always-on host
A Raspberry Pi (Zero 2, 3, 4, or 5), a small server, or a Mac. Linux or macOS, Python 3.11 or newer, and a wired or reliable wireless network connection.
A USB data cable
Charge-only cables are the classic wasted hour here. The host has to actually enumerate a serial device, which step 02 has you confirm before going any further.
The host has to stay powered too
Your repeater keeps its identity
Standing it up
Flash the radio, wire it to the host, install the service, then point it at us. The installer handles the Python environment and the service definition for you.
- 01
Flash a packet-logging build
This is the prerequisite people miss. A stock repeater build does not print packets to serial, so the bridge would connect happily and forward nothing. You need a debug or packet-logging image.
Prebuilt images are the easy path. LetsMesh hosts them behind its observer onboarding flow, pick Repeater as the node type:
Prebuilt debug firmware on LetsMesh↗
That page asks you to acknowledge a legal notice before it shows the downloads, so read it and accept it yourself, we have not done that on your behalf. If you would rather compile your own, the flag you need is:
-D MESH_PACKET_LOGGING=1Adding
-D MESH_DEBUG=1as well gets you the extra debug topic. It is optional, and it is noisier.Firmware version matters for token auth
Token authentication works by reading the private key off the device with
get prv.key, which needs MeshCore v1.8.0 or later. On older firmware the bridge cannot sign anything and the connection to our broker will fail. - 02
Connect the radio and find its serial port
Plug the repeater into the host over USB, then confirm the host actually sees it. Everything after this depends on having the right device path.
ls -l /dev/serial/by-id/ # or, more bluntly: ls /dev/ttyACM* /dev/ttyUSB*Most RAK and Heltec boards come up as
/dev/ttyACM0; boards behind a CP210x or CH340 USB-serial chip usually land on/dev/ttyUSB0. If nothing appears at all, suspect the cable before anything else.Note the path down. If the host has more than one USB serial device attached, prefer the stable
/dev/serial/by-id/…path, which survives a reboot reordering the numbered ones. - 03
Install the bridge
Upstream ships an installer that does the whole setup: creates a dedicated
mctomqttsystem user, installs to/opt/mctomqtt, builds a Python virtual environment, writes config to/etc/mctomqtt, and registers a systemd service (or a launchd daemon on macOS).curl -fsSL https://raw.githubusercontent.com/Cisien/meshcoretomqtt/main/install.sh | sudo bashThat is a remote script piped into a root shell, which is worth being deliberate about. If you would rather read it first, and on a node you are going to leave running unattended that is a reasonable instinct:
curl -fsSL https://raw.githubusercontent.com/Cisien/meshcoretomqtt/main/install.sh -o install.sh less install.sh sudo bash install.shThe installer will ask you some questions, including which broker presets to add. You can skip the broker prompts entirely and do it in step 04, which is what we recommend, our broker is not one of the bundled presets so it has to be written by hand either way.
It also offers a Docker deployment instead of a system service. If that is what you want, jump to running it in Docker below, the configuration in step 04 is identical either way.
- 04
Point it at Comchan
Config is layered. The installer owns
/etc/mctomqtt/config.tomland overwrites it on every update, so your settings go in a drop-in file that it never touches.sudo nano /etc/mctomqtt/config.d/99-user.tomlPut this in it, adjusting the serial port to whatever you found in step 02:
/etc/mctomqtt/config.d/99-user.toml [general] iata = "AUS" [serial] # Whatever the repeater enumerated as. See step 02. ports = ["/dev/ttyACM0"] [[broker]] name = "comchan" enabled = true server = "obs.comchan.net" port = 443 transport = "websockets" keepalive = 60 qos = 0 retain = true [broker.tls] enabled = true verify = true [broker.auth] # Signs a JWT with the repeater's own Ed25519 key, read over serial. # No username, no password, nothing to issue. method = "token" audience = "obs.comchan.net"The
[broker.auth]block is the part that matters. Settingmethod = "token"tells the bridge to read the repeater's private key over serial and sign a JWT with it, connecting asv1_<PUBLIC_KEY>. The key is used for signing only, it is never written to disk and never sent anywhere. That is the same scheme the observer firmware uses, which is why neither method asks you for a credential.Reporting to more than one collector
Add further
[[broker]]blocks and they all run at once. Unlike the firmware, which is boxed in by how much memory a TLS stack needs, a Pi will happily hold several connections open, so there is no reason to choose between us and the wider community analyzers here. - 05
Start it and read the logs
Bring the service up, then watch it actually connect.
sudo systemctl enable --now mctomqtt sudo systemctl status mctomqtt sudo journalctl -u mctomqtt -fThe log is where this method earns its keep. A healthy start shows the serial port opening, the public key being read off the device, and the broker connection coming up. The three failures you are likely to see, in the order they bite:
- Permission denied on the serial port, the
mctomqttuser is not in the group that owns it (dialouton most distributions,uucpon Arch). - The key read fails, the firmware predates
get prv.key. Back to step 01. - The broker rejects the token, usually a clock problem. JWTs carry an expiry, so a host whose time is wrong cannot authenticate. Make sure NTP is working.
Once it is up, your repeater should start appearing on the analyzer within a few minutes.
- Permission denied on the serial port, the
Running it in Docker
Same bridge, same config file, no Python environment on the host. There is a published multi-architecture image, so this works on a Pi without building anything.
Write the config first
The container mounts a config directory rather than carrying one, so create the same layout on the host and drop in the file from step 04.
sudo mkdir -p /etc/mctomqtt/config.d
sudo nano /etc/mctomqtt/config.d/99-user.tomlRun the container
The radio is passed through as a device, and the config directory is mounted read-only. Substitute your own serial port.
docker run -d \
--name mctomqtt \
--restart unless-stopped \
--device=/dev/ttyACM0 \
-v /etc/mctomqtt:/etc/mctomqtt:ro \
ghcr.io/cisien/meshcoretomqtt:latestThe image is published for linux/amd64, linux/arm64, and linux/arm/v7, which covers every Raspberry Pi worth leaving on a shelf. Docker picks the right one automatically.
Check on it the same way
docker logs -f mctomqtt
docker restart mctomqtt
docker pull ghcr.io/cisien/meshcoretomqtt:latest && docker restart mctomqttThe same three failure modes from step 05 apply. The serial permission one is more common here: if the container cannot open the device, check that the --device path is right and that the host actually has the radio attached at that path right now.
What you are agreeing to forward
Be clear-eyed about this before you turn it on. The bridge forwards every packet the repeater hears, as raw data, without filtering. It does not decrypt anything and it cannot, privacy on MeshCore comes from the channel keys, and an observer never has them. But the packets themselves, their timing, and their routing all leave your site and land on our collector.
That is the trade the whole analyzer runs on, and it is the same trade the firmware method makes. It is worth making deliberately rather than by accident, and you can stop at any time by stopping the service.
Neither method fitting?
There are several other community routes to an observer, reading a companion radio over USB, running a software repeater, or letting a Home Assistant integration do it. If your situation is unusual, one of them probably covers it.
