Releases: liquidraver/ZephCore
Release list
Firmware 1.17.1-zephcore
ZephCore 1.17.1-zephcore
A hardware release. A new tracker board lands — the Seeed SenseCAP MeshTracker X1 — and with it the
LR2021 radio becomes a real, on-air-validated option instead of bring-up code. Plus GPS diagnostics, an
LED master switch, and a batch of receive- and transmit-path hardening.
Note
1.17.0 was never published, so this release carries everything that was written up for it. If you
are coming from 1.16.8, everything below is new to you.
Important
Before you upgrade
From v1.16.5 – v1.16.8 — just flash it. Bonds and data survive, no re-pairing.
From v1.16.2 – v1.16.4 on an ESP32-S3 / ESP32-C board — you still owe a one-time USB/serial
reflash of -merged.bin. The app moved to flash offset 0x10000 in v1.16.5, and a board on the old
layout cannot cross that gap over WiFi-OTA or the browser flasher. Affected: Heltec V3 / V4 / V4.3,
Station G2, Wireless Tracker / V2, XIAO ESP32-S3 / C3 / C6, LilyGo T-Lora C6, ThinkNode M9. Your
identity, contacts, channels, prefs and bonds are preserved. Everything else (nRF52, classic ESP32,
STM32WL) upgrades normally.
From v1.16.1 or older — flash it; BLE bonds are cleared on first boot, so re-pair your phone once.
Identity, contacts, channels and prefs are kept.
Coming from Arduino MeshCore — flash it; storage auto-formats and you get a new identity.
ThinkNode M6 owners — this release stops using the external flash chip, because the board doesn't
actually have one. Contacts, channels and prefs move to internal flash on first boot.
Repeater / room-server telemetry no longer shares GPS position with non-admins. If you were
relying on that, set the location in prefs and advertise it instead.
ESP32 light sleep stays on by default for Heltec repeaters. It has now been confirmed working on a
Heltec V4 — but only that one board so far. Side effect: the USB CLI stops answering ~10 minutes after
boot; reconnect the terminal to get another window. Rebuild with -DCONFIG_PM=n to remove it.
Slow presets no longer lose long packets
The biggest fix in this release, and it is invisible until you run a narrow bandwidth. Transmission was
bounded by two fixed deadlines that are shorter than the airtime of many perfectly legal presets:
- The chip's own transmit timer was pinned at 10 s. The SX126x datasheet is explicit that when this
timer fires the transmission is stopped — so on slow presets it was not a safeguard, it was a
truncation. A full-size packet is 17.7 s at SF10/BW31.25 and 28.6 s at SF12/BW62.5; those presets
were cutting every packet from ~136 bytes and ~76 bytes up, mid-air. - The host's wait for "transmit finished" was a fixed 5 s, after which the radio was yanked back
into receive — killing a transmission that was going perfectly well.
Both now scale from the driver's own airtime calculation, floored at the old values so nothing that
works today gets a tighter deadline. Only slow presets move. There was nothing in the log connecting
the loss to either line, so if you have ever run SF10–SF12 at 31.25 or 62.5 kHz and seen packets simply
not arrive, this is likely why.
Alongside it: a driver that reports a failed transmit is now believed. Previously such a packet was
counted as sent, so the statistics disagreed with reality.
Replies no longer dropped when flood.max.unscoped is low
Only affects operators who have lowered flood.max.unscoped from its default of 64 — but on those
meshes it looked like repeaters and room servers were ignoring requests. Two separate causes:
- A reply to a DIRECT request went out un-scoped, because a direct request carries no transport
codes and there was no fallback. Any repeater on the return path runningflood.max.unscoped=0then
dropped it at hop 0. Replies now fall back to the node's own default region scope. - A repeater answering a direct login flooded the reply even when it already had a return path
stored for that client. It now replies directly along that path.
Ported from upstream MeshCore, with one local difference: ZephCore can tell "the request arrived
un-scoped" apart from "the request was direct", so a requester who reached you un-scoped still gets an
un-scoped reply rather than a scoped one they might not hear.
New board — Seeed SenseCAP MeshTracker X1
meshtracker_x1 — nRF52840 + Semtech LR2021, 863–928 MHz / 22 dBm, dual-band L1+L5 GNSS,
barometer, haptic motor, RGB LED, buzzer, 1100 mAh battery, 8 MB external flash and an on-board RTC.
Companion, repeater and room server all build and run.
It is essentially a T1000-E with a different radio, so most of the board reuses well-travelled code.
What's new:
- Barometer support — a from-scratch SPA06-003 driver. Pressure and temperature are meant to show
up in normal environment telemetry. Not yet confirmed on hardware. - Haptic notifications. The buzzer setting became a four-way notification mode:
set buzzer 0|1|2|3= silent / sound+vibrate / vibrate / sound, and the 3-tap button cycles them.
Boards without a motor keep the plain on/off toggle. - 8 MB external flash for contacts, channels and the blob cache, plus the on-board RTC.
The LR2021 radio is now validated on hardware
Previously this driver was marked experimental with no board behind it. It has now had a full audit and
an on-air validation pass on the X1 — transmit, receive, listen-before-talk and RX duty cycle are all
confirmed working. Highlights of what changed to get there:
- The chip's firmware patch is now loaded at boot (Semtech "highly recommends" it; without it the
chip runs unpatched). ThePRAM loaded:log line is proof it took. - Listen-before-talk runs on the chip itself — it checks the channel and transmits in one shot with
no round-trip to the CPU. Longer transmissions automatically fall back to the classic route. - RX duty cycle (sniff mode) works, with the sleep/wake timing derived from the datasheet rather
than estimated. - Several datasheet-conformance bugs fixed, the most user-visible being a receive filter that was
not restored after transmitting — a node would silently stop hearing any packet longer than its own
last transmission.
promicro_lr2021 still builds but remains untested (the module was destroyed during bring-up).
LR2021 multi-SF receive — set extra.sf
The LR2021 can listen on up to three extra spreading factors at once, on the same bandwidth. One
repeater can serve several SF communities without splitting the site.
set extra.sf 9 10 # also receive SF9 and SF10
get extra.sf # -> 9,10
set extra.sf off # back to single-SF
Important
Receive only — the bridge it creates is one-way. Transmission always uses your configured sf.
A node on sf 7 with extra.sf 8 hears and forwards SF8 traffic, but forwards it at SF7. So traffic
flows SF8 → SF7 and never back. Adverts and flood traffic propagate fine; an SF8 node's direct
messages get delivered but its ACKs never arrive, so it retries to its limit every time.
There is no configuration that reverses this — extra SFs must always be higher than your main SF,
and TX always uses the main SF. Treat it as a collector that pulls slower-SF stragglers into a fast
mesh, not as a link between two SF islands.
The chip's rules are enforced for you, so anything accepted is valid: extra SFs must be higher than
sf, distinct, within 4 of each other, and at BW ≥ 500 kHz at most two. A set that no longer fits
after you change sf or bw is refused at boot and logged. Error: unsupported on other radios.
No more silent mute-until-reboot
All four radio drivers gate transmission on "am I currently receiving?" — and on the LR11xx and LR2021
that answer could get stuck true forever if a packet header arrived but the packet never completed.
The node would keep receiving and never transmit again, with nothing in the log pointing at it. Both
now bound that state with a generous deadline (max-length packet airtime + margin) and log a warning if
it ever fires. The SX126x got the same fix in 1.16.x.
Related: an error-only interrupt on the LR2021 no longer triggers a receive restart that regenerates
the same error, which on the X1 could cost ~88 ms of deafness and a hardware reset.
Virtual-contact fixes (companion)
The built-in admin chat contact got three fixes worth knowing about:
- Deleting it in the app is now temporary. It comes back on the next app connect. Previously a
"purge all contacts" in the app permanently disabled the feature with no way back except the USB CLI.
The only durable off switch isset v.contact offon the node. - Favourite star and telemetry permissions stick across reconnects and reboots.
- "Last seen" no longer grows forever, and the contact re-syncs to the app properly instead of
being sent exactly once ever.
Adaptive CAD now uses only levels the chip can distinguish
The auto-tuning channel-detection threshold walks a range of levels, but where those levels fell
outside the radio's own hardware limits, several of them programmed the identical setting — so the
tuner was comparing physically identical rungs and reading noise as a trend. The usable window is now
derived per-driver, so every level is a distinct configuration and the pk value in get cad is what
the chip actually got. This mattered most on the LR2021.
GPS: dynamic model and a diagnostics readout
CONFIG_ZEPHCORE_GPS_NAV_MODEsets the dynamic model on CASIC modules (Quectel L76K / L76KB,
Air530Z) — stationary for repeaters and room servers, automotive otherwise. Worth setting: the
model is stored in the module and ...
Firmware 1.16.8-zephcore
ZephCore 1.16.8-zephcore
Caution
ESP32 light sleep is enabled by default on Heltec repeaters and has
been validated only on one hardware yet — see the light-sleep section for what to check and how to build it
The nRF based boards should be fine.
Important
Before you upgrade
From v1.16.5 / v1.16.6 / v1.16.7 — clean flash, no re-bond, bonds and data survive.
From v1.16.2 / v1.16.3 / v1.16.4 — clean flash, bonds and data survive. If you are on an
ESP32-S3 / ESP32-C board and have not yet taken the v1.16.5 update, you still owe the one-time
serial reflash: the app moved to flash offset 0x10000 in v1.16.5, and a board on the old layout
cannot cross that update over WiFi-OTA or the browser flasher — flash the -merged.bin once over
USB/serial. Affected boards: Heltec V3 / V4 / V4.3, Station G2, Wireless Tracker / V2, XIAO
ESP32-S3 / C3 / C6, LilyGo T-Lora C6, ThinkNode M9. Identity, contacts, channels, prefs, and BLE
bonds are preserved. nRF52, classic ESP32 (T-Beam / PICO-D4 / TTGO LoRa32), STM32WL, and native
Linux are unaffected.
From v1.16.1 or older — flash it; on first boot it clears BLE bonds automatically (identity,
contacts, channels, prefs preserved). Re-bond your phone/desktop once.
Coming from Arduino MeshCore — flash it; auto-formats on first boot (new identity, clean storage).
Heltec ESP32 repeaters now light-sleep, and their USB CLI stops answering 10 minutes after boot
as a result. Reconnecting your terminal normally resets the node and gives you another 10 minutes;
if it doesn't, power-cycle. Details in the light-sleep section below.
If your node's GPS went quiet after its first fix, this release fixes it. A wrong bit in the
sleep command put u-blox modules into permanent backup mode on boards without hardware GPS power
control. Recovery needed a physical power cycle — a reboot was not enough. See the GPS section.
One CLI rename: set/get cad.probe.interval is now set/get probe.interval — the same setting,
renamed because one measurement now feeds both the noise floor and the CAD probe. Your stored value
carries over; only the command name changed. set/get agc.reset.interval is gone (see below).
Take this with a grain of salt — try the formatters if anything anomalous happens with your node.
A power and radio-measurement release. Repeaters no longer wake on a fixed 5 s tick — they sleep
until their next real deadline. Every ESP32 build drops to 80 MHz, and Heltec repeaters now light-sleep
between packets. The noise-floor sampler and CAD probe were unified into one measurement, the
SX126x RSSI/AGC calibration is now band-correct, and the observer role got a batch of fixes for settings
that silently never reached the hardware.
Highlights
Repeaters are now deadline-driven — the periodic housekeeping tick is gone
Every repeater woke five times a second-and-a-bit forever: a fixed 5 s "housekeeping" timer that ran the
noise-floor sample, the CAD probe, advert timers and the tempradio revert. None of that actually needs a
poll — each item is either a deadline or a sampler with its own interval.
The new mesh/Maintenance.h contract lets the event loop ask "when does maintenance next have work?"
and arm one one-shot wake for exactly that moment. An idle repeater now sleeps until its soonest real
deadline instead of on a fixed cadence. A hard ceiling (CONFIG_ZEPHCORE_MAINTENANCE_BACKSTOP_MS,
default 60 s) clamps the wake as a safety net against a missed deadline — it should never bind in
normal operation.
Companions and room servers keep their periodic tick deliberately: the companion block contains genuine
pollers (contact-dump progress, BLE advertising watchdog) with no deadline to arm, and the room server is
paced by its own 500 ms push timer regardless.
ESP32 power: 80 MHz everywhere, and light sleep on Heltec repeaters
Espressif parts have no equivalent of the nRF52's "idle at microamps in WFI" — WAITI gates the core
clock but leaves PLL, peripherals and RAM powered, so an idle ESP32 sits in the tens of mA. CPU
frequency is therefore a first-order term in a battery node's draw, and light sleep (sub-mA) is what
removes the rest.
- Every ESP32 build is now pulled to 80 MHz, matching what Arduino MeshCore ships (new
hal-espressif patch0002— upstream omits 80 from its DT map). Observers are the exception and
keep the SoC maximum (240 MHz on S3/classic, 160 MHz on C3/C6): they run a full WiFi + TLS + MQTT
stack and are mains- or big-panel powered, so throughput matters more than current. - Light sleep is now enabled automatically for repeater builds on Heltec V3, V4, V4.3,
Wireless Tracker and Wireless Tracker V2. Patch0012marks the LoRa DIO1 line as a wakeup source,
so a received packet wakes the SoC — the node sleeps between packets and deadlines and wakes for real
work. Combined with the deadline-driven change above, an idle repeater now spends most of its life
asleep instead of waking five times a second at 240 MHz.
Important
The USB CLI stops answering 10 minutes after boot on these boards. Nothing arms a UART wake
source on ESP32, so characters typed at a sleeping node are dropped. To make configuration reliable,
light sleep is blocked for the first 10 minutes after every boot
(CONFIG_ZEPHCORE_PM_BOOT_AWAKE_MS) — and because these boards' USB bridge drives EN from DTR,
connecting a terminal normally resets the node and re-arms that window. If your terminal doesn't
toggle DTR, power-cycle the node to get a console. Remote admin over LoRa is unaffected.
Console output itself is safe: a new pm_notifier flushes the console UART to idle before every sleep,
which Zephyr's PM path does not do — without it, lines would be cut mid-character. Packet-logging and
debug builds are therefore fine to run with sleep enabled.
Anything that can't tolerate sleeping holds a pm_policy lock instead of being excluded by hand: a
WiFi OTA session holds one for its duration, an uplink/observer WiFi station holds one
permanently (the association would drop), and GPS holds one while the module is powered, since the
GNSS UART is not a wake source and inbound NMEA would be lost. Under a GPS duty cycle the lock is only
held during the awake phase.
Warning
Not yet validated on hardware, and now on by default for the boards above — so this is the first
thing to suspect if an allowlisted board misbehaves. The failure mode to rule out is a deaf
repeater: check the boot log for Pin N is not wakeup capable, confirm the node still receives
packets, and only then measure current. Rebuild with -DCONFIG_PM=n to take it back out.
Boards deliberately excluded because DIO1 can't wake the SoC: XIAO ESP32-S3 (GPIO39), Station G2
(GPIO48), ThinkNode M9 (GPIO42), LilyGo T-Lora C6 (GPIO23, outside the C6's LP range), and
TTGO LoRa32 (SX127x — patch 0012 only arms the sx126x driver). Companion builds never get it on
any board: the Espressif HCI driver takes no pm_policy locks, so the SoC could sleep mid-advertising
or mid-connection. Passing pm_esp32.conf by hand still works for testing another board.
One measurement now serves both the noise floor and CAD — set probe.interval
The noise-floor sampler and the CAD calibration probe used to be two separate things riding the same
5 s tick. They are now a single periodic radio measurement: one median-of-8 RSSI reading that the
CAD probe consumes rather than measuring again.
set cad.probe.interval <sec>→set probe.interval <sec>(default still 15,0still
disables CAD probing). It is now also the noise-floor sampling rate, and it sets how often an idle
repeater wakes.- New
CONFIG_ZEPHCORE_NOISE_FLOOR_INTERVAL_MSis the build-time fallback used when probing is off —
15 s on repeaters, 5 s on companions/room servers (which wake on their tick anyway). - A sample blocked by TX, mid-RX or a duty-cycle sleep window retries on a 5 s deadline (max 2 retries)
instead of losing a full interval — deliberately reproducing the old retry grid, which matters under
RX duty cycle where blocked attempts are the norm.
RSSI reads are now correctly spaced. A median only rejects outliers if the reads are independent,
and the chip refreshes RSSI once per averaging window (~16 µs at BW 62.5, ~134 µs at BW 7.8) — reads
issued faster return the same underlying sample and the median collapses to a single read. The spacing
and the RX-entry settle delay are now derived from the SX1261/2 datasheet (rev 2.2, Table 13-82).
get cad reports the result so the assumption stays falsifiable on real hardware: the old iv: field is
replaced by sp:<mean-spread>/<zero-spread %>(<count>). A non-zero mean proves the reads are
independent, however high the zero-spread share climbs — only mean 0.0 with a high share indicts the
sampler. Measured on air at BW 62.5: 0.6/90% quiet, 0.9/84% with the floor at −103 dBm.
GPS: module configuration was never actually running — multi-constellation now works
The whole boot-time GNSS configuration path (constellations, AssistNow/EASY, elevation mask, fix rate)
was dead code. A feature macro was used ~500 lines before it was defined, and an undefined
identifier in #if is silently zero, so every board on the generic-NMEA driver ran on whatever its
module happened to default to. Fixing the ordering exposed several further problems, all of which had
been masked by the code never executing:
- u-blox
UBX-CFG-GNSSwas malformed. Config blocks were written 7 bytes long instead of 8 (the
reserved0field was missing from every block), so the receiver rejected the whole ...
Firmware 1.16.7-zephcore
ZephCore 1.16.7-zephcore
Important
Before you upgrade
From v1.16.6 / v1.16.5 — clean flash, no re-bond, bonds and data survive.
From v1.16.2 / v1.16.3 / v1.16.4 — clean flash, bonds and data survive. If you are on an
ESP32-S3 / ESP32-C board and have not yet taken the v1.16.5 update, you still owe the one-time
serial reflash: the app moved to flash offset 0x10000 in v1.16.5, and a board on the old layout
cannot cross that update over WiFi-OTA or the browser flasher — flash the -merged.bin once over
USB/serial. Affected boards: Heltec V3 / V4 / V4.3, Station G2, Wireless Tracker / V2, XIAO
ESP32-S3 / C3 / C6, LilyGo T-Lora C6. Identity, contacts, channels, prefs, and BLE bonds are
preserved. nRF52, classic ESP32 (T-Beam / PICO-D4 / TTGO LoRa32), STM32WL, and native Linux are
unaffected.
From v1.16.1 or older — flash it; on first boot it clears BLE bonds automatically (identity,
contacts, channels, prefs preserved). Re-bond your phone/desktop once.
Coming from Arduino MeshCore — flash it; auto-formats on first boot (new identity, clean storage).
Take this with a grain of salt — try the formatters if anything anomalous happens with your node.
A stability-and-hardware release. The headline is a whole-node wedge fixed in the GPS power-management
path that shipped in v1.16.6, plus RX duty cycle finally working on LR1110 boards and a new safety
margin that stops signal-independent duty-cycle packet drops on every radio. Rounded out by joystick-UI
fixes and a color-display improvement for the Heltec T114.
Highlights
Fixed: a GPS node could wedge completely on standby (regression in v1.16.6)
v1.16.6 introduced GPS-UART suspend to save power (an armed nRF UARTE receiver costs ~0.5–1 mA even with
the GPS module powered down). It had a race that could hang the entire node — no LoRa, no USB, no BLE,
CLI answering -> busy, recoverable only by reboot.
The upstream nRF UARTE suspend path spins forever waiting for an RXTO event after issuing STOPRX. In
interrupt-driven mode the receiver runs one byte at a time and re-arms from its ISR; if a byte happened to
finish in the narrow window where the driver had already disabled the ENDRX interrupt, the receiver was
left stopped-and-un-rearmed, STOPRX produced no RXTO, and the wait never returned — with the main mesh
thread stuck inside it. Any nRF board that powers its GPS module down and suspends the port on standby
could hit this.
Fixed in two independent layers, so neither alone is load-bearing:
ZephyrGPSManagersettles ~5 ms before suspending the GPS UART, letting the last in-flight byte
finish and the RX ISR re-arm, so the receiver is armed-and-idle whenSTOPRXfires — the state that
reliably yieldsRXTO. Standby happens at most every few minutes, so the cost is negligible.- New patch
0010bounds the driver'sRXTOwait to 4 ms instead of spinning forever. On timeout it
falls through to the unconditionalnrf_uarte_disable()that force-stops the receiver anyway — so even
if a byte still lands in the race window, the node can never hang.
RX duty cycle now works on LR1110 boards
LR1110 boards (T1000-E, ThinkNode M9, and other LR11xx radios) now get real RX duty-cycle sniff mode
instead of sitting in continuous receive — a substantial receive-power saving on battery nodes, the same
mechanism SX126x boards have had. This took a significant rework of the LR11xx LoRa driver to land
correctly.
New: duty-cycle safety margin stops signal-independent packet drops
set-able via CONFIG_ZEPHCORE_LORA_DC_MARGIN_PCT (default 15%), shared by every radio
(SX126x / LR11xx / LR20xx).
The theoretical per-cycle "deaf-time" budget that duty cycle is computed from assumes the sleep clock and
the wake transition are exact. They are not: the chip's sleep timer runs on an internal RC oscillator
(RC64k on SX126x, RTC on LR11xx) that drifts several percent over temperature, and the wake-transition
figure is a datasheet number the datasheet itself calls "not accurate and may vary." Either overshoot
silently pushes real deaf time past the budget and drops exactly the fraction of packets whose preamble
phase lands on the window edge — a strength-independent loss that shows up as random duty-cycle packet
drops, not as weak-signal loss.
The margin derates the whole budget before the sleep period is programmed, trading a little radio-off time
for robustness. Set it to 0 to restore the old zero-margin behaviour; lower it below 15% once you have
measured your board's clock-plus-transition on air.
Joystick UI fixes
- The 2-tap and 5-tap actions were swapped. The everyday action (LED heartbeat toggle) is now 2 taps
and the rarer one (flood advert) is 5 taps — matching the button-UI mapping and the on-device help.
Applies to both the joystick UI and single-button boards. - Fixed: a node booted with LEDs disabled could never turn its LEDs back on from the UI. The toggle
called only the heartbeat helper and left the underlyingleds_disabledgate at its boot value, so
leds_disabled=1was effectively one-way. It now flips the real gate. - The LED toggle now shows an on-screen "LEDs: ON / OFF" confirmation, so you can tell it registered.
Heltec T114 color display improvements
From PR #62 by @Calvario:
the button UI now respects CONFIG_ZEPHCORE_DISPLAY_LARGE_FONT on color panels, and the gray used for
secondary text was retuned (0x8410 → 0xEF7D) so it actually reads as a visible gray on the T114's TFT,
which crushes darker grays to invisibility. Thanks to Steve Calvário.
Other changes
- west manifest bumped (routine pinned-tree advance).
Recommended upgrade checklist
- Running v1.16.6 on a GPS-equipped nRF node? Update — this release fixes the standby wedge described
above. Bonds and data survive. - ESP32-S3 / ESP32-C boards that skipped v1.16.5: flash
-merged.binonce over USB/serial. Data survives. - Everything else, from v1.16.2 onward: just flash — bonds and data survive.
- From v1.16.1 or older: just flash — self-migrates on first boot; re-bond once.
- From Official MeshCore: just flash — auto-formats on first boot.
- Anything odd? Format first.
Firmware 1.16.6-zephcore
ZephCore 1.16.6-zephcore
Important
Before you upgrade
From v1.16.5 — clean flash, no re-bond, bonds and data survive.
From v1.16.2 / v1.16.3 / v1.16.4 — clean flash, bonds and data survive. If you are on an
ESP32-S3 / ESP32-C board and have not yet taken the v1.16.5 update, you still owe the one-time
serial reflash described there: the app moved to flash offset 0x10000 in v1.16.5, and a board on the
old layout cannot cross that update over WiFi-OTA or the browser flasher — flash the -merged.bin
once over USB/serial. Affected boards: Heltec V3 / V4 / V4.3, Station G2, Wireless Tracker / V2,
XIAO ESP32-S3 / C3 / C6, LilyGo T-Lora C6. Identity, contacts, channels, prefs, and BLE bonds are
preserved. nRF52, classic ESP32 (T-Beam / PICO-D4 / TTGO LoRa32), STM32WL, and native Linux are
unaffected.
From v1.16.1 or older — flash it; on first boot it clears BLE bonds automatically (identity,
contacts, channels, prefs preserved). Re-bond your phone/desktop once.
Coming from Arduino MeshCore — flash it; auto-formats on first boot (new identity, clean storage).
Take this with a grain of salt — try the formatters if anything anomalous happens with your node.
A maintenance release: an important admin-password fix, a substantial identity-key randomness
hardening on ESP32, GPS standby power savings, and the removal of Adaptive Power Control —
plus two new boards, muzi works R1 Neo and an experimental first cut at the ThinkNode M9 in dev.
Highlights
2-tap and 5-tap are switched
After a lot of inadvertently sent adverts, the functions of the 2 and 5 clicks were switched, so a double click with the user button now only enables/disables the LEDs and no longer accidentally sends a flood advert.
Fixed: uppercase letters in an admin password were silently lowercased
If you set or changed an admin password on v1.16.5 and can no longer log in, this is why. v1.16.5
introduced case-insensitive CLI keywords by lowercasing the first two words of every command before
matching them. That is safe for get cad — but password <value> puts the value in the second word,
so password MyPass was stored as mypass, and the password you typed afterwards never matched.
(set guest.password <value> has three words and was never affected.)
The normalizer has been removed and CLI commands are case-sensitive again, matching upstream Arduino
MeshCore. If you are locked out, re-flash and reconfigure, or log in with the all-lowercase form of the
password you set.
The one place autocapitalization genuinely hurts — typing commands to the V-Contact from a phone keyboard,
which capitalizes the first letter of every line — is handled narrowly instead: only character 0 of a
V-Contact chat line is folded. No command takes an argument at position 0, so no value can be touched.
Identity keys are now generated with hardened, hardware-verified randomness
Your node's identity — the Ed25519 keypair that is your node on the mesh — is generated once, on first
boot, and kept for life. An investigation into how the randomness behind that key is gathered found that
ESP32 boards were generating identities with far less entropy than intended. Two independent problems,
both fixed in this release:
- The ESP32 hardware RNG was running unseeded. It only produces true random numbers while the radio
(WiFi/BT) is active — and identity generation runs before the radio comes up (on repeaters, the radio
never comes up at all). Key generation now feeds the RNG from the chip's ADC noise source for its
duration, Espressif's documented method for exactly this pre-radio situation. Verified on hardware:
the RNG's output went from a fixed pattern to statistically ideal. - The timing-jitter backup source measured nothing on ESP32. It sampled the CPU's cycle counter
against work paced by that same clock — deterministic, confirmed dead on hardware. It has been replaced
everywhere by a two-clock beat source: counting CPU cycles across fixed intervals of an
independent low-frequency oscillator, whose physical drift is genuinely random. The same mechanism
now runs uniformly on ESP32, nRF52 and MG24. (nRF was never in danger — its RNG is radio-independent —
this adds an equally strong second source there for defence in depth.)
This was not signed off by inspection. A new on-device self-test (tools/rng_selftest) generated 1024
identities per platform through the real generation path: zero duplicate keys, and pairwise-difference
statistics matching ideal randomness — including a torture run with the hardware RNG deliberately
disabled, where the beat source alone still produced fully diverse keys. A node whose hardware RNG
silently dies no longer means a predictable identity.
Tip
Upgrading does not regenerate your identity — the key made at first boot stays. If your node is an
ESP32 board and its identity was generated by an earlier release, that key was created under the old,
weaker entropy. If you want one generated under the new code, factory-reset / format the node (or
erase-flash and reflash) — the next boot creates a fresh identity through the hardened path. The cost is
real: a new public key, so your contacts must re-add you, and contacts, channels and bonds start clean.
Whether that trade is worth it is your call — for a casual node, probably not; for a node whose identity
matters (an admin key, a well-known repeater), it is worth considering. nRF boards have no reason to
regenerate.
GPS standby now actually saves power
CONFIG_PM_DEVICE (device power management) is enabled for the first time, carefully scoped: system-managed
PM stays off, nothing suspends automatically, and every PM call is made explicitly from the main thread.
This buys two things:
- The GNSS UART is suspended while GPS is off or in standby. An armed nRF UARTE receiver keeps the
high-frequency clock running — roughly 0.5–1 mA continuously on nRF52840 — even with the GPS module
itself powered down. Nodes with GPS disabled in prefs were paying this for their entire uptime. The UART
is resumed before every wake, so no NMEA is lost. - GNSS drivers that boot suspended now get resumed.
gnss-nmea-genericinitializes suspended under
device PM and never opens its data pipe until told to — this was the old "enabling PM breaks GPS" trap,
now handled once at boot with retries.
The UART suspend requires an nRF UARTE port and a board that can power the GPS module down. Boards
without power control keep the UART running and forgo the saving: suspending the port while the module is
still streaming can wedge the main thread, leaving the radio deaf and the CLI answering -> busy. This
affects RAK3401 1W and GAT562 30S. Other UART drivers are left alone entirely — the saving is
UARTE-specific.
Removed: Adaptive Power Control
APC tried to save battery by lowering TX power when neighbors reported more signal margin than they
needed. It has been removed entirely, because the saving it chased is not one most nodes actually pay.
TX power is only spent at the instant of a transmission. A companion that sits idle for days and sends a
handful of messages spends almost nothing on transmit to begin with — its drain is BLE advertising, the
radio sitting in receive, and the MCU. APC could not meaningfully reduce that. On the nodes where transmit
volume is high enough to matter, the feature never got a clean run: its measurement was structurally
confounded (the echo it measured comes from the neighbor's own fixed-power transmission and does not
respond to your reduction), and the staleness timer that ramped power back up could not tell "our link
degraded" from "the mesh was quiet." Rather than ship another round of tuning on a feature with no
demonstrated payoff and a real downside — a node that reduces too far goes silent, and cannot detect that
it has — it's gone.
What changes for you:
set tx apcandset/get apc.marginare removed.set tx <dbm>sets a fixed power, as it always
did, andget txnow answers with a plain number (> 22) exactly like upstream Arduino MeshCore.- Nodes that had APC enabled now transmit at their configured
txpower. If you had turned it on,
check that your configured power is what you actually want — it is now used verbatim. - The APC line is gone from the on-device radio display, and the TX row now shows a single power
value instead of aneffective/maxpair — there is only one number now, so it is only printed once. - Your saved settings are safe. APC's two prefs bytes are kept reserved at their original offsets, so
no stored configuration shifts and nothing else is misread on upgrade.
New boards
muzi works R1 Neo
A pocket LoRa node — nRF52840 + SX1262 (RAK4630 stamp on a custom muzi baseboard), with GNSS, buzzer,
battery-backed RTC and a single user button. No display, so the UI is button actions only. It reuses the
RAK4631 flash layout (SoftDevice s140 v6.1.1, app @ 0x26000), so the stock UF2 bootloader works as-is —
no bootloader reflash needed. The board's soft-power design is handled in devicetree: the DCDC rail latches
on at boot and is released on shutdown, so long-press power-off works properly.
Two things still want confirming against real hardware: the battery divider multiplier is an estimate, and the GNSS module identity is
unconfirmed. Reports welcome.
Thanks to @WillyJL, who requested this port and provided the hardware details / most of the code
it is based on.
ThinkNode M9 — experimental, source build only
Not part of this release's prebuilt binaries or the web configurator — bring...
Firmware 1.16.5-zephcore
ZephCore 1.16.5-zephcore
Important
Before you upgrade
ESP32-S3 / ESP32-C boards — one-time serial reflash required; WiFi-OTA cannot bridge this update.
This release moves the application to a new flash location (0x10000). A board on the old layout can't
update to this build over WiFi-OTA or the browser flasher — flash the -merged.bin once over USB/serial.
Affected boards: Heltec V3 / V4 / V4.3, Station G2, Wireless Tracker / V2, XIAO ESP32-S3 / C3 / C6, LilyGo T-Lora C6.
Your identity, contacts, channels, prefs, and BLE bonds are preserved. After this reflash, OTA and
app-updates work again.
nRF52, classic ESP32 (T-Beam / PICO-D4 / TTGO LoRa32), STM32WL, and native Linux are unaffected — upgrade as usual.
From v1.16.2 / v1.16.3 / v1.16.4 — clean flash, no re-bond, bonds and data survive.
From v1.16.1 or older — flash it; on first boot it clears BLE bonds automatically (identity, contacts, channels, prefs preserved). Re-bond your phone/desktop once.
Coming from Arduino MeshCore — flash it; auto-formats on first boot (new identity, clean storage).
Take this with a grain of salt — try the formatters if anything anomalous happens with your node.
The headline of this release:
Adaptive CAD, a self-tuning listen-before-talk that calibrates itself to each site;
V-Contact, an in-app admin "channel" for companions; and a batch of LoRa-driver reliability fixes.
Highlights
Fixed: ESP32 WiFi-OTA silently reverted — upload hit 100%, then booted the old firmware
On ESP32-S3 / ESP32-C boards, the app and MCUboot bootloader were using different flash partition tables,
so each OTA update landed at an address the bootloader never checked. Every upload "succeeded" and then
quietly did nothing. Both images now share one partition map, and OTA works correctly. (The XIAO C3/C6 and
T-Lora C6 used the stock layout and were never affected.)
ESP32 app slot moved to 0x10000
The ESP32 app slot moved from 0x20000 to 0x10000 — matching the Arduino / ESP-IDF offset. This is the
change that fixes OTA above, and it also enables the configurator's fast app-only update (rewrites just
the app, keeps your settings). Requires the one-time serial reflash (see Before you upgrade).
New: Adaptive CAD — listen-before-talk that tunes itself to your site
Every transmission is preceded by a Channel Activity Detection (CAD) check that listens for other LoRa
traffic and holds off if the channel is busy. How sensitive that check is has always been a single
hardcoded number — but the right value depends on where the node lives. A quiet valley node and a
50-network hilltop need very different thresholds: too sensitive and the node keeps deferring for phantom
traffic (wasted airtime, latency); not sensitive enough and it transmits over real packets (collisions).
Nodes now measure their own RF environment and adjust automatically. In the background, between
transmissions, the radio runs short calibration probes, tracks how often each sensitivity level produces a
false "busy", and settles on the lowest false-positive threshold for that site — responding to a
changing environment within an hour or two. It runs on repeaters and companions, and it is on by
default; there is nothing to configure.
get cad— watch what it's doing: the current threshold and per-level false-positive statistics.set cad.auto off— turn off the auto-adjust to observe or hand-tune withset cad.offset.
Quiet sites end up more sensitive than the old fixed default (fewer stomped packets); noisy sites back
off until the false alarms stop. CAD also now uses 4 detection symbols everywhere, which improves detection
of an in-progress packet's payload (not just its preamble). Full guide, including the honest limitation
that the "missed detection" side isn't locally observable, is in
ADAPTIVE_CAD.md.
(SX127x boards have no hardware CAD and keep their RSSI-based gate.)
New: V-Contact — admin your companion from the chat app, no cable
Companions now expose a special contact named v<your-node-name>. Chat with it and your messages run the
same text CLI you'd use over USB serial — the reply comes back as ordinary chat messages. It's the easiest
way to check or change settings on a companion in the field without plugging in.
It's also used for two unsolicited notices: a restart-reason message after a reboot (so an unexpected
brownout or watchdog reset is visible instead of silent) and a one-shot low-battery alert. The whole
thing is local to your device — the v-contact has no real keypair and never creates a packet or touches
the radio, so none of this costs airtime. On by default; toggle the v_contact_enabled variable from the
app to disable.
LoRa driver reliability (LR1110 / LR2021)
- Fixed: LR1110 accepted CRC-failed packets. On the LR11xx/LR20xx family a CRC-failed receive raises
RX_DONEandCRC_ERRORtogether; the driver was acting onRX_DONEalone and passing corrupt frames
up the stack. It now gates on the error bits (matching the SX126x path). - Ported the SX126x preamble-grace / RX-busy handling to LR11xx/LR20xx. A foreign or noise-triggered
preamble detection could pin the channel-busy state and stall listen-before-talk recovery. The same
SF-aware grace window and header-valid latch the SX126x driver uses now guard the LR path, so a real
packet promotes to "receiving" and a false preamble releases after a bounded window.
Other fixes and improvements
- Classic ESP32 companion builds (TTGO LoRa32, T-Beam) now build and link correctly. The Espressif BT
HCI controller node wasn't wired on TTGO LoRa32 (companion needs BLE), and the CLI backing the V-Contact
chat wasn't compiled on plain (non-logging) companion builds — both fixed. Contact-table sizes on these
DRAM-tight classic-ESP32 boards were trimmed to fit alongside the BLE controller. - SenseCAP Solar LED devicetree fix.
- LilyGo T-Impulse Plus display fixes (follow-up to the initial port).
- Repeater + observer hybrid role wiring fixups (MQTT publisher / uplink credentials / observer mesh).
get/setcommand keywords are now case-insensitive (e.g.Get cadworks). Command values —
passwords, node names, keys — remain case-sensitive as they must be.- Platform: Zephyr west manifest advanced (continuing along the
v4.4-branch). - New
-updatebuild artifacts for OTA / manual app-only updates, published alongside the full images. - Docs: new
ADAPTIVE_CAD.md;Repeater_CLI_commands.md,ARCHITECTURE.md, andPROVIDER_CATALOG.md
updated for the above.
Recommended upgrade checklist
- ESP32-S3 / ESP32-C boards: flash
-merged.binonce over USB/serial. WiFi-OTA can't cross this update; data survives. - Everything else, from v1.16.2 / v1.16.3 / v1.16.4: just flash — bonds and data survive.
- From v1.16.1 or older: just flash — self-migrates on first boot; re-bond once.
- From Official MeshCore: just flash — auto-formats on first boot.
- Anything odd? Format first.
Firmware v20260714.095706
ZephCore v1.16.5-zephyr
(re-release because vContact caused regression in settings import / update)
Important
Before you upgrade
ESP32-S3 / ESP32-C boards — one-time serial reflash required; WiFi-OTA cannot bridge this update.
This release moves the application to a new flash location (0x10000). A board on the old layout can't
update to this build over WiFi-OTA or the browser flasher — flash the -merged.bin once over USB/serial.
Affected boards: Heltec V3 / V4 / V4.3, Station G2, Wireless Tracker / V2, XIAO ESP32-S3 / C3 / C6, LilyGo T-Lora C6.
Your identity, contacts, channels, prefs, and BLE bonds are preserved. After this reflash, OTA and
app-updates work again.
nRF52, classic ESP32 (T-Beam / PICO-D4 / TTGO LoRa32), STM32WL, and native Linux are unaffected — upgrade as usual.
From v1.16.2 / v1.16.3 / v1.16.4 — clean flash, no re-bond, bonds and data survive.
From v1.16.1 or older — flash it; on first boot it clears BLE bonds automatically (identity, contacts, channels, prefs preserved). Re-bond your phone/desktop once.
Coming from Arduino MeshCore — flash it; auto-formats on first boot (new identity, clean storage).
Take this with a grain of salt — try the formatters if anything anomalous happens with your node.
The headline of this release:
New: flash ZephCore from your browser — Mesh America configurator
ZephCore is now a listed provider in the Mesh America Device Configurator.
Pick your board and flash directly — companion (BLE and USB) or repeater — no toolchain, no drag-and-drop.
Adaptive CAD, a self-tuning listen-before-talk that calibrates itself to each site;
V-Contact, an in-app admin "channel" for companions; and a batch of LoRa-driver reliability fixes.
Highlights
Fixed: ESP32 WiFi-OTA silently reverted — upload hit 100%, then booted the old firmware
On ESP32-S3 / ESP32-C boards, the app and MCUboot bootloader were using different flash partition tables,
so each OTA update landed at an address the bootloader never checked. Every upload "succeeded" and then
quietly did nothing. Both images now share one partition map, and OTA works correctly. (The XIAO C3/C6 and
T-Lora C6 used the stock layout and were never affected.)
ESP32 app slot moved to 0x10000
The ESP32 app slot moved from 0x20000 to 0x10000 — matching the Arduino / ESP-IDF offset. This is the
change that fixes OTA above, and it also enables the configurator's fast app-only update (rewrites just
the app, keeps your settings). Requires the one-time serial reflash (see Before you upgrade).
New: Adaptive CAD — listen-before-talk that tunes itself to your site
Every transmission is preceded by a Channel Activity Detection (CAD) check that listens for other LoRa
traffic and holds off if the channel is busy. How sensitive that check is has always been a single
hardcoded number — but the right value depends on where the node lives. A quiet valley node and a
50-network hilltop need very different thresholds: too sensitive and the node keeps deferring for phantom
traffic (wasted airtime, latency); not sensitive enough and it transmits over real packets (collisions).
Nodes now measure their own RF environment and adjust automatically. In the background, between
transmissions, the radio runs short calibration probes, tracks how often each sensitivity level produces a
false "busy", and settles on the lowest false-positive threshold for that site — responding to a
changing environment within an hour or two. It runs on repeaters and companions, and it is on by
default; there is nothing to configure.
get cad— watch what it's doing: the current threshold and per-level false-positive statistics.set cad.auto off— turn off the auto-adjust to observe or hand-tune withset cad.offset.
Quiet sites end up more sensitive than the old fixed default (fewer stomped packets); noisy sites back
off until the false alarms stop. CAD also now uses 4 detection symbols everywhere, which improves detection
of an in-progress packet's payload (not just its preamble). Full guide, including the honest limitation
that the "missed detection" side isn't locally observable, is in
ADAPTIVE_CAD.md.
(SX127x boards have no hardware CAD and keep their RSSI-based gate.)
New: V-Contact — admin your companion from the chat app, no cable
Companions now expose a special contact named v<your-node-name>. Chat with it and your messages run the
same text CLI you'd use over USB serial — the reply comes back as ordinary chat messages. It's the easiest
way to check or change settings on a companion in the field without plugging in.
It's also used for two unsolicited notices: a restart-reason message after a reboot (so an unexpected
brownout or watchdog reset is visible instead of silent) and a one-shot low-battery alert. The whole
thing is local to your device — the v-contact has no real keypair and never creates a packet or touches
the radio, so none of this costs airtime. On by default; toggle the v_contact_enabled variable from the
app to disable.
LoRa driver reliability (LR1110 / LR2021)
- Fixed: LR1110 accepted CRC-failed packets. On the LR11xx/LR20xx family a CRC-failed receive raises
RX_DONEandCRC_ERRORtogether; the driver was acting onRX_DONEalone and passing corrupt frames
up the stack. It now gates on the error bits (matching the SX126x path). - Ported the SX126x preamble-grace / RX-busy handling to LR11xx/LR20xx. A foreign or noise-triggered
preamble detection could pin the channel-busy state and stall listen-before-talk recovery. The same
SF-aware grace window and header-valid latch the SX126x driver uses now guard the LR path, so a real
packet promotes to "receiving" and a false preamble releases after a bounded window.
Other fixes and improvements
- Classic ESP32 companion builds (TTGO LoRa32, T-Beam) now build and link correctly. The Espressif BT
HCI controller node wasn't wired on TTGO LoRa32 (companion needs BLE), and the CLI backing the V-Contact
chat wasn't compiled on plain (non-logging) companion builds — both fixed. Contact-table sizes on these
DRAM-tight classic-ESP32 boards were trimmed to fit alongside the BLE controller. - SenseCAP Solar LED devicetree fix.
- LilyGo T-Impulse Plus display fixes (follow-up to the initial port).
- Repeater + observer hybrid role wiring fixups (MQTT publisher / uplink credentials / observer mesh).
get/setcommand keywords are now case-insensitive (e.g.Get cadworks). Command values —
passwords, node names, keys — remain case-sensitive as they must be.- Platform: Zephyr west manifest advanced (continuing along the
v4.4-branch). - New
-updatebuild artifacts for OTA / manual app-only updates, published alongside the full images. - Docs: new
ADAPTIVE_CAD.md;Repeater_CLI_commands.md,ARCHITECTURE.md, andPROVIDER_CATALOG.md
updated for the above.
Recommended upgrade checklist
- ESP32-S3 / ESP32-C boards: flash
-merged.binonce over USB/serial. WiFi-OTA can't cross this update; data survives. - Everything else, from v1.16.2 / v1.16.3 / v1.16.4: just flash — bonds and data survive.
- From v1.16.1 or older: just flash — self-migrates on first boot; re-bond once.
- From Official MeshCore: just flash — auto-formats on first boot.
- Anything odd? Format first.
Firmware v20260704.220543
ZephCore v1.16.4-zephyr
Important
Before you upgrade
Updating from v1.16.2 or v1.16.3 — clean flash, no re-bond required, bonds and data survive.
Updating from v1.16.1 or older — flash the firmware; on first boot it detects the old installation and clears the BLE bond storage automatically. Your identity, contacts, channels, and prefs are preserved. You will need to re-bond your phone/desktop once.
Coming from Arduino MeshCore — flash the firmware; it detects the incompatible filesystem on first boot and formats automatically (new identity, clean storage). No formatter tool required.
Take this with a grain of salt though — try the formatters if anything anomalous happens with your node.
(re-release because meshtimesync command's reply needed a facelift, UI lost uptime and had some offset quirk, nothing more)
The headline of this release is Mesh Time Sync: an optional, off-by-default feature that lets a node fix its own clock from the mesh — no GPS, no phone. Alongside it: two new board ports, a color-TFT UI pass, a flash-wear fix on GPS nodes, and a batch of multi-byte-path-hash bug fixes.
Highlights
New: Mesh Time Sync — self-correcting clocks from the mesh (simulated from EmpireMesh live data)
Your node can now listen to the timestamps inside other nodes' signed adverts and, if its own clock is clearly wrong, gently correct itself. No GPS and no phone required. It works on every role (repeater, room server, observer, companion).
Why it matters: a node with a wrong clock shows garbage "last heard" times, and after a reboot without a time sync its own adverts can be silently ignored by every node that already knows it. On the live mesh today, almost half of all repeaters are more than an hour off — this fixes that whole class of problem automatically.
It is OFF by default. Nothing changes unless you enable it.
set meshtimesync on— enable (USB serial CLI or remote admin on repeaters/room servers/observers; on companions toggle themeshtimesynccustom variable from the app, or use the same command over the USB text CLI).get meshtimesync— live view, even while the feature is off: how many trustworthy senders it sees, how many agree, and what correction it would make. Worth watching this dry-run for a day before (and after) you enable.
Guardrails, in short: any clock set protects the node for 7 days — a GPS fix and a manual time/clock sync use the exact same 7-day suppression window, so a GPS-synced node is left alone just like a manually-set one (a live GPS re-arms it on every fix); it steps at most ±1 h at a time and at most once per 6 h; it does nothing without a quorum (default 6) of tenured, agreeing senders; and room servers/companions never step backward.
Full documentation — how to switch it on, the honest limitations, and the recovery recipe — is in MESHTIMESYNC.md. Design details (consensus algorithm, security model, why the limits are what they are) are in ARCHITECTURE.md §4.9.
New boards
- Heltec Mesh Node T096 (nRF52840) — SX1262 + KCT8103L PA/FEM, UC6580 GNSS, ST7735S 160×80 color TFT, button, LED, battery ADC.
- Seeed LoRa-E5 mini (STM32WLE5JC) — integrated sub-GHz radio (SX1262-class). No BLE/USB: runs as a companion + CLI over USART1 (serial companion transport).
- TTGO LoRa32 (ESP32 PICO-D4, SX1276) — added as the SX127x reference board (loramac-node backend), USB-UART CLI.
- LilyGo T-Impulse Plus (nRF52840) — LoRa wristband: SX1262 (S62F module, embedded 32 MHz TCXO, GPIO antenna switch), u-blox MIA-M10Q GNSS, SSD1315 128×64 OLED, TTP223 touch button, haptic vibration motor, ZD25WQ32C 4 MB QSPI flash, battery ADC. Companion + repeater. Ported from LilyGo's official sources and builds clean; on-device bring-up pending (see the board README for the hardware-verify checklist).
- Heltec Wireless Tracker V2 (ESP32-S3) — SX1262, ST7735R 160×80 color TFT, UC6580 GNSS. Build-verified across companion, repeater, room server, and observer roles.
Color TFT UI
The single-button and joystick UIs gained color and reworked screens for TFT boards (Heltec T114 ST7789V, T096 ST7735S, Wireless Tracker ST7735R). Color rendering is now gated to TFT panels only, so monochrome OLED/e-paper boards are unaffected. Also fixed: set tx (ADC/TX power) not sticking on the color-screen build.
Fixed: GPS nodes were hammering flash
ZephyrGPSManager was writing to flash on GPS activity, adding needless flash wear on tracker/companion nodes. Those writes are gone — GPS state is kept in RAM and only the intended, rate-limited persistence remains.
Other fixes and improvements
- Fixed: multi-byte path-hash mode bugs. Several paths mishandled the packed path-hash byte (top 2 bits encode hash size, bottom 6 the hop count) when
path.hash.modewas 1 or 2:- Path-discovery responses were wrongly rejected at 2/3-byte hash sizes.
- The joystick UI path editor encoded/decoded paths at a fixed 1-byte hash size instead of the mesh's actual size.
- The companion direct-send retry timeout scaled off the raw packed byte, inflating the timeout by up to ~17× on multi-byte-hash paths.
- Fixed: companion send-retry bug (direct message timeout math, per above).
- Fixed: region scope leaking onto direct-request replies (repeaters & room servers). When a repeater or room server answered a direct request, its flood reply could inherit the transport region of the last flood packet it happened to hear, instead of being sent un-scoped — so replies could go out under the wrong scope. The region is now resolved per received packet and cleared for direct packets. (Ported from upstream PR #2874.)
- Fixed: region table save silently reporting success. Writing the region map to flash always returned "success" even when a write failed (e.g. full filesystem); it now reports the actual write result.
- Fixed: ThinkNode M6 GPS & regulator handling.
- Fixed: Heltec V4 battery reading.
- Fixed: Wio Tracker L1 Grove sensor (I2C) port.
- ESP32: drop the serial/USB transport cleanly on reboot so the port releases instead of hanging.
- Live radio values now auto-refresh on-screen (non-EPD boards). Repeater/UI screens repaint automatically when a live radio value actually changes (frequency, SF, bandwidth, CR, TX power, noise floor, APC state, RX/TX activity) instead of waiting for a manual refresh. E-paper panels are deliberately left out of the auto-repaint so they don't flash on every value change (only real UI events repaint them).
- Platform: Zephyr advanced past v4.4.1 (to the
v4.4-branchtip, heading toward v4.4.2). The most relevant thing this pulls in is an upstream BLE host use-after-free fix: when a phone or desktop disconnected in the middle of a transfer, a deferred ATT "sent" callback could dereference already-freed memory and fault. All companions (nRF52, ESP32-S3, MG24) inherit the fix. - Native Linux target documented — ZephCore also runs as a native Linux process on SBCs (Femtofox / Luckfox Pico Mini, Raspberry Pi + RAK6421 HAT) with a real SX1262 on SPI/GPIO and the app connecting over TCP. See LINUX_NATIVE.md.
- README / ARCHITECTURE / CLI docs updated (RX duty cycle description,
backoff.multiplierdefault, GPS duty knobs, board tables, meshtimesync commands).
Recommended upgrade checklist
- From v1.16.2 / v1.16.3: just flash — bonds and data survive intact.
- From v1.16.1 or older: just flash — firmware self-migrates on first boot; re-bond your phone/computer once.
- From Official MeshCore: just flash — firmware auto-formats on first boot, start fresh.
- Anything odd or suspicious? Format first.
Firmware v20260619.094253
ZephCore v1.16.3-zephyr
(re-re-re-release(last time I swear) with the "smearing fix on eink after the full update" and "automount error on a freshly formatted node")
Important
Before you upgrade
Updating from v1.16.2 — clean flash, no re-bond required, bonds survive.
Updating from v1.16.1 or older — flash the firmware; the firmware detects the old installation on first boot and clears the BLE bond storage automatically. Your identity, contacts, channels, and prefs are preserved. You will need to re-bond your phone/desktop once (old pairings were stored in a different location that v1.16.2 moved away from). No formatter tool required.
Coming from Arduino MeshCore — flash the firmware; it detects the incompatible filesystem on first boot and formats automatically. Everything starts fresh (new identity, clean storage). No formatter tool required.
Take this with a grain of salt tho… try the formatters if anything anomalous happens with your node.
This is a GPS-focused release: it stops a battery-killing bug on GPS trackers, puts the GPS duty cycle under your control, and follows up the v1.16.2 ESP32 pairing work with a fix for recent Android phones.
Highlights
Fixed: ThinkNode M1 draining its battery in a day
On the ThinkNode M1, the GPS module was never actually powered down between fixes — the reset line was mislabeled in the board definition and held permanently released, so the receiver kept drawing current even when the firmware thought it was asleep. A full battery could disappear in roughly a day with GPS enabled.
The M1 now asserts the GPS hardware reset on standby, so the GPS genuinely stops between fixes. If you run an M1 with GPS on, this is the fix you want.
New: configurable GPS duty cycle — set gps duty <seconds>
You can now decide how your node handles GPS, instead of it being baked in:
set gps duty <seconds>— how long the GPS sleeps between fixes. Persists to flash and applies immediately.set gps duty 0— always-on: the GPS never sleeps. It streams fresh fixes (so telemetry is always current) and stays on long enough to download a full almanac.set gps duty default— reset to the role default.get gps duty— show the now-effective interval.
This unifies repeaters and companions behind one knob. Repeaters default to 48 h (GPS wakes only for periodic time-sync) and companions to 5 minutes — both now adjustable. Existing repeaters are migrated automatically; nothing to do.
Bad input is rejected with a usage hint, and the value is clamped to a sane range (10 s … 1 week, or 0 for always-on).
Fixed: GPS no longer pins itself on waiting for a first fix
Previously, after enabling GPS a companion would keep the receiver powered indefinitely until it got its first fix — so a node left somewhere without sky view (indoors, in a bag) could quietly drain its battery forever. The first cold-start acquisition is now bounded (5 minutes by default, enough for a genuine cold start), after which the node drops into its normal duty cycle whether or not it got a fix.
Fixed: ESP32 Secure Connections pairing (Pixel 7 / recent Android)
Following the v1.16.2 ESP32 pairing fix, SC-capable centrals — notably recent Android phones like the Pixel 7 — still failed to bond: the controller's encryption table was left uninitialized because the relevant code was guarded on an ESP-IDF symbol that never exists in a Zephyr build, so the link dropped with a MIC failure at encryption start. The guard is corrected (carried as a managed hal_espressif patch), and SC pairing now completes. Legacy-pairing devices were unaffected and continue to work. Thanks @seagull9000.
Other fixes and improvements
- Fixed: GPS always-on on a freshly flashed companion — a new install would default
gps_intervalto 0 (always-on) instead of the intended 5-minute duty cycle, causing the GPS to run continuously from the first boot even on battery-powered devices. The correct default is now applied before prefs are saved on first boot. - Fixed: BLE silent on first boot after flashing over Official MeshCore or old ZephCore — the first boot now auto-detects the previous firmware and performs the minimum necessary storage migration without requiring the formatter tool:
- No prefs or Arduino prefs detected: full LFS + NVS format (clean slate, new identity).
- Valid ZephCore prefs + old file-based bond store (
/lfs/settings): NVS erased only; identity, prefs, and contacts are preserved. - Valid ZephCore prefs + NVS already initialized (v1.16.2+): nothing erased; bonds survive.
A marker file (/lfs/_zc_init) is written after the first clean boot so the check never runs again.
- GPS manager refactor — the acquisition/standby state machine was consolidated around a single duty-interval source, removing duplicated timeout logic and the hard-coded repeater interval.
- README updates.
Recommended upgrade checklist
- From v1.16.2: just flash — bonds and data survive intact.
- From v1.16.1 or older: just flash — firmware self-migrates on first boot, re-bond your phone/computer once.
- From Official MeshCore: just flash — firmware auto-formats on first boot, start fresh.
- ** Anything odd or suspicious? Format first.
Firmware v20260616.202853
ZephCore v1.16.2-zephyr
Important
Before you upgrade — please read
This release changes where devices store their BLE pairing data. The change affects all platforms, but the upgrade steps differ:
All boards (nRF52 and ESP32): you must re-bond. Existing phone/desktop pairings will not carry over. After flashing, remove the old pairing from your device's Bluetooth settings (and from the ZephCore app), then pair again from scratch.
nRF should update cleanly, but I recommend doing a flash erase + flash fw + import backup to be absolutely sure. Formatters can be found in the repo's formatter folder. Check your softdevice version before using one.
Highlights
Critical fix: ESP32 Bluetooth pairing restored
On the updated Zephyr 4.4.1 base, ESP32 boards could advertise but never completed pairing — the Espressif Bluetooth controller would assert the moment a phone began to pair. This release restores the controller's link-layer encryption so pairing works again. If you have an ESP32-based node (Heltec, XIAO, Station G2, T-Beam, etc.), this is the fix you've been waiting for.
Additionally, Android "connect from app" now works on ESP32. Advertising now always exposes the stable identity address, so the Android companion app's in-app connect flow succeeds while iOS pairing continues to work.
BLE pairing data moved to its own storage area
Bluetooth bonds previously shared the same filesystem as your identity, preferences, and contacts. They now live in a dedicated, isolated storage partition (NVS) on both nRF52 and ESP32. This means a busy pairing store can no longer corrupt your contacts or settings, and pairing writes never touch your user data.
A factory reset (erase) now also clears stored BLE bonds, then reboots automatically so everything comes back up clean — previously bonds could survive an erase.
New board: Heltec Wireless Tracker V1.1 (ESP32-S3 + SX1262), thanks to @seagull9000
Full support for the Heltec Wireless Tracker V1.1 companion (ESP32-S3 + SX1262 radio + ST7735R TFT + UC6580 GPS), including:
- Correct battery ADC routing (avoids a wiring conflict in the upstream board definition that otherwise broke all LoRa SPI reads)
- Battery voltage/percentage readings
- Onboard GNSS
- Status-LED heartbeat
- OLED/TFT display and BLE companion transport
New board: LilyGo T-Beam v1.2 (SX1262)
Full support for the classic-ESP32 LilyGo T-Beam v1.2 with the SX1262 radio, including:
- AXP2101 power management (LoRa + GPS rails brought up automatically at boot)
- Battery fuel-gauge readings over I2C — accurate battery voltage and percentage, even though this board has no battery ADC
- Onboard GNSS (NEO-6M / NEO-M8N)
- The user button wired up (short press = page/select, long press = action)
- OLED display, BLE companion transport, and CLI-based repeater mode
Note: classic-ESP32 repeaters (e.g. T-Beam) run CLI-only and do not include WiFi-AP OTA — the WiFi driver buffers plus OTA heap don't fit the classic ESP32's RAM. ESP32-S3 / C-series repeaters keep WiFi OTA.
E-paper anti-ghosting
E-paper displays now perform a periodic full refresh (every 8 partial updates by default) to clear the faint "ghost" images that build up where text changes most often, like the status bar. The interval is configurable build-time, and the feature is automatically ignored on OLED/TFT screens that don't ghost.
More reliable companion connection (Linux / native transport)
The native TCP transport now applies the same congestion control already used over Bluetooth. Instead of silently dropping data when the connection is briefly saturated (for example, an incoming message arriving during a large contact/channel import), it now back-pressures and retries, and important protocol responses are never dropped. A stalled peer that stops reading is cleanly disconnected instead of hanging the node.
Consistent adverts from every trigger
Sending an advert from the device button or the on-screen menu now behaves exactly like sending one from the mobile app — it correctly honors your path-hash mode and region scope. Previously, button/menu adverts could bypass those settings.
Other fixes and improvements
- Fixed password storage bug. The
passwordandguest.passwordCLI commands now correctly null-terminate the stored value, preventing a possible mismatch on later comparison. - Updated Zephyr base to 4.4.1 and synced with upstream MeshCore
dev. - ESP32 debug builds now log over the USB/UART console with appropriately sized buffers, instead of expecting nRF-style J-Link/RTT hardware.
Recommended Upgrade checklist (recap)
- Back up contacts, channels, and identity; flash the updated formatter UF2 / erase flash command for ESP.
- Flash v1.16.2-zephyr firmware.
- Remove the old BLE pairing on phone/computer and re-bond.
- Restore your data.
Firmware v20260613.205222
DO NOT DOWNLOAD THIS RELEASE JUST TO UPDATE, YOU NEED A FLASH ERASE FOR IT TO WORK PROPERLY.
I'LL RE-RELEASE IT SOON AS I CATCH THE BUG!
(it seems devices with multiple bondings are affected only)
So if something is broken: delete bond, erase flash and try again.
What's New
- RX duty cycle (power saving) reworked — now properly tested against the datasheet, gives ~40-55% radio-off time with no packet loss on up-to-date nodes. Toggle with
set rxduty on/off. (Mixed old/new meshes: keep it off on infra nodes for now. Needs preamble 32 on adjacent nodes) - Joystick UI now shows accented characters (Hungarian/Slovak/Czech/Polish/etc. names display correctly), emojis are stripped instead of showing garbage. Unlock gesture changed to Left -> OK -> Right (easier/faster than the old Back-OK-Back).
set rxduty/set radio.rxgainnow also accepton/off, not just1/0.
Fixes
- WisMesh Tag battery percentage was reading high — calibration corrected.
- RTC clock could get permanently stuck blank after battery loss — now recovers and re-syncs.
- Clock source indicator (GPS/App/Local) and display now update correctly/immediately.
- Auto-shutdown no longer triggers on a brief voltage dip during transmit (needs 3 low readings in a row now).
- Fixed a race in MQTT "online" status reporting on repeaters/observers.
- Several small crash/overflow hardening fixes from ongoing code audit (no user-visible behavior change).