forked from home-assistant/core
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Split august and yale integrations (home-assistant#124677)
* Split august and yale integrations [part 1] (home-assistant#122253) * merge with dev * Remove unused constant * Remove yale IPv6 workaround (home-assistant#123409) * Convert yale to use oauth (home-assistant#123806) Co-authored-by: Joost Lekkerkerker <[email protected]> * Update yale for switch from pubnub to websockets (home-assistant#124675) --------- Co-authored-by: Joost Lekkerkerker <[email protected]>
- Loading branch information
Showing
72 changed files
with
5,811 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,11 @@ | ||
{ | ||
"domain": "yale", | ||
"name": "Yale", | ||
"integrations": ["august", "yale_smart_alarm", "yalexs_ble", "yale_home"] | ||
"integrations": [ | ||
"august", | ||
"yale_smart_alarm", | ||
"yalexs_ble", | ||
"yale_home", | ||
"yale" | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
"""Support for Yale devices.""" | ||
|
||
from __future__ import annotations | ||
|
||
from pathlib import Path | ||
from typing import cast | ||
|
||
from aiohttp import ClientResponseError | ||
from yalexs.const import Brand | ||
from yalexs.exceptions import YaleApiError | ||
from yalexs.manager.const import CONF_BRAND | ||
from yalexs.manager.exceptions import CannotConnect, InvalidAuth, RequireValidation | ||
from yalexs.manager.gateway import Config as YaleXSConfig | ||
|
||
from homeassistant.config_entries import ConfigEntry | ||
from homeassistant.const import EVENT_HOMEASSISTANT_STOP | ||
from homeassistant.core import HomeAssistant | ||
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady | ||
from homeassistant.helpers import config_entry_oauth2_flow, device_registry as dr | ||
|
||
from .const import DOMAIN, PLATFORMS | ||
from .data import YaleData | ||
from .gateway import YaleGateway | ||
from .util import async_create_yale_clientsession | ||
|
||
type YaleConfigEntry = ConfigEntry[YaleData] | ||
|
||
|
||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: | ||
"""Set up yale from a config entry.""" | ||
session = async_create_yale_clientsession(hass) | ||
implementation = ( | ||
await config_entry_oauth2_flow.async_get_config_entry_implementation( | ||
hass, entry | ||
) | ||
) | ||
oauth_session = config_entry_oauth2_flow.OAuth2Session(hass, entry, implementation) | ||
yale_gateway = YaleGateway(Path(hass.config.config_dir), session, oauth_session) | ||
try: | ||
await async_setup_yale(hass, entry, yale_gateway) | ||
except (RequireValidation, InvalidAuth) as err: | ||
raise ConfigEntryAuthFailed from err | ||
except TimeoutError as err: | ||
raise ConfigEntryNotReady("Timed out connecting to yale api") from err | ||
except (YaleApiError, ClientResponseError, CannotConnect) as err: | ||
raise ConfigEntryNotReady from err | ||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) | ||
return True | ||
|
||
|
||
async def async_unload_entry(hass: HomeAssistant, entry: YaleConfigEntry) -> bool: | ||
"""Unload a config entry.""" | ||
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS) | ||
|
||
|
||
async def async_setup_yale( | ||
hass: HomeAssistant, entry: YaleConfigEntry, yale_gateway: YaleGateway | ||
) -> None: | ||
"""Set up the yale component.""" | ||
config = cast(YaleXSConfig, entry.data) | ||
await yale_gateway.async_setup({**config, CONF_BRAND: Brand.YALE_GLOBAL}) | ||
await yale_gateway.async_authenticate() | ||
await yale_gateway.async_refresh_access_token_if_needed() | ||
data = entry.runtime_data = YaleData(hass, yale_gateway) | ||
entry.async_on_unload( | ||
hass.bus.async_listen(EVENT_HOMEASSISTANT_STOP, data.async_stop) | ||
) | ||
entry.async_on_unload(data.async_stop) | ||
await data.async_setup() | ||
|
||
|
||
async def async_remove_config_entry_device( | ||
hass: HomeAssistant, config_entry: YaleConfigEntry, device_entry: dr.DeviceEntry | ||
) -> bool: | ||
"""Remove yale config entry from a device if its no longer present.""" | ||
return not any( | ||
identifier | ||
for identifier in device_entry.identifiers | ||
if identifier[0] == DOMAIN | ||
and config_entry.runtime_data.get_device(identifier[1]) | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
"""application_credentials platform the yale integration.""" | ||
|
||
from homeassistant.components.application_credentials import AuthorizationServer | ||
from homeassistant.core import HomeAssistant | ||
|
||
OAUTH2_AUTHORIZE = "https://oauth.aaecosystem.com/authorize" | ||
OAUTH2_TOKEN = "https://oauth.aaecosystem.com/access_token" | ||
|
||
|
||
async def async_get_authorization_server(hass: HomeAssistant) -> AuthorizationServer: | ||
"""Return authorization server.""" | ||
return AuthorizationServer( | ||
authorize_url=OAUTH2_AUTHORIZE, | ||
token_url=OAUTH2_TOKEN, | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,189 @@ | ||
"""Support for Yale binary sensors.""" | ||
|
||
from __future__ import annotations | ||
|
||
from collections.abc import Callable | ||
from dataclasses import dataclass | ||
from datetime import datetime, timedelta | ||
from functools import partial | ||
import logging | ||
|
||
from yalexs.activity import Activity, ActivityType | ||
from yalexs.doorbell import DoorbellDetail | ||
from yalexs.lock import LockDetail, LockDoorStatus | ||
from yalexs.manager.const import ACTIVITY_UPDATE_INTERVAL | ||
from yalexs.util import update_lock_detail_from_activity | ||
|
||
from homeassistant.components.binary_sensor import ( | ||
BinarySensorDeviceClass, | ||
BinarySensorEntity, | ||
BinarySensorEntityDescription, | ||
) | ||
from homeassistant.const import EntityCategory | ||
from homeassistant.core import HomeAssistant, callback | ||
from homeassistant.helpers.entity_platform import AddEntitiesCallback | ||
from homeassistant.helpers.event import async_call_later | ||
|
||
from . import YaleConfigEntry, YaleData | ||
from .entity import YaleDescriptionEntity | ||
from .util import ( | ||
retrieve_ding_activity, | ||
retrieve_doorbell_motion_activity, | ||
retrieve_online_state, | ||
retrieve_time_based_activity, | ||
) | ||
|
||
_LOGGER = logging.getLogger(__name__) | ||
|
||
TIME_TO_RECHECK_DETECTION = timedelta( | ||
seconds=ACTIVITY_UPDATE_INTERVAL.total_seconds() * 3 | ||
) | ||
|
||
|
||
@dataclass(frozen=True, kw_only=True) | ||
class YaleDoorbellBinarySensorEntityDescription(BinarySensorEntityDescription): | ||
"""Describes Yale binary_sensor entity.""" | ||
|
||
value_fn: Callable[[YaleData, DoorbellDetail | LockDetail], Activity | None] | ||
is_time_based: bool | ||
|
||
|
||
SENSOR_TYPE_DOOR = BinarySensorEntityDescription( | ||
key="open", | ||
device_class=BinarySensorDeviceClass.DOOR, | ||
) | ||
|
||
SENSOR_TYPES_VIDEO_DOORBELL = ( | ||
YaleDoorbellBinarySensorEntityDescription( | ||
key="motion", | ||
device_class=BinarySensorDeviceClass.MOTION, | ||
value_fn=retrieve_doorbell_motion_activity, | ||
is_time_based=True, | ||
), | ||
YaleDoorbellBinarySensorEntityDescription( | ||
key="image capture", | ||
translation_key="image_capture", | ||
value_fn=partial( | ||
retrieve_time_based_activity, {ActivityType.DOORBELL_IMAGE_CAPTURE} | ||
), | ||
is_time_based=True, | ||
), | ||
YaleDoorbellBinarySensorEntityDescription( | ||
key="online", | ||
device_class=BinarySensorDeviceClass.CONNECTIVITY, | ||
entity_category=EntityCategory.DIAGNOSTIC, | ||
value_fn=retrieve_online_state, | ||
is_time_based=False, | ||
), | ||
) | ||
|
||
|
||
SENSOR_TYPES_DOORBELL: tuple[YaleDoorbellBinarySensorEntityDescription, ...] = ( | ||
YaleDoorbellBinarySensorEntityDescription( | ||
key="ding", | ||
translation_key="ding", | ||
device_class=BinarySensorDeviceClass.OCCUPANCY, | ||
value_fn=retrieve_ding_activity, | ||
is_time_based=True, | ||
), | ||
) | ||
|
||
|
||
async def async_setup_entry( | ||
hass: HomeAssistant, | ||
config_entry: YaleConfigEntry, | ||
async_add_entities: AddEntitiesCallback, | ||
) -> None: | ||
"""Set up the Yale binary sensors.""" | ||
data = config_entry.runtime_data | ||
entities: list[BinarySensorEntity] = [] | ||
|
||
for lock in data.locks: | ||
detail = data.get_device_detail(lock.device_id) | ||
if detail.doorsense: | ||
entities.append(YaleDoorBinarySensor(data, lock, SENSOR_TYPE_DOOR)) | ||
|
||
if detail.doorbell: | ||
entities.extend( | ||
YaleDoorbellBinarySensor(data, lock, description) | ||
for description in SENSOR_TYPES_DOORBELL | ||
) | ||
|
||
for doorbell in data.doorbells: | ||
entities.extend( | ||
YaleDoorbellBinarySensor(data, doorbell, description) | ||
for description in SENSOR_TYPES_DOORBELL + SENSOR_TYPES_VIDEO_DOORBELL | ||
) | ||
|
||
async_add_entities(entities) | ||
|
||
|
||
class YaleDoorBinarySensor(YaleDescriptionEntity, BinarySensorEntity): | ||
"""Representation of an Yale Door binary sensor.""" | ||
|
||
_attr_device_class = BinarySensorDeviceClass.DOOR | ||
description: BinarySensorEntityDescription | ||
|
||
@callback | ||
def _update_from_data(self) -> None: | ||
"""Get the latest state of the sensor and update activity.""" | ||
if door_activity := self._get_latest({ActivityType.DOOR_OPERATION}): | ||
update_lock_detail_from_activity(self._detail, door_activity) | ||
if door_activity.was_pushed: | ||
self._detail.set_online(True) | ||
|
||
if bridge_activity := self._get_latest({ActivityType.BRIDGE_OPERATION}): | ||
update_lock_detail_from_activity(self._detail, bridge_activity) | ||
self._attr_available = self._detail.bridge_is_online | ||
self._attr_is_on = self._detail.door_state == LockDoorStatus.OPEN | ||
|
||
|
||
class YaleDoorbellBinarySensor(YaleDescriptionEntity, BinarySensorEntity): | ||
"""Representation of an Yale binary sensor.""" | ||
|
||
entity_description: YaleDoorbellBinarySensorEntityDescription | ||
_check_for_off_update_listener: Callable[[], None] | None = None | ||
|
||
@callback | ||
def _update_from_data(self) -> None: | ||
"""Get the latest state of the sensor.""" | ||
self._cancel_any_pending_updates() | ||
self._attr_is_on = bool( | ||
self.entity_description.value_fn(self._data, self._detail) | ||
) | ||
|
||
if self.entity_description.is_time_based: | ||
self._attr_available = retrieve_online_state(self._data, self._detail) | ||
self._schedule_update_to_recheck_turn_off_sensor() | ||
else: | ||
self._attr_available = True | ||
|
||
@callback | ||
def _async_scheduled_update(self, now: datetime) -> None: | ||
"""Timer callback for sensor update.""" | ||
self._check_for_off_update_listener = None | ||
self._update_from_data() | ||
if not self.is_on: | ||
self.async_write_ha_state() | ||
|
||
def _schedule_update_to_recheck_turn_off_sensor(self) -> None: | ||
"""Schedule an update to recheck the sensor to see if it is ready to turn off.""" | ||
# If the sensor is already off there is nothing to do | ||
if not self.is_on: | ||
return | ||
self._check_for_off_update_listener = async_call_later( | ||
self.hass, TIME_TO_RECHECK_DETECTION, self._async_scheduled_update | ||
) | ||
|
||
def _cancel_any_pending_updates(self) -> None: | ||
"""Cancel any updates to recheck a sensor to see if it is ready to turn off.""" | ||
if not self._check_for_off_update_listener: | ||
return | ||
_LOGGER.debug("%s: canceled pending update", self.entity_id) | ||
self._check_for_off_update_listener() | ||
self._check_for_off_update_listener = None | ||
|
||
async def async_will_remove_from_hass(self) -> None: | ||
"""When removing cancel any scheduled updates.""" | ||
self._cancel_any_pending_updates() | ||
await super().async_will_remove_from_hass() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
"""Support for Yale buttons.""" | ||
|
||
from homeassistant.components.button import ButtonEntity | ||
from homeassistant.core import HomeAssistant, callback | ||
from homeassistant.helpers.entity_platform import AddEntitiesCallback | ||
|
||
from . import YaleConfigEntry | ||
from .entity import YaleEntityMixin | ||
|
||
|
||
async def async_setup_entry( | ||
hass: HomeAssistant, | ||
config_entry: YaleConfigEntry, | ||
async_add_entities: AddEntitiesCallback, | ||
) -> None: | ||
"""Set up Yale lock wake buttons.""" | ||
data = config_entry.runtime_data | ||
async_add_entities(YaleWakeLockButton(data, lock, "wake") for lock in data.locks) | ||
|
||
|
||
class YaleWakeLockButton(YaleEntityMixin, ButtonEntity): | ||
"""Representation of an Yale lock wake button.""" | ||
|
||
_attr_translation_key = "wake" | ||
|
||
async def async_press(self) -> None: | ||
"""Wake the device.""" | ||
await self._data.async_status_async(self._device_id, self._hyper_bridge) | ||
|
||
@callback | ||
def _update_from_data(self) -> None: | ||
"""Nothing to update as buttons are stateless.""" |
Oops, something went wrong.