From bd3fe1d4ad7c6536b5bdf5a55bb5489a48d27b5c Mon Sep 17 00:00:00 2001 From: Brett Adams Date: Mon, 4 Aug 2025 19:26:14 +1000 Subject: [PATCH 01/30] Fix credit sensor when there are no vehicles in Teslemetry (#149925) --- homeassistant/components/teslemetry/models.py | 2 +- homeassistant/components/teslemetry/sensor.py | 15 ++++++++------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/teslemetry/models.py b/homeassistant/components/teslemetry/models.py index 51eed97227e14d..6d12aa56470f41 100644 --- a/homeassistant/components/teslemetry/models.py +++ b/homeassistant/components/teslemetry/models.py @@ -28,7 +28,7 @@ class TeslemetryData: vehicles: list[TeslemetryVehicleData] energysites: list[TeslemetryEnergyData] scopes: list[Scope] - stream: TeslemetryStream + stream: TeslemetryStream | None @dataclass diff --git a/homeassistant/components/teslemetry/sensor.py b/homeassistant/components/teslemetry/sensor.py index 1ffe073cc5cfc7..34ee2d4b8e9553 100644 --- a/homeassistant/components/teslemetry/sensor.py +++ b/homeassistant/components/teslemetry/sensor.py @@ -45,7 +45,7 @@ TeslemetryVehicleStreamEntity, TeslemetryWallConnectorEntity, ) -from .models import TeslemetryData, TeslemetryEnergyData, TeslemetryVehicleData +from .models import TeslemetryEnergyData, TeslemetryVehicleData PARALLEL_UPDATES = 0 @@ -1617,11 +1617,12 @@ async def async_setup_entry( if energysite.history_coordinator is not None ) - entities.append( - TeslemetryCreditBalanceSensor( - entry.unique_id or entry.entry_id, entry.runtime_data + if entry.runtime_data.stream is not None: + entities.append( + TeslemetryCreditBalanceSensor( + entry.unique_id or entry.entry_id, entry.runtime_data.stream + ) ) - ) async_add_entities(entities) @@ -1840,12 +1841,12 @@ class TeslemetryCreditBalanceSensor(RestoreSensor): _attr_state_class = SensorStateClass.MEASUREMENT _attr_suggested_display_precision = 0 - def __init__(self, uid: str, data: TeslemetryData) -> None: + def __init__(self, uid: str, stream: TeslemetryStream) -> None: """Initialize common aspects of a Teslemetry entity.""" self._attr_translation_key = "credit_balance" self._attr_unique_id = f"{uid}_credit_balance" - self.stream = data.stream + self.stream = stream async def async_added_to_hass(self) -> None: """Handle entity which will be added.""" From cf14226b02df6bc133d49404ea40816a5184c38e Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 4 Aug 2025 12:02:21 +0200 Subject: [PATCH 02/30] Pass config entry to Fronius coordinator (#149954) --- homeassistant/components/fronius/__init__.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/homeassistant/components/fronius/__init__.py b/homeassistant/components/fronius/__init__.py index 8a3d1ebf04cece..cfbdfbcb424f71 100644 --- a/homeassistant/components/fronius/__init__.py +++ b/homeassistant/components/fronius/__init__.py @@ -106,6 +106,7 @@ async def init_devices(self) -> None: solar_net=self, logger=_LOGGER, name=f"{DOMAIN}_logger_{self.host}", + config_entry=self.config_entry, ) await self.logger_coordinator.async_config_entry_first_refresh() @@ -120,6 +121,7 @@ async def init_devices(self) -> None: solar_net=self, logger=_LOGGER, name=f"{DOMAIN}_meters_{self.host}", + config_entry=self.config_entry, ) ) @@ -129,6 +131,7 @@ async def init_devices(self) -> None: solar_net=self, logger=_LOGGER, name=f"{DOMAIN}_ohmpilot_{self.host}", + config_entry=self.config_entry, ) ) @@ -138,6 +141,7 @@ async def init_devices(self) -> None: solar_net=self, logger=_LOGGER, name=f"{DOMAIN}_power_flow_{self.host}", + config_entry=self.config_entry, ) ) @@ -147,6 +151,7 @@ async def init_devices(self) -> None: solar_net=self, logger=_LOGGER, name=f"{DOMAIN}_storages_{self.host}", + config_entry=self.config_entry, ) ) @@ -206,6 +211,7 @@ async def _init_devices_inverter(self, _now: datetime | None = None) -> None: logger=_LOGGER, name=_inverter_name, inverter_info=_inverter_info, + config_entry=self.config_entry, ) if self.config_entry.state == ConfigEntryState.LOADED: await _coordinator.async_refresh() From fe2bd8d09e896fbead01306d0d8e36b31647c09f Mon Sep 17 00:00:00 2001 From: epenet <6771947+epenet@users.noreply.github.com> Date: Mon, 4 Aug 2025 12:02:34 +0200 Subject: [PATCH 03/30] Add Tuya snapshots for ywcgq category (#149948) --- tests/components/tuya/__init__.py | 4 + .../tuya/fixtures/ywcgq_wtzwyhkev3b4ubns.json | 139 ++++++++++++++++++ 2 files changed, 143 insertions(+) create mode 100644 tests/components/tuya/fixtures/ywcgq_wtzwyhkev3b4ubns.json diff --git a/tests/components/tuya/__init__.py b/tests/components/tuya/__init__.py index f3525a6e173cdb..4cbe270cdad909 100644 --- a/tests/components/tuya/__init__.py +++ b/tests/components/tuya/__init__.py @@ -256,6 +256,10 @@ Platform.BINARY_SENSOR, Platform.SENSOR, ], + "ywcgq_wtzwyhkev3b4ubns": [ + # https://community.home-assistant.io/t/something-is-wrong-with-tuya-tank-level-sensors-with-the-new-official-integration/689321 + # not (yet) supported + ], "zndb_ze8faryrxr0glqnn": [ # https://github.com/home-assistant/core/issues/138372 Platform.SENSOR, diff --git a/tests/components/tuya/fixtures/ywcgq_wtzwyhkev3b4ubns.json b/tests/components/tuya/fixtures/ywcgq_wtzwyhkev3b4ubns.json new file mode 100644 index 00000000000000..f724ffe164ff51 --- /dev/null +++ b/tests/components/tuya/fixtures/ywcgq_wtzwyhkev3b4ubns.json @@ -0,0 +1,139 @@ +{ + "endpoint": "https://apigw.tuyaeu.com", + "terminal_id": "REDACTED", + "mqtt_connected": true, + "disabled_by": null, + "disabled_polling": false, + "id": "bf27a4********368f4w", + "name": "Nivel del tanque A", + "category": "ywcgq", + "product_id": "wtzwyhkev3b4ubns", + "product_name": "Tank A Level", + "online": true, + "sub": false, + "time_zone": "+01:00", + "active_time": "2024-01-05T10:22:24+00:00", + "create_time": "2024-01-05T10:22:24+00:00", + "update_time": "2024-01-05T10:22:24+00:00", + "function": { + "max_set": { + "type": "Integer", + "value": { + "unit": "%", + "min": 0, + "max": 100, + "scale": 0, + "step": 1 + } + }, + "mini_set": { + "type": "Integer", + "value": { + "unit": "%", + "min": 0, + "max": 100, + "scale": 0, + "step": 1 + } + }, + "installation_height": { + "type": "Integer", + "value": { + "unit": "m", + "min": 200, + "max": 2500, + "scale": 3, + "step": 1 + } + }, + "liquid_depth_max": { + "type": "Integer", + "value": { + "unit": "m", + "min": 100, + "max": 2400, + "scale": 3, + "step": 1 + } + } + }, + "status_range": { + "liquid_state": { + "type": "Enum", + "value": { + "range": ["normal", "lower_alarm", "upper_alarm"] + } + }, + "liquid_depth": { + "type": "Integer", + "value": { + "unit": "m", + "min": 0, + "max": 10000, + "scale": 2, + "step": 1 + } + }, + "max_set": { + "type": "Integer", + "value": { + "unit": "%", + "min": 0, + "max": 100, + "scale": 0, + "step": 1 + } + }, + "mini_set": { + "type": "Integer", + "value": { + "unit": "%", + "min": 0, + "max": 100, + "scale": 0, + "step": 1 + } + }, + "installation_height": { + "type": "Integer", + "value": { + "unit": "m", + "min": 200, + "max": 2500, + "scale": 3, + "step": 1 + } + }, + "liquid_depth_max": { + "type": "Integer", + "value": { + "unit": "m", + "min": 100, + "max": 2400, + "scale": 3, + "step": 1 + } + }, + "liquid_level_percent": { + "type": "Integer", + "value": { + "unit": "%", + "min": 0, + "max": 100, + "scale": 0, + "step": 1 + } + } + }, + "status": { + "liquid_state": "normal", + "liquid_depth": 77, + "max_set": 100, + "mini_set": 10, + "installation_height": 980, + "liquid_depth_max": 140, + "liquid_level_percent": 97 + }, + "set_up": false, + "support_local": true +} From f350a1a1fa8e44f8ee21ab1388b8c8ccca3d81d0 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Mon, 4 Aug 2025 12:03:39 +0200 Subject: [PATCH 04/30] Add hassfest check to help with future dependency updates (#149624) --- script/hassfest/requirements.py | 30 +++++++++++++++++-- tests/hassfest/test_requirements.py | 46 ++++++++++++++++++++++++++++- 2 files changed, 72 insertions(+), 4 deletions(-) diff --git a/script/hassfest/requirements.py b/script/hassfest/requirements.py index 99a1c255e60f74..9b5334823b9645 100644 --- a/script/hassfest/requirements.py +++ b/script/hassfest/requirements.py @@ -43,6 +43,13 @@ "urllib3": "SemVer", "yarl": "SemVer", } +PACKAGE_CHECK_PREPARE_UPDATE: dict[str, int] = { + # In the form dict("dependencyX": n+1) + # - dependencyX should be the name of the referenced dependency + # - current major version +1 + # Pandas will only fully support Python 3.14 in v3. + "pandas": 3, +} PACKAGE_CHECK_VERSION_RANGE_EXCEPTIONS: dict[str, dict[str, set[str]]] = { # In the form dict("domain": {"package": {"dependency1", "dependency2"}}) # - domain is the integration domain @@ -53,6 +60,10 @@ # geocachingapi > reverse_geocode > scipy > numpy "scipy": {"numpy"} }, + "noaa_tides": { + # https://github.com/GClunies/noaa_coops/pull/69 + "noaa-coops": {"pandas"} + }, } PACKAGE_REGEX = re.compile( @@ -568,7 +579,7 @@ def check_dependency_version_range( version == "Any" or (convention := PACKAGE_CHECK_VERSION_RANGE.get(pkg)) is None or all( - _is_dependency_version_range_valid(version_part, convention) + _is_dependency_version_range_valid(version_part, convention, pkg) for version_part in version.split(";", 1)[0].split(",") ) ): @@ -582,22 +593,35 @@ def check_dependency_version_range( return False -def _is_dependency_version_range_valid(version_part: str, convention: str) -> bool: +def _is_dependency_version_range_valid( + version_part: str, convention: str, pkg: str | None = None +) -> bool: + prepare_update = PACKAGE_CHECK_PREPARE_UPDATE.get(pkg) if pkg else None version_match = PIP_VERSION_RANGE_SEPARATOR.match(version_part.strip()) operator = version_match.group(1) version = version_match.group(2) + awesome = AwesomeVersion(version) if operator in (">", ">=", "!="): # Lower version binding and version exclusion are fine return True + if prepare_update is not None: + if operator in ("==", "~="): + # Only current major version allowed which prevents updates to the next one + return False + # Allow upper constraints for major version + 1 + if operator == "<" and awesome.section(0) < prepare_update + 1: + return False + if operator == "<=" and awesome.section(0) < prepare_update: + return False + if convention == "SemVer": if operator == "==": # Explicit version with wildcard is allowed only on major version # e.g. ==1.* is allowed, but ==1.2.* is not return version.endswith(".*") and version.count(".") == 1 - awesome = AwesomeVersion(version) if operator in ("<", "<="): # Upper version binding only allowed on major version # e.g. <=3 is allowed, but <=3.1 is not diff --git a/tests/hassfest/test_requirements.py b/tests/hassfest/test_requirements.py index b9259596c65bce..dcd35a3aca70d1 100644 --- a/tests/hassfest/test_requirements.py +++ b/tests/hassfest/test_requirements.py @@ -1,11 +1,17 @@ """Tests for hassfest requirements.""" from pathlib import Path +from unittest.mock import patch import pytest from script.hassfest.model import Config, Integration -from script.hassfest.requirements import validate_requirements_format +from script.hassfest.requirements import ( + PACKAGE_CHECK_PREPARE_UPDATE, + PACKAGE_CHECK_VERSION_RANGE, + check_dependency_version_range, + validate_requirements_format, +) @pytest.fixture @@ -105,3 +111,41 @@ def test_validate_requirements_format_github_custom(integration: Integration) -> integration.path = Path("") assert validate_requirements_format(integration) assert len(integration.errors) == 0 + + +@pytest.mark.parametrize( + ("version", "result"), + [ + (">2", True), + (">=2.0", True), + (">=2.0,<4", True), + ("<4", True), + ("<=3.0", True), + (">=2.0,<4;python_version<'3.14'", True), + ("<3", False), + ("==2.*", False), + ("~=2.0", False), + ("<=2.100", False), + (">2,<3", False), + (">=2.0,<3", False), + (">=2.0,<3;python_version<'3.14'", False), + ], +) +def test_dependency_version_range_prepare_update( + version: str, result: bool, integration: Integration +) -> None: + """Test dependency version range check for prepare update is working correctly.""" + with ( + patch.dict(PACKAGE_CHECK_VERSION_RANGE, {"numpy-test": "SemVer"}, clear=True), + patch.dict(PACKAGE_CHECK_PREPARE_UPDATE, {"numpy-test": 3}, clear=True), + ): + assert ( + check_dependency_version_range( + integration, + "test", + pkg="numpy-test", + version=version, + package_exceptions=set(), + ) + == result + ) From 8d8383e1c16d37c07f5c76f3fbb0bc23d4c4bb3e Mon Sep 17 00:00:00 2001 From: epenet <6771947+epenet@users.noreply.github.com> Date: Mon, 4 Aug 2025 12:07:25 +0200 Subject: [PATCH 05/30] Add extra Tuya snapshots for dc and dj category (lights) (#149940) --- tests/components/tuya/__init__.py | 106 +- .../tuya/fixtures/dc_l3bpgg8ibsagon4x.json | 149 ++ .../tuya/fixtures/dj_8szt7whdvwpmxglk.json | 495 +++++ .../tuya/fixtures/dj_8y0aquaa8v6tho8w.json | 338 +++ .../tuya/fixtures/dj_baf9tt9lb8t5uc7z.json | 77 + .../tuya/fixtures/dj_d4g0fbsoaal841o6.json | 377 ++++ .../tuya/fixtures/dj_djnozmdyqyriow8z.json | 484 +++++ .../tuya/fixtures/dj_ekwolitfjhxn55js.json | 559 +++++ .../tuya/fixtures/dj_fuupmcr2mb1odkja.json | 338 +++ .../tuya/fixtures/dj_hp6orhaqm6as3jnv.json | 510 +++++ .../tuya/fixtures/dj_hpc8ddyfv85haxa7.json | 156 ++ .../tuya/fixtures/dj_iayz2jmtlipjnxj7.json | 529 +++++ .../tuya/fixtures/dj_idnfq7xbx8qewyoa.json | 523 +++++ .../tuya/fixtures/dj_ilddqqih3tucdk68.json | 77 + .../tuya/fixtures/dj_j1bgp31cffutizub.json | 434 ++++ .../tuya/fixtures/dj_lmnt3uyltk1xffrt.json | 77 + .../tuya/fixtures/dj_nbumqpv8vz61enji.json | 559 +++++ .../tuya/fixtures/dj_nlxvjzy1hoeiqsg6.json | 77 + .../components/tuya/fixtures/dj_oe0cpnjg.json | 226 ++ .../components/tuya/fixtures/dj_riwp3k79.json | 402 ++++ .../tuya/fixtures/dj_tmsloaroqavbucgn.json | 377 ++++ .../tuya/fixtures/dj_ufq2xwuzd4nb0qdr.json | 335 +++ .../tuya/fixtures/dj_vqwcnabamzrc2kab.json | 532 +++++ .../tuya/fixtures/dj_xokdfs6kh5ednakk.json | 377 ++++ .../tuya/fixtures/dj_zakhnlpdiu0ycdxn.json | 77 + .../tuya/fixtures/dj_zav1pa32pyxray78.json | 322 +++ .../tuya/fixtures/dj_zputiamzanuk6yky.json | 413 ++++ .../components/tuya/snapshots/test_light.ambr | 1919 ++++++++++++++++- 28 files changed, 10836 insertions(+), 9 deletions(-) create mode 100644 tests/components/tuya/fixtures/dc_l3bpgg8ibsagon4x.json create mode 100644 tests/components/tuya/fixtures/dj_8szt7whdvwpmxglk.json create mode 100644 tests/components/tuya/fixtures/dj_8y0aquaa8v6tho8w.json create mode 100644 tests/components/tuya/fixtures/dj_baf9tt9lb8t5uc7z.json create mode 100644 tests/components/tuya/fixtures/dj_d4g0fbsoaal841o6.json create mode 100644 tests/components/tuya/fixtures/dj_djnozmdyqyriow8z.json create mode 100644 tests/components/tuya/fixtures/dj_ekwolitfjhxn55js.json create mode 100644 tests/components/tuya/fixtures/dj_fuupmcr2mb1odkja.json create mode 100644 tests/components/tuya/fixtures/dj_hp6orhaqm6as3jnv.json create mode 100644 tests/components/tuya/fixtures/dj_hpc8ddyfv85haxa7.json create mode 100644 tests/components/tuya/fixtures/dj_iayz2jmtlipjnxj7.json create mode 100644 tests/components/tuya/fixtures/dj_idnfq7xbx8qewyoa.json create mode 100644 tests/components/tuya/fixtures/dj_ilddqqih3tucdk68.json create mode 100644 tests/components/tuya/fixtures/dj_j1bgp31cffutizub.json create mode 100644 tests/components/tuya/fixtures/dj_lmnt3uyltk1xffrt.json create mode 100644 tests/components/tuya/fixtures/dj_nbumqpv8vz61enji.json create mode 100644 tests/components/tuya/fixtures/dj_nlxvjzy1hoeiqsg6.json create mode 100644 tests/components/tuya/fixtures/dj_oe0cpnjg.json create mode 100644 tests/components/tuya/fixtures/dj_riwp3k79.json create mode 100644 tests/components/tuya/fixtures/dj_tmsloaroqavbucgn.json create mode 100644 tests/components/tuya/fixtures/dj_ufq2xwuzd4nb0qdr.json create mode 100644 tests/components/tuya/fixtures/dj_vqwcnabamzrc2kab.json create mode 100644 tests/components/tuya/fixtures/dj_xokdfs6kh5ednakk.json create mode 100644 tests/components/tuya/fixtures/dj_zakhnlpdiu0ycdxn.json create mode 100644 tests/components/tuya/fixtures/dj_zav1pa32pyxray78.json create mode 100644 tests/components/tuya/fixtures/dj_zputiamzanuk6yky.json diff --git a/tests/components/tuya/__init__.py b/tests/components/tuya/__init__.py index 4cbe270cdad909..7d6cd32959c22c 100644 --- a/tests/components/tuya/__init__.py +++ b/tests/components/tuya/__init__.py @@ -97,9 +97,113 @@ Platform.SELECT, Platform.SWITCH, ], + "dc_l3bpgg8ibsagon4x": [ + # https://github.com/home-assistant/core/issues/149704 + Platform.LIGHT, + ], + "dj_8szt7whdvwpmxglk": [ + # https://github.com/home-assistant/core/issues/149704 + Platform.LIGHT, + ], + "dj_8y0aquaa8v6tho8w": [ + # https://github.com/home-assistant/core/issues/149704 + Platform.LIGHT, + ], + "dj_baf9tt9lb8t5uc7z": [ + # https://github.com/home-assistant/core/issues/149704 + Platform.LIGHT, + ], + "dj_d4g0fbsoaal841o6": [ + # https://github.com/home-assistant/core/issues/149704 + Platform.LIGHT, + ], + "dj_djnozmdyqyriow8z": [ + # https://github.com/home-assistant/core/issues/149704 + Platform.LIGHT, + ], + "dj_ekwolitfjhxn55js": [ + # https://github.com/home-assistant/core/issues/149704 + Platform.LIGHT, + ], + "dj_fuupmcr2mb1odkja": [ + # https://github.com/home-assistant/core/issues/149704 + Platform.LIGHT, + ], + "dj_hp6orhaqm6as3jnv": [ + # https://github.com/home-assistant/core/issues/149704 + Platform.LIGHT, + ], + "dj_hpc8ddyfv85haxa7": [ + # https://github.com/home-assistant/core/issues/149704 + Platform.LIGHT, + ], + "dj_iayz2jmtlipjnxj7": [ + # https://github.com/home-assistant/core/issues/149704 + Platform.LIGHT, + ], + "dj_idnfq7xbx8qewyoa": [ + # https://github.com/home-assistant/core/issues/149704 + Platform.LIGHT, + ], + "dj_ilddqqih3tucdk68": [ + # https://github.com/home-assistant/core/issues/149704 + Platform.LIGHT, + ], + "dj_j1bgp31cffutizub": [ + # https://github.com/home-assistant/core/issues/149704 + Platform.LIGHT, + ], + "dj_lmnt3uyltk1xffrt": [ + # https://github.com/home-assistant/core/issues/149704 + Platform.LIGHT, + ], "dj_mki13ie507rlry4r": [ # https://github.com/home-assistant/core/pull/126242 - Platform.LIGHT + Platform.LIGHT, + ], + "dj_nbumqpv8vz61enji": [ + # https://github.com/home-assistant/core/issues/149704 + Platform.LIGHT, + ], + "dj_nlxvjzy1hoeiqsg6": [ + # https://github.com/home-assistant/core/issues/149704 + Platform.LIGHT, + ], + "dj_oe0cpnjg": [ + # https://github.com/home-assistant/core/issues/149704 + Platform.LIGHT, + ], + "dj_riwp3k79": [ + # https://github.com/home-assistant/core/issues/149704 + Platform.LIGHT, + ], + "dj_tmsloaroqavbucgn": [ + # https://github.com/home-assistant/core/issues/149704 + Platform.LIGHT, + ], + "dj_ufq2xwuzd4nb0qdr": [ + # https://github.com/home-assistant/core/issues/149704 + Platform.LIGHT, + ], + "dj_vqwcnabamzrc2kab": [ + # https://github.com/home-assistant/core/issues/149704 + Platform.LIGHT, + ], + "dj_xokdfs6kh5ednakk": [ + # https://github.com/home-assistant/core/issues/149704 + Platform.LIGHT, + ], + "dj_zakhnlpdiu0ycdxn": [ + # https://github.com/home-assistant/core/issues/149704 + Platform.LIGHT, + ], + "dj_zav1pa32pyxray78": [ + # https://github.com/home-assistant/core/issues/149704 + Platform.LIGHT, + ], + "dj_zputiamzanuk6yky": [ + # https://github.com/home-assistant/core/issues/149704 + Platform.LIGHT, ], "dlq_0tnvg2xaisqdadcf": [ # https://github.com/home-assistant/core/issues/102769 diff --git a/tests/components/tuya/fixtures/dc_l3bpgg8ibsagon4x.json b/tests/components/tuya/fixtures/dc_l3bpgg8ibsagon4x.json new file mode 100644 index 00000000000000..b37591786189d9 --- /dev/null +++ b/tests/components/tuya/fixtures/dc_l3bpgg8ibsagon4x.json @@ -0,0 +1,149 @@ +{ + "endpoint": "https://apigw.tuyaeu.com", + "terminal_id": "REDACTED", + "mqtt_connected": true, + "disabled_by": null, + "disabled_polling": false, + "id": "bfd9f45c6b882c9f46dxfc", + "name": "LSC Party String Light RGBIC+CCT ", + "category": "dc", + "product_id": "l3bpgg8ibsagon4x", + "product_name": "LSC Party String Light RGBIC+CCT ", + "online": false, + "sub": false, + "time_zone": "+02:00", + "active_time": "2024-07-18T20:38:14+00:00", + "create_time": "2024-07-18T20:38:14+00:00", + "update_time": "2024-07-18T20:38:14+00:00", + "function": { + "switch_led": { + "type": "Boolean", + "value": {} + }, + "work_mode": { + "type": "Enum", + "value": { + "range": ["white", "colour", "scene", "music"] + } + }, + "bright_value": { + "type": "Integer", + "value": { + "min": 10, + "max": 1000, + "scale": 0, + "step": 1 + } + }, + "temp_value": { + "type": "Integer", + "value": { + "min": 0, + "max": 1000, + "scale": 0, + "step": 1 + } + }, + "colour_data": { + "type": "Json", + "value": { + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + } + } + }, + "music_data": { + "type": "String", + "value": { + "maxlen": 255 + } + } + }, + "status_range": { + "switch_led": { + "type": "Boolean", + "value": {} + }, + "work_mode": { + "type": "Enum", + "value": { + "range": ["white", "colour", "scene", "music"] + } + }, + "bright_value": { + "type": "Integer", + "value": { + "min": 10, + "max": 1000, + "scale": 0, + "step": 1 + } + }, + "temp_value": { + "type": "Integer", + "value": { + "min": 0, + "max": 1000, + "scale": 0, + "step": 1 + } + }, + "colour_data": { + "type": "Json", + "value": { + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + } + } + } + }, + "status": { + "switch_led": true, + "work_mode": "colour", + "bright_value": 1000, + "temp_value": 0, + "colour_data": { + "h": 229, + "s": 1000, + "v": 1000 + } + }, + "set_up": true, + "support_local": true +} diff --git a/tests/components/tuya/fixtures/dj_8szt7whdvwpmxglk.json b/tests/components/tuya/fixtures/dj_8szt7whdvwpmxglk.json new file mode 100644 index 00000000000000..6cd0ca55379736 --- /dev/null +++ b/tests/components/tuya/fixtures/dj_8szt7whdvwpmxglk.json @@ -0,0 +1,495 @@ +{ + "endpoint": "https://apigw.tuyaus.com", + "terminal_id": "REDACTED", + "mqtt_connected": true, + "disabled_by": null, + "disabled_polling": false, + "id": "eb10549aadfc74b7c8q2ti", + "name": "Porch light E", + "category": "dj", + "product_id": "8szt7whdvwpmxglk", + "product_name": "Smart Light Bulb", + "online": true, + "sub": false, + "time_zone": "-06:00", + "active_time": "2024-06-19T00:38:29+00:00", + "create_time": "2024-06-19T00:38:29+00:00", + "update_time": "2024-06-19T00:38:29+00:00", + "function": { + "switch_led": { + "type": "Boolean", + "value": {} + }, + "work_mode": { + "type": "Enum", + "value": { + "range": ["white", "colour", "scene", "music"] + } + }, + "bright_value_v2": { + "type": "Integer", + "value": { + "min": 10, + "max": 1000, + "scale": 0, + "step": 1 + } + }, + "colour_data_v2": { + "type": "Json", + "value": { + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + } + } + }, + "scene_data_v2": { + "type": "Json", + "value": { + "scene_num": { + "min": 1, + "scale": 0, + "max": 8, + "step": 1 + }, + "scene_units": { + "unit_change_mode": { + "range": ["static", "jump", "gradient"] + }, + "unit_switch_duration": { + "min": 0, + "scale": 0, + "max": 100, + "step": 1 + }, + "unit_gradient_duration": { + "min": 0, + "scale": 0, + "max": 100, + "step": 1 + }, + "bright": { + "min": 0, + "scale": 0, + "max": 1000, + "step": 1 + }, + "temperature": { + "min": 0, + "scale": 0, + "max": 1000, + "step": 1 + }, + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + } + } + } + }, + "countdown_1": { + "type": "Integer", + "value": { + "unit": "s", + "min": 0, + "max": 86400, + "scale": 0, + "step": 1 + } + }, + "music_data": { + "type": "Json", + "value": { + "change_mode": { + "range": ["direct", "gradient"] + }, + "bright": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "temperature": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + } + } + }, + "control_data": { + "type": "Json", + "value": { + "change_mode": { + "range": ["direct", "gradient"] + }, + "bright": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "temperature": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + } + } + }, + "rhythm_mode": { + "type": "Raw", + "value": { + "maxlen": 255 + } + }, + "sleep_mode": { + "type": "Raw", + "value": { + "maxlen": 255 + } + }, + "wakeup_mode": { + "type": "Raw", + "value": { + "maxlen": 255 + } + } + }, + "status_range": { + "switch_led": { + "type": "Boolean", + "value": {} + }, + "work_mode": { + "type": "Enum", + "value": { + "range": ["white", "colour", "scene", "music"] + } + }, + "bright_value_v2": { + "type": "Integer", + "value": { + "min": 10, + "max": 1000, + "scale": 0, + "step": 1 + } + }, + "colour_data_v2": { + "type": "Json", + "value": { + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + } + } + }, + "scene_data_v2": { + "type": "Json", + "value": { + "scene_num": { + "min": 1, + "scale": 0, + "max": 8, + "step": 1 + }, + "scene_units": { + "unit_change_mode": { + "range": ["static", "jump", "gradient"] + }, + "unit_switch_duration": { + "min": 0, + "scale": 0, + "max": 100, + "step": 1 + }, + "unit_gradient_duration": { + "min": 0, + "scale": 0, + "max": 100, + "step": 1 + }, + "bright": { + "min": 0, + "scale": 0, + "max": 1000, + "step": 1 + }, + "temperature": { + "min": 0, + "scale": 0, + "max": 1000, + "step": 1 + }, + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + } + } + } + }, + "countdown_1": { + "type": "Integer", + "value": { + "unit": "s", + "min": 0, + "max": 86400, + "scale": 0, + "step": 1 + } + }, + "music_data": { + "type": "Json", + "value": { + "change_mode": { + "range": ["direct", "gradient"] + }, + "bright": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "temperature": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + } + } + }, + "control_data": { + "type": "Json", + "value": { + "change_mode": { + "range": ["direct", "gradient"] + }, + "bright": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "temperature": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + } + } + }, + "rhythm_mode": { + "type": "Raw", + "value": { + "maxlen": "255" + } + }, + "sleep_mode": { + "type": "Raw", + "value": { + "maxlen": "255" + } + }, + "wakeup_mode": { + "type": "Raw", + "value": { + "maxlen": "255" + } + } + }, + "status": { + "switch_led": false, + "work_mode": "white", + "bright_value_v2": 1000, + "colour_data_v2": { + "h": 245, + "s": 780, + "v": 1000 + }, + "scene_data_v2": { + "scene_num": 1, + "scene_units": [ + { + "bright": 200, + "h": 0, + "s": 0, + "temperature": 1000, + "unit_change_mode": "static", + "unit_gradient_duration": 13, + "unit_switch_duration": 14, + "v": 0 + } + ] + }, + "countdown_1": 0, + "music_data": "", + "control_data": "", + "rhythm_mode": "AAAAAAA=", + "sleep_mode": "AAA=", + "wakeup_mode": "AAA=" + }, + "set_up": true, + "support_local": true +} diff --git a/tests/components/tuya/fixtures/dj_8y0aquaa8v6tho8w.json b/tests/components/tuya/fixtures/dj_8y0aquaa8v6tho8w.json new file mode 100644 index 00000000000000..ec8f6a0a4d5b90 --- /dev/null +++ b/tests/components/tuya/fixtures/dj_8y0aquaa8v6tho8w.json @@ -0,0 +1,338 @@ +{ + "endpoint": "https://apigw.tuyaeu.com", + "terminal_id": "REDACTED", + "mqtt_connected": true, + "disabled_by": null, + "disabled_polling": false, + "id": "bf71858c3d27943679dsx9", + "name": "dressoir spot", + "category": "dj", + "product_id": "8y0aquaa8v6tho8w", + "product_name": "A60 Clear", + "online": true, + "sub": false, + "time_zone": "+01:00", + "active_time": "2023-01-18T07:49:40+00:00", + "create_time": "2023-01-18T07:49:40+00:00", + "update_time": "2023-01-18T07:49:40+00:00", + "function": { + "switch_led": { + "type": "Boolean", + "value": {} + }, + "work_mode": { + "type": "Enum", + "value": { + "range": ["white", "colour", "scene", "music"] + } + }, + "bright_value_v2": { + "type": "Integer", + "value": { + "min": 10, + "max": 1000, + "scale": 0, + "step": 1 + } + }, + "temp_value_v2": { + "type": "Integer", + "value": { + "min": 0, + "max": 1000, + "scale": 0, + "step": 1 + } + }, + "scene_data_v2": { + "type": "Json", + "value": { + "scene_num": { + "min": 1, + "scale": 0, + "max": 8, + "step": 1 + }, + "scene_units": { + "unit_change_mode": { + "range": ["static", "jump", "gradient"] + }, + "unit_switch_duration": { + "min": 0, + "scale": 0, + "max": 100, + "step": 1 + }, + "unit_gradient_duration": { + "min": 0, + "scale": 0, + "max": 100, + "step": 1 + }, + "bright": { + "min": 0, + "scale": 0, + "max": 1000, + "step": 1 + }, + "temperature": { + "min": 0, + "scale": 0, + "max": 1000, + "step": 1 + }, + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + } + } + } + }, + "countdown_1": { + "type": "Integer", + "value": { + "min": 0, + "max": 86400, + "scale": 0, + "step": 1 + } + }, + "control_data": { + "type": "Json", + "value": { + "change_mode": { + "range": ["direct", "gradient"] + }, + "bright": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "temperature": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + } + } + }, + "remote_switch": { + "type": "Boolean", + "value": {} + } + }, + "status_range": { + "switch_led": { + "type": "Boolean", + "value": {} + }, + "work_mode": { + "type": "Enum", + "value": { + "range": ["white", "colour", "scene", "music"] + } + }, + "bright_value_v2": { + "type": "Integer", + "value": { + "min": 10, + "max": 1000, + "scale": 0, + "step": 1 + } + }, + "temp_value_v2": { + "type": "Integer", + "value": { + "min": 0, + "max": 1000, + "scale": 0, + "step": 1 + } + }, + "scene_data_v2": { + "type": "Json", + "value": { + "scene_num": { + "min": 1, + "scale": 0, + "max": 8, + "step": 1 + }, + "scene_units": { + "unit_change_mode": { + "range": ["static", "jump", "gradient"] + }, + "unit_switch_duration": { + "min": 0, + "scale": 0, + "max": 100, + "step": 1 + }, + "unit_gradient_duration": { + "min": 0, + "scale": 0, + "max": 100, + "step": 1 + }, + "bright": { + "min": 0, + "scale": 0, + "max": 1000, + "step": 1 + }, + "temperature": { + "min": 0, + "scale": 0, + "max": 1000, + "step": 1 + }, + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + } + } + } + }, + "countdown_1": { + "type": "Integer", + "value": { + "min": 0, + "max": 86400, + "scale": 0, + "step": 1 + } + }, + "control_data": { + "type": "Json", + "value": { + "change_mode": { + "range": ["direct", "gradient"] + }, + "bright": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "temperature": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + } + } + }, + "remote_switch": { + "type": "Boolean", + "value": {} + } + }, + "status": { + "switch_led": false, + "work_mode": "white", + "bright_value_v2": 1000, + "temp_value_v2": 0, + "scene_data_v2": { + "scene_num": 1, + "scene_units": [ + { + "bright": 200, + "h": 0, + "s": 0, + "temperature": 0, + "unit_change_mode": "static", + "unit_gradient_duration": 13, + "unit_switch_duration": 14, + "v": 0 + } + ] + }, + "countdown_1": 0, + "control_data": "", + "remote_switch": false + }, + "set_up": true, + "support_local": true +} diff --git a/tests/components/tuya/fixtures/dj_baf9tt9lb8t5uc7z.json b/tests/components/tuya/fixtures/dj_baf9tt9lb8t5uc7z.json new file mode 100644 index 00000000000000..211c0bc12cf73f --- /dev/null +++ b/tests/components/tuya/fixtures/dj_baf9tt9lb8t5uc7z.json @@ -0,0 +1,77 @@ +{ + "endpoint": "https://apigw.tuyaeu.com", + "terminal_id": "REDACTED", + "mqtt_connected": true, + "disabled_by": null, + "disabled_polling": false, + "id": "40611462e09806c73134", + "name": "Pokerlamp 2", + "category": "dj", + "product_id": "baf9tt9lb8t5uc7z", + "product_name": "LED SMART", + "online": false, + "sub": false, + "time_zone": "+01:00", + "active_time": "2021-10-30T17:22:29+00:00", + "create_time": "2021-10-30T17:22:29+00:00", + "update_time": "2021-10-30T17:22:29+00:00", + "function": { + "switch_led": { + "type": "Boolean", + "value": {} + }, + "bright_value": { + "type": "Integer", + "value": { + "min": 25, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + } + }, + "temp_value": { + "type": "Integer", + "value": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + } + } + }, + "status_range": { + "switch_led": { + "type": "Boolean", + "value": {} + }, + "bright_value": { + "type": "Integer", + "value": { + "min": 25, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + } + }, + "temp_value": { + "type": "Integer", + "value": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + } + } + }, + "status": { + "switch_led": true, + "bright_value": 45, + "temp_value": 0 + }, + "set_up": true, + "support_local": true +} diff --git a/tests/components/tuya/fixtures/dj_d4g0fbsoaal841o6.json b/tests/components/tuya/fixtures/dj_d4g0fbsoaal841o6.json new file mode 100644 index 00000000000000..22650f7ae37518 --- /dev/null +++ b/tests/components/tuya/fixtures/dj_d4g0fbsoaal841o6.json @@ -0,0 +1,377 @@ +{ + "endpoint": "https://apigw.tuyaeu.com", + "terminal_id": "REDACTED", + "mqtt_connected": true, + "disabled_by": null, + "disabled_polling": false, + "id": "bf671413db4cee1f9bqdcx", + "name": "WC D1", + "category": "dj", + "product_id": "d4g0fbsoaal841o6", + "product_name": "A60 GOLD", + "online": false, + "sub": false, + "time_zone": "+02:00", + "active_time": "2021-06-30T11:36:31+00:00", + "create_time": "2021-06-30T11:36:31+00:00", + "update_time": "2021-06-30T11:36:31+00:00", + "function": { + "switch_led": { + "type": "Boolean", + "value": {} + }, + "work_mode": { + "type": "Enum", + "value": { + "range": ["white", "colour", "scene", "music"] + } + }, + "bright_value_v2": { + "type": "Integer", + "value": { + "min": 10, + "max": 1000, + "scale": 0, + "step": 1 + } + }, + "temp_value_v2": { + "type": "Integer", + "value": { + "min": 0, + "max": 1000, + "scale": 0, + "step": 1 + } + }, + "scene_data_v2": { + "type": "Json", + "value": { + "scene_num": { + "min": 1, + "scale": 0, + "max": 8, + "step": 1 + }, + "scene_units": { + "unit_change_mode": { + "range": ["static", "jump", "gradient"] + }, + "unit_switch_duration": { + "min": 0, + "scale": 0, + "max": 100, + "step": 1 + }, + "unit_gradient_duration": { + "min": 0, + "scale": 0, + "max": 100, + "step": 1 + }, + "bright": { + "min": 0, + "scale": 0, + "max": 1000, + "step": 1 + }, + "temperature": { + "min": 0, + "scale": 0, + "max": 1000, + "step": 1 + }, + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + } + } + } + }, + "countdown_1": { + "type": "Integer", + "value": { + "min": 0, + "max": 86400, + "scale": 0, + "step": 1 + } + }, + "control_data": { + "type": "Json", + "value": { + "change_mode": { + "range": ["direct", "gradient"] + }, + "bright": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "temperature": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + } + } + }, + "rhythm_mode": { + "type": "Raw", + "value": { + "maxlen": 255 + } + }, + "sleep_mode": { + "type": "Raw", + "value": { + "maxlen": 255 + } + }, + "wakeup_mode": { + "type": "Raw", + "value": { + "maxlen": 255 + } + }, + "remote_switch": { + "type": "Boolean", + "value": {} + } + }, + "status_range": { + "switch_led": { + "type": "Boolean", + "value": {} + }, + "work_mode": { + "type": "Enum", + "value": { + "range": ["white", "colour", "scene", "music"] + } + }, + "bright_value_v2": { + "type": "Integer", + "value": { + "min": 10, + "max": 1000, + "scale": 0, + "step": 1 + } + }, + "temp_value_v2": { + "type": "Integer", + "value": { + "min": 0, + "max": 1000, + "scale": 0, + "step": 1 + } + }, + "scene_data_v2": { + "type": "Json", + "value": { + "scene_num": { + "min": 1, + "scale": 0, + "max": 8, + "step": 1 + }, + "scene_units": { + "unit_change_mode": { + "range": ["static", "jump", "gradient"] + }, + "unit_switch_duration": { + "min": 0, + "scale": 0, + "max": 100, + "step": 1 + }, + "unit_gradient_duration": { + "min": 0, + "scale": 0, + "max": 100, + "step": 1 + }, + "bright": { + "min": 0, + "scale": 0, + "max": 1000, + "step": 1 + }, + "temperature": { + "min": 0, + "scale": 0, + "max": 1000, + "step": 1 + }, + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + } + } + } + }, + "countdown_1": { + "type": "Integer", + "value": { + "min": 0, + "max": 86400, + "scale": 0, + "step": 1 + } + }, + "control_data": { + "type": "Json", + "value": { + "change_mode": { + "range": ["direct", "gradient"] + }, + "bright": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "temperature": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + } + } + }, + "rhythm_mode": { + "type": "Raw", + "value": { + "maxlen": "255" + } + }, + "sleep_mode": { + "type": "Raw", + "value": { + "maxlen": "255" + } + }, + "wakeup_mode": { + "type": "Raw", + "value": { + "maxlen": "255" + } + }, + "remote_switch": { + "type": "Boolean", + "value": {} + } + }, + "status": { + "switch_led": true, + "work_mode": "white", + "bright_value_v2": 1000, + "temp_value_v2": 1000, + "scene_data_v2": { + "scene_num": 1, + "scene_units": [ + { + "bright": 200, + "h": 0, + "s": 0, + "temperature": 0, + "unit_change_mode": "static", + "unit_gradient_duration": 13, + "unit_switch_duration": 14, + "v": 0 + } + ] + }, + "countdown_1": 0, + "control_data": "", + "rhythm_mode": "AAAAAAA=", + "sleep_mode": "AAA=", + "wakeup_mode": "AAA=", + "remote_switch": true + }, + "set_up": true, + "support_local": true +} diff --git a/tests/components/tuya/fixtures/dj_djnozmdyqyriow8z.json b/tests/components/tuya/fixtures/dj_djnozmdyqyriow8z.json new file mode 100644 index 00000000000000..67df13c674f219 --- /dev/null +++ b/tests/components/tuya/fixtures/dj_djnozmdyqyriow8z.json @@ -0,0 +1,484 @@ +{ + "endpoint": "https://apigw.tuyaeu.com", + "terminal_id": "REDACTED", + "mqtt_connected": true, + "disabled_by": null, + "disabled_polling": false, + "id": "bf8885f3d18a73e395bfac", + "name": "Fakkel 8", + "category": "dj", + "product_id": "djnozmdyqyriow8z", + "product_name": "Candle RGB-CCT", + "online": true, + "sub": false, + "time_zone": "+02:00", + "active_time": "2021-06-30T12:13:49+00:00", + "create_time": "2021-06-30T12:13:49+00:00", + "update_time": "2021-06-30T12:13:49+00:00", + "function": { + "switch_led": { + "type": "Boolean", + "value": {} + }, + "work_mode": { + "type": "Enum", + "value": { + "range": ["white", "colour", "scene", "music"] + } + }, + "bright_value_v2": { + "type": "Integer", + "value": { + "min": 10, + "max": 1000, + "scale": 0, + "step": 1 + } + }, + "temp_value_v2": { + "type": "Integer", + "value": { + "min": 0, + "max": 1000, + "scale": 0, + "step": 1 + } + }, + "colour_data_v2": { + "type": "Json", + "value": { + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + } + } + }, + "scene_data_v2": { + "type": "Json", + "value": { + "scene_num": { + "min": 1, + "scale": 0, + "max": 8, + "step": 1 + }, + "scene_units": { + "unit_change_mode": { + "range": ["static", "jump", "gradient"] + }, + "unit_switch_duration": { + "min": 0, + "scale": 0, + "max": 100, + "step": 1 + }, + "unit_gradient_duration": { + "min": 0, + "scale": 0, + "max": 100, + "step": 1 + }, + "bright": { + "min": 0, + "scale": 0, + "max": 1000, + "step": 1 + }, + "temperature": { + "min": 0, + "scale": 0, + "max": 1000, + "step": 1 + }, + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + } + } + } + }, + "countdown_1": { + "type": "Integer", + "value": { + "min": 0, + "max": 86400, + "scale": 0, + "step": 1 + } + }, + "control_data": { + "type": "Json", + "value": { + "change_mode": { + "range": ["direct", "gradient"] + }, + "bright": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "temperature": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + } + } + }, + "rhythm_mode": { + "type": "Raw", + "value": { + "maxlen": 255 + } + }, + "sleep_mode": { + "type": "Raw", + "value": { + "maxlen": 255 + } + }, + "wakeup_mode": { + "type": "Raw", + "value": { + "maxlen": 255 + } + }, + "remote_switch": { + "type": "Boolean", + "value": {} + } + }, + "status_range": { + "switch_led": { + "type": "Boolean", + "value": {} + }, + "work_mode": { + "type": "Enum", + "value": { + "range": ["white", "colour", "scene", "music"] + } + }, + "bright_value_v2": { + "type": "Integer", + "value": { + "min": 10, + "max": 1000, + "scale": 0, + "step": 1 + } + }, + "temp_value_v2": { + "type": "Integer", + "value": { + "min": 0, + "max": 1000, + "scale": 0, + "step": 1 + } + }, + "colour_data_v2": { + "type": "Json", + "value": { + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + } + } + }, + "scene_data_v2": { + "type": "Json", + "value": { + "scene_num": { + "min": 1, + "scale": 0, + "max": 8, + "step": 1 + }, + "scene_units": { + "unit_change_mode": { + "range": ["static", "jump", "gradient"] + }, + "unit_switch_duration": { + "min": 0, + "scale": 0, + "max": 100, + "step": 1 + }, + "unit_gradient_duration": { + "min": 0, + "scale": 0, + "max": 100, + "step": 1 + }, + "bright": { + "min": 0, + "scale": 0, + "max": 1000, + "step": 1 + }, + "temperature": { + "min": 0, + "scale": 0, + "max": 1000, + "step": 1 + }, + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + } + } + } + }, + "countdown_1": { + "type": "Integer", + "value": { + "min": 0, + "max": 86400, + "scale": 0, + "step": 1 + } + }, + "control_data": { + "type": "Json", + "value": { + "change_mode": { + "range": ["direct", "gradient"] + }, + "bright": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "temperature": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + } + } + }, + "rhythm_mode": { + "type": "Raw", + "value": { + "maxlen": "255" + } + }, + "sleep_mode": { + "type": "Raw", + "value": { + "maxlen": "255" + } + }, + "wakeup_mode": { + "type": "Raw", + "value": { + "maxlen": "255" + } + }, + "remote_switch": { + "type": "Boolean", + "value": {} + } + }, + "status": { + "switch_led": true, + "work_mode": "white", + "bright_value_v2": 280, + "temp_value_v2": 0, + "colour_data_v2": { + "h": 56, + "s": 1000, + "v": 1000 + }, + "scene_data_v2": { + "scene_num": 8, + "scene_units": [ + { + "bright": 0, + "h": 0, + "s": 1000, + "temperature": 0, + "unit_change_mode": "gradient", + "unit_gradient_duration": 70, + "unit_switch_duration": 70, + "v": 1000 + }, + { + "bright": 0, + "h": 120, + "s": 1000, + "temperature": 0, + "unit_change_mode": "gradient", + "unit_gradient_duration": 70, + "unit_switch_duration": 70, + "v": 1000 + }, + { + "bright": 0, + "h": 240, + "s": 1000, + "temperature": 0, + "unit_change_mode": "gradient", + "unit_gradient_duration": 70, + "unit_switch_duration": 70, + "v": 1000 + }, + { + "bright": 0, + "h": 61, + "s": 1000, + "temperature": 0, + "unit_change_mode": "gradient", + "unit_gradient_duration": 70, + "unit_switch_duration": 70, + "v": 1000 + }, + { + "bright": 0, + "h": 174, + "s": 1000, + "temperature": 0, + "unit_change_mode": "gradient", + "unit_gradient_duration": 70, + "unit_switch_duration": 70, + "v": 1000 + }, + { + "bright": 0, + "h": 275, + "s": 1000, + "temperature": 0, + "unit_change_mode": "gradient", + "unit_gradient_duration": 70, + "unit_switch_duration": 70, + "v": 1000 + } + ] + }, + "countdown_1": 0, + "control_data": "", + "rhythm_mode": "AAAAAAA=", + "sleep_mode": "AAA=", + "wakeup_mode": "AAA=", + "remote_switch": true + }, + "set_up": true, + "support_local": true +} diff --git a/tests/components/tuya/fixtures/dj_ekwolitfjhxn55js.json b/tests/components/tuya/fixtures/dj_ekwolitfjhxn55js.json new file mode 100644 index 00000000000000..90cad22fd09a43 --- /dev/null +++ b/tests/components/tuya/fixtures/dj_ekwolitfjhxn55js.json @@ -0,0 +1,559 @@ +{ + "endpoint": "https://apigw.tuyaeu.com", + "terminal_id": "REDACTED", + "mqtt_connected": true, + "disabled_by": null, + "disabled_polling": false, + "id": "bfb99bba00c9c90ba8gzgl", + "name": "ab6", + "category": "dj", + "product_id": "ekwolitfjhxn55js", + "product_name": "LSC Smart Connect GU10 RGB+CCT", + "online": false, + "sub": false, + "time_zone": "+01:00", + "active_time": "2024-10-30T21:26:33+00:00", + "create_time": "2024-10-30T21:26:33+00:00", + "update_time": "2024-10-30T21:26:33+00:00", + "function": { + "switch_led": { + "type": "Boolean", + "value": {} + }, + "work_mode": { + "type": "Enum", + "value": { + "range": ["white", "colour", "scene", "music"] + } + }, + "bright_value_v2": { + "type": "Integer", + "value": { + "min": 10, + "max": 1000, + "scale": 0, + "step": 1 + } + }, + "temp_value_v2": { + "type": "Integer", + "value": { + "min": 0, + "max": 1000, + "scale": 0, + "step": 1 + } + }, + "colour_data_v2": { + "type": "Json", + "value": { + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + } + } + }, + "scene_data_v2": { + "type": "Json", + "value": { + "scene_num": { + "min": 1, + "scale": 0, + "max": 8, + "step": 1 + }, + "scene_units": { + "unit_change_mode": { + "range": ["static", "jump", "gradient"] + }, + "unit_switch_duration": { + "min": 0, + "scale": 0, + "max": 100, + "step": 1 + }, + "unit_gradient_duration": { + "min": 0, + "scale": 0, + "max": 100, + "step": 1 + }, + "bright": { + "min": 0, + "scale": 0, + "max": 1000, + "step": 1 + }, + "temperature": { + "min": 0, + "scale": 0, + "max": 1000, + "step": 1 + }, + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + } + } + } + }, + "countdown_1": { + "type": "Integer", + "value": { + "unit": "s", + "min": 0, + "max": 86400, + "scale": 0, + "step": 1 + } + }, + "music_data": { + "type": "Json", + "value": { + "change_mode": { + "range": ["direct", "gradient"] + }, + "bright": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "temperature": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + } + } + }, + "control_data": { + "type": "Json", + "value": { + "change_mode": { + "range": ["direct", "gradient"] + }, + "bright": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "temperature": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + } + } + }, + "rhythm_mode": { + "type": "Raw", + "value": { + "maxlen": 255 + } + }, + "sleep_mode": { + "type": "Raw", + "value": { + "maxlen": 255 + } + }, + "wakeup_mode": { + "type": "Raw", + "value": { + "maxlen": 255 + } + }, + "power_memory": { + "type": "Raw", + "value": {} + }, + "do_not_disturb": { + "type": "Boolean", + "value": {} + }, + "remote_switch": { + "type": "Boolean", + "value": {} + }, + "cycle_timing": { + "type": "Raw", + "value": {} + }, + "random_timing": { + "type": "Raw", + "value": {} + } + }, + "status_range": { + "switch_led": { + "type": "Boolean", + "value": {} + }, + "work_mode": { + "type": "Enum", + "value": { + "range": ["white", "colour", "scene", "music"] + } + }, + "bright_value_v2": { + "type": "Integer", + "value": { + "min": 10, + "max": 1000, + "scale": 0, + "step": 1 + } + }, + "temp_value_v2": { + "type": "Integer", + "value": { + "min": 0, + "max": 1000, + "scale": 0, + "step": 1 + } + }, + "colour_data_v2": { + "type": "Json", + "value": { + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + } + } + }, + "scene_data_v2": { + "type": "Json", + "value": { + "scene_num": { + "min": 1, + "scale": 0, + "max": 8, + "step": 1 + }, + "scene_units": { + "unit_change_mode": { + "range": ["static", "jump", "gradient"] + }, + "unit_switch_duration": { + "min": 0, + "scale": 0, + "max": 100, + "step": 1 + }, + "unit_gradient_duration": { + "min": 0, + "scale": 0, + "max": 100, + "step": 1 + }, + "bright": { + "min": 0, + "scale": 0, + "max": 1000, + "step": 1 + }, + "temperature": { + "min": 0, + "scale": 0, + "max": 1000, + "step": 1 + }, + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + } + } + } + }, + "countdown_1": { + "type": "Integer", + "value": { + "unit": "s", + "min": 0, + "max": 86400, + "scale": 0, + "step": 1 + } + }, + "music_data": { + "type": "Json", + "value": { + "change_mode": { + "range": ["direct", "gradient"] + }, + "bright": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "temperature": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + } + } + }, + "control_data": { + "type": "Json", + "value": { + "change_mode": { + "range": ["direct", "gradient"] + }, + "bright": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "temperature": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + } + } + }, + "rhythm_mode": { + "type": "Raw", + "value": { + "maxlen": "255" + } + }, + "sleep_mode": { + "type": "Raw", + "value": { + "maxlen": "255" + } + }, + "wakeup_mode": { + "type": "Raw", + "value": { + "maxlen": "255" + } + }, + "power_memory": { + "type": "Raw", + "value": {} + }, + "do_not_disturb": { + "type": "Boolean", + "value": {} + }, + "remote_switch": { + "type": "Boolean", + "value": {} + }, + "cycle_timing": { + "type": "Raw", + "value": {} + }, + "random_timing": { + "type": "Raw", + "value": {} + } + }, + "status": { + "switch_led": false, + "work_mode": "colour", + "bright_value_v2": 1000, + "temp_value_v2": 0, + "colour_data_v2": { + "h": 3, + "s": 994, + "v": 443 + }, + "scene_data_v2": { + "scene_num": 1, + "scene_units": [ + { + "bright": 200, + "h": 0, + "s": 0, + "temperature": 0, + "unit_change_mode": "static", + "unit_gradient_duration": 13, + "unit_switch_duration": 14, + "v": 0 + } + ] + }, + "countdown_1": 0, + "music_data": "", + "control_data": "", + "rhythm_mode": "AAAAAAA=", + "sleep_mode": "AAA=", + "wakeup_mode": "AAA=", + "power_memory": "AAEAAAPoA+gD6AAA", + "do_not_disturb": false, + "remote_switch": true, + "cycle_timing": "AAAA", + "random_timing": "AAAA" + }, + "set_up": true, + "support_local": true +} diff --git a/tests/components/tuya/fixtures/dj_fuupmcr2mb1odkja.json b/tests/components/tuya/fixtures/dj_fuupmcr2mb1odkja.json new file mode 100644 index 00000000000000..5b189b6a3e4a22 --- /dev/null +++ b/tests/components/tuya/fixtures/dj_fuupmcr2mb1odkja.json @@ -0,0 +1,338 @@ +{ + "endpoint": "https://apigw.tuyaeu.com", + "terminal_id": "REDACTED", + "mqtt_connected": true, + "disabled_by": null, + "disabled_polling": false, + "id": "bf0914a82b06ecf151xsf5", + "name": "Slaapkamer", + "category": "dj", + "product_id": "fuupmcr2mb1odkja", + "product_name": "ST64 Clear", + "online": true, + "sub": false, + "time_zone": "+01:00", + "active_time": "2023-01-28T01:25:04+00:00", + "create_time": "2023-01-28T01:25:04+00:00", + "update_time": "2023-01-28T01:25:04+00:00", + "function": { + "switch_led": { + "type": "Boolean", + "value": {} + }, + "work_mode": { + "type": "Enum", + "value": { + "range": ["white", "colour", "scene", "music"] + } + }, + "bright_value_v2": { + "type": "Integer", + "value": { + "min": 10, + "max": 1000, + "scale": 0, + "step": 1 + } + }, + "temp_value_v2": { + "type": "Integer", + "value": { + "min": 0, + "max": 1000, + "scale": 0, + "step": 1 + } + }, + "scene_data_v2": { + "type": "Json", + "value": { + "scene_num": { + "min": 1, + "scale": 0, + "max": 8, + "step": 1 + }, + "scene_units": { + "unit_change_mode": { + "range": ["static", "jump", "gradient"] + }, + "unit_switch_duration": { + "min": 0, + "scale": 0, + "max": 100, + "step": 1 + }, + "unit_gradient_duration": { + "min": 0, + "scale": 0, + "max": 100, + "step": 1 + }, + "bright": { + "min": 0, + "scale": 0, + "max": 1000, + "step": 1 + }, + "temperature": { + "min": 0, + "scale": 0, + "max": 1000, + "step": 1 + }, + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + } + } + } + }, + "countdown_1": { + "type": "Integer", + "value": { + "min": 0, + "max": 86400, + "scale": 0, + "step": 1 + } + }, + "control_data": { + "type": "Json", + "value": { + "change_mode": { + "range": ["direct", "gradient"] + }, + "bright": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "temperature": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + } + } + }, + "remote_switch": { + "type": "Boolean", + "value": {} + } + }, + "status_range": { + "switch_led": { + "type": "Boolean", + "value": {} + }, + "work_mode": { + "type": "Enum", + "value": { + "range": ["white", "colour", "scene", "music"] + } + }, + "bright_value_v2": { + "type": "Integer", + "value": { + "min": 10, + "max": 1000, + "scale": 0, + "step": 1 + } + }, + "temp_value_v2": { + "type": "Integer", + "value": { + "min": 0, + "max": 1000, + "scale": 0, + "step": 1 + } + }, + "scene_data_v2": { + "type": "Json", + "value": { + "scene_num": { + "min": 1, + "scale": 0, + "max": 8, + "step": 1 + }, + "scene_units": { + "unit_change_mode": { + "range": ["static", "jump", "gradient"] + }, + "unit_switch_duration": { + "min": 0, + "scale": 0, + "max": 100, + "step": 1 + }, + "unit_gradient_duration": { + "min": 0, + "scale": 0, + "max": 100, + "step": 1 + }, + "bright": { + "min": 0, + "scale": 0, + "max": 1000, + "step": 1 + }, + "temperature": { + "min": 0, + "scale": 0, + "max": 1000, + "step": 1 + }, + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + } + } + } + }, + "countdown_1": { + "type": "Integer", + "value": { + "min": 0, + "max": 86400, + "scale": 0, + "step": 1 + } + }, + "control_data": { + "type": "Json", + "value": { + "change_mode": { + "range": ["direct", "gradient"] + }, + "bright": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "temperature": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + } + } + }, + "remote_switch": { + "type": "Boolean", + "value": {} + } + }, + "status": { + "switch_led": false, + "work_mode": "white", + "bright_value_v2": 1000, + "temp_value_v2": 0, + "scene_data_v2": { + "scene_num": 1, + "scene_units": [ + { + "bright": 200, + "h": 0, + "s": 0, + "temperature": 0, + "unit_change_mode": "static", + "unit_gradient_duration": 13, + "unit_switch_duration": 14, + "v": 0 + } + ] + }, + "countdown_1": 0, + "control_data": "", + "remote_switch": true + }, + "set_up": true, + "support_local": true +} diff --git a/tests/components/tuya/fixtures/dj_hp6orhaqm6as3jnv.json b/tests/components/tuya/fixtures/dj_hp6orhaqm6as3jnv.json new file mode 100644 index 00000000000000..e8166a192dc5d7 --- /dev/null +++ b/tests/components/tuya/fixtures/dj_hp6orhaqm6as3jnv.json @@ -0,0 +1,510 @@ +{ + "endpoint": "https://apigw.tuyaus.com", + "terminal_id": "REDACTED", + "mqtt_connected": true, + "disabled_by": null, + "disabled_polling": false, + "id": "00450321483fda81c529", + "name": "Master bedroom TV lights", + "category": "dj", + "product_id": "hp6orhaqm6as3jnv", + "product_name": "LED Strip Lights", + "online": true, + "sub": false, + "time_zone": "-07:00", + "active_time": "2024-06-19T03:35:54+00:00", + "create_time": "2024-06-19T03:35:54+00:00", + "update_time": "2024-06-19T03:35:54+00:00", + "function": { + "switch_led": { + "type": "Boolean", + "value": {} + }, + "work_mode": { + "type": "Enum", + "value": { + "range": ["white", "colour"] + } + }, + "bright_value": { + "type": "Integer", + "value": { + "min": 25, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + } + }, + "temp_value": { + "type": "Integer", + "value": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + } + }, + "colour_data": { + "type": "Json", + "value": { + "h": { + "min": 1, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 1, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + }, + "v": { + "min": 1, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + } + } + }, + "scene_data": { + "type": "Json", + "value": { + "h": { + "min": 1, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 1, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + }, + "v": { + "min": 1, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + } + } + }, + "flash_scene_1": { + "type": "Json", + "value": { + "h": { + "min": 1, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 1, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + }, + "v": { + "min": 1, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + } + } + }, + "flash_scene_2": { + "type": "Json", + "value": { + "h": { + "min": 1, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 1, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + }, + "v": { + "min": 1, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + } + } + }, + "flash_scene_3": { + "type": "Json", + "value": { + "h": { + "min": 1, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 1, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + }, + "v": { + "min": 1, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + } + } + }, + "flash_scene_4": { + "type": "Json", + "value": { + "h": { + "min": 1, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 1, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + }, + "v": { + "min": 1, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + } + } + } + }, + "status_range": { + "switch_led": { + "type": "Boolean", + "value": {} + }, + "work_mode": { + "type": "Enum", + "value": { + "range": ["white", "colour"] + } + }, + "bright_value": { + "type": "Integer", + "value": { + "min": 25, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + } + }, + "temp_value": { + "type": "Integer", + "value": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + } + }, + "colour_data": { + "type": "Json", + "value": { + "h": { + "min": 1, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 1, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + }, + "v": { + "min": 1, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + } + } + }, + "scene_data": { + "type": "Json", + "value": { + "h": { + "min": 1, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 1, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + }, + "v": { + "min": 1, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + } + } + }, + "flash_scene_1": { + "type": "Json", + "value": { + "h": { + "min": 1, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 1, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + }, + "v": { + "min": 1, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + } + } + }, + "flash_scene_2": { + "type": "Json", + "value": { + "h": { + "min": 1, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 1, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + }, + "v": { + "min": 1, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + } + } + }, + "flash_scene_3": { + "type": "Json", + "value": { + "h": { + "min": 1, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 1, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + }, + "v": { + "min": 1, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + } + } + }, + "flash_scene_4": { + "type": "Json", + "value": { + "h": { + "min": 1, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 1, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + }, + "v": { + "min": 1, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + } + } + } + }, + "status": { + "switch_led": true, + "work_mode": "colour", + "bright_value": 96, + "temp_value": 223, + "colour_data": { + "h": 27.0, + "s": 255.0, + "v": 52.0 + }, + "scene_data": { + "h": 16.0, + "s": 255.0, + "v": 210.9 + }, + "flash_scene_1": { + "bright": 255, + "frequency": 80, + "hsv": [ + { + "h": 120.0, + "s": 255.0, + "v": 255.0 + } + ], + "temperature": 255 + }, + "flash_scene_2": { + "bright": 255, + "frequency": 128, + "hsv": [ + { + "h": 0.0, + "s": 255.0, + "v": 255.0 + }, + { + "h": 120.0, + "s": 255.0, + "v": 255.0 + }, + { + "h": 240.0, + "s": 255.0, + "v": 255.0 + }, + { + "h": 0.0, + "s": 0.0, + "v": 0.0 + }, + { + "h": 0.0, + "s": 0.0, + "v": 0.0 + }, + { + "h": 0.0, + "s": 0.0, + "v": 0.0 + } + ], + "temperature": 255 + }, + "flash_scene_3": { + "bright": 255, + "frequency": 80, + "hsv": [ + { + "h": 0.0, + "s": 255.0, + "v": 255.0 + } + ], + "temperature": 255 + }, + "flash_scene_4": { + "bright": 255, + "frequency": 5, + "hsv": [ + { + "h": 0.0, + "s": 255.0, + "v": 255.0 + }, + { + "h": 120.0, + "s": 255.0, + "v": 255.0 + }, + { + "h": 60.0, + "s": 255.0, + "v": 255.0 + }, + { + "h": 300.0, + "s": 255.0, + "v": 255.0 + }, + { + "h": 240.0, + "s": 255.0, + "v": 255.0 + }, + { + "h": 0.0, + "s": 0.0, + "v": 0.0 + } + ], + "temperature": 255 + } + }, + "set_up": true, + "support_local": true +} diff --git a/tests/components/tuya/fixtures/dj_hpc8ddyfv85haxa7.json b/tests/components/tuya/fixtures/dj_hpc8ddyfv85haxa7.json new file mode 100644 index 00000000000000..893aafa37590fd --- /dev/null +++ b/tests/components/tuya/fixtures/dj_hpc8ddyfv85haxa7.json @@ -0,0 +1,156 @@ +{ + "endpoint": "https://apigw.tuyaeu.com", + "terminal_id": "REDACTED", + "mqtt_connected": true, + "disabled_by": null, + "disabled_polling": false, + "id": "63362034840d8eb9029f", + "name": "Garage", + "category": "dj", + "product_id": "hpc8ddyfv85haxa7", + "product_name": "RGB Smart Plug", + "online": true, + "sub": false, + "time_zone": "+01:00", + "active_time": "2020-12-21T14:43:57+00:00", + "create_time": "2020-12-21T14:43:57+00:00", + "update_time": "2020-12-21T14:43:57+00:00", + "function": { + "switch_led": { + "type": "Boolean", + "value": {} + }, + "work_mode": { + "type": "Enum", + "value": { + "range": ["white", "colour", "scene", "music"] + } + }, + "bright_value": { + "type": "Integer", + "value": { + "min": 25, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + } + }, + "temp_value": { + "type": "Integer", + "value": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + } + }, + "switch_1": { + "type": "Boolean", + "value": {} + }, + "colour_data_v2": { + "type": "Json", + "value": { + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + } + } + } + }, + "status_range": { + "switch_1": { + "type": "Boolean", + "value": {} + }, + "switch_led": { + "type": "Boolean", + "value": {} + }, + "work_mode": { + "type": "Enum", + "value": { + "range": ["white", "colour", "scene", "music"] + } + }, + "bright_value": { + "type": "Integer", + "value": { + "min": 25, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + } + }, + "temp_value": { + "type": "Integer", + "value": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + } + }, + "colour_data_v2": { + "type": "Json", + "value": { + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + } + } + } + }, + "status": { + "switch_led": false, + "work_mode": "colour", + "bright_value": 255, + "temp_value": 255, + "colour_data_v2": { + "h": 16384, + "s": 65280, + "v": 65535 + }, + "switch_1": false + }, + "set_up": true, + "support_local": true +} diff --git a/tests/components/tuya/fixtures/dj_iayz2jmtlipjnxj7.json b/tests/components/tuya/fixtures/dj_iayz2jmtlipjnxj7.json new file mode 100644 index 00000000000000..f9062d9146de55 --- /dev/null +++ b/tests/components/tuya/fixtures/dj_iayz2jmtlipjnxj7.json @@ -0,0 +1,529 @@ +{ + "endpoint": "https://apigw.tuyaeu.com", + "terminal_id": "REDACTED", + "mqtt_connected": true, + "disabled_by": null, + "disabled_polling": false, + "id": "bf0fc1d7d4caa71a59us7c", + "name": "LED Porch 2", + "category": "dj", + "product_id": "iayz2jmtlipjnxj7", + "product_name": "LED Strip RGB+W", + "online": false, + "sub": false, + "time_zone": "+02:00", + "active_time": "2021-06-07T10:55:19+00:00", + "create_time": "2021-06-07T10:55:19+00:00", + "update_time": "2021-06-07T10:55:19+00:00", + "function": { + "switch_led": { + "type": "Boolean", + "value": {} + }, + "work_mode": { + "type": "Enum", + "value": { + "range": ["white", "colour", "scene", "music"] + } + }, + "bright_value_v2": { + "type": "Integer", + "value": { + "min": 10, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + } + }, + "temp_value_v2": { + "type": "Integer", + "value": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + } + }, + "colour_data_v2": { + "type": "Json", + "value": { + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + } + } + }, + "scene_data_v2": { + "type": "Json", + "value": { + "scene_num": { + "min": 1, + "scale": 0, + "max": 8, + "step": 1 + }, + "scene_units": { + "unit_change_mode": { + "range": ["static", "jump", "gradient"] + }, + "unit_switch_duration": { + "min": 0, + "scale": 0, + "max": 100, + "step": 1 + }, + "unit_gradient_duration": { + "min": 0, + "scale": 0, + "max": 100, + "step": 1 + }, + "bright": { + "min": 0, + "scale": 0, + "max": 1000, + "step": 1 + }, + "temperature": { + "min": 0, + "scale": 0, + "max": 1000, + "step": 1 + }, + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + } + } + } + }, + "countdown_1": { + "type": "Integer", + "value": { + "unit": "", + "min": 0, + "max": 86400, + "scale": 0, + "step": 1 + } + }, + "music_data": { + "type": "Json", + "value": { + "change_mode": { + "range": ["direct", "gradient"] + }, + "bright": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "temperature": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + } + } + }, + "control_data": { + "type": "Json", + "value": { + "change_mode": { + "range": ["direct", "gradient"] + }, + "bright": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "temperature": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + } + } + } + }, + "status_range": { + "switch_led": { + "type": "Boolean", + "value": {} + }, + "work_mode": { + "type": "Enum", + "value": { + "range": ["white", "colour", "scene", "music"] + } + }, + "bright_value_v2": { + "type": "Integer", + "value": { + "min": 10, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + } + }, + "temp_value_v2": { + "type": "Integer", + "value": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + } + }, + "colour_data_v2": { + "type": "Json", + "value": { + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + } + } + }, + "scene_data_v2": { + "type": "Json", + "value": { + "scene_num": { + "min": 1, + "scale": 0, + "max": 8, + "step": 1 + }, + "scene_units": { + "unit_change_mode": { + "range": ["static", "jump", "gradient"] + }, + "unit_switch_duration": { + "min": 0, + "scale": 0, + "max": 100, + "step": 1 + }, + "unit_gradient_duration": { + "min": 0, + "scale": 0, + "max": 100, + "step": 1 + }, + "bright": { + "min": 0, + "scale": 0, + "max": 1000, + "step": 1 + }, + "temperature": { + "min": 0, + "scale": 0, + "max": 1000, + "step": 1 + }, + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + } + } + } + }, + "countdown_1": { + "type": "Integer", + "value": { + "unit": "", + "min": 0, + "max": 86400, + "scale": 0, + "step": 1 + } + }, + "music_data": { + "type": "Json", + "value": { + "change_mode": { + "range": ["direct", "gradient"] + }, + "bright": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "temperature": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + } + } + }, + "control_data": { + "type": "Json", + "value": { + "change_mode": { + "range": ["direct", "gradient"] + }, + "bright": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "temperature": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + } + } + } + }, + "status": { + "switch_led": true, + "work_mode": "colour", + "bright_value_v2": 1000, + "temp_value_v2": 839, + "colour_data_v2": { + "h": 13, + "s": 992, + "v": 1000 + }, + "scene_data_v2": { + "scene_num": 8, + "scene_units": [ + { + "bright": 0, + "h": 0, + "s": 1000, + "temperature": 0, + "unit_change_mode": "gradient", + "unit_gradient_duration": 70, + "unit_switch_duration": 70, + "v": 1000 + }, + { + "bright": 0, + "h": 120, + "s": 1000, + "temperature": 0, + "unit_change_mode": "gradient", + "unit_gradient_duration": 70, + "unit_switch_duration": 70, + "v": 1000 + }, + { + "bright": 0, + "h": 240, + "s": 1000, + "temperature": 0, + "unit_change_mode": "gradient", + "unit_gradient_duration": 70, + "unit_switch_duration": 70, + "v": 1000 + }, + { + "bright": 0, + "h": 61, + "s": 1000, + "temperature": 0, + "unit_change_mode": "gradient", + "unit_gradient_duration": 70, + "unit_switch_duration": 70, + "v": 1000 + }, + { + "bright": 0, + "h": 174, + "s": 1000, + "temperature": 0, + "unit_change_mode": "gradient", + "unit_gradient_duration": 70, + "unit_switch_duration": 70, + "v": 1000 + }, + { + "bright": 0, + "h": 275, + "s": 1000, + "temperature": 0, + "unit_change_mode": "gradient", + "unit_gradient_duration": 70, + "unit_switch_duration": 70, + "v": 1000 + } + ] + }, + "countdown_1": 0, + "music_data": "", + "control_data": "" + }, + "set_up": true, + "support_local": true +} diff --git a/tests/components/tuya/fixtures/dj_idnfq7xbx8qewyoa.json b/tests/components/tuya/fixtures/dj_idnfq7xbx8qewyoa.json new file mode 100644 index 00000000000000..295157d83701cd --- /dev/null +++ b/tests/components/tuya/fixtures/dj_idnfq7xbx8qewyoa.json @@ -0,0 +1,523 @@ +{ + "endpoint": "https://apigw.tuyaeu.com", + "terminal_id": "REDACTED", + "mqtt_connected": true, + "disabled_by": null, + "disabled_polling": false, + "id": "bf599f5cffe1a5985depyk", + "name": "AB1", + "category": "dj", + "product_id": "idnfq7xbx8qewyoa", + "product_name": "Smart Lamp", + "online": true, + "sub": false, + "time_zone": "+02:00", + "active_time": "2021-08-16T12:51:52+00:00", + "create_time": "2021-08-16T12:51:52+00:00", + "update_time": "2021-08-16T12:51:52+00:00", + "function": { + "switch_led": { + "type": "Boolean", + "value": {} + }, + "work_mode": { + "type": "Enum", + "value": { + "range": ["white", "colour", "scene", "music"] + } + }, + "bright_value_v2": { + "type": "Integer", + "value": { + "min": 10, + "max": 1000, + "scale": 0, + "step": 1 + } + }, + "temp_value_v2": { + "type": "Integer", + "value": { + "min": 0, + "max": 1000, + "scale": 0, + "step": 1 + } + }, + "colour_data_v2": { + "type": "Json", + "value": { + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + } + } + }, + "scene_data_v2": { + "type": "Json", + "value": { + "scene_num": { + "min": 1, + "scale": 0, + "max": 8, + "step": 1 + }, + "scene_units": { + "unit_change_mode": { + "range": ["static", "jump", "gradient"] + }, + "unit_switch_duration": { + "min": 0, + "scale": 0, + "max": 100, + "step": 1 + }, + "unit_gradient_duration": { + "min": 0, + "scale": 0, + "max": 100, + "step": 1 + }, + "bright": { + "min": 0, + "scale": 0, + "max": 1000, + "step": 1 + }, + "temperature": { + "min": 0, + "scale": 0, + "max": 1000, + "step": 1 + }, + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + } + } + } + }, + "countdown_1": { + "type": "Integer", + "value": { + "min": 0, + "max": 86400, + "scale": 0, + "step": 1 + } + }, + "music_data": { + "type": "Json", + "value": { + "change_mode": { + "range": ["direct", "gradient"] + }, + "bright": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "temperature": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + } + } + }, + "control_data": { + "type": "Json", + "value": { + "change_mode": { + "range": ["direct", "gradient"] + }, + "bright": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "temperature": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + } + } + } + }, + "status_range": { + "switch_led": { + "type": "Boolean", + "value": {} + }, + "work_mode": { + "type": "Enum", + "value": { + "range": ["white", "colour", "scene", "music"] + } + }, + "bright_value_v2": { + "type": "Integer", + "value": { + "min": 10, + "max": 1000, + "scale": 0, + "step": 1 + } + }, + "temp_value_v2": { + "type": "Integer", + "value": { + "min": 0, + "max": 1000, + "scale": 0, + "step": 1 + } + }, + "colour_data_v2": { + "type": "Json", + "value": { + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + } + } + }, + "scene_data_v2": { + "type": "Json", + "value": { + "scene_num": { + "min": 1, + "scale": 0, + "max": 8, + "step": 1 + }, + "scene_units": { + "unit_change_mode": { + "range": ["static", "jump", "gradient"] + }, + "unit_switch_duration": { + "min": 0, + "scale": 0, + "max": 100, + "step": 1 + }, + "unit_gradient_duration": { + "min": 0, + "scale": 0, + "max": 100, + "step": 1 + }, + "bright": { + "min": 0, + "scale": 0, + "max": 1000, + "step": 1 + }, + "temperature": { + "min": 0, + "scale": 0, + "max": 1000, + "step": 1 + }, + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + } + } + } + }, + "countdown_1": { + "type": "Integer", + "value": { + "min": 0, + "max": 86400, + "scale": 0, + "step": 1 + } + }, + "music_data": { + "type": "Json", + "value": { + "change_mode": { + "range": ["direct", "gradient"] + }, + "bright": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "temperature": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + } + } + }, + "control_data": { + "type": "Json", + "value": { + "change_mode": { + "range": ["direct", "gradient"] + }, + "bright": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "temperature": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + } + } + } + }, + "status": { + "switch_led": true, + "work_mode": "scene", + "bright_value_v2": 1000, + "temp_value_v2": 1000, + "colour_data_v2": { + "h": 6, + "s": 978, + "v": 1000 + }, + "scene_data_v2": { + "scene_num": 6, + "scene_units": [ + { + "bright": 0, + "h": 0, + "s": 1000, + "temperature": 0, + "unit_change_mode": "jump", + "unit_gradient_duration": 70, + "unit_switch_duration": 70, + "v": 1000 + }, + { + "bright": 0, + "h": 120, + "s": 1000, + "temperature": 0, + "unit_change_mode": "jump", + "unit_gradient_duration": 70, + "unit_switch_duration": 70, + "v": 1000 + }, + { + "bright": 0, + "h": 240, + "s": 1000, + "temperature": 0, + "unit_change_mode": "jump", + "unit_gradient_duration": 70, + "unit_switch_duration": 70, + "v": 1000 + }, + { + "bright": 0, + "h": 61, + "s": 1000, + "temperature": 0, + "unit_change_mode": "jump", + "unit_gradient_duration": 70, + "unit_switch_duration": 70, + "v": 1000 + }, + { + "bright": 0, + "h": 174, + "s": 1000, + "temperature": 0, + "unit_change_mode": "jump", + "unit_gradient_duration": 70, + "unit_switch_duration": 70, + "v": 1000 + }, + { + "bright": 0, + "h": 275, + "s": 1000, + "temperature": 0, + "unit_change_mode": "jump", + "unit_gradient_duration": 70, + "unit_switch_duration": 70, + "v": 1000 + } + ] + }, + "countdown_1": 0, + "music_data": "", + "control_data": "" + }, + "set_up": true, + "support_local": true +} diff --git a/tests/components/tuya/fixtures/dj_ilddqqih3tucdk68.json b/tests/components/tuya/fixtures/dj_ilddqqih3tucdk68.json new file mode 100644 index 00000000000000..1181b650f3eea2 --- /dev/null +++ b/tests/components/tuya/fixtures/dj_ilddqqih3tucdk68.json @@ -0,0 +1,77 @@ +{ + "endpoint": "https://apigw.tuyaeu.com", + "terminal_id": "REDACTED", + "mqtt_connected": true, + "disabled_by": null, + "disabled_polling": false, + "id": "84178216d8f15be52dc4", + "name": "Ieskas", + "category": "dj", + "product_id": "ilddqqih3tucdk68", + "product_name": "LED SMART", + "online": true, + "sub": false, + "time_zone": "+01:00", + "active_time": "2025-05-28T20:07:13+00:00", + "create_time": "2025-05-28T20:07:13+00:00", + "update_time": "2025-05-28T20:07:13+00:00", + "function": { + "switch_led": { + "type": "Boolean", + "value": {} + }, + "bright_value": { + "type": "Integer", + "value": { + "min": 25, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + } + }, + "temp_value": { + "type": "Integer", + "value": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + } + } + }, + "status_range": { + "switch_led": { + "type": "Boolean", + "value": {} + }, + "bright_value": { + "type": "Integer", + "value": { + "min": 25, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + } + }, + "temp_value": { + "type": "Integer", + "value": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + } + } + }, + "status": { + "switch_led": true, + "bright_value": 255, + "temp_value": 158 + }, + "set_up": true, + "support_local": true +} diff --git a/tests/components/tuya/fixtures/dj_j1bgp31cffutizub.json b/tests/components/tuya/fixtures/dj_j1bgp31cffutizub.json new file mode 100644 index 00000000000000..d95179c921f9d6 --- /dev/null +++ b/tests/components/tuya/fixtures/dj_j1bgp31cffutizub.json @@ -0,0 +1,434 @@ +{ + "endpoint": "https://apigw.tuyaeu.com", + "terminal_id": "REDACTED", + "mqtt_connected": true, + "disabled_by": null, + "disabled_polling": false, + "id": "bfe49d7b6cd80536efdldi", + "name": "Ceiling Portal", + "category": "dj", + "product_id": "j1bgp31cffutizub", + "product_name": "LSC Smart Ceiling Light", + "online": true, + "sub": false, + "time_zone": "+01:00", + "active_time": "2022-01-31T12:27:35+00:00", + "create_time": "2022-01-31T12:27:35+00:00", + "update_time": "2022-01-31T12:27:35+00:00", + "function": { + "switch_led": { + "type": "Boolean", + "value": {} + }, + "work_mode": { + "type": "Enum", + "value": { + "range": ["white", "colour", "scene", "music"] + } + }, + "bright_value_v2": { + "type": "Integer", + "value": { + "min": 10, + "max": 1000, + "scale": 0, + "step": 1 + } + }, + "temp_value_v2": { + "type": "Integer", + "value": { + "min": 0, + "max": 1000, + "scale": 0, + "step": 1 + } + }, + "colour_data_v2": { + "type": "Json", + "value": { + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + } + } + }, + "scene_data_v2": { + "type": "Json", + "value": { + "scene_num": { + "min": 1, + "scale": 0, + "max": 8, + "step": 1 + }, + "scene_units": { + "unit_change_mode": { + "range": ["static", "jump", "gradient"] + }, + "unit_switch_duration": { + "min": 0, + "scale": 0, + "max": 100, + "step": 1 + }, + "unit_gradient_duration": { + "min": 0, + "scale": 0, + "max": 100, + "step": 1 + }, + "bright": { + "min": 0, + "scale": 0, + "max": 1000, + "step": 1 + }, + "temperature": { + "min": 0, + "scale": 0, + "max": 1000, + "step": 1 + }, + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + } + } + } + }, + "countdown_1": { + "type": "Integer", + "value": { + "min": 0, + "max": 86400, + "scale": 0, + "step": 1 + } + }, + "control_data": { + "type": "Json", + "value": { + "change_mode": { + "range": ["direct", "gradient"] + }, + "bright": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "temperature": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + } + } + }, + "rhythm_mode": { + "type": "Raw", + "value": { + "maxlen": 255 + } + }, + "sleep_mode": { + "type": "Raw", + "value": { + "maxlen": 255 + } + }, + "wakeup_mode": { + "type": "Raw", + "value": { + "maxlen": 255 + } + }, + "remote_switch": { + "type": "Boolean", + "value": {} + } + }, + "status_range": { + "switch_led": { + "type": "Boolean", + "value": {} + }, + "work_mode": { + "type": "Enum", + "value": { + "range": ["white", "colour", "scene", "music"] + } + }, + "bright_value_v2": { + "type": "Integer", + "value": { + "min": 10, + "max": 1000, + "scale": 0, + "step": 1 + } + }, + "temp_value_v2": { + "type": "Integer", + "value": { + "min": 0, + "max": 1000, + "scale": 0, + "step": 1 + } + }, + "colour_data_v2": { + "type": "Json", + "value": { + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + } + } + }, + "scene_data_v2": { + "type": "Json", + "value": { + "scene_num": { + "min": 1, + "scale": 0, + "max": 8, + "step": 1 + }, + "scene_units": { + "unit_change_mode": { + "range": ["static", "jump", "gradient"] + }, + "unit_switch_duration": { + "min": 0, + "scale": 0, + "max": 100, + "step": 1 + }, + "unit_gradient_duration": { + "min": 0, + "scale": 0, + "max": 100, + "step": 1 + }, + "bright": { + "min": 0, + "scale": 0, + "max": 1000, + "step": 1 + }, + "temperature": { + "min": 0, + "scale": 0, + "max": 1000, + "step": 1 + }, + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + } + } + } + }, + "countdown_1": { + "type": "Integer", + "value": { + "min": 0, + "max": 86400, + "scale": 0, + "step": 1 + } + }, + "control_data": { + "type": "Json", + "value": { + "change_mode": { + "range": ["direct", "gradient"] + }, + "bright": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "temperature": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + } + } + }, + "rhythm_mode": { + "type": "Raw", + "value": { + "maxlen": "255" + } + }, + "sleep_mode": { + "type": "Raw", + "value": { + "maxlen": "255" + } + }, + "wakeup_mode": { + "type": "Raw", + "value": { + "maxlen": "255" + } + }, + "remote_switch": { + "type": "Boolean", + "value": {} + } + }, + "status": { + "switch_led": false, + "work_mode": "white", + "bright_value_v2": 950, + "temp_value_v2": 0, + "colour_data_v2": { + "h": 0, + "s": 1000, + "v": 1000 + }, + "scene_data_v2": { + "scene_num": 1, + "scene_units": [ + { + "bright": 200, + "h": 0, + "s": 0, + "temperature": 0, + "unit_change_mode": "static", + "unit_gradient_duration": 13, + "unit_switch_duration": 14, + "v": 0 + } + ] + }, + "countdown_1": 0, + "control_data": "", + "rhythm_mode": "AAAAAAA=", + "sleep_mode": "AAA=", + "wakeup_mode": "AAA=", + "remote_switch": true + }, + "set_up": true, + "support_local": true +} diff --git a/tests/components/tuya/fixtures/dj_lmnt3uyltk1xffrt.json b/tests/components/tuya/fixtures/dj_lmnt3uyltk1xffrt.json new file mode 100644 index 00000000000000..93a802a7ee33f7 --- /dev/null +++ b/tests/components/tuya/fixtures/dj_lmnt3uyltk1xffrt.json @@ -0,0 +1,77 @@ +{ + "endpoint": "https://apigw.tuyaeu.com", + "terminal_id": "REDACTED", + "mqtt_connected": true, + "disabled_by": null, + "disabled_polling": false, + "id": "07608286600194e94248", + "name": "DirectietKamer", + "category": "dj", + "product_id": "lmnt3uyltk1xffrt", + "product_name": "LED SMART", + "online": true, + "sub": false, + "time_zone": "+01:00", + "active_time": "2025-05-28T20:00:48+00:00", + "create_time": "2025-05-28T20:00:48+00:00", + "update_time": "2025-05-28T20:00:48+00:00", + "function": { + "switch_led": { + "type": "Boolean", + "value": {} + }, + "bright_value": { + "type": "Integer", + "value": { + "min": 25, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + } + }, + "temp_value": { + "type": "Integer", + "value": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + } + } + }, + "status_range": { + "switch_led": { + "type": "Boolean", + "value": {} + }, + "temp_value": { + "type": "Integer", + "value": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + } + }, + "bright_value": { + "type": "Integer", + "value": { + "min": 25, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + } + } + }, + "status": { + "switch_led": false, + "bright_value": 255, + "temp_value": 255 + }, + "set_up": true, + "support_local": true +} diff --git a/tests/components/tuya/fixtures/dj_nbumqpv8vz61enji.json b/tests/components/tuya/fixtures/dj_nbumqpv8vz61enji.json new file mode 100644 index 00000000000000..bc919dd92d2e44 --- /dev/null +++ b/tests/components/tuya/fixtures/dj_nbumqpv8vz61enji.json @@ -0,0 +1,559 @@ +{ + "endpoint": "https://apigw.tuyaeu.com", + "terminal_id": "REDACTED", + "mqtt_connected": true, + "disabled_by": null, + "disabled_polling": false, + "id": "bf77c04cbd6a52a7be16ll", + "name": "b2", + "category": "dj", + "product_id": "nbumqpv8vz61enji", + "product_name": "LSC smart GU10", + "online": false, + "sub": false, + "time_zone": "+01:00", + "active_time": "2024-10-30T21:35:27+00:00", + "create_time": "2024-10-30T21:35:27+00:00", + "update_time": "2024-10-30T21:35:27+00:00", + "function": { + "switch_led": { + "type": "Boolean", + "value": {} + }, + "work_mode": { + "type": "Enum", + "value": { + "range": ["white", "colour", "scene", "music"] + } + }, + "bright_value_v2": { + "type": "Integer", + "value": { + "min": 10, + "max": 1000, + "scale": 0, + "step": 1 + } + }, + "temp_value_v2": { + "type": "Integer", + "value": { + "min": 0, + "max": 1000, + "scale": 0, + "step": 1 + } + }, + "colour_data_v2": { + "type": "Json", + "value": { + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + } + } + }, + "scene_data_v2": { + "type": "Json", + "value": { + "scene_num": { + "min": 1, + "scale": 0, + "max": 8, + "step": 1 + }, + "scene_units": { + "unit_change_mode": { + "range": ["static", "jump", "gradient"] + }, + "unit_switch_duration": { + "min": 0, + "scale": 0, + "max": 100, + "step": 1 + }, + "unit_gradient_duration": { + "min": 0, + "scale": 0, + "max": 100, + "step": 1 + }, + "bright": { + "min": 0, + "scale": 0, + "max": 1000, + "step": 1 + }, + "temperature": { + "min": 0, + "scale": 0, + "max": 1000, + "step": 1 + }, + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + } + } + } + }, + "countdown_1": { + "type": "Integer", + "value": { + "unit": "s", + "min": 0, + "max": 86400, + "scale": 0, + "step": 1 + } + }, + "music_data": { + "type": "Json", + "value": { + "change_mode": { + "range": ["direct", "gradient"] + }, + "bright": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "temperature": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + } + } + }, + "control_data": { + "type": "Json", + "value": { + "change_mode": { + "range": ["direct", "gradient"] + }, + "bright": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "temperature": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + } + } + }, + "rhythm_mode": { + "type": "Raw", + "value": { + "maxlen": 255 + } + }, + "sleep_mode": { + "type": "Raw", + "value": { + "maxlen": 255 + } + }, + "wakeup_mode": { + "type": "Raw", + "value": { + "maxlen": 255 + } + }, + "power_memory": { + "type": "Raw", + "value": {} + }, + "do_not_disturb": { + "type": "Boolean", + "value": {} + }, + "remote_switch": { + "type": "Boolean", + "value": {} + }, + "cycle_timing": { + "type": "Raw", + "value": {} + }, + "random_timing": { + "type": "Raw", + "value": {} + } + }, + "status_range": { + "switch_led": { + "type": "Boolean", + "value": {} + }, + "work_mode": { + "type": "Enum", + "value": { + "range": ["white", "colour", "scene", "music"] + } + }, + "bright_value_v2": { + "type": "Integer", + "value": { + "min": 10, + "max": 1000, + "scale": 0, + "step": 1 + } + }, + "temp_value_v2": { + "type": "Integer", + "value": { + "min": 0, + "max": 1000, + "scale": 0, + "step": 1 + } + }, + "colour_data_v2": { + "type": "Json", + "value": { + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + } + } + }, + "scene_data_v2": { + "type": "Json", + "value": { + "scene_num": { + "min": 1, + "scale": 0, + "max": 8, + "step": 1 + }, + "scene_units": { + "unit_change_mode": { + "range": ["static", "jump", "gradient"] + }, + "unit_switch_duration": { + "min": 0, + "scale": 0, + "max": 100, + "step": 1 + }, + "unit_gradient_duration": { + "min": 0, + "scale": 0, + "max": 100, + "step": 1 + }, + "bright": { + "min": 0, + "scale": 0, + "max": 1000, + "step": 1 + }, + "temperature": { + "min": 0, + "scale": 0, + "max": 1000, + "step": 1 + }, + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + } + } + } + }, + "countdown_1": { + "type": "Integer", + "value": { + "unit": "s", + "min": 0, + "max": 86400, + "scale": 0, + "step": 1 + } + }, + "music_data": { + "type": "Json", + "value": { + "change_mode": { + "range": ["direct", "gradient"] + }, + "bright": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "temperature": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + } + } + }, + "control_data": { + "type": "Json", + "value": { + "change_mode": { + "range": ["direct", "gradient"] + }, + "bright": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "temperature": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + } + } + }, + "rhythm_mode": { + "type": "Raw", + "value": { + "maxlen": "255" + } + }, + "sleep_mode": { + "type": "Raw", + "value": { + "maxlen": "255" + } + }, + "wakeup_mode": { + "type": "Raw", + "value": { + "maxlen": "255" + } + }, + "power_memory": { + "type": "Raw", + "value": {} + }, + "do_not_disturb": { + "type": "Boolean", + "value": {} + }, + "remote_switch": { + "type": "Boolean", + "value": {} + }, + "cycle_timing": { + "type": "Raw", + "value": {} + }, + "random_timing": { + "type": "Raw", + "value": {} + } + }, + "status": { + "switch_led": false, + "work_mode": "colour", + "bright_value_v2": 10, + "temp_value_v2": 150, + "colour_data_v2": { + "h": 119, + "s": 935, + "v": 132 + }, + "scene_data_v2": { + "scene_num": 1, + "scene_units": [ + { + "bright": 200, + "h": 0, + "s": 0, + "temperature": 0, + "unit_change_mode": "static", + "unit_gradient_duration": 13, + "unit_switch_duration": 14, + "v": 0 + } + ] + }, + "countdown_1": 0, + "music_data": "", + "control_data": "", + "rhythm_mode": "AAAAAAA=", + "sleep_mode": "AAA=", + "wakeup_mode": "AAA=", + "power_memory": "AAEAAAPoA+gD6ACW", + "do_not_disturb": true, + "remote_switch": true, + "cycle_timing": "AAAA", + "random_timing": "AAAA" + }, + "set_up": true, + "support_local": true +} diff --git a/tests/components/tuya/fixtures/dj_nlxvjzy1hoeiqsg6.json b/tests/components/tuya/fixtures/dj_nlxvjzy1hoeiqsg6.json new file mode 100644 index 00000000000000..c519f1aa593383 --- /dev/null +++ b/tests/components/tuya/fixtures/dj_nlxvjzy1hoeiqsg6.json @@ -0,0 +1,77 @@ +{ + "endpoint": "https://apigw.tuyaeu.com", + "terminal_id": "REDACTED", + "mqtt_connected": true, + "disabled_by": null, + "disabled_polling": false, + "id": "40350105dc4f229a464e", + "name": "hall 💡 ", + "category": "dj", + "product_id": "nlxvjzy1hoeiqsg6", + "product_name": "LED SMART", + "online": true, + "sub": false, + "time_zone": "+01:00", + "active_time": "2020-06-23T21:37:40+00:00", + "create_time": "2020-06-23T21:37:40+00:00", + "update_time": "2020-06-23T21:37:40+00:00", + "function": { + "switch_led": { + "type": "Boolean", + "value": {} + }, + "bright_value": { + "type": "Integer", + "value": { + "min": 25, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + } + }, + "temp_value": { + "type": "Integer", + "value": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + } + } + }, + "status_range": { + "switch_led": { + "type": "Boolean", + "value": {} + }, + "temp_value": { + "type": "Integer", + "value": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + } + }, + "bright_value": { + "type": "Integer", + "value": { + "min": 25, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + } + } + }, + "status": { + "switch_led": false, + "bright_value": 135, + "temp_value": 0 + }, + "set_up": true, + "support_local": true +} diff --git a/tests/components/tuya/fixtures/dj_oe0cpnjg.json b/tests/components/tuya/fixtures/dj_oe0cpnjg.json new file mode 100644 index 00000000000000..646ce8a93d731b --- /dev/null +++ b/tests/components/tuya/fixtures/dj_oe0cpnjg.json @@ -0,0 +1,226 @@ +{ + "endpoint": "https://apigw.tuyaeu.com", + "terminal_id": "REDACTED", + "mqtt_connected": true, + "disabled_by": null, + "disabled_polling": false, + "id": "bf8d8af3ddfe75b0195r0h", + "name": "Front right Lighting trap", + "category": "dj", + "product_id": "oe0cpnjg", + "product_name": "Smart Lighting", + "online": true, + "sub": true, + "time_zone": "+01:00", + "active_time": "2023-10-03T13:23:20+00:00", + "create_time": "2023-10-03T13:23:20+00:00", + "update_time": "2023-10-03T13:23:20+00:00", + "function": { + "switch_led": { + "type": "Boolean", + "value": {} + }, + "work_mode": { + "type": "Enum", + "value": { + "range": ["white", "colour", "music"] + } + }, + "bright_value_v2": { + "type": "Integer", + "value": { + "min": 10, + "max": 1000, + "scale": 0, + "step": 1 + } + }, + "temp_value_v2": { + "type": "Integer", + "value": { + "min": 0, + "max": 1000, + "scale": 0, + "step": 1 + } + }, + "colour_data_v2": { + "type": "Json", + "value": { + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + } + } + }, + "music_data": { + "type": "Json", + "value": { + "change_mode": { + "range": ["direct", "gradient"] + }, + "bright": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "temperature": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + } + } + } + }, + "status_range": { + "switch_led": { + "type": "Boolean", + "value": {} + }, + "work_mode": { + "type": "Enum", + "value": { + "range": ["white", "colour", "music"] + } + }, + "bright_value_v2": { + "type": "Integer", + "value": { + "min": 10, + "max": 1000, + "scale": 0, + "step": 1 + } + }, + "temp_value_v2": { + "type": "Integer", + "value": { + "min": 0, + "max": 1000, + "scale": 0, + "step": 1 + } + }, + "colour_data_v2": { + "type": "Json", + "value": { + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + } + } + }, + "music_data": { + "type": "Json", + "value": { + "change_mode": { + "range": ["direct", "gradient"] + }, + "bright": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "temperature": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + } + } + } + }, + "status": { + "switch_led": false, + "work_mode": "colour", + "bright_value_v2": 1000, + "temp_value_v2": 985, + "colour_data_v2": "", + "music_data": "" + }, + "set_up": false, + "support_local": true +} diff --git a/tests/components/tuya/fixtures/dj_riwp3k79.json b/tests/components/tuya/fixtures/dj_riwp3k79.json new file mode 100644 index 00000000000000..f1a3579e6607e1 --- /dev/null +++ b/tests/components/tuya/fixtures/dj_riwp3k79.json @@ -0,0 +1,402 @@ +{ + "endpoint": "https://apigw.tuyaeu.com", + "terminal_id": "REDACTED", + "mqtt_connected": true, + "disabled_by": null, + "disabled_polling": false, + "id": "bf46b2b81ca41ce0c1xpsw", + "name": "LED KEUKEN 2", + "category": "dj", + "product_id": "riwp3k79", + "product_name": "atmosphere", + "online": true, + "sub": true, + "time_zone": "+08:00", + "active_time": "2020-12-29T16:16:11+00:00", + "create_time": "2020-12-29T16:16:11+00:00", + "update_time": "2020-12-29T16:16:11+00:00", + "function": { + "switch_led": { + "type": "Boolean", + "value": {} + }, + "work_mode": { + "type": "Enum", + "value": { + "range": ["white", "colour", "scene", "music"] + } + }, + "bright_value_v2": { + "type": "Integer", + "value": { + "min": 10, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + } + }, + "temp_value_v2": { + "type": "Integer", + "value": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + } + }, + "colour_data_v2": { + "type": "Json", + "value": { + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + } + } + }, + "scene_data_v2": { + "type": "Json", + "value": { + "scene_num": { + "min": 1, + "scale": 0, + "max": 8, + "step": 1 + }, + "scene_units": { + "unit_change_mode": { + "range": ["static", "jump", "gradient"] + }, + "unit_switch_duration": { + "min": 0, + "scale": 0, + "max": 100, + "step": 1 + }, + "unit_gradient_duration": { + "min": 0, + "scale": 0, + "max": 100, + "step": 1 + }, + "bright": { + "min": 0, + "scale": 0, + "max": 1000, + "step": 1 + }, + "temperature": { + "min": 0, + "scale": 0, + "max": 1000, + "step": 1 + }, + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + } + } + } + }, + "control_data": { + "type": "Json", + "value": { + "change_mode": { + "range": ["direct", "gradient"] + }, + "bright": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "temperature": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + } + } + }, + "countdown_1": { + "type": "Integer", + "value": { + "unit": "", + "min": 0, + "max": 43200, + "scale": 0, + "step": 1 + } + } + }, + "status_range": { + "switch_led": { + "type": "Boolean", + "value": {} + }, + "work_mode": { + "type": "Enum", + "value": { + "range": ["white", "colour", "scene", "music"] + } + }, + "bright_value_v2": { + "type": "Integer", + "value": { + "min": 10, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + } + }, + "temp_value_v2": { + "type": "Integer", + "value": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + } + }, + "colour_data_v2": { + "type": "Json", + "value": { + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + } + } + }, + "scene_data_v2": { + "type": "Json", + "value": { + "scene_num": { + "min": 1, + "scale": 0, + "max": 8, + "step": 1 + }, + "scene_units": { + "unit_change_mode": { + "range": ["static", "jump", "gradient"] + }, + "unit_switch_duration": { + "min": 0, + "scale": 0, + "max": 100, + "step": 1 + }, + "unit_gradient_duration": { + "min": 0, + "scale": 0, + "max": 100, + "step": 1 + }, + "bright": { + "min": 0, + "scale": 0, + "max": 1000, + "step": 1 + }, + "temperature": { + "min": 0, + "scale": 0, + "max": 1000, + "step": 1 + }, + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + } + } + } + }, + "control_data": { + "type": "Json", + "value": { + "change_mode": { + "range": ["direct", "gradient"] + }, + "bright": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "temperature": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + } + } + }, + "countdown_1": { + "type": "Integer", + "value": { + "unit": "", + "min": 0, + "max": 43200, + "scale": 0, + "step": 1 + } + } + }, + "status": { + "switch_led": true, + "work_mode": "white", + "bright_value_v2": 1000, + "temp_value_v2": 0, + "colour_data_v2": { + "h": 27, + "s": 1000, + "v": 1000 + }, + "scene_data_v2": { + "scene_num": 6, + "scene_units": [ + { + "bright": 0, + "h": 0, + "s": 1000, + "temperature": 0, + "unit_change_mode": "gradient", + "unit_gradient_duration": 100, + "unit_switch_duration": 100, + "v": 1000 + }, + { + "bright": 0, + "h": 240, + "s": 1000, + "temperature": 0, + "unit_change_mode": "gradient", + "unit_gradient_duration": 100, + "unit_switch_duration": 100, + "v": 1000 + } + ] + }, + "countdown_1": 0, + "control_data": "" + }, + "set_up": true, + "support_local": true +} diff --git a/tests/components/tuya/fixtures/dj_tmsloaroqavbucgn.json b/tests/components/tuya/fixtures/dj_tmsloaroqavbucgn.json new file mode 100644 index 00000000000000..20c91ad77393e8 --- /dev/null +++ b/tests/components/tuya/fixtures/dj_tmsloaroqavbucgn.json @@ -0,0 +1,377 @@ +{ + "endpoint": "https://apigw.tuyaeu.com", + "terminal_id": "REDACTED", + "mqtt_connected": true, + "disabled_by": null, + "disabled_polling": false, + "id": "bf252b8ee16b2e78bdoxlp", + "name": "Pokerlamp 1", + "category": "dj", + "product_id": "tmsloaroqavbucgn", + "product_name": "G95-Filament", + "online": false, + "sub": false, + "time_zone": "+02:00", + "active_time": "2021-06-29T16:12:54+00:00", + "create_time": "2021-06-29T16:12:54+00:00", + "update_time": "2021-06-29T16:12:54+00:00", + "function": { + "switch_led": { + "type": "Boolean", + "value": {} + }, + "work_mode": { + "type": "Enum", + "value": { + "range": ["white", "colour", "scene", "music"] + } + }, + "bright_value_v2": { + "type": "Integer", + "value": { + "min": 10, + "max": 1000, + "scale": 0, + "step": 1 + } + }, + "temp_value_v2": { + "type": "Integer", + "value": { + "min": 0, + "max": 1000, + "scale": 0, + "step": 1 + } + }, + "scene_data_v2": { + "type": "Json", + "value": { + "scene_num": { + "min": 1, + "scale": 0, + "max": 8, + "step": 1 + }, + "scene_units": { + "unit_change_mode": { + "range": ["static", "jump", "gradient"] + }, + "unit_switch_duration": { + "min": 0, + "scale": 0, + "max": 100, + "step": 1 + }, + "unit_gradient_duration": { + "min": 0, + "scale": 0, + "max": 100, + "step": 1 + }, + "bright": { + "min": 0, + "scale": 0, + "max": 1000, + "step": 1 + }, + "temperature": { + "min": 0, + "scale": 0, + "max": 1000, + "step": 1 + }, + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + } + } + } + }, + "countdown_1": { + "type": "Integer", + "value": { + "min": 0, + "max": 86400, + "scale": 0, + "step": 1 + } + }, + "control_data": { + "type": "Json", + "value": { + "change_mode": { + "range": ["direct", "gradient"] + }, + "bright": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "temperature": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + } + } + }, + "rhythm_mode": { + "type": "Raw", + "value": { + "maxlen": 255 + } + }, + "sleep_mode": { + "type": "Raw", + "value": { + "maxlen": 255 + } + }, + "wakeup_mode": { + "type": "Raw", + "value": { + "maxlen": 255 + } + }, + "remote_switch": { + "type": "Boolean", + "value": {} + } + }, + "status_range": { + "switch_led": { + "type": "Boolean", + "value": {} + }, + "work_mode": { + "type": "Enum", + "value": { + "range": ["white", "colour", "scene", "music"] + } + }, + "bright_value_v2": { + "type": "Integer", + "value": { + "min": 10, + "max": 1000, + "scale": 0, + "step": 1 + } + }, + "temp_value_v2": { + "type": "Integer", + "value": { + "min": 0, + "max": 1000, + "scale": 0, + "step": 1 + } + }, + "scene_data_v2": { + "type": "Json", + "value": { + "scene_num": { + "min": 1, + "scale": 0, + "max": 8, + "step": 1 + }, + "scene_units": { + "unit_change_mode": { + "range": ["static", "jump", "gradient"] + }, + "unit_switch_duration": { + "min": 0, + "scale": 0, + "max": 100, + "step": 1 + }, + "unit_gradient_duration": { + "min": 0, + "scale": 0, + "max": 100, + "step": 1 + }, + "bright": { + "min": 0, + "scale": 0, + "max": 1000, + "step": 1 + }, + "temperature": { + "min": 0, + "scale": 0, + "max": 1000, + "step": 1 + }, + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + } + } + } + }, + "countdown_1": { + "type": "Integer", + "value": { + "min": 0, + "max": 86400, + "scale": 0, + "step": 1 + } + }, + "control_data": { + "type": "Json", + "value": { + "change_mode": { + "range": ["direct", "gradient"] + }, + "bright": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "temperature": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + } + } + }, + "rhythm_mode": { + "type": "Raw", + "value": { + "maxlen": "255" + } + }, + "sleep_mode": { + "type": "Raw", + "value": { + "maxlen": "255" + } + }, + "wakeup_mode": { + "type": "Raw", + "value": { + "maxlen": "255" + } + }, + "remote_switch": { + "type": "Boolean", + "value": {} + } + }, + "status": { + "switch_led": true, + "work_mode": "white", + "bright_value_v2": 400, + "temp_value_v2": 1000, + "scene_data_v2": { + "scene_num": 1, + "scene_units": [ + { + "bright": 200, + "h": 0, + "s": 0, + "temperature": 0, + "unit_change_mode": "static", + "unit_gradient_duration": 13, + "unit_switch_duration": 14, + "v": 0 + } + ] + }, + "countdown_1": 0, + "control_data": "", + "rhythm_mode": "AAAAAAA=", + "sleep_mode": "AAA=", + "wakeup_mode": "AAA=", + "remote_switch": true + }, + "set_up": true, + "support_local": true +} diff --git a/tests/components/tuya/fixtures/dj_ufq2xwuzd4nb0qdr.json b/tests/components/tuya/fixtures/dj_ufq2xwuzd4nb0qdr.json new file mode 100644 index 00000000000000..7ea5905411dd47 --- /dev/null +++ b/tests/components/tuya/fixtures/dj_ufq2xwuzd4nb0qdr.json @@ -0,0 +1,335 @@ +{ + "endpoint": "https://apigw.tuyaeu.com", + "terminal_id": "REDACTED", + "mqtt_connected": true, + "disabled_by": null, + "disabled_polling": false, + "id": "bf8edbd51a52c01a4bfgqf", + "name": "Sjiethoes", + "category": "dj", + "product_id": "ufq2xwuzd4nb0qdr", + "product_name": "Smart Ceiling Lamp", + "online": false, + "sub": false, + "time_zone": "+02:00", + "active_time": "2025-04-29T09:40:53+00:00", + "create_time": "2025-04-29T09:40:53+00:00", + "update_time": "2025-04-29T09:40:53+00:00", + "function": { + "switch_led": { + "type": "Boolean", + "value": {} + }, + "work_mode": { + "type": "Enum", + "value": { + "range": ["white", "colour", "scene", "music"] + } + }, + "bright_value_v2": { + "type": "Integer", + "value": { + "min": 10, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + } + }, + "temp_value_v2": { + "type": "Integer", + "value": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + } + }, + "scene_data_v2": { + "type": "Json", + "value": { + "scene_num": { + "min": 1, + "scale": 0, + "max": 8, + "step": 1 + }, + "scene_units": { + "unit_change_mode": { + "range": ["static", "jump", "gradient"] + }, + "unit_switch_duration": { + "min": 0, + "scale": 0, + "max": 100, + "step": 1 + }, + "unit_gradient_duration": { + "min": 0, + "scale": 0, + "max": 100, + "step": 1 + }, + "bright": { + "min": 0, + "scale": 0, + "max": 1000, + "step": 1 + }, + "temperature": { + "min": 0, + "scale": 0, + "max": 1000, + "step": 1 + }, + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + } + } + } + }, + "countdown_1": { + "type": "Integer", + "value": { + "unit": "", + "min": 0, + "max": 86400, + "scale": 0, + "step": 1 + } + }, + "control_data": { + "type": "Json", + "value": { + "change_mode": { + "range": ["direct", "gradient"] + }, + "bright": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "temperature": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + } + } + } + }, + "status_range": { + "switch_led": { + "type": "Boolean", + "value": {} + }, + "work_mode": { + "type": "Enum", + "value": { + "range": ["white", "colour", "scene", "music"] + } + }, + "bright_value_v2": { + "type": "Integer", + "value": { + "min": 10, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + } + }, + "temp_value_v2": { + "type": "Integer", + "value": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + } + }, + "scene_data_v2": { + "type": "Json", + "value": { + "scene_num": { + "min": 1, + "scale": 0, + "max": 8, + "step": 1 + }, + "scene_units": { + "unit_change_mode": { + "range": ["static", "jump", "gradient"] + }, + "unit_switch_duration": { + "min": 0, + "scale": 0, + "max": 100, + "step": 1 + }, + "unit_gradient_duration": { + "min": 0, + "scale": 0, + "max": 100, + "step": 1 + }, + "bright": { + "min": 0, + "scale": 0, + "max": 1000, + "step": 1 + }, + "temperature": { + "min": 0, + "scale": 0, + "max": 1000, + "step": 1 + }, + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + } + } + } + }, + "countdown_1": { + "type": "Integer", + "value": { + "unit": "", + "min": 0, + "max": 86400, + "scale": 0, + "step": 1 + } + }, + "control_data": { + "type": "Json", + "value": { + "change_mode": { + "range": ["direct", "gradient"] + }, + "bright": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "temperature": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + } + } + } + }, + "status": { + "switch_led": false, + "work_mode": "white", + "bright_value_v2": 1000, + "temp_value_v2": 1000, + "scene_data_v2": { + "scene_num": 1, + "scene_units": [ + { + "bright": 200, + "h": 46, + "s": 1000, + "temperature": 0, + "unit_change_mode": "static", + "unit_gradient_duration": 13, + "unit_switch_duration": 14, + "v": 0 + } + ] + }, + "countdown_1": 0, + "control_data": "" + }, + "set_up": true, + "support_local": true +} diff --git a/tests/components/tuya/fixtures/dj_vqwcnabamzrc2kab.json b/tests/components/tuya/fixtures/dj_vqwcnabamzrc2kab.json new file mode 100644 index 00000000000000..4d6749ea0b4e5e --- /dev/null +++ b/tests/components/tuya/fixtures/dj_vqwcnabamzrc2kab.json @@ -0,0 +1,532 @@ +{ + "endpoint": "https://apigw.tuyaeu.com", + "terminal_id": "REDACTED", + "mqtt_connected": true, + "disabled_by": null, + "disabled_polling": false, + "id": "bfd56f4718874ee8830xdw", + "name": "Strip 2", + "category": "dj", + "product_id": "vqwcnabamzrc2kab", + "product_name": "Light Strip-RGBCW ", + "online": false, + "sub": false, + "time_zone": "+02:00", + "active_time": "2021-10-22T13:55:55+00:00", + "create_time": "2021-10-22T13:55:55+00:00", + "update_time": "2021-10-22T13:55:55+00:00", + "function": { + "switch_led": { + "type": "Boolean", + "value": {} + }, + "work_mode": { + "type": "Enum", + "value": { + "range": ["white", "colour", "scene", "music"] + } + }, + "bright_value_v2": { + "type": "Integer", + "value": { + "min": 10, + "max": 1000, + "scale": 0, + "step": 1 + } + }, + "temp_value_v2": { + "type": "Integer", + "value": { + "min": 0, + "max": 1000, + "scale": 0, + "step": 1 + } + }, + "colour_data_v2": { + "type": "Json", + "value": { + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + } + } + }, + "scene_data_v2": { + "type": "Json", + "value": { + "scene_num": { + "min": 1, + "scale": 0, + "max": 8, + "step": 1 + }, + "scene_units": { + "unit_change_mode": { + "range": ["static", "jump", "gradient"] + }, + "unit_switch_duration": { + "min": 0, + "scale": 0, + "max": 100, + "step": 1 + }, + "unit_gradient_duration": { + "min": 0, + "scale": 0, + "max": 100, + "step": 1 + }, + "bright": { + "min": 0, + "scale": 0, + "max": 1000, + "step": 1 + }, + "temperature": { + "min": 0, + "scale": 0, + "max": 1000, + "step": 1 + }, + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + } + } + } + }, + "countdown_1": { + "type": "Integer", + "value": { + "min": 0, + "max": 86400, + "scale": 0, + "step": 1 + } + }, + "music_data": { + "type": "Json", + "value": { + "change_mode": { + "range": ["direct", "gradient"] + }, + "bright": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "temperature": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + } + } + }, + "control_data": { + "type": "Json", + "value": { + "change_mode": { + "range": ["direct", "gradient"] + }, + "bright": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "temperature": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + } + } + }, + "remote_switch": { + "type": "Boolean", + "value": {} + } + }, + "status_range": { + "switch_led": { + "type": "Boolean", + "value": {} + }, + "work_mode": { + "type": "Enum", + "value": { + "range": ["white", "colour", "scene", "music"] + } + }, + "bright_value_v2": { + "type": "Integer", + "value": { + "min": 10, + "max": 1000, + "scale": 0, + "step": 1 + } + }, + "temp_value_v2": { + "type": "Integer", + "value": { + "min": 0, + "max": 1000, + "scale": 0, + "step": 1 + } + }, + "colour_data_v2": { + "type": "Json", + "value": { + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + } + } + }, + "scene_data_v2": { + "type": "Json", + "value": { + "scene_num": { + "min": 1, + "scale": 0, + "max": 8, + "step": 1 + }, + "scene_units": { + "unit_change_mode": { + "range": ["static", "jump", "gradient"] + }, + "unit_switch_duration": { + "min": 0, + "scale": 0, + "max": 100, + "step": 1 + }, + "unit_gradient_duration": { + "min": 0, + "scale": 0, + "max": 100, + "step": 1 + }, + "bright": { + "min": 0, + "scale": 0, + "max": 1000, + "step": 1 + }, + "temperature": { + "min": 0, + "scale": 0, + "max": 1000, + "step": 1 + }, + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + } + } + } + }, + "countdown_1": { + "type": "Integer", + "value": { + "min": 0, + "max": 86400, + "scale": 0, + "step": 1 + } + }, + "music_data": { + "type": "Json", + "value": { + "change_mode": { + "range": ["direct", "gradient"] + }, + "bright": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "temperature": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + } + } + }, + "control_data": { + "type": "Json", + "value": { + "change_mode": { + "range": ["direct", "gradient"] + }, + "bright": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "temperature": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + } + } + }, + "remote_switch": { + "type": "Boolean", + "value": {} + } + }, + "status": { + "switch_led": true, + "work_mode": "colour", + "bright_value_v2": 1000, + "temp_value_v2": 1000, + "colour_data_v2": { + "h": 218, + "s": 1000, + "v": 1000 + }, + "scene_data_v2": { + "scene_num": 8, + "scene_units": [ + { + "bright": 0, + "h": 0, + "s": 1000, + "temperature": 0, + "unit_change_mode": "gradient", + "unit_gradient_duration": 70, + "unit_switch_duration": 70, + "v": 1000 + }, + { + "bright": 0, + "h": 120, + "s": 1000, + "temperature": 0, + "unit_change_mode": "gradient", + "unit_gradient_duration": 70, + "unit_switch_duration": 70, + "v": 1000 + }, + { + "bright": 0, + "h": 240, + "s": 1000, + "temperature": 0, + "unit_change_mode": "gradient", + "unit_gradient_duration": 70, + "unit_switch_duration": 70, + "v": 1000 + }, + { + "bright": 0, + "h": 61, + "s": 1000, + "temperature": 0, + "unit_change_mode": "gradient", + "unit_gradient_duration": 70, + "unit_switch_duration": 70, + "v": 1000 + }, + { + "bright": 0, + "h": 174, + "s": 1000, + "temperature": 0, + "unit_change_mode": "gradient", + "unit_gradient_duration": 70, + "unit_switch_duration": 70, + "v": 1000 + }, + { + "bright": 0, + "h": 275, + "s": 1000, + "temperature": 0, + "unit_change_mode": "gradient", + "unit_gradient_duration": 70, + "unit_switch_duration": 70, + "v": 1000 + } + ] + }, + "countdown_1": 0, + "music_data": "", + "control_data": "", + "remote_switch": true + }, + "set_up": true, + "support_local": true +} diff --git a/tests/components/tuya/fixtures/dj_xokdfs6kh5ednakk.json b/tests/components/tuya/fixtures/dj_xokdfs6kh5ednakk.json new file mode 100644 index 00000000000000..cce66d90b0c467 --- /dev/null +++ b/tests/components/tuya/fixtures/dj_xokdfs6kh5ednakk.json @@ -0,0 +1,377 @@ +{ + "endpoint": "https://apigw.tuyaeu.com", + "terminal_id": "REDACTED", + "mqtt_connected": true, + "disabled_by": null, + "disabled_polling": false, + "id": "bfc1ef4da4accc0731oggw", + "name": "ERKER 1-Gold ", + "category": "dj", + "product_id": "xokdfs6kh5ednakk", + "product_name": "LSC-G125-Gold ", + "online": true, + "sub": false, + "time_zone": "+01:00", + "active_time": "2022-01-30T22:02:31+00:00", + "create_time": "2022-01-30T22:02:31+00:00", + "update_time": "2022-01-30T22:02:31+00:00", + "function": { + "switch_led": { + "type": "Boolean", + "value": {} + }, + "work_mode": { + "type": "Enum", + "value": { + "range": ["white", "colour", "scene", "music"] + } + }, + "bright_value_v2": { + "type": "Integer", + "value": { + "min": 10, + "max": 1000, + "scale": 0, + "step": 1 + } + }, + "temp_value_v2": { + "type": "Integer", + "value": { + "min": 0, + "max": 1000, + "scale": 0, + "step": 1 + } + }, + "scene_data_v2": { + "type": "Json", + "value": { + "scene_num": { + "min": 1, + "scale": 0, + "max": 8, + "step": 1 + }, + "scene_units": { + "unit_change_mode": { + "range": ["static", "jump", "gradient"] + }, + "unit_switch_duration": { + "min": 0, + "scale": 0, + "max": 100, + "step": 1 + }, + "unit_gradient_duration": { + "min": 0, + "scale": 0, + "max": 100, + "step": 1 + }, + "bright": { + "min": 0, + "scale": 0, + "max": 1000, + "step": 1 + }, + "temperature": { + "min": 0, + "scale": 0, + "max": 1000, + "step": 1 + }, + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + } + } + } + }, + "countdown_1": { + "type": "Integer", + "value": { + "min": 0, + "max": 86400, + "scale": 0, + "step": 1 + } + }, + "control_data": { + "type": "Json", + "value": { + "change_mode": { + "range": ["direct", "gradient"] + }, + "bright": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "temperature": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + } + } + }, + "rhythm_mode": { + "type": "Raw", + "value": { + "maxlen": 255 + } + }, + "sleep_mode": { + "type": "Raw", + "value": { + "maxlen": 255 + } + }, + "wakeup_mode": { + "type": "Raw", + "value": { + "maxlen": 255 + } + }, + "remote_switch": { + "type": "Boolean", + "value": {} + } + }, + "status_range": { + "switch_led": { + "type": "Boolean", + "value": {} + }, + "work_mode": { + "type": "Enum", + "value": { + "range": ["white", "colour", "scene", "music"] + } + }, + "bright_value_v2": { + "type": "Integer", + "value": { + "min": 10, + "max": 1000, + "scale": 0, + "step": 1 + } + }, + "temp_value_v2": { + "type": "Integer", + "value": { + "min": 0, + "max": 1000, + "scale": 0, + "step": 1 + } + }, + "scene_data_v2": { + "type": "Json", + "value": { + "scene_num": { + "min": 1, + "scale": 0, + "max": 8, + "step": 1 + }, + "scene_units": { + "unit_change_mode": { + "range": ["static", "jump", "gradient"] + }, + "unit_switch_duration": { + "min": 0, + "scale": 0, + "max": 100, + "step": 1 + }, + "unit_gradient_duration": { + "min": 0, + "scale": 0, + "max": 100, + "step": 1 + }, + "bright": { + "min": 0, + "scale": 0, + "max": 1000, + "step": 1 + }, + "temperature": { + "min": 0, + "scale": 0, + "max": 1000, + "step": 1 + }, + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + } + } + } + }, + "countdown_1": { + "type": "Integer", + "value": { + "min": 0, + "max": 86400, + "scale": 0, + "step": 1 + } + }, + "control_data": { + "type": "Json", + "value": { + "change_mode": { + "range": ["direct", "gradient"] + }, + "bright": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "temperature": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + } + } + }, + "rhythm_mode": { + "type": "Raw", + "value": { + "maxlen": "255" + } + }, + "sleep_mode": { + "type": "Raw", + "value": { + "maxlen": "255" + } + }, + "wakeup_mode": { + "type": "Raw", + "value": { + "maxlen": "255" + } + }, + "remote_switch": { + "type": "Boolean", + "value": {} + } + }, + "status": { + "switch_led": true, + "work_mode": "white", + "bright_value_v2": 1000, + "temp_value_v2": 0, + "scene_data_v2": { + "scene_num": 1, + "scene_units": [ + { + "bright": 200, + "h": 0, + "s": 0, + "temperature": 0, + "unit_change_mode": "static", + "unit_gradient_duration": 13, + "unit_switch_duration": 14, + "v": 0 + } + ] + }, + "countdown_1": 0, + "control_data": "", + "rhythm_mode": "AAAAAAA=", + "sleep_mode": "AAA=", + "wakeup_mode": "AAA=", + "remote_switch": true + }, + "set_up": true, + "support_local": true +} diff --git a/tests/components/tuya/fixtures/dj_zakhnlpdiu0ycdxn.json b/tests/components/tuya/fixtures/dj_zakhnlpdiu0ycdxn.json new file mode 100644 index 00000000000000..d1c236631440a5 --- /dev/null +++ b/tests/components/tuya/fixtures/dj_zakhnlpdiu0ycdxn.json @@ -0,0 +1,77 @@ +{ + "endpoint": "https://apigw.tuyaeu.com", + "terminal_id": "REDACTED", + "mqtt_connected": true, + "disabled_by": null, + "disabled_polling": false, + "id": "03010850c44f33966362", + "name": "Stoel", + "category": "dj", + "product_id": "zakhnlpdiu0ycdxn", + "product_name": "LED SMART", + "online": false, + "sub": false, + "time_zone": "+01:00", + "active_time": "2023-08-10T18:55:12+00:00", + "create_time": "2023-08-10T18:55:12+00:00", + "update_time": "2023-08-10T18:55:12+00:00", + "function": { + "switch_led": { + "type": "Boolean", + "value": {} + }, + "bright_value": { + "type": "Integer", + "value": { + "min": 25, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + } + }, + "temp_value": { + "type": "Integer", + "value": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + } + } + }, + "status_range": { + "switch_led": { + "type": "Boolean", + "value": {} + }, + "temp_value": { + "type": "Integer", + "value": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + } + }, + "bright_value": { + "type": "Integer", + "value": { + "min": 25, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + } + } + }, + "status": { + "switch_led": false, + "bright_value": 71, + "temp_value": 0 + }, + "set_up": true, + "support_local": true +} diff --git a/tests/components/tuya/fixtures/dj_zav1pa32pyxray78.json b/tests/components/tuya/fixtures/dj_zav1pa32pyxray78.json new file mode 100644 index 00000000000000..624f7fb4347f79 --- /dev/null +++ b/tests/components/tuya/fixtures/dj_zav1pa32pyxray78.json @@ -0,0 +1,322 @@ +{ + "endpoint": "https://apigw.tuyaeu.com", + "terminal_id": "REDACTED", + "mqtt_connected": true, + "disabled_by": null, + "disabled_polling": false, + "id": "500425642462ab50909b", + "name": "Gengske 💡 ", + "category": "dj", + "product_id": "zav1pa32pyxray78", + "product_name": "Ceiling Light RGBTW", + "online": true, + "sub": false, + "time_zone": "+01:00", + "active_time": "2025-05-28T20:00:48+00:00", + "create_time": "2025-05-28T20:00:48+00:00", + "update_time": "2025-05-28T20:00:48+00:00", + "function": { + "switch_led": { + "type": "Boolean", + "value": {} + }, + "work_mode": { + "type": "Enum", + "value": { + "range": ["white", "colour", "scene", "music"] + } + }, + "bright_value_v2": { + "type": "Integer", + "value": { + "min": 10, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + } + }, + "temp_value_v2": { + "type": "Integer", + "value": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + } + }, + "colour_data_v2": { + "type": "Json", + "value": { + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + } + } + }, + "scene_data_v2": { + "type": "Json", + "value": { + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + } + } + }, + "countdown_1": { + "type": "Integer", + "value": { + "unit": "", + "min": 0, + "max": 86400, + "scale": 0, + "step": 1 + } + }, + "control_data": { + "type": "Json", + "value": { + "change_mode": { + "range": ["direct", "gradient"] + }, + "bright": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "temperature": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + } + } + } + }, + "status_range": { + "switch_led": { + "type": "Boolean", + "value": {} + }, + "work_mode": { + "type": "Enum", + "value": { + "range": ["white", "colour", "scene", "music"] + } + }, + "bright_value_v2": { + "type": "Integer", + "value": { + "min": 10, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + } + }, + "temp_value_v2": { + "type": "Integer", + "value": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + } + }, + "colour_data_v2": { + "type": "Json", + "value": { + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + } + } + }, + "scene_data_v2": { + "type": "Json", + "value": { + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + } + } + }, + "countdown_1": { + "type": "Integer", + "value": { + "unit": "", + "min": 0, + "max": 86400, + "scale": 0, + "step": 1 + } + }, + "control_data": { + "type": "Json", + "value": { + "change_mode": { + "range": ["direct", "gradient"] + }, + "bright": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "temperature": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + } + } + } + }, + "status": { + "switch_led": false, + "work_mode": "colour", + "bright_value_v2": 1000, + "temp_value_v2": 380, + "colour_data_v2": { + "h": 0, + "s": 1000, + "v": 102 + }, + "scene_data_v2": { + "scene_num": 1, + "scene_units": [ + { + "bright": 200, + "h": 0, + "s": 0, + "temperature": 0, + "unit_change_mode": "static", + "unit_gradient_duration": 13, + "unit_switch_duration": 14, + "v": 0 + } + ] + }, + "countdown_1": 0, + "control_data": "" + }, + "set_up": true, + "support_local": true +} diff --git a/tests/components/tuya/fixtures/dj_zputiamzanuk6yky.json b/tests/components/tuya/fixtures/dj_zputiamzanuk6yky.json new file mode 100644 index 00000000000000..cede2b656821e0 --- /dev/null +++ b/tests/components/tuya/fixtures/dj_zputiamzanuk6yky.json @@ -0,0 +1,413 @@ +{ + "endpoint": "https://apigw.tuyaeu.com", + "terminal_id": "REDACTED", + "mqtt_connected": true, + "disabled_by": null, + "disabled_polling": false, + "id": "bf74164049de868395pbci", + "name": "Floodlight", + "category": "dj", + "product_id": "zputiamzanuk6yky", + "product_name": "LSC Floodlight", + "online": true, + "sub": false, + "time_zone": "+02:00", + "active_time": "2025-06-09T08:14:06+00:00", + "create_time": "2025-06-09T08:14:06+00:00", + "update_time": "2025-06-09T08:14:06+00:00", + "function": { + "switch_led": { + "type": "Boolean", + "value": {} + }, + "work_mode": { + "type": "Enum", + "value": { + "range": ["white", "colour", "scene", "music"] + } + }, + "bright_value_v2": { + "type": "Integer", + "value": { + "min": 10, + "max": 1000, + "scale": 0, + "step": 1 + } + }, + "colour_data_v2": { + "type": "Json", + "value": { + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + } + } + }, + "scene_data_v2": { + "type": "Json", + "value": { + "scene_num": { + "min": 1, + "scale": 0, + "max": 8, + "step": 1 + }, + "scene_units": { + "unit_change_mode": { + "range": ["static", "jump", "gradient"] + }, + "unit_switch_duration": { + "min": 0, + "scale": 0, + "max": 100, + "step": 1 + }, + "unit_gradient_duration": { + "min": 0, + "scale": 0, + "max": 100, + "step": 1 + }, + "bright": { + "min": 0, + "scale": 0, + "max": 1000, + "step": 1 + }, + "temperature": { + "min": 0, + "scale": 0, + "max": 1000, + "step": 1 + }, + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + } + } + } + }, + "countdown_1": { + "type": "Integer", + "value": { + "unit": "s", + "min": 0, + "max": 86400, + "scale": 0, + "step": 1 + } + }, + "control_data": { + "type": "Json", + "value": { + "change_mode": { + "range": ["direct", "gradient"] + }, + "bright": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "temperature": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + } + } + }, + "sleep_mode": { + "type": "Raw", + "value": { + "maxlen": 255 + } + }, + "wakeup_mode": { + "type": "Raw", + "value": { + "maxlen": 255 + } + }, + "power_memory": { + "type": "Raw", + "value": {} + }, + "do_not_disturb": { + "type": "Boolean", + "value": {} + } + }, + "status_range": { + "switch_led": { + "type": "Boolean", + "value": {} + }, + "work_mode": { + "type": "Enum", + "value": { + "range": ["white", "colour", "scene", "music"] + } + }, + "bright_value_v2": { + "type": "Integer", + "value": { + "min": 10, + "max": 1000, + "scale": 0, + "step": 1 + } + }, + "colour_data_v2": { + "type": "Json", + "value": { + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + } + } + }, + "scene_data_v2": { + "type": "Json", + "value": { + "scene_num": { + "min": 1, + "scale": 0, + "max": 8, + "step": 1 + }, + "scene_units": { + "unit_change_mode": { + "range": ["static", "jump", "gradient"] + }, + "unit_switch_duration": { + "min": 0, + "scale": 0, + "max": 100, + "step": 1 + }, + "unit_gradient_duration": { + "min": 0, + "scale": 0, + "max": 100, + "step": 1 + }, + "bright": { + "min": 0, + "scale": 0, + "max": 1000, + "step": 1 + }, + "temperature": { + "min": 0, + "scale": 0, + "max": 1000, + "step": 1 + }, + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + } + } + } + }, + "countdown_1": { + "type": "Integer", + "value": { + "unit": "s", + "min": 0, + "max": 86400, + "scale": 0, + "step": 1 + } + }, + "control_data": { + "type": "Json", + "value": { + "change_mode": { + "range": ["direct", "gradient"] + }, + "bright": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "temperature": { + "min": 0, + "scale": 0, + "unit": "", + "max": 1000, + "step": 1 + }, + "h": { + "min": 0, + "scale": 0, + "unit": "", + "max": 360, + "step": 1 + }, + "s": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + }, + "v": { + "min": 0, + "scale": 0, + "unit": "", + "max": 255, + "step": 1 + } + } + }, + "sleep_mode": { + "type": "Raw", + "value": { + "maxlen": "255" + } + }, + "wakeup_mode": { + "type": "Raw", + "value": { + "maxlen": "255" + } + }, + "power_memory": { + "type": "Raw", + "value": {} + }, + "do_not_disturb": { + "type": "Boolean", + "value": {} + } + }, + "status": { + "switch_led": false, + "work_mode": "colour", + "bright_value_v2": 1000, + "colour_data_v2": { + "h": 295, + "s": 920, + "v": 1000 + }, + "scene_data_v2": { + "scene_num": 1, + "scene_units": [ + { + "bright": 200, + "h": 0, + "s": 0, + "temperature": 1000, + "unit_change_mode": "static", + "unit_gradient_duration": 13, + "unit_switch_duration": 14, + "v": 0 + } + ] + }, + "countdown_1": 0, + "control_data": "", + "sleep_mode": "AAA=", + "wakeup_mode": "AAA=", + "power_memory": "AAEAGwG/A+gD6APo", + "do_not_disturb": false + }, + "set_up": true, + "support_local": true +} diff --git a/tests/components/tuya/snapshots/test_light.ambr b/tests/components/tuya/snapshots/test_light.ambr index 26648b0f2cb81e..b5dca58f8e7c3d 100644 --- a/tests/components/tuya/snapshots/test_light.ambr +++ b/tests/components/tuya/snapshots/test_light.ambr @@ -56,6 +56,1141 @@ 'state': 'on', }) # --- +# name: test_platform_setup_and_discovery[dc_l3bpgg8ibsagon4x][light.lsc_party_string_light_rgbic_cct-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'max_color_temp_kelvin': 6500, + 'max_mireds': 500, + 'min_color_temp_kelvin': 2000, + 'min_mireds': 153, + 'supported_color_modes': list([ + , + , + ]), + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'light', + 'entity_category': None, + 'entity_id': 'light.lsc_party_string_light_rgbic_cct', + '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': 0, + 'translation_key': None, + 'unique_id': 'tuya.bfd9f45c6b882c9f46dxfcswitch_led', + 'unit_of_measurement': None, + }) +# --- +# name: test_platform_setup_and_discovery[dc_l3bpgg8ibsagon4x][light.lsc_party_string_light_rgbic_cct-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'LSC Party String Light RGBIC+CCT ', + 'max_color_temp_kelvin': 6500, + 'max_mireds': 500, + 'min_color_temp_kelvin': 2000, + 'min_mireds': 153, + 'supported_color_modes': list([ + , + , + ]), + 'supported_features': , + }), + 'context': , + 'entity_id': 'light.lsc_party_string_light_rgbic_cct', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unavailable', + }) +# --- +# name: test_platform_setup_and_discovery[dj_8szt7whdvwpmxglk][light.porch_light_e-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': None, + 'entity_id': 'light.porch_light_e', + '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': 0, + 'translation_key': None, + 'unique_id': 'tuya.eb10549aadfc74b7c8q2tiswitch_led', + 'unit_of_measurement': None, + }) +# --- +# name: test_platform_setup_and_discovery[dj_8szt7whdvwpmxglk][light.porch_light_e-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'brightness': None, + 'color_mode': None, + 'friendly_name': 'Porch light E', + 'hs_color': None, + 'rgb_color': None, + 'supported_color_modes': list([ + , + , + ]), + 'supported_features': , + 'xy_color': None, + }), + 'context': , + 'entity_id': 'light.porch_light_e', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'off', + }) +# --- +# name: test_platform_setup_and_discovery[dj_8y0aquaa8v6tho8w][light.dressoir_spot-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'max_color_temp_kelvin': 6500, + 'max_mireds': 500, + 'min_color_temp_kelvin': 2000, + 'min_mireds': 153, + 'supported_color_modes': list([ + , + ]), + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'light', + 'entity_category': None, + 'entity_id': 'light.dressoir_spot', + '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': 0, + 'translation_key': None, + 'unique_id': 'tuya.bf71858c3d27943679dsx9switch_led', + 'unit_of_measurement': None, + }) +# --- +# name: test_platform_setup_and_discovery[dj_8y0aquaa8v6tho8w][light.dressoir_spot-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'brightness': None, + 'color_mode': None, + 'color_temp': None, + 'color_temp_kelvin': None, + 'friendly_name': 'dressoir spot', + 'hs_color': None, + 'max_color_temp_kelvin': 6500, + 'max_mireds': 500, + 'min_color_temp_kelvin': 2000, + 'min_mireds': 153, + 'rgb_color': None, + 'supported_color_modes': list([ + , + ]), + 'supported_features': , + 'xy_color': None, + }), + 'context': , + 'entity_id': 'light.dressoir_spot', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'off', + }) +# --- +# name: test_platform_setup_and_discovery[dj_baf9tt9lb8t5uc7z][light.pokerlamp_2-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'max_color_temp_kelvin': 6500, + 'max_mireds': 500, + 'min_color_temp_kelvin': 2000, + 'min_mireds': 153, + 'supported_color_modes': list([ + , + ]), + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'light', + 'entity_category': None, + 'entity_id': 'light.pokerlamp_2', + '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': 0, + 'translation_key': None, + 'unique_id': 'tuya.40611462e09806c73134switch_led', + 'unit_of_measurement': None, + }) +# --- +# name: test_platform_setup_and_discovery[dj_baf9tt9lb8t5uc7z][light.pokerlamp_2-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Pokerlamp 2', + 'max_color_temp_kelvin': 6500, + 'max_mireds': 500, + 'min_color_temp_kelvin': 2000, + 'min_mireds': 153, + 'supported_color_modes': list([ + , + ]), + 'supported_features': , + }), + 'context': , + 'entity_id': 'light.pokerlamp_2', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unavailable', + }) +# --- +# name: test_platform_setup_and_discovery[dj_d4g0fbsoaal841o6][light.wc_d1-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'max_color_temp_kelvin': 6500, + 'max_mireds': 500, + 'min_color_temp_kelvin': 2000, + 'min_mireds': 153, + 'supported_color_modes': list([ + , + ]), + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'light', + 'entity_category': None, + 'entity_id': 'light.wc_d1', + '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': 0, + 'translation_key': None, + 'unique_id': 'tuya.bf671413db4cee1f9bqdcxswitch_led', + 'unit_of_measurement': None, + }) +# --- +# name: test_platform_setup_and_discovery[dj_d4g0fbsoaal841o6][light.wc_d1-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'WC D1', + 'max_color_temp_kelvin': 6500, + 'max_mireds': 500, + 'min_color_temp_kelvin': 2000, + 'min_mireds': 153, + 'supported_color_modes': list([ + , + ]), + 'supported_features': , + }), + 'context': , + 'entity_id': 'light.wc_d1', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unavailable', + }) +# --- +# name: test_platform_setup_and_discovery[dj_djnozmdyqyriow8z][light.fakkel_8-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'max_color_temp_kelvin': 6500, + 'max_mireds': 500, + 'min_color_temp_kelvin': 2000, + 'min_mireds': 153, + 'supported_color_modes': list([ + , + , + ]), + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'light', + 'entity_category': None, + 'entity_id': 'light.fakkel_8', + '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': 0, + 'translation_key': None, + 'unique_id': 'tuya.bf8885f3d18a73e395bfacswitch_led', + 'unit_of_measurement': None, + }) +# --- +# name: test_platform_setup_and_discovery[dj_djnozmdyqyriow8z][light.fakkel_8-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'brightness': 70, + 'color_mode': , + 'color_temp': 500, + 'color_temp_kelvin': 2000, + 'friendly_name': 'Fakkel 8', + 'hs_color': tuple( + 30.601, + 94.547, + ), + 'max_color_temp_kelvin': 6500, + 'max_mireds': 500, + 'min_color_temp_kelvin': 2000, + 'min_mireds': 153, + 'rgb_color': tuple( + 255, + 137, + 14, + ), + 'supported_color_modes': list([ + , + , + ]), + 'supported_features': , + 'xy_color': tuple( + 0.598, + 0.383, + ), + }), + 'context': , + 'entity_id': 'light.fakkel_8', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'on', + }) +# --- +# name: test_platform_setup_and_discovery[dj_ekwolitfjhxn55js][light.ab6-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'max_color_temp_kelvin': 6500, + 'max_mireds': 500, + 'min_color_temp_kelvin': 2000, + 'min_mireds': 153, + 'supported_color_modes': list([ + , + , + ]), + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'light', + 'entity_category': None, + 'entity_id': 'light.ab6', + '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': 0, + 'translation_key': None, + 'unique_id': 'tuya.bfb99bba00c9c90ba8gzglswitch_led', + 'unit_of_measurement': None, + }) +# --- +# name: test_platform_setup_and_discovery[dj_ekwolitfjhxn55js][light.ab6-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'ab6', + 'max_color_temp_kelvin': 6500, + 'max_mireds': 500, + 'min_color_temp_kelvin': 2000, + 'min_mireds': 153, + 'supported_color_modes': list([ + , + , + ]), + 'supported_features': , + }), + 'context': , + 'entity_id': 'light.ab6', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unavailable', + }) +# --- +# name: test_platform_setup_and_discovery[dj_fuupmcr2mb1odkja][light.slaapkamer-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'max_color_temp_kelvin': 6500, + 'max_mireds': 500, + 'min_color_temp_kelvin': 2000, + 'min_mireds': 153, + 'supported_color_modes': list([ + , + ]), + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'light', + 'entity_category': None, + 'entity_id': 'light.slaapkamer', + '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': 0, + 'translation_key': None, + 'unique_id': 'tuya.bf0914a82b06ecf151xsf5switch_led', + 'unit_of_measurement': None, + }) +# --- +# name: test_platform_setup_and_discovery[dj_fuupmcr2mb1odkja][light.slaapkamer-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'brightness': None, + 'color_mode': None, + 'color_temp': None, + 'color_temp_kelvin': None, + 'friendly_name': 'Slaapkamer', + 'hs_color': None, + 'max_color_temp_kelvin': 6500, + 'max_mireds': 500, + 'min_color_temp_kelvin': 2000, + 'min_mireds': 153, + 'rgb_color': None, + 'supported_color_modes': list([ + , + ]), + 'supported_features': , + 'xy_color': None, + }), + 'context': , + 'entity_id': 'light.slaapkamer', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'off', + }) +# --- +# name: test_platform_setup_and_discovery[dj_hp6orhaqm6as3jnv][light.master_bedroom_tv_lights-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'max_color_temp_kelvin': 6500, + 'max_mireds': 500, + 'min_color_temp_kelvin': 2000, + 'min_mireds': 153, + 'supported_color_modes': list([ + , + , + ]), + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'light', + 'entity_category': None, + 'entity_id': 'light.master_bedroom_tv_lights', + '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': 0, + 'translation_key': None, + 'unique_id': 'tuya.00450321483fda81c529switch_led', + 'unit_of_measurement': None, + }) +# --- +# name: test_platform_setup_and_discovery[dj_hp6orhaqm6as3jnv][light.master_bedroom_tv_lights-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'brightness': 51, + 'color_mode': , + 'color_temp': None, + 'color_temp_kelvin': None, + 'friendly_name': 'Master bedroom TV lights', + 'hs_color': tuple( + 26.072, + 100.0, + ), + 'max_color_temp_kelvin': 6500, + 'max_mireds': 500, + 'min_color_temp_kelvin': 2000, + 'min_mireds': 153, + 'rgb_color': tuple( + 255, + 111, + 0, + ), + 'supported_color_modes': list([ + , + , + ]), + 'supported_features': , + 'xy_color': tuple( + 0.632, + 0.358, + ), + }), + 'context': , + 'entity_id': 'light.master_bedroom_tv_lights', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'on', + }) +# --- +# name: test_platform_setup_and_discovery[dj_hpc8ddyfv85haxa7][light.garage-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'max_color_temp_kelvin': 6500, + 'max_mireds': 500, + 'min_color_temp_kelvin': 2000, + 'min_mireds': 153, + 'supported_color_modes': list([ + , + , + ]), + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'light', + 'entity_category': None, + 'entity_id': 'light.garage', + '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': 0, + 'translation_key': None, + 'unique_id': 'tuya.63362034840d8eb9029fswitch_led', + 'unit_of_measurement': None, + }) +# --- +# name: test_platform_setup_and_discovery[dj_hpc8ddyfv85haxa7][light.garage-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'brightness': None, + 'color_mode': None, + 'color_temp': None, + 'color_temp_kelvin': None, + 'friendly_name': 'Garage', + 'hs_color': None, + 'max_color_temp_kelvin': 6500, + 'max_mireds': 500, + 'min_color_temp_kelvin': 2000, + 'min_mireds': 153, + 'rgb_color': None, + 'supported_color_modes': list([ + , + , + ]), + 'supported_features': , + 'xy_color': None, + }), + 'context': , + 'entity_id': 'light.garage', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'off', + }) +# --- +# name: test_platform_setup_and_discovery[dj_hpc8ddyfv85haxa7][light.garage_light_1-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': None, + 'entity_id': 'light.garage_light_1', + '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': 'Light 1', + 'platform': 'tuya', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'indexed_light', + 'unique_id': 'tuya.63362034840d8eb9029fswitch_1', + 'unit_of_measurement': None, + }) +# --- +# name: test_platform_setup_and_discovery[dj_hpc8ddyfv85haxa7][light.garage_light_1-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'color_mode': None, + 'friendly_name': 'Garage Light 1', + 'supported_color_modes': list([ + , + ]), + 'supported_features': , + }), + 'context': , + 'entity_id': 'light.garage_light_1', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'off', + }) +# --- +# name: test_platform_setup_and_discovery[dj_iayz2jmtlipjnxj7][light.led_porch_2-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'max_color_temp_kelvin': 6500, + 'max_mireds': 500, + 'min_color_temp_kelvin': 2000, + 'min_mireds': 153, + 'supported_color_modes': list([ + , + , + ]), + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'light', + 'entity_category': None, + 'entity_id': 'light.led_porch_2', + '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': 0, + 'translation_key': None, + 'unique_id': 'tuya.bf0fc1d7d4caa71a59us7cswitch_led', + 'unit_of_measurement': None, + }) +# --- +# name: test_platform_setup_and_discovery[dj_iayz2jmtlipjnxj7][light.led_porch_2-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'LED Porch 2', + 'max_color_temp_kelvin': 6500, + 'max_mireds': 500, + 'min_color_temp_kelvin': 2000, + 'min_mireds': 153, + 'supported_color_modes': list([ + , + , + ]), + 'supported_features': , + }), + 'context': , + 'entity_id': 'light.led_porch_2', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unavailable', + }) +# --- +# name: test_platform_setup_and_discovery[dj_idnfq7xbx8qewyoa][light.ab1-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'max_color_temp_kelvin': 6500, + 'max_mireds': 500, + 'min_color_temp_kelvin': 2000, + 'min_mireds': 153, + 'supported_color_modes': list([ + , + , + ]), + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'light', + 'entity_category': None, + 'entity_id': 'light.ab1', + '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': 0, + 'translation_key': None, + 'unique_id': 'tuya.bf599f5cffe1a5985depykswitch_led', + 'unit_of_measurement': None, + }) +# --- +# name: test_platform_setup_and_discovery[dj_idnfq7xbx8qewyoa][light.ab1-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'brightness': 255, + 'color_mode': , + 'color_temp': None, + 'color_temp_kelvin': None, + 'friendly_name': 'AB1', + 'hs_color': tuple( + 6.0, + 97.8, + ), + 'max_color_temp_kelvin': 6500, + 'max_mireds': 500, + 'min_color_temp_kelvin': 2000, + 'min_mireds': 153, + 'rgb_color': tuple( + 255, + 31, + 6, + ), + 'supported_color_modes': list([ + , + , + ]), + 'supported_features': , + 'xy_color': tuple( + 0.693, + 0.304, + ), + }), + 'context': , + 'entity_id': 'light.ab1', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'on', + }) +# --- +# name: test_platform_setup_and_discovery[dj_ilddqqih3tucdk68][light.ieskas-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'max_color_temp_kelvin': 6500, + 'max_mireds': 500, + 'min_color_temp_kelvin': 2000, + 'min_mireds': 153, + 'supported_color_modes': list([ + , + ]), + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'light', + 'entity_category': None, + 'entity_id': 'light.ieskas', + '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': 0, + 'translation_key': None, + 'unique_id': 'tuya.84178216d8f15be52dc4switch_led', + 'unit_of_measurement': None, + }) +# --- +# name: test_platform_setup_and_discovery[dj_ilddqqih3tucdk68][light.ieskas-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'brightness': 255, + 'color_mode': , + 'color_temp': 285, + 'color_temp_kelvin': 3508, + 'friendly_name': 'Ieskas', + 'hs_color': tuple( + 27.165, + 44.6, + ), + 'max_color_temp_kelvin': 6500, + 'max_mireds': 500, + 'min_color_temp_kelvin': 2000, + 'min_mireds': 153, + 'rgb_color': tuple( + 255, + 193, + 141, + ), + 'supported_color_modes': list([ + , + ]), + 'supported_features': , + 'xy_color': tuple( + 0.453, + 0.374, + ), + }), + 'context': , + 'entity_id': 'light.ieskas', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'on', + }) +# --- +# name: test_platform_setup_and_discovery[dj_j1bgp31cffutizub][light.ceiling_portal-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'max_color_temp_kelvin': 6500, + 'max_mireds': 500, + 'min_color_temp_kelvin': 2000, + 'min_mireds': 153, + 'supported_color_modes': list([ + , + , + ]), + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'light', + 'entity_category': None, + 'entity_id': 'light.ceiling_portal', + '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': 0, + 'translation_key': None, + 'unique_id': 'tuya.bfe49d7b6cd80536efdldiswitch_led', + 'unit_of_measurement': None, + }) +# --- +# name: test_platform_setup_and_discovery[dj_j1bgp31cffutizub][light.ceiling_portal-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'brightness': None, + 'color_mode': None, + 'color_temp': None, + 'color_temp_kelvin': None, + 'friendly_name': 'Ceiling Portal', + 'hs_color': None, + 'max_color_temp_kelvin': 6500, + 'max_mireds': 500, + 'min_color_temp_kelvin': 2000, + 'min_mireds': 153, + 'rgb_color': None, + 'supported_color_modes': list([ + , + , + ]), + 'supported_features': , + 'xy_color': None, + }), + 'context': , + 'entity_id': 'light.ceiling_portal', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'off', + }) +# --- +# name: test_platform_setup_and_discovery[dj_lmnt3uyltk1xffrt][light.directietkamer-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'max_color_temp_kelvin': 6500, + 'max_mireds': 500, + 'min_color_temp_kelvin': 2000, + 'min_mireds': 153, + 'supported_color_modes': list([ + , + ]), + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'light', + 'entity_category': None, + 'entity_id': 'light.directietkamer', + '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': 0, + 'translation_key': None, + 'unique_id': 'tuya.07608286600194e94248switch_led', + 'unit_of_measurement': None, + }) +# --- +# name: test_platform_setup_and_discovery[dj_lmnt3uyltk1xffrt][light.directietkamer-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'brightness': None, + 'color_mode': None, + 'color_temp': None, + 'color_temp_kelvin': None, + 'friendly_name': 'DirectietKamer', + 'hs_color': None, + 'max_color_temp_kelvin': 6500, + 'max_mireds': 500, + 'min_color_temp_kelvin': 2000, + 'min_mireds': 153, + 'rgb_color': None, + 'supported_color_modes': list([ + , + ]), + 'supported_features': , + 'xy_color': None, + }), + 'context': , + 'entity_id': 'light.directietkamer', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'off', + }) +# --- # name: test_platform_setup_and_discovery[dj_mki13ie507rlry4r][light.garage_light-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ @@ -74,7 +1209,775 @@ 'disabled_by': None, 'domain': 'light', 'entity_category': None, - 'entity_id': 'light.garage_light', + 'entity_id': 'light.garage_light', + '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': 0, + 'translation_key': None, + 'unique_id': 'tuya.REDACTEDswitch_led', + 'unit_of_measurement': None, + }) +# --- +# name: test_platform_setup_and_discovery[dj_mki13ie507rlry4r][light.garage_light-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'brightness': 138, + 'color_mode': , + 'friendly_name': 'Garage light', + 'hs_color': None, + 'rgb_color': None, + 'supported_color_modes': list([ + , + , + ]), + 'supported_features': , + 'xy_color': None, + }), + 'context': , + 'entity_id': 'light.garage_light', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'on', + }) +# --- +# name: test_platform_setup_and_discovery[dj_nbumqpv8vz61enji][light.b2-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'max_color_temp_kelvin': 6500, + 'max_mireds': 500, + 'min_color_temp_kelvin': 2000, + 'min_mireds': 153, + 'supported_color_modes': list([ + , + , + ]), + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'light', + 'entity_category': None, + 'entity_id': 'light.b2', + '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': 0, + 'translation_key': None, + 'unique_id': 'tuya.bf77c04cbd6a52a7be16llswitch_led', + 'unit_of_measurement': None, + }) +# --- +# name: test_platform_setup_and_discovery[dj_nbumqpv8vz61enji][light.b2-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'b2', + 'max_color_temp_kelvin': 6500, + 'max_mireds': 500, + 'min_color_temp_kelvin': 2000, + 'min_mireds': 153, + 'supported_color_modes': list([ + , + , + ]), + 'supported_features': , + }), + 'context': , + 'entity_id': 'light.b2', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unavailable', + }) +# --- +# name: test_platform_setup_and_discovery[dj_nlxvjzy1hoeiqsg6][light.hall-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'max_color_temp_kelvin': 6500, + 'max_mireds': 500, + 'min_color_temp_kelvin': 2000, + 'min_mireds': 153, + 'supported_color_modes': list([ + , + ]), + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'light', + 'entity_category': None, + 'entity_id': 'light.hall', + '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': 0, + 'translation_key': None, + 'unique_id': 'tuya.40350105dc4f229a464eswitch_led', + 'unit_of_measurement': None, + }) +# --- +# name: test_platform_setup_and_discovery[dj_nlxvjzy1hoeiqsg6][light.hall-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'brightness': None, + 'color_mode': None, + 'color_temp': None, + 'color_temp_kelvin': None, + 'friendly_name': 'hall 💡 ', + 'hs_color': None, + 'max_color_temp_kelvin': 6500, + 'max_mireds': 500, + 'min_color_temp_kelvin': 2000, + 'min_mireds': 153, + 'rgb_color': None, + 'supported_color_modes': list([ + , + ]), + 'supported_features': , + 'xy_color': None, + }), + 'context': , + 'entity_id': 'light.hall', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'off', + }) +# --- +# name: test_platform_setup_and_discovery[dj_oe0cpnjg][light.front_right_lighting_trap-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'max_color_temp_kelvin': 6500, + 'max_mireds': 500, + 'min_color_temp_kelvin': 2000, + 'min_mireds': 153, + 'supported_color_modes': list([ + , + , + ]), + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'light', + 'entity_category': None, + 'entity_id': 'light.front_right_lighting_trap', + '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': 0, + 'translation_key': None, + 'unique_id': 'tuya.bf8d8af3ddfe75b0195r0hswitch_led', + 'unit_of_measurement': None, + }) +# --- +# name: test_platform_setup_and_discovery[dj_oe0cpnjg][light.front_right_lighting_trap-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'brightness': None, + 'color_mode': None, + 'color_temp': None, + 'color_temp_kelvin': None, + 'friendly_name': 'Front right Lighting trap', + 'hs_color': None, + 'max_color_temp_kelvin': 6500, + 'max_mireds': 500, + 'min_color_temp_kelvin': 2000, + 'min_mireds': 153, + 'rgb_color': None, + 'supported_color_modes': list([ + , + , + ]), + 'supported_features': , + 'xy_color': None, + }), + 'context': , + 'entity_id': 'light.front_right_lighting_trap', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'off', + }) +# --- +# name: test_platform_setup_and_discovery[dj_riwp3k79][light.led_keuken_2-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'max_color_temp_kelvin': 6500, + 'max_mireds': 500, + 'min_color_temp_kelvin': 2000, + 'min_mireds': 153, + 'supported_color_modes': list([ + , + , + ]), + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'light', + 'entity_category': None, + 'entity_id': 'light.led_keuken_2', + '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': 0, + 'translation_key': None, + 'unique_id': 'tuya.bf46b2b81ca41ce0c1xpswswitch_led', + 'unit_of_measurement': None, + }) +# --- +# name: test_platform_setup_and_discovery[dj_riwp3k79][light.led_keuken_2-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'brightness': 255, + 'color_mode': , + 'color_temp': 500, + 'color_temp_kelvin': 2000, + 'friendly_name': 'LED KEUKEN 2', + 'hs_color': tuple( + 30.601, + 94.547, + ), + 'max_color_temp_kelvin': 6500, + 'max_mireds': 500, + 'min_color_temp_kelvin': 2000, + 'min_mireds': 153, + 'rgb_color': tuple( + 255, + 137, + 14, + ), + 'supported_color_modes': list([ + , + , + ]), + 'supported_features': , + 'xy_color': tuple( + 0.598, + 0.383, + ), + }), + 'context': , + 'entity_id': 'light.led_keuken_2', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'on', + }) +# --- +# name: test_platform_setup_and_discovery[dj_tmsloaroqavbucgn][light.pokerlamp_1-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'max_color_temp_kelvin': 6500, + 'max_mireds': 500, + 'min_color_temp_kelvin': 2000, + 'min_mireds': 153, + 'supported_color_modes': list([ + , + ]), + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'light', + 'entity_category': None, + 'entity_id': 'light.pokerlamp_1', + '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': 0, + 'translation_key': None, + 'unique_id': 'tuya.bf252b8ee16b2e78bdoxlpswitch_led', + 'unit_of_measurement': None, + }) +# --- +# name: test_platform_setup_and_discovery[dj_tmsloaroqavbucgn][light.pokerlamp_1-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Pokerlamp 1', + 'max_color_temp_kelvin': 6500, + 'max_mireds': 500, + 'min_color_temp_kelvin': 2000, + 'min_mireds': 153, + 'supported_color_modes': list([ + , + ]), + 'supported_features': , + }), + 'context': , + 'entity_id': 'light.pokerlamp_1', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unavailable', + }) +# --- +# name: test_platform_setup_and_discovery[dj_ufq2xwuzd4nb0qdr][light.sjiethoes-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'max_color_temp_kelvin': 6500, + 'max_mireds': 500, + 'min_color_temp_kelvin': 2000, + 'min_mireds': 153, + 'supported_color_modes': list([ + , + ]), + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'light', + 'entity_category': None, + 'entity_id': 'light.sjiethoes', + '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': 0, + 'translation_key': None, + 'unique_id': 'tuya.bf8edbd51a52c01a4bfgqfswitch_led', + 'unit_of_measurement': None, + }) +# --- +# name: test_platform_setup_and_discovery[dj_ufq2xwuzd4nb0qdr][light.sjiethoes-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Sjiethoes', + 'max_color_temp_kelvin': 6500, + 'max_mireds': 500, + 'min_color_temp_kelvin': 2000, + 'min_mireds': 153, + 'supported_color_modes': list([ + , + ]), + 'supported_features': , + }), + 'context': , + 'entity_id': 'light.sjiethoes', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unavailable', + }) +# --- +# name: test_platform_setup_and_discovery[dj_vqwcnabamzrc2kab][light.strip_2-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'max_color_temp_kelvin': 6500, + 'max_mireds': 500, + 'min_color_temp_kelvin': 2000, + 'min_mireds': 153, + 'supported_color_modes': list([ + , + , + ]), + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'light', + 'entity_category': None, + 'entity_id': 'light.strip_2', + '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': 0, + 'translation_key': None, + 'unique_id': 'tuya.bfd56f4718874ee8830xdwswitch_led', + 'unit_of_measurement': None, + }) +# --- +# name: test_platform_setup_and_discovery[dj_vqwcnabamzrc2kab][light.strip_2-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Strip 2', + 'max_color_temp_kelvin': 6500, + 'max_mireds': 500, + 'min_color_temp_kelvin': 2000, + 'min_mireds': 153, + 'supported_color_modes': list([ + , + , + ]), + 'supported_features': , + }), + 'context': , + 'entity_id': 'light.strip_2', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unavailable', + }) +# --- +# name: test_platform_setup_and_discovery[dj_xokdfs6kh5ednakk][light.erker_1_gold-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'max_color_temp_kelvin': 6500, + 'max_mireds': 500, + 'min_color_temp_kelvin': 2000, + 'min_mireds': 153, + 'supported_color_modes': list([ + , + ]), + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'light', + 'entity_category': None, + 'entity_id': 'light.erker_1_gold', + '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': 0, + 'translation_key': None, + 'unique_id': 'tuya.bfc1ef4da4accc0731oggwswitch_led', + 'unit_of_measurement': None, + }) +# --- +# name: test_platform_setup_and_discovery[dj_xokdfs6kh5ednakk][light.erker_1_gold-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'brightness': 255, + 'color_mode': , + 'color_temp': 500, + 'color_temp_kelvin': 2000, + 'friendly_name': 'ERKER 1-Gold ', + 'hs_color': tuple( + 30.601, + 94.547, + ), + 'max_color_temp_kelvin': 6500, + 'max_mireds': 500, + 'min_color_temp_kelvin': 2000, + 'min_mireds': 153, + 'rgb_color': tuple( + 255, + 137, + 14, + ), + 'supported_color_modes': list([ + , + ]), + 'supported_features': , + 'xy_color': tuple( + 0.598, + 0.383, + ), + }), + 'context': , + 'entity_id': 'light.erker_1_gold', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'on', + }) +# --- +# name: test_platform_setup_and_discovery[dj_zakhnlpdiu0ycdxn][light.stoel-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'max_color_temp_kelvin': 6500, + 'max_mireds': 500, + 'min_color_temp_kelvin': 2000, + 'min_mireds': 153, + 'supported_color_modes': list([ + , + ]), + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'light', + 'entity_category': None, + 'entity_id': 'light.stoel', + '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': 0, + 'translation_key': None, + 'unique_id': 'tuya.03010850c44f33966362switch_led', + 'unit_of_measurement': None, + }) +# --- +# name: test_platform_setup_and_discovery[dj_zakhnlpdiu0ycdxn][light.stoel-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Stoel', + 'max_color_temp_kelvin': 6500, + 'max_mireds': 500, + 'min_color_temp_kelvin': 2000, + 'min_mireds': 153, + 'supported_color_modes': list([ + , + ]), + 'supported_features': , + }), + 'context': , + 'entity_id': 'light.stoel', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unavailable', + }) +# --- +# name: test_platform_setup_and_discovery[dj_zav1pa32pyxray78][light.gengske-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'max_color_temp_kelvin': 6500, + 'max_mireds': 500, + 'min_color_temp_kelvin': 2000, + 'min_mireds': 153, + 'supported_color_modes': list([ + , + , + ]), + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'light', + 'entity_category': None, + 'entity_id': 'light.gengske', + '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': 0, + 'translation_key': None, + 'unique_id': 'tuya.500425642462ab50909bswitch_led', + 'unit_of_measurement': None, + }) +# --- +# name: test_platform_setup_and_discovery[dj_zav1pa32pyxray78][light.gengske-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'brightness': None, + 'color_mode': None, + 'color_temp': None, + 'color_temp_kelvin': None, + 'friendly_name': 'Gengske 💡 ', + 'hs_color': None, + 'max_color_temp_kelvin': 6500, + 'max_mireds': 500, + 'min_color_temp_kelvin': 2000, + 'min_mireds': 153, + 'rgb_color': None, + 'supported_color_modes': list([ + , + , + ]), + 'supported_features': , + 'xy_color': None, + }), + 'context': , + 'entity_id': 'light.gengske', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'off', + }) +# --- +# name: test_platform_setup_and_discovery[dj_zputiamzanuk6yky][light.floodlight-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': None, + 'entity_id': 'light.floodlight', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -92,16 +1995,16 @@ 'suggested_object_id': None, 'supported_features': 0, 'translation_key': None, - 'unique_id': 'tuya.REDACTEDswitch_led', + 'unique_id': 'tuya.bf74164049de868395pbciswitch_led', 'unit_of_measurement': None, }) # --- -# name: test_platform_setup_and_discovery[dj_mki13ie507rlry4r][light.garage_light-state] +# name: test_platform_setup_and_discovery[dj_zputiamzanuk6yky][light.floodlight-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'brightness': 138, - 'color_mode': , - 'friendly_name': 'Garage light', + 'brightness': None, + 'color_mode': None, + 'friendly_name': 'Floodlight', 'hs_color': None, 'rgb_color': None, 'supported_color_modes': list([ @@ -112,11 +2015,11 @@ 'xy_color': None, }), 'context': , - 'entity_id': 'light.garage_light', + 'entity_id': 'light.floodlight', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': 'on', + 'state': 'off', }) # --- # name: test_platform_setup_and_discovery[gyd_lgekqfxdabipm3tn][light.colorful_pir_night_light-entry] From c1ccfee7cc79f9ef8b6731d006c481253a5820b2 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 4 Aug 2025 12:08:03 +0200 Subject: [PATCH 06/30] Pass config entry to AsusWRT coordinator (#149953) --- homeassistant/components/asuswrt/router.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/asuswrt/router.py b/homeassistant/components/asuswrt/router.py index a34f191b7a780e..3cf8d2e863de1b 100644 --- a/homeassistant/components/asuswrt/router.py +++ b/homeassistant/components/asuswrt/router.py @@ -5,7 +5,7 @@ from collections.abc import Callable, Mapping from datetime import datetime, timedelta import logging -from typing import Any +from typing import TYPE_CHECKING, Any from pyasuswrt import AsusWrtError @@ -40,6 +40,9 @@ SENSORS_CONNECTED_DEVICE, ) +if TYPE_CHECKING: + from . import AsusWrtConfigEntry + CONF_REQ_RELOAD = [CONF_DNSMASQ, CONF_INTERFACE, CONF_REQUIRE_IP] SCAN_INTERVAL = timedelta(seconds=30) @@ -52,10 +55,13 @@ class AsusWrtSensorDataHandler: """Data handler for AsusWrt sensor.""" - def __init__(self, hass: HomeAssistant, api: AsusWrtBridge) -> None: + def __init__( + self, hass: HomeAssistant, api: AsusWrtBridge, entry: AsusWrtConfigEntry + ) -> None: """Initialize a AsusWrt sensor data handler.""" self._hass = hass self._api = api + self._entry = entry self._connected_devices = 0 async def _get_connected_devices(self) -> dict[str, int]: @@ -91,6 +97,7 @@ async def get_coordinator( update_method=method, # Polling interval. Will only be polled if there are subscribers. update_interval=SCAN_INTERVAL if should_poll else None, + config_entry=self._entry, ) await coordinator.async_refresh() @@ -321,7 +328,9 @@ async def init_sensors_coordinator(self) -> None: if self._sensors_data_handler: return - self._sensors_data_handler = AsusWrtSensorDataHandler(self.hass, self._api) + self._sensors_data_handler = AsusWrtSensorDataHandler( + self.hass, self._api, self._entry + ) self._sensors_data_handler.update_device_count(self._connected_devices) sensors_types = await self._api.async_get_available_sensors() From afffe0b08bf450494f242e709529ac052ad19fce Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Mon, 4 Aug 2025 12:20:30 +0200 Subject: [PATCH 07/30] Fix DeviceEntry.suggested_area deprecation warning (#149951) --- homeassistant/helpers/device_registry.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/homeassistant/helpers/device_registry.py b/homeassistant/helpers/device_registry.py index 72d0cf651f2b3f..c7f7d4c369d2f7 100644 --- a/homeassistant/helpers/device_registry.py +++ b/homeassistant/helpers/device_registry.py @@ -1221,8 +1221,6 @@ def async_update_device( # noqa: C901 ("name", name), ("name_by_user", name_by_user), ("serial_number", serial_number), - # Can be removed when suggested_area is removed from DeviceEntry - ("suggested_area", suggested_area), ("sw_version", sw_version), ("via_device_id", via_device_id), ): @@ -1230,6 +1228,11 @@ def async_update_device( # noqa: C901 new_values[attr_name] = value old_values[attr_name] = getattr(old, attr_name) + # Can be removed when suggested_area is removed from DeviceEntry + if suggested_area is not UNDEFINED and suggested_area != old._suggested_area: # noqa: SLF001 + new_values["suggested_area"] = suggested_area + old_values["suggested_area"] = old._suggested_area # noqa: SLF001 + if old.is_new: new_values["is_new"] = False From cbf4130bff17d746d72b8e307e195767fec40fbd Mon Sep 17 00:00:00 2001 From: Markus Adrario Date: Mon, 4 Aug 2025 12:26:22 +0200 Subject: [PATCH 08/30] Add zeroconf flow to Homee (#149820) Co-authored-by: Joostlek --- homeassistant/components/homee/config_flow.py | 155 ++++++++--- homeassistant/components/homee/const.py | 5 + homeassistant/components/homee/manifest.json | 8 +- homeassistant/components/homee/strings.json | 11 + homeassistant/generated/zeroconf.py | 4 + tests/components/homee/test_config_flow.py | 248 ++++++++++++++---- 6 files changed, 342 insertions(+), 89 deletions(-) diff --git a/homeassistant/components/homee/config_flow.py b/homeassistant/components/homee/config_flow.py index 7030752f4c31ff..44c9b70953bc9a 100644 --- a/homeassistant/components/homee/config_flow.py +++ b/homeassistant/components/homee/config_flow.py @@ -11,10 +11,16 @@ ) import voluptuous as vol -from homeassistant.config_entries import ConfigFlow, ConfigFlowResult +from homeassistant.config_entries import SOURCE_USER, ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME +from homeassistant.helpers.service_info.zeroconf import ZeroconfServiceInfo -from .const import DOMAIN +from .const import ( + DOMAIN, + RESULT_CANNOT_CONNECT, + RESULT_INVALID_AUTH, + RESULT_UNKNOWN_ERROR, +) _LOGGER = logging.getLogger(__name__) @@ -33,58 +39,135 @@ class HomeeConfigFlow(ConfigFlow, domain=DOMAIN): VERSION = 1 homee: Homee + _host: str + _name: str _reauth_host: str _reauth_username: str + async def _connect_homee(self) -> dict[str, str]: + errors: dict[str, str] = {} + try: + await self.homee.get_access_token() + except HomeeConnectionFailedException: + errors["base"] = RESULT_CANNOT_CONNECT + except HomeeAuthenticationFailedException: + errors["base"] = RESULT_INVALID_AUTH + except Exception: + _LOGGER.exception("Unexpected exception") + errors["base"] = RESULT_UNKNOWN_ERROR + else: + _LOGGER.info("Got access token for homee") + self.hass.loop.create_task(self.homee.run()) + _LOGGER.debug("Homee task created") + await self.homee.wait_until_connected() + _LOGGER.info("Homee connected") + self.homee.disconnect() + _LOGGER.debug("Homee disconnecting") + await self.homee.wait_until_disconnected() + _LOGGER.info("Homee config successfully tested") + + await self.async_set_unique_id( + self.homee.settings.uid, raise_on_progress=self.source != SOURCE_USER + ) + + self._abort_if_unique_id_configured() + + _LOGGER.info("Created new homee entry with ID %s", self.homee.settings.uid) + + return errors + async def async_step_user( self, user_input: dict[str, Any] | None = None ) -> ConfigFlowResult: """Handle the initial user step.""" + errors: dict[str, str] = {} - errors = {} if user_input is not None: self.homee = Homee( user_input[CONF_HOST], user_input[CONF_USERNAME], user_input[CONF_PASSWORD], ) + errors = await self._connect_homee() - try: - await self.homee.get_access_token() - except HomeeConnectionFailedException: - errors["base"] = "cannot_connect" - except HomeeAuthenticationFailedException: - errors["base"] = "invalid_auth" - except Exception: - _LOGGER.exception("Unexpected exception") - errors["base"] = "unknown" - else: - _LOGGER.info("Got access token for homee") - self.hass.loop.create_task(self.homee.run()) - _LOGGER.debug("Homee task created") - await self.homee.wait_until_connected() - _LOGGER.info("Homee connected") - self.homee.disconnect() - _LOGGER.debug("Homee disconnecting") - await self.homee.wait_until_disconnected() - _LOGGER.info("Homee config successfully tested") + if not errors: + return self.async_create_entry( + title=f"{self.homee.settings.homee_name} ({self.homee.host})", + data=user_input, + ) - await self.async_set_unique_id(self.homee.settings.uid) + return self.async_show_form( + step_id="user", + data_schema=AUTH_SCHEMA, + errors=errors, + ) - self._abort_if_unique_id_configured() + async def async_step_zeroconf( + self, discovery_info: ZeroconfServiceInfo + ) -> ConfigFlowResult: + """Handle zeroconf discovery.""" - _LOGGER.info( - "Created new homee entry with ID %s", self.homee.settings.uid - ) + # Ensure that an IPv4 address is received + self._host = discovery_info.host + self._name = discovery_info.hostname[6:18] + if discovery_info.ip_address.version == 6: + return self.async_abort(reason="ipv6_address") + + await self.async_set_unique_id(self._name) + self._abort_if_unique_id_configured(updates={CONF_HOST: self._host}) + + # Cause an auth-error to see if homee is reachable. + self.homee = Homee( + self._host, + "dummy_username", + "dummy_password", + ) + errors = await self._connect_homee() + if errors["base"] != RESULT_INVALID_AUTH: + return self.async_abort(reason=RESULT_CANNOT_CONNECT) + + self.context["title_placeholders"] = {"name": self._name, "host": self._host} + return await self.async_step_zeroconf_confirm() + + async def async_step_zeroconf_confirm( + self, user_input: dict[str, Any] | None = None + ) -> ConfigFlowResult: + """Confirm the configuration of the device.""" + + errors: dict[str, str] = {} + if user_input is not None: + self.homee = Homee( + self._host, + user_input[CONF_USERNAME], + user_input[CONF_PASSWORD], + ) + + errors = await self._connect_homee() + + if not errors: return self.async_create_entry( title=f"{self.homee.settings.homee_name} ({self.homee.host})", - data=user_input, + data={ + CONF_HOST: self._host, + CONF_USERNAME: user_input[CONF_USERNAME], + CONF_PASSWORD: user_input[CONF_PASSWORD], + }, ) + return self.async_show_form( - step_id="user", - data_schema=AUTH_SCHEMA, + step_id="zeroconf_confirm", + data_schema=vol.Schema( + { + vol.Required(CONF_USERNAME): str, + vol.Required(CONF_PASSWORD): str, + } + ), errors=errors, + description_placeholders={ + CONF_HOST: self._name, + }, + last_step=True, ) async def async_step_reauth( @@ -108,12 +191,12 @@ async def async_step_reauth_confirm( try: await self.homee.get_access_token() except HomeeConnectionFailedException: - errors["base"] = "cannot_connect" + errors["base"] = RESULT_CANNOT_CONNECT except HomeeAuthenticationFailedException: - errors["base"] = "invalid_auth" + errors["base"] = RESULT_INVALID_AUTH except Exception: _LOGGER.exception("Unexpected exception") - errors["base"] = "unknown" + errors["base"] = RESULT_UNKNOWN_ERROR else: self.hass.loop.create_task(self.homee.run()) await self.homee.wait_until_connected() @@ -161,12 +244,12 @@ async def async_step_reconfigure( try: await self.homee.get_access_token() except HomeeConnectionFailedException: - errors["base"] = "cannot_connect" + errors["base"] = RESULT_CANNOT_CONNECT except HomeeAuthenticationFailedException: - errors["base"] = "invalid_auth" + errors["base"] = RESULT_INVALID_AUTH except Exception: _LOGGER.exception("Unexpected exception") - errors["base"] = "unknown" + errors["base"] = RESULT_UNKNOWN_ERROR else: self.hass.loop.create_task(self.homee.run()) await self.homee.wait_until_connected() diff --git a/homeassistant/components/homee/const.py b/homeassistant/components/homee/const.py index 7bc3de189d63bb..718baf346ae7be 100644 --- a/homeassistant/components/homee/const.py +++ b/homeassistant/components/homee/const.py @@ -20,6 +20,11 @@ # General DOMAIN = "homee" +# Error strings +RESULT_CANNOT_CONNECT = "cannot_connect" +RESULT_INVALID_AUTH = "invalid_auth" +RESULT_UNKNOWN_ERROR = "unknown" + # Sensor mappings HOMEE_UNIT_TO_HA_UNIT = { "": None, diff --git a/homeassistant/components/homee/manifest.json b/homeassistant/components/homee/manifest.json index 9cac876f3251e4..35e89ec645adab 100644 --- a/homeassistant/components/homee/manifest.json +++ b/homeassistant/components/homee/manifest.json @@ -8,5 +8,11 @@ "iot_class": "local_push", "loggers": ["homee"], "quality_scale": "silver", - "requirements": ["pyHomee==1.2.10"] + "requirements": ["pyHomee==1.2.10"], + "zeroconf": [ + { + "type": "_ssh._tcp.local.", + "name": "homee-*" + } + ] } diff --git a/homeassistant/components/homee/strings.json b/homeassistant/components/homee/strings.json index 267d5553a8c5f1..26fa335d1478ae 100644 --- a/homeassistant/components/homee/strings.json +++ b/homeassistant/components/homee/strings.json @@ -46,6 +46,17 @@ "data_description": { "host": "[%key:component::homee::config::step::user::data_description::host%]" } + }, + "zeroconf_confirm": { + "title": "Configure discovered homee {host}", + "data": { + "username": "[%key:common::config_flow::data::username%]", + "password": "[%key:common::config_flow::data::password%]" + }, + "data_description": { + "username": "[%key:component::homee::config::step::user::data_description::username%]", + "password": "[%key:component::homee::config::step::user::data_description::password%]" + } } } }, diff --git a/homeassistant/generated/zeroconf.py b/homeassistant/generated/zeroconf.py index a3668acee8d09f..742840fa84957e 100644 --- a/homeassistant/generated/zeroconf.py +++ b/homeassistant/generated/zeroconf.py @@ -890,6 +890,10 @@ }, ], "_ssh._tcp.local.": [ + { + "domain": "homee", + "name": "homee-*", + }, { "domain": "smappee", "name": "smappee1*", diff --git a/tests/components/homee/test_config_flow.py b/tests/components/homee/test_config_flow.py index 6f45dcbdb0d3e9..3d2195443a28df 100644 --- a/tests/components/homee/test_config_flow.py +++ b/tests/components/homee/test_config_flow.py @@ -1,15 +1,23 @@ """Test the Homee config flow.""" +from ipaddress import ip_address from unittest.mock import AsyncMock from pyHomee import HomeeAuthFailedException, HomeeConnectionFailedException import pytest -from homeassistant.components.homee.const import DOMAIN +from homeassistant import config_entries +from homeassistant.components.homee.const import ( + DOMAIN, + RESULT_CANNOT_CONNECT, + RESULT_INVALID_AUTH, + RESULT_UNKNOWN_ERROR, +) from homeassistant.config_entries import SOURCE_USER from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME from homeassistant.core import HomeAssistant from homeassistant.data_entry_flow import FlowResultType +from homeassistant.helpers.service_info.zeroconf import ZeroconfServiceInfo from .conftest import ( HOMEE_ID, @@ -24,6 +32,24 @@ from tests.common import MockConfigEntry +PARAMETRIZED_ERRORS = ( + ("side_eff", "error"), + [ + ( + HomeeConnectionFailedException("connection timed out"), + {"base": RESULT_CANNOT_CONNECT}, + ), + ( + HomeeAuthFailedException("wrong username or password"), + {"base": RESULT_INVALID_AUTH}, + ), + ( + Exception, + {"base": RESULT_UNKNOWN_ERROR}, + ), + ], +) + @pytest.mark.usefixtures("mock_homee", "mock_config_entry", "mock_setup_entry") async def test_config_flow( @@ -58,23 +84,7 @@ async def test_config_flow( assert result["result"].unique_id == HOMEE_ID -@pytest.mark.parametrize( - ("side_eff", "error"), - [ - ( - HomeeConnectionFailedException("connection timed out"), - {"base": "cannot_connect"}, - ), - ( - HomeeAuthFailedException("wrong username or password"), - {"base": "invalid_auth"}, - ), - ( - Exception, - {"base": "unknown"}, - ), - ], -) +@pytest.mark.parametrize(*PARAMETRIZED_ERRORS) async def test_config_flow_errors( hass: HomeAssistant, mock_homee: AsyncMock, @@ -140,6 +150,172 @@ async def test_flow_already_configured( assert result["reason"] == "already_configured" +@pytest.mark.usefixtures("mock_homee", "mock_config_entry") +async def test_zeroconf_success( + hass: HomeAssistant, + mock_setup_entry: AsyncMock, + mock_homee: AsyncMock, +) -> None: + """Test zeroconf discovery flow.""" + mock_homee.get_access_token.side_effect = HomeeAuthFailedException( + "wrong username or password" + ) + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_ZEROCONF}, + data=ZeroconfServiceInfo( + name=f"homee-{HOMEE_ID}._ssh._tcp.local.", + type="_ssh._tcp.local.", + hostname=f"homee-{HOMEE_ID}.local.", + ip_address=ip_address(HOMEE_IP), + ip_addresses=[ip_address(HOMEE_IP)], + port=22, + properties={}, + ), + ) + + assert result["type"] is FlowResultType.FORM + assert result["step_id"] == "zeroconf_confirm" + assert result["handler"] == DOMAIN + mock_setup_entry.assert_not_called() + + mock_homee.get_access_token.side_effect = None + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={ + CONF_USERNAME: TESTUSER, + CONF_PASSWORD: TESTPASS, + }, + ) + + assert result["data"] == { + CONF_HOST: HOMEE_IP, + CONF_USERNAME: TESTUSER, + CONF_PASSWORD: TESTPASS, + } + + mock_setup_entry.assert_called_once() + + +@pytest.mark.parametrize(*PARAMETRIZED_ERRORS) +async def test_zeroconf_confirm_errors( + hass: HomeAssistant, + mock_homee: AsyncMock, + side_eff: Exception, + error: dict[str, str], +) -> None: + """Test zeroconf discovery flow errors.""" + mock_homee.get_access_token.side_effect = HomeeAuthFailedException( + "wrong username or password" + ) + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_ZEROCONF}, + data=ZeroconfServiceInfo( + name=f"homee-{HOMEE_ID}._ssh._tcp.local.", + type="_ssh._tcp.local.", + hostname=f"homee-{HOMEE_ID}.local.", + ip_address=ip_address(HOMEE_IP), + ip_addresses=[ip_address(HOMEE_IP)], + port=22, + properties={}, + ), + ) + + flow_id = result["flow_id"] + + assert result["type"] is FlowResultType.FORM + assert result["step_id"] == "zeroconf_confirm" + assert result["handler"] == DOMAIN + + mock_homee.get_access_token.side_effect = side_eff + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={ + CONF_USERNAME: TESTUSER, + CONF_PASSWORD: TESTPASS, + }, + ) + + assert result["type"] == FlowResultType.FORM + assert result["errors"] == error + + mock_homee.get_access_token.side_effect = None + result = await hass.config_entries.flow.async_configure( + flow_id, + user_input={ + CONF_USERNAME: TESTUSER, + CONF_PASSWORD: TESTPASS, + }, + ) + + assert result["type"] == FlowResultType.CREATE_ENTRY + + +async def test_zeroconf_already_configured( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, +) -> None: + """Test zeroconf discovery flow when already configured.""" + mock_config_entry.add_to_hass(hass) + + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_ZEROCONF}, + data=ZeroconfServiceInfo( + name=f"homee-{HOMEE_ID}._ssh._tcp.local.", + type="_ssh._tcp.local.", + hostname=f"homee-{HOMEE_ID}.local.", + ip_address=ip_address(HOMEE_IP), + ip_addresses=[ip_address(HOMEE_IP)], + port=22, + properties={}, + ), + ) + + assert result["type"] is FlowResultType.ABORT + assert result["reason"] == "already_configured" + + +@pytest.mark.parametrize( + ("side_eff", "ip", "reason"), + [ + ( + HomeeConnectionFailedException("connection timed out"), + HOMEE_IP, + RESULT_CANNOT_CONNECT, + ), + (Exception, HOMEE_IP, RESULT_CANNOT_CONNECT), + (None, "2001:db8::1", "ipv6_address"), + ], +) +async def test_zeroconf_errors( + hass: HomeAssistant, + mock_homee: AsyncMock, + side_eff: Exception, + ip: str, + reason: str, +) -> None: + """Test zeroconf discovery flow with an IPv6 address.""" + mock_homee.get_access_token.side_effect = side_eff + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_ZEROCONF}, + data=ZeroconfServiceInfo( + name=f"homee-{HOMEE_ID}._ssh._tcp.local.", + type="_ssh._tcp.local.", + hostname=f"homee-{HOMEE_ID}.local.", + ip_address=ip_address(ip), + ip_addresses=[ip_address(ip)], + port=22, + properties={}, + ), + ) + + assert result["type"] is FlowResultType.ABORT + assert result["reason"] == reason + + @pytest.mark.usefixtures("mock_homee", "mock_setup_entry") async def test_reauth_success( hass: HomeAssistant, @@ -171,23 +347,7 @@ async def test_reauth_success( assert mock_config_entry.data[CONF_PASSWORD] == NEW_TESTPASS -@pytest.mark.parametrize( - ("side_eff", "error"), - [ - ( - HomeeConnectionFailedException("connection timed out"), - {"base": "cannot_connect"}, - ), - ( - HomeeAuthFailedException("wrong username or password"), - {"base": "invalid_auth"}, - ), - ( - Exception, - {"base": "unknown"}, - ), - ], -) +@pytest.mark.parametrize(*PARAMETRIZED_ERRORS) async def test_reauth_errors( hass: HomeAssistant, mock_config_entry: MockConfigEntry, @@ -296,23 +456,7 @@ async def test_reconfigure_success( assert mock_config_entry.data[CONF_PASSWORD] == TESTPASS -@pytest.mark.parametrize( - ("side_eff", "error"), - [ - ( - HomeeConnectionFailedException("connection timed out"), - {"base": "cannot_connect"}, - ), - ( - HomeeAuthFailedException("wrong username or password"), - {"base": "invalid_auth"}, - ), - ( - Exception, - {"base": "unknown"}, - ), - ], -) +@pytest.mark.parametrize(*PARAMETRIZED_ERRORS) async def test_reconfigure_errors( hass: HomeAssistant, mock_config_entry: MockConfigEntry, From 106c086e8bc99eb6a504bd53421a484b2b5da761 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 4 Aug 2025 12:29:27 +0200 Subject: [PATCH 09/30] Pass config entry to Unifi coordinator (#149952) --- homeassistant/components/unifi/hub/entity_loader.py | 8 +++++--- homeassistant/components/unifi/hub/hub.py | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/unifi/hub/entity_loader.py b/homeassistant/components/unifi/hub/entity_loader.py index 84948a92e98819..4fd3d34a51dc26 100644 --- a/homeassistant/components/unifi/hub/entity_loader.py +++ b/homeassistant/components/unifi/hub/entity_loader.py @@ -25,6 +25,7 @@ from ..entity import UnifiEntity, UnifiEntityDescription if TYPE_CHECKING: + from .. import UnifiConfigEntry from .hub import UnifiHub CHECK_HEARTBEAT_INTERVAL = timedelta(seconds=1) @@ -34,7 +35,7 @@ class UnifiEntityLoader: """UniFi Network integration handling platforms for entity registration.""" - def __init__(self, hub: UnifiHub) -> None: + def __init__(self, hub: UnifiHub, config_entry: UnifiConfigEntry) -> None: """Initialize the UniFi entity loader.""" self.hub = hub self.api_updaters = ( @@ -57,15 +58,16 @@ def __init__(self, hub: UnifiHub) -> None: ) self.wireless_clients = hub.hass.data[UNIFI_WIRELESS_CLIENTS] - self._dataUpdateCoordinator = DataUpdateCoordinator( + self._data_update_coordinator = DataUpdateCoordinator( hub.hass, LOGGER, name="Unifi entity poller", + config_entry=config_entry, update_method=self._update_pollable_api_data, update_interval=POLL_INTERVAL, ) - self._update_listener = self._dataUpdateCoordinator.async_add_listener( + self._update_listener = self._data_update_coordinator.async_add_listener( update_callback=lambda: None ) diff --git a/homeassistant/components/unifi/hub/hub.py b/homeassistant/components/unifi/hub/hub.py index 6cf8825a26cd58..9ea887bdb29a71 100644 --- a/homeassistant/components/unifi/hub/hub.py +++ b/homeassistant/components/unifi/hub/hub.py @@ -39,7 +39,7 @@ def __init__( self.hass = hass self.api = api self.config = UnifiConfig.from_config_entry(config_entry) - self.entity_loader = UnifiEntityLoader(self) + self.entity_loader = UnifiEntityLoader(self, config_entry) self._entity_helper = UnifiEntityHelper(hass, api) self.websocket = UnifiWebsocket(hass, api, self.signal_reachable) From c2b298283e5f8b30198c775a70b9e4ee0e3e8108 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Mon, 4 Aug 2025 11:32:01 +0100 Subject: [PATCH 10/30] Bump hass-nabucasa from 0.110.0 to 0.110.1 (#149956) --- homeassistant/components/cloud/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- pyproject.toml | 2 +- requirements.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/cloud/manifest.json b/homeassistant/components/cloud/manifest.json index a819203e549f6f..63eae6261d4587 100644 --- a/homeassistant/components/cloud/manifest.json +++ b/homeassistant/components/cloud/manifest.json @@ -13,6 +13,6 @@ "integration_type": "system", "iot_class": "cloud_push", "loggers": ["acme", "hass_nabucasa", "snitun"], - "requirements": ["hass-nabucasa==0.110.0"], + "requirements": ["hass-nabucasa==0.110.1"], "single_config_entry": true } diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 80354c706d6bbb..f8a57ba61bbfab 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -35,7 +35,7 @@ fnv-hash-fast==1.5.0 go2rtc-client==0.2.1 ha-ffmpeg==3.2.2 habluetooth==4.0.1 -hass-nabucasa==0.110.0 +hass-nabucasa==0.110.1 hassil==2.2.3 home-assistant-bluetooth==1.13.1 home-assistant-frontend==20250731.0 diff --git a/pyproject.toml b/pyproject.toml index 2bcb7787601e70..a32e9308fe2a36 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -47,7 +47,7 @@ dependencies = [ "fnv-hash-fast==1.5.0", # hass-nabucasa is imported by helpers which don't depend on the cloud # integration - "hass-nabucasa==0.110.0", + "hass-nabucasa==0.110.1", # When bumping httpx, please check the version pins of # httpcore, anyio, and h11 in gen_requirements_all "httpx==0.28.1", diff --git a/requirements.txt b/requirements.txt index a332eb930c2115..ba08a72e324ce2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -22,7 +22,7 @@ certifi>=2021.5.30 ciso8601==2.3.2 cronsim==2.6 fnv-hash-fast==1.5.0 -hass-nabucasa==0.110.0 +hass-nabucasa==0.110.1 httpx==0.28.1 home-assistant-bluetooth==1.13.1 ifaddr==0.2.0 diff --git a/requirements_all.txt b/requirements_all.txt index 52b2fdb42b848f..8dcc7478d7c21c 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1133,7 +1133,7 @@ habiticalib==0.4.1 habluetooth==4.0.1 # homeassistant.components.cloud -hass-nabucasa==0.110.0 +hass-nabucasa==0.110.1 # homeassistant.components.splunk hass-splunk==0.1.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index ffed56c6f3e321..ce01dbd5a51b40 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -994,7 +994,7 @@ habiticalib==0.4.1 habluetooth==4.0.1 # homeassistant.components.cloud -hass-nabucasa==0.110.0 +hass-nabucasa==0.110.1 # homeassistant.components.assist_satellite # homeassistant.components.conversation From 93e11aa8bc7dcda4e1b9ae467540908b719b2d6b Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk <11290930+bouwew@users.noreply.github.com> Date: Mon, 4 Aug 2025 12:35:24 +0200 Subject: [PATCH 11/30] Refresh plugwise test-fixtures (#149875) --- tests/components/plugwise/fixtures/legacy_anna/data.json | 2 ++ .../fixtures/m_adam_multiple_devices_per_zone/data.json | 3 ++- tests/components/plugwise/snapshots/test_diagnostics.ambr | 4 +++- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/tests/components/plugwise/fixtures/legacy_anna/data.json b/tests/components/plugwise/fixtures/legacy_anna/data.json index cc7e66fb174842..75c12a4c8c259c 100644 --- a/tests/components/plugwise/fixtures/legacy_anna/data.json +++ b/tests/components/plugwise/fixtures/legacy_anna/data.json @@ -35,6 +35,7 @@ }, "0d266432d64443e283b5d708ae98b455": { "active_preset": "home", + "available_schedules": [], "climate_mode": "heat", "control_state": "heating", "dev_class": "thermostat", @@ -44,6 +45,7 @@ "model": "ThermoTouch", "name": "Anna", "preset_modes": ["away", "vacation", "asleep", "home", "no_frost"], + "select_schedule": null, "sensors": { "illuminance": 150.8, "setpoint": 20.5, diff --git a/tests/components/plugwise/fixtures/m_adam_multiple_devices_per_zone/data.json b/tests/components/plugwise/fixtures/m_adam_multiple_devices_per_zone/data.json index 06459a11798125..f1880ba69e1495 100644 --- a/tests/components/plugwise/fixtures/m_adam_multiple_devices_per_zone/data.json +++ b/tests/components/plugwise/fixtures/m_adam_multiple_devices_per_zone/data.json @@ -112,12 +112,14 @@ }, "446ac08dd04d4eff8ac57489757b7314": { "active_preset": "no_frost", + "available_schedules": [], "climate_mode": "heat", "control_state": "idle", "dev_class": "climate", "model": "ThermoZone", "name": "Garage", "preset_modes": ["home", "asleep", "away", "vacation", "no_frost"], + "select_schedule": null, "sensors": { "temperature": 15.6 }, @@ -587,7 +589,6 @@ "warning": "Node Plug (with MAC address 000D6F000D13CB01, in room 'n.a.') has been unreachable since 23:03 2020-01-18. Please check the connection and restart the device." } }, - "select_regulation_mode": "heating", "sensors": { "outdoor_temperature": 7.81 }, diff --git a/tests/components/plugwise/snapshots/test_diagnostics.ambr b/tests/components/plugwise/snapshots/test_diagnostics.ambr index 4aa367bc11605b..91411c323ac3a5 100644 --- a/tests/components/plugwise/snapshots/test_diagnostics.ambr +++ b/tests/components/plugwise/snapshots/test_diagnostics.ambr @@ -131,6 +131,8 @@ }), '446ac08dd04d4eff8ac57489757b7314': dict({ 'active_preset': 'no_frost', + 'available_schedules': list([ + ]), 'climate_mode': 'heat', 'control_state': 'idle', 'dev_class': 'climate', @@ -143,6 +145,7 @@ 'vacation', 'no_frost', ]), + 'select_schedule': None, 'sensors': dict({ 'temperature': 15.6, }), @@ -635,7 +638,6 @@ 'warning': "Node Plug (with MAC address 000D6F000D13CB01, in room 'n.a.') has been unreachable since 23:03 2020-01-18. Please check the connection and restart the device.", }), }), - 'select_regulation_mode': 'heating', 'sensors': dict({ 'outdoor_temperature': 7.81, }), From 9edd24273457234ff35c84a4b78b89b48afeda05 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 4 Aug 2025 12:49:26 +0200 Subject: [PATCH 12/30] Pass config entry to SMS coordinator (#149955) --- homeassistant/components/sms/__init__.py | 4 ++-- homeassistant/components/sms/coordinator.py | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/sms/__init__.py b/homeassistant/components/sms/__init__.py index 6c7c5374f7d475..78f7899a571932 100644 --- a/homeassistant/components/sms/__init__.py +++ b/homeassistant/components/sms/__init__.py @@ -83,8 +83,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: if not gateway: raise ConfigEntryNotReady(f"Cannot find device {device}") - signal_coordinator = SignalCoordinator(hass, gateway) - network_coordinator = NetworkCoordinator(hass, gateway) + signal_coordinator = SignalCoordinator(hass, entry, gateway) + network_coordinator = NetworkCoordinator(hass, entry, gateway) # Fetch initial data so we have data when entities subscribe await signal_coordinator.async_config_entry_first_refresh() diff --git a/homeassistant/components/sms/coordinator.py b/homeassistant/components/sms/coordinator.py index 7bc691afedf65b..858fc30380534c 100644 --- a/homeassistant/components/sms/coordinator.py +++ b/homeassistant/components/sms/coordinator.py @@ -16,13 +16,14 @@ class SignalCoordinator(DataUpdateCoordinator): """Signal strength coordinator.""" - def __init__(self, hass, gateway): + def __init__(self, hass, entry, gateway): """Initialize signal strength coordinator.""" super().__init__( hass, _LOGGER, name="Device signal state", update_interval=timedelta(seconds=DEFAULT_SCAN_INTERVAL), + config_entry=entry, ) self._gateway = gateway @@ -38,13 +39,14 @@ async def _async_update_data(self): class NetworkCoordinator(DataUpdateCoordinator): """Network info coordinator.""" - def __init__(self, hass, gateway): + def __init__(self, hass, entry, gateway): """Initialize network info coordinator.""" super().__init__( hass, _LOGGER, name="Device network state", update_interval=timedelta(seconds=DEFAULT_SCAN_INTERVAL), + config_entry=entry, ) self._gateway = gateway From 9f867f268ce0072e48248e0a1fc8aad2d95e1f0f Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 4 Aug 2025 12:58:19 +0200 Subject: [PATCH 13/30] Pass config entry to Snoo coordinator (#149947) --- homeassistant/components/snoo/__init__.py | 2 +- homeassistant/components/snoo/coordinator.py | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/snoo/__init__.py b/homeassistant/components/snoo/__init__.py index 54834bf58ce247..20d94be7c033e0 100644 --- a/homeassistant/components/snoo/__init__.py +++ b/homeassistant/components/snoo/__init__.py @@ -46,7 +46,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: SnooConfigEntry) -> bool coordinators: dict[str, SnooCoordinator] = {} tasks = [] for device in devices: - coordinators[device.serialNumber] = SnooCoordinator(hass, device, snoo) + coordinators[device.serialNumber] = SnooCoordinator(hass, entry, device, snoo) tasks.append(coordinators[device.serialNumber].setup()) await asyncio.gather(*tasks) entry.runtime_data = coordinators diff --git a/homeassistant/components/snoo/coordinator.py b/homeassistant/components/snoo/coordinator.py index bc06d20955c310..8ce0db34621252 100644 --- a/homeassistant/components/snoo/coordinator.py +++ b/homeassistant/components/snoo/coordinator.py @@ -19,11 +19,18 @@ class SnooCoordinator(DataUpdateCoordinator[SnooData]): config_entry: SnooConfigEntry - def __init__(self, hass: HomeAssistant, device: SnooDevice, snoo: Snoo) -> None: + def __init__( + self, + hass: HomeAssistant, + entry: SnooConfigEntry, + device: SnooDevice, + snoo: Snoo, + ) -> None: """Set up Snoo Coordinator.""" super().__init__( hass, name=device.name, + config_entry=entry, logger=_LOGGER, ) self.device_unique_id = device.serialNumber From 594ce8f266902be504d7bd01328a4b070f1cd441 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 4 Aug 2025 12:58:46 +0200 Subject: [PATCH 14/30] Pass config entry to Smarttub coordinator (#149946) --- homeassistant/components/smarttub/controller.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/smarttub/controller.py b/homeassistant/components/smarttub/controller.py index 337959e0316797..095179d618a060 100644 --- a/homeassistant/components/smarttub/controller.py +++ b/homeassistant/components/smarttub/controller.py @@ -74,6 +74,7 @@ async def async_setup_entry(self, entry: SmartTubConfigEntry) -> bool: self._hass, _LOGGER, name=DOMAIN, + config_entry=entry, update_method=self.async_update_data, update_interval=timedelta(seconds=SCAN_INTERVAL), ) From a962777a2e04bf2adb17a7d80b449edee87f7a76 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 4 Aug 2025 13:14:50 +0200 Subject: [PATCH 15/30] Pass config entry to Meteo France coordinator (#149945) --- homeassistant/components/meteo_france/__init__.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/homeassistant/components/meteo_france/__init__.py b/homeassistant/components/meteo_france/__init__.py index 20e6c02f5d4055..94918ab4d4fe00 100644 --- a/homeassistant/components/meteo_france/__init__.py +++ b/homeassistant/components/meteo_france/__init__.py @@ -63,6 +63,7 @@ async def _async_update_data_alert() -> CurrentPhenomenons: hass, _LOGGER, name=f"Météo-France forecast for city {entry.title}", + config_entry=entry, update_method=_async_update_data_forecast_forecast, update_interval=SCAN_INTERVAL, ) @@ -80,6 +81,7 @@ async def _async_update_data_alert() -> CurrentPhenomenons: hass, _LOGGER, name=f"Météo-France rain for city {entry.title}", + config_entry=entry, update_method=_async_update_data_rain, update_interval=SCAN_INTERVAL_RAIN, ) @@ -103,6 +105,7 @@ async def _async_update_data_alert() -> CurrentPhenomenons: hass, _LOGGER, name=f"Météo-France alert for department {department}", + config_entry=entry, update_method=_async_update_data_alert, update_interval=SCAN_INTERVAL, ) From 39b651e07560fac1de222a34af40989fd447b736 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 4 Aug 2025 13:17:27 +0200 Subject: [PATCH 16/30] Pass config entry to Kraken coordinator (#149944) --- homeassistant/components/kraken/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/kraken/__init__.py b/homeassistant/components/kraken/__init__.py index c981f3fd438362..5c3158bddf2330 100644 --- a/homeassistant/components/kraken/__init__.py +++ b/homeassistant/components/kraken/__init__.py @@ -135,6 +135,7 @@ async def async_setup(self) -> None: self._hass, _LOGGER, name=DOMAIN, + config_entry=self._config_entry, update_method=self.async_update, update_interval=timedelta( seconds=self._config_entry.options[CONF_SCAN_INTERVAL] From 3d27d501b17192728a91f7746bfe0f2448c28ea4 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 4 Aug 2025 13:20:30 +0200 Subject: [PATCH 17/30] Pass config entry to Mill coordinator (#149942) --- homeassistant/components/mill/__init__.py | 1 + homeassistant/components/mill/coordinator.py | 2 ++ tests/components/mill/test_coordinator.py | 19 ++++++++++++++----- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/mill/__init__.py b/homeassistant/components/mill/__init__.py index 246ea7789162e1..ce258712090254 100644 --- a/homeassistant/components/mill/__init__.py +++ b/homeassistant/components/mill/__init__.py @@ -43,6 +43,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: historic_data_coordinator = MillHistoricDataUpdateCoordinator( hass, + entry, mill_data_connection=mill_data_connection, ) historic_data_coordinator.async_add_listener(lambda: None) diff --git a/homeassistant/components/mill/coordinator.py b/homeassistant/components/mill/coordinator.py index a701acb8ddbf16..ea1295376ae465 100644 --- a/homeassistant/components/mill/coordinator.py +++ b/homeassistant/components/mill/coordinator.py @@ -60,6 +60,7 @@ class MillHistoricDataUpdateCoordinator(DataUpdateCoordinator): def __init__( self, hass: HomeAssistant, + config_entry: ConfigEntry, *, mill_data_connection: Mill, ) -> None: @@ -70,6 +71,7 @@ def __init__( hass, _LOGGER, name="MillHistoricDataUpdateCoordinator", + config_entry=config_entry, ) async def _async_update_data(self): diff --git a/tests/components/mill/test_coordinator.py b/tests/components/mill/test_coordinator.py index a2a3bd57b65c5d..2e6e08016b7503 100644 --- a/tests/components/mill/test_coordinator.py +++ b/tests/components/mill/test_coordinator.py @@ -11,12 +11,15 @@ from homeassistant.core import HomeAssistant from homeassistant.util import dt as dt_util +from tests.common import MockConfigEntry from tests.components.recorder.common import async_wait_recording_done async def test_mill_historic_data(recorder_mock: Recorder, hass: HomeAssistant) -> None: """Test historic data from Mill.""" + entry = MockConfigEntry(domain=DOMAIN) + data = { dt_util.parse_datetime("2024-12-03T00:00:00+01:00"): 2, dt_util.parse_datetime("2024-12-03T01:00:00+01:00"): 3, @@ -31,7 +34,7 @@ async def test_mill_historic_data(recorder_mock: Recorder, hass: HomeAssistant) statistic_id = f"{DOMAIN}:energy_dev_id" coordinator = MillHistoricDataUpdateCoordinator( - hass, mill_data_connection=mill_data_connection + hass, entry, mill_data_connection=mill_data_connection ) await coordinator._async_update_data() await async_wait_recording_done(hass) @@ -96,6 +99,8 @@ async def test_mill_historic_data_no_heater( ) -> None: """Test historic data from Mill.""" + entry = MockConfigEntry(domain=DOMAIN) + data = { dt_util.parse_datetime("2024-12-03T00:00:00+01:00"): 2, dt_util.parse_datetime("2024-12-03T01:00:00+01:00"): 3, @@ -110,7 +115,7 @@ async def test_mill_historic_data_no_heater( statistic_id = f"{DOMAIN}:energy_dev_id" coordinator = MillHistoricDataUpdateCoordinator( - hass, mill_data_connection=mill_data_connection + hass, entry, mill_data_connection=mill_data_connection ) await coordinator._async_update_data() await async_wait_recording_done(hass) @@ -133,6 +138,8 @@ async def test_mill_historic_data_no_data( ) -> None: """Test historic data from Mill.""" + entry = MockConfigEntry(domain=DOMAIN) + data = { dt_util.parse_datetime("2024-12-03T00:00:00+01:00"): 2, dt_util.parse_datetime("2024-12-03T01:00:00+01:00"): 3, @@ -145,7 +152,7 @@ async def test_mill_historic_data_no_data( mill_data_connection.fetch_historic_energy_usage = AsyncMock(return_value=data) coordinator = MillHistoricDataUpdateCoordinator( - hass, mill_data_connection=mill_data_connection + hass, entry, mill_data_connection=mill_data_connection ) await coordinator._async_update_data() await async_wait_recording_done(hass) @@ -168,7 +175,7 @@ async def test_mill_historic_data_no_data( mill_data_connection.fetch_historic_energy_usage = AsyncMock(return_value=None) coordinator = MillHistoricDataUpdateCoordinator( - hass, mill_data_connection=mill_data_connection + hass, entry, mill_data_connection=mill_data_connection ) await coordinator._async_update_data() await async_wait_recording_done(hass) @@ -192,6 +199,8 @@ async def test_mill_historic_data_invalid_data( ) -> None: """Test historic data from Mill.""" + entry = MockConfigEntry(domain=DOMAIN) + data = { dt_util.parse_datetime("2024-12-03T00:00:00+01:00"): None, dt_util.parse_datetime("2024-12-03T01:00:00+01:00"): 3, @@ -206,7 +215,7 @@ async def test_mill_historic_data_invalid_data( statistic_id = f"{DOMAIN}:energy_dev_id" coordinator = MillHistoricDataUpdateCoordinator( - hass, mill_data_connection=mill_data_connection + hass, entry, mill_data_connection=mill_data_connection ) await coordinator._async_update_data() await async_wait_recording_done(hass) From 33eaca24d6d4cda2c7198b48e875a99be46f94ed Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 4 Aug 2025 13:21:29 +0200 Subject: [PATCH 18/30] Pass config entry to Simplisafe coordinator (#149943) --- homeassistant/components/simplisafe/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/simplisafe/__init__.py b/homeassistant/components/simplisafe/__init__.py index 8a75baa69c6098..67bf94c61ae5ea 100644 --- a/homeassistant/components/simplisafe/__init__.py +++ b/homeassistant/components/simplisafe/__init__.py @@ -573,6 +573,7 @@ async def async_websocket_disconnect_listener(_: Event) -> None: self._hass, LOGGER, name=self.entry.title, + config_entry=self.entry, update_interval=DEFAULT_SCAN_INTERVAL, update_method=self.async_update, ) From 7a6aaf667bf3b80b1e53299ab6f719bef97a5779 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 4 Aug 2025 13:27:10 +0200 Subject: [PATCH 19/30] Pass config entry to hue coordinator (#149941) --- homeassistant/components/hue/v1/light.py | 2 ++ homeassistant/components/hue/v1/sensor_base.py | 1 + 2 files changed, 3 insertions(+) diff --git a/homeassistant/components/hue/v1/light.py b/homeassistant/components/hue/v1/light.py index b725138229660c..36dfdd423efff2 100644 --- a/homeassistant/components/hue/v1/light.py +++ b/homeassistant/components/hue/v1/light.py @@ -163,6 +163,7 @@ async def async_setup_entry( name="light", update_method=partial(async_safe_fetch, bridge, bridge.api.lights.update), update_interval=SCAN_INTERVAL, + config_entry=config_entry, request_refresh_debouncer=Debouncer( bridge.hass, LOGGER, cooldown=REQUEST_REFRESH_DELAY, immediate=True ), @@ -197,6 +198,7 @@ async def async_setup_entry( name="group", update_method=partial(async_safe_fetch, bridge, bridge.api.groups.update), update_interval=SCAN_INTERVAL, + config_entry=config_entry, request_refresh_debouncer=Debouncer( bridge.hass, LOGGER, cooldown=REQUEST_REFRESH_DELAY, immediate=True ), diff --git a/homeassistant/components/hue/v1/sensor_base.py b/homeassistant/components/hue/v1/sensor_base.py index 393069b0c7c80d..fb8f3c572c1227 100644 --- a/homeassistant/components/hue/v1/sensor_base.py +++ b/homeassistant/components/hue/v1/sensor_base.py @@ -53,6 +53,7 @@ def __init__(self, bridge): LOGGER, name="sensor", update_method=self.async_update_data, + config_entry=bridge.config_entry, update_interval=self.SCAN_INTERVAL, request_refresh_debouncer=debounce.Debouncer( bridge.hass, LOGGER, cooldown=REQUEST_REFRESH_DELAY, immediate=True From 312e5903609e8292c7b25eee0a0f9f24e581d06e Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 4 Aug 2025 13:27:51 +0200 Subject: [PATCH 20/30] Pass config entry to Broadlink coordinator (#149949) --- homeassistant/components/broadlink/updater.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/broadlink/updater.py b/homeassistant/components/broadlink/updater.py index 7c1644fff540ca..8fdbb5054a8c75 100644 --- a/homeassistant/components/broadlink/updater.py +++ b/homeassistant/components/broadlink/updater.py @@ -64,6 +64,7 @@ def __init__(self, device: BroadlinkDevice[_ApiT]) -> None: device.hass, _LOGGER, name=f"{device.name} ({device.api.model} at {device.api.host[0]})", + config_entry=device.config, update_method=self.async_update, update_interval=self.SCAN_INTERVAL, ) From 0bdf6757c49a2544c22c344ba347d62b8d0c140f Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 4 Aug 2025 13:28:59 +0200 Subject: [PATCH 21/30] Pass config entry to Remote Calendar coordinator (#149958) --- homeassistant/components/remote_calendar/coordinator.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/remote_calendar/coordinator.py b/homeassistant/components/remote_calendar/coordinator.py index 26876b532244a7..7a7abe37b8955a 100644 --- a/homeassistant/components/remote_calendar/coordinator.py +++ b/homeassistant/components/remote_calendar/coordinator.py @@ -39,6 +39,7 @@ def __init__( _LOGGER, name=f"{DOMAIN}_{config_entry.title}", update_interval=SCAN_INTERVAL, + config_entry=config_entry, always_update=True, ) self._client = get_async_client(hass) From 46cfdddc80e505c25955858c7c3a77e39132a38d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Mon, 4 Aug 2025 12:29:11 +0100 Subject: [PATCH 22/30] Move to the new handler for migrate_paypal_agreement (#149934) --- homeassistant/components/cloud/subscription.py | 16 +++++++++------- tests/components/cloud/conftest.py | 1 + tests/components/cloud/test_repairs.py | 9 ++++++++- tests/components/cloud/test_subscription.py | 6 ++---- 4 files changed, 20 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/cloud/subscription.py b/homeassistant/components/cloud/subscription.py index 9ee154dbff400c..c1b8fc095c3dda 100644 --- a/homeassistant/components/cloud/subscription.py +++ b/homeassistant/components/cloud/subscription.py @@ -4,11 +4,13 @@ import asyncio import logging -from typing import Any -from aiohttp.client_exceptions import ClientError -from hass_nabucasa import Cloud, cloud_api -from hass_nabucasa.payments_api import PaymentsApiError, SubscriptionInfo +from hass_nabucasa import ( + Cloud, + MigratePaypalAgreementInfo, + PaymentsApiError, + SubscriptionInfo, +) from .client import CloudClient from .const import REQUEST_TIMEOUT @@ -29,17 +31,17 @@ async def async_subscription_info(cloud: Cloud[CloudClient]) -> SubscriptionInfo async def async_migrate_paypal_agreement( cloud: Cloud[CloudClient], -) -> dict[str, Any] | None: +) -> MigratePaypalAgreementInfo | None: """Migrate a paypal agreement from legacy.""" try: async with asyncio.timeout(REQUEST_TIMEOUT): - return await cloud_api.async_migrate_paypal_agreement(cloud) + return await cloud.payments.migrate_paypal_agreement() except TimeoutError: _LOGGER.error( "A timeout of %s was reached while trying to start agreement migration", REQUEST_TIMEOUT, ) - except ClientError as exception: + except PaymentsApiError as exception: _LOGGER.error("Failed to start agreement migration - %s", exception) return None diff --git a/tests/components/cloud/conftest.py b/tests/components/cloud/conftest.py index e63af0ced09143..a4625fcce92f61 100644 --- a/tests/components/cloud/conftest.py +++ b/tests/components/cloud/conftest.py @@ -74,6 +74,7 @@ async def cloud_fixture() -> AsyncGenerator[MagicMock]: mock_cloud.payments = MagicMock( spec=payments_api.PaymentsApi, subscription_info=AsyncMock(), + migrate_paypal_agreement=AsyncMock(), ) mock_cloud.ice_servers = MagicMock( spec=IceServers, diff --git a/tests/components/cloud/test_repairs.py b/tests/components/cloud/test_repairs.py index bb3c874c0772f3..0377ee81dba66f 100644 --- a/tests/components/cloud/test_repairs.py +++ b/tests/components/cloud/test_repairs.py @@ -4,6 +4,7 @@ from http import HTTPStatus from unittest.mock import patch +from hass_nabucasa.payments_api import PaymentsApiError import pytest from homeassistant.components.cloud.const import DOMAIN @@ -210,7 +211,13 @@ async def test_legacy_subscription_repair_flow_timeout( "preview": None, } - with patch("homeassistant.components.cloud.repairs.MAX_RETRIES", new=0): + with ( + patch("homeassistant.components.cloud.repairs.MAX_RETRIES", new=0), + patch( + "hass_nabucasa.payments_api.PaymentsApi.migrate_paypal_agreement", + side_effect=PaymentsApiError("some error", status=403), + ), + ): resp = await client.post(f"/api/repairs/issues/fix/{flow_id}") assert resp.status == HTTPStatus.OK data = await resp.json() diff --git a/tests/components/cloud/test_subscription.py b/tests/components/cloud/test_subscription.py index c34ca1bc871f0c..ba45e6bca57daf 100644 --- a/tests/components/cloud/test_subscription.py +++ b/tests/components/cloud/test_subscription.py @@ -25,6 +25,7 @@ async def mocked_cloud_object(hass: HomeAssistant) -> Cloud: payments=Mock( spec=payments_api.PaymentsApi, subscription_info=AsyncMock(), + migrate_paypal_agreement=AsyncMock(), ), ) @@ -52,10 +53,7 @@ async def test_migrate_paypal_agreement_with_timeout_error( mocked_cloud: Cloud, ) -> None: """Test that we handle timeout error.""" - aioclient_mock.post( - "https://accounts.nabucasa.com/payments/migrate_paypal_agreement", - exc=TimeoutError(), - ) + mocked_cloud.payments.migrate_paypal_agreement.side_effect = TimeoutError() assert await async_migrate_paypal_agreement(mocked_cloud) is None assert ( From e2bc73f153b9a00658cd9e2652a731b86c2198a3 Mon Sep 17 00:00:00 2001 From: Petro31 <35082313+Petro31@users.noreply.github.com> Date: Mon, 4 Aug 2025 07:35:13 -0400 Subject: [PATCH 23/30] Fix optimistic covers (#149962) --- homeassistant/components/template/cover.py | 1 + homeassistant/components/template/entity.py | 12 +++++++++--- tests/components/template/test_cover.py | 17 +++++++++++++++++ 3 files changed, 27 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/template/cover.py b/homeassistant/components/template/cover.py index caac8cf5a1de57..44981fcb08fd4a 100644 --- a/homeassistant/components/template/cover.py +++ b/homeassistant/components/template/cover.py @@ -216,6 +216,7 @@ class AbstractTemplateCover(AbstractTemplateEntity, CoverEntity): _entity_id_format = ENTITY_ID_FORMAT _optimistic_entity = True + _extra_optimistic_options = (CONF_POSITION,) # The super init is not called because TemplateEntity and TriggerEntity will call AbstractTemplateEntity.__init__. # This ensures that the __init__ on AbstractTemplateEntity is not called twice. diff --git a/homeassistant/components/template/entity.py b/homeassistant/components/template/entity.py index e9a630594d71a5..03a93f50ec3c8f 100644 --- a/homeassistant/components/template/entity.py +++ b/homeassistant/components/template/entity.py @@ -20,6 +20,7 @@ class AbstractTemplateEntity(Entity): _entity_id_format: str _optimistic_entity: bool = False + _extra_optimistic_options: tuple[str, ...] | None = None _template: Template | None = None def __init__( @@ -35,9 +36,14 @@ def __init__( if self._optimistic_entity: self._template = config.get(CONF_STATE) - self._attr_assumed_state = self._template is None or config.get( - CONF_OPTIMISTIC, False - ) + optimistic = self._template is None + if self._extra_optimistic_options: + optimistic = optimistic and all( + config.get(option) is None + for option in self._extra_optimistic_options + ) + + self._attr_assumed_state = optimistic or config.get(CONF_OPTIMISTIC, False) if (object_id := config.get(CONF_OBJECT_ID)) is not None: self.entity_id = async_generate_entity_id( diff --git a/tests/components/template/test_cover.py b/tests/components/template/test_cover.py index dc3428330b00e4..692567c7aa897a 100644 --- a/tests/components/template/test_cover.py +++ b/tests/components/template/test_cover.py @@ -239,6 +239,7 @@ async def setup_position_cover( { TEST_OBJECT_ID: { **COVER_ACTIONS, + "set_cover_position": SET_COVER_POSITION, "position_template": position_template, } }, @@ -249,6 +250,7 @@ async def setup_position_cover( count, { **NAMED_COVER_ACTIONS, + "set_cover_position": SET_COVER_POSITION, "position": position_template, }, ) @@ -258,6 +260,7 @@ async def setup_position_cover( count, { **NAMED_COVER_ACTIONS, + "set_cover_position": SET_COVER_POSITION, "position": position_template, }, ) @@ -565,6 +568,7 @@ async def test_template_position( position: int | None, expected: str, caplog: pytest.LogCaptureFixture, + calls: list[ServiceCall], ) -> None: """Test the position_template attribute.""" hass.states.async_set(TEST_STATE_ENTITY_ID, CoverState.OPEN) @@ -580,6 +584,19 @@ async def test_template_position( assert state.state == expected assert "ValueError" not in caplog.text + # Test to make sure optimistic is not set with only a position template. + await hass.services.async_call( + COVER_DOMAIN, + SERVICE_SET_COVER_POSITION, + {ATTR_ENTITY_ID: TEST_ENTITY_ID, "position": 10}, + blocking=True, + ) + await hass.async_block_till_done() + + state = hass.states.get(TEST_ENTITY_ID) + assert state.attributes.get("current_position") == position + assert state.state == expected + @pytest.mark.parametrize("count", [1]) @pytest.mark.parametrize( From 1632e0aef69b2e6962af5084b6ea00ea59b9366b Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Mon, 4 Aug 2025 13:36:12 +0200 Subject: [PATCH 24/30] Direct migrations with Z-Wave JS UI to docs (#149966) --- .../components/zwave_js/config_flow.py | 18 ++++++++++++++++-- homeassistant/components/zwave_js/strings.json | 2 +- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/zwave_js/config_flow.py b/homeassistant/components/zwave_js/config_flow.py index 308e6c9cc1af48..6121bd0050891b 100644 --- a/homeassistant/components/zwave_js/config_flow.py +++ b/homeassistant/components/zwave_js/config_flow.py @@ -93,6 +93,10 @@ NETWORK_TYPE_NEW = "new" NETWORK_TYPE_EXISTING = "existing" +ZWAVE_JS_UI_MIGRATION_INSTRUCTIONS = ( + "https://www.home-assistant.io/integrations/zwave_js/" + "#how-to-migrate-from-one-adapter-to-a-new-adapter-using-z-wave-js-ui" +) def get_manual_schema(user_input: dict[str, Any]) -> vol.Schema: @@ -446,7 +450,12 @@ async def async_step_usb(self, discovery_info: UsbServiceInfo) -> ConfigFlowResu None, ) if not self._reconfigure_config_entry: - return self.async_abort(reason="addon_required") + return self.async_abort( + reason="addon_required", + description_placeholders={ + "zwave_js_ui_migration": ZWAVE_JS_UI_MIGRATION_INSTRUCTIONS, + }, + ) vid = discovery_info.vid pid = discovery_info.pid @@ -890,7 +899,12 @@ async def async_step_intent_migrate( config_entry = self._reconfigure_config_entry assert config_entry is not None if not self._usb_discovery and not config_entry.data.get(CONF_USE_ADDON): - return self.async_abort(reason="addon_required") + return self.async_abort( + reason="addon_required", + description_placeholders={ + "zwave_js_ui_migration": ZWAVE_JS_UI_MIGRATION_INSTRUCTIONS, + }, + ) try: driver = self._get_driver() diff --git a/homeassistant/components/zwave_js/strings.json b/homeassistant/components/zwave_js/strings.json index 0288fbd7131724..8ac356a40b0436 100644 --- a/homeassistant/components/zwave_js/strings.json +++ b/homeassistant/components/zwave_js/strings.json @@ -4,7 +4,7 @@ "addon_get_discovery_info_failed": "Failed to get Z-Wave add-on discovery info.", "addon_info_failed": "Failed to get Z-Wave add-on info.", "addon_install_failed": "Failed to install the Z-Wave add-on.", - "addon_required": "The Z-Wave migration flow requires the integration to be configured using the Z-Wave Supervisor add-on. You can still use the Backup and Restore buttons to migrate your network manually.", + "addon_required": "The Z-Wave migration flow requires the integration to be configured using the Z-Wave Supervisor add-on. If you are using Z-Wave JS UI, please follow our [migration instructions]({zwave_js_ui_migration}).", "addon_set_config_failed": "Failed to set Z-Wave configuration.", "addon_start_failed": "Failed to start the Z-Wave add-on.", "addon_stop_failed": "Failed to stop the Z-Wave add-on.", From 822e1ffc8d8b63d3712e6a85905ba99a764ee403 Mon Sep 17 00:00:00 2001 From: hanwg Date: Mon, 4 Aug 2025 20:27:15 +0800 Subject: [PATCH 25/30] Minor UI improvements for Telegram bot actions (#149889) --- .../components/telegram_bot/services.yaml | 57 +++++++++++++------ 1 file changed, 40 insertions(+), 17 deletions(-) diff --git a/homeassistant/components/telegram_bot/services.yaml b/homeassistant/components/telegram_bot/services.yaml index ce7ebea2b665cf..0ebe7988642f79 100644 --- a/homeassistant/components/telegram_bot/services.yaml +++ b/homeassistant/components/telegram_bot/services.yaml @@ -18,7 +18,8 @@ send_message: target: example: "[12345, 67890] or 12345" selector: - object: + text: + multiple: true parse_mode: selector: select: @@ -43,7 +44,8 @@ send_message: keyboard: example: '["/command1, /command2", "/command3"]' selector: - object: + text: + multiple: true inline_keyboard: example: '["/button1, /button2", "/button3"] or ["Text button1:/button1, Text @@ -98,10 +100,12 @@ send_photo: example: myuser_pwd selector: text: + type: password target: example: "[12345, 67890] or 12345" selector: - object: + text: + multiple: true parse_mode: selector: select: @@ -126,7 +130,8 @@ send_photo: keyboard: example: '["/command1, /command2", "/command3"]' selector: - object: + text: + multiple: true inline_keyboard: example: '["/button1, /button2", "/button3"] or [[["Text button1", "/button1"], @@ -180,10 +185,12 @@ send_sticker: example: myuser_pwd selector: text: + type: password target: example: "[12345, 67890] or 12345" selector: - object: + text: + multiple: true disable_notification: selector: boolean: @@ -199,7 +206,8 @@ send_sticker: keyboard: example: '["/command1, /command2", "/command3"]' selector: - object: + text: + multiple: true inline_keyboard: example: '["/button1, /button2", "/button3"] or [[["Text button1", "/button1"], @@ -253,10 +261,12 @@ send_animation: example: myuser_pwd selector: text: + type: password target: example: "[12345, 67890] or 12345" selector: - object: + text: + multiple: true parse_mode: selector: select: @@ -281,7 +291,8 @@ send_animation: keyboard: example: '["/command1, /command2", "/command3"]' selector: - object: + text: + multiple: true inline_keyboard: example: '["/button1, /button2", "/button3"] or [[["Text button1", "/button1"], @@ -335,10 +346,12 @@ send_video: example: myuser_pwd selector: text: + type: password target: example: "[12345, 67890] or 12345" selector: - object: + text: + multiple: true parse_mode: selector: select: @@ -363,7 +376,8 @@ send_video: keyboard: example: '["/command1, /command2", "/command3"]' selector: - object: + text: + multiple: true inline_keyboard: example: '["/button1, /button2", "/button3"] or [[["Text button1", "/button1"], @@ -417,10 +431,12 @@ send_voice: example: myuser_pwd selector: text: + type: password target: example: "[12345, 67890] or 12345" selector: - object: + text: + multiple: true disable_notification: selector: boolean: @@ -436,7 +452,8 @@ send_voice: keyboard: example: '["/command1, /command2", "/command3"]' selector: - object: + text: + multiple: true inline_keyboard: example: '["/button1, /button2", "/button3"] or [[["Text button1", "/button1"], @@ -490,10 +507,12 @@ send_document: example: myuser_pwd selector: text: + type: password target: example: "[12345, 67890] or 12345" selector: - object: + text: + multiple: true parse_mode: selector: select: @@ -518,7 +537,8 @@ send_document: keyboard: example: '["/command1, /command2", "/command3"]' selector: - object: + text: + multiple: true inline_keyboard: example: '["/button1, /button2", "/button3"] or [[["Text button1", "/button1"], @@ -563,7 +583,8 @@ send_location: target: example: "[12345, 67890] or 12345" selector: - object: + text: + multiple: true disable_notification: selector: boolean: @@ -576,7 +597,8 @@ send_location: keyboard: example: '["/command1, /command2", "/command3"]' selector: - object: + text: + multiple: true inline_keyboard: example: '["/button1, /button2", "/button3"] or [[["Text button1", "/button1"], @@ -605,7 +627,8 @@ send_poll: target: example: "[12345, 67890] or 12345" selector: - object: + text: + multiple: true question: required: true selector: From b76f47cd9ff3d14c84be44ac6cce74a5776f3675 Mon Sep 17 00:00:00 2001 From: hanwg Date: Mon, 4 Aug 2025 20:32:48 +0800 Subject: [PATCH 26/30] Add bot details to Telegram bot events (#148638) --- homeassistant/components/telegram_bot/bot.py | 20 +++++++++++++++++- .../components/telegram_bot/polling.py | 2 +- .../components/telegram_bot/webhooks.py | 2 +- tests/components/telegram_bot/conftest.py | 2 +- .../telegram_bot/test_telegram_bot.py | 21 ++++++++++++++++++- 5 files changed, 42 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/telegram_bot/bot.py b/homeassistant/components/telegram_bot/bot.py index c57648c9551f55..3145badbed74f5 100644 --- a/homeassistant/components/telegram_bot/bot.py +++ b/homeassistant/components/telegram_bot/bot.py @@ -101,13 +101,26 @@ type TelegramBotConfigEntry = ConfigEntry[TelegramNotificationService] +def _get_bot_info(bot: Bot, config_entry: ConfigEntry) -> dict[str, Any]: + return { + "config_entry_id": config_entry.entry_id, + "id": bot.id, + "first_name": bot.first_name, + "last_name": bot.last_name, + "username": bot.username, + } + + class BaseTelegramBot: """The base class for the telegram bot.""" - def __init__(self, hass: HomeAssistant, config: TelegramBotConfigEntry) -> None: + def __init__( + self, hass: HomeAssistant, config: TelegramBotConfigEntry, bot: Bot + ) -> None: """Initialize the bot base class.""" self.hass = hass self.config = config + self._bot = bot @abstractmethod async def shutdown(self) -> None: @@ -134,6 +147,8 @@ async def handle_update(self, update: Update, context: CallbackContext) -> bool: _LOGGER.warning("Unhandled update: %s", update) return True + event_data["bot"] = _get_bot_info(self._bot, self.config) + event_context = Context() _LOGGER.debug("Firing event %s: %s", event_type, event_data) @@ -442,6 +457,9 @@ async def _send_msg( event_data[ATTR_MESSAGE_THREAD_ID] = kwargs_msg[ ATTR_MESSAGE_THREAD_ID ] + + event_data["bot"] = _get_bot_info(self.bot, self.config) + self.hass.bus.async_fire( EVENT_TELEGRAM_SENT, event_data, context=context ) diff --git a/homeassistant/components/telegram_bot/polling.py b/homeassistant/components/telegram_bot/polling.py index 6c38a0e53b8d2d..b8640c5c00520d 100644 --- a/homeassistant/components/telegram_bot/polling.py +++ b/homeassistant/components/telegram_bot/polling.py @@ -54,7 +54,7 @@ def __init__( self, hass: HomeAssistant, bot: Bot, config: TelegramBotConfigEntry ) -> None: """Create Application to poll for updates.""" - super().__init__(hass, config) + super().__init__(hass, config, bot) self.bot = bot self.application = ApplicationBuilder().bot(self.bot).build() self.application.add_handler(TypeHandler(Update, self.handle_update)) diff --git a/homeassistant/components/telegram_bot/webhooks.py b/homeassistant/components/telegram_bot/webhooks.py index 29c3305858bb80..61843e6ffbf310 100644 --- a/homeassistant/components/telegram_bot/webhooks.py +++ b/homeassistant/components/telegram_bot/webhooks.py @@ -77,7 +77,7 @@ def __init__( # Dumb Application that just gets our updates to our handler callback (self.handle_update) self.application = ApplicationBuilder().bot(bot).updater(None).build() self.application.add_handler(TypeHandler(Update, self.handle_update)) - super().__init__(hass, config) + super().__init__(hass, config, bot) self.base_url = config.data.get(CONF_URL) or get_url( hass, require_ssl=True, allow_internal=False diff --git a/tests/components/telegram_bot/conftest.py b/tests/components/telegram_bot/conftest.py index 66c3c43ea866ab..489cb034ac23d3 100644 --- a/tests/components/telegram_bot/conftest.py +++ b/tests/components/telegram_bot/conftest.py @@ -96,7 +96,7 @@ def mock_external_calls() -> Generator[None]: max_reaction_count=100, accent_color_id=AccentColor.COLOR_000, ) - test_user = User(123456, "Testbot", True) + test_user = User(123456, "Testbot", True, "mock last name", "mock username") message = Message( message_id=12345, date=datetime.now(), diff --git a/tests/components/telegram_bot/test_telegram_bot.py b/tests/components/telegram_bot/test_telegram_bot.py index 80b9859ceabdd6..eec2bd5ecf7e1b 100644 --- a/tests/components/telegram_bot/test_telegram_bot.py +++ b/tests/components/telegram_bot/test_telegram_bot.py @@ -174,6 +174,15 @@ async def test_send_message( assert len(events) == 1 assert events[0].context == context + config_entry = hass.config_entries.async_entry_for_domain_unique_id( + DOMAIN, "1234567890:ABC" + ) + assert events[0].data["bot"]["config_entry_id"] == config_entry.entry_id + assert events[0].data["bot"]["id"] == 123456 + assert events[0].data["bot"]["first_name"] == "Testbot" + assert events[0].data["bot"]["last_name"] == "mock last name" + assert events[0].data["bot"]["username"] == "mock username" + assert len(response["chats"]) == 1 assert (response["chats"][0]["message_id"]) == 12345 @@ -479,6 +488,16 @@ async def test_polling_platform_message_text_update( assert len(events) == 1 assert events[0].data["text"] == update_message_text["message"]["text"] + + config_entry = hass.config_entries.async_entry_for_domain_unique_id( + DOMAIN, "1234567890:ABC" + ) + assert events[0].data["bot"]["config_entry_id"] == config_entry.entry_id + assert events[0].data["bot"]["id"] == 123456 + assert events[0].data["bot"]["first_name"] == "Testbot" + assert events[0].data["bot"]["last_name"] == "mock last name" + assert events[0].data["bot"]["username"] == "mock username" + assert isinstance(events[0].context, Context) @@ -752,7 +771,7 @@ async def test_send_message_no_chat_id_error( ) assert err.value.translation_key == "missing_allowed_chat_ids" - assert err.value.translation_placeholders["bot_name"] == "Testbot" + assert err.value.translation_placeholders["bot_name"] == "Testbot mock last name" async def test_send_message_config_entry_error( From 88c9d5dbe3804474f1fb727614a21af44a7df35f Mon Sep 17 00:00:00 2001 From: Willem-Jan van Rootselaar Date: Mon, 4 Aug 2025 15:35:41 +0200 Subject: [PATCH 27/30] Fix bsblan reauthentication (#149926) --- .../components/bsblan/config_flow.py | 32 ++-- tests/components/bsblan/test_config_flow.py | 158 ++++++++++++++++++ 2 files changed, 170 insertions(+), 20 deletions(-) diff --git a/homeassistant/components/bsblan/config_flow.py b/homeassistant/components/bsblan/config_flow.py index 1491322ae138c8..5f4f67a114af11 100644 --- a/homeassistant/components/bsblan/config_flow.py +++ b/homeassistant/components/bsblan/config_flow.py @@ -211,16 +211,16 @@ async def async_step_reauth_confirm( ), ) - # Use existing host and port, update auth credentials - self.host = existing_entry.data[CONF_HOST] - self.port = existing_entry.data[CONF_PORT] - self.passkey = user_input.get(CONF_PASSKEY) or existing_entry.data.get( - CONF_PASSKEY - ) - self.username = user_input.get(CONF_USERNAME) or existing_entry.data.get( - CONF_USERNAME - ) - self.password = user_input.get(CONF_PASSWORD) + # Combine existing data with the user's new input for validation. + # This correctly handles adding, changing, and clearing credentials. + config_data = existing_entry.data.copy() + config_data.update(user_input) + + self.host = config_data[CONF_HOST] + self.port = config_data[CONF_PORT] + self.passkey = config_data.get(CONF_PASSKEY) + self.username = config_data.get(CONF_USERNAME) + self.password = config_data.get(CONF_PASSWORD) try: await self._get_bsblan_info(raise_on_progress=False, is_reauth=True) @@ -267,17 +267,9 @@ async def async_step_reauth_confirm( errors={"base": "cannot_connect"}, ) - # Update the config entry with new auth data - data_updates = {} - if self.passkey is not None: - data_updates[CONF_PASSKEY] = self.passkey - if self.username is not None: - data_updates[CONF_USERNAME] = self.username - if self.password is not None: - data_updates[CONF_PASSWORD] = self.password - + # Update only the fields that were provided by the user return self.async_update_reload_and_abort( - existing_entry, data_updates=data_updates, reason="reauth_successful" + existing_entry, data_updates=user_input, reason="reauth_successful" ) @callback diff --git a/tests/components/bsblan/test_config_flow.py b/tests/components/bsblan/test_config_flow.py index 3ca0de5b78f9c7..a06131f7216c99 100644 --- a/tests/components/bsblan/test_config_flow.py +++ b/tests/components/bsblan/test_config_flow.py @@ -866,6 +866,164 @@ async def test_reauth_flow_partial_credentials_update( assert mock_config_entry.data[CONF_PORT] == 80 +async def test_reauth_flow_preserves_non_credential_fields( + hass: HomeAssistant, + mock_bsblan: MagicMock, +) -> None: + """Test reauth flow preserves non-credential fields using data_updates.""" + # Create a config entry with additional custom fields that should be preserved + entry = MockConfigEntry( + domain=DOMAIN, + data={ + CONF_HOST: "127.0.0.1", + CONF_PORT: 80, + CONF_PASSKEY: "old_key", + CONF_USERNAME: "old_user", + CONF_PASSWORD: "old_pass", + # Add some custom fields that should be preserved + "custom_field": "should_be_preserved", + "another_field": 42, + }, + unique_id="00:80:41:19:69:90", + ) + entry.add_to_hass(hass) + + # Start reauth flow + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={ + "source": SOURCE_REAUTH, + "entry_id": entry.entry_id, + }, + ) + + # Submit with only new credentials + result = await _configure_flow( + hass, + result["flow_id"], + { + CONF_PASSKEY: "new_key", + CONF_USERNAME: "new_user", + CONF_PASSWORD: "new_pass", + }, + ) + + _assert_abort_result(result, "reauth_successful") + + # Verify that only the provided fields were updated, others preserved + assert entry.data[CONF_PASSKEY] == "new_key" # Updated + assert entry.data[CONF_USERNAME] == "new_user" # Updated + assert entry.data[CONF_PASSWORD] == "new_pass" # Updated + + # These fields should remain unchanged (preserved by data_updates) + assert entry.data[CONF_HOST] == "127.0.0.1" + assert entry.data[CONF_PORT] == 80 + assert entry.data["custom_field"] == "should_be_preserved" + assert entry.data["another_field"] == 42 + + +async def test_reauth_flow_clears_credentials_with_empty_strings( + hass: HomeAssistant, + mock_bsblan: MagicMock, +) -> None: + """Test reauth flow can clear credentials by providing empty strings.""" + # Create a config entry with existing credentials + entry = MockConfigEntry( + domain=DOMAIN, + data={ + CONF_HOST: "127.0.0.1", + CONF_PORT: 80, + CONF_PASSKEY: "existing_key", + CONF_USERNAME: "existing_user", + CONF_PASSWORD: "existing_pass", + }, + unique_id="00:80:41:19:69:90", + ) + entry.add_to_hass(hass) + + # Start reauth flow + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={ + "source": SOURCE_REAUTH, + "entry_id": entry.entry_id, + }, + ) + + # Submit with empty strings to clear credentials + result = await _configure_flow( + hass, + result["flow_id"], + { + CONF_PASSKEY: "", # Clear passkey + CONF_USERNAME: "", # Clear username + CONF_PASSWORD: "", # Clear password + }, + ) + + _assert_abort_result(result, "reauth_successful") + + # Verify that credentials were cleared (set to empty strings) + assert entry.data[CONF_PASSKEY] == "" + assert entry.data[CONF_USERNAME] == "" + assert entry.data[CONF_PASSWORD] == "" + + # Host and port should remain unchanged + assert entry.data[CONF_HOST] == "127.0.0.1" + assert entry.data[CONF_PORT] == 80 + + +async def test_reauth_flow_partial_clear_credentials( + hass: HomeAssistant, + mock_bsblan: MagicMock, +) -> None: + """Test reauth flow can partially clear some credentials while updating others.""" + # Create a config entry with existing credentials + entry = MockConfigEntry( + domain=DOMAIN, + data={ + CONF_HOST: "127.0.0.1", + CONF_PORT: 80, + CONF_PASSKEY: "existing_key", + CONF_USERNAME: "existing_user", + CONF_PASSWORD: "existing_pass", + }, + unique_id="00:80:41:19:69:90", + ) + entry.add_to_hass(hass) + + # Start reauth flow + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={ + "source": SOURCE_REAUTH, + "entry_id": entry.entry_id, + }, + ) + + # Submit with mix of clearing and updating credentials + result = await _configure_flow( + hass, + result["flow_id"], + { + CONF_PASSKEY: "", # Clear passkey + CONF_USERNAME: "new_user", # Update username + CONF_PASSWORD: "", # Clear password + }, + ) + + _assert_abort_result(result, "reauth_successful") + + # Verify mixed update: some cleared, some updated, some preserved + assert entry.data[CONF_PASSKEY] == "" # Cleared + assert entry.data[CONF_USERNAME] == "new_user" # Updated + assert entry.data[CONF_PASSWORD] == "" # Cleared + + # Host and port should remain unchanged + assert entry.data[CONF_HOST] == "127.0.0.1" + assert entry.data[CONF_PORT] == 80 + + async def test_zeroconf_discovery_auth_error_during_confirm( hass: HomeAssistant, mock_bsblan: MagicMock, From ae48179e959c8cc42dce3bb31d32136d34800e1e Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Mon, 4 Aug 2025 15:58:57 +0200 Subject: [PATCH 28/30] Bump zwave-js-server-python to 0.67.1 (#149972) --- homeassistant/components/zwave_js/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/zwave_js/manifest.json b/homeassistant/components/zwave_js/manifest.json index 2cad8df3805aea..153e8e6a7fe664 100644 --- a/homeassistant/components/zwave_js/manifest.json +++ b/homeassistant/components/zwave_js/manifest.json @@ -9,7 +9,7 @@ "integration_type": "hub", "iot_class": "local_push", "loggers": ["zwave_js_server"], - "requirements": ["pyserial==3.5", "zwave-js-server-python==0.67.0"], + "requirements": ["pyserial==3.5", "zwave-js-server-python==0.67.1"], "usb": [ { "vid": "0658", diff --git a/requirements_all.txt b/requirements_all.txt index 8dcc7478d7c21c..1c7280547c7141 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -3215,7 +3215,7 @@ ziggo-mediabox-xl==1.1.0 zm-py==0.5.4 # homeassistant.components.zwave_js -zwave-js-server-python==0.67.0 +zwave-js-server-python==0.67.1 # homeassistant.components.zwave_me zwave-me-ws==0.4.3 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index ce01dbd5a51b40..4aa7a5063e6084 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -2650,7 +2650,7 @@ zeversolar==0.3.2 zha==0.0.65 # homeassistant.components.zwave_js -zwave-js-server-python==0.67.0 +zwave-js-server-python==0.67.1 # homeassistant.components.zwave_me zwave-me-ws==0.4.3 From fac5b2c09cac9fffc6dc682539124e0c22f05041 Mon Sep 17 00:00:00 2001 From: epenet <6771947+epenet@users.noreply.github.com> Date: Mon, 4 Aug 2025 16:58:46 +0200 Subject: [PATCH 29/30] Add Tuya snapshots tests for camera platform (#149959) --- tests/components/tuya/__init__.py | 11 + .../tuya/fixtures/sp_sdd5f5f2dl5wydjf.json | 383 ++++++++++++++++++ .../tuya/snapshots/test_camera.ambr | 162 ++++++++ .../tuya/snapshots/test_number.ambr | 58 +++ .../tuya/snapshots/test_select.ambr | 173 ++++++++ .../tuya/snapshots/test_sensor.ambr | 53 +++ .../components/tuya/snapshots/test_siren.ambr | 49 +++ .../tuya/snapshots/test_switch.ambr | 336 +++++++++++++++ tests/components/tuya/test_camera.py | 73 ++++ 9 files changed, 1298 insertions(+) create mode 100644 tests/components/tuya/fixtures/sp_sdd5f5f2dl5wydjf.json create mode 100644 tests/components/tuya/snapshots/test_camera.ambr create mode 100644 tests/components/tuya/test_camera.py diff --git a/tests/components/tuya/__init__.py b/tests/components/tuya/__init__.py index 7d6cd32959c22c..05d636b8393f09 100644 --- a/tests/components/tuya/__init__.py +++ b/tests/components/tuya/__init__.py @@ -306,16 +306,27 @@ ], "sp_drezasavompxpcgm": [ # https://github.com/home-assistant/core/issues/149704 + Platform.CAMERA, Platform.LIGHT, Platform.SELECT, Platform.SWITCH, ], "sp_rjKXWRohlvOTyLBu": [ # https://github.com/home-assistant/core/issues/149704 + Platform.CAMERA, Platform.LIGHT, Platform.SELECT, Platform.SWITCH, ], + "sp_sdd5f5f2dl5wydjf": [ + # https://github.com/home-assistant/core/issues/144087 + Platform.CAMERA, + Platform.NUMBER, + Platform.SENSOR, + Platform.SELECT, + Platform.SIREN, + Platform.SWITCH, + ], "tdq_cq1p0nt0a4rixnex": [ # https://github.com/home-assistant/core/issues/146845 Platform.SELECT, diff --git a/tests/components/tuya/fixtures/sp_sdd5f5f2dl5wydjf.json b/tests/components/tuya/fixtures/sp_sdd5f5f2dl5wydjf.json new file mode 100644 index 00000000000000..7e4705650b1ea8 --- /dev/null +++ b/tests/components/tuya/fixtures/sp_sdd5f5f2dl5wydjf.json @@ -0,0 +1,383 @@ +{ + "endpoint": "https://apigw.tuyaeu.com", + "terminal_id": "REDACTED", + "mqtt_connected": true, + "disabled_by": null, + "disabled_polling": false, + "id": "bf3f8b448bbc123e29oghf", + "name": "C9", + "category": "sp", + "product_id": "sdd5f5f2dl5wydjf", + "product_name": "Security Camera", + "online": true, + "sub": false, + "time_zone": "+11:00", + "active_time": "2025-03-13T07:28:30+00:00", + "create_time": "2025-03-13T07:28:30+00:00", + "update_time": "2025-03-13T07:28:30+00:00", + "function": { + "basic_flip": { + "type": "Boolean", + "value": {} + }, + "basic_osd": { + "type": "Boolean", + "value": {} + }, + "motion_sensitivity": { + "type": "Enum", + "value": { + "range": ["0", "1", "2"] + } + }, + "basic_wdr": { + "type": "Boolean", + "value": {} + }, + "sd_format": { + "type": "Boolean", + "value": {} + }, + "motion_record": { + "type": "Boolean", + "value": {} + }, + "ptz_stop": { + "type": "Boolean", + "value": {} + }, + "ptz_control": { + "type": "Enum", + "value": { + "range": ["0", "1", "2", "3", "4", "5", "6", "7"] + } + }, + "ipc_auto_siren": { + "type": "Boolean", + "value": {} + }, + "nightvision_mode": { + "type": "Enum", + "value": { + "range": ["auto", "ir_mode", "color_mode"] + } + }, + "ptz_calibration": { + "type": "Boolean", + "value": {} + }, + "motion_switch": { + "type": "Boolean", + "value": {} + }, + "wireless_lowpower": { + "type": "Integer", + "value": { + "min": 10, + "max": 50, + "scale": 0, + "step": 1 + } + }, + "wireless_awake": { + "type": "Boolean", + "value": {} + }, + "record_switch": { + "type": "Boolean", + "value": {} + }, + "record_mode": { + "type": "Enum", + "value": { + "range": ["1", "2"] + } + }, + "pir_switch": { + "type": "Enum", + "value": { + "range": ["0", "1", "2", "3", "4"] + } + }, + "siren_switch": { + "type": "Boolean", + "value": {} + }, + "basic_device_volume": { + "type": "Integer", + "value": { + "unit": "", + "min": 1, + "max": 10, + "scale": 0, + "step": 1 + } + }, + "motion_tracking": { + "type": "Boolean", + "value": {} + }, + "device_restart": { + "type": "Boolean", + "value": {} + }, + "humanoid_filter": { + "type": "Boolean", + "value": {} + }, + "cruise_switch": { + "type": "Boolean", + "value": {} + }, + "cruise_mode": { + "type": "Enum", + "value": { + "range": ["0", "1"] + } + }, + "ipc_work_mode": { + "type": "Enum", + "value": { + "range": ["0", "1"] + } + } + }, + "status_range": { + "basic_flip": { + "type": "Boolean", + "value": {} + }, + "basic_osd": { + "type": "Boolean", + "value": {} + }, + "motion_sensitivity": { + "type": "Enum", + "value": { + "range": ["0", "1", "2"] + } + }, + "basic_wdr": { + "type": "Boolean", + "value": {} + }, + "sd_storge": { + "type": "String", + "value": { + "maxlen": 255 + } + }, + "sd_status": { + "type": "Integer", + "value": { + "min": 1, + "max": 5, + "scale": 0, + "step": 1 + } + }, + "sd_format": { + "type": "Boolean", + "value": {} + }, + "motion_record": { + "type": "Boolean", + "value": {} + }, + "movement_detect_pic": { + "type": "Raw", + "value": {} + }, + "ptz_stop": { + "type": "Boolean", + "value": {} + }, + "sd_format_state": { + "type": "Integer", + "value": { + "min": -20000, + "max": 200000, + "scale": 0, + "step": 1 + } + }, + "ptz_control": { + "type": "Enum", + "value": { + "range": ["0", "1", "2", "3", "4", "5", "6", "7"] + } + }, + "ipc_auto_siren": { + "type": "Boolean", + "value": {} + }, + "nightvision_mode": { + "type": "Enum", + "value": { + "range": ["auto", "ir_mode", "color_mode"] + } + }, + "battery_report_cap": { + "type": "Integer", + "value": { + "min": 0, + "max": 15, + "scale": 0, + "step": 1 + } + }, + "ptz_calibration": { + "type": "Boolean", + "value": {} + }, + "motion_switch": { + "type": "Boolean", + "value": {} + }, + "doorbell_active": { + "type": "String", + "value": { + "maxlen": 255 + } + }, + "wireless_electricity": { + "type": "Integer", + "value": { + "unit": "%", + "min": 0, + "max": 100, + "scale": 0, + "step": 1 + } + }, + "wireless_powermode": { + "type": "Enum", + "value": { + "range": ["0", "1"] + } + }, + "wireless_lowpower": { + "type": "Integer", + "value": { + "min": 10, + "max": 50, + "scale": 0, + "step": 1 + } + }, + "wireless_awake": { + "type": "Boolean", + "value": {} + }, + "record_switch": { + "type": "Boolean", + "value": {} + }, + "record_mode": { + "type": "Enum", + "value": { + "range": ["1", "2"] + } + }, + "pir_switch": { + "type": "Enum", + "value": { + "range": ["0", "1", "2", "3", "4"] + } + }, + "doorbell_pic": { + "type": "Raw", + "value": {} + }, + "siren_switch": { + "type": "Boolean", + "value": {} + }, + "basic_device_volume": { + "type": "Integer", + "value": { + "unit": "", + "min": 1, + "max": 10, + "scale": 0, + "step": 1 + } + }, + "motion_tracking": { + "type": "Boolean", + "value": {} + }, + "device_restart": { + "type": "Boolean", + "value": {} + }, + "humanoid_filter": { + "type": "Boolean", + "value": {} + }, + "cruise_switch": { + "type": "Boolean", + "value": {} + }, + "cruise_mode": { + "type": "Enum", + "value": { + "range": ["0", "1"] + } + }, + "alarm_message": { + "type": "String", + "value": {} + }, + "ipc_work_mode": { + "type": "Enum", + "value": { + "range": ["0", "1"] + } + }, + "initiative_message": { + "type": "Raw", + "value": {} + } + }, + "status": { + "basic_flip": false, + "basic_osd": true, + "motion_sensitivity": 1, + "basic_wdr": false, + "sd_storge": "30932992|3407872|27525120", + "sd_status": 1, + "sd_format": false, + "motion_record": false, + "movement_detect_pic": "**REDACTED**", + "ptz_stop": true, + "sd_format_state": 0, + "ptz_control": 5, + "ipc_auto_siren": false, + "nightvision_mode": "auto", + "battery_report_cap": 1, + "ptz_calibration": false, + "motion_switch": true, + "doorbell_active": "", + "wireless_electricity": 80, + "wireless_powermode": 0, + "wireless_lowpower": 10, + "wireless_awake": false, + "record_switch": true, + "record_mode": 1, + "pir_switch": 2, + "doorbell_pic": "", + "siren_switch": false, + "basic_device_volume": 1, + "motion_tracking": true, + "device_restart": false, + "humanoid_filter": true, + "cruise_switch": false, + "cruise_mode": 0, + "alarm_message": "**REDACTED**", + "ipc_work_mode": 0, + "initiative_message": "" + }, + "set_up": true, + "support_local": false +} diff --git a/tests/components/tuya/snapshots/test_camera.ambr b/tests/components/tuya/snapshots/test_camera.ambr new file mode 100644 index 00000000000000..e1945f03d3c6e5 --- /dev/null +++ b/tests/components/tuya/snapshots/test_camera.ambr @@ -0,0 +1,162 @@ +# serializer version: 1 +# name: test_platform_setup_and_discovery[sp_drezasavompxpcgm][camera.cam_garage-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'camera', + 'entity_category': None, + 'entity_id': 'camera.cam_garage', + '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.bf7b8e59f8cd49f425mmfm', + 'unit_of_measurement': None, + }) +# --- +# name: test_platform_setup_and_discovery[sp_drezasavompxpcgm][camera.cam_garage-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'access_token': '1', + 'brand': 'Tuya', + 'entity_picture': '/api/camera_proxy/camera.cam_garage?token=1', + 'friendly_name': 'CAM GARAGE', + 'model_name': 'Indoor camera ', + 'motion_detection': True, + 'supported_features': , + }), + 'context': , + 'entity_id': 'camera.cam_garage', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'idle', + }) +# --- +# name: test_platform_setup_and_discovery[sp_rjKXWRohlvOTyLBu][camera.cam_porch-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'camera', + 'entity_category': None, + 'entity_id': 'camera.cam_porch', + '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.bf9d5b7ea61ea4c9a6rom9', + 'unit_of_measurement': None, + }) +# --- +# name: test_platform_setup_and_discovery[sp_rjKXWRohlvOTyLBu][camera.cam_porch-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'access_token': '1', + 'brand': 'Tuya', + 'entity_picture': '/api/camera_proxy/camera.cam_porch?token=1', + 'friendly_name': 'CAM PORCH', + 'model_name': 'Indoor cam Pan/Tilt ', + 'supported_features': , + }), + 'context': , + 'entity_id': 'camera.cam_porch', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'idle', + }) +# --- +# name: test_platform_setup_and_discovery[sp_sdd5f5f2dl5wydjf][camera.c9-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'camera', + 'entity_category': None, + 'entity_id': 'camera.c9', + '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.bf3f8b448bbc123e29oghf', + 'unit_of_measurement': None, + }) +# --- +# name: test_platform_setup_and_discovery[sp_sdd5f5f2dl5wydjf][camera.c9-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'access_token': '1', + 'brand': 'Tuya', + 'entity_picture': '/api/camera_proxy/camera.c9?token=1', + 'friendly_name': 'C9', + 'model_name': 'Security Camera', + 'motion_detection': True, + 'supported_features': , + }), + 'context': , + 'entity_id': 'camera.c9', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'recording', + }) +# --- diff --git a/tests/components/tuya/snapshots/test_number.ambr b/tests/components/tuya/snapshots/test_number.ambr index 9a04b9dd78c270..fa9d7358314276 100644 --- a/tests/components/tuya/snapshots/test_number.ambr +++ b/tests/components/tuya/snapshots/test_number.ambr @@ -527,6 +527,64 @@ 'state': '10.0', }) # --- +# name: test_platform_setup_and_discovery[sp_sdd5f5f2dl5wydjf][number.c9_volume-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'max': 10.0, + 'min': 1.0, + 'mode': , + 'step': 1.0, + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'number', + 'entity_category': , + 'entity_id': 'number.c9_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': 'tuya', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'volume', + 'unique_id': 'tuya.bf3f8b448bbc123e29oghfbasic_device_volume', + 'unit_of_measurement': '', + }) +# --- +# name: test_platform_setup_and_discovery[sp_sdd5f5f2dl5wydjf][number.c9_volume-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'C9 Volume', + 'max': 10.0, + 'min': 1.0, + 'mode': , + 'step': 1.0, + 'unit_of_measurement': '', + }), + 'context': , + 'entity_id': 'number.c9_volume', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '1.0', + }) +# --- # name: test_platform_setup_and_discovery[wk_fi6dne5tu4t1nm6j][number.wifi_smart_gas_boiler_thermostat_temperature_correction-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ diff --git a/tests/components/tuya/snapshots/test_select.ambr b/tests/components/tuya/snapshots/test_select.ambr index 943e230b7cde04..84af76355d5c2e 100644 --- a/tests/components/tuya/snapshots/test_select.ambr +++ b/tests/components/tuya/snapshots/test_select.ambr @@ -945,6 +945,179 @@ 'state': 'unknown', }) # --- +# name: test_platform_setup_and_discovery[sp_sdd5f5f2dl5wydjf][select.c9_ipc_mode-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'options': list([ + '0', + '1', + ]), + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'select', + 'entity_category': , + 'entity_id': 'select.c9_ipc_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': 'IPC mode', + 'platform': 'tuya', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'ipc_work_mode', + 'unique_id': 'tuya.bf3f8b448bbc123e29oghfipc_work_mode', + 'unit_of_measurement': None, + }) +# --- +# name: test_platform_setup_and_discovery[sp_sdd5f5f2dl5wydjf][select.c9_ipc_mode-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'C9 IPC mode', + 'options': list([ + '0', + '1', + ]), + }), + 'context': , + 'entity_id': 'select.c9_ipc_mode', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_platform_setup_and_discovery[sp_sdd5f5f2dl5wydjf][select.c9_motion_detection_sensitivity-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'options': list([ + '0', + '1', + '2', + ]), + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'select', + 'entity_category': , + 'entity_id': 'select.c9_motion_detection_sensitivity', + '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': 'Motion detection sensitivity', + 'platform': 'tuya', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'motion_sensitivity', + 'unique_id': 'tuya.bf3f8b448bbc123e29oghfmotion_sensitivity', + 'unit_of_measurement': None, + }) +# --- +# name: test_platform_setup_and_discovery[sp_sdd5f5f2dl5wydjf][select.c9_motion_detection_sensitivity-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'C9 Motion detection sensitivity', + 'options': list([ + '0', + '1', + '2', + ]), + }), + 'context': , + 'entity_id': 'select.c9_motion_detection_sensitivity', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_platform_setup_and_discovery[sp_sdd5f5f2dl5wydjf][select.c9_record_mode-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'options': list([ + '1', + '2', + ]), + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'select', + 'entity_category': , + 'entity_id': 'select.c9_record_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': 'Record mode', + 'platform': 'tuya', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'record_mode', + 'unique_id': 'tuya.bf3f8b448bbc123e29oghfrecord_mode', + 'unit_of_measurement': None, + }) +# --- +# name: test_platform_setup_and_discovery[sp_sdd5f5f2dl5wydjf][select.c9_record_mode-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'C9 Record mode', + 'options': list([ + '1', + '2', + ]), + }), + 'context': , + 'entity_id': 'select.c9_record_mode', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unknown', + }) +# --- # name: test_platform_setup_and_discovery[tdq_cq1p0nt0a4rixnex][select.4_433_power_on_behavior-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ diff --git a/tests/components/tuya/snapshots/test_sensor.ambr b/tests/components/tuya/snapshots/test_sensor.ambr index e8b9900185e5d0..d2cd0eb0676f06 100644 --- a/tests/components/tuya/snapshots/test_sensor.ambr +++ b/tests/components/tuya/snapshots/test_sensor.ambr @@ -2556,6 +2556,59 @@ 'state': '0.0', }) # --- +# name: test_platform_setup_and_discovery[sp_sdd5f5f2dl5wydjf][sensor.c9_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.c9_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.bf3f8b448bbc123e29oghfwireless_electricity', + 'unit_of_measurement': '%', + }) +# --- +# name: test_platform_setup_and_discovery[sp_sdd5f5f2dl5wydjf][sensor.c9_battery-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'battery', + 'friendly_name': 'C9 Battery', + 'state_class': , + 'unit_of_measurement': '%', + }), + 'context': , + 'entity_id': 'sensor.c9_battery', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '80.0', + }) +# --- # name: test_platform_setup_and_discovery[tyndj_pyakuuoc][sensor.solar_zijpad_battery-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ diff --git a/tests/components/tuya/snapshots/test_siren.ambr b/tests/components/tuya/snapshots/test_siren.ambr index 5c46c2bbd1990c..876db171c7bddc 100644 --- a/tests/components/tuya/snapshots/test_siren.ambr +++ b/tests/components/tuya/snapshots/test_siren.ambr @@ -97,3 +97,52 @@ 'state': 'off', }) # --- +# name: test_platform_setup_and_discovery[sp_sdd5f5f2dl5wydjf][siren.c9-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'siren', + 'entity_category': None, + 'entity_id': 'siren.c9', + '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.bf3f8b448bbc123e29oghfsiren_switch', + 'unit_of_measurement': None, + }) +# --- +# name: test_platform_setup_and_discovery[sp_sdd5f5f2dl5wydjf][siren.c9-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'C9', + 'supported_features': , + }), + 'context': , + 'entity_id': 'siren.c9', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'off', + }) +# --- diff --git a/tests/components/tuya/snapshots/test_switch.ambr b/tests/components/tuya/snapshots/test_switch.ambr index 1b90c21bb4668a..aa80ac08ee5adb 100644 --- a/tests/components/tuya/snapshots/test_switch.ambr +++ b/tests/components/tuya/snapshots/test_switch.ambr @@ -1934,6 +1934,342 @@ 'state': 'off', }) # --- +# name: test_platform_setup_and_discovery[sp_sdd5f5f2dl5wydjf][switch.c9_flip-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.c9_flip', + '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': 'Flip', + 'platform': 'tuya', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'flip', + 'unique_id': 'tuya.bf3f8b448bbc123e29oghfbasic_flip', + 'unit_of_measurement': None, + }) +# --- +# name: test_platform_setup_and_discovery[sp_sdd5f5f2dl5wydjf][switch.c9_flip-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'C9 Flip', + }), + 'context': , + 'entity_id': 'switch.c9_flip', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'off', + }) +# --- +# name: test_platform_setup_and_discovery[sp_sdd5f5f2dl5wydjf][switch.c9_motion_alarm-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.c9_motion_alarm', + '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': 'Motion alarm', + 'platform': 'tuya', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'motion_alarm', + 'unique_id': 'tuya.bf3f8b448bbc123e29oghfmotion_switch', + 'unit_of_measurement': None, + }) +# --- +# name: test_platform_setup_and_discovery[sp_sdd5f5f2dl5wydjf][switch.c9_motion_alarm-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'C9 Motion alarm', + }), + 'context': , + 'entity_id': 'switch.c9_motion_alarm', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'on', + }) +# --- +# name: test_platform_setup_and_discovery[sp_sdd5f5f2dl5wydjf][switch.c9_motion_recording-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.c9_motion_recording', + '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': 'Motion recording', + 'platform': 'tuya', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'motion_recording', + 'unique_id': 'tuya.bf3f8b448bbc123e29oghfmotion_record', + 'unit_of_measurement': None, + }) +# --- +# name: test_platform_setup_and_discovery[sp_sdd5f5f2dl5wydjf][switch.c9_motion_recording-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'C9 Motion recording', + }), + 'context': , + 'entity_id': 'switch.c9_motion_recording', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'off', + }) +# --- +# name: test_platform_setup_and_discovery[sp_sdd5f5f2dl5wydjf][switch.c9_motion_tracking-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.c9_motion_tracking', + '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': 'Motion tracking', + 'platform': 'tuya', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'motion_tracking', + 'unique_id': 'tuya.bf3f8b448bbc123e29oghfmotion_tracking', + 'unit_of_measurement': None, + }) +# --- +# name: test_platform_setup_and_discovery[sp_sdd5f5f2dl5wydjf][switch.c9_motion_tracking-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'C9 Motion tracking', + }), + 'context': , + 'entity_id': 'switch.c9_motion_tracking', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'on', + }) +# --- +# name: test_platform_setup_and_discovery[sp_sdd5f5f2dl5wydjf][switch.c9_time_watermark-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.c9_time_watermark', + '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': 'Time watermark', + 'platform': 'tuya', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'time_watermark', + 'unique_id': 'tuya.bf3f8b448bbc123e29oghfbasic_osd', + 'unit_of_measurement': None, + }) +# --- +# name: test_platform_setup_and_discovery[sp_sdd5f5f2dl5wydjf][switch.c9_time_watermark-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'C9 Time watermark', + }), + 'context': , + 'entity_id': 'switch.c9_time_watermark', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'on', + }) +# --- +# name: test_platform_setup_and_discovery[sp_sdd5f5f2dl5wydjf][switch.c9_video_recording-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.c9_video_recording', + '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': 'Video recording', + 'platform': 'tuya', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'video_recording', + 'unique_id': 'tuya.bf3f8b448bbc123e29oghfrecord_switch', + 'unit_of_measurement': None, + }) +# --- +# name: test_platform_setup_and_discovery[sp_sdd5f5f2dl5wydjf][switch.c9_video_recording-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'C9 Video recording', + }), + 'context': , + 'entity_id': 'switch.c9_video_recording', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'on', + }) +# --- +# name: test_platform_setup_and_discovery[sp_sdd5f5f2dl5wydjf][switch.c9_wide_dynamic_range-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.c9_wide_dynamic_range', + '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': 'Wide dynamic range', + 'platform': 'tuya', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'wide_dynamic_range', + 'unique_id': 'tuya.bf3f8b448bbc123e29oghfbasic_wdr', + 'unit_of_measurement': None, + }) +# --- +# name: test_platform_setup_and_discovery[sp_sdd5f5f2dl5wydjf][switch.c9_wide_dynamic_range-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'C9 Wide dynamic range', + }), + 'context': , + 'entity_id': 'switch.c9_wide_dynamic_range', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'off', + }) +# --- # name: test_platform_setup_and_discovery[tdq_cq1p0nt0a4rixnex][switch.4_433_switch_1-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ diff --git a/tests/components/tuya/test_camera.py b/tests/components/tuya/test_camera.py new file mode 100644 index 00000000000000..25bfe57ea0cf05 --- /dev/null +++ b/tests/components/tuya/test_camera.py @@ -0,0 +1,73 @@ +"""Test Tuya camera platform.""" + +from __future__ import annotations + +from unittest.mock import patch + +import pytest +from syrupy.assertion import SnapshotAssertion +from tuya_sharing import CustomerDevice + +from homeassistant.components.tuya import ManagerCompat +from homeassistant.const import Platform +from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry as er + +from . import DEVICE_MOCKS, initialize_entry + +from tests.common import MockConfigEntry, snapshot_platform + + +@pytest.fixture(autouse=True) +def mock_getrandbits(): + """Mock camera access token which normally is randomized.""" + with patch( + "homeassistant.components.camera.SystemRandom.getrandbits", + return_value=1, + ): + yield + + +@pytest.mark.parametrize( + "mock_device_code", + [k for k, v in DEVICE_MOCKS.items() if Platform.CAMERA in v], +) +@patch("homeassistant.components.tuya.PLATFORMS", [Platform.CAMERA]) +async def test_platform_setup_and_discovery( + hass: HomeAssistant, + mock_manager: ManagerCompat, + mock_config_entry: MockConfigEntry, + mock_device: CustomerDevice, + entity_registry: er.EntityRegistry, + snapshot: SnapshotAssertion, +) -> None: + """Test platform setup and discovery.""" + + await initialize_entry(hass, mock_manager, mock_config_entry, mock_device) + + await snapshot_platform( + hass, + entity_registry, + snapshot, + mock_config_entry.entry_id, + ) + + +@pytest.mark.parametrize( + "mock_device_code", + [k for k, v in DEVICE_MOCKS.items() if Platform.CAMERA not in v], +) +@patch("homeassistant.components.tuya.PLATFORMS", [Platform.CAMERA]) +async def test_platform_setup_no_discovery( + hass: HomeAssistant, + mock_manager: ManagerCompat, + mock_config_entry: MockConfigEntry, + mock_device: CustomerDevice, + entity_registry: er.EntityRegistry, +) -> None: + """Test platform setup without discovery.""" + await initialize_entry(hass, mock_manager, mock_config_entry, mock_device) + + assert not er.async_entries_for_config_entry( + entity_registry, mock_config_entry.entry_id + ) From 31e647b5b004fa42e32aedec6391f85185b046b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Mon, 4 Aug 2025 15:59:07 +0100 Subject: [PATCH 30/30] Bump hass-nabucasa from 0.110.1 to 0.111.0 (#149977) --- homeassistant/components/cloud/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- pyproject.toml | 2 +- requirements.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/cloud/manifest.json b/homeassistant/components/cloud/manifest.json index 63eae6261d4587..0ef407b3628a02 100644 --- a/homeassistant/components/cloud/manifest.json +++ b/homeassistant/components/cloud/manifest.json @@ -13,6 +13,6 @@ "integration_type": "system", "iot_class": "cloud_push", "loggers": ["acme", "hass_nabucasa", "snitun"], - "requirements": ["hass-nabucasa==0.110.1"], + "requirements": ["hass-nabucasa==0.111.0"], "single_config_entry": true } diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index f8a57ba61bbfab..bca5e4648af0bd 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -35,7 +35,7 @@ fnv-hash-fast==1.5.0 go2rtc-client==0.2.1 ha-ffmpeg==3.2.2 habluetooth==4.0.1 -hass-nabucasa==0.110.1 +hass-nabucasa==0.111.0 hassil==2.2.3 home-assistant-bluetooth==1.13.1 home-assistant-frontend==20250731.0 diff --git a/pyproject.toml b/pyproject.toml index a32e9308fe2a36..99ea68be9001d1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -47,7 +47,7 @@ dependencies = [ "fnv-hash-fast==1.5.0", # hass-nabucasa is imported by helpers which don't depend on the cloud # integration - "hass-nabucasa==0.110.1", + "hass-nabucasa==0.111.0", # When bumping httpx, please check the version pins of # httpcore, anyio, and h11 in gen_requirements_all "httpx==0.28.1", diff --git a/requirements.txt b/requirements.txt index ba08a72e324ce2..90953842e20ce3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -22,7 +22,7 @@ certifi>=2021.5.30 ciso8601==2.3.2 cronsim==2.6 fnv-hash-fast==1.5.0 -hass-nabucasa==0.110.1 +hass-nabucasa==0.111.0 httpx==0.28.1 home-assistant-bluetooth==1.13.1 ifaddr==0.2.0 diff --git a/requirements_all.txt b/requirements_all.txt index 1c7280547c7141..6e93cb6c59526b 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1133,7 +1133,7 @@ habiticalib==0.4.1 habluetooth==4.0.1 # homeassistant.components.cloud -hass-nabucasa==0.110.1 +hass-nabucasa==0.111.0 # homeassistant.components.splunk hass-splunk==0.1.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 4aa7a5063e6084..945c16d3250ad0 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -994,7 +994,7 @@ habiticalib==0.4.1 habluetooth==4.0.1 # homeassistant.components.cloud -hass-nabucasa==0.110.1 +hass-nabucasa==0.111.0 # homeassistant.components.assist_satellite # homeassistant.components.conversation