From 3034e14920651846b4fe44f1d4cb2dfa010ada73 Mon Sep 17 00:00:00 2001 From: Scott Petersen Date: Mon, 13 Jan 2014 12:28:09 -0800 Subject: [PATCH 01/11] Added timestamp to messages. --- alarmdecoder/messages.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/alarmdecoder/messages.py b/alarmdecoder/messages.py index a5d67cf..ad7b0b7 100644 --- a/alarmdecoder/messages.py +++ b/alarmdecoder/messages.py @@ -13,6 +13,7 @@ devices. """ import re +import datetime from .util import InvalidMessageError @@ -25,11 +26,14 @@ class BaseMessage(object): raw = None """The raw message text""" + timestamp = None + """The timestamp of the message""" + def __init__(self): """ Constructor """ - pass + self.timestamp = datetime.datetime.now() def __str__(self): """ From 0a6d493d1153e0e05f934eeb7af9e43870e89ce7 Mon Sep 17 00:00:00 2001 From: Scott Petersen Date: Mon, 3 Feb 2014 12:59:42 -0800 Subject: [PATCH 02/11] Updated default address mask, proper handling of invalid messages, added fileno() to support use with select(). --- alarmdecoder/decoder.py | 2 +- alarmdecoder/devices.py | 21 ++++++++++++++++++--- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/alarmdecoder/decoder.py b/alarmdecoder/decoder.py index 568de9d..074c98a 100644 --- a/alarmdecoder/decoder.py +++ b/alarmdecoder/decoder.py @@ -66,7 +66,7 @@ class AlarmDecoder(object): """The keypad address in use by the device.""" configbits = 0xFF00 """The configuration bits set on the device.""" - address_mask = 0x00000000 + address_mask = 0xFFFFFFFF """The address mask configured on the device.""" emulate_zone = [False for _ in range(5)] """List containing the devices zone emulation status.""" diff --git a/alarmdecoder/devices.py b/alarmdecoder/devices.py index 7fce36c..f1e7a7c 100644 --- a/alarmdecoder/devices.py +++ b/alarmdecoder/devices.py @@ -25,7 +25,7 @@ import socket from OpenSSL import SSL, crypto from pyftdi.pyftdi.ftdi import Ftdi, FtdiError -from .util import CommError, TimeoutError, NoDeviceError +from .util import CommError, TimeoutError, NoDeviceError, InvalidMessageError from .event import event @@ -149,7 +149,10 @@ class Device(object): except TimeoutError: pass - except Exception: + except InvalidMessageError: + pass + + except Exception, err: self._running = False @@ -234,7 +237,10 @@ class USBDevice(Device): """ cls.__detect_thread = USBDevice.DetectThread(on_attached, on_detached) - cls.find_all() + try: + cls.find_all() + except CommError: + pass cls.__detect_thread.start() @@ -390,6 +396,9 @@ class USBDevice(Device): except Exception: pass + def fileno(self): + raise NotImplementedError('USB devices do not support fileno()') + def write(self, data): """ Writes data to the device. @@ -670,6 +679,9 @@ class SerialDevice(Device): except Exception: pass + def fileno(self): + return self._device.fileno() + def write(self, data): """ Writes data to the device. @@ -944,6 +956,9 @@ class SocketDevice(Device): except Exception: pass + def fileno(self): + return self._device.fileno() + def write(self, data): """ Writes data to the device. From 7b4b8a045d098b1c3ca0b8821912a4111b700451 Mon Sep 17 00:00:00 2001 From: Ryan Date: Mon, 17 Feb 2014 04:13:27 -0800 Subject: [PATCH 03/11] Strip any leading null characters from data before trying to parse message --- alarmdecoder/decoder.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/alarmdecoder/decoder.py b/alarmdecoder/decoder.py index 568de9d..19349ca 100644 --- a/alarmdecoder/decoder.py +++ b/alarmdecoder/decoder.py @@ -294,6 +294,8 @@ class AlarmDecoder(object): :returns: :py:class:`~alarmdecoder.messages.Message` """ + data = data.lstrip('\0') + if data is None: raise InvalidMessageError() From d18b070f62d9ce5885544806683e6129391baa0d Mon Sep 17 00:00:00 2001 From: Scott Petersen Date: Fri, 21 Feb 2014 12:40:35 -0800 Subject: [PATCH 04/11] More error handling and closing of device on error. --- alarmdecoder/devices.py | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/alarmdecoder/devices.py b/alarmdecoder/devices.py index f1e7a7c..cb113cc 100644 --- a/alarmdecoder/devices.py +++ b/alarmdecoder/devices.py @@ -152,8 +152,16 @@ class Device(object): except InvalidMessageError: pass + except SSL.WantReadError: + pass + + except CommError, err: + self._device.close() + except Exception, err: + self._device.close() self._running = False + raise class USBDevice(Device): @@ -920,9 +928,15 @@ class SocketDevice(Device): self._init_ssl() self._device.connect((self._host, self._port)) + #self._device.setblocking(1) if self._use_ssl: - self._device.do_handshake() + while True: + try: + self._device.do_handshake() + break + except SSL.WantReadError: + pass self._id = '{0}:{1}'.format(self._host, self._port) @@ -951,11 +965,11 @@ class SocketDevice(Device): # Make sure that it closes immediately. self._device.shutdown(socket.SHUT_RDWR) - Device.close(self) - except Exception: pass + Device.close(self) + def fileno(self): return self._device.fileno() @@ -1048,6 +1062,10 @@ class SocketDevice(Device): except socket.error, err: raise CommError('Error reading from device: {0}'.format(str(err)), err) + except SSL.SysCallError, err: + errno, msg = err + raise CommError('SSL error while reading from device: {0} ({1})'.format(msg, errno)) + else: if got_line: ret, self._buffer = self._buffer, '' From f6113b7fa9c2802d56a5fb8fff856ef26f693485 Mon Sep 17 00:00:00 2001 From: Scott Petersen Date: Fri, 21 Feb 2014 12:54:00 -0800 Subject: [PATCH 05/11] Added missing potential exception when opening a SerialDevice. --- alarmdecoder/devices.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/alarmdecoder/devices.py b/alarmdecoder/devices.py index cb113cc..d2edffe 100644 --- a/alarmdecoder/devices.py +++ b/alarmdecoder/devices.py @@ -665,8 +665,8 @@ class SerialDevice(Device): # all issues with it. self._device.baudrate = baudrate - except (serial.SerialException, ValueError), err: - raise NoDeviceError('Error opening device on port {0}.'.format(self._port), err) + except (serial.SerialException, ValueError, OSError), err: + raise NoDeviceError('Error opening device on {0}.'.format(self._port), err) else: self._running = True From 2dbf7064569978bdba9df51935bd71853d884112 Mon Sep 17 00:00:00 2001 From: Scott Petersen Date: Mon, 24 Mar 2014 12:29:42 -0700 Subject: [PATCH 06/11] Added support for catching key send events, added KEY_PANIC, bugfixes. --- alarmdecoder/decoder.py | 32 +++++++++++++++++++++++++++++--- alarmdecoder/devices.py | 3 +++ 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/alarmdecoder/decoder.py b/alarmdecoder/decoder.py index 903ccad..7c32179 100644 --- a/alarmdecoder/decoder.py +++ b/alarmdecoder/decoder.py @@ -7,6 +7,7 @@ Provides the main AlarmDecoder class. """ import time +import re from .event import event from .util import InvalidMessageError @@ -39,6 +40,7 @@ class AlarmDecoder(object): on_expander_message = event.Event("This event is called when an :py:class:`~alarmdecoder.messages.ExpanderMessage` is received.\n\n**Callback definition:** *def callback(device, message)*") on_lrr_message = event.Event("This event is called when an :py:class:`~alarmdecoder.messages.LRRMessage` is received.\n\n**Callback definition:** *def callback(device, message)*") on_rfx_message = event.Event("This event is called when an :py:class:`~alarmdecoder.messages.RFMessage` is received.\n\n**Callback definition:** *def callback(device, message)*") + on_sending_received = event.Event("This event is called when a !Sending.done message is received from the AlarmDecoder.\n\n**Callback definition:** *def callback(device, status, message)*") # Low-level Events on_open = event.Event("This event is called when the device has been opened.\n\n**Callback definition:** *def callback(device)*") @@ -55,6 +57,8 @@ class AlarmDecoder(object): """Represents panel function key #3""" KEY_F4 = unichr(4) + unichr(4) + unichr(4) """Represents panel function key #4""" + KEY_PANIC = unichr(5) + unichr(5) + unichr(5) + """Represents a panic keypress""" BATTERY_TIMEOUT = 30 """Default timeout (in seconds) before the battery status reverts.""" @@ -200,8 +204,9 @@ class AlarmDecoder(object): :param data: data to send :type data: string """ + if self._device: - self._device.write(data) + self._device.write(str(data)) def get_config(self): """ @@ -294,9 +299,11 @@ class AlarmDecoder(object): :returns: :py:class:`~alarmdecoder.messages.Message` """ - data = data.lstrip('\0') - if data is None: + if data is not None: + data = data.lstrip('\0') + + if data is None or data == '': raise InvalidMessageError() msg = None @@ -320,6 +327,9 @@ class AlarmDecoder(object): elif data.startswith('!CONFIG'): self._handle_config(data) + elif data.startswith('!Sending'): + self._handle_sending(data) + return msg def _handle_keypad_message(self, data): @@ -423,6 +433,22 @@ class AlarmDecoder(object): self.on_config_received() + def _handle_sending(self, data): + """ + Handles results of a keypress send. + + :param data: Sending string to parse + :type data: string + """ + + matches = re.match('^!Sending(\.{1,5})done.*', data) + if matches is not None: + good_send = False + if len(matches.group(1)) < 5: + good_send = True + + self.on_sending_received(status=good_send, message=data) + def _update_internal_states(self, message): """ Updates internal device states. diff --git a/alarmdecoder/devices.py b/alarmdecoder/devices.py index d2edffe..42175e4 100644 --- a/alarmdecoder/devices.py +++ b/alarmdecoder/devices.py @@ -1066,6 +1066,9 @@ class SocketDevice(Device): errno, msg = err raise CommError('SSL error while reading from device: {0} ({1})'.format(msg, errno)) + except Exception: + raise + else: if got_line: ret, self._buffer = self._buffer, '' From 41c72580084e530412acc884a3d2331e23eaca55 Mon Sep 17 00:00:00 2001 From: Scott Petersen Date: Thu, 10 Apr 2014 12:30:35 -0700 Subject: [PATCH 07/11] Added support for iterating over event handlers. --- alarmdecoder/event/event.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/alarmdecoder/event/event.py b/alarmdecoder/event/event.py index 6200fa8..0086cbb 100644 --- a/alarmdecoder/event/event.py +++ b/alarmdecoder/event/event.py @@ -31,6 +31,9 @@ class EventHandler(object): self.event = event self.obj = obj + def __iter__(self): + return iter(self._getfunctionlist()) + def _getfunctionlist(self): """(internal use) """ From 5b9265e73c1101b4c162306656ef93a0a86daf12 Mon Sep 17 00:00:00 2001 From: "Spencer E. Olson" Date: Thu, 10 Apr 2014 04:19:24 +0000 Subject: [PATCH 08/11] also .pyo files --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index a911660..c171734 100644 --- a/.gitignore +++ b/.gitignore @@ -2,5 +2,6 @@ build/ dist tmp *.pyc +*.pyo *.egg-info bin/ad2-test From ee5bddfb05bf839063e70f18a622aed8d830b173 Mon Sep 17 00:00:00 2001 From: "Spencer E. Olson" Date: Thu, 10 Apr 2014 04:20:19 +0000 Subject: [PATCH 09/11] ignore backup edit files --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index c171734..921905b 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ tmp *.pyo *.egg-info bin/ad2-test +*~ From 6f3592445144fd4ca9fdfa0df11dc5fc2a7d197f Mon Sep 17 00:00:00 2001 From: "Spencer E. Olson" Date: Sat, 19 Apr 2014 13:44:48 -0600 Subject: [PATCH 10/11] messages->dict conversion --- alarmdecoder/messages.py | 104 ++++++++++++++++++++++++++++++--------- 1 file changed, 80 insertions(+), 24 deletions(-) diff --git a/alarmdecoder/messages.py b/alarmdecoder/messages.py index ad7b0b7..b613f15 100644 --- a/alarmdecoder/messages.py +++ b/alarmdecoder/messages.py @@ -41,6 +41,18 @@ class BaseMessage(object): """ return self.raw + def dict(self, **kwargs): + """ + Dictionary representation. + """ + return dict(time=self.timestamp, mesg=self.raw, **kwargs) + + def __repr__(self): + """ + String representation. + """ + return repr(self.dict()) + class Message(BaseMessage): """ @@ -106,12 +118,6 @@ class Message(BaseMessage): if data is not None: self._parse_message(data) - def __str__(self): - """ - String conversion operator. - """ - return self.raw - def _parse_message(self, data): """ Parse the message from the device. @@ -155,6 +161,37 @@ class Message(BaseMessage): # Current cursor location on the alpha display. self.cursor_location = int(self.bitfield[21:23], 16) + def dict(self, **kwargs): + """ + Dictionary representation. + """ + return dict( + time = self.timestamp, + bitfield = self.bitfield, + numeric_code = self.numeric_code, + panel_data = self.panel_data, + mask = self.mask, + ready = self.ready, + armed_away = self.armed_away, + armed_home = self.armed_home, + backlight_on = self.backlight_on, + programming_mode = self.programming_mode, + beeps = self.beeps, + zone_bypassed = self.zone_bypassed, + ac_power = self.ac_power, + chime_on = self.chime_on, + alarm_event_occurred = self.alarm_event_occurred, + alarm_sounding = self.alarm_sounding, + battery_low = self.battery_low, + entry_delay_off = self.entry_delay_off, + fire_alarm = self.fire_alarm, + check_zone = self.check_zone, + perimeter_only = self.perimeter_only, + text = self.text, + cursor_location = self.cursor_location, + **kwargs + ) + class ExpanderMessage(BaseMessage): """ @@ -187,12 +224,6 @@ class ExpanderMessage(BaseMessage): if data is not None: self._parse_message(data) - def __str__(self): - """ - String conversion operator. - """ - return self.raw - def _parse_message(self, data): """ Parse the raw message from the device. @@ -221,6 +252,18 @@ class ExpanderMessage(BaseMessage): else: raise InvalidMessageError('Unknown expander message header: {0}'.format(data)) + def dict(self, **kwargs): + """ + Dictionary representation. + """ + return dict( + time = self.timestamp, + address = self.address, + channel = self.channel, + value = self.value, + **kwargs + ) + class RFMessage(BaseMessage): """ @@ -250,12 +293,6 @@ class RFMessage(BaseMessage): if data is not None: self._parse_message(data) - def __str__(self): - """ - String conversion operator. - """ - return self.raw - def _parse_message(self, data): """ Parses the raw message from the device. @@ -286,6 +323,19 @@ class RFMessage(BaseMessage): except ValueError: raise InvalidMessageError('Received invalid message: {0}'.format(data)) + def dict(self, **kwargs): + """ + Dictionary representation. + """ + return dict( + time = self.timestamp, + serial_number = self.serial_number, + value = self.value, + battery = self.battery, + supervision = self.supervision, + **kwargs + ) + class LRRMessage(BaseMessage): """ @@ -311,12 +361,6 @@ class LRRMessage(BaseMessage): if data is not None: self._parse_message(data) - def __str__(self): - """ - String conversion operator. - """ - return self.raw - def _parse_message(self, data): """ Parses the raw message from the device. @@ -334,3 +378,15 @@ class LRRMessage(BaseMessage): except ValueError: raise InvalidMessageError('Received invalid message: {0}'.format(data)) + + def dict(self, **kwargs): + """ + Dictionary representation. + """ + return dict( + time = self.timestamp, + event_data = self.event_data, + event_type = self.event_type, + partition = self.partition, + **kwargs + ) From ce58e5554a54f8abc33e18043e3b23f713f5a6fb Mon Sep 17 00:00:00 2001 From: Scott Petersen Date: Tue, 29 Apr 2014 11:54:26 -0700 Subject: [PATCH 11/11] Fixed tabs. --- alarmdecoder/messages.py | 92 +++++++++++++++++++++------------------- 1 file changed, 48 insertions(+), 44 deletions(-) diff --git a/alarmdecoder/messages.py b/alarmdecoder/messages.py index b613f15..f44edff 100644 --- a/alarmdecoder/messages.py +++ b/alarmdecoder/messages.py @@ -42,10 +42,14 @@ class BaseMessage(object): return self.raw def dict(self, **kwargs): - """ - Dictionary representation. - """ - return dict(time=self.timestamp, mesg=self.raw, **kwargs) + """ + Dictionary representation. + """ + return dict( + time=self.timestamp, + mesg=self.raw, + **kwargs + ) def __repr__(self): """ @@ -166,30 +170,30 @@ class Message(BaseMessage): Dictionary representation. """ return dict( - time = self.timestamp, - bitfield = self.bitfield, - numeric_code = self.numeric_code, - panel_data = self.panel_data, - mask = self.mask, - ready = self.ready, - armed_away = self.armed_away, - armed_home = self.armed_home, - backlight_on = self.backlight_on, - programming_mode = self.programming_mode, - beeps = self.beeps, - zone_bypassed = self.zone_bypassed, - ac_power = self.ac_power, - chime_on = self.chime_on, - alarm_event_occurred = self.alarm_event_occurred, - alarm_sounding = self.alarm_sounding, - battery_low = self.battery_low, - entry_delay_off = self.entry_delay_off, - fire_alarm = self.fire_alarm, - check_zone = self.check_zone, - perimeter_only = self.perimeter_only, - text = self.text, - cursor_location = self.cursor_location, - **kwargs + time = self.timestamp, + bitfield = self.bitfield, + numeric_code = self.numeric_code, + panel_data = self.panel_data, + mask = self.mask, + ready = self.ready, + armed_away = self.armed_away, + armed_home = self.armed_home, + backlight_on = self.backlight_on, + programming_mode = self.programming_mode, + beeps = self.beeps, + zone_bypassed = self.zone_bypassed, + ac_power = self.ac_power, + chime_on = self.chime_on, + alarm_event_occurred = self.alarm_event_occurred, + alarm_sounding = self.alarm_sounding, + battery_low = self.battery_low, + entry_delay_off = self.entry_delay_off, + fire_alarm = self.fire_alarm, + check_zone = self.check_zone, + perimeter_only = self.perimeter_only, + text = self.text, + cursor_location = self.cursor_location, + **kwargs ) @@ -257,11 +261,11 @@ class ExpanderMessage(BaseMessage): Dictionary representation. """ return dict( - time = self.timestamp, - address = self.address, - channel = self.channel, - value = self.value, - **kwargs + time = self.timestamp, + address = self.address, + channel = self.channel, + value = self.value, + **kwargs ) @@ -328,12 +332,12 @@ class RFMessage(BaseMessage): Dictionary representation. """ return dict( - time = self.timestamp, - serial_number = self.serial_number, - value = self.value, - battery = self.battery, - supervision = self.supervision, - **kwargs + time = self.timestamp, + serial_number = self.serial_number, + value = self.value, + battery = self.battery, + supervision = self.supervision, + **kwargs ) @@ -384,9 +388,9 @@ class LRRMessage(BaseMessage): Dictionary representation. """ return dict( - time = self.timestamp, - event_data = self.event_data, - event_type = self.event_type, - partition = self.partition, - **kwargs + time = self.timestamp, + event_data = self.event_data, + event_type = self.event_type, + partition = self.partition, + **kwargs )