pyg90alarm 1.20.0__py3-none-any.whl → 2.0.1__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 +130 -34
- 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/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.20.0.dist-info → pyg90alarm-2.0.1.dist-info}/METADATA +101 -18
- pyg90alarm-2.0.1.dist-info/RECORD +40 -0
- {pyg90alarm-1.20.0.dist-info → pyg90alarm-2.0.1.dist-info}/WHEEL +1 -1
- pyg90alarm-1.20.0.dist-info/RECORD +0 -31
- /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.20.0.dist-info → pyg90alarm-2.0.1.dist-info/licenses}/LICENSE +0 -0
- {pyg90alarm-1.20.0.dist-info → pyg90alarm-2.0.1.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,29 +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)
|
|
72
78
|
from .entities.sensor_list import G90SensorList
|
|
73
79
|
from .entities.device import G90Device
|
|
74
80
|
from .entities.device_list import G90DeviceList
|
|
75
|
-
from .
|
|
76
|
-
|
|
81
|
+
from .notifications.protocol import (
|
|
82
|
+
G90NotificationProtocol
|
|
77
83
|
)
|
|
78
|
-
from .
|
|
79
|
-
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 (
|
|
80
88
|
G90TargetedDiscovery, G90DiscoveredDeviceTargeted,
|
|
81
89
|
)
|
|
82
|
-
from .host_info import G90HostInfo
|
|
83
|
-
from .host_status import G90HostStatus
|
|
84
|
-
from .config import (G90AlertConfig, G90AlertConfigFlags)
|
|
85
|
-
from .history import G90History
|
|
86
|
-
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
|
|
87
95
|
from .callback import G90Callback
|
|
88
96
|
from .exceptions import G90Error, G90TimeoutError
|
|
97
|
+
from .cloud.notifications import G90CloudNotifications
|
|
89
98
|
|
|
90
99
|
_LOGGER = logging.getLogger(__name__)
|
|
91
100
|
|
|
@@ -153,7 +162,7 @@ if TYPE_CHECKING:
|
|
|
153
162
|
|
|
154
163
|
|
|
155
164
|
# pylint: disable=too-many-public-methods
|
|
156
|
-
class G90Alarm(
|
|
165
|
+
class G90Alarm(G90NotificationProtocol):
|
|
157
166
|
|
|
158
167
|
"""
|
|
159
168
|
Allows to interact with G90 alarm panel.
|
|
@@ -169,19 +178,15 @@ class G90Alarm(G90DeviceNotifications):
|
|
|
169
178
|
simulated to go into inactive state.
|
|
170
179
|
"""
|
|
171
180
|
# pylint: disable=too-many-instance-attributes,too-many-arguments
|
|
172
|
-
def __init__(
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
super().__init__(
|
|
177
|
-
local_host=notifications_local_host,
|
|
178
|
-
local_port=notifications_local_port
|
|
179
|
-
)
|
|
181
|
+
def __init__(
|
|
182
|
+
self, host: str, port: int = REMOTE_PORT,
|
|
183
|
+
reset_occupancy_interval: float = 3.0
|
|
184
|
+
) -> None:
|
|
180
185
|
self._host: str = host
|
|
181
186
|
self._port: int = port
|
|
187
|
+
self._notifications: Optional[G90NotificationsBase] = None
|
|
182
188
|
self._sensors = G90SensorList(self)
|
|
183
189
|
self._devices = G90DeviceList(self)
|
|
184
|
-
self._notifications: Optional[G90DeviceNotifications] = None
|
|
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:
|
|
@@ -338,7 +361,8 @@ class G90Alarm(G90DeviceNotifications):
|
|
|
338
361
|
"""
|
|
339
362
|
res = await self.command(G90Commands.GETHOSTINFO)
|
|
340
363
|
info = G90HostInfo(*res)
|
|
341
|
-
self.
|
|
364
|
+
if self._notifications:
|
|
365
|
+
self._notifications.device_id = info.host_guid
|
|
342
366
|
return info
|
|
343
367
|
|
|
344
368
|
@property
|
|
@@ -493,6 +517,9 @@ class G90Alarm(G90DeviceNotifications):
|
|
|
493
517
|
alert_config_flags = await self.alert_config
|
|
494
518
|
door_close_alert_enabled = (
|
|
495
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
|
|
496
523
|
sensor_is_door = sensor.type == G90SensorTypes.DOOR
|
|
497
524
|
|
|
498
525
|
# Alarm panel could emit door close alerts (if enabled) for sensors
|
|
@@ -502,7 +529,7 @@ class G90Alarm(G90DeviceNotifications):
|
|
|
502
529
|
if not door_close_alert_enabled or not sensor_is_door:
|
|
503
530
|
_LOGGER.debug("Sensor '%s' is not a door (type %s),"
|
|
504
531
|
' or door close alert is disabled'
|
|
505
|
-
' (alert config flags %s),'
|
|
532
|
+
' (alert config flags %s) or is a cord sensor,'
|
|
506
533
|
' closing event will be emulated upon'
|
|
507
534
|
' %s seconds',
|
|
508
535
|
name, sensor.type, alert_config_flags,
|
|
@@ -831,17 +858,19 @@ class G90Alarm(G90DeviceNotifications):
|
|
|
831
858
|
def tamper_callback(self, value: TamperCallback) -> None:
|
|
832
859
|
self._tamper_cb = value
|
|
833
860
|
|
|
834
|
-
async def
|
|
861
|
+
async def listen_notifications(self) -> None:
|
|
835
862
|
"""
|
|
836
863
|
Starts internal listener for device notifications/alerts.
|
|
837
864
|
"""
|
|
838
|
-
|
|
865
|
+
if self._notifications:
|
|
866
|
+
await self._notifications.listen()
|
|
839
867
|
|
|
840
|
-
def
|
|
868
|
+
async def close_notifications(self) -> None:
|
|
841
869
|
"""
|
|
842
870
|
Closes the listener for device notifications/alerts.
|
|
843
871
|
"""
|
|
844
|
-
self.
|
|
872
|
+
if self._notifications:
|
|
873
|
+
await self._notifications.close()
|
|
845
874
|
|
|
846
875
|
async def arm_away(self) -> None:
|
|
847
876
|
"""
|
|
@@ -896,9 +925,12 @@ class G90Alarm(G90DeviceNotifications):
|
|
|
896
925
|
each polling cycle
|
|
897
926
|
"""
|
|
898
927
|
# Remember if device notifications listener has been started already
|
|
899
|
-
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
|
+
)
|
|
900
932
|
# And then stop it
|
|
901
|
-
self.
|
|
933
|
+
await self.close_notifications()
|
|
902
934
|
|
|
903
935
|
# Start the task
|
|
904
936
|
self._alert_simulation_task = asyncio.create_task(
|
|
@@ -920,8 +952,11 @@ class G90Alarm(G90DeviceNotifications):
|
|
|
920
952
|
|
|
921
953
|
# Start device notifications listener back if it was running when
|
|
922
954
|
# simulated alerts have been enabled
|
|
923
|
-
if
|
|
924
|
-
|
|
955
|
+
if (
|
|
956
|
+
self._notifications
|
|
957
|
+
and self._alert_simulation_start_listener_back
|
|
958
|
+
):
|
|
959
|
+
await self._notifications.listen()
|
|
925
960
|
|
|
926
961
|
async def _simulate_alerts_from_history(
|
|
927
962
|
self, interval: float, history_depth: int
|
|
@@ -935,6 +970,10 @@ class G90Alarm(G90DeviceNotifications):
|
|
|
935
970
|
|
|
936
971
|
See :meth:`.start_simulating_alerts_from_history` for the parameters.
|
|
937
972
|
"""
|
|
973
|
+
dummy_notifications = G90NotificationsBase(
|
|
974
|
+
protocol_factory=lambda: self
|
|
975
|
+
)
|
|
976
|
+
|
|
938
977
|
last_history_ts = None
|
|
939
978
|
|
|
940
979
|
_LOGGER.debug(
|
|
@@ -978,8 +1017,7 @@ class G90Alarm(G90DeviceNotifications):
|
|
|
978
1017
|
# Send the history entry down the device notification
|
|
979
1018
|
# code as alert, as if it came from the device and its
|
|
980
1019
|
# notifications port
|
|
981
|
-
|
|
982
|
-
(self._host, self._notifications_local_port),
|
|
1020
|
+
dummy_notifications.handle_alert(
|
|
983
1021
|
item.as_device_alert(),
|
|
984
1022
|
# Skip verifying device GUID, since history entry
|
|
985
1023
|
# don't have it
|
|
@@ -1005,3 +1043,61 @@ class G90Alarm(G90DeviceNotifications):
|
|
|
1005
1043
|
|
|
1006
1044
|
# Sleep to next iteration
|
|
1007
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: Optional[str] = REMOTE_CLOUD_HOST,
|
|
1068
|
+
upstream_port: Optional[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
|