diff --git a/pyad2usb/ad2usb.py b/pyad2usb/ad2usb.py index ea5c787..2628f53 100644 --- a/pyad2usb/ad2usb.py +++ b/pyad2usb/ad2usb.py @@ -4,6 +4,7 @@ Provides the full AD2USB class and factory. import time import threading +import re from .event import event from . import devices from . import util @@ -154,18 +155,28 @@ class AD2USB(object): """ # High-level Events - on_open = event.Event('Called when the device has been opened') - on_close = event.Event('Called when the device has been closed') + on_open = event.Event('Called when the device has been opened.') + on_close = event.Event('Called when the device has been closed.') + + on_status_changed = event.Event('Called when the panel status changes.') + on_power_changed = event.Event('Called when panel power switches between AC and DC.') + on_alarm = event.Event('Called when the alarm is triggered.') + on_bypass = event.Event('Called when a zone is bypassed.') + + # Mid-level Events on_message = event.Event('Called when a message has been received from the device.') # Low-level Events - on_read = event.Event('Called when a line has been read from the device') - on_write = event.Event('Called when data has been written to the device') + on_read = event.Event('Called when a line has been read from the device.') + on_write = event.Event('Called when data has been written to the device.') def __init__(self, device): """ Constructor """ + self._power_status = None + self._alarm_status = None + self._bypass_status = None self._device = device def __del__(self): @@ -204,9 +215,33 @@ class AD2USB(object): if data[0] == '!': # TEMP: Remove this. return None - msg = Message() + msg = Message(data) + # parse and build stuff + # TEMP + address_mask = 0xFF80 + + if address_mask & msg.mask > 0: + #print 'ac={0}, alarm={1}, bypass={2}'.format(msg.ac, msg.alarm_bell, msg.bypass) + if msg.ac != self._power_status: + self._power_status, old_status = msg.ac, self._power_status + #print '\tpower: new={0}, old={1}'.format(self._power_status, old_status) + if old_status is not None: + self.on_power_changed(self._power_status) + + if msg.alarm_bell != self._alarm_status: + self._alarm_status, old_status = msg.alarm_bell, self._alarm_status + #print '\talarm: new={0}, old={1}'.format(self._alarm_status, old_status) + if old_status is not None: + self.on_alarm(self._alarm_status) + + if msg.bypass != self._bypass_status: + self._bypass_status, old_status = msg.bypass, self._bypass_status + #print '\tbypass: new={0}, old={1}'.format(self._bypass_status, old_status) + if old_status is not None: + self.on_bypass(self._bypass_status) + def _on_open(self, sender, args): """ Internal handler for opening the device. @@ -260,17 +295,77 @@ class Message(object): self._text = "" self._cursor = -1 self._raw = "" + self._mask = "" + + self._msg_bitfields = "" + self._msg_zone = "" + self._msg_binary = "" + self._msg_alpha = "" + + self._regex = re.compile('("(?:[^"]|"")*"|[^,]*),("(?:[^"]|"")*"|[^,]*),("(?:[^"]|"")*"|[^,]*),("(?:[^"]|"")*"|[^,]*)') if data is not None: self._parse_message(data) def _parse_message(self, data): - pattern = '("(?:[^"]|"")*"|[^,]*),("(?:[^"]|"")*"|[^,]*),("(?:[^"]|"")*"|[^,]*),("(?:[^"]|"")*"|[^,]*)' - - - - - pass + m = self._regex.match(data) + + if m is None: + raise util.InvalidMessageError('Received invalid message: {0}'.format(data)) + + self._msg_bitfields, self._msg_zone, self._msg_binary, self._msg_alpha = m.group(1, 2, 3, 4) + self.mask = int(self._msg_binary[3:3+8], 16) + + self.raw = data + self.ready = not self._msg_bitfields[1:2] == "0" + self.armed_away = not self._msg_bitfields[2:3] == "0" + self.armed_home = not self._msg_bitfields[3:4] == "0" + self.backlight = not self._msg_bitfields[4:5] == "0" + self.programming_mode = not self._msg_bitfields[5:6] == "0" + self.beeps = int(self._msg_bitfields[6:7], 16) + self.bypass = not self._msg_bitfields[7:8] == "0" + self.ac = not self._msg_bitfields[8:9] == "0" + self.chime_mode = not self._msg_bitfields[9:10] == "0" + self.alarm_event_occurred = not self._msg_bitfields[10:11] == "0" + self.alarm_bell = not self._msg_bitfields[11:12] == "0" + self.numeric = self._msg_zone + self.text = self._msg_alpha.strip('"') + + if int(self._msg_binary[19:21], 16) & 0x01 > 0: + self.cursor = int(self._msg_bitfields[21:23], 16) + + #print "Message:\r\n" \ + # "\tmask: {0}\r\n" \ + # "\tready: {1}\r\n" \ + # "\tarmed_away: {2}\r\n" \ + # "\tarmed_home: {3}\r\n" \ + # "\tbacklight: {4}\r\n" \ + # "\tprogramming_mode: {5}\r\n" \ + # "\tbeeps: {6}\r\n" \ + # "\tbypass: {7}\r\n" \ + # "\tac: {8}\r\n" \ + # "\tchime_mode: {9}\r\n" \ + # "\talarm_event_occurred: {10}\r\n" \ + # "\talarm_bell: {11}\r\n" \ + # "\tcursor: {12}\r\n" \ + # "\tnumeric: {13}\r\n" \ + # "\ttext: {14}\r\n".format( + # self.mask, + # self.ready, + # self.armed_away, + # self.armed_home, + # self.backlight, + # self.programming_mode, + # self.beeps, + # self.bypass, + # self.ac, + # self.chime_mode, + # self.alarm_event_occurred, + # self.alarm_bell, + # self.cursor, + # self.numeric, + # self.text + # ) @property def ignore_packet(self): @@ -495,3 +590,17 @@ class Message(object): Sets the raw representation of the message data from the panel. """ self._raw = value + + @property + def mask(self): + """ + The panel mask for which this message is intended. + """ + return self._mask + + @mask.setter + def mask(self, value): + """ + Sets the panel mask for which this message is intended. + """ + self._mask = value diff --git a/pyad2usb/util.py b/pyad2usb/util.py index 109ff7b..e0a115c 100644 --- a/pyad2usb/util.py +++ b/pyad2usb/util.py @@ -24,6 +24,12 @@ class TimeoutError(Exception): """ pass +class InvalidMessageError(Exception): + """ + The format of the panel message was invalid. + """ + pass + class Firmware(object): """ Represents firmware for the AD2USB/AD2SERIAL devices. diff --git a/test.py b/test.py index 080eee5..1dfcc9b 100755 --- a/test.py +++ b/test.py @@ -31,6 +31,15 @@ def handle_attached(sender, args): def handle_detached(sender, args): print '-', args +def handle_power_changed(sender, args): + print 'power changed', args + +def handle_alarm_bell(sender, args): + print 'alarm', args + +def handle_bypass(sender, args): + print 'bypass', args + def handle_firmware(stage): if stage == pyad2usb.ad2usb.util.Firmware.STAGE_START: handle_firmware.wait_tick = 0 @@ -95,6 +104,10 @@ def test_usb(): a2u.on_read += handle_read a2u.on_write += handle_write + a2u.on_power_changed += handle_power_changed + a2u.on_alarm += handle_alarm_bell + a2u.on_bypass += handle_bypass + a2u.open() while running: @@ -176,6 +189,10 @@ def test_socket(): a2u.on_read += handle_read a2u.on_write += handle_write + a2u.on_power_changed += handle_power_changed + a2u.on_alarm += handle_alarm_bell + a2u.on_bypass += handle_bypass + a2u.open() while running: