pyg90alarm 2.3.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 (42) hide show
  1. pyg90alarm/__init__.py +84 -0
  2. pyg90alarm/alarm.py +1274 -0
  3. pyg90alarm/callback.py +146 -0
  4. pyg90alarm/cloud/__init__.py +31 -0
  5. pyg90alarm/cloud/const.py +56 -0
  6. pyg90alarm/cloud/messages.py +593 -0
  7. pyg90alarm/cloud/notifications.py +410 -0
  8. pyg90alarm/cloud/protocol.py +518 -0
  9. pyg90alarm/const.py +273 -0
  10. pyg90alarm/definitions/__init__.py +3 -0
  11. pyg90alarm/definitions/base.py +247 -0
  12. pyg90alarm/definitions/devices.py +366 -0
  13. pyg90alarm/definitions/sensors.py +843 -0
  14. pyg90alarm/entities/__init__.py +3 -0
  15. pyg90alarm/entities/base_entity.py +93 -0
  16. pyg90alarm/entities/base_list.py +268 -0
  17. pyg90alarm/entities/device.py +97 -0
  18. pyg90alarm/entities/device_list.py +156 -0
  19. pyg90alarm/entities/sensor.py +891 -0
  20. pyg90alarm/entities/sensor_list.py +183 -0
  21. pyg90alarm/exceptions.py +63 -0
  22. pyg90alarm/local/__init__.py +0 -0
  23. pyg90alarm/local/base_cmd.py +293 -0
  24. pyg90alarm/local/config.py +157 -0
  25. pyg90alarm/local/discovery.py +103 -0
  26. pyg90alarm/local/history.py +272 -0
  27. pyg90alarm/local/host_info.py +89 -0
  28. pyg90alarm/local/host_status.py +52 -0
  29. pyg90alarm/local/notifications.py +117 -0
  30. pyg90alarm/local/paginated_cmd.py +132 -0
  31. pyg90alarm/local/paginated_result.py +135 -0
  32. pyg90alarm/local/targeted_discovery.py +162 -0
  33. pyg90alarm/local/user_data_crc.py +46 -0
  34. pyg90alarm/notifications/__init__.py +0 -0
  35. pyg90alarm/notifications/base.py +481 -0
  36. pyg90alarm/notifications/protocol.py +127 -0
  37. pyg90alarm/py.typed +0 -0
  38. pyg90alarm-2.3.0.dist-info/METADATA +277 -0
  39. pyg90alarm-2.3.0.dist-info/RECORD +42 -0
  40. pyg90alarm-2.3.0.dist-info/WHEEL +5 -0
  41. pyg90alarm-2.3.0.dist-info/licenses/LICENSE +21 -0
  42. pyg90alarm-2.3.0.dist-info/top_level.txt +1 -0
pyg90alarm/const.py ADDED
@@ -0,0 +1,273 @@
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
+ Definies different constants for G90 alarm panel.
23
+ """
24
+ from __future__ import annotations
25
+ from enum import IntEnum
26
+ from typing import Optional
27
+
28
+ REMOTE_PORT = 12368
29
+ REMOTE_TARGETED_DISCOVERY_PORT = 12900
30
+ LOCAL_TARGETED_DISCOVERY_PORT = 12901
31
+ LOCAL_NOTIFICATIONS_HOST = '0.0.0.0'
32
+ LOCAL_NOTIFICATIONS_PORT = 12901
33
+ CLOUD_NOTIFICATIONS_HOST = '0.0.0.0'
34
+ CLOUD_NOTIFICATIONS_PORT = 5678
35
+ REMOTE_CLOUD_HOST = '47.88.7.61'
36
+ REMOTE_CLOUD_PORT = 5678
37
+ DEVICE_REGISTRATION_TIMEOUT = 30
38
+ ROOM_ID = 0
39
+
40
+ CMD_PAGE_SIZE = 10
41
+
42
+
43
+ class G90Commands(IntEnum):
44
+ """
45
+ Defines the alarm panel commands and their codes.
46
+
47
+ The list consists of the entities known so far, and does not pretend to be
48
+ comprehensive or complete.
49
+ """
50
+
51
+ def __new__(cls, value: int, doc: Optional[str] = None) -> G90Commands:
52
+ """
53
+ Allows to set the docstring along with the value to enum entry.
54
+ """
55
+ obj = int.__new__(cls, value)
56
+ obj._value_ = value
57
+ obj.__doc__ = doc
58
+ return obj
59
+
60
+ NONE = (0, """
61
+ Pseudo command, to be used for proper typing with subclasses of
62
+ `G90BaseCommand` invoking its constructor but implementing special
63
+ processing
64
+ """)
65
+
66
+ # Host status
67
+ GETHOSTSTATUS = (100, 'Get host status')
68
+ SETHOSTSTATUS = (101, 'Set host status')
69
+ # Host info
70
+ GETHOSTINFO = 206
71
+ # History
72
+ GETHISTORY = 200
73
+ # Sensors
74
+ GETSENSORLIST = (102, """
75
+ Get list of sensors
76
+
77
+ .. note:: Paginated command, see :py:class:`.G90PaginatedResult`
78
+ """)
79
+ SETSINGLESENSOR = 103
80
+ DELSENSOR = 131
81
+ ADDSENSOR = 156
82
+ LEARNSENSOR = 157
83
+ CANCELLEARNSENSOR = 163
84
+ DELALLSENSORS = 202
85
+ # Switches (relays)
86
+ ADDDEVICE = 134
87
+ REGDEVICE = 135
88
+ DELDEVICE = 136
89
+ CONTROLDEVICE = 137
90
+ GETDEVICELIST = (138, """
91
+ Get list of devices (switches)
92
+
93
+ .. note:: Paginated command, see :py:class:`.G90PaginatedResult`
94
+ """)
95
+ GETSINGLEDEVICE = 139
96
+ SETSINGLEDEVICE = 140
97
+ SENDREGDEVICERESULT = 162
98
+ DELALLDEVICES = 203
99
+ # Host config
100
+ GETHOSTCONFIG = 106
101
+ SETHOSTCONFIG = 107
102
+ SETALMPHONE = 108
103
+ SETAUTOARM = 109
104
+ # Wireless sirens
105
+ GETSIREN = 110
106
+ SETSIREN = 111
107
+ # Alarm phones, notifications
108
+ GETALMPHONE = 114
109
+ GETAUTOARM = 115
110
+ SETNOTICEFLAG = 116
111
+ GETNOTICEFLAG = 117
112
+ # Factory reset
113
+ SETFACTORY = 118
114
+ GETALARM = 119
115
+ # Rooms
116
+ SETROOMINFO = 141
117
+ GETROOMINFO = 142
118
+ ADDROOM = 158
119
+ DELROOM = 159
120
+ # Scenes
121
+ ADDSCENE = 143
122
+ DELSCENE = 144
123
+ CTLSCENE = 145
124
+ GETSCENELIST = (146, """
125
+ Get list of scenes
126
+
127
+ .. note:: Paginated command, see :py:class:`.G90PaginatedResult`
128
+ """)
129
+ GETSINGLESCENE = 147
130
+ SETSINGLESCENE = 148
131
+ GETROOMANDSCENE = 149
132
+ DELALLSCENES = 204
133
+ # IFTTT (scenarios)
134
+ ADDIFTTT = 150
135
+ DELIFTTT = 151
136
+ GETIFTTTLIST = (152, """
137
+ Get list of if-then-else scenarios
138
+
139
+ .. note:: Paginated command, see :py:class:`.G90PaginatedResult`
140
+ """)
141
+ GETSINGLEIFTTT = 153
142
+ SETSINGLEIFTTT = 154
143
+ IFTTTREQTIMERID = 164
144
+ DELALLIFTTT = 205
145
+ # Data CRC
146
+ GETUSERDATACRC = 160
147
+ # Fingerprint scanners
148
+ GETFPLOCKLIST = (165, """
149
+ Get list of fingerprint scanners
150
+
151
+ .. note:: Paginated command, see :py:class:`.G90PaginatedResult`
152
+ """)
153
+ SETFPLOCKNAME = 166
154
+ GETFPLOCKUSERNAME = 167
155
+ SETFPLOCKUSERNAME = 168
156
+ DELALLLOCK = 223
157
+ # Miscellaneous
158
+ GETAPINFO = 212
159
+ PINGBYGPRS = 218
160
+ PING = 219
161
+
162
+
163
+ class G90MessageTypes(IntEnum):
164
+ """
165
+ Defines message types (codes) from messages coming from the alarm panel.
166
+ """
167
+ NOTIFICATION = 170
168
+ ALERT = 208
169
+
170
+
171
+ class G90NotificationTypes(IntEnum):
172
+ """
173
+ Defines types of notifications sent by the alarm panel.
174
+ """
175
+ ARM_DISARM = 1
176
+ SENSOR_CHANGE = 4
177
+ SENSOR_ACTIVITY = 5
178
+ DOOR_OPEN_WHEN_ARMING = 6
179
+ FIRMWARE_UPDATING = 8
180
+
181
+
182
+ class G90ArmDisarmTypes(IntEnum):
183
+ """
184
+ Defines arm/disarm states of the device, applicable both for setting device
185
+ state and one the device sends in notification messages.
186
+ """
187
+ ARM_AWAY = 1
188
+ ARM_HOME = 2
189
+ DISARM = 3
190
+ ALARMED = 4
191
+
192
+
193
+ class G90AlertTypes(IntEnum):
194
+ """
195
+ Defines types of alerts sent by the alarm panel.
196
+ """
197
+ HOST_SOS = 1
198
+ STATE_CHANGE = 2
199
+ ALARM = 3
200
+ SENSOR_ACTIVITY = 4
201
+ # Retained for compatibility, deprecated
202
+ DOOR_OPEN_CLOSE = 4
203
+
204
+
205
+ class G90AlertSources(IntEnum):
206
+ """
207
+ Defines possible sources of the alert sent by the panel.
208
+ """
209
+ DEVICE = 0
210
+ SENSOR = 1
211
+ TAMPER = 3
212
+ REMOTE = 10
213
+ RFID = 11
214
+ DOORBELL = 12
215
+ FINGERPRINT = 15
216
+
217
+
218
+ class G90AlertStates(IntEnum):
219
+ """
220
+ Defines possible states of the alert sent by the panel.
221
+ """
222
+ DOOR_CLOSE = 0
223
+ DOOR_OPEN = 1
224
+ SOS = 2
225
+ TAMPER = 3
226
+ LOW_BATTERY = 4
227
+ ALARM = 254
228
+
229
+
230
+ class G90AlertStateChangeTypes(IntEnum):
231
+ """
232
+ Defines types of alert for device state changes.
233
+ """
234
+ AC_POWER_FAILURE = 1
235
+ AC_POWER_RECOVER = 2
236
+ DISARM = 3
237
+ ARM_AWAY = 4
238
+ ARM_HOME = 5
239
+ LOW_BATTERY = 6
240
+ WIFI_CONNECTED = 7
241
+ WIFI_DISCONNECTED = 8
242
+
243
+
244
+ class G90HistoryStates(IntEnum):
245
+ """
246
+ Defines possible states for history entities.
247
+ """
248
+ DOOR_CLOSE = 1
249
+ DOOR_OPEN = 2
250
+ TAMPER = 3
251
+ ALARM = 4
252
+ AC_POWER_FAILURE = 5
253
+ AC_POWER_RECOVER = 6
254
+ DISARM = 7
255
+ ARM_AWAY = 8
256
+ ARM_HOME = 9
257
+ LOW_BATTERY = 10
258
+ WIFI_CONNECTED = 11
259
+ WIFI_DISCONNECTED = 12
260
+ REMOTE_BUTTON_ARM_AWAY = 13
261
+ REMOTE_BUTTON_ARM_HOME = 14
262
+ REMOTE_BUTTON_DISARM = 15
263
+ REMOTE_BUTTON_SOS = 16
264
+
265
+
266
+ class G90RemoteButtonStates(IntEnum):
267
+ """
268
+ Defines possible states for remote control buttons.
269
+ """
270
+ ARM_AWAY = 0
271
+ ARM_HOME = 1
272
+ DISARM = 2
273
+ SOS = 3
@@ -0,0 +1,3 @@
1
+ """
2
+ Contains various definitions for G90 devices (sensors etc.)
3
+ """
@@ -0,0 +1,247 @@
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
+ Base entities for peripheral definitions.
22
+ """
23
+ from __future__ import annotations
24
+ from dataclasses import dataclass
25
+ from itertools import groupby
26
+ from enum import IntEnum
27
+ from abc import ABC, abstractmethod
28
+ import logging
29
+ from ..exceptions import G90PeripheralDefinitionNotFound, G90Error
30
+
31
+ _LOGGER = logging.getLogger(__name__)
32
+
33
+
34
+ class G90PeripheralProtocols(IntEnum):
35
+ """
36
+ Protocol types for the peripherals.
37
+ """
38
+ RF_1527 = 0
39
+ RF_2262 = 1
40
+ RF_PRIVATE = 2
41
+ RF_SLIDER = 3
42
+ CORD = 5
43
+ WIFI = 4
44
+ USB = 6
45
+
46
+
47
+ class G90PeripheralTypes(IntEnum):
48
+ """
49
+ Peripheral types.
50
+ """
51
+ DOOR = 1
52
+ GLASS = 2
53
+ GAS = 3
54
+ SMOKE = 4
55
+ SOS = 5
56
+ VIB = 6
57
+ WATER = 7
58
+ INFRARED = 8
59
+ IN_BEAM = 9
60
+ REMOTE = 10
61
+ RFID = 11
62
+ DOORBELL = 12
63
+ BUTTONID = 13
64
+ WATCH = 14
65
+ FINGER_LOCK = 15
66
+ SUBHOST = 16
67
+ REMOTE_2_4G = 17
68
+ GAS_VALVE = 18
69
+ CORD_SENSOR = 126
70
+ SOCKET = 128
71
+ SIREN = 129
72
+ CURTAIN = 130
73
+ SLIDINGWIN = 131
74
+ AIRCON = 133
75
+ TV = 135
76
+ TV_BOX = 136
77
+ SMART_SWITCH = 137
78
+ NIGHTLIGHT = 138
79
+ SOCKET_2_4G = 140
80
+ SIREN_2_4G = 141
81
+ SWITCH_2_4G = 142
82
+ TOUCH_SWITCH_2_4G = 143
83
+ CURTAIN_2_4G = 144
84
+ IR_2_4G = 145
85
+ CORD_DEV = 254
86
+ UNKNOWN = 255
87
+
88
+
89
+ class G90PeripheralMatchModes(IntEnum):
90
+ """
91
+ Defines compare (match) mode for the peripheral.
92
+ """
93
+ ALL = 0
94
+ ONLY20BITS = 1
95
+ ONLY16BITS = 2
96
+
97
+
98
+ class G90PeripheralRwModes(IntEnum):
99
+ """
100
+ Defines read/write mode for the peripheral.
101
+ """
102
+ READ = 0
103
+ WRITE = 1
104
+ READ_WRITE = 2
105
+
106
+
107
+ @dataclass(frozen=True)
108
+ class G90PeripheralDefinition:
109
+ # pylint: disable=too-many-instance-attributes
110
+ """
111
+ Holds peripheral definition data.
112
+ """
113
+ type: G90PeripheralTypes
114
+ subtype: int
115
+ rx: int
116
+ tx: int
117
+ private_data: str
118
+ rw_mode: G90PeripheralRwModes
119
+ match_mode: G90PeripheralMatchModes
120
+ name: str
121
+ protocol: G90PeripheralProtocols
122
+ timeout: int
123
+ baudrate: int
124
+ node_count: int
125
+
126
+ @property
127
+ def reserved_data(self) -> int:
128
+ """
129
+ Peripheral's 'reserved_data' field to be written, combined of match
130
+ and RW mode values bitwise.
131
+ """
132
+ return self.match_mode.value << 4 | self.rw_mode.value
133
+
134
+
135
+ def unique_definitions(
136
+ obj: type[G90PeripheralDefinitionsBase]
137
+ ) -> type[G90PeripheralDefinitionsBase]:
138
+ """
139
+ Decorator to ensure that peripheral definitions are unique by name
140
+ and type/subtype/protocol.
141
+
142
+ :param obj: Class to decorate.
143
+ :return: Decorated class with unique definitions.
144
+ :raises G90Error: If definitions are not unique.
145
+ """
146
+ names = groupby(
147
+ sorted(obj.definitions(), key=lambda x: x.name),
148
+ lambda x: x.name
149
+ )
150
+ type_subtype = groupby(
151
+ sorted(
152
+ obj.definitions(),
153
+ key=lambda x: (x.type, x.subtype, x.protocol)
154
+ ),
155
+ lambda x: (x.type, x.subtype, x.protocol)
156
+ )
157
+ non_unique_names = [
158
+ k for _, group in names if len(k := list(group)) > 1
159
+ ]
160
+ non_unique_types = [
161
+ k for _, group in type_subtype if len(k := list(group)) > 1
162
+ ]
163
+
164
+ msgs = []
165
+ if non_unique_names:
166
+ msgs.append(
167
+ f"{obj}: Peripheral definitions have non-unique names: \n"
168
+ f"{non_unique_names}"
169
+ )
170
+ if non_unique_types:
171
+ msgs.append(
172
+ f"{obj}: Peripheral definitions have non-unique types: \n"
173
+ f"{non_unique_types}"
174
+ )
175
+
176
+ if msgs:
177
+ raise G90Error('.\n'.join(msgs))
178
+
179
+ return obj
180
+
181
+
182
+ class G90PeripheralDefinitionsBase(ABC):
183
+ """
184
+ Base class for peripheral definitions.
185
+ """
186
+ @classmethod
187
+ @abstractmethod
188
+ def definitions(cls) -> list[G90PeripheralDefinition]:
189
+ """
190
+ Get all peripheral definitions.
191
+
192
+ :return: List of peripheral definitions.
193
+ """
194
+
195
+ @classmethod
196
+ def get_by_id(
197
+ cls, id_type: G90PeripheralTypes, id_subtype: int,
198
+ protocol: G90PeripheralProtocols
199
+ ) -> G90PeripheralDefinition:
200
+ """
201
+ Gets peripheral definition by type, subtype and protocol.
202
+
203
+ :param id_type: Peripheral type.
204
+ :param id_subtype: Peripheral subtype.
205
+ :param protocol: Peripheral protocol.
206
+ :raises G90PeripheralDefinitionNotFound: If definition not found.
207
+ """
208
+ for definition in cls.definitions():
209
+ if (
210
+ definition.type == id_type
211
+ and definition.subtype == id_subtype
212
+ and definition.protocol == protocol
213
+ ):
214
+ _LOGGER.debug(
215
+ "Found peripheral definition by"
216
+ " type %d, subtype %d and protocol %d: %s",
217
+ id_type, id_subtype, protocol, definition
218
+ )
219
+ return definition
220
+
221
+ raise G90PeripheralDefinitionNotFound(
222
+ "Peripheral definition not found"
223
+ f" by type={id_type}, subtype={id_subtype}"
224
+ f" and protocol={protocol}",
225
+ )
226
+
227
+ @classmethod
228
+ def get_by_name(
229
+ cls, name: str
230
+ ) -> G90PeripheralDefinition:
231
+ """
232
+ Gets peripheral definition by name.
233
+
234
+ :param name: Peripheral name.
235
+ :raises G90PeripheralDefinitionNotFound: If definition not found.
236
+ """
237
+ for definition in cls.definitions():
238
+ if definition.name == name:
239
+ _LOGGER.debug(
240
+ "Found peripheral definition by name '%s': %s",
241
+ name, definition
242
+ )
243
+ return definition
244
+
245
+ raise G90PeripheralDefinitionNotFound(
246
+ f"Peripheral definition not found by name='{name}'"
247
+ )