Update Withings measurements incrementally after the first update (#102002)
* Update incrementally after the first update * Update incrementally after the first updatepull/101978/head^2
parent
302b444269
commit
76083a0b4c
|
@ -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(
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
||||
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()
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue