v0.5.6
Pre-release
Pre-release
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.