From 72458d143dac1cc3df891bf5bf03c174d0eca375 Mon Sep 17 00:00:00 2001 From: David Knowles Date: Sun, 7 Jul 2024 14:42:45 -0400 Subject: [PATCH] Add valve support to Hydrawise (#116302) --- .../components/hydrawise/__init__.py | 7 +- homeassistant/components/hydrawise/valve.py | 66 +++++++++++++ .../hydrawise/snapshots/test_valve.ambr | 99 +++++++++++++++++++ tests/components/hydrawise/test_valve.py | 59 +++++++++++ 4 files changed, 230 insertions(+), 1 deletion(-) create mode 100644 homeassistant/components/hydrawise/valve.py create mode 100644 tests/components/hydrawise/snapshots/test_valve.ambr create mode 100644 tests/components/hydrawise/test_valve.py diff --git a/homeassistant/components/hydrawise/__init__.py b/homeassistant/components/hydrawise/__init__.py index b4e14c42709..d2af8f37e36 100644 --- a/homeassistant/components/hydrawise/__init__.py +++ b/homeassistant/components/hydrawise/__init__.py @@ -10,7 +10,12 @@ from homeassistant.exceptions import ConfigEntryAuthFailed from .const import DOMAIN, SCAN_INTERVAL from .coordinator import HydrawiseDataUpdateCoordinator -PLATFORMS: list[Platform] = [Platform.BINARY_SENSOR, Platform.SENSOR, Platform.SWITCH] +PLATFORMS: list[Platform] = [ + Platform.BINARY_SENSOR, + Platform.SENSOR, + Platform.SWITCH, + Platform.VALVE, +] async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool: diff --git a/homeassistant/components/hydrawise/valve.py b/homeassistant/components/hydrawise/valve.py new file mode 100644 index 00000000000..6ceb3673c71 --- /dev/null +++ b/homeassistant/components/hydrawise/valve.py @@ -0,0 +1,66 @@ +"""Support for Hydrawise sprinkler valves.""" + +from __future__ import annotations + +from typing import Any + +from pydrawise.schema import Zone + +from homeassistant.components.valve import ( + ValveDeviceClass, + ValveEntity, + ValveEntityDescription, + ValveEntityFeature, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from .const import DOMAIN +from .coordinator import HydrawiseDataUpdateCoordinator +from .entity import HydrawiseEntity + +VALVE_TYPES: tuple[ValveEntityDescription, ...] = ( + ValveEntityDescription( + key="zone", + device_class=ValveDeviceClass.WATER, + ), +) + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up the Hydrawise valve platform.""" + coordinator: HydrawiseDataUpdateCoordinator = hass.data[DOMAIN][ + config_entry.entry_id + ] + async_add_entities( + HydrawiseValve(coordinator, description, controller, zone_id=zone.id) + for controller in coordinator.data.controllers.values() + for zone in controller.zones + for description in VALVE_TYPES + ) + + +class HydrawiseValve(HydrawiseEntity, ValveEntity): + """A Hydrawise valve.""" + + _attr_name = None + _attr_reports_position = False + _attr_supported_features = ValveEntityFeature.OPEN | ValveEntityFeature.CLOSE + zone: Zone + + async def async_open_valve(self, **kwargs: Any) -> None: + """Open the valve.""" + await self.coordinator.api.start_zone(self.zone) + + async def async_close_valve(self) -> None: + """Close the valve.""" + await self.coordinator.api.stop_zone(self.zone) + + def _update_attrs(self) -> None: + """Update state attributes.""" + self._attr_is_closed = self.zone.scheduled_runs.current_run is None diff --git a/tests/components/hydrawise/snapshots/test_valve.ambr b/tests/components/hydrawise/snapshots/test_valve.ambr new file mode 100644 index 00000000000..cac08893324 --- /dev/null +++ b/tests/components/hydrawise/snapshots/test_valve.ambr @@ -0,0 +1,99 @@ +# serializer version: 1 +# name: test_all_valves[valve.zone_one-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'valve', + 'entity_category': None, + 'entity_id': 'valve.zone_one', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': None, + 'platform': 'hydrawise', + 'previous_unique_id': None, + 'supported_features': , + 'translation_key': None, + 'unique_id': '5965394_zone', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_valves[valve.zone_one-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by hydrawise.com', + 'device_class': 'water', + 'friendly_name': 'Zone One', + 'supported_features': , + }), + 'context': , + 'entity_id': 'valve.zone_one', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'closed', + }) +# --- +# name: test_all_valves[valve.zone_two-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'valve', + 'entity_category': None, + 'entity_id': 'valve.zone_two', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': None, + 'platform': 'hydrawise', + 'previous_unique_id': None, + 'supported_features': , + 'translation_key': None, + 'unique_id': '5965395_zone', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_valves[valve.zone_two-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by hydrawise.com', + 'device_class': 'water', + 'friendly_name': 'Zone Two', + 'supported_features': , + }), + 'context': , + 'entity_id': 'valve.zone_two', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'open', + }) +# --- diff --git a/tests/components/hydrawise/test_valve.py b/tests/components/hydrawise/test_valve.py new file mode 100644 index 00000000000..918fae00017 --- /dev/null +++ b/tests/components/hydrawise/test_valve.py @@ -0,0 +1,59 @@ +"""Test Hydrawise valve.""" + +from collections.abc import Awaitable, Callable +from unittest.mock import AsyncMock, patch + +from pydrawise.schema import Zone +from syrupy.assertion import SnapshotAssertion + +from homeassistant.components.valve import DOMAIN +from homeassistant.const import ( + ATTR_ENTITY_ID, + SERVICE_CLOSE_VALVE, + SERVICE_OPEN_VALVE, + Platform, +) +from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry as er + +from tests.common import MockConfigEntry, snapshot_platform + + +async def test_all_valves( + hass: HomeAssistant, + mock_add_config_entry: Callable[[], Awaitable[MockConfigEntry]], + entity_registry: er.EntityRegistry, + snapshot: SnapshotAssertion, +) -> None: + """Test that all valves are working.""" + with patch( + "homeassistant.components.hydrawise.PLATFORMS", + [Platform.VALVE], + ): + config_entry = await mock_add_config_entry() + await snapshot_platform(hass, entity_registry, snapshot, config_entry.entry_id) + + +async def test_services( + hass: HomeAssistant, + mock_added_config_entry: MockConfigEntry, + mock_pydrawise: AsyncMock, + zones: list[Zone], +) -> None: + """Test valve services.""" + await hass.services.async_call( + DOMAIN, + SERVICE_OPEN_VALVE, + service_data={ATTR_ENTITY_ID: "valve.zone_one"}, + blocking=True, + ) + mock_pydrawise.start_zone.assert_called_once_with(zones[0]) + mock_pydrawise.reset_mock() + + await hass.services.async_call( + DOMAIN, + SERVICE_CLOSE_VALVE, + service_data={ATTR_ENTITY_ID: "valve.zone_one"}, + blocking=True, + ) + mock_pydrawise.stop_zone.assert_called_once_with(zones[0])