@@ -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 | |||
) |