Update Withings measurements incrementally after the first update (#102002)

* Update incrementally after the first update

* Update incrementally after the first update
pull/101978/head^2
Joost Lekkerkerker 2023-10-14 16:19:04 +02:00 committed by GitHub
parent 302b444269
commit 76083a0b4c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 171 additions and 10 deletions

View File

@ -1,6 +1,6 @@
"""Withings coordinator."""
from abc import abstractmethod
from datetime import timedelta
from datetime import datetime, timedelta
from typing import TypeVar
from aiowithings import (
@ -33,6 +33,7 @@ class WithingsDataUpdateCoordinator(DataUpdateCoordinator[_T]):
config_entry: ConfigEntry
_default_update_interval: timedelta | None = UPDATE_INTERVAL
_last_valid_update: datetime | None = None
def __init__(self, hass: HomeAssistant, client: WithingsClient) -> None:
"""Initialize the Withings data coordinator."""
@ -80,15 +81,24 @@ class WithingsMeasurementDataUpdateCoordinator(
NotificationCategory.ACTIVITY,
NotificationCategory.PRESSURE,
}
self._previous_data: dict[MeasurementType, float] = {}
async def _internal_update_data(self) -> dict[MeasurementType, float]:
"""Retrieve measurement data."""
now = dt_util.utcnow()
startdate = now - timedelta(days=7)
if self._last_valid_update is None:
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)
return aggregate_measurements(response)
if measurements:
self._last_valid_update = measurements[0].taken_at
aggregated_measurements = aggregate_measurements(measurements)
self._previous_data.update(aggregated_measurements)
return self._previous_data
class WithingsSleepDataUpdateCoordinator(

View File

@ -151,6 +151,7 @@ def mock_withings():
mock = AsyncMock(spec=WithingsClient)
mock.get_devices.return_value = devices
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.list_notification_configurations.return_value = notifications

View 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
}
]

View File

@ -212,6 +212,7 @@ async def test_webhooks_request_data(
client = await hass_client_no_auth()
assert withings.get_measurement_since.call_count == 0
assert withings.get_measurement_in_period.call_count == 1
await call_webhook(
@ -220,7 +221,8 @@ async def test_webhooks_request_data(
{"userid": USER_ID, "appli": NotificationCategory.WEIGHT},
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(
@ -240,7 +242,7 @@ async def test_triggering_reauth(
"""Test triggering reauth."""
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))
async_fire_time_changed(hass)
await hass.async_block_till_done()

View File

@ -2,6 +2,7 @@
from datetime import timedelta
from unittest.mock import AsyncMock
from aiowithings import MeasurementGroup
from freezegun.api import FrozenDateTimeFactory
import pytest
from syrupy import SnapshotAssertion
@ -16,7 +17,11 @@ from homeassistant.helpers import entity_registry as er
from . import setup_integration
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(
@ -57,7 +62,7 @@ async def test_update_failed(
"""Test all entities."""
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))
async_fire_time_changed(hass)
await hass.async_block_till_done()
@ -65,3 +70,49 @@ async def test_update_failed(
state = hass.states.get("sensor.henk_weight")
assert state is not None
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