From 768721ab1e052673bb1b8d247725eee589e3032e Mon Sep 17 00:00:00 2001 From: Scott Petersen Date: Wed, 22 May 2013 12:01:46 -0700 Subject: [PATCH 1/6] Test --- pyad2usb/ad2usb.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/pyad2usb/ad2usb.py b/pyad2usb/ad2usb.py index 0c98039..ea5c787 100644 --- a/pyad2usb/ad2usb.py +++ b/pyad2usb/ad2usb.py @@ -240,7 +240,7 @@ class Message(object): Represents a message from the alarm panel. """ - def __init__(self): + def __init__(self, data=None): """ Constructor """ @@ -261,6 +261,17 @@ class Message(object): self._cursor = -1 self._raw = "" + if data is not None: + self._parse_message(data) + + def _parse_message(self, data): + pattern = '("(?:[^"]|"")*"|[^,]*),("(?:[^"]|"")*"|[^,]*),("(?:[^"]|"")*"|[^,]*),("(?:[^"]|"")*"|[^,]*)' + + + + + pass + @property def ignore_packet(self): """ From 8f74c843902bfcc53b3c1b258df4c82ceeac46e4 Mon Sep 17 00:00:00 2001 From: Scott Petersen Date: Thu, 23 May 2013 10:01:41 -0700 Subject: [PATCH 2/6] Message parsing complete. Added some new events to AD2USB. --- pyad2usb/ad2usb.py | 131 +++++++++++++++++++++++++++++++++++++++++---- pyad2usb/util.py | 6 +++ test.py | 17 ++++++ 3 files changed, 143 insertions(+), 11 deletions(-) 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: From b9bc8ce0532b19e7b3f359b6affab3825b9194cd Mon Sep 17 00:00:00 2001 From: Scott Petersen Date: Thu, 23 May 2013 10:43:34 -0700 Subject: [PATCH 3/6] Implemented ZoneExpanderMessage. Cleanup and comments. --- pyad2usb/ad2usb.py | 134 +++++++++++++++++++++++++++++++++++++-------- test.py | 8 ++- 2 files changed, 117 insertions(+), 25 deletions(-) diff --git a/pyad2usb/ad2usb.py b/pyad2usb/ad2usb.py index 2628f53..699fa11 100644 --- a/pyad2usb/ad2usb.py +++ b/pyad2usb/ad2usb.py @@ -179,6 +179,9 @@ class AD2USB(object): self._bypass_status = None self._device = device + + self._address_mask = 0xFF80 # TEMP + def __del__(self): """ Destructor @@ -212,35 +215,37 @@ class AD2USB(object): """ Parses messages from the panel. """ - if data[0] == '!': # TEMP: Remove this. - return None + msg = None - msg = Message(data) + if data[0] != '!': + msg = Message(data) - # parse and build stuff + # parse and build stuff + if self._address_mask & msg.mask > 0: + if msg.ac != self._power_status: + self._power_status, old_status = msg.ac, self._power_status - # TEMP - address_mask = 0xFF80 + if old_status is not None: + self.on_power_changed(self._power_status) - 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 - 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 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) + if msg.bypass != self._bypass_status: + self._bypass_status, old_status = msg.bypass, self._bypass_status + + if old_status is not None: + self.on_bypass(self._bypass_status) + else: + # specialty messages + if data[0:4] == '!EXP': + msg = ZoneExpanderMessage(data) + + if msg: + self.on_message(msg) def _on_open(self, sender, args): """ @@ -308,6 +313,9 @@ class Message(object): self._parse_message(data) def _parse_message(self, data): + """ + Parse the raw message from the device. + """ m = self._regex.match(data) if m is None: @@ -367,6 +375,12 @@ class Message(object): # self.text # ) + def __str__(self): + """ + String conversion operator. + """ + 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, self.text) + @property def ignore_packet(self): """ @@ -604,3 +618,77 @@ class Message(object): Sets the panel mask for which this message is intended. """ self._mask = value + +def ZoneExpanderMessage(object): + def __init__(self, data=None): + """ + Constructor + """ + self._address = None + self._channel = None + self._value = None + self._raw = None + + if data is not None: + self._parse_message(data) + + def __str__(self): + """ + String conversion operator. + """ + return 'zonemsg > {0}:{1} -- {2}'.format(self.address, self.channel, self.value) + + def _parse_message(self, data): + """ + Parse the raw message from the device. + """ + if data[0:4] == '!EXP': + header, address, channel, value = data.split(',') + + self.address = address + self.channel = channel + self.value = value + + self._raw = data + + @property + def address(self): + """ + The relay address from which the message originated. + """ + return self._address + + @address.setter + def address(self, value): + """ + Sets the relay address from which the message originated. + """ + self._address = value + + @property + def channel(self): + """ + The zone expander channel from which the message originated. + """ + return self._channel + + @channel.setter + def channel(self, value): + """ + Sets the zone expander channel from which the message originated. + """ + self._channel = value + + @property + def value(self): + """ + The value associated with the message. + """ + return self._value + + @value.setter + def value(self, value): + """ + Sets the value associated with the message. + """ + self._value = value diff --git a/test.py b/test.py index 1dfcc9b..44cde72 100755 --- a/test.py +++ b/test.py @@ -40,6 +40,9 @@ def handle_alarm_bell(sender, args): def handle_bypass(sender, args): print 'bypass', args +def handle_message(sender, args): + print args + def handle_firmware(stage): if stage == pyad2usb.ad2usb.util.Firmware.STAGE_START: handle_firmware.wait_tick = 0 @@ -186,9 +189,10 @@ def test_socket(): a2u = pyad2usb.ad2usb.AD2USB(dev) a2u.on_open += handle_open a2u.on_close += handle_close - a2u.on_read += handle_read - a2u.on_write += handle_write + #a2u.on_read += handle_read + #a2u.on_write += handle_write + a2u.on_message += handle_message a2u.on_power_changed += handle_power_changed a2u.on_alarm += handle_alarm_bell a2u.on_bypass += handle_bypass From 4ad60c936044d04091cda4aa8bec4d3fb904c0d6 Mon Sep 17 00:00:00 2001 From: Scott Petersen Date: Thu, 23 May 2013 12:07:42 -0700 Subject: [PATCH 4/6] Added ExpanderMessage and RFMessage. --- pyad2usb/ad2usb.py | 141 +++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 131 insertions(+), 10 deletions(-) diff --git a/pyad2usb/ad2usb.py b/pyad2usb/ad2usb.py index 699fa11..efa08c7 100644 --- a/pyad2usb/ad2usb.py +++ b/pyad2usb/ad2usb.py @@ -239,10 +239,16 @@ class AD2USB(object): if old_status is not None: self.on_bypass(self._bypass_status) + else: # specialty messages - if data[0:4] == '!EXP': - msg = ZoneExpanderMessage(data) + header = data[0:4] + #print data + + if header == '!EXP' or header == '!REL': + msg = ExpanderMessage(data) + elif header == '!RFX': + msg = RFMessage(data) if msg: self.on_message(msg) @@ -619,11 +625,19 @@ class Message(object): """ self._mask = value -def ZoneExpanderMessage(object): +class ExpanderMessage(object): + """ + Represents a message from a zone or relay expansion module. + """ + + ZONE = 0 + RELAY = 1 + def __init__(self, data=None): """ Constructor """ + self._type = None self._address = None self._channel = None self._value = None @@ -636,20 +650,30 @@ def ZoneExpanderMessage(object): """ String conversion operator. """ - return 'zonemsg > {0}:{1} -- {2}'.format(self.address, self.channel, self.value) + expander_type = 'UNKWN' + if self.type == ExpanderMessage.ZONE: + expander_type = 'ZONE' + elif self.type == ExpanderMessage.RELAY: + expander_type = 'RELAY' + + return 'exp > [{0: <5}] {1}/{2} -- {3}'.format(expander_type, self.address, self.channel, self.value) def _parse_message(self, data): """ Parse the raw message from the device. """ - if data[0:4] == '!EXP': - header, address, channel, value = data.split(',') + header, values = data.split(':') + address, channel, value = values.split(',') - self.address = address - self.channel = channel - self.value = value + self.raw = data + self.address = address + self.channel = channel + self.value = value - self._raw = data + if header == '!EXP': + self.type = ExpanderMessage.ZONE + elif header == '!REL': + self.type = ExpanderMessage.RELAY @property def address(self): @@ -692,3 +716,100 @@ def ZoneExpanderMessage(object): Sets the value associated with the message. """ self._value = value + + @property + def raw(self): + """ + The raw message from the expander device. + """ + return self._raw + + @raw.setter + def raw(self, value): + """ + Sets the raw message from the expander device. + """ + self._value = value + + @property + def type(self): + """ + The type of expander associated with this message. + """ + return self._type + + @type.setter + def type(self, value): + """ + Sets the type of expander associated with this message. + """ + self._type = value + +class RFMessage(object): + """ + Represents a message from an RF receiver. + """ + def __init__(self, data=None): + """ + Constructor + """ + self._raw = None + self._serial_number = None + self._value = None + + if data is not None: + self._parse_message(data) + + def __str__(self): + """ + String conversion operator. + """ + return 'rf > {0}: {1}'.format(self.serial_number, self.value) + + def _parse_message(self, data): + """ + Parses the raw message from the device. + """ + self.raw = data + + _, values = data.split(':') + self.serial_number, self.value = values.split(',') + + @property + def serial_number(self): + """ + The serial number for the RF receiver. + """ + return self._serial_number + + @serial_number.setter + def serial_number(self, value): + self._serial_number = value + + @property + def value(self): + """ + The value of the RF message. + """ + return self._value + + @value.setter + def value(self, value): + """ + Sets the value of the RF message. + """ + self._value = value + + @property + def raw(self): + """ + The raw message from the RF receiver. + """ + return self._raw + + @raw.setter + def raw(self, value): + """ + Sets the raw message from the RF receiver. + """ + self._raw = value From 1465869322d2e864a65f2c6fe1639712cfc0b803 Mon Sep 17 00:00:00 2001 From: Scott Petersen Date: Thu, 23 May 2013 12:17:36 -0700 Subject: [PATCH 5/6] Seperated the internal states into their own method. --- pyad2usb/ad2usb.py | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/pyad2usb/ad2usb.py b/pyad2usb/ad2usb.py index efa08c7..2801cf0 100644 --- a/pyad2usb/ad2usb.py +++ b/pyad2usb/ad2usb.py @@ -220,30 +220,11 @@ class AD2USB(object): if data[0] != '!': msg = Message(data) - # parse and build stuff if self._address_mask & msg.mask > 0: - if msg.ac != self._power_status: - self._power_status, old_status = msg.ac, self._power_status + self._update_internal_states(msg) - 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 - - 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 - - if old_status is not None: - self.on_bypass(self._bypass_status) - - else: - # specialty messages + else: # specialty messages header = data[0:4] - #print data if header == '!EXP' or header == '!REL': msg = ExpanderMessage(data) @@ -253,6 +234,25 @@ class AD2USB(object): if msg: self.on_message(msg) + def _update_internal_states(self, message): + if message.ac != self._power_status: + self._power_status, old_status = message.ac, self._power_status + + if old_status is not None: + self.on_power_changed(self._power_status) + + if message.alarm_bell != self._alarm_status: + self._alarm_status, old_status = message.alarm_bell, self._alarm_status + + if old_status is not None: + self.on_alarm(self._alarm_status) + + if message.bypass != self._bypass_status: + self._bypass_status, old_status = message.bypass, self._bypass_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. From 247bc829b8f05892d6fee421f0f2d480c98b9e4d Mon Sep 17 00:00:00 2001 From: Scott Petersen Date: Thu, 23 May 2013 12:40:52 -0700 Subject: [PATCH 6/6] Reworked Message fields. --- pyad2usb/ad2usb.py | 246 ++++++++++++++++++++------------------------- 1 file changed, 110 insertions(+), 136 deletions(-) diff --git a/pyad2usb/ad2usb.py b/pyad2usb/ad2usb.py index 2801cf0..46bd08c 100644 --- a/pyad2usb/ad2usb.py +++ b/pyad2usb/ad2usb.py @@ -231,24 +231,23 @@ class AD2USB(object): elif header == '!RFX': msg = RFMessage(data) - if msg: - self.on_message(msg) + return msg def _update_internal_states(self, message): - if message.ac != self._power_status: - self._power_status, old_status = message.ac, self._power_status + if message.ac_power != self._power_status: + self._power_status, old_status = message.ac_power, self._power_status if old_status is not None: self.on_power_changed(self._power_status) - if message.alarm_bell != self._alarm_status: - self._alarm_status, old_status = message.alarm_bell, self._alarm_status + if message.alarm_sounding != self._alarm_status: + self._alarm_status, old_status = message.alarm_sounding, self._alarm_status if old_status is not None: self.on_alarm(self._alarm_status) - if message.bypass != self._bypass_status: - self._bypass_status, old_status = message.bypass, self._bypass_status + if message.zone_bypassed != self._bypass_status: + self._bypass_status, old_status = message.zone_bypassed, self._bypass_status if old_status is not None: self.on_bypass(self._bypass_status) @@ -269,12 +268,12 @@ class AD2USB(object): """ Internal handler for reading from the device. """ + self.on_read(args) + msg = self._handle_message(args) if msg: self.on_message(msg) - self.on_read(args) - def _on_write(self, sender, args): """ Internal handler for writing to the device. @@ -290,28 +289,24 @@ class Message(object): """ Constructor """ - self._ignore_packet = False self._ready = False self._armed_away = False self._armed_home = False - self._backlight = False + self._backlight_on = False self._programming_mode = False self._beeps = -1 - self._bypass = False - self._ac = False - self._chime_mode = False + self._zone_bypassed = False + self._ac_power = False + self._chime_on = False self._alarm_event_occurred = False - self._alarm_bell = False - self._numeric = "" + self._alarm_sounding = False + self._numeric_code = "" self._text = "" - self._cursor = -1 - self._raw = "" + self._cursor_location = -1 + self._data = "" self._mask = "" - - self._msg_bitfields = "" - self._msg_zone = "" - self._msg_binary = "" - self._msg_alpha = "" + self._bitfield = "" + self._panel_data = "" self._regex = re.compile('("(?:[^"]|"")*"|[^,]*),("(?:[^"]|"")*"|[^,]*),("(?:[^"]|"")*"|[^,]*),("(?:[^"]|"")*"|[^,]*)') @@ -320,86 +315,38 @@ class Message(object): def _parse_message(self, data): """ - Parse the raw message from the device. + Parse the message from the device. """ 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 - # ) + self._bitfield, self._numeric_code, self._panel_data, alpha = m.group(1, 2, 3, 4) + self._mask = int(self._panel_data[3:3+8], 16) + + self._data = data + self._ready = not self._bitfield[1:2] == "0" + self._armed_away = not self._bitfield[2:3] == "0" + self._armed_home = not self._bitfield[3:4] == "0" + self._backlight_on = not self._bitfield[4:5] == "0" + self._programming_mode = not self._bitfield[5:6] == "0" + self._beeps = int(self._bitfield[6:7], 16) + self._zone_bypassed = not self._bitfield[7:8] == "0" + self._ac_power = not self._bitfield[8:9] == "0" + self._chime_on = not self._bitfield[9:10] == "0" + self._alarm_event_occurred = not self._bitfield[10:11] == "0" + self._alarm_sounding = not self._bitfield[11:12] == "0" + self._text = alpha.strip('"') + + if int(self._panel_data[19:21], 16) & 0x01 > 0: + self._cursor_location = int(self._bitfield[21:23], 16) # Alpha character index that the cursor is on. def __str__(self): """ String conversion operator. """ - 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, self.text) - - @property - def ignore_packet(self): - """ - Indicates whether or not this message should be ignored. - """ - return self._ignore_packet - - @ignore_packet.setter - def ignore_packet(self, value): - """ - Sets the value indicating whether or not this packet should be ignored. - """ - self._ignore_packet = value + 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) @property def ready(self): @@ -444,18 +391,18 @@ class Message(object): self._armed_home = value @property - def backlight(self): + def backlight_on(self): """ Indicates whether or not the panel backlight is on. """ - return self._backlight + return self._backlight_on - @backlight.setter - def backlight(self, value): + @backlight_on.setter + def backlight_on(self, value): """ Sets the value indicating whether or not the panel backlight is on. """ - self._backlight = value + self._backlight_on = value @property def programming_mode(self): @@ -486,46 +433,46 @@ class Message(object): self._beeps = value @property - def bypass(self): + def zone_bypassed(self): """ Indicates whether or not zones have been bypassed. """ - return self._bypass + return self._zone_bypassed - @bypass.setter - def bypass(self, value): + @zone_bypassed.setter + def zone_bypassed(self, value): """ Sets the value indicating whether or not zones have been bypassed. """ - self._bypass = value + self._zone_bypassed = value @property - def ac(self): + def ac_power(self): """ Indicates whether or not the system is on AC power. """ - return self._ac + return self._ac_power - @ac.setter - def ac(self, value): + @ac_power.setter + def ac_power(self, value): """ Sets the value indicating whether or not the system is on AC power. """ - self._ac = value + self._ac_power = value @property - def chime_mode(self): + def chime_on(self): """ Indicates whether or not panel chimes are enabled. """ - return self._chime_mode + return self._chime_on - @chime_mode.setter - def chime_mode(self, value): + @chime_on.setter + def chime_on(self, value): """ Sets the value indicating whether or not the panel chimes are enabled. """ - self._chime_mode = value + self._chime_on = value @property def alarm_event_occurred(self): @@ -542,32 +489,32 @@ class Message(object): self._alarm_event_occurred = value @property - def alarm_bell(self): + def alarm_sounding(self): """ Indicates whether or not an alarm is currently sounding. """ - return self._alarm_bell + return self._alarm_sounding - @alarm_bell.setter - def alarm_bell(self, value): + @alarm_sounding.setter + def alarm_sounding(self, value): """ Sets the value indicating whether or not an alarm is currently sounding. """ - self._alarm_bell = value + self._alarm_sounding = value @property - def numeric(self): + def numeric_code(self): """ Numeric indicator of associated with message. For example: If zone #3 is faulted, this value is 003. """ - return self._numeric + return self._numeric_code - @numeric.setter - def numeric(self, value): + @numeric_code.setter + def numeric_code(self, value): """ Sets the numeric indicator associated with this message. """ - self._numeric = value + self._numeric_code = value @property def text(self): @@ -584,32 +531,32 @@ class Message(object): self._text = value @property - def cursor(self): + def cursor_location(self): """ Indicates which text position has the cursor underneath it. """ - return self._cursor + return self._cursor_location - @cursor.setter - def cursor(self, value): + @cursor_location.setter + def cursor_location(self, value): """ Sets the value indicating which text position has the cursor underneath it. """ - self._cursor = value + self._cursor_location = value @property - def raw(self): + def data(self): """ - Raw representation of the message data from the panel. + Raw representation of the message from the panel. """ - return self._raw + return self._data - @raw.setter - def raw(self, value): + @data.setter + def data(self, value): """ - Sets the raw representation of the message data from the panel. + Sets the raw representation of the message from the panel. """ - self._raw = value + self._data = value @property def mask(self): @@ -625,11 +572,38 @@ class Message(object): """ self._mask = value + @property + def bitfield(self): + """ + The bit field associated with this message. + """ + return self._bitfield + + @bitfield.setter + def bitfield(self, value): + """ + Sets the bit field associated with this message. + """ + self._bitfield = value + + @property + def panel_data(self): + """ + The binary field associated with this message. + """ + return self._panel_data + + @panel_data.setter + def panel_data(self, value): + """ + Sets the binary field associated with this message. + """ + self._panel_data = value + class ExpanderMessage(object): """ Represents a message from a zone or relay expansion module. """ - ZONE = 0 RELAY = 1