From a5afe47df7a1ac498fb5813877a51f5875128e6b Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 1 Dec 2022 01:21:06 +0100 Subject: [PATCH 001/174] Bumped version to 2022.12.0b0 --- homeassistant/const.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index ca69307a485..32c79bbc889 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -8,7 +8,7 @@ from .backports.enum import StrEnum APPLICATION_NAME: Final = "HomeAssistant" MAJOR_VERSION: Final = 2022 MINOR_VERSION: Final = 12 -PATCH_VERSION: Final = "0.dev0" +PATCH_VERSION: Final = "0b0" __short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__: Final = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 9, 0) diff --git a/pyproject.toml b/pyproject.toml index 710b025bee3..729892e41cc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "homeassistant" -version = "2022.12.0.dev0" +version = "2022.12.0b0" license = {text = "Apache-2.0"} description = "Open-source home automation platform running on Python 3." readme = "README.rst" From cc2ab25916a0257fa26ae16ed2821a1892de1453 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 30 Nov 2022 15:07:22 -1000 Subject: [PATCH 002/174] Bump aiohomekit to 2.3.2 (#83019) --- homeassistant/components/homekit_controller/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/homekit_controller/manifest.json b/homeassistant/components/homekit_controller/manifest.json index 54d5c5b854d..1b7061146ea 100644 --- a/homeassistant/components/homekit_controller/manifest.json +++ b/homeassistant/components/homekit_controller/manifest.json @@ -3,7 +3,7 @@ "name": "HomeKit Controller", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/homekit_controller", - "requirements": ["aiohomekit==2.3.1"], + "requirements": ["aiohomekit==2.3.2"], "zeroconf": ["_hap._tcp.local.", "_hap._udp.local."], "bluetooth": [{ "manufacturer_id": 76, "manufacturer_data_start": [6] }], "dependencies": ["bluetooth", "zeroconf"], diff --git a/requirements_all.txt b/requirements_all.txt index cc0bb13bafe..507d3658a83 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -174,7 +174,7 @@ aioguardian==2022.07.0 aioharmony==0.2.9 # homeassistant.components.homekit_controller -aiohomekit==2.3.1 +aiohomekit==2.3.2 # homeassistant.components.emulated_hue # homeassistant.components.http diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 790c067a3b1..1d2eb9388f3 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -158,7 +158,7 @@ aioguardian==2022.07.0 aioharmony==0.2.9 # homeassistant.components.homekit_controller -aiohomekit==2.3.1 +aiohomekit==2.3.2 # homeassistant.components.emulated_hue # homeassistant.components.http From b14dc306cff56aa6633fd3c1f241eb67d07bd41d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 30 Nov 2022 16:36:02 -1000 Subject: [PATCH 003/174] Fix yale access bluetooth locks delaying startup when key changes (#83024) If the keys changed for the yale locks, the locks would be slow to setup. Because august had yalexs_ble as an after dep, it would be waiting with the new keys but not able to setup because it was waiting for the locks to setup which would be trying over and over until they failed because the key had changed out from under it. This change moves some more code into the lib to avoid the dep and allows both to startup at the same time so the cloud service can feed the new keys in if needed without waiting for the lock to fail to setup changelog: https://github.com/bdraco/yalexs-ble/compare/v1.9.8...v1.10.0 --- homeassistant/components/august/__init__.py | 13 ++++++---- homeassistant/components/august/manifest.json | 5 ++-- .../components/yalexs_ble/__init__.py | 25 +------------------ .../components/yalexs_ble/manifest.json | 2 +- requirements_all.txt | 5 +++- requirements_test_all.txt | 5 +++- tests/components/august/test_init.py | 4 +-- 7 files changed, 22 insertions(+), 37 deletions(-) diff --git a/homeassistant/components/august/__init__.py b/homeassistant/components/august/__init__.py index f4a0f57eb76..249d9e51a85 100644 --- a/homeassistant/components/august/__init__.py +++ b/homeassistant/components/august/__init__.py @@ -12,9 +12,9 @@ from yalexs.exceptions import AugustApiAIOHTTPError from yalexs.lock import Lock, LockDetail from yalexs.pubnub_activity import activities_from_pubnub_message from yalexs.pubnub_async import AugustPubNub, async_create_pubnub +from yalexs_ble import YaleXSBLEDiscovery -from homeassistant.components import yalexs_ble -from homeassistant.config_entries import ConfigEntry +from homeassistant.config_entries import SOURCE_INTEGRATION_DISCOVERY, ConfigEntry from homeassistant.const import CONF_PASSWORD from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import ( @@ -22,7 +22,7 @@ from homeassistant.exceptions import ( ConfigEntryNotReady, HomeAssistantError, ) -from homeassistant.helpers import device_registry as dr +from homeassistant.helpers import device_registry as dr, discovery_flow from .activity import ActivityStream from .const import DOMAIN, MIN_TIME_BETWEEN_DETAIL_UPDATES, PLATFORMS @@ -38,6 +38,7 @@ API_CACHED_ATTRS = { "lock_status", "lock_status_datetime", } +YALEXS_BLE_DOMAIN = "yalexs_ble" async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: @@ -100,9 +101,11 @@ def _async_trigger_ble_lock_discovery( ): """Update keys for the yalexs-ble integration if available.""" for lock_detail in locks_with_offline_keys: - yalexs_ble.async_discovery( + discovery_flow.async_create_flow( hass, - yalexs_ble.YaleXSBLEDiscovery( + YALEXS_BLE_DOMAIN, + context={"source": SOURCE_INTEGRATION_DISCOVERY}, + data=YaleXSBLEDiscovery( { "name": lock_detail.device_name, "address": lock_detail.mac_address, diff --git a/homeassistant/components/august/manifest.json b/homeassistant/components/august/manifest.json index b7dde070049..5470f03eb4c 100644 --- a/homeassistant/components/august/manifest.json +++ b/homeassistant/components/august/manifest.json @@ -2,7 +2,7 @@ "domain": "august", "name": "August", "documentation": "https://www.home-assistant.io/integrations/august", - "requirements": ["yalexs==1.2.6"], + "requirements": ["yalexs==1.2.6", "yalexs_ble==1.10.0"], "codeowners": ["@bdraco"], "dhcp": [ { @@ -24,6 +24,5 @@ ], "config_flow": true, "iot_class": "cloud_push", - "loggers": ["pubnub", "yalexs"], - "after_dependencies": ["yalexs_ble"] + "loggers": ["pubnub", "yalexs"] } diff --git a/homeassistant/components/yalexs_ble/__init__.py b/homeassistant/components/yalexs_ble/__init__.py index 7a2b3146265..fcecec19e6b 100644 --- a/homeassistant/components/yalexs_ble/__init__.py +++ b/homeassistant/components/yalexs_ble/__init__.py @@ -2,17 +2,15 @@ from __future__ import annotations import asyncio -from typing import TypedDict import async_timeout from yalexs_ble import PushLock, local_name_is_unique from homeassistant.components import bluetooth -from homeassistant.config_entries import SOURCE_INTEGRATION_DISCOVERY, ConfigEntry +from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_ADDRESS, Platform from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import ConfigEntryNotReady -from homeassistant.helpers import discovery_flow from .const import CONF_KEY, CONF_LOCAL_NAME, CONF_SLOT, DEVICE_TIMEOUT, DOMAIN from .models import YaleXSBLEData @@ -21,27 +19,6 @@ from .util import async_find_existing_service_info, bluetooth_callback_matcher PLATFORMS: list[Platform] = [Platform.BINARY_SENSOR, Platform.LOCK, Platform.SENSOR] -class YaleXSBLEDiscovery(TypedDict): - """A validated discovery of a Yale XS BLE device.""" - - name: str - address: str - serial: str - key: str - slot: int - - -@callback -def async_discovery(hass: HomeAssistant, discovery: YaleXSBLEDiscovery) -> None: - """Update keys for the yalexs-ble integration if available.""" - discovery_flow.async_create_flow( - hass, - DOMAIN, - context={"source": SOURCE_INTEGRATION_DISCOVERY}, - data=discovery, - ) - - async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up Yale Access Bluetooth from a config entry.""" local_name = entry.data[CONF_LOCAL_NAME] diff --git a/homeassistant/components/yalexs_ble/manifest.json b/homeassistant/components/yalexs_ble/manifest.json index 73c802261d4..b54dda37548 100644 --- a/homeassistant/components/yalexs_ble/manifest.json +++ b/homeassistant/components/yalexs_ble/manifest.json @@ -3,7 +3,7 @@ "name": "Yale Access Bluetooth", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/yalexs_ble", - "requirements": ["yalexs-ble==1.9.8"], + "requirements": ["yalexs-ble==1.10.0"], "dependencies": ["bluetooth"], "codeowners": ["@bdraco"], "bluetooth": [ diff --git a/requirements_all.txt b/requirements_all.txt index 507d3658a83..24979f18203 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2603,11 +2603,14 @@ xs1-api-client==3.0.0 yalesmartalarmclient==0.3.9 # homeassistant.components.yalexs_ble -yalexs-ble==1.9.8 +yalexs-ble==1.10.0 # homeassistant.components.august yalexs==1.2.6 +# homeassistant.components.august +yalexs_ble==1.10.0 + # homeassistant.components.yeelight yeelight==0.7.10 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 1d2eb9388f3..2d5b57dc410 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1813,11 +1813,14 @@ xmltodict==0.13.0 yalesmartalarmclient==0.3.9 # homeassistant.components.yalexs_ble -yalexs-ble==1.9.8 +yalexs-ble==1.10.0 # homeassistant.components.august yalexs==1.2.6 +# homeassistant.components.august +yalexs_ble==1.10.0 + # homeassistant.components.yeelight yeelight==0.7.10 diff --git a/tests/components/august/test_init.py b/tests/components/august/test_init.py index a4bc3d7b16f..ef82efae177 100644 --- a/tests/components/august/test_init.py +++ b/tests/components/august/test_init.py @@ -332,7 +332,7 @@ async def test_load_triggers_ble_discovery(hass): august_lock_without_key = await _mock_operative_august_lock_detail(hass) with patch( - "homeassistant.components.august.yalexs_ble.async_discovery" + "homeassistant.components.august.discovery_flow.async_create_flow" ) as mock_discovery: config_entry = await _create_august_with_devices( hass, [august_lock_with_key, august_lock_without_key] @@ -341,7 +341,7 @@ async def test_load_triggers_ble_discovery(hass): assert config_entry.state is ConfigEntryState.LOADED assert len(mock_discovery.mock_calls) == 1 - assert mock_discovery.mock_calls[0][1][1] == { + assert mock_discovery.mock_calls[0].kwargs["data"] == { "name": "Front Door Lock", "address": None, "serial": "X2FSW05DGA", From 5147beb475f4fc6d739ba0f78be1498eb3809dd5 Mon Sep 17 00:00:00 2001 From: Keilin Bickar Date: Thu, 1 Dec 2022 04:53:48 -0500 Subject: [PATCH 004/174] Bump sense_api to 0.11.0 and add refresh token support (#83030) fixes undefined --- homeassistant/components/emulated_kasa/manifest.json | 2 +- homeassistant/components/sense/__init__.py | 5 ++++- homeassistant/components/sense/config_flow.py | 2 ++ homeassistant/components/sense/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/sense/test_config_flow.py | 4 ++++ 7 files changed, 14 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/emulated_kasa/manifest.json b/homeassistant/components/emulated_kasa/manifest.json index 31319a656cd..86e8f2fc2ca 100644 --- a/homeassistant/components/emulated_kasa/manifest.json +++ b/homeassistant/components/emulated_kasa/manifest.json @@ -2,7 +2,7 @@ "domain": "emulated_kasa", "name": "Emulated Kasa", "documentation": "https://www.home-assistant.io/integrations/emulated_kasa", - "requirements": ["sense_energy==0.10.4"], + "requirements": ["sense_energy==0.11.0"], "codeowners": ["@kbickar"], "quality_scale": "internal", "iot_class": "local_push", diff --git a/homeassistant/components/sense/__init__.py b/homeassistant/components/sense/__init__.py index 82b25802b02..316cbb841ed 100644 --- a/homeassistant/components/sense/__init__.py +++ b/homeassistant/components/sense/__init__.py @@ -66,6 +66,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: access_token = entry_data.get("access_token", "") user_id = entry_data.get("user_id", "") + device_id = entry_data.get("device_id", "") + refresh_token = entry_data.get("refresh_token", "") monitor_id = entry_data.get("monitor_id", "") client_session = async_get_clientsession(hass) @@ -76,7 +78,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: gateway.rate_limit = ACTIVE_UPDATE_RATE try: - gateway.load_auth(access_token, user_id, monitor_id) + gateway.load_auth(access_token, user_id, device_id, refresh_token) + gateway.set_monitor_id(monitor_id) await gateway.get_monitor_data() except (SenseAuthenticationException, SenseMFARequiredException) as err: _LOGGER.warning("Sense authentication expired") diff --git a/homeassistant/components/sense/config_flow.py b/homeassistant/components/sense/config_flow.py index 0690344ccf1..d7f7588beb2 100644 --- a/homeassistant/components/sense/config_flow.py +++ b/homeassistant/components/sense/config_flow.py @@ -60,6 +60,8 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): """Create the entry from the config data.""" self._auth_data["access_token"] = self._gateway.sense_access_token self._auth_data["user_id"] = self._gateway.sense_user_id + self._auth_data["device_id"] = self._gateway.device_id + self._auth_data["refresh_token"] = self._gateway.refresh_token self._auth_data["monitor_id"] = self._gateway.sense_monitor_id existing_entry = await self.async_set_unique_id(self._auth_data[CONF_EMAIL]) if not existing_entry: diff --git a/homeassistant/components/sense/manifest.json b/homeassistant/components/sense/manifest.json index 192a0f6defe..158ef7cae61 100644 --- a/homeassistant/components/sense/manifest.json +++ b/homeassistant/components/sense/manifest.json @@ -2,7 +2,7 @@ "domain": "sense", "name": "Sense", "documentation": "https://www.home-assistant.io/integrations/sense", - "requirements": ["sense_energy==0.10.4"], + "requirements": ["sense_energy==0.11.0"], "codeowners": ["@kbickar"], "config_flow": true, "dhcp": [ diff --git a/requirements_all.txt b/requirements_all.txt index 24979f18203..5f52fa49204 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2258,7 +2258,7 @@ sendgrid==6.8.2 # homeassistant.components.emulated_kasa # homeassistant.components.sense -sense_energy==0.10.4 +sense_energy==0.11.0 # homeassistant.components.sensirion_ble sensirion-ble==0.0.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 2d5b57dc410..0ac98658296 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1561,7 +1561,7 @@ securetar==2022.2.0 # homeassistant.components.emulated_kasa # homeassistant.components.sense -sense_energy==0.10.4 +sense_energy==0.11.0 # homeassistant.components.sensirion_ble sensirion-ble==0.0.1 diff --git a/tests/components/sense/test_config_flow.py b/tests/components/sense/test_config_flow.py index 690e5d2e530..7dc2b2469ec 100644 --- a/tests/components/sense/test_config_flow.py +++ b/tests/components/sense/test_config_flow.py @@ -22,6 +22,8 @@ MOCK_CONFIG = { "access_token": "ABC", "user_id": "123", "monitor_id": "456", + "device_id": "789", + "refresh_token": "XYZ", } @@ -36,6 +38,8 @@ def mock_sense(): mock_sense.return_value.sense_access_token = "ABC" mock_sense.return_value.sense_user_id = "123" mock_sense.return_value.sense_monitor_id = "456" + mock_sense.return_value.device_id = "789" + mock_sense.return_value.refresh_token = "XYZ" yield mock_sense From 88072512d8a697c600f66d4aeb534cca6450dbf9 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 30 Nov 2022 18:11:01 -1000 Subject: [PATCH 005/174] Fix incorrect mapping of sensitive mode on oralb 4000 series (#83031) --- homeassistant/components/oralb/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/oralb/manifest.json b/homeassistant/components/oralb/manifest.json index 8868330a7e7..94abb85a7b8 100644 --- a/homeassistant/components/oralb/manifest.json +++ b/homeassistant/components/oralb/manifest.json @@ -8,7 +8,7 @@ "manufacturer_id": 220 } ], - "requirements": ["oralb-ble==0.14.2"], + "requirements": ["oralb-ble==0.14.3"], "dependencies": ["bluetooth"], "codeowners": ["@bdraco"], "iot_class": "local_push" diff --git a/requirements_all.txt b/requirements_all.txt index 5f52fa49204..ca3a13dffce 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1261,7 +1261,7 @@ openwrt-luci-rpc==1.1.11 openwrt-ubus-rpc==0.0.2 # homeassistant.components.oralb -oralb-ble==0.14.2 +oralb-ble==0.14.3 # homeassistant.components.oru oru==0.1.11 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 0ac98658296..adce784642c 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -906,7 +906,7 @@ open-meteo==0.2.1 openerz-api==0.1.0 # homeassistant.components.oralb -oralb-ble==0.14.2 +oralb-ble==0.14.3 # homeassistant.components.ovo_energy ovoenergy==1.2.0 From a9e1c14024767ac20e900f5ea9b95c05f2bc50e5 Mon Sep 17 00:00:00 2001 From: Jc2k Date: Thu, 1 Dec 2022 08:14:42 +0000 Subject: [PATCH 006/174] Fix getting updated thread IP at startup in homekit_controller (#83037) --- homeassistant/components/homekit_controller/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/homekit_controller/manifest.json b/homeassistant/components/homekit_controller/manifest.json index 1b7061146ea..041f2ad293e 100644 --- a/homeassistant/components/homekit_controller/manifest.json +++ b/homeassistant/components/homekit_controller/manifest.json @@ -3,7 +3,7 @@ "name": "HomeKit Controller", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/homekit_controller", - "requirements": ["aiohomekit==2.3.2"], + "requirements": ["aiohomekit==2.3.3"], "zeroconf": ["_hap._tcp.local.", "_hap._udp.local."], "bluetooth": [{ "manufacturer_id": 76, "manufacturer_data_start": [6] }], "dependencies": ["bluetooth", "zeroconf"], diff --git a/requirements_all.txt b/requirements_all.txt index ca3a13dffce..bc28123eec6 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -174,7 +174,7 @@ aioguardian==2022.07.0 aioharmony==0.2.9 # homeassistant.components.homekit_controller -aiohomekit==2.3.2 +aiohomekit==2.3.3 # homeassistant.components.emulated_hue # homeassistant.components.http diff --git a/requirements_test_all.txt b/requirements_test_all.txt index adce784642c..4f0a91e933d 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -158,7 +158,7 @@ aioguardian==2022.07.0 aioharmony==0.2.9 # homeassistant.components.homekit_controller -aiohomekit==2.3.2 +aiohomekit==2.3.3 # homeassistant.components.emulated_hue # homeassistant.components.http From 47af4a675367c70fc6e65215b9399ab3331d2a2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Thu, 1 Dec 2022 19:26:30 +0100 Subject: [PATCH 007/174] Fix hassfest serializer injecting commas in empty lists (#83052) * No trailing comma for empty lists * Update script/hassfest/serializer.py Co-authored-by: Aarni Koskela Co-authored-by: Paulus Schoutsen Co-authored-by: Aarni Koskela --- script/hassfest/serializer.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/script/hassfest/serializer.py b/script/hassfest/serializer.py index 101aea6837e..41f6a554aff 100644 --- a/script/hassfest/serializer.py +++ b/script/hassfest/serializer.py @@ -21,7 +21,9 @@ def _wrap_items( # on one line and some on multiple. if sort: items = sorted(items) - return f"{opener}{','.join(items)},{closer}" + + joined_items = ", ".join(items) + return f"{opener}{joined_items}{',' if joined_items else ''}{closer}" def _mapping_to_str(data: Mapping[Any, Any]) -> str: From 1bf5925cdfe2621b375127c4ab237f08295cf06b Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Thu, 1 Dec 2022 22:35:53 +0100 Subject: [PATCH 008/174] Update frontend to 20221201.1 (#83062) --- homeassistant/components/frontend/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/frontend/manifest.json b/homeassistant/components/frontend/manifest.json index 5f46c6d334d..74408788d49 100644 --- a/homeassistant/components/frontend/manifest.json +++ b/homeassistant/components/frontend/manifest.json @@ -2,7 +2,7 @@ "domain": "frontend", "name": "Home Assistant Frontend", "documentation": "https://www.home-assistant.io/integrations/frontend", - "requirements": ["home-assistant-frontend==20221130.0"], + "requirements": ["home-assistant-frontend==20221201.1"], "dependencies": [ "api", "auth", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 39bd8e35504..323cc927c96 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -22,7 +22,7 @@ dbus-fast==1.75.0 fnvhash==0.1.0 hass-nabucasa==0.56.0 home-assistant-bluetooth==1.8.1 -home-assistant-frontend==20221130.0 +home-assistant-frontend==20221201.1 httpx==0.23.1 ifaddr==0.1.7 janus==1.0.0 diff --git a/requirements_all.txt b/requirements_all.txt index bc28123eec6..0eee6b15f39 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -884,7 +884,7 @@ hole==0.7.0 holidays==0.17.2 # homeassistant.components.frontend -home-assistant-frontend==20221130.0 +home-assistant-frontend==20221201.1 # homeassistant.components.home_connect homeconnect==0.7.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 4f0a91e933d..fd43ce5ab4c 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -664,7 +664,7 @@ hole==0.7.0 holidays==0.17.2 # homeassistant.components.frontend -home-assistant-frontend==20221130.0 +home-assistant-frontend==20221201.1 # homeassistant.components.home_connect homeconnect==0.7.2 From 095cc77bf98e9445a00ffe6d121db58032cf9f89 Mon Sep 17 00:00:00 2001 From: Marcel van der Veldt Date: Thu, 1 Dec 2022 20:44:56 +0100 Subject: [PATCH 009/174] Add matter integration BETA (#83064) * Add matter base (#79372) Co-authored-by: Marcel van der Veldt * Add matter server add-on flow (#82698) * Add matter server add-on flow * Fix stale error argument * Clean docstrings * Use localhost as default address * Add matter websocket api foundation (#82848) * Add matter config entry add-on management (#82865) * Use matter refactored server/client library (#83003) Co-authored-by: Martin Hjelmare * Bump python-matter-server to 1.0.6 (#83059) * Extend matter websocket api (#82948) * Extend matter websocket api * Finish docstring * Fix pin type * Adjust api after new client * Adjust api to frontend for now Co-authored-by: Martin Hjelmare --- .coveragerc | 3 + .strict-typing | 1 + CODEOWNERS | 2 + homeassistant/components/matter/__init__.py | 351 +++++++ homeassistant/components/matter/adapter.py | 141 +++ homeassistant/components/matter/addon.py | 17 + homeassistant/components/matter/api.py | 152 +++ .../components/matter/config_flow.py | 325 ++++++ homeassistant/components/matter/const.py | 10 + .../components/matter/device_platform.py | 24 + homeassistant/components/matter/entity.py | 118 +++ homeassistant/components/matter/light.py | 173 ++++ homeassistant/components/matter/manifest.json | 10 + homeassistant/components/matter/services.yaml | 66 ++ homeassistant/components/matter/strings.json | 47 + .../components/matter/translations/en.json | 47 + homeassistant/components/matter/util.py | 11 + homeassistant/generated/config_flows.py | 1 + homeassistant/generated/integrations.json | 6 + mypy.ini | 10 + requirements_all.txt | 3 + requirements_test_all.txt | 3 + tests/components/matter/__init__.py | 1 + tests/components/matter/common.py | 146 +++ tests/components/matter/conftest.py | 187 ++++ .../fixtures/nodes/fake-bridge-two-light.json | 174 ++++ .../fixtures/nodes/lighting-example-app.json | 882 ++++++++++++++++ tests/components/matter/test_adapter.py | 78 ++ tests/components/matter/test_api.py | 179 ++++ tests/components/matter/test_config_flow.py | 979 ++++++++++++++++++ tests/components/matter/test_init.py | 398 +++++++ tests/components/matter/test_light.py | 82 ++ 32 files changed, 4627 insertions(+) create mode 100644 homeassistant/components/matter/__init__.py create mode 100644 homeassistant/components/matter/adapter.py create mode 100644 homeassistant/components/matter/addon.py create mode 100644 homeassistant/components/matter/api.py create mode 100644 homeassistant/components/matter/config_flow.py create mode 100644 homeassistant/components/matter/const.py create mode 100644 homeassistant/components/matter/device_platform.py create mode 100644 homeassistant/components/matter/entity.py create mode 100644 homeassistant/components/matter/light.py create mode 100644 homeassistant/components/matter/manifest.json create mode 100644 homeassistant/components/matter/services.yaml create mode 100644 homeassistant/components/matter/strings.json create mode 100644 homeassistant/components/matter/translations/en.json create mode 100644 homeassistant/components/matter/util.py create mode 100644 tests/components/matter/__init__.py create mode 100644 tests/components/matter/common.py create mode 100644 tests/components/matter/conftest.py create mode 100644 tests/components/matter/fixtures/nodes/fake-bridge-two-light.json create mode 100644 tests/components/matter/fixtures/nodes/lighting-example-app.json create mode 100644 tests/components/matter/test_adapter.py create mode 100644 tests/components/matter/test_api.py create mode 100644 tests/components/matter/test_config_flow.py create mode 100644 tests/components/matter/test_init.py create mode 100644 tests/components/matter/test_light.py diff --git a/.coveragerc b/.coveragerc index 8582f2daed8..94e1667bad8 100644 --- a/.coveragerc +++ b/.coveragerc @@ -724,6 +724,9 @@ omit = homeassistant/components/map/* homeassistant/components/mastodon/notify.py homeassistant/components/matrix/* + homeassistant/components/matter/__init__.py + homeassistant/components/matter/adapter.py + homeassistant/components/matter/entity.py homeassistant/components/meater/__init__.py homeassistant/components/meater/const.py homeassistant/components/meater/sensor.py diff --git a/.strict-typing b/.strict-typing index baa7f9e24f7..e47533d6ca9 100644 --- a/.strict-typing +++ b/.strict-typing @@ -179,6 +179,7 @@ homeassistant.components.logger.* homeassistant.components.lookin.* homeassistant.components.luftdaten.* homeassistant.components.mailbox.* +homeassistant.components.matter.* homeassistant.components.media_player.* homeassistant.components.media_source.* homeassistant.components.metoffice.* diff --git a/CODEOWNERS b/CODEOWNERS index 8920d85defe..2966d69b032 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -666,6 +666,8 @@ build.json @home-assistant/supervisor /tests/components/lyric/ @timmo001 /homeassistant/components/mastodon/ @fabaff /homeassistant/components/matrix/ @tinloaf +/homeassistant/components/matter/ @MartinHjelmare @marcelveldt +/tests/components/matter/ @MartinHjelmare @marcelveldt /homeassistant/components/mazda/ @bdr99 /tests/components/mazda/ @bdr99 /homeassistant/components/meater/ @Sotolotl @emontnemery diff --git a/homeassistant/components/matter/__init__.py b/homeassistant/components/matter/__init__.py new file mode 100644 index 00000000000..845d48ea883 --- /dev/null +++ b/homeassistant/components/matter/__init__.py @@ -0,0 +1,351 @@ +"""The Matter integration.""" +from __future__ import annotations + +import asyncio +from typing import cast + +import async_timeout +from matter_server.client import MatterClient +from matter_server.client.exceptions import ( + CannotConnect, + FailedCommand, + InvalidServerVersion, +) +import voluptuous as vol + +from homeassistant.components.hassio import AddonError, AddonManager, AddonState +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_URL, EVENT_HOMEASSISTANT_STOP +from homeassistant.core import Event, HomeAssistant, ServiceCall, callback +from homeassistant.exceptions import ConfigEntryNotReady, HomeAssistantError +from homeassistant.helpers import device_registry as dr +from homeassistant.helpers.aiohttp_client import async_get_clientsession +from homeassistant.helpers.issue_registry import ( + IssueSeverity, + async_create_issue, + async_delete_issue, +) +from homeassistant.helpers.service import async_register_admin_service + +from .adapter import MatterAdapter +from .addon import get_addon_manager +from .api import async_register_api +from .const import CONF_INTEGRATION_CREATED_ADDON, CONF_USE_ADDON, DOMAIN, LOGGER +from .device_platform import DEVICE_PLATFORM + + +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Set up Matter from a config entry.""" + if use_addon := entry.data.get(CONF_USE_ADDON): + await _async_ensure_addon_running(hass, entry) + + matter_client = MatterClient(entry.data[CONF_URL], async_get_clientsession(hass)) + try: + await matter_client.connect() + except CannotConnect as err: + raise ConfigEntryNotReady("Failed to connect to matter server") from err + except InvalidServerVersion as err: + if use_addon: + addon_manager = _get_addon_manager(hass) + addon_manager.async_schedule_update_addon(catch_error=True) + else: + async_create_issue( + hass, + DOMAIN, + "invalid_server_version", + is_fixable=False, + severity=IssueSeverity.ERROR, + translation_key="invalid_server_version", + ) + raise ConfigEntryNotReady(f"Invalid server version: {err}") from err + + except Exception as err: + matter_client.logger.exception("Failed to connect to matter server") + raise ConfigEntryNotReady( + "Unknown error connecting to the Matter server" + ) from err + else: + async_delete_issue(hass, DOMAIN, "invalid_server_version") + + async def on_hass_stop(event: Event) -> None: + """Handle incoming stop event from Home Assistant.""" + await matter_client.disconnect() + + entry.async_on_unload( + hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, on_hass_stop) + ) + + # register websocket api + async_register_api(hass) + + # launch the matter client listen task in the background + # use the init_ready event to keep track if it did initialize successfully + init_ready = asyncio.Event() + listen_task = asyncio.create_task(matter_client.start_listening(init_ready)) + + try: + async with async_timeout.timeout(30): + await init_ready.wait() + except asyncio.TimeoutError as err: + listen_task.cancel() + raise ConfigEntryNotReady("Matter client not ready") from err + + if DOMAIN not in hass.data: + hass.data[DOMAIN] = {} + _async_init_services(hass) + + # we create an intermediate layer (adapter) which keeps track of our nodes + # and discovery of platform entities from the node's attributes + matter = MatterAdapter(hass, matter_client, entry) + hass.data[DOMAIN][entry.entry_id] = matter + + # forward platform setup to all platforms in the discovery schema + await hass.config_entries.async_forward_entry_setups(entry, DEVICE_PLATFORM) + + # start discovering of node entities as task + asyncio.create_task(matter.setup_nodes()) + + return True + + +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Unload a config entry.""" + unload_ok = await hass.config_entries.async_unload_platforms(entry, DEVICE_PLATFORM) + + if unload_ok: + matter: MatterAdapter = hass.data[DOMAIN].pop(entry.entry_id) + await matter.matter_client.disconnect() + + if entry.data.get(CONF_USE_ADDON) and entry.disabled_by: + addon_manager: AddonManager = get_addon_manager(hass) + LOGGER.debug("Stopping Matter Server add-on") + try: + await addon_manager.async_stop_addon() + except AddonError as err: + LOGGER.error("Failed to stop the Matter Server add-on: %s", err) + return False + + return unload_ok + + +async def async_remove_entry(hass: HomeAssistant, entry: ConfigEntry) -> None: + """Config entry is being removed.""" + + if not entry.data.get(CONF_INTEGRATION_CREATED_ADDON): + return + + addon_manager: AddonManager = get_addon_manager(hass) + try: + await addon_manager.async_stop_addon() + except AddonError as err: + LOGGER.error(err) + return + try: + await addon_manager.async_create_backup() + except AddonError as err: + LOGGER.error(err) + return + try: + await addon_manager.async_uninstall_addon() + except AddonError as err: + LOGGER.error(err) + + +async def async_remove_config_entry_device( + hass: HomeAssistant, config_entry: ConfigEntry, device_entry: dr.DeviceEntry +) -> bool: + """Remove a config entry from a device.""" + unique_id = None + + for ident in device_entry.identifiers: + if ident[0] == DOMAIN: + unique_id = ident[1] + break + + if not unique_id: + return True + + matter: MatterAdapter = hass.data[DOMAIN][config_entry.entry_id] + + for node in await matter.matter_client.get_nodes(): + if node.unique_id == unique_id: + await matter.matter_client.remove_node(node.node_id) + break + + return True + + +@callback +def get_matter(hass: HomeAssistant) -> MatterAdapter: + """Return MatterAdapter instance.""" + # NOTE: This assumes only one Matter connection/fabric can exist. + # Shall we support connecting to multiple servers in the client or by config entries? + # In case of the config entry we need to fix this. + matter: MatterAdapter = next(iter(hass.data[DOMAIN].values())) + return matter + + +@callback +def _async_init_services(hass: HomeAssistant) -> None: + """Init services.""" + + async def commission(call: ServiceCall) -> None: + """Handle commissioning.""" + matter_client = get_matter(hass).matter_client + try: + await matter_client.commission_with_code(call.data["code"]) + except FailedCommand as err: + raise HomeAssistantError(str(err)) from err + + async_register_admin_service( + hass, + DOMAIN, + "commission", + commission, + vol.Schema({"code": str}), + ) + + async def accept_shared_device(call: ServiceCall) -> None: + """Accept a shared device.""" + matter_client = get_matter(hass).matter_client + try: + await matter_client.commission_on_network(call.data["pin"]) + except FailedCommand as err: + raise HomeAssistantError(str(err)) from err + + async_register_admin_service( + hass, + DOMAIN, + "accept_shared_device", + accept_shared_device, + vol.Schema({"pin": vol.Coerce(int)}), + ) + + async def set_wifi(call: ServiceCall) -> None: + """Handle set wifi creds.""" + matter_client = get_matter(hass).matter_client + try: + await matter_client.set_wifi_credentials( + call.data["ssid"], call.data["password"] + ) + except FailedCommand as err: + raise HomeAssistantError(str(err)) from err + + async_register_admin_service( + hass, + DOMAIN, + "set_wifi", + set_wifi, + vol.Schema( + { + "ssid": str, + "password": str, + } + ), + ) + + async def set_thread_dataset(call: ServiceCall) -> None: + """Handle set Thread creds.""" + matter_client = get_matter(hass).matter_client + thread_dataset = bytes.fromhex(call.data["dataset"]) + try: + await matter_client.set_thread_operational_dataset(thread_dataset) + except FailedCommand as err: + raise HomeAssistantError(str(err)) from err + + async_register_admin_service( + hass, + DOMAIN, + "set_thread", + set_thread_dataset, + vol.Schema({"dataset": str}), + ) + + async def _node_id_from_ha_device_id(ha_device_id: str) -> int | None: + """Get node id from ha device id.""" + dev_reg = dr.async_get(hass) + device = dev_reg.async_get(ha_device_id) + + if device is None: + return None + + matter_id = next( + ( + identifier + for identifier in device.identifiers + if identifier[0] == DOMAIN + ), + None, + ) + + if not matter_id: + return None + + unique_id = matter_id[1] + + matter_client = get_matter(hass).matter_client + + # This could be more efficient + for node in await matter_client.get_nodes(): + if node.unique_id == unique_id: + return cast(int, node.node_id) + + return None + + async def open_commissioning_window(call: ServiceCall) -> None: + """Open commissioning window on specific node.""" + node_id = await _node_id_from_ha_device_id(call.data["device_id"]) + + if node_id is None: + raise HomeAssistantError("This is not a Matter device") + + matter_client = get_matter(hass).matter_client + + # We are sending device ID . + + try: + await matter_client.open_commissioning_window(node_id) + except FailedCommand as err: + raise HomeAssistantError(str(err)) from err + + async_register_admin_service( + hass, + DOMAIN, + "open_commissioning_window", + open_commissioning_window, + vol.Schema({"device_id": str}), + ) + + +async def _async_ensure_addon_running(hass: HomeAssistant, entry: ConfigEntry) -> None: + """Ensure that Matter Server add-on is installed and running.""" + addon_manager = _get_addon_manager(hass) + try: + addon_info = await addon_manager.async_get_addon_info() + except AddonError as err: + raise ConfigEntryNotReady(err) from err + + addon_state = addon_info.state + + if addon_state == AddonState.NOT_INSTALLED: + addon_manager.async_schedule_install_setup_addon( + addon_info.options, + catch_error=True, + ) + raise ConfigEntryNotReady + + if addon_state == AddonState.NOT_RUNNING: + addon_manager.async_schedule_start_addon(catch_error=True) + raise ConfigEntryNotReady + + +@callback +def _get_addon_manager(hass: HomeAssistant) -> AddonManager: + """Ensure that Matter Server add-on is updated and running. + + May only be used as part of async_setup_entry above. + """ + addon_manager: AddonManager = get_addon_manager(hass) + if addon_manager.task_in_progress(): + raise ConfigEntryNotReady + return addon_manager diff --git a/homeassistant/components/matter/adapter.py b/homeassistant/components/matter/adapter.py new file mode 100644 index 00000000000..c2ad11cb10f --- /dev/null +++ b/homeassistant/components/matter/adapter.py @@ -0,0 +1,141 @@ +"""Matter to Home Assistant adapter.""" +from __future__ import annotations + +import asyncio +import logging +from typing import TYPE_CHECKING + +from chip.clusters import Objects as all_clusters +from matter_server.common.models.node_device import AbstractMatterNodeDevice + +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import Platform +from homeassistant.core import HomeAssistant +from homeassistant.helpers import device_registry as dr +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from .const import DOMAIN +from .device_platform import DEVICE_PLATFORM + +if TYPE_CHECKING: + from matter_server.client import MatterClient + from matter_server.common.models.node import MatterNode + + +class MatterAdapter: + """Connect Matter into Home Assistant.""" + + def __init__( + self, + hass: HomeAssistant, + matter_client: MatterClient, + config_entry: ConfigEntry, + ) -> None: + """Initialize the adapter.""" + self.matter_client = matter_client + self.hass = hass + self.config_entry = config_entry + self.logger = logging.getLogger(__name__) + self.platform_handlers: dict[Platform, AddEntitiesCallback] = {} + self._platforms_set_up = asyncio.Event() + + def register_platform_handler( + self, platform: Platform, add_entities: AddEntitiesCallback + ) -> None: + """Register a platform handler.""" + self.platform_handlers[platform] = add_entities + if len(self.platform_handlers) == len(DEVICE_PLATFORM): + self._platforms_set_up.set() + + async def setup_nodes(self) -> None: + """Set up all existing nodes.""" + await self._platforms_set_up.wait() + for node in await self.matter_client.get_nodes(): + await self._setup_node(node) + + async def _setup_node(self, node: MatterNode) -> None: + """Set up an node.""" + self.logger.debug("Setting up entities for node %s", node.node_id) + + bridge_unique_id: str | None = None + + if node.aggregator_device_type_instance is not None: + node_info = node.root_device_type_instance.get_cluster(all_clusters.Basic) + self._create_device_registry( + node_info, node_info.nodeLabel or "Hub device", None + ) + bridge_unique_id = node_info.uniqueID + + for node_device in node.node_devices: + self._setup_node_device(node_device, bridge_unique_id) + + def _create_device_registry( + self, + info: all_clusters.Basic | all_clusters.BridgedDeviceBasic, + name: str, + bridge_unique_id: str | None, + ) -> None: + """Create a device registry entry.""" + dr.async_get(self.hass).async_get_or_create( + name=name, + config_entry_id=self.config_entry.entry_id, + identifiers={(DOMAIN, info.uniqueID)}, + hw_version=info.hardwareVersionString, + sw_version=info.softwareVersionString, + manufacturer=info.vendorName, + model=info.productName, + via_device=(DOMAIN, bridge_unique_id) if bridge_unique_id else None, + ) + + def _setup_node_device( + self, node_device: AbstractMatterNodeDevice, bridge_unique_id: str | None + ) -> None: + """Set up a node device.""" + node = node_device.node() + basic_info = node_device.device_info() + device_type_instances = node_device.device_type_instances() + + name = basic_info.nodeLabel + if not name and device_type_instances: + name = f"{device_type_instances[0].device_type.__doc__[:-1]} {node.node_id}" + + self._create_device_registry(basic_info, name, bridge_unique_id) + + for instance in device_type_instances: + created = False + + for platform, devices in DEVICE_PLATFORM.items(): + entity_descriptions = devices.get(instance.device_type) + + if entity_descriptions is None: + continue + + if not isinstance(entity_descriptions, list): + entity_descriptions = [entity_descriptions] + + entities = [] + for entity_description in entity_descriptions: + self.logger.debug( + "Creating %s entity for %s (%s)", + platform, + instance.device_type.__name__, + hex(instance.device_type.device_type), + ) + entities.append( + entity_description.entity_cls( + self.matter_client, + node_device, + instance, + entity_description, + ) + ) + + self.platform_handlers[platform](entities) + created = True + + if not created: + self.logger.warning( + "Found unsupported device %s (%s)", + type(instance).__name__, + hex(instance.device_type.device_type), + ) diff --git a/homeassistant/components/matter/addon.py b/homeassistant/components/matter/addon.py new file mode 100644 index 00000000000..84f430a58d8 --- /dev/null +++ b/homeassistant/components/matter/addon.py @@ -0,0 +1,17 @@ +"""Provide add-on management.""" +from __future__ import annotations + +from homeassistant.components.hassio import AddonManager +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.singleton import singleton + +from .const import ADDON_SLUG, DOMAIN, LOGGER + +DATA_ADDON_MANAGER = f"{DOMAIN}_addon_manager" + + +@singleton(DATA_ADDON_MANAGER) +@callback +def get_addon_manager(hass: HomeAssistant) -> AddonManager: + """Get the add-on manager.""" + return AddonManager(hass, LOGGER, "Matter Server", ADDON_SLUG) diff --git a/homeassistant/components/matter/api.py b/homeassistant/components/matter/api.py new file mode 100644 index 00000000000..36cf83fd0da --- /dev/null +++ b/homeassistant/components/matter/api.py @@ -0,0 +1,152 @@ +"""Handle websocket api for Matter.""" +from __future__ import annotations + +from collections.abc import Callable +from functools import wraps +from typing import Any + +from matter_server.client.exceptions import FailedCommand +import voluptuous as vol + +from homeassistant.components import websocket_api +from homeassistant.components.websocket_api import ActiveConnection +from homeassistant.core import HomeAssistant, callback + +from .adapter import MatterAdapter +from .const import DOMAIN + +ID = "id" +TYPE = "type" + + +@callback +def async_register_api(hass: HomeAssistant) -> None: + """Register all of our api endpoints.""" + websocket_api.async_register_command(hass, websocket_commission) + websocket_api.async_register_command(hass, websocket_commission_on_network) + websocket_api.async_register_command(hass, websocket_set_thread_dataset) + websocket_api.async_register_command(hass, websocket_set_wifi_credentials) + + +def async_get_matter_adapter(func: Callable) -> Callable: + """Decorate function to get the MatterAdapter.""" + + @wraps(func) + async def _get_matter( + hass: HomeAssistant, connection: ActiveConnection, msg: dict + ) -> None: + """Provide the Matter client to the function.""" + matter: MatterAdapter = next(iter(hass.data[DOMAIN].values())) + + await func(hass, connection, msg, matter) + + return _get_matter + + +def async_handle_failed_command(func: Callable) -> Callable: + """Decorate function to handle FailedCommand and send relevant error.""" + + @wraps(func) + async def async_handle_failed_command_func( + hass: HomeAssistant, + connection: ActiveConnection, + msg: dict[str, Any], + *args: Any, + **kwargs: Any, + ) -> None: + """Handle FailedCommand within function and send relevant error.""" + try: + await func(hass, connection, msg, *args, **kwargs) + except FailedCommand as err: + connection.send_error(msg[ID], err.error_code, err.args[0]) + + return async_handle_failed_command_func + + +@websocket_api.require_admin +@websocket_api.websocket_command( + { + vol.Required(TYPE): "matter/commission", + vol.Required("code"): str, + } +) +@websocket_api.async_response +@async_handle_failed_command +@async_get_matter_adapter +async def websocket_commission( + hass: HomeAssistant, + connection: ActiveConnection, + msg: dict[str, Any], + matter: MatterAdapter, +) -> None: + """Add a device to the network and commission the device.""" + await matter.matter_client.commission_with_code(msg["code"]) + connection.send_result(msg[ID]) + + +@websocket_api.require_admin +@websocket_api.websocket_command( + { + vol.Required(TYPE): "matter/commission_on_network", + vol.Required("pin"): int, + } +) +@websocket_api.async_response +@async_handle_failed_command +@async_get_matter_adapter +async def websocket_commission_on_network( + hass: HomeAssistant, + connection: ActiveConnection, + msg: dict[str, Any], + matter: MatterAdapter, +) -> None: + """Commission a device already on the network.""" + await matter.matter_client.commission_on_network(msg["pin"]) + connection.send_result(msg[ID]) + + +@websocket_api.require_admin +@websocket_api.websocket_command( + { + vol.Required(TYPE): "matter/set_thread", + vol.Required("thread_operation_dataset"): str, + } +) +@websocket_api.async_response +@async_handle_failed_command +@async_get_matter_adapter +async def websocket_set_thread_dataset( + hass: HomeAssistant, + connection: ActiveConnection, + msg: dict[str, Any], + matter: MatterAdapter, +) -> None: + """Set thread dataset.""" + await matter.matter_client.set_thread_operational_dataset( + msg["thread_operation_dataset"] + ) + connection.send_result(msg[ID]) + + +@websocket_api.require_admin +@websocket_api.websocket_command( + { + vol.Required(TYPE): "matter/set_wifi_credentials", + vol.Required("network_name"): str, + vol.Required("password"): str, + } +) +@websocket_api.async_response +@async_handle_failed_command +@async_get_matter_adapter +async def websocket_set_wifi_credentials( + hass: HomeAssistant, + connection: ActiveConnection, + msg: dict[str, Any], + matter: MatterAdapter, +) -> None: + """Set WiFi credentials for a device.""" + await matter.matter_client.set_wifi_credentials( + ssid=msg["network_name"], credentials=msg["password"] + ) + connection.send_result(msg[ID]) diff --git a/homeassistant/components/matter/config_flow.py b/homeassistant/components/matter/config_flow.py new file mode 100644 index 00000000000..f570b0cf14c --- /dev/null +++ b/homeassistant/components/matter/config_flow.py @@ -0,0 +1,325 @@ +"""Config flow for Matter integration.""" +from __future__ import annotations + +import asyncio +from typing import Any + +from matter_server.client import MatterClient +from matter_server.client.exceptions import CannotConnect, InvalidServerVersion +import voluptuous as vol + +from homeassistant import config_entries +from homeassistant.components.hassio import ( + AddonError, + AddonInfo, + AddonManager, + AddonState, + HassioServiceInfo, + is_hassio, +) +from homeassistant.const import CONF_URL +from homeassistant.core import HomeAssistant +from homeassistant.data_entry_flow import AbortFlow, FlowResult +from homeassistant.helpers import aiohttp_client + +from .addon import get_addon_manager +from .const import ( + ADDON_SLUG, + CONF_INTEGRATION_CREATED_ADDON, + CONF_USE_ADDON, + DOMAIN, + LOGGER, +) + +ADDON_SETUP_TIMEOUT = 5 +ADDON_SETUP_TIMEOUT_ROUNDS = 40 +DEFAULT_URL = "ws://localhost:5580/ws" +ON_SUPERVISOR_SCHEMA = vol.Schema({vol.Optional(CONF_USE_ADDON, default=True): bool}) + + +def get_manual_schema(user_input: dict[str, Any]) -> vol.Schema: + """Return a schema for the manual step.""" + default_url = user_input.get(CONF_URL, DEFAULT_URL) + return vol.Schema({vol.Required(CONF_URL, default=default_url): str}) + + +async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> None: + """Validate the user input allows us to connect.""" + client = MatterClient(data[CONF_URL], aiohttp_client.async_get_clientsession(hass)) + await client.connect() + + +def build_ws_address(host: str, port: int) -> str: + """Return the websocket address.""" + return f"ws://{host}:{port}/ws" + + +class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): + """Handle a config flow for Matter.""" + + VERSION = 1 + + def __init__(self) -> None: + """Set up flow instance.""" + self.ws_address: str | None = None + # If we install the add-on we should uninstall it on entry remove. + self.integration_created_addon = False + self.install_task: asyncio.Task | None = None + self.start_task: asyncio.Task | None = None + self.use_addon = False + + async def async_step_install_addon( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Install Matter Server add-on.""" + if not self.install_task: + self.install_task = self.hass.async_create_task(self._async_install_addon()) + return self.async_show_progress( + step_id="install_addon", progress_action="install_addon" + ) + + try: + await self.install_task + except AddonError as err: + self.install_task = None + LOGGER.error(err) + return self.async_show_progress_done(next_step_id="install_failed") + + self.integration_created_addon = True + self.install_task = None + + return self.async_show_progress_done(next_step_id="start_addon") + + async def async_step_install_failed( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Add-on installation failed.""" + return self.async_abort(reason="addon_install_failed") + + async def _async_install_addon(self) -> None: + """Install the Matter Server add-on.""" + addon_manager: AddonManager = get_addon_manager(self.hass) + try: + await addon_manager.async_schedule_install_addon() + finally: + # Continue the flow after show progress when the task is done. + self.hass.async_create_task( + self.hass.config_entries.flow.async_configure(flow_id=self.flow_id) + ) + + async def _async_get_addon_discovery_info(self) -> dict: + """Return add-on discovery info.""" + addon_manager: AddonManager = get_addon_manager(self.hass) + try: + discovery_info_config = await addon_manager.async_get_addon_discovery_info() + except AddonError as err: + LOGGER.error(err) + raise AbortFlow("addon_get_discovery_info_failed") from err + + return discovery_info_config + + async def async_step_start_addon( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Start Matter Server add-on.""" + if not self.start_task: + self.start_task = self.hass.async_create_task(self._async_start_addon()) + return self.async_show_progress( + step_id="start_addon", progress_action="start_addon" + ) + + try: + await self.start_task + except (CannotConnect, AddonError, AbortFlow) as err: + self.start_task = None + LOGGER.error(err) + return self.async_show_progress_done(next_step_id="start_failed") + + self.start_task = None + return self.async_show_progress_done(next_step_id="finish_addon_setup") + + async def async_step_start_failed( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Add-on start failed.""" + return self.async_abort(reason="addon_start_failed") + + async def _async_start_addon(self) -> None: + """Start the Matter Server add-on.""" + addon_manager: AddonManager = get_addon_manager(self.hass) + + try: + await addon_manager.async_schedule_start_addon() + # Sleep some seconds to let the add-on start properly before connecting. + for _ in range(ADDON_SETUP_TIMEOUT_ROUNDS): + await asyncio.sleep(ADDON_SETUP_TIMEOUT) + try: + if not (ws_address := self.ws_address): + discovery_info = await self._async_get_addon_discovery_info() + ws_address = self.ws_address = build_ws_address( + discovery_info["host"], discovery_info["port"] + ) + await validate_input(self.hass, {CONF_URL: ws_address}) + except (AbortFlow, CannotConnect) as err: + LOGGER.debug( + "Add-on not ready yet, waiting %s seconds: %s", + ADDON_SETUP_TIMEOUT, + err, + ) + else: + break + else: + raise CannotConnect("Failed to start Matter Server add-on: timeout") + finally: + # Continue the flow after show progress when the task is done. + self.hass.async_create_task( + self.hass.config_entries.flow.async_configure(flow_id=self.flow_id) + ) + + async def _async_get_addon_info(self) -> AddonInfo: + """Return Matter Server add-on info.""" + addon_manager: AddonManager = get_addon_manager(self.hass) + try: + addon_info: AddonInfo = await addon_manager.async_get_addon_info() + except AddonError as err: + LOGGER.error(err) + raise AbortFlow("addon_info_failed") from err + + return addon_info + + async def async_step_user( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Handle the initial step.""" + if is_hassio(self.hass): + return await self.async_step_on_supervisor() + + return await self.async_step_manual() + + async def async_step_manual( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Handle a manual configuration.""" + if user_input is None: + return self.async_show_form( + step_id="manual", data_schema=get_manual_schema({}) + ) + + errors = {} + + try: + await validate_input(self.hass, user_input) + except CannotConnect: + errors["base"] = "cannot_connect" + except InvalidServerVersion: + errors["base"] = "invalid_server_version" + except Exception: # pylint: disable=broad-except + LOGGER.exception("Unexpected exception") + errors["base"] = "unknown" + else: + self.ws_address = user_input[CONF_URL] + + return await self._async_create_entry_or_abort() + + return self.async_show_form( + step_id="manual", data_schema=get_manual_schema(user_input), errors=errors + ) + + async def async_step_hassio(self, discovery_info: HassioServiceInfo) -> FlowResult: + """Receive configuration from add-on discovery info. + + This flow is triggered by the Matter Server add-on. + """ + if discovery_info.slug != ADDON_SLUG: + return self.async_abort(reason="not_matter_addon") + + await self._async_handle_discovery_without_unique_id() + + self.ws_address = build_ws_address( + discovery_info.config["host"], discovery_info.config["port"] + ) + + return await self.async_step_hassio_confirm() + + async def async_step_hassio_confirm( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Confirm the add-on discovery.""" + if user_input is not None: + return await self.async_step_on_supervisor( + user_input={CONF_USE_ADDON: True} + ) + + return self.async_show_form(step_id="hassio_confirm") + + async def async_step_on_supervisor( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Handle logic when on Supervisor host.""" + if user_input is None: + return self.async_show_form( + step_id="on_supervisor", data_schema=ON_SUPERVISOR_SCHEMA + ) + if not user_input[CONF_USE_ADDON]: + return await self.async_step_manual() + + self.use_addon = True + + addon_info = await self._async_get_addon_info() + + if addon_info.state == AddonState.RUNNING: + return await self.async_step_finish_addon_setup() + + if addon_info.state == AddonState.NOT_RUNNING: + return await self.async_step_start_addon() + + return await self.async_step_install_addon() + + async def async_step_finish_addon_setup( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Prepare info needed to complete the config entry.""" + if not self.ws_address: + discovery_info = await self._async_get_addon_discovery_info() + ws_address = self.ws_address = build_ws_address( + discovery_info["host"], discovery_info["port"] + ) + # Check that we can connect to the address. + try: + await validate_input(self.hass, {CONF_URL: ws_address}) + except CannotConnect: + return self.async_abort(reason="cannot_connect") + + return await self._async_create_entry_or_abort() + + async def _async_create_entry_or_abort(self) -> FlowResult: + """Return a config entry for the flow or abort if already configured.""" + assert self.ws_address is not None + + if existing_config_entries := self._async_current_entries(): + config_entry = existing_config_entries[0] + self.hass.config_entries.async_update_entry( + config_entry, + data={ + **config_entry.data, + CONF_URL: self.ws_address, + CONF_USE_ADDON: self.use_addon, + CONF_INTEGRATION_CREATED_ADDON: self.integration_created_addon, + }, + title=self.ws_address, + ) + await self.hass.config_entries.async_reload(config_entry.entry_id) + raise AbortFlow("reconfiguration_successful") + + # Abort any other flows that may be in progress + for progress in self._async_in_progress(): + self.hass.config_entries.flow.async_abort(progress["flow_id"]) + + return self.async_create_entry( + title=self.ws_address, + data={ + CONF_URL: self.ws_address, + CONF_USE_ADDON: self.use_addon, + CONF_INTEGRATION_CREATED_ADDON: self.integration_created_addon, + }, + ) diff --git a/homeassistant/components/matter/const.py b/homeassistant/components/matter/const.py new file mode 100644 index 00000000000..c5ec1173ac0 --- /dev/null +++ b/homeassistant/components/matter/const.py @@ -0,0 +1,10 @@ +"""Constants for the Matter integration.""" +import logging + +ADDON_SLUG = "core_matter_server" + +CONF_INTEGRATION_CREATED_ADDON = "integration_created_addon" +CONF_USE_ADDON = "use_addon" + +DOMAIN = "matter" +LOGGER = logging.getLogger(__package__) diff --git a/homeassistant/components/matter/device_platform.py b/homeassistant/components/matter/device_platform.py new file mode 100644 index 00000000000..25a83d28b98 --- /dev/null +++ b/homeassistant/components/matter/device_platform.py @@ -0,0 +1,24 @@ +"""All mappings of Matter devices to Home Assistant platforms.""" +from __future__ import annotations + +from typing import TYPE_CHECKING + +from homeassistant.const import Platform + +from .light import DEVICE_ENTITY as LIGHT_DEVICE_ENTITY + +if TYPE_CHECKING: + from matter_server.common.models.device_types import DeviceType + + from .entity import MatterEntityDescriptionBaseClass + + +DEVICE_PLATFORM: dict[ + Platform, + dict[ + type[DeviceType], + MatterEntityDescriptionBaseClass | list[MatterEntityDescriptionBaseClass], + ], +] = { + Platform.LIGHT: LIGHT_DEVICE_ENTITY, +} diff --git a/homeassistant/components/matter/entity.py b/homeassistant/components/matter/entity.py new file mode 100644 index 00000000000..019631750f4 --- /dev/null +++ b/homeassistant/components/matter/entity.py @@ -0,0 +1,118 @@ +"""Matter entity base class.""" +from __future__ import annotations + +from abc import abstractmethod +from collections.abc import Callable +from dataclasses import dataclass +import logging +from typing import TYPE_CHECKING, Any + +from matter_server.common.models.device_type_instance import MatterDeviceTypeInstance +from matter_server.common.models.events import EventType +from matter_server.common.models.node_device import AbstractMatterNodeDevice + +from homeassistant.core import callback +from homeassistant.helpers.entity import DeviceInfo, Entity, EntityDescription + +from .const import DOMAIN + +if TYPE_CHECKING: + from matter_server.client import MatterClient + from matter_server.common.models.node import MatterAttribute + +LOGGER = logging.getLogger(__name__) + + +@dataclass +class MatterEntityDescription: + """Mixin to map a matter device to a Home Assistant entity.""" + + entity_cls: type[MatterEntity] + subscribe_attributes: tuple + + +@dataclass +class MatterEntityDescriptionBaseClass(EntityDescription, MatterEntityDescription): + """For typing a base class that inherits from both entity descriptions.""" + + +class MatterEntity(Entity): + """Entity class for Matter devices.""" + + entity_description: MatterEntityDescriptionBaseClass + _attr_should_poll = False + _attr_has_entity_name = True + + def __init__( + self, + matter_client: MatterClient, + node_device: AbstractMatterNodeDevice, + device_type_instance: MatterDeviceTypeInstance, + entity_description: MatterEntityDescriptionBaseClass, + ) -> None: + """Initialize the entity.""" + self.matter_client = matter_client + self._node_device = node_device + self._device_type_instance = device_type_instance + self.entity_description = entity_description + node = device_type_instance.node + self._unsubscribes: list[Callable] = [] + # for fast lookups we create a mapping to the attribute paths + self._attributes_map: dict[type, str] = {} + self._attr_unique_id = f"{matter_client.server_info.compressed_fabric_id}-{node.unique_id}-{device_type_instance.endpoint}-{device_type_instance.device_type.device_type}" + + @property + def device_info(self) -> DeviceInfo | None: + """Return device info for device registry.""" + return {"identifiers": {(DOMAIN, self._node_device.device_info().uniqueID)}} + + async def async_added_to_hass(self) -> None: + """Handle being added to Home Assistant.""" + await super().async_added_to_hass() + + # Subscribe to attribute updates. + for attr_cls in self.entity_description.subscribe_attributes: + if matter_attr := self.get_matter_attribute(attr_cls): + self._attributes_map[attr_cls] = matter_attr.path + self._unsubscribes.append( + self.matter_client.subscribe( + self._on_matter_event, + EventType.ATTRIBUTE_UPDATED, + self._device_type_instance.node.node_id, + matter_attr.path, + ) + ) + continue + # not sure if this can happen, but just in case log it. + LOGGER.warning("Attribute not found on device: %s", attr_cls) + + # make sure to update the attributes once + self._update_from_device() + + async def async_will_remove_from_hass(self) -> None: + """Run when entity will be removed from hass.""" + for unsub in self._unsubscribes: + unsub() + + @callback + def _on_matter_event(self, event: EventType, data: Any = None) -> None: + """Call on update.""" + self._update_from_device() + self.async_write_ha_state() + + @callback + @abstractmethod + def _update_from_device(self) -> None: + """Update data from Matter device.""" + + @callback + def get_matter_attribute(self, attribute: type) -> MatterAttribute | None: + """Lookup MatterAttribute instance on device instance by providing the attribute class.""" + return next( + ( + x + for x in self._device_type_instance.attributes + if x.attribute_type == attribute + ), + None, + ) diff --git a/homeassistant/components/matter/light.py b/homeassistant/components/matter/light.py new file mode 100644 index 00000000000..37454e3005c --- /dev/null +++ b/homeassistant/components/matter/light.py @@ -0,0 +1,173 @@ +"""Matter light.""" +from __future__ import annotations + +from dataclasses import dataclass +from functools import partial +from typing import TYPE_CHECKING, Any + +from chip.clusters import Objects as clusters +from matter_server.common.models import device_types + +from homeassistant.components.light import ( + ATTR_BRIGHTNESS, + ColorMode, + LightEntity, + LightEntityDescription, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import Platform +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from .const import DOMAIN +from .entity import MatterEntity, MatterEntityDescriptionBaseClass +from .util import renormalize + +if TYPE_CHECKING: + from .adapter import MatterAdapter + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up Matter Light from Config Entry.""" + matter: MatterAdapter = hass.data[DOMAIN][config_entry.entry_id] + matter.register_platform_handler(Platform.LIGHT, async_add_entities) + + +class MatterLight(MatterEntity, LightEntity): + """Representation of a Matter light.""" + + entity_description: MatterLightEntityDescription + + def _supports_brightness(self) -> bool: + """Return if device supports brightness.""" + return ( + clusters.LevelControl.Attributes.CurrentLevel + in self.entity_description.subscribe_attributes + ) + + async def async_turn_on(self, **kwargs: Any) -> None: + """Turn light on.""" + if ATTR_BRIGHTNESS not in kwargs or not self._supports_brightness(): + await self.matter_client.send_device_command( + node_id=self._device_type_instance.node.node_id, + endpoint=self._device_type_instance.endpoint, + command=clusters.OnOff.Commands.On(), + ) + return + + level_control = self._device_type_instance.get_cluster(clusters.LevelControl) + level = round( + renormalize( + kwargs[ATTR_BRIGHTNESS], + (0, 255), + (level_control.minLevel, level_control.maxLevel), + ) + ) + + await self.matter_client.send_device_command( + node_id=self._device_type_instance.node.node_id, + endpoint=self._device_type_instance.endpoint, + command=clusters.LevelControl.Commands.MoveToLevelWithOnOff( + level=level, + # It's required in TLV. We don't implement transition time yet. + transitionTime=0, + ), + ) + + async def async_turn_off(self, **kwargs: Any) -> None: + """Turn light off.""" + await self.matter_client.send_device_command( + node_id=self._device_type_instance.node.node_id, + endpoint=self._device_type_instance.endpoint, + command=clusters.OnOff.Commands.Off(), + ) + + @callback + def _update_from_device(self) -> None: + """Update from device.""" + if self._attr_supported_color_modes is None: + if self._supports_brightness(): + self._attr_supported_color_modes = {ColorMode.BRIGHTNESS} + + if attr := self.get_matter_attribute(clusters.OnOff.Attributes.OnOff): + self._attr_is_on = attr.value + + if ( + clusters.LevelControl.Attributes.CurrentLevel + in self.entity_description.subscribe_attributes + ): + level_control = self._device_type_instance.get_cluster( + clusters.LevelControl + ) + + # Convert brightness to Home Assistant = 0..255 + self._attr_brightness = round( + renormalize( + level_control.currentLevel, + (level_control.minLevel, level_control.maxLevel), + (0, 255), + ) + ) + + +@dataclass +class MatterLightEntityDescription( + LightEntityDescription, + MatterEntityDescriptionBaseClass, +): + """Matter light entity description.""" + + +# You can't set default values on inherited data classes +MatterLightEntityDescriptionFactory = partial( + MatterLightEntityDescription, entity_cls=MatterLight +) + +# Mapping of a Matter Device type to Light Entity Description. +# A Matter device type (instance) can consist of multiple attributes. +# For example a Color Light which has an attribute to control brightness +# but also for color. + +DEVICE_ENTITY: dict[ + type[device_types.DeviceType], + MatterEntityDescriptionBaseClass | list[MatterEntityDescriptionBaseClass], +] = { + device_types.OnOffLight: MatterLightEntityDescriptionFactory( + key=device_types.OnOffLight, + subscribe_attributes=(clusters.OnOff.Attributes.OnOff,), + ), + device_types.DimmableLight: MatterLightEntityDescriptionFactory( + key=device_types.DimmableLight, + subscribe_attributes=( + clusters.OnOff.Attributes.OnOff, + clusters.LevelControl.Attributes.CurrentLevel, + ), + ), + device_types.DimmablePlugInUnit: MatterLightEntityDescriptionFactory( + key=device_types.DimmablePlugInUnit, + subscribe_attributes=( + clusters.OnOff.Attributes.OnOff, + clusters.LevelControl.Attributes.CurrentLevel, + ), + ), + device_types.ColorTemperatureLight: MatterLightEntityDescriptionFactory( + key=device_types.ColorTemperatureLight, + subscribe_attributes=( + clusters.OnOff.Attributes.OnOff, + clusters.LevelControl.Attributes.CurrentLevel, + clusters.ColorControl, + ), + ), + device_types.ExtendedColorLight: MatterLightEntityDescriptionFactory( + key=device_types.ExtendedColorLight, + subscribe_attributes=( + clusters.OnOff.Attributes.OnOff, + clusters.LevelControl.Attributes.CurrentLevel, + clusters.ColorControl, + ), + ), +} diff --git a/homeassistant/components/matter/manifest.json b/homeassistant/components/matter/manifest.json new file mode 100644 index 00000000000..aa64ac4755e --- /dev/null +++ b/homeassistant/components/matter/manifest.json @@ -0,0 +1,10 @@ +{ + "domain": "matter", + "name": "Matter (BETA)", + "config_flow": true, + "documentation": "https://www.home-assistant.io/integrations/matter", + "requirements": ["python-matter-server==1.0.6"], + "dependencies": ["websocket_api"], + "codeowners": ["@MartinHjelmare", "@marcelveldt"], + "iot_class": "local_push" +} diff --git a/homeassistant/components/matter/services.yaml b/homeassistant/components/matter/services.yaml new file mode 100644 index 00000000000..18bb4be6452 --- /dev/null +++ b/homeassistant/components/matter/services.yaml @@ -0,0 +1,66 @@ +commission: + name: Commission device + description: > + Add a new device to your Matter network. + fields: + code: + name: Pairing code + description: The pairing code for the device. + required: true + selector: + text: +accept_shared_device: + name: Accept shared device + description: > + Add a shared device to your Matter network. + fields: + pin: + name: Pin code + description: The pin code for the device. + required: true + selector: + text: + +set_wifi: + name: Set Wi-Fi credentials + description: > + The Wi-Fi credentials will be sent as part of commissioning to a Matter device so it can connect to the Wi-Fi network. + fields: + ssid: + name: Network name + description: The SSID network name. + required: true + selector: + text: + password: + name: Password + description: The Wi-Fi network password. + required: true + selector: + text: + type: password +set_thread: + name: Set Thread network operational dataset + description: > + The Thread keys will be used as part of commissioning to let a Matter device join the Thread network. + + Get keys by running `ot-ctl dataset active -x` on the Open Thread Border Router. + fields: + thread_operation_dataset: + name: Thread Operational Dataset + description: The Thread Operational Dataset to set. + required: true + selector: + text: +open_commissioning_window: + name: Open Commissioning Window + description: > + Allow adding one of your devices to another Matter network by opening the commissioning window for this Matter device for 60 seconds. + fields: + device_id: + name: Device + description: The Matter device to add to the other Matter network. + required: true + selector: + device: + integration: matter diff --git a/homeassistant/components/matter/strings.json b/homeassistant/components/matter/strings.json new file mode 100644 index 00000000000..594998c236f --- /dev/null +++ b/homeassistant/components/matter/strings.json @@ -0,0 +1,47 @@ +{ + "config": { + "flow_title": "{name}", + "step": { + "manual": { + "data": { + "url": "[%key:common::config_flow::data::url%]" + } + }, + "on_supervisor": { + "title": "Select connection method", + "description": "Do you want to use the official Matter Server Supervisor add-on?\n\nIf you are already running the Matter Server in another add-on, in a custom container, natively etc., then do not select this option.", + "data": { + "use_addon": "Use the official Matter Server Supervisor add-on" + } + }, + "install_addon": { + "title": "The add-on installation has started" + }, + "start_addon": { + "title": "Starting add-on." + }, + "hassio_confirm": { + "title": "Set up the Matter integration with the Matter Server add-on" + } + }, + "error": { + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", + "invalid_server_version": "The Matter server is not the correct version", + "unknown": "[%key:common::config_flow::error::unknown%]" + }, + "abort": { + "addon_get_discovery_info_failed": "Failed to get Matter Server add-on discovery info.", + "addon_info_failed": "Failed to get Matter Server add-on info.", + "addon_install_failed": "Failed to install the Matter Server add-on.", + "addon_start_failed": "Failed to start the Matter Server add-on.", + "already_configured": "[%key:common::config_flow::abort::already_configured_device%]", + "already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]", + "not_matter_addon": "Discovered add-on is not the official Matter Server add-on.", + "reconfiguration_successful": "Successfully reconfigured the Matter integration." + }, + "progress": { + "install_addon": "Please wait while the Matter Server add-on installation finishes. This can take several minutes.", + "start_addon": "Please wait while the Matter Server add-on starts. This add-on is what powers Matter in Home Assistant. This may take some seconds." + } + } +} diff --git a/homeassistant/components/matter/translations/en.json b/homeassistant/components/matter/translations/en.json new file mode 100644 index 00000000000..a812772142f --- /dev/null +++ b/homeassistant/components/matter/translations/en.json @@ -0,0 +1,47 @@ +{ + "config": { + "abort": { + "addon_get_discovery_info_failed": "Failed to get Matter Server add-on discovery info.", + "addon_info_failed": "Failed to get Matter Server add-on info.", + "addon_install_failed": "Failed to install the Matter Server add-on.", + "addon_start_failed": "Failed to start the Matter Server add-on.", + "already_configured": "Device is already configured", + "already_in_progress": "Configuration flow is already in progress", + "not_matter_addon": "Discovered add-on is not the official Matter Server add-on.", + "reconfiguration_successful": "Successfully reconfigured the Matter integration." + }, + "error": { + "cannot_connect": "Failed to connect", + "invalid_server_version": "The Matter server is not the correct version", + "unknown": "Unexpected error" + }, + "flow_title": "{name}", + "progress": { + "install_addon": "Please wait while the Matter Server add-on installation finishes. This can take several minutes.", + "start_addon": "Please wait while the Matter Server add-on starts. This add-on is what powers Matter in Home Assistant. This may take some seconds." + }, + "step": { + "hassio_confirm": { + "title": "Set up the Matter integration with the Matter Server add-on" + }, + "install_addon": { + "title": "The add-on installation has started" + }, + "manual": { + "data": { + "url": "URL" + } + }, + "on_supervisor": { + "data": { + "use_addon": "Use the official Matter Server Supervisor add-on" + }, + "description": "Do you want to use the official Matter Server Supervisor add-on?\n\nIf you are already running the Matter Server in another add-on, in a custom container, natively etc., then do not select this option.", + "title": "Select connection method" + }, + "start_addon": { + "title": "Starting add-on." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/matter/util.py b/homeassistant/components/matter/util.py new file mode 100644 index 00000000000..9f51ee0c0e6 --- /dev/null +++ b/homeassistant/components/matter/util.py @@ -0,0 +1,11 @@ +"""Provide integration utilities.""" +from __future__ import annotations + + +def renormalize( + number: float, from_range: tuple[float, float], to_range: tuple[float, float] +) -> float: + """Change value from from_range to to_range.""" + delta1 = from_range[1] - from_range[0] + delta2 = to_range[1] - to_range[0] + return (delta2 * (number - from_range[0]) / delta1) + to_range[0] diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index 97c7b925378..5875c9021f6 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -235,6 +235,7 @@ FLOWS = { "lutron_caseta", "lyric", "mailgun", + "matter", "mazda", "meater", "melcloud", diff --git a/homeassistant/generated/integrations.json b/homeassistant/generated/integrations.json index 5b24a1ae02e..02068ecafa5 100644 --- a/homeassistant/generated/integrations.json +++ b/homeassistant/generated/integrations.json @@ -3046,6 +3046,12 @@ "config_flow": false, "iot_class": "cloud_push" }, + "matter": { + "name": "Matter (BETA)", + "integration_type": "hub", + "config_flow": true, + "iot_class": "local_push" + }, "mazda": { "name": "Mazda Connected Services", "integration_type": "hub", diff --git a/mypy.ini b/mypy.ini index 8d15bee1460..5a6615d0f48 100644 --- a/mypy.ini +++ b/mypy.ini @@ -1543,6 +1543,16 @@ disallow_untyped_defs = true warn_return_any = true warn_unreachable = true +[mypy-homeassistant.components.matter.*] +check_untyped_defs = true +disallow_incomplete_defs = true +disallow_subclassing_any = true +disallow_untyped_calls = true +disallow_untyped_decorators = true +disallow_untyped_defs = true +warn_return_any = true +warn_unreachable = true + [mypy-homeassistant.components.media_player.*] check_untyped_defs = true disallow_incomplete_defs = true diff --git a/requirements_all.txt b/requirements_all.txt index 0eee6b15f39..6b97c0c5868 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2029,6 +2029,9 @@ python-kasa==0.5.0 # homeassistant.components.lirc # python-lirc==1.2.3 +# homeassistant.components.matter +python-matter-server==1.0.6 + # homeassistant.components.xiaomi_miio python-miio==0.5.12 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index fd43ce5ab4c..44eddd3a92f 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1416,6 +1416,9 @@ python-juicenet==1.1.0 # homeassistant.components.tplink python-kasa==0.5.0 +# homeassistant.components.matter +python-matter-server==1.0.6 + # homeassistant.components.xiaomi_miio python-miio==0.5.12 diff --git a/tests/components/matter/__init__.py b/tests/components/matter/__init__.py new file mode 100644 index 00000000000..a2452274b14 --- /dev/null +++ b/tests/components/matter/__init__.py @@ -0,0 +1 @@ +"""Tests for the Matter integration.""" diff --git a/tests/components/matter/common.py b/tests/components/matter/common.py new file mode 100644 index 00000000000..d26842a728f --- /dev/null +++ b/tests/components/matter/common.py @@ -0,0 +1,146 @@ +"""Provide common test tools.""" +from __future__ import annotations + +import asyncio +from functools import cache +import json +import logging +from typing import TYPE_CHECKING, Any +from unittest.mock import Mock, patch + +from matter_server.client import MatterClient +from matter_server.common.models.node import MatterNode +from matter_server.common.models.server_information import ServerInfo +import pytest + +from tests.common import MockConfigEntry, load_fixture + +if TYPE_CHECKING: + from homeassistant.core import HomeAssistant + +MOCK_FABRIC_ID = 12341234 +MOCK_COMPR_FABRIC_ID = 1234 + +# TEMP: Tests need to be fixed +pytestmark = pytest.mark.skip("all tests still WIP") + + +class MockClient(MatterClient): + """Represent a mock Matter client.""" + + mock_client_disconnect: asyncio.Event + mock_commands: dict[type, Any] = {} + mock_sent_commands: list[dict[str, Any]] = [] + + def __init__(self) -> None: + """Initialize the mock client.""" + super().__init__("mock-url", None) + self.mock_commands: dict[type, Any] = {} + self.mock_sent_commands = [] + self.server_info = ServerInfo( + fabric_id=MOCK_FABRIC_ID, compressed_fabric_id=MOCK_COMPR_FABRIC_ID + ) + + async def connect(self) -> None: + """Connect to the Matter server.""" + self.server_info = Mock(compressed_abric_d=MOCK_COMPR_FABRIC_ID) + + async def listen(self, driver_ready: asyncio.Event) -> None: + """Listen for events.""" + driver_ready.set() + self.mock_client_disconnect = asyncio.Event() + await self.mock_client_disconnect.wait() + + def mock_command(self, command_type: type, response: Any) -> None: + """Mock a command.""" + self.mock_commands[command_type] = response + + async def async_send_command( + self, + command: str, + args: dict[str, Any], + require_schema: int | None = None, + ) -> dict: + """Send mock commands.""" + if command == "device_controller.SendCommand" and ( + (cmd_type := type(args.get("payload"))) in self.mock_commands + ): + self.mock_sent_commands.append(args) + return self.mock_commands[cmd_type] + + return await super().async_send_command(command, args, require_schema) + + async def async_send_command_no_wait( + self, command: str, args: dict[str, Any], require_schema: int | None = None + ) -> None: + """Send a command without waiting for the response.""" + if command == "SendCommand" and ( + (cmd_type := type(args.get("payload"))) in self.mock_commands + ): + self.mock_sent_commands.append(args) + return self.mock_commands[cmd_type] + + return await super().async_send_command_no_wait(command, args, require_schema) + + +@pytest.fixture +async def mock_matter() -> Mock: + """Mock matter fixture.""" + return await get_mock_matter() + + +async def get_mock_matter() -> Mock: + """Get mock Matter.""" + return Mock( + adapter=Mock(logger=logging.getLogger("mock_matter")), client=MockClient() + ) + + +@cache +def load_node_fixture(fixture: str) -> str: + """Load a fixture.""" + return load_fixture(f"matter/nodes/{fixture}.json") + + +def load_and_parse_node_fixture(fixture: str) -> dict[str, Any]: + """Load and parse a node fixture.""" + return json.loads(load_node_fixture(fixture)) + + +async def setup_integration_with_node_fixture( + hass: HomeAssistant, hass_storage: dict[str, Any], node_fixture: str +) -> MatterNode: + """Set up Matter integration with fixture as node.""" + node_data = load_and_parse_node_fixture(node_fixture) + node = MatterNode( + await get_mock_matter(), + node_data, + ) + config_entry = MockConfigEntry( + domain="matter", data={"url": "http://mock-matter-server-url"} + ) + config_entry.add_to_hass(hass) + + storage_key = f"matter_{config_entry.entry_id}" + hass_storage[storage_key] = { + "version": 1, + "minor_version": 0, + "key": storage_key, + "data": { + "compressed_fabric_id": MOCK_COMPR_FABRIC_ID, + "next_node_id": 4339, + "nodes": {str(node.node_id): node_data}, + }, + } + + with patch( + "matter_server.client.matter.Client", return_value=node.matter.client + ), patch( + "matter_server.client.model.node.MatterDeviceTypeInstance.subscribe_updates", + ), patch( + "matter_server.client.model.node.MatterDeviceTypeInstance.update_attributes" + ): + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + return node diff --git a/tests/components/matter/conftest.py b/tests/components/matter/conftest.py new file mode 100644 index 00000000000..6a8ffd152bc --- /dev/null +++ b/tests/components/matter/conftest.py @@ -0,0 +1,187 @@ +"""Provide common fixtures.""" +from __future__ import annotations + +import asyncio +from collections.abc import AsyncGenerator, Generator +from unittest.mock import AsyncMock, MagicMock, patch + +import pytest + +from homeassistant.core import HomeAssistant + +from tests.common import MockConfigEntry + + +@pytest.fixture(name="matter_client") +async def matter_client_fixture() -> AsyncGenerator[MagicMock, None]: + """Fixture for a Matter client.""" + with patch( + "homeassistant.components.matter.MatterClient", autospec=True + ) as client_class: + client = client_class.return_value + + async def connect() -> None: + """Mock connect.""" + await asyncio.sleep(0) + client.connected = True + + async def listen(init_ready: asyncio.Event | None) -> None: + """Mock listen.""" + if init_ready is not None: + init_ready.set() + + client.connect = AsyncMock(side_effect=connect) + client.start_listening = AsyncMock(side_effect=listen) + + yield client + + +@pytest.fixture(name="integration") +async def integration_fixture( + hass: HomeAssistant, matter_client: MagicMock +) -> MockConfigEntry: + """Set up the Matter integration.""" + entry = MockConfigEntry(domain="matter", data={"url": "ws://localhost:5580/ws"}) + entry.add_to_hass(hass) + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + return entry + + +@pytest.fixture(name="create_backup") +def create_backup_fixture() -> Generator[AsyncMock, None, None]: + """Mock Supervisor create backup of add-on.""" + with patch( + "homeassistant.components.hassio.addon_manager.async_create_backup" + ) as create_backup: + yield create_backup + + +@pytest.fixture(name="addon_store_info") +def addon_store_info_fixture() -> Generator[AsyncMock, None, None]: + """Mock Supervisor add-on store info.""" + with patch( + "homeassistant.components.hassio.addon_manager.async_get_addon_store_info" + ) as addon_store_info: + addon_store_info.return_value = { + "installed": None, + "state": None, + "version": "1.0.0", + } + yield addon_store_info + + +@pytest.fixture(name="addon_info") +def addon_info_fixture() -> Generator[AsyncMock, None, None]: + """Mock Supervisor add-on info.""" + with patch( + "homeassistant.components.hassio.addon_manager.async_get_addon_info", + ) as addon_info: + addon_info.return_value = { + "hostname": None, + "options": {}, + "state": None, + "update_available": False, + "version": None, + } + yield addon_info + + +@pytest.fixture(name="addon_not_installed") +def addon_not_installed_fixture( + addon_store_info: AsyncMock, addon_info: AsyncMock +) -> AsyncMock: + """Mock add-on not installed.""" + return addon_info + + +@pytest.fixture(name="addon_installed") +def addon_installed_fixture( + addon_store_info: AsyncMock, addon_info: AsyncMock +) -> AsyncMock: + """Mock add-on already installed but not running.""" + addon_store_info.return_value = { + "installed": "1.0.0", + "state": "stopped", + "version": "1.0.0", + } + addon_info.return_value["hostname"] = "core-matter-server" + addon_info.return_value["state"] = "stopped" + addon_info.return_value["version"] = "1.0.0" + return addon_info + + +@pytest.fixture(name="addon_running") +def addon_running_fixture( + addon_store_info: AsyncMock, addon_info: AsyncMock +) -> AsyncMock: + """Mock add-on already running.""" + addon_store_info.return_value = { + "installed": "1.0.0", + "state": "started", + "version": "1.0.0", + } + addon_info.return_value["hostname"] = "core-matter-server" + addon_info.return_value["state"] = "started" + addon_info.return_value["version"] = "1.0.0" + return addon_info + + +@pytest.fixture(name="install_addon") +def install_addon_fixture( + addon_store_info: AsyncMock, addon_info: AsyncMock +) -> Generator[AsyncMock, None, None]: + """Mock install add-on.""" + + async def install_addon_side_effect(hass: HomeAssistant, slug: str) -> None: + """Mock install add-on.""" + addon_store_info.return_value = { + "installed": "1.0.0", + "state": "stopped", + "version": "1.0.0", + } + addon_info.return_value["state"] = "stopped" + addon_info.return_value["version"] = "1.0.0" + + with patch( + "homeassistant.components.hassio.addon_manager.async_install_addon" + ) as install_addon: + install_addon.side_effect = install_addon_side_effect + yield install_addon + + +@pytest.fixture(name="start_addon") +def start_addon_fixture() -> Generator[AsyncMock, None, None]: + """Mock start add-on.""" + with patch( + "homeassistant.components.hassio.addon_manager.async_start_addon" + ) as start_addon: + yield start_addon + + +@pytest.fixture(name="stop_addon") +def stop_addon_fixture() -> Generator[AsyncMock, None, None]: + """Mock stop add-on.""" + with patch( + "homeassistant.components.hassio.addon_manager.async_stop_addon" + ) as stop_addon: + yield stop_addon + + +@pytest.fixture(name="uninstall_addon") +def uninstall_addon_fixture() -> Generator[AsyncMock, None, None]: + """Mock uninstall add-on.""" + with patch( + "homeassistant.components.hassio.addon_manager.async_uninstall_addon" + ) as uninstall_addon: + yield uninstall_addon + + +@pytest.fixture(name="update_addon") +def update_addon_fixture() -> Generator[AsyncMock, None, None]: + """Mock update add-on.""" + with patch( + "homeassistant.components.hassio.addon_manager.async_update_addon" + ) as update_addon: + yield update_addon diff --git a/tests/components/matter/fixtures/nodes/fake-bridge-two-light.json b/tests/components/matter/fixtures/nodes/fake-bridge-two-light.json new file mode 100644 index 00000000000..49cc1e4f217 --- /dev/null +++ b/tests/components/matter/fixtures/nodes/fake-bridge-two-light.json @@ -0,0 +1,174 @@ +{ + "attributes": { + "0": { + "Descriptor": { + "deviceTypeList": [ + { + "type": 22, + "revision": 1, + "_type": "chip.clusters.Objects.Descriptor.Structs.DeviceTypeStruct" + }, + { + "type": 14, + "revision": 1, + "_type": "chip.clusters.Objects.Descriptor.Structs.DeviceTypeStruct" + } + ], + "serverList": [29, 37, 40, 48, 49, 50, 51, 60, 62, 64, 65], + "clientList": [], + "partsList": [9, 10], + "generatedCommandList": [], + "acceptedCommandList": [], + "attributeList": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533], + "featureMap": 0, + "clusterRevision": 1, + "_type": "chip.clusters.Objects.Descriptor" + }, + "Basic": { + "dataModelRevision": 0, + "vendorName": "Mock Vendor", + "vendorID": 1234, + "productName": "Mock Bridge", + "productID": 2, + "nodeLabel": "My Mock Bridge", + "location": "nl", + "hardwareVersion": 123, + "hardwareVersionString": "TEST_VERSION", + "softwareVersion": 12345, + "softwareVersionString": "123.4.5", + "manufacturingDate": null, + "partNumber": null, + "productURL": null, + "productLabel": null, + "serialNumber": null, + "localConfigDisabled": null, + "reachable": null, + "uniqueID": "mock-hub-id", + "capabilityMinima": null, + "generatedCommandList": [], + "acceptedCommandList": [], + "attributeList": [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 18, 65528, 65529, 65531, 65532, + 65533 + ], + "featureMap": 0, + "clusterRevision": 3, + "_type": "chip.clusters.Objects.Basic" + } + }, + "9": { + "OnOff": { + "onOff": true, + "globalSceneControl": true, + "onTime": 0, + "offWaitTime": 0, + "startUpOnOff": 0, + "generatedCommandList": [], + "acceptedCommandList": [0, 1, 2, 64, 65, 66], + "attributeList": [ + 0, 16384, 16385, 16386, 16387, 65528, 65529, 65531, 65532, 65533 + ], + "featureMap": 1, + "clusterRevision": 4, + "_type": "chip.clusters.Objects.OnOff" + }, + "Descriptor": { + "deviceTypeList": [ + { + "type": 256, + "revision": 1, + "_type": "chip.clusters.Objects.Descriptor.Structs.DeviceTypeStruct" + }, + { + "type": 19, + "revision": 1, + "_type": "chip.clusters.Objects.Descriptor.Structs.DeviceTypeStruct" + } + ], + "serverList": [6, 29, 57, 768, 8, 40], + "clientList": [], + "partsList": [], + "generatedCommandList": [], + "acceptedCommandList": [], + "attributeList": [0, 1, 2, 3, 65528, 65529, 65531, 65533], + "featureMap": null, + "clusterRevision": 1, + "_type": "chip.clusters.Objects.Descriptor" + }, + "BridgedDeviceBasic": { + "nodeLabel": "Kitchen Ceiling", + "reachable": true, + "vendorID": 1234, + "softwareVersionString": "67.8.9", + "softwareVersion": 6789, + "vendorName": "Mock Vendor", + "productName": "Mock Light", + "uniqueID": "mock-id-kitchen-ceiling", + "generatedCommandList": [], + "acceptedCommandList": [], + "attributeList": [ + 5, 17, 2, 4, 10, 9, 1, 3, 18, 65528, 65529, 65531, 65532, 65533 + ], + "_type": "chip.clusters.Objects.BridgedDeviceBasic" + } + }, + "10": { + "OnOff": { + "onOff": false, + "globalSceneControl": true, + "onTime": 0, + "offWaitTime": 0, + "startUpOnOff": 0, + "generatedCommandList": [], + "acceptedCommandList": [0, 1, 2, 64, 65, 66], + "attributeList": [ + 0, 16384, 16385, 16386, 16387, 65528, 65529, 65531, 65532, 65533 + ], + "featureMap": 1, + "clusterRevision": 4, + "_type": "chip.clusters.Objects.OnOff" + }, + "Descriptor": { + "deviceTypeList": [ + { + "type": 256, + "revision": 1, + "_type": "chip.clusters.Objects.Descriptor.Structs.DeviceTypeStruct" + }, + { + "type": 19, + "revision": 1, + "_type": "chip.clusters.Objects.Descriptor.Structs.DeviceTypeStruct" + } + ], + "serverList": [6, 29, 57, 768, 40], + "clientList": [], + "partsList": [], + "generatedCommandList": [], + "acceptedCommandList": [], + "attributeList": [0, 1, 2, 3, 65528, 65529, 65531, 65533], + "featureMap": null, + "clusterRevision": 1, + "_type": "chip.clusters.Objects.Descriptor" + }, + "BridgedDeviceBasic": { + "nodeLabel": "Living Room Ceiling", + "reachable": true, + "vendorID": 1234, + "softwareVersionString": "1.49.1", + "softwareVersion": 19988481, + "vendorName": "Mock Vendor", + "productName": "Mock Light", + "uniqueID": "mock-id-living-room-ceiling", + "generatedCommandList": [], + "acceptedCommandList": [], + "attributeList": [ + 5, 17, 2, 4, 10, 9, 1, 3, 18, 65528, 65529, 65531, 65532, 65533 + ], + "_type": "chip.clusters.Objects.BridgedDeviceBasic" + } + } + }, + "events": [], + "node_id": 4338 +} diff --git a/tests/components/matter/fixtures/nodes/lighting-example-app.json b/tests/components/matter/fixtures/nodes/lighting-example-app.json new file mode 100644 index 00000000000..06e903d866c --- /dev/null +++ b/tests/components/matter/fixtures/nodes/lighting-example-app.json @@ -0,0 +1,882 @@ +{ + "attributes": { + "0": { + "Groups": { + "nameSupport": 128, + "generatedCommandList": [0, 1, 2, 3], + "acceptedCommandList": [0, 1, 2, 3, 4, 5], + "attributeList": [0, 65528, 65529, 65531, 65532, 65533], + "featureMap": 0, + "clusterRevision": 4, + "_type": "chip.clusters.Objects.Groups" + }, + "Descriptor": { + "deviceTypeList": [ + { + "type": 22, + "revision": 1, + "_type": "chip.clusters.Objects.Descriptor.Structs.DeviceTypeStruct" + } + ], + "serverList": [ + 4, 29, 31, 40, 42, 43, 44, 48, 49, 50, 51, 52, 53, 54, 55, 59, 60, 62, + 63, 64, 65 + ], + "clientList": [41], + "partsList": [1], + "generatedCommandList": [], + "acceptedCommandList": [], + "attributeList": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533], + "featureMap": 0, + "clusterRevision": 1, + "_type": "chip.clusters.Objects.Descriptor" + }, + "AccessControl": { + "acl": [ + { + "privilege": 5, + "authMode": 2, + "subjects": [1], + "targets": null, + "fabricIndex": 1, + "_type": "chip.clusters.Objects.AccessControl.Structs.AccessControlEntry" + } + ], + "extension": [], + "subjectsPerAccessControlEntry": 4, + "targetsPerAccessControlEntry": 3, + "accessControlEntriesPerFabric": 3, + "generatedCommandList": [], + "acceptedCommandList": [], + "attributeList": [0, 1, 2, 3, 4, 65528, 65529, 65531, 65532, 65533], + "featureMap": 0, + "clusterRevision": 1, + "_type": "chip.clusters.Objects.AccessControl" + }, + "Basic": { + "dataModelRevision": 1, + "vendorName": "Nabu Casa", + "vendorID": 65521, + "productName": "M5STAMP Lighting App", + "productID": 32768, + "nodeLabel": "My Cool Light", + "location": "XX", + "hardwareVersion": 0, + "hardwareVersionString": "v1.0", + "softwareVersion": 1, + "softwareVersionString": "55ab764bea", + "manufacturingDate": "20200101", + "partNumber": "", + "productURL": "", + "productLabel": "", + "serialNumber": "", + "localConfigDisabled": false, + "reachable": true, + "uniqueID": "BE8F70AA40DDAE41", + "capabilityMinima": { + "caseSessionsPerFabric": 3, + "subscriptionsPerFabric": 65506, + "_type": "chip.clusters.Objects.Basic.Structs.CapabilityMinimaStruct" + }, + "generatedCommandList": [], + "acceptedCommandList": [], + "attributeList": [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, + 65528, 65529, 65531, 65532, 65533 + ], + "featureMap": 0, + "clusterRevision": 1, + "_type": "chip.clusters.Objects.Basic" + }, + "OtaSoftwareUpdateRequestor": { + "defaultOtaProviders": [], + "updatePossible": true, + "updateState": 0, + "updateStateProgress": 0, + "generatedCommandList": [], + "acceptedCommandList": [0], + "attributeList": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533], + "featureMap": 0, + "clusterRevision": 1, + "_type": "chip.clusters.Objects.OtaSoftwareUpdateRequestor" + }, + "LocalizationConfiguration": { + "activeLocale": "en-US", + "supportedLocales": [ + "en-US", + "de-DE", + "fr-FR", + "en-GB", + "es-ES", + "zh-CN", + "it-IT", + "ja-JP" + ], + "generatedCommandList": [], + "acceptedCommandList": [], + "attributeList": [0, 1, 65528, 65529, 65531, 65532, 65533], + "featureMap": 0, + "clusterRevision": 1, + "_type": "chip.clusters.Objects.LocalizationConfiguration" + }, + "TimeFormatLocalization": { + "hourFormat": 0, + "activeCalendarType": 0, + "supportedCalendarTypes": [0, 1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 7], + "generatedCommandList": [], + "acceptedCommandList": [], + "attributeList": [0, 1, 2, 65528, 65529, 65531, 65532, 65533], + "featureMap": 0, + "clusterRevision": 1, + "_type": "chip.clusters.Objects.TimeFormatLocalization" + }, + "GeneralCommissioning": { + "breadcrumb": 0, + "basicCommissioningInfo": { + "failSafeExpiryLengthSeconds": 60, + "_type": "chip.clusters.Objects.GeneralCommissioning.Structs.BasicCommissioningInfo" + }, + "regulatoryConfig": 0, + "locationCapability": 0, + "supportsConcurrentConnection": true, + "generatedCommandList": [1, 3, 5], + "acceptedCommandList": [0, 2, 4], + "attributeList": [0, 1, 2, 3, 4, 65528, 65529, 65531, 65532, 65533], + "featureMap": 6, + "clusterRevision": 1, + "_type": "chip.clusters.Objects.GeneralCommissioning" + }, + "NetworkCommissioning": { + "maxNetworks": 1, + "networks": [ + { + "networkID": { + "_type": "bytes", + "value": "TGF6eUlvVA==" + }, + "connected": true, + "_type": "chip.clusters.Objects.NetworkCommissioning.Structs.NetworkInfo" + } + ], + "scanMaxTimeSeconds": 10, + "connectMaxTimeSeconds": 30, + "interfaceEnabled": true, + "lastNetworkingStatus": 0, + "lastNetworkID": { + "_type": "bytes", + "value": "TGF6eUlvVA==" + }, + "lastConnectErrorValue": null, + "generatedCommandList": [1, 5, 7], + "acceptedCommandList": [0, 2, 4, 6, 8], + "attributeList": [ + 0, 1, 2, 3, 4, 5, 6, 7, 65528, 65529, 65531, 65532, 65533 + ], + "featureMap": 1, + "clusterRevision": 1, + "_type": "chip.clusters.Objects.NetworkCommissioning" + }, + "DiagnosticLogs": { + "generatedCommandList": [1], + "acceptedCommandList": [0], + "attributeList": [65528, 65529, 65531, 65532, 65533], + "featureMap": 0, + "clusterRevision": 1, + "_type": "chip.clusters.Objects.DiagnosticLogs" + }, + "GeneralDiagnostics": { + "networkInterfaces": [ + { + "name": "WIFI_AP_DEF", + "isOperational": true, + "offPremiseServicesReachableIPv4": null, + "offPremiseServicesReachableIPv6": null, + "hardwareAddress": { + "_type": "bytes", + "value": "AAAAAAAA" + }, + "IPv4Addresses": [], + "IPv6Addresses": [], + "type": 1, + "_type": "chip.clusters.Objects.GeneralDiagnostics.Structs.NetworkInterfaceType" + }, + { + "name": "WIFI_STA_DEF", + "isOperational": true, + "offPremiseServicesReachableIPv4": null, + "offPremiseServicesReachableIPv6": null, + "hardwareAddress": { + "_type": "bytes", + "value": "hPcDJ8rI" + }, + "IPv4Addresses": [], + "IPv6Addresses": [], + "type": 1, + "_type": "chip.clusters.Objects.GeneralDiagnostics.Structs.NetworkInterfaceType" + } + ], + "rebootCount": 12, + "upTime": 458, + "totalOperationalHours": 0, + "bootReasons": 1, + "activeHardwareFaults": [], + "activeRadioFaults": [], + "activeNetworkFaults": [], + "testEventTriggersEnabled": false, + "generatedCommandList": [], + "acceptedCommandList": [0], + "attributeList": [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 65528, 65529, 65531, 65532, 65533 + ], + "featureMap": 0, + "clusterRevision": 1, + "_type": "chip.clusters.Objects.GeneralDiagnostics" + }, + "SoftwareDiagnostics": { + "threadMetrics": [], + "currentHeapFree": 116140, + "currentHeapUsed": 138932, + "currentHeapHighWatermark": 153796, + "generatedCommandList": [], + "acceptedCommandList": [0], + "attributeList": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533], + "featureMap": 0, + "clusterRevision": 1, + "_type": "chip.clusters.Objects.SoftwareDiagnostics" + }, + "ThreadNetworkDiagnostics": { + "TLVValue": { + "0": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + }, + "1": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + }, + "2": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + }, + "3": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + }, + "4": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + }, + "5": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + }, + "6": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + }, + "7": [], + "8": [], + "9": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + }, + "10": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + }, + "11": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + }, + "12": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + }, + "13": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + }, + "14": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + }, + "15": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + }, + "16": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + }, + "17": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + }, + "18": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + }, + "19": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + }, + "20": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + }, + "21": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + }, + "22": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + }, + "23": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + }, + "24": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + }, + "25": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + }, + "26": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + }, + "27": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + }, + "28": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + }, + "29": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + }, + "30": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + }, + "31": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + }, + "32": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + }, + "33": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + }, + "34": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + }, + "35": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + }, + "36": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + }, + "37": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + }, + "38": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + }, + "39": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + }, + "40": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + }, + "41": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + }, + "42": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + }, + "43": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + }, + "44": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + }, + "45": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + }, + "46": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + }, + "47": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + }, + "48": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + }, + "49": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + }, + "50": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + }, + "51": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + }, + "52": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + }, + "53": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + }, + "54": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + }, + "55": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + }, + "56": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + }, + "57": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + }, + "58": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + }, + "59": [], + "60": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + }, + "61": [], + "62": [], + "65532": 15, + "65533": 1, + "65528": [], + "65529": [0], + "65531": [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, + 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, + 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, + 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 65528, 65529, 65531, 65532, + 65533 + ] + }, + "Reason": "Failed to decode field [].channel, expected type , got " + }, + "WiFiNetworkDiagnostics": { + "bssid": { + "_type": "bytes", + "value": "1iH5ZUbu" + }, + "securityType": 4, + "wiFiVersion": 3, + "channelNumber": 1, + "rssi": -38, + "beaconLostCount": 0, + "beaconRxCount": 0, + "packetMulticastRxCount": 0, + "packetMulticastTxCount": 0, + "packetUnicastRxCount": 0, + "packetUnicastTxCount": 0, + "currentMaxRate": 0, + "overrunCount": 0, + "generatedCommandList": [], + "acceptedCommandList": [0], + "attributeList": [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 65528, 65529, 65531, 65532, + 65533 + ], + "featureMap": 3, + "clusterRevision": 1, + "_type": "chip.clusters.Objects.WiFiNetworkDiagnostics" + }, + "EthernetNetworkDiagnostics": { + "PHYRate": null, + "fullDuplex": null, + "packetRxCount": 0, + "packetTxCount": 0, + "txErrCount": 0, + "collisionCount": 0, + "overrunCount": 0, + "carrierDetect": null, + "timeSinceReset": 0, + "generatedCommandList": [], + "acceptedCommandList": [0], + "attributeList": [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 65528, 65529, 65531, 65532, 65533 + ], + "featureMap": 3, + "clusterRevision": 1, + "_type": "chip.clusters.Objects.EthernetNetworkDiagnostics" + }, + "Switch": { + "numberOfPositions": null, + "currentPosition": null, + "multiPressMax": null, + "generatedCommandList": [], + "acceptedCommandList": [], + "attributeList": [65528, 65529, 65531, 65532, 65533], + "featureMap": 0, + "clusterRevision": 1, + "_type": "chip.clusters.Objects.Switch" + }, + "AdministratorCommissioning": { + "windowStatus": 0, + "adminFabricIndex": 0, + "adminVendorId": 0, + "generatedCommandList": [], + "acceptedCommandList": [0, 1, 2], + "attributeList": [0, 1, 2, 65528, 65529, 65531, 65532, 65533], + "featureMap": 0, + "clusterRevision": 1, + "_type": "chip.clusters.Objects.AdministratorCommissioning" + }, + "OperationalCredentials": { + "NOCs": [ + { + "noc": { + "_type": "bytes", + "value": "FTABAQEkAgE3AyQTARgmBIAigScmBYAlTTo3BiQVASUR8RAYJAcBJAgBMAlBBHQsjZ/8Hpm4iqznEv0dAO03bZx8LDgqpIOpBsHeysZu8KAmI0K+p6B8FuI1h3wld1V+tIj5OHVHtrigg6Ssl043CjUBKAEYJAIBNgMEAgQBGDAEFEWrZiyeoUgEIXz4c40+Nzq9cfxHMAUUSTs2LnMMrX7nj+dns0cSq3SmK3MYMAtAoFdxyvsbLm6VekNCQ6yqJOucAcRSVY3Si4ov1alKPK9CaIPl+u5dvBWNfyEPXSLsPmzyfd2njl8WRz8e7CBiSRg=" + }, + "icac": { + "_type": "bytes", + "value": "FTABAQAkAgE3AyQUABgmBIAigScmBYAlTTo3BiQTARgkBwEkCAEwCUEE09c6S9xVbf3/blpXSgRAZzKXx/4KQC274cEfa2tFjdVAJYJUvM/8PMurRHEroPpA3FXpJ8/hfabkNvHGi2l8tTcKNQEpARgkAmAwBBRJOzYucwytfueP52ezRxKrdKYrczAFFBf0ohq+KHQlEVBIMgEeZCBPR72hGDALQNwd63sOjWKYhjlvDJmcPtIzljSsXlQ10vFrB5j9V9CdiZHDfy537G39fo0RJmpU63EGXYEtXVrEfSMiafshKVcY" + }, + "fabricIndex": 1, + "_type": "chip.clusters.Objects.OperationalCredentials.Structs.NOCStruct" + } + ], + "fabrics": [ + { + "rootPublicKey": { + "_type": "bytes", + "value": "BBGg+O3i3tDVYryXkUmEXk1fnSMHN06+poGIfZODdvbZW4JvxHnrQVAxvZWIE6poLa0sKA8X8A7jmJsVFMUqLFM=" + }, + "vendorId": 35328, + "fabricId": 1, + "nodeId": 4337, + "label": "", + "fabricIndex": 1, + "_type": "chip.clusters.Objects.OperationalCredentials.Structs.FabricDescriptor" + } + ], + "supportedFabrics": 5, + "commissionedFabrics": 1, + "trustedRootCertificates": [ + { + "_type": "bytes", + "value": "FTABAQAkAgE3AyQUABgmBIAigScmBYAlTTo3BiQUABgkBwEkCAEwCUEEEaD47eLe0NVivJeRSYReTV+dIwc3Tr6mgYh9k4N29tlbgm/EeetBUDG9lYgTqmgtrSwoDxfwDuOYmxUUxSosUzcKNQEpARgkAmAwBBQX9KIavih0JRFQSDIBHmQgT0e9oTAFFBf0ohq+KHQlEVBIMgEeZCBPR72hGDALQO3xFiF2cEXl+/kk0CQfedzHJxSJiziHEjWCMjIj7SVlDVx4CpvNYHnheq+9vJFgcL8JQhAEdz6p6C3INBDL7dsY" + } + ], + "currentFabricIndex": 1, + "generatedCommandList": [1, 3, 5, 8], + "acceptedCommandList": [0, 2, 4, 6, 7, 9, 10, 11], + "attributeList": [0, 1, 2, 3, 4, 5, 65528, 65529, 65531, 65532, 65533], + "featureMap": 0, + "clusterRevision": 1, + "_type": "chip.clusters.Objects.OperationalCredentials" + }, + "GroupKeyManagement": { + "groupKeyMap": [], + "groupTable": [], + "maxGroupsPerFabric": 3, + "maxGroupKeysPerFabric": 2, + "generatedCommandList": [2, 5], + "acceptedCommandList": [0, 1, 3, 4], + "attributeList": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533], + "featureMap": 0, + "clusterRevision": 1, + "_type": "chip.clusters.Objects.GroupKeyManagement" + }, + "FixedLabel": { + "labelList": [ + { + "label": "room", + "value": "bedroom 2", + "_type": "chip.clusters.Objects.FixedLabel.Structs.LabelStruct" + }, + { + "label": "orientation", + "value": "North", + "_type": "chip.clusters.Objects.FixedLabel.Structs.LabelStruct" + }, + { + "label": "floor", + "value": "2", + "_type": "chip.clusters.Objects.FixedLabel.Structs.LabelStruct" + }, + { + "label": "direction", + "value": "up", + "_type": "chip.clusters.Objects.FixedLabel.Structs.LabelStruct" + } + ], + "generatedCommandList": [], + "acceptedCommandList": [], + "attributeList": [0, 65528, 65529, 65531, 65532, 65533], + "featureMap": 0, + "clusterRevision": 1, + "_type": "chip.clusters.Objects.FixedLabel" + }, + "UserLabel": { + "labelList": [], + "generatedCommandList": [], + "acceptedCommandList": [], + "attributeList": [0, 65528, 65529, 65531, 65532, 65533], + "featureMap": 0, + "clusterRevision": 1, + "_type": "chip.clusters.Objects.UserLabel" + } + }, + "1": { + "Identify": { + "identifyTime": 0, + "identifyType": 0, + "generatedCommandList": [], + "acceptedCommandList": [0, 64], + "attributeList": [0, 1, 65528, 65529, 65531, 65532, 65533], + "featureMap": 0, + "clusterRevision": 4, + "_type": "chip.clusters.Objects.Identify" + }, + "Groups": { + "nameSupport": 128, + "generatedCommandList": [0, 1, 2, 3], + "acceptedCommandList": [0, 1, 2, 3, 4, 5], + "attributeList": [0, 65528, 65529, 65531, 65532, 65533], + "featureMap": 0, + "clusterRevision": 4, + "_type": "chip.clusters.Objects.Groups" + }, + "OnOff": { + "onOff": false, + "globalSceneControl": true, + "onTime": 0, + "offWaitTime": 0, + "startUpOnOff": null, + "generatedCommandList": [], + "acceptedCommandList": [0, 1, 2, 64, 65, 66], + "attributeList": [ + 0, 16384, 16385, 16386, 16387, 65528, 65529, 65531, 65532, 65533 + ], + "featureMap": 1, + "clusterRevision": 4, + "_type": "chip.clusters.Objects.OnOff" + }, + "LevelControl": { + "currentLevel": 254, + "remainingTime": 0, + "minLevel": 0, + "maxLevel": 254, + "currentFrequency": 0, + "minFrequency": 0, + "maxFrequency": 0, + "options": 0, + "onOffTransitionTime": 0, + "onLevel": null, + "onTransitionTime": 0, + "offTransitionTime": 0, + "defaultMoveRate": 50, + "startUpCurrentLevel": null, + "generatedCommandList": [], + "acceptedCommandList": [0, 1, 2, 3, 4, 5, 6, 7], + "attributeList": [ + 0, 1, 2, 3, 4, 5, 6, 15, 16, 17, 18, 19, 20, 16384, 65528, 65529, + 65531, 65532, 65533 + ], + "featureMap": 3, + "clusterRevision": 5, + "_type": "chip.clusters.Objects.LevelControl" + }, + "Descriptor": { + "deviceTypeList": [ + { + "type": 257, + "revision": 1, + "_type": "chip.clusters.Objects.Descriptor.Structs.DeviceTypeStruct" + } + ], + "serverList": [3, 4, 6, 8, 29, 768, 1030], + "clientList": [], + "partsList": [], + "generatedCommandList": [], + "acceptedCommandList": [], + "attributeList": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533], + "featureMap": 0, + "clusterRevision": 1, + "_type": "chip.clusters.Objects.Descriptor" + }, + "ColorControl": { + "currentHue": 0, + "currentSaturation": 0, + "remainingTime": 0, + "currentX": 24939, + "currentY": 24701, + "driftCompensation": null, + "compensationText": null, + "colorTemperatureMireds": 0, + "colorMode": 2, + "options": 0, + "numberOfPrimaries": 0, + "primary1X": null, + "primary1Y": null, + "primary1Intensity": null, + "primary2X": null, + "primary2Y": null, + "primary2Intensity": null, + "primary3X": null, + "primary3Y": null, + "primary3Intensity": null, + "primary4X": null, + "primary4Y": null, + "primary4Intensity": null, + "primary5X": null, + "primary5Y": null, + "primary5Intensity": null, + "primary6X": null, + "primary6Y": null, + "primary6Intensity": null, + "whitePointX": null, + "whitePointY": null, + "colorPointRX": null, + "colorPointRY": null, + "colorPointRIntensity": null, + "colorPointGX": null, + "colorPointGY": null, + "colorPointGIntensity": null, + "colorPointBX": null, + "colorPointBY": null, + "colorPointBIntensity": null, + "enhancedCurrentHue": 0, + "enhancedColorMode": 2, + "colorLoopActive": 0, + "colorLoopDirection": 0, + "colorLoopTime": 25, + "colorLoopStartEnhancedHue": 8960, + "colorLoopStoredEnhancedHue": 0, + "colorCapabilities": 0, + "colorTempPhysicalMinMireds": 0, + "colorTempPhysicalMaxMireds": 65279, + "coupleColorTempToLevelMinMireds": 0, + "startUpColorTemperatureMireds": 0, + "generatedCommandList": [], + "acceptedCommandList": [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 64, 65, 66, 67, 68, 71, 75, 76 + ], + "attributeList": [ + 0, 1, 2, 3, 4, 7, 8, 15, 16, 16384, 16385, 16386, 16387, 16388, 16389, + 16390, 16394, 16395, 16396, 16397, 16400, 65528, 65529, 65531, 65532, + 65533 + ], + "featureMap": 31, + "clusterRevision": 5, + "_type": "chip.clusters.Objects.ColorControl" + }, + "OccupancySensing": { + "occupancy": 0, + "occupancySensorType": 0, + "occupancySensorTypeBitmap": 1, + "pirOccupiedToUnoccupiedDelay": null, + "pirUnoccupiedToOccupiedDelay": null, + "pirUnoccupiedToOccupiedThreshold": null, + "ultrasonicOccupiedToUnoccupiedDelay": null, + "ultrasonicUnoccupiedToOccupiedDelay": null, + "ultrasonicUnoccupiedToOccupiedThreshold": null, + "physicalContactOccupiedToUnoccupiedDelay": null, + "physicalContactUnoccupiedToOccupiedDelay": null, + "physicalContactUnoccupiedToOccupiedThreshold": null, + "generatedCommandList": [], + "acceptedCommandList": [], + "attributeList": [0, 1, 2, 65528, 65529, 65531, 65532, 65533], + "featureMap": 0, + "clusterRevision": 3, + "_type": "chip.clusters.Objects.OccupancySensing" + } + } + }, + "events": [ + { + "Header": { + "EndpointId": 0, + "ClusterId": 40, + "EventId": 0, + "EventNumber": 262144, + "Priority": 2, + "Timestamp": 2019, + "TimestampType": 0 + }, + "Status": 0, + "Data": { + "softwareVersion": 1, + "_type": "chip.clusters.Objects.Basic.Events.StartUp" + } + }, + { + "Header": { + "EndpointId": 0, + "ClusterId": 51, + "EventId": 3, + "EventNumber": 262145, + "Priority": 2, + "Timestamp": 2020, + "TimestampType": 0 + }, + "Status": 0, + "Data": { + "bootReason": 1, + "_type": "chip.clusters.Objects.GeneralDiagnostics.Events.BootReason" + } + }, + { + "Header": { + "EndpointId": 0, + "ClusterId": 54, + "EventId": 2, + "EventNumber": 262146, + "Priority": 1, + "Timestamp": 2216, + "TimestampType": 0 + }, + "Status": 0, + "Data": { + "connectionStatus": 0, + "_type": "chip.clusters.Objects.WiFiNetworkDiagnostics.Events.ConnectionStatus" + } + } + ], + "node_id": 4337 +} diff --git a/tests/components/matter/test_adapter.py b/tests/components/matter/test_adapter.py new file mode 100644 index 00000000000..33a439d2b09 --- /dev/null +++ b/tests/components/matter/test_adapter.py @@ -0,0 +1,78 @@ +"""Test the adapter.""" +from __future__ import annotations + +from typing import Any + +import pytest + +from homeassistant.components.matter.const import DOMAIN +from homeassistant.core import HomeAssistant +from homeassistant.helpers import device_registry as dr + +from .common import setup_integration_with_node_fixture + +# TEMP: Tests need to be fixed +pytestmark = pytest.mark.skip("all tests still WIP") + + +async def test_device_registry_single_node_device( + hass: HomeAssistant, hass_storage: dict[str, Any] +) -> None: + """Test bridge devices are set up correctly with via_device.""" + await setup_integration_with_node_fixture( + hass, hass_storage, "lighting-example-app" + ) + + dev_reg = dr.async_get(hass) + + entry = dev_reg.async_get_device({(DOMAIN, "BE8F70AA40DDAE41")}) + assert entry is not None + + assert entry.name == "My Cool Light" + assert entry.manufacturer == "Nabu Casa" + assert entry.model == "M5STAMP Lighting App" + assert entry.hw_version == "v1.0" + assert entry.sw_version == "55ab764bea" + + +async def test_device_registry_bridge( + hass: HomeAssistant, hass_storage: dict[str, Any] +) -> None: + """Test bridge devices are set up correctly with via_device.""" + await setup_integration_with_node_fixture( + hass, hass_storage, "fake-bridge-two-light" + ) + + dev_reg = dr.async_get(hass) + + # Validate bridge + bridge_entry = dev_reg.async_get_device({(DOMAIN, "mock-hub-id")}) + assert bridge_entry is not None + + assert bridge_entry.name == "My Mock Bridge" + assert bridge_entry.manufacturer == "Mock Vendor" + assert bridge_entry.model == "Mock Bridge" + assert bridge_entry.hw_version == "TEST_VERSION" + assert bridge_entry.sw_version == "123.4.5" + + # Device 1 + device1_entry = dev_reg.async_get_device({(DOMAIN, "mock-id-kitchen-ceiling")}) + assert device1_entry is not None + + assert device1_entry.via_device_id == bridge_entry.id + assert device1_entry.name == "Kitchen Ceiling" + assert device1_entry.manufacturer == "Mock Vendor" + assert device1_entry.model == "Mock Light" + assert device1_entry.hw_version is None + assert device1_entry.sw_version == "67.8.9" + + # Device 2 + device2_entry = dev_reg.async_get_device({(DOMAIN, "mock-id-living-room-ceiling")}) + assert device2_entry is not None + + assert device2_entry.via_device_id == bridge_entry.id + assert device2_entry.name == "Living Room Ceiling" + assert device2_entry.manufacturer == "Mock Vendor" + assert device2_entry.model == "Mock Light" + assert device2_entry.hw_version is None + assert device2_entry.sw_version == "1.49.1" diff --git a/tests/components/matter/test_api.py b/tests/components/matter/test_api.py new file mode 100644 index 00000000000..6fe18d7c3b1 --- /dev/null +++ b/tests/components/matter/test_api.py @@ -0,0 +1,179 @@ +"""Test the api module.""" +from collections.abc import Awaitable, Callable +from unittest.mock import MagicMock, call + +from aiohttp import ClientWebSocketResponse +from matter_server.client.exceptions import FailedCommand + +from homeassistant.components.matter.api import ID, TYPE +from homeassistant.core import HomeAssistant + +from tests.common import MockConfigEntry + + +async def test_commission( + hass: HomeAssistant, + hass_ws_client: Callable[[HomeAssistant], Awaitable[ClientWebSocketResponse]], + matter_client: MagicMock, + integration: MockConfigEntry, +) -> None: + """Test the commission command.""" + ws_client = await hass_ws_client(hass) + + await ws_client.send_json( + { + ID: 1, + TYPE: "matter/commission", + "code": "12345678", + } + ) + msg = await ws_client.receive_json() + + assert msg["success"] + matter_client.commission_with_code.assert_called_once_with("12345678") + + matter_client.commission_with_code.reset_mock() + matter_client.commission_with_code.side_effect = FailedCommand( + "test_id", "test_code", "Failed to commission" + ) + + await ws_client.send_json( + { + ID: 2, + TYPE: "matter/commission", + "code": "12345678", + } + ) + msg = await ws_client.receive_json() + + assert not msg["success"] + assert msg["error"]["code"] == "test_code" + matter_client.commission_with_code.assert_called_once_with("12345678") + + +async def test_commission_on_network( + hass: HomeAssistant, + hass_ws_client: Callable[[HomeAssistant], Awaitable[ClientWebSocketResponse]], + matter_client: MagicMock, + integration: MockConfigEntry, +) -> None: + """Test the commission on network command.""" + ws_client = await hass_ws_client(hass) + + await ws_client.send_json( + { + ID: 1, + TYPE: "matter/commission_on_network", + "pin": 1234, + } + ) + msg = await ws_client.receive_json() + + assert msg["success"] + matter_client.commission_on_network.assert_called_once_with(1234) + + matter_client.commission_on_network.reset_mock() + matter_client.commission_on_network.side_effect = FailedCommand( + "test_id", "test_code", "Failed to commission on network" + ) + + await ws_client.send_json( + { + ID: 2, + TYPE: "matter/commission_on_network", + "pin": 1234, + } + ) + msg = await ws_client.receive_json() + + assert not msg["success"] + assert msg["error"]["code"] == "test_code" + matter_client.commission_on_network.assert_called_once_with(1234) + + +async def test_set_thread_dataset( + hass: HomeAssistant, + hass_ws_client: Callable[[HomeAssistant], Awaitable[ClientWebSocketResponse]], + matter_client: MagicMock, + integration: MockConfigEntry, +) -> None: + """Test the set thread dataset command.""" + ws_client = await hass_ws_client(hass) + + await ws_client.send_json( + { + ID: 1, + TYPE: "matter/set_thread", + "thread_operation_dataset": "test_dataset", + } + ) + msg = await ws_client.receive_json() + + assert msg["success"] + matter_client.set_thread_operational_dataset.assert_called_once_with("test_dataset") + + matter_client.set_thread_operational_dataset.reset_mock() + matter_client.set_thread_operational_dataset.side_effect = FailedCommand( + "test_id", "test_code", "Failed to commission" + ) + + await ws_client.send_json( + { + ID: 2, + TYPE: "matter/set_thread", + "thread_operation_dataset": "test_dataset", + } + ) + msg = await ws_client.receive_json() + + assert not msg["success"] + assert msg["error"]["code"] == "test_code" + matter_client.set_thread_operational_dataset.assert_called_once_with("test_dataset") + + +async def test_set_wifi_credentials( + hass: HomeAssistant, + hass_ws_client: Callable[[HomeAssistant], Awaitable[ClientWebSocketResponse]], + matter_client: MagicMock, + integration: MockConfigEntry, +) -> None: + """Test the set WiFi credentials command.""" + ws_client = await hass_ws_client(hass) + + await ws_client.send_json( + { + ID: 1, + TYPE: "matter/set_wifi_credentials", + "network_name": "test_network", + "password": "test_password", + } + ) + msg = await ws_client.receive_json() + + assert msg["success"] + assert matter_client.set_wifi_credentials.call_count == 1 + assert matter_client.set_wifi_credentials.call_args == call( + ssid="test_network", credentials="test_password" + ) + + matter_client.set_wifi_credentials.reset_mock() + matter_client.set_wifi_credentials.side_effect = FailedCommand( + "test_id", "test_code", "Failed to commission on network" + ) + + await ws_client.send_json( + { + ID: 2, + TYPE: "matter/set_wifi_credentials", + "network_name": "test_network", + "password": "test_password", + } + ) + msg = await ws_client.receive_json() + + assert not msg["success"] + assert msg["error"]["code"] == "test_code" + assert matter_client.set_wifi_credentials.call_count == 1 + assert matter_client.set_wifi_credentials.call_args == call( + ssid="test_network", credentials="test_password" + ) diff --git a/tests/components/matter/test_config_flow.py b/tests/components/matter/test_config_flow.py new file mode 100644 index 00000000000..cad8cd91eb0 --- /dev/null +++ b/tests/components/matter/test_config_flow.py @@ -0,0 +1,979 @@ +"""Test the Matter config flow.""" +from __future__ import annotations + +from collections.abc import Generator +from typing import Any +from unittest.mock import DEFAULT, AsyncMock, MagicMock, call, patch + +from matter_server.client.exceptions import CannotConnect, InvalidServerVersion +import pytest + +from homeassistant import config_entries +from homeassistant.components.hassio import HassioAPIError, HassioServiceInfo +from homeassistant.components.matter.const import ADDON_SLUG, DOMAIN +from homeassistant.core import HomeAssistant +from homeassistant.data_entry_flow import FlowResultType + +from tests.common import MockConfigEntry + +ADDON_DISCOVERY_INFO = { + "addon": "Matter Server", + "host": "host1", + "port": 5581, +} + + +@pytest.fixture(name="setup_entry", autouse=True) +def setup_entry_fixture() -> Generator[AsyncMock, None, None]: + """Mock entry setup.""" + with patch( + "homeassistant.components.matter.async_setup_entry", return_value=True + ) as mock_setup_entry: + yield mock_setup_entry + + +@pytest.fixture(name="client_connect", autouse=True) +def client_connect_fixture() -> Generator[AsyncMock, None, None]: + """Mock server version.""" + with patch( + "homeassistant.components.matter.config_flow.MatterClient.connect" + ) as client_connect: + yield client_connect + + +@pytest.fixture(name="supervisor") +def supervisor_fixture() -> Generator[MagicMock, None, None]: + """Mock Supervisor.""" + with patch( + "homeassistant.components.matter.config_flow.is_hassio", return_value=True + ) as is_hassio: + yield is_hassio + + +@pytest.fixture(name="discovery_info") +def discovery_info_fixture() -> Any: + """Return the discovery info from the supervisor.""" + return DEFAULT + + +@pytest.fixture(name="get_addon_discovery_info", autouse=True) +def get_addon_discovery_info_fixture( + discovery_info: Any, +) -> Generator[AsyncMock, None, None]: + """Mock get add-on discovery info.""" + with patch( + "homeassistant.components.hassio.addon_manager.async_get_addon_discovery_info", + return_value=discovery_info, + ) as get_addon_discovery_info: + yield get_addon_discovery_info + + +@pytest.fixture(name="addon_setup_time", autouse=True) +def addon_setup_time_fixture() -> Generator[int, None, None]: + """Mock add-on setup sleep time.""" + with patch( + "homeassistant.components.matter.config_flow.ADDON_SETUP_TIMEOUT", new=0 + ) as addon_setup_time: + yield addon_setup_time + + +async def test_manual_create_entry( + hass: HomeAssistant, + client_connect: AsyncMock, + setup_entry: AsyncMock, +) -> None: + """Test user step create entry.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result["type"] == FlowResultType.FORM + assert result["errors"] is None + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + "url": "ws://localhost:5580/ws", + }, + ) + await hass.async_block_till_done() + + assert client_connect.call_count == 1 + assert result["type"] == FlowResultType.CREATE_ENTRY + assert result["title"] == "ws://localhost:5580/ws" + assert result["data"] == { + "url": "ws://localhost:5580/ws", + "integration_created_addon": False, + "use_addon": False, + } + assert setup_entry.call_count == 1 + + +@pytest.mark.parametrize( + "error, side_effect", + [ + ("cannot_connect", CannotConnect(Exception("Boom"))), + ("invalid_server_version", InvalidServerVersion("Invalid version")), + ("unknown", Exception("Unknown boom")), + ], +) +async def test_manual_errors( + hass: HomeAssistant, + client_connect: AsyncMock, + error: str, + side_effect: Exception, +) -> None: + """Test user step cannot connect error.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + client_connect.side_effect = side_effect + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + "url": "ws://localhost:5580/ws", + }, + ) + + assert client_connect.call_count == 1 + assert result["type"] == FlowResultType.FORM + assert result["errors"] == {"base": error} + + +async def test_manual_already_configured( + hass: HomeAssistant, + client_connect: AsyncMock, + setup_entry: AsyncMock, +) -> None: + """Test manual step abort if already configured.""" + entry = MockConfigEntry( + domain=DOMAIN, data={"url": "ws://host1:5581/ws"}, title="Matter" + ) + entry.add_to_hass(hass) + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + assert result["type"] == FlowResultType.FORM + assert result["errors"] is None + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + "url": "ws://localhost:5580/ws", + }, + ) + await hass.async_block_till_done() + + assert client_connect.call_count == 1 + assert result["type"] == FlowResultType.ABORT + assert result["reason"] == "reconfiguration_successful" + assert entry.data["url"] == "ws://localhost:5580/ws" + assert entry.data["use_addon"] is False + assert entry.data["integration_created_addon"] is False + assert entry.title == "ws://localhost:5580/ws" + assert setup_entry.call_count == 1 + + +@pytest.mark.parametrize("discovery_info", [{"config": ADDON_DISCOVERY_INFO}]) +async def test_supervisor_discovery( + hass: HomeAssistant, + supervisor: MagicMock, + addon_running: AsyncMock, + addon_info: AsyncMock, + client_connect: AsyncMock, + setup_entry: AsyncMock, +) -> None: + """Test flow started from Supervisor discovery.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_HASSIO}, + data=HassioServiceInfo( + config=ADDON_DISCOVERY_INFO, + name="Matter Server", + slug=ADDON_SLUG, + ), + ) + + result = await hass.config_entries.flow.async_configure(result["flow_id"], {}) + await hass.async_block_till_done() + + assert addon_info.call_count == 1 + assert client_connect.call_count == 0 + assert result["type"] == FlowResultType.CREATE_ENTRY + assert result["title"] == "ws://host1:5581/ws" + assert result["data"] == { + "url": "ws://host1:5581/ws", + "use_addon": True, + "integration_created_addon": False, + } + assert setup_entry.call_count == 1 + + +@pytest.mark.parametrize( + "discovery_info, error", + [({"config": ADDON_DISCOVERY_INFO}, HassioAPIError())], +) +async def test_supervisor_discovery_addon_info_failed( + hass: HomeAssistant, + supervisor: MagicMock, + addon_running: AsyncMock, + addon_info: AsyncMock, + error: Exception, +) -> None: + """Test Supervisor discovery and addon info failed.""" + addon_info.side_effect = error + + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_HASSIO}, + data=HassioServiceInfo( + config=ADDON_DISCOVERY_INFO, + name="Matter Server", + slug=ADDON_SLUG, + ), + ) + + assert result["type"] == FlowResultType.FORM + assert result["step_id"] == "hassio_confirm" + + result = await hass.config_entries.flow.async_configure(result["flow_id"], {}) + + assert addon_info.call_count == 1 + assert result["type"] == FlowResultType.ABORT + assert result["reason"] == "addon_info_failed" + + +@pytest.mark.parametrize("discovery_info", [{"config": ADDON_DISCOVERY_INFO}]) +async def test_clean_supervisor_discovery_on_user_create( + hass: HomeAssistant, + supervisor: MagicMock, + addon_running: AsyncMock, + addon_info: AsyncMock, + client_connect: AsyncMock, + setup_entry: AsyncMock, +) -> None: + """Test discovery flow is cleaned up when a user flow is finished.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_HASSIO}, + data=HassioServiceInfo( + config=ADDON_DISCOVERY_INFO, + name="Matter Server", + slug=ADDON_SLUG, + ), + ) + + assert result["type"] == FlowResultType.FORM + assert result["step_id"] == "hassio_confirm" + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + assert result["type"] == FlowResultType.FORM + assert result["step_id"] == "on_supervisor" + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], {"use_addon": False} + ) + + assert addon_info.call_count == 0 + assert result["type"] == FlowResultType.FORM + assert result["step_id"] == "manual" + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + "url": "ws://localhost:5580/ws", + }, + ) + await hass.async_block_till_done() + + assert len(hass.config_entries.flow.async_progress()) == 0 + assert client_connect.call_count == 1 + assert result["type"] == FlowResultType.CREATE_ENTRY + assert result["title"] == "ws://localhost:5580/ws" + assert result["data"] == { + "url": "ws://localhost:5580/ws", + "use_addon": False, + "integration_created_addon": False, + } + assert setup_entry.call_count == 1 + + +async def test_abort_supervisor_discovery_with_existing_entry( + hass: HomeAssistant, + supervisor: MagicMock, + addon_running: AsyncMock, + addon_info: AsyncMock, +) -> None: + """Test discovery flow is aborted if an entry already exists.""" + entry = MockConfigEntry( + domain=DOMAIN, + data={"url": "ws://localhost:5580/ws"}, + title="ws://localhost:5580/ws", + ) + entry.add_to_hass(hass) + + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_HASSIO}, + data=HassioServiceInfo( + config=ADDON_DISCOVERY_INFO, + name="Matter Server", + slug=ADDON_SLUG, + ), + ) + + assert addon_info.call_count == 0 + assert result["type"] == FlowResultType.ABORT + assert result["reason"] == "already_configured" + + +async def test_abort_supervisor_discovery_with_existing_flow( + hass: HomeAssistant, + supervisor: MagicMock, + addon_installed: AsyncMock, + addon_info: AsyncMock, +) -> None: + """Test hassio discovery flow is aborted when another flow is in progress.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_USER}, + ) + assert result["type"] == FlowResultType.FORM + assert result["step_id"] == "on_supervisor" + + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_HASSIO}, + data=HassioServiceInfo( + config=ADDON_DISCOVERY_INFO, + name="Matter Server", + slug=ADDON_SLUG, + ), + ) + + assert addon_info.call_count == 0 + assert result["type"] == FlowResultType.ABORT + assert result["reason"] == "already_in_progress" + + +async def test_abort_supervisor_discovery_for_other_addon( + hass: HomeAssistant, + supervisor: MagicMock, + addon_installed: AsyncMock, + addon_info: AsyncMock, +) -> None: + """Test hassio discovery flow is aborted for a non official add-on discovery.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_HASSIO}, + data=HassioServiceInfo( + config={ + "addon": "Other Matter Server", + "host": "host1", + "port": 3001, + }, + name="Other Matter Server", + slug="other_addon", + ), + ) + + assert addon_info.call_count == 0 + assert result["type"] == FlowResultType.ABORT + assert result["reason"] == "not_matter_addon" + + +async def test_supervisor_discovery_addon_not_running( + hass: HomeAssistant, + supervisor: MagicMock, + addon_installed: AsyncMock, + addon_info: AsyncMock, + start_addon: AsyncMock, + client_connect: AsyncMock, + setup_entry: AsyncMock, +) -> None: + """Test discovery with add-on already installed but not running.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_HASSIO}, + data=HassioServiceInfo( + config=ADDON_DISCOVERY_INFO, + name="Matter Server", + slug=ADDON_SLUG, + ), + ) + + assert addon_info.call_count == 0 + assert result["step_id"] == "hassio_confirm" + assert result["type"] == FlowResultType.FORM + + result = await hass.config_entries.flow.async_configure(result["flow_id"], {}) + + assert addon_info.call_count == 1 + assert result["type"] == FlowResultType.SHOW_PROGRESS + assert result["step_id"] == "start_addon" + + await hass.async_block_till_done() + result = await hass.config_entries.flow.async_configure(result["flow_id"]) + await hass.async_block_till_done() + + assert start_addon.call_args == call(hass, "core_matter_server") + assert client_connect.call_count == 1 + assert result["type"] == FlowResultType.CREATE_ENTRY + assert result["title"] == "ws://host1:5581/ws" + assert result["data"] == { + "url": "ws://host1:5581/ws", + "use_addon": True, + "integration_created_addon": False, + } + assert setup_entry.call_count == 1 + + +async def test_supervisor_discovery_addon_not_installed( + hass: HomeAssistant, + supervisor: MagicMock, + addon_not_installed: AsyncMock, + install_addon: AsyncMock, + addon_info: AsyncMock, + addon_store_info: AsyncMock, + start_addon: AsyncMock, + client_connect: AsyncMock, + setup_entry: AsyncMock, +) -> None: + """Test discovery with add-on not installed.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_HASSIO}, + data=HassioServiceInfo( + config=ADDON_DISCOVERY_INFO, + name="Matter Server", + slug=ADDON_SLUG, + ), + ) + + assert addon_info.call_count == 0 + assert addon_store_info.call_count == 0 + assert result["step_id"] == "hassio_confirm" + assert result["type"] == FlowResultType.FORM + + result = await hass.config_entries.flow.async_configure(result["flow_id"], {}) + + assert addon_info.call_count == 0 + assert addon_store_info.call_count == 1 + assert result["step_id"] == "install_addon" + assert result["type"] == FlowResultType.SHOW_PROGRESS + + await hass.async_block_till_done() + result = await hass.config_entries.flow.async_configure(result["flow_id"]) + + assert install_addon.call_args == call(hass, "core_matter_server") + assert result["type"] == FlowResultType.SHOW_PROGRESS + assert result["step_id"] == "start_addon" + + await hass.async_block_till_done() + result = await hass.config_entries.flow.async_configure(result["flow_id"]) + await hass.async_block_till_done() + + assert start_addon.call_args == call(hass, "core_matter_server") + assert client_connect.call_count == 1 + assert result["type"] == FlowResultType.CREATE_ENTRY + assert result["title"] == "ws://host1:5581/ws" + assert result["data"] == { + "url": "ws://host1:5581/ws", + "use_addon": True, + "integration_created_addon": True, + } + assert setup_entry.call_count == 1 + + +async def test_not_addon( + hass: HomeAssistant, + supervisor: MagicMock, + client_connect: AsyncMock, + setup_entry: AsyncMock, +) -> None: + """Test opting out of add-on on Supervisor.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + assert result["type"] == FlowResultType.FORM + assert result["step_id"] == "on_supervisor" + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], {"use_addon": False} + ) + + assert result["type"] == FlowResultType.FORM + assert result["step_id"] == "manual" + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + "url": "ws://localhost:5581/ws", + }, + ) + await hass.async_block_till_done() + + assert client_connect.call_count == 1 + assert result["type"] == FlowResultType.CREATE_ENTRY + assert result["title"] == "ws://localhost:5581/ws" + assert result["data"] == { + "url": "ws://localhost:5581/ws", + "use_addon": False, + "integration_created_addon": False, + } + assert setup_entry.call_count == 1 + + +@pytest.mark.parametrize("discovery_info", [{"config": ADDON_DISCOVERY_INFO}]) +async def test_addon_running( + hass: HomeAssistant, + supervisor: MagicMock, + addon_running: AsyncMock, + addon_info: AsyncMock, + client_connect: AsyncMock, + setup_entry: AsyncMock, +) -> None: + """Test add-on already running on Supervisor.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + assert result["type"] == FlowResultType.FORM + assert result["step_id"] == "on_supervisor" + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], {"use_addon": True} + ) + await hass.async_block_till_done() + + assert addon_info.call_count == 1 + assert client_connect.call_count == 1 + assert result["type"] == FlowResultType.CREATE_ENTRY + assert result["title"] == "ws://host1:5581/ws" + assert result["data"] == { + "url": "ws://host1:5581/ws", + "use_addon": True, + "integration_created_addon": False, + } + assert setup_entry.call_count == 1 + + +@pytest.mark.parametrize( + "discovery_info, discovery_info_error, client_connect_error, addon_info_error, " + "abort_reason, discovery_info_called, client_connect_called", + [ + ( + {"config": ADDON_DISCOVERY_INFO}, + HassioAPIError(), + None, + None, + "addon_get_discovery_info_failed", + True, + False, + ), + ( + {"config": ADDON_DISCOVERY_INFO}, + None, + CannotConnect(Exception("Boom")), + None, + "cannot_connect", + True, + True, + ), + ( + None, + None, + None, + None, + "addon_get_discovery_info_failed", + True, + False, + ), + ( + {"config": ADDON_DISCOVERY_INFO}, + None, + None, + HassioAPIError(), + "addon_info_failed", + False, + False, + ), + ], +) +async def test_addon_running_failures( + hass: HomeAssistant, + supervisor: MagicMock, + addon_running: AsyncMock, + addon_info: AsyncMock, + get_addon_discovery_info: AsyncMock, + client_connect: AsyncMock, + discovery_info_error: Exception | None, + client_connect_error: Exception | None, + addon_info_error: Exception | None, + abort_reason: str, + discovery_info_called: bool, + client_connect_called: bool, +) -> None: + """Test all failures when add-on is running.""" + get_addon_discovery_info.side_effect = discovery_info_error + client_connect.side_effect = client_connect_error + addon_info.side_effect = addon_info_error + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + assert result["type"] == FlowResultType.FORM + assert result["step_id"] == "on_supervisor" + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], {"use_addon": True} + ) + + assert addon_info.call_count == 1 + assert get_addon_discovery_info.called is discovery_info_called + assert client_connect.called is client_connect_called + assert result["type"] == FlowResultType.ABORT + assert result["reason"] == abort_reason + + +@pytest.mark.parametrize("discovery_info", [{"config": ADDON_DISCOVERY_INFO}]) +async def test_addon_running_already_configured( + hass: HomeAssistant, + supervisor: MagicMock, + addon_running: AsyncMock, + addon_info: AsyncMock, + setup_entry: AsyncMock, +) -> None: + """Test that only one instance is allowed when add-on is running.""" + entry = MockConfigEntry( + domain=DOMAIN, + data={ + "url": "ws://localhost:5580/ws", + }, + title="ws://localhost:5580/ws", + ) + entry.add_to_hass(hass) + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + assert result["type"] == FlowResultType.FORM + assert result["step_id"] == "on_supervisor" + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], {"use_addon": True} + ) + await hass.async_block_till_done() + + assert addon_info.call_count == 1 + assert result["type"] == FlowResultType.ABORT + assert result["reason"] == "reconfiguration_successful" + assert entry.data["url"] == "ws://host1:5581/ws" + assert entry.title == "ws://host1:5581/ws" + assert setup_entry.call_count == 1 + + +@pytest.mark.parametrize("discovery_info", [{"config": ADDON_DISCOVERY_INFO}]) +async def test_addon_installed( + hass: HomeAssistant, + supervisor: MagicMock, + addon_installed: AsyncMock, + addon_info: AsyncMock, + start_addon: AsyncMock, + setup_entry: AsyncMock, +) -> None: + """Test add-on already installed but not running on Supervisor.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + assert result["type"] == FlowResultType.FORM + assert result["step_id"] == "on_supervisor" + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], {"use_addon": True} + ) + + assert addon_info.call_count == 1 + assert result["type"] == FlowResultType.SHOW_PROGRESS + assert result["step_id"] == "start_addon" + + await hass.async_block_till_done() + result = await hass.config_entries.flow.async_configure(result["flow_id"]) + await hass.async_block_till_done() + + assert start_addon.call_args == call(hass, "core_matter_server") + assert result["type"] == FlowResultType.CREATE_ENTRY + assert result["title"] == "ws://host1:5581/ws" + assert result["data"] == { + "url": "ws://host1:5581/ws", + "use_addon": True, + "integration_created_addon": False, + } + assert setup_entry.call_count == 1 + + +@pytest.mark.parametrize( + "discovery_info, start_addon_error, client_connect_error, " + "discovery_info_called, client_connect_called", + [ + ( + {"config": ADDON_DISCOVERY_INFO}, + HassioAPIError(), + None, + False, + False, + ), + ( + {"config": ADDON_DISCOVERY_INFO}, + None, + CannotConnect(Exception("Boom")), + True, + True, + ), + ( + None, + None, + None, + True, + False, + ), + ], +) +async def test_addon_installed_failures( + hass: HomeAssistant, + supervisor: MagicMock, + addon_installed: AsyncMock, + addon_info: AsyncMock, + start_addon: AsyncMock, + get_addon_discovery_info: AsyncMock, + client_connect: AsyncMock, + start_addon_error: Exception | None, + client_connect_error: Exception | None, + discovery_info_called: bool, + client_connect_called: bool, +) -> None: + """Test add-on start failure when add-on is installed.""" + start_addon.side_effect = start_addon_error + client_connect.side_effect = client_connect_error + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + assert result["type"] == FlowResultType.FORM + assert result["step_id"] == "on_supervisor" + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], {"use_addon": True} + ) + + assert addon_info.call_count == 1 + assert result["type"] == FlowResultType.SHOW_PROGRESS + assert result["step_id"] == "start_addon" + + await hass.async_block_till_done() + result = await hass.config_entries.flow.async_configure(result["flow_id"]) + + assert start_addon.call_args == call(hass, "core_matter_server") + assert get_addon_discovery_info.called is discovery_info_called + assert client_connect.called is client_connect_called + assert result["type"] == FlowResultType.ABORT + assert result["reason"] == "addon_start_failed" + + +@pytest.mark.parametrize("discovery_info", [{"config": ADDON_DISCOVERY_INFO}]) +async def test_addon_installed_already_configured( + hass: HomeAssistant, + supervisor: MagicMock, + addon_installed: AsyncMock, + addon_info: AsyncMock, + start_addon: AsyncMock, + setup_entry: AsyncMock, +) -> None: + """Test that only one instance is allowed when add-on is installed.""" + entry = MockConfigEntry( + domain=DOMAIN, + data={ + "url": "ws://localhost:5580/ws", + }, + title="ws://localhost:5580/ws", + ) + entry.add_to_hass(hass) + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + assert result["type"] == FlowResultType.FORM + assert result["step_id"] == "on_supervisor" + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], {"use_addon": True} + ) + + assert addon_info.call_count == 1 + assert result["type"] == FlowResultType.SHOW_PROGRESS + assert result["step_id"] == "start_addon" + + await hass.async_block_till_done() + result = await hass.config_entries.flow.async_configure(result["flow_id"]) + await hass.async_block_till_done() + + assert start_addon.call_args == call(hass, "core_matter_server") + assert result["type"] == FlowResultType.ABORT + assert result["reason"] == "reconfiguration_successful" + assert entry.data["url"] == "ws://host1:5581/ws" + assert entry.title == "ws://host1:5581/ws" + assert setup_entry.call_count == 1 + + +@pytest.mark.parametrize("discovery_info", [{"config": ADDON_DISCOVERY_INFO}]) +async def test_addon_not_installed( + hass: HomeAssistant, + supervisor: MagicMock, + addon_not_installed: AsyncMock, + install_addon: AsyncMock, + addon_info: AsyncMock, + addon_store_info: AsyncMock, + start_addon: AsyncMock, + setup_entry: AsyncMock, +) -> None: + """Test add-on not installed.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + assert result["type"] == FlowResultType.FORM + assert result["step_id"] == "on_supervisor" + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], {"use_addon": True} + ) + + assert addon_info.call_count == 0 + assert addon_store_info.call_count == 1 + assert result["type"] == FlowResultType.SHOW_PROGRESS + assert result["step_id"] == "install_addon" + + # Make sure the flow continues when the progress task is done. + await hass.async_block_till_done() + result = await hass.config_entries.flow.async_configure(result["flow_id"]) + + assert install_addon.call_args == call(hass, "core_matter_server") + assert result["type"] == FlowResultType.SHOW_PROGRESS + assert result["step_id"] == "start_addon" + + await hass.async_block_till_done() + result = await hass.config_entries.flow.async_configure(result["flow_id"]) + await hass.async_block_till_done() + + assert start_addon.call_args == call(hass, "core_matter_server") + assert result["type"] == FlowResultType.CREATE_ENTRY + assert result["title"] == "ws://host1:5581/ws" + assert result["data"] == { + "url": "ws://host1:5581/ws", + "use_addon": True, + "integration_created_addon": True, + } + assert setup_entry.call_count == 1 + + +async def test_addon_not_installed_failures( + hass: HomeAssistant, + supervisor: MagicMock, + addon_not_installed: AsyncMock, + addon_info: AsyncMock, + install_addon: AsyncMock, +) -> None: + """Test add-on install failure.""" + install_addon.side_effect = HassioAPIError() + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + assert result["type"] == FlowResultType.FORM + assert result["step_id"] == "on_supervisor" + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], {"use_addon": True} + ) + + assert result["type"] == FlowResultType.SHOW_PROGRESS + assert result["step_id"] == "install_addon" + + # Make sure the flow continues when the progress task is done. + await hass.async_block_till_done() + result = await hass.config_entries.flow.async_configure(result["flow_id"]) + + assert install_addon.call_args == call(hass, "core_matter_server") + assert addon_info.call_count == 0 + assert result["type"] == FlowResultType.ABORT + assert result["reason"] == "addon_install_failed" + + +@pytest.mark.parametrize("discovery_info", [{"config": ADDON_DISCOVERY_INFO}]) +async def test_addon_not_installed_already_configured( + hass: HomeAssistant, + supervisor: MagicMock, + addon_not_installed: AsyncMock, + addon_info: AsyncMock, + addon_store_info: AsyncMock, + install_addon: AsyncMock, + start_addon: AsyncMock, + client_connect: AsyncMock, + setup_entry: AsyncMock, +) -> None: + """Test that only one instance is allowed when add-on is not installed.""" + entry = MockConfigEntry( + domain=DOMAIN, + data={ + "url": "ws://localhost:5580/ws", + }, + title="ws://localhost:5580/ws", + ) + entry.add_to_hass(hass) + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + assert result["type"] == FlowResultType.FORM + assert result["step_id"] == "on_supervisor" + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], {"use_addon": True} + ) + + assert addon_info.call_count == 0 + assert addon_store_info.call_count == 1 + assert result["type"] == FlowResultType.SHOW_PROGRESS + assert result["step_id"] == "install_addon" + + # Make sure the flow continues when the progress task is done. + await hass.async_block_till_done() + result = await hass.config_entries.flow.async_configure(result["flow_id"]) + + assert install_addon.call_args == call(hass, "core_matter_server") + assert result["type"] == FlowResultType.SHOW_PROGRESS + assert result["step_id"] == "start_addon" + + await hass.async_block_till_done() + result = await hass.config_entries.flow.async_configure(result["flow_id"]) + await hass.async_block_till_done() + + assert start_addon.call_args == call(hass, "core_matter_server") + assert client_connect.call_count == 1 + assert result["type"] == FlowResultType.ABORT + assert result["reason"] == "reconfiguration_successful" + assert entry.data["url"] == "ws://host1:5581/ws" + assert entry.title == "ws://host1:5581/ws" + assert setup_entry.call_count == 1 diff --git a/tests/components/matter/test_init.py b/tests/components/matter/test_init.py new file mode 100644 index 00000000000..f34e428ecc0 --- /dev/null +++ b/tests/components/matter/test_init.py @@ -0,0 +1,398 @@ +"""Test the Matter integration init.""" +from __future__ import annotations + +import asyncio +from unittest.mock import AsyncMock, MagicMock, call + +from matter_server.client.exceptions import InvalidServerVersion +import pytest + +from homeassistant.components.hassio import HassioAPIError +from homeassistant.components.matter.const import DOMAIN +from homeassistant.config_entries import ConfigEntryDisabler, ConfigEntryState +from homeassistant.core import HomeAssistant +from homeassistant.helpers import issue_registry as ir + +from tests.common import MockConfigEntry + + +async def test_raise_addon_task_in_progress( + hass: HomeAssistant, + addon_not_installed: AsyncMock, + install_addon: AsyncMock, + start_addon: AsyncMock, +) -> None: + """Test raise ConfigEntryNotReady if an add-on task is in progress.""" + install_event = asyncio.Event() + + install_addon_original_side_effect = install_addon.side_effect + + async def install_addon_side_effect(hass: HomeAssistant, slug: str) -> None: + """Mock install add-on.""" + await install_event.wait() + await install_addon_original_side_effect(hass, slug) + + install_addon.side_effect = install_addon_side_effect + + entry = MockConfigEntry( + domain=DOMAIN, + title="Matter", + data={ + "url": "ws://host1:5581/ws", + "use_addon": True, + }, + ) + entry.add_to_hass(hass) + + await hass.config_entries.async_setup(entry.entry_id) + await asyncio.sleep(0.05) + + assert entry.state is ConfigEntryState.SETUP_RETRY + assert install_addon.call_count == 1 + assert start_addon.call_count == 0 + + # Check that we only call install add-on once if a task is in progress. + await hass.config_entries.async_reload(entry.entry_id) + await asyncio.sleep(0.05) + + assert entry.state is ConfigEntryState.SETUP_RETRY + assert install_addon.call_count == 1 + assert start_addon.call_count == 0 + + install_event.set() + await hass.async_block_till_done() + + assert install_addon.call_count == 1 + assert start_addon.call_count == 1 + + +async def test_start_addon( + hass: HomeAssistant, + addon_installed: AsyncMock, + addon_info: AsyncMock, + install_addon: AsyncMock, + start_addon: AsyncMock, +) -> None: + """Test start the Matter Server add-on during entry setup.""" + entry = MockConfigEntry( + domain=DOMAIN, + title="Matter", + data={ + "url": "ws://host1:5581/ws", + "use_addon": True, + }, + ) + entry.add_to_hass(hass) + + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + assert entry.state is ConfigEntryState.SETUP_RETRY + assert addon_info.call_count == 1 + assert install_addon.call_count == 0 + assert start_addon.call_count == 1 + assert start_addon.call_args == call(hass, "core_matter_server") + + +async def test_install_addon( + hass: HomeAssistant, + addon_not_installed: AsyncMock, + addon_store_info: AsyncMock, + install_addon: AsyncMock, + start_addon: AsyncMock, +) -> None: + """Test install and start the Matter add-on during entry setup.""" + entry = MockConfigEntry( + domain=DOMAIN, + title="Matter", + data={ + "url": "ws://host1:5581/ws", + "use_addon": True, + }, + ) + entry.add_to_hass(hass) + + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + assert entry.state is ConfigEntryState.SETUP_RETRY + assert addon_store_info.call_count == 2 + assert install_addon.call_count == 1 + assert install_addon.call_args == call(hass, "core_matter_server") + assert start_addon.call_count == 1 + assert start_addon.call_args == call(hass, "core_matter_server") + + +async def test_addon_info_failure( + hass: HomeAssistant, + addon_installed: AsyncMock, + addon_info: AsyncMock, + install_addon: AsyncMock, + start_addon: AsyncMock, +) -> None: + """Test failure to get add-on info for Matter add-on during entry setup.""" + addon_info.side_effect = HassioAPIError("Boom") + entry = MockConfigEntry( + domain=DOMAIN, + title="Matter", + data={ + "url": "ws://host1:5581/ws", + "use_addon": True, + }, + ) + entry.add_to_hass(hass) + + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + assert entry.state is ConfigEntryState.SETUP_RETRY + assert addon_info.call_count == 1 + assert install_addon.call_count == 0 + assert start_addon.call_count == 0 + + +@pytest.mark.parametrize( + "addon_version, update_available, update_calls, backup_calls, " + "update_addon_side_effect, create_backup_side_effect", + [ + ("1.0.0", True, 1, 1, None, None), + ("1.0.0", False, 0, 0, None, None), + ("1.0.0", True, 1, 1, HassioAPIError("Boom"), None), + ("1.0.0", True, 0, 1, None, HassioAPIError("Boom")), + ], +) +async def test_update_addon( + hass: HomeAssistant, + addon_installed: AsyncMock, + addon_running: AsyncMock, + addon_info: AsyncMock, + install_addon: AsyncMock, + start_addon: AsyncMock, + create_backup: AsyncMock, + update_addon: AsyncMock, + matter_client: MagicMock, + addon_version: str, + update_available: bool, + update_calls: int, + backup_calls: int, + update_addon_side_effect: Exception | None, + create_backup_side_effect: Exception | None, +): + """Test update the Matter add-on during entry setup.""" + addon_info.return_value["version"] = addon_version + addon_info.return_value["update_available"] = update_available + create_backup.side_effect = create_backup_side_effect + update_addon.side_effect = update_addon_side_effect + matter_client.connect.side_effect = InvalidServerVersion("Invalid version") + entry = MockConfigEntry( + domain=DOMAIN, + title="Matter", + data={ + "url": "ws://host1:5581/ws", + "use_addon": True, + }, + ) + entry.add_to_hass(hass) + + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + assert entry.state is ConfigEntryState.SETUP_RETRY + assert create_backup.call_count == backup_calls + assert update_addon.call_count == update_calls + + +async def test_issue_registry_invalid_version( + hass: HomeAssistant, + matter_client: MagicMock, +) -> None: + """Test issue registry for invalid version.""" + original_connect_side_effect = matter_client.connect.side_effect + matter_client.connect.side_effect = InvalidServerVersion("Invalid version") + entry = MockConfigEntry( + domain=DOMAIN, + title="Matter", + data={ + "url": "ws://host1:5581/ws", + "use_addon": False, + }, + ) + entry.add_to_hass(hass) + + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + issue_reg = ir.async_get(hass) + entry_state = entry.state + assert entry_state is ConfigEntryState.SETUP_RETRY + assert issue_reg.async_get_issue(DOMAIN, "invalid_server_version") + + matter_client.connect.side_effect = original_connect_side_effect + + await hass.config_entries.async_reload(entry.entry_id) + await hass.async_block_till_done() + + assert entry.state is ConfigEntryState.LOADED + assert not issue_reg.async_get_issue(DOMAIN, "invalid_server_version") + + +@pytest.mark.parametrize( + "stop_addon_side_effect, entry_state", + [ + (None, ConfigEntryState.NOT_LOADED), + (HassioAPIError("Boom"), ConfigEntryState.LOADED), + ], +) +async def test_stop_addon( + hass, + matter_client: MagicMock, + addon_installed: AsyncMock, + addon_running: AsyncMock, + addon_info: AsyncMock, + stop_addon: AsyncMock, + stop_addon_side_effect: Exception | None, + entry_state: ConfigEntryState, +): + """Test stop the Matter add-on on entry unload if entry is disabled.""" + stop_addon.side_effect = stop_addon_side_effect + entry = MockConfigEntry( + domain=DOMAIN, + title="Matter", + data={ + "url": "ws://host1:5581/ws", + "use_addon": True, + }, + ) + entry.add_to_hass(hass) + + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + assert entry.state is ConfigEntryState.LOADED + assert addon_info.call_count == 1 + addon_info.reset_mock() + + await hass.config_entries.async_set_disabled_by( + entry.entry_id, ConfigEntryDisabler.USER + ) + await hass.async_block_till_done() + + assert entry.state == entry_state + assert stop_addon.call_count == 1 + assert stop_addon.call_args == call(hass, "core_matter_server") + + +async def test_remove_entry( + hass: HomeAssistant, + addon_installed: AsyncMock, + stop_addon: AsyncMock, + create_backup: AsyncMock, + uninstall_addon: AsyncMock, + caplog: pytest.LogCaptureFixture, +) -> None: + """Test remove the config entry.""" + # test successful remove without created add-on + entry = MockConfigEntry( + domain=DOMAIN, + title="Matter", + data={"integration_created_addon": False}, + ) + entry.add_to_hass(hass) + assert entry.state is ConfigEntryState.NOT_LOADED + assert len(hass.config_entries.async_entries(DOMAIN)) == 1 + + await hass.config_entries.async_remove(entry.entry_id) + + assert entry.state is ConfigEntryState.NOT_LOADED + assert len(hass.config_entries.async_entries(DOMAIN)) == 0 + + # test successful remove with created add-on + entry = MockConfigEntry( + domain=DOMAIN, + title="Matter", + data={"integration_created_addon": True}, + ) + entry.add_to_hass(hass) + assert len(hass.config_entries.async_entries(DOMAIN)) == 1 + + await hass.config_entries.async_remove(entry.entry_id) + + assert stop_addon.call_count == 1 + assert stop_addon.call_args == call(hass, "core_matter_server") + assert create_backup.call_count == 1 + assert create_backup.call_args == call( + hass, + {"name": "addon_core_matter_server_1.0.0", "addons": ["core_matter_server"]}, + partial=True, + ) + assert uninstall_addon.call_count == 1 + assert uninstall_addon.call_args == call(hass, "core_matter_server") + assert entry.state is ConfigEntryState.NOT_LOADED + assert len(hass.config_entries.async_entries(DOMAIN)) == 0 + stop_addon.reset_mock() + create_backup.reset_mock() + uninstall_addon.reset_mock() + + # test add-on stop failure + entry.add_to_hass(hass) + assert len(hass.config_entries.async_entries(DOMAIN)) == 1 + stop_addon.side_effect = HassioAPIError() + + await hass.config_entries.async_remove(entry.entry_id) + + assert stop_addon.call_count == 1 + assert stop_addon.call_args == call(hass, "core_matter_server") + assert create_backup.call_count == 0 + assert uninstall_addon.call_count == 0 + assert entry.state is ConfigEntryState.NOT_LOADED + assert len(hass.config_entries.async_entries(DOMAIN)) == 0 + assert "Failed to stop the Matter Server add-on" in caplog.text + stop_addon.side_effect = None + stop_addon.reset_mock() + create_backup.reset_mock() + uninstall_addon.reset_mock() + + # test create backup failure + entry.add_to_hass(hass) + assert len(hass.config_entries.async_entries(DOMAIN)) == 1 + create_backup.side_effect = HassioAPIError() + + await hass.config_entries.async_remove(entry.entry_id) + + assert stop_addon.call_count == 1 + assert stop_addon.call_args == call(hass, "core_matter_server") + assert create_backup.call_count == 1 + assert create_backup.call_args == call( + hass, + {"name": "addon_core_matter_server_1.0.0", "addons": ["core_matter_server"]}, + partial=True, + ) + assert uninstall_addon.call_count == 0 + assert entry.state is ConfigEntryState.NOT_LOADED + assert len(hass.config_entries.async_entries(DOMAIN)) == 0 + assert "Failed to create a backup of the Matter Server add-on" in caplog.text + create_backup.side_effect = None + stop_addon.reset_mock() + create_backup.reset_mock() + uninstall_addon.reset_mock() + + # test add-on uninstall failure + entry.add_to_hass(hass) + assert len(hass.config_entries.async_entries(DOMAIN)) == 1 + uninstall_addon.side_effect = HassioAPIError() + + await hass.config_entries.async_remove(entry.entry_id) + + assert stop_addon.call_count == 1 + assert stop_addon.call_args == call(hass, "core_matter_server") + assert create_backup.call_count == 1 + assert create_backup.call_args == call( + hass, + {"name": "addon_core_matter_server_1.0.0", "addons": ["core_matter_server"]}, + partial=True, + ) + assert uninstall_addon.call_count == 1 + assert uninstall_addon.call_args == call(hass, "core_matter_server") + assert entry.state is ConfigEntryState.NOT_LOADED + assert len(hass.config_entries.async_entries(DOMAIN)) == 0 + assert "Failed to uninstall the Matter Server add-on" in caplog.text diff --git a/tests/components/matter/test_light.py b/tests/components/matter/test_light.py new file mode 100644 index 00000000000..eeebbac6414 --- /dev/null +++ b/tests/components/matter/test_light.py @@ -0,0 +1,82 @@ +"""Test Matter lights.""" +from typing import Any + +from chip.clusters import Objects as clusters +from matter_server.common.models.node import MatterNode +import pytest + +from homeassistant.core import HomeAssistant + +from .common import setup_integration_with_node_fixture + +# TEMP: Tests need to be fixed +pytestmark = pytest.mark.skip("all tests still WIP") + + +@pytest.fixture(name="light_node") +async def light_node_fixture( + hass: HomeAssistant, hass_storage: dict[str, Any] +) -> MatterNode: + """Fixture for a light node.""" + return await setup_integration_with_node_fixture( + hass, hass_storage, "lighting-example-app" + ) + + +async def test_turn_on(hass: HomeAssistant, light_node: MatterNode) -> None: + """Test turning on a light.""" + light_node.matter.client.mock_command(clusters.OnOff.Commands.On, None) + + await hass.services.async_call( + "light", + "turn_on", + { + "entity_id": "light.my_cool_light", + }, + blocking=True, + ) + + assert len(light_node.matter.client.mock_sent_commands) == 1 + args = light_node.matter.client.mock_sent_commands[0] + assert args["nodeid"] == light_node.node_id + assert args["endpoint"] == 1 + + light_node.matter.client.mock_command( + clusters.LevelControl.Commands.MoveToLevelWithOnOff, None + ) + + await hass.services.async_call( + "light", + "turn_on", + { + "entity_id": "light.my_cool_light", + "brightness": 128, + }, + blocking=True, + ) + + assert len(light_node.matter.client.mock_sent_commands) == 2 + args = light_node.matter.client.mock_sent_commands[1] + assert args["nodeid"] == light_node.node_id + assert args["endpoint"] == 1 + assert args["payload"].level == 127 + assert args["payload"].transitionTime == 0 + + +async def test_turn_off(hass: HomeAssistant, light_node: MatterNode) -> None: + """Test turning off a light.""" + light_node.matter.client.mock_command(clusters.OnOff.Commands.Off, None) + + await hass.services.async_call( + "light", + "turn_off", + { + "entity_id": "light.my_cool_light", + }, + blocking=True, + ) + + assert len(light_node.matter.client.mock_sent_commands) == 1 + args = light_node.matter.client.mock_sent_commands[0] + assert args["nodeid"] == light_node.node_id + assert args["endpoint"] == 1 From 5192b20a7c6dd3a617a15e74633a788d8ac745fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Hjelseth=20H=C3=B8yer?= Date: Thu, 1 Dec 2022 19:36:46 +0100 Subject: [PATCH 010/174] Tibber, fix date parsing (#83067) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Daniel Hjelseth Høyer Signed-off-by: Daniel Hjelseth Høyer --- homeassistant/components/tibber/sensor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/tibber/sensor.py b/homeassistant/components/tibber/sensor.py index 066e1f921c5..31106990a03 100644 --- a/homeassistant/components/tibber/sensor.py +++ b/homeassistant/components/tibber/sensor.py @@ -613,7 +613,7 @@ class TibberDataCoordinator(DataUpdateCoordinator): else home.hourly_consumption_data ) - from_time = hourly_data[0]["from"] + from_time = dt_util.parse_datetime(hourly_data[0]["from"]) if from_time is None: continue start = from_time - timedelta(hours=1) From 90dfa9d2af97ff2e8d3361de7991e9145118bd99 Mon Sep 17 00:00:00 2001 From: Christopher Bailey Date: Thu, 1 Dec 2022 16:37:44 -0500 Subject: [PATCH 011/174] Assorted fixes for UniFi Protect (#83032) --- .../components/unifiprotect/binary_sensor.py | 18 +++++++----------- homeassistant/components/unifiprotect/const.py | 2 -- .../components/unifiprotect/entity.py | 1 - .../components/unifiprotect/sensor.py | 10 ++++++++-- .../unifiprotect/strings.sensor.json | 7 +++++++ .../unifiprotect/translations/sensor.en.json | 7 +++++++ tests/components/unifiprotect/test_sensor.py | 10 +++++----- 7 files changed, 34 insertions(+), 21 deletions(-) create mode 100644 homeassistant/components/unifiprotect/strings.sensor.json create mode 100644 homeassistant/components/unifiprotect/translations/sensor.en.json diff --git a/homeassistant/components/unifiprotect/binary_sensor.py b/homeassistant/components/unifiprotect/binary_sensor.py index d38fe8582ab..bf6e89c3caf 100644 --- a/homeassistant/components/unifiprotect/binary_sensor.py +++ b/homeassistant/components/unifiprotect/binary_sensor.py @@ -30,7 +30,7 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity import EntityCategory from homeassistant.helpers.entity_platform import AddEntitiesCallback -from .const import DEVICE_CLASS_DETECTION, DISPATCH_ADOPT, DOMAIN +from .const import DISPATCH_ADOPT, DOMAIN from .data import ProtectData from .entity import ( EventEntityMixin, @@ -351,7 +351,6 @@ MOTION_SENSORS: tuple[ProtectBinaryEventEntityDescription, ...] = ( key="smart_obj_any", name="Object Detected", icon="mdi:eye", - device_class=DEVICE_CLASS_DETECTION, ufp_value="is_smart_detected", ufp_required_field="feature_flags.has_smart_detect", ufp_event_obj="last_smart_detect_event", @@ -360,7 +359,6 @@ MOTION_SENSORS: tuple[ProtectBinaryEventEntityDescription, ...] = ( key="smart_obj_person", name="Person Detected", icon="mdi:walk", - device_class=DEVICE_CLASS_DETECTION, ufp_value="is_smart_detected", ufp_required_field="can_detect_person", ufp_enabled="is_person_detection_on", @@ -371,7 +369,6 @@ MOTION_SENSORS: tuple[ProtectBinaryEventEntityDescription, ...] = ( key="smart_obj_vehicle", name="Vehicle Detected", icon="mdi:car", - device_class=DEVICE_CLASS_DETECTION, ufp_value="is_smart_detected", ufp_required_field="can_detect_vehicle", ufp_enabled="is_vehicle_detection_on", @@ -381,7 +378,6 @@ MOTION_SENSORS: tuple[ProtectBinaryEventEntityDescription, ...] = ( ProtectBinaryEventEntityDescription( key="smart_obj_face", name="Face Detected", - device_class=DEVICE_CLASS_DETECTION, icon="mdi:mdi-face", ufp_value="is_smart_detected", ufp_required_field="can_detect_face", @@ -392,7 +388,6 @@ MOTION_SENSORS: tuple[ProtectBinaryEventEntityDescription, ...] = ( ProtectBinaryEventEntityDescription( key="smart_obj_package", name="Package Detected", - device_class=DEVICE_CLASS_DETECTION, icon="mdi:package-variant-closed", ufp_value="is_smart_detected", ufp_required_field="can_detect_package", @@ -402,9 +397,8 @@ MOTION_SENSORS: tuple[ProtectBinaryEventEntityDescription, ...] = ( ), ProtectBinaryEventEntityDescription( key="smart_audio_any", - name="Audio Detected", + name="Audio Object Detected", icon="mdi:eye", - device_class=DEVICE_CLASS_DETECTION, ufp_value="is_smart_detected", ufp_required_field="feature_flags.has_smart_detect", ufp_event_obj="last_smart_audio_detect_event", @@ -412,7 +406,6 @@ MOTION_SENSORS: tuple[ProtectBinaryEventEntityDescription, ...] = ( ProtectBinaryEventEntityDescription( key="smart_audio_smoke", name="Smoke Alarm Detected", - device_class=DEVICE_CLASS_DETECTION, icon="mdi:fire", ufp_value="is_smart_detected", ufp_required_field="can_detect_smoke", @@ -423,7 +416,6 @@ MOTION_SENSORS: tuple[ProtectBinaryEventEntityDescription, ...] = ( ProtectBinaryEventEntityDescription( key="smart_audio_cmonx", name="CO Alarm Detected", - device_class=DEVICE_CLASS_DETECTION, icon="mdi:fire", ufp_value="is_smart_detected", ufp_required_field="can_detect_smoke", @@ -629,4 +621,8 @@ class ProtectEventBinarySensor(EventEntityMixin, BinarySensorEntity): @callback def _async_update_device_from_protect(self, device: ProtectModelWithId) -> None: super()._async_update_device_from_protect(device) - self._attr_is_on = self.entity_description.get_ufp_value(self.device) + is_on = self.entity_description.get_is_on(device) + self._attr_is_on: bool | None = is_on + if not is_on: + self._event = None + self._attr_extra_state_attributes = {} diff --git a/homeassistant/components/unifiprotect/const.py b/homeassistant/components/unifiprotect/const.py index cbf15c076c5..df4d8f77d99 100644 --- a/homeassistant/components/unifiprotect/const.py +++ b/homeassistant/components/unifiprotect/const.py @@ -68,5 +68,3 @@ PLATFORMS = [ DISPATCH_ADD = "add_device" DISPATCH_ADOPT = "adopt_device" DISPATCH_CHANNELS = "new_camera_channels" - -DEVICE_CLASS_DETECTION = "unifiprotect__detection" diff --git a/homeassistant/components/unifiprotect/entity.py b/homeassistant/components/unifiprotect/entity.py index a733f6ea1af..134b55c4b0d 100644 --- a/homeassistant/components/unifiprotect/entity.py +++ b/homeassistant/components/unifiprotect/entity.py @@ -325,7 +325,6 @@ class EventEntityMixin(ProtectDeviceEntity): @callback def _async_update_device_from_protect(self, device: ProtectModelWithId) -> None: super()._async_update_device_from_protect(device) - self._attr_is_on: bool | None = self.entity_description.get_is_on(device) self._event = self.entity_description.get_event_obj(device) attrs = self.extra_state_attributes or {} diff --git a/homeassistant/components/unifiprotect/sensor.py b/homeassistant/components/unifiprotect/sensor.py index 621fad2a1a5..d608907fa83 100644 --- a/homeassistant/components/unifiprotect/sensor.py +++ b/homeassistant/components/unifiprotect/sensor.py @@ -41,7 +41,7 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity import EntityCategory from homeassistant.helpers.entity_platform import AddEntitiesCallback -from .const import DEVICE_CLASS_DETECTION, DISPATCH_ADOPT, DOMAIN +from .const import DISPATCH_ADOPT, DOMAIN from .data import ProtectData from .entity import ( EventEntityMixin, @@ -54,6 +54,8 @@ from .utils import async_dispatch_id as _ufpd, async_get_light_motion_current _LOGGER = logging.getLogger(__name__) OBJECT_TYPE_NONE = "none" +DEVICE_CLASS_DETECTION = "unifiprotect__detection" +DEVICE_CLASS_LICENSE_PLATE = "unifiprotect__license_plate" @dataclass @@ -532,8 +534,9 @@ MOTION_SENSORS: tuple[ProtectSensorEventEntityDescription, ...] = ( key="smart_obj_licenseplate", name="License Plate Detected", icon="mdi:car", - device_class=DEVICE_CLASS_DETECTION, + device_class=DEVICE_CLASS_LICENSE_PLATE, ufp_value="is_smart_detected", + ufp_required_field="can_detect_license_plate", ufp_event_obj="last_smart_detect_event", ufp_smart_type=SmartDetectObjectType.LICENSE_PLATE, ), @@ -685,6 +688,9 @@ def _async_motion_entities( continue for event_desc in MOTION_SENSORS: + if not event_desc.has_required(device): + continue + entities.append(ProtectEventSensor(data, device, event_desc)) _LOGGER.debug( "Adding sensor entity %s for %s", diff --git a/homeassistant/components/unifiprotect/strings.sensor.json b/homeassistant/components/unifiprotect/strings.sensor.json new file mode 100644 index 00000000000..07b1955ba81 --- /dev/null +++ b/homeassistant/components/unifiprotect/strings.sensor.json @@ -0,0 +1,7 @@ +{ + "state": { + "unifiprotect__license_plate": { + "none": "Clear" + } + } +} diff --git a/homeassistant/components/unifiprotect/translations/sensor.en.json b/homeassistant/components/unifiprotect/translations/sensor.en.json new file mode 100644 index 00000000000..25d99e9ce4a --- /dev/null +++ b/homeassistant/components/unifiprotect/translations/sensor.en.json @@ -0,0 +1,7 @@ +{ + "state": { + "unifiprotect__license_plate": { + "none": "Clear" + } + } +} \ No newline at end of file diff --git a/tests/components/unifiprotect/test_sensor.py b/tests/components/unifiprotect/test_sensor.py index 0ec50398e7a..e67195df5e9 100644 --- a/tests/components/unifiprotect/test_sensor.py +++ b/tests/components/unifiprotect/test_sensor.py @@ -62,11 +62,11 @@ async def test_sensor_camera_remove( ufp.api.bootstrap.nvr.system_info.ustorage = None await init_entry(hass, ufp, [doorbell, unadopted_camera]) - assert_entity_counts(hass, Platform.SENSOR, 26, 13) + assert_entity_counts(hass, Platform.SENSOR, 25, 12) await remove_entities(hass, ufp, [doorbell, unadopted_camera]) assert_entity_counts(hass, Platform.SENSOR, 12, 9) await adopt_devices(hass, ufp, [doorbell, unadopted_camera]) - assert_entity_counts(hass, Platform.SENSOR, 26, 13) + assert_entity_counts(hass, Platform.SENSOR, 25, 12) async def test_sensor_sensor_remove( @@ -318,7 +318,7 @@ async def test_sensor_setup_camera( """Test sensor entity setup for camera devices.""" await init_entry(hass, ufp, [doorbell]) - assert_entity_counts(hass, Platform.SENSOR, 26, 13) + assert_entity_counts(hass, Platform.SENSOR, 25, 12) entity_registry = er.async_get(hass) @@ -424,7 +424,7 @@ async def test_sensor_setup_camera_with_last_trip_time( """Test sensor entity setup for camera devices with last trip time.""" await init_entry(hass, ufp, [doorbell]) - assert_entity_counts(hass, Platform.SENSOR, 26, 26) + assert_entity_counts(hass, Platform.SENSOR, 25, 25) entity_registry = er.async_get(hass) @@ -452,7 +452,7 @@ async def test_sensor_update_motion( """Test sensor motion entity.""" await init_entry(hass, ufp, [doorbell]) - assert_entity_counts(hass, Platform.SENSOR, 26, 13) + assert_entity_counts(hass, Platform.SENSOR, 25, 12) _, entity_id = ids_from_device_description( Platform.SENSOR, doorbell, MOTION_SENSORS[0] From 9cf2bd079443f0bc5a85afd83b30bbacec75b4c5 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 1 Dec 2022 11:37:22 -1000 Subject: [PATCH 012/174] Defer xiaomi ble polling until after startup (#83074) fixes https://github.com/home-assistant/core/issues/77178 --- homeassistant/components/xiaomi_ble/__init__.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/xiaomi_ble/__init__.py b/homeassistant/components/xiaomi_ble/__init__.py index f899600a8d1..201e4f14582 100644 --- a/homeassistant/components/xiaomi_ble/__init__.py +++ b/homeassistant/components/xiaomi_ble/__init__.py @@ -17,7 +17,7 @@ from homeassistant.components.bluetooth.active_update_coordinator import ( ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import Platform -from homeassistant.core import HomeAssistant +from homeassistant.core import CoreState, HomeAssistant from .const import DOMAIN @@ -60,7 +60,17 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: def _needs_poll( service_info: BluetoothServiceInfoBleak, last_poll: float | None ) -> bool: - return data.poll_needed(service_info, last_poll) + # Only poll if hass is running, we need to poll, + # and we actually have a way to connect to the device + return ( + hass.state == CoreState.running + and data.poll_needed(service_info, last_poll) + and bool( + async_ble_device_from_address( + hass, service_info.device.address, connectable=True + ) + ) + ) async def _async_poll(service_info: BluetoothServiceInfoBleak): # BluetoothServiceInfoBleak is defined in HA, otherwise would just pass it From 68f5a3a82cf6ccb7c46e6c869a5e333115626baa Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 1 Dec 2022 16:38:12 -0500 Subject: [PATCH 013/174] Bumped version to 2022.12.0b1 --- homeassistant/const.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 32c79bbc889..16dfdc644d3 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -8,7 +8,7 @@ from .backports.enum import StrEnum APPLICATION_NAME: Final = "HomeAssistant" MAJOR_VERSION: Final = 2022 MINOR_VERSION: Final = 12 -PATCH_VERSION: Final = "0b0" +PATCH_VERSION: Final = "0b1" __short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__: Final = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 9, 0) diff --git a/pyproject.toml b/pyproject.toml index 729892e41cc..260d7553cb6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "homeassistant" -version = "2022.12.0b0" +version = "2022.12.0b1" license = {text = "Apache-2.0"} description = "Open-source home automation platform running on Python 3." readme = "README.rst" From e6b0d93c1d52a96ea99783237ae1edfaf8dbb63a Mon Sep 17 00:00:00 2001 From: Alex Yao <33379584+alexyao2015@users.noreply.github.com> Date: Fri, 2 Dec 2022 19:51:16 -0600 Subject: [PATCH 014/174] Fix html5 Firefox Notifications (#82556) Co-authored-by: Paulus Schoutsen fixes undefined --- homeassistant/components/html5/notify.py | 25 ++++++- tests/components/html5/test_notify.py | 89 ++++++++++-------------- 2 files changed, 59 insertions(+), 55 deletions(-) diff --git a/homeassistant/components/html5/notify.py b/homeassistant/components/html5/notify.py index a6ca769250f..30465c9bd81 100644 --- a/homeassistant/components/html5/notify.py +++ b/homeassistant/components/html5/notify.py @@ -99,6 +99,7 @@ SCHEMA_WS_APPKEY = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend( # The number of days after the moment a notification is sent that a JWT # is valid. JWT_VALID_DAYS = 7 +VAPID_CLAIM_VALID_HOURS = 12 KEYS_SCHEMA = vol.All( dict, @@ -514,7 +515,10 @@ class HTML5NotificationService(BaseNotificationService): webpusher = WebPusher(info[ATTR_SUBSCRIPTION]) if self._vapid_prv and self._vapid_email: vapid_headers = create_vapid_headers( - self._vapid_email, info[ATTR_SUBSCRIPTION], self._vapid_prv + self._vapid_email, + info[ATTR_SUBSCRIPTION], + self._vapid_prv, + timestamp, ) vapid_headers.update({"urgency": priority, "priority": priority}) response = webpusher.send( @@ -540,6 +544,12 @@ class HTML5NotificationService(BaseNotificationService): _LOGGER.error("Error saving registration") else: _LOGGER.info("Configuration saved") + elif response.status_code > 399: + _LOGGER.error( + "There was an issue sending the notification %s: %s", + response.status, + response.text, + ) def add_jwt(timestamp, target, tag, jwt_secret): @@ -556,14 +566,23 @@ def add_jwt(timestamp, target, tag, jwt_secret): return jwt.encode(jwt_claims, jwt_secret) -def create_vapid_headers(vapid_email, subscription_info, vapid_private_key): +def create_vapid_headers(vapid_email, subscription_info, vapid_private_key, timestamp): """Create encrypted headers to send to WebPusher.""" - if vapid_email and vapid_private_key and ATTR_ENDPOINT in subscription_info: + if ( + vapid_email + and vapid_private_key + and ATTR_ENDPOINT in subscription_info + and timestamp + ): + vapid_exp = datetime.fromtimestamp(timestamp) + timedelta( + hours=VAPID_CLAIM_VALID_HOURS + ) url = urlparse(subscription_info.get(ATTR_ENDPOINT)) vapid_claims = { "sub": f"mailto:{vapid_email}", "aud": f"{url.scheme}://{url.netloc}", + "exp": int(vapid_exp.timestamp()), } vapid = Vapid.from_string(private_key=vapid_private_key) return vapid.sign(vapid_claims) diff --git a/tests/components/html5/test_notify.py b/tests/components/html5/test_notify.py index 1614555c493..b77986441ed 100644 --- a/tests/components/html5/test_notify.py +++ b/tests/components/html5/test_notify.py @@ -93,6 +93,7 @@ class TestHtml5Notify: def test_dismissing_message(self, mock_wp): """Test dismissing message.""" hass = MagicMock() + mock_wp().send().status_code = 201 data = {"device": SUBSCRIPTION_1} @@ -104,15 +105,13 @@ class TestHtml5Notify: service.dismiss(target=["device", "non_existing"], data={"tag": "test"}) - assert len(mock_wp.mock_calls) == 3 + assert len(mock_wp.mock_calls) == 4 # WebPusher constructor - assert mock_wp.mock_calls[0][1][0] == SUBSCRIPTION_1["subscription"] - # Third mock_call checks the status_code of the response. - assert mock_wp.mock_calls[2][0] == "().send().status_code.__eq__" + assert mock_wp.mock_calls[2][1][0] == SUBSCRIPTION_1["subscription"] # Call to send - payload = json.loads(mock_wp.mock_calls[1][1][0]) + payload = json.loads(mock_wp.mock_calls[3][1][0]) assert payload["dismiss"] is True assert payload["tag"] == "test" @@ -121,6 +120,7 @@ class TestHtml5Notify: def test_sending_message(self, mock_wp): """Test sending message.""" hass = MagicMock() + mock_wp().send().status_code = 201 data = {"device": SUBSCRIPTION_1} @@ -134,15 +134,13 @@ class TestHtml5Notify: "Hello", target=["device", "non_existing"], data={"icon": "beer.png"} ) - assert len(mock_wp.mock_calls) == 3 + assert len(mock_wp.mock_calls) == 4 # WebPusher constructor - assert mock_wp.mock_calls[0][1][0] == SUBSCRIPTION_1["subscription"] - # Third mock_call checks the status_code of the response. - assert mock_wp.mock_calls[2][0] == "().send().status_code.__eq__" + assert mock_wp.mock_calls[2][1][0] == SUBSCRIPTION_1["subscription"] # Call to send - payload = json.loads(mock_wp.mock_calls[1][1][0]) + payload = json.loads(mock_wp.mock_calls[3][1][0]) assert payload["body"] == "Hello" assert payload["icon"] == "beer.png" @@ -151,6 +149,7 @@ class TestHtml5Notify: def test_gcm_key_include(self, mock_wp): """Test if the gcm_key is only included for GCM endpoints.""" hass = MagicMock() + mock_wp().send().status_code = 201 data = {"chrome": SUBSCRIPTION_1, "firefox": SUBSCRIPTION_2} @@ -167,21 +166,18 @@ class TestHtml5Notify: assert len(mock_wp.mock_calls) == 6 # WebPusher constructor - assert mock_wp.mock_calls[0][1][0] == SUBSCRIPTION_1["subscription"] - assert mock_wp.mock_calls[3][1][0] == SUBSCRIPTION_2["subscription"] - - # Third mock_call checks the status_code of the response. - assert mock_wp.mock_calls[2][0] == "().send().status_code.__eq__" - assert mock_wp.mock_calls[5][0] == "().send().status_code.__eq__" + assert mock_wp.mock_calls[2][1][0] == SUBSCRIPTION_1["subscription"] + assert mock_wp.mock_calls[4][1][0] == SUBSCRIPTION_2["subscription"] # Get the keys passed to the WebPusher's send method - assert mock_wp.mock_calls[1][2]["gcm_key"] is not None - assert mock_wp.mock_calls[4][2]["gcm_key"] is None + assert mock_wp.mock_calls[3][2]["gcm_key"] is not None + assert mock_wp.mock_calls[5][2]["gcm_key"] is None @patch("homeassistant.components.html5.notify.WebPusher") def test_fcm_key_include(self, mock_wp): """Test if the FCM header is included.""" hass = MagicMock() + mock_wp().send().status_code = 201 data = {"chrome": SUBSCRIPTION_5} @@ -193,20 +189,18 @@ class TestHtml5Notify: service.send_message("Hello", target=["chrome"]) - assert len(mock_wp.mock_calls) == 3 + assert len(mock_wp.mock_calls) == 4 # WebPusher constructor - assert mock_wp.mock_calls[0][1][0] == SUBSCRIPTION_5["subscription"] - - # Third mock_call checks the status_code of the response. - assert mock_wp.mock_calls[2][0] == "().send().status_code.__eq__" + assert mock_wp.mock_calls[2][1][0] == SUBSCRIPTION_5["subscription"] # Get the keys passed to the WebPusher's send method - assert mock_wp.mock_calls[1][2]["headers"]["Authorization"] is not None + assert mock_wp.mock_calls[3][2]["headers"]["Authorization"] is not None @patch("homeassistant.components.html5.notify.WebPusher") def test_fcm_send_with_unknown_priority(self, mock_wp): """Test if the gcm_key is only included for GCM endpoints.""" hass = MagicMock() + mock_wp().send().status_code = 201 data = {"chrome": SUBSCRIPTION_5} @@ -218,20 +212,18 @@ class TestHtml5Notify: service.send_message("Hello", target=["chrome"], priority="undefined") - assert len(mock_wp.mock_calls) == 3 + assert len(mock_wp.mock_calls) == 4 # WebPusher constructor - assert mock_wp.mock_calls[0][1][0] == SUBSCRIPTION_5["subscription"] - - # Third mock_call checks the status_code of the response. - assert mock_wp.mock_calls[2][0] == "().send().status_code.__eq__" + assert mock_wp.mock_calls[2][1][0] == SUBSCRIPTION_5["subscription"] # Get the keys passed to the WebPusher's send method - assert mock_wp.mock_calls[1][2]["headers"]["priority"] == "normal" + assert mock_wp.mock_calls[3][2]["headers"]["priority"] == "normal" @patch("homeassistant.components.html5.notify.WebPusher") def test_fcm_no_targets(self, mock_wp): """Test if the gcm_key is only included for GCM endpoints.""" hass = MagicMock() + mock_wp().send().status_code = 201 data = {"chrome": SUBSCRIPTION_5} @@ -243,20 +235,18 @@ class TestHtml5Notify: service.send_message("Hello") - assert len(mock_wp.mock_calls) == 3 + assert len(mock_wp.mock_calls) == 4 # WebPusher constructor - assert mock_wp.mock_calls[0][1][0] == SUBSCRIPTION_5["subscription"] - - # Third mock_call checks the status_code of the response. - assert mock_wp.mock_calls[2][0] == "().send().status_code.__eq__" + assert mock_wp.mock_calls[2][1][0] == SUBSCRIPTION_5["subscription"] # Get the keys passed to the WebPusher's send method - assert mock_wp.mock_calls[1][2]["headers"]["priority"] == "normal" + assert mock_wp.mock_calls[3][2]["headers"]["priority"] == "normal" @patch("homeassistant.components.html5.notify.WebPusher") def test_fcm_additional_data(self, mock_wp): """Test if the gcm_key is only included for GCM endpoints.""" hass = MagicMock() + mock_wp().send().status_code = 201 data = {"chrome": SUBSCRIPTION_5} @@ -268,21 +258,18 @@ class TestHtml5Notify: service.send_message("Hello", data={"mykey": "myvalue"}) - assert len(mock_wp.mock_calls) == 3 + assert len(mock_wp.mock_calls) == 4 # WebPusher constructor - assert mock_wp.mock_calls[0][1][0] == SUBSCRIPTION_5["subscription"] - - # Third mock_call checks the status_code of the response. - assert mock_wp.mock_calls[2][0] == "().send().status_code.__eq__" + assert mock_wp.mock_calls[2][1][0] == SUBSCRIPTION_5["subscription"] # Get the keys passed to the WebPusher's send method - assert mock_wp.mock_calls[1][2]["headers"]["priority"] == "normal" + assert mock_wp.mock_calls[3][2]["headers"]["priority"] == "normal" def test_create_vapid_withoutvapid(): """Test creating empty vapid.""" resp = html5.create_vapid_headers( - vapid_email=None, vapid_private_key=None, subscription_info=None + vapid_email=None, vapid_private_key=None, subscription_info=None, timestamp=None ) assert resp is None @@ -478,6 +465,7 @@ async def test_callback_view_with_jwt(hass, hass_client): client = await mock_client(hass, hass_client, registrations) with patch("homeassistant.components.html5.notify.WebPusher") as mock_wp: + mock_wp().send().status_code = 201 await hass.services.async_call( "notify", "notify", @@ -485,15 +473,13 @@ async def test_callback_view_with_jwt(hass, hass_client): blocking=True, ) - assert len(mock_wp.mock_calls) == 3 + assert len(mock_wp.mock_calls) == 4 # WebPusher constructor - assert mock_wp.mock_calls[0][1][0] == SUBSCRIPTION_1["subscription"] - # Third mock_call checks the status_code of the response. - assert mock_wp.mock_calls[2][0] == "().send().status_code.__eq__" + assert mock_wp.mock_calls[2][1][0] == SUBSCRIPTION_1["subscription"] # Call to send - push_payload = json.loads(mock_wp.mock_calls[1][1][0]) + push_payload = json.loads(mock_wp.mock_calls[3][1][0]) assert push_payload["body"] == "Hello" assert push_payload["icon"] == "beer.png" @@ -514,6 +500,7 @@ async def test_send_fcm_without_targets(hass, hass_client): registrations = {"device": SUBSCRIPTION_5} await mock_client(hass, hass_client, registrations) with patch("homeassistant.components.html5.notify.WebPusher") as mock_wp: + mock_wp().send().status_code = 201 await hass.services.async_call( "notify", "notify", @@ -521,12 +508,10 @@ async def test_send_fcm_without_targets(hass, hass_client): blocking=True, ) - assert len(mock_wp.mock_calls) == 3 + assert len(mock_wp.mock_calls) == 4 # WebPusher constructor - assert mock_wp.mock_calls[0][1][0] == SUBSCRIPTION_5["subscription"] - # Third mock_call checks the status_code of the response. - assert mock_wp.mock_calls[2][0] == "().send().status_code.__eq__" + assert mock_wp.mock_calls[2][1][0] == SUBSCRIPTION_5["subscription"] async def test_send_fcm_expired(hass, hass_client): From 305dfda9aca5b254f9108a575594340b76dbba51 Mon Sep 17 00:00:00 2001 From: majuss Date: Fri, 2 Dec 2022 17:30:25 +0100 Subject: [PATCH 015/174] Bump lupupy to 0.2.1 (#83071) --- homeassistant/components/lupusec/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/lupusec/manifest.json b/homeassistant/components/lupusec/manifest.json index cb526b004de..5792f186798 100644 --- a/homeassistant/components/lupusec/manifest.json +++ b/homeassistant/components/lupusec/manifest.json @@ -2,7 +2,7 @@ "domain": "lupusec", "name": "Lupus Electronics LUPUSEC", "documentation": "https://www.home-assistant.io/integrations/lupusec", - "requirements": ["lupupy==0.1.9"], + "requirements": ["lupupy==0.2.1"], "codeowners": ["@majuss"], "iot_class": "local_polling", "loggers": ["lupupy"] diff --git a/requirements_all.txt b/requirements_all.txt index 6b97c0c5868..cede4f28175 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1052,7 +1052,7 @@ london-tube-status==0.5 luftdaten==0.7.4 # homeassistant.components.lupusec -lupupy==0.1.9 +lupupy==0.2.1 # homeassistant.components.lw12wifi lw12==0.9.2 From ee467e0f3a1663cebb02a179c6f176964b6ae0f1 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Thu, 1 Dec 2022 23:54:12 +0100 Subject: [PATCH 016/174] Fix prettier [ci] (#83077) --- homeassistant/components/unifiprotect/strings.sensor.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/unifiprotect/strings.sensor.json b/homeassistant/components/unifiprotect/strings.sensor.json index 07b1955ba81..ccb16e2d445 100644 --- a/homeassistant/components/unifiprotect/strings.sensor.json +++ b/homeassistant/components/unifiprotect/strings.sensor.json @@ -1,7 +1,7 @@ { - "state": { - "unifiprotect__license_plate": { - "none": "Clear" - } + "state": { + "unifiprotect__license_plate": { + "none": "Clear" } + } } From 52b3a309f871913c4231a92c025cd5ffb2a8446a Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Fri, 2 Dec 2022 18:27:33 -0700 Subject: [PATCH 017/174] Return empty data when OpenUV API call fails (#83089) Co-authored-by: Paulus Schoutsen fixes undefined --- homeassistant/components/openuv/coordinator.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/openuv/coordinator.py b/homeassistant/components/openuv/coordinator.py index 1df6a91b398..f89a9c696a8 100644 --- a/homeassistant/components/openuv/coordinator.py +++ b/homeassistant/components/openuv/coordinator.py @@ -32,10 +32,10 @@ class InvalidApiKeyMonitor: async def async_increment(self) -> None: """Increment the counter.""" - LOGGER.debug("Invalid API key response detected (number %s)", self._count) async with self._lock: self._count += 1 if self._count > self.DEFAULT_FAILED_API_CALL_THRESHOLD: + LOGGER.info("Starting reauth after multiple failed API calls") self._reauth_flow_manager.start_reauth() async def async_reset(self) -> None: @@ -114,10 +114,11 @@ class OpenUvCoordinator(DataUpdateCoordinator): """Fetch data from OpenUV.""" try: data = await self.update_method() - except InvalidApiKeyError: + except InvalidApiKeyError as err: await self._invalid_api_key_monitor.async_increment() + raise UpdateFailed(str(err)) from err except OpenUvError as err: - raise UpdateFailed(f"Error during protection data update: {err}") from err + raise UpdateFailed(str(err)) from err await self._invalid_api_key_monitor.async_reset() return cast(dict[str, Any], data["result"]) From 1f804e2eedf0764fe84ce6d1795ac0a40d6f8edb Mon Sep 17 00:00:00 2001 From: Christopher Bailey Date: Fri, 2 Dec 2022 03:15:24 -0500 Subject: [PATCH 018/174] Bump pyunifiprotect to 4.5.2 (#83090) --- homeassistant/components/unifiprotect/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/unifiprotect/manifest.json b/homeassistant/components/unifiprotect/manifest.json index 52e5ec31557..c7259356b66 100644 --- a/homeassistant/components/unifiprotect/manifest.json +++ b/homeassistant/components/unifiprotect/manifest.json @@ -4,7 +4,7 @@ "integration_type": "hub", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/unifiprotect", - "requirements": ["pyunifiprotect==4.5.1", "unifi-discovery==1.1.7"], + "requirements": ["pyunifiprotect==4.5.2", "unifi-discovery==1.1.7"], "dependencies": ["http", "repairs"], "codeowners": ["@briis", "@AngellusMortis", "@bdraco"], "quality_scale": "platinum", diff --git a/requirements_all.txt b/requirements_all.txt index cede4f28175..012ea4ca3d4 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2101,7 +2101,7 @@ pytrafikverket==0.2.2 pyudev==0.23.2 # homeassistant.components.unifiprotect -pyunifiprotect==4.5.1 +pyunifiprotect==4.5.2 # homeassistant.components.uptimerobot pyuptimerobot==22.2.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 44eddd3a92f..515d74a831c 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1464,7 +1464,7 @@ pytrafikverket==0.2.2 pyudev==0.23.2 # homeassistant.components.unifiprotect -pyunifiprotect==4.5.1 +pyunifiprotect==4.5.2 # homeassistant.components.uptimerobot pyuptimerobot==22.2.0 From f13f165d6428e5717e47d48e94c38aec4da887fe Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 1 Dec 2022 21:08:35 -1000 Subject: [PATCH 019/174] Fix wrong ble device being logged (#83091) This code can be simplified a bit now that we have the fast bleak lookup. We do connect via the correct path, but we logged the wrong one --- homeassistant/components/bluetooth/wrappers.py | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/bluetooth/wrappers.py b/homeassistant/components/bluetooth/wrappers.py index ca4f2314b65..d81331af331 100644 --- a/homeassistant/components/bluetooth/wrappers.py +++ b/homeassistant/components/bluetooth/wrappers.py @@ -162,7 +162,6 @@ class HaBleakClientWrapper(BleakClient): self.__address = address_or_ble_device self.__disconnected_callback = disconnected_callback self.__timeout = timeout - self.__ble_device: BLEDevice | None = None self._backend: BaseBleakClient | None = None # type: ignore[assignment] @property @@ -183,12 +182,9 @@ class HaBleakClientWrapper(BleakClient): async def connect(self, **kwargs: Any) -> bool: """Connect to the specified GATT server.""" assert models.MANAGER is not None - ( - wrapped_backend, - self.__ble_device, - ) = self._async_get_best_available_backend_and_device() + wrapped_backend = self._async_get_best_available_backend_and_device() self._backend = wrapped_backend.client( - self.__ble_device, + wrapped_backend.device, disconnected_callback=self.__disconnected_callback, timeout=self.__timeout, hass=models.MANAGER.hass, @@ -218,7 +214,7 @@ class HaBleakClientWrapper(BleakClient): @hass_callback def _async_get_best_available_backend_and_device( self, - ) -> tuple[_HaWrappedBleakBackend, BLEDevice]: + ) -> _HaWrappedBleakBackend: """Get a best available backend and device for the given address. This method will return the backend with the best rssi @@ -235,9 +231,10 @@ class HaBleakClientWrapper(BleakClient): or NO_RSSI_VALUE, reverse=True, ): - ble_device = device_advertisement_data[0] - if backend := self._async_get_backend_for_ble_device(ble_device): - return backend, ble_device + if backend := self._async_get_backend_for_ble_device( + device_advertisement_data[0] + ): + return backend raise BleakError( f"No backend with an available connection slot that can reach address {address} was found" From 83af8434cda193d0489d8cd6e7915c1d69a0a71b Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Fri, 2 Dec 2022 01:56:29 -0500 Subject: [PATCH 020/174] Bump zwave-js-server-python to 0.43.1 (#83093) --- 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 38c1e8b181f..4dc86fed92c 100644 --- a/homeassistant/components/zwave_js/manifest.json +++ b/homeassistant/components/zwave_js/manifest.json @@ -3,7 +3,7 @@ "name": "Z-Wave", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/zwave_js", - "requirements": ["pyserial==3.5", "zwave-js-server-python==0.43.0"], + "requirements": ["pyserial==3.5", "zwave-js-server-python==0.43.1"], "codeowners": ["@home-assistant/z-wave"], "dependencies": ["usb", "http", "websocket_api"], "iot_class": "local_push", diff --git a/requirements_all.txt b/requirements_all.txt index 012ea4ca3d4..d9715751a07 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2666,7 +2666,7 @@ zigpy==0.52.2 zm-py==0.5.2 # homeassistant.components.zwave_js -zwave-js-server-python==0.43.0 +zwave-js-server-python==0.43.1 # homeassistant.components.zwave_me zwave_me_ws==0.3.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 515d74a831c..3b7e782c3b6 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1858,7 +1858,7 @@ zigpy-znp==0.9.2 zigpy==0.52.2 # homeassistant.components.zwave_js -zwave-js-server-python==0.43.0 +zwave-js-server-python==0.43.1 # homeassistant.components.zwave_me zwave_me_ws==0.3.0 From 67d03031d5a2f0bdbbdccfdca356d0482c460a18 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 1 Dec 2022 22:07:31 -1000 Subject: [PATCH 021/174] Bump aiohomekit to 2.3.4 (#83094) --- homeassistant/components/homekit_controller/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/homekit_controller/manifest.json b/homeassistant/components/homekit_controller/manifest.json index 041f2ad293e..234108d616d 100644 --- a/homeassistant/components/homekit_controller/manifest.json +++ b/homeassistant/components/homekit_controller/manifest.json @@ -3,7 +3,7 @@ "name": "HomeKit Controller", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/homekit_controller", - "requirements": ["aiohomekit==2.3.3"], + "requirements": ["aiohomekit==2.3.4"], "zeroconf": ["_hap._tcp.local.", "_hap._udp.local."], "bluetooth": [{ "manufacturer_id": 76, "manufacturer_data_start": [6] }], "dependencies": ["bluetooth", "zeroconf"], diff --git a/requirements_all.txt b/requirements_all.txt index d9715751a07..db94c694628 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -174,7 +174,7 @@ aioguardian==2022.07.0 aioharmony==0.2.9 # homeassistant.components.homekit_controller -aiohomekit==2.3.3 +aiohomekit==2.3.4 # homeassistant.components.emulated_hue # homeassistant.components.http diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 3b7e782c3b6..d64914f56ce 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -158,7 +158,7 @@ aioguardian==2022.07.0 aioharmony==0.2.9 # homeassistant.components.homekit_controller -aiohomekit==2.3.3 +aiohomekit==2.3.4 # homeassistant.components.emulated_hue # homeassistant.components.http From f635751020d88bd46ce6720e37fbfbeb73d28407 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 1 Dec 2022 21:08:56 -1000 Subject: [PATCH 022/174] Bump bleak-retry-connector to 2.8.7 (#83095) changelog: https://github.com/Bluetooth-Devices/bleak-retry-connector/compare/v2.8.6...v2.8.7 --- homeassistant/components/bluetooth/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/bluetooth/manifest.json b/homeassistant/components/bluetooth/manifest.json index 982edf10df4..a59411aae5a 100644 --- a/homeassistant/components/bluetooth/manifest.json +++ b/homeassistant/components/bluetooth/manifest.json @@ -7,7 +7,7 @@ "quality_scale": "internal", "requirements": [ "bleak==0.19.2", - "bleak-retry-connector==2.8.6", + "bleak-retry-connector==2.8.7", "bluetooth-adapters==0.11.0", "bluetooth-auto-recovery==0.5.3", "bluetooth-data-tools==0.3.0", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 323cc927c96..dc9172c78bf 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -10,7 +10,7 @@ atomicwrites-homeassistant==1.4.1 attrs==21.2.0 awesomeversion==22.9.0 bcrypt==3.1.7 -bleak-retry-connector==2.8.6 +bleak-retry-connector==2.8.7 bleak==0.19.2 bluetooth-adapters==0.11.0 bluetooth-auto-recovery==0.5.3 diff --git a/requirements_all.txt b/requirements_all.txt index db94c694628..c453f3ded39 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -422,7 +422,7 @@ bimmer_connected==0.10.4 bizkaibus==0.1.1 # homeassistant.components.bluetooth -bleak-retry-connector==2.8.6 +bleak-retry-connector==2.8.7 # homeassistant.components.bluetooth bleak==0.19.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index d64914f56ce..ed8a8f51c76 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -346,7 +346,7 @@ bellows==0.34.5 bimmer_connected==0.10.4 # homeassistant.components.bluetooth -bleak-retry-connector==2.8.6 +bleak-retry-connector==2.8.7 # homeassistant.components.bluetooth bleak==0.19.2 From daad93dd311c8b4b9b0407577ae1a8b35ed26d5e Mon Sep 17 00:00:00 2001 From: Olen Date: Fri, 2 Dec 2022 09:58:29 +0100 Subject: [PATCH 023/174] Fix twinkly effects (#83104) Adding additional checks --- homeassistant/components/twinkly/light.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/twinkly/light.py b/homeassistant/components/twinkly/light.py index cc2270f25e1..8f8e8218d87 100644 --- a/homeassistant/components/twinkly/light.py +++ b/homeassistant/components/twinkly/light.py @@ -288,11 +288,13 @@ class TwinklyLight(LightEntity): async def async_update_movies(self) -> None: """Update the list of movies (effects).""" movies = await self._client.get_saved_movies() - if "movies" in movies: + _LOGGER.debug("Movies: %s", movies) + if movies and "movies" in movies: self._movies = movies["movies"] async def async_update_current_movie(self) -> None: """Update the current active movie.""" current_movie = await self._client.get_current_movie() - if "id" in current_movie: + _LOGGER.debug("Current movie: %s", current_movie) + if current_movie and "id" in current_movie: self._current_movie = current_movie From 0be9391d7954d7f875f4847454b6a66af95c3544 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 2 Dec 2022 14:45:49 -1000 Subject: [PATCH 024/174] Fix esphome ble client leaking notify on disconnect (#83106) * Fix esphome ble client leaking notify on disconnect needs: https://github.com/esphome/aioesphomeapi/pull/329 * leak * more cleanup * more cleanup * bump --- .../components/esphome/bluetooth/client.py | 32 ++++++++++++------- .../components/esphome/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 24 insertions(+), 14 deletions(-) diff --git a/homeassistant/components/esphome/bluetooth/client.py b/homeassistant/components/esphome/bluetooth/client.py index da4d5fb3d26..1ec3b585fae 100644 --- a/homeassistant/components/esphome/bluetooth/client.py +++ b/homeassistant/components/esphome/bluetooth/client.py @@ -142,7 +142,9 @@ class ESPHomeClient(BaseBleakClient): self._is_connected = False self._mtu: int | None = None self._cancel_connection_state: CALLBACK_TYPE | None = None - self._notify_cancels: dict[int, Callable[[], Coroutine[Any, Any, None]]] = {} + self._notify_cancels: dict[ + int, tuple[Callable[[], Coroutine[Any, Any, None]], Callable[[], None]] + ] = {} self._disconnected_event: asyncio.Event | None = None device_info = self.entry_data.device_info assert device_info is not None @@ -169,15 +171,22 @@ class ESPHomeClient(BaseBleakClient): ) self._cancel_connection_state = None - def _async_ble_device_disconnected(self) -> None: - """Handle the BLE device disconnecting from the ESP.""" - was_connected = self._is_connected + def _async_disconnected_cleanup(self) -> None: + """Clean up on disconnect.""" self.services = BleakGATTServiceCollection() # type: ignore[no-untyped-call] self._is_connected = False + for _, notify_abort in self._notify_cancels.values(): + notify_abort() self._notify_cancels.clear() if self._disconnected_event: self._disconnected_event.set() self._disconnected_event = None + self._unsubscribe_connection_state() + + def _async_ble_device_disconnected(self) -> None: + """Handle the BLE device disconnecting from the ESP.""" + was_connected = self._is_connected + self._async_disconnected_cleanup() if was_connected: _LOGGER.debug( "%s: %s - %s: BLE device disconnected", @@ -186,7 +195,6 @@ class ESPHomeClient(BaseBleakClient): self._ble_device.address, ) self._async_call_bleak_disconnected_callback() - self._unsubscribe_connection_state() def _async_esp_disconnected(self) -> None: """Handle the esp32 client disconnecting from hass.""" @@ -316,7 +324,7 @@ class ESPHomeClient(BaseBleakClient): @api_error_as_bleak_error async def disconnect(self) -> bool: """Disconnect from the peripheral device.""" - self._unsubscribe_connection_state() + self._async_disconnected_cleanup() await self._client.bluetooth_device_disconnect(self._address_as_int) await self._wait_for_free_connection_slot(DISCONNECT_TIMEOUT) return True @@ -551,12 +559,13 @@ class ESPHomeClient(BaseBleakClient): f"Characteristic {characteristic.uuid} does not have notify or indicate property set." ) - cancel_coro = await self._client.bluetooth_gatt_start_notify( + self._notify_cancels[ + ble_handle + ] = await self._client.bluetooth_gatt_start_notify( self._address_as_int, ble_handle, lambda handle, data: callback(data), ) - self._notify_cancels[ble_handle] = cancel_coro if self._connection_version < MIN_BLUETOOTH_PROXY_VERSION_HAS_CACHE: return @@ -604,8 +613,9 @@ class ESPHomeClient(BaseBleakClient): characteristic = self._resolve_characteristic(char_specifier) # Do not raise KeyError if notifications are not enabled on this characteristic # to be consistent with the behavior of the BlueZ backend - if coro := self._notify_cancels.pop(characteristic.handle, None): - await coro() + if notify_cancel := self._notify_cancels.pop(characteristic.handle, None): + notify_stop, _ = notify_cancel + await notify_stop() def __del__(self) -> None: """Destructor to make sure the connection state is unsubscribed.""" @@ -617,4 +627,4 @@ class ESPHomeClient(BaseBleakClient): self._ble_device.address, ) if not self._hass.loop.is_closed(): - self._hass.loop.call_soon_threadsafe(self._unsubscribe_connection_state) + self._hass.loop.call_soon_threadsafe(self._async_disconnected_cleanup) diff --git a/homeassistant/components/esphome/manifest.json b/homeassistant/components/esphome/manifest.json index 42ebc471b7c..6d9796b8541 100644 --- a/homeassistant/components/esphome/manifest.json +++ b/homeassistant/components/esphome/manifest.json @@ -3,7 +3,7 @@ "name": "ESPHome", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/esphome", - "requirements": ["aioesphomeapi==12.2.1"], + "requirements": ["aioesphomeapi==13.0.0"], "zeroconf": ["_esphomelib._tcp.local."], "dhcp": [{ "registered_devices": true }], "codeowners": ["@OttoWinter", "@jesserockz"], diff --git a/requirements_all.txt b/requirements_all.txt index c453f3ded39..113b41fe2e9 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -156,7 +156,7 @@ aioecowitt==2022.11.0 aioemonitor==1.0.5 # homeassistant.components.esphome -aioesphomeapi==12.2.1 +aioesphomeapi==13.0.0 # homeassistant.components.flo aioflo==2021.11.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index ed8a8f51c76..2ddf911f16a 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -143,7 +143,7 @@ aioecowitt==2022.11.0 aioemonitor==1.0.5 # homeassistant.components.esphome -aioesphomeapi==12.2.1 +aioesphomeapi==13.0.0 # homeassistant.components.flo aioflo==2021.11.0 From e13413ee0938fcf83f2dec97eccdf9597c854d9b Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 2 Dec 2022 14:48:06 -1000 Subject: [PATCH 025/174] Bump bluetooth-auto-recovery to 0.5.4 (#83155) changelog: https://github.com/Bluetooth-Devices/bluetooth-auto-recovery/compare/v0.5.3...v0.5.4 --- homeassistant/components/bluetooth/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/bluetooth/manifest.json b/homeassistant/components/bluetooth/manifest.json index a59411aae5a..8968d03267b 100644 --- a/homeassistant/components/bluetooth/manifest.json +++ b/homeassistant/components/bluetooth/manifest.json @@ -9,7 +9,7 @@ "bleak==0.19.2", "bleak-retry-connector==2.8.7", "bluetooth-adapters==0.11.0", - "bluetooth-auto-recovery==0.5.3", + "bluetooth-auto-recovery==0.5.4", "bluetooth-data-tools==0.3.0", "dbus-fast==1.75.0" ], diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index dc9172c78bf..e1e88d2ebca 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -13,7 +13,7 @@ bcrypt==3.1.7 bleak-retry-connector==2.8.7 bleak==0.19.2 bluetooth-adapters==0.11.0 -bluetooth-auto-recovery==0.5.3 +bluetooth-auto-recovery==0.5.4 bluetooth-data-tools==0.3.0 certifi>=2021.5.30 ciso8601==2.2.0 diff --git a/requirements_all.txt b/requirements_all.txt index 113b41fe2e9..406cf36c859 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -450,7 +450,7 @@ bluemaestro-ble==0.2.0 bluetooth-adapters==0.11.0 # homeassistant.components.bluetooth -bluetooth-auto-recovery==0.5.3 +bluetooth-auto-recovery==0.5.4 # homeassistant.components.bluetooth # homeassistant.components.led_ble diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 2ddf911f16a..cd7502476ea 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -364,7 +364,7 @@ bluemaestro-ble==0.2.0 bluetooth-adapters==0.11.0 # homeassistant.components.bluetooth -bluetooth-auto-recovery==0.5.3 +bluetooth-auto-recovery==0.5.4 # homeassistant.components.bluetooth # homeassistant.components.led_ble From 81c7a2413348358fde2e30c8505b4c14a5f23d28 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 2 Dec 2022 13:24:12 -1000 Subject: [PATCH 026/174] Fix logging the wrong bluetooth adapter while connecting and out of slots (#83158) --- homeassistant/components/bluetooth/manifest.json | 2 +- homeassistant/components/bluetooth/wrappers.py | 12 ++++++++++-- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 5 files changed, 14 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/bluetooth/manifest.json b/homeassistant/components/bluetooth/manifest.json index 8968d03267b..0ba5768d37c 100644 --- a/homeassistant/components/bluetooth/manifest.json +++ b/homeassistant/components/bluetooth/manifest.json @@ -7,7 +7,7 @@ "quality_scale": "internal", "requirements": [ "bleak==0.19.2", - "bleak-retry-connector==2.8.7", + "bleak-retry-connector==2.8.9", "bluetooth-adapters==0.11.0", "bluetooth-auto-recovery==0.5.4", "bluetooth-data-tools==0.3.0", diff --git a/homeassistant/components/bluetooth/wrappers.py b/homeassistant/components/bluetooth/wrappers.py index d81331af331..beaa2acc78a 100644 --- a/homeassistant/components/bluetooth/wrappers.py +++ b/homeassistant/components/bluetooth/wrappers.py @@ -12,7 +12,7 @@ from bleak import BleakClient, BleakError from bleak.backends.client import BaseBleakClient, get_platform_client_backend_type from bleak.backends.device import BLEDevice from bleak.backends.scanner import AdvertisementDataCallback, BaseBleakScanner -from bleak_retry_connector import NO_RSSI_VALUE +from bleak_retry_connector import NO_RSSI_VALUE, ble_device_description from homeassistant.core import CALLBACK_TYPE, callback as hass_callback from homeassistant.helpers.frame import report @@ -189,7 +189,15 @@ class HaBleakClientWrapper(BleakClient): timeout=self.__timeout, hass=models.MANAGER.hass, ) - return await super().connect(**kwargs) + if debug_logging := _LOGGER.isEnabledFor(logging.DEBUG): + # Only lookup the description if we are going to log it + description = ble_device_description(wrapped_backend.device) + rssi = wrapped_backend.device.rssi + _LOGGER.debug("%s: Connecting (last rssi: %s)", description, rssi) + connected = await super().connect(**kwargs) + if debug_logging: + _LOGGER.debug("%s: Connected (last rssi: %s)", description, rssi) + return connected @hass_callback def _async_get_backend_for_ble_device( diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index e1e88d2ebca..f41444de531 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -10,7 +10,7 @@ atomicwrites-homeassistant==1.4.1 attrs==21.2.0 awesomeversion==22.9.0 bcrypt==3.1.7 -bleak-retry-connector==2.8.7 +bleak-retry-connector==2.8.9 bleak==0.19.2 bluetooth-adapters==0.11.0 bluetooth-auto-recovery==0.5.4 diff --git a/requirements_all.txt b/requirements_all.txt index 406cf36c859..428b8e9a1fd 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -422,7 +422,7 @@ bimmer_connected==0.10.4 bizkaibus==0.1.1 # homeassistant.components.bluetooth -bleak-retry-connector==2.8.7 +bleak-retry-connector==2.8.9 # homeassistant.components.bluetooth bleak==0.19.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index cd7502476ea..f9a42dac94e 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -346,7 +346,7 @@ bellows==0.34.5 bimmer_connected==0.10.4 # homeassistant.components.bluetooth -bleak-retry-connector==2.8.7 +bleak-retry-connector==2.8.9 # homeassistant.components.bluetooth bleak==0.19.2 From f5de016f25e0c4b12b56c9bfc04a03790ee57954 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 2 Dec 2022 14:49:13 -1000 Subject: [PATCH 027/174] Fix reauth with esphome when adding noise encryption (#83164) * Fix reauth with esphome when adding noise encryption fixes #80813 * fix with unique id --- .../components/esphome/config_flow.py | 16 ++++--- tests/components/esphome/test_config_flow.py | 47 +++++++++++++++++++ 2 files changed, 57 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/esphome/config_flow.py b/homeassistant/components/esphome/config_flow.py index ea64fb7fb7f..542aa011a7b 100644 --- a/homeassistant/components/esphome/config_flow.py +++ b/homeassistant/components/esphome/config_flow.py @@ -17,7 +17,7 @@ from aioesphomeapi import ( import voluptuous as vol from homeassistant.components import dhcp, zeroconf -from homeassistant.config_entries import ConfigFlow +from homeassistant.config_entries import ConfigEntry, ConfigFlow from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_PORT from homeassistant.core import callback from homeassistant.data_entry_flow import FlowResult @@ -40,6 +40,7 @@ class EsphomeFlowHandler(ConfigFlow, domain=DOMAIN): self._password: str | None = None self._noise_psk: str | None = None self._device_info: DeviceInfo | None = None + self._reauth_entry: ConfigEntry | None = None async def _async_step_user_base( self, user_input: dict[str, Any] | None = None, error: str | None = None @@ -72,6 +73,7 @@ class EsphomeFlowHandler(ConfigFlow, domain=DOMAIN): """Handle a flow initialized by a reauth event.""" entry = self.hass.config_entries.async_get_entry(self.context["entry_id"]) assert entry is not None + self._reauth_entry = entry self._host = entry.data[CONF_HOST] self._port = entry.data[CONF_PORT] self._password = entry.data[CONF_PASSWORD] @@ -245,10 +247,11 @@ class EsphomeFlowHandler(ConfigFlow, domain=DOMAIN): CONF_PASSWORD: self._password or "", CONF_NOISE_PSK: self._noise_psk or "", } - if "entry_id" in self.context: - entry = self.hass.config_entries.async_get_entry(self.context["entry_id"]) - assert entry is not None - self.hass.config_entries.async_update_entry(entry, data=config_data) + if self._reauth_entry: + entry = self._reauth_entry + self.hass.config_entries.async_update_entry( + entry, data=self._reauth_entry.data | config_data + ) # Reload the config entry to notify of updated config self.hass.async_create_task( self.hass.config_entries.async_reload(entry.entry_id) @@ -332,7 +335,8 @@ class EsphomeFlowHandler(ConfigFlow, domain=DOMAIN): self._name = self._device_info.name await self.async_set_unique_id(self._name, raise_on_progress=False) - self._abort_if_unique_id_configured(updates={CONF_HOST: self._host}) + if not self._reauth_entry: + self._abort_if_unique_id_configured(updates={CONF_HOST: self._host}) return None diff --git a/tests/components/esphome/test_config_flow.py b/tests/components/esphome/test_config_flow.py index a4d1f416868..efeb2d376cf 100644 --- a/tests/components/esphome/test_config_flow.py +++ b/tests/components/esphome/test_config_flow.py @@ -559,6 +559,53 @@ async def test_reauth_confirm_invalid(hass, mock_client, mock_zeroconf): assert result["errors"] assert result["errors"]["base"] == "invalid_psk" + mock_client.device_info = AsyncMock(return_value=MockDeviceInfo(False, "test")) + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input={CONF_NOISE_PSK: VALID_NOISE_PSK} + ) + + assert result["type"] == FlowResultType.ABORT + assert result["reason"] == "reauth_successful" + assert entry.data[CONF_NOISE_PSK] == VALID_NOISE_PSK + + +async def test_reauth_confirm_invalid_with_unique_id(hass, mock_client, mock_zeroconf): + """Test reauth initiation with invalid PSK.""" + entry = MockConfigEntry( + domain=DOMAIN, + data={CONF_HOST: "127.0.0.1", CONF_PORT: 6053, CONF_PASSWORD: ""}, + unique_id="test", + ) + entry.add_to_hass(hass) + + result = await hass.config_entries.flow.async_init( + "esphome", + context={ + "source": config_entries.SOURCE_REAUTH, + "entry_id": entry.entry_id, + "unique_id": entry.unique_id, + }, + ) + + mock_client.device_info.side_effect = InvalidEncryptionKeyAPIError + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input={CONF_NOISE_PSK: INVALID_NOISE_PSK} + ) + + assert result["type"] == FlowResultType.FORM + assert result["step_id"] == "reauth_confirm" + assert result["errors"] + assert result["errors"]["base"] == "invalid_psk" + + mock_client.device_info = AsyncMock(return_value=MockDeviceInfo(False, "test")) + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input={CONF_NOISE_PSK: VALID_NOISE_PSK} + ) + + assert result["type"] == FlowResultType.ABORT + assert result["reason"] == "reauth_successful" + assert entry.data[CONF_NOISE_PSK] == VALID_NOISE_PSK + async def test_discovery_dhcp_updates_host(hass, mock_client): """Test dhcp discovery updates host and aborts.""" From 2b4587a7a8c9254664907254e0cc96a3e95e6593 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 2 Dec 2022 13:49:14 -1000 Subject: [PATCH 028/174] Fix race setting up homekit controller triggers (#83166) fixes https://github.com/home-assistant/core/issues/83165 --- .../components/homekit_controller/__init__.py | 3 +-- .../components/homekit_controller/device_trigger.py | 11 +++++++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/homekit_controller/__init__.py b/homeassistant/components/homekit_controller/__init__.py index 54da6e71c8c..aa56bbbda78 100644 --- a/homeassistant/components/homekit_controller/__init__.py +++ b/homeassistant/components/homekit_controller/__init__.py @@ -21,7 +21,7 @@ from homeassistant.helpers.typing import ConfigType from .config_flow import normalize_hkid from .connection import HKDevice -from .const import KNOWN_DEVICES, TRIGGERS +from .const import KNOWN_DEVICES from .utils import async_get_controller _LOGGER = logging.getLogger(__name__) @@ -59,7 +59,6 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: await async_get_controller(hass) hass.data[KNOWN_DEVICES] = {} - hass.data[TRIGGERS] = {} async def _async_stop_homekit_controller(event: Event) -> None: await asyncio.gather( diff --git a/homeassistant/components/homekit_controller/device_trigger.py b/homeassistant/components/homekit_controller/device_trigger.py index bc1434f4bd9..229c8aecc00 100644 --- a/homeassistant/components/homekit_controller/device_trigger.py +++ b/homeassistant/components/homekit_controller/device_trigger.py @@ -224,7 +224,7 @@ async def async_setup_triggers_for_entry( # They have to be different accessories (they can be on the same bridge) # In practice, this is inline with what iOS actually supports AFAWCT. device_id = conn.devices[aid] - if device_id in hass.data[TRIGGERS]: + if TRIGGERS in hass.data and device_id in hass.data[TRIGGERS]: return False # Just because we recognize the service type doesn't mean we can actually @@ -246,15 +246,18 @@ def async_get_or_create_trigger_source( hass: HomeAssistant, device_id: str ) -> TriggerSource: """Get or create a trigger source for a device id.""" - if not (source := hass.data[TRIGGERS].get(device_id)): + trigger_sources: dict[str, TriggerSource] = hass.data.setdefault(TRIGGERS, {}) + if not (source := trigger_sources.get(device_id)): source = TriggerSource(hass) - hass.data[TRIGGERS][device_id] = source + trigger_sources[device_id] = source return source def async_fire_triggers(conn: HKDevice, events: dict[tuple[int, int], dict[str, Any]]): """Process events generated by a HomeKit accessory into automation triggers.""" - trigger_sources: dict[str, TriggerSource] = conn.hass.data[TRIGGERS] + trigger_sources: dict[str, TriggerSource] = conn.hass.data.get(TRIGGERS, {}) + if not trigger_sources: + return for (aid, iid), ev in events.items(): if aid in conn.devices: device_id = conn.devices[aid] From 5c8ccc89b13dc34a566254ee6ecaf0ce06d8488a Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 2 Dec 2022 16:00:49 -1000 Subject: [PATCH 029/174] Bump aiohomekit to 2.3.5 (#83168) --- homeassistant/components/homekit_controller/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/homekit_controller/manifest.json b/homeassistant/components/homekit_controller/manifest.json index 234108d616d..d67eeac1fd3 100644 --- a/homeassistant/components/homekit_controller/manifest.json +++ b/homeassistant/components/homekit_controller/manifest.json @@ -3,7 +3,7 @@ "name": "HomeKit Controller", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/homekit_controller", - "requirements": ["aiohomekit==2.3.4"], + "requirements": ["aiohomekit==2.3.5"], "zeroconf": ["_hap._tcp.local.", "_hap._udp.local."], "bluetooth": [{ "manufacturer_id": 76, "manufacturer_data_start": [6] }], "dependencies": ["bluetooth", "zeroconf"], diff --git a/requirements_all.txt b/requirements_all.txt index 428b8e9a1fd..42b8b05c137 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -174,7 +174,7 @@ aioguardian==2022.07.0 aioharmony==0.2.9 # homeassistant.components.homekit_controller -aiohomekit==2.3.4 +aiohomekit==2.3.5 # homeassistant.components.emulated_hue # homeassistant.components.http diff --git a/requirements_test_all.txt b/requirements_test_all.txt index f9a42dac94e..273cc72c140 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -158,7 +158,7 @@ aioguardian==2022.07.0 aioharmony==0.2.9 # homeassistant.components.homekit_controller -aiohomekit==2.3.4 +aiohomekit==2.3.5 # homeassistant.components.emulated_hue # homeassistant.components.http From 627f337e1e302d384c57ccef710cd7326bd3f500 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 2 Dec 2022 21:01:38 -0500 Subject: [PATCH 030/174] Bumped version to 2022.12.0b2 --- homeassistant/const.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 16dfdc644d3..333b3076dfc 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -8,7 +8,7 @@ from .backports.enum import StrEnum APPLICATION_NAME: Final = "HomeAssistant" MAJOR_VERSION: Final = 2022 MINOR_VERSION: Final = 12 -PATCH_VERSION: Final = "0b1" +PATCH_VERSION: Final = "0b2" __short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__: Final = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 9, 0) diff --git a/pyproject.toml b/pyproject.toml index 260d7553cb6..441c4e95e91 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "homeassistant" -version = "2022.12.0b1" +version = "2022.12.0b2" license = {text = "Apache-2.0"} description = "Open-source home automation platform running on Python 3." readme = "README.rst" From 96cb856308ceef921b3652a8a1a3f7cb4a6d24bd Mon Sep 17 00:00:00 2001 From: Lars Date: Sat, 3 Dec 2022 19:24:06 +0100 Subject: [PATCH 031/174] Add integration_type to AVM FRITZ!SmartHome (#81096) --- homeassistant/components/fritzbox/manifest.json | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/fritzbox/manifest.json b/homeassistant/components/fritzbox/manifest.json index 422db12b68a..d9b8b1795af 100644 --- a/homeassistant/components/fritzbox/manifest.json +++ b/homeassistant/components/fritzbox/manifest.json @@ -1,6 +1,7 @@ { "domain": "fritzbox", "name": "AVM FRITZ!SmartHome", + "integration_type": "hub", "documentation": "https://www.home-assistant.io/integrations/fritzbox", "requirements": ["pyfritzhome==0.6.7"], "ssdp": [ From fcb3445b8edc0cb89603189052576a133d445cee Mon Sep 17 00:00:00 2001 From: Matthias Alphart Date: Sat, 3 Dec 2022 12:53:12 +0100 Subject: [PATCH 032/174] KNX Config/OptionsFlow: Test connection to manually configured tunnel (#82872) --- homeassistant/components/knx/config_flow.py | 39 ++++- homeassistant/components/knx/strings.json | 6 +- tests/components/knx/test_config_flow.py | 181 +++++++++++++++++++- 3 files changed, 208 insertions(+), 18 deletions(-) diff --git a/homeassistant/components/knx/config_flow.py b/homeassistant/components/knx/config_flow.py index 30e48cd4d6a..f7ef0d943a9 100644 --- a/homeassistant/components/knx/config_flow.py +++ b/homeassistant/components/knx/config_flow.py @@ -7,9 +7,10 @@ from typing import Any, Final import voluptuous as vol from xknx import XKNX -from xknx.exceptions.exception import InvalidSecureConfiguration +from xknx.exceptions.exception import CommunicationError, InvalidSecureConfiguration from xknx.io import DEFAULT_MCAST_GRP, DEFAULT_MCAST_PORT from xknx.io.gateway_scanner import GatewayDescriptor, GatewayScanner +from xknx.io.self_description import request_description from xknx.secure import load_keyring from homeassistant.config_entries import ConfigEntry, ConfigFlow, OptionsFlow @@ -204,8 +205,11 @@ class KNXCommonFlow(ABC, FlowHandler): return await self.async_step_manual_tunnel() errors: dict = {} - tunnel_options = [str(tunnel) for tunnel in self._found_tunnels] - tunnel_options.append(OPTION_MANUAL_TUNNEL) + tunnel_options = { + str(tunnel): f"{tunnel}{' 🔐' if tunnel.tunnelling_requires_secure else ''}" + for tunnel in self._found_tunnels + } + tunnel_options |= {OPTION_MANUAL_TUNNEL: OPTION_MANUAL_TUNNEL} fields = {vol.Required(CONF_KNX_GATEWAY): vol.In(tunnel_options)} return self.async_show_form( @@ -230,17 +234,38 @@ class KNXCommonFlow(ABC, FlowHandler): except vol.Invalid: errors[CONF_KNX_LOCAL_IP] = "invalid_ip_address" + selected_tunnelling_type = user_input[CONF_KNX_TUNNELING_TYPE] + if not errors: + try: + self._selected_tunnel = await request_description( + gateway_ip=_host, + gateway_port=user_input[CONF_PORT], + local_ip=_local_ip, + route_back=user_input[CONF_KNX_ROUTE_BACK], + ) + except CommunicationError: + errors["base"] = "cannot_connect" + else: + if bool(self._selected_tunnel.tunnelling_requires_secure) is not ( + selected_tunnelling_type == CONF_KNX_TUNNELING_TCP_SECURE + ): + errors[CONF_KNX_TUNNELING_TYPE] = "unsupported_tunnel_type" + elif ( + selected_tunnelling_type == CONF_KNX_TUNNELING_TCP + and not self._selected_tunnel.supports_tunnelling_tcp + ): + errors[CONF_KNX_TUNNELING_TYPE] = "unsupported_tunnel_type" + if not errors: - connection_type = user_input[CONF_KNX_TUNNELING_TYPE] self.new_entry_data = KNXConfigEntryData( + connection_type=selected_tunnelling_type, host=_host, port=user_input[CONF_PORT], route_back=user_input[CONF_KNX_ROUTE_BACK], local_ip=_local_ip, - connection_type=connection_type, ) - if connection_type == CONF_KNX_TUNNELING_TCP_SECURE: + if selected_tunnelling_type == CONF_KNX_TUNNELING_TCP_SECURE: return self.async_show_menu( step_id="secure_key_source", menu_options=["secure_knxkeys", "secure_routing_manual"], @@ -299,7 +324,7 @@ class KNXCommonFlow(ABC, FlowHandler): if self.show_advanced_options: fields[vol.Optional(CONF_KNX_LOCAL_IP)] = _IP_SELECTOR - if not self._found_tunnels: + if not self._found_tunnels and not errors.get("base"): errors["base"] = "no_tunnel_discovered" return self.async_show_form( step_id="manual_tunnel", data_schema=vol.Schema(fields), errors=errors diff --git a/homeassistant/components/knx/strings.json b/homeassistant/components/knx/strings.json index 0fbb7fc3ae6..632af9961dc 100644 --- a/homeassistant/components/knx/strings.json +++ b/homeassistant/components/knx/strings.json @@ -99,7 +99,8 @@ "invalid_signature": "The password to decrypt the `.knxkeys` file is wrong.", "file_not_found": "The specified `.knxkeys` file was not found in the path config/.storage/knx/", "no_router_discovered": "No KNXnet/IP router was discovered on the network.", - "no_tunnel_discovered": "Could not find a KNX tunneling server on your network." + "no_tunnel_discovered": "Could not find a KNX tunneling server on your network.", + "unsupported_tunnel_type": "Selected tunnelling type not supported by gateway." } }, "options": { @@ -214,7 +215,8 @@ "invalid_signature": "[%key:component::knx::config::error::invalid_signature%]", "file_not_found": "[%key:component::knx::config::error::file_not_found%]", "no_router_discovered": "[%key:component::knx::config::error::no_router_discovered%]", - "no_tunnel_discovered": "[%key:component::knx::config::error::no_tunnel_discovered%]" + "no_tunnel_discovered": "[%key:component::knx::config::error::no_tunnel_discovered%]", + "unsupported_tunnel_type": "[%key:component::knx::config::error::unsupported_tunnel_type%]" } } } diff --git a/tests/components/knx/test_config_flow.py b/tests/components/knx/test_config_flow.py index 9d6eaf68c54..55f7b2a8891 100644 --- a/tests/components/knx/test_config_flow.py +++ b/tests/components/knx/test_config_flow.py @@ -1,8 +1,8 @@ """Test the KNX config flow.""" -from unittest.mock import patch +from unittest.mock import Mock, patch import pytest -from xknx.exceptions.exception import InvalidSecureConfiguration +from xknx.exceptions.exception import CommunicationError, InvalidSecureConfiguration from xknx.io import DEFAULT_MCAST_GRP, DEFAULT_MCAST_PORT from xknx.io.gateway_scanner import GatewayDescriptor @@ -441,7 +441,11 @@ async def test_routing_secure_keyfile( return_value=GatewayScannerMock(), ) async def test_tunneling_setup_manual( - gateway_scanner_mock, hass: HomeAssistant, knx_setup, user_input, config_entry_data + _gateway_scanner_mock, + hass: HomeAssistant, + knx_setup, + user_input, + config_entry_data, ) -> None: """Test tunneling if no gateway was found found (or `manual` option was chosen).""" result = await hass.config_entries.flow.async_init( @@ -460,11 +464,21 @@ async def test_tunneling_setup_manual( assert result2["step_id"] == "manual_tunnel" assert result2["errors"] == {"base": "no_tunnel_discovered"} - result3 = await hass.config_entries.flow.async_configure( - result2["flow_id"], - user_input, - ) - await hass.async_block_till_done() + with patch( + "homeassistant.components.knx.config_flow.request_description", + return_value=_gateway_descriptor( + user_input[CONF_HOST], + user_input[CONF_PORT], + supports_tunnelling_tcp=( + user_input[CONF_KNX_TUNNELING_TYPE] == CONF_KNX_TUNNELING_TCP + ), + ), + ): + result3 = await hass.config_entries.flow.async_configure( + result2["flow_id"], + user_input, + ) + await hass.async_block_till_done() assert result3["type"] == FlowResultType.CREATE_ENTRY assert result3["title"] == "Tunneling @ 192.168.0.1" assert result3["data"] == config_entry_data @@ -475,8 +489,146 @@ async def test_tunneling_setup_manual( "homeassistant.components.knx.config_flow.GatewayScanner", return_value=GatewayScannerMock(), ) +async def test_tunneling_setup_manual_request_description_error( + _gateway_scanner_mock, + hass: HomeAssistant, + knx_setup, +) -> None: + """Test tunneling if no gateway was found found (or `manual` option was chosen).""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_KNX_CONNECTION_TYPE: CONF_KNX_TUNNELING, + }, + ) + assert result["step_id"] == "manual_tunnel" + assert result["errors"] == {"base": "no_tunnel_discovered"} + + # TCP configured but not supported by gateway + with patch( + "homeassistant.components.knx.config_flow.request_description", + return_value=_gateway_descriptor( + "192.168.0.1", + 3671, + supports_tunnelling_tcp=False, + ), + ): + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_KNX_TUNNELING_TYPE: CONF_KNX_TUNNELING_TCP, + CONF_HOST: "192.168.0.1", + CONF_PORT: 3671, + }, + ) + assert result["step_id"] == "manual_tunnel" + assert result["errors"] == { + "base": "no_tunnel_discovered", + "tunneling_type": "unsupported_tunnel_type", + } + # TCP configured but Secure required by gateway + with patch( + "homeassistant.components.knx.config_flow.request_description", + return_value=_gateway_descriptor( + "192.168.0.1", + 3671, + supports_tunnelling_tcp=True, + requires_secure=True, + ), + ): + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_KNX_TUNNELING_TYPE: CONF_KNX_TUNNELING_TCP, + CONF_HOST: "192.168.0.1", + CONF_PORT: 3671, + }, + ) + assert result["step_id"] == "manual_tunnel" + assert result["errors"] == { + "base": "no_tunnel_discovered", + "tunneling_type": "unsupported_tunnel_type", + } + # Secure configured but not enabled on gateway + with patch( + "homeassistant.components.knx.config_flow.request_description", + return_value=_gateway_descriptor( + "192.168.0.1", + 3671, + supports_tunnelling_tcp=True, + requires_secure=False, + ), + ): + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_KNX_TUNNELING_TYPE: CONF_KNX_TUNNELING_TCP_SECURE, + CONF_HOST: "192.168.0.1", + CONF_PORT: 3671, + }, + ) + assert result["step_id"] == "manual_tunnel" + assert result["errors"] == { + "base": "no_tunnel_discovered", + "tunneling_type": "unsupported_tunnel_type", + } + # No connection to gateway + with patch( + "homeassistant.components.knx.config_flow.request_description", + side_effect=CommunicationError(""), + ): + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_KNX_TUNNELING_TYPE: CONF_KNX_TUNNELING_TCP, + CONF_HOST: "192.168.0.1", + CONF_PORT: 3671, + }, + ) + assert result["step_id"] == "manual_tunnel" + assert result["errors"] == {"base": "cannot_connect"} + # OK configuration + with patch( + "homeassistant.components.knx.config_flow.request_description", + return_value=_gateway_descriptor( + "192.168.0.1", + 3671, + supports_tunnelling_tcp=True, + ), + ): + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_KNX_TUNNELING_TYPE: CONF_KNX_TUNNELING_TCP, + CONF_HOST: "192.168.0.1", + CONF_PORT: 3671, + }, + ) + await hass.async_block_till_done() + assert result["type"] == FlowResultType.CREATE_ENTRY + assert result["title"] == "Tunneling @ 192.168.0.1" + assert result["data"] == { + **DEFAULT_ENTRY_DATA, + CONF_KNX_CONNECTION_TYPE: CONF_KNX_TUNNELING_TCP, + CONF_HOST: "192.168.0.1", + CONF_PORT: 3671, + } + knx_setup.assert_called_once() + + +@patch( + "homeassistant.components.knx.config_flow.GatewayScanner", + return_value=GatewayScannerMock(), +) +@patch( + "homeassistant.components.knx.config_flow.request_description", + return_value=_gateway_descriptor("192.168.0.2", 3675), +) async def test_tunneling_setup_for_local_ip( - gateway_scanner_mock, hass: HomeAssistant, knx_setup + _request_description_mock, _gateway_scanner_mock, hass: HomeAssistant, knx_setup ) -> None: """Test tunneling if only one gateway is found.""" result = await hass.config_entries.flow.async_init( @@ -715,7 +867,17 @@ async def _get_menu_step(hass: HomeAssistant) -> FlowResult: return result3 +@patch( + "homeassistant.components.knx.config_flow.request_description", + return_value=_gateway_descriptor( + "192.168.0.1", + 3675, + supports_tunnelling_tcp=True, + requires_secure=True, + ), +) async def test_get_secure_menu_step_manual_tunnelling( + _request_description_mock, hass: HomeAssistant, ): """Test flow reaches secure_tunnellinn menu step from manual tunnelling configuration.""" @@ -908,6 +1070,7 @@ async def test_options_flow_connection_type( gateway = _gateway_descriptor("192.168.0.1", 3675) await hass.config_entries.async_setup(mock_config_entry.entry_id) + hass.data[DOMAIN] = Mock() # GatewayScanner uses running XKNX() in options flow menu_step = await hass.config_entries.options.async_init(mock_config_entry.entry_id) with patch( From a7993e06402f6667c5b67952d3d03213e0cdcda3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Thu, 1 Dec 2022 09:08:39 +0100 Subject: [PATCH 033/174] Bump hass-nabucasa from 0.56.0 to 0.59.0 (#82987) * Bump hass-nabucasa from 0.56.0 to 0.58.0 * 0.59.0 --- homeassistant/components/cloud/__init__.py | 38 ++++++++++--------- homeassistant/components/cloud/const.py | 20 +++++----- homeassistant/components/cloud/manifest.json | 2 +- .../components/cloud/system_health.py | 5 +-- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/cloud/test_alexa_config.py | 12 +++--- tests/components/cloud/test_http_api.py | 6 +-- tests/components/cloud/test_init.py | 33 ++++++++-------- tests/components/cloud/test_system_health.py | 6 +-- 11 files changed, 64 insertions(+), 64 deletions(-) diff --git a/homeassistant/components/cloud/__init__.py b/homeassistant/components/cloud/__init__.py index 0662487b765..c5918dcf28f 100644 --- a/homeassistant/components/cloud/__init__.py +++ b/homeassistant/components/cloud/__init__.py @@ -36,22 +36,23 @@ from homeassistant.util.aiohttp import MockRequest from . import account_link, http_api from .client import CloudClient from .const import ( - CONF_ACCOUNT_LINK_URL, - CONF_ACME_DIRECTORY_SERVER, + CONF_ACCOUNT_LINK_SERVER, + CONF_ACCOUNTS_SERVER, + CONF_ACME_SERVER, CONF_ALEXA, - CONF_ALEXA_ACCESS_TOKEN_URL, + CONF_ALEXA_SERVER, CONF_ALIASES, - CONF_CLOUDHOOK_CREATE_URL, + CONF_CLOUDHOOK_SERVER, CONF_COGNITO_CLIENT_ID, CONF_ENTITY_CONFIG, CONF_FILTER, CONF_GOOGLE_ACTIONS, - CONF_GOOGLE_ACTIONS_REPORT_STATE_URL, - CONF_RELAYER, - CONF_REMOTE_API_URL, - CONF_SUBSCRIPTION_INFO_URL, + CONF_RELAYER_SERVER, + CONF_REMOTE_SNI_SERVER, + CONF_REMOTESTATE_SERVER, + CONF_THINGTALK_SERVER, CONF_USER_POOL_ID, - CONF_VOICE_API_URL, + CONF_VOICE_SERVER, DOMAIN, MODE_DEV, MODE_PROD, @@ -107,17 +108,18 @@ CONFIG_SCHEMA = vol.Schema( vol.Optional(CONF_COGNITO_CLIENT_ID): str, vol.Optional(CONF_USER_POOL_ID): str, vol.Optional(CONF_REGION): str, - vol.Optional(CONF_RELAYER): str, - vol.Optional(CONF_SUBSCRIPTION_INFO_URL): vol.Url(), - vol.Optional(CONF_CLOUDHOOK_CREATE_URL): vol.Url(), - vol.Optional(CONF_REMOTE_API_URL): vol.Url(), - vol.Optional(CONF_ACME_DIRECTORY_SERVER): vol.Url(), vol.Optional(CONF_ALEXA): ALEXA_SCHEMA, vol.Optional(CONF_GOOGLE_ACTIONS): GACTIONS_SCHEMA, - vol.Optional(CONF_ALEXA_ACCESS_TOKEN_URL): vol.Url(), - vol.Optional(CONF_GOOGLE_ACTIONS_REPORT_STATE_URL): vol.Url(), - vol.Optional(CONF_ACCOUNT_LINK_URL): vol.Url(), - vol.Optional(CONF_VOICE_API_URL): vol.Url(), + vol.Optional(CONF_ACCOUNT_LINK_SERVER): str, + vol.Optional(CONF_ACCOUNTS_SERVER): str, + vol.Optional(CONF_ACME_SERVER): str, + vol.Optional(CONF_ALEXA_SERVER): str, + vol.Optional(CONF_CLOUDHOOK_SERVER): str, + vol.Optional(CONF_RELAYER_SERVER): str, + vol.Optional(CONF_REMOTE_SNI_SERVER): str, + vol.Optional(CONF_REMOTESTATE_SERVER): str, + vol.Optional(CONF_THINGTALK_SERVER): str, + vol.Optional(CONF_VOICE_SERVER): str, } ) }, diff --git a/homeassistant/components/cloud/const.py b/homeassistant/components/cloud/const.py index ea0240acccf..9fb4ffc7047 100644 --- a/homeassistant/components/cloud/const.py +++ b/homeassistant/components/cloud/const.py @@ -47,16 +47,18 @@ CONF_COGNITO_CLIENT_ID = "cognito_client_id" CONF_ENTITY_CONFIG = "entity_config" CONF_FILTER = "filter" CONF_GOOGLE_ACTIONS = "google_actions" -CONF_RELAYER = "relayer" CONF_USER_POOL_ID = "user_pool_id" -CONF_SUBSCRIPTION_INFO_URL = "subscription_info_url" -CONF_CLOUDHOOK_CREATE_URL = "cloudhook_create_url" -CONF_REMOTE_API_URL = "remote_api_url" -CONF_ACME_DIRECTORY_SERVER = "acme_directory_server" -CONF_ALEXA_ACCESS_TOKEN_URL = "alexa_access_token_url" -CONF_GOOGLE_ACTIONS_REPORT_STATE_URL = "google_actions_report_state_url" -CONF_ACCOUNT_LINK_URL = "account_link_url" -CONF_VOICE_API_URL = "voice_api_url" + +CONF_ACCOUNT_LINK_SERVER = "account_link_server" +CONF_ACCOUNTS_SERVER = "accounts_server" +CONF_ACME_SERVER = "acme_server" +CONF_ALEXA_SERVER = "alexa_server" +CONF_CLOUDHOOK_SERVER = "cloudhook_server" +CONF_RELAYER_SERVER = "relayer_server" +CONF_REMOTE_SNI_SERVER = "remote_sni_server" +CONF_REMOTESTATE_SERVER = "remotestate_server" +CONF_THINGTALK_SERVER = "thingtalk_server" +CONF_VOICE_SERVER = "voice_server" MODE_DEV = "development" MODE_PROD = "production" diff --git a/homeassistant/components/cloud/manifest.json b/homeassistant/components/cloud/manifest.json index 97f581d3bf0..23d72f7cb03 100644 --- a/homeassistant/components/cloud/manifest.json +++ b/homeassistant/components/cloud/manifest.json @@ -2,7 +2,7 @@ "domain": "cloud", "name": "Home Assistant Cloud", "documentation": "https://www.home-assistant.io/integrations/cloud", - "requirements": ["hass-nabucasa==0.56.0"], + "requirements": ["hass-nabucasa==0.59.0"], "dependencies": ["http", "webhook"], "after_dependencies": ["google_assistant", "alexa"], "codeowners": ["@home-assistant/cloud"], diff --git a/homeassistant/components/cloud/system_health.py b/homeassistant/components/cloud/system_health.py index 4d8a6eab64c..9f836114b3e 100644 --- a/homeassistant/components/cloud/system_health.py +++ b/homeassistant/components/cloud/system_health.py @@ -1,6 +1,5 @@ """Provide info to system health.""" from hass_nabucasa import Cloud -from yarl import URL from homeassistant.components import system_health from homeassistant.core import HomeAssistant, callback @@ -36,14 +35,14 @@ async def system_health_info(hass): data["remote_server"] = cloud.remote.snitun_server data["can_reach_cert_server"] = system_health.async_check_can_reach_url( - hass, cloud.acme_directory_server + hass, f"https://{cloud.acme_server}/directory" ) data["can_reach_cloud_auth"] = system_health.async_check_can_reach_url( hass, f"https://cognito-idp.{cloud.region}.amazonaws.com/{cloud.user_pool_id}/.well-known/jwks.json", ) data["can_reach_cloud"] = system_health.async_check_can_reach_url( - hass, URL(cloud.relayer).with_scheme("https").with_path("/status") + hass, f"https://{cloud.relayer_server}/status" ) return data diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index f41444de531..d281d0cedcd 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -20,7 +20,7 @@ ciso8601==2.2.0 cryptography==38.0.3 dbus-fast==1.75.0 fnvhash==0.1.0 -hass-nabucasa==0.56.0 +hass-nabucasa==0.59.0 home-assistant-bluetooth==1.8.1 home-assistant-frontend==20221201.1 httpx==0.23.1 diff --git a/requirements_all.txt b/requirements_all.txt index 42b8b05c137..e225bfdc68e 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -848,7 +848,7 @@ ha-philipsjs==2.9.0 habitipy==0.2.0 # homeassistant.components.cloud -hass-nabucasa==0.56.0 +hass-nabucasa==0.59.0 # homeassistant.components.splunk hass_splunk==0.1.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 273cc72c140..00001f1c723 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -640,7 +640,7 @@ ha-philipsjs==2.9.0 habitipy==0.2.0 # homeassistant.components.cloud -hass-nabucasa==0.56.0 +hass-nabucasa==0.59.0 # homeassistant.components.tasmota hatasmota==0.6.1 diff --git a/tests/components/cloud/test_alexa_config.py b/tests/components/cloud/test_alexa_config.py index 4e0df3c8ee3..b7637780b12 100644 --- a/tests/components/cloud/test_alexa_config.py +++ b/tests/components/cloud/test_alexa_config.py @@ -129,7 +129,7 @@ async def test_alexa_config_report_state(hass, cloud_prefs, cloud_stub): async def test_alexa_config_invalidate_token(hass, cloud_prefs, aioclient_mock): """Test Alexa config should expose using prefs.""" aioclient_mock.post( - "http://example/alexa_token", + "https://example/access_token", json={ "access_token": "mock-token", "event_endpoint": "http://example.com/alexa_endpoint", @@ -142,7 +142,7 @@ async def test_alexa_config_invalidate_token(hass, cloud_prefs, aioclient_mock): "mock-user-id", cloud_prefs, Mock( - alexa_access_token_url="http://example/alexa_token", + alexa_server="example", auth=Mock(async_check_token=AsyncMock()), websession=async_get_clientsession(hass), ), @@ -181,7 +181,7 @@ async def test_alexa_config_fail_refresh_token( """Test Alexa config failing to refresh token.""" aioclient_mock.post( - "http://example/alexa_token", + "https://example/access_token", json={ "access_token": "mock-token", "event_endpoint": "http://example.com/alexa_endpoint", @@ -198,7 +198,7 @@ async def test_alexa_config_fail_refresh_token( "mock-user-id", cloud_prefs, Mock( - alexa_access_token_url="http://example/alexa_token", + alexa_server="example", auth=Mock(async_check_token=AsyncMock()), websession=async_get_clientsession(hass), ), @@ -228,7 +228,7 @@ async def test_alexa_config_fail_refresh_token( conf.async_invalidate_access_token() aioclient_mock.clear_requests() aioclient_mock.post( - "http://example/alexa_token", + "https://example/access_token", json={"reason": reject_reason}, status=400, ) @@ -254,7 +254,7 @@ async def test_alexa_config_fail_refresh_token( # State reporting should now be re-enabled for Alexa aioclient_mock.clear_requests() aioclient_mock.post( - "http://example/alexa_token", + "https://example/access_token", json={ "access_token": "mock-token", "event_endpoint": "http://example.com/alexa_endpoint", diff --git a/tests/components/cloud/test_http_api.py b/tests/components/cloud/test_http_api.py index 1f9af960f08..0dbc20d4f91 100644 --- a/tests/components/cloud/test_http_api.py +++ b/tests/components/cloud/test_http_api.py @@ -21,7 +21,7 @@ from . import mock_cloud, mock_cloud_prefs from tests.components.google_assistant import MockConfig -SUBSCRIPTION_INFO_URL = "https://api-test.hass.io/subscription_info" +SUBSCRIPTION_INFO_URL = "https://api-test.hass.io/payments/subscription_info" @pytest.fixture(name="mock_cloud_login") @@ -48,8 +48,8 @@ def setup_api_fixture(hass, aioclient_mock): "cognito_client_id": "cognito_client_id", "user_pool_id": "user_pool_id", "region": "region", - "relayer": "relayer", - "subscription_info_url": SUBSCRIPTION_INFO_URL, + "relayer_server": "relayer", + "accounts_server": "api-test.hass.io", "google_actions": {"filter": {"include_domains": "light"}}, "alexa": { "filter": {"include_entities": ["light.kitchen", "switch.ac"]} diff --git a/tests/components/cloud/test_init.py b/tests/components/cloud/test_init.py index 78a8f83eef6..48caf27dfe2 100644 --- a/tests/components/cloud/test_init.py +++ b/tests/components/cloud/test_init.py @@ -26,13 +26,13 @@ async def test_constructor_loads_info_from_config(hass): "cognito_client_id": "test-cognito_client_id", "user_pool_id": "test-user_pool_id", "region": "test-region", - "relayer": "test-relayer", - "subscription_info_url": "http://test-subscription-info-url", - "cloudhook_create_url": "http://test-cloudhook_create_url", - "remote_api_url": "http://test-remote_api_url", - "alexa_access_token_url": "http://test-alexa-token-url", - "acme_directory_server": "http://test-acme-directory-server", - "google_actions_report_state_url": "http://test-google-actions-report-state-url", + "relayer_server": "test-relayer-server", + "accounts_server": "test-acounts-server", + "cloudhook_server": "test-cloudhook-server", + "remote_sni_server": "test-remote-sni-server", + "alexa_server": "test-alexa-server", + "acme_server": "test-acme-server", + "remotestate_server": "test-remotestate-server", }, }, ) @@ -43,16 +43,13 @@ async def test_constructor_loads_info_from_config(hass): assert cl.cognito_client_id == "test-cognito_client_id" assert cl.user_pool_id == "test-user_pool_id" assert cl.region == "test-region" - assert cl.relayer == "test-relayer" - assert cl.subscription_info_url == "http://test-subscription-info-url" - assert cl.cloudhook_create_url == "http://test-cloudhook_create_url" - assert cl.remote_api_url == "http://test-remote_api_url" - assert cl.alexa_access_token_url == "http://test-alexa-token-url" - assert cl.acme_directory_server == "http://test-acme-directory-server" - assert ( - cl.google_actions_report_state_url - == "http://test-google-actions-report-state-url" - ) + assert cl.relayer_server == "test-relayer-server" + assert cl.iot.ws_server_url == "wss://test-relayer-server/websocket" + assert cl.accounts_server == "test-acounts-server" + assert cl.cloudhook_server == "test-cloudhook-server" + assert cl.alexa_server == "test-alexa-server" + assert cl.acme_server == "test-acme-server" + assert cl.remotestate_server == "test-remotestate-server" async def test_remote_services(hass, mock_cloud_fixture, hass_read_only_user): @@ -120,7 +117,7 @@ async def test_setup_existing_cloud_user(hass, hass_storage): "cognito_client_id": "test-cognito_client_id", "user_pool_id": "test-user_pool_id", "region": "test-region", - "relayer": "test-relayer", + "relayer_server": "test-relayer-serer", }, }, ) diff --git a/tests/components/cloud/test_system_health.py b/tests/components/cloud/test_system_health.py index cc37788bc4c..dae5ac4b4cc 100644 --- a/tests/components/cloud/test_system_health.py +++ b/tests/components/cloud/test_system_health.py @@ -13,7 +13,7 @@ from tests.common import get_system_health_info async def test_cloud_system_health(hass, aioclient_mock): """Test cloud system health.""" aioclient_mock.get("https://cloud.bla.com/status", text="") - aioclient_mock.get("https://cert-server", text="") + aioclient_mock.get("https://cert-server/directory", text="") aioclient_mock.get( "https://cognito-idp.us-east-1.amazonaws.com/AAAA/.well-known/jwks.json", exc=ClientError, @@ -25,8 +25,8 @@ async def test_cloud_system_health(hass, aioclient_mock): hass.data["cloud"] = Mock( region="us-east-1", user_pool_id="AAAA", - relayer="wss://cloud.bla.com/websocket_api", - acme_directory_server="https://cert-server", + relayer_server="cloud.bla.com", + acme_server="cert-server", is_logged_in=True, remote=Mock(is_connected=False, snitun_server="us-west-1"), expiration_date=now, From bb827a60ed27fd74797bd1f5276f2653d7bd3d57 Mon Sep 17 00:00:00 2001 From: Olen Date: Sat, 3 Dec 2022 19:23:29 +0100 Subject: [PATCH 034/174] Support older twinkly devices without effects (#83145) fixes undefined --- homeassistant/components/twinkly/const.py | 4 + homeassistant/components/twinkly/light.py | 96 ++++++++++----- tests/components/twinkly/__init__.py | 18 ++- tests/components/twinkly/test_light.py | 135 +++++++++++++++++++--- 4 files changed, 204 insertions(+), 49 deletions(-) diff --git a/homeassistant/components/twinkly/const.py b/homeassistant/components/twinkly/const.py index e48ff165c67..d0d905a5752 100644 --- a/homeassistant/components/twinkly/const.py +++ b/homeassistant/components/twinkly/const.py @@ -9,6 +9,7 @@ CONF_NAME = "name" # Strongly named HA attributes keys ATTR_HOST = "host" +ATTR_VERSION = "version" # Keys of attributes read from the get_device_info DEV_ID = "uuid" @@ -27,3 +28,6 @@ HIDDEN_DEV_VALUES = ( "copyright", # We should not display a copyright "LEDWORKS 2018" in the Home-Assistant UI "mac", # Does not report the actual device mac address ) + +# Minimum version required to support effects +MIN_EFFECT_VERSION = "2.7.1" diff --git a/homeassistant/components/twinkly/light.py b/homeassistant/components/twinkly/light.py index 8f8e8218d87..c7cf13b6563 100644 --- a/homeassistant/components/twinkly/light.py +++ b/homeassistant/components/twinkly/light.py @@ -7,6 +7,7 @@ import logging from typing import Any from aiohttp import ClientError +from packaging import version from ttls.client import Twinkly from homeassistant.components.light import ( @@ -25,6 +26,7 @@ from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.entity_platform import AddEntitiesCallback from .const import ( + ATTR_VERSION, CONF_HOST, CONF_ID, CONF_NAME, @@ -37,6 +39,7 @@ from .const import ( DEV_PROFILE_RGBW, DOMAIN, HIDDEN_DEV_VALUES, + MIN_EFFECT_VERSION, ) _LOGGER = logging.getLogger(__name__) @@ -96,6 +99,9 @@ class TwinklyLight(LightEntity): self._attributes: dict[Any, Any] = {} self._current_movie: dict[Any, Any] = {} self._movies: list[Any] = [] + self._software_version = "" + # We guess that most devices are "new" and support effects + self._attr_supported_features = LightEntityFeature.EFFECT @property def available(self) -> bool: @@ -130,13 +136,9 @@ class TwinklyLight(LightEntity): manufacturer="LEDWORKS", model=self.model, name=self.name, + sw_version=self._software_version, ) - @property - def supported_features(self) -> LightEntityFeature: - """Return supported features.""" - return LightEntityFeature.EFFECT - @property def is_on(self) -> bool: """Return true if light is on.""" @@ -165,6 +167,19 @@ class TwinklyLight(LightEntity): effect_list.append(f"{movie['id']} {movie['name']}") return effect_list + async def async_added_to_hass(self) -> None: + """Device is added to hass.""" + software_version = await self._client.get_firmware_version() + if ATTR_VERSION in software_version: + self._software_version = software_version[ATTR_VERSION] + + if version.parse(self._software_version) < version.parse( + MIN_EFFECT_VERSION + ): + self._attr_supported_features = ( + self.supported_features & ~LightEntityFeature.EFFECT + ) + async def async_turn_on(self, **kwargs: Any) -> None: """Turn device on.""" if ATTR_BRIGHTNESS in kwargs: @@ -178,36 +193,54 @@ class TwinklyLight(LightEntity): await self._client.set_brightness(brightness) - if ATTR_RGBW_COLOR in kwargs: - if kwargs[ATTR_RGBW_COLOR] != self._attr_rgbw_color: - self._attr_rgbw_color = kwargs[ATTR_RGBW_COLOR] + if ( + ATTR_RGBW_COLOR in kwargs + and kwargs[ATTR_RGBW_COLOR] != self._attr_rgbw_color + ): - if isinstance(self._attr_rgbw_color, tuple): - - await self._client.interview() - # Static color only supports rgb - await self._client.set_static_colour( - ( - self._attr_rgbw_color[0], - self._attr_rgbw_color[1], - self._attr_rgbw_color[2], - ) + await self._client.interview() + if LightEntityFeature.EFFECT & self.supported_features: + # Static color only supports rgb + await self._client.set_static_colour( + ( + kwargs[ATTR_RGBW_COLOR][0], + kwargs[ATTR_RGBW_COLOR][1], + kwargs[ATTR_RGBW_COLOR][2], ) - await self._client.set_mode("color") - self._client.default_mode = "color" + ) + await self._client.set_mode("color") + self._client.default_mode = "color" + else: + await self._client.set_cycle_colours( + ( + kwargs[ATTR_RGBW_COLOR][3], + kwargs[ATTR_RGBW_COLOR][0], + kwargs[ATTR_RGBW_COLOR][1], + kwargs[ATTR_RGBW_COLOR][2], + ) + ) + await self._client.set_mode("movie") + self._client.default_mode = "movie" + self._attr_rgbw_color = kwargs[ATTR_RGBW_COLOR] - if ATTR_RGB_COLOR in kwargs: - if kwargs[ATTR_RGB_COLOR] != self._attr_rgb_color: - self._attr_rgb_color = kwargs[ATTR_RGB_COLOR] + if ATTR_RGB_COLOR in kwargs and kwargs[ATTR_RGB_COLOR] != self._attr_rgb_color: - if isinstance(self._attr_rgb_color, tuple): + await self._client.interview() + if LightEntityFeature.EFFECT & self.supported_features: + await self._client.set_static_colour(kwargs[ATTR_RGB_COLOR]) + await self._client.set_mode("color") + self._client.default_mode = "color" + else: + await self._client.set_cycle_colours(kwargs[ATTR_RGB_COLOR]) + await self._client.set_mode("movie") + self._client.default_mode = "movie" - await self._client.interview() - await self._client.set_static_colour(self._attr_rgb_color) - await self._client.set_mode("color") - self._client.default_mode = "color" + self._attr_rgb_color = kwargs[ATTR_RGB_COLOR] - if ATTR_EFFECT in kwargs: + if ( + ATTR_EFFECT in kwargs + and LightEntityFeature.EFFECT & self.supported_features + ): movie_id = kwargs[ATTR_EFFECT].split(" ")[0] if "id" not in self._current_movie or int(movie_id) != int( self._current_movie["id"] @@ -268,8 +301,9 @@ class TwinklyLight(LightEntity): if key not in HIDDEN_DEV_VALUES: self._attributes[key] = value - await self.async_update_movies() - await self.async_update_current_movie() + if LightEntityFeature.EFFECT & self.supported_features: + await self.async_update_movies() + await self.async_update_current_movie() if not self._is_available: _LOGGER.info("Twinkly '%s' is now available", self._client.host) diff --git a/tests/components/twinkly/__init__.py b/tests/components/twinkly/__init__.py index 351f5509aa2..31d1eff2a61 100644 --- a/tests/components/twinkly/__init__.py +++ b/tests/components/twinkly/__init__.py @@ -25,6 +25,8 @@ class ClientMock: self.movies = [{"id": 1, "name": "Rainbow"}, {"id": 2, "name": "Flare"}] self.current_movie = {} self.default_mode = "movie" + self.mode = None + self.version = "2.8.10" self.id = str(uuid4()) self.device_info = { @@ -55,6 +57,7 @@ class ClientMock: if self.is_offline: raise ClientConnectionError() self.state = True + self.mode = self.default_mode async def turn_off(self) -> None: """Set the mocked off state.""" @@ -81,6 +84,12 @@ class ClientMock: async def set_static_colour(self, colour) -> None: """Set static color.""" self.color = colour + self.default_mode = "color" + + async def set_cycle_colours(self, colour) -> None: + """Set static color.""" + self.color = colour + self.default_mode = "movie" async def interview(self) -> None: """Interview.""" @@ -100,6 +109,11 @@ class ClientMock: async def set_mode(self, mode: str) -> None: """Set mode.""" if mode == "off": - self.turn_off + await self.turn_off() else: - self.turn_on + await self.turn_on() + self.mode = mode + + async def get_firmware_version(self) -> dict: + """Get firmware version.""" + return {"version": self.version} diff --git a/tests/components/twinkly/test_light.py b/tests/components/twinkly/test_light.py index f6fe9e297f6..53e589c564b 100644 --- a/tests/components/twinkly/test_light.py +++ b/tests/components/twinkly/test_light.py @@ -3,7 +3,7 @@ from __future__ import annotations from unittest.mock import patch -from homeassistant.components.light import ATTR_BRIGHTNESS +from homeassistant.components.light import ATTR_BRIGHTNESS, LightEntityFeature from homeassistant.components.twinkly.const import ( CONF_HOST, CONF_ID, @@ -55,9 +55,8 @@ async def test_turn_on_off(hass: HomeAssistant): assert hass.states.get(entity.entity_id).state == "off" await hass.services.async_call( - "light", "turn_on", service_data={"entity_id": entity.entity_id} + "light", "turn_on", service_data={"entity_id": entity.entity_id}, blocking=True ) - await hass.async_block_till_done() state = hass.states.get(entity.entity_id) @@ -78,8 +77,8 @@ async def test_turn_on_with_brightness(hass: HomeAssistant): "light", "turn_on", service_data={"entity_id": entity.entity_id, "brightness": 255}, + blocking=True, ) - await hass.async_block_till_done() state = hass.states.get(entity.entity_id) @@ -90,8 +89,8 @@ async def test_turn_on_with_brightness(hass: HomeAssistant): "light", "turn_on", service_data={"entity_id": entity.entity_id, "brightness": 1}, + blocking=True, ) - await hass.async_block_till_done() state = hass.states.get(entity.entity_id) @@ -99,7 +98,7 @@ async def test_turn_on_with_brightness(hass: HomeAssistant): async def test_turn_on_with_color_rgbw(hass: HomeAssistant): - """Test support of the light.turn_on service with a brightness parameter.""" + """Test support of the light.turn_on service with a rgbw parameter.""" client = ClientMock() client.state = False client.device_info["led_profile"] = "RGBW" @@ -107,23 +106,28 @@ async def test_turn_on_with_color_rgbw(hass: HomeAssistant): entity, _, _, _ = await _create_entries(hass, client) assert hass.states.get(entity.entity_id).state == "off" + assert ( + LightEntityFeature.EFFECT + & hass.states.get(entity.entity_id).attributes["supported_features"] + ) await hass.services.async_call( "light", "turn_on", service_data={"entity_id": entity.entity_id, "rgbw_color": (128, 64, 32, 0)}, + blocking=True, ) - await hass.async_block_till_done() state = hass.states.get(entity.entity_id) assert state.state == "on" assert client.color == (128, 64, 32) assert client.default_mode == "color" + assert client.mode == "color" async def test_turn_on_with_color_rgb(hass: HomeAssistant): - """Test support of the light.turn_on service with a brightness parameter.""" + """Test support of the light.turn_on service with a rgb parameter.""" client = ClientMock() client.state = False client.device_info["led_profile"] = "RGB" @@ -131,23 +135,28 @@ async def test_turn_on_with_color_rgb(hass: HomeAssistant): entity, _, _, _ = await _create_entries(hass, client) assert hass.states.get(entity.entity_id).state == "off" + assert ( + LightEntityFeature.EFFECT + & hass.states.get(entity.entity_id).attributes["supported_features"] + ) await hass.services.async_call( "light", "turn_on", service_data={"entity_id": entity.entity_id, "rgb_color": (128, 64, 32)}, + blocking=True, ) - await hass.async_block_till_done() state = hass.states.get(entity.entity_id) assert state.state == "on" assert client.color == (128, 64, 32) assert client.default_mode == "color" + assert client.mode == "color" async def test_turn_on_with_effect(hass: HomeAssistant): - """Test support of the light.turn_on service with a brightness parameter.""" + """Test support of the light.turn_on service with effects.""" client = ClientMock() client.state = False client.device_info["led_profile"] = "RGB" @@ -155,20 +164,116 @@ async def test_turn_on_with_effect(hass: HomeAssistant): entity, _, _, _ = await _create_entries(hass, client) assert hass.states.get(entity.entity_id).state == "off" - assert client.current_movie == {} + assert not client.current_movie + assert ( + LightEntityFeature.EFFECT + & hass.states.get(entity.entity_id).attributes["supported_features"] + ) await hass.services.async_call( "light", "turn_on", service_data={"entity_id": entity.entity_id, "effect": "1 Rainbow"}, + blocking=True, ) - await hass.async_block_till_done() state = hass.states.get(entity.entity_id) assert state.state == "on" assert client.current_movie["id"] == 1 assert client.default_mode == "movie" + assert client.mode == "movie" + + +async def test_turn_on_with_color_rgbw_and_missing_effect(hass: HomeAssistant): + """Test support of the light.turn_on service with rgbw color and missing effect support.""" + client = ClientMock() + client.state = False + client.device_info["led_profile"] = "RGBW" + client.brightness = {"mode": "enabled", "value": 255} + client.version = "2.7.0" + entity, _, _, _ = await _create_entries(hass, client) + + assert hass.states.get(entity.entity_id).state == "off" + assert ( + not LightEntityFeature.EFFECT + & hass.states.get(entity.entity_id).attributes["supported_features"] + ) + + await hass.services.async_call( + "light", + "turn_on", + service_data={"entity_id": entity.entity_id, "rgbw_color": (128, 64, 32, 0)}, + blocking=True, + ) + + state = hass.states.get(entity.entity_id) + + assert state.state == "on" + assert client.color == (0, 128, 64, 32) + assert client.mode == "movie" + assert client.default_mode == "movie" + + +async def test_turn_on_with_color_rgb_and_missing_effect(hass: HomeAssistant): + """Test support of the light.turn_on service with rgb color and missing effect support.""" + client = ClientMock() + client.state = False + client.device_info["led_profile"] = "RGB" + client.brightness = {"mode": "enabled", "value": 255} + client.version = "2.7.0" + entity, _, _, _ = await _create_entries(hass, client) + + assert hass.states.get(entity.entity_id).state == "off" + assert ( + not LightEntityFeature.EFFECT + & hass.states.get(entity.entity_id).attributes["supported_features"] + ) + + await hass.services.async_call( + "light", + "turn_on", + service_data={"entity_id": entity.entity_id, "rgb_color": (128, 64, 32)}, + blocking=True, + ) + + state = hass.states.get(entity.entity_id) + + assert state.state == "on" + assert client.color == (128, 64, 32) + assert client.mode == "movie" + assert client.default_mode == "movie" + + +async def test_turn_on_with_effect_missing_effects(hass: HomeAssistant): + """Test support of the light.turn_on service with effect set even if effects are not supported.""" + client = ClientMock() + client.state = False + client.device_info["led_profile"] = "RGB" + client.brightness = {"mode": "enabled", "value": 255} + client.version = "2.7.0" + entity, _, _, _ = await _create_entries(hass, client) + + assert hass.states.get(entity.entity_id).state == "off" + assert not client.current_movie + assert ( + not LightEntityFeature.EFFECT + & hass.states.get(entity.entity_id).attributes["supported_features"] + ) + + await hass.services.async_call( + "light", + "turn_on", + service_data={"entity_id": entity.entity_id, "effect": "1 Rainbow"}, + blocking=True, + ) + + state = hass.states.get(entity.entity_id) + + assert state.state == "on" + assert not client.current_movie + assert client.default_mode == "movie" + assert client.mode == "movie" async def test_turn_off(hass: HomeAssistant): @@ -178,9 +283,8 @@ async def test_turn_off(hass: HomeAssistant): assert hass.states.get(entity.entity_id).state == "on" await hass.services.async_call( - "light", "turn_off", service_data={"entity_id": entity.entity_id} + "light", "turn_off", service_data={"entity_id": entity.entity_id}, blocking=True ) - await hass.async_block_till_done() state = hass.states.get(entity.entity_id) @@ -199,9 +303,8 @@ async def test_update_name(hass: HomeAssistant): client.change_name("new_device_name") await hass.services.async_call( - "light", "turn_off", service_data={"entity_id": entity.entity_id} + "light", "turn_off", service_data={"entity_id": entity.entity_id}, blocking=True ) # We call turn_off which will automatically cause an async_update - await hass.async_block_till_done() state = hass.states.get(entity.entity_id) From 75038d420ca30b8f13cfee4ec2ea62d6be0bec3d Mon Sep 17 00:00:00 2001 From: Kevin Stillhammer Date: Sat, 3 Dec 2022 19:06:10 +0100 Subject: [PATCH 035/174] Raise UpdateFailed when here_travel_time cannot find_location (#83157) Fixes https://github.com/home-assistant/core/issues/83100 fixes undefined --- .../components/here_travel_time/coordinator.py | 14 ++++++-------- .../components/here_travel_time/sensor.py | 4 ++-- tests/components/here_travel_time/test_sensor.py | 12 ++++++++---- 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/homeassistant/components/here_travel_time/coordinator.py b/homeassistant/components/here_travel_time/coordinator.py index 1d3a4edb6b0..97759510d36 100644 --- a/homeassistant/components/here_travel_time/coordinator.py +++ b/homeassistant/components/here_travel_time/coordinator.py @@ -14,7 +14,7 @@ from homeassistant.const import ATTR_ATTRIBUTION, UnitOfLength from homeassistant.core import HomeAssistant import homeassistant.helpers.config_validation as cv from homeassistant.helpers.location import find_coordinates -from homeassistant.helpers.update_coordinator import DataUpdateCoordinator +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from homeassistant.util import dt from homeassistant.util.unit_conversion import DistanceConverter @@ -215,13 +215,15 @@ def prepare_parameters( def _from_entity_id(entity_id: str) -> list[str]: coordinates = find_coordinates(hass, entity_id) if coordinates is None: - raise InvalidCoordinatesException(f"No coordinates found for {entity_id}") + raise UpdateFailed(f"No coordinates found for {entity_id}") + if coordinates is entity_id: + raise UpdateFailed(f"Could not find entity {entity_id}") try: formatted_coordinates = coordinates.split(",") vol.Schema(cv.gps(formatted_coordinates)) except (AttributeError, vol.ExactSequenceInvalid) as ex: - raise InvalidCoordinatesException( - f"{coordinates} are not valid coordinates" + raise UpdateFailed( + f"{entity_id} does not have valid coordinates: {coordinates}" ) from ex return formatted_coordinates @@ -275,7 +277,3 @@ def next_datetime(simple_time: time) -> datetime: if combined < datetime.now(): combined = combined + timedelta(days=1) return combined - - -class InvalidCoordinatesException(Exception): - """Coordinates for origin or destination are malformed.""" diff --git a/homeassistant/components/here_travel_time/sensor.py b/homeassistant/components/here_travel_time/sensor.py index e432511c486..7e1c93bcfb5 100644 --- a/homeassistant/components/here_travel_time/sensor.py +++ b/homeassistant/components/here_travel_time/sensor.py @@ -25,7 +25,7 @@ from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.device_registry import DeviceEntryType from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.start import async_at_start +from homeassistant.helpers.start import async_at_started from homeassistant.helpers.update_coordinator import CoordinatorEntity from .const import ( @@ -134,7 +134,7 @@ class HERETravelTimeSensor(CoordinatorEntity, RestoreSensor): async def _update_at_start(_): await self.async_update() - self.async_on_remove(async_at_start(self.hass, _update_at_start)) + self.async_on_remove(async_at_started(self.hass, _update_at_start)) @callback def _handle_coordinator_update(self) -> None: diff --git a/tests/components/here_travel_time/test_sensor.py b/tests/components/here_travel_time/test_sensor.py index 34859956032..144ac063040 100644 --- a/tests/components/here_travel_time/test_sensor.py +++ b/tests/components/here_travel_time/test_sensor.py @@ -330,7 +330,7 @@ async def test_destination_entity_not_found(hass: HomeAssistant, caplog): hass.bus.async_fire(EVENT_HOMEASSISTANT_START) await hass.async_block_till_done() - assert "device_tracker.test are not valid coordinates" in caplog.text + assert "Could not find entity device_tracker.test" in caplog.text @pytest.mark.usefixtures("valid_response") @@ -356,7 +356,7 @@ async def test_origin_entity_not_found(hass: HomeAssistant, caplog): hass.bus.async_fire(EVENT_HOMEASSISTANT_START) await hass.async_block_till_done() - assert "device_tracker.test are not valid coordinates" in caplog.text + assert "Could not find entity device_tracker.test" in caplog.text @pytest.mark.usefixtures("valid_response") @@ -386,7 +386,9 @@ async def test_invalid_destination_entity_state(hass: HomeAssistant, caplog): hass.bus.async_fire(EVENT_HOMEASSISTANT_START) await hass.async_block_till_done() - assert "test_state are not valid coordinates" in caplog.text + assert ( + "device_tracker.test does not have valid coordinates: test_state" in caplog.text + ) @pytest.mark.usefixtures("valid_response") @@ -416,7 +418,9 @@ async def test_invalid_origin_entity_state(hass: HomeAssistant, caplog): hass.bus.async_fire(EVENT_HOMEASSISTANT_START) await hass.async_block_till_done() - assert "test_state are not valid coordinates" in caplog.text + assert ( + "device_tracker.test does not have valid coordinates: test_state" in caplog.text + ) async def test_route_not_found(hass: HomeAssistant, caplog): From 968a3c4eb227742eaa326188a01063cdb5dfc61b Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 3 Dec 2022 00:14:39 -1000 Subject: [PATCH 036/174] Bump pySwitchbot to 0.20.7 (#83170) changelog: https://github.com/Danielhiversen/pySwitchbot/compare/0.20.5...0.20.7 --- homeassistant/components/switchbot/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/switchbot/manifest.json b/homeassistant/components/switchbot/manifest.json index 274c5784b2f..87d62b09c39 100644 --- a/homeassistant/components/switchbot/manifest.json +++ b/homeassistant/components/switchbot/manifest.json @@ -2,7 +2,7 @@ "domain": "switchbot", "name": "SwitchBot", "documentation": "https://www.home-assistant.io/integrations/switchbot", - "requirements": ["PySwitchbot==0.20.5"], + "requirements": ["PySwitchbot==0.20.7"], "config_flow": true, "dependencies": ["bluetooth"], "codeowners": [ diff --git a/requirements_all.txt b/requirements_all.txt index e225bfdc68e..175de49b5c6 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -37,7 +37,7 @@ PyRMVtransport==0.3.3 PySocks==1.7.1 # homeassistant.components.switchbot -PySwitchbot==0.20.5 +PySwitchbot==0.20.7 # homeassistant.components.transport_nsw PyTransportNSW==0.1.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 00001f1c723..ac6220248c8 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -33,7 +33,7 @@ PyRMVtransport==0.3.3 PySocks==1.7.1 # homeassistant.components.switchbot -PySwitchbot==0.20.5 +PySwitchbot==0.20.7 # homeassistant.components.transport_nsw PyTransportNSW==0.1.1 From 31725df4d5eb7e9d1ba5f69866b51c76871236be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Hjelseth=20H=C3=B8yer?= Date: Sat, 3 Dec 2022 12:30:03 +0100 Subject: [PATCH 037/174] Update pyTibber to 0.26.3 (#83175) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update Tibber lib 0.26.3 Signed-off-by: Daniel Hjelseth Høyer Signed-off-by: Daniel Hjelseth Høyer --- homeassistant/components/tibber/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/tibber/manifest.json b/homeassistant/components/tibber/manifest.json index 892b47f39ca..81b3c1274d2 100644 --- a/homeassistant/components/tibber/manifest.json +++ b/homeassistant/components/tibber/manifest.json @@ -3,7 +3,7 @@ "domain": "tibber", "name": "Tibber", "documentation": "https://www.home-assistant.io/integrations/tibber", - "requirements": ["pyTibber==0.26.1"], + "requirements": ["pyTibber==0.26.3"], "codeowners": ["@danielhiversen"], "quality_scale": "silver", "config_flow": true, diff --git a/requirements_all.txt b/requirements_all.txt index 175de49b5c6..9e3df1f81a9 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1432,7 +1432,7 @@ pyRFXtrx==0.30.0 pySwitchmate==0.5.1 # homeassistant.components.tibber -pyTibber==0.26.1 +pyTibber==0.26.3 # homeassistant.components.dlink pyW215==0.7.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index ac6220248c8..18b77af31e1 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1032,7 +1032,7 @@ pyMetno==0.9.0 pyRFXtrx==0.30.0 # homeassistant.components.tibber -pyTibber==0.26.1 +pyTibber==0.26.3 # homeassistant.components.nextbus py_nextbusnext==0.1.5 From b015c5ab0c08ae952a3ae0bd8b54d8871bf81b93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Sat, 3 Dec 2022 16:19:16 +0100 Subject: [PATCH 038/174] Add CLOSED as an expected type (#83180) --- homeassistant/components/websocket_api/http.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/websocket_api/http.py b/homeassistant/components/websocket_api/http.py index 23c8fddd56c..acb5bf48131 100644 --- a/homeassistant/components/websocket_api/http.py +++ b/homeassistant/components/websocket_api/http.py @@ -214,7 +214,7 @@ class WebSocketHandler: disconnect_warn = "Did not receive auth message within 10 seconds" raise Disconnect from err - if msg.type in (WSMsgType.CLOSE, WSMsgType.CLOSING): + if msg.type in (WSMsgType.CLOSE, WSMsgType.CLOSED, WSMsgType.CLOSING): raise Disconnect if msg.type != WSMsgType.TEXT: @@ -238,7 +238,7 @@ class WebSocketHandler: while not wsock.closed: msg = await wsock.receive() - if msg.type in (WSMsgType.CLOSE, WSMsgType.CLOSING): + if msg.type in (WSMsgType.CLOSE, WSMsgType.CLOSED, WSMsgType.CLOSING): break if msg.type != WSMsgType.TEXT: From 1a2f23f110a6f8bab18ea470897c0cb091e1c137 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 3 Dec 2022 08:50:59 -1000 Subject: [PATCH 039/174] Fix missing services with esp32 proxies (#83192) --- .../components/bluetooth/manifest.json | 2 +- .../components/bluetooth/wrappers.py | 8 ++- .../components/esphome/bluetooth/client.py | 5 ++ .../components/esphome/entry_data.py | 8 +++ homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/bluetooth/__init__.py | 4 ++ tests/components/bluetooth/test_models.py | 57 +++++++++++++++++++ 9 files changed, 85 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/bluetooth/manifest.json b/homeassistant/components/bluetooth/manifest.json index 0ba5768d37c..4f5e1bd1b64 100644 --- a/homeassistant/components/bluetooth/manifest.json +++ b/homeassistant/components/bluetooth/manifest.json @@ -7,7 +7,7 @@ "quality_scale": "internal", "requirements": [ "bleak==0.19.2", - "bleak-retry-connector==2.8.9", + "bleak-retry-connector==2.9.0", "bluetooth-adapters==0.11.0", "bluetooth-auto-recovery==0.5.4", "bluetooth-data-tools==0.3.0", diff --git a/homeassistant/components/bluetooth/wrappers.py b/homeassistant/components/bluetooth/wrappers.py index beaa2acc78a..b1b06d43e31 100644 --- a/homeassistant/components/bluetooth/wrappers.py +++ b/homeassistant/components/bluetooth/wrappers.py @@ -12,7 +12,7 @@ from bleak import BleakClient, BleakError from bleak.backends.client import BaseBleakClient, get_platform_client_backend_type from bleak.backends.device import BLEDevice from bleak.backends.scanner import AdvertisementDataCallback, BaseBleakScanner -from bleak_retry_connector import NO_RSSI_VALUE, ble_device_description +from bleak_retry_connector import NO_RSSI_VALUE, ble_device_description, clear_cache from homeassistant.core import CALLBACK_TYPE, callback as hass_callback from homeassistant.helpers.frame import report @@ -169,6 +169,12 @@ class HaBleakClientWrapper(BleakClient): """Return True if the client is connected to a device.""" return self._backend is not None and self._backend.is_connected + async def clear_cache(self) -> bool: + """Clear the GATT cache.""" + if self._backend is not None and hasattr(self._backend, "clear_cache"): + return await self._backend.clear_cache() # type: ignore[no-any-return] + return await clear_cache(self.__address) + def set_disconnected_callback( self, callback: Callable[[BleakClient], None] | None, diff --git a/homeassistant/components/esphome/bluetooth/client.py b/homeassistant/components/esphome/bluetooth/client.py index 1ec3b585fae..d4200c24215 100644 --- a/homeassistant/components/esphome/bluetooth/client.py +++ b/homeassistant/components/esphome/bluetooth/client.py @@ -449,6 +449,11 @@ class ESPHomeClient(BaseBleakClient): raise BleakError(f"Characteristic {char_specifier} was not found!") return characteristic + async def clear_cache(self) -> None: + """Clear the GATT cache.""" + self.entry_data.clear_gatt_services_cache(self._address_as_int) + self.entry_data.clear_gatt_mtu_cache(self._address_as_int) + @verify_connected @api_error_as_bleak_error async def read_gatt_char( diff --git a/homeassistant/components/esphome/entry_data.py b/homeassistant/components/esphome/entry_data.py index 89377ba9a6a..2e05e01309e 100644 --- a/homeassistant/components/esphome/entry_data.py +++ b/homeassistant/components/esphome/entry_data.py @@ -119,6 +119,10 @@ class RuntimeEntryData: """Set the BleakGATTServiceCollection for the given address.""" self._gatt_services_cache[address] = services + def clear_gatt_services_cache(self, address: int) -> None: + """Clear the BleakGATTServiceCollection for the given address.""" + self._gatt_services_cache.pop(address, None) + def get_gatt_mtu_cache(self, address: int) -> int | None: """Get the mtu cache for the given address.""" return self._gatt_mtu_cache.get(address) @@ -127,6 +131,10 @@ class RuntimeEntryData: """Set the mtu cache for the given address.""" self._gatt_mtu_cache[address] = mtu + def clear_gatt_mtu_cache(self, address: int) -> None: + """Clear the mtu cache for the given address.""" + self._gatt_mtu_cache.pop(address, None) + @callback def async_update_ble_connection_limits(self, free: int, limit: int) -> None: """Update the BLE connection limits.""" diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index d281d0cedcd..caed4e23b53 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -10,7 +10,7 @@ atomicwrites-homeassistant==1.4.1 attrs==21.2.0 awesomeversion==22.9.0 bcrypt==3.1.7 -bleak-retry-connector==2.8.9 +bleak-retry-connector==2.9.0 bleak==0.19.2 bluetooth-adapters==0.11.0 bluetooth-auto-recovery==0.5.4 diff --git a/requirements_all.txt b/requirements_all.txt index 9e3df1f81a9..0d3d27ede0f 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -422,7 +422,7 @@ bimmer_connected==0.10.4 bizkaibus==0.1.1 # homeassistant.components.bluetooth -bleak-retry-connector==2.8.9 +bleak-retry-connector==2.9.0 # homeassistant.components.bluetooth bleak==0.19.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 18b77af31e1..20a42af4b3d 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -346,7 +346,7 @@ bellows==0.34.5 bimmer_connected==0.10.4 # homeassistant.components.bluetooth -bleak-retry-connector==2.8.9 +bleak-retry-connector==2.9.0 # homeassistant.components.bluetooth bleak==0.19.2 diff --git a/tests/components/bluetooth/__init__.py b/tests/components/bluetooth/__init__.py index e4dd2a8bb57..e36d1d4b644 100644 --- a/tests/components/bluetooth/__init__.py +++ b/tests/components/bluetooth/__init__.py @@ -219,3 +219,7 @@ class MockBleakClient(BleakClient): async def get_services(self, *args, **kwargs): """Mock get_services.""" return [] + + async def clear_cache(self, *args, **kwargs): + """Mock clear_cache.""" + return True diff --git a/tests/components/bluetooth/test_models.py b/tests/components/bluetooth/test_models.py index 612f33a68bd..e200450f656 100644 --- a/tests/components/bluetooth/test_models.py +++ b/tests/components/bluetooth/test_models.py @@ -45,6 +45,7 @@ async def test_wrapped_bleak_client_raises_device_missing(hass, enable_bluetooth await client.connect() assert client.is_connected is False await client.disconnect() + assert await client.clear_cache() is False async def test_wrapped_bleak_client_set_disconnected_callback_before_connected( @@ -168,6 +169,62 @@ async def test_ble_device_with_proxy_client_out_of_connections( await client.disconnect() +async def test_ble_device_with_proxy_clear_cache(hass, enable_bluetooth, one_adapter): + """Test we can clear cache on the proxy.""" + manager = _get_manager() + + switchbot_proxy_device_with_connection_slot = BLEDevice( + "44:44:33:11:23:45", + "wohand", + { + "connector": HaBluetoothConnector( + MockBleakClient, "mock_bleak_client", lambda: True + ), + "path": "/org/bluez/hci0/dev_44_44_33_11_23_45", + }, + rssi=-30, + ) + switchbot_adv = generate_advertisement_data( + local_name="wohand", service_uuids=[], manufacturer_data={1: b"\x01"} + ) + + class FakeScanner(BaseHaScanner): + @property + def discovered_devices_and_advertisement_data( + self, + ) -> dict[str, tuple[BLEDevice, AdvertisementData]]: + """Return a list of discovered devices.""" + return { + switchbot_proxy_device_with_connection_slot.address: ( + switchbot_proxy_device_with_connection_slot, + switchbot_adv, + ) + } + + async def async_get_device_by_address(self, address: str) -> BLEDevice | None: + """Return a list of discovered devices.""" + if address == switchbot_proxy_device_with_connection_slot.address: + return switchbot_adv + return None + + scanner = FakeScanner(hass, "esp32", "esp32") + cancel = manager.async_register_scanner(scanner, True) + inject_advertisement_with_source( + hass, switchbot_proxy_device_with_connection_slot, switchbot_adv, "esp32" + ) + + assert manager.async_discovered_devices(True) == [ + switchbot_proxy_device_with_connection_slot + ] + + client = HaBleakClientWrapper(switchbot_proxy_device_with_connection_slot) + await client.connect() + assert client.is_connected is True + assert await client.clear_cache() is True + await client.disconnect() + cancel() + + async def test_ble_device_with_proxy_client_out_of_connections_uses_best_available( hass, enable_bluetooth, one_adapter ): From 5c918e9053365fcb16ea8476b19deb6e0b77d4c6 Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Sat, 3 Dec 2022 11:02:25 -0800 Subject: [PATCH 040/174] Bump ical to 4.1.2 to fix emoji in local calendar (#83193) Bump ical to 4.1.2 --- homeassistant/components/local_calendar/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/local_calendar/manifest.json b/homeassistant/components/local_calendar/manifest.json index 016d2fee052..037c9ff67bd 100644 --- a/homeassistant/components/local_calendar/manifest.json +++ b/homeassistant/components/local_calendar/manifest.json @@ -3,7 +3,7 @@ "name": "Local Calendar", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/local_calendar", - "requirements": ["ical==4.1.1"], + "requirements": ["ical==4.1.2"], "codeowners": ["@allenporter"], "iot_class": "local_polling", "loggers": ["ical"] diff --git a/requirements_all.txt b/requirements_all.txt index 0d3d27ede0f..42d9704cbc8 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -926,7 +926,7 @@ ibm-watson==5.2.2 ibmiotf==0.3.4 # homeassistant.components.local_calendar -ical==4.1.1 +ical==4.1.2 # homeassistant.components.ping icmplib==3.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 20a42af4b3d..90c63ecd7e3 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -691,7 +691,7 @@ iaqualink==0.5.0 ibeacon_ble==1.0.1 # homeassistant.components.local_calendar -ical==4.1.1 +ical==4.1.2 # homeassistant.components.ping icmplib==3.0 From 284c226cc05dbec80f4d353a0935f0f50e139a63 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 3 Dec 2022 09:34:11 -1000 Subject: [PATCH 041/174] Bump aiohomekit to 2.3.6 (#83196) --- homeassistant/components/homekit_controller/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/homekit_controller/manifest.json b/homeassistant/components/homekit_controller/manifest.json index d67eeac1fd3..4ac8578eff5 100644 --- a/homeassistant/components/homekit_controller/manifest.json +++ b/homeassistant/components/homekit_controller/manifest.json @@ -3,7 +3,7 @@ "name": "HomeKit Controller", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/homekit_controller", - "requirements": ["aiohomekit==2.3.5"], + "requirements": ["aiohomekit==2.3.6"], "zeroconf": ["_hap._tcp.local.", "_hap._udp.local."], "bluetooth": [{ "manufacturer_id": 76, "manufacturer_data_start": [6] }], "dependencies": ["bluetooth", "zeroconf"], diff --git a/requirements_all.txt b/requirements_all.txt index 42d9704cbc8..a1ef6f4dfe6 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -174,7 +174,7 @@ aioguardian==2022.07.0 aioharmony==0.2.9 # homeassistant.components.homekit_controller -aiohomekit==2.3.5 +aiohomekit==2.3.6 # homeassistant.components.emulated_hue # homeassistant.components.http diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 90c63ecd7e3..229f7d8e0d5 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -158,7 +158,7 @@ aioguardian==2022.07.0 aioharmony==0.2.9 # homeassistant.components.homekit_controller -aiohomekit==2.3.5 +aiohomekit==2.3.6 # homeassistant.components.emulated_hue # homeassistant.components.http From c63bb0e1d50675c9a3cfcb12c4249b19dc50edfe Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 3 Dec 2022 09:21:19 -1000 Subject: [PATCH 042/174] Bump pySwitchbot to 0.20.8 (#83197) fixes https://github.com/home-assistant/core/issues/80491 --- homeassistant/components/switchbot/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/switchbot/manifest.json b/homeassistant/components/switchbot/manifest.json index 87d62b09c39..c96b2884d3b 100644 --- a/homeassistant/components/switchbot/manifest.json +++ b/homeassistant/components/switchbot/manifest.json @@ -2,7 +2,7 @@ "domain": "switchbot", "name": "SwitchBot", "documentation": "https://www.home-assistant.io/integrations/switchbot", - "requirements": ["PySwitchbot==0.20.7"], + "requirements": ["PySwitchbot==0.20.8"], "config_flow": true, "dependencies": ["bluetooth"], "codeowners": [ diff --git a/requirements_all.txt b/requirements_all.txt index a1ef6f4dfe6..4280a19a54f 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -37,7 +37,7 @@ PyRMVtransport==0.3.3 PySocks==1.7.1 # homeassistant.components.switchbot -PySwitchbot==0.20.7 +PySwitchbot==0.20.8 # homeassistant.components.transport_nsw PyTransportNSW==0.1.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 229f7d8e0d5..41a05e3acb4 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -33,7 +33,7 @@ PyRMVtransport==0.3.3 PySocks==1.7.1 # homeassistant.components.switchbot -PySwitchbot==0.20.7 +PySwitchbot==0.20.8 # homeassistant.components.transport_nsw PyTransportNSW==0.1.1 From f19322b1f38ad2950960e74b4f7126cd4de5b7fd Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 3 Dec 2022 09:55:53 -1000 Subject: [PATCH 043/174] Bump yalexs-ble to 1.10.2 (#83199) --- homeassistant/components/august/manifest.json | 2 +- homeassistant/components/yalexs_ble/manifest.json | 2 +- requirements_all.txt | 4 ++-- requirements_test_all.txt | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/august/manifest.json b/homeassistant/components/august/manifest.json index 5470f03eb4c..e8f8736b522 100644 --- a/homeassistant/components/august/manifest.json +++ b/homeassistant/components/august/manifest.json @@ -2,7 +2,7 @@ "domain": "august", "name": "August", "documentation": "https://www.home-assistant.io/integrations/august", - "requirements": ["yalexs==1.2.6", "yalexs_ble==1.10.0"], + "requirements": ["yalexs==1.2.6", "yalexs_ble==1.10.2"], "codeowners": ["@bdraco"], "dhcp": [ { diff --git a/homeassistant/components/yalexs_ble/manifest.json b/homeassistant/components/yalexs_ble/manifest.json index b54dda37548..ce86f7f7ea1 100644 --- a/homeassistant/components/yalexs_ble/manifest.json +++ b/homeassistant/components/yalexs_ble/manifest.json @@ -3,7 +3,7 @@ "name": "Yale Access Bluetooth", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/yalexs_ble", - "requirements": ["yalexs-ble==1.10.0"], + "requirements": ["yalexs-ble==1.10.2"], "dependencies": ["bluetooth"], "codeowners": ["@bdraco"], "bluetooth": [ diff --git a/requirements_all.txt b/requirements_all.txt index 4280a19a54f..aa92eb6d87c 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2606,13 +2606,13 @@ xs1-api-client==3.0.0 yalesmartalarmclient==0.3.9 # homeassistant.components.yalexs_ble -yalexs-ble==1.10.0 +yalexs-ble==1.10.2 # homeassistant.components.august yalexs==1.2.6 # homeassistant.components.august -yalexs_ble==1.10.0 +yalexs_ble==1.10.2 # homeassistant.components.yeelight yeelight==0.7.10 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 41a05e3acb4..9bf0e586502 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1816,13 +1816,13 @@ xmltodict==0.13.0 yalesmartalarmclient==0.3.9 # homeassistant.components.yalexs_ble -yalexs-ble==1.10.0 +yalexs-ble==1.10.2 # homeassistant.components.august yalexs==1.2.6 # homeassistant.components.august -yalexs_ble==1.10.0 +yalexs_ble==1.10.2 # homeassistant.components.yeelight yeelight==0.7.10 From ccd5783daf96944d582333ff23e5e5f29b07d19b Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Sat, 3 Dec 2022 12:20:53 -0700 Subject: [PATCH 044/174] Fix SimpliSafe service calls that require a device selector (#83200) fixes undefined --- homeassistant/components/simplisafe/__init__.py | 3 ++- homeassistant/components/simplisafe/services.yaml | 9 ++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/simplisafe/__init__.py b/homeassistant/components/simplisafe/__init__.py index 27cee778655..7b431368328 100644 --- a/homeassistant/components/simplisafe/__init__.py +++ b/homeassistant/components/simplisafe/__init__.py @@ -237,11 +237,12 @@ def _async_get_system_for_service_call( ) is None: raise ValueError("No base station registered for alarm control panel") - [system_id] = [ + [system_id_str] = [ identity[1] for identity in base_station_device_entry.identifiers if identity[0] == DOMAIN ] + system_id = int(system_id_str) for entry_id in base_station_device_entry.config_entries: if (simplisafe := hass.data[DOMAIN].get(entry_id)) is None: diff --git a/homeassistant/components/simplisafe/services.yaml b/homeassistant/components/simplisafe/services.yaml index 6f9cedc77cb..8aeefcf7846 100644 --- a/homeassistant/components/simplisafe/services.yaml +++ b/homeassistant/components/simplisafe/services.yaml @@ -10,7 +10,8 @@ remove_pin: selector: device: integration: simplisafe - model: alarm_control_panel + entity: + domain: alarm_control_panel label_or_pin: name: Label/PIN description: The label/value to remove. @@ -29,7 +30,8 @@ set_pin: selector: device: integration: simplisafe - model: alarm_control_panel + entity: + domain: alarm_control_panel label: name: Label description: The label of the PIN @@ -55,7 +57,8 @@ set_system_properties: selector: device: integration: simplisafe - model: alarm_control_panel + entity: + domain: alarm_control_panel alarm_duration: name: Alarm duration description: The length of a triggered alarm From 7f5ae00d58490e656996fdce8087e991168412ab Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sat, 3 Dec 2022 14:56:41 -0500 Subject: [PATCH 045/174] Bumped version to 2022.12.0b3 --- homeassistant/const.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 333b3076dfc..bdd8b4640ca 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -8,7 +8,7 @@ from .backports.enum import StrEnum APPLICATION_NAME: Final = "HomeAssistant" MAJOR_VERSION: Final = 2022 MINOR_VERSION: Final = 12 -PATCH_VERSION: Final = "0b2" +PATCH_VERSION: Final = "0b3" __short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__: Final = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 9, 0) diff --git a/pyproject.toml b/pyproject.toml index 441c4e95e91..239052d5285 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "homeassistant" -version = "2022.12.0b2" +version = "2022.12.0b3" license = {text = "Apache-2.0"} description = "Open-source home automation platform running on Python 3." readme = "README.rst" From f396d663861a38b3ee25a7bbe9d0f088ed0b4866 Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Sat, 3 Dec 2022 21:26:21 +0000 Subject: [PATCH 046/174] Update aiolyric to 1.0.9 (#83190) --- homeassistant/components/lyric/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/lyric/manifest.json b/homeassistant/components/lyric/manifest.json index c0d9168f46f..6101101bf70 100644 --- a/homeassistant/components/lyric/manifest.json +++ b/homeassistant/components/lyric/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/lyric", "dependencies": ["application_credentials"], - "requirements": ["aiolyric==1.0.8"], + "requirements": ["aiolyric==1.0.9"], "codeowners": ["@timmo001"], "quality_scale": "silver", "dhcp": [ diff --git a/requirements_all.txt b/requirements_all.txt index aa92eb6d87c..9f0688b3a13 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -208,7 +208,7 @@ aiolivisi==0.0.14 aiolookin==0.1.1 # homeassistant.components.lyric -aiolyric==1.0.8 +aiolyric==1.0.9 # homeassistant.components.modern_forms aiomodernforms==0.1.8 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 9bf0e586502..85db3a8434c 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -186,7 +186,7 @@ aiolivisi==0.0.14 aiolookin==0.1.1 # homeassistant.components.lyric -aiolyric==1.0.8 +aiolyric==1.0.9 # homeassistant.components.modern_forms aiomodernforms==0.1.8 From fe170ccd440f27cba79871b2b25cae3b20078ade Mon Sep 17 00:00:00 2001 From: Olen Date: Sat, 3 Dec 2022 22:43:10 +0100 Subject: [PATCH 047/174] Switch Twinkly to awesomeversion (#83205) Switch to awesomeversion --- homeassistant/components/twinkly/light.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/twinkly/light.py b/homeassistant/components/twinkly/light.py index c7cf13b6563..3174f60edf8 100644 --- a/homeassistant/components/twinkly/light.py +++ b/homeassistant/components/twinkly/light.py @@ -7,7 +7,7 @@ import logging from typing import Any from aiohttp import ClientError -from packaging import version +from awesomeversion import AwesomeVersion from ttls.client import Twinkly from homeassistant.components.light import ( @@ -173,7 +173,7 @@ class TwinklyLight(LightEntity): if ATTR_VERSION in software_version: self._software_version = software_version[ATTR_VERSION] - if version.parse(self._software_version) < version.parse( + if AwesomeVersion(self._software_version) < AwesomeVersion( MIN_EFFECT_VERSION ): self._attr_supported_features = ( From be94e67ecc4d0761934f3b19d45858821548172b Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Dec 2022 07:01:37 -1000 Subject: [PATCH 048/174] Restore HomeKit Controller BLE GSN at startup (#83206) --- .../components/homekit_controller/button.py | 2 +- .../homekit_controller/config_flow.py | 1 + .../homekit_controller/connection.py | 70 +++++++++++-------- .../homekit_controller/manifest.json | 2 +- .../components/homekit_controller/number.py | 2 +- .../components/homekit_controller/storage.py | 6 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 8 files changed, 50 insertions(+), 37 deletions(-) diff --git a/homeassistant/components/homekit_controller/button.py b/homeassistant/components/homekit_controller/button.py index 4ce2b425a5e..11e935df455 100644 --- a/homeassistant/components/homekit_controller/button.py +++ b/homeassistant/components/homekit_controller/button.py @@ -84,7 +84,7 @@ async def async_setup_entry( entity.old_unique_id, entity.unique_id, Platform.BUTTON ) - async_add_entities(entities, True) + async_add_entities(entities) return True conn.add_char_factory(async_add_characteristic) diff --git a/homeassistant/components/homekit_controller/config_flow.py b/homeassistant/components/homekit_controller/config_flow.py index 10d3ed0cfa8..1cfedb05847 100644 --- a/homeassistant/components/homekit_controller/config_flow.py +++ b/homeassistant/components/homekit_controller/config_flow.py @@ -583,6 +583,7 @@ class HomekitControllerFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): accessories_state.config_num, accessories_state.accessories.serialize(), serialize_broadcast_key(accessories_state.broadcast_key), + accessories_state.state_num, ) return self.async_create_entry(title=name, data=pairing_data) diff --git a/homeassistant/components/homekit_controller/connection.py b/homeassistant/components/homekit_controller/connection.py index dbcd8b28fc2..d230fe64517 100644 --- a/homeassistant/components/homekit_controller/connection.py +++ b/homeassistant/components/homekit_controller/connection.py @@ -20,7 +20,7 @@ from aiohomekit.model.services import Service from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_VIA_DEVICE, EVENT_HOMEASSISTANT_STARTED -from homeassistant.core import CALLBACK_TYPE, CoreState, Event, HomeAssistant, callback +from homeassistant.core import CoreState, Event, HomeAssistant, callback from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.helpers.debounce import Debouncer from homeassistant.helpers.dispatcher import async_dispatcher_send @@ -116,11 +116,6 @@ class HKDevice: self.pollable_characteristics: list[tuple[int, int]] = [] - # If this is set polling is active and can be disabled by calling - # this method. - self._polling_interval_remover: CALLBACK_TYPE | None = None - self._ble_available_interval_remover: CALLBACK_TYPE | None = None - # Never allow concurrent polling of the same accessory or bridge self._polling_lock = asyncio.Lock() self._polling_lock_warned = False @@ -185,8 +180,8 @@ class HKDevice: self.available = available async_dispatcher_send(self.hass, self.signal_state_updated) - async def _async_retry_populate_ble_accessory_state(self, event: Event) -> None: - """Try again to populate the BLE accessory state. + async def _async_populate_ble_accessory_state(self, event: Event) -> None: + """Populate the BLE accessory state without blocking startup. If the accessory was asleep at startup we need to retry since we continued on to allow startup to proceed. @@ -194,6 +189,7 @@ class HKDevice: If this fails the state may be inconsistent, but will get corrected as soon as the accessory advertises again. """ + self._async_start_polling() try: await self.pairing.async_populate_accessories_state(force_update=True) except STARTUP_EXCEPTIONS as ex: @@ -221,20 +217,28 @@ class HKDevice: # so we only poll those chars but that is not possible # yet. attempts = None if self.hass.state == CoreState.running else 1 - try: - await self.pairing.async_populate_accessories_state( - force_update=True, attempts=attempts - ) - except AccessoryNotFoundError: - if transport != Transport.BLE or not pairing.accessories: - # BLE devices may sleep and we can't force a connection - raise + if ( + transport == Transport.BLE + and pairing.accessories + and pairing.accessories.has_aid(1) + ): + # The GSN gets restored and a catch up poll will be + # triggered via disconnected events automatically + # if we are out of sync. To be sure we are in sync; + # If for some reason the BLE connection failed + # previously we force an update after startup + # is complete. entry.async_on_unload( self.hass.bus.async_listen( EVENT_HOMEASSISTANT_STARTED, - self._async_retry_populate_ble_accessory_state, + self._async_populate_ble_accessory_state, ) ) + else: + await self.pairing.async_populate_accessories_state( + force_update=True, attempts=attempts + ) + self._async_start_polling() entry.async_on_unload(pairing.dispatcher_connect(self.process_new_events)) entry.async_on_unload( @@ -252,27 +256,34 @@ class HKDevice: self.async_set_available_state(self.pairing.is_available) - # We use async_request_update to avoid multiple updates - # at the same time which would generate a spurious warning - # in the log about concurrent polling. - self._polling_interval_remover = async_track_time_interval( - self.hass, self.async_request_update, self.pairing.poll_interval - ) - if transport == Transport.BLE: # If we are using BLE, we need to periodically check of the # BLE device is available since we won't get callbacks # when it goes away since we HomeKit supports disconnected # notifications and we cannot treat a disconnect as unavailability. - self._ble_available_interval_remover = async_track_time_interval( - self.hass, - self.async_update_available_state, - timedelta(seconds=BLE_AVAILABILITY_CHECK_INTERVAL), + entry.async_on_unload( + async_track_time_interval( + self.hass, + self.async_update_available_state, + timedelta(seconds=BLE_AVAILABILITY_CHECK_INTERVAL), + ) ) # BLE devices always get an RSSI sensor as well if "sensor" not in self.platforms: await self.async_load_platform("sensor") + @callback + def _async_start_polling(self) -> None: + """Start polling for updates.""" + # We use async_request_update to avoid multiple updates + # at the same time which would generate a spurious warning + # in the log about concurrent polling. + self.config_entry.async_on_unload( + async_track_time_interval( + self.hass, self.async_request_update, self.pairing.poll_interval + ) + ) + async def async_add_new_entities(self) -> None: """Add new entities to Home Assistant.""" await self.async_load_platforms() @@ -529,9 +540,6 @@ class HKDevice: async def async_unload(self) -> None: """Stop interacting with device and prepare for removal from hass.""" - if self._polling_interval_remover: - self._polling_interval_remover() - await self.pairing.shutdown() await self.hass.config_entries.async_unload_platforms( diff --git a/homeassistant/components/homekit_controller/manifest.json b/homeassistant/components/homekit_controller/manifest.json index 4ac8578eff5..c5047f9d215 100644 --- a/homeassistant/components/homekit_controller/manifest.json +++ b/homeassistant/components/homekit_controller/manifest.json @@ -3,7 +3,7 @@ "name": "HomeKit Controller", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/homekit_controller", - "requirements": ["aiohomekit==2.3.6"], + "requirements": ["aiohomekit==2.4.0"], "zeroconf": ["_hap._tcp.local.", "_hap._udp.local."], "bluetooth": [{ "manufacturer_id": 76, "manufacturer_data_start": [6] }], "dependencies": ["bluetooth", "zeroconf"], diff --git a/homeassistant/components/homekit_controller/number.py b/homeassistant/components/homekit_controller/number.py index a20ba83e80a..5d72516bc06 100644 --- a/homeassistant/components/homekit_controller/number.py +++ b/homeassistant/components/homekit_controller/number.py @@ -78,7 +78,7 @@ async def async_setup_entry( entity.old_unique_id, entity.unique_id, Platform.NUMBER ) - async_add_entities(entities, True) + async_add_entities(entities) return True conn.add_char_factory(async_add_characteristic) diff --git a/homeassistant/components/homekit_controller/storage.py b/homeassistant/components/homekit_controller/storage.py index a5afb07620a..de4f23ad8da 100644 --- a/homeassistant/components/homekit_controller/storage.py +++ b/homeassistant/components/homekit_controller/storage.py @@ -61,11 +61,15 @@ class EntityMapStorage: config_num: int, accessories: list[Any], broadcast_key: str | None = None, + state_num: int | None = None, ) -> Pairing: """Create a new pairing cache.""" _LOGGER.debug("Creating or updating entity map for %s", homekit_id) data = Pairing( - config_num=config_num, accessories=accessories, broadcast_key=broadcast_key + config_num=config_num, + accessories=accessories, + broadcast_key=broadcast_key, + state_num=state_num, ) self.storage_data[homekit_id] = data self._async_schedule_save() diff --git a/requirements_all.txt b/requirements_all.txt index 9f0688b3a13..a2ce759123a 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -174,7 +174,7 @@ aioguardian==2022.07.0 aioharmony==0.2.9 # homeassistant.components.homekit_controller -aiohomekit==2.3.6 +aiohomekit==2.4.0 # homeassistant.components.emulated_hue # homeassistant.components.http diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 85db3a8434c..389a1521d59 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -158,7 +158,7 @@ aioguardian==2022.07.0 aioharmony==0.2.9 # homeassistant.components.homekit_controller -aiohomekit==2.3.6 +aiohomekit==2.4.0 # homeassistant.components.emulated_hue # homeassistant.components.http From d32c32608b6095699ff1417956ef49a513357acb Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Dec 2022 09:05:46 -1000 Subject: [PATCH 049/174] Raise an exception when an esp proxy gets empty services during connection so callers know to retry (#83211) --- .../components/esphome/bluetooth/client.py | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/esphome/bluetooth/client.py b/homeassistant/components/esphome/bluetooth/client.py index d4200c24215..541eb831ca5 100644 --- a/homeassistant/components/esphome/bluetooth/client.py +++ b/homeassistant/components/esphome/bluetooth/client.py @@ -317,7 +317,20 @@ class ESPHomeClient(BaseBleakClient): connected_future.cancel() raise await connected_future - await self.get_services(dangerous_use_bleak_cache=dangerous_use_bleak_cache) + + try: + await self.get_services(dangerous_use_bleak_cache=dangerous_use_bleak_cache) + except asyncio.CancelledError: + # On cancel we must still raise cancelled error + # to avoid blocking the cancellation even if the + # disconnect call fails. + with contextlib.suppress(Exception): + await self.disconnect() + raise + except Exception: + await self.disconnect() + raise + self._disconnected_event = asyncio.Event() return True @@ -427,6 +440,12 @@ class ESPHomeClient(BaseBleakClient): characteristic.handle, ) ) + + if not esphome_services.services: + # If we got no services, we must have disconnected + # or something went wrong on the ESP32's BLE stack. + raise BleakError("Failed to get services from remote esp") + self.services = services _LOGGER.debug( "%s: %s - %s: Cached services saved", From d14324c7922d0d46b7447c55ac37387dcf2bdf92 Mon Sep 17 00:00:00 2001 From: Michael Chisholm Date: Mon, 5 Dec 2022 05:05:53 +1100 Subject: [PATCH 050/174] Update async-upnp-client to 0.32.3 (#83215) --- homeassistant/components/dlna_dmr/manifest.json | 2 +- homeassistant/components/dlna_dms/manifest.json | 2 +- homeassistant/components/samsungtv/manifest.json | 2 +- homeassistant/components/ssdp/manifest.json | 2 +- homeassistant/components/upnp/manifest.json | 2 +- homeassistant/components/yeelight/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/dlna_dmr/manifest.json b/homeassistant/components/dlna_dmr/manifest.json index e4620386b98..3da3de8434f 100644 --- a/homeassistant/components/dlna_dmr/manifest.json +++ b/homeassistant/components/dlna_dmr/manifest.json @@ -3,7 +3,7 @@ "name": "DLNA Digital Media Renderer", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/dlna_dmr", - "requirements": ["async-upnp-client==0.32.2"], + "requirements": ["async-upnp-client==0.32.3"], "dependencies": ["ssdp"], "after_dependencies": ["media_source"], "ssdp": [ diff --git a/homeassistant/components/dlna_dms/manifest.json b/homeassistant/components/dlna_dms/manifest.json index 7c7a312159b..3630e5ce309 100644 --- a/homeassistant/components/dlna_dms/manifest.json +++ b/homeassistant/components/dlna_dms/manifest.json @@ -3,7 +3,7 @@ "name": "DLNA Digital Media Server", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/dlna_dms", - "requirements": ["async-upnp-client==0.32.2"], + "requirements": ["async-upnp-client==0.32.3"], "dependencies": ["ssdp"], "after_dependencies": ["media_source"], "ssdp": [ diff --git a/homeassistant/components/samsungtv/manifest.json b/homeassistant/components/samsungtv/manifest.json index 0a65a47bd23..8a922fbbaf0 100644 --- a/homeassistant/components/samsungtv/manifest.json +++ b/homeassistant/components/samsungtv/manifest.json @@ -8,7 +8,7 @@ "samsungctl[websocket]==0.7.1", "samsungtvws[async,encrypted]==2.5.0", "wakeonlan==2.1.0", - "async-upnp-client==0.32.2" + "async-upnp-client==0.32.3" ], "ssdp": [ { diff --git a/homeassistant/components/ssdp/manifest.json b/homeassistant/components/ssdp/manifest.json index 3b30146e756..d532dc8f292 100644 --- a/homeassistant/components/ssdp/manifest.json +++ b/homeassistant/components/ssdp/manifest.json @@ -2,7 +2,7 @@ "domain": "ssdp", "name": "Simple Service Discovery Protocol (SSDP)", "documentation": "https://www.home-assistant.io/integrations/ssdp", - "requirements": ["async-upnp-client==0.32.2"], + "requirements": ["async-upnp-client==0.32.3"], "dependencies": ["network"], "after_dependencies": ["zeroconf"], "codeowners": [], diff --git a/homeassistant/components/upnp/manifest.json b/homeassistant/components/upnp/manifest.json index 4c45b099193..214f5ce2931 100644 --- a/homeassistant/components/upnp/manifest.json +++ b/homeassistant/components/upnp/manifest.json @@ -3,7 +3,7 @@ "name": "UPnP/IGD", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/upnp", - "requirements": ["async-upnp-client==0.32.2", "getmac==0.8.2"], + "requirements": ["async-upnp-client==0.32.3", "getmac==0.8.2"], "dependencies": ["network", "ssdp"], "codeowners": ["@StevenLooman"], "ssdp": [ diff --git a/homeassistant/components/yeelight/manifest.json b/homeassistant/components/yeelight/manifest.json index 6c450548135..f3d9d8b24ca 100644 --- a/homeassistant/components/yeelight/manifest.json +++ b/homeassistant/components/yeelight/manifest.json @@ -2,7 +2,7 @@ "domain": "yeelight", "name": "Yeelight", "documentation": "https://www.home-assistant.io/integrations/yeelight", - "requirements": ["yeelight==0.7.10", "async-upnp-client==0.32.2"], + "requirements": ["yeelight==0.7.10", "async-upnp-client==0.32.3"], "codeowners": ["@zewelor", "@shenxn", "@starkillerOG", "@alexyao2015"], "config_flow": true, "dependencies": ["network"], diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index caed4e23b53..e42e7e2b9aa 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -4,7 +4,7 @@ aiodiscover==1.4.13 aiohttp==3.8.3 aiohttp_cors==0.7.0 astral==2.2 -async-upnp-client==0.32.2 +async-upnp-client==0.32.3 async_timeout==4.0.2 atomicwrites-homeassistant==1.4.1 attrs==21.2.0 diff --git a/requirements_all.txt b/requirements_all.txt index a2ce759123a..a60b3b74aca 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -362,7 +362,7 @@ asterisk_mbox==0.5.0 # homeassistant.components.ssdp # homeassistant.components.upnp # homeassistant.components.yeelight -async-upnp-client==0.32.2 +async-upnp-client==0.32.3 # homeassistant.components.supla asyncpysupla==0.0.5 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 389a1521d59..1215dcbf604 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -316,7 +316,7 @@ arcam-fmj==1.0.1 # homeassistant.components.ssdp # homeassistant.components.upnp # homeassistant.components.yeelight -async-upnp-client==0.32.2 +async-upnp-client==0.32.3 # homeassistant.components.sleepiq asyncsleepiq==1.2.3 From eb0450ad0c8289f3982a9e028c288bedc08c037d Mon Sep 17 00:00:00 2001 From: Joe Rogers <1337joe@users.noreply.github.com> Date: Sun, 4 Dec 2022 15:52:48 -0500 Subject: [PATCH 051/174] Handle numeric versions in mqtt update (#83218) * Handle numeric versions in mqtt update * Remove need for type:ignore Co-authored-by: Jan Bouwhuis Co-authored-by: Jan Bouwhuis --- homeassistant/components/mqtt/update.py | 20 ++++-- tests/components/mqtt/test_update.py | 94 +++++++++++++++++++++++++ 2 files changed, 108 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/mqtt/update.py b/homeassistant/components/mqtt/update.py index 0875778059f..874f6024a37 100644 --- a/homeassistant/components/mqtt/update.py +++ b/homeassistant/components/mqtt/update.py @@ -172,14 +172,22 @@ class MqttUpdate(MqttEntity, UpdateEntity, RestoreEntity): ) return - json_payload = {} + json_payload: Any | dict = {} try: json_payload = json_loads(payload) - _LOGGER.debug( - "JSON payload detected after processing payload '%s' on topic %s", - json_payload, - msg.topic, - ) + if isinstance(json_payload, dict): + _LOGGER.debug( + "JSON payload detected after processing payload '%s' on topic %s", + json_payload, + msg.topic, + ) + else: + _LOGGER.debug( + "Non-dictionary JSON payload detected after processing payload '%s' on topic %s", + payload, + msg.topic, + ) + json_payload = {"installed_version": payload} except JSON_DECODE_EXCEPTIONS: _LOGGER.debug( "No valid (JSON) payload detected after processing payload '%s' on topic %s", diff --git a/tests/components/mqtt/test_update.py b/tests/components/mqtt/test_update.py index 5746f2ac471..a633b99fb1a 100644 --- a/tests/components/mqtt/test_update.py +++ b/tests/components/mqtt/test_update.py @@ -110,6 +110,54 @@ async def test_run_update_setup(hass, mqtt_mock_entry_with_yaml_config): assert state.attributes.get("latest_version") == "2.0.0" +async def test_run_update_setup_float(hass, mqtt_mock_entry_with_yaml_config): + """Test that it fetches the given payload when the version is parsable as a number.""" + installed_version_topic = "test/installed-version" + latest_version_topic = "test/latest-version" + await async_setup_component( + hass, + mqtt.DOMAIN, + { + mqtt.DOMAIN: { + update.DOMAIN: { + "state_topic": installed_version_topic, + "latest_version_topic": latest_version_topic, + "name": "Test Update", + "release_summary": "Test release summary", + "release_url": "https://example.com/release", + "title": "Test Update Title", + "entity_picture": "https://example.com/icon.png", + } + } + }, + ) + await hass.async_block_till_done() + await mqtt_mock_entry_with_yaml_config() + + async_fire_mqtt_message(hass, installed_version_topic, "1.9") + async_fire_mqtt_message(hass, latest_version_topic, "1.9") + + await hass.async_block_till_done() + + state = hass.states.get("update.test_update") + assert state.state == STATE_OFF + assert state.attributes.get("installed_version") == "1.9" + assert state.attributes.get("latest_version") == "1.9" + assert state.attributes.get("release_summary") == "Test release summary" + assert state.attributes.get("release_url") == "https://example.com/release" + assert state.attributes.get("title") == "Test Update Title" + assert state.attributes.get("entity_picture") == "https://example.com/icon.png" + + async_fire_mqtt_message(hass, latest_version_topic, "2.0") + + await hass.async_block_till_done() + + state = hass.states.get("update.test_update") + assert state.state == STATE_ON + assert state.attributes.get("installed_version") == "1.9" + assert state.attributes.get("latest_version") == "2.0" + + async def test_value_template(hass, mqtt_mock_entry_with_yaml_config): """Test that it fetches the given payload with a template.""" installed_version_topic = "test/installed-version" @@ -156,6 +204,52 @@ async def test_value_template(hass, mqtt_mock_entry_with_yaml_config): assert state.attributes.get("latest_version") == "2.0.0" +async def test_value_template_float(hass, mqtt_mock_entry_with_yaml_config): + """Test that it fetches the given payload with a template when the version is parsable as a number.""" + installed_version_topic = "test/installed-version" + latest_version_topic = "test/latest-version" + await async_setup_component( + hass, + mqtt.DOMAIN, + { + mqtt.DOMAIN: { + update.DOMAIN: { + "state_topic": installed_version_topic, + "value_template": "{{ value_json.installed }}", + "latest_version_topic": latest_version_topic, + "latest_version_template": "{{ value_json.latest }}", + "name": "Test Update", + } + } + }, + ) + await hass.async_block_till_done() + await mqtt_mock_entry_with_yaml_config() + + async_fire_mqtt_message(hass, installed_version_topic, '{"installed":"1.9"}') + async_fire_mqtt_message(hass, latest_version_topic, '{"latest":"1.9"}') + + await hass.async_block_till_done() + + state = hass.states.get("update.test_update") + assert state.state == STATE_OFF + assert state.attributes.get("installed_version") == "1.9" + assert state.attributes.get("latest_version") == "1.9" + assert ( + state.attributes.get("entity_picture") + == "https://brands.home-assistant.io/_/mqtt/icon.png" + ) + + async_fire_mqtt_message(hass, latest_version_topic, '{"latest":"2.0"}') + + await hass.async_block_till_done() + + state = hass.states.get("update.test_update") + assert state.state == STATE_ON + assert state.attributes.get("installed_version") == "1.9" + assert state.attributes.get("latest_version") == "2.0" + + async def test_empty_json_state_message(hass, mqtt_mock_entry_with_yaml_config): """Test an empty JSON payload.""" state_topic = "test/state-topic" From d6b691e3e1cdd1eacb226a934c831d3350ac6f15 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Dec 2022 16:09:10 -1000 Subject: [PATCH 052/174] Bump aioesphomeapi to 13.0.1 (#83223) fixes https://github.com/home-assistant/core/issues/83212 --- homeassistant/components/esphome/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/esphome/manifest.json b/homeassistant/components/esphome/manifest.json index 6d9796b8541..ae11aa59fce 100644 --- a/homeassistant/components/esphome/manifest.json +++ b/homeassistant/components/esphome/manifest.json @@ -3,7 +3,7 @@ "name": "ESPHome", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/esphome", - "requirements": ["aioesphomeapi==13.0.0"], + "requirements": ["aioesphomeapi==13.0.1"], "zeroconf": ["_esphomelib._tcp.local."], "dhcp": [{ "registered_devices": true }], "codeowners": ["@OttoWinter", "@jesserockz"], diff --git a/requirements_all.txt b/requirements_all.txt index a60b3b74aca..c751343bb28 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -156,7 +156,7 @@ aioecowitt==2022.11.0 aioemonitor==1.0.5 # homeassistant.components.esphome -aioesphomeapi==13.0.0 +aioesphomeapi==13.0.1 # homeassistant.components.flo aioflo==2021.11.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 1215dcbf604..ac69bdf4144 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -143,7 +143,7 @@ aioecowitt==2022.11.0 aioemonitor==1.0.5 # homeassistant.components.esphome -aioesphomeapi==13.0.0 +aioesphomeapi==13.0.1 # homeassistant.components.flo aioflo==2021.11.0 From c1e808cc843717b1af8f4b918e0565bbc9d6b50e Mon Sep 17 00:00:00 2001 From: Simone Chemelli Date: Sun, 4 Dec 2022 19:11:24 +0100 Subject: [PATCH 053/174] Fix missing title placeholders in Synology DSM reauth flow (#83238) * Fix missing title placeholders in reauth flow * fix tests Co-authored-by: mib1185 --- homeassistant/components/synology_dsm/config_flow.py | 5 ++++- tests/components/synology_dsm/test_config_flow.py | 3 ++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/synology_dsm/config_flow.py b/homeassistant/components/synology_dsm/config_flow.py index 0314165eb41..ba332ca7e7d 100644 --- a/homeassistant/components/synology_dsm/config_flow.py +++ b/homeassistant/components/synology_dsm/config_flow.py @@ -164,6 +164,7 @@ class SynologyDSMFlowHandler(ConfigFlow, domain=DOMAIN): use_ssl = user_input.get(CONF_SSL, DEFAULT_USE_SSL) verify_ssl = user_input.get(CONF_VERIFY_SSL, DEFAULT_VERIFY_SSL) otp_code = user_input.get(CONF_OTP_CODE) + friendly_name = user_input.get(CONF_NAME) if not port: if use_ssl is True: @@ -229,7 +230,7 @@ class SynologyDSMFlowHandler(ConfigFlow, domain=DOMAIN): return self.async_abort(reason="reauth_successful") return self.async_abort(reason="reconfigure_successful") - return self.async_create_entry(title=host, data=config_data) + return self.async_create_entry(title=friendly_name or host, data=config_data) async def async_step_user( self, user_input: dict[str, Any] | None = None @@ -303,6 +304,8 @@ class SynologyDSMFlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: """Perform reauth upon an API authentication error.""" self.reauth_conf = entry_data + self.context["title_placeholders"][CONF_HOST] = entry_data[CONF_HOST] + return await self.async_step_reauth_confirm() async def async_step_reauth_confirm( diff --git a/tests/components/synology_dsm/test_config_flow.py b/tests/components/synology_dsm/test_config_flow.py index 015c3a2ab16..25259ac7ee9 100644 --- a/tests/components/synology_dsm/test_config_flow.py +++ b/tests/components/synology_dsm/test_config_flow.py @@ -281,6 +281,7 @@ async def test_reauth(hass: HomeAssistant, service: MagicMock): "source": SOURCE_REAUTH, "entry_id": entry.entry_id, "unique_id": entry.unique_id, + "title_placeholders": {"name": entry.title}, }, data={ CONF_HOST: HOST, @@ -409,7 +410,7 @@ async def test_form_ssdp(hass: HomeAssistant, service: MagicMock): assert result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY assert result["result"].unique_id == SERIAL - assert result["title"] == "192.168.1.5" + assert result["title"] == "mydsm" assert result["data"][CONF_HOST] == "192.168.1.5" assert result["data"][CONF_PORT] == 5001 assert result["data"][CONF_SSL] == DEFAULT_USE_SSL From 1aa2820c639612c5cb1e272ad42f90d895b4644a Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Dec 2022 08:08:21 -1000 Subject: [PATCH 054/174] Bump pySwitchbot to 0.22.0 (#83243) fixes https://github.com/home-assistant/core/issues/82961 --- homeassistant/components/switchbot/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/switchbot/manifest.json b/homeassistant/components/switchbot/manifest.json index c96b2884d3b..831d14d8459 100644 --- a/homeassistant/components/switchbot/manifest.json +++ b/homeassistant/components/switchbot/manifest.json @@ -2,7 +2,7 @@ "domain": "switchbot", "name": "SwitchBot", "documentation": "https://www.home-assistant.io/integrations/switchbot", - "requirements": ["PySwitchbot==0.20.8"], + "requirements": ["PySwitchbot==0.22.0"], "config_flow": true, "dependencies": ["bluetooth"], "codeowners": [ diff --git a/requirements_all.txt b/requirements_all.txt index c751343bb28..fc0e10b671b 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -37,7 +37,7 @@ PyRMVtransport==0.3.3 PySocks==1.7.1 # homeassistant.components.switchbot -PySwitchbot==0.20.8 +PySwitchbot==0.22.0 # homeassistant.components.transport_nsw PyTransportNSW==0.1.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index ac69bdf4144..28362613fe9 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -33,7 +33,7 @@ PyRMVtransport==0.3.3 PySocks==1.7.1 # homeassistant.components.switchbot -PySwitchbot==0.20.8 +PySwitchbot==0.22.0 # homeassistant.components.transport_nsw PyTransportNSW==0.1.1 From 5d8650484b5ac118d3245d289eb4e3964e20a3e6 Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Sun, 4 Dec 2022 10:18:27 -0800 Subject: [PATCH 055/174] Bump ical to 4.2.0 (#83244) --- homeassistant/components/local_calendar/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/local_calendar/manifest.json b/homeassistant/components/local_calendar/manifest.json index 037c9ff67bd..88324e30f54 100644 --- a/homeassistant/components/local_calendar/manifest.json +++ b/homeassistant/components/local_calendar/manifest.json @@ -3,7 +3,7 @@ "name": "Local Calendar", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/local_calendar", - "requirements": ["ical==4.1.2"], + "requirements": ["ical==4.2.0"], "codeowners": ["@allenporter"], "iot_class": "local_polling", "loggers": ["ical"] diff --git a/requirements_all.txt b/requirements_all.txt index fc0e10b671b..b5a5b7231e2 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -926,7 +926,7 @@ ibm-watson==5.2.2 ibmiotf==0.3.4 # homeassistant.components.local_calendar -ical==4.1.2 +ical==4.2.0 # homeassistant.components.ping icmplib==3.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 28362613fe9..26dc265dba3 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -691,7 +691,7 @@ iaqualink==0.5.0 ibeacon_ble==1.0.1 # homeassistant.components.local_calendar -ical==4.1.2 +ical==4.2.0 # homeassistant.components.ping icmplib==3.0 From 7da31f6ee423f006e2afcedcc7d4e786ab4f4773 Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Sun, 4 Dec 2022 14:16:51 -0800 Subject: [PATCH 056/174] Bump gcal_sync to 4.0.4 (#83245) --- homeassistant/components/google/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/google/manifest.json b/homeassistant/components/google/manifest.json index bc6c719c8fd..542eb72206a 100644 --- a/homeassistant/components/google/manifest.json +++ b/homeassistant/components/google/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "dependencies": ["application_credentials"], "documentation": "https://www.home-assistant.io/integrations/calendar.google/", - "requirements": ["gcal-sync==4.0.3", "oauth2client==4.1.3"], + "requirements": ["gcal-sync==4.0.4", "oauth2client==4.1.3"], "codeowners": ["@allenporter"], "iot_class": "cloud_polling", "loggers": ["googleapiclient"] diff --git a/requirements_all.txt b/requirements_all.txt index b5a5b7231e2..f565c68aa1f 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -738,7 +738,7 @@ gTTS==2.2.4 garages-amsterdam==3.0.0 # homeassistant.components.google -gcal-sync==4.0.3 +gcal-sync==4.0.4 # homeassistant.components.geniushub geniushub-client==0.6.30 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 26dc265dba3..3665adea181 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -554,7 +554,7 @@ gTTS==2.2.4 garages-amsterdam==3.0.0 # homeassistant.components.google -gcal-sync==4.0.3 +gcal-sync==4.0.4 # homeassistant.components.geocaching geocachingapi==0.2.1 From 17cd7d0a854f2bdc628bc15e156f75384cfb32b6 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 4 Dec 2022 16:09:45 -1000 Subject: [PATCH 057/174] Avoid regex overhead in processing esphome bluetooth advertisements (#83246) --- homeassistant/components/esphome/bluetooth/scanner.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/esphome/bluetooth/scanner.py b/homeassistant/components/esphome/bluetooth/scanner.py index b6cd3d2ec35..ea44ff45d1c 100644 --- a/homeassistant/components/esphome/bluetooth/scanner.py +++ b/homeassistant/components/esphome/bluetooth/scanner.py @@ -1,7 +1,6 @@ """Bluetooth scanner for esphome.""" from __future__ import annotations -import re from typing import Any from aioesphomeapi import BluetoothLEAdvertisement @@ -9,8 +8,6 @@ from aioesphomeapi import BluetoothLEAdvertisement from homeassistant.components.bluetooth import BaseHaRemoteScanner from homeassistant.core import callback -TWO_CHAR = re.compile("..") - class ESPHomeScanner(BaseHaRemoteScanner): """Scanner for esphome.""" @@ -18,9 +15,10 @@ class ESPHomeScanner(BaseHaRemoteScanner): @callback def async_on_advertisement(self, adv: BluetoothLEAdvertisement) -> None: """Call the registered callback.""" - address = ":".join(TWO_CHAR.findall("%012X" % adv.address)) # must be upper + # The mac address is a uint64, but we need a string + mac_hex = f"{adv.address:012X}" self._async_on_advertisement( - address, + f"{mac_hex[0:2]}:{mac_hex[2:4]}:{mac_hex[4:6]}:{mac_hex[6:8]}:{mac_hex[8:10]}:{mac_hex[10:12]}", adv.rssi, adv.name, adv.service_uuids, From 8c92f99cda9ee93e61fb3ed751b0e6c37e0d2e5f Mon Sep 17 00:00:00 2001 From: jjlawren Date: Sun, 4 Dec 2022 20:11:30 -0600 Subject: [PATCH 058/174] Do not discard zero positions for Sonos media players (#83249) fixes undefined --- homeassistant/components/sonos/media.py | 6 +++--- homeassistant/components/sonos/media_player.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/sonos/media.py b/homeassistant/components/sonos/media.py index 24233b1316f..ab34457e3fc 100644 --- a/homeassistant/components/sonos/media.py +++ b/homeassistant/components/sonos/media.py @@ -43,11 +43,11 @@ DURATION_SECONDS = "duration_in_s" POSITION_SECONDS = "position_in_s" -def _timespan_secs(timespan: str | None) -> None | float: +def _timespan_secs(timespan: str | None) -> None | int: """Parse a time-span into number of seconds.""" if timespan in UNAVAILABLE_VALUES: return None - return time_period_str(timespan).total_seconds() # type: ignore[arg-type] + return int(time_period_str(timespan).total_seconds()) # type: ignore[arg-type] class SonosMedia: @@ -73,7 +73,7 @@ class SonosMedia: self.title: str | None = None self.uri: str | None = None - self.position: float | None = None + self.position: int | None = None self.position_updated_at: datetime.datetime | None = None def clear(self) -> None: diff --git a/homeassistant/components/sonos/media_player.py b/homeassistant/components/sonos/media_player.py index 4195f284ffe..bd50a090175 100644 --- a/homeassistant/components/sonos/media_player.py +++ b/homeassistant/components/sonos/media_player.py @@ -323,7 +323,7 @@ class SonosMediaPlayerEntity(SonosEntity, MediaPlayerEntity): @property def media_position(self) -> int | None: """Position of current playing media in seconds.""" - return int(self.media.position) if self.media.position else None + return self.media.position @property def media_position_updated_at(self) -> datetime.datetime | None: From d89d2d3b3371e2d926669fceb7624075028ae197 Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Sun, 4 Dec 2022 14:05:35 -0800 Subject: [PATCH 059/174] Bump ical to 4.2.1 (#83254) --- homeassistant/components/local_calendar/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/local_calendar/manifest.json b/homeassistant/components/local_calendar/manifest.json index 88324e30f54..fa258c389ab 100644 --- a/homeassistant/components/local_calendar/manifest.json +++ b/homeassistant/components/local_calendar/manifest.json @@ -3,7 +3,7 @@ "name": "Local Calendar", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/local_calendar", - "requirements": ["ical==4.2.0"], + "requirements": ["ical==4.2.1"], "codeowners": ["@allenporter"], "iot_class": "local_polling", "loggers": ["ical"] diff --git a/requirements_all.txt b/requirements_all.txt index f565c68aa1f..e5bfa674a9b 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -926,7 +926,7 @@ ibm-watson==5.2.2 ibmiotf==0.3.4 # homeassistant.components.local_calendar -ical==4.2.0 +ical==4.2.1 # homeassistant.components.ping icmplib==3.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 3665adea181..f3c92e59e47 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -691,7 +691,7 @@ iaqualink==0.5.0 ibeacon_ble==1.0.1 # homeassistant.components.local_calendar -ical==4.2.0 +ical==4.2.1 # homeassistant.components.ping icmplib==3.0 From 8d433aa48130af6589796a01b25afc51f752308a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joris=20Pelgr=C3=B6m?= Date: Mon, 5 Dec 2022 03:10:26 +0100 Subject: [PATCH 060/174] Fix mobile_app returning cloud URL when not subscribed (#83257) fixes undefined --- homeassistant/components/mobile_app/http_api.py | 5 +++-- homeassistant/components/mobile_app/webhook.py | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/mobile_app/http_api.py b/homeassistant/components/mobile_app/http_api.py index ea8c56d1a7c..3c34a291df1 100644 --- a/homeassistant/components/mobile_app/http_api.py +++ b/homeassistant/components/mobile_app/http_api.py @@ -91,8 +91,9 @@ class RegistrationsView(HomeAssistantView): ) remote_ui_url = None - with suppress(hass.components.cloud.CloudNotAvailable): - remote_ui_url = cloud.async_remote_ui_url(hass) + if cloud.async_active_subscription(hass): + with suppress(hass.components.cloud.CloudNotAvailable): + remote_ui_url = cloud.async_remote_ui_url(hass) return self.json( { diff --git a/homeassistant/components/mobile_app/webhook.py b/homeassistant/components/mobile_app/webhook.py index cd54c4216b5..2dd578a3fea 100644 --- a/homeassistant/components/mobile_app/webhook.py +++ b/homeassistant/components/mobile_app/webhook.py @@ -693,8 +693,9 @@ async def webhook_get_config( if CONF_CLOUDHOOK_URL in config_entry.data: resp[CONF_CLOUDHOOK_URL] = config_entry.data[CONF_CLOUDHOOK_URL] - with suppress(hass.components.cloud.CloudNotAvailable): - resp[CONF_REMOTE_UI_URL] = cloud.async_remote_ui_url(hass) + if cloud.async_active_subscription(hass): + with suppress(hass.components.cloud.CloudNotAvailable): + resp[CONF_REMOTE_UI_URL] = cloud.async_remote_ui_url(hass) webhook_id = config_entry.data[CONF_WEBHOOK_ID] From e862caa7041ce73857680bf5fb3375796dff0f8d Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 4 Dec 2022 21:17:16 -0500 Subject: [PATCH 061/174] Bumped version to 2022.12.0b4 --- homeassistant/const.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index bdd8b4640ca..856c0202396 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -8,7 +8,7 @@ from .backports.enum import StrEnum APPLICATION_NAME: Final = "HomeAssistant" MAJOR_VERSION: Final = 2022 MINOR_VERSION: Final = 12 -PATCH_VERSION: Final = "0b3" +PATCH_VERSION: Final = "0b4" __short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__: Final = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 9, 0) diff --git a/pyproject.toml b/pyproject.toml index 239052d5285..b423f299811 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "homeassistant" -version = "2022.12.0b3" +version = "2022.12.0b4" license = {text = "Apache-2.0"} description = "Open-source home automation platform running on Python 3." readme = "README.rst" From 8c02c778d42bfbdb5754c8f6d1dc02330e9a9b0c Mon Sep 17 00:00:00 2001 From: Stephan Singer <3062329+SteSinger@users.noreply.github.com> Date: Mon, 5 Dec 2022 00:48:41 +0100 Subject: [PATCH 062/174] Add IPv6 sensor to fritz component (#75708) * Add IPv6 sensor to fritz component * Cast return type to string * Make ipv6 sensor suitable * simplify cast to str * use extisting property Co-authored-by: chemelli74 Co-authored-by: mib1185 --- homeassistant/components/fritz/common.py | 10 ++++++++++ homeassistant/components/fritz/sensor.py | 12 ++++++++++++ tests/components/fritz/const.py | 1 + tests/components/fritz/test_sensor.py | 4 ++++ 4 files changed, 27 insertions(+) diff --git a/homeassistant/components/fritz/common.py b/homeassistant/components/fritz/common.py index 6364ada9fb2..5af4f0c2239 100644 --- a/homeassistant/components/fritz/common.py +++ b/homeassistant/components/fritz/common.py @@ -663,6 +663,14 @@ class AvmWrapper(FritzBoxTools): partial(self.get_wan_link_properties) ) + async def async_ipv6_active(self) -> bool: + """Check ip an ipv6 is active on the WAn interface.""" + + def wrap_external_ipv6() -> str: + return str(self.fritz_status.external_ipv6) + + return bool(await self.hass.async_add_executor_job(wrap_external_ipv6)) + async def async_get_connection_info(self) -> ConnectionInfo: """Return ConnectionInfo data.""" @@ -671,6 +679,7 @@ class AvmWrapper(FritzBoxTools): connection=link_properties.get("NewWANAccessType", "").lower(), mesh_role=self.mesh_role, wan_enabled=self.device_is_router, + ipv6_active=await self.async_ipv6_active(), ) _LOGGER.debug( "ConnectionInfo for FritzBox %s: %s", @@ -1011,3 +1020,4 @@ class ConnectionInfo: connection: str mesh_role: MeshRoles wan_enabled: bool + ipv6_active: bool diff --git a/homeassistant/components/fritz/sensor.py b/homeassistant/components/fritz/sensor.py index 0f3d8cb1ae0..821a0000a5e 100644 --- a/homeassistant/components/fritz/sensor.py +++ b/homeassistant/components/fritz/sensor.py @@ -66,6 +66,11 @@ def _retrieve_external_ip_state(status: FritzStatus, last_value: str) -> str: return status.external_ip # type: ignore[no-any-return] +def _retrieve_external_ipv6_state(status: FritzStatus, last_value: str) -> str: + """Return external ipv6 from device.""" + return str(status.external_ipv6) + + def _retrieve_kb_s_sent_state(status: FritzStatus, last_value: str) -> float: """Return upload transmission rate.""" return round(status.transmission_rate[0] / 1000, 1) # type: ignore[no-any-return] @@ -155,6 +160,13 @@ SENSOR_TYPES: tuple[FritzSensorEntityDescription, ...] = ( icon="mdi:earth", value_fn=_retrieve_external_ip_state, ), + FritzSensorEntityDescription( + key="external_ipv6", + name="External IPv6", + icon="mdi:earth", + value_fn=_retrieve_external_ipv6_state, + is_suitable=lambda info: info.ipv6_active, + ), FritzSensorEntityDescription( key="device_uptime", name="Device Uptime", diff --git a/tests/components/fritz/const.py b/tests/components/fritz/const.py index 32f2211d16b..fb62e14bc6f 100644 --- a/tests/components/fritz/const.py +++ b/tests/components/fritz/const.py @@ -138,6 +138,7 @@ MOCK_FB_SERVICES: dict[str, dict] = { "NewUptime": 35307, }, "GetExternalIPAddress": {"NewExternalIPAddress": "1.2.3.4"}, + "X_AVM_DE_GetExternalIPv6Address": {"NewExternalIPv6Address": "fec0::1"}, }, "WANPPPConnection1": { "GetInfo": { diff --git a/tests/components/fritz/test_sensor.py b/tests/components/fritz/test_sensor.py index 2a3435210e7..117e9d31967 100644 --- a/tests/components/fritz/test_sensor.py +++ b/tests/components/fritz/test_sensor.py @@ -35,6 +35,10 @@ SENSOR_STATES: dict[str, dict[str, Any]] = { ATTR_STATE: "1.2.3.4", ATTR_ICON: "mdi:earth", }, + "sensor.mock_title_external_ipv6": { + ATTR_STATE: "fec0::1", + ATTR_ICON: "mdi:earth", + }, "sensor.mock_title_device_uptime": { # ATTR_STATE: "2022-02-05T17:46:04+00:00", ATTR_DEVICE_CLASS: SensorDeviceClass.TIMESTAMP, From b30c98c525d9070f133997c47f372975b5b0e576 Mon Sep 17 00:00:00 2001 From: Garrett <7310260+G-Two@users.noreply.github.com> Date: Mon, 5 Dec 2022 04:53:05 -0500 Subject: [PATCH 063/174] Bump subarulink to v0.7.0 (#83213) fixes undefined --- homeassistant/components/subaru/manifest.json | 2 +- homeassistant/components/subaru/sensor.py | 16 ------- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/subaru/api_responses.py | 48 ++++++++----------- .../fixtures/diagnostics_config_entry.json | 14 +++--- .../subaru/fixtures/diagnostics_device.json | 14 +++--- 7 files changed, 36 insertions(+), 62 deletions(-) diff --git a/homeassistant/components/subaru/manifest.json b/homeassistant/components/subaru/manifest.json index df3a97cbda3..d0e1193f92b 100644 --- a/homeassistant/components/subaru/manifest.json +++ b/homeassistant/components/subaru/manifest.json @@ -3,7 +3,7 @@ "name": "Subaru", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/subaru", - "requirements": ["subarulink==0.6.1"], + "requirements": ["subarulink==0.7.0"], "codeowners": ["@G-Two"], "iot_class": "cloud_polling", "loggers": ["stdiomask", "subarulink"] diff --git a/homeassistant/components/subaru/sensor.py b/homeassistant/components/subaru/sensor.py index cae5a7b14a4..9db2d329210 100644 --- a/homeassistant/components/subaru/sensor.py +++ b/homeassistant/components/subaru/sensor.py @@ -14,12 +14,10 @@ from homeassistant.components.sensor import ( ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( - ELECTRIC_POTENTIAL_VOLT, LENGTH_KILOMETERS, LENGTH_MILES, PERCENTAGE, PRESSURE_HPA, - TEMP_CELSIUS, VOLUME_GALLONS, VOLUME_LITERS, ) @@ -117,20 +115,6 @@ API_GEN_2_SENSORS = [ native_unit_of_measurement=PRESSURE_HPA, state_class=SensorStateClass.MEASUREMENT, ), - SensorEntityDescription( - key=sc.EXTERNAL_TEMP, - device_class=SensorDeviceClass.TEMPERATURE, - name="External temp", - native_unit_of_measurement=TEMP_CELSIUS, - state_class=SensorStateClass.MEASUREMENT, - ), - SensorEntityDescription( - key=sc.BATTERY_VOLTAGE, - device_class=SensorDeviceClass.VOLTAGE, - name="12V battery voltage", - native_unit_of_measurement=ELECTRIC_POTENTIAL_VOLT, - state_class=SensorStateClass.MEASUREMENT, - ), ] # Sensors available to "Subaru Safety Plus" subscribers with PHEV vehicles diff --git a/requirements_all.txt b/requirements_all.txt index e5bfa674a9b..0fee807b5c4 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2373,7 +2373,7 @@ streamlabswater==1.0.1 stringcase==1.2.0 # homeassistant.components.subaru -subarulink==0.6.1 +subarulink==0.7.0 # homeassistant.components.solarlog sunwatcher==0.2.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index f3c92e59e47..a1012f29e1e 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1652,7 +1652,7 @@ stookalert==0.1.4 stringcase==1.2.0 # homeassistant.components.subaru -subarulink==0.6.1 +subarulink==0.7.0 # homeassistant.components.solarlog sunwatcher==0.2.1 diff --git a/tests/components/subaru/api_responses.py b/tests/components/subaru/api_responses.py index bd107f4bb37..315530c15b3 100644 --- a/tests/components/subaru/api_responses.py +++ b/tests/components/subaru/api_responses.py @@ -53,7 +53,6 @@ MOCK_DATETIME = datetime.fromtimestamp(1595560000, timezone.utc) VEHICLE_STATUS_EV = { "status": { "AVG_FUEL_CONSUMPTION": 2.3, - "BATTERY_VOLTAGE": 12.0, "DISTANCE_TO_EMPTY_FUEL": 707, "DOOR_BOOT_LOCK_STATUS": "UNKNOWN", "DOOR_BOOT_POSITION": "CLOSED", @@ -75,7 +74,6 @@ VEHICLE_STATUS_EV = { "EV_STATE_OF_CHARGE_MODE": "EV_MODE", "EV_STATE_OF_CHARGE_PERCENT": 20, "EV_TIME_TO_FULLY_CHARGED_UTC": MOCK_DATETIME, - "EXT_EXTERNAL_TEMP": 21.5, "ODOMETER": 1234, "POSITION_HEADING_DEGREE": 150, "POSITION_SPEED_KMPH": "0", @@ -103,7 +101,7 @@ VEHICLE_STATUS_EV = { "TYRE_PRESSURE_FRONT_LEFT": 0, "TYRE_PRESSURE_FRONT_RIGHT": 2550, "TYRE_PRESSURE_REAR_LEFT": 2450, - "TYRE_PRESSURE_REAR_RIGHT": 2350, + "TYRE_PRESSURE_REAR_RIGHT": None, "TYRE_STATUS_FRONT_LEFT": "UNKNOWN", "TYRE_STATUS_FRONT_RIGHT": "UNKNOWN", "TYRE_STATUS_REAR_LEFT": "UNKNOWN", @@ -115,9 +113,9 @@ VEHICLE_STATUS_EV = { "WINDOW_REAR_LEFT_STATUS": "UNKNOWN", "WINDOW_REAR_RIGHT_STATUS": "UNKNOWN", "WINDOW_SUNROOF_STATUS": "UNKNOWN", - "heading": 170, - "latitude": 40.0, - "longitude": -100.0, + "HEADING": 170, + "LATITUDE": 40.0, + "LONGITUDE": -100.0, } } @@ -125,7 +123,6 @@ VEHICLE_STATUS_EV = { VEHICLE_STATUS_G2 = { "status": { "AVG_FUEL_CONSUMPTION": 2.3, - "BATTERY_VOLTAGE": 12.0, "DISTANCE_TO_EMPTY_FUEL": 707, "DOOR_BOOT_LOCK_STATUS": "UNKNOWN", "DOOR_BOOT_POSITION": "CLOSED", @@ -139,7 +136,6 @@ VEHICLE_STATUS_G2 = { "DOOR_REAR_LEFT_POSITION": "CLOSED", "DOOR_REAR_RIGHT_LOCK_STATUS": "UNKNOWN", "DOOR_REAR_RIGHT_POSITION": "CLOSED", - "EXT_EXTERNAL_TEMP": None, "ODOMETER": 1234, "POSITION_HEADING_DEGREE": 150, "POSITION_SPEED_KMPH": "0", @@ -167,7 +163,7 @@ VEHICLE_STATUS_G2 = { "TYRE_PRESSURE_FRONT_LEFT": 2550, "TYRE_PRESSURE_FRONT_RIGHT": 2550, "TYRE_PRESSURE_REAR_LEFT": 2450, - "TYRE_PRESSURE_REAR_RIGHT": 2350, + "TYRE_PRESSURE_REAR_RIGHT": None, "TYRE_STATUS_FRONT_LEFT": "UNKNOWN", "TYRE_STATUS_FRONT_RIGHT": "UNKNOWN", "TYRE_STATUS_REAR_LEFT": "UNKNOWN", @@ -179,15 +175,14 @@ VEHICLE_STATUS_G2 = { "WINDOW_REAR_LEFT_STATUS": "UNKNOWN", "WINDOW_REAR_RIGHT_STATUS": "UNKNOWN", "WINDOW_SUNROOF_STATUS": "UNKNOWN", - "heading": 170, - "latitude": 40.0, - "longitude": -100.0, + "HEADING": 170, + "LATITUDE": 40.0, + "LONGITUDE": -100.0, } } EXPECTED_STATE_EV_IMPERIAL = { "AVG_FUEL_CONSUMPTION": "102.3", - "BATTERY_VOLTAGE": "12.0", "DISTANCE_TO_EMPTY_FUEL": "439.3", "EV_CHARGER_STATE_TYPE": "CHARGING", "EV_CHARGE_SETTING_AMPERE_TYPE": "MAXIMUM", @@ -197,7 +192,6 @@ EXPECTED_STATE_EV_IMPERIAL = { "EV_STATE_OF_CHARGE_MODE": "EV_MODE", "EV_STATE_OF_CHARGE_PERCENT": "20", "EV_TIME_TO_FULLY_CHARGED_UTC": "2020-07-24T03:06:40+00:00", - "EXT_EXTERNAL_TEMP": "70.7", "ODOMETER": "766.8", "POSITION_HEADING_DEGREE": "150", "POSITION_SPEED_KMPH": "0", @@ -207,16 +201,15 @@ EXPECTED_STATE_EV_IMPERIAL = { "TYRE_PRESSURE_FRONT_LEFT": "0.0", "TYRE_PRESSURE_FRONT_RIGHT": "37.0", "TYRE_PRESSURE_REAR_LEFT": "35.5", - "TYRE_PRESSURE_REAR_RIGHT": "34.1", + "TYRE_PRESSURE_REAR_RIGHT": "unknown", "VEHICLE_STATE_TYPE": "IGNITION_OFF", - "heading": 170, - "latitude": 40.0, - "longitude": -100.0, + "HEADING": 170, + "LATITUDE": 40.0, + "LONGITUDE": -100.0, } EXPECTED_STATE_EV_METRIC = { "AVG_FUEL_CONSUMPTION": "2.3", - "BATTERY_VOLTAGE": "12.0", "DISTANCE_TO_EMPTY_FUEL": "707", "EV_CHARGER_STATE_TYPE": "CHARGING", "EV_CHARGE_SETTING_AMPERE_TYPE": "MAXIMUM", @@ -226,7 +219,6 @@ EXPECTED_STATE_EV_METRIC = { "EV_STATE_OF_CHARGE_MODE": "EV_MODE", "EV_STATE_OF_CHARGE_PERCENT": "20", "EV_TIME_TO_FULLY_CHARGED_UTC": "2020-07-24T03:06:40+00:00", - "EXT_EXTERNAL_TEMP": "21.5", "ODOMETER": "1234", "POSITION_HEADING_DEGREE": "150", "POSITION_SPEED_KMPH": "0", @@ -236,17 +228,16 @@ EXPECTED_STATE_EV_METRIC = { "TYRE_PRESSURE_FRONT_LEFT": "0", "TYRE_PRESSURE_FRONT_RIGHT": "2550", "TYRE_PRESSURE_REAR_LEFT": "2450", - "TYRE_PRESSURE_REAR_RIGHT": "2350", + "TYRE_PRESSURE_REAR_RIGHT": "unknown", "VEHICLE_STATE_TYPE": "IGNITION_OFF", - "heading": 170, - "latitude": 40.0, - "longitude": -100.0, + "HEADING": 170, + "LATITUDE": 40.0, + "LONGITUDE": -100.0, } EXPECTED_STATE_EV_UNAVAILABLE = { "AVG_FUEL_CONSUMPTION": "unavailable", - "BATTERY_VOLTAGE": "unavailable", "DISTANCE_TO_EMPTY_FUEL": "unavailable", "EV_CHARGER_STATE_TYPE": "unavailable", "EV_CHARGE_SETTING_AMPERE_TYPE": "unavailable", @@ -256,7 +247,6 @@ EXPECTED_STATE_EV_UNAVAILABLE = { "EV_STATE_OF_CHARGE_MODE": "unavailable", "EV_STATE_OF_CHARGE_PERCENT": "unavailable", "EV_TIME_TO_FULLY_CHARGED_UTC": "unavailable", - "EXT_EXTERNAL_TEMP": "unavailable", "ODOMETER": "unavailable", "POSITION_HEADING_DEGREE": "unavailable", "POSITION_SPEED_KMPH": "unavailable", @@ -268,7 +258,7 @@ EXPECTED_STATE_EV_UNAVAILABLE = { "TYRE_PRESSURE_REAR_LEFT": "unavailable", "TYRE_PRESSURE_REAR_RIGHT": "unavailable", "VEHICLE_STATE_TYPE": "unavailable", - "heading": "unavailable", - "latitude": "unavailable", - "longitude": "unavailable", + "HEADING": "unavailable", + "LATITUDE": "unavailable", + "LONGITUDE": "unavailable", } diff --git a/tests/components/subaru/fixtures/diagnostics_config_entry.json b/tests/components/subaru/fixtures/diagnostics_config_entry.json index 09e8119d669..32e9ac070be 100644 --- a/tests/components/subaru/fixtures/diagnostics_config_entry.json +++ b/tests/components/subaru/fixtures/diagnostics_config_entry.json @@ -6,12 +6,13 @@ "pin": "**REDACTED**", "device_id": "**REDACTED**" }, - "options": { "update_enabled": true }, + "options": { + "update_enabled": true + }, "data": [ { "status": { "AVG_FUEL_CONSUMPTION": 2.3, - "BATTERY_VOLTAGE": 12.0, "DISTANCE_TO_EMPTY_FUEL": 707, "DOOR_BOOT_LOCK_STATUS": "UNKNOWN", "DOOR_BOOT_POSITION": "CLOSED", @@ -33,7 +34,6 @@ "EV_STATE_OF_CHARGE_MODE": "EV_MODE", "EV_STATE_OF_CHARGE_PERCENT": 20, "EV_TIME_TO_FULLY_CHARGED_UTC": "2020-07-24T03:06:40+00:00", - "EXT_EXTERNAL_TEMP": 21.5, "ODOMETER": "**REDACTED**", "POSITION_HEADING_DEGREE": 150, "POSITION_SPEED_KMPH": "0", @@ -61,7 +61,7 @@ "TYRE_PRESSURE_FRONT_LEFT": 0, "TYRE_PRESSURE_FRONT_RIGHT": 2550, "TYRE_PRESSURE_REAR_LEFT": 2450, - "TYRE_PRESSURE_REAR_RIGHT": 2350, + "TYRE_PRESSURE_REAR_RIGHT": null, "TYRE_STATUS_FRONT_LEFT": "UNKNOWN", "TYRE_STATUS_FRONT_RIGHT": "UNKNOWN", "TYRE_STATUS_REAR_LEFT": "UNKNOWN", @@ -73,9 +73,9 @@ "WINDOW_REAR_LEFT_STATUS": "UNKNOWN", "WINDOW_REAR_RIGHT_STATUS": "UNKNOWN", "WINDOW_SUNROOF_STATUS": "UNKNOWN", - "heading": 170, - "latitude": "**REDACTED**", - "longitude": "**REDACTED**" + "HEADING": 170, + "LATITUDE": "**REDACTED**", + "LONGITUDE": "**REDACTED**" } } ] diff --git a/tests/components/subaru/fixtures/diagnostics_device.json b/tests/components/subaru/fixtures/diagnostics_device.json index 83b2c2a836e..c3762925d04 100644 --- a/tests/components/subaru/fixtures/diagnostics_device.json +++ b/tests/components/subaru/fixtures/diagnostics_device.json @@ -6,11 +6,12 @@ "pin": "**REDACTED**", "device_id": "**REDACTED**" }, - "options": { "update_enabled": true }, + "options": { + "update_enabled": true + }, "data": { "status": { "AVG_FUEL_CONSUMPTION": 2.3, - "BATTERY_VOLTAGE": 12.0, "DISTANCE_TO_EMPTY_FUEL": 707, "DOOR_BOOT_LOCK_STATUS": "UNKNOWN", "DOOR_BOOT_POSITION": "CLOSED", @@ -32,7 +33,6 @@ "EV_STATE_OF_CHARGE_MODE": "EV_MODE", "EV_STATE_OF_CHARGE_PERCENT": 20, "EV_TIME_TO_FULLY_CHARGED_UTC": "2020-07-24T03:06:40+00:00", - "EXT_EXTERNAL_TEMP": 21.5, "ODOMETER": "**REDACTED**", "POSITION_HEADING_DEGREE": 150, "POSITION_SPEED_KMPH": "0", @@ -60,7 +60,7 @@ "TYRE_PRESSURE_FRONT_LEFT": 0, "TYRE_PRESSURE_FRONT_RIGHT": 2550, "TYRE_PRESSURE_REAR_LEFT": 2450, - "TYRE_PRESSURE_REAR_RIGHT": 2350, + "TYRE_PRESSURE_REAR_RIGHT": null, "TYRE_STATUS_FRONT_LEFT": "UNKNOWN", "TYRE_STATUS_FRONT_RIGHT": "UNKNOWN", "TYRE_STATUS_REAR_LEFT": "UNKNOWN", @@ -72,9 +72,9 @@ "WINDOW_REAR_LEFT_STATUS": "UNKNOWN", "WINDOW_REAR_RIGHT_STATUS": "UNKNOWN", "WINDOW_SUNROOF_STATUS": "UNKNOWN", - "heading": 170, - "latitude": "**REDACTED**", - "longitude": "**REDACTED**" + "HEADING": 170, + "LATITUDE": "**REDACTED**", + "LONGITUDE": "**REDACTED**" } } } From 8985a3cc06a4452b9abf8f16460e0e5df12c329c Mon Sep 17 00:00:00 2001 From: Christopher Bailey Date: Sun, 4 Dec 2022 22:06:55 -0500 Subject: [PATCH 064/174] Update UniFi Protect ring entity to use event entity (#83270) --- .../components/unifiprotect/binary_sensor.py | 27 ++++++++++--------- .../components/unifiprotect/sensor.py | 10 +++---- .../unifiprotect/test_binary_sensor.py | 12 ++++----- tests/components/unifiprotect/test_sensor.py | 8 +++--- 4 files changed, 29 insertions(+), 28 deletions(-) diff --git a/homeassistant/components/unifiprotect/binary_sensor.py b/homeassistant/components/unifiprotect/binary_sensor.py index bf6e89c3caf..bf53dc8d206 100644 --- a/homeassistant/components/unifiprotect/binary_sensor.py +++ b/homeassistant/components/unifiprotect/binary_sensor.py @@ -67,14 +67,6 @@ MOUNT_DEVICE_CLASS_MAP = { CAMERA_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = ( - ProtectBinaryEntityDescription( - key="doorbell", - name="Doorbell", - device_class=BinarySensorDeviceClass.OCCUPANCY, - icon="mdi:doorbell-video", - ufp_required_field="feature_flags.has_chime", - ufp_value="is_ringing", - ), ProtectBinaryEntityDescription( key="dark", name="Is Dark", @@ -339,7 +331,16 @@ SENSE_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = ( ), ) -MOTION_SENSORS: tuple[ProtectBinaryEventEntityDescription, ...] = ( +EVENT_SENSORS: tuple[ProtectBinaryEventEntityDescription, ...] = ( + ProtectBinaryEventEntityDescription( + key="doorbell", + name="Doorbell", + device_class=BinarySensorDeviceClass.OCCUPANCY, + icon="mdi:doorbell-video", + ufp_required_field="feature_flags.has_chime", + ufp_value="is_ringing", + ufp_event_obj="last_ring_event", + ), ProtectBinaryEventEntityDescription( key="motion", name="Motion", @@ -485,7 +486,7 @@ async def async_setup_entry( ufp_device=device, ) if device.is_adopted and isinstance(device, Camera): - entities += _async_motion_entities(data, ufp_device=device) + entities += _async_event_entities(data, ufp_device=device) async_add_entities(entities) entry.async_on_unload( @@ -501,14 +502,14 @@ async def async_setup_entry( lock_descs=DOORLOCK_SENSORS, viewer_descs=VIEWER_SENSORS, ) - entities += _async_motion_entities(data) + entities += _async_event_entities(data) entities += _async_nvr_entities(data) async_add_entities(entities) @callback -def _async_motion_entities( +def _async_event_entities( data: ProtectData, ufp_device: ProtectAdoptableDeviceModel | None = None, ) -> list[ProtectDeviceEntity]: @@ -517,7 +518,7 @@ def _async_motion_entities( data.get_by_types({ModelType.CAMERA}) if ufp_device is None else [ufp_device] ) for device in devices: - for description in MOTION_SENSORS: + for description in EVENT_SENSORS: if not description.has_required(device): continue entities.append(ProtectEventBinarySensor(data, device, description)) diff --git a/homeassistant/components/unifiprotect/sensor.py b/homeassistant/components/unifiprotect/sensor.py index d608907fa83..fa08892e2d3 100644 --- a/homeassistant/components/unifiprotect/sensor.py +++ b/homeassistant/components/unifiprotect/sensor.py @@ -521,7 +521,7 @@ NVR_DISABLED_SENSORS: tuple[ProtectSensorEntityDescription, ...] = ( ), ) -MOTION_SENSORS: tuple[ProtectSensorEventEntityDescription, ...] = ( +EVENT_SENSORS: tuple[ProtectSensorEventEntityDescription, ...] = ( ProtectSensorEventEntityDescription( key="detected_object", name="Detected Object", @@ -641,7 +641,7 @@ async def async_setup_entry( ufp_device=device, ) if device.is_adopted_by_us and isinstance(device, Camera): - entities += _async_motion_entities(data, ufp_device=device) + entities += _async_event_entities(data, ufp_device=device) async_add_entities(entities) entry.async_on_unload( @@ -659,14 +659,14 @@ async def async_setup_entry( chime_descs=CHIME_SENSORS, viewer_descs=VIEWER_SENSORS, ) - entities += _async_motion_entities(data) + entities += _async_event_entities(data) entities += _async_nvr_entities(data) async_add_entities(entities) @callback -def _async_motion_entities( +def _async_event_entities( data: ProtectData, ufp_device: Camera | None = None, ) -> list[ProtectDeviceEntity]: @@ -687,7 +687,7 @@ def _async_motion_entities( if not device.feature_flags.has_smart_detect: continue - for event_desc in MOTION_SENSORS: + for event_desc in EVENT_SENSORS: if not event_desc.has_required(device): continue diff --git a/tests/components/unifiprotect/test_binary_sensor.py b/tests/components/unifiprotect/test_binary_sensor.py index b2935552855..152628c75f9 100644 --- a/tests/components/unifiprotect/test_binary_sensor.py +++ b/tests/components/unifiprotect/test_binary_sensor.py @@ -11,8 +11,8 @@ from pyunifiprotect.data.nvr import EventMetadata from homeassistant.components.binary_sensor import BinarySensorDeviceClass from homeassistant.components.unifiprotect.binary_sensor import ( CAMERA_SENSORS, + EVENT_SENSORS, LIGHT_SENSORS, - MOTION_SENSORS, SENSE_SENSORS, ) from homeassistant.components.unifiprotect.const import ( @@ -124,7 +124,7 @@ async def test_binary_sensor_setup_camera_all( entity_registry = er.async_get(hass) - description = CAMERA_SENSORS[0] + description = EVENT_SENSORS[0] unique_id, entity_id = ids_from_device_description( Platform.BINARY_SENSOR, doorbell, description ) @@ -139,7 +139,7 @@ async def test_binary_sensor_setup_camera_all( assert state.attributes[ATTR_ATTRIBUTION] == DEFAULT_ATTRIBUTION # Is Dark - description = CAMERA_SENSORS[1] + description = CAMERA_SENSORS[0] unique_id, entity_id = ids_from_device_description( Platform.BINARY_SENSOR, doorbell, description ) @@ -154,7 +154,7 @@ async def test_binary_sensor_setup_camera_all( assert state.attributes[ATTR_ATTRIBUTION] == DEFAULT_ATTRIBUTION # Motion - description = MOTION_SENSORS[0] + description = EVENT_SENSORS[1] unique_id, entity_id = ids_from_device_description( Platform.BINARY_SENSOR, doorbell, description ) @@ -179,7 +179,7 @@ async def test_binary_sensor_setup_camera_none( assert_entity_counts(hass, Platform.BINARY_SENSOR, 2, 2) entity_registry = er.async_get(hass) - description = CAMERA_SENSORS[1] + description = CAMERA_SENSORS[0] unique_id, entity_id = ids_from_device_description( Platform.BINARY_SENSOR, camera, description @@ -265,7 +265,7 @@ async def test_binary_sensor_update_motion( assert_entity_counts(hass, Platform.BINARY_SENSOR, 13, 13) _, entity_id = ids_from_device_description( - Platform.BINARY_SENSOR, doorbell, MOTION_SENSORS[0] + Platform.BINARY_SENSOR, doorbell, EVENT_SENSORS[1] ) event = Event( diff --git a/tests/components/unifiprotect/test_sensor.py b/tests/components/unifiprotect/test_sensor.py index e67195df5e9..f5779e78b1c 100644 --- a/tests/components/unifiprotect/test_sensor.py +++ b/tests/components/unifiprotect/test_sensor.py @@ -23,7 +23,7 @@ from homeassistant.components.unifiprotect.sensor import ( ALL_DEVICES_SENSORS, CAMERA_DISABLED_SENSORS, CAMERA_SENSORS, - MOTION_SENSORS, + EVENT_SENSORS, MOTION_TRIP_SENSORS, NVR_DISABLED_SENSORS, NVR_SENSORS, @@ -399,7 +399,7 @@ async def test_sensor_setup_camera( # Detected Object unique_id, entity_id = ids_from_device_description( - Platform.SENSOR, doorbell, MOTION_SENSORS[0] + Platform.SENSOR, doorbell, EVENT_SENSORS[0] ) entity = entity_registry.async_get(entity_id) @@ -455,7 +455,7 @@ async def test_sensor_update_motion( assert_entity_counts(hass, Platform.SENSOR, 25, 12) _, entity_id = ids_from_device_description( - Platform.SENSOR, doorbell, MOTION_SENSORS[0] + Platform.SENSOR, doorbell, EVENT_SENSORS[0] ) await enable_entity(hass, ufp.entry.entry_id, entity_id) @@ -582,7 +582,7 @@ async def test_camera_update_licenseplate( assert_entity_counts(hass, Platform.SENSOR, 24, 13) _, entity_id = ids_from_device_description( - Platform.SENSOR, camera, MOTION_SENSORS[1] + Platform.SENSOR, camera, EVENT_SENSORS[1] ) event_metadata = EventMetadata( From c34f8dc246294144a275baf3b4503fb35d9f8de6 Mon Sep 17 00:00:00 2001 From: Maikel Punie Date: Mon, 5 Dec 2022 17:12:27 +0100 Subject: [PATCH 065/174] Small fix for the velbus clear-cache service (#83279) --- homeassistant/components/velbus/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/velbus/__init__.py b/homeassistant/components/velbus/__init__.py index 2ac751e283d..0da64227fd3 100644 --- a/homeassistant/components/velbus/__init__.py +++ b/homeassistant/components/velbus/__init__.py @@ -139,7 +139,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Handle a clear cache service call.""" # clear the cache with suppress(FileNotFoundError): - if call.data[CONF_ADDRESS]: + if CONF_ADDRESS in call.data and call.data[CONF_ADDRESS]: await hass.async_add_executor_job( os.unlink, hass.config.path( From f951808863f1dbe5124eafb486a1d6ed482ddf84 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 5 Dec 2022 17:34:21 -1000 Subject: [PATCH 066/174] Fix bluetooth device connection failure when device is seen by dbus but not bleak (#83281) --- homeassistant/components/bluetooth/manifest.json | 4 ++-- homeassistant/components/bluetooth/scanner.py | 2 ++ homeassistant/package_constraints.txt | 4 ++-- requirements_all.txt | 4 ++-- requirements_test_all.txt | 4 ++-- 5 files changed, 10 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/bluetooth/manifest.json b/homeassistant/components/bluetooth/manifest.json index 4f5e1bd1b64..a8e1c02c8b0 100644 --- a/homeassistant/components/bluetooth/manifest.json +++ b/homeassistant/components/bluetooth/manifest.json @@ -7,8 +7,8 @@ "quality_scale": "internal", "requirements": [ "bleak==0.19.2", - "bleak-retry-connector==2.9.0", - "bluetooth-adapters==0.11.0", + "bleak-retry-connector==2.10.1", + "bluetooth-adapters==0.12.0", "bluetooth-auto-recovery==0.5.4", "bluetooth-data-tools==0.3.0", "dbus-fast==1.75.0" diff --git a/homeassistant/components/bluetooth/scanner.py b/homeassistant/components/bluetooth/scanner.py index 6f61e16c583..09032715c74 100644 --- a/homeassistant/components/bluetooth/scanner.py +++ b/homeassistant/components/bluetooth/scanner.py @@ -16,6 +16,7 @@ from bleak.backends.bluezdbus.advertisement_monitor import OrPattern from bleak.backends.bluezdbus.scanner import BlueZScannerArgs from bleak.backends.device import BLEDevice from bleak.backends.scanner import AdvertisementData, AdvertisementDataCallback +from bleak_retry_connector import restore_discoveries from bluetooth_adapters import DEFAULT_ADDRESS from dbus_fast import InvalidMessageError @@ -314,6 +315,7 @@ class HaScanner(BaseHaScanner): self.scanning = True self._async_setup_scanner_watchdog() + await restore_discoveries(self.scanner, self.adapter) @hass_callback def _async_setup_scanner_watchdog(self) -> None: diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index e42e7e2b9aa..b63615111cd 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -10,9 +10,9 @@ atomicwrites-homeassistant==1.4.1 attrs==21.2.0 awesomeversion==22.9.0 bcrypt==3.1.7 -bleak-retry-connector==2.9.0 +bleak-retry-connector==2.10.1 bleak==0.19.2 -bluetooth-adapters==0.11.0 +bluetooth-adapters==0.12.0 bluetooth-auto-recovery==0.5.4 bluetooth-data-tools==0.3.0 certifi>=2021.5.30 diff --git a/requirements_all.txt b/requirements_all.txt index 0fee807b5c4..d36e6951aa1 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -422,7 +422,7 @@ bimmer_connected==0.10.4 bizkaibus==0.1.1 # homeassistant.components.bluetooth -bleak-retry-connector==2.9.0 +bleak-retry-connector==2.10.1 # homeassistant.components.bluetooth bleak==0.19.2 @@ -447,7 +447,7 @@ bluemaestro-ble==0.2.0 # bluepy==1.3.0 # homeassistant.components.bluetooth -bluetooth-adapters==0.11.0 +bluetooth-adapters==0.12.0 # homeassistant.components.bluetooth bluetooth-auto-recovery==0.5.4 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index a1012f29e1e..23f7c17a56e 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -346,7 +346,7 @@ bellows==0.34.5 bimmer_connected==0.10.4 # homeassistant.components.bluetooth -bleak-retry-connector==2.9.0 +bleak-retry-connector==2.10.1 # homeassistant.components.bluetooth bleak==0.19.2 @@ -361,7 +361,7 @@ blinkpy==0.19.2 bluemaestro-ble==0.2.0 # homeassistant.components.bluetooth -bluetooth-adapters==0.11.0 +bluetooth-adapters==0.12.0 # homeassistant.components.bluetooth bluetooth-auto-recovery==0.5.4 From fed08a9d8053da6e022c7b0801070aac3c8c6913 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Ajduk?= Date: Mon, 5 Dec 2022 16:55:21 +0100 Subject: [PATCH 067/174] Fix Tuya number native value raw return (#83333) fixes undefined --- homeassistant/components/tuya/number.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/tuya/number.py b/homeassistant/components/tuya/number.py index 39874d0ae8d..21c6dcee6e1 100644 --- a/homeassistant/components/tuya/number.py +++ b/homeassistant/components/tuya/number.py @@ -410,7 +410,7 @@ class TuyaNumberEntity(TuyaEntity, NumberEntity): return None # Raw value - if not (value := self.device.status.get(self.entity_description.key)): + if (value := self.device.status.get(self.entity_description.key)) is None: return None return self._number.scale_value(value) From 218046bfa5d859e7f054d7f0b640a7a6488a986e Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Mon, 5 Dec 2022 16:54:36 +0100 Subject: [PATCH 068/174] Deprecate the Xbox Live integration (#83339) --- homeassistant/components/xbox_live/sensor.py | 14 ++++++++++++++ homeassistant/components/xbox_live/strings.json | 8 ++++++++ .../components/xbox_live/translations/en.json | 8 ++++++++ 3 files changed, 30 insertions(+) create mode 100644 homeassistant/components/xbox_live/strings.json create mode 100644 homeassistant/components/xbox_live/translations/en.json diff --git a/homeassistant/components/xbox_live/sensor.py b/homeassistant/components/xbox_live/sensor.py index 07adcbeb5cc..f75dbe6ba4e 100644 --- a/homeassistant/components/xbox_live/sensor.py +++ b/homeassistant/components/xbox_live/sensor.py @@ -13,6 +13,7 @@ from homeassistant.core import HomeAssistant, callback import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.event import async_track_time_interval +from homeassistant.helpers.issue_registry import IssueSeverity, create_issue from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType _LOGGER = logging.getLogger(__name__) @@ -36,6 +37,19 @@ def setup_platform( discovery_info: DiscoveryInfoType | None = None, ) -> None: """Set up the Xbox platform.""" + create_issue( + hass, + "xbox_live", + "pending_removal", + breaks_in_ha_version="2023.2.0", + is_fixable=False, + severity=IssueSeverity.WARNING, + translation_key="pending_removal", + ) + _LOGGER.warning( + "The Xbox Live integration is deprecated " + "and will be removed in Home Assistant 2023.2" + ) api = Client(api_key=config[CONF_API_KEY]) entities = [] diff --git a/homeassistant/components/xbox_live/strings.json b/homeassistant/components/xbox_live/strings.json new file mode 100644 index 00000000000..0f73f851bd7 --- /dev/null +++ b/homeassistant/components/xbox_live/strings.json @@ -0,0 +1,8 @@ +{ + "issues": { + "pending_removal": { + "title": "The Xbox Live integration is being removed", + "description": "The Xbox Live integration is pending removal from Home Assistant and will no longer be available as of Home Assistant 2023.2.\n\nThe integration is being removed, because it is only useful for the legacy device Xbox 360 and the upstream API now requires a paid subscription. Newer consoles are supported by the Xbox integration for free.\n\nRemove the Xbox Live YAML configuration from your configuration.yaml file and restart Home Assistant to fix this issue." + } + } +} diff --git a/homeassistant/components/xbox_live/translations/en.json b/homeassistant/components/xbox_live/translations/en.json new file mode 100644 index 00000000000..cb2ca622a22 --- /dev/null +++ b/homeassistant/components/xbox_live/translations/en.json @@ -0,0 +1,8 @@ +{ + "issues": { + "pending_removal": { + "description": "The Xbox Live integration is pending removal from Home Assistant and will no longer be available as of Home Assistant 2023.2.\n\nThe integration is being removed, because it is only useful for the legacy device Xbox 360 and the upstream API now requires a paid subscription. Newer consoles are supported by the Xbox integration for free.\n\nRemove the Xbox Live YAML configuration from your configuration.yaml file and restart Home Assistant to fix this issue.", + "title": "The Xbox Live integration is being removed" + } + } +} \ No newline at end of file From 604c4588ccb639ee6de5a4ff83a46cc25e0cb405 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 5 Dec 2022 08:54:27 -1000 Subject: [PATCH 069/174] Bump aiohomekit to 2.4.1 (#83341) --- homeassistant/components/homekit_controller/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/homekit_controller/manifest.json b/homeassistant/components/homekit_controller/manifest.json index c5047f9d215..47112d3bd50 100644 --- a/homeassistant/components/homekit_controller/manifest.json +++ b/homeassistant/components/homekit_controller/manifest.json @@ -3,7 +3,7 @@ "name": "HomeKit Controller", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/homekit_controller", - "requirements": ["aiohomekit==2.4.0"], + "requirements": ["aiohomekit==2.4.1"], "zeroconf": ["_hap._tcp.local.", "_hap._udp.local."], "bluetooth": [{ "manufacturer_id": 76, "manufacturer_data_start": [6] }], "dependencies": ["bluetooth", "zeroconf"], diff --git a/requirements_all.txt b/requirements_all.txt index d36e6951aa1..3e8d0bc4aa3 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -174,7 +174,7 @@ aioguardian==2022.07.0 aioharmony==0.2.9 # homeassistant.components.homekit_controller -aiohomekit==2.4.0 +aiohomekit==2.4.1 # homeassistant.components.emulated_hue # homeassistant.components.http diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 23f7c17a56e..300bea75067 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -158,7 +158,7 @@ aioguardian==2022.07.0 aioharmony==0.2.9 # homeassistant.components.homekit_controller -aiohomekit==2.4.0 +aiohomekit==2.4.1 # homeassistant.components.emulated_hue # homeassistant.components.http From 351bdff5313826d53e6f08d58596ed63088fc406 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk <11290930+bouwew@users.noreply.github.com> Date: Mon, 5 Dec 2022 20:17:19 +0100 Subject: [PATCH 070/174] Update plugwise to v0.25.14, improve number detection (#83345) fixes undefined --- homeassistant/components/plugwise/manifest.json | 2 +- homeassistant/components/plugwise/number.py | 2 +- homeassistant/components/plugwise/sensor.py | 16 ++++++++++++++++ requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- .../fixtures/anna_heatpump_heating/all_data.json | 3 ++- .../m_anna_heatpump_cooling/all_data.json | 11 ++++++----- tests/components/plugwise/test_sensor.py | 4 ++++ 8 files changed, 32 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/plugwise/manifest.json b/homeassistant/components/plugwise/manifest.json index 6a11b47da57..e10d86f2779 100644 --- a/homeassistant/components/plugwise/manifest.json +++ b/homeassistant/components/plugwise/manifest.json @@ -2,7 +2,7 @@ "domain": "plugwise", "name": "Plugwise", "documentation": "https://www.home-assistant.io/integrations/plugwise", - "requirements": ["plugwise==0.25.12"], + "requirements": ["plugwise==0.25.14"], "codeowners": ["@CoMPaTech", "@bouwew", "@brefra", "@frenck"], "zeroconf": ["_plugwise._tcp.local."], "config_flow": true, diff --git a/homeassistant/components/plugwise/number.py b/homeassistant/components/plugwise/number.py index 9100e006968..0b8c7f820b4 100644 --- a/homeassistant/components/plugwise/number.py +++ b/homeassistant/components/plugwise/number.py @@ -71,7 +71,7 @@ async def async_setup_entry( entities: list[PlugwiseNumberEntity] = [] for device_id, device in coordinator.data.devices.items(): for description in NUMBER_TYPES: - if description.key in device: + if description.key in device and "setpoint" in device[description.key]: entities.append( PlugwiseNumberEntity(coordinator, device_id, description) ) diff --git a/homeassistant/components/plugwise/sensor.py b/homeassistant/components/plugwise/sensor.py index b6e2a3aa413..a59eb0cfc39 100644 --- a/homeassistant/components/plugwise/sensor.py +++ b/homeassistant/components/plugwise/sensor.py @@ -301,6 +301,22 @@ SENSORS: tuple[SensorEntityDescription, ...] = ( entity_category=EntityCategory.DIAGNOSTIC, state_class=SensorStateClass.MEASUREMENT, ), + SensorEntityDescription( + key="domestic_hot_water_setpoint", + name="DHW setpoint", + native_unit_of_measurement=TEMP_CELSIUS, + device_class=SensorDeviceClass.TEMPERATURE, + entity_category=EntityCategory.DIAGNOSTIC, + state_class=SensorStateClass.MEASUREMENT, + ), + SensorEntityDescription( + key="maximum_boiler_temperature", + name="Maximum boiler temperature", + native_unit_of_measurement=TEMP_CELSIUS, + device_class=SensorDeviceClass.TEMPERATURE, + entity_category=EntityCategory.DIAGNOSTIC, + state_class=SensorStateClass.MEASUREMENT, + ), ) diff --git a/requirements_all.txt b/requirements_all.txt index 3e8d0bc4aa3..fbc04681462 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1335,7 +1335,7 @@ plexauth==0.0.6 plexwebsocket==0.0.13 # homeassistant.components.plugwise -plugwise==0.25.12 +plugwise==0.25.14 # homeassistant.components.plum_lightpad plumlightpad==0.0.11 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 300bea75067..8133c7080f1 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -962,7 +962,7 @@ plexauth==0.0.6 plexwebsocket==0.0.13 # homeassistant.components.plugwise -plugwise==0.25.12 +plugwise==0.25.14 # homeassistant.components.plum_lightpad plumlightpad==0.0.11 diff --git a/tests/components/plugwise/fixtures/anna_heatpump_heating/all_data.json b/tests/components/plugwise/fixtures/anna_heatpump_heating/all_data.json index 1dc0fb6f2ad..f00293a6554 100644 --- a/tests/components/plugwise/fixtures/anna_heatpump_heating/all_data.json +++ b/tests/components/plugwise/fixtures/anna_heatpump_heating/all_data.json @@ -30,8 +30,9 @@ }, "sensors": { "water_temperature": 29.1, + "domestic_hot_water_setpoint": 60.0, "dhw_temperature": 46.3, - "intended_boiler_temperature": 0.0, + "intended_boiler_temperature": 35.0, "modulation_level": 52, "return_temperature": 25.1, "water_pressure": 1.57, diff --git a/tests/components/plugwise/fixtures/m_anna_heatpump_cooling/all_data.json b/tests/components/plugwise/fixtures/m_anna_heatpump_cooling/all_data.json index cb9e340efb8..ba980a7fce3 100644 --- a/tests/components/plugwise/fixtures/m_anna_heatpump_cooling/all_data.json +++ b/tests/components/plugwise/fixtures/m_anna_heatpump_cooling/all_data.json @@ -30,13 +30,14 @@ "flame_state": false }, "sensors": { - "water_temperature": 29.1, - "dhw_temperature": 46.3, + "water_temperature": 22.7, + "domestic_hot_water_setpoint": 60.0, + "dhw_temperature": 41.5, "intended_boiler_temperature": 0.0, - "modulation_level": 52, - "return_temperature": 25.1, + "modulation_level": 40, + "return_temperature": 23.8, "water_pressure": 1.57, - "outdoor_air_temperature": 3.0 + "outdoor_air_temperature": 28.0 }, "switches": { "dhw_cm_switch": false diff --git a/tests/components/plugwise/test_sensor.py b/tests/components/plugwise/test_sensor.py index 9039c5a476e..b08c2113d80 100644 --- a/tests/components/plugwise/test_sensor.py +++ b/tests/components/plugwise/test_sensor.py @@ -47,6 +47,10 @@ async def test_anna_as_smt_climate_sensor_entities( assert state assert float(state.state) == 29.1 + state = hass.states.get("sensor.opentherm_dhw_setpoint") + assert state + assert float(state.state) == 60.0 + state = hass.states.get("sensor.opentherm_dhw_temperature") assert state assert float(state.state) == 46.3 From 7fc3708f7656e5b75ec82c2df3ff691ff7a09087 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Mon, 5 Dec 2022 23:59:54 +0100 Subject: [PATCH 071/174] Update frontend to 20221205.0 (#83349) --- homeassistant/components/frontend/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/frontend/manifest.json b/homeassistant/components/frontend/manifest.json index 74408788d49..76da0335769 100644 --- a/homeassistant/components/frontend/manifest.json +++ b/homeassistant/components/frontend/manifest.json @@ -2,7 +2,7 @@ "domain": "frontend", "name": "Home Assistant Frontend", "documentation": "https://www.home-assistant.io/integrations/frontend", - "requirements": ["home-assistant-frontend==20221201.1"], + "requirements": ["home-assistant-frontend==20221205.0"], "dependencies": [ "api", "auth", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index b63615111cd..52f37d2451f 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -22,7 +22,7 @@ dbus-fast==1.75.0 fnvhash==0.1.0 hass-nabucasa==0.59.0 home-assistant-bluetooth==1.8.1 -home-assistant-frontend==20221201.1 +home-assistant-frontend==20221205.0 httpx==0.23.1 ifaddr==0.1.7 janus==1.0.0 diff --git a/requirements_all.txt b/requirements_all.txt index fbc04681462..d0e23c811d2 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -884,7 +884,7 @@ hole==0.7.0 holidays==0.17.2 # homeassistant.components.frontend -home-assistant-frontend==20221201.1 +home-assistant-frontend==20221205.0 # homeassistant.components.home_connect homeconnect==0.7.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 8133c7080f1..2b66e19dbeb 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -664,7 +664,7 @@ hole==0.7.0 holidays==0.17.2 # homeassistant.components.frontend -home-assistant-frontend==20221201.1 +home-assistant-frontend==20221205.0 # homeassistant.components.home_connect homeconnect==0.7.2 From afd27792da5fddcd51728599dea81ce909396b9f Mon Sep 17 00:00:00 2001 From: puddly <32534428+puddly@users.noreply.github.com> Date: Mon, 5 Dec 2022 22:34:39 -0500 Subject: [PATCH 072/174] Bump ZHA dependencies (#83350) --- homeassistant/components/zha/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/zha/manifest.json b/homeassistant/components/zha/manifest.json index d617b3474f9..8493d8678a7 100644 --- a/homeassistant/components/zha/manifest.json +++ b/homeassistant/components/zha/manifest.json @@ -9,7 +9,7 @@ "pyserial-asyncio==0.6", "zha-quirks==0.0.87", "zigpy-deconz==0.19.2", - "zigpy==0.52.2", + "zigpy==0.52.3", "zigpy-xbee==0.16.2", "zigpy-zigate==0.10.3", "zigpy-znp==0.9.2" diff --git a/requirements_all.txt b/requirements_all.txt index d0e23c811d2..67c7e1ff56d 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2660,7 +2660,7 @@ zigpy-zigate==0.10.3 zigpy-znp==0.9.2 # homeassistant.components.zha -zigpy==0.52.2 +zigpy==0.52.3 # homeassistant.components.zoneminder zm-py==0.5.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 2b66e19dbeb..8bd3b438994 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1855,7 +1855,7 @@ zigpy-zigate==0.10.3 zigpy-znp==0.9.2 # homeassistant.components.zha -zigpy==0.52.2 +zigpy==0.52.3 # homeassistant.components.zwave_js zwave-js-server-python==0.43.1 From 1f31e621c85cce8ce3ad2d4fef9b0ff3b2f25463 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Tue, 6 Dec 2022 04:33:06 +0100 Subject: [PATCH 073/174] Fix repairing datetime precision for PostgreSQL (#83351) --- homeassistant/components/recorder/migration.py | 2 +- homeassistant/components/recorder/statistics.py | 13 ++++++++++--- tests/components/recorder/test_statistics.py | 16 ++++++++++++++-- 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/recorder/migration.py b/homeassistant/components/recorder/migration.py index cf56c39a885..28952f127e2 100644 --- a/homeassistant/components/recorder/migration.py +++ b/homeassistant/components/recorder/migration.py @@ -161,7 +161,7 @@ def migrate_schema( "Database is about to correct DB schema errors: %s", ", ".join(sorted(schema_errors)), ) - statistics_correct_db_schema(engine, session_maker, schema_errors) + statistics_correct_db_schema(instance, engine, session_maker, schema_errors) def _create_index( diff --git a/homeassistant/components/recorder/statistics.py b/homeassistant/components/recorder/statistics.py index 303a925f9a0..dea454a5a33 100644 --- a/homeassistant/components/recorder/statistics.py +++ b/homeassistant/components/recorder/statistics.py @@ -2404,7 +2404,10 @@ def validate_db_schema( def correct_db_schema( - engine: Engine, session_maker: Callable[[], Session], schema_errors: set[str] + instance: Recorder, + engine: Engine, + session_maker: Callable[[], Session], + schema_errors: set[str], ) -> None: """Correct issues detected by validate_db_schema.""" from .migration import _modify_columns # pylint: disable=import-outside-toplevel @@ -2450,12 +2453,16 @@ def correct_db_schema( ) if f"{table.__tablename__}.µs precision" in schema_errors: # Attempt to convert datetime columns to µs precision + if instance.dialect_name == SupportedDialect.MYSQL: + datetime_type = "DATETIME(6)" + else: + datetime_type = "TIMESTAMP(6) WITH TIME ZONE" _modify_columns( session_maker, engine, table.__tablename__, [ - "last_reset DATETIME(6)", - "start DATETIME(6)", + f"last_reset {datetime_type}", + f"start {datetime_type}", ], ) diff --git a/tests/components/recorder/test_statistics.py b/tests/components/recorder/test_statistics.py index 376087fdb1e..d3a1c8b7fe0 100644 --- a/tests/components/recorder/test_statistics.py +++ b/tests/components/recorder/test_statistics.py @@ -1601,7 +1601,19 @@ async def test_validate_db_schema_fix_float_issue( @pytest.mark.parametrize("enable_statistics_table_validation", [True]) -@pytest.mark.parametrize("db_engine", ("mysql", "postgresql")) +@pytest.mark.parametrize( + "db_engine, modification", + ( + ("mysql", ["last_reset DATETIME(6)", "start DATETIME(6)"]), + ( + "postgresql", + [ + "last_reset TIMESTAMP(6) WITH TIME ZONE", + "start TIMESTAMP(6) WITH TIME ZONE", + ], + ), + ), +) @pytest.mark.parametrize( "table, replace_index", (("statistics", 0), ("statistics_short_term", 1)) ) @@ -1617,6 +1629,7 @@ async def test_validate_db_schema_fix_statistics_datetime_issue( hass, caplog, db_engine, + modification, table, replace_index, column, @@ -1664,7 +1677,6 @@ async def test_validate_db_schema_fix_statistics_datetime_issue( f"Database is about to correct DB schema errors: {table}.µs precision" in caplog.text ) - modification = ["last_reset DATETIME(6)", "start DATETIME(6)"] modify_columns_mock.assert_called_once_with(ANY, ANY, table, modification) From d14655d89188e07716b5f07daf6a4b302041334c Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 5 Dec 2022 22:43:59 -0500 Subject: [PATCH 074/174] Bumped version to 2022.12.0b5 --- homeassistant/const.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 856c0202396..32a0b85ac96 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -8,7 +8,7 @@ from .backports.enum import StrEnum APPLICATION_NAME: Final = "HomeAssistant" MAJOR_VERSION: Final = 2022 MINOR_VERSION: Final = 12 -PATCH_VERSION: Final = "0b4" +PATCH_VERSION: Final = "0b5" __short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__: Final = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 9, 0) diff --git a/pyproject.toml b/pyproject.toml index b423f299811..4b63472ef20 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "homeassistant" -version = "2022.12.0b4" +version = "2022.12.0b5" license = {text = "Apache-2.0"} description = "Open-source home automation platform running on Python 3." readme = "README.rst" From 2b0c0cc6d2669e0e3d7c3579cc1029ffc8c71bf9 Mon Sep 17 00:00:00 2001 From: Marcel van der Veldt Date: Tue, 6 Dec 2022 18:38:13 +0100 Subject: [PATCH 075/174] Replace fixtures for Matter tests (#83328) Co-authored-by: Martin Hjelmare --- tests/components/matter/common.py | 150 +- tests/components/matter/conftest.py | 12 + .../matter/fixtures/nodes/contact-sensor.json | 545 +++ .../matter/fixtures/nodes/dimmable-light.json | 4220 +++++++++++++++++ .../fixtures/nodes/fake-bridge-two-light.json | 174 - .../matter/fixtures/nodes/flow-sensor.json | 518 ++ .../fixtures/nodes/humidity-sensor.json | 507 ++ .../matter/fixtures/nodes/light-sensor.json | 595 +++ .../fixtures/nodes/lighting-example-app.json | 882 ---- .../fixtures/nodes/occupancy-sensor.json | 633 +++ .../fixtures/nodes/on-off-plugin-unit.json | 1055 +++++ .../matter/fixtures/nodes/onoff-light.json | 4216 ++++++++++++++++ .../fixtures/nodes/pressure-sensor.json | 507 ++ .../fixtures/nodes/temperature-sensor.json | 574 +++ tests/components/matter/test_light.py | 86 +- 15 files changed, 13476 insertions(+), 1198 deletions(-) create mode 100644 tests/components/matter/fixtures/nodes/contact-sensor.json create mode 100644 tests/components/matter/fixtures/nodes/dimmable-light.json delete mode 100644 tests/components/matter/fixtures/nodes/fake-bridge-two-light.json create mode 100644 tests/components/matter/fixtures/nodes/flow-sensor.json create mode 100644 tests/components/matter/fixtures/nodes/humidity-sensor.json create mode 100644 tests/components/matter/fixtures/nodes/light-sensor.json delete mode 100644 tests/components/matter/fixtures/nodes/lighting-example-app.json create mode 100644 tests/components/matter/fixtures/nodes/occupancy-sensor.json create mode 100644 tests/components/matter/fixtures/nodes/on-off-plugin-unit.json create mode 100644 tests/components/matter/fixtures/nodes/onoff-light.json create mode 100644 tests/components/matter/fixtures/nodes/pressure-sensor.json create mode 100644 tests/components/matter/fixtures/nodes/temperature-sensor.json diff --git a/tests/components/matter/common.py b/tests/components/matter/common.py index d26842a728f..92a4cd4c8f1 100644 --- a/tests/components/matter/common.py +++ b/tests/components/matter/common.py @@ -1,100 +1,19 @@ """Provide common test tools.""" from __future__ import annotations -import asyncio from functools import cache import json -import logging -from typing import TYPE_CHECKING, Any -from unittest.mock import Mock, patch +from typing import Any +from unittest.mock import MagicMock -from matter_server.client import MatterClient +from matter_server.common.helpers.util import dataclass_from_dict +from matter_server.common.models.events import EventType from matter_server.common.models.node import MatterNode -from matter_server.common.models.server_information import ServerInfo -import pytest + +from homeassistant.core import HomeAssistant from tests.common import MockConfigEntry, load_fixture -if TYPE_CHECKING: - from homeassistant.core import HomeAssistant - -MOCK_FABRIC_ID = 12341234 -MOCK_COMPR_FABRIC_ID = 1234 - -# TEMP: Tests need to be fixed -pytestmark = pytest.mark.skip("all tests still WIP") - - -class MockClient(MatterClient): - """Represent a mock Matter client.""" - - mock_client_disconnect: asyncio.Event - mock_commands: dict[type, Any] = {} - mock_sent_commands: list[dict[str, Any]] = [] - - def __init__(self) -> None: - """Initialize the mock client.""" - super().__init__("mock-url", None) - self.mock_commands: dict[type, Any] = {} - self.mock_sent_commands = [] - self.server_info = ServerInfo( - fabric_id=MOCK_FABRIC_ID, compressed_fabric_id=MOCK_COMPR_FABRIC_ID - ) - - async def connect(self) -> None: - """Connect to the Matter server.""" - self.server_info = Mock(compressed_abric_d=MOCK_COMPR_FABRIC_ID) - - async def listen(self, driver_ready: asyncio.Event) -> None: - """Listen for events.""" - driver_ready.set() - self.mock_client_disconnect = asyncio.Event() - await self.mock_client_disconnect.wait() - - def mock_command(self, command_type: type, response: Any) -> None: - """Mock a command.""" - self.mock_commands[command_type] = response - - async def async_send_command( - self, - command: str, - args: dict[str, Any], - require_schema: int | None = None, - ) -> dict: - """Send mock commands.""" - if command == "device_controller.SendCommand" and ( - (cmd_type := type(args.get("payload"))) in self.mock_commands - ): - self.mock_sent_commands.append(args) - return self.mock_commands[cmd_type] - - return await super().async_send_command(command, args, require_schema) - - async def async_send_command_no_wait( - self, command: str, args: dict[str, Any], require_schema: int | None = None - ) -> None: - """Send a command without waiting for the response.""" - if command == "SendCommand" and ( - (cmd_type := type(args.get("payload"))) in self.mock_commands - ): - self.mock_sent_commands.append(args) - return self.mock_commands[cmd_type] - - return await super().async_send_command_no_wait(command, args, require_schema) - - -@pytest.fixture -async def mock_matter() -> Mock: - """Mock matter fixture.""" - return await get_mock_matter() - - -async def get_mock_matter() -> Mock: - """Get mock Matter.""" - return Mock( - adapter=Mock(logger=logging.getLogger("mock_matter")), client=MockClient() - ) - @cache def load_node_fixture(fixture: str) -> str: @@ -108,39 +27,48 @@ def load_and_parse_node_fixture(fixture: str) -> dict[str, Any]: async def setup_integration_with_node_fixture( - hass: HomeAssistant, hass_storage: dict[str, Any], node_fixture: str + hass: HomeAssistant, + node_fixture: str, + client: MagicMock, ) -> MatterNode: """Set up Matter integration with fixture as node.""" node_data = load_and_parse_node_fixture(node_fixture) - node = MatterNode( - await get_mock_matter(), + node = dataclass_from_dict( + MatterNode, node_data, ) + client.get_nodes.return_value = [node] + client.get_node.return_value = node config_entry = MockConfigEntry( domain="matter", data={"url": "http://mock-matter-server-url"} ) config_entry.add_to_hass(hass) - storage_key = f"matter_{config_entry.entry_id}" - hass_storage[storage_key] = { - "version": 1, - "minor_version": 0, - "key": storage_key, - "data": { - "compressed_fabric_id": MOCK_COMPR_FABRIC_ID, - "next_node_id": 4339, - "nodes": {str(node.node_id): node_data}, - }, - } - - with patch( - "matter_server.client.matter.Client", return_value=node.matter.client - ), patch( - "matter_server.client.model.node.MatterDeviceTypeInstance.subscribe_updates", - ), patch( - "matter_server.client.model.node.MatterDeviceTypeInstance.update_attributes" - ): - assert await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() return node + + +def set_node_attribute( + node: MatterNode, + endpoint: int, + cluster_id: int, + attribute_id: int, + value: Any, +) -> None: + """Set a node attribute.""" + attribute = node.attributes[f"{endpoint}/{cluster_id}/{attribute_id}"] + attribute.value = value + + +async def trigger_subscription_callback( + hass: HomeAssistant, + client: MagicMock, + event: EventType = EventType.ATTRIBUTE_UPDATED, + data: Any = None, +) -> None: + """Trigger a subscription callback.""" + callback = client.subscribe.call_args[0][0] + callback(event, data) + await hass.async_block_till_done() diff --git a/tests/components/matter/conftest.py b/tests/components/matter/conftest.py index 6a8ffd152bc..03c8bc35687 100644 --- a/tests/components/matter/conftest.py +++ b/tests/components/matter/conftest.py @@ -5,12 +5,16 @@ import asyncio from collections.abc import AsyncGenerator, Generator from unittest.mock import AsyncMock, MagicMock, patch +from matter_server.common.models.server_information import ServerInfo import pytest from homeassistant.core import HomeAssistant from tests.common import MockConfigEntry +MOCK_FABRIC_ID = 12341234 +MOCK_COMPR_FABRIC_ID = 1234 + @pytest.fixture(name="matter_client") async def matter_client_fixture() -> AsyncGenerator[MagicMock, None]: @@ -32,6 +36,14 @@ async def matter_client_fixture() -> AsyncGenerator[MagicMock, None]: client.connect = AsyncMock(side_effect=connect) client.start_listening = AsyncMock(side_effect=listen) + client.server_info = ServerInfo( + fabric_id=MOCK_FABRIC_ID, + compressed_fabric_id=MOCK_COMPR_FABRIC_ID, + schema_version=1, + sdk_version="2022.11.1", + wifi_credentials_set=True, + thread_credentials_set=True, + ) yield client diff --git a/tests/components/matter/fixtures/nodes/contact-sensor.json b/tests/components/matter/fixtures/nodes/contact-sensor.json new file mode 100644 index 00000000000..6890b38e8f1 --- /dev/null +++ b/tests/components/matter/fixtures/nodes/contact-sensor.json @@ -0,0 +1,545 @@ +{ + "node_id": 1, + "date_commissioned": "2022-11-29T21:23:48.485051", + "last_interview": "2022-11-29T21:23:48.485057", + "interview_version": 1, + "attributes": { + "0/40/0": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 0, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.DataModelRevision", + "attribute_name": "DataModelRevision", + "value": 1 + }, + "0/40/1": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 1, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.VendorName", + "attribute_name": "VendorName", + "value": "Nabu Casa" + }, + "0/40/2": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 2, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.VendorID", + "attribute_name": "VendorID", + "value": 65521 + }, + "0/40/3": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 3, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.ProductName", + "attribute_name": "ProductName", + "value": "Mock ContactSensor" + }, + "0/40/4": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 4, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.ProductID", + "attribute_name": "ProductID", + "value": 32768 + }, + "0/40/5": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 5, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.NodeLabel", + "attribute_name": "NodeLabel", + "value": "Mock Contact sensor" + }, + "0/40/6": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 6, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.Location", + "attribute_name": "Location", + "value": "XX" + }, + "0/40/7": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 7, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.HardwareVersion", + "attribute_name": "HardwareVersion", + "value": 0 + }, + "0/40/8": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 8, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.HardwareVersionString", + "attribute_name": "HardwareVersionString", + "value": "v1.0" + }, + "0/40/9": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 9, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.SoftwareVersion", + "attribute_name": "SoftwareVersion", + "value": 1 + }, + "0/40/10": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 10, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.SoftwareVersionString", + "attribute_name": "SoftwareVersionString", + "value": "v1.0" + }, + "0/40/11": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 11, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.ManufacturingDate", + "attribute_name": "ManufacturingDate", + "value": "20221206" + }, + "0/40/12": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 12, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.PartNumber", + "attribute_name": "PartNumber", + "value": "" + }, + "0/40/13": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 13, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.ProductURL", + "attribute_name": "ProductURL", + "value": "" + }, + "0/40/14": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 14, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.ProductLabel", + "attribute_name": "ProductLabel", + "value": "" + }, + "0/40/15": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 15, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.SerialNumber", + "attribute_name": "SerialNumber", + "value": "TEST_SN" + }, + "0/40/16": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 16, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.LocalConfigDisabled", + "attribute_name": "LocalConfigDisabled", + "value": false + }, + "0/40/17": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 17, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.Reachable", + "attribute_name": "Reachable", + "value": true + }, + "0/40/18": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 18, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.UniqueID", + "attribute_name": "UniqueID", + "value": "mock-contact-sensor" + }, + "0/40/19": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 19, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.CapabilityMinima", + "attribute_name": "CapabilityMinima", + "value": { + "caseSessionsPerFabric": 3, + "subscriptionsPerFabric": 3 + } + }, + "0/40/65532": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 65532, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.FeatureMap", + "attribute_name": "FeatureMap", + "value": 0 + }, + "0/40/65533": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 65533, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.ClusterRevision", + "attribute_name": "ClusterRevision", + "value": 1 + }, + "0/40/65528": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 65528, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.GeneratedCommandList", + "attribute_name": "GeneratedCommandList", + "value": [] + }, + "0/40/65529": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 65529, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.AcceptedCommandList", + "attribute_name": "AcceptedCommandList", + "value": [] + }, + "0/40/65531": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 65531, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.AttributeList", + "attribute_name": "AttributeList", + "value": [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, + 65528, 65529, 65531, 65532, 65533 + ] + }, + + "1/3/0": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 3, + "cluster_type": "chip.clusters.Objects.Identify", + "cluster_name": "Identify", + "attribute_id": 0, + "attribute_type": "chip.clusters.Objects.Identify.Attributes.IdentifyTime", + "attribute_name": "IdentifyTime", + "value": 0 + }, + "1/3/1": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 3, + "cluster_type": "chip.clusters.Objects.Identify", + "cluster_name": "Identify", + "attribute_id": 1, + "attribute_type": "chip.clusters.Objects.Identify.Attributes.IdentifyType", + "attribute_name": "IdentifyType", + "value": 2 + }, + "1/3/65532": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 3, + "cluster_type": "chip.clusters.Objects.Identify", + "cluster_name": "Identify", + "attribute_id": 65532, + "attribute_type": "chip.clusters.Objects.Identify.Attributes.FeatureMap", + "attribute_name": "FeatureMap", + "value": 0 + }, + "1/3/65533": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 3, + "cluster_type": "chip.clusters.Objects.Identify", + "cluster_name": "Identify", + "attribute_id": 65533, + "attribute_type": "chip.clusters.Objects.Identify.Attributes.ClusterRevision", + "attribute_name": "ClusterRevision", + "value": 4 + }, + "1/3/65528": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 3, + "cluster_type": "chip.clusters.Objects.Identify", + "cluster_name": "Identify", + "attribute_id": 65528, + "attribute_type": "chip.clusters.Objects.Identify.Attributes.GeneratedCommandList", + "attribute_name": "GeneratedCommandList", + "value": [] + }, + "1/3/65529": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 3, + "cluster_type": "chip.clusters.Objects.Identify", + "cluster_name": "Identify", + "attribute_id": 65529, + "attribute_type": "chip.clusters.Objects.Identify.Attributes.AcceptedCommandList", + "attribute_name": "AcceptedCommandList", + "value": [0, 64] + }, + "1/3/65531": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 3, + "cluster_type": "chip.clusters.Objects.Identify", + "cluster_name": "Identify", + "attribute_id": 65531, + "attribute_type": "chip.clusters.Objects.Identify.Attributes.AttributeList", + "attribute_name": "AttributeList", + "value": [0, 1, 65528, 65529, 65531, 65532, 65533] + }, + + "1/29/0": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 0, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.DeviceTypeList", + "attribute_name": "DeviceTypeList", + "value": [ + { + "type": 21, + "revision": 1 + } + ] + }, + "1/29/1": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 1, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.ServerList", + "attribute_name": "ServerList", + "value": [ + 3, 4, 5, 6, 7, 8, 15, 29, 30, 37, 47, 59, 64, 65, 69, 80, 257, 258, 259, + 512, 513, 514, 516, 768, 1024, 1026, 1027, 1028, 1029, 1030, 1283, 1284, + 1285, 1286, 1287, 1288, 1289, 1290, 1291, 1292, 1293, 1294, 2820, + 4294048773 + ] + }, + "1/29/2": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 2, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.ClientList", + "attribute_name": "ClientList", + "value": [] + }, + "1/29/3": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 3, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.PartsList", + "attribute_name": "PartsList", + "value": [] + }, + "1/29/65532": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 65532, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.FeatureMap", + "attribute_name": "FeatureMap", + "value": 0 + }, + "1/29/65533": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 65533, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.ClusterRevision", + "attribute_name": "ClusterRevision", + "value": 1 + }, + "1/29/65528": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 65528, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.GeneratedCommandList", + "attribute_name": "GeneratedCommandList", + "value": [] + }, + "1/29/65529": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 65529, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.AcceptedCommandList", + "attribute_name": "AcceptedCommandList", + "value": [] + }, + "1/29/65531": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 65531, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.AttributeList", + "attribute_name": "AttributeList", + "value": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533] + }, + "1/69/0": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 69, + "cluster_type": "chip.clusters.Objects.BooleanState", + "cluster_name": "BooleanState", + "attribute_id": 0, + "attribute_type": "chip.clusters.Objects.BooleanState.Attributes.StateValue", + "attribute_name": "StateValue", + "value": true + }, + "1/69/65532": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 69, + "cluster_type": "chip.clusters.Objects.BooleanState", + "cluster_name": "BooleanState", + "attribute_id": 65532, + "attribute_type": "chip.clusters.Objects.BooleanState.Attributes.FeatureMap", + "attribute_name": "FeatureMap", + "value": 0 + }, + "1/69/65533": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 69, + "cluster_type": "chip.clusters.Objects.BooleanState", + "cluster_name": "BooleanState", + "attribute_id": 65533, + "attribute_type": "chip.clusters.Objects.BooleanState.Attributes.ClusterRevision", + "attribute_name": "ClusterRevision", + "value": 1 + }, + "1/69/65528": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 69, + "cluster_type": "chip.clusters.Objects.BooleanState", + "cluster_name": "BooleanState", + "attribute_id": 65528, + "attribute_type": "chip.clusters.Objects.BooleanState.Attributes.GeneratedCommandList", + "attribute_name": "GeneratedCommandList", + "value": [] + }, + "1/69/65529": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 69, + "cluster_type": "chip.clusters.Objects.BooleanState", + "cluster_name": "BooleanState", + "attribute_id": 65529, + "attribute_type": "chip.clusters.Objects.BooleanState.Attributes.AcceptedCommandList", + "attribute_name": "AcceptedCommandList", + "value": [] + }, + "1/69/65531": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 69, + "cluster_type": "chip.clusters.Objects.BooleanState", + "cluster_name": "BooleanState", + "attribute_id": 65531, + "attribute_type": "chip.clusters.Objects.BooleanState.Attributes.AttributeList", + "attribute_name": "AttributeList", + "value": [0, 65528, 65529, 65531, 65532, 65533] + } + }, + "endpoints": [0, 1], + "_type": "matter_server.common.models.node.MatterNode" +} diff --git a/tests/components/matter/fixtures/nodes/dimmable-light.json b/tests/components/matter/fixtures/nodes/dimmable-light.json new file mode 100644 index 00000000000..03067468f24 --- /dev/null +++ b/tests/components/matter/fixtures/nodes/dimmable-light.json @@ -0,0 +1,4220 @@ +{ + "node_id": 1, + "date_commissioned": "2022-11-29T21:23:48.485051", + "last_interview": "2022-11-29T21:23:48.485057", + "interview_version": 1, + "attributes": { + "0/4/0": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 4, + "cluster_type": "chip.clusters.Objects.Groups", + "cluster_name": "Groups", + "attribute_id": 0, + "attribute_type": "chip.clusters.Objects.Groups.Attributes.NameSupport", + "attribute_name": "NameSupport", + "value": 128 + }, + "0/4/65532": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 4, + "cluster_type": "chip.clusters.Objects.Groups", + "cluster_name": "Groups", + "attribute_id": 65532, + "attribute_type": "chip.clusters.Objects.Groups.Attributes.FeatureMap", + "attribute_name": "FeatureMap", + "value": 1 + }, + "0/4/65533": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 4, + "cluster_type": "chip.clusters.Objects.Groups", + "cluster_name": "Groups", + "attribute_id": 65533, + "attribute_type": "chip.clusters.Objects.Groups.Attributes.ClusterRevision", + "attribute_name": "ClusterRevision", + "value": 4 + }, + "0/4/65528": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 4, + "cluster_type": "chip.clusters.Objects.Groups", + "cluster_name": "Groups", + "attribute_id": 65528, + "attribute_type": "chip.clusters.Objects.Groups.Attributes.GeneratedCommandList", + "attribute_name": "GeneratedCommandList", + "value": [0, 1, 2, 3] + }, + "0/4/65529": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 4, + "cluster_type": "chip.clusters.Objects.Groups", + "cluster_name": "Groups", + "attribute_id": 65529, + "attribute_type": "chip.clusters.Objects.Groups.Attributes.AcceptedCommandList", + "attribute_name": "AcceptedCommandList", + "value": [0, 1, 2, 3, 4, 5] + }, + "0/4/65531": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 4, + "cluster_type": "chip.clusters.Objects.Groups", + "cluster_name": "Groups", + "attribute_id": 65531, + "attribute_type": "chip.clusters.Objects.Groups.Attributes.AttributeList", + "attribute_name": "AttributeList", + "value": [0, 65528, 65529, 65531, 65532, 65533] + }, + "0/29/0": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 0, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.DeviceTypeList", + "attribute_name": "DeviceTypeList", + "value": [ + { + "type": 22, + "revision": 1 + } + ] + }, + "0/29/1": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 1, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.ServerList", + "attribute_name": "ServerList", + "value": [ + 4, 29, 31, 40, 42, 43, 44, 48, 49, 50, 51, 52, 53, 54, 55, 59, 60, 62, + 63, 64, 65 + ] + }, + "0/29/2": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 2, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.ClientList", + "attribute_name": "ClientList", + "value": [41] + }, + "0/29/3": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 3, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.PartsList", + "attribute_name": "PartsList", + "value": [1] + }, + "0/29/65532": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 65532, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.FeatureMap", + "attribute_name": "FeatureMap", + "value": 0 + }, + "0/29/65533": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 65533, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.ClusterRevision", + "attribute_name": "ClusterRevision", + "value": 1 + }, + "0/29/65528": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 65528, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.GeneratedCommandList", + "attribute_name": "GeneratedCommandList", + "value": [] + }, + "0/29/65529": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 65529, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.AcceptedCommandList", + "attribute_name": "AcceptedCommandList", + "value": [] + }, + "0/29/65531": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 65531, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.AttributeList", + "attribute_name": "AttributeList", + "value": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533] + }, + "0/31/0": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 31, + "cluster_type": "chip.clusters.Objects.AccessControl", + "cluster_name": "AccessControl", + "attribute_id": 0, + "attribute_type": "chip.clusters.Objects.AccessControl.Attributes.Acl", + "attribute_name": "Acl", + "value": [ + { + "privilege": 5, + "authMode": 2, + "subjects": [112233], + "targets": null, + "fabricIndex": 1 + } + ] + }, + "0/31/1": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 31, + "cluster_type": "chip.clusters.Objects.AccessControl", + "cluster_name": "AccessControl", + "attribute_id": 1, + "attribute_type": "chip.clusters.Objects.AccessControl.Attributes.Extension", + "attribute_name": "Extension", + "value": [] + }, + "0/31/2": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 31, + "cluster_type": "chip.clusters.Objects.AccessControl", + "cluster_name": "AccessControl", + "attribute_id": 2, + "attribute_type": "chip.clusters.Objects.AccessControl.Attributes.SubjectsPerAccessControlEntry", + "attribute_name": "SubjectsPerAccessControlEntry", + "value": 4 + }, + "0/31/3": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 31, + "cluster_type": "chip.clusters.Objects.AccessControl", + "cluster_name": "AccessControl", + "attribute_id": 3, + "attribute_type": "chip.clusters.Objects.AccessControl.Attributes.TargetsPerAccessControlEntry", + "attribute_name": "TargetsPerAccessControlEntry", + "value": 3 + }, + "0/31/4": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 31, + "cluster_type": "chip.clusters.Objects.AccessControl", + "cluster_name": "AccessControl", + "attribute_id": 4, + "attribute_type": "chip.clusters.Objects.AccessControl.Attributes.AccessControlEntriesPerFabric", + "attribute_name": "AccessControlEntriesPerFabric", + "value": 3 + }, + "0/31/65532": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 31, + "cluster_type": "chip.clusters.Objects.AccessControl", + "cluster_name": "AccessControl", + "attribute_id": 65532, + "attribute_type": "chip.clusters.Objects.AccessControl.Attributes.FeatureMap", + "attribute_name": "FeatureMap", + "value": 0 + }, + "0/31/65533": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 31, + "cluster_type": "chip.clusters.Objects.AccessControl", + "cluster_name": "AccessControl", + "attribute_id": 65533, + "attribute_type": "chip.clusters.Objects.AccessControl.Attributes.ClusterRevision", + "attribute_name": "ClusterRevision", + "value": 1 + }, + "0/31/65528": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 31, + "cluster_type": "chip.clusters.Objects.AccessControl", + "cluster_name": "AccessControl", + "attribute_id": 65528, + "attribute_type": "chip.clusters.Objects.AccessControl.Attributes.GeneratedCommandList", + "attribute_name": "GeneratedCommandList", + "value": [] + }, + "0/31/65529": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 31, + "cluster_type": "chip.clusters.Objects.AccessControl", + "cluster_name": "AccessControl", + "attribute_id": 65529, + "attribute_type": "chip.clusters.Objects.AccessControl.Attributes.AcceptedCommandList", + "attribute_name": "AcceptedCommandList", + "value": [] + }, + "0/31/65531": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 31, + "cluster_type": "chip.clusters.Objects.AccessControl", + "cluster_name": "AccessControl", + "attribute_id": 65531, + "attribute_type": "chip.clusters.Objects.AccessControl.Attributes.AttributeList", + "attribute_name": "AttributeList", + "value": [0, 1, 2, 3, 4, 65528, 65529, 65531, 65532, 65533] + }, + "0/40/0": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 0, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.DataModelRevision", + "attribute_name": "DataModelRevision", + "value": 1 + }, + "0/40/1": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 1, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.VendorName", + "attribute_name": "VendorName", + "value": "Nabu Casa" + }, + "0/40/2": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 2, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.VendorID", + "attribute_name": "VendorID", + "value": 65521 + }, + "0/40/3": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 3, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.ProductName", + "attribute_name": "ProductName", + "value": "Mock Dimmable Light" + }, + "0/40/4": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 4, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.ProductID", + "attribute_name": "ProductID", + "value": 32768 + }, + "0/40/5": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 5, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.NodeLabel", + "attribute_name": "NodeLabel", + "value": "Mock Dimmable Light" + }, + "0/40/6": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 6, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.Location", + "attribute_name": "Location", + "value": "XX" + }, + "0/40/7": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 7, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.HardwareVersion", + "attribute_name": "HardwareVersion", + "value": 0 + }, + "0/40/8": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 8, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.HardwareVersionString", + "attribute_name": "HardwareVersionString", + "value": "v1.0" + }, + "0/40/9": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 9, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.SoftwareVersion", + "attribute_name": "SoftwareVersion", + "value": 1 + }, + "0/40/10": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 10, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.SoftwareVersionString", + "attribute_name": "SoftwareVersionString", + "value": "v1.0" + }, + "0/40/11": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 11, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.ManufacturingDate", + "attribute_name": "ManufacturingDate", + "value": "20200101" + }, + "0/40/12": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 12, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.PartNumber", + "attribute_name": "PartNumber", + "value": "" + }, + "0/40/13": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 13, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.ProductURL", + "attribute_name": "ProductURL", + "value": "" + }, + "0/40/14": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 14, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.ProductLabel", + "attribute_name": "ProductLabel", + "value": "" + }, + "0/40/15": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 15, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.SerialNumber", + "attribute_name": "SerialNumber", + "value": "TEST_SN" + }, + "0/40/16": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 16, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.LocalConfigDisabled", + "attribute_name": "LocalConfigDisabled", + "value": false + }, + "0/40/17": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 17, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.Reachable", + "attribute_name": "Reachable", + "value": true + }, + "0/40/18": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 18, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.UniqueID", + "attribute_name": "UniqueID", + "value": "mock-dimmable-light" + }, + "0/40/19": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 19, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.CapabilityMinima", + "attribute_name": "CapabilityMinima", + "value": { + "caseSessionsPerFabric": 3, + "subscriptionsPerFabric": 3 + } + }, + "0/40/65532": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 65532, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.FeatureMap", + "attribute_name": "FeatureMap", + "value": 0 + }, + "0/40/65533": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 65533, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.ClusterRevision", + "attribute_name": "ClusterRevision", + "value": 1 + }, + "0/40/65528": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 65528, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.GeneratedCommandList", + "attribute_name": "GeneratedCommandList", + "value": [] + }, + "0/40/65529": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 65529, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.AcceptedCommandList", + "attribute_name": "AcceptedCommandList", + "value": [] + }, + "0/40/65531": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 65531, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.AttributeList", + "attribute_name": "AttributeList", + "value": [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, + 65528, 65529, 65531, 65532, 65533 + ] + }, + "0/42/0": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 42, + "cluster_type": "chip.clusters.Objects.OtaSoftwareUpdateRequestor", + "cluster_name": "OtaSoftwareUpdateRequestor", + "attribute_id": 0, + "attribute_type": "chip.clusters.Objects.OtaSoftwareUpdateRequestor.Attributes.DefaultOtaProviders", + "attribute_name": "DefaultOtaProviders", + "value": [] + }, + "0/42/1": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 42, + "cluster_type": "chip.clusters.Objects.OtaSoftwareUpdateRequestor", + "cluster_name": "OtaSoftwareUpdateRequestor", + "attribute_id": 1, + "attribute_type": "chip.clusters.Objects.OtaSoftwareUpdateRequestor.Attributes.UpdatePossible", + "attribute_name": "UpdatePossible", + "value": true + }, + "0/42/2": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 42, + "cluster_type": "chip.clusters.Objects.OtaSoftwareUpdateRequestor", + "cluster_name": "OtaSoftwareUpdateRequestor", + "attribute_id": 2, + "attribute_type": "chip.clusters.Objects.OtaSoftwareUpdateRequestor.Attributes.UpdateState", + "attribute_name": "UpdateState", + "value": 0 + }, + "0/42/3": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 42, + "cluster_type": "chip.clusters.Objects.OtaSoftwareUpdateRequestor", + "cluster_name": "OtaSoftwareUpdateRequestor", + "attribute_id": 3, + "attribute_type": "chip.clusters.Objects.OtaSoftwareUpdateRequestor.Attributes.UpdateStateProgress", + "attribute_name": "UpdateStateProgress", + "value": 0 + }, + "0/42/65532": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 42, + "cluster_type": "chip.clusters.Objects.OtaSoftwareUpdateRequestor", + "cluster_name": "OtaSoftwareUpdateRequestor", + "attribute_id": 65532, + "attribute_type": "chip.clusters.Objects.OtaSoftwareUpdateRequestor.Attributes.FeatureMap", + "attribute_name": "FeatureMap", + "value": 0 + }, + "0/42/65533": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 42, + "cluster_type": "chip.clusters.Objects.OtaSoftwareUpdateRequestor", + "cluster_name": "OtaSoftwareUpdateRequestor", + "attribute_id": 65533, + "attribute_type": "chip.clusters.Objects.OtaSoftwareUpdateRequestor.Attributes.ClusterRevision", + "attribute_name": "ClusterRevision", + "value": 1 + }, + "0/42/65528": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 42, + "cluster_type": "chip.clusters.Objects.OtaSoftwareUpdateRequestor", + "cluster_name": "OtaSoftwareUpdateRequestor", + "attribute_id": 65528, + "attribute_type": "chip.clusters.Objects.OtaSoftwareUpdateRequestor.Attributes.GeneratedCommandList", + "attribute_name": "GeneratedCommandList", + "value": [] + }, + "0/42/65529": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 42, + "cluster_type": "chip.clusters.Objects.OtaSoftwareUpdateRequestor", + "cluster_name": "OtaSoftwareUpdateRequestor", + "attribute_id": 65529, + "attribute_type": "chip.clusters.Objects.OtaSoftwareUpdateRequestor.Attributes.AcceptedCommandList", + "attribute_name": "AcceptedCommandList", + "value": [0] + }, + "0/42/65531": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 42, + "cluster_type": "chip.clusters.Objects.OtaSoftwareUpdateRequestor", + "cluster_name": "OtaSoftwareUpdateRequestor", + "attribute_id": 65531, + "attribute_type": "chip.clusters.Objects.OtaSoftwareUpdateRequestor.Attributes.AttributeList", + "attribute_name": "AttributeList", + "value": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533] + }, + "0/43/0": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 43, + "cluster_type": "chip.clusters.Objects.LocalizationConfiguration", + "cluster_name": "LocalizationConfiguration", + "attribute_id": 0, + "attribute_type": "chip.clusters.Objects.LocalizationConfiguration.Attributes.ActiveLocale", + "attribute_name": "ActiveLocale", + "value": "en-US" + }, + "0/43/1": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 43, + "cluster_type": "chip.clusters.Objects.LocalizationConfiguration", + "cluster_name": "LocalizationConfiguration", + "attribute_id": 1, + "attribute_type": "chip.clusters.Objects.LocalizationConfiguration.Attributes.SupportedLocales", + "attribute_name": "SupportedLocales", + "value": [ + "en-US", + "de-DE", + "fr-FR", + "en-GB", + "es-ES", + "zh-CN", + "it-IT", + "ja-JP" + ] + }, + "0/43/65532": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 43, + "cluster_type": "chip.clusters.Objects.LocalizationConfiguration", + "cluster_name": "LocalizationConfiguration", + "attribute_id": 65532, + "attribute_type": "chip.clusters.Objects.LocalizationConfiguration.Attributes.FeatureMap", + "attribute_name": "FeatureMap", + "value": 0 + }, + "0/43/65533": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 43, + "cluster_type": "chip.clusters.Objects.LocalizationConfiguration", + "cluster_name": "LocalizationConfiguration", + "attribute_id": 65533, + "attribute_type": "chip.clusters.Objects.LocalizationConfiguration.Attributes.ClusterRevision", + "attribute_name": "ClusterRevision", + "value": 1 + }, + "0/43/65528": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 43, + "cluster_type": "chip.clusters.Objects.LocalizationConfiguration", + "cluster_name": "LocalizationConfiguration", + "attribute_id": 65528, + "attribute_type": "chip.clusters.Objects.LocalizationConfiguration.Attributes.GeneratedCommandList", + "attribute_name": "GeneratedCommandList", + "value": [] + }, + "0/43/65529": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 43, + "cluster_type": "chip.clusters.Objects.LocalizationConfiguration", + "cluster_name": "LocalizationConfiguration", + "attribute_id": 65529, + "attribute_type": "chip.clusters.Objects.LocalizationConfiguration.Attributes.AcceptedCommandList", + "attribute_name": "AcceptedCommandList", + "value": [] + }, + "0/43/65531": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 43, + "cluster_type": "chip.clusters.Objects.LocalizationConfiguration", + "cluster_name": "LocalizationConfiguration", + "attribute_id": 65531, + "attribute_type": "chip.clusters.Objects.LocalizationConfiguration.Attributes.AttributeList", + "attribute_name": "AttributeList", + "value": [0, 1, 65528, 65529, 65531, 65532, 65533] + }, + "0/44/0": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 44, + "cluster_type": "chip.clusters.Objects.TimeFormatLocalization", + "cluster_name": "TimeFormatLocalization", + "attribute_id": 0, + "attribute_type": "chip.clusters.Objects.TimeFormatLocalization.Attributes.HourFormat", + "attribute_name": "HourFormat", + "value": 0 + }, + "0/44/1": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 44, + "cluster_type": "chip.clusters.Objects.TimeFormatLocalization", + "cluster_name": "TimeFormatLocalization", + "attribute_id": 1, + "attribute_type": "chip.clusters.Objects.TimeFormatLocalization.Attributes.ActiveCalendarType", + "attribute_name": "ActiveCalendarType", + "value": 0 + }, + "0/44/2": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 44, + "cluster_type": "chip.clusters.Objects.TimeFormatLocalization", + "cluster_name": "TimeFormatLocalization", + "attribute_id": 2, + "attribute_type": "chip.clusters.Objects.TimeFormatLocalization.Attributes.SupportedCalendarTypes", + "attribute_name": "SupportedCalendarTypes", + "value": [0, 1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 7] + }, + "0/44/65532": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 44, + "cluster_type": "chip.clusters.Objects.TimeFormatLocalization", + "cluster_name": "TimeFormatLocalization", + "attribute_id": 65532, + "attribute_type": "chip.clusters.Objects.TimeFormatLocalization.Attributes.FeatureMap", + "attribute_name": "FeatureMap", + "value": 0 + }, + "0/44/65533": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 44, + "cluster_type": "chip.clusters.Objects.TimeFormatLocalization", + "cluster_name": "TimeFormatLocalization", + "attribute_id": 65533, + "attribute_type": "chip.clusters.Objects.TimeFormatLocalization.Attributes.ClusterRevision", + "attribute_name": "ClusterRevision", + "value": 1 + }, + "0/44/65528": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 44, + "cluster_type": "chip.clusters.Objects.TimeFormatLocalization", + "cluster_name": "TimeFormatLocalization", + "attribute_id": 65528, + "attribute_type": "chip.clusters.Objects.TimeFormatLocalization.Attributes.GeneratedCommandList", + "attribute_name": "GeneratedCommandList", + "value": [] + }, + "0/44/65529": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 44, + "cluster_type": "chip.clusters.Objects.TimeFormatLocalization", + "cluster_name": "TimeFormatLocalization", + "attribute_id": 65529, + "attribute_type": "chip.clusters.Objects.TimeFormatLocalization.Attributes.AcceptedCommandList", + "attribute_name": "AcceptedCommandList", + "value": [] + }, + "0/44/65531": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 44, + "cluster_type": "chip.clusters.Objects.TimeFormatLocalization", + "cluster_name": "TimeFormatLocalization", + "attribute_id": 65531, + "attribute_type": "chip.clusters.Objects.TimeFormatLocalization.Attributes.AttributeList", + "attribute_name": "AttributeList", + "value": [0, 1, 2, 65528, 65529, 65531, 65532, 65533] + }, + "0/48/0": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 48, + "cluster_type": "chip.clusters.Objects.GeneralCommissioning", + "cluster_name": "GeneralCommissioning", + "attribute_id": 0, + "attribute_type": "chip.clusters.Objects.GeneralCommissioning.Attributes.Breadcrumb", + "attribute_name": "Breadcrumb", + "value": 0 + }, + "0/48/1": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 48, + "cluster_type": "chip.clusters.Objects.GeneralCommissioning", + "cluster_name": "GeneralCommissioning", + "attribute_id": 1, + "attribute_type": "chip.clusters.Objects.GeneralCommissioning.Attributes.BasicCommissioningInfo", + "attribute_name": "BasicCommissioningInfo", + "value": { + "failSafeExpiryLengthSeconds": 60, + "maxCumulativeFailsafeSeconds": 900 + } + }, + "0/48/2": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 48, + "cluster_type": "chip.clusters.Objects.GeneralCommissioning", + "cluster_name": "GeneralCommissioning", + "attribute_id": 2, + "attribute_type": "chip.clusters.Objects.GeneralCommissioning.Attributes.RegulatoryConfig", + "attribute_name": "RegulatoryConfig", + "value": 0 + }, + "0/48/3": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 48, + "cluster_type": "chip.clusters.Objects.GeneralCommissioning", + "cluster_name": "GeneralCommissioning", + "attribute_id": 3, + "attribute_type": "chip.clusters.Objects.GeneralCommissioning.Attributes.LocationCapability", + "attribute_name": "LocationCapability", + "value": 0 + }, + "0/48/4": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 48, + "cluster_type": "chip.clusters.Objects.GeneralCommissioning", + "cluster_name": "GeneralCommissioning", + "attribute_id": 4, + "attribute_type": "chip.clusters.Objects.GeneralCommissioning.Attributes.SupportsConcurrentConnection", + "attribute_name": "SupportsConcurrentConnection", + "value": true + }, + "0/48/65532": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 48, + "cluster_type": "chip.clusters.Objects.GeneralCommissioning", + "cluster_name": "GeneralCommissioning", + "attribute_id": 65532, + "attribute_type": "chip.clusters.Objects.GeneralCommissioning.Attributes.FeatureMap", + "attribute_name": "FeatureMap", + "value": 0 + }, + "0/48/65533": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 48, + "cluster_type": "chip.clusters.Objects.GeneralCommissioning", + "cluster_name": "GeneralCommissioning", + "attribute_id": 65533, + "attribute_type": "chip.clusters.Objects.GeneralCommissioning.Attributes.ClusterRevision", + "attribute_name": "ClusterRevision", + "value": 1 + }, + "0/48/65528": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 48, + "cluster_type": "chip.clusters.Objects.GeneralCommissioning", + "cluster_name": "GeneralCommissioning", + "attribute_id": 65528, + "attribute_type": "chip.clusters.Objects.GeneralCommissioning.Attributes.GeneratedCommandList", + "attribute_name": "GeneratedCommandList", + "value": [1, 3, 5] + }, + "0/48/65529": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 48, + "cluster_type": "chip.clusters.Objects.GeneralCommissioning", + "cluster_name": "GeneralCommissioning", + "attribute_id": 65529, + "attribute_type": "chip.clusters.Objects.GeneralCommissioning.Attributes.AcceptedCommandList", + "attribute_name": "AcceptedCommandList", + "value": [0, 2, 4] + }, + "0/48/65531": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 48, + "cluster_type": "chip.clusters.Objects.GeneralCommissioning", + "cluster_name": "GeneralCommissioning", + "attribute_id": 65531, + "attribute_type": "chip.clusters.Objects.GeneralCommissioning.Attributes.AttributeList", + "attribute_name": "AttributeList", + "value": [0, 1, 2, 3, 4, 65528, 65529, 65531, 65532, 65533] + }, + "0/49/0": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 49, + "cluster_type": "chip.clusters.Objects.NetworkCommissioning", + "cluster_name": "NetworkCommissioning", + "attribute_id": 0, + "attribute_type": "chip.clusters.Objects.NetworkCommissioning.Attributes.MaxNetworks", + "attribute_name": "MaxNetworks", + "value": 1 + }, + "0/49/1": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 49, + "cluster_type": "chip.clusters.Objects.NetworkCommissioning", + "cluster_name": "NetworkCommissioning", + "attribute_id": 1, + "attribute_type": "chip.clusters.Objects.NetworkCommissioning.Attributes.Networks", + "attribute_name": "Networks", + "value": [ + { + "networkID": "b'wifi-sm'", + "connected": true + } + ] + }, + "0/49/2": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 49, + "cluster_type": "chip.clusters.Objects.NetworkCommissioning", + "cluster_name": "NetworkCommissioning", + "attribute_id": 2, + "attribute_type": "chip.clusters.Objects.NetworkCommissioning.Attributes.ScanMaxTimeSeconds", + "attribute_name": "ScanMaxTimeSeconds", + "value": 10 + }, + "0/49/3": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 49, + "cluster_type": "chip.clusters.Objects.NetworkCommissioning", + "cluster_name": "NetworkCommissioning", + "attribute_id": 3, + "attribute_type": "chip.clusters.Objects.NetworkCommissioning.Attributes.ConnectMaxTimeSeconds", + "attribute_name": "ConnectMaxTimeSeconds", + "value": 30 + }, + "0/49/4": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 49, + "cluster_type": "chip.clusters.Objects.NetworkCommissioning", + "cluster_name": "NetworkCommissioning", + "attribute_id": 4, + "attribute_type": "chip.clusters.Objects.NetworkCommissioning.Attributes.InterfaceEnabled", + "attribute_name": "InterfaceEnabled", + "value": true + }, + "0/49/5": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 49, + "cluster_type": "chip.clusters.Objects.NetworkCommissioning", + "cluster_name": "NetworkCommissioning", + "attribute_id": 5, + "attribute_type": "chip.clusters.Objects.NetworkCommissioning.Attributes.LastNetworkingStatus", + "attribute_name": "LastNetworkingStatus", + "value": 0 + }, + "0/49/6": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 49, + "cluster_type": "chip.clusters.Objects.NetworkCommissioning", + "cluster_name": "NetworkCommissioning", + "attribute_id": 6, + "attribute_type": "chip.clusters.Objects.NetworkCommissioning.Attributes.LastNetworkID", + "attribute_name": "LastNetworkID", + "value": "b'wifi-sm'" + }, + "0/49/7": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 49, + "cluster_type": "chip.clusters.Objects.NetworkCommissioning", + "cluster_name": "NetworkCommissioning", + "attribute_id": 7, + "attribute_type": "chip.clusters.Objects.NetworkCommissioning.Attributes.LastConnectErrorValue", + "attribute_name": "LastConnectErrorValue", + "value": null + }, + "0/49/65532": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 49, + "cluster_type": "chip.clusters.Objects.NetworkCommissioning", + "cluster_name": "NetworkCommissioning", + "attribute_id": 65532, + "attribute_type": "chip.clusters.Objects.NetworkCommissioning.Attributes.FeatureMap", + "attribute_name": "FeatureMap", + "value": 1 + }, + "0/49/65533": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 49, + "cluster_type": "chip.clusters.Objects.NetworkCommissioning", + "cluster_name": "NetworkCommissioning", + "attribute_id": 65533, + "attribute_type": "chip.clusters.Objects.NetworkCommissioning.Attributes.ClusterRevision", + "attribute_name": "ClusterRevision", + "value": 1 + }, + "0/49/65528": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 49, + "cluster_type": "chip.clusters.Objects.NetworkCommissioning", + "cluster_name": "NetworkCommissioning", + "attribute_id": 65528, + "attribute_type": "chip.clusters.Objects.NetworkCommissioning.Attributes.GeneratedCommandList", + "attribute_name": "GeneratedCommandList", + "value": [1, 5, 7] + }, + "0/49/65529": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 49, + "cluster_type": "chip.clusters.Objects.NetworkCommissioning", + "cluster_name": "NetworkCommissioning", + "attribute_id": 65529, + "attribute_type": "chip.clusters.Objects.NetworkCommissioning.Attributes.AcceptedCommandList", + "attribute_name": "AcceptedCommandList", + "value": [0, 2, 4, 6, 8] + }, + "0/49/65531": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 49, + "cluster_type": "chip.clusters.Objects.NetworkCommissioning", + "cluster_name": "NetworkCommissioning", + "attribute_id": 65531, + "attribute_type": "chip.clusters.Objects.NetworkCommissioning.Attributes.AttributeList", + "attribute_name": "AttributeList", + "value": [0, 1, 2, 3, 4, 5, 6, 7, 65528, 65529, 65531, 65532, 65533] + }, + "0/50/65532": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 50, + "cluster_type": "chip.clusters.Objects.DiagnosticLogs", + "cluster_name": "DiagnosticLogs", + "attribute_id": 65532, + "attribute_type": "chip.clusters.Objects.DiagnosticLogs.Attributes.FeatureMap", + "attribute_name": "FeatureMap", + "value": 0 + }, + "0/50/65533": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 50, + "cluster_type": "chip.clusters.Objects.DiagnosticLogs", + "cluster_name": "DiagnosticLogs", + "attribute_id": 65533, + "attribute_type": "chip.clusters.Objects.DiagnosticLogs.Attributes.ClusterRevision", + "attribute_name": "ClusterRevision", + "value": 1 + }, + "0/50/65528": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 50, + "cluster_type": "chip.clusters.Objects.DiagnosticLogs", + "cluster_name": "DiagnosticLogs", + "attribute_id": 65528, + "attribute_type": "chip.clusters.Objects.DiagnosticLogs.Attributes.GeneratedCommandList", + "attribute_name": "GeneratedCommandList", + "value": [1] + }, + "0/50/65529": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 50, + "cluster_type": "chip.clusters.Objects.DiagnosticLogs", + "cluster_name": "DiagnosticLogs", + "attribute_id": 65529, + "attribute_type": "chip.clusters.Objects.DiagnosticLogs.Attributes.AcceptedCommandList", + "attribute_name": "AcceptedCommandList", + "value": [0] + }, + "0/50/65531": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 50, + "cluster_type": "chip.clusters.Objects.DiagnosticLogs", + "cluster_name": "DiagnosticLogs", + "attribute_id": 65531, + "attribute_type": "chip.clusters.Objects.DiagnosticLogs.Attributes.AttributeList", + "attribute_name": "AttributeList", + "value": [65528, 65529, 65531, 65532, 65533] + }, + "0/51/0": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 51, + "cluster_type": "chip.clusters.Objects.GeneralDiagnostics", + "cluster_name": "GeneralDiagnostics", + "attribute_id": 0, + "attribute_type": "chip.clusters.Objects.GeneralDiagnostics.Attributes.NetworkInterfaces", + "attribute_name": "NetworkInterfaces", + "value": [ + { + "name": "WIFI_STA_DEF", + "isOperational": true, + "offPremiseServicesReachableIPv4": null, + "offPremiseServicesReachableIPv6": null, + "hardwareAddress": "b\"\\x84\\xf7\\x03'\\xb5,\"", + "IPv4Addresses": ["b'\\xc0\\xa8\\x01\\x84'"], + "IPv6Addresses": [ + "b\"\\xfe\\x80\\x00\\x00\\x00\\x00\\x00\\x00\\x86\\xf7\\x03\\xff\\xfe'\\xb5,\"", + "b\"*\\x00\\xbb\\xa0\\x12\\x12\\x8f\\x00\\x86\\xf7\\x03\\xff\\xfe'\\xb5,\"", + "b\"\\xfd\\xf5\\nn\\xf0cO\\xed\\x86\\xf7\\x03\\xff\\xfe'\\xb5,\"" + ], + "type": 1 + } + ] + }, + "0/51/1": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 51, + "cluster_type": "chip.clusters.Objects.GeneralDiagnostics", + "cluster_name": "GeneralDiagnostics", + "attribute_id": 1, + "attribute_type": "chip.clusters.Objects.GeneralDiagnostics.Attributes.RebootCount", + "attribute_name": "RebootCount", + "value": 6 + }, + "0/51/2": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 51, + "cluster_type": "chip.clusters.Objects.GeneralDiagnostics", + "cluster_name": "GeneralDiagnostics", + "attribute_id": 2, + "attribute_type": "chip.clusters.Objects.GeneralDiagnostics.Attributes.UpTime", + "attribute_name": "UpTime", + "value": 31279 + }, + "0/51/3": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 51, + "cluster_type": "chip.clusters.Objects.GeneralDiagnostics", + "cluster_name": "GeneralDiagnostics", + "attribute_id": 3, + "attribute_type": "chip.clusters.Objects.GeneralDiagnostics.Attributes.TotalOperationalHours", + "attribute_name": "TotalOperationalHours", + "value": 8 + }, + "0/51/4": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 51, + "cluster_type": "chip.clusters.Objects.GeneralDiagnostics", + "cluster_name": "GeneralDiagnostics", + "attribute_id": 4, + "attribute_type": "chip.clusters.Objects.GeneralDiagnostics.Attributes.BootReasons", + "attribute_name": "BootReasons", + "value": 1 + }, + "0/51/5": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 51, + "cluster_type": "chip.clusters.Objects.GeneralDiagnostics", + "cluster_name": "GeneralDiagnostics", + "attribute_id": 5, + "attribute_type": "chip.clusters.Objects.GeneralDiagnostics.Attributes.ActiveHardwareFaults", + "attribute_name": "ActiveHardwareFaults", + "value": [] + }, + "0/51/6": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 51, + "cluster_type": "chip.clusters.Objects.GeneralDiagnostics", + "cluster_name": "GeneralDiagnostics", + "attribute_id": 6, + "attribute_type": "chip.clusters.Objects.GeneralDiagnostics.Attributes.ActiveRadioFaults", + "attribute_name": "ActiveRadioFaults", + "value": [] + }, + "0/51/7": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 51, + "cluster_type": "chip.clusters.Objects.GeneralDiagnostics", + "cluster_name": "GeneralDiagnostics", + "attribute_id": 7, + "attribute_type": "chip.clusters.Objects.GeneralDiagnostics.Attributes.ActiveNetworkFaults", + "attribute_name": "ActiveNetworkFaults", + "value": [] + }, + "0/51/8": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 51, + "cluster_type": "chip.clusters.Objects.GeneralDiagnostics", + "cluster_name": "GeneralDiagnostics", + "attribute_id": 8, + "attribute_type": "chip.clusters.Objects.GeneralDiagnostics.Attributes.TestEventTriggersEnabled", + "attribute_name": "TestEventTriggersEnabled", + "value": false + }, + "0/51/65532": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 51, + "cluster_type": "chip.clusters.Objects.GeneralDiagnostics", + "cluster_name": "GeneralDiagnostics", + "attribute_id": 65532, + "attribute_type": "chip.clusters.Objects.GeneralDiagnostics.Attributes.FeatureMap", + "attribute_name": "FeatureMap", + "value": 0 + }, + "0/51/65533": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 51, + "cluster_type": "chip.clusters.Objects.GeneralDiagnostics", + "cluster_name": "GeneralDiagnostics", + "attribute_id": 65533, + "attribute_type": "chip.clusters.Objects.GeneralDiagnostics.Attributes.ClusterRevision", + "attribute_name": "ClusterRevision", + "value": 1 + }, + "0/51/65528": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 51, + "cluster_type": "chip.clusters.Objects.GeneralDiagnostics", + "cluster_name": "GeneralDiagnostics", + "attribute_id": 65528, + "attribute_type": "chip.clusters.Objects.GeneralDiagnostics.Attributes.GeneratedCommandList", + "attribute_name": "GeneratedCommandList", + "value": [] + }, + "0/51/65529": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 51, + "cluster_type": "chip.clusters.Objects.GeneralDiagnostics", + "cluster_name": "GeneralDiagnostics", + "attribute_id": 65529, + "attribute_type": "chip.clusters.Objects.GeneralDiagnostics.Attributes.AcceptedCommandList", + "attribute_name": "AcceptedCommandList", + "value": [0] + }, + "0/51/65531": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 51, + "cluster_type": "chip.clusters.Objects.GeneralDiagnostics", + "cluster_name": "GeneralDiagnostics", + "attribute_id": 65531, + "attribute_type": "chip.clusters.Objects.GeneralDiagnostics.Attributes.AttributeList", + "attribute_name": "AttributeList", + "value": [0, 1, 2, 3, 4, 5, 6, 7, 8, 65528, 65529, 65531, 65532, 65533] + }, + "0/52/0": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 52, + "cluster_type": "chip.clusters.Objects.SoftwareDiagnostics", + "cluster_name": "SoftwareDiagnostics", + "attribute_id": 0, + "attribute_type": "chip.clusters.Objects.SoftwareDiagnostics.Attributes.ThreadMetrics", + "attribute_name": "ThreadMetrics", + "value": [] + }, + "0/52/1": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 52, + "cluster_type": "chip.clusters.Objects.SoftwareDiagnostics", + "cluster_name": "SoftwareDiagnostics", + "attribute_id": 1, + "attribute_type": "chip.clusters.Objects.SoftwareDiagnostics.Attributes.CurrentHeapFree", + "attribute_name": "CurrentHeapFree", + "value": 166480 + }, + "0/52/2": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 52, + "cluster_type": "chip.clusters.Objects.SoftwareDiagnostics", + "cluster_name": "SoftwareDiagnostics", + "attribute_id": 2, + "attribute_type": "chip.clusters.Objects.SoftwareDiagnostics.Attributes.CurrentHeapUsed", + "attribute_name": "CurrentHeapUsed", + "value": 86512 + }, + "0/52/3": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 52, + "cluster_type": "chip.clusters.Objects.SoftwareDiagnostics", + "cluster_name": "SoftwareDiagnostics", + "attribute_id": 3, + "attribute_type": "chip.clusters.Objects.SoftwareDiagnostics.Attributes.CurrentHeapHighWatermark", + "attribute_name": "CurrentHeapHighWatermark", + "value": 157052 + }, + "0/52/65532": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 52, + "cluster_type": "chip.clusters.Objects.SoftwareDiagnostics", + "cluster_name": "SoftwareDiagnostics", + "attribute_id": 65532, + "attribute_type": "chip.clusters.Objects.SoftwareDiagnostics.Attributes.FeatureMap", + "attribute_name": "FeatureMap", + "value": 0 + }, + "0/52/65533": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 52, + "cluster_type": "chip.clusters.Objects.SoftwareDiagnostics", + "cluster_name": "SoftwareDiagnostics", + "attribute_id": 65533, + "attribute_type": "chip.clusters.Objects.SoftwareDiagnostics.Attributes.ClusterRevision", + "attribute_name": "ClusterRevision", + "value": 1 + }, + "0/52/65528": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 52, + "cluster_type": "chip.clusters.Objects.SoftwareDiagnostics", + "cluster_name": "SoftwareDiagnostics", + "attribute_id": 65528, + "attribute_type": "chip.clusters.Objects.SoftwareDiagnostics.Attributes.GeneratedCommandList", + "attribute_name": "GeneratedCommandList", + "value": [] + }, + "0/52/65529": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 52, + "cluster_type": "chip.clusters.Objects.SoftwareDiagnostics", + "cluster_name": "SoftwareDiagnostics", + "attribute_id": 65529, + "attribute_type": "chip.clusters.Objects.SoftwareDiagnostics.Attributes.AcceptedCommandList", + "attribute_name": "AcceptedCommandList", + "value": [] + }, + "0/52/65531": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 52, + "cluster_type": "chip.clusters.Objects.SoftwareDiagnostics", + "cluster_name": "SoftwareDiagnostics", + "attribute_id": 65531, + "attribute_type": "chip.clusters.Objects.SoftwareDiagnostics.Attributes.AttributeList", + "attribute_name": "AttributeList", + "value": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533] + }, + "0/53/0": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 0, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.Channel", + "attribute_name": "Channel", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/1": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 1, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.RoutingRole", + "attribute_name": "RoutingRole", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/2": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 2, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.NetworkName", + "attribute_name": "NetworkName", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/3": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 3, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.PanId", + "attribute_name": "PanId", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/4": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 4, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.ExtendedPanId", + "attribute_name": "ExtendedPanId", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/5": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 5, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.MeshLocalPrefix", + "attribute_name": "MeshLocalPrefix", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/6": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 6, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.OverrunCount", + "attribute_name": "OverrunCount", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/7": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 7, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.NeighborTableList", + "attribute_name": "NeighborTableList", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/8": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 8, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.RouteTableList", + "attribute_name": "RouteTableList", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/9": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 9, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.PartitionId", + "attribute_name": "PartitionId", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/10": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 10, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.Weighting", + "attribute_name": "Weighting", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/11": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 11, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.DataVersion", + "attribute_name": "DataVersion", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/12": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 12, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.StableDataVersion", + "attribute_name": "StableDataVersion", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/13": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 13, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.LeaderRouterId", + "attribute_name": "LeaderRouterId", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/14": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 14, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.DetachedRoleCount", + "attribute_name": "DetachedRoleCount", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/15": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 15, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.ChildRoleCount", + "attribute_name": "ChildRoleCount", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/16": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 16, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.RouterRoleCount", + "attribute_name": "RouterRoleCount", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/17": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 17, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.LeaderRoleCount", + "attribute_name": "LeaderRoleCount", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/18": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 18, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.AttachAttemptCount", + "attribute_name": "AttachAttemptCount", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/19": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 19, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.PartitionIdChangeCount", + "attribute_name": "PartitionIdChangeCount", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/20": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 20, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.BetterPartitionAttachAttemptCount", + "attribute_name": "BetterPartitionAttachAttemptCount", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/21": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 21, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.ParentChangeCount", + "attribute_name": "ParentChangeCount", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/22": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 22, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.TxTotalCount", + "attribute_name": "TxTotalCount", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/23": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 23, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.TxUnicastCount", + "attribute_name": "TxUnicastCount", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/24": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 24, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.TxBroadcastCount", + "attribute_name": "TxBroadcastCount", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/25": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 25, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.TxAckRequestedCount", + "attribute_name": "TxAckRequestedCount", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/26": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 26, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.TxAckedCount", + "attribute_name": "TxAckedCount", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/27": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 27, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.TxNoAckRequestedCount", + "attribute_name": "TxNoAckRequestedCount", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/28": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 28, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.TxDataCount", + "attribute_name": "TxDataCount", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/29": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 29, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.TxDataPollCount", + "attribute_name": "TxDataPollCount", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/30": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 30, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.TxBeaconCount", + "attribute_name": "TxBeaconCount", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/31": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 31, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.TxBeaconRequestCount", + "attribute_name": "TxBeaconRequestCount", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/32": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 32, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.TxOtherCount", + "attribute_name": "TxOtherCount", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/33": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 33, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.TxRetryCount", + "attribute_name": "TxRetryCount", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/34": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 34, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.TxDirectMaxRetryExpiryCount", + "attribute_name": "TxDirectMaxRetryExpiryCount", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/35": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 35, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.TxIndirectMaxRetryExpiryCount", + "attribute_name": "TxIndirectMaxRetryExpiryCount", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/36": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 36, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.TxErrCcaCount", + "attribute_name": "TxErrCcaCount", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/37": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 37, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.TxErrAbortCount", + "attribute_name": "TxErrAbortCount", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/38": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 38, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.TxErrBusyChannelCount", + "attribute_name": "TxErrBusyChannelCount", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/39": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 39, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.RxTotalCount", + "attribute_name": "RxTotalCount", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/40": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 40, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.RxUnicastCount", + "attribute_name": "RxUnicastCount", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/41": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 41, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.RxBroadcastCount", + "attribute_name": "RxBroadcastCount", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/42": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 42, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.RxDataCount", + "attribute_name": "RxDataCount", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/43": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 43, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.RxDataPollCount", + "attribute_name": "RxDataPollCount", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/44": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 44, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.RxBeaconCount", + "attribute_name": "RxBeaconCount", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/45": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 45, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.RxBeaconRequestCount", + "attribute_name": "RxBeaconRequestCount", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/46": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 46, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.RxOtherCount", + "attribute_name": "RxOtherCount", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/47": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 47, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.RxAddressFilteredCount", + "attribute_name": "RxAddressFilteredCount", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/48": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 48, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.RxDestAddrFilteredCount", + "attribute_name": "RxDestAddrFilteredCount", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/49": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 49, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.RxDuplicatedCount", + "attribute_name": "RxDuplicatedCount", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/50": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 50, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.RxErrNoFrameCount", + "attribute_name": "RxErrNoFrameCount", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/51": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 51, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.RxErrUnknownNeighborCount", + "attribute_name": "RxErrUnknownNeighborCount", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/52": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 52, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.RxErrInvalidSrcAddrCount", + "attribute_name": "RxErrInvalidSrcAddrCount", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/53": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 53, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.RxErrSecCount", + "attribute_name": "RxErrSecCount", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/54": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 54, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.RxErrFcsCount", + "attribute_name": "RxErrFcsCount", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/55": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 55, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.RxErrOtherCount", + "attribute_name": "RxErrOtherCount", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/56": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 56, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.ActiveTimestamp", + "attribute_name": "ActiveTimestamp", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/57": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 57, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.PendingTimestamp", + "attribute_name": "PendingTimestamp", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/58": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 58, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.Delay", + "attribute_name": "Delay", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/59": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 59, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.SecurityPolicy", + "attribute_name": "SecurityPolicy", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/60": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 60, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.ChannelPage0Mask", + "attribute_name": "ChannelPage0Mask", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/61": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 61, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.OperationalDatasetComponents", + "attribute_name": "OperationalDatasetComponents", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/62": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 62, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.ActiveNetworkFaultsList", + "attribute_name": "ActiveNetworkFaultsList", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/65532": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 65532, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.FeatureMap", + "attribute_name": "FeatureMap", + "value": 15 + }, + "0/53/65533": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 65533, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.ClusterRevision", + "attribute_name": "ClusterRevision", + "value": 1 + }, + "0/53/65528": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 65528, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.GeneratedCommandList", + "attribute_name": "GeneratedCommandList", + "value": [] + }, + "0/53/65529": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 65529, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.AcceptedCommandList", + "attribute_name": "AcceptedCommandList", + "value": [0] + }, + "0/53/65531": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 65531, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.AttributeList", + "attribute_name": "AttributeList", + "value": [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, + 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, + 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, + 56, 57, 58, 59, 60, 61, 62, 65528, 65529, 65531, 65532, 65533 + ] + }, + "0/54/0": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 54, + "cluster_type": "chip.clusters.Objects.WiFiNetworkDiagnostics", + "cluster_name": "WiFiNetworkDiagnostics", + "attribute_id": 0, + "attribute_type": "chip.clusters.Objects.WiFiNetworkDiagnostics.Attributes.Bssid", + "attribute_name": "Bssid", + "value": "b'r\\xa7A\\x91\\xf1!'" + }, + "0/54/1": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 54, + "cluster_type": "chip.clusters.Objects.WiFiNetworkDiagnostics", + "cluster_name": "WiFiNetworkDiagnostics", + "attribute_id": 1, + "attribute_type": "chip.clusters.Objects.WiFiNetworkDiagnostics.Attributes.SecurityType", + "attribute_name": "SecurityType", + "value": 4 + }, + "0/54/2": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 54, + "cluster_type": "chip.clusters.Objects.WiFiNetworkDiagnostics", + "cluster_name": "WiFiNetworkDiagnostics", + "attribute_id": 2, + "attribute_type": "chip.clusters.Objects.WiFiNetworkDiagnostics.Attributes.WiFiVersion", + "attribute_name": "WiFiVersion", + "value": 3 + }, + "0/54/3": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 54, + "cluster_type": "chip.clusters.Objects.WiFiNetworkDiagnostics", + "cluster_name": "WiFiNetworkDiagnostics", + "attribute_id": 3, + "attribute_type": "chip.clusters.Objects.WiFiNetworkDiagnostics.Attributes.ChannelNumber", + "attribute_name": "ChannelNumber", + "value": 6 + }, + "0/54/4": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 54, + "cluster_type": "chip.clusters.Objects.WiFiNetworkDiagnostics", + "cluster_name": "WiFiNetworkDiagnostics", + "attribute_id": 4, + "attribute_type": "chip.clusters.Objects.WiFiNetworkDiagnostics.Attributes.Rssi", + "attribute_name": "Rssi", + "value": -61 + }, + "0/54/5": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 54, + "cluster_type": "chip.clusters.Objects.WiFiNetworkDiagnostics", + "cluster_name": "WiFiNetworkDiagnostics", + "attribute_id": 5, + "attribute_type": "chip.clusters.Objects.WiFiNetworkDiagnostics.Attributes.BeaconLostCount", + "attribute_name": "BeaconLostCount", + "value": null + }, + "0/54/6": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 54, + "cluster_type": "chip.clusters.Objects.WiFiNetworkDiagnostics", + "cluster_name": "WiFiNetworkDiagnostics", + "attribute_id": 6, + "attribute_type": "chip.clusters.Objects.WiFiNetworkDiagnostics.Attributes.BeaconRxCount", + "attribute_name": "BeaconRxCount", + "value": null + }, + "0/54/7": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 54, + "cluster_type": "chip.clusters.Objects.WiFiNetworkDiagnostics", + "cluster_name": "WiFiNetworkDiagnostics", + "attribute_id": 7, + "attribute_type": "chip.clusters.Objects.WiFiNetworkDiagnostics.Attributes.PacketMulticastRxCount", + "attribute_name": "PacketMulticastRxCount", + "value": null + }, + "0/54/8": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 54, + "cluster_type": "chip.clusters.Objects.WiFiNetworkDiagnostics", + "cluster_name": "WiFiNetworkDiagnostics", + "attribute_id": 8, + "attribute_type": "chip.clusters.Objects.WiFiNetworkDiagnostics.Attributes.PacketMulticastTxCount", + "attribute_name": "PacketMulticastTxCount", + "value": null + }, + "0/54/9": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 54, + "cluster_type": "chip.clusters.Objects.WiFiNetworkDiagnostics", + "cluster_name": "WiFiNetworkDiagnostics", + "attribute_id": 9, + "attribute_type": "chip.clusters.Objects.WiFiNetworkDiagnostics.Attributes.PacketUnicastRxCount", + "attribute_name": "PacketUnicastRxCount", + "value": null + }, + "0/54/10": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 54, + "cluster_type": "chip.clusters.Objects.WiFiNetworkDiagnostics", + "cluster_name": "WiFiNetworkDiagnostics", + "attribute_id": 10, + "attribute_type": "chip.clusters.Objects.WiFiNetworkDiagnostics.Attributes.PacketUnicastTxCount", + "attribute_name": "PacketUnicastTxCount", + "value": null + }, + "0/54/11": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 54, + "cluster_type": "chip.clusters.Objects.WiFiNetworkDiagnostics", + "cluster_name": "WiFiNetworkDiagnostics", + "attribute_id": 11, + "attribute_type": "chip.clusters.Objects.WiFiNetworkDiagnostics.Attributes.CurrentMaxRate", + "attribute_name": "CurrentMaxRate", + "value": null + }, + "0/54/12": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 54, + "cluster_type": "chip.clusters.Objects.WiFiNetworkDiagnostics", + "cluster_name": "WiFiNetworkDiagnostics", + "attribute_id": 12, + "attribute_type": "chip.clusters.Objects.WiFiNetworkDiagnostics.Attributes.OverrunCount", + "attribute_name": "OverrunCount", + "value": null + }, + "0/54/65532": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 54, + "cluster_type": "chip.clusters.Objects.WiFiNetworkDiagnostics", + "cluster_name": "WiFiNetworkDiagnostics", + "attribute_id": 65532, + "attribute_type": "chip.clusters.Objects.WiFiNetworkDiagnostics.Attributes.FeatureMap", + "attribute_name": "FeatureMap", + "value": 3 + }, + "0/54/65533": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 54, + "cluster_type": "chip.clusters.Objects.WiFiNetworkDiagnostics", + "cluster_name": "WiFiNetworkDiagnostics", + "attribute_id": 65533, + "attribute_type": "chip.clusters.Objects.WiFiNetworkDiagnostics.Attributes.ClusterRevision", + "attribute_name": "ClusterRevision", + "value": 1 + }, + "0/54/65528": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 54, + "cluster_type": "chip.clusters.Objects.WiFiNetworkDiagnostics", + "cluster_name": "WiFiNetworkDiagnostics", + "attribute_id": 65528, + "attribute_type": "chip.clusters.Objects.WiFiNetworkDiagnostics.Attributes.GeneratedCommandList", + "attribute_name": "GeneratedCommandList", + "value": [] + }, + "0/54/65529": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 54, + "cluster_type": "chip.clusters.Objects.WiFiNetworkDiagnostics", + "cluster_name": "WiFiNetworkDiagnostics", + "attribute_id": 65529, + "attribute_type": "chip.clusters.Objects.WiFiNetworkDiagnostics.Attributes.AcceptedCommandList", + "attribute_name": "AcceptedCommandList", + "value": [0] + }, + "0/54/65531": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 54, + "cluster_type": "chip.clusters.Objects.WiFiNetworkDiagnostics", + "cluster_name": "WiFiNetworkDiagnostics", + "attribute_id": 65531, + "attribute_type": "chip.clusters.Objects.WiFiNetworkDiagnostics.Attributes.AttributeList", + "attribute_name": "AttributeList", + "value": [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 65528, 65529, 65531, 65532, + 65533 + ] + }, + "0/55/0": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 55, + "cluster_type": "chip.clusters.Objects.EthernetNetworkDiagnostics", + "cluster_name": "EthernetNetworkDiagnostics", + "attribute_id": 0, + "attribute_type": "chip.clusters.Objects.EthernetNetworkDiagnostics.Attributes.PHYRate", + "attribute_name": "PHYRate", + "value": null + }, + "0/55/1": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 55, + "cluster_type": "chip.clusters.Objects.EthernetNetworkDiagnostics", + "cluster_name": "EthernetNetworkDiagnostics", + "attribute_id": 1, + "attribute_type": "chip.clusters.Objects.EthernetNetworkDiagnostics.Attributes.FullDuplex", + "attribute_name": "FullDuplex", + "value": null + }, + "0/55/2": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 55, + "cluster_type": "chip.clusters.Objects.EthernetNetworkDiagnostics", + "cluster_name": "EthernetNetworkDiagnostics", + "attribute_id": 2, + "attribute_type": "chip.clusters.Objects.EthernetNetworkDiagnostics.Attributes.PacketRxCount", + "attribute_name": "PacketRxCount", + "value": 0 + }, + "0/55/3": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 55, + "cluster_type": "chip.clusters.Objects.EthernetNetworkDiagnostics", + "cluster_name": "EthernetNetworkDiagnostics", + "attribute_id": 3, + "attribute_type": "chip.clusters.Objects.EthernetNetworkDiagnostics.Attributes.PacketTxCount", + "attribute_name": "PacketTxCount", + "value": 0 + }, + "0/55/4": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 55, + "cluster_type": "chip.clusters.Objects.EthernetNetworkDiagnostics", + "cluster_name": "EthernetNetworkDiagnostics", + "attribute_id": 4, + "attribute_type": "chip.clusters.Objects.EthernetNetworkDiagnostics.Attributes.TxErrCount", + "attribute_name": "TxErrCount", + "value": 0 + }, + "0/55/5": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 55, + "cluster_type": "chip.clusters.Objects.EthernetNetworkDiagnostics", + "cluster_name": "EthernetNetworkDiagnostics", + "attribute_id": 5, + "attribute_type": "chip.clusters.Objects.EthernetNetworkDiagnostics.Attributes.CollisionCount", + "attribute_name": "CollisionCount", + "value": 0 + }, + "0/55/6": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 55, + "cluster_type": "chip.clusters.Objects.EthernetNetworkDiagnostics", + "cluster_name": "EthernetNetworkDiagnostics", + "attribute_id": 6, + "attribute_type": "chip.clusters.Objects.EthernetNetworkDiagnostics.Attributes.OverrunCount", + "attribute_name": "OverrunCount", + "value": 0 + }, + "0/55/7": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 55, + "cluster_type": "chip.clusters.Objects.EthernetNetworkDiagnostics", + "cluster_name": "EthernetNetworkDiagnostics", + "attribute_id": 7, + "attribute_type": "chip.clusters.Objects.EthernetNetworkDiagnostics.Attributes.CarrierDetect", + "attribute_name": "CarrierDetect", + "value": null + }, + "0/55/8": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 55, + "cluster_type": "chip.clusters.Objects.EthernetNetworkDiagnostics", + "cluster_name": "EthernetNetworkDiagnostics", + "attribute_id": 8, + "attribute_type": "chip.clusters.Objects.EthernetNetworkDiagnostics.Attributes.TimeSinceReset", + "attribute_name": "TimeSinceReset", + "value": 0 + }, + "0/55/65532": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 55, + "cluster_type": "chip.clusters.Objects.EthernetNetworkDiagnostics", + "cluster_name": "EthernetNetworkDiagnostics", + "attribute_id": 65532, + "attribute_type": "chip.clusters.Objects.EthernetNetworkDiagnostics.Attributes.FeatureMap", + "attribute_name": "FeatureMap", + "value": 3 + }, + "0/55/65533": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 55, + "cluster_type": "chip.clusters.Objects.EthernetNetworkDiagnostics", + "cluster_name": "EthernetNetworkDiagnostics", + "attribute_id": 65533, + "attribute_type": "chip.clusters.Objects.EthernetNetworkDiagnostics.Attributes.ClusterRevision", + "attribute_name": "ClusterRevision", + "value": 1 + }, + "0/55/65528": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 55, + "cluster_type": "chip.clusters.Objects.EthernetNetworkDiagnostics", + "cluster_name": "EthernetNetworkDiagnostics", + "attribute_id": 65528, + "attribute_type": "chip.clusters.Objects.EthernetNetworkDiagnostics.Attributes.GeneratedCommandList", + "attribute_name": "GeneratedCommandList", + "value": [] + }, + "0/55/65529": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 55, + "cluster_type": "chip.clusters.Objects.EthernetNetworkDiagnostics", + "cluster_name": "EthernetNetworkDiagnostics", + "attribute_id": 65529, + "attribute_type": "chip.clusters.Objects.EthernetNetworkDiagnostics.Attributes.AcceptedCommandList", + "attribute_name": "AcceptedCommandList", + "value": [0] + }, + "0/55/65531": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 55, + "cluster_type": "chip.clusters.Objects.EthernetNetworkDiagnostics", + "cluster_name": "EthernetNetworkDiagnostics", + "attribute_id": 65531, + "attribute_type": "chip.clusters.Objects.EthernetNetworkDiagnostics.Attributes.AttributeList", + "attribute_name": "AttributeList", + "value": [0, 1, 2, 3, 4, 5, 6, 7, 8, 65528, 65529, 65531, 65532, 65533] + }, + "0/59/65532": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 59, + "cluster_type": "chip.clusters.Objects.Switch", + "cluster_name": "Switch", + "attribute_id": 65532, + "attribute_type": "chip.clusters.Objects.Switch.Attributes.FeatureMap", + "attribute_name": "FeatureMap", + "value": 0 + }, + "0/59/65533": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 59, + "cluster_type": "chip.clusters.Objects.Switch", + "cluster_name": "Switch", + "attribute_id": 65533, + "attribute_type": "chip.clusters.Objects.Switch.Attributes.ClusterRevision", + "attribute_name": "ClusterRevision", + "value": 1 + }, + "0/59/65528": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 59, + "cluster_type": "chip.clusters.Objects.Switch", + "cluster_name": "Switch", + "attribute_id": 65528, + "attribute_type": "chip.clusters.Objects.Switch.Attributes.GeneratedCommandList", + "attribute_name": "GeneratedCommandList", + "value": [] + }, + "0/59/65529": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 59, + "cluster_type": "chip.clusters.Objects.Switch", + "cluster_name": "Switch", + "attribute_id": 65529, + "attribute_type": "chip.clusters.Objects.Switch.Attributes.AcceptedCommandList", + "attribute_name": "AcceptedCommandList", + "value": [] + }, + "0/59/65531": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 59, + "cluster_type": "chip.clusters.Objects.Switch", + "cluster_name": "Switch", + "attribute_id": 65531, + "attribute_type": "chip.clusters.Objects.Switch.Attributes.AttributeList", + "attribute_name": "AttributeList", + "value": [65528, 65529, 65531, 65532, 65533] + }, + "0/60/0": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 60, + "cluster_type": "chip.clusters.Objects.AdministratorCommissioning", + "cluster_name": "AdministratorCommissioning", + "attribute_id": 0, + "attribute_type": "chip.clusters.Objects.AdministratorCommissioning.Attributes.WindowStatus", + "attribute_name": "WindowStatus", + "value": 0 + }, + "0/60/1": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 60, + "cluster_type": "chip.clusters.Objects.AdministratorCommissioning", + "cluster_name": "AdministratorCommissioning", + "attribute_id": 1, + "attribute_type": "chip.clusters.Objects.AdministratorCommissioning.Attributes.AdminFabricIndex", + "attribute_name": "AdminFabricIndex", + "value": null + }, + "0/60/2": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 60, + "cluster_type": "chip.clusters.Objects.AdministratorCommissioning", + "cluster_name": "AdministratorCommissioning", + "attribute_id": 2, + "attribute_type": "chip.clusters.Objects.AdministratorCommissioning.Attributes.AdminVendorId", + "attribute_name": "AdminVendorId", + "value": null + }, + "0/60/65532": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 60, + "cluster_type": "chip.clusters.Objects.AdministratorCommissioning", + "cluster_name": "AdministratorCommissioning", + "attribute_id": 65532, + "attribute_type": "chip.clusters.Objects.AdministratorCommissioning.Attributes.FeatureMap", + "attribute_name": "FeatureMap", + "value": 0 + }, + "0/60/65533": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 60, + "cluster_type": "chip.clusters.Objects.AdministratorCommissioning", + "cluster_name": "AdministratorCommissioning", + "attribute_id": 65533, + "attribute_type": "chip.clusters.Objects.AdministratorCommissioning.Attributes.ClusterRevision", + "attribute_name": "ClusterRevision", + "value": 1 + }, + "0/60/65528": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 60, + "cluster_type": "chip.clusters.Objects.AdministratorCommissioning", + "cluster_name": "AdministratorCommissioning", + "attribute_id": 65528, + "attribute_type": "chip.clusters.Objects.AdministratorCommissioning.Attributes.GeneratedCommandList", + "attribute_name": "GeneratedCommandList", + "value": [] + }, + "0/60/65529": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 60, + "cluster_type": "chip.clusters.Objects.AdministratorCommissioning", + "cluster_name": "AdministratorCommissioning", + "attribute_id": 65529, + "attribute_type": "chip.clusters.Objects.AdministratorCommissioning.Attributes.AcceptedCommandList", + "attribute_name": "AcceptedCommandList", + "value": [0, 1, 2] + }, + "0/60/65531": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 60, + "cluster_type": "chip.clusters.Objects.AdministratorCommissioning", + "cluster_name": "AdministratorCommissioning", + "attribute_id": 65531, + "attribute_type": "chip.clusters.Objects.AdministratorCommissioning.Attributes.AttributeList", + "attribute_name": "AttributeList", + "value": [0, 1, 2, 65528, 65529, 65531, 65532, 65533] + }, + "0/62/0": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 62, + "cluster_type": "chip.clusters.Objects.OperationalCredentials", + "cluster_name": "OperationalCredentials", + "attribute_id": 0, + "attribute_type": "chip.clusters.Objects.OperationalCredentials.Attributes.NOCs", + "attribute_name": "NOCs", + "value": [ + { + "noc": "b'\\x150\\x01\\x01\\x01$\\x02\\x017\\x03$\\x13\\x02\\x18&\\x04\\x80\"\\x81\\'&\\x05\\x80%M:7\\x06$\\x15\\x01$\\x11\\x01\\x18$\\x07\\x01$\\x08\\x010\\tA\\x04Z\\xec\\xd9\\x11h\\'J[%&\\xe1\\xd8\\xa0\\xb15\\x01\\x9c\\x83\\xf8\\x1a\\x9bg\\xbb\\x81e\\x18\\x8b\\xc8\\x9dr\\xed\\xe7\\xc3\\\\\\xd21\\x99\\xc1\\xdb\\xfe\\xd0}\\xf5)\\xaeMq\\xd5\\xeeRg\\x16\\xd5%\\x9c\\xfcD\\t\\xcbQ\\xc8\\x83*\\x997\\n5\\x01(\\x01\\x18$\\x02\\x016\\x03\\x04\\x02\\x04\\x01\\x180\\x04\\x14\\xca\\xe2\\x07\\xdb\\x89, got " - }, - "WiFiNetworkDiagnostics": { - "bssid": { - "_type": "bytes", - "value": "1iH5ZUbu" - }, - "securityType": 4, - "wiFiVersion": 3, - "channelNumber": 1, - "rssi": -38, - "beaconLostCount": 0, - "beaconRxCount": 0, - "packetMulticastRxCount": 0, - "packetMulticastTxCount": 0, - "packetUnicastRxCount": 0, - "packetUnicastTxCount": 0, - "currentMaxRate": 0, - "overrunCount": 0, - "generatedCommandList": [], - "acceptedCommandList": [0], - "attributeList": [ - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 65528, 65529, 65531, 65532, - 65533 - ], - "featureMap": 3, - "clusterRevision": 1, - "_type": "chip.clusters.Objects.WiFiNetworkDiagnostics" - }, - "EthernetNetworkDiagnostics": { - "PHYRate": null, - "fullDuplex": null, - "packetRxCount": 0, - "packetTxCount": 0, - "txErrCount": 0, - "collisionCount": 0, - "overrunCount": 0, - "carrierDetect": null, - "timeSinceReset": 0, - "generatedCommandList": [], - "acceptedCommandList": [0], - "attributeList": [ - 0, 1, 2, 3, 4, 5, 6, 7, 8, 65528, 65529, 65531, 65532, 65533 - ], - "featureMap": 3, - "clusterRevision": 1, - "_type": "chip.clusters.Objects.EthernetNetworkDiagnostics" - }, - "Switch": { - "numberOfPositions": null, - "currentPosition": null, - "multiPressMax": null, - "generatedCommandList": [], - "acceptedCommandList": [], - "attributeList": [65528, 65529, 65531, 65532, 65533], - "featureMap": 0, - "clusterRevision": 1, - "_type": "chip.clusters.Objects.Switch" - }, - "AdministratorCommissioning": { - "windowStatus": 0, - "adminFabricIndex": 0, - "adminVendorId": 0, - "generatedCommandList": [], - "acceptedCommandList": [0, 1, 2], - "attributeList": [0, 1, 2, 65528, 65529, 65531, 65532, 65533], - "featureMap": 0, - "clusterRevision": 1, - "_type": "chip.clusters.Objects.AdministratorCommissioning" - }, - "OperationalCredentials": { - "NOCs": [ - { - "noc": { - "_type": "bytes", - "value": "FTABAQEkAgE3AyQTARgmBIAigScmBYAlTTo3BiQVASUR8RAYJAcBJAgBMAlBBHQsjZ/8Hpm4iqznEv0dAO03bZx8LDgqpIOpBsHeysZu8KAmI0K+p6B8FuI1h3wld1V+tIj5OHVHtrigg6Ssl043CjUBKAEYJAIBNgMEAgQBGDAEFEWrZiyeoUgEIXz4c40+Nzq9cfxHMAUUSTs2LnMMrX7nj+dns0cSq3SmK3MYMAtAoFdxyvsbLm6VekNCQ6yqJOucAcRSVY3Si4ov1alKPK9CaIPl+u5dvBWNfyEPXSLsPmzyfd2njl8WRz8e7CBiSRg=" - }, - "icac": { - "_type": "bytes", - "value": "FTABAQAkAgE3AyQUABgmBIAigScmBYAlTTo3BiQTARgkBwEkCAEwCUEE09c6S9xVbf3/blpXSgRAZzKXx/4KQC274cEfa2tFjdVAJYJUvM/8PMurRHEroPpA3FXpJ8/hfabkNvHGi2l8tTcKNQEpARgkAmAwBBRJOzYucwytfueP52ezRxKrdKYrczAFFBf0ohq+KHQlEVBIMgEeZCBPR72hGDALQNwd63sOjWKYhjlvDJmcPtIzljSsXlQ10vFrB5j9V9CdiZHDfy537G39fo0RJmpU63EGXYEtXVrEfSMiafshKVcY" - }, - "fabricIndex": 1, - "_type": "chip.clusters.Objects.OperationalCredentials.Structs.NOCStruct" - } - ], - "fabrics": [ - { - "rootPublicKey": { - "_type": "bytes", - "value": "BBGg+O3i3tDVYryXkUmEXk1fnSMHN06+poGIfZODdvbZW4JvxHnrQVAxvZWIE6poLa0sKA8X8A7jmJsVFMUqLFM=" - }, - "vendorId": 35328, - "fabricId": 1, - "nodeId": 4337, - "label": "", - "fabricIndex": 1, - "_type": "chip.clusters.Objects.OperationalCredentials.Structs.FabricDescriptor" - } - ], - "supportedFabrics": 5, - "commissionedFabrics": 1, - "trustedRootCertificates": [ - { - "_type": "bytes", - "value": "FTABAQAkAgE3AyQUABgmBIAigScmBYAlTTo3BiQUABgkBwEkCAEwCUEEEaD47eLe0NVivJeRSYReTV+dIwc3Tr6mgYh9k4N29tlbgm/EeetBUDG9lYgTqmgtrSwoDxfwDuOYmxUUxSosUzcKNQEpARgkAmAwBBQX9KIavih0JRFQSDIBHmQgT0e9oTAFFBf0ohq+KHQlEVBIMgEeZCBPR72hGDALQO3xFiF2cEXl+/kk0CQfedzHJxSJiziHEjWCMjIj7SVlDVx4CpvNYHnheq+9vJFgcL8JQhAEdz6p6C3INBDL7dsY" - } - ], - "currentFabricIndex": 1, - "generatedCommandList": [1, 3, 5, 8], - "acceptedCommandList": [0, 2, 4, 6, 7, 9, 10, 11], - "attributeList": [0, 1, 2, 3, 4, 5, 65528, 65529, 65531, 65532, 65533], - "featureMap": 0, - "clusterRevision": 1, - "_type": "chip.clusters.Objects.OperationalCredentials" - }, - "GroupKeyManagement": { - "groupKeyMap": [], - "groupTable": [], - "maxGroupsPerFabric": 3, - "maxGroupKeysPerFabric": 2, - "generatedCommandList": [2, 5], - "acceptedCommandList": [0, 1, 3, 4], - "attributeList": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533], - "featureMap": 0, - "clusterRevision": 1, - "_type": "chip.clusters.Objects.GroupKeyManagement" - }, - "FixedLabel": { - "labelList": [ - { - "label": "room", - "value": "bedroom 2", - "_type": "chip.clusters.Objects.FixedLabel.Structs.LabelStruct" - }, - { - "label": "orientation", - "value": "North", - "_type": "chip.clusters.Objects.FixedLabel.Structs.LabelStruct" - }, - { - "label": "floor", - "value": "2", - "_type": "chip.clusters.Objects.FixedLabel.Structs.LabelStruct" - }, - { - "label": "direction", - "value": "up", - "_type": "chip.clusters.Objects.FixedLabel.Structs.LabelStruct" - } - ], - "generatedCommandList": [], - "acceptedCommandList": [], - "attributeList": [0, 65528, 65529, 65531, 65532, 65533], - "featureMap": 0, - "clusterRevision": 1, - "_type": "chip.clusters.Objects.FixedLabel" - }, - "UserLabel": { - "labelList": [], - "generatedCommandList": [], - "acceptedCommandList": [], - "attributeList": [0, 65528, 65529, 65531, 65532, 65533], - "featureMap": 0, - "clusterRevision": 1, - "_type": "chip.clusters.Objects.UserLabel" - } - }, - "1": { - "Identify": { - "identifyTime": 0, - "identifyType": 0, - "generatedCommandList": [], - "acceptedCommandList": [0, 64], - "attributeList": [0, 1, 65528, 65529, 65531, 65532, 65533], - "featureMap": 0, - "clusterRevision": 4, - "_type": "chip.clusters.Objects.Identify" - }, - "Groups": { - "nameSupport": 128, - "generatedCommandList": [0, 1, 2, 3], - "acceptedCommandList": [0, 1, 2, 3, 4, 5], - "attributeList": [0, 65528, 65529, 65531, 65532, 65533], - "featureMap": 0, - "clusterRevision": 4, - "_type": "chip.clusters.Objects.Groups" - }, - "OnOff": { - "onOff": false, - "globalSceneControl": true, - "onTime": 0, - "offWaitTime": 0, - "startUpOnOff": null, - "generatedCommandList": [], - "acceptedCommandList": [0, 1, 2, 64, 65, 66], - "attributeList": [ - 0, 16384, 16385, 16386, 16387, 65528, 65529, 65531, 65532, 65533 - ], - "featureMap": 1, - "clusterRevision": 4, - "_type": "chip.clusters.Objects.OnOff" - }, - "LevelControl": { - "currentLevel": 254, - "remainingTime": 0, - "minLevel": 0, - "maxLevel": 254, - "currentFrequency": 0, - "minFrequency": 0, - "maxFrequency": 0, - "options": 0, - "onOffTransitionTime": 0, - "onLevel": null, - "onTransitionTime": 0, - "offTransitionTime": 0, - "defaultMoveRate": 50, - "startUpCurrentLevel": null, - "generatedCommandList": [], - "acceptedCommandList": [0, 1, 2, 3, 4, 5, 6, 7], - "attributeList": [ - 0, 1, 2, 3, 4, 5, 6, 15, 16, 17, 18, 19, 20, 16384, 65528, 65529, - 65531, 65532, 65533 - ], - "featureMap": 3, - "clusterRevision": 5, - "_type": "chip.clusters.Objects.LevelControl" - }, - "Descriptor": { - "deviceTypeList": [ - { - "type": 257, - "revision": 1, - "_type": "chip.clusters.Objects.Descriptor.Structs.DeviceTypeStruct" - } - ], - "serverList": [3, 4, 6, 8, 29, 768, 1030], - "clientList": [], - "partsList": [], - "generatedCommandList": [], - "acceptedCommandList": [], - "attributeList": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533], - "featureMap": 0, - "clusterRevision": 1, - "_type": "chip.clusters.Objects.Descriptor" - }, - "ColorControl": { - "currentHue": 0, - "currentSaturation": 0, - "remainingTime": 0, - "currentX": 24939, - "currentY": 24701, - "driftCompensation": null, - "compensationText": null, - "colorTemperatureMireds": 0, - "colorMode": 2, - "options": 0, - "numberOfPrimaries": 0, - "primary1X": null, - "primary1Y": null, - "primary1Intensity": null, - "primary2X": null, - "primary2Y": null, - "primary2Intensity": null, - "primary3X": null, - "primary3Y": null, - "primary3Intensity": null, - "primary4X": null, - "primary4Y": null, - "primary4Intensity": null, - "primary5X": null, - "primary5Y": null, - "primary5Intensity": null, - "primary6X": null, - "primary6Y": null, - "primary6Intensity": null, - "whitePointX": null, - "whitePointY": null, - "colorPointRX": null, - "colorPointRY": null, - "colorPointRIntensity": null, - "colorPointGX": null, - "colorPointGY": null, - "colorPointGIntensity": null, - "colorPointBX": null, - "colorPointBY": null, - "colorPointBIntensity": null, - "enhancedCurrentHue": 0, - "enhancedColorMode": 2, - "colorLoopActive": 0, - "colorLoopDirection": 0, - "colorLoopTime": 25, - "colorLoopStartEnhancedHue": 8960, - "colorLoopStoredEnhancedHue": 0, - "colorCapabilities": 0, - "colorTempPhysicalMinMireds": 0, - "colorTempPhysicalMaxMireds": 65279, - "coupleColorTempToLevelMinMireds": 0, - "startUpColorTemperatureMireds": 0, - "generatedCommandList": [], - "acceptedCommandList": [ - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 64, 65, 66, 67, 68, 71, 75, 76 - ], - "attributeList": [ - 0, 1, 2, 3, 4, 7, 8, 15, 16, 16384, 16385, 16386, 16387, 16388, 16389, - 16390, 16394, 16395, 16396, 16397, 16400, 65528, 65529, 65531, 65532, - 65533 - ], - "featureMap": 31, - "clusterRevision": 5, - "_type": "chip.clusters.Objects.ColorControl" - }, - "OccupancySensing": { - "occupancy": 0, - "occupancySensorType": 0, - "occupancySensorTypeBitmap": 1, - "pirOccupiedToUnoccupiedDelay": null, - "pirUnoccupiedToOccupiedDelay": null, - "pirUnoccupiedToOccupiedThreshold": null, - "ultrasonicOccupiedToUnoccupiedDelay": null, - "ultrasonicUnoccupiedToOccupiedDelay": null, - "ultrasonicUnoccupiedToOccupiedThreshold": null, - "physicalContactOccupiedToUnoccupiedDelay": null, - "physicalContactUnoccupiedToOccupiedDelay": null, - "physicalContactUnoccupiedToOccupiedThreshold": null, - "generatedCommandList": [], - "acceptedCommandList": [], - "attributeList": [0, 1, 2, 65528, 65529, 65531, 65532, 65533], - "featureMap": 0, - "clusterRevision": 3, - "_type": "chip.clusters.Objects.OccupancySensing" - } - } - }, - "events": [ - { - "Header": { - "EndpointId": 0, - "ClusterId": 40, - "EventId": 0, - "EventNumber": 262144, - "Priority": 2, - "Timestamp": 2019, - "TimestampType": 0 - }, - "Status": 0, - "Data": { - "softwareVersion": 1, - "_type": "chip.clusters.Objects.Basic.Events.StartUp" - } - }, - { - "Header": { - "EndpointId": 0, - "ClusterId": 51, - "EventId": 3, - "EventNumber": 262145, - "Priority": 2, - "Timestamp": 2020, - "TimestampType": 0 - }, - "Status": 0, - "Data": { - "bootReason": 1, - "_type": "chip.clusters.Objects.GeneralDiagnostics.Events.BootReason" - } - }, - { - "Header": { - "EndpointId": 0, - "ClusterId": 54, - "EventId": 2, - "EventNumber": 262146, - "Priority": 1, - "Timestamp": 2216, - "TimestampType": 0 - }, - "Status": 0, - "Data": { - "connectionStatus": 0, - "_type": "chip.clusters.Objects.WiFiNetworkDiagnostics.Events.ConnectionStatus" - } - } - ], - "node_id": 4337 -} diff --git a/tests/components/matter/fixtures/nodes/occupancy-sensor.json b/tests/components/matter/fixtures/nodes/occupancy-sensor.json new file mode 100644 index 00000000000..2944853f9e1 --- /dev/null +++ b/tests/components/matter/fixtures/nodes/occupancy-sensor.json @@ -0,0 +1,633 @@ +{ + "node_id": 1, + "date_commissioned": "2022-11-29T21:23:48.485051", + "last_interview": "2022-11-29T21:23:48.485057", + "interview_version": 1, + "attributes": { + "0/40/0": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 0, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.DataModelRevision", + "attribute_name": "DataModelRevision", + "value": 1 + }, + "0/40/1": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 1, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.VendorName", + "attribute_name": "VendorName", + "value": "Nabu Casa" + }, + "0/40/2": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 2, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.VendorID", + "attribute_name": "VendorID", + "value": 65521 + }, + "0/40/3": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 3, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.ProductName", + "attribute_name": "ProductName", + "value": "Mock OccupancySensor" + }, + "0/40/4": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 4, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.ProductID", + "attribute_name": "ProductID", + "value": 32768 + }, + "0/40/5": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 5, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.NodeLabel", + "attribute_name": "NodeLabel", + "value": "Mock Occupancy Sensor" + }, + "0/40/6": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 6, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.Location", + "attribute_name": "Location", + "value": "XX" + }, + "0/40/7": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 7, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.HardwareVersion", + "attribute_name": "HardwareVersion", + "value": 0 + }, + "0/40/8": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 8, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.HardwareVersionString", + "attribute_name": "HardwareVersionString", + "value": "v1.0" + }, + "0/40/9": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 9, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.SoftwareVersion", + "attribute_name": "SoftwareVersion", + "value": 1 + }, + "0/40/10": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 10, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.SoftwareVersionString", + "attribute_name": "SoftwareVersionString", + "value": "v1.0" + }, + "0/40/11": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 11, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.ManufacturingDate", + "attribute_name": "ManufacturingDate", + "value": "20221206" + }, + "0/40/12": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 12, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.PartNumber", + "attribute_name": "PartNumber", + "value": "" + }, + "0/40/13": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 13, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.ProductURL", + "attribute_name": "ProductURL", + "value": "" + }, + "0/40/14": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 14, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.ProductLabel", + "attribute_name": "ProductLabel", + "value": "" + }, + "0/40/15": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 15, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.SerialNumber", + "attribute_name": "SerialNumber", + "value": "TEST_SN" + }, + "0/40/16": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 16, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.LocalConfigDisabled", + "attribute_name": "LocalConfigDisabled", + "value": false + }, + "0/40/17": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 17, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.Reachable", + "attribute_name": "Reachable", + "value": true + }, + "0/40/18": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 18, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.UniqueID", + "attribute_name": "UniqueID", + "value": "mock-temperature-sensor" + }, + "0/40/19": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 19, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.CapabilityMinima", + "attribute_name": "CapabilityMinima", + "value": { + "caseSessionsPerFabric": 3, + "subscriptionsPerFabric": 3 + } + }, + "0/40/65532": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 65532, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.FeatureMap", + "attribute_name": "FeatureMap", + "value": 0 + }, + "0/40/65533": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 65533, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.ClusterRevision", + "attribute_name": "ClusterRevision", + "value": 1 + }, + "0/40/65528": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 65528, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.GeneratedCommandList", + "attribute_name": "GeneratedCommandList", + "value": [] + }, + "0/40/65529": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 65529, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.AcceptedCommandList", + "attribute_name": "AcceptedCommandList", + "value": [] + }, + "0/40/65531": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 65531, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.AttributeList", + "attribute_name": "AttributeList", + "value": [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, + 65528, 65529, 65531, 65532, 65533 + ] + }, + + "1/3/0": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 3, + "cluster_type": "chip.clusters.Objects.Identify", + "cluster_name": "Identify", + "attribute_id": 0, + "attribute_type": "chip.clusters.Objects.Identify.Attributes.IdentifyTime", + "attribute_name": "IdentifyTime", + "value": 0 + }, + "1/3/1": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 3, + "cluster_type": "chip.clusters.Objects.Identify", + "cluster_name": "Identify", + "attribute_id": 1, + "attribute_type": "chip.clusters.Objects.Identify.Attributes.IdentifyType", + "attribute_name": "IdentifyType", + "value": 2 + }, + "1/3/65532": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 3, + "cluster_type": "chip.clusters.Objects.Identify", + "cluster_name": "Identify", + "attribute_id": 65532, + "attribute_type": "chip.clusters.Objects.Identify.Attributes.FeatureMap", + "attribute_name": "FeatureMap", + "value": 0 + }, + "1/3/65533": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 3, + "cluster_type": "chip.clusters.Objects.Identify", + "cluster_name": "Identify", + "attribute_id": 65533, + "attribute_type": "chip.clusters.Objects.Identify.Attributes.ClusterRevision", + "attribute_name": "ClusterRevision", + "value": 4 + }, + "1/3/65528": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 3, + "cluster_type": "chip.clusters.Objects.Identify", + "cluster_name": "Identify", + "attribute_id": 65528, + "attribute_type": "chip.clusters.Objects.Identify.Attributes.GeneratedCommandList", + "attribute_name": "GeneratedCommandList", + "value": [] + }, + "1/3/65529": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 3, + "cluster_type": "chip.clusters.Objects.Identify", + "cluster_name": "Identify", + "attribute_id": 65529, + "attribute_type": "chip.clusters.Objects.Identify.Attributes.AcceptedCommandList", + "attribute_name": "AcceptedCommandList", + "value": [0, 64] + }, + "1/3/65531": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 3, + "cluster_type": "chip.clusters.Objects.Identify", + "cluster_name": "Identify", + "attribute_id": 65531, + "attribute_type": "chip.clusters.Objects.Identify.Attributes.AttributeList", + "attribute_name": "AttributeList", + "value": [0, 1, 65528, 65529, 65531, 65532, 65533] + }, + + "1/29/0": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 0, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.DeviceTypeList", + "attribute_name": "DeviceTypeList", + "value": [ + { + "type": 263, + "revision": 1 + } + ] + }, + "1/29/1": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 1, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.ServerList", + "attribute_name": "ServerList", + "value": [ + 3, 4, 5, 6, 7, 8, 15, 29, 30, 37, 47, 59, 64, 65, 69, 80, 257, 258, 259, + 512, 513, 514, 516, 768, 1024, 1026, 1027, 1028, 1029, 1030, 1283, 1284, + 1285, 1286, 1287, 1288, 1289, 1290, 1291, 1292, 1293, 1294, 2820, + 4294048773 + ] + }, + "1/29/2": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 2, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.ClientList", + "attribute_name": "ClientList", + "value": [] + }, + "1/29/3": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 3, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.PartsList", + "attribute_name": "PartsList", + "value": [] + }, + "1/29/65532": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 65532, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.FeatureMap", + "attribute_name": "FeatureMap", + "value": 0 + }, + "1/29/65533": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 65533, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.ClusterRevision", + "attribute_name": "ClusterRevision", + "value": 1 + }, + "1/29/65528": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 65528, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.GeneratedCommandList", + "attribute_name": "GeneratedCommandList", + "value": [] + }, + "1/29/65529": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 65529, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.AcceptedCommandList", + "attribute_name": "AcceptedCommandList", + "value": [] + }, + "1/29/65531": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 65531, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.AttributeList", + "attribute_name": "AttributeList", + "value": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533] + }, + "1/30/0": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 30, + "cluster_type": "chip.clusters.Objects.Binding", + "cluster_name": "Binding", + "attribute_id": 0, + "attribute_type": "chip.clusters.Objects.Binding.Attributes.Binding", + "attribute_name": "Binding", + "value": [] + }, + "1/30/65532": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 30, + "cluster_type": "chip.clusters.Objects.Binding", + "cluster_name": "Binding", + "attribute_id": 65532, + "attribute_type": "chip.clusters.Objects.Binding.Attributes.FeatureMap", + "attribute_name": "FeatureMap", + "value": 0 + }, + "1/30/65533": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 30, + "cluster_type": "chip.clusters.Objects.Binding", + "cluster_name": "Binding", + "attribute_id": 65533, + "attribute_type": "chip.clusters.Objects.Binding.Attributes.ClusterRevision", + "attribute_name": "ClusterRevision", + "value": 1 + }, + "1/30/65528": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 30, + "cluster_type": "chip.clusters.Objects.Binding", + "cluster_name": "Binding", + "attribute_id": 65528, + "attribute_type": "chip.clusters.Objects.Binding.Attributes.GeneratedCommandList", + "attribute_name": "GeneratedCommandList", + "value": [] + }, + "1/30/65529": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 30, + "cluster_type": "chip.clusters.Objects.Binding", + "cluster_name": "Binding", + "attribute_id": 65529, + "attribute_type": "chip.clusters.Objects.Binding.Attributes.AcceptedCommandList", + "attribute_name": "AcceptedCommandList", + "value": [] + }, + "1/30/65531": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 30, + "cluster_type": "chip.clusters.Objects.Binding", + "cluster_name": "Binding", + "attribute_id": 65531, + "attribute_type": "chip.clusters.Objects.Binding.Attributes.AttributeList", + "attribute_name": "AttributeList", + "value": [0, 65528, 65529, 65531, 65532, 65533] + }, + "1/1030/0": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 1030, + "cluster_type": "chip.clusters.Objects.OccupancySensing", + "cluster_name": "OccupancySensing", + "attribute_id": 0, + "attribute_type": "chip.clusters.Objects.OccupancySensing.Attributes.Occupancy", + "attribute_name": "Occupancy", + "value": 1 + }, + "1/1030/1": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 1030, + "cluster_type": "chip.clusters.Objects.OccupancySensing", + "cluster_name": "OccupancySensing", + "attribute_id": 1, + "attribute_type": "chip.clusters.Objects.OccupancySensing.Attributes.OccupancySensorType", + "attribute_name": "OccupancySensorType", + "value": 0 + }, + "1/1030/2": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 1030, + "cluster_type": "chip.clusters.Objects.OccupancySensing", + "cluster_name": "OccupancySensing", + "attribute_id": 2, + "attribute_type": "chip.clusters.Objects.OccupancySensing.Attributes.OccupancySensorTypeBitmap", + "attribute_name": "OccupancySensorTypeBitmap", + "value": 1 + }, + "1/1030/65532": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 1030, + "cluster_type": "chip.clusters.Objects.OccupancySensing", + "cluster_name": "OccupancySensing", + "attribute_id": 65532, + "attribute_type": "chip.clusters.Objects.OccupancySensing.Attributes.FeatureMap", + "attribute_name": "FeatureMap", + "value": 0 + }, + "1/1030/65533": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 1030, + "cluster_type": "chip.clusters.Objects.OccupancySensing", + "cluster_name": "OccupancySensing", + "attribute_id": 65533, + "attribute_type": "chip.clusters.Objects.OccupancySensing.Attributes.ClusterRevision", + "attribute_name": "ClusterRevision", + "value": 3 + }, + "1/1030/65528": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 1030, + "cluster_type": "chip.clusters.Objects.OccupancySensing", + "cluster_name": "OccupancySensing", + "attribute_id": 65528, + "attribute_type": "chip.clusters.Objects.OccupancySensing.Attributes.GeneratedCommandList", + "attribute_name": "GeneratedCommandList", + "value": [] + }, + "1/1030/65529": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 1030, + "cluster_type": "chip.clusters.Objects.OccupancySensing", + "cluster_name": "OccupancySensing", + "attribute_id": 65529, + "attribute_type": "chip.clusters.Objects.OccupancySensing.Attributes.AcceptedCommandList", + "attribute_name": "AcceptedCommandList", + "value": [] + }, + "1/1030/65531": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 1030, + "cluster_type": "chip.clusters.Objects.OccupancySensing", + "cluster_name": "OccupancySensing", + "attribute_id": 65531, + "attribute_type": "chip.clusters.Objects.OccupancySensing.Attributes.AttributeList", + "attribute_name": "AttributeList", + "value": [0, 1, 2, 65528, 65529, 65531, 65532, 65533] + } + }, + "endpoints": [0, 1], + "_type": "matter_server.common.models.node.MatterNode" +} diff --git a/tests/components/matter/fixtures/nodes/on-off-plugin-unit.json b/tests/components/matter/fixtures/nodes/on-off-plugin-unit.json new file mode 100644 index 00000000000..dd1c005bdf8 --- /dev/null +++ b/tests/components/matter/fixtures/nodes/on-off-plugin-unit.json @@ -0,0 +1,1055 @@ +{ + "node_id": 1, + "date_commissioned": "2022-11-29T21:23:48.485051", + "last_interview": "2022-11-29T21:23:48.485057", + "interview_version": 1, + "attributes": { + "0/40/0": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 0, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.DataModelRevision", + "attribute_name": "DataModelRevision", + "value": 1 + }, + "0/40/1": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 1, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.VendorName", + "attribute_name": "VendorName", + "value": "Nabu Casa" + }, + "0/40/2": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 2, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.VendorID", + "attribute_name": "VendorID", + "value": 65521 + }, + "0/40/3": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 3, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.ProductName", + "attribute_name": "ProductName", + "value": "Mock OnOffPluginUnit (powerplug/switch)" + }, + "0/40/4": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 4, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.ProductID", + "attribute_name": "ProductID", + "value": 32768 + }, + "0/40/5": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 5, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.NodeLabel", + "attribute_name": "NodeLabel", + "value": "Mock OnOff Plugin Unit" + }, + "0/40/6": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 6, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.Location", + "attribute_name": "Location", + "value": "XX" + }, + "0/40/7": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 7, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.HardwareVersion", + "attribute_name": "HardwareVersion", + "value": 0 + }, + "0/40/8": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 8, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.HardwareVersionString", + "attribute_name": "HardwareVersionString", + "value": "v1.0" + }, + "0/40/9": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 9, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.SoftwareVersion", + "attribute_name": "SoftwareVersion", + "value": 1 + }, + "0/40/10": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 10, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.SoftwareVersionString", + "attribute_name": "SoftwareVersionString", + "value": "v1.0" + }, + "0/40/11": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 11, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.ManufacturingDate", + "attribute_name": "ManufacturingDate", + "value": "20221206" + }, + "0/40/12": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 12, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.PartNumber", + "attribute_name": "PartNumber", + "value": "" + }, + "0/40/13": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 13, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.ProductURL", + "attribute_name": "ProductURL", + "value": "" + }, + "0/40/14": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 14, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.ProductLabel", + "attribute_name": "ProductLabel", + "value": "" + }, + "0/40/15": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 15, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.SerialNumber", + "attribute_name": "SerialNumber", + "value": "TEST_SN" + }, + "0/40/16": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 16, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.LocalConfigDisabled", + "attribute_name": "LocalConfigDisabled", + "value": false + }, + "0/40/17": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 17, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.Reachable", + "attribute_name": "Reachable", + "value": true + }, + "0/40/18": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 18, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.UniqueID", + "attribute_name": "UniqueID", + "value": "mock-onoff-plugin-unit" + }, + "0/40/19": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 19, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.CapabilityMinima", + "attribute_name": "CapabilityMinima", + "value": { + "caseSessionsPerFabric": 3, + "subscriptionsPerFabric": 3 + } + }, + "0/40/65532": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 65532, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.FeatureMap", + "attribute_name": "FeatureMap", + "value": 0 + }, + "0/40/65533": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 65533, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.ClusterRevision", + "attribute_name": "ClusterRevision", + "value": 1 + }, + "0/40/65528": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 65528, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.GeneratedCommandList", + "attribute_name": "GeneratedCommandList", + "value": [] + }, + "0/40/65529": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 65529, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.AcceptedCommandList", + "attribute_name": "AcceptedCommandList", + "value": [] + }, + "0/40/65531": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 65531, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.AttributeList", + "attribute_name": "AttributeList", + "value": [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, + 65528, 65529, 65531, 65532, 65533 + ] + }, + + "1/3/0": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 3, + "cluster_type": "chip.clusters.Objects.Identify", + "cluster_name": "Identify", + "attribute_id": 0, + "attribute_type": "chip.clusters.Objects.Identify.Attributes.IdentifyTime", + "attribute_name": "IdentifyTime", + "value": 0 + }, + "1/3/1": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 3, + "cluster_type": "chip.clusters.Objects.Identify", + "cluster_name": "Identify", + "attribute_id": 1, + "attribute_type": "chip.clusters.Objects.Identify.Attributes.IdentifyType", + "attribute_name": "IdentifyType", + "value": 2 + }, + "1/3/65532": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 3, + "cluster_type": "chip.clusters.Objects.Identify", + "cluster_name": "Identify", + "attribute_id": 65532, + "attribute_type": "chip.clusters.Objects.Identify.Attributes.FeatureMap", + "attribute_name": "FeatureMap", + "value": 0 + }, + "1/3/65533": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 3, + "cluster_type": "chip.clusters.Objects.Identify", + "cluster_name": "Identify", + "attribute_id": 65533, + "attribute_type": "chip.clusters.Objects.Identify.Attributes.ClusterRevision", + "attribute_name": "ClusterRevision", + "value": 4 + }, + "1/3/65528": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 3, + "cluster_type": "chip.clusters.Objects.Identify", + "cluster_name": "Identify", + "attribute_id": 65528, + "attribute_type": "chip.clusters.Objects.Identify.Attributes.GeneratedCommandList", + "attribute_name": "GeneratedCommandList", + "value": [] + }, + "1/3/65529": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 3, + "cluster_type": "chip.clusters.Objects.Identify", + "cluster_name": "Identify", + "attribute_id": 65529, + "attribute_type": "chip.clusters.Objects.Identify.Attributes.AcceptedCommandList", + "attribute_name": "AcceptedCommandList", + "value": [0, 64] + }, + "1/3/65531": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 3, + "cluster_type": "chip.clusters.Objects.Identify", + "cluster_name": "Identify", + "attribute_id": 65531, + "attribute_type": "chip.clusters.Objects.Identify.Attributes.AttributeList", + "attribute_name": "AttributeList", + "value": [0, 1, 65528, 65529, 65531, 65532, 65533] + }, + "1/4/0": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 4, + "cluster_type": "chip.clusters.Objects.Groups", + "cluster_name": "Groups", + "attribute_id": 0, + "attribute_type": "chip.clusters.Objects.Groups.Attributes.NameSupport", + "attribute_name": "NameSupport", + "value": 128 + }, + "1/4/65532": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 4, + "cluster_type": "chip.clusters.Objects.Groups", + "cluster_name": "Groups", + "attribute_id": 65532, + "attribute_type": "chip.clusters.Objects.Groups.Attributes.FeatureMap", + "attribute_name": "FeatureMap", + "value": 1 + }, + "1/4/65533": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 4, + "cluster_type": "chip.clusters.Objects.Groups", + "cluster_name": "Groups", + "attribute_id": 65533, + "attribute_type": "chip.clusters.Objects.Groups.Attributes.ClusterRevision", + "attribute_name": "ClusterRevision", + "value": 4 + }, + "1/4/65528": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 4, + "cluster_type": "chip.clusters.Objects.Groups", + "cluster_name": "Groups", + "attribute_id": 65528, + "attribute_type": "chip.clusters.Objects.Groups.Attributes.GeneratedCommandList", + "attribute_name": "GeneratedCommandList", + "value": [0, 1, 2, 3] + }, + "1/4/65529": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 4, + "cluster_type": "chip.clusters.Objects.Groups", + "cluster_name": "Groups", + "attribute_id": 65529, + "attribute_type": "chip.clusters.Objects.Groups.Attributes.AcceptedCommandList", + "attribute_name": "AcceptedCommandList", + "value": [0, 1, 2, 3, 4, 5] + }, + "1/4/65531": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 4, + "cluster_type": "chip.clusters.Objects.Groups", + "cluster_name": "Groups", + "attribute_id": 65531, + "attribute_type": "chip.clusters.Objects.Groups.Attributes.AttributeList", + "attribute_name": "AttributeList", + "value": [0, 65528, 65529, 65531, 65532, 65533] + }, + "1/5/0": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 5, + "cluster_type": "chip.clusters.Objects.Scenes", + "cluster_name": "Scenes", + "attribute_id": 0, + "attribute_type": "chip.clusters.Objects.Scenes.Attributes.SceneCount", + "attribute_name": "SceneCount", + "value": 0 + }, + "1/5/1": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 5, + "cluster_type": "chip.clusters.Objects.Scenes", + "cluster_name": "Scenes", + "attribute_id": 1, + "attribute_type": "chip.clusters.Objects.Scenes.Attributes.CurrentScene", + "attribute_name": "CurrentScene", + "value": 0 + }, + "1/5/2": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 5, + "cluster_type": "chip.clusters.Objects.Scenes", + "cluster_name": "Scenes", + "attribute_id": 2, + "attribute_type": "chip.clusters.Objects.Scenes.Attributes.CurrentGroup", + "attribute_name": "CurrentGroup", + "value": 0 + }, + "1/5/3": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 5, + "cluster_type": "chip.clusters.Objects.Scenes", + "cluster_name": "Scenes", + "attribute_id": 3, + "attribute_type": "chip.clusters.Objects.Scenes.Attributes.SceneValid", + "attribute_name": "SceneValid", + "value": false + }, + "1/5/4": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 5, + "cluster_type": "chip.clusters.Objects.Scenes", + "cluster_name": "Scenes", + "attribute_id": 4, + "attribute_type": "chip.clusters.Objects.Scenes.Attributes.NameSupport", + "attribute_name": "NameSupport", + "value": 0 + }, + "1/5/65532": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 5, + "cluster_type": "chip.clusters.Objects.Scenes", + "cluster_name": "Scenes", + "attribute_id": 65532, + "attribute_type": "chip.clusters.Objects.Scenes.Attributes.FeatureMap", + "attribute_name": "FeatureMap", + "value": 0 + }, + "1/5/65533": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 5, + "cluster_type": "chip.clusters.Objects.Scenes", + "cluster_name": "Scenes", + "attribute_id": 65533, + "attribute_type": "chip.clusters.Objects.Scenes.Attributes.ClusterRevision", + "attribute_name": "ClusterRevision", + "value": 4 + }, + "1/5/65528": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 5, + "cluster_type": "chip.clusters.Objects.Scenes", + "cluster_name": "Scenes", + "attribute_id": 65528, + "attribute_type": "chip.clusters.Objects.Scenes.Attributes.GeneratedCommandList", + "attribute_name": "GeneratedCommandList", + "value": [0, 1, 2, 3, 4, 6] + }, + "1/5/65529": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 5, + "cluster_type": "chip.clusters.Objects.Scenes", + "cluster_name": "Scenes", + "attribute_id": 65529, + "attribute_type": "chip.clusters.Objects.Scenes.Attributes.AcceptedCommandList", + "attribute_name": "AcceptedCommandList", + "value": [0, 1, 2, 3, 4, 5, 6] + }, + "1/5/65531": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 5, + "cluster_type": "chip.clusters.Objects.Scenes", + "cluster_name": "Scenes", + "attribute_id": 65531, + "attribute_type": "chip.clusters.Objects.Scenes.Attributes.AttributeList", + "attribute_name": "AttributeList", + "value": [0, 1, 2, 3, 4, 65528, 65529, 65531, 65532, 65533] + }, + "1/6/0": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 6, + "cluster_type": "chip.clusters.Objects.OnOff", + "cluster_name": "OnOff", + "attribute_id": 0, + "attribute_type": "chip.clusters.Objects.OnOff.Attributes.OnOff", + "attribute_name": "OnOff", + "value": false + }, + "1/6/16384": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 6, + "cluster_type": "chip.clusters.Objects.OnOff", + "cluster_name": "OnOff", + "attribute_id": 16384, + "attribute_type": "chip.clusters.Objects.OnOff.Attributes.GlobalSceneControl", + "attribute_name": "GlobalSceneControl", + "value": true + }, + "1/6/16385": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 6, + "cluster_type": "chip.clusters.Objects.OnOff", + "cluster_name": "OnOff", + "attribute_id": 16385, + "attribute_type": "chip.clusters.Objects.OnOff.Attributes.OnTime", + "attribute_name": "OnTime", + "value": 0 + }, + "1/6/16386": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 6, + "cluster_type": "chip.clusters.Objects.OnOff", + "cluster_name": "OnOff", + "attribute_id": 16386, + "attribute_type": "chip.clusters.Objects.OnOff.Attributes.OffWaitTime", + "attribute_name": "OffWaitTime", + "value": 0 + }, + "1/6/16387": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 6, + "cluster_type": "chip.clusters.Objects.OnOff", + "cluster_name": "OnOff", + "attribute_id": 16387, + "attribute_type": "chip.clusters.Objects.OnOff.Attributes.StartUpOnOff", + "attribute_name": "StartUpOnOff", + "value": null + }, + "1/6/65532": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 6, + "cluster_type": "chip.clusters.Objects.OnOff", + "cluster_name": "OnOff", + "attribute_id": 65532, + "attribute_type": "chip.clusters.Objects.OnOff.Attributes.FeatureMap", + "attribute_name": "FeatureMap", + "value": 1 + }, + "1/6/65533": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 6, + "cluster_type": "chip.clusters.Objects.OnOff", + "cluster_name": "OnOff", + "attribute_id": 65533, + "attribute_type": "chip.clusters.Objects.OnOff.Attributes.ClusterRevision", + "attribute_name": "ClusterRevision", + "value": 4 + }, + "1/6/65528": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 6, + "cluster_type": "chip.clusters.Objects.OnOff", + "cluster_name": "OnOff", + "attribute_id": 65528, + "attribute_type": "chip.clusters.Objects.OnOff.Attributes.GeneratedCommandList", + "attribute_name": "GeneratedCommandList", + "value": [] + }, + "1/6/65529": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 6, + "cluster_type": "chip.clusters.Objects.OnOff", + "cluster_name": "OnOff", + "attribute_id": 65529, + "attribute_type": "chip.clusters.Objects.OnOff.Attributes.AcceptedCommandList", + "attribute_name": "AcceptedCommandList", + "value": [0, 1, 2, 64, 65, 66] + }, + "1/6/65531": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 6, + "cluster_type": "chip.clusters.Objects.OnOff", + "cluster_name": "OnOff", + "attribute_id": 65531, + "attribute_type": "chip.clusters.Objects.OnOff.Attributes.AttributeList", + "attribute_name": "AttributeList", + "value": [ + 0, 16384, 16385, 16386, 16387, 65528, 65529, 65531, 65532, 65533 + ] + }, + "1/7/0": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 7, + "cluster_type": "chip.clusters.Objects.OnOffSwitchConfiguration", + "cluster_name": "OnOffSwitchConfiguration", + "attribute_id": 0, + "attribute_type": "chip.clusters.Objects.OnOffSwitchConfiguration.Attributes.SwitchType", + "attribute_name": "SwitchType", + "value": 0 + }, + "1/7/16": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 7, + "cluster_type": "chip.clusters.Objects.OnOffSwitchConfiguration", + "cluster_name": "OnOffSwitchConfiguration", + "attribute_id": 16, + "attribute_type": "chip.clusters.Objects.OnOffSwitchConfiguration.Attributes.SwitchActions", + "attribute_name": "SwitchActions", + "value": 0 + }, + "1/7/65532": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 7, + "cluster_type": "chip.clusters.Objects.OnOffSwitchConfiguration", + "cluster_name": "OnOffSwitchConfiguration", + "attribute_id": 65532, + "attribute_type": "chip.clusters.Objects.OnOffSwitchConfiguration.Attributes.FeatureMap", + "attribute_name": "FeatureMap", + "value": 0 + }, + "1/7/65533": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 7, + "cluster_type": "chip.clusters.Objects.OnOffSwitchConfiguration", + "cluster_name": "OnOffSwitchConfiguration", + "attribute_id": 65533, + "attribute_type": "chip.clusters.Objects.OnOffSwitchConfiguration.Attributes.ClusterRevision", + "attribute_name": "ClusterRevision", + "value": 1 + }, + "1/7/65528": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 7, + "cluster_type": "chip.clusters.Objects.OnOffSwitchConfiguration", + "cluster_name": "OnOffSwitchConfiguration", + "attribute_id": 65528, + "attribute_type": "chip.clusters.Objects.OnOffSwitchConfiguration.Attributes.GeneratedCommandList", + "attribute_name": "GeneratedCommandList", + "value": [] + }, + "1/7/65529": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 7, + "cluster_type": "chip.clusters.Objects.OnOffSwitchConfiguration", + "cluster_name": "OnOffSwitchConfiguration", + "attribute_id": 65529, + "attribute_type": "chip.clusters.Objects.OnOffSwitchConfiguration.Attributes.AcceptedCommandList", + "attribute_name": "AcceptedCommandList", + "value": [] + }, + "1/7/65531": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 7, + "cluster_type": "chip.clusters.Objects.OnOffSwitchConfiguration", + "cluster_name": "OnOffSwitchConfiguration", + "attribute_id": 65531, + "attribute_type": "chip.clusters.Objects.OnOffSwitchConfiguration.Attributes.AttributeList", + "attribute_name": "AttributeList", + "value": [0, 16, 65528, 65529, 65531, 65532, 65533] + }, + "1/8/0": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 8, + "cluster_type": "chip.clusters.Objects.LevelControl", + "cluster_name": "LevelControl", + "attribute_id": 0, + "attribute_type": "chip.clusters.Objects.LevelControl.Attributes.CurrentLevel", + "attribute_name": "CurrentLevel", + "value": 254 + }, + "1/8/1": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 8, + "cluster_type": "chip.clusters.Objects.LevelControl", + "cluster_name": "LevelControl", + "attribute_id": 1, + "attribute_type": "chip.clusters.Objects.LevelControl.Attributes.RemainingTime", + "attribute_name": "RemainingTime", + "value": 0 + }, + "1/8/2": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 8, + "cluster_type": "chip.clusters.Objects.LevelControl", + "cluster_name": "LevelControl", + "attribute_id": 2, + "attribute_type": "chip.clusters.Objects.LevelControl.Attributes.MinLevel", + "attribute_name": "MinLevel", + "value": 1 + }, + "1/8/3": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 8, + "cluster_type": "chip.clusters.Objects.LevelControl", + "cluster_name": "LevelControl", + "attribute_id": 3, + "attribute_type": "chip.clusters.Objects.LevelControl.Attributes.MaxLevel", + "attribute_name": "MaxLevel", + "value": 254 + }, + "1/8/4": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 8, + "cluster_type": "chip.clusters.Objects.LevelControl", + "cluster_name": "LevelControl", + "attribute_id": 4, + "attribute_type": "chip.clusters.Objects.LevelControl.Attributes.CurrentFrequency", + "attribute_name": "CurrentFrequency", + "value": 0 + }, + "1/8/5": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 8, + "cluster_type": "chip.clusters.Objects.LevelControl", + "cluster_name": "LevelControl", + "attribute_id": 5, + "attribute_type": "chip.clusters.Objects.LevelControl.Attributes.MinFrequency", + "attribute_name": "MinFrequency", + "value": 0 + }, + "1/8/6": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 8, + "cluster_type": "chip.clusters.Objects.LevelControl", + "cluster_name": "LevelControl", + "attribute_id": 6, + "attribute_type": "chip.clusters.Objects.LevelControl.Attributes.MaxFrequency", + "attribute_name": "MaxFrequency", + "value": 0 + }, + "1/8/15": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 8, + "cluster_type": "chip.clusters.Objects.LevelControl", + "cluster_name": "LevelControl", + "attribute_id": 15, + "attribute_type": "chip.clusters.Objects.LevelControl.Attributes.Options", + "attribute_name": "Options", + "value": 0 + }, + "1/8/16": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 8, + "cluster_type": "chip.clusters.Objects.LevelControl", + "cluster_name": "LevelControl", + "attribute_id": 16, + "attribute_type": "chip.clusters.Objects.LevelControl.Attributes.OnOffTransitionTime", + "attribute_name": "OnOffTransitionTime", + "value": 0 + }, + "1/8/17": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 8, + "cluster_type": "chip.clusters.Objects.LevelControl", + "cluster_name": "LevelControl", + "attribute_id": 17, + "attribute_type": "chip.clusters.Objects.LevelControl.Attributes.OnLevel", + "attribute_name": "OnLevel", + "value": null + }, + "1/8/18": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 8, + "cluster_type": "chip.clusters.Objects.LevelControl", + "cluster_name": "LevelControl", + "attribute_id": 18, + "attribute_type": "chip.clusters.Objects.LevelControl.Attributes.OnTransitionTime", + "attribute_name": "OnTransitionTime", + "value": 0 + }, + "1/8/19": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 8, + "cluster_type": "chip.clusters.Objects.LevelControl", + "cluster_name": "LevelControl", + "attribute_id": 19, + "attribute_type": "chip.clusters.Objects.LevelControl.Attributes.OffTransitionTime", + "attribute_name": "OffTransitionTime", + "value": 0 + }, + "1/8/20": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 8, + "cluster_type": "chip.clusters.Objects.LevelControl", + "cluster_name": "LevelControl", + "attribute_id": 20, + "attribute_type": "chip.clusters.Objects.LevelControl.Attributes.DefaultMoveRate", + "attribute_name": "DefaultMoveRate", + "value": 50 + }, + "1/8/16384": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 8, + "cluster_type": "chip.clusters.Objects.LevelControl", + "cluster_name": "LevelControl", + "attribute_id": 16384, + "attribute_type": "chip.clusters.Objects.LevelControl.Attributes.StartUpCurrentLevel", + "attribute_name": "StartUpCurrentLevel", + "value": null + }, + "1/8/65532": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 8, + "cluster_type": "chip.clusters.Objects.LevelControl", + "cluster_name": "LevelControl", + "attribute_id": 65532, + "attribute_type": "chip.clusters.Objects.LevelControl.Attributes.FeatureMap", + "attribute_name": "FeatureMap", + "value": 3 + }, + "1/8/65533": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 8, + "cluster_type": "chip.clusters.Objects.LevelControl", + "cluster_name": "LevelControl", + "attribute_id": 65533, + "attribute_type": "chip.clusters.Objects.LevelControl.Attributes.ClusterRevision", + "attribute_name": "ClusterRevision", + "value": 5 + }, + "1/8/65528": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 8, + "cluster_type": "chip.clusters.Objects.LevelControl", + "cluster_name": "LevelControl", + "attribute_id": 65528, + "attribute_type": "chip.clusters.Objects.LevelControl.Attributes.GeneratedCommandList", + "attribute_name": "GeneratedCommandList", + "value": [] + }, + "1/8/65529": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 8, + "cluster_type": "chip.clusters.Objects.LevelControl", + "cluster_name": "LevelControl", + "attribute_id": 65529, + "attribute_type": "chip.clusters.Objects.LevelControl.Attributes.AcceptedCommandList", + "attribute_name": "AcceptedCommandList", + "value": [0, 1, 2, 3, 4, 5, 6, 7] + }, + "1/8/65531": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 8, + "cluster_type": "chip.clusters.Objects.LevelControl", + "cluster_name": "LevelControl", + "attribute_id": 65531, + "attribute_type": "chip.clusters.Objects.LevelControl.Attributes.AttributeList", + "attribute_name": "AttributeList", + "value": [ + 0, 1, 2, 3, 4, 5, 6, 15, 16, 17, 18, 19, 20, 16384, 65528, 65529, 65531, + 65532, 65533 + ] + }, + "1/29/0": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 0, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.DeviceTypeList", + "attribute_name": "DeviceTypeList", + "value": [ + { + "type": 266, + "revision": 1 + } + ] + }, + "1/29/1": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 1, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.ServerList", + "attribute_name": "ServerList", + "value": [ + 3, 4, 5, 6, 7, 8, 15, 29, 30, 37, 47, 59, 64, 65, 69, 80, 257, 258, 259, + 512, 513, 514, 516, 768, 1024, 1026, 1027, 1028, 1029, 1030, 1283, 1284, + 1285, 1286, 1287, 1288, 1289, 1290, 1291, 1292, 1293, 1294, 2820, + 4294048773 + ] + }, + "1/29/2": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 2, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.ClientList", + "attribute_name": "ClientList", + "value": [] + }, + "1/29/3": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 3, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.PartsList", + "attribute_name": "PartsList", + "value": [] + }, + "1/29/65532": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 65532, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.FeatureMap", + "attribute_name": "FeatureMap", + "value": 0 + }, + "1/29/65533": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 65533, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.ClusterRevision", + "attribute_name": "ClusterRevision", + "value": 1 + }, + "1/29/65528": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 65528, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.GeneratedCommandList", + "attribute_name": "GeneratedCommandList", + "value": [] + }, + "1/29/65529": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 65529, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.AcceptedCommandList", + "attribute_name": "AcceptedCommandList", + "value": [] + }, + "1/29/65531": { + "node_id": 1, + "endpoint": 1, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 65531, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.AttributeList", + "attribute_name": "AttributeList", + "value": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533] + } + }, + "endpoints": [0, 1], + "_type": "matter_server.common.models.node.MatterNode" +} diff --git a/tests/components/matter/fixtures/nodes/onoff-light.json b/tests/components/matter/fixtures/nodes/onoff-light.json new file mode 100644 index 00000000000..cc6521aa2e3 --- /dev/null +++ b/tests/components/matter/fixtures/nodes/onoff-light.json @@ -0,0 +1,4216 @@ +{ + "node_id": 1, + "date_commissioned": "2022-11-29T21:23:48.485051", + "last_interview": "2022-11-29T21:23:48.485057", + "interview_version": 1, + "attributes": { + "0/4/0": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 4, + "cluster_type": "chip.clusters.Objects.Groups", + "cluster_name": "Groups", + "attribute_id": 0, + "attribute_type": "chip.clusters.Objects.Groups.Attributes.NameSupport", + "attribute_name": "NameSupport", + "value": 128 + }, + "0/4/65532": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 4, + "cluster_type": "chip.clusters.Objects.Groups", + "cluster_name": "Groups", + "attribute_id": 65532, + "attribute_type": "chip.clusters.Objects.Groups.Attributes.FeatureMap", + "attribute_name": "FeatureMap", + "value": 1 + }, + "0/4/65533": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 4, + "cluster_type": "chip.clusters.Objects.Groups", + "cluster_name": "Groups", + "attribute_id": 65533, + "attribute_type": "chip.clusters.Objects.Groups.Attributes.ClusterRevision", + "attribute_name": "ClusterRevision", + "value": 4 + }, + "0/4/65528": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 4, + "cluster_type": "chip.clusters.Objects.Groups", + "cluster_name": "Groups", + "attribute_id": 65528, + "attribute_type": "chip.clusters.Objects.Groups.Attributes.GeneratedCommandList", + "attribute_name": "GeneratedCommandList", + "value": [0, 1, 2, 3] + }, + "0/4/65529": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 4, + "cluster_type": "chip.clusters.Objects.Groups", + "cluster_name": "Groups", + "attribute_id": 65529, + "attribute_type": "chip.clusters.Objects.Groups.Attributes.AcceptedCommandList", + "attribute_name": "AcceptedCommandList", + "value": [0, 1, 2, 3, 4, 5] + }, + "0/4/65531": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 4, + "cluster_type": "chip.clusters.Objects.Groups", + "cluster_name": "Groups", + "attribute_id": 65531, + "attribute_type": "chip.clusters.Objects.Groups.Attributes.AttributeList", + "attribute_name": "AttributeList", + "value": [0, 65528, 65529, 65531, 65532, 65533] + }, + "0/29/0": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 0, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.DeviceTypeList", + "attribute_name": "DeviceTypeList", + "value": [ + { + "type": 22, + "revision": 1 + } + ] + }, + "0/29/1": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 1, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.ServerList", + "attribute_name": "ServerList", + "value": [ + 4, 29, 31, 40, 42, 43, 44, 48, 49, 50, 51, 52, 53, 54, 55, 59, 60, 62, + 63, 64, 65 + ] + }, + "0/29/2": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 2, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.ClientList", + "attribute_name": "ClientList", + "value": [41] + }, + "0/29/3": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 3, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.PartsList", + "attribute_name": "PartsList", + "value": [1] + }, + "0/29/65532": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 65532, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.FeatureMap", + "attribute_name": "FeatureMap", + "value": 0 + }, + "0/29/65533": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 65533, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.ClusterRevision", + "attribute_name": "ClusterRevision", + "value": 1 + }, + "0/29/65528": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 65528, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.GeneratedCommandList", + "attribute_name": "GeneratedCommandList", + "value": [] + }, + "0/29/65529": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 65529, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.AcceptedCommandList", + "attribute_name": "AcceptedCommandList", + "value": [] + }, + "0/29/65531": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 65531, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.AttributeList", + "attribute_name": "AttributeList", + "value": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533] + }, + "0/31/0": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 31, + "cluster_type": "chip.clusters.Objects.AccessControl", + "cluster_name": "AccessControl", + "attribute_id": 0, + "attribute_type": "chip.clusters.Objects.AccessControl.Attributes.Acl", + "attribute_name": "Acl", + "value": [ + { + "privilege": 5, + "authMode": 2, + "subjects": [112233], + "targets": null, + "fabricIndex": 1 + } + ] + }, + "0/31/1": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 31, + "cluster_type": "chip.clusters.Objects.AccessControl", + "cluster_name": "AccessControl", + "attribute_id": 1, + "attribute_type": "chip.clusters.Objects.AccessControl.Attributes.Extension", + "attribute_name": "Extension", + "value": [] + }, + "0/31/2": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 31, + "cluster_type": "chip.clusters.Objects.AccessControl", + "cluster_name": "AccessControl", + "attribute_id": 2, + "attribute_type": "chip.clusters.Objects.AccessControl.Attributes.SubjectsPerAccessControlEntry", + "attribute_name": "SubjectsPerAccessControlEntry", + "value": 4 + }, + "0/31/3": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 31, + "cluster_type": "chip.clusters.Objects.AccessControl", + "cluster_name": "AccessControl", + "attribute_id": 3, + "attribute_type": "chip.clusters.Objects.AccessControl.Attributes.TargetsPerAccessControlEntry", + "attribute_name": "TargetsPerAccessControlEntry", + "value": 3 + }, + "0/31/4": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 31, + "cluster_type": "chip.clusters.Objects.AccessControl", + "cluster_name": "AccessControl", + "attribute_id": 4, + "attribute_type": "chip.clusters.Objects.AccessControl.Attributes.AccessControlEntriesPerFabric", + "attribute_name": "AccessControlEntriesPerFabric", + "value": 3 + }, + "0/31/65532": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 31, + "cluster_type": "chip.clusters.Objects.AccessControl", + "cluster_name": "AccessControl", + "attribute_id": 65532, + "attribute_type": "chip.clusters.Objects.AccessControl.Attributes.FeatureMap", + "attribute_name": "FeatureMap", + "value": 0 + }, + "0/31/65533": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 31, + "cluster_type": "chip.clusters.Objects.AccessControl", + "cluster_name": "AccessControl", + "attribute_id": 65533, + "attribute_type": "chip.clusters.Objects.AccessControl.Attributes.ClusterRevision", + "attribute_name": "ClusterRevision", + "value": 1 + }, + "0/31/65528": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 31, + "cluster_type": "chip.clusters.Objects.AccessControl", + "cluster_name": "AccessControl", + "attribute_id": 65528, + "attribute_type": "chip.clusters.Objects.AccessControl.Attributes.GeneratedCommandList", + "attribute_name": "GeneratedCommandList", + "value": [] + }, + "0/31/65529": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 31, + "cluster_type": "chip.clusters.Objects.AccessControl", + "cluster_name": "AccessControl", + "attribute_id": 65529, + "attribute_type": "chip.clusters.Objects.AccessControl.Attributes.AcceptedCommandList", + "attribute_name": "AcceptedCommandList", + "value": [] + }, + "0/31/65531": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 31, + "cluster_type": "chip.clusters.Objects.AccessControl", + "cluster_name": "AccessControl", + "attribute_id": 65531, + "attribute_type": "chip.clusters.Objects.AccessControl.Attributes.AttributeList", + "attribute_name": "AttributeList", + "value": [0, 1, 2, 3, 4, 65528, 65529, 65531, 65532, 65533] + }, + "0/40/0": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 0, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.DataModelRevision", + "attribute_name": "DataModelRevision", + "value": 1 + }, + "0/40/1": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 1, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.VendorName", + "attribute_name": "VendorName", + "value": "Nabu Casa" + }, + "0/40/2": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 2, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.VendorID", + "attribute_name": "VendorID", + "value": 65521 + }, + "0/40/3": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 3, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.ProductName", + "attribute_name": "ProductName", + "value": "Mock Light" + }, + "0/40/4": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 4, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.ProductID", + "attribute_name": "ProductID", + "value": 32768 + }, + "0/40/5": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 5, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.NodeLabel", + "attribute_name": "NodeLabel", + "value": "Mock OnOff Light" + }, + "0/40/6": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 6, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.Location", + "attribute_name": "Location", + "value": "XX" + }, + "0/40/7": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 7, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.HardwareVersion", + "attribute_name": "HardwareVersion", + "value": 0 + }, + "0/40/8": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 8, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.HardwareVersionString", + "attribute_name": "HardwareVersionString", + "value": "v1.0" + }, + "0/40/9": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 9, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.SoftwareVersion", + "attribute_name": "SoftwareVersion", + "value": 1 + }, + "0/40/10": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 10, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.SoftwareVersionString", + "attribute_name": "SoftwareVersionString", + "value": "v1.0" + }, + "0/40/11": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 11, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.ManufacturingDate", + "attribute_name": "ManufacturingDate", + "value": "20200101" + }, + "0/40/12": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 12, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.PartNumber", + "attribute_name": "PartNumber", + "value": "" + }, + "0/40/13": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 13, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.ProductURL", + "attribute_name": "ProductURL", + "value": "" + }, + "0/40/14": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 14, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.ProductLabel", + "attribute_name": "ProductLabel", + "value": "" + }, + "0/40/15": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 15, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.SerialNumber", + "attribute_name": "SerialNumber", + "value": "TEST_SN" + }, + "0/40/16": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 16, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.LocalConfigDisabled", + "attribute_name": "LocalConfigDisabled", + "value": false + }, + "0/40/17": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 17, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.Reachable", + "attribute_name": "Reachable", + "value": true + }, + "0/40/18": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 18, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.UniqueID", + "attribute_name": "UniqueID", + "value": "mock-onoff-light" + }, + "0/40/19": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 19, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.CapabilityMinima", + "attribute_name": "CapabilityMinima", + "value": { + "caseSessionsPerFabric": 3, + "subscriptionsPerFabric": 3 + } + }, + "0/40/65532": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 65532, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.FeatureMap", + "attribute_name": "FeatureMap", + "value": 0 + }, + "0/40/65533": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 65533, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.ClusterRevision", + "attribute_name": "ClusterRevision", + "value": 1 + }, + "0/40/65528": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 65528, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.GeneratedCommandList", + "attribute_name": "GeneratedCommandList", + "value": [] + }, + "0/40/65529": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 65529, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.AcceptedCommandList", + "attribute_name": "AcceptedCommandList", + "value": [] + }, + "0/40/65531": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 40, + "cluster_type": "chip.clusters.Objects.Basic", + "cluster_name": "Basic", + "attribute_id": 65531, + "attribute_type": "chip.clusters.Objects.Basic.Attributes.AttributeList", + "attribute_name": "AttributeList", + "value": [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, + 65528, 65529, 65531, 65532, 65533 + ] + }, + "0/42/0": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 42, + "cluster_type": "chip.clusters.Objects.OtaSoftwareUpdateRequestor", + "cluster_name": "OtaSoftwareUpdateRequestor", + "attribute_id": 0, + "attribute_type": "chip.clusters.Objects.OtaSoftwareUpdateRequestor.Attributes.DefaultOtaProviders", + "attribute_name": "DefaultOtaProviders", + "value": [] + }, + "0/42/1": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 42, + "cluster_type": "chip.clusters.Objects.OtaSoftwareUpdateRequestor", + "cluster_name": "OtaSoftwareUpdateRequestor", + "attribute_id": 1, + "attribute_type": "chip.clusters.Objects.OtaSoftwareUpdateRequestor.Attributes.UpdatePossible", + "attribute_name": "UpdatePossible", + "value": true + }, + "0/42/2": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 42, + "cluster_type": "chip.clusters.Objects.OtaSoftwareUpdateRequestor", + "cluster_name": "OtaSoftwareUpdateRequestor", + "attribute_id": 2, + "attribute_type": "chip.clusters.Objects.OtaSoftwareUpdateRequestor.Attributes.UpdateState", + "attribute_name": "UpdateState", + "value": 0 + }, + "0/42/3": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 42, + "cluster_type": "chip.clusters.Objects.OtaSoftwareUpdateRequestor", + "cluster_name": "OtaSoftwareUpdateRequestor", + "attribute_id": 3, + "attribute_type": "chip.clusters.Objects.OtaSoftwareUpdateRequestor.Attributes.UpdateStateProgress", + "attribute_name": "UpdateStateProgress", + "value": 0 + }, + "0/42/65532": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 42, + "cluster_type": "chip.clusters.Objects.OtaSoftwareUpdateRequestor", + "cluster_name": "OtaSoftwareUpdateRequestor", + "attribute_id": 65532, + "attribute_type": "chip.clusters.Objects.OtaSoftwareUpdateRequestor.Attributes.FeatureMap", + "attribute_name": "FeatureMap", + "value": 0 + }, + "0/42/65533": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 42, + "cluster_type": "chip.clusters.Objects.OtaSoftwareUpdateRequestor", + "cluster_name": "OtaSoftwareUpdateRequestor", + "attribute_id": 65533, + "attribute_type": "chip.clusters.Objects.OtaSoftwareUpdateRequestor.Attributes.ClusterRevision", + "attribute_name": "ClusterRevision", + "value": 1 + }, + "0/42/65528": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 42, + "cluster_type": "chip.clusters.Objects.OtaSoftwareUpdateRequestor", + "cluster_name": "OtaSoftwareUpdateRequestor", + "attribute_id": 65528, + "attribute_type": "chip.clusters.Objects.OtaSoftwareUpdateRequestor.Attributes.GeneratedCommandList", + "attribute_name": "GeneratedCommandList", + "value": [] + }, + "0/42/65529": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 42, + "cluster_type": "chip.clusters.Objects.OtaSoftwareUpdateRequestor", + "cluster_name": "OtaSoftwareUpdateRequestor", + "attribute_id": 65529, + "attribute_type": "chip.clusters.Objects.OtaSoftwareUpdateRequestor.Attributes.AcceptedCommandList", + "attribute_name": "AcceptedCommandList", + "value": [0] + }, + "0/42/65531": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 42, + "cluster_type": "chip.clusters.Objects.OtaSoftwareUpdateRequestor", + "cluster_name": "OtaSoftwareUpdateRequestor", + "attribute_id": 65531, + "attribute_type": "chip.clusters.Objects.OtaSoftwareUpdateRequestor.Attributes.AttributeList", + "attribute_name": "AttributeList", + "value": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533] + }, + "0/43/0": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 43, + "cluster_type": "chip.clusters.Objects.LocalizationConfiguration", + "cluster_name": "LocalizationConfiguration", + "attribute_id": 0, + "attribute_type": "chip.clusters.Objects.LocalizationConfiguration.Attributes.ActiveLocale", + "attribute_name": "ActiveLocale", + "value": "en-US" + }, + "0/43/1": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 43, + "cluster_type": "chip.clusters.Objects.LocalizationConfiguration", + "cluster_name": "LocalizationConfiguration", + "attribute_id": 1, + "attribute_type": "chip.clusters.Objects.LocalizationConfiguration.Attributes.SupportedLocales", + "attribute_name": "SupportedLocales", + "value": [ + "en-US", + "de-DE", + "fr-FR", + "en-GB", + "es-ES", + "zh-CN", + "it-IT", + "ja-JP" + ] + }, + "0/43/65532": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 43, + "cluster_type": "chip.clusters.Objects.LocalizationConfiguration", + "cluster_name": "LocalizationConfiguration", + "attribute_id": 65532, + "attribute_type": "chip.clusters.Objects.LocalizationConfiguration.Attributes.FeatureMap", + "attribute_name": "FeatureMap", + "value": 0 + }, + "0/43/65533": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 43, + "cluster_type": "chip.clusters.Objects.LocalizationConfiguration", + "cluster_name": "LocalizationConfiguration", + "attribute_id": 65533, + "attribute_type": "chip.clusters.Objects.LocalizationConfiguration.Attributes.ClusterRevision", + "attribute_name": "ClusterRevision", + "value": 1 + }, + "0/43/65528": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 43, + "cluster_type": "chip.clusters.Objects.LocalizationConfiguration", + "cluster_name": "LocalizationConfiguration", + "attribute_id": 65528, + "attribute_type": "chip.clusters.Objects.LocalizationConfiguration.Attributes.GeneratedCommandList", + "attribute_name": "GeneratedCommandList", + "value": [] + }, + "0/43/65529": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 43, + "cluster_type": "chip.clusters.Objects.LocalizationConfiguration", + "cluster_name": "LocalizationConfiguration", + "attribute_id": 65529, + "attribute_type": "chip.clusters.Objects.LocalizationConfiguration.Attributes.AcceptedCommandList", + "attribute_name": "AcceptedCommandList", + "value": [] + }, + "0/43/65531": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 43, + "cluster_type": "chip.clusters.Objects.LocalizationConfiguration", + "cluster_name": "LocalizationConfiguration", + "attribute_id": 65531, + "attribute_type": "chip.clusters.Objects.LocalizationConfiguration.Attributes.AttributeList", + "attribute_name": "AttributeList", + "value": [0, 1, 65528, 65529, 65531, 65532, 65533] + }, + "0/44/0": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 44, + "cluster_type": "chip.clusters.Objects.TimeFormatLocalization", + "cluster_name": "TimeFormatLocalization", + "attribute_id": 0, + "attribute_type": "chip.clusters.Objects.TimeFormatLocalization.Attributes.HourFormat", + "attribute_name": "HourFormat", + "value": 0 + }, + "0/44/1": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 44, + "cluster_type": "chip.clusters.Objects.TimeFormatLocalization", + "cluster_name": "TimeFormatLocalization", + "attribute_id": 1, + "attribute_type": "chip.clusters.Objects.TimeFormatLocalization.Attributes.ActiveCalendarType", + "attribute_name": "ActiveCalendarType", + "value": 0 + }, + "0/44/2": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 44, + "cluster_type": "chip.clusters.Objects.TimeFormatLocalization", + "cluster_name": "TimeFormatLocalization", + "attribute_id": 2, + "attribute_type": "chip.clusters.Objects.TimeFormatLocalization.Attributes.SupportedCalendarTypes", + "attribute_name": "SupportedCalendarTypes", + "value": [0, 1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 7] + }, + "0/44/65532": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 44, + "cluster_type": "chip.clusters.Objects.TimeFormatLocalization", + "cluster_name": "TimeFormatLocalization", + "attribute_id": 65532, + "attribute_type": "chip.clusters.Objects.TimeFormatLocalization.Attributes.FeatureMap", + "attribute_name": "FeatureMap", + "value": 0 + }, + "0/44/65533": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 44, + "cluster_type": "chip.clusters.Objects.TimeFormatLocalization", + "cluster_name": "TimeFormatLocalization", + "attribute_id": 65533, + "attribute_type": "chip.clusters.Objects.TimeFormatLocalization.Attributes.ClusterRevision", + "attribute_name": "ClusterRevision", + "value": 1 + }, + "0/44/65528": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 44, + "cluster_type": "chip.clusters.Objects.TimeFormatLocalization", + "cluster_name": "TimeFormatLocalization", + "attribute_id": 65528, + "attribute_type": "chip.clusters.Objects.TimeFormatLocalization.Attributes.GeneratedCommandList", + "attribute_name": "GeneratedCommandList", + "value": [] + }, + "0/44/65529": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 44, + "cluster_type": "chip.clusters.Objects.TimeFormatLocalization", + "cluster_name": "TimeFormatLocalization", + "attribute_id": 65529, + "attribute_type": "chip.clusters.Objects.TimeFormatLocalization.Attributes.AcceptedCommandList", + "attribute_name": "AcceptedCommandList", + "value": [] + }, + "0/44/65531": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 44, + "cluster_type": "chip.clusters.Objects.TimeFormatLocalization", + "cluster_name": "TimeFormatLocalization", + "attribute_id": 65531, + "attribute_type": "chip.clusters.Objects.TimeFormatLocalization.Attributes.AttributeList", + "attribute_name": "AttributeList", + "value": [0, 1, 2, 65528, 65529, 65531, 65532, 65533] + }, + "0/48/0": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 48, + "cluster_type": "chip.clusters.Objects.GeneralCommissioning", + "cluster_name": "GeneralCommissioning", + "attribute_id": 0, + "attribute_type": "chip.clusters.Objects.GeneralCommissioning.Attributes.Breadcrumb", + "attribute_name": "Breadcrumb", + "value": 0 + }, + "0/48/1": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 48, + "cluster_type": "chip.clusters.Objects.GeneralCommissioning", + "cluster_name": "GeneralCommissioning", + "attribute_id": 1, + "attribute_type": "chip.clusters.Objects.GeneralCommissioning.Attributes.BasicCommissioningInfo", + "attribute_name": "BasicCommissioningInfo", + "value": { + "failSafeExpiryLengthSeconds": 60, + "maxCumulativeFailsafeSeconds": 900 + } + }, + "0/48/2": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 48, + "cluster_type": "chip.clusters.Objects.GeneralCommissioning", + "cluster_name": "GeneralCommissioning", + "attribute_id": 2, + "attribute_type": "chip.clusters.Objects.GeneralCommissioning.Attributes.RegulatoryConfig", + "attribute_name": "RegulatoryConfig", + "value": 0 + }, + "0/48/3": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 48, + "cluster_type": "chip.clusters.Objects.GeneralCommissioning", + "cluster_name": "GeneralCommissioning", + "attribute_id": 3, + "attribute_type": "chip.clusters.Objects.GeneralCommissioning.Attributes.LocationCapability", + "attribute_name": "LocationCapability", + "value": 0 + }, + "0/48/4": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 48, + "cluster_type": "chip.clusters.Objects.GeneralCommissioning", + "cluster_name": "GeneralCommissioning", + "attribute_id": 4, + "attribute_type": "chip.clusters.Objects.GeneralCommissioning.Attributes.SupportsConcurrentConnection", + "attribute_name": "SupportsConcurrentConnection", + "value": true + }, + "0/48/65532": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 48, + "cluster_type": "chip.clusters.Objects.GeneralCommissioning", + "cluster_name": "GeneralCommissioning", + "attribute_id": 65532, + "attribute_type": "chip.clusters.Objects.GeneralCommissioning.Attributes.FeatureMap", + "attribute_name": "FeatureMap", + "value": 0 + }, + "0/48/65533": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 48, + "cluster_type": "chip.clusters.Objects.GeneralCommissioning", + "cluster_name": "GeneralCommissioning", + "attribute_id": 65533, + "attribute_type": "chip.clusters.Objects.GeneralCommissioning.Attributes.ClusterRevision", + "attribute_name": "ClusterRevision", + "value": 1 + }, + "0/48/65528": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 48, + "cluster_type": "chip.clusters.Objects.GeneralCommissioning", + "cluster_name": "GeneralCommissioning", + "attribute_id": 65528, + "attribute_type": "chip.clusters.Objects.GeneralCommissioning.Attributes.GeneratedCommandList", + "attribute_name": "GeneratedCommandList", + "value": [1, 3, 5] + }, + "0/48/65529": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 48, + "cluster_type": "chip.clusters.Objects.GeneralCommissioning", + "cluster_name": "GeneralCommissioning", + "attribute_id": 65529, + "attribute_type": "chip.clusters.Objects.GeneralCommissioning.Attributes.AcceptedCommandList", + "attribute_name": "AcceptedCommandList", + "value": [0, 2, 4] + }, + "0/48/65531": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 48, + "cluster_type": "chip.clusters.Objects.GeneralCommissioning", + "cluster_name": "GeneralCommissioning", + "attribute_id": 65531, + "attribute_type": "chip.clusters.Objects.GeneralCommissioning.Attributes.AttributeList", + "attribute_name": "AttributeList", + "value": [0, 1, 2, 3, 4, 65528, 65529, 65531, 65532, 65533] + }, + "0/49/0": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 49, + "cluster_type": "chip.clusters.Objects.NetworkCommissioning", + "cluster_name": "NetworkCommissioning", + "attribute_id": 0, + "attribute_type": "chip.clusters.Objects.NetworkCommissioning.Attributes.MaxNetworks", + "attribute_name": "MaxNetworks", + "value": 1 + }, + "0/49/1": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 49, + "cluster_type": "chip.clusters.Objects.NetworkCommissioning", + "cluster_name": "NetworkCommissioning", + "attribute_id": 1, + "attribute_type": "chip.clusters.Objects.NetworkCommissioning.Attributes.Networks", + "attribute_name": "Networks", + "value": [ + { + "networkID": "b'wifi-sm'", + "connected": true + } + ] + }, + "0/49/2": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 49, + "cluster_type": "chip.clusters.Objects.NetworkCommissioning", + "cluster_name": "NetworkCommissioning", + "attribute_id": 2, + "attribute_type": "chip.clusters.Objects.NetworkCommissioning.Attributes.ScanMaxTimeSeconds", + "attribute_name": "ScanMaxTimeSeconds", + "value": 10 + }, + "0/49/3": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 49, + "cluster_type": "chip.clusters.Objects.NetworkCommissioning", + "cluster_name": "NetworkCommissioning", + "attribute_id": 3, + "attribute_type": "chip.clusters.Objects.NetworkCommissioning.Attributes.ConnectMaxTimeSeconds", + "attribute_name": "ConnectMaxTimeSeconds", + "value": 30 + }, + "0/49/4": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 49, + "cluster_type": "chip.clusters.Objects.NetworkCommissioning", + "cluster_name": "NetworkCommissioning", + "attribute_id": 4, + "attribute_type": "chip.clusters.Objects.NetworkCommissioning.Attributes.InterfaceEnabled", + "attribute_name": "InterfaceEnabled", + "value": true + }, + "0/49/5": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 49, + "cluster_type": "chip.clusters.Objects.NetworkCommissioning", + "cluster_name": "NetworkCommissioning", + "attribute_id": 5, + "attribute_type": "chip.clusters.Objects.NetworkCommissioning.Attributes.LastNetworkingStatus", + "attribute_name": "LastNetworkingStatus", + "value": 0 + }, + "0/49/6": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 49, + "cluster_type": "chip.clusters.Objects.NetworkCommissioning", + "cluster_name": "NetworkCommissioning", + "attribute_id": 6, + "attribute_type": "chip.clusters.Objects.NetworkCommissioning.Attributes.LastNetworkID", + "attribute_name": "LastNetworkID", + "value": "b'wifi-sm'" + }, + "0/49/7": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 49, + "cluster_type": "chip.clusters.Objects.NetworkCommissioning", + "cluster_name": "NetworkCommissioning", + "attribute_id": 7, + "attribute_type": "chip.clusters.Objects.NetworkCommissioning.Attributes.LastConnectErrorValue", + "attribute_name": "LastConnectErrorValue", + "value": null + }, + "0/49/65532": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 49, + "cluster_type": "chip.clusters.Objects.NetworkCommissioning", + "cluster_name": "NetworkCommissioning", + "attribute_id": 65532, + "attribute_type": "chip.clusters.Objects.NetworkCommissioning.Attributes.FeatureMap", + "attribute_name": "FeatureMap", + "value": 1 + }, + "0/49/65533": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 49, + "cluster_type": "chip.clusters.Objects.NetworkCommissioning", + "cluster_name": "NetworkCommissioning", + "attribute_id": 65533, + "attribute_type": "chip.clusters.Objects.NetworkCommissioning.Attributes.ClusterRevision", + "attribute_name": "ClusterRevision", + "value": 1 + }, + "0/49/65528": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 49, + "cluster_type": "chip.clusters.Objects.NetworkCommissioning", + "cluster_name": "NetworkCommissioning", + "attribute_id": 65528, + "attribute_type": "chip.clusters.Objects.NetworkCommissioning.Attributes.GeneratedCommandList", + "attribute_name": "GeneratedCommandList", + "value": [1, 5, 7] + }, + "0/49/65529": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 49, + "cluster_type": "chip.clusters.Objects.NetworkCommissioning", + "cluster_name": "NetworkCommissioning", + "attribute_id": 65529, + "attribute_type": "chip.clusters.Objects.NetworkCommissioning.Attributes.AcceptedCommandList", + "attribute_name": "AcceptedCommandList", + "value": [0, 2, 4, 6, 8] + }, + "0/49/65531": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 49, + "cluster_type": "chip.clusters.Objects.NetworkCommissioning", + "cluster_name": "NetworkCommissioning", + "attribute_id": 65531, + "attribute_type": "chip.clusters.Objects.NetworkCommissioning.Attributes.AttributeList", + "attribute_name": "AttributeList", + "value": [0, 1, 2, 3, 4, 5, 6, 7, 65528, 65529, 65531, 65532, 65533] + }, + "0/50/65532": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 50, + "cluster_type": "chip.clusters.Objects.DiagnosticLogs", + "cluster_name": "DiagnosticLogs", + "attribute_id": 65532, + "attribute_type": "chip.clusters.Objects.DiagnosticLogs.Attributes.FeatureMap", + "attribute_name": "FeatureMap", + "value": 0 + }, + "0/50/65533": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 50, + "cluster_type": "chip.clusters.Objects.DiagnosticLogs", + "cluster_name": "DiagnosticLogs", + "attribute_id": 65533, + "attribute_type": "chip.clusters.Objects.DiagnosticLogs.Attributes.ClusterRevision", + "attribute_name": "ClusterRevision", + "value": 1 + }, + "0/50/65528": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 50, + "cluster_type": "chip.clusters.Objects.DiagnosticLogs", + "cluster_name": "DiagnosticLogs", + "attribute_id": 65528, + "attribute_type": "chip.clusters.Objects.DiagnosticLogs.Attributes.GeneratedCommandList", + "attribute_name": "GeneratedCommandList", + "value": [1] + }, + "0/50/65529": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 50, + "cluster_type": "chip.clusters.Objects.DiagnosticLogs", + "cluster_name": "DiagnosticLogs", + "attribute_id": 65529, + "attribute_type": "chip.clusters.Objects.DiagnosticLogs.Attributes.AcceptedCommandList", + "attribute_name": "AcceptedCommandList", + "value": [0] + }, + "0/50/65531": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 50, + "cluster_type": "chip.clusters.Objects.DiagnosticLogs", + "cluster_name": "DiagnosticLogs", + "attribute_id": 65531, + "attribute_type": "chip.clusters.Objects.DiagnosticLogs.Attributes.AttributeList", + "attribute_name": "AttributeList", + "value": [65528, 65529, 65531, 65532, 65533] + }, + "0/51/0": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 51, + "cluster_type": "chip.clusters.Objects.GeneralDiagnostics", + "cluster_name": "GeneralDiagnostics", + "attribute_id": 0, + "attribute_type": "chip.clusters.Objects.GeneralDiagnostics.Attributes.NetworkInterfaces", + "attribute_name": "NetworkInterfaces", + "value": [ + { + "name": "WIFI_STA_DEF", + "isOperational": true, + "offPremiseServicesReachableIPv4": null, + "offPremiseServicesReachableIPv6": null, + "hardwareAddress": "b\"\\x84\\xf7\\x03'\\xb5,\"", + "IPv4Addresses": ["b'\\xc0\\xa8\\x01\\x84'"], + "IPv6Addresses": [ + "b\"\\xfe\\x80\\x00\\x00\\x00\\x00\\x00\\x00\\x86\\xf7\\x03\\xff\\xfe'\\xb5,\"", + "b\"*\\x00\\xbb\\xa0\\x12\\x12\\x8f\\x00\\x86\\xf7\\x03\\xff\\xfe'\\xb5,\"", + "b\"\\xfd\\xf5\\nn\\xf0cO\\xed\\x86\\xf7\\x03\\xff\\xfe'\\xb5,\"" + ], + "type": 1 + } + ] + }, + "0/51/1": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 51, + "cluster_type": "chip.clusters.Objects.GeneralDiagnostics", + "cluster_name": "GeneralDiagnostics", + "attribute_id": 1, + "attribute_type": "chip.clusters.Objects.GeneralDiagnostics.Attributes.RebootCount", + "attribute_name": "RebootCount", + "value": 6 + }, + "0/51/2": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 51, + "cluster_type": "chip.clusters.Objects.GeneralDiagnostics", + "cluster_name": "GeneralDiagnostics", + "attribute_id": 2, + "attribute_type": "chip.clusters.Objects.GeneralDiagnostics.Attributes.UpTime", + "attribute_name": "UpTime", + "value": 31279 + }, + "0/51/3": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 51, + "cluster_type": "chip.clusters.Objects.GeneralDiagnostics", + "cluster_name": "GeneralDiagnostics", + "attribute_id": 3, + "attribute_type": "chip.clusters.Objects.GeneralDiagnostics.Attributes.TotalOperationalHours", + "attribute_name": "TotalOperationalHours", + "value": 8 + }, + "0/51/4": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 51, + "cluster_type": "chip.clusters.Objects.GeneralDiagnostics", + "cluster_name": "GeneralDiagnostics", + "attribute_id": 4, + "attribute_type": "chip.clusters.Objects.GeneralDiagnostics.Attributes.BootReasons", + "attribute_name": "BootReasons", + "value": 1 + }, + "0/51/5": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 51, + "cluster_type": "chip.clusters.Objects.GeneralDiagnostics", + "cluster_name": "GeneralDiagnostics", + "attribute_id": 5, + "attribute_type": "chip.clusters.Objects.GeneralDiagnostics.Attributes.ActiveHardwareFaults", + "attribute_name": "ActiveHardwareFaults", + "value": [] + }, + "0/51/6": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 51, + "cluster_type": "chip.clusters.Objects.GeneralDiagnostics", + "cluster_name": "GeneralDiagnostics", + "attribute_id": 6, + "attribute_type": "chip.clusters.Objects.GeneralDiagnostics.Attributes.ActiveRadioFaults", + "attribute_name": "ActiveRadioFaults", + "value": [] + }, + "0/51/7": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 51, + "cluster_type": "chip.clusters.Objects.GeneralDiagnostics", + "cluster_name": "GeneralDiagnostics", + "attribute_id": 7, + "attribute_type": "chip.clusters.Objects.GeneralDiagnostics.Attributes.ActiveNetworkFaults", + "attribute_name": "ActiveNetworkFaults", + "value": [] + }, + "0/51/8": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 51, + "cluster_type": "chip.clusters.Objects.GeneralDiagnostics", + "cluster_name": "GeneralDiagnostics", + "attribute_id": 8, + "attribute_type": "chip.clusters.Objects.GeneralDiagnostics.Attributes.TestEventTriggersEnabled", + "attribute_name": "TestEventTriggersEnabled", + "value": false + }, + "0/51/65532": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 51, + "cluster_type": "chip.clusters.Objects.GeneralDiagnostics", + "cluster_name": "GeneralDiagnostics", + "attribute_id": 65532, + "attribute_type": "chip.clusters.Objects.GeneralDiagnostics.Attributes.FeatureMap", + "attribute_name": "FeatureMap", + "value": 0 + }, + "0/51/65533": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 51, + "cluster_type": "chip.clusters.Objects.GeneralDiagnostics", + "cluster_name": "GeneralDiagnostics", + "attribute_id": 65533, + "attribute_type": "chip.clusters.Objects.GeneralDiagnostics.Attributes.ClusterRevision", + "attribute_name": "ClusterRevision", + "value": 1 + }, + "0/51/65528": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 51, + "cluster_type": "chip.clusters.Objects.GeneralDiagnostics", + "cluster_name": "GeneralDiagnostics", + "attribute_id": 65528, + "attribute_type": "chip.clusters.Objects.GeneralDiagnostics.Attributes.GeneratedCommandList", + "attribute_name": "GeneratedCommandList", + "value": [] + }, + "0/51/65529": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 51, + "cluster_type": "chip.clusters.Objects.GeneralDiagnostics", + "cluster_name": "GeneralDiagnostics", + "attribute_id": 65529, + "attribute_type": "chip.clusters.Objects.GeneralDiagnostics.Attributes.AcceptedCommandList", + "attribute_name": "AcceptedCommandList", + "value": [0] + }, + "0/51/65531": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 51, + "cluster_type": "chip.clusters.Objects.GeneralDiagnostics", + "cluster_name": "GeneralDiagnostics", + "attribute_id": 65531, + "attribute_type": "chip.clusters.Objects.GeneralDiagnostics.Attributes.AttributeList", + "attribute_name": "AttributeList", + "value": [0, 1, 2, 3, 4, 5, 6, 7, 8, 65528, 65529, 65531, 65532, 65533] + }, + "0/52/0": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 52, + "cluster_type": "chip.clusters.Objects.SoftwareDiagnostics", + "cluster_name": "SoftwareDiagnostics", + "attribute_id": 0, + "attribute_type": "chip.clusters.Objects.SoftwareDiagnostics.Attributes.ThreadMetrics", + "attribute_name": "ThreadMetrics", + "value": [] + }, + "0/52/1": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 52, + "cluster_type": "chip.clusters.Objects.SoftwareDiagnostics", + "cluster_name": "SoftwareDiagnostics", + "attribute_id": 1, + "attribute_type": "chip.clusters.Objects.SoftwareDiagnostics.Attributes.CurrentHeapFree", + "attribute_name": "CurrentHeapFree", + "value": 166480 + }, + "0/52/2": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 52, + "cluster_type": "chip.clusters.Objects.SoftwareDiagnostics", + "cluster_name": "SoftwareDiagnostics", + "attribute_id": 2, + "attribute_type": "chip.clusters.Objects.SoftwareDiagnostics.Attributes.CurrentHeapUsed", + "attribute_name": "CurrentHeapUsed", + "value": 86512 + }, + "0/52/3": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 52, + "cluster_type": "chip.clusters.Objects.SoftwareDiagnostics", + "cluster_name": "SoftwareDiagnostics", + "attribute_id": 3, + "attribute_type": "chip.clusters.Objects.SoftwareDiagnostics.Attributes.CurrentHeapHighWatermark", + "attribute_name": "CurrentHeapHighWatermark", + "value": 157052 + }, + "0/52/65532": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 52, + "cluster_type": "chip.clusters.Objects.SoftwareDiagnostics", + "cluster_name": "SoftwareDiagnostics", + "attribute_id": 65532, + "attribute_type": "chip.clusters.Objects.SoftwareDiagnostics.Attributes.FeatureMap", + "attribute_name": "FeatureMap", + "value": 0 + }, + "0/52/65533": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 52, + "cluster_type": "chip.clusters.Objects.SoftwareDiagnostics", + "cluster_name": "SoftwareDiagnostics", + "attribute_id": 65533, + "attribute_type": "chip.clusters.Objects.SoftwareDiagnostics.Attributes.ClusterRevision", + "attribute_name": "ClusterRevision", + "value": 1 + }, + "0/52/65528": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 52, + "cluster_type": "chip.clusters.Objects.SoftwareDiagnostics", + "cluster_name": "SoftwareDiagnostics", + "attribute_id": 65528, + "attribute_type": "chip.clusters.Objects.SoftwareDiagnostics.Attributes.GeneratedCommandList", + "attribute_name": "GeneratedCommandList", + "value": [] + }, + "0/52/65529": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 52, + "cluster_type": "chip.clusters.Objects.SoftwareDiagnostics", + "cluster_name": "SoftwareDiagnostics", + "attribute_id": 65529, + "attribute_type": "chip.clusters.Objects.SoftwareDiagnostics.Attributes.AcceptedCommandList", + "attribute_name": "AcceptedCommandList", + "value": [] + }, + "0/52/65531": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 52, + "cluster_type": "chip.clusters.Objects.SoftwareDiagnostics", + "cluster_name": "SoftwareDiagnostics", + "attribute_id": 65531, + "attribute_type": "chip.clusters.Objects.SoftwareDiagnostics.Attributes.AttributeList", + "attribute_name": "AttributeList", + "value": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533] + }, + "0/53/0": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 0, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.Channel", + "attribute_name": "Channel", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/1": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 1, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.RoutingRole", + "attribute_name": "RoutingRole", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/2": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 2, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.NetworkName", + "attribute_name": "NetworkName", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/3": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 3, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.PanId", + "attribute_name": "PanId", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/4": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 4, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.ExtendedPanId", + "attribute_name": "ExtendedPanId", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/5": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 5, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.MeshLocalPrefix", + "attribute_name": "MeshLocalPrefix", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/6": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 6, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.OverrunCount", + "attribute_name": "OverrunCount", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/7": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 7, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.NeighborTableList", + "attribute_name": "NeighborTableList", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/8": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 8, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.RouteTableList", + "attribute_name": "RouteTableList", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/9": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 9, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.PartitionId", + "attribute_name": "PartitionId", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/10": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 10, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.Weighting", + "attribute_name": "Weighting", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/11": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 11, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.DataVersion", + "attribute_name": "DataVersion", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/12": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 12, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.StableDataVersion", + "attribute_name": "StableDataVersion", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/13": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 13, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.LeaderRouterId", + "attribute_name": "LeaderRouterId", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/14": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 14, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.DetachedRoleCount", + "attribute_name": "DetachedRoleCount", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/15": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 15, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.ChildRoleCount", + "attribute_name": "ChildRoleCount", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/16": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 16, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.RouterRoleCount", + "attribute_name": "RouterRoleCount", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/17": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 17, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.LeaderRoleCount", + "attribute_name": "LeaderRoleCount", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/18": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 18, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.AttachAttemptCount", + "attribute_name": "AttachAttemptCount", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/19": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 19, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.PartitionIdChangeCount", + "attribute_name": "PartitionIdChangeCount", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/20": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 20, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.BetterPartitionAttachAttemptCount", + "attribute_name": "BetterPartitionAttachAttemptCount", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/21": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 21, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.ParentChangeCount", + "attribute_name": "ParentChangeCount", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/22": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 22, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.TxTotalCount", + "attribute_name": "TxTotalCount", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/23": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 23, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.TxUnicastCount", + "attribute_name": "TxUnicastCount", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/24": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 24, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.TxBroadcastCount", + "attribute_name": "TxBroadcastCount", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/25": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 25, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.TxAckRequestedCount", + "attribute_name": "TxAckRequestedCount", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/26": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 26, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.TxAckedCount", + "attribute_name": "TxAckedCount", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/27": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 27, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.TxNoAckRequestedCount", + "attribute_name": "TxNoAckRequestedCount", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/28": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 28, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.TxDataCount", + "attribute_name": "TxDataCount", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/29": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 29, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.TxDataPollCount", + "attribute_name": "TxDataPollCount", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/30": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 30, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.TxBeaconCount", + "attribute_name": "TxBeaconCount", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/31": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 31, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.TxBeaconRequestCount", + "attribute_name": "TxBeaconRequestCount", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/32": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 32, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.TxOtherCount", + "attribute_name": "TxOtherCount", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/33": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 33, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.TxRetryCount", + "attribute_name": "TxRetryCount", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/34": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 34, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.TxDirectMaxRetryExpiryCount", + "attribute_name": "TxDirectMaxRetryExpiryCount", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/35": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 35, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.TxIndirectMaxRetryExpiryCount", + "attribute_name": "TxIndirectMaxRetryExpiryCount", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/36": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 36, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.TxErrCcaCount", + "attribute_name": "TxErrCcaCount", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/37": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 37, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.TxErrAbortCount", + "attribute_name": "TxErrAbortCount", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/38": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 38, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.TxErrBusyChannelCount", + "attribute_name": "TxErrBusyChannelCount", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/39": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 39, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.RxTotalCount", + "attribute_name": "RxTotalCount", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/40": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 40, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.RxUnicastCount", + "attribute_name": "RxUnicastCount", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/41": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 41, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.RxBroadcastCount", + "attribute_name": "RxBroadcastCount", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/42": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 42, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.RxDataCount", + "attribute_name": "RxDataCount", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/43": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 43, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.RxDataPollCount", + "attribute_name": "RxDataPollCount", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/44": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 44, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.RxBeaconCount", + "attribute_name": "RxBeaconCount", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/45": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 45, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.RxBeaconRequestCount", + "attribute_name": "RxBeaconRequestCount", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/46": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 46, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.RxOtherCount", + "attribute_name": "RxOtherCount", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/47": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 47, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.RxAddressFilteredCount", + "attribute_name": "RxAddressFilteredCount", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/48": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 48, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.RxDestAddrFilteredCount", + "attribute_name": "RxDestAddrFilteredCount", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/49": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 49, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.RxDuplicatedCount", + "attribute_name": "RxDuplicatedCount", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/50": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 50, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.RxErrNoFrameCount", + "attribute_name": "RxErrNoFrameCount", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/51": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 51, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.RxErrUnknownNeighborCount", + "attribute_name": "RxErrUnknownNeighborCount", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/52": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 52, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.RxErrInvalidSrcAddrCount", + "attribute_name": "RxErrInvalidSrcAddrCount", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/53": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 53, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.RxErrSecCount", + "attribute_name": "RxErrSecCount", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/54": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 54, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.RxErrFcsCount", + "attribute_name": "RxErrFcsCount", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/55": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 55, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.RxErrOtherCount", + "attribute_name": "RxErrOtherCount", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/56": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 56, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.ActiveTimestamp", + "attribute_name": "ActiveTimestamp", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/57": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 57, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.PendingTimestamp", + "attribute_name": "PendingTimestamp", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/58": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 58, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.Delay", + "attribute_name": "Delay", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/59": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 59, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.SecurityPolicy", + "attribute_name": "SecurityPolicy", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/60": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 60, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.ChannelPage0Mask", + "attribute_name": "ChannelPage0Mask", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/61": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 61, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.OperationalDatasetComponents", + "attribute_name": "OperationalDatasetComponents", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/62": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 62, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.ActiveNetworkFaultsList", + "attribute_name": "ActiveNetworkFaultsList", + "value": { + "TLVValue": null, + "Reason": "InteractionModelError: Failure (0x1)" + } + }, + "0/53/65532": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 65532, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.FeatureMap", + "attribute_name": "FeatureMap", + "value": 15 + }, + "0/53/65533": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 65533, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.ClusterRevision", + "attribute_name": "ClusterRevision", + "value": 1 + }, + "0/53/65528": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 65528, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.GeneratedCommandList", + "attribute_name": "GeneratedCommandList", + "value": [] + }, + "0/53/65529": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 65529, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.AcceptedCommandList", + "attribute_name": "AcceptedCommandList", + "value": [0] + }, + "0/53/65531": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 53, + "cluster_type": "chip.clusters.Objects.ThreadNetworkDiagnostics", + "cluster_name": "ThreadNetworkDiagnostics", + "attribute_id": 65531, + "attribute_type": "chip.clusters.Objects.ThreadNetworkDiagnostics.Attributes.AttributeList", + "attribute_name": "AttributeList", + "value": [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, + 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, + 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, + 56, 57, 58, 59, 60, 61, 62, 65528, 65529, 65531, 65532, 65533 + ] + }, + "0/54/0": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 54, + "cluster_type": "chip.clusters.Objects.WiFiNetworkDiagnostics", + "cluster_name": "WiFiNetworkDiagnostics", + "attribute_id": 0, + "attribute_type": "chip.clusters.Objects.WiFiNetworkDiagnostics.Attributes.Bssid", + "attribute_name": "Bssid", + "value": "b'r\\xa7A\\x91\\xf1!'" + }, + "0/54/1": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 54, + "cluster_type": "chip.clusters.Objects.WiFiNetworkDiagnostics", + "cluster_name": "WiFiNetworkDiagnostics", + "attribute_id": 1, + "attribute_type": "chip.clusters.Objects.WiFiNetworkDiagnostics.Attributes.SecurityType", + "attribute_name": "SecurityType", + "value": 4 + }, + "0/54/2": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 54, + "cluster_type": "chip.clusters.Objects.WiFiNetworkDiagnostics", + "cluster_name": "WiFiNetworkDiagnostics", + "attribute_id": 2, + "attribute_type": "chip.clusters.Objects.WiFiNetworkDiagnostics.Attributes.WiFiVersion", + "attribute_name": "WiFiVersion", + "value": 3 + }, + "0/54/3": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 54, + "cluster_type": "chip.clusters.Objects.WiFiNetworkDiagnostics", + "cluster_name": "WiFiNetworkDiagnostics", + "attribute_id": 3, + "attribute_type": "chip.clusters.Objects.WiFiNetworkDiagnostics.Attributes.ChannelNumber", + "attribute_name": "ChannelNumber", + "value": 6 + }, + "0/54/4": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 54, + "cluster_type": "chip.clusters.Objects.WiFiNetworkDiagnostics", + "cluster_name": "WiFiNetworkDiagnostics", + "attribute_id": 4, + "attribute_type": "chip.clusters.Objects.WiFiNetworkDiagnostics.Attributes.Rssi", + "attribute_name": "Rssi", + "value": -61 + }, + "0/54/5": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 54, + "cluster_type": "chip.clusters.Objects.WiFiNetworkDiagnostics", + "cluster_name": "WiFiNetworkDiagnostics", + "attribute_id": 5, + "attribute_type": "chip.clusters.Objects.WiFiNetworkDiagnostics.Attributes.BeaconLostCount", + "attribute_name": "BeaconLostCount", + "value": null + }, + "0/54/6": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 54, + "cluster_type": "chip.clusters.Objects.WiFiNetworkDiagnostics", + "cluster_name": "WiFiNetworkDiagnostics", + "attribute_id": 6, + "attribute_type": "chip.clusters.Objects.WiFiNetworkDiagnostics.Attributes.BeaconRxCount", + "attribute_name": "BeaconRxCount", + "value": null + }, + "0/54/7": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 54, + "cluster_type": "chip.clusters.Objects.WiFiNetworkDiagnostics", + "cluster_name": "WiFiNetworkDiagnostics", + "attribute_id": 7, + "attribute_type": "chip.clusters.Objects.WiFiNetworkDiagnostics.Attributes.PacketMulticastRxCount", + "attribute_name": "PacketMulticastRxCount", + "value": null + }, + "0/54/8": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 54, + "cluster_type": "chip.clusters.Objects.WiFiNetworkDiagnostics", + "cluster_name": "WiFiNetworkDiagnostics", + "attribute_id": 8, + "attribute_type": "chip.clusters.Objects.WiFiNetworkDiagnostics.Attributes.PacketMulticastTxCount", + "attribute_name": "PacketMulticastTxCount", + "value": null + }, + "0/54/9": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 54, + "cluster_type": "chip.clusters.Objects.WiFiNetworkDiagnostics", + "cluster_name": "WiFiNetworkDiagnostics", + "attribute_id": 9, + "attribute_type": "chip.clusters.Objects.WiFiNetworkDiagnostics.Attributes.PacketUnicastRxCount", + "attribute_name": "PacketUnicastRxCount", + "value": null + }, + "0/54/10": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 54, + "cluster_type": "chip.clusters.Objects.WiFiNetworkDiagnostics", + "cluster_name": "WiFiNetworkDiagnostics", + "attribute_id": 10, + "attribute_type": "chip.clusters.Objects.WiFiNetworkDiagnostics.Attributes.PacketUnicastTxCount", + "attribute_name": "PacketUnicastTxCount", + "value": null + }, + "0/54/11": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 54, + "cluster_type": "chip.clusters.Objects.WiFiNetworkDiagnostics", + "cluster_name": "WiFiNetworkDiagnostics", + "attribute_id": 11, + "attribute_type": "chip.clusters.Objects.WiFiNetworkDiagnostics.Attributes.CurrentMaxRate", + "attribute_name": "CurrentMaxRate", + "value": null + }, + "0/54/12": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 54, + "cluster_type": "chip.clusters.Objects.WiFiNetworkDiagnostics", + "cluster_name": "WiFiNetworkDiagnostics", + "attribute_id": 12, + "attribute_type": "chip.clusters.Objects.WiFiNetworkDiagnostics.Attributes.OverrunCount", + "attribute_name": "OverrunCount", + "value": null + }, + "0/54/65532": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 54, + "cluster_type": "chip.clusters.Objects.WiFiNetworkDiagnostics", + "cluster_name": "WiFiNetworkDiagnostics", + "attribute_id": 65532, + "attribute_type": "chip.clusters.Objects.WiFiNetworkDiagnostics.Attributes.FeatureMap", + "attribute_name": "FeatureMap", + "value": 3 + }, + "0/54/65533": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 54, + "cluster_type": "chip.clusters.Objects.WiFiNetworkDiagnostics", + "cluster_name": "WiFiNetworkDiagnostics", + "attribute_id": 65533, + "attribute_type": "chip.clusters.Objects.WiFiNetworkDiagnostics.Attributes.ClusterRevision", + "attribute_name": "ClusterRevision", + "value": 1 + }, + "0/54/65528": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 54, + "cluster_type": "chip.clusters.Objects.WiFiNetworkDiagnostics", + "cluster_name": "WiFiNetworkDiagnostics", + "attribute_id": 65528, + "attribute_type": "chip.clusters.Objects.WiFiNetworkDiagnostics.Attributes.GeneratedCommandList", + "attribute_name": "GeneratedCommandList", + "value": [] + }, + "0/54/65529": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 54, + "cluster_type": "chip.clusters.Objects.WiFiNetworkDiagnostics", + "cluster_name": "WiFiNetworkDiagnostics", + "attribute_id": 65529, + "attribute_type": "chip.clusters.Objects.WiFiNetworkDiagnostics.Attributes.AcceptedCommandList", + "attribute_name": "AcceptedCommandList", + "value": [0] + }, + "0/54/65531": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 54, + "cluster_type": "chip.clusters.Objects.WiFiNetworkDiagnostics", + "cluster_name": "WiFiNetworkDiagnostics", + "attribute_id": 65531, + "attribute_type": "chip.clusters.Objects.WiFiNetworkDiagnostics.Attributes.AttributeList", + "attribute_name": "AttributeList", + "value": [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 65528, 65529, 65531, 65532, + 65533 + ] + }, + "0/55/0": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 55, + "cluster_type": "chip.clusters.Objects.EthernetNetworkDiagnostics", + "cluster_name": "EthernetNetworkDiagnostics", + "attribute_id": 0, + "attribute_type": "chip.clusters.Objects.EthernetNetworkDiagnostics.Attributes.PHYRate", + "attribute_name": "PHYRate", + "value": null + }, + "0/55/1": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 55, + "cluster_type": "chip.clusters.Objects.EthernetNetworkDiagnostics", + "cluster_name": "EthernetNetworkDiagnostics", + "attribute_id": 1, + "attribute_type": "chip.clusters.Objects.EthernetNetworkDiagnostics.Attributes.FullDuplex", + "attribute_name": "FullDuplex", + "value": null + }, + "0/55/2": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 55, + "cluster_type": "chip.clusters.Objects.EthernetNetworkDiagnostics", + "cluster_name": "EthernetNetworkDiagnostics", + "attribute_id": 2, + "attribute_type": "chip.clusters.Objects.EthernetNetworkDiagnostics.Attributes.PacketRxCount", + "attribute_name": "PacketRxCount", + "value": 0 + }, + "0/55/3": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 55, + "cluster_type": "chip.clusters.Objects.EthernetNetworkDiagnostics", + "cluster_name": "EthernetNetworkDiagnostics", + "attribute_id": 3, + "attribute_type": "chip.clusters.Objects.EthernetNetworkDiagnostics.Attributes.PacketTxCount", + "attribute_name": "PacketTxCount", + "value": 0 + }, + "0/55/4": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 55, + "cluster_type": "chip.clusters.Objects.EthernetNetworkDiagnostics", + "cluster_name": "EthernetNetworkDiagnostics", + "attribute_id": 4, + "attribute_type": "chip.clusters.Objects.EthernetNetworkDiagnostics.Attributes.TxErrCount", + "attribute_name": "TxErrCount", + "value": 0 + }, + "0/55/5": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 55, + "cluster_type": "chip.clusters.Objects.EthernetNetworkDiagnostics", + "cluster_name": "EthernetNetworkDiagnostics", + "attribute_id": 5, + "attribute_type": "chip.clusters.Objects.EthernetNetworkDiagnostics.Attributes.CollisionCount", + "attribute_name": "CollisionCount", + "value": 0 + }, + "0/55/6": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 55, + "cluster_type": "chip.clusters.Objects.EthernetNetworkDiagnostics", + "cluster_name": "EthernetNetworkDiagnostics", + "attribute_id": 6, + "attribute_type": "chip.clusters.Objects.EthernetNetworkDiagnostics.Attributes.OverrunCount", + "attribute_name": "OverrunCount", + "value": 0 + }, + "0/55/7": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 55, + "cluster_type": "chip.clusters.Objects.EthernetNetworkDiagnostics", + "cluster_name": "EthernetNetworkDiagnostics", + "attribute_id": 7, + "attribute_type": "chip.clusters.Objects.EthernetNetworkDiagnostics.Attributes.CarrierDetect", + "attribute_name": "CarrierDetect", + "value": null + }, + "0/55/8": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 55, + "cluster_type": "chip.clusters.Objects.EthernetNetworkDiagnostics", + "cluster_name": "EthernetNetworkDiagnostics", + "attribute_id": 8, + "attribute_type": "chip.clusters.Objects.EthernetNetworkDiagnostics.Attributes.TimeSinceReset", + "attribute_name": "TimeSinceReset", + "value": 0 + }, + "0/55/65532": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 55, + "cluster_type": "chip.clusters.Objects.EthernetNetworkDiagnostics", + "cluster_name": "EthernetNetworkDiagnostics", + "attribute_id": 65532, + "attribute_type": "chip.clusters.Objects.EthernetNetworkDiagnostics.Attributes.FeatureMap", + "attribute_name": "FeatureMap", + "value": 3 + }, + "0/55/65533": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 55, + "cluster_type": "chip.clusters.Objects.EthernetNetworkDiagnostics", + "cluster_name": "EthernetNetworkDiagnostics", + "attribute_id": 65533, + "attribute_type": "chip.clusters.Objects.EthernetNetworkDiagnostics.Attributes.ClusterRevision", + "attribute_name": "ClusterRevision", + "value": 1 + }, + "0/55/65528": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 55, + "cluster_type": "chip.clusters.Objects.EthernetNetworkDiagnostics", + "cluster_name": "EthernetNetworkDiagnostics", + "attribute_id": 65528, + "attribute_type": "chip.clusters.Objects.EthernetNetworkDiagnostics.Attributes.GeneratedCommandList", + "attribute_name": "GeneratedCommandList", + "value": [] + }, + "0/55/65529": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 55, + "cluster_type": "chip.clusters.Objects.EthernetNetworkDiagnostics", + "cluster_name": "EthernetNetworkDiagnostics", + "attribute_id": 65529, + "attribute_type": "chip.clusters.Objects.EthernetNetworkDiagnostics.Attributes.AcceptedCommandList", + "attribute_name": "AcceptedCommandList", + "value": [0] + }, + "0/55/65531": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 55, + "cluster_type": "chip.clusters.Objects.EthernetNetworkDiagnostics", + "cluster_name": "EthernetNetworkDiagnostics", + "attribute_id": 65531, + "attribute_type": "chip.clusters.Objects.EthernetNetworkDiagnostics.Attributes.AttributeList", + "attribute_name": "AttributeList", + "value": [0, 1, 2, 3, 4, 5, 6, 7, 8, 65528, 65529, 65531, 65532, 65533] + }, + "0/59/65532": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 59, + "cluster_type": "chip.clusters.Objects.Switch", + "cluster_name": "Switch", + "attribute_id": 65532, + "attribute_type": "chip.clusters.Objects.Switch.Attributes.FeatureMap", + "attribute_name": "FeatureMap", + "value": 0 + }, + "0/59/65533": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 59, + "cluster_type": "chip.clusters.Objects.Switch", + "cluster_name": "Switch", + "attribute_id": 65533, + "attribute_type": "chip.clusters.Objects.Switch.Attributes.ClusterRevision", + "attribute_name": "ClusterRevision", + "value": 1 + }, + "0/59/65528": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 59, + "cluster_type": "chip.clusters.Objects.Switch", + "cluster_name": "Switch", + "attribute_id": 65528, + "attribute_type": "chip.clusters.Objects.Switch.Attributes.GeneratedCommandList", + "attribute_name": "GeneratedCommandList", + "value": [] + }, + "0/59/65529": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 59, + "cluster_type": "chip.clusters.Objects.Switch", + "cluster_name": "Switch", + "attribute_id": 65529, + "attribute_type": "chip.clusters.Objects.Switch.Attributes.AcceptedCommandList", + "attribute_name": "AcceptedCommandList", + "value": [] + }, + "0/59/65531": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 59, + "cluster_type": "chip.clusters.Objects.Switch", + "cluster_name": "Switch", + "attribute_id": 65531, + "attribute_type": "chip.clusters.Objects.Switch.Attributes.AttributeList", + "attribute_name": "AttributeList", + "value": [65528, 65529, 65531, 65532, 65533] + }, + "0/60/0": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 60, + "cluster_type": "chip.clusters.Objects.AdministratorCommissioning", + "cluster_name": "AdministratorCommissioning", + "attribute_id": 0, + "attribute_type": "chip.clusters.Objects.AdministratorCommissioning.Attributes.WindowStatus", + "attribute_name": "WindowStatus", + "value": 0 + }, + "0/60/1": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 60, + "cluster_type": "chip.clusters.Objects.AdministratorCommissioning", + "cluster_name": "AdministratorCommissioning", + "attribute_id": 1, + "attribute_type": "chip.clusters.Objects.AdministratorCommissioning.Attributes.AdminFabricIndex", + "attribute_name": "AdminFabricIndex", + "value": null + }, + "0/60/2": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 60, + "cluster_type": "chip.clusters.Objects.AdministratorCommissioning", + "cluster_name": "AdministratorCommissioning", + "attribute_id": 2, + "attribute_type": "chip.clusters.Objects.AdministratorCommissioning.Attributes.AdminVendorId", + "attribute_name": "AdminVendorId", + "value": null + }, + "0/60/65532": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 60, + "cluster_type": "chip.clusters.Objects.AdministratorCommissioning", + "cluster_name": "AdministratorCommissioning", + "attribute_id": 65532, + "attribute_type": "chip.clusters.Objects.AdministratorCommissioning.Attributes.FeatureMap", + "attribute_name": "FeatureMap", + "value": 0 + }, + "0/60/65533": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 60, + "cluster_type": "chip.clusters.Objects.AdministratorCommissioning", + "cluster_name": "AdministratorCommissioning", + "attribute_id": 65533, + "attribute_type": "chip.clusters.Objects.AdministratorCommissioning.Attributes.ClusterRevision", + "attribute_name": "ClusterRevision", + "value": 1 + }, + "0/60/65528": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 60, + "cluster_type": "chip.clusters.Objects.AdministratorCommissioning", + "cluster_name": "AdministratorCommissioning", + "attribute_id": 65528, + "attribute_type": "chip.clusters.Objects.AdministratorCommissioning.Attributes.GeneratedCommandList", + "attribute_name": "GeneratedCommandList", + "value": [] + }, + "0/60/65529": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 60, + "cluster_type": "chip.clusters.Objects.AdministratorCommissioning", + "cluster_name": "AdministratorCommissioning", + "attribute_id": 65529, + "attribute_type": "chip.clusters.Objects.AdministratorCommissioning.Attributes.AcceptedCommandList", + "attribute_name": "AcceptedCommandList", + "value": [0, 1, 2] + }, + "0/60/65531": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 60, + "cluster_type": "chip.clusters.Objects.AdministratorCommissioning", + "cluster_name": "AdministratorCommissioning", + "attribute_id": 65531, + "attribute_type": "chip.clusters.Objects.AdministratorCommissioning.Attributes.AttributeList", + "attribute_name": "AttributeList", + "value": [0, 1, 2, 65528, 65529, 65531, 65532, 65533] + }, + "0/62/0": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 62, + "cluster_type": "chip.clusters.Objects.OperationalCredentials", + "cluster_name": "OperationalCredentials", + "attribute_id": 0, + "attribute_type": "chip.clusters.Objects.OperationalCredentials.Attributes.NOCs", + "attribute_name": "NOCs", + "value": [ + { + "noc": "b'\\x150\\x01\\x01\\x01$\\x02\\x017\\x03$\\x13\\x02\\x18&\\x04\\x80\"\\x81\\'&\\x05\\x80%M:7\\x06$\\x15\\x01$\\x11\\x01\\x18$\\x07\\x01$\\x08\\x010\\tA\\x04Z\\xec\\xd9\\x11h\\'J[%&\\xe1\\xd8\\xa0\\xb15\\x01\\x9c\\x83\\xf8\\x1a\\x9bg\\xbb\\x81e\\x18\\x8b\\xc8\\x9dr\\xed\\xe7\\xc3\\\\\\xd21\\x99\\xc1\\xdb\\xfe\\xd0}\\xf5)\\xaeMq\\xd5\\xeeRg\\x16\\xd5%\\x9c\\xfcD\\t\\xcbQ\\xc8\\x83*\\x997\\n5\\x01(\\x01\\x18$\\x02\\x016\\x03\\x04\\x02\\x04\\x01\\x180\\x04\\x14\\xca\\xe2\\x07\\xdb\\x89 MatterNode: """Fixture for a light node.""" return await setup_integration_with_node_fixture( - hass, hass_storage, "lighting-example-app" + hass, "dimmable-light", matter_client ) -async def test_turn_on(hass: HomeAssistant, light_node: MatterNode) -> None: +async def test_turn_on( + hass: HomeAssistant, + matter_client: MagicMock, + light_node: MatterNode, +) -> None: """Test turning on a light.""" - light_node.matter.client.mock_command(clusters.OnOff.Commands.On, None) + state = hass.states.get("light.mock_dimmable_light") + assert state + assert state.state == "on" + + set_node_attribute(light_node, 1, 6, 0, False) + await trigger_subscription_callback(hass, matter_client) + + state = hass.states.get("light.mock_dimmable_light") + assert state + assert state.state == "off" await hass.services.async_call( "light", "turn_on", { - "entity_id": "light.my_cool_light", + "entity_id": "light.mock_dimmable_light", }, blocking=True, ) - assert len(light_node.matter.client.mock_sent_commands) == 1 - args = light_node.matter.client.mock_sent_commands[0] - assert args["nodeid"] == light_node.node_id - assert args["endpoint"] == 1 - - light_node.matter.client.mock_command( - clusters.LevelControl.Commands.MoveToLevelWithOnOff, None + assert matter_client.send_device_command.call_count == 1 + assert matter_client.send_device_command.call_args == call( + node_id=light_node.node_id, + endpoint=1, + command=clusters.OnOff.Commands.On(), ) + matter_client.send_device_command.reset_mock() await hass.services.async_call( "light", "turn_on", { - "entity_id": "light.my_cool_light", + "entity_id": "light.mock_dimmable_light", "brightness": 128, }, blocking=True, ) - assert len(light_node.matter.client.mock_sent_commands) == 2 - args = light_node.matter.client.mock_sent_commands[1] - assert args["nodeid"] == light_node.node_id - assert args["endpoint"] == 1 - assert args["payload"].level == 127 - assert args["payload"].transitionTime == 0 + assert matter_client.send_device_command.call_count == 1 + assert matter_client.send_device_command.call_args == call( + node_id=light_node.node_id, + endpoint=1, + command=clusters.LevelControl.Commands.MoveToLevelWithOnOff( + level=128, + transitionTime=0, + ), + ) -async def test_turn_off(hass: HomeAssistant, light_node: MatterNode) -> None: +async def test_turn_off( + hass: HomeAssistant, + matter_client: MagicMock, + light_node: MatterNode, +) -> None: """Test turning off a light.""" - light_node.matter.client.mock_command(clusters.OnOff.Commands.Off, None) + state = hass.states.get("light.mock_dimmable_light") + assert state + assert state.state == "on" await hass.services.async_call( "light", "turn_off", { - "entity_id": "light.my_cool_light", + "entity_id": "light.mock_dimmable_light", }, blocking=True, ) - assert len(light_node.matter.client.mock_sent_commands) == 1 - args = light_node.matter.client.mock_sent_commands[0] - assert args["nodeid"] == light_node.node_id - assert args["endpoint"] == 1 + assert matter_client.send_device_command.call_count == 1 + assert matter_client.send_device_command.call_args == call( + node_id=light_node.node_id, + endpoint=1, + command=clusters.OnOff.Commands.Off(), + ) From 861a8ee3c77def91138c99fd8f2158f4226c840e Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Tue, 6 Dec 2022 04:25:37 -0500 Subject: [PATCH 076/174] Fix zwave_js_value_updated event (#83358) * Fix zwave_js_value_updated event * Fix zwave_js_value_updated event --- homeassistant/components/zwave_js/__init__.py | 2 +- tests/components/zwave_js/test_events.py | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/zwave_js/__init__.py b/homeassistant/components/zwave_js/__init__.py index c492cd8618f..e7b17b30b03 100644 --- a/homeassistant/components/zwave_js/__init__.py +++ b/homeassistant/components/zwave_js/__init__.py @@ -717,7 +717,7 @@ class NodeEvents: raw_value = value_ = value.value if value.metadata.states: - value_ = value.metadata.states.get(str(value), value_) + value_ = value.metadata.states.get(str(value_), value_) self.hass.bus.async_fire( ZWAVE_JS_VALUE_UPDATED_EVENT, diff --git a/tests/components/zwave_js/test_events.py b/tests/components/zwave_js/test_events.py index 8552f69936d..8201016b9d9 100644 --- a/tests/components/zwave_js/test_events.py +++ b/tests/components/zwave_js/test_events.py @@ -234,6 +234,8 @@ async def test_notifications(hass, hank_binary_switch, integration, client): async def test_value_updated(hass, vision_security_zl7432, integration, client): """Test value updated events.""" node = vision_security_zl7432 + # Add states to the value we are updating to ensure the translation happens + node.values["7-37-1-currentValue"].metadata.data["states"] = {"1": "on", "0": "off"} events = async_capture_events(hass, "zwave_js_value_updated") event = Event( @@ -266,7 +268,7 @@ async def test_value_updated(hass, vision_security_zl7432, integration, client): assert events[0].data["endpoint"] == 1 assert events[0].data["property_name"] == "currentValue" assert events[0].data["property"] == "currentValue" - assert events[0].data["value"] == 1 + assert events[0].data["value"] == "on" assert events[0].data["value_raw"] == 1 # Try a value updated event on a value we aren't watching to make sure From 3a7efddb4b705c270b674f54b2fb179a766ba037 Mon Sep 17 00:00:00 2001 From: epenet <6771947+epenet@users.noreply.github.com> Date: Tue, 6 Dec 2022 10:29:23 +0100 Subject: [PATCH 077/174] Fix sensor schema for device classes (#83378) --- homeassistant/components/sensor/device_condition.py | 8 +++++++- homeassistant/components/sensor/device_trigger.py | 6 ++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/sensor/device_condition.py b/homeassistant/components/sensor/device_condition.py index 55e993f46ba..34a2590ce8e 100644 --- a/homeassistant/components/sensor/device_condition.py +++ b/homeassistant/components/sensor/device_condition.py @@ -128,16 +128,18 @@ CONDITION_SCHEMA = vol.All( CONF_IS_GAS, CONF_IS_HUMIDITY, CONF_IS_ILLUMINANCE, - CONF_IS_OZONE, CONF_IS_MOISTURE, CONF_IS_NITROGEN_DIOXIDE, CONF_IS_NITROGEN_MONOXIDE, CONF_IS_NITROUS_OXIDE, + CONF_IS_OZONE, CONF_IS_POWER, CONF_IS_POWER_FACTOR, CONF_IS_PM1, CONF_IS_PM10, CONF_IS_PM25, + CONF_IS_PRECIPITATION, + CONF_IS_PRECIPITATION_INTENSITY, CONF_IS_PRESSURE, CONF_IS_REACTIVE_POWER, CONF_IS_SIGNAL_STRENGTH, @@ -145,6 +147,10 @@ CONDITION_SCHEMA = vol.All( CONF_IS_TEMPERATURE, CONF_IS_VOLATILE_ORGANIC_COMPOUNDS, CONF_IS_VOLTAGE, + CONF_IS_VOLUME, + CONF_IS_WATER, + CONF_IS_WEIGHT, + CONF_IS_WIND_SPEED, CONF_IS_VALUE, ] ), diff --git a/homeassistant/components/sensor/device_trigger.py b/homeassistant/components/sensor/device_trigger.py index ef724881c52..c1b0699664e 100644 --- a/homeassistant/components/sensor/device_trigger.py +++ b/homeassistant/components/sensor/device_trigger.py @@ -138,6 +138,8 @@ TRIGGER_SCHEMA = vol.All( CONF_PM25, CONF_POWER, CONF_POWER_FACTOR, + CONF_PRECIPITATION, + CONF_PRECIPITATION_INTENSITY, CONF_PRESSURE, CONF_REACTIVE_POWER, CONF_SIGNAL_STRENGTH, @@ -145,6 +147,10 @@ TRIGGER_SCHEMA = vol.All( CONF_TEMPERATURE, CONF_VOLATILE_ORGANIC_COMPOUNDS, CONF_VOLTAGE, + CONF_VOLUME, + CONF_WATER, + CONF_WEIGHT, + CONF_WIND_SPEED, CONF_VALUE, ] ), From 048553cd02a45956c3b7608cb405314517553fa1 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 6 Dec 2022 12:14:15 +0100 Subject: [PATCH 078/174] Fix Goalzero time to empty sensor device class (#83389) * Fix Goalzero time to empty sensor device class * Fix tests --- homeassistant/components/goalzero/sensor.py | 2 +- tests/components/goalzero/test_sensor.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/goalzero/sensor.py b/homeassistant/components/goalzero/sensor.py index 345c3b41f7d..2538639883c 100644 --- a/homeassistant/components/goalzero/sensor.py +++ b/homeassistant/components/goalzero/sensor.py @@ -91,7 +91,7 @@ SENSOR_TYPES: tuple[SensorEntityDescription, ...] = ( SensorEntityDescription( key="timeToEmptyFull", name="Time to empty/full", - device_class=TIME_MINUTES, + device_class=SensorDeviceClass.DURATION, native_unit_of_measurement=TIME_MINUTES, ), SensorEntityDescription( diff --git a/tests/components/goalzero/test_sensor.py b/tests/components/goalzero/test_sensor.py index e61015a4925..402c03f2e51 100644 --- a/tests/components/goalzero/test_sensor.py +++ b/tests/components/goalzero/test_sensor.py @@ -77,7 +77,7 @@ async def test_sensors( assert state.attributes.get(ATTR_STATE_CLASS) is None state = hass.states.get(f"sensor.{DEFAULT_NAME}_time_to_empty_full") assert state.state == "-1" - assert state.attributes.get(ATTR_DEVICE_CLASS) == TIME_MINUTES + assert state.attributes.get(ATTR_DEVICE_CLASS) == SensorDeviceClass.DURATION assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == TIME_MINUTES assert state.attributes.get(ATTR_STATE_CLASS) is None state = hass.states.get(f"sensor.{DEFAULT_NAME}_temperature") From 1cfd292075fbb3a4cf413ed05c14d6821db35ff3 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Tue, 6 Dec 2022 18:41:09 +0100 Subject: [PATCH 079/174] Bypass zwave_js config validation if driver not ready (#83410) --- .../zwave_js/device_automation_helpers.py | 10 ++- .../zwave_js/triggers/trigger_helpers.py | 21 ++++-- .../zwave_js/test_device_trigger.py | 68 +++++++++++++++++++ tests/components/zwave_js/test_trigger.py | 59 ++++++++++++++++ 4 files changed, 149 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/zwave_js/device_automation_helpers.py b/homeassistant/components/zwave_js/device_automation_helpers.py index 25cce978df1..11c4fde3137 100644 --- a/homeassistant/components/zwave_js/device_automation_helpers.py +++ b/homeassistant/components/zwave_js/device_automation_helpers.py @@ -4,6 +4,7 @@ from __future__ import annotations from typing import cast import voluptuous as vol +from zwave_js_server.client import Client as ZwaveClient from zwave_js_server.const import ConfigurationValueType from zwave_js_server.model.node import Node from zwave_js_server.model.value import ConfigurationValue @@ -12,7 +13,7 @@ from homeassistant.config_entries import ConfigEntryState from homeassistant.core import HomeAssistant, callback from homeassistant.helpers import device_registry as dr -from .const import DOMAIN +from .const import DATA_CLIENT, DOMAIN NODE_STATUSES = ["asleep", "awake", "dead", "alive"] @@ -66,4 +67,9 @@ def async_bypass_dynamic_config_validation(hass: HomeAssistant, device_id: str) ), None, ) - return not entry + if not entry: + return True + + # The driver may not be ready when the config entry is loaded. + client: ZwaveClient = hass.data[DOMAIN][entry.entry_id][DATA_CLIENT] + return client.driver is None diff --git a/homeassistant/components/zwave_js/triggers/trigger_helpers.py b/homeassistant/components/zwave_js/triggers/trigger_helpers.py index 706c4fc0aca..0fd9c3b4291 100644 --- a/homeassistant/components/zwave_js/triggers/trigger_helpers.py +++ b/homeassistant/components/zwave_js/triggers/trigger_helpers.py @@ -1,11 +1,13 @@ """Helpers for Z-Wave JS custom triggers.""" +from zwave_js_server.client import Client as ZwaveClient + from homeassistant.config_entries import ConfigEntryState from homeassistant.const import ATTR_DEVICE_ID, ATTR_ENTITY_ID from homeassistant.core import HomeAssistant, callback from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.helpers.typing import ConfigType -from ..const import ATTR_CONFIG_ENTRY_ID, DOMAIN +from ..const import ATTR_CONFIG_ENTRY_ID, DATA_CLIENT, DOMAIN @callback @@ -19,9 +21,8 @@ def async_bypass_dynamic_config_validation( ent_reg = er.async_get(hass) trigger_devices = config.get(ATTR_DEVICE_ID, []) trigger_entities = config.get(ATTR_ENTITY_ID, []) - return any( - entry.state != ConfigEntryState.LOADED - and ( + for entry in hass.config_entries.async_entries(DOMAIN): + if entry.state != ConfigEntryState.LOADED and ( entry.entry_id == config.get(ATTR_CONFIG_ENTRY_ID) or any( device.id in trigger_devices @@ -31,6 +32,12 @@ def async_bypass_dynamic_config_validation( entity.entity_id in trigger_entities for entity in er.async_entries_for_config_entry(ent_reg, entry.entry_id) ) - ) - for entry in hass.config_entries.async_entries(DOMAIN) - ) + ): + return True + + # The driver may not be ready when the config entry is loaded. + client: ZwaveClient = hass.data[DOMAIN][entry.entry_id][DATA_CLIENT] + if client.driver is None: + return True + + return False diff --git a/tests/components/zwave_js/test_device_trigger.py b/tests/components/zwave_js/test_device_trigger.py index 859164aa4c3..84d87efed63 100644 --- a/tests/components/zwave_js/test_device_trigger.py +++ b/tests/components/zwave_js/test_device_trigger.py @@ -1080,6 +1080,74 @@ async def test_if_value_updated_value_fires( ) +async def test_value_updated_value_no_driver( + hass, client, lock_schlage_be469, integration, calls +): + """Test zwave_js.value_updated.value trigger with missing driver.""" + node: Node = lock_schlage_be469 + dev_reg = async_get_dev_reg(hass) + device = async_entries_for_config_entry(dev_reg, integration.entry_id)[0] + driver = client.driver + client.driver = None + + assert await async_setup_component( + hass, + automation.DOMAIN, + { + automation.DOMAIN: [ + { + "trigger": { + "platform": "device", + "domain": DOMAIN, + "device_id": device.id, + "type": "zwave_js.value_updated.value", + "command_class": CommandClass.DOOR_LOCK.value, + "property": "latchStatus", + "property_key": None, + "endpoint": None, + "from": "open", + }, + "action": { + "service": "test.automation", + "data_template": { + "some": ( + "zwave_js.value_updated.value - " + "{{ trigger.platform}} - " + "{{ trigger.previous_value }}" + ) + }, + }, + }, + ] + }, + ) + await hass.async_block_till_done() + + client.driver = driver + + # No trigger as automation failed to setup. + event = Event( + type="value updated", + data={ + "source": "node", + "event": "value updated", + "nodeId": node.node_id, + "args": { + "commandClassName": "Door Lock", + "commandClass": 98, + "endpoint": 0, + "property": "latchStatus", + "newValue": "closed", + "prevValue": "open", + "propertyName": "latchStatus", + }, + }, + ) + node.receive_event(event) + await hass.async_block_till_done() + assert len(calls) == 0 + + async def test_get_trigger_capabilities_value_updated_value( hass, client, lock_schlage_be469, integration ): diff --git a/tests/components/zwave_js/test_trigger.py b/tests/components/zwave_js/test_trigger.py index a5c226057d4..b3b6910e5f5 100644 --- a/tests/components/zwave_js/test_trigger.py +++ b/tests/components/zwave_js/test_trigger.py @@ -385,6 +385,65 @@ async def test_zwave_js_value_updated_bypass_dynamic_validation_no_nodes( assert len(no_value_filter) == 0 +async def test_zwave_js_value_updated_bypass_dynamic_validation_no_driver( + hass, client, lock_schlage_be469, integration +): + """Test zwave_js.value_updated trigger without driver.""" + trigger_type = f"{DOMAIN}.value_updated" + node: Node = lock_schlage_be469 + driver = client.driver + client.driver = None + + no_value_filter = async_capture_events(hass, "no_value_filter") + + assert await async_setup_component( + hass, + automation.DOMAIN, + { + automation.DOMAIN: [ + # no value filter + { + "trigger": { + "platform": trigger_type, + "entity_id": SCHLAGE_BE469_LOCK_ENTITY, + "command_class": CommandClass.DOOR_LOCK.value, + "property": "latchStatus", + }, + "action": { + "event": "no_value_filter", + }, + }, + ] + }, + ) + await hass.async_block_till_done() + + client.driver = driver + + # Test that no value filter is NOT triggered because automation failed setup + event = Event( + type="value updated", + data={ + "source": "node", + "event": "value updated", + "nodeId": node.node_id, + "args": { + "commandClassName": "Door Lock", + "commandClass": 98, + "endpoint": 0, + "property": "latchStatus", + "newValue": "boo", + "prevValue": "hiss", + "propertyName": "latchStatus", + }, + }, + ) + node.receive_event(event) + await hass.async_block_till_done() + + assert len(no_value_filter) == 0 + + async def test_zwave_js_event(hass, client, lock_schlage_be469, integration): """Test for zwave_js.event automation trigger.""" trigger_type = f"{DOMAIN}.event" From a2ba126be16e1853287bed00975979a5a9392250 Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Tue, 6 Dec 2022 10:04:32 -0800 Subject: [PATCH 080/174] Tighten validation on calendar create event websocket (#83413) --- homeassistant/components/calendar/__init__.py | 67 +++++++++++-- .../local_calendar/test_calendar.py | 93 +++++++++++++++++++ 2 files changed, 152 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/calendar/__init__.py b/homeassistant/components/calendar/__init__.py index b5d605b9f6f..c89b36ce636 100644 --- a/homeassistant/components/calendar/__init__.py +++ b/homeassistant/components/calendar/__init__.py @@ -1,10 +1,11 @@ """Support for Google Calendar event device sensors.""" from __future__ import annotations -from collections.abc import Iterable +from collections.abc import Callable, Iterable import dataclasses import datetime from http import HTTPStatus +from itertools import groupby import logging import re from typing import Any, cast, final @@ -365,17 +366,67 @@ class CalendarListView(http.HomeAssistantView): return self.json(sorted(calendar_list, key=lambda x: cast(str, x["name"]))) +def _has_same_type(*keys: Any) -> Callable[[dict[str, Any]], dict[str, Any]]: + """Verify that all values are of the same type.""" + + def validate(obj: dict[str, Any]) -> dict[str, Any]: + """Test that all keys in the dict have values of the same type.""" + uniq_values = groupby(type(obj[k]) for k in keys) + if len(list(uniq_values)) > 1: + raise vol.Invalid(f"Expected all values to be the same type: {keys}") + return obj + + return validate + + +def _has_consistent_timezone(*keys: Any) -> Callable[[dict[str, Any]], dict[str, Any]]: + """Verify that all datetime values have a consistent timezone.""" + + def validate(obj: dict[str, Any]) -> dict[str, Any]: + """Test that all keys that are datetime values have the same timezone.""" + values = [obj[k] for k in keys] + if all(isinstance(value, datetime.datetime) for value in values): + uniq_values = groupby(value.tzinfo for value in values) + if len(list(uniq_values)) > 1: + raise vol.Invalid( + f"Expected all values to have the same timezone: {values}" + ) + return obj + + return validate + + +def _is_sorted(*keys: Any) -> Callable[[dict[str, Any]], dict[str, Any]]: + """Verify that the specified values are sequential.""" + + def validate(obj: dict[str, Any]) -> dict[str, Any]: + """Test that all keys in the dict are in order.""" + values = [obj[k] for k in keys] + if values != sorted(values): + raise vol.Invalid(f"Values were not in order: {values}") + return obj + + return validate + + @websocket_api.websocket_command( { vol.Required("type"): "calendar/event/create", vol.Required("entity_id"): cv.entity_id, - vol.Required(CONF_EVENT): { - vol.Required(EVENT_START): vol.Any(cv.date, cv.datetime), - vol.Required(EVENT_END): vol.Any(cv.date, cv.datetime), - vol.Required(EVENT_SUMMARY): cv.string, - vol.Optional(EVENT_DESCRIPTION): cv.string, - vol.Optional(EVENT_RRULE): _validate_rrule, - }, + CONF_EVENT: vol.Schema( + vol.All( + { + vol.Required(EVENT_START): vol.Any(cv.date, cv.datetime), + vol.Required(EVENT_END): vol.Any(cv.date, cv.datetime), + vol.Required(EVENT_SUMMARY): cv.string, + vol.Optional(EVENT_DESCRIPTION): cv.string, + vol.Optional(EVENT_RRULE): _validate_rrule, + }, + _has_same_type(EVENT_START, EVENT_END), + _has_consistent_timezone(EVENT_START, EVENT_END), + _is_sorted(EVENT_START, EVENT_END), + ) + ), } ) @websocket_api.async_response diff --git a/tests/components/local_calendar/test_calendar.py b/tests/components/local_calendar/test_calendar.py index 77632c8bfe1..092fcb1c1fb 100644 --- a/tests/components/local_calendar/test_calendar.py +++ b/tests/components/local_calendar/test_calendar.py @@ -612,3 +612,96 @@ async def test_all_day_iter_order( events = await get_events("2022-10-06T00:00:00Z", "2022-10-09T00:00:00Z") assert [event["summary"] for event in events] == event_order + + +async def test_start_end_types( + ws_client: ClientFixture, + setup_integration: None, +): + """Test a start and end with different date and date time types.""" + client = await ws_client() + result = await client.cmd( + "create", + { + "entity_id": TEST_ENTITY, + "event": { + "summary": "Bastille Day Party", + "dtstart": "1997-07-15", + "dtend": "1997-07-14T17:00:00+00:00", + }, + }, + ) + assert not result.get("success") + assert "error" in result + assert "code" in result.get("error") + assert result["error"]["code"] == "invalid_format" + + +async def test_end_before_start( + ws_client: ClientFixture, + setup_integration: None, +): + """Test an event with a start/end date time.""" + client = await ws_client() + result = await client.cmd( + "create", + { + "entity_id": TEST_ENTITY, + "event": { + "summary": "Bastille Day Party", + "dtstart": "1997-07-15T04:00:00+00:00", + "dtend": "1997-07-14T17:00:00+00:00", + }, + }, + ) + assert not result.get("success") + assert "error" in result + assert "code" in result.get("error") + assert result["error"]["code"] == "invalid_format" + + +async def test_invalid_recurrence_rule( + ws_client: ClientFixture, + setup_integration: None, +): + """Test an event with a recurrence rule.""" + client = await ws_client() + result = await client.cmd( + "create", + { + "entity_id": TEST_ENTITY, + "event": { + "summary": "Monday meeting", + "dtstart": "2022-08-29T09:00:00", + "dtend": "2022-08-29T10:00:00", + "rrule": "FREQ=invalid;'", + }, + }, + ) + assert not result.get("success") + assert "error" in result + assert "code" in result.get("error") + assert result["error"]["code"] == "invalid_format" + + +async def test_invalid_date_formats( + ws_client: ClientFixture, setup_integration: None, get_events: GetEventsFn +): + """Exercises a validation error within rfc5545 parsing in ical.""" + client = await ws_client() + result = await client.cmd( + "create", + { + "entity_id": TEST_ENTITY, + "event": { + "summary": "Bastille Day Party", + # Can't mix offset aware and floating dates + "dtstart": "1997-07-15T04:00:00+08:00", + "dtend": "1997-07-14T17:00:00", + }, + }, + ) + assert not result.get("success") + assert "error" in result + assert "code" in result.get("error") + assert result["error"]["code"] == "invalid_format" From 52ed121970ed93fdd2e81ab340405244ccb2b381 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Tue, 6 Dec 2022 19:21:28 +0100 Subject: [PATCH 081/174] Update frontend to 20221206.0 (#83415) --- homeassistant/components/frontend/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/frontend/manifest.json b/homeassistant/components/frontend/manifest.json index 76da0335769..3093928f21f 100644 --- a/homeassistant/components/frontend/manifest.json +++ b/homeassistant/components/frontend/manifest.json @@ -2,7 +2,7 @@ "domain": "frontend", "name": "Home Assistant Frontend", "documentation": "https://www.home-assistant.io/integrations/frontend", - "requirements": ["home-assistant-frontend==20221205.0"], + "requirements": ["home-assistant-frontend==20221206.0"], "dependencies": [ "api", "auth", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 52f37d2451f..406b9972aca 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -22,7 +22,7 @@ dbus-fast==1.75.0 fnvhash==0.1.0 hass-nabucasa==0.59.0 home-assistant-bluetooth==1.8.1 -home-assistant-frontend==20221205.0 +home-assistant-frontend==20221206.0 httpx==0.23.1 ifaddr==0.1.7 janus==1.0.0 diff --git a/requirements_all.txt b/requirements_all.txt index 67c7e1ff56d..130349a5d40 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -884,7 +884,7 @@ hole==0.7.0 holidays==0.17.2 # homeassistant.components.frontend -home-assistant-frontend==20221205.0 +home-assistant-frontend==20221206.0 # homeassistant.components.home_connect homeconnect==0.7.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 8bd3b438994..6b1a3326184 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -664,7 +664,7 @@ hole==0.7.0 holidays==0.17.2 # homeassistant.components.frontend -home-assistant-frontend==20221205.0 +home-assistant-frontend==20221206.0 # homeassistant.components.home_connect homeconnect==0.7.2 From ced924805199ec611a3b2814d7f228519092accb Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 6 Dec 2022 13:22:52 -0500 Subject: [PATCH 082/174] Bumped version to 2022.12.0b6 --- homeassistant/const.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 32a0b85ac96..cd915aee3e0 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -8,7 +8,7 @@ from .backports.enum import StrEnum APPLICATION_NAME: Final = "HomeAssistant" MAJOR_VERSION: Final = 2022 MINOR_VERSION: Final = 12 -PATCH_VERSION: Final = "0b5" +PATCH_VERSION: Final = "0b6" __short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__: Final = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 9, 0) diff --git a/pyproject.toml b/pyproject.toml index 4b63472ef20..3cf775d2039 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "homeassistant" -version = "2022.12.0b5" +version = "2022.12.0b6" license = {text = "Apache-2.0"} description = "Open-source home automation platform running on Python 3." readme = "README.rst" From 7903f01f0c5dd7d00a2ed700e8d3ae42962af58f Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Tue, 6 Dec 2022 20:28:06 +0100 Subject: [PATCH 083/174] Add matter binary sensor platform (#83144) --- .../components/matter/binary_sensor.py | 95 +++++++++++++++ .../components/matter/device_platform.py | 2 + .../matter/fixtures/nodes/contact-sensor.json | 111 ++++++++++++++++++ .../fixtures/nodes/occupancy-sensor.json | 107 +++++++++++++++++ tests/components/matter/test_binary_sensor.py | 69 +++++++++++ 5 files changed, 384 insertions(+) create mode 100644 homeassistant/components/matter/binary_sensor.py create mode 100644 tests/components/matter/test_binary_sensor.py diff --git a/homeassistant/components/matter/binary_sensor.py b/homeassistant/components/matter/binary_sensor.py new file mode 100644 index 00000000000..a4ca54920fb --- /dev/null +++ b/homeassistant/components/matter/binary_sensor.py @@ -0,0 +1,95 @@ +"""Matter binary sensors.""" +from __future__ import annotations + +from dataclasses import dataclass +from functools import partial +from typing import TYPE_CHECKING + +from chip.clusters import Objects as clusters +from matter_server.common.models import device_types + +from homeassistant.components.binary_sensor import ( + BinarySensorDeviceClass, + BinarySensorEntity, + BinarySensorEntityDescription, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import Platform +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from .const import DOMAIN +from .entity import MatterEntity, MatterEntityDescriptionBaseClass + +if TYPE_CHECKING: + from .adapter import MatterAdapter + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up Matter binary sensor from Config Entry.""" + matter: MatterAdapter = hass.data[DOMAIN][config_entry.entry_id] + matter.register_platform_handler(Platform.BINARY_SENSOR, async_add_entities) + + +class MatterBinarySensor(MatterEntity, BinarySensorEntity): + """Representation of a Matter binary sensor.""" + + entity_description: MatterBinarySensorEntityDescription + + @callback + def _update_from_device(self) -> None: + """Update from device.""" + self._attr_is_on = self._device_type_instance.get_cluster( + clusters.BooleanState + ).stateValue + + +class MatterOccupancySensor(MatterBinarySensor): + """Representation of a Matter occupancy sensor.""" + + _attr_device_class = BinarySensorDeviceClass.OCCUPANCY + + @callback + def _update_from_device(self) -> None: + """Update from device.""" + occupancy = self._device_type_instance.get_cluster( + clusters.OccupancySensing + ).occupancy + # The first bit = if occupied + self._attr_is_on = occupancy & 1 == 1 + + +@dataclass +class MatterBinarySensorEntityDescription( + BinarySensorEntityDescription, + MatterEntityDescriptionBaseClass, +): + """Matter Binary Sensor entity description.""" + + +# You can't set default values on inherited data classes +MatterSensorEntityDescriptionFactory = partial( + MatterBinarySensorEntityDescription, entity_cls=MatterBinarySensor +) + +DEVICE_ENTITY: dict[ + type[device_types.DeviceType], + MatterEntityDescriptionBaseClass | list[MatterEntityDescriptionBaseClass], +] = { + device_types.ContactSensor: MatterSensorEntityDescriptionFactory( + key=device_types.ContactSensor, + name="Contact", + subscribe_attributes=(clusters.BooleanState.Attributes.StateValue,), + device_class=BinarySensorDeviceClass.DOOR, + ), + device_types.OccupancySensor: MatterSensorEntityDescriptionFactory( + key=device_types.OccupancySensor, + name="Occupancy", + entity_cls=MatterOccupancySensor, + subscribe_attributes=(clusters.OccupancySensing.Attributes.Occupancy,), + ), +} diff --git a/homeassistant/components/matter/device_platform.py b/homeassistant/components/matter/device_platform.py index 25a83d28b98..3a4d11ab95f 100644 --- a/homeassistant/components/matter/device_platform.py +++ b/homeassistant/components/matter/device_platform.py @@ -5,6 +5,7 @@ from typing import TYPE_CHECKING from homeassistant.const import Platform +from .binary_sensor import DEVICE_ENTITY as BINARY_SENSOR_DEVICE_ENTITY from .light import DEVICE_ENTITY as LIGHT_DEVICE_ENTITY if TYPE_CHECKING: @@ -20,5 +21,6 @@ DEVICE_PLATFORM: dict[ MatterEntityDescriptionBaseClass | list[MatterEntityDescriptionBaseClass], ], ] = { + Platform.BINARY_SENSOR: BINARY_SENSOR_DEVICE_ENTITY, Platform.LIGHT: LIGHT_DEVICE_ENTITY, } diff --git a/tests/components/matter/fixtures/nodes/contact-sensor.json b/tests/components/matter/fixtures/nodes/contact-sensor.json index 6890b38e8f1..2aec6a32516 100644 --- a/tests/components/matter/fixtures/nodes/contact-sensor.json +++ b/tests/components/matter/fixtures/nodes/contact-sensor.json @@ -4,6 +4,113 @@ "last_interview": "2022-11-29T21:23:48.485057", "interview_version": 1, "attributes": { + "0/29/0": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 0, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.DeviceTypeList", + "attribute_name": "DeviceTypeList", + "value": [ + { + "type": 22, + "revision": 1 + } + ] + }, + "0/29/1": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 1, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.ServerList", + "attribute_name": "ServerList", + "value": [ + 4, 29, 31, 40, 42, 43, 44, 48, 49, 50, 51, 52, 53, 54, 55, 59, 60, 62, + 63, 64, 65 + ] + }, + "0/29/2": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 2, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.ClientList", + "attribute_name": "ClientList", + "value": [41] + }, + "0/29/3": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 3, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.PartsList", + "attribute_name": "PartsList", + "value": [1] + }, + "0/29/65532": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 65532, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.FeatureMap", + "attribute_name": "FeatureMap", + "value": 0 + }, + "0/29/65533": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 65533, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.ClusterRevision", + "attribute_name": "ClusterRevision", + "value": 1 + }, + "0/29/65528": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 65528, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.GeneratedCommandList", + "attribute_name": "GeneratedCommandList", + "value": [] + }, + "0/29/65529": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 65529, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.AcceptedCommandList", + "attribute_name": "AcceptedCommandList", + "value": [] + }, + "0/29/65531": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 65531, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.AttributeList", + "attribute_name": "AttributeList", + "value": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533] + }, "0/40/0": { "node_id": 1, "endpoint": 0, @@ -541,5 +648,9 @@ } }, "endpoints": [0, 1], + "root_device_type_instance": null, + "aggregator_device_type_instance": null, + "device_type_instances": [null], + "node_devices": [null], "_type": "matter_server.common.models.node.MatterNode" } diff --git a/tests/components/matter/fixtures/nodes/occupancy-sensor.json b/tests/components/matter/fixtures/nodes/occupancy-sensor.json index 2944853f9e1..3e16b92f261 100644 --- a/tests/components/matter/fixtures/nodes/occupancy-sensor.json +++ b/tests/components/matter/fixtures/nodes/occupancy-sensor.json @@ -4,6 +4,113 @@ "last_interview": "2022-11-29T21:23:48.485057", "interview_version": 1, "attributes": { + "0/29/0": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 0, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.DeviceTypeList", + "attribute_name": "DeviceTypeList", + "value": [ + { + "type": 22, + "revision": 1 + } + ] + }, + "0/29/1": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 1, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.ServerList", + "attribute_name": "ServerList", + "value": [ + 4, 29, 31, 40, 42, 43, 44, 48, 49, 50, 51, 52, 53, 54, 55, 59, 60, 62, + 63, 64, 65 + ] + }, + "0/29/2": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 2, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.ClientList", + "attribute_name": "ClientList", + "value": [41] + }, + "0/29/3": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 3, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.PartsList", + "attribute_name": "PartsList", + "value": [1] + }, + "0/29/65532": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 65532, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.FeatureMap", + "attribute_name": "FeatureMap", + "value": 0 + }, + "0/29/65533": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 65533, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.ClusterRevision", + "attribute_name": "ClusterRevision", + "value": 1 + }, + "0/29/65528": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 65528, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.GeneratedCommandList", + "attribute_name": "GeneratedCommandList", + "value": [] + }, + "0/29/65529": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 65529, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.AcceptedCommandList", + "attribute_name": "AcceptedCommandList", + "value": [] + }, + "0/29/65531": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 65531, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.AttributeList", + "attribute_name": "AttributeList", + "value": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533] + }, "0/40/0": { "node_id": 1, "endpoint": 0, diff --git a/tests/components/matter/test_binary_sensor.py b/tests/components/matter/test_binary_sensor.py new file mode 100644 index 00000000000..522cda2dccc --- /dev/null +++ b/tests/components/matter/test_binary_sensor.py @@ -0,0 +1,69 @@ +"""Test Matter binary sensors.""" +from unittest.mock import MagicMock + +from matter_server.common.models.node import MatterNode +import pytest + +from homeassistant.core import HomeAssistant + +from .common import ( + set_node_attribute, + setup_integration_with_node_fixture, + trigger_subscription_callback, +) + + +@pytest.fixture(name="contact_sensor_node") +async def contact_sensor_node_fixture( + hass: HomeAssistant, matter_client: MagicMock +) -> MatterNode: + """Fixture for a contact sensor node.""" + return await setup_integration_with_node_fixture( + hass, "contact-sensor", matter_client + ) + + +async def test_contact_sensor( + hass: HomeAssistant, + matter_client: MagicMock, + contact_sensor_node: MatterNode, +) -> None: + """Test contact sensor.""" + state = hass.states.get("binary_sensor.mock_contact_sensor_contact") + assert state + assert state.state == "on" + + set_node_attribute(contact_sensor_node, 1, 69, 0, False) + await trigger_subscription_callback(hass, matter_client) + + state = hass.states.get("binary_sensor.mock_contact_sensor_contact") + assert state + assert state.state == "off" + + +@pytest.fixture(name="occupancy_sensor_node") +async def occupancy_sensor_node_fixture( + hass: HomeAssistant, matter_client: MagicMock +) -> MatterNode: + """Fixture for a occupancy sensor node.""" + return await setup_integration_with_node_fixture( + hass, "occupancy-sensor", matter_client + ) + + +async def test_occupancy_sensor( + hass: HomeAssistant, + matter_client: MagicMock, + occupancy_sensor_node: MatterNode, +) -> None: + """Test occupancy sensor.""" + state = hass.states.get("binary_sensor.mock_occupancy_sensor_occupancy") + assert state + assert state.state == "on" + + set_node_attribute(occupancy_sensor_node, 1, 1030, 0, 0) + await trigger_subscription_callback(hass, matter_client) + + state = hass.states.get("binary_sensor.mock_occupancy_sensor_occupancy") + assert state + assert state.state == "off" From 172d587f84988fc429058ddb085b54520e1a02e0 Mon Sep 17 00:00:00 2001 From: Guido Pio Mariotti Date: Wed, 7 Dec 2022 00:42:08 +0100 Subject: [PATCH 084/174] Update pyvesync to 2.1.1 (#83146) Update pyvesync to 2.1.1 to close #70420 --- homeassistant/components/vesync/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/vesync/manifest.json b/homeassistant/components/vesync/manifest.json index 49be473b748..730da2d586b 100644 --- a/homeassistant/components/vesync/manifest.json +++ b/homeassistant/components/vesync/manifest.json @@ -3,7 +3,7 @@ "name": "VeSync", "documentation": "https://www.home-assistant.io/integrations/vesync", "codeowners": ["@markperdue", "@webdjoe", "@thegardenmonkey"], - "requirements": ["pyvesync==2.0.3"], + "requirements": ["pyvesync==2.1.1"], "config_flow": true, "iot_class": "cloud_polling", "loggers": ["pyvesync"] diff --git a/requirements_all.txt b/requirements_all.txt index 130349a5d40..ffd8b562d88 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2116,7 +2116,7 @@ pyvera==0.3.13 pyversasense==0.0.6 # homeassistant.components.vesync -pyvesync==2.0.3 +pyvesync==2.1.1 # homeassistant.components.vizio pyvizio==0.1.57 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 6b1a3326184..c487cf916e6 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1473,7 +1473,7 @@ pyuptimerobot==22.2.0 pyvera==0.3.13 # homeassistant.components.vesync -pyvesync==2.0.3 +pyvesync==2.1.1 # homeassistant.components.vizio pyvizio==0.1.57 From 7795cc7fe8e1ab3ca5042e2987c8658de8e9027e Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Tue, 6 Dec 2022 21:39:00 +0100 Subject: [PATCH 085/174] Add matter sensor platform (#83147) --- .../components/matter/device_platform.py | 2 + homeassistant/components/matter/sensor.py | 167 +++++++++++++++++ .../matter/fixtures/nodes/flow-sensor.json | 107 +++++++++++ .../fixtures/nodes/humidity-sensor.json | 157 +++++++++++++--- .../matter/fixtures/nodes/light-sensor.json | 107 +++++++++++ .../fixtures/nodes/pressure-sensor.json | 107 +++++++++++ .../fixtures/nodes/temperature-sensor.json | 107 +++++++++++ tests/components/matter/test_sensor.py | 169 ++++++++++++++++++ 8 files changed, 898 insertions(+), 25 deletions(-) create mode 100644 homeassistant/components/matter/sensor.py create mode 100644 tests/components/matter/test_sensor.py diff --git a/homeassistant/components/matter/device_platform.py b/homeassistant/components/matter/device_platform.py index 3a4d11ab95f..02267b02880 100644 --- a/homeassistant/components/matter/device_platform.py +++ b/homeassistant/components/matter/device_platform.py @@ -7,6 +7,7 @@ from homeassistant.const import Platform from .binary_sensor import DEVICE_ENTITY as BINARY_SENSOR_DEVICE_ENTITY from .light import DEVICE_ENTITY as LIGHT_DEVICE_ENTITY +from .sensor import DEVICE_ENTITY as SENSOR_DEVICE_ENTITY if TYPE_CHECKING: from matter_server.common.models.device_types import DeviceType @@ -23,4 +24,5 @@ DEVICE_PLATFORM: dict[ ] = { Platform.BINARY_SENSOR: BINARY_SENSOR_DEVICE_ENTITY, Platform.LIGHT: LIGHT_DEVICE_ENTITY, + Platform.SENSOR: SENSOR_DEVICE_ENTITY, } diff --git a/homeassistant/components/matter/sensor.py b/homeassistant/components/matter/sensor.py new file mode 100644 index 00000000000..2b659fc1304 --- /dev/null +++ b/homeassistant/components/matter/sensor.py @@ -0,0 +1,167 @@ +"""Matter sensors.""" +from __future__ import annotations + +from collections.abc import Callable +from dataclasses import dataclass +from functools import partial +from typing import TYPE_CHECKING, Any + +from chip.clusters import Objects as clusters +from chip.clusters.Types import Nullable, NullValue +from matter_server.common.models import device_types +from matter_server.common.models.device_type_instance import MatterDeviceTypeInstance + +from homeassistant.components.sensor import ( + SensorDeviceClass, + SensorEntity, + SensorEntityDescription, + SensorStateClass, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import ( + LIGHT_LUX, + PERCENTAGE, + VOLUME_FLOW_RATE_CUBIC_METERS_PER_HOUR, + Platform, + UnitOfPressure, + UnitOfTemperature, +) +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from .const import DOMAIN +from .entity import MatterEntity, MatterEntityDescriptionBaseClass + +if TYPE_CHECKING: + from .adapter import MatterAdapter + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up Matter sensors from Config Entry.""" + matter: MatterAdapter = hass.data[DOMAIN][config_entry.entry_id] + matter.register_platform_handler(Platform.SENSOR, async_add_entities) + + +class MatterSensor(MatterEntity, SensorEntity): + """Representation of a Matter sensor.""" + + _attr_state_class = SensorStateClass.MEASUREMENT + entity_description: MatterSensorEntityDescription + + @callback + def _update_from_device(self) -> None: + """Update from device.""" + measurement: Nullable | float | None + measurement = _get_attribute_value( + self._device_type_instance, + # We always subscribe to a single value + self.entity_description.subscribe_attributes[0], + ) + + if measurement is NullValue or measurement is None: + measurement = None + else: + measurement = self.entity_description.measurement_to_ha(measurement) + + self._attr_native_value = measurement + + +def _get_attribute_value( + device_type_instance: MatterDeviceTypeInstance, + attribute: clusters.ClusterAttributeDescriptor, +) -> Any: + """Return the value of an attribute.""" + # Find the cluster for this attribute. We don't have a lookup table yet. + cluster_cls: clusters.Cluster = next( + cluster + for cluster in device_type_instance.device_type.clusters + if cluster.id == attribute.cluster_id + ) + + # Find the attribute descriptor so we know the instance variable to fetch + attribute_descriptor: clusters.ClusterObjectFieldDescriptor = next( + descriptor + for descriptor in cluster_cls.descriptor.Fields + if descriptor.Tag == attribute.attribute_id + ) + + cluster_data = device_type_instance.get_cluster(cluster_cls) + return getattr(cluster_data, attribute_descriptor.Label) + + +@dataclass +class MatterSensorEntityDescriptionMixin: + """Required fields for sensor device mapping.""" + + measurement_to_ha: Callable[[float], float] + + +@dataclass +class MatterSensorEntityDescription( + SensorEntityDescription, + MatterEntityDescriptionBaseClass, + MatterSensorEntityDescriptionMixin, +): + """Matter Sensor entity description.""" + + +# You can't set default values on inherited data classes +MatterSensorEntityDescriptionFactory = partial( + MatterSensorEntityDescription, entity_cls=MatterSensor +) + + +DEVICE_ENTITY: dict[ + type[device_types.DeviceType], + MatterEntityDescriptionBaseClass | list[MatterEntityDescriptionBaseClass], +] = { + device_types.TemperatureSensor: MatterSensorEntityDescriptionFactory( + key=device_types.TemperatureSensor, + name="Temperature", + measurement_to_ha=lambda x: x / 100, + subscribe_attributes=( + clusters.TemperatureMeasurement.Attributes.MeasuredValue, + ), + native_unit_of_measurement=UnitOfTemperature.CELSIUS, + device_class=SensorDeviceClass.TEMPERATURE, + ), + device_types.PressureSensor: MatterSensorEntityDescriptionFactory( + key=device_types.PressureSensor, + name="Pressure", + measurement_to_ha=lambda x: x / 10, + subscribe_attributes=(clusters.PressureMeasurement.Attributes.MeasuredValue,), + native_unit_of_measurement=UnitOfPressure.KPA, + device_class=SensorDeviceClass.PRESSURE, + ), + device_types.FlowSensor: MatterSensorEntityDescriptionFactory( + key=device_types.FlowSensor, + name="Flow", + measurement_to_ha=lambda x: x / 10, + subscribe_attributes=(clusters.FlowMeasurement.Attributes.MeasuredValue,), + native_unit_of_measurement=VOLUME_FLOW_RATE_CUBIC_METERS_PER_HOUR, + ), + device_types.HumiditySensor: MatterSensorEntityDescriptionFactory( + key=device_types.HumiditySensor, + name="Humidity", + measurement_to_ha=lambda x: x / 100, + subscribe_attributes=( + clusters.RelativeHumidityMeasurement.Attributes.MeasuredValue, + ), + native_unit_of_measurement=PERCENTAGE, + device_class=SensorDeviceClass.HUMIDITY, + ), + device_types.LightSensor: MatterSensorEntityDescriptionFactory( + key=device_types.LightSensor, + name="Light", + measurement_to_ha=lambda x: round(pow(10, ((x - 1) / 10000)), 1), + subscribe_attributes=( + clusters.IlluminanceMeasurement.Attributes.MeasuredValue, + ), + native_unit_of_measurement=LIGHT_LUX, + device_class=SensorDeviceClass.ILLUMINANCE, + ), +} diff --git a/tests/components/matter/fixtures/nodes/flow-sensor.json b/tests/components/matter/fixtures/nodes/flow-sensor.json index 04135f8aa82..92f4c5b73b2 100644 --- a/tests/components/matter/fixtures/nodes/flow-sensor.json +++ b/tests/components/matter/fixtures/nodes/flow-sensor.json @@ -4,6 +4,113 @@ "last_interview": "2022-11-29T21:23:48.485057", "interview_version": 1, "attributes": { + "0/29/0": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 0, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.DeviceTypeList", + "attribute_name": "DeviceTypeList", + "value": [ + { + "type": 22, + "revision": 1 + } + ] + }, + "0/29/1": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 1, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.ServerList", + "attribute_name": "ServerList", + "value": [ + 4, 29, 31, 40, 42, 43, 44, 48, 49, 50, 51, 52, 53, 54, 55, 59, 60, 62, + 63, 64, 65 + ] + }, + "0/29/2": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 2, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.ClientList", + "attribute_name": "ClientList", + "value": [41] + }, + "0/29/3": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 3, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.PartsList", + "attribute_name": "PartsList", + "value": [1] + }, + "0/29/65532": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 65532, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.FeatureMap", + "attribute_name": "FeatureMap", + "value": 0 + }, + "0/29/65533": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 65533, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.ClusterRevision", + "attribute_name": "ClusterRevision", + "value": 1 + }, + "0/29/65528": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 65528, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.GeneratedCommandList", + "attribute_name": "GeneratedCommandList", + "value": [] + }, + "0/29/65529": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 65529, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.AcceptedCommandList", + "attribute_name": "AcceptedCommandList", + "value": [] + }, + "0/29/65531": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 65531, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.AttributeList", + "attribute_name": "AttributeList", + "value": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533] + }, "0/40/0": { "node_id": 1, "endpoint": 0, diff --git a/tests/components/matter/fixtures/nodes/humidity-sensor.json b/tests/components/matter/fixtures/nodes/humidity-sensor.json index f29227ce14b..34b95263de5 100644 --- a/tests/components/matter/fixtures/nodes/humidity-sensor.json +++ b/tests/components/matter/fixtures/nodes/humidity-sensor.json @@ -4,6 +4,113 @@ "last_interview": "2022-11-29T21:23:48.485057", "interview_version": 1, "attributes": { + "0/29/0": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 0, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.DeviceTypeList", + "attribute_name": "DeviceTypeList", + "value": [ + { + "type": 22, + "revision": 1 + } + ] + }, + "0/29/1": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 1, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.ServerList", + "attribute_name": "ServerList", + "value": [ + 4, 29, 31, 40, 42, 43, 44, 48, 49, 50, 51, 52, 53, 54, 55, 59, 60, 62, + 63, 64, 65 + ] + }, + "0/29/2": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 2, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.ClientList", + "attribute_name": "ClientList", + "value": [41] + }, + "0/29/3": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 3, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.PartsList", + "attribute_name": "PartsList", + "value": [1] + }, + "0/29/65532": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 65532, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.FeatureMap", + "attribute_name": "FeatureMap", + "value": 0 + }, + "0/29/65533": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 65533, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.ClusterRevision", + "attribute_name": "ClusterRevision", + "value": 1 + }, + "0/29/65528": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 65528, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.GeneratedCommandList", + "attribute_name": "GeneratedCommandList", + "value": [] + }, + "0/29/65529": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 65529, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.AcceptedCommandList", + "attribute_name": "AcceptedCommandList", + "value": [] + }, + "0/29/65531": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 65531, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.AttributeList", + "attribute_name": "AttributeList", + "value": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533] + }, "0/40/0": { "node_id": 1, "endpoint": 0, @@ -404,7 +511,7 @@ }, "1/29/65531": { "node_id": 1, - "endpoint": 0, + "endpoint": 1, "cluster_id": 29, "cluster_type": "chip.clusters.Objects.Descriptor", "cluster_name": "Descriptor", @@ -413,9 +520,9 @@ "attribute_name": "AttributeList", "value": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533] }, - "0/1029/0": { - "node_id": 3, - "endpoint": 0, + "1/1029/0": { + "node_id": 1, + "endpoint": 1, "cluster_id": 1029, "cluster_type": "chip.clusters.Objects.RelativeHumidityMeasurement", "cluster_name": "RelativeHumidityMeasurement", @@ -424,9 +531,9 @@ "attribute_name": "MeasuredValue", "value": 0 }, - "0/1029/1": { - "node_id": 3, - "endpoint": 0, + "1/1029/1": { + "node_id": 1, + "endpoint": 1, "cluster_id": 1029, "cluster_type": "chip.clusters.Objects.RelativeHumidityMeasurement", "cluster_name": "RelativeHumidityMeasurement", @@ -435,9 +542,9 @@ "attribute_name": "MinMeasuredValue", "value": 0 }, - "0/1029/2": { - "node_id": 3, - "endpoint": 0, + "1/1029/2": { + "node_id": 1, + "endpoint": 1, "cluster_id": 1029, "cluster_type": "chip.clusters.Objects.RelativeHumidityMeasurement", "cluster_name": "RelativeHumidityMeasurement", @@ -446,9 +553,9 @@ "attribute_name": "MaxMeasuredValue", "value": 10000 }, - "0/1029/65532": { - "node_id": 3, - "endpoint": 0, + "1/1029/65532": { + "node_id": 1, + "endpoint": 1, "cluster_id": 1029, "cluster_type": "chip.clusters.Objects.RelativeHumidityMeasurement", "cluster_name": "RelativeHumidityMeasurement", @@ -457,9 +564,9 @@ "attribute_name": "FeatureMap", "value": 0 }, - "0/1029/65533": { - "node_id": 3, - "endpoint": 0, + "1/1029/65533": { + "node_id": 1, + "endpoint": 1, "cluster_id": 1029, "cluster_type": "chip.clusters.Objects.RelativeHumidityMeasurement", "cluster_name": "RelativeHumidityMeasurement", @@ -468,9 +575,9 @@ "attribute_name": "ClusterRevision", "value": 3 }, - "0/1029/65528": { - "node_id": 3, - "endpoint": 0, + "1/1029/65528": { + "node_id": 1, + "endpoint": 1, "cluster_id": 1029, "cluster_type": "chip.clusters.Objects.RelativeHumidityMeasurement", "cluster_name": "RelativeHumidityMeasurement", @@ -479,9 +586,9 @@ "attribute_name": "GeneratedCommandList", "value": [] }, - "0/1029/65529": { - "node_id": 3, - "endpoint": 0, + "1/1029/65529": { + "node_id": 1, + "endpoint": 1, "cluster_id": 1029, "cluster_type": "chip.clusters.Objects.RelativeHumidityMeasurement", "cluster_name": "RelativeHumidityMeasurement", @@ -490,9 +597,9 @@ "attribute_name": "AcceptedCommandList", "value": [] }, - "0/1029/65531": { - "node_id": 3, - "endpoint": 0, + "1/1029/65531": { + "node_id": 1, + "endpoint": 1, "cluster_id": 1029, "cluster_type": "chip.clusters.Objects.RelativeHumidityMeasurement", "cluster_name": "RelativeHumidityMeasurement", diff --git a/tests/components/matter/fixtures/nodes/light-sensor.json b/tests/components/matter/fixtures/nodes/light-sensor.json index 1e7b09c752b..152292d4589 100644 --- a/tests/components/matter/fixtures/nodes/light-sensor.json +++ b/tests/components/matter/fixtures/nodes/light-sensor.json @@ -4,6 +4,113 @@ "last_interview": "2022-11-29T21:23:48.485057", "interview_version": 1, "attributes": { + "0/29/0": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 0, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.DeviceTypeList", + "attribute_name": "DeviceTypeList", + "value": [ + { + "type": 22, + "revision": 1 + } + ] + }, + "0/29/1": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 1, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.ServerList", + "attribute_name": "ServerList", + "value": [ + 4, 29, 31, 40, 42, 43, 44, 48, 49, 50, 51, 52, 53, 54, 55, 59, 60, 62, + 63, 64, 65 + ] + }, + "0/29/2": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 2, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.ClientList", + "attribute_name": "ClientList", + "value": [41] + }, + "0/29/3": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 3, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.PartsList", + "attribute_name": "PartsList", + "value": [1] + }, + "0/29/65532": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 65532, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.FeatureMap", + "attribute_name": "FeatureMap", + "value": 0 + }, + "0/29/65533": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 65533, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.ClusterRevision", + "attribute_name": "ClusterRevision", + "value": 1 + }, + "0/29/65528": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 65528, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.GeneratedCommandList", + "attribute_name": "GeneratedCommandList", + "value": [] + }, + "0/29/65529": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 65529, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.AcceptedCommandList", + "attribute_name": "AcceptedCommandList", + "value": [] + }, + "0/29/65531": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 65531, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.AttributeList", + "attribute_name": "AttributeList", + "value": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533] + }, "0/40/0": { "node_id": 1, "endpoint": 0, diff --git a/tests/components/matter/fixtures/nodes/pressure-sensor.json b/tests/components/matter/fixtures/nodes/pressure-sensor.json index 9422f0c659b..e338933fbc8 100644 --- a/tests/components/matter/fixtures/nodes/pressure-sensor.json +++ b/tests/components/matter/fixtures/nodes/pressure-sensor.json @@ -4,6 +4,113 @@ "last_interview": "2022-11-29T21:23:48.485057", "interview_version": 1, "attributes": { + "0/29/0": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 0, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.DeviceTypeList", + "attribute_name": "DeviceTypeList", + "value": [ + { + "type": 22, + "revision": 1 + } + ] + }, + "0/29/1": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 1, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.ServerList", + "attribute_name": "ServerList", + "value": [ + 4, 29, 31, 40, 42, 43, 44, 48, 49, 50, 51, 52, 53, 54, 55, 59, 60, 62, + 63, 64, 65 + ] + }, + "0/29/2": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 2, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.ClientList", + "attribute_name": "ClientList", + "value": [41] + }, + "0/29/3": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 3, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.PartsList", + "attribute_name": "PartsList", + "value": [1] + }, + "0/29/65532": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 65532, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.FeatureMap", + "attribute_name": "FeatureMap", + "value": 0 + }, + "0/29/65533": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 65533, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.ClusterRevision", + "attribute_name": "ClusterRevision", + "value": 1 + }, + "0/29/65528": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 65528, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.GeneratedCommandList", + "attribute_name": "GeneratedCommandList", + "value": [] + }, + "0/29/65529": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 65529, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.AcceptedCommandList", + "attribute_name": "AcceptedCommandList", + "value": [] + }, + "0/29/65531": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 65531, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.AttributeList", + "attribute_name": "AttributeList", + "value": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533] + }, "0/40/0": { "node_id": 1, "endpoint": 0, diff --git a/tests/components/matter/fixtures/nodes/temperature-sensor.json b/tests/components/matter/fixtures/nodes/temperature-sensor.json index 3631992def8..5e451f31fd2 100644 --- a/tests/components/matter/fixtures/nodes/temperature-sensor.json +++ b/tests/components/matter/fixtures/nodes/temperature-sensor.json @@ -4,6 +4,113 @@ "last_interview": "2022-11-29T21:23:48.485057", "interview_version": 1, "attributes": { + "0/29/0": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 0, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.DeviceTypeList", + "attribute_name": "DeviceTypeList", + "value": [ + { + "type": 22, + "revision": 1 + } + ] + }, + "0/29/1": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 1, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.ServerList", + "attribute_name": "ServerList", + "value": [ + 4, 29, 31, 40, 42, 43, 44, 48, 49, 50, 51, 52, 53, 54, 55, 59, 60, 62, + 63, 64, 65 + ] + }, + "0/29/2": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 2, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.ClientList", + "attribute_name": "ClientList", + "value": [41] + }, + "0/29/3": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 3, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.PartsList", + "attribute_name": "PartsList", + "value": [1] + }, + "0/29/65532": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 65532, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.FeatureMap", + "attribute_name": "FeatureMap", + "value": 0 + }, + "0/29/65533": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 65533, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.ClusterRevision", + "attribute_name": "ClusterRevision", + "value": 1 + }, + "0/29/65528": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 65528, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.GeneratedCommandList", + "attribute_name": "GeneratedCommandList", + "value": [] + }, + "0/29/65529": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 65529, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.AcceptedCommandList", + "attribute_name": "AcceptedCommandList", + "value": [] + }, + "0/29/65531": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 65531, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.AttributeList", + "attribute_name": "AttributeList", + "value": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533] + }, "0/40/0": { "node_id": 1, "endpoint": 0, diff --git a/tests/components/matter/test_sensor.py b/tests/components/matter/test_sensor.py new file mode 100644 index 00000000000..ccdf09b8dc9 --- /dev/null +++ b/tests/components/matter/test_sensor.py @@ -0,0 +1,169 @@ +"""Test Matter sensors.""" +from unittest.mock import MagicMock + +from matter_server.common.models.node import MatterNode +import pytest + +from homeassistant.core import HomeAssistant + +from .common import ( + set_node_attribute, + setup_integration_with_node_fixture, + trigger_subscription_callback, +) + + +@pytest.fixture(name="flow_sensor_node") +async def flow_sensor_node_fixture( + hass: HomeAssistant, matter_client: MagicMock +) -> MatterNode: + """Fixture for a flow sensor node.""" + return await setup_integration_with_node_fixture(hass, "flow-sensor", matter_client) + + +@pytest.fixture(name="humidity_sensor_node") +async def humidity_sensor_node_fixture( + hass: HomeAssistant, matter_client: MagicMock +) -> MatterNode: + """Fixture for a humidity sensor node.""" + return await setup_integration_with_node_fixture( + hass, "humidity-sensor", matter_client + ) + + +@pytest.fixture(name="light_sensor_node") +async def light_sensor_node_fixture( + hass: HomeAssistant, matter_client: MagicMock +) -> MatterNode: + """Fixture for a light sensor node.""" + return await setup_integration_with_node_fixture( + hass, "light-sensor", matter_client + ) + + +@pytest.fixture(name="pressure_sensor_node") +async def pressure_sensor_node_fixture( + hass: HomeAssistant, matter_client: MagicMock +) -> MatterNode: + """Fixture for a pressure sensor node.""" + return await setup_integration_with_node_fixture( + hass, "pressure-sensor", matter_client + ) + + +@pytest.fixture(name="temperature_sensor_node") +async def temperature_sensor_node_fixture( + hass: HomeAssistant, matter_client: MagicMock +) -> MatterNode: + """Fixture for a temperature sensor node.""" + return await setup_integration_with_node_fixture( + hass, "temperature-sensor", matter_client + ) + + +async def test_sensor_null_value( + hass: HomeAssistant, + matter_client: MagicMock, + flow_sensor_node: MatterNode, +) -> None: + """Test flow sensor.""" + state = hass.states.get("sensor.mock_flow_sensor_flow") + assert state + assert state.state == "0.0" + + set_node_attribute(flow_sensor_node, 1, 1028, 0, None) + await trigger_subscription_callback(hass, matter_client) + + state = hass.states.get("sensor.mock_flow_sensor_flow") + assert state + assert state.state == "unknown" + + +async def test_flow_sensor( + hass: HomeAssistant, + matter_client: MagicMock, + flow_sensor_node: MatterNode, +) -> None: + """Test flow sensor.""" + state = hass.states.get("sensor.mock_flow_sensor_flow") + assert state + assert state.state == "0.0" + + set_node_attribute(flow_sensor_node, 1, 1028, 0, 20) + await trigger_subscription_callback(hass, matter_client) + + state = hass.states.get("sensor.mock_flow_sensor_flow") + assert state + assert state.state == "2.0" + + +async def test_humidity_sensor( + hass: HomeAssistant, + matter_client: MagicMock, + humidity_sensor_node: MatterNode, +) -> None: + """Test humidity sensor.""" + state = hass.states.get("sensor.mock_humidity_sensor_humidity") + assert state + assert state.state == "0.0" + + set_node_attribute(humidity_sensor_node, 1, 1029, 0, 4000) + await trigger_subscription_callback(hass, matter_client) + + state = hass.states.get("sensor.mock_humidity_sensor_humidity") + assert state + assert state.state == "40.0" + + +async def test_light_sensor( + hass: HomeAssistant, + matter_client: MagicMock, + light_sensor_node: MatterNode, +) -> None: + """Test light sensor.""" + state = hass.states.get("sensor.mock_light_sensor_light") + assert state + assert state.state == "1.3" + + set_node_attribute(light_sensor_node, 1, 1024, 0, 3000) + await trigger_subscription_callback(hass, matter_client) + + state = hass.states.get("sensor.mock_light_sensor_light") + assert state + assert state.state == "2.0" + + +async def test_pressure_sensor( + hass: HomeAssistant, + matter_client: MagicMock, + pressure_sensor_node: MatterNode, +) -> None: + """Test pressure sensor.""" + state = hass.states.get("sensor.mock_pressure_sensor_pressure") + assert state + assert state.state == "0.0" + + set_node_attribute(pressure_sensor_node, 1, 1027, 0, 1010) + await trigger_subscription_callback(hass, matter_client) + + state = hass.states.get("sensor.mock_pressure_sensor_pressure") + assert state + assert state.state == "101.0" + + +async def test_temperature_sensor( + hass: HomeAssistant, + matter_client: MagicMock, + temperature_sensor_node: MatterNode, +) -> None: + """Test temperature sensor.""" + state = hass.states.get("sensor.mock_temperature_sensor_temperature") + assert state + assert state.state == "21.0" + + set_node_attribute(temperature_sensor_node, 1, 1026, 0, 2500) + await trigger_subscription_callback(hass, matter_client) + + state = hass.states.get("sensor.mock_temperature_sensor_temperature") + assert state + assert state.state == "25.0" From 07c60686b1b190ff9cca60b2b8e6622290089660 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Tue, 6 Dec 2022 21:57:24 +0100 Subject: [PATCH 086/174] Add matter switch platform (#83149) --- .../components/matter/device_platform.py | 2 + homeassistant/components/matter/switch.py | 88 ++++++++++++++ .../fixtures/nodes/on-off-plugin-unit.json | 107 ++++++++++++++++++ tests/components/matter/test_switch.py | 85 ++++++++++++++ 4 files changed, 282 insertions(+) create mode 100644 homeassistant/components/matter/switch.py create mode 100644 tests/components/matter/test_switch.py diff --git a/homeassistant/components/matter/device_platform.py b/homeassistant/components/matter/device_platform.py index 02267b02880..24e7f8b5dc4 100644 --- a/homeassistant/components/matter/device_platform.py +++ b/homeassistant/components/matter/device_platform.py @@ -8,6 +8,7 @@ from homeassistant.const import Platform from .binary_sensor import DEVICE_ENTITY as BINARY_SENSOR_DEVICE_ENTITY from .light import DEVICE_ENTITY as LIGHT_DEVICE_ENTITY from .sensor import DEVICE_ENTITY as SENSOR_DEVICE_ENTITY +from .switch import DEVICE_ENTITY as SWITCH_DEVICE_ENTITY if TYPE_CHECKING: from matter_server.common.models.device_types import DeviceType @@ -25,4 +26,5 @@ DEVICE_PLATFORM: dict[ Platform.BINARY_SENSOR: BINARY_SENSOR_DEVICE_ENTITY, Platform.LIGHT: LIGHT_DEVICE_ENTITY, Platform.SENSOR: SENSOR_DEVICE_ENTITY, + Platform.SWITCH: SWITCH_DEVICE_ENTITY, } diff --git a/homeassistant/components/matter/switch.py b/homeassistant/components/matter/switch.py new file mode 100644 index 00000000000..b7c2b999918 --- /dev/null +++ b/homeassistant/components/matter/switch.py @@ -0,0 +1,88 @@ +"""Matter switches.""" +from __future__ import annotations + +from dataclasses import dataclass +from functools import partial +from typing import TYPE_CHECKING, Any + +from chip.clusters import Objects as clusters +from matter_server.common.models import device_types + +from homeassistant.components.switch import ( + SwitchDeviceClass, + SwitchEntity, + SwitchEntityDescription, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import Platform +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from .const import DOMAIN +from .entity import MatterEntity, MatterEntityDescriptionBaseClass + +if TYPE_CHECKING: + from .adapter import MatterAdapter + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up Matter switches from Config Entry.""" + matter: MatterAdapter = hass.data[DOMAIN][config_entry.entry_id] + matter.register_platform_handler(Platform.SWITCH, async_add_entities) + + +class MatterSwitch(MatterEntity, SwitchEntity): + """Representation of a Matter switch.""" + + entity_description: MatterSwitchEntityDescription + + async def async_turn_on(self, **kwargs: Any) -> None: + """Turn switch on.""" + await self.matter_client.send_device_command( + node_id=self._device_type_instance.node.node_id, + endpoint=self._device_type_instance.endpoint, + command=clusters.OnOff.Commands.On(), + ) + + async def async_turn_off(self, **kwargs: Any) -> None: + """Turn switch off.""" + await self.matter_client.send_device_command( + node_id=self._device_type_instance.node.node_id, + endpoint=self._device_type_instance.endpoint, + command=clusters.OnOff.Commands.Off(), + ) + + @callback + def _update_from_device(self) -> None: + """Update from device.""" + self._attr_is_on = self._device_type_instance.get_cluster(clusters.OnOff).onOff + + +@dataclass +class MatterSwitchEntityDescription( + SwitchEntityDescription, + MatterEntityDescriptionBaseClass, +): + """Matter Switch entity description.""" + + +# You can't set default values on inherited data classes +MatterSwitchEntityDescriptionFactory = partial( + MatterSwitchEntityDescription, entity_cls=MatterSwitch +) + + +DEVICE_ENTITY: dict[ + type[device_types.DeviceType], + MatterEntityDescriptionBaseClass | list[MatterEntityDescriptionBaseClass], +] = { + device_types.OnOffPlugInUnit: MatterSwitchEntityDescriptionFactory( + key=device_types.OnOffPlugInUnit, + subscribe_attributes=(clusters.OnOff.Attributes.OnOff,), + device_class=SwitchDeviceClass.OUTLET, + ), +} diff --git a/tests/components/matter/fixtures/nodes/on-off-plugin-unit.json b/tests/components/matter/fixtures/nodes/on-off-plugin-unit.json index dd1c005bdf8..cbbe39b1f09 100644 --- a/tests/components/matter/fixtures/nodes/on-off-plugin-unit.json +++ b/tests/components/matter/fixtures/nodes/on-off-plugin-unit.json @@ -4,6 +4,113 @@ "last_interview": "2022-11-29T21:23:48.485057", "interview_version": 1, "attributes": { + "0/29/0": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 0, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.DeviceTypeList", + "attribute_name": "DeviceTypeList", + "value": [ + { + "type": 22, + "revision": 1 + } + ] + }, + "0/29/1": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 1, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.ServerList", + "attribute_name": "ServerList", + "value": [ + 4, 29, 31, 40, 42, 43, 44, 48, 49, 50, 51, 52, 53, 54, 55, 59, 60, 62, + 63, 64, 65 + ] + }, + "0/29/2": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 2, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.ClientList", + "attribute_name": "ClientList", + "value": [41] + }, + "0/29/3": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 3, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.PartsList", + "attribute_name": "PartsList", + "value": [1] + }, + "0/29/65532": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 65532, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.FeatureMap", + "attribute_name": "FeatureMap", + "value": 0 + }, + "0/29/65533": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 65533, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.ClusterRevision", + "attribute_name": "ClusterRevision", + "value": 1 + }, + "0/29/65528": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 65528, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.GeneratedCommandList", + "attribute_name": "GeneratedCommandList", + "value": [] + }, + "0/29/65529": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 65529, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.AcceptedCommandList", + "attribute_name": "AcceptedCommandList", + "value": [] + }, + "0/29/65531": { + "node_id": 1, + "endpoint": 0, + "cluster_id": 29, + "cluster_type": "chip.clusters.Objects.Descriptor", + "cluster_name": "Descriptor", + "attribute_id": 65531, + "attribute_type": "chip.clusters.Objects.Descriptor.Attributes.AttributeList", + "attribute_name": "AttributeList", + "value": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533] + }, "0/40/0": { "node_id": 1, "endpoint": 0, diff --git a/tests/components/matter/test_switch.py b/tests/components/matter/test_switch.py new file mode 100644 index 00000000000..a79edd6010b --- /dev/null +++ b/tests/components/matter/test_switch.py @@ -0,0 +1,85 @@ +"""Test Matter switches.""" +from unittest.mock import MagicMock, call + +from chip.clusters import Objects as clusters +from matter_server.common.models.node import MatterNode +import pytest + +from homeassistant.core import HomeAssistant + +from .common import ( + set_node_attribute, + setup_integration_with_node_fixture, + trigger_subscription_callback, +) + + +@pytest.fixture(name="switch_node") +async def switch_node_fixture( + hass: HomeAssistant, matter_client: MagicMock +) -> MatterNode: + """Fixture for a switch node.""" + return await setup_integration_with_node_fixture( + hass, "on-off-plugin-unit", matter_client + ) + + +async def test_turn_on( + hass: HomeAssistant, + matter_client: MagicMock, + switch_node: MatterNode, +) -> None: + """Test turning on a switch.""" + state = hass.states.get("switch.mock_onoff_plugin_unit") + assert state + assert state.state == "off" + + await hass.services.async_call( + "switch", + "turn_on", + { + "entity_id": "switch.mock_onoff_plugin_unit", + }, + blocking=True, + ) + + assert matter_client.send_device_command.call_count == 1 + assert matter_client.send_device_command.call_args == call( + node_id=switch_node.node_id, + endpoint=1, + command=clusters.OnOff.Commands.On(), + ) + + set_node_attribute(switch_node, 1, 6, 0, True) + await trigger_subscription_callback(hass, matter_client) + + state = hass.states.get("switch.mock_onoff_plugin_unit") + assert state + assert state.state == "on" + + +async def test_turn_off( + hass: HomeAssistant, + matter_client: MagicMock, + switch_node: MatterNode, +) -> None: + """Test turning off a switch.""" + state = hass.states.get("switch.mock_onoff_plugin_unit") + assert state + assert state.state == "off" + + await hass.services.async_call( + "switch", + "turn_off", + { + "entity_id": "switch.mock_onoff_plugin_unit", + }, + blocking=True, + ) + + assert matter_client.send_device_command.call_count == 1 + assert matter_client.send_device_command.call_args == call( + node_id=switch_node.node_id, + endpoint=1, + command=clusters.OnOff.Commands.Off(), + ) From c8d560946b3e6817cf695cbc06ec3a84a9c92b55 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Tue, 6 Dec 2022 22:56:33 -0500 Subject: [PATCH 087/174] Add via_device support to zwave_js (#83219) Co-authored-by: Paulus Schoutsen --- homeassistant/components/zwave_js/__init__.py | 31 +++- .../components/zwave_js/device_action.py | 3 + .../components/zwave_js/device_condition.py | 5 +- .../components/zwave_js/device_trigger.py | 9 +- tests/components/zwave_js/conftest.py | 14 +- tests/components/zwave_js/test_button.py | 1 - .../components/zwave_js/test_device_action.py | 17 +- .../zwave_js/test_device_condition.py | 73 ++++++--- .../zwave_js/test_device_trigger.py | 151 ++++++++++++++---- tests/components/zwave_js/test_init.py | 8 +- tests/components/zwave_js/test_sensor.py | 4 +- tests/components/zwave_js/test_services.py | 13 +- tests/components/zwave_js/test_trigger.py | 21 ++- tests/components/zwave_js/test_update.py | 1 - 14 files changed, 251 insertions(+), 100 deletions(-) diff --git a/homeassistant/components/zwave_js/__init__.py b/homeassistant/components/zwave_js/__init__.py index e7b17b30b03..2d6e7350899 100644 --- a/homeassistant/components/zwave_js/__init__.py +++ b/homeassistant/components/zwave_js/__init__.py @@ -230,6 +230,7 @@ class DriverEvents: async def setup(self, driver: Driver) -> None: """Set up devices using the ready driver.""" self.driver = driver + controller = driver.controller # If opt in preference hasn't been specified yet, we do nothing, otherwise # we apply the preference @@ -244,7 +245,7 @@ class DriverEvents: ) known_devices = [ self.dev_reg.async_get_device({get_device_id(driver, node)}) - for node in driver.controller.nodes.values() + for node in controller.nodes.values() ] # Devices that are in the device registry that are not known by the controller can be removed @@ -252,17 +253,24 @@ class DriverEvents: if device not in known_devices: self.dev_reg.async_remove_device(device.id) - # run discovery on all ready nodes + # run discovery on controller node + c_node_id = controller.own_node_id + controller_node = controller.nodes.get(c_node_id) if c_node_id else None + if controller_node: + await self.controller_events.async_on_node_added(controller_node) + + # run discovery on all other ready nodes await asyncio.gather( *( self.controller_events.async_on_node_added(node) - for node in driver.controller.nodes.values() + for node in controller.nodes.values() + if controller_node is None or node != controller_node ) ) # listen for new nodes being added to the mesh self.config_entry.async_on_unload( - driver.controller.on( + controller.on( "node added", lambda event: self.hass.async_create_task( self.controller_events.async_on_node_added(event["node"]) @@ -272,9 +280,7 @@ class DriverEvents: # listen for nodes being removed from the mesh # NOTE: This will not remove nodes that were removed when HA was not running self.config_entry.async_on_unload( - driver.controller.on( - "node removed", self.controller_events.async_on_node_removed - ) + controller.on("node removed", self.controller_events.async_on_node_removed) ) async def async_setup_platform(self, platform: Platform) -> None: @@ -383,6 +389,16 @@ class ControllerEvents: device_id = get_device_id(driver, node) device_id_ext = get_device_id_ext(driver, node) device = self.dev_reg.async_get_device({device_id}) + via_device_id = None + controller = driver.controller + # Get the controller node device ID if this node is not the controller + if ( + controller.own_node_id is not None + and controller.own_node_id != node.node_id + ): + via_device_id = get_device_id( + driver, controller.nodes[controller.own_node_id] + ) # Replace the device if it can be determined that this node is not the # same product as it was previously. @@ -408,6 +424,7 @@ class ControllerEvents: model=node.device_config.label, manufacturer=node.device_config.manufacturer, suggested_area=node.location if node.location else UNDEFINED, + via_device=via_device_id, ) async_dispatcher_send(self.hass, EVENT_DEVICE_ADDED_TO_REGISTRY, device) diff --git a/homeassistant/components/zwave_js/device_action.py b/homeassistant/components/zwave_js/device_action.py index 004e4cc2aae..54dd17b7b83 100644 --- a/homeassistant/components/zwave_js/device_action.py +++ b/homeassistant/components/zwave_js/device_action.py @@ -150,6 +150,9 @@ async def async_get_actions( node = async_get_node_from_device_id(hass, device_id) + if node.client.driver and node.client.driver.controller.own_node_id == node.node_id: + return actions + base_action = { CONF_DEVICE_ID: device_id, CONF_DOMAIN: DOMAIN, diff --git a/homeassistant/components/zwave_js/device_condition.py b/homeassistant/components/zwave_js/device_condition.py index c42b5af71c4..87967c21dd5 100644 --- a/homeassistant/components/zwave_js/device_condition.py +++ b/homeassistant/components/zwave_js/device_condition.py @@ -126,7 +126,7 @@ async def async_get_conditions( hass: HomeAssistant, device_id: str ) -> list[dict[str, str]]: """List device conditions for Z-Wave JS devices.""" - conditions = [] + conditions: list[dict] = [] base_condition = { CONF_CONDITION: "device", CONF_DEVICE_ID: device_id, @@ -134,6 +134,9 @@ async def async_get_conditions( } node = async_get_node_from_device_id(hass, device_id) + if node.client.driver and node.client.driver.controller.own_node_id == node.node_id: + return conditions + # Any value's value condition conditions.append({**base_condition, CONF_TYPE: VALUE_TYPE}) diff --git a/homeassistant/components/zwave_js/device_trigger.py b/homeassistant/components/zwave_js/device_trigger.py index 76a7f134d17..348346680d7 100644 --- a/homeassistant/components/zwave_js/device_trigger.py +++ b/homeassistant/components/zwave_js/device_trigger.py @@ -248,9 +248,6 @@ async def async_get_triggers( hass: HomeAssistant, device_id: str ) -> list[dict[str, Any]]: """List device triggers for Z-Wave JS devices.""" - dev_reg = device_registry.async_get(hass) - node = async_get_node_from_device_id(hass, device_id, dev_reg) - triggers: list[dict] = [] base_trigger = { CONF_PLATFORM: "device", @@ -258,6 +255,12 @@ async def async_get_triggers( CONF_DOMAIN: DOMAIN, } + dev_reg = device_registry.async_get(hass) + node = async_get_node_from_device_id(hass, device_id, dev_reg) + + if node.client.driver and node.client.driver.controller.own_node_id == node.node_id: + return triggers + # We can add a node status trigger if the node status sensor is enabled ent_reg = entity_registry.async_get(hass) entity_id = async_get_node_status_sensor_entity_id( diff --git a/tests/components/zwave_js/conftest.py b/tests/components/zwave_js/conftest.py index e5a759ba0a0..ba97cfe4c36 100644 --- a/tests/components/zwave_js/conftest.py +++ b/tests/components/zwave_js/conftest.py @@ -583,7 +583,9 @@ def lock_home_connect_620_state_fixture(): @pytest.fixture(name="client") -def mock_client_fixture(controller_state, version_state, log_config_state): +def mock_client_fixture( + controller_state, controller_node_state, version_state, log_config_state +): """Mock a client.""" with patch( @@ -608,6 +610,8 @@ def mock_client_fixture(controller_state, version_state, log_config_state): client.listen = AsyncMock(side_effect=listen) client.disconnect = AsyncMock(side_effect=disconnect) client.driver = Driver(client, controller_state, log_config_state) + node = Node(client, copy.deepcopy(controller_node_state)) + client.driver.controller.nodes[node.node_id] = node client.version = VersionInfo.from_message(version_state) client.ws_server_url = "ws://test:3000/zjs" @@ -615,14 +619,6 @@ def mock_client_fixture(controller_state, version_state, log_config_state): yield client -@pytest.fixture(name="controller_node") -def controller_node_fixture(client, controller_node_state): - """Mock a controller node.""" - node = Node(client, copy.deepcopy(controller_node_state)) - client.driver.controller.nodes[node.node_id] = node - return node - - @pytest.fixture(name="multisensor_6") def multisensor_6_fixture(client, multisensor_6_state): """Mock a multisensor 6 node.""" diff --git a/tests/components/zwave_js/test_button.py b/tests/components/zwave_js/test_button.py index 336d3688988..27bd41b3142 100644 --- a/tests/components/zwave_js/test_button.py +++ b/tests/components/zwave_js/test_button.py @@ -10,7 +10,6 @@ async def test_ping_entity( hass, client, climate_radio_thermostat_ct100_plus_different_endpoints, - controller_node, integration, caplog, ): diff --git a/tests/components/zwave_js/test_device_action.py b/tests/components/zwave_js/test_device_action.py index ad9d61b3f33..a60f10761b6 100644 --- a/tests/components/zwave_js/test_device_action.py +++ b/tests/components/zwave_js/test_device_action.py @@ -91,6 +91,16 @@ async def test_get_actions( for action in expected_actions: assert action in actions + # Test that we don't return actions for a controller node + device = dev_reg.async_get_device( + {get_device_id(driver, client.driver.controller.nodes[1])} + ) + assert device + assert ( + await async_get_device_automations(hass, DeviceAutomationType.ACTION, device.id) + == [] + ) + async def test_get_actions_meter( hass: HomeAssistant, @@ -408,9 +418,10 @@ async def test_get_action_capabilities( ): """Test we get the expected action capabilities.""" dev_reg = device_registry.async_get(hass) - device = device_registry.async_entries_for_config_entry( - dev_reg, integration.entry_id - )[0] + device = dev_reg.async_get_device( + {get_device_id(client.driver, climate_radio_thermostat_ct100_plus)} + ) + assert device # Test refresh_value capabilities = await device_action.async_get_action_capabilities( diff --git a/tests/components/zwave_js/test_device_condition.py b/tests/components/zwave_js/test_device_condition.py index 8b538a3c48b..b42573f3aa9 100644 --- a/tests/components/zwave_js/test_device_condition.py +++ b/tests/components/zwave_js/test_device_condition.py @@ -15,7 +15,10 @@ from homeassistant.components.device_automation.exceptions import ( InvalidDeviceAutomationConfig, ) from homeassistant.components.zwave_js import DOMAIN, device_condition -from homeassistant.components.zwave_js.helpers import get_zwave_value_from_config +from homeassistant.components.zwave_js.helpers import ( + get_device_id, + get_zwave_value_from_config, +) from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import config_validation as cv, device_registry from homeassistant.setup import async_setup_component @@ -32,9 +35,10 @@ def calls(hass): async def test_get_conditions(hass, client, lock_schlage_be469, integration) -> None: """Test we get the expected onditions from a zwave_js.""" dev_reg = device_registry.async_get(hass) - device = device_registry.async_entries_for_config_entry( - dev_reg, integration.entry_id - )[0] + device = dev_reg.async_get_device( + {get_device_id(client.driver, lock_schlage_be469)} + ) + assert device config_value = list(lock_schlage_be469.get_configuration_values().values())[0] value_id = config_value.value_id name = config_value.property_name @@ -70,15 +74,28 @@ async def test_get_conditions(hass, client, lock_schlage_be469, integration) -> for condition in expected_conditions: assert condition in conditions + # Test that we don't return actions for a controller node + device = dev_reg.async_get_device( + {get_device_id(client.driver, client.driver.controller.nodes[1])} + ) + assert device + assert ( + await async_get_device_automations( + hass, DeviceAutomationType.CONDITION, device.id + ) + == [] + ) + async def test_node_status_state( hass, client, lock_schlage_be469, integration, calls ) -> None: """Test for node_status conditions.""" dev_reg = device_registry.async_get(hass) - device = device_registry.async_entries_for_config_entry( - dev_reg, integration.entry_id - )[0] + device = dev_reg.async_get_device( + {get_device_id(client.driver, lock_schlage_be469)} + ) + assert device assert await async_setup_component( hass, @@ -224,9 +241,10 @@ async def test_config_parameter_state( ) -> None: """Test for config_parameter conditions.""" dev_reg = device_registry.async_get(hass) - device = device_registry.async_entries_for_config_entry( - dev_reg, integration.entry_id - )[0] + device = dev_reg.async_get_device( + {get_device_id(client.driver, lock_schlage_be469)} + ) + assert device assert await async_setup_component( hass, @@ -333,9 +351,10 @@ async def test_value_state( ) -> None: """Test for value conditions.""" dev_reg = device_registry.async_get(hass) - device = device_registry.async_entries_for_config_entry( - dev_reg, integration.entry_id - )[0] + device = dev_reg.async_get_device( + {get_device_id(client.driver, lock_schlage_be469)} + ) + assert device assert await async_setup_component( hass, @@ -377,9 +396,10 @@ async def test_get_condition_capabilities_node_status( ): """Test we don't get capabilities from a node_status condition.""" dev_reg = device_registry.async_get(hass) - device = device_registry.async_entries_for_config_entry( - dev_reg, integration.entry_id - )[0] + device = dev_reg.async_get_device( + {get_device_id(client.driver, lock_schlage_be469)} + ) + assert device capabilities = await device_condition.async_get_condition_capabilities( hass, @@ -413,9 +433,10 @@ async def test_get_condition_capabilities_value( ): """Test we get the expected capabilities from a value condition.""" dev_reg = device_registry.async_get(hass) - device = device_registry.async_entries_for_config_entry( - dev_reg, integration.entry_id - )[0] + device = dev_reg.async_get_device( + {get_device_id(client.driver, lock_schlage_be469)} + ) + assert device capabilities = await device_condition.async_get_condition_capabilities( hass, @@ -462,9 +483,10 @@ async def test_get_condition_capabilities_config_parameter( """Test we get the expected capabilities from a config_parameter condition.""" node = climate_radio_thermostat_ct100_plus dev_reg = device_registry.async_get(hass) - device = device_registry.async_entries_for_config_entry( - dev_reg, integration.entry_id - )[0] + device = dev_reg.async_get_device( + {get_device_id(client.driver, climate_radio_thermostat_ct100_plus)} + ) + assert device # Test enumerated type param capabilities = await device_condition.async_get_condition_capabilities( @@ -541,9 +563,10 @@ async def test_get_condition_capabilities_config_parameter( async def test_failure_scenarios(hass, client, hank_binary_switch, integration): """Test failure scenarios.""" dev_reg = device_registry.async_get(hass) - device = device_registry.async_entries_for_config_entry( - dev_reg, integration.entry_id - )[0] + device = dev_reg.async_get_device( + {get_device_id(client.driver, hank_binary_switch)} + ) + assert device with pytest.raises(HomeAssistantError): await device_condition.async_condition_from_config( diff --git a/tests/components/zwave_js/test_device_trigger.py b/tests/components/zwave_js/test_device_trigger.py index 84d87efed63..e9bc319fe4d 100644 --- a/tests/components/zwave_js/test_device_trigger.py +++ b/tests/components/zwave_js/test_device_trigger.py @@ -15,13 +15,11 @@ from homeassistant.components.device_automation.exceptions import ( from homeassistant.components.zwave_js import DOMAIN, device_trigger from homeassistant.components.zwave_js.helpers import ( async_get_node_status_sensor_entity_id, + get_device_id, ) from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import config_validation as cv -from homeassistant.helpers.device_registry import ( - async_entries_for_config_entry, - async_get as async_get_dev_reg, -) +from homeassistant.helpers.device_registry import async_get as async_get_dev_reg from homeassistant.helpers.entity_registry import async_get as async_get_ent_reg from homeassistant.setup import async_setup_component @@ -38,12 +36,30 @@ def calls(hass): return async_mock_service(hass, "test", "automation") +async def test_no_controller_triggers(hass, client, integration): + """Test that we do not get triggers for the controller.""" + dev_reg = async_get_dev_reg(hass) + device = dev_reg.async_get_device( + {get_device_id(client.driver, client.driver.controller.nodes[1])} + ) + assert device + assert ( + await async_get_device_automations( + hass, DeviceAutomationType.TRIGGER, device.id + ) + == [] + ) + + async def test_get_notification_notification_triggers( hass, client, lock_schlage_be469, integration ): """Test we get the expected triggers from a zwave_js device with the Notification CC.""" dev_reg = async_get_dev_reg(hass) - device = async_entries_for_config_entry(dev_reg, integration.entry_id)[0] + device = dev_reg.async_get_device( + {get_device_id(client.driver, lock_schlage_be469)} + ) + assert device expected_trigger = { "platform": "device", "domain": DOMAIN, @@ -64,7 +80,10 @@ async def test_if_notification_notification_fires( """Test for event.notification.notification trigger firing.""" node: Node = lock_schlage_be469 dev_reg = async_get_dev_reg(hass) - device = async_entries_for_config_entry(dev_reg, integration.entry_id)[0] + device = dev_reg.async_get_device( + {get_device_id(client.driver, lock_schlage_be469)} + ) + assert device assert await async_setup_component( hass, @@ -157,7 +176,10 @@ async def test_get_trigger_capabilities_notification_notification( ): """Test we get the expected capabilities from a notification.notification trigger.""" dev_reg = async_get_dev_reg(hass) - device = async_entries_for_config_entry(dev_reg, integration.entry_id)[0] + device = dev_reg.async_get_device( + {get_device_id(client.driver, lock_schlage_be469)} + ) + assert device capabilities = await device_trigger.async_get_trigger_capabilities( hass, { @@ -189,7 +211,10 @@ async def test_if_entry_control_notification_fires( """Test for notification.entry_control trigger firing.""" node: Node = lock_schlage_be469 dev_reg = async_get_dev_reg(hass) - device = async_entries_for_config_entry(dev_reg, integration.entry_id)[0] + device = dev_reg.async_get_device( + {get_device_id(client.driver, lock_schlage_be469)} + ) + assert device assert await async_setup_component( hass, @@ -281,7 +306,10 @@ async def test_get_trigger_capabilities_entry_control_notification( ): """Test we get the expected capabilities from a notification.entry_control trigger.""" dev_reg = async_get_dev_reg(hass) - device = async_entries_for_config_entry(dev_reg, integration.entry_id)[0] + device = dev_reg.async_get_device( + {get_device_id(client.driver, lock_schlage_be469)} + ) + assert device capabilities = await device_trigger.async_get_trigger_capabilities( hass, { @@ -308,7 +336,10 @@ async def test_get_trigger_capabilities_entry_control_notification( async def test_get_node_status_triggers(hass, client, lock_schlage_be469, integration): """Test we get the expected triggers from a device with node status sensor enabled.""" dev_reg = async_get_dev_reg(hass) - device = async_entries_for_config_entry(dev_reg, integration.entry_id)[0] + device = dev_reg.async_get_device( + {get_device_id(client.driver, lock_schlage_be469)} + ) + assert device ent_reg = async_get_ent_reg(hass) entity_id = async_get_node_status_sensor_entity_id( hass, device.id, ent_reg, dev_reg @@ -337,7 +368,10 @@ async def test_if_node_status_change_fires( """Test for node_status trigger firing.""" node: Node = lock_schlage_be469 dev_reg = async_get_dev_reg(hass) - device = async_entries_for_config_entry(dev_reg, integration.entry_id)[0] + device = dev_reg.async_get_device( + {get_device_id(client.driver, lock_schlage_be469)} + ) + assert device ent_reg = async_get_ent_reg(hass) entity_id = async_get_node_status_sensor_entity_id( hass, device.id, ent_reg, dev_reg @@ -412,7 +446,10 @@ async def test_get_trigger_capabilities_node_status( ): """Test we get the expected capabilities from a node_status trigger.""" dev_reg = async_get_dev_reg(hass) - device = async_entries_for_config_entry(dev_reg, integration.entry_id)[0] + device = dev_reg.async_get_device( + {get_device_id(client.driver, lock_schlage_be469)} + ) + assert device ent_reg = async_get_ent_reg(hass) entity_id = async_get_node_status_sensor_entity_id( hass, device.id, ent_reg, dev_reg @@ -467,7 +504,10 @@ async def test_get_basic_value_notification_triggers( ): """Test we get the expected triggers from a zwave_js device with the Basic CC.""" dev_reg = async_get_dev_reg(hass) - device = async_entries_for_config_entry(dev_reg, integration.entry_id)[0] + device = dev_reg.async_get_device( + {get_device_id(client.driver, ge_in_wall_dimmer_switch)} + ) + assert device expected_trigger = { "platform": "device", "domain": DOMAIN, @@ -492,7 +532,10 @@ async def test_if_basic_value_notification_fires( """Test for event.value_notification.basic trigger firing.""" node: Node = ge_in_wall_dimmer_switch dev_reg = async_get_dev_reg(hass) - device = async_entries_for_config_entry(dev_reg, integration.entry_id)[0] + device = dev_reg.async_get_device( + {get_device_id(client.driver, ge_in_wall_dimmer_switch)} + ) + assert device assert await async_setup_component( hass, @@ -600,7 +643,10 @@ async def test_get_trigger_capabilities_basic_value_notification( ): """Test we get the expected capabilities from a value_notification.basic trigger.""" dev_reg = async_get_dev_reg(hass) - device = async_entries_for_config_entry(dev_reg, integration.entry_id)[0] + device = dev_reg.async_get_device( + {get_device_id(client.driver, ge_in_wall_dimmer_switch)} + ) + assert device capabilities = await device_trigger.async_get_trigger_capabilities( hass, { @@ -635,7 +681,10 @@ async def test_get_central_scene_value_notification_triggers( ): """Test we get the expected triggers from a zwave_js device with the Central Scene CC.""" dev_reg = async_get_dev_reg(hass) - device = async_entries_for_config_entry(dev_reg, integration.entry_id)[0] + device = dev_reg.async_get_device( + {get_device_id(client.driver, wallmote_central_scene)} + ) + assert device expected_trigger = { "platform": "device", "domain": DOMAIN, @@ -660,7 +709,10 @@ async def test_if_central_scene_value_notification_fires( """Test for event.value_notification.central_scene trigger firing.""" node: Node = wallmote_central_scene dev_reg = async_get_dev_reg(hass) - device = async_entries_for_config_entry(dev_reg, integration.entry_id)[0] + device = dev_reg.async_get_device( + {get_device_id(client.driver, wallmote_central_scene)} + ) + assert device assert await async_setup_component( hass, @@ -775,7 +827,10 @@ async def test_get_trigger_capabilities_central_scene_value_notification( ): """Test we get the expected capabilities from a value_notification.central_scene trigger.""" dev_reg = async_get_dev_reg(hass) - device = async_entries_for_config_entry(dev_reg, integration.entry_id)[0] + device = dev_reg.async_get_device( + {get_device_id(client.driver, wallmote_central_scene)} + ) + assert device capabilities = await device_trigger.async_get_trigger_capabilities( hass, { @@ -809,7 +864,10 @@ async def test_get_scene_activation_value_notification_triggers( ): """Test we get the expected triggers from a zwave_js device with the SceneActivation CC.""" dev_reg = async_get_dev_reg(hass) - device = async_entries_for_config_entry(dev_reg, integration.entry_id)[0] + device = dev_reg.async_get_device( + {get_device_id(client.driver, hank_binary_switch)} + ) + assert device expected_trigger = { "platform": "device", "domain": DOMAIN, @@ -834,7 +892,10 @@ async def test_if_scene_activation_value_notification_fires( """Test for event.value_notification.scene_activation trigger firing.""" node: Node = hank_binary_switch dev_reg = async_get_dev_reg(hass) - device = async_entries_for_config_entry(dev_reg, integration.entry_id)[0] + device = dev_reg.async_get_device( + {get_device_id(client.driver, hank_binary_switch)} + ) + assert device assert await async_setup_component( hass, @@ -942,7 +1003,10 @@ async def test_get_trigger_capabilities_scene_activation_value_notification( ): """Test we get the expected capabilities from a value_notification.scene_activation trigger.""" dev_reg = async_get_dev_reg(hass) - device = async_entries_for_config_entry(dev_reg, integration.entry_id)[0] + device = dev_reg.async_get_device( + {get_device_id(client.driver, hank_binary_switch)} + ) + assert device capabilities = await device_trigger.async_get_trigger_capabilities( hass, { @@ -977,7 +1041,10 @@ async def test_get_value_updated_value_triggers( ): """Test we get the zwave_js.value_updated.value trigger from a zwave_js device.""" dev_reg = async_get_dev_reg(hass) - device = async_entries_for_config_entry(dev_reg, integration.entry_id)[0] + device = dev_reg.async_get_device( + {get_device_id(client.driver, lock_schlage_be469)} + ) + assert device expected_trigger = { "platform": "device", "domain": DOMAIN, @@ -997,7 +1064,10 @@ async def test_if_value_updated_value_fires( """Test for zwave_js.value_updated.value trigger firing.""" node: Node = lock_schlage_be469 dev_reg = async_get_dev_reg(hass) - device = async_entries_for_config_entry(dev_reg, integration.entry_id)[0] + device = dev_reg.async_get_device( + {get_device_id(client.driver, lock_schlage_be469)} + ) + assert device assert await async_setup_component( hass, @@ -1086,7 +1156,10 @@ async def test_value_updated_value_no_driver( """Test zwave_js.value_updated.value trigger with missing driver.""" node: Node = lock_schlage_be469 dev_reg = async_get_dev_reg(hass) - device = async_entries_for_config_entry(dev_reg, integration.entry_id)[0] + device = dev_reg.async_get_device( + {get_device_id(client.driver, lock_schlage_be469)} + ) + assert device driver = client.driver client.driver = None @@ -1153,7 +1226,10 @@ async def test_get_trigger_capabilities_value_updated_value( ): """Test we get the expected capabilities from a zwave_js.value_updated.value trigger.""" dev_reg = async_get_dev_reg(hass) - device = async_entries_for_config_entry(dev_reg, integration.entry_id)[0] + device = dev_reg.async_get_device( + {get_device_id(client.driver, lock_schlage_be469)} + ) + assert device capabilities = await device_trigger.async_get_trigger_capabilities( hass, { @@ -1201,7 +1277,10 @@ async def test_get_value_updated_config_parameter_triggers( ): """Test we get the zwave_js.value_updated.config_parameter trigger from a zwave_js device.""" dev_reg = async_get_dev_reg(hass) - device = async_entries_for_config_entry(dev_reg, integration.entry_id)[0] + device = dev_reg.async_get_device( + {get_device_id(client.driver, lock_schlage_be469)} + ) + assert device expected_trigger = { "platform": "device", "domain": DOMAIN, @@ -1226,7 +1305,10 @@ async def test_if_value_updated_config_parameter_fires( """Test for zwave_js.value_updated.config_parameter trigger firing.""" node: Node = lock_schlage_be469 dev_reg = async_get_dev_reg(hass) - device = async_entries_for_config_entry(dev_reg, integration.entry_id)[0] + device = dev_reg.async_get_device( + {get_device_id(client.driver, lock_schlage_be469)} + ) + assert device assert await async_setup_component( hass, @@ -1293,7 +1375,10 @@ async def test_get_trigger_capabilities_value_updated_config_parameter_range( ): """Test we get the expected capabilities from a range zwave_js.value_updated.config_parameter trigger.""" dev_reg = async_get_dev_reg(hass) - device = async_entries_for_config_entry(dev_reg, integration.entry_id)[0] + device = dev_reg.async_get_device( + {get_device_id(client.driver, lock_schlage_be469)} + ) + assert device capabilities = await device_trigger.async_get_trigger_capabilities( hass, { @@ -1335,7 +1420,10 @@ async def test_get_trigger_capabilities_value_updated_config_parameter_enumerate ): """Test we get the expected capabilities from an enumerated zwave_js.value_updated.config_parameter trigger.""" dev_reg = async_get_dev_reg(hass) - device = async_entries_for_config_entry(dev_reg, integration.entry_id)[0] + device = dev_reg.async_get_device( + {get_device_id(client.driver, lock_schlage_be469)} + ) + assert device capabilities = await device_trigger.async_get_trigger_capabilities( hass, { @@ -1386,7 +1474,10 @@ async def test_failure_scenarios(hass, client, hank_binary_switch, integration): ) dev_reg = async_get_dev_reg(hass) - device = async_entries_for_config_entry(dev_reg, integration.entry_id)[0] + device = dev_reg.async_get_device( + {get_device_id(client.driver, hank_binary_switch)} + ) + assert device with pytest.raises(HomeAssistantError): await device_trigger.async_attach_trigger( diff --git a/tests/components/zwave_js/test_init.py b/tests/components/zwave_js/test_init.py index 4f58c87febb..0d22484bea6 100644 --- a/tests/components/zwave_js/test_init.py +++ b/tests/components/zwave_js/test_init.py @@ -199,7 +199,7 @@ async def test_on_node_added_not_ready( device_id = f"{client.driver.controller.home_id}-{zp3111_not_ready_state['nodeId']}" assert len(hass.states.async_all()) == 0 - assert not dev_reg.devices + assert len(dev_reg.devices) == 1 node_state = deepcopy(zp3111_not_ready_state) node_state["isSecure"] = False @@ -911,12 +911,12 @@ async def test_removed_device( driver = client.driver assert driver # Verify how many nodes are available - assert len(driver.controller.nodes) == 2 + assert len(driver.controller.nodes) == 3 # Make sure there are the same number of devices dev_reg = dr.async_get(hass) device_entries = dr.async_entries_for_config_entry(dev_reg, integration.entry_id) - assert len(device_entries) == 2 + assert len(device_entries) == 3 # Check how many entities there are ent_reg = er.async_get(hass) @@ -931,7 +931,7 @@ async def test_removed_device( # Assert that the node and all of it's entities were removed from the device and # entity registry device_entries = dr.async_entries_for_config_entry(dev_reg, integration.entry_id) - assert len(device_entries) == 1 + assert len(device_entries) == 2 entity_entries = er.async_entries_for_config_entry(ent_reg, integration.entry_id) assert len(entity_entries) == 18 assert dev_reg.async_get_device({get_device_id(driver, old_node)}) is None diff --git a/tests/components/zwave_js/test_sensor.py b/tests/components/zwave_js/test_sensor.py index 848c4d7b0e5..a32537b1d0d 100644 --- a/tests/components/zwave_js/test_sensor.py +++ b/tests/components/zwave_js/test_sensor.py @@ -156,9 +156,7 @@ async def test_config_parameter_sensor(hass, lock_id_lock_as_id150, integration) assert entity_entry.disabled -async def test_node_status_sensor( - hass, client, controller_node, lock_id_lock_as_id150, integration -): +async def test_node_status_sensor(hass, client, lock_id_lock_as_id150, integration): """Test node status sensor is created and gets updated on node state changes.""" NODE_STATUS_ENTITY = "sensor.z_wave_module_for_id_lock_150_and_101_node_status" node = lock_id_lock_as_id150 diff --git a/tests/components/zwave_js/test_services.py b/tests/components/zwave_js/test_services.py index 6e425bff042..710892c4741 100644 --- a/tests/components/zwave_js/test_services.py +++ b/tests/components/zwave_js/test_services.py @@ -34,10 +34,7 @@ from homeassistant.components.zwave_js.helpers import get_device_id from homeassistant.const import ATTR_AREA_ID, ATTR_DEVICE_ID, ATTR_ENTITY_ID from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.area_registry import async_get as async_get_area_reg -from homeassistant.helpers.device_registry import ( - async_entries_for_config_entry, - async_get as async_get_dev_reg, -) +from homeassistant.helpers.device_registry import async_get as async_get_dev_reg from homeassistant.helpers.entity_registry import async_get as async_get_ent_reg from homeassistant.setup import async_setup_component @@ -408,7 +405,8 @@ async def test_set_config_parameter_gather( async def test_bulk_set_config_parameters(hass, client, multisensor_6, integration): """Test the bulk_set_partial_config_parameters service.""" dev_reg = async_get_dev_reg(hass) - device = async_entries_for_config_entry(dev_reg, integration.entry_id)[0] + device = dev_reg.async_get_device({get_device_id(client.driver, multisensor_6)}) + assert device # Test setting config parameter by property and property_key await hass.services.async_call( DOMAIN, @@ -736,7 +734,10 @@ async def test_refresh_value( async def test_set_value(hass, client, climate_danfoss_lc_13, integration): """Test set_value service.""" dev_reg = async_get_dev_reg(hass) - device = async_entries_for_config_entry(dev_reg, integration.entry_id)[0] + device = dev_reg.async_get_device( + {get_device_id(client.driver, climate_danfoss_lc_13)} + ) + assert device await hass.services.async_call( DOMAIN, diff --git a/tests/components/zwave_js/test_trigger.py b/tests/components/zwave_js/test_trigger.py index b3b6910e5f5..56a8e63b439 100644 --- a/tests/components/zwave_js/test_trigger.py +++ b/tests/components/zwave_js/test_trigger.py @@ -9,15 +9,13 @@ from zwave_js_server.model.node import Node from homeassistant.components import automation from homeassistant.components.zwave_js import DOMAIN +from homeassistant.components.zwave_js.helpers import get_device_id from homeassistant.components.zwave_js.trigger import async_validate_trigger_config from homeassistant.components.zwave_js.triggers.trigger_helpers import ( async_bypass_dynamic_config_validation, ) from homeassistant.const import SERVICE_RELOAD -from homeassistant.helpers.device_registry import ( - async_entries_for_config_entry, - async_get as async_get_dev_reg, -) +from homeassistant.helpers.device_registry import async_get as async_get_dev_reg from homeassistant.setup import async_setup_component from .common import SCHLAGE_BE469_LOCK_ENTITY @@ -30,7 +28,10 @@ async def test_zwave_js_value_updated(hass, client, lock_schlage_be469, integrat trigger_type = f"{DOMAIN}.value_updated" node: Node = lock_schlage_be469 dev_reg = async_get_dev_reg(hass) - device = async_entries_for_config_entry(dev_reg, integration.entry_id)[0] + device = dev_reg.async_get_device( + {get_device_id(client.driver, lock_schlage_be469)} + ) + assert device no_value_filter = async_capture_events(hass, "no_value_filter") single_from_value_filter = async_capture_events(hass, "single_from_value_filter") @@ -449,7 +450,10 @@ async def test_zwave_js_event(hass, client, lock_schlage_be469, integration): trigger_type = f"{DOMAIN}.event" node: Node = lock_schlage_be469 dev_reg = async_get_dev_reg(hass) - device = async_entries_for_config_entry(dev_reg, integration.entry_id)[0] + device = dev_reg.async_get_device( + {get_device_id(client.driver, lock_schlage_be469)} + ) + assert device node_no_event_data_filter = async_capture_events(hass, "node_no_event_data_filter") node_event_data_filter = async_capture_events(hass, "node_event_data_filter") @@ -992,7 +996,10 @@ async def test_zwave_js_trigger_config_entry_unloaded( ): """Test zwave_js triggers bypass dynamic validation when needed.""" dev_reg = async_get_dev_reg(hass) - device = async_entries_for_config_entry(dev_reg, integration.entry_id)[0] + device = dev_reg.async_get_device( + {get_device_id(client.driver, lock_schlage_be469)} + ) + assert device # Test bypass check is False assert not async_bypass_dynamic_config_validation( diff --git a/tests/components/zwave_js/test_update.py b/tests/components/zwave_js/test_update.py index 4c00c1c9a3a..1650b7e8edd 100644 --- a/tests/components/zwave_js/test_update.py +++ b/tests/components/zwave_js/test_update.py @@ -72,7 +72,6 @@ async def test_update_entity_states( hass, client, climate_radio_thermostat_ct100_plus_different_endpoints, - controller_node, integration, caplog, hass_ws_client, From 54748cec821c800ce71707965edac4119c8008e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Hjelseth=20H=C3=B8yer?= Date: Tue, 6 Dec 2022 20:08:27 +0100 Subject: [PATCH 088/174] Update tibber lib to 0.26.4, improve logging (#83418) --- homeassistant/components/tibber/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/tibber/manifest.json b/homeassistant/components/tibber/manifest.json index 81b3c1274d2..05bfe96b071 100644 --- a/homeassistant/components/tibber/manifest.json +++ b/homeassistant/components/tibber/manifest.json @@ -3,7 +3,7 @@ "domain": "tibber", "name": "Tibber", "documentation": "https://www.home-assistant.io/integrations/tibber", - "requirements": ["pyTibber==0.26.3"], + "requirements": ["pyTibber==0.26.4"], "codeowners": ["@danielhiversen"], "quality_scale": "silver", "config_flow": true, diff --git a/requirements_all.txt b/requirements_all.txt index ffd8b562d88..d18865533ac 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1432,7 +1432,7 @@ pyRFXtrx==0.30.0 pySwitchmate==0.5.1 # homeassistant.components.tibber -pyTibber==0.26.3 +pyTibber==0.26.4 # homeassistant.components.dlink pyW215==0.7.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index c487cf916e6..7e623d368a5 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1032,7 +1032,7 @@ pyMetno==0.9.0 pyRFXtrx==0.30.0 # homeassistant.components.tibber -pyTibber==0.26.3 +pyTibber==0.26.4 # homeassistant.components.nextbus py_nextbusnext==0.1.5 From 599236171746f5d115b9bb055a8a036297d6dca5 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 6 Dec 2022 17:57:54 -1000 Subject: [PATCH 089/174] Do not try to stop the shelly ble scanner if not connected (#83424) Co-authored-by: Shay Levy --- .../components/shelly/coordinator.py | 3 +- tests/components/shelly/test_init.py | 32 +++++++++++++++++-- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/shelly/coordinator.py b/homeassistant/components/shelly/coordinator.py index 6190181dca9..867cacb3647 100644 --- a/homeassistant/components/shelly/coordinator.py +++ b/homeassistant/components/shelly/coordinator.py @@ -564,7 +564,8 @@ class ShellyRpcCoordinator(DataUpdateCoordinator): async def shutdown(self) -> None: """Shutdown the coordinator.""" - await async_stop_scanner(self.device) + if self.device.connected: + await async_stop_scanner(self.device) await self.device.shutdown() await self._async_disconnected() diff --git a/tests/components/shelly/test_init.py b/tests/components/shelly/test_init.py index 251dc8a8da7..48ea978e5d9 100644 --- a/tests/components/shelly/test_init.py +++ b/tests/components/shelly/test_init.py @@ -1,12 +1,16 @@ """Test cases for the Shelly component.""" from __future__ import annotations -from unittest.mock import AsyncMock +from unittest.mock import AsyncMock, patch from aioshelly.exceptions import DeviceConnectionError, InvalidAuthError import pytest -from homeassistant.components.shelly.const import DOMAIN +from homeassistant.components.shelly.const import ( + CONF_BLE_SCANNER_MODE, + DOMAIN, + BLEScannerMode, +) from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntryState from homeassistant.const import STATE_ON, STATE_UNAVAILABLE from homeassistant.helpers import device_registry @@ -183,3 +187,27 @@ async def test_entry_unload_device_not_ready( await hass.async_block_till_done() assert entry.state is ConfigEntryState.NOT_LOADED + + +async def test_entry_unload_not_connected(hass, mock_rpc_device, monkeypatch): + """Test entry unload when not connected.""" + with patch( + "homeassistant.components.shelly.coordinator.async_stop_scanner" + ) as mock_stop_scanner: + + entry = await init_integration( + hass, 2, options={CONF_BLE_SCANNER_MODE: BLEScannerMode.ACTIVE} + ) + entity_id = "switch.test_switch_0" + + assert entry.state is ConfigEntryState.LOADED + assert hass.states.get(entity_id).state is STATE_ON + assert not mock_stop_scanner.call_count + + monkeypatch.setattr(mock_rpc_device, "connected", False) + + await hass.config_entries.async_reload(entry.entry_id) + await hass.async_block_till_done() + + assert not mock_stop_scanner.call_count + assert entry.state is ConfigEntryState.LOADED From 8703e14cabad3a99b1679075638ec0abfaa3f9f8 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 6 Dec 2022 22:58:47 -0500 Subject: [PATCH 090/174] Bumped version to 2022.12.0b7 --- homeassistant/const.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index cd915aee3e0..5d9de18db12 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -8,7 +8,7 @@ from .backports.enum import StrEnum APPLICATION_NAME: Final = "HomeAssistant" MAJOR_VERSION: Final = 2022 MINOR_VERSION: Final = 12 -PATCH_VERSION: Final = "0b6" +PATCH_VERSION: Final = "0b7" __short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__: Final = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 9, 0) diff --git a/pyproject.toml b/pyproject.toml index 3cf775d2039..d20fe2965c2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "homeassistant" -version = "2022.12.0b6" +version = "2022.12.0b7" license = {text = "Apache-2.0"} description = "Open-source home automation platform running on Python 3." readme = "README.rst" From 047012d167818e54e7d522178f44596a902a1443 Mon Sep 17 00:00:00 2001 From: mbo18 Date: Wed, 7 Dec 2022 13:13:32 +0100 Subject: [PATCH 091/174] Add Child lock support to Tuya devices (#83233) * Add Child lock support to Tuya devices * flake/black * Add attribute to general.py * apply only to TS011F * also update general.py --- .../components/zha/core/channels/general.py | 8 +++++++- homeassistant/components/zha/switch.py | 12 ++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/zha/core/channels/general.py b/homeassistant/components/zha/core/channels/general.py index 3756e3c1233..fbafbdc03e7 100644 --- a/homeassistant/components/zha/core/channels/general.py +++ b/homeassistant/components/zha/core/channels/general.py @@ -348,7 +348,6 @@ class OnOffChannel(ZigbeeChannel): self._off_listener = None if self.cluster.endpoint.model in ( - "TS011F", "TS0121", "TS0001", "TS0002", @@ -360,6 +359,13 @@ class OnOffChannel(ZigbeeChannel): ) self.ZCL_INIT_ATTRS["backlight_mode"] = True self.ZCL_INIT_ATTRS["power_on_state"] = True + elif self.cluster.endpoint.model == "TS011F": + self.ZCL_INIT_ATTRS = ( # pylint: disable=invalid-name + self.ZCL_INIT_ATTRS.copy() + ) + self.ZCL_INIT_ATTRS["backlight_mode"] = True + self.ZCL_INIT_ATTRS["child_lock"] = True + self.ZCL_INIT_ATTRS["power_on_state"] = True @property def on_off(self) -> bool | None: diff --git a/homeassistant/components/zha/switch.py b/homeassistant/components/zha/switch.py index ac285700a27..c9469747a3f 100644 --- a/homeassistant/components/zha/switch.py +++ b/homeassistant/components/zha/switch.py @@ -457,3 +457,15 @@ class AqaraPetFeederChildLock(ZHASwitchConfigurationEntity, id_suffix="child_loc _zcl_attribute: str = "child_lock" _attr_name = "Child lock" _attr_icon: str = "mdi:account-lock" + + +@CONFIG_DIAGNOSTIC_MATCH( + channel_names=CHANNEL_ON_OFF, + models={"TS011F"}, +) +class TuyaChildLockSwitch(ZHASwitchConfigurationEntity, id_suffix="child_lock"): + """Representation of a child lock configuration entity.""" + + _zcl_attribute: str = "child_lock" + _attr_name = "Child lock" + _attr_icon: str = "mdi:account-lock" From 9a15494e695f3f19c614738486cb194a553fd861 Mon Sep 17 00:00:00 2001 From: Maciej Bieniek Date: Wed, 7 Dec 2022 08:11:18 +0100 Subject: [PATCH 092/174] Fix restored temperature values in Shelly climate platform (#83428) * Set last_target_temp value according the unit system * Convert restored temperature values * Add test * Improve comments * Move _last_target_temp value to constants --- homeassistant/components/shelly/climate.py | 33 +++++++++++++-- homeassistant/components/shelly/const.py | 1 + tests/components/shelly/test_climate.py | 48 ++++++++++++++++++++++ 3 files changed, 79 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/shelly/climate.py b/homeassistant/components/shelly/climate.py index cc831c10ee0..d313d6b5757 100644 --- a/homeassistant/components/shelly/climate.py +++ b/homeassistant/components/shelly/climate.py @@ -24,6 +24,8 @@ from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.restore_state import RestoreEntity from homeassistant.helpers.update_coordinator import CoordinatorEntity +from homeassistant.util.unit_conversion import TemperatureConverter +from homeassistant.util.unit_system import US_CUSTOMARY_SYSTEM from .const import LOGGER, SHTRV_01_TEMPERATURE_SETTINGS from .coordinator import ShellyBlockCoordinator, get_entry_data @@ -126,7 +128,14 @@ class BlockSleepingClimate( self.last_state: State | None = None self.last_state_attributes: Mapping[str, Any] self._preset_modes: list[str] = [] - self._last_target_temp = 20.0 + if coordinator.hass.config.units is US_CUSTOMARY_SYSTEM: + self._last_target_temp = TemperatureConverter.convert( + SHTRV_01_TEMPERATURE_SETTINGS["default"], + UnitOfTemperature.CELSIUS, + UnitOfTemperature.FAHRENHEIT, + ) + else: + self._last_target_temp = SHTRV_01_TEMPERATURE_SETTINGS["default"] if self.block is not None and self.device_block is not None: self._unique_id = f"{self.coordinator.mac}-{self.block.description}" @@ -157,14 +166,32 @@ class BlockSleepingClimate( """Set target temperature.""" if self.block is not None: return cast(float, self.block.targetTemp) - return self.last_state_attributes.get("temperature") + # The restored value can be in Fahrenheit so we have to convert it to Celsius + # because we use this unit internally in integration. + target_temp = self.last_state_attributes.get("temperature") + if self.hass.config.units is US_CUSTOMARY_SYSTEM and target_temp: + return TemperatureConverter.convert( + cast(float, target_temp), + UnitOfTemperature.FAHRENHEIT, + UnitOfTemperature.CELSIUS, + ) + return target_temp @property def current_temperature(self) -> float | None: """Return current temperature.""" if self.block is not None: return cast(float, self.block.temp) - return self.last_state_attributes.get("current_temperature") + # The restored value can be in Fahrenheit so we have to convert it to Celsius + # because we use this unit internally in integration. + current_temp = self.last_state_attributes.get("current_temperature") + if self.hass.config.units is US_CUSTOMARY_SYSTEM and current_temp: + return TemperatureConverter.convert( + cast(float, current_temp), + UnitOfTemperature.FAHRENHEIT, + UnitOfTemperature.CELSIUS, + ) + return current_temp @property def available(self) -> bool: diff --git a/homeassistant/components/shelly/const.py b/homeassistant/components/shelly/const.py index 81d4bfd7b14..41656bbcd6f 100644 --- a/homeassistant/components/shelly/const.py +++ b/homeassistant/components/shelly/const.py @@ -147,6 +147,7 @@ SHTRV_01_TEMPERATURE_SETTINGS: Final = { "min": 4, "max": 31, "step": 0.5, + "default": 20.0, } # Kelvin value for colorTemp diff --git a/tests/components/shelly/test_climate.py b/tests/components/shelly/test_climate.py index 56effa156e6..0d43ae118cf 100644 --- a/tests/components/shelly/test_climate.py +++ b/tests/components/shelly/test_climate.py @@ -21,6 +21,7 @@ from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntryState from homeassistant.const import ATTR_ENTITY_ID, ATTR_TEMPERATURE, STATE_UNAVAILABLE from homeassistant.core import State from homeassistant.exceptions import HomeAssistantError +from homeassistant.util.unit_system import US_CUSTOMARY_SYSTEM from . import init_integration, register_device, register_entity @@ -212,6 +213,53 @@ async def test_block_restored_climate(hass, mock_block_device, device_reg, monke assert hass.states.get(entity_id).state == HVACMode.OFF +async def test_block_restored_climate_us_customery( + hass, mock_block_device, device_reg, monkeypatch +): + """Test block restored climate with US CUSTOMATY unit system.""" + hass.config.units = US_CUSTOMARY_SYSTEM + monkeypatch.delattr(mock_block_device.blocks[DEVICE_BLOCK_ID], "targetTemp") + monkeypatch.setattr(mock_block_device.blocks[DEVICE_BLOCK_ID], "valveError", 0) + entry = await init_integration(hass, 1, sleep_period=1000, skip_setup=True) + register_device(device_reg, entry) + entity_id = register_entity( + hass, + CLIMATE_DOMAIN, + "test_name", + "sensor_0", + entry, + ) + attrs = {"current_temperature": 67, "temperature": 68} + mock_restore_cache(hass, [State(entity_id, HVACMode.HEAT, attributes=attrs)]) + + monkeypatch.setattr(mock_block_device, "initialized", False) + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + assert hass.states.get(entity_id).state == HVACMode.HEAT + assert hass.states.get(entity_id).attributes.get("temperature") == 68 + assert hass.states.get(entity_id).attributes.get("current_temperature") == 67 + + # Partial update, should not change state + mock_block_device.mock_update() + await hass.async_block_till_done() + + assert hass.states.get(entity_id).state == HVACMode.HEAT + assert hass.states.get(entity_id).attributes.get("temperature") == 68 + assert hass.states.get(entity_id).attributes.get("current_temperature") == 67 + + # Make device online + monkeypatch.setattr(mock_block_device, "initialized", True) + monkeypatch.setattr(mock_block_device.blocks[SENSOR_BLOCK_ID], "targetTemp", 19.7) + monkeypatch.setattr(mock_block_device.blocks[SENSOR_BLOCK_ID], "temp", 18.2) + mock_block_device.mock_update() + await hass.async_block_till_done() + + assert hass.states.get(entity_id).state == HVACMode.HEAT + assert hass.states.get(entity_id).attributes.get("temperature") == 67 + assert hass.states.get(entity_id).attributes.get("current_temperature") == 65 + + async def test_block_restored_climate_unavailable( hass, mock_block_device, device_reg, monkeypatch ): From 886525112b5c2541a857fafa1e7768f02ec14d26 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 7 Dec 2022 16:46:28 +0100 Subject: [PATCH 093/174] Remove doubtful repairs issue from UniFi Protect (#83463) --- .../components/unifiprotect/migrate.py | 22 +------- .../components/unifiprotect/strings.json | 4 -- .../unifiprotect/translations/en.json | 4 -- tests/components/unifiprotect/test_repairs.py | 54 +------------------ 4 files changed, 2 insertions(+), 82 deletions(-) diff --git a/homeassistant/components/unifiprotect/migrate.py b/homeassistant/components/unifiprotect/migrate.py index b40f78d0ccf..893ca3e458a 100644 --- a/homeassistant/components/unifiprotect/migrate.py +++ b/homeassistant/components/unifiprotect/migrate.py @@ -12,10 +12,7 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.const import Platform from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryNotReady -from homeassistant.helpers import entity_registry as er, issue_registry as ir -from homeassistant.helpers.issue_registry import IssueSeverity - -from .const import DOMAIN +from homeassistant.helpers import entity_registry as er _LOGGER = logging.getLogger(__name__) @@ -33,23 +30,6 @@ async def async_migrate_data( await async_migrate_device_ids(hass, entry, protect) _LOGGER.debug("Completed Migrate: async_migrate_device_ids") - entity_registry = er.async_get(hass) - for entity in er.async_entries_for_config_entry(entity_registry, entry.entry_id): - if ( - entity.domain == Platform.SENSOR - and entity.disabled_by is None - and "detected_object" in entity.unique_id - ): - ir.async_create_issue( - hass, - DOMAIN, - "deprecate_smart_sensor", - is_fixable=False, - breaks_in_ha_version="2023.2.0", - severity=IssueSeverity.WARNING, - translation_key="deprecate_smart_sensor", - ) - async def async_get_bootstrap(protect: ProtectApiClient) -> Bootstrap: """Get UniFi Protect bootstrap or raise appropriate HA error.""" diff --git a/homeassistant/components/unifiprotect/strings.json b/homeassistant/components/unifiprotect/strings.json index 8bdd844fdbf..abac7701279 100644 --- a/homeassistant/components/unifiprotect/strings.json +++ b/homeassistant/components/unifiprotect/strings.json @@ -75,10 +75,6 @@ "ea_setup_failed": { "title": "Setup error using Early Access version", "description": "You are using v{version} of UniFi Protect which is an Early Access version. An unrecoverable error occurred while trying to load the integration. Please [downgrade to a stable version](https://www.home-assistant.io/integrations/unifiprotect#downgrading-unifi-protect) of UniFi Protect to continue using the integration.\n\nError: {error}" - }, - "deprecate_smart_sensor": { - "title": "Smart Detection Sensor Deprecated", - "description": "The unified \"Detected Object\" sensor for smart detections is now deprecated. It has been replaced with individual smart detection binary sensors for each smart detection type. Please update any templates or automations accordingly." } } } diff --git a/homeassistant/components/unifiprotect/translations/en.json b/homeassistant/components/unifiprotect/translations/en.json index 21b3cd64360..65a398375fe 100644 --- a/homeassistant/components/unifiprotect/translations/en.json +++ b/homeassistant/components/unifiprotect/translations/en.json @@ -42,10 +42,6 @@ } }, "issues": { - "deprecate_smart_sensor": { - "description": "The unified \"Detected Object\" sensor for smart detections is now deprecated. It has been replaced with individual smart detection binary sensors for each smart detection type. Please update any templates or automations accordingly.", - "title": "Smart Detection Sensor Deprecated" - }, "ea_setup_failed": { "description": "You are using v{version} of UniFi Protect which is an Early Access version. An unrecoverable error occurred while trying to load the integration. Please [downgrade to a stable version](https://www.home-assistant.io/integrations/unifiprotect#downgrading-unifi-protect) of UniFi Protect to continue using the integration.\n\nError: {error}", "title": "Setup error using Early Access version" diff --git a/tests/components/unifiprotect/test_repairs.py b/tests/components/unifiprotect/test_repairs.py index a1279dbed84..3ffd2ea4a43 100644 --- a/tests/components/unifiprotect/test_repairs.py +++ b/tests/components/unifiprotect/test_repairs.py @@ -6,7 +6,7 @@ from copy import copy from http import HTTPStatus from unittest.mock import Mock -from pyunifiprotect.data import Camera, Version +from pyunifiprotect.data import Version from homeassistant.components.repairs.issue_handler import ( async_process_repairs_platforms, @@ -16,9 +16,7 @@ from homeassistant.components.repairs.websocket_api import ( RepairsFlowResourceView, ) from homeassistant.components.unifiprotect.const import DOMAIN -from homeassistant.const import Platform from homeassistant.core import HomeAssistant -from homeassistant.helpers import entity_registry as er from .utils import MockUFPFixture, init_entry @@ -126,53 +124,3 @@ async def test_ea_warning_fix( data = await resp.json() assert data["type"] == "create_entry" - - -async def test_deprecate_smart_default( - hass: HomeAssistant, ufp: MockUFPFixture, hass_ws_client, doorbell: Camera -): - """Test Deprecate Sensor repair does not exist by default (new installs).""" - - await init_entry(hass, ufp, [doorbell]) - - await async_process_repairs_platforms(hass) - ws_client = await hass_ws_client(hass) - - await ws_client.send_json({"id": 1, "type": "repairs/list_issues"}) - msg = await ws_client.receive_json() - - assert msg["success"] - issue = None - for i in msg["result"]["issues"]: - if i["issue_id"] == "deprecate_smart_sensor": - issue = i - assert issue is None - - -async def test_deprecate_smart_active( - hass: HomeAssistant, ufp: MockUFPFixture, hass_ws_client, doorbell: Camera -): - """Test Deprecate Sensor repair exists for existing installs.""" - - registry = er.async_get(hass) - registry.async_get_or_create( - Platform.SENSOR, - DOMAIN, - f"{doorbell.mac}_detected_object", - config_entry=ufp.entry, - ) - - await init_entry(hass, ufp, [doorbell]) - - await async_process_repairs_platforms(hass) - ws_client = await hass_ws_client(hass) - - await ws_client.send_json({"id": 1, "type": "repairs/list_issues"}) - msg = await ws_client.receive_json() - - assert msg["success"] - issue = None - for i in msg["result"]["issues"]: - if i["issue_id"] == "deprecate_smart_sensor": - issue = i - assert issue is not None From ebf133ef802dd20c295db61ec2ed66e875c0195a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Wed, 7 Dec 2022 13:17:45 +0100 Subject: [PATCH 094/174] Bump hass-nabucasa from 0.59.0 to 0.61.0 (#83466) --- homeassistant/components/cloud/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/cloud/manifest.json b/homeassistant/components/cloud/manifest.json index 23d72f7cb03..70049e2a426 100644 --- a/homeassistant/components/cloud/manifest.json +++ b/homeassistant/components/cloud/manifest.json @@ -2,7 +2,7 @@ "domain": "cloud", "name": "Home Assistant Cloud", "documentation": "https://www.home-assistant.io/integrations/cloud", - "requirements": ["hass-nabucasa==0.59.0"], + "requirements": ["hass-nabucasa==0.61.0"], "dependencies": ["http", "webhook"], "after_dependencies": ["google_assistant", "alexa"], "codeowners": ["@home-assistant/cloud"], diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 406b9972aca..7fc929ff2c8 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -20,7 +20,7 @@ ciso8601==2.2.0 cryptography==38.0.3 dbus-fast==1.75.0 fnvhash==0.1.0 -hass-nabucasa==0.59.0 +hass-nabucasa==0.61.0 home-assistant-bluetooth==1.8.1 home-assistant-frontend==20221206.0 httpx==0.23.1 diff --git a/requirements_all.txt b/requirements_all.txt index d18865533ac..9b7c3816d8a 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -848,7 +848,7 @@ ha-philipsjs==2.9.0 habitipy==0.2.0 # homeassistant.components.cloud -hass-nabucasa==0.59.0 +hass-nabucasa==0.61.0 # homeassistant.components.splunk hass_splunk==0.1.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 7e623d368a5..06891e4cf24 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -640,7 +640,7 @@ ha-philipsjs==2.9.0 habitipy==0.2.0 # homeassistant.components.cloud -hass-nabucasa==0.59.0 +hass-nabucasa==0.61.0 # homeassistant.components.tasmota hatasmota==0.6.1 From f97795fbb94bef4fa0a639542df1e23df1db3bba Mon Sep 17 00:00:00 2001 From: "David F. Mulcahey" Date: Wed, 7 Dec 2022 10:48:13 -0500 Subject: [PATCH 095/174] Bump the ZHA quirks lib to 0.0.88 (#83468) --- homeassistant/components/zha/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/zha/manifest.json b/homeassistant/components/zha/manifest.json index 8493d8678a7..50a30142bc5 100644 --- a/homeassistant/components/zha/manifest.json +++ b/homeassistant/components/zha/manifest.json @@ -7,7 +7,7 @@ "bellows==0.34.5", "pyserial==3.5", "pyserial-asyncio==0.6", - "zha-quirks==0.0.87", + "zha-quirks==0.0.88", "zigpy-deconz==0.19.2", "zigpy==0.52.3", "zigpy-xbee==0.16.2", diff --git a/requirements_all.txt b/requirements_all.txt index 9b7c3816d8a..81085056c00 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2639,7 +2639,7 @@ zengge==0.2 zeroconf==0.39.4 # homeassistant.components.zha -zha-quirks==0.0.87 +zha-quirks==0.0.88 # homeassistant.components.zhong_hong zhong_hong_hvac==1.0.9 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 06891e4cf24..f9b6609d03a 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1840,7 +1840,7 @@ zamg==0.1.1 zeroconf==0.39.4 # homeassistant.components.zha -zha-quirks==0.0.87 +zha-quirks==0.0.88 # homeassistant.components.zha zigpy-deconz==0.19.2 From 141f37504d95a507b56b2457dff810fe763b8b27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Wed, 7 Dec 2022 16:48:34 +0100 Subject: [PATCH 096/174] When an account exist without a subscription "provider" will not exist (#83472) --- homeassistant/components/cloud/repairs.py | 2 +- tests/components/cloud/test_repairs.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/cloud/repairs.py b/homeassistant/components/cloud/repairs.py index 779c0eb64b0..0d217521c21 100644 --- a/homeassistant/components/cloud/repairs.py +++ b/homeassistant/components/cloud/repairs.py @@ -30,7 +30,7 @@ def async_manage_legacy_subscription_issue( If the provider is "legacy" create an issue, in all other cases remove the issue. """ - if subscription_info["provider"] == "legacy": + if subscription_info.get("provider") == "legacy": ir.async_create_issue( hass=hass, domain=DOMAIN, diff --git a/tests/components/cloud/test_repairs.py b/tests/components/cloud/test_repairs.py index ef96efaa402..052cdde0d0d 100644 --- a/tests/components/cloud/test_repairs.py +++ b/tests/components/cloud/test_repairs.py @@ -70,7 +70,7 @@ async def test_legacy_subscription_delete_issue_if_no_longer_legacy( domain="cloud", issue_id="legacy_subscription" ) - cloud_repairs.async_manage_legacy_subscription_issue(hass, {"provider": None}) + cloud_repairs.async_manage_legacy_subscription_issue(hass, {}) assert not issue_registry.async_get_issue( domain="cloud", issue_id="legacy_subscription" ) From 1631d10365678e9dd7a66b92cc3c135a6788b63c Mon Sep 17 00:00:00 2001 From: TheJulianJES Date: Wed, 7 Dec 2022 16:53:08 +0100 Subject: [PATCH 097/174] Cleanup ZHA initialization for TS011F child_lock (#83478) --- homeassistant/components/zha/core/channels/general.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/zha/core/channels/general.py b/homeassistant/components/zha/core/channels/general.py index fbafbdc03e7..47d0cafb01c 100644 --- a/homeassistant/components/zha/core/channels/general.py +++ b/homeassistant/components/zha/core/channels/general.py @@ -348,6 +348,7 @@ class OnOffChannel(ZigbeeChannel): self._off_listener = None if self.cluster.endpoint.model in ( + "TS011F", "TS0121", "TS0001", "TS0002", @@ -359,13 +360,8 @@ class OnOffChannel(ZigbeeChannel): ) self.ZCL_INIT_ATTRS["backlight_mode"] = True self.ZCL_INIT_ATTRS["power_on_state"] = True - elif self.cluster.endpoint.model == "TS011F": - self.ZCL_INIT_ATTRS = ( # pylint: disable=invalid-name - self.ZCL_INIT_ATTRS.copy() - ) - self.ZCL_INIT_ATTRS["backlight_mode"] = True - self.ZCL_INIT_ATTRS["child_lock"] = True - self.ZCL_INIT_ATTRS["power_on_state"] = True + if self.cluster.endpoint.model == "TS011F": + self.ZCL_INIT_ATTRS["child_lock"] = True @property def on_off(self) -> bool | None: From 8553faf3c891b6b789b05a869a1c4b071bc5194e Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Wed, 7 Dec 2022 17:46:42 +0100 Subject: [PATCH 098/174] Update frontend to 20221207.0 (#83479) --- homeassistant/components/frontend/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/frontend/manifest.json b/homeassistant/components/frontend/manifest.json index 3093928f21f..83514976793 100644 --- a/homeassistant/components/frontend/manifest.json +++ b/homeassistant/components/frontend/manifest.json @@ -2,7 +2,7 @@ "domain": "frontend", "name": "Home Assistant Frontend", "documentation": "https://www.home-assistant.io/integrations/frontend", - "requirements": ["home-assistant-frontend==20221206.0"], + "requirements": ["home-assistant-frontend==20221207.0"], "dependencies": [ "api", "auth", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 7fc929ff2c8..367eb4f6a8a 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -22,7 +22,7 @@ dbus-fast==1.75.0 fnvhash==0.1.0 hass-nabucasa==0.61.0 home-assistant-bluetooth==1.8.1 -home-assistant-frontend==20221206.0 +home-assistant-frontend==20221207.0 httpx==0.23.1 ifaddr==0.1.7 janus==1.0.0 diff --git a/requirements_all.txt b/requirements_all.txt index 81085056c00..58decaa1158 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -884,7 +884,7 @@ hole==0.7.0 holidays==0.17.2 # homeassistant.components.frontend -home-assistant-frontend==20221206.0 +home-assistant-frontend==20221207.0 # homeassistant.components.home_connect homeconnect==0.7.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index f9b6609d03a..4f1a6bc31bf 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -664,7 +664,7 @@ hole==0.7.0 holidays==0.17.2 # homeassistant.components.frontend -home-assistant-frontend==20221206.0 +home-assistant-frontend==20221207.0 # homeassistant.components.home_connect homeconnect==0.7.2 From 54dd55645949609138eca2275fa67c20adcfd522 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 7 Dec 2022 17:49:50 +0100 Subject: [PATCH 099/174] Bumped version to 2022.12.0 --- homeassistant/const.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 5d9de18db12..70b1f7799e6 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -8,7 +8,7 @@ from .backports.enum import StrEnum APPLICATION_NAME: Final = "HomeAssistant" MAJOR_VERSION: Final = 2022 MINOR_VERSION: Final = 12 -PATCH_VERSION: Final = "0b7" +PATCH_VERSION: Final = "0" __short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__: Final = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 9, 0) diff --git a/pyproject.toml b/pyproject.toml index d20fe2965c2..3c65b01d26d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "homeassistant" -version = "2022.12.0b7" +version = "2022.12.0" license = {text = "Apache-2.0"} description = "Open-source home automation platform running on Python 3." readme = "README.rst" From 92bc93466eb5e5bfdf259a8e5a317a43198be71d Mon Sep 17 00:00:00 2001 From: puddly <32534428+puddly@users.noreply.github.com> Date: Wed, 7 Dec 2022 12:45:16 -0500 Subject: [PATCH 100/174] Fix missing Shelly `UnitOfTemperature` import (#83483) --- homeassistant/components/shelly/climate.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/shelly/climate.py b/homeassistant/components/shelly/climate.py index d313d6b5757..a624ba341af 100644 --- a/homeassistant/components/shelly/climate.py +++ b/homeassistant/components/shelly/climate.py @@ -16,7 +16,7 @@ from homeassistant.components.climate import ( HVACMode, ) from homeassistant.config_entries import ConfigEntry -from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS +from homeassistant.const import ATTR_TEMPERATURE, UnitOfTemperature from homeassistant.core import HomeAssistant, State, callback from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import device_registry, entity_registry @@ -110,7 +110,7 @@ class BlockSleepingClimate( ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.PRESET_MODE ) _attr_target_temperature_step = SHTRV_01_TEMPERATURE_SETTINGS["step"] - _attr_temperature_unit = TEMP_CELSIUS + _attr_temperature_unit = UnitOfTemperature.CELSIUS def __init__( self, From ec7302ef1bf8880f583adfcb261d31c06c8d9f79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20K=C3=BCndig?= Date: Thu, 8 Dec 2022 14:23:25 +0100 Subject: [PATCH 101/174] Set connectable as false for sensirion_ble (#83481) This is a passive ble integration. Not setting it as false means it won't match properly when using through a passive remote scanner. --- homeassistant/components/sensirion_ble/manifest.json | 2 ++ homeassistant/generated/bluetooth.py | 2 ++ 2 files changed, 4 insertions(+) diff --git a/homeassistant/components/sensirion_ble/manifest.json b/homeassistant/components/sensirion_ble/manifest.json index f13f393a844..a3011639d3e 100644 --- a/homeassistant/components/sensirion_ble/manifest.json +++ b/homeassistant/components/sensirion_ble/manifest.json @@ -5,9 +5,11 @@ "documentation": "https://www.home-assistant.io/integrations/sensirion_ble", "bluetooth": [ { + "connectable": false, "manufacturer_id": 1749 }, { + "connectable": false, "local_name": "MyCO2*" } ], diff --git a/homeassistant/generated/bluetooth.py b/homeassistant/generated/bluetooth.py index 922e754e84a..42bea46086e 100644 --- a/homeassistant/generated/bluetooth.py +++ b/homeassistant/generated/bluetooth.py @@ -273,10 +273,12 @@ BLUETOOTH: list[dict[str, bool | str | int | list[int]]] = [ "local_name": "Ruuvi *", }, { + "connectable": False, "domain": "sensirion_ble", "manufacturer_id": 1749, }, { + "connectable": False, "domain": "sensirion_ble", "local_name": "MyCO2*", }, From f4948e8f48676929b842c7ace0349d6fb2ca2be3 Mon Sep 17 00:00:00 2001 From: Joakim Plate Date: Thu, 8 Dec 2022 02:21:55 +0100 Subject: [PATCH 102/174] Make sure super async_added_to_hass is called (#83493) Make sure super added to hass is called --- homeassistant/components/philips_js/media_player.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/homeassistant/components/philips_js/media_player.py b/homeassistant/components/philips_js/media_player.py index 04e63008e7b..d9ea06c2f2c 100644 --- a/homeassistant/components/philips_js/media_player.py +++ b/homeassistant/components/philips_js/media_player.py @@ -101,6 +101,8 @@ class PhilipsTVMediaPlayer( async def async_added_to_hass(self) -> None: """Handle being added to hass.""" + await super().async_added_to_hass() + if (entry := self.registry_entry) and entry.device_id: self.async_on_remove( self._turn_on.async_register( From 94160903a74fb093c8edfebb184b2137be6da5e9 Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Wed, 7 Dec 2022 19:54:58 -0700 Subject: [PATCH 103/174] Bump `simplisafe-python` to 2022.12.0 (#83497) --- homeassistant/components/simplisafe/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/simplisafe/manifest.json b/homeassistant/components/simplisafe/manifest.json index 99c332955c3..3db4ec9e8ac 100644 --- a/homeassistant/components/simplisafe/manifest.json +++ b/homeassistant/components/simplisafe/manifest.json @@ -3,7 +3,7 @@ "name": "SimpliSafe", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/simplisafe", - "requirements": ["simplisafe-python==2022.11.2"], + "requirements": ["simplisafe-python==2022.12.0"], "codeowners": ["@bachya"], "iot_class": "cloud_polling", "dhcp": [ diff --git a/requirements_all.txt b/requirements_all.txt index 58decaa1158..44d8672f2ab 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2291,7 +2291,7 @@ simplehound==0.3 simplepush==2.1.1 # homeassistant.components.simplisafe -simplisafe-python==2022.11.2 +simplisafe-python==2022.12.0 # homeassistant.components.sisyphus sisyphus-control==3.1.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 4f1a6bc31bf..d4979c807e4 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1588,7 +1588,7 @@ simplehound==0.3 simplepush==2.1.1 # homeassistant.components.simplisafe -simplisafe-python==2022.11.2 +simplisafe-python==2022.12.0 # homeassistant.components.slack slackclient==2.5.0 From a64e56c8adbdda9f4fd0241c7e42034d0ad27a41 Mon Sep 17 00:00:00 2001 From: Marcel van der Veldt Date: Thu, 8 Dec 2022 01:31:44 +0100 Subject: [PATCH 104/174] Bump python-matter-server to 1.0.7 (#83507) --- homeassistant/components/matter/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/matter/manifest.json b/homeassistant/components/matter/manifest.json index aa64ac4755e..280a6b38da5 100644 --- a/homeassistant/components/matter/manifest.json +++ b/homeassistant/components/matter/manifest.json @@ -3,7 +3,7 @@ "name": "Matter (BETA)", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/matter", - "requirements": ["python-matter-server==1.0.6"], + "requirements": ["python-matter-server==1.0.7"], "dependencies": ["websocket_api"], "codeowners": ["@MartinHjelmare", "@marcelveldt"], "iot_class": "local_push" diff --git a/requirements_all.txt b/requirements_all.txt index 44d8672f2ab..e65d8da8002 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2030,7 +2030,7 @@ python-kasa==0.5.0 # python-lirc==1.2.3 # homeassistant.components.matter -python-matter-server==1.0.6 +python-matter-server==1.0.7 # homeassistant.components.xiaomi_miio python-miio==0.5.12 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index d4979c807e4..4c867e0d289 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1417,7 +1417,7 @@ python-juicenet==1.1.0 python-kasa==0.5.0 # homeassistant.components.matter -python-matter-server==1.0.6 +python-matter-server==1.0.7 # homeassistant.components.xiaomi_miio python-miio==0.5.12 From b7396a736ec3d898e3fc58a404cc7834f520817a Mon Sep 17 00:00:00 2001 From: mezz64 <2854333+mezz64@users.noreply.github.com> Date: Thu, 8 Dec 2022 13:30:20 -0500 Subject: [PATCH 105/174] Bump pyhik to 0.3.2 (#83517) Increase pyHik version to fix videoloss error --- homeassistant/components/hikvision/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/hikvision/manifest.json b/homeassistant/components/hikvision/manifest.json index 4b27e37b0f6..2df3e4b6e64 100644 --- a/homeassistant/components/hikvision/manifest.json +++ b/homeassistant/components/hikvision/manifest.json @@ -2,7 +2,7 @@ "domain": "hikvision", "name": "Hikvision", "documentation": "https://www.home-assistant.io/integrations/hikvision", - "requirements": ["pyhik==0.3.1"], + "requirements": ["pyhik==0.3.2"], "codeowners": ["@mezz64"], "iot_class": "local_push", "loggers": ["pyhik"] diff --git a/requirements_all.txt b/requirements_all.txt index e65d8da8002..d6d8b46af3c 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1627,7 +1627,7 @@ pyhaversion==22.8.0 pyheos==0.7.2 # homeassistant.components.hikvision -pyhik==0.3.1 +pyhik==0.3.2 # homeassistant.components.hive pyhiveapi==0.5.14 From 609fc9196fac86adbb9884864668ee9ea57a3919 Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Thu, 8 Dec 2022 08:45:21 -0800 Subject: [PATCH 106/174] Bump ical to 4.2.2 (#83520) Co-authored-by: Shay Levy --- homeassistant/components/local_calendar/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/local_calendar/manifest.json b/homeassistant/components/local_calendar/manifest.json index fa258c389ab..2921b3e2e10 100644 --- a/homeassistant/components/local_calendar/manifest.json +++ b/homeassistant/components/local_calendar/manifest.json @@ -3,7 +3,7 @@ "name": "Local Calendar", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/local_calendar", - "requirements": ["ical==4.2.1"], + "requirements": ["ical==4.2.2"], "codeowners": ["@allenporter"], "iot_class": "local_polling", "loggers": ["ical"] diff --git a/requirements_all.txt b/requirements_all.txt index d6d8b46af3c..c57260af2b4 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -926,7 +926,7 @@ ibm-watson==5.2.2 ibmiotf==0.3.4 # homeassistant.components.local_calendar -ical==4.2.1 +ical==4.2.2 # homeassistant.components.ping icmplib==3.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 4c867e0d289..d01f0bb2119 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -691,7 +691,7 @@ iaqualink==0.5.0 ibeacon_ble==1.0.1 # homeassistant.components.local_calendar -ical==4.2.1 +ical==4.2.2 # homeassistant.components.ping icmplib==3.0 From 2cf1235b78d5388b9a65d8fb8824083f5f1b82c4 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Thu, 8 Dec 2022 15:42:40 +0100 Subject: [PATCH 107/174] Fix issue with Callable, Union, and Python 3.9 [mqtt] (#83547) --- homeassistant/components/mqtt/client.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/mqtt/client.py b/homeassistant/components/mqtt/client.py index 36e12c47419..f55b1066420 100644 --- a/homeassistant/components/mqtt/client.py +++ b/homeassistant/components/mqtt/client.py @@ -1,8 +1,11 @@ """Support for MQTT message handling.""" +# pylint: disable=deprecated-typing-alias +# In Python 3.9.0 and 3.9.1 collections.abc.Callable +# can't be used inside typing.Union or typing.Optional from __future__ import annotations import asyncio -from collections.abc import Callable, Coroutine, Iterable +from collections.abc import Coroutine, Iterable from functools import lru_cache, partial, wraps import inspect from itertools import groupby @@ -10,7 +13,7 @@ import logging from operator import attrgetter import ssl import time -from typing import TYPE_CHECKING, Any, Union, cast +from typing import TYPE_CHECKING, Any, Callable, Union, cast import uuid import attr From b548be35bc7e994dacffe186a28a4980ca6c6ad8 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Thu, 8 Dec 2022 17:18:39 +0100 Subject: [PATCH 108/174] Update frontend to 20221208.0 (#83551) --- homeassistant/components/frontend/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/frontend/manifest.json b/homeassistant/components/frontend/manifest.json index 83514976793..57a31456c3c 100644 --- a/homeassistant/components/frontend/manifest.json +++ b/homeassistant/components/frontend/manifest.json @@ -2,7 +2,7 @@ "domain": "frontend", "name": "Home Assistant Frontend", "documentation": "https://www.home-assistant.io/integrations/frontend", - "requirements": ["home-assistant-frontend==20221207.0"], + "requirements": ["home-assistant-frontend==20221208.0"], "dependencies": [ "api", "auth", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 367eb4f6a8a..04412d26580 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -22,7 +22,7 @@ dbus-fast==1.75.0 fnvhash==0.1.0 hass-nabucasa==0.61.0 home-assistant-bluetooth==1.8.1 -home-assistant-frontend==20221207.0 +home-assistant-frontend==20221208.0 httpx==0.23.1 ifaddr==0.1.7 janus==1.0.0 diff --git a/requirements_all.txt b/requirements_all.txt index c57260af2b4..2e72a7f34a3 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -884,7 +884,7 @@ hole==0.7.0 holidays==0.17.2 # homeassistant.components.frontend -home-assistant-frontend==20221207.0 +home-assistant-frontend==20221208.0 # homeassistant.components.home_connect homeconnect==0.7.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index d01f0bb2119..c529492eb9d 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -664,7 +664,7 @@ hole==0.7.0 holidays==0.17.2 # homeassistant.components.frontend -home-assistant-frontend==20221207.0 +home-assistant-frontend==20221208.0 # homeassistant.components.home_connect homeconnect==0.7.2 From 5994dd558bb2629c6a05f7cb4b5e9108b34def95 Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Thu, 8 Dec 2022 13:44:45 -0800 Subject: [PATCH 109/174] Improve local calendar input validation error handling (#83563) --- .../components/local_calendar/calendar.py | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/local_calendar/calendar.py b/homeassistant/components/local_calendar/calendar.py index 79d16634883..63a2f2b03d2 100644 --- a/homeassistant/components/local_calendar/calendar.py +++ b/homeassistant/components/local_calendar/calendar.py @@ -11,6 +11,8 @@ from ical.calendar_stream import IcsCalendarStream from ical.event import Event from ical.store import EventStore from ical.types import Range, Recur +from pydantic import ValidationError +import voluptuous as vol from homeassistant.components.calendar import ( EVENT_DESCRIPTION, @@ -102,14 +104,19 @@ class LocalCalendarEntity(CalendarEntity): async def async_create_event(self, **kwargs: Any) -> None: """Add a new event to calendar.""" - event = Event.parse_obj( - { - EVENT_SUMMARY: kwargs[EVENT_SUMMARY], - EVENT_START: kwargs[EVENT_START], - EVENT_END: kwargs[EVENT_END], - EVENT_DESCRIPTION: kwargs.get(EVENT_DESCRIPTION), - } - ) + event_data = { + EVENT_SUMMARY: kwargs[EVENT_SUMMARY], + EVENT_START: kwargs[EVENT_START], + EVENT_END: kwargs[EVENT_END], + EVENT_DESCRIPTION: kwargs.get(EVENT_DESCRIPTION), + } + try: + event = Event.parse_obj(event_data) + except ValidationError as err: + _LOGGER.debug( + "Error parsing event input fields: %s (%s)", event_data, str(err) + ) + raise vol.Invalid("Error parsing event input fields") from err if rrule := kwargs.get(EVENT_RRULE): event.rrule = Recur.from_rrule(rrule) From 8ccc77eb3de5a06877645007bf75713550fef6d4 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 8 Dec 2022 16:45:23 -0500 Subject: [PATCH 110/174] Bumped version to 2022.12.1 --- homeassistant/const.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 70b1f7799e6..3c96f48f6be 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -8,7 +8,7 @@ from .backports.enum import StrEnum APPLICATION_NAME: Final = "HomeAssistant" MAJOR_VERSION: Final = 2022 MINOR_VERSION: Final = 12 -PATCH_VERSION: Final = "0" +PATCH_VERSION: Final = "1" __short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__: Final = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 9, 0) diff --git a/pyproject.toml b/pyproject.toml index 3c65b01d26d..a1ae9d9c89a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "homeassistant" -version = "2022.12.0" +version = "2022.12.1" license = {text = "Apache-2.0"} description = "Open-source home automation platform running on Python 3." readme = "README.rst" From 803d4a999886255ceb413d4e088a621a328cf92b Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 8 Dec 2022 12:44:33 +0100 Subject: [PATCH 111/174] Bump pip_check conflicts +1 (#83536) * Bump pip_check conflicts +1 * Update script/pip_check Co-authored-by: Philip Allgaier Co-authored-by: Philip Allgaier --- script/pip_check | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/script/pip_check b/script/pip_check index 9ed327b54f4..cbe6a3851e0 100755 --- a/script/pip_check +++ b/script/pip_check @@ -2,8 +2,8 @@ PIP_CACHE=$1 # Number of existing dependency conflicts -# Update if a PR resolve one! -DEPENDENCY_CONFLICTS=3 +# Update if a PR resolves one! +DEPENDENCY_CONFLICTS=4 PIP_CHECK=$(pip check --cache-dir=$PIP_CACHE) LINE_COUNT=$(echo "$PIP_CHECK" | wc -l) From 5cdcbcd5fe9af7b931ddceb3c2c5cba43a961420 Mon Sep 17 00:00:00 2001 From: Jeef Date: Thu, 8 Dec 2022 15:43:41 -0700 Subject: [PATCH 112/174] Bump intellifire4py to 2.2.2 (#83589) --- homeassistant/components/intellifire/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/intellifire/manifest.json b/homeassistant/components/intellifire/manifest.json index 67dc5a96ad7..d83e5f76397 100644 --- a/homeassistant/components/intellifire/manifest.json +++ b/homeassistant/components/intellifire/manifest.json @@ -3,7 +3,7 @@ "name": "IntelliFire", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/intellifire", - "requirements": ["intellifire4py==2.2.1"], + "requirements": ["intellifire4py==2.2.2"], "codeowners": ["@jeeftor"], "iot_class": "local_polling", "loggers": ["intellifire4py"], diff --git a/requirements_all.txt b/requirements_all.txt index 2e72a7f34a3..e3b2341a0d0 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -956,7 +956,7 @@ inkbird-ble==0.5.5 insteon-frontend-home-assistant==0.2.0 # homeassistant.components.intellifire -intellifire4py==2.2.1 +intellifire4py==2.2.2 # homeassistant.components.iotawatt iotawattpy==0.1.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index c529492eb9d..9a44a2cc0a2 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -712,7 +712,7 @@ inkbird-ble==0.5.5 insteon-frontend-home-assistant==0.2.0 # homeassistant.components.intellifire -intellifire4py==2.2.1 +intellifire4py==2.2.2 # homeassistant.components.iotawatt iotawattpy==0.1.0 From 19ddf478e2470514b674e7bfd4ca7e68ab9bb5d5 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 8 Dec 2022 15:29:14 -1000 Subject: [PATCH 113/174] Bump bluetooth-auto-recovery to 0.5.5 (#83597) changelog: https://github.com/Bluetooth-Devices/bluetooth-auto-recovery/compare/v0.5.4...v0.5.5 fixes #78036 --- homeassistant/components/bluetooth/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/bluetooth/manifest.json b/homeassistant/components/bluetooth/manifest.json index a8e1c02c8b0..3c926db79d4 100644 --- a/homeassistant/components/bluetooth/manifest.json +++ b/homeassistant/components/bluetooth/manifest.json @@ -9,7 +9,7 @@ "bleak==0.19.2", "bleak-retry-connector==2.10.1", "bluetooth-adapters==0.12.0", - "bluetooth-auto-recovery==0.5.4", + "bluetooth-auto-recovery==0.5.5", "bluetooth-data-tools==0.3.0", "dbus-fast==1.75.0" ], diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 04412d26580..f0f3db19ca7 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -13,7 +13,7 @@ bcrypt==3.1.7 bleak-retry-connector==2.10.1 bleak==0.19.2 bluetooth-adapters==0.12.0 -bluetooth-auto-recovery==0.5.4 +bluetooth-auto-recovery==0.5.5 bluetooth-data-tools==0.3.0 certifi>=2021.5.30 ciso8601==2.2.0 diff --git a/requirements_all.txt b/requirements_all.txt index e3b2341a0d0..9c5329d5072 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -450,7 +450,7 @@ bluemaestro-ble==0.2.0 bluetooth-adapters==0.12.0 # homeassistant.components.bluetooth -bluetooth-auto-recovery==0.5.4 +bluetooth-auto-recovery==0.5.5 # homeassistant.components.bluetooth # homeassistant.components.led_ble diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 9a44a2cc0a2..97451c725df 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -364,7 +364,7 @@ bluemaestro-ble==0.2.0 bluetooth-adapters==0.12.0 # homeassistant.components.bluetooth -bluetooth-auto-recovery==0.5.4 +bluetooth-auto-recovery==0.5.5 # homeassistant.components.bluetooth # homeassistant.components.led_ble From 9c7b80090a1d4537282400b8f533c5d48a144948 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 8 Dec 2022 22:43:52 -0500 Subject: [PATCH 114/174] Disable multi-pan (#83603) * Disable multi-pan * One more test skip --- .../silabs_multiprotocol_addon.py | 11 ++++++++++- .../components/homeassistant_hardware/strings.json | 3 ++- .../homeassistant_hardware/translations/en.json | 1 + .../components/homeassistant_sky_connect/strings.json | 3 ++- .../homeassistant_sky_connect/translations/en.json | 1 + .../components/homeassistant_yellow/strings.json | 3 ++- .../homeassistant_yellow/translations/en.json | 1 + .../test_silabs_multiprotocol_addon.py | 3 +++ .../homeassistant_sky_connect/test_config_flow.py | 4 ++++ .../homeassistant_yellow/test_config_flow.py | 4 ++++ 10 files changed, 30 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/homeassistant_hardware/silabs_multiprotocol_addon.py b/homeassistant/components/homeassistant_hardware/silabs_multiprotocol_addon.py index 500555cc6ae..38c20a8e3cc 100644 --- a/homeassistant/components/homeassistant_hardware/silabs_multiprotocol_addon.py +++ b/homeassistant/components/homeassistant_hardware/silabs_multiprotocol_addon.py @@ -236,7 +236,16 @@ class OptionsFlowHandler(BaseMultiPanFlow, config_entries.OptionsFlow): if not is_hassio(self.hass): return self.async_abort(reason="not_hassio") - return await self.async_step_on_supervisor() + return self.async_abort( + reason="disabled_due_to_bug", + description_placeholders={ + "url": "https://developers.home-assistant.io/blog/2022/12/08/multi-pan-rollback" + }, + ) + + # pylint: disable=unreachable + + return await self.async_step_on_supervisor() # type: ignore[unreachable] async def async_step_on_supervisor( self, user_input: dict[str, Any] | None = None diff --git a/homeassistant/components/homeassistant_hardware/strings.json b/homeassistant/components/homeassistant_hardware/strings.json index 47549794fc8..819ec38925f 100644 --- a/homeassistant/components/homeassistant_hardware/strings.json +++ b/homeassistant/components/homeassistant_hardware/strings.json @@ -32,7 +32,8 @@ "addon_set_config_failed": "Failed to set Silicon Labs Multiprotocol configuration.", "addon_start_failed": "Failed to start the Silicon Labs Multiprotocol add-on.", "not_hassio": "The hardware options can only be configured on HassOS installations.", - "zha_migration_failed": "The ZHA migration did not succeed." + "zha_migration_failed": "The ZHA migration did not succeed.", + "disabled_due_to_bug": "The hardware options are temporarily disabled while we fix a bug. [Learn more]({url})" }, "progress": { "install_addon": "Please wait while the Silicon Labs Multiprotocol add-on installation finishes. This can take several minutes.", diff --git a/homeassistant/components/homeassistant_hardware/translations/en.json b/homeassistant/components/homeassistant_hardware/translations/en.json index ec75e234c4d..2d2e59c30fa 100644 --- a/homeassistant/components/homeassistant_hardware/translations/en.json +++ b/homeassistant/components/homeassistant_hardware/translations/en.json @@ -6,6 +6,7 @@ "addon_install_failed": "Failed to install the Silicon Labs Multiprotocol add-on.", "addon_set_config_failed": "Failed to set Silicon Labs Multiprotocol configuration.", "addon_start_failed": "Failed to start the Silicon Labs Multiprotocol add-on.", + "disabled_due_to_bug": "The hardware options are temporarily disabled while we fix a bug. [Learn more]({url})", "not_hassio": "The hardware options can only be configured on HassOS installations.", "zha_migration_failed": "The ZHA migration did not succeed." }, diff --git a/homeassistant/components/homeassistant_sky_connect/strings.json b/homeassistant/components/homeassistant_sky_connect/strings.json index 970f9d97a4c..d18d2620318 100644 --- a/homeassistant/components/homeassistant_sky_connect/strings.json +++ b/homeassistant/components/homeassistant_sky_connect/strings.json @@ -31,7 +31,8 @@ "addon_set_config_failed": "[%key:component::homeassistant_hardware::silabs_multiprotocol_hardware::options::abort::addon_set_config_failed%]", "addon_start_failed": "[%key:component::homeassistant_hardware::silabs_multiprotocol_hardware::options::abort::addon_start_failed%]", "not_hassio": "[%key:component::homeassistant_hardware::silabs_multiprotocol_hardware::options::abort::not_hassio%]", - "zha_migration_failed": "[%key:component::homeassistant_hardware::silabs_multiprotocol_hardware::options::abort::zha_migration_failed%]" + "zha_migration_failed": "[%key:component::homeassistant_hardware::silabs_multiprotocol_hardware::options::abort::zha_migration_failed%]", + "disabled_due_to_bug": "[%key:component::homeassistant_hardware::silabs_multiprotocol_hardware::options::abort::disabled_due_to_bug%]" }, "progress": { "install_addon": "[%key:component::homeassistant_hardware::silabs_multiprotocol_hardware::options::progress::install_addon%]", diff --git a/homeassistant/components/homeassistant_sky_connect/translations/en.json b/homeassistant/components/homeassistant_sky_connect/translations/en.json index 8e12173f86a..0631d74db75 100644 --- a/homeassistant/components/homeassistant_sky_connect/translations/en.json +++ b/homeassistant/components/homeassistant_sky_connect/translations/en.json @@ -5,6 +5,7 @@ "addon_install_failed": "Failed to install the Silicon Labs Multiprotocol add-on.", "addon_set_config_failed": "Failed to set Silicon Labs Multiprotocol configuration.", "addon_start_failed": "Failed to start the Silicon Labs Multiprotocol add-on.", + "disabled_due_to_bug": "The hardware options are temporarily disabled while we fix a bug. [Learn more]({url})", "not_hassio": "The hardware options can only be configured on HassOS installations.", "zha_migration_failed": "The ZHA migration did not succeed." }, diff --git a/homeassistant/components/homeassistant_yellow/strings.json b/homeassistant/components/homeassistant_yellow/strings.json index 970f9d97a4c..d18d2620318 100644 --- a/homeassistant/components/homeassistant_yellow/strings.json +++ b/homeassistant/components/homeassistant_yellow/strings.json @@ -31,7 +31,8 @@ "addon_set_config_failed": "[%key:component::homeassistant_hardware::silabs_multiprotocol_hardware::options::abort::addon_set_config_failed%]", "addon_start_failed": "[%key:component::homeassistant_hardware::silabs_multiprotocol_hardware::options::abort::addon_start_failed%]", "not_hassio": "[%key:component::homeassistant_hardware::silabs_multiprotocol_hardware::options::abort::not_hassio%]", - "zha_migration_failed": "[%key:component::homeassistant_hardware::silabs_multiprotocol_hardware::options::abort::zha_migration_failed%]" + "zha_migration_failed": "[%key:component::homeassistant_hardware::silabs_multiprotocol_hardware::options::abort::zha_migration_failed%]", + "disabled_due_to_bug": "[%key:component::homeassistant_hardware::silabs_multiprotocol_hardware::options::abort::disabled_due_to_bug%]" }, "progress": { "install_addon": "[%key:component::homeassistant_hardware::silabs_multiprotocol_hardware::options::progress::install_addon%]", diff --git a/homeassistant/components/homeassistant_yellow/translations/en.json b/homeassistant/components/homeassistant_yellow/translations/en.json index 8e12173f86a..0631d74db75 100644 --- a/homeassistant/components/homeassistant_yellow/translations/en.json +++ b/homeassistant/components/homeassistant_yellow/translations/en.json @@ -5,6 +5,7 @@ "addon_install_failed": "Failed to install the Silicon Labs Multiprotocol add-on.", "addon_set_config_failed": "Failed to set Silicon Labs Multiprotocol configuration.", "addon_start_failed": "Failed to start the Silicon Labs Multiprotocol add-on.", + "disabled_due_to_bug": "The hardware options are temporarily disabled while we fix a bug. [Learn more]({url})", "not_hassio": "The hardware options can only be configured on HassOS installations.", "zha_migration_failed": "The ZHA migration did not succeed." }, diff --git a/tests/components/homeassistant_hardware/test_silabs_multiprotocol_addon.py b/tests/components/homeassistant_hardware/test_silabs_multiprotocol_addon.py index 577ac25eb82..fbf6cdda4c7 100644 --- a/tests/components/homeassistant_hardware/test_silabs_multiprotocol_addon.py +++ b/tests/components/homeassistant_hardware/test_silabs_multiprotocol_addon.py @@ -20,6 +20,9 @@ from tests.common import MockConfigEntry, MockModule, mock_integration, mock_pla TEST_DOMAIN = "test" +pytest.skip(reason="Temporarily disabled", allow_module_level=True) + + class TestConfigFlow(ConfigFlow, domain=TEST_DOMAIN): """Handle a config flow for the silabs multiprotocol add-on.""" diff --git a/tests/components/homeassistant_sky_connect/test_config_flow.py b/tests/components/homeassistant_sky_connect/test_config_flow.py index c38edf00fa7..931abc69c4a 100644 --- a/tests/components/homeassistant_sky_connect/test_config_flow.py +++ b/tests/components/homeassistant_sky_connect/test_config_flow.py @@ -2,6 +2,8 @@ import copy from unittest.mock import Mock, patch +import pytest + from homeassistant.components import homeassistant_sky_connect, usb from homeassistant.components.homeassistant_sky_connect.const import DOMAIN from homeassistant.components.zha.core.const import ( @@ -150,6 +152,7 @@ async def test_config_flow_update_device(hass: HomeAssistant) -> None: assert len(mock_unload_entry.mock_calls) == 1 +@pytest.mark.skip(reason="Temporarily disabled") async def test_option_flow_install_multi_pan_addon( hass: HomeAssistant, addon_store_info, @@ -240,6 +243,7 @@ def mock_detect_radio_type(radio_type=RadioType.ezsp, ret=True): return detect +@pytest.mark.skip(reason="Temporarily disabled") @patch( "homeassistant.components.zha.radio_manager.ZhaRadioManager.detect_radio_type", mock_detect_radio_type(), diff --git a/tests/components/homeassistant_yellow/test_config_flow.py b/tests/components/homeassistant_yellow/test_config_flow.py index 53d1c5e974d..3d846501524 100644 --- a/tests/components/homeassistant_yellow/test_config_flow.py +++ b/tests/components/homeassistant_yellow/test_config_flow.py @@ -1,6 +1,8 @@ """Test the Home Assistant Yellow config flow.""" from unittest.mock import Mock, patch +import pytest + from homeassistant.components.homeassistant_yellow.const import DOMAIN from homeassistant.components.zha.core.const import DOMAIN as ZHA_DOMAIN from homeassistant.core import HomeAssistant @@ -59,6 +61,7 @@ async def test_config_flow_single_entry(hass: HomeAssistant) -> None: mock_setup_entry.assert_not_called() +@pytest.mark.skip(reason="Temporarily disabled") async def test_option_flow_install_multi_pan_addon( hass: HomeAssistant, addon_store_info, @@ -127,6 +130,7 @@ async def test_option_flow_install_multi_pan_addon( assert result["type"] == FlowResultType.CREATE_ENTRY +@pytest.mark.skip(reason="Temporarily disabled") async def test_option_flow_install_multi_pan_addon_zha( hass: HomeAssistant, addon_store_info, From e9796547c6360a37ccba2b5f84ea835d396871e7 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 8 Dec 2022 23:21:45 -0500 Subject: [PATCH 115/174] Make mypy happy (#83605) --- .../homeassistant_hardware/silabs_multiprotocol_addon.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/homeassistant_hardware/silabs_multiprotocol_addon.py b/homeassistant/components/homeassistant_hardware/silabs_multiprotocol_addon.py index 38c20a8e3cc..41dbcabae43 100644 --- a/homeassistant/components/homeassistant_hardware/silabs_multiprotocol_addon.py +++ b/homeassistant/components/homeassistant_hardware/silabs_multiprotocol_addon.py @@ -245,7 +245,7 @@ class OptionsFlowHandler(BaseMultiPanFlow, config_entries.OptionsFlow): # pylint: disable=unreachable - return await self.async_step_on_supervisor() # type: ignore[unreachable] + return await self.async_step_on_supervisor() async def async_step_on_supervisor( self, user_input: dict[str, Any] | None = None From b22bcc568a87285938a2d207540144b21ce77562 Mon Sep 17 00:00:00 2001 From: Maarten Weyns Date: Fri, 9 Dec 2022 23:16:15 +0100 Subject: [PATCH 116/174] Bump pyrisco to 0.5.7 (#83548) --- homeassistant/components/risco/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/risco/manifest.json b/homeassistant/components/risco/manifest.json index d31d148f4da..c827718c4ff 100644 --- a/homeassistant/components/risco/manifest.json +++ b/homeassistant/components/risco/manifest.json @@ -3,7 +3,7 @@ "name": "Risco", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/risco", - "requirements": ["pyrisco==0.5.6"], + "requirements": ["pyrisco==0.5.7"], "codeowners": ["@OnFreund"], "quality_scale": "platinum", "iot_class": "local_push", diff --git a/requirements_all.txt b/requirements_all.txt index 9c5329d5072..97de678b1f9 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1866,7 +1866,7 @@ pyrecswitch==1.0.2 pyrepetierng==0.1.0 # homeassistant.components.risco -pyrisco==0.5.6 +pyrisco==0.5.7 # homeassistant.components.rituals_perfume_genie pyrituals==0.0.6 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 97451c725df..a31a6eb3798 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1322,7 +1322,7 @@ pyps4-2ndscreen==1.3.1 pyqwikswitch==0.93 # homeassistant.components.risco -pyrisco==0.5.6 +pyrisco==0.5.7 # homeassistant.components.rituals_perfume_genie pyrituals==0.0.6 From 169b7c91c367abcc905746b747d70e2dc4012966 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Fri, 9 Dec 2022 20:33:00 +0100 Subject: [PATCH 117/174] Bump pychromecast to 13.0.2 (#83625) * Bump pychromecast to 13.0.2 * Pass hass UUID to HomeAssistantController --- homeassistant/components/cast/home_assistant_cast.py | 5 ++++- homeassistant/components/cast/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/cast/home_assistant_cast.py b/homeassistant/components/cast/home_assistant_cast.py index 010e4a046b4..dc15bc3440e 100644 --- a/homeassistant/components/cast/home_assistant_cast.py +++ b/homeassistant/components/cast/home_assistant_cast.py @@ -7,7 +7,7 @@ import voluptuous as vol from homeassistant import auth, config_entries, core from homeassistant.const import ATTR_ENTITY_ID from homeassistant.exceptions import HomeAssistantError -from homeassistant.helpers import config_validation as cv, dispatcher +from homeassistant.helpers import config_validation as cv, dispatcher, instance_id from homeassistant.helpers.network import NoURLAvailableError, get_url from homeassistant.helpers.service import async_register_admin_service @@ -49,10 +49,13 @@ async def async_setup_ha_cast( except NoURLAvailableError as err: raise HomeAssistantError(NO_URL_AVAILABLE_ERROR) from err + hass_uuid = await instance_id.async_get(hass) + controller = HomeAssistantController( # If you are developing Home Assistant Cast, uncomment and set to your dev app id. # app_id="5FE44367", hass_url=hass_url, + hass_uuid=hass_uuid, client_id=None, refresh_token=refresh_token.token, ) diff --git a/homeassistant/components/cast/manifest.json b/homeassistant/components/cast/manifest.json index c5dc56a7da0..00688f642ad 100644 --- a/homeassistant/components/cast/manifest.json +++ b/homeassistant/components/cast/manifest.json @@ -3,7 +3,7 @@ "name": "Google Cast", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/cast", - "requirements": ["pychromecast==13.0.1"], + "requirements": ["pychromecast==13.0.2"], "after_dependencies": [ "cloud", "http", diff --git a/requirements_all.txt b/requirements_all.txt index 97de678b1f9..f6f63561e53 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1504,7 +1504,7 @@ pycfdns==2.0.1 pychannels==1.2.3 # homeassistant.components.cast -pychromecast==13.0.1 +pychromecast==13.0.2 # homeassistant.components.pocketcasts pycketcasts==1.0.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index a31a6eb3798..71969f10091 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1077,7 +1077,7 @@ pybravia==0.2.3 pycfdns==2.0.1 # homeassistant.components.cast -pychromecast==13.0.1 +pychromecast==13.0.2 # homeassistant.components.comfoconnect pycomfoconnect==0.4 From 77336427a4d0d7b90be3436ed5c5a1bdd667b65e Mon Sep 17 00:00:00 2001 From: rappenze Date: Sat, 10 Dec 2022 19:23:07 +0100 Subject: [PATCH 118/174] Fix fibaro climate unit (#83659) --- homeassistant/components/fibaro/climate.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/fibaro/climate.py b/homeassistant/components/fibaro/climate.py index 7731b2544c4..692a0781797 100644 --- a/homeassistant/components/fibaro/climate.py +++ b/homeassistant/components/fibaro/climate.py @@ -164,7 +164,8 @@ class FibaroThermostat(FibaroDevice, ClimateEntity): ): self._target_temp_device = FibaroDevice(device) self._attr_supported_features |= ClimateEntityFeature.TARGET_TEMPERATURE - tempunit = device.properties.unit + if "unit" in device.properties: + tempunit = device.properties.unit if any(action for action in OP_MODE_ACTIONS if action in device.actions): self._op_mode_device = FibaroDevice(device) From eb03307bce04b494b7b0380f8660e83ed4cf86d9 Mon Sep 17 00:00:00 2001 From: Christopher Bailey Date: Sun, 11 Dec 2022 10:32:26 -0800 Subject: [PATCH 119/174] Fix event sensor for UniFi Protect (#83663) * Fix event sensor for UniFi Protect * Linting * Adds no cover * Clean up check * Linting --- .../components/unifiprotect/sensor.py | 35 ++++++++++++------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/homeassistant/components/unifiprotect/sensor.py b/homeassistant/components/unifiprotect/sensor.py index fa08892e2d3..25f8e814536 100644 --- a/homeassistant/components/unifiprotect/sensor.py +++ b/homeassistant/components/unifiprotect/sensor.py @@ -772,20 +772,29 @@ class ProtectEventSensor(EventEntityMixin, SensorEntity): def _async_update_device_from_protect(self, device: ProtectModelWithId) -> None: # do not call ProtectDeviceSensor method since we want event to get value here EventEntityMixin._async_update_device_from_protect(self, device) - if ( + is_on = self.entity_description.get_is_on(device) + is_license_plate = ( self.entity_description.ufp_smart_type == SmartDetectObjectType.LICENSE_PLATE + ) + if ( + not is_on + or self._event is None + or ( + is_license_plate + and ( + self._event.metadata is None + or self._event.metadata.license_plate is None + ) + ) ): - if ( - self._event is None - or self._event.metadata is None - or self._event.metadata.license_plate is None - ): - self._attr_native_value = OBJECT_TYPE_NONE - else: - self._attr_native_value = self._event.metadata.license_plate.name + self._attr_native_value = OBJECT_TYPE_NONE + self._event = None + self._attr_extra_state_attributes = {} + return + + if is_license_plate: + # type verified above + self._attr_native_value = self._event.metadata.license_plate.name # type: ignore[union-attr] else: - if self._event is None: - self._attr_native_value = OBJECT_TYPE_NONE - else: - self._attr_native_value = self._event.smart_detect_types[0].value + self._attr_native_value = self._event.smart_detect_types[0].value From 4c346697f08d71ca811e942e02f46ef4688fd5ee Mon Sep 17 00:00:00 2001 From: SukramJ Date: Sat, 10 Dec 2022 03:18:47 +0100 Subject: [PATCH 120/174] Add missing CONF_SPEED to sensor device_trigger SCHEMA (#83679) * Update device_trigger.py * add missing CONF_IS_SPEED to device_condition --- homeassistant/components/sensor/device_condition.py | 1 + homeassistant/components/sensor/device_trigger.py | 1 + 2 files changed, 2 insertions(+) diff --git a/homeassistant/components/sensor/device_condition.py b/homeassistant/components/sensor/device_condition.py index 34a2590ce8e..0e6a629a648 100644 --- a/homeassistant/components/sensor/device_condition.py +++ b/homeassistant/components/sensor/device_condition.py @@ -143,6 +143,7 @@ CONDITION_SCHEMA = vol.All( CONF_IS_PRESSURE, CONF_IS_REACTIVE_POWER, CONF_IS_SIGNAL_STRENGTH, + CONF_IS_SPEED, CONF_IS_SULPHUR_DIOXIDE, CONF_IS_TEMPERATURE, CONF_IS_VOLATILE_ORGANIC_COMPOUNDS, diff --git a/homeassistant/components/sensor/device_trigger.py b/homeassistant/components/sensor/device_trigger.py index c1b0699664e..2550afad2fc 100644 --- a/homeassistant/components/sensor/device_trigger.py +++ b/homeassistant/components/sensor/device_trigger.py @@ -143,6 +143,7 @@ TRIGGER_SCHEMA = vol.All( CONF_PRESSURE, CONF_REACTIVE_POWER, CONF_SIGNAL_STRENGTH, + CONF_SPEED, CONF_SULPHUR_DIOXIDE, CONF_TEMPERATURE, CONF_VOLATILE_ORGANIC_COMPOUNDS, From d8982116fbe0fc3a6604a95452c0f89e3b2d7008 Mon Sep 17 00:00:00 2001 From: Shay Levy Date: Fri, 9 Dec 2022 22:21:38 +0200 Subject: [PATCH 121/174] Bump aioshelly to 5.1.1 (#83680) --- homeassistant/components/shelly/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/shelly/manifest.json b/homeassistant/components/shelly/manifest.json index 7c2fe3beef6..aae3e0107b5 100644 --- a/homeassistant/components/shelly/manifest.json +++ b/homeassistant/components/shelly/manifest.json @@ -3,7 +3,7 @@ "name": "Shelly", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/shelly", - "requirements": ["aioshelly==5.1.0"], + "requirements": ["aioshelly==5.1.1"], "dependencies": ["bluetooth", "http"], "zeroconf": [ { diff --git a/requirements_all.txt b/requirements_all.txt index f6f63561e53..5c565e5b4de 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -261,7 +261,7 @@ aiosenseme==0.6.1 aiosenz==1.0.0 # homeassistant.components.shelly -aioshelly==5.1.0 +aioshelly==5.1.1 # homeassistant.components.skybell aioskybell==22.7.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 71969f10091..d25b85a4a56 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -236,7 +236,7 @@ aiosenseme==0.6.1 aiosenz==1.0.0 # homeassistant.components.shelly -aioshelly==5.1.0 +aioshelly==5.1.1 # homeassistant.components.skybell aioskybell==22.7.0 From 3a2fb402a4cdb5e4e2f83c4be3d5d58d60d70d19 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 9 Dec 2022 15:55:06 -1000 Subject: [PATCH 122/174] Fix delay setting up new Yale Access Bluetooth entries (#83683) Entries took a while to setup because of the async_wait_init_flow_finish call in _async_setup_component The delay was so long that users thought the integration was broken We had a wait in place for advertisements to arrive during discovery in case the lock was not yet seen. Since integration discovery is deferred until after startup this wait it no longer needed --- .../components/yalexs_ble/config_flow.py | 12 +++---- homeassistant/components/yalexs_ble/util.py | 19 ---------- .../components/yalexs_ble/test_config_flow.py | 36 +++++++++---------- 3 files changed, 23 insertions(+), 44 deletions(-) diff --git a/homeassistant/components/yalexs_ble/config_flow.py b/homeassistant/components/yalexs_ble/config_flow.py index 5fee6f62848..7845e8aa5e8 100644 --- a/homeassistant/components/yalexs_ble/config_flow.py +++ b/homeassistant/components/yalexs_ble/config_flow.py @@ -1,7 +1,6 @@ """Config flow for Yale Access Bluetooth integration.""" from __future__ import annotations -import asyncio import logging from typing import Any @@ -27,7 +26,7 @@ from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.typing import DiscoveryInfoType from .const import CONF_KEY, CONF_LOCAL_NAME, CONF_SLOT, DOMAIN -from .util import async_get_service_info, human_readable_name +from .util import async_find_existing_service_info, human_readable_name _LOGGER = logging.getLogger(__name__) @@ -110,11 +109,10 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): ) raise AbortFlow(reason="already_configured") - try: - self._discovery_info = await async_get_service_info( - hass, local_name, address - ) - except asyncio.TimeoutError: + self._discovery_info = async_find_existing_service_info( + hass, local_name, address + ) + if not self._discovery_info: return self.async_abort(reason="no_devices_found") # Integration discovery should abort other flows unless they diff --git a/homeassistant/components/yalexs_ble/util.py b/homeassistant/components/yalexs_ble/util.py index 465f4487c0b..e361e141a42 100644 --- a/homeassistant/components/yalexs_ble/util.py +++ b/homeassistant/components/yalexs_ble/util.py @@ -6,10 +6,8 @@ import platform from yalexs_ble import local_name_is_unique from homeassistant.components.bluetooth import ( - BluetoothScanningMode, BluetoothServiceInfoBleak, async_discovered_service_info, - async_process_advertisements, ) from homeassistant.components.bluetooth.match import ( ADDRESS, @@ -18,8 +16,6 @@ from homeassistant.components.bluetooth.match import ( ) from homeassistant.core import HomeAssistant, callback -from .const import DEVICE_TIMEOUT - def bluetooth_callback_matcher( local_name: str, address: str @@ -51,21 +47,6 @@ def async_find_existing_service_info( return None -async def async_get_service_info( - hass: HomeAssistant, local_name: str, address: str -) -> BluetoothServiceInfoBleak: - """Wait for the service info for the given local_name and address.""" - if service_info := async_find_existing_service_info(hass, local_name, address): - return service_info - return await async_process_advertisements( - hass, - lambda service_info: True, - bluetooth_callback_matcher(local_name, address), - BluetoothScanningMode.ACTIVE, - DEVICE_TIMEOUT, - ) - - def short_address(address: str) -> str: """Convert a Bluetooth address to a short address.""" split_address = address.replace("-", ":").split(":") diff --git a/tests/components/yalexs_ble/test_config_flow.py b/tests/components/yalexs_ble/test_config_flow.py index 64a4e93eae2..7f9c1fdf948 100644 --- a/tests/components/yalexs_ble/test_config_flow.py +++ b/tests/components/yalexs_ble/test_config_flow.py @@ -400,8 +400,8 @@ async def test_bluetooth_step_success(hass: HomeAssistant) -> None: async def test_integration_discovery_success(hass: HomeAssistant) -> None: """Test integration discovery step success path.""" with patch( - "homeassistant.components.yalexs_ble.util.async_process_advertisements", - return_value=YALE_ACCESS_LOCK_DISCOVERY_INFO, + "homeassistant.components.yalexs_ble.util.async_discovered_service_info", + return_value=[YALE_ACCESS_LOCK_DISCOVERY_INFO], ): result = await hass.config_entries.flow.async_init( DOMAIN, @@ -443,8 +443,8 @@ async def test_integration_discovery_success(hass: HomeAssistant) -> None: async def test_integration_discovery_device_not_found(hass: HomeAssistant) -> None: """Test integration discovery when the device is not found.""" with patch( - "homeassistant.components.yalexs_ble.util.async_process_advertisements", - side_effect=asyncio.TimeoutError, + "homeassistant.components.yalexs_ble.util.async_discovered_service_info", + return_value=[], ): result = await hass.config_entries.flow.async_init( DOMAIN, @@ -483,8 +483,8 @@ async def test_integration_discovery_takes_precedence_over_bluetooth( assert flows[0]["context"]["local_name"] == YALE_ACCESS_LOCK_DISCOVERY_INFO.name with patch( - "homeassistant.components.yalexs_ble.util.async_process_advertisements", - return_value=YALE_ACCESS_LOCK_DISCOVERY_INFO, + "homeassistant.components.yalexs_ble.util.async_discovered_service_info", + return_value=[YALE_ACCESS_LOCK_DISCOVERY_INFO], ): result = await hass.config_entries.flow.async_init( DOMAIN, @@ -558,8 +558,8 @@ async def test_integration_discovery_updates_key_unique_local_name( entry.add_to_hass(hass) with patch( - "homeassistant.components.yalexs_ble.util.async_process_advertisements", - return_value=LOCK_DISCOVERY_INFO_UUID_ADDRESS, + "homeassistant.components.yalexs_ble.util.async_discovered_service_info", + return_value=[LOCK_DISCOVERY_INFO_UUID_ADDRESS], ), patch( "homeassistant.components.yalexs_ble.async_setup_entry", return_value=True, @@ -600,8 +600,8 @@ async def test_integration_discovery_updates_key_without_unique_local_name( entry.add_to_hass(hass) with patch( - "homeassistant.components.yalexs_ble.util.async_process_advertisements", - return_value=LOCK_DISCOVERY_INFO_UUID_ADDRESS, + "homeassistant.components.yalexs_ble.util.async_discovered_service_info", + return_value=[LOCK_DISCOVERY_INFO_UUID_ADDRESS], ): result = await hass.config_entries.flow.async_init( DOMAIN, @@ -649,8 +649,8 @@ async def test_integration_discovery_updates_key_duplicate_local_name( entry2.add_to_hass(hass) with patch( - "homeassistant.components.yalexs_ble.util.async_process_advertisements", - return_value=LOCK_DISCOVERY_INFO_UUID_ADDRESS, + "homeassistant.components.yalexs_ble.util.async_discovered_service_info", + return_value=[LOCK_DISCOVERY_INFO_UUID_ADDRESS], ): result = await hass.config_entries.flow.async_init( DOMAIN, @@ -695,8 +695,8 @@ async def test_integration_discovery_takes_precedence_over_bluetooth_uuid_addres assert flows[0]["context"]["local_name"] == LOCK_DISCOVERY_INFO_UUID_ADDRESS.name with patch( - "homeassistant.components.yalexs_ble.util.async_process_advertisements", - return_value=LOCK_DISCOVERY_INFO_UUID_ADDRESS, + "homeassistant.components.yalexs_ble.util.async_discovered_service_info", + return_value=[LOCK_DISCOVERY_INFO_UUID_ADDRESS], ): result = await hass.config_entries.flow.async_init( DOMAIN, @@ -775,8 +775,8 @@ async def test_integration_discovery_takes_precedence_over_bluetooth_non_unique_ assert flows[0]["context"]["local_name"] == OLD_FIRMWARE_LOCK_DISCOVERY_INFO.name with patch( - "homeassistant.components.yalexs_ble.util.async_process_advertisements", - return_value=OLD_FIRMWARE_LOCK_DISCOVERY_INFO, + "homeassistant.components.yalexs_ble.util.async_discovered_service_info", + return_value=[OLD_FIRMWARE_LOCK_DISCOVERY_INFO], ): result = await hass.config_entries.flow.async_init( DOMAIN, @@ -851,8 +851,8 @@ async def test_user_is_setting_up_lock_and_discovery_happens_in_the_middle( await valdidate_started.wait() with patch( - "homeassistant.components.yalexs_ble.util.async_process_advertisements", - return_value=LOCK_DISCOVERY_INFO_UUID_ADDRESS, + "homeassistant.components.yalexs_ble.util.async_discovered_service_info", + return_value=[LOCK_DISCOVERY_INFO_UUID_ADDRESS], ): discovery_result = await hass.config_entries.flow.async_init( DOMAIN, From e1505339a51de8aa23274dbe2116c38b0082763f Mon Sep 17 00:00:00 2001 From: Michael <35783820+mib1185@users.noreply.github.com> Date: Sat, 10 Dec 2022 02:54:22 +0100 Subject: [PATCH 123/174] Fix loading of Fritz!Smarthome issues on older Fritz!Box (#83688) * check if templates are available * add test --- .../components/fritzbox/coordinator.py | 21 ++++++++++++++----- tests/components/fritzbox/test_init.py | 17 ++++++++++++++- 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/fritzbox/coordinator.py b/homeassistant/components/fritzbox/coordinator.py index c16751c8c9f..6bc3bac623f 100644 --- a/homeassistant/components/fritzbox/coordinator.py +++ b/homeassistant/components/fritzbox/coordinator.py @@ -3,6 +3,7 @@ from __future__ import annotations from dataclasses import dataclass from datetime import timedelta +from xml.etree.ElementTree import ParseError from pyfritzhome import Fritzhome, FritzhomeDevice, LoginError from pyfritzhome.devicetypes import FritzhomeTemplate @@ -34,6 +35,13 @@ class FritzboxDataUpdateCoordinator(DataUpdateCoordinator[FritzboxCoordinatorDat self.entry = entry self.fritz: Fritzhome = hass.data[DOMAIN][self.entry.entry_id][CONF_CONNECTIONS] self.configuration_url = self.fritz.get_prefixed_host() + self.has_templates = True + try: + hass.async_add_executor_job(self.fritz.update_templates) + except ParseError: + LOGGER.info("Disable smarthome templates") + self.has_templates = False + super().__init__( hass, LOGGER, @@ -45,7 +53,8 @@ class FritzboxDataUpdateCoordinator(DataUpdateCoordinator[FritzboxCoordinatorDat """Update all fritzbox device data.""" try: self.fritz.update_devices() - self.fritz.update_templates() + if self.has_templates: + self.fritz.update_templates() except requests.exceptions.ConnectionError as ex: raise UpdateFailed from ex except requests.exceptions.HTTPError: @@ -55,7 +64,8 @@ class FritzboxDataUpdateCoordinator(DataUpdateCoordinator[FritzboxCoordinatorDat except LoginError as ex: raise ConfigEntryAuthFailed from ex self.fritz.update_devices() - self.fritz.update_templates() + if self.has_templates: + self.fritz.update_templates() devices = self.fritz.get_devices() device_data = {} @@ -75,10 +85,11 @@ class FritzboxDataUpdateCoordinator(DataUpdateCoordinator[FritzboxCoordinatorDat device_data[device.ain] = device - templates = self.fritz.get_templates() template_data = {} - for template in templates: - template_data[template.ain] = template + if self.has_templates: + templates = self.fritz.get_templates() + for template in templates: + template_data[template.ain] = template return FritzboxCoordinatorData(devices=device_data, templates=template_data) diff --git a/tests/components/fritzbox/test_init.py b/tests/components/fritzbox/test_init.py index d68d9e1679c..dbec8b4ec14 100644 --- a/tests/components/fritzbox/test_init.py +++ b/tests/components/fritzbox/test_init.py @@ -2,6 +2,7 @@ from __future__ import annotations from unittest.mock import Mock, call, patch +from xml.etree.ElementTree import ParseError from pyfritzhome import LoginError import pytest @@ -167,7 +168,7 @@ async def test_coordinator_update_after_reboot(hass: HomeAssistant, fritz: Mock) assert await hass.config_entries.async_setup(entry.entry_id) assert fritz().update_devices.call_count == 2 - assert fritz().update_templates.call_count == 1 + assert fritz().update_templates.call_count == 2 assert fritz().get_devices.call_count == 1 assert fritz().get_templates.call_count == 1 assert fritz().login.call_count == 2 @@ -265,3 +266,17 @@ async def test_raise_config_entry_not_ready_when_offline(hass: HomeAssistant): entries = hass.config_entries.async_entries() config_entry = entries[0] assert config_entry.state is ConfigEntryState.SETUP_ERROR + + +async def test_disable_smarthome_templates(hass: HomeAssistant, fritz: Mock): + """Test smarthome templates are disabled.""" + entry = MockConfigEntry( + domain=FB_DOMAIN, + data=MOCK_CONFIG[FB_DOMAIN][CONF_DEVICES][0], + unique_id="any", + ) + entry.add_to_hass(hass) + fritz().update_templates.side_effect = [ParseError(), ""] + + assert await hass.config_entries.async_setup(entry.entry_id) + assert fritz().update_templates.call_count == 1 From c169654bdf617b325544d5d7d777c7dd8cc49868 Mon Sep 17 00:00:00 2001 From: Kostas Chatzikokolakis Date: Sat, 10 Dec 2022 20:14:15 +0200 Subject: [PATCH 124/174] Bump androidtv dependency of androitv component (#83707) Fixes #80555 --- homeassistant/components/androidtv/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/androidtv/manifest.json b/homeassistant/components/androidtv/manifest.json index 6b1f5669345..a2883653869 100644 --- a/homeassistant/components/androidtv/manifest.json +++ b/homeassistant/components/androidtv/manifest.json @@ -4,7 +4,7 @@ "documentation": "https://www.home-assistant.io/integrations/androidtv", "requirements": [ "adb-shell[async]==0.4.3", - "androidtv[async]==0.0.69", + "androidtv[async]==0.0.70", "pure-python-adb[async]==0.3.0.dev0" ], "codeowners": ["@JeffLIrion", "@ollo69"], diff --git a/requirements_all.txt b/requirements_all.txt index 5c565e5b4de..b461b644224 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -321,7 +321,7 @@ ambiclimate==0.2.1 amcrest==1.9.7 # homeassistant.components.androidtv -androidtv[async]==0.0.69 +androidtv[async]==0.0.70 # homeassistant.components.anel_pwrctrl anel_pwrctrl-homeassistant==0.0.1.dev2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index d25b85a4a56..17073df8245 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -290,7 +290,7 @@ amberelectric==1.0.4 ambiclimate==0.2.1 # homeassistant.components.androidtv -androidtv[async]==0.0.69 +androidtv[async]==0.0.70 # homeassistant.components.anthemav anthemav==1.4.1 From 00655ef21c7cb4c3c7cfa44eb4f347b4a09f0a52 Mon Sep 17 00:00:00 2001 From: Avi Miller Date: Sun, 11 Dec 2022 12:03:13 +1100 Subject: [PATCH 125/174] lifx: bump aiolifx_effects dependency to v0.3.1 (#83730) Fixes #83719 Signed-off-by: Avi Miller Signed-off-by: Avi Miller --- homeassistant/components/lifx/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/lifx/manifest.json b/homeassistant/components/lifx/manifest.json index c1e7cc56915..8adbd18f264 100644 --- a/homeassistant/components/lifx/manifest.json +++ b/homeassistant/components/lifx/manifest.json @@ -5,7 +5,7 @@ "documentation": "https://www.home-assistant.io/integrations/lifx", "requirements": [ "aiolifx==0.8.7", - "aiolifx_effects==0.3.0", + "aiolifx_effects==0.3.1", "aiolifx_themes==0.2.0" ], "quality_scale": "platinum", diff --git a/requirements_all.txt b/requirements_all.txt index b461b644224..802db40dfd5 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -196,7 +196,7 @@ aiokef==0.2.16 aiolifx==0.8.7 # homeassistant.components.lifx -aiolifx_effects==0.3.0 +aiolifx_effects==0.3.1 # homeassistant.components.lifx aiolifx_themes==0.2.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 17073df8245..0fd60298193 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -174,7 +174,7 @@ aiokafka==0.7.2 aiolifx==0.8.7 # homeassistant.components.lifx -aiolifx_effects==0.3.0 +aiolifx_effects==0.3.1 # homeassistant.components.lifx aiolifx_themes==0.2.0 From c9611303d54f378eea74717293bda56dfcc45953 Mon Sep 17 00:00:00 2001 From: Michael <35783820+mib1185@users.noreply.github.com> Date: Sun, 11 Dec 2022 19:22:54 +0100 Subject: [PATCH 126/174] Fix IPv6 sensor is only loaded when Fritz!Box is in router mode (#83757) do not check ipv6 when device is not a router --- homeassistant/components/fritz/common.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/homeassistant/components/fritz/common.py b/homeassistant/components/fritz/common.py index 5af4f0c2239..14e0f154374 100644 --- a/homeassistant/components/fritz/common.py +++ b/homeassistant/components/fritz/common.py @@ -669,6 +669,9 @@ class AvmWrapper(FritzBoxTools): def wrap_external_ipv6() -> str: return str(self.fritz_status.external_ipv6) + if not self.device_is_router: + return False + return bool(await self.hass.async_add_executor_job(wrap_external_ipv6)) async def async_get_connection_info(self) -> ConnectionInfo: From 3838c2ab6224d997c7dba2a74d26bcb1276de7af Mon Sep 17 00:00:00 2001 From: G Johansson Date: Sun, 11 Dec 2022 19:19:04 +0100 Subject: [PATCH 127/174] Fix scan_interval in Scrape (#83758) Scrape fix scan_interval --- homeassistant/components/scrape/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/scrape/__init__.py b/homeassistant/components/scrape/__init__.py index cb5657a9649..8953d9facd0 100644 --- a/homeassistant/components/scrape/__init__.py +++ b/homeassistant/components/scrape/__init__.py @@ -61,7 +61,9 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: load_coroutines: list[Coroutine[Any, Any, None]] = [] for resource_config in scrape_config: rest = create_rest_data_from_config(hass, resource_config) - scan_interval: timedelta = config.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL) + scan_interval: timedelta = resource_config.get( + CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL + ) coordinator = ScrapeCoordinator(hass, rest, scan_interval) sensors: list[ConfigType] = resource_config.get(SENSOR_DOMAIN, []) From d61be9a0eb0ca94e4f93541578c859c4132dcc37 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 11 Dec 2022 13:55:31 -0500 Subject: [PATCH 128/174] Bumped version to 2022.12.2 --- homeassistant/const.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 3c96f48f6be..a073c1d1b10 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -8,7 +8,7 @@ from .backports.enum import StrEnum APPLICATION_NAME: Final = "HomeAssistant" MAJOR_VERSION: Final = 2022 MINOR_VERSION: Final = 12 -PATCH_VERSION: Final = "1" +PATCH_VERSION: Final = "2" __short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__: Final = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 9, 0) diff --git a/pyproject.toml b/pyproject.toml index a1ae9d9c89a..b4de72935a0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "homeassistant" -version = "2022.12.1" +version = "2022.12.2" license = {text = "Apache-2.0"} description = "Open-source home automation platform running on Python 3." readme = "README.rst" From 858cf8ca335a1ecc34b8a6e95fba9a79bfda5b3c Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 11 Dec 2022 21:19:02 -0500 Subject: [PATCH 129/174] Drop aiohttp to 3.8.1 (#83795) --- homeassistant/package_constraints.txt | 2 +- pyproject.toml | 2 +- requirements.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index f0f3db19ca7..6e8f3a511dd 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -1,7 +1,7 @@ PyJWT==2.5.0 PyNaCl==1.5.0 aiodiscover==1.4.13 -aiohttp==3.8.3 +aiohttp==3.8.1 aiohttp_cors==0.7.0 astral==2.2 async-upnp-client==0.32.3 diff --git a/pyproject.toml b/pyproject.toml index b4de72935a0..7e6e267d913 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,7 +24,7 @@ classifiers = [ ] requires-python = ">=3.9.0" dependencies = [ - "aiohttp==3.8.3", + "aiohttp==3.8.1", "astral==2.2", "async_timeout==4.0.2", "attrs==21.2.0", diff --git a/requirements.txt b/requirements.txt index 6c878206b07..eca20f3047b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ -c homeassistant/package_constraints.txt # Home Assistant Core -aiohttp==3.8.3 +aiohttp==3.8.1 astral==2.2 async_timeout==4.0.2 attrs==21.2.0 From 87432be10239e9f92cad6412bd35b561a46165af Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 11 Dec 2022 21:19:18 -0500 Subject: [PATCH 130/174] Bumped version to 2022.12.3 --- homeassistant/const.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index a073c1d1b10..c99e823ca59 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -8,7 +8,7 @@ from .backports.enum import StrEnum APPLICATION_NAME: Final = "HomeAssistant" MAJOR_VERSION: Final = 2022 MINOR_VERSION: Final = 12 -PATCH_VERSION: Final = "2" +PATCH_VERSION: Final = "3" __short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__: Final = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 9, 0) diff --git a/pyproject.toml b/pyproject.toml index 7e6e267d913..125c7b77e9e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "homeassistant" -version = "2022.12.2" +version = "2022.12.3" license = {text = "Apache-2.0"} description = "Open-source home automation platform running on Python 3." readme = "README.rst" From 753a4e9e5e677a307feac905102297f71825717a Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 11 Dec 2022 21:43:25 -0500 Subject: [PATCH 131/174] SleepIQ to not retry a re-auth right away --- homeassistant/components/sleepiq/config_flow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/sleepiq/config_flow.py b/homeassistant/components/sleepiq/config_flow.py index 16034b64e8b..77806a1f977 100644 --- a/homeassistant/components/sleepiq/config_flow.py +++ b/homeassistant/components/sleepiq/config_flow.py @@ -85,7 +85,7 @@ class SleepIQFlowHandler(ConfigFlow, domain=DOMAIN): self._reauth_entry = self.hass.config_entries.async_get_entry( self.context["entry_id"] ) - return await self.async_step_reauth_confirm(dict(entry_data)) + return await self.async_step_reauth_confirm() async def async_step_reauth_confirm( self, user_input: dict[str, Any] | None = None From 4452a84ee0b9eaa8c5c9b4b162d6c22a88c8602e Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 12 Dec 2022 00:49:57 -1000 Subject: [PATCH 132/174] Bump bluetooth-auto-recovery to 1.0.0 (#83800) fixes https://github.com/home-assistant/core/issues/83673 --- homeassistant/components/bluetooth/manifest.json | 2 +- homeassistant/components/bluetooth/scanner.py | 3 ++- homeassistant/components/bluetooth/util.py | 4 ++-- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 6 files changed, 8 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/bluetooth/manifest.json b/homeassistant/components/bluetooth/manifest.json index 3c926db79d4..4ca4add7fc3 100644 --- a/homeassistant/components/bluetooth/manifest.json +++ b/homeassistant/components/bluetooth/manifest.json @@ -9,7 +9,7 @@ "bleak==0.19.2", "bleak-retry-connector==2.10.1", "bluetooth-adapters==0.12.0", - "bluetooth-auto-recovery==0.5.5", + "bluetooth-auto-recovery==1.0.0", "bluetooth-data-tools==0.3.0", "dbus-fast==1.75.0" ], diff --git a/homeassistant/components/bluetooth/scanner.py b/homeassistant/components/bluetooth/scanner.py index 09032715c74..d56c3f76c4c 100644 --- a/homeassistant/components/bluetooth/scanner.py +++ b/homeassistant/components/bluetooth/scanner.py @@ -130,6 +130,7 @@ class HaScanner(BaseHaScanner): new_info_callback: Callable[[BluetoothServiceInfoBleak], None], ) -> None: """Init bluetooth discovery.""" + self.mac_address = address source = address if address != DEFAULT_ADDRESS else adapter or SOURCE_LOCAL super().__init__(hass, source, adapter) self.mode = mode @@ -375,7 +376,7 @@ class HaScanner(BaseHaScanner): # so we log at debug level. If we later come up with a repair # strategy, we will change this to raise a repair issue as well. _LOGGER.debug("%s: adapter stopped responding; executing reset", self.name) - result = await async_reset_adapter(self.adapter) + result = await async_reset_adapter(self.adapter, self.mac_address) _LOGGER.debug("%s: adapter reset result: %s", self.name, result) async def async_stop(self) -> None: diff --git a/homeassistant/components/bluetooth/util.py b/homeassistant/components/bluetooth/util.py index c2336dd7af0..e3f44daff2b 100644 --- a/homeassistant/components/bluetooth/util.py +++ b/homeassistant/components/bluetooth/util.py @@ -36,9 +36,9 @@ def async_load_history_from_system( } -async def async_reset_adapter(adapter: str | None) -> bool | None: +async def async_reset_adapter(adapter: str | None, mac_address: str) -> bool | None: """Reset the adapter.""" if adapter and adapter.startswith("hci"): adapter_id = int(adapter[3:]) - return await recover_adapter(adapter_id) + return await recover_adapter(adapter_id, mac_address) return False diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 6e8f3a511dd..05767d91dc0 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -13,7 +13,7 @@ bcrypt==3.1.7 bleak-retry-connector==2.10.1 bleak==0.19.2 bluetooth-adapters==0.12.0 -bluetooth-auto-recovery==0.5.5 +bluetooth-auto-recovery==1.0.0 bluetooth-data-tools==0.3.0 certifi>=2021.5.30 ciso8601==2.2.0 diff --git a/requirements_all.txt b/requirements_all.txt index 802db40dfd5..bf5b8630216 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -450,7 +450,7 @@ bluemaestro-ble==0.2.0 bluetooth-adapters==0.12.0 # homeassistant.components.bluetooth -bluetooth-auto-recovery==0.5.5 +bluetooth-auto-recovery==1.0.0 # homeassistant.components.bluetooth # homeassistant.components.led_ble diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 0fd60298193..6699e7b536d 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -364,7 +364,7 @@ bluemaestro-ble==0.2.0 bluetooth-adapters==0.12.0 # homeassistant.components.bluetooth -bluetooth-auto-recovery==0.5.5 +bluetooth-auto-recovery==1.0.0 # homeassistant.components.bluetooth # homeassistant.components.led_ble From 712cb1157f3b2ffbe7210982f996919149434975 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Mon, 12 Dec 2022 18:52:47 +0100 Subject: [PATCH 133/174] Update frontend to 20221212.0 (#83842) --- homeassistant/components/frontend/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/frontend/manifest.json b/homeassistant/components/frontend/manifest.json index 57a31456c3c..e1c12272db4 100644 --- a/homeassistant/components/frontend/manifest.json +++ b/homeassistant/components/frontend/manifest.json @@ -2,7 +2,7 @@ "domain": "frontend", "name": "Home Assistant Frontend", "documentation": "https://www.home-assistant.io/integrations/frontend", - "requirements": ["home-assistant-frontend==20221208.0"], + "requirements": ["home-assistant-frontend==20221212.0"], "dependencies": [ "api", "auth", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 05767d91dc0..8ea2da044d1 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -22,7 +22,7 @@ dbus-fast==1.75.0 fnvhash==0.1.0 hass-nabucasa==0.61.0 home-assistant-bluetooth==1.8.1 -home-assistant-frontend==20221208.0 +home-assistant-frontend==20221212.0 httpx==0.23.1 ifaddr==0.1.7 janus==1.0.0 diff --git a/requirements_all.txt b/requirements_all.txt index bf5b8630216..3e81c5d794e 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -884,7 +884,7 @@ hole==0.7.0 holidays==0.17.2 # homeassistant.components.frontend -home-assistant-frontend==20221208.0 +home-assistant-frontend==20221212.0 # homeassistant.components.home_connect homeconnect==0.7.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 6699e7b536d..94f662496bb 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -664,7 +664,7 @@ hole==0.7.0 holidays==0.17.2 # homeassistant.components.frontend -home-assistant-frontend==20221208.0 +home-assistant-frontend==20221212.0 # homeassistant.components.home_connect homeconnect==0.7.2 From 3fe2f4f57203dbaf90e4a393bb2181ac22c8a0a9 Mon Sep 17 00:00:00 2001 From: "David F. Mulcahey" Date: Mon, 12 Dec 2022 12:33:50 -0500 Subject: [PATCH 134/174] Bump version of ZHA quirks to 0.0.89 (#83848) --- homeassistant/components/zha/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/zha/manifest.json b/homeassistant/components/zha/manifest.json index 50a30142bc5..ef5dbb91346 100644 --- a/homeassistant/components/zha/manifest.json +++ b/homeassistant/components/zha/manifest.json @@ -7,7 +7,7 @@ "bellows==0.34.5", "pyserial==3.5", "pyserial-asyncio==0.6", - "zha-quirks==0.0.88", + "zha-quirks==0.0.89", "zigpy-deconz==0.19.2", "zigpy==0.52.3", "zigpy-xbee==0.16.2", diff --git a/requirements_all.txt b/requirements_all.txt index 3e81c5d794e..80256ece9e2 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2639,7 +2639,7 @@ zengge==0.2 zeroconf==0.39.4 # homeassistant.components.zha -zha-quirks==0.0.88 +zha-quirks==0.0.89 # homeassistant.components.zhong_hong zhong_hong_hvac==1.0.9 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 94f662496bb..497af3994e3 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1840,7 +1840,7 @@ zamg==0.1.1 zeroconf==0.39.4 # homeassistant.components.zha -zha-quirks==0.0.88 +zha-quirks==0.0.89 # homeassistant.components.zha zigpy-deconz==0.19.2 From 89efc455e31ddb45e08a1be210354227a2d019cc Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Mon, 12 Dec 2022 21:57:12 +0100 Subject: [PATCH 135/174] Bump pychromecast to 13.0.3 (#83861) --- homeassistant/components/cast/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/cast/manifest.json b/homeassistant/components/cast/manifest.json index 00688f642ad..6661614b9cb 100644 --- a/homeassistant/components/cast/manifest.json +++ b/homeassistant/components/cast/manifest.json @@ -3,7 +3,7 @@ "name": "Google Cast", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/cast", - "requirements": ["pychromecast==13.0.2"], + "requirements": ["pychromecast==13.0.3"], "after_dependencies": [ "cloud", "http", diff --git a/requirements_all.txt b/requirements_all.txt index 80256ece9e2..9653e4af335 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1504,7 +1504,7 @@ pycfdns==2.0.1 pychannels==1.2.3 # homeassistant.components.cast -pychromecast==13.0.2 +pychromecast==13.0.3 # homeassistant.components.pocketcasts pycketcasts==1.0.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 497af3994e3..fce89b7bbad 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1077,7 +1077,7 @@ pybravia==0.2.3 pycfdns==2.0.1 # homeassistant.components.cast -pychromecast==13.0.2 +pychromecast==13.0.3 # homeassistant.components.comfoconnect pycomfoconnect==0.4 From 0a2fa9984d9be7606bdf4c19fea203119c86e705 Mon Sep 17 00:00:00 2001 From: Michael <35783820+mib1185@users.noreply.github.com> Date: Mon, 12 Dec 2022 22:11:57 +0100 Subject: [PATCH 136/174] Move template check into fritzbox entry setup (#83863) * move template check into entry setup * use else in try-except block --- homeassistant/components/fritzbox/__init__.py | 12 +++++++++++- homeassistant/components/fritzbox/coordinator.py | 12 ++++-------- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/fritzbox/__init__.py b/homeassistant/components/fritzbox/__init__.py index 40d170db3b3..43bd0bfeeb0 100644 --- a/homeassistant/components/fritzbox/__init__.py +++ b/homeassistant/components/fritzbox/__init__.py @@ -2,6 +2,7 @@ from __future__ import annotations from abc import ABC, abstractmethod +from xml.etree.ElementTree import ParseError from pyfritzhome import Fritzhome, FritzhomeDevice, LoginError from pyfritzhome.devicetypes.fritzhomeentitybase import FritzhomeEntityBase @@ -43,7 +44,16 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: CONF_CONNECTIONS: fritz, } - coordinator = FritzboxDataUpdateCoordinator(hass, entry) + try: + await hass.async_add_executor_job(fritz.update_templates) + except ParseError: + LOGGER.debug("Disable smarthome templates") + has_templates = False + else: + LOGGER.debug("Enable smarthome templates") + has_templates = True + + coordinator = FritzboxDataUpdateCoordinator(hass, entry, has_templates) await coordinator.async_config_entry_first_refresh() diff --git a/homeassistant/components/fritzbox/coordinator.py b/homeassistant/components/fritzbox/coordinator.py index 6bc3bac623f..80087adf9ac 100644 --- a/homeassistant/components/fritzbox/coordinator.py +++ b/homeassistant/components/fritzbox/coordinator.py @@ -3,7 +3,6 @@ from __future__ import annotations from dataclasses import dataclass from datetime import timedelta -from xml.etree.ElementTree import ParseError from pyfritzhome import Fritzhome, FritzhomeDevice, LoginError from pyfritzhome.devicetypes import FritzhomeTemplate @@ -30,17 +29,14 @@ class FritzboxDataUpdateCoordinator(DataUpdateCoordinator[FritzboxCoordinatorDat configuration_url: str - def __init__(self, hass: HomeAssistant, entry: ConfigEntry) -> None: + def __init__( + self, hass: HomeAssistant, entry: ConfigEntry, has_templates: bool + ) -> None: """Initialize the Fritzbox Smarthome device coordinator.""" self.entry = entry self.fritz: Fritzhome = hass.data[DOMAIN][self.entry.entry_id][CONF_CONNECTIONS] self.configuration_url = self.fritz.get_prefixed_host() - self.has_templates = True - try: - hass.async_add_executor_job(self.fritz.update_templates) - except ParseError: - LOGGER.info("Disable smarthome templates") - self.has_templates = False + self.has_templates = has_templates super().__init__( hass, From 463edd984beb1255ce9776759b7bf148df028cc0 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 12 Dec 2022 16:16:41 -0500 Subject: [PATCH 137/174] Bumped version to 2022.12.4 --- homeassistant/const.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index c99e823ca59..74e090d166e 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -8,7 +8,7 @@ from .backports.enum import StrEnum APPLICATION_NAME: Final = "HomeAssistant" MAJOR_VERSION: Final = 2022 MINOR_VERSION: Final = 12 -PATCH_VERSION: Final = "3" +PATCH_VERSION: Final = "4" __short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__: Final = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 9, 0) diff --git a/pyproject.toml b/pyproject.toml index 125c7b77e9e..8c8399bf425 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "homeassistant" -version = "2022.12.3" +version = "2022.12.4" license = {text = "Apache-2.0"} description = "Open-source home automation platform running on Python 3." readme = "README.rst" From 38ab48b4fd992cb175183d84551fa50fba779f30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Wed, 7 Dec 2022 20:02:22 +0100 Subject: [PATCH 138/174] Use the async_migrate_paypal_agreement function to get the migration URL (#83469) * Use the async_migrate_paypal_agreement function to get the migration URL * Update URL * Handle timeout error --- homeassistant/components/cloud/repairs.py | 9 +-- .../components/cloud/subscription.py | 22 +++++++ tests/components/cloud/test_repairs.py | 13 +++- tests/components/cloud/test_subscription.py | 61 +++++++++++++++++++ 4 files changed, 100 insertions(+), 5 deletions(-) create mode 100644 tests/components/cloud/test_subscription.py diff --git a/homeassistant/components/cloud/repairs.py b/homeassistant/components/cloud/repairs.py index 0d217521c21..bf2df23aca9 100644 --- a/homeassistant/components/cloud/repairs.py +++ b/homeassistant/components/cloud/repairs.py @@ -13,7 +13,7 @@ from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import issue_registry as ir from .const import DOMAIN -from .subscription import async_subscription_info +from .subscription import async_migrate_paypal_agreement, async_subscription_info BACKOFF_TIME = 5 MAX_RETRIES = 60 # This allows for 10 minutes of retries @@ -68,13 +68,13 @@ class LegacySubscriptionRepairFlow(RepairsFlow): async def async_step_change_plan(self, _: None = None) -> FlowResult: """Wait for the user to authorize the app installation.""" + cloud: Cloud = self.hass.data[DOMAIN] + async def _async_wait_for_plan_change() -> None: flow_manager = repairs_flow_manager(self.hass) # We can not get here without a flow manager assert flow_manager is not None - cloud: Cloud = self.hass.data[DOMAIN] - retries = 0 while retries < MAX_RETRIES: self._data = await async_subscription_info(cloud) @@ -90,9 +90,10 @@ class LegacySubscriptionRepairFlow(RepairsFlow): if not self.wait_task: self.wait_task = self.hass.async_create_task(_async_wait_for_plan_change()) + migration = await async_migrate_paypal_agreement(cloud) return self.async_external_step( step_id="change_plan", - url="https://account.nabucasa.com/", + url=migration["url"] if migration else "https://account.nabucasa.com/", ) await self.wait_task diff --git a/homeassistant/components/cloud/subscription.py b/homeassistant/components/cloud/subscription.py index 9a2e5bd87cf..4c18b4f0253 100644 --- a/homeassistant/components/cloud/subscription.py +++ b/homeassistant/components/cloud/subscription.py @@ -1,6 +1,7 @@ """Subscription information.""" from __future__ import annotations +import asyncio import logging from typing import Any @@ -18,7 +19,28 @@ async def async_subscription_info(cloud: Cloud) -> dict[str, Any] | None: try: async with async_timeout.timeout(REQUEST_TIMEOUT): return await cloud_api.async_subscription_info(cloud) + except asyncio.TimeoutError: + _LOGGER.error( + "A timeout of %s was reached while trying to fetch subscription information", + REQUEST_TIMEOUT, + ) except ClientError: _LOGGER.error("Failed to fetch subscription information") return None + + +async def async_migrate_paypal_agreement(cloud: Cloud) -> dict[str, Any] | None: + """Migrate a paypal agreement from legacy.""" + try: + async with async_timeout.timeout(REQUEST_TIMEOUT): + return await cloud_api.async_migrate_paypal_agreement(cloud) + except asyncio.TimeoutError: + _LOGGER.error( + "A timeout of %s was reached while trying to start agreement migration", + REQUEST_TIMEOUT, + ) + except ClientError as exception: + _LOGGER.error("Failed to start agreement migration - %s", exception) + + return None diff --git a/tests/components/cloud/test_repairs.py b/tests/components/cloud/test_repairs.py index 052cdde0d0d..a7f8b2332d7 100644 --- a/tests/components/cloud/test_repairs.py +++ b/tests/components/cloud/test_repairs.py @@ -88,6 +88,10 @@ async def test_legacy_subscription_repair_flow( "https://accounts.nabucasa.com/payments/subscription_info", json={"provider": None}, ) + aioclient_mock.post( + "https://accounts.nabucasa.com/payments/migrate_paypal_agreement", + json={"url": "https://paypal.com"}, + ) cloud_repairs.async_manage_legacy_subscription_issue(hass, {"provider": "legacy"}) repair_issue = issue_registry.async_get_issue( @@ -133,7 +137,7 @@ async def test_legacy_subscription_repair_flow( "flow_id": flow_id, "handler": DOMAIN, "step_id": "change_plan", - "url": "https://account.nabucasa.com/", + "url": "https://paypal.com", "description_placeholders": None, } @@ -161,8 +165,15 @@ async def test_legacy_subscription_repair_flow( async def test_legacy_subscription_repair_flow_timeout( hass: HomeAssistant, hass_client: Callable[..., Awaitable[ClientSession]], + mock_auth: Generator[None, AsyncMock, None], + aioclient_mock: AiohttpClientMocker, ): """Test timeout flow of the fix flow for legacy subscription.""" + aioclient_mock.post( + "https://accounts.nabucasa.com/payments/migrate_paypal_agreement", + status=403, + ) + issue_registry: ir.IssueRegistry = ir.async_get(hass) cloud_repairs.async_manage_legacy_subscription_issue(hass, {"provider": "legacy"}) diff --git a/tests/components/cloud/test_subscription.py b/tests/components/cloud/test_subscription.py new file mode 100644 index 00000000000..4dac93e92a3 --- /dev/null +++ b/tests/components/cloud/test_subscription.py @@ -0,0 +1,61 @@ +"""Test cloud subscription functions.""" +import asyncio +from unittest.mock import AsyncMock, Mock + +from hass_nabucasa import Cloud +import pytest + +from homeassistant.components.cloud.subscription import ( + async_migrate_paypal_agreement, + async_subscription_info, +) +from homeassistant.core import HomeAssistant +from homeassistant.helpers.aiohttp_client import async_get_clientsession + +from tests.test_util.aiohttp import AiohttpClientMocker + + +@pytest.fixture(name="mocked_cloud") +def mocked_cloud_object(hass: HomeAssistant) -> Cloud: + """Mock cloud object.""" + return Mock( + accounts_server="accounts.nabucasa.com", + auth=Mock(async_check_token=AsyncMock()), + websession=async_get_clientsession(hass), + ) + + +async def test_fetching_subscription_with_timeout_error( + aioclient_mock: AiohttpClientMocker, + caplog: pytest.LogCaptureFixture, + mocked_cloud: Cloud, +): + """Test that we handle timeout error.""" + aioclient_mock.get( + "https://accounts.nabucasa.com/payments/subscription_info", + exc=asyncio.TimeoutError(), + ) + + assert await async_subscription_info(mocked_cloud) is None + assert ( + "A timeout of 10 was reached while trying to fetch subscription information" + in caplog.text + ) + + +async def test_migrate_paypal_agreement_with_timeout_error( + aioclient_mock: AiohttpClientMocker, + caplog: pytest.LogCaptureFixture, + mocked_cloud: Cloud, +): + """Test that we handle timeout error.""" + aioclient_mock.post( + "https://accounts.nabucasa.com/payments/migrate_paypal_agreement", + exc=asyncio.TimeoutError(), + ) + + assert await async_migrate_paypal_agreement(mocked_cloud) is None + assert ( + "A timeout of 10 was reached while trying to start agreement migration" + in caplog.text + ) From 4c2975ad993973fcc9384479cfdf645f44d5671f Mon Sep 17 00:00:00 2001 From: Nyro Date: Tue, 13 Dec 2022 00:08:10 +0100 Subject: [PATCH 139/174] Fix issue on Overkiz Domestic Hot water heater entities with away mode (#83684) * Don't call set boostMode Duration with 0 to disable boost mode * use states.get() instead of states[] * Revert select states --- .../domestic_hot_water_production.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/overkiz/water_heater_entities/domestic_hot_water_production.py b/homeassistant/components/overkiz/water_heater_entities/domestic_hot_water_production.py index 49524e19373..2a8d93bf8eb 100644 --- a/homeassistant/components/overkiz/water_heater_entities/domestic_hot_water_production.py +++ b/homeassistant/components/overkiz/water_heater_entities/domestic_hot_water_production.py @@ -305,14 +305,6 @@ class DomesticHotWaterProduction(OverkizEntity, WaterHeaterEntity): OverkizCommand.SET_BOOST_MODE, OverkizCommand.OFF ) - if self.executor.has_command(OverkizCommand.SET_BOOST_MODE_DURATION): - await self.executor.async_execute_command( - OverkizCommand.SET_BOOST_MODE_DURATION, 0 - ) - await self.executor.async_execute_command( - OverkizCommand.REFRESH_BOOST_MODE_DURATION - ) - if self.executor.has_command(OverkizCommand.SET_CURRENT_OPERATING_MODE): current_operating_mode = self.executor.select_state( OverkizState.CORE_OPERATING_MODE @@ -331,5 +323,10 @@ class DomesticHotWaterProduction(OverkizEntity, WaterHeaterEntity): OverkizCommand.SET_DHW_MODE, self.overkiz_to_operation_mode[operation_mode] ) + if self.executor.has_command(OverkizCommand.REFRESH_BOOST_MODE_DURATION): + await self.executor.async_execute_command( + OverkizCommand.REFRESH_BOOST_MODE_DURATION + ) + if self.executor.has_command(OverkizCommand.REFRESH_DHW_MODE): await self.executor.async_execute_command(OverkizCommand.REFRESH_DHW_MODE) From a3fb8a65148d3b787370f3aa1e3944e2f38b66b4 Mon Sep 17 00:00:00 2001 From: Nyro Date: Tue, 13 Dec 2022 00:17:05 +0100 Subject: [PATCH 140/174] Bump pyoverkiz to 1.7.2 (#83866) Bump pyoverkiz --- homeassistant/components/overkiz/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/overkiz/manifest.json b/homeassistant/components/overkiz/manifest.json index 49a65b78c06..864c34714aa 100644 --- a/homeassistant/components/overkiz/manifest.json +++ b/homeassistant/components/overkiz/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "integration_type": "hub", "documentation": "https://www.home-assistant.io/integrations/overkiz", - "requirements": ["pyoverkiz==1.7.1"], + "requirements": ["pyoverkiz==1.7.2"], "zeroconf": [ { "type": "_kizbox._tcp.local.", diff --git a/requirements_all.txt b/requirements_all.txt index 9653e4af335..c0ccabfc900 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1812,7 +1812,7 @@ pyotgw==2.1.3 pyotp==2.7.0 # homeassistant.components.overkiz -pyoverkiz==1.7.1 +pyoverkiz==1.7.2 # homeassistant.components.openweathermap pyowm==3.2.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index fce89b7bbad..517ad04614a 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1289,7 +1289,7 @@ pyotgw==2.1.3 pyotp==2.7.0 # homeassistant.components.overkiz -pyoverkiz==1.7.1 +pyoverkiz==1.7.2 # homeassistant.components.openweathermap pyowm==3.2.0 From 8fb400e7ca8b71b328e83c7482822db666edd61b Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 12 Dec 2022 16:29:39 -1000 Subject: [PATCH 141/174] Fix bleak-retry-connector get_device hang with HAOS 9.4 and only proxies (#83879) The bleak connection setup to dbus hangs forever instead of errors if there are no local bluetooth adapters and we are using remote proxies. The library now times out when this happens instead of hanging forever. changelog: https://github.com/Bluetooth-Devices/bleak-retry-connector/compare/v2.10.1...v2.10.2 --- homeassistant/components/bluetooth/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/bluetooth/manifest.json b/homeassistant/components/bluetooth/manifest.json index 4ca4add7fc3..069bb64011d 100644 --- a/homeassistant/components/bluetooth/manifest.json +++ b/homeassistant/components/bluetooth/manifest.json @@ -7,7 +7,7 @@ "quality_scale": "internal", "requirements": [ "bleak==0.19.2", - "bleak-retry-connector==2.10.1", + "bleak-retry-connector==2.10.2", "bluetooth-adapters==0.12.0", "bluetooth-auto-recovery==1.0.0", "bluetooth-data-tools==0.3.0", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 8ea2da044d1..f9c4c4dd2f6 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -10,7 +10,7 @@ atomicwrites-homeassistant==1.4.1 attrs==21.2.0 awesomeversion==22.9.0 bcrypt==3.1.7 -bleak-retry-connector==2.10.1 +bleak-retry-connector==2.10.2 bleak==0.19.2 bluetooth-adapters==0.12.0 bluetooth-auto-recovery==1.0.0 diff --git a/requirements_all.txt b/requirements_all.txt index c0ccabfc900..b088ec3104a 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -422,7 +422,7 @@ bimmer_connected==0.10.4 bizkaibus==0.1.1 # homeassistant.components.bluetooth -bleak-retry-connector==2.10.1 +bleak-retry-connector==2.10.2 # homeassistant.components.bluetooth bleak==0.19.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 517ad04614a..3f857e1839b 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -346,7 +346,7 @@ bellows==0.34.5 bimmer_connected==0.10.4 # homeassistant.components.bluetooth -bleak-retry-connector==2.10.1 +bleak-retry-connector==2.10.2 # homeassistant.components.bluetooth bleak==0.19.2 From 31d0e5e316a52bfc4a7bac536eba950e493309a3 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 12 Dec 2022 16:29:06 -1000 Subject: [PATCH 142/174] Fix HomeKit media players when entity has duplicate sources (#83890) fixes #83852 fixes #83698 --- .../components/homekit/type_remotes.py | 21 +++++++-- .../homekit/test_type_media_players.py | 45 +++++++++++++++++++ 2 files changed, 63 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/homekit/type_remotes.py b/homeassistant/components/homekit/type_remotes.py index fb808eff8b0..9cc90a6cc28 100644 --- a/homeassistant/components/homekit/type_remotes.py +++ b/homeassistant/components/homekit/type_remotes.py @@ -18,7 +18,7 @@ from homeassistant.const import ( SERVICE_TURN_ON, STATE_ON, ) -from homeassistant.core import callback +from homeassistant.core import State, callback from .accessories import TYPES, HomeAccessory from .const import ( @@ -96,7 +96,7 @@ class RemoteInputSelectAccessory(HomeAccessory): self.sources = [] self.support_select_source = False if features & required_feature: - sources = state.attributes.get(source_list_key, []) + sources = self._get_ordered_source_list_from_state(state) if len(sources) > MAXIMUM_SOURCES: _LOGGER.warning( "%s: Reached maximum number of sources (%s)", @@ -143,6 +143,21 @@ class RemoteInputSelectAccessory(HomeAccessory): serv_input.configure_char(CHAR_CURRENT_VISIBILITY_STATE, value=False) _LOGGER.debug("%s: Added source %s", self.entity_id, source) + def _get_ordered_source_list_from_state(self, state: State) -> list[str]: + """Return ordered source list while preserving order with duplicates removed. + + Some integrations have duplicate sources in the source list + which will make the source list conflict as HomeKit requires + unique source names. + """ + seen = set() + sources: list[str] = [] + for source in state.attributes.get(self.source_list_key, []): + if source not in seen: + sources.append(source) + seen.add(source) + return sources + @abstractmethod def set_on_off(self, value): """Move switch state to value if call came from HomeKit.""" @@ -169,7 +184,7 @@ class RemoteInputSelectAccessory(HomeAccessory): self.char_input_source.set_value(index) return - possible_sources = new_state.attributes.get(self.source_list_key, []) + possible_sources = self._get_ordered_source_list_from_state(new_state) if source in possible_sources: index = possible_sources.index(source) if index >= MAXIMUM_SOURCES: diff --git a/tests/components/homekit/test_type_media_players.py b/tests/components/homekit/test_type_media_players.py index 30b9bc77f5d..e815a25ee7d 100644 --- a/tests/components/homekit/test_type_media_players.py +++ b/tests/components/homekit/test_type_media_players.py @@ -512,3 +512,48 @@ async def test_media_player_television_max_sources(hass, hk_driver, events, capl ) await hass.async_block_till_done() assert acc.char_input_source.value == 0 + + +async def test_media_player_television_duplicate_sources( + hass, hk_driver, events, caplog +): + """Test if television accessory with duplicate sources.""" + entity_id = "media_player.television" + sources = ["MUSIC", "HDMI", "SCREEN MIRRORING", "HDMI", "MUSIC"] + hass.states.async_set( + entity_id, + None, + { + ATTR_DEVICE_CLASS: MediaPlayerDeviceClass.TV, + ATTR_SUPPORTED_FEATURES: 3469, + ATTR_MEDIA_VOLUME_MUTED: False, + ATTR_INPUT_SOURCE: "HDMI", + ATTR_INPUT_SOURCE_LIST: sources, + }, + ) + await hass.async_block_till_done() + acc = TelevisionMediaPlayer(hass, hk_driver, "MediaPlayer", entity_id, 2, None) + await acc.run() + await hass.async_block_till_done() + + assert acc.aid == 2 + assert acc.category == 31 # Television + + assert acc.char_active.value == 0 + assert acc.char_remote_key.value == 0 + assert acc.char_input_source.value == 1 + assert acc.char_mute.value is False + + hass.states.async_set( + entity_id, + None, + { + ATTR_DEVICE_CLASS: MediaPlayerDeviceClass.TV, + ATTR_SUPPORTED_FEATURES: 3469, + ATTR_MEDIA_VOLUME_MUTED: False, + ATTR_INPUT_SOURCE: "MUSIC", + ATTR_INPUT_SOURCE_LIST: sources, + }, + ) + await hass.async_block_till_done() + assert acc.char_input_source.value == 0 From fcbb40092a83b6755bfeeca6f5ddad3e4971aa7f Mon Sep 17 00:00:00 2001 From: Koen van Zuijlen <8818390+kvanzuijlen@users.noreply.github.com> Date: Tue, 13 Dec 2022 10:59:13 +0100 Subject: [PATCH 143/174] Fix Just Nimbus error codes (#83856) --- homeassistant/components/justnimbus/entity.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/justnimbus/entity.py b/homeassistant/components/justnimbus/entity.py index cfd261f1bc0..67575809135 100644 --- a/homeassistant/components/justnimbus/entity.py +++ b/homeassistant/components/justnimbus/entity.py @@ -30,4 +30,4 @@ class JustNimbusEntity( @property def available(self) -> bool: """Return device availability.""" - return super().available and getattr(self.coordinator.data, "error_code") == 0 + return super().available and self.coordinator.data is not None From 700dbcf2a4ffa3816150e7f9ef9172117d30644c Mon Sep 17 00:00:00 2001 From: Artem Draft Date: Tue, 13 Dec 2022 10:45:04 +0300 Subject: [PATCH 144/174] Fix saving options with missing ignored sources in BraviaTV (#83891) fix https://github.com/home-assistant/core/issues/83217 fixes undefined --- homeassistant/components/braviatv/config_flow.py | 6 ++++++ tests/components/braviatv/test_config_flow.py | 13 +++++++++++++ 2 files changed, 19 insertions(+) diff --git a/homeassistant/components/braviatv/config_flow.py b/homeassistant/components/braviatv/config_flow.py index 183e13a19e8..369aae374cf 100644 --- a/homeassistant/components/braviatv/config_flow.py +++ b/homeassistant/components/braviatv/config_flow.py @@ -264,6 +264,12 @@ class BraviaTVOptionsFlowHandler(config_entries.OptionsFlowWithConfigEntry): sources = coordinator.source_map.values() source_list = [item["title"] for item in sources] + ignored_sources = self.options.get(CONF_IGNORED_SOURCES, []) + + for item in ignored_sources: + if item not in source_list: + source_list.append(item) + self.data_schema = vol.Schema( { vol.Optional(CONF_IGNORED_SOURCES): cv.multi_select(source_list), diff --git a/tests/components/braviatv/test_config_flow.py b/tests/components/braviatv/test_config_flow.py index ee835493e66..18576207a30 100644 --- a/tests/components/braviatv/test_config_flow.py +++ b/tests/components/braviatv/test_config_flow.py @@ -422,6 +422,19 @@ async def test_options_flow(hass: HomeAssistant) -> None: assert result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY assert config_entry.options == {CONF_IGNORED_SOURCES: ["HDMI 1", "HDMI 2"]} + # Test that saving with missing sources is ok + with patch( + "pybravia.BraviaTV.get_external_status", + return_value=BRAVIA_SOURCES[1:], + ): + result = await hass.config_entries.options.async_init(config_entry.entry_id) + result = await hass.config_entries.options.async_configure( + result["flow_id"], user_input={CONF_IGNORED_SOURCES: ["HDMI 1"]} + ) + await hass.async_block_till_done() + assert result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY + assert config_entry.options == {CONF_IGNORED_SOURCES: ["HDMI 1"]} + async def test_options_flow_error(hass: HomeAssistant) -> None: """Test config flow options.""" From 412e9d34d9b53af090cfcedc97c8492100c84f85 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Tue, 13 Dec 2022 11:55:23 +0100 Subject: [PATCH 145/174] Bump pychromecast to 13.0.4 (#83896) --- homeassistant/components/cast/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/cast/manifest.json b/homeassistant/components/cast/manifest.json index 6661614b9cb..326a25d9613 100644 --- a/homeassistant/components/cast/manifest.json +++ b/homeassistant/components/cast/manifest.json @@ -3,7 +3,7 @@ "name": "Google Cast", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/cast", - "requirements": ["pychromecast==13.0.3"], + "requirements": ["pychromecast==13.0.4"], "after_dependencies": [ "cloud", "http", diff --git a/requirements_all.txt b/requirements_all.txt index b088ec3104a..9d199abb1a8 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1504,7 +1504,7 @@ pycfdns==2.0.1 pychannels==1.2.3 # homeassistant.components.cast -pychromecast==13.0.3 +pychromecast==13.0.4 # homeassistant.components.pocketcasts pycketcasts==1.0.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 3f857e1839b..c152f41ee92 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1077,7 +1077,7 @@ pybravia==0.2.3 pycfdns==2.0.1 # homeassistant.components.cast -pychromecast==13.0.3 +pychromecast==13.0.4 # homeassistant.components.comfoconnect pycomfoconnect==0.4 From edffb794f7262410bba863655e98e8bbe4904b44 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Tue, 13 Dec 2022 19:24:24 +0100 Subject: [PATCH 146/174] Update frontend to 20221213.0 (#83940) --- homeassistant/components/frontend/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/frontend/manifest.json b/homeassistant/components/frontend/manifest.json index e1c12272db4..9ff4c13585d 100644 --- a/homeassistant/components/frontend/manifest.json +++ b/homeassistant/components/frontend/manifest.json @@ -2,7 +2,7 @@ "domain": "frontend", "name": "Home Assistant Frontend", "documentation": "https://www.home-assistant.io/integrations/frontend", - "requirements": ["home-assistant-frontend==20221212.0"], + "requirements": ["home-assistant-frontend==20221213.0"], "dependencies": [ "api", "auth", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index f9c4c4dd2f6..b378d03bbf2 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -22,7 +22,7 @@ dbus-fast==1.75.0 fnvhash==0.1.0 hass-nabucasa==0.61.0 home-assistant-bluetooth==1.8.1 -home-assistant-frontend==20221212.0 +home-assistant-frontend==20221213.0 httpx==0.23.1 ifaddr==0.1.7 janus==1.0.0 diff --git a/requirements_all.txt b/requirements_all.txt index 9d199abb1a8..01270a86382 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -884,7 +884,7 @@ hole==0.7.0 holidays==0.17.2 # homeassistant.components.frontend -home-assistant-frontend==20221212.0 +home-assistant-frontend==20221213.0 # homeassistant.components.home_connect homeconnect==0.7.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index c152f41ee92..f4148c585fb 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -664,7 +664,7 @@ hole==0.7.0 holidays==0.17.2 # homeassistant.components.frontend -home-assistant-frontend==20221212.0 +home-assistant-frontend==20221213.0 # homeassistant.components.home_connect homeconnect==0.7.2 From c75bf20a3cf887a8eff6409babeff2388d7839d6 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 13 Dec 2022 13:26:18 -0500 Subject: [PATCH 147/174] Bumped version to 2022.12.5 --- homeassistant/const.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 74e090d166e..ca64acdab46 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -8,7 +8,7 @@ from .backports.enum import StrEnum APPLICATION_NAME: Final = "HomeAssistant" MAJOR_VERSION: Final = 2022 MINOR_VERSION: Final = 12 -PATCH_VERSION: Final = "4" +PATCH_VERSION: Final = "5" __short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__: Final = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 9, 0) diff --git a/pyproject.toml b/pyproject.toml index 8c8399bf425..9874825ac27 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "homeassistant" -version = "2022.12.4" +version = "2022.12.5" license = {text = "Apache-2.0"} description = "Open-source home automation platform running on Python 3." readme = "README.rst" From a0518295d517467f650efd59e3912cdc7190c4d2 Mon Sep 17 00:00:00 2001 From: Sergio Oller Date: Tue, 13 Dec 2022 08:50:20 +0100 Subject: [PATCH 148/174] Do not add a Content-Type header to ingress (#83425) --- homeassistant/components/hassio/ingress.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/hassio/ingress.py b/homeassistant/components/hassio/ingress.py index 8aacbac99f6..dceff75bca8 100644 --- a/homeassistant/components/hassio/ingress.py +++ b/homeassistant/components/hassio/ingress.py @@ -130,6 +130,7 @@ class HassIOIngress(HomeAssistantView): allow_redirects=False, data=request.content, timeout=ClientTimeout(total=None), + skip_auto_headers={hdrs.CONTENT_TYPE}, ) as result: headers = _response_header(result) From 4ef2e91fd55feb94c0efd43de7a21fdc685c72f4 Mon Sep 17 00:00:00 2001 From: majuss Date: Wed, 14 Dec 2022 14:56:55 +0100 Subject: [PATCH 149/174] Bump lupupy dependency to v0.2.3 (#83765) fixes undefined --- homeassistant/components/lupusec/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/lupusec/manifest.json b/homeassistant/components/lupusec/manifest.json index 5792f186798..74593977eab 100644 --- a/homeassistant/components/lupusec/manifest.json +++ b/homeassistant/components/lupusec/manifest.json @@ -2,7 +2,7 @@ "domain": "lupusec", "name": "Lupus Electronics LUPUSEC", "documentation": "https://www.home-assistant.io/integrations/lupusec", - "requirements": ["lupupy==0.2.1"], + "requirements": ["lupupy==0.2.3"], "codeowners": ["@majuss"], "iot_class": "local_polling", "loggers": ["lupupy"] diff --git a/requirements_all.txt b/requirements_all.txt index 01270a86382..355a48e04da 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1052,7 +1052,7 @@ london-tube-status==0.5 luftdaten==0.7.4 # homeassistant.components.lupusec -lupupy==0.2.1 +lupupy==0.2.3 # homeassistant.components.lw12wifi lw12==0.9.2 From bedb587290aa49babb9bf61d1001d955a8b4fc9f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 13 Dec 2022 11:27:05 -1000 Subject: [PATCH 150/174] Bump aioshelly to 5.1.2 to fix state updates not firing after reconnect (#83950) Bump aioshelly to 5.1.2 changelog: https://github.com/home-assistant-libs/aioshelly/compare/5.1.1...5.1.2 --- homeassistant/components/shelly/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/shelly/manifest.json b/homeassistant/components/shelly/manifest.json index aae3e0107b5..39723bf4b0f 100644 --- a/homeassistant/components/shelly/manifest.json +++ b/homeassistant/components/shelly/manifest.json @@ -3,7 +3,7 @@ "name": "Shelly", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/shelly", - "requirements": ["aioshelly==5.1.1"], + "requirements": ["aioshelly==5.1.2"], "dependencies": ["bluetooth", "http"], "zeroconf": [ { diff --git a/requirements_all.txt b/requirements_all.txt index 355a48e04da..5b73f242bb5 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -261,7 +261,7 @@ aiosenseme==0.6.1 aiosenz==1.0.0 # homeassistant.components.shelly -aioshelly==5.1.1 +aioshelly==5.1.2 # homeassistant.components.skybell aioskybell==22.7.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index f4148c585fb..a60b2849628 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -236,7 +236,7 @@ aiosenseme==0.6.1 aiosenz==1.0.0 # homeassistant.components.shelly -aioshelly==5.1.1 +aioshelly==5.1.2 # homeassistant.components.skybell aioskybell==22.7.0 From 353fb3be7db9477d65e4f7e9fc863e41a874d8e9 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 13 Dec 2022 12:57:54 -1000 Subject: [PATCH 151/174] Bump aioesphomeapi to 13.0.2 to fix reconnects after bad protobuf message (#83951) --- homeassistant/components/esphome/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/esphome/manifest.json b/homeassistant/components/esphome/manifest.json index ae11aa59fce..83ebc60edd6 100644 --- a/homeassistant/components/esphome/manifest.json +++ b/homeassistant/components/esphome/manifest.json @@ -3,7 +3,7 @@ "name": "ESPHome", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/esphome", - "requirements": ["aioesphomeapi==13.0.1"], + "requirements": ["aioesphomeapi==13.0.2"], "zeroconf": ["_esphomelib._tcp.local."], "dhcp": [{ "registered_devices": true }], "codeowners": ["@OttoWinter", "@jesserockz"], diff --git a/requirements_all.txt b/requirements_all.txt index 5b73f242bb5..a2fd7cc1eeb 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -156,7 +156,7 @@ aioecowitt==2022.11.0 aioemonitor==1.0.5 # homeassistant.components.esphome -aioesphomeapi==13.0.1 +aioesphomeapi==13.0.2 # homeassistant.components.flo aioflo==2021.11.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index a60b2849628..1acca1db340 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -143,7 +143,7 @@ aioecowitt==2022.11.0 aioemonitor==1.0.5 # homeassistant.components.esphome -aioesphomeapi==13.0.1 +aioesphomeapi==13.0.2 # homeassistant.components.flo aioflo==2021.11.0 From 59b6d562079c96037982e0bc7a19547ec9b72072 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Wed, 14 Dec 2022 16:41:11 +0100 Subject: [PATCH 152/174] Ignore certain device trigger validation errors (#83972) --- homeassistant/components/device_automation/trigger.py | 4 ++-- tests/components/deconz/test_device_trigger.py | 1 + tests/components/zha/test_device_trigger.py | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/device_automation/trigger.py b/homeassistant/components/device_automation/trigger.py index 05f2f79ff28..cd5b3a84c82 100644 --- a/homeassistant/components/device_automation/trigger.py +++ b/homeassistant/components/device_automation/trigger.py @@ -68,7 +68,7 @@ async def async_validate_trigger_config( # Only call the dynamic validator if the relevant config entry is loaded registry = dr.async_get(hass) if not (device := registry.async_get(config[CONF_DEVICE_ID])): - raise InvalidDeviceAutomationConfig + return config device_config_entry = None for entry_id in device.config_entries: @@ -80,7 +80,7 @@ async def async_validate_trigger_config( break if not device_config_entry: - raise InvalidDeviceAutomationConfig + return config if not await hass.config_entries.async_wait_component(device_config_entry): return config diff --git a/tests/components/deconz/test_device_trigger.py b/tests/components/deconz/test_device_trigger.py index bcd178f21d4..66033aa286d 100644 --- a/tests/components/deconz/test_device_trigger.py +++ b/tests/components/deconz/test_device_trigger.py @@ -349,6 +349,7 @@ async def test_functional_device_trigger( assert automation_calls[0].data["some"] == "test_trigger_button_press" +@pytest.mark.skip(reason="Temporarily disabled until automation validation is improved") async def test_validate_trigger_unknown_device(hass, aioclient_mock): """Test unknown device does not return a trigger config.""" await setup_deconz_integration(hass, aioclient_mock) diff --git a/tests/components/zha/test_device_trigger.py b/tests/components/zha/test_device_trigger.py index 8e19fe5b637..49eeacc0e42 100644 --- a/tests/components/zha/test_device_trigger.py +++ b/tests/components/zha/test_device_trigger.py @@ -372,6 +372,7 @@ async def test_exception_bad_trigger(hass, mock_devices, calls, caplog): assert "Invalid config for [automation]" in caplog.text +@pytest.mark.skip(reason="Temporarily disabled until automation validation is improved") async def test_exception_no_device(hass, mock_devices, calls, caplog): """Test for exception on event triggers firing.""" From 283349f17e0c43056efd755dc1cae332addc4bcc Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 14 Dec 2022 12:48:04 -0500 Subject: [PATCH 153/174] Bumped version to 2022.12.6 --- homeassistant/const.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index ca64acdab46..e36cf99b288 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -8,7 +8,7 @@ from .backports.enum import StrEnum APPLICATION_NAME: Final = "HomeAssistant" MAJOR_VERSION: Final = 2022 MINOR_VERSION: Final = 12 -PATCH_VERSION: Final = "5" +PATCH_VERSION: Final = "6" __short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__: Final = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 9, 0) diff --git a/pyproject.toml b/pyproject.toml index 9874825ac27..1097ff5c708 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "homeassistant" -version = "2022.12.5" +version = "2022.12.6" license = {text = "Apache-2.0"} description = "Open-source home automation platform running on Python 3." readme = "README.rst" From 2b3123b925d4f2dfa5f4abf0059d9d57eff5e66f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 14 Dec 2022 12:38:26 -1000 Subject: [PATCH 154/174] Bump pySwitchbot to 0.23.2 (#84002) --- homeassistant/components/switchbot/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/switchbot/manifest.json b/homeassistant/components/switchbot/manifest.json index 831d14d8459..34dfbddbf2e 100644 --- a/homeassistant/components/switchbot/manifest.json +++ b/homeassistant/components/switchbot/manifest.json @@ -2,7 +2,7 @@ "domain": "switchbot", "name": "SwitchBot", "documentation": "https://www.home-assistant.io/integrations/switchbot", - "requirements": ["PySwitchbot==0.22.0"], + "requirements": ["PySwitchbot==0.23.2"], "config_flow": true, "dependencies": ["bluetooth"], "codeowners": [ diff --git a/requirements_all.txt b/requirements_all.txt index a2fd7cc1eeb..3a59478202f 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -37,7 +37,7 @@ PyRMVtransport==0.3.3 PySocks==1.7.1 # homeassistant.components.switchbot -PySwitchbot==0.22.0 +PySwitchbot==0.23.2 # homeassistant.components.transport_nsw PyTransportNSW==0.1.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 1acca1db340..b36f9a23333 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -33,7 +33,7 @@ PyRMVtransport==0.3.3 PySocks==1.7.1 # homeassistant.components.switchbot -PySwitchbot==0.22.0 +PySwitchbot==0.23.2 # homeassistant.components.transport_nsw PyTransportNSW==0.1.1 From c97b48cf3bddddf58f6954d9e951bdcad07c8e75 Mon Sep 17 00:00:00 2001 From: muppet3000 Date: Fri, 16 Dec 2022 14:31:49 +0000 Subject: [PATCH 155/174] Re-add missing Growatt TLX values (#84040) * Growatt - Re-adding missing TLX values (#81470) * Growatt - Added missing files to .coveragerc * Growatt - Correcting TLX Voltage types * Growatt - Addressing review comments --- .coveragerc | 2 + .../components/growatt_server/sensor.py | 19 ++ .../sensor_types/sensor_entity_description.py | 1 + .../growatt_server/sensor_types/tlx.py | 250 +++++++++++++++++- 4 files changed, 268 insertions(+), 4 deletions(-) diff --git a/.coveragerc b/.coveragerc index 94e1667bad8..85e08d51f71 100644 --- a/.coveragerc +++ b/.coveragerc @@ -471,7 +471,9 @@ omit = homeassistant/components/greenwave/light.py homeassistant/components/group/notify.py homeassistant/components/growatt_server/__init__.py + homeassistant/components/growatt_server/const.py homeassistant/components/growatt_server/sensor.py + homeassistant/components/growatt_server/sensor_types/* homeassistant/components/gstreamer/media_player.py homeassistant/components/gtfs/sensor.py homeassistant/components/guardian/__init__.py diff --git a/homeassistant/components/growatt_server/sensor.py b/homeassistant/components/growatt_server/sensor.py index d9ca800131f..b22664897fd 100644 --- a/homeassistant/components/growatt_server/sensor.py +++ b/homeassistant/components/growatt_server/sensor.py @@ -315,6 +315,25 @@ class GrowattData: "%s - No drop detected, using API value", entity_description.name ) + # Lifetime total values should always be increasing, they will never reset, however + # the API sometimes returns 0 values when the clock turns to 00:00 local time + # in that scenario we should just return the previous value + # Scenarios: + # 1 - System has a genuine 0 value when it it first commissioned: + # - will return 0 until a non-zero value is registered + # 2 - System has been running fine but temporarily resets to 0 briefly at midnight: + # - will return the previous value + # 3 - HA is restarted during the midnight 'outage' - Not handled: + # - Previous value will not exist meaning 0 will be returned + # - This is an edge case that would be better handled by looking up the previous + # value of the entity from the recorder + if entity_description.never_resets and api_value == 0 and previous_value: + _LOGGER.debug( + "API value is 0, but this value should never reset, returning previous value (%s) instead", + previous_value, + ) + return_value = previous_value + self.previous_values[variable] = return_value return return_value diff --git a/homeassistant/components/growatt_server/sensor_types/sensor_entity_description.py b/homeassistant/components/growatt_server/sensor_types/sensor_entity_description.py index 08a20209098..cd286e228b4 100644 --- a/homeassistant/components/growatt_server/sensor_types/sensor_entity_description.py +++ b/homeassistant/components/growatt_server/sensor_types/sensor_entity_description.py @@ -20,3 +20,4 @@ class GrowattSensorEntityDescription(SensorEntityDescription, GrowattRequiredKey precision: int | None = None currency: bool = False previous_value_drop_threshold: float | None = None + never_resets: bool = False diff --git a/homeassistant/components/growatt_server/sensor_types/tlx.py b/homeassistant/components/growatt_server/sensor_types/tlx.py index ba455747457..2a6e76a55a7 100644 --- a/homeassistant/components/growatt_server/sensor_types/tlx.py +++ b/homeassistant/components/growatt_server/sensor_types/tlx.py @@ -1,4 +1,8 @@ -"""Growatt Sensor definitions for the TLX type.""" +""" +Growatt Sensor definitions for the TLX type. + +TLX Type is also shown on the UI as: "MIN/MIC/MOD/NEO" +""" from __future__ import annotations from homeassistant.components.sensor import SensorDeviceClass, SensorStateClass @@ -7,6 +11,7 @@ from homeassistant.const import ( ELECTRIC_POTENTIAL_VOLT, ENERGY_KILO_WATT_HOUR, FREQUENCY_HERTZ, + PERCENTAGE, POWER_WATT, TEMP_CELSIUS, ) @@ -29,8 +34,9 @@ TLX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = ( api_key="eacTotal", native_unit_of_measurement=ENERGY_KILO_WATT_HOUR, device_class=SensorDeviceClass.ENERGY, - state_class=SensorStateClass.TOTAL, + state_class=SensorStateClass.TOTAL_INCREASING, precision=1, + never_resets=True, ), GrowattSensorEntityDescription( key="tlx_energy_total_input_1", @@ -38,8 +44,9 @@ TLX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = ( api_key="epv1Total", native_unit_of_measurement=ENERGY_KILO_WATT_HOUR, device_class=SensorDeviceClass.ENERGY, - state_class=SensorStateClass.TOTAL, + state_class=SensorStateClass.TOTAL_INCREASING, precision=1, + never_resets=True, ), GrowattSensorEntityDescription( key="tlx_energy_today_input_1", @@ -80,8 +87,9 @@ TLX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = ( api_key="epv2Total", native_unit_of_measurement=ENERGY_KILO_WATT_HOUR, device_class=SensorDeviceClass.ENERGY, - state_class=SensorStateClass.TOTAL, + state_class=SensorStateClass.TOTAL_INCREASING, precision=1, + never_resets=True, ), GrowattSensorEntityDescription( key="tlx_energy_today_input_2", @@ -116,6 +124,101 @@ TLX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = ( device_class=SensorDeviceClass.POWER, precision=1, ), + GrowattSensorEntityDescription( + key="tlx_energy_total_input_3", + name="Lifetime total energy input 3", + api_key="epv3Total", + native_unit_of_measurement=ENERGY_KILO_WATT_HOUR, + device_class=SensorDeviceClass.ENERGY, + state_class=SensorStateClass.TOTAL_INCREASING, + precision=1, + never_resets=True, + ), + GrowattSensorEntityDescription( + key="tlx_energy_today_input_3", + name="Energy Today Input 3", + api_key="epv3Today", + native_unit_of_measurement=ENERGY_KILO_WATT_HOUR, + device_class=SensorDeviceClass.ENERGY, + state_class=SensorStateClass.TOTAL_INCREASING, + precision=1, + ), + GrowattSensorEntityDescription( + key="tlx_voltage_input_3", + name="Input 3 voltage", + api_key="vpv3", + native_unit_of_measurement=ELECTRIC_POTENTIAL_VOLT, + device_class=SensorDeviceClass.VOLTAGE, + precision=1, + ), + GrowattSensorEntityDescription( + key="tlx_amperage_input_3", + name="Input 3 Amperage", + api_key="ipv3", + native_unit_of_measurement=ELECTRIC_CURRENT_AMPERE, + device_class=SensorDeviceClass.CURRENT, + precision=1, + ), + GrowattSensorEntityDescription( + key="tlx_wattage_input_3", + name="Input 3 Wattage", + api_key="ppv3", + native_unit_of_measurement=POWER_WATT, + device_class=SensorDeviceClass.POWER, + precision=1, + ), + GrowattSensorEntityDescription( + key="tlx_energy_total_input_4", + name="Lifetime total energy input 4", + api_key="epv4Total", + native_unit_of_measurement=ENERGY_KILO_WATT_HOUR, + device_class=SensorDeviceClass.ENERGY, + state_class=SensorStateClass.TOTAL_INCREASING, + precision=1, + never_resets=True, + ), + GrowattSensorEntityDescription( + key="tlx_energy_today_input_4", + name="Energy Today Input 4", + api_key="epv4Today", + native_unit_of_measurement=ENERGY_KILO_WATT_HOUR, + device_class=SensorDeviceClass.ENERGY, + state_class=SensorStateClass.TOTAL_INCREASING, + precision=1, + ), + GrowattSensorEntityDescription( + key="tlx_voltage_input_4", + name="Input 4 voltage", + api_key="vpv4", + native_unit_of_measurement=ELECTRIC_POTENTIAL_VOLT, + device_class=SensorDeviceClass.VOLTAGE, + precision=1, + ), + GrowattSensorEntityDescription( + key="tlx_amperage_input_4", + name="Input 4 Amperage", + api_key="ipv4", + native_unit_of_measurement=ELECTRIC_CURRENT_AMPERE, + device_class=SensorDeviceClass.CURRENT, + precision=1, + ), + GrowattSensorEntityDescription( + key="tlx_wattage_input_4", + name="Input 4 Wattage", + api_key="ppv4", + native_unit_of_measurement=POWER_WATT, + device_class=SensorDeviceClass.POWER, + precision=1, + ), + GrowattSensorEntityDescription( + key="tlx_solar_generation_total", + name="Lifetime total solar energy", + api_key="epvTotal", + native_unit_of_measurement=ENERGY_KILO_WATT_HOUR, + device_class=SensorDeviceClass.ENERGY, + state_class=SensorStateClass.TOTAL_INCREASING, + never_resets=True, + ), GrowattSensorEntityDescription( key="tlx_internal_wattage", name="Internal wattage", @@ -187,4 +290,143 @@ TLX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = ( device_class=SensorDeviceClass.TEMPERATURE, precision=1, ), + GrowattSensorEntityDescription( + key="tlx_all_batteries_discharge_today", + name="All batteries discharged today", + api_key="edischargeToday", + native_unit_of_measurement=ENERGY_KILO_WATT_HOUR, + device_class=SensorDeviceClass.ENERGY, + state_class=SensorStateClass.TOTAL_INCREASING, + ), + GrowattSensorEntityDescription( + key="tlx_all_batteries_discharge_total", + name="Lifetime total all batteries discharged", + api_key="edischargeTotal", + native_unit_of_measurement=ENERGY_KILO_WATT_HOUR, + device_class=SensorDeviceClass.ENERGY, + state_class=SensorStateClass.TOTAL_INCREASING, + never_resets=True, + ), + GrowattSensorEntityDescription( + key="tlx_battery_1_discharge_w", + name="Battery 1 discharging W", + api_key="bdc1DischargePower", + native_unit_of_measurement=POWER_WATT, + device_class=SensorDeviceClass.POWER, + ), + GrowattSensorEntityDescription( + key="tlx_battery_1_discharge_total", + name="Lifetime total battery 1 discharged", + api_key="bdc1DischargeTotal", + native_unit_of_measurement=ENERGY_KILO_WATT_HOUR, + device_class=SensorDeviceClass.ENERGY, + state_class=SensorStateClass.TOTAL_INCREASING, + never_resets=True, + ), + GrowattSensorEntityDescription( + key="tlx_battery_2_discharge_w", + name="Battery 2 discharging W", + api_key="bdc1DischargePower", + native_unit_of_measurement=POWER_WATT, + device_class=SensorDeviceClass.POWER, + ), + GrowattSensorEntityDescription( + key="tlx_battery_2_discharge_total", + name="Lifetime total battery 2 discharged", + api_key="bdc1DischargeTotal", + native_unit_of_measurement=ENERGY_KILO_WATT_HOUR, + device_class=SensorDeviceClass.ENERGY, + state_class=SensorStateClass.TOTAL_INCREASING, + never_resets=True, + ), + GrowattSensorEntityDescription( + key="tlx_all_batteries_charge_today", + name="All batteries charged today", + api_key="echargeToday", + native_unit_of_measurement=ENERGY_KILO_WATT_HOUR, + device_class=SensorDeviceClass.ENERGY, + state_class=SensorStateClass.TOTAL_INCREASING, + ), + GrowattSensorEntityDescription( + key="tlx_all_batteries_charge_total", + name="Lifetime total all batteries charged", + api_key="echargeTotal", + native_unit_of_measurement=ENERGY_KILO_WATT_HOUR, + device_class=SensorDeviceClass.ENERGY, + state_class=SensorStateClass.TOTAL_INCREASING, + never_resets=True, + ), + GrowattSensorEntityDescription( + key="tlx_battery_1_charge_w", + name="Battery 1 charging W", + api_key="bdc1ChargePower", + native_unit_of_measurement=POWER_WATT, + device_class=SensorDeviceClass.POWER, + ), + GrowattSensorEntityDescription( + key="tlx_battery_1_charge_total", + name="Lifetime total battery 1 charged", + api_key="bdc1ChargeTotal", + native_unit_of_measurement=ENERGY_KILO_WATT_HOUR, + device_class=SensorDeviceClass.ENERGY, + state_class=SensorStateClass.TOTAL_INCREASING, + never_resets=True, + ), + GrowattSensorEntityDescription( + key="tlx_battery_2_charge_w", + name="Battery 2 charging W", + api_key="bdc1ChargePower", + native_unit_of_measurement=POWER_WATT, + device_class=SensorDeviceClass.POWER, + ), + GrowattSensorEntityDescription( + key="tlx_battery_2_charge_total", + name="Lifetime total battery 2 charged", + api_key="bdc1ChargeTotal", + native_unit_of_measurement=ENERGY_KILO_WATT_HOUR, + device_class=SensorDeviceClass.ENERGY, + state_class=SensorStateClass.TOTAL_INCREASING, + never_resets=True, + ), + GrowattSensorEntityDescription( + key="tlx_export_to_grid_today", + name="Export to grid today", + api_key="etoGridToday", + native_unit_of_measurement=ENERGY_KILO_WATT_HOUR, + device_class=SensorDeviceClass.ENERGY, + state_class=SensorStateClass.TOTAL_INCREASING, + ), + GrowattSensorEntityDescription( + key="tlx_export_to_grid_total", + name="Lifetime total export to grid", + api_key="etoGridTotal", + native_unit_of_measurement=ENERGY_KILO_WATT_HOUR, + device_class=SensorDeviceClass.ENERGY, + state_class=SensorStateClass.TOTAL_INCREASING, + never_resets=True, + ), + GrowattSensorEntityDescription( + key="tlx_load_consumption_today", + name="Load consumption today", + api_key="elocalLoadToday", + native_unit_of_measurement=ENERGY_KILO_WATT_HOUR, + device_class=SensorDeviceClass.ENERGY, + state_class=SensorStateClass.TOTAL_INCREASING, + ), + GrowattSensorEntityDescription( + key="mix_load_consumption_total", + name="Lifetime total load consumption", + api_key="elocalLoadTotal", + native_unit_of_measurement=ENERGY_KILO_WATT_HOUR, + device_class=SensorDeviceClass.ENERGY, + state_class=SensorStateClass.TOTAL_INCREASING, + never_resets=True, + ), + GrowattSensorEntityDescription( + key="tlx_statement_of_charge", + name="Statement of charge (SoC)", + api_key="bmsSoc", + native_unit_of_measurement=PERCENTAGE, + device_class=SensorDeviceClass.BATTERY, + ), ) From b32ba3325059fa269d89802ccb044a7d052beab9 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Thu, 15 Dec 2022 17:35:02 +0100 Subject: [PATCH 156/174] Update frontend to 20221213.1 (#84058) --- homeassistant/components/frontend/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/frontend/manifest.json b/homeassistant/components/frontend/manifest.json index 9ff4c13585d..2b3962e2241 100644 --- a/homeassistant/components/frontend/manifest.json +++ b/homeassistant/components/frontend/manifest.json @@ -2,7 +2,7 @@ "domain": "frontend", "name": "Home Assistant Frontend", "documentation": "https://www.home-assistant.io/integrations/frontend", - "requirements": ["home-assistant-frontend==20221213.0"], + "requirements": ["home-assistant-frontend==20221213.1"], "dependencies": [ "api", "auth", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index b378d03bbf2..e00395fc2d6 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -22,7 +22,7 @@ dbus-fast==1.75.0 fnvhash==0.1.0 hass-nabucasa==0.61.0 home-assistant-bluetooth==1.8.1 -home-assistant-frontend==20221213.0 +home-assistant-frontend==20221213.1 httpx==0.23.1 ifaddr==0.1.7 janus==1.0.0 diff --git a/requirements_all.txt b/requirements_all.txt index 3a59478202f..0128c39aba4 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -884,7 +884,7 @@ hole==0.7.0 holidays==0.17.2 # homeassistant.components.frontend -home-assistant-frontend==20221213.0 +home-assistant-frontend==20221213.1 # homeassistant.components.home_connect homeconnect==0.7.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index b36f9a23333..b859181dab5 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -664,7 +664,7 @@ hole==0.7.0 holidays==0.17.2 # homeassistant.components.frontend -home-assistant-frontend==20221213.0 +home-assistant-frontend==20221213.1 # homeassistant.components.home_connect homeconnect==0.7.2 From 6ce2bf6be5c7d6aee5e6808546723b97d99af3ce Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 16 Dec 2022 04:25:49 -1000 Subject: [PATCH 157/174] Bump govee-ble to 0.19.3 (#84062) * Bump govee-ble to 0.19.2 fixes #82994 fixes #81335 changelog: https://github.com/Bluetooth-Devices/govee-ble/compare/v0.19.1...v0.19.2 * .3 --- homeassistant/components/govee_ble/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/govee_ble/manifest.json b/homeassistant/components/govee_ble/manifest.json index 1cb1eeaddd8..8272ab5d20e 100644 --- a/homeassistant/components/govee_ble/manifest.json +++ b/homeassistant/components/govee_ble/manifest.json @@ -73,7 +73,7 @@ "connectable": false } ], - "requirements": ["govee-ble==0.19.1"], + "requirements": ["govee-ble==0.19.3"], "dependencies": ["bluetooth"], "codeowners": ["@bdraco"], "iot_class": "local_push" diff --git a/requirements_all.txt b/requirements_all.txt index 0128c39aba4..18f72811018 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -796,7 +796,7 @@ googlemaps==2.5.1 goslide-api==0.5.1 # homeassistant.components.govee_ble -govee-ble==0.19.1 +govee-ble==0.19.3 # homeassistant.components.remote_rpi_gpio gpiozero==1.6.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index b859181dab5..95d297a088c 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -600,7 +600,7 @@ google-nest-sdm==2.1.0 googlemaps==2.5.1 # homeassistant.components.govee_ble -govee-ble==0.19.1 +govee-ble==0.19.3 # homeassistant.components.gree greeclimate==1.3.0 From 2d2318438c697ae08ef7190ad9c7629096ae317a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Hjelseth=20H=C3=B8yer?= Date: Fri, 16 Dec 2022 20:11:54 +0100 Subject: [PATCH 158/174] Update Tibber lib, improve realtime streaming (#84065) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update tibber lib, improve realtime streaming Signed-off-by: Daniel Hjelseth Høyer Signed-off-by: Daniel Hjelseth Høyer --- homeassistant/components/tibber/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/tibber/manifest.json b/homeassistant/components/tibber/manifest.json index 05bfe96b071..814f36e018c 100644 --- a/homeassistant/components/tibber/manifest.json +++ b/homeassistant/components/tibber/manifest.json @@ -3,7 +3,7 @@ "domain": "tibber", "name": "Tibber", "documentation": "https://www.home-assistant.io/integrations/tibber", - "requirements": ["pyTibber==0.26.4"], + "requirements": ["pyTibber==0.26.5"], "codeowners": ["@danielhiversen"], "quality_scale": "silver", "config_flow": true, diff --git a/requirements_all.txt b/requirements_all.txt index 18f72811018..51771cc4f47 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1432,7 +1432,7 @@ pyRFXtrx==0.30.0 pySwitchmate==0.5.1 # homeassistant.components.tibber -pyTibber==0.26.4 +pyTibber==0.26.5 # homeassistant.components.dlink pyW215==0.7.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 95d297a088c..029e9f961b0 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1032,7 +1032,7 @@ pyMetno==0.9.0 pyRFXtrx==0.30.0 # homeassistant.components.tibber -pyTibber==0.26.4 +pyTibber==0.26.5 # homeassistant.components.nextbus py_nextbusnext==0.1.5 From 5e949b062a90f93e21da88f7e39195cad8b36f94 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 16 Dec 2022 04:25:23 -1000 Subject: [PATCH 159/174] Bump bluetooth-auto-recovery to 1.0.3 (#84075) * Bump bluetooth-auto-recovery to 1.0.1 changelog: https://github.com/Bluetooth-Devices/bluetooth-auto-recovery/compare/v1.0.0...v1.0.1 Handles the case were the adapter gets a new hci number after a usb reset. * one more * bump again --- homeassistant/components/bluetooth/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/bluetooth/manifest.json b/homeassistant/components/bluetooth/manifest.json index 069bb64011d..ab3f9906f0b 100644 --- a/homeassistant/components/bluetooth/manifest.json +++ b/homeassistant/components/bluetooth/manifest.json @@ -9,7 +9,7 @@ "bleak==0.19.2", "bleak-retry-connector==2.10.2", "bluetooth-adapters==0.12.0", - "bluetooth-auto-recovery==1.0.0", + "bluetooth-auto-recovery==1.0.3", "bluetooth-data-tools==0.3.0", "dbus-fast==1.75.0" ], diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index e00395fc2d6..674271b427f 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -13,7 +13,7 @@ bcrypt==3.1.7 bleak-retry-connector==2.10.2 bleak==0.19.2 bluetooth-adapters==0.12.0 -bluetooth-auto-recovery==1.0.0 +bluetooth-auto-recovery==1.0.3 bluetooth-data-tools==0.3.0 certifi>=2021.5.30 ciso8601==2.2.0 diff --git a/requirements_all.txt b/requirements_all.txt index 51771cc4f47..7b319d3597f 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -450,7 +450,7 @@ bluemaestro-ble==0.2.0 bluetooth-adapters==0.12.0 # homeassistant.components.bluetooth -bluetooth-auto-recovery==1.0.0 +bluetooth-auto-recovery==1.0.3 # homeassistant.components.bluetooth # homeassistant.components.led_ble diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 029e9f961b0..f1a7c7931d1 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -364,7 +364,7 @@ bluemaestro-ble==0.2.0 bluetooth-adapters==0.12.0 # homeassistant.components.bluetooth -bluetooth-auto-recovery==1.0.0 +bluetooth-auto-recovery==1.0.3 # homeassistant.components.bluetooth # homeassistant.components.led_ble From dac8560e14deda1fef9c4deba4c8ee69c870dbdc Mon Sep 17 00:00:00 2001 From: Joakim Plate Date: Fri, 16 Dec 2022 15:25:05 +0100 Subject: [PATCH 160/174] Make sure philips_hs remote entity calls parent added (#84082) Make sure remote calls parent added --- homeassistant/components/philips_js/remote.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/homeassistant/components/philips_js/remote.py b/homeassistant/components/philips_js/remote.py index 3496ec5f576..2c2f8752e0d 100644 --- a/homeassistant/components/philips_js/remote.py +++ b/homeassistant/components/philips_js/remote.py @@ -58,6 +58,8 @@ class PhilipsTVRemote(CoordinatorEntity[PhilipsTVDataUpdateCoordinator], RemoteE async def async_added_to_hass(self) -> None: """Handle being added to hass.""" + await super().async_added_to_hass() + if (entry := self.registry_entry) and entry.device_id: self.async_on_remove( self._turn_on.async_register( From 1e70901b9e515e5c3c856487cce7062a03f7ff47 Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Sat, 17 Dec 2022 16:42:55 -0800 Subject: [PATCH 161/174] Bump ical to 4.2.3 (#84104) --- homeassistant/components/local_calendar/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/local_calendar/manifest.json b/homeassistant/components/local_calendar/manifest.json index 2921b3e2e10..3e1a1afc609 100644 --- a/homeassistant/components/local_calendar/manifest.json +++ b/homeassistant/components/local_calendar/manifest.json @@ -3,7 +3,7 @@ "name": "Local Calendar", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/local_calendar", - "requirements": ["ical==4.2.2"], + "requirements": ["ical==4.2.3"], "codeowners": ["@allenporter"], "iot_class": "local_polling", "loggers": ["ical"] diff --git a/requirements_all.txt b/requirements_all.txt index 7b319d3597f..5429021cdf5 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -926,7 +926,7 @@ ibm-watson==5.2.2 ibmiotf==0.3.4 # homeassistant.components.local_calendar -ical==4.2.2 +ical==4.2.3 # homeassistant.components.ping icmplib==3.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index f1a7c7931d1..777c4928eb1 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -691,7 +691,7 @@ iaqualink==0.5.0 ibeacon_ble==1.0.1 # homeassistant.components.local_calendar -ical==4.2.2 +ical==4.2.3 # homeassistant.components.ping icmplib==3.0 From 0b4d1fe6415d55cf5264bd82b12531b5a4a47445 Mon Sep 17 00:00:00 2001 From: Michael <35783820+mib1185@users.noreply.github.com> Date: Sun, 18 Dec 2022 01:43:18 +0100 Subject: [PATCH 162/174] Fix check if Surveillance Station is available in Synology DSM (#84140) start coordinator only when surveillance_station is avialble during api setup --- homeassistant/components/synology_dsm/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/synology_dsm/__init__.py b/homeassistant/components/synology_dsm/__init__.py index c37540b14b7..0b3001215a1 100644 --- a/homeassistant/components/synology_dsm/__init__.py +++ b/homeassistant/components/synology_dsm/__init__.py @@ -98,7 +98,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: # The central coordinator needs to be refreshed first since # the next two rely on data from it coordinator_cameras: SynologyDSMCameraUpdateCoordinator | None = None - if SynoSurveillanceStation.CAMERA_API_KEY in available_apis: + if api.surveillance_station is not None: coordinator_cameras = SynologyDSMCameraUpdateCoordinator(hass, entry, api) await coordinator_cameras.async_config_entry_first_refresh() From 76d33fdaf1a9db4937c93c8f6406b4f58f3b0f08 Mon Sep 17 00:00:00 2001 From: Matthias Alphart Date: Sun, 18 Dec 2022 00:06:14 +0100 Subject: [PATCH 163/174] Fix KNX ConfigFlow for manual secure tunnel keys (#84155) --- homeassistant/components/knx/config_flow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/knx/config_flow.py b/homeassistant/components/knx/config_flow.py index f7ef0d943a9..6e0c357b5fa 100644 --- a/homeassistant/components/knx/config_flow.py +++ b/homeassistant/components/knx/config_flow.py @@ -268,7 +268,7 @@ class KNXCommonFlow(ABC, FlowHandler): if selected_tunnelling_type == CONF_KNX_TUNNELING_TCP_SECURE: return self.async_show_menu( step_id="secure_key_source", - menu_options=["secure_knxkeys", "secure_routing_manual"], + menu_options=["secure_knxkeys", "secure_tunnel_manual"], ) return self.finish_flow(title=f"Tunneling @ {_host}") From d95a843d935db97fb433593a18a56d50db169c51 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sat, 17 Dec 2022 20:05:11 -0500 Subject: [PATCH 164/174] Bumped version to 2022.12.7 --- homeassistant/const.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index e36cf99b288..1324526804f 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -8,7 +8,7 @@ from .backports.enum import StrEnum APPLICATION_NAME: Final = "HomeAssistant" MAJOR_VERSION: Final = 2022 MINOR_VERSION: Final = 12 -PATCH_VERSION: Final = "6" +PATCH_VERSION: Final = "7" __short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__: Final = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 9, 0) diff --git a/pyproject.toml b/pyproject.toml index 1097ff5c708..68307acc33c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "homeassistant" -version = "2022.12.6" +version = "2022.12.7" license = {text = "Apache-2.0"} description = "Open-source home automation platform running on Python 3." readme = "README.rst" From 756070cd817eb0591235f10bccc3f9a7baf502f1 Mon Sep 17 00:00:00 2001 From: Nyro Date: Tue, 20 Dec 2022 10:06:24 +0100 Subject: [PATCH 165/174] Add missing preset for Overkiz atlantic electrical heater (#84080) Readd missing preset for atlantic electrical heater --- .../overkiz/climate_entities/atlantic_electrical_heater.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/homeassistant/components/overkiz/climate_entities/atlantic_electrical_heater.py b/homeassistant/components/overkiz/climate_entities/atlantic_electrical_heater.py index c0d1dd04663..36d657d5846 100644 --- a/homeassistant/components/overkiz/climate_entities/atlantic_electrical_heater.py +++ b/homeassistant/components/overkiz/climate_entities/atlantic_electrical_heater.py @@ -17,6 +17,8 @@ from homeassistant.const import TEMP_CELSIUS from ..entity import OverkizEntity +PRESET_COMFORT1 = "comfort-1" +PRESET_COMFORT2 = "comfort-2" PRESET_FROST_PROTECTION = "frost_protection" OVERKIZ_TO_HVAC_MODES: dict[str, HVACMode] = { @@ -31,6 +33,8 @@ OVERKIZ_TO_PRESET_MODES: dict[str, str] = { OverkizCommandParam.FROSTPROTECTION: PRESET_FROST_PROTECTION, OverkizCommandParam.ECO: PRESET_ECO, OverkizCommandParam.COMFORT: PRESET_COMFORT, + OverkizCommandParam.COMFORT_1: PRESET_COMFORT1, + OverkizCommandParam.COMFORT_2: PRESET_COMFORT2, } PRESET_MODES_TO_OVERKIZ = {v: k for k, v in OVERKIZ_TO_PRESET_MODES.items()} From 3bdf80574dc439abacce6e7ce800ace6f1d51587 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 19 Dec 2022 02:37:29 -1000 Subject: [PATCH 166/174] Fix connectable Bluetooth devices not going available after scanner recovers (#84172) --- homeassistant/components/bluetooth/manager.py | 27 ++- tests/components/bluetooth/test_manager.py | 195 +++++++++++++++++- 2 files changed, 209 insertions(+), 13 deletions(-) diff --git a/homeassistant/components/bluetooth/manager.py b/homeassistant/components/bluetooth/manager.py index 534bd636355..513dc9dda14 100644 --- a/homeassistant/components/bluetooth/manager.py +++ b/homeassistant/components/bluetooth/manager.py @@ -369,6 +369,7 @@ class BluetoothManager: all_history = self._all_history connectable = service_info.connectable connectable_history = self._connectable_history + old_connectable_service_info = connectable and connectable_history.get(address) source = service_info.source debug = _LOGGER.isEnabledFor(logging.DEBUG) @@ -399,7 +400,6 @@ class BluetoothManager: # but not in the connectable history or the connectable source is the same # as the new source, we need to add it to the connectable history if connectable: - old_connectable_service_info = connectable_history.get(address) if old_connectable_service_info and ( # If its the same as the preferred source, we are done # as we know we prefer the old advertisement @@ -442,17 +442,24 @@ class BluetoothManager: tracker.async_collect(service_info) # If the advertisement data is the same as the last time we saw it, we - # don't need to do anything else. - if old_service_info and not ( - service_info.manufacturer_data != old_service_info.manufacturer_data - or service_info.service_data != old_service_info.service_data - or service_info.service_uuids != old_service_info.service_uuids - or service_info.name != old_service_info.name + # don't need to do anything else unless its connectable and we are missing + # connectable history for the device so we can make it available again + # after unavailable callbacks. + if ( + # Ensure its not a connectable device missing from connectable history + not (connectable and not old_connectable_service_info) + # Than check if advertisement data is the same + and old_service_info + and not ( + service_info.manufacturer_data != old_service_info.manufacturer_data + or service_info.service_data != old_service_info.service_data + or service_info.service_uuids != old_service_info.service_uuids + or service_info.name != old_service_info.name + ) ): return - is_connectable_by_any_source = address in self._connectable_history - if not connectable and is_connectable_by_any_source: + if not connectable and old_connectable_service_info: # Since we have a connectable path and our BleakClient will # route any connection attempts to the connectable path, we # mark the service_info as connectable so that the callbacks @@ -481,7 +488,7 @@ class BluetoothManager: matched_domains, ) - if is_connectable_by_any_source: + if connectable or old_connectable_service_info: # Bleak callbacks must get a connectable device for callback_filters in self._bleak_callbacks: _dispatch_bleak_callback(*callback_filters, device, advertisement_data) diff --git a/tests/components/bluetooth/test_manager.py b/tests/components/bluetooth/test_manager.py index e295291068a..7c8f9b93af7 100644 --- a/tests/components/bluetooth/test_manager.py +++ b/tests/components/bluetooth/test_manager.py @@ -1,27 +1,47 @@ """Tests for the Bluetooth integration manager.""" +from datetime import timedelta import time from unittest.mock import patch -from bleak.backends.scanner import BLEDevice +from bleak.backends.scanner import AdvertisementData, BLEDevice from bluetooth_adapters import AdvertisementHistory import pytest from homeassistant.components import bluetooth -from homeassistant.components.bluetooth import BaseHaScanner +from homeassistant.components.bluetooth import ( + BaseHaScanner, + BaseHaRemoteScanner, + BluetoothChange, + BluetoothScanningMode, + BluetoothServiceInfo, + BluetoothServiceInfoBleak, + HaBluetoothConnector, + async_ble_device_from_address, + async_get_advertisement_callback, + async_scanner_count, + async_track_unavailable, +) +from homeassistant.components.bluetooth.const import UNAVAILABLE_TRACK_SECONDS from homeassistant.components.bluetooth.manager import ( FALLBACK_MAXIMUM_STALE_ADVERTISEMENT_SECONDS, ) -from homeassistant.core import HomeAssistant +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.json import json_loads from homeassistant.setup import async_setup_component +from homeassistant.util import dt as dt_util from . import ( + MockBleakClient, + _get_manager, generate_advertisement_data, inject_advertisement_with_source, inject_advertisement_with_time_and_source, inject_advertisement_with_time_and_source_connectable, ) +from tests.common import async_fire_time_changed, load_fixture + @pytest.fixture def register_hci0_scanner(hass: HomeAssistant) -> None: @@ -514,3 +534,172 @@ async def test_switching_adapters_when_one_stop_scanning( ) cancel_hci2() + + +async def test_goes_unavailable_connectable_only_and_recovers( + hass, mock_bluetooth_adapters +): + """Test all connectable scanners go unavailable, and than recover when there is a non-connectable scanner.""" + assert await async_setup_component(hass, bluetooth.DOMAIN, {}) + await hass.async_block_till_done() + + assert async_scanner_count(hass, connectable=True) == 0 + assert async_scanner_count(hass, connectable=False) == 0 + switchbot_device_connectable = BLEDevice( + "44:44:33:11:23:45", + "wohand", + {}, + rssi=-100, + ) + switchbot_device_non_connectable = BLEDevice( + "44:44:33:11:23:45", + "wohand", + {}, + rssi=-100, + ) + switchbot_device_adv = generate_advertisement_data( + local_name="wohand", + service_uuids=["050a021a-0000-1000-8000-00805f9b34fb"], + service_data={"050a021a-0000-1000-8000-00805f9b34fb": b"\n\xff"}, + manufacturer_data={1: b"\x01"}, + rssi=-100, + ) + callbacks = [] + + def _fake_subscriber( + service_info: BluetoothServiceInfo, + change: BluetoothChange, + ) -> None: + """Fake subscriber for the BleakScanner.""" + callbacks.append((service_info, change)) + + cancel = bluetooth.async_register_callback( + hass, + _fake_subscriber, + {"address": "44:44:33:11:23:45", "connectable": True}, + BluetoothScanningMode.ACTIVE, + ) + + class FakeScanner(BaseHaRemoteScanner): + def inject_advertisement( + self, device: BLEDevice, advertisement_data: AdvertisementData + ) -> None: + """Inject an advertisement.""" + self._async_on_advertisement( + device.address, + advertisement_data.rssi, + device.name, + advertisement_data.service_uuids, + advertisement_data.service_data, + advertisement_data.manufacturer_data, + advertisement_data.tx_power, + {"scanner_specific_data": "test"}, + ) + + new_info_callback = async_get_advertisement_callback(hass) + connector = ( + HaBluetoothConnector(MockBleakClient, "mock_bleak_client", lambda: False), + ) + connectable_scanner = FakeScanner( + hass, + "connectable", + "connectable", + new_info_callback, + connector, + True, + ) + unsetup_connectable_scanner = connectable_scanner.async_setup() + cancel_connectable_scanner = _get_manager().async_register_scanner( + connectable_scanner, True + ) + connectable_scanner.inject_advertisement( + switchbot_device_connectable, switchbot_device_adv + ) + assert async_ble_device_from_address(hass, "44:44:33:11:23:45") is not None + assert async_scanner_count(hass, connectable=True) == 1 + assert len(callbacks) == 1 + + assert ( + "44:44:33:11:23:45" + in connectable_scanner.discovered_devices_and_advertisement_data + ) + + not_connectable_scanner = FakeScanner( + hass, + "not_connectable", + "not_connectable", + new_info_callback, + connector, + False, + ) + unsetup_not_connectable_scanner = not_connectable_scanner.async_setup() + cancel_not_connectable_scanner = _get_manager().async_register_scanner( + not_connectable_scanner, False + ) + not_connectable_scanner.inject_advertisement( + switchbot_device_non_connectable, switchbot_device_adv + ) + assert async_scanner_count(hass, connectable=True) == 1 + assert async_scanner_count(hass, connectable=False) == 2 + + assert ( + "44:44:33:11:23:45" + in not_connectable_scanner.discovered_devices_and_advertisement_data + ) + + unavailable_callbacks: list[BluetoothServiceInfoBleak] = [] + + @callback + def _unavailable_callback(service_info: BluetoothServiceInfoBleak) -> None: + """Wrong device unavailable callback.""" + nonlocal unavailable_callbacks + unavailable_callbacks.append(service_info.address) + + cancel_unavailable = async_track_unavailable( + hass, + _unavailable_callback, + switchbot_device_connectable.address, + connectable=True, + ) + + assert async_scanner_count(hass, connectable=True) == 1 + cancel_connectable_scanner() + unsetup_connectable_scanner() + assert async_scanner_count(hass, connectable=True) == 0 + assert async_scanner_count(hass, connectable=False) == 1 + + async_fire_time_changed( + hass, dt_util.utcnow() + timedelta(seconds=UNAVAILABLE_TRACK_SECONDS) + ) + await hass.async_block_till_done() + assert "44:44:33:11:23:45" in unavailable_callbacks + cancel_unavailable() + + connectable_scanner_2 = FakeScanner( + hass, + "connectable", + "connectable", + new_info_callback, + connector, + True, + ) + unsetup_connectable_scanner_2 = connectable_scanner_2.async_setup() + cancel_connectable_scanner_2 = _get_manager().async_register_scanner( + connectable_scanner, True + ) + connectable_scanner_2.inject_advertisement( + switchbot_device_connectable, switchbot_device_adv + ) + assert ( + "44:44:33:11:23:45" + in connectable_scanner_2.discovered_devices_and_advertisement_data + ) + + # We should get another callback to make the device available again + assert len(callbacks) == 2 + + cancel() + cancel_connectable_scanner_2() + unsetup_connectable_scanner_2() + cancel_not_connectable_scanner() + unsetup_not_connectable_scanner() From 39a47c939cdaf7c4748dafdc30bac30303a9f7cb Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Mon, 19 Dec 2022 10:00:38 -0800 Subject: [PATCH 167/174] Bump ical to 4.2.4 (#84248) --- homeassistant/components/local_calendar/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/local_calendar/manifest.json b/homeassistant/components/local_calendar/manifest.json index 3e1a1afc609..21ec76bf967 100644 --- a/homeassistant/components/local_calendar/manifest.json +++ b/homeassistant/components/local_calendar/manifest.json @@ -3,7 +3,7 @@ "name": "Local Calendar", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/local_calendar", - "requirements": ["ical==4.2.3"], + "requirements": ["ical==4.2.4"], "codeowners": ["@allenporter"], "iot_class": "local_polling", "loggers": ["ical"] diff --git a/requirements_all.txt b/requirements_all.txt index 5429021cdf5..859baba3364 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -926,7 +926,7 @@ ibm-watson==5.2.2 ibmiotf==0.3.4 # homeassistant.components.local_calendar -ical==4.2.3 +ical==4.2.4 # homeassistant.components.ping icmplib==3.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 777c4928eb1..2d96c425c5b 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -691,7 +691,7 @@ iaqualink==0.5.0 ibeacon_ble==1.0.1 # homeassistant.components.local_calendar -ical==4.2.3 +ical==4.2.4 # homeassistant.components.ping icmplib==3.0 From 93cffe8631192996db94cb3bd44c90686f3e86c2 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 19 Dec 2022 10:45:39 -1000 Subject: [PATCH 168/174] Bump bluetooth-data-tools to 0.3.1 (#84258) changelog: https://github.com/Bluetooth-Devices/bluetooth-data-tools/compare/v0.3.0...v0.3.1 --- homeassistant/components/bluetooth/manifest.json | 2 +- homeassistant/components/led_ble/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/bluetooth/manifest.json b/homeassistant/components/bluetooth/manifest.json index ab3f9906f0b..35659a0b1db 100644 --- a/homeassistant/components/bluetooth/manifest.json +++ b/homeassistant/components/bluetooth/manifest.json @@ -10,7 +10,7 @@ "bleak-retry-connector==2.10.2", "bluetooth-adapters==0.12.0", "bluetooth-auto-recovery==1.0.3", - "bluetooth-data-tools==0.3.0", + "bluetooth-data-tools==0.3.1", "dbus-fast==1.75.0" ], "codeowners": ["@bdraco"], diff --git a/homeassistant/components/led_ble/manifest.json b/homeassistant/components/led_ble/manifest.json index ea272dcacc0..9282e0bd8a2 100644 --- a/homeassistant/components/led_ble/manifest.json +++ b/homeassistant/components/led_ble/manifest.json @@ -3,7 +3,7 @@ "name": "LED BLE", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/led_ble/", - "requirements": ["bluetooth-data-tools==0.3.0", "led-ble==1.0.0"], + "requirements": ["bluetooth-data-tools==0.3.1", "led-ble==1.0.0"], "dependencies": ["bluetooth"], "codeowners": ["@bdraco"], "bluetooth": [ diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 674271b427f..cf6cf03c472 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -14,7 +14,7 @@ bleak-retry-connector==2.10.2 bleak==0.19.2 bluetooth-adapters==0.12.0 bluetooth-auto-recovery==1.0.3 -bluetooth-data-tools==0.3.0 +bluetooth-data-tools==0.3.1 certifi>=2021.5.30 ciso8601==2.2.0 cryptography==38.0.3 diff --git a/requirements_all.txt b/requirements_all.txt index 859baba3364..184accfaedd 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -454,7 +454,7 @@ bluetooth-auto-recovery==1.0.3 # homeassistant.components.bluetooth # homeassistant.components.led_ble -bluetooth-data-tools==0.3.0 +bluetooth-data-tools==0.3.1 # homeassistant.components.bond bond-async==0.1.22 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 2d96c425c5b..374d7b916e1 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -368,7 +368,7 @@ bluetooth-auto-recovery==1.0.3 # homeassistant.components.bluetooth # homeassistant.components.led_ble -bluetooth-data-tools==0.3.0 +bluetooth-data-tools==0.3.1 # homeassistant.components.bond bond-async==0.1.22 From e8e4f6a5158a2d904344b2d805464d0d2b96b37c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Tue, 20 Dec 2022 15:32:27 +0100 Subject: [PATCH 169/174] Skip client metadata values that are None (#84293) --- homeassistant/components/cloud/http_api.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/cloud/http_api.py b/homeassistant/components/cloud/http_api.py index 76d8bea1664..4345afae746 100644 --- a/homeassistant/components/cloud/http_api.py +++ b/homeassistant/components/cloud/http_api.py @@ -226,14 +226,16 @@ class CloudRegisterView(HomeAssistantView): client_metadata = None - if location_info := await async_detect_location_info( - async_get_clientsession(hass) - ): - client_metadata = { - "NC_COUNTRY_CODE": location_info.country_code, - "NC_REGION_CODE": location_info.region_code, - "NC_ZIP_CODE": location_info.zip_code, - } + if ( + location_info := await async_detect_location_info( + async_get_clientsession(hass) + ) + ) and location_info.country_code is not None: + client_metadata = {"NC_COUNTRY_CODE": location_info.country_code} + if location_info.region_code is not None: + client_metadata["NC_REGION_CODE"] = location_info.region_code + if location_info.zip_code is not None: + client_metadata["NC_ZIP_CODE"] = location_info.zip_code async with async_timeout.timeout(REQUEST_TIMEOUT): await cloud.auth.async_register( From 7f0a9a58979dcb924d6ff0096fb377d31e8d8719 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Hjelseth=20H=C3=B8yer?= Date: Wed, 21 Dec 2022 05:59:52 +0100 Subject: [PATCH 170/174] Improve Tibber rt Pulse streaming (#84313) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Improve rt Pulse streaming Signed-off-by: Daniel Hjelseth Høyer Signed-off-by: Daniel Hjelseth Høyer --- homeassistant/components/tibber/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/tibber/manifest.json b/homeassistant/components/tibber/manifest.json index 814f36e018c..cc296d06ea8 100644 --- a/homeassistant/components/tibber/manifest.json +++ b/homeassistant/components/tibber/manifest.json @@ -3,7 +3,7 @@ "domain": "tibber", "name": "Tibber", "documentation": "https://www.home-assistant.io/integrations/tibber", - "requirements": ["pyTibber==0.26.5"], + "requirements": ["pyTibber==0.26.6"], "codeowners": ["@danielhiversen"], "quality_scale": "silver", "config_flow": true, diff --git a/requirements_all.txt b/requirements_all.txt index 184accfaedd..a3bd7cfe7f0 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1432,7 +1432,7 @@ pyRFXtrx==0.30.0 pySwitchmate==0.5.1 # homeassistant.components.tibber -pyTibber==0.26.5 +pyTibber==0.26.6 # homeassistant.components.dlink pyW215==0.7.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 374d7b916e1..b0fc670a396 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1032,7 +1032,7 @@ pyMetno==0.9.0 pyRFXtrx==0.30.0 # homeassistant.components.tibber -pyTibber==0.26.5 +pyTibber==0.26.6 # homeassistant.components.nextbus py_nextbusnext==0.1.5 From 7de11cfcfaee2958e51e8ee3a2c34327462a2a0d Mon Sep 17 00:00:00 2001 From: Michael <35783820+mib1185@users.noreply.github.com> Date: Wed, 21 Dec 2022 01:32:00 +0100 Subject: [PATCH 171/174] Fix attribute check in prometheus exporter (#84321) * fix attribute check * add test --- .../components/prometheus/__init__.py | 2 +- tests/components/prometheus/test_init.py | 23 +++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/prometheus/__init__.py b/homeassistant/components/prometheus/__init__.py index c1573755e11..954dcfd1eb1 100644 --- a/homeassistant/components/prometheus/__init__.py +++ b/homeassistant/components/prometheus/__init__.py @@ -394,7 +394,7 @@ class PrometheusMetrics: metric.labels(**self._labels(state)).set(value) def _handle_climate_temp(self, state, attr, metric_name, metric_description): - if temp := state.attributes.get(attr): + if (temp := state.attributes.get(attr)) is not None: if self._climate_units == TEMP_FAHRENHEIT: temp = TemperatureConverter.convert(temp, TEMP_FAHRENHEIT, TEMP_CELSIUS) metric = self._metric( diff --git a/tests/components/prometheus/test_init.py b/tests/components/prometheus/test_init.py index 1730e6c3f23..febbc72ec31 100644 --- a/tests/components/prometheus/test_init.py +++ b/tests/components/prometheus/test_init.py @@ -293,6 +293,12 @@ async def test_climate(client, climate_entities): 'friendly_name="Ecobee"} 24.0' in body ) + assert ( + 'climate_target_temperature_celsius{domain="climate",' + 'entity="climate.fritzdect",' + 'friendly_name="Fritz!DECT"} 0.0' in body + ) + @pytest.mark.parametrize("namespace", [""]) async def test_humidifier(client, humidifier_entities): @@ -1001,6 +1007,23 @@ async def climate_fixture(hass, registry): data["climate_2"] = climate_2 data["climate_2_attributes"] = climate_2_attributes + climate_3 = registry.async_get_or_create( + domain=climate.DOMAIN, + platform="test", + unique_id="climate_3", + unit_of_measurement=TEMP_CELSIUS, + suggested_object_id="fritzdect", + original_name="Fritz!DECT", + ) + climate_3_attributes = { + ATTR_TEMPERATURE: 0, + ATTR_CURRENT_TEMPERATURE: 22, + ATTR_HVAC_ACTION: climate.HVACAction.OFF, + } + set_state_with_entry(hass, climate_3, climate.HVACAction.OFF, climate_3_attributes) + data["climate_3"] = climate_3 + data["climate_3_attributes"] = climate_3_attributes + await hass.async_block_till_done() return data From 115dc4fa4be8ab5dd135c5f2236d17ba8f949e50 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 21 Dec 2022 09:15:53 -0500 Subject: [PATCH 172/174] Bumped version to 2022.12.8 --- homeassistant/const.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 1324526804f..16015beec72 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -8,7 +8,7 @@ from .backports.enum import StrEnum APPLICATION_NAME: Final = "HomeAssistant" MAJOR_VERSION: Final = 2022 MINOR_VERSION: Final = 12 -PATCH_VERSION: Final = "7" +PATCH_VERSION: Final = "8" __short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__: Final = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 9, 0) diff --git a/pyproject.toml b/pyproject.toml index 68307acc33c..8cb74b24793 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "homeassistant" -version = "2022.12.7" +version = "2022.12.8" license = {text = "Apache-2.0"} description = "Open-source home automation platform running on Python 3." readme = "README.rst" From 99b4c9d5f455a26a69b1c55cabada073b4bcf1a5 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 21 Dec 2022 09:20:56 -0500 Subject: [PATCH 173/174] Isort --- tests/components/bluetooth/test_manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/components/bluetooth/test_manager.py b/tests/components/bluetooth/test_manager.py index 7c8f9b93af7..80e2efff505 100644 --- a/tests/components/bluetooth/test_manager.py +++ b/tests/components/bluetooth/test_manager.py @@ -10,8 +10,8 @@ import pytest from homeassistant.components import bluetooth from homeassistant.components.bluetooth import ( - BaseHaScanner, BaseHaRemoteScanner, + BaseHaScanner, BluetoothChange, BluetoothScanningMode, BluetoothServiceInfo, From 97efcab310d4ef3db2c875458e5ea1f0d0883d20 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 21 Dec 2022 09:37:35 -0500 Subject: [PATCH 174/174] flake8 --- tests/components/bluetooth/test_manager.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/components/bluetooth/test_manager.py b/tests/components/bluetooth/test_manager.py index 80e2efff505..3ec77a2ecd9 100644 --- a/tests/components/bluetooth/test_manager.py +++ b/tests/components/bluetooth/test_manager.py @@ -27,7 +27,6 @@ from homeassistant.components.bluetooth.manager import ( FALLBACK_MAXIMUM_STALE_ADVERTISEMENT_SECONDS, ) from homeassistant.core import HomeAssistant, callback -from homeassistant.helpers.json import json_loads from homeassistant.setup import async_setup_component from homeassistant.util import dt as dt_util @@ -40,7 +39,7 @@ from . import ( inject_advertisement_with_time_and_source_connectable, ) -from tests.common import async_fire_time_changed, load_fixture +from tests.common import async_fire_time_changed @pytest.fixture