Improve legacy support for Hunter Douglas PowerView (#50918)
Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
parent
dbefa8fac0
commit
c1a1a38ffc
5 changed files with 126 additions and 43 deletions
|
@ -3,6 +3,7 @@ from datetime import timedelta
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from aiopvapi.helpers.aiorequest import AioRequest
|
from aiopvapi.helpers.aiorequest import AioRequest
|
||||||
|
from aiopvapi.helpers.api_base import ApiEntryPoint
|
||||||
from aiopvapi.helpers.constants import ATTR_ID
|
from aiopvapi.helpers.constants import ATTR_ID
|
||||||
from aiopvapi.helpers.tools import base64_to_unicode
|
from aiopvapi.helpers.tools import base64_to_unicode
|
||||||
from aiopvapi.rooms import Rooms
|
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 homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
|
API_PATH_FWVERSION,
|
||||||
COORDINATOR,
|
COORDINATOR,
|
||||||
|
DEFAULT_LEGACY_MAINPROCESSOR,
|
||||||
DEVICE_FIRMWARE,
|
DEVICE_FIRMWARE,
|
||||||
DEVICE_INFO,
|
DEVICE_INFO,
|
||||||
DEVICE_MAC_ADDRESS,
|
DEVICE_MAC_ADDRESS,
|
||||||
|
@ -29,24 +32,18 @@ from .const import (
|
||||||
DEVICE_REVISION,
|
DEVICE_REVISION,
|
||||||
DEVICE_SERIAL_NUMBER,
|
DEVICE_SERIAL_NUMBER,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
FIRMWARE_BUILD,
|
FIRMWARE,
|
||||||
FIRMWARE_IN_USERDATA,
|
FIRMWARE_MAINPROCESSOR,
|
||||||
FIRMWARE_SUB_REVISION,
|
FIRMWARE_NAME,
|
||||||
|
FIRMWARE_REVISION,
|
||||||
HUB_EXCEPTIONS,
|
HUB_EXCEPTIONS,
|
||||||
HUB_NAME,
|
HUB_NAME,
|
||||||
LEGACY_DEVICE_BUILD,
|
|
||||||
LEGACY_DEVICE_MODEL,
|
|
||||||
LEGACY_DEVICE_REVISION,
|
|
||||||
LEGACY_DEVICE_SUB_REVISION,
|
|
||||||
MAC_ADDRESS_IN_USERDATA,
|
MAC_ADDRESS_IN_USERDATA,
|
||||||
MAINPROCESSOR_IN_USERDATA_FIRMWARE,
|
|
||||||
MODEL_IN_MAINPROCESSOR,
|
|
||||||
PV_API,
|
PV_API,
|
||||||
PV_ROOM_DATA,
|
PV_ROOM_DATA,
|
||||||
PV_SCENE_DATA,
|
PV_SCENE_DATA,
|
||||||
PV_SHADE_DATA,
|
PV_SHADE_DATA,
|
||||||
PV_SHADES,
|
PV_SHADES,
|
||||||
REVISION_IN_MAINPROCESSOR,
|
|
||||||
ROOM_DATA,
|
ROOM_DATA,
|
||||||
SCENE_DATA,
|
SCENE_DATA,
|
||||||
SERIAL_NUMBER_IN_USERDATA,
|
SERIAL_NUMBER_IN_USERDATA,
|
||||||
|
@ -137,26 +134,25 @@ async def async_get_device_info(pv_request):
|
||||||
resources = await userdata.get_resources()
|
resources = await userdata.get_resources()
|
||||||
userdata_data = resources[USER_DATA]
|
userdata_data = resources[USER_DATA]
|
||||||
|
|
||||||
if FIRMWARE_IN_USERDATA in userdata_data:
|
if FIRMWARE in userdata_data:
|
||||||
main_processor_info = userdata_data[FIRMWARE_IN_USERDATA][
|
main_processor_info = userdata_data[FIRMWARE][FIRMWARE_MAINPROCESSOR]
|
||||||
MAINPROCESSOR_IN_USERDATA_FIRMWARE
|
elif userdata_data:
|
||||||
]
|
|
||||||
else:
|
|
||||||
# Legacy devices
|
# Legacy devices
|
||||||
main_processor_info = {
|
fwversion = ApiEntryPoint(pv_request, API_PATH_FWVERSION)
|
||||||
REVISION_IN_MAINPROCESSOR: LEGACY_DEVICE_REVISION,
|
resources = await fwversion.get_resources()
|
||||||
FIRMWARE_SUB_REVISION: LEGACY_DEVICE_SUB_REVISION,
|
|
||||||
FIRMWARE_BUILD: LEGACY_DEVICE_BUILD,
|
if FIRMWARE in resources:
|
||||||
MODEL_IN_MAINPROCESSOR: LEGACY_DEVICE_MODEL,
|
main_processor_info = resources[FIRMWARE][FIRMWARE_MAINPROCESSOR]
|
||||||
}
|
else:
|
||||||
|
main_processor_info = DEFAULT_LEGACY_MAINPROCESSOR
|
||||||
|
|
||||||
return {
|
return {
|
||||||
DEVICE_NAME: base64_to_unicode(userdata_data[HUB_NAME]),
|
DEVICE_NAME: base64_to_unicode(userdata_data[HUB_NAME]),
|
||||||
DEVICE_MAC_ADDRESS: userdata_data[MAC_ADDRESS_IN_USERDATA],
|
DEVICE_MAC_ADDRESS: userdata_data[MAC_ADDRESS_IN_USERDATA],
|
||||||
DEVICE_SERIAL_NUMBER: userdata_data[SERIAL_NUMBER_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_FIRMWARE: main_processor_info,
|
||||||
DEVICE_MODEL: main_processor_info[MODEL_IN_MAINPROCESSOR],
|
DEVICE_MODEL: main_processor_info[FIRMWARE_NAME],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -19,14 +19,11 @@ USER_DATA = "userData"
|
||||||
|
|
||||||
MAC_ADDRESS_IN_USERDATA = "macAddress"
|
MAC_ADDRESS_IN_USERDATA = "macAddress"
|
||||||
SERIAL_NUMBER_IN_USERDATA = "serialNumber"
|
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"
|
HUB_NAME = "hubName"
|
||||||
|
|
||||||
FIRMWARE_IN_SHADE = "firmware"
|
FIRMWARE = "firmware"
|
||||||
|
FIRMWARE_MAINPROCESSOR = "mainProcessor"
|
||||||
|
FIRMWARE_NAME = "name"
|
||||||
FIRMWARE_REVISION = "revision"
|
FIRMWARE_REVISION = "revision"
|
||||||
FIRMWARE_SUB_REVISION = "subRevision"
|
FIRMWARE_SUB_REVISION = "subRevision"
|
||||||
FIRMWARE_BUILD = "build"
|
FIRMWARE_BUILD = "build"
|
||||||
|
@ -70,4 +67,14 @@ HUB_EXCEPTIONS = (ServerDisconnectedError, asyncio.TimeoutError, PvApiConnection
|
||||||
LEGACY_DEVICE_SUB_REVISION = 1
|
LEGACY_DEVICE_SUB_REVISION = 1
|
||||||
LEGACY_DEVICE_REVISION = 0
|
LEGACY_DEVICE_REVISION = 0
|
||||||
LEGACY_DEVICE_BUILD = 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"
|
||||||
|
|
|
@ -12,8 +12,8 @@ from .const import (
|
||||||
DEVICE_NAME,
|
DEVICE_NAME,
|
||||||
DEVICE_SERIAL_NUMBER,
|
DEVICE_SERIAL_NUMBER,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
|
FIRMWARE,
|
||||||
FIRMWARE_BUILD,
|
FIRMWARE_BUILD,
|
||||||
FIRMWARE_IN_SHADE,
|
|
||||||
FIRMWARE_REVISION,
|
FIRMWARE_REVISION,
|
||||||
FIRMWARE_SUB_REVISION,
|
FIRMWARE_SUB_REVISION,
|
||||||
MANUFACTURER,
|
MANUFACTURER,
|
||||||
|
@ -71,20 +71,21 @@ class ShadeEntity(HDEntity):
|
||||||
"name": self._shade_name,
|
"name": self._shade_name,
|
||||||
"suggested_area": self._room_name,
|
"suggested_area": self._room_name,
|
||||||
"manufacturer": MANUFACTURER,
|
"manufacturer": MANUFACTURER,
|
||||||
|
"model": self._shade.raw_data[ATTR_TYPE],
|
||||||
"via_device": (DOMAIN, self._device_info[DEVICE_SERIAL_NUMBER]),
|
"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:
|
for shade in self._shade.shade_types:
|
||||||
if shade.shade_type == model:
|
if shade.shade_type == device_info["model"]:
|
||||||
model = shade.description
|
device_info["model"] = shade.description
|
||||||
break
|
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["sw_version"] = sw_version
|
||||||
device_info["model"] = model
|
|
||||||
return device_info
|
return device_info
|
||||||
|
|
|
@ -41,12 +41,34 @@ def _get_mock_powerview_userdata(userdata=None, get_resources=None):
|
||||||
if not userdata:
|
if not userdata:
|
||||||
userdata = json.loads(load_fixture("hunterdouglas_powerview/userdata.json"))
|
userdata = json.loads(load_fixture("hunterdouglas_powerview/userdata.json"))
|
||||||
if get_resources:
|
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
|
side_effect=get_resources
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
type(mock_powerview_userdata).get_resources = AsyncMock(return_value=userdata)
|
mock_powerview_userdata_legacy.get_resources = AsyncMock(return_value=userdata)
|
||||||
return mock_powerview_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):
|
async def test_user_form(hass):
|
||||||
|
@ -92,6 +114,53 @@ async def test_user_form(hass):
|
||||||
assert result4["type"] == "abort"
|
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)
|
@pytest.mark.parametrize("source, discovery_info", DISCOVERY_DATA)
|
||||||
async def test_form_homekit_and_dhcp_cannot_connect(hass, source, discovery_info):
|
async def test_form_homekit_and_dhcp_cannot_connect(hass, source, discovery_info):
|
||||||
"""Test we get the form with homekit and dhcp source."""
|
"""Test we get the form with homekit and dhcp source."""
|
||||||
|
|
10
tests/fixtures/hunterdouglas_powerview/fwversion.json
vendored
Normal file
10
tests/fixtures/hunterdouglas_powerview/fwversion.json
vendored
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"firmware": {
|
||||||
|
"mainProcessor": {
|
||||||
|
"name": "PowerView Hub",
|
||||||
|
"revision": 1,
|
||||||
|
"subRevision": 1,
|
||||||
|
"build": 857
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue