Dynamically adjust Netatmo polling frequency (#106742)

This commit is contained in:
Tobias Sauerwein
2024-01-14 11:22:02 +01:00
committed by GitHub
parent 965499dd90
commit 10d5382ae6

View File

@@ -17,6 +17,7 @@ from pyatmo.modules.device_types import (
DeviceType as NetatmoDeviceType, DeviceType as NetatmoDeviceType,
) )
from homeassistant.components import cloud
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback
from homeassistant.helpers.dispatcher import ( from homeassistant.helpers.dispatcher import (
@@ -69,6 +70,10 @@ PUBLISHERS = {
} }
BATCH_SIZE = 3 BATCH_SIZE = 3
DEV_FACTOR = 7
DEV_LIMIT = 400
CLOUD_FACTOR = 2
CLOUD_LIMIT = 150
DEFAULT_INTERVALS = { DEFAULT_INTERVALS = {
ACCOUNT: 10800, ACCOUNT: 10800,
HOME: 300, HOME: 300,
@@ -126,6 +131,7 @@ class NetatmoDataHandler:
"""Manages the Netatmo data handling.""" """Manages the Netatmo data handling."""
account: pyatmo.AsyncAccount account: pyatmo.AsyncAccount
_interval_factor: int
def __init__(self, hass: HomeAssistant, config_entry: ConfigEntry) -> None: def __init__(self, hass: HomeAssistant, config_entry: ConfigEntry) -> None:
"""Initialize self.""" """Initialize self."""
@@ -135,6 +141,14 @@ class NetatmoDataHandler:
self.publisher: dict[str, NetatmoPublisher] = {} self.publisher: dict[str, NetatmoPublisher] = {}
self._queue: deque = deque() self._queue: deque = deque()
self._webhook: bool = False self._webhook: bool = False
if config_entry.data["auth_implementation"] == cloud.DOMAIN:
self._interval_factor = CLOUD_FACTOR
self._rate_limit = CLOUD_LIMIT
else:
self._interval_factor = DEV_FACTOR
self._rate_limit = DEV_LIMIT
self.poll_start = time()
self.poll_count = 0
async def async_setup(self) -> None: async def async_setup(self) -> None:
"""Set up the Netatmo data handler.""" """Set up the Netatmo data handler."""
@@ -167,16 +181,29 @@ class NetatmoDataHandler:
We do up to BATCH_SIZE calls in one update in order We do up to BATCH_SIZE calls in one update in order
to minimize the calls on the api service. to minimize the calls on the api service.
""" """
for data_class in islice(self._queue, 0, BATCH_SIZE): for data_class in islice(self._queue, 0, BATCH_SIZE * self._interval_factor):
if data_class.next_scan > time(): if data_class.next_scan > time():
continue continue
if publisher := data_class.name: if publisher := data_class.name:
self.publisher[publisher].next_scan = time() + data_class.interval error = await self.async_fetch_data(publisher)
await self.async_fetch_data(publisher) if error:
self.publisher[publisher].next_scan = (
time() + data_class.interval * 10
)
else:
self.publisher[publisher].next_scan = time() + data_class.interval
self._queue.rotate(BATCH_SIZE) self._queue.rotate(BATCH_SIZE)
cph = self.poll_count / (time() - self.poll_start) * 3600
_LOGGER.debug("Calls per hour: %i", cph)
if cph > self._rate_limit:
for publisher in self.publisher.values():
publisher.next_scan += 60
if (time() - self.poll_start) > 3600:
self.poll_start = time()
self.poll_count = 0
@callback @callback
def async_force_update(self, signal_name: str) -> None: def async_force_update(self, signal_name: str) -> None:
@@ -198,31 +225,29 @@ class NetatmoDataHandler:
_LOGGER.debug("%s camera reconnected", MANUFACTURER) _LOGGER.debug("%s camera reconnected", MANUFACTURER)
self.async_force_update(ACCOUNT) self.async_force_update(ACCOUNT)
async def async_fetch_data(self, signal_name: str) -> None: async def async_fetch_data(self, signal_name: str) -> bool:
"""Fetch data and notify.""" """Fetch data and notify."""
self.poll_count += 1
has_error = False
try: try:
await getattr(self.account, self.publisher[signal_name].method)( await getattr(self.account, self.publisher[signal_name].method)(
**self.publisher[signal_name].kwargs **self.publisher[signal_name].kwargs
) )
except pyatmo.NoDevice as err: except (pyatmo.NoDevice, pyatmo.ApiError) as err:
_LOGGER.debug(err) _LOGGER.debug(err)
has_error = True
except pyatmo.ApiError as err: except (asyncio.TimeoutError, aiohttp.ClientConnectorError) as err:
_LOGGER.debug(err) _LOGGER.debug(err)
return True
except asyncio.TimeoutError as err:
_LOGGER.debug(err)
return
except aiohttp.ClientConnectorError as err:
_LOGGER.debug(err)
return
for update_callback in self.publisher[signal_name].subscriptions: for update_callback in self.publisher[signal_name].subscriptions:
if update_callback: if update_callback:
update_callback() update_callback()
return has_error
async def subscribe( async def subscribe(
self, self,
publisher: str, publisher: str,
@@ -239,10 +264,11 @@ class NetatmoDataHandler:
if publisher == "public": if publisher == "public":
kwargs = {"area_id": self.account.register_public_weather_area(**kwargs)} kwargs = {"area_id": self.account.register_public_weather_area(**kwargs)}
interval = int(DEFAULT_INTERVALS[publisher] / self._interval_factor)
self.publisher[signal_name] = NetatmoPublisher( self.publisher[signal_name] = NetatmoPublisher(
name=signal_name, name=signal_name,
interval=DEFAULT_INTERVALS[publisher], interval=interval,
next_scan=time() + DEFAULT_INTERVALS[publisher], next_scan=time() + interval,
subscriptions={update_callback}, subscriptions={update_callback},
method=PUBLISHERS[publisher], method=PUBLISHERS[publisher],
kwargs=kwargs, kwargs=kwargs,