文档暂以英文提供 · docs are in English for now

DOCS / network & radio

Connect a LoRa radio to Quiet Spaces — the Meshtastic path

This is not the main path. Meshtastic in quite.space is an experimental feature: the driver exists and a node does attach to such a board, but the product scenario over radio has never been run on it end to end. The proven carrier is RNode, and that is where to start: Radio in quite.space.

This document remains the full Meshtastic guide — firmware, region, segment channel, profile and verification. Everything about the terminal radio * and quiet-radio utilities also lives here; the RNode documents do not repeat it, they only link to it.

Step by step, for the Heltec WiFi LoRa 32 V3 and any other Meshtastic node. Nothing needs to be known in advance — every step explains why it exists.


What is going on

Quiet Spaces does not drive the radio chip itself. It talks to the Meshtastic firmware already running on the board, which knows how to send and receive packets over the air. Our node connects to it over USB (or Wi-Fi) and asks: “send these bytes”.

Quiet Spaces  ──USB──▶  Meshtastic firmware  ──LoRa──▶  another radio

Two consequences follow, and they shape the whole process:

  1. The board must run Meshtastic. Heltec’s factory firmware (the one showing “LoRa mode 0” on the little screen) is the manufacturer’s demo program. It does not speak the Meshtastic protocol, and our node will not connect to it.
  2. Two radios hear each other only if three things match: region, modem preset, and the channel key. If even one differs, both radios transmit happily and neither hears anything. No error appears anywhere: silence is indistinguishable from “nobody is nearby”. That is exactly why there is a dedicated verification utility, see step 5.

Step 0. See what is connected at all

Plug the board in over USB and ask:

terminal radio list

The command walks every serial port and says what it found on each:

  /dev/cu.usbserial-0001           no Meshtastic here · chip esp32s3 (from its boot banner)
  /dev/cu.Bluetooth-Incoming-Port  Bluetooth port, not a USB radio

Three possible answers and what they mean:

Answer What to do
Meshtastic node … with a region and a preset the firmware is already there → step 3
no Meshtastic here · chip … firmware needed → step 1
busy — another program holds this port the port is taken: close the Meshtastic app, a serial monitor, or a second running node

Why an “empty” board needs a separate trick. A board without Meshtastic says nothing to the port at all — zero bytes at any baud rate. But every ESP32 prints its boot-ROM banner on reset, and radio list pulls the reset line precisely for that. That is where chip esp32s3 comes from.

This is the only thing one can learn about an unflashed board: the chip family. The board model does not follow from it — dozens of different boards sit on the ESP32-S3, with different radio wiring. So from here on, you choose the model, not us.

To inspect the detailed configuration of a radio that already works — quiet-radio reads it and changes nothing:

quiet-radio --radio serial:/dev/cu.usbserial-0001 --raw

Only one program holds a port at a time. If your node terminal ui --mesh serial:... is already running, it holds the port and quiet-radio will not connect. That is normal: stop the node while you configure.

How to tell the factory firmware for certain

Heltec’s factory firmware prints roughly this to the port after a reset:

ESP-ROM:esp32s3-20210327
rst:0x1 (POWERON),boot:0x9 (SPI_FAST_FLASH_BOOT)
select external 32K RTC
ESP32ChipID=08EAFCF61B44

and then stays silent. Meshtastic instead continuously prints lines like INFO | ??:??:?? ... and answers a configuration request.

The ESP-ROM:esp32s3 line also tells you the board is on an ESP32-S3 chip, which means it is a V3, not a V2 (the V2 has a plain ESP32). That matters when choosing the firmware file.


Step 1. Install the Meshtastic firmware

Method A — with our command

terminal radio flash

What it does, step by step:

  1. Finds the connected boards and shows the list — with the port and whatever it managed to identify on it.
  2. Asks Meshtastic which firmware release is current.
  3. Narrows the list of board variants by chip family and shows it. You choose: enter a number, or type part of a name to filter (heltec → only Heltec boards remain).
  4. Reads from the release the install recipe for that exact board: which files to write and at which addresses. We store no offsets of our own — everything comes from the release data, so it cannot go stale.
  5. Shows the whole plan: board, version, files, addresses, download size, and a warning that the board will be erased. It writes only after yes.
  6. Downloads only the required files (not the whole 170 MB archive — via partial HTTP requests) and checks each against the MD5 from the release. If one does not match, nothing is written to the board.
  7. Flashes through esptool and then re-reads the board: success is counted only when the device itself answers as a Meshtastic node.

esptool must be installed — the command will not install it for you:

pip install esptool

A program that silently downloads an executable from the internet and runs it on your hardware has taken a decision that was yours to take.

Method B — the web flasher

If a browser is more convenient (Chrome or Edge):

https://flasher.meshtastic.org

  1. Plug the board in over USB.
  2. Choose the model: Heltec V3 (for a board on the ESP32-S3).
  3. Choose the stable release (Stable).
  4. Press Flash and pick the board’s serial port.

⚠️ Flashing erases the factory program. It can be restored later — Heltec publishes its demo firmware — but that is a separate operation. Make sure you want this board as a network node.

The browser-free alternative is esptool.py and the release files from https://github.com/meshtastic/firmware/releases.

After flashing the board reboots, the Meshtastic logo appears on the little screen, and logs start flowing to the port. Run step 0 again — quiet-radio should now answer.


Step 2. Choose a region — a decision, not a setting

The region determines which frequency the board transmits on. That is a matter of your country’s law, not of convenience. Until a region is set, Meshtastic does not transmit at all.

terminal radio region

Without a name the command writes nothing — it reads the board and shows what it is currently set to, plus the list of accepted names:

/dev/cu.usbserial-0001 says it is set to:
  region        UNSET
  modem preset  LONG_FAST
  hop limit     3

UNSET means the board transmits nothing whatsoever. To set one:

terminal radio region EU_868

The command shows exactly what it will write, warns, and asks for confirmation; after the board reboots it re-reads it and shows what actually landed. Only a match counts as success.

Optional: --preset LONG_FAST and --hop 3. Without them only the region changes — what was not asked for is not touched.

A typo in the region name is an error, not a “zero”. EU868 instead of EU_868 would be written as UNSET, and the board would look configured while staying mute. So a name we do not know is rejected, with the list of accepted ones.

Region names the build understands:

US        EU_433    EU_868    CN        JP        ANZ       KR
TW        RU        IN        NZ_865    TH        LORA_24   UA_433
UA_868    MY_433    MY_919    SG_923    PH_433    PH_868    PH_915
ANZ_433

Presets: LONG_FAST (the default, longest practical range), LONG_SLOW, VERY_LONG_SLOW, MEDIUM_SLOW, MEDIUM_FAST, SHORT_SLOW, SHORT_FAST, LONG_MODERATE, SHORT_TURBO. The “longer” the preset, the greater the range and the lower the speed. Start with LONG_FAST.

Choose the one that matches where you are and the band your board is built for (on a Heltec it is usually printed on the antenna itself or stated in the product listing: 433 MHz or 868 MHz).

Never transmit without an antenna. A board with no antenna attached can be destroyed by its first transmission.


Step 3. Create the segment channel

A “segment” is your group of radio nodes that hear each other. It has its own channel with its own key, separate from the public one.

Start a node with the radio attached:

terminal ui --data ~/.quiet-places --name myname --mesh serial:/dev/cu.usbserial-0001

Open the interface, go to the Gateway screen and press Prepare this device. The node reads the radio and produces:

  • a channel link and QR code — open them in the Meshtastic app on the phone paired with the radio and choose ADD, not Replace (Replace would erase every channel already on the board);
  • ready-made commands for the official CLI, if that is more convenient;
  • the segment profile — a text file, see step 4;
  • the key fingerprint — a short string that lets you confirm another radio holds the same key without showing the key itself.

🔑 The channel link contains the key. Whoever receives it is on the segment. Treat it like a password and do not forward it over the radio network itself. It is shown once: we store no key anywhere, only its fingerprint.

Why we hand you a link instead of writing to the radio ourselves: to obtain the key it would have to be read off the board, and our configuration reader deliberately hashes keys at the moment of reading and discards the plaintext. We can be the source of a key, but not its extractor.

The same link must be applied on every radio of the segment. Different keys are guaranteed silence.

If the command line is more convenient

The Prepare screen produces ready-made lines of this shape (N is filled in automatically — the first free channel slot on your board):

meshtastic --port /dev/cu.usbserial-0001 --ch-add mysegment
meshtastic --port /dev/cu.usbserial-0001 --ch-index N --ch-set psk base64:...
meshtastic --port /dev/cu.usbserial-0001 --ch-index N --ch-set name mysegment

The third command is not redundant: the official CLI erases the channel name when it writes a key into it. Without it the channel stays nameless, and two radios will disagree about which channel counts as the same one.

And, if the region and preset are not set yet:

meshtastic --port /dev/cu.usbserial-0001 --set lora.region EU_868
meshtastic --port /dev/cu.usbserial-0001 --set lora.modem_preset LONG_FAST --set lora.use_preset true
meshtastic --port /dev/cu.usbserial-0001 --set lora.hop_limit 3

Step 4. Save the segment profile

The profile is a plain text file describing how every radio of the segment must be configured. It contains no key, only the fingerprint, so it can be passed around and committed to a repository freely.

It looks like this:

# Quiet Spaces radio profile — the settings every node on
# this segment must share. Contains no keys: `channel_key` is a
# fingerprint, which identifies a key without revealing it.
name          = mysegment
region        = EU_868
modem_preset  = LONG_FAST
hop_limit     = 3
tx_enabled    = true
channel_index = 1
channel_name  = mysegment
channel_key   = private:9f2a...

The Prepare screen hands it out directly. If a radio was configured by hand, the profile can be taken off it:

quiet-radio --radio serial:/dev/cu.usbserial-0001 \
  --channel 1 --save-profile mysegment.profile

--channel says which channel to capture. By default the primary one (index 0) is taken, and on most boards that is the public channel with the standard key; a profile taken from it would describe something other than your segment.


Step 5. Verify every radio against the profile

This is the check the whole exercise was for: it turns a silent mismatch into a concrete line saying “this field is wrong”.

quiet-radio --radio serial:/dev/cu.usbserial-0001 \
  --profile mysegment.profile

The exit codes differ meaningfully:

code meaning
0 everything checked matches
1 something does not match — the field will be named
3 nothing contradicts, but something could not be checked (an old firmware did not send that field)
2 could not parse the arguments or could not connect

The difference between 1 and 3 is deliberate: “this radio is configured wrong” and “this radio did not tell me” call for different actions. And whatever the node did not report counts as unknown, not as “the default value” — we do not describe somebody else’s hardware by guessing.


Step 6. Run a node on this segment

terminal ui \
  --data ~/.quiet-places --name myname \
  --mesh serial:/dev/cu.usbserial-0001 \
  --mesh-channel 1 \
  --radio-profile mysegment.profile

What each flag does:

  • --mesh serial:PATH — connect to the radio over USB. For a radio reachable over Wi-Fi, use --mesh tcp:192.168.1.50 instead.
  • --mesh-channel N — which channel to talk on. This matters: channel 0 is the board’s primary channel, usually the public one. Packets on it are seen and relayed by everyone in range (the content stays encrypted by our own layer, but the fact of the exchange is visible). Use your own channel’s number.
  • --radio-profile FILE — the Gateway screen will check the radio against the profile and name any mismatch. The profile also sets the channel number.

If everything lines up, the node prints:

mesh: channel 1 · connected as node 1234567890 via serial:/dev/cu.usbserial-0001 (raw wire, summaries every 60s — LoRa airtime)

You need two nodes with two radios configured to the same profile. Both nodes can run on one machine — one radio per USB port.

⚠️ The main trap: switch the other paths off. Two nodes on one machine find each other over the local network in a second, the message arrives instantly, and you conclude the radio works. It may not even be plugged in. A relay, if one is configured, does the same.

So, in a radio test: --no-lan on both nodes and no relay in the settings (a fresh data directory has none). Then the only path left is the air, and the result means exactly what it means.

Each node needs its own: data directory, name, web-interface port and, of course, its own serial port. The data directory is locked to one process — two nodes on one directory will not start, and that is correct.

# first
terminal ui --passphrase p1 --data /tmp/qp-a --name alice \
  --no-lan --port 8801 --mesh serial:/dev/cu.usbserial-0001 --mesh-channel 1

# second, in another terminal
terminal ui --passphrase p2 --data /tmp/qp-b --name bob \
  --no-lan --port 8802 --mesh serial:/dev/cu.usbserial-0002 --mesh-channel 1

terminal radio list will tell you the second board’s port — it shows both.

  1. Create a space on the first node and invite the second.
  2. Write a message and wait for it on the second.
  3. Detach one radio and confirm the messages stopped arriving. That is half of the test: without it you do not know it was the radio that delivered.

On distance: put the boards in different rooms or outside. Radios lying next to each other sometimes “deafen” one another — the receiver is overloaded by a transmitter ten centimetres away, and a link across a desk can be worse than one through two floors.

On speed. LoRa is single-digit kilobits per second shared between everyone. Text moves fine; photographs and audio will not go by radio. Our node prints a summary every 60 seconds precisely because the air is a shared and limited resource.


If nothing is heard

Go down the list top to bottom — it is sorted by how often each cause is the one.

Symptom Most likely
no Meshtastic here the board does not run Meshtastic — step 1
Serial port busy another process holds the port — the Meshtastic app, a second terminal, a serial monitor
The node connected, but packets do not arrive different channel keys — check the fingerprint against the profile (step 5)
The same, and the keys match different region or preset — the same check will name them
The radio does not transmit at all the region is not set (UNSET), or tx_enabled = false
Packets arrive intermittently too far, no antenna, or a small hop_limit

The main debugging rule here: silence is not a diagnosis. A correctly configured radio with nobody nearby looks exactly like a radio set to a different region. Do not guess — run step 5.


Debugging with no hardware

The entire radio side can be debugged without a single board: terminal meshhub brings up a fake Meshtastic mesh locally, and nodes connect to it via --mesh tcp:ADDR.