pyg90alarm 1.19.0__py3-none-any.whl → 2.0.0__py3-none-any.whl
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/__init__.py +5 -5
- pyg90alarm/alarm.py +159 -114
- pyg90alarm/cloud/__init__.py +31 -0
- pyg90alarm/cloud/const.py +56 -0
- pyg90alarm/cloud/messages.py +593 -0
- pyg90alarm/cloud/notifications.py +409 -0
- pyg90alarm/cloud/protocol.py +518 -0
- pyg90alarm/const.py +5 -0
- pyg90alarm/entities/base_entity.py +83 -0
- pyg90alarm/entities/base_list.py +165 -0
- pyg90alarm/entities/device_list.py +58 -0
- pyg90alarm/entities/sensor.py +63 -3
- pyg90alarm/entities/sensor_list.py +50 -0
- pyg90alarm/local/__init__.py +0 -0
- pyg90alarm/{base_cmd.py → local/base_cmd.py} +3 -6
- pyg90alarm/{discovery.py → local/discovery.py} +1 -1
- pyg90alarm/{history.py → local/history.py} +4 -2
- pyg90alarm/{host_status.py → local/host_status.py} +1 -1
- pyg90alarm/local/notifications.py +116 -0
- pyg90alarm/{paginated_cmd.py → local/paginated_cmd.py} +2 -2
- pyg90alarm/{paginated_result.py → local/paginated_result.py} +1 -1
- pyg90alarm/{targeted_discovery.py → local/targeted_discovery.py} +2 -2
- pyg90alarm/notifications/__init__.py +0 -0
- pyg90alarm/{device_notifications.py → notifications/base.py} +115 -173
- pyg90alarm/notifications/protocol.py +116 -0
- {pyg90alarm-1.19.0.dist-info → pyg90alarm-2.0.0.dist-info}/METADATA +112 -18
- pyg90alarm-2.0.0.dist-info/RECORD +40 -0
- {pyg90alarm-1.19.0.dist-info → pyg90alarm-2.0.0.dist-info}/WHEEL +1 -1
- pyg90alarm-1.19.0.dist-info/RECORD +0 -27
- /pyg90alarm/{config.py → local/config.py} +0 -0
- /pyg90alarm/{host_info.py → local/host_info.py} +0 -0
- /pyg90alarm/{user_data_crc.py → local/user_data_crc.py} +0 -0
- {pyg90alarm-1.19.0.dist-info → pyg90alarm-2.0.0.dist-info/licenses}/LICENSE +0 -0
- {pyg90alarm-1.19.0.dist-info → pyg90alarm-2.0.0.dist-info}/top_level.txt +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Copyright (c)
|
|
1
|
+
# Copyright (c) 2025 Ilia Sotnikov
|
|
2
2
|
#
|
|
3
3
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
4
4
|
# of this software and associated documentation files (the "Software"), to deal
|
|
@@ -24,14 +24,14 @@ Implements support for notifications/alerts sent by G90 alarm panel.
|
|
|
24
24
|
import json
|
|
25
25
|
import logging
|
|
26
26
|
from typing import (
|
|
27
|
-
Optional,
|
|
27
|
+
Optional, List, Any, Callable
|
|
28
28
|
)
|
|
29
29
|
from dataclasses import dataclass
|
|
30
|
-
import asyncio
|
|
31
30
|
from asyncio.transports import BaseTransport
|
|
32
|
-
from
|
|
33
|
-
|
|
34
|
-
from
|
|
31
|
+
from datetime import datetime, timezone
|
|
32
|
+
|
|
33
|
+
from ..callback import G90Callback
|
|
34
|
+
from ..const import (
|
|
35
35
|
G90MessageTypes,
|
|
36
36
|
G90NotificationTypes,
|
|
37
37
|
G90AlertTypes,
|
|
@@ -41,6 +41,7 @@ from .const import (
|
|
|
41
41
|
G90AlertStates,
|
|
42
42
|
G90RemoteButtonStates,
|
|
43
43
|
)
|
|
44
|
+
from .protocol import G90NotificationProtocol
|
|
44
45
|
|
|
45
46
|
_LOGGER = logging.getLogger(__name__)
|
|
46
47
|
|
|
@@ -104,7 +105,7 @@ class G90DeviceAlert: # pylint: disable=too-many-instance-attributes
|
|
|
104
105
|
other: str
|
|
105
106
|
|
|
106
107
|
|
|
107
|
-
class
|
|
108
|
+
class G90NotificationsBase:
|
|
108
109
|
"""
|
|
109
110
|
Implements support for notifications/alerts sent by alarm panel.
|
|
110
111
|
|
|
@@ -115,32 +116,35 @@ class G90DeviceNotifications(DatagramProtocol):
|
|
|
115
116
|
command to be performed first, one that fetches device GUID and then stores
|
|
116
117
|
it using :attr:`.device_id` (e.g. :meth:`G90Alarm.get_host_info`).
|
|
117
118
|
"""
|
|
118
|
-
def __init__(
|
|
119
|
+
def __init__(
|
|
120
|
+
self, protocol_factory: Callable[[], G90NotificationProtocol],
|
|
121
|
+
):
|
|
119
122
|
# pylint: disable=too-many-arguments
|
|
120
|
-
self.
|
|
121
|
-
self._notifications_local_host = local_host
|
|
122
|
-
self._notifications_local_port = local_port
|
|
123
|
-
# Host/port of the device is configured to communicating via commands.
|
|
124
|
-
# Inteded to validate if notifications/alert are received from the
|
|
125
|
-
# correct device.
|
|
126
|
-
self._host: Optional[str] = None
|
|
127
|
-
self._port: Optional[int] = None
|
|
123
|
+
self._transport: Optional[BaseTransport] = None
|
|
128
124
|
# Same but for device ID (GUID) - the notifications logic uses it to
|
|
129
125
|
# perform validation, but doesn't set it from messages received (it
|
|
130
126
|
# will diminish the purpose of the validation, should be done by an
|
|
131
127
|
# ancestor class).
|
|
132
128
|
self._device_id: Optional[str] = None
|
|
129
|
+
self._protocol = protocol_factory()
|
|
130
|
+
self._last_device_packet_time: Optional[datetime] = None
|
|
131
|
+
self._last_upstream_packet_time: Optional[datetime] = None
|
|
133
132
|
|
|
134
|
-
def
|
|
135
|
-
self,
|
|
133
|
+
def handle_notification(
|
|
134
|
+
self, notification: G90Notification
|
|
136
135
|
) -> None:
|
|
136
|
+
"""
|
|
137
|
+
Handles notification received from the device.
|
|
138
|
+
|
|
139
|
+
:param notification: The notification to handle.
|
|
140
|
+
"""
|
|
137
141
|
# Sensor activity notification
|
|
138
142
|
if notification.kind == G90NotificationTypes.SENSOR_ACTIVITY:
|
|
139
143
|
g90_zone_info = G90ZoneInfo(*notification.data)
|
|
140
144
|
|
|
141
145
|
_LOGGER.debug('Sensor notification: %s', g90_zone_info)
|
|
142
146
|
G90Callback.invoke(
|
|
143
|
-
self.on_sensor_activity,
|
|
147
|
+
self._protocol.on_sensor_activity,
|
|
144
148
|
g90_zone_info.idx, g90_zone_info.name
|
|
145
149
|
)
|
|
146
150
|
|
|
@@ -155,7 +159,7 @@ class G90DeviceNotifications(DatagramProtocol):
|
|
|
155
159
|
|
|
156
160
|
_LOGGER.debug('Arm/disarm notification: %s',
|
|
157
161
|
state)
|
|
158
|
-
G90Callback.invoke(self.on_armdisarm, state)
|
|
162
|
+
G90Callback.invoke(self._protocol.on_armdisarm, state)
|
|
159
163
|
|
|
160
164
|
return
|
|
161
165
|
|
|
@@ -164,14 +168,14 @@ class G90DeviceNotifications(DatagramProtocol):
|
|
|
164
168
|
g90_zone_info = G90ZoneInfo(*notification.data)
|
|
165
169
|
_LOGGER.debug('Door open detected when arming: %s', g90_zone_info)
|
|
166
170
|
G90Callback.invoke(
|
|
167
|
-
self.on_door_open_when_arming,
|
|
171
|
+
self._protocol.on_door_open_when_arming,
|
|
168
172
|
g90_zone_info.idx, g90_zone_info.name
|
|
169
173
|
)
|
|
170
174
|
return
|
|
171
175
|
|
|
172
|
-
_LOGGER.warning('Unknown notification received
|
|
176
|
+
_LOGGER.warning('Unknown notification received:'
|
|
173
177
|
' kind %s, data %s',
|
|
174
|
-
|
|
178
|
+
notification.kind, notification.data)
|
|
175
179
|
|
|
176
180
|
def _handle_alert_sensor_activity(self, alert: G90DeviceAlert) -> bool:
|
|
177
181
|
"""
|
|
@@ -180,7 +184,7 @@ class G90DeviceNotifications(DatagramProtocol):
|
|
|
180
184
|
if alert.source == G90AlertSources.REMOTE:
|
|
181
185
|
_LOGGER.debug('Remote button press alert: %s', alert)
|
|
182
186
|
G90Callback.invoke(
|
|
183
|
-
self.on_remote_button_press,
|
|
187
|
+
self._protocol.on_remote_button_press,
|
|
184
188
|
alert.event_id, alert.zone_name,
|
|
185
189
|
G90RemoteButtonStates(alert.state)
|
|
186
190
|
)
|
|
@@ -197,7 +201,7 @@ class G90DeviceNotifications(DatagramProtocol):
|
|
|
197
201
|
|
|
198
202
|
_LOGGER.debug('Door open_close alert: %s', alert)
|
|
199
203
|
G90Callback.invoke(
|
|
200
|
-
self.on_door_open_close,
|
|
204
|
+
self._protocol.on_door_open_close,
|
|
201
205
|
alert.event_id, alert.zone_name, is_open
|
|
202
206
|
)
|
|
203
207
|
|
|
@@ -209,7 +213,7 @@ class G90DeviceNotifications(DatagramProtocol):
|
|
|
209
213
|
):
|
|
210
214
|
_LOGGER.debug('Low battery alert: %s', alert)
|
|
211
215
|
G90Callback.invoke(
|
|
212
|
-
self.on_low_battery,
|
|
216
|
+
self._protocol.on_low_battery,
|
|
213
217
|
alert.event_id, alert.zone_name
|
|
214
218
|
)
|
|
215
219
|
|
|
@@ -217,10 +221,17 @@ class G90DeviceNotifications(DatagramProtocol):
|
|
|
217
221
|
|
|
218
222
|
return False
|
|
219
223
|
|
|
220
|
-
def
|
|
221
|
-
self,
|
|
224
|
+
def handle_alert(
|
|
225
|
+
self, alert: G90DeviceAlert,
|
|
222
226
|
verify_device_id: bool = True
|
|
223
227
|
) -> None:
|
|
228
|
+
"""
|
|
229
|
+
Handles alert received from the device.
|
|
230
|
+
|
|
231
|
+
:param alert: The alert to handle.
|
|
232
|
+
:param verify_device_id: Whether to verify the device ID (GUID) in the
|
|
233
|
+
alert. If set to False, the device ID will not be verified.
|
|
234
|
+
"""
|
|
224
235
|
handled = False
|
|
225
236
|
|
|
226
237
|
# Stop processing when alert is received from the device with different
|
|
@@ -256,7 +267,7 @@ class G90DeviceNotifications(DatagramProtocol):
|
|
|
256
267
|
# We received the device state change related to arm/disarm,
|
|
257
268
|
# invoke the corresponding callback
|
|
258
269
|
_LOGGER.debug('Arm/disarm state change: %s', state)
|
|
259
|
-
G90Callback.invoke(self.on_armdisarm, state)
|
|
270
|
+
G90Callback.invoke(self._protocol.on_armdisarm, state)
|
|
260
271
|
|
|
261
272
|
handled = True
|
|
262
273
|
|
|
@@ -265,7 +276,8 @@ class G90DeviceNotifications(DatagramProtocol):
|
|
|
265
276
|
if alert.source == G90AlertSources.REMOTE:
|
|
266
277
|
_LOGGER.debug('SOS: %s', alert.zone_name)
|
|
267
278
|
G90Callback.invoke(
|
|
268
|
-
self.on_sos,
|
|
279
|
+
self._protocol.on_sos,
|
|
280
|
+
alert.event_id, alert.zone_name, False
|
|
269
281
|
)
|
|
270
282
|
# Regular alarm
|
|
271
283
|
else:
|
|
@@ -274,7 +286,7 @@ class G90DeviceNotifications(DatagramProtocol):
|
|
|
274
286
|
'Alarm: %s, is tampered: %s', alert.zone_name, is_tampered
|
|
275
287
|
)
|
|
276
288
|
G90Callback.invoke(
|
|
277
|
-
self.on_alarm,
|
|
289
|
+
self._protocol.on_alarm,
|
|
278
290
|
alert.event_id, alert.zone_name, is_tampered
|
|
279
291
|
)
|
|
280
292
|
|
|
@@ -286,42 +298,22 @@ class G90DeviceNotifications(DatagramProtocol):
|
|
|
286
298
|
|
|
287
299
|
_LOGGER.debug('SOS: Host')
|
|
288
300
|
G90Callback.invoke(
|
|
289
|
-
self.on_sos, alert.event_id, zone_name, True
|
|
301
|
+
self._protocol.on_sos, alert.event_id, zone_name, True
|
|
290
302
|
)
|
|
291
303
|
|
|
292
304
|
handled = True
|
|
293
305
|
|
|
294
306
|
if not handled:
|
|
295
|
-
_LOGGER.warning(
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
# Implementation of datagram protocol,
|
|
300
|
-
# https://docs.python.org/3/library/asyncio-protocol.html#datagram-protocols
|
|
301
|
-
def connection_made(self, transport: BaseTransport) -> None:
|
|
302
|
-
"""
|
|
303
|
-
Invoked when connection from the device is made.
|
|
304
|
-
"""
|
|
305
|
-
|
|
306
|
-
def connection_lost(self, exc: Optional[Exception]) -> None:
|
|
307
|
-
"""
|
|
308
|
-
Same but when the connection is lost.
|
|
309
|
-
"""
|
|
307
|
+
_LOGGER.warning(
|
|
308
|
+
'Unknown alert received: type %s, data %s',
|
|
309
|
+
alert.type, alert
|
|
310
|
+
)
|
|
310
311
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
) -> None:
|
|
312
|
+
# pylint:disable=too-many-return-statements
|
|
313
|
+
def handle(self, data: bytes) -> None:
|
|
314
314
|
"""
|
|
315
|
-
Invoked when
|
|
315
|
+
Invoked when message is received from the device.
|
|
316
316
|
"""
|
|
317
|
-
if self._host and self._host != addr[0]:
|
|
318
|
-
_LOGGER.error(
|
|
319
|
-
"Received notification/alert from wrong host '%s',"
|
|
320
|
-
" expected from '%s'",
|
|
321
|
-
addr[0], self._host
|
|
322
|
-
)
|
|
323
|
-
return
|
|
324
|
-
|
|
325
317
|
try:
|
|
326
318
|
s_data = data.decode('utf-8')
|
|
327
319
|
except UnicodeDecodeError:
|
|
@@ -332,8 +324,7 @@ class G90DeviceNotifications(DatagramProtocol):
|
|
|
332
324
|
_LOGGER.error('Missing end marker in data')
|
|
333
325
|
return
|
|
334
326
|
payload = s_data[:-1]
|
|
335
|
-
|
|
336
|
-
addr[0], addr[1], payload)
|
|
327
|
+
|
|
337
328
|
try:
|
|
338
329
|
message = json.loads(payload)
|
|
339
330
|
g90_message = G90Message(*message)
|
|
@@ -351,10 +342,9 @@ class G90DeviceNotifications(DatagramProtocol):
|
|
|
351
342
|
try:
|
|
352
343
|
notification_data = G90Notification(*g90_message.data)
|
|
353
344
|
except TypeError as exc:
|
|
354
|
-
_LOGGER.error('Bad notification received
|
|
355
|
-
addr[0], addr[1], exc)
|
|
345
|
+
_LOGGER.error('Bad notification received: %s', exc)
|
|
356
346
|
return
|
|
357
|
-
self.
|
|
347
|
+
self.handle_notification(notification_data)
|
|
358
348
|
return
|
|
359
349
|
|
|
360
350
|
# Device alerts
|
|
@@ -362,132 +352,34 @@ class G90DeviceNotifications(DatagramProtocol):
|
|
|
362
352
|
try:
|
|
363
353
|
alert_data = G90DeviceAlert(*g90_message.data)
|
|
364
354
|
except TypeError as exc:
|
|
365
|
-
_LOGGER.error('Bad alert received
|
|
366
|
-
addr[0], addr[1], exc)
|
|
355
|
+
_LOGGER.error('Bad alert received: %s', exc)
|
|
367
356
|
return
|
|
368
|
-
self.
|
|
357
|
+
self.handle_alert(alert_data)
|
|
369
358
|
return
|
|
370
359
|
|
|
371
|
-
_LOGGER.warning('Unknown message received
|
|
372
|
-
addr[0], addr[1], message)
|
|
373
|
-
|
|
374
|
-
async def on_armdisarm(self, state: G90ArmDisarmTypes) -> None:
|
|
375
|
-
"""
|
|
376
|
-
Invoked when device is armed or disarmed.
|
|
377
|
-
|
|
378
|
-
:param state: State of the device
|
|
379
|
-
"""
|
|
380
|
-
|
|
381
|
-
async def on_sensor_activity(self, idx: int, name: str) -> None:
|
|
382
|
-
"""
|
|
383
|
-
Invoked on sensor activity.
|
|
384
|
-
|
|
385
|
-
:param idx: Index of the sensor.
|
|
386
|
-
:param name: Name of the sensor.
|
|
387
|
-
"""
|
|
388
|
-
|
|
389
|
-
async def on_door_open_when_arming(
|
|
390
|
-
self, event_id: int, zone_name: str
|
|
391
|
-
) -> None:
|
|
392
|
-
"""
|
|
393
|
-
Invoked when door open is detected when panel is armed.
|
|
394
|
-
|
|
395
|
-
:param event_id: Index of the sensor.
|
|
396
|
-
:param zone_name: Name of the sensor that reports door open.
|
|
397
|
-
"""
|
|
398
|
-
|
|
399
|
-
async def on_door_open_close(
|
|
400
|
-
self, event_id: int, zone_name: str, is_open: bool
|
|
401
|
-
) -> None:
|
|
402
|
-
"""
|
|
403
|
-
Invoked when door sensor reports it opened or closed.
|
|
404
|
-
|
|
405
|
-
:param event_id: Index of the sensor reporting the event.
|
|
406
|
-
:param zone_name: Name of the sensor that reports door open/close.
|
|
407
|
-
:param is_open: Indicates if the door is open.
|
|
408
|
-
"""
|
|
409
|
-
|
|
410
|
-
async def on_low_battery(self, event_id: int, zone_name: str) -> None:
|
|
411
|
-
"""
|
|
412
|
-
Invoked when a sensor reports it is low on battery.
|
|
413
|
-
|
|
414
|
-
:param event_id: Index of the sensor.
|
|
415
|
-
:param zone_name: Name of the sensor that reports low battery.
|
|
416
|
-
"""
|
|
417
|
-
|
|
418
|
-
async def on_alarm(
|
|
419
|
-
self, event_id: int, zone_name: str, is_tampered: bool
|
|
420
|
-
) -> None:
|
|
421
|
-
"""
|
|
422
|
-
Invoked when device triggers the alarm.
|
|
423
|
-
|
|
424
|
-
:param event_id: Index of the sensor.
|
|
425
|
-
:param zone_name: Name of the zone that triggered the alarm.
|
|
426
|
-
"""
|
|
427
|
-
|
|
428
|
-
async def on_remote_button_press(
|
|
429
|
-
self, event_id: int, zone_name: str, button: G90RemoteButtonStates
|
|
430
|
-
) -> None:
|
|
431
|
-
"""
|
|
432
|
-
Invoked when a remote button is pressed.
|
|
433
|
-
|
|
434
|
-
Please note there will only be call to the method w/o invoking
|
|
435
|
-
:meth:`G90DeviceNotifications.on_sensor_activity`.
|
|
436
|
-
|
|
437
|
-
:param event_id: Index of the sensor associated with the remote.
|
|
438
|
-
:param zone_name: Name of the sensor that reports remote button press.
|
|
439
|
-
:param button: The button pressed on the remote
|
|
440
|
-
"""
|
|
441
|
-
|
|
442
|
-
async def on_sos(
|
|
443
|
-
self, event_id: int, zone_name: str, is_host_sos: bool
|
|
444
|
-
) -> None:
|
|
445
|
-
"""
|
|
446
|
-
Invoked when SOS is triggered.
|
|
447
|
-
|
|
448
|
-
Please note that the panel might not set its status to alarm
|
|
449
|
-
internally, so that :meth:`G90DeviceNotifications` might need an
|
|
450
|
-
explicit call in the derived class to simulate that.
|
|
451
|
-
|
|
452
|
-
:param event_id: Index of the sensor.
|
|
453
|
-
:param zone_name: Name of the sensor that reports SOS.
|
|
454
|
-
:param is_host_sos: Indicates if the SOS is host-initiated.
|
|
455
|
-
"""
|
|
360
|
+
_LOGGER.warning('Unknown message received: %s', message)
|
|
456
361
|
|
|
457
362
|
async def listen(self) -> None:
|
|
458
363
|
"""
|
|
459
|
-
Listens for notifications/
|
|
364
|
+
Listens for notifications/alerts from the device.
|
|
460
365
|
"""
|
|
461
|
-
|
|
462
|
-
loop = asyncio.get_running_loop()
|
|
463
|
-
except AttributeError:
|
|
464
|
-
loop = asyncio.get_event_loop()
|
|
465
|
-
|
|
466
|
-
_LOGGER.debug('Creating UDP endpoint for %s:%s',
|
|
467
|
-
self._notifications_local_host,
|
|
468
|
-
self._notifications_local_port)
|
|
469
|
-
(self._notification_transport,
|
|
470
|
-
_protocol) = await loop.create_datagram_endpoint(
|
|
471
|
-
lambda: self,
|
|
472
|
-
local_addr=(
|
|
473
|
-
self._notifications_local_host, self._notifications_local_port
|
|
474
|
-
))
|
|
366
|
+
raise NotImplementedError
|
|
475
367
|
|
|
476
368
|
@property
|
|
477
369
|
def listener_started(self) -> bool:
|
|
478
370
|
"""
|
|
479
371
|
Indicates if the listener of the device notifications has been started.
|
|
480
372
|
"""
|
|
481
|
-
return self.
|
|
373
|
+
return self._transport is not None
|
|
482
374
|
|
|
483
|
-
def close(self) -> None:
|
|
375
|
+
async def close(self) -> None:
|
|
484
376
|
"""
|
|
485
377
|
Closes the listener.
|
|
486
378
|
"""
|
|
487
|
-
if self.
|
|
379
|
+
if self._transport:
|
|
488
380
|
_LOGGER.debug('No longer listening for device notifications')
|
|
489
|
-
self.
|
|
490
|
-
self.
|
|
381
|
+
self._transport.close()
|
|
382
|
+
self._transport = None
|
|
491
383
|
|
|
492
384
|
@property
|
|
493
385
|
def device_id(self) -> Optional[str]:
|
|
@@ -495,7 +387,7 @@ class G90DeviceNotifications(DatagramProtocol):
|
|
|
495
387
|
The ID (GUID) of the panel being communicated with thru commands.
|
|
496
388
|
|
|
497
389
|
Available when any panel command receives it from the device
|
|
498
|
-
(
|
|
390
|
+
(`GETHOSTINFO` local command or Hello / HelloDiscovery cloud ones).
|
|
499
391
|
"""
|
|
500
392
|
return self._device_id
|
|
501
393
|
|
|
@@ -510,3 +402,53 @@ class G90DeviceNotifications(DatagramProtocol):
|
|
|
510
402
|
return
|
|
511
403
|
|
|
512
404
|
self._device_id = device_id
|
|
405
|
+
|
|
406
|
+
def clear_device_id(self) -> None:
|
|
407
|
+
"""
|
|
408
|
+
Clears the device ID.
|
|
409
|
+
"""
|
|
410
|
+
self._device_id = None
|
|
411
|
+
|
|
412
|
+
@property
|
|
413
|
+
def last_device_packet_time(self) -> Optional[datetime]:
|
|
414
|
+
"""
|
|
415
|
+
Returns the timestamp of the last packet received from the device.
|
|
416
|
+
|
|
417
|
+
This property can be used to monitor the communication health with the
|
|
418
|
+
device.
|
|
419
|
+
"""
|
|
420
|
+
return self._last_device_packet_time
|
|
421
|
+
|
|
422
|
+
def set_last_device_packet_time(self) -> None:
|
|
423
|
+
"""
|
|
424
|
+
Updates the timestamp of the last packet received from the device.
|
|
425
|
+
|
|
426
|
+
This method is called internally when a packet is received from the
|
|
427
|
+
device.
|
|
428
|
+
"""
|
|
429
|
+
self._last_device_packet_time = datetime.now(tz=timezone.utc)
|
|
430
|
+
_LOGGER.debug(
|
|
431
|
+
'Last device packet time: %s', self._last_device_packet_time
|
|
432
|
+
)
|
|
433
|
+
|
|
434
|
+
@property
|
|
435
|
+
def last_upstream_packet_time(self) -> Optional[datetime]:
|
|
436
|
+
"""
|
|
437
|
+
Returns the timestamp of the last packet sent to the upstream server.
|
|
438
|
+
|
|
439
|
+
This property can be used to monitor the communication health with the
|
|
440
|
+
cloud/upstream server.
|
|
441
|
+
"""
|
|
442
|
+
return self._last_upstream_packet_time
|
|
443
|
+
|
|
444
|
+
def set_last_upstream_packet_time(self) -> None:
|
|
445
|
+
"""
|
|
446
|
+
Updates the timestamp of the last packet sent to the upstream server.
|
|
447
|
+
|
|
448
|
+
This method is called internally when a packet is sent to the upstream
|
|
449
|
+
server.
|
|
450
|
+
"""
|
|
451
|
+
self._last_upstream_packet_time = datetime.now(tz=timezone.utc)
|
|
452
|
+
_LOGGER.debug(
|
|
453
|
+
'Last upstream packet time: %s', self._last_upstream_packet_time
|
|
454
|
+
)
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# Copyright (c) 2021 Ilia Sotnikov
|
|
2
|
+
#
|
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
|
8
|
+
# furnished to do so, subject to the following conditions:
|
|
9
|
+
#
|
|
10
|
+
# The above copyright notice and this permission notice shall be included in
|
|
11
|
+
# all copies or substantial portions of the Software.
|
|
12
|
+
#
|
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
19
|
+
# SOFTWARE.
|
|
20
|
+
|
|
21
|
+
"""
|
|
22
|
+
Defines notification protocol for `NotificationBase` class.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from ..const import (
|
|
26
|
+
G90ArmDisarmTypes,
|
|
27
|
+
G90RemoteButtonStates,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class G90NotificationProtocol:
|
|
32
|
+
"""
|
|
33
|
+
Protocol for notification handling.
|
|
34
|
+
"""
|
|
35
|
+
async def on_armdisarm(self, state: G90ArmDisarmTypes) -> None:
|
|
36
|
+
"""
|
|
37
|
+
Invoked when device is armed or disarmed.
|
|
38
|
+
|
|
39
|
+
:param state: State of the device
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
async def on_sensor_activity(self, idx: int, name: str) -> None:
|
|
43
|
+
"""
|
|
44
|
+
Invoked on sensor activity.
|
|
45
|
+
|
|
46
|
+
:param idx: Index of the sensor.
|
|
47
|
+
:param name: Name of the sensor.
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
async def on_door_open_when_arming(
|
|
51
|
+
self, event_id: int, zone_name: str
|
|
52
|
+
) -> None:
|
|
53
|
+
"""
|
|
54
|
+
Invoked when door open is detected when panel is armed.
|
|
55
|
+
|
|
56
|
+
:param event_id: Index of the sensor.
|
|
57
|
+
:param zone_name: Name of the sensor that reports door open.
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
async def on_door_open_close(
|
|
61
|
+
self, event_id: int, zone_name: str, is_open: bool
|
|
62
|
+
) -> None:
|
|
63
|
+
"""
|
|
64
|
+
Invoked when door sensor reports it opened or closed.
|
|
65
|
+
|
|
66
|
+
:param event_id: Index of the sensor reporting the event.
|
|
67
|
+
:param zone_name: Name of the sensor that reports door open/close.
|
|
68
|
+
:param is_open: Indicates if the door is open.
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
async def on_low_battery(self, event_id: int, zone_name: str) -> None:
|
|
72
|
+
"""
|
|
73
|
+
Invoked when a sensor reports it is low on battery.
|
|
74
|
+
|
|
75
|
+
:param event_id: Index of the sensor.
|
|
76
|
+
:param zone_name: Name of the sensor that reports low battery.
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
async def on_alarm(
|
|
80
|
+
self, event_id: int, zone_name: str, is_tampered: bool
|
|
81
|
+
) -> None:
|
|
82
|
+
"""
|
|
83
|
+
Invoked when device triggers the alarm.
|
|
84
|
+
|
|
85
|
+
:param event_id: Index of the sensor.
|
|
86
|
+
:param zone_name: Name of the zone that triggered the alarm.
|
|
87
|
+
"""
|
|
88
|
+
|
|
89
|
+
async def on_remote_button_press(
|
|
90
|
+
self, event_id: int, zone_name: str, button: G90RemoteButtonStates
|
|
91
|
+
) -> None:
|
|
92
|
+
"""
|
|
93
|
+
Invoked when a remote button is pressed.
|
|
94
|
+
|
|
95
|
+
Please note there will only be call to the method w/o invoking
|
|
96
|
+
:meth:`G90DeviceNotifications.on_sensor_activity`.
|
|
97
|
+
|
|
98
|
+
:param event_id: Index of the sensor associated with the remote.
|
|
99
|
+
:param zone_name: Name of the sensor that reports remote button press.
|
|
100
|
+
:param button: The button pressed on the remote
|
|
101
|
+
"""
|
|
102
|
+
|
|
103
|
+
async def on_sos(
|
|
104
|
+
self, event_id: int, zone_name: str, is_host_sos: bool
|
|
105
|
+
) -> None:
|
|
106
|
+
"""
|
|
107
|
+
Invoked when SOS is triggered.
|
|
108
|
+
|
|
109
|
+
Please note that the panel might not set its status to alarm
|
|
110
|
+
internally, so that :meth:`G90DeviceNotifications` might need an
|
|
111
|
+
explicit call in the derived class to simulate that.
|
|
112
|
+
|
|
113
|
+
:param event_id: Index of the sensor.
|
|
114
|
+
:param zone_name: Name of the sensor that reports SOS.
|
|
115
|
+
:param is_host_sos: Indicates if the SOS is host-initiated.
|
|
116
|
+
"""
|