Releases: ilyakruchinin/SomnoTrace
Release list
v0.6.3
Changes since v0.6.2
OTA Firmware Update
- Download firmware from URL — device fetches and flashes directly from GitHub or any HTTPS URL, no computer needed
- "I'm Feeling Adventurous" button — auto-finds and installs the latest release with one click
- Real-time progress bar — shows download and flash progress during update
- Fixed OTA download failures — TLS buffer too small for GitHub's CDN caused silent handshake failures; now uses full-size TLS buffers with on-demand allocation to keep RAM usage low
- Concurrent uploads supported — OTA download works even while SleepHQ upload is running
BLE Reconnect
- Auto-reconnects when AS11 is turned on later — if the CPAP is off at boot, the device keeps retrying every 60s instead of giving up and requiring a reboot
- Cleaner disconnect handling — retry loop exits immediately when pairing is cleared or user disconnects
Faster Dashboard Graph Loading
- Halved breathing-graph download size — flow and pressure data now stored in separate files; the dashboard only fetches flow (pressure comes from the summary data), cutting transfer size in half
- Backwards compatible — old sessions still load; new sessions use the split format automatically
NTP Failure Recovery
- BOOT button escape on Wi-Fi failure — if NTP sync fails, the alarm sounds but you can now hold BOOT to enter Wi-Fi setup instead of waiting for a forced reboot
Stream Quality Diagnostics
- BLE stream health metrics — session list now shows notification count, gap events, and missing-sample compensation for each session
v0.6.2
Changes since v0.6.1
STR.edf bi-level mode superset expansion + bug fixes
Commit: f5dce49 — Expanded STR.edf signal count from 78 to 134 signals
File changed: main/edf_gen.c (+354 / −113)
Bug fixes
- Fixed missing settings summary for users with 30+ days of history. The STR.edf generator had a hard 30-record cap (
calloc(30, ...)+n_records < 30guard) that prevented the current day's synthesized STR record from being added when 30 historical spool files already existed. Since the AS11 doesn't finalize the current day's spool until noon rollover, the current day was never found in the parsed spools, and the synthesis branch was blocked by then_records < 30guard being false. OSCAR would then fall back to copying stale settings and flagnoSettings, hiding the settings summary for that day. Fixed by allocating 31 record slots and changing the guard ton_records < 31. This works for any number of spool files on disk (60, 180, 600+) because the spool trim always reduces parsed records to ≤30 — the extra slot is solely for the synthesized current-day record. - Fixed
AutoSetForHerProfileMOP mapping from index 11 → 2 (APAP/Her), which previously caused settings to not appear at all in OSCAR for AutoSet For Her users.
New features
- Added bi-level therapy mode signals (VAuto, Spont, ST, Timed, ASV, ASVAuto) to build_str_data_values(), including start pressure, EPAP, IPAP, PS, PSMin/Max, TiMin/Max, backup rate, trigger/cycle sensitivity, EasyBreathe, and ERatio settings
- Added new summary stat signals: SpontTriggerPercentage, SpontCyclePercentage, TargetMinuteVentilation, IeRatio, InspiratoryDuration (Ti)
- Added
iVAPSProfile(9) andPACProfile(10) to profile_name_to_mop() - Added enum conversion helpers for trigger/cycle sensitivity, EasyBreathe mode, and RespiratoryRateEnable
Internal changes
- Shifted all existing signal indices (CPAP/AutoSet, comfort/settings, spool-derived stats) to accommodate the new bi-level signals inserted at indices [14–58]
- Updated
rec_bufsize from 115 to 171 int16s (133 data signals + Crc16, accounting for 2 signals with 20 samples-per-record) - Updated all hardcoded
115references in the record writing/CRC code to match the new buffer size
v0.6.1
Changes since v0.6.0
WiFi Configuration UX
- Configured SSIDs now visible on page load. The
/api/statusendpoint now returnsssids(array of SSID strings) andhas_pass(array of booleans) so the web UI can display all saved WiFi networks immediately — no scan required. Passwords are never sent to the frontend. - Pre-populated networks use text-input mode. Saved SSIDs appear as editable text fields instead of empty dropdowns, making them visible as soon as the settings page loads.
- Cache invalidation after save. save_post_handler now sets
s_status_cache.cfg_valid = falseafter saving WiFi config, so the next/api/statuscall returns the updated SSID list without waiting for the 2-minute cache TTL.
Display — New "Backlight always off" Mode
- Added
LCD_THERAPY_ALWAYS_OFFmode (battery-friendly). A third display mode option complements the existing "Show live graph" and "Backlight off":- Show live graph — backlight always on (unchanged)
- Backlight off — backlight off during therapy, on otherwise (unchanged)
- Backlight always off (battery) — backlight fully off in all states except during boot and SoftAP setup mode
- Centralized backlight policy. New bsp_display_apply_backlight_policy() function evaluates the current LCD mode and therapy state to set the correct backlight on/off. Called after boot completes, when entering SoftAP, and when the mode is changed via the web UI.
- SoftAP force-on override. A
s_backlight_force_onflag ensures the backlight stays on during SoftAP/portal mode regardless of the selected display mode, so users can always interact with the setup screen. - Immediate effect on save. Backlight policy is evaluated after the new mode is persisted to NVS (not before), so selecting "Backlight always off" turns the LCD off immediately without waiting for therapy to start.
- Therapy stop respects always-off. When therapy ends,
LCD_THERAPY_OFFrestores the backlight butLCD_THERAPY_ALWAYS_OFFkeeps it off.
v0.6.0
SomnoTrace v0.6.0 Release Notes
🚨 CRITICAL: FULL FLASH REQUIRED (0x0)
Due to updates in the partition table (addition of a dedicated 64 KBcoredumppartition), OTA update will NOT work for this release. You must perform a full flash over USB at offset0x0usingsomnotrace-v0.6.0-merged.bin.
🚀 What's New & Changed (since v0.5.7)
🛠️ Crash Diagnostics & Reliability
- On-Flash Core Dumps: Added a dedicated
coredumpflash partition. On firmware crashes (panic/watchdog), crash state and call stacks are preserved to flash. - On-Boot Diagnostics: Added automatic crash report processing on boot. Crash summaries (crashed task, exception PC, backtrace stack, ELF hash) are logged into the download log buffer to make troubleshooting seamless.
- Reset Reason Logging: Every boot now logs the precise hardware/software reset reason (
POWERON,SOFTWARE,PANIC,TASK_WDT,BROWNOUT, etc.).
📺 Display & Therapy Graph Fixes
- Decoupled Therapy Graph Activation: Fixed an issue where the live therapy flow graph failed to trigger on the LCD if SD card initialization was delayed or encountered an error. Live graph rendering now triggers unconditionally on
TherapyStart. - Enhanced Display Logging: Added comprehensive state transition and task status logging for display state management.
⚡ Flashing Instructions (Full Flash)
- Hold the "BOOT" (left) button and connect the device to USB-C (screen will remain black/off).
- Open a Web Flasher in Chrome/Edge:
- Select the merged binary (
somnotrace-v0.6.0-merged.bin) and flash to offset0x0.
⚙️ Initial Setup & Usage
- First Boot / Setup: The device will enter SoftAP mode on first boot. Connect to the Wi-Fi access point and navigate to
http://192.168.4.1to configure your Wi-Fi and timezone. - Main UI Access: Once Wi-Fi is configured, the device reboots into the main interface where you can access the full Web UI.
- Manual SoftAP Mode: Hold the "BOOT" (left) button for 5 seconds from the main UI to force SoftAP mode.
- Powering Off: Hold the "POWER" (middle) button for 10 seconds.
🔗 Pairing AirSense 11 (AS11)
- On your AS11 screen, navigate to More ➔ MyAir App ➔ OK, downloaded ➔ Connect.
- Perform the pairing procedure from the SomnoTrace Web UI.
- Note: If your mobile phone's MyAir app is currently connected via BLE, disconnect it first, or it will hold the connection and prevent pairing from succeeding.
ℹ️ Data Analysis & Upload Notes
- OSCAR Usage: We recommend using OSCAR with separate test profiles.
- SleepHQ Duplicate Protection: If you upload a session to SleepHQ via SomnoTrace and later attempt to upload the same therapy session files directly from the AS11 SD card, SleepHQ will likely return an error. This is expected behavior from SleepHQ (duplicate detection). Please choose to upload a given session either via SomnoTrace or via SD card import, not both. Different therapy sessions upload fine.
v0.5.7
Battery: adaptive 100% charge anchor and charger-transition filter reset
Adaptive full-charge anchor
- The OCV curve hardcoded 4200 mV as 100%, but real Li-ion cells terminate charging at ~4130 mV, so the display never reached 100% (peaked at ~94%).
- When CHG_STAT goes high (charge complete), the current filtered terminal voltage is recorded as the 100% anchor. The top segment of the OCV lookup table is stretched to use this anchor instead of 4200 mV.
- The anchor is persisted to NVS (
batnamespace, keyfull_mv) via the existingnvs_writertask (internal RAM stack), so it survives reboots. Loaded at boot before the monitor task starts. - Anchor only accepted in the 4000–4200 mV range to reject transient spikes and partial-charge false triggers. NVS write only occurs when the value actually changes.
IIR filter and percentage reset on charger transitions
- When charging stopped, the 120 mV IR-offset subtraction was removed, but the IIR filter only moved 25% per cycle — taking ~4 cycles to catch up. The slew-limited percentage then took additional minutes to walk to the new target.
- Both
filtered_mvandshown_pctare now reset to -1 on any charger state change (plug or unplug). The next sample adopts directly, eliminating the lag.
nvs_writer_init() in battery monitor start
- Added
nvs_writer_init()call in bsp_power_battery_monitor_start() to ensure the internal-stack NVS writer task exists in both STA and SoftAP modes, not just when the connected HTTP server starts.
v0.5.6
SomnoTrace — Changes since v0.5.5
Battery Monitoring (comprehensive rewrite)
Battery sampling
- ADC calibration: Uses ESP-IDF eFuse-backed curve-fitting calibration (
adc_cali_curve_fitting) instead of nominal scaling. Without eFuse data, falls back to nominal (reads a few percent low). - Trimmed-mean sampling: 256 samples spread over ~1 second (4 ms apart) with lowest/highest 12.5% discarded. Rejects Wi-Fi/BLE TX burst dips that corrupted back-to-back samples.
- OCV-based percentage: Li-ion discharge curve is non-linear (flat 3.9–3.6 V, cliff below). Replaced linear voltage-to-percent map with a piecewise-linear OCV lookup table (4200 mV = 100%, 3300 mV = 0%).
- Charging IR offset: Subtracts 120 mV from terminal voltage while charging to approximate true OCV.
- Slew limiting: Published percentage moves at most 1 point per update cycle (60 s discharge / 20 s charge) with monotonic-while-charging rule. Eliminates visible "jump" on plug-in.
- Unplug settle: 30-second hold after charger disconnect lets terminal voltage relax to OCV before trusting the reading.
No-battery detection
- Threshold: If VBAT > 4250 mV (above any real Li-ion cell max of ~4200 mV), the battery is considered absent. USB power with no cell installed pegs the ADC at ~5 V through the 200 kΩ / 100 kΩ divider.
- Behaviour: Publishes
valid = falseandpercent = -1, causing the LCD battery indicator to be hidden. Filter state is reset so a newly installed battery is detected within one sample cycle. - Stale state fix: bat_publish() now clears
valid/percent/millivoltswhenok = false, preventing stale readings from persisting after battery removal.
Battery API (bsp_power.h)
- New
bsp_battery_tstruct:percent,millivolts,charging,valid,age_s. - bsp_power_battery_monitor_start() — background monitor task ( sole ADC owner, samples on slow cadence, publishes snapshot).
- bsp_power_battery_get() — non-blocking snapshot read, safe from any task.
- bsp_power_battery_percent() — convenience wrapper.
- bsp_power_is_charging() — reads CHG_STAT (GPIO3) directly, cheap and always current.
Wi-Fi — Stale Connection Fix & Failover
Stale connection bug
- Root cause: ESP-IDF Wi-Fi event handler does not automatically clear the application-level "connected" flag on
WIFI_EVENT_STA_DISCONNECTED. The old code lefts_connected/s_connected_ipstale, so the LCD and/api/statuskept advertising a dead connection. - Fix: Added
link_mark_down()called on disconnect, which immediately clears the published IP and SSID. Consumers read live state vianetprov_get_link()and never cache "connected".
Autonomous failover
- Problem:
esp_wifi_connect()only retries the single SSID currently programmed into the driver. Without intervention, a permanently disappeared network strands the device even when another configured network is in range. - Fix: Event handler counts failed reconnects; after 5 failures it sets a rescan flag. A background
link_supervisor_taskperforms a full scan-and-rank across all configured networks and connects to the best available candidate. Credentials are cached at initial connect so the supervisor can rescan without NVS access.
Link state API (net_provision.h)
- New
netprov_link_tstruct:up,ssid,ip,rssi,rssi_valid. netprov_get_link()— non-blocking snapshot, safe from any task.netprov_is_link_up()— convenience boolean.
LCD status display
- Replaced hardcoded
"Wi-Fi Connected"text with the actual SSID fromnetprov_get_link()in all three status display paths (boot, periodic refresh with SD OK, periodic refresh with SD error).
EDF Export — Enum Mapping Fixes (edf_gen.c)
- AutoSetComfort (S.AS.Comfort): Removed invalid
"Plus"value (does not exist in AS11 JSON). FixedOffmapping from 0 → 1 (AS11 EDF convention: raw enum + 1). OSCAR decrements by 1 for AS11, so EDF value 1 → OSCAR 0 = "Standard", EDF value 2 → OSCAR 1 = "Soft". - Mask type (S.Mask): Fixed mapping to use raw + 2 convention.
Pillows= 2,Nasal= 4,Pediatric= 5 (was incorrectly 1/2/4). - Tube type (S.Tube): Fixed
15mmNonHeatedmapping from"3m"to correct string match, value 3. - Documentation: Updated inline comments to reference
spec/0002-edf-export.md §4.3.4for the enum convention.
OTA Firmware Update
Backend (net_provision.c)
- Endpoint:
POST /api/otaaccepts raw binary (application/octet-stream), max 4 MB. - Architecture: Two-task design to work around ESP32-S3 PSRAM cache freeze:
- httpd handler (PSRAM stack) — receives HTTP body in 4 KB chunks, pushes to an 8 KB FreeRTOS stream buffer.
ota_flash_task(internal RAM stack viaxTaskCreate) — reads from stream buffer, callsesp_ota_begin/esp_ota_write/esp_ota_end/esp_ota_set_boot_partition. Internal stack required becauseesp_ota_*functions freeze the SPI cache, which asserts the task stack is not in PSRAM.
- Flow: Validate size → create stream buffer → launch flash task → feed data from socket → wait for flash task completion → respond
{"ok":true}→ reboot after 1.5 s delay. - Error handling: Every stage has cleanup (
esp_ota_abort,vStreamBufferDelete). Stream buffer timeout (10 s) and flash task timeout (30 s) prevent hangs.
Build (CMakeLists.txt)
- Added
app_updateto REQUIRES foresp_ota_ops.h.
Frontend (portal.html)
- New "Firmware Update (OTA)" card in the Actions tab with file picker (
.binonly, 4 MB max). - Real-time progress bar with percentage and KB counter via
XMLHttpRequest.upload.onprogress. - Confirmation dialog before flashing. Inputs disabled during upload to prevent double-submit.
- Status messages: uploading → complete/rebooting, or error with details.
Documentation
- docs/hardware/README.md: Added battery monitoring section (divider, ADC calibration, TX burst rejection, charging IR offset, OCV curve, slew limiting, sampling cadence) and Wi-Fi section (RSSI selection, stale connection fix, failover, retry timing).
spec/0002-edf-export.md: Added §4.3.4 documenting AS11 EDF enum conventions (raw + 1 for most signals, raw + 2 for S.Mask) with OSCAR decrement behavior.
v0.5.5
Changes since v0.5.4
Bug fixes
-
MaskEvents undercount in STR.edf —
MaskEventswas set to the number of
on/off pairs instead of the total number of on+off events. OSCAR divides
MaskEventsby 2 to get the pair count, so the reported session count was
half of what it should have been.
(main/edf_gen.c) -
Repeating -120 L/min flow spikes in OSCAR/SleepHQ — BLE notification
drops (typically every ~10 minutes) triggered insertion ofSNT_MISSING
(INT16_MIN) sentinels into BRP flow/pressure data. During EDF conversion
these were clamped todig_min = -1000, which OSCAR scales bygain = 0.12
(L/s→L/min), producing visible-120 L/minspikes. Changed BRP gap
compensation to hold-previous-value (matching the existing SA2/PLD
strategy). Gaps are typically 200–400 ms (<10 % of a breath cycle), so the
held value is visually indistinguishable from real data. The AS11 native EDF
never has missing BRP data, and OSCAR has no sentinel convention for
breathing waveforms.
(main/session_writer.c, main/edf_gen.c)
v0.5.4
Changes since v0.5.3:
-
AS11 settings EDF mapping fixes (edf_gen.c):
- Added "Auto" → 3 to on_off_to_edf() for HeatedTubeSettingEnable
and other On/Off/Auto enum fields. Fixes Temperature and
Temperature Enable not displaying in OSCAR when set to "Auto". - Added "Off" → 0 for AutoSetComfort (S.AS.Comfort / Response).
- Added "Full" → 1 for PatientView (S.PtAccess), treating it as
a firmware rename of "Advanced".
- Added "Auto" → 3 to on_off_to_edf() for HeatedTubeSettingEnable
-
SMB upload timestamp preservation (uploader_smb.c):
- Added smb_set_mtime() — sends SMB2 SET_INFO with
FILE_BASIC_INFORMATION to set remote file last_write_time
and change_time to match the local file's mtime. - Called before smb2_close() so destination files retain
original modification timestamps instead of upload time.
- Added smb_set_mtime() — sends SMB2 SET_INFO with
-
FAT32 rename / HTTP header fixes (uploader_state.c, sdkconfig.defaults):
- Remove destination file before rename() to avoid FAT32 EEXIST
errors on the uploader state file. - Increase HTTPD_MAX_REQ_HDR_LEN from 2048 to 4096 to accommodate
Cloudflare Access proxy headers (cookies + JWT).
- Remove destination file before rename() to avoid FAT32 EEXIST
v0.5.3
Changes since v0.5.2
Battery Charge Indicator (LCD)
- Added battery percentage reading via ADC1 (GPIO1) with 200k/100k voltage divider compensation
- Added charger status detection via GPIO3 (active-low when charging)
- Battery icon with percentage text and yellow lightning bolt when charging, displayed on the LCD top row alongside Wi-Fi and AS11 indicators
- Calibrated voltage-to-percentage mapping (3.3V–3.93V → 0–100%)
- Periodic updates every 3 seconds in both normal and SoftAP modes
BLE RX Buffer Improvements
- Moved BLE notification RX buffers to PSRAM to free internal RAM
- Increased RX_BUF_MAX from 4KB to 16KB — fixes truncated settings summaries from large AS11 notifications
SD Card Format Support
- Added sd_storage_format() — unmounts, formats (f_mkfs), remounts, and recreates the directory tree
- Exposed via API for in-field card formatting without removing the card
Session Writer Robustness
- Therapy auto-start now handles SD card not ready gracefully — displays "SD Card Error" warning instead of silently failing
- bsp_display_set_therapy_active(true) only called when session actually starts
Web UI (portal.html)
- Dashboard progress bar: Fixed display:none bug preventing the loading progress bar from appearing; bar now stays visible as a green separator after load completes (no layout jump)
- Double-tap reset: Reduced touch double-tap zoom-reset threshold from 300ms to 200ms to prevent accidental triggers on mobile
HTTP Server Tuning
- max_open_sockets: 10 → 24 to handle parallel dashboard fetches
- recv_wait_timeout: 5s → 2s to proactively close idle connections before browser RSTs
- Enabled TCP keepalive (5s idle / 5s interval / 3 retries) to detect dead connections
- Reduces "error in recv : 104" (ECONNRESET) warnings from browser connection pooling
What needs to be tested
- Battery charging indicator and percentage display on LCD
- Boot without SD card — warning displayed
- Therapy start without SD card — "SD Card Error" warning shown
- Dashboard progress bar visibility during and after data load (no layout jump)
- BLE settings summary no longer truncated
- HTTP server stability under parallel dashboard fetches
v0.5.2
v0.5.2
Accuracy release: exported files and dashboard summaries now line up with what
the AirSense 11 writes itself.
Fixed
- Daily summary records were dated one day early. The noon-to-noon day for
each daily record was picked using clock-corrected time, which shifted every
record back a day and pushed its session start/stop times out of the valid
range. OSCAR treated those times as corrupt and discarded them, falling back
to less precise values — which is why daily usage and AHI could read a minute
(and a fraction of a point) below the SD card import. - Session end detection. Session data now ends on the machine's own data
gating signal, mirroring how the start is already detected. Previously the end
relied on an event that some sessions never emit (for example when SmartStop
triggers), in which case no trimming happened at all. - Start/end alignment precision. Session boundaries now use the
higher-precision timestamps available and round to the nearest sample instead
of always rounding down. This removes a sub-second offset that slightly moved
average leak rate and large-leak totals. - Aborted sessions are no longer exported. A session where the mask was
never detected and no therapy data was produced now produces no files at all,
matching the machine's behaviour. Previously it created empty data files
without their companion event files, adding a phantom session to the day. - Web dashboard now matches OSCAR/SleepHQ. Graphs and summary statistics are
limited to the machine's own therapy data window, so the pre-therapy pressure
ramp no longer inflates average leak rate and 95th-percentile figures.
Sessions with no therapy are excluded from the graphs and statistics.
Notes
- No changes to how raw session data is captured or stored, and no action needed
on upgrade. Machine settings, event annotations and waveform data were already
correct and are unchanged. - Existing exports can be regenerated to pick up the corrected daily records.