mirror of
https://github.com/home-assistant/core.git
synced 2025-07-09 22:37:11 +00:00
Only add Withings sleep sensors when we have data (#102578)
Co-authored-by: Robert Resch <robert@resch.dev>
This commit is contained in:
parent
d5af6c595d
commit
42c062de68
@ -263,7 +263,6 @@ SLEEP_SENSORS = [
|
|||||||
icon="mdi:sleep",
|
icon="mdi:sleep",
|
||||||
device_class=SensorDeviceClass.DURATION,
|
device_class=SensorDeviceClass.DURATION,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
entity_registry_enabled_default=False,
|
|
||||||
),
|
),
|
||||||
WithingsSleepSensorEntityDescription(
|
WithingsSleepSensorEntityDescription(
|
||||||
key="sleep_tosleep_duration_seconds",
|
key="sleep_tosleep_duration_seconds",
|
||||||
@ -645,9 +644,33 @@ async def async_setup_entry(
|
|||||||
|
|
||||||
sleep_coordinator = withings_data.sleep_coordinator
|
sleep_coordinator = withings_data.sleep_coordinator
|
||||||
|
|
||||||
entities.extend(
|
sleep_entities_setup_before = ent_reg.async_get_entity_id(
|
||||||
WithingsSleepSensor(sleep_coordinator, attribute) for attribute in SLEEP_SENSORS
|
Platform.SENSOR,
|
||||||
|
DOMAIN,
|
||||||
|
f"withings_{entry.unique_id}_sleep_deep_duration_seconds",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if sleep_coordinator.data is not None or sleep_entities_setup_before:
|
||||||
|
entities.extend(
|
||||||
|
WithingsSleepSensor(sleep_coordinator, attribute)
|
||||||
|
for attribute in SLEEP_SENSORS
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
remove_listener: Callable[[], None]
|
||||||
|
|
||||||
|
def _async_add_sleep_entities() -> None:
|
||||||
|
"""Add sleep entities."""
|
||||||
|
if sleep_coordinator.data is not None:
|
||||||
|
async_add_entities(
|
||||||
|
WithingsSleepSensor(sleep_coordinator, attribute)
|
||||||
|
for attribute in SLEEP_SENSORS
|
||||||
|
)
|
||||||
|
remove_listener()
|
||||||
|
|
||||||
|
remove_listener = sleep_coordinator.async_add_listener(
|
||||||
|
_async_add_sleep_entities
|
||||||
|
)
|
||||||
|
|
||||||
async_add_entities(entities)
|
async_add_entities(entities)
|
||||||
|
|
||||||
|
|
||||||
@ -695,14 +718,10 @@ class WithingsSleepSensor(WithingsSensor):
|
|||||||
@property
|
@property
|
||||||
def native_value(self) -> StateType:
|
def native_value(self) -> StateType:
|
||||||
"""Return the state of the entity."""
|
"""Return the state of the entity."""
|
||||||
assert self.coordinator.data
|
if not self.coordinator.data:
|
||||||
|
return None
|
||||||
return self.entity_description.value_fn(self.coordinator.data)
|
return self.entity_description.value_fn(self.coordinator.data)
|
||||||
|
|
||||||
@property
|
|
||||||
def available(self) -> bool:
|
|
||||||
"""Return if the sensor is available."""
|
|
||||||
return super().available and self.coordinator.data is not None
|
|
||||||
|
|
||||||
|
|
||||||
class WithingsGoalsSensor(WithingsSensor):
|
class WithingsGoalsSensor(WithingsSensor):
|
||||||
"""Implementation of a Withings goals sensor."""
|
"""Implementation of a Withings goals sensor."""
|
||||||
|
@ -5,7 +5,7 @@ from typing import Any
|
|||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
from aiohttp.test_utils import TestClient
|
from aiohttp.test_utils import TestClient
|
||||||
from aiowithings import Activity, Goals, MeasurementGroup
|
from aiowithings import Activity, Goals, MeasurementGroup, SleepSummary
|
||||||
from freezegun.api import FrozenDateTimeFactory
|
from freezegun.api import FrozenDateTimeFactory
|
||||||
|
|
||||||
from homeassistant.components.webhook import async_generate_url
|
from homeassistant.components.webhook import async_generate_url
|
||||||
@ -92,3 +92,11 @@ def load_activity_fixture(
|
|||||||
"""Return measurement from fixture."""
|
"""Return measurement from fixture."""
|
||||||
activity_json = load_json_array_fixture(fixture)
|
activity_json = load_json_array_fixture(fixture)
|
||||||
return [Activity.from_api(activity) for activity in activity_json]
|
return [Activity.from_api(activity) for activity in activity_json]
|
||||||
|
|
||||||
|
|
||||||
|
def load_sleep_fixture(
|
||||||
|
fixture: str = "withings/sleep_summaries.json",
|
||||||
|
) -> list[SleepSummary]:
|
||||||
|
"""Return sleep summaries from fixture."""
|
||||||
|
sleep_json = load_json_array_fixture("withings/sleep_summaries.json")
|
||||||
|
return [SleepSummary.from_api(sleep_summary) for sleep_summary in sleep_json]
|
||||||
|
@ -3,7 +3,7 @@ from datetime import timedelta
|
|||||||
import time
|
import time
|
||||||
from unittest.mock import AsyncMock, patch
|
from unittest.mock import AsyncMock, patch
|
||||||
|
|
||||||
from aiowithings import Device, SleepSummary, WithingsClient
|
from aiowithings import Device, WithingsClient
|
||||||
from aiowithings.models import NotificationConfiguration
|
from aiowithings.models import NotificationConfiguration
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
@ -20,6 +20,7 @@ from tests.components.withings import (
|
|||||||
load_activity_fixture,
|
load_activity_fixture,
|
||||||
load_goals_fixture,
|
load_goals_fixture,
|
||||||
load_measurements_fixture,
|
load_measurements_fixture,
|
||||||
|
load_sleep_fixture,
|
||||||
)
|
)
|
||||||
|
|
||||||
CLIENT_ID = "1234"
|
CLIENT_ID = "1234"
|
||||||
@ -138,11 +139,6 @@ def mock_withings():
|
|||||||
|
|
||||||
measurement_groups = load_measurements_fixture()
|
measurement_groups = load_measurements_fixture()
|
||||||
|
|
||||||
sleep_json = load_json_array_fixture("withings/sleep_summaries.json")
|
|
||||||
sleep_summaries = [
|
|
||||||
SleepSummary.from_api(sleep_summary) for sleep_summary in sleep_json
|
|
||||||
]
|
|
||||||
|
|
||||||
notification_json = load_json_array_fixture("withings/notifications.json")
|
notification_json = load_json_array_fixture("withings/notifications.json")
|
||||||
notifications = [
|
notifications = [
|
||||||
NotificationConfiguration.from_api(not_conf) for not_conf in notification_json
|
NotificationConfiguration.from_api(not_conf) for not_conf in notification_json
|
||||||
@ -155,7 +151,7 @@ def mock_withings():
|
|||||||
mock.get_goals.return_value = load_goals_fixture()
|
mock.get_goals.return_value = load_goals_fixture()
|
||||||
mock.get_measurement_in_period.return_value = measurement_groups
|
mock.get_measurement_in_period.return_value = measurement_groups
|
||||||
mock.get_measurement_since.return_value = measurement_groups
|
mock.get_measurement_since.return_value = measurement_groups
|
||||||
mock.get_sleep_summary_since.return_value = sleep_summaries
|
mock.get_sleep_summary_since.return_value = load_sleep_fixture()
|
||||||
mock.get_activities_since.return_value = activities
|
mock.get_activities_since.return_value = activities
|
||||||
mock.get_activities_in_period.return_value = activities
|
mock.get_activities_in_period.return_value = activities
|
||||||
mock.list_notification_configurations.return_value = notifications
|
mock.list_notification_configurations.return_value = notifications
|
||||||
|
@ -59,8 +59,8 @@ async def test_binary_sensor(
|
|||||||
|
|
||||||
assert hass.states.get(entity_id).state == STATE_UNKNOWN
|
assert hass.states.get(entity_id).state == STATE_UNKNOWN
|
||||||
assert (
|
assert (
|
||||||
"Platform withings does not generate unique IDs. ID withings_12345_in_bed already exists - ignoring binary_sensor.henk_in_bed"
|
"Platform withings does not generate unique IDs. ID withings_12345_in_bed "
|
||||||
not in caplog.text
|
"already exists - ignoring binary_sensor.henk_in_bed" not in caplog.text
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -14,6 +14,7 @@ from . import (
|
|||||||
load_activity_fixture,
|
load_activity_fixture,
|
||||||
load_goals_fixture,
|
load_goals_fixture,
|
||||||
load_measurements_fixture,
|
load_measurements_fixture,
|
||||||
|
load_sleep_fixture,
|
||||||
setup_integration,
|
setup_integration,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -127,7 +128,7 @@ async def test_update_new_measurement_creates_new_sensor(
|
|||||||
async_fire_time_changed(hass)
|
async_fire_time_changed(hass)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
assert hass.states.get("sensor.henk_fat_mass") is not None
|
assert hass.states.get("sensor.henk_fat_mass")
|
||||||
|
|
||||||
|
|
||||||
async def test_update_new_goals_creates_new_sensor(
|
async def test_update_new_goals_creates_new_sensor(
|
||||||
@ -143,7 +144,7 @@ async def test_update_new_goals_creates_new_sensor(
|
|||||||
await setup_integration(hass, polling_config_entry, False)
|
await setup_integration(hass, polling_config_entry, False)
|
||||||
|
|
||||||
assert hass.states.get("sensor.henk_step_goal") is None
|
assert hass.states.get("sensor.henk_step_goal") is None
|
||||||
assert hass.states.get("sensor.henk_weight_goal") is not None
|
assert hass.states.get("sensor.henk_weight_goal")
|
||||||
|
|
||||||
withings.get_goals.return_value = load_goals_fixture()
|
withings.get_goals.return_value = load_goals_fixture()
|
||||||
|
|
||||||
@ -151,7 +152,7 @@ async def test_update_new_goals_creates_new_sensor(
|
|||||||
async_fire_time_changed(hass)
|
async_fire_time_changed(hass)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
assert hass.states.get("sensor.henk_step_goal") is not None
|
assert hass.states.get("sensor.henk_step_goal")
|
||||||
|
|
||||||
|
|
||||||
async def test_activity_sensors_unknown_next_day(
|
async def test_activity_sensors_unknown_next_day(
|
||||||
@ -164,7 +165,7 @@ async def test_activity_sensors_unknown_next_day(
|
|||||||
freezer.move_to("2023-10-21")
|
freezer.move_to("2023-10-21")
|
||||||
await setup_integration(hass, polling_config_entry, False)
|
await setup_integration(hass, polling_config_entry, False)
|
||||||
|
|
||||||
assert hass.states.get("sensor.henk_steps_today") is not None
|
assert hass.states.get("sensor.henk_steps_today")
|
||||||
|
|
||||||
withings.get_activities_since.return_value = []
|
withings.get_activities_since.return_value = []
|
||||||
|
|
||||||
@ -206,7 +207,7 @@ async def test_activity_sensors_created_when_existed(
|
|||||||
freezer.move_to("2023-10-21")
|
freezer.move_to("2023-10-21")
|
||||||
await setup_integration(hass, polling_config_entry, False)
|
await setup_integration(hass, polling_config_entry, False)
|
||||||
|
|
||||||
assert hass.states.get("sensor.henk_steps_today") is not None
|
assert hass.states.get("sensor.henk_steps_today")
|
||||||
assert hass.states.get("sensor.henk_steps_today").state != STATE_UNKNOWN
|
assert hass.states.get("sensor.henk_steps_today").state != STATE_UNKNOWN
|
||||||
|
|
||||||
withings.get_activities_in_period.return_value = []
|
withings.get_activities_in_period.return_value = []
|
||||||
@ -242,25 +243,53 @@ async def test_activity_sensors_created_when_receive_activity_data(
|
|||||||
async_fire_time_changed(hass)
|
async_fire_time_changed(hass)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
assert hass.states.get("sensor.henk_steps_today") is not None
|
assert hass.states.get("sensor.henk_steps_today")
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
|
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
|
||||||
async def test_no_sleep(
|
async def test_sleep_sensors_created_when_existed(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
snapshot: SnapshotAssertion,
|
|
||||||
withings: AsyncMock,
|
withings: AsyncMock,
|
||||||
polling_config_entry: MockConfigEntry,
|
polling_config_entry: MockConfigEntry,
|
||||||
freezer: FrozenDateTimeFactory,
|
freezer: FrozenDateTimeFactory,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test no sleep found."""
|
"""Test sleep sensors will be added if they existed before."""
|
||||||
await setup_integration(hass, polling_config_entry, False)
|
await setup_integration(hass, polling_config_entry, False)
|
||||||
|
|
||||||
|
assert hass.states.get("sensor.henk_deep_sleep")
|
||||||
|
assert hass.states.get("sensor.henk_deep_sleep").state != STATE_UNKNOWN
|
||||||
|
|
||||||
withings.get_sleep_summary_since.return_value = []
|
withings.get_sleep_summary_since.return_value = []
|
||||||
|
|
||||||
|
await hass.config_entries.async_reload(polling_config_entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert hass.states.get("sensor.henk_deep_sleep").state == STATE_UNKNOWN
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
|
||||||
|
async def test_sleep_sensors_created_when_receive_sleep_data(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
withings: AsyncMock,
|
||||||
|
polling_config_entry: MockConfigEntry,
|
||||||
|
freezer: FrozenDateTimeFactory,
|
||||||
|
) -> None:
|
||||||
|
"""Test sleep sensors will be added if we receive sleep data."""
|
||||||
|
withings.get_sleep_summary_since.return_value = []
|
||||||
|
await setup_integration(hass, polling_config_entry, False)
|
||||||
|
|
||||||
|
assert hass.states.get("sensor.henk_deep_sleep") is None
|
||||||
|
|
||||||
freezer.tick(timedelta(minutes=10))
|
freezer.tick(timedelta(minutes=10))
|
||||||
async_fire_time_changed(hass)
|
async_fire_time_changed(hass)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
state = hass.states.get("sensor.henk_average_respiratory_rate")
|
assert hass.states.get("sensor.henk_deep_sleep") is None
|
||||||
assert state is not None
|
|
||||||
assert state.state == STATE_UNAVAILABLE
|
withings.get_sleep_summary_since.return_value = load_sleep_fixture()
|
||||||
|
|
||||||
|
freezer.tick(timedelta(minutes=10))
|
||||||
|
async_fire_time_changed(hass)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert hass.states.get("sensor.henk_deep_sleep")
|
||||||
|
Loading…
x
Reference in New Issue
Block a user