Skip to content

About

SleepRadar: contact-free Aqara FP2 sleep vitals (heart rate, breathing, stages) for Home Assistant

Resources

Contributing

Security policy

Stars

2 stars

Watchers

0 watching

Forks

Repository files navigation

SleepRadar logo

SleepRadar

CI

Contact-free sleep vitals (heart rate, breathing, and sleep stages) from an Aqara FP2 in Home Assistant. No wearable, no Docker bridge, no developer account.

Terminology. "App" means the SleepRadar Home Assistant app. "Aqara Home app" means the Aqara mobile app. They are different things.

SleepRadar is a Home Assistant app for people who use an Aqara FP2 in Sleep Monitor mode and want the sleep data that the local HomeKit or Matter integration does not expose. It polls the Aqara Home app API and publishes five MQTT entities you can see, automate, and build on.

Unofficial API. SleepRadar uses Aqara's private, undocumented app API. Aqara can change or restrict it without notice. If that happens, the sensors stop updating. History already recorded in Home Assistant stays yours.

Fast triggers. SleepRadar polls Aqara for vitals. If Matter or an Aqara push/RocketMQ integration gives you a faster FP2 sleep-state entity, use that for sleep/wake automations. Keep SleepRadar for heart rate, breathing, body movement, illuminance, and the bundled card. The bundled sleep_tracking.yaml and automations.yaml examples are polling-based: replace binary_sensor.fp2_asleep in asleep/wake automations with your faster entity. Phase-specific examples still follow SleepRadar's poll interval.

What It Looks Like

Live readings and empty-bed behavior, illustrated using the previous card layout:

Animated SleepRadar card showing current stage, heart rate, and breathing

The bundled SleepRadar Card (card/sleepradar-card.js) reads three of the five sensors (sleep stage, heart rate, breathing), and needs no other cards or plugins. See The SleepRadar Card.

Heart rate and breathing are sensor-reported measurements. Here, "measured" identifies the kind of signal; it does not claim independent validation or clinical accuracy. Sleep stages are the device's best estimate, shown honestly as such. The point is not a prettier chart — it is that this data was hidden from Home Assistant entirely, and now it is five entities you can see, automate, and build on.

SleepRadar creates five MQTT sensors in Home Assistant:

Sensor What it shows Kind
sensor.aqara_fp2_sleep_heart_rate Heart rate in bpm Sensor-reported measurement
sensor.aqara_fp2_sleep_respiration_rate Respiration rate in breaths/min Sensor-reported measurement
sensor.aqara_fp2_sleep_sleep_state Sleep stage (raw Aqara code) Estimated
sensor.aqara_fp2_sleep_body_movement Body movement value Sensor-reported measurement
sensor.aqara_fp2_sleep_illuminance Illuminance in lux Sensor-reported measurement

Before You Start

You need:

  • Home Assistant OS or Home Assistant Supervised.
  • The Mosquitto broker app, or another MQTT broker accessible to apps.
  • MQTT discovery enabled in Home Assistant.
  • An Aqara FP2 in Sleep Monitor mode.
  • Your Aqara Home app account credentials. This is the mobile app login, not the Aqara webshop account — they are separate.
  • The FP2 subject_id from the Aqara Home app.

Tested with Home Assistant 2026.6. Older versions may still label this area as Add-ons instead of Apps.

Install

Add repository to my Home Assistant

If the button opens My Home Assistant and asks for your Home Assistant URL, enter it there once, or skip the button and use the manual repository URL below. The manual path works the same way.

Or add it by hand:

  1. In Home Assistant, open Settings > Apps > Install app.

  2. Open the three-dot menu (top right), choose Repositories, then click Add (bottom right). This opens an Add repository box.

  3. Paste this repository URL in the box, then click Add:

    https://github.com/florianhorner/ha-fp2-sleep
    
  4. Install SleepRadar.

  5. Open the Configuration tab and fill in your details:

    # Required
    aqara_username: "your-aqara-home-app-email"
    aqara_password: "your-aqara-home-app-password"
    subject_id: "lumi1.xxxxxxxxxxxx"
    # CN | EU | USA | RU | KR: the region your Aqara Home app uses (where your
    # FP2 is registered), not where you live. A wrong region can refuse the
    # sign-in or sign in without access to your FP2 (see Login Fails).
    aqara_area: "EU"
    
    # Optional
    poll_interval: 60
    log_level: info
    device_name: "Aqara FP2 Sleep Monitor"
    mqtt_node_id: "aqara_fp2_sleep"
  6. Start the app.

  7. Open the log. A working setup shows:

    Aqara login OK
    Published discovery for 5 sensors
    
  8. Check for the five sensor.aqara_fp2_sleep_* sensors in your entity states (Developer Tools, then States).

First useful moment: sensor.aqara_fp2_sleep_sleep_state changes when the FP2 reports a bed or sleep state.

Find Your subject_id

In the Aqara Home app, open the sleep FP2, then open Device Information. It looks like lumi1.xxxxxxxxxxxx. Treat it as private because it identifies your device.

When asking for help, redact it:

lumi1.xxxxxxxxxxxx

Do not paste a full app options dump into an issue.

The SleepRadar Card

The compact readout puts the current phase above two sensor-reported vitals. Fresh Deep, Light and REM stages use a filled circle, hollow circle and diamond alongside their names, so phase identification does not rely on color. The card follows Home Assistant's light/dark theme; stale or uncertain phases stay neutral. The stage estimate remains labeled as the sensor's best guess.

  1. Download card/sleepradar-card.js from this repository.

  2. Copy it to /config/www/sleepradar-card.js.

  3. Open your dashboard resources (Settings > Dashboards, three-dot menu, then Resources).

  4. Add a resource:

    • URL: /local/sleepradar-card.js
    • Resource type: JavaScript module
  5. Hard-refresh your browser (Ctrl+Shift+R / Cmd+Shift+R).

  6. Add a card manually:

    type: custom:sleepradar-card

The card defaults to the app's default entities. Override if you changed mqtt_node_id:

type: custom:sleepradar-card
mqtt_node_id: your_custom_node_id

Or override individual entities:

type: custom:sleepradar-card
entities:
  sleep_state: sensor.your_sleep_state_entity
  heart_rate: sensor.your_heart_rate_entity
  respiration_rate: sensor.your_respiration_rate_entity

When the app reports a connection problem, the card shows that reason instead of the generic "no data yet" message. It reads binary_sensor.<mqtt_node_id>_connection_problem by default; override it the same way if Home Assistant pinned a different id:

type: custom:sleepradar-card
entities:
  connection_problem: binary_sensor.your_connection_problem_entity

If you changed poll_interval, set the same value on the card so the stale notice stays accurate:

type: custom:sleepradar-card
poll_interval_seconds: 120

Optionally gate the live card with an independent bed-occupancy entity:

type: custom:sleepradar-card
bed_occupancy:
  entity: binary_sensor.bed_occupied

bed_occupancy must be a mapping, and its entity is required. For a binary_sensor.*, you can omit occupied_states; it defaults to ["on"]. For every other entity, occupied_states must be a non-empty list of its exact occupied state values. Quote on/off — unquoted, YAML reads them as booleans and the card rejects them. The list may not contain unknown, unavailable, none, or an empty string (those always mean uncertain occupancy), and it may not contain both on and off, which would leave no state meaning the bed is empty. The card rejects an invalid gate configuration instead of silently ignoring it. It also rejects direct reuse of the configured sleep state, heart-rate, or respiration entity as the occupancy source, including via the entities: override block. Home Assistant template aliases cannot be detected here, so the configured entity must still be independently sourced.

When the gate is closed, the card still reports the health of the sleep-state feed: a missing or unavailable sleep-state entity shows a no sensor data badge and a stale one shows stale, so a dead app does not look like an ordinary empty bed.

With the gate enabled, confirmed occupancy is authoritative. Codes 3–5 show the mapped stage and fresh sensor-reported vitals. Code 0 shows In bed, the existing not measuring badge, and dashes; codes 1 and 2 show In bed — stage unknown and may show fresh sensor-reported vitals. A concrete non-occupied state overrides every Aqara code with Out of bed, not measuring, and dashes. A missing entity, a non-string state, or an unknown, unavailable, none, or empty value renders Occupancy unknown and hides the vitals. Any other state that is simply not listed in occupied_states — including device-specific ones like offline or calibrating — is treated as not occupied and renders Out of bed; the card does not claim the sensor reported an empty bed, only that it is not reporting an occupied one. Either way this is fail-closed: occupancy that is not positively confirmed never exposes retained values as live.

The gate trusts Home Assistant's current state and availability. It has no occupancy-age timeout because stable binary sensors may legitimately remain unchanged for a long time; it cannot detect a source that is silently stale while still available. Omit bed_occupancy to preserve the legacy Aqara-only labels: code 0 is Out of bed, while codes 1 and 2 are Awake.

The card shows "no data yet" if the sleep state sensor is missing, "not measuring" when the bed is empty, and a stale notice if readings are older than three poll intervals. In those states it hides retained heart-rate and breathing values rather than showing stale in-bed numbers as live.

The occupancy gate changes only what the card displays. It does not change the five MQTT entities, polling, Recorder data, or historical charts.

Custom Home Assistant themes can override the phase text and marker colors with sleepradar-deep-color, sleepradar-light-color, and sleepradar-rem-color theme variables. Check those colors against your card background: labels still carry the meaning, but custom colors must remain readable. If Home Assistant provides no theme-mode metadata, phase text uses the inherited primary text color.

For a local preview with synthetic data, open proof/compact-card-preview.html from a checkout. It loads the actual card script and includes phase, fault, theme, 320px-width and enlarged-text controls; it does not connect to Home Assistant.

Entity ID note. Home Assistant pins an entity ID on first creation and does not rename it later. If you installed before v1.1.0, your entity IDs may not match the sensor.aqara_fp2_sleep_* defaults. Check your entity states and use entities: to override if needed.

Optional Templates and Dashboard

The examples/ folder contains optional YAML:

  • examples/sleep_tracking.yaml combines the raw sleep code with an independent binary_sensor.bed_occupied. It gives codes 3–5 indicative labels and keeps codes 0–2 from asserting occupancy or wakefulness. The dashboard's optional cross-check card uses it.
  • examples/recorder.yaml keeps sleep sensors in Recorder. It has no dependencies.
  • examples/dashboard-sleep.yaml is a Lovelace sleep dashboard. Register the SleepRadar Card first; the historical charts also require ApexCharts Card. Its optional cross-check card requires sleep_tracking.yaml and Mushroom Cards. Its live card has an enabled, fail-closed bed_occupancy gate using binary_sensor.bed_occupied; expose an independently sourced entity under that name before importing.
  • examples/automations.yaml contains example automations. Replace PLACEHOLDER_* values with your own light, vacuum, and thermostat entity IDs, and load the occupancy-gated helpers from sleep_tracking.yaml.

Optional SleepRadar dashboard showing previous-night sleep state, heart rate, and breathing histories

All files are pre-wired to the default mqtt_node_id. If you changed it, update sensor.aqara_fp2_sleep_* references to match.

What You Can Build

Because these are normal Home Assistant entities, you can automate on raw sensor-reported data. A few starting points (see examples/automations.yaml):

Animated Home Assistant automation capping light brightness while the FP2 reports sleep

  • Don't run lights at full brightness while someone is asleep.
  • Hold off the robot vacuum until the room is empty or awake.
  • Nudge the thermostat down during deep sleep.

What's Next

A preview of what's coming, not a current feature:

Planned dashboard preview. Heart rate, breathing rate and indicative sleep stages. Synthetic illustration.

Today, examples/dashboard-sleep.yaml gives you a live "Now" card plus a "Last night" view that charts sleep stage, heart rate, and breathing over the previous night as raw MQTT/Recorder history. The optional live occupancy gate does not rewrite or filter that history. The example does not compute session duration, averaged vitals, or the segmented stage timeline shown above. That sessionization is the next planned SleepRadar Card release.

Sleep State Codes

Code Legacy label without a gate With confirmed occupancy
0 Out of bed In bed; not measuring
1 Awake In bed — stage unknown
2 Awake In bed — stage unknown
3 REM REM
4 Light sleep Light sleep
5 Deep sleep Deep sleep

This mapping is community-derived and unverified. At the 2026-07-31 truth check, Aqara's public resource documentation explained how resource values and metadata are queried and directed detailed resource lists to its developer console, but did not publish the FP2 sleep_state enumeration. A community mapping reviewed by this project labels code 1 as In Bed, not Awake. Its labels for codes 3/4/5 match the table above. Treat 0, 1, and 2 as "SleepRadar cannot determine occupancy or wakefulness from this code alone," and read the raw code from the attribute if you need to build your own logic.

The optional template (examples/sleep_tracking.yaml) implements the gate-aware labels and keeps the raw code available as an attribute.

Heart rate and breathing are sensor-reported measurements, but the FP2 can retain or report values when it incorrectly considers an empty bed occupied. The optional bed_occupancy gate keeps those values out of the live card; the raw entities and their history remain unchanged.

Sleep stage scoring is the device's best estimate, and it can be confidently wrong. A synthetic regression fixture uses authored test values, not recorded sensor data: independent occupancy stays empty while codes 3, 4, and 5 accompany changing and repeated vital values. The card tests require every sample to hide stages and vitals. The source uses force_update, so Recorder can store repeated events even when a sensor-reported value does not change. Plausible-looking FP2 values alone do not establish true occupancy.

So treat stages as indicative, and use bed_occupancy with an independent occupancy signal (a bed sensor, a pressure mat, a separate presence zone) as the authority for whether anyone is in bed. Do not derive occupancy from sleep_state itself.

Troubleshooting

Login Fails

The app still shows as started while it cannot get your data from Aqara. The process is alive and retrying; only the sensors go unavailable. The "started" badge is not proof that data is flowing. Read the binary_sensor.aqara_fp2_sleep_connection_problem entity or the app log.

Every problem message says what happened, then what to do, then the technical details. The ones you are most likely to see:

"Aqara didn't accept SleepRadar's sign-in." Check aqara_username and aqara_password: use the Aqara Home app account (mobile app email), not the Aqara webshop account. A wrong aqara_area can also cause this, so set it to the region your Aqara Home app uses. Then restart the app.

"SleepRadar signed in to Aqara but can't read your FP2: Aqara's … server won't give it access to this sensor." The sign-in worked, but the selected region's server does not give SleepRadar access to the sensor. This happens when aqara_area is not the region your FP2 is registered in, or when subject_id points at another device. Fix the one that is wrong and restart the app. If both are already right, report it in the issue tracker. Technical details: Aqara code 755.

"SleepRadar signed in to Aqara but can't read your FP2." Check that subject_id points at your sleep FP2 and that the FP2 is still in your Aqara Home account, then restart the app.

"SleepRadar can't get your data from Aqara: Aqara's … server rejected SleepRadar's request." This error does not mean your password or region is wrong: Aqara rejected the request SleepRadar sent. Update SleepRadar. If you already have the latest version, report it in the issue tracker. Technical details: Aqara code 106, "Invalid sign".

"Aqara ended SleepRadar's session right after it signed in." This happens when another app or a second SleepRadar signs in with the same Aqara account. Make sure only one SleepRadar (or other Aqara cloud tool) uses the account, then restart the app. Technical details: Aqara code 108.

"SleepRadar can't get an answer from Aqara right now." Usually a short network problem or a busy Aqara server, and it clears on its own. If it does not, check that Home Assistant is online and that nothing on your network, such as a DNS filter, blocks Aqara's servers.

"Aqara answered, but SleepRadar couldn't read the reply." Usually temporary. If it keeps happening, report it in the issue tracker.

The app log carries the same message at error level, together with Aqara's own detail text. SleepRadar keeps retrying with a growing delay between attempts, so a wrong password is not hammered against Aqara's login endpoint. The sensors stay unavailable until SleepRadar can read your FP2 again. Once the cause is gone it recovers on its own; after changing the options, restart the app.

If required fields are blank, SleepRadar logs the missing field and waits about 30 seconds before exiting. Fill in the options and start it again.

Know When It Stops

SleepRadar publishes a diagnostic entity, binary_sensor.aqara_fp2_sleep_connection_problem (or binary_sensor.<mqtt_node_id>_connection_problem if you changed mqtt_node_id). It turns on when data stops, and its cause attribute carries the reason in plain language. It does not depend on the five vitals sensors, so it stays readable while they are unavailable, and its state survives a Home Assistant restart.

The app also raises a Home Assistant notification naming the cause and the fix, updates it if the cause changes, and dismisses it once the connection recovers.

Two timing details:

  • If the app is updated from a version without Home Assistant API access, approve that access once in the Configuration tab. Until then the diagnostic entity still works and the notification is skipped; the log says so.
  • If the app crashes rather than failing to sign in, the diagnostic entity flips immediately but the five sensors stay on their last value for up to three poll intervals (about three minutes at the default) before Home Assistant expires them. The card shows the reported cause during that window instead of calling the feed stale.

Neither reaches your phone on its own. examples/automations.yaml has a ready-made automation that forwards the cause to a notify service, with a five-minute delay so a brief network blip does not wake you.

These outages happen overnight, and there is no backfill: a night SleepRadar did not record stays missing.

Sensors Do Not Appear

  • Mosquitto broker app is installed and running.
  • SleepRadar log shows Published discovery for 5 sensors.
  • MQTT discovery is enabled in Home Assistant.
  • mqtt_node_id is aqara_fp2_sleep unless you changed it.

MQTT startup race. If SleepRadar and the broker start at the same time (for example, after a reboot), the log may show MQTT connect failed — retrying for up to about 2 minutes. This is expected. If it still fails after that, restart SleepRadar once the broker app shows as running. If you enable Home Assistant's Watchdog setting yourself, startup failure paths still slow-exit first so a permanent misconfiguration does not turn into a rapid restart loop.

Values Stay Unknown

  • The FP2 is in Sleep Monitor mode.
  • subject_id points to the sleep FP2, not another Aqara device.
  • The Aqara Home app still shows current sleep data for that FP2.

The Card Shows "No Data Yet"

  • sleepradar-card.js is registered as a resource (see The SleepRadar Card) and the browser was hard-refreshed after adding it.
  • The five sensors exist in your entity states.
  • If you changed mqtt_node_id, the card config reflects it.
  • Check actual entity IDs. See the Entity ID note in The SleepRadar Card.

Support

For normal problems, open the bug report form. For vulnerabilities, read the security policy and report them privately.

Contributing

See Contributing for local setup and the pre-PR checks. The repository validation reference explains gate ownership, CI and Conductor boundaries, and how to change a check safely.

Security and Privacy

Your Aqara credentials are stored in Home Assistant app options only. They are not stored in any YAML file in this repository.

The appid, appkey, and RSA public key in the source are public constants from the Aqara Home app, not user credentials.

SleepRadar re-authenticates when the session token expires but cannot guarantee the private API keeps working indefinitely.

Follow the security policy before sharing logs.

About

SleepRadar: contact-free Aqara FP2 sleep vitals (heart rate, breathing, stages) for Home Assistant

Resources

Contributing

Security policy

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages