diff --git a/homeassistant/components/sleepiq/__init__.py b/homeassistant/components/sleepiq/__init__.py index 565611fe169..8eb703b7f5f 100644 --- a/homeassistant/components/sleepiq/__init__.py +++ b/homeassistant/components/sleepiq/__init__.py @@ -26,6 +26,7 @@ from .coordinator import ( SleepIQData, SleepIQDataUpdateCoordinator, SleepIQPauseUpdateCoordinator, + SleepIQSleepDataCoordinator, ) _LOGGER = logging.getLogger(__name__) @@ -96,14 +97,17 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: coordinator = SleepIQDataUpdateCoordinator(hass, entry, gateway) pause_coordinator = SleepIQPauseUpdateCoordinator(hass, entry, gateway) + sleep_data_coordinator = SleepIQSleepDataCoordinator(hass, entry, gateway) # Call the SleepIQ API to refresh data await coordinator.async_config_entry_first_refresh() await pause_coordinator.async_config_entry_first_refresh() + await sleep_data_coordinator.async_config_entry_first_refresh() hass.data.setdefault(DOMAIN, {})[entry.entry_id] = SleepIQData( data_coordinator=coordinator, pause_coordinator=pause_coordinator, + sleep_data_coordinator=sleep_data_coordinator, client=gateway, ) diff --git a/homeassistant/components/sleepiq/const.py b/homeassistant/components/sleepiq/const.py index 7a9415bac20..0efb8e94ebe 100644 --- a/homeassistant/components/sleepiq/const.py +++ b/homeassistant/components/sleepiq/const.py @@ -15,6 +15,11 @@ PRESSURE = "pressure" SLEEP_NUMBER = "sleep_number" FOOT_WARMING_TIMER = "foot_warming_timer" FOOT_WARMER = "foot_warmer" +SLEEP_SCORE = "sleep_score" +SLEEP_DURATION = "sleep_duration" +HEART_RATE = "heart_rate" +RESPIRATORY_RATE = "respiratory_rate" +HRV = "hrv" ENTITY_TYPES = { ACTUATOR: "Position", CORE_CLIMATE_TIMER: "Core Climate Timer", @@ -25,6 +30,11 @@ ENTITY_TYPES = { SLEEP_NUMBER: "SleepNumber", FOOT_WARMING_TIMER: "Foot Warming Timer", FOOT_WARMER: "Foot Warmer", + SLEEP_SCORE: "Sleep Score", + SLEEP_DURATION: "Sleep Duration", + HEART_RATE: "Heart Rate Average", + RESPIRATORY_RATE: "Respiratory Rate Average", + HRV: "Heart Rate Variability", } LEFT = "left" diff --git a/homeassistant/components/sleepiq/coordinator.py b/homeassistant/components/sleepiq/coordinator.py index 46b754976e5..0baeca03fe5 100644 --- a/homeassistant/components/sleepiq/coordinator.py +++ b/homeassistant/components/sleepiq/coordinator.py @@ -5,17 +5,18 @@ from dataclasses import dataclass from datetime import timedelta import logging -from asyncsleepiq import AsyncSleepIQ +from asyncsleepiq import AsyncSleepIQ, SleepIQAPIException, SleepIQTimeoutException from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_USERNAME from homeassistant.core import HomeAssistant -from homeassistant.helpers.update_coordinator import DataUpdateCoordinator +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed _LOGGER = logging.getLogger(__name__) UPDATE_INTERVAL = timedelta(seconds=60) LONGER_UPDATE_INTERVAL = timedelta(minutes=5) +SLEEP_DATA_UPDATE_INTERVAL = timedelta(hours=1) # Sleep data doesn't change frequently class SleepIQDataUpdateCoordinator(DataUpdateCoordinator[None]): @@ -74,10 +75,48 @@ class SleepIQPauseUpdateCoordinator(DataUpdateCoordinator[None]): ) +class SleepIQSleepDataCoordinator(DataUpdateCoordinator[None]): + """SleepIQ sleep health data coordinator.""" + + config_entry: ConfigEntry + + def __init__( + self, + hass: HomeAssistant, + config_entry: ConfigEntry, + client: AsyncSleepIQ, + ) -> None: + """Initialize coordinator.""" + super().__init__( + hass, + _LOGGER, + config_entry=config_entry, + name=f"{config_entry.data[CONF_USERNAME]}@SleepIQSleepData", + update_interval=SLEEP_DATA_UPDATE_INTERVAL, + ) + self.client = client + + async def _async_update_data(self) -> None: + """Fetch sleep health data from API via asyncsleepiq library.""" + try: + await asyncio.gather( + *[ + sleeper.fetch_sleep_data() + for bed in self.client.beds.values() + for sleeper in bed.sleepers + ] + ) + except SleepIQTimeoutException as err: + raise UpdateFailed(f"Timed out fetching SleepIQ sleep data: {err}") from err + except SleepIQAPIException as err: + raise UpdateFailed(f"Failed to fetch SleepIQ sleep data: {err}") from err + + @dataclass class SleepIQData: """Data for the sleepiq integration.""" data_coordinator: SleepIQDataUpdateCoordinator pause_coordinator: SleepIQPauseUpdateCoordinator + sleep_data_coordinator: SleepIQSleepDataCoordinator client: AsyncSleepIQ diff --git a/homeassistant/components/sleepiq/entity.py b/homeassistant/components/sleepiq/entity.py index 829e3a00e6f..49d58b7d5e1 100644 --- a/homeassistant/components/sleepiq/entity.py +++ b/homeassistant/components/sleepiq/entity.py @@ -11,9 +11,17 @@ from homeassistant.helpers.entity import Entity from homeassistant.helpers.update_coordinator import CoordinatorEntity from .const import ENTITY_TYPES, ICON_OCCUPIED -from .coordinator import SleepIQDataUpdateCoordinator, SleepIQPauseUpdateCoordinator +from .coordinator import ( + SleepIQDataUpdateCoordinator, + SleepIQPauseUpdateCoordinator, + SleepIQSleepDataCoordinator, +) -type _DataCoordinatorType = SleepIQDataUpdateCoordinator | SleepIQPauseUpdateCoordinator +type _DataCoordinatorType = ( + SleepIQDataUpdateCoordinator + | SleepIQPauseUpdateCoordinator + | SleepIQSleepDataCoordinator +) def device_from_bed(bed: SleepIQBed) -> DeviceInfo: diff --git a/homeassistant/components/sleepiq/icons.json b/homeassistant/components/sleepiq/icons.json new file mode 100644 index 00000000000..6a3534e3256 --- /dev/null +++ b/homeassistant/components/sleepiq/icons.json @@ -0,0 +1,21 @@ +{ + "entity": { + "sensor": { + "heart_rate_avg": { + "default": "mdi:heart-pulse" + }, + "hrv": { + "default": "mdi:heart-flash" + }, + "respiratory_rate_avg": { + "default": "mdi:lungs" + }, + "sleep_duration": { + "default": "mdi:sleep" + }, + "sleep_score": { + "default": "mdi:sleep" + } + } + } +} diff --git a/homeassistant/components/sleepiq/sensor.py b/homeassistant/components/sleepiq/sensor.py index 0b8f7fc5002..5d22897d97b 100644 --- a/homeassistant/components/sleepiq/sensor.py +++ b/homeassistant/components/sleepiq/sensor.py @@ -8,16 +8,31 @@ from dataclasses import dataclass from asyncsleepiq import SleepIQBed, SleepIQSleeper from homeassistant.components.sensor import ( + SensorDeviceClass, SensorEntity, SensorEntityDescription, SensorStateClass, ) from homeassistant.config_entries import ConfigEntry +from homeassistant.const import UnitOfTime from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback -from .const import DOMAIN, PRESSURE, SLEEP_NUMBER -from .coordinator import SleepIQData, SleepIQDataUpdateCoordinator +from .const import ( + DOMAIN, + HEART_RATE, + HRV, + PRESSURE, + RESPIRATORY_RATE, + SLEEP_DURATION, + SLEEP_NUMBER, + SLEEP_SCORE, +) +from .coordinator import ( + SleepIQData, + SleepIQDataUpdateCoordinator, + SleepIQSleepDataCoordinator, +) from .entity import SleepIQSleeperEntity @@ -28,7 +43,7 @@ class SleepIQSensorEntityDescription(SensorEntityDescription): value_fn: Callable[[SleepIQSleeper], float | int | None] -SENSORS: tuple[SleepIQSensorEntityDescription, ...] = ( +BED_SENSORS: tuple[SleepIQSensorEntityDescription, ...] = ( SleepIQSensorEntityDescription( key=PRESSURE, translation_key="pressure", @@ -43,6 +58,57 @@ SENSORS: tuple[SleepIQSensorEntityDescription, ...] = ( ), ) +SLEEP_HEALTH_SENSORS: tuple[SleepIQSensorEntityDescription, ...] = ( + SleepIQSensorEntityDescription( + key=SLEEP_SCORE, + translation_key="sleep_score", + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement="score", + value_fn=lambda sleeper: ( + sleeper.sleep_data.sleep_score if sleeper.sleep_data else None + ), + ), + SleepIQSensorEntityDescription( + key=SLEEP_DURATION, + translation_key="sleep_duration", + device_class=SensorDeviceClass.DURATION, + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=UnitOfTime.HOURS, + suggested_display_precision=1, + value_fn=lambda sleeper: ( + round(sleeper.sleep_data.duration / 3600, 1) + if sleeper.sleep_data and sleeper.sleep_data.duration + else None + ), + ), + SleepIQSensorEntityDescription( + key=HEART_RATE, + translation_key="heart_rate_avg", + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement="bpm", + value_fn=lambda sleeper: ( + sleeper.sleep_data.heart_rate if sleeper.sleep_data else None + ), + ), + SleepIQSensorEntityDescription( + key=RESPIRATORY_RATE, + translation_key="respiratory_rate_avg", + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement="brpm", + value_fn=lambda sleeper: ( + sleeper.sleep_data.respiratory_rate if sleeper.sleep_data else None + ), + ), + SleepIQSensorEntityDescription( + key=HRV, + translation_key="hrv", + device_class=SensorDeviceClass.DURATION, + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=UnitOfTime.MILLISECONDS, + value_fn=lambda sleeper: sleeper.sleep_data.hrv if sleeper.sleep_data else None, + ), +) + async def async_setup_entry( hass: HomeAssistant, @@ -51,16 +117,29 @@ async def async_setup_entry( ) -> None: """Set up the SleepIQ bed sensors.""" data: SleepIQData = hass.data[DOMAIN][entry.entry_id] - async_add_entities( + + entities: list[SensorEntity] = [] + + entities.extend( SleepIQSensorEntity(data.data_coordinator, bed, sleeper, description) for bed in data.client.beds.values() for sleeper in bed.sleepers - for description in SENSORS + for description in BED_SENSORS ) + entities.extend( + SleepIQSensorEntity(data.sleep_data_coordinator, bed, sleeper, description) + for bed in data.client.beds.values() + for sleeper in bed.sleepers + for description in SLEEP_HEALTH_SENSORS + ) + + async_add_entities(entities) + class SleepIQSensorEntity( - SleepIQSleeperEntity[SleepIQDataUpdateCoordinator], SensorEntity + SleepIQSleeperEntity[SleepIQDataUpdateCoordinator | SleepIQSleepDataCoordinator], + SensorEntity, ): """Representation of a SleepIQ sensor.""" @@ -68,7 +147,7 @@ class SleepIQSensorEntity( def __init__( self, - coordinator: SleepIQDataUpdateCoordinator, + coordinator: SleepIQDataUpdateCoordinator | SleepIQSleepDataCoordinator, bed: SleepIQBed, sleeper: SleepIQSleeper, description: SleepIQSensorEntityDescription, diff --git a/tests/components/sleepiq/conftest.py b/tests/components/sleepiq/conftest.py index f52f489aec3..683f6e3b20c 100644 --- a/tests/components/sleepiq/conftest.py +++ b/tests/components/sleepiq/conftest.py @@ -10,6 +10,7 @@ from asyncsleepiq import ( CoreTemps, FootWarmingTemps, Side, + SleepData, SleepIQActuator, SleepIQBed, SleepIQCoreClimate, @@ -76,6 +77,13 @@ def mock_bed() -> MagicMock: sleeper_l.sleep_number = 40 sleeper_l.pressure = 1000 sleeper_l.sleeper_id = SLEEPER_L_ID + sleeper_l.sleep_data = SleepData( + duration=28800, # 8 hours in seconds + sleep_score=85, + heart_rate=60, + respiratory_rate=14, + hrv=68, + ) sleeper_r.side = Side.RIGHT sleeper_r.name = SLEEPER_R_NAME @@ -83,6 +91,13 @@ def mock_bed() -> MagicMock: sleeper_r.sleep_number = 80 sleeper_r.pressure = 1400 sleeper_r.sleeper_id = SLEEPER_R_ID + sleeper_r.sleep_data = SleepData( + duration=25200, # 7 hours in seconds + sleep_score=78, + heart_rate=65, + respiratory_rate=15, + hrv=72, + ) bed.foundation = create_autospec(SleepIQFoundation) light_1 = create_autospec(SleepIQLight) diff --git a/tests/components/sleepiq/snapshots/test_sensor.ambr b/tests/components/sleepiq/snapshots/test_sensor.ambr index 22093c0fb37..45c7ff08a8f 100644 --- a/tests/components/sleepiq/snapshots/test_sensor.ambr +++ b/tests/components/sleepiq/snapshots/test_sensor.ambr @@ -1,4 +1,116 @@ # serializer version: 1 +# name: test_sensors[sensor.sleepnumber_test_bed_sleeper_r_heart_rate_average-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.sleepnumber_test_bed_sleeper_r_heart_rate_average', + 'has_entity_name': False, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'SleepNumber Test Bed Sleeper R Heart Rate Average', + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': 'mdi:bed', + 'original_name': 'SleepNumber Test Bed Sleeper R Heart Rate Average', + 'platform': 'sleepiq', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'heart_rate_avg', + 'unique_id': '43219_heart_rate', + 'unit_of_measurement': 'bpm', + }) +# --- +# name: test_sensors[sensor.sleepnumber_test_bed_sleeper_r_heart_rate_average-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'SleepNumber Test Bed Sleeper R Heart Rate Average', + 'icon': 'mdi:bed', + 'state_class': , + 'unit_of_measurement': 'bpm', + }), + 'context': , + 'entity_id': 'sensor.sleepnumber_test_bed_sleeper_r_heart_rate_average', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '65', + }) +# --- +# name: test_sensors[sensor.sleepnumber_test_bed_sleeper_r_heart_rate_variability-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.sleepnumber_test_bed_sleeper_r_heart_rate_variability', + 'has_entity_name': False, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'SleepNumber Test Bed Sleeper R Heart Rate Variability', + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 0, + }), + }), + 'original_device_class': , + 'original_icon': 'mdi:bed', + 'original_name': 'SleepNumber Test Bed Sleeper R Heart Rate Variability', + 'platform': 'sleepiq', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'hrv', + 'unique_id': '43219_hrv', + 'unit_of_measurement': , + }) +# --- +# name: test_sensors[sensor.sleepnumber_test_bed_sleeper_r_heart_rate_variability-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'duration', + 'friendly_name': 'SleepNumber Test Bed Sleeper R Heart Rate Variability', + 'icon': 'mdi:bed', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.sleepnumber_test_bed_sleeper_r_heart_rate_variability', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '72', + }) +# --- # name: test_sensors[sensor.sleepnumber_test_bed_sleeper_r_pressure-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ @@ -52,6 +164,172 @@ 'state': '1400', }) # --- +# name: test_sensors[sensor.sleepnumber_test_bed_sleeper_r_respiratory_rate_average-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.sleepnumber_test_bed_sleeper_r_respiratory_rate_average', + 'has_entity_name': False, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'SleepNumber Test Bed Sleeper R Respiratory Rate Average', + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': 'mdi:bed', + 'original_name': 'SleepNumber Test Bed Sleeper R Respiratory Rate Average', + 'platform': 'sleepiq', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'respiratory_rate_avg', + 'unique_id': '43219_respiratory_rate', + 'unit_of_measurement': 'brpm', + }) +# --- +# name: test_sensors[sensor.sleepnumber_test_bed_sleeper_r_respiratory_rate_average-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'SleepNumber Test Bed Sleeper R Respiratory Rate Average', + 'icon': 'mdi:bed', + 'state_class': , + 'unit_of_measurement': 'brpm', + }), + 'context': , + 'entity_id': 'sensor.sleepnumber_test_bed_sleeper_r_respiratory_rate_average', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '15', + }) +# --- +# name: test_sensors[sensor.sleepnumber_test_bed_sleeper_r_sleep_duration-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.sleepnumber_test_bed_sleeper_r_sleep_duration', + 'has_entity_name': False, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'SleepNumber Test Bed Sleeper R Sleep Duration', + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 1, + }), + }), + 'original_device_class': , + 'original_icon': 'mdi:bed', + 'original_name': 'SleepNumber Test Bed Sleeper R Sleep Duration', + 'platform': 'sleepiq', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'sleep_duration', + 'unique_id': '43219_sleep_duration', + 'unit_of_measurement': , + }) +# --- +# name: test_sensors[sensor.sleepnumber_test_bed_sleeper_r_sleep_duration-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'duration', + 'friendly_name': 'SleepNumber Test Bed Sleeper R Sleep Duration', + 'icon': 'mdi:bed', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.sleepnumber_test_bed_sleeper_r_sleep_duration', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '7.0', + }) +# --- +# name: test_sensors[sensor.sleepnumber_test_bed_sleeper_r_sleep_score-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.sleepnumber_test_bed_sleeper_r_sleep_score', + 'has_entity_name': False, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'SleepNumber Test Bed Sleeper R Sleep Score', + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': 'mdi:bed', + 'original_name': 'SleepNumber Test Bed Sleeper R Sleep Score', + 'platform': 'sleepiq', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'sleep_score', + 'unique_id': '43219_sleep_score', + 'unit_of_measurement': 'score', + }) +# --- +# name: test_sensors[sensor.sleepnumber_test_bed_sleeper_r_sleep_score-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'SleepNumber Test Bed Sleeper R Sleep Score', + 'icon': 'mdi:bed', + 'state_class': , + 'unit_of_measurement': 'score', + }), + 'context': , + 'entity_id': 'sensor.sleepnumber_test_bed_sleeper_r_sleep_score', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '78', + }) +# --- # name: test_sensors[sensor.sleepnumber_test_bed_sleeper_r_sleepnumber-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ @@ -105,6 +383,118 @@ 'state': '80', }) # --- +# name: test_sensors[sensor.sleepnumber_test_bed_sleeperl_heart_rate_average-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.sleepnumber_test_bed_sleeperl_heart_rate_average', + 'has_entity_name': False, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'SleepNumber Test Bed SleeperL Heart Rate Average', + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': 'mdi:bed', + 'original_name': 'SleepNumber Test Bed SleeperL Heart Rate Average', + 'platform': 'sleepiq', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'heart_rate_avg', + 'unique_id': '98765_heart_rate', + 'unit_of_measurement': 'bpm', + }) +# --- +# name: test_sensors[sensor.sleepnumber_test_bed_sleeperl_heart_rate_average-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'SleepNumber Test Bed SleeperL Heart Rate Average', + 'icon': 'mdi:bed', + 'state_class': , + 'unit_of_measurement': 'bpm', + }), + 'context': , + 'entity_id': 'sensor.sleepnumber_test_bed_sleeperl_heart_rate_average', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '60', + }) +# --- +# name: test_sensors[sensor.sleepnumber_test_bed_sleeperl_heart_rate_variability-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.sleepnumber_test_bed_sleeperl_heart_rate_variability', + 'has_entity_name': False, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'SleepNumber Test Bed SleeperL Heart Rate Variability', + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 0, + }), + }), + 'original_device_class': , + 'original_icon': 'mdi:bed', + 'original_name': 'SleepNumber Test Bed SleeperL Heart Rate Variability', + 'platform': 'sleepiq', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'hrv', + 'unique_id': '98765_hrv', + 'unit_of_measurement': , + }) +# --- +# name: test_sensors[sensor.sleepnumber_test_bed_sleeperl_heart_rate_variability-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'duration', + 'friendly_name': 'SleepNumber Test Bed SleeperL Heart Rate Variability', + 'icon': 'mdi:bed', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.sleepnumber_test_bed_sleeperl_heart_rate_variability', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '68', + }) +# --- # name: test_sensors[sensor.sleepnumber_test_bed_sleeperl_pressure-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ @@ -158,6 +548,172 @@ 'state': '1000', }) # --- +# name: test_sensors[sensor.sleepnumber_test_bed_sleeperl_respiratory_rate_average-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.sleepnumber_test_bed_sleeperl_respiratory_rate_average', + 'has_entity_name': False, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'SleepNumber Test Bed SleeperL Respiratory Rate Average', + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': 'mdi:bed', + 'original_name': 'SleepNumber Test Bed SleeperL Respiratory Rate Average', + 'platform': 'sleepiq', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'respiratory_rate_avg', + 'unique_id': '98765_respiratory_rate', + 'unit_of_measurement': 'brpm', + }) +# --- +# name: test_sensors[sensor.sleepnumber_test_bed_sleeperl_respiratory_rate_average-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'SleepNumber Test Bed SleeperL Respiratory Rate Average', + 'icon': 'mdi:bed', + 'state_class': , + 'unit_of_measurement': 'brpm', + }), + 'context': , + 'entity_id': 'sensor.sleepnumber_test_bed_sleeperl_respiratory_rate_average', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '14', + }) +# --- +# name: test_sensors[sensor.sleepnumber_test_bed_sleeperl_sleep_duration-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.sleepnumber_test_bed_sleeperl_sleep_duration', + 'has_entity_name': False, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'SleepNumber Test Bed SleeperL Sleep Duration', + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 1, + }), + }), + 'original_device_class': , + 'original_icon': 'mdi:bed', + 'original_name': 'SleepNumber Test Bed SleeperL Sleep Duration', + 'platform': 'sleepiq', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'sleep_duration', + 'unique_id': '98765_sleep_duration', + 'unit_of_measurement': , + }) +# --- +# name: test_sensors[sensor.sleepnumber_test_bed_sleeperl_sleep_duration-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'duration', + 'friendly_name': 'SleepNumber Test Bed SleeperL Sleep Duration', + 'icon': 'mdi:bed', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.sleepnumber_test_bed_sleeperl_sleep_duration', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '8.0', + }) +# --- +# name: test_sensors[sensor.sleepnumber_test_bed_sleeperl_sleep_score-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.sleepnumber_test_bed_sleeperl_sleep_score', + 'has_entity_name': False, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'SleepNumber Test Bed SleeperL Sleep Score', + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': 'mdi:bed', + 'original_name': 'SleepNumber Test Bed SleeperL Sleep Score', + 'platform': 'sleepiq', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'sleep_score', + 'unique_id': '98765_sleep_score', + 'unit_of_measurement': 'score', + }) +# --- +# name: test_sensors[sensor.sleepnumber_test_bed_sleeperl_sleep_score-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'SleepNumber Test Bed SleeperL Sleep Score', + 'icon': 'mdi:bed', + 'state_class': , + 'unit_of_measurement': 'score', + }), + 'context': , + 'entity_id': 'sensor.sleepnumber_test_bed_sleeperl_sleep_score', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '85', + }) +# --- # name: test_sensors[sensor.sleepnumber_test_bed_sleeperl_sleepnumber-entry] EntityRegistryEntrySnapshot({ 'aliases': set({