diff --git a/homeassistant/components/bosch_shc/number.py b/homeassistant/components/bosch_shc/number.py index bd2aa9a0815f..be47538cd7ca 100644 --- a/homeassistant/components/bosch_shc/number.py +++ b/homeassistant/components/bosch_shc/number.py @@ -4,7 +4,7 @@ from collections.abc import Callable, Coroutine from dataclasses import dataclass from typing import TYPE_CHECKING, Any, override -from boschshcpy import SHCMicromoduleRelay +from boschshcpy import SHCMicromoduleRelay, SHCShutterContact2 from boschshcpy.device import SHCDevice from homeassistant.components.number import ( @@ -66,6 +66,21 @@ NUMBER_TYPES: dict[str, SHCNumberEntityDescription] = { value_fn=_impulse_length_value_fn, set_value_fn=_impulse_length_set_value_fn, ), + "bypass_timeout": SHCNumberEntityDescription[SHCShutterContact2]( + key="bypass_timeout", + translation_key="bypass_timeout", + entity_category=EntityCategory.CONFIG, + device_class=NumberDeviceClass.DURATION, + native_unit_of_measurement=UnitOfTime.MINUTES, + native_min_value=1.0, + native_max_value=15.0, + native_step=1.0, + mode=NumberMode.BOX, + value_fn=lambda device: float(device.bypass_timeout), + set_value_fn=lambda device, value: device.async_set_bypass_timeout( + round(value) + ), + ), } @@ -101,6 +116,17 @@ async def async_setup_entry( ) ) + entities.extend( + SHCNumber( + hass=hass, + device=device, + parent_id=shc_info.unique_id, + entry_id=config_entry.entry_id, + description=NUMBER_TYPES["bypass_timeout"], + ) + for device in session.device_helper.shutter_contacts2 + ) + async_add_entities(entities) diff --git a/homeassistant/components/bosch_shc/strings.json b/homeassistant/components/bosch_shc/strings.json index f3e6d9b6a505..f00d6c613641 100644 --- a/homeassistant/components/bosch_shc/strings.json +++ b/homeassistant/components/bosch_shc/strings.json @@ -60,6 +60,9 @@ } }, "number": { + "bypass_timeout": { + "name": "Break function timeout" + }, "impulse_length": { "name": "Pulse length" } diff --git a/tests/components/bosch_shc/conftest.py b/tests/components/bosch_shc/conftest.py index a11b0663ea1c..3c201910a698 100644 --- a/tests/components/bosch_shc/conftest.py +++ b/tests/components/bosch_shc/conftest.py @@ -463,6 +463,7 @@ def shutter_contact2_device( name: str = "Shutter contact", bypass: BypassService.State = BypassService.State.BYPASS_INACTIVE, bypass_infinite: bool = False, + bypass_timeout: int = 5, ) -> SHCShutterContact2: """Build a minimal device double for the shutter_contacts2 bucket.""" device = create_autospec(SHCShutterContact2, instance=True, spec_set=True) @@ -477,6 +478,7 @@ def shutter_contact2_device( device.status = "AVAILABLE" device.bypass = bypass device.bypass_infinite = bypass_infinite + device.bypass_timeout = bypass_timeout return device diff --git a/tests/components/bosch_shc/test_number.py b/tests/components/bosch_shc/test_number.py index e2a7fde4790b..25fb2cd88016 100644 --- a/tests/components/bosch_shc/test_number.py +++ b/tests/components/bosch_shc/test_number.py @@ -12,11 +12,16 @@ from homeassistant.components.number import ( from homeassistant.const import ATTR_ENTITY_ID from homeassistant.core import HomeAssistant -from .conftest import micromodule_relay_device, setup_integration +from .conftest import ( + micromodule_relay_device, + setup_integration, + shutter_contact2_device, +) from tests.common import MockConfigEntry IMPULSE_LENGTH_ENTITY_ID = "number.relay_pulse_length" +BYPASS_TIMEOUT_ENTITY_ID = "number.shutter_contact_break_function_timeout" @pytest.mark.parametrize( @@ -99,3 +104,45 @@ async def test_micromodule_impulse_relay_impulse_length_partial_poll( state = hass.states.get(IMPULSE_LENGTH_ENTITY_ID) assert state is not None assert state.state == "unknown" + + +@pytest.mark.parametrize( + "device_buckets", + [{"shutter_contacts2": [shutter_contact2_device(bypass_timeout=7)]}], + indirect=True, +) +@pytest.mark.usefixtures("mock_session") +async def test_shutter_contact2_bypass_timeout_value( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, +) -> None: + """The bypass timeout is reported in minutes.""" + await setup_integration(hass, mock_config_entry) + + state = hass.states.get(BYPASS_TIMEOUT_ENTITY_ID) + assert state is not None + assert state.state == "7.0" + + +@pytest.mark.parametrize( + "device_buckets", + [{"shutter_contacts2": [shutter_contact2_device()]}], + indirect=True, +) +@pytest.mark.usefixtures("mock_session") +async def test_shutter_contact2_bypass_timeout_set_value( + hass: HomeAssistant, + mock_session: MagicMock, + mock_config_entry: MockConfigEntry, +) -> None: + """Setting a value writes the rounded minutes to the device.""" + await setup_integration(hass, mock_config_entry) + device = mock_session.device_helper.shutter_contacts2[0] + + await hass.services.async_call( + NUMBER_DOMAIN, + SERVICE_SET_VALUE, + {ATTR_ENTITY_ID: BYPASS_TIMEOUT_ENTITY_ID, ATTR_VALUE: 10.6}, + blocking=True, + ) + device.async_set_bypass_timeout.assert_awaited_once_with(11)