Only add Withings sleep sensors when we have data (#102578)

Co-authored-by: Robert Resch <robert@resch.dev>
This commit is contained in:
Joost Lekkerkerker 2023-10-23 12:59:13 +02:00 committed by GitHub
parent d5af6c595d
commit 42c062de68
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 83 additions and 31 deletions

View File

@ -263,7 +263,6 @@ SLEEP_SENSORS = [
icon="mdi:sleep",
device_class=SensorDeviceClass.DURATION,
state_class=SensorStateClass.MEASUREMENT,
entity_registry_enabled_default=False,
),
WithingsSleepSensorEntityDescription(
key="sleep_tosleep_duration_seconds",
@ -645,9 +644,33 @@ async def async_setup_entry(
sleep_coordinator = withings_data.sleep_coordinator
entities.extend(
WithingsSleepSensor(sleep_coordinator, attribute) for attribute in SLEEP_SENSORS
sleep_entities_setup_before = ent_reg.async_get_entity_id(
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)
@ -695,14 +718,10 @@ class WithingsSleepSensor(WithingsSensor):
@property
def native_value(self) -> StateType:
"""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)
@property
def available(self) -> bool:
"""Return if the sensor is available."""
return super().available and self.coordinator.data is not None
class WithingsGoalsSensor(WithingsSensor):
"""Implementation of a Withings goals sensor."""

View File

@ -5,7 +5,7 @@ from typing import Any
from urllib.parse import urlparse
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 homeassistant.components.webhook import async_generate_url
@ -92,3 +92,11 @@ def load_activity_fixture(
"""Return measurement from fixture."""
activity_json = load_json_array_fixture(fixture)
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]

View File

@ -3,7 +3,7 @@ from datetime import timedelta
import time
from unittest.mock import AsyncMock, patch
from aiowithings import Device, SleepSummary, WithingsClient
from aiowithings import Device, WithingsClient
from aiowithings.models import NotificationConfiguration
import pytest
@ -20,6 +20,7 @@ from tests.components.withings import (
load_activity_fixture,
load_goals_fixture,
load_measurements_fixture,
load_sleep_fixture,
)
CLIENT_ID = "1234"
@ -138,11 +139,6 @@ def mock_withings():
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")
notifications = [
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_measurement_in_period.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_in_period.return_value = activities
mock.list_notification_configurations.return_value = notifications

View File

@ -59,8 +59,8 @@ async def test_binary_sensor(
assert hass.states.get(entity_id).state == STATE_UNKNOWN
assert (
"Platform withings does not generate unique IDs. ID withings_12345_in_bed already exists - ignoring binary_sensor.henk_in_bed"
not in caplog.text
"Platform withings does not generate unique IDs. ID withings_12345_in_bed "
"already exists - ignoring binary_sensor.henk_in_bed" not in caplog.text
)

View File

@ -14,6 +14,7 @@ from . import (
load_activity_fixture,
load_goals_fixture,
load_measurements_fixture,
load_sleep_fixture,
setup_integration,
)
@ -127,7 +128,7 @@ async def test_update_new_measurement_creates_new_sensor(
async_fire_time_changed(hass)
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(
@ -143,7 +144,7 @@ async def test_update_new_goals_creates_new_sensor(
await setup_integration(hass, polling_config_entry, False)
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()
@ -151,7 +152,7 @@ async def test_update_new_goals_creates_new_sensor(
async_fire_time_changed(hass)
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(
@ -164,7 +165,7 @@ async def test_activity_sensors_unknown_next_day(
freezer.move_to("2023-10-21")
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 = []
@ -206,7 +207,7 @@ async def test_activity_sensors_created_when_existed(
freezer.move_to("2023-10-21")
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
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)
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")
async def test_no_sleep(
async def test_sleep_sensors_created_when_existed(
hass: HomeAssistant,
snapshot: SnapshotAssertion,
withings: AsyncMock,
polling_config_entry: MockConfigEntry,
freezer: FrozenDateTimeFactory,
) -> None:
"""Test no sleep found."""
"""Test sleep sensors will be added if they existed before."""
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 = []
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))
async_fire_time_changed(hass)
await hass.async_block_till_done()
state = hass.states.get("sensor.henk_average_respiratory_rate")
assert state is not None
assert state.state == STATE_UNAVAILABLE
assert hass.states.get("sensor.henk_deep_sleep") is None
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")