210 lines
6.8 KiB
Python
210 lines
6.8 KiB
Python
"""Integration with the Rachio Iro sprinkler system controller."""
|
|
|
|
from collections.abc import Callable
|
|
from dataclasses import dataclass
|
|
import logging
|
|
from typing import Any
|
|
|
|
from homeassistant.components.binary_sensor import (
|
|
BinarySensorDeviceClass,
|
|
BinarySensorEntity,
|
|
BinarySensorEntityDescription,
|
|
)
|
|
from homeassistant.const import EntityCategory
|
|
from homeassistant.core import HomeAssistant, callback
|
|
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
|
from homeassistant.helpers.entity import Entity
|
|
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
|
|
|
from .const import (
|
|
KEY_BATTERY,
|
|
KEY_DETECT_FLOW,
|
|
KEY_DEVICE_ID,
|
|
KEY_FLOW,
|
|
KEY_ONLINE,
|
|
KEY_RAIN_SENSOR,
|
|
KEY_RAIN_SENSOR_TRIPPED,
|
|
KEY_STATUS,
|
|
KEY_SUBTYPE,
|
|
SIGNAL_RACHIO_CONTROLLER_UPDATE,
|
|
SIGNAL_RACHIO_RAIN_SENSOR_UPDATE,
|
|
STATUS_ONLINE,
|
|
)
|
|
from .coordinator import RachioUpdateCoordinator
|
|
from .device import RachioConfigEntry, RachioIro
|
|
from .entity import RachioDevice, RachioHoseTimerEntity
|
|
from .webhooks import (
|
|
SUBTYPE_COLD_REBOOT,
|
|
SUBTYPE_OFFLINE,
|
|
SUBTYPE_ONLINE,
|
|
SUBTYPE_RAIN_SENSOR_DETECTION_OFF,
|
|
SUBTYPE_RAIN_SENSOR_DETECTION_ON,
|
|
)
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
|
|
@dataclass(frozen=True, kw_only=True)
|
|
class RachioControllerBinarySensorDescription(BinarySensorEntityDescription):
|
|
"""Describe a Rachio controller binary sensor."""
|
|
|
|
update_received: Callable[[str], bool | None]
|
|
is_on: Callable[[RachioIro], bool]
|
|
signal_string: str
|
|
|
|
|
|
CONTROLLER_BINARY_SENSOR_TYPES: tuple[RachioControllerBinarySensorDescription, ...] = (
|
|
RachioControllerBinarySensorDescription(
|
|
key=KEY_ONLINE,
|
|
device_class=BinarySensorDeviceClass.CONNECTIVITY,
|
|
signal_string=SIGNAL_RACHIO_CONTROLLER_UPDATE,
|
|
is_on=lambda controller: controller.init_data[KEY_STATUS] == STATUS_ONLINE,
|
|
update_received={
|
|
SUBTYPE_ONLINE: True,
|
|
SUBTYPE_COLD_REBOOT: True,
|
|
SUBTYPE_OFFLINE: False,
|
|
}.get,
|
|
),
|
|
RachioControllerBinarySensorDescription(
|
|
key=KEY_RAIN_SENSOR,
|
|
translation_key="rain",
|
|
device_class=BinarySensorDeviceClass.MOISTURE,
|
|
signal_string=SIGNAL_RACHIO_RAIN_SENSOR_UPDATE,
|
|
is_on=lambda controller: controller.init_data[KEY_RAIN_SENSOR_TRIPPED],
|
|
update_received={
|
|
SUBTYPE_RAIN_SENSOR_DETECTION_ON: True,
|
|
SUBTYPE_RAIN_SENSOR_DETECTION_OFF: False,
|
|
}.get,
|
|
),
|
|
)
|
|
|
|
|
|
@dataclass(frozen=True, kw_only=True)
|
|
class RachioHoseTimerBinarySensorDescription(BinarySensorEntityDescription):
|
|
"""Describe a Rachio hose timer binary sensor."""
|
|
|
|
value_fn: Callable[[RachioHoseTimerEntity], bool]
|
|
exists_fn: Callable[[dict[str, Any]], bool] = lambda _: True
|
|
|
|
|
|
HOSE_TIMER_BINARY_SENSOR_TYPES: tuple[RachioHoseTimerBinarySensorDescription, ...] = (
|
|
RachioHoseTimerBinarySensorDescription(
|
|
key=KEY_BATTERY,
|
|
entity_category=EntityCategory.DIAGNOSTIC,
|
|
device_class=BinarySensorDeviceClass.BATTERY,
|
|
value_fn=lambda device: device.battery,
|
|
),
|
|
RachioHoseTimerBinarySensorDescription(
|
|
key=KEY_FLOW,
|
|
device_class=BinarySensorDeviceClass.PROBLEM,
|
|
entity_category=EntityCategory.DIAGNOSTIC,
|
|
translation_key="flow",
|
|
value_fn=lambda device: device.no_flow_detected,
|
|
exists_fn=lambda valve: valve[KEY_DETECT_FLOW],
|
|
),
|
|
)
|
|
|
|
|
|
async def async_setup_entry(
|
|
hass: HomeAssistant,
|
|
config_entry: RachioConfigEntry,
|
|
async_add_entities: AddConfigEntryEntitiesCallback,
|
|
) -> None:
|
|
"""Set up the Rachio binary sensors."""
|
|
entities = await hass.async_add_executor_job(_create_entities, hass, config_entry)
|
|
async_add_entities(entities)
|
|
|
|
|
|
def _create_entities(
|
|
hass: HomeAssistant, config_entry: RachioConfigEntry
|
|
) -> list[Entity]:
|
|
entities: list[Entity] = []
|
|
person = config_entry.runtime_data
|
|
entities.extend(
|
|
RachioControllerBinarySensor(controller, description)
|
|
for controller in person.controllers
|
|
for description in CONTROLLER_BINARY_SENSOR_TYPES
|
|
)
|
|
entities.extend(
|
|
RachioHoseTimerBinarySensor(valve, base_station.status_coordinator, description)
|
|
for base_station in person.base_stations
|
|
for valve in base_station.status_coordinator.data.values()
|
|
for description in HOSE_TIMER_BINARY_SENSOR_TYPES
|
|
if description.exists_fn(valve)
|
|
)
|
|
return entities
|
|
|
|
|
|
class RachioControllerBinarySensor(RachioDevice, BinarySensorEntity):
|
|
"""Represent a binary sensor that reflects a Rachio controller state."""
|
|
|
|
entity_description: RachioControllerBinarySensorDescription
|
|
_attr_has_entity_name = True
|
|
|
|
def __init__(
|
|
self,
|
|
controller: RachioIro,
|
|
description: RachioControllerBinarySensorDescription,
|
|
) -> None:
|
|
"""Initialize a controller binary sensor."""
|
|
super().__init__(controller)
|
|
self.entity_description = description
|
|
self._attr_unique_id = f"{controller.controller_id}-{description.key}"
|
|
|
|
@callback
|
|
def _async_handle_any_update(self, *args, **kwargs) -> None:
|
|
"""Determine whether an update event applies to this device."""
|
|
if args[0][KEY_DEVICE_ID] != self._controller.controller_id:
|
|
# For another device
|
|
return
|
|
|
|
# For this device
|
|
self._async_handle_update(args, kwargs)
|
|
|
|
@callback
|
|
def _async_handle_update(self, *args, **kwargs) -> None:
|
|
"""Handle an update to the state of this sensor."""
|
|
if (
|
|
updated_state := self.entity_description.update_received(
|
|
args[0][0][KEY_SUBTYPE]
|
|
)
|
|
) is not None:
|
|
self._attr_is_on = updated_state
|
|
|
|
self.async_write_ha_state()
|
|
|
|
async def async_added_to_hass(self) -> None:
|
|
"""Subscribe to updates."""
|
|
self._attr_is_on = self.entity_description.is_on(self._controller)
|
|
|
|
self.async_on_remove(
|
|
async_dispatcher_connect(
|
|
self.hass,
|
|
self.entity_description.signal_string,
|
|
self._async_handle_any_update,
|
|
)
|
|
)
|
|
|
|
|
|
class RachioHoseTimerBinarySensor(RachioHoseTimerEntity, BinarySensorEntity):
|
|
"""Represents a binary sensor for a smart hose timer."""
|
|
|
|
entity_description: RachioHoseTimerBinarySensorDescription
|
|
|
|
def __init__(
|
|
self,
|
|
data: dict[str, Any],
|
|
coordinator: RachioUpdateCoordinator,
|
|
description: RachioHoseTimerBinarySensorDescription,
|
|
) -> None:
|
|
"""Initialize a smart hose timer binary sensor."""
|
|
super().__init__(data, coordinator)
|
|
self.entity_description = description
|
|
self._attr_unique_id = f"{self.id}-{description.key}"
|
|
self._update_attr()
|
|
|
|
@callback
|
|
def _update_attr(self) -> None:
|
|
"""Handle updated coordinator data."""
|
|
self._attr_is_on = self.entity_description.value_fn(self)
|