Improve legacy support for Hunter Douglas PowerView (#50918)

Co-authored-by: J. Nick Koston <nick@koston.org>
pull/51014/head
HighOnMikey 2021-05-23 12:47:19 -05:00 committed by GitHub
parent dbefa8fac0
commit c1a1a38ffc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 126 additions and 43 deletions

View File

@ -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],
}

View File

@ -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"

View File

@ -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

View File

@ -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."""

View File

@ -0,0 +1,10 @@
{
"firmware": {
"mainProcessor": {
"name": "PowerView Hub",
"revision": 1,
"subRevision": 1,
"build": 857
}
}
}