github ilyakruchinin/SomnoTrace v2.0.5

7 hours ago

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, MaskOff was 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 using PeriodStart and PeriodEnd to generate mask event pairs. However, PeriodStart and PeriodEnd define 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_ms to PeriodStart and PeriodEnd. 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:
    1. 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 -1 sentinels. OSCAR now correctly identifies the day as having zero therapy.
    2. Dropped + clock_drift_ms from PeriodStart/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.
    3. 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 and spec/archive/as11-summary-spool-and-mask-events.md §2).

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_t block of internal SRAM were permanently lost to the heap.
  • The Root Cause: FreeRTOS tasks created via xTaskCreateStaticPinnedToCore are flagged as statically allocated (tskSTATICALLY_ALLOCATED_STACK_AND_TCB). When they exit via vTaskDelete(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. In therapy_alert.c, nothing ever freed them. Additionally, calling heap_caps_free() immediately upon seeing a task enter eDeleted can cause memory corruption because the FreeRTOS idle task has not yet unlinked the task.
  • The Fix:
    1. 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 consecutive eDeleted observations before freeing. This eliminates all dynamic tracking overhead and keeps the component cleanly decoupled from main/.
    2. Fixed memory leaks on the task-creation failure path when stack or TCB allocation fails.
    3. Added an automatic memory reclamation subsystem with 2-sweep idle task confirmation in main/psram_task.c for self-deleting system tasks.

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.c previously tested a hand-copied duplicate of the event notification parser rather than the real parser in main/session_writer.c. Any changes or edge-case bugs in production event handling could slip through undetected.
  • The Fix:
    1. Extracted the event classifier and as11_event_t taxonomy out of session_writer.c into a clean, standalone module: main/as11_events.{c,h}.
    2. Wired the real production parser directly into scripts/as11_events_test.c and CI runners.
    3. Added Test 9 to cover malformed JSON payloads (non-array "events" containers, non-string event names, and TherapyStart payloads with out_report == NULL), pinning guards that prevent NULL pointer dereferences and misclassifications discovered via mutation analysis.

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.defaults combinations), 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.

Don't miss a new SomnoTrace release

NewReleases is sending notifications on new releases.