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.

243 lines
6.5 KiB

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