diff --git a/plugwise_usb/api.py b/plugwise_usb/api.py index 0350fdadb..97873d328 100644 --- a/plugwise_usb/api.py +++ b/plugwise_usb/api.py @@ -233,14 +233,14 @@ class MotionConfig: Attributes: reset_timer: int | None: Motion reset timer in minutes before the motion detection is switched off. daylight_mode: bool | None: Motion detection when light level is below threshold. - sensitivity_level: int | None: Motion sensitivity level. + sensitivity_level: MotionSensitivity | None: Motion sensitivity level. dirty: bool: Settings changed, device update pending """ daylight_mode: bool | None = None reset_timer: int | None = None - sensitivity_level: int | None = None + sensitivity_level: MotionSensitivity | None = None dirty: bool = False @@ -678,6 +678,13 @@ async def set_motion_sensitivity_level(self, level: MotionSensitivity) -> bool: """ + async def scan_calibrate_light(self) -> bool: + """Request to calibration light sensitivity of Scan device. + + Description: + Request to calibration light sensitivity of Scan device. + """ + async def set_relay_init(self, state: bool) -> bool: """Change the initial state of the relay. diff --git a/plugwise_usb/messages/requests.py b/plugwise_usb/messages/requests.py index 39e07a46b..4e6dc2b36 100644 --- a/plugwise_usb/messages/requests.py +++ b/plugwise_usb/messages/requests.py @@ -9,6 +9,7 @@ import logging from typing import Any +from ..api import MotionSensitivity from ..constants import ( DAY_IN_MINUTES, HOUR_IN_MINUTES, @@ -1376,14 +1377,14 @@ def __init__( send_fn: Callable[[PlugwiseRequest, bool], Awaitable[PlugwiseResponse | None]], mac: bytes, reset_timer: int, - sensitivity: int, + sensitivity: MotionSensitivity, light: bool, ): """Initialize ScanConfigureRequest message object.""" super().__init__(send_fn, mac) reset_timer_value = Int(reset_timer, length=2) # Sensitivity: HIGH(0x14), MEDIUM(0x1E), OFF(0xFF) - sensitivity_value = Int(sensitivity, length=2) + sensitivity_value = Int(sensitivity.value, length=2) light_temp = 1 if light else 0 light_value = Int(light_temp, length=2) self._args += [ diff --git a/plugwise_usb/nodes/scan.py b/plugwise_usb/nodes/scan.py index fc3e84c46..59cee3dde 100644 --- a/plugwise_usb/nodes/scan.py +++ b/plugwise_usb/nodes/scan.py @@ -86,7 +86,7 @@ def __init__( self._motion_state = MotionState() self._motion_config = MotionConfig() - + self._scan_calibrate_light_scheduled = False self._configure_daylight_mode_task: Task[Coroutine[Any, Any, None]] | None = ( None ) @@ -198,7 +198,7 @@ def _reset_timer_from_cache(self) -> int | None: return int(reset_timer) return None - def _sensitivity_level_from_cache(self) -> int | None: + def _sensitivity_level_from_cache(self) -> MotionSensitivity | None: """Load sensitivity level from cache.""" if ( sensitivity_level := self._get_cache( @@ -274,7 +274,7 @@ def reset_timer(self) -> int: return DEFAULT_RESET_TIMER @property - def sensitivity_level(self) -> int: + def sensitivity_level(self) -> MotionSensitivity: """Sensitivity level of motion sensor.""" if self._motion_config.sensitivity_level is not None: return self._motion_config.sensitivity_level @@ -326,13 +326,13 @@ async def set_motion_reset_timer(self, minutes: int) -> bool: await self._scan_configure_update() return True - async def set_motion_sensitivity_level(self, level: int) -> bool: + async def set_motion_sensitivity_level(self, level: MotionSensitivity) -> bool: """Configure the motion sensitivity level.""" _LOGGER.debug( "set_motion_sensitivity_level | Device %s | %s -> %s", self.name, - self._motion_config.sensitivity_level, - level, + self._motion_config.sensitivity_level.name, + level.name, ) if self._motion_config.sensitivity_level == level: return False @@ -426,6 +426,8 @@ async def _run_awake_tasks(self) -> None: await super()._run_awake_tasks() if self._motion_config.dirty: await self._configure_scan_task() + if self._scan_calibrate_light_scheduled: + await self._scan_calibrate_light() await self.publish_feature_update_to_subscribers( NodeFeature.MOTION_CONFIG, self._motion_config, @@ -446,9 +448,9 @@ async def scan_configure(self) -> bool: request = ScanConfigureRequest( self._send, self._mac_in_bytes, - self._motion_config.reset_timer, - self._motion_config.sensitivity_level, - self._motion_config.daylight_mode, + self.reset_timer, + self.sensitivity_level, + self.daylight_mode, ) if (response := await request.send()) is None: _LOGGER.warning( @@ -473,17 +475,13 @@ async def scan_configure(self) -> bool: async def _scan_configure_update(self) -> None: """Push scan configuration update to cache.""" - self._set_cache( - CACHE_SCAN_CONFIG_RESET_TIMER, str(self._motion_config.reset_timer) - ) + self._set_cache(CACHE_SCAN_CONFIG_RESET_TIMER, str(self.reset_timer)) self._set_cache( CACHE_SCAN_CONFIG_SENSITIVITY, - str(MotionSensitivity(self._motion_config.sensitivity_level).name), - ) - self._set_cache( - CACHE_SCAN_CONFIG_DAYLIGHT_MODE, str(self._motion_config.daylight_mode) + self._motion_config.sensitivity_level.name, ) - self._set_cache(CACHE_SCAN_CONFIG_DIRTY, str(self._motion_config.dirty)) + self._set_cache(CACHE_SCAN_CONFIG_DAYLIGHT_MODE, str(self.daylight_mode)) + self._set_cache(CACHE_SCAN_CONFIG_DIRTY, str(self.dirty)) await gather( self.publish_feature_update_to_subscribers( NodeFeature.MOTION_CONFIG, @@ -492,7 +490,11 @@ async def _scan_configure_update(self) -> None: self.save_cache(), ) - async def scan_calibrate_light(self) -> bool: + async def scan_calibrate_light(self) -> None: + """Schedule light sensitivity calibration of Scan device.""" + self._scan_calibrate_light_scheduled = True + + async def _scan_calibrate_light(self) -> bool: """Request to calibration light sensitivity of Scan device.""" request = ScanLightCalibrateRequest(self._send, self._mac_in_bytes) if (response := await request.send()) is not None: @@ -500,6 +502,7 @@ async def scan_calibrate_light(self) -> bool: response.node_ack_type == NodeAckResponseType.SCAN_LIGHT_CALIBRATION_ACCEPTED ): + self._scan_calibrate_light_scheduled = False return True return False raise NodeTimeout( diff --git a/pyproject.toml b/pyproject.toml index 6b79f94dd..1e6f29c52 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "plugwise_usb" -version = "0.44.12a1" +version = "0.44.12a2" license = "MIT" keywords = ["home", "automation", "plugwise", "module", "usb"] classifiers = [ diff --git a/tests/test_usb.py b/tests/test_usb.py index 2c0917bc6..7b20edc5e 100644 --- a/tests/test_usb.py +++ b/tests/test_usb.py @@ -1494,7 +1494,7 @@ async def test_creating_request_messages(self) -> None: self.dummy_fn, b"1111222233334444", 5, # Delay in minutes when signal is send when no motion is detected - 30, # Sensitivity of Motion sensor (High, Medium, Off) + pw_api.MotionSensitivity.MEDIUM, # Sensitivity of Motion sensor (High, Medium, Off) False, # Daylight override to only report motion when lightlevel is below calibrated level ) assert ( @@ -1834,7 +1834,7 @@ async def load_callback(event: pw_api.NodeEvent, mac: str) -> None: # type: ign await test_node.set_motion_daylight_mode(True) with pytest.raises(pw_exceptions.NodeError): - await test_node.set_motion_sensitivity_level(20) + await test_node.set_motion_sensitivity_level(pw_api.MotionSensitivity.HIGH) with pytest.raises(pw_exceptions.NodeError): await test_node.set_motion_reset_timer(5) @@ -1865,7 +1865,7 @@ async def load_callback(event: pw_api.NodeEvent, mac: str) -> None: # type: ign await test_node.set_motion_daylight_mode(True) with pytest.raises(pw_exceptions.FeatureError): - await test_node.set_motion_sensitivity_level(20) + await test_node.set_motion_sensitivity_level(pw_api.MotionSensitivity.HIGH) with pytest.raises(pw_exceptions.FeatureError): await test_node.set_motion_reset_timer(5) @@ -1892,7 +1892,7 @@ async def load_callback(event: pw_api.NodeEvent, mac: str) -> None: # type: ign with pytest.raises(NotImplementedError): await test_node.set_motion_daylight_mode(True) with pytest.raises(NotImplementedError): - await test_node.set_motion_sensitivity_level(20) + await test_node.set_motion_sensitivity_level(pw_api.MotionSensitivity.HIGH) with pytest.raises(NotImplementedError): await test_node.set_motion_reset_timer(5) @@ -2240,12 +2240,18 @@ async def load_callback(event: pw_api.NodeEvent, mac: str) -> None: # type: ign assert test_scan.motion_config.daylight_mode # test motion sensitivity level - assert test_scan.sensitivity_level == 30 - assert test_scan.motion_config.sensitivity_level == 30 - assert not await test_scan.set_motion_sensitivity_level(30) + assert test_scan.sensitivity_level == pw_api.MotionSensitivity.MEDIUM + assert ( + test_scan.motion_config.sensitivity_level == pw_api.MotionSensitivity.MEDIUM + ) + assert not await test_scan.set_motion_sensitivity_level( + pw_api.MotionSensitivity.MEDIUM + ) assert not test_scan.motion_config.dirty - assert await test_scan.set_motion_sensitivity_level(20) + assert await test_scan.set_motion_sensitivity_level( + pw_api.MotionSensitivity.HIGH + ) assert test_scan.motion_config.dirty awake_response4 = pw_responses.NodeAwakeResponse() awake_response4.deserialize( @@ -2257,8 +2263,10 @@ async def load_callback(event: pw_api.NodeEvent, mac: str) -> None: # type: ign await test_scan._awake_response(awake_response4) # pylint: disable=protected-access await asyncio.sleep(0.001) # Ensure time for task to be executed assert not test_scan.motion_config.dirty - assert test_scan.sensitivity_level == 20 - assert test_scan.motion_config.sensitivity_level == 20 + assert test_scan.sensitivity_level == pw_api.MotionSensitivity.HIGH + assert ( + test_scan.motion_config.sensitivity_level == pw_api.MotionSensitivity.HIGH + ) # scan with cache enabled mock_stick_controller.send_response = None