SomnoTrace v2.0.5
Highlights & Changes
1. STR.edf & OSCAR: Fixed 24-Hour Phantom Sessions on Idle Days & Clock Drift Clamping (Discussion #266)
- What was happening: On days when the CPAP machine was not used (e.g. traveling without CPAP, off-therapy nights, or daytime rest without the mask), OSCAR displayed a bizarre, full-day session lasting approximately 24 hours (
MaskOn = 12:00 PM,MaskOff ≈ 11:56 AM). This corrupted OSCAR's compliance overview and monthly averages by falsely counting idle days as "valid therapy days" with 24 hours of mask time. Furthermore, on days where the fallback fired,MaskOffwas shifted by the device's clock drift (e.g. 1436 or 1431 minutes instead of 1440). - The Root Cause: When no therapy occurs, the AS11 summary spool file contains no individual session entries (
SessionModeEntries, field 6). The firmware's summary generator fell back to usingPeriodStartandPeriodEndto generate mask event pairs. However,PeriodStartandPeriodEnddefine the machine's nominal 24-hour reporting boundary (noon to noon), not sensor activity. Emitting this window unconditionally on idle days manufactured a synthetic 24-hour session. In addition, the code added+ clock_drift_mstoPeriodStartandPeriodEnd. Because these timestamps are fixed calendar anchors already aligned to local noon on the AS11's clock face, applying device RTC drift erroneously double-corrected the nominal window. - The Fix:
- Gated the period fallback strictly behind
(DurationMin > 0 || SessionCount > 0). If both counters are zero (an idle day), the firmware emits zero mask events with standard-1sentinels. OSCAR now correctly identifies the day as having zero therapy. - Dropped
+ clock_drift_msfromPeriodStart/PeriodEnd, ensuring that if a rare therapy day is missing field-6 entries, the emitted fallback window spans the clean, invariant[0, 1440]nominal range without clock drift distortion. - Formalized the 3 distinct clock domains (NTP wall clock, AS11 RTC drift, and nominal calendar buckets) across active system specifications (
spec/0002-edf-export.md§4.3.5 andspec/archive/as11-summary-spool-and-mask-events.md§2).
- Gated the period fallback strictly behind
2. Therapy Alert & System Tasks: Fixed 16 KB PSRAM and Internal SRAM Leaks (PR #271)
- What was happening: Every time a therapy alert escalation routine ran (e.g. buzzer ramping or push notification on therapy stop), 16 KB of PSRAM and one
StaticTask_tblock of internal SRAM were permanently lost to the heap. - The Root Cause: FreeRTOS tasks created via
xTaskCreateStaticPinnedToCoreare flagged as statically allocated (tskSTATICALLY_ALLOCATED_STACK_AND_TCB). When they exit viavTaskDelete(NULL), FreeRTOS's internal task deletion logic takes the branch that frees neither the stack nor the TCB, assuming they are managed by the caller. Intherapy_alert.c, nothing ever freed them. Additionally, callingheap_caps_free()immediately upon seeing a task entereDeletedcan cause memory corruption because the FreeRTOS idle task has not yet unlinked the task. - The Fix:
- Implemented a dedicated 4-slot static reaper in
components/therapy_alert/therapy_alert.c(PR #271) that sweeps on periodic 30-second monitor ticks and before new routine allocations, requiring two consecutiveeDeletedobservations before freeing. This eliminates all dynamic tracking overhead and keeps the component cleanly decoupled frommain/. - Fixed memory leaks on the task-creation failure path when stack or TCB allocation fails.
- Added an automatic memory reclamation subsystem with 2-sweep idle task confirmation in
main/psram_task.cfor self-deleting system tasks.
- Implemented a dedicated 4-slot static reaper in
3. Battery Telemetry: Decoupled Therapy Session State from Display Controller
- What was happening: During active therapy, battery level updates and state-of-charge (SoC) measurements could freeze or stop updating on the display when the screen dimmed or entered sleep mode.
- The Root Cause: Battery monitoring logic and state updates were previously coupled to the active display state machine. When the display transitioned to low-power or dimmed states during overnight sleep, battery updates were inadvertently throttled or skipped.
- The Fix: Fully decoupled therapy session tracking from the UI display mode. Battery monitoring and power telemetry now run continuously and independently throughout active therapy sessions regardless of screen state.
4. Wi-Fi & Timekeeping: Pre-Connection SNTP Initialization & Static Buffer Safety
- What was happening: Under certain network conditions or when configured with custom NTP server addresses, time synchronization could intermittently fail to start or encounter memory lifecycle issues with temporary configuration strings.
- The Fix: Initialized the SNTP time synchronization subsystem prior to Wi-Fi association, and moved custom NTP server hostname storage to a dedicated static buffer, guaranteeing reliable wall-clock acquisition on boot.
5. AS11 Event Classification: Linked Production Parser & Added Malformed Payload Guards (PR #272)
- What was happening: The host unit test
scripts/as11_events_test.cpreviously tested a hand-copied duplicate of the event notification parser rather than the real parser inmain/session_writer.c. Any changes or edge-case bugs in production event handling could slip through undetected. - The Fix:
- Extracted the event classifier and
as11_event_ttaxonomy out ofsession_writer.cinto a clean, standalone module:main/as11_events.{c,h}. - Wired the real production parser directly into
scripts/as11_events_test.cand CI runners. - Added Test 9 to cover malformed JSON payloads (non-array
"events"containers, non-string event names, andTherapyStartpayloads without_report == NULL), pinning guards that prevent NULL pointer dereferences and misclassifications discovered via mutation analysis.
- Extracted the event classifier and
6. CI & Test Suite Hardening
- ASan & UBSan Integration: Host unit tests now build and run with AddressSanitizer and UndefinedBehaviorSanitizer enabled (
scripts/run_host_tests.sh), catching memory errors and undefined behavior instantly. - Multi-Board Build Matrix (PR #251): CI now automatically discovers and lints every board configuration (
sdkconfig.defaultscombinations), ensuring secondary board variants cannot silently regress. - Waveform & Advertising Guards (PRs #264, #265): Added comprehensive test coverage for malformed BLE advertising data salvage, reconnection schedules, and 18 previously unasserted event-file guards in
main/edf_waveform.c. - Documentation: Updated the README and setup guides with instructions for the 1-click web installer.
Contributors & Acknowledgements
Special thanks to our community members and contributors who made this release possible:
- @ronmis — For reporting Discussion #266 regarding the phantom 24-hour sessions on idle days, and providing the complete SD card backup archives and OSCAR comparative diagnostics that allowed us to pinpoint the dual-timebase summary spool behavior.
- Michal Planicka (@Plantucha) — For contributing PR #271 (therapy alert PSRAM and TCB reclamation), PR #272 (decoupling and testing the real AS11 event parser), PRs #251/#254/#256/#264/#265 (multi-board build matrix, advertising salvage tests, and waveform guards), as well as foundational work on ASan/UBSan host test integration and mutation coverage.
- Ilya Kruchinin (@ilyakruchinin) — For architecture, deep-dive root-cause analysis of summary spool clock domains, the idle-day compliance gating fix, display/battery state decoupling, SNTP pre-initialization, and overall firmware integration.