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.

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