pyg90alarm 1.15.1__tar.gz → 1.15.2__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.15.1 → pyg90alarm-1.15.2}/PKG-INFO +1 -1
  2. {pyg90alarm-1.15.1 → pyg90alarm-1.15.2}/src/pyg90alarm/alarm.py +14 -8
  3. {pyg90alarm-1.15.1 → pyg90alarm-1.15.2}/src/pyg90alarm/const.py +2 -1
  4. {pyg90alarm-1.15.1 → pyg90alarm-1.15.2}/src/pyg90alarm/device_notifications.py +55 -9
  5. {pyg90alarm-1.15.1 → pyg90alarm-1.15.2}/src/pyg90alarm.egg-info/PKG-INFO +1 -1
  6. {pyg90alarm-1.15.1 → pyg90alarm-1.15.2}/tests/test_alarm.py +14 -14
  7. {pyg90alarm-1.15.1 → pyg90alarm-1.15.2}/tests/test_notifications.py +104 -16
  8. {pyg90alarm-1.15.1 → pyg90alarm-1.15.2}/.github/CODEOWNERS +0 -0
  9. {pyg90alarm-1.15.1 → pyg90alarm-1.15.2}/.github/workflows/main.yml +0 -0
  10. {pyg90alarm-1.15.1 → pyg90alarm-1.15.2}/.gitignore +0 -0
  11. {pyg90alarm-1.15.1 → pyg90alarm-1.15.2}/.pylintrc +0 -0
  12. {pyg90alarm-1.15.1 → pyg90alarm-1.15.2}/.readthedocs.yaml +0 -0
  13. {pyg90alarm-1.15.1 → pyg90alarm-1.15.2}/LICENSE +0 -0
  14. {pyg90alarm-1.15.1 → pyg90alarm-1.15.2}/MANIFEST.in +0 -0
  15. {pyg90alarm-1.15.1 → pyg90alarm-1.15.2}/README.rst +0 -0
  16. {pyg90alarm-1.15.1 → pyg90alarm-1.15.2}/docs/.DS_Store +0 -0
  17. {pyg90alarm-1.15.1 → pyg90alarm-1.15.2}/docs/.gitignore +0 -0
  18. {pyg90alarm-1.15.1 → pyg90alarm-1.15.2}/docs/api-docs.rst +0 -0
  19. {pyg90alarm-1.15.1 → pyg90alarm-1.15.2}/docs/conf.py +0 -0
  20. {pyg90alarm-1.15.1 → pyg90alarm-1.15.2}/docs/index.rst +0 -0
  21. {pyg90alarm-1.15.1 → pyg90alarm-1.15.2}/docs/protocol.rst +0 -0
  22. {pyg90alarm-1.15.1 → pyg90alarm-1.15.2}/docs/requirements.txt +0 -0
  23. {pyg90alarm-1.15.1 → pyg90alarm-1.15.2}/pyproject.toml +0 -0
  24. {pyg90alarm-1.15.1 → pyg90alarm-1.15.2}/setup.cfg +0 -0
  25. {pyg90alarm-1.15.1 → pyg90alarm-1.15.2}/setup.py +0 -0
  26. {pyg90alarm-1.15.1 → pyg90alarm-1.15.2}/sonar-project.properties +0 -0
  27. {pyg90alarm-1.15.1 → pyg90alarm-1.15.2}/src/pyg90alarm/__init__.py +0 -0
  28. {pyg90alarm-1.15.1 → pyg90alarm-1.15.2}/src/pyg90alarm/base_cmd.py +0 -0
  29. {pyg90alarm-1.15.1 → pyg90alarm-1.15.2}/src/pyg90alarm/callback.py +0 -0
  30. {pyg90alarm-1.15.1 → pyg90alarm-1.15.2}/src/pyg90alarm/config.py +0 -0
  31. {pyg90alarm-1.15.1 → pyg90alarm-1.15.2}/src/pyg90alarm/definitions/__init__.py +0 -0
  32. {pyg90alarm-1.15.1 → pyg90alarm-1.15.2}/src/pyg90alarm/definitions/sensors.py +0 -0
  33. {pyg90alarm-1.15.1 → pyg90alarm-1.15.2}/src/pyg90alarm/discovery.py +0 -0
  34. {pyg90alarm-1.15.1 → pyg90alarm-1.15.2}/src/pyg90alarm/entities/__init__.py +0 -0
  35. {pyg90alarm-1.15.1 → pyg90alarm-1.15.2}/src/pyg90alarm/entities/device.py +0 -0
  36. {pyg90alarm-1.15.1 → pyg90alarm-1.15.2}/src/pyg90alarm/entities/sensor.py +0 -0
  37. {pyg90alarm-1.15.1 → pyg90alarm-1.15.2}/src/pyg90alarm/exceptions.py +0 -0
  38. {pyg90alarm-1.15.1 → pyg90alarm-1.15.2}/src/pyg90alarm/history.py +0 -0
  39. {pyg90alarm-1.15.1 → pyg90alarm-1.15.2}/src/pyg90alarm/host_info.py +0 -0
  40. {pyg90alarm-1.15.1 → pyg90alarm-1.15.2}/src/pyg90alarm/host_status.py +0 -0
  41. {pyg90alarm-1.15.1 → pyg90alarm-1.15.2}/src/pyg90alarm/paginated_cmd.py +0 -0
  42. {pyg90alarm-1.15.1 → pyg90alarm-1.15.2}/src/pyg90alarm/paginated_result.py +0 -0
  43. {pyg90alarm-1.15.1 → pyg90alarm-1.15.2}/src/pyg90alarm/py.typed +0 -0
  44. {pyg90alarm-1.15.1 → pyg90alarm-1.15.2}/src/pyg90alarm/targeted_discovery.py +0 -0
  45. {pyg90alarm-1.15.1 → pyg90alarm-1.15.2}/src/pyg90alarm/user_data_crc.py +0 -0
  46. {pyg90alarm-1.15.1 → pyg90alarm-1.15.2}/src/pyg90alarm.egg-info/SOURCES.txt +0 -0
  47. {pyg90alarm-1.15.1 → pyg90alarm-1.15.2}/src/pyg90alarm.egg-info/dependency_links.txt +0 -0
  48. {pyg90alarm-1.15.1 → pyg90alarm-1.15.2}/src/pyg90alarm.egg-info/requires.txt +0 -0
  49. {pyg90alarm-1.15.1 → pyg90alarm-1.15.2}/src/pyg90alarm.egg-info/top_level.txt +0 -0
  50. {pyg90alarm-1.15.1 → pyg90alarm-1.15.2}/tests/__init__.py +0 -0
  51. {pyg90alarm-1.15.1 → pyg90alarm-1.15.2}/tests/conftest.py +0 -0
  52. {pyg90alarm-1.15.1 → pyg90alarm-1.15.2}/tests/device_mock.py +0 -0
  53. {pyg90alarm-1.15.1 → pyg90alarm-1.15.2}/tests/test_base_commands.py +0 -0
  54. {pyg90alarm-1.15.1 → pyg90alarm-1.15.2}/tests/test_discovery.py +0 -0
  55. {pyg90alarm-1.15.1 → pyg90alarm-1.15.2}/tests/test_paginated_commands.py +0 -0
  56. {pyg90alarm-1.15.1 → pyg90alarm-1.15.2}/tox.ini +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pyg90alarm
3
- Version: 1.15.1
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
@@ -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)
@@ -140,11 +141,14 @@ class G90Alarm(G90DeviceNotifications):
140
141
  # pylint: disable=too-many-instance-attributes,too-many-arguments
141
142
  def __init__(self, host: str, port: int = REMOTE_PORT,
142
143
  reset_occupancy_interval: float = 3.0,
143
- notifications_host: str = '0.0.0.0',
144
- notifications_port: int = NOTIFICATIONS_PORT):
145
- super().__init__(host=notifications_host, port=notifications_port)
146
- self._host = host
147
- 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
148
152
  self._sensors: List[G90Sensor] = []
149
153
  self._devices: List[G90Device] = []
150
154
  self._notifications: Optional[G90DeviceNotifications] = None
@@ -336,7 +340,9 @@ class G90Alarm(G90DeviceNotifications):
336
340
  :return: Device information
337
341
  """
338
342
  res = await self.command(G90Commands.GETHOSTINFO)
339
- return G90HostInfo(*res)
343
+ info = G90HostInfo(*res)
344
+ self.device_id = info.host_guid
345
+ return info
340
346
 
341
347
  @property
342
348
  async def host_status(self) -> G90HostStatus:
@@ -813,7 +819,7 @@ class G90Alarm(G90DeviceNotifications):
813
819
  # code as alert, as if it came from the device and its
814
820
  # notifications port
815
821
  self._handle_alert(
816
- (self._host, self._notifications_port),
822
+ (self._host, self._notifications_local_port),
817
823
  item.as_device_alert()
818
824
  )
819
825
 
@@ -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.1
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
@@ -308,8 +308,8 @@ async def test_sensor_event(mock_device: DeviceMock) -> None:
308
308
  reset_interval = 0.5
309
309
  g90 = G90Alarm(host=mock_device.host, port=mock_device.port,
310
310
  reset_occupancy_interval=reset_interval,
311
- notifications_host=mock_device.notification_host,
312
- notifications_port=mock_device.notification_port)
311
+ notifications_local_host=mock_device.notification_host,
312
+ notifications_local_port=mock_device.notification_port)
313
313
 
314
314
  sensors = await g90.get_sensors()
315
315
  prop_sensors = await g90.sensors
@@ -352,8 +352,8 @@ async def test_sensor_low_battery_event(mock_device: DeviceMock) -> None:
352
352
  Tests for sensor low battery callback.
353
353
  """
354
354
  g90 = G90Alarm(host=mock_device.host, port=mock_device.port,
355
- notifications_host=mock_device.notification_host,
356
- notifications_port=mock_device.notification_port)
355
+ notifications_local_host=mock_device.notification_host,
356
+ notifications_local_port=mock_device.notification_port)
357
357
 
358
358
  sensors = await g90.get_sensors()
359
359
  prop_sensors = await g90.sensors
@@ -401,8 +401,8 @@ async def test_armdisarm_callback(mock_device: DeviceMock) -> None:
401
401
  armdisarm_cb = MagicMock()
402
402
  armdisarm_cb.side_effect = lambda *args: future.set_result(True)
403
403
  g90 = G90Alarm(host=mock_device.host, port=mock_device.port,
404
- notifications_host=mock_device.notification_host,
405
- notifications_port=mock_device.notification_port)
404
+ notifications_local_host=mock_device.notification_host,
405
+ notifications_local_port=mock_device.notification_port)
406
406
  g90.armdisarm_callback = armdisarm_cb
407
407
  await g90.listen_device_notifications()
408
408
  await mock_device.send_next_notification()
@@ -434,8 +434,8 @@ async def test_door_open_close_callback(mock_device: DeviceMock) -> None:
434
434
  door_open_close_cb.side_effect = lambda *args: future.set_result(True)
435
435
 
436
436
  g90 = G90Alarm(host=mock_device.host, port=mock_device.port,
437
- notifications_host=mock_device.notification_host,
438
- notifications_port=mock_device.notification_port)
437
+ notifications_local_host=mock_device.notification_host,
438
+ notifications_local_port=mock_device.notification_port)
439
439
  g90.door_open_close_callback = door_open_close_cb
440
440
 
441
441
  # Simulate two device alerts - for opening (this one) and then closing the
@@ -492,8 +492,8 @@ async def test_alarm_callback(mock_device: DeviceMock) -> None:
492
492
  alarm_cb.side_effect = lambda *args: future.set_result(True)
493
493
 
494
494
  g90 = G90Alarm(host=mock_device.host, port=mock_device.port,
495
- notifications_host=mock_device.notification_host,
496
- notifications_port=mock_device.notification_port)
495
+ notifications_local_host=mock_device.notification_host,
496
+ notifications_local_port=mock_device.notification_port)
497
497
  sensors = await g90.get_sensors()
498
498
  # Set extra data for the 1st sensor
499
499
  sensors[0].extra_data = 'Dummy extra data'
@@ -753,8 +753,8 @@ async def test_sms_alert_when_armed(mock_device: DeviceMock) -> None:
753
753
  armdisarm_cb = MagicMock()
754
754
  armdisarm_cb.side_effect = lambda *args: future.set_result(True)
755
755
  g90 = G90Alarm(host=mock_device.host, port=mock_device.port,
756
- notifications_host=mock_device.notification_host,
757
- notifications_port=mock_device.notification_port)
756
+ notifications_local_host=mock_device.notification_host,
757
+ notifications_local_port=mock_device.notification_port)
758
758
  g90.armdisarm_callback = armdisarm_cb
759
759
  g90.sms_alert_when_armed = True
760
760
  await g90.listen_device_notifications()
@@ -787,8 +787,8 @@ async def test_sms_alert_when_disarmed(mock_device: DeviceMock) -> None:
787
787
  armdisarm_cb = MagicMock()
788
788
  armdisarm_cb.side_effect = lambda *args: future.set_result(True)
789
789
  g90 = G90Alarm(host=mock_device.host, port=mock_device.port,
790
- notifications_host=mock_device.notification_host,
791
- notifications_port=mock_device.notification_port)
790
+ notifications_local_host=mock_device.notification_host,
791
+ notifications_local_port=mock_device.notification_port)
792
792
  g90.armdisarm_callback = armdisarm_cb
793
793
  g90.sms_alert_when_armed = True
794
794
  await g90.listen_device_notifications()
@@ -10,6 +10,7 @@ from pytest import LogCaptureFixture
10
10
  from pyg90alarm.device_notifications import (
11
11
  G90DeviceNotifications,
12
12
  )
13
+ from pyg90alarm.alarm import G90Alarm
13
14
 
14
15
  from .device_mock import DeviceMock
15
16
 
@@ -24,7 +25,8 @@ async def test_device_notification_missing_header(
24
25
  Verifies that missing header in device notification is handled correctly.
25
26
  """
26
27
  notifications = G90DeviceNotifications(
27
- host=mock_device.notification_host, port=mock_device.notification_port
28
+ local_host=mock_device.notification_host,
29
+ local_port=mock_device.notification_port
28
30
  )
29
31
  caplog.set_level('ERROR')
30
32
  await notifications.listen()
@@ -49,7 +51,8 @@ async def test_device_notification_malformed_message(
49
51
  correctly.
50
52
  """
51
53
  notifications = G90DeviceNotifications(
52
- host=mock_device.notification_host, port=mock_device.notification_port
54
+ local_host=mock_device.notification_host,
55
+ local_port=mock_device.notification_port
53
56
  )
54
57
  caplog.set_level('ERROR')
55
58
  await notifications.listen()
@@ -72,7 +75,8 @@ async def test_device_notification_missing_end_marker(
72
75
  correctly.
73
76
  """
74
77
  notifications = G90DeviceNotifications(
75
- host=mock_device.notification_host, port=mock_device.notification_port
78
+ local_host=mock_device.notification_host,
79
+ local_port=mock_device.notification_port
76
80
  )
77
81
  caplog.set_level('ERROR')
78
82
  await notifications.listen()
@@ -91,7 +95,8 @@ async def test_wrong_device_notification_format(
91
95
  Verifies that wrong device notification format is handled correctly.
92
96
  """
93
97
  notifications = G90DeviceNotifications(
94
- host=mock_device.notification_host, port=mock_device.notification_port
98
+ local_host=mock_device.notification_host,
99
+ local_port=mock_device.notification_port
95
100
  )
96
101
  caplog.set_level('ERROR')
97
102
  await notifications.listen()
@@ -114,7 +119,8 @@ async def test_wrong_device_alert_format(
114
119
  Verifies that wrong device alert format is handled correctly.
115
120
  """
116
121
  notifications = G90DeviceNotifications(
117
- host=mock_device.notification_host, port=mock_device.notification_port
122
+ local_host=mock_device.notification_host,
123
+ local_port=mock_device.notification_port
118
124
  )
119
125
 
120
126
  caplog.set_level('ERROR')
@@ -140,7 +146,8 @@ async def test_unknown_device_notification(
140
146
  Verifies that unknown device notification is handled correctly.
141
147
  """
142
148
  notifications = G90DeviceNotifications(
143
- host=mock_device.notification_host, port=mock_device.notification_port
149
+ local_host=mock_device.notification_host,
150
+ local_port=mock_device.notification_port
144
151
  )
145
152
  caplog.set_level('WARNING')
146
153
  await notifications.listen()
@@ -164,7 +171,8 @@ async def test_unknown_device_alert(
164
171
  Verifies that unknown device alert is handled correctly.
165
172
  """
166
173
  notifications = G90DeviceNotifications(
167
- host=mock_device.notification_host, port=mock_device.notification_port
174
+ local_host=mock_device.notification_host,
175
+ local_port=mock_device.notification_port
168
176
  )
169
177
  caplog.set_level('WARNING')
170
178
  await notifications.listen()
@@ -179,6 +187,79 @@ async def test_unknown_device_alert(
179
187
  notifications.close()
180
188
 
181
189
 
190
+ @pytest.mark.g90device(notification_data=[
191
+ b'[208,[999,100,1,1,"Hall","DUMMYGUID",'
192
+ b'1631545189,0,[""]]]\0',
193
+ ])
194
+ async def test_wrong_host(
195
+ mock_device: DeviceMock, caplog: LogCaptureFixture
196
+ ) -> None:
197
+ """
198
+ Verifies that unknown device alert is handled correctly.
199
+ """
200
+ g90 = G90Alarm(
201
+ host='1.2.3.4',
202
+ notifications_local_host=mock_device.notification_host,
203
+ notifications_local_port=mock_device.notification_port
204
+ )
205
+ # pylint: disable=protected-access
206
+ g90._handle_alert = ( # type: ignore[method-assign]
207
+ MagicMock()
208
+ )
209
+ # pylint: disable=protected-access
210
+ g90._handle_notification = ( # type: ignore[method-assign]
211
+ MagicMock()
212
+ )
213
+ caplog.set_level('WARNING')
214
+ await g90.listen()
215
+ await mock_device.send_next_notification()
216
+ assert ''.join(caplog.messages) == (
217
+ "Received notification/alert from wrong host '127.0.0.1'"
218
+ ", expected from '1.2.3.4'"
219
+ )
220
+ g90.close()
221
+ # pylint: disable=protected-access
222
+ g90._handle_alert.assert_not_called()
223
+ # pylint: disable=protected-access
224
+ g90._handle_notification.assert_not_called()
225
+
226
+
227
+ @pytest.mark.g90device(
228
+ sent_data=[
229
+ b'ISTART[206,'
230
+ b'["DUMMYGUID","DUMMYPRODUCT",'
231
+ b'"1.2","1.1","206","206",3,3,0,2,"4242",50,100]]IEND\0',
232
+ ],
233
+ notification_data=[
234
+ b'[208,[2,4,0,0,"","DIFFERENTGUID",1630876128,0,[""]]]\0'
235
+ ],
236
+ )
237
+ async def test_wrong_device_guid(
238
+ mock_device: DeviceMock, caplog: LogCaptureFixture
239
+ ) -> None:
240
+ """
241
+ Verifies that alert from device with different GUID is ignored.
242
+ """
243
+ g90 = G90Alarm(
244
+ host=mock_device.host, port=mock_device.port,
245
+ notifications_local_host=mock_device.notification_host,
246
+ notifications_local_port=mock_device.notification_port
247
+ )
248
+ caplog.set_level('WARNING')
249
+ # The command will fetch the host info and store the GIUD
250
+ await g90.get_host_info()
251
+ g90.on_armdisarm = MagicMock() # type: ignore[method-assign]
252
+ await g90.listen()
253
+ await mock_device.send_next_notification()
254
+ assert ''.join(caplog.messages) == (
255
+ "Received alert from wrong device: expected 'DUMMYGUID'"
256
+ ", got 'DIFFERENTGUID'"
257
+ )
258
+ g90.close()
259
+ # Verify the associated callback was not called
260
+ g90.on_armdisarm.assert_not_called()
261
+
262
+
182
263
  @pytest.mark.g90device(notification_data=[
183
264
  b'[170,[5,[100,"Hall"]]]\0',
184
265
  ])
@@ -188,8 +269,8 @@ async def test_sensor_callback(mock_device: DeviceMock) -> None:
188
269
  """
189
270
  future = asyncio.get_running_loop().create_future()
190
271
  notifications = G90DeviceNotifications(
191
- host=mock_device.notification_host,
192
- port=mock_device.notification_port,
272
+ local_host=mock_device.notification_host,
273
+ local_port=mock_device.notification_port,
193
274
  )
194
275
 
195
276
  notifications.on_sensor_activity = ( # type: ignore[method-assign]
@@ -216,7 +297,8 @@ async def test_armdisarm_notification_callback(
216
297
  """
217
298
  future = asyncio.get_running_loop().create_future()
218
299
  notifications = G90DeviceNotifications(
219
- host=mock_device.notification_host, port=mock_device.notification_port,
300
+ local_host=mock_device.notification_host,
301
+ local_port=mock_device.notification_port
220
302
  )
221
303
  notifications.on_armdisarm = MagicMock() # type: ignore[method-assign]
222
304
  notifications.on_armdisarm.side_effect = (
@@ -238,7 +320,8 @@ async def test_armdisarm_alert_callback(mock_device: DeviceMock) -> None:
238
320
  """
239
321
  future = asyncio.get_running_loop().create_future()
240
322
  notifications = G90DeviceNotifications(
241
- host=mock_device.notification_host, port=mock_device.notification_port,
323
+ local_host=mock_device.notification_host,
324
+ local_port=mock_device.notification_port
242
325
  )
243
326
  notifications.on_armdisarm = MagicMock() # type: ignore[method-assign]
244
327
  notifications.on_armdisarm.side_effect = (
@@ -260,7 +343,8 @@ async def test_door_open_callback(mock_device: DeviceMock) -> None:
260
343
  """
261
344
  future = asyncio.get_running_loop().create_future()
262
345
  notifications = G90DeviceNotifications(
263
- host=mock_device.notification_host, port=mock_device.notification_port,
346
+ local_host=mock_device.notification_host,
347
+ local_port=mock_device.notification_port
264
348
  )
265
349
 
266
350
  notifications.on_door_open_close = ( # type: ignore[method-assign]
@@ -285,7 +369,8 @@ async def test_door_close_callback(mock_device: DeviceMock) -> None:
285
369
  """
286
370
  future = asyncio.get_running_loop().create_future()
287
371
  notifications = G90DeviceNotifications(
288
- host=mock_device.notification_host, port=mock_device.notification_port,
372
+ local_host=mock_device.notification_host,
373
+ local_port=mock_device.notification_port
289
374
  )
290
375
 
291
376
  notifications.on_door_open_close = ( # type: ignore[method-assign]
@@ -312,7 +397,8 @@ async def test_doorbell_callback(mock_device: DeviceMock) -> None:
312
397
  """
313
398
  future = asyncio.get_running_loop().create_future()
314
399
  notifications = G90DeviceNotifications(
315
- host=mock_device.notification_host, port=mock_device.notification_port,
400
+ local_host=mock_device.notification_host,
401
+ local_port=mock_device.notification_port
316
402
  )
317
403
 
318
404
  notifications.on_door_open_close = ( # type: ignore[method-assign]
@@ -339,7 +425,8 @@ async def test_alarm_callback(mock_device: DeviceMock) -> None:
339
425
  """
340
426
  future = asyncio.get_running_loop().create_future()
341
427
  notifications = G90DeviceNotifications(
342
- host=mock_device.notification_host, port=mock_device.notification_port,
428
+ local_host=mock_device.notification_host,
429
+ local_port=mock_device.notification_port
343
430
  )
344
431
  notifications.on_alarm = MagicMock() # type: ignore[method-assign]
345
432
  notifications.on_alarm.side_effect = (
@@ -361,7 +448,8 @@ async def test_low_battery_callback(mock_device: DeviceMock) -> None:
361
448
  """
362
449
  future = asyncio.get_running_loop().create_future()
363
450
  notifications = G90DeviceNotifications(
364
- host=mock_device.notification_host, port=mock_device.notification_port,
451
+ local_host=mock_device.notification_host,
452
+ local_port=mock_device.notification_port
365
453
  )
366
454
  notifications.on_low_battery = MagicMock() # type: ignore[method-assign]
367
455
  notifications.on_low_battery.side_effect = (
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