* Fix webhook registration * Only load camera platform with valid scope * Add initial data handler and netatmo base class * Update camera to use data handler * Update init * Parallelize API calls * Remove cruft * Minor tweaks * Refactor data handler * Update climate to use data handler * Fix pylint error * Fix climate update not getting fresh data * Update climate data * update to pyatmo 4.0.0 * Refactor for pyatmo 4.0.0 * Exclude from coverage until tests are written * Fix typo * Reduce parallel calls * Add heating request attr * Async get_entities * Undo parallel updates * Fix camera issue * Introduce individual scan interval per device class * Some cleanup * Add basic webhook support for climate to improve responsiveness * Replace ClimateDevice by ClimateEntity * Add support for turning camera on/off * Update camera state upon webhook events * Guard data class registration with lock * Capture errors * Add light platform * Add dis-/connect handling * Fix set schedule service * Remove extra calls * Add service to set person(s) home/away * Add service descriptions * Improve service descriptions * Use LightEntity instead of Light * Add guard if no data is retrieved * Make services entity based * Only raise platform not ready if there is a NOC * Register webhook even during runtime * Fix turning off event * Fix linter error * Fix linter error * Exclude light platform from coverage * Change log level * Refactor public weather sensor to use data handler * Prevent too short coordinates * Ignore modules without _id * Code cleanup * Fix test * Exit early if no home data is retrieved * Prevent discovery if already active * Add services to (un-)register webhook * Fix tests * Not actually a coroutine * Move methods to base class * Address pylint comment * Address pylint complaints * Address comments * Address more comments * Add docstring * Use single instance allowed * Extract method * Remove cruft * Write state directly * Fix test * Add file to coverage * Move nested function * Move nested function * Update docstring * Clean up code * Fix webhook bug * Clean up listeners * Use deque * Clean up prints * Update homeassistant/components/netatmo/sensor.py Co-authored-by: J. Nick Koston <nick@koston.org> * Update homeassistant/components/netatmo/sensor.py Co-authored-by: J. Nick Koston <nick@koston.org> * Update homeassistant/components/netatmo/sensor.py Co-authored-by: J. Nick Koston <nick@koston.org> * Update homeassistant/components/netatmo/sensor.py Co-authored-by: J. Nick Koston <nick@koston.org> * Update homeassistant/components/netatmo/sensor.py Co-authored-by: J. Nick Koston <nick@koston.org> * Update homeassistant/components/netatmo/sensor.py Co-authored-by: J. Nick Koston <nick@koston.org> * Update homeassistant/components/netatmo/camera.py Co-authored-by: J. Nick Koston <nick@koston.org> * Update homeassistant/components/netatmo/camera.py Co-authored-by: J. Nick Koston <nick@koston.org> * Update homeassistant/components/netatmo/camera.py Co-authored-by: J. Nick Koston <nick@koston.org> * Update homeassistant/components/netatmo/camera.py Co-authored-by: J. Nick Koston <nick@koston.org> * Update homeassistant/components/netatmo/camera.py Co-authored-by: J. Nick Koston <nick@koston.org> * Update homeassistant/components/netatmo/camera.py Co-authored-by: J. Nick Koston <nick@koston.org> * Rename data_class variable * Break when match * Extract method * Extract methods * Rename variable * Improve comment * Some refinements * Extra * Extract method * Simplify code * Improve reability * Code simplification * Simplify code * Simplify code * Code cleanup * Fix import * Clean up * Clean up magic strings * Replace data_class_name with CAMERA_DATA_CLASS_NAME * Replace data_class_name with CAMERA_DATA_CLASS_NAME * Replace data_class_name with HOMEDATA_DATA_CLASS_NAME * Replace data_class_name in public weather sensor * Clean up * Remove deprecated config options * Schedule immediate update on camera reconnect * Use UUID to clearly identify public weather areas * Use subscription mode * Move clean up of temporary data classes * Delay data class removal * Fix linter complaints * Adjust test * Only setup lights if webhook are registered * Prevent crash with old config entries * Don't cache home ids * Remove stale code * Fix coordinates if entered mixed up by the user * Move nested function * Add test case for swapped coordinates * Only wait for discovery entries * Only use what I need * Bring stuff closer to where it's used * Auto clean up setup data classes * Code cleanup * Remove unneccessary lock * Update homeassistant/components/netatmo/sensor.py Co-authored-by: J. Nick Koston <nick@koston.org> * Update tests/components/netatmo/test_config_flow.py Co-authored-by: J. Nick Koston <nick@koston.org> * Clean up dead code * Fix formating * Extend coverage * Extend coverage Co-authored-by: J. Nick Koston <nick@koston.org>
166 lines
5.6 KiB
Python
166 lines
5.6 KiB
Python
"""The Netatmo data handler."""
|
|
from collections import deque
|
|
from datetime import timedelta
|
|
from functools import partial
|
|
from itertools import islice
|
|
import logging
|
|
from time import time
|
|
from typing import Deque, Dict, List
|
|
|
|
import pyatmo
|
|
|
|
from homeassistant.config_entries import ConfigEntry
|
|
from homeassistant.core import CALLBACK_TYPE, HomeAssistant
|
|
from homeassistant.helpers.event import async_track_time_interval
|
|
|
|
from .const import AUTH, DOMAIN, MANUFACTURER
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
CAMERA_DATA_CLASS_NAME = "CameraData"
|
|
WEATHERSTATION_DATA_CLASS_NAME = "WeatherStationData"
|
|
HOMECOACH_DATA_CLASS_NAME = "HomeCoachData"
|
|
HOMEDATA_DATA_CLASS_NAME = "HomeData"
|
|
HOMESTATUS_DATA_CLASS_NAME = "HomeStatus"
|
|
PUBLICDATA_DATA_CLASS_NAME = "PublicData"
|
|
|
|
NEXT_SCAN = "next_scan"
|
|
|
|
DATA_CLASSES = {
|
|
WEATHERSTATION_DATA_CLASS_NAME: pyatmo.WeatherStationData,
|
|
HOMECOACH_DATA_CLASS_NAME: pyatmo.HomeCoachData,
|
|
CAMERA_DATA_CLASS_NAME: pyatmo.CameraData,
|
|
HOMEDATA_DATA_CLASS_NAME: pyatmo.HomeData,
|
|
HOMESTATUS_DATA_CLASS_NAME: pyatmo.HomeStatus,
|
|
PUBLICDATA_DATA_CLASS_NAME: pyatmo.PublicData,
|
|
}
|
|
|
|
MAX_CALLS_1H = 20
|
|
BATCH_SIZE = 3
|
|
DEFAULT_INTERVALS = {
|
|
HOMEDATA_DATA_CLASS_NAME: 900,
|
|
HOMESTATUS_DATA_CLASS_NAME: 300,
|
|
CAMERA_DATA_CLASS_NAME: 900,
|
|
WEATHERSTATION_DATA_CLASS_NAME: 300,
|
|
HOMECOACH_DATA_CLASS_NAME: 300,
|
|
PUBLICDATA_DATA_CLASS_NAME: 600,
|
|
}
|
|
SCAN_INTERVAL = 60
|
|
|
|
|
|
class NetatmoDataHandler:
|
|
"""Manages the Netatmo data handling."""
|
|
|
|
def __init__(self, hass: HomeAssistant, entry: ConfigEntry):
|
|
"""Initialize self."""
|
|
self.hass = hass
|
|
self._auth = hass.data[DOMAIN][entry.entry_id][AUTH]
|
|
self.listeners: List[CALLBACK_TYPE] = []
|
|
self._data_classes: Dict = {}
|
|
self.data = {}
|
|
self._queue: Deque = deque()
|
|
self._webhook: bool = False
|
|
|
|
async def async_setup(self):
|
|
"""Set up the Netatmo data handler."""
|
|
|
|
async_track_time_interval(
|
|
self.hass, self.async_update, timedelta(seconds=SCAN_INTERVAL)
|
|
)
|
|
|
|
self.listeners.append(
|
|
self.hass.bus.async_listen("netatmo_event", self.handle_event)
|
|
)
|
|
|
|
async def async_update(self, event_time):
|
|
"""
|
|
Update device.
|
|
|
|
We do up to BATCH_SIZE calls in one update in order
|
|
to minimize the calls on the api service.
|
|
"""
|
|
for data_class in islice(self._queue, 0, BATCH_SIZE):
|
|
if data_class[NEXT_SCAN] > time():
|
|
continue
|
|
self._data_classes[data_class["name"]][NEXT_SCAN] = (
|
|
time() + data_class["interval"]
|
|
)
|
|
|
|
await self.async_fetch_data(
|
|
data_class["class"], data_class["name"], **data_class["kwargs"]
|
|
)
|
|
|
|
self._queue.rotate(BATCH_SIZE)
|
|
|
|
async def async_cleanup(self):
|
|
"""Clean up the Netatmo data handler."""
|
|
for listener in self.listeners:
|
|
listener()
|
|
|
|
async def handle_event(self, event):
|
|
"""Handle webhook events."""
|
|
if event.data["data"]["push_type"] == "webhook_activation":
|
|
_LOGGER.info("%s webhook successfully registered", MANUFACTURER)
|
|
self._webhook = True
|
|
|
|
elif event.data["data"]["push_type"] == "NACamera-connection":
|
|
_LOGGER.debug("%s camera reconnected", MANUFACTURER)
|
|
self._data_classes[CAMERA_DATA_CLASS_NAME][NEXT_SCAN] = time()
|
|
|
|
async def async_fetch_data(self, data_class, data_class_entry, **kwargs):
|
|
"""Fetch data and notify."""
|
|
try:
|
|
self.data[data_class_entry] = await self.hass.async_add_executor_job(
|
|
partial(data_class, **kwargs), self._auth,
|
|
)
|
|
for update_callback in self._data_classes[data_class_entry][
|
|
"subscriptions"
|
|
]:
|
|
if update_callback:
|
|
update_callback()
|
|
|
|
except (pyatmo.NoDevice, pyatmo.ApiError) as err:
|
|
_LOGGER.debug(err)
|
|
|
|
async def register_data_class(
|
|
self, data_class_name, data_class_entry, update_callback, **kwargs
|
|
):
|
|
"""Register data class."""
|
|
if data_class_entry not in self._data_classes:
|
|
self._data_classes[data_class_entry] = {
|
|
"class": DATA_CLASSES[data_class_name],
|
|
"name": data_class_entry,
|
|
"interval": DEFAULT_INTERVALS[data_class_name],
|
|
NEXT_SCAN: time() + DEFAULT_INTERVALS[data_class_name],
|
|
"kwargs": kwargs,
|
|
"subscriptions": [update_callback],
|
|
}
|
|
|
|
await self.async_fetch_data(
|
|
DATA_CLASSES[data_class_name], data_class_entry, **kwargs
|
|
)
|
|
|
|
self._queue.append(self._data_classes[data_class_entry])
|
|
_LOGGER.debug("Data class %s added", data_class_entry)
|
|
|
|
else:
|
|
self._data_classes[data_class_entry]["subscriptions"].append(
|
|
update_callback
|
|
)
|
|
|
|
async def unregister_data_class(self, data_class_entry, update_callback):
|
|
"""Unregister data class."""
|
|
if update_callback not in self._data_classes[data_class_entry]["subscriptions"]:
|
|
return
|
|
|
|
self._data_classes[data_class_entry]["subscriptions"].remove(update_callback)
|
|
|
|
if not self._data_classes[data_class_entry].get("subscriptions"):
|
|
self._queue.remove(self._data_classes[data_class_entry])
|
|
self._data_classes.pop(data_class_entry)
|
|
_LOGGER.debug("Data class %s removed", data_class_entry)
|
|
|
|
@property
|
|
def webhook(self) -> bool:
|
|
"""Return the webhook state."""
|
|
return self._webhook
|