190 lines
5.9 KiB
Python
190 lines
5.9 KiB
Python
"""Support for Brunt Blind Engine covers."""
|
|
from __future__ import annotations
|
|
|
|
from collections.abc import MutableMapping
|
|
from typing import Any
|
|
|
|
from aiohttp.client_exceptions import ClientResponseError
|
|
from brunt import BruntClientAsync, Thing
|
|
|
|
from homeassistant.components.cover import (
|
|
ATTR_POSITION,
|
|
CoverDeviceClass,
|
|
CoverEntity,
|
|
CoverEntityFeature,
|
|
)
|
|
from homeassistant.config_entries import ConfigEntry
|
|
from homeassistant.core import HomeAssistant, callback
|
|
from homeassistant.exceptions import HomeAssistantError
|
|
from homeassistant.helpers.entity import DeviceInfo
|
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
|
from homeassistant.helpers.update_coordinator import (
|
|
CoordinatorEntity,
|
|
DataUpdateCoordinator,
|
|
)
|
|
|
|
from .const import (
|
|
ATTR_REQUEST_POSITION,
|
|
ATTRIBUTION,
|
|
CLOSED_POSITION,
|
|
DATA_BAPI,
|
|
DATA_COOR,
|
|
DOMAIN,
|
|
FAST_INTERVAL,
|
|
OPEN_POSITION,
|
|
REGULAR_INTERVAL,
|
|
)
|
|
|
|
|
|
async def async_setup_entry(
|
|
hass: HomeAssistant,
|
|
entry: ConfigEntry,
|
|
async_add_entities: AddEntitiesCallback,
|
|
) -> None:
|
|
"""Set up the brunt platform."""
|
|
bapi: BruntClientAsync = hass.data[DOMAIN][entry.entry_id][DATA_BAPI]
|
|
coordinator: DataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id][DATA_COOR]
|
|
|
|
async_add_entities(
|
|
BruntDevice(coordinator, serial, thing, bapi, entry.entry_id)
|
|
for serial, thing in coordinator.data.items()
|
|
)
|
|
|
|
|
|
class BruntDevice(CoordinatorEntity, CoverEntity):
|
|
"""
|
|
Representation of a Brunt cover device.
|
|
|
|
Contains the common logic for all Brunt devices.
|
|
"""
|
|
|
|
_attr_supported_features = (
|
|
CoverEntityFeature.OPEN
|
|
| CoverEntityFeature.CLOSE
|
|
| CoverEntityFeature.SET_POSITION
|
|
)
|
|
|
|
def __init__(
|
|
self,
|
|
coordinator: DataUpdateCoordinator,
|
|
serial: str,
|
|
thing: Thing,
|
|
bapi: BruntClientAsync,
|
|
entry_id: str,
|
|
) -> None:
|
|
"""Init the Brunt device."""
|
|
super().__init__(coordinator)
|
|
self._attr_unique_id = serial
|
|
self._bapi = bapi
|
|
self._thing = thing
|
|
self._entry_id = entry_id
|
|
|
|
self._remove_update_listener = None
|
|
|
|
self._attr_name = self._thing.name
|
|
self._attr_device_class = CoverDeviceClass.BLIND
|
|
self._attr_attribution = ATTRIBUTION
|
|
self._attr_device_info = DeviceInfo(
|
|
identifiers={(DOMAIN, self._attr_unique_id)},
|
|
name=self._attr_name,
|
|
via_device=(DOMAIN, self._entry_id),
|
|
manufacturer="Brunt",
|
|
sw_version=self._thing.fw_version,
|
|
model=self._thing.model,
|
|
)
|
|
|
|
async def async_added_to_hass(self) -> None:
|
|
"""When entity is added to hass."""
|
|
await super().async_added_to_hass()
|
|
self.async_on_remove(
|
|
self.coordinator.async_add_listener(self._brunt_update_listener)
|
|
)
|
|
|
|
@property
|
|
def current_cover_position(self) -> int | None:
|
|
"""
|
|
Return current position of cover.
|
|
|
|
None is unknown, 0 is closed, 100 is fully open.
|
|
"""
|
|
return self.coordinator.data[self.unique_id].current_position
|
|
|
|
@property
|
|
def request_cover_position(self) -> int | None:
|
|
"""
|
|
Return request position of cover.
|
|
|
|
The request position is the position of the last request
|
|
to Brunt, at times there is a diff of 1 to current
|
|
None is unknown, 0 is closed, 100 is fully open.
|
|
"""
|
|
return self.coordinator.data[self.unique_id].request_position
|
|
|
|
@property
|
|
def move_state(self) -> int | None:
|
|
"""
|
|
Return current moving state of cover.
|
|
|
|
None is unknown, 0 when stopped, 1 when opening, 2 when closing
|
|
"""
|
|
return self.coordinator.data[self.unique_id].move_state
|
|
|
|
@property
|
|
def is_opening(self) -> bool:
|
|
"""Return if the cover is opening or not."""
|
|
return self.move_state == 1
|
|
|
|
@property
|
|
def is_closing(self) -> bool:
|
|
"""Return if the cover is closing or not."""
|
|
return self.move_state == 2
|
|
|
|
@property
|
|
def extra_state_attributes(self) -> MutableMapping[str, Any]:
|
|
"""Return the detailed device state attributes."""
|
|
return {
|
|
ATTR_REQUEST_POSITION: self.request_cover_position,
|
|
}
|
|
|
|
@property
|
|
def is_closed(self) -> bool:
|
|
"""Return true if cover is closed, else False."""
|
|
return self.current_cover_position == CLOSED_POSITION
|
|
|
|
async def async_open_cover(self, **kwargs: Any) -> None:
|
|
"""Set the cover to the open position."""
|
|
await self._async_update_cover(OPEN_POSITION)
|
|
|
|
async def async_close_cover(self, **kwargs: Any) -> None:
|
|
"""Set the cover to the closed position."""
|
|
await self._async_update_cover(CLOSED_POSITION)
|
|
|
|
async def async_set_cover_position(self, **kwargs: Any) -> None:
|
|
"""Set the cover to a specific position."""
|
|
await self._async_update_cover(int(kwargs[ATTR_POSITION]))
|
|
|
|
async def _async_update_cover(self, position: int) -> None:
|
|
"""Set the cover to the new position and wait for the update to be reflected."""
|
|
try:
|
|
await self._bapi.async_change_request_position(
|
|
position, thing_uri=self._thing.thing_uri
|
|
)
|
|
except ClientResponseError as exc:
|
|
raise HomeAssistantError(
|
|
f"Unable to reposition {self._thing.name}"
|
|
) from exc
|
|
self.coordinator.update_interval = FAST_INTERVAL
|
|
await self.coordinator.async_request_refresh()
|
|
|
|
@callback
|
|
def _brunt_update_listener(self) -> None:
|
|
"""Update the update interval after each refresh."""
|
|
if (
|
|
self.request_cover_position
|
|
== self._bapi.last_requested_positions[self._thing.thing_uri]
|
|
and self.move_state == 0
|
|
):
|
|
self.coordinator.update_interval = REGULAR_INTERVAL
|
|
else:
|
|
self.coordinator.update_interval = FAST_INTERVAL
|