From 7af0f16f79e1155723f095171b8796b227a9ebf1 Mon Sep 17 00:00:00 2001 From: Poltorak Serguei <poltorak@alsenet.com> Date: Wed, 2 Nov 2022 19:53:10 +0300 Subject: [PATCH] Rework Z-Wave.Me to group entities of one physical devices (#78553) Co-authored-by: Martin Hjelmare <marhje52@gmail.com> Co-authored-by: Dmitry Vlasov <kerbalspacema@gmail.com> --- homeassistant/components/zwave_me/__init__.py | 37 +++++++--- .../components/zwave_me/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- .../zwave_me/test_remove_stale_devices.py | 74 +++++++++++++++++++ 5 files changed, 102 insertions(+), 15 deletions(-) create mode 100644 tests/components/zwave_me/test_remove_stale_devices.py diff --git a/homeassistant/components/zwave_me/__init__.py b/homeassistant/components/zwave_me/__init__.py index 10312f36dfc..ed3d538d052 100644 --- a/homeassistant/components/zwave_me/__init__.py +++ b/homeassistant/components/zwave_me/__init__.py @@ -8,6 +8,8 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_TOKEN, CONF_URL from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import ConfigEntryNotReady +from homeassistant.helpers import device_registry +from homeassistant.helpers.device_registry import DeviceRegistry from homeassistant.helpers.dispatcher import async_dispatcher_connect, dispatcher_send from homeassistant.helpers.entity import DeviceInfo, Entity @@ -23,6 +25,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: controller = hass.data[DOMAIN][entry.entry_id] = ZWaveMeController(hass, entry) if await controller.async_establish_connection(): hass.async_create_task(async_setup_platforms(hass, entry, controller)) + registry = device_registry.async_get(hass) + controller.remove_stale_devices(registry) return True raise ConfigEntryNotReady() @@ -62,24 +66,33 @@ class ZWaveMeController: def add_device(self, device: ZWaveMeData) -> None: """Send signal to create device.""" - if device.deviceType in ZWAVE_ME_PLATFORMS and self.platforms_inited: - if device.id in self.device_ids: - dispatcher_send(self._hass, f"ZWAVE_ME_INFO_{device.id}", device) - else: - dispatcher_send( - self._hass, f"ZWAVE_ME_NEW_{device.deviceType.upper()}", device - ) - self.device_ids.add(device.id) - - def on_device_create(self, devices: list) -> None: + if device.id in self.device_ids: + dispatcher_send(self._hass, f"ZWAVE_ME_INFO_{device.id}", device) + else: + dispatcher_send( + self._hass, f"ZWAVE_ME_NEW_{device.deviceType.upper()}", device + ) + self.device_ids.add(device.id) + + def on_device_create(self, devices: list[ZWaveMeData]) -> None: """Create multiple devices.""" for device in devices: - self.add_device(device) + if device.deviceType in ZWAVE_ME_PLATFORMS and self.platforms_inited: + self.add_device(device) def on_device_update(self, new_info: ZWaveMeData) -> None: """Send signal to update device.""" dispatcher_send(self._hass, f"ZWAVE_ME_INFO_{new_info.id}", new_info) + def remove_stale_devices(self, registry: DeviceRegistry): + """Remove old-format devices in the registry.""" + for device_id in self.device_ids: + device = registry.async_get_device( + {(DOMAIN, f"{self.config.unique_id}-{device_id}")} + ) + if device is not None: + registry.async_remove_device(device.id) + async def async_setup_platforms( hass: HomeAssistant, entry: ConfigEntry, controller: ZWaveMeController @@ -113,7 +126,7 @@ class ZWaveMeEntity(Entity): def device_info(self) -> DeviceInfo: """Return device specific attributes.""" return DeviceInfo( - identifiers={(DOMAIN, self._attr_unique_id)}, + identifiers={(DOMAIN, self.device.deviceIdentifier)}, name=self._attr_name, manufacturer=self.device.manufacturer, sw_version=self.device.firmware, diff --git a/homeassistant/components/zwave_me/manifest.json b/homeassistant/components/zwave_me/manifest.json index 4ca933f43bc..9aeeb7b2a40 100644 --- a/homeassistant/components/zwave_me/manifest.json +++ b/homeassistant/components/zwave_me/manifest.json @@ -3,7 +3,7 @@ "name": "Z-Wave.Me", "documentation": "https://www.home-assistant.io/integrations/zwave_me", "iot_class": "local_push", - "requirements": ["zwave_me_ws==0.2.6", "url-normalize==1.4.3"], + "requirements": ["zwave_me_ws==0.3.0", "url-normalize==1.4.3"], "after_dependencies": ["zeroconf"], "zeroconf": [{ "type": "_hap._tcp.local.", "name": "*z.wave-me*" }], "config_flow": true, diff --git a/requirements_all.txt b/requirements_all.txt index dd54335dd71..29e9d79aa0d 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2640,4 +2640,4 @@ zm-py==0.5.2 zwave-js-server-python==0.43.0 # homeassistant.components.zwave_me -zwave_me_ws==0.2.6 +zwave_me_ws==0.3.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 3ec5e21e4f6..691b19b8b12 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1832,4 +1832,4 @@ zigpy==0.51.5 zwave-js-server-python==0.43.0 # homeassistant.components.zwave_me -zwave_me_ws==0.2.6 +zwave_me_ws==0.3.0 diff --git a/tests/components/zwave_me/test_remove_stale_devices.py b/tests/components/zwave_me/test_remove_stale_devices.py new file mode 100644 index 00000000000..484c38b9f33 --- /dev/null +++ b/tests/components/zwave_me/test_remove_stale_devices.py @@ -0,0 +1,74 @@ +"""Test the zwave_me removal of stale devices.""" +from unittest.mock import patch +import uuid + +import pytest as pytest +from zwave_me_ws import ZWaveMeData + +from homeassistant.components.zwave_me import ZWaveMePlatform +from homeassistant.const import CONF_TOKEN, CONF_URL +from homeassistant.core import HomeAssistant + +from tests.common import MockConfigEntry, mock_device_registry + +DEFAULT_DEVICE_INFO = ZWaveMeData( + id="DummyDevice", + deviceType=ZWaveMePlatform.BINARY_SENSOR, + title="DeviceDevice", + level=100, + deviceIdentifier="16-23", +) + + +@pytest.fixture +def device_reg(hass): + """Return an empty, loaded, registry.""" + return mock_device_registry(hass) + + +async def mock_connection(controller): + """Mock established connection and setting identifiers.""" + controller.on_new_device(DEFAULT_DEVICE_INFO) + return True + + +@pytest.mark.parametrize( + "identifier,should_exist", + [ + (DEFAULT_DEVICE_INFO.id, False), + (DEFAULT_DEVICE_INFO.deviceIdentifier, True), + ], +) +async def test_remove_stale_devices( + hass: HomeAssistant, device_reg, identifier, should_exist +): + """Test removing devices with old-format ids.""" + + config_entry = MockConfigEntry( + unique_id=uuid.uuid4(), + domain="zwave_me", + data={CONF_TOKEN: "test_token", CONF_URL: "http://test_test"}, + ) + config_entry.add_to_hass(hass) + device_reg.async_get_or_create( + config_entry_id=config_entry.entry_id, + connections={("mac", "12:34:56:AB:CD:EF")}, + identifiers={("zwave_me", f"{config_entry.unique_id}-{identifier}")}, + ) + with patch("zwave_me_ws.ZWaveMe.get_connection", mock_connection,), patch( + "homeassistant.components.zwave_me.async_setup_platforms", + ): + await hass.config_entries.async_setup(config_entry.entry_id) + assert ( + bool( + device_reg.async_get_device( + { + ( + "zwave_me", + f"{config_entry.unique_id}-{identifier}", + ) + } + ) + ) + == should_exist + ) -- GitLab