From 34984a8af8efc5ef6d1d204404c517e7f7c2d1bb Mon Sep 17 00:00:00 2001 From: Leonardo Figueiro Date: Sun, 7 Aug 2022 06:07:01 -0300 Subject: [PATCH] Add switch to wilight (#62873) * Created switch.py and support * updated support.py * test for wilight switch * Update for Test * Updated test_switch.py * Trigger service with index * Updated support.py and switch.py * Updated support.py * Updated switch.py as PR#63614 * Updated switch.py * add type hints * Updated support.py * Updated switch.py * Updated switch.py and services.yaml * Updated pywilight * Update homeassistant/components/wilight/switch.py Co-authored-by: Martin Hjelmare * Update homeassistant/components/wilight/switch.py Co-authored-by: Martin Hjelmare * Update homeassistant/components/wilight/switch.py Co-authored-by: Martin Hjelmare * Update homeassistant/components/wilight/switch.py Co-authored-by: Martin Hjelmare * Update ci.yaml * Update ci.yaml * Updated as pywilight Renamed Device as PyWiLightDevice in pywilight. * Updated as pywilight Renamed Device as PyWiLightDevice in pywilight. * Updated as pywilight Renamed Device as PyWiLightDevice in pywilight. * Updated as pywilight Renamed Device as PyWiLightDevice in pywilight. * Update switch.py * Update homeassistant/components/wilight/support.py Co-authored-by: Martin Hjelmare * Update support.py * Update switch.py * Update support.py * Update support.py * Update switch.py * Update switch.py * Update services.yaml * Update switch.py * Update services.yaml * Update switch.py * Update homeassistant/components/wilight/switch.py Co-authored-by: Martin Hjelmare * Update homeassistant/components/wilight/switch.py Co-authored-by: Martin Hjelmare * Update homeassistant/components/wilight/switch.py Co-authored-by: Martin Hjelmare * Update switch.py * Update switch.py * Update switch.py * Update test_switch.py * Update test_switch.py * Update test_switch.py * Decrease exception scope * Clean up Co-authored-by: Martin Hjelmare --- homeassistant/components/wilight/__init__.py | 4 +- .../components/wilight/config_flow.py | 2 +- homeassistant/components/wilight/fan.py | 2 +- homeassistant/components/wilight/light.py | 2 +- .../components/wilight/manifest.json | 2 +- .../components/wilight/parent_device.py | 2 +- .../components/wilight/services.yaml | 24 ++ homeassistant/components/wilight/support.py | 87 +++++ homeassistant/components/wilight/switch.py | 322 ++++++++++++++++++ requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/wilight/__init__.py | 1 + tests/components/wilight/test_switch.py | 264 ++++++++++++++ 13 files changed, 707 insertions(+), 9 deletions(-) create mode 100644 homeassistant/components/wilight/services.yaml create mode 100644 homeassistant/components/wilight/support.py create mode 100644 homeassistant/components/wilight/switch.py create mode 100644 tests/components/wilight/test_switch.py diff --git a/homeassistant/components/wilight/__init__.py b/homeassistant/components/wilight/__init__.py index fefde1644ad..326265b8b3f 100644 --- a/homeassistant/components/wilight/__init__.py +++ b/homeassistant/components/wilight/__init__.py @@ -2,7 +2,7 @@ from typing import Any -from pywilight.wilight_device import Device as PyWiLightDevice +from pywilight.wilight_device import PyWiLightDevice from homeassistant.config_entries import ConfigEntry from homeassistant.const import Platform @@ -15,7 +15,7 @@ from .parent_device import WiLightParent DOMAIN = "wilight" # List the platforms that you want to support. -PLATFORMS = [Platform.COVER, Platform.FAN, Platform.LIGHT] +PLATFORMS = [Platform.COVER, Platform.FAN, Platform.LIGHT, Platform.SWITCH] async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: diff --git a/homeassistant/components/wilight/config_flow.py b/homeassistant/components/wilight/config_flow.py index dc8e5fc39cc..b7df1932cab 100644 --- a/homeassistant/components/wilight/config_flow.py +++ b/homeassistant/components/wilight/config_flow.py @@ -16,7 +16,7 @@ CONF_MODEL_NAME = "model_name" WILIGHT_MANUFACTURER = "All Automacao Ltda" # List the components supported by this integration. -ALLOWED_WILIGHT_COMPONENTS = ["cover", "fan", "light"] +ALLOWED_WILIGHT_COMPONENTS = ["cover", "fan", "light", "switch"] class WiLightFlowHandler(ConfigFlow, domain=DOMAIN): diff --git a/homeassistant/components/wilight/fan.py b/homeassistant/components/wilight/fan.py index c598e6db397..3d0c6d0ff39 100644 --- a/homeassistant/components/wilight/fan.py +++ b/homeassistant/components/wilight/fan.py @@ -13,7 +13,7 @@ from pywilight.const import ( WL_SPEED_LOW, WL_SPEED_MEDIUM, ) -from pywilight.wilight_device import Device as PyWiLightDevice +from pywilight.wilight_device import PyWiLightDevice from homeassistant.components.fan import DIRECTION_FORWARD, FanEntity, FanEntityFeature from homeassistant.config_entries import ConfigEntry diff --git a/homeassistant/components/wilight/light.py b/homeassistant/components/wilight/light.py index ea9e19dcb30..2509dc50737 100644 --- a/homeassistant/components/wilight/light.py +++ b/homeassistant/components/wilight/light.py @@ -4,7 +4,7 @@ from __future__ import annotations from typing import Any from pywilight.const import ITEM_LIGHT, LIGHT_COLOR, LIGHT_DIMMER, LIGHT_ON_OFF -from pywilight.wilight_device import Device as PyWiLightDevice +from pywilight.wilight_device import PyWiLightDevice from homeassistant.components.light import ( ATTR_BRIGHTNESS, diff --git a/homeassistant/components/wilight/manifest.json b/homeassistant/components/wilight/manifest.json index 972de72a9c9..b1be0c80122 100644 --- a/homeassistant/components/wilight/manifest.json +++ b/homeassistant/components/wilight/manifest.json @@ -3,7 +3,7 @@ "name": "WiLight", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/wilight", - "requirements": ["pywilight==0.0.70"], + "requirements": ["pywilight==0.0.74"], "ssdp": [ { "manufacturer": "All Automacao Ltda" diff --git a/homeassistant/components/wilight/parent_device.py b/homeassistant/components/wilight/parent_device.py index 17a33fef633..8091e78cc76 100644 --- a/homeassistant/components/wilight/parent_device.py +++ b/homeassistant/components/wilight/parent_device.py @@ -5,7 +5,7 @@ import asyncio import logging import pywilight -from pywilight.wilight_device import Device as PyWiLightDevice +from pywilight.wilight_device import PyWiLightDevice import requests from homeassistant.config_entries import ConfigEntry diff --git a/homeassistant/components/wilight/services.yaml b/homeassistant/components/wilight/services.yaml new file mode 100644 index 00000000000..07a545bd5d7 --- /dev/null +++ b/homeassistant/components/wilight/services.yaml @@ -0,0 +1,24 @@ +set_watering_time: + description: Set watering time + target: + fields: + watering_time: + description: Duration for this irrigation to be turned on + example: 30 +set_pause_time: + description: Set pause time + target: + fields: + pause_time: + description: Duration for this irrigation to be paused + example: 24 +set_trigger: + description: Set trigger + target: + fields: + trigger_index: + description: Index of Trigger from 1 to 4 + example: "1" + trigger: + description: Configuration of trigger + example: "'12707001'" diff --git a/homeassistant/components/wilight/support.py b/homeassistant/components/wilight/support.py new file mode 100644 index 00000000000..6a03a854c70 --- /dev/null +++ b/homeassistant/components/wilight/support.py @@ -0,0 +1,87 @@ +"""Support for config validation using voluptuous and Translate Trigger.""" +from __future__ import annotations + +import calendar +import locale +import re +from typing import Any + +import voluptuous as vol + + +def wilight_trigger(value: Any) -> str | None: + """Check rules for WiLight Trigger.""" + step = 1 + err_desc = "Value is None" + result_128 = False + result_24 = False + result_60 = False + result_2 = False + + if value is not None: + step = 2 + err_desc = "Expected a string" + + if (step == 2) & isinstance(value, str): + step = 3 + err_desc = "String should only contain 8 decimals character" + if re.search(r"^([0-9]{8})$", value) is not None: + step = 4 + err_desc = "First 3 character should be less than 128" + result_128 = int(value[0:3]) < 128 + result_24 = int(value[3:5]) < 24 + result_60 = int(value[5:7]) < 60 + result_2 = int(value[7:8]) < 2 + + if (step == 4) & result_128: + step = 5 + err_desc = "Hour part should be less than 24" + + if (step == 5) & result_24: + step = 6 + err_desc = "Minute part should be less than 60" + + if (step == 6) & result_60: + step = 7 + err_desc = "Active part shoul be less than 2" + + if (step == 7) & result_2: + return value + + raise vol.Invalid(err_desc) + + +def wilight_to_hass_trigger(value: str | None) -> str | None: + """Convert wilight trigger to hass description. + + Ex: "12719001" -> "sun mon tue wed thu fri sat 19:00 On" + "00000000" -> "00:00 Off" + """ + if value is None: + return value + + locale.setlocale(locale.LC_ALL, "") + week_days = list(calendar.day_abbr) + days = bin(int(value[0:3]))[2:].zfill(8) + desc = "" + if int(days[7:8]) == 1: + desc += f"{week_days[6]} " + if int(days[6:7]) == 1: + desc += f"{week_days[0]} " + if int(days[5:6]) == 1: + desc += f"{week_days[1]} " + if int(days[4:5]) == 1: + desc += f"{week_days[2]} " + if int(days[3:4]) == 1: + desc += f"{week_days[3]} " + if int(days[2:3]) == 1: + desc += f"{week_days[4]} " + if int(days[1:2]) == 1: + desc += f"{week_days[5]} " + desc += f"{value[3:5]}:{value[5:7]} " + if int(value[7:8]) == 1: + desc += "On" + else: + desc += "Off" + + return desc diff --git a/homeassistant/components/wilight/switch.py b/homeassistant/components/wilight/switch.py new file mode 100644 index 00000000000..920fb66908c --- /dev/null +++ b/homeassistant/components/wilight/switch.py @@ -0,0 +1,322 @@ +"""Support for WiLight switches.""" +from __future__ import annotations + +from typing import Any + +from pywilight.const import ITEM_SWITCH, SWITCH_PAUSE_VALVE, SWITCH_VALVE +from pywilight.wilight_device import PyWiLightDevice +import voluptuous as vol + +from homeassistant.components.switch import SwitchEntity +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_platform +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from . import DOMAIN, WiLightDevice +from .parent_device import WiLightParent +from .support import wilight_to_hass_trigger, wilight_trigger as wl_trigger + +# Attr of features supported by the valve switch entities +ATTR_WATERING_TIME = "watering_time" +ATTR_PAUSE_TIME = "pause_time" +ATTR_TRIGGER_1 = "trigger_1" +ATTR_TRIGGER_2 = "trigger_2" +ATTR_TRIGGER_3 = "trigger_3" +ATTR_TRIGGER_4 = "trigger_4" +ATTR_TRIGGER_1_DESC = "trigger_1_description" +ATTR_TRIGGER_2_DESC = "trigger_2_description" +ATTR_TRIGGER_3_DESC = "trigger_3_description" +ATTR_TRIGGER_4_DESC = "trigger_4_description" + +# Attr of services data supported by the valve switch entities +ATTR_TRIGGER = "trigger" +ATTR_TRIGGER_INDEX = "trigger_index" + +# Service of features supported by the valve switch entities +SERVICE_SET_WATERING_TIME = "set_watering_time" +SERVICE_SET_PAUSE_TIME = "set_pause_time" +SERVICE_SET_TRIGGER = "set_trigger" + +# Range of features supported by the valve switch entities +RANGE_WATERING_TIME = 1800 +RANGE_PAUSE_TIME = 24 +RANGE_TRIGGER_INDEX = 4 + +# Service call validation schemas +VALID_WATERING_TIME = vol.All( + vol.Coerce(int), vol.Range(min=1, max=RANGE_WATERING_TIME) +) +VALID_PAUSE_TIME = vol.All(vol.Coerce(int), vol.Range(min=1, max=RANGE_PAUSE_TIME)) +VALID_TRIGGER_INDEX = vol.All( + vol.Coerce(int), vol.Range(min=1, max=RANGE_TRIGGER_INDEX) +) + +# Descriptions of the valve switch entities +DESC_WATERING = "watering" +DESC_PAUSE = "pause" + +# Icons of the valve switch entities +ICON_WATERING = "mdi:water" +ICON_PAUSE = "mdi:pause-circle-outline" + + +def entities_from_discovered_wilight(api_device: PyWiLightDevice) -> tuple[Any]: + """Parse configuration and add WiLight switch entities.""" + entities: Any = [] + for item in api_device.items: + if item["type"] == ITEM_SWITCH: + index = item["index"] + item_name = item["name"] + if item["sub_type"] == SWITCH_VALVE: + entities.append(WiLightValveSwitch(api_device, index, item_name)) + elif item["sub_type"] == SWITCH_PAUSE_VALVE: + entities.append(WiLightValvePauseSwitch(api_device, index, item_name)) + + return entities + + +async def async_setup_entry( + hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback +) -> None: + """Set up WiLight switches from a config entry.""" + parent: WiLightParent = hass.data[DOMAIN][entry.entry_id] + + # Handle a discovered WiLight device. + assert parent.api + entities = entities_from_discovered_wilight(parent.api) + async_add_entities(entities) + + # Handle services for a discovered WiLight device. + async def set_watering_time(entity, service: Any) -> None: + if not isinstance(entity, WiLightValveSwitch): + raise ValueError("Entity is not a WiLight valve switch") + watering_time = service.data[ATTR_WATERING_TIME] + await entity.async_set_watering_time(watering_time=watering_time) + + async def set_trigger(entity, service: Any) -> None: + if not isinstance(entity, WiLightValveSwitch): + raise ValueError("Entity is not a WiLight valve switch") + trigger_index = service.data[ATTR_TRIGGER_INDEX] + trigger = service.data[ATTR_TRIGGER] + await entity.async_set_trigger(trigger_index=trigger_index, trigger=trigger) + + async def set_pause_time(entity, service: Any) -> None: + if not isinstance(entity, WiLightValvePauseSwitch): + raise ValueError("Entity is not a WiLight valve pause switch") + pause_time = service.data[ATTR_PAUSE_TIME] + await entity.async_set_pause_time(pause_time=pause_time) + + platform = entity_platform.async_get_current_platform() + + platform.async_register_entity_service( + SERVICE_SET_WATERING_TIME, + { + vol.Required(ATTR_WATERING_TIME): VALID_WATERING_TIME, + }, + set_watering_time, + ) + + platform.async_register_entity_service( + SERVICE_SET_TRIGGER, + { + vol.Required(ATTR_TRIGGER_INDEX): VALID_TRIGGER_INDEX, + vol.Required(ATTR_TRIGGER): wl_trigger, + }, + set_trigger, + ) + + platform.async_register_entity_service( + SERVICE_SET_PAUSE_TIME, + { + vol.Required(ATTR_PAUSE_TIME): VALID_PAUSE_TIME, + }, + set_pause_time, + ) + + +def wilight_to_hass_pause_time(value: int) -> int: + """Convert wilight pause_time seconds to hass hour.""" + return round(value / 3600) + + +def hass_to_wilight_pause_time(value: int) -> int: + """Convert hass pause_time hours to wilight seconds.""" + return round(value * 3600) + + +class WiLightValveSwitch(WiLightDevice, SwitchEntity): + """Representation of a WiLights Valve switch.""" + + @property + def name(self) -> str: + """Return the name of the switch.""" + return f"{self._attr_name} {DESC_WATERING}" + + @property + def is_on(self) -> bool: + """Return true if device is on.""" + return self._status.get("on", False) + + @property + def watering_time(self) -> int | None: + """Return watering time of valve switch. + + None is unknown, 1 is minimum, 1800 is maximum. + """ + return self._status.get("timer_target") + + @property + def trigger_1(self) -> str | None: + """Return trigger_1 of valve switch.""" + return self._status.get("trigger_1") + + @property + def trigger_2(self) -> str | None: + """Return trigger_2 of valve switch.""" + return self._status.get("trigger_2") + + @property + def trigger_3(self) -> str | None: + """Return trigger_3 of valve switch.""" + return self._status.get("trigger_3") + + @property + def trigger_4(self) -> str | None: + """Return trigger_4 of valve switch.""" + return self._status.get("trigger_4") + + @property + def trigger_1_description(self) -> str | None: + """Return trigger_1_description of valve switch.""" + return wilight_to_hass_trigger(self._status.get("trigger_1")) + + @property + def trigger_2_description(self) -> str | None: + """Return trigger_2_description of valve switch.""" + return wilight_to_hass_trigger(self._status.get("trigger_2")) + + @property + def trigger_3_description(self) -> str | None: + """Return trigger_3_description of valve switch.""" + return wilight_to_hass_trigger(self._status.get("trigger_3")) + + @property + def trigger_4_description(self) -> str | None: + """Return trigger_4_description of valve switch.""" + return wilight_to_hass_trigger(self._status.get("trigger_4")) + + @property + def extra_state_attributes(self) -> dict[str, Any]: + """Return the state attributes.""" + attr: dict[str, Any] = {} + + if self.watering_time is not None: + attr[ATTR_WATERING_TIME] = self.watering_time + + if self.trigger_1 is not None: + attr[ATTR_TRIGGER_1] = self.trigger_1 + + if self.trigger_2 is not None: + attr[ATTR_TRIGGER_2] = self.trigger_2 + + if self.trigger_3 is not None: + attr[ATTR_TRIGGER_3] = self.trigger_3 + + if self.trigger_4 is not None: + attr[ATTR_TRIGGER_4] = self.trigger_4 + + if self.trigger_1_description is not None: + attr[ATTR_TRIGGER_1_DESC] = self.trigger_1_description + + if self.trigger_2_description is not None: + attr[ATTR_TRIGGER_2_DESC] = self.trigger_2_description + + if self.trigger_3_description is not None: + attr[ATTR_TRIGGER_3_DESC] = self.trigger_3_description + + if self.trigger_4_description is not None: + attr[ATTR_TRIGGER_4_DESC] = self.trigger_4_description + + return attr + + @property + def icon(self) -> str: + """Return the icon to use in the frontend.""" + return ICON_WATERING + + async def async_turn_on(self, **kwargs: Any) -> None: + """Turn the device on.""" + await self._client.turn_on(self._index) + + async def async_turn_off(self, **kwargs: Any) -> None: + """Turn the device off.""" + await self._client.turn_off(self._index) + + async def async_set_watering_time(self, watering_time: int) -> None: + """Set the watering time.""" + await self._client.set_switch_time(self._index, watering_time) + + async def async_set_trigger(self, trigger_index: int, trigger: str) -> None: + """Set the trigger according to index.""" + if trigger_index == 1: + await self._client.set_switch_trigger_1(self._index, trigger) + if trigger_index == 2: + await self._client.set_switch_trigger_2(self._index, trigger) + if trigger_index == 3: + await self._client.set_switch_trigger_3(self._index, trigger) + if trigger_index == 4: + await self._client.set_switch_trigger_4(self._index, trigger) + + +class WiLightValvePauseSwitch(WiLightDevice, SwitchEntity): + """Representation of a WiLights Valve Pause switch.""" + + @property + def name(self) -> str: + """Return the name of the switch.""" + return f"{self._attr_name} {DESC_PAUSE}" + + @property + def is_on(self) -> bool: + """Return true if device is on.""" + return self._status.get("on", False) + + @property + def pause_time(self) -> int | None: + """Return pause time of valve switch. + + None is unknown, 1 is minimum, 24 is maximum. + """ + pause_time = self._status.get("timer_target") + if pause_time is not None: + return wilight_to_hass_pause_time(pause_time) + return pause_time + + @property + def extra_state_attributes(self) -> dict[str, Any]: + """Return the state attributes.""" + attr: dict[str, Any] = {} + + if self.pause_time is not None: + attr[ATTR_PAUSE_TIME] = self.pause_time + + return attr + + @property + def icon(self) -> str: + """Return the icon to use in the frontend.""" + return ICON_PAUSE + + async def async_turn_on(self, **kwargs: Any) -> None: + """Turn the device on.""" + await self._client.turn_on(self._index) + + async def async_turn_off(self, **kwargs: Any) -> None: + """Turn the device off.""" + await self._client.turn_off(self._index) + + async def async_set_pause_time(self, pause_time: int) -> None: + """Set the pause time.""" + target_time = hass_to_wilight_pause_time(pause_time) + await self._client.set_switch_time(self._index, target_time) diff --git a/requirements_all.txt b/requirements_all.txt index 8dbcab9d520..44075fdf14a 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2039,7 +2039,7 @@ pywebpush==1.9.2 pywemo==0.9.1 # homeassistant.components.wilight -pywilight==0.0.70 +pywilight==0.0.74 # homeassistant.components.wiz pywizlight==0.5.14 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index eba56137019..5470bf91d73 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1378,7 +1378,7 @@ pywebpush==1.9.2 pywemo==0.9.1 # homeassistant.components.wilight -pywilight==0.0.70 +pywilight==0.0.74 # homeassistant.components.wiz pywizlight==0.5.14 diff --git a/tests/components/wilight/__init__.py b/tests/components/wilight/__init__.py index dbcfbbaaa8c..acaf2aef2a8 100644 --- a/tests/components/wilight/__init__.py +++ b/tests/components/wilight/__init__.py @@ -27,6 +27,7 @@ UPNP_MODEL_NAME_DIMMER = "WiLight 0100001700020009-10010010" UPNP_MODEL_NAME_COLOR = "WiLight 0107001800020009-11010" UPNP_MODEL_NAME_LIGHT_FAN = "WiLight 0104001800010009-10" UPNP_MODEL_NAME_COVER = "WiLight 0103001800010009-10" +UPNP_MODEL_NAME_SWITCH = "WiLight 0105001900010011-00000000000010" UPNP_MODEL_NUMBER = "123456789012345678901234567890123456" UPNP_SERIAL = "000000000099" UPNP_MAC_ADDRESS = "5C:CF:7F:8B:CA:56" diff --git a/tests/components/wilight/test_switch.py b/tests/components/wilight/test_switch.py new file mode 100644 index 00000000000..035f5b37be5 --- /dev/null +++ b/tests/components/wilight/test_switch.py @@ -0,0 +1,264 @@ +"""Tests for the WiLight integration.""" +from unittest.mock import patch + +import pytest +import pywilight + +from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN +from homeassistant.components.wilight import DOMAIN as WILIGHT_DOMAIN +from homeassistant.components.wilight.switch import ( + ATTR_PAUSE_TIME, + ATTR_TRIGGER, + ATTR_TRIGGER_1, + ATTR_TRIGGER_2, + ATTR_TRIGGER_3, + ATTR_TRIGGER_4, + ATTR_TRIGGER_INDEX, + ATTR_WATERING_TIME, + SERVICE_SET_PAUSE_TIME, + SERVICE_SET_TRIGGER, + SERVICE_SET_WATERING_TIME, +) +from homeassistant.const import ( + ATTR_ENTITY_ID, + SERVICE_TURN_OFF, + SERVICE_TURN_ON, + STATE_OFF, + STATE_ON, +) +from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry as er + +from . import ( + HOST, + UPNP_MAC_ADDRESS, + UPNP_MODEL_NAME_SWITCH, + UPNP_MODEL_NUMBER, + UPNP_SERIAL, + WILIGHT_ID, + setup_integration, +) + + +@pytest.fixture(name="dummy_device_from_host_switch") +def mock_dummy_device_from_host_switch(): + """Mock a valid api_devce.""" + + device = pywilight.wilight_from_discovery( + f"http://{HOST}:45995/wilight.xml", + UPNP_MAC_ADDRESS, + UPNP_MODEL_NAME_SWITCH, + UPNP_SERIAL, + UPNP_MODEL_NUMBER, + ) + + device.set_dummy(True) + + with patch( + "pywilight.device_from_host", + return_value=device, + ): + yield device + + +async def test_loading_switch( + hass: HomeAssistant, + dummy_device_from_host_switch, +) -> None: + """Test the WiLight configuration entry loading.""" + + entry = await setup_integration(hass) + assert entry + assert entry.unique_id == WILIGHT_ID + + entity_registry = er.async_get(hass) + + # First segment of the strip + state = hass.states.get("switch.wl000000000099_1_watering") + assert state + assert state.state == STATE_OFF + + entry = entity_registry.async_get("switch.wl000000000099_1_watering") + assert entry + assert entry.unique_id == "WL000000000099_0" + + # Seconnd segment of the strip + state = hass.states.get("switch.wl000000000099_2_pause") + assert state + assert state.state == STATE_OFF + + entry = entity_registry.async_get("switch.wl000000000099_2_pause") + assert entry + assert entry.unique_id == "WL000000000099_1" + + +async def test_on_off_switch_state( + hass: HomeAssistant, dummy_device_from_host_switch +) -> None: + """Test the change of state of the switch.""" + await setup_integration(hass) + + # On - watering + await hass.services.async_call( + SWITCH_DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: "switch.wl000000000099_1_watering"}, + blocking=True, + ) + + await hass.async_block_till_done() + state = hass.states.get("switch.wl000000000099_1_watering") + assert state + assert state.state == STATE_ON + + # Off - watering + await hass.services.async_call( + SWITCH_DOMAIN, + SERVICE_TURN_OFF, + {ATTR_ENTITY_ID: "switch.wl000000000099_1_watering"}, + blocking=True, + ) + + await hass.async_block_till_done() + state = hass.states.get("switch.wl000000000099_1_watering") + assert state + assert state.state == STATE_OFF + + # On - pause + await hass.services.async_call( + SWITCH_DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: "switch.wl000000000099_2_pause"}, + blocking=True, + ) + + await hass.async_block_till_done() + state = hass.states.get("switch.wl000000000099_2_pause") + assert state + assert state.state == STATE_ON + + # Off - pause + await hass.services.async_call( + SWITCH_DOMAIN, + SERVICE_TURN_OFF, + {ATTR_ENTITY_ID: "switch.wl000000000099_2_pause"}, + blocking=True, + ) + + await hass.async_block_till_done() + state = hass.states.get("switch.wl000000000099_2_pause") + assert state + assert state.state == STATE_OFF + + +async def test_switch_services( + hass: HomeAssistant, dummy_device_from_host_switch +) -> None: + """Test the services of the switch.""" + await setup_integration(hass) + + # Set watering time + await hass.services.async_call( + WILIGHT_DOMAIN, + SERVICE_SET_WATERING_TIME, + {ATTR_WATERING_TIME: 30, ATTR_ENTITY_ID: "switch.wl000000000099_1_watering"}, + blocking=True, + ) + + await hass.async_block_till_done() + state = hass.states.get("switch.wl000000000099_1_watering") + assert state + assert state.attributes.get(ATTR_WATERING_TIME) == 30 + + # Set pause time + await hass.services.async_call( + WILIGHT_DOMAIN, + SERVICE_SET_PAUSE_TIME, + {ATTR_PAUSE_TIME: 18, ATTR_ENTITY_ID: "switch.wl000000000099_2_pause"}, + blocking=True, + ) + + await hass.async_block_till_done() + state = hass.states.get("switch.wl000000000099_2_pause") + assert state + assert state.attributes.get(ATTR_PAUSE_TIME) == 18 + + # Set trigger_1 + await hass.services.async_call( + WILIGHT_DOMAIN, + SERVICE_SET_TRIGGER, + { + ATTR_TRIGGER_INDEX: "1", + ATTR_TRIGGER: "12715301", + ATTR_ENTITY_ID: "switch.wl000000000099_1_watering", + }, + blocking=True, + ) + + await hass.async_block_till_done() + state = hass.states.get("switch.wl000000000099_1_watering") + assert state + assert state.attributes.get(ATTR_TRIGGER_1) == "12715301" + + # Set trigger_2 + await hass.services.async_call( + WILIGHT_DOMAIN, + SERVICE_SET_TRIGGER, + { + ATTR_TRIGGER_INDEX: "2", + ATTR_TRIGGER: "12707301", + ATTR_ENTITY_ID: "switch.wl000000000099_1_watering", + }, + blocking=True, + ) + + await hass.async_block_till_done() + state = hass.states.get("switch.wl000000000099_1_watering") + assert state + assert state.attributes.get(ATTR_TRIGGER_2) == "12707301" + + # Set trigger_3 + await hass.services.async_call( + WILIGHT_DOMAIN, + SERVICE_SET_TRIGGER, + { + ATTR_TRIGGER_INDEX: "3", + ATTR_TRIGGER: "00015301", + ATTR_ENTITY_ID: "switch.wl000000000099_1_watering", + }, + blocking=True, + ) + + await hass.async_block_till_done() + state = hass.states.get("switch.wl000000000099_1_watering") + assert state + assert state.attributes.get(ATTR_TRIGGER_3) == "00015301" + + # Set trigger_4 + await hass.services.async_call( + WILIGHT_DOMAIN, + SERVICE_SET_TRIGGER, + { + ATTR_TRIGGER_INDEX: "4", + ATTR_TRIGGER: "00008300", + ATTR_ENTITY_ID: "switch.wl000000000099_1_watering", + }, + blocking=True, + ) + + await hass.async_block_till_done() + state = hass.states.get("switch.wl000000000099_1_watering") + assert state + assert state.attributes.get(ATTR_TRIGGER_4) == "00008300" + + # Set watering time using WiLight Pause Switch to raise + with pytest.raises(ValueError) as exc_info: + await hass.services.async_call( + WILIGHT_DOMAIN, + SERVICE_SET_WATERING_TIME, + {ATTR_WATERING_TIME: 30, ATTR_ENTITY_ID: "switch.wl000000000099_2_pause"}, + blocking=True, + ) + + await hass.async_block_till_done() + assert str(exc_info.value) == "Entity is not a WiLight valve switch"