mirror of
https://github.com/home-assistant/core.git
synced 2025-07-19 11:17:21 +00:00
Additional doorbird cleanups to prepare for event entities (#98542)
This commit is contained in:
parent
2d4decc9b1
commit
30a88e9e61
@ -14,30 +14,21 @@ from homeassistant.components import persistent_notification
|
|||||||
from homeassistant.components.http import HomeAssistantView
|
from homeassistant.components.http import HomeAssistantView
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_ENTITY_ID,
|
|
||||||
CONF_HOST,
|
CONF_HOST,
|
||||||
CONF_NAME,
|
CONF_NAME,
|
||||||
CONF_PASSWORD,
|
CONF_PASSWORD,
|
||||||
CONF_TOKEN,
|
CONF_TOKEN,
|
||||||
CONF_USERNAME,
|
CONF_USERNAME,
|
||||||
)
|
)
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import Event, HomeAssistant, callback
|
||||||
from homeassistant.exceptions import ConfigEntryNotReady
|
from homeassistant.exceptions import ConfigEntryNotReady
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
from homeassistant.helpers.typing import ConfigType
|
from homeassistant.helpers.typing import ConfigType
|
||||||
|
|
||||||
from .const import (
|
from .const import API_URL, CONF_EVENTS, DOMAIN, PLATFORMS
|
||||||
API_URL,
|
|
||||||
CONF_EVENTS,
|
|
||||||
DOMAIN,
|
|
||||||
DOOR_STATION,
|
|
||||||
DOOR_STATION_EVENT_ENTITY_IDS,
|
|
||||||
DOOR_STATION_INFO,
|
|
||||||
PLATFORMS,
|
|
||||||
UNDO_UPDATE_LISTENER,
|
|
||||||
)
|
|
||||||
from .device import ConfiguredDoorBird
|
from .device import ConfiguredDoorBird
|
||||||
from .util import get_doorstation_by_token
|
from .models import DoorBirdData
|
||||||
|
from .util import get_door_station_by_token
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -64,26 +55,25 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
|||||||
"""Set up the DoorBird component."""
|
"""Set up the DoorBird component."""
|
||||||
hass.data.setdefault(DOMAIN, {})
|
hass.data.setdefault(DOMAIN, {})
|
||||||
|
|
||||||
# Provide an endpoint for the doorstations to call to trigger events
|
# Provide an endpoint for the door stations to call to trigger events
|
||||||
hass.http.register_view(DoorBirdRequestView)
|
hass.http.register_view(DoorBirdRequestView)
|
||||||
|
|
||||||
def _reset_device_favorites_handler(event):
|
def _reset_device_favorites_handler(event: Event) -> None:
|
||||||
"""Handle clearing favorites on device."""
|
"""Handle clearing favorites on device."""
|
||||||
if (token := event.data.get("token")) is None:
|
if (token := event.data.get("token")) is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
doorstation = get_doorstation_by_token(hass, token)
|
door_station = get_door_station_by_token(hass, token)
|
||||||
|
|
||||||
if doorstation is None:
|
if door_station is None:
|
||||||
_LOGGER.error("Device not found for provided token")
|
_LOGGER.error("Device not found for provided token")
|
||||||
return
|
return
|
||||||
|
|
||||||
# Clear webhooks
|
# Clear webhooks
|
||||||
favorites = doorstation.device.favorites()
|
favorites: dict[str, list[str]] = door_station.device.favorites()
|
||||||
|
for favorite_type, favorite_ids in favorites.items():
|
||||||
for favorite_type in favorites:
|
for favorite_id in favorite_ids:
|
||||||
for favorite_id in favorites[favorite_type]:
|
door_station.device.delete_favorite(favorite_type, favorite_id)
|
||||||
doorstation.device.delete_favorite(favorite_type, favorite_id)
|
|
||||||
|
|
||||||
hass.bus.async_listen(RESET_DEVICE_FAVORITES, _reset_device_favorites_handler)
|
hass.bus.async_listen(RESET_DEVICE_FAVORITES, _reset_device_favorites_handler)
|
||||||
|
|
||||||
@ -95,17 +85,16 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
|
|
||||||
_async_import_options_from_data_if_missing(hass, entry)
|
_async_import_options_from_data_if_missing(hass, entry)
|
||||||
|
|
||||||
doorstation_config = entry.data
|
door_station_config = entry.data
|
||||||
doorstation_options = entry.options
|
|
||||||
config_entry_id = entry.entry_id
|
config_entry_id = entry.entry_id
|
||||||
|
|
||||||
device_ip = doorstation_config[CONF_HOST]
|
device_ip = door_station_config[CONF_HOST]
|
||||||
username = doorstation_config[CONF_USERNAME]
|
username = door_station_config[CONF_USERNAME]
|
||||||
password = doorstation_config[CONF_PASSWORD]
|
password = door_station_config[CONF_PASSWORD]
|
||||||
|
|
||||||
device = DoorBird(device_ip, username, password)
|
device = DoorBird(device_ip, username, password)
|
||||||
try:
|
try:
|
||||||
status, info = await hass.async_add_executor_job(_init_doorbird_device, device)
|
status, info = await hass.async_add_executor_job(_init_door_bird_device, device)
|
||||||
except requests.exceptions.HTTPError as err:
|
except requests.exceptions.HTTPError as err:
|
||||||
if err.response.status_code == HTTPStatus.UNAUTHORIZED:
|
if err.response.status_code == HTTPStatus.UNAUTHORIZED:
|
||||||
_LOGGER.error(
|
_LOGGER.error(
|
||||||
@ -126,50 +115,43 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
)
|
)
|
||||||
raise ConfigEntryNotReady
|
raise ConfigEntryNotReady
|
||||||
|
|
||||||
token: str = doorstation_config.get(CONF_TOKEN, config_entry_id)
|
token: str = door_station_config.get(CONF_TOKEN, config_entry_id)
|
||||||
custom_url: str | None = doorstation_config.get(CONF_CUSTOM_URL)
|
custom_url: str | None = door_station_config.get(CONF_CUSTOM_URL)
|
||||||
name: str | None = doorstation_config.get(CONF_NAME)
|
name: str | None = door_station_config.get(CONF_NAME)
|
||||||
events = doorstation_options.get(CONF_EVENTS, [])
|
events = entry.options.get(CONF_EVENTS, [])
|
||||||
doorstation = ConfiguredDoorBird(device, name, custom_url, token)
|
event_entity_ids: dict[str, str] = {}
|
||||||
doorstation.update_events(events)
|
door_station = ConfiguredDoorBird(device, name, custom_url, token, event_entity_ids)
|
||||||
|
door_bird_data = DoorBirdData(door_station, info, event_entity_ids)
|
||||||
|
door_station.update_events(events)
|
||||||
# Subscribe to doorbell or motion events
|
# Subscribe to doorbell or motion events
|
||||||
if not await _async_register_events(hass, doorstation):
|
if not await _async_register_events(hass, door_station):
|
||||||
raise ConfigEntryNotReady
|
raise ConfigEntryNotReady
|
||||||
|
|
||||||
undo_listener = entry.add_update_listener(_update_listener)
|
entry.async_on_unload(entry.add_update_listener(_update_listener))
|
||||||
|
hass.data[DOMAIN][config_entry_id] = door_bird_data
|
||||||
hass.data[DOMAIN][config_entry_id] = {
|
|
||||||
DOOR_STATION: doorstation,
|
|
||||||
DOOR_STATION_INFO: info,
|
|
||||||
UNDO_UPDATE_LISTENER: undo_listener,
|
|
||||||
}
|
|
||||||
|
|
||||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def _init_doorbird_device(device: DoorBird) -> tuple[tuple[bool, int], dict[str, Any]]:
|
def _init_door_bird_device(device: DoorBird) -> tuple[tuple[bool, int], dict[str, Any]]:
|
||||||
|
"""Verify we can connect to the device and return the status."""
|
||||||
return device.ready(), device.info()
|
return device.ready(), device.info()
|
||||||
|
|
||||||
|
|
||||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
"""Unload a config entry."""
|
"""Unload a config entry."""
|
||||||
|
data: dict[str, DoorBirdData] = hass.data[DOMAIN]
|
||||||
hass.data[DOMAIN][entry.entry_id][UNDO_UPDATE_LISTENER]()
|
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
|
||||||
|
data.pop(entry.entry_id)
|
||||||
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
|
||||||
if unload_ok:
|
|
||||||
hass.data[DOMAIN].pop(entry.entry_id)
|
|
||||||
|
|
||||||
return unload_ok
|
return unload_ok
|
||||||
|
|
||||||
|
|
||||||
async def _async_register_events(
|
async def _async_register_events(
|
||||||
hass: HomeAssistant, doorstation: ConfiguredDoorBird
|
hass: HomeAssistant, door_station: ConfiguredDoorBird
|
||||||
) -> bool:
|
) -> bool:
|
||||||
try:
|
try:
|
||||||
await hass.async_add_executor_job(doorstation.register_events, hass)
|
await hass.async_add_executor_job(door_station.register_events, hass)
|
||||||
except requests.exceptions.HTTPError:
|
except requests.exceptions.HTTPError:
|
||||||
persistent_notification.async_create(
|
persistent_notification.async_create(
|
||||||
hass,
|
hass,
|
||||||
@ -190,10 +172,11 @@ async def _async_register_events(
|
|||||||
async def _update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None:
|
async def _update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None:
|
||||||
"""Handle options update."""
|
"""Handle options update."""
|
||||||
config_entry_id = entry.entry_id
|
config_entry_id = entry.entry_id
|
||||||
doorstation = hass.data[DOMAIN][config_entry_id][DOOR_STATION]
|
data: DoorBirdData = hass.data[DOMAIN][config_entry_id]
|
||||||
doorstation.update_events(entry.options[CONF_EVENTS])
|
door_station = data.door_station
|
||||||
|
door_station.update_events(entry.options[CONF_EVENTS])
|
||||||
# Subscribe to doorbell or motion events
|
# Subscribe to doorbell or motion events
|
||||||
await _async_register_events(hass, doorstation)
|
await _async_register_events(hass, door_station)
|
||||||
|
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
@ -217,21 +200,17 @@ class DoorBirdRequestView(HomeAssistantView):
|
|||||||
name = API_URL[1:].replace("/", ":")
|
name = API_URL[1:].replace("/", ":")
|
||||||
extra_urls = [API_URL + "/{event}"]
|
extra_urls = [API_URL + "/{event}"]
|
||||||
|
|
||||||
async def get(self, request, event):
|
async def get(self, request: web.Request, event: str) -> web.Response:
|
||||||
"""Respond to requests from the device."""
|
"""Respond to requests from the device."""
|
||||||
hass = request.app["hass"]
|
hass: HomeAssistant = request.app["hass"]
|
||||||
|
token: str | None = request.query.get("token")
|
||||||
token = request.query.get("token")
|
if token is None or (device := get_door_station_by_token(hass, token)) is None:
|
||||||
|
|
||||||
device = get_doorstation_by_token(hass, token)
|
|
||||||
|
|
||||||
if device is None:
|
|
||||||
return web.Response(
|
return web.Response(
|
||||||
status=HTTPStatus.UNAUTHORIZED, text="Invalid token provided."
|
status=HTTPStatus.UNAUTHORIZED, text="Invalid token provided."
|
||||||
)
|
)
|
||||||
|
|
||||||
if device:
|
if device:
|
||||||
event_data = device.get_event_data()
|
event_data = device.get_event_data(event)
|
||||||
else:
|
else:
|
||||||
event_data = {}
|
event_data = {}
|
||||||
|
|
||||||
@ -241,10 +220,6 @@ class DoorBirdRequestView(HomeAssistantView):
|
|||||||
message = f"HTTP Favorites cleared for {device.slug}"
|
message = f"HTTP Favorites cleared for {device.slug}"
|
||||||
return web.Response(text=message)
|
return web.Response(text=message)
|
||||||
|
|
||||||
event_data[ATTR_ENTITY_ID] = hass.data[DOMAIN][
|
|
||||||
DOOR_STATION_EVENT_ENTITY_IDS
|
|
||||||
].get(event)
|
|
||||||
|
|
||||||
hass.bus.async_fire(f"{DOMAIN}_{event}", event_data)
|
hass.bus.async_fire(f"{DOMAIN}_{event}", event_data)
|
||||||
|
|
||||||
return web.Response(text="OK")
|
return web.Response(text="OK")
|
||||||
|
@ -10,8 +10,9 @@ from homeassistant.config_entries import ConfigEntry
|
|||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
|
||||||
from .const import DOMAIN, DOOR_STATION, DOOR_STATION_INFO
|
from .const import DOMAIN
|
||||||
from .entity import DoorBirdEntity
|
from .entity import DoorBirdEntity
|
||||||
|
from .models import DoorBirdData
|
||||||
|
|
||||||
IR_RELAY = "__ir_light__"
|
IR_RELAY = "__ir_light__"
|
||||||
|
|
||||||
@ -49,20 +50,14 @@ async def async_setup_entry(
|
|||||||
) -> None:
|
) -> None:
|
||||||
"""Set up the DoorBird button platform."""
|
"""Set up the DoorBird button platform."""
|
||||||
config_entry_id = config_entry.entry_id
|
config_entry_id = config_entry.entry_id
|
||||||
|
door_bird_data: DoorBirdData = hass.data[DOMAIN][config_entry_id]
|
||||||
data = hass.data[DOMAIN][config_entry_id]
|
relays = door_bird_data.door_station_info["RELAYS"]
|
||||||
doorstation = data[DOOR_STATION]
|
|
||||||
doorstation_info = data[DOOR_STATION_INFO]
|
|
||||||
|
|
||||||
relays = doorstation_info["RELAYS"]
|
|
||||||
|
|
||||||
entities = [
|
entities = [
|
||||||
DoorBirdButton(doorstation, doorstation_info, relay, RELAY_ENTITY_DESCRIPTION)
|
DoorBirdButton(door_bird_data, relay, RELAY_ENTITY_DESCRIPTION)
|
||||||
for relay in relays
|
for relay in relays
|
||||||
]
|
]
|
||||||
entities.append(
|
entities.append(DoorBirdButton(door_bird_data, IR_RELAY, IR_ENTITY_DESCRIPTION))
|
||||||
DoorBirdButton(doorstation, doorstation_info, IR_RELAY, IR_ENTITY_DESCRIPTION)
|
|
||||||
)
|
|
||||||
|
|
||||||
async_add_entities(entities)
|
async_add_entities(entities)
|
||||||
|
|
||||||
@ -74,16 +69,14 @@ class DoorBirdButton(DoorBirdEntity, ButtonEntity):
|
|||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
doorstation: DoorBird,
|
door_bird_data: DoorBirdData,
|
||||||
doorstation_info,
|
|
||||||
relay: str,
|
relay: str,
|
||||||
entity_description: DoorbirdButtonEntityDescription,
|
entity_description: DoorbirdButtonEntityDescription,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize a relay in a DoorBird device."""
|
"""Initialize a relay in a DoorBird device."""
|
||||||
super().__init__(doorstation, doorstation_info)
|
super().__init__(door_bird_data)
|
||||||
self._relay = relay
|
self._relay = relay
|
||||||
self.entity_description = entity_description
|
self.entity_description = entity_description
|
||||||
|
|
||||||
if self._relay == IR_RELAY:
|
if self._relay == IR_RELAY:
|
||||||
self._attr_name = "IR"
|
self._attr_name = "IR"
|
||||||
else:
|
else:
|
||||||
@ -92,4 +85,4 @@ class DoorBirdButton(DoorBirdEntity, ButtonEntity):
|
|||||||
|
|
||||||
def press(self) -> None:
|
def press(self) -> None:
|
||||||
"""Power the relay."""
|
"""Power the relay."""
|
||||||
self.entity_description.press_action(self._doorstation.device, self._relay)
|
self.entity_description.press_action(self._door_station.device, self._relay)
|
||||||
|
@ -14,13 +14,9 @@ from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
|||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
import homeassistant.util.dt as dt_util
|
import homeassistant.util.dt as dt_util
|
||||||
|
|
||||||
from .const import (
|
from .const import DOMAIN
|
||||||
DOMAIN,
|
|
||||||
DOOR_STATION,
|
|
||||||
DOOR_STATION_EVENT_ENTITY_IDS,
|
|
||||||
DOOR_STATION_INFO,
|
|
||||||
)
|
|
||||||
from .entity import DoorBirdEntity
|
from .entity import DoorBirdEntity
|
||||||
|
from .models import DoorBirdData
|
||||||
|
|
||||||
_LAST_VISITOR_INTERVAL = datetime.timedelta(minutes=2)
|
_LAST_VISITOR_INTERVAL = datetime.timedelta(minutes=2)
|
||||||
_LAST_MOTION_INTERVAL = datetime.timedelta(seconds=30)
|
_LAST_MOTION_INTERVAL = datetime.timedelta(seconds=30)
|
||||||
@ -36,39 +32,31 @@ async def async_setup_entry(
|
|||||||
) -> None:
|
) -> None:
|
||||||
"""Set up the DoorBird camera platform."""
|
"""Set up the DoorBird camera platform."""
|
||||||
config_entry_id = config_entry.entry_id
|
config_entry_id = config_entry.entry_id
|
||||||
config_data = hass.data[DOMAIN][config_entry_id]
|
door_bird_data: DoorBirdData = hass.data[DOMAIN][config_entry_id]
|
||||||
doorstation = config_data[DOOR_STATION]
|
device = door_bird_data.door_station.device
|
||||||
doorstation_info = config_data[DOOR_STATION_INFO]
|
|
||||||
device = doorstation.device
|
|
||||||
|
|
||||||
async_add_entities(
|
async_add_entities(
|
||||||
[
|
[
|
||||||
DoorBirdCamera(
|
DoorBirdCamera(
|
||||||
doorstation,
|
door_bird_data,
|
||||||
doorstation_info,
|
|
||||||
device.live_image_url,
|
device.live_image_url,
|
||||||
"live",
|
"live",
|
||||||
"live",
|
"live",
|
||||||
doorstation.doorstation_events,
|
|
||||||
_LIVE_INTERVAL,
|
_LIVE_INTERVAL,
|
||||||
device.rtsp_live_video_url,
|
device.rtsp_live_video_url,
|
||||||
),
|
),
|
||||||
DoorBirdCamera(
|
DoorBirdCamera(
|
||||||
doorstation,
|
door_bird_data,
|
||||||
doorstation_info,
|
|
||||||
device.history_image_url(1, "doorbell"),
|
device.history_image_url(1, "doorbell"),
|
||||||
"last_ring",
|
"last_ring",
|
||||||
"last_ring",
|
"last_ring",
|
||||||
[],
|
|
||||||
_LAST_VISITOR_INTERVAL,
|
_LAST_VISITOR_INTERVAL,
|
||||||
),
|
),
|
||||||
DoorBirdCamera(
|
DoorBirdCamera(
|
||||||
doorstation,
|
door_bird_data,
|
||||||
doorstation_info,
|
|
||||||
device.history_image_url(1, "motionsensor"),
|
device.history_image_url(1, "motionsensor"),
|
||||||
"last_motion",
|
"last_motion",
|
||||||
"last_motion",
|
"last_motion",
|
||||||
[],
|
|
||||||
_LAST_MOTION_INTERVAL,
|
_LAST_MOTION_INTERVAL,
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
@ -80,17 +68,15 @@ class DoorBirdCamera(DoorBirdEntity, Camera):
|
|||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
doorstation,
|
door_bird_data: DoorBirdData,
|
||||||
doorstation_info,
|
url: str,
|
||||||
url,
|
camera_id: str,
|
||||||
camera_id,
|
translation_key: str,
|
||||||
translation_key,
|
interval: datetime.timedelta,
|
||||||
doorstation_events,
|
stream_url: str | None = None,
|
||||||
interval,
|
|
||||||
stream_url=None,
|
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize the camera on a DoorBird device."""
|
"""Initialize the camera on a DoorBird device."""
|
||||||
super().__init__(doorstation, doorstation_info)
|
super().__init__(door_bird_data)
|
||||||
self._url = url
|
self._url = url
|
||||||
self._stream_url = stream_url
|
self._stream_url = stream_url
|
||||||
self._attr_translation_key = translation_key
|
self._attr_translation_key = translation_key
|
||||||
@ -100,7 +86,6 @@ class DoorBirdCamera(DoorBirdEntity, Camera):
|
|||||||
self._interval = interval
|
self._interval = interval
|
||||||
self._last_update = datetime.datetime.min
|
self._last_update = datetime.datetime.min
|
||||||
self._attr_unique_id = f"{self._mac_addr}_{camera_id}"
|
self._attr_unique_id = f"{self._mac_addr}_{camera_id}"
|
||||||
self._doorstation_events = doorstation_events
|
|
||||||
|
|
||||||
async def stream_source(self):
|
async def stream_source(self):
|
||||||
"""Return the stream source."""
|
"""Return the stream source."""
|
||||||
@ -133,19 +118,15 @@ class DoorBirdCamera(DoorBirdEntity, Camera):
|
|||||||
return self._last_image
|
return self._last_image
|
||||||
|
|
||||||
async def async_added_to_hass(self) -> None:
|
async def async_added_to_hass(self) -> None:
|
||||||
"""Add callback after being added to hass.
|
"""Subscribe to events."""
|
||||||
|
await super().async_added_to_hass()
|
||||||
Registers entity_id map for the logbook
|
event_to_entity_id = self._door_bird_data.event_entity_ids
|
||||||
"""
|
for event in self._door_station.events:
|
||||||
event_to_entity_id = self.hass.data[DOMAIN].setdefault(
|
|
||||||
DOOR_STATION_EVENT_ENTITY_IDS, {}
|
|
||||||
)
|
|
||||||
for event in self._doorstation_events:
|
|
||||||
event_to_entity_id[event] = self.entity_id
|
event_to_entity_id[event] = self.entity_id
|
||||||
|
|
||||||
async def will_remove_from_hass(self):
|
async def async_will_remove_from_hass(self) -> None:
|
||||||
"""Unregister entity_id map for the logbook."""
|
"""Unsubscribe from events."""
|
||||||
event_to_entity_id = self.hass.data[DOMAIN][DOOR_STATION_EVENT_ENTITY_IDS]
|
event_to_entity_id = self._door_bird_data.event_entity_ids
|
||||||
for event in self._doorstation_events:
|
for event in self._door_station.events:
|
||||||
if event in event_to_entity_id:
|
del event_to_entity_id[event]
|
||||||
del event_to_entity_id[event]
|
await super().async_will_remove_from_hass()
|
||||||
|
@ -4,6 +4,7 @@ from __future__ import annotations
|
|||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
from ipaddress import ip_address
|
from ipaddress import ip_address
|
||||||
import logging
|
import logging
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from doorbirdpy import DoorBird
|
from doorbirdpy import DoorBird
|
||||||
import requests
|
import requests
|
||||||
@ -12,12 +13,12 @@ import voluptuous as vol
|
|||||||
from homeassistant import config_entries, core, exceptions
|
from homeassistant import config_entries, core, exceptions
|
||||||
from homeassistant.components import zeroconf
|
from homeassistant.components import zeroconf
|
||||||
from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_USERNAME
|
from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_USERNAME
|
||||||
from homeassistant.core import callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.data_entry_flow import FlowResult
|
from homeassistant.data_entry_flow import FlowResult
|
||||||
from homeassistant.util.network import is_ipv4_address, is_link_local
|
from homeassistant.util.network import is_ipv4_address, is_link_local
|
||||||
|
|
||||||
from .const import CONF_EVENTS, DOMAIN, DOORBIRD_OUI
|
from .const import CONF_EVENTS, DOMAIN, DOORBIRD_OUI
|
||||||
from .util import get_mac_address_from_doorstation_info
|
from .util import get_mac_address_from_door_station_info
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -33,7 +34,7 @@ def _schema_with_defaults(host=None, name=None):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _check_device(device):
|
def _check_device(device: DoorBird) -> tuple[tuple[bool, int], dict[str, Any]]:
|
||||||
"""Verify we can connect to the device and return the status."""
|
"""Verify we can connect to the device and return the status."""
|
||||||
return device.ready(), device.info()
|
return device.ready(), device.info()
|
||||||
|
|
||||||
@ -53,13 +54,13 @@ async def validate_input(hass: core.HomeAssistant, data):
|
|||||||
if not status[0]:
|
if not status[0]:
|
||||||
raise CannotConnect
|
raise CannotConnect
|
||||||
|
|
||||||
mac_addr = get_mac_address_from_doorstation_info(info)
|
mac_addr = get_mac_address_from_door_station_info(info)
|
||||||
|
|
||||||
# Return info that you want to store in the config entry.
|
# Return info that you want to store in the config entry.
|
||||||
return {"title": data[CONF_HOST], "mac_addr": mac_addr}
|
return {"title": data[CONF_HOST], "mac_addr": mac_addr}
|
||||||
|
|
||||||
|
|
||||||
async def async_verify_supported_device(hass, host):
|
async def async_verify_supported_device(hass: HomeAssistant, host: str) -> bool:
|
||||||
"""Verify the doorbell state endpoint returns a 401."""
|
"""Verify the doorbell state endpoint returns a 401."""
|
||||||
device = DoorBird(host, "", "")
|
device = DoorBird(host, "", "")
|
||||||
try:
|
try:
|
||||||
|
@ -6,6 +6,7 @@ from typing import Any
|
|||||||
|
|
||||||
from doorbirdpy import DoorBird
|
from doorbirdpy import DoorBird
|
||||||
|
|
||||||
|
from homeassistant.const import ATTR_ENTITY_ID
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.network import get_url
|
from homeassistant.helpers.network import get_url
|
||||||
from homeassistant.util import dt as dt_util, slugify
|
from homeassistant.util import dt as dt_util, slugify
|
||||||
@ -19,20 +20,28 @@ class ConfiguredDoorBird:
|
|||||||
"""Attach additional information to pass along with configured device."""
|
"""Attach additional information to pass along with configured device."""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, device: DoorBird, name: str | None, custom_url: str | None, token: str
|
self,
|
||||||
|
device: DoorBird,
|
||||||
|
name: str | None,
|
||||||
|
custom_url: str | None,
|
||||||
|
token: str,
|
||||||
|
event_entity_ids: dict[str, str],
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize configured device."""
|
"""Initialize configured device."""
|
||||||
self._name = name
|
self._name = name
|
||||||
self._device = device
|
self._device = device
|
||||||
self._custom_url = custom_url
|
self._custom_url = custom_url
|
||||||
self.events = None
|
|
||||||
self.doorstation_events = None
|
|
||||||
self._token = token
|
self._token = token
|
||||||
|
self._event_entity_ids = event_entity_ids
|
||||||
|
self.events: list[str] = []
|
||||||
|
self.door_station_events: list[str] = []
|
||||||
|
|
||||||
def update_events(self, events):
|
def update_events(self, events: list[str]) -> None:
|
||||||
"""Update the doorbird events."""
|
"""Update the doorbird events."""
|
||||||
self.events = events
|
self.events = events
|
||||||
self.doorstation_events = [self._get_event_name(event) for event in self.events]
|
self.door_station_events = [
|
||||||
|
self._get_event_name(event) for event in self.events
|
||||||
|
]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self) -> str | None:
|
def name(self) -> str | None:
|
||||||
@ -63,12 +72,12 @@ class ConfiguredDoorBird:
|
|||||||
if self.custom_url is not None:
|
if self.custom_url is not None:
|
||||||
hass_url = self.custom_url
|
hass_url = self.custom_url
|
||||||
|
|
||||||
if not self.doorstation_events:
|
if not self.door_station_events:
|
||||||
# User may not have permission to get the favorites
|
# User may not have permission to get the favorites
|
||||||
return
|
return
|
||||||
|
|
||||||
favorites = self.device.favorites()
|
favorites = self.device.favorites()
|
||||||
for event in self.doorstation_events:
|
for event in self.door_station_events:
|
||||||
if self._register_event(hass_url, event, favs=favorites):
|
if self._register_event(hass_url, event, favs=favorites):
|
||||||
_LOGGER.info(
|
_LOGGER.info(
|
||||||
"Successfully registered URL for %s on %s", event, self.name
|
"Successfully registered URL for %s on %s", event, self.name
|
||||||
@ -126,7 +135,7 @@ class ConfiguredDoorBird:
|
|||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def get_event_data(self) -> dict[str, str]:
|
def get_event_data(self, event: str) -> dict[str, str | None]:
|
||||||
"""Get data to pass along with HA event."""
|
"""Get data to pass along with HA event."""
|
||||||
return {
|
return {
|
||||||
"timestamp": dt_util.utcnow().isoformat(),
|
"timestamp": dt_util.utcnow().isoformat(),
|
||||||
@ -134,4 +143,5 @@ class ConfiguredDoorBird:
|
|||||||
"live_image_url": self._device.live_image_url,
|
"live_image_url": self._device.live_image_url,
|
||||||
"rtsp_live_video_url": self._device.rtsp_live_video_url,
|
"rtsp_live_video_url": self._device.rtsp_live_video_url,
|
||||||
"html5_viewer_url": self._device.html5_viewer_url,
|
"html5_viewer_url": self._device.html5_viewer_url,
|
||||||
|
ATTR_ENTITY_ID: self._event_entity_ids.get(event),
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
"""The DoorBird integration base entity."""
|
"""The DoorBird integration base entity."""
|
||||||
|
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
from homeassistant.helpers import device_registry as dr
|
from homeassistant.helpers import device_registry as dr
|
||||||
from homeassistant.helpers.device_registry import DeviceInfo
|
from homeassistant.helpers.device_registry import DeviceInfo
|
||||||
@ -12,8 +11,8 @@ from .const import (
|
|||||||
DOORBIRD_INFO_KEY_FIRMWARE,
|
DOORBIRD_INFO_KEY_FIRMWARE,
|
||||||
MANUFACTURER,
|
MANUFACTURER,
|
||||||
)
|
)
|
||||||
from .device import ConfiguredDoorBird
|
from .models import DoorBirdData
|
||||||
from .util import get_mac_address_from_doorstation_info
|
from .util import get_mac_address_from_door_station_info
|
||||||
|
|
||||||
|
|
||||||
class DoorBirdEntity(Entity):
|
class DoorBirdEntity(Entity):
|
||||||
@ -21,21 +20,20 @@ class DoorBirdEntity(Entity):
|
|||||||
|
|
||||||
_attr_has_entity_name = True
|
_attr_has_entity_name = True
|
||||||
|
|
||||||
def __init__(
|
def __init__(self, door_bird_data: DoorBirdData) -> None:
|
||||||
self, doorstation: ConfiguredDoorBird, doorstation_info: dict[str, Any]
|
|
||||||
) -> None:
|
|
||||||
"""Initialize the entity."""
|
"""Initialize the entity."""
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self._doorstation = doorstation
|
self._door_bird_data = door_bird_data
|
||||||
self._mac_addr = get_mac_address_from_doorstation_info(doorstation_info)
|
self._door_station = door_bird_data.door_station
|
||||||
|
door_station_info = door_bird_data.door_station_info
|
||||||
firmware = doorstation_info[DOORBIRD_INFO_KEY_FIRMWARE]
|
self._mac_addr = get_mac_address_from_door_station_info(door_station_info)
|
||||||
firmware_build = doorstation_info[DOORBIRD_INFO_KEY_BUILD_NUMBER]
|
firmware = door_station_info[DOORBIRD_INFO_KEY_FIRMWARE]
|
||||||
|
firmware_build = door_station_info[DOORBIRD_INFO_KEY_BUILD_NUMBER]
|
||||||
self._attr_device_info = DeviceInfo(
|
self._attr_device_info = DeviceInfo(
|
||||||
configuration_url="https://webadmin.doorbird.com/",
|
configuration_url="https://webadmin.doorbird.com/",
|
||||||
connections={(dr.CONNECTION_NETWORK_MAC, self._mac_addr)},
|
connections={(dr.CONNECTION_NETWORK_MAC, self._mac_addr)},
|
||||||
manufacturer=MANUFACTURER,
|
manufacturer=MANUFACTURER,
|
||||||
model=doorstation_info[DOORBIRD_INFO_KEY_DEVICE_TYPE],
|
model=door_station_info[DOORBIRD_INFO_KEY_DEVICE_TYPE],
|
||||||
name=self._doorstation.name,
|
name=self._door_station.name,
|
||||||
sw_version=f"{firmware} {firmware_build}",
|
sw_version=f"{firmware} {firmware_build}",
|
||||||
)
|
)
|
||||||
|
@ -1,43 +1,35 @@
|
|||||||
"""Describe logbook events."""
|
"""Describe logbook events."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
from homeassistant.components.logbook import (
|
from homeassistant.components.logbook import (
|
||||||
LOGBOOK_ENTRY_ENTITY_ID,
|
LOGBOOK_ENTRY_ENTITY_ID,
|
||||||
LOGBOOK_ENTRY_MESSAGE,
|
LOGBOOK_ENTRY_MESSAGE,
|
||||||
LOGBOOK_ENTRY_NAME,
|
LOGBOOK_ENTRY_NAME,
|
||||||
)
|
)
|
||||||
from homeassistant.const import ATTR_ENTITY_ID
|
from homeassistant.const import ATTR_ENTITY_ID
|
||||||
from homeassistant.core import callback
|
from homeassistant.core import Event, HomeAssistant, callback
|
||||||
|
|
||||||
from .const import DOMAIN, DOOR_STATION, DOOR_STATION_EVENT_ENTITY_IDS
|
from .const import DOMAIN
|
||||||
|
from .models import DoorBirdData
|
||||||
|
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def async_describe_events(hass, async_describe_event):
|
def async_describe_events(hass: HomeAssistant, async_describe_event):
|
||||||
"""Describe logbook events."""
|
"""Describe logbook events."""
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def async_describe_logbook_event(event):
|
def async_describe_logbook_event(event: Event):
|
||||||
"""Describe a logbook event."""
|
"""Describe a logbook event."""
|
||||||
doorbird_event = event.event_type.split("_", 1)[1]
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
LOGBOOK_ENTRY_NAME: "Doorbird",
|
LOGBOOK_ENTRY_NAME: "Doorbird",
|
||||||
LOGBOOK_ENTRY_MESSAGE: f"Event {event.event_type} was fired",
|
LOGBOOK_ENTRY_MESSAGE: f"Event {event.event_type} was fired",
|
||||||
LOGBOOK_ENTRY_ENTITY_ID: hass.data[DOMAIN][
|
# Database entries before Jun 25th 2020 will not have an entity ID
|
||||||
DOOR_STATION_EVENT_ENTITY_IDS
|
LOGBOOK_ENTRY_ENTITY_ID: event.data.get(ATTR_ENTITY_ID),
|
||||||
].get(doorbird_event, event.data.get(ATTR_ENTITY_ID)),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
domain_data: dict[str, Any] = hass.data[DOMAIN]
|
domain_data: dict[str, DoorBirdData] = hass.data[DOMAIN]
|
||||||
|
|
||||||
for data in domain_data.values():
|
for data in domain_data.values():
|
||||||
if DOOR_STATION not in data:
|
for event in data.door_station.door_station_events:
|
||||||
# We need to skip door_station_event_entity_ids
|
|
||||||
continue
|
|
||||||
for event in data[DOOR_STATION].doorstation_events:
|
|
||||||
async_describe_event(
|
async_describe_event(
|
||||||
DOMAIN, f"{DOMAIN}_{event}", async_describe_logbook_event
|
DOMAIN, f"{DOMAIN}_{event}", async_describe_logbook_event
|
||||||
)
|
)
|
||||||
|
26
homeassistant/components/doorbird/models.py
Normal file
26
homeassistant/components/doorbird/models.py
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
"""The doorbird integration models."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from .device import ConfiguredDoorBird
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class DoorBirdData:
|
||||||
|
"""Data for the doorbird integration."""
|
||||||
|
|
||||||
|
door_station: ConfiguredDoorBird
|
||||||
|
door_station_info: dict[str, Any]
|
||||||
|
|
||||||
|
#
|
||||||
|
# This integration uses a different event for
|
||||||
|
# each entity id. 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.
|
||||||
|
#
|
||||||
|
event_entity_ids: dict[str, str]
|
@ -2,50 +2,23 @@
|
|||||||
|
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
|
||||||
from .const import DOMAIN, DOOR_STATION
|
from .const import DOMAIN
|
||||||
from .device import ConfiguredDoorBird
|
from .device import ConfiguredDoorBird
|
||||||
|
from .models import DoorBirdData
|
||||||
|
|
||||||
|
|
||||||
def get_mac_address_from_doorstation_info(doorstation_info):
|
def get_mac_address_from_door_station_info(door_station_info):
|
||||||
"""Get the mac address depending on the device type."""
|
"""Get the mac address depending on the device type."""
|
||||||
if "PRIMARY_MAC_ADDR" in doorstation_info:
|
return door_station_info.get("PRIMARY_MAC_ADDR", door_station_info["WIFI_MAC_ADDR"])
|
||||||
return doorstation_info["PRIMARY_MAC_ADDR"]
|
|
||||||
return doorstation_info["WIFI_MAC_ADDR"]
|
|
||||||
|
|
||||||
|
|
||||||
def get_doorstation_by_token(
|
def get_door_station_by_token(
|
||||||
hass: HomeAssistant, token: str
|
hass: HomeAssistant, token: str
|
||||||
) -> ConfiguredDoorBird | None:
|
) -> ConfiguredDoorBird | None:
|
||||||
"""Get doorstation by token."""
|
"""Get door station by token."""
|
||||||
return _get_doorstation_by_attr(hass, "token", token)
|
domain_data: dict[str, DoorBirdData] = hass.data[DOMAIN]
|
||||||
|
for data in domain_data.values():
|
||||||
|
door_station = data.door_station
|
||||||
def get_doorstation_by_slug(
|
if door_station.token == token:
|
||||||
hass: HomeAssistant, slug: str
|
return door_station
|
||||||
) -> ConfiguredDoorBird | None:
|
|
||||||
"""Get doorstation by slug."""
|
|
||||||
return _get_doorstation_by_attr(hass, "slug", slug)
|
|
||||||
|
|
||||||
|
|
||||||
def _get_doorstation_by_attr(
|
|
||||||
hass: HomeAssistant, attr: str, val: str
|
|
||||||
) -> ConfiguredDoorBird | None:
|
|
||||||
for entry in hass.data[DOMAIN].values():
|
|
||||||
if DOOR_STATION not in entry:
|
|
||||||
continue
|
|
||||||
|
|
||||||
doorstation = entry[DOOR_STATION]
|
|
||||||
|
|
||||||
if getattr(doorstation, attr) == val:
|
|
||||||
return doorstation
|
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def get_all_doorstations(hass: HomeAssistant) -> list[ConfiguredDoorBird]:
|
|
||||||
"""Get all doorstations."""
|
|
||||||
return [
|
|
||||||
entry[DOOR_STATION]
|
|
||||||
for entry in hass.data[DOMAIN].values()
|
|
||||||
if DOOR_STATION in entry
|
|
||||||
]
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user