Treat unknown homematicip_cloud device types as devices (#182394)

Co-authored-by: Markus Tuominen <3738613+Markus98@users.noreply.github.com>
This commit is contained in:
Christian Lackas
2026-09-24 17:23:01 +03:00
committed by GitHub
co-authored by Markus Tuominen
parent 6f36091610
commit 620a329431
3 changed files with 88 additions and 6 deletions
@@ -5,7 +5,7 @@ import logging
from typing import TYPE_CHECKING, Any, override
from homematicip.base.functionalChannels import FunctionalChannel
from homematicip.device import Device
from homematicip.device import BaseDevice
from homematicip.group import Group
from homeassistant.const import ATTR_ID
@@ -133,7 +133,7 @@ class HomematicipGenericEntity(Entity):
def device_info(self) -> DeviceInfo | None:
"""Return device specific attributes."""
# Only physical devices should be HA devices.
if isinstance(self._device, Device):
if isinstance(self._device, BaseDevice):
device_id = str(self._device.id)
home_id = str(self._device.homeId)
@@ -152,7 +152,7 @@ class HomematicipGenericEntity(Entity):
# Serial numbers of Homematic IP device
(DOMAIN, device_id)
},
manufacturer=self._device.oem,
manufacturer=getattr(self._device, "oem", None),
model=self._device.modelType,
name=device_name,
sw_version=self._device.firmwareVersion,
@@ -325,13 +325,14 @@ class HomematicipGenericEntity(Entity):
@override
def available(self) -> bool:
"""Return if entity is available."""
return not self._device.unreach
# BaseDevice, the fallback for an unknown device type, has no unreach.
return not getattr(self._device, "unreach", False)
@property
@override
def unique_id(self) -> str:
"""Return a unique ID."""
if not isinstance(self._device, Device):
if not isinstance(self._device, BaseDevice):
return f"{self._device.id}_{self._feature_id}"
channel_index = self.get_channel_index()
return f"{self._device.id}_{channel_index}_{self._feature_id}"
@@ -352,7 +353,7 @@ class HomematicipGenericEntity(Entity):
"""Return the state attributes of the generic entity."""
state_attr = {}
if isinstance(self._device, Device):
if isinstance(self._device, BaseDevice):
for attr, attr_key in DEVICE_ATTRIBUTES.items():
if attr_value := getattr(self._device, attr, None):
state_attr[attr_key] = attr_value
@@ -70,6 +70,53 @@ async def default_mock_hap_factory_fixture(
return HomeFactory(hass, mock_connection, hmip_config_entry)
@pytest.fixture(name="unknown_type_device_data")
def unknown_type_device_data_fixture() -> dict[str, Any]:
"""Return fixture data for a device whose type the library does not know.
The type is invented so that it stays unknown as devices get added.
"""
device_id = "3014F711000000000UNKNOWN"
return {
"connectionType": "HMIP_RF",
"deviceArchetype": "HMIP",
"firmwareVersion": "1.0.10",
"functionalChannels": {
"0": {
"deviceId": device_id,
"functionalChannelType": "DEVICE_BASE",
"groupIndex": 0,
"groups": [],
"index": 0,
"label": "",
},
"1": {
"deviceId": device_id,
"functionalChannelType": "SINGLE_KEY_CHANNEL",
"groupIndex": 1,
"groups": [],
"index": 1,
"label": "",
},
"2": {
"deviceId": device_id,
"functionalChannelType": "SINGLE_KEY_CHANNEL",
"groupIndex": 2,
"groups": [],
"index": 2,
"label": "",
},
},
"homeId": "00000000-0000-0000-0000-000000000001",
"id": device_id,
"label": "Unknown Device",
"lastStatusUpdate": 1614066137987,
"modelType": "HmIP-UNKNOWN",
"permanentlyReachable": True,
"type": "SOME_FUTURE_DEVICE",
}
@pytest.fixture(name="full_flush_lock_controller_device_data")
def full_flush_lock_controller_device_data_fixture() -> dict[str, Any]:
"""Return fixture data for an HmIP-FLC device."""
@@ -1,5 +1,6 @@
"""Common tests for HomematicIP devices."""
from typing import Any
from unittest.mock import patch
from homematicip.base.enums import EventType
@@ -330,3 +331,36 @@ async def test_hmip_child_device_links_to_access_point(
assert access_point_device is not None
assert child_device.via_device_id == access_point_device.id
async def test_hmip_unknown_device_type(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
device_registry: dr.DeviceRegistry,
default_mock_hap_factory: HomeFactory,
unknown_type_device_data: dict[str, Any],
) -> None:
"""Test a device whose type the library does not know yet."""
await default_mock_hap_factory.async_get_mock_hap(
test_devices=["Unknown Device"], extra_devices=[unknown_type_device_data]
)
entities = [
entry
for entry in entity_registry.entities.values()
if entry.unique_id.startswith(unknown_type_device_data["id"])
]
assert sorted(entry.unique_id for entry in entities) == [
"3014F711000000000UNKNOWN_1_button",
"3014F711000000000UNKNOWN_2_button",
]
device = device_registry.async_get(entities[0].device_id)
assert device is not None
assert (DOMAIN, unknown_type_device_data["id"]) in device.identifiers
assert {entry.device_id for entry in entities} == {device.id}
for entry in entities:
state = hass.states.get(entry.entity_id)
assert state is not None
assert state.state != STATE_UNAVAILABLE