Refactor bosch_shc ShutterContactSensor to entity-description pattern (#183115)

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
ThomasandClaude Sonnet 5 authored and GitHub committed 2026-09-25 20:58:11 +01:00
1 parent 2ae015598d
commit b8d535cdcf
3 files changed
+126 -2

No files matched your search

@@ -1,5 +1,7 @@
"""Platform for binarysensor integration."""
from collections.abc import Callable
from dataclasses import dataclass
from typing import TYPE_CHECKING, override
from boschshcpy import (
@@ -13,6 +15,7 @@ from boschshcpy.device import SHCDevice
from homeassistant.components.binary_sensor import (
BinarySensorDeviceClass,
BinarySensorEntity,
BinarySensorEntityDescription,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
@@ -23,6 +26,19 @@ from .entity import SHCEntity
PARALLEL_UPDATES = 0
@dataclass(frozen=True, kw_only=True)
class SHCShutterContactSensorEntityDescription(BinarySensorEntityDescription):
"""Describes a SHC shutter contact binary sensor."""
is_on_fn: Callable[[SHCShutterContact], bool]
SHUTTER_CONTACT_DESCRIPTION = SHCShutterContactSensorEntityDescription(
key="shutter_contact",
is_on_fn=lambda device: device.state is ShutterContactService.State.OPEN,
)
async def async_setup_entry(
hass: HomeAssistant,
config_entry: BoschConfigEntry,
@@ -41,6 +57,7 @@ async def async_setup_entry(
device=binary_sensor,
parent_id=shc_info.unique_id,
entry_id=config_entry.entry_id,
entity_description=SHUTTER_CONTACT_DESCRIPTION,
)
for binary_sensor in (
*session.device_helper.shutter_contacts,
@@ -76,11 +93,18 @@ class ShutterContactSensor(SHCEntity, BinarySensorEntity):
_attr_name = None
_device: SHCShutterContact
entity_description: SHCShutterContactSensorEntityDescription
def __init__(
self, hass: HomeAssistant, device: SHCDevice, parent_id: str, entry_id: str
self,
hass: HomeAssistant,
device: SHCDevice,
parent_id: str,
entry_id: str,
entity_description: SHCShutterContactSensorEntityDescription,
) -> None:
"""Initialize an SHC shutter contact sensor."""
self.entity_description = entity_description
super().__init__(hass, device, parent_id, entry_id)
switcher: dict[str | None, BinarySensorDeviceClass] = {
"ENTRANCE_DOOR": BinarySensorDeviceClass.DOOR,
@@ -96,7 +120,7 @@ class ShutterContactSensor(SHCEntity, BinarySensorEntity):
@override
def is_on(self) -> bool:
"""Return the state of the sensor."""
return self._device.state is ShutterContactService.State.OPEN
return self.entity_description.is_on_fn(self._device)
class BatterySensor(SHCEntity, BinarySensorEntity):
+24
View File
@@ -16,6 +16,7 @@ from boschshcpy import (
SHCMicromoduleRelay,
SHCMotionDetector2,
SHCPresenceSimulationSystem,
SHCShutterContact,
SHCShutterContact2,
SHCShutterControl,
SHCSmartPlug,
@@ -24,6 +25,7 @@ from boschshcpy import (
SHCThermostat,
SHCThermostatGen2,
SHCTwinguard,
ShutterContactService,
ShutterControlService,
ThermostatService,
)
@@ -367,6 +369,28 @@ def presence_simulation_system_device(
return device
def shutter_contact_device(
device_id: str = "hdm:ZigBee:shuttercontact1",
name: str = "Shutter contact",
device_class: str = "GENERIC",
state: ShutterContactService.State = ShutterContactService.State.CLOSED,
) -> SHCShutterContact:
"""Build a minimal device double for the shutter_contacts bucket."""
device = create_autospec(SHCShutterContact, instance=True, spec_set=True)
device.name = name
device.id = device_id
device.root_device_id = "test-mac"
device.serial = f"serial-{device_id}"
device.manufacturer = "Bosch"
device.device_model = "SWD"
device.device_class = device_class
device.device_services = []
device.deleted = False
device.status = "AVAILABLE"
device.state = state
return device
def shutter_contact2_device(
device_id: str = "hdm:ZigBee:shuttercontact1",
name: str = "Shutter contact",
@@ -0,0 +1,76 @@
"""Tests for the Bosch SHC binary_sensor platform."""
from boschshcpy import ShutterContactService
import pytest
from homeassistant.core import HomeAssistant
from .conftest import setup_integration, shutter_contact_device
from tests.common import MockConfigEntry
@pytest.mark.parametrize(
"device_buckets",
[
{
"shutter_contacts": [
shutter_contact_device(state=ShutterContactService.State.CLOSED)
]
}
],
indirect=True,
)
@pytest.mark.usefixtures("mock_session")
async def test_shutter_contact_closed(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
) -> None:
"""A closed Shutter Contact is reported as off."""
await setup_integration(hass, mock_config_entry)
state = hass.states.get("binary_sensor.shutter_contact")
assert state is not None
assert state.state == "off"
@pytest.mark.parametrize(
"device_buckets",
[
{
"shutter_contacts": [
shutter_contact_device(state=ShutterContactService.State.OPEN)
]
}
],
indirect=True,
)
@pytest.mark.usefixtures("mock_session")
async def test_shutter_contact_open(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
) -> None:
"""An open Shutter Contact is reported as on."""
await setup_integration(hass, mock_config_entry)
state = hass.states.get("binary_sensor.shutter_contact")
assert state is not None
assert state.state == "on"
@pytest.mark.parametrize(
"device_buckets",
[{"shutter_contacts": [shutter_contact_device(device_class="ENTRANCE_DOOR")]}],
indirect=True,
)
@pytest.mark.usefixtures("mock_session")
async def test_shutter_contact_device_class(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
) -> None:
"""A Shutter Contact's device_class maps to the binary_sensor device class."""
await setup_integration(hass, mock_config_entry)
state = hass.states.get("binary_sensor.shutter_contact")
assert state is not None
assert state.attributes["device_class"] == "door"