@@ -2,5 +2,7 @@ build/ | |||||
dist | dist | ||||
tmp | tmp | ||||
*.pyc | *.pyc | ||||
*.pyo | |||||
*.egg-info | *.egg-info | ||||
bin/ad2-test | bin/ad2-test | ||||
*~ |
@@ -7,6 +7,7 @@ Provides the main AlarmDecoder class. | |||||
""" | """ | ||||
import time | import time | ||||
import re | |||||
from .event import event | from .event import event | ||||
from .util import InvalidMessageError | 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_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_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_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 | # Low-level Events | ||||
on_open = event.Event("This event is called when the device has been opened.\n\n**Callback definition:** *def callback(device)*") | 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""" | """Represents panel function key #3""" | ||||
KEY_F4 = unichr(4) + unichr(4) + unichr(4) | KEY_F4 = unichr(4) + unichr(4) + unichr(4) | ||||
"""Represents panel function key #4""" | """Represents panel function key #4""" | ||||
KEY_PANIC = unichr(5) + unichr(5) + unichr(5) | |||||
"""Represents a panic keypress""" | |||||
BATTERY_TIMEOUT = 30 | BATTERY_TIMEOUT = 30 | ||||
"""Default timeout (in seconds) before the battery status reverts.""" | """Default timeout (in seconds) before the battery status reverts.""" | ||||
@@ -66,7 +70,7 @@ class AlarmDecoder(object): | |||||
"""The keypad address in use by the device.""" | """The keypad address in use by the device.""" | ||||
configbits = 0xFF00 | configbits = 0xFF00 | ||||
"""The configuration bits set on the device.""" | """The configuration bits set on the device.""" | ||||
address_mask = 0x00000000 | |||||
address_mask = 0xFFFFFFFF | |||||
"""The address mask configured on the device.""" | """The address mask configured on the device.""" | ||||
emulate_zone = [False for _ in range(5)] | emulate_zone = [False for _ in range(5)] | ||||
"""List containing the devices zone emulation status.""" | """List containing the devices zone emulation status.""" | ||||
@@ -200,8 +204,9 @@ class AlarmDecoder(object): | |||||
:param data: data to send | :param data: data to send | ||||
:type data: string | :type data: string | ||||
""" | """ | ||||
if self._device: | if self._device: | ||||
self._device.write(data) | |||||
self._device.write(str(data)) | |||||
def get_config(self): | def get_config(self): | ||||
""" | """ | ||||
@@ -294,7 +299,11 @@ class AlarmDecoder(object): | |||||
:returns: :py:class:`~alarmdecoder.messages.Message` | :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() | raise InvalidMessageError() | ||||
msg = None | msg = None | ||||
@@ -318,6 +327,9 @@ class AlarmDecoder(object): | |||||
elif data.startswith('!CONFIG'): | elif data.startswith('!CONFIG'): | ||||
self._handle_config(data) | self._handle_config(data) | ||||
elif data.startswith('!Sending'): | |||||
self._handle_sending(data) | |||||
return msg | return msg | ||||
def _handle_keypad_message(self, data): | def _handle_keypad_message(self, data): | ||||
@@ -421,6 +433,22 @@ class AlarmDecoder(object): | |||||
self.on_config_received() | 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): | def _update_internal_states(self, message): | ||||
""" | """ | ||||
Updates internal device states. | Updates internal device states. | ||||
@@ -25,7 +25,7 @@ import socket | |||||
from OpenSSL import SSL, crypto | from OpenSSL import SSL, crypto | ||||
from pyftdi.pyftdi.ftdi import Ftdi, FtdiError | from pyftdi.pyftdi.ftdi import Ftdi, FtdiError | ||||
from .util import CommError, TimeoutError, NoDeviceError | |||||
from .util import CommError, TimeoutError, NoDeviceError, InvalidMessageError | |||||
from .event import event | from .event import event | ||||
@@ -149,8 +149,19 @@ class Device(object): | |||||
except TimeoutError: | except TimeoutError: | ||||
pass | 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 | self._running = False | ||||
raise | |||||
class USBDevice(Device): | class USBDevice(Device): | ||||
@@ -234,7 +245,10 @@ class USBDevice(Device): | |||||
""" | """ | ||||
cls.__detect_thread = USBDevice.DetectThread(on_attached, on_detached) | cls.__detect_thread = USBDevice.DetectThread(on_attached, on_detached) | ||||
cls.find_all() | |||||
try: | |||||
cls.find_all() | |||||
except CommError: | |||||
pass | |||||
cls.__detect_thread.start() | cls.__detect_thread.start() | ||||
@@ -390,6 +404,9 @@ class USBDevice(Device): | |||||
except Exception: | except Exception: | ||||
pass | pass | ||||
def fileno(self): | |||||
raise NotImplementedError('USB devices do not support fileno()') | |||||
def write(self, data): | def write(self, data): | ||||
""" | """ | ||||
Writes data to the device. | Writes data to the device. | ||||
@@ -648,8 +665,8 @@ class SerialDevice(Device): | |||||
# all issues with it. | # all issues with it. | ||||
self._device.baudrate = baudrate | 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: | else: | ||||
self._running = True | self._running = True | ||||
@@ -670,6 +687,9 @@ class SerialDevice(Device): | |||||
except Exception: | except Exception: | ||||
pass | pass | ||||
def fileno(self): | |||||
return self._device.fileno() | |||||
def write(self, data): | def write(self, data): | ||||
""" | """ | ||||
Writes data to the device. | Writes data to the device. | ||||
@@ -908,9 +928,15 @@ class SocketDevice(Device): | |||||
self._init_ssl() | self._init_ssl() | ||||
self._device.connect((self._host, self._port)) | self._device.connect((self._host, self._port)) | ||||
#self._device.setblocking(1) | |||||
if self._use_ssl: | 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) | self._id = '{0}:{1}'.format(self._host, self._port) | ||||
@@ -939,11 +965,14 @@ class SocketDevice(Device): | |||||
# Make sure that it closes immediately. | # Make sure that it closes immediately. | ||||
self._device.shutdown(socket.SHUT_RDWR) | self._device.shutdown(socket.SHUT_RDWR) | ||||
Device.close(self) | |||||
except Exception: | except Exception: | ||||
pass | pass | ||||
Device.close(self) | |||||
def fileno(self): | |||||
return self._device.fileno() | |||||
def write(self, data): | def write(self, data): | ||||
""" | """ | ||||
Writes data to the device. | Writes data to the device. | ||||
@@ -1033,6 +1062,13 @@ class SocketDevice(Device): | |||||
except socket.error, err: | except socket.error, err: | ||||
raise CommError('Error reading from device: {0}'.format(str(err)), 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: | else: | ||||
if got_line: | if got_line: | ||||
ret, self._buffer = self._buffer, '' | ret, self._buffer = self._buffer, '' | ||||
@@ -31,6 +31,9 @@ class EventHandler(object): | |||||
self.event = event | self.event = event | ||||
self.obj = obj | self.obj = obj | ||||
def __iter__(self): | |||||
return iter(self._getfunctionlist()) | |||||
def _getfunctionlist(self): | def _getfunctionlist(self): | ||||
"""(internal use) """ | """(internal use) """ | ||||
@@ -13,6 +13,7 @@ devices. | |||||
""" | """ | ||||
import re | import re | ||||
import datetime | |||||
from .util import InvalidMessageError | from .util import InvalidMessageError | ||||
@@ -25,11 +26,14 @@ class BaseMessage(object): | |||||
raw = None | raw = None | ||||
"""The raw message text""" | """The raw message text""" | ||||
timestamp = None | |||||
"""The timestamp of the message""" | |||||
def __init__(self): | def __init__(self): | ||||
""" | """ | ||||
Constructor | Constructor | ||||
""" | """ | ||||
pass | |||||
self.timestamp = datetime.datetime.now() | |||||
def __str__(self): | def __str__(self): | ||||
""" | """ | ||||
@@ -37,6 +41,22 @@ class BaseMessage(object): | |||||
""" | """ | ||||
return self.raw | 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): | class Message(BaseMessage): | ||||
""" | """ | ||||
@@ -102,12 +122,6 @@ class Message(BaseMessage): | |||||
if data is not None: | if data is not None: | ||||
self._parse_message(data) | self._parse_message(data) | ||||
def __str__(self): | |||||
""" | |||||
String conversion operator. | |||||
""" | |||||
return self.raw | |||||
def _parse_message(self, data): | def _parse_message(self, data): | ||||
""" | """ | ||||
Parse the message from the device. | Parse the message from the device. | ||||
@@ -151,6 +165,37 @@ class Message(BaseMessage): | |||||
# Current cursor location on the alpha display. | # Current cursor location on the alpha display. | ||||
self.cursor_location = int(self.bitfield[21:23], 16) | 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): | class ExpanderMessage(BaseMessage): | ||||
""" | """ | ||||
@@ -183,12 +228,6 @@ class ExpanderMessage(BaseMessage): | |||||
if data is not None: | if data is not None: | ||||
self._parse_message(data) | self._parse_message(data) | ||||
def __str__(self): | |||||
""" | |||||
String conversion operator. | |||||
""" | |||||
return self.raw | |||||
def _parse_message(self, data): | def _parse_message(self, data): | ||||
""" | """ | ||||
Parse the raw message from the device. | Parse the raw message from the device. | ||||
@@ -217,6 +256,18 @@ class ExpanderMessage(BaseMessage): | |||||
else: | else: | ||||
raise InvalidMessageError('Unknown expander message header: {0}'.format(data)) | 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): | class RFMessage(BaseMessage): | ||||
""" | """ | ||||
@@ -246,12 +297,6 @@ class RFMessage(BaseMessage): | |||||
if data is not None: | if data is not None: | ||||
self._parse_message(data) | self._parse_message(data) | ||||
def __str__(self): | |||||
""" | |||||
String conversion operator. | |||||
""" | |||||
return self.raw | |||||
def _parse_message(self, data): | def _parse_message(self, data): | ||||
""" | """ | ||||
Parses the raw message from the device. | Parses the raw message from the device. | ||||
@@ -282,6 +327,19 @@ class RFMessage(BaseMessage): | |||||
except ValueError: | except ValueError: | ||||
raise InvalidMessageError('Received invalid message: {0}'.format(data)) | 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): | class LRRMessage(BaseMessage): | ||||
""" | """ | ||||
@@ -307,12 +365,6 @@ class LRRMessage(BaseMessage): | |||||
if data is not None: | if data is not None: | ||||
self._parse_message(data) | self._parse_message(data) | ||||
def __str__(self): | |||||
""" | |||||
String conversion operator. | |||||
""" | |||||
return self.raw | |||||
def _parse_message(self, data): | def _parse_message(self, data): | ||||
""" | """ | ||||
Parses the raw message from the device. | Parses the raw message from the device. | ||||
@@ -330,3 +382,15 @@ class LRRMessage(BaseMessage): | |||||
except ValueError: | except ValueError: | ||||
raise InvalidMessageError('Received invalid message: {0}'.format(data)) | 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 | |||||
) |