From c1a1a38ffc5711d5a2fddda11a18b570f2268c3f Mon Sep 17 00:00:00 2001 From: HighOnMikey <3639519+HighOnMikey@users.noreply.github.com> Date: Sun, 23 May 2021 12:47:19 -0500 Subject: [PATCH] Improve legacy support for Hunter Douglas PowerView (#50918) Co-authored-by: J. Nick Koston --- .../hunterdouglas_powerview/__init__.py | 42 +++++------ .../hunterdouglas_powerview/const.py | 21 ++++-- .../hunterdouglas_powerview/entity.py | 21 +++--- .../test_config_flow.py | 75 ++++++++++++++++++- .../hunterdouglas_powerview/fwversion.json | 10 +++ 5 files changed, 126 insertions(+), 43 deletions(-) create mode 100644 tests/fixtures/hunterdouglas_powerview/fwversion.json diff --git a/homeassistant/components/hunterdouglas_powerview/__init__.py b/homeassistant/components/hunterdouglas_powerview/__init__.py index a25d24fef81..d9a52446028 100644 --- a/homeassistant/components/hunterdouglas_powerview/__init__.py +++ b/homeassistant/components/hunterdouglas_powerview/__init__.py @@ -3,6 +3,7 @@ from datetime import timedelta import logging from aiopvapi.helpers.aiorequest import AioRequest +from aiopvapi.helpers.api_base import ApiEntryPoint from aiopvapi.helpers.constants import ATTR_ID from aiopvapi.helpers.tools import base64_to_unicode from aiopvapi.rooms import Rooms @@ -20,7 +21,9 @@ import homeassistant.helpers.config_validation as cv from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from .const import ( + API_PATH_FWVERSION, COORDINATOR, + DEFAULT_LEGACY_MAINPROCESSOR, DEVICE_FIRMWARE, DEVICE_INFO, DEVICE_MAC_ADDRESS, @@ -29,24 +32,18 @@ from .const import ( DEVICE_REVISION, DEVICE_SERIAL_NUMBER, DOMAIN, - FIRMWARE_BUILD, - FIRMWARE_IN_USERDATA, - FIRMWARE_SUB_REVISION, + FIRMWARE, + FIRMWARE_MAINPROCESSOR, + FIRMWARE_NAME, + FIRMWARE_REVISION, HUB_EXCEPTIONS, HUB_NAME, - LEGACY_DEVICE_BUILD, - LEGACY_DEVICE_MODEL, - LEGACY_DEVICE_REVISION, - LEGACY_DEVICE_SUB_REVISION, MAC_ADDRESS_IN_USERDATA, - MAINPROCESSOR_IN_USERDATA_FIRMWARE, - MODEL_IN_MAINPROCESSOR, PV_API, PV_ROOM_DATA, PV_SCENE_DATA, PV_SHADE_DATA, PV_SHADES, - REVISION_IN_MAINPROCESSOR, ROOM_DATA, SCENE_DATA, SERIAL_NUMBER_IN_USERDATA, @@ -137,26 +134,25 @@ async def async_get_device_info(pv_request): resources = await userdata.get_resources() userdata_data = resources[USER_DATA] - if FIRMWARE_IN_USERDATA in userdata_data: - main_processor_info = userdata_data[FIRMWARE_IN_USERDATA][ - MAINPROCESSOR_IN_USERDATA_FIRMWARE - ] - else: + if FIRMWARE in userdata_data: + main_processor_info = userdata_data[FIRMWARE][FIRMWARE_MAINPROCESSOR] + elif userdata_data: # Legacy devices - main_processor_info = { - REVISION_IN_MAINPROCESSOR: LEGACY_DEVICE_REVISION, - FIRMWARE_SUB_REVISION: LEGACY_DEVICE_SUB_REVISION, - FIRMWARE_BUILD: LEGACY_DEVICE_BUILD, - MODEL_IN_MAINPROCESSOR: LEGACY_DEVICE_MODEL, - } + fwversion = ApiEntryPoint(pv_request, API_PATH_FWVERSION) + resources = await fwversion.get_resources() + + if FIRMWARE in resources: + main_processor_info = resources[FIRMWARE][FIRMWARE_MAINPROCESSOR] + else: + main_processor_info = DEFAULT_LEGACY_MAINPROCESSOR return { DEVICE_NAME: base64_to_unicode(userdata_data[HUB_NAME]), DEVICE_MAC_ADDRESS: userdata_data[MAC_ADDRESS_IN_USERDATA], DEVICE_SERIAL_NUMBER: userdata_data[SERIAL_NUMBER_IN_USERDATA], - DEVICE_REVISION: main_processor_info[REVISION_IN_MAINPROCESSOR], + DEVICE_REVISION: main_processor_info[FIRMWARE_REVISION], DEVICE_FIRMWARE: main_processor_info, - DEVICE_MODEL: main_processor_info[MODEL_IN_MAINPROCESSOR], + DEVICE_MODEL: main_processor_info[FIRMWARE_NAME], } diff --git a/homeassistant/components/hunterdouglas_powerview/const.py b/homeassistant/components/hunterdouglas_powerview/const.py index e83a9d8945b..e827b055995 100644 --- a/homeassistant/components/hunterdouglas_powerview/const.py +++ b/homeassistant/components/hunterdouglas_powerview/const.py @@ -19,14 +19,11 @@ USER_DATA = "userData" MAC_ADDRESS_IN_USERDATA = "macAddress" SERIAL_NUMBER_IN_USERDATA = "serialNumber" -FIRMWARE_IN_USERDATA = "firmware" -MAINPROCESSOR_IN_USERDATA_FIRMWARE = "mainProcessor" -REVISION_IN_MAINPROCESSOR = "revision" -MODEL_IN_MAINPROCESSOR = "name" HUB_NAME = "hubName" -FIRMWARE_IN_SHADE = "firmware" - +FIRMWARE = "firmware" +FIRMWARE_MAINPROCESSOR = "mainProcessor" +FIRMWARE_NAME = "name" FIRMWARE_REVISION = "revision" FIRMWARE_SUB_REVISION = "subRevision" FIRMWARE_BUILD = "build" @@ -70,4 +67,14 @@ HUB_EXCEPTIONS = (ServerDisconnectedError, asyncio.TimeoutError, PvApiConnection LEGACY_DEVICE_SUB_REVISION = 1 LEGACY_DEVICE_REVISION = 0 LEGACY_DEVICE_BUILD = 0 -LEGACY_DEVICE_MODEL = "PV Hub1.0" +LEGACY_DEVICE_MODEL = "PowerView Hub" + +DEFAULT_LEGACY_MAINPROCESSOR = { + FIRMWARE_REVISION: LEGACY_DEVICE_REVISION, + FIRMWARE_SUB_REVISION: LEGACY_DEVICE_SUB_REVISION, + FIRMWARE_BUILD: LEGACY_DEVICE_BUILD, + FIRMWARE_NAME: LEGACY_DEVICE_MODEL, +} + + +API_PATH_FWVERSION = "api/fwversion" diff --git a/homeassistant/components/hunterdouglas_powerview/entity.py b/homeassistant/components/hunterdouglas_powerview/entity.py index 679e55e806c..bf0d5d564ff 100644 --- a/homeassistant/components/hunterdouglas_powerview/entity.py +++ b/homeassistant/components/hunterdouglas_powerview/entity.py @@ -12,8 +12,8 @@ from .const import ( DEVICE_NAME, DEVICE_SERIAL_NUMBER, DOMAIN, + FIRMWARE, FIRMWARE_BUILD, - FIRMWARE_IN_SHADE, FIRMWARE_REVISION, FIRMWARE_SUB_REVISION, MANUFACTURER, @@ -71,20 +71,21 @@ class ShadeEntity(HDEntity): "name": self._shade_name, "suggested_area": self._room_name, "manufacturer": MANUFACTURER, + "model": self._shade.raw_data[ATTR_TYPE], "via_device": (DOMAIN, self._device_info[DEVICE_SERIAL_NUMBER]), } - if FIRMWARE_IN_SHADE not in self._shade.raw_data: - return device_info - - firmware = self._shade.raw_data[FIRMWARE_IN_SHADE] - sw_version = f"{firmware[FIRMWARE_REVISION]}.{firmware[FIRMWARE_SUB_REVISION]}.{firmware[FIRMWARE_BUILD]}" - model = self._shade.raw_data[ATTR_TYPE] for shade in self._shade.shade_types: - if shade.shade_type == model: - model = shade.description + if shade.shade_type == device_info["model"]: + device_info["model"] = shade.description break + if FIRMWARE not in self._shade.raw_data: + return device_info + + firmware = self._shade.raw_data[FIRMWARE] + sw_version = f"{firmware[FIRMWARE_REVISION]}.{firmware[FIRMWARE_SUB_REVISION]}.{firmware[FIRMWARE_BUILD]}" + device_info["sw_version"] = sw_version - device_info["model"] = model + return device_info diff --git a/tests/components/hunterdouglas_powerview/test_config_flow.py b/tests/components/hunterdouglas_powerview/test_config_flow.py index 712ebea64a9..6423daff3f8 100644 --- a/tests/components/hunterdouglas_powerview/test_config_flow.py +++ b/tests/components/hunterdouglas_powerview/test_config_flow.py @@ -41,12 +41,34 @@ def _get_mock_powerview_userdata(userdata=None, get_resources=None): if not userdata: userdata = json.loads(load_fixture("hunterdouglas_powerview/userdata.json")) if get_resources: - type(mock_powerview_userdata).get_resources = AsyncMock( + mock_powerview_userdata.get_resources = AsyncMock(side_effect=get_resources) + else: + mock_powerview_userdata.get_resources = AsyncMock(return_value=userdata) + return mock_powerview_userdata + + +def _get_mock_powerview_legacy_userdata(userdata=None, get_resources=None): + mock_powerview_userdata_legacy = MagicMock() + if not userdata: + userdata = json.loads(load_fixture("hunterdouglas_powerview/userdata_v1.json")) + if get_resources: + mock_powerview_userdata_legacy.get_resources = AsyncMock( side_effect=get_resources ) else: - type(mock_powerview_userdata).get_resources = AsyncMock(return_value=userdata) - return mock_powerview_userdata + mock_powerview_userdata_legacy.get_resources = AsyncMock(return_value=userdata) + return mock_powerview_userdata_legacy + + +def _get_mock_powerview_fwversion(fwversion=None, get_resources=None): + mock_powerview_fwversion = MagicMock() + if not fwversion: + fwversion = json.loads(load_fixture("hunterdouglas_powerview/fwversion.json")) + if get_resources: + mock_powerview_fwversion.get_resources = AsyncMock(side_effect=get_resources) + else: + mock_powerview_fwversion.get_resources = AsyncMock(return_value=fwversion) + return mock_powerview_fwversion async def test_user_form(hass): @@ -92,6 +114,53 @@ async def test_user_form(hass): assert result4["type"] == "abort" +async def test_user_form_legacy(hass): + """Test we get the user form with a legacy device.""" + await setup.async_setup_component(hass, "persistent_notification", {}) + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result["type"] == "form" + assert result["errors"] == {} + + mock_powerview_userdata = _get_mock_powerview_legacy_userdata() + mock_powerview_fwversion = _get_mock_powerview_fwversion() + with patch( + "homeassistant.components.hunterdouglas_powerview.UserData", + return_value=mock_powerview_userdata, + ), patch( + "homeassistant.components.hunterdouglas_powerview.ApiEntryPoint", + return_value=mock_powerview_fwversion, + ), patch( + "homeassistant.components.hunterdouglas_powerview.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + {"host": "1.2.3.4"}, + ) + await hass.async_block_till_done() + + assert result2["type"] == "create_entry" + assert result2["title"] == "PowerView Hub Gen 1" + assert result2["data"] == { + "host": "1.2.3.4", + } + assert len(mock_setup_entry.mock_calls) == 1 + + result3 = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result3["type"] == "form" + assert result3["errors"] == {} + + result4 = await hass.config_entries.flow.async_configure( + result3["flow_id"], + {"host": "1.2.3.4"}, + ) + assert result4["type"] == "abort" + + @pytest.mark.parametrize("source, discovery_info", DISCOVERY_DATA) async def test_form_homekit_and_dhcp_cannot_connect(hass, source, discovery_info): """Test we get the form with homekit and dhcp source.""" diff --git a/tests/fixtures/hunterdouglas_powerview/fwversion.json b/tests/fixtures/hunterdouglas_powerview/fwversion.json new file mode 100644 index 00000000000..96d301802ff --- /dev/null +++ b/tests/fixtures/hunterdouglas_powerview/fwversion.json @@ -0,0 +1,10 @@ +{ + "firmware": { + "mainProcessor": { + "name": "PowerView Hub", + "revision": 1, + "subRevision": 1, + "build": 857 + } + } + } \ No newline at end of file