diff --git a/homeassistant/components/bosch_shc/strings.json b/homeassistant/components/bosch_shc/strings.json index 7ef81f26d22e..2776f96c616f 100644 --- a/homeassistant/components/bosch_shc/strings.json +++ b/homeassistant/components/bosch_shc/strings.json @@ -119,6 +119,9 @@ "routing": { "name": "Range extension" }, + "silent_mode": { + "name": "Whisper mode" + }, "smart_sensitivity_enabled": { "name": "Automatic sensitivity" }, diff --git a/homeassistant/components/bosch_shc/switch.py b/homeassistant/components/bosch_shc/switch.py index eddbc079d9cd..6d0db5aa556c 100644 --- a/homeassistant/components/bosch_shc/switch.py +++ b/homeassistant/components/bosch_shc/switch.py @@ -11,6 +11,7 @@ from boschshcpy import ( PrivacyModeService, SHCShutterContact2, SHCSmartPlug, + SilentModeService, ThermostatService, ) from boschshcpy.device import SHCDevice @@ -161,6 +162,15 @@ SWITCH_TYPES: dict[str, SHCSwitchEntityDescription] = { on_value=True, should_poll=False, ), + "silent_mode": SHCSwitchEntityDescription( + key="silent_mode", + translation_key="silent_mode", + device_class=SwitchDeviceClass.SWITCH, + entity_category=EntityCategory.CONFIG, + on_key="silentmode", + on_value=SilentModeService.State.MODE_SILENT, + should_poll=False, + ), "smart_sensitivity_enabled": SHCSwitchEntityDescription( key="smart_sensitivity_enabled", translation_key="smart_sensitivity_enabled", @@ -355,6 +365,19 @@ async def async_setup_entry( if switch.supports_intrusion_alarm ) + entities.extend( + SHCSwitch( + hass=hass, + device=switch, + parent_id=shc_info.unique_id, + entry_id=config_entry.entry_id, + description=SWITCH_TYPES["silent_mode"], + unique_id_suffix="silent_mode", + ) + for switch in session.device_helper.thermostats + if switch.supports_silentmode + ) + entities.extend( SHCSwitch( hass=hass, diff --git a/tests/components/bosch_shc/conftest.py b/tests/components/bosch_shc/conftest.py index e9100a4e41d6..416d4692506c 100644 --- a/tests/components/bosch_shc/conftest.py +++ b/tests/components/bosch_shc/conftest.py @@ -27,6 +27,7 @@ from boschshcpy import ( SHCTwinguard, ShutterContactService, ShutterControlService, + SilentModeService, ThermostatService, ) from boschshcpy.services_impl import ( @@ -260,6 +261,8 @@ def thermostat_device( child_lock: ThermostatService.State = ThermostatService.State.OFF, position: int = 50, valvestate: ValveTappetService.State = ValveTappetService.State.VALVE_ADAPTION_SUCCESSFUL, + supports_silentmode: bool = False, + silentmode: SilentModeService.State = SilentModeService.State.MODE_NORMAL, ) -> SHCThermostat: """Build a minimal device double for the thermostats/roomthermostats/wallthermostats buckets.""" device = create_autospec(SHCThermostat, instance=True, spec_set=True) @@ -275,6 +278,8 @@ def thermostat_device( device.child_lock = child_lock device.position = position device.valvestate = valvestate + device.supports_silentmode = supports_silentmode + device.silentmode = silentmode return device diff --git a/tests/components/bosch_shc/test_switch.py b/tests/components/bosch_shc/test_switch.py index 773283781779..70a1dcef5a95 100644 --- a/tests/components/bosch_shc/test_switch.py +++ b/tests/components/bosch_shc/test_switch.py @@ -3,7 +3,7 @@ from collections.abc import Generator from unittest.mock import MagicMock, patch -from boschshcpy import BypassService, ThermostatService +from boschshcpy import BypassService, SilentModeService, ThermostatService import pytest from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN @@ -77,6 +77,67 @@ async def test_thermostat_child_lock( assert device.child_lock is False +@pytest.mark.parametrize( + "device_buckets", + [ + { + "thermostats": [ + thermostat_device( + supports_silentmode=True, + silentmode=SilentModeService.State.MODE_NORMAL, + ) + ] + } + ], + indirect=True, +) +@pytest.mark.usefixtures("mock_session") +async def test_thermostat_silent_mode( + hass: HomeAssistant, + mock_session: MagicMock, + mock_config_entry: MockConfigEntry, +) -> None: + """A thermostat's silent mode is exposed and controllable as a switch.""" + await setup_integration(hass, mock_config_entry) + device = mock_session.device_helper.thermostats[0] + + state = hass.states.get("switch.thermostat_whisper_mode") + assert state is not None + assert state.state == "off" + + await hass.services.async_call( + SWITCH_DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: "switch.thermostat_whisper_mode"}, + blocking=True, + ) + assert device.silentmode is True + + await hass.services.async_call( + SWITCH_DOMAIN, + SERVICE_TURN_OFF, + {ATTR_ENTITY_ID: "switch.thermostat_whisper_mode"}, + blocking=True, + ) + assert device.silentmode is False + + +@pytest.mark.parametrize( + "device_buckets", + [{"thermostats": [thermostat_device(supports_silentmode=False)]}], + indirect=True, +) +@pytest.mark.usefixtures("mock_session") +async def test_thermostat_no_silent_mode_support( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, +) -> None: + """No switch is created for a thermostat without silent-mode support.""" + await setup_integration(hass, mock_config_entry) + + assert hass.states.get("switch.thermostat_whisper_mode") is None + + @pytest.mark.parametrize( "device_buckets", [{"micromodule_relays": [micromodule_relay_device(child_lock=False)]}],