paradox-alarm-interface 3.3.0__tar.gz → 3.4.2.dev0__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 (115) hide show
  1. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/PKG-INFO +5 -5
  2. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/README.md +2 -2
  3. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/paradox/__init__.py +1 -1
  4. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/paradox/config.py +1 -0
  5. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/paradox/connections/ip/connection.py +8 -7
  6. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/paradox/connections/protocols.py +17 -15
  7. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/paradox/console_scripts/ip150_connection_decrypt.py +61 -27
  8. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/paradox/hardware/__init__.py +1 -0
  9. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/paradox/hardware/common.py +1 -0
  10. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/paradox/hardware/evo/panel.py +40 -28
  11. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/paradox/hardware/spectra_magellan/adapters.py +17 -12
  12. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/paradox/interfaces/ip_interface/interface.py +8 -12
  13. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/paradox/interfaces/mqtt/basic.py +3 -2
  14. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/paradox/lib/async_message_manager.py +6 -4
  15. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/paradox/lib/handlers.py +9 -5
  16. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/paradox/lib/help.py +8 -5
  17. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/paradox/paradox.py +56 -57
  18. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/paradox_alarm_interface.egg-info/PKG-INFO +5 -5
  19. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/paradox_alarm_interface.egg-info/requires.txt +1 -1
  20. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/requirements.txt +1 -1
  21. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/setup.cfg +2 -2
  22. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/LICENSE +0 -0
  23. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/MANIFEST.in +0 -0
  24. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/paradox/connections/__init__.py +0 -0
  25. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/paradox/connections/connection.py +0 -0
  26. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/paradox/connections/handler.py +0 -0
  27. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/paradox/connections/ip/__init__.py +0 -0
  28. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/paradox/connections/ip/commands.py +0 -0
  29. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/paradox/connections/ip/parsers.py +0 -0
  30. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/paradox/connections/ip/stun_session.py +0 -0
  31. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/paradox/connections/serial_connection.py +0 -0
  32. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/paradox/console_scripts/__init__.py +0 -0
  33. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/paradox/console_scripts/pai_dump_memory.py +0 -0
  34. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/paradox/console_scripts/pai_run.py +0 -0
  35. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/paradox/data/__init__.py +0 -0
  36. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/paradox/data/element_type_container.py +0 -0
  37. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/paradox/data/enums.py +0 -0
  38. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/paradox/data/memory_storage.py +0 -0
  39. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/paradox/data/model.py +0 -0
  40. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/paradox/event.py +0 -0
  41. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/paradox/exceptions.py +0 -0
  42. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/paradox/hardware/evo/__init__.py +0 -0
  43. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/paradox/hardware/evo/adapters.py +0 -0
  44. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/paradox/hardware/evo/event.py +0 -0
  45. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/paradox/hardware/evo/models/__init__.py +0 -0
  46. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/paradox/hardware/evo/models/evo192.py +0 -0
  47. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/paradox/hardware/evo/models/evo48.py +0 -0
  48. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/paradox/hardware/evo/models/evo96.py +0 -0
  49. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/paradox/hardware/evo/models/evohd.py +0 -0
  50. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/paradox/hardware/evo/parsers.py +0 -0
  51. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/paradox/hardware/evo/property.py +0 -0
  52. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/paradox/hardware/panel.py +0 -0
  53. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/paradox/hardware/parsers.py +0 -0
  54. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/paradox/hardware/spectra_magellan/__init__.py +0 -0
  55. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/paradox/hardware/spectra_magellan/event.py +0 -0
  56. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/paradox/hardware/spectra_magellan/panel.py +0 -0
  57. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/paradox/hardware/spectra_magellan/parsers.py +0 -0
  58. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/paradox/hardware/spectra_magellan/property.py +0 -0
  59. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/paradox/interfaces/__init__.py +0 -0
  60. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/paradox/interfaces/interface_manager.py +0 -0
  61. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/paradox/interfaces/ip_interface/__init__.py +0 -0
  62. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/paradox/interfaces/ip_interface/client_connection.py +0 -0
  63. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/paradox/interfaces/mqtt/__init__.py +0 -0
  64. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/paradox/interfaces/mqtt/core.py +0 -0
  65. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/paradox/interfaces/mqtt/entities/__init__.py +0 -0
  66. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/paradox/interfaces/mqtt/entities/abstract_entity.py +0 -0
  67. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/paradox/interfaces/mqtt/entities/alarm_control_panel.py +0 -0
  68. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/paradox/interfaces/mqtt/entities/binary_sensors.py +0 -0
  69. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/paradox/interfaces/mqtt/entities/device.py +0 -0
  70. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/paradox/interfaces/mqtt/entities/factory.py +0 -0
  71. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/paradox/interfaces/mqtt/entities/sensor.py +0 -0
  72. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/paradox/interfaces/mqtt/entities/switch.py +0 -0
  73. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/paradox/interfaces/mqtt/helpers.py +0 -0
  74. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/paradox/interfaces/mqtt/homeassistant.py +0 -0
  75. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/paradox/interfaces/text/__init__.py +0 -0
  76. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/paradox/interfaces/text/core.py +0 -0
  77. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/paradox/interfaces/text/dummy.py +0 -0
  78. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/paradox/interfaces/text/gsm.py +0 -0
  79. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/paradox/interfaces/text/homeassistant_notifications.py +0 -0
  80. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/paradox/interfaces/text/pushbullet.py +0 -0
  81. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/paradox/interfaces/text/pushover.py +0 -0
  82. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/paradox/interfaces/text/signal.py +0 -0
  83. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/paradox/lib/__init__.py +0 -0
  84. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/paradox/lib/crypto.py +0 -0
  85. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/paradox/lib/encodings/__init__.py +0 -0
  86. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/paradox/lib/encodings/charmaps/__init__.py +0 -0
  87. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/paradox/lib/encodings/charmaps/ar.py +0 -0
  88. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/paradox/lib/encodings/charmaps/de.py +0 -0
  89. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/paradox/lib/encodings/charmaps/el.py +0 -0
  90. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/paradox/lib/encodings/charmaps/en.py +0 -0
  91. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/paradox/lib/encodings/charmaps/et.py +0 -0
  92. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/paradox/lib/encodings/charmaps/he.py +0 -0
  93. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/paradox/lib/encodings/charmaps/hu.py +0 -0
  94. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/paradox/lib/encodings/charmaps/pl.py +0 -0
  95. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/paradox/lib/encodings/charmaps/pt.py +0 -0
  96. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/paradox/lib/encodings/charmaps/ro.py +0 -0
  97. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/paradox/lib/encodings/charmaps/ru.py +0 -0
  98. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/paradox/lib/encodings/charmaps/tr.py +0 -0
  99. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/paradox/lib/event_filter.py +0 -0
  100. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/paradox/lib/format.py +0 -0
  101. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/paradox/lib/ps.py +0 -0
  102. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/paradox/lib/stun.py +0 -0
  103. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/paradox/lib/utils.py +0 -0
  104. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/paradox/main.py +0 -0
  105. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/paradox/parsers/__init__.py +0 -0
  106. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/paradox/parsers/status.py +0 -0
  107. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/paradox_alarm_interface.egg-info/SOURCES.txt +0 -0
  108. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/paradox_alarm_interface.egg-info/dependency_links.txt +0 -0
  109. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/paradox_alarm_interface.egg-info/entry_points.txt +0 -0
  110. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/paradox_alarm_interface.egg-info/top_level.txt +0 -0
  111. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/paradox_alarm_interface.egg-info/zip-safe +0 -0
  112. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/pyproject.toml +0 -0
  113. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/tests/test_async_queue.py +0 -0
  114. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/tests/test_config.py +0 -0
  115. {paradox-alarm-interface-3.3.0 → paradox_alarm_interface-3.4.2.dev0}/tests/test_paradox.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: paradox-alarm-interface
3
- Version: 3.3.0
3
+ Version: 3.4.2.dev0
4
4
  Summary: Interface to Paradox Alarm Panels
5
5
  Home-page: https://github.com/ParadoxAlarmInterface/pai
6
6
  Author: Jevgeni Kiski, João Paulo Barraca
@@ -18,14 +18,14 @@ Classifier: Programming Language :: Python :: 3.8
18
18
  Classifier: Programming Language :: Python :: 3.9
19
19
  Classifier: Programming Language :: Python :: 3.10
20
20
  Classifier: Programming Language :: Python :: 3.11
21
- Requires-Python: >=3.8
21
+ Requires-Python: <3.12,>=3.8
22
22
  Description-Content-Type: text/markdown
23
23
  License-File: LICENSE
24
24
  Requires-Dist: construct~=2.9.43
25
25
  Requires-Dist: argparse>=1.4.0
26
26
  Requires-Dist: python-slugify>=4.0.1
27
27
  Requires-Dist: pytz>=2021.3
28
- Requires-Dist: paho_mqtt>=1.5.0
28
+ Requires-Dist: paho_mqtt<2,>=1.5.0
29
29
  Requires-Dist: requests>=2.20.0
30
30
  Requires-Dist: pyserial-asyncio>=0.4
31
31
  Provides-Extra: yaml
@@ -107,8 +107,8 @@ See [wiki](https://github.com/ParadoxAlarmInterface/pai/wiki/Installation)
107
107
  ## Tested Environment
108
108
 
109
109
  Tested in the following environment:
110
- * Python 3.6, 3.7, 3.8
111
- * Mosquitto MQTT Broker >v 1.4.8
110
+ * Python 3.6, 3.7, 3.8, 3.9, 3.10, 3.11
111
+ * Mosquitto MQTT Broker > 1.4.8
112
112
  * OrangePi 2G-IOT, NanoPi NEO, and Raspberry Pi 3 through their built in Serial Port (with a level shifter!), or a USB RS232 TTL adapter (CP2102, PL2303, CH340, etc..)
113
113
  * Ubuntu Server 16.04.3 LTS
114
114
  * Paradox MG5050, SP7000 and EVO panels
@@ -65,8 +65,8 @@ See [wiki](https://github.com/ParadoxAlarmInterface/pai/wiki/Installation)
65
65
  ## Tested Environment
66
66
 
67
67
  Tested in the following environment:
68
- * Python 3.6, 3.7, 3.8
69
- * Mosquitto MQTT Broker >v 1.4.8
68
+ * Python 3.6, 3.7, 3.8, 3.9, 3.10, 3.11
69
+ * Mosquitto MQTT Broker > 1.4.8
70
70
  * OrangePi 2G-IOT, NanoPi NEO, and Raspberry Pi 3 through their built in Serial Port (with a level shifter!), or a USB RS232 TTL adapter (CP2102, PL2303, CH340, etc..)
71
71
  * Ubuntu Server 16.04.3 LTS
72
72
  * Paradox MG5050, SP7000 and EVO panels
@@ -1,3 +1,3 @@
1
- VERSION = "3.3.0"
1
+ VERSION = "3.4.2-dev"
2
2
 
3
3
  __version__ = VERSION
@@ -55,6 +55,7 @@ class Config:
55
55
  "IP_CONNECTION_BARE": False, # IP endpoint connects directly to panel. Used for Serial Tunnels over TCP
56
56
  # Paradox
57
57
  "KEEP_ALIVE_INTERVAL": 10, # Interval between status updates
58
+ "IO_TIMEOUT": 0.5, # Timeout for IO operations
58
59
  "LIMITS": {}, # By default all zones will be monitored
59
60
  "LABEL_ENCODING": "paradox-en", # Encoding to use when decoding labels. paradox-* or https://docs.python.org/3/library/codecs.html#standard-encodings
60
61
  "LABEL_REFRESH_INTERVAL": (
@@ -1,17 +1,15 @@
1
- # -*- coding: utf-8 -*-
2
-
1
+ from abc import ABC, abstractmethod
3
2
  import asyncio
4
3
  import logging
5
- from abc import ABC, abstractmethod
6
4
 
7
5
  from construct import Container
8
6
 
7
+ from paradox.config import config as cfg
9
8
  from paradox.connections.connection import Connection
10
9
  from paradox.connections.handler import IPConnectionHandler
11
10
  from paradox.connections.ip.commands import IPModuleConnectCommand
12
11
  from paradox.connections.ip.stun_session import StunSession
13
- from paradox.connections.protocols import (IPConnectionProtocol,
14
- SerialConnectionProtocol)
12
+ from paradox.connections.protocols import IPConnectionProtocol, SerialConnectionProtocol
15
13
  from paradox.exceptions import PAICriticalException
16
14
  from paradox.lib.handlers import FutureHandler, HandlerRegistry
17
15
 
@@ -94,7 +92,7 @@ class IPConnectionWithEncryption(MultiAttemptConnection, IPConnectionHandler, AB
94
92
  def on_ip_message(self, container: Container):
95
93
  return self.loop.create_task(self.ip_handler_registry.handle(container))
96
94
 
97
- async def wait_for_ip_message(self, timeout=2) -> Container:
95
+ async def wait_for_ip_message(self, timeout=cfg.IO_TIMEOUT) -> Container:
98
96
  future = FutureHandler()
99
97
  return await self.ip_handler_registry.wait_until_complete(future, timeout)
100
98
 
@@ -107,7 +105,10 @@ class IPConnectionWithEncryption(MultiAttemptConnection, IPConnectionHandler, AB
107
105
 
108
106
  class LocalIPConnection(IPConnectionWithEncryption):
109
107
  def __init__(
110
- self, host, port, password,
108
+ self,
109
+ host,
110
+ port,
111
+ password,
111
112
  ):
112
113
  super().__init__(password)
113
114
  self.host = host
@@ -1,12 +1,16 @@
1
+ from abc import abstractmethod
1
2
  import asyncio
2
3
  import binascii
3
4
  import logging
4
- from abc import abstractmethod
5
5
 
6
6
  from paradox.config import config as cfg
7
7
  from paradox.connections.handler import ConnectionHandler, IPConnectionHandler
8
- from paradox.connections.ip.parsers import (IPMessageCommand, IPMessageRequest,
9
- IPMessageResponse, IPMessageType)
8
+ from paradox.connections.ip.parsers import (
9
+ IPMessageCommand,
10
+ IPMessageRequest,
11
+ IPMessageResponse,
12
+ IPMessageType,
13
+ )
10
14
 
11
15
  logger = logging.getLogger("PAI").getChild(__name__)
12
16
 
@@ -52,11 +56,11 @@ class ConnectionProtocol(asyncio.Protocol):
52
56
  if self.transport:
53
57
  try:
54
58
  self.transport.close()
55
- except:
59
+ except Exception:
56
60
  logger.exception("Connection transport close raised Exception")
57
61
  self.transport = None
58
62
 
59
- await asyncio.wait_for(self._closed, timeout=1)
63
+ await asyncio.wait_for(self._closed, timeout=cfg.IO_TIMEOUT)
60
64
 
61
65
  @abstractmethod
62
66
  def send_message(self, message):
@@ -94,7 +98,7 @@ class ConnectionProtocol(asyncio.Protocol):
94
98
  class SerialConnectionProtocol(ConnectionProtocol):
95
99
  def send_message(self, message):
96
100
  if cfg.LOGGING_DUMP_PACKETS:
97
- logger.debug("PAI -> SER {}".format(binascii.hexlify(message)))
101
+ logger.debug(f"PAI -> SER {binascii.hexlify(message)}")
98
102
 
99
103
  self.check_active()
100
104
 
@@ -137,7 +141,7 @@ class SerialConnectionProtocol(ConnectionProtocol):
137
141
  if checksum(frame, min_length):
138
142
  self.buffer = self.buffer[len(frame) :] # Remove message
139
143
  if cfg.LOGGING_DUMP_PACKETS:
140
- logger.debug("SER -> PAI {}".format(binascii.hexlify(frame)))
144
+ logger.debug(f"SER -> PAI {binascii.hexlify(frame)}")
141
145
 
142
146
  self.handler.on_message(frame)
143
147
  else:
@@ -146,14 +150,14 @@ class SerialConnectionProtocol(ConnectionProtocol):
146
150
 
147
151
  class IPConnectionProtocol(ConnectionProtocol):
148
152
  def __init__(self, handler: IPConnectionHandler, key):
149
- super(IPConnectionProtocol, self).__init__(handler)
153
+ super().__init__(handler)
150
154
 
151
155
  self.handler = handler
152
156
  self.key = key
153
157
 
154
158
  def send_raw(self, raw):
155
159
  if cfg.LOGGING_DUMP_PACKETS:
156
- logger.debug("PAI -> IP (raw) {}".format(binascii.hexlify(raw)))
160
+ logger.debug(f"PAI -> IP (raw) {binascii.hexlify(raw)}")
157
161
 
158
162
  self.check_active()
159
163
 
@@ -161,7 +165,7 @@ class IPConnectionProtocol(ConnectionProtocol):
161
165
 
162
166
  def send_message(self, message):
163
167
  if cfg.LOGGING_DUMP_PACKETS:
164
- logger.debug("PAI -> IP (payload) {}".format(binascii.hexlify(message)))
168
+ logger.debug(f"PAI -> IP (payload) {binascii.hexlify(message)}")
165
169
 
166
170
  self.check_active()
167
171
 
@@ -180,7 +184,7 @@ class IPConnectionProtocol(ConnectionProtocol):
180
184
  password=self.key,
181
185
  )
182
186
  if cfg.LOGGING_DUMP_PACKETS:
183
- logger.debug("PAI -> IP (raw) {}".format(binascii.hexlify(msg)))
187
+ logger.debug(f"PAI -> IP (raw) {binascii.hexlify(msg)}")
184
188
 
185
189
  self.transport.write(msg)
186
190
 
@@ -188,9 +192,7 @@ class IPConnectionProtocol(ConnectionProtocol):
188
192
  message = IPMessageResponse.parse(data, password=self.key)
189
193
 
190
194
  if cfg.LOGGING_DUMP_PACKETS:
191
- logger.debug(
192
- "IP -> PAI (payload) {}".format(binascii.hexlify(message.payload))
193
- )
195
+ logger.debug(f"IP -> PAI (payload) {binascii.hexlify(message.payload)}")
194
196
 
195
197
  if message.header.message_type == IPMessageType.serial_passthrough_response:
196
198
  self.handler.on_message(message.payload)
@@ -218,7 +220,7 @@ class IPConnectionProtocol(ConnectionProtocol):
218
220
  return
219
221
 
220
222
  if cfg.LOGGING_DUMP_PACKETS:
221
- logger.debug("IP -> PAI (raw) {}".format(binascii.hexlify(self.buffer)))
223
+ logger.debug(f"IP -> PAI (raw) {binascii.hexlify(self.buffer)}")
222
224
 
223
225
  self._process_message(self.buffer)
224
226
  self.buffer = b""
@@ -1,27 +1,32 @@
1
1
  #!/usr/bin/env python3
2
2
  import argparse
3
3
  import binascii
4
- import traceback
5
4
  from collections import OrderedDict
5
+ import re
6
+ import traceback
6
7
 
7
8
  import yaml
8
9
 
9
- from paradox.connections.ip.parsers import (IPMessageCommand, IPMessageRequest,
10
- IPMessageResponse, IPMessageType,
11
- IPPayloadConnectResponse)
10
+ from paradox.connections.ip.parsers import (
11
+ IPMessageCommand,
12
+ IPMessageRequest,
13
+ IPMessageResponse,
14
+ IPMessageType,
15
+ IPPayloadConnectResponse,
16
+ )
12
17
  from paradox.hardware import create_panel
13
18
  from paradox.hardware.parsers import InitiateCommunicationResponse
14
19
 
15
20
 
16
- class Colors: # You may need to change color settings
17
- RED = '\033[31m'
18
- ENDC = '\033[m'
19
- GREEN = '\033[32m'
20
- YELLOW = '\033[33m'
21
- BLUE = '\033[34m'
22
- MAGENTA = '\033[95m'
23
- CYAN = '\033[96m'
24
- ON_WHITE = '\033[47m'
21
+ class Colors: # You may need to change color settings
22
+ RED = "\033[31m"
23
+ ENDC = "\033[m"
24
+ GREEN = "\033[32m"
25
+ YELLOW = "\033[33m"
26
+ BLUE = "\033[34m"
27
+ MAGENTA = "\033[95m"
28
+ CYAN = "\033[96m"
29
+ ON_WHITE = "\033[30m\033[47m"
25
30
 
26
31
 
27
32
  def ordered_load(stream, Loader=yaml.loader.SafeLoader, object_pairs_hook=OrderedDict):
@@ -54,14 +59,22 @@ class PayloadParser:
54
59
  elif message_type == IPMessageType.ip_response:
55
60
  self._parse_ip_response(parsed)
56
61
  except Exception:
62
+ print(f"{Colors.RED}Failed to parse message: \n{parsed}{Colors.ENDC}")
57
63
  traceback.print_exc()
58
64
 
59
65
  def _parse_ip_response(self, parsed):
60
- if parsed.header.command == IPMessageCommand.connect and parsed.header.sub_command == 0:
61
- print(f"{Colors.ON_WHITE}{IPPayloadConnectResponse.parse(parsed.payload)}{Colors.ENDC}")
66
+ if (
67
+ parsed.header.command == IPMessageCommand.connect
68
+ and parsed.header.sub_command == 0
69
+ ):
70
+ print(
71
+ f"{Colors.ON_WHITE}{IPPayloadConnectResponse.parse(parsed.payload)}{Colors.ENDC}"
72
+ )
62
73
  if len(parsed.payload) > 25:
63
74
  p = parsed.payload[25:62]
64
- print(f"{Colors.ON_WHITE}InitiateCommunicationResponse:\n{InitiateCommunicationResponse.parse(p)}{Colors.ENDC}")
75
+ print(
76
+ f"{Colors.ON_WHITE}InitiateCommunicationResponse:\n{InitiateCommunicationResponse.parse(p)}{Colors.ENDC}"
77
+ )
65
78
  elif parsed.header.command == IPMessageCommand.multicommand:
66
79
  self._parse_multicommand(parsed, "frompanel")
67
80
  else:
@@ -74,7 +87,7 @@ class PayloadParser:
74
87
  self._parse_generic_multicommand(parsed, direction)
75
88
  elif parsed.header.sub_command == 0x1: # MGSP multiread
76
89
  self._parse_generic_mgsp_multiread(parsed, direction)
77
- elif parsed.header.sub_command == 0xdd: # Unified multiread
90
+ elif parsed.header.sub_command == 0xDD: # Unified multiread
78
91
  pass
79
92
 
80
93
  def _parse_serial_passthrough_response(self, parsed):
@@ -95,7 +108,9 @@ class PayloadParser:
95
108
  ram_address = parsed_payload.fields.value.address
96
109
  ram_parser = self.panel.get_message("RAMDataParserMap").get(ram_address)
97
110
  if ram_parser is not None:
98
- print(f"{Colors.ON_WHITE}{ram_parser.parse(parsed_payload.fields.value.data)}{Colors.ENDC}")
111
+ print(
112
+ f"{Colors.ON_WHITE}{ram_parser.parse(parsed_payload.fields.value.data)}{Colors.ENDC}"
113
+ )
99
114
  else:
100
115
  print(
101
116
  f"{Colors.RED}No parser for {ram_address} ram address, data: {binascii.hexlify(parsed_payload.fields.value.data)}{Colors.ENDC}"
@@ -105,14 +120,16 @@ class PayloadParser:
105
120
  if parsed.header.command == IPMessageCommand.multicommand:
106
121
  self._parse_multicommand(parsed, "topanel")
107
122
  else:
108
- print(f"{Colors.RED}No parser for ip_request payload: {binascii.hexlify(parsed.payload)}{Colors.ENDC}")
123
+ print(
124
+ f"{Colors.RED}No parser for ip_request payload: {binascii.hexlify(parsed.payload)}{Colors.ENDC}"
125
+ )
109
126
 
110
127
  def _parse_generic_multicommand(self, parsed, direction: str):
111
128
  i = 0
112
129
  while i < len(parsed.payload):
113
130
  cmd_len = parsed.payload[i]
114
131
  i += 1
115
- cmd = parsed.payload[i:i + cmd_len]
132
+ cmd = parsed.payload[i : i + cmd_len]
116
133
  assert len(cmd) == cmd_len
117
134
  i += cmd_len
118
135
  print(f"{Colors.ON_WHITE}Multicommand: {cmd}{Colors.ENDC}")
@@ -130,9 +147,20 @@ class PayloadParser:
130
147
  return parsed_payload
131
148
 
132
149
  def _parse_generic_mgsp_multiread(self, parsed, direction):
133
- print(
134
- f"{Colors.RED}No parser for {direction} mgsp_multiread{Colors.ENDC}"
135
- )
150
+ print(f"{Colors.RED}No parser for {direction} mgsp_multiread{Colors.ENDC}")
151
+
152
+
153
+ def old_yaml_format_traverser(data):
154
+ re_key = re.compile(r"peer(\d+)_(\d+)")
155
+ for key, value in data.items():
156
+ # example peer0_0, where 0 is peer number and 0 is index number
157
+ matches = re_key.match(key)
158
+
159
+ yield {
160
+ "peer": int(matches.group(1)),
161
+ "data": value,
162
+ "index": int(matches.group(2)),
163
+ }
136
164
 
137
165
 
138
166
  def decrypt_file(file, password, max_packets: int = None):
@@ -140,9 +168,16 @@ def decrypt_file(file, password, max_packets: int = None):
140
168
  data = ordered_load(file, yaml.loader.SafeLoader)
141
169
  parser = PayloadParser()
142
170
 
171
+ if "peers" in data and "packets" in data:
172
+ iterator = data["packets"]
173
+ else:
174
+ iterator = old_yaml_format_traverser(data)
175
+
143
176
  n = 0
144
- for key, value in data.items():
145
- if not value[0] == 0xaa:
177
+ for packet in iterator:
178
+ value = packet["data"]
179
+ key = packet["index"]
180
+ if not value[0] == 0xAA:
146
181
  print(f"{Colors.RED}Not an IP packet: {value}{Colors.ENDC}")
147
182
  continue
148
183
  header = value[0:16]
@@ -167,7 +202,6 @@ def decrypt_file(file, password, max_packets: int = None):
167
202
  if parsed.header.sub_command == 0:
168
203
  assert password == parsed.payload, "Wrong decryption password"
169
204
 
170
-
171
205
  if (
172
206
  parsed.header.command == IPMessageCommand.connect
173
207
  and parsed.header.message_type == IPMessageType.ip_response
@@ -187,7 +221,7 @@ def decrypt_file(file, password, max_packets: int = None):
187
221
 
188
222
  print(
189
223
  f"\tpayload: {binascii.hexlify(parsed.payload)}\n",
190
- f"\tpayload_raw: {parsed.payload}"
224
+ f"\tpayload_raw: {parsed.payload}",
191
225
  )
192
226
 
193
227
  print(parsed)
@@ -30,6 +30,7 @@ def create_panel(core, start_communication_response: Container = None) -> Panel:
30
30
  elif product_id in [
31
31
  "SPECTRA_SP4000",
32
32
  "SPECTRA_SP5500",
33
+ "SPECTRA_SP550_PLUS",
33
34
  "SPECTRA_SP6000",
34
35
  "SPECTRA_SP7000",
35
36
  "SPECTRA_SP65",
@@ -44,6 +44,7 @@ ProductIdEnum = Enum(
44
44
  SPECTRA_SP7000=23,
45
45
  SPECTRA_SP4000=26,
46
46
  SPECTRA_SP65=27,
47
+ SPECTRA_SP550_PLUS=28,
47
48
  SPECTRA_SP6000_PLUS=29,
48
49
  SPECTRA_SP7000_PLUS=30,
49
50
  MAGELLAN_MG5000=64,
@@ -1,5 +1,3 @@
1
- # -*- coding: utf-8 -*-
2
-
3
1
  import binascii
4
2
  import inspect
5
3
  import logging
@@ -10,17 +8,26 @@ from construct import ChecksumError, Construct, Container, MappingError
10
8
  from paradox.config import config as cfg
11
9
  from paradox.exceptions import AuthenticationFailed, StatusRequestException
12
10
 
13
- from ..panel import Panel as PanelBase
14
11
  from . import parsers
12
+ from ..panel import Panel as PanelBase
15
13
  from .event import event_map
16
14
  from .property import property_map
17
15
 
18
16
  logger = logging.getLogger("PAI").getChild(__name__)
19
17
 
20
18
  ZONE_ACTIONS = dict(
21
- bypass={"flags": {"bypassed": True}, "operation": "set",},
22
- clear_bypass={"flags": {"bypassed": True}, "operation": "clear",},
23
- clear_alarm_memory={"flags": {"generated_alarm": True}, "operation": "clear",},
19
+ bypass={
20
+ "flags": {"bypassed": True},
21
+ "operation": "set",
22
+ },
23
+ clear_bypass={
24
+ "flags": {"bypassed": True},
25
+ "operation": "clear",
26
+ },
27
+ clear_alarm_memory={
28
+ "flags": {"generated_alarm": True},
29
+ "operation": "clear",
30
+ },
24
31
  )
25
32
 
26
33
 
@@ -33,14 +40,14 @@ class Panel_EVOBase(PanelBase):
33
40
  def __init__(
34
41
  self, core, start_communication_response, variable_message_length=True
35
42
  ):
36
- super(Panel_EVOBase, self).__init__(core, variable_message_length)
43
+ super().__init__(core, variable_message_length)
37
44
 
38
45
  self._populate_settings(start_communication_response)
39
46
 
40
47
  def _populate_settings(self, start_communication_response):
41
48
  raw_data = (
42
- start_communication_response.fields.data
43
- + start_communication_response.checksum
49
+ start_communication_response.fields.data
50
+ + start_communication_response.checksum
44
51
  )
45
52
  parsed = parsers.InitializeCommunication.parse(raw_data)
46
53
  if cfg.LOGGING_DUMP_MESSAGES:
@@ -55,7 +62,7 @@ class Panel_EVOBase(PanelBase):
55
62
  except ResourceWarning:
56
63
  pass
57
64
 
58
- return super(Panel_EVOBase, self).get_message(name)
65
+ return super().get_message(name)
59
66
 
60
67
  async def dump_memory(self, file, memory_type):
61
68
  """
@@ -73,6 +80,12 @@ class Panel_EVOBase(PanelBase):
73
80
  mem_type = "RAM" if ram else "EEPROM"
74
81
  logger.info("Dump " + mem_type)
75
82
 
83
+ def expect(command, address):
84
+ return (
85
+ lambda m: m.fields.value.po.command == command
86
+ and m.fields.value.address == address
87
+ )
88
+
76
89
  packet_length = 64 # 64 is max
77
90
  for address in range_:
78
91
  args = dict(
@@ -82,8 +95,7 @@ class Panel_EVOBase(PanelBase):
82
95
  reply = await self.core.send_wait(
83
96
  parsers.ReadEEPROM,
84
97
  args,
85
- reply_expected=lambda m: m.fields.value.po.command == 0x5
86
- and m.fields.value.address == address,
98
+ reply_expected=expect(0x5, address),
87
99
  )
88
100
 
89
101
  if reply is None:
@@ -101,7 +113,7 @@ class Panel_EVOBase(PanelBase):
101
113
  if message is None or len(message) == 0:
102
114
  return None
103
115
 
104
- parent_parsed = super(Panel_EVOBase, self).parse_message(message, direction)
116
+ parent_parsed = super().parse_message(message, direction)
105
117
  if parent_parsed:
106
118
  return parent_parsed
107
119
 
@@ -147,13 +159,9 @@ class Panel_EVOBase(PanelBase):
147
159
  return parsers.RequestedEvent.parse(message)
148
160
 
149
161
  except ChecksumError as e:
150
- logger.error(
151
- "ChecksumError %s, message: %s" % (str(e), binascii.hexlify(message))
152
- )
153
- except:
154
- logger.exception(
155
- "Exception parsing message: %s" % (binascii.hexlify(message))
156
- )
162
+ logger.error("ChecksumError %s, message: %s", e, binascii.hexlify(message))
163
+ except Exception:
164
+ logger.exception("Exception parsing message: %s", binascii.hexlify(message))
157
165
 
158
166
  return None
159
167
 
@@ -179,13 +187,17 @@ class Panel_EVOBase(PanelBase):
179
187
  "(Babyware) Tab."
180
188
  )
181
189
  raise AuthenticationFailed("Wrong PASSWORD")
182
- else: # command == 0x1
183
- if reply.fields.value.po.status.Winload_connected:
184
- logger.info("Authentication Success")
185
- return True
186
- else:
187
- logger.error("Authentication Failed")
188
- return False
190
+
191
+ if reply.fields.value.po.command == 0x1:
192
+ logger.info("Authentication Success")
193
+
194
+ if not reply.fields.value.po.status.Winload_connected:
195
+ logger.warning("Winload_connected is still False")
196
+
197
+ return True
198
+
199
+ logger.error("Invalid response to InitializeCommunication: %s", reply)
200
+ return False
189
201
 
190
202
  @staticmethod
191
203
  def _request_status_reply_check(message: Container, address: int):
@@ -223,7 +235,7 @@ class Panel_EVOBase(PanelBase):
223
235
  :param str command: textual command
224
236
  :return: True if we have at least one success
225
237
  """
226
- args = dict(partitions=dict((i, command) for i in partitions))
238
+ args = dict(partitions={i: command for i in partitions})
227
239
 
228
240
  try:
229
241
  reply = await self.core.send_wait(
@@ -1,9 +1,8 @@
1
- # -*- coding: utf-8 -*-
2
-
3
1
  import datetime
2
+ from enum import Enum
4
3
 
5
4
  from construct import Adapter
6
- from enum import Enum
5
+
7
6
 
8
7
  class DateAdapter(Adapter):
9
8
  def _decode(self, obj, context, path):
@@ -35,7 +34,6 @@ class PartitionStateAdapter(Adapter):
35
34
  states = dict(arm=4, disarm=5, arm_sleep=3, arm_stay=1, none=0)
36
35
 
37
36
  def _decode(self, obj, context, path):
38
-
39
37
  for k, v in enumerate(self.states):
40
38
  if v == obj[0]:
41
39
  return k
@@ -53,7 +51,6 @@ class ZoneStateAdapter(Adapter):
53
51
  states = dict(bypass=0x10)
54
52
 
55
53
  def _decode(self, obj, context, path):
56
-
57
54
  for k, v in enumerate(self.states):
58
55
  if v == obj[0]:
59
56
  return k
@@ -227,16 +224,24 @@ class PGMDefinitionAdapter(Adapter):
227
224
  disabled = sum(obj) == 0
228
225
 
229
226
  if disabled:
230
- return 'disabled'
227
+ return "disabled"
231
228
 
232
229
  try:
233
- activation_event = PGMDefinitionAdapter.PGMEvent(obj[0]) if sum(obj[:3]) != 0 else 'disabled'
234
- except:
235
- activation_event = 'unknown'
230
+ activation_event = (
231
+ PGMDefinitionAdapter.PGMEvent(obj[0])
232
+ if sum(obj[:3]) != 0
233
+ else "disabled"
234
+ )
235
+ except Exception:
236
+ activation_event = "unknown"
236
237
 
237
238
  try:
238
- deactivation_event = PGMDefinitionAdapter.PGMEvent(obj[3]) if sum(obj[3:]) != 0 else 'disabled'
239
- except:
240
- deactivation_event = 'unknown'
239
+ deactivation_event = (
240
+ PGMDefinitionAdapter.PGMEvent(obj[3])
241
+ if sum(obj[3:]) != 0
242
+ else "disabled"
243
+ )
244
+ except Exception:
245
+ deactivation_event = "unknown"
241
246
 
242
247
  return dict(activation=activation_event, deactivation=deactivation_event)
@@ -1,5 +1,3 @@
1
- # -*- coding: utf-8 -*-
2
-
3
1
  # IP Interface
4
2
 
5
3
  import asyncio
@@ -46,16 +44,14 @@ class IPInterface(Interface):
46
44
  async def run(self):
47
45
  try:
48
46
  self.server = await asyncio.start_server(
49
- self.handle_client, self.addr, self.port, loop=self.alarm.work_loop
47
+ self.handle_client, self.addr, self.port
50
48
  )
51
49
  logger.info(
52
- "IP Interface: serving on {}".format(
53
- self.server.sockets[0].getsockname()
54
- )
50
+ "IP Interface: serving on %s", self.server.sockets[0].getsockname()
55
51
  )
56
52
  logger.info("IP Interface started")
57
53
  except Exception as e:
58
- logger.error("Failed to start IP Interface {}".format(e))
54
+ logger.error("Failed to start IP Interface: %s", e)
59
55
 
60
56
  async def handle_client(self, reader, writer):
61
57
  """
@@ -67,10 +63,10 @@ class IPInterface(Interface):
67
63
  """
68
64
 
69
65
  self.client_nr = (self.client_nr + 1) % 256
70
- logger.info("Client %d connected" % self.client_nr)
66
+ logger.info("Client %d connected", self.client_nr)
71
67
 
72
68
  connection = ClientConnection(reader, writer, self.alarm, self.key)
73
- handler_name = "%s_%d" % (self.name, self.client_nr)
69
+ handler_name = f"{self.name}_{self.client_nr}"
74
70
  self.alarm.connection.register_raw_handler(
75
71
  PersistentHandler(connection.handle_panel_raw_message, name=handler_name)
76
72
  )
@@ -79,10 +75,10 @@ class IPInterface(Interface):
79
75
 
80
76
  try:
81
77
  await connection.handle()
82
- except:
83
- logger.exception("Client %d connection raised exception" % self.client_nr)
78
+ except Exception:
79
+ logger.exception("Client %d connection raised exception", self.client_nr)
84
80
  finally:
85
81
  self.alarm.connection.deregister_raw_handler(handler_name)
86
82
 
87
83
  asyncio.get_event_loop().create_task(self.alarm.resume())
88
- logger.info("Client %d disconnected" % self.client_nr)
84
+ logger.info("Client %d disconnected", self.client_nr)