pyg90alarm 1.10.1__py3-none-any.whl → 1.12.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/history.py CHANGED
@@ -22,35 +22,197 @@
22
22
  History protocol entity.
23
23
  """
24
24
 
25
- import time
25
+ from datetime import datetime, timezone
26
26
  from collections import namedtuple
27
+ from .const import (
28
+ G90AlertTypes,
29
+ G90AlertSources,
30
+ G90AlertStates,
31
+ G90AlertStateChangeTypes,
32
+ G90HistoryStates,
33
+ )
34
+ from .device_notifications import G90DeviceAlert
35
+
36
+
37
+ # 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
+ G90AlertStates.DOOR_CLOSE:
43
+ G90HistoryStates.DOOR_CLOSE,
44
+ G90AlertStates.DOOR_OPEN:
45
+ G90HistoryStates.DOOR_OPEN,
46
+ G90AlertStates.TAMPER:
47
+ G90HistoryStates.TAMPER,
48
+ G90AlertStates.LOW_BATTERY:
49
+ G90HistoryStates.LOW_BATTERY,
50
+ G90AlertStateChangeTypes.AC_POWER_FAILURE:
51
+ G90HistoryStates.AC_POWER_FAILURE,
52
+ G90AlertStateChangeTypes.AC_POWER_RECOVER:
53
+ G90HistoryStates.AC_POWER_RECOVER,
54
+ G90AlertStateChangeTypes.DISARM:
55
+ G90HistoryStates.DISARM,
56
+ G90AlertStateChangeTypes.ARM_AWAY:
57
+ G90HistoryStates.ARM_AWAY,
58
+ G90AlertStateChangeTypes.ARM_HOME:
59
+ G90HistoryStates.ARM_HOME,
60
+ G90AlertStateChangeTypes.LOW_BATTERY:
61
+ G90HistoryStates.LOW_BATTERY,
62
+ G90AlertStateChangeTypes.WIFI_CONNECTED:
63
+ G90HistoryStates.WIFI_CONNECTED,
64
+ G90AlertStateChangeTypes.WIFI_DISCONNECTED:
65
+ G90HistoryStates.WIFI_DISCONNECTED,
66
+ }
27
67
 
28
68
  INCOMING_FIELDS = [
29
- 'log_type', # (1 or 3 - alarm, 2 or 4 - notification)
30
- 'param1', # (type 1: 1 - SOS, 2 - tamper alarm; type 3 - device ID; type
31
- # 2 - 5 stayarm, 3 - disarm, 4 - awayarm )
32
- 'param2', # (type 3: device type)
33
- 'param3',
69
+ 'type',
70
+ 'event_id',
71
+ 'source',
72
+ 'state',
34
73
  'sensor_name',
35
74
  'unix_time',
36
- 'rest',
75
+ 'other',
37
76
  ]
77
+ # Class representing the data incoming from the device
78
+ ProtocolData = namedtuple('ProtocolData', INCOMING_FIELDS)
38
79
 
39
80
 
40
- class G90History(namedtuple('G90History', INCOMING_FIELDS)):
81
+ class G90History:
41
82
  """
42
83
  tbd
43
84
  """
85
+ def __init__(self, *args, **kwargs):
86
+ self._protocol_data = ProtocolData(*args, **kwargs)
44
87
 
45
88
  @property
46
89
  def datetime(self):
47
90
  """
48
- tbd
91
+ Date/time of the history entry.
92
+
93
+ :rtype: :class:`datetime.datetime`
94
+ """
95
+ return datetime.fromtimestamp(
96
+ self._protocol_data.unix_time, tz=timezone.utc
97
+ )
98
+
99
+ @property
100
+ def type(self):
101
+ """
102
+ Type of the history entry.
103
+
104
+ :rtype: :class:`.G90AlertTypes`
105
+ """
106
+ return G90AlertTypes(self._protocol_data.type)
107
+
108
+ @property
109
+ def state(self):
110
+ """
111
+ State for the history entry.
112
+
113
+ :rtype: :class:`.G90HistoryStates`
114
+ """
115
+ # Door open/close type, mapped against `G90AlertStates` using `state`
116
+ # incoming field
117
+ if self.type == G90AlertTypes.DOOR_OPEN_CLOSE:
118
+ return G90HistoryStates(
119
+ states_mapping[G90AlertStates(self._protocol_data.state)]
120
+ )
121
+
122
+ # Device state change, mapped against `G90AlertStateChangeTypes` using
123
+ # `event_id` incoming field
124
+ if self.type == G90AlertTypes.STATE_CHANGE:
125
+ return G90HistoryStates(
126
+ states_mapping[
127
+ G90AlertStateChangeTypes(self._protocol_data.event_id)
128
+ ]
129
+ )
130
+
131
+ # Alarm gets mapped to its counterpart in `G90HistoryStates`
132
+ if self.type == G90AlertTypes.ALARM:
133
+ return G90HistoryStates.ALARM
134
+
135
+ # Other types are mapped against `G90AlertStateChangeTypes`
136
+ return G90HistoryStates(
137
+ states_mapping[
138
+ G90AlertStateChangeTypes(self._protocol_data.event_id)
139
+ ]
140
+ )
141
+
142
+ @property
143
+ def source(self):
49
144
  """
50
- return time.ctime(self.unix_time)
145
+ Source of the history entry.
146
+
147
+ :rtype: :class:`.G90AlertSources`
148
+ """
149
+ # Device state changes or open/close events are mapped against
150
+ # `G90AlertSources` using `source` incoming field
151
+ if self.type in [
152
+ G90AlertTypes.STATE_CHANGE, G90AlertTypes.DOOR_OPEN_CLOSE
153
+ ]:
154
+ return G90AlertSources(self._protocol_data.source)
155
+
156
+ # Alarm will have `SENSOR` as the source, since that is likely what
157
+ # triggered it
158
+ if self.type == G90AlertTypes.ALARM:
159
+ return G90AlertSources.SENSOR
160
+
161
+ # Other sources are assumed to be initiated by device itself
162
+ return G90AlertSources.DEVICE
163
+
164
+ @property
165
+ def sensor_name(self):
166
+ """
167
+ Name of the sensor related to the history entry, might be empty if none
168
+ associated.
169
+
170
+ :rtype: str|None
171
+ """
172
+ return self._protocol_data.sensor_name or None
173
+
174
+ @property
175
+ def sensor_idx(self):
176
+ """
177
+ ID of the sensor related to the history entry, might be empty if none
178
+ associated.
179
+
180
+ :rtype: str|None
181
+ """
182
+ # Sensor ID will only be available if entry source is a sensor
183
+ if self.source == G90AlertSources.SENSOR:
184
+ return self._protocol_data.event_id
185
+
186
+ return None
187
+
188
+ def as_device_alert(self):
189
+ """
190
+ Returns the history entry represented as device alert structure,
191
+ suitable for :meth:`G90DeviceNotifications._handle_alert`.
192
+
193
+ :rtype: :class:`.G90DeviceAlert`
194
+ """
195
+ return G90DeviceAlert(
196
+ type=self._protocol_data.type,
197
+ event_id=self._protocol_data.event_id,
198
+ source=self._protocol_data.source,
199
+ state=self._protocol_data.state,
200
+ zone_name=self._protocol_data.sensor_name,
201
+ device_id=None,
202
+ unix_time=self._protocol_data.unix_time,
203
+ resv4=None,
204
+ other=self._protocol_data.other
205
+ )
51
206
 
52
207
  def __repr__(self):
53
208
  """
54
- tbd
209
+ Textural representation of the history entry.
210
+
211
+ :rtype: str
55
212
  """
56
- return super().__repr__() + f'(datetime={str(self.datetime)})'
213
+ return f'type={repr(self.type)}' \
214
+ + f' source={repr(self.source)}' \
215
+ + f' state={repr(self.state)}' \
216
+ + f' sensor_name={self.sensor_name}' \
217
+ + f' sensor_idx={self.sensor_idx}' \
218
+ + f' datetime={repr(self.datetime)}'
@@ -55,29 +55,21 @@ class G90TargetedDiscoveryInfo(namedtuple('G90TargetedDiscoveryInfo',
55
55
  """
56
56
 
57
57
 
58
- class G90TargetedDiscoveryProtocol:
58
+ class G90TargetedDiscovery(G90Discovery):
59
59
  """
60
60
  tbd
61
-
62
- :meta private:
63
61
  """
64
- def __init__(self, device_id, parent):
65
- """
66
- tbd
67
- """
68
- self._parent = parent
69
- self._device_id = device_id
70
62
 
71
- def connection_made(self, transport):
72
- """
73
- tbd
74
- """
75
-
76
- def connection_lost(self, exc):
63
+ # pylint: disable=too-few-public-methods
64
+ def __init__(self, device_id, **kwargs):
77
65
  """
78
66
  tbd
79
67
  """
68
+ super().__init__(**kwargs)
69
+ self._device_id = device_id
80
70
 
71
+ # Implementation of datagram protocol,
72
+ # https://docs.python.org/3/library/asyncio-protocol.html#datagram-protocols
81
73
  def datagram_received(self, data, addr):
82
74
  """
83
75
  tbd
@@ -95,39 +87,12 @@ class G90TargetedDiscoveryProtocol:
95
87
  'port': addr[1]}
96
88
  res.update(host_info._asdict())
97
89
  _LOGGER.debug('Discovered device: %s', res)
98
- self._parent.add_device(res)
90
+ self.add_device(res)
99
91
  except Exception as exc: # pylint: disable=broad-except
100
92
  _LOGGER.warning('Got exception, ignoring: %s', exc)
101
93
 
102
- def error_received(self, exc):
103
- """
104
- tbd
105
- """
106
-
107
-
108
- class G90TargetedDiscovery(G90Discovery):
109
- """
110
- tbd
111
- """
112
-
113
- # pylint: disable=too-few-public-methods
114
- def __init__(self, device_id, **kwargs):
115
- """
116
- tbd
117
- """
118
-
119
- super().__init__(**kwargs)
120
- self._device_id = device_id
121
-
122
94
  def to_wire(self):
123
95
  """
124
96
  tbd
125
97
  """
126
98
  return bytes(f'IWTAC_PROBE_DEVICE,{self._device_id}\0', 'ascii')
127
-
128
- def _proto_factory(self):
129
- """
130
- tbd
131
- """
132
- return G90TargetedDiscoveryProtocol(self._device_id,
133
- self)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pyg90alarm
3
- Version: 1.10.1
3
+ Version: 1.12.0
4
4
  Summary: G90 Alarm system protocol
5
5
  Home-page: https://github.com/hostcc/pyg90alarm
6
6
  Author: Ilia Sotnikov
@@ -24,13 +24,13 @@ Requires-Python: >=3.7, <4
24
24
  Description-Content-Type: text/x-rst
25
25
  License-File: LICENSE
26
26
  Provides-Extra: dev
27
- Requires-Dist: check-manifest ; extra == 'dev'
27
+ Requires-Dist: check-manifest; extra == "dev"
28
28
  Provides-Extra: docs
29
- Requires-Dist: sphinx ; extra == 'docs'
30
- Requires-Dist: sphinx-rtd-theme ; extra == 'docs'
29
+ Requires-Dist: sphinx; extra == "docs"
30
+ Requires-Dist: sphinx-rtd-theme; extra == "docs"
31
31
  Provides-Extra: test
32
- Requires-Dist: coverage ; extra == 'test'
33
- Requires-Dist: asynctest ; extra == 'test'
32
+ Requires-Dist: coverage; extra == "test"
33
+ Requires-Dist: asynctest; extra == "test"
34
34
 
35
35
  .. image:: https://github.com/hostcc/pyg90alarm/actions/workflows/main.yml/badge.svg?branch=master
36
36
  :target: https://github.com/hostcc/pyg90alarm/tree/master
@@ -158,6 +158,13 @@ set the IP address allocation up.
158
158
  status will not be reflected and those will always be reported as inactive,
159
159
  since there is no way to read their state in a polled manner.
160
160
 
161
+ To work that limitation around the package now supports simulating device
162
+ notifications from periodically polling the history it records - the
163
+ simulation works only for the alerts, not notifications (e.g. notifications
164
+ include low battery events and alike). This also requires the particular
165
+ alert to be enabled in the mobile application, otherwise it won't be
166
+ recorded in the history.
167
+
161
168
  Quick start
162
169
  ===========
163
170
 
@@ -0,0 +1,26 @@
1
+ pyg90alarm/__init__.py,sha256=zidYApReScSFZCpC9Tk7pdsBNPMql6XiUtt-O7l3D5M,1381
2
+ pyg90alarm/alarm.py,sha256=IsNpnPyBh90lTP9L03R1GJ608Ld6ZgUsJ7D5nS3WpCk,29157
3
+ pyg90alarm/base_cmd.py,sha256=rjpjIzEgtI5mUvmurBasrt9psYKdbIEsivnx8QboivY,8561
4
+ pyg90alarm/callback.py,sha256=zg698TCjjYhjAMk770J9CZp8-dDbX0Zj5wtoC6axq6w,4033
5
+ pyg90alarm/config.py,sha256=FiYjiz_WrDH2OEqHyUJXZDDK7v1fLAUpZcQ3JRMmmX0,1974
6
+ pyg90alarm/const.py,sha256=XP_x5w6quiKQceOJDpExXI86L5KbQLdTed4lbjlNth0,5760
7
+ pyg90alarm/device_notifications.py,sha256=JLdOIIwyoxQdqvIjuLj6_5Rz_W6slYeM8HBPzPIa1ew,10628
8
+ pyg90alarm/discovery.py,sha256=sPk2mRhc0IgpOIlC9kRlKCnlAfdXsT7TyWfHAc1bfqQ,3077
9
+ pyg90alarm/exceptions.py,sha256=eiOcRe7D18EIPyPFDNU9DdFgbnkwPmkiLl8lGPOhBNw,1475
10
+ pyg90alarm/history.py,sha256=Ln3-v8hvagIXTmj_2iURE3oOsVLeQb8OMczUy-SKSRs,7039
11
+ pyg90alarm/host_info.py,sha256=fGGI2ZH6GVD0WhYT72rIELTbiIAmmPiT31eZkyVugwY,2571
12
+ pyg90alarm/host_status.py,sha256=PEPgpkfGNkUzKUgRpfPKldz5qq3_9lqBwX86Ld613vk,1406
13
+ pyg90alarm/paginated_cmd.py,sha256=7pXLAgFQHheByBpwRV-I1yEdZnm8hk6j2OMPZ_Wn-vE,3768
14
+ pyg90alarm/paginated_result.py,sha256=xweFfPLn1a2yYm5h0AxGoDCyDIoy0JkUC_tI80vsrLc,5246
15
+ pyg90alarm/targeted_discovery.py,sha256=DPxNvs8qJfyIOq6KLBYBpqkHPCpHbMW70_gfNyvNAoY,3221
16
+ pyg90alarm/user_data_crc.py,sha256=RsQlbuXC4baD88hX4y0XdysmxEMtQkqkNVX_FhTLSmw,1467
17
+ pyg90alarm/definitions/__init__.py,sha256=s0NZnkW_gMH718DJbgez28z9WA231CyszUf1O_ojUiI,68
18
+ pyg90alarm/definitions/sensors.py,sha256=2Liap0stTT5qNmvsbP_7UscA21IYXQcSAKQLLm7YfHQ,19921
19
+ pyg90alarm/entities/__init__.py,sha256=hHb6AOiC4Tz--rOWiiICMdLaZDs1Tf_xpWk_HeS_gO4,66
20
+ pyg90alarm/entities/device.py,sha256=QbsQyIq2wFLjIH389zyD3d0CyME_rpG_ciD3srAdqXQ,2772
21
+ pyg90alarm/entities/sensor.py,sha256=AkVj_qrK35K5SwSlAHXU9UXdBKVaJzV4TBcoeZUevn4,13942
22
+ pyg90alarm-1.12.0.dist-info/LICENSE,sha256=f884inRbeNv-O-hbwz62Ro_1J8xiHRTnJ2cCx6A0WvU,1070
23
+ pyg90alarm-1.12.0.dist-info/METADATA,sha256=8zrPZpzJVGK7UZ-It72WrJEpQst6S5BWVGjatOaLr_M,7611
24
+ pyg90alarm-1.12.0.dist-info/WHEEL,sha256=HiCZjzuy6Dw0hdX5R3LCFPDmFS4BWl8H-8W39XfmgX4,91
25
+ pyg90alarm-1.12.0.dist-info/top_level.txt,sha256=czHiGxYMyTk5QEDTDb0EpPiKqUMRa8zI4zx58Ii409M,11
26
+ pyg90alarm-1.12.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.38.4)
2
+ Generator: setuptools (72.2.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,26 +0,0 @@
1
- pyg90alarm/__init__.py,sha256=zidYApReScSFZCpC9Tk7pdsBNPMql6XiUtt-O7l3D5M,1381
2
- pyg90alarm/alarm.py,sha256=0SMGPe9J1550j_Yq-KxoFlKPjzVIZ5Z-RhsjUusUS9U,23528
3
- pyg90alarm/base_cmd.py,sha256=xuW-jjuScag1kAVPKNa2GUmL99UYeyu2n-C9ppLmOMo,8768
4
- pyg90alarm/callback.py,sha256=zg698TCjjYhjAMk770J9CZp8-dDbX0Zj5wtoC6axq6w,4033
5
- pyg90alarm/config.py,sha256=FiYjiz_WrDH2OEqHyUJXZDDK7v1fLAUpZcQ3JRMmmX0,1974
6
- pyg90alarm/const.py,sha256=nhqrP9GqsU4VeEYHSW4NJTh6QO20L7uDea2reqDnqO4,5226
7
- pyg90alarm/device_notifications.py,sha256=wxDf0m_LOt6Sj_jG8cfTFZM3LNry2xrPwkFPa4lVscI,9315
8
- pyg90alarm/discovery.py,sha256=07TZIIGgPsRcW_iYpS-82lG18rotuykYqfGV-cE2eHc,3474
9
- pyg90alarm/exceptions.py,sha256=eiOcRe7D18EIPyPFDNU9DdFgbnkwPmkiLl8lGPOhBNw,1475
10
- pyg90alarm/history.py,sha256=E8c8ucy5cscPSawIz4nXxV4HKdyhsoJda4Akvvt3k6I,1836
11
- pyg90alarm/host_info.py,sha256=fGGI2ZH6GVD0WhYT72rIELTbiIAmmPiT31eZkyVugwY,2571
12
- pyg90alarm/host_status.py,sha256=PEPgpkfGNkUzKUgRpfPKldz5qq3_9lqBwX86Ld613vk,1406
13
- pyg90alarm/paginated_cmd.py,sha256=7pXLAgFQHheByBpwRV-I1yEdZnm8hk6j2OMPZ_Wn-vE,3768
14
- pyg90alarm/paginated_result.py,sha256=xweFfPLn1a2yYm5h0AxGoDCyDIoy0JkUC_tI80vsrLc,5246
15
- pyg90alarm/targeted_discovery.py,sha256=_SR_pBZxDkLNL5WA8LfGboQw2AH09G65ASn9EEtxlDs,3735
16
- pyg90alarm/user_data_crc.py,sha256=RsQlbuXC4baD88hX4y0XdysmxEMtQkqkNVX_FhTLSmw,1467
17
- pyg90alarm/definitions/__init__.py,sha256=s0NZnkW_gMH718DJbgez28z9WA231CyszUf1O_ojUiI,68
18
- pyg90alarm/definitions/sensors.py,sha256=htarrXk46u9RwFR_JLdsLJiZi0ilQSZdfWaCc5TydEQ,14697
19
- pyg90alarm/entities/__init__.py,sha256=hHb6AOiC4Tz--rOWiiICMdLaZDs1Tf_xpWk_HeS_gO4,66
20
- pyg90alarm/entities/device.py,sha256=QbsQyIq2wFLjIH389zyD3d0CyME_rpG_ciD3srAdqXQ,2772
21
- pyg90alarm/entities/sensor.py,sha256=2d_671Kefx7wM6mWRQR79XyScEkkp3bk7HsA1Qnx75I,13354
22
- pyg90alarm-1.10.1.dist-info/LICENSE,sha256=f884inRbeNv-O-hbwz62Ro_1J8xiHRTnJ2cCx6A0WvU,1070
23
- pyg90alarm-1.10.1.dist-info/METADATA,sha256=llXidozyi21SpryQm0IGdu42QjUNNQIKX5NTHF-qU_8,7211
24
- pyg90alarm-1.10.1.dist-info/WHEEL,sha256=2wepM1nk4DS4eFpYrW1TTqPcoGNfHhhO_i5m4cOimbo,92
25
- pyg90alarm-1.10.1.dist-info/top_level.txt,sha256=czHiGxYMyTk5QEDTDb0EpPiKqUMRa8zI4zx58Ii409M,11
26
- pyg90alarm-1.10.1.dist-info/RECORD,,