pyg90alarm 2.0.1__tar.gz → 2.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pyg90alarm-2.0.1 → pyg90alarm-2.1.0}/PKG-INFO +10 -9
- {pyg90alarm-2.0.1 → pyg90alarm-2.1.0}/README.rst +9 -8
- {pyg90alarm-2.0.1 → pyg90alarm-2.1.0}/src/pyg90alarm/__init__.py +7 -3
- {pyg90alarm-2.0.1 → pyg90alarm-2.1.0}/src/pyg90alarm/alarm.py +21 -46
- {pyg90alarm-2.0.1 → pyg90alarm-2.1.0}/src/pyg90alarm/entities/device.py +8 -8
- {pyg90alarm-2.0.1 → pyg90alarm-2.1.0}/src/pyg90alarm/entities/sensor.py +211 -42
- pyg90alarm-2.1.0/src/pyg90alarm/local/config.py +158 -0
- {pyg90alarm-2.0.1 → pyg90alarm-2.1.0}/src/pyg90alarm.egg-info/PKG-INFO +10 -9
- {pyg90alarm-2.0.1 → pyg90alarm-2.1.0}/src/pyg90alarm.egg-info/SOURCES.txt +3 -1
- {pyg90alarm-2.0.1 → pyg90alarm-2.1.0}/tests/test_alarm.py +10 -284
- pyg90alarm-2.1.0/tests/test_config.py +52 -0
- pyg90alarm-2.1.0/tests/test_sensor.py +410 -0
- pyg90alarm-2.0.1/src/pyg90alarm/local/config.py +0 -59
- {pyg90alarm-2.0.1 → pyg90alarm-2.1.0}/.github/CODEOWNERS +0 -0
- {pyg90alarm-2.0.1 → pyg90alarm-2.1.0}/.github/workflows/main.yml +0 -0
- {pyg90alarm-2.0.1 → pyg90alarm-2.1.0}/.gitignore +0 -0
- {pyg90alarm-2.0.1 → pyg90alarm-2.1.0}/.pylintrc +0 -0
- {pyg90alarm-2.0.1 → pyg90alarm-2.1.0}/.readthedocs.yaml +0 -0
- {pyg90alarm-2.0.1 → pyg90alarm-2.1.0}/LICENSE +0 -0
- {pyg90alarm-2.0.1 → pyg90alarm-2.1.0}/MANIFEST.in +0 -0
- {pyg90alarm-2.0.1 → pyg90alarm-2.1.0}/docs/.DS_Store +0 -0
- {pyg90alarm-2.0.1 → pyg90alarm-2.1.0}/docs/.gitignore +0 -0
- {pyg90alarm-2.0.1 → pyg90alarm-2.1.0}/docs/api-docs.rst +0 -0
- {pyg90alarm-2.0.1 → pyg90alarm-2.1.0}/docs/cloud-protocol.rst +0 -0
- {pyg90alarm-2.0.1 → pyg90alarm-2.1.0}/docs/conf.py +0 -0
- {pyg90alarm-2.0.1 → pyg90alarm-2.1.0}/docs/index.rst +0 -0
- {pyg90alarm-2.0.1 → pyg90alarm-2.1.0}/docs/local-protocol.rst +0 -0
- {pyg90alarm-2.0.1 → pyg90alarm-2.1.0}/docs/requirements.txt +0 -0
- {pyg90alarm-2.0.1 → pyg90alarm-2.1.0}/pyproject.toml +0 -0
- {pyg90alarm-2.0.1 → pyg90alarm-2.1.0}/setup.cfg +0 -0
- {pyg90alarm-2.0.1 → pyg90alarm-2.1.0}/setup.py +0 -0
- {pyg90alarm-2.0.1 → pyg90alarm-2.1.0}/sonar-project.properties +0 -0
- {pyg90alarm-2.0.1 → pyg90alarm-2.1.0}/src/pyg90alarm/callback.py +0 -0
- {pyg90alarm-2.0.1 → pyg90alarm-2.1.0}/src/pyg90alarm/cloud/__init__.py +0 -0
- {pyg90alarm-2.0.1 → pyg90alarm-2.1.0}/src/pyg90alarm/cloud/const.py +0 -0
- {pyg90alarm-2.0.1 → pyg90alarm-2.1.0}/src/pyg90alarm/cloud/messages.py +0 -0
- {pyg90alarm-2.0.1 → pyg90alarm-2.1.0}/src/pyg90alarm/cloud/notifications.py +0 -0
- {pyg90alarm-2.0.1 → pyg90alarm-2.1.0}/src/pyg90alarm/cloud/protocol.py +0 -0
- {pyg90alarm-2.0.1 → pyg90alarm-2.1.0}/src/pyg90alarm/const.py +0 -0
- {pyg90alarm-2.0.1 → pyg90alarm-2.1.0}/src/pyg90alarm/definitions/__init__.py +0 -0
- {pyg90alarm-2.0.1 → pyg90alarm-2.1.0}/src/pyg90alarm/definitions/sensors.py +0 -0
- {pyg90alarm-2.0.1 → pyg90alarm-2.1.0}/src/pyg90alarm/entities/__init__.py +0 -0
- {pyg90alarm-2.0.1 → pyg90alarm-2.1.0}/src/pyg90alarm/entities/base_entity.py +0 -0
- {pyg90alarm-2.0.1 → pyg90alarm-2.1.0}/src/pyg90alarm/entities/base_list.py +0 -0
- {pyg90alarm-2.0.1 → pyg90alarm-2.1.0}/src/pyg90alarm/entities/device_list.py +0 -0
- {pyg90alarm-2.0.1 → pyg90alarm-2.1.0}/src/pyg90alarm/entities/sensor_list.py +0 -0
- {pyg90alarm-2.0.1 → pyg90alarm-2.1.0}/src/pyg90alarm/exceptions.py +0 -0
- {pyg90alarm-2.0.1 → pyg90alarm-2.1.0}/src/pyg90alarm/local/__init__.py +0 -0
- {pyg90alarm-2.0.1 → pyg90alarm-2.1.0}/src/pyg90alarm/local/base_cmd.py +0 -0
- {pyg90alarm-2.0.1 → pyg90alarm-2.1.0}/src/pyg90alarm/local/discovery.py +0 -0
- {pyg90alarm-2.0.1 → pyg90alarm-2.1.0}/src/pyg90alarm/local/history.py +0 -0
- {pyg90alarm-2.0.1 → pyg90alarm-2.1.0}/src/pyg90alarm/local/host_info.py +0 -0
- {pyg90alarm-2.0.1 → pyg90alarm-2.1.0}/src/pyg90alarm/local/host_status.py +0 -0
- {pyg90alarm-2.0.1 → pyg90alarm-2.1.0}/src/pyg90alarm/local/notifications.py +0 -0
- {pyg90alarm-2.0.1 → pyg90alarm-2.1.0}/src/pyg90alarm/local/paginated_cmd.py +0 -0
- {pyg90alarm-2.0.1 → pyg90alarm-2.1.0}/src/pyg90alarm/local/paginated_result.py +0 -0
- {pyg90alarm-2.0.1 → pyg90alarm-2.1.0}/src/pyg90alarm/local/targeted_discovery.py +0 -0
- {pyg90alarm-2.0.1 → pyg90alarm-2.1.0}/src/pyg90alarm/local/user_data_crc.py +0 -0
- {pyg90alarm-2.0.1 → pyg90alarm-2.1.0}/src/pyg90alarm/notifications/__init__.py +0 -0
- {pyg90alarm-2.0.1 → pyg90alarm-2.1.0}/src/pyg90alarm/notifications/base.py +0 -0
- {pyg90alarm-2.0.1 → pyg90alarm-2.1.0}/src/pyg90alarm/notifications/protocol.py +0 -0
- {pyg90alarm-2.0.1 → pyg90alarm-2.1.0}/src/pyg90alarm/py.typed +0 -0
- {pyg90alarm-2.0.1 → pyg90alarm-2.1.0}/src/pyg90alarm.egg-info/dependency_links.txt +0 -0
- {pyg90alarm-2.0.1 → pyg90alarm-2.1.0}/src/pyg90alarm.egg-info/requires.txt +0 -0
- {pyg90alarm-2.0.1 → pyg90alarm-2.1.0}/src/pyg90alarm.egg-info/top_level.txt +0 -0
- {pyg90alarm-2.0.1 → pyg90alarm-2.1.0}/tests/__init__.py +0 -0
- {pyg90alarm-2.0.1 → pyg90alarm-2.1.0}/tests/conftest.py +0 -0
- {pyg90alarm-2.0.1 → pyg90alarm-2.1.0}/tests/device_mock.py +0 -0
- {pyg90alarm-2.0.1 → pyg90alarm-2.1.0}/tests/test_base_commands.py +0 -0
- {pyg90alarm-2.0.1 → pyg90alarm-2.1.0}/tests/test_cloud_notifications.py +0 -0
- {pyg90alarm-2.0.1 → pyg90alarm-2.1.0}/tests/test_discovery.py +0 -0
- {pyg90alarm-2.0.1 → pyg90alarm-2.1.0}/tests/test_history.py +0 -0
- {pyg90alarm-2.0.1 → pyg90alarm-2.1.0}/tests/test_local_notifications.py +0 -0
- {pyg90alarm-2.0.1 → pyg90alarm-2.1.0}/tests/test_paginated_commands.py +0 -0
- {pyg90alarm-2.0.1 → pyg90alarm-2.1.0}/tox.ini +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pyg90alarm
|
|
3
|
-
Version: 2.0
|
|
3
|
+
Version: 2.1.0
|
|
4
4
|
Summary: G90 Alarm system protocol
|
|
5
5
|
Home-page: https://github.com/hostcc/pyg90alarm
|
|
6
6
|
Author: Ilia Sotnikov
|
|
@@ -96,8 +96,8 @@ Python, and it opens up a way for further integrations.
|
|
|
96
96
|
Supported hardware
|
|
97
97
|
==================
|
|
98
98
|
|
|
99
|
-
It
|
|
100
|
-
manufacturers
|
|
99
|
+
It might not be possible to list every system supported by the package due to
|
|
100
|
+
manufacturers naming the products differently. Here is the list of hardware
|
|
101
101
|
known to work with the package:
|
|
102
102
|
|
|
103
103
|
* `PST G90B Plus <http://www.cameralarms.com/products/auto_dial_alarm_system/185.html>`_
|
|
@@ -170,7 +170,7 @@ set the IP address allocation up.
|
|
|
170
170
|
alert to be enabled in the mobile application, otherwise it won't be
|
|
171
171
|
recorded in the history.
|
|
172
172
|
|
|
173
|
-
For the local notifications to be enabled the
|
|
173
|
+
For the local notifications to be enabled the ``G90Alarm.use_local_notifications()`` needs to be called upon constructing an instance of ``G90Alarm`` class, then ``G90Alarm.listen_notifications()`` to start processing those coming from the panel - the code fragment below demonstrates that though being incomplete since callbacks (e.g. ``G90Alarm.on_armdisarm()``) should be set for the actual processing of the notifications.
|
|
174
174
|
|
|
175
175
|
.. code:: python
|
|
176
176
|
|
|
@@ -189,10 +189,10 @@ Cloud notifications
|
|
|
189
189
|
The cloud protocol is native to the panel and is used to interact with mobile application. The package can mimic the cloud server and interpret the messages the panel sends to the cloud, allowing to receive the notifications and alerts.
|
|
190
190
|
While the protocol also allows to send commands to the panel, it is not implemented and local protocol is used for that - i.e. when cloud notifications are in use the local protocol still utilized for sending commands to the panel.
|
|
191
191
|
|
|
192
|
-
The cloud protocol is TCP based and typically interacts with cloud service at known IP address and port (not
|
|
192
|
+
The cloud protocol is TCP based and typically interacts with cloud service at known IP address and port (not customizable at panel side). To process the cloud notifications all the traffic from panel towards the cloud (IP address ``47.88.7.61`` and TCP port ``5678`` as of writing) needs to be diverted to the node where the package is running - depending on your network equipment it could be port forwarding, DNAT or other means. It is unclear whether the panel utilizes DNS to resolve the cloud service IP address, hence the documentation only mentions IP-based traffic redirection.
|
|
193
193
|
|
|
194
194
|
Please see
|
|
195
|
-
`
|
|
195
|
+
`the section <docs/cloud-protocol.rst>`_ for further details on the protocol.
|
|
196
196
|
|
|
197
197
|
The benefit of the cloud notifications is that the panel no longer required to have ``10.10.10.250`` IP address.
|
|
198
198
|
|
|
@@ -200,10 +200,10 @@ The package could act as:
|
|
|
200
200
|
|
|
201
201
|
- Standalone cloud server with no Internet connectivity or cloud service
|
|
202
202
|
required at all - good if you'd like to avoid having a vendor service involved. Please note the mobile application will show panel as offline in this mode
|
|
203
|
-
- Chained cloud server, where in addition to
|
|
204
|
-
will also forward all packets received from the panel to the cloud server, and pass its responses back to the panel. This allows to have
|
|
203
|
+
- Chained cloud server, where in addition to interpreting the notifications it
|
|
204
|
+
will also forward all packets received from the panel to the cloud server, and pass its responses back to the panel. This allows to have notifications processed by the package and the mobile application working as well.
|
|
205
205
|
|
|
206
|
-
.. note:: Sending packets upstream to the known IP address and port of the cloud server might result in those looped back (since traffic from panel to cloud service has to be redirected to the host where package runs), if your network equipment can't account for source address in redirection rules (i.e. limiting the port redirection to the panel's IP address). In that case you'll need another redirection, from the host where the package runs to the cloud service using an IP from your network. That way those two redirection rules will coexist correctly. To
|
|
206
|
+
.. note:: Sending packets upstream to the known IP address and port of the cloud server might result in those looped back (since traffic from panel to cloud service has to be redirected to the host where package runs), if your network equipment can't account for source address in redirection rules (i.e. limiting the port redirection to the panel's IP address). In that case you'll need another redirection, from the host where the package runs to the cloud service using an IP from your network. That way those two redirection rules will coexist correctly. To illustrate:
|
|
207
207
|
|
|
208
208
|
Port forwarding rule 1:
|
|
209
209
|
|
|
@@ -213,6 +213,7 @@ The package could act as:
|
|
|
213
213
|
- Redirect to host: host where package runs
|
|
214
214
|
- Redirect to port: 5678 (or other port if you want to use it)
|
|
215
215
|
|
|
216
|
+
|
|
216
217
|
Port forwarding rule 2 (optional):
|
|
217
218
|
|
|
218
219
|
- Source: host where package runs
|
|
@@ -49,8 +49,8 @@ Python, and it opens up a way for further integrations.
|
|
|
49
49
|
Supported hardware
|
|
50
50
|
==================
|
|
51
51
|
|
|
52
|
-
It
|
|
53
|
-
manufacturers
|
|
52
|
+
It might not be possible to list every system supported by the package due to
|
|
53
|
+
manufacturers naming the products differently. Here is the list of hardware
|
|
54
54
|
known to work with the package:
|
|
55
55
|
|
|
56
56
|
* `PST G90B Plus <http://www.cameralarms.com/products/auto_dial_alarm_system/185.html>`_
|
|
@@ -123,7 +123,7 @@ set the IP address allocation up.
|
|
|
123
123
|
alert to be enabled in the mobile application, otherwise it won't be
|
|
124
124
|
recorded in the history.
|
|
125
125
|
|
|
126
|
-
For the local notifications to be enabled the
|
|
126
|
+
For the local notifications to be enabled the ``G90Alarm.use_local_notifications()`` needs to be called upon constructing an instance of ``G90Alarm`` class, then ``G90Alarm.listen_notifications()`` to start processing those coming from the panel - the code fragment below demonstrates that though being incomplete since callbacks (e.g. ``G90Alarm.on_armdisarm()``) should be set for the actual processing of the notifications.
|
|
127
127
|
|
|
128
128
|
.. code:: python
|
|
129
129
|
|
|
@@ -142,10 +142,10 @@ Cloud notifications
|
|
|
142
142
|
The cloud protocol is native to the panel and is used to interact with mobile application. The package can mimic the cloud server and interpret the messages the panel sends to the cloud, allowing to receive the notifications and alerts.
|
|
143
143
|
While the protocol also allows to send commands to the panel, it is not implemented and local protocol is used for that - i.e. when cloud notifications are in use the local protocol still utilized for sending commands to the panel.
|
|
144
144
|
|
|
145
|
-
The cloud protocol is TCP based and typically interacts with cloud service at known IP address and port (not
|
|
145
|
+
The cloud protocol is TCP based and typically interacts with cloud service at known IP address and port (not customizable at panel side). To process the cloud notifications all the traffic from panel towards the cloud (IP address ``47.88.7.61`` and TCP port ``5678`` as of writing) needs to be diverted to the node where the package is running - depending on your network equipment it could be port forwarding, DNAT or other means. It is unclear whether the panel utilizes DNS to resolve the cloud service IP address, hence the documentation only mentions IP-based traffic redirection.
|
|
146
146
|
|
|
147
147
|
Please see
|
|
148
|
-
`
|
|
148
|
+
`the section <docs/cloud-protocol.rst>`_ for further details on the protocol.
|
|
149
149
|
|
|
150
150
|
The benefit of the cloud notifications is that the panel no longer required to have ``10.10.10.250`` IP address.
|
|
151
151
|
|
|
@@ -153,10 +153,10 @@ The package could act as:
|
|
|
153
153
|
|
|
154
154
|
- Standalone cloud server with no Internet connectivity or cloud service
|
|
155
155
|
required at all - good if you'd like to avoid having a vendor service involved. Please note the mobile application will show panel as offline in this mode
|
|
156
|
-
- Chained cloud server, where in addition to
|
|
157
|
-
will also forward all packets received from the panel to the cloud server, and pass its responses back to the panel. This allows to have
|
|
156
|
+
- Chained cloud server, where in addition to interpreting the notifications it
|
|
157
|
+
will also forward all packets received from the panel to the cloud server, and pass its responses back to the panel. This allows to have notifications processed by the package and the mobile application working as well.
|
|
158
158
|
|
|
159
|
-
.. note:: Sending packets upstream to the known IP address and port of the cloud server might result in those looped back (since traffic from panel to cloud service has to be redirected to the host where package runs), if your network equipment can't account for source address in redirection rules (i.e. limiting the port redirection to the panel's IP address). In that case you'll need another redirection, from the host where the package runs to the cloud service using an IP from your network. That way those two redirection rules will coexist correctly. To
|
|
159
|
+
.. note:: Sending packets upstream to the known IP address and port of the cloud server might result in those looped back (since traffic from panel to cloud service has to be redirected to the host where package runs), if your network equipment can't account for source address in redirection rules (i.e. limiting the port redirection to the panel's IP address). In that case you'll need another redirection, from the host where the package runs to the cloud service using an IP from your network. That way those two redirection rules will coexist correctly. To illustrate:
|
|
160
160
|
|
|
161
161
|
Port forwarding rule 1:
|
|
162
162
|
|
|
@@ -166,6 +166,7 @@ The package could act as:
|
|
|
166
166
|
- Redirect to host: host where package runs
|
|
167
167
|
- Redirect to port: 5678 (or other port if you want to use it)
|
|
168
168
|
|
|
169
|
+
|
|
169
170
|
Port forwarding rule 2 (optional):
|
|
170
171
|
|
|
171
172
|
- Source: host where package runs
|
|
@@ -28,7 +28,9 @@ from .local.paginated_result import G90PaginatedResult
|
|
|
28
28
|
from .notifications.base import (
|
|
29
29
|
G90DeviceAlert,
|
|
30
30
|
)
|
|
31
|
-
from .entities.sensor import
|
|
31
|
+
from .entities.sensor import (
|
|
32
|
+
G90Sensor, G90SensorTypes, G90SensorAlertModes, G90SensorUserFlags
|
|
33
|
+
)
|
|
32
34
|
from .entities.device import G90Device
|
|
33
35
|
from .local.host_info import (
|
|
34
36
|
G90HostInfo, G90HostInfoWifiStatus, G90HostInfoGsmStatus
|
|
@@ -48,8 +50,10 @@ from .exceptions import G90Error, G90TimeoutError
|
|
|
48
50
|
|
|
49
51
|
__all__ = [
|
|
50
52
|
'G90Alarm', 'G90BaseCommand', 'G90PaginatedResult', 'G90DeviceAlert',
|
|
51
|
-
'G90Sensor', 'G90SensorTypes', '
|
|
52
|
-
'
|
|
53
|
+
'G90Sensor', 'G90SensorTypes', 'G90SensorAlertModes', 'G90SensorUserFlags',
|
|
54
|
+
'G90Device',
|
|
55
|
+
'G90HostInfo', 'G90HostInfoWifiStatus', 'G90HostInfoGsmStatus',
|
|
56
|
+
'G90HostStatus',
|
|
53
57
|
'G90MessageTypes', 'G90NotificationTypes', 'G90ArmDisarmTypes',
|
|
54
58
|
'G90AlertTypes', 'G90AlertSources', 'G90AlertStates',
|
|
55
59
|
'G90AlertStateChangeTypes', 'G90HistoryStates', 'G90Error',
|
|
@@ -201,7 +201,7 @@ class G90Alarm(G90NotificationProtocol):
|
|
|
201
201
|
] = None
|
|
202
202
|
self._tamper_cb: Optional[TamperCallback] = None
|
|
203
203
|
self._reset_occupancy_interval = reset_occupancy_interval
|
|
204
|
-
self._alert_config
|
|
204
|
+
self._alert_config = G90AlertConfig(self)
|
|
205
205
|
self._sms_alert_when_armed = False
|
|
206
206
|
self._alert_simulation_task: Optional[Task[Any]] = None
|
|
207
207
|
self._alert_simulation_start_listener_back = False
|
|
@@ -385,50 +385,29 @@ class G90Alarm(G90NotificationProtocol):
|
|
|
385
385
|
return G90HostStatus(*res)
|
|
386
386
|
|
|
387
387
|
@property
|
|
388
|
-
|
|
388
|
+
def alert_config(self) -> G90AlertConfig:
|
|
389
389
|
"""
|
|
390
|
-
|
|
391
|
-
compatibility.
|
|
392
|
-
"""
|
|
393
|
-
return await self.get_alert_config()
|
|
394
|
-
|
|
395
|
-
async def get_alert_config(self) -> G90AlertConfigFlags:
|
|
396
|
-
"""
|
|
397
|
-
Retrieves the alert configuration flags from the device. Please note
|
|
398
|
-
the configuration is cached upon first call, so you need to
|
|
399
|
-
re-instantiate the class to reflect any updates there.
|
|
390
|
+
Provides alert configuration object.
|
|
400
391
|
|
|
401
|
-
:return:
|
|
392
|
+
:return: Alert configuration object
|
|
402
393
|
"""
|
|
403
|
-
if not self._alert_config:
|
|
404
|
-
self._alert_config = await self._alert_config_uncached()
|
|
405
394
|
return self._alert_config
|
|
406
395
|
|
|
407
|
-
async def
|
|
396
|
+
async def get_alert_config(self) -> G90AlertConfigFlags:
|
|
408
397
|
"""
|
|
409
|
-
|
|
398
|
+
Provides alert configuration flags, retained for compatibility - using
|
|
399
|
+
`:attr:alert_config` and `:class:G90AlertConfig` is preferred.
|
|
410
400
|
|
|
411
401
|
:return: The alerts configured
|
|
412
402
|
"""
|
|
413
|
-
|
|
414
|
-
return G90AlertConfig(*res).flags
|
|
403
|
+
return await self.alert_config.flags
|
|
415
404
|
|
|
416
405
|
async def set_alert_config(self, flags: G90AlertConfigFlags) -> None:
|
|
417
406
|
"""
|
|
418
|
-
Sets the alert configuration flags
|
|
407
|
+
Sets the alert configuration flags, retained for compatibility - using
|
|
408
|
+
`:attr:alert_config` and `:class:G90AlertConfig` is preferred.
|
|
419
409
|
"""
|
|
420
|
-
|
|
421
|
-
# actual value retrieved from the device
|
|
422
|
-
alert_config = await self._alert_config_uncached()
|
|
423
|
-
if alert_config != self._alert_config:
|
|
424
|
-
_LOGGER.warning(
|
|
425
|
-
'Alert configuration changed externally,'
|
|
426
|
-
' overwriting (read "%s", will be set to "%s")',
|
|
427
|
-
str(alert_config), str(flags)
|
|
428
|
-
)
|
|
429
|
-
await self.command(G90Commands.SETNOTICEFLAG, [flags.value])
|
|
430
|
-
# Update the alert configuration stored
|
|
431
|
-
self._alert_config = flags
|
|
410
|
+
await self.alert_config.set(flags)
|
|
432
411
|
|
|
433
412
|
@property
|
|
434
413
|
async def user_data_crc(self) -> G90UserDataCRC:
|
|
@@ -514,9 +493,9 @@ class G90Alarm(G90NotificationProtocol):
|
|
|
514
493
|
|
|
515
494
|
# Determine if door close notifications are available for the given
|
|
516
495
|
# sensor
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
496
|
+
door_close_alert_enabled = await self.alert_config.get_flag(
|
|
497
|
+
G90AlertConfigFlags.DOOR_CLOSE
|
|
498
|
+
)
|
|
520
499
|
# The condition intentionally doesn't account for cord sensors of
|
|
521
500
|
# subtype door, since those won't send door open/close alerts, only
|
|
522
501
|
# notifications
|
|
@@ -532,7 +511,8 @@ class G90Alarm(G90NotificationProtocol):
|
|
|
532
511
|
' (alert config flags %s) or is a cord sensor,'
|
|
533
512
|
' closing event will be emulated upon'
|
|
534
513
|
' %s seconds',
|
|
535
|
-
name, sensor.type,
|
|
514
|
+
name, sensor.type,
|
|
515
|
+
await self.alert_config.flags,
|
|
536
516
|
self._reset_occupancy_interval)
|
|
537
517
|
G90Callback.invoke_delayed(
|
|
538
518
|
self._reset_occupancy_interval,
|
|
@@ -599,17 +579,12 @@ class G90Alarm(G90NotificationProtocol):
|
|
|
599
579
|
:param state: Device state (armed, disarmed, armed home)
|
|
600
580
|
"""
|
|
601
581
|
if self._sms_alert_when_armed:
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
)
|
|
607
|
-
if state in (G90ArmDisarmTypes.ARM_AWAY,
|
|
608
|
-
G90ArmDisarmTypes.ARM_HOME):
|
|
609
|
-
# Enable SMS alerts from the device
|
|
610
|
-
await self.set_alert_config(
|
|
611
|
-
await self.alert_config | G90AlertConfigFlags.SMS_PUSH
|
|
582
|
+
await self.alert_config.set_flag(
|
|
583
|
+
G90AlertConfigFlags.SMS_PUSH,
|
|
584
|
+
state in (
|
|
585
|
+
G90ArmDisarmTypes.ARM_AWAY, G90ArmDisarmTypes.ARM_HOME
|
|
612
586
|
)
|
|
587
|
+
)
|
|
613
588
|
|
|
614
589
|
# Reset the tampered and door open when arming flags on all sensors
|
|
615
590
|
# having those set
|
|
@@ -50,7 +50,7 @@ class G90Device(G90Sensor):
|
|
|
50
50
|
[self.index, 1, self.subindex])
|
|
51
51
|
|
|
52
52
|
@property
|
|
53
|
-
def
|
|
53
|
+
def supports_updates(self) -> bool:
|
|
54
54
|
"""
|
|
55
55
|
Indicates if disabling/enabling the device (relay) is supported.
|
|
56
56
|
|
|
@@ -64,12 +64,12 @@ class G90Device(G90Sensor):
|
|
|
64
64
|
# mostly.
|
|
65
65
|
return False
|
|
66
66
|
|
|
67
|
-
async def
|
|
67
|
+
async def delete(self) -> None:
|
|
68
68
|
"""
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
:param value: Whether to enable or disable the device
|
|
69
|
+
Deletes the device (relay) from the G90 alarm panel.
|
|
72
70
|
"""
|
|
73
|
-
_LOGGER.
|
|
74
|
-
|
|
75
|
-
|
|
71
|
+
_LOGGER.debug("Deleting device: %s", self)
|
|
72
|
+
# Mark the device as unavailable
|
|
73
|
+
self.is_unavailable = True
|
|
74
|
+
# Delete the device from the alarm panel
|
|
75
|
+
await self.parent.command(G90Commands.DELDEVICE, [self.index])
|
|
@@ -52,7 +52,7 @@ class G90SensorCommonData: # pylint:disable=too-many-instance-attributes
|
|
|
52
52
|
type_id: int
|
|
53
53
|
subtype: int
|
|
54
54
|
timeout: int
|
|
55
|
-
|
|
55
|
+
user_flags_data: int
|
|
56
56
|
baudrate: int
|
|
57
57
|
protocol_id: int
|
|
58
58
|
reserved_data: int
|
|
@@ -117,6 +117,35 @@ class G90SensorUserFlags(IntFlag):
|
|
|
117
117
|
)
|
|
118
118
|
|
|
119
119
|
|
|
120
|
+
class G90SensorAlertModes(IntEnum):
|
|
121
|
+
"""
|
|
122
|
+
Dedicated alert modes for the sensors (subset of user flags).
|
|
123
|
+
"""
|
|
124
|
+
ALERT_ALWAYS = 0
|
|
125
|
+
ALERT_WHEN_AWAY = 1
|
|
126
|
+
ALERT_WHEN_AWAY_AND_HOME = 2
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
# Mapping of relevant user flags to alert modes
|
|
130
|
+
ALERT_MODES_MAP_BY_FLAG = {
|
|
131
|
+
# No 'when away' or 'when away and home' flag set means 'alert always
|
|
132
|
+
G90SensorUserFlags.NONE:
|
|
133
|
+
G90SensorAlertModes.ALERT_ALWAYS,
|
|
134
|
+
G90SensorUserFlags.ALERT_WHEN_AWAY:
|
|
135
|
+
G90SensorAlertModes.ALERT_WHEN_AWAY,
|
|
136
|
+
G90SensorUserFlags.ALERT_WHEN_AWAY_AND_HOME:
|
|
137
|
+
G90SensorAlertModes.ALERT_WHEN_AWAY_AND_HOME,
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
# Reversed mapping of alert modes to corresponding user flags
|
|
141
|
+
ALERT_MODES_MAP_BY_VALUE = dict(
|
|
142
|
+
zip(
|
|
143
|
+
ALERT_MODES_MAP_BY_FLAG.values(),
|
|
144
|
+
ALERT_MODES_MAP_BY_FLAG.keys()
|
|
145
|
+
)
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
|
|
120
149
|
class G90SensorProtocols(IntEnum):
|
|
121
150
|
"""
|
|
122
151
|
Protocol types for the sensors.
|
|
@@ -363,12 +392,22 @@ class G90Sensor(G90BaseEntity): # pylint:disable=too-many-instance-attributes
|
|
|
363
392
|
|
|
364
393
|
@property
|
|
365
394
|
def user_flag(self) -> G90SensorUserFlags:
|
|
395
|
+
"""
|
|
396
|
+
User flags for the sensor, retained for compatibility - please use
|
|
397
|
+
`:attr:user_flags` instead.
|
|
398
|
+
|
|
399
|
+
:return: User flags
|
|
400
|
+
"""
|
|
401
|
+
return self.user_flags
|
|
402
|
+
|
|
403
|
+
@property
|
|
404
|
+
def user_flags(self) -> G90SensorUserFlags:
|
|
366
405
|
"""
|
|
367
406
|
User flags for the sensor (disabled/enabled, arming type etc).
|
|
368
407
|
|
|
369
408
|
:return: User flags
|
|
370
409
|
"""
|
|
371
|
-
return G90SensorUserFlags(self._protocol_data.
|
|
410
|
+
return G90SensorUserFlags(self._protocol_data.user_flags_data)
|
|
372
411
|
|
|
373
412
|
@property
|
|
374
413
|
def node_count(self) -> int:
|
|
@@ -417,6 +456,25 @@ class G90Sensor(G90BaseEntity): # pylint:disable=too-many-instance-attributes
|
|
|
417
456
|
"""
|
|
418
457
|
return self._proto_idx
|
|
419
458
|
|
|
459
|
+
@property
|
|
460
|
+
def supports_updates(self) -> bool:
|
|
461
|
+
"""
|
|
462
|
+
Indicates if the sensor supports updates.
|
|
463
|
+
|
|
464
|
+
:return: Support for updates
|
|
465
|
+
"""
|
|
466
|
+
if not self._definition:
|
|
467
|
+
_LOGGER.warning(
|
|
468
|
+
'Manipulating with user flags for sensor index=%s'
|
|
469
|
+
' is unsupported - no sensor definition for'
|
|
470
|
+
' type=%s, subtype=%s',
|
|
471
|
+
self.index,
|
|
472
|
+
self._protocol_data.type_id,
|
|
473
|
+
self._protocol_data.subtype
|
|
474
|
+
)
|
|
475
|
+
return False
|
|
476
|
+
return True
|
|
477
|
+
|
|
420
478
|
@property
|
|
421
479
|
def supports_enable_disable(self) -> bool:
|
|
422
480
|
"""
|
|
@@ -424,7 +482,7 @@ class G90Sensor(G90BaseEntity): # pylint:disable=too-many-instance-attributes
|
|
|
424
482
|
|
|
425
483
|
:return: Support for enabling/disabling the sensor
|
|
426
484
|
"""
|
|
427
|
-
return self.
|
|
485
|
+
return self.supports_updates
|
|
428
486
|
|
|
429
487
|
@property
|
|
430
488
|
def protocol_data(self) -> G90SensorIncomingData:
|
|
@@ -518,16 +576,14 @@ class G90Sensor(G90BaseEntity): # pylint:disable=too-many-instance-attributes
|
|
|
518
576
|
)
|
|
519
577
|
self._door_open_when_arming = value
|
|
520
578
|
|
|
521
|
-
|
|
522
|
-
def enabled(self) -> bool:
|
|
579
|
+
async def set_user_flag(self, value: G90SensorUserFlags) -> None:
|
|
523
580
|
"""
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
:return: If sensor is enabled
|
|
581
|
+
Sets user flags of the sensor, retained for compatibility - please use
|
|
582
|
+
`:meth:set_user_flags` instead.
|
|
527
583
|
"""
|
|
528
|
-
|
|
584
|
+
await self.set_user_flags(value)
|
|
529
585
|
|
|
530
|
-
async def
|
|
586
|
+
async def set_user_flags(self, value: G90SensorUserFlags) -> None:
|
|
531
587
|
"""
|
|
532
588
|
Sets user flags of the sensor.
|
|
533
589
|
|
|
@@ -535,26 +591,21 @@ class G90Sensor(G90BaseEntity): # pylint:disable=too-many-instance-attributes
|
|
|
535
591
|
:attr:`.G90SensorUserFlags.USER_SETTABLE` will be ignored and
|
|
536
592
|
preserved from existing sensor flags.
|
|
537
593
|
"""
|
|
594
|
+
if not self.supports_updates:
|
|
595
|
+
return
|
|
596
|
+
|
|
597
|
+
# Checking private attribute directly, since `mypy` doesn't recognize
|
|
598
|
+
# the check for sensor definition is done over
|
|
599
|
+
# `self.supports_updates` property
|
|
600
|
+
if not self._definition:
|
|
601
|
+
return
|
|
602
|
+
|
|
538
603
|
if value & ~G90SensorUserFlags.USER_SETTABLE:
|
|
539
604
|
_LOGGER.warning(
|
|
540
605
|
'User flags for sensor index=%s contain non-user settable'
|
|
541
606
|
' flags, those will be ignored: %s',
|
|
542
607
|
self.index, repr(value & ~G90SensorUserFlags.USER_SETTABLE)
|
|
543
608
|
)
|
|
544
|
-
# Checking private attribute directly, since `mypy` doesn't recognize
|
|
545
|
-
# the check for sensor definition to be defined if done over
|
|
546
|
-
# `self.supports_enable_disable` property
|
|
547
|
-
if not self._definition:
|
|
548
|
-
_LOGGER.warning(
|
|
549
|
-
'Manipulating with user flags for sensor index=%s'
|
|
550
|
-
' is unsupported - no sensor definition for'
|
|
551
|
-
' type=%s, subtype=%s',
|
|
552
|
-
self.index,
|
|
553
|
-
self._protocol_data.type_id,
|
|
554
|
-
self._protocol_data.subtype
|
|
555
|
-
)
|
|
556
|
-
|
|
557
|
-
return
|
|
558
609
|
|
|
559
610
|
# Refresh actual sensor data from the alarm panel before modifying it.
|
|
560
611
|
# This implies the sensor is at the same position within sensor list
|
|
@@ -593,24 +644,32 @@ class G90Sensor(G90BaseEntity): # pylint:disable=too-many-instance-attributes
|
|
|
593
644
|
)
|
|
594
645
|
return
|
|
595
646
|
|
|
596
|
-
|
|
647
|
+
prev_user_flags = self.user_flags
|
|
597
648
|
|
|
598
649
|
# Re-instantiate the protocol data with modified user flags
|
|
599
650
|
_data = asdict(self._protocol_data)
|
|
600
|
-
_data['
|
|
651
|
+
_data['user_flags_data'] = (
|
|
601
652
|
# Preserve flags that are not user-settable
|
|
602
|
-
self.
|
|
653
|
+
self.user_flags & ~G90SensorUserFlags.USER_SETTABLE
|
|
603
654
|
) | (
|
|
604
655
|
# Combine them with the new user-settable flags
|
|
605
656
|
value & G90SensorUserFlags.USER_SETTABLE
|
|
606
657
|
)
|
|
607
658
|
self._protocol_data = self._protocol_incoming_data_kls(**_data)
|
|
608
659
|
|
|
660
|
+
if self.user_flags == prev_user_flags:
|
|
661
|
+
_LOGGER.debug(
|
|
662
|
+
'Sensor index=%s: user flags %s have not changed,'
|
|
663
|
+
' skipping update',
|
|
664
|
+
self._protocol_data.index, repr(prev_user_flags)
|
|
665
|
+
)
|
|
666
|
+
return
|
|
667
|
+
|
|
609
668
|
_LOGGER.debug(
|
|
610
|
-
'Sensor index=%s: previous
|
|
669
|
+
'Sensor index=%s: previous user flags %s, resulting flags %s',
|
|
611
670
|
self._protocol_data.index,
|
|
612
|
-
repr(
|
|
613
|
-
repr(self.
|
|
671
|
+
repr(prev_user_flags),
|
|
672
|
+
repr(self.user_flags)
|
|
614
673
|
)
|
|
615
674
|
|
|
616
675
|
# Generate protocol data from write operation, deriving values either
|
|
@@ -623,7 +682,7 @@ class G90Sensor(G90BaseEntity): # pylint:disable=too-many-instance-attributes
|
|
|
623
682
|
type_id=self._protocol_data.type_id,
|
|
624
683
|
subtype=self._protocol_data.subtype,
|
|
625
684
|
timeout=self._protocol_data.timeout,
|
|
626
|
-
|
|
685
|
+
user_flags_data=self._protocol_data.user_flags_data,
|
|
627
686
|
baudrate=self._protocol_data.baudrate,
|
|
628
687
|
protocol_id=self._protocol_data.protocol_id,
|
|
629
688
|
reserved_data=self._definition.reserved_data,
|
|
@@ -637,20 +696,110 @@ class G90Sensor(G90BaseEntity): # pylint:disable=too-many-instance-attributes
|
|
|
637
696
|
G90Commands.SETSINGLESENSOR, list(astuple(outgoing_data))
|
|
638
697
|
)
|
|
639
698
|
|
|
640
|
-
|
|
699
|
+
def get_flag(self, flag: G90SensorUserFlags) -> bool:
|
|
641
700
|
"""
|
|
642
|
-
|
|
701
|
+
Gets the user flag for the sensor.
|
|
702
|
+
|
|
703
|
+
:param flag: User flag to get
|
|
704
|
+
:return: User flag value
|
|
643
705
|
"""
|
|
706
|
+
return flag in self.user_flag
|
|
644
707
|
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
708
|
+
async def set_flag(
|
|
709
|
+
self, flag: G90SensorUserFlags, value: bool
|
|
710
|
+
) -> None:
|
|
711
|
+
"""
|
|
712
|
+
Sets the user flag for the sensor.
|
|
713
|
+
|
|
714
|
+
:param flag: User flag to set
|
|
715
|
+
:param value: New value for the user flag
|
|
716
|
+
"""
|
|
717
|
+
# Skip updating the flag if it has the desired value
|
|
718
|
+
if self.get_flag(flag) == value:
|
|
719
|
+
_LOGGER.debug(
|
|
720
|
+
'Sensor index=%s: user flag %s has not changed,'
|
|
721
|
+
' skipping update',
|
|
722
|
+
self._protocol_data.index, repr(flag)
|
|
723
|
+
)
|
|
724
|
+
return
|
|
725
|
+
|
|
726
|
+
# Invert corresponding user flag and set it
|
|
727
|
+
user_flag = self.user_flag ^ flag
|
|
652
728
|
await self.set_user_flag(user_flag)
|
|
653
729
|
|
|
730
|
+
@property
|
|
731
|
+
def enabled(self) -> bool:
|
|
732
|
+
"""
|
|
733
|
+
Indicates if the sensor is enabled, using `:meth:get_user_flag` instead
|
|
734
|
+
is preferred.
|
|
735
|
+
|
|
736
|
+
:return: If sensor is enabled
|
|
737
|
+
"""
|
|
738
|
+
return self.get_flag(G90SensorUserFlags.ENABLED)
|
|
739
|
+
|
|
740
|
+
async def set_enabled(self, value: bool) -> None:
|
|
741
|
+
"""
|
|
742
|
+
Sets the sensor enabled/disabled, using `:meth:set_user_flag` instead
|
|
743
|
+
is preferred.
|
|
744
|
+
|
|
745
|
+
:param value: New the sensor should be enabled
|
|
746
|
+
"""
|
|
747
|
+
await self.set_flag(G90SensorUserFlags.ENABLED, value)
|
|
748
|
+
|
|
749
|
+
@property
|
|
750
|
+
def alert_mode(self) -> G90SensorAlertModes:
|
|
751
|
+
"""
|
|
752
|
+
Alert mode for the sensor.
|
|
753
|
+
|
|
754
|
+
:return: Alert mode
|
|
755
|
+
"""
|
|
756
|
+
# Filter out irrelevant flags
|
|
757
|
+
mode = self.user_flag & (
|
|
758
|
+
G90SensorUserFlags.ALERT_WHEN_AWAY
|
|
759
|
+
| G90SensorUserFlags.ALERT_WHEN_AWAY_AND_HOME
|
|
760
|
+
)
|
|
761
|
+
# Map the relevant user flags to alert mode
|
|
762
|
+
result = ALERT_MODES_MAP_BY_FLAG.get(mode, None)
|
|
763
|
+
|
|
764
|
+
if result is None:
|
|
765
|
+
raise ValueError(
|
|
766
|
+
f"Unknown alert mode for sensor {self.name}: {mode}"
|
|
767
|
+
f" (user flag: {self.user_flag})"
|
|
768
|
+
)
|
|
769
|
+
|
|
770
|
+
return result
|
|
771
|
+
|
|
772
|
+
async def set_alert_mode(self, value: G90SensorAlertModes) -> None:
|
|
773
|
+
"""
|
|
774
|
+
Sets the sensor alert mode.
|
|
775
|
+
"""
|
|
776
|
+
# Skip update if the value is already set to the requested one
|
|
777
|
+
if self.alert_mode == value:
|
|
778
|
+
_LOGGER.debug(
|
|
779
|
+
'Sensor index=%s: alert mode %s has not changed,'
|
|
780
|
+
' skipping update',
|
|
781
|
+
self._protocol_data.index, repr(value)
|
|
782
|
+
)
|
|
783
|
+
return
|
|
784
|
+
|
|
785
|
+
# Map the alert mode to user flag value
|
|
786
|
+
result = ALERT_MODES_MAP_BY_VALUE.get(value, None)
|
|
787
|
+
|
|
788
|
+
if result is None:
|
|
789
|
+
raise ValueError(
|
|
790
|
+
f"Attempting to set alert mode for sensor {self.name} to"
|
|
791
|
+
f" unknown value '{value}'"
|
|
792
|
+
)
|
|
793
|
+
|
|
794
|
+
# Add the mapped value over the user flags, filtering out previous
|
|
795
|
+
# value of the alert mode
|
|
796
|
+
user_flags = self.user_flag & ~(
|
|
797
|
+
G90SensorUserFlags.ALERT_WHEN_AWAY
|
|
798
|
+
| G90SensorUserFlags.ALERT_WHEN_AWAY_AND_HOME
|
|
799
|
+
) | result
|
|
800
|
+
# Set the updated user flags
|
|
801
|
+
await self.set_user_flags(user_flags)
|
|
802
|
+
|
|
654
803
|
@property
|
|
655
804
|
def extra_data(self) -> Any:
|
|
656
805
|
"""
|
|
@@ -674,6 +823,19 @@ class G90Sensor(G90BaseEntity): # pylint:disable=too-many-instance-attributes
|
|
|
674
823
|
def is_unavailable(self, value: bool) -> None:
|
|
675
824
|
self._unavailable = value
|
|
676
825
|
|
|
826
|
+
async def delete(self) -> None:
|
|
827
|
+
"""
|
|
828
|
+
Deletes the sensor from the alarm panel.
|
|
829
|
+
"""
|
|
830
|
+
_LOGGER.debug("Deleting sensor: %s", self)
|
|
831
|
+
|
|
832
|
+
# Mark the sensor as unavailable
|
|
833
|
+
self.is_unavailable = True
|
|
834
|
+
# Delete the sensor from the alarm panel
|
|
835
|
+
await self.parent.command(
|
|
836
|
+
G90Commands.DELSENSOR, [self.index]
|
|
837
|
+
)
|
|
838
|
+
|
|
677
839
|
def _asdict(self) -> Dict[str, Any]:
|
|
678
840
|
"""
|
|
679
841
|
Returns dictionary representation of the sensor.
|
|
@@ -693,8 +855,15 @@ class G90Sensor(G90BaseEntity): # pylint:disable=too-many-instance-attributes
|
|
|
693
855
|
'user_flag': self.user_flag,
|
|
694
856
|
'reserved': self.reserved,
|
|
695
857
|
'extra_data': self.extra_data,
|
|
696
|
-
'enabled': self.
|
|
697
|
-
'
|
|
858
|
+
'enabled': self.get_flag(G90SensorUserFlags.ENABLED),
|
|
859
|
+
'detect_door': self.get_flag(G90SensorUserFlags.DETECT_DOOR),
|
|
860
|
+
'door_chime': self.get_flag(G90SensorUserFlags.DOOR_CHIME),
|
|
861
|
+
'independent_zone': self.get_flag(
|
|
862
|
+
G90SensorUserFlags.INDEPENDENT_ZONE
|
|
863
|
+
),
|
|
864
|
+
'arm_delay': self.get_flag(G90SensorUserFlags.ARM_DELAY),
|
|
865
|
+
'alert_mode': self.alert_mode,
|
|
866
|
+
'supports_updates': self.supports_updates,
|
|
698
867
|
'is_wireless': self.is_wireless,
|
|
699
868
|
'is_low_battery': self.is_low_battery,
|
|
700
869
|
'is_tampered': self.is_tampered,
|