Lightweight C++ microservice for ResMed CPAP data collection with Home Assistant integration.
Automatically extracts sleep therapy data from your ResMed AirSense 10/11 CPAP machine, parses EDF files using OSCAR algorithms, and publishes 47+ metrics to Home Assistant via MQTT discovery. Supports three data sources: ez Share WiFi SD (HTTP polling), FYSETC SD WiFi Pro (MQTT push with realtime therapy data), or local filesystem.
- 3 Data Sources - ezShare WiFi SD (polling), FYSETC SD WiFi Pro (MQTT push), or local files
- 47+ Metrics - AHI, leak rate, pressure, usage hours, events, STR daily summary, LLM AI summary
- Home Assistant Auto-Discovery - Instant MQTT integration with 47 sensor entities
- PostgreSQL Storage - Historical data for ML analysis and long-term trends
- Session Grouping - Intelligently combines BRP/PLD/SAD/EVE/CSL files into therapy sessions
- Realtime Therapy Data - FYSETC mode streams BRP data every 60s during therapy
- LLM Session Summary - Optional AI-generated therapy analysis via Ollama
- Ultra-Lightweight - 6.5 MB native binary
- 155 Unit Tests - Comprehensive coverage across all services
- Quick Start
- Data Sources
- Configuration
- Deployment
- Home Assistant Integration
- Architecture
- Development
- FAQ
- Contributing
# 1. Clone and build
git clone https://github.com/hms-homelab/hms-cpap.git
cd hms-cpap
mkdir build && cd build
cmake .. && make -j$(nproc)
# 2. Configure
cp ../.env.example ../.env
nano ../.env # Set MQTT, DB, and source settings
# 3. Run (choose your data source)
CPAP_SOURCE=ezshare ./hms_cpap # ezShare WiFi SD polling
CPAP_SOURCE=fysetc ./hms_cpap # FYSETC SD WiFi Pro MQTT push
CPAP_SOURCE=local ./hms_cpap # Local filesystemHow it works: HTTP polling every 65s via WiFi bridge on Raspberry Pi.
Hardware: ezShare WiFi SD card + Raspberry Pi with dual WiFi (wlan0=home, wlan1=ezShare AP).
Pros: Simple, no hardware mods. Cons: 2-4 min session detection latency, requires dedicated Pi WiFi interface.
How it works: ESP32-based PCB sits between CPAP and SD card on the SPI bus. Passively monitors CS line via hardware pulse counter, performs surgical bus steals (<10ms) during therapy, pushes EDF data via MQTT.
Hardware: FYSETC SD WiFi Pro board installed inside CPAP machine.
Pros: 65s end-to-end latency, realtime BRP streaming during therapy, zero network disruption, BRP-validated therapy detection. Cons: Requires hardware installation inside CPAP.
Firmware: hms-cpap-fysetc -- ESP-IDF project, builds with idf.py build.
See docs/FYSETC_RECEIVER.md for the full protocol, MQTT topics, and FSM state diagram.
How it works: Reads EDF files from a local directory (e.g. mounted SD card or NAS).
Use case: Offline analysis, backfill, or reparse of archived data.
All configuration via environment variables (12-factor app). See .env.example for complete reference.
# ez Share WiFi SD card base URL
EZSHARE_BASE_URL=http://192.168.4.1
# MQTT broker (required for Home Assistant)
MQTT_BROKER=localhost
MQTT_PORT=1883
MQTT_USER=mqtt_user
MQTT_PASSWORD=your_mqtt_password# Device identification
CPAP_DEVICE_ID=resmed_airsense10
CPAP_DEVICE_NAME="ResMed AirSense 10"
# Collection interval (seconds)
BURST_INTERVAL=120 # Default: 2 minutes
# PostgreSQL (for historical storage)
DB_HOST=localhost
DB_NAME=cpap_data
DB_USER=cpap_user
DB_PASSWORD=your_db_password
# Health check port
HEALTH_CHECK_PORT=8893# Build
mkdir build && cd build && cmake .. && make -j$(nproc)
# Install
sudo cp hms_cpap /usr/local/bin/
sudo cp ../.env /etc/hms-cpap/.env # Edit with your settings
# Service file: /etc/systemd/system/hms-cpap.service[Unit]
Description=HMS-CPAP Data Collection Service
After=network.target postgresql.service emqx.service
[Service]
Type=simple
EnvironmentFile=/etc/hms-cpap/.env
ExecStart=/usr/local/bin/hms_cpap
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.targetsudo systemctl daemon-reload
sudo systemctl enable hms-cpap
sudo systemctl start hms-cpap./build_arm.sh # Cross-compile for ARM
./deploy_to_pi.sh # Build + deploy + restart in one stepSee docs/CROSS_COMPILATION.md for sysroot setup.
Docker image available for CI and containerized deployments:
docker run -d --env-file .env -p 8893:8893 ghcr.io/hms-homelab/hms-cpap:latestHMS-CPAP uses MQTT Discovery for automatic Home Assistant integration.
configuration.yaml:
mqtt:
broker: localhost # Your MQTT broker
username: mqtt_user
password: your_mqtt_password
discovery: true # Enable discoverySensors will auto-appear as a device:
Device: ResMed AirSense 10
Sensors (34 total):
sensor.cpap_ahi- Apnea-Hypopnea Indexsensor.cpap_leak_rate- Leak rate (L/min)sensor.cpap_pressure_current- Current pressure (cmH2O)sensor.cpap_usage_hours- Total usage hourssensor.cpap_session_start- Last session start timesensor.cpap_central_apneas- Central apnea eventssensor.cpap_obstructive_apneas- Obstructive apnea events- ... and 27 more metrics
Example Lovelace card:
type: entities
title: CPAP Therapy
entities:
- entity: sensor.cpap_ahi
name: AHI (events/hour)
- entity: sensor.cpap_leak_rate
name: Leak Rate
- entity: sensor.cpap_usage_hours
name: Usage Tonight
- entity: sensor.cpap_pressure_current
name: Current Pressureπ Full Guide: See docs/HOME_ASSISTANT.md for dashboard examples and automations
βββββββββββββββββββ
β ResMed CPAP β
β AirSense 10 β
ββββββββββ¬βββββββββ
β SD Card (SPI bus)
β
ββββββ΄βββββββββββββββββ
β β
βΌ βΌ
ββββββββββββ ββββββββββββββββββββ
β ezShare β β FYSETC SD WiFi β
β WiFi SD β β Pro (ESP32) β
β β β - CS line PCNT β
β HTTP β β - <10ms steals β
β polling β β - MQTT push β
ββββββ¬ββββββ ββββββββββ¬ββββββββββ
β HTTP β MQTT
βΌ βΌ
ββββββββββββββββββββββββββββββββββββ
β HMS-CPAP Service β
β BurstCollector | FysetcReceiver β
β EDFParser + SessionDiscovery β
β DataPublisher + LLM Summary β
ββββββββββββ¬βββββββββββ¬βββββββββββββ
β β
ββββββββ ββββββββ
βΌ βΌ
ββββββββββββ ββββββββββββββββ
βPostgreSQLβ β MQTT (EMQX) β
β(history) β β 47 sensors β
ββββββββββββ ββββββββ¬ββββββββ
β
βΌ
βββββββββββββββββ
βHome Assistant β
βββββββββββββββββ
ezShare mode: Poll HTTP every 65s -> download EDF deltas -> detect session stop (files unchanged across 2 cycles) -> parse -> publish
FYSETC mode: Receive BRP chunks in realtime during therapy -> post-therapy: manifest + STR from FYSETC -> diff & fetch missing files -> parse -> publish + LLM summary
| File | Content | During Therapy | After Mask-Off |
|---|---|---|---|
| BRP.edf | Flow/pressure (25 Hz) | Grows every 60s | Final flush |
| PLD.edf | Pressure/leak (0.5 Hz) | Grows every 60s | Final flush |
| SAD.edf | SpO2/HR (1 Hz) | Grows every 60s | Final flush |
| EVE.edf | Apnea/hypopnea events | Updated live | Final flush |
| CSL.edf | Clinical summary | Created at start | Final flush |
| STR.edf | Daily therapy summary | N/A | Written ~50s after mask-off |
Multiple checkpoint files per session are grouped by SESSION_GAP_MINUTES (default 60).
- C++17 compiler (GCC 9+, Clang 10+)
- CMake 3.16+
- libcurl, libpq, libssl
Native x86 Build:
mkdir build && cd build
cmake -DCMAKE_BUILD_TYPE=Release ..
make -j$(nproc)
# Run tests
make test
# Run with verbose output
./hms_cpapARM Cross-Compilation (for Pi Zero 2 W):
# One-time setup: Install ARM toolchain
sudo apt-get install -y g++-arm-linux-gnueabihf sshpass
# Sync Pi libraries to sysroot (one-time, ~220MB)
mkdir -p sysroot/usr sysroot/lib/arm-linux-gnueabihf
rsync -avz aamat@192.168.2.73:/usr/include ./sysroot/usr/
rsync -avz aamat@192.168.2.73:/usr/lib ./sysroot/usr/
rsync -avz aamat@192.168.2.73:/lib/arm-linux-gnueabihf ./sysroot/lib/
# Build for ARM (40 seconds vs 5 minutes on Pi!)
./build_arm.sh
# Deploy to Pi and restart service
./deploy_to_pi.shπ Full documentation: See docs/CROSS_COMPILATION.md for detailed setup, troubleshooting, and architecture details.
hms-cpap/
βββ src/
β βββ main.cpp # Entry point (ezshare/fysetc/local modes)
β βββ clients/
β β βββ EzShareClient.cpp # WiFi SD HTTP client
β βββ parsers/
β β βββ EDFParser.cpp # EDF file parsing (OSCAR algorithms)
β βββ services/
β β βββ BurstCollectorService.cpp # ezShare/local polling mode
β β βββ FysetcReceiverService.cpp # FYSETC MQTT push mode
β β βββ SessionDiscoveryService.cpp # Session grouping
β β βββ DataPublisherService.cpp # MQTT + DB publishing
β βββ database/
β β βββ DatabaseService.cpp # PostgreSQL interface
β βββ mqtt/
β βββ DiscoveryPublisher.cpp # HA MQTT auto-discovery
βββ include/ # Header files (mirrors src/)
βββ tests/ # 155 unit tests (GTest + GMock)
βββ docs/ # FYSETC_RECEIVER, RESMED_WRITE_TIMING, etc.
βββ cmake/ # ARM cross-compilation toolchain
βββ scripts/ # Utility scripts
βββ CMakeLists.txt
cd build && make -j$(nproc)
./tests/run_tests155 tests across 14 test suites:
- EDF parsing (BRP, EVE, SAD, STR)
- Session discovery and grouping
- MQTT publishing and discovery
- Database operations
- FysetcReceiver (sync, chunk, manifest, base64)
- Configuration, models, integration
Most solutions require cloud services, proprietary apps, or manual SD card removal. HMS-CPAP provides:
- 100% local, no cloud
- Automatic collection via WiFi
- Open-source algorithms (OSCAR)
- Home Assistant integration
- ML-ready PostgreSQL storage
Currently optimized for ResMed AirSense 10/11 (EDF format). Other ResMed models may work. Philips/Respironics use different formats and would need parser modifications.
All data stays local:
- No cloud services
- No external API calls
- Your network only
- PostgreSQL encryption optional
- MQTT can use TLS
Yes! HMS-CPAP uses OSCAR algorithms for parsing. You can:
- Import HMS-CPAP archives into OSCAR
- Run both simultaneously
- Cross-validate metrics
Yes, if you value:
- Stable WiFi (no network switching)
- Reliable collection (always connected)
- WAF (Wife Acceptance Factor - no disruptions)
Direct connection works fine if:
- Single user, dedicated device
- Manual collection acceptable
- Don't mind WiFi switching
| Metric | Description | Unit |
|---|---|---|
| AHI | Apnea-Hypopnea Index | events/hour |
| Leak Rate | Current leak rate | L/min |
| Pressure | Current therapy pressure | cmH2O |
| Tidal Volume | Breath volume | mL |
| Minute Ventilation | Respiratory rate Γ tidal volume | L/min |
| Respiratory Rate | Breaths per minute | breaths/min |
| Metric | Description |
|---|---|
| Session Duration | Total therapy time |
| Usage Hours | Hours with mask on |
| Total Apneas | Central + Obstructive + Hypopneas |
| 95th Percentile Leak | Peak leak rate |
| Median Pressure | Middle pressure value |
| Efficacy | Overall therapy effectiveness |
π Complete List: See docs/METRICS.md
Contributions welcome! Please:
- Fork repository
- Create feature branch (
git checkout -b feature/amazing-feature) - Add tests for new functionality
- Ensure tests pass (
make test) - Commit changes (
git commit -m 'Add amazing feature') - Push to branch (
git push origin feature/amazing-feature) - Open Pull Request
- C++17 standard
- Google C++ Style Guide
- Include unit tests
- Document public APIs
This project is licensed under the MIT License - see LICENSE file.
- OSCAR algorithms - GPL-3.0 (EDF parsing logic)
- libcurl - MIT-style license
- PostgreSQL libpq - PostgreSQL License
- Paho MQTT - EPL 2.0
- OSCAR Project - EDF parsing algorithms
- ResMed - CPAP hardware
- ez Share - WiFi SD card
- Home Assistant - Smart home platform
- CPAP community on Reddit
- Issues: GitHub Issues
- Discussions: GitHub Discussions
- Reddit: r/CPAP, r/homeassistant
Made with β€οΈ for better sleep and open health data
If this project is useful to you, consider buying me a coffee!