pyg90alarm 2.3.1__tar.gz → 2.4.0__tar.gz

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 (88) hide show
  1. {pyg90alarm-2.3.1 → pyg90alarm-2.4.0}/.github/workflows/main.yml +1 -1
  2. {pyg90alarm-2.3.1 → pyg90alarm-2.4.0}/PKG-INFO +1 -1
  3. {pyg90alarm-2.3.1 → pyg90alarm-2.4.0}/docs/requirements.txt +2 -1
  4. {pyg90alarm-2.3.1 → pyg90alarm-2.4.0}/src/pyg90alarm/__init__.py +1 -1
  5. {pyg90alarm-2.3.1 → pyg90alarm-2.4.0}/src/pyg90alarm/alarm.py +105 -17
  6. {pyg90alarm-2.3.1 → pyg90alarm-2.4.0}/src/pyg90alarm/const.py +53 -1
  7. pyg90alarm-2.4.0/src/pyg90alarm/event_mapping.py +99 -0
  8. pyg90alarm-2.4.0/src/pyg90alarm/local/alarm_phones.py +77 -0
  9. pyg90alarm-2.3.1/src/pyg90alarm/local/config.py → pyg90alarm-2.4.0/src/pyg90alarm/local/alert_config.py +34 -1
  10. pyg90alarm-2.4.0/src/pyg90alarm/local/config.py +32 -0
  11. pyg90alarm-2.4.0/src/pyg90alarm/local/dataclass_load_save.py +131 -0
  12. {pyg90alarm-2.3.1 → pyg90alarm-2.4.0}/src/pyg90alarm/local/history.py +55 -13
  13. pyg90alarm-2.4.0/src/pyg90alarm/local/host_config.py +167 -0
  14. pyg90alarm-2.4.0/src/pyg90alarm/local/net_config.py +127 -0
  15. {pyg90alarm-2.3.1 → pyg90alarm-2.4.0}/src/pyg90alarm/notifications/base.py +18 -1
  16. {pyg90alarm-2.3.1 → pyg90alarm-2.4.0}/src/pyg90alarm/notifications/protocol.py +12 -0
  17. {pyg90alarm-2.3.1 → pyg90alarm-2.4.0}/src/pyg90alarm.egg-info/PKG-INFO +1 -1
  18. {pyg90alarm-2.3.1 → pyg90alarm-2.4.0}/src/pyg90alarm.egg-info/SOURCES.txt +9 -0
  19. {pyg90alarm-2.3.1 → pyg90alarm-2.4.0}/tests/test_alarm.py +174 -27
  20. pyg90alarm-2.4.0/tests/test_alarm_phones.py +51 -0
  21. {pyg90alarm-2.3.1 → pyg90alarm-2.4.0}/tests/test_history.py +32 -5
  22. pyg90alarm-2.4.0/tests/test_host_config.py +54 -0
  23. pyg90alarm-2.4.0/tests/test_net_config.py +52 -0
  24. {pyg90alarm-2.3.1 → pyg90alarm-2.4.0}/.github/CODEOWNERS +0 -0
  25. {pyg90alarm-2.3.1 → pyg90alarm-2.4.0}/.github/dependabot.yml +0 -0
  26. {pyg90alarm-2.3.1 → pyg90alarm-2.4.0}/.gitignore +0 -0
  27. {pyg90alarm-2.3.1 → pyg90alarm-2.4.0}/.pylintrc +0 -0
  28. {pyg90alarm-2.3.1 → pyg90alarm-2.4.0}/.readthedocs.yaml +0 -0
  29. {pyg90alarm-2.3.1 → pyg90alarm-2.4.0}/LICENSE +0 -0
  30. {pyg90alarm-2.3.1 → pyg90alarm-2.4.0}/MANIFEST.in +0 -0
  31. {pyg90alarm-2.3.1 → pyg90alarm-2.4.0}/README.rst +0 -0
  32. {pyg90alarm-2.3.1 → pyg90alarm-2.4.0}/docs/.DS_Store +0 -0
  33. {pyg90alarm-2.3.1 → pyg90alarm-2.4.0}/docs/.gitignore +0 -0
  34. {pyg90alarm-2.3.1 → pyg90alarm-2.4.0}/docs/api-docs.rst +0 -0
  35. {pyg90alarm-2.3.1 → pyg90alarm-2.4.0}/docs/cloud-protocol.rst +0 -0
  36. {pyg90alarm-2.3.1 → pyg90alarm-2.4.0}/docs/conf.py +0 -0
  37. {pyg90alarm-2.3.1 → pyg90alarm-2.4.0}/docs/index.rst +0 -0
  38. {pyg90alarm-2.3.1 → pyg90alarm-2.4.0}/docs/local-protocol.rst +0 -0
  39. {pyg90alarm-2.3.1 → pyg90alarm-2.4.0}/pyproject.toml +0 -0
  40. {pyg90alarm-2.3.1 → pyg90alarm-2.4.0}/setup.cfg +0 -0
  41. {pyg90alarm-2.3.1 → pyg90alarm-2.4.0}/setup.py +0 -0
  42. {pyg90alarm-2.3.1 → pyg90alarm-2.4.0}/sonar-project.properties +0 -0
  43. {pyg90alarm-2.3.1 → pyg90alarm-2.4.0}/src/pyg90alarm/callback.py +0 -0
  44. {pyg90alarm-2.3.1 → pyg90alarm-2.4.0}/src/pyg90alarm/cloud/__init__.py +0 -0
  45. {pyg90alarm-2.3.1 → pyg90alarm-2.4.0}/src/pyg90alarm/cloud/const.py +0 -0
  46. {pyg90alarm-2.3.1 → pyg90alarm-2.4.0}/src/pyg90alarm/cloud/messages.py +0 -0
  47. {pyg90alarm-2.3.1 → pyg90alarm-2.4.0}/src/pyg90alarm/cloud/notifications.py +0 -0
  48. {pyg90alarm-2.3.1 → pyg90alarm-2.4.0}/src/pyg90alarm/cloud/protocol.py +0 -0
  49. {pyg90alarm-2.3.1 → pyg90alarm-2.4.0}/src/pyg90alarm/definitions/__init__.py +0 -0
  50. {pyg90alarm-2.3.1 → pyg90alarm-2.4.0}/src/pyg90alarm/definitions/base.py +0 -0
  51. {pyg90alarm-2.3.1 → pyg90alarm-2.4.0}/src/pyg90alarm/definitions/devices.py +0 -0
  52. {pyg90alarm-2.3.1 → pyg90alarm-2.4.0}/src/pyg90alarm/definitions/sensors.py +0 -0
  53. {pyg90alarm-2.3.1 → pyg90alarm-2.4.0}/src/pyg90alarm/entities/__init__.py +0 -0
  54. {pyg90alarm-2.3.1 → pyg90alarm-2.4.0}/src/pyg90alarm/entities/base_entity.py +0 -0
  55. {pyg90alarm-2.3.1 → pyg90alarm-2.4.0}/src/pyg90alarm/entities/base_list.py +0 -0
  56. {pyg90alarm-2.3.1 → pyg90alarm-2.4.0}/src/pyg90alarm/entities/device.py +0 -0
  57. {pyg90alarm-2.3.1 → pyg90alarm-2.4.0}/src/pyg90alarm/entities/device_list.py +0 -0
  58. {pyg90alarm-2.3.1 → pyg90alarm-2.4.0}/src/pyg90alarm/entities/sensor.py +0 -0
  59. {pyg90alarm-2.3.1 → pyg90alarm-2.4.0}/src/pyg90alarm/entities/sensor_list.py +0 -0
  60. {pyg90alarm-2.3.1 → pyg90alarm-2.4.0}/src/pyg90alarm/exceptions.py +0 -0
  61. {pyg90alarm-2.3.1 → pyg90alarm-2.4.0}/src/pyg90alarm/local/__init__.py +0 -0
  62. {pyg90alarm-2.3.1 → pyg90alarm-2.4.0}/src/pyg90alarm/local/base_cmd.py +0 -0
  63. {pyg90alarm-2.3.1 → pyg90alarm-2.4.0}/src/pyg90alarm/local/discovery.py +0 -0
  64. {pyg90alarm-2.3.1 → pyg90alarm-2.4.0}/src/pyg90alarm/local/host_info.py +0 -0
  65. {pyg90alarm-2.3.1 → pyg90alarm-2.4.0}/src/pyg90alarm/local/host_status.py +0 -0
  66. {pyg90alarm-2.3.1 → pyg90alarm-2.4.0}/src/pyg90alarm/local/notifications.py +0 -0
  67. {pyg90alarm-2.3.1 → pyg90alarm-2.4.0}/src/pyg90alarm/local/paginated_cmd.py +0 -0
  68. {pyg90alarm-2.3.1 → pyg90alarm-2.4.0}/src/pyg90alarm/local/paginated_result.py +0 -0
  69. {pyg90alarm-2.3.1 → pyg90alarm-2.4.0}/src/pyg90alarm/local/targeted_discovery.py +0 -0
  70. {pyg90alarm-2.3.1 → pyg90alarm-2.4.0}/src/pyg90alarm/local/user_data_crc.py +0 -0
  71. {pyg90alarm-2.3.1 → pyg90alarm-2.4.0}/src/pyg90alarm/notifications/__init__.py +0 -0
  72. {pyg90alarm-2.3.1 → pyg90alarm-2.4.0}/src/pyg90alarm/py.typed +0 -0
  73. {pyg90alarm-2.3.1 → pyg90alarm-2.4.0}/src/pyg90alarm.egg-info/dependency_links.txt +0 -0
  74. {pyg90alarm-2.3.1 → pyg90alarm-2.4.0}/src/pyg90alarm.egg-info/requires.txt +0 -0
  75. {pyg90alarm-2.3.1 → pyg90alarm-2.4.0}/src/pyg90alarm.egg-info/top_level.txt +0 -0
  76. {pyg90alarm-2.3.1 → pyg90alarm-2.4.0}/tests/__init__.py +0 -0
  77. {pyg90alarm-2.3.1 → pyg90alarm-2.4.0}/tests/conftest.py +0 -0
  78. {pyg90alarm-2.3.1 → pyg90alarm-2.4.0}/tests/device_mock.py +0 -0
  79. {pyg90alarm-2.3.1 → pyg90alarm-2.4.0}/tests/test_base_commands.py +0 -0
  80. {pyg90alarm-2.3.1 → pyg90alarm-2.4.0}/tests/test_cloud_notifications.py +0 -0
  81. {pyg90alarm-2.3.1 → pyg90alarm-2.4.0}/tests/test_config.py +0 -0
  82. {pyg90alarm-2.3.1 → pyg90alarm-2.4.0}/tests/test_devices.py +0 -0
  83. {pyg90alarm-2.3.1 → pyg90alarm-2.4.0}/tests/test_discovery.py +0 -0
  84. {pyg90alarm-2.3.1 → pyg90alarm-2.4.0}/tests/test_local_notifications.py +0 -0
  85. {pyg90alarm-2.3.1 → pyg90alarm-2.4.0}/tests/test_paginated_commands.py +0 -0
  86. {pyg90alarm-2.3.1 → pyg90alarm-2.4.0}/tests/test_sensor.py +0 -0
  87. {pyg90alarm-2.3.1 → pyg90alarm-2.4.0}/tests/unit/entities/test_base_list.py +0 -0
  88. {pyg90alarm-2.3.1 → pyg90alarm-2.4.0}/tox.ini +0 -0
@@ -56,7 +56,7 @@ jobs:
56
56
  package_version=`python3 setup.py --version`
57
57
  echo "VALUE=$package_version" >> $GITHUB_OUTPUT
58
58
  - name: SonarCloud scanning
59
- uses: SonarSource/sonarqube-scan-action@v6
59
+ uses: SonarSource/sonarqube-scan-action@v7
60
60
  env:
61
61
  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
62
62
  SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyg90alarm
3
- Version: 2.3.1
3
+ Version: 2.4.0
4
4
  Summary: G90 Alarm system protocol
5
5
  Home-page: https://github.com/hostcc/pyg90alarm
6
6
  Author: Ilia Sotnikov
@@ -1,6 +1,7 @@
1
1
  sphinx-rtd-theme
2
2
  enum-tools[sphinx]
3
- sphinx-autodoc-typehints==2.3.0
3
+ sphinx-autodoc-typehints==2.3.0; python_version < '3.11'
4
+ sphinx-autodoc-typehints==3.6.2; python_version >= '3.11'
4
5
  pygments>=2.15.0 # not directly required, pinned by Snyk to avoid a vulnerability
5
6
  sphinx>=3.3.0 # not directly required, pinned by Snyk to avoid a vulnerability
6
7
  setuptools>=65.5.1 # not directly required, pinned by Snyk to avoid a vulnerability
@@ -44,7 +44,7 @@ from .definitions.devices import (
44
44
  from .definitions.base import (
45
45
  G90PeripheralTypes,
46
46
  )
47
- from .local.config import G90AlertConfigFlags
47
+ from .local.alert_config import G90AlertConfigFlags
48
48
  from .local.host_status import G90HostStatus
49
49
  from .const import (
50
50
  G90MessageTypes,
@@ -73,6 +73,7 @@ from .const import (
73
73
  ROOM_ID,
74
74
  G90ArmDisarmTypes,
75
75
  G90RemoteButtonStates,
76
+ G90RFIDKeypadStates,
76
77
  )
77
78
  from .local.base_cmd import (G90BaseCommand, G90BaseCommandData)
78
79
  from .local.paginated_result import G90PaginatedResult, G90PaginatedResponse
@@ -95,9 +96,12 @@ from .local.targeted_discovery import (
95
96
  )
96
97
  from .local.host_info import G90HostInfo
97
98
  from .local.host_status import G90HostStatus
98
- from .local.config import (G90AlertConfig, G90AlertConfigFlags)
99
+ from .local.alert_config import (G90AlertConfig, G90AlertConfigFlags)
99
100
  from .local.history import G90History
100
101
  from .local.user_data_crc import G90UserDataCRC
102
+ from .local.alarm_phones import G90AlarmPhones
103
+ from .local.host_config import G90HostConfig
104
+ from .local.net_config import G90NetConfig
101
105
  from .callback import G90Callback, G90CallbackList
102
106
  from .exceptions import G90Error, G90TimeoutError
103
107
  from .cloud.notifications import G90CloudNotifications
@@ -146,6 +150,10 @@ if TYPE_CHECKING:
146
150
  Callable[[int, str], None],
147
151
  Callable[[int, str], Coroutine[None, None, None]]
148
152
  ]
153
+ RFIDKeypadCallback = Union[
154
+ Callable[[int, str, G90RFIDKeypadStates], None],
155
+ Callable[[int, str, G90RFIDKeypadStates], Coroutine[None, None, None]]
156
+ ]
149
157
  # Sensor-related callbacks for `G90Sensor` class - despite that class
150
158
  # stores them, the invocation is done by the `G90Alarm` class hence these
151
159
  # are defined here
@@ -217,6 +225,9 @@ class G90Alarm(G90NotificationProtocol):
217
225
  self._remote_button_press_cb: G90CallbackList[
218
226
  RemoteButtonPressCallback
219
227
  ] = G90CallbackList()
228
+ self._rfid_keypad_cb: G90CallbackList[
229
+ RFIDKeypadCallback
230
+ ] = G90CallbackList()
220
231
  self._door_open_when_arming_cb: G90CallbackList[
221
232
  DoorOpenWhenArmingCallback
222
233
  ] = G90CallbackList()
@@ -484,6 +495,30 @@ class G90Alarm(G90NotificationProtocol):
484
495
  """
485
496
  await self.alert_config.set(flags)
486
497
 
498
+ async def alarm_phones(self) -> G90AlarmPhones:
499
+ """
500
+ Provides access to alarm panel phone numbers.
501
+
502
+ :return: Alarm panel phone numbers
503
+ """
504
+ return await G90AlarmPhones.load(parent=self)
505
+
506
+ async def host_config(self) -> G90HostConfig:
507
+ """
508
+ Provides access to alarm panel configuration.
509
+
510
+ :return: Alarm panel configuration
511
+ """
512
+ return await G90HostConfig.load(parent=self)
513
+
514
+ async def net_config(self) -> G90NetConfig:
515
+ """
516
+ Provides access to alarm panel network configuration.
517
+
518
+ :return: Alarm panel network configuration
519
+ """
520
+ return await G90NetConfig.load(parent=self)
521
+
487
522
  @property
488
523
  async def user_data_crc(self) -> G90UserDataCRC:
489
524
  """
@@ -566,12 +601,18 @@ class G90Alarm(G90NotificationProtocol):
566
601
  sensor._set_occupancy(False)
567
602
  sensor.state_callback.invoke(sensor.occupancy)
568
603
 
569
- alert_config_flags = await self.alert_config.flags
570
- # Determine if door close notifications are available for the given
571
- # sensor
572
- door_close_alert_enabled = (
573
- G90AlertConfigFlags.DOOR_CLOSE in alert_config_flags
574
- )
604
+ # Determine if door close notifications are available for the
605
+ # given sensor
606
+ alert_config_flags = await self.alert_config.flags_with_fallback
607
+ if alert_config_flags is None:
608
+ # No alert configuration available, assume door close alerts
609
+ # are disabled
610
+ door_close_alert_enabled = False
611
+ else:
612
+ door_close_alert_enabled = (
613
+ G90AlertConfigFlags.DOOR_CLOSE in alert_config_flags
614
+ )
615
+
575
616
  # The condition intentionally doesn't account for cord sensors of
576
617
  # subtype door, since those won't send door open/close alerts, only
577
618
  # notifications
@@ -588,7 +629,7 @@ class G90Alarm(G90NotificationProtocol):
588
629
  ' closing event will be emulated upon'
589
630
  ' %s seconds',
590
631
  name, sensor.type,
591
- alert_config_flags,
632
+ alert_config_flags or 'N/A',
592
633
  self._reset_occupancy_interval)
593
634
  G90Callback.invoke_delayed(
594
635
  self._reset_occupancy_interval,
@@ -646,7 +687,7 @@ class G90Alarm(G90NotificationProtocol):
646
687
  is opened or closed (if corresponding alert is configured on the
647
688
  device).
648
689
 
649
- .. seealso:: :attr:`.sensor_callback` for compatiblity notes
690
+ .. seealso:: :attr:`.sensor_callback` for compatibility notes
650
691
  """
651
692
  return self._door_open_close_cb
652
693
 
@@ -689,7 +730,7 @@ class G90Alarm(G90NotificationProtocol):
689
730
  The device arm/disarm callback, which is invoked when device state
690
731
  changes.
691
732
 
692
- .. seealso:: :attr:`.sensor_callback` for compatiblity notes
733
+ .. seealso:: :attr:`.sensor_callback` for compatibility notes
693
734
  """
694
735
  return self._armdisarm_cb
695
736
 
@@ -743,7 +784,7 @@ class G90Alarm(G90NotificationProtocol):
743
784
  """
744
785
  The device alarm callback, which is invoked when device alarm triggers.
745
786
 
746
- .. seealso:: :attr:`.sensor_callback` for compatiblity notes
787
+ .. seealso:: :attr:`.sensor_callback` for compatibility notes
747
788
  """
748
789
  return self._alarm_cb
749
790
 
@@ -779,7 +820,7 @@ class G90Alarm(G90NotificationProtocol):
779
820
  Low battery callback, which is invoked when sensor reports the
780
821
  condition.
781
822
 
782
- .. seealso:: :attr:`.sensor_callback` for compatiblity notes
823
+ .. seealso:: :attr:`.sensor_callback` for compatibility notes
783
824
  """
784
825
  return self._low_battery_cb
785
826
 
@@ -824,7 +865,7 @@ class G90Alarm(G90NotificationProtocol):
824
865
  """
825
866
  SOS callback, which is invoked when SOS alert is triggered.
826
867
 
827
- .. seealso:: :attr:`.sensor_callback` for compatiblity notes
868
+ .. seealso:: :attr:`.sensor_callback` for compatibility notes
828
869
  """
829
870
  return self._sos_cb
830
871
 
@@ -862,7 +903,7 @@ class G90Alarm(G90NotificationProtocol):
862
903
  Remote button press callback, which is invoked when remote button is
863
904
  pressed.
864
905
 
865
- .. seealso:: :attr:`.sensor_callback` for compatiblity notes
906
+ .. seealso:: :attr:`.sensor_callback` for compatibility notes
866
907
  """
867
908
  return self._remote_button_press_cb
868
909
 
@@ -872,6 +913,53 @@ class G90Alarm(G90NotificationProtocol):
872
913
  ) -> None:
873
914
  self._remote_button_press_cb.add(value)
874
915
 
916
+ async def on_rfid_keypad(
917
+ self, event_id: int, zone_name: str,
918
+ state: G90RFIDKeypadStates
919
+ ) -> None:
920
+ """
921
+ Invoked when RFID keypad event occurs. Fires corresponding callback if
922
+ set by the user with :attr:`.rfid_keypad_callback`.
923
+
924
+ Please note the method is for internal use by the class.
925
+
926
+ :param event_id: Index of the RFID keypad (sensor associated with the
927
+ RFID keypad)
928
+ :param zone_name: Sensor name
929
+ :param state: The RFID keypad state
930
+ """
931
+ _LOGGER.debug(
932
+ 'on_rfid_keypad: %s %s %s', event_id, zone_name, state
933
+ )
934
+ self._rfid_keypad_cb.invoke(event_id, zone_name, state)
935
+
936
+ # Invoke corresponding low battery callback for unification with
937
+ # regular sensors. Note that on_sensor_activity callback is not
938
+ # invoked, since it will reset the low battery flag
939
+ if state == G90RFIDKeypadStates.LOW_BATTERY:
940
+ await self.on_low_battery(event_id, zone_name)
941
+ else:
942
+ # Similar to remote button press, also report the event as sensor
943
+ # activity for unification
944
+ await self.on_sensor_activity(event_id, zone_name, True)
945
+
946
+ @property
947
+ def rfid_keypad_callback(
948
+ self
949
+ ) -> G90CallbackList[RFIDKeypadCallback]:
950
+ """
951
+ RFID keypad callback, which is invoked when RFID keypad event occurs.
952
+
953
+ .. seealso:: :attr:`.sensor_callback` for compatibility notes
954
+ """
955
+ return self._rfid_keypad_cb
956
+
957
+ @rfid_keypad_callback.setter
958
+ def rfid_keypad_callback(
959
+ self, value: RFIDKeypadCallback
960
+ ) -> None:
961
+ self._rfid_keypad_cb.add(value)
962
+
875
963
  async def on_door_open_when_arming(
876
964
  self, event_id: int, zone_name: str
877
965
  ) -> None:
@@ -905,7 +993,7 @@ class G90Alarm(G90NotificationProtocol):
905
993
  Door open when arming callback, which is invoked when sensor reports
906
994
  the condition.
907
995
 
908
- .. seealso:: :attr:`.sensor_callback` for compatiblity notes
996
+ .. seealso:: :attr:`.sensor_callback` for compatibility notes
909
997
  """
910
998
  return self._door_open_when_arming_cb
911
999
 
@@ -961,7 +1049,7 @@ class G90Alarm(G90NotificationProtocol):
961
1049
  Sensor list change callback, which is invoked when sensor list
962
1050
  changes.
963
1051
 
964
- .. seealso:: :attr:`.sensor_callback` for compatiblity notes
1052
+ .. seealso:: :attr:`.sensor_callback` for compatibility notes
965
1053
  """
966
1054
  return self._sensor_list_change_cb
967
1055
 
@@ -998,7 +1086,7 @@ class G90Alarm(G90NotificationProtocol):
998
1086
  Device list change callback, which is invoked when device list
999
1087
  changes.
1000
1088
 
1001
- .. seealso:: :attr:`.sensor_callback` for compatiblity notes
1089
+ .. seealso:: :attr:`.sensor_callback` for compatibility notes
1002
1090
  """
1003
1091
  return self._device_list_change_cb
1004
1092
 
@@ -156,6 +156,7 @@ class G90Commands(IntEnum):
156
156
  DELALLLOCK = 223
157
157
  # Miscellaneous
158
158
  GETAPINFO = 212
159
+ SETAPINFO = 213
159
160
  PINGBYGPRS = 218
160
161
  PING = 219
161
162
 
@@ -209,21 +210,48 @@ class G90AlertSources(IntEnum):
209
210
  DEVICE = 0
210
211
  SENSOR = 1
211
212
  TAMPER = 3
213
+ INFRARED = 8
212
214
  REMOTE = 10
213
215
  RFID = 11
214
216
  DOORBELL = 12
215
217
  FINGERPRINT = 15
216
218
 
217
219
 
220
+ class G90CommonSensorAlertStates(IntEnum):
221
+ """
222
+ Defines possible states of the alert sent by most sensors.
223
+ """
224
+ DOOR_CLOSE = 0
225
+ DOOR_OPEN = 1
226
+ SOS = 2
227
+ TAMPER = 3
228
+ LOW_BATTERY = 4
229
+ ALARM = 254
230
+
231
+
232
+ class G90InfraredAlertStates(IntEnum):
233
+ """
234
+ Defines possible states of the alerts sent by infrared sensors.
235
+ """
236
+ MOTION_DETECTED = 0
237
+ TAMPER = 1
238
+ LOW_BATTERY = 2
239
+
240
+
218
241
  class G90AlertStates(IntEnum):
219
242
  """
220
- Defines possible states of the alert sent by the panel.
243
+ Defines consolidated states of the alert sent by infrared and other
244
+ sensors.
245
+
246
+ By a reason the infrared sensors use different codes for their alert
247
+ states, this enum consolidates them into a single set for unification.
221
248
  """
222
249
  DOOR_CLOSE = 0
223
250
  DOOR_OPEN = 1
224
251
  SOS = 2
225
252
  TAMPER = 3
226
253
  LOW_BATTERY = 4
254
+ MOTION_DETECTED = 5
227
255
  ALARM = 254
228
256
 
229
257
 
@@ -261,6 +289,15 @@ class G90HistoryStates(IntEnum):
261
289
  REMOTE_BUTTON_ARM_HOME = 14
262
290
  REMOTE_BUTTON_DISARM = 15
263
291
  REMOTE_BUTTON_SOS = 16
292
+ RFID_KEY_ARM_AWAY = 17
293
+ RFID_KEY_ARM_HOME = 18
294
+ RFID_KEY_DISARM = 19
295
+ RFID_CARD_0 = 20
296
+ RFID_CARD_1 = 21
297
+ RFID_CARD_2 = 22
298
+ RFID_CARD_3 = 23
299
+ RFID_CARD_4 = 24
300
+ MOTION_DETECTED = 25
264
301
 
265
302
 
266
303
  class G90RemoteButtonStates(IntEnum):
@@ -271,3 +308,18 @@ class G90RemoteButtonStates(IntEnum):
271
308
  ARM_HOME = 1
272
309
  DISARM = 2
273
310
  SOS = 3
311
+
312
+
313
+ class G90RFIDKeypadStates(IntEnum):
314
+ """
315
+ Defines possible states for RFID keypads.
316
+ """
317
+ ARM_AWAY = 0
318
+ ARM_HOME = 1
319
+ DISARM = 2
320
+ LOW_BATTERY = 5
321
+ CARD_0 = 6
322
+ CARD_1 = 7
323
+ CARD_2 = 8
324
+ CARD_3 = 9
325
+ CARD_4 = 10
@@ -0,0 +1,99 @@
1
+ # Copyright (c) 2026 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 mapping of various events.
23
+ """
24
+ from __future__ import annotations
25
+ from typing import Dict, TYPE_CHECKING
26
+ import logging
27
+ from .const import (
28
+ G90AlertSources,
29
+ G90AlertStates,
30
+ G90CommonSensorAlertStates,
31
+ G90InfraredAlertStates,
32
+ )
33
+
34
+ _LOGGER = logging.getLogger(__name__)
35
+
36
+
37
+ def map_alert_state(
38
+ source: G90AlertSources, state: int
39
+ ) -> G90AlertStates:
40
+ """
41
+ Converts alert state of infrared and other sensors to consolidated one.
42
+
43
+ :param source: Source of the alert
44
+ :param state: State code as received from the panel
45
+ :return: Consolidated alert state
46
+ """
47
+ if TYPE_CHECKING:
48
+ # Forward declarations
49
+ key: G90CommonSensorAlertStates | G90InfraredAlertStates
50
+ mapping: Dict[
51
+ G90CommonSensorAlertStates | G90InfraredAlertStates,
52
+ G90AlertStates
53
+ ]
54
+
55
+ # Mapping for common sensors
56
+ mapping = {
57
+ G90CommonSensorAlertStates.DOOR_CLOSE:
58
+ G90AlertStates.DOOR_CLOSE,
59
+ G90CommonSensorAlertStates.DOOR_OPEN:
60
+ G90AlertStates.DOOR_OPEN,
61
+ G90CommonSensorAlertStates.SOS:
62
+ G90AlertStates.SOS,
63
+ G90CommonSensorAlertStates.TAMPER:
64
+ G90AlertStates.TAMPER,
65
+ G90CommonSensorAlertStates.LOW_BATTERY:
66
+ G90AlertStates.LOW_BATTERY,
67
+ G90CommonSensorAlertStates.ALARM:
68
+ G90AlertStates.ALARM,
69
+ }
70
+ key = G90CommonSensorAlertStates(state)
71
+ mapping_kind = 'common'
72
+
73
+ # Mapping for infrared sensors
74
+ if source == G90AlertSources.INFRARED:
75
+ mapping = {
76
+ G90InfraredAlertStates.MOTION_DETECTED:
77
+ G90AlertStates.MOTION_DETECTED,
78
+ G90InfraredAlertStates.TAMPER:
79
+ G90AlertStates.TAMPER,
80
+ G90InfraredAlertStates.LOW_BATTERY:
81
+ G90AlertStates.LOW_BATTERY,
82
+ }
83
+ key = G90InfraredAlertStates(state)
84
+ mapping_kind = 'infrared'
85
+
86
+ try:
87
+ result = mapping[key]
88
+ except KeyError as exc:
89
+ # Raise the error similar to Enum if state is invalid
90
+ raise ValueError(
91
+ f'{state} is not valid for source {source}'
92
+ ) from exc
93
+
94
+ _LOGGER.debug(
95
+ 'Mapped %s sensor alert state %d to consolidated state %s'
96
+ ' for source %s',
97
+ mapping_kind, state, repr(result), repr(source)
98
+ )
99
+ return result
@@ -0,0 +1,77 @@
1
+ # Copyright (c) 2026 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
+ Protocol entity for G90 alarm panel phone numbers.
22
+ """
23
+ from __future__ import annotations
24
+ from typing import Dict, Any
25
+ from dataclasses import dataclass
26
+ from ..const import G90Commands
27
+ from .dataclass_load_save import DataclassLoadSave
28
+
29
+
30
+ @dataclass
31
+ class G90AlarmPhones(DataclassLoadSave):
32
+ """
33
+ Interprets data fields of GETALMPHONE/SETALMPHONE commands.
34
+ """
35
+ # pylint: disable=too-many-instance-attributes
36
+ LOAD_COMMAND = G90Commands.GETALMPHONE
37
+ SAVE_COMMAND = G90Commands.SETALMPHONE
38
+
39
+ # Password to operate the panel via SMS or incoming call.
40
+ panel_password: str
41
+ # Phone number of the alarm panel's SIM card.
42
+ panel_phone_number: str
43
+ # Alarm phone number to be called on alarm.
44
+ # Should be in country code + number format.
45
+ phone_number_1: str
46
+ # Same, but for second alarm phone number.
47
+ phone_number_2: str
48
+ # Same, but for third alarm phone number.
49
+ phone_number_3: str
50
+ # Same, but for fourth alarm phone number.
51
+ phone_number_4: str
52
+ # Same, but for fifth alarm phone number.
53
+ phone_number_5: str
54
+ # Same, but for sixth alarm phone number.
55
+ phone_number_6: str
56
+ # Phone number to send SMS notifications on alarm.
57
+ # Should be in country code + number format.
58
+ sms_push_number_1: str
59
+ # Same, but for second SMS notification phone number.
60
+ sms_push_number_2: str
61
+
62
+ def _asdict(self) -> Dict[str, Any]:
63
+ """
64
+ Returns the dataclass fields as a dictionary, masking sensitive data.
65
+ """
66
+ return {
67
+ 'panel_password': '********',
68
+ 'panel_phone_number': self.panel_phone_number,
69
+ 'phone_number_1': self.phone_number_1,
70
+ 'phone_number_2': self.phone_number_2,
71
+ 'phone_number_3': self.phone_number_3,
72
+ 'phone_number_4': self.phone_number_4,
73
+ 'phone_number_5': self.phone_number_5,
74
+ 'phone_number_6': self.phone_number_6,
75
+ 'sms_push_number_1': self.sms_push_number_1,
76
+ 'sms_push_number_2': self.sms_push_number_2,
77
+ }
@@ -22,10 +22,12 @@
22
22
  Represents various configuration aspects of the alarm panel.
23
23
  """
24
24
  from __future__ import annotations
25
- from typing import TYPE_CHECKING
25
+ from typing import TYPE_CHECKING, Optional
26
26
  import logging
27
27
  from dataclasses import dataclass
28
28
  from enum import IntFlag
29
+
30
+ from pyg90alarm.exceptions import G90Error
29
31
  from ..const import G90Commands
30
32
  if TYPE_CHECKING:
31
33
  from ..alarm import G90Alarm
@@ -80,6 +82,7 @@ class G90AlertConfig:
80
82
  """
81
83
  def __init__(self, parent: G90Alarm) -> None:
82
84
  self.parent = parent
85
+ self._cached_data: Optional[G90AlertConfigData] = None
83
86
 
84
87
  async def _get(self) -> G90AlertConfigData:
85
88
  """
@@ -94,6 +97,10 @@ class G90AlertConfig:
94
97
  'Alert configuration: %s, flags: %s', data,
95
98
  repr(data.flags)
96
99
  )
100
+
101
+ # Cache the retrieved data for `flags_with_fallback` property
102
+ self._cached_data = data
103
+
97
104
  return data
98
105
 
99
106
  async def set(self, flags: G90AlertConfigFlags) -> None:
@@ -155,3 +162,29 @@ class G90AlertConfig:
155
162
  :return: Symbolic names for corresponding flag bits
156
163
  """
157
164
  return (await self._get()).flags
165
+
166
+ @property
167
+ async def flags_with_fallback(self) -> Optional[G90AlertConfigFlags]:
168
+ """
169
+ :return: Symbolic names for corresponding flag bits, falling back to
170
+ cached data if device communication fails
171
+ """
172
+ result = None
173
+
174
+ try:
175
+ result = (await self._get()).flags
176
+ except G90Error as exc:
177
+ _LOGGER.debug(
178
+ 'Retrieving alert config flags resulted in error %s',
179
+ repr(exc)
180
+ )
181
+ if self._cached_data is not None:
182
+ _LOGGER.debug(
183
+ 'Falling back to cached alert configuration flags: %s',
184
+ repr(self._cached_data.flags)
185
+ )
186
+ result = self._cached_data.flags
187
+ else:
188
+ _LOGGER.debug('No cached alert configuration flags available')
189
+
190
+ return result
@@ -0,0 +1,32 @@
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
+ Compatibility module for the alert configuration, which should be imported
22
+ from `local.alert_config` instead.
23
+ """
24
+ from .alert_config import (
25
+ G90AlertConfig, G90AlertConfigData, G90AlertConfigFlags
26
+ )
27
+
28
+ __all__ = [
29
+ 'G90AlertConfig',
30
+ 'G90AlertConfigData',
31
+ 'G90AlertConfigFlags',
32
+ ]