From 3dd941eff7cdbc7f36ca1bcefcc113e6d6118c32 Mon Sep 17 00:00:00 2001 From: Petro31 <35082313+Petro31@users.noreply.github.com> Date: Tue, 23 Sep 2025 02:12:24 -0400 Subject: [PATCH 01/10] Fix section and entity variable resolution for template platforms (#149660) Co-authored-by: Erik Montnemery --- homeassistant/components/template/config.py | 45 +++-- .../components/template/trigger_entity.py | 22 ++- tests/components/template/test_config.py | 165 +++++++++++++++++- tests/components/template/test_sensor.py | 59 +++++++ .../template/test_trigger_entity.py | 47 +++-- 5 files changed, 311 insertions(+), 27 deletions(-) diff --git a/homeassistant/components/template/config.py b/homeassistant/components/template/config.py index 51ed3bf015513..bcbc958458870 100644 --- a/homeassistant/components/template/config.py +++ b/homeassistant/components/template/config.py @@ -176,7 +176,15 @@ def _backward_compat_schema(value: Any | None) -> Any: ) -async def _async_resolve_blueprints( +def _merge_section_variables(config: ConfigType, section_variables: ConfigType) -> None: + """Merges a template entity configuration's variables with the section variables.""" + if (variables := config.pop(CONF_VARIABLES, None)) and isinstance(variables, dict): + config[CONF_VARIABLES] = {**section_variables, **variables} + else: + config[CONF_VARIABLES] = section_variables + + +async def _async_resolve_template_config( hass: HomeAssistant, config: ConfigType, ) -> TemplateConfig: @@ -187,12 +195,11 @@ async def _async_resolve_blueprints( with suppress(ValueError): # Invalid config raw_config = dict(config) + config = _backward_compat_schema(config) if is_blueprint_instance_config(config): blueprints = async_get_blueprints(hass) - blueprint_inputs = await blueprints.async_inputs_from_config( - _backward_compat_schema(config) - ) + blueprint_inputs = await blueprints.async_inputs_from_config(config) raw_blueprint_inputs = blueprint_inputs.config_with_inputs config = blueprint_inputs.async_substitute() @@ -205,14 +212,32 @@ async def _async_resolve_blueprints( for prop in (CONF_NAME, CONF_UNIQUE_ID): if prop in config: config[platform][prop] = config.pop(prop) - # For regular template entities, CONF_VARIABLES should be removed because they just - # house input results for template entities. For Trigger based template entities - # CONF_VARIABLES should not be removed because the variables are always - # executed between the trigger and action. + # State based template entities remove CONF_VARIABLES because they pass + # blueprint inputs to the template entities. Trigger based template entities + # retain CONF_VARIABLES because the variables are always executed between + # the trigger and action. if CONF_TRIGGERS not in config and CONF_VARIABLES in config: - config[platform][CONF_VARIABLES] = config.pop(CONF_VARIABLES) + _merge_section_variables(config[platform], config.pop(CONF_VARIABLES)) + raw_config = dict(config) + # Trigger based template entities retain CONF_VARIABLES because the variables are + # always executed between the trigger and action. + elif CONF_TRIGGERS not in config and CONF_VARIABLES in config: + # State based template entities have 2 layers of variables. Variables at the section level + # and variables at the entity level should be merged together at the entity level. + section_variables = config.pop(CONF_VARIABLES) + platform_config: list[ConfigType] | ConfigType + platforms = [platform for platform in PLATFORMS if platform in config] + for platform in platforms: + platform_config = config[platform] + if platform in PLATFORMS: + if isinstance(platform_config, dict): + platform_config = [platform_config] + + for entity_config in platform_config: + _merge_section_variables(entity_config, section_variables) + template_config = TemplateConfig(CONFIG_SECTION_SCHEMA(config)) template_config.raw_blueprint_inputs = raw_blueprint_inputs template_config.raw_config = raw_config @@ -225,7 +250,7 @@ async def async_validate_config_section( ) -> TemplateConfig: """Validate an entire config section for the template integration.""" - validated_config = await _async_resolve_blueprints(hass, config) + validated_config = await _async_resolve_template_config(hass, config) if CONF_TRIGGERS in validated_config: validated_config[CONF_TRIGGERS] = await async_validate_trigger_config( diff --git a/homeassistant/components/template/trigger_entity.py b/homeassistant/components/template/trigger_entity.py index 66c57eb2aab40..e75d62352b507 100644 --- a/homeassistant/components/template/trigger_entity.py +++ b/homeassistant/components/template/trigger_entity.py @@ -4,8 +4,9 @@ from typing import Any -from homeassistant.const import CONF_STATE +from homeassistant.const import CONF_STATE, CONF_VARIABLES from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.script_variables import ScriptVariables from homeassistant.helpers.template import _SENTINEL from homeassistant.helpers.trigger_template_entity import TriggerBaseEntity from homeassistant.helpers.update_coordinator import CoordinatorEntity @@ -32,6 +33,8 @@ def __init__( TriggerBaseEntity.__init__(self, hass, config) AbstractTemplateEntity.__init__(self, hass, config) + self._entity_variables: ScriptVariables | None = config.get(CONF_VARIABLES) + self._rendered_entity_variables: dict | None = None self._state_render_error = False async def async_added_to_hass(self) -> None: @@ -63,9 +66,7 @@ def available(self) -> bool: @callback def _render_script_variables(self) -> dict: """Render configured variables.""" - if self.coordinator.data is None: - return {} - return self.coordinator.data["run_variables"] or {} + return self._rendered_entity_variables or {} def _render_templates(self, variables: dict[str, Any]) -> None: """Render templates.""" @@ -92,7 +93,18 @@ def _render_templates(self, variables: dict[str, Any]) -> None: def _process_data(self) -> None: """Process new data.""" - variables = self._template_variables(self.coordinator.data["run_variables"]) + coordinator_variables = self.coordinator.data["run_variables"] + if self._entity_variables: + entity_variables = self._entity_variables.async_simple_render( + coordinator_variables + ) + self._rendered_entity_variables = { + **coordinator_variables, + **entity_variables, + } + else: + self._rendered_entity_variables = coordinator_variables + variables = self._template_variables(self._rendered_entity_variables) if self._render_availability_template(variables): self._render_templates(variables) diff --git a/tests/components/template/test_config.py b/tests/components/template/test_config.py index 77d4c4bc3c2b2..88d6a2554f538 100644 --- a/tests/components/template/test_config.py +++ b/tests/components/template/test_config.py @@ -5,8 +5,12 @@ import pytest import voluptuous as vol -from homeassistant.components.template.config import CONFIG_SECTION_SCHEMA +from homeassistant.components.template.config import ( + CONFIG_SECTION_SCHEMA, + async_validate_config_section, +) from homeassistant.core import HomeAssistant +from homeassistant.helpers.script_variables import ScriptVariables from homeassistant.helpers.template import Template @@ -93,3 +97,162 @@ async def test_invalid_default_entity_id( } with pytest.raises(vol.Invalid): CONFIG_SECTION_SCHEMA(config) + + +@pytest.mark.parametrize( + ("config", "expected"), + [ + ( + { + "variables": {"a": 1}, + "button": { + "press": { + "service": "test.automation", + "data_template": {"caller": "{{ this.entity_id }}"}, + }, + "variables": {"b": 2}, + "device_class": "restart", + "unique_id": "test", + "name": "test", + "icon": "mdi:test", + }, + }, + {"a": 1, "b": 2}, + ), + ( + { + "variables": {"a": 1}, + "button": [ + { + "press": { + "service": "test.automation", + "data_template": {"caller": "{{ this.entity_id }}"}, + }, + "variables": {"b": 2}, + "device_class": "restart", + "unique_id": "test", + "name": "test", + "icon": "mdi:test", + } + ], + }, + {"a": 1, "b": 2}, + ), + ( + { + "variables": {"a": 1}, + "button": [ + { + "press": { + "service": "test.automation", + "data_template": {"caller": "{{ this.entity_id }}"}, + }, + "variables": {"a": 2, "b": 2}, + "device_class": "restart", + "unique_id": "test", + "name": "test", + "icon": "mdi:test", + } + ], + }, + {"a": 2, "b": 2}, + ), + ( + { + "variables": {"a": 1}, + "button": { + "press": { + "service": "test.automation", + "data_template": {"caller": "{{ this.entity_id }}"}, + }, + "device_class": "restart", + "unique_id": "test", + "name": "test", + "icon": "mdi:test", + }, + }, + {"a": 1}, + ), + ( + { + "button": { + "press": { + "service": "test.automation", + "data_template": {"caller": "{{ this.entity_id }}"}, + }, + "variables": {"b": 2}, + "device_class": "restart", + "unique_id": "test", + "name": "test", + "icon": "mdi:test", + }, + }, + {"b": 2}, + ), + ], +) +async def test_combined_state_variables( + hass: HomeAssistant, config: dict, expected: dict +) -> None: + """Tests combining variables for state based template entities.""" + validated = await async_validate_config_section(hass, config) + assert "variables" not in validated + variables: ScriptVariables = validated["button"][0]["variables"] + assert variables.as_dict() == expected + + +@pytest.mark.parametrize( + ("config", "expected_root", "expected_entity"), + [ + ( + { + "trigger": {"trigger": "event", "event_type": "my_event"}, + "variables": {"a": 1}, + "binary_sensor": { + "name": "test", + "state": "{{ trigger.event.event_type }}", + "variables": {"b": 2}, + }, + }, + {"a": 1}, + {"b": 2}, + ), + ( + { + "triggers": {"trigger": "event", "event_type": "my_event"}, + "variables": {"a": 1}, + "binary_sensor": { + "name": "test", + "state": "{{ trigger.event.event_type }}", + }, + }, + {"a": 1}, + {}, + ), + ( + { + "trigger": {"trigger": "event", "event_type": "my_event"}, + "binary_sensor": { + "name": "test", + "state": "{{ trigger.event.event_type }}", + "variables": {"b": 2}, + }, + }, + {}, + {"b": 2}, + ), + ], +) +async def test_combined_trigger_variables( + hass: HomeAssistant, + config: dict, + expected_root: dict, + expected_entity: dict, +) -> None: + """Tests variable are not combined for trigger based template entities.""" + empty = ScriptVariables({}) + validated = await async_validate_config_section(hass, config) + root_variables: ScriptVariables = validated.get("variables", empty) + assert root_variables.as_dict() == expected_root + variables: ScriptVariables = validated["binary_sensor"][0].get("variables", empty) + assert variables.as_dict() == expected_entity diff --git a/tests/components/template/test_sensor.py b/tests/components/template/test_sensor.py index 9aba85111929a..0a940d111c5ec 100644 --- a/tests/components/template/test_sensor.py +++ b/tests/components/template/test_sensor.py @@ -2298,6 +2298,65 @@ async def test_trigger_action(hass: HomeAssistant) -> None: assert events[0].context.parent_id == context.id +@pytest.mark.parametrize(("count", "domain"), [(1, "template")]) +@pytest.mark.parametrize( + "config", + [ + { + "template": [ + { + "unique_id": "listening-test-event", + "trigger": {"platform": "event", "event_type": "test_event"}, + "variables": {"a": "{{ trigger.event.data.a }}"}, + "action": [ + { + "variables": {"b": "{{ a + 1 }}"}, + }, + {"event": "test_event2", "event_data": {"hello": "world"}}, + ], + "sensor": [ + { + "name": "Hello Name", + "state": "{{ a + b + c }}", + "variables": {"c": "{{ b + 1 }}"}, + "attributes": { + "a": "{{ a }}", + "b": "{{ b }}", + "c": "{{ c }}", + }, + } + ], + }, + ], + }, + ], +) +@pytest.mark.usefixtures("start_ha") +async def test_trigger_action_variables(hass: HomeAssistant) -> None: + """Test trigger entity with variables in an action works.""" + event = "test_event2" + context = Context() + events = async_capture_events(hass, event) + + state = hass.states.get("sensor.hello_name") + assert state is not None + assert state.state == STATE_UNKNOWN + + context = Context() + hass.bus.async_fire("test_event", {"a": 1}, context=context) + await hass.async_block_till_done() + + state = hass.states.get("sensor.hello_name") + assert state.state == str(1 + 2 + 3) + assert state.context is context + assert state.attributes["a"] == 1 + assert state.attributes["b"] == 2 + assert state.attributes["c"] == 3 + + assert len(events) == 1 + assert events[0].context.parent_id == context.id + + @pytest.mark.parametrize(("count", "domain"), [(1, template.DOMAIN)]) @pytest.mark.parametrize( "config", diff --git a/tests/components/template/test_trigger_entity.py b/tests/components/template/test_trigger_entity.py index 7077cbc6f29c4..22201ab5ca91d 100644 --- a/tests/components/template/test_trigger_entity.py +++ b/tests/components/template/test_trigger_entity.py @@ -7,6 +7,7 @@ from homeassistant.const import CONF_ICON, CONF_NAME, CONF_STATE, STATE_OFF, STATE_ON from homeassistant.core import HomeAssistant from homeassistant.helpers import template +from homeassistant.helpers.script_variables import ScriptVariables from homeassistant.helpers.trigger_template_entity import CONF_PICTURE _ICON_TEMPLATE = 'mdi:o{{ "n" if value=="on" else "ff" }}' @@ -123,18 +124,42 @@ async def test_template_state_syntax_error( async def test_script_variables_from_coordinator(hass: HomeAssistant) -> None: """Test script variables.""" - coordinator = TriggerUpdateCoordinator(hass, {}) - entity = TestEntity(hass, coordinator, {}) - - assert entity._render_script_variables() == {} - - coordinator.data = {"run_variables": None} - - assert entity._render_script_variables() == {} - coordinator._execute_update({"value": STATE_ON}) - - assert entity._render_script_variables() == {"value": STATE_ON} + hass.states.async_set("sensor.test", "1") + + coordinator = TriggerUpdateCoordinator( + hass, + { + "variables": ScriptVariables( + {"a": template.Template("{{ states('sensor.test') }}", hass), "c": 0} + ) + }, + ) + entity = TestEntity( + hass, + coordinator, + { + "state": template.Template("{{ 'on' }}", hass), + "variables": ScriptVariables( + {"b": template.Template("{{ a + 1 }}", hass), "c": 1} + ), + }, + ) + await coordinator._handle_triggered({}) + entity._process_data() + assert entity._render_script_variables() == {"a": 1, "b": 2, "c": 1} + + hass.states.async_set("sensor.test", "2") + + await coordinator._handle_triggered({"value": STATE_ON}) + entity._process_data() + + assert entity._render_script_variables() == { + "value": STATE_ON, + "a": 2, + "b": 3, + "c": 1, + } async def test_default_entity_id(hass: HomeAssistant) -> None: From a3cfd7f707d2632850d3bee8d4539cf4faa3387e Mon Sep 17 00:00:00 2001 From: Manu <4445816+tr4nt0r@users.noreply.github.com> Date: Tue, 23 Sep 2025 09:03:01 +0200 Subject: [PATCH 02/10] Fix coordinator data handling in Bring integration (#152786) --- homeassistant/components/bring/coordinator.py | 1 + homeassistant/components/bring/event.py | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/bring/coordinator.py b/homeassistant/components/bring/coordinator.py index 0a8d980a6aae3..e03acca5bb554 100644 --- a/homeassistant/components/bring/coordinator.py +++ b/homeassistant/components/bring/coordinator.py @@ -205,6 +205,7 @@ def __init__( async def _async_update_data(self) -> dict[str, BringActivityData]: """Fetch activity data from bring.""" + self.lists = self.coordinator.lists list_dict: dict[str, BringActivityData] = {} for lst in self.lists: diff --git a/homeassistant/components/bring/event.py b/homeassistant/components/bring/event.py index e9e286dccf074..9cc41af10f772 100644 --- a/homeassistant/components/bring/event.py +++ b/homeassistant/components/bring/event.py @@ -43,7 +43,7 @@ def add_entities() -> None: ) lists_added |= new_lists - coordinator.activity.async_add_listener(add_entities) + coordinator.data.async_add_listener(add_entities) add_entities() @@ -67,7 +67,8 @@ def __init__( def _async_handle_event(self) -> None: """Handle the activity event.""" - bring_list = self.coordinator.data[self._list_uuid] + if (bring_list := self.coordinator.data.get(self._list_uuid)) is None: + return last_event_triggered = self.state if bring_list.activity.timeline and ( last_event_triggered is None From 19fdea024caffe6ccf7e3c086acf151c2952c0bb Mon Sep 17 00:00:00 2001 From: Przemko92 <33545571+Przemko92@users.noreply.github.com> Date: Tue, 23 Sep 2025 09:48:53 +0200 Subject: [PATCH 03/10] Bump compit-inext-api to 0.3.1 (#152781) --- homeassistant/components/compit/climate.py | 7 ++++--- homeassistant/components/compit/coordinator.py | 2 +- homeassistant/components/compit/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 5 files changed, 8 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/compit/climate.py b/homeassistant/components/compit/climate.py index 40fae2b0de75d..5647b3b58267a 100644 --- a/homeassistant/components/compit/climate.py +++ b/homeassistant/components/compit/climate.py @@ -78,8 +78,8 @@ async def async_setup_entry( coordinator = entry.runtime_data climate_entities = [] - for device_id in coordinator.connector.devices: - device = coordinator.connector.devices[device_id] + for device_id in coordinator.connector.all_devices: + device = coordinator.connector.all_devices[device_id] if device.definition.device_class == CLIMATE_DEVICE_CLASS: climate_entities.append( @@ -140,7 +140,8 @@ def __init__( def available(self) -> bool: """Return if entity is available.""" return ( - super().available and self.device_id in self.coordinator.connector.devices + super().available + and self.device_id in self.coordinator.connector.all_devices ) @property diff --git a/homeassistant/components/compit/coordinator.py b/homeassistant/components/compit/coordinator.py index 6eaf961845720..98668b2603972 100644 --- a/homeassistant/components/compit/coordinator.py +++ b/homeassistant/components/compit/coordinator.py @@ -40,4 +40,4 @@ def __init__( async def _async_update_data(self) -> dict[int, DeviceInstance]: """Update data via library.""" await self.connector.update_state(device_id=None) # Update all devices - return self.connector.devices + return self.connector.all_devices diff --git a/homeassistant/components/compit/manifest.json b/homeassistant/components/compit/manifest.json index 9a7aac816584f..b686c406ad1f9 100644 --- a/homeassistant/components/compit/manifest.json +++ b/homeassistant/components/compit/manifest.json @@ -8,5 +8,5 @@ "iot_class": "cloud_polling", "loggers": ["compit"], "quality_scale": "bronze", - "requirements": ["compit-inext-api==0.2.1"] + "requirements": ["compit-inext-api==0.3.1"] } diff --git a/requirements_all.txt b/requirements_all.txt index 6977bd18c92b4..1b7697142cc89 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -736,7 +736,7 @@ colorlog==6.9.0 colorthief==0.2.1 # homeassistant.components.compit -compit-inext-api==0.2.1 +compit-inext-api==0.3.1 # homeassistant.components.concord232 concord232==0.15.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index f59df41a02f88..bc858873cfbb0 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -645,7 +645,7 @@ colorlog==6.9.0 colorthief==0.2.1 # homeassistant.components.compit -compit-inext-api==0.2.1 +compit-inext-api==0.3.1 # homeassistant.components.xiaomi_miio construct==2.10.68 From d73309ba60290167294c19d8b33ee1de3b5f34f8 Mon Sep 17 00:00:00 2001 From: Karsten Bade Date: Tue, 23 Sep 2025 09:49:33 +0200 Subject: [PATCH 04/10] Bump SoCo to 0.30.12 (#152797) --- homeassistant/components/sonos/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/sonos/manifest.json b/homeassistant/components/sonos/manifest.json index fdb88e4b13616..bf1dea7154410 100644 --- a/homeassistant/components/sonos/manifest.json +++ b/homeassistant/components/sonos/manifest.json @@ -9,7 +9,7 @@ "iot_class": "local_push", "loggers": ["soco", "sonos_websocket"], "quality_scale": "bronze", - "requirements": ["soco==0.30.11", "sonos-websocket==0.1.3"], + "requirements": ["soco==0.30.12", "sonos-websocket==0.1.3"], "ssdp": [ { "st": "urn:schemas-upnp-org:device:ZonePlayer:1" diff --git a/requirements_all.txt b/requirements_all.txt index 1b7697142cc89..370631c95edd2 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2827,7 +2827,7 @@ smart-meter-texas==0.5.5 snapcast==2.3.6 # homeassistant.components.sonos -soco==0.30.11 +soco==0.30.12 # homeassistant.components.solaredge_local solaredge-local==0.2.3 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index bc858873cfbb0..6a5ffbb786271 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -2343,7 +2343,7 @@ smart-meter-texas==0.5.5 snapcast==2.3.6 # homeassistant.components.sonos -soco==0.30.11 +soco==0.30.12 # homeassistant.components.solarlog solarlog_cli==0.6.0 From e76bed4a837e0d44d01d78c3654d6a9cd3acda0f Mon Sep 17 00:00:00 2001 From: Matthias Lohr Date: Tue, 23 Sep 2025 10:21:06 +0200 Subject: [PATCH 05/10] Add reconfigure flow to tolo (#137609) Co-authored-by: Josef Zweck --- homeassistant/components/tolo/config_flow.py | 64 ++++++++--- homeassistant/components/tolo/strings.json | 3 +- tests/components/tolo/test_config_flow.py | 108 +++++++++++++++++-- 3 files changed, 149 insertions(+), 26 deletions(-) diff --git a/homeassistant/components/tolo/config_flow.py b/homeassistant/components/tolo/config_flow.py index fed4ff332fc69..7b97fb20343b3 100644 --- a/homeassistant/components/tolo/config_flow.py +++ b/homeassistant/components/tolo/config_flow.py @@ -1,14 +1,19 @@ -"""Config flow for tolo.""" +"""Config flow for TOLO integration.""" from __future__ import annotations import logging +from types import MappingProxyType from typing import Any from tololib import ToloClient, ToloCommunicationError import voluptuous as vol -from homeassistant.config_entries import ConfigFlow, ConfigFlowResult +from homeassistant.config_entries import ( + SOURCE_RECONFIGURE, + ConfigFlow, + ConfigFlowResult, +) from homeassistant.const import CONF_HOST from homeassistant.helpers.device_registry import format_mac from homeassistant.helpers.service_info.dhcp import DhcpServiceInfo @@ -17,13 +22,19 @@ _LOGGER = logging.getLogger(__name__) +CONFIG_SCHEMA = vol.Schema( + { + vol.Required(CONF_HOST): str, + } +) -class ToloSaunaConfigFlow(ConfigFlow, domain=DOMAIN): - """ConfigFlow for TOLO Sauna.""" + +class ToloConfigFlow(ConfigFlow, domain=DOMAIN): + """ConfigFlow for the TOLO Integration.""" VERSION = 1 - _discovered_host: str + _dhcp_discovery_info: DhcpServiceInfo | None = None @staticmethod def _check_device_availability(host: str) -> bool: @@ -37,7 +48,7 @@ def _check_device_availability(host: str) -> bool: async def async_step_user( self, user_input: dict[str, Any] | None = None ) -> ConfigFlowResult: - """Handle a flow initialized by the user.""" + """Handle a config flow initialized by the user.""" errors = {} if user_input is not None: @@ -47,19 +58,36 @@ async def async_step_user( self._check_device_availability, user_input[CONF_HOST] ) - if not device_available: - errors["base"] = "cannot_connect" - else: - return self.async_create_entry( - title=DEFAULT_NAME, data={CONF_HOST: user_input[CONF_HOST]} - ) + if device_available: + if self.source == SOURCE_RECONFIGURE: + return self.async_update_reload_and_abort( + self._get_reconfigure_entry(), data_updates=user_input + ) + return self.async_create_entry(title=DEFAULT_NAME, data=user_input) + + errors["base"] = "cannot_connect" + + schema_values: dict[str, Any] | MappingProxyType[str, Any] = {} + if user_input is not None: + schema_values = user_input + elif self.source == SOURCE_RECONFIGURE: + schema_values = self._get_reconfigure_entry().data return self.async_show_form( step_id="user", - data_schema=vol.Schema({vol.Required(CONF_HOST): str}), + data_schema=self.add_suggested_values_to_schema( + CONFIG_SCHEMA, + schema_values, + ), errors=errors, ) + async def async_step_reconfigure( + self, user_input: dict[str, Any] | None = None + ) -> ConfigFlowResult: + """Handle a reconfiguration config flow initialized by the user.""" + return await self.async_step_user(user_input) + async def async_step_dhcp( self, discovery_info: DhcpServiceInfo ) -> ConfigFlowResult: @@ -73,7 +101,7 @@ async def async_step_dhcp( ) if device_available: - self._discovered_host = discovery_info.ip + self._dhcp_discovery_info = discovery_info return await self.async_step_confirm() return self.async_abort(reason="not_tolo_device") @@ -81,13 +109,15 @@ async def async_step_confirm( self, user_input: dict[str, Any] | None = None ) -> ConfigFlowResult: """Handle user-confirmation of discovered node.""" + assert self._dhcp_discovery_info is not None + if user_input is not None: - self._async_abort_entries_match({CONF_HOST: self._discovered_host}) + self._async_abort_entries_match({CONF_HOST: self._dhcp_discovery_info.ip}) return self.async_create_entry( - title=DEFAULT_NAME, data={CONF_HOST: self._discovered_host} + title=DEFAULT_NAME, data={CONF_HOST: self._dhcp_discovery_info.ip} ) return self.async_show_form( step_id="confirm", - description_placeholders={CONF_HOST: self._discovered_host}, + description_placeholders={CONF_HOST: self._dhcp_discovery_info.ip}, ) diff --git a/homeassistant/components/tolo/strings.json b/homeassistant/components/tolo/strings.json index 82b6ecee9e7c0..55c8274c19b19 100644 --- a/homeassistant/components/tolo/strings.json +++ b/homeassistant/components/tolo/strings.json @@ -16,7 +16,8 @@ "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]" }, "abort": { - "already_configured": "[%key:common::config_flow::abort::already_configured_device%]" + "already_configured": "[%key:common::config_flow::abort::already_configured_device%]", + "reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]" } }, "entity": { diff --git a/tests/components/tolo/test_config_flow.py b/tests/components/tolo/test_config_flow.py index e918edf70a47a..b6cb8f91f825f 100644 --- a/tests/components/tolo/test_config_flow.py +++ b/tests/components/tolo/test_config_flow.py @@ -12,6 +12,8 @@ from homeassistant.data_entry_flow import FlowResultType from homeassistant.helpers.service_info.dhcp import DhcpServiceInfo +from tests.common import MockConfigEntry + MOCK_DHCP_DATA = DhcpServiceInfo( ip="127.0.0.2", macaddress="001122334455", hostname="mock_hostname" ) @@ -36,6 +38,22 @@ def coordinator_toloclient() -> Mock: yield toloclient +@pytest.fixture(name="config_entry") +async def mock_config_entry(hass: HomeAssistant) -> MockConfigEntry: + """Return a MockConfigEntry for testing.""" + config_entry = MockConfigEntry( + domain=DOMAIN, + title="TOLO Steam Bath", + entry_id="1", + data={ + CONF_HOST: "127.0.0.1", + }, + ) + config_entry.add_to_hass(hass) + + return config_entry + + async def test_user_with_timed_out_host(hass: HomeAssistant, toloclient: Mock) -> None: """Test a user initiated config flow with provided host which times out.""" toloclient().get_status.side_effect = ToloCommunicationError @@ -64,25 +82,25 @@ async def test_user_walkthrough( toloclient().get_status.side_effect = lambda *args, **kwargs: None - result2 = await hass.config_entries.flow.async_configure( + result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input={CONF_HOST: "127.0.0.2"}, ) - assert result2["type"] is FlowResultType.FORM - assert result2["step_id"] == "user" - assert result2["errors"] == {"base": "cannot_connect"} + assert result["type"] is FlowResultType.FORM + assert result["step_id"] == "user" + assert result["errors"] == {"base": "cannot_connect"} toloclient().get_status.side_effect = lambda *args, **kwargs: object() - result3 = await hass.config_entries.flow.async_configure( + result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input={CONF_HOST: "127.0.0.1"}, ) - assert result3["type"] is FlowResultType.CREATE_ENTRY - assert result3["title"] == "TOLO Sauna" - assert result3["data"][CONF_HOST] == "127.0.0.1" + assert result["type"] is FlowResultType.CREATE_ENTRY + assert result["title"] == "TOLO Sauna" + assert result["data"][CONF_HOST] == "127.0.0.1" async def test_dhcp( @@ -116,3 +134,77 @@ async def test_dhcp_invalid_device(hass: HomeAssistant, toloclient: Mock) -> Non DOMAIN, context={"source": SOURCE_DHCP}, data=MOCK_DHCP_DATA ) assert result["type"] is FlowResultType.ABORT + + +async def test_reconfigure_walkthrough( + hass: HomeAssistant, + toloclient: Mock, + coordinator_toloclient: Mock, + config_entry: MockConfigEntry, +) -> None: + """Test a reconfigure flow without problems.""" + result = await config_entry.start_reconfigure_flow(hass) + + assert result["step_id"] == "user" + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input={CONF_HOST: "127.0.0.4"} + ) + + assert result["type"] == FlowResultType.ABORT + assert result["reason"] == "reconfigure_successful" + assert config_entry.data[CONF_HOST] == "127.0.0.4" + + +async def test_reconfigure_error_then_fix( + hass: HomeAssistant, + toloclient: Mock, + coordinator_toloclient: Mock, + config_entry: MockConfigEntry, +) -> None: + """Test a reconfigure flow which first fails and then recovers.""" + result = await config_entry.start_reconfigure_flow(hass) + assert result["step_id"] == "user" + + toloclient().get_status.side_effect = ToloCommunicationError + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input={CONF_HOST: "127.0.0.5"} + ) + + assert result["type"] == FlowResultType.FORM + assert result["step_id"] == "user" + assert result["errors"]["base"] == "cannot_connect" + + toloclient().get_status.side_effect = None + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input={CONF_HOST: "127.0.0.4"} + ) + + assert result["type"] == FlowResultType.ABORT + assert result["reason"] == "reconfigure_successful" + assert config_entry.data[CONF_HOST] == "127.0.0.4" + + +async def test_reconfigure_duplicate_ip( + hass: HomeAssistant, + toloclient: Mock, + coordinator_toloclient: Mock, + config_entry: MockConfigEntry, +) -> None: + """Test a reconfigure flow where the user is trying to have to entries with the same IP.""" + config_entry2 = MockConfigEntry( + domain=DOMAIN, data={CONF_HOST: "127.0.0.6"}, unique_id="second_entry" + ) + config_entry2.add_to_hass(hass) + + result = await config_entry.start_reconfigure_flow(hass) + assert result["step_id"] == "user" + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input={CONF_HOST: "127.0.0.6"} + ) + + assert result["type"] == FlowResultType.ABORT + assert result["reason"] == "already_configured" + + assert config_entry.data[CONF_HOST] == "127.0.0.1" From 38a5a3ed4b275376f919e7d12cbe8abd5897ba54 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 23 Sep 2025 03:27:13 -0500 Subject: [PATCH 06/10] Handle wrong ESPHome device without encryption appearing at the configured IP (#152758) --- .../components/esphome/config_flow.py | 62 ++++++++++++------- tests/components/esphome/test_config_flow.py | 39 ++++++++++++ 2 files changed, 79 insertions(+), 22 deletions(-) diff --git a/homeassistant/components/esphome/config_flow.py b/homeassistant/components/esphome/config_flow.py index 4efb0e494ef96..e1aedb90b3cb0 100644 --- a/homeassistant/components/esphome/config_flow.py +++ b/homeassistant/components/esphome/config_flow.py @@ -138,6 +138,16 @@ async def async_step_reauth( return await self._async_authenticate_or_add() if error is None and entry_data.get(CONF_NOISE_PSK): + # Device was configured with encryption but now connects without it. + # Check if it's the same device before offering to remove encryption. + if self._reauth_entry.unique_id and self._device_mac: + expected_mac = format_mac(self._reauth_entry.unique_id) + actual_mac = format_mac(self._device_mac) + if expected_mac != actual_mac: + # Different device at the same IP - do not offer to remove encryption + return self._async_abort_wrong_device( + self._reauth_entry, expected_mac, actual_mac + ) return await self.async_step_reauth_encryption_removed_confirm() return await self.async_step_reauth_confirm() @@ -508,6 +518,28 @@ def _async_make_config_data(self) -> dict[str, Any]: CONF_DEVICE_NAME: self._device_name, } + @callback + def _async_abort_wrong_device( + self, entry: ConfigEntry, expected_mac: str, actual_mac: str + ) -> ConfigFlowResult: + """Abort flow because a different device was found at the IP address.""" + assert self._host is not None + assert self._device_name is not None + if self.source == SOURCE_RECONFIGURE: + reason = "reconfigure_unique_id_changed" + else: + reason = "reauth_unique_id_changed" + return self.async_abort( + reason=reason, + description_placeholders={ + "name": entry.data.get(CONF_DEVICE_NAME, entry.title), + "host": self._host, + "expected_mac": expected_mac, + "unexpected_mac": actual_mac, + "unexpected_device_name": self._device_name, + }, + ) + async def _async_validated_connection(self) -> ConfigFlowResult: """Handle validated connection.""" if self.source == SOURCE_RECONFIGURE: @@ -539,17 +571,10 @@ async def _async_reauth_validated_connection(self) -> ConfigFlowResult: # Reauth was triggered a while ago, and since than # a new device resides at the same IP address. assert self._device_name is not None - return self.async_abort( - reason="reauth_unique_id_changed", - description_placeholders={ - "name": self._reauth_entry.data.get( - CONF_DEVICE_NAME, self._reauth_entry.title - ), - "host": self._host, - "expected_mac": format_mac(self._reauth_entry.unique_id), - "unexpected_mac": format_mac(self.unique_id), - "unexpected_device_name": self._device_name, - }, + return self._async_abort_wrong_device( + self._reauth_entry, + format_mac(self._reauth_entry.unique_id), + format_mac(self.unique_id), ) async def _async_reconfig_validated_connection(self) -> ConfigFlowResult: @@ -589,17 +614,10 @@ async def _async_reconfig_validated_connection(self) -> ConfigFlowResult: if self._reconfig_entry.data.get(CONF_DEVICE_NAME) == self._device_name: self._entry_with_name_conflict = self._reconfig_entry return await self.async_step_name_conflict() - return self.async_abort( - reason="reconfigure_unique_id_changed", - description_placeholders={ - "name": self._reconfig_entry.data.get( - CONF_DEVICE_NAME, self._reconfig_entry.title - ), - "host": self._host, - "expected_mac": format_mac(self._reconfig_entry.unique_id), - "unexpected_mac": format_mac(self.unique_id), - "unexpected_device_name": self._device_name, - }, + return self._async_abort_wrong_device( + self._reconfig_entry, + format_mac(self._reconfig_entry.unique_id), + format_mac(self.unique_id), ) async def async_step_encryption_key( diff --git a/tests/components/esphome/test_config_flow.py b/tests/components/esphome/test_config_flow.py index e0da680afe3fb..f3bb1c77e408f 100644 --- a/tests/components/esphome/test_config_flow.py +++ b/tests/components/esphome/test_config_flow.py @@ -1458,6 +1458,45 @@ async def test_reauth_encryption_key_removed(hass: HomeAssistant) -> None: assert entry.data[CONF_NOISE_PSK] == "" +async def test_reauth_different_device_at_same_address( + hass: HomeAssistant, mock_client: APIClient +) -> None: + """Test reauth aborts when a different device is found at the same IP address.""" + entry = MockConfigEntry( + domain=DOMAIN, + data={ + CONF_HOST: "127.0.0.1", + CONF_PORT: 6053, + CONF_PASSWORD: "", + CONF_NOISE_PSK: VALID_NOISE_PSK, + CONF_DEVICE_NAME: "old_device", + }, + unique_id="11:22:33:44:55:aa", + ) + entry.add_to_hass(hass) + + # Mock a different device at the same IP (different MAC address) + mock_client.device_info.return_value = DeviceInfo( + uses_password=False, + name="new_device", + legacy_bluetooth_proxy_version=0, + # Different MAC address than the entry + mac_address="AA:BB:CC:DD:EE:FF", + esphome_version="1.0.0", + ) + + result = await entry.start_reauth_flow(hass) + assert result["type"] is FlowResultType.ABORT + assert result["reason"] == "reauth_unique_id_changed" + assert result["description_placeholders"] == { + "name": "old_device", + "host": "127.0.0.1", + "expected_mac": "11:22:33:44:55:aa", + "unexpected_mac": "aa:bb:cc:dd:ee:ff", + "unexpected_device_name": "new_device", + } + + @pytest.mark.usefixtures("mock_setup_entry", "mock_zeroconf") async def test_discovery_dhcp_updates_host( hass: HomeAssistant, mock_client: APIClient From a19e37844739b245d6bbc005e641defa90df6829 Mon Sep 17 00:00:00 2001 From: epenet <6771947+epenet@users.noreply.github.com> Date: Tue, 23 Sep 2025 10:44:36 +0200 Subject: [PATCH 07/10] Add Tuya test fixture files (#152795) --- tests/components/tuya/__init__.py | 15 + .../tuya/fixtures/cl_rD7uqAAgQOpSA2Rx.json | 37 + .../tuya/fixtures/clkg_xqvhthwkbmp3aghs.json | 114 ++ .../tuya/fixtures/cs_b9oyi2yofflroq1g.json | 134 ++ .../tuya/fixtures/cz_0fHWRe8ULjtmnBNd.json | 149 ++ .../tuya/fixtures/cz_IGzCi97RpN2Lf9cu.json | 149 ++ .../tuya/fixtures/cz_PGEkBctAbtzKOZng.json | 54 + .../tuya/fixtures/cz_mQUhiTg9kwydBFBd.json | 87 + .../components/tuya/fixtures/cz_piuensvr.json | 33 + .../tuya/fixtures/cz_qxJSyTLEtX5WrzA9.json | 87 + .../tuya/fixtures/mcs_oxslv1c9.json | 39 + .../tuya/fixtures/qt_TtXKwTMwiPpURWLJ.json | 37 + .../tuya/fixtures/wfcon_plp0gnfcacdeqk5o.json | 21 + .../tuya/fixtures/wkf_9xfjixap.json | 85 + .../tuya/fixtures/wkf_p3dbf6qs.json | 85 + .../tuya/fixtures/wsdcg_qrztc3ev.json | 210 +++ .../tuya/snapshots/test_binary_sensor.ambr | 49 + .../tuya/snapshots/test_climate.ambr | 151 ++ .../components/tuya/snapshots/test_cover.ambr | 101 ++ tests/components/tuya/snapshots/test_fan.ambr | 57 + .../tuya/snapshots/test_humidifier.ambr | 57 + .../components/tuya/snapshots/test_init.ambr | 465 ++++++ .../components/tuya/snapshots/test_light.ambr | 57 + .../tuya/snapshots/test_select.ambr | 61 + .../tuya/snapshots/test_sensor.ambr | 1434 ++++++++++++++--- .../tuya/snapshots/test_switch.ambr | 488 ++++++ 26 files changed, 4073 insertions(+), 183 deletions(-) create mode 100644 tests/components/tuya/fixtures/cl_rD7uqAAgQOpSA2Rx.json create mode 100644 tests/components/tuya/fixtures/clkg_xqvhthwkbmp3aghs.json create mode 100644 tests/components/tuya/fixtures/cs_b9oyi2yofflroq1g.json create mode 100644 tests/components/tuya/fixtures/cz_0fHWRe8ULjtmnBNd.json create mode 100644 tests/components/tuya/fixtures/cz_IGzCi97RpN2Lf9cu.json create mode 100644 tests/components/tuya/fixtures/cz_PGEkBctAbtzKOZng.json create mode 100644 tests/components/tuya/fixtures/cz_mQUhiTg9kwydBFBd.json create mode 100644 tests/components/tuya/fixtures/cz_piuensvr.json create mode 100644 tests/components/tuya/fixtures/cz_qxJSyTLEtX5WrzA9.json create mode 100644 tests/components/tuya/fixtures/mcs_oxslv1c9.json create mode 100644 tests/components/tuya/fixtures/qt_TtXKwTMwiPpURWLJ.json create mode 100644 tests/components/tuya/fixtures/wfcon_plp0gnfcacdeqk5o.json create mode 100644 tests/components/tuya/fixtures/wkf_9xfjixap.json create mode 100644 tests/components/tuya/fixtures/wkf_p3dbf6qs.json create mode 100644 tests/components/tuya/fixtures/wsdcg_qrztc3ev.json diff --git a/tests/components/tuya/__init__.py b/tests/components/tuya/__init__.py index 6aba86680cb99..1d12b972e7e9d 100644 --- a/tests/components/tuya/__init__.py +++ b/tests/components/tuya/__init__.py @@ -22,12 +22,15 @@ "cl_ebt12ypvexnixvtf", # https://github.com/tuya/tuya-home-assistant/issues/754 "cl_g1cp07dsqnbdbbki", # https://github.com/home-assistant/core/issues/139966 "cl_qqdxfdht", # https://github.com/orgs/home-assistant/discussions/539 + "cl_rD7uqAAgQOpSA2Rx", # https://github.com/home-assistant/core/issues/139966 "cl_zah67ekd", # https://github.com/home-assistant/core/issues/71242 "clkg_nhyj64w2", # https://github.com/home-assistant/core/issues/136055 "clkg_wltqkykhni0papzj", # https://github.com/home-assistant/core/issues/151635 + "clkg_xqvhthwkbmp3aghs", # https://github.com/home-assistant/core/issues/139966 "co2bj_yakol79dibtswovc", # https://github.com/home-assistant/core/issues/151784 "co2bj_yrr3eiyiacm31ski", # https://github.com/orgs/home-assistant/discussions/842 "cobj_hcdy5zrq3ikzthws", # https://github.com/orgs/home-assistant/discussions/482 + "cs_b9oyi2yofflroq1g", # https://github.com/home-assistant/core/issues/139966 "cs_ipmyy4nigpqcnd8q", # https://github.com/home-assistant/core/pull/148726 "cs_ka2wfrdoogpvgzfi", # https://github.com/home-assistant/core/issues/119865 "cs_qhxmvae667uap4zh", # https://github.com/home-assistant/core/issues/141278 @@ -38,6 +41,7 @@ "cwwsq_wfkzyy0evslzsmoi", # https://github.com/home-assistant/core/issues/144745 "cwysj_akln8rb04cav403q", # https://github.com/home-assistant/core/pull/146599 "cwysj_z3rpyvznfcch99aa", # https://github.com/home-assistant/core/pull/146599 + "cz_0fHWRe8ULjtmnBNd", # https://github.com/home-assistant/core/issues/139966 "cz_0g1fmqh6d5io7lcn", # https://github.com/home-assistant/core/issues/149704 "cz_2iepauebcvo74ujc", # https://github.com/home-assistant/core/issues/141278 "cz_2jxesipczks0kdct", # https://github.com/home-assistant/core/issues/147149 @@ -49,6 +53,8 @@ "cz_AiHXxAyyn7eAkLQY", # https://github.com/home-assistant/core/issues/150662 "cz_CHLZe9HQ6QIXujVN", # https://github.com/home-assistant/core/issues/149233 "cz_HBRBzv1UVBVfF6SL", # https://github.com/tuya/tuya-home-assistant/issues/754 + "cz_IGzCi97RpN2Lf9cu", # https://github.com/home-assistant/core/issues/139966 + "cz_PGEkBctAbtzKOZng", # https://github.com/home-assistant/core/issues/139966 "cz_anwgf2xugjxpkfxb", # https://github.com/orgs/home-assistant/discussions/539 "cz_cuhokdii7ojyw8k2", # https://github.com/home-assistant/core/issues/149704 "cz_dhto3y4uachr1wll", # https://github.com/orgs/home-assistant/discussions/169 @@ -62,10 +68,13 @@ "cz_ipabufmlmodje1ws", # https://github.com/home-assistant/core/issues/63978 "cz_iqhidxhhmgxk5eja", # https://github.com/home-assistant/core/issues/149233 "cz_jnbbxsb84gvvyfg5", # https://github.com/tuya/tuya-home-assistant/issues/754 + "cz_mQUhiTg9kwydBFBd", # https://github.com/home-assistant/core/issues/139966 "cz_n8iVBAPLFKAAAszH", # https://github.com/home-assistant/core/issues/146164 "cz_nkb0fmtlfyqosnvk", # https://github.com/orgs/home-assistant/discussions/482 "cz_nx8rv6jpe1tsnffk", # https://github.com/home-assistant/core/issues/148347 + "cz_piuensvr", # https://github.com/home-assistant/core/issues/139966 "cz_qm0iq4nqnrlzh4qc", # https://github.com/home-assistant/core/issues/141278 + "cz_qxJSyTLEtX5WrzA9", # https://github.com/home-assistant/core/issues/139966 "cz_raceucn29wk2yawe", # https://github.com/tuya/tuya-home-assistant/issues/754 "cz_sb6bwb1n8ma2c5q4", # https://github.com/home-assistant/core/issues/141278 "cz_t0a4hwsf8anfsadp", # https://github.com/home-assistant/core/issues/149704 @@ -153,6 +162,7 @@ "mcs_7jIGJAymiH8OsFFb", # https://github.com/home-assistant/core/issues/108301 "mcs_8yhypbo7", # https://github.com/orgs/home-assistant/discussions/482 "mcs_hx5ztlztij4yxxvg", # https://github.com/home-assistant/core/issues/148347 + "mcs_oxslv1c9", # https://github.com/home-assistant/core/issues/139966 "mcs_qxu3flpqjsc1kqu3", # https://github.com/home-assistant/core/issues/141278 "msp_3ddulzljdjjwkhoy", # https://github.com/orgs/home-assistant/discussions/262 "mzj_jlapoy5liocmtdvd", # https://github.com/home-assistant/core/issues/150662 @@ -168,6 +178,7 @@ "pir_wqz93nrdomectyoz", # https://github.com/home-assistant/core/issues/149704 "qccdz_7bvgooyjhiua1yyq", # https://github.com/home-assistant/core/issues/136207 "qn_5ls2jw49hpczwqng", # https://github.com/home-assistant/core/issues/149233 + "qt_TtXKwTMwiPpURWLJ", # https://github.com/home-assistant/core/issues/139966 "qxj_fsea1lat3vuktbt6", # https://github.com/orgs/home-assistant/discussions/318 "qxj_is2indt9nlth6esa", # https://github.com/home-assistant/core/issues/136472 "qxj_xbwbniyt6bgws9ia", # https://github.com/orgs/home-assistant/discussions/823 @@ -205,6 +216,7 @@ "tyndj_pyakuuoc", # https://github.com/home-assistant/core/issues/149704 "wfcon_b25mh8sxawsgndck", # https://github.com/home-assistant/core/issues/149704 "wfcon_lieerjyy6l4ykjor", # https://github.com/home-assistant/core/issues/136055 + "wfcon_plp0gnfcacdeqk5o", # https://github.com/home-assistant/core/issues/139966 "wg2_2gowdgni", # https://github.com/home-assistant/core/issues/150856 "wg2_haclbl0qkqlf2qds", # https://github.com/orgs/home-assistant/discussions/517 "wg2_nwxr8qcu4seltoro", # https://github.com/orgs/home-assistant/discussions/430 @@ -221,6 +233,8 @@ "wk_gogb05wrtredz3bs", # https://github.com/home-assistant/core/issues/136337 "wk_y5obtqhuztqsf2mj", # https://github.com/home-assistant/core/issues/139735 "wkcz_gc4b1mdw7kebtuyz", # https://github.com/home-assistant/core/issues/135617 + "wkf_9xfjixap", # https://github.com/home-assistant/core/issues/139966 + "wkf_p3dbf6qs", # https://github.com/home-assistant/core/issues/139966 "wnykq_kzwdw5bpxlbs9h9g", # https://github.com/orgs/home-assistant/discussions/842 "wnykq_npbbca46yiug8ysk", # https://github.com/orgs/home-assistant/discussions/539 "wnykq_om518smspsaltzdi", # https://github.com/home-assistant/core/issues/150662 @@ -230,6 +244,7 @@ "wsdcg_iv7hudlj", # https://github.com/home-assistant/core/issues/141278 "wsdcg_krlcihrpzpc8olw9", # https://github.com/orgs/home-assistant/discussions/517 "wsdcg_lf36y5nwb8jkxwgg", # https://github.com/orgs/home-assistant/discussions/539 + "wsdcg_qrztc3ev", # https://github.com/home-assistant/core/issues/139966 "wsdcg_vtA4pDd6PLUZzXgZ", # https://github.com/orgs/home-assistant/discussions/482 "wsdcg_xr3htd96", # https://github.com/orgs/home-assistant/discussions/482 "wsdcg_yqiqbaldtr0i7mru", # https://github.com/home-assistant/core/issues/136223 diff --git a/tests/components/tuya/fixtures/cl_rD7uqAAgQOpSA2Rx.json b/tests/components/tuya/fixtures/cl_rD7uqAAgQOpSA2Rx.json new file mode 100644 index 0000000000000..d50a48766a5c0 --- /dev/null +++ b/tests/components/tuya/fixtures/cl_rD7uqAAgQOpSA2Rx.json @@ -0,0 +1,37 @@ +{ + "endpoint": "https://apigw.tuyaeu.com", + "mqtt_connected": true, + "disabled_by": null, + "disabled_polling": false, + "name": "Kit-Blinds", + "category": "cl", + "product_id": "rD7uqAAgQOpSA2Rx", + "product_name": "Wi-Fi Curtian Switch", + "online": true, + "sub": false, + "time_zone": "+01:00", + "active_time": "2020-04-04T08:17:44+00:00", + "create_time": "2020-04-04T08:17:44+00:00", + "update_time": "2020-04-04T08:17:44+00:00", + "function": { + "control": { + "type": "Enum", + "value": { + "range": ["open", "stop", "close"] + } + } + }, + "status_range": { + "control": { + "type": "Enum", + "value": { + "range": ["open", "close", "stop"] + } + } + }, + "status": { + "control": "open" + }, + "set_up": true, + "support_local": true +} diff --git a/tests/components/tuya/fixtures/clkg_xqvhthwkbmp3aghs.json b/tests/components/tuya/fixtures/clkg_xqvhthwkbmp3aghs.json new file mode 100644 index 0000000000000..0f90f2af3c28a --- /dev/null +++ b/tests/components/tuya/fixtures/clkg_xqvhthwkbmp3aghs.json @@ -0,0 +1,114 @@ +{ + "endpoint": "https://apigw.tuyaeu.com", + "mqtt_connected": true, + "disabled_by": null, + "disabled_polling": false, + "name": "Pergola", + "category": "clkg", + "product_id": "xqvhthwkbmp3aghs", + "product_name": "Curtain switch", + "online": true, + "sub": false, + "time_zone": "+02:00", + "active_time": "2023-05-15T12:00:44+00:00", + "create_time": "2023-05-15T12:00:44+00:00", + "update_time": "2023-05-15T12:00:44+00:00", + "function": { + "control": { + "type": "Enum", + "value": { + "range": ["open", "stop", "close"] + } + }, + "percent_control": { + "type": "Integer", + "value": { + "unit": "%", + "min": 0, + "max": 100, + "scale": 0, + "step": 10 + } + }, + "cur_calibration": { + "type": "Enum", + "value": { + "range": ["start", "end"] + } + }, + "switch_backlight": { + "type": "Boolean", + "value": {} + }, + "control_back_mode": { + "type": "Enum", + "value": { + "range": ["forward", "back"] + } + }, + "tr_timecon": { + "type": "Integer", + "value": { + "unit": "s", + "min": 10, + "max": 240, + "scale": 0, + "step": 1 + } + } + }, + "status_range": { + "control": { + "type": "Enum", + "value": { + "range": ["open", "stop", "close"] + } + }, + "percent_control": { + "type": "Integer", + "value": { + "unit": "%", + "min": 0, + "max": 100, + "scale": 0, + "step": 10 + } + }, + "cur_calibration": { + "type": "Enum", + "value": { + "range": ["start", "end"] + } + }, + "switch_backlight": { + "type": "Boolean", + "value": {} + }, + "control_back_mode": { + "type": "Enum", + "value": { + "range": ["forward", "back"] + } + }, + "tr_timecon": { + "type": "Integer", + "value": { + "unit": "s", + "min": 10, + "max": 240, + "scale": 0, + "step": 1 + } + } + }, + "status": { + "control": "stop", + "percent_control": 0, + "cur_calibration": "end", + "switch_backlight": false, + "control_back_mode": "forward", + "tr_timecon": 32 + }, + "set_up": true, + "support_local": true +} diff --git a/tests/components/tuya/fixtures/cs_b9oyi2yofflroq1g.json b/tests/components/tuya/fixtures/cs_b9oyi2yofflroq1g.json new file mode 100644 index 0000000000000..ad35e3c0e453c --- /dev/null +++ b/tests/components/tuya/fixtures/cs_b9oyi2yofflroq1g.json @@ -0,0 +1,134 @@ +{ + "endpoint": "https://apigw.tuyaeu.com", + "mqtt_connected": true, + "disabled_by": null, + "disabled_polling": false, + "name": "Living room dehumidifier", + "category": "cs", + "product_id": "b9oyi2yofflroq1g", + "product_name": "Dehumidifier ", + "online": true, + "sub": false, + "time_zone": "+01:00", + "active_time": "2025-02-25T10:34:41+00:00", + "create_time": "2025-02-25T10:34:41+00:00", + "update_time": "2025-02-25T10:34:41+00:00", + "function": { + "switch": { + "type": "Boolean", + "value": {} + }, + "dehumidify_set_value": { + "type": "Integer", + "value": { + "unit": "%", + "min": 25, + "max": 80, + "scale": 0, + "step": 5 + } + }, + "fan_speed_enum": { + "type": "Enum", + "value": { + "range": ["low", "high"] + } + }, + "swing": { + "type": "Boolean", + "value": {} + }, + "anion": { + "type": "Boolean", + "value": {} + }, + "uv": { + "type": "Boolean", + "value": {} + }, + "child_lock": { + "type": "Boolean", + "value": {} + }, + "countdown_set": { + "type": "Enum", + "value": { + "range": ["cancel", "1h", "2h", "3h"] + } + } + }, + "status_range": { + "switch": { + "type": "Boolean", + "value": {} + }, + "dehumidify_set_value": { + "type": "Integer", + "value": { + "unit": "%", + "min": 25, + "max": 80, + "scale": 0, + "step": 5 + } + }, + "fan_speed_enum": { + "type": "Enum", + "value": { + "range": ["low", "high"] + } + }, + "humidity_indoor": { + "type": "Integer", + "value": { + "unit": "%", + "min": 0, + "max": 100, + "scale": 0, + "step": 1 + } + }, + "swing": { + "type": "Boolean", + "value": {} + }, + "anion": { + "type": "Boolean", + "value": {} + }, + "uv": { + "type": "Boolean", + "value": {} + }, + "child_lock": { + "type": "Boolean", + "value": {} + }, + "countdown_set": { + "type": "Enum", + "value": { + "range": ["cancel", "1h", "2h", "3h"] + } + }, + "fault": { + "type": "Bitmap", + "value": { + "label": ["E1", "E2"] + } + } + }, + "status": { + "switch": false, + "dehumidify_set_value": 47, + "fan_speed_enum": "high", + "humidity_indoor": 48, + "swing": true, + "anion": false, + "uv": false, + "child_lock": false, + "countdown_set": "cancel", + "fault": 0 + }, + "set_up": true, + "support_local": true +} diff --git a/tests/components/tuya/fixtures/cz_0fHWRe8ULjtmnBNd.json b/tests/components/tuya/fixtures/cz_0fHWRe8ULjtmnBNd.json new file mode 100644 index 0000000000000..ea3e338ac1bab --- /dev/null +++ b/tests/components/tuya/fixtures/cz_0fHWRe8ULjtmnBNd.json @@ -0,0 +1,149 @@ +{ + "endpoint": "https://apigw.tuyaeu.com", + "mqtt_connected": true, + "disabled_by": null, + "disabled_polling": false, + "name": "Weihnachten3", + "category": "cz", + "product_id": "0fHWRe8ULjtmnBNd", + "product_name": "SP22-10A", + "online": true, + "sub": false, + "time_zone": "+01:00", + "active_time": "2018-12-07T12:58:37+00:00", + "create_time": "2018-12-07T12:58:37+00:00", + "update_time": "2018-12-07T12:58:37+00:00", + "function": { + "switch_1": { + "type": "Boolean", + "value": {} + }, + "countdown_1": { + "type": "Integer", + "value": { + "unit": "s", + "min": 0, + "max": 86400, + "scale": 0, + "step": 1 + } + } + }, + "status_range": { + "switch_1": { + "type": "Boolean", + "value": {} + }, + "countdown_1": { + "type": "Integer", + "value": { + "unit": "s", + "min": 0, + "max": 86400, + "scale": 0, + "step": 1 + } + }, + "add_ele": { + "type": "Integer", + "value": { + "unit": "", + "min": 0, + "max": 50000, + "scale": 3, + "step": 100 + } + }, + "cur_current": { + "type": "Integer", + "value": { + "unit": "mA", + "min": 0, + "max": 30000, + "scale": 0, + "step": 1 + } + }, + "cur_power": { + "type": "Integer", + "value": { + "unit": "W", + "min": 0, + "max": 50000, + "scale": 1, + "step": 1 + } + }, + "cur_voltage": { + "type": "Integer", + "value": { + "unit": "V", + "min": 0, + "max": 5000, + "scale": 1, + "step": 1 + } + }, + "voltage_coe": { + "type": "Integer", + "value": { + "unit": "", + "min": 0, + "max": 1000000, + "scale": 0, + "step": 1 + } + }, + "electric_coe": { + "type": "Integer", + "value": { + "unit": "", + "min": 0, + "max": 1000000, + "scale": 0, + "step": 1 + } + }, + "power_coe": { + "type": "Integer", + "value": { + "unit": "", + "min": 0, + "max": 1000000, + "scale": 0, + "step": 1 + } + }, + "electricity_coe": { + "type": "Integer", + "value": { + "unit": "", + "min": 0, + "max": 1000000, + "scale": 0, + "step": 1 + } + }, + "fault": { + "type": "Bitmap", + "value": { + "label": ["ov_cr", "ov_vol", "ov_pwr", "ls_cr", "ls_vol", "ls_pow"] + } + } + }, + "status": { + "switch_1": false, + "countdown_1": 0, + "add_ele": 1, + "cur_current": 18, + "cur_power": 21, + "cur_voltage": 2351, + "voltage_coe": 638, + "electric_coe": 31090, + "power_coe": 17883, + "electricity_coe": 1165, + "fault": 0 + }, + "set_up": true, + "support_local": true +} diff --git a/tests/components/tuya/fixtures/cz_IGzCi97RpN2Lf9cu.json b/tests/components/tuya/fixtures/cz_IGzCi97RpN2Lf9cu.json new file mode 100644 index 0000000000000..4f2a7287a3bc7 --- /dev/null +++ b/tests/components/tuya/fixtures/cz_IGzCi97RpN2Lf9cu.json @@ -0,0 +1,149 @@ +{ + "endpoint": "https://apigw.tuyaeu.com", + "mqtt_connected": true, + "disabled_by": null, + "disabled_polling": false, + "name": "N4-Auto", + "category": "cz", + "product_id": "IGzCi97RpN2Lf9cu", + "product_name": "Smart Socket", + "online": false, + "sub": false, + "time_zone": "+01:00", + "active_time": "2020-11-15T07:45:07+00:00", + "create_time": "2020-11-15T07:45:07+00:00", + "update_time": "2020-11-15T07:45:07+00:00", + "function": { + "switch_1": { + "type": "Boolean", + "value": {} + }, + "countdown_1": { + "type": "Integer", + "value": { + "unit": "s", + "min": 0, + "max": 86400, + "scale": 0, + "step": 1 + } + } + }, + "status_range": { + "switch_1": { + "type": "Boolean", + "value": {} + }, + "countdown_1": { + "type": "Integer", + "value": { + "unit": "s", + "min": 0, + "max": 86400, + "scale": 0, + "step": 1 + } + }, + "add_ele": { + "type": "Integer", + "value": { + "unit": "", + "min": 0, + "max": 50000, + "scale": 3, + "step": 100 + } + }, + "cur_current": { + "type": "Integer", + "value": { + "unit": "mA", + "min": 0, + "max": 30000, + "scale": 0, + "step": 1 + } + }, + "cur_power": { + "type": "Integer", + "value": { + "unit": "W", + "min": 0, + "max": 50000, + "scale": 1, + "step": 1 + } + }, + "cur_voltage": { + "type": "Integer", + "value": { + "unit": "V", + "min": 0, + "max": 5000, + "scale": 1, + "step": 1 + } + }, + "voltage_coe": { + "type": "Integer", + "value": { + "unit": "", + "min": 0, + "max": 1000000, + "scale": 0, + "step": 1 + } + }, + "electric_coe": { + "type": "Integer", + "value": { + "unit": "", + "min": 0, + "max": 1000000, + "scale": 0, + "step": 1 + } + }, + "power_coe": { + "type": "Integer", + "value": { + "unit": "", + "min": 0, + "max": 1000000, + "scale": 0, + "step": 1 + } + }, + "electricity_coe": { + "type": "Integer", + "value": { + "unit": "", + "min": 0, + "max": 1000000, + "scale": 0, + "step": 1 + } + }, + "fault": { + "type": "Bitmap", + "value": { + "label": ["ov_cr", "ov_vol", "ov_pwr", "ls_cr", "ls_vol", "ls_pow"] + } + } + }, + "status": { + "switch_1": false, + "countdown_1": 0, + "add_ele": 1, + "cur_current": 14, + "cur_power": 16, + "cur_voltage": 2287, + "voltage_coe": 757, + "electric_coe": 31906, + "power_coe": 21760, + "electricity_coe": 960, + "fault": 0 + }, + "set_up": true, + "support_local": true +} diff --git a/tests/components/tuya/fixtures/cz_PGEkBctAbtzKOZng.json b/tests/components/tuya/fixtures/cz_PGEkBctAbtzKOZng.json new file mode 100644 index 0000000000000..16623e0dc2857 --- /dev/null +++ b/tests/components/tuya/fixtures/cz_PGEkBctAbtzKOZng.json @@ -0,0 +1,54 @@ +{ + "endpoint": "https://apigw.tuyaeu.com", + "mqtt_connected": true, + "disabled_by": null, + "disabled_polling": false, + "name": "Din", + "category": "cz", + "product_id": "PGEkBctAbtzKOZng", + "product_name": "Smart Plug", + "online": true, + "sub": false, + "time_zone": "+02:00", + "active_time": "2018-07-13T13:18:44+00:00", + "create_time": "2018-07-13T13:18:44+00:00", + "update_time": "2018-07-13T13:18:44+00:00", + "function": { + "switch": { + "type": "Boolean", + "value": {} + }, + "countdown_1": { + "type": "Integer", + "value": { + "unit": "\u79d2", + "min": 0, + "max": 86400, + "scale": 0, + "step": 1 + } + } + }, + "status_range": { + "switch": { + "type": "Boolean", + "value": {} + }, + "countdown_1": { + "type": "Integer", + "value": { + "unit": "\u79d2", + "min": 0, + "max": 86400, + "scale": 0, + "step": 1 + } + } + }, + "status": { + "switch": false, + "countdown_1": 0 + }, + "set_up": true, + "support_local": true +} diff --git a/tests/components/tuya/fixtures/cz_mQUhiTg9kwydBFBd.json b/tests/components/tuya/fixtures/cz_mQUhiTg9kwydBFBd.json new file mode 100644 index 0000000000000..1dc2722610424 --- /dev/null +++ b/tests/components/tuya/fixtures/cz_mQUhiTg9kwydBFBd.json @@ -0,0 +1,87 @@ +{ + "endpoint": "https://apigw.tuyaeu.com", + "mqtt_connected": true, + "disabled_by": null, + "disabled_polling": false, + "name": "Waschmaschine", + "category": "cz", + "product_id": "mQUhiTg9kwydBFBd", + "product_name": "Smart Socket", + "online": true, + "sub": false, + "time_zone": "+02:00", + "active_time": "2018-08-13T17:59:14+00:00", + "create_time": "2018-08-13T17:59:14+00:00", + "update_time": "2018-08-13T17:59:14+00:00", + "function": { + "switch": { + "type": "Boolean", + "value": {} + }, + "countdown_1": { + "type": "Integer", + "value": { + "unit": "\u79d2", + "min": 0, + "max": 86400, + "scale": 0, + "step": 1 + } + } + }, + "status_range": { + "switch": { + "type": "Boolean", + "value": {} + }, + "countdown_1": { + "type": "Integer", + "value": { + "unit": "\u79d2", + "min": 0, + "max": 86400, + "scale": 0, + "step": 1 + } + }, + "cur_current": { + "type": "Integer", + "value": { + "unit": "mA", + "min": 0, + "max": 30000, + "scale": 0, + "step": 1 + } + }, + "cur_power": { + "type": "Integer", + "value": { + "unit": "W", + "min": 0, + "max": 50000, + "scale": 0, + "step": 1 + } + }, + "cur_voltage": { + "type": "Integer", + "value": { + "unit": "V", + "min": 0, + "max": 3000, + "scale": 0, + "step": 1 + } + } + }, + "status": { + "switch": false, + "countdown_1": 0, + "cur_current": 1, + "cur_power": 10455, + "cur_voltage": 2381 + }, + "set_up": true, + "support_local": true +} diff --git a/tests/components/tuya/fixtures/cz_piuensvr.json b/tests/components/tuya/fixtures/cz_piuensvr.json new file mode 100644 index 0000000000000..8489f44da8fc7 --- /dev/null +++ b/tests/components/tuya/fixtures/cz_piuensvr.json @@ -0,0 +1,33 @@ +{ + "endpoint": "https://apigw.tuyaeu.com", + "mqtt_connected": true, + "disabled_by": null, + "disabled_polling": false, + "name": "Signal repeater", + "category": "cz", + "product_id": "piuensvr", + "product_name": "Signal repeater", + "online": true, + "sub": true, + "time_zone": "+02:00", + "active_time": "2025-07-16T17:52:11+00:00", + "create_time": "2025-07-16T17:52:11+00:00", + "update_time": "2025-07-16T17:52:11+00:00", + "function": { + "switch_1": { + "type": "Boolean", + "value": {} + } + }, + "status_range": { + "switch_1": { + "type": "Boolean", + "value": {} + } + }, + "status": { + "switch_1": false + }, + "set_up": true, + "support_local": true +} diff --git a/tests/components/tuya/fixtures/cz_qxJSyTLEtX5WrzA9.json b/tests/components/tuya/fixtures/cz_qxJSyTLEtX5WrzA9.json new file mode 100644 index 0000000000000..7581500a3c96f --- /dev/null +++ b/tests/components/tuya/fixtures/cz_qxJSyTLEtX5WrzA9.json @@ -0,0 +1,87 @@ +{ + "endpoint": "https://apigw.tuyaeu.com", + "mqtt_connected": true, + "disabled_by": null, + "disabled_polling": false, + "name": "LivR", + "category": "cz", + "product_id": "qxJSyTLEtX5WrzA9", + "product_name": "Mini Smart Plug", + "online": true, + "sub": false, + "time_zone": "+01:00", + "active_time": "2018-02-21T13:32:25+00:00", + "create_time": "2018-02-21T13:32:25+00:00", + "update_time": "2018-02-21T13:32:25+00:00", + "function": { + "switch": { + "type": "Boolean", + "value": {} + }, + "countdown_1": { + "type": "Integer", + "value": { + "unit": "\u79d2", + "min": 0, + "max": 86400, + "scale": 0, + "step": 1 + } + } + }, + "status_range": { + "switch": { + "type": "Boolean", + "value": {} + }, + "countdown_1": { + "type": "Integer", + "value": { + "unit": "\u79d2", + "min": 0, + "max": 86400, + "scale": 0, + "step": 1 + } + }, + "cur_current": { + "type": "Integer", + "value": { + "unit": "mA", + "min": 0, + "max": 30000, + "scale": 0, + "step": 1 + } + }, + "cur_power": { + "type": "Integer", + "value": { + "unit": "W", + "min": 0, + "max": 50000, + "scale": 0, + "step": 1 + } + }, + "cur_voltage": { + "type": "Integer", + "value": { + "unit": "V", + "min": 0, + "max": 3000, + "scale": 0, + "step": 1 + } + } + }, + "status": { + "switch": false, + "countdown_1": 0, + "cur_current": 81, + "cur_power": 83, + "cur_voltage": 2352 + }, + "set_up": true, + "support_local": true +} diff --git a/tests/components/tuya/fixtures/mcs_oxslv1c9.json b/tests/components/tuya/fixtures/mcs_oxslv1c9.json new file mode 100644 index 0000000000000..20a5060df69a6 --- /dev/null +++ b/tests/components/tuya/fixtures/mcs_oxslv1c9.json @@ -0,0 +1,39 @@ +{ + "endpoint": "https://apigw.tuyaeu.com", + "mqtt_connected": true, + "disabled_by": null, + "disabled_polling": false, + "name": "Window downstairs", + "category": "mcs", + "product_id": "oxslv1c9", + "product_name": "Contact Sensor", + "online": true, + "sub": true, + "time_zone": "+02:00", + "active_time": "2025-03-27T08:28:40+00:00", + "create_time": "2025-03-27T08:28:40+00:00", + "update_time": "2025-03-27T08:28:40+00:00", + "function": {}, + "status_range": { + "doorcontact_state": { + "type": "Boolean", + "value": {} + }, + "battery_percentage": { + "type": "Integer", + "value": { + "unit": "%", + "min": 0, + "max": 100, + "scale": 0, + "step": 1 + } + } + }, + "status": { + "doorcontact_state": false, + "battery_percentage": 100 + }, + "set_up": true, + "support_local": true +} diff --git a/tests/components/tuya/fixtures/qt_TtXKwTMwiPpURWLJ.json b/tests/components/tuya/fixtures/qt_TtXKwTMwiPpURWLJ.json new file mode 100644 index 0000000000000..d66a997ee1383 --- /dev/null +++ b/tests/components/tuya/fixtures/qt_TtXKwTMwiPpURWLJ.json @@ -0,0 +1,37 @@ +{ + "endpoint": "https://apigw.tuyaeu.com", + "mqtt_connected": true, + "disabled_by": null, + "disabled_polling": false, + "name": "Dining-Blinds", + "category": "qt", + "product_id": "TtXKwTMwiPpURWLJ", + "product_name": "Curtain switch", + "online": true, + "sub": false, + "time_zone": "+02:00", + "active_time": "2019-06-07T09:33:41+00:00", + "create_time": "2019-06-07T09:33:41+00:00", + "update_time": "2019-06-07T09:33:41+00:00", + "function": { + "control": { + "type": "Enum", + "value": { + "range": ["open", "stop", "close"] + } + } + }, + "status_range": { + "control": { + "type": "Enum", + "value": { + "range": ["open", "stop", "close"] + } + } + }, + "status": { + "control": "open" + }, + "set_up": false, + "support_local": true +} diff --git a/tests/components/tuya/fixtures/wfcon_plp0gnfcacdeqk5o.json b/tests/components/tuya/fixtures/wfcon_plp0gnfcacdeqk5o.json new file mode 100644 index 0000000000000..2aba962e5863d --- /dev/null +++ b/tests/components/tuya/fixtures/wfcon_plp0gnfcacdeqk5o.json @@ -0,0 +1,21 @@ +{ + "endpoint": "https://apigw.tuyaeu.com", + "mqtt_connected": true, + "disabled_by": null, + "disabled_polling": false, + "name": "Zigbee Gateway", + "category": "wfcon", + "product_id": "plp0gnfcacdeqk5o", + "product_name": "Zigbee Gateway", + "online": true, + "sub": false, + "time_zone": "+02:00", + "active_time": "2023-10-14T06:02:39+00:00", + "create_time": "2023-10-14T06:02:39+00:00", + "update_time": "2023-10-14T06:02:39+00:00", + "function": {}, + "status_range": {}, + "status": {}, + "set_up": false, + "support_local": true +} diff --git a/tests/components/tuya/fixtures/wkf_9xfjixap.json b/tests/components/tuya/fixtures/wkf_9xfjixap.json new file mode 100644 index 0000000000000..88c6d6b3cc415 --- /dev/null +++ b/tests/components/tuya/fixtures/wkf_9xfjixap.json @@ -0,0 +1,85 @@ +{ + "endpoint": "https://apigw.tuyaeu.com", + "mqtt_connected": true, + "disabled_by": null, + "disabled_polling": false, + "name": "Empore", + "category": "wkf", + "product_id": "9xfjixap", + "product_name": "Smart Radiator Thermostat Controller", + "online": true, + "sub": true, + "time_zone": "+02:00", + "active_time": "2025-03-06T17:22:27+00:00", + "create_time": "2025-03-06T17:22:27+00:00", + "update_time": "2025-03-06T17:22:27+00:00", + "function": { + "mode": { + "type": "Enum", + "value": { + "range": ["auto", "manual", "off"] + } + }, + "temp_set": { + "type": "Integer", + "value": { + "unit": "\u2103", + "min": 50, + "max": 350, + "scale": 1, + "step": 10 + } + }, + "child_lock": { + "type": "Boolean", + "value": {} + } + }, + "status_range": { + "mode": { + "type": "Enum", + "value": { + "range": ["auto", "manual", "off"] + } + }, + "work_state": { + "type": "Enum", + "value": { + "range": ["opened", "closed"] + } + }, + "temp_set": { + "type": "Integer", + "value": { + "unit": "\u2103", + "min": 50, + "max": 350, + "scale": 1, + "step": 10 + } + }, + "temp_current": { + "type": "Integer", + "value": { + "unit": "\u2103", + "min": 0, + "max": 500, + "scale": 1, + "step": 10 + } + }, + "child_lock": { + "type": "Boolean", + "value": {} + } + }, + "status": { + "mode": "manual", + "work_state": "opened", + "temp_set": 350, + "temp_current": 190, + "child_lock": false + }, + "set_up": true, + "support_local": true +} diff --git a/tests/components/tuya/fixtures/wkf_p3dbf6qs.json b/tests/components/tuya/fixtures/wkf_p3dbf6qs.json new file mode 100644 index 0000000000000..0e083e877f4fc --- /dev/null +++ b/tests/components/tuya/fixtures/wkf_p3dbf6qs.json @@ -0,0 +1,85 @@ +{ + "endpoint": "https://apigw.tuyaeu.com", + "mqtt_connected": true, + "disabled_by": null, + "disabled_polling": false, + "name": "Anbau", + "category": "wkf", + "product_id": "p3dbf6qs", + "product_name": "Smart Radiator Thermostat", + "online": false, + "sub": true, + "time_zone": "+02:00", + "active_time": "2023-10-14T06:23:27+00:00", + "create_time": "2023-10-14T06:23:27+00:00", + "update_time": "2023-10-14T06:23:27+00:00", + "function": { + "mode": { + "type": "Enum", + "value": { + "range": ["auto", "manual", "off"] + } + }, + "temp_set": { + "type": "Integer", + "value": { + "unit": "\u2103", + "min": 50, + "max": 350, + "scale": 1, + "step": 10 + } + }, + "child_lock": { + "type": "Boolean", + "value": {} + } + }, + "status_range": { + "mode": { + "type": "Enum", + "value": { + "range": ["auto", "manual", "off"] + } + }, + "work_state": { + "type": "Enum", + "value": { + "range": ["opened", "closed"] + } + }, + "temp_set": { + "type": "Integer", + "value": { + "unit": "\u2103", + "min": 50, + "max": 350, + "scale": 1, + "step": 10 + } + }, + "temp_current": { + "type": "Integer", + "value": { + "unit": "\u2103", + "min": 0, + "max": 500, + "scale": 1, + "step": 10 + } + }, + "child_lock": { + "type": "Boolean", + "value": {} + } + }, + "status": { + "mode": "manual", + "work_state": "opened", + "temp_set": 250, + "temp_current": 220, + "child_lock": false + }, + "set_up": true, + "support_local": true +} diff --git a/tests/components/tuya/fixtures/wsdcg_qrztc3ev.json b/tests/components/tuya/fixtures/wsdcg_qrztc3ev.json new file mode 100644 index 0000000000000..629e543706b19 --- /dev/null +++ b/tests/components/tuya/fixtures/wsdcg_qrztc3ev.json @@ -0,0 +1,210 @@ +{ + "endpoint": "https://apigw.tuyaeu.com", + "mqtt_connected": true, + "disabled_by": null, + "disabled_polling": false, + "name": "Temperature and humidity sensor", + "category": "wsdcg", + "product_id": "qrztc3ev", + "product_name": "Temperature and humidity sensor", + "online": true, + "sub": true, + "time_zone": "+02:00", + "active_time": "2025-03-29T14:26:44+00:00", + "create_time": "2025-03-29T14:26:44+00:00", + "update_time": "2025-03-29T14:26:44+00:00", + "function": { + "temp_unit_convert": { + "type": "Enum", + "value": { + "range": ["c", "f"] + } + }, + "maxtemp_set": { + "type": "Integer", + "value": { + "unit": "\u2103", + "min": -200, + "max": 600, + "scale": 1, + "step": 10 + } + }, + "minitemp_set": { + "type": "Integer", + "value": { + "unit": "\u2103", + "min": -200, + "max": 600, + "scale": 1, + "step": 10 + } + }, + "maxhum_set": { + "type": "Integer", + "value": { + "unit": "%", + "min": 0, + "max": 100, + "scale": 0, + "step": 1 + } + }, + "minihum_set": { + "type": "Integer", + "value": { + "unit": "%", + "min": 0, + "max": 100, + "scale": 0, + "step": 1 + } + }, + "temp_sensitivity": { + "type": "Integer", + "value": { + "unit": "\u2103", + "min": 3, + "max": 50, + "scale": 1, + "step": 1 + } + }, + "hum_sensitivity": { + "type": "Integer", + "value": { + "unit": "%", + "min": 3, + "max": 10, + "scale": 0, + "step": 1 + } + } + }, + "status_range": { + "va_temperature": { + "type": "Integer", + "value": { + "unit": "\u2103", + "min": -200, + "max": 600, + "scale": 1, + "step": 1 + } + }, + "va_humidity": { + "type": "Integer", + "value": { + "unit": "%", + "min": 0, + "max": 100, + "scale": 0, + "step": 1 + } + }, + "battery_percentage": { + "type": "Integer", + "value": { + "unit": "%", + "min": 0, + "max": 100, + "scale": 0, + "step": 1 + } + }, + "temp_unit_convert": { + "type": "Enum", + "value": { + "range": ["c", "f"] + } + }, + "maxtemp_set": { + "type": "Integer", + "value": { + "unit": "\u2103", + "min": -200, + "max": 600, + "scale": 1, + "step": 10 + } + }, + "minitemp_set": { + "type": "Integer", + "value": { + "unit": "\u2103", + "min": -200, + "max": 600, + "scale": 1, + "step": 10 + } + }, + "maxhum_set": { + "type": "Integer", + "value": { + "unit": "%", + "min": 0, + "max": 100, + "scale": 0, + "step": 1 + } + }, + "minihum_set": { + "type": "Integer", + "value": { + "unit": "%", + "min": 0, + "max": 100, + "scale": 0, + "step": 1 + } + }, + "temp_alarm": { + "type": "Enum", + "value": { + "range": ["cancel", "loweralarm", "upperalarm"] + } + }, + "hum_alarm": { + "type": "Enum", + "value": { + "range": ["cancel", "loweralarm", "upperalarm"] + } + }, + "temp_sensitivity": { + "type": "Integer", + "value": { + "unit": "\u2103", + "min": 3, + "max": 50, + "scale": 1, + "step": 1 + } + }, + "hum_sensitivity": { + "type": "Integer", + "value": { + "unit": "%", + "min": 3, + "max": 10, + "scale": 0, + "step": 1 + } + } + }, + "status": { + "va_temperature": 200, + "va_humidity": 59, + "battery_percentage": 8, + "temp_unit_convert": "c", + "maxtemp_set": 600, + "minitemp_set": -100, + "maxhum_set": 70, + "minihum_set": 40, + "temp_alarm": "cancel", + "hum_alarm": "cancel", + "temp_sensitivity": 6, + "hum_sensitivity": 4 + }, + "set_up": true, + "support_local": true +} diff --git a/tests/components/tuya/snapshots/test_binary_sensor.ambr b/tests/components/tuya/snapshots/test_binary_sensor.ambr index 6c2b5b3548a9c..d0a1d5619ecb7 100644 --- a/tests/components/tuya/snapshots/test_binary_sensor.ambr +++ b/tests/components/tuya/snapshots/test_binary_sensor.ambr @@ -1712,6 +1712,55 @@ 'state': 'off', }) # --- +# name: test_platform_setup_and_discovery[binary_sensor.window_downstairs_door-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'binary_sensor', + 'entity_category': None, + 'entity_id': 'binary_sensor.window_downstairs_door', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Door', + 'platform': 'tuya', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': None, + 'unique_id': 'tuya.9c1vlsxoscmdoorcontact_state', + 'unit_of_measurement': None, + }) +# --- +# name: test_platform_setup_and_discovery[binary_sensor.window_downstairs_door-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'door', + 'friendly_name': 'Window downstairs Door', + }), + 'context': , + 'entity_id': 'binary_sensor.window_downstairs_door', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'off', + }) +# --- # name: test_platform_setup_and_discovery[binary_sensor.x5_zigbee_gateway_problem-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ diff --git a/tests/components/tuya/snapshots/test_climate.ambr b/tests/components/tuya/snapshots/test_climate.ambr index 3ed6aa3bf5857..344f638ddf2a4 100644 --- a/tests/components/tuya/snapshots/test_climate.ambr +++ b/tests/components/tuya/snapshots/test_climate.ambr @@ -74,6 +74,80 @@ 'state': 'off', }) # --- +# name: test_platform_setup_and_discovery[climate.anbau-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'hvac_modes': list([ + , + , + , + , + ]), + 'max_temp': 35.0, + 'min_temp': 5.0, + 'preset_modes': list([ + 'off', + ]), + 'target_temp_step': 1.0, + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'climate', + 'entity_category': None, + 'entity_id': 'climate.anbau', + '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': None, + 'platform': 'tuya', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': , + 'translation_key': None, + 'unique_id': 'tuya.sq6fbd3pfkw', + 'unit_of_measurement': None, + }) +# --- +# name: test_platform_setup_and_discovery[climate.anbau-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Anbau', + 'hvac_modes': list([ + , + , + , + , + ]), + 'max_temp': 35.0, + 'min_temp': 5.0, + 'preset_modes': list([ + 'off', + ]), + 'supported_features': , + 'target_temp_step': 1.0, + }), + 'context': , + 'entity_id': 'climate.anbau', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unavailable', + }) +# --- # name: test_platform_setup_and_discovery[climate.bathroom_radiator-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ @@ -364,6 +438,83 @@ 'state': 'off', }) # --- +# name: test_platform_setup_and_discovery[climate.empore-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'hvac_modes': list([ + , + , + , + , + ]), + 'max_temp': 35.0, + 'min_temp': 5.0, + 'preset_modes': list([ + 'off', + ]), + 'target_temp_step': 1.0, + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'climate', + 'entity_category': None, + 'entity_id': 'climate.empore', + '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': None, + 'platform': 'tuya', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': , + 'translation_key': None, + 'unique_id': 'tuya.paxijfx9fkw', + 'unit_of_measurement': None, + }) +# --- +# name: test_platform_setup_and_discovery[climate.empore-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'current_temperature': 19.0, + 'friendly_name': 'Empore', + 'hvac_modes': list([ + , + , + , + , + ]), + 'max_temp': 35.0, + 'min_temp': 5.0, + 'preset_mode': None, + 'preset_modes': list([ + 'off', + ]), + 'supported_features': , + 'target_temp_step': 1.0, + 'temperature': 35.0, + }), + 'context': , + 'entity_id': 'climate.empore', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'heat_cool', + }) +# --- # name: test_platform_setup_and_discovery[climate.kabinet-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ diff --git a/tests/components/tuya/snapshots/test_cover.ambr b/tests/components/tuya/snapshots/test_cover.ambr index 42fecee7a932f..e47af2155c44f 100644 --- a/tests/components/tuya/snapshots/test_cover.ambr +++ b/tests/components/tuya/snapshots/test_cover.ambr @@ -151,6 +151,56 @@ 'state': 'open', }) # --- +# name: test_platform_setup_and_discovery[cover.kit_blinds_curtain-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'cover', + 'entity_category': None, + 'entity_id': 'cover.kit_blinds_curtain', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Curtain', + 'platform': 'tuya', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': , + 'translation_key': 'curtain', + 'unique_id': 'tuya.xR2ASpOQgAAqu7Drlccontrol', + 'unit_of_measurement': None, + }) +# --- +# name: test_platform_setup_and_discovery[cover.kit_blinds_curtain-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'curtain', + 'friendly_name': 'Kit-Blinds Curtain', + 'supported_features': , + }), + 'context': , + 'entity_id': 'cover.kit_blinds_curtain', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unknown', + }) +# --- # name: test_platform_setup_and_discovery[cover.kitchen_blinds_blind-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ @@ -304,6 +354,57 @@ 'state': 'open', }) # --- +# name: test_platform_setup_and_discovery[cover.pergola_curtain-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'cover', + 'entity_category': None, + 'entity_id': 'cover.pergola_curtain', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Curtain', + 'platform': 'tuya', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': , + 'translation_key': 'curtain', + 'unique_id': 'tuya.shga3pmbkwhthvqxgklccontrol', + 'unit_of_measurement': None, + }) +# --- +# name: test_platform_setup_and_discovery[cover.pergola_curtain-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'current_position': 100, + 'device_class': 'curtain', + 'friendly_name': 'Pergola Curtain', + 'supported_features': , + }), + 'context': , + 'entity_id': 'cover.pergola_curtain', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'open', + }) +# --- # name: test_platform_setup_and_discovery[cover.persiana_do_quarto_curtain-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ diff --git a/tests/components/tuya/snapshots/test_fan.ambr b/tests/components/tuya/snapshots/test_fan.ambr index f2b615ec26973..88dfbf14ee6d5 100644 --- a/tests/components/tuya/snapshots/test_fan.ambr +++ b/tests/components/tuya/snapshots/test_fan.ambr @@ -450,6 +450,63 @@ 'state': 'off', }) # --- +# name: test_platform_setup_and_discovery[fan.living_room_dehumidifier-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'preset_modes': list([ + ]), + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'fan', + 'entity_category': None, + 'entity_id': 'fan.living_room_dehumidifier', + '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': None, + 'platform': 'tuya', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': , + 'translation_key': None, + 'unique_id': 'tuya.g1qorlffoy2iyo9bsc', + 'unit_of_measurement': None, + }) +# --- +# name: test_platform_setup_and_discovery[fan.living_room_dehumidifier-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Living room dehumidifier', + 'percentage': 100, + 'percentage_step': 50.0, + 'preset_mode': None, + 'preset_modes': list([ + ]), + 'supported_features': , + }), + 'context': , + 'entity_id': 'fan.living_room_dehumidifier', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'off', + }) +# --- # name: test_platform_setup_and_discovery[fan.tower_fan_ca_407g_smart-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ diff --git a/tests/components/tuya/snapshots/test_humidifier.ambr b/tests/components/tuya/snapshots/test_humidifier.ambr index 46535810d7d95..5343b73e5e7e8 100644 --- a/tests/components/tuya/snapshots/test_humidifier.ambr +++ b/tests/components/tuya/snapshots/test_humidifier.ambr @@ -111,3 +111,60 @@ 'state': 'on', }) # --- +# name: test_platform_setup_and_discovery[humidifier.living_room_dehumidifier-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'max_humidity': 80, + 'min_humidity': 25, + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'humidifier', + 'entity_category': None, + 'entity_id': 'humidifier.living_room_dehumidifier', + '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': 'tuya', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': None, + 'unique_id': 'tuya.g1qorlffoy2iyo9bscswitch', + 'unit_of_measurement': None, + }) +# --- +# name: test_platform_setup_and_discovery[humidifier.living_room_dehumidifier-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'current_humidity': 48, + 'device_class': 'dehumidifier', + 'friendly_name': 'Living room dehumidifier', + 'humidity': 47, + 'max_humidity': 80, + 'min_humidity': 25, + 'supported_features': , + }), + 'context': , + 'entity_id': 'humidifier.living_room_dehumidifier', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'off', + }) +# --- diff --git a/tests/components/tuya/snapshots/test_init.ambr b/tests/components/tuya/snapshots/test_init.ambr index 2a3f5687c525d..399cc99e6b841 100644 --- a/tests/components/tuya/snapshots/test_init.ambr +++ b/tests/components/tuya/snapshots/test_init.ambr @@ -1146,6 +1146,68 @@ 'via_device_id': None, }) # --- +# name: test_device_registry[9AzrW5XtELTySJxqzc] + DeviceRegistryEntrySnapshot({ + 'area_id': None, + 'config_entries': , + 'config_entries_subentries': , + 'configuration_url': None, + 'connections': set({ + }), + 'disabled_by': None, + 'entry_type': None, + 'hw_version': None, + 'id': , + 'identifiers': set({ + tuple( + 'tuya', + '9AzrW5XtELTySJxqzc', + ), + }), + 'labels': set({ + }), + 'manufacturer': 'Tuya', + 'model': 'Mini Smart Plug', + 'model_id': 'qxJSyTLEtX5WrzA9', + 'name': 'LivR', + 'name_by_user': None, + 'primary_config_entry': , + 'serial_number': None, + 'sw_version': None, + 'via_device_id': None, + }) +# --- +# name: test_device_registry[9c1vlsxoscm] + DeviceRegistryEntrySnapshot({ + 'area_id': None, + 'config_entries': , + 'config_entries_subentries': , + 'configuration_url': None, + 'connections': set({ + }), + 'disabled_by': None, + 'entry_type': None, + 'hw_version': None, + 'id': , + 'identifiers': set({ + tuple( + 'tuya', + '9c1vlsxoscm', + ), + }), + 'labels': set({ + }), + 'manufacturer': 'Tuya', + 'model': 'Contact Sensor', + 'model_id': 'oxslv1c9', + 'name': 'Window downstairs', + 'name_by_user': None, + 'primary_config_entry': , + 'serial_number': None, + 'sw_version': None, + 'via_device_id': None, + }) +# --- # name: test_device_registry[9oh1h1uyalfykgg4bdnz] DeviceRegistryEntrySnapshot({ 'area_id': None, @@ -1301,6 +1363,37 @@ 'via_device_id': None, }) # --- +# name: test_device_registry[JLWRUpPiwMTwKXtTtq] + DeviceRegistryEntrySnapshot({ + 'area_id': None, + 'config_entries': , + 'config_entries_subentries': , + 'configuration_url': None, + 'connections': set({ + }), + 'disabled_by': None, + 'entry_type': None, + 'hw_version': None, + 'id': , + 'identifiers': set({ + tuple( + 'tuya', + 'JLWRUpPiwMTwKXtTtq', + ), + }), + 'labels': set({ + }), + 'manufacturer': 'Tuya', + 'model': 'Curtain switch (unsupported)', + 'model_id': 'TtXKwTMwiPpURWLJ', + 'name': 'Dining-Blinds', + 'name_by_user': None, + 'primary_config_entry': , + 'serial_number': None, + 'sw_version': None, + 'via_device_id': None, + }) +# --- # name: test_device_registry[LJ9zTFQTfMgsG2Ahzc] DeviceRegistryEntrySnapshot({ 'area_id': None, @@ -2479,6 +2572,68 @@ 'via_device_id': None, }) # --- +# name: test_device_registry[dBFBdywk9gTihUQmzc] + DeviceRegistryEntrySnapshot({ + 'area_id': None, + 'config_entries': , + 'config_entries_subentries': , + 'configuration_url': None, + 'connections': set({ + }), + 'disabled_by': None, + 'entry_type': None, + 'hw_version': None, + 'id': , + 'identifiers': set({ + tuple( + 'tuya', + 'dBFBdywk9gTihUQmzc', + ), + }), + 'labels': set({ + }), + 'manufacturer': 'Tuya', + 'model': 'Smart Socket', + 'model_id': 'mQUhiTg9kwydBFBd', + 'name': 'Waschmaschine', + 'name_by_user': None, + 'primary_config_entry': , + 'serial_number': None, + 'sw_version': None, + 'via_device_id': None, + }) +# --- +# name: test_device_registry[dNBnmtjLU8eRWHf0zc] + DeviceRegistryEntrySnapshot({ + 'area_id': None, + 'config_entries': , + 'config_entries_subentries': , + 'configuration_url': None, + 'connections': set({ + }), + 'disabled_by': None, + 'entry_type': None, + 'hw_version': None, + 'id': , + 'identifiers': set({ + tuple( + 'tuya', + 'dNBnmtjLU8eRWHf0zc', + ), + }), + 'labels': set({ + }), + 'manufacturer': 'Tuya', + 'model': 'SP22-10A', + 'model_id': '0fHWRe8ULjtmnBNd', + 'name': 'Weihnachten3', + 'name_by_user': None, + 'primary_config_entry': , + 'serial_number': None, + 'sw_version': None, + 'via_device_id': None, + }) +# --- # name: test_device_registry[dke76hazlc] DeviceRegistryEntrySnapshot({ 'area_id': None, @@ -3006,6 +3161,37 @@ 'via_device_id': None, }) # --- +# name: test_device_registry[g1qorlffoy2iyo9bsc] + DeviceRegistryEntrySnapshot({ + 'area_id': None, + 'config_entries': , + 'config_entries_subentries': , + 'configuration_url': None, + 'connections': set({ + }), + 'disabled_by': None, + 'entry_type': None, + 'hw_version': None, + 'id': , + 'identifiers': set({ + tuple( + 'tuya', + 'g1qorlffoy2iyo9bsc', + ), + }), + 'labels': set({ + }), + 'manufacturer': 'Tuya', + 'model': 'Dehumidifier ', + 'model_id': 'b9oyi2yofflroq1g', + 'name': 'Living room dehumidifier', + 'name_by_user': None, + 'primary_config_entry': , + 'serial_number': None, + 'sw_version': None, + 'via_device_id': None, + }) +# --- # name: test_device_registry[g5uso5ajgkxw] DeviceRegistryEntrySnapshot({ 'area_id': None, @@ -3316,6 +3502,37 @@ 'via_device_id': None, }) # --- +# name: test_device_registry[gnZOKztbAtcBkEGPzc] + DeviceRegistryEntrySnapshot({ + 'area_id': None, + 'config_entries': , + 'config_entries_subentries': , + 'configuration_url': None, + 'connections': set({ + }), + 'disabled_by': None, + 'entry_type': None, + 'hw_version': None, + 'id': , + 'identifiers': set({ + tuple( + 'tuya', + 'gnZOKztbAtcBkEGPzc', + ), + }), + 'labels': set({ + }), + 'manufacturer': 'Tuya', + 'model': 'Smart Plug', + 'model_id': 'PGEkBctAbtzKOZng', + 'name': 'Din', + 'name_by_user': None, + 'primary_config_entry': , + 'serial_number': None, + 'sw_version': None, + 'via_device_id': None, + }) +# --- # name: test_device_registry[gnqwzcph94wj2sl5nq] DeviceRegistryEntrySnapshot({ 'area_id': None, @@ -4897,6 +5114,37 @@ 'via_device_id': None, }) # --- +# name: test_device_registry[o5kqedcacfng0plpnocfw] + DeviceRegistryEntrySnapshot({ + 'area_id': None, + 'config_entries': , + 'config_entries_subentries': , + 'configuration_url': None, + 'connections': set({ + }), + 'disabled_by': None, + 'entry_type': None, + 'hw_version': None, + 'id': , + 'identifiers': set({ + tuple( + 'tuya', + 'o5kqedcacfng0plpnocfw', + ), + }), + 'labels': set({ + }), + 'manufacturer': 'Tuya', + 'model': 'Zigbee Gateway (unsupported)', + 'model_id': 'plp0gnfcacdeqk5o', + 'name': 'Zigbee Gateway', + 'name_by_user': None, + 'primary_config_entry': , + 'serial_number': None, + 'sw_version': None, + 'via_device_id': None, + }) +# --- # name: test_device_registry[o71einxvuuktuljcjbwy] DeviceRegistryEntrySnapshot({ 'area_id': None, @@ -5207,6 +5455,37 @@ 'via_device_id': None, }) # --- +# name: test_device_registry[paxijfx9fkw] + DeviceRegistryEntrySnapshot({ + 'area_id': None, + 'config_entries': , + 'config_entries_subentries': , + 'configuration_url': None, + 'connections': set({ + }), + 'disabled_by': None, + 'entry_type': None, + 'hw_version': None, + 'id': , + 'identifiers': set({ + tuple( + 'tuya', + 'paxijfx9fkw', + ), + }), + 'labels': set({ + }), + 'manufacturer': 'Tuya', + 'model': 'Smart Radiator Thermostat Controller', + 'model_id': '9xfjixap', + 'name': 'Empore', + 'name_by_user': None, + 'primary_config_entry': , + 'serial_number': None, + 'sw_version': None, + 'via_device_id': None, + }) +# --- # name: test_device_registry[pdasfna8fswh4a0tzc] DeviceRegistryEntrySnapshot({ 'area_id': None, @@ -5858,6 +6137,37 @@ 'via_device_id': None, }) # --- +# name: test_device_registry[rvsneuipzc] + DeviceRegistryEntrySnapshot({ + 'area_id': None, + 'config_entries': , + 'config_entries_subentries': , + 'configuration_url': None, + 'connections': set({ + }), + 'disabled_by': None, + 'entry_type': None, + 'hw_version': None, + 'id': , + 'identifiers': set({ + tuple( + 'tuya', + 'rvsneuipzc', + ), + }), + 'labels': set({ + }), + 'manufacturer': 'Tuya', + 'model': 'Signal repeater', + 'model_id': 'piuensvr', + 'name': 'Signal repeater', + 'name_by_user': None, + 'primary_config_entry': , + 'serial_number': None, + 'sw_version': None, + 'via_device_id': None, + }) +# --- # name: test_device_registry[rwp6kdezm97s2nktzc] DeviceRegistryEntrySnapshot({ 'area_id': None, @@ -5982,6 +6292,37 @@ 'via_device_id': None, }) # --- +# name: test_device_registry[shga3pmbkwhthvqxgklc] + DeviceRegistryEntrySnapshot({ + 'area_id': None, + 'config_entries': , + 'config_entries_subentries': , + 'configuration_url': None, + 'connections': set({ + }), + 'disabled_by': None, + 'entry_type': None, + 'hw_version': None, + 'id': , + 'identifiers': set({ + tuple( + 'tuya', + 'shga3pmbkwhthvqxgklc', + ), + }), + 'labels': set({ + }), + 'manufacturer': 'Tuya', + 'model': 'Curtain switch', + 'model_id': 'xqvhthwkbmp3aghs', + 'name': 'Pergola', + 'name_by_user': None, + 'primary_config_entry': , + 'serial_number': None, + 'sw_version': None, + 'via_device_id': None, + }) +# --- # name: test_device_registry[sifg4pfqsylsayg0jd] DeviceRegistryEntrySnapshot({ 'area_id': None, @@ -6075,6 +6416,37 @@ 'via_device_id': None, }) # --- +# name: test_device_registry[sq6fbd3pfkw] + DeviceRegistryEntrySnapshot({ + 'area_id': None, + 'config_entries': , + 'config_entries_subentries': , + 'configuration_url': None, + 'connections': set({ + }), + 'disabled_by': None, + 'entry_type': None, + 'hw_version': None, + 'id': , + 'identifiers': set({ + tuple( + 'tuya', + 'sq6fbd3pfkw', + ), + }), + 'labels': set({ + }), + 'manufacturer': 'Tuya', + 'model': 'Smart Radiator Thermostat', + 'model_id': 'p3dbf6qs', + 'name': 'Anbau', + 'name_by_user': None, + 'primary_config_entry': , + 'serial_number': None, + 'sw_version': None, + 'via_device_id': None, + }) +# --- # name: test_device_registry[srp7cfjtn6sshwmt2gw] DeviceRegistryEntrySnapshot({ 'area_id': None, @@ -6509,6 +6881,37 @@ 'via_device_id': None, }) # --- +# name: test_device_registry[uc9fL2NpR79iCzGIzc] + DeviceRegistryEntrySnapshot({ + 'area_id': None, + 'config_entries': , + 'config_entries_subentries': , + 'configuration_url': None, + 'connections': set({ + }), + 'disabled_by': None, + 'entry_type': None, + 'hw_version': None, + 'id': , + 'identifiers': set({ + tuple( + 'tuya', + 'uc9fL2NpR79iCzGIzc', + ), + }), + 'labels': set({ + }), + 'manufacturer': 'Tuya', + 'model': 'Smart Socket', + 'model_id': 'IGzCi97RpN2Lf9cu', + 'name': 'N4-Auto', + 'name_by_user': None, + 'primary_config_entry': , + 'serial_number': None, + 'sw_version': None, + 'via_device_id': None, + }) +# --- # name: test_device_registry[uew54dymycjwz] DeviceRegistryEntrySnapshot({ 'area_id': None, @@ -6664,6 +7067,37 @@ 'via_device_id': None, }) # --- +# name: test_device_registry[ve3ctzrqgcdsw] + DeviceRegistryEntrySnapshot({ + 'area_id': None, + 'config_entries': , + 'config_entries_subentries': , + 'configuration_url': None, + 'connections': set({ + }), + 'disabled_by': None, + 'entry_type': None, + 'hw_version': None, + 'id': , + 'identifiers': set({ + tuple( + 'tuya', + 've3ctzrqgcdsw', + ), + }), + 'labels': set({ + }), + 'manufacturer': 'Tuya', + 'model': 'Temperature and humidity sensor', + 'model_id': 'qrztc3ev', + 'name': 'Temperature and humidity sensor', + 'name_by_user': None, + 'primary_config_entry': , + 'serial_number': None, + 'sw_version': None, + 'via_device_id': None, + }) +# --- # name: test_device_registry[vnj3sa6mqahro6phjd] DeviceRegistryEntrySnapshot({ 'area_id': None, @@ -6974,6 +7408,37 @@ 'via_device_id': None, }) # --- +# name: test_device_registry[xR2ASpOQgAAqu7Drlc] + DeviceRegistryEntrySnapshot({ + 'area_id': None, + 'config_entries': , + 'config_entries_subentries': , + 'configuration_url': None, + 'connections': set({ + }), + 'disabled_by': None, + 'entry_type': None, + 'hw_version': None, + 'id': , + 'identifiers': set({ + tuple( + 'tuya', + 'xR2ASpOQgAAqu7Drlc', + ), + }), + 'labels': set({ + }), + 'manufacturer': 'Tuya', + 'model': 'Wi-Fi Curtian Switch', + 'model_id': 'rD7uqAAgQOpSA2Rx', + 'name': 'Kit-Blinds', + 'name_by_user': None, + 'primary_config_entry': , + 'serial_number': None, + 'sw_version': None, + 'via_device_id': None, + }) +# --- # name: test_device_registry[xenxir4a0tn0p1qcqdt] DeviceRegistryEntrySnapshot({ 'area_id': None, diff --git a/tests/components/tuya/snapshots/test_light.ambr b/tests/components/tuya/snapshots/test_light.ambr index c8d7556fa11c5..b50bb1804be46 100644 --- a/tests/components/tuya/snapshots/test_light.ambr +++ b/tests/components/tuya/snapshots/test_light.ambr @@ -2408,6 +2408,63 @@ 'state': 'unavailable', }) # --- +# name: test_platform_setup_and_discovery[light.pergola_backlight-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'supported_color_modes': list([ + , + ]), + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'light', + 'entity_category': , + 'entity_id': 'light.pergola_backlight', + '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': 'Backlight', + 'platform': 'tuya', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'backlight', + 'unique_id': 'tuya.shga3pmbkwhthvqxgklcswitch_backlight', + 'unit_of_measurement': None, + }) +# --- +# name: test_platform_setup_and_discovery[light.pergola_backlight-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'color_mode': None, + 'friendly_name': 'Pergola Backlight', + 'supported_color_modes': list([ + , + ]), + 'supported_features': , + }), + 'context': , + 'entity_id': 'light.pergola_backlight', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'off', + }) +# --- # name: test_platform_setup_and_discovery[light.plafond_bureau-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ diff --git a/tests/components/tuya/snapshots/test_select.ambr b/tests/components/tuya/snapshots/test_select.ambr index ce90522885d65..31862ae9d6cf9 100644 --- a/tests/components/tuya/snapshots/test_select.ambr +++ b/tests/components/tuya/snapshots/test_select.ambr @@ -3136,6 +3136,67 @@ 'state': 'power_on', }) # --- +# name: test_platform_setup_and_discovery[select.living_room_dehumidifier_countdown-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'options': list([ + 'cancel', + '1h', + '2h', + '3h', + ]), + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'select', + 'entity_category': , + 'entity_id': 'select.living_room_dehumidifier_countdown', + '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': 'Countdown', + 'platform': 'tuya', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'countdown', + 'unique_id': 'tuya.g1qorlffoy2iyo9bsccountdown_set', + 'unit_of_measurement': None, + }) +# --- +# name: test_platform_setup_and_discovery[select.living_room_dehumidifier_countdown-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Living room dehumidifier Countdown', + 'options': list([ + 'cancel', + '1h', + '2h', + '3h', + ]), + }), + 'context': , + 'entity_id': 'select.living_room_dehumidifier_countdown', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'cancel', + }) +# --- # name: test_platform_setup_and_discovery[select.mesa_level-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ diff --git a/tests/components/tuya/snapshots/test_sensor.ambr b/tests/components/tuya/snapshots/test_sensor.ambr index 0b428f8e30d74..f2769f8324025 100644 --- a/tests/components/tuya/snapshots/test_sensor.ambr +++ b/tests/components/tuya/snapshots/test_sensor.ambr @@ -10241,6 +10241,233 @@ 'state': 'unavailable', }) # --- +# name: test_platform_setup_and_discovery[sensor.living_room_dehumidifier_humidity-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': None, + 'entity_id': 'sensor.living_room_dehumidifier_humidity', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Humidity', + 'platform': 'tuya', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'humidity', + 'unique_id': 'tuya.g1qorlffoy2iyo9bschumidity_indoor', + 'unit_of_measurement': '%', + }) +# --- +# name: test_platform_setup_and_discovery[sensor.living_room_dehumidifier_humidity-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'humidity', + 'friendly_name': 'Living room dehumidifier Humidity', + 'state_class': , + 'unit_of_measurement': '%', + }), + 'context': , + 'entity_id': 'sensor.living_room_dehumidifier_humidity', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '48.0', + }) +# --- +# name: test_platform_setup_and_discovery[sensor.livr_current-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': None, + 'entity_id': 'sensor.livr_current', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 2, + }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': , + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Current', + 'platform': 'tuya', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'current', + 'unique_id': 'tuya.9AzrW5XtELTySJxqzccur_current', + 'unit_of_measurement': , + }) +# --- +# name: test_platform_setup_and_discovery[sensor.livr_current-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'current', + 'friendly_name': 'LivR Current', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.livr_current', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '0.081', + }) +# --- +# name: test_platform_setup_and_discovery[sensor.livr_power-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': None, + 'entity_id': 'sensor.livr_power', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 0, + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Power', + 'platform': 'tuya', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'power', + 'unique_id': 'tuya.9AzrW5XtELTySJxqzccur_power', + 'unit_of_measurement': 'W', + }) +# --- +# name: test_platform_setup_and_discovery[sensor.livr_power-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'power', + 'friendly_name': 'LivR Power', + 'state_class': , + 'unit_of_measurement': 'W', + }), + 'context': , + 'entity_id': 'sensor.livr_power', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '83.0', + }) +# --- +# name: test_platform_setup_and_discovery[sensor.livr_voltage-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': None, + 'entity_id': 'sensor.livr_voltage', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 0, + }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': , + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Voltage', + 'platform': 'tuya', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'voltage', + 'unique_id': 'tuya.9AzrW5XtELTySJxqzccur_voltage', + 'unit_of_measurement': , + }) +# --- +# name: test_platform_setup_and_discovery[sensor.livr_voltage-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'voltage', + 'friendly_name': 'LivR Voltage', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.livr_voltage', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '2352.0', + }) +# --- # name: test_platform_setup_and_discovery[sensor.lounge_dark_blind_last_operation_duration-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ @@ -11957,7 +12184,7 @@ 'state': 'unavailable', }) # --- -# name: test_platform_setup_and_discovery[sensor.np_downstairs_north_battery-entry] +# name: test_platform_setup_and_discovery[sensor.n4_auto_current-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -11971,8 +12198,8 @@ 'device_id': , 'disabled_by': None, 'domain': 'sensor', - 'entity_category': , - 'entity_id': 'sensor.np_downstairs_north_battery', + 'entity_category': None, + 'entity_id': 'sensor.n4_auto_current', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -11981,36 +12208,42 @@ }), 'name': None, 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 2, + }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': , + }), }), - 'original_device_class': , + 'original_device_class': , 'original_icon': None, - 'original_name': 'Battery', + 'original_name': 'Current', 'platform': 'tuya', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, - 'translation_key': 'battery', - 'unique_id': 'tuya.vayhq2aj3p3z6y2ggcdswbattery_percentage', - 'unit_of_measurement': '%', + 'translation_key': 'current', + 'unique_id': 'tuya.uc9fL2NpR79iCzGIzccur_current', + 'unit_of_measurement': , }) # --- -# name: test_platform_setup_and_discovery[sensor.np_downstairs_north_battery-state] +# name: test_platform_setup_and_discovery[sensor.n4_auto_current-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'device_class': 'battery', - 'friendly_name': 'NP DownStairs North Battery', + 'device_class': 'current', + 'friendly_name': 'N4-Auto Current', 'state_class': , - 'unit_of_measurement': '%', + 'unit_of_measurement': , }), 'context': , - 'entity_id': 'sensor.np_downstairs_north_battery', + 'entity_id': 'sensor.n4_auto_current', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '0.0', + 'state': 'unavailable', }) # --- -# name: test_platform_setup_and_discovery[sensor.np_downstairs_north_humidity-entry] +# name: test_platform_setup_and_discovery[sensor.n4_auto_power-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -12025,7 +12258,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.np_downstairs_north_humidity', + 'entity_id': 'sensor.n4_auto_power', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -12034,42 +12267,45 @@ }), 'name': None, 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 0, + }), }), - 'original_device_class': , + 'original_device_class': , 'original_icon': None, - 'original_name': 'Humidity', + 'original_name': 'Power', 'platform': 'tuya', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, - 'translation_key': 'humidity', - 'unique_id': 'tuya.vayhq2aj3p3z6y2ggcdswva_humidity', - 'unit_of_measurement': '%', + 'translation_key': 'power', + 'unique_id': 'tuya.uc9fL2NpR79iCzGIzccur_power', + 'unit_of_measurement': 'W', }) # --- -# name: test_platform_setup_and_discovery[sensor.np_downstairs_north_humidity-state] +# name: test_platform_setup_and_discovery[sensor.n4_auto_power-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'device_class': 'humidity', - 'friendly_name': 'NP DownStairs North Humidity', + 'device_class': 'power', + 'friendly_name': 'N4-Auto Power', 'state_class': , - 'unit_of_measurement': '%', + 'unit_of_measurement': 'W', }), 'context': , - 'entity_id': 'sensor.np_downstairs_north_humidity', + 'entity_id': 'sensor.n4_auto_power', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '47.0', + 'state': 'unavailable', }) # --- -# name: test_platform_setup_and_discovery[sensor.np_downstairs_north_temperature-entry] +# name: test_platform_setup_and_discovery[sensor.n4_auto_total_energy-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), 'area_id': None, 'capabilities': dict({ - 'state_class': , + 'state_class': , }), 'config_entry_id': , 'config_subentry_id': , @@ -12078,7 +12314,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.np_downstairs_north_temperature', + 'entity_id': 'sensor.n4_auto_total_energy', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -12087,8 +12323,225 @@ }), 'name': None, 'options': dict({ - 'sensor': dict({ - 'suggested_display_precision': 1, + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Total energy', + 'platform': 'tuya', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'total_energy', + 'unique_id': 'tuya.uc9fL2NpR79iCzGIzcadd_ele', + 'unit_of_measurement': '', + }) +# --- +# name: test_platform_setup_and_discovery[sensor.n4_auto_total_energy-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'N4-Auto Total energy', + 'state_class': , + 'unit_of_measurement': '', + }), + 'context': , + 'entity_id': 'sensor.n4_auto_total_energy', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unavailable', + }) +# --- +# name: test_platform_setup_and_discovery[sensor.n4_auto_voltage-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': None, + 'entity_id': 'sensor.n4_auto_voltage', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 0, + }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': , + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Voltage', + 'platform': 'tuya', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'voltage', + 'unique_id': 'tuya.uc9fL2NpR79iCzGIzccur_voltage', + 'unit_of_measurement': , + }) +# --- +# name: test_platform_setup_and_discovery[sensor.n4_auto_voltage-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'voltage', + 'friendly_name': 'N4-Auto Voltage', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.n4_auto_voltage', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unavailable', + }) +# --- +# name: test_platform_setup_and_discovery[sensor.np_downstairs_north_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.np_downstairs_north_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.vayhq2aj3p3z6y2ggcdswbattery_percentage', + 'unit_of_measurement': '%', + }) +# --- +# name: test_platform_setup_and_discovery[sensor.np_downstairs_north_battery-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'battery', + 'friendly_name': 'NP DownStairs North Battery', + 'state_class': , + 'unit_of_measurement': '%', + }), + 'context': , + 'entity_id': 'sensor.np_downstairs_north_battery', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '0.0', + }) +# --- +# name: test_platform_setup_and_discovery[sensor.np_downstairs_north_humidity-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': None, + 'entity_id': 'sensor.np_downstairs_north_humidity', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Humidity', + 'platform': 'tuya', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'humidity', + 'unique_id': 'tuya.vayhq2aj3p3z6y2ggcdswva_humidity', + 'unit_of_measurement': '%', + }) +# --- +# name: test_platform_setup_and_discovery[sensor.np_downstairs_north_humidity-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'humidity', + 'friendly_name': 'NP DownStairs North Humidity', + 'state_class': , + 'unit_of_measurement': '%', + }), + 'context': , + 'entity_id': 'sensor.np_downstairs_north_humidity', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '47.0', + }) +# --- +# name: test_platform_setup_and_discovery[sensor.np_downstairs_north_temperature-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': None, + 'entity_id': 'sensor.np_downstairs_north_temperature', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 1, }), }), 'original_device_class': , @@ -17229,7 +17682,7 @@ 'state': '0.0', }) # --- -# name: test_platform_setup_and_discovery[sensor.tournesol_battery-entry] +# name: test_platform_setup_and_discovery[sensor.temperature_and_humidity_sensor_battery-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -17244,7 +17697,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': , - 'entity_id': 'sensor.tournesol_battery', + 'entity_id': 'sensor.temperature_and_humidity_sensor_battery', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -17262,27 +17715,27 @@ 'suggested_object_id': None, 'supported_features': 0, 'translation_key': 'battery', - 'unique_id': 'tuya.codvtvgtjsbattery_percentage', + 'unique_id': 'tuya.ve3ctzrqgcdswbattery_percentage', 'unit_of_measurement': '%', }) # --- -# name: test_platform_setup_and_discovery[sensor.tournesol_battery-state] +# name: test_platform_setup_and_discovery[sensor.temperature_and_humidity_sensor_battery-state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'battery', - 'friendly_name': 'Tournesol Battery', + 'friendly_name': 'Temperature and humidity sensor Battery', 'state_class': , 'unit_of_measurement': '%', }), 'context': , - 'entity_id': 'sensor.tournesol_battery', + 'entity_id': 'sensor.temperature_and_humidity_sensor_battery', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '98.0', + 'state': '8.0', }) # --- -# name: test_platform_setup_and_discovery[sensor.v20_battery-entry] +# name: test_platform_setup_and_discovery[sensor.temperature_and_humidity_sensor_humidity-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -17296,8 +17749,8 @@ 'device_id': , 'disabled_by': None, 'domain': 'sensor', - 'entity_category': , - 'entity_id': 'sensor.v20_battery', + 'entity_category': None, + 'entity_id': 'sensor.temperature_and_humidity_sensor_humidity', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -17307,35 +17760,35 @@ 'name': None, 'options': dict({ }), - 'original_device_class': , + 'original_device_class': , 'original_icon': None, - 'original_name': 'Battery', + 'original_name': 'Humidity', 'platform': 'tuya', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, - 'translation_key': 'battery', - 'unique_id': 'tuya.zrrraytdoanz33rldselectricity_left', + 'translation_key': 'humidity', + 'unique_id': 'tuya.ve3ctzrqgcdswva_humidity', 'unit_of_measurement': '%', }) # --- -# name: test_platform_setup_and_discovery[sensor.v20_battery-state] +# name: test_platform_setup_and_discovery[sensor.temperature_and_humidity_sensor_humidity-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'device_class': 'battery', - 'friendly_name': 'V20 Battery', + 'device_class': 'humidity', + 'friendly_name': 'Temperature and humidity sensor Humidity', 'state_class': , 'unit_of_measurement': '%', }), 'context': , - 'entity_id': 'sensor.v20_battery', + 'entity_id': 'sensor.temperature_and_humidity_sensor_humidity', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '100.0', + 'state': '59.0', }) # --- -# name: test_platform_setup_and_discovery[sensor.v20_cleaning_area-entry] +# name: test_platform_setup_and_discovery[sensor.temperature_and_humidity_sensor_temperature-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -17350,7 +17803,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.v20_cleaning_area', + 'entity_id': 'sensor.temperature_and_humidity_sensor_temperature', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -17359,32 +17812,194 @@ }), 'name': None, 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 1, + }), }), - 'original_device_class': None, + 'original_device_class': , 'original_icon': None, - 'original_name': 'Cleaning area', + 'original_name': 'Temperature', 'platform': 'tuya', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, - 'translation_key': 'cleaning_area', - 'unique_id': 'tuya.zrrraytdoanz33rldsclean_area', - 'unit_of_measurement': '㎡', + 'translation_key': 'temperature', + 'unique_id': 'tuya.ve3ctzrqgcdswva_temperature', + 'unit_of_measurement': , }) # --- -# name: test_platform_setup_and_discovery[sensor.v20_cleaning_area-state] +# name: test_platform_setup_and_discovery[sensor.temperature_and_humidity_sensor_temperature-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'friendly_name': 'V20 Cleaning area', + 'device_class': 'temperature', + 'friendly_name': 'Temperature and humidity sensor Temperature', 'state_class': , - 'unit_of_measurement': '㎡', + 'unit_of_measurement': , }), 'context': , - 'entity_id': 'sensor.v20_cleaning_area', + 'entity_id': 'sensor.temperature_and_humidity_sensor_temperature', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '0.0', + 'state': '20.0', + }) +# --- +# name: test_platform_setup_and_discovery[sensor.tournesol_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.tournesol_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.codvtvgtjsbattery_percentage', + 'unit_of_measurement': '%', + }) +# --- +# name: test_platform_setup_and_discovery[sensor.tournesol_battery-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'battery', + 'friendly_name': 'Tournesol Battery', + 'state_class': , + 'unit_of_measurement': '%', + }), + 'context': , + 'entity_id': 'sensor.tournesol_battery', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '98.0', + }) +# --- +# name: test_platform_setup_and_discovery[sensor.v20_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.v20_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.zrrraytdoanz33rldselectricity_left', + 'unit_of_measurement': '%', + }) +# --- +# name: test_platform_setup_and_discovery[sensor.v20_battery-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'battery', + 'friendly_name': 'V20 Battery', + 'state_class': , + 'unit_of_measurement': '%', + }), + 'context': , + 'entity_id': 'sensor.v20_battery', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '100.0', + }) +# --- +# name: test_platform_setup_and_discovery[sensor.v20_cleaning_area-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': None, + 'entity_id': 'sensor.v20_cleaning_area', + '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': 'Cleaning area', + 'platform': 'tuya', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'cleaning_area', + 'unique_id': 'tuya.zrrraytdoanz33rldsclean_area', + 'unit_of_measurement': '㎡', + }) +# --- +# name: test_platform_setup_and_discovery[sensor.v20_cleaning_area-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'V20 Cleaning area', + 'state_class': , + 'unit_of_measurement': '㎡', + }), + 'context': , + 'entity_id': 'sensor.v20_cleaning_area', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '0.0', }) # --- # name: test_platform_setup_and_discovery[sensor.v20_cleaning_time-entry] @@ -17848,20 +18463,410 @@ 'unit_of_measurement': '%', }), 'context': , - 'entity_id': 'sensor.valve_controller_2_battery', + 'entity_id': 'sensor.valve_controller_2_battery', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unavailable', + }) +# --- +# name: test_platform_setup_and_discovery[sensor.valve_controller_2_total_watering_time-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.valve_controller_2_total_watering_time', + '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': 'Total watering time', + 'platform': 'tuya', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'total_watering_time', + 'unique_id': 'tuya.kx8dncf1qzkfstime_use', + 'unit_of_measurement': 's', + }) +# --- +# name: test_platform_setup_and_discovery[sensor.valve_controller_2_total_watering_time-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Valve Controller 2 Total watering time', + 'state_class': , + 'unit_of_measurement': 's', + }), + 'context': , + 'entity_id': 'sensor.valve_controller_2_total_watering_time', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unavailable', + }) +# --- +# name: test_platform_setup_and_discovery[sensor.varmelampa_current-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': None, + 'entity_id': 'sensor.varmelampa_current', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 2, + }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': , + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Current', + 'platform': 'tuya', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'current', + 'unique_id': 'tuya.sw1ejdomlmfubapizccur_current', + 'unit_of_measurement': , + }) +# --- +# name: test_platform_setup_and_discovery[sensor.varmelampa_current-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'current', + 'friendly_name': 'Värmelampa Current', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.varmelampa_current', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '0.435', + }) +# --- +# name: test_platform_setup_and_discovery[sensor.varmelampa_power-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': None, + 'entity_id': 'sensor.varmelampa_power', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 0, + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Power', + 'platform': 'tuya', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'power', + 'unique_id': 'tuya.sw1ejdomlmfubapizccur_power', + 'unit_of_measurement': 'W', + }) +# --- +# name: test_platform_setup_and_discovery[sensor.varmelampa_power-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'power', + 'friendly_name': 'Värmelampa Power', + 'state_class': , + 'unit_of_measurement': 'W', + }), + 'context': , + 'entity_id': 'sensor.varmelampa_power', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '1642.0', + }) +# --- +# name: test_platform_setup_and_discovery[sensor.varmelampa_total_energy-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': None, + 'entity_id': 'sensor.varmelampa_total_energy', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 2, + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Total energy', + 'platform': 'tuya', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'total_energy', + 'unique_id': 'tuya.sw1ejdomlmfubapizcadd_ele', + 'unit_of_measurement': , + }) +# --- +# name: test_platform_setup_and_discovery[sensor.varmelampa_total_energy-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'energy', + 'friendly_name': 'Värmelampa Total energy', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.varmelampa_total_energy', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '0.082', + }) +# --- +# name: test_platform_setup_and_discovery[sensor.varmelampa_voltage-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': None, + 'entity_id': 'sensor.varmelampa_voltage', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 0, + }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': , + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Voltage', + 'platform': 'tuya', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'voltage', + 'unique_id': 'tuya.sw1ejdomlmfubapizccur_voltage', + 'unit_of_measurement': , + }) +# --- +# name: test_platform_setup_and_discovery[sensor.varmelampa_voltage-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'voltage', + 'friendly_name': 'Värmelampa Voltage', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.varmelampa_voltage', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '224.6', + }) +# --- +# name: test_platform_setup_and_discovery[sensor.vividstorm_screen_last_operation_duration-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.vividstorm_screen_last_operation_duration', + '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': 'Last operation duration', + 'platform': 'tuya', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'last_operation_duration', + 'unique_id': 'tuya.4hbnivc4w2rsw966lctime_total', + 'unit_of_measurement': 'ms', + }) +# --- +# name: test_platform_setup_and_discovery[sensor.vividstorm_screen_last_operation_duration-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'VIVIDSTORM SCREEN Last operation duration', + 'unit_of_measurement': 'ms', + }), + 'context': , + 'entity_id': 'sensor.vividstorm_screen_last_operation_duration', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '0.0', + }) +# --- +# name: test_platform_setup_and_discovery[sensor.waschmaschine_current-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': None, + 'entity_id': 'sensor.waschmaschine_current', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 2, + }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': , + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Current', + 'platform': 'tuya', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'current', + 'unique_id': 'tuya.dBFBdywk9gTihUQmzccur_current', + 'unit_of_measurement': , + }) +# --- +# name: test_platform_setup_and_discovery[sensor.waschmaschine_current-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'current', + 'friendly_name': 'Waschmaschine Current', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.waschmaschine_current', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': 'unavailable', + 'state': '0.001', }) # --- -# name: test_platform_setup_and_discovery[sensor.valve_controller_2_total_watering_time-entry] +# name: test_platform_setup_and_discovery[sensor.waschmaschine_power-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), 'area_id': None, 'capabilities': dict({ - 'state_class': , + 'state_class': , }), 'config_entry_id': , 'config_subentry_id': , @@ -17869,8 +18874,8 @@ 'device_id': , 'disabled_by': None, 'domain': 'sensor', - 'entity_category': , - 'entity_id': 'sensor.valve_controller_2_total_watering_time', + 'entity_category': None, + 'entity_id': 'sensor.waschmaschine_power', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -17879,35 +18884,39 @@ }), 'name': None, 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 0, + }), }), - 'original_device_class': None, + 'original_device_class': , 'original_icon': None, - 'original_name': 'Total watering time', + 'original_name': 'Power', 'platform': 'tuya', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, - 'translation_key': 'total_watering_time', - 'unique_id': 'tuya.kx8dncf1qzkfstime_use', - 'unit_of_measurement': 's', + 'translation_key': 'power', + 'unique_id': 'tuya.dBFBdywk9gTihUQmzccur_power', + 'unit_of_measurement': 'W', }) # --- -# name: test_platform_setup_and_discovery[sensor.valve_controller_2_total_watering_time-state] +# name: test_platform_setup_and_discovery[sensor.waschmaschine_power-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'friendly_name': 'Valve Controller 2 Total watering time', - 'state_class': , - 'unit_of_measurement': 's', + 'device_class': 'power', + 'friendly_name': 'Waschmaschine Power', + 'state_class': , + 'unit_of_measurement': 'W', }), 'context': , - 'entity_id': 'sensor.valve_controller_2_total_watering_time', + 'entity_id': 'sensor.waschmaschine_power', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': 'unavailable', + 'state': '10455.0', }) # --- -# name: test_platform_setup_and_discovery[sensor.varmelampa_current-entry] +# name: test_platform_setup_and_discovery[sensor.waschmaschine_voltage-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -17922,7 +18931,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.varmelampa_current', + 'entity_id': 'sensor.waschmaschine_voltage', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -17932,41 +18941,41 @@ 'name': None, 'options': dict({ 'sensor': dict({ - 'suggested_display_precision': 2, + 'suggested_display_precision': 0, }), 'sensor.private': dict({ - 'suggested_unit_of_measurement': , + 'suggested_unit_of_measurement': , }), }), - 'original_device_class': , + 'original_device_class': , 'original_icon': None, - 'original_name': 'Current', + 'original_name': 'Voltage', 'platform': 'tuya', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, - 'translation_key': 'current', - 'unique_id': 'tuya.sw1ejdomlmfubapizccur_current', - 'unit_of_measurement': , + 'translation_key': 'voltage', + 'unique_id': 'tuya.dBFBdywk9gTihUQmzccur_voltage', + 'unit_of_measurement': , }) # --- -# name: test_platform_setup_and_discovery[sensor.varmelampa_current-state] +# name: test_platform_setup_and_discovery[sensor.waschmaschine_voltage-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'device_class': 'current', - 'friendly_name': 'Värmelampa Current', + 'device_class': 'voltage', + 'friendly_name': 'Waschmaschine Voltage', 'state_class': , - 'unit_of_measurement': , + 'unit_of_measurement': , }), 'context': , - 'entity_id': 'sensor.varmelampa_current', + 'entity_id': 'sensor.waschmaschine_voltage', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '0.435', + 'state': '2381.0', }) # --- -# name: test_platform_setup_and_discovery[sensor.varmelampa_power-entry] +# name: test_platform_setup_and_discovery[sensor.water_fountain_filter_duration-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -17981,7 +18990,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.varmelampa_power', + 'entity_id': 'sensor.water_fountain_filter_duration', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -17990,45 +18999,41 @@ }), 'name': None, 'options': dict({ - 'sensor': dict({ - 'suggested_display_precision': 0, - }), }), - 'original_device_class': , + 'original_device_class': None, 'original_icon': None, - 'original_name': 'Power', + 'original_name': 'Filter duration', 'platform': 'tuya', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, - 'translation_key': 'power', - 'unique_id': 'tuya.sw1ejdomlmfubapizccur_power', - 'unit_of_measurement': 'W', + 'translation_key': 'filter_duration', + 'unique_id': 'tuya.q304vac40br8nlkajsywcfilter_life', + 'unit_of_measurement': 'day', }) # --- -# name: test_platform_setup_and_discovery[sensor.varmelampa_power-state] +# name: test_platform_setup_and_discovery[sensor.water_fountain_filter_duration-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'device_class': 'power', - 'friendly_name': 'Värmelampa Power', + 'friendly_name': 'Water Fountain Filter duration', 'state_class': , - 'unit_of_measurement': 'W', + 'unit_of_measurement': 'day', }), 'context': , - 'entity_id': 'sensor.varmelampa_power', + 'entity_id': 'sensor.water_fountain_filter_duration', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '1642.0', + 'state': '14.0', }) # --- -# name: test_platform_setup_and_discovery[sensor.varmelampa_total_energy-entry] +# name: test_platform_setup_and_discovery[sensor.water_fountain_water_pump_duration-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), 'area_id': None, 'capabilities': dict({ - 'state_class': , + 'state_class': , }), 'config_entry_id': , 'config_subentry_id': , @@ -18037,7 +19042,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.varmelampa_total_energy', + 'entity_id': 'sensor.water_fountain_water_pump_duration', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -18046,39 +19051,35 @@ }), 'name': None, 'options': dict({ - 'sensor': dict({ - 'suggested_display_precision': 2, - }), }), - 'original_device_class': , + 'original_device_class': None, 'original_icon': None, - 'original_name': 'Total energy', + 'original_name': 'Water pump duration', 'platform': 'tuya', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, - 'translation_key': 'total_energy', - 'unique_id': 'tuya.sw1ejdomlmfubapizcadd_ele', - 'unit_of_measurement': , + 'translation_key': 'pump_time', + 'unique_id': 'tuya.q304vac40br8nlkajsywcpump_time', + 'unit_of_measurement': 'day', }) # --- -# name: test_platform_setup_and_discovery[sensor.varmelampa_total_energy-state] +# name: test_platform_setup_and_discovery[sensor.water_fountain_water_pump_duration-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'device_class': 'energy', - 'friendly_name': 'Värmelampa Total energy', - 'state_class': , - 'unit_of_measurement': , + 'friendly_name': 'Water Fountain Water pump duration', + 'state_class': , + 'unit_of_measurement': 'day', }), 'context': , - 'entity_id': 'sensor.varmelampa_total_energy', + 'entity_id': 'sensor.water_fountain_water_pump_duration', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '0.082', + 'state': '7.0', }) # --- -# name: test_platform_setup_and_discovery[sensor.varmelampa_voltage-entry] +# name: test_platform_setup_and_discovery[sensor.weihnachten3_current-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -18093,7 +19094,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.varmelampa_voltage', + 'entity_id': 'sensor.weihnachten3_current', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -18103,54 +19104,56 @@ 'name': None, 'options': dict({ 'sensor': dict({ - 'suggested_display_precision': 0, + 'suggested_display_precision': 2, }), 'sensor.private': dict({ - 'suggested_unit_of_measurement': , + 'suggested_unit_of_measurement': , }), }), - 'original_device_class': , + 'original_device_class': , 'original_icon': None, - 'original_name': 'Voltage', + 'original_name': 'Current', 'platform': 'tuya', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, - 'translation_key': 'voltage', - 'unique_id': 'tuya.sw1ejdomlmfubapizccur_voltage', - 'unit_of_measurement': , + 'translation_key': 'current', + 'unique_id': 'tuya.dNBnmtjLU8eRWHf0zccur_current', + 'unit_of_measurement': , }) # --- -# name: test_platform_setup_and_discovery[sensor.varmelampa_voltage-state] +# name: test_platform_setup_and_discovery[sensor.weihnachten3_current-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'device_class': 'voltage', - 'friendly_name': 'Värmelampa Voltage', + 'device_class': 'current', + 'friendly_name': 'Weihnachten3 Current', 'state_class': , - 'unit_of_measurement': , + 'unit_of_measurement': , }), 'context': , - 'entity_id': 'sensor.varmelampa_voltage', + 'entity_id': 'sensor.weihnachten3_current', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '224.6', + 'state': '0.018', }) # --- -# name: test_platform_setup_and_discovery[sensor.vividstorm_screen_last_operation_duration-entry] +# name: test_platform_setup_and_discovery[sensor.weihnachten3_power-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), 'area_id': None, - 'capabilities': 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.vividstorm_screen_last_operation_duration', + 'entity_category': None, + 'entity_id': 'sensor.weihnachten3_power', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -18159,40 +19162,45 @@ }), 'name': None, 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 0, + }), }), - 'original_device_class': None, + 'original_device_class': , 'original_icon': None, - 'original_name': 'Last operation duration', + 'original_name': 'Power', 'platform': 'tuya', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, - 'translation_key': 'last_operation_duration', - 'unique_id': 'tuya.4hbnivc4w2rsw966lctime_total', - 'unit_of_measurement': 'ms', + 'translation_key': 'power', + 'unique_id': 'tuya.dNBnmtjLU8eRWHf0zccur_power', + 'unit_of_measurement': 'W', }) # --- -# name: test_platform_setup_and_discovery[sensor.vividstorm_screen_last_operation_duration-state] +# name: test_platform_setup_and_discovery[sensor.weihnachten3_power-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'friendly_name': 'VIVIDSTORM SCREEN Last operation duration', - 'unit_of_measurement': 'ms', + 'device_class': 'power', + 'friendly_name': 'Weihnachten3 Power', + 'state_class': , + 'unit_of_measurement': 'W', }), 'context': , - 'entity_id': 'sensor.vividstorm_screen_last_operation_duration', + 'entity_id': 'sensor.weihnachten3_power', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '0.0', + 'state': '2.1', }) # --- -# name: test_platform_setup_and_discovery[sensor.water_fountain_filter_duration-entry] +# name: test_platform_setup_and_discovery[sensor.weihnachten3_total_energy-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), 'area_id': None, 'capabilities': dict({ - 'state_class': , + 'state_class': , }), 'config_entry_id': , 'config_subentry_id': , @@ -18201,7 +19209,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.water_fountain_filter_duration', + 'entity_id': 'sensor.weihnachten3_total_energy', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -18213,32 +19221,32 @@ }), 'original_device_class': None, 'original_icon': None, - 'original_name': 'Filter duration', + 'original_name': 'Total energy', 'platform': 'tuya', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, - 'translation_key': 'filter_duration', - 'unique_id': 'tuya.q304vac40br8nlkajsywcfilter_life', - 'unit_of_measurement': 'day', + 'translation_key': 'total_energy', + 'unique_id': 'tuya.dNBnmtjLU8eRWHf0zcadd_ele', + 'unit_of_measurement': '', }) # --- -# name: test_platform_setup_and_discovery[sensor.water_fountain_filter_duration-state] +# name: test_platform_setup_and_discovery[sensor.weihnachten3_total_energy-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'friendly_name': 'Water Fountain Filter duration', - 'state_class': , - 'unit_of_measurement': 'day', + 'friendly_name': 'Weihnachten3 Total energy', + 'state_class': , + 'unit_of_measurement': '', }), 'context': , - 'entity_id': 'sensor.water_fountain_filter_duration', + 'entity_id': 'sensor.weihnachten3_total_energy', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '14.0', + 'state': '0.001', }) # --- -# name: test_platform_setup_and_discovery[sensor.water_fountain_water_pump_duration-entry] +# name: test_platform_setup_and_discovery[sensor.weihnachten3_voltage-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -18253,7 +19261,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.water_fountain_water_pump_duration', + 'entity_id': 'sensor.weihnachten3_voltage', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -18262,32 +19270,39 @@ }), 'name': None, 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 0, + }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': , + }), }), - 'original_device_class': None, + 'original_device_class': , 'original_icon': None, - 'original_name': 'Water pump duration', + 'original_name': 'Voltage', 'platform': 'tuya', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, - 'translation_key': 'pump_time', - 'unique_id': 'tuya.q304vac40br8nlkajsywcpump_time', - 'unit_of_measurement': 'day', + 'translation_key': 'voltage', + 'unique_id': 'tuya.dNBnmtjLU8eRWHf0zccur_voltage', + 'unit_of_measurement': , }) # --- -# name: test_platform_setup_and_discovery[sensor.water_fountain_water_pump_duration-state] +# name: test_platform_setup_and_discovery[sensor.weihnachten3_voltage-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'friendly_name': 'Water Fountain Water pump duration', + 'device_class': 'voltage', + 'friendly_name': 'Weihnachten3 Voltage', 'state_class': , - 'unit_of_measurement': 'day', + 'unit_of_measurement': , }), 'context': , - 'entity_id': 'sensor.water_fountain_water_pump_duration', + 'entity_id': 'sensor.weihnachten3_voltage', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '7.0', + 'state': '235.1', }) # --- # name: test_platform_setup_and_discovery[sensor.weihnachtsmann_current-entry] @@ -19007,6 +20022,59 @@ 'state': '25.1', }) # --- +# name: test_platform_setup_and_discovery[sensor.window_downstairs_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.window_downstairs_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.9c1vlsxoscmbattery_percentage', + 'unit_of_measurement': '%', + }) +# --- +# name: test_platform_setup_and_discovery[sensor.window_downstairs_battery-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'battery', + 'friendly_name': 'Window downstairs Battery', + 'state_class': , + 'unit_of_measurement': '%', + }), + 'context': , + 'entity_id': 'sensor.window_downstairs_battery', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '100.0', + }) +# --- # name: test_platform_setup_and_discovery[sensor.xoca_dac212xc_v2_s1_phase_a_current-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ diff --git a/tests/components/tuya/snapshots/test_switch.ambr b/tests/components/tuya/snapshots/test_switch.ambr index 7df3249aa67d8..eb12e64fe42cd 100644 --- a/tests/components/tuya/snapshots/test_switch.ambr +++ b/tests/components/tuya/snapshots/test_switch.ambr @@ -486,6 +486,54 @@ 'state': 'off', }) # --- +# name: test_platform_setup_and_discovery[switch.anbau_child_lock-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'switch', + 'entity_category': , + 'entity_id': 'switch.anbau_child_lock', + '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': 'Child lock', + 'platform': 'tuya', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'child_lock', + 'unique_id': 'tuya.sq6fbd3pfkwchild_lock', + 'unit_of_measurement': None, + }) +# --- +# name: test_platform_setup_and_discovery[switch.anbau_child_lock-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Anbau Child lock', + }), + 'context': , + 'entity_id': 'switch.anbau_child_lock', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unavailable', + }) +# --- # name: test_platform_setup_and_discovery[switch.apollo_light_socket_1-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ @@ -2855,6 +2903,55 @@ 'state': 'off', }) # --- +# name: test_platform_setup_and_discovery[switch.din_socket-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'switch', + 'entity_category': None, + 'entity_id': 'switch.din_socket', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Socket', + 'platform': 'tuya', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'socket', + 'unique_id': 'tuya.gnZOKztbAtcBkEGPzcswitch', + 'unit_of_measurement': None, + }) +# --- +# name: test_platform_setup_and_discovery[switch.din_socket-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'outlet', + 'friendly_name': 'Din Socket', + }), + 'context': , + 'entity_id': 'switch.din_socket', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'off', + }) +# --- # name: test_platform_setup_and_discovery[switch.droger_socket_1-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ @@ -3386,6 +3483,54 @@ 'state': 'on', }) # --- +# name: test_platform_setup_and_discovery[switch.empore_child_lock-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'switch', + 'entity_category': , + 'entity_id': 'switch.empore_child_lock', + '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': 'Child lock', + 'platform': 'tuya', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'child_lock', + 'unique_id': 'tuya.paxijfx9fkwchild_lock', + 'unit_of_measurement': None, + }) +# --- +# name: test_platform_setup_and_discovery[switch.empore_child_lock-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Empore Child lock', + }), + 'context': , + 'entity_id': 'switch.empore_child_lock', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'off', + }) +# --- # name: test_platform_setup_and_discovery[switch.fakkel_veranda_socket_1-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ @@ -5514,6 +5659,153 @@ 'state': 'unavailable', }) # --- +# name: test_platform_setup_and_discovery[switch.living_room_dehumidifier_child_lock-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'switch', + 'entity_category': , + 'entity_id': 'switch.living_room_dehumidifier_child_lock', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': 'mdi:account-lock', + 'original_name': 'Child lock', + 'platform': 'tuya', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'child_lock', + 'unique_id': 'tuya.g1qorlffoy2iyo9bscchild_lock', + 'unit_of_measurement': None, + }) +# --- +# name: test_platform_setup_and_discovery[switch.living_room_dehumidifier_child_lock-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Living room dehumidifier Child lock', + 'icon': 'mdi:account-lock', + }), + 'context': , + 'entity_id': 'switch.living_room_dehumidifier_child_lock', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'off', + }) +# --- +# name: test_platform_setup_and_discovery[switch.living_room_dehumidifier_ionizer-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'switch', + 'entity_category': , + 'entity_id': 'switch.living_room_dehumidifier_ionizer', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': 'mdi:atom', + 'original_name': 'Ionizer', + 'platform': 'tuya', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'ionizer', + 'unique_id': 'tuya.g1qorlffoy2iyo9bscanion', + 'unit_of_measurement': None, + }) +# --- +# name: test_platform_setup_and_discovery[switch.living_room_dehumidifier_ionizer-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Living room dehumidifier Ionizer', + 'icon': 'mdi:atom', + }), + 'context': , + 'entity_id': 'switch.living_room_dehumidifier_ionizer', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'off', + }) +# --- +# name: test_platform_setup_and_discovery[switch.livr_socket-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'switch', + 'entity_category': None, + 'entity_id': 'switch.livr_socket', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Socket', + 'platform': 'tuya', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'socket', + 'unique_id': 'tuya.9AzrW5XtELTySJxqzcswitch', + 'unit_of_measurement': None, + }) +# --- +# name: test_platform_setup_and_discovery[switch.livr_socket-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'outlet', + 'friendly_name': 'LivR Socket', + }), + 'context': , + 'entity_id': 'switch.livr_socket', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'off', + }) +# --- # name: test_platform_setup_and_discovery[switch.lounge_dark_blind_reverse-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ @@ -5850,6 +6142,55 @@ 'state': 'on', }) # --- +# name: test_platform_setup_and_discovery[switch.n4_auto_socket_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': 'switch', + 'entity_category': None, + 'entity_id': 'switch.n4_auto_socket_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': 'Socket 1', + 'platform': 'tuya', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'indexed_socket', + 'unique_id': 'tuya.uc9fL2NpR79iCzGIzcswitch_1', + 'unit_of_measurement': None, + }) +# --- +# name: test_platform_setup_and_discovery[switch.n4_auto_socket_1-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'outlet', + 'friendly_name': 'N4-Auto Socket 1', + }), + 'context': , + 'entity_id': 'switch.n4_auto_socket_1', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unavailable', + }) +# --- # name: test_platform_setup_and_discovery[switch.office_child_lock-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ @@ -7260,6 +7601,55 @@ 'state': 'on', }) # --- +# name: test_platform_setup_and_discovery[switch.signal_repeater_socket_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': 'switch', + 'entity_category': None, + 'entity_id': 'switch.signal_repeater_socket_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': 'Socket 1', + 'platform': 'tuya', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'indexed_socket', + 'unique_id': 'tuya.rvsneuipzcswitch_1', + 'unit_of_measurement': None, + }) +# --- +# name: test_platform_setup_and_discovery[switch.signal_repeater_socket_1-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'outlet', + 'friendly_name': 'Signal repeater Socket 1', + }), + 'context': , + 'entity_id': 'switch.signal_repeater_socket_1', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'off', + }) +# --- # name: test_platform_setup_and_discovery[switch.smart_odor_eliminator_pro_switch-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ @@ -8964,6 +9354,55 @@ 'state': 'unavailable', }) # --- +# name: test_platform_setup_and_discovery[switch.waschmaschine_socket-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'switch', + 'entity_category': None, + 'entity_id': 'switch.waschmaschine_socket', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Socket', + 'platform': 'tuya', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'socket', + 'unique_id': 'tuya.dBFBdywk9gTihUQmzcswitch', + 'unit_of_measurement': None, + }) +# --- +# name: test_platform_setup_and_discovery[switch.waschmaschine_socket-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'outlet', + 'friendly_name': 'Waschmaschine Socket', + }), + 'context': , + 'entity_id': 'switch.waschmaschine_socket', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'off', + }) +# --- # name: test_platform_setup_and_discovery[switch.water_fountain_filter_reset-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ @@ -9108,6 +9547,55 @@ 'state': 'off', }) # --- +# name: test_platform_setup_and_discovery[switch.weihnachten3_socket_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': 'switch', + 'entity_category': None, + 'entity_id': 'switch.weihnachten3_socket_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': 'Socket 1', + 'platform': 'tuya', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'indexed_socket', + 'unique_id': 'tuya.dNBnmtjLU8eRWHf0zcswitch_1', + 'unit_of_measurement': None, + }) +# --- +# name: test_platform_setup_and_discovery[switch.weihnachten3_socket_1-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'outlet', + 'friendly_name': 'Weihnachten3 Socket 1', + }), + 'context': , + 'entity_id': 'switch.weihnachten3_socket_1', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'off', + }) +# --- # name: test_platform_setup_and_discovery[switch.weihnachtsmann_child_lock-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ From 58459cb80f05bc3d7e4e21446696813c753120ce Mon Sep 17 00:00:00 2001 From: Robert Resch Date: Tue, 23 Sep 2025 10:47:37 +0200 Subject: [PATCH 08/10] Bump deebot-client to 14.0.0 (#152448) --- .../components/ecovacs/manifest.json | 2 +- homeassistant/components/ecovacs/select.py | 4 +- homeassistant/components/ecovacs/strings.json | 4 +- homeassistant/components/ecovacs/util.py | 5 -- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- .../fixtures/devices/n0vyif/device.json | 27 ++++++++ .../ecovacs/snapshots/test_select.ambr | 61 +++++++++++++++++++ .../ecovacs/snapshots/test_sensor.ambr | 4 ++ tests/components/ecovacs/test_select.py | 8 +++ tests/components/ecovacs/test_sensor.py | 2 +- 11 files changed, 110 insertions(+), 11 deletions(-) create mode 100644 tests/components/ecovacs/fixtures/devices/n0vyif/device.json diff --git a/homeassistant/components/ecovacs/manifest.json b/homeassistant/components/ecovacs/manifest.json index b45c06062eebc..3495126fd15f7 100644 --- a/homeassistant/components/ecovacs/manifest.json +++ b/homeassistant/components/ecovacs/manifest.json @@ -6,5 +6,5 @@ "documentation": "https://www.home-assistant.io/integrations/ecovacs", "iot_class": "cloud_push", "loggers": ["sleekxmppfs", "sucks", "deebot_client"], - "requirements": ["py-sucks==0.9.11", "deebot-client==13.7.0"] + "requirements": ["py-sucks==0.9.11", "deebot-client==14.0.0"] } diff --git a/homeassistant/components/ecovacs/select.py b/homeassistant/components/ecovacs/select.py index 84f86fdd2cd46..dc64f70da31ea 100644 --- a/homeassistant/components/ecovacs/select.py +++ b/homeassistant/components/ecovacs/select.py @@ -33,7 +33,9 @@ class EcovacsSelectEntityDescription[EventT: Event]( ENTITY_DESCRIPTIONS: tuple[EcovacsSelectEntityDescription, ...] = ( EcovacsSelectEntityDescription[WaterAmountEvent]( - capability_fn=lambda caps: caps.water.amount if caps.water else None, + capability_fn=lambda caps: caps.water.amount + if caps.water and isinstance(caps.water.amount, CapabilitySetTypes) + else None, current_option_fn=lambda e: get_name_key(e.value), options_fn=lambda water: [get_name_key(amount) for amount in water.types], key="water_amount", diff --git a/homeassistant/components/ecovacs/strings.json b/homeassistant/components/ecovacs/strings.json index 1be81ab129254..8d2d387f6e664 100644 --- a/homeassistant/components/ecovacs/strings.json +++ b/homeassistant/components/ecovacs/strings.json @@ -152,8 +152,10 @@ "station_state": { "name": "Station state", "state": { + "drying_mop": "Drying mop", "idle": "[%key:common::state::idle%]", - "emptying_dustbin": "Emptying dustbin" + "emptying_dustbin": "Emptying dustbin", + "washing_mop": "Washing mop" } }, "stats_area": { diff --git a/homeassistant/components/ecovacs/util.py b/homeassistant/components/ecovacs/util.py index 968ab92851b8a..d26bd1981d7ff 100644 --- a/homeassistant/components/ecovacs/util.py +++ b/homeassistant/components/ecovacs/util.py @@ -7,8 +7,6 @@ import string from typing import TYPE_CHECKING -from deebot_client.events.station import State - from homeassistant.core import HomeAssistant, callback from homeassistant.util import slugify @@ -49,9 +47,6 @@ def get_supported_entities( @callback def get_name_key(enum: Enum) -> str: """Return the lower case name of the enum.""" - if enum is State.EMPTYING: - # Will be fixed in the next major release of deebot-client - return "emptying_dustbin" return enum.name.lower() diff --git a/requirements_all.txt b/requirements_all.txt index 370631c95edd2..1a6649fa5587a 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -782,7 +782,7 @@ decora-wifi==1.4 # decora==0.6 # homeassistant.components.ecovacs -deebot-client==13.7.0 +deebot-client==14.0.0 # homeassistant.components.ihc # homeassistant.components.namecheapdns diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 6a5ffbb786271..4f28c7b5bcf50 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -682,7 +682,7 @@ debugpy==1.8.16 # decora==0.6 # homeassistant.components.ecovacs -deebot-client==13.7.0 +deebot-client==14.0.0 # homeassistant.components.ihc # homeassistant.components.namecheapdns diff --git a/tests/components/ecovacs/fixtures/devices/n0vyif/device.json b/tests/components/ecovacs/fixtures/devices/n0vyif/device.json new file mode 100644 index 0000000000000..71aec03a78692 --- /dev/null +++ b/tests/components/ecovacs/fixtures/devices/n0vyif/device.json @@ -0,0 +1,27 @@ +{ + "did": "E1234567890000000009", + "name": "E1234567890000000009", + "class": "n0vyif", + "resource": "eSQtNR9N", + "company": "eco-ng", + "service": { + "jmq": "jmq-ngiot-eu.dc.ww.ecouser.net", + "mqs": "api-ngiot.dc-eu.ww.ecouser.net" + }, + "deviceName": "DEEBOT X8 PRO OMNI", + "icon": "https://api-app.dc-eu.ww.ecouser.net/api/pim/file/get/66e3ac63a2928902a25d83a0", + "ota": true, + "UILogicId": "keplerh_ww_h_keplerh5", + "materialNo": "110-2417-0402", + "pid": "66daaa789dd37cf146cb1d2e", + "product_category": "DEEBOT", + "model": "KEPLER_BLACK_AI_INT", + "updateInfo": { + "needUpdate": false, + "changeLog": "" + }, + "nick": "X8 PRO OMNI", + "homeSort": 9999, + "status": 1, + "otaUpgrade": {} +} diff --git a/tests/components/ecovacs/snapshots/test_select.ambr b/tests/components/ecovacs/snapshots/test_select.ambr index 420a4a2d48e82..f8e269593d9bb 100644 --- a/tests/components/ecovacs/snapshots/test_select.ambr +++ b/tests/components/ecovacs/snapshots/test_select.ambr @@ -1,4 +1,65 @@ # serializer version: 1 +# name: test_selects[n0vyif-entity_ids1][select.x8_pro_omni_work_mode:entity-registry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'options': list([ + 'mop', + 'mop_after_vacuum', + 'vacuum', + 'vacuum_and_mop', + ]), + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'select', + 'entity_category': , + 'entity_id': 'select.x8_pro_omni_work_mode', + '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': 'Work mode', + 'platform': 'ecovacs', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'work_mode', + 'unique_id': 'E1234567890000000009_work_mode', + 'unit_of_measurement': None, + }) +# --- +# name: test_selects[n0vyif-entity_ids1][select.x8_pro_omni_work_mode:state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'X8 PRO OMNI Work mode', + 'options': list([ + 'mop', + 'mop_after_vacuum', + 'vacuum', + 'vacuum_and_mop', + ]), + }), + 'context': , + 'entity_id': 'select.x8_pro_omni_work_mode', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'vacuum', + }) +# --- # name: test_selects[yna5x1-entity_ids0][select.ozmo_950_water_flow_level:entity-registry] EntityRegistryEntrySnapshot({ 'aliases': set({ diff --git a/tests/components/ecovacs/snapshots/test_sensor.ambr b/tests/components/ecovacs/snapshots/test_sensor.ambr index c216c4c9e4a1c..a3a891e6a87be 100644 --- a/tests/components/ecovacs/snapshots/test_sensor.ambr +++ b/tests/components/ecovacs/snapshots/test_sensor.ambr @@ -1288,6 +1288,8 @@ 'options': list([ 'idle', 'emptying_dustbin', + 'washing_mop', + 'drying_mop', ]), }), 'config_entry_id': , @@ -1327,6 +1329,8 @@ 'options': list([ 'idle', 'emptying_dustbin', + 'washing_mop', + 'drying_mop', ]), }), 'context': , diff --git a/tests/components/ecovacs/test_select.py b/tests/components/ecovacs/test_select.py index c3025d99cfab1..538ab66bed0ef 100644 --- a/tests/components/ecovacs/test_select.py +++ b/tests/components/ecovacs/test_select.py @@ -4,6 +4,7 @@ from deebot_client.commands.json import SetWaterInfo from deebot_client.event_bus import EventBus from deebot_client.events.water_info import WaterAmount, WaterAmountEvent +from deebot_client.events.work_mode import WorkMode, WorkModeEvent import pytest from syrupy.assertion import SnapshotAssertion @@ -34,6 +35,7 @@ def platforms() -> Platform | list[Platform]: async def notify_events(hass: HomeAssistant, event_bus: EventBus): """Notify events.""" event_bus.notify(WaterAmountEvent(WaterAmount.ULTRAHIGH)) + event_bus.notify(WorkModeEvent(WorkMode.VACUUM)) await block_till_done(hass, event_bus) @@ -47,6 +49,12 @@ async def notify_events(hass: HomeAssistant, event_bus: EventBus): "select.ozmo_950_water_flow_level", ], ), + ( + "n0vyif", + [ + "select.x8_pro_omni_work_mode", + ], + ), ], ) async def test_selects( diff --git a/tests/components/ecovacs/test_sensor.py b/tests/components/ecovacs/test_sensor.py index 6c3900ccd1974..5e7173912ba53 100644 --- a/tests/components/ecovacs/test_sensor.py +++ b/tests/components/ecovacs/test_sensor.py @@ -46,7 +46,7 @@ async def notify_events(hass: HomeAssistant, event_bus: EventBus): event_bus.notify(LifeSpanEvent(LifeSpan.FILTER, 56, 40 * 60)) event_bus.notify(LifeSpanEvent(LifeSpan.SIDE_BRUSH, 40, 20 * 60)) event_bus.notify(ErrorEvent(0, "NoError: Robot is operational")) - event_bus.notify(station.StationEvent(station.State.EMPTYING)) + event_bus.notify(station.StationEvent(station.State.EMPTYING_DUSTBIN)) await block_till_done(hass, event_bus) From f0c049237534be3d6bc68320436d8e9e8b063928 Mon Sep 17 00:00:00 2001 From: Lukas <12813107+lmaertin@users.noreply.github.com> Date: Tue, 23 Sep 2025 11:11:08 +0200 Subject: [PATCH 09/10] Add MAC address to Pooldose device (#152760) Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../components/pooldose/config_flow.py | 30 +++++--- .../components/pooldose/coordinator.py | 1 + homeassistant/components/pooldose/entity.py | 14 +++- tests/components/pooldose/test_config_flow.py | 75 ++++++++++++++++++- 4 files changed, 105 insertions(+), 15 deletions(-) diff --git a/homeassistant/components/pooldose/config_flow.py b/homeassistant/components/pooldose/config_flow.py index 36cd93b7515f0..6deb4eafb13c7 100644 --- a/homeassistant/components/pooldose/config_flow.py +++ b/homeassistant/components/pooldose/config_flow.py @@ -10,7 +10,7 @@ import voluptuous as vol from homeassistant.config_entries import ConfigFlow, ConfigFlowResult -from homeassistant.const import CONF_HOST +from homeassistant.const import CONF_HOST, CONF_MAC from homeassistant.helpers import config_validation as cv from homeassistant.helpers.service_info.dhcp import DhcpServiceInfo @@ -31,9 +31,10 @@ class PooldoseConfigFlow(ConfigFlow, domain=DOMAIN): VERSION = 1 def __init__(self) -> None: - """Initialize the config flow and store the discovered IP address.""" + """Initialize the config flow and store the discovered IP address and MAC.""" super().__init__() self._discovered_ip: str | None = None + self._discovered_mac: str | None = None async def _validate_host( self, host: str @@ -71,13 +72,20 @@ async def async_step_dhcp( if not serial_number: return self.async_abort(reason="no_serial_number") - await self.async_set_unique_id(serial_number) - - # Conditionally update IP and abort if entry exists - self._abort_if_unique_id_configured(updates={CONF_HOST: discovery_info.ip}) - - # Continue with new device flow + # If an existing entry is found + existing_entry = await self.async_set_unique_id(serial_number) + if existing_entry: + # Only update the MAC if it's not already set + if CONF_MAC not in existing_entry.data: + self.hass.config_entries.async_update_entry( + existing_entry, + data={**existing_entry.data, CONF_MAC: discovery_info.macaddress}, + ) + self._abort_if_unique_id_configured(updates={CONF_HOST: discovery_info.ip}) + + # Else: Continue with new flow self._discovered_ip = discovery_info.ip + self._discovered_mac = discovery_info.macaddress return self.async_show_form( step_id="dhcp_confirm", description_placeholders={ @@ -91,10 +99,12 @@ async def async_step_dhcp_confirm( self, user_input: dict[str, Any] | None = None ) -> ConfigFlowResult: """Create the entry after the confirmation dialog.""" - discovered_ip = self._discovered_ip return self.async_create_entry( title=f"PoolDose {self.unique_id}", - data={CONF_HOST: discovered_ip}, + data={ + CONF_HOST: self._discovered_ip, + CONF_MAC: self._discovered_mac, + }, ) async def async_step_user( diff --git a/homeassistant/components/pooldose/coordinator.py b/homeassistant/components/pooldose/coordinator.py index 18261ff415616..cd2fa5d991d84 100644 --- a/homeassistant/components/pooldose/coordinator.py +++ b/homeassistant/components/pooldose/coordinator.py @@ -22,6 +22,7 @@ class PooldoseCoordinator(DataUpdateCoordinator[dict[str, Any]]): """Coordinator for PoolDose integration.""" device_info: dict[str, Any] + config_entry: PooldoseConfigEntry def __init__( self, diff --git a/homeassistant/components/pooldose/entity.py b/homeassistant/components/pooldose/entity.py index 84ae216e8ba3b..06c617ad524a9 100644 --- a/homeassistant/components/pooldose/entity.py +++ b/homeassistant/components/pooldose/entity.py @@ -4,7 +4,8 @@ from typing import Any -from homeassistant.helpers.device_registry import DeviceInfo +from homeassistant.const import CONF_MAC +from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC, DeviceInfo from homeassistant.helpers.entity import EntityDescription from homeassistant.helpers.update_coordinator import CoordinatorEntity @@ -12,7 +13,9 @@ from .coordinator import PooldoseCoordinator -def device_info(info: dict | None, unique_id: str) -> DeviceInfo: +def device_info( + info: dict | None, unique_id: str, mac: str | None = None +) -> DeviceInfo: """Create device info for PoolDose devices.""" if info is None: info = {} @@ -35,6 +38,7 @@ def device_info(info: dict | None, unique_id: str) -> DeviceInfo: configuration_url=( f"http://{info['IP']}/index.html" if info.get("IP") else None ), + connections={(CONNECTION_NETWORK_MAC, mac)} if mac else set(), ) @@ -56,7 +60,11 @@ def __init__( self.entity_description = entity_description self.platform_name = platform_name self._attr_unique_id = f"{serial_number}_{entity_description.key}" - self._attr_device_info = device_info(device_properties, serial_number) + self._attr_device_info = device_info( + device_properties, + serial_number, + coordinator.config_entry.data.get(CONF_MAC), + ) @property def available(self) -> bool: diff --git a/tests/components/pooldose/test_config_flow.py b/tests/components/pooldose/test_config_flow.py index 777f2843bba20..354808c51d3c9 100644 --- a/tests/components/pooldose/test_config_flow.py +++ b/tests/components/pooldose/test_config_flow.py @@ -7,7 +7,7 @@ from homeassistant.components.pooldose.const import DOMAIN from homeassistant.config_entries import SOURCE_DHCP, SOURCE_USER -from homeassistant.const import CONF_HOST +from homeassistant.const import CONF_HOST, CONF_MAC from homeassistant.core import HomeAssistant from homeassistant.data_entry_flow import FlowResultType from homeassistant.helpers.service_info.dhcp import DhcpServiceInfo @@ -256,7 +256,8 @@ async def test_dhcp_flow(hass: HomeAssistant, mock_setup_entry: AsyncMock) -> No result = await hass.config_entries.flow.async_configure(result["flow_id"], {}) assert result["type"] is FlowResultType.CREATE_ENTRY assert result["title"] == "PoolDose TEST123456789" - assert result["data"] == {CONF_HOST: "192.168.0.123"} + assert result["data"][CONF_HOST] == "192.168.0.123" + assert result["data"][CONF_MAC] == "a4e57caabbcc" assert result["result"].unique_id == "TEST123456789" @@ -355,3 +356,73 @@ async def test_dhcp_updates_host( assert result["reason"] == "already_configured" assert mock_config_entry.data[CONF_HOST] == "192.168.0.123" + + +async def test_dhcp_adds_mac_if_not_present( + hass: HomeAssistant, mock_pooldose_client: AsyncMock, mock_setup_entry: AsyncMock +) -> None: + """Test that DHCP flow adds MAC address if not already in config entry data.""" + # Create a config entry without MAC address + entry = MockConfigEntry( + domain=DOMAIN, + unique_id="TEST123456789", + data={CONF_HOST: "192.168.1.100"}, + ) + entry.add_to_hass(hass) + + # Verify initial state has no MAC + assert CONF_MAC not in entry.data + + # Simulate DHCP discovery event + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_DHCP}, + data=DhcpServiceInfo( + ip="192.168.0.123", hostname="kommspot", macaddress="a4e57caabbcc" + ), + ) + + # Verify flow aborts as device is already configured + assert result["type"] is FlowResultType.ABORT + assert result["reason"] == "already_configured" + + # Verify MAC was added to the config entry + assert entry.data[CONF_HOST] == "192.168.0.123" + assert entry.data[CONF_MAC] == "a4e57caabbcc" + + +async def test_dhcp_preserves_existing_mac( + hass: HomeAssistant, mock_pooldose_client: AsyncMock, mock_setup_entry: AsyncMock +) -> None: + """Test that DHCP flow preserves existing MAC in config entry data.""" + # Create a config entry with MAC address already set + entry = MockConfigEntry( + domain=DOMAIN, + unique_id="TEST123456789", + data={ + CONF_HOST: "192.168.1.100", + CONF_MAC: "existing11aabb", # Existing MAC that should be preserved + }, + ) + entry.add_to_hass(hass) + + # Verify initial state has the expected MAC + assert entry.data[CONF_MAC] == "existing11aabb" + + # Simulate DHCP discovery event with different MAC + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_DHCP}, + data=DhcpServiceInfo( + ip="192.168.0.123", hostname="kommspot", macaddress="different22ccdd" + ), + ) + + # Verify flow aborts as device is already configured + assert result["type"] is FlowResultType.ABORT + assert result["reason"] == "already_configured" + + # Verify MAC in config entry was NOT updated (original MAC preserved) + assert entry.data[CONF_HOST] == "192.168.0.123" # IP was updated + assert entry.data[CONF_MAC] == "existing11aabb" # MAC remains unchanged + assert entry.data[CONF_MAC] != "different22ccdd" # Not updated to new MAC From 22709506c6fec95172b5d867b0a80d0df6fcd3da Mon Sep 17 00:00:00 2001 From: Robert Resch Date: Tue, 23 Sep 2025 11:21:11 +0200 Subject: [PATCH 10/10] Add Ecovacs custom water amount entity (#152782) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- homeassistant/components/ecovacs/number.py | 29 ++- homeassistant/components/ecovacs/select.py | 8 +- homeassistant/components/ecovacs/strings.json | 5 +- .../ecovacs/snapshots/test_number.ambr | 171 ++++++++++++++++++ tests/components/ecovacs/test_number.py | 38 +++- 5 files changed, 243 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/ecovacs/number.py b/homeassistant/components/ecovacs/number.py index 513a0d350f657..e8cefbd6d1f50 100644 --- a/homeassistant/components/ecovacs/number.py +++ b/homeassistant/components/ecovacs/number.py @@ -5,9 +5,11 @@ from collections.abc import Callable from dataclasses import dataclass -from deebot_client.capabilities import CapabilitySet +from deebot_client.capabilities import CapabilityNumber, CapabilitySet +from deebot_client.device import Device from deebot_client.events import CleanCountEvent, CutDirectionEvent, VolumeEvent from deebot_client.events.base import Event +from deebot_client.events.water_info import WaterCustomAmountEvent from homeassistant.components.number import ( NumberEntity, @@ -75,6 +77,19 @@ class EcovacsNumberEntityDescription[EventT: Event]( native_step=1.0, mode=NumberMode.BOX, ), + EcovacsNumberEntityDescription[WaterCustomAmountEvent]( + capability_fn=lambda caps: ( + caps.water.amount + if caps.water and isinstance(caps.water.amount, CapabilityNumber) + else None + ), + value_fn=lambda e: e.value, + key="water_amount", + translation_key="water_amount", + entity_category=EntityCategory.CONFIG, + native_step=1.0, + mode=NumberMode.BOX, + ), ) @@ -100,6 +115,18 @@ class EcovacsNumberEntity[EventT: Event]( entity_description: EcovacsNumberEntityDescription + def __init__( + self, + device: Device, + capability: CapabilitySet[EventT, [int]], + entity_description: EcovacsNumberEntityDescription, + ) -> None: + """Initialize entity.""" + super().__init__(device, capability, entity_description) + if isinstance(capability, CapabilityNumber): + self._attr_native_min_value = capability.min + self._attr_native_max_value = capability.max + async def async_added_to_hass(self) -> None: """Set up the event listeners now that hass is ready.""" await super().async_added_to_hass() diff --git a/homeassistant/components/ecovacs/select.py b/homeassistant/components/ecovacs/select.py index dc64f70da31ea..440141bbceed4 100644 --- a/homeassistant/components/ecovacs/select.py +++ b/homeassistant/components/ecovacs/select.py @@ -33,9 +33,11 @@ class EcovacsSelectEntityDescription[EventT: Event]( ENTITY_DESCRIPTIONS: tuple[EcovacsSelectEntityDescription, ...] = ( EcovacsSelectEntityDescription[WaterAmountEvent]( - capability_fn=lambda caps: caps.water.amount - if caps.water and isinstance(caps.water.amount, CapabilitySetTypes) - else None, + capability_fn=lambda caps: ( + caps.water.amount + if caps.water and isinstance(caps.water.amount, CapabilitySetTypes) + else None + ), current_option_fn=lambda e: get_name_key(e.value), options_fn=lambda water: [get_name_key(amount) for amount in water.types], key="water_amount", diff --git a/homeassistant/components/ecovacs/strings.json b/homeassistant/components/ecovacs/strings.json index 8d2d387f6e664..e69da61799ff8 100644 --- a/homeassistant/components/ecovacs/strings.json +++ b/homeassistant/components/ecovacs/strings.json @@ -102,6 +102,9 @@ }, "volume": { "name": "Volume" + }, + "water_amount": { + "name": "Water flow level" } }, "sensor": { @@ -176,7 +179,7 @@ }, "select": { "water_amount": { - "name": "Water flow level", + "name": "[%key:component::ecovacs::entity::number::water_amount::name%]", "state": { "high": "[%key:common::state::high%]", "low": "[%key:common::state::low%]", diff --git a/tests/components/ecovacs/snapshots/test_number.ambr b/tests/components/ecovacs/snapshots/test_number.ambr index b89a490c77214..f35ee92ceb86d 100644 --- a/tests/components/ecovacs/snapshots/test_number.ambr +++ b/tests/components/ecovacs/snapshots/test_number.ambr @@ -114,6 +114,177 @@ 'state': '3', }) # --- +# name: test_number_entities[n0vyif][number.x8_pro_omni_clean_count:entity-registry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'max': 4, + 'min': 1, + '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.x8_pro_omni_clean_count', + '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': 'Clean count', + 'platform': 'ecovacs', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'clean_count', + 'unique_id': 'E1234567890000000009_clean_count', + 'unit_of_measurement': None, + }) +# --- +# name: test_number_entities[n0vyif][number.x8_pro_omni_clean_count:state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'X8 PRO OMNI Clean count', + 'max': 4, + 'min': 1, + 'mode': , + 'step': 1.0, + }), + 'context': , + 'entity_id': 'number.x8_pro_omni_clean_count', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '1', + }) +# --- +# name: test_number_entities[n0vyif][number.x8_pro_omni_volume:entity-registry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'max': 11, + 'min': 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.x8_pro_omni_volume', + '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': 'Volume', + 'platform': 'ecovacs', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'volume', + 'unique_id': 'E1234567890000000009_volume', + 'unit_of_measurement': None, + }) +# --- +# name: test_number_entities[n0vyif][number.x8_pro_omni_volume:state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'X8 PRO OMNI Volume', + 'max': 11, + 'min': 0, + 'mode': , + 'step': 1.0, + }), + 'context': , + 'entity_id': 'number.x8_pro_omni_volume', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '5', + }) +# --- +# name: test_number_entities[n0vyif][number.x8_pro_omni_water_flow_level:entity-registry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'max': 50, + 'min': 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.x8_pro_omni_water_flow_level', + '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': 'Water flow level', + 'platform': 'ecovacs', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'water_amount', + 'unique_id': 'E1234567890000000009_water_amount', + 'unit_of_measurement': None, + }) +# --- +# name: test_number_entities[n0vyif][number.x8_pro_omni_water_flow_level:state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'X8 PRO OMNI Water flow level', + 'max': 50, + 'min': 0, + 'mode': , + 'step': 1.0, + }), + 'context': , + 'entity_id': 'number.x8_pro_omni_water_flow_level', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '14', + }) +# --- # name: test_number_entities[yna5x1][number.ozmo_950_volume:entity-registry] EntityRegistryEntrySnapshot({ 'aliases': set({ diff --git a/tests/components/ecovacs/test_number.py b/tests/components/ecovacs/test_number.py index dd7308e18fdf5..02628554519eb 100644 --- a/tests/components/ecovacs/test_number.py +++ b/tests/components/ecovacs/test_number.py @@ -3,8 +3,14 @@ from dataclasses import dataclass from deebot_client.command import Command -from deebot_client.commands.json import SetCutDirection, SetVolume -from deebot_client.events import CutDirectionEvent, Event, VolumeEvent +from deebot_client.commands.json import ( + SetCleanCount, + SetCutDirection, + SetVolume, + SetWaterInfo, +) +from deebot_client.events import CleanCountEvent, CutDirectionEvent, Event, VolumeEvent +from deebot_client.events.water_info import WaterCustomAmountEvent import pytest from syrupy.assertion import SnapshotAssertion @@ -68,8 +74,34 @@ class NumberTestCase: ), ], ), + ( + "n0vyif", + [ + NumberTestCase( + "number.x8_pro_omni_clean_count", + CleanCountEvent(1), + "1", + 4, + SetCleanCount(4), + ), + NumberTestCase( + "number.x8_pro_omni_volume", + VolumeEvent(5, 11), + "5", + 10, + SetVolume(10), + ), + NumberTestCase( + "number.x8_pro_omni_water_flow_level", + WaterCustomAmountEvent(14), + "14", + 7, + SetWaterInfo(custom_amount=7), + ), + ], + ), ], - ids=["yna5x1", "5xu9h3"], + ids=["yna5x1", "5xu9h3", "n0vyif"], ) async def test_number_entities( hass: HomeAssistant,