From 0aa7f15ddf99024ded535664766c886bef500e5f Mon Sep 17 00:00:00 2001 From: Thomas <10558666+mosandlt@users.noreply.github.com> Date: Tue, 29 Sep 2026 19:55:26 +0200 Subject: [PATCH] bosch_shc: add Outdoor Camera Gen2 privacy-mode switch entity (#183138) Co-authored-by: Claude Sonnet 5 --- homeassistant/components/bosch_shc/switch.py | 18 ++++++++ tests/components/bosch_shc/conftest.py | 23 ++++++++++ tests/components/bosch_shc/test_switch.py | 44 ++++++++++++++++++++ 3 files changed, 85 insertions(+) diff --git a/homeassistant/components/bosch_shc/switch.py b/homeassistant/components/bosch_shc/switch.py index bb536330affa..7d2ec51365a8 100644 --- a/homeassistant/components/bosch_shc/switch.py +++ b/homeassistant/components/bosch_shc/switch.py @@ -86,6 +86,13 @@ SWITCH_TYPES: dict[str, SHCSwitchEntityDescription] = { on_value=PrivacyModeService.State.DISABLED, should_poll=True, ), + "cameraoutdoorgen2": SHCSwitchEntityDescription( + key="cameraoutdoorgen2", + device_class=SwitchDeviceClass.SWITCH, + on_key="privacymode", + on_value=PrivacyModeService.State.DISABLED, + should_poll=True, + ), "child_lock": SHCSwitchEntityDescription( key="child_lock", translation_key="child_lock", @@ -309,6 +316,17 @@ async def async_setup_entry( for switch in session.device_helper.camera_360 ) + entities.extend( + SHCSwitch( + hass=hass, + device=switch, + parent_id=shc_info.unique_id, + entry_id=config_entry.entry_id, + description=SWITCH_TYPES["cameraoutdoorgen2"], + ) + for switch in session.device_helper.camera_outdoor_gen2 + ) + entities.extend( SHCSwitch( hass=hass, diff --git a/tests/components/bosch_shc/conftest.py b/tests/components/bosch_shc/conftest.py index fab4af7b737c..34556a8e4388 100644 --- a/tests/components/bosch_shc/conftest.py +++ b/tests/components/bosch_shc/conftest.py @@ -11,9 +11,11 @@ from boschshcpy import ( BypassService, CameraLightService, PowerSwitchService, + PrivacyModeService, RoutingService, SHCBatteryDevice, SHCCameraEyes, + SHCCameraOutdoorGen2, SHCLightSwitchBSM, SHCMicromoduleBlinds, SHCMicromoduleRelay, @@ -78,6 +80,7 @@ _EMPTY_DEVICE_BUCKETS: dict[str, Any] = { for bucket in ( "camera_360", "camera_eyes", + "camera_outdoor_gen2", "light_switches_bsm", "micromodule_blinds", "micromodule_dimmers", @@ -185,6 +188,26 @@ def camera_eyes_device( return device +def camera_outdoor_gen2_device( + device_id: str = "hdm:Cameras:outdoorgen2-1", + name: str = "Outdoor Camera", + privacymode: PrivacyModeService.State = PrivacyModeService.State.ENABLED, +) -> SHCCameraOutdoorGen2: + """Build a minimal device double for the camera_outdoor_gen2 bucket.""" + device = create_autospec(SHCCameraOutdoorGen2, 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 = "CAMERA_OUTDOOR_GEN2" + device.device_services = [] + device.deleted = False + device.status = "AVAILABLE" + device.privacymode = privacymode + return device + + def shutter_control_device( device_id: str = "hdm:ZigBee:shutter1", name: str = "Shutter", diff --git a/tests/components/bosch_shc/test_switch.py b/tests/components/bosch_shc/test_switch.py index 95b02b424054..6493b83ca230 100644 --- a/tests/components/bosch_shc/test_switch.py +++ b/tests/components/bosch_shc/test_switch.py @@ -6,6 +6,7 @@ from unittest.mock import MagicMock, patch from boschshcpy import ( BypassService, CameraLightService, + PrivacyModeService, SilentModeService, ThermostatService, ) @@ -23,6 +24,7 @@ from homeassistant.helpers import entity_registry as er from .conftest import ( camera_eyes_device, + camera_outdoor_gen2_device, light_switch_bsm_device, micromodule_relay_device, motion_detector2_device, @@ -1043,3 +1045,45 @@ async def test_camera_eyes_cameralight( blocking=True, ) assert device.cameralight is False + + +@pytest.mark.parametrize( + "device_buckets", + [ + { + "camera_outdoor_gen2": [ + camera_outdoor_gen2_device(privacymode=PrivacyModeService.State.ENABLED) + ] + } + ], + indirect=True, +) +@pytest.mark.usefixtures("mock_session") +async def test_camera_outdoor_gen2_privacy_mode( + hass: HomeAssistant, + mock_session: MagicMock, + mock_config_entry: MockConfigEntry, +) -> None: + """An Outdoor Camera Gen2's privacy mode is exposed and controllable as a switch.""" + await setup_integration(hass, mock_config_entry) + device = mock_session.device_helper.camera_outdoor_gen2[0] + + state = hass.states.get("switch.outdoor_camera") + assert state is not None + assert state.state == "off" + + await hass.services.async_call( + SWITCH_DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: "switch.outdoor_camera"}, + blocking=True, + ) + assert device.privacymode is True + + await hass.services.async_call( + SWITCH_DOMAIN, + SERVICE_TURN_OFF, + {ATTR_ENTITY_ID: "switch.outdoor_camera"}, + blocking=True, + ) + assert device.privacymode is False