fluxcore docs

Firmware built from upstream MeshCore's dev branch plus a handful of fixes, for a short list of boards.

Latest build#

2026-11-04 (b115223) · tag beala-b115223

Changes since beala-4d912c9:

Full diff against upstream

How it differs from the official flasher#

flasher.meshcore.ioThis site
FirmwareOfficial stable releasesUpstream dev (unreleased) plus the changes listed below
UpdatesWhen MeshCore cuts a releaseAutomatically, within about 45 minutes of a change upstream or here
BoardsEvery supported boardThe 7 boards listed below
RolesCompanion, repeater, room serverCompanion (USB and BLE); repeater for the RAK 1W only
Version showne.g. v1.18.0e.g. v1.18.0-dev-beala (the last upstream release it's newer than)
Older versionsAll past releasesThe last 5 builds

The flasher itself is the official one, unchanged. Erase images and bootloaders are copied from flasher.meshcore.io, so those are the same files.

Because these builds track dev, they get upstream's newest work before it's released, including its bugs. If something breaks, flash an older build from the version list, or the official firmware from flasher.meshcore.io.

Boards#

BoardRadioFirmware
Heltec Mesh Node T1SX1262Companion USB / BLE
Seeed SenseCAP MeshTracker X1LR2021Companion USB / BLE
RAK WisMesh TagSX1262Companion USB / BLE
Seeed SenseCAP T1000-ELR1110Companion USB / BLE
Heltec V3SX1262Companion USB / BLE
Heltec V4SX1262Companion USB / BLE
RAK WisMesh 1W Booster (3401)SX1262Repeater

What's different from mainline firmware#

All boards#

Packet parser security fixes. Neil Alexander's fixes from meshcore-dev/MeshCore#3521, not yet merged upstream. Malformed or truncated packets from the air could make the firmware read past the end of buffers or use stale memory; the parser now rejects them. Covers empty and short frames, partial encrypted payloads, oversized advert data, bad TRACE and CONTROL packets, and ACK overflows. One addition on top: packets with a newer, unsupported protocol version are dropped instead of being handled as version 1. beala/pr-3521

Safer randomness for new keys. A radio now only keeps a new private key if it gathered enough randomness from sources it can trust, and the Heltec V3 and V4 use their chip's random generator properly (see below). beala/entropy

INA219 shunt setting. The INA219 current sensor's readings assumed a 0.1 ohm shunt resistor. Set yours with sensor set ina219_shunt <ohms> (for example 0.01 for a board marked R010). It's saved and survives a reboot. sensor get ina219_shunt shows it; it only appears in sensor list if an INA219 was found at boot. The most it can measure is 0.32 V divided by the shunt: 3.2 A with 0.1 ohm, 32 A with 0.01 ohm. beala/ina219-shunt

Resend messages that don't get out. After sending a message, the radio listens for the next repeater passing it on. If it hears none, it sends the exact same packet once more. Radios that already have it ignore the copy, so nobody sees it twice. Works for channel messages and for direct messages along a path. On by default on companions, off by default on repeaters (see below). beala/flood-retry

Several companions with one key. Pick one radio as the main radio; the others with the same key still show the messages they hear but never answer them, so senders get one reply and one path, and "delivered" means the main radio has it. A Radio console contact in the app runs radio commands and tells you when another radio with your key is heard (see below). beala/main

Companion boards#

Buzzer, vibration and LED switches. Commands to turn the buzzer, the vibration motor and the LEDs on and off, each on its own (see below). Mainline only has a buzzer command, and the X1's button cycles buzzer and vibration together. beala/alert-toggles

Stronger encryption (experimental, opt-in). A new encryption format that fixes real weaknesses in the current one, and works alongside radios that don't have it (see below). beala/crypto-v2

Find a lost radio. Make a radio beep for a couple of minutes, from itself or from another radio you've allowed, and measure the distance between two LR2021 radios such as X1s (see below). beala/find-me

T1, X1, WisMesh Tag and T1000-E#

Stay off when plugged in. On mainline, plugging a switched-off node into a charger turns it on. These builds stay off and just charge; press the button to turn on. Change it with set charger.wake on|off. A node that switched itself off for low battery still comes back when charged. beala/charger-stay-off, beala/custom-defaults

Heltec T1 and T1000-E#

Buzzer starts off on both, and the status LED starts off on the T1000-E. Turn them back on with set buzzer on / set led on. beala/custom

MeshTracker X1 (LR2021 radio)#

These change the LR2021 driver, so they only affect the X1 on this site.

Buzzer, vibration, LED and charging commands#

Send these from the companion app's command line. Each takes effect immediately and is saved. A board without that part replies Unknown command.

CommandWhat it controlsBoards
get buzzer
set buzzer on|off
The buzzerT1, T1000-E, X1, WisMesh Tag
get vibe
set vibe on|off
The vibration motor, separately from the buzzer. Like the buzzer, it only goes off for new messages while the app isn't connectedX1
get charger.wake
set charger.wake on|off
Whether plugging in a charger turns the node on (off on this site: it stays off and charges)T1, X1, WisMesh Tag, T1000-E
get led
set led on|off
The status LED (heartbeat, unread messages, charging) and the flash of the TX LED when it transmitsAll companion boards on this site

Replies: > on or > off for get, OK for set. The X1's triple-press still cycles buzzer and vibration modes, and the commands show the result.

Find a lost radio#

Make a radio beep so you can find it by ear, from the radio itself or from another one. Between two radios with an LR2021 (such as two X1s) you can also measure how far apart they are.

How to use it#

  1. Set it up once, before anything gets lost. You need two radios that have each other as contacts. On the radio you might lose, run set find.allow <other radio's name>. Do the same the other way round if either could get lost.
  2. Lost, but you can still reach it from the app (for example over Bluetooth): run find me on it. It beeps for 2 minutes, even with the buzzer turned off, and vibrates on the X1. Press its button or run find me stop to end it.
  3. Lost for real: on the other radio, run find beep <name>, wait a few seconds, then get find. You should see beeping for 120 s. find stop <name> silences it.
  4. Both radios have an LR2021 (X1, AB Labs XIAO): run find range <name>, then get find, for something like 42 m (+/- 4 m, 30/32 answers, -97 dBm). Move and repeat: if the number goes down, you're getting closer.

Commands#

From the companion app's command line.

CommandWhat it doesBoards
find me
find me <seconds>
Beep for 2 minutes, or 1 to 600 secondsT1, X1, WisMesh Tag, T1000-E
find me stopStop beeping (the button does too)T1, X1, WisMesh Tag, T1000-E
set find.allow <name>
set find.deny <name>
get find.allow
Let a contact make this radio beep and range to it, take that away, or list who's allowed (up to 8). They can't do anything elseAll companion boards
find beep <name>
find stop <name>
Make another radio beep for 2 minutes, or stop itAll companion boards; the other radio needs a buzzer or vibration motor
find range <name>Measure the distance to another radioLR2021 to LR2021 (X1, AB Labs XIAO)
set find.extended on
set find.extended off
get find.extended
Use extended ranging for find range (off by default). The distance stays right while the radios move, and it adds a rough relative speed: closing at ~20 m/s, moving apart at ~40 m/s, or not moving fast. Speed comes in steps of about 20 m/s, so it shows vehicles, not walking. Both radios need this buildLR2021 to LR2021
get findThe latest answer, and how long ago it cameAll companion boards

If it doesn't work#

Technical details#

Full write-up: docs/find_me.md. Source: beala/find-me.

Flood retry#

When you send a message, your radio listens for the next repeater passing it on. If it hears nothing within about 2.5 seconds, it sends the same packet again, byte for byte, down to the timestamp. That covers channel messages, messages to contacts it doesn't have a path to yet, and direct messages that follow a path through repeaters. For a direct message, a lost hop is fixed in a few seconds instead of waiting for the app to time out and retry.

That's safe because every radio and repeater remembers the messages it has seen and drops repeats of them. A repeater that got the first copy ignores the resend, and so do the radios that showed you the message. A repeater that missed the first copy treats the resend as new and passes it on. So the resend only fixes the case where the first hop was lost, and costs one extra transmission when it wasn't needed.

On a repeater it works the same way for messages it forwards: if it doesn't hear the next hop pass a message on, it sends its copy again. That helps on every hop of a direct route, and where one repeater is the only link between two areas. It's off by default. Adverts are never resent, and a resend is skipped while the repeater has other packets waiting. A repeater at the edge of the mesh, with nobody further out, would resend every channel message once, so leave it off there.

Commands#

From the companion app's command line, or a repeater's command line (USB or remote admin).

CommandWhat it does
get flood.retryHow many resends, and the wait
set flood.retry 0 to 3
set flood.retry off
How many times to resend if nothing is heard. Companions start at 1, repeaters at 0 (off)
set flood.retry.wait <ms>
set flood.retry.wait auto
How long to wait for a repeat, 500 to 30000 ms. auto (the default) is 1 second plus 8 times the message's airtime, about 2.5 s at SF7 / 62.5 kHz
get flood.retry.statsCounts since boot, for example watched 18, echoed 15 (9 after a resend), resends 12, no echo 3, skipped 0. "After a resend" means the first send didn't get through and the resend did

Adverts, traces, and the last hand-off to the recipient are never resent: nobody passes those on, so there's no repeat to listen for. More detail and bench results are in docs/flood_retry.md.

One key, several radios#

You can load the same private key onto more than one companion, say one at home and one you carry, and both show your messages. With more than one switched on, a message that arrives by flood reaches each of them and each replies. The sender gets two replies and two paths, keeps the last, and sends its direct messages to that one radio from then on.

With this firmware, one radio can take over as the main radio. The others become backups: they still show the messages they hear, but send no acknowledgement, path or response. Senders only ever hear from the main radio, however far apart your radios are. If the main radio is off or out of range, nothing answers and the sender sees the message fail, so take over on the radio you have with you. If you send a message from a backup, its console offers to make it the main radio.

The Radio console contact#

Every radio with this firmware adds a contact called Radio console to the app. It isn't a real node, and messages to it never go on air: the radio runs them as commands and replies in the same chat. Send help to start. Commands aren't case sensitive. Other radio commands work there too, such as get flood.retry, so you can use them from an app that has no command line.

The console also tells you things. When your radio hears another radio with your key, it posts: "Another radio with this key is active: it sent to Bob. Make this the main radio, so the others stay quiet? Reply "yes"."

Commands#

Send these to the Radio console contact, or from the companion app's command line.

CommandWhat it does
takeoverMake this the main radio. It floods a short claim only radios with your key can read; each of them becomes a backup and says so in its console
yesAccept the takeover the console just offered
cloneThis radio's role, how many messages it left unanswered as a backup, and when another radio with the key was last heard
clone offAnswer as normal again

The role is saved and survives a reboot. A radio that was off during the takeover doesn't know about it; the main radio offers to send the takeover again when it hears that radio reply. Senders keep one path for your key, to the main radio; a backup elsewhere only gets messages that come by flood. Messages you send from one radio don't show up on the others. More detail and bench results are in docs/clone_takeover.md.

Encryption v2 (experimental)#

Opt-in, per contact and per channel. It's new and hasn't been reviewed independently yet, so don't rely on it for anything sensitive.

In plain terms#

A private message's encryption has two jobs: scramble it so listeners can't read it, and seal it so the receiver knows it really came from you and wasn't changed or made up.

Why MeshCore's current encryption is weak:

What v2 changes: it uses AES-SIV, a published standard (RFC 5297), not a home-made scheme.

Working with radios that don't have it: repeaters relay v2 like any other message, and radios without v2 quietly ignore it. Everything starts on the old format. You switch a contact or channel to v2 once the other side has it. When a contact sends you v2, your radio starts replying in v2 by itself, but nothing ever switches back to the old format automatically. v2only refuses the old format from that contact or channel entirely.

Cost: about 7 more bytes per message on average. 160-character texts still fit.

What it doesn't fix: channel members can still read everything and post under any name; public and hashtag channels aren't private; someone who records messages and later steals your radio's key can read them; recorded messages can be replayed; and who's talking, how often and how much is still visible.

Commands#

From the companion app's command line. They only work on the device itself, not from a remote admin.

CommandWhat it does
set crypto v2 AliceSend v2 to the contact Alice, still accept both formats (name, or the start of the public key in hex)
set crypto v2only AliceSend v2 and refuse old-format messages from Alice
set crypto legacy AliceBack to the old format (the default)
get crypto AliceShow Alice's setting
set crypto.ch v2 1
get crypto.ch 1
The same for a channel, by number or name. Channels never switch by themselves
get cryptoChannels not on legacy, and how many contacts are on v2
set crypto.recoverOnly needed if saved settings were lost (see below)
get crypto.benchTimes decryption on this radio with a test key, and says whether AES runs on hardware (aes=hw) or software

Keep repeaters and room servers on legacy unless they run this firmware: they can't read v2, so logins would fail.

Technical details#

Full specification, test vectors and limitations: docs/crypto_v2.md.

Key randomness#

In plain terms#

The first time a radio starts, it makes its own private key: a secret number that everything private hangs off. Every direct message to or from that radio is protected with it. The key is only as good as the randomness used to pick it. If that randomness were predictable, someone could work out the key and read those messages.

What was wrong:

What changed:

Which radios it affects: it's a real fix on the Heltec V3 and V4. The T1, X1, T1000-E, WisMesh Tag and RAK3401 already used their chip's hardware generator, and now also check that it worked.

Your existing key doesn't change. Updating the firmware keeps it. To get a key made the new way, do a full erase when flashing. That gives the radio a new identity, so your contacts will need to add it again. Run get entropy afterwards to check there was enough randomness.

The random 6-digit Bluetooth PIN, shown on boards with a screen, comes from the same generator, and every PIN is equally likely. Channel keys aren't made on the radio: the phone app picks them. Hashtag channels aren't private at all.

Technical details#

Full write-up: docs/entropy.md. Source: beala/entropy.

New commands (MeshTracker X1)#

Send these from the companion app's command line (or any client that can run companion CLI commands). Other radios reply Unknown command.

get radio.faults#

Transmits that went wrong since boot, newest first (up to 8), with how long ago each was.

> 3 since boot: 12s OCP; 95s ERR=0004; 300s NODONE
FlagMeaning
NODONEThe transmit didn't finish
OCPThe power amplifier hit its over-current or over-voltage limit
ERR=xxxxThe chip reported an error (its error register in hex; 0004 is a PLL lock failure)
CMDThe chip rejected a command
TIMEOUT, EOLThe chip's timeout and end-of-life signals

With no faults: > no TX faults since boot

get radio.agc#

The receiver gain step the radio picked for each of the last packets, with their signal strength, SNR and age, newest first.

> G13 -112dBm -6.5dB 4s; G9 -87dBm 8.0dB 31s

Higher gain steps mean the radio amplified a weaker signal. Useful for spotting a receiver that's being overloaded by a nearby transmitter (low gain steps on weak packets). Before anything is received: > no packets received yet

How the builds are made#

The builds come from beala/main, the fork's own main line. Every 30 minutes, and whenever beala/main changes, a GitHub workflow in beala/MeshCore merges in the newest upstream dev, builds all boards in parallel, and publishes a release tagged beala-<commit>. This site checks for new releases every 15 minutes and republishes.

The release notes list what changed since the previous release, with a link to the full diff against upstream. The branch names next to each feature above are where that change is kept for sending upstream.