A clone of: https://github.com/nutechsoftware/alarmdecoder This is requires as they dropped support for older firmware releases w/o building in backward compatibility code, and they had previously hardcoded pyserial to a python2 only version.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

224 lines
6.2 KiB

  1. """
  2. Message representations received from the panel through the AD2USB.
  3. """
  4. import re
  5. class Message(object):
  6. """
  7. Represents a message from the alarm panel.
  8. """
  9. def __init__(self, data=None):
  10. """
  11. Constructor
  12. :param data: Message data to parse.
  13. :type data: str
  14. """
  15. self.ready = False
  16. self.armed_away = False
  17. self.armed_home = False
  18. self.backlight_on = False
  19. self.programming_mode = False
  20. self.beeps = -1
  21. self.zone_bypassed = False
  22. self.ac_power = False
  23. self.chime_on = False
  24. self.alarm_event_occurred = False
  25. self.alarm_sounding = False
  26. self.battery_low = False
  27. self.entry_delay_off = False
  28. self.fire_alarm = False
  29. self.check_zone = False
  30. self.perimeter_only = False
  31. self.numeric_code = ""
  32. self.text = ""
  33. self.cursor_location = -1
  34. self.data = ""
  35. self.mask = ""
  36. self.bitfield = ""
  37. self.panel_data = ""
  38. self._regex = re.compile('("(?:[^"]|"")*"|[^,]*),("(?:[^"]|"")*"|[^,]*),("(?:[^"]|"")*"|[^,]*),("(?:[^"]|"")*"|[^,]*)')
  39. if data is not None:
  40. self._parse_message(data)
  41. def _parse_message(self, data):
  42. """
  43. Parse the message from the device.
  44. :param data: The message data.
  45. :type data: str
  46. :raises: util.InvalidMessageError
  47. """
  48. m = self._regex.match(data)
  49. if m is None:
  50. raise util.InvalidMessageError('Received invalid message: {0}'.format(data))
  51. self.bitfield, self.numeric_code, self.panel_data, alpha = m.group(1, 2, 3, 4)
  52. self.mask = int(self.panel_data[3:3+8], 16)
  53. self.data = data
  54. self.ready = not self.bitfield[1:2] == "0"
  55. self.armed_away = not self.bitfield[2:3] == "0"
  56. self.armed_home = not self.bitfield[3:4] == "0"
  57. self.backlight_on = not self.bitfield[4:5] == "0"
  58. self.programming_mode = not self.bitfield[5:6] == "0"
  59. self.beeps = int(self.bitfield[6:7], 16)
  60. self.zone_bypassed = not self.bitfield[7:8] == "0"
  61. self.ac_power = not self.bitfield[8:9] == "0"
  62. self.chime_on = not self.bitfield[9:10] == "0"
  63. self.alarm_event_occurred = not self.bitfield[10:11] == "0"
  64. self.alarm_sounding = not self.bitfield[11:12] == "0"
  65. self.battery_low = not self.bitfield[12:13] == "0"
  66. self.entry_delay_off = not self.bitfield[13:14] == "0"
  67. self.fire_alarm = not self.bitfield[14:15] == "0"
  68. self.check_zone = not self.bitfield[15:16] == "0"
  69. self.perimeter_only = not self.bitfield[16:17] == "0"
  70. # bits 17-20 unused.
  71. self.text = alpha.strip('"')
  72. if int(self.panel_data[19:21], 16) & 0x01 > 0:
  73. self.cursor_location = int(self.bitfield[21:23], 16) # Alpha character index that the cursor is on.
  74. def __str__(self):
  75. """
  76. String conversion operator.
  77. """
  78. return 'msg > {0:0<9} [{1}{2}{3}] -- ({4}) {5}'.format(hex(self.mask), 1 if self.ready else 0, 1 if self.armed_away else 0, 1 if self.armed_home else 0, self.numeric_code, self.text)
  79. class ExpanderMessage(object):
  80. """
  81. Represents a message from a zone or relay expansion module.
  82. """
  83. ZONE = 0
  84. RELAY = 1
  85. def __init__(self, data=None):
  86. """
  87. Constructor
  88. :param data: The message data to parse.
  89. :type data: str
  90. """
  91. self.type = None
  92. self.address = None
  93. self.channel = None
  94. self.value = None
  95. self.raw = None
  96. if data is not None:
  97. self._parse_message(data)
  98. def __str__(self):
  99. """
  100. String conversion operator.
  101. """
  102. expander_type = 'UNKWN'
  103. if self.type == ExpanderMessage.ZONE:
  104. expander_type = 'ZONE'
  105. elif self.type == ExpanderMessage.RELAY:
  106. expander_type = 'RELAY'
  107. return 'exp > [{0: <5}] {1}/{2} -- {3}'.format(expander_type, self.address, self.channel, self.value)
  108. def _parse_message(self, data):
  109. """
  110. Parse the raw message from the device.
  111. :param data: The message data
  112. :type data: str
  113. """
  114. header, values = data.split(':')
  115. address, channel, value = values.split(',')
  116. self.raw = data
  117. self.address = address
  118. self.channel = channel
  119. self.value = value
  120. if header == '!EXP':
  121. self.type = ExpanderMessage.ZONE
  122. elif header == '!REL':
  123. self.type = ExpanderMessage.RELAY
  124. class RFMessage(object):
  125. """
  126. Represents a message from an RF receiver.
  127. """
  128. def __init__(self, data=None):
  129. """
  130. Constructor
  131. :param data: The message data to parse
  132. :type data: str
  133. """
  134. self.raw = None
  135. self.serial_number = None
  136. self.value = None
  137. if data is not None:
  138. self._parse_message(data)
  139. def __str__(self):
  140. """
  141. String conversion operator.
  142. """
  143. return 'rf > {0}: {1}'.format(self.serial_number, self.value)
  144. def _parse_message(self, data):
  145. """
  146. Parses the raw message from the device.
  147. :param data: The message data.
  148. :type data: str
  149. """
  150. self.raw = data
  151. _, values = data.split(':')
  152. self.serial_number, self.value = values.split(',')
  153. class LRRMessage(object):
  154. """
  155. Represent a message from a Long Range Radio.
  156. """
  157. def __init__(self, data=None):
  158. """
  159. Constructor
  160. :param data: The message data to parse.
  161. :type data: str
  162. """
  163. self.raw = None
  164. self._event_data = None
  165. self._partition = None
  166. self._event_type = None
  167. if data is not None:
  168. self._parse_message(data)
  169. def __str__(self):
  170. """
  171. String conversion operator.
  172. """
  173. return 'lrr > {0} @ {1} -- {2}'.format()
  174. def _parse_message(self, data):
  175. """
  176. Parses the raw message from the device.
  177. :param data: The message data.
  178. :type data: str
  179. """
  180. self.raw = data
  181. _, values = data.split(':')
  182. self._event_data, self._partition, self._event_type = values.split(',')