Update Withings measurements incrementally after the first update (#102002)
* Update incrementally after the first update * Update incrementally after the first update
This commit is contained in:
parent
302b444269
commit
76083a0b4c
5 changed files with 171 additions and 10 deletions
|
@ -1,6 +1,6 @@
|
||||||
"""Withings coordinator."""
|
"""Withings coordinator."""
|
||||||
from abc import abstractmethod
|
from abc import abstractmethod
|
||||||
from datetime import timedelta
|
from datetime import datetime, timedelta
|
||||||
from typing import TypeVar
|
from typing import TypeVar
|
||||||
|
|
||||||
from aiowithings import (
|
from aiowithings import (
|
||||||
|
@ -33,6 +33,7 @@ class WithingsDataUpdateCoordinator(DataUpdateCoordinator[_T]):
|
||||||
|
|
||||||
config_entry: ConfigEntry
|
config_entry: ConfigEntry
|
||||||
_default_update_interval: timedelta | None = UPDATE_INTERVAL
|
_default_update_interval: timedelta | None = UPDATE_INTERVAL
|
||||||
|
_last_valid_update: datetime | None = None
|
||||||
|
|
||||||
def __init__(self, hass: HomeAssistant, client: WithingsClient) -> None:
|
def __init__(self, hass: HomeAssistant, client: WithingsClient) -> None:
|
||||||
"""Initialize the Withings data coordinator."""
|
"""Initialize the Withings data coordinator."""
|
||||||
|
@ -80,15 +81,24 @@ class WithingsMeasurementDataUpdateCoordinator(
|
||||||
NotificationCategory.ACTIVITY,
|
NotificationCategory.ACTIVITY,
|
||||||
NotificationCategory.PRESSURE,
|
NotificationCategory.PRESSURE,
|
||||||
}
|
}
|
||||||
|
self._previous_data: dict[MeasurementType, float] = {}
|
||||||
|
|
||||||
async def _internal_update_data(self) -> dict[MeasurementType, float]:
|
async def _internal_update_data(self) -> dict[MeasurementType, float]:
|
||||||
"""Retrieve measurement data."""
|
"""Retrieve measurement data."""
|
||||||
now = dt_util.utcnow()
|
if self._last_valid_update is None:
|
||||||
startdate = now - timedelta(days=7)
|
now = dt_util.utcnow()
|
||||||
|
startdate = now - timedelta(days=14)
|
||||||
|
measurements = await self._client.get_measurement_in_period(startdate, now)
|
||||||
|
else:
|
||||||
|
measurements = await self._client.get_measurement_since(
|
||||||
|
self._last_valid_update
|
||||||
|
)
|
||||||
|
|
||||||
response = await self._client.get_measurement_in_period(startdate, now)
|
if measurements:
|
||||||
|
self._last_valid_update = measurements[0].taken_at
|
||||||
return aggregate_measurements(response)
|
aggregated_measurements = aggregate_measurements(measurements)
|
||||||
|
self._previous_data.update(aggregated_measurements)
|
||||||
|
return self._previous_data
|
||||||
|
|
||||||
|
|
||||||
class WithingsSleepDataUpdateCoordinator(
|
class WithingsSleepDataUpdateCoordinator(
|
||||||
|
|
|
@ -151,6 +151,7 @@ def mock_withings():
|
||||||
mock = AsyncMock(spec=WithingsClient)
|
mock = AsyncMock(spec=WithingsClient)
|
||||||
mock.get_devices.return_value = devices
|
mock.get_devices.return_value = devices
|
||||||
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_sleep_summary_since.return_value = sleep_summaries
|
mock.get_sleep_summary_since.return_value = sleep_summaries
|
||||||
mock.list_notification_configurations.return_value = notifications
|
mock.list_notification_configurations.return_value = notifications
|
||||||
|
|
||||||
|
|
97
tests/components/withings/fixtures/get_meas_1.json
Normal file
97
tests/components/withings/fixtures/get_meas_1.json
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"grpid": 1,
|
||||||
|
"attrib": 0,
|
||||||
|
"date": 1618605055,
|
||||||
|
"created": 1618605055,
|
||||||
|
"modified": 1618605055,
|
||||||
|
"category": 1,
|
||||||
|
"deviceid": "91a7e556c2022ef54dca6e07a853c3193734d148",
|
||||||
|
"hash_deviceid": "91a7e556c2022ef54dca6e07a853c3193734d148",
|
||||||
|
"measures": [
|
||||||
|
{
|
||||||
|
"type": 1,
|
||||||
|
"unit": 0,
|
||||||
|
"value": 71
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": 8,
|
||||||
|
"unit": 0,
|
||||||
|
"value": 5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": 5,
|
||||||
|
"unit": 0,
|
||||||
|
"value": 60
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": 76,
|
||||||
|
"unit": 0,
|
||||||
|
"value": 50
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": 88,
|
||||||
|
"unit": 0,
|
||||||
|
"value": 10
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": 4,
|
||||||
|
"unit": 0,
|
||||||
|
"value": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": 12,
|
||||||
|
"unit": 0,
|
||||||
|
"value": 40
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": 71,
|
||||||
|
"unit": 0,
|
||||||
|
"value": 40
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": 73,
|
||||||
|
"unit": 0,
|
||||||
|
"value": 20
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": 6,
|
||||||
|
"unit": -3,
|
||||||
|
"value": 70
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": 9,
|
||||||
|
"unit": 0,
|
||||||
|
"value": 70
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": 10,
|
||||||
|
"unit": 0,
|
||||||
|
"value": 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": 11,
|
||||||
|
"unit": 0,
|
||||||
|
"value": 60
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": 54,
|
||||||
|
"unit": -2,
|
||||||
|
"value": 95
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": 77,
|
||||||
|
"unit": -2,
|
||||||
|
"value": 95
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": 91,
|
||||||
|
"unit": 0,
|
||||||
|
"value": 100
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"modelid": 45,
|
||||||
|
"model": "BPM Connect",
|
||||||
|
"comment": null
|
||||||
|
}
|
||||||
|
]
|
|
@ -212,6 +212,7 @@ async def test_webhooks_request_data(
|
||||||
|
|
||||||
client = await hass_client_no_auth()
|
client = await hass_client_no_auth()
|
||||||
|
|
||||||
|
assert withings.get_measurement_since.call_count == 0
|
||||||
assert withings.get_measurement_in_period.call_count == 1
|
assert withings.get_measurement_in_period.call_count == 1
|
||||||
|
|
||||||
await call_webhook(
|
await call_webhook(
|
||||||
|
@ -220,7 +221,8 @@ async def test_webhooks_request_data(
|
||||||
{"userid": USER_ID, "appli": NotificationCategory.WEIGHT},
|
{"userid": USER_ID, "appli": NotificationCategory.WEIGHT},
|
||||||
client,
|
client,
|
||||||
)
|
)
|
||||||
assert withings.get_measurement_in_period.call_count == 2
|
assert withings.get_measurement_since.call_count == 1
|
||||||
|
assert withings.get_measurement_in_period.call_count == 1
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
|
@ -240,7 +242,7 @@ async def test_triggering_reauth(
|
||||||
"""Test triggering reauth."""
|
"""Test triggering reauth."""
|
||||||
await setup_integration(hass, polling_config_entry, False)
|
await setup_integration(hass, polling_config_entry, False)
|
||||||
|
|
||||||
withings.get_measurement_in_period.side_effect = error
|
withings.get_measurement_since.side_effect = error
|
||||||
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()
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from unittest.mock import AsyncMock
|
from unittest.mock import AsyncMock
|
||||||
|
|
||||||
|
from aiowithings import MeasurementGroup
|
||||||
from freezegun.api import FrozenDateTimeFactory
|
from freezegun.api import FrozenDateTimeFactory
|
||||||
import pytest
|
import pytest
|
||||||
from syrupy import SnapshotAssertion
|
from syrupy import SnapshotAssertion
|
||||||
|
@ -16,7 +17,11 @@ from homeassistant.helpers import entity_registry as er
|
||||||
from . import setup_integration
|
from . import setup_integration
|
||||||
from .conftest import USER_ID
|
from .conftest import USER_ID
|
||||||
|
|
||||||
from tests.common import MockConfigEntry, async_fire_time_changed
|
from tests.common import (
|
||||||
|
MockConfigEntry,
|
||||||
|
async_fire_time_changed,
|
||||||
|
load_json_array_fixture,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def async_get_entity_id(
|
async def async_get_entity_id(
|
||||||
|
@ -57,7 +62,7 @@ async def test_update_failed(
|
||||||
"""Test all entities."""
|
"""Test all entities."""
|
||||||
await setup_integration(hass, polling_config_entry, False)
|
await setup_integration(hass, polling_config_entry, False)
|
||||||
|
|
||||||
withings.get_measurement_in_period.side_effect = Exception
|
withings.get_measurement_since.side_effect = Exception
|
||||||
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()
|
||||||
|
@ -65,3 +70,49 @@ async def test_update_failed(
|
||||||
state = hass.states.get("sensor.henk_weight")
|
state = hass.states.get("sensor.henk_weight")
|
||||||
assert state is not None
|
assert state is not None
|
||||||
assert state.state == STATE_UNAVAILABLE
|
assert state.state == STATE_UNAVAILABLE
|
||||||
|
|
||||||
|
|
||||||
|
async def test_update_updates_incrementally(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
snapshot: SnapshotAssertion,
|
||||||
|
withings: AsyncMock,
|
||||||
|
polling_config_entry: MockConfigEntry,
|
||||||
|
freezer: FrozenDateTimeFactory,
|
||||||
|
) -> None:
|
||||||
|
"""Test fetching new data updates since the last valid update."""
|
||||||
|
await setup_integration(hass, polling_config_entry, False)
|
||||||
|
|
||||||
|
async def _skip_10_minutes() -> None:
|
||||||
|
freezer.tick(timedelta(minutes=10))
|
||||||
|
async_fire_time_changed(hass)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
meas_json = load_json_array_fixture("withings/get_meas_1.json")
|
||||||
|
measurement_groups = [
|
||||||
|
MeasurementGroup.from_api(measurement) for measurement in meas_json
|
||||||
|
]
|
||||||
|
|
||||||
|
assert withings.get_measurement_since.call_args_list == []
|
||||||
|
await _skip_10_minutes()
|
||||||
|
assert (
|
||||||
|
str(withings.get_measurement_since.call_args_list[0].args[0])
|
||||||
|
== "2019-08-01 12:00:00+00:00"
|
||||||
|
)
|
||||||
|
|
||||||
|
withings.get_measurement_since.return_value = measurement_groups
|
||||||
|
await _skip_10_minutes()
|
||||||
|
assert (
|
||||||
|
str(withings.get_measurement_since.call_args_list[1].args[0])
|
||||||
|
== "2019-08-01 12:00:00+00:00"
|
||||||
|
)
|
||||||
|
|
||||||
|
await _skip_10_minutes()
|
||||||
|
assert (
|
||||||
|
str(withings.get_measurement_since.call_args_list[2].args[0])
|
||||||
|
== "2021-04-16 20:30:55+00:00"
|
||||||
|
)
|
||||||
|
|
||||||
|
state = hass.states.get("sensor.henk_weight")
|
||||||
|
assert state is not None
|
||||||
|
assert state.state == "71"
|
||||||
|
assert len(withings.get_measurement_in_period.call_args_list) == 1
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue