mirror of
https://github.com/home-assistant/core.git
synced 2026-08-24 10:13:52 -05:00
Adapt bosch_shc to set via_device_id in DeviceInfo (#177711)
This commit is contained in:
@@ -37,6 +37,7 @@ async def async_setup_entry(
|
||||
|
||||
entities: list[BinarySensorEntity] = [
|
||||
ShutterContactSensor(
|
||||
hass=hass,
|
||||
device=binary_sensor,
|
||||
parent_id=shc_info.unique_id,
|
||||
entry_id=config_entry.entry_id,
|
||||
@@ -49,6 +50,7 @@ async def async_setup_entry(
|
||||
|
||||
entities.extend(
|
||||
BatterySensor(
|
||||
hass=hass,
|
||||
device=binary_sensor,
|
||||
parent_id=shc_info.unique_id,
|
||||
entry_id=config_entry.entry_id,
|
||||
@@ -75,9 +77,11 @@ class ShutterContactSensor(SHCEntity, BinarySensorEntity):
|
||||
_attr_name = None
|
||||
_device: SHCShutterContact
|
||||
|
||||
def __init__(self, device: SHCDevice, parent_id: str, entry_id: str) -> None:
|
||||
def __init__(
|
||||
self, hass: HomeAssistant, device: SHCDevice, parent_id: str, entry_id: str
|
||||
) -> None:
|
||||
"""Initialize an SHC shutter contact sensor."""
|
||||
super().__init__(device, parent_id, entry_id)
|
||||
super().__init__(hass, device, parent_id, entry_id)
|
||||
switcher: dict[str | None, BinarySensorDeviceClass] = {
|
||||
"ENTRANCE_DOOR": BinarySensorDeviceClass.DOOR,
|
||||
"REGULAR_WINDOW": BinarySensorDeviceClass.WINDOW,
|
||||
@@ -101,9 +105,11 @@ class BatterySensor(SHCEntity, BinarySensorEntity):
|
||||
_attr_device_class = BinarySensorDeviceClass.BATTERY
|
||||
_device: SHCBatteryDevice
|
||||
|
||||
def __init__(self, device: SHCDevice, parent_id: str, entry_id: str) -> None:
|
||||
def __init__(
|
||||
self, hass: HomeAssistant, device: SHCDevice, parent_id: str, entry_id: str
|
||||
) -> None:
|
||||
"""Initialize an SHC battery reporting sensor."""
|
||||
super().__init__(device, parent_id, entry_id)
|
||||
super().__init__(hass, device, parent_id, entry_id)
|
||||
self._attr_unique_id = f"{device.serial}_battery"
|
||||
|
||||
@property
|
||||
|
||||
@@ -33,6 +33,7 @@ async def async_setup_entry(
|
||||
|
||||
async_add_entities(
|
||||
ShutterControlCover(
|
||||
hass=hass,
|
||||
device=cover,
|
||||
parent_id=shc_info.unique_id,
|
||||
entry_id=config_entry.entry_id,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
from typing import override
|
||||
|
||||
from boschshcpy import SHCDevice, SHCIntrusionSystem
|
||||
from boschshcpy import SHCDevice
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
@@ -30,9 +30,7 @@ class SHCBaseEntity(Entity):
|
||||
_attr_should_poll = False
|
||||
_attr_has_entity_name = True
|
||||
|
||||
def __init__(
|
||||
self, device: SHCDevice | SHCIntrusionSystem, parent_id: str, entry_id: str
|
||||
) -> None:
|
||||
def __init__(self, device: SHCDevice, parent_id: str, entry_id: str) -> None:
|
||||
"""Initialize the generic SHC device."""
|
||||
self._device = device
|
||||
self._entry_id = entry_id
|
||||
@@ -67,16 +65,25 @@ class SHCEntity(SHCBaseEntity):
|
||||
|
||||
_device: SHCDevice
|
||||
|
||||
def __init__(self, device: SHCDevice, parent_id: str, entry_id: str) -> None:
|
||||
def __init__(
|
||||
self, hass: HomeAssistant, device: SHCDevice, parent_id: str, entry_id: str
|
||||
) -> None:
|
||||
"""Initialize generic SHC device."""
|
||||
self._attr_unique_id = device.serial
|
||||
self._attr_device_info = DeviceInfo(
|
||||
device_info = DeviceInfo(
|
||||
identifiers={(DOMAIN, device.id)},
|
||||
manufacturer=device.manufacturer,
|
||||
model=device.device_model,
|
||||
name=device.name,
|
||||
via_device=(DOMAIN, device.root_device_id),
|
||||
)
|
||||
# boschshcpy may render the hub identifier (shc_info.unique_id) and a
|
||||
# device's root_device_id differently, so the lookup can miss; link only
|
||||
# when it resolves instead of raising out of setup.
|
||||
if hub := dr.async_get(hass).async_get_device_by_identifier(
|
||||
(DOMAIN, device.root_device_id), entry_id
|
||||
):
|
||||
device_info["via_device_id"] = hub.id
|
||||
self._attr_device_info = device_info
|
||||
super().__init__(device=device, parent_id=parent_id, entry_id=entry_id)
|
||||
|
||||
@override
|
||||
@@ -102,32 +109,3 @@ class SHCEntity(SHCBaseEntity):
|
||||
def available(self) -> bool:
|
||||
"""Return false if status is unavailable."""
|
||||
return self._device.status == "AVAILABLE"
|
||||
|
||||
|
||||
class SHCDomainEntity(SHCBaseEntity):
|
||||
"""Representation of a SHC domain service entity."""
|
||||
|
||||
_device: SHCIntrusionSystem
|
||||
|
||||
def __init__(
|
||||
self, domain: SHCIntrusionSystem, parent_id: str, entry_id: str
|
||||
) -> None:
|
||||
"""Initialize the generic SHC device."""
|
||||
self._attr_unique_id = domain.id
|
||||
self._attr_device_info = DeviceInfo(
|
||||
identifiers={(DOMAIN, domain.id)},
|
||||
manufacturer=domain.manufacturer,
|
||||
model=domain.device_model,
|
||||
name=domain.name,
|
||||
via_device=(
|
||||
DOMAIN,
|
||||
parent_id,
|
||||
),
|
||||
)
|
||||
super().__init__(device=domain, parent_id=parent_id, entry_id=entry_id)
|
||||
|
||||
@property
|
||||
@override
|
||||
def available(self) -> bool:
|
||||
"""Return false if status is unavailable."""
|
||||
return self._device.system_availability
|
||||
|
||||
@@ -212,6 +212,7 @@ async def async_setup_entry(
|
||||
|
||||
entities: list[SensorEntity] = [
|
||||
SHCSensor(
|
||||
hass,
|
||||
device,
|
||||
description,
|
||||
shc_info.unique_id,
|
||||
@@ -226,6 +227,7 @@ async def async_setup_entry(
|
||||
|
||||
entities.extend(
|
||||
SHCSensor(
|
||||
hass,
|
||||
device,
|
||||
description,
|
||||
shc_info.unique_id,
|
||||
@@ -240,6 +242,7 @@ async def async_setup_entry(
|
||||
|
||||
entities.extend(
|
||||
SHCSensor(
|
||||
hass,
|
||||
device,
|
||||
description,
|
||||
shc_info.unique_id,
|
||||
@@ -263,6 +266,7 @@ async def async_setup_entry(
|
||||
]
|
||||
entities.extend(
|
||||
SHCSensor(
|
||||
hass,
|
||||
device,
|
||||
description,
|
||||
shc_info.unique_id,
|
||||
@@ -274,6 +278,7 @@ async def async_setup_entry(
|
||||
|
||||
entities.extend(
|
||||
SHCSensor(
|
||||
hass,
|
||||
device,
|
||||
description,
|
||||
shc_info.unique_id,
|
||||
@@ -297,13 +302,14 @@ class SHCSensor[_DeviceT: SHCDevice](SHCEntity, SensorEntity):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
device: _DeviceT,
|
||||
entity_description: SHCSensorEntityDescription[_DeviceT],
|
||||
parent_id: str,
|
||||
entry_id: str,
|
||||
) -> None:
|
||||
"""Initialize sensor."""
|
||||
super().__init__(device, parent_id, entry_id)
|
||||
super().__init__(hass, device, parent_id, entry_id)
|
||||
self._device: _DeviceT = device
|
||||
self.entity_description = entity_description
|
||||
self._attr_unique_id = f"{device.serial}_{entity_description.key}"
|
||||
|
||||
@@ -89,6 +89,7 @@ async def async_setup_entry(
|
||||
|
||||
entities: list[SwitchEntity] = [
|
||||
SHCSwitch(
|
||||
hass=hass,
|
||||
device=switch,
|
||||
parent_id=shc_info.unique_id,
|
||||
entry_id=config_entry.entry_id,
|
||||
@@ -99,6 +100,7 @@ async def async_setup_entry(
|
||||
|
||||
entities.extend(
|
||||
SHCRoutingSwitch(
|
||||
hass=hass,
|
||||
device=switch,
|
||||
parent_id=shc_info.unique_id,
|
||||
entry_id=config_entry.entry_id,
|
||||
@@ -108,6 +110,7 @@ async def async_setup_entry(
|
||||
|
||||
entities.extend(
|
||||
SHCSwitch(
|
||||
hass=hass,
|
||||
device=switch,
|
||||
parent_id=shc_info.unique_id,
|
||||
entry_id=config_entry.entry_id,
|
||||
@@ -118,6 +121,7 @@ async def async_setup_entry(
|
||||
|
||||
entities.extend(
|
||||
SHCSwitch(
|
||||
hass=hass,
|
||||
device=switch,
|
||||
parent_id=shc_info.unique_id,
|
||||
entry_id=config_entry.entry_id,
|
||||
@@ -128,6 +132,7 @@ async def async_setup_entry(
|
||||
|
||||
entities.extend(
|
||||
SHCSwitch(
|
||||
hass=hass,
|
||||
device=switch,
|
||||
parent_id=shc_info.unique_id,
|
||||
entry_id=config_entry.entry_id,
|
||||
@@ -138,6 +143,7 @@ async def async_setup_entry(
|
||||
|
||||
entities.extend(
|
||||
SHCSwitch(
|
||||
hass=hass,
|
||||
device=switch,
|
||||
parent_id=shc_info.unique_id,
|
||||
entry_id=config_entry.entry_id,
|
||||
@@ -156,13 +162,14 @@ class SHCSwitch(SHCEntity, SwitchEntity):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
device: SHCDevice,
|
||||
parent_id: str,
|
||||
entry_id: str,
|
||||
description: SHCSwitchEntityDescription,
|
||||
) -> None:
|
||||
"""Initialize a SHC switch."""
|
||||
super().__init__(device, parent_id, entry_id)
|
||||
super().__init__(hass, device, parent_id, entry_id)
|
||||
self.entity_description = description
|
||||
|
||||
@property
|
||||
@@ -202,9 +209,11 @@ class SHCRoutingSwitch(SHCEntity, SwitchEntity):
|
||||
_attr_entity_category = EntityCategory.CONFIG
|
||||
_device: SHCSmartPlug
|
||||
|
||||
def __init__(self, device: SHCDevice, parent_id: str, entry_id: str) -> None:
|
||||
def __init__(
|
||||
self, hass: HomeAssistant, device: SHCDevice, parent_id: str, entry_id: str
|
||||
) -> None:
|
||||
"""Initialize an SHC routing switch."""
|
||||
super().__init__(device, parent_id, entry_id)
|
||||
super().__init__(hass, device, parent_id, entry_id)
|
||||
self._attr_unique_id = f"{device.serial}_routing"
|
||||
|
||||
@property
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
"""Tests for the Bosch SHC entity base classes."""
|
||||
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from boschshcpy import SHCDevice
|
||||
|
||||
from homeassistant.components.bosch_shc.const import DOMAIN
|
||||
from homeassistant.components.bosch_shc.entity import SHCEntity
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
|
||||
async def test_shc_entity_via_device_id(
|
||||
hass: HomeAssistant, device_registry: dr.DeviceRegistry
|
||||
) -> None:
|
||||
"""Test SHCEntity links its device to the SHC hub via via_device_id."""
|
||||
entry = MockConfigEntry(domain=DOMAIN)
|
||||
entry.add_to_hass(hass)
|
||||
|
||||
hub_device = device_registry.async_get_or_create(
|
||||
config_entry_id=entry.entry_id,
|
||||
identifiers={(DOMAIN, "root-serial")},
|
||||
manufacturer="Bosch",
|
||||
name="Bosch SHC",
|
||||
model="SmartHomeController",
|
||||
)
|
||||
|
||||
device = MagicMock(spec=SHCDevice)
|
||||
device.serial = "child-serial"
|
||||
device.manufacturer = "Bosch"
|
||||
device.device_model = "SWD"
|
||||
device.name = "Shutter Contact"
|
||||
device.id = "child-id"
|
||||
device.root_device_id = "root-serial"
|
||||
|
||||
entity = SHCEntity(
|
||||
hass=hass, device=device, parent_id="root-serial", entry_id=entry.entry_id
|
||||
)
|
||||
|
||||
assert entity.device_info is not None
|
||||
assert entity.device_info["via_device_id"] == hub_device.id
|
||||
|
||||
|
||||
async def test_shc_entity_via_device_id_mismatch(
|
||||
hass: HomeAssistant, device_registry: dr.DeviceRegistry
|
||||
) -> None:
|
||||
"""Test SHCEntity sets up without a link when the hub identifier does not match.
|
||||
|
||||
boschshcpy may render the hub identifier and a device's root_device_id
|
||||
differently, so the lookup can miss; setup must not crash in that case.
|
||||
"""
|
||||
entry = MockConfigEntry(domain=DOMAIN)
|
||||
entry.add_to_hass(hass)
|
||||
|
||||
device_registry.async_get_or_create(
|
||||
config_entry_id=entry.entry_id,
|
||||
identifiers={(DOMAIN, "hub-serial")},
|
||||
manufacturer="Bosch",
|
||||
name="Bosch SHC",
|
||||
model="SmartHomeController",
|
||||
)
|
||||
|
||||
device = MagicMock(spec=SHCDevice)
|
||||
device.serial = "child-serial"
|
||||
device.manufacturer = "Bosch"
|
||||
device.device_model = "SWD"
|
||||
device.name = "Shutter Contact"
|
||||
device.id = "child-id"
|
||||
device.root_device_id = "root-serial-mismatch"
|
||||
|
||||
entity = SHCEntity(
|
||||
hass=hass, device=device, parent_id="hub-serial", entry_id=entry.entry_id
|
||||
)
|
||||
|
||||
assert entity.device_info is not None
|
||||
assert "via_device_id" not in entity.device_info
|
||||
Reference in New Issue
Block a user