diff --git a/homeassistant/components/bosch_shc/strings.json b/homeassistant/components/bosch_shc/strings.json index 2776f96c616f..0cc40b0126f5 100644 --- a/homeassistant/components/bosch_shc/strings.json +++ b/homeassistant/components/bosch_shc/strings.json @@ -127,6 +127,9 @@ }, "tamper_protection_enabled": { "name": "Sabotage detection" + }, + "vibration_enabled": { + "name": "Vibration detection" } }, "valve": { diff --git a/homeassistant/components/bosch_shc/switch.py b/homeassistant/components/bosch_shc/switch.py index 6d0db5aa556c..ce4fe2c90865 100644 --- a/homeassistant/components/bosch_shc/switch.py +++ b/homeassistant/components/bosch_shc/switch.py @@ -10,6 +10,7 @@ from boschshcpy import ( PowerSwitchService, PrivacyModeService, SHCShutterContact2, + SHCShutterContact2Plus, SHCSmartPlug, SilentModeService, ThermostatService, @@ -180,6 +181,15 @@ SWITCH_TYPES: dict[str, SHCSwitchEntityDescription] = { on_value=True, should_poll=False, ), + "vibration_enabled": SHCSwitchEntityDescription( + key="vibration_enabled", + translation_key="vibration_enabled", + device_class=SwitchDeviceClass.SWITCH, + entity_category=EntityCategory.CONFIG, + on_key="enabled", + on_value=True, + should_poll=False, + ), } @@ -329,6 +339,19 @@ async def async_setup_entry( for switch in session.device_helper.shutter_contacts2 ) + entities.extend( + SHCSwitch( + hass=hass, + device=switch, + parent_id=shc_info.unique_id, + entry_id=config_entry.entry_id, + description=SWITCH_TYPES["vibration_enabled"], + unique_id_suffix="vibration_enabled", + ) + for switch in session.device_helper.shutter_contacts2 + if isinstance(switch, SHCShutterContact2Plus) + ) + entities.extend( SHCSwitch( hass=hass, diff --git a/tests/components/bosch_shc/conftest.py b/tests/components/bosch_shc/conftest.py index 416d4692506c..66ec559b4960 100644 --- a/tests/components/bosch_shc/conftest.py +++ b/tests/components/bosch_shc/conftest.py @@ -18,6 +18,7 @@ from boschshcpy import ( SHCPresenceSimulationSystem, SHCShutterContact, SHCShutterContact2, + SHCShutterContact2Plus, SHCShutterControl, SHCSmartPlug, SHCSmartPlugCompact, @@ -418,6 +419,30 @@ def shutter_contact2_device( return device +def shutter_contact2_plus_device( + device_id: str = "hdm:ZigBee:shuttercontact1", + name: str = "Shutter contact", + bypass: BypassService.State = BypassService.State.BYPASS_INACTIVE, + bypass_infinite: bool = False, + vibration_enabled: bool = False, +) -> SHCShutterContact2Plus: + """Build a minimal device double for a vibration-capable Door/Window Contact II Plus.""" + device = create_autospec(SHCShutterContact2Plus, 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 = "SWD2_PLUS" + device.device_services = [] + device.deleted = False + device.status = "AVAILABLE" + device.bypass = bypass + device.bypass_infinite = bypass_infinite + device.enabled = vibration_enabled + return device + + def motion_detector2_device( device_id: str = "hdm:ZigBee:motiondetector1", name: str = "Motion Detector", diff --git a/tests/components/bosch_shc/test_switch.py b/tests/components/bosch_shc/test_switch.py index 70a1dcef5a95..908a5cb99316 100644 --- a/tests/components/bosch_shc/test_switch.py +++ b/tests/components/bosch_shc/test_switch.py @@ -23,6 +23,7 @@ from .conftest import ( presence_simulation_system_device, setup_integration, shutter_contact2_device, + shutter_contact2_plus_device, smart_plug_compact_device, smart_plug_device, smoke_detector_device, @@ -385,6 +386,58 @@ async def test_shutter_contact2_bypass_unique_id( assert bypass_entry.unique_id != bypass_infinite_entry.unique_id +@pytest.mark.parametrize( + "device_buckets", + [{"shutter_contacts2": [shutter_contact2_plus_device(vibration_enabled=False)]}], + indirect=True, +) +@pytest.mark.usefixtures("mock_session") +async def test_shutter_contact2_plus_vibration_enabled( + hass: HomeAssistant, + mock_session: MagicMock, + mock_config_entry: MockConfigEntry, +) -> None: + """A Door/Window Contact II Plus's vibration detection is exposed and controllable.""" + await setup_integration(hass, mock_config_entry) + device = mock_session.device_helper.shutter_contacts2[0] + + state = hass.states.get("switch.shutter_contact_vibration_detection") + assert state is not None + assert state.state == "off" + + await hass.services.async_call( + SWITCH_DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: "switch.shutter_contact_vibration_detection"}, + blocking=True, + ) + assert device.enabled is True + + await hass.services.async_call( + SWITCH_DOMAIN, + SERVICE_TURN_OFF, + {ATTR_ENTITY_ID: "switch.shutter_contact_vibration_detection"}, + blocking=True, + ) + assert device.enabled is False + + +@pytest.mark.parametrize( + "device_buckets", + [{"shutter_contacts2": [shutter_contact2_device()]}], + indirect=True, +) +@pytest.mark.usefixtures("mock_session") +async def test_shutter_contact2_no_vibration_enabled_support( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, +) -> None: + """No vibration switch is created for a plain (non-Plus) Door/Window Contact II.""" + await setup_integration(hass, mock_config_entry) + + assert hass.states.get("switch.shutter_contact_vibration_detection") is None + + @pytest.mark.parametrize( "device_buckets", [{"motion_detectors2": [motion_detector2_device(pet_immunity_enabled=False)]}],