@@ -1,7 +1,8 @@ | |||||
from decoder import AlarmDecoder | |||||
import devices | |||||
import util | |||||
import messages | |||||
import zonetracking | |||||
from alarmdecoder.decoder import AlarmDecoder | |||||
import alarmdecoder.decoder | |||||
import alarmdecoder.devices | |||||
import alarmdecoder.util | |||||
import alarmdecoder.messages | |||||
import alarmdecoder.zonetracking | |||||
__all__ = ['decoder', 'devices', 'util', 'messages', 'zonetracking'] | |||||
__all__ = ['AlarmDecoder', 'decoder', 'devices', 'util', 'messages', 'zonetracking'] |
@@ -5,10 +5,9 @@ Provides the full AlarmDecoder class. | |||||
""" | """ | ||||
import time | import time | ||||
import threading | |||||
from .event import event | from .event import event | ||||
from .util import CommError, NoDeviceError | |||||
from .util import InvalidMessageError | |||||
from .messages import Message, ExpanderMessage, RFMessage, LRRMessage | from .messages import Message, ExpanderMessage, RFMessage, LRRMessage | ||||
from .zonetracking import Zonetracker | from .zonetracking import Zonetracker | ||||
@@ -45,13 +44,13 @@ class AlarmDecoder(object): | |||||
on_write = event.Event('Called when data has been written to the device.') | on_write = event.Event('Called when data has been written to the device.') | ||||
# Constants | # Constants | ||||
F1 = unichr(1) + unichr(1) + unichr(1) | |||||
KEY_F1 = unichr(1) + unichr(1) + unichr(1) | |||||
"""Represents panel function key #1""" | """Represents panel function key #1""" | ||||
F2 = unichr(2) + unichr(2) + unichr(2) | |||||
KEY_F2 = unichr(2) + unichr(2) + unichr(2) | |||||
"""Represents panel function key #2""" | """Represents panel function key #2""" | ||||
F3 = unichr(3) + unichr(3) + unichr(3) | |||||
KEY_F3 = unichr(3) + unichr(3) + unichr(3) | |||||
"""Represents panel function key #3""" | """Represents panel function key #3""" | ||||
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""" | ||||
BATTERY_TIMEOUT = 30 | BATTERY_TIMEOUT = 30 | ||||
@@ -63,7 +62,8 @@ class AlarmDecoder(object): | |||||
""" | """ | ||||
Constructor | Constructor | ||||
:param device: The low-level device used for this Alarm Decoder interface. | |||||
:param device: The low-level device used for this Alarm Decoder | |||||
interface. | |||||
:type device: Device | :type device: Device | ||||
""" | """ | ||||
self._device = device | self._device = device | ||||
@@ -92,7 +92,7 @@ class AlarmDecoder(object): | |||||
""" | """ | ||||
return self | return self | ||||
def __exit__(self, type, value, traceback): | |||||
def __exit__(self, exc_type, exc_value, traceback): | |||||
""" | """ | ||||
Support for context manager __exit__. | Support for context manager __exit__. | ||||
""" | """ | ||||
@@ -115,7 +115,8 @@ class AlarmDecoder(object): | |||||
:param baudrate: The baudrate used for the device. | :param baudrate: The baudrate used for the device. | ||||
:type baudrate: int | :type baudrate: int | ||||
:param no_reader_thread: Specifies whether or not the automatic reader thread should be started or not | |||||
:param no_reader_thread: Specifies whether or not the automatic reader | |||||
thread should be started or not | |||||
:type no_reader_thread: bool | :type no_reader_thread: bool | ||||
""" | """ | ||||
self._wire_events() | self._wire_events() | ||||
@@ -155,7 +156,8 @@ class AlarmDecoder(object): | |||||
""" | """ | ||||
config_string = '' | config_string = '' | ||||
# HACK: Both of these methods are ugly.. but I can't think of an elegant way of doing it. | |||||
# HACK: Both of these methods are ugly.. but I can't think of an | |||||
# elegant way of doing it. | |||||
#config_string += 'ADDRESS={0}&'.format(self.address) | #config_string += 'ADDRESS={0}&'.format(self.address) | ||||
#config_string += 'CONFIGBITS={0:x}&'.format(self.configbits) | #config_string += 'CONFIGBITS={0:x}&'.format(self.configbits) | ||||
@@ -166,13 +168,20 @@ class AlarmDecoder(object): | |||||
#config_string += 'DEDUPLICATE={0}'.format('Y' if self.deduplicate else 'N') | #config_string += 'DEDUPLICATE={0}'.format('Y' if self.deduplicate else 'N') | ||||
config_entries = [] | config_entries = [] | ||||
config_entries.append(('ADDRESS', '{0}'.format(self.address))) | |||||
config_entries.append(('CONFIGBITS', '{0:x}'.format(self.configbits))) | |||||
config_entries.append(('MASK', '{0:x}'.format(self.address_mask))) | |||||
config_entries.append(('EXP', ''.join(['Y' if z else 'N' for z in self.emulate_zone]))) | |||||
config_entries.append(('REL', ''.join(['Y' if r else 'N' for r in self.emulate_relay]))) | |||||
config_entries.append(('LRR', 'Y' if self.emulate_lrr else 'N')) | |||||
config_entries.append(('DEDUPLICATE', 'Y' if self.deduplicate else 'N')) | |||||
config_entries.append(('ADDRESS', | |||||
'{0}'.format(self.address))) | |||||
config_entries.append(('CONFIGBITS', | |||||
'{0:x}'.format(self.configbits))) | |||||
config_entries.append(('MASK', | |||||
'{0:x}'.format(self.address_mask))) | |||||
config_entries.append(('EXP', | |||||
''.join(['Y' if z else 'N' for z in self.emulate_zone]))) | |||||
config_entries.append(('REL', | |||||
''.join(['Y' if r else 'N' for r in self.emulate_relay]))) | |||||
config_entries.append(('LRR', | |||||
'Y' if self.emulate_lrr else 'N')) | |||||
config_entries.append(('DEDUPLICATE', | |||||
'Y' if self.deduplicate else 'N')) | |||||
config_string = '&'.join(['='.join(t) for t in config_entries]) | config_string = '&'.join(['='.join(t) for t in config_entries]) | ||||
@@ -199,7 +208,9 @@ class AlarmDecoder(object): | |||||
# | # | ||||
# Format (expander index, channel) | # Format (expander index, channel) | ||||
if isinstance(zone, tuple): | if isinstance(zone, tuple): | ||||
zone = self._zonetracker._expander_to_zone(*zone) | |||||
expander_idx, channel = zone | |||||
zone = self._zonetracker.expander_to_zone(expander_idx, channel) | |||||
status = 2 if simulate_wire_problem else 1 | status = 2 if simulate_wire_problem else 1 | ||||
@@ -313,22 +324,22 @@ class AlarmDecoder(object): | |||||
""" | """ | ||||
_, config_string = data.split('>') | _, config_string = data.split('>') | ||||
for setting in config_string.split('&'): | for setting in config_string.split('&'): | ||||
k, v = setting.split('=') | |||||
if k == 'ADDRESS': | |||||
self.address = int(v) | |||||
elif k == 'CONFIGBITS': | |||||
self.configbits = int(v, 16) | |||||
elif k == 'MASK': | |||||
self.address_mask = int(v, 16) | |||||
elif k == 'EXP': | |||||
self.emulate_zone = [v[z] == 'Y' for z in range(5)] | |||||
elif k == 'REL': | |||||
self.emulate_relay = [v[r] == 'Y' for r in range(4)] | |||||
elif k == 'LRR': | |||||
self.emulate_lrr = (v == 'Y') | |||||
elif k == 'DEDUPLICATE': | |||||
self.deduplicate = (v == 'Y') | |||||
key, val = setting.split('=') | |||||
if key == 'ADDRESS': | |||||
self.address = int(val) | |||||
elif key == 'CONFIGBITS': | |||||
self.configbits = int(val, 16) | |||||
elif key == 'MASK': | |||||
self.address_mask = int(val, 16) | |||||
elif key == 'EXP': | |||||
self.emulate_zone = [val[z] == 'Y' for z in range(5)] | |||||
elif key == 'REL': | |||||
self.emulate_relay = [val[r] == 'Y' for r in range(4)] | |||||
elif key == 'LRR': | |||||
self.emulate_lrr = (val == 'Y') | |||||
elif key == 'DEDUPLICATE': | |||||
self.deduplicate = (val == 'Y') | |||||
self.on_config_received() | self.on_config_received() | ||||
@@ -13,8 +13,7 @@ import serial.tools.list_ports | |||||
import socket | import socket | ||||
from OpenSSL import SSL, crypto | from OpenSSL import SSL, crypto | ||||
from pyftdi.pyftdi.ftdi import * | |||||
from pyftdi.pyftdi.usbtools import * | |||||
from pyftdi.pyftdi.ftdi import Ftdi, FtdiError | |||||
from .util import CommError, TimeoutError, NoDeviceError | from .util import CommError, TimeoutError, NoDeviceError | ||||
from .event import event | from .event import event | ||||
@@ -46,7 +45,7 @@ class Device(object): | |||||
""" | """ | ||||
return self | return self | ||||
def __exit__(self, type, value, traceback): | |||||
def __exit__(self, exc_type, exc_value, traceback): | |||||
""" | """ | ||||
Support for context manager __exit__. | Support for context manager __exit__. | ||||
""" | """ | ||||
@@ -96,7 +95,7 @@ class Device(object): | |||||
self._read_thread.stop() | self._read_thread.stop() | ||||
self._device.close() | self._device.close() | ||||
except: | |||||
except Exception: | |||||
pass | pass | ||||
self.on_close() | self.on_close() | ||||
@@ -136,14 +135,12 @@ class Device(object): | |||||
try: | try: | ||||
self._device.read_line(timeout=self.READ_TIMEOUT) | self._device.read_line(timeout=self.READ_TIMEOUT) | ||||
except TimeoutError, err: | |||||
except TimeoutError: | |||||
pass | pass | ||||
except Exception, err: | |||||
except Exception: | |||||
self._running = False | self._running = False | ||||
#raise err | |||||
time.sleep(0.01) | time.sleep(0.01) | ||||
@@ -192,9 +189,11 @@ class USBDevice(Device): | |||||
@classmethod | @classmethod | ||||
def find(cls, device=None): | def find(cls, device=None): | ||||
""" | """ | ||||
Factory method that returns the requested USBDevice device, or the first device. | |||||
Factory method that returns the requested USBDevice device, or the | |||||
first device. | |||||
:param device: Tuple describing the USB device to open, as returned by find_all(). | |||||
:param device: Tuple describing the USB device to open, as returned | |||||
by find_all(). | |||||
:type device: tuple | :type device: tuple | ||||
:returns: USBDevice object utilizing the specified device. | :returns: USBDevice object utilizing the specified device. | ||||
@@ -236,7 +235,7 @@ class USBDevice(Device): | |||||
try: | try: | ||||
cls.__detect_thread.stop() | cls.__detect_thread.stop() | ||||
except: | |||||
except Exception: | |||||
pass | pass | ||||
@property | @property | ||||
@@ -305,28 +304,32 @@ class USBDevice(Device): | |||||
""" | """ | ||||
Constructor | Constructor | ||||
:param interface: May specify either the serial number or the device index. | |||||
:param interface: May specify either the serial number or the device | |||||
index. | |||||
:type interface: str or int | :type interface: str or int | ||||
""" | """ | ||||
Device.__init__(self) | Device.__init__(self) | ||||
self._device = Ftdi() | self._device = Ftdi() | ||||
self._interface = 0 | |||||
self._device_number = 0 | self._device_number = 0 | ||||
self._serial_number = None | self._serial_number = None | ||||
self.interface = interface | |||||
self._vendor_id = USBDevice.FTDI_VENDOR_ID | self._vendor_id = USBDevice.FTDI_VENDOR_ID | ||||
self._product_id = USBDevice.FTDI_PRODUCT_ID | self._product_id = USBDevice.FTDI_PRODUCT_ID | ||||
self._endpoint = 0 | self._endpoint = 0 | ||||
self._description = None | self._description = None | ||||
self.interface = interface | |||||
def open(self, baudrate=BAUDRATE, no_reader_thread=False): | def open(self, baudrate=BAUDRATE, no_reader_thread=False): | ||||
""" | """ | ||||
Opens the device. | Opens the device. | ||||
:param baudrate: The baudrate to use. | :param baudrate: The baudrate to use. | ||||
:type baudrate: int | :type baudrate: int | ||||
:param no_reader_thread: Whether or not to automatically start the reader thread. | |||||
:param no_reader_thread: Whether or not to automatically start the | |||||
reader thread. | |||||
:type no_reader_thread: bool | :type no_reader_thread: bool | ||||
:raises: NoDeviceError | :raises: NoDeviceError | ||||
@@ -370,10 +373,10 @@ class USBDevice(Device): | |||||
try: | try: | ||||
Device.close(self) | Device.close(self) | ||||
# HACK: Probably should fork pyftdi and make this call in .close(). | |||||
# HACK: Probably should fork pyftdi and make this call in .close() | |||||
self._device.usb_dev.attach_kernel_driver(self._device_number) | self._device.usb_dev.attach_kernel_driver(self._device_number) | ||||
except: | |||||
except Exception: | |||||
pass | pass | ||||
def write(self, data): | def write(self, data): | ||||
@@ -416,7 +419,8 @@ class USBDevice(Device): | |||||
:param timeout: Read timeout | :param timeout: Read timeout | ||||
:type timeout: float | :type timeout: float | ||||
:param purge_buffer: Indicates whether to purge the buffer prior to reading. | |||||
:param purge_buffer: Indicates whether to purge the buffer prior to | |||||
reading. | |||||
:type purge_buffer: bool | :type purge_buffer: bool | ||||
:returns: The line that was read. | :returns: The line that was read. | ||||
@@ -427,6 +431,7 @@ class USBDevice(Device): | |||||
self._buffer = '' | self._buffer = '' | ||||
def timeout_event(): | def timeout_event(): | ||||
"""Handles read timeout event""" | |||||
timeout_event.reading = False | timeout_event.reading = False | ||||
timeout_event.reading = True | timeout_event.reading = True | ||||
@@ -451,7 +456,8 @@ class USBDevice(Device): | |||||
if self._buffer[-2] == "\r": | if self._buffer[-2] == "\r": | ||||
self._buffer = self._buffer[:-2] | self._buffer = self._buffer[:-2] | ||||
# ignore if we just got \r\n with nothing else in the buffer. | |||||
# Ignore if we just got \r\n with nothing else | |||||
# in the buffer. | |||||
if len(self._buffer) != 0: | if len(self._buffer) != 0: | ||||
got_line = True | got_line = True | ||||
break | break | ||||
@@ -531,17 +537,17 @@ class USBDevice(Device): | |||||
try: | try: | ||||
current_devices = set(USBDevice.find_all()) | current_devices = set(USBDevice.find_all()) | ||||
new_devices = [d for d in current_devices if d not in last_devices] | |||||
removed_devices = [d for d in last_devices if d not in current_devices] | |||||
new_devices = [dev for dev in current_devices if dev not in last_devices] | |||||
removed_devices = [dev for dev in last_devices if dev not in current_devices] | |||||
last_devices = current_devices | last_devices = current_devices | ||||
for d in new_devices: | |||||
self.on_attached(device=d) | |||||
for dev in new_devices: | |||||
self.on_attached(device=dev) | |||||
for d in removed_devices: | |||||
self.on_detached(device=d) | |||||
for dev in removed_devices: | |||||
self.on_detached(device=dev) | |||||
except CommError, err: | |||||
except CommError: | |||||
pass | pass | ||||
time.sleep(0.25) | time.sleep(0.25) | ||||
@@ -575,7 +581,7 @@ class SerialDevice(Device): | |||||
else: | else: | ||||
devices = serial.tools.list_ports.comports() | devices = serial.tools.list_ports.comports() | ||||
except SerialException, err: | |||||
except serial.SerialException, err: | |||||
raise CommError('Error enumerating serial devices: {0}'.format(str(err)), err) | raise CommError('Error enumerating serial devices: {0}'.format(str(err)), err) | ||||
return devices | return devices | ||||
@@ -610,7 +616,8 @@ class SerialDevice(Device): | |||||
self._port = interface | self._port = interface | ||||
self._id = interface | self._id = interface | ||||
self._device = serial.Serial(timeout=0, writeTimeout=0) # Timeout = non-blocking to match pyftdi. | |||||
# Timeout = non-blocking to match pyftdi. | |||||
self._device = serial.Serial(timeout=0, writeTimeout=0) | |||||
def open(self, baudrate=BAUDRATE, no_reader_thread=False): | def open(self, baudrate=BAUDRATE, no_reader_thread=False): | ||||
""" | """ | ||||
@@ -618,7 +625,8 @@ class SerialDevice(Device): | |||||
:param baudrate: The baudrate to use with the device. | :param baudrate: The baudrate to use with the device. | ||||
:type baudrate: int | :type baudrate: int | ||||
:param no_reader_thread: Whether or not to automatically start the reader thread. | |||||
:param no_reader_thread: Whether or not to automatically start the | |||||
reader thread. | |||||
:type no_reader_thread: bool | :type no_reader_thread: bool | ||||
:raises: NoDeviceError | :raises: NoDeviceError | ||||
@@ -635,12 +643,13 @@ class SerialDevice(Device): | |||||
# Open the device and start up the reader thread. | # Open the device and start up the reader thread. | ||||
try: | try: | ||||
self._device.open() | self._device.open() | ||||
self._device.baudrate = baudrate # NOTE: Setting the baudrate before opening the | |||||
# port caused issues with Moschip 7840/7820 | |||||
# USB Serial Driver converter. (mos7840) | |||||
# | |||||
# Moving it to this point seems to resolve | |||||
# all issues with it. | |||||
# NOTE: Setting the baudrate before opening the | |||||
# port caused issues with Moschip 7840/7820 | |||||
# USB Serial Driver converter. (mos7840) | |||||
# | |||||
# Moving it to this point seems to resolve | |||||
# all issues with it. | |||||
self._device.baudrate = baudrate | |||||
except (serial.SerialException, ValueError), err: | except (serial.SerialException, ValueError), err: | ||||
raise NoDeviceError('Error opening device on port {0}.'.format(self._port), err) | raise NoDeviceError('Error opening device on port {0}.'.format(self._port), err) | ||||
@@ -661,7 +670,7 @@ class SerialDevice(Device): | |||||
try: | try: | ||||
Device.close(self) | Device.close(self) | ||||
except: | |||||
except Exception: | |||||
pass | pass | ||||
def write(self, data): | def write(self, data): | ||||
@@ -708,13 +717,19 @@ class SerialDevice(Device): | |||||
:param timeout: The read timeout. | :param timeout: The read timeout. | ||||
:type timeout: float | :type timeout: float | ||||
:param purge_buffer: Indicates whether to purge the buffer prior to reading. | |||||
:param purge_buffer: Indicates whether to purge the buffer prior to | |||||
reading. | |||||
:type purge_buffer: bool | :type purge_buffer: bool | ||||
:returns: The line read. | :returns: The line read. | ||||
:raises: CommError, TimeoutError | :raises: CommError, TimeoutError | ||||
""" | """ | ||||
if purge_buffer: | |||||
self._buffer = '' | |||||
def timeout_event(): | def timeout_event(): | ||||
"""Handles read timeout event""" | |||||
timeout_event.reading = False | timeout_event.reading = False | ||||
timeout_event.reading = True | timeout_event.reading = True | ||||
@@ -731,7 +746,8 @@ class SerialDevice(Device): | |||||
while timeout_event.reading: | while timeout_event.reading: | ||||
buf = self._device.read(1) | buf = self._device.read(1) | ||||
if buf != '' and buf != "\xff": # AD2SERIAL specifically apparently sends down \xFF on boot. | |||||
# NOTE: AD2SERIAL apparently sends down \xFF on boot. | |||||
if buf != '' and buf != "\xff": | |||||
self._buffer += buf | self._buffer += buf | ||||
if buf == "\n": | if buf == "\n": | ||||
@@ -739,7 +755,8 @@ class SerialDevice(Device): | |||||
if self._buffer[-2] == "\r": | if self._buffer[-2] == "\r": | ||||
self._buffer = self._buffer[:-2] | self._buffer = self._buffer[:-2] | ||||
# ignore if we just got \r\n with nothing else in the buffer. | |||||
# Ignore if we just got \r\n with nothing else | |||||
# in the buffer. | |||||
if len(self._buffer) != 0: | if len(self._buffer) != 0: | ||||
got_line = True | got_line = True | ||||
break | break | ||||
@@ -854,7 +871,8 @@ class SocketDevice(Device): | |||||
@property | @property | ||||
def ssl_ca(self): | def ssl_ca(self): | ||||
""" | """ | ||||
Retrieves the SSL Certificate Authority certificate used for authentication. | |||||
Retrieves the SSL Certificate Authority certificate used for | |||||
authentication. | |||||
:returns: The CA path | :returns: The CA path | ||||
""" | """ | ||||
@@ -891,7 +909,8 @@ class SocketDevice(Device): | |||||
:param baudrate: The baudrate to use | :param baudrate: The baudrate to use | ||||
:type baudrate: int | :type baudrate: int | ||||
:param no_reader_thread: Whether or not to automatically open the reader thread. | |||||
:param no_reader_thread: Whether or not to automatically open the reader | |||||
thread. | |||||
:type no_reader_thread: bool | :type no_reader_thread: bool | ||||
:raises: NoDeviceError, CommError | :raises: NoDeviceError, CommError | ||||
@@ -932,11 +951,12 @@ class SocketDevice(Device): | |||||
self._device.shutdown() | self._device.shutdown() | ||||
else: | else: | ||||
self._device.shutdown(socket.SHUT_RDWR) # Make sure that it closes immediately. | |||||
# Make sure that it closes immediately. | |||||
self._device.shutdown(socket.SHUT_RDWR) | |||||
Device.close(self) | Device.close(self) | ||||
except Exception, ex: | |||||
except Exception: | |||||
pass | pass | ||||
def write(self, data): | def write(self, data): | ||||
@@ -987,7 +1007,8 @@ class SocketDevice(Device): | |||||
:param timeout: The read timeout. | :param timeout: The read timeout. | ||||
:type timeout: float | :type timeout: float | ||||
:param purge_buffer: Indicates whether to purge the buffer prior to reading. | |||||
:param purge_buffer: Indicates whether to purge the buffer prior to | |||||
reading. | |||||
:type purge_buffer: bool | :type purge_buffer: bool | ||||
:returns: The line read from the device. | :returns: The line read from the device. | ||||
@@ -998,6 +1019,7 @@ class SocketDevice(Device): | |||||
self._buffer = '' | self._buffer = '' | ||||
def timeout_event(): | def timeout_event(): | ||||
"""Handles read timeout event""" | |||||
timeout_event.reading = False | timeout_event.reading = False | ||||
timeout_event.reading = True | timeout_event.reading = True | ||||
@@ -1022,7 +1044,8 @@ class SocketDevice(Device): | |||||
if self._buffer[-2] == "\r": | if self._buffer[-2] == "\r": | ||||
self._buffer = self._buffer[:-2] | self._buffer = self._buffer[:-2] | ||||
# ignore if we just got \r\n with nothing else in the buffer. | |||||
# Ignore if we just got \r\n with nothing else | |||||
# in the buffer. | |||||
if len(self._buffer) != 0: | if len(self._buffer) != 0: | ||||
got_line = True | got_line = True | ||||
break | break | ||||
@@ -1051,6 +1074,10 @@ class SocketDevice(Device): | |||||
return ret | return ret | ||||
def _init_ssl(self): | def _init_ssl(self): | ||||
""" | |||||
Initializes our device as an SSL connection. | |||||
""" | |||||
try: | try: | ||||
ctx = SSL.Context(SSL.TLSv1_METHOD) | ctx = SSL.Context(SSL.TLSv1_METHOD) | ||||
@@ -1078,4 +1105,7 @@ class SocketDevice(Device): | |||||
raise CommError('Error setting up SSL connection.', err) | raise CommError('Error setting up SSL connection.', err) | ||||
def _verify_ssl_callback(self, connection, x509, errnum, errdepth, ok): | def _verify_ssl_callback(self, connection, x509, errnum, errdepth, ok): | ||||
""" | |||||
SSL verification callback. | |||||
""" | |||||
return ok | return ok |
@@ -110,12 +110,12 @@ class Message(BaseMessage): | |||||
:raises: InvalidMessageError | :raises: InvalidMessageError | ||||
""" | """ | ||||
m = self._regex.match(data) | |||||
match = self._regex.match(data) | |||||
if m is None: | |||||
if match is None: | |||||
raise InvalidMessageError('Received invalid message: {0}'.format(data)) | raise InvalidMessageError('Received invalid message: {0}'.format(data)) | ||||
self.bitfield, self.numeric_code, self.panel_data, alpha = m.group(1, 2, 3, 4) | |||||
self.bitfield, self.numeric_code, self.panel_data, alpha = match.group(1, 2, 3, 4) | |||||
self.mask = int(self.panel_data[3:3+8], 16) | self.mask = int(self.panel_data[3:3+8], 16) | ||||
is_bit_set = lambda bit: not self.bitfield[bit] == "0" | is_bit_set = lambda bit: not self.bitfield[bit] == "0" | ||||
@@ -141,7 +141,8 @@ class Message(BaseMessage): | |||||
self.text = alpha.strip('"') | self.text = alpha.strip('"') | ||||
if int(self.panel_data[19:21], 16) & 0x01 > 0: | 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. | |||||
# Current cursor location on the alpha display. | |||||
self.cursor_location = int(self.bitfield[21:23], 16) | |||||
class ExpanderMessage(BaseMessage): | class ExpanderMessage(BaseMessage): | ||||
@@ -26,7 +26,7 @@ class TestZonetracking(TestCase): | |||||
def _build_expander_message(self, msg): | def _build_expander_message(self, msg): | ||||
msg = ExpanderMessage(msg) | msg = ExpanderMessage(msg) | ||||
zone = self._zonetracker._expander_to_zone(msg.address, msg.channel) | |||||
zone = self._zonetracker.expander_to_zone(msg.address, msg.channel) | |||||
return zone, msg | return zone, msg | ||||
@@ -66,13 +66,13 @@ class Firmware(object): | |||||
""" | """ | ||||
Perform the actual firmware upload to the device. | Perform the actual firmware upload to the device. | ||||
""" | """ | ||||
with open(filename) as f: | |||||
for line in f: | |||||
with open(filename) as upload_file: | |||||
for line in upload_file: | |||||
line = line.rstrip() | line = line.rstrip() | ||||
if line[0] == ':': | if line[0] == ':': | ||||
dev.write(line + "\r") | dev.write(line + "\r") | ||||
res = dev.read_line(timeout=10.0) | |||||
dev.read_line(timeout=10.0) | |||||
if progress_callback is not None: | if progress_callback is not None: | ||||
progress_callback(Firmware.STAGE_UPLOADING) | progress_callback(Firmware.STAGE_UPLOADING) | ||||
@@ -81,9 +81,11 @@ class Firmware(object): | |||||
def read_until(pattern, timeout=0.0): | def read_until(pattern, timeout=0.0): | ||||
""" | """ | ||||
Read characters until a specific pattern is found or the timeout is hit. | |||||
Read characters until a specific pattern is found or the timeout is | |||||
hit. | |||||
""" | """ | ||||
def timeout_event(): | def timeout_event(): | ||||
"""Handles the read timeout event.""" | |||||
timeout_event.reading = False | timeout_event.reading = False | ||||
timeout_event.reading = True | timeout_event.reading = True | ||||
@@ -93,7 +95,6 @@ class Firmware(object): | |||||
timer = threading.Timer(timeout, timeout_event) | timer = threading.Timer(timeout, timeout_event) | ||||
timer.start() | timer.start() | ||||
buf = '' | |||||
position = 0 | position = 0 | ||||
while timeout_event.reading: | while timeout_event.reading: | ||||
@@ -108,7 +109,7 @@ class Firmware(object): | |||||
else: | else: | ||||
position = 0 | position = 0 | ||||
except Exception, err: | |||||
except Exception: | |||||
pass | pass | ||||
if timer: | if timer: | ||||
@@ -118,6 +119,7 @@ class Firmware(object): | |||||
raise TimeoutError('Timeout while waiting for line terminator.') | raise TimeoutError('Timeout while waiting for line terminator.') | ||||
def stage_callback(stage): | def stage_callback(stage): | ||||
"""Callback to update progress for the specified stage.""" | |||||
if progress_callback is not None: | if progress_callback is not None: | ||||
progress_callback(stage) | progress_callback(stage) | ||||
@@ -82,7 +82,7 @@ class Zonetracker(object): | |||||
""" | """ | ||||
if isinstance(message, ExpanderMessage): | if isinstance(message, ExpanderMessage): | ||||
if message.type == ExpanderMessage.ZONE: | if message.type == ExpanderMessage.ZONE: | ||||
zone = self._expander_to_zone(message.address, message.channel) | |||||
zone = self.expander_to_zone(message.address, message.channel) | |||||
status = Zone.CLEAR | status = Zone.CLEAR | ||||
if message.value == 1: | if message.value == 1: | ||||
@@ -90,9 +90,10 @@ class Zonetracker(object): | |||||
elif message.value == 2: | elif message.value == 2: | ||||
status = Zone.CHECK | status = Zone.CHECK | ||||
# NOTE: Expander zone faults are handled differently than regular messages. | |||||
# We don't include them in self._zones_faulted because they are not reported | |||||
# by the panel in it's rolling list of faults. | |||||
# NOTE: Expander zone faults are handled differently than | |||||
# regular messages. We don't include them in | |||||
# self._zones_faulted because they are not reported | |||||
# by the panel in it's rolling list of faults. | |||||
try: | try: | ||||
self._update_zone(zone, status=status) | self._update_zone(zone, status=status) | ||||
@@ -102,12 +103,13 @@ class Zonetracker(object): | |||||
else: | else: | ||||
# Panel is ready, restore all zones. | # Panel is ready, restore all zones. | ||||
# | # | ||||
# NOTE: This will need to be updated to support panels with multiple partitions. | |||||
# In it's current state a ready on partition #1 will end up clearing all zones, even | |||||
# if they exist elsewhere and it shouldn't. | |||||
# NOTE: This will need to be updated to support panels with | |||||
# multiple partitions. In it's current state a ready on | |||||
# partition #1 will end up clearing all zones, even if they | |||||
# exist elsewhere and it shouldn't. | |||||
if message.ready: | if message.ready: | ||||
for z in self._zones_faulted: | |||||
self._update_zone(z, Zone.CLEAR) | |||||
for zone in self._zones_faulted: | |||||
self._update_zone(zone, Zone.CLEAR) | |||||
self._last_zone_fault = 0 | self._last_zone_fault = 0 | ||||
@@ -121,17 +123,18 @@ class Zonetracker(object): | |||||
except ValueError: | except ValueError: | ||||
zone = int(message.numeric_code, 16) | zone = int(message.numeric_code, 16) | ||||
# NOTE: Odd case for ECP failures. Apparently they report as zone 191 (0xBF) regardless | |||||
# of whether or not the 3-digit mode is enabled... so we have to pull it out of the | |||||
# alpha message. | |||||
# NOTE: Odd case for ECP failures. Apparently they report as | |||||
# zone 191 (0xBF) regardless of whether or not the | |||||
# 3-digit mode is enabled... so we have to pull it out | |||||
# of the alpha message. | |||||
if zone == 191: | if zone == 191: | ||||
zone_regex = re.compile('^CHECK (\d+).*$') | zone_regex = re.compile('^CHECK (\d+).*$') | ||||
m = zone_regex.match(message.text) | |||||
if m is None: | |||||
match = zone_regex.match(message.text) | |||||
if match is None: | |||||
return | return | ||||
zone = m.group(1) | |||||
zone = match.group(1) | |||||
# Add new zones and clear expired ones. | # Add new zones and clear expired ones. | ||||
if zone in self._zones_faulted: | if zone in self._zones_faulted: | ||||
@@ -152,6 +155,25 @@ class Zonetracker(object): | |||||
self._clear_expired_zones() | self._clear_expired_zones() | ||||
def expander_to_zone(self, address, channel): | |||||
""" | |||||
Convert an address and channel into a zone number. | |||||
:param address: The expander address | |||||
:type address: int | |||||
:param channel: The channel | |||||
:type channel: int | |||||
:returns: The zone number associated with an address and channel. | |||||
""" | |||||
# TODO: This is going to need to be reworked to support the larger | |||||
# panels without fixed addressing on the expanders. | |||||
idx = address - 7 # Expanders start at address 7. | |||||
return address + channel + (idx * 7) + 1 | |||||
def _clear_zones(self, zone): | def _clear_zones(self, zone): | ||||
""" | """ | ||||
Clear all expired zones from our status list. | Clear all expired zones from our status list. | ||||
@@ -277,22 +299,3 @@ class Zonetracker(object): | |||||
:returns: Whether or not the zone is expired. | :returns: Whether or not the zone is expired. | ||||
""" | """ | ||||
return time.time() > self._zones[zone].timestamp + Zonetracker.EXPIRE | return time.time() > self._zones[zone].timestamp + Zonetracker.EXPIRE | ||||
def _expander_to_zone(self, address, channel): | |||||
""" | |||||
Convert an address and channel into a zone number. | |||||
:param address: The expander address | |||||
:type address: int | |||||
:param channel: The channel | |||||
:type channel: int | |||||
:returns: The zone number associated with an address and channel. | |||||
""" | |||||
# TODO: This is going to need to be reworked to support the larger | |||||
# panels without fixed addressing on the expanders. | |||||
idx = address - 7 # Expanders start at address 7. | |||||
return address + channel + (idx * 7) + 1 |
@@ -1,12 +1,17 @@ | |||||
"""Setup script""" | |||||
from setuptools import setup | from setuptools import setup | ||||
def readme(): | def readme(): | ||||
with open('README.md') as f: | |||||
return f.read() | |||||
"""Returns the contents of README.md""" | |||||
with open('README.md') as readme_file: | |||||
return readme_file.read() | |||||
setup(name='alarmdecoder', | setup(name='alarmdecoder', | ||||
version='0.5', | version='0.5', | ||||
description='Python interface library for the Alarm Decoder (AD2) family of alarm devices, including: the AD2USB, AD2SERIAL and AD2PI.', | |||||
description='Python interface library for the Alarm Decoder (AD2) family ' | |||||
'of alarm devices, including: the AD2USB, AD2SERIAL and AD2PI.', | |||||
long_description=readme(), | long_description=readme(), | ||||
classifiers=[ | classifiers=[ | ||||
'Development Status :: 4 - Beta', | 'Development Status :: 4 - Beta', | ||||
@@ -17,7 +22,8 @@ setup(name='alarmdecoder', | |||||
'Topic :: Home Automation', | 'Topic :: Home Automation', | ||||
'Topic :: Security', | 'Topic :: Security', | ||||
], | ], | ||||
keywords='alarmdecoder alarm decoder ad2 ad2usb ad2serial ad2pi security ademco dsc', | |||||
keywords='alarmdecoder alarm decoder ad2 ad2usb ad2serial ad2pi security ' | |||||
'ademco dsc', | |||||
url='http://github.com/nutechsoftware/alarmdecoder', | url='http://github.com/nutechsoftware/alarmdecoder', | ||||
author='Nu Tech Software Solutions, Inc.', | author='Nu Tech Software Solutions, Inc.', | ||||
author_email='general@support.nutech.com', | author_email='general@support.nutech.com', | ||||