From d4ad11f61ba4ca5d5fc51a4f171643b5c4b7a8d6 Mon Sep 17 00:00:00 2001 From: Ilia Sotnikov Date: Tue, 21 Jun 2022 09:11:00 +0300 Subject: [PATCH 1/8] + Added support for handling alerts from the alarm panel when doorbell sensor is activated --- src/pyg90alarm/const.py | 8 ++++++++ src/pyg90alarm/device_notifications.py | 9 +++++---- tests/test_notifications.py | 23 +++++++++++++++++++---- 3 files changed, 32 insertions(+), 8 deletions(-) diff --git a/src/pyg90alarm/const.py b/src/pyg90alarm/const.py index 0ed466b..bd6b7df 100644 --- a/src/pyg90alarm/const.py +++ b/src/pyg90alarm/const.py @@ -180,6 +180,14 @@ class G90AlertTypes(IntEnum): DOOR_OPEN_CLOSE = 4 +class G90AlertSources(IntEnum): + """ + Defines possible sources of the alert sent by the panel. + """ + SENSOR = 1 + DOORBELL = 12 + + class G90AlertStateChangeTypes(IntEnum): """ Defines types of alert for device state changes. diff --git a/src/pyg90alarm/device_notifications.py b/src/pyg90alarm/device_notifications.py index 252bf35..98c7c77 100644 --- a/src/pyg90alarm/device_notifications.py +++ b/src/pyg90alarm/device_notifications.py @@ -33,6 +33,7 @@ G90AlertTypes, G90AlertStateChangeTypes, G90ArmDisarmTypes, + G90AlertSources, ) _LOGGER = logging.getLogger(__name__) @@ -75,7 +76,7 @@ class G90ArmDisarmInfo(namedtuple('G90ArmDisarmInfo', class G90DeviceAlert(namedtuple('G90DeviceAlert', - ['type', 'event_id', 'resv2', 'resv3', + ['type', 'event_id', 'source', 'state', 'zone_name', 'device_id', 'unix_time', 'resv4', 'other'])): """ @@ -138,9 +139,9 @@ def _handle_notification(self, addr, notification): def _handle_alert(self, addr, alert): if alert.type == G90AlertTypes.DOOR_OPEN_CLOSE: - # `.resv3` field indicates whether the door is opened (1) or closed - # (0) - is_open = alert.resv3 == 1 + is_open = ( + alert.source == G90AlertSources.SENSOR and alert.state == 1 + ) or alert.source == G90AlertSources.DOORBELL _LOGGER.debug('Door open_close alert: %s', alert) G90Callback.invoke(self._door_open_close_cb, alert.event_id, alert.zone_name, diff --git a/tests/test_notifications.py b/tests/test_notifications.py index 9d3d227..a06765c 100644 --- a/tests/test_notifications.py +++ b/tests/test_notifications.py @@ -109,12 +109,12 @@ def sock_data_awaitable(*args): 'ERROR:pyg90alarm.device_notifications:' 'Bad alert received from mocked:12345:' " () missing 9 required positional arguments: 'type'," - " 'event_id', 'resv2', 'resv3', 'zone_name', 'device_id'," + " 'event_id', 'source', 'state', 'zone_name', 'device_id'," " 'unix_time', 'resv4', and 'other'", 'ERROR:pyg90alarm.device_notifications:' 'Bad alert received from mocked:12345:' " __new__() missing 9 required positional arguments: 'type'," - " 'event_id', 'resv2', 'resv3', 'zone_name', 'device_id'," + " 'event_id', 'source', 'state', 'zone_name', 'device_id'," " 'unix_time', 'resv4', and 'other'", ]) notifications.close() @@ -153,8 +153,8 @@ def sock_data_awaitable(*args): self.assertEqual(cm.output, [ 'WARNING:pyg90alarm.device_notifications:' 'Unknown alert received from mocked:12345: type 999,' - ' data G90DeviceAlert(type=999, event_id=100, resv2=1,' - " resv3=1, zone_name='Hall', device_id='DUMMYGUID'," + ' data G90DeviceAlert(type=999, event_id=100, source=1,' + " state=1, zone_name='Hall', device_id='DUMMYGUID'," " unix_time=1631545189, resv4=0, other=[''])" ]) notifications.close() @@ -216,3 +216,18 @@ async def test_door_open_close_callback(self): await asyncio.wait([future], timeout=0.1) notifications.close() door_open_close_cb.assert_called_once_with(100, 'Hall', True) + + async def test_doorbell_callback(self): + future = self.loop.create_future() + door_open_close_cb = MagicMock() + door_open_close_cb.side_effect = lambda *args: future.set_result(True) + notifications = G90DeviceNotifications( + door_open_close_cb=door_open_close_cb, sock=self.socket_mock) + await notifications.listen() + asynctest.set_read_ready(self.socket_mock, self.loop) + self.socket_mock.recvfrom.return_value = ( + b'[208,[4,111,12,0,"Doorbell","DUMMYGUID",1655745021,0,[""]]]\0', + ('mocked', 12345)) + await asyncio.wait([future], timeout=0.1) + notifications.close() + door_open_close_cb.assert_called_once_with(111, 'Doorbell', True) From a4ddf3b82cb7c9225734009ae5878e13382a3b1a Mon Sep 17 00:00:00 2001 From: Ilia Sotnikov Date: Tue, 21 Jun 2022 10:47:32 +0300 Subject: [PATCH 2/8] * `src/`: Linting fixes --- src/pyg90alarm/base_cmd.py | 2 +- src/pyg90alarm/targeted_discovery.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pyg90alarm/base_cmd.py b/src/pyg90alarm/base_cmd.py index 4478d27..9852762 100644 --- a/src/pyg90alarm/base_cmd.py +++ b/src/pyg90alarm/base_cmd.py @@ -132,7 +132,7 @@ def __init__(self, host, port, code, self._resp = G90Header() self._sock = sock - def _proto_factory(self): # pylint: disable=no-self-use + def _proto_factory(self): """ tbd """ diff --git a/src/pyg90alarm/targeted_discovery.py b/src/pyg90alarm/targeted_discovery.py index 8fec61f..a221a8a 100644 --- a/src/pyg90alarm/targeted_discovery.py +++ b/src/pyg90alarm/targeted_discovery.py @@ -119,7 +119,7 @@ def __init__(self, device_id, **kwargs): super().__init__(**kwargs) self._device_id = device_id - def to_wire(self): # pylint: disable=no-self-use + def to_wire(self): """ tbd """ From d2f78ce7fd2db8dc4b49c458c25ef1f990a1f562 Mon Sep 17 00:00:00 2001 From: Ilia Sotnikov Date: Tue, 21 Jun 2022 10:47:46 +0300 Subject: [PATCH 3/8] * `tox`: Added version specifiers to the test dependencies to (hopefully) ensure stable test environment --- tox.ini | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tox.ini b/tox.ini index 909fb82..7872a57 100644 --- a/tox.ini +++ b/tox.ini @@ -15,10 +15,10 @@ isolated_build = true [testenv] deps = check-manifest >= 0.42 - flake8 - asynctest - pylint - coverage + flake8 >= 4.0.1 + asynctest >= 0.13.0 + pylint >= 2.14 + coverage >= 6.1.2 setenv = # Ensure the module under test will be found under `src/` directory, in # case of any test command below will attempt importing it. In particular, From 57378661d61805a080f10938ebcbd63de69b5e1e Mon Sep 17 00:00:00 2001 From: Ilia Sotnikov Date: Tue, 21 Jun 2022 10:52:51 +0300 Subject: [PATCH 4/8] * `tox`: Fixed required version of `pylint` --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 7872a57..5e7cf7f 100644 --- a/tox.ini +++ b/tox.ini @@ -17,7 +17,7 @@ deps = check-manifest >= 0.42 flake8 >= 4.0.1 asynctest >= 0.13.0 - pylint >= 2.14 + pylint >= 2.13.9 coverage >= 6.1.2 setenv = # Ensure the module under test will be found under `src/` directory, in From a71309b5eeadc2ada19e60f5754cbefd9e81c2bc Mon Sep 17 00:00:00 2001 From: Ilia Sotnikov Date: Tue, 21 Jun 2022 10:55:25 +0300 Subject: [PATCH 5/8] Revert "* `src/`: Linting fixes" This reverts commit a4ddf3b82cb7c9225734009ae5878e13382a3b1a. --- src/pyg90alarm/base_cmd.py | 2 +- src/pyg90alarm/targeted_discovery.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pyg90alarm/base_cmd.py b/src/pyg90alarm/base_cmd.py index 9852762..4478d27 100644 --- a/src/pyg90alarm/base_cmd.py +++ b/src/pyg90alarm/base_cmd.py @@ -132,7 +132,7 @@ def __init__(self, host, port, code, self._resp = G90Header() self._sock = sock - def _proto_factory(self): + def _proto_factory(self): # pylint: disable=no-self-use """ tbd """ diff --git a/src/pyg90alarm/targeted_discovery.py b/src/pyg90alarm/targeted_discovery.py index a221a8a..8fec61f 100644 --- a/src/pyg90alarm/targeted_discovery.py +++ b/src/pyg90alarm/targeted_discovery.py @@ -119,7 +119,7 @@ def __init__(self, device_id, **kwargs): super().__init__(**kwargs) self._device_id = device_id - def to_wire(self): + def to_wire(self): # pylint: disable=no-self-use """ tbd """ From 93780f50caf2d1920d4d42fe9b1d7a73ca340649 Mon Sep 17 00:00:00 2001 From: Ilia Sotnikov Date: Tue, 21 Jun 2022 10:59:55 +0300 Subject: [PATCH 6/8] * `tox`: Use exact version specifier for testing dependencies for predictable results --- tox.ini | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tox.ini b/tox.ini index 5e7cf7f..6cb8b45 100644 --- a/tox.ini +++ b/tox.ini @@ -14,11 +14,11 @@ isolated_build = true [testenv] deps = - check-manifest >= 0.42 - flake8 >= 4.0.1 - asynctest >= 0.13.0 - pylint >= 2.13.9 - coverage >= 6.1.2 + check-manifest == 0.48 + flake8 == 4.0.1 + aynctest == 0.13.0 + pylint == 2.13.9 + coverage == 6.4.1 setenv = # Ensure the module under test will be found under `src/` directory, in # case of any test command below will attempt importing it. In particular, From 34d8b3ce7d6c21db937fc0af31e9351ced53e500 Mon Sep 17 00:00:00 2001 From: Ilia Sotnikov Date: Tue, 21 Jun 2022 11:05:10 +0300 Subject: [PATCH 7/8] * `tox`: Fixed typo in `asynctest` package name --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 6cb8b45..25e07a0 100644 --- a/tox.ini +++ b/tox.ini @@ -16,7 +16,7 @@ isolated_build = true deps = check-manifest == 0.48 flake8 == 4.0.1 - aynctest == 0.13.0 + asynctest == 0.13.0 pylint == 2.13.9 coverage == 6.4.1 setenv = From 99ae9a510059b2314131e4215a902018a744d24b Mon Sep 17 00:00:00 2001 From: Ilia Sotnikov Date: Tue, 21 Jun 2022 11:11:38 +0300 Subject: [PATCH 8/8] * `tox`: Fixed version requirement for `coverage` to use most recent one available for Python 3.6 --- tox.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tox.ini b/tox.ini index 25e07a0..da11910 100644 --- a/tox.ini +++ b/tox.ini @@ -17,8 +17,8 @@ deps = check-manifest == 0.48 flake8 == 4.0.1 asynctest == 0.13.0 - pylint == 2.13.9 - coverage == 6.4.1 + pylint == 2.13.9 # Most recent for Python 3.6 + coverage == 6.2 # Most recent for Python 3.6 setenv = # Ensure the module under test will be found under `src/` directory, in # case of any test command below will attempt importing it. In particular,