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.yamlandautomations.yamlexamples are polling-based: replacebinary_sensor.fp2_asleepin asleep/wake automations with your faster entity. Phase-specific examples still follow SleepRadar's poll interval.
Live readings and empty-bed behavior, illustrated using the previous card layout:
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 |
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_idfrom the Aqara Home app.
Tested with Home Assistant 2026.6. Older versions may still label this area as Add-ons instead of Apps.
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:
-
In Home Assistant, open Settings > Apps > Install app.
-
Open the three-dot menu (top right), choose Repositories, then click Add (bottom right). This opens an Add repository box.
-
Paste this repository URL in the box, then click Add:
https://github.com/florianhorner/ha-fp2-sleep -
Install SleepRadar.
-
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"
-
Start the app.
-
Open the log. A working setup shows:
Aqara login OK Published discovery for 5 sensors -
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.
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 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.
-
Download
card/sleepradar-card.jsfrom this repository. -
Copy it to
/config/www/sleepradar-card.js. -
Open your dashboard resources (Settings > Dashboards, three-dot menu, then Resources).
-
Add a resource:
- URL:
/local/sleepradar-card.js - Resource type: JavaScript module
- URL:
-
Hard-refresh your browser (
Ctrl+Shift+R/Cmd+Shift+R). -
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_idOr 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_entityWhen 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_entityIf you changed poll_interval, set the same value on the card so the stale
notice stays accurate:
type: custom:sleepradar-card
poll_interval_seconds: 120Optionally gate the live card with an independent bed-occupancy entity:
type: custom:sleepradar-card
bed_occupancy:
entity: binary_sensor.bed_occupiedbed_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 useentities:to override if needed.
The examples/ folder contains optional YAML:
examples/sleep_tracking.yamlcombines the raw sleep code with an independentbinary_sensor.bed_occupied. It gives codes3–5indicative labels and keeps codes0–2from asserting occupancy or wakefulness. The dashboard's optional cross-check card uses it.examples/recorder.yamlkeeps sleep sensors in Recorder. It has no dependencies.examples/dashboard-sleep.yamlis a Lovelace sleep dashboard. Register the SleepRadar Card first; the historical charts also require ApexCharts Card. Its optional cross-check card requiressleep_tracking.yamland Mushroom Cards. Its live card has an enabled, fail-closedbed_occupancygate usingbinary_sensor.bed_occupied; expose an independently sourced entity under that name before importing.examples/automations.yamlcontains example automations. ReplacePLACEHOLDER_*values with your own light, vacuum, and thermostat entity IDs, and load the occupancy-gated helpers fromsleep_tracking.yaml.
All files are pre-wired to the default mqtt_node_id. If you changed it,
update sensor.aqara_fp2_sleep_* references to match.
Because these are normal Home Assistant entities, you can automate on raw
sensor-reported data. A few starting points (see examples/automations.yaml):
- 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.
A preview of what's coming, not a current feature:
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.
| 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.
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.
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.
- Mosquitto broker app is installed and running.
- SleepRadar log shows
Published discovery for 5 sensors. - MQTT discovery is enabled in Home Assistant.
mqtt_node_idisaqara_fp2_sleepunless 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 — retryingfor 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.
- The FP2 is in Sleep Monitor mode.
subject_idpoints to the sleep FP2, not another Aqara device.- The Aqara Home app still shows current sleep data for that FP2.
sleepradar-card.jsis 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.
For normal problems, open the bug report form. For vulnerabilities, read the security policy and report them privately.
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.
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.



