pyg90alarm 1.19.0__py3-none-any.whl → 2.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. pyg90alarm/__init__.py +5 -5
  2. pyg90alarm/alarm.py +159 -114
  3. pyg90alarm/cloud/__init__.py +31 -0
  4. pyg90alarm/cloud/const.py +56 -0
  5. pyg90alarm/cloud/messages.py +593 -0
  6. pyg90alarm/cloud/notifications.py +409 -0
  7. pyg90alarm/cloud/protocol.py +518 -0
  8. pyg90alarm/const.py +5 -0
  9. pyg90alarm/entities/base_entity.py +83 -0
  10. pyg90alarm/entities/base_list.py +165 -0
  11. pyg90alarm/entities/device_list.py +58 -0
  12. pyg90alarm/entities/sensor.py +63 -3
  13. pyg90alarm/entities/sensor_list.py +50 -0
  14. pyg90alarm/local/__init__.py +0 -0
  15. pyg90alarm/{base_cmd.py → local/base_cmd.py} +3 -6
  16. pyg90alarm/{discovery.py → local/discovery.py} +1 -1
  17. pyg90alarm/{history.py → local/history.py} +4 -2
  18. pyg90alarm/{host_status.py → local/host_status.py} +1 -1
  19. pyg90alarm/local/notifications.py +116 -0
  20. pyg90alarm/{paginated_cmd.py → local/paginated_cmd.py} +2 -2
  21. pyg90alarm/{paginated_result.py → local/paginated_result.py} +1 -1
  22. pyg90alarm/{targeted_discovery.py → local/targeted_discovery.py} +2 -2
  23. pyg90alarm/notifications/__init__.py +0 -0
  24. pyg90alarm/{device_notifications.py → notifications/base.py} +115 -173
  25. pyg90alarm/notifications/protocol.py +116 -0
  26. {pyg90alarm-1.19.0.dist-info → pyg90alarm-2.0.0.dist-info}/METADATA +112 -18
  27. pyg90alarm-2.0.0.dist-info/RECORD +40 -0
  28. {pyg90alarm-1.19.0.dist-info → pyg90alarm-2.0.0.dist-info}/WHEEL +1 -1
  29. pyg90alarm-1.19.0.dist-info/RECORD +0 -27
  30. /pyg90alarm/{config.py → local/config.py} +0 -0
  31. /pyg90alarm/{host_info.py → local/host_info.py} +0 -0
  32. /pyg90alarm/{user_data_crc.py → local/user_data_crc.py} +0 -0
  33. {pyg90alarm-1.19.0.dist-info → pyg90alarm-2.0.0.dist-info/licenses}/LICENSE +0 -0
  34. {pyg90alarm-1.19.0.dist-info → pyg90alarm-2.0.0.dist-info}/top_level.txt +0 -0
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2021 Ilia Sotnikov
1
+ # Copyright (c) 2025 Ilia Sotnikov
2
2
  #
3
3
  # Permission is hereby granted, free of charge, to any person obtaining a copy
4
4
  # of this software and associated documentation files (the "Software"), to deal
@@ -24,14 +24,14 @@ Implements support for notifications/alerts sent by G90 alarm panel.
24
24
  import json
25
25
  import logging
26
26
  from typing import (
27
- Optional, Tuple, List, Any
27
+ Optional, List, Any, Callable
28
28
  )
29
29
  from dataclasses import dataclass
30
- import asyncio
31
30
  from asyncio.transports import BaseTransport
32
- from asyncio.protocols import DatagramProtocol
33
- from .callback import G90Callback
34
- from .const import (
31
+ from datetime import datetime, timezone
32
+
33
+ from ..callback import G90Callback
34
+ from ..const import (
35
35
  G90MessageTypes,
36
36
  G90NotificationTypes,
37
37
  G90AlertTypes,
@@ -41,6 +41,7 @@ from .const import (
41
41
  G90AlertStates,
42
42
  G90RemoteButtonStates,
43
43
  )
44
+ from .protocol import G90NotificationProtocol
44
45
 
45
46
  _LOGGER = logging.getLogger(__name__)
46
47
 
@@ -104,7 +105,7 @@ class G90DeviceAlert: # pylint: disable=too-many-instance-attributes
104
105
  other: str
105
106
 
106
107
 
107
- class G90DeviceNotifications(DatagramProtocol):
108
+ class G90NotificationsBase:
108
109
  """
109
110
  Implements support for notifications/alerts sent by alarm panel.
110
111
 
@@ -115,32 +116,35 @@ class G90DeviceNotifications(DatagramProtocol):
115
116
  command to be performed first, one that fetches device GUID and then stores
116
117
  it using :attr:`.device_id` (e.g. :meth:`G90Alarm.get_host_info`).
117
118
  """
118
- def __init__(self, local_port: int, local_host: str):
119
+ def __init__(
120
+ self, protocol_factory: Callable[[], G90NotificationProtocol],
121
+ ):
119
122
  # pylint: disable=too-many-arguments
120
- self._notification_transport: Optional[BaseTransport] = None
121
- self._notifications_local_host = local_host
122
- self._notifications_local_port = local_port
123
- # Host/port of the device is configured to communicating via commands.
124
- # Inteded to validate if notifications/alert are received from the
125
- # correct device.
126
- self._host: Optional[str] = None
127
- self._port: Optional[int] = None
123
+ self._transport: Optional[BaseTransport] = None
128
124
  # Same but for device ID (GUID) - the notifications logic uses it to
129
125
  # perform validation, but doesn't set it from messages received (it
130
126
  # will diminish the purpose of the validation, should be done by an
131
127
  # ancestor class).
132
128
  self._device_id: Optional[str] = None
129
+ self._protocol = protocol_factory()
130
+ self._last_device_packet_time: Optional[datetime] = None
131
+ self._last_upstream_packet_time: Optional[datetime] = None
133
132
 
134
- def _handle_notification(
135
- self, addr: Tuple[str, int], notification: G90Notification
133
+ def handle_notification(
134
+ self, notification: G90Notification
136
135
  ) -> None:
136
+ """
137
+ Handles notification received from the device.
138
+
139
+ :param notification: The notification to handle.
140
+ """
137
141
  # Sensor activity notification
138
142
  if notification.kind == G90NotificationTypes.SENSOR_ACTIVITY:
139
143
  g90_zone_info = G90ZoneInfo(*notification.data)
140
144
 
141
145
  _LOGGER.debug('Sensor notification: %s', g90_zone_info)
142
146
  G90Callback.invoke(
143
- self.on_sensor_activity,
147
+ self._protocol.on_sensor_activity,
144
148
  g90_zone_info.idx, g90_zone_info.name
145
149
  )
146
150
 
@@ -155,7 +159,7 @@ class G90DeviceNotifications(DatagramProtocol):
155
159
 
156
160
  _LOGGER.debug('Arm/disarm notification: %s',
157
161
  state)
158
- G90Callback.invoke(self.on_armdisarm, state)
162
+ G90Callback.invoke(self._protocol.on_armdisarm, state)
159
163
 
160
164
  return
161
165
 
@@ -164,14 +168,14 @@ class G90DeviceNotifications(DatagramProtocol):
164
168
  g90_zone_info = G90ZoneInfo(*notification.data)
165
169
  _LOGGER.debug('Door open detected when arming: %s', g90_zone_info)
166
170
  G90Callback.invoke(
167
- self.on_door_open_when_arming,
171
+ self._protocol.on_door_open_when_arming,
168
172
  g90_zone_info.idx, g90_zone_info.name
169
173
  )
170
174
  return
171
175
 
172
- _LOGGER.warning('Unknown notification received from %s:%s:'
176
+ _LOGGER.warning('Unknown notification received:'
173
177
  ' kind %s, data %s',
174
- addr[0], addr[1], notification.kind, notification.data)
178
+ notification.kind, notification.data)
175
179
 
176
180
  def _handle_alert_sensor_activity(self, alert: G90DeviceAlert) -> bool:
177
181
  """
@@ -180,7 +184,7 @@ class G90DeviceNotifications(DatagramProtocol):
180
184
  if alert.source == G90AlertSources.REMOTE:
181
185
  _LOGGER.debug('Remote button press alert: %s', alert)
182
186
  G90Callback.invoke(
183
- self.on_remote_button_press,
187
+ self._protocol.on_remote_button_press,
184
188
  alert.event_id, alert.zone_name,
185
189
  G90RemoteButtonStates(alert.state)
186
190
  )
@@ -197,7 +201,7 @@ class G90DeviceNotifications(DatagramProtocol):
197
201
 
198
202
  _LOGGER.debug('Door open_close alert: %s', alert)
199
203
  G90Callback.invoke(
200
- self.on_door_open_close,
204
+ self._protocol.on_door_open_close,
201
205
  alert.event_id, alert.zone_name, is_open
202
206
  )
203
207
 
@@ -209,7 +213,7 @@ class G90DeviceNotifications(DatagramProtocol):
209
213
  ):
210
214
  _LOGGER.debug('Low battery alert: %s', alert)
211
215
  G90Callback.invoke(
212
- self.on_low_battery,
216
+ self._protocol.on_low_battery,
213
217
  alert.event_id, alert.zone_name
214
218
  )
215
219
 
@@ -217,10 +221,17 @@ class G90DeviceNotifications(DatagramProtocol):
217
221
 
218
222
  return False
219
223
 
220
- def _handle_alert(
221
- self, addr: Tuple[str, int], alert: G90DeviceAlert,
224
+ def handle_alert(
225
+ self, alert: G90DeviceAlert,
222
226
  verify_device_id: bool = True
223
227
  ) -> None:
228
+ """
229
+ Handles alert received from the device.
230
+
231
+ :param alert: The alert to handle.
232
+ :param verify_device_id: Whether to verify the device ID (GUID) in the
233
+ alert. If set to False, the device ID will not be verified.
234
+ """
224
235
  handled = False
225
236
 
226
237
  # Stop processing when alert is received from the device with different
@@ -256,7 +267,7 @@ class G90DeviceNotifications(DatagramProtocol):
256
267
  # We received the device state change related to arm/disarm,
257
268
  # invoke the corresponding callback
258
269
  _LOGGER.debug('Arm/disarm state change: %s', state)
259
- G90Callback.invoke(self.on_armdisarm, state)
270
+ G90Callback.invoke(self._protocol.on_armdisarm, state)
260
271
 
261
272
  handled = True
262
273
 
@@ -265,7 +276,8 @@ class G90DeviceNotifications(DatagramProtocol):
265
276
  if alert.source == G90AlertSources.REMOTE:
266
277
  _LOGGER.debug('SOS: %s', alert.zone_name)
267
278
  G90Callback.invoke(
268
- self.on_sos, alert.event_id, alert.zone_name, False
279
+ self._protocol.on_sos,
280
+ alert.event_id, alert.zone_name, False
269
281
  )
270
282
  # Regular alarm
271
283
  else:
@@ -274,7 +286,7 @@ class G90DeviceNotifications(DatagramProtocol):
274
286
  'Alarm: %s, is tampered: %s', alert.zone_name, is_tampered
275
287
  )
276
288
  G90Callback.invoke(
277
- self.on_alarm,
289
+ self._protocol.on_alarm,
278
290
  alert.event_id, alert.zone_name, is_tampered
279
291
  )
280
292
 
@@ -286,42 +298,22 @@ class G90DeviceNotifications(DatagramProtocol):
286
298
 
287
299
  _LOGGER.debug('SOS: Host')
288
300
  G90Callback.invoke(
289
- self.on_sos, alert.event_id, zone_name, True
301
+ self._protocol.on_sos, alert.event_id, zone_name, True
290
302
  )
291
303
 
292
304
  handled = True
293
305
 
294
306
  if not handled:
295
- _LOGGER.warning('Unknown alert received from %s:%s:'
296
- ' type %s, data %s',
297
- addr[0], addr[1], alert.type, alert)
298
-
299
- # Implementation of datagram protocol,
300
- # https://docs.python.org/3/library/asyncio-protocol.html#datagram-protocols
301
- def connection_made(self, transport: BaseTransport) -> None:
302
- """
303
- Invoked when connection from the device is made.
304
- """
305
-
306
- def connection_lost(self, exc: Optional[Exception]) -> None:
307
- """
308
- Same but when the connection is lost.
309
- """
307
+ _LOGGER.warning(
308
+ 'Unknown alert received: type %s, data %s',
309
+ alert.type, alert
310
+ )
310
311
 
311
- def datagram_received( # pylint:disable=R0911
312
- self, data: bytes, addr: Tuple[str, int]
313
- ) -> None:
312
+ # pylint:disable=too-many-return-statements
313
+ def handle(self, data: bytes) -> None:
314
314
  """
315
- Invoked when datagram is received from the device.
315
+ Invoked when message is received from the device.
316
316
  """
317
- if self._host and self._host != addr[0]:
318
- _LOGGER.error(
319
- "Received notification/alert from wrong host '%s',"
320
- " expected from '%s'",
321
- addr[0], self._host
322
- )
323
- return
324
-
325
317
  try:
326
318
  s_data = data.decode('utf-8')
327
319
  except UnicodeDecodeError:
@@ -332,8 +324,7 @@ class G90DeviceNotifications(DatagramProtocol):
332
324
  _LOGGER.error('Missing end marker in data')
333
325
  return
334
326
  payload = s_data[:-1]
335
- _LOGGER.debug('Received device message from %s:%s: %s',
336
- addr[0], addr[1], payload)
327
+
337
328
  try:
338
329
  message = json.loads(payload)
339
330
  g90_message = G90Message(*message)
@@ -351,10 +342,9 @@ class G90DeviceNotifications(DatagramProtocol):
351
342
  try:
352
343
  notification_data = G90Notification(*g90_message.data)
353
344
  except TypeError as exc:
354
- _LOGGER.error('Bad notification received from %s:%s: %s',
355
- addr[0], addr[1], exc)
345
+ _LOGGER.error('Bad notification received: %s', exc)
356
346
  return
357
- self._handle_notification(addr, notification_data)
347
+ self.handle_notification(notification_data)
358
348
  return
359
349
 
360
350
  # Device alerts
@@ -362,132 +352,34 @@ class G90DeviceNotifications(DatagramProtocol):
362
352
  try:
363
353
  alert_data = G90DeviceAlert(*g90_message.data)
364
354
  except TypeError as exc:
365
- _LOGGER.error('Bad alert received from %s:%s: %s',
366
- addr[0], addr[1], exc)
355
+ _LOGGER.error('Bad alert received: %s', exc)
367
356
  return
368
- self._handle_alert(addr, alert_data)
357
+ self.handle_alert(alert_data)
369
358
  return
370
359
 
371
- _LOGGER.warning('Unknown message received from %s:%s: %s',
372
- addr[0], addr[1], message)
373
-
374
- async def on_armdisarm(self, state: G90ArmDisarmTypes) -> None:
375
- """
376
- Invoked when device is armed or disarmed.
377
-
378
- :param state: State of the device
379
- """
380
-
381
- async def on_sensor_activity(self, idx: int, name: str) -> None:
382
- """
383
- Invoked on sensor activity.
384
-
385
- :param idx: Index of the sensor.
386
- :param name: Name of the sensor.
387
- """
388
-
389
- async def on_door_open_when_arming(
390
- self, event_id: int, zone_name: str
391
- ) -> None:
392
- """
393
- Invoked when door open is detected when panel is armed.
394
-
395
- :param event_id: Index of the sensor.
396
- :param zone_name: Name of the sensor that reports door open.
397
- """
398
-
399
- async def on_door_open_close(
400
- self, event_id: int, zone_name: str, is_open: bool
401
- ) -> None:
402
- """
403
- Invoked when door sensor reports it opened or closed.
404
-
405
- :param event_id: Index of the sensor reporting the event.
406
- :param zone_name: Name of the sensor that reports door open/close.
407
- :param is_open: Indicates if the door is open.
408
- """
409
-
410
- async def on_low_battery(self, event_id: int, zone_name: str) -> None:
411
- """
412
- Invoked when a sensor reports it is low on battery.
413
-
414
- :param event_id: Index of the sensor.
415
- :param zone_name: Name of the sensor that reports low battery.
416
- """
417
-
418
- async def on_alarm(
419
- self, event_id: int, zone_name: str, is_tampered: bool
420
- ) -> None:
421
- """
422
- Invoked when device triggers the alarm.
423
-
424
- :param event_id: Index of the sensor.
425
- :param zone_name: Name of the zone that triggered the alarm.
426
- """
427
-
428
- async def on_remote_button_press(
429
- self, event_id: int, zone_name: str, button: G90RemoteButtonStates
430
- ) -> None:
431
- """
432
- Invoked when a remote button is pressed.
433
-
434
- Please note there will only be call to the method w/o invoking
435
- :meth:`G90DeviceNotifications.on_sensor_activity`.
436
-
437
- :param event_id: Index of the sensor associated with the remote.
438
- :param zone_name: Name of the sensor that reports remote button press.
439
- :param button: The button pressed on the remote
440
- """
441
-
442
- async def on_sos(
443
- self, event_id: int, zone_name: str, is_host_sos: bool
444
- ) -> None:
445
- """
446
- Invoked when SOS is triggered.
447
-
448
- Please note that the panel might not set its status to alarm
449
- internally, so that :meth:`G90DeviceNotifications` might need an
450
- explicit call in the derived class to simulate that.
451
-
452
- :param event_id: Index of the sensor.
453
- :param zone_name: Name of the sensor that reports SOS.
454
- :param is_host_sos: Indicates if the SOS is host-initiated.
455
- """
360
+ _LOGGER.warning('Unknown message received: %s', message)
456
361
 
457
362
  async def listen(self) -> None:
458
363
  """
459
- Listens for notifications/alers from the device.
364
+ Listens for notifications/alerts from the device.
460
365
  """
461
- try:
462
- loop = asyncio.get_running_loop()
463
- except AttributeError:
464
- loop = asyncio.get_event_loop()
465
-
466
- _LOGGER.debug('Creating UDP endpoint for %s:%s',
467
- self._notifications_local_host,
468
- self._notifications_local_port)
469
- (self._notification_transport,
470
- _protocol) = await loop.create_datagram_endpoint(
471
- lambda: self,
472
- local_addr=(
473
- self._notifications_local_host, self._notifications_local_port
474
- ))
366
+ raise NotImplementedError
475
367
 
476
368
  @property
477
369
  def listener_started(self) -> bool:
478
370
  """
479
371
  Indicates if the listener of the device notifications has been started.
480
372
  """
481
- return self._notification_transport is not None
373
+ return self._transport is not None
482
374
 
483
- def close(self) -> None:
375
+ async def close(self) -> None:
484
376
  """
485
377
  Closes the listener.
486
378
  """
487
- if self._notification_transport:
379
+ if self._transport:
488
380
  _LOGGER.debug('No longer listening for device notifications')
489
- self._notification_transport.close()
490
- self._notification_transport = None
381
+ self._transport.close()
382
+ self._transport = None
491
383
 
492
384
  @property
493
385
  def device_id(self) -> Optional[str]:
@@ -495,7 +387,7 @@ class G90DeviceNotifications(DatagramProtocol):
495
387
  The ID (GUID) of the panel being communicated with thru commands.
496
388
 
497
389
  Available when any panel command receives it from the device
498
- (:meth:`G90Alarm.get_host_info` currently).
390
+ (`GETHOSTINFO` local command or Hello / HelloDiscovery cloud ones).
499
391
  """
500
392
  return self._device_id
501
393
 
@@ -510,3 +402,53 @@ class G90DeviceNotifications(DatagramProtocol):
510
402
  return
511
403
 
512
404
  self._device_id = device_id
405
+
406
+ def clear_device_id(self) -> None:
407
+ """
408
+ Clears the device ID.
409
+ """
410
+ self._device_id = None
411
+
412
+ @property
413
+ def last_device_packet_time(self) -> Optional[datetime]:
414
+ """
415
+ Returns the timestamp of the last packet received from the device.
416
+
417
+ This property can be used to monitor the communication health with the
418
+ device.
419
+ """
420
+ return self._last_device_packet_time
421
+
422
+ def set_last_device_packet_time(self) -> None:
423
+ """
424
+ Updates the timestamp of the last packet received from the device.
425
+
426
+ This method is called internally when a packet is received from the
427
+ device.
428
+ """
429
+ self._last_device_packet_time = datetime.now(tz=timezone.utc)
430
+ _LOGGER.debug(
431
+ 'Last device packet time: %s', self._last_device_packet_time
432
+ )
433
+
434
+ @property
435
+ def last_upstream_packet_time(self) -> Optional[datetime]:
436
+ """
437
+ Returns the timestamp of the last packet sent to the upstream server.
438
+
439
+ This property can be used to monitor the communication health with the
440
+ cloud/upstream server.
441
+ """
442
+ return self._last_upstream_packet_time
443
+
444
+ def set_last_upstream_packet_time(self) -> None:
445
+ """
446
+ Updates the timestamp of the last packet sent to the upstream server.
447
+
448
+ This method is called internally when a packet is sent to the upstream
449
+ server.
450
+ """
451
+ self._last_upstream_packet_time = datetime.now(tz=timezone.utc)
452
+ _LOGGER.debug(
453
+ 'Last upstream packet time: %s', self._last_upstream_packet_time
454
+ )
@@ -0,0 +1,116 @@
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
+ Defines notification protocol for `NotificationBase` class.
23
+ """
24
+
25
+ from ..const import (
26
+ G90ArmDisarmTypes,
27
+ G90RemoteButtonStates,
28
+ )
29
+
30
+
31
+ class G90NotificationProtocol:
32
+ """
33
+ Protocol for notification handling.
34
+ """
35
+ async def on_armdisarm(self, state: G90ArmDisarmTypes) -> None:
36
+ """
37
+ Invoked when device is armed or disarmed.
38
+
39
+ :param state: State of the device
40
+ """
41
+
42
+ async def on_sensor_activity(self, idx: int, name: str) -> None:
43
+ """
44
+ Invoked on sensor activity.
45
+
46
+ :param idx: Index of the sensor.
47
+ :param name: Name of the sensor.
48
+ """
49
+
50
+ async def on_door_open_when_arming(
51
+ self, event_id: int, zone_name: str
52
+ ) -> None:
53
+ """
54
+ Invoked when door open is detected when panel is armed.
55
+
56
+ :param event_id: Index of the sensor.
57
+ :param zone_name: Name of the sensor that reports door open.
58
+ """
59
+
60
+ async def on_door_open_close(
61
+ self, event_id: int, zone_name: str, is_open: bool
62
+ ) -> None:
63
+ """
64
+ Invoked when door sensor reports it opened or closed.
65
+
66
+ :param event_id: Index of the sensor reporting the event.
67
+ :param zone_name: Name of the sensor that reports door open/close.
68
+ :param is_open: Indicates if the door is open.
69
+ """
70
+
71
+ async def on_low_battery(self, event_id: int, zone_name: str) -> None:
72
+ """
73
+ Invoked when a sensor reports it is low on battery.
74
+
75
+ :param event_id: Index of the sensor.
76
+ :param zone_name: Name of the sensor that reports low battery.
77
+ """
78
+
79
+ async def on_alarm(
80
+ self, event_id: int, zone_name: str, is_tampered: bool
81
+ ) -> None:
82
+ """
83
+ Invoked when device triggers the alarm.
84
+
85
+ :param event_id: Index of the sensor.
86
+ :param zone_name: Name of the zone that triggered the alarm.
87
+ """
88
+
89
+ async def on_remote_button_press(
90
+ self, event_id: int, zone_name: str, button: G90RemoteButtonStates
91
+ ) -> None:
92
+ """
93
+ Invoked when a remote button is pressed.
94
+
95
+ Please note there will only be call to the method w/o invoking
96
+ :meth:`G90DeviceNotifications.on_sensor_activity`.
97
+
98
+ :param event_id: Index of the sensor associated with the remote.
99
+ :param zone_name: Name of the sensor that reports remote button press.
100
+ :param button: The button pressed on the remote
101
+ """
102
+
103
+ async def on_sos(
104
+ self, event_id: int, zone_name: str, is_host_sos: bool
105
+ ) -> None:
106
+ """
107
+ Invoked when SOS is triggered.
108
+
109
+ Please note that the panel might not set its status to alarm
110
+ internally, so that :meth:`G90DeviceNotifications` might need an
111
+ explicit call in the derived class to simulate that.
112
+
113
+ :param event_id: Index of the sensor.
114
+ :param zone_name: Name of the sensor that reports SOS.
115
+ :param is_host_sos: Indicates if the SOS is host-initiated.
116
+ """