"""Platform for button integration.""" from collections.abc import Callable import logging from typing import Any from aiohttp.client_exceptions import ClientConnectionError from wyzeapy import Wyzeapy from wyzeapy.services.irrigation_service import Irrigation, IrrigationService, Zone from wyzeapy.services.switch_service import Switch from homeassistant.components.button import ButtonDeviceClass, ButtonEntity from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.entity_registry import EntityCategory from .const import CONF_CLIENT, DOMAIN, RESET_BUTTON_PRESSED from .token_manager import token_exception_handler _LOGGER = logging.getLogger(__name__) ATTRIBUTION = "Data provided by Wyze" OUTDOOR_PLUGS = ["WLPPO"] @token_exception_handler async def async_setup_entry( hass: HomeAssistant, config_entry: ConfigEntry, async_add_entities: Callable[[list[Any], bool], None], ) -> None: """This function sets up the config entry. :param hass: The Home Assistant Instance :param config_entry: The current config entry :param async_add_entities: This function adds entities to the config entry :return: """ _LOGGER.debug("""Creating new Wyze button component""") client: Wyzeapy = hass.data[DOMAIN][config_entry.entry_id][CONF_CLIENT] irrigation_service = await client.irrigation_service switch_service = await client.switch_service # Get all irrigation devices irrigation_devices = await irrigation_service.get_irrigations() # Create a button entity for each zone in each irrigation device buttons = [] for device in irrigation_devices: # Update the device to get its zones device = await irrigation_service.update(device) # Add a button entity for each enabled zone in the irrigation device buttons.extend( [ WyzeIrrigationZoneButton(irrigation_service, device, zone) for zone in device.zones if zone.enabled ] ) # Add a stop all schedules button for each irrigation device, not each zone buttons.append(WyzeIrrigationStopAllButton(irrigation_service, device)) plugs = await switch_service.get_switches() buttons.extend( [ WyzePowerSensorResetButton(plug) for plug in plugs if plug.product_model in OUTDOOR_PLUGS ] ) async_add_entities(buttons, True) class WyzeIrrigationZoneButton(ButtonEntity): """Representation of a Wyze Irrigation Zone Button.""" _attr_has_entity_name = True def __init__( self, irrigation_service: IrrigationService, irrigation: Irrigation, zone: Zone ) -> None: """Initialize the irrigation zone button.""" self._irrigation_service = irrigation_service self._device = irrigation self._zone = zone @property def name(self) -> str: """Return the name of the zone.""" return f"{self._zone.name}" @property def unique_id(self) -> str: """Return a unique ID for the zone.""" return f"Start {self._device.mac}-zone-{self._zone.zone_number}" @property def device_info(self) -> DeviceInfo: """Return device information about this entity.""" return DeviceInfo( identifiers={(DOMAIN, self._device.mac)}, name=self._device.nickname, manufacturer="WyzeLabs", model=self._device.product_model, connections={(dr.CONNECTION_NETWORK_MAC, self._device.mac)}, ) @property def device_class(self) -> str: """Return the device class of the button.""" return ButtonDeviceClass.RESTART @property def icon(self) -> str: """Return the icon for the zone start button.""" return "mdi:sprinkler" @property def extra_state_attributes(self) -> dict[str, Any]: """Return entity specific state attributes.""" return { "zone_number": self._zone.zone_number, "zone_id": self._zone.zone_id, "enabled": self._zone.enabled, "quickrun_duration": self._zone.quickrun_duration, } async def async_press(self) -> None: """Start the zone with its quickrun duration. This method is called when the button is pressed in Home Assistant. It starts the irrigation zone for the duration specified in the corresponding number entity. The number entity is found using the exact unique_id pattern that follows the format: {device.mac}-zone-{zone.zone_number}-quickrun-duration Raises: HomeAssistantError: If the zone cannot be started or the number entity is invalid. """ try: # The quickrun duration field doesnt exist in the Wyze API # It has been created in Home Assistant as a number entity # to conveniently trigger a zone start with a specific duration # Get the device registry and find the device device_registry = dr.async_get(self.hass) device = device_registry.async_get_device( identifiers={(DOMAIN, self._device.mac)} ) if not device: raise HomeAssistantError(f"Device not found for MAC {self._device.mac}") # Get the entity registry entity_registry = er.async_get(self.hass) # Find the matching number entity using the zone number and device MAC # The number entities have unique_id pattern: {device.mac}-zone-{zone.zone_number}-quickrun-duration expected_unique_id = ( f"{self._device.mac}-zone-{self._zone.zone_number}-quickrun-duration" ) matching_entity = None for entity_id, entity in entity_registry.entities.items(): if ( entity.device_id == device.id and entity.platform == DOMAIN and entity_id.startswith("number.") and entity.unique_id == expected_unique_id ): matching_entity = entity_id break _LOGGER.debug( "Looking for number entity with unique_id: %s", expected_unique_id ) _LOGGER.debug("Found matching entity: %s", matching_entity) if not matching_entity: raise HomeAssistantError( f"No number entity found for zone {self._zone.name} (zone {self._zone.zone_number}, device: {self._device.mac})" ) # Get the current state of the number entity state = self.hass.states.get(matching_entity) if state is None or state.state in ["unavailable", "unknown"]: raise HomeAssistantError( f"Number entity {matching_entity} is unavailable or unknown" ) # Convert duration from minutes to seconds try: duration_minutes = float(state.state) if duration_minutes <= 0: raise ValueError("Duration must be greater than 0") duration_seconds = int(duration_minutes * 60) except ValueError as err: raise HomeAssistantError( f"Invalid duration {state.state} for {matching_entity}" ) from err _LOGGER.debug( "Starting zone %s (zone %s) for %s minutes (%s seconds)", self._zone.name, self._zone.zone_number, duration_minutes, duration_seconds, ) # Start the zone with the specified duration await self._irrigation_service.start_zone( self._device, self._zone.zone_number, duration_seconds ) except HomeAssistantError as err: _LOGGER.error("Failed to start zone %s: %s", self._zone.name, err) raise except Exception as err: _LOGGER.error("Unexpected error starting zone %s: %s", self._zone.name, err) raise HomeAssistantError( f"Failed to start zone {self._zone.name}: {err}" ) from err class WyzeIrrigationStopAllButton(ButtonEntity): """Representation of a Wyze Irrigation Stop All Schedules Button.""" _attr_name = "Stop All Zones" def __init__( self, irrigation_service: IrrigationService, irrigation: Irrigation ) -> None: """Initialize the irrigation stop all button.""" self._irrigation_service = irrigation_service self._device = irrigation @property def unique_id(self) -> str: """Return a unique ID for the button.""" return f"Stop All {self._device.mac}" @property def device_info(self) -> DeviceInfo: """Return device information about this entity.""" return DeviceInfo( identifiers={(DOMAIN, self._device.mac)}, name=self._device.nickname, manufacturer="WyzeLabs", model=self._device.product_model, serial_number=self._device.sn, connections={(dr.CONNECTION_NETWORK_MAC, self._device.mac)}, ) @property def device_class(self) -> str: """Return the device class of the button.""" return ButtonDeviceClass.RESTART @property def icon(self) -> str: """Return the icon for the stop all button.""" return "mdi:octagon" async def async_press(self) -> None: """Stop all running irrigation schedules. This method is called when the button is pressed in Home Assistant. It will stop all running irrigation schedules for the device. Raises: HomeAssistantError: If the schedules cannot be stopped. """ try: await self._irrigation_service.stop_running_schedule(self._device) except ClientConnectionError as err: raise HomeAssistantError(f"Failed to stop schedules: {err}") from err except Exception as err: _LOGGER.error("Error stopping schedules: %s", err) raise HomeAssistantError(f"Failed to stop schedules: {err}") from err class WyzePowerSensorResetButton(ButtonEntity): """Wyze Power Sensor Reset Button.""" _attr_has_entity_name = True _attr_should_poll = False _attr_entity_category = EntityCategory.CONFIG _attr_name = "Energy Usage Reset" def __init__(self, switch: Switch) -> None: """Initialize a power sensor reset button.""" self._switch = switch @property def device_info(self) -> DeviceInfo: """Return device information about this entity.""" return DeviceInfo( identifiers={(DOMAIN, self._switch.mac)}, name=self._switch.nickname, ) @property def unique_id(self) -> str: """Create a unique ID for the button.""" return f"{self._switch.mac} Reset button" async def async_press(self) -> None: """Reset the sensor usage.""" async_dispatcher_send( self.hass, f"{RESET_BUTTON_PRESSED}-{self._switch.mac}", self._switch, )