mirror of
https://github.com/home-assistant/core.git
synced 2026-10-08 07:19:32 -04:00
bosch_shc: add Motion Detector II motion sensitivity select (#183732)
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5.5
parent
6a686b9105
commit
534202fe0a
@@ -4,8 +4,9 @@ from collections.abc import Callable, Coroutine, Sequence
|
||||
from dataclasses import dataclass
|
||||
from typing import TYPE_CHECKING, Any, override
|
||||
|
||||
from boschshcpy import OutdoorSirenService, SHCOutdoorSiren
|
||||
from boschshcpy import OutdoorSirenService, SHCMotionDetector2, SHCOutdoorSiren
|
||||
from boschshcpy.device import SHCDevice
|
||||
from boschshcpy.services_impl import PirSensorConfigurationService
|
||||
|
||||
from homeassistant.components.select import SelectEntity, SelectEntityDescription
|
||||
from homeassistant.const import EntityCategory
|
||||
@@ -52,6 +53,23 @@ SIREN_SOUND_LEVEL_DESCRIPTION = SHCSelectEntityDescription[SHCOutdoorSiren](
|
||||
)
|
||||
|
||||
|
||||
async def _motion_select_option(device: SHCMotionDetector2, option: str) -> None:
|
||||
"""Write the Motion Detector II's motion sensitivity."""
|
||||
await device.async_set_motion_sensitivity(
|
||||
PirSensorConfigurationService.MotionSensitivity[option.upper()]
|
||||
)
|
||||
|
||||
|
||||
MOTION_SENSITIVITY_DESCRIPTION = SHCSelectEntityDescription[SHCMotionDetector2](
|
||||
key="motion_sensitivity",
|
||||
translation_key="motion_sensitivity",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
options=["high", "middle", "low"],
|
||||
current_option_fn=lambda device, options: device.motion_sensitivity.name.lower(),
|
||||
select_option_fn=_motion_select_option,
|
||||
)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: BoschConfigEntry,
|
||||
@@ -75,6 +93,25 @@ async def async_setup_entry(
|
||||
if siren.siren is not None
|
||||
)
|
||||
|
||||
motion_detectors: list[SHCMotionDetector2] = []
|
||||
for detector in session.device_helper.motion_detectors2:
|
||||
try:
|
||||
_ = detector.motion_sensitivity
|
||||
except AttributeError:
|
||||
continue
|
||||
motion_detectors.append(detector)
|
||||
|
||||
async_add_entities(
|
||||
SHCSelect(
|
||||
hass=hass,
|
||||
device=detector,
|
||||
parent_id=shc_info.unique_id,
|
||||
entry_id=config_entry.entry_id,
|
||||
description=MOTION_SENSITIVITY_DESCRIPTION,
|
||||
)
|
||||
for detector in motion_detectors
|
||||
)
|
||||
|
||||
|
||||
class SHCSelect[_DeviceT: SHCDevice](SHCEntity, SelectEntity):
|
||||
"""Generic SHC select entity, driven by a SHCSelectEntityDescription."""
|
||||
|
||||
@@ -71,6 +71,14 @@
|
||||
}
|
||||
},
|
||||
"select": {
|
||||
"motion_sensitivity": {
|
||||
"name": "Motion sensitivity",
|
||||
"state": {
|
||||
"high": "High",
|
||||
"low": "Low",
|
||||
"middle": "Medium"
|
||||
}
|
||||
},
|
||||
"siren_sound_level": {
|
||||
"name": "Siren volume",
|
||||
"state": {
|
||||
|
||||
@@ -44,6 +44,7 @@ from boschshcpy import (
|
||||
)
|
||||
from boschshcpy.services_impl import (
|
||||
OutdoorSirenService,
|
||||
PirSensorConfigurationService,
|
||||
PresenceSimulationConfigurationService,
|
||||
ValveTappetService,
|
||||
)
|
||||
@@ -633,6 +634,8 @@ def motion_detector2_device(
|
||||
supports_smart_sensitivity: bool = False,
|
||||
smart_sensitivity_enabled: bool = False,
|
||||
latestmotion: str = "",
|
||||
motion_sensitivity: PirSensorConfigurationService.MotionSensitivity
|
||||
| None = PirSensorConfigurationService.MotionSensitivity.MIDDLE,
|
||||
) -> SHCMotionDetector2:
|
||||
"""Build a minimal device double for the motion_detectors2 bucket."""
|
||||
device = create_autospec(SHCMotionDetector2, instance=True, spec_set=True)
|
||||
@@ -650,6 +653,10 @@ def motion_detector2_device(
|
||||
device.supports_smart_sensitivity = supports_smart_sensitivity
|
||||
device.smart_sensitivity_enabled = smart_sensitivity_enabled
|
||||
device.latestmotion = latestmotion
|
||||
if motion_sensitivity is None:
|
||||
del device.motion_sensitivity
|
||||
else:
|
||||
device.motion_sensitivity = motion_sensitivity
|
||||
return device
|
||||
|
||||
|
||||
|
||||
@@ -2,18 +2,19 @@
|
||||
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from boschshcpy.services_impl import OutdoorSirenService
|
||||
from boschshcpy.services_impl import OutdoorSirenService, PirSensorConfigurationService
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.select import DOMAIN as SELECT_DOMAIN
|
||||
from homeassistant.const import ATTR_ENTITY_ID, SERVICE_SELECT_OPTION
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from .conftest import outdoor_siren_device, setup_integration
|
||||
from .conftest import motion_detector2_device, outdoor_siren_device, setup_integration
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
SOUND_LEVEL_ENTITY_ID = "select.outdoor_siren_siren_volume"
|
||||
MOTION_SENSITIVITY_ENTITY_ID = "select.motion_detector_motion_sensitivity"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -82,3 +83,71 @@ async def test_outdoor_siren_no_siren_service(
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
assert hass.states.get(SOUND_LEVEL_ENTITY_ID) is None
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"device_buckets",
|
||||
[
|
||||
{
|
||||
"motion_detectors2": [
|
||||
motion_detector2_device(
|
||||
motion_sensitivity=PirSensorConfigurationService.MotionSensitivity.LOW
|
||||
)
|
||||
]
|
||||
}
|
||||
],
|
||||
indirect=True,
|
||||
)
|
||||
@pytest.mark.usefixtures("mock_session")
|
||||
async def test_motion_sensitivity_current_option(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""The Motion Detector II's current motion sensitivity is reported."""
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
state = hass.states.get(MOTION_SENSITIVITY_ENTITY_ID)
|
||||
assert state is not None
|
||||
assert state.state == "low"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"device_buckets",
|
||||
[{"motion_detectors2": [motion_detector2_device()]}],
|
||||
indirect=True,
|
||||
)
|
||||
@pytest.mark.usefixtures("mock_session")
|
||||
async def test_motion_sensitivity_select_option(
|
||||
hass: HomeAssistant,
|
||||
mock_session: MagicMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Selecting a motion sensitivity writes it to the device."""
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
device = mock_session.device_helper.motion_detectors2[0]
|
||||
|
||||
await hass.services.async_call(
|
||||
SELECT_DOMAIN,
|
||||
SERVICE_SELECT_OPTION,
|
||||
{ATTR_ENTITY_ID: MOTION_SENSITIVITY_ENTITY_ID, "option": "high"},
|
||||
blocking=True,
|
||||
)
|
||||
device.async_set_motion_sensitivity.assert_awaited_once_with(
|
||||
PirSensorConfigurationService.MotionSensitivity.HIGH
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"device_buckets",
|
||||
[{"motion_detectors2": [motion_detector2_device(motion_sensitivity=None)]}],
|
||||
indirect=True,
|
||||
)
|
||||
@pytest.mark.usefixtures("mock_session")
|
||||
async def test_motion_sensitivity_not_supported(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""No select entity is created without the motion sensitivity service."""
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
assert hass.states.get(MOTION_SENSITIVITY_ENTITY_ID) is None
|
||||
|
||||
Reference in New Issue
Block a user