mirror of
https://github.com/home-assistant/core.git
synced 2026-09-26 09:23:17 -04:00
bosch_shc: add presence simulation switch entity (#182335)
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
bb56e6c5c8
commit
7e197f4516
@@ -91,6 +91,13 @@ SWITCH_TYPES: dict[str, SHCSwitchEntityDescription] = {
|
||||
on_value=ThermostatService.State.ON,
|
||||
should_poll=False,
|
||||
),
|
||||
"presencesimulation": SHCSwitchEntityDescription(
|
||||
key="presencesimulation",
|
||||
device_class=SwitchDeviceClass.SWITCH,
|
||||
on_key="enabled",
|
||||
on_value=True,
|
||||
should_poll=False,
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
@@ -207,6 +214,18 @@ async def async_setup_entry(
|
||||
)
|
||||
)
|
||||
|
||||
presence_simulation_system = session.device_helper.presence_simulation_system
|
||||
if presence_simulation_system is not None:
|
||||
entities.append(
|
||||
SHCSwitch(
|
||||
hass=hass,
|
||||
device=presence_simulation_system,
|
||||
parent_id=shc_info.unique_id,
|
||||
entry_id=config_entry.entry_id,
|
||||
description=SWITCH_TYPES["presencesimulation"],
|
||||
)
|
||||
)
|
||||
|
||||
async_add_entities(entities)
|
||||
|
||||
|
||||
|
||||
@@ -12,11 +12,13 @@ from boschshcpy import (
|
||||
SHCLightSwitchBSM,
|
||||
SHCMicromoduleBlinds,
|
||||
SHCMicromoduleRelay,
|
||||
SHCPresenceSimulationSystem,
|
||||
SHCShutterControl,
|
||||
SHCThermostat,
|
||||
ShutterControlService,
|
||||
ThermostatService,
|
||||
)
|
||||
from boschshcpy.services_impl import PresenceSimulationConfigurationService
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.bosch_shc.const import (
|
||||
@@ -51,7 +53,7 @@ def mock_config_entry() -> MockConfigEntry:
|
||||
|
||||
# Keep in sync with binary_sensor.py's device_helper buckets — a bucket
|
||||
# missing here breaks the mock_session fixture.
|
||||
_EMPTY_DEVICE_BUCKETS: dict[str, list[Any]] = {
|
||||
_EMPTY_DEVICE_BUCKETS: dict[str, Any] = {
|
||||
bucket: []
|
||||
for bucket in (
|
||||
"camera_360",
|
||||
@@ -77,22 +79,26 @@ _EMPTY_DEVICE_BUCKETS: dict[str, list[Any]] = {
|
||||
"wallthermostats",
|
||||
"water_leakage_detectors",
|
||||
)
|
||||
} | {
|
||||
# Not a list bucket — presence_simulation_system is a single optional
|
||||
# device on device_helper, not a device_helper.<x> list of devices.
|
||||
"presence_simulation_system": None,
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def device_buckets(request: pytest.FixtureRequest) -> dict[str, list[Any]]:
|
||||
def device_buckets(request: pytest.FixtureRequest) -> dict[str, Any]:
|
||||
"""device_helper buckets for the mock session.
|
||||
|
||||
Empty by default; a test overrides specific buckets via
|
||||
``@pytest.mark.parametrize("device_buckets", [{...}], indirect=True)``.
|
||||
"""
|
||||
overrides: dict[str, list[Any]] = getattr(request, "param", {})
|
||||
overrides: dict[str, Any] = getattr(request, "param", {})
|
||||
return {**_EMPTY_DEVICE_BUCKETS, **overrides}
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_session(device_buckets: dict[str, list[Any]]) -> Generator[MagicMock]:
|
||||
def mock_session(device_buckets: dict[str, Any]) -> Generator[MagicMock]:
|
||||
"""Mock SHCSession, patched in for the duration of the test."""
|
||||
session = MagicMock()
|
||||
session.information.unique_id = "test-mac"
|
||||
@@ -248,3 +254,27 @@ def light_switch_bsm_device(
|
||||
device.switchstate = PowerSwitchService.State.OFF
|
||||
device.child_lock = child_lock
|
||||
return device
|
||||
|
||||
|
||||
def presence_simulation_system_device(
|
||||
device_id: str = "presenceSimulationService",
|
||||
name: str = "Presence Simulation",
|
||||
enabled: bool = False,
|
||||
) -> SHCPresenceSimulationSystem:
|
||||
"""Build a minimal device double for the presence_simulation_system slot."""
|
||||
device = create_autospec(SHCPresenceSimulationSystem, 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 = "PRESENCE_SIMULATION_SERVICE"
|
||||
device.device_services = [
|
||||
create_autospec(
|
||||
PresenceSimulationConfigurationService, instance=True, spec_set=True
|
||||
)
|
||||
]
|
||||
device.deleted = False
|
||||
device.status = "AVAILABLE"
|
||||
device.enabled = enabled
|
||||
return device
|
||||
|
||||
@@ -19,6 +19,7 @@ from homeassistant.helpers import entity_registry as er
|
||||
from .conftest import (
|
||||
light_switch_bsm_device,
|
||||
micromodule_relay_device,
|
||||
presence_simulation_system_device,
|
||||
setup_integration,
|
||||
thermostat_device,
|
||||
)
|
||||
@@ -116,3 +117,79 @@ async def test_light_switch_bsm_child_lock_unique_id(
|
||||
assert lightswitch_entry is not None
|
||||
assert child_lock_entry is not None
|
||||
assert lightswitch_entry.unique_id != child_lock_entry.unique_id
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"device_buckets",
|
||||
[{"presence_simulation_system": presence_simulation_system_device(enabled=False)}],
|
||||
indirect=True,
|
||||
)
|
||||
@pytest.mark.usefixtures("mock_session")
|
||||
async def test_presence_simulation_system(
|
||||
hass: HomeAssistant,
|
||||
mock_session: MagicMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""The presence simulation system is exposed and controllable as a switch."""
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
device = mock_session.device_helper.presence_simulation_system
|
||||
|
||||
state = hass.states.get("switch.presence_simulation")
|
||||
assert state is not None
|
||||
assert state.state == "off"
|
||||
|
||||
await hass.services.async_call(
|
||||
SWITCH_DOMAIN,
|
||||
SERVICE_TURN_ON,
|
||||
{ATTR_ENTITY_ID: "switch.presence_simulation"},
|
||||
blocking=True,
|
||||
)
|
||||
assert device.enabled is True
|
||||
|
||||
await hass.services.async_call(
|
||||
SWITCH_DOMAIN,
|
||||
SERVICE_TURN_OFF,
|
||||
{ATTR_ENTITY_ID: "switch.presence_simulation"},
|
||||
blocking=True,
|
||||
)
|
||||
assert device.enabled is False
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"device_buckets",
|
||||
[{"presence_simulation_system": presence_simulation_system_device(enabled=False)}],
|
||||
indirect=True,
|
||||
)
|
||||
@pytest.mark.usefixtures("mock_session")
|
||||
async def test_presence_simulation_system_push_update(
|
||||
hass: HomeAssistant,
|
||||
mock_session: MagicMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""A controller-side push update reaches the switch without polling."""
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
device = mock_session.device_helper.presence_simulation_system
|
||||
service = device.device_services[0]
|
||||
on_state_changed = service.subscribe_callback.call_args.args[1]
|
||||
|
||||
device.enabled = True
|
||||
on_state_changed()
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert hass.states.get("switch.presence_simulation").state == "on"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"device_buckets",
|
||||
[{"presence_simulation_system": None}],
|
||||
indirect=True,
|
||||
)
|
||||
@pytest.mark.usefixtures("mock_session")
|
||||
async def test_no_presence_simulation_system(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""No switch is created when the controller has no presence simulation system."""
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
assert hass.states.get("switch.presence_simulation") is None
|
||||
|
||||
Reference in New Issue
Block a user