Adapt bosch_alarm to set via_device_id in DeviceInfo (#177709)

Co-authored-by: Markus Tuominen <3738613+Markus98@users.noreply.github.com>
This commit is contained in:
Erik Montnemery
2026-08-03 15:57:08 +02:00
committed by GitHub
co-authored by Markus Tuominen
parent 132e0bf011
commit 62fa51cc52
6 changed files with 130 additions and 26 deletions
@@ -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
@@ -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
+40 -7
View File
@@ -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
@@ -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,
+17 -7
View File
@@ -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
@@ -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