diff --git a/homeassistant/components/nobo_hub/climate.py b/homeassistant/components/nobo_hub/climate.py index 83c9a9966e03..40dc0ba20d71 100644 --- a/homeassistant/components/nobo_hub/climate.py +++ b/homeassistant/components/nobo_hub/climate.py @@ -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() diff --git a/homeassistant/components/nobo_hub/entity.py b/homeassistant/components/nobo_hub/entity.py index 7ad26edb40bf..4620b9c0596e 100644 --- a/homeassistant/components/nobo_hub/entity.py +++ b/homeassistant/components/nobo_hub/entity.py @@ -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: diff --git a/homeassistant/components/nobo_hub/select.py b/homeassistant/components/nobo_hub/select.py index ef6f7f29f365..3473bf57b064 100644 --- a/homeassistant/components/nobo_hub/select.py +++ b/homeassistant/components/nobo_hub/select.py @@ -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 diff --git a/homeassistant/components/nobo_hub/sensor.py b/homeassistant/components/nobo_hub/sensor.py index 371fa96e6823..885c80b40f06 100644 --- a/homeassistant/components/nobo_hub/sensor.py +++ b/homeassistant/components/nobo_hub/sensor.py @@ -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() diff --git a/tests/components/nobo_hub/test_init.py b/tests/components/nobo_hub/test_init.py index f89c50b5bffc..e36da7d6859f 100644 --- a/tests/components/nobo_hub/test_init.py +++ b/tests/components/nobo_hub/test_init.py @@ -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."""