"""Support for Hydrawise sprinkler binary sensors.""" from collections.abc import Callable, Iterable from dataclasses import dataclass from datetime import datetime from typing import override from pydrawise import Controller, Zone import voluptuous as vol from homeassistant.components.binary_sensor import ( BinarySensorDeviceClass, BinarySensorEntity, BinarySensorEntityDescription, ) from homeassistant.core import HomeAssistant from homeassistant.helpers import config_validation as cv, entity_platform from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback from homeassistant.helpers.typing import VolDictType from .const import SERVICE_RESUME, SERVICE_START_WATERING, SERVICE_SUSPEND from .coordinator import HydrawiseConfigEntry from .entity import HydrawiseEntity PARALLEL_UPDATES = 1 @dataclass(frozen=True, kw_only=True) class HydrawiseBinarySensorEntityDescription(BinarySensorEntityDescription): """Describes Hydrawise binary sensor.""" value_fn: Callable[[HydrawiseBinarySensor], bool | None] always_available: bool = False CONTROLLER_BINARY_SENSORS: tuple[HydrawiseBinarySensorEntityDescription, ...] = ( HydrawiseBinarySensorEntityDescription( key="status", device_class=BinarySensorDeviceClass.CONNECTIVITY, value_fn=( lambda status_sensor: ( status_sensor.coordinator.last_update_success and status_sensor.controller.online ) ), # Connectivtiy sensor is always available always_available=True, ), ) RAIN_SENSOR_BINARY_SENSOR: tuple[HydrawiseBinarySensorEntityDescription, ...] = ( HydrawiseBinarySensorEntityDescription( key="rain_sensor", translation_key="rain_sensor", device_class=BinarySensorDeviceClass.MOISTURE, value_fn=lambda rain_sensor: rain_sensor.sensor.status.active, ), ) ZONE_BINARY_SENSORS: tuple[HydrawiseBinarySensorEntityDescription, ...] = ( HydrawiseBinarySensorEntityDescription( key="is_watering", translation_key="watering", device_class=BinarySensorDeviceClass.RUNNING, value_fn=( lambda watering_sensor: ( watering_sensor.zone.scheduled_runs.current_run is not None ) ), ), ) SCHEMA_START_WATERING: VolDictType = { vol.Optional("duration"): vol.All(vol.Coerce(int), vol.Range(min=0, max=1440)), } SCHEMA_SUSPEND: VolDictType = { vol.Required("until"): cv.datetime, } async def async_setup_entry( hass: HomeAssistant, config_entry: HydrawiseConfigEntry, async_add_entities: AddConfigEntryEntitiesCallback, ) -> None: """Set up the Hydrawise binary_sensor platform.""" coordinators = config_entry.runtime_data def _add_new_controllers(controllers: Iterable[Controller]) -> None: entities: list[HydrawiseBinarySensor] = [] for controller in controllers: entities.extend( HydrawiseBinarySensor(coordinators.main, description, controller) for description in CONTROLLER_BINARY_SENSORS ) entities.extend( HydrawiseBinarySensor( coordinators.main, description, controller, sensor_id=sensor.id, ) for sensor in controller.sensors for description in RAIN_SENSOR_BINARY_SENSOR if "rain sensor" in sensor.model.name.lower() ) async_add_entities(entities) def _add_new_zones(zones: Iterable[tuple[Zone, Controller]]) -> None: async_add_entities( HydrawiseZoneBinarySensor( coordinators.main, description, controller, zone_id=zone.id ) for zone, controller in zones for description in ZONE_BINARY_SENSORS ) _add_new_controllers(coordinators.main.data.controllers.values()) _add_new_zones( [ (zone, coordinators.main.data.zone_id_to_controller[zone.id]) for zone in coordinators.main.data.zones.values() ] ) coordinators.main.new_controllers_callbacks.append(_add_new_controllers) coordinators.main.new_zones_callbacks.append(_add_new_zones) platform = entity_platform.async_get_current_platform() platform.async_register_entity_service( SERVICE_RESUME, None, "resume", entity_device_classes=(BinarySensorDeviceClass.RUNNING,), ) platform.async_register_entity_service( SERVICE_START_WATERING, SCHEMA_START_WATERING, "start_watering", entity_device_classes=(BinarySensorDeviceClass.RUNNING,), ) platform.async_register_entity_service( SERVICE_SUSPEND, SCHEMA_SUSPEND, "suspend", entity_device_classes=(BinarySensorDeviceClass.RUNNING,), ) class HydrawiseBinarySensor(HydrawiseEntity, BinarySensorEntity): """A sensor implementation for Hydrawise device.""" entity_description: HydrawiseBinarySensorEntityDescription @override def _update_attrs(self) -> None: """Update state attributes.""" self._attr_is_on = self.entity_description.value_fn(self) @property @override def available(self) -> bool: """Set the entity availability.""" if self.entity_description.always_available: return True return super().available class HydrawiseZoneBinarySensor(HydrawiseBinarySensor): """A binary sensor for a Hydrawise irrigation zone. This is only used for irrigation zones, as they have special methods for service actions that don't apply to other binary sensors. """ zone: Zone async def start_watering(self, duration: int | None = None) -> None: """Start watering in the irrigation zone.""" await self.coordinator.api.start_zone( self.zone, custom_run_duration=int((duration or 0) * 60) ) async def suspend(self, until: datetime) -> None: """Suspend automatic watering in the irrigation zone.""" await self.coordinator.api.suspend_zone(self.zone, until=until) async def resume(self) -> None: """Resume automatic watering in the irrigation zone.""" await self.coordinator.api.resume_zone(self.zone)