diff --git a/src/pyg90alarm/entities/sensor.py b/src/pyg90alarm/entities/sensor.py index 215cdf4..fce88d7 100644 --- a/src/pyg90alarm/entities/sensor.py +++ b/src/pyg90alarm/entities/sensor.py @@ -104,6 +104,16 @@ class G90SensorUserFlags(IntFlag): ALERT_WHEN_AWAY_AND_HOME = 32 ALERT_WHEN_AWAY = 64 SUPPORTS_UPDATING_SUBTYPE = 512 # Only relevant for cord sensors + # Flags that can be set by the user + USER_SETTABLE = ( + ENABLED + | ARM_DELAY + | DETECT_DOOR + | DOOR_CHIME + | INDEPENDENT_ZONE + | ALERT_WHEN_AWAY_AND_HOME + | ALERT_WHEN_AWAY + ) class G90SensorProtocols(IntEnum): @@ -487,18 +497,26 @@ def enabled(self) -> bool: """ return self.user_flag & G90SensorUserFlags.ENABLED != 0 - async def set_enabled(self, value: bool) -> None: + async def set_user_flag(self, value: G90SensorUserFlags) -> None: """ - Sets disabled/enabled state of the sensor. + Sets user flags of the sensor. - :param value: Whether to enable or disable the sensor + :param value: User flags to set, values other than + :attr:`.G90SensorUserFlags.USER_SETTABLE` will be ignored and + preserved from existing sensor flags. """ + if value & ~G90SensorUserFlags.USER_SETTABLE: + _LOGGER.warning( + 'User flags for sensor index=%s contain non-user settable' + ' flags, those will be ignored: %s', + self.index, repr(value & ~G90SensorUserFlags.USER_SETTABLE) + ) # Checking private attribute directly, since `mypy` doesn't recognize # the check for sensor definition to be defined if done over # `self.supports_enable_disable` property if not self._definition: _LOGGER.warning( - 'Manipulating with enable/disable for sensor index=%s' + 'Manipulating with user flags for sensor index=%s' ' is unsupported - no sensor definition for' ' type=%s, subtype=%s', self.index, @@ -526,7 +544,7 @@ async def set_enabled(self, value: bool) -> None: if not sensors: _LOGGER.error( 'Sensor index=%s not found when attempting to set its' - ' enable/disable state', + ' user flag', self.index, ) return @@ -539,30 +557,30 @@ async def set_enabled(self, value: bool) -> None: ) != self._protocol_data: _LOGGER.error( "Sensor index=%s '%s' has been changed externally," - " refusing to alter its enable/disable state", + " refusing to alter its user flag", self.index, self.name ) return - # Modify the value of the user flag setting enabled/disabled one - # appropriately. - user_flag = self.user_flag - if value: - user_flag |= G90SensorUserFlags.ENABLED - else: - user_flag &= ~G90SensorUserFlags.ENABLED + prev_user_flag = self.user_flag # Re-instantiate the protocol data with modified user flags _data = asdict(self._protocol_data) - _data['user_flag_data'] = user_flag + _data['user_flag_data'] = ( + # Preserve flags that are not user-settable + self.user_flag & ~G90SensorUserFlags.USER_SETTABLE + ) | ( + # Combine them with the new user-settable flags + value & G90SensorUserFlags.USER_SETTABLE + ) self._protocol_data = self._protocol_incoming_data_kls(**_data) _LOGGER.debug( - 'Sensor index=%s: %s enabled flag, resulting user_flag %s', + 'Sensor index=%s: previous user_flag %s, resulting user_flag %s', self._protocol_data.index, - 'Setting' if value else 'Clearing', - self.user_flag + repr(prev_user_flag), + repr(self.user_flag) ) # Generate protocol data from write operation, deriving values either @@ -589,6 +607,20 @@ async def set_enabled(self, value: bool) -> None: G90Commands.SETSINGLESENSOR, list(astuple(outgoing_data)) ) + async def set_enabled(self, value: bool) -> None: + """ + Sets the sensor enabled/disabled. + """ + + # Modify the value of the user flag setting enabled/disabled one + # appropriately. + user_flag = self.user_flag + if value: + user_flag |= G90SensorUserFlags.ENABLED + else: + user_flag &= ~G90SensorUserFlags.ENABLED + await self.set_user_flag(user_flag) + @property def extra_data(self) -> Any: """ @@ -623,6 +655,8 @@ def _asdict(self) -> Dict[str, Any]: 'supports_enable_disable': self.supports_enable_disable, 'is_wireless': self.is_wireless, 'is_low_battery': self.is_low_battery, + 'is_tampered': self.is_tampered, + 'is_door_open_when_arming': self.is_door_open_when_arming, } def __repr__(self) -> str: diff --git a/tests/test_alarm.py b/tests/test_alarm.py index 656e570..21f49d4 100644 --- a/tests/test_alarm.py +++ b/tests/test_alarm.py @@ -15,7 +15,7 @@ G90HostInfo, G90HostInfoGsmStatus, G90HostInfoWifiStatus, ) from pyg90alarm.entities.sensor import ( - G90Sensor, + G90Sensor, G90SensorUserFlags, ) from pyg90alarm.entities.device import ( G90Device, @@ -1099,3 +1099,36 @@ async def test_device_unsupported_disable(mock_device: DeviceMock) -> None: assert mock_device.recv_data == [ b'ISTART[138,138,[138,[1,10]]]IEND\0', ] + + +@pytest.mark.g90device(sent_data=[ + b'ISTART[102,' + b'[[1,1,1],' + b'["Night Light2",10,0,138,0,0,33,0,0,17,1,0,""]' + b']]IEND\0', + b'ISTART[102,' + b'[[1,1,1],' + b'["Night Light2",10,0,138,0,0,33,0,0,17,1,0,""]' + b']]IEND\0', + b"ISTARTIEND\0", +]) +async def test_sensor_set_user_flags(mock_device: DeviceMock) -> None: + """ + Tests for setting user flags on a sensor. + """ + g90 = G90Alarm(host=mock_device.host, port=mock_device.port) + sensors = await g90.get_sensors() + await sensors[0].set_user_flag( + # Intentionally contains non-user settable flag, which should be + # ignored and not configured for the sensor that initial doesn't have + # it set + G90SensorUserFlags.INDEPENDENT_ZONE | G90SensorUserFlags.ARM_DELAY + | G90SensorUserFlags.SUPPORTS_UPDATING_SUBTYPE + ) + assert mock_device.recv_data == [ + b'ISTART[102,102,[102,[1,10]]]IEND\0', + b'ISTART[102,102,[102,[1,1]]]IEND\0', + b'ISTART[103,103,[103,' + b'["Night Light2",10,0,138,0,0,18,0,0,17,1,0,2,"060A0600"]' + b']]IEND\0', + ]