pyg90alarm 1.16.0__tar.gz → 1.16.1__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 (56) hide show
  1. {pyg90alarm-1.16.0 → pyg90alarm-1.16.1}/PKG-INFO +1 -1
  2. {pyg90alarm-1.16.0 → pyg90alarm-1.16.1}/src/pyg90alarm/const.py +7 -0
  3. {pyg90alarm-1.16.0 → pyg90alarm-1.16.1}/src/pyg90alarm/history.py +66 -34
  4. {pyg90alarm-1.16.0 → pyg90alarm-1.16.1}/src/pyg90alarm.egg-info/PKG-INFO +1 -1
  5. {pyg90alarm-1.16.0 → pyg90alarm-1.16.1}/tests/test_alarm.py +29 -2
  6. {pyg90alarm-1.16.0 → pyg90alarm-1.16.1}/.github/CODEOWNERS +0 -0
  7. {pyg90alarm-1.16.0 → pyg90alarm-1.16.1}/.github/workflows/main.yml +0 -0
  8. {pyg90alarm-1.16.0 → pyg90alarm-1.16.1}/.gitignore +0 -0
  9. {pyg90alarm-1.16.0 → pyg90alarm-1.16.1}/.pylintrc +0 -0
  10. {pyg90alarm-1.16.0 → pyg90alarm-1.16.1}/.readthedocs.yaml +0 -0
  11. {pyg90alarm-1.16.0 → pyg90alarm-1.16.1}/LICENSE +0 -0
  12. {pyg90alarm-1.16.0 → pyg90alarm-1.16.1}/MANIFEST.in +0 -0
  13. {pyg90alarm-1.16.0 → pyg90alarm-1.16.1}/README.rst +0 -0
  14. {pyg90alarm-1.16.0 → pyg90alarm-1.16.1}/docs/.DS_Store +0 -0
  15. {pyg90alarm-1.16.0 → pyg90alarm-1.16.1}/docs/.gitignore +0 -0
  16. {pyg90alarm-1.16.0 → pyg90alarm-1.16.1}/docs/api-docs.rst +0 -0
  17. {pyg90alarm-1.16.0 → pyg90alarm-1.16.1}/docs/conf.py +0 -0
  18. {pyg90alarm-1.16.0 → pyg90alarm-1.16.1}/docs/index.rst +0 -0
  19. {pyg90alarm-1.16.0 → pyg90alarm-1.16.1}/docs/protocol.rst +0 -0
  20. {pyg90alarm-1.16.0 → pyg90alarm-1.16.1}/docs/requirements.txt +0 -0
  21. {pyg90alarm-1.16.0 → pyg90alarm-1.16.1}/pyproject.toml +0 -0
  22. {pyg90alarm-1.16.0 → pyg90alarm-1.16.1}/setup.cfg +0 -0
  23. {pyg90alarm-1.16.0 → pyg90alarm-1.16.1}/setup.py +0 -0
  24. {pyg90alarm-1.16.0 → pyg90alarm-1.16.1}/sonar-project.properties +0 -0
  25. {pyg90alarm-1.16.0 → pyg90alarm-1.16.1}/src/pyg90alarm/__init__.py +0 -0
  26. {pyg90alarm-1.16.0 → pyg90alarm-1.16.1}/src/pyg90alarm/alarm.py +0 -0
  27. {pyg90alarm-1.16.0 → pyg90alarm-1.16.1}/src/pyg90alarm/base_cmd.py +0 -0
  28. {pyg90alarm-1.16.0 → pyg90alarm-1.16.1}/src/pyg90alarm/callback.py +0 -0
  29. {pyg90alarm-1.16.0 → pyg90alarm-1.16.1}/src/pyg90alarm/config.py +0 -0
  30. {pyg90alarm-1.16.0 → pyg90alarm-1.16.1}/src/pyg90alarm/definitions/__init__.py +0 -0
  31. {pyg90alarm-1.16.0 → pyg90alarm-1.16.1}/src/pyg90alarm/definitions/sensors.py +0 -0
  32. {pyg90alarm-1.16.0 → pyg90alarm-1.16.1}/src/pyg90alarm/device_notifications.py +0 -0
  33. {pyg90alarm-1.16.0 → pyg90alarm-1.16.1}/src/pyg90alarm/discovery.py +0 -0
  34. {pyg90alarm-1.16.0 → pyg90alarm-1.16.1}/src/pyg90alarm/entities/__init__.py +0 -0
  35. {pyg90alarm-1.16.0 → pyg90alarm-1.16.1}/src/pyg90alarm/entities/device.py +0 -0
  36. {pyg90alarm-1.16.0 → pyg90alarm-1.16.1}/src/pyg90alarm/entities/sensor.py +0 -0
  37. {pyg90alarm-1.16.0 → pyg90alarm-1.16.1}/src/pyg90alarm/exceptions.py +0 -0
  38. {pyg90alarm-1.16.0 → pyg90alarm-1.16.1}/src/pyg90alarm/host_info.py +0 -0
  39. {pyg90alarm-1.16.0 → pyg90alarm-1.16.1}/src/pyg90alarm/host_status.py +0 -0
  40. {pyg90alarm-1.16.0 → pyg90alarm-1.16.1}/src/pyg90alarm/paginated_cmd.py +0 -0
  41. {pyg90alarm-1.16.0 → pyg90alarm-1.16.1}/src/pyg90alarm/paginated_result.py +0 -0
  42. {pyg90alarm-1.16.0 → pyg90alarm-1.16.1}/src/pyg90alarm/py.typed +0 -0
  43. {pyg90alarm-1.16.0 → pyg90alarm-1.16.1}/src/pyg90alarm/targeted_discovery.py +0 -0
  44. {pyg90alarm-1.16.0 → pyg90alarm-1.16.1}/src/pyg90alarm/user_data_crc.py +0 -0
  45. {pyg90alarm-1.16.0 → pyg90alarm-1.16.1}/src/pyg90alarm.egg-info/SOURCES.txt +0 -0
  46. {pyg90alarm-1.16.0 → pyg90alarm-1.16.1}/src/pyg90alarm.egg-info/dependency_links.txt +0 -0
  47. {pyg90alarm-1.16.0 → pyg90alarm-1.16.1}/src/pyg90alarm.egg-info/requires.txt +0 -0
  48. {pyg90alarm-1.16.0 → pyg90alarm-1.16.1}/src/pyg90alarm.egg-info/top_level.txt +0 -0
  49. {pyg90alarm-1.16.0 → pyg90alarm-1.16.1}/tests/__init__.py +0 -0
  50. {pyg90alarm-1.16.0 → pyg90alarm-1.16.1}/tests/conftest.py +0 -0
  51. {pyg90alarm-1.16.0 → pyg90alarm-1.16.1}/tests/device_mock.py +0 -0
  52. {pyg90alarm-1.16.0 → pyg90alarm-1.16.1}/tests/test_base_commands.py +0 -0
  53. {pyg90alarm-1.16.0 → pyg90alarm-1.16.1}/tests/test_discovery.py +0 -0
  54. {pyg90alarm-1.16.0 → pyg90alarm-1.16.1}/tests/test_notifications.py +0 -0
  55. {pyg90alarm-1.16.0 → pyg90alarm-1.16.1}/tests/test_paginated_commands.py +0 -0
  56. {pyg90alarm-1.16.0 → pyg90alarm-1.16.1}/tox.ini +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pyg90alarm
3
- Version: 1.16.0
3
+ Version: 1.16.1
4
4
  Summary: G90 Alarm system protocol
5
5
  Home-page: https://github.com/hostcc/pyg90alarm
6
6
  Author: Ilia Sotnikov
@@ -166,7 +166,10 @@ class G90NotificationTypes(IntEnum):
166
166
  Defines types of notifications sent by the alarm panel.
167
167
  """
168
168
  ARM_DISARM = 1
169
+ SENSOR_ADDED = 4
169
170
  SENSOR_ACTIVITY = 5
171
+ DOOR_OPEN_WHEN_ARMING = 6
172
+ FIRMWARE_UPDATING = 8
170
173
 
171
174
 
172
175
  class G90ArmDisarmTypes(IntEnum):
@@ -195,7 +198,10 @@ class G90AlertSources(IntEnum):
195
198
  """
196
199
  DEVICE = 0
197
200
  SENSOR = 1
201
+ REMOTE = 10
202
+ RFID = 11
198
203
  DOORBELL = 12
204
+ FINGERPRINT = 15
199
205
 
200
206
 
201
207
  class G90AlertStates(IntEnum):
@@ -204,6 +210,7 @@ class G90AlertStates(IntEnum):
204
210
  """
205
211
  DOOR_CLOSE = 0
206
212
  DOOR_OPEN = 1
213
+ SOS = 2
207
214
  TAMPER = 3
208
215
  LOW_BATTERY = 4
209
216
 
@@ -21,6 +21,8 @@
21
21
  """
22
22
  History protocol entity.
23
23
  """
24
+ import logging
25
+
24
26
  from typing import Any, Optional, Dict
25
27
  from dataclasses import dataclass
26
28
  from datetime import datetime, timezone
@@ -33,12 +35,13 @@ from .const import (
33
35
  )
34
36
  from .device_notifications import G90DeviceAlert
35
37
 
38
+ _LOGGER = logging.getLogger(__name__)
39
+
36
40
 
37
41
  # The state of the incoming history entries are mixed of `G90AlertStates` and
38
- # `G90AlertStateChangeTypes`, depending on entry type - the mapping
39
- # consilidates them into unified `G90HistoryStates`. The latter enum can't be
40
- # just an union of former two, since those have conflicting values
41
- states_mapping = {
42
+ # `G90AlertStateChangeTypes`, depending on entry type - hence two separate
43
+ # dictionaries, since enums used for keys have conflicting values
44
+ states_mapping_alerts = {
42
45
  G90AlertStates.DOOR_CLOSE:
43
46
  G90HistoryStates.DOOR_CLOSE,
44
47
  G90AlertStates.DOOR_OPEN:
@@ -47,6 +50,9 @@ states_mapping = {
47
50
  G90HistoryStates.TAMPER,
48
51
  G90AlertStates.LOW_BATTERY:
49
52
  G90HistoryStates.LOW_BATTERY,
53
+ }
54
+
55
+ states_mapping_state_changes = {
50
56
  G90AlertStateChangeTypes.AC_POWER_FAILURE:
51
57
  G90HistoryStates.AC_POWER_FAILURE,
52
58
  G90AlertStateChangeTypes.AC_POWER_RECOVER:
@@ -87,6 +93,7 @@ class G90History:
87
93
  Represents a history entry from the alarm panel.
88
94
  """
89
95
  def __init__(self, *args: Any, **kwargs: Any):
96
+ self._raw_data = args
90
97
  self._protocol_data = ProtocolData(*args, **kwargs)
91
98
 
92
99
  @property
@@ -99,55 +106,79 @@ class G90History:
99
106
  )
100
107
 
101
108
  @property
102
- def type(self) -> G90AlertTypes:
109
+ def type(self) -> Optional[G90AlertTypes]:
103
110
  """
104
111
  Type of the history entry.
105
112
  """
106
- return G90AlertTypes(self._protocol_data.type)
113
+ try:
114
+ return G90AlertTypes(self._protocol_data.type)
115
+ except ValueError:
116
+ _LOGGER.warning(
117
+ "Can't interpret '%s' as alert type (decoded protocol"
118
+ " data '%s', raw data '%s')",
119
+ self._protocol_data.type, self._protocol_data, self._raw_data
120
+ )
121
+ return None
107
122
 
108
123
  @property
109
- def state(self) -> G90HistoryStates:
124
+ def state(self) -> Optional[G90HistoryStates]:
110
125
  """
111
126
  State for the history entry.
112
127
  """
113
- # Door open/close type, mapped against `G90AlertStates` using `state`
114
- # incoming field
115
- if self.type == G90AlertTypes.DOOR_OPEN_CLOSE:
116
- return G90HistoryStates(
117
- states_mapping[G90AlertStates(self._protocol_data.state)]
128
+ try:
129
+ # Door open/close or alert types, mapped against `G90AlertStates`
130
+ # using `state` incoming field
131
+ if self.type in [
132
+ G90AlertTypes.DOOR_OPEN_CLOSE, G90AlertTypes.ALARM
133
+ ]:
134
+ return G90HistoryStates(
135
+ states_mapping_alerts[
136
+ G90AlertStates(self._protocol_data.state)
137
+ ]
138
+ )
139
+ except ValueError:
140
+ _LOGGER.warning(
141
+ "Can't interpret '%s' as alert state (decoded protocol"
142
+ " data '%s', raw data '%s')",
143
+ self._protocol_data.state, self._protocol_data, self._raw_data
118
144
  )
145
+ return None
119
146
 
120
- # Device state change, mapped against `G90AlertStateChangeTypes` using
121
- # `event_id` incoming field
122
- if self.type == G90AlertTypes.STATE_CHANGE:
147
+ try:
148
+ # Other types are mapped against `G90AlertStateChangeTypes`
123
149
  return G90HistoryStates(
124
- states_mapping[
150
+ states_mapping_state_changes[
125
151
  G90AlertStateChangeTypes(self._protocol_data.event_id)
126
152
  ]
127
153
  )
128
-
129
- # Alarm gets mapped to its counterpart in `G90HistoryStates`
130
- if self.type == G90AlertTypes.ALARM:
131
- return G90HistoryStates.ALARM
132
-
133
- # Other types are mapped against `G90AlertStateChangeTypes`
134
- return G90HistoryStates(
135
- states_mapping[
136
- G90AlertStateChangeTypes(self._protocol_data.event_id)
137
- ]
138
- )
154
+ except ValueError:
155
+ _LOGGER.warning(
156
+ "Can't interpret '%s' as state change (decoded protocol"
157
+ " data '%s', raw data '%s')",
158
+ self._protocol_data.event_id, self._protocol_data,
159
+ self._raw_data
160
+ )
161
+ return None
139
162
 
140
163
  @property
141
- def source(self) -> G90AlertSources:
164
+ def source(self) -> Optional[G90AlertSources]:
142
165
  """
143
166
  Source of the history entry.
144
167
  """
145
- # Device state changes or open/close events are mapped against
146
- # `G90AlertSources` using `source` incoming field
147
- if self.type in [
148
- G90AlertTypes.STATE_CHANGE, G90AlertTypes.DOOR_OPEN_CLOSE
149
- ]:
150
- return G90AlertSources(self._protocol_data.source)
168
+ try:
169
+ # Device state changes or open/close events are mapped against
170
+ # `G90AlertSources` using `source` incoming field
171
+ if self.type in [
172
+ G90AlertTypes.STATE_CHANGE, G90AlertTypes.DOOR_OPEN_CLOSE
173
+ ]:
174
+ return G90AlertSources(self._protocol_data.source)
175
+ except ValueError:
176
+ _LOGGER.warning(
177
+ "Can't interpret '%s' as alert source (decoded protocol"
178
+ " data '%s', raw data '%s')",
179
+ self._protocol_data.source, self._protocol_data, self._raw_data
180
+ )
181
+ return None
151
182
 
152
183
  # Alarm will have `SENSOR` as the source, since that is likely what
153
184
  # triggered it
@@ -182,6 +213,7 @@ class G90History:
182
213
  Returns the history entry represented as device alert structure,
183
214
  suitable for :meth:`G90DeviceNotifications._handle_alert`.
184
215
  """
216
+
185
217
  return G90DeviceAlert(
186
218
  type=self._protocol_data.type,
187
219
  event_id=self._protocol_data.event_id,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pyg90alarm
3
- Version: 1.16.0
3
+ Version: 1.16.1
4
4
  Summary: G90 Alarm system protocol
5
5
  Home-page: https://github.com/hostcc/pyg90alarm
6
6
  Author: Ilia Sotnikov
@@ -575,7 +575,7 @@ async def test_disarm(mock_device: DeviceMock) -> None:
575
575
 
576
576
  @pytest.mark.g90device(sent_data=[
577
577
  b'ISTART[200,[[50,1,5],'
578
- b'[3,33,7,254,"Sensor 1",1630147285,""],'
578
+ b'[3,33,7,1,"Sensor 1",1630147285,""],'
579
579
  b'[2,3,0,0,"",1630142877,""],'
580
580
  b'[2,5,0,0,"",1630142871,""],'
581
581
  b'[2,4,0,0,"",1630142757,""],'
@@ -595,6 +595,33 @@ async def test_history(mock_device: DeviceMock) -> None:
595
595
  assert isinstance(history[0]._asdict(), dict)
596
596
 
597
597
 
598
+ @pytest.mark.g90device(sent_data=[
599
+ b'ISTART[200,[[3,1,3],'
600
+ # Wrong state
601
+ b'[3,33,7,254,"Sensor 1",1630147285,""],'
602
+ # Wrong source
603
+ b'[2,33,254,1,"Sensor 1",1630147285,""],'
604
+ # Wrong type
605
+ b'[254,33,1,1,"Sensor 1",1630147285,""]'
606
+ b']]IEND\0',
607
+ ])
608
+ async def test_history_parsing_error(mock_device: DeviceMock) -> None:
609
+ """
610
+ Tests for processing history from the device, when the parsing error
611
+ occurs.
612
+ """
613
+ g90 = G90Alarm(host=mock_device.host, port=mock_device.port)
614
+ history = await g90.history(count=5)
615
+ assert len(history) == 3
616
+ assert isinstance(history[0], G90History)
617
+ assert isinstance(history[0]._asdict(), dict)
618
+ # Wrong entry element should result in corresponding key having 'None'
619
+ # value
620
+ assert history[0]._asdict()['state'] is None
621
+ assert history[1]._asdict()['source'] is None
622
+ assert history[2]._asdict()['type'] is None
623
+
624
+
598
625
  @pytest.mark.g90device(sent_data=[
599
626
  # Simulate empty history initially
600
627
  b'ISTART[200,[[0,0,0]]]IEND\0',
@@ -606,7 +633,7 @@ async def test_history(mock_device: DeviceMock) -> None:
606
633
  # The records will be used to simulate the device alerts, but only for
607
634
  # those newer that one above
608
635
  b'ISTART[200,[[3,1,3],'
609
- b'[3,33,7,254,"Sensor 1",1630147285,""],'
636
+ b'[3,33,7,1,"Sensor 1",1630147285,""],'
610
637
  b'[2,3,0,0,"",1630142877,""],'
611
638
  b'[2,5,0,0,"",1630142871,""]'
612
639
  b']]IEND\0',
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes