pyg90alarm 1.13.0__py3-none-any.whl → 1.14.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.
@@ -21,48 +21,67 @@
21
21
  """
22
22
  Provides interface to sensors of G90 alarm panel.
23
23
  """
24
-
24
+ from __future__ import annotations
25
25
  import logging
26
- from collections import namedtuple
26
+ from dataclasses import dataclass, asdict, astuple
27
+ from typing import (
28
+ Any, Optional, TYPE_CHECKING, Dict
29
+ )
30
+
27
31
  from enum import IntEnum, IntFlag
28
- from ..definitions.sensors import SENSOR_DEFINITIONS
32
+ from ..definitions.sensors import SENSOR_DEFINITIONS, SensorDefinition
29
33
  from ..const import G90Commands
34
+ if TYPE_CHECKING:
35
+ from ..alarm import G90Alarm, SensorStateCallback, SensorLowBatteryCallback
30
36
 
31
- # Common protocol fields across read and write operations
32
- COMMON_FIELDS = [
33
- 'parent_name',
34
- 'index',
35
- 'room_id',
36
- 'type_id',
37
- 'subtype',
38
- 'timeout',
39
- 'user_flag_data',
40
- 'baudrate',
41
- 'protocol_id',
42
- 'reserved_data',
43
- 'node_count',
44
- ]
45
-
46
- # Incoming (read operation) protocol fields
47
- INCOMING_FIELDS = COMMON_FIELDS + [
48
- 'mask',
49
- 'private_data',
50
- ]
51
-
52
- # Outgoing (write operation) protocol fields
53
- OUTGOING_FIELDS = COMMON_FIELDS + [
54
- 'rx',
55
- 'tx',
56
- 'private_data',
57
- ]
58
37
 
38
+ @dataclass
39
+ class G90SensorCommonData: # pylint:disable=too-many-instance-attributes
40
+ """
41
+ Common protocol fields across read and write operations.
59
42
 
60
- class G90SensorReservedFlags(IntFlag):
43
+ :meta private:
61
44
  """
62
- Reserved flags of the sensor.
45
+ parent_name: str
46
+ index: int
47
+ room_id: int
48
+ type_id: int
49
+ subtype: int
50
+ timeout: int
51
+ user_flag_data: int
52
+ baudrate: int
53
+ protocol_id: int
54
+ reserved_data: int
55
+ node_count: int
56
+
57
+
58
+ @dataclass
59
+ class G90SensorIncomingData(G90SensorCommonData):
60
+ """
61
+ Incoming (read operation) protocol fields.
63
62
 
64
63
  :meta private:
65
64
  """
65
+ mask: int
66
+ private_data: str
67
+
68
+
69
+ @dataclass
70
+ class G90SensorOutgoingData(G90SensorCommonData):
71
+ """
72
+ Outgoing (write operation) protocol fields.
73
+
74
+ :meta private:
75
+ """
76
+ rx: int # pylint:disable=invalid-name
77
+ tx: int # pylint:disable=invalid-name
78
+ private_data: str
79
+
80
+
81
+ class G90SensorReservedFlags(IntFlag):
82
+ """
83
+ Reserved flags of the sensor.
84
+ """
66
85
  NONE = 0
67
86
  CAN_READ = 16
68
87
  CAN_READ_EXT = 32
@@ -72,8 +91,6 @@ class G90SensorReservedFlags(IntFlag):
72
91
  class G90SensorUserFlags(IntFlag):
73
92
  """
74
93
  User flags of the sensor.
75
-
76
- :meta private:
77
94
  """
78
95
  NONE = 0
79
96
  ENABLED = 1
@@ -89,8 +106,6 @@ class G90SensorUserFlags(IntFlag):
89
106
  class G90SensorProtocols(IntEnum):
90
107
  """
91
108
  Protocol types for the sensors.
92
-
93
- :meta private:
94
109
  """
95
110
  RF_1527 = 0
96
111
  RF_2262 = 1
@@ -104,8 +119,6 @@ class G90SensorProtocols(IntEnum):
104
119
  class G90SensorTypes(IntEnum):
105
120
  """
106
121
  Sensor types.
107
-
108
- :meta private:
109
122
  """
110
123
  DOOR = 1
111
124
  GLASS = 2
@@ -160,23 +173,22 @@ class G90Sensor: # pylint:disable=too-many-instance-attributes
160
173
  :param kwargs: Pass-through keyword arguments for for interpreting protocol
161
174
  fields
162
175
  """
163
- def __init__(self, *args, parent, subindex, proto_idx, **kwargs):
164
- self._protocol_incoming_data_kls = (
165
- namedtuple('G90SensorIncomingData', INCOMING_FIELDS)
166
- )
167
- self._protocol_outgoing_data_kls = (
168
- namedtuple('G90SensorOutgoingData', OUTGOING_FIELDS)
169
- )
176
+ def __init__(
177
+ self, *args: Any, parent: G90Alarm, subindex: int, proto_idx: int,
178
+ **kwargs: Any
179
+ ) -> None:
180
+ self._protocol_incoming_data_kls = G90SensorIncomingData
181
+ self._protocol_outgoing_data_kls = G90SensorOutgoingData
170
182
  self._protocol_data = self._protocol_incoming_data_kls(*args, **kwargs)
171
183
  self._parent = parent
172
184
  self._subindex = subindex
173
185
  self._occupancy = False
174
- self._state_callback = None
175
- self._low_battery_callback = None
186
+ self._state_callback: Optional[SensorStateCallback] = None
187
+ self._low_battery_callback: Optional[SensorLowBatteryCallback] = None
176
188
  self._proto_idx = proto_idx
177
- self._extra_data = None
189
+ self._extra_data: Any = None
178
190
 
179
- self._definition = None
191
+ self._definition: Optional[SensorDefinition] = None
180
192
  # Get sensor definition corresponds to the sensor type/subtype if any
181
193
  for s_def in SENSOR_DEFINITIONS:
182
194
  if (
@@ -187,184 +199,168 @@ class G90Sensor: # pylint:disable=too-many-instance-attributes
187
199
  break
188
200
 
189
201
  @property
190
- def name(self):
202
+ def name(self) -> str:
191
203
  """
192
- Returns sensor name, accounting for multi-channel entities (single
204
+ Sensor name, accounting for multi-channel entities (single
193
205
  protocol entity results in multiple :class:`.G90Sensor` instances).
194
206
 
195
207
  :return: Sensor name
196
- :rtype: str
197
208
  """
198
209
  if self._protocol_data.node_count == 1:
199
210
  return self._protocol_data.parent_name
200
211
  return f'{self._protocol_data.parent_name}#{self._subindex + 1}'
201
212
 
202
213
  @property
203
- def state_callback(self):
214
+ def state_callback(self) -> Optional[SensorStateCallback]:
204
215
  """
205
- Returns state callback the sensor might have set.
216
+ Callback that is invoked when the sensor changes its state.
206
217
 
207
218
  :return: Sensor state callback
208
- :rtype: object
209
219
  """
210
220
  return self._state_callback
211
221
 
212
222
  @state_callback.setter
213
- def state_callback(self, value):
214
- """
215
- Sets callback for the state changes of the sensor.
216
-
217
- :param object value: Sensor state callback
218
- """
223
+ def state_callback(self, value: SensorStateCallback) -> None:
219
224
  self._state_callback = value
220
225
 
221
226
  @property
222
- def low_battery_callback(self):
227
+ def low_battery_callback(self) -> Optional[SensorLowBatteryCallback]:
223
228
  """
224
- Returns callback the sensor might have set for low battery condition.
229
+ Callback that is invoked when the sensor reports on low battery
230
+ condition.
225
231
 
226
232
  :return: Sensor's low battery callback
227
- :rtype: object
228
233
  """
229
234
  return self._low_battery_callback
230
235
 
231
236
  @low_battery_callback.setter
232
- def low_battery_callback(self, value):
233
- """
234
- Sets callback for the low battery condition reported by the sensor.
235
-
236
- :param object value: Sensor's low battery callback
237
- """
237
+ def low_battery_callback(self, value: SensorLowBatteryCallback) -> None:
238
238
  self._low_battery_callback = value
239
239
 
240
240
  @property
241
- def occupancy(self):
241
+ def occupancy(self) -> bool:
242
242
  """
243
243
  Occupancy (occupied/not occupied, or triggered/not triggered)
244
244
  for the sensor.
245
245
 
246
246
  :return: Sensor occupancy
247
- :rtype: bool
248
247
  """
249
248
  return self._occupancy
250
249
 
251
250
  @occupancy.setter
252
- def occupancy(self, value):
253
- """
254
- Sets occupancy state for the sensor.
255
-
256
- :param bool value: Sensor occupancy
257
- """
251
+ def occupancy(self, value: bool) -> None:
258
252
  self._occupancy = value
259
253
 
260
254
  @property
261
- def protocol(self):
255
+ def protocol(self) -> G90SensorProtocols:
262
256
  """
263
- Returns protocol type of the sensor.
257
+ Protocol type of the sensor.
264
258
 
265
259
  :return: Protocol type
266
- :rtype: int
267
260
  """
268
261
  return G90SensorProtocols(self._protocol_data.protocol_id)
269
262
 
270
263
  @property
271
- def type(self):
264
+ def type(self) -> G90SensorTypes:
272
265
  """
273
- Returns type of the sensor.
266
+ Type of the sensor.
274
267
 
275
268
  :return: Sensor type
276
- :rtype: int
277
269
  """
278
270
  return G90SensorTypes(self._protocol_data.type_id)
279
271
 
280
272
  @property
281
- def reserved(self):
273
+ def subtype(self) -> int:
274
+ """
275
+ Sub-type of the sensor.
276
+
277
+ :return: Sensor sub-type
282
278
  """
283
- Returns reserved flags (read/write mode) for the sensor.
279
+ return self._protocol_data.subtype
280
+
281
+ @property
282
+ def reserved(self) -> G90SensorReservedFlags:
283
+ """
284
+ Reserved flags (read/write mode) for the sensor.
284
285
 
285
286
  :return: Reserved flags
286
- :rtype: int
287
287
  """
288
288
  return G90SensorReservedFlags(self._protocol_data.reserved_data)
289
289
 
290
290
  @property
291
- def user_flag(self):
291
+ def user_flag(self) -> G90SensorUserFlags:
292
292
  """
293
- Returns user flags for the sensor (disabled/enabled, arming type etc).
293
+ User flags for the sensor (disabled/enabled, arming type etc).
294
294
 
295
295
  :return: User flags
296
- :rtype: int
297
296
  """
298
297
  return G90SensorUserFlags(self._protocol_data.user_flag_data)
299
298
 
300
299
  @property
301
- def node_count(self):
300
+ def node_count(self) -> int:
302
301
  """
303
- Returns number of nodes (channels) for the sensor.
302
+ Number of nodes (channels) for the sensor.
304
303
 
305
304
  :return: Number of nodes
306
- :rtype: int
307
305
  """
308
306
  return self._protocol_data.node_count
309
307
 
310
308
  @property
311
- def parent(self):
309
+ def parent(self) -> G90Alarm:
312
310
  """
313
- Returns parent :class:`.G90Alarm` instance the sensor is associated
311
+ Parent instance of alarm panel class the sensor is associated
314
312
  with.
315
313
 
316
- :return: Parent :class:`.G90Alarm` instance
317
- :rtype: :class:`.G90Alarm`
314
+ :return: Parent instance
318
315
  """
319
316
  return self._parent
320
317
 
321
318
  @property
322
- def index(self):
319
+ def index(self) -> int:
323
320
  """
324
- Returns index (internal position) of the sensor in the G90 alarm panel.
321
+ Index (internal position) of the sensor in the alarm panel.
325
322
 
326
323
  :return: Internal sensor position
327
- :rtype: int
328
324
  """
329
325
  return self._protocol_data.index
330
326
 
331
327
  @property
332
- def subindex(self):
328
+ def subindex(self) -> int:
333
329
  """
334
- Returns index of the sensor within multi-node device.
330
+ Index of the sensor within multi-node device.
335
331
 
336
332
  :return: Index of sensor in multi-node device.
337
- :rtype: int
338
333
  """
339
334
  return self._subindex
340
335
 
341
336
  @property
342
- def supports_enable_disable(self):
337
+ def supports_enable_disable(self) -> bool:
343
338
  """
344
339
  Indicates if disabling/enabling the sensor is supported.
345
340
 
346
341
  :return: Support for enabling/disabling the sensor
347
- :rtype: bool
348
342
  """
349
343
  return self._definition is not None
350
344
 
351
345
  @property
352
- def enabled(self):
346
+ def enabled(self) -> bool:
353
347
  """
354
348
  Indicates if the sensor is enabled.
355
349
 
356
350
  :return: If sensor is enabled
357
- :rtype: bool
358
351
  """
359
- return self.user_flag & G90SensorUserFlags.ENABLED
352
+ return self.user_flag & G90SensorUserFlags.ENABLED != 0
360
353
 
361
- async def set_enabled(self, value):
354
+ async def set_enabled(self, value: bool) -> None:
362
355
  """
363
356
  Sets disabled/enabled state of the sensor.
364
357
 
365
- :param bool value: Whether to enable or disable the sensor
358
+ :param value: Whether to enable or disable the sensor
366
359
  """
367
- if not self.supports_enable_disable:
360
+ # Checking private attribute directly, since `mypy` doesn't recognize
361
+ # the check for sensor definition to be defined if done over
362
+ # `self.supports_enable_disable` property
363
+ if not self._definition:
368
364
  _LOGGER.warning(
369
365
  'Manipulating with enable/disable for sensor index=%s'
370
366
  ' is unsupported - no sensor definition for'
@@ -388,7 +384,7 @@ class G90Sensor: # pylint:disable=too-many-instance-attributes
388
384
  G90Commands.GETSENSORLIST,
389
385
  start=self._proto_idx, end=self._proto_idx
390
386
  )
391
- sensors = [x async for x in sensors_result]
387
+ sensors = [x.data async for x in sensors_result]
392
388
 
393
389
  # Abort if sensor is not found
394
390
  if not sensors:
@@ -401,7 +397,7 @@ class G90Sensor: # pylint:disable=too-many-instance-attributes
401
397
 
402
398
  # Compare actual sensor data from what the sensor has been instantiated
403
399
  # from, and abort the operation if out-of-band changes are detected.
404
- _sensor_pos, sensor_data = sensors[0]
400
+ sensor_data = sensors[0]
405
401
  if self._protocol_incoming_data_kls(
406
402
  *sensor_data
407
403
  ) != self._protocol_data:
@@ -422,7 +418,7 @@ class G90Sensor: # pylint:disable=too-many-instance-attributes
422
418
  user_flag &= ~G90SensorUserFlags.ENABLED
423
419
 
424
420
  # Re-instantiate the protocol data with modified user flags
425
- _data = self._protocol_data._asdict()
421
+ _data = asdict(self._protocol_data)
426
422
  _data['user_flag_data'] = user_flag
427
423
  self._protocol_data = self._protocol_incoming_data_kls(**_data)
428
424
 
@@ -454,30 +450,52 @@ class G90Sensor: # pylint:disable=too-many-instance-attributes
454
450
  )
455
451
  # Modify the sensor
456
452
  await self._parent.command(
457
- G90Commands.SETSINGLESENSOR, list(outgoing_data)
453
+ G90Commands.SETSINGLESENSOR, list(astuple(outgoing_data))
458
454
  )
459
455
 
460
456
  @property
461
- def extra_data(self):
457
+ def extra_data(self) -> Any:
462
458
  """
463
- Sets extra data for the sensor, that can be used to store
459
+ Extra data for the sensor, that can be used to store
464
460
  caller-specific information and will be carried by the sensor instance.
465
461
  """
466
462
  return self._extra_data
467
463
 
468
464
  @extra_data.setter
469
- def extra_data(self, val):
465
+ def extra_data(self, val: Any) -> None:
470
466
  self._extra_data = val
471
467
 
472
- def __repr__(self):
468
+ def _asdict(self) -> Dict[str, Any]:
469
+ """
470
+ Returns dictionary representation of the sensor.
471
+
472
+ :return: Dictionary representation
473
+ """
474
+ return {
475
+ 'name': self.name,
476
+ 'type': self.type,
477
+ 'subtype': self.subtype,
478
+ 'index': self.index,
479
+ 'subindex': self.subindex,
480
+ 'node_count': self.node_count,
481
+ 'protocol': self.protocol,
482
+ 'occupancy': self.occupancy,
483
+ 'user_flag': self.user_flag,
484
+ 'reserved': self.reserved,
485
+ 'extra_data': self.extra_data,
486
+ 'enabled': self.enabled,
487
+ 'supports_enable_disable': self.supports_enable_disable,
488
+ }
489
+
490
+ def __repr__(self) -> str:
473
491
  """
474
492
  Returns string representation of the sensor.
475
493
 
476
494
  :return: String representation
477
- :rtype: str
478
495
  """
479
496
  return super().__repr__() + f"(name='{str(self.name)}'" \
480
497
  f' type={str(self.type)}' \
498
+ f' subtype={str(self.subtype)}' \
481
499
  f' protocol={str(self.protocol)}' \
482
500
  f' occupancy={self.occupancy}' \
483
501
  f' user flag={str(self.user_flag)}' \
pyg90alarm/history.py CHANGED
@@ -21,9 +21,9 @@
21
21
  """
22
22
  History protocol entity.
23
23
  """
24
-
24
+ from typing import Any, Optional, Dict
25
+ from dataclasses import dataclass
25
26
  from datetime import datetime, timezone
26
- from collections import namedtuple
27
27
  from .const import (
28
28
  G90AlertTypes,
29
29
  G90AlertSources,
@@ -65,52 +65,50 @@ states_mapping = {
65
65
  G90HistoryStates.WIFI_DISCONNECTED,
66
66
  }
67
67
 
68
- INCOMING_FIELDS = [
69
- 'type',
70
- 'event_id',
71
- 'source',
72
- 'state',
73
- 'sensor_name',
74
- 'unix_time',
75
- 'other',
76
- ]
77
- # Class representing the data incoming from the device
78
- ProtocolData = namedtuple('ProtocolData', INCOMING_FIELDS)
68
+
69
+ @dataclass
70
+ class ProtocolData:
71
+ """
72
+ Class representing the data incoming from the device
73
+
74
+ :meta private:
75
+ """
76
+ type: G90AlertTypes
77
+ event_id: G90AlertStateChangeTypes
78
+ source: G90AlertSources
79
+ state: int
80
+ sensor_name: str
81
+ unix_time: int
82
+ other: str
79
83
 
80
84
 
81
85
  class G90History:
82
86
  """
83
- tbd
87
+ Represents a history entry from the alarm panel.
84
88
  """
85
- def __init__(self, *args, **kwargs):
89
+ def __init__(self, *args: Any, **kwargs: Any):
86
90
  self._protocol_data = ProtocolData(*args, **kwargs)
87
91
 
88
92
  @property
89
- def datetime(self):
93
+ def datetime(self) -> datetime:
90
94
  """
91
95
  Date/time of the history entry.
92
-
93
- :rtype: :class:`datetime.datetime`
94
96
  """
95
97
  return datetime.fromtimestamp(
96
98
  self._protocol_data.unix_time, tz=timezone.utc
97
99
  )
98
100
 
99
101
  @property
100
- def type(self):
102
+ def type(self) -> G90AlertTypes:
101
103
  """
102
104
  Type of the history entry.
103
-
104
- :rtype: :class:`.G90AlertTypes`
105
105
  """
106
106
  return G90AlertTypes(self._protocol_data.type)
107
107
 
108
108
  @property
109
- def state(self):
109
+ def state(self) -> G90HistoryStates:
110
110
  """
111
111
  State for the history entry.
112
-
113
- :rtype: :class:`.G90HistoryStates`
114
112
  """
115
113
  # Door open/close type, mapped against `G90AlertStates` using `state`
116
114
  # incoming field
@@ -140,11 +138,9 @@ class G90History:
140
138
  )
141
139
 
142
140
  @property
143
- def source(self):
141
+ def source(self) -> G90AlertSources:
144
142
  """
145
143
  Source of the history entry.
146
-
147
- :rtype: :class:`.G90AlertSources`
148
144
  """
149
145
  # Device state changes or open/close events are mapped against
150
146
  # `G90AlertSources` using `source` incoming field
@@ -162,22 +158,18 @@ class G90History:
162
158
  return G90AlertSources.DEVICE
163
159
 
164
160
  @property
165
- def sensor_name(self):
161
+ def sensor_name(self) -> Optional[str]:
166
162
  """
167
163
  Name of the sensor related to the history entry, might be empty if none
168
164
  associated.
169
-
170
- :rtype: str|None
171
165
  """
172
166
  return self._protocol_data.sensor_name or None
173
167
 
174
168
  @property
175
- def sensor_idx(self):
169
+ def sensor_idx(self) -> Optional[int]:
176
170
  """
177
171
  ID of the sensor related to the history entry, might be empty if none
178
172
  associated.
179
-
180
- :rtype: str|None
181
173
  """
182
174
  # Sensor ID will only be available if entry source is a sensor
183
175
  if self.source == G90AlertSources.SENSOR:
@@ -185,12 +177,10 @@ class G90History:
185
177
 
186
178
  return None
187
179
 
188
- def as_device_alert(self):
180
+ def as_device_alert(self) -> G90DeviceAlert:
189
181
  """
190
182
  Returns the history entry represented as device alert structure,
191
183
  suitable for :meth:`G90DeviceNotifications._handle_alert`.
192
-
193
- :rtype: :class:`.G90DeviceAlert`
194
184
  """
195
185
  return G90DeviceAlert(
196
186
  type=self._protocol_data.type,
@@ -198,17 +188,28 @@ class G90History:
198
188
  source=self._protocol_data.source,
199
189
  state=self._protocol_data.state,
200
190
  zone_name=self._protocol_data.sensor_name,
201
- device_id=None,
191
+ device_id='',
202
192
  unix_time=self._protocol_data.unix_time,
203
- resv4=None,
193
+ resv4=0,
204
194
  other=self._protocol_data.other
205
195
  )
206
196
 
207
- def __repr__(self):
197
+ def _asdict(self) -> Dict[str, Any]:
208
198
  """
209
- Textural representation of the history entry.
199
+ Returns the history entry as dictionary.
200
+ """
201
+ return {
202
+ 'type': self.type,
203
+ 'source': self.source,
204
+ 'state': self.state,
205
+ 'sensor_name': self.sensor_name,
206
+ 'sensor_idx': self.sensor_idx,
207
+ 'datetime': self.datetime,
208
+ }
210
209
 
211
- :rtype: str
210
+ def __repr__(self) -> str:
211
+ """
212
+ Textural representation of the history entry.
212
213
  """
213
214
  return f'type={repr(self.type)}' \
214
215
  + f' source={repr(self.source)}' \