mirror of
https://github.com/home-assistant/core.git
synced 2025-04-25 01:38:02 +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",
|
||||
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."""
|
||||
|
@ -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]
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
)
|
||||
|
||||
|
||||
|
@ -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")
|
||||
|
Loading…
x
Reference in New Issue
Block a user