bosch_shc: add Twinguard nightly promise (Heartbeat) switch entity (#182874)

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Thomas
2026-09-24 20:37:06 +02:00
committed by GitHub
co-authored by Claude Sonnet 5
parent c600261063
commit 79224c3a1c
4 changed files with 108 additions and 0 deletions
@@ -101,6 +101,9 @@
"child_lock": {
"name": "Child lock"
},
"nightly_promise_enabled": {
"name": "Heartbeat"
},
"pet_immunity_enabled": {
"name": "Pet immunity"
},
@@ -108,6 +108,15 @@ SWITCH_TYPES: dict[str, SHCSwitchEntityDescription] = {
on_value=BypassService.State.BYPASS_ACTIVE,
should_poll=False,
),
"nightly_promise_enabled": SHCSwitchEntityDescription(
key="nightly_promise_enabled",
translation_key="nightly_promise_enabled",
device_class=SwitchDeviceClass.SWITCH,
entity_category=EntityCategory.CONFIG,
on_key="nightly_promise_enabled",
on_value=True,
should_poll=False,
),
"pet_immunity_enabled": SHCSwitchEntityDescription(
key="pet_immunity_enabled",
translation_key="pet_immunity_enabled",
@@ -278,6 +287,18 @@ async def async_setup_entry(
for switch in session.device_helper.motion_detectors2
)
entities.extend(
SHCSwitch(
hass=hass,
device=switch,
parent_id=shc_info.unique_id,
entry_id=config_entry.entry_id,
description=SWITCH_TYPES["nightly_promise_enabled"],
)
for switch in session.device_helper.twinguards
if switch.supports_nightly_promise
)
async_add_entities(entities)
+23
View File
@@ -20,6 +20,7 @@ from boschshcpy import (
SHCShutterControl,
SHCSmartPlug,
SHCThermostat,
SHCTwinguard,
ShutterControlService,
ThermostatService,
)
@@ -354,3 +355,25 @@ def motion_detector2_device(
device.status = "AVAILABLE"
device.pet_immunity_enabled = pet_immunity_enabled
return device
def twinguard_device(
device_id: str = "hdm:HomeMaticIP:twinguard1",
name: str = "Twinguard",
supports_nightly_promise: bool = False,
nightly_promise_enabled: bool = False,
) -> SHCTwinguard:
"""Build a minimal device double for the twinguards bucket."""
device = create_autospec(SHCTwinguard, 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 = "TWINGUARD"
device.device_services = []
device.deleted = False
device.status = "AVAILABLE"
device.supports_nightly_promise = supports_nightly_promise
device.nightly_promise_enabled = nightly_promise_enabled
return device
+61
View File
@@ -25,6 +25,7 @@ from .conftest import (
shutter_contact2_device,
smart_plug_device,
thermostat_device,
twinguard_device,
)
from tests.common import MockConfigEntry
@@ -354,3 +355,63 @@ async def test_motion_detector2_pet_immunity(
blocking=True,
)
assert device.pet_immunity_enabled is False
@pytest.mark.parametrize(
"device_buckets",
[
{
"twinguards": [
twinguard_device(
supports_nightly_promise=True, nightly_promise_enabled=False
)
]
}
],
indirect=True,
)
@pytest.mark.usefixtures("mock_session")
async def test_twinguard_nightly_promise(
hass: HomeAssistant,
mock_session: MagicMock,
mock_config_entry: MockConfigEntry,
) -> None:
"""A Twinguard's nightly promise (Heartbeat) is exposed and controllable as a switch."""
await setup_integration(hass, mock_config_entry)
device = mock_session.device_helper.twinguards[0]
state = hass.states.get("switch.twinguard_heartbeat")
assert state is not None
assert state.state == "off"
await hass.services.async_call(
SWITCH_DOMAIN,
SERVICE_TURN_ON,
{ATTR_ENTITY_ID: "switch.twinguard_heartbeat"},
blocking=True,
)
assert device.nightly_promise_enabled is True
await hass.services.async_call(
SWITCH_DOMAIN,
SERVICE_TURN_OFF,
{ATTR_ENTITY_ID: "switch.twinguard_heartbeat"},
blocking=True,
)
assert device.nightly_promise_enabled is False
@pytest.mark.parametrize(
"device_buckets",
[{"twinguards": [twinguard_device(supports_nightly_promise=False)]}],
indirect=True,
)
@pytest.mark.usefixtures("mock_session")
async def test_twinguard_no_nightly_promise_support(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
) -> None:
"""No switch is created for a Twinguard without nightly-promise support."""
await setup_integration(hass, mock_config_entry)
assert hass.states.get("switch.twinguard_heartbeat") is None