pyg90alarm 1.15.0__py3-none-any.whl → 1.15.2__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/alarm.py CHANGED
@@ -60,7 +60,8 @@ from .const import (
60
60
  G90Commands, REMOTE_PORT,
61
61
  REMOTE_TARGETED_DISCOVERY_PORT,
62
62
  LOCAL_TARGETED_DISCOVERY_PORT,
63
- NOTIFICATIONS_PORT,
63
+ LOCAL_NOTIFICATIONS_HOST,
64
+ LOCAL_NOTIFICATIONS_PORT,
64
65
  G90ArmDisarmTypes,
65
66
  )
66
67
  from .base_cmd import (G90BaseCommand, G90BaseCommandData)
@@ -100,22 +101,25 @@ if TYPE_CHECKING:
100
101
  Callable[[int, str, bool], None],
101
102
  Callable[[int, str, bool], Coroutine[None, None, None]]
102
103
  ]
103
- SensorStateCallback = Union[
104
- Callable[[bool], None],
105
- Callable[[bool], Coroutine[None, None, None]]
106
- ]
107
104
  LowBatteryCallback = Union[
108
105
  Callable[[int, str], None],
109
106
  Callable[[int, str], Coroutine[None, None, None]]
110
107
  ]
111
- SensorLowBatteryCallback = Union[
112
- Callable[[bool], None],
113
- Callable[[bool], Coroutine[None, None, None]]
114
- ]
115
108
  ArmDisarmCallback = Union[
116
109
  Callable[[G90ArmDisarmTypes], None],
117
110
  Callable[[G90ArmDisarmTypes], Coroutine[None, None, None]]
118
111
  ]
112
+ # Sensor-related callbacks for `G90Sensor` class - despite that class
113
+ # stores them, the invication is done by the `G90Alarm` class hence these
114
+ # are defined here
115
+ SensorStateCallback = Union[
116
+ Callable[[bool], None],
117
+ Callable[[bool], Coroutine[None, None, None]]
118
+ ]
119
+ SensorLowBatteryCallback = Union[
120
+ Callable[[], None],
121
+ Callable[[], Coroutine[None, None, None]]
122
+ ]
119
123
 
120
124
 
121
125
  # pylint: disable=too-many-public-methods
@@ -137,11 +141,14 @@ class G90Alarm(G90DeviceNotifications):
137
141
  # pylint: disable=too-many-instance-attributes,too-many-arguments
138
142
  def __init__(self, host: str, port: int = REMOTE_PORT,
139
143
  reset_occupancy_interval: float = 3.0,
140
- notifications_host: str = '0.0.0.0',
141
- notifications_port: int = NOTIFICATIONS_PORT):
142
- super().__init__(host=notifications_host, port=notifications_port)
143
- self._host = host
144
- self._port = port
144
+ notifications_local_host: str = LOCAL_NOTIFICATIONS_HOST,
145
+ notifications_local_port: int = LOCAL_NOTIFICATIONS_PORT):
146
+ super().__init__(
147
+ local_host=notifications_local_host,
148
+ local_port=notifications_local_port
149
+ )
150
+ self._host: str = host
151
+ self._port: int = port
145
152
  self._sensors: List[G90Sensor] = []
146
153
  self._devices: List[G90Device] = []
147
154
  self._notifications: Optional[G90DeviceNotifications] = None
@@ -333,7 +340,9 @@ class G90Alarm(G90DeviceNotifications):
333
340
  :return: Device information
334
341
  """
335
342
  res = await self.command(G90Commands.GETHOSTINFO)
336
- return G90HostInfo(*res)
343
+ info = G90HostInfo(*res)
344
+ self.device_id = info.host_guid
345
+ return info
337
346
 
338
347
  @property
339
348
  async def host_status(self) -> G90HostStatus:
@@ -810,7 +819,7 @@ class G90Alarm(G90DeviceNotifications):
810
819
  # code as alert, as if it came from the device and its
811
820
  # notifications port
812
821
  self._handle_alert(
813
- (self._host, self._notifications_port),
822
+ (self._host, self._notifications_local_port),
814
823
  item.as_device_alert()
815
824
  )
816
825
 
pyg90alarm/const.py CHANGED
@@ -28,7 +28,8 @@ from typing import Optional
28
28
  REMOTE_PORT = 12368
29
29
  REMOTE_TARGETED_DISCOVERY_PORT = 12900
30
30
  LOCAL_TARGETED_DISCOVERY_PORT = 12901
31
- NOTIFICATIONS_PORT = 12901
31
+ LOCAL_NOTIFICATIONS_HOST = '0.0.0.0'
32
+ LOCAL_NOTIFICATIONS_PORT = 12901
32
33
 
33
34
  CMD_PAGE_SIZE = 10
34
35
 
@@ -41,7 +41,6 @@ from .const import (
41
41
  G90AlertStates,
42
42
  )
43
43
 
44
-
45
44
  _LOGGER = logging.getLogger(__name__)
46
45
 
47
46
 
@@ -107,12 +106,29 @@ class G90DeviceAlert: # pylint: disable=too-many-instance-attributes
107
106
  class G90DeviceNotifications(DatagramProtocol):
108
107
  """
109
108
  Implements support for notifications/alerts sent by alarm panel.
109
+
110
+ There is a basic check to ensure only notifications/alerts from the correct
111
+ device are processed - the check uses the host and port of the device, and
112
+ the device ID (GUID) that is set by the ancestor class that implements the
113
+ commands (e.g. :class:`G90Alarm`). The latter to work correctly needs a
114
+ command to be performed first, one that fetches device GUID and then stores
115
+ it using :attr:`.device_id` (e.g. :meth:`G90Alarm.get_host_info`).
110
116
  """
111
- def __init__(self, port: int, host: str):
117
+ def __init__(self, local_port: int, local_host: str):
112
118
  # pylint: disable=too-many-arguments
113
119
  self._notification_transport: Optional[BaseTransport] = None
114
- self._notifications_host = host
115
- self._notifications_port = port
120
+ self._notifications_local_host = local_host
121
+ self._notifications_local_port = local_port
122
+ # Host/port of the device is configured to communicating via commands.
123
+ # Inteded to validate if notifications/alert are received from the
124
+ # correct device.
125
+ self._host: Optional[str] = None
126
+ self._port: Optional[int] = None
127
+ # Same but for device ID (GUID) - the notifications logic uses it to
128
+ # perform validation, but doesn't set it from messages received (it
129
+ # will diminish the purpose of the validation, should be done by an
130
+ # ancestor class).
131
+ self._device_id: Optional[str] = None
116
132
 
117
133
  def _handle_notification(
118
134
  self, addr: Tuple[str, int], notification: G90Notification
@@ -145,6 +161,15 @@ class G90DeviceNotifications(DatagramProtocol):
145
161
  def _handle_alert(
146
162
  self, addr: Tuple[str, int], alert: G90DeviceAlert
147
163
  ) -> None:
164
+ # Stop processing when alert is received from the device with different
165
+ # GUID
166
+ if self.device_id and alert.device_id != self.device_id:
167
+ _LOGGER.error(
168
+ "Received alert from wrong device: expected '%s', got '%s'",
169
+ self.device_id, alert.device_id
170
+ )
171
+ return
172
+
148
173
  if alert.type == G90AlertTypes.DOOR_OPEN_CLOSE:
149
174
  if alert.state in (
150
175
  G90AlertStates.DOOR_OPEN, G90AlertStates.DOOR_CLOSE
@@ -219,10 +244,17 @@ class G90DeviceNotifications(DatagramProtocol):
219
244
  def datagram_received( # pylint:disable=R0911
220
245
  self, data: bytes, addr: Tuple[str, int]
221
246
  ) -> None:
222
-
223
247
  """
224
- Invoked from datagram is received from the device.
248
+ Invoked when datagram is received from the device.
225
249
  """
250
+ if self._host and self._host != addr[0]:
251
+ _LOGGER.error(
252
+ "Received notification/alert from wrong host '%s',"
253
+ " expected from '%s'",
254
+ addr[0], self._host
255
+ )
256
+ return
257
+
226
258
  s_data = data.decode('utf-8')
227
259
  if not s_data.endswith('\0'):
228
260
  _LOGGER.error('Missing end marker in data')
@@ -304,13 +336,13 @@ class G90DeviceNotifications(DatagramProtocol):
304
336
  loop = asyncio.get_event_loop()
305
337
 
306
338
  _LOGGER.debug('Creating UDP endpoint for %s:%s',
307
- self._notifications_host,
308
- self._notifications_port)
339
+ self._notifications_local_host,
340
+ self._notifications_local_port)
309
341
  (self._notification_transport,
310
342
  _protocol) = await loop.create_datagram_endpoint(
311
343
  lambda: self,
312
344
  local_addr=(
313
- self._notifications_host, self._notifications_port
345
+ self._notifications_local_host, self._notifications_local_port
314
346
  ))
315
347
 
316
348
  @property
@@ -328,3 +360,17 @@ class G90DeviceNotifications(DatagramProtocol):
328
360
  _LOGGER.debug('No longer listening for device notifications')
329
361
  self._notification_transport.close()
330
362
  self._notification_transport = None
363
+
364
+ @property
365
+ def device_id(self) -> Optional[str]:
366
+ """
367
+ The ID (GUID) of the panel being communicated with thru commands.
368
+
369
+ Available when any panel command receives it from the device
370
+ (:meth:`G90Alarm.get_host_info` currently).
371
+ """
372
+ return self._device_id
373
+
374
+ @device_id.setter
375
+ def device_id(self, device_id: str) -> None:
376
+ self._device_id = device_id
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pyg90alarm
3
- Version: 1.15.0
3
+ Version: 1.15.2
4
4
  Summary: G90 Alarm system protocol
5
5
  Home-page: https://github.com/hostcc/pyg90alarm
6
6
  Author: Ilia Sotnikov
@@ -1,10 +1,10 @@
1
1
  pyg90alarm/__init__.py,sha256=5AITRm5jZSzuQaL7PS8fZZMZb4-IuGRhSqyAdfTt0Cs,2236
2
- pyg90alarm/alarm.py,sha256=jCxNzyjfnCUfJl6bSvXih-0YoNtz66EbsYhUvlElFxs,32044
2
+ pyg90alarm/alarm.py,sha256=VssyGxYXb-r-fb8TdN2NfLaIPr1Zq8ogdGUV521bjsQ,32414
3
3
  pyg90alarm/base_cmd.py,sha256=wu2v_RKpcPHxUW4HBDLWcUFMzPsGooY4o2Rc0LgcanU,9754
4
4
  pyg90alarm/callback.py,sha256=3JsD_JChmZD24OyjaCP-PxxuBDBX7myGYhkM4RN7bk4,3742
5
5
  pyg90alarm/config.py,sha256=2YtIgdT7clQXmYvkdn_fhIdS05CY8E1Yc90R8_tAmRI,1961
6
- pyg90alarm/const.py,sha256=4eJyEZpUE5XZkNL1sePKnuPloIvcjKIAJJ4g2e1cXVA,6054
7
- pyg90alarm/device_notifications.py,sha256=I5K8IsyYTe0XDJT8XiQoC8a2xY1XLjaGx4QFrEsaixg,11135
6
+ pyg90alarm/const.py,sha256=1nzx2rueRdAdv1J5nsfS-c_9a4tcM4_z5sWwCNG7tiQ,6097
7
+ pyg90alarm/device_notifications.py,sha256=1nOeG-YrtaoD5wsJUfUM1kV7JIXv3KwnN2QuJ-C--kQ,13238
8
8
  pyg90alarm/discovery.py,sha256=fwyBHDCKGej06OwhpbVCHYTRU9WWkeYysAFgv3FiwqI,3575
9
9
  pyg90alarm/exceptions.py,sha256=eiOcRe7D18EIPyPFDNU9DdFgbnkwPmkiLl8lGPOhBNw,1475
10
10
  pyg90alarm/history.py,sha256=GnCVRsQNV2x9g6KngBBvvIUnTAIyXKX2nVKF_aFA0oM,7160
@@ -20,8 +20,8 @@ pyg90alarm/definitions/sensors.py,sha256=rKOu21ZpI44xk6aMh_vBjniFqnsNTc1CKwAvnv4
20
20
  pyg90alarm/entities/__init__.py,sha256=hHb6AOiC4Tz--rOWiiICMdLaZDs1Tf_xpWk_HeS_gO4,66
21
21
  pyg90alarm/entities/device.py,sha256=f_LHvKCAqTEebZ4mrRh3CpPUI7o-OvpvOfyTRCbftJs,2818
22
22
  pyg90alarm/entities/sensor.py,sha256=4r8ouAYTZB8ih8I4ncWdQOaifYsRxaC-ukY9jvnrRvk,16139
23
- pyg90alarm-1.15.0.dist-info/LICENSE,sha256=f884inRbeNv-O-hbwz62Ro_1J8xiHRTnJ2cCx6A0WvU,1070
24
- pyg90alarm-1.15.0.dist-info/METADATA,sha256=-NPVNpOmAXyptTOMERM1rWW81EtU0BnXMsklnSsbaqg,7663
25
- pyg90alarm-1.15.0.dist-info/WHEEL,sha256=UvcQYKBHoFqaQd6LKyqHw9fxEolWLQnlzP0h_LgJAfI,91
26
- pyg90alarm-1.15.0.dist-info/top_level.txt,sha256=czHiGxYMyTk5QEDTDb0EpPiKqUMRa8zI4zx58Ii409M,11
27
- pyg90alarm-1.15.0.dist-info/RECORD,,
23
+ pyg90alarm-1.15.2.dist-info/LICENSE,sha256=f884inRbeNv-O-hbwz62Ro_1J8xiHRTnJ2cCx6A0WvU,1070
24
+ pyg90alarm-1.15.2.dist-info/METADATA,sha256=8aflU3AAg8Jb3a0wsqqyumSJPnO_HokLb3ZM3z3coNk,7663
25
+ pyg90alarm-1.15.2.dist-info/WHEEL,sha256=ixB2d4u7mugx_bCBycvM9OzZ5yD7NmPXFRtKlORZS2Y,91
26
+ pyg90alarm-1.15.2.dist-info/top_level.txt,sha256=czHiGxYMyTk5QEDTDb0EpPiKqUMRa8zI4zx58Ii409M,11
27
+ pyg90alarm-1.15.2.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (74.0.0)
2
+ Generator: setuptools (74.1.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5