pyg90alarm 1.13.0__py3-none-any.whl → 1.15.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 +31 -4
- pyg90alarm/alarm.py +194 -166
- pyg90alarm/base_cmd.py +60 -52
- pyg90alarm/callback.py +41 -51
- pyg90alarm/config.py +10 -6
- pyg90alarm/const.py +9 -2
- pyg90alarm/definitions/sensors.py +12 -11
- pyg90alarm/device_notifications.py +65 -41
- pyg90alarm/discovery.py +31 -22
- pyg90alarm/entities/device.py +6 -7
- pyg90alarm/entities/sensor.py +180 -126
- pyg90alarm/history.py +43 -47
- pyg90alarm/host_info.py +27 -25
- pyg90alarm/host_status.py +25 -12
- pyg90alarm/paginated_cmd.py +46 -33
- pyg90alarm/paginated_result.py +13 -7
- pyg90alarm/py.typed +0 -0
- pyg90alarm/targeted_discovery.py +98 -39
- pyg90alarm/user_data_crc.py +18 -13
- {pyg90alarm-1.13.0.dist-info → pyg90alarm-1.15.0.dist-info}/METADATA +4 -3
- pyg90alarm-1.15.0.dist-info/RECORD +27 -0
- {pyg90alarm-1.13.0.dist-info → pyg90alarm-1.15.0.dist-info}/WHEEL +1 -1
- pyg90alarm-1.13.0.dist-info/RECORD +0 -26
- {pyg90alarm-1.13.0.dist-info → pyg90alarm-1.15.0.dist-info}/LICENSE +0 -0
- {pyg90alarm-1.13.0.dist-info → pyg90alarm-1.15.0.dist-info}/top_level.txt +0 -0
pyg90alarm/alarm.py
CHANGED
|
@@ -48,8 +48,14 @@ G90HostInfo(host_guid='<...>',
|
|
|
48
48
|
wifi_signal_level=100)
|
|
49
49
|
|
|
50
50
|
"""
|
|
51
|
+
from __future__ import annotations
|
|
51
52
|
import asyncio
|
|
53
|
+
from asyncio import Task
|
|
52
54
|
import logging
|
|
55
|
+
from typing import (
|
|
56
|
+
TYPE_CHECKING, Any, List, Optional, AsyncGenerator,
|
|
57
|
+
Callable, Coroutine, Union
|
|
58
|
+
)
|
|
53
59
|
from .const import (
|
|
54
60
|
G90Commands, REMOTE_PORT,
|
|
55
61
|
REMOTE_TARGETED_DISCOVERY_PORT,
|
|
@@ -57,15 +63,17 @@ from .const import (
|
|
|
57
63
|
NOTIFICATIONS_PORT,
|
|
58
64
|
G90ArmDisarmTypes,
|
|
59
65
|
)
|
|
60
|
-
from .base_cmd import G90BaseCommand
|
|
61
|
-
from .paginated_result import G90PaginatedResult
|
|
66
|
+
from .base_cmd import (G90BaseCommand, G90BaseCommandData)
|
|
67
|
+
from .paginated_result import G90PaginatedResult, G90PaginatedResponse
|
|
62
68
|
from .entities.sensor import (G90Sensor, G90SensorTypes)
|
|
63
69
|
from .entities.device import G90Device
|
|
64
70
|
from .device_notifications import (
|
|
65
71
|
G90DeviceNotifications,
|
|
66
72
|
)
|
|
67
|
-
from .discovery import G90Discovery
|
|
68
|
-
from .targeted_discovery import
|
|
73
|
+
from .discovery import G90Discovery, G90DiscoveredDevice
|
|
74
|
+
from .targeted_discovery import (
|
|
75
|
+
G90TargetedDiscovery, G90DiscoveredDeviceTargeted,
|
|
76
|
+
)
|
|
69
77
|
from .host_info import G90HostInfo
|
|
70
78
|
from .host_status import G90HostStatus
|
|
71
79
|
from .config import (G90AlertConfig, G90AlertConfigFlags)
|
|
@@ -76,6 +84,39 @@ from .exceptions import G90Error, G90TimeoutError
|
|
|
76
84
|
|
|
77
85
|
_LOGGER = logging.getLogger(__name__)
|
|
78
86
|
|
|
87
|
+
if TYPE_CHECKING:
|
|
88
|
+
# Type alias for the callback functions available to the user, should be
|
|
89
|
+
# compatible with `G90Callback.Callback` type, since `G90Callback.invoke`
|
|
90
|
+
# is used to invoke them
|
|
91
|
+
AlarmCallback = Union[
|
|
92
|
+
Callable[[int, str, Any], None],
|
|
93
|
+
Callable[[int, str, Any], Coroutine[None, None, None]]
|
|
94
|
+
]
|
|
95
|
+
DoorOpenCloseCallback = Union[
|
|
96
|
+
Callable[[int, str, bool], None],
|
|
97
|
+
Callable[[int, str, bool], Coroutine[None, None, None]]
|
|
98
|
+
]
|
|
99
|
+
SensorCallback = Union[
|
|
100
|
+
Callable[[int, str, bool], None],
|
|
101
|
+
Callable[[int, str, bool], Coroutine[None, None, None]]
|
|
102
|
+
]
|
|
103
|
+
SensorStateCallback = Union[
|
|
104
|
+
Callable[[bool], None],
|
|
105
|
+
Callable[[bool], Coroutine[None, None, None]]
|
|
106
|
+
]
|
|
107
|
+
LowBatteryCallback = Union[
|
|
108
|
+
Callable[[int, str], None],
|
|
109
|
+
Callable[[int, str], Coroutine[None, None, None]]
|
|
110
|
+
]
|
|
111
|
+
SensorLowBatteryCallback = Union[
|
|
112
|
+
Callable[[bool], None],
|
|
113
|
+
Callable[[bool], Coroutine[None, None, None]]
|
|
114
|
+
]
|
|
115
|
+
ArmDisarmCallback = Union[
|
|
116
|
+
Callable[[G90ArmDisarmTypes], None],
|
|
117
|
+
Callable[[G90ArmDisarmTypes], Coroutine[None, None, None]]
|
|
118
|
+
]
|
|
119
|
+
|
|
79
120
|
|
|
80
121
|
# pylint: disable=too-many-public-methods
|
|
81
122
|
class G90Alarm(G90DeviceNotifications):
|
|
@@ -83,121 +124,117 @@ class G90Alarm(G90DeviceNotifications):
|
|
|
83
124
|
"""
|
|
84
125
|
Allows to interact with G90 alarm panel.
|
|
85
126
|
|
|
86
|
-
:param
|
|
127
|
+
:param host: Hostname or IP address of the alarm panel. Since the
|
|
87
128
|
protocol is UDP-based it is ok to use broadcast or directed broadcast
|
|
88
129
|
addresses, such as `255.255.255.255` or `10.10.10.255` (the latter assumes
|
|
89
130
|
the device is on `10.10.10.0/24` network)
|
|
90
131
|
:param port: The UDP port of the device where it listens for the
|
|
91
132
|
protocol commands on WiFi interface, currently the devices don't allow it
|
|
92
133
|
to be customized
|
|
93
|
-
:type port: int, optional
|
|
94
134
|
:param reset_occupancy_interval: The interval upon that the sensors are
|
|
95
135
|
simulated to go into inactive state.
|
|
96
|
-
:type reset_occupancy_interval: int, optional
|
|
97
136
|
"""
|
|
98
137
|
# pylint: disable=too-many-instance-attributes,too-many-arguments
|
|
99
|
-
def __init__(self, host, port=REMOTE_PORT,
|
|
100
|
-
reset_occupancy_interval=3,
|
|
101
|
-
notifications_host='0.0.0.0',
|
|
102
|
-
notifications_port=NOTIFICATIONS_PORT):
|
|
138
|
+
def __init__(self, host: str, port: int = REMOTE_PORT,
|
|
139
|
+
reset_occupancy_interval: float = 3.0,
|
|
140
|
+
notifications_host: str = '0.0.0.0',
|
|
141
|
+
notifications_port: int = NOTIFICATIONS_PORT):
|
|
103
142
|
super().__init__(host=notifications_host, port=notifications_port)
|
|
104
143
|
self._host = host
|
|
105
144
|
self._port = port
|
|
106
|
-
self._sensors = []
|
|
107
|
-
self._devices = []
|
|
108
|
-
self._notifications = None
|
|
109
|
-
self._sensor_cb = None
|
|
110
|
-
self._armdisarm_cb = None
|
|
111
|
-
self._door_open_close_cb = None
|
|
112
|
-
self._alarm_cb = None
|
|
113
|
-
self._low_battery_cb = None
|
|
145
|
+
self._sensors: List[G90Sensor] = []
|
|
146
|
+
self._devices: List[G90Device] = []
|
|
147
|
+
self._notifications: Optional[G90DeviceNotifications] = None
|
|
148
|
+
self._sensor_cb: Optional[SensorCallback] = None
|
|
149
|
+
self._armdisarm_cb: Optional[ArmDisarmCallback] = None
|
|
150
|
+
self._door_open_close_cb: Optional[DoorOpenCloseCallback] = None
|
|
151
|
+
self._alarm_cb: Optional[AlarmCallback] = None
|
|
152
|
+
self._low_battery_cb: Optional[LowBatteryCallback] = None
|
|
114
153
|
self._reset_occupancy_interval = reset_occupancy_interval
|
|
115
|
-
self._alert_config = None
|
|
154
|
+
self._alert_config: Optional[G90AlertConfigFlags] = None
|
|
116
155
|
self._sms_alert_when_armed = False
|
|
117
|
-
self._alert_simulation_task = None
|
|
156
|
+
self._alert_simulation_task: Optional[Task[Any]] = None
|
|
118
157
|
self._alert_simulation_start_listener_back = False
|
|
119
158
|
|
|
120
|
-
async def command(
|
|
159
|
+
async def command(
|
|
160
|
+
self, code: G90Commands, data: Optional[G90BaseCommandData] = None
|
|
161
|
+
) -> G90BaseCommandData:
|
|
121
162
|
"""
|
|
122
163
|
Invokes a command against the alarm panel.
|
|
123
164
|
|
|
124
165
|
:param code: Command code
|
|
125
|
-
:type code: :class:`.G90Commands`
|
|
126
166
|
:param data: Command data
|
|
127
|
-
:
|
|
128
|
-
:return: :attr:`.G90BaseCommand.result` that contains the result of
|
|
129
|
-
command invocation
|
|
167
|
+
:return: The result of command invocation
|
|
130
168
|
"""
|
|
131
|
-
cmd = await G90BaseCommand(
|
|
169
|
+
cmd: G90BaseCommand = await G90BaseCommand(
|
|
132
170
|
self._host, self._port, code, data).process()
|
|
133
171
|
return cmd.result
|
|
134
172
|
|
|
135
|
-
def paginated_result(
|
|
173
|
+
def paginated_result(
|
|
174
|
+
self, code: G90Commands, start: int = 1, end: Optional[int] = None
|
|
175
|
+
) -> AsyncGenerator[G90PaginatedResponse, None]:
|
|
136
176
|
"""
|
|
137
177
|
Returns asynchronous generator for a paginated command, that is -
|
|
138
178
|
command operating on a range of records.
|
|
139
179
|
|
|
140
180
|
:param code: Command code
|
|
141
|
-
:
|
|
142
|
-
:param
|
|
143
|
-
:
|
|
144
|
-
:return: :class:`.G90PaginatedResult` being asynchronous generator
|
|
145
|
-
over the result of command invocation. Each access to the generator
|
|
146
|
-
yields :class:`.G90PaginatedResponse` instance
|
|
181
|
+
:param start: Starting record position (one-based)
|
|
182
|
+
:param end: Ending record position (one-based)
|
|
183
|
+
:return: Asynchronous generator over the result of command invocation.
|
|
147
184
|
"""
|
|
148
185
|
return G90PaginatedResult(
|
|
149
186
|
self._host, self._port, code, start, end
|
|
150
187
|
).process()
|
|
151
188
|
|
|
152
189
|
@classmethod
|
|
153
|
-
async def discover(cls):
|
|
190
|
+
async def discover(cls) -> List[G90DiscoveredDevice]:
|
|
154
191
|
"""
|
|
155
192
|
Initiates discovering devices available in the same network segment, by
|
|
156
193
|
using global broadcast address as the destination.
|
|
157
194
|
|
|
158
195
|
:return: List of discovered devices
|
|
159
|
-
:rtype: list[{'guid', 'host', 'port'}]
|
|
160
196
|
"""
|
|
161
|
-
|
|
197
|
+
cmd: G90Discovery = await G90Discovery(
|
|
162
198
|
port=REMOTE_PORT,
|
|
163
199
|
host='255.255.255.255'
|
|
164
200
|
).process()
|
|
201
|
+
return cmd.devices
|
|
165
202
|
|
|
166
203
|
@classmethod
|
|
167
|
-
async def targeted_discover(
|
|
204
|
+
async def targeted_discover(
|
|
205
|
+
cls, device_id: str
|
|
206
|
+
) -> List[G90DiscoveredDeviceTargeted]:
|
|
168
207
|
"""
|
|
169
208
|
Initiates discovering devices available in the same network segment
|
|
170
209
|
using targeted protocol, that is - specifying target device GUID in the
|
|
171
210
|
request, so only the specific device should respond to the query.
|
|
172
211
|
|
|
173
212
|
:param device_id: GUID of the target device to discover
|
|
174
|
-
:type device_id: str
|
|
175
213
|
:return: List of discovered devices
|
|
176
|
-
:rtype: list[{'guid', 'host', 'port'}]
|
|
177
214
|
"""
|
|
178
|
-
|
|
215
|
+
cmd = await G90TargetedDiscovery(
|
|
179
216
|
device_id=device_id,
|
|
180
217
|
port=REMOTE_TARGETED_DISCOVERY_PORT,
|
|
181
218
|
local_port=LOCAL_TARGETED_DISCOVERY_PORT,
|
|
182
219
|
host='255.255.255.255'
|
|
183
220
|
).process()
|
|
221
|
+
return cmd.devices
|
|
184
222
|
|
|
185
223
|
@property
|
|
186
|
-
async def sensors(self):
|
|
224
|
+
async def sensors(self) -> List[G90Sensor]:
|
|
187
225
|
"""
|
|
188
226
|
Property over new :meth:`.get_sensors` method, retained for
|
|
189
227
|
compatibility.
|
|
190
228
|
"""
|
|
191
229
|
return await self.get_sensors()
|
|
192
230
|
|
|
193
|
-
async def get_sensors(self):
|
|
231
|
+
async def get_sensors(self) -> List[G90Sensor]:
|
|
194
232
|
"""
|
|
195
233
|
Provides list of sensors configured in the device. Please note the list
|
|
196
234
|
is cached upon first call, so you need to re-instantiate the class to
|
|
197
235
|
reflect any updates there.
|
|
198
236
|
|
|
199
237
|
:return: List of sensors
|
|
200
|
-
:rtype: list(:class:`.G90Sensor`)
|
|
201
238
|
"""
|
|
202
239
|
if not self._sensors:
|
|
203
240
|
sensors = self.paginated_result(
|
|
@@ -214,14 +251,13 @@ class G90Alarm(G90DeviceNotifications):
|
|
|
214
251
|
|
|
215
252
|
return self._sensors
|
|
216
253
|
|
|
217
|
-
async def find_sensor(self, idx, name):
|
|
254
|
+
async def find_sensor(self, idx: int, name: str) -> Optional[G90Sensor]:
|
|
218
255
|
"""
|
|
219
256
|
Finds sensor by index and name.
|
|
220
257
|
|
|
221
|
-
:param
|
|
222
|
-
:param
|
|
258
|
+
:param idx: Sensor index
|
|
259
|
+
:param name: Sensor name
|
|
223
260
|
:return: Sensor instance
|
|
224
|
-
:rtype :class:`.G90Sensor`|None
|
|
225
261
|
"""
|
|
226
262
|
sensors = await self.get_sensors()
|
|
227
263
|
|
|
@@ -241,14 +277,14 @@ class G90Alarm(G90DeviceNotifications):
|
|
|
241
277
|
return None
|
|
242
278
|
|
|
243
279
|
@property
|
|
244
|
-
async def devices(self):
|
|
280
|
+
async def devices(self) -> List[G90Device]:
|
|
245
281
|
"""
|
|
246
282
|
Property over new :meth:`.get_devices` method, retained for
|
|
247
283
|
compatibility.
|
|
248
284
|
"""
|
|
249
285
|
return await self.get_devices()
|
|
250
286
|
|
|
251
|
-
async def get_devices(self):
|
|
287
|
+
async def get_devices(self) -> List[G90Device]:
|
|
252
288
|
"""
|
|
253
289
|
Provides list of devices (switches) configured in the device. Please
|
|
254
290
|
note the list is cached upon first call, so you need to re-instantiate
|
|
@@ -257,7 +293,6 @@ class G90Alarm(G90DeviceNotifications):
|
|
|
257
293
|
resulting entries.
|
|
258
294
|
|
|
259
295
|
:return: List of devices
|
|
260
|
-
:rtype: list(:class:`.G90Device`)
|
|
261
296
|
"""
|
|
262
297
|
if not self._devices:
|
|
263
298
|
devices = self.paginated_result(
|
|
@@ -283,82 +318,74 @@ class G90Alarm(G90DeviceNotifications):
|
|
|
283
318
|
return self._devices
|
|
284
319
|
|
|
285
320
|
@property
|
|
286
|
-
async def host_info(self):
|
|
321
|
+
async def host_info(self) -> G90HostInfo:
|
|
287
322
|
"""
|
|
288
323
|
Property over new :meth:`.get_host_info` method, retained for
|
|
289
324
|
compatibility.
|
|
290
325
|
"""
|
|
291
326
|
return await self.get_host_info()
|
|
292
327
|
|
|
293
|
-
async def get_host_info(self):
|
|
328
|
+
async def get_host_info(self) -> G90HostInfo:
|
|
294
329
|
"""
|
|
295
330
|
Provides the device information (for example hardware versions, signal
|
|
296
331
|
levels etc.).
|
|
297
332
|
|
|
298
333
|
:return: Device information
|
|
299
|
-
:rtype: Instance of :class:`.G90HostInfo`
|
|
300
334
|
"""
|
|
301
335
|
res = await self.command(G90Commands.GETHOSTINFO)
|
|
302
336
|
return G90HostInfo(*res)
|
|
303
337
|
|
|
304
338
|
@property
|
|
305
|
-
async def host_status(self):
|
|
339
|
+
async def host_status(self) -> G90HostStatus:
|
|
306
340
|
"""
|
|
307
341
|
Property over new :meth:`.get_host_status` method, retained for
|
|
308
342
|
compatibility.
|
|
309
343
|
"""
|
|
310
344
|
return await self.get_host_status()
|
|
311
345
|
|
|
312
|
-
async def get_host_status(self):
|
|
346
|
+
async def get_host_status(self) -> G90HostStatus:
|
|
313
347
|
"""
|
|
314
348
|
Provides the device status (for example, armed or disarmed, configured
|
|
315
349
|
phone number, product name etc.).
|
|
316
350
|
|
|
317
351
|
:return: Device information
|
|
318
|
-
:rtype: Instance of :class:`.G90HostStatus`
|
|
319
352
|
|
|
320
353
|
"""
|
|
321
354
|
res = await self.command(G90Commands.GETHOSTSTATUS)
|
|
322
355
|
return G90HostStatus(*res)
|
|
323
356
|
|
|
324
357
|
@property
|
|
325
|
-
async def alert_config(self):
|
|
358
|
+
async def alert_config(self) -> G90AlertConfigFlags:
|
|
326
359
|
"""
|
|
327
360
|
Property over new :meth:`.get_alert_config` method, retained for
|
|
328
361
|
compatibility.
|
|
329
362
|
"""
|
|
330
363
|
return await self.get_alert_config()
|
|
331
364
|
|
|
332
|
-
async def get_alert_config(self):
|
|
365
|
+
async def get_alert_config(self) -> G90AlertConfigFlags:
|
|
333
366
|
"""
|
|
334
367
|
Retrieves the alert configuration flags from the device. Please note
|
|
335
368
|
the configuration is cached upon first call, so you need to
|
|
336
369
|
re-instantiate the class to reflect any updates there.
|
|
337
370
|
|
|
338
|
-
:return:
|
|
339
|
-
alerts configured
|
|
371
|
+
:return: The alerts configured
|
|
340
372
|
"""
|
|
341
373
|
if not self._alert_config:
|
|
342
374
|
self._alert_config = await self._alert_config_uncached()
|
|
343
375
|
return self._alert_config
|
|
344
376
|
|
|
345
|
-
async def _alert_config_uncached(self):
|
|
377
|
+
async def _alert_config_uncached(self) -> G90AlertConfigFlags:
|
|
346
378
|
"""
|
|
347
379
|
Retrieves the alert configuration flags directly from the device.
|
|
348
380
|
|
|
349
|
-
:return:
|
|
350
|
-
alerts configured
|
|
381
|
+
:return: The alerts configured
|
|
351
382
|
"""
|
|
352
383
|
res = await self.command(G90Commands.GETNOTICEFLAG)
|
|
353
384
|
return G90AlertConfig(*res).flags
|
|
354
385
|
|
|
355
|
-
async def set_alert_config(self,
|
|
386
|
+
async def set_alert_config(self, flags: G90AlertConfigFlags) -> None:
|
|
356
387
|
"""
|
|
357
|
-
|
|
358
|
-
`async_as_sync` decorator, although it might have implications with the
|
|
359
|
-
setter not executed if the program terminates earlier. Hence, for the
|
|
360
|
-
sake of better predictability this is implemented as regular
|
|
361
|
-
(non-property) method
|
|
388
|
+
Sets the alert configuration flags on the device.
|
|
362
389
|
"""
|
|
363
390
|
# Use uncached method retrieving the alert configuration, to ensure the
|
|
364
391
|
# actual value retrieved from the device
|
|
@@ -367,45 +394,43 @@ class G90Alarm(G90DeviceNotifications):
|
|
|
367
394
|
_LOGGER.warning(
|
|
368
395
|
'Alert configuration changed externally,'
|
|
369
396
|
' overwriting (read "%s", will be set to "%s")',
|
|
370
|
-
str(alert_config), str(
|
|
397
|
+
str(alert_config), str(flags)
|
|
371
398
|
)
|
|
372
|
-
await self.command(G90Commands.SETNOTICEFLAG, [value])
|
|
399
|
+
await self.command(G90Commands.SETNOTICEFLAG, [flags.value])
|
|
373
400
|
# Update the alert configuration stored
|
|
374
|
-
self._alert_config =
|
|
401
|
+
self._alert_config = flags
|
|
375
402
|
|
|
376
403
|
@property
|
|
377
|
-
async def user_data_crc(self):
|
|
404
|
+
async def user_data_crc(self) -> G90UserDataCRC:
|
|
378
405
|
"""
|
|
379
406
|
Property over new :meth:`.get_user_data_crc` method, retained for
|
|
380
407
|
compatibility.
|
|
381
408
|
"""
|
|
382
409
|
return await self.get_user_data_crc()
|
|
383
410
|
|
|
384
|
-
async def get_user_data_crc(self):
|
|
411
|
+
async def get_user_data_crc(self) -> G90UserDataCRC:
|
|
385
412
|
"""
|
|
386
413
|
Retieves checksums (CRC) for different on-device databases (history,
|
|
387
414
|
sensors etc.). Might be used to detect if there is a change in a
|
|
388
415
|
particular database.
|
|
389
416
|
|
|
390
|
-
.. note:: Note that due to a bug in the firmware CRC for
|
|
417
|
+
.. note:: Note that due to a bug in the firmware CRC for sensors and
|
|
391
418
|
device databases change on each call even if there were no changes
|
|
392
419
|
|
|
393
|
-
:return:
|
|
394
|
-
different databases
|
|
420
|
+
:return: Checksums for different databases
|
|
395
421
|
"""
|
|
396
422
|
res = await self.command(G90Commands.GETUSERDATACRC)
|
|
397
423
|
return G90UserDataCRC(*res)
|
|
398
424
|
|
|
399
|
-
async def history(
|
|
425
|
+
async def history(
|
|
426
|
+
self, start: int = 1, count: int = 1
|
|
427
|
+
) -> List[G90History]:
|
|
400
428
|
"""
|
|
401
429
|
Retrieves event history from the device.
|
|
402
430
|
|
|
403
431
|
:param start: Starting record number (one-based)
|
|
404
|
-
:type start: int
|
|
405
432
|
:param count: Number of records to retrieve
|
|
406
|
-
:type count: int
|
|
407
433
|
:return: List of history entries
|
|
408
|
-
:rtype: list[:class:`.G90History`]
|
|
409
434
|
"""
|
|
410
435
|
res = self.paginated_result(G90Commands.GETHISTORY,
|
|
411
436
|
start, count)
|
|
@@ -417,18 +442,23 @@ class G90Alarm(G90DeviceNotifications):
|
|
|
417
442
|
key=lambda x: x.datetime, reverse=True
|
|
418
443
|
)
|
|
419
444
|
|
|
420
|
-
async def on_sensor_activity(
|
|
445
|
+
async def on_sensor_activity(
|
|
446
|
+
self, idx: int, name: str, occupancy: bool = True
|
|
447
|
+
) -> None:
|
|
421
448
|
"""
|
|
422
|
-
|
|
423
|
-
alerts, since the logic for both is same and could be reused.
|
|
424
|
-
|
|
449
|
+
Invoked both for sensor notifications and door open/close
|
|
450
|
+
alerts, since the logic for both is same and could be reused.
|
|
451
|
+
Fires corresponding callback if set by the user with
|
|
452
|
+
:attr:`.sensor_callback`.
|
|
453
|
+
|
|
454
|
+
Please note the method is for internal use by the class.
|
|
425
455
|
|
|
426
|
-
:param
|
|
456
|
+
:param idx: The index of the sensor the callback is invoked for.
|
|
427
457
|
Please note the index is a property of sensor, not the direct index of
|
|
428
458
|
:attr:`sensors` array
|
|
429
|
-
:param
|
|
459
|
+
:param name: The name of the sensor, along with the `idx` parameter
|
|
430
460
|
it is used to look the sensor up from the :attr:`sensors` list
|
|
431
|
-
:param
|
|
461
|
+
:param occupancy: The flag indicating the target sensor state
|
|
432
462
|
(=occupancy), will always be `True` for callbacks invoked from alarm
|
|
433
463
|
panel notifications, and reflects actual sensor state for device
|
|
434
464
|
alerts (only for `door` type sensors, if door open/close alerts are
|
|
@@ -437,16 +467,19 @@ class G90Alarm(G90DeviceNotifications):
|
|
|
437
467
|
_LOGGER.debug('on_sensor_activity: %s %s %s', idx, name, occupancy)
|
|
438
468
|
sensor = await self.find_sensor(idx, name)
|
|
439
469
|
if sensor:
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
470
|
+
# Reset the low battery flag since the sensor reports activity,
|
|
471
|
+
# implying it has sufficient battery power
|
|
472
|
+
# pylint: disable=protected-access
|
|
473
|
+
sensor._set_low_battery(False)
|
|
474
|
+
# Set the sensor occupancy
|
|
475
|
+
# pylint: disable=protected-access
|
|
476
|
+
sensor._set_occupancy(occupancy)
|
|
443
477
|
|
|
444
478
|
# Emulate turning off the occupancy - most of sensors will not
|
|
445
479
|
# notify the device of that, nor the device would emit such
|
|
446
480
|
# notification itself
|
|
447
|
-
def reset_sensor_occupancy(sensor):
|
|
448
|
-
|
|
449
|
-
sensor.occupancy = False
|
|
481
|
+
def reset_sensor_occupancy(sensor: G90Sensor) -> None:
|
|
482
|
+
sensor._set_occupancy(False)
|
|
450
483
|
G90Callback.invoke(sensor.state_callback, sensor.occupancy)
|
|
451
484
|
|
|
452
485
|
# Determine if door close notifications are available for the given
|
|
@@ -479,25 +512,27 @@ class G90Alarm(G90DeviceNotifications):
|
|
|
479
512
|
G90Callback.invoke(self._sensor_cb, idx, name, occupancy)
|
|
480
513
|
|
|
481
514
|
@property
|
|
482
|
-
def sensor_callback(self):
|
|
515
|
+
def sensor_callback(self) -> Optional[SensorCallback]:
|
|
483
516
|
"""
|
|
484
|
-
|
|
485
|
-
sensor activates.
|
|
486
|
-
|
|
487
|
-
:type: .. py:function:: ()(idx, name, occupancy)
|
|
517
|
+
Sensor activity callback, which is invoked when sensor activates.
|
|
488
518
|
"""
|
|
489
519
|
return self._sensor_cb
|
|
490
520
|
|
|
491
521
|
@sensor_callback.setter
|
|
492
|
-
def sensor_callback(self, value):
|
|
522
|
+
def sensor_callback(self, value: SensorCallback) -> None:
|
|
493
523
|
self._sensor_cb = value
|
|
494
524
|
|
|
495
|
-
async def on_door_open_close(
|
|
525
|
+
async def on_door_open_close(
|
|
526
|
+
self, event_id: int, zone_name: str, is_open: bool
|
|
527
|
+
) -> None:
|
|
496
528
|
"""
|
|
497
|
-
|
|
498
|
-
panel.
|
|
529
|
+
Invoked when door open/close alert comes from the alarm
|
|
530
|
+
panel. Fires corresponding callback if set by the user with
|
|
531
|
+
:attr:`.door_open_close_callback`.
|
|
532
|
+
|
|
533
|
+
Please note the method is for internal use by the class.
|
|
499
534
|
|
|
500
|
-
.. seealso:: `method
|
|
535
|
+
.. seealso:: :meth:`.on_sensor_activity` method for arguments
|
|
501
536
|
"""
|
|
502
537
|
# Same internal callback is reused both for door open/close alerts and
|
|
503
538
|
# sensor notifications. The former adds reporting when a door is
|
|
@@ -509,30 +544,26 @@ class G90Alarm(G90DeviceNotifications):
|
|
|
509
544
|
)
|
|
510
545
|
|
|
511
546
|
@property
|
|
512
|
-
def door_open_close_callback(self):
|
|
547
|
+
def door_open_close_callback(self) -> Optional[DoorOpenCloseCallback]:
|
|
513
548
|
"""
|
|
514
|
-
|
|
549
|
+
The door open/close callback, which is invoked when door
|
|
515
550
|
is opened or closed (if corresponding alert is configured on the
|
|
516
551
|
device).
|
|
517
|
-
|
|
518
|
-
:type: .. py:function:: ()(idx: int, name: str, is_open: bool)
|
|
519
552
|
"""
|
|
520
553
|
return self._door_open_close_cb
|
|
521
554
|
|
|
522
555
|
@door_open_close_callback.setter
|
|
523
|
-
def door_open_close_callback(self, value):
|
|
524
|
-
"""
|
|
525
|
-
Sets callback for door open/close events.
|
|
526
|
-
"""
|
|
556
|
+
def door_open_close_callback(self, value: DoorOpenCloseCallback) -> None:
|
|
527
557
|
self._door_open_close_cb = value
|
|
528
558
|
|
|
529
|
-
async def on_armdisarm(self, state):
|
|
559
|
+
async def on_armdisarm(self, state: G90ArmDisarmTypes) -> None:
|
|
530
560
|
"""
|
|
531
|
-
|
|
532
|
-
|
|
561
|
+
Invoked when the device is armed or disarmed. Fires corresponding
|
|
562
|
+
callback if set by the user with :attr:`.armdisarm_callback`.
|
|
563
|
+
|
|
564
|
+
Please note the method is for internal use by the class.
|
|
533
565
|
|
|
534
566
|
:param state: Device state (armed, disarmed, armed home)
|
|
535
|
-
:type state: :class:`G90ArmDisarmTypes`
|
|
536
567
|
"""
|
|
537
568
|
if self._sms_alert_when_armed:
|
|
538
569
|
if state == G90ArmDisarmTypes.DISARM:
|
|
@@ -549,27 +580,26 @@ class G90Alarm(G90DeviceNotifications):
|
|
|
549
580
|
G90Callback.invoke(self._armdisarm_cb, state)
|
|
550
581
|
|
|
551
582
|
@property
|
|
552
|
-
def armdisarm_callback(self):
|
|
583
|
+
def armdisarm_callback(self) -> Optional[ArmDisarmCallback]:
|
|
553
584
|
"""
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
:type: .. py:function:: ()(state: :class:`.G90ArmDisarmTypes`)
|
|
585
|
+
The device arm/disarm callback, which is invoked when device state
|
|
586
|
+
changes.
|
|
558
587
|
"""
|
|
559
588
|
return self._armdisarm_cb
|
|
560
589
|
|
|
561
590
|
@armdisarm_callback.setter
|
|
562
|
-
def armdisarm_callback(self, value):
|
|
591
|
+
def armdisarm_callback(self, value: ArmDisarmCallback) -> None:
|
|
563
592
|
self._armdisarm_cb = value
|
|
564
593
|
|
|
565
|
-
async def on_alarm(self, event_id, zone_name):
|
|
594
|
+
async def on_alarm(self, event_id: int, zone_name: str) -> None:
|
|
566
595
|
"""
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
596
|
+
Invoked when alarm is triggered. Fires corresponding callback if set by
|
|
597
|
+
the user with :attr:`.alarm_callback`.
|
|
598
|
+
|
|
599
|
+
Please note the method is for internal use by the class.
|
|
570
600
|
|
|
571
|
-
:param
|
|
572
|
-
:param
|
|
601
|
+
:param event_id: Index of the sensor triggered alarm
|
|
602
|
+
:param zone_name: Sensor name
|
|
573
603
|
"""
|
|
574
604
|
sensor = await self.find_sensor(event_id, zone_name)
|
|
575
605
|
# The callback is still delivered to the caller even if the sensor
|
|
@@ -588,67 +618,63 @@ class G90Alarm(G90DeviceNotifications):
|
|
|
588
618
|
)
|
|
589
619
|
|
|
590
620
|
@property
|
|
591
|
-
def alarm_callback(self):
|
|
621
|
+
def alarm_callback(self) -> Optional[AlarmCallback]:
|
|
592
622
|
"""
|
|
593
|
-
|
|
594
|
-
device alarm triggers.
|
|
595
|
-
|
|
596
|
-
:type:
|
|
597
|
-
.. py:function:: ()(
|
|
598
|
-
sensor_idx: int, sensor_name: str, extra_data: str|None
|
|
599
|
-
)
|
|
623
|
+
The device alarm callback, which is invoked when device alarm triggers.
|
|
600
624
|
"""
|
|
601
625
|
return self._alarm_cb
|
|
602
626
|
|
|
603
627
|
@alarm_callback.setter
|
|
604
|
-
def alarm_callback(self, value):
|
|
628
|
+
def alarm_callback(self, value: AlarmCallback) -> None:
|
|
605
629
|
self._alarm_cb = value
|
|
606
630
|
|
|
607
|
-
async def on_low_battery(self, event_id, zone_name):
|
|
631
|
+
async def on_low_battery(self, event_id: int, zone_name: str) -> None:
|
|
608
632
|
"""
|
|
609
|
-
|
|
633
|
+
Invoked when the sensor reports on low battery. Fires
|
|
610
634
|
corresponding callback if set by the user with
|
|
611
|
-
|
|
612
|
-
|
|
635
|
+
:attr:`.on_low_battery_callback`.
|
|
636
|
+
|
|
637
|
+
Please note the method is for internal use by the class.
|
|
613
638
|
|
|
614
|
-
:param
|
|
615
|
-
:param
|
|
639
|
+
:param event_id: Index of the sensor triggered alarm
|
|
640
|
+
:param zone_name: Sensor name
|
|
616
641
|
"""
|
|
642
|
+
_LOGGER.debug('on_low_battery: %s %s', event_id, zone_name)
|
|
617
643
|
sensor = await self.find_sensor(event_id, zone_name)
|
|
618
644
|
if sensor:
|
|
645
|
+
# Set the low battery flag on the sensor
|
|
646
|
+
# pylint: disable=protected-access
|
|
647
|
+
sensor._set_low_battery(True)
|
|
619
648
|
# Invoke per-sensor callback if provided
|
|
620
649
|
G90Callback.invoke(sensor.low_battery_callback)
|
|
621
650
|
|
|
622
651
|
G90Callback.invoke(self._low_battery_cb, event_id, zone_name)
|
|
623
652
|
|
|
624
653
|
@property
|
|
625
|
-
def low_battery_callback(self):
|
|
654
|
+
def low_battery_callback(self) -> Optional[LowBatteryCallback]:
|
|
626
655
|
"""
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
:type: .. py:function:: ()(idx, name)
|
|
656
|
+
Low battery callback, which is invoked when sensor reports the
|
|
657
|
+
condition.
|
|
631
658
|
"""
|
|
632
659
|
return self._low_battery_cb
|
|
633
660
|
|
|
634
661
|
@low_battery_callback.setter
|
|
635
|
-
def low_battery_callback(self, value):
|
|
662
|
+
def low_battery_callback(self, value: LowBatteryCallback) -> None:
|
|
636
663
|
self._low_battery_cb = value
|
|
637
664
|
|
|
638
|
-
async def listen_device_notifications(self):
|
|
665
|
+
async def listen_device_notifications(self) -> None:
|
|
639
666
|
"""
|
|
640
667
|
Starts internal listener for device notifications/alerts.
|
|
641
|
-
|
|
642
668
|
"""
|
|
643
669
|
await self.listen()
|
|
644
670
|
|
|
645
|
-
def close_device_notifications(self):
|
|
671
|
+
def close_device_notifications(self) -> None:
|
|
646
672
|
"""
|
|
647
673
|
Closes the listener for device notifications/alerts.
|
|
648
674
|
"""
|
|
649
675
|
self.close()
|
|
650
676
|
|
|
651
|
-
async def arm_away(self):
|
|
677
|
+
async def arm_away(self) -> None:
|
|
652
678
|
"""
|
|
653
679
|
Arms the device in away mode.
|
|
654
680
|
"""
|
|
@@ -656,7 +682,7 @@ class G90Alarm(G90DeviceNotifications):
|
|
|
656
682
|
await self.command(G90Commands.SETHOSTSTATUS,
|
|
657
683
|
[state])
|
|
658
684
|
|
|
659
|
-
async def arm_home(self):
|
|
685
|
+
async def arm_home(self) -> None:
|
|
660
686
|
"""
|
|
661
687
|
Arms the device in home mode.
|
|
662
688
|
"""
|
|
@@ -664,7 +690,7 @@ class G90Alarm(G90DeviceNotifications):
|
|
|
664
690
|
await self.command(G90Commands.SETHOSTSTATUS,
|
|
665
691
|
[state])
|
|
666
692
|
|
|
667
|
-
async def disarm(self):
|
|
693
|
+
async def disarm(self) -> None:
|
|
668
694
|
"""
|
|
669
695
|
Disarms the device.
|
|
670
696
|
"""
|
|
@@ -673,7 +699,7 @@ class G90Alarm(G90DeviceNotifications):
|
|
|
673
699
|
[state])
|
|
674
700
|
|
|
675
701
|
@property
|
|
676
|
-
def sms_alert_when_armed(self):
|
|
702
|
+
def sms_alert_when_armed(self) -> bool:
|
|
677
703
|
"""
|
|
678
704
|
When enabled, allows to save costs on SMS by having corresponding alert
|
|
679
705
|
enabled only when device is armed.
|
|
@@ -681,12 +707,12 @@ class G90Alarm(G90DeviceNotifications):
|
|
|
681
707
|
return self._sms_alert_when_armed
|
|
682
708
|
|
|
683
709
|
@sms_alert_when_armed.setter
|
|
684
|
-
def sms_alert_when_armed(self, value):
|
|
710
|
+
def sms_alert_when_armed(self, value: bool) -> None:
|
|
685
711
|
self._sms_alert_when_armed = value
|
|
686
712
|
|
|
687
713
|
async def start_simulating_alerts_from_history(
|
|
688
|
-
self, interval=5, history_depth=5
|
|
689
|
-
):
|
|
714
|
+
self, interval: float = 5, history_depth: int = 5
|
|
715
|
+
) -> None:
|
|
690
716
|
"""
|
|
691
717
|
Starts the separate task to simulate device alerts from history
|
|
692
718
|
entries.
|
|
@@ -695,9 +721,9 @@ class G90Alarm(G90DeviceNotifications):
|
|
|
695
721
|
notifications will not be processed thus resulting in possible
|
|
696
722
|
duplicated if those could be received from the network.
|
|
697
723
|
|
|
698
|
-
:param
|
|
724
|
+
:param interval: Interval (in seconds) between polling for newer
|
|
699
725
|
history entities
|
|
700
|
-
:param
|
|
726
|
+
:param history_depth: Amount of history entries to fetch during
|
|
701
727
|
each polling cycle
|
|
702
728
|
"""
|
|
703
729
|
# Remember if device notifications listener has been started already
|
|
@@ -710,7 +736,7 @@ class G90Alarm(G90DeviceNotifications):
|
|
|
710
736
|
self._simulate_alerts_from_history(interval, history_depth)
|
|
711
737
|
)
|
|
712
738
|
|
|
713
|
-
async def stop_simulating_alerts_from_history(self):
|
|
739
|
+
async def stop_simulating_alerts_from_history(self) -> None:
|
|
714
740
|
"""
|
|
715
741
|
Stops the task simulating device alerts from history entries.
|
|
716
742
|
|
|
@@ -728,7 +754,9 @@ class G90Alarm(G90DeviceNotifications):
|
|
|
728
754
|
if self._alert_simulation_start_listener_back:
|
|
729
755
|
await self.listen()
|
|
730
756
|
|
|
731
|
-
async def _simulate_alerts_from_history(
|
|
757
|
+
async def _simulate_alerts_from_history(
|
|
758
|
+
self, interval: float, history_depth: int
|
|
759
|
+
) -> None:
|
|
732
760
|
"""
|
|
733
761
|
Periodically fetches history entries from the device and simulates
|
|
734
762
|
device alerts off of those.
|
|
@@ -736,7 +764,7 @@ class G90Alarm(G90DeviceNotifications):
|
|
|
736
764
|
Only the history entries occur after the process is started are
|
|
737
765
|
handled, to avoid triggering callbacks retrospectively.
|
|
738
766
|
|
|
739
|
-
See :
|
|
767
|
+
See :meth:`.start_simulating_alerts_from_history` for the parameters.
|
|
740
768
|
"""
|
|
741
769
|
last_history_ts = None
|
|
742
770
|
|
|
@@ -773,7 +801,7 @@ class G90Alarm(G90DeviceNotifications):
|
|
|
773
801
|
for item in reversed(history):
|
|
774
802
|
# Process only the entries newer than one been recorded as
|
|
775
803
|
# most recent one
|
|
776
|
-
if item.datetime > last_history_ts:
|
|
804
|
+
if last_history_ts and item.datetime > last_history_ts:
|
|
777
805
|
_LOGGER.debug(
|
|
778
806
|
'Found newer history entry: %s, simulating alert',
|
|
779
807
|
repr(item)
|