release note supdate and X1 sensor tuning

This commit is contained in:
liquidraver
2026-08-12 22:14:39 +02:00
parent ebbbfdc559
commit ae0ff65cce
3 changed files with 179 additions and 147 deletions
+130 -147
View File
@@ -1,198 +1,181 @@
# ZephCore 1.17.0-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-path hardening.
---
> [!IMPORTANT]
> ## Before you upgrade
>
> **From v1.16.5 v1.16.8** — clean flash, no re-bond, bonds and data survive.
> **From v1.16.5 v1.16.8** — just flash it. Bonds and data survive, no re-pairing.
>
> **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.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; on first boot it clears BLE bonds automatically (identity,
> contacts, channels, prefs preserved). Re-bond your phone/desktop once.
> **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; auto-formats on first boot (new identity, clean storage).
> **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 does not
> have one.** Contacts, channels and prefs move to internal flash on first boot. See the M6 section.
> **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 and room-server telemetry no longer hands out your GPS position to non-admins.**
> If you were relying on public position reporting from a fixed node, set the location in prefs and
> advertise it instead.
> **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 (new in 1.16.8) is still awaiting hardware validation.** It remains on by default
> for Heltec repeaters, and their USB CLI still stops answering 10 minutes after boot as a result —
> reconnecting the terminal resets the node and gives you another window. Rebuild with `-DCONFIG_PM=n`
> to take it out.
> **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.
---
A **hardware and receive-path release**. A new tracker board lands (Seeed SenseCAP MeshTracker X1, the
first LR2021 target with real hardware behind it), the LR2021 gains multi-SF receive, the LR11xx and
LR20xx receive gates get the same stuck-state bound the SX126x got in 1.16.x, GPS module configuration
grew a diagnostics path and a dynamic-model setting, and the Zephyr pin moves to 4.4.2 final.
## New board — Seeed SenseCAP MeshTracker X1
## Highlights
`meshtracker_x1` — nRF52840 + **Semtech LR2021**, 863928 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.
### New board — Seeed SenseCAP MeshTracker X1
It is essentially a T1000-E with a different radio, so most of the board reuses well-travelled code.
What's new:
`meshtracker_x1` — nRF52840 + **Semtech LR2021**, 863928 MHz / 22 dBm, dual-band L1+L5 Airoha GNSS,
SPA06-003 barometer, DRV2605L haptic motor, RGB LED, buzzer, 1100 mAh high-voltage LiPo, 8 MB QSPI
flash and an on-board RTC. Companion, repeater and room-server all build.
- **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. **The X1's motor and buzzer are not yet
confirmed working** — see Known limitations.
- **8 MB external flash** for contacts, channels and the blob cache, plus the on-board RTC.
Hardware-wise it is a T1000-E with the radio swapped, so the LoRa SPI pins, button, buzzer, battery
divider and the whole GNSS control pin set map straight onto the existing T1000-E paths. What is new
on this board:
## The LR2021 radio is now validated on hardware
- **SPA06-003 barometer driver, written from scratch** (`adapters/sensors/spa06.c` plus its binding) —
no upstream Zephyr driver exists. It lives in the app tree rather than the Zephyr tree, so it needs
no patch and survives `west update`. Pressure and temperature are reported through the normal
environment-telemetry path.
- **Haptic notifications.** `helpers/ui/haptic.c` wraps Zephyr's `ti,drv2605`, and the buzzer setting
became a four-state notification mode — `set buzzer 0|1|2|3` = silent / sound+vibrate / vibrate /
sound, with the 3-tap button cycling through them. Boards with no motor keep a plain on/off toggle.
The value rides in the existing `buzzer_quiet` byte, so there is no prefs migration.
- **8 MB of QSPI flash as `/ext`** for contacts, channels and the blob cache, and the on-board
YSN8900 RTC on its own I²C bus, discovered and BCD-validated at boot.
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:
> [!NOTE]
> The **LR2021 radio path is still bring-up code.** The driver is ZephCore's own, and only one LR2021 board
> has been validated on air yet. Treat X1 radio behaviour as untested; the rest of the board (GNSS,
> sensors, storage, UI, power) is on well-travelled paths.
- **The chip's firmware patch is now loaded at boot** (Semtech "highly recommends" it; without it the
chip runs unpatched). The `PRAM 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.
### LR2021 multi-SF receive — `set extra.sf`
`promicro_lr2021` still builds but remains untested (the module was destroyed during bring-up).
The LR2021 can demodulate up to **three extra spreading factors at once**, alongside the configured
one, on the same bandwidth. One repeater can therefore serve several SF communities without splitting
the site, and which SF a packet arrived on is a chip-side readout rather than an inference.
## 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 # receive SF9 and SF10 in addition to the configured SF
set extra.sf 9 10 # also receive SF9 and SF10
get extra.sf # -> 9,10
set extra.sf off # back to single-SF receive
set extra.sf off # back to single-SF
```
> [!IMPORTANT]
> **Receive only, and the bridge it creates is one-way.** Transmission always uses the single
> configured `sf`, and every detector shares one bandwidth — multi-SF, not multi-channel.
> **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.
>
> A node running `sf 7` with `extra.sf 8` hears SF8 traffic and *does* forward it, but the forward goes
> out at SF7. Traffic therefore moves **SF8 → SF7 only, and nothing comes back**. Adverts and one-way
> 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. That is worth weighing before deploying it next to a busy
> slow-SF island.
>
> The direction cannot be reversed. Because every extra SF must be *greater* than `sf`, the main SF is
> always the lowest in the set, and TX always uses the main SF — so a side-detector node is always
> high-SF-in / low-SF-out. Two of them back to back point the same way; **no configuration carries
> SF7 → SF8.** Think of this as a collector that pulls slower-SF stragglers into a fast mesh, not as a
> link between two SF islands. A genuine bidirectional bridge needs two nodes joined by a non-LoRa
> link, or one node time-slicing its radio config — neither of which this feature provides.
> 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 constraints are enforced in the driver, so an accepted set is a valid one: every extra SF
must be **greater** than the configured `sf`, all distinct, highestlowest ≤ 4, and at BW ≥ 500 kHz at
most two (only one when `sf` ≥ 10). The setting is persisted and re-applied on every RX entry; a set
that no longer fits after an `sf` or `bw` change is refused at boot and logged rather than silently
half-applied. `Error: unsupported` on every non-LR2021 radio.
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.
One thing worth knowing about how this interacts with listen-before-talk: the chip's SF constraint for
**CAD** is the exact inverse of the one for **receive** — CAD wants the main SF above the side SFs,
receive wants it below. Both cannot hold at once, so the driver switches side detectors off for each
LBT CAD and restores them when RX re-arms. That costs two extra SPI commands per transmission and
needs no configuration. (Arduino MeshCore hit the same wall from the other direction, as a `-706`
error on the receive restart after a hardware CAD.)
## No more silent mute-until-reboot
### The LR11xx receive gate can no longer wedge
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.
The driver reports "we are currently receiving" from the chip's latched PREAMBLE_DETECTED and
HEADER_VALID bits, and that answer is what gates every transmission. Neither bit is self-clearing:
they are dropped only by a terminal interrupt or an RX restart, and in continuous receive there is no
symbol timer, so neither is guaranteed to arrive. A header whose packet never completes would pin the
gate true **forever** — the node keeps receiving but never transmits again, until reboot, with nothing
in the log pointing at it.
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.
This is the same class of fault the SX126x driver was hardened against earlier (`0013`), and it is now
closed on the LR11xx:
## Adaptive CAD now uses only levels the chip can distinguish
- **LR11xx (LR1110)** already had the preamble grace; it gains the payload deadline.
- **LR20xx (LR2021)** still has an unbounded gate — a bare OR of the two bits. The bound was written
for it too, then pulled back out: it landed alongside a duty-cycle rework that regressed receive on
the MeshTracker X1, and the whole group was reverted to the last configuration confirmed working on
hardware. It will return once the LR2021 receive path is settled.
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.
The deadline is the airtime of a maximum-length packet at the current SF/BW at coding rate 4/8 with
LDRO pinned on, plus 25% and 100 ms — deliberately generous, because this is a stuck-state safety net
and releasing early would start a transmission on top of a packet that is still arriving. When it
fires it logs a warning, which is the tripwire that would prove the fault happens in the field; it has
never been reproduced on a bench.
## Virtual-contact fixes (companion)
### ThinkNode M6 — the external flash is gone, because the chip is not there
The built-in admin chat contact got three fixes worth knowing about:
The M6 routes a QSPI bus and its datasheet lists an MX25R1635F, so ZephCore declared the part, gave it
a power-enable regulator, and mounted it as `/ext`. Upstream Arduino MeshCore has since dropped the
same assumption after hitting it on real hardware — *"M6 doesn't have flash chip available, so
companion roles fail to start"* — which means companion builds most likely never came up on this board
here either.
- **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 is `set v.contact off` on 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.
The flash node, its partition, the enable regulator and the `/ext` mount are removed. Everything now
lives on internal flash: identity, prefs, contacts and channels all on `/lfs`. This is the second
ThinkNode M6 assumption disproved by upstream bench data in as many releases — the GPS REINIT pin was
the first — so the board is worth treating as under-verified generally.
## GPS: dynamic model and a diagnostics readout
### GPS module configuration: dynamic model, and a diagnostics readout
- **`CONFIG_ZEPHCORE_GPS_NAV_MODE`** sets 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 survives reflashing the host, so a second-hand module can arrive
stuck in an airborne profile and behave oddly on a rooftop.
- **`set gps diag on` / `get gps diag`** shows what the last module-configuration attempt actually did:
which path ran, bytes sent, and tracked satellites per constellation. Not persisted.
Two additions on the GNSS side, both aimed at the thing that is hardest to see from outside: whether
the module actually accepted what the firmware sent it.
- **`CONFIG_ZEPHCORE_GPS_NAV_MODE`** sets the dynamic model on CASIC-family modules (Quectel L76K /
L76KB, Air530Z) — stationary by default for repeaters and room servers, automotive otherwise. This
is worth setting rather than leaving alone, because **the model is stored in the module** and
survives reflashing the host: a module pulled from another device can arrive stuck in an airborne
profile and behave oddly on a rooftop.
- **`set gps diag on` / `get gps diag`** reports what the last module-configuration attempt actually
did: which path ran, how many bytes were sent, and tracked satellites per constellation. Not
persisted — it clears on reboot.
### `set leds on|off` — one master switch
## `set leds on|off` — one master switch
A single persisted switch for every LED on the node, applied live: heartbeat, unread-message and LoRa
TX-activity LEDs, plus the message and shutdown flashes. It works on every role, including headless
repeaters where the TX LED is the only one that ever lights. The display backlight is not covered —
that is the separate UI brightness setting.
TX-activity LEDs, plus the message and shutdown flashes. Works on every role, including headless
repeaters. Display backlight is separate (that's the UI brightness setting).
### Repeater and room-server GPS position is now admin-only
## Repeater / room-server GPS position is admin-only
Telemetry from a repeater or room server used to include the node's GPS position for any requester
holding the location permission. It is now released to **admins only**. A fixed node that should
publish its position can still do so through prefs and adverts, which is the deliberate, visible way
to share it.
Telemetry from a repeater or room server used to hand out its GPS position to anyone holding the
location permission. It now goes to **admins only**. A fixed node that should publish its position can
still do so through prefs and adverts.
## ThinkNode M6 — external flash removed
The M6's datasheet lists a flash chip that the board doesn't have, which almost certainly meant
companion builds never came up on it. The flash node, partition, power regulator and `/ext` mount are
gone; identity, prefs, contacts and channels now all live on internal flash. This is the second M6
datasheet claim disproved by real hardware in as many releases, so treat this board as under-verified
generally.
## Other changes
- **Zephyr pinned to 4.4.2 final** (was a 4.4.2 pre-release commit).
- **ESP32: the user button is now a light-sleep wake source** on boards using the shared PM overlay, so
a sleeping node wakes on a button press rather than only on a received packet.
- **LR2021 SPI HAL resynced with RadioLib**, and the driver picked up a batch of datasheet-conformance
fixes: the TCXO start-up deadline is counted in 32 MHz periods rather than RTC ticks (the previous
unit turned a 5 ms allowance into 5 µs and guaranteed a start-up error), front-end calibration is
issued in its full three-slot form, the DIO interrupt mask is narrowed to the events the handler
actually services, the CAD detection thresholds now come from the datasheet's 4-symbol row to match
the 4-symbol window actually used, and the receive payload-length filter is restored after each
transmission — without that, a node silently stopped hearing any packet longer than its own last
transmission.
- **Adaptive-CAD and noise-floor sampling** picked up further fixes in the SX126x patch set.
- **Zephyr pinned to 4.4.2 final.**
- **ESP32: the user button now wakes the node from light sleep** on boards using the shared PM overlay
— previously only an incoming packet would.
- **`get freqerr`** (LR2021 only) reports carrier frequency error measured on received packets. Purely
diagnostic — nothing acts on it, and small values are the expected answer.
- **X1 external flash runs in quad mode**, and the LR2021 SPI layer was resynced with RadioLib.
- Further adaptive-CAD and noise-floor sampling fixes in the SX126x driver.
## Known limitations
- **LR2021 is not hardware-validated.** No LR2021 board has been confirmed on air. The X1's radio path
and everything in the multi-SF feature above are code-complete and build-verified only.
- **ESP32 light sleep is not hardware-validated** and remains on by default for Heltec repeaters. The
failure mode to rule out is a deaf repeater; check the boot log for `Pin N is not wakeup capable`.
- **ThinkNode M3 / M6 GPS fixes are not hardware-confirmed** — both were derived from upstream bench
data on their respective boards, not measured here.
- **ESP32 light sleep is validated on one board only** — a Heltec V4, where it works. It is on by
default for all Heltec repeaters, so the other variants are running it untested. The failure mode to
watch for is a deaf repeater; check the boot log for `Pin N is not wakeup capable`.
- **ThinkNode M3 / M6 GPS fixes are not confirmed here** — both came from upstream bench data on those
boards, not measured on ours.
- **`promicro_lr2021` is untested.** The X1 is the only validated LR2021 board.
- **The X1's barometer, haptic motor and buzzer are not validated.** The radio, GNSS, storage, UI and
power paths are; these three are not. The haptic driver is currently failing to come up at boot
(`haptic drv2605@5a not ready`) and is under investigation.
- **The X1's on-board RTC does not hold time across a reboot.** Its supply is switched by an MCU pin,
which goes high-impedance on reset, so the chip raises its power-loss flag and the clock is re-set
from the next GPS/app/CLI sync. Time is still correct in normal operation; it just isn't preserved
across a restart.
@@ -291,11 +291,24 @@
reg = <0x77>;
};
/* Deferred-init so haptic_init() owns the power-up sequence.
*
* Upstream's drv2605_init() spends DRV2605_POWER_UP_DELAY_US *before*
* drv2605_gpio_config() raises en-gpios, then reads the status register
* over I2C immediately after — so the part gets no settling time at all
* between EN rising and its first transaction, and NACKs. Deferring the
* node is what lets us assert EN and wait first; without it the driver
* has already run (and failed) by the time the app gets a say, and
* device_init() would just answer -EALREADY.
*
* Not a rail problem: the SPA06 shares this bus, this rail and this init
* priority and comes up fine. The enable pin is the only difference. */
haptic: drv2605@5a {
compatible = "ti,drv2605";
reg = <0x5a>;
actuator-mode = "LRA";
en-gpios = <&gpio1 5 GPIO_ACTIVE_HIGH>;
zephyr,deferred-init;
};
};
+36
View File
@@ -14,11 +14,44 @@ LOG_MODULE_REGISTER(zephcore_haptic, CONFIG_ZEPHCORE_SENSORS_LOG_LEVEL);
#if DT_HAS_COMPAT_STATUS_OKAY(ti_drv2605)
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/haptics.h>
#include <zephyr/drivers/haptics/drv2605.h>
#define HAPTIC_NODE DT_COMPAT_GET_ANY_STATUS_OKAY(ti_drv2605)
#if DT_PROP(HAPTIC_NODE, zephyr_deferred_init)
/* Wake a deferred-init DRV2605 and run its init here, where the part's supply
* rail has been up since boot instead of microseconds.
*
* The enable pin is asserted *before* device_init() on purpose. Upstream's
* drv2605_init() spends its DRV2605_POWER_UP_DELAY_US before it configures the
* GPIOs, so EN rises and the very next thing it does is read the status
* register over I2C no settling time at all between the two. Raising EN
* ourselves and waiting turns that race into a plain sequence; the driver's own
* gpio_pin_configure_dt() to OUTPUT_ACTIVE afterwards is then a no-op. */
static void haptic_deferred_init(const struct device *dev)
{
static const struct gpio_dt_spec en =
GPIO_DT_SPEC_GET_OR(HAPTIC_NODE, en_gpios, {0});
int rc;
if (en.port != NULL && gpio_is_ready_dt(&en)) {
if (gpio_pin_configure_dt(&en, GPIO_OUTPUT_ACTIVE) == 0) {
/* 250 us is the datasheet figure; 1 ms costs nothing on a
* one-shot boot path and covers the rail as well. */
k_msleep(1);
}
}
rc = device_init(dev);
LOG_DBG("haptic deferred init: rc=%d ready=%d", rc,
(int)device_is_ready(dev));
}
#else
static inline void haptic_deferred_init(const struct device *dev) { ARG_UNUSED(dev); }
#endif
static const struct device *haptic_dev;
static bool haptic_enabled = true;
@@ -36,6 +69,9 @@ int haptic_init(void)
int rc;
haptic_dev = DEVICE_DT_GET(HAPTIC_NODE);
if (!device_is_ready(haptic_dev)) {
haptic_deferred_init(haptic_dev);
}
if (!device_is_ready(haptic_dev)) {
/* The DRV2605 driver logs its own reason at debug level, so say
* enough here to tell "chip absent" from "bus down". */