pyg90alarm 2.1.1__py3-none-any.whl → 2.2.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 CHANGED
@@ -29,12 +29,21 @@ from .notifications.base import (
29
29
  G90DeviceAlert,
30
30
  )
31
31
  from .entities.sensor import (
32
- G90Sensor, G90SensorTypes, G90SensorAlertModes, G90SensorUserFlags
32
+ G90Sensor, G90SensorAlertModes, G90SensorUserFlags
33
33
  )
34
34
  from .entities.device import G90Device
35
35
  from .local.host_info import (
36
36
  G90HostInfo, G90HostInfoWifiStatus, G90HostInfoGsmStatus
37
37
  )
38
+ from .definitions.sensors import (
39
+ G90SensorDefinitions
40
+ )
41
+ from .definitions.devices import (
42
+ G90DeviceDefinitions
43
+ )
44
+ from .definitions.base import (
45
+ G90PeripheralTypes,
46
+ )
38
47
  from .local.config import G90AlertConfigFlags
39
48
  from .local.host_status import G90HostStatus
40
49
  from .const import (
@@ -47,16 +56,29 @@ from .const import (
47
56
  G90AlertStateChangeTypes,
48
57
  G90HistoryStates,
49
58
  )
50
- from .exceptions import G90Error, G90TimeoutError
59
+ from .exceptions import (
60
+ G90Error, G90TimeoutError, G90CommandError, G90CommandFailure,
61
+ G90EntityRegistrationError, G90PeripheralDefinitionNotFound,
62
+ )
51
63
 
52
64
  __all__ = [
53
65
  'G90Alarm', 'G90BaseCommand', 'G90PaginatedResult', 'G90DeviceAlert',
54
- 'G90Sensor', 'G90SensorTypes', 'G90SensorAlertModes', 'G90SensorUserFlags',
55
- 'G90AlertConfigFlags', 'G90Device',
66
+ # Sensors and related
67
+ 'G90Sensor', 'G90PeripheralTypes', 'G90SensorAlertModes',
68
+ 'G90SensorUserFlags',
69
+ 'G90AlertConfigFlags',
70
+ 'G90Device',
71
+ # Panel information and status
56
72
  'G90HostInfo', 'G90HostInfoWifiStatus', 'G90HostInfoGsmStatus',
57
73
  'G90HostStatus',
74
+ # Types for alerts and notifications
58
75
  'G90MessageTypes', 'G90NotificationTypes', 'G90ArmDisarmTypes',
59
76
  'G90AlertTypes', 'G90AlertSources', 'G90AlertStates',
60
- 'G90AlertStateChangeTypes', 'G90HistoryStates', 'G90Error',
61
- 'G90TimeoutError',
77
+ 'G90AlertStateChangeTypes', 'G90HistoryStates',
78
+ # Exceptions
79
+ 'G90Error',
80
+ 'G90TimeoutError', 'G90CommandError', 'G90CommandFailure',
81
+ 'G90EntityRegistrationError', 'G90PeripheralDefinitionNotFound',
82
+ # Definitions
83
+ 'G90SensorDefinitions', 'G90DeviceDefinitions'
62
84
  ]
pyg90alarm/alarm.py CHANGED
@@ -69,15 +69,21 @@ from .const import (
69
69
  CLOUD_NOTIFICATIONS_PORT,
70
70
  REMOTE_CLOUD_HOST,
71
71
  REMOTE_CLOUD_PORT,
72
+ DEVICE_REGISTRATION_TIMEOUT,
73
+ ROOM_ID,
72
74
  G90ArmDisarmTypes,
73
75
  G90RemoteButtonStates,
74
76
  )
75
77
  from .local.base_cmd import (G90BaseCommand, G90BaseCommandData)
76
78
  from .local.paginated_result import G90PaginatedResult, G90PaginatedResponse
77
- from .entities.sensor import (G90Sensor, G90SensorTypes)
79
+ from .entities.base_list import ListChangeCallback
80
+ from .entities.sensor import G90Sensor
78
81
  from .entities.sensor_list import G90SensorList
79
82
  from .entities.device import G90Device
80
83
  from .entities.device_list import G90DeviceList
84
+ from .definitions.base import (
85
+ G90PeripheralTypes
86
+ )
81
87
  from .notifications.protocol import (
82
88
  G90NotificationProtocol
83
89
  )
@@ -92,7 +98,7 @@ from .local.host_status import G90HostStatus
92
98
  from .local.config import (G90AlertConfig, G90AlertConfigFlags)
93
99
  from .local.history import G90History
94
100
  from .local.user_data_crc import G90UserDataCRC
95
- from .callback import G90Callback
101
+ from .callback import G90Callback, G90CallbackList
96
102
  from .exceptions import G90Error, G90TimeoutError
97
103
  from .cloud.notifications import G90CloudNotifications
98
104
 
@@ -159,6 +165,10 @@ if TYPE_CHECKING:
159
165
  Callable[[], None],
160
166
  Callable[[], Coroutine[None, None, None]]
161
167
  ]
168
+ SensorChangeCallback = Union[
169
+ Callable[[int, str, bool], None],
170
+ Callable[[int, str, bool], Coroutine[None, None, None]]
171
+ ]
162
172
 
163
173
 
164
174
  # pylint: disable=too-many-public-methods
@@ -186,20 +196,37 @@ class G90Alarm(G90NotificationProtocol):
186
196
  self._port: int = port
187
197
  self._notifications: Optional[G90NotificationsBase] = None
188
198
  self._sensors = G90SensorList(self)
199
+ # The callback will be invoked when sensor list changes, e.g. sensor is
200
+ # added or updated
201
+ self._sensors.list_change_callback = self.on_sensor_list_change
189
202
  self._devices = G90DeviceList(self)
190
- self._sensor_cb: Optional[SensorCallback] = None
191
- self._armdisarm_cb: Optional[ArmDisarmCallback] = None
192
- self._door_open_close_cb: Optional[DoorOpenCloseCallback] = None
193
- self._alarm_cb: Optional[AlarmCallback] = None
194
- self._low_battery_cb: Optional[LowBatteryCallback] = None
195
- self._sos_cb: Optional[SosCallback] = None
196
- self._remote_button_press_cb: Optional[
203
+ # Similarly for the device list
204
+ self._devices.list_change_callback = self.on_device_list_change
205
+ self._sensor_cb: G90CallbackList[SensorCallback] = G90CallbackList()
206
+ self._armdisarm_cb: G90CallbackList[ArmDisarmCallback] = (
207
+ G90CallbackList()
208
+ )
209
+ self._door_open_close_cb: G90CallbackList[DoorOpenCloseCallback] = (
210
+ G90CallbackList()
211
+ )
212
+ self._alarm_cb: G90CallbackList[AlarmCallback] = G90CallbackList()
213
+ self._low_battery_cb: G90CallbackList[LowBatteryCallback] = (
214
+ G90CallbackList()
215
+ )
216
+ self._sos_cb: G90CallbackList[SosCallback] = G90CallbackList()
217
+ self._remote_button_press_cb: G90CallbackList[
197
218
  RemoteButtonPressCallback
198
- ] = None
199
- self._door_open_when_arming_cb: Optional[
219
+ ] = G90CallbackList()
220
+ self._door_open_when_arming_cb: G90CallbackList[
200
221
  DoorOpenWhenArmingCallback
201
- ] = None
202
- self._tamper_cb: Optional[TamperCallback] = None
222
+ ] = G90CallbackList()
223
+ self._tamper_cb: G90CallbackList[TamperCallback] = G90CallbackList()
224
+ self._sensor_list_change_cb: G90CallbackList[
225
+ ListChangeCallback[G90Sensor]
226
+ ] = G90CallbackList()
227
+ self._device_list_change_cb: G90CallbackList[
228
+ ListChangeCallback[G90Device]
229
+ ] = G90CallbackList()
203
230
  self._reset_occupancy_interval = reset_occupancy_interval
204
231
  self._alert_config = G90AlertConfig(self)
205
232
  self._sms_alert_when_armed = False
@@ -322,6 +349,23 @@ class G90Alarm(G90NotificationProtocol):
322
349
  """
323
350
  return await self._sensors.find(idx, name, exclude_unavailable)
324
351
 
352
+ async def register_sensor(
353
+ self, definition_name: str, name: Optional[str] = None,
354
+ timeout: float = DEVICE_REGISTRATION_TIMEOUT
355
+ ) -> G90Sensor:
356
+ """
357
+ Registers the sensor with the panel.
358
+
359
+ :param definition_name: Name of the sensor definition to register
360
+ :param name: Optional name of the sensor to register, if not provided
361
+ the name will be taken from the definition
362
+ :param timeout: Timeout for the registration process, in seconds
363
+ :return: Sensor instance
364
+ """
365
+ return await self._sensors.register(
366
+ definition_name, ROOM_ID, timeout, name
367
+ )
368
+
325
369
  @property
326
370
  async def devices(self) -> List[G90Device]:
327
371
  """
@@ -344,6 +388,37 @@ class G90Alarm(G90NotificationProtocol):
344
388
  """
345
389
  return await self._devices.update()
346
390
 
391
+ async def find_device(
392
+ self, idx: int, name: str, exclude_unavailable: bool = True
393
+ ) -> Optional[G90Device]:
394
+ """
395
+ Finds device by index and name.
396
+
397
+ :param idx: Device index
398
+ :param name: Device name
399
+ :param exclude_unavailable: Flag indicating if unavailable devices
400
+ should be excluded from the search
401
+ :return: Device instance
402
+ """
403
+ return await self._devices.find(idx, name, exclude_unavailable)
404
+
405
+ async def register_device(
406
+ self, definition_name: str, name: Optional[str] = None,
407
+ timeout: float = DEVICE_REGISTRATION_TIMEOUT
408
+ ) -> G90Device:
409
+ """
410
+ Registers device (relay, switch) with the panel.
411
+
412
+ :param definition_name: Name of the device definition to register
413
+ :param name: Optional name of the device to register, if not provided
414
+ the name will be taken from the definition
415
+ :param timeout: Timeout for the registration process, in seconds
416
+ :return: Device instance
417
+ """
418
+ return await self._devices.register(
419
+ definition_name, ROOM_ID, timeout, name
420
+ )
421
+
347
422
  @property
348
423
  async def host_info(self) -> G90HostInfo:
349
424
  """
@@ -396,7 +471,7 @@ class G90Alarm(G90NotificationProtocol):
396
471
  async def get_alert_config(self) -> G90AlertConfigFlags:
397
472
  """
398
473
  Provides alert configuration flags, retained for compatibility - using
399
- `:attr:alert_config` and `:class:G90AlertConfig` is preferred.
474
+ :attr:`alert_config` and :class:`.G90AlertConfig` is preferred.
400
475
 
401
476
  :return: The alerts configured
402
477
  """
@@ -405,7 +480,7 @@ class G90Alarm(G90NotificationProtocol):
405
480
  async def set_alert_config(self, flags: G90AlertConfigFlags) -> None:
406
481
  """
407
482
  Sets the alert configuration flags, retained for compatibility - using
408
- `:attr:alert_config` and `:class:G90AlertConfig` is preferred.
483
+ :attr:`alert_config` and :class:`.G90AlertConfig` is preferred.
409
484
  """
410
485
  await self.alert_config.set(flags)
411
486
 
@@ -489,7 +564,7 @@ class G90Alarm(G90NotificationProtocol):
489
564
  # notification itself
490
565
  def reset_sensor_occupancy(sensor: G90Sensor) -> None:
491
566
  sensor._set_occupancy(False)
492
- G90Callback.invoke(sensor.state_callback, sensor.occupancy)
567
+ sensor.state_callback.invoke(sensor.occupancy)
493
568
 
494
569
  # Determine if door close notifications are available for the given
495
570
  # sensor
@@ -499,7 +574,7 @@ class G90Alarm(G90NotificationProtocol):
499
574
  # The condition intentionally doesn't account for cord sensors of
500
575
  # subtype door, since those won't send door open/close alerts, only
501
576
  # notifications
502
- sensor_is_door = sensor.type == G90SensorTypes.DOOR
577
+ sensor_is_door = sensor.type == G90PeripheralTypes.DOOR
503
578
 
504
579
  # Alarm panel could emit door close alerts (if enabled) for sensors
505
580
  # of type `door`, and such event will be used to reset the
@@ -519,21 +594,28 @@ class G90Alarm(G90NotificationProtocol):
519
594
  reset_sensor_occupancy, sensor)
520
595
 
521
596
  # Invoke per-sensor callback if provided
522
- G90Callback.invoke(sensor.state_callback, occupancy)
597
+ sensor.state_callback.invoke(occupancy)
523
598
 
524
599
  # Invoke global callback if provided
525
- G90Callback.invoke(self._sensor_cb, idx, name, occupancy)
600
+ self._sensor_cb.invoke(idx, name, occupancy)
526
601
 
527
602
  @property
528
- def sensor_callback(self) -> Optional[SensorCallback]:
603
+ def sensor_callback(self) -> G90CallbackList[SensorCallback]:
529
604
  """
530
605
  Sensor activity callback, which is invoked when sensor activates.
606
+
607
+ Setting the property will add the callback to the list of (retained for
608
+ compatilibity with earlier package versions), or
609
+ :class:`.G90CallbackList` instance could be accessed over the
610
+ property - `G90Alarm(...).sensor_callback.add(callback)` or
611
+ `G90Alarm(...).sensor_callback.remove(callback)` methods could be used
612
+ to add or remove the callback, respectively.
531
613
  """
532
614
  return self._sensor_cb
533
615
 
534
616
  @sensor_callback.setter
535
617
  def sensor_callback(self, value: SensorCallback) -> None:
536
- self._sensor_cb = value
618
+ self._sensor_cb.add(value)
537
619
 
538
620
  async def on_door_open_close(
539
621
  self, event_id: int, zone_name: str, is_open: bool
@@ -552,22 +634,24 @@ class G90Alarm(G90NotificationProtocol):
552
634
  # closed, since the notifications aren't sent for such events
553
635
  await self.on_sensor_activity(event_id, zone_name, is_open)
554
636
  # Invoke user specified callback if any
555
- G90Callback.invoke(
556
- self._door_open_close_cb, event_id, zone_name, is_open
557
- )
637
+ self._door_open_close_cb.invoke(event_id, zone_name, is_open)
558
638
 
559
639
  @property
560
- def door_open_close_callback(self) -> Optional[DoorOpenCloseCallback]:
640
+ def door_open_close_callback(
641
+ self
642
+ ) -> G90CallbackList[DoorOpenCloseCallback]:
561
643
  """
562
644
  The door open/close callback, which is invoked when door
563
645
  is opened or closed (if corresponding alert is configured on the
564
646
  device).
647
+
648
+ .. seealso:: :attr:`.sensor_callback` for compatiblity notes
565
649
  """
566
650
  return self._door_open_close_cb
567
651
 
568
652
  @door_open_close_callback.setter
569
653
  def door_open_close_callback(self, value: DoorOpenCloseCallback) -> None:
570
- self._door_open_close_cb = value
654
+ self._door_open_close_cb.add(value)
571
655
 
572
656
  async def on_armdisarm(self, state: G90ArmDisarmTypes) -> None:
573
657
  """
@@ -596,19 +680,21 @@ class G90Alarm(G90NotificationProtocol):
596
680
  # pylint: disable=protected-access
597
681
  sensor._set_door_open_when_arming(False)
598
682
 
599
- G90Callback.invoke(self._armdisarm_cb, state)
683
+ self._armdisarm_cb.invoke(state)
600
684
 
601
685
  @property
602
- def armdisarm_callback(self) -> Optional[ArmDisarmCallback]:
686
+ def armdisarm_callback(self) -> G90CallbackList[ArmDisarmCallback]:
603
687
  """
604
688
  The device arm/disarm callback, which is invoked when device state
605
689
  changes.
690
+
691
+ .. seealso:: :attr:`.sensor_callback` for compatiblity notes
606
692
  """
607
693
  return self._armdisarm_cb
608
694
 
609
695
  @armdisarm_callback.setter
610
696
  def armdisarm_callback(self, value: ArmDisarmCallback) -> None:
611
- self._armdisarm_cb = value
697
+ self._armdisarm_cb.add(value)
612
698
 
613
699
  async def on_alarm(
614
700
  self, event_id: int, zone_name: str, is_tampered: bool
@@ -643,26 +729,26 @@ class G90Alarm(G90NotificationProtocol):
643
729
  sensor._set_tampered(True)
644
730
 
645
731
  # Invoke per-sensor callback if provided
646
- G90Callback.invoke(sensor.tamper_callback)
732
+ sensor.tamper_callback.invoke()
647
733
 
648
734
  # Invoke global tamper callback if provided and the sensor is tampered
649
735
  if is_tampered:
650
- G90Callback.invoke(self._tamper_cb, event_id, zone_name)
736
+ self._tamper_cb.invoke(event_id, zone_name)
651
737
 
652
- G90Callback.invoke(
653
- self._alarm_cb, event_id, zone_name, extra_data
654
- )
738
+ self._alarm_cb.invoke(event_id, zone_name, extra_data)
655
739
 
656
740
  @property
657
- def alarm_callback(self) -> Optional[AlarmCallback]:
741
+ def alarm_callback(self) -> G90CallbackList[AlarmCallback]:
658
742
  """
659
743
  The device alarm callback, which is invoked when device alarm triggers.
744
+
745
+ .. seealso:: :attr:`.sensor_callback` for compatiblity notes
660
746
  """
661
747
  return self._alarm_cb
662
748
 
663
749
  @alarm_callback.setter
664
750
  def alarm_callback(self, value: AlarmCallback) -> None:
665
- self._alarm_cb = value
751
+ self._alarm_cb.add(value)
666
752
 
667
753
  async def on_low_battery(self, event_id: int, zone_name: str) -> None:
668
754
  """
@@ -682,21 +768,23 @@ class G90Alarm(G90NotificationProtocol):
682
768
  # pylint: disable=protected-access
683
769
  sensor._set_low_battery(True)
684
770
  # Invoke per-sensor callback if provided
685
- G90Callback.invoke(sensor.low_battery_callback)
771
+ sensor.low_battery_callback.invoke()
686
772
 
687
- G90Callback.invoke(self._low_battery_cb, event_id, zone_name)
773
+ self._low_battery_cb.invoke(event_id, zone_name)
688
774
 
689
775
  @property
690
- def low_battery_callback(self) -> Optional[LowBatteryCallback]:
776
+ def low_battery_callback(self) -> G90CallbackList[LowBatteryCallback]:
691
777
  """
692
778
  Low battery callback, which is invoked when sensor reports the
693
779
  condition.
780
+
781
+ .. seealso:: :attr:`.sensor_callback` for compatiblity notes
694
782
  """
695
783
  return self._low_battery_cb
696
784
 
697
785
  @low_battery_callback.setter
698
786
  def low_battery_callback(self, value: LowBatteryCallback) -> None:
699
- self._low_battery_cb = value
787
+ self._low_battery_cb.add(value)
700
788
 
701
789
  async def on_sos(
702
790
  self, event_id: int, zone_name: str, is_host_sos: bool
@@ -714,7 +802,7 @@ class G90Alarm(G90NotificationProtocol):
714
802
  (host)
715
803
  """
716
804
  _LOGGER.debug('on_sos: %s %s %s', event_id, zone_name, is_host_sos)
717
- G90Callback.invoke(self._sos_cb, event_id, zone_name, is_host_sos)
805
+ self._sos_cb.invoke(event_id, zone_name, is_host_sos)
718
806
 
719
807
  # Also report the event as alarm for unification, hard-coding the
720
808
  # sensor name in case of host SOS
@@ -731,15 +819,17 @@ class G90Alarm(G90NotificationProtocol):
731
819
  )
732
820
 
733
821
  @property
734
- def sos_callback(self) -> Optional[SosCallback]:
822
+ def sos_callback(self) -> G90CallbackList[SosCallback]:
735
823
  """
736
824
  SOS callback, which is invoked when SOS alert is triggered.
825
+
826
+ .. seealso:: :attr:`.sensor_callback` for compatiblity notes
737
827
  """
738
828
  return self._sos_cb
739
829
 
740
830
  @sos_callback.setter
741
831
  def sos_callback(self, value: SosCallback) -> None:
742
- self._sos_cb = value
832
+ self._sos_cb.add(value)
743
833
 
744
834
  async def on_remote_button_press(
745
835
  self, event_id: int, zone_name: str, button: G90RemoteButtonStates
@@ -757,9 +847,7 @@ class G90Alarm(G90NotificationProtocol):
757
847
  _LOGGER.debug(
758
848
  'on_remote_button_press: %s %s %s', event_id, zone_name, button
759
849
  )
760
- G90Callback.invoke(
761
- self._remote_button_press_cb, event_id, zone_name, button
762
- )
850
+ self._remote_button_press_cb.invoke(event_id, zone_name, button)
763
851
 
764
852
  # Also report the event as sensor activity for unification (remote is
765
853
  # just a special type of the sensor)
@@ -768,10 +856,12 @@ class G90Alarm(G90NotificationProtocol):
768
856
  @property
769
857
  def remote_button_press_callback(
770
858
  self
771
- ) -> Optional[RemoteButtonPressCallback]:
859
+ ) -> G90CallbackList[RemoteButtonPressCallback]:
772
860
  """
773
861
  Remote button press callback, which is invoked when remote button is
774
862
  pressed.
863
+
864
+ .. seealso:: :attr:`.sensor_callback` for compatiblity notes
775
865
  """
776
866
  return self._remote_button_press_cb
777
867
 
@@ -779,7 +869,7 @@ class G90Alarm(G90NotificationProtocol):
779
869
  def remote_button_press_callback(
780
870
  self, value: RemoteButtonPressCallback
781
871
  ) -> None:
782
- self._remote_button_press_cb = value
872
+ self._remote_button_press_cb.add(value)
783
873
 
784
874
  async def on_door_open_when_arming(
785
875
  self, event_id: int, zone_name: str
@@ -802,17 +892,19 @@ class G90Alarm(G90NotificationProtocol):
802
892
  # pylint: disable=protected-access
803
893
  sensor._set_door_open_when_arming(True)
804
894
  # Invoke per-sensor callback if provided
805
- G90Callback.invoke(sensor.door_open_when_arming_callback)
895
+ sensor.door_open_when_arming_callback.invoke()
806
896
 
807
- G90Callback.invoke(self._door_open_when_arming_cb, event_id, zone_name)
897
+ self._door_open_when_arming_cb.invoke(event_id, zone_name)
808
898
 
809
899
  @property
810
900
  def door_open_when_arming_callback(
811
901
  self
812
- ) -> Optional[DoorOpenWhenArmingCallback]:
902
+ ) -> G90CallbackList[DoorOpenWhenArmingCallback]:
813
903
  """
814
904
  Door open when arming callback, which is invoked when sensor reports
815
905
  the condition.
906
+
907
+ .. seealso:: :attr:`.sensor_callback` for compatiblity notes
816
908
  """
817
909
  return self._door_open_when_arming_cb
818
910
 
@@ -820,10 +912,10 @@ class G90Alarm(G90NotificationProtocol):
820
912
  def door_open_when_arming_callback(
821
913
  self, value: DoorOpenWhenArmingCallback
822
914
  ) -> None:
823
- self._door_open_when_arming_cb = value
915
+ self._door_open_when_arming_cb.add(value)
824
916
 
825
917
  @property
826
- async def tamper_callback(self) -> Optional[TamperCallback]:
918
+ def tamper_callback(self) -> G90CallbackList[TamperCallback]:
827
919
  """
828
920
  Tamper callback, which is invoked when sensor reports the condition.
829
921
  """
@@ -831,7 +923,109 @@ class G90Alarm(G90NotificationProtocol):
831
923
 
832
924
  @tamper_callback.setter
833
925
  def tamper_callback(self, value: TamperCallback) -> None:
834
- self._tamper_cb = value
926
+ self._tamper_cb.add(value)
927
+
928
+ async def on_sensor_change(
929
+ self, sensor_idx: int, sensor_name: str, added: bool
930
+ ) -> None:
931
+ """
932
+ Invoked when sensor is added or removed from the device.
933
+
934
+ There is no user-visible callback assoiciated with this method, those
935
+ will be handled by `on_sensor_list_change()` method.
936
+
937
+ Please note the method is for internal use by the class.
938
+
939
+ :param sensor_idx: The index of the sensor being added/removed.
940
+ :param sensor_name: The name of the sensor.
941
+ :param added: Flag indicating if the sensor is added or removed
942
+ """
943
+ _LOGGER.debug(
944
+ 'on_sensor_change: idx=%s name=%s added=%s',
945
+ sensor_idx, sensor_name, added
946
+ )
947
+
948
+ # Invoke internal callback for sensor list to finish the registration
949
+ # process
950
+ G90Callback.invoke(
951
+ self._sensors.sensor_change_callback,
952
+ sensor_idx, sensor_name, added
953
+ )
954
+
955
+ @property
956
+ def sensor_list_change_callback(
957
+ self
958
+ ) -> G90CallbackList[ListChangeCallback[G90Sensor]]:
959
+ """
960
+ Sensor list change callback, which is invoked when sensor list
961
+ changes.
962
+
963
+ .. seealso:: :attr:`.sensor_callback` for compatiblity notes
964
+ """
965
+ return self._sensor_list_change_cb
966
+
967
+ @sensor_list_change_callback.setter
968
+ def sensor_list_change_callback(
969
+ self, value: ListChangeCallback[G90Sensor]
970
+ ) -> None:
971
+ self._sensor_list_change_cb.add(value)
972
+
973
+ async def on_sensor_list_change(
974
+ self, sensor: G90Sensor, added: bool
975
+ ) -> None:
976
+ """
977
+ Invoked when sensor list is changed.
978
+
979
+ Fires corresponding callback if set by the user with
980
+ :attr:`.sensor_list_change_callback`.
981
+ Please note the method is for internal use by the class.
982
+
983
+ :param sensor: The sensor being added or removed
984
+ :param added: Flag indicating if the sensor is added or removed
985
+ """
986
+ _LOGGER.debug(
987
+ 'on_sensor_list_change: %s added=%s', repr(sensor), added
988
+ )
989
+
990
+ self._sensor_list_change_cb.invoke(sensor, added)
991
+
992
+ @property
993
+ def device_list_change_callback(
994
+ self
995
+ ) -> G90CallbackList[ListChangeCallback[G90Device]]:
996
+ """
997
+ Device list change callback, which is invoked when device list
998
+ changes.
999
+
1000
+ .. seealso:: :attr:`.sensor_callback` for compatiblity notes
1001
+ """
1002
+ return self._device_list_change_cb
1003
+
1004
+ @device_list_change_callback.setter
1005
+ def device_list_change_callback(
1006
+ self, value: ListChangeCallback[G90Device]
1007
+ ) -> None:
1008
+ self._device_list_change_cb.add(value)
1009
+
1010
+ async def on_device_list_change(
1011
+ self, device: G90Device, added: bool
1012
+ ) -> None:
1013
+ """
1014
+ Invoked when device list is changed.
1015
+
1016
+ Fires corresponding callback if set by the user with
1017
+ :attr:`.device_list_change_callback`.
1018
+
1019
+ Please note the method is for internal use by the class.
1020
+
1021
+ :param device: The device being added or removed
1022
+ :param added: Flag indicating if the device is added or removed
1023
+ """
1024
+ _LOGGER.debug(
1025
+ 'on_device_list_change: %s added=%s', repr(device), added
1026
+ )
1027
+
1028
+ self._device_list_change_cb.invoke(device, added)
835
1029
 
836
1030
  async def listen_notifications(self) -> None:
837
1031
  """
pyg90alarm/callback.py CHANGED
@@ -25,17 +25,20 @@ from __future__ import annotations
25
25
  import asyncio
26
26
  from functools import (partial, wraps)
27
27
  from asyncio import Task
28
- from typing import Any, Callable, Coroutine, cast, Optional, Union
28
+ from typing import (
29
+ Any, Callable, Coroutine, cast, Optional, Union, TypeVar, Generic
30
+ )
29
31
  import logging
30
32
 
31
- _LOGGER = logging.getLogger(__name__)
32
-
33
33
  Callback = Optional[
34
34
  Union[
35
35
  Callable[..., None],
36
36
  Callable[..., Coroutine[None, None, None]],
37
37
  ]
38
38
  ]
39
+ T = TypeVar('T', bound=Callback)
40
+
41
+ _LOGGER = logging.getLogger(__name__)
39
42
 
40
43
 
41
44
  class G90Callback:
@@ -106,3 +109,38 @@ class G90Callback:
106
109
  """
107
110
  loop = asyncio.get_running_loop()
108
111
  loop.call_later(delay, partial(callback, *args, **kwargs))
112
+
113
+
114
+ class G90CallbackList(Generic[T]):
115
+ """
116
+ Implements a list of callbacks.
117
+ """
118
+ def __init__(self) -> None:
119
+ self._callbacks: list[T] = []
120
+
121
+ def add(self, callback: T) -> None:
122
+ """
123
+ Adds a callback to the list.
124
+ """
125
+ if callback and callback not in self._callbacks:
126
+ self._callbacks.append(callback)
127
+
128
+ def remove(self, callback: T) -> None:
129
+ """
130
+ Removes a callback from the list.
131
+ """
132
+ if callback in self._callbacks:
133
+ self._callbacks.remove(callback)
134
+
135
+ def clear(self) -> None:
136
+ """
137
+ Clears the list of callbacks.
138
+ """
139
+ self._callbacks.clear()
140
+
141
+ def invoke(self, *args: Any, **kwargs: Any) -> None:
142
+ """
143
+ Invokes all callbacks in the list.
144
+ """
145
+ for callback in self._callbacks:
146
+ G90Callback.invoke(callback, *args, **kwargs)