pyg90alarm 2.3.0__py3-none-any.whl → 2.4.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.
@@ -0,0 +1,167 @@
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 config.
22
+ """
23
+ from __future__ import annotations
24
+ from typing import Dict, Any
25
+ from dataclasses import dataclass
26
+ from enum import IntEnum
27
+ from .dataclass_load_save import DataclassLoadSave
28
+ from ..const import G90Commands
29
+
30
+
31
+ class G90SpeechLanguage(IntEnum):
32
+ """
33
+ Supported speech languages.
34
+ """
35
+ ENGLISH_FEMALE = 1
36
+ ENGLISH_MALE = 2
37
+ CHINESE_FEMALE = 3
38
+ CHINESE_MALE = 4
39
+ GERMAN_FEMALE = 5
40
+ GERMAN_MALE = 6
41
+ SPANISH_FEMALE = 7
42
+ SPANISH_MALE = 8
43
+ DUTCH_FEMALE = 9
44
+ DUTCH_MALE = 10
45
+ SWEDISH_FEMALE = 11
46
+ SWEDISH_MALE = 12
47
+ FRENCH_FEMALE = 13
48
+ FRENCH_MALE = 14
49
+ TURKISH_FEMALE = 15
50
+ TURKISH_MALE = 16
51
+ RUSSIAN_FEMALE = 17
52
+ RUSSIAN_MALE = 18
53
+
54
+
55
+ class G90VolumeLevel(IntEnum):
56
+ """
57
+ Supported volume levels.
58
+ """
59
+ MUTE = 0
60
+ LOW = 1
61
+ HIGH = 2
62
+
63
+
64
+ @dataclass
65
+ class G90HostConfig(DataclassLoadSave):
66
+ """
67
+ Interprets data fields of GETHOSTCONFIG/SETHOSTCONFIG commands.
68
+ """
69
+ # pylint: disable=too-many-instance-attributes
70
+ LOAD_COMMAND = G90Commands.GETHOSTCONFIG
71
+ SAVE_COMMAND = G90Commands.SETHOSTCONFIG
72
+
73
+ # Duration of the alarm siren when triggered, in seconds
74
+ alarm_siren_duration: int
75
+ # Delay before arming the panel, in seconds
76
+ arm_delay: int
77
+ # Delay before the alarm is triggered, in seconds
78
+ alarm_delay: int
79
+ # Duration of the backlight, in seconds
80
+ backlight_duration: int
81
+ # Alarm volume level, applies to panel's built-in speaker
82
+ _alarm_volume_level: int
83
+ # Speech volume level
84
+ _speech_volume_level: int
85
+ # Duration of the ring for the incoming call, in seconds
86
+ ring_duration: int
87
+ # Speech language
88
+ _speech_language: int
89
+ # Key tone volume level
90
+ _key_tone_volume_level: int
91
+ # Timezone offset, in minutes
92
+ timezone_offset_m: int
93
+ # Ring volume level for incoming calls
94
+ _ring_volume_level: int
95
+
96
+ @property
97
+ def speech_language(self) -> G90SpeechLanguage:
98
+ """
99
+ Returns the speech language as an enum.
100
+ """
101
+ return G90SpeechLanguage(self._speech_language)
102
+
103
+ @speech_language.setter
104
+ def speech_language(self, value: G90SpeechLanguage) -> None:
105
+ self._speech_language = value.value
106
+
107
+ @property
108
+ def alarm_volume_level(self) -> G90VolumeLevel:
109
+ """
110
+ Returns the alarm volume level as an enum.
111
+ """
112
+ return G90VolumeLevel(self._alarm_volume_level)
113
+
114
+ @alarm_volume_level.setter
115
+ def alarm_volume_level(self, value: G90VolumeLevel) -> None:
116
+ self._alarm_volume_level = value.value
117
+
118
+ @property
119
+ def speech_volume_level(self) -> G90VolumeLevel:
120
+ """
121
+ Returns the speech volume level as an enum.
122
+ """
123
+ return G90VolumeLevel(self._speech_volume_level)
124
+
125
+ @speech_volume_level.setter
126
+ def speech_volume_level(self, value: G90VolumeLevel) -> None:
127
+ self._speech_volume_level = value.value
128
+
129
+ @property
130
+ def key_tone_volume_level(self) -> G90VolumeLevel:
131
+ """
132
+ Returns the key tone volume level as an enum.
133
+ """
134
+ return G90VolumeLevel(self._key_tone_volume_level)
135
+
136
+ @key_tone_volume_level.setter
137
+ def key_tone_volume_level(self, value: G90VolumeLevel) -> None:
138
+ self._key_tone_volume_level = value.value
139
+
140
+ @property
141
+ def ring_volume_level(self) -> G90VolumeLevel:
142
+ """
143
+ Returns the ring volume level as an enum.
144
+ """
145
+ return G90VolumeLevel(self._ring_volume_level)
146
+
147
+ @ring_volume_level.setter
148
+ def ring_volume_level(self, value: G90VolumeLevel) -> None:
149
+ self._ring_volume_level = value.value
150
+
151
+ def _asdict(self) -> Dict[str, Any]:
152
+ """
153
+ Returns the dataclass fields as a dictionary.
154
+ """
155
+ return {
156
+ 'alarm_siren_duration': self.alarm_siren_duration,
157
+ 'arm_delay': self.arm_delay,
158
+ 'alarm_delay': self.alarm_delay,
159
+ 'backlight_duration': self.backlight_duration,
160
+ 'alarm_volume_level': self.alarm_volume_level.name,
161
+ 'speech_volume_level': self.speech_volume_level.name,
162
+ 'ring_duration': self.ring_duration,
163
+ 'speech_language': self.speech_language.name,
164
+ 'key_tone_volume_level': self.key_tone_volume_level.name,
165
+ 'timezone_offset_m': self.timezone_offset_m,
166
+ 'ring_volume_level': self.ring_volume_level.name,
167
+ }
@@ -0,0 +1,127 @@
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
+ Interprets network configuration data fields of GETAPINFO/SETAPINFO commands.
22
+ """
23
+ from __future__ import annotations
24
+ from enum import IntEnum
25
+ from dataclasses import dataclass
26
+ from typing import Any, Dict
27
+ from ..const import G90Commands
28
+ from .dataclass_load_save import DataclassLoadSave
29
+
30
+
31
+ class G90APNAuth(IntEnum):
32
+ """
33
+ Supported APN authentication methods.
34
+ """
35
+ NONE = 0
36
+ PAP = 1
37
+ CHAP = 2
38
+ PAP_OR_CHAP = 3
39
+
40
+
41
+ @dataclass
42
+ class G90NetConfig(DataclassLoadSave):
43
+ """
44
+ Interprets data fields of GETAPINFO/SETAPINFO commands.
45
+ """
46
+ # pylint: disable=too-many-instance-attributes
47
+ LOAD_COMMAND = G90Commands.GETAPINFO
48
+ SAVE_COMMAND = G90Commands.SETAPINFO
49
+
50
+ # Whether the access point is enabled, so that the device can be accessed
51
+ # via WiFi
52
+ _ap_enabled: int
53
+ # Access point password
54
+ ap_password: str
55
+ # Whether WiFi is enabled, so that the device can connect to WiFi network
56
+ _wifi_enabled: int
57
+ # Whether GPRS is enabled, so that the device can connect via cellular
58
+ # network
59
+ _gprs_enabled: int
60
+ # Access Point Name (APN) for GPRS connection, as provided by the cellular
61
+ # operator
62
+ apn_name: str
63
+ # User name for APN authentication, as provided by the cellular operator
64
+ apn_user: str
65
+ # Password for APN authentication, as provided by the cellular operator
66
+ apn_password: str
67
+ # APN authentication method, as provided by the cellular operator
68
+ _apn_auth: int
69
+ # GSM operator code
70
+ gsm_operator: str
71
+
72
+ @property
73
+ def ap_enabled(self) -> bool:
74
+ """
75
+ Returns whether the access point is enabled.
76
+ """
77
+ return bool(self._ap_enabled)
78
+
79
+ @ap_enabled.setter
80
+ def ap_enabled(self, value: bool) -> None:
81
+ self._ap_enabled = int(value)
82
+
83
+ @property
84
+ def wifi_enabled(self) -> bool:
85
+ """
86
+ Returns whether WiFi is enabled.
87
+ """
88
+ return bool(self._wifi_enabled)
89
+
90
+ @wifi_enabled.setter
91
+ def wifi_enabled(self, value: bool) -> None:
92
+ self._wifi_enabled = int(value)
93
+
94
+ @property
95
+ def gprs_enabled(self) -> bool:
96
+ """
97
+ Returns whether GPRS is enabled.
98
+ """
99
+ return bool(self._gprs_enabled)
100
+
101
+ @gprs_enabled.setter
102
+ def gprs_enabled(self, value: bool) -> None:
103
+ self._gprs_enabled = int(value)
104
+
105
+ @property
106
+ def apn_auth(self) -> G90APNAuth:
107
+ """
108
+ Returns the APN authentication method as an enum.
109
+ """
110
+ return G90APNAuth(self._apn_auth)
111
+
112
+ @apn_auth.setter
113
+ def apn_auth(self, value: G90APNAuth) -> None:
114
+ self._apn_auth = value.value
115
+
116
+ def _asdict(self) -> Dict[str, Any]:
117
+ return {
118
+ 'ap_enabled': self.ap_enabled,
119
+ 'ap_password': '********',
120
+ 'wifi_enabled': self.wifi_enabled,
121
+ 'gprs_enabled': self.gprs_enabled,
122
+ 'apn_name': self.apn_name,
123
+ 'apn_user': self.apn_user,
124
+ 'apn_password': '********',
125
+ 'apn_auth': self.apn_auth,
126
+ 'gsm_operator': self.gsm_operator
127
+ }
@@ -40,7 +40,9 @@ from ..const import (
40
40
  G90AlertSources,
41
41
  G90AlertStates,
42
42
  G90RemoteButtonStates,
43
+ G90RFIDKeypadStates,
43
44
  )
45
+ from ..event_mapping import map_alert_state
44
46
  from .protocol import G90NotificationProtocol
45
47
 
46
48
  _LOGGER = logging.getLogger(__name__)
@@ -218,6 +220,17 @@ class G90NotificationsBase:
218
220
 
219
221
  return True
220
222
 
223
+ if alert.source == G90AlertSources.RFID:
224
+ _LOGGER.debug('RFID keypad alert: %s', alert)
225
+ # Invoke the callback specific to RFID keypad events
226
+ G90Callback.invoke(
227
+ self._protocol.on_rfid_keypad,
228
+ alert.event_id, alert.zone_name,
229
+ G90RFIDKeypadStates(alert.state)
230
+ )
231
+
232
+ return True
233
+
221
234
  if alert.state in (
222
235
  G90AlertStates.DOOR_OPEN, G90AlertStates.DOOR_CLOSE
223
236
  ):
@@ -308,7 +321,11 @@ class G90NotificationsBase:
308
321
  )
309
322
  # Regular alarm
310
323
  else:
311
- is_tampered = alert.state == G90AlertStates.TAMPER
324
+ # Determine the state depending on alert source
325
+ alert_state = map_alert_state(
326
+ G90AlertSources(alert.source), alert.state
327
+ )
328
+ is_tampered = alert_state == G90AlertStates.TAMPER
312
329
  _LOGGER.debug(
313
330
  'Alarm: %s, is tampered: %s', alert.zone_name, is_tampered
314
331
  )
@@ -25,6 +25,7 @@ Defines notification protocol for `NotificationBase` class.
25
25
  from ..const import (
26
26
  G90ArmDisarmTypes,
27
27
  G90RemoteButtonStates,
28
+ G90RFIDKeypadStates,
28
29
  )
29
30
 
30
31
 
@@ -125,3 +126,14 @@ class G90NotificationProtocol:
125
126
  :param sensor_name: Name of the sensor.
126
127
  :param added: True if the sensor was added.
127
128
  """
129
+
130
+ async def on_rfid_keypad(
131
+ self, event_id: int, zone_name: str, state: G90RFIDKeypadStates
132
+ ) -> None:
133
+ """
134
+ Invoked when an RFID keypad event occurs.
135
+
136
+ :param event_id: Index of the RFID keypad.
137
+ :param zone_name: Name of the RFID keypad.
138
+ :param state: State of the RFID keypad.
139
+ """
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyg90alarm
3
- Version: 2.3.0
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,7 +1,8 @@
1
- pyg90alarm/__init__.py,sha256=9RyHo4m46owUxAdPMFOkrr0VSd1lXK9QWyXMQlia7tM,3018
2
- pyg90alarm/alarm.py,sha256=Y80AsrC5-s4xfsCnFcVpNPKbIWBZ8yDo_bTCsthL4iM,47033
1
+ pyg90alarm/__init__.py,sha256=_tJhV_LpED_ei4n4pKdu5tTcUCuaj5vPPO1nwuUHY-k,3024
2
+ pyg90alarm/alarm.py,sha256=BDBVQ8qRa2Bx2-786uytShTw8znlFEWakxweCGvgxME,50130
3
3
  pyg90alarm/callback.py,sha256=9PVtjRs2MLn80AgiM-UJNL8ZJF4_PxcopJIpxMmB3vc,4707
4
- pyg90alarm/const.py,sha256=w2BzsVRDxfdwOb9590zsntGMlVWEH80zbQ4djojft9U,6851
4
+ pyg90alarm/const.py,sha256=XBmtojOV0OrjqwL7x_wixqnt4Vcs9xOGlz-wHY4uO_Q,7948
5
+ pyg90alarm/event_mapping.py,sha256=hSmRWkkuA5hlauGvYakdOrw8QFt0TMNfUuDQ4J3vHpQ,3438
5
6
  pyg90alarm/exceptions.py,sha256=9LleQC2SkJXjv80FlWMeaHs9ZjXxCvZx9UtYdrvejyY,1951
6
7
  pyg90alarm/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
8
  pyg90alarm/cloud/__init__.py,sha256=u7ZzrIwCsdgmBmgtpCe6c_Kipdo8nG8PEOEcaAHXCxI,1407
@@ -15,28 +16,33 @@ pyg90alarm/definitions/devices.py,sha256=K0DQnyE-1hlhIGwRfZojKJYMSmlJzmyWcZ_98ur
15
16
  pyg90alarm/definitions/sensors.py,sha256=bDecBGyUo7wFVNuD5Fu1JNZQHcMDv-234BuNKioaQQs,27426
16
17
  pyg90alarm/entities/__init__.py,sha256=hHb6AOiC4Tz--rOWiiICMdLaZDs1Tf_xpWk_HeS_gO4,66
17
18
  pyg90alarm/entities/base_entity.py,sha256=hNhhuuwNuir54uVMZzPbjE0N6WL8wKvoW_KZa4R8L8U,2680
18
- pyg90alarm/entities/base_list.py,sha256=Ot4kd44CUheSvYETjEQBQNT_HaOTWZhKa-4L5pr8LMI,9276
19
+ pyg90alarm/entities/base_list.py,sha256=vNP0T8qg7lgrES0IBs4zOggQM6Uxyfarfds46bbAnH0,9482
19
20
  pyg90alarm/entities/device.py,sha256=eWE_N83hiDs5I-TT5F_W0Vb8sVugLldrDc9Lj9sgVLo,3700
20
21
  pyg90alarm/entities/device_list.py,sha256=PKnHEazeT3iUEaz70bW1OaWh0wq-7WOY-7dpV4FVCTc,5984
21
22
  pyg90alarm/entities/sensor.py,sha256=hunu-X-RhC8Wp52VMeanfqQg8rBSP8TG0qacJiFZEhg,27653
22
23
  pyg90alarm/entities/sensor_list.py,sha256=0S88bhTn91O45WgbIIMQ0iXaNjlUWmMBOOFwj2Hg73U,6993
23
24
  pyg90alarm/local/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
25
+ pyg90alarm/local/alarm_phones.py,sha256=OKH7t8jBziHOSJth1bnYKYVKFiX1o83JzA-6ijCYBDc,3149
26
+ pyg90alarm/local/alert_config.py,sha256=n2dycEf6TH0MKefQXMcRsCFJyYvUMV55LRsy4FARtDI,6027
24
27
  pyg90alarm/local/base_cmd.py,sha256=f0PozHJjErIg08vn0sAacHbigJSFor8q5jdxfXiM27c,10491
25
- pyg90alarm/local/config.py,sha256=AYDswfeM1aZImoAr6ZB-4fVIvMuTiNgkF_bLIROVckI,4922
28
+ pyg90alarm/local/config.py,sha256=QYetAc6QLrAN8T-37D4Esifvao52w_uJ01nHciLbGME,1390
29
+ pyg90alarm/local/dataclass_load_save.py,sha256=wmHT5834_rOlgPMe0ryZW0SbdFNTLC6-4NjJlSt6mhY,4738
26
30
  pyg90alarm/local/discovery.py,sha256=8YVIXuNe57lhas0VSRf7QXZH83pEDGNj9oehNY4Kh2U,3576
27
- pyg90alarm/local/history.py,sha256=v_deRl62tHeysydyeLRvyHJwcBMnKonsEbXXSXb8XP8,9138
31
+ pyg90alarm/local/history.py,sha256=sL6_Z1BNYkuUwAZUi78d4p5hhcCfsXKw29i4Qu1O60M,10811
32
+ pyg90alarm/local/host_config.py,sha256=ycGx8WdSQ_azuBWpK366gvldZcxfVGnYBa_frbI9-TM,5502
28
33
  pyg90alarm/local/host_info.py,sha256=4lFIaFEpYd3EvgNrDJmKijTrzX9i29nFISLLlXGnkmE,2759
29
34
  pyg90alarm/local/host_status.py,sha256=WHGtw-A0wHILqVWP4DnZhXj8DPRGyS26AV0bL1isTz4,1863
35
+ pyg90alarm/local/net_config.py,sha256=wjJMzL6tcHAfYF-OtregRpCoNEviOlkHHr5SpDaTmNs,4157
30
36
  pyg90alarm/local/notifications.py,sha256=Vs6NQJciYqDALV-WwzH6wIcTGdX_UD4XBuHWjSOpCDY,4591
31
37
  pyg90alarm/local/paginated_cmd.py,sha256=5pPVP8f4ydjgu8Yq6MwqINJAUt52fFlD17wO4AI88Pc,4467
32
38
  pyg90alarm/local/paginated_result.py,sha256=2tW3kMYkfCoNIhXi0N8acoIAhGhZYIbWdc347CH-Pjg,5474
33
39
  pyg90alarm/local/targeted_discovery.py,sha256=Ik2C2VBtVLurf3-RKko4O2R3B6MrmFdOskd457uyASU,5516
34
40
  pyg90alarm/local/user_data_crc.py,sha256=JQBOPY3RlOgVtvR55R-rM8OuKjYW-BPXQ0W4pi6CEH0,1689
35
41
  pyg90alarm/notifications/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
36
- pyg90alarm/notifications/base.py,sha256=_YX0kL9bqMAOdKBRZ5M-SIrwR4xotnZwgxqWoyEgxAk,16032
37
- pyg90alarm/notifications/protocol.py,sha256=mvn6hVNopOUgwQy4nWHKp7ydt5r84lsDWCCaWYJjL9Q,4422
38
- pyg90alarm-2.3.0.dist-info/licenses/LICENSE,sha256=f884inRbeNv-O-hbwz62Ro_1J8xiHRTnJ2cCx6A0WvU,1070
39
- pyg90alarm-2.3.0.dist-info/METADATA,sha256=bPvzjRYbZ8Cq_pO0pCbD4ensWw0m_HICuo0x4FbeORQ,12568
40
- pyg90alarm-2.3.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
41
- pyg90alarm-2.3.0.dist-info/top_level.txt,sha256=czHiGxYMyTk5QEDTDb0EpPiKqUMRa8zI4zx58Ii409M,11
42
- pyg90alarm-2.3.0.dist-info/RECORD,,
42
+ pyg90alarm/notifications/base.py,sha256=d3N_zNPa_jcTX4QpA78jdgMHDhmrgwqyM3HdvuO14Jk,16682
43
+ pyg90alarm/notifications/protocol.py,sha256=TlZQ3P8-N-E2X5bzkGefz432x4lBYyIBF9VriwYn9ds,4790
44
+ pyg90alarm-2.4.0.dist-info/licenses/LICENSE,sha256=f884inRbeNv-O-hbwz62Ro_1J8xiHRTnJ2cCx6A0WvU,1070
45
+ pyg90alarm-2.4.0.dist-info/METADATA,sha256=F4oEK7YEmXUXUXtwVhRxn-qPM0MSvOjdmNlmHfUbYxo,12568
46
+ pyg90alarm-2.4.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
47
+ pyg90alarm-2.4.0.dist-info/top_level.txt,sha256=czHiGxYMyTk5QEDTDb0EpPiKqUMRa8zI4zx58Ii409M,11
48
+ pyg90alarm-2.4.0.dist-info/RECORD,,