Field manual · W2MMD / GCARC
W2MMD MeshCore Handbook
Choosing a firmware role, flashing it, and configuring the result — for gateways and for companion nodes alike. Ends with the club’s own MeshCore repeater as a worked example.
Device values were read live over USB serial on 28 Aug and 3 Sep 2026. Where something comes from documentation rather than from a radio that answered for itself, it says so.
Two different protocols on the same LoRa hardware. They cannot hear each other, they share no tooling, and the meshtastic CLI and the MeshMon collector are blind to every node in this document. Where this manual says “repeater” it means a MeshCore repeater — nothing to do with the club’s voice repeaters.
Roles and firmware variants
In MeshCore the role is the firmware. There is no set role command — you decide at flash time, and changing your mind means reflashing. Pick wrong and the node works perfectly while being unreachable by the tool you expected to use.
Six builds exist. Two are infrastructure, one is the client radio in three connection flavours, and three are specialist.
Infrastructure — “gateway” roles
| Build | What it does | How you reach it |
|---|---|---|
| Repeater | Forwards packets to extend range. No message storage. The backbone role. | USB serial CLI, or over the mesh with an admin password |
| Room Server | Everything a repeater does, plus a stored message history that users can pull — a small BBS. | Same as repeater; adds a guest password (default hello) |
Both expose the same text CLI over USB and both are screenless-friendly. Neither has Bluetooth of any kind — that surprises people, so it is worth saying plainly: you cannot pair a phone to a repeater.
Client — companion radio, three flavours
| Build | Connection | Good for |
|---|---|---|
| Companion BLE | Bluetooth LE. Pairing PIN, default 123456 on nodes without a display. | A handheld radio paired to your phone. The usual choice. |
| Companion USB | Binary frame protocol over the USB cable. | A node cabled to a PC that you want to script or log from. |
| Companion WiFi | TCP on port 5000, over your LAN. | A node that lives anywhere on the network, no cable, reachable from a browser. |
No companion build speaks the text CLI. Open one in PuTTY, type get name, and you get silence — it only answers a binary frame protocol. A Companion BLE node is silent on USB entirely: it boots, runs perfectly, and says nothing on the wire, which looks exactly like a dead board.
If a node boots but answers nothing over serial, that is the expected behaviour of Companion BLE, not a fault.
Specialist
| Build | What it does |
|---|---|
| Sensor Node | Telemetry source that reports readings into the mesh |
| KISS Modem | Presents the radio as a KISS TNC to other software |
| Secure Chat Terminal | Standalone chat on a device with a keyboard and screen — a T-Deck, typically. No phone needed. |
Choosing
- Infrastructure at a site, meant to relay for everyone → Repeater.
- The same, but you also want stored messages people can retrieve → Room Server.
- Your own radio, driven from your phone → Companion BLE.
- A radio you want a computer to poll, log, or automate → Companion USB (cabled) or Companion WiFi (networked).
Flashing
The procedure is the same for a gateway and a companion node; only the firmware you select differs. The hardware precautions below apply to both, and the board-target one is the mistake that costs the most time.
Boards that look interchangeable are not. The RAK3401 talks to its radio over SPI1 rather than standard SPI, and its 1W booster needs pin 31 (ANT_SW / IO3) driven to key the amplifier. Flash a RAK4631 build onto RAK3401 hardware and the node boots, accepts every command, reports healthy, and emits nothing usable.
The same class of mismatch bites on ESP32-S3: a build that routes its serial to the chip’s native USB peripheral will be silent on a board whose USB connector is wired through a CP2102 UART bridge. Symptom is identical — boots clean, says nothing.
Connect the antenna before the node is powered. A 1W amplifier into no load can destroy itself. And many USB cables are charge-only — one of those enumerates nothing and is indistinguishable from a dead board.
-
Pick the firmware
From the MeshCore releases, take the build matching both your exact board and your chosen role from the tables above. Roles ship as separate downloads — there is no single image you configure afterwards.
-
Open the flasher
flasher.meshcore.io in Chrome or Edge. It talks to the board over USB only, and needs no install.
File formats differ by chip: ESP32 takes
.bin, nRF52 takes an OTA.zip. Grab the matching.uf2as well if one exists — that is your recovery path. -
Enter bootloader mode if the board needs it
Varies by hardware: hold BOOT while connecting on many ESP32 boards, double-tap RESET on nRF52. Most boards are handled automatically by the flasher.
-
Recovery route for nRF52
If the board will not enumerate or a flash fails partway, double-tap RESET. The Adafruit nRF52 bootloader mounts a small USB drive; drag the
.uf2onto it and the board reboots itself. This works even when the application firmware is bricked, because the bootloader lives in protected flash. -
Confirm what came up
For a gateway, open the serial console and ask its version. A response confirms both the flash and the board target:
ver -> v1.17.1-d929643 (Build: 14-Aug-2026) board -> RAK 3401For a companion node, silence on serial is expected for the BLE build — confirm it over Bluetooth instead. For Companion USB, confirm with
meshcli -s <port> infos.
The three web tools
MeshCore has three separate browser tools, and they are not interchangeable. Most confusion about “I can’t find my node in the web interface” is someone in the wrong one for their firmware role.
| Tool | Works with | Over |
|---|---|---|
| flasher.meshcore.io | Any board, any role — installs firmware | USB only |
| config.meshcore.io | Repeater and Room Server only | USB serial (WebSerial) |
| app.meshcore.nz | Companion firmware only | BLE, USB, or WiFi |
The config tool gives repeaters a full settings GUI, a manual CLI box, JSON import/export, and over-the-air firmware updates. The web client is a complete replacement for the phone app — messaging, contacts, node map, settings — which makes it the practical way to drive a companion node from a desktop.
Chrome, Edge, or another Chromium browser is the safe choice; WebSerial and WebBluetooth are what these pages rely on. Firefox works over WiFi, where no such API is needed, and Safari is reported reliable on Mac and iOS.
These pages are static and the traffic is local — browser to node over USB, Bluetooth, or your LAN. The mesh itself never touches the internet.
Web / WiFi access to a node
This is the flasher option you have seen listed and found nothing written about. It is real, it is genuinely useful, and it is thinly documented for a specific reason worth knowing before you commit to it.
What it gives you
A Companion WiFi build joins your wireless network and exposes the companion interface over TCP on port 5000, accepting multiple clients at once. The node can then sit anywhere on the LAN — no cable, no Bluetooth range limit — and you reach it from:
- A browser at app.meshcore.nz, choosing the network connection
- The CLI:
meshcli -t <ip> -p 5000 - Your own code — meshcore.js takes
new TCPConnection("10.1.0.226", 5000)
On a node with a display there is a Network (TCP) page showing connection status, the node’s IP, the port, and the SSID it joined. A long press toggles TCP on and off.
For most boards the WiFi build is not something you flash and then configure. The SSID and password are compile-time constants — you edit WIFI_SSID and WIFI_PWD in ./variants/<board>/platformio.ini, build, and flash the result. Some builds accept credentials at runtime, but that is not the general case.
That is why prebuilt WiFi images are scarce and the documentation is thin: for most people this variant means compiling MeshCore yourself. If your network password changes, you rebuild and reflash.
Worth it for a fixed node at a known site where you want browser access without walking over to it. Not worth it for anything you carry.
The TCP port, the multi-client behaviour, the Network (TCP) page, and the build-time credential requirement all come from MeshCore documentation and community write-ups — not from a WiFi node tested here. Treat the exact provisioning steps as a starting point rather than a verified recipe.
Configuring a gateway
Repeaters and room servers share one configuration surface: a text CLI at 115200 8N1 over USB, reachable from PuTTY, from config.meshcore.io, or from a script.
The console echoes what you type and answers with ->. There is no help and no ? — both return Unknown command. The CLI reference below is the actual command surface.
A freshly flashed gateway is anonymous, unsecured, and on default radio settings. Do these in order — the password first, because until you set it the node accepts the published default from anyone in earshot.
-
Lock it down
The default admin password is
passwordand it is public knowledge. Anyone who can hear the node can otherwise reconfigure it. There is no getter —get passwordis not valid, by design.password <something-only-you-know> set guest.password <guest-password> -
Name it
This is what appears in everyone’s contact list, so make it identify the site rather than the person.
set name W2MMD Clubhouse -
Set the radio
Frequency, bandwidth, spreading factor and coding rate in one command. All four must match the rest of your mesh exactly or the node is deaf and mute — not degraded, silent. Requires a reboot.
set radio 919.5,500,10,5 set path.hash.mode 1 set tx 22 rebootTo trial a change on a node you cannot easily reach, use
tempradio f,bw,sf,cr,<minutes>instead — it applies without saving and reverts on its own. -
Place it and set the clock
Position feeds the map. Note there is no
timequery — baretimereturns Unknown command; it only takes an epoch argument.set lat 39.716999 set lon -75.2099533 time <epoch> clockGet the epoch from
date +%son Linux. On Windows use[int][DateTimeOffset]::UtcNow.ToUnixTimeSeconds()— PowerShell’sGet-Date -UFormat %scomputes from local time and will set the node hours off. The config tool pushes the browser’s clock for you, which avoids the whole problem. -
Make it discoverable
Zero-hop adverts in minutes, flood adverts in hours, then one manual advert so anything nearby can add it immediately.
set advert.interval 60 set flood.advert.interval 12 advert -> OK - Advert sentadvert.interval has a floorThe firmware accepts 60–240 minutes only, or
0to disable. Anything lower is rejected outright:set advert.interval 30 Error: interval range is 60-240 minutes60 is as often as a gateway can announce itself. This range is not in the published documentation — it was found on the device.
-
Check it hears the mesh
Give it real time on air first — an hour or more at a quiet site.
neighbors
Administering one remotely
A gateway can be driven over the mesh rather than the cable. The client must first hear an advert from it, which is why advert.interval 0 makes a node effectively unreachable. Then log in with the admin password and issue commands by RF. Both the phone app and meshcore-cli can do this.
Configuring a companion node
Nothing from the previous section applies here. Companion firmware has no text CLI and does not appear in the config tool. Three ways in, depending on which flavour you flashed:
| Flavour | Phone app | Web client | meshcore-cli |
|---|---|---|---|
| BLE | Pair over Bluetooth | WebBluetooth | meshcli -a <address> |
| USB | USB OTG | WebSerial | meshcli -s <port> |
| WiFi | Over LAN | Network connection | meshcli -t <ip> -p 5000 |
The scriptable path
meshcore-cli installs from PyPI as pip install meshcore-cli and runs as meshcli. It speaks the binary companion protocol, which is the only thing these nodes answer.
| Command | Does |
|---|---|
| infos / i | Node details |
| ver / v | Firmware version |
| set <param> <value> | Configure. set help lists parameters. |
| get <param> | Read a parameter |
| self_telemetry / t | Own telemetry |
| time <epoch> / clock sync | Set clock. clock sync pulls from the connected client — no epoch arithmetic. |
| advert / floodadv | Send an advert / flood advert |
| contacts / lc | List contacts |
| import_contact / export_contact | Exchange contacts by URI |
| card / e | Export this node’s own URI |
Chain them in one non-interactive call, -j for JSON:
meshcli -s /dev/ttyACM0 -j infos set name MNFQ-MC advert contacts
Two companion nodes must each hold the other in contacts before direct messages work. A node with Contacts (0) can hear the mesh but cannot address anyone on it. Either let it pick up an advert, or import the far end’s contact URI directly.
A companion with position 0, 0 and “Share Position in Advert” enabled broadcasts null island to everyone in range — a wrong position rather than an absent one. Either set real coordinates or turn position sharing off.
Backing one up
The app’s Export Config screen writes out identity, radio settings, channels and contacts. Do this before changing radio settings. The export contains the private identity key, which is the one unrecoverable item: lose it and the node returns with a new public key and every contact in the mesh loses it. Anyone holding that export can impersonate the node, so keep it somewhere safe.
CLI reference
Gateways only — repeater and room server. Verified against a live repeater on firmware v1.17.1. The n/a rows are traps worth knowing, not omissions.
Status and identity
| Command | Returns | |
|---|---|---|
| ver | Firmware version and build date | works |
| board | Hardware identifier, from firmware rather than the USB descriptor | works |
| clock | Device time, UTC | works |
| neighbors | Up to 8 recent, as {pubkey-prefix}:{timestamp}:{snr×4}, or -none- | works |
| discover.neighbors | Actively probe for zero-hop neighbours | works |
| get name / role / public.key | Identity | works |
| help | Unknown command — so are ?, status, stats, bare time, bat | n/a |
Radio
| Command | Notes | |
|---|---|---|
| get radio | All four values at once: freq,bw,sf,cr | works |
| get bw / sf / cr | Not individually gettable — use get radio | n/a |
| set radio f,bw,sf,cr | Saves to preferences. Reboot required. | reboot |
| tempradio f,bw,sf,cr,mins | Applies without saving, reverts after N minutes. The safe way to test a change remotely. | works |
| set tx <dBm> | Chip-level power. Reboot required. | reboot |
| get af | Airtime factor | works |
Network behaviour
| Command | Notes | |
|---|---|---|
| advert | Send one advert now | works |
| set advert.interval <min> | Zero-hop timer, minutes. Accepts 60–240 only, or 0 to disable. | works |
| set flood.advert.interval <hr> | Flood timer, hours. Default 12. 0 disables. | works |
| set repeat on|off | Whether it forwards packets at all | works |
| get flood.max | Maximum hop count | works |
| set path.hash.mode 0|1|2 | Bytes of public key per recorded hop: 1, 2 or 3. Needs firmware ≥1.14. Governs what the node originates; 1.14+ relays any width. | works |
| set allow.read.only | Guest read access | works |
| set rxdelay / txdelay | Collision-avoidance timing | works |
| get acl | Access control list (serial only) | works |
Security and lifecycle
| Command | Notes | |
|---|---|---|
| password <new> | Sets admin password. Write-only — no getter, by design. | works |
| set guest.password <pw> | Guest access; this one is readable | works |
| time <epoch> | Set clock from unix epoch seconds | works |
| reboot | A timeout message on the console is normal | careful |
| erase | Wipes the filesystem — identity, keys, all config. New public key on next boot, so every contact loses it. | destructive |
Monitoring
Gateway telemetry hides behind three hyphenated commands. They are easy to miss — status, stats and get stats all return Unknown command. Each returns JSON, so they drop into a logger with no parsing work.
| Command | Example output |
|---|---|
| stats-core | {“battery_mv”:4175,”uptime_secs”:2800,”errors”:0,”queue_len”:0} |
| stats-radio | {“noise_floor”:-88,”last_rssi”:-50,”last_snr”:-11.00,”tx_air_secs”:0,”rx_air_secs”:0} |
| stats-packets | {“recv”:0,”sent”:1,”flood_tx”:0,”direct_tx”:1,”flood_rx”:0,”direct_rx”:0,”recv_errors”:0} |
Also clear stats to zero the counters, and log start / log / log stop to capture received packets to flash and dump them. All of these are serial only; over the mesh you get a smaller set.
Reading them: recv with recv_errors at zero means nothing is arriving at all; errors climbing means the node hears traffic it cannot decode, which points at a radio-parameter mismatch. A sensible noise floor proves the receiver is alive and measuring even when nothing is heard.
last_rssi and last_snr hold boot-time values until something is actually received. Identical readings across days are a sign of no traffic, not of a steady signal — do not trust them until recv is above zero.
Companion telemetry
Companion nodes report through meshcore-cli instead: self_telemetry for the node you are connected to, and req_status / req_telemetry / req_neighbours against a named gateway over the mesh. cmd <gateway> <command> sends raw CLI by RF.
meshcli -s /dev/ttyACM0 -j login <adminpw> \
req_status "W2MMD Clubhouse" \
req_telemetry "W2MMD Clubhouse"
There is no battery-backed RTC. A genuine reboot or power loss resets the clock to a fixed default date and it counts up from there. Since set radio and set tx both require a reboot, any radio change silently wipes the clock — re-set it afterwards.
Pulling the USB cable is not necessarily a power cycle. With a battery attached the node keeps running and only the data link drops. Check stats-core uptime before assuming a reboot happened.
Network profile and FCC compliance
Every node on a mesh must share its radio parameters exactly. The profile below is MeshCore 500, adopted by PhillyMesh in August 2026 after the group concluded that the common defaults are not FCC compliant.
PhillyMesh replaced its first MeshCore 500 guidance. The frequency moved from 902.250 to 919.500 MHz and the spreading factor from 11 to 10, after summer 2026 testing in the Philadelphia area found heavier ISM-band interference at the original channel. A path hash mode setting was added at the same time.
Nodes still on 902.25 are on a dead network. Everything has to move together.
| Parameter | MeshCore 500 |
|---|---|
| Frequency | 919.500 MHz |
| Bandwidth | 500 kHz |
| Spreading factor | 10 |
| Coding rate | 4/5 (shows as 5) |
| Path hash mode | 1 — 2-byte (some apps say 1-2 byte) |
set radio 919.5,500,10,5
set path.hash.mode 1
reboot
Path hash mode
A repeater’s path hash is the first N bytes of its public key — a truncated fingerprint identifying it in a packet’s recorded route. path.hash.mode chooses how many bytes each hop consumes:
| Mode | Bytes per hop | Distinct IDs | Max hops |
|---|---|---|---|
| 0 | 1 | 256 | 64 |
| 1 | 2 | 65,536 | 32 |
| 2 | 3 | 16,777,216 | 21 |
The path field in a MeshCore packet is capped at 64 bytes, so wider hashes trade maximum hop count for fewer collisions between repeaters. One byte gives only 256 possible IDs, which on a growing regional mesh produces genuine clashes; two bytes effectively eliminates them while still allowing 32 hops — far more than any real route needs. That is why PhillyMesh standardised on mode 1.
set path.hash.mode 1
This needs firmware 1.14 or later. A repeater on 1.14+ forwards one-, two- and three-byte paths regardless of its own path.hash.mode, so the setting governs what a node originates, not what it relays. Older repeaters cannot forward multi-byte paths at all.
Why 500 kHz
These meshes operate as unlicensed Part 15 devices rather than under an amateur licence — the default encrypted channels would be a problem under Part 97, which prohibits obscuring the meaning of a transmission. That puts them under 47 CFR §15.247, where digital modulation in the 902–928 MHz band must have a minimum 6 dB bandwidth of 500 kHz.
That one rule disqualifies most familiar presets. MeshCore’s own US default of 910.525, 62.5, 7, 5 is 62.5 kHz — eight times narrower than the floor. On the Meshtastic side the same rule leaves only LongTurbo.
The 1W output is not the problem: §15.247 permits up to 1 watt conducted in this band for digital modulation, which is what a booster module is built for. Antenna gain above 6 dBi requires a corresponding power reduction.
Infrastructure nodes move to MeshCore 500 as the primary network. LongTurbo tested poorly for region-wide use — workable hyper-locally, but it would not carry from the city to the suburbs the way LongFast did. Dual-radio sites keep Meshtastic LongTurbo on the default frequency slot alongside. The LF910 test ended because it was not compliant.
Radio settings are all-or-nothing. MeshCore 500 and the stock 910.525/62.5 defaults cannot hear each other, and neither can the revised 919.5 profile and the original 902.25 one — a node left on the old frequency is simply absent from the mesh. Every node you own has to move together, companions included.
The W2MMD Clubhouse node
A worked example of everything above: the club’s own MeshCore repeater. Note the distinction — this is a MeshCore data node, unrelated to the club’s voice repeaters, and invisible to every Meshtastic tool including the MeshMon dashboard.
- Name
- W2MMD Clubhouse
- Role
- MeshCore repeater (gateway)
- Firmware
- MeshCore v1.17.1-d929643 · 14 Aug 2026
- Board
- WisCore RAK3401 + RAK13302 1W booster
- MCU / radio
- nRF52840 · SX1262 + SKY66122 PA
- Antennas
- 915 MHz SMA on RAK13302 (live) · 2.4 GHz PCB on RAK3401 (idle)
- Host port
- USB serial on the clubhouse workstation
- Public key
- CC247CAA69D682305F66A5DEF7195B1D514611DD0CC3AEAB4C6D6F6EF09AAA14
- Network
- PhillyMesh MeshCore 500
- Radio
- 902.25 MHz · BW 500 · SF 11 · CR 5 — superseded, needs retune
- Should be
- 919.5 MHz · BW 500 · SF 10 · CR 5 · path.hash.mode 1
- TX
- 22 dBm at the chip → ~30 dBm after the booster
- Position
- 39.716999, −75.2099533
- Adverts
- zero-hop 60 min · flood 12 h
- Guest password
- (ask a club officer)
Reaching it
Repeater firmware, so: config.meshcore.io in Chrome or Edge on the machine holding the USB cable (WebSerial needs a local connection), a serial console at 115200, or remotely over the mesh with the admin password once something has heard its advert. No Bluetooth — the repeater build has none.
Node-specific notes
- TX 22 is correct — do not raise it. +22 dBm is the SX1262’s own hardware ceiling; the RAK13302’s SKY66122 amplifier takes that to roughly 30 dBm at the antenna. The node is already at full output.
- Two antennas, mechanically interchangeable connectors. The flat PCB antenna belongs on the RAK3401 (2.4 GHz, idle under repeater firmware); the SMA whip via IPEX-to-SMA belongs on the RAK13302 (915 MHz, 1W). MHF1 and IPEX/U.FL will seat in either socket — crossing them puts 1W into a 2.4 GHz PCB antenna and kills the amplifier.
- Flash only the
RAK_3401target. Never RAK4631, for the SPI1 and pin-31 reasons in the flashing section. - The clock needs re-setting after any reboot, including every
set radio. It has run correctly across USB unplugs because the battery keeps it alive. - Windows gives one program exclusive use of a serial port. The config tool, a PuTTY window, or a stray Python process will lock everyone else out with Access is denied.
Status as of 3 Sep 2026
As last read on 3 Sep: battery 4175 mV, noise floor −88 dBm, receiver alive and measuring, recv 0 with recv_errors also 0 — it had heard nothing at all.
It is now on the superseded profile. PhillyMesh revised MeshCore 500 on 6 October, so this node and the club’s companion nodes all sit on a frequency nothing else is using. Firmware v1.17.1 is still the current release, so no firmware update is needed first — only the retune:
set radio 919.5,500,10,5
set path.hash.mode 1
reboot
# the reboot wipes the clock — set it again afterwards
time <epoch>
Until that is done, the zero recv count says nothing useful about coverage.
Troubleshooting
The node boots but answers nothing on serial
Work down in this order:
- It may be a companion build. Companion BLE is silent on USB by design; Companion USB answers only the binary protocol, never text. Neither is a fault.
- Line states. Opening a serial port asserts DTR and RTS, which holds an ESP32 in reset. Open with both deasserted, then pulse RTS to boot into the application.
- Board-target mismatch. Wrong variant means a clean boot and no working I/O. Reflash with the exact target.
- Charge-only cable. Enumerates nothing, looks like a dead board.
The node is alive but hears nothing
- Radio mismatch.
get radiomust match the rest of the mesh on all four values. One wrong number is total silence, not reduced range. - Adverts off. Nothing can route to a node that never announces itself.
- Nobody there. On a newly adopted profile, an empty
neighborsmay simply mean no one else is on it yet. Checkrecv_errors: zero alongside zerorecvmeans an empty band, not a broken receiver. - Antenna and feedline. Confirm the SMA cable is on the LoRa module and not the BLE socket — the connectors mate either way.
“Access is denied” opening the port
Windows gives one program at a time exclusive use of a serial port. Close the other terminal, browser tab, or script.
Forgotten admin password
No recovery and no getter. Set a new one over USB with password <new> — physical access is the intended override. Do not reach for erase: that changes the public key, and every contact in the mesh loses the node’s identity.
Don’t point Meshtastic tools at any of this
Separate protocol. The meshtastic CLI and the MeshMon collector cannot read a MeshCore node and will error on the port. Equally, MeshCore nodes are invisible to the MeshMon dashboard by design — not a fault to chase.
