From 1a28ea7ef3f59665b20eabbc3fcc122b8caa8d70 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Mon, 3 Aug 2026 15:57:08 +0200 Subject: [PATCH] Adapt bosch_alarm to set via_device_id in DeviceInfo (#177709) Co-authored-by: Markus Tuominen <3738613+Markus98@users.noreply.github.com> --- .../bosch_alarm/alarm_control_panel.py | 15 +++++- .../components/bosch_alarm/binary_sensor.py | 32 +++++++++---- .../components/bosch_alarm/entity.py | 47 ++++++++++++++++--- .../components/bosch_alarm/sensor.py | 8 +++- .../components/bosch_alarm/switch.py | 24 +++++++--- .../bosch_alarm/test_binary_sensor.py | 30 +++++++++++- 6 files changed, 130 insertions(+), 26 deletions(-) diff --git a/homeassistant/components/bosch_alarm/alarm_control_panel.py b/homeassistant/components/bosch_alarm/alarm_control_panel.py index 83c979fe5ec2..519986e473e5 100644 --- a/homeassistant/components/bosch_alarm/alarm_control_panel.py +++ b/homeassistant/components/bosch_alarm/alarm_control_panel.py @@ -26,9 +26,11 @@ async def async_setup_entry( async_add_entities( AreaAlarmControlPanel( + hass, panel, area_id, config_entry.unique_id or config_entry.entry_id, + config_entry.entry_id, ) for area_id in panel.areas ) @@ -48,9 +50,18 @@ class AreaAlarmControlPanel(BoschAlarmAreaEntity, AlarmControlPanelEntity): _attr_code_arm_required = False _attr_name = None - def __init__(self, panel: Panel, area_id: int, unique_id: str) -> None: + def __init__( + self, + hass: HomeAssistant, + panel: Panel, + area_id: int, + unique_id: str, + config_entry_id: str, + ) -> None: """Initialise a Bosch Alarm control panel entity.""" - super().__init__(panel, area_id, unique_id, True, False, True) + super().__init__( + hass, panel, area_id, unique_id, config_entry_id, True, False, True + ) self._attr_unique_id = self._area_unique_id @property diff --git a/homeassistant/components/bosch_alarm/binary_sensor.py b/homeassistant/components/bosch_alarm/binary_sensor.py index 02992f149157..1c87511676ed 100644 --- a/homeassistant/components/bosch_alarm/binary_sensor.py +++ b/homeassistant/components/bosch_alarm/binary_sensor.py @@ -116,16 +116,17 @@ async def async_setup_entry( ) -> None: """Set up binary sensors for alarm points and the connection status.""" panel = config_entry.runtime_data + unique_id = config_entry.unique_id or config_entry.entry_id entities: list[BinarySensorEntity] = [ - PointSensor(panel, point_id, config_entry.unique_id or config_entry.entry_id) + PointSensor(hass, panel, point_id, unique_id, config_entry.entry_id) for point_id in panel.points ] entities.extend( PanelFaultsSensor( panel, - config_entry.unique_id or config_entry.entry_id, + unique_id, fault_type, ) for fault_type in FAULT_TYPES @@ -133,14 +134,14 @@ async def async_setup_entry( entities.extend( AreaReadyToArmSensor( - panel, area_id, config_entry.unique_id or config_entry.entry_id, "away" + hass, panel, area_id, unique_id, config_entry.entry_id, "away" ) for area_id in panel.areas ) entities.extend( AreaReadyToArmSensor( - panel, area_id, config_entry.unique_id or config_entry.entry_id, "home" + hass, panel, area_id, unique_id, config_entry.entry_id, "home" ) for area_id in panel.areas ) @@ -182,10 +183,18 @@ class AreaReadyToArmSensor(BoschAlarmAreaEntity, BinarySensorEntity): _attr_entity_category = EntityCategory.DIAGNOSTIC def __init__( - self, panel: Panel, area_id: int, unique_id: str, arm_type: str + self, + hass: HomeAssistant, + panel: Panel, + area_id: int, + unique_id: str, + config_entry_id: str, + arm_type: str, ) -> None: """Set up a binary sensor for arming status in a bosch panel.""" - super().__init__(panel, area_id, unique_id, False, False, True) + super().__init__( + hass, panel, area_id, unique_id, config_entry_id, False, False, True + ) self.panel = panel self._arm_type = arm_type self._attr_translation_key = f"area_ready_to_arm_{arm_type}" @@ -207,9 +216,16 @@ class PointSensor(BoschAlarmPointEntity, BinarySensorEntity): _attr_name = None - def __init__(self, panel: Panel, point_id: int, unique_id: str) -> None: + def __init__( + self, + hass: HomeAssistant, + panel: Panel, + point_id: int, + unique_id: str, + config_entry_id: str, + ) -> None: """Set up a binary sensor entity for a point in a bosch alarm panel.""" - super().__init__(panel, point_id, unique_id) + super().__init__(hass, panel, point_id, unique_id, config_entry_id) self._attr_unique_id = self._point_unique_id @property diff --git a/homeassistant/components/bosch_alarm/entity.py b/homeassistant/components/bosch_alarm/entity.py index 33a6516792a6..c52e1e990b9d 100644 --- a/homeassistant/components/bosch_alarm/entity.py +++ b/homeassistant/components/bosch_alarm/entity.py @@ -5,6 +5,8 @@ from typing import override from bosch_alarm_mode2 import Panel from homeassistant.components.sensor import Entity +from homeassistant.core import HomeAssistant +from homeassistant.helpers import device_registry as dr from homeassistant.helpers.device_registry import DeviceInfo from .const import DOMAIN @@ -56,9 +58,11 @@ class BoschAlarmAreaEntity(BoschAlarmEntity): def __init__( self, + hass: HomeAssistant, panel: Panel, area_id: int, unique_id: str, + config_entry_id: str, observe_alarms: bool, observe_ready: bool, observe_status: bool, @@ -75,7 +79,9 @@ class BoschAlarmAreaEntity(BoschAlarmEntity): identifiers={(DOMAIN, self._area_unique_id)}, name=self._area.name, manufacturer="Bosch Security Systems", - via_device=(DOMAIN, unique_id), + via_device_id=dr.async_get_device_id_by_identifier( + hass, (DOMAIN, unique_id), config_entry_id=config_entry_id + ), ) @override @@ -104,7 +110,14 @@ class BoschAlarmAreaEntity(BoschAlarmEntity): class BoschAlarmPointEntity(BoschAlarmEntity): """A base entity for point related entities within a bosch alarm panel.""" - def __init__(self, panel: Panel, point_id: int, unique_id: str) -> None: + def __init__( + self, + hass: HomeAssistant, + panel: Panel, + point_id: int, + unique_id: str, + config_entry_id: str, + ) -> None: """Set up a area related entity for a bosch alarm panel.""" super().__init__(panel, unique_id) self._point_id = point_id @@ -114,7 +127,9 @@ class BoschAlarmPointEntity(BoschAlarmEntity): identifiers={(DOMAIN, self._point_unique_id)}, name=self._point.name, manufacturer="Bosch Security Systems", - via_device=(DOMAIN, unique_id), + via_device_id=dr.async_get_device_id_by_identifier( + hass, (DOMAIN, unique_id), config_entry_id=config_entry_id + ), ) @override @@ -133,7 +148,14 @@ class BoschAlarmPointEntity(BoschAlarmEntity): class BoschAlarmDoorEntity(BoschAlarmEntity): """A base entity for area related entities within a bosch alarm panel.""" - def __init__(self, panel: Panel, door_id: int, unique_id: str) -> None: + def __init__( + self, + hass: HomeAssistant, + panel: Panel, + door_id: int, + unique_id: str, + config_entry_id: str, + ) -> None: """Set up a area related entity for a bosch alarm panel.""" super().__init__(panel, unique_id) self._door_id = door_id @@ -143,7 +165,9 @@ class BoschAlarmDoorEntity(BoschAlarmEntity): identifiers={(DOMAIN, self._door_unique_id)}, name=self._door.name, manufacturer="Bosch Security Systems", - via_device=(DOMAIN, unique_id), + via_device_id=dr.async_get_device_id_by_identifier( + hass, (DOMAIN, unique_id), config_entry_id=config_entry_id + ), ) @override @@ -162,7 +186,14 @@ class BoschAlarmDoorEntity(BoschAlarmEntity): class BoschAlarmOutputEntity(BoschAlarmEntity): """A base entity for area related entities within a bosch alarm panel.""" - def __init__(self, panel: Panel, output_id: int, unique_id: str) -> None: + def __init__( + self, + hass: HomeAssistant, + panel: Panel, + output_id: int, + unique_id: str, + config_entry_id: str, + ) -> None: """Set up a output related entity for a bosch alarm panel.""" super().__init__(panel, unique_id) self._output_id = output_id @@ -172,7 +203,9 @@ class BoschAlarmOutputEntity(BoschAlarmEntity): identifiers={(DOMAIN, self._output_unique_id)}, name=self._output.name, manufacturer="Bosch Security Systems", - via_device=(DOMAIN, unique_id), + via_device_id=dr.async_get_device_id_by_identifier( + hass, (DOMAIN, unique_id), config_entry_id=config_entry_id + ), ) @override diff --git a/homeassistant/components/bosch_alarm/sensor.py b/homeassistant/components/bosch_alarm/sensor.py index deb53a07079e..d292f97bde95 100644 --- a/homeassistant/components/bosch_alarm/sensor.py +++ b/homeassistant/components/bosch_alarm/sensor.py @@ -82,7 +82,9 @@ async def async_setup_entry( unique_id = config_entry.unique_id or config_entry.entry_id async_add_entities( - BoschAreaSensor(panel, area_id, unique_id, template) + BoschAreaSensor( + hass, panel, area_id, unique_id, config_entry.entry_id, template + ) for area_id in panel.areas for template in SENSOR_TYPES ) @@ -98,16 +100,20 @@ class BoschAreaSensor(BoschAlarmAreaEntity, SensorEntity): def __init__( self, + hass: HomeAssistant, panel: Panel, area_id: int, unique_id: str, + config_entry_id: str, entity_description: BoschAlarmSensorEntityDescription, ) -> None: """Set up an area sensor entity for a bosch alarm panel.""" super().__init__( + hass, panel, area_id, unique_id, + config_entry_id, entity_description.observe_alarms, entity_description.observe_ready, entity_description.observe_status, diff --git a/homeassistant/components/bosch_alarm/switch.py b/homeassistant/components/bosch_alarm/switch.py index d7bce72ef0da..238e077d468f 100644 --- a/homeassistant/components/bosch_alarm/switch.py +++ b/homeassistant/components/bosch_alarm/switch.py @@ -59,18 +59,19 @@ async def async_setup_entry( """Set up switch entities for outputs.""" panel = config_entry.runtime_data + unique_id = config_entry.unique_id or config_entry.entry_id entities: list[SwitchEntity] = [ - PanelOutputEntity( - panel, output_id, config_entry.unique_id or config_entry.entry_id - ) + PanelOutputEntity(hass, panel, output_id, unique_id, config_entry.entry_id) for output_id in panel.outputs ] entities.extend( PanelDoorEntity( + hass, panel, door_id, - config_entry.unique_id or config_entry.entry_id, + unique_id, + config_entry.entry_id, entity_description, ) for door_id in panel.doors @@ -90,13 +91,15 @@ class PanelDoorEntity(BoschAlarmDoorEntity, SwitchEntity): def __init__( self, + hass: HomeAssistant, panel: Panel, door_id: int, unique_id: str, + config_entry_id: str, entity_description: BoschAlarmSwitchEntityDescription, ) -> None: """Set up a switch entity for a door on a bosch alarm panel.""" - super().__init__(panel, door_id, unique_id) + super().__init__(hass, panel, door_id, unique_id, config_entry_id) self.entity_description = entity_description self._attr_unique_id = f"{self._door_unique_id}_{entity_description.key}" @@ -134,9 +137,16 @@ class PanelOutputEntity(BoschAlarmOutputEntity, SwitchEntity): _attr_name = None - def __init__(self, panel: Panel, output_id: int, unique_id: str) -> None: + def __init__( + self, + hass: HomeAssistant, + panel: Panel, + output_id: int, + unique_id: str, + config_entry_id: str, + ) -> None: """Set up an output entity for a bosch alarm panel.""" - super().__init__(panel, output_id, unique_id) + super().__init__(hass, panel, output_id, unique_id, config_entry_id) self._attr_unique_id = self._output_unique_id @property diff --git a/tests/components/bosch_alarm/test_binary_sensor.py b/tests/components/bosch_alarm/test_binary_sensor.py index e788d7c5eda5..d0e3970d1c1f 100644 --- a/tests/components/bosch_alarm/test_binary_sensor.py +++ b/tests/components/bosch_alarm/test_binary_sensor.py @@ -7,9 +7,10 @@ from bosch_alarm_mode2.const import ALARM_PANEL_FAULTS import pytest from syrupy.assertion import SnapshotAssertion +from homeassistant.components.bosch_alarm.const import DOMAIN from homeassistant.const import STATE_OFF, STATE_ON, Platform from homeassistant.core import HomeAssistant -from homeassistant.helpers import entity_registry as er +from homeassistant.helpers import device_registry as dr, entity_registry as er from . import call_observable, setup_integration @@ -76,3 +77,30 @@ async def test_area_ready_to_arm( await call_observable(hass, area.status_observer) assert hass.states.get(entity_id).state == STATE_OFF assert hass.states.get(entity_id_2).state == STATE_OFF + + +@pytest.mark.parametrize("model", ["b5512"]) +@pytest.mark.usefixtures("mock_panel") +async def test_point_via_device_id( + hass: HomeAssistant, + entity_registry: er.EntityRegistry, + device_registry: dr.DeviceRegistry, + mock_config_entry: MockConfigEntry, +) -> None: + """Test that a point's device links to the panel device via via_device_id.""" + await setup_integration(hass, mock_config_entry) + + entity_entry = entity_registry.async_get("binary_sensor.window") + assert entity_entry + assert entity_entry.device_id + + point_device = device_registry.async_get(entity_entry.device_id) + assert point_device + + panel_device = device_registry.async_get_device_by_identifier( + (DOMAIN, mock_config_entry.unique_id), + mock_config_entry.entry_id, + ) + assert panel_device + + assert point_device.via_device_id == panel_device.id