Reduce august polling frequency (#114904)
Co-authored-by: TheJulianJES <TheJulianJES@users.noreply.github.com>
This commit is contained in:
parent
a66ed1d936
commit
78920e1d71
4 changed files with 58 additions and 21 deletions
|
@ -5,6 +5,7 @@ from __future__ import annotations
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from functools import partial
|
from functools import partial
|
||||||
import logging
|
import logging
|
||||||
|
from time import monotonic
|
||||||
|
|
||||||
from aiohttp import ClientError
|
from aiohttp import ClientError
|
||||||
from yalexs.activity import Activity, ActivityType
|
from yalexs.activity import Activity, ActivityType
|
||||||
|
@ -26,9 +27,11 @@ _LOGGER = logging.getLogger(__name__)
|
||||||
ACTIVITY_STREAM_FETCH_LIMIT = 10
|
ACTIVITY_STREAM_FETCH_LIMIT = 10
|
||||||
ACTIVITY_CATCH_UP_FETCH_LIMIT = 2500
|
ACTIVITY_CATCH_UP_FETCH_LIMIT = 2500
|
||||||
|
|
||||||
|
INITIAL_LOCK_RESYNC_TIME = 60
|
||||||
|
|
||||||
# If there is a storm of activity (ie lock, unlock, door open, door close, etc)
|
# If there is a storm of activity (ie lock, unlock, door open, door close, etc)
|
||||||
# we want to debounce the updates so we don't hammer the activity api too much.
|
# we want to debounce the updates so we don't hammer the activity api too much.
|
||||||
ACTIVITY_DEBOUNCE_COOLDOWN = 3
|
ACTIVITY_DEBOUNCE_COOLDOWN = 4
|
||||||
|
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
|
@ -62,6 +65,7 @@ class ActivityStream(AugustSubscriberMixin):
|
||||||
self.pubnub = pubnub
|
self.pubnub = pubnub
|
||||||
self._update_debounce: dict[str, Debouncer] = {}
|
self._update_debounce: dict[str, Debouncer] = {}
|
||||||
self._update_debounce_jobs: dict[str, HassJob] = {}
|
self._update_debounce_jobs: dict[str, HassJob] = {}
|
||||||
|
self._start_time: float | None = None
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def _async_update_house_id_later(self, debouncer: Debouncer, _: datetime) -> None:
|
def _async_update_house_id_later(self, debouncer: Debouncer, _: datetime) -> None:
|
||||||
|
@ -70,6 +74,7 @@ class ActivityStream(AugustSubscriberMixin):
|
||||||
|
|
||||||
async def async_setup(self) -> None:
|
async def async_setup(self) -> None:
|
||||||
"""Token refresh check and catch up the activity stream."""
|
"""Token refresh check and catch up the activity stream."""
|
||||||
|
self._start_time = monotonic()
|
||||||
update_debounce = self._update_debounce
|
update_debounce = self._update_debounce
|
||||||
update_debounce_jobs = self._update_debounce_jobs
|
update_debounce_jobs = self._update_debounce_jobs
|
||||||
for house_id in self._house_ids:
|
for house_id in self._house_ids:
|
||||||
|
@ -140,11 +145,25 @@ class ActivityStream(AugustSubscriberMixin):
|
||||||
|
|
||||||
debouncer = self._update_debounce[house_id]
|
debouncer = self._update_debounce[house_id]
|
||||||
debouncer.async_schedule_call()
|
debouncer.async_schedule_call()
|
||||||
|
|
||||||
# Schedule two updates past the debounce time
|
# Schedule two updates past the debounce time
|
||||||
# to ensure we catch the case where the activity
|
# to ensure we catch the case where the activity
|
||||||
# api does not update right away and we need to poll
|
# api does not update right away and we need to poll
|
||||||
# it again. Sometimes the lock operator or a doorbell
|
# it again. Sometimes the lock operator or a doorbell
|
||||||
# will not show up in the activity stream right away.
|
# will not show up in the activity stream right away.
|
||||||
|
# Only do additional polls if we are past
|
||||||
|
# the initial lock resync time to avoid a storm
|
||||||
|
# of activity at setup.
|
||||||
|
if (
|
||||||
|
not self._start_time
|
||||||
|
or monotonic() - self._start_time < INITIAL_LOCK_RESYNC_TIME
|
||||||
|
):
|
||||||
|
_LOGGER.debug(
|
||||||
|
"Skipping additional updates due to ongoing initial lock resync time"
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
_LOGGER.debug("Scheduling additional updates for house id %s", house_id)
|
||||||
job = self._update_debounce_jobs[house_id]
|
job = self._update_debounce_jobs[house_id]
|
||||||
for step in (1, 2):
|
for step in (1, 2):
|
||||||
future_updates.append(
|
future_updates.append(
|
||||||
|
|
|
@ -40,7 +40,7 @@ ATTR_OPERATION_TAG = "tag"
|
||||||
# Limit battery, online, and hardware updates to hourly
|
# Limit battery, online, and hardware updates to hourly
|
||||||
# in order to reduce the number of api requests and
|
# in order to reduce the number of api requests and
|
||||||
# avoid hitting rate limits
|
# avoid hitting rate limits
|
||||||
MIN_TIME_BETWEEN_DETAIL_UPDATES = timedelta(hours=1)
|
MIN_TIME_BETWEEN_DETAIL_UPDATES = timedelta(hours=24)
|
||||||
|
|
||||||
# Activity needs to be checked more frequently as the
|
# Activity needs to be checked more frequently as the
|
||||||
# doorbell motion and rings are included here
|
# doorbell motion and rings are included here
|
||||||
|
|
|
@ -49,9 +49,17 @@ class AugustSubscriberMixin:
|
||||||
"""Call the refresh method."""
|
"""Call the refresh method."""
|
||||||
self._hass.async_create_task(self._async_refresh(now), eager_start=True)
|
self._hass.async_create_task(self._async_refresh(now), eager_start=True)
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def _async_cancel_update_interval(self, _: Event | None = None) -> None:
|
||||||
|
"""Cancel the scheduled update."""
|
||||||
|
if self._unsub_interval:
|
||||||
|
self._unsub_interval()
|
||||||
|
self._unsub_interval = None
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def _async_setup_listeners(self) -> None:
|
def _async_setup_listeners(self) -> None:
|
||||||
"""Create interval and stop listeners."""
|
"""Create interval and stop listeners."""
|
||||||
|
self._async_cancel_update_interval()
|
||||||
self._unsub_interval = async_track_time_interval(
|
self._unsub_interval = async_track_time_interval(
|
||||||
self._hass,
|
self._hass,
|
||||||
self._async_scheduled_refresh,
|
self._async_scheduled_refresh,
|
||||||
|
@ -59,15 +67,10 @@ class AugustSubscriberMixin:
|
||||||
name="august refresh",
|
name="august refresh",
|
||||||
)
|
)
|
||||||
|
|
||||||
@callback
|
if not self._stop_interval:
|
||||||
def _async_cancel_update_interval(_: Event) -> None:
|
|
||||||
self._stop_interval = None
|
|
||||||
if self._unsub_interval:
|
|
||||||
self._unsub_interval()
|
|
||||||
|
|
||||||
self._stop_interval = self._hass.bus.async_listen(
|
self._stop_interval = self._hass.bus.async_listen(
|
||||||
EVENT_HOMEASSISTANT_STOP,
|
EVENT_HOMEASSISTANT_STOP,
|
||||||
_async_cancel_update_interval,
|
self._async_cancel_update_interval,
|
||||||
run_immediately=True,
|
run_immediately=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -82,13 +85,7 @@ class AugustSubscriberMixin:
|
||||||
|
|
||||||
if self._subscriptions:
|
if self._subscriptions:
|
||||||
return
|
return
|
||||||
|
self._async_cancel_update_interval()
|
||||||
if self._unsub_interval:
|
|
||||||
self._unsub_interval()
|
|
||||||
self._unsub_interval = None
|
|
||||||
if self._stop_interval:
|
|
||||||
self._stop_interval()
|
|
||||||
self._stop_interval = None
|
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def async_signal_device_id_update(self, device_id: str) -> None:
|
def async_signal_device_id_update(self, device_id: str) -> None:
|
||||||
|
|
|
@ -4,9 +4,11 @@ import datetime
|
||||||
from unittest.mock import Mock
|
from unittest.mock import Mock
|
||||||
|
|
||||||
from aiohttp import ClientResponseError
|
from aiohttp import ClientResponseError
|
||||||
|
from freezegun.api import FrozenDateTimeFactory
|
||||||
import pytest
|
import pytest
|
||||||
from yalexs.pubnub_async import AugustPubNub
|
from yalexs.pubnub_async import AugustPubNub
|
||||||
|
|
||||||
|
from homeassistant.components.august.activity import INITIAL_LOCK_RESYNC_TIME
|
||||||
from homeassistant.components.lock import (
|
from homeassistant.components.lock import (
|
||||||
DOMAIN as LOCK_DOMAIN,
|
DOMAIN as LOCK_DOMAIN,
|
||||||
STATE_JAMMED,
|
STATE_JAMMED,
|
||||||
|
@ -155,7 +157,9 @@ async def test_one_lock_operation(
|
||||||
|
|
||||||
|
|
||||||
async def test_one_lock_operation_pubnub_connected(
|
async def test_one_lock_operation_pubnub_connected(
|
||||||
hass: HomeAssistant, entity_registry: er.EntityRegistry
|
hass: HomeAssistant,
|
||||||
|
entity_registry: er.EntityRegistry,
|
||||||
|
freezer: FrozenDateTimeFactory,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test lock and unlock operations are async when pubnub is connected."""
|
"""Test lock and unlock operations are async when pubnub is connected."""
|
||||||
lock_one = await _mock_doorsense_enabled_august_lock_detail(hass)
|
lock_one = await _mock_doorsense_enabled_august_lock_detail(hass)
|
||||||
|
@ -230,6 +234,23 @@ async def test_one_lock_operation_pubnub_connected(
|
||||||
== STATE_UNKNOWN
|
== STATE_UNKNOWN
|
||||||
)
|
)
|
||||||
|
|
||||||
|
freezer.tick(INITIAL_LOCK_RESYNC_TIME)
|
||||||
|
|
||||||
|
pubnub.message(
|
||||||
|
pubnub,
|
||||||
|
Mock(
|
||||||
|
channel=lock_one.pubsub_channel,
|
||||||
|
timetoken=(dt_util.utcnow().timestamp() + 2) * 10000000,
|
||||||
|
message={
|
||||||
|
"status": "kAugLockState_Unlocked",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
lock_online_with_doorsense_name = hass.states.get("lock.online_with_doorsense_name")
|
||||||
|
assert lock_online_with_doorsense_name.state == STATE_UNLOCKED
|
||||||
|
|
||||||
|
|
||||||
async def test_lock_jammed(hass: HomeAssistant) -> None:
|
async def test_lock_jammed(hass: HomeAssistant) -> None:
|
||||||
"""Test lock gets jammed on unlock."""
|
"""Test lock gets jammed on unlock."""
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue