From 5b232226e99c8a136f32b6bf5ceca2eba16f8b35 Mon Sep 17 00:00:00 2001 From: yufeng Date: Tue, 12 Aug 2025 16:53:08 +0800 Subject: [PATCH] Add timers and switches to Tuya irrigation systems (#149236) --- homeassistant/components/tuya/const.py | 9 + homeassistant/components/tuya/number.py | 60 +++ homeassistant/components/tuya/strings.json | 8 + homeassistant/components/tuya/valve.py | 140 ++++++ tests/components/tuya/__init__.py | 1 + .../tuya/fixtures/sfkzq_ed7frwissyqrejic.json | 282 +++++++++++ .../tuya/snapshots/test_number.ambr | 472 ++++++++++++++++++ .../tuya/snapshots/test_sensor.ambr | 101 ++++ .../components/tuya/snapshots/test_valve.ambr | 401 +++++++++++++++ tests/components/tuya/test_valve.py | 96 ++++ 10 files changed, 1570 insertions(+) create mode 100644 homeassistant/components/tuya/valve.py create mode 100644 tests/components/tuya/fixtures/sfkzq_ed7frwissyqrejic.json create mode 100644 tests/components/tuya/snapshots/test_valve.ambr create mode 100644 tests/components/tuya/test_valve.py diff --git a/homeassistant/components/tuya/const.py b/homeassistant/components/tuya/const.py index 62a6c904a1f..752911f98f6 100644 --- a/homeassistant/components/tuya/const.py +++ b/homeassistant/components/tuya/const.py @@ -66,6 +66,7 @@ PLATFORMS = [ Platform.SIREN, Platform.SWITCH, Platform.VACUUM, + Platform.VALVE, ] @@ -166,6 +167,14 @@ class DPCode(StrEnum): CONTROL_BACK = "control_back" CONTROL_BACK_MODE = "control_back_mode" COUNTDOWN = "countdown" # Countdown + COUNTDOWN_1 = "countdown_1" + COUNTDOWN_2 = "countdown_2" + COUNTDOWN_3 = "countdown_3" + COUNTDOWN_4 = "countdown_4" + COUNTDOWN_5 = "countdown_5" + COUNTDOWN_6 = "countdown_6" + COUNTDOWN_7 = "countdown_7" + COUNTDOWN_8 = "countdown_8" COUNTDOWN_LEFT = "countdown_left" COUNTDOWN_SET = "countdown_set" # Countdown setting CRY_DETECTION_SWITCH = "cry_detection_switch" diff --git a/homeassistant/components/tuya/number.py b/homeassistant/components/tuya/number.py index 88216ae3d06..7fadaa0489b 100644 --- a/homeassistant/components/tuya/number.py +++ b/homeassistant/components/tuya/number.py @@ -224,6 +224,66 @@ NUMBERS: dict[str, tuple[NumberEntityDescription, ...]] = { entity_category=EntityCategory.CONFIG, ), ), + # Smart Water Timer + "sfkzq": ( + # Controls the irrigation duration for the water valve + NumberEntityDescription( + key=DPCode.COUNTDOWN_1, + translation_key="indexed_irrigation_duration", + translation_placeholders={"index": "1"}, + device_class=NumberDeviceClass.DURATION, + entity_category=EntityCategory.CONFIG, + ), + NumberEntityDescription( + key=DPCode.COUNTDOWN_2, + translation_key="indexed_irrigation_duration", + translation_placeholders={"index": "2"}, + device_class=NumberDeviceClass.DURATION, + entity_category=EntityCategory.CONFIG, + ), + NumberEntityDescription( + key=DPCode.COUNTDOWN_3, + translation_key="indexed_irrigation_duration", + translation_placeholders={"index": "3"}, + device_class=NumberDeviceClass.DURATION, + entity_category=EntityCategory.CONFIG, + ), + NumberEntityDescription( + key=DPCode.COUNTDOWN_4, + translation_key="indexed_irrigation_duration", + translation_placeholders={"index": "4"}, + device_class=NumberDeviceClass.DURATION, + entity_category=EntityCategory.CONFIG, + ), + NumberEntityDescription( + key=DPCode.COUNTDOWN_5, + translation_key="indexed_irrigation_duration", + translation_placeholders={"index": "5"}, + device_class=NumberDeviceClass.DURATION, + entity_category=EntityCategory.CONFIG, + ), + NumberEntityDescription( + key=DPCode.COUNTDOWN_6, + translation_key="indexed_irrigation_duration", + translation_placeholders={"index": "6"}, + device_class=NumberDeviceClass.DURATION, + entity_category=EntityCategory.CONFIG, + ), + NumberEntityDescription( + key=DPCode.COUNTDOWN_7, + translation_key="indexed_irrigation_duration", + translation_placeholders={"index": "7"}, + device_class=NumberDeviceClass.DURATION, + entity_category=EntityCategory.CONFIG, + ), + NumberEntityDescription( + key=DPCode.COUNTDOWN_8, + translation_key="indexed_irrigation_duration", + translation_placeholders={"index": "8"}, + device_class=NumberDeviceClass.DURATION, + entity_category=EntityCategory.CONFIG, + ), + ), # Siren Alarm # https://developer.tuya.com/en/docs/iot/categorysgbj?id=Kaiuz37tlpbnu "sgbj": ( diff --git a/homeassistant/components/tuya/strings.json b/homeassistant/components/tuya/strings.json index c8268484c3a..fab93b52fc1 100644 --- a/homeassistant/components/tuya/strings.json +++ b/homeassistant/components/tuya/strings.json @@ -148,6 +148,9 @@ "heat_preservation_time": { "name": "Heat preservation time" }, + "indexed_irrigation_duration": { + "name": "Irrigation duration {index}" + }, "feed": { "name": "Feed" }, @@ -899,6 +902,11 @@ "frost_protection": { "name": "Frost protection" } + }, + "valve": { + "indexed_valve": { + "name": "Valve {index}" + } } }, "exceptions": { diff --git a/homeassistant/components/tuya/valve.py b/homeassistant/components/tuya/valve.py new file mode 100644 index 00000000000..06218c7030f --- /dev/null +++ b/homeassistant/components/tuya/valve.py @@ -0,0 +1,140 @@ +"""Support for Tuya valves.""" + +from __future__ import annotations + +from tuya_sharing import CustomerDevice, Manager + +from homeassistant.components.valve import ( + ValveDeviceClass, + ValveEntity, + ValveEntityDescription, + ValveEntityFeature, +) +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.dispatcher import async_dispatcher_connect +from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback + +from . import TuyaConfigEntry +from .const import TUYA_DISCOVERY_NEW, DPCode +from .entity import TuyaEntity + +# All descriptions can be found here. Mostly the Boolean data types in the +# default instruction set of each category end up being a Valve. +# https://developer.tuya.com/en/docs/iot/standarddescription?id=K9i5ql6waswzq +VALVES: dict[str, tuple[ValveEntityDescription, ...]] = { + # Smart Water Timer + "sfkzq": ( + ValveEntityDescription( + key=DPCode.SWITCH_1, + translation_key="indexed_valve", + translation_placeholders={"index": "1"}, + device_class=ValveDeviceClass.WATER, + ), + ValveEntityDescription( + key=DPCode.SWITCH_2, + translation_key="indexed_valve", + translation_placeholders={"index": "2"}, + device_class=ValveDeviceClass.WATER, + ), + ValveEntityDescription( + key=DPCode.SWITCH_3, + translation_key="indexed_valve", + translation_placeholders={"index": "3"}, + device_class=ValveDeviceClass.WATER, + ), + ValveEntityDescription( + key=DPCode.SWITCH_4, + translation_key="indexed_valve", + translation_placeholders={"index": "4"}, + device_class=ValveDeviceClass.WATER, + ), + ValveEntityDescription( + key=DPCode.SWITCH_5, + translation_key="indexed_valve", + translation_placeholders={"index": "5"}, + device_class=ValveDeviceClass.WATER, + ), + ValveEntityDescription( + key=DPCode.SWITCH_6, + translation_key="indexed_valve", + translation_placeholders={"index": "6"}, + device_class=ValveDeviceClass.WATER, + ), + ValveEntityDescription( + key=DPCode.SWITCH_7, + translation_key="indexed_valve", + translation_placeholders={"index": "7"}, + device_class=ValveDeviceClass.WATER, + ), + ValveEntityDescription( + key=DPCode.SWITCH_8, + translation_key="indexed_valve", + translation_placeholders={"index": "8"}, + device_class=ValveDeviceClass.WATER, + ), + ), +} + + +async def async_setup_entry( + hass: HomeAssistant, + entry: TuyaConfigEntry, + async_add_entities: AddConfigEntryEntitiesCallback, +) -> None: + """Set up tuya valves dynamically through tuya discovery.""" + hass_data = entry.runtime_data + + @callback + def async_discover_device(device_ids: list[str]) -> None: + """Discover and add a discovered tuya valve.""" + entities: list[TuyaValveEntity] = [] + for device_id in device_ids: + device = hass_data.manager.device_map[device_id] + if descriptions := VALVES.get(device.category): + entities.extend( + TuyaValveEntity(device, hass_data.manager, description) + for description in descriptions + if description.key in device.status + ) + + async_add_entities(entities) + + async_discover_device([*hass_data.manager.device_map]) + + entry.async_on_unload( + async_dispatcher_connect(hass, TUYA_DISCOVERY_NEW, async_discover_device) + ) + + +class TuyaValveEntity(TuyaEntity, ValveEntity): + """Tuya Valve Device.""" + + _attr_supported_features = ValveEntityFeature.OPEN | ValveEntityFeature.CLOSE + + def __init__( + self, + device: CustomerDevice, + device_manager: Manager, + description: ValveEntityDescription, + ) -> None: + """Init TuyaValveEntity.""" + super().__init__(device, device_manager) + self.entity_description = description + self._attr_unique_id = f"{super().unique_id}{description.key}" + + @property + def is_closed(self) -> bool: + """Return if the valve is closed.""" + return not self.device.status.get(self.entity_description.key, False) + + async def async_open_valve(self) -> None: + """Open the valve.""" + await self.hass.async_add_executor_job( + self._send_command, [{"code": self.entity_description.key, "value": True}] + ) + + async def async_close_valve(self) -> None: + """Close the valve.""" + await self.hass.async_add_executor_job( + self._send_command, [{"code": self.entity_description.key, "value": False}] + ) diff --git a/tests/components/tuya/__init__.py b/tests/components/tuya/__init__.py index 44fe55fbb0b..5daa3d77427 100644 --- a/tests/components/tuya/__init__.py +++ b/tests/components/tuya/__init__.py @@ -140,6 +140,7 @@ DEVICE_MOCKS = [ "sd_i6hyjg3af7doaswm", # https://github.com/orgs/home-assistant/discussions/539 "sd_lr33znaodtyarrrz", # https://github.com/home-assistant/core/issues/141278 "sfkzq_1fcnd8xk", # https://github.com/orgs/home-assistant/discussions/539 + "sfkzq_ed7frwissyqrejic", # https://github.com/home-assistant/core/pull/149236 "sfkzq_o6dagifntoafakst", # https://github.com/home-assistant/core/issues/148116 "sfkzq_rzklytdei8i8vo37", # https://github.com/home-assistant/core/issues/146164 "sgbj_ulv4nnue7gqp0rjk", # https://github.com/home-assistant/core/issues/149704 diff --git a/tests/components/tuya/fixtures/sfkzq_ed7frwissyqrejic.json b/tests/components/tuya/fixtures/sfkzq_ed7frwissyqrejic.json new file mode 100644 index 00000000000..e301e25930f --- /dev/null +++ b/tests/components/tuya/fixtures/sfkzq_ed7frwissyqrejic.json @@ -0,0 +1,282 @@ +{ + "endpoint": "https://apigw.tuyacn.com", + "mqtt_connected": true, + "disabled_by": null, + "disabled_polling": false, + "name": "\u63a5HA\u6c34\u9600", + "category": "sfkzq", + "product_id": "ed7frwissyqrejic", + "product_name": "\u63a5HA\u6c34\u9600", + "online": true, + "sub": false, + "time_zone": "+08:00", + "active_time": "2025-07-14T06:32:48+00:00", + "create_time": "2025-07-14T06:32:48+00:00", + "update_time": "2025-07-14T06:32:48+00:00", + "function": { + "switch_1": { + "type": "Boolean", + "value": {} + }, + "switch_2": { + "type": "Boolean", + "value": {} + }, + "switch_3": { + "type": "Boolean", + "value": {} + }, + "switch_4": { + "type": "Boolean", + "value": {} + }, + "switch_5": { + "type": "Boolean", + "value": {} + }, + "switch_6": { + "type": "Boolean", + "value": {} + }, + "switch_7": { + "type": "Boolean", + "value": {} + }, + "switch_8": { + "type": "Boolean", + "value": {} + }, + "countdown_1": { + "type": "Integer", + "value": { + "unit": "min", + "min": 1, + "max": 1439, + "scale": 0, + "step": 1 + } + }, + "countdown_2": { + "type": "Integer", + "value": { + "unit": "min", + "min": 1, + "max": 1439, + "scale": 0, + "step": 1 + } + }, + "countdown_3": { + "type": "Integer", + "value": { + "unit": "min", + "min": 1, + "max": 1439, + "scale": 0, + "step": 1 + } + }, + "countdown_4": { + "type": "Integer", + "value": { + "unit": "min", + "min": 1, + "max": 1439, + "scale": 0, + "step": 1 + } + }, + "countdown_5": { + "type": "Integer", + "value": { + "unit": "min", + "min": 1, + "max": 1439, + "scale": 0, + "step": 1 + } + }, + "countdown_6": { + "type": "Integer", + "value": { + "unit": "min", + "min": 1, + "max": 1439, + "scale": 0, + "step": 1 + } + }, + "countdown_7": { + "type": "Integer", + "value": { + "unit": "min", + "min": 1, + "max": 1439, + "scale": 0, + "step": 1 + } + }, + "countdown_8": { + "type": "Integer", + "value": { + "unit": "min", + "min": 1, + "max": 1439, + "scale": 0, + "step": 1 + } + } + }, + "status_range": { + "switch_1": { + "type": "Boolean", + "value": {} + }, + "switch_2": { + "type": "Boolean", + "value": {} + }, + "switch_3": { + "type": "Boolean", + "value": {} + }, + "switch_4": { + "type": "Boolean", + "value": {} + }, + "switch_5": { + "type": "Boolean", + "value": {} + }, + "switch_6": { + "type": "Boolean", + "value": {} + }, + "switch_7": { + "type": "Boolean", + "value": {} + }, + "switch_8": { + "type": "Boolean", + "value": {} + }, + "countdown_1": { + "type": "Integer", + "value": { + "unit": "min", + "min": 1, + "max": 1439, + "scale": 0, + "step": 1 + } + }, + "countdown_2": { + "type": "Integer", + "value": { + "unit": "min", + "min": 1, + "max": 1439, + "scale": 0, + "step": 1 + } + }, + "countdown_3": { + "type": "Integer", + "value": { + "unit": "min", + "min": 1, + "max": 1439, + "scale": 0, + "step": 1 + } + }, + "countdown_4": { + "type": "Integer", + "value": { + "unit": "min", + "min": 1, + "max": 1439, + "scale": 0, + "step": 1 + } + }, + "countdown_5": { + "type": "Integer", + "value": { + "unit": "min", + "min": 1, + "max": 1439, + "scale": 0, + "step": 1 + } + }, + "countdown_6": { + "type": "Integer", + "value": { + "unit": "min", + "min": 1, + "max": 1439, + "scale": 0, + "step": 1 + } + }, + "countdown_7": { + "type": "Integer", + "value": { + "unit": "min", + "min": 1, + "max": 1439, + "scale": 0, + "step": 1 + } + }, + "countdown_8": { + "type": "Integer", + "value": { + "unit": "min", + "min": 1, + "max": 1439, + "scale": 0, + "step": 1 + } + }, + "battery_percentage": { + "type": "Integer", + "value": { + "unit": "%", + "min": 0, + "max": 100, + "scale": 0, + "step": 1 + } + }, + "battery_state": { + "type": "Enum", + "value": { + "range": ["low", "middle", "high"] + } + } + }, + "status": { + "switch_1": true, + "switch_2": false, + "switch_3": true, + "switch_4": false, + "switch_5": true, + "switch_6": false, + "switch_7": true, + "switch_8": false, + "countdown_1": 1, + "countdown_2": 1, + "countdown_3": 3, + "countdown_4": 2, + "countdown_5": 2, + "countdown_6": 3, + "countdown_7": 1, + "countdown_8": 1, + "battery_percentage": 0, + "battery_state": "low" + }, + "set_up": true, + "support_local": true +} diff --git a/tests/components/tuya/snapshots/test_number.ambr b/tests/components/tuya/snapshots/test_number.ambr index adfc7543a3e..b1007431046 100644 --- a/tests/components/tuya/snapshots/test_number.ambr +++ b/tests/components/tuya/snapshots/test_number.ambr @@ -643,6 +643,478 @@ 'state': '3.0', }) # --- +# name: test_platform_setup_and_discovery[number.jie_hashui_fa_irrigation_duration_1-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'max': 1439.0, + 'min': 1.0, + 'mode': , + 'step': 1.0, + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'number', + 'entity_category': , + 'entity_id': 'number.jie_hashui_fa_irrigation_duration_1', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Irrigation duration 1', + 'platform': 'tuya', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'indexed_irrigation_duration', + 'unique_id': 'tuya.cijerqyssiwrf7deqzkfscountdown_1', + 'unit_of_measurement': 'min', + }) +# --- +# name: test_platform_setup_and_discovery[number.jie_hashui_fa_irrigation_duration_1-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'duration', + 'friendly_name': '接HA水阀 Irrigation duration 1', + 'max': 1439.0, + 'min': 1.0, + 'mode': , + 'step': 1.0, + 'unit_of_measurement': 'min', + }), + 'context': , + 'entity_id': 'number.jie_hashui_fa_irrigation_duration_1', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '1.0', + }) +# --- +# name: test_platform_setup_and_discovery[number.jie_hashui_fa_irrigation_duration_2-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'max': 1439.0, + 'min': 1.0, + 'mode': , + 'step': 1.0, + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'number', + 'entity_category': , + 'entity_id': 'number.jie_hashui_fa_irrigation_duration_2', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Irrigation duration 2', + 'platform': 'tuya', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'indexed_irrigation_duration', + 'unique_id': 'tuya.cijerqyssiwrf7deqzkfscountdown_2', + 'unit_of_measurement': 'min', + }) +# --- +# name: test_platform_setup_and_discovery[number.jie_hashui_fa_irrigation_duration_2-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'duration', + 'friendly_name': '接HA水阀 Irrigation duration 2', + 'max': 1439.0, + 'min': 1.0, + 'mode': , + 'step': 1.0, + 'unit_of_measurement': 'min', + }), + 'context': , + 'entity_id': 'number.jie_hashui_fa_irrigation_duration_2', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '1.0', + }) +# --- +# name: test_platform_setup_and_discovery[number.jie_hashui_fa_irrigation_duration_3-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'max': 1439.0, + 'min': 1.0, + 'mode': , + 'step': 1.0, + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'number', + 'entity_category': , + 'entity_id': 'number.jie_hashui_fa_irrigation_duration_3', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Irrigation duration 3', + 'platform': 'tuya', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'indexed_irrigation_duration', + 'unique_id': 'tuya.cijerqyssiwrf7deqzkfscountdown_3', + 'unit_of_measurement': 'min', + }) +# --- +# name: test_platform_setup_and_discovery[number.jie_hashui_fa_irrigation_duration_3-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'duration', + 'friendly_name': '接HA水阀 Irrigation duration 3', + 'max': 1439.0, + 'min': 1.0, + 'mode': , + 'step': 1.0, + 'unit_of_measurement': 'min', + }), + 'context': , + 'entity_id': 'number.jie_hashui_fa_irrigation_duration_3', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '3.0', + }) +# --- +# name: test_platform_setup_and_discovery[number.jie_hashui_fa_irrigation_duration_4-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'max': 1439.0, + 'min': 1.0, + 'mode': , + 'step': 1.0, + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'number', + 'entity_category': , + 'entity_id': 'number.jie_hashui_fa_irrigation_duration_4', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Irrigation duration 4', + 'platform': 'tuya', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'indexed_irrigation_duration', + 'unique_id': 'tuya.cijerqyssiwrf7deqzkfscountdown_4', + 'unit_of_measurement': 'min', + }) +# --- +# name: test_platform_setup_and_discovery[number.jie_hashui_fa_irrigation_duration_4-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'duration', + 'friendly_name': '接HA水阀 Irrigation duration 4', + 'max': 1439.0, + 'min': 1.0, + 'mode': , + 'step': 1.0, + 'unit_of_measurement': 'min', + }), + 'context': , + 'entity_id': 'number.jie_hashui_fa_irrigation_duration_4', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '2.0', + }) +# --- +# name: test_platform_setup_and_discovery[number.jie_hashui_fa_irrigation_duration_5-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'max': 1439.0, + 'min': 1.0, + 'mode': , + 'step': 1.0, + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'number', + 'entity_category': , + 'entity_id': 'number.jie_hashui_fa_irrigation_duration_5', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Irrigation duration 5', + 'platform': 'tuya', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'indexed_irrigation_duration', + 'unique_id': 'tuya.cijerqyssiwrf7deqzkfscountdown_5', + 'unit_of_measurement': 'min', + }) +# --- +# name: test_platform_setup_and_discovery[number.jie_hashui_fa_irrigation_duration_5-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'duration', + 'friendly_name': '接HA水阀 Irrigation duration 5', + 'max': 1439.0, + 'min': 1.0, + 'mode': , + 'step': 1.0, + 'unit_of_measurement': 'min', + }), + 'context': , + 'entity_id': 'number.jie_hashui_fa_irrigation_duration_5', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '2.0', + }) +# --- +# name: test_platform_setup_and_discovery[number.jie_hashui_fa_irrigation_duration_6-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'max': 1439.0, + 'min': 1.0, + 'mode': , + 'step': 1.0, + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'number', + 'entity_category': , + 'entity_id': 'number.jie_hashui_fa_irrigation_duration_6', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Irrigation duration 6', + 'platform': 'tuya', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'indexed_irrigation_duration', + 'unique_id': 'tuya.cijerqyssiwrf7deqzkfscountdown_6', + 'unit_of_measurement': 'min', + }) +# --- +# name: test_platform_setup_and_discovery[number.jie_hashui_fa_irrigation_duration_6-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'duration', + 'friendly_name': '接HA水阀 Irrigation duration 6', + 'max': 1439.0, + 'min': 1.0, + 'mode': , + 'step': 1.0, + 'unit_of_measurement': 'min', + }), + 'context': , + 'entity_id': 'number.jie_hashui_fa_irrigation_duration_6', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '3.0', + }) +# --- +# name: test_platform_setup_and_discovery[number.jie_hashui_fa_irrigation_duration_7-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'max': 1439.0, + 'min': 1.0, + 'mode': , + 'step': 1.0, + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'number', + 'entity_category': , + 'entity_id': 'number.jie_hashui_fa_irrigation_duration_7', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Irrigation duration 7', + 'platform': 'tuya', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'indexed_irrigation_duration', + 'unique_id': 'tuya.cijerqyssiwrf7deqzkfscountdown_7', + 'unit_of_measurement': 'min', + }) +# --- +# name: test_platform_setup_and_discovery[number.jie_hashui_fa_irrigation_duration_7-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'duration', + 'friendly_name': '接HA水阀 Irrigation duration 7', + 'max': 1439.0, + 'min': 1.0, + 'mode': , + 'step': 1.0, + 'unit_of_measurement': 'min', + }), + 'context': , + 'entity_id': 'number.jie_hashui_fa_irrigation_duration_7', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '1.0', + }) +# --- +# name: test_platform_setup_and_discovery[number.jie_hashui_fa_irrigation_duration_8-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'max': 1439.0, + 'min': 1.0, + 'mode': , + 'step': 1.0, + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'number', + 'entity_category': , + 'entity_id': 'number.jie_hashui_fa_irrigation_duration_8', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Irrigation duration 8', + 'platform': 'tuya', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'indexed_irrigation_duration', + 'unique_id': 'tuya.cijerqyssiwrf7deqzkfscountdown_8', + 'unit_of_measurement': 'min', + }) +# --- +# name: test_platform_setup_and_discovery[number.jie_hashui_fa_irrigation_duration_8-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'duration', + 'friendly_name': '接HA水阀 Irrigation duration 8', + 'max': 1439.0, + 'min': 1.0, + 'mode': , + 'step': 1.0, + 'unit_of_measurement': 'min', + }), + 'context': , + 'entity_id': 'number.jie_hashui_fa_irrigation_duration_8', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '1.0', + }) +# --- # name: test_platform_setup_and_discovery[number.kabinet_temperature_correction-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ diff --git a/tests/components/tuya/snapshots/test_sensor.ambr b/tests/components/tuya/snapshots/test_sensor.ambr index 375e52f6bbd..88731f34f10 100644 --- a/tests/components/tuya/snapshots/test_sensor.ambr +++ b/tests/components/tuya/snapshots/test_sensor.ambr @@ -6094,6 +6094,107 @@ 'state': '9.0', }) # --- +# name: test_platform_setup_and_discovery[sensor.jie_hashui_fa_battery-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.jie_hashui_fa_battery', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Battery', + 'platform': 'tuya', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'battery', + 'unique_id': 'tuya.cijerqyssiwrf7deqzkfsbattery_percentage', + 'unit_of_measurement': '%', + }) +# --- +# name: test_platform_setup_and_discovery[sensor.jie_hashui_fa_battery-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'battery', + 'friendly_name': '接HA水阀 Battery', + 'state_class': , + 'unit_of_measurement': '%', + }), + 'context': , + 'entity_id': 'sensor.jie_hashui_fa_battery', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '0.0', + }) +# --- +# name: test_platform_setup_and_discovery[sensor.jie_hashui_fa_battery_state-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.jie_hashui_fa_battery_state', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Battery state', + 'platform': 'tuya', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'battery_state', + 'unique_id': 'tuya.cijerqyssiwrf7deqzkfsbattery_state', + 'unit_of_measurement': None, + }) +# --- +# name: test_platform_setup_and_discovery[sensor.jie_hashui_fa_battery_state-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': '接HA水阀 Battery state', + }), + 'context': , + 'entity_id': 'sensor.jie_hashui_fa_battery_state', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'low', + }) +# --- # name: test_platform_setup_and_discovery[sensor.kalado_air_purifier_air_quality-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ diff --git a/tests/components/tuya/snapshots/test_valve.ambr b/tests/components/tuya/snapshots/test_valve.ambr new file mode 100644 index 00000000000..cb5f78a5610 --- /dev/null +++ b/tests/components/tuya/snapshots/test_valve.ambr @@ -0,0 +1,401 @@ +# serializer version: 1 +# name: test_platform_setup_and_discovery[valve.jie_hashui_fa_valve_1-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'valve', + 'entity_category': None, + 'entity_id': 'valve.jie_hashui_fa_valve_1', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Valve 1', + 'platform': 'tuya', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': , + 'translation_key': 'indexed_valve', + 'unique_id': 'tuya.cijerqyssiwrf7deqzkfsswitch_1', + 'unit_of_measurement': None, + }) +# --- +# name: test_platform_setup_and_discovery[valve.jie_hashui_fa_valve_1-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'water', + 'friendly_name': '接HA水阀 Valve 1', + 'supported_features': , + }), + 'context': , + 'entity_id': 'valve.jie_hashui_fa_valve_1', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'open', + }) +# --- +# name: test_platform_setup_and_discovery[valve.jie_hashui_fa_valve_2-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'valve', + 'entity_category': None, + 'entity_id': 'valve.jie_hashui_fa_valve_2', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Valve 2', + 'platform': 'tuya', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': , + 'translation_key': 'indexed_valve', + 'unique_id': 'tuya.cijerqyssiwrf7deqzkfsswitch_2', + 'unit_of_measurement': None, + }) +# --- +# name: test_platform_setup_and_discovery[valve.jie_hashui_fa_valve_2-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'water', + 'friendly_name': '接HA水阀 Valve 2', + 'supported_features': , + }), + 'context': , + 'entity_id': 'valve.jie_hashui_fa_valve_2', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'closed', + }) +# --- +# name: test_platform_setup_and_discovery[valve.jie_hashui_fa_valve_3-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'valve', + 'entity_category': None, + 'entity_id': 'valve.jie_hashui_fa_valve_3', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Valve 3', + 'platform': 'tuya', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': , + 'translation_key': 'indexed_valve', + 'unique_id': 'tuya.cijerqyssiwrf7deqzkfsswitch_3', + 'unit_of_measurement': None, + }) +# --- +# name: test_platform_setup_and_discovery[valve.jie_hashui_fa_valve_3-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'water', + 'friendly_name': '接HA水阀 Valve 3', + 'supported_features': , + }), + 'context': , + 'entity_id': 'valve.jie_hashui_fa_valve_3', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'open', + }) +# --- +# name: test_platform_setup_and_discovery[valve.jie_hashui_fa_valve_4-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'valve', + 'entity_category': None, + 'entity_id': 'valve.jie_hashui_fa_valve_4', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Valve 4', + 'platform': 'tuya', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': , + 'translation_key': 'indexed_valve', + 'unique_id': 'tuya.cijerqyssiwrf7deqzkfsswitch_4', + 'unit_of_measurement': None, + }) +# --- +# name: test_platform_setup_and_discovery[valve.jie_hashui_fa_valve_4-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'water', + 'friendly_name': '接HA水阀 Valve 4', + 'supported_features': , + }), + 'context': , + 'entity_id': 'valve.jie_hashui_fa_valve_4', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'closed', + }) +# --- +# name: test_platform_setup_and_discovery[valve.jie_hashui_fa_valve_5-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'valve', + 'entity_category': None, + 'entity_id': 'valve.jie_hashui_fa_valve_5', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Valve 5', + 'platform': 'tuya', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': , + 'translation_key': 'indexed_valve', + 'unique_id': 'tuya.cijerqyssiwrf7deqzkfsswitch_5', + 'unit_of_measurement': None, + }) +# --- +# name: test_platform_setup_and_discovery[valve.jie_hashui_fa_valve_5-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'water', + 'friendly_name': '接HA水阀 Valve 5', + 'supported_features': , + }), + 'context': , + 'entity_id': 'valve.jie_hashui_fa_valve_5', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'open', + }) +# --- +# name: test_platform_setup_and_discovery[valve.jie_hashui_fa_valve_6-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'valve', + 'entity_category': None, + 'entity_id': 'valve.jie_hashui_fa_valve_6', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Valve 6', + 'platform': 'tuya', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': , + 'translation_key': 'indexed_valve', + 'unique_id': 'tuya.cijerqyssiwrf7deqzkfsswitch_6', + 'unit_of_measurement': None, + }) +# --- +# name: test_platform_setup_and_discovery[valve.jie_hashui_fa_valve_6-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'water', + 'friendly_name': '接HA水阀 Valve 6', + 'supported_features': , + }), + 'context': , + 'entity_id': 'valve.jie_hashui_fa_valve_6', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'closed', + }) +# --- +# name: test_platform_setup_and_discovery[valve.jie_hashui_fa_valve_7-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'valve', + 'entity_category': None, + 'entity_id': 'valve.jie_hashui_fa_valve_7', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Valve 7', + 'platform': 'tuya', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': , + 'translation_key': 'indexed_valve', + 'unique_id': 'tuya.cijerqyssiwrf7deqzkfsswitch_7', + 'unit_of_measurement': None, + }) +# --- +# name: test_platform_setup_and_discovery[valve.jie_hashui_fa_valve_7-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'water', + 'friendly_name': '接HA水阀 Valve 7', + 'supported_features': , + }), + 'context': , + 'entity_id': 'valve.jie_hashui_fa_valve_7', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'open', + }) +# --- +# name: test_platform_setup_and_discovery[valve.jie_hashui_fa_valve_8-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'valve', + 'entity_category': None, + 'entity_id': 'valve.jie_hashui_fa_valve_8', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Valve 8', + 'platform': 'tuya', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': , + 'translation_key': 'indexed_valve', + 'unique_id': 'tuya.cijerqyssiwrf7deqzkfsswitch_8', + 'unit_of_measurement': None, + }) +# --- +# name: test_platform_setup_and_discovery[valve.jie_hashui_fa_valve_8-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'water', + 'friendly_name': '接HA水阀 Valve 8', + 'supported_features': , + }), + 'context': , + 'entity_id': 'valve.jie_hashui_fa_valve_8', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'closed', + }) +# --- diff --git a/tests/components/tuya/test_valve.py b/tests/components/tuya/test_valve.py new file mode 100644 index 00000000000..9c00f0f4c75 --- /dev/null +++ b/tests/components/tuya/test_valve.py @@ -0,0 +1,96 @@ +"""Test Tuya valve platform.""" + +from __future__ import annotations + +from unittest.mock import patch + +import pytest +from syrupy.assertion import SnapshotAssertion +from tuya_sharing import CustomerDevice + +from homeassistant.components.tuya import ManagerCompat +from homeassistant.components.valve import ( + DOMAIN as VALVE_DOMAIN, + SERVICE_CLOSE_VALVE, + SERVICE_OPEN_VALVE, +) +from homeassistant.const import Platform +from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry as er + +from . import initialize_entry + +from tests.common import MockConfigEntry, snapshot_platform + + +@patch("homeassistant.components.tuya.PLATFORMS", [Platform.VALVE]) +async def test_platform_setup_and_discovery( + hass: HomeAssistant, + mock_manager: ManagerCompat, + mock_config_entry: MockConfigEntry, + mock_devices: list[CustomerDevice], + entity_registry: er.EntityRegistry, + snapshot: SnapshotAssertion, +) -> None: + """Test platform setup and discovery.""" + await initialize_entry(hass, mock_manager, mock_config_entry, mock_devices) + + await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id) + + +@pytest.mark.parametrize( + "mock_device_code", + ["sfkzq_ed7frwissyqrejic"], +) +async def test_open_valve( + hass: HomeAssistant, + mock_manager: ManagerCompat, + mock_config_entry: MockConfigEntry, + mock_device: CustomerDevice, +) -> None: + """Test opening a valve.""" + entity_id = "valve.jie_hashui_fa_valve_1" + await initialize_entry(hass, mock_manager, mock_config_entry, mock_device) + + state = hass.states.get(entity_id) + assert state is not None, f"{entity_id} does not exist" + await hass.services.async_call( + VALVE_DOMAIN, + SERVICE_OPEN_VALVE, + { + "entity_id": entity_id, + }, + blocking=True, + ) + mock_manager.send_commands.assert_called_once_with( + mock_device.id, [{"code": "switch_1", "value": True}] + ) + + +@pytest.mark.parametrize( + "mock_device_code", + ["sfkzq_ed7frwissyqrejic"], +) +async def test_close_valve( + hass: HomeAssistant, + mock_manager: ManagerCompat, + mock_config_entry: MockConfigEntry, + mock_device: CustomerDevice, +) -> None: + """Test closing a valve.""" + entity_id = "valve.jie_hashui_fa_valve_1" + await initialize_entry(hass, mock_manager, mock_config_entry, mock_device) + + state = hass.states.get(entity_id) + assert state is not None, f"{entity_id} does not exist" + await hass.services.async_call( + VALVE_DOMAIN, + SERVICE_CLOSE_VALVE, + { + "entity_id": entity_id, + }, + blocking=True, + ) + mock_manager.send_commands.assert_called_once_with( + mock_device.id, [{"code": "switch_1", "value": False}] + )