Initial Home Assistant commit
This commit is contained in:
@@ -0,0 +1,317 @@
|
||||
"""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,
|
||||
)
|
||||
Reference in New Issue
Block a user