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