"""Binary sensor platform for irrigation_unlimited.""" from homeassistant.core import HomeAssistant from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.entity_platform import ( EntityPlatform, current_platform, async_get_platforms, ) from homeassistant.util import dt from .irrigation_unlimited import IUCoordinator from .entity import IUEntity from .service import register_platform_services from .const import ( ATTR_ENABLED, ATTR_SEQUENCE_STATUS, ATTR_STATUS, ATTR_INDEX, ATTR_CURRENT_SCHEDULE, ATTR_CURRENT_NAME, ATTR_CURRENT_ADJUSTMENT, ATTR_CURRENT_START, ATTR_CURRENT_DURATION, ATTR_NEXT_SCHEDULE, ATTR_NEXT_ZONE, ATTR_NEXT_NAME, ATTR_NEXT_ADJUSTMENT, ATTR_NEXT_START, ATTR_NEXT_DURATION, ATTR_TIME_REMAINING, ATTR_PERCENT_COMPLETE, ATTR_ZONE_COUNT, ATTR_CURRENT_ZONE, ATTR_TOTAL_TODAY, ATTR_SCHEDULE_COUNT, ATTR_ADJUSTMENT, ATTR_CONFIGURATION, ATTR_TIMELINE, ATTR_SUSPENDED, BINARY_SENSOR, DOMAIN, COORDINATOR, CONF_SCHEDULES, CONF_ZONE_ID, RES_MANUAL, RES_NOT_RUNNING, RES_NONE, ATTR_VOLUME, ATTR_FLOW_RATE, ATTR_SEQUENCE_COUNT, ATTR_ZONES, ) def find_platform(hass: HomeAssistant, name: str) -> EntityPlatform: """Find a platform in our domain""" platforms = async_get_platforms(hass, DOMAIN) for platform in platforms: if platform.domain == name: return platform return None async def async_setup_platform( hass, config, async_add_entities, discovery_info=None ) -> None: """Setup binary_sensor platform.""" # pylint: disable=unused-argument coordinator: IUCoordinator = hass.data[DOMAIN][COORDINATOR] entities = [] for controller in coordinator.controllers: entities.append(IUMasterEntity(coordinator, controller, None, None)) for zone in controller.zones: entities.append(IUZoneEntity(coordinator, controller, zone, None)) for sequence in controller.sequences: entities.append(IUSequenceEntity(coordinator, controller, None, sequence)) async_add_entities(entities) platform = current_platform.get() register_platform_services(platform) return async def async_reload_platform( component: EntityComponent, coordinator: IUCoordinator ) -> bool: """Handle the reloading of this platform""" def remove_entity(entities: "dict[Entity]", entity_id: str) -> bool: entity_id = f"{BINARY_SENSOR}.{DOMAIN}_{entity_id}" if entity_id in entities: entities.pop(entity_id) return True return False platform: EntityPlatform = find_platform(component.hass, BINARY_SENSOR) if platform is None: return False old_entities: dict[Entity] = platform.entities.copy() new_entities: list[Entity] = [] for controller in coordinator.controllers: if not remove_entity(old_entities, controller.unique_id): new_entities.append(IUMasterEntity(coordinator, controller, None, None)) for zone in controller.zones: if not remove_entity(old_entities, zone.unique_id): new_entities.append(IUZoneEntity(coordinator, controller, zone, None)) for sequence in controller.sequences: if not remove_entity(old_entities, sequence.unique_id): new_entities.append( IUSequenceEntity(coordinator, controller, None, sequence) ) if len(new_entities) > 0: await platform.async_add_entities(new_entities) coordinator.initialise() for entity in old_entities: await platform.async_remove_entity(entity) return True class IUMasterEntity(IUEntity): """irrigation_unlimited controller binary_sensor class.""" @property def unique_id(self): """Return a unique ID.""" return self._controller.unique_id @property def name(self): """Return the friendly name of the binary_sensor.""" return self._controller.name @property def is_on(self): """Return true if the binary_sensor is on.""" return self._controller.is_on @property def should_poll(self): """Indicate that we need to poll data""" return False @property def icon(self): """Return the icon to use in the frontend.""" return self._controller.icon @property def extra_state_attributes(self): """Return the state attributes of the device.""" attr = {} attr[ATTR_INDEX] = self._controller.index attr[ATTR_ENABLED] = self._controller.enabled attr[ATTR_SUSPENDED] = self._controller.suspended attr[ATTR_STATUS] = self._controller.status attr[ATTR_ZONE_COUNT] = len(self._controller.zones) attr[ATTR_SEQUENCE_COUNT] = len(self._controller.sequences) attr[ATTR_ZONES] = "" attr[ATTR_SEQUENCE_STATUS] = self._controller.sequence_status() current = self._controller.runs.current_run if current is not None: attr[ATTR_CURRENT_ZONE] = current.zone.index + 1 attr[ATTR_CURRENT_NAME] = current.zone.name attr[ATTR_CURRENT_START] = dt.as_local(current.start_time) attr[ATTR_CURRENT_DURATION] = str(current.duration) attr[ATTR_TIME_REMAINING] = str(current.time_remaining) attr[ATTR_PERCENT_COMPLETE] = current.percent_complete else: attr[ATTR_CURRENT_SCHEDULE] = "deprecated (use current_zone)" attr[ATTR_CURRENT_ZONE] = RES_NOT_RUNNING attr[ATTR_PERCENT_COMPLETE] = 0 next_run = self._controller.runs.next_run if next_run is not None: attr[ATTR_NEXT_ZONE] = next_run.zone.index + 1 attr[ATTR_NEXT_NAME] = next_run.zone.name attr[ATTR_NEXT_START] = dt.as_local(next_run.start_time) attr[ATTR_NEXT_DURATION] = str(next_run.duration) else: attr[ATTR_NEXT_SCHEDULE] = "deprecated (use next_zone)" attr[ATTR_NEXT_ZONE] = RES_NONE attr[ATTR_VOLUME] = self._controller.volume.total attr[ATTR_FLOW_RATE] = self._controller.volume.flow_rate attr |= self._controller.user return attr class IUZoneEntity(IUEntity): """irrigation_unlimited zone binary_sensor class.""" @property def unique_id(self): """Return a unique ID.""" return self._zone.unique_id @property def name(self): """Return the friendly name of the binary_sensor.""" return self._zone.name @property def is_on(self): """Return true if the binary_sensor is on.""" return self._zone.is_on @property def should_poll(self): """Indicate that we need to poll data""" return False @property def icon(self): """Return the icon to use in the frontend.""" return self._zone.icon @property def extra_state_attributes(self): """Return the state attributes of the device.""" # pylint: disable=too-many-branches attr = {} attr[CONF_ZONE_ID] = self._zone.zone_id attr[ATTR_INDEX] = self._zone.index attr[ATTR_ENABLED] = self._zone.enabled attr[ATTR_SUSPENDED] = self._zone.suspended attr[ATTR_STATUS] = self._zone.status attr[ATTR_SCHEDULE_COUNT] = len(self._zone.schedules) attr[CONF_SCHEDULES] = "" attr[ATTR_ADJUSTMENT] = str(self._zone.adjustment) current = self._zone.runs.current_run if current is not None: attr[ATTR_CURRENT_ADJUSTMENT] = current.adjustment if current.schedule is not None: attr[ATTR_CURRENT_SCHEDULE] = current.schedule.index + 1 attr[ATTR_CURRENT_NAME] = current.schedule.name else: attr[ATTR_CURRENT_SCHEDULE] = 0 attr[ATTR_CURRENT_NAME] = RES_MANUAL attr[ATTR_CURRENT_START] = dt.as_local(current.start_time) attr[ATTR_CURRENT_DURATION] = str(current.duration) attr[ATTR_TIME_REMAINING] = str(current.time_remaining) attr[ATTR_PERCENT_COMPLETE] = current.percent_complete else: attr[ATTR_CURRENT_SCHEDULE] = None attr[ATTR_PERCENT_COMPLETE] = 0 next_run = self._zone.runs.next_run if next_run is not None: attr[ATTR_NEXT_ADJUSTMENT] = next_run.adjustment if next_run.schedule is not None: attr[ATTR_NEXT_SCHEDULE] = next_run.schedule.index + 1 attr[ATTR_NEXT_NAME] = next_run.schedule.name else: attr[ATTR_NEXT_SCHEDULE] = 0 attr[ATTR_NEXT_NAME] = RES_MANUAL attr[ATTR_NEXT_START] = dt.as_local(next_run.start_time) attr[ATTR_NEXT_DURATION] = str(next_run.duration) else: attr[ATTR_NEXT_SCHEDULE] = None attr[ATTR_TOTAL_TODAY] = round( self._zone.today_total.total_seconds() / 60, 1, ) if self._zone.show_config: attr[ATTR_CONFIGURATION] = self._zone.configuration if self._zone.show_timeline: attr[ATTR_TIMELINE] = self._zone.timeline() attr[ATTR_VOLUME] = self._zone.volume.total attr[ATTR_FLOW_RATE] = self._zone.volume.flow_rate attr |= self._zone.user return attr class IUSequenceEntity(IUEntity): """irrigation_unlimited sequence binary_sensor class.""" @property def unique_id(self): """Return a unique ID.""" return self._sequence.unique_id @property def name(self): """Return the friendly name of the binary_sensor.""" return self._sequence.name @property def is_on(self): """Return true if the binary_sensor is on.""" return self._sequence.is_on @property def should_poll(self): """Indicate that we need to poll data""" return False @property def icon(self): """Return the icon to use in the frontend.""" return self._sequence.icon @property def extra_state_attributes(self): """Return the state attributes of the device.""" attr = {} attr[ATTR_INDEX] = self._sequence.index attr[ATTR_ENABLED] = self._sequence.enabled attr[ATTR_SUSPENDED] = ( dt.as_local(self._sequence.suspended) if self._sequence.suspended else None ) attr[ATTR_STATUS] = self._sequence.status attr[ATTR_ZONE_COUNT] = len(self._sequence.zones) attr[ATTR_SCHEDULE_COUNT] = len(self._sequence.schedules) attr[ATTR_ADJUSTMENT] = str(self._sequence.adjustment) attr[ATTR_VOLUME] = self._sequence.volume if (current := self._sequence.runs.current_run) is not None: if current.active_zone is not None: attr[ATTR_CURRENT_ZONE] = current.active_zone.sequence_zone.id1 else: attr[ATTR_CURRENT_ZONE] = None attr[ATTR_CURRENT_START] = dt.as_local(current.start_time) attr[ATTR_CURRENT_DURATION] = str(current.total_time) attr[ATTR_TIME_REMAINING] = str(current.time_remaining) attr[ATTR_PERCENT_COMPLETE] = current.percent_complete if current.schedule is not None: attr[ATTR_CURRENT_SCHEDULE] = current.schedule.id1 attr[ATTR_CURRENT_NAME] = current.schedule.name else: attr[ATTR_CURRENT_SCHEDULE] = 0 attr[ATTR_CURRENT_NAME] = RES_MANUAL else: attr[ATTR_CURRENT_ZONE] = None attr[ATTR_CURRENT_SCHEDULE] = None attr[ATTR_PERCENT_COMPLETE] = 0 if (next_run := self._sequence.runs.next_run) is not None: attr[ATTR_NEXT_START] = dt.as_local(next_run.start_time) attr[ATTR_NEXT_DURATION] = str(next_run.total_time) if next_run.schedule is not None: attr[ATTR_NEXT_SCHEDULE] = next_run.schedule.id1 attr[ATTR_NEXT_NAME] = next_run.schedule.name else: attr[ATTR_NEXT_SCHEDULE] = 0 attr[ATTR_NEXT_NAME] = RES_MANUAL else: attr[ATTR_NEXT_SCHEDULE] = None attr[ATTR_ZONES] = self._sequence.ha_zone_attr() return attr