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
pyg90alarm/__init__.py
CHANGED
|
@@ -23,17 +23,17 @@ Python package to control G90-based alarm systems.
|
|
|
23
23
|
"""
|
|
24
24
|
|
|
25
25
|
from .alarm import G90Alarm
|
|
26
|
-
from .base_cmd import G90BaseCommand
|
|
27
|
-
from .paginated_result import G90PaginatedResult
|
|
28
|
-
from .
|
|
26
|
+
from .local.base_cmd import G90BaseCommand
|
|
27
|
+
from .local.paginated_result import G90PaginatedResult
|
|
28
|
+
from .notifications.base import (
|
|
29
29
|
G90DeviceAlert,
|
|
30
30
|
)
|
|
31
31
|
from .entities.sensor import G90Sensor, G90SensorTypes
|
|
32
32
|
from .entities.device import G90Device
|
|
33
|
-
from .host_info import (
|
|
33
|
+
from .local.host_info import (
|
|
34
34
|
G90HostInfo, G90HostInfoWifiStatus, G90HostInfoGsmStatus
|
|
35
35
|
)
|
|
36
|
-
from .host_status import G90HostStatus
|
|
36
|
+
from .local.host_status import G90HostStatus
|
|
37
37
|
from .const import (
|
|
38
38
|
G90MessageTypes,
|
|
39
39
|
G90NotificationTypes,
|
pyg90alarm/alarm.py
CHANGED
|
@@ -22,7 +22,8 @@
|
|
|
22
22
|
"""
|
|
23
23
|
Provides interface to G90 alarm panel.
|
|
24
24
|
|
|
25
|
-
.. note::
|
|
25
|
+
.. note:: Both local protocol (referred to as 1.2) and cloud one
|
|
26
|
+
(mentioned as 1.1) are supported.
|
|
26
27
|
|
|
27
28
|
The next example queries the device with IP address `10.10.10.250` for the
|
|
28
29
|
information - the product name, protocol version, HW versions and such.
|
|
@@ -52,6 +53,7 @@ G90HostInfo(host_guid='<...>',
|
|
|
52
53
|
from __future__ import annotations
|
|
53
54
|
import asyncio
|
|
54
55
|
from asyncio import Task
|
|
56
|
+
from datetime import datetime
|
|
55
57
|
import logging
|
|
56
58
|
from typing import (
|
|
57
59
|
TYPE_CHECKING, Any, List, Optional, AsyncGenerator,
|
|
@@ -63,27 +65,36 @@ from .const import (
|
|
|
63
65
|
LOCAL_TARGETED_DISCOVERY_PORT,
|
|
64
66
|
LOCAL_NOTIFICATIONS_HOST,
|
|
65
67
|
LOCAL_NOTIFICATIONS_PORT,
|
|
68
|
+
CLOUD_NOTIFICATIONS_HOST,
|
|
69
|
+
CLOUD_NOTIFICATIONS_PORT,
|
|
70
|
+
REMOTE_CLOUD_HOST,
|
|
71
|
+
REMOTE_CLOUD_PORT,
|
|
66
72
|
G90ArmDisarmTypes,
|
|
67
73
|
G90RemoteButtonStates,
|
|
68
74
|
)
|
|
69
|
-
from .base_cmd import (G90BaseCommand, G90BaseCommandData)
|
|
70
|
-
from .paginated_result import G90PaginatedResult, G90PaginatedResponse
|
|
75
|
+
from .local.base_cmd import (G90BaseCommand, G90BaseCommandData)
|
|
76
|
+
from .local.paginated_result import G90PaginatedResult, G90PaginatedResponse
|
|
71
77
|
from .entities.sensor import (G90Sensor, G90SensorTypes)
|
|
78
|
+
from .entities.sensor_list import G90SensorList
|
|
72
79
|
from .entities.device import G90Device
|
|
73
|
-
from .
|
|
74
|
-
|
|
80
|
+
from .entities.device_list import G90DeviceList
|
|
81
|
+
from .notifications.protocol import (
|
|
82
|
+
G90NotificationProtocol
|
|
75
83
|
)
|
|
76
|
-
from .
|
|
77
|
-
from .
|
|
84
|
+
from .notifications.base import G90NotificationsBase
|
|
85
|
+
from .local.notifications import G90LocalNotifications
|
|
86
|
+
from .local.discovery import G90Discovery, G90DiscoveredDevice
|
|
87
|
+
from .local.targeted_discovery import (
|
|
78
88
|
G90TargetedDiscovery, G90DiscoveredDeviceTargeted,
|
|
79
89
|
)
|
|
80
|
-
from .host_info import G90HostInfo
|
|
81
|
-
from .host_status import G90HostStatus
|
|
82
|
-
from .config import (G90AlertConfig, G90AlertConfigFlags)
|
|
83
|
-
from .history import G90History
|
|
84
|
-
from .user_data_crc import G90UserDataCRC
|
|
90
|
+
from .local.host_info import G90HostInfo
|
|
91
|
+
from .local.host_status import G90HostStatus
|
|
92
|
+
from .local.config import (G90AlertConfig, G90AlertConfigFlags)
|
|
93
|
+
from .local.history import G90History
|
|
94
|
+
from .local.user_data_crc import G90UserDataCRC
|
|
85
95
|
from .callback import G90Callback
|
|
86
96
|
from .exceptions import G90Error, G90TimeoutError
|
|
97
|
+
from .cloud.notifications import G90CloudNotifications
|
|
87
98
|
|
|
88
99
|
_LOGGER = logging.getLogger(__name__)
|
|
89
100
|
|
|
@@ -151,7 +162,7 @@ if TYPE_CHECKING:
|
|
|
151
162
|
|
|
152
163
|
|
|
153
164
|
# pylint: disable=too-many-public-methods
|
|
154
|
-
class G90Alarm(
|
|
165
|
+
class G90Alarm(G90NotificationProtocol):
|
|
155
166
|
|
|
156
167
|
"""
|
|
157
168
|
Allows to interact with G90 alarm panel.
|
|
@@ -167,21 +178,15 @@ class G90Alarm(G90DeviceNotifications):
|
|
|
167
178
|
simulated to go into inactive state.
|
|
168
179
|
"""
|
|
169
180
|
# pylint: disable=too-many-instance-attributes,too-many-arguments
|
|
170
|
-
def __init__(
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
super().__init__(
|
|
175
|
-
local_host=notifications_local_host,
|
|
176
|
-
local_port=notifications_local_port
|
|
177
|
-
)
|
|
181
|
+
def __init__(
|
|
182
|
+
self, host: str, port: int = REMOTE_PORT,
|
|
183
|
+
reset_occupancy_interval: float = 3.0
|
|
184
|
+
) -> None:
|
|
178
185
|
self._host: str = host
|
|
179
186
|
self._port: int = port
|
|
180
|
-
self.
|
|
181
|
-
self.
|
|
182
|
-
self._devices
|
|
183
|
-
self._devices_lock = asyncio.Lock()
|
|
184
|
-
self._notifications: Optional[G90DeviceNotifications] = None
|
|
187
|
+
self._notifications: Optional[G90NotificationsBase] = None
|
|
188
|
+
self._sensors = G90SensorList(self)
|
|
189
|
+
self._devices = G90DeviceList(self)
|
|
185
190
|
self._sensor_cb: Optional[SensorCallback] = None
|
|
186
191
|
self._armdisarm_cb: Optional[ArmDisarmCallback] = None
|
|
187
192
|
self._door_open_close_cb: Optional[DoorOpenCloseCallback] = None
|
|
@@ -201,6 +206,24 @@ class G90Alarm(G90DeviceNotifications):
|
|
|
201
206
|
self._alert_simulation_task: Optional[Task[Any]] = None
|
|
202
207
|
self._alert_simulation_start_listener_back = False
|
|
203
208
|
|
|
209
|
+
@property
|
|
210
|
+
def host(self) -> str:
|
|
211
|
+
"""
|
|
212
|
+
Returns the hostname or IP address of the alarm panel.
|
|
213
|
+
|
|
214
|
+
This is the address used for communication with the device.
|
|
215
|
+
"""
|
|
216
|
+
return self._host
|
|
217
|
+
|
|
218
|
+
@property
|
|
219
|
+
def port(self) -> int:
|
|
220
|
+
"""
|
|
221
|
+
Returns the UDP port number used to communicate with the alarm panel.
|
|
222
|
+
|
|
223
|
+
By default, this is set to the standard G90 protocol port.
|
|
224
|
+
"""
|
|
225
|
+
return self._port
|
|
226
|
+
|
|
204
227
|
async def command(
|
|
205
228
|
self, code: G90Commands, data: Optional[G90BaseCommandData] = None
|
|
206
229
|
) -> G90BaseCommandData:
|
|
@@ -268,109 +291,58 @@ class G90Alarm(G90DeviceNotifications):
|
|
|
268
291
|
@property
|
|
269
292
|
async def sensors(self) -> List[G90Sensor]:
|
|
270
293
|
"""
|
|
271
|
-
|
|
272
|
-
|
|
294
|
+
Returns the list of sensors configured in the device. Please note
|
|
295
|
+
it doesn't update those from the panel except initially when the list
|
|
296
|
+
if empty.
|
|
297
|
+
|
|
298
|
+
:return: List of sensors
|
|
273
299
|
"""
|
|
274
|
-
return await self.
|
|
300
|
+
return await self._sensors.entities
|
|
275
301
|
|
|
276
302
|
async def get_sensors(self) -> List[G90Sensor]:
|
|
277
303
|
"""
|
|
278
|
-
Provides list of sensors configured in the device
|
|
279
|
-
|
|
280
|
-
reflect any updates there.
|
|
304
|
+
Provides list of sensors configured in the device, updating them from
|
|
305
|
+
panel on each call.
|
|
281
306
|
|
|
282
307
|
:return: List of sensors
|
|
283
308
|
"""
|
|
284
|
-
|
|
285
|
-
# resulting list or redundant exchanges with panel are made when the
|
|
286
|
-
# method is called concurrently
|
|
287
|
-
async with self._sensors_lock:
|
|
288
|
-
if not self._sensors:
|
|
289
|
-
sensors = self.paginated_result(
|
|
290
|
-
G90Commands.GETSENSORLIST
|
|
291
|
-
)
|
|
292
|
-
async for sensor in sensors:
|
|
293
|
-
obj = G90Sensor(
|
|
294
|
-
*sensor.data, parent=self, subindex=0,
|
|
295
|
-
proto_idx=sensor.proto_idx
|
|
296
|
-
)
|
|
297
|
-
self._sensors.append(obj)
|
|
298
|
-
|
|
299
|
-
_LOGGER.debug(
|
|
300
|
-
'Total number of sensors: %s', len(self._sensors)
|
|
301
|
-
)
|
|
309
|
+
return await self._sensors.update()
|
|
302
310
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
311
|
+
async def find_sensor(
|
|
312
|
+
self, idx: int, name: str, exclude_unavailable: bool = True
|
|
313
|
+
) -> Optional[G90Sensor]:
|
|
306
314
|
"""
|
|
307
315
|
Finds sensor by index and name.
|
|
308
316
|
|
|
309
317
|
:param idx: Sensor index
|
|
310
318
|
:param name: Sensor name
|
|
319
|
+
:param exclude_unavailable: Flag indicating if unavailable sensors
|
|
320
|
+
should be excluded from the search
|
|
311
321
|
:return: Sensor instance
|
|
312
322
|
"""
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
# Fast lookup by direct index
|
|
316
|
-
if idx < len(sensors) and sensors[idx].name == name:
|
|
317
|
-
sensor = sensors[idx]
|
|
318
|
-
_LOGGER.debug('Found sensor via fast lookup: %s', sensor)
|
|
319
|
-
return sensor
|
|
320
|
-
|
|
321
|
-
# Fast lookup failed, perform slow one over the whole sensors list
|
|
322
|
-
for sensor in sensors:
|
|
323
|
-
if sensor.index == idx and sensor.name == name:
|
|
324
|
-
_LOGGER.debug('Found sensor: %s', sensor)
|
|
325
|
-
return sensor
|
|
326
|
-
|
|
327
|
-
_LOGGER.error('Sensor not found: idx=%s, name=%s', idx, name)
|
|
328
|
-
return None
|
|
323
|
+
return await self._sensors.find(idx, name, exclude_unavailable)
|
|
329
324
|
|
|
330
325
|
@property
|
|
331
326
|
async def devices(self) -> List[G90Device]:
|
|
332
327
|
"""
|
|
333
|
-
|
|
334
|
-
|
|
328
|
+
Returns the list of devices (switches) configured in the device. Please
|
|
329
|
+
note it doesn't update those from the panel except initially when
|
|
330
|
+
the list if empty.
|
|
331
|
+
|
|
332
|
+
:return: List of devices
|
|
335
333
|
"""
|
|
336
|
-
return await self.
|
|
334
|
+
return await self._devices.entities
|
|
337
335
|
|
|
338
336
|
async def get_devices(self) -> List[G90Device]:
|
|
339
337
|
"""
|
|
340
|
-
Provides list of devices (switches) configured in the device
|
|
341
|
-
|
|
342
|
-
the class to reflect any updates there. Multi-node devices, those
|
|
338
|
+
Provides list of devices (switches) configured in the device, updating
|
|
339
|
+
them from panel on each call. Multi-node devices, those
|
|
343
340
|
having multiple ports, are expanded into corresponding number of
|
|
344
341
|
resulting entries.
|
|
345
342
|
|
|
346
343
|
:return: List of devices
|
|
347
344
|
"""
|
|
348
|
-
|
|
349
|
-
async with self._devices_lock:
|
|
350
|
-
if not self._devices:
|
|
351
|
-
devices = self.paginated_result(
|
|
352
|
-
G90Commands.GETDEVICELIST
|
|
353
|
-
)
|
|
354
|
-
async for device in devices:
|
|
355
|
-
obj = G90Device(
|
|
356
|
-
*device.data, parent=self, subindex=0,
|
|
357
|
-
proto_idx=device.proto_idx
|
|
358
|
-
)
|
|
359
|
-
self._devices.append(obj)
|
|
360
|
-
# Multi-node devices (first node has already been added
|
|
361
|
-
# above
|
|
362
|
-
for node in range(1, obj.node_count):
|
|
363
|
-
obj = G90Device(
|
|
364
|
-
*device.data, parent=self,
|
|
365
|
-
subindex=node, proto_idx=device.proto_idx
|
|
366
|
-
)
|
|
367
|
-
self._devices.append(obj)
|
|
368
|
-
|
|
369
|
-
_LOGGER.debug(
|
|
370
|
-
'Total number of devices: %s', len(self._devices)
|
|
371
|
-
)
|
|
372
|
-
|
|
373
|
-
return self._devices
|
|
345
|
+
return await self._devices.update()
|
|
374
346
|
|
|
375
347
|
@property
|
|
376
348
|
async def host_info(self) -> G90HostInfo:
|
|
@@ -389,7 +361,8 @@ class G90Alarm(G90DeviceNotifications):
|
|
|
389
361
|
"""
|
|
390
362
|
res = await self.command(G90Commands.GETHOSTINFO)
|
|
391
363
|
info = G90HostInfo(*res)
|
|
392
|
-
self.
|
|
364
|
+
if self._notifications:
|
|
365
|
+
self._notifications.device_id = info.host_guid
|
|
393
366
|
return info
|
|
394
367
|
|
|
395
368
|
@property
|
|
@@ -544,6 +517,9 @@ class G90Alarm(G90DeviceNotifications):
|
|
|
544
517
|
alert_config_flags = await self.alert_config
|
|
545
518
|
door_close_alert_enabled = (
|
|
546
519
|
G90AlertConfigFlags.DOOR_CLOSE in alert_config_flags)
|
|
520
|
+
# The condition intentionally doesn't account for cord sensors of
|
|
521
|
+
# subtype door, since those won't send door open/close alerts, only
|
|
522
|
+
# notifications
|
|
547
523
|
sensor_is_door = sensor.type == G90SensorTypes.DOOR
|
|
548
524
|
|
|
549
525
|
# Alarm panel could emit door close alerts (if enabled) for sensors
|
|
@@ -553,7 +529,7 @@ class G90Alarm(G90DeviceNotifications):
|
|
|
553
529
|
if not door_close_alert_enabled or not sensor_is_door:
|
|
554
530
|
_LOGGER.debug("Sensor '%s' is not a door (type %s),"
|
|
555
531
|
' or door close alert is disabled'
|
|
556
|
-
' (alert config flags %s),'
|
|
532
|
+
' (alert config flags %s) or is a cord sensor,'
|
|
557
533
|
' closing event will be emulated upon'
|
|
558
534
|
' %s seconds',
|
|
559
535
|
name, sensor.type, alert_config_flags,
|
|
@@ -637,7 +613,7 @@ class G90Alarm(G90DeviceNotifications):
|
|
|
637
613
|
|
|
638
614
|
# Reset the tampered and door open when arming flags on all sensors
|
|
639
615
|
# having those set
|
|
640
|
-
for sensor in await self.
|
|
616
|
+
for sensor in await self.sensors:
|
|
641
617
|
if sensor.is_tampered:
|
|
642
618
|
# pylint: disable=protected-access
|
|
643
619
|
sensor._set_tampered(False)
|
|
@@ -882,17 +858,19 @@ class G90Alarm(G90DeviceNotifications):
|
|
|
882
858
|
def tamper_callback(self, value: TamperCallback) -> None:
|
|
883
859
|
self._tamper_cb = value
|
|
884
860
|
|
|
885
|
-
async def
|
|
861
|
+
async def listen_notifications(self) -> None:
|
|
886
862
|
"""
|
|
887
863
|
Starts internal listener for device notifications/alerts.
|
|
888
864
|
"""
|
|
889
|
-
|
|
865
|
+
if self._notifications:
|
|
866
|
+
await self._notifications.listen()
|
|
890
867
|
|
|
891
|
-
def
|
|
868
|
+
async def close_notifications(self) -> None:
|
|
892
869
|
"""
|
|
893
870
|
Closes the listener for device notifications/alerts.
|
|
894
871
|
"""
|
|
895
|
-
self.
|
|
872
|
+
if self._notifications:
|
|
873
|
+
await self._notifications.close()
|
|
896
874
|
|
|
897
875
|
async def arm_away(self) -> None:
|
|
898
876
|
"""
|
|
@@ -947,9 +925,12 @@ class G90Alarm(G90DeviceNotifications):
|
|
|
947
925
|
each polling cycle
|
|
948
926
|
"""
|
|
949
927
|
# Remember if device notifications listener has been started already
|
|
950
|
-
self._alert_simulation_start_listener_back =
|
|
928
|
+
self._alert_simulation_start_listener_back = (
|
|
929
|
+
self._notifications is not None
|
|
930
|
+
and self._notifications.listener_started
|
|
931
|
+
)
|
|
951
932
|
# And then stop it
|
|
952
|
-
self.
|
|
933
|
+
await self.close_notifications()
|
|
953
934
|
|
|
954
935
|
# Start the task
|
|
955
936
|
self._alert_simulation_task = asyncio.create_task(
|
|
@@ -971,8 +952,11 @@ class G90Alarm(G90DeviceNotifications):
|
|
|
971
952
|
|
|
972
953
|
# Start device notifications listener back if it was running when
|
|
973
954
|
# simulated alerts have been enabled
|
|
974
|
-
if
|
|
975
|
-
|
|
955
|
+
if (
|
|
956
|
+
self._notifications
|
|
957
|
+
and self._alert_simulation_start_listener_back
|
|
958
|
+
):
|
|
959
|
+
await self._notifications.listen()
|
|
976
960
|
|
|
977
961
|
async def _simulate_alerts_from_history(
|
|
978
962
|
self, interval: float, history_depth: int
|
|
@@ -986,6 +970,10 @@ class G90Alarm(G90DeviceNotifications):
|
|
|
986
970
|
|
|
987
971
|
See :meth:`.start_simulating_alerts_from_history` for the parameters.
|
|
988
972
|
"""
|
|
973
|
+
dummy_notifications = G90NotificationsBase(
|
|
974
|
+
protocol_factory=lambda: self
|
|
975
|
+
)
|
|
976
|
+
|
|
989
977
|
last_history_ts = None
|
|
990
978
|
|
|
991
979
|
_LOGGER.debug(
|
|
@@ -1029,8 +1017,7 @@ class G90Alarm(G90DeviceNotifications):
|
|
|
1029
1017
|
# Send the history entry down the device notification
|
|
1030
1018
|
# code as alert, as if it came from the device and its
|
|
1031
1019
|
# notifications port
|
|
1032
|
-
|
|
1033
|
-
(self._host, self._notifications_local_port),
|
|
1020
|
+
dummy_notifications.handle_alert(
|
|
1034
1021
|
item.as_device_alert(),
|
|
1035
1022
|
# Skip verifying device GUID, since history entry
|
|
1036
1023
|
# don't have it
|
|
@@ -1056,3 +1043,61 @@ class G90Alarm(G90DeviceNotifications):
|
|
|
1056
1043
|
|
|
1057
1044
|
# Sleep to next iteration
|
|
1058
1045
|
await asyncio.sleep(interval)
|
|
1046
|
+
|
|
1047
|
+
async def use_local_notifications(
|
|
1048
|
+
self, notifications_local_host: str = LOCAL_NOTIFICATIONS_HOST,
|
|
1049
|
+
notifications_local_port: int = LOCAL_NOTIFICATIONS_PORT
|
|
1050
|
+
) -> None:
|
|
1051
|
+
"""
|
|
1052
|
+
Switches to use local notifications for device alerts.
|
|
1053
|
+
"""
|
|
1054
|
+
await self.close_notifications()
|
|
1055
|
+
|
|
1056
|
+
self._notifications = G90LocalNotifications(
|
|
1057
|
+
protocol_factory=lambda: self,
|
|
1058
|
+
host=self._host,
|
|
1059
|
+
port=self._port,
|
|
1060
|
+
local_host=notifications_local_host,
|
|
1061
|
+
local_port=notifications_local_port
|
|
1062
|
+
)
|
|
1063
|
+
|
|
1064
|
+
async def use_cloud_notifications(
|
|
1065
|
+
self, cloud_local_host: str = CLOUD_NOTIFICATIONS_HOST,
|
|
1066
|
+
cloud_local_port: int = CLOUD_NOTIFICATIONS_PORT,
|
|
1067
|
+
upstream_host: str = REMOTE_CLOUD_HOST,
|
|
1068
|
+
upstream_port: int = REMOTE_CLOUD_PORT,
|
|
1069
|
+
keep_single_connection: bool = True
|
|
1070
|
+
) -> None:
|
|
1071
|
+
"""
|
|
1072
|
+
Switches to use cloud notifications for device alerts.
|
|
1073
|
+
"""
|
|
1074
|
+
await self.close_notifications()
|
|
1075
|
+
|
|
1076
|
+
self._notifications = G90CloudNotifications(
|
|
1077
|
+
protocol_factory=lambda: self,
|
|
1078
|
+
upstream_host=upstream_host,
|
|
1079
|
+
upstream_port=upstream_port,
|
|
1080
|
+
local_host=cloud_local_host,
|
|
1081
|
+
local_port=cloud_local_port,
|
|
1082
|
+
keep_single_connection=keep_single_connection
|
|
1083
|
+
)
|
|
1084
|
+
|
|
1085
|
+
@property
|
|
1086
|
+
def last_device_packet_time(self) -> Optional[datetime]:
|
|
1087
|
+
"""
|
|
1088
|
+
Returns the time of the last packet received from the device.
|
|
1089
|
+
"""
|
|
1090
|
+
if not self._notifications:
|
|
1091
|
+
return None
|
|
1092
|
+
|
|
1093
|
+
return self._notifications.last_device_packet_time
|
|
1094
|
+
|
|
1095
|
+
@property
|
|
1096
|
+
def last_upstream_packet_time(self) -> Optional[datetime]:
|
|
1097
|
+
"""
|
|
1098
|
+
Returns the time of the last packet received from the upstream server.
|
|
1099
|
+
"""
|
|
1100
|
+
if not self._notifications:
|
|
1101
|
+
return None
|
|
1102
|
+
|
|
1103
|
+
return self._notifications.last_upstream_packet_time
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Copyright (c) 2025 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
|
+
Cloud communication implementation for G90 alarm systems.
|
|
23
|
+
|
|
24
|
+
This module provides the necessary components to interact with G90 alarm
|
|
25
|
+
systems through their cloud protocol (referred to as version 1.1).
|
|
26
|
+
"""
|
|
27
|
+
from .notifications import G90CloudNotifications # noqa: F401
|
|
28
|
+
|
|
29
|
+
__all__ = [
|
|
30
|
+
'G90CloudNotifications',
|
|
31
|
+
]
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Copyright (c) 2025 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
|
+
Constants used in the G90 cloud protocol implementation.
|
|
23
|
+
|
|
24
|
+
This module defines the main enumerations for direction and command types
|
|
25
|
+
used in the cloud protocol communication with G90 alarm systems.
|
|
26
|
+
"""
|
|
27
|
+
from enum import IntEnum
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class G90CloudDirection(IntEnum):
|
|
31
|
+
"""
|
|
32
|
+
Defines the direction of G90 cloud protocol messages.
|
|
33
|
+
|
|
34
|
+
These values indicate whether messages are flowing from the cloud to the
|
|
35
|
+
device, from the device to the cloud, or are part of discovery processes.
|
|
36
|
+
"""
|
|
37
|
+
UNSPECIFIED = 0
|
|
38
|
+
CLOUD = 32 # 0x20
|
|
39
|
+
DEVICE = 16 # 0x10
|
|
40
|
+
DEVICE_DISCOVERY = 48 # 0x30
|
|
41
|
+
CLOUD_DISCOVERY = 208 # 0xD0
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class G90CloudCommand(IntEnum):
|
|
45
|
+
"""
|
|
46
|
+
Defines the command types used in G90 cloud protocol messages.
|
|
47
|
+
|
|
48
|
+
These values identify the purpose of each cloud protocol message,
|
|
49
|
+
such as hello messages, notifications, commands, and status updates.
|
|
50
|
+
"""
|
|
51
|
+
HELLO = 0x01
|
|
52
|
+
HELLO_ACK = 0x41
|
|
53
|
+
NOTIFICATION = 0x22
|
|
54
|
+
STATUS_CHANGE = 0x21
|
|
55
|
+
HELLO_INFO = 0x63
|
|
56
|
+
COMMAND = 0x29
|