Skip to content
Contribute data · Advanced

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.

Before you start

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

The same warning as the firmware method, moved one box over. A Pi that browns out takes the observer down with it, and the repeater keeps repeating none the wiser, so the failure is quiet. Mains power, and put it somewhere you will notice.

Your repeater keeps its identity

The bridge reads the device's key over serial and signs with it, so the observer reports under the same public key the mesh already knows. You are not creating a second node, and there is no separate identity to register.
Walkthrough

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.

  1. 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=1

    Adding -D MESH_DEBUG=1 as 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.

  2. 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.

  3. 03

    Install the bridge

    Upstream ships an installer that does the whole setup: creates a dedicated mctomqtt system 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 bash

    That 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.sh

    The 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.

  4. 04

    Point it at Comchan

    Config is layered. The installer owns /etc/mctomqtt/config.toml and 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.toml

    Put 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. Setting method = "token" tells the bridge to read the repeater's private key over serial and sign a JWT with it, connecting as v1_<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.

  5. 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 -f

    The 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 mctomqtt user is not in the group that owns it (dialout on most distributions, uucp on 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.

Alternative

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.toml

Run 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:latest

The 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 mctomqtt

The 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.