SomnoTrace v4.0.0-beta6
✨ New since v4.0.0-beta5
🖥️ Bridge status at a glance
The LCD now shows the SomnoBridge BLE link and the EZShare card's Wi-Fi
state, so a dropped bridge or a card that failed to join is visible without
opening the portal.
🧾 Historical settings are provenance-checked
- Imported session settings now need a verified source and matching machine
identity before they are treated as historical facts. Legacy sessions
without that proof stay unknown; they are no longer filled from the
current machine's settings. - Native
STR.edfimports keep their source files if safe processing fails,
and a root identity/settings conflict no longer blocks unrelated day
folders. - AS10 card settings use a family-specific import/export path: supported
fields keep the card's own scaling and enum values; fields whose
translation is not verified stay unknown instead of being guessed. - AirCurve 11 VAuto/S-mode settings are handled, missing snore/flow-limit
samples are preserved, and empty dashboard panels now explain the empty
state instead of shimmering forever. - Missing days in the STR calendar get explicit unknown placeholders, so a
gap cannot shift later nights onto the wrong date.
🛟 Import recovery is isolated
Malformed or unreadable root files are retained or quarantined instead of
being consumed. One bad root file no longer stops queued day folders, and
copied or cached identity is no longer accepted as proof of a session's
machine.
🌉 Bridge sync reliability
- Recent days are no longer hidden by a 64-entry limit. The bridge
selects from the complete catalog, sorts newest-first, and only then
applies the requested window. - Catalog activity is honest. First scans, evicted reloads and GONE-only
changes no longer mark a folder permanently active; only real writes do.
Budget pressure is preflighted and reported asINCOMPLETEinstead of
publishing an over-budget catalog, and a complete parent listing retires
unscanned folders that really disappeared. - Failed feeds no longer look like healthy monitoring. A failed parent
or day feed marks the round failed, marks the folder stale, and does not
refresh therapy liveness. GONE-only changes no longer open a therapy
session. - No silent truncation.
LIST_DAYandLIST_DIRECTORYescape card
names and return an explicit resource error when the complete response
cannot fit.PREPARE_DAYSreports resident/stale/gone and honest queue
results, andGET_FILEdistinguishes gone/unscanned/evicted/stale days
instead of reviving a removed folder. - FATFS-safe publication. A changed file replaces the previous copy
through a backup protocol instead of failing on an existing destination. - Ledger recovery. Malformed or oversized ledgers are preserved as
.bad, every save is checked, null timestamps compare as null, and
duplicate keys are replaced. - Stricter sync validation. Malformed day/file arrays and stale
listings are rejected, in-flight.part/.tmpand unsupported files are
filtered, drift metadata is written atomically, and an explicit pending
marker preserves bridge provenance when drift is unavailable. - Expected timestamps and day-specific drift. A source that changed
under a transfer, or a drift record from another day, is rejected instead
of being accepted silently.
🔔 Release status is honest
The update checker now compares versions instead of raw strings. An older
stable release is no longer offered as an update for a newer beta; optional
v, build metadata, dirty/dev builds and unparsable tags are handled
without guessing.
✅ Testing
- SomnoTrace host suite: 33 run, 0 failed, 1 skipped (host mbedTLS absent).
- SomnoBridge host suite: passed, including new catalog capacity/GONE tests.
- Both ESP-IDF target builds pass.
Everything in v4.0.0-beta5
SomnoTrace v4.0.0-beta5
The SomnoBridge release. 🌉 SomnoTrace now pairs, provisions, time-syncs
and pulls therapy data through the Wi-Fi↔BLE bridge — the AirSense's SD
card never has to leave the machine.
beta5 fixes release-image builds: a clean build (which regenerates its
configuration from sdkconfig.defaults) had NimBLE security compiled
out entirely, so SomnoBridge pairing failed instantly on every attempt —
with the bridge never even contacted.
✨ New since v4.0.0-beta4
🔐 Clean-built release images could not pair at all
- The root cause of beta4's pairing failures —
build-dist.sh --clean
deletessdkconfigand regenerates it fromsdkconfig.defaults, which
had NimBLE SMP disabled (# CONFIG_BT_NIMBLE_SECURITY_ENABLE is not set). With SMP compiled out,ble_gap_security_initiate()is a stub
that returnsBLE_HS_ENOTSUP (8)unconditionally — before the bond
store is consulted and before any SMP packet is sent. Every clean-built
release image could therefore never pair a SomnoBridge; only developer
builds with a hand-tunedsdkconfigworked. - SMP enabled in the defaults —
CONFIG_BT_NIMBLE_SECURITY_ENABLE,
SM_LEGACY,SM_SC(LE Secure Connections) andNVS_PERSIST(bond
persistence across reboots) are now set insdkconfig.defaults, so a
clean release build pairs exactly like a developer build. - Diagnostics name both causes — an
ENOTSUP (8)from the SM layer
now logs "SMP disabled or bond store full; the bridge was never
contacted", so this class of failure is one line to diagnose instead of
a bridge-reset goose chase. - Verified on hardware — a build made from defaults only (the exact
--cleanpath, confirmed by the 21 KB smaller image and the SMP code
present insdkconfig.h) completes
link encrypted, authenticated and bonded→paired→subscribed.
✨ New since v4.0.0-beta3
🔗 SomnoBridge pairing — local bond-store failures fixed
- Pairing no longer fails before the bridge is contacted — NimBLE's
SM layer refuses to start pairing when its capacity check
(ble_sm_chk_store_overflow) trips, returningBLE_HS_ENOTSUP (8)
synchronously. That check counts stored bonds plus any active SMP
procedure againstCONFIG_BT_NIMBLE_MAX_BONDS— which was 2 (and 1
in the shipped defaults). One stored bridge bond plus a single live
pairing procedure was enough to block every attempt. The failure was
then reported as "bridge refused pairing — FORGET_PEER is required",
which sent users to reset the bridge for no reason. - Bond store now self-heals — registered a store-status callback
that evicts the oldest peer bond (ble_store_util_delete_oldest_peer)
when the store reports FULL, instead of refusing to pair. - Capacity raised —
CONFIG_BT_NIMBLE_MAX_BONDS2 → 4, and
sdkconfig.defaults1 → 4 (a clean build from defaults could not
pair anything at all). - Honest diagnostics — rc=8 now says the refusal is local and the
bridge was never contacted, and the portal reports "pairing blocked
locally" instead of blaming the bridge. - No more spurious "Pair failed" — a connect still winding down
answersEBUSY (15); the pair op now retries once after 400 ms
instead of surfacing an error on a quick second press. - Verified on hardware — NVS bond-store dump (IDF
nvs_partition_tool) confirmed a single valid bridge bond (LTK
present, LESC, authenticated); live pairing completes
encrypted, authenticated and bonded→paired→subscribed.
✨ New since v4.0.0-beta1
💾 SomnoBridge "Configure" dialog — smarter Save & Test
- Join is now verified, not assumed — after saving a custom card
password, the dialog polls the bridge's real link state
(GET_STATUS.sta_connected, 1 s × 12) instead of running a redundant
probe-join. Success shows "Bridge joined the card"; a typo'd password
gets a clear red error instead of a false "good". - Security probe only when it matters — the factory-password check
now runs only when the saved password is88888888or the card is
open. Custom passwords skip it entirely. - Dialog closes itself on success — the button flips to OK and the
window auto-dismisses; nothing left to configure means nothing left
on screen. It stays open on any failure. GET_STATUSadded to the/api/sb/opproxy whitelist;
EZSHARE_CHECKtimeout raised 25 s → 32 s.
✍️ Portal wording — data-source direction made explicit (#345)
- Pairing card renamed "ResMed Series 11 / SomnoBridge" — covers
AirSense 11 and AirCurve 11 plus the bridge path (was "ResMed
AirSense 11 CPAP" even though it pairs a SomnoBridge too). - Scan button renamed "Scan for ResMed / SomnoBridge".
- Upload Window now states the direction: "days recorded on this
device are uploaded from SomnoTrace to the destinations above" —
plus a note that SomnoTrace records live over Bluetooth and cannot
import history stored on the SD card inside your CPAP machine. - Reset Upload State description and confirm dialog say the same,
and now correctly state the upload-window cap instead of "all days". - Maintenance section says "the SomnoTrace SD card" — no more
ambiguity about which card an action touches.
🐛 Fixes
- 📊 Dashboard could shimmer forever — with no sessions recorded,
the "Connecting & loading sessions…" progress bar was never
dismissed; "No sessions recorded yet" appeared only as a small muted
line. This is what looked like an 18-hour hang in #345. The dashboard
now stops loading and explains the empty state in one line.
Everything in v4.0.0-beta1
✨ Highlights
- 🌉 SomnoBridge integration — a complete BLE client (
sb_ble,
~1.5k LoC): discovery, passkey pairing, bonded GATT ops over the
framed protocol, streaming file pulls, and a/api/sb/opHTTP proxy
that drives every bridge op from the portal. - 🕐 Authoritative UTC & drift correction — SomnoTrace anchors its
NTP time on the bridge's monotonic clock with periodic exchanges,
reads back the fitted drift on every sync pass, and applies the
estimate when importing sessions — AS11 clock wander is corrected
instead of silently skewing therapy data. - ⚙️ "Configure SomnoBridge" dialog — card network picker (scrollable
AP list, OPEN/SECURED badges, saved network), AS10/AS11 machine cards
with the AS11 GMT-offset picker, an X close button, and an
insecure-card flow: factory88888888and open cards are detected by
a real probe-join, flagged with a warning, and can be re-secured
in-place viaEZSHARE_ADMIN— the bridge changes the card's own Wi-Fi
password through its admin form and rejoins automatically. - 📊 Bridge status in the portal — pairing state, card Wi-Fi,
therapy-active and transfer-allowed indicators, last sync, and sync
progress straight from the bridge. - 📥 Session augmentation — late-recovered stream files now merge
into already-committed sessions instead of being discarded as
duplicates; coverage is verified against the manifest and annotations
are regenerated byte-identically.
🐛 Fixes
- 🗂️ Duplicate recording folders — oximetry day labels are now a pure
function of the recording's civil digits (ESP-local noon rule) and an
existingrecording_idunder any day folder wins — DST/offset changes
can no longer file one recording under two days. - 🔁 Uploader fingerprint ping-pong — duplicate
recording_ids across
day folders are deduplicated in reconcile (newest day wins) so two
files can never take turns invalidating each other's uploads. - 🛑 Retry storms — any unit that fails 5 uploads is parked and
retried at most once a day, reviving automatically when its content
changes. A local anomaly can never again become hours of HTTP 429s. - 📱 Logs tab scroll — narrow screens no longer open the log view at
the oldest line of a page-long scrollback.
🏗️ Under the hood
sb_protorequest/response framing with reassembly; BLE ops run off
the NimBLE host task so provisioning never starves indications.- Transfer ledger coordination keeps already-downloaded card files from
re-pulling — zero redundant transfers across reconnects. edf_importevents rendering extracted behind a byte-identical
emit-to-file / emit-to-set abstraction for coverage checks.- AS11 BLE: connection handling improvements alongside the bridge path.
✅ Testing
- New
sb_ble_testhost suite: 153 checks covering pairing state
machine, framing, reassembly and op dispatch. somno_ml_workersurvivor tests: guards, deadlines, SST checks,
fragment grouping.- Full host suite: 31 targets, 0 failures, sanitizer-clean.
📚 Docs
- Windows 10/11 SMB share setup guide with screenshots (contributed).