| @@ -2,5 +2,7 @@ build/ | |||
| dist | |||
| tmp | |||
| *.pyc | |||
| *.pyo | |||
| *.egg-info | |||
| bin/ad2-test | |||
| *~ | |||
| @@ -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.""" | |||
| @@ -66,7 +70,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.""" | |||
| @@ -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,7 +299,11 @@ class AlarmDecoder(object): | |||
| :returns: :py:class:`~alarmdecoder.messages.Message` | |||
| """ | |||
| if data is None: | |||
| if data is not None: | |||
| data = data.lstrip('\0') | |||
| if data is None or data == '': | |||
| raise InvalidMessageError() | |||
| msg = None | |||
| @@ -318,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): | |||
| @@ -421,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. | |||
| @@ -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,8 +149,19 @@ class Device(object): | |||
| except TimeoutError: | |||
| pass | |||
| except Exception: | |||
| 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): | |||
| @@ -234,7 +245,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 +404,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. | |||
| @@ -648,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 | |||
| @@ -670,6 +687,9 @@ class SerialDevice(Device): | |||
| except Exception: | |||
| pass | |||
| def fileno(self): | |||
| return self._device.fileno() | |||
| def write(self, data): | |||
| """ | |||
| Writes data to the device. | |||
| @@ -908,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) | |||
| @@ -939,11 +965,14 @@ 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() | |||
| def write(self, data): | |||
| """ | |||
| Writes data to the device. | |||
| @@ -1033,6 +1062,13 @@ 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)) | |||
| except Exception: | |||
| raise | |||
| else: | |||
| if got_line: | |||
| ret, self._buffer = self._buffer, '' | |||
| @@ -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) """ | |||
| @@ -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): | |||
| """ | |||
| @@ -37,6 +41,22 @@ 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): | |||
| """ | |||
| @@ -102,12 +122,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. | |||
| @@ -151,6 +165,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): | |||
| """ | |||
| @@ -183,12 +228,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. | |||
| @@ -217,6 +256,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): | |||
| """ | |||
| @@ -246,12 +297,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. | |||
| @@ -282,6 +327,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): | |||
| """ | |||
| @@ -307,12 +365,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. | |||
| @@ -330,3 +382,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 | |||
| ) | |||