Adapt nobo_hub to set via_device_id in DeviceInfo (#177737)

This commit is contained in:
Erik Montnemery
2026-08-04 14:21:26 +02:00
committed by Bram Kragten
parent e9c4db7481
commit 8d71ae4f5a
5 changed files with 79 additions and 24 deletions
+16 -8
View File
@@ -29,7 +29,6 @@ from homeassistant.util import dt as dt_util
from . import NoboHubConfigEntry
from .const import (
ATTR_SERIAL,
ATTR_TEMP_COMFORT_C,
ATTR_TEMP_ECO_C,
CONF_OVERRIDE_TYPE,
@@ -78,7 +77,8 @@ async def async_setup_entry(
new_zones = [zone_id for zone_id in hub.zones if zone_id not in known_zones]
known_zones.update(new_zones)
async_add_entities(
NoboZone(zone_id, hub, override_type) for zone_id in new_zones
NoboZone(hass, zone_id, hub, override_type, config_entry.entry_id)
for zone_id in new_zones
)
_add_zones(hub)
@@ -106,17 +106,25 @@ class NoboZone(NoboBaseEntity, ClimateEntity):
# Need to poll to get preset change when in HVACMode.AUTO
_attr_should_poll = True
def __init__(self, zone_id: str, hub: nobo, override_type: str) -> None:
def __init__(
self,
hass: HomeAssistant,
zone_id: str,
hub: nobo,
override_type: str,
entry_id: str,
) -> None:
"""Initialize the climate device."""
super().__init__(hub)
super().__init__(hass, hub, entry_id)
self._id = zone_id
self._attr_unique_id = f"{hub.hub_serial}:{zone_id}"
self._override_type = override_type
zone_name = hub.zones[zone_id][ATTR_NAME]
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, f"{hub.hub_serial}:{zone_id}")},
name=hub.zones[zone_id][ATTR_NAME],
via_device=(DOMAIN, hub.hub_info[ATTR_SERIAL]),
suggested_area=hub.zones[zone_id][ATTR_NAME],
identifiers={(DOMAIN, self._attr_unique_id)},
name=zone_name,
via_device_id=self._hub_device_id,
suggested_area=zone_name,
)
self._read_state()
+8 -2
View File
@@ -4,9 +4,12 @@ from typing import override
from pynobo import nobo
from homeassistant.core import callback
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import device_registry as dr
from homeassistant.helpers.entity import Entity
from .const import ATTR_SERIAL, DOMAIN
class NoboBaseEntity(Entity):
"""Base class for Nobø Ecohub entities."""
@@ -14,10 +17,13 @@ class NoboBaseEntity(Entity):
_attr_has_entity_name = True
_attr_should_poll = False
def __init__(self, hub: nobo) -> None:
def __init__(self, hass: HomeAssistant, hub: nobo, entry_id: str) -> None:
"""Initialize the entity."""
self._nobo = hub
self._attr_available = hub.connected
self._hub_device_id = dr.async_get_device_id_by_identifier(
hass, (DOMAIN, hub.hub_info[ATTR_SERIAL]), config_entry_id=entry_id
)
@override
async def async_added_to_hass(self) -> None:
+20 -10
View File
@@ -14,7 +14,6 @@ from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from . import NoboHubConfigEntry
from .const import (
ATTR_HARDWARE_VERSION,
ATTR_SERIAL,
ATTR_SOFTWARE_VERSION,
CONF_OVERRIDE_TYPE,
DOMAIN,
@@ -40,7 +39,9 @@ async def async_setup_entry(
else nobo.API.OVERRIDE_TYPE_CONSTANT
)
async_add_entities([NoboGlobalSelector(hub, override_type)], True)
async_add_entities(
[NoboGlobalSelector(hass, hub, override_type, config_entry.entry_id)], True
)
known_zones: set[str] = set()
@@ -56,7 +57,11 @@ async def async_setup_entry(
new_zones = [zone_id for zone_id in hub.zones if zone_id not in known_zones]
known_zones.update(new_zones)
async_add_entities(
(NoboProfileSelector(zone_id, hub) for zone_id in new_zones), True
(
NoboProfileSelector(hass, zone_id, hub, config_entry.entry_id)
for zone_id in new_zones
),
True,
)
_add_profiles(hub)
@@ -77,9 +82,11 @@ class NoboGlobalSelector(NoboBaseEntity, SelectEntity):
_attr_options = list(_modes.values())
_attr_current_option: str | None = None
def __init__(self, hub: nobo, override_type: str) -> None:
def __init__(
self, hass: HomeAssistant, hub: nobo, override_type: str, entry_id: str
) -> None:
"""Initialize the global override selector."""
super().__init__(hub)
super().__init__(hass, hub, entry_id)
self._attr_unique_id = hub.hub_serial
self._override_type = override_type
self._attr_device_info = DeviceInfo(
@@ -126,18 +133,21 @@ class NoboProfileSelector(NoboBaseEntity, SelectEntity):
_attr_translation_key = "week_profile"
_attr_current_option: str | None = None
def __init__(self, zone_id: str, hub: nobo) -> None:
def __init__(
self, hass: HomeAssistant, zone_id: str, hub: nobo, entry_id: str
) -> None:
"""Initialize the week profile selector."""
super().__init__(hub)
super().__init__(hass, hub, entry_id)
self._id = zone_id
self._profiles: dict[str, str] = {}
self._attr_options: list[str] = []
self._attr_unique_id = f"{hub.hub_serial}:{zone_id}:profile"
zone_name = hub.zones[zone_id][ATTR_NAME]
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, f"{hub.hub_serial}:{zone_id}")},
name=hub.zones[zone_id][ATTR_NAME],
via_device=(DOMAIN, hub.hub_info[ATTR_SERIAL]),
suggested_area=hub.zones[zone_id][ATTR_NAME],
name=zone_name,
via_device_id=self._hub_device_id,
suggested_area=zone_name,
)
@override
+7 -4
View File
@@ -48,7 +48,8 @@ async def async_setup_entry(
]
known_components.update(new_components)
async_add_entities(
NoboTemperatureSensor(serial, hub) for serial in new_components
NoboTemperatureSensor(hass, serial, hub, config_entry.entry_id)
for serial in new_components
)
_add_sensors(hub)
@@ -64,9 +65,11 @@ class NoboTemperatureSensor(NoboBaseEntity, SensorEntity):
_attr_state_class = SensorStateClass.MEASUREMENT
_attr_suggested_display_precision = 1
def __init__(self, serial: str, hub: nobo) -> None:
def __init__(
self, hass: HomeAssistant, serial: str, hub: nobo, entry_id: str
) -> None:
"""Initialize the temperature sensor."""
super().__init__(hub)
super().__init__(hass, hub, entry_id)
self._temperature: StateType = None
self._id = serial
component = hub.components[self._id]
@@ -81,7 +84,7 @@ class NoboTemperatureSensor(NoboBaseEntity, SensorEntity):
name=component[ATTR_NAME],
manufacturer=NOBO_MANUFACTURER,
model=component[ATTR_MODEL].name,
via_device=(DOMAIN, hub.hub_info[ATTR_SERIAL]),
via_device_id=self._hub_device_id,
suggested_area=suggested_area,
)
self._read_state()
+28
View File
@@ -262,6 +262,34 @@ async def test_setup_registers_hub_device_with_mac(
}
@pytest.mark.parametrize(
"platforms", [[Platform.CLIMATE, Platform.SENSOR]], indirect=True
)
@pytest.mark.usefixtures("init_integration")
async def test_zone_and_component_devices_link_to_hub(
device_registry: dr.DeviceRegistry,
mock_config_entry: MockConfigEntry,
) -> None:
"""Zone and component devices reference the hub device via via_device_id."""
entry_id = mock_config_entry.entry_id
hub_device = device_registry.async_get_device_by_identifier(
(DOMAIN, SERIAL), entry_id
)
assert hub_device is not None
zone_device = device_registry.async_get_device_by_identifier(
(DOMAIN, f"{SERIAL}:1"), entry_id
)
assert zone_device is not None
assert zone_device.via_device_id == hub_device.id
component_device = device_registry.async_get_device_by_identifier(
(DOMAIN, "200000059091"), entry_id
)
assert component_device is not None
assert component_device.via_device_id == hub_device.id
@pytest.mark.usefixtures("init_integration")
async def test_entity_available_when_hub_connected(hass: HomeAssistant) -> None:
"""Entities are available when the hub reports connected."""