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.
Files changed (34) hide show
  1. pyg90alarm/__init__.py +5 -5
  2. pyg90alarm/alarm.py +159 -114
  3. pyg90alarm/cloud/__init__.py +31 -0
  4. pyg90alarm/cloud/const.py +56 -0
  5. pyg90alarm/cloud/messages.py +593 -0
  6. pyg90alarm/cloud/notifications.py +409 -0
  7. pyg90alarm/cloud/protocol.py +518 -0
  8. pyg90alarm/const.py +5 -0
  9. pyg90alarm/entities/base_entity.py +83 -0
  10. pyg90alarm/entities/base_list.py +165 -0
  11. pyg90alarm/entities/device_list.py +58 -0
  12. pyg90alarm/entities/sensor.py +63 -3
  13. pyg90alarm/entities/sensor_list.py +50 -0
  14. pyg90alarm/local/__init__.py +0 -0
  15. pyg90alarm/{base_cmd.py → local/base_cmd.py} +3 -6
  16. pyg90alarm/{discovery.py → local/discovery.py} +1 -1
  17. pyg90alarm/{history.py → local/history.py} +4 -2
  18. pyg90alarm/{host_status.py → local/host_status.py} +1 -1
  19. pyg90alarm/local/notifications.py +116 -0
  20. pyg90alarm/{paginated_cmd.py → local/paginated_cmd.py} +2 -2
  21. pyg90alarm/{paginated_result.py → local/paginated_result.py} +1 -1
  22. pyg90alarm/{targeted_discovery.py → local/targeted_discovery.py} +2 -2
  23. pyg90alarm/notifications/__init__.py +0 -0
  24. pyg90alarm/{device_notifications.py → notifications/base.py} +115 -173
  25. pyg90alarm/notifications/protocol.py +116 -0
  26. {pyg90alarm-1.19.0.dist-info → pyg90alarm-2.0.0.dist-info}/METADATA +112 -18
  27. pyg90alarm-2.0.0.dist-info/RECORD +40 -0
  28. {pyg90alarm-1.19.0.dist-info → pyg90alarm-2.0.0.dist-info}/WHEEL +1 -1
  29. pyg90alarm-1.19.0.dist-info/RECORD +0 -27
  30. /pyg90alarm/{config.py → local/config.py} +0 -0
  31. /pyg90alarm/{host_info.py → local/host_info.py} +0 -0
  32. /pyg90alarm/{user_data_crc.py → local/user_data_crc.py} +0 -0
  33. {pyg90alarm-1.19.0.dist-info → pyg90alarm-2.0.0.dist-info/licenses}/LICENSE +0 -0
  34. {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 .device_notifications import (
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:: Only protocol 1.2 is supported!
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 .device_notifications import (
74
- G90DeviceNotifications,
80
+ from .entities.device_list import G90DeviceList
81
+ from .notifications.protocol import (
82
+ G90NotificationProtocol
75
83
  )
76
- from .discovery import G90Discovery, G90DiscoveredDevice
77
- from .targeted_discovery import (
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(G90DeviceNotifications):
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__(self, host: str, port: int = REMOTE_PORT,
171
- reset_occupancy_interval: float = 3.0,
172
- notifications_local_host: str = LOCAL_NOTIFICATIONS_HOST,
173
- notifications_local_port: int = LOCAL_NOTIFICATIONS_PORT):
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._sensors: List[G90Sensor] = []
181
- self._sensors_lock = asyncio.Lock()
182
- self._devices: List[G90Device] = []
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
- Property over new :meth:`.get_sensors` method, retained for
272
- compatibility.
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.get_sensors()
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. Please note the list
279
- is cached upon first call, so you need to re-instantiate the class to
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
- # Use lock around the operation, to ensure no duplicated entries in the
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
- return self._sensors
304
-
305
- async def find_sensor(self, idx: int, name: str) -> Optional[G90Sensor]:
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
- sensors = await self.get_sensors()
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
- Property over new :meth:`.get_devices` method, retained for
334
- compatibility.
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.get_devices()
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. Please
341
- note the list is cached upon first call, so you need to re-instantiate
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
- # See `get_sensors` method for the rationale behind the lock usage
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.device_id = info.host_guid
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.get_sensors():
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 listen_device_notifications(self) -> None:
861
+ async def listen_notifications(self) -> None:
886
862
  """
887
863
  Starts internal listener for device notifications/alerts.
888
864
  """
889
- await self.listen()
865
+ if self._notifications:
866
+ await self._notifications.listen()
890
867
 
891
- def close_device_notifications(self) -> None:
868
+ async def close_notifications(self) -> None:
892
869
  """
893
870
  Closes the listener for device notifications/alerts.
894
871
  """
895
- self.close()
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 = self.listener_started
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.close()
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 self._alert_simulation_start_listener_back:
975
- await self.listen()
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
- self._handle_alert(
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