diff --git a/homeassistant/components/bosch_shc/strings.json b/homeassistant/components/bosch_shc/strings.json index 0cc40b0126f5..72abebca1673 100644 --- a/homeassistant/components/bosch_shc/strings.json +++ b/homeassistant/components/bosch_shc/strings.json @@ -125,6 +125,12 @@ "smart_sensitivity_enabled": { "name": "Automatic sensitivity" }, + "swap_inputs": { + "name": "Swap inputs" + }, + "swap_outputs": { + "name": "Swap outputs" + }, "tamper_protection_enabled": { "name": "Sabotage detection" }, diff --git a/homeassistant/components/bosch_shc/switch.py b/homeassistant/components/bosch_shc/switch.py index ce4fe2c90865..f808709950ed 100644 --- a/homeassistant/components/bosch_shc/switch.py +++ b/homeassistant/components/bosch_shc/switch.py @@ -190,6 +190,24 @@ SWITCH_TYPES: dict[str, SHCSwitchEntityDescription] = { on_value=True, should_poll=False, ), + "swap_inputs": SHCSwitchEntityDescription( + key="swap_inputs", + translation_key="swap_inputs", + device_class=SwitchDeviceClass.SWITCH, + entity_category=EntityCategory.CONFIG, + on_key="swap_inputs", + on_value=True, + should_poll=False, + ), + "swap_outputs": SHCSwitchEntityDescription( + key="swap_outputs", + translation_key="swap_outputs", + device_class=SwitchDeviceClass.SWITCH, + entity_category=EntityCategory.CONFIG, + on_key="swap_outputs", + on_value=True, + should_poll=False, + ), } @@ -467,6 +485,34 @@ async def async_setup_entry( if switch.supports_smart_sensitivity ) + entities.extend( + SHCSwitch( + hass=hass, + device=switch, + parent_id=shc_info.unique_id, + entry_id=config_entry.entry_id, + description=SWITCH_TYPES["swap_inputs"], + unique_id_suffix="swap_inputs", + ) + for switch in session.device_helper.micromodule_relays + if getattr(switch, "supports_switch_configuration", False) + and getattr(switch, "swap_inputs", None) is not None + ) + + entities.extend( + SHCSwitch( + hass=hass, + device=switch, + parent_id=shc_info.unique_id, + entry_id=config_entry.entry_id, + description=SWITCH_TYPES["swap_outputs"], + unique_id_suffix="swap_outputs", + ) + for switch in session.device_helper.micromodule_relays + if getattr(switch, "supports_switch_configuration", False) + and getattr(switch, "swap_outputs", None) is not None + ) + async_add_entities(entities) diff --git a/tests/components/bosch_shc/conftest.py b/tests/components/bosch_shc/conftest.py index 082db8963af1..1cc265c03c15 100644 --- a/tests/components/bosch_shc/conftest.py +++ b/tests/components/bosch_shc/conftest.py @@ -311,6 +311,9 @@ def micromodule_relay_device( device_id: str = "hdm:ZigBee:relay1", name: str = "Relay", child_lock: bool = False, + supports_switch_configuration: bool = False, + swap_inputs: bool = False, + swap_outputs: bool = False, ) -> SHCMicromoduleRelay: """Build a minimal device double for the micromodule_relays bucket.""" device = create_autospec(SHCMicromoduleRelay, instance=True, spec_set=True) @@ -324,6 +327,9 @@ def micromodule_relay_device( device.deleted = False device.status = "AVAILABLE" device.child_lock = child_lock + device.supports_switch_configuration = supports_switch_configuration + device.swap_inputs = swap_inputs + device.swap_outputs = swap_outputs return device diff --git a/tests/components/bosch_shc/test_switch.py b/tests/components/bosch_shc/test_switch.py index 908a5cb99316..c61d55cb2ee9 100644 --- a/tests/components/bosch_shc/test_switch.py +++ b/tests/components/bosch_shc/test_switch.py @@ -890,3 +890,114 @@ async def test_motion_detector2_no_smart_sensitivity_support( await setup_integration(hass, mock_config_entry) assert hass.states.get("switch.motion_detector_automatic_sensitivity") is None + + +@pytest.mark.parametrize( + "device_buckets", + [ + { + "micromodule_relays": [ + micromodule_relay_device( + supports_switch_configuration=True, swap_inputs=False + ) + ] + } + ], + indirect=True, +) +@pytest.mark.usefixtures("mock_session") +async def test_micromodule_relay_swap_inputs( + hass: HomeAssistant, + mock_session: MagicMock, + mock_config_entry: MockConfigEntry, +) -> None: + """A Micromodule Relay's swap-inputs setting is exposed and controllable.""" + await setup_integration(hass, mock_config_entry) + device = mock_session.device_helper.micromodule_relays[0] + + state = hass.states.get("switch.relay_swap_inputs") + assert state is not None + assert state.state == "off" + + await hass.services.async_call( + SWITCH_DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: "switch.relay_swap_inputs"}, + blocking=True, + ) + assert device.swap_inputs is True + + await hass.services.async_call( + SWITCH_DOMAIN, + SERVICE_TURN_OFF, + {ATTR_ENTITY_ID: "switch.relay_swap_inputs"}, + blocking=True, + ) + assert device.swap_inputs is False + + +@pytest.mark.parametrize( + "device_buckets", + [ + { + "micromodule_relays": [ + micromodule_relay_device( + supports_switch_configuration=True, swap_outputs=False + ) + ] + } + ], + indirect=True, +) +@pytest.mark.usefixtures("mock_session") +async def test_micromodule_relay_swap_outputs( + hass: HomeAssistant, + mock_session: MagicMock, + mock_config_entry: MockConfigEntry, +) -> None: + """A Micromodule Relay's swap-outputs setting is exposed and controllable.""" + await setup_integration(hass, mock_config_entry) + device = mock_session.device_helper.micromodule_relays[0] + + state = hass.states.get("switch.relay_swap_outputs") + assert state is not None + assert state.state == "off" + + await hass.services.async_call( + SWITCH_DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: "switch.relay_swap_outputs"}, + blocking=True, + ) + assert device.swap_outputs is True + + await hass.services.async_call( + SWITCH_DOMAIN, + SERVICE_TURN_OFF, + {ATTR_ENTITY_ID: "switch.relay_swap_outputs"}, + blocking=True, + ) + assert device.swap_outputs is False + + +@pytest.mark.parametrize( + "device_buckets", + [ + { + "micromodule_relays": [ + micromodule_relay_device(supports_switch_configuration=False) + ] + } + ], + indirect=True, +) +@pytest.mark.usefixtures("mock_session") +async def test_micromodule_relay_no_switch_configuration_support( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, +) -> None: + """No swap-inputs/outputs switches are created without switch-configuration support.""" + await setup_integration(hass, mock_config_entry) + + assert hass.states.get("switch.relay_swap_inputs") is None + assert hass.states.get("switch.relay_swap_outputs") is None