From 38af44225ebed6cf2a87028e6e8e4f0cd4ae01b5 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 20 Aug 2023 07:49:33 -0500 Subject: [PATCH] Refactor doorbird to avoid using events internally (#98585) --- .coveragerc | 1 + homeassistant/components/doorbird/__init__.py | 64 ++----------------- homeassistant/components/doorbird/camera.py | 3 +- homeassistant/components/doorbird/device.py | 17 +++++ homeassistant/components/doorbird/util.py | 4 +- homeassistant/components/doorbird/view.py | 58 +++++++++++++++++ 6 files changed, 85 insertions(+), 62 deletions(-) create mode 100644 homeassistant/components/doorbird/view.py diff --git a/.coveragerc b/.coveragerc index 02b0cf7a143..58aa8eb52c3 100644 --- a/.coveragerc +++ b/.coveragerc @@ -221,6 +221,7 @@ omit = homeassistant/components/doorbird/device.py homeassistant/components/doorbird/entity.py homeassistant/components/doorbird/util.py + homeassistant/components/doorbird/view.py homeassistant/components/dormakaba_dkey/__init__.py homeassistant/components/dormakaba_dkey/binary_sensor.py homeassistant/components/dormakaba_dkey/entity.py diff --git a/homeassistant/components/doorbird/__init__.py b/homeassistant/components/doorbird/__init__.py index d1ad91bbb2c..bf5fdeb1f60 100644 --- a/homeassistant/components/doorbird/__init__.py +++ b/homeassistant/components/doorbird/__init__.py @@ -5,13 +5,11 @@ from http import HTTPStatus import logging from typing import Any -from aiohttp import web from doorbirdpy import DoorBird import requests import voluptuous as vol from homeassistant.components import persistent_notification -from homeassistant.components.http import HomeAssistantView from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( CONF_HOST, @@ -20,21 +18,20 @@ from homeassistant.const import ( CONF_TOKEN, CONF_USERNAME, ) -from homeassistant.core import Event, HomeAssistant, callback +from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import ConfigEntryNotReady import homeassistant.helpers.config_validation as cv from homeassistant.helpers.typing import ConfigType -from .const import API_URL, CONF_EVENTS, DOMAIN, PLATFORMS +from .const import CONF_EVENTS, DOMAIN, PLATFORMS from .device import ConfiguredDoorBird from .models import DoorBirdData -from .util import get_door_station_by_token +from .view import DoorBirdRequestView _LOGGER = logging.getLogger(__name__) CONF_CUSTOM_URL = "hass_url_override" -RESET_DEVICE_FAVORITES = "doorbird_reset_favorites" DEVICE_SCHEMA = vol.Schema( { @@ -54,29 +51,8 @@ CONFIG_SCHEMA = cv.removed(DOMAIN, raise_if_present=False) async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up the DoorBird component.""" hass.data.setdefault(DOMAIN, {}) - # Provide an endpoint for the door stations to call to trigger events hass.http.register_view(DoorBirdRequestView) - - def _reset_device_favorites_handler(event: Event) -> None: - """Handle clearing favorites on device.""" - if (token := event.data.get("token")) is None: - return - - door_station = get_door_station_by_token(hass, token) - - if door_station is None: - _LOGGER.error("Device not found for provided token") - return - - # Clear webhooks - favorites: dict[str, list[str]] = door_station.device.favorites() - for favorite_type, favorite_ids in favorites.items(): - for favorite_id in favorite_ids: - door_station.device.delete_favorite(favorite_type, favorite_id) - - hass.bus.async_listen(RESET_DEVICE_FAVORITES, _reset_device_favorites_handler) - return True @@ -150,6 +126,7 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def _async_register_events( hass: HomeAssistant, door_station: ConfiguredDoorBird ) -> bool: + """Register events on device.""" try: await hass.async_add_executor_job(door_station.register_events, hass) except requests.exceptions.HTTPError: @@ -190,36 +167,3 @@ def _async_import_options_from_data_if_missing(hass: HomeAssistant, entry: Confi if modified: hass.config_entries.async_update_entry(entry, options=options) - - -class DoorBirdRequestView(HomeAssistantView): - """Provide a page for the device to call.""" - - requires_auth = False - url = API_URL - name = API_URL[1:].replace("/", ":") - extra_urls = [API_URL + "/{event}"] - - async def get(self, request: web.Request, event: str) -> web.Response: - """Respond to requests from the device.""" - hass: HomeAssistant = request.app["hass"] - token: str | None = request.query.get("token") - if token is None or (device := get_door_station_by_token(hass, token)) is None: - return web.Response( - status=HTTPStatus.UNAUTHORIZED, text="Invalid token provided." - ) - - if device: - event_data = device.get_event_data(event) - else: - event_data = {} - - if event == "clear": - hass.bus.async_fire(RESET_DEVICE_FAVORITES, {"token": token}) - - message = f"HTTP Favorites cleared for {device.slug}" - return web.Response(text=message) - - hass.bus.async_fire(f"{DOMAIN}_{event}", event_data) - - return web.Response(text="OK") diff --git a/homeassistant/components/doorbird/camera.py b/homeassistant/components/doorbird/camera.py index 06bdb494463..a29272168d4 100644 --- a/homeassistant/components/doorbird/camera.py +++ b/homeassistant/components/doorbird/camera.py @@ -128,5 +128,6 @@ class DoorBirdCamera(DoorBirdEntity, Camera): """Unsubscribe from events.""" event_to_entity_id = self._door_bird_data.event_entity_ids for event in self._door_station.events: - del event_to_entity_id[event] + # If the clear api was called, the events may not be in the dict + event_to_entity_id.pop(event, None) await super().async_will_remove_from_hass() diff --git a/homeassistant/components/doorbird/device.py b/homeassistant/components/doorbird/device.py index aced0d8723f..3a50700fa37 100644 --- a/homeassistant/components/doorbird/device.py +++ b/homeassistant/components/doorbird/device.py @@ -145,3 +145,20 @@ class ConfiguredDoorBird: "html5_viewer_url": self._device.html5_viewer_url, ATTR_ENTITY_ID: self._event_entity_ids.get(event), } + + +async def async_reset_device_favorites( + hass: HomeAssistant, door_station: ConfiguredDoorBird +) -> None: + """Handle clearing favorites on device.""" + await hass.async_add_executor_job(_reset_device_favorites, door_station) + + +def _reset_device_favorites(door_station: ConfiguredDoorBird) -> None: + """Handle clearing favorites on device.""" + # Clear webhooks + door_bird = door_station.device + favorites: dict[str, list[str]] = door_bird.favorites() + for favorite_type, favorite_ids in favorites.items(): + for favorite_id in favorite_ids: + door_bird.delete_favorite(favorite_type, favorite_id) diff --git a/homeassistant/components/doorbird/util.py b/homeassistant/components/doorbird/util.py index 52c1417a67c..b3b62a4985a 100644 --- a/homeassistant/components/doorbird/util.py +++ b/homeassistant/components/doorbird/util.py @@ -1,5 +1,7 @@ """DoorBird integration utils.""" +from typing import Any + from homeassistant.core import HomeAssistant from .const import DOMAIN @@ -7,7 +9,7 @@ from .device import ConfiguredDoorBird from .models import DoorBirdData -def get_mac_address_from_door_station_info(door_station_info): +def get_mac_address_from_door_station_info(door_station_info: dict[str, Any]) -> str: """Get the mac address depending on the device type.""" return door_station_info.get("PRIMARY_MAC_ADDR", door_station_info["WIFI_MAC_ADDR"]) diff --git a/homeassistant/components/doorbird/view.py b/homeassistant/components/doorbird/view.py new file mode 100644 index 00000000000..fca72d36fc1 --- /dev/null +++ b/homeassistant/components/doorbird/view.py @@ -0,0 +1,58 @@ +"""Support for DoorBird devices.""" +from __future__ import annotations + +from http import HTTPStatus +import logging + +from aiohttp import web + +from homeassistant.components.http import HomeAssistantView +from homeassistant.core import HomeAssistant + +from .const import API_URL, DOMAIN +from .device import async_reset_device_favorites +from .util import get_door_station_by_token + +_LOGGER = logging.getLogger(__name__) + + +class DoorBirdRequestView(HomeAssistantView): + """Provide a page for the device to call.""" + + requires_auth = False + url = API_URL + name = API_URL[1:].replace("/", ":") + extra_urls = [API_URL + "/{event}"] + + async def get(self, request: web.Request, event: str) -> web.Response: + """Respond to requests from the device.""" + hass: HomeAssistant = request.app["hass"] + token: str | None = request.query.get("token") + if ( + token is None + or (door_station := get_door_station_by_token(hass, token)) is None + ): + return web.Response( + status=HTTPStatus.UNAUTHORIZED, text="Invalid token provided." + ) + + if door_station: + event_data = door_station.get_event_data(event) + else: + event_data = {} + + if event == "clear": + await async_reset_device_favorites(hass, door_station) + message = f"HTTP Favorites cleared for {door_station.slug}" + return web.Response(text=message) + + # + # This integration uses a multiple different events. + # It would be a major breaking change to change this to + # a single event at this point. + # + # Do not copy this pattern in the future + # for any new integrations. + # + hass.bus.async_fire(f"{DOMAIN}_{event}", event_data) + return web.Response(text="OK")