Skip to content

Add Health Connect sleep stages sensors - #7356

Open
KruseLuds wants to merge 22 commits into
home-assistant:mainfrom
KruseLuds:health-connect-sleep-stages
Open

KruseLuds wants to merge 22 commits into
home-assistant:mainfrom
KruseLuds:health-connect-sleep-stages

Conversation

@KruseLuds

@KruseLuds KruseLuds commented Aug 22, 2026 •

Copy link
Copy Markdown

Summary

Adds richer Health Connect sleep session data to the Android Companion App by exposing the individual standardized sleep stages already available through SleepSessionRecord.

The existing Health Connect sleep duration sensor remains unchanged for backward compatibility. This change adds sensors for sleep start/end times and the duration of each standardized Health Connect sleep stage, including Light, Deep, REM, Awake, Awake in bed, Out of bed, Sleeping/unspecified, and Unknown.

The latest SleepSessionRecord is read once per sensor update and its stages are analyzed once, avoiding duplicate Health Connect queries when multiple sleep sensors are enabled.

The existing Sleep Duration sensor also preserves the ordered sleep-stage timeline as structured attributes containing each stage's type, start time, and end time, allowing Home Assistant consumers to use the original Health Connect stage data for dashboards and analysis.

The implementation is provider-neutral. It does not contain Samsung-, Fitbit-, or device-specific logic and does not attempt to merge separate SleepSessionRecord entries.

Missing stage data is kept unavailable/unknown rather than being converted to zero, because a provider not supplying a stage is semantically different from reporting zero duration.

Related feature request:
home-assistant/feature-requests#4080

Checklist

  • New or updated tests have been added to cover the changes following the testing guidelines.
  • The code follows the project's code style and best_practices.
  • The changes have been thoroughly tested, and edge cases have been considered.
  • Changes are backward compatible whenever feasible. Any breaking changes are documented in the changelog for users and/or in the code for developers depending on the relevance.
  • I have read the Open Home Foundation AI Policy.

Select exactly one option that describes AI usage in this contribution:

  • I have not used AI for this contribution.
  • AI assistance was used for this contribution.
  • AI fully generated the code for this contribution, but I've reviewed and understood it before submitting and will respond without AI during review.

Link to pull request in documentation repositories

User Documentation: home-assistant/companion.home-assistant#1407

Any other notes

Targeted HealthConnectSensorManagerTest: 6/6 tests passed.

:app:lintFullDebug: passed.

ktlintCheck :build-logic:convention:ktlintCheck --continue: passed.

The full :app:testFullDebugUnitTest run completed 1,530 tests with two unrelated failures in WearOnboardingNavigationTest and ServerDiscoveryNavigationTest. Both failures were reproduced independently when those test classes were run in isolation.

Copilot AI lite review requested due to automatic review settings August 22, 2026 05:22

@home-assistant home-assistant Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @KruseLuds

It seems you haven't yet signed a CLA. Please do so here.

Once you do that we will be able to review and accept this pull request.

Thanks!

@home-assistant
home-assistant Bot marked this pull request as draft August 22, 2026 05:22
@home-assistant

Copy link
Copy Markdown

Please take a look at the requested changes, and use the Ready for review button when you are done, thanks 👍

Learn more about our pull request process.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds richer Health Connect sleep session reporting by reusing a single SleepSessionRecord read per update cycle and deriving additional sensors for sleep start/end and per-stage durations, while keeping the existing sleep duration sensor for backward compatibility.

Changes:

  • Added new Health Connect sensors for sleep start/end timestamps and standardized sleep-stage durations.
  • Centralized sleep record processing via updateSleepSensors() + analyzeSleepStages() to avoid duplicate Health Connect queries.
  • Added unit tests validating per-stage duration aggregation and timeline preservation.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

File Description
common/src/main/res/values/strings.xml Adds new sensor names/descriptions for sleep start/end and stage durations.
app/src/main/kotlin/io/homeassistant/companion/android/sensors/HealthConnectSensorManager.kt Implements new sleep sensors and shared sleep-stage analysis + attribute building.
app/src/test/kotlin/io/homeassistant/companion/android/sensors/HealthConnectSensorManagerTest.kt Adds tests for sleep-stage analysis aggregation and timeline preservation.
app/src/main/res/xml/changelog_master.xml Documents the new Health Connect sleep sensors in the 2026.8.4 changelog.
Suppressed comments (2)

app/src/main/kotlin/io/homeassistant/companion/android/sensors/HealthConnectSensorManager.kt:1135

  • stages is passed as a List<Map<String, Any>>, but the attribute serialization in SensorManager.onSensorUpdated treats non-primitive lists as liststring and stores map.toString() values. That means the sleep-stage timeline won’t actually arrive as structured data (type/start/end fields), which conflicts with the PR description and likely makes the attribute hard to consume reliably.
        "deviceManufacturer" to sleepRecord.metadata.device?.manufacturer,
        "deviceModel" to sleepRecord.metadata.device?.model,
        "stages" to analysis.timeline,
    )

app/src/main/kotlin/io/homeassistant/companion/android/sensors/HealthConnectSensorManager.kt:1133

  • buildSleepSessionAttributes includes title, notes, and record/client IDs plus device manufacturer/model. Unlike other Health Connect sensors in this file (which only expose time + source), this expands the data sent to Home Assistant and may unintentionally expose sensitive/free-text data (especially notes). Consider limiting attributes to what’s needed for consumers (e.g., start/end, source, and stage timeline) or making the extra metadata opt-in.
        "title" to sleepRecord.title,
        "notes" to sleepRecord.notes,
        "sources" to sleepRecord.metadata.dataOrigin.packageName,
        "recordId" to sleepRecord.metadata.id,
        "lastModifiedTime" to sleepRecord.metadata.lastModifiedTime,
        "clientRecordId" to sleepRecord.metadata.clientRecordId,
        "clientRecordVersion" to sleepRecord.metadata.clientRecordVersion,
        "recordingMethod" to sleepRecord.metadata.recordingMethod,
        "deviceType" to sleepRecord.metadata.device?.type,
        "deviceManufacturer" to sleepRecord.metadata.device?.manufacturer,
        "deviceModel" to sleepRecord.metadata.device?.model,

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread common/src/main/res/values/strings.xml

@home-assistant home-assistant Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @KruseLuds

It seems you haven't yet signed a CLA. Please do so here.

Once you do that we will be able to review and accept this pull request.

Thanks!

@KruseLuds

Copy link
Copy Markdown
Author

Hi @KruseLuds

It seems you haven't yet signed a CLA. Please do so here.

Once you do that we will be able to review and accept this pull request.

Thanks!

Done

@KruseLuds

Copy link
Copy Markdown
Author

Sleep-stage timeline serialization Fixed. The nested list of maps has been replaced with parallel primitive lists for stage type, stage type ID, start time, and end time so the Companion App attribute serializer preserves the timeline structure.

Unnecessary/sensitive sleep metadata fixed - Removed the additional title, notes, record/client IDs, device metadata, and other unnecessary sleep-session metadata. The attributes are now limited to the session times, source, and sleep-stage timeline data needed for this feature.

@KruseLuds
KruseLuds marked this pull request as ready for review August 22, 2026 06:09
@home-assistant
home-assistant Bot dismissed stale reviews from themself August 22, 2026 06:09

Stale

@KruseLuds

Copy link
Copy Markdown
Author

All review feedback has been addressed and the latest changes are pushed. The targeted Health Connect tests and ktlint passed locally, and all GitHub CI checks are now green. Ready for maintainer review when someone has availability. Thanks!

@TimoPtr TimoPtr left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a lot of new sensors, it almost feels like it could be one with multiple states no?

Also I would like @jpelgrom opinion on this.

Comment thread app/src/main/res/xml/changelog_master.xml Outdated
@TimoPtr
TimoPtr marked this pull request as draft September 14, 2026 13:33
@jpelgrom

Copy link
Copy Markdown
Member

That's a lot of new sensors, it almost feels like it could be one with multiple states no?

Also I would like @jpelgrom opinion on this.

Do you mean with attributes? The core team really discourages the use of attributes. Looking at one of the best practices health integratios in core, Withings, it also exposes a lot of individual sensor for sleep stages etc not too dissimilar from what is proposed here: https://github.com/home-assistant/core/blob/83ca3988d515ddde893626a068d9f64d3e53a44a/homeassistant/components/withings/sensor.py#L329. As such, I think this is better than trying to send sleep data in attributes or events.

@KruseLuds

Copy link
Copy Markdown
Author

Hi @jpelgrom,

Thanks for the clarification and the link to the documentation! I see that the five screenshot failures are all related to the updated changelog entry. I noticed the documentation also mentions a GitHub Actions workflow that maintainers can use to regenerate the reference screenshots. Would it be possible to use that workflow for this PR?

I'd prefer to avoid setting up and running the full Android build environment locally just to regenerate the screenshots, as doing so would bring my Raspberry Pi to its knees. It's already running Home Assistant, and I don't have the resources to run such a demanding build alongside it, particularly since the compilation, lint, unit, and instrumentation tests are now passing.

I'm sorry for any additional inconvenience this may cause, and I really appreciate your help and patience.

If the maintainer workflow isn't an option for external PRs, please let me know what you'd recommend.

Thanks again!

@jpelgrom

jpelgrom commented Oct 8, 2026

Copy link
Copy Markdown
Member

The workflow can only be triggered for branches in this repository, not forks, sorry.

We could update the screenshot test for you and push to your branch if that's the only remaining item for you. The branch is quite far behind though so please merge upstream first.

Does it also mean that you haven't built the app to test your changes on a device or emulator?

@KruseLuds

KruseLuds commented Oct 9, 2026 •

Copy link
Copy Markdown
Author

Hi @jpelgrom,

Thank you again for offering to help with the screenshots. I really appreciate it!

I've now merged the latest upstream main into my branch and pushed the changes.

I also tested the modified app on my actual Samsung phone, I downloaded the debug APK generated by GitHub Actions and connected it to a separate Home Assistant test instance running on my Windows PC.

I'm happy to report that the Health Connect sleep-stage sensors are working! Light, Deep, REM, Awake, total sleep duration, and sleep start/end timestamps all populated successfully in Home Assistant. The measurements also closely matched the values shown in Samsung Health. I've attached a screenshot showing the actual sensor values in Home Assistant:

image

The physical-device testing was performed with the APK generated before today's upstream merge, so the updated branch still needs its CI checks to complete.

The latest workflow is now awaiting approval. If everything else looks good after the checks run, I'd be very grateful if you could help update the changelog reference screenshots as you offered.

Thanks again for your time, patience, and help with this contribution!

@jpelgrom
jpelgrom marked this pull request as ready for review October 9, 2026 19:14
@jpelgrom jpelgrom changed the title Health connect sleep stages Add Health Connect sleep stages sensors Oct 12, 2026
),
ChangelogEntry(
contentRes = commonR.string.changelog_entry_health_connect_sleep_stages,
platforms = setOf(ChangelogPlatform.APP, ChangelogPlatform.AUTOMOTIVE),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it make sense to have it for Automotive? You'll need to regenerate the changelog screenshots for this, it was not clear on your previous message you want us to generate it?

Comment thread common/src/main/res/values/strings.xml
@home-assistant
home-assistant Bot marked this pull request as draft October 12, 2026 13:53
@KruseLuds

KruseLuds commented Oct 12, 2026 •

Copy link
Copy Markdown
Author

Hi @TimoPtr,

Thank you for the detailed review. I've pushed the requested changes in commits 23c62be7d and 67e3cde93.

The updates include:

  • Restricting the changelog entry to the app platform (I accidentally included automotive while juggling a few different projects. Sorry about that!).
  • Correcting the sleep-stage attribute ordering.
  • Reusing the existing sleep-duration sensor update helper.
  • Explicitly handling known sleep-stage types and failing on unsupported types.
  • Adding a test against SleepSessionRecord.VALID_TYPES.
  • Removing the redundant duration calculation wrapper.
  • Expanding the REM description.
  • Adding the changelog action to open the sleep-duration sensor settings.

The changes are ready for another look. The required checks are still pending, with a workflow awaiting maintainer approval.

Oh - and with regards to your comment above, "...it was not clear on your previous message you want us to generate it?" - with regards to the screenshot tests that will still fail when it reaches that point - yes please!

Thanks again!

@KruseLuds
KruseLuds requested a review from TimoPtr October 12, 2026 17:26
SleepSessionRecord.STAGE_TYPE_AWAKE_IN_BED,
)

assertEquals(expectedTypes, SleepSessionRecord.VALID_TYPES.toSet())

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a failure message that the usage needs to be checked because a type has been added. (removed would fail the build and I don't think it would happen).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was this addressed? There is a comment but not a failure message. It also only fails if something is removed...

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding this comment because I don't see easily how we could make this happen btw, with it being constants and not a sealed class and the list of all possible values restricted to the library.

@KruseLuds

Copy link
Copy Markdown
Author

Hi @TimoPtr,

Thank you for the detailed review and helpful feedback.

I've pushed the requested changes, including the ktlint formatting corrections, handling unknown sleep stage types without crashing, and a clearer failure message in the sleep-stage coverage test.

I believe I've addressed all outstanding review comments. When you have a chance, I'd appreciate another look.

Thanks again!

@KruseLuds
KruseLuds requested a review from TimoPtr October 12, 2026 22:04
@TimoPtr
TimoPtr marked this pull request as ready for review October 13, 2026 07:01

@TimoPtr TimoPtr left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've updated the screenshots, I'll let @jpelgrom give the final go.

val sleepRecordDuration = calculateSleepDurationInMinutes(lastSleepRecord.stages)
onSensorUpdated(
val analysis = analyzeSleepStages(lastSleepRecord.stages)
val sessionAttributes = buildSleepSessionAttributes(lastSleepRecord, analysis)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is only used for sleepDuration, so only calculate them if that sensor is enabled

"endTime" to sleepRecord.endTime,
"sources" to sleepRecord.metadata.dataOrigin.packageName,
"stageTypes" to analysis.stageTypes,
"stageTypeIds" to analysis.stageTypeIds,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the point of including these IDs? They aren't documented anywhere and duplicate the human readable stages above.

Suggested change
"stageTypeIds" to analysis.stageTypeIds,

<string name="changelog_entry_assistant_volume_sensor">Added an &lt;b&gt;assistant volume&lt;/b&gt; sensor (Android 17+)</string>
<string name="changelog_entry_assistant_volume_command">Added a notification command to set the &lt;b&gt;assistant volume&lt;/b&gt; (Android 17+)</string>
<string name="changelog_entry_health_connect">&lt;b&gt;Health Connect&lt;/b&gt; sleep duration now ignores awake and out-of-bed time</string>
<string name="changelog_entry_health_connect_sleep_stages">Added &lt;b&gt;Health Connect&lt;/b&gt; sleep start/end and individual sleep-stage sensors</string>

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would emphasize sleep, not just Health Connect

Suggested change
<string name="changelog_entry_health_connect_sleep_stages">Added &lt;b&gt;Health Connect&lt;/b&gt; sleep start/end and individual sleep-stage sensors</string>
<string name="changelog_entry_health_connect_sleep_stages">Added more &lt;b&gt;Health Connect sleep sensors&lt;/b&gt; for start/end time and individual sleep stages</string>

@jpelgrom

Copy link
Copy Markdown
Member

Potential future conflict with #7619 (we'll monitor this, as well as the changelog screenshot update)

@KruseLuds

KruseLuds commented Oct 14, 2026 •

Copy link
Copy Markdown
Author

I've addressed the review feedback, updated the changelog wording, and merged the latest upstream main to resolve the conflict.

The changelog reference screenshots still need to be regenerated to reflect the updated Health Connect wording and the upstream file-picker entry.

According to the Android screenshot-testing documentation, the CI screenshot-update workflow is restricted to maintainers and isn't available to external contributors using forks...

@joostlek Would you or another maintainer (thank you @TimoPtr for your heklp with this before the wording changed!) be able to help regenerate the screenshots for this PR?

Thank you!

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants