| @@ -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 threading | |||
| from .event import event | |||
| from .util import CommError, NoDeviceError | |||
| from .util import InvalidMessageError | |||
| from .messages import Message, ExpanderMessage, RFMessage, LRRMessage | |||
| from .zonetracking import Zonetracker | |||
| @@ -45,13 +44,13 @@ class AlarmDecoder(object): | |||
| on_write = event.Event('Called when data has been written to the device.') | |||
| # Constants | |||
| F1 = unichr(1) + unichr(1) + unichr(1) | |||
| KEY_F1 = unichr(1) + unichr(1) + unichr(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""" | |||
| F3 = unichr(3) + unichr(3) + unichr(3) | |||
| KEY_F3 = unichr(3) + unichr(3) + unichr(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""" | |||
| BATTERY_TIMEOUT = 30 | |||
| @@ -63,7 +62,8 @@ class AlarmDecoder(object): | |||
| """ | |||
| 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 | |||
| """ | |||
| self._device = device | |||
| @@ -92,7 +92,7 @@ class AlarmDecoder(object): | |||
| """ | |||
| return self | |||
| def __exit__(self, type, value, traceback): | |||
| def __exit__(self, exc_type, exc_value, traceback): | |||
| """ | |||
| Support for context manager __exit__. | |||
| """ | |||
| @@ -115,7 +115,8 @@ class AlarmDecoder(object): | |||
| :param baudrate: The baudrate used for the device. | |||
| :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 | |||
| """ | |||
| self._wire_events() | |||
| @@ -155,7 +156,8 @@ class AlarmDecoder(object): | |||
| """ | |||
| 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 += '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_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]) | |||
| @@ -199,7 +208,9 @@ class AlarmDecoder(object): | |||
| # | |||
| # Format (expander index, channel) | |||
| 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 | |||
| @@ -313,22 +324,22 @@ class AlarmDecoder(object): | |||
| """ | |||
| _, config_string = data.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() | |||
| @@ -13,8 +13,7 @@ import serial.tools.list_ports | |||
| import socket | |||
| 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 .event import event | |||
| @@ -46,7 +45,7 @@ class Device(object): | |||
| """ | |||
| return self | |||
| def __exit__(self, type, value, traceback): | |||
| def __exit__(self, exc_type, exc_value, traceback): | |||
| """ | |||
| Support for context manager __exit__. | |||
| """ | |||
| @@ -96,7 +95,7 @@ class Device(object): | |||
| self._read_thread.stop() | |||
| self._device.close() | |||
| except: | |||
| except Exception: | |||
| pass | |||
| self.on_close() | |||
| @@ -136,14 +135,12 @@ class Device(object): | |||
| try: | |||
| self._device.read_line(timeout=self.READ_TIMEOUT) | |||
| except TimeoutError, err: | |||
| except TimeoutError: | |||
| pass | |||
| except Exception, err: | |||
| except Exception: | |||
| self._running = False | |||
| #raise err | |||
| time.sleep(0.01) | |||
| @@ -192,9 +189,11 @@ class USBDevice(Device): | |||
| @classmethod | |||
| 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 | |||
| :returns: USBDevice object utilizing the specified device. | |||
| @@ -236,7 +235,7 @@ class USBDevice(Device): | |||
| try: | |||
| cls.__detect_thread.stop() | |||
| except: | |||
| except Exception: | |||
| pass | |||
| @property | |||
| @@ -305,28 +304,32 @@ class USBDevice(Device): | |||
| """ | |||
| 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 | |||
| """ | |||
| Device.__init__(self) | |||
| self._device = Ftdi() | |||
| self._interface = 0 | |||
| self._device_number = 0 | |||
| self._serial_number = None | |||
| self.interface = interface | |||
| self._vendor_id = USBDevice.FTDI_VENDOR_ID | |||
| self._product_id = USBDevice.FTDI_PRODUCT_ID | |||
| self._endpoint = 0 | |||
| self._description = None | |||
| self.interface = interface | |||
| def open(self, baudrate=BAUDRATE, no_reader_thread=False): | |||
| """ | |||
| Opens the device. | |||
| :param baudrate: The baudrate to use. | |||
| :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 | |||
| :raises: NoDeviceError | |||
| @@ -370,10 +373,10 @@ class USBDevice(Device): | |||
| try: | |||
| 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) | |||
| except: | |||
| except Exception: | |||
| pass | |||
| def write(self, data): | |||
| @@ -416,7 +419,8 @@ class USBDevice(Device): | |||
| :param timeout: Read timeout | |||
| :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 | |||
| :returns: The line that was read. | |||
| @@ -427,6 +431,7 @@ class USBDevice(Device): | |||
| self._buffer = '' | |||
| def timeout_event(): | |||
| """Handles read timeout event""" | |||
| timeout_event.reading = False | |||
| timeout_event.reading = True | |||
| @@ -451,7 +456,8 @@ class USBDevice(Device): | |||
| if self._buffer[-2] == "\r": | |||
| 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: | |||
| got_line = True | |||
| break | |||
| @@ -531,17 +537,17 @@ class USBDevice(Device): | |||
| try: | |||
| 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 | |||
| 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 | |||
| time.sleep(0.25) | |||
| @@ -575,7 +581,7 @@ class SerialDevice(Device): | |||
| else: | |||
| devices = serial.tools.list_ports.comports() | |||
| except SerialException, err: | |||
| except serial.SerialException, err: | |||
| raise CommError('Error enumerating serial devices: {0}'.format(str(err)), err) | |||
| return devices | |||
| @@ -610,7 +616,8 @@ class SerialDevice(Device): | |||
| self._port = 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): | |||
| """ | |||
| @@ -618,7 +625,8 @@ class SerialDevice(Device): | |||
| :param baudrate: The baudrate to use with the device. | |||
| :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 | |||
| :raises: NoDeviceError | |||
| @@ -635,12 +643,13 @@ class SerialDevice(Device): | |||
| # Open the device and start up the reader thread. | |||
| try: | |||
| 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: | |||
| raise NoDeviceError('Error opening device on port {0}.'.format(self._port), err) | |||
| @@ -661,7 +670,7 @@ class SerialDevice(Device): | |||
| try: | |||
| Device.close(self) | |||
| except: | |||
| except Exception: | |||
| pass | |||
| def write(self, data): | |||
| @@ -708,13 +717,19 @@ class SerialDevice(Device): | |||
| :param timeout: The read timeout. | |||
| :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 | |||
| :returns: The line read. | |||
| :raises: CommError, TimeoutError | |||
| """ | |||
| if purge_buffer: | |||
| self._buffer = '' | |||
| def timeout_event(): | |||
| """Handles read timeout event""" | |||
| timeout_event.reading = False | |||
| timeout_event.reading = True | |||
| @@ -731,7 +746,8 @@ class SerialDevice(Device): | |||
| while timeout_event.reading: | |||
| 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 | |||
| if buf == "\n": | |||
| @@ -739,7 +755,8 @@ class SerialDevice(Device): | |||
| if self._buffer[-2] == "\r": | |||
| 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: | |||
| got_line = True | |||
| break | |||
| @@ -854,7 +871,8 @@ class SocketDevice(Device): | |||
| @property | |||
| 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 | |||
| """ | |||
| @@ -891,7 +909,8 @@ class SocketDevice(Device): | |||
| :param baudrate: The baudrate to use | |||
| :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 | |||
| :raises: NoDeviceError, CommError | |||
| @@ -932,11 +951,12 @@ class SocketDevice(Device): | |||
| self._device.shutdown() | |||
| 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) | |||
| except Exception, ex: | |||
| except Exception: | |||
| pass | |||
| def write(self, data): | |||
| @@ -987,7 +1007,8 @@ class SocketDevice(Device): | |||
| :param timeout: The read timeout. | |||
| :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 | |||
| :returns: The line read from the device. | |||
| @@ -998,6 +1019,7 @@ class SocketDevice(Device): | |||
| self._buffer = '' | |||
| def timeout_event(): | |||
| """Handles read timeout event""" | |||
| timeout_event.reading = False | |||
| timeout_event.reading = True | |||
| @@ -1022,7 +1044,8 @@ class SocketDevice(Device): | |||
| if self._buffer[-2] == "\r": | |||
| 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: | |||
| got_line = True | |||
| break | |||
| @@ -1051,6 +1074,10 @@ class SocketDevice(Device): | |||
| return ret | |||
| def _init_ssl(self): | |||
| """ | |||
| Initializes our device as an SSL connection. | |||
| """ | |||
| try: | |||
| ctx = SSL.Context(SSL.TLSv1_METHOD) | |||
| @@ -1078,4 +1105,7 @@ class SocketDevice(Device): | |||
| raise CommError('Error setting up SSL connection.', err) | |||
| def _verify_ssl_callback(self, connection, x509, errnum, errdepth, ok): | |||
| """ | |||
| SSL verification callback. | |||
| """ | |||
| return ok | |||
| @@ -110,12 +110,12 @@ class Message(BaseMessage): | |||
| :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)) | |||
| 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) | |||
| is_bit_set = lambda bit: not self.bitfield[bit] == "0" | |||
| @@ -141,7 +141,8 @@ class Message(BaseMessage): | |||
| self.text = alpha.strip('"') | |||
| 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): | |||
| @@ -26,7 +26,7 @@ class TestZonetracking(TestCase): | |||
| def _build_expander_message(self, 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 | |||
| @@ -66,13 +66,13 @@ class Firmware(object): | |||
| """ | |||
| 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() | |||
| if line[0] == ':': | |||
| dev.write(line + "\r") | |||
| res = dev.read_line(timeout=10.0) | |||
| dev.read_line(timeout=10.0) | |||
| if progress_callback is not None: | |||
| progress_callback(Firmware.STAGE_UPLOADING) | |||
| @@ -81,9 +81,11 @@ class Firmware(object): | |||
| 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(): | |||
| """Handles the read timeout event.""" | |||
| timeout_event.reading = False | |||
| timeout_event.reading = True | |||
| @@ -93,7 +95,6 @@ class Firmware(object): | |||
| timer = threading.Timer(timeout, timeout_event) | |||
| timer.start() | |||
| buf = '' | |||
| position = 0 | |||
| while timeout_event.reading: | |||
| @@ -108,7 +109,7 @@ class Firmware(object): | |||
| else: | |||
| position = 0 | |||
| except Exception, err: | |||
| except Exception: | |||
| pass | |||
| if timer: | |||
| @@ -118,6 +119,7 @@ class Firmware(object): | |||
| raise TimeoutError('Timeout while waiting for line terminator.') | |||
| def stage_callback(stage): | |||
| """Callback to update progress for the specified stage.""" | |||
| if progress_callback is not None: | |||
| progress_callback(stage) | |||
| @@ -82,7 +82,7 @@ class Zonetracker(object): | |||
| """ | |||
| if isinstance(message, ExpanderMessage): | |||
| 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 | |||
| if message.value == 1: | |||
| @@ -90,9 +90,10 @@ class Zonetracker(object): | |||
| elif message.value == 2: | |||
| 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: | |||
| self._update_zone(zone, status=status) | |||
| @@ -102,12 +103,13 @@ class Zonetracker(object): | |||
| else: | |||
| # 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: | |||
| 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 | |||
| @@ -121,17 +123,18 @@ class Zonetracker(object): | |||
| except ValueError: | |||
| 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: | |||
| 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 | |||
| zone = m.group(1) | |||
| zone = match.group(1) | |||
| # Add new zones and clear expired ones. | |||
| if zone in self._zones_faulted: | |||
| @@ -152,6 +155,25 @@ class Zonetracker(object): | |||
| 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): | |||
| """ | |||
| Clear all expired zones from our status list. | |||
| @@ -277,22 +299,3 @@ class Zonetracker(object): | |||
| :returns: Whether or not the zone is expired. | |||
| """ | |||
| 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 | |||
| 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', | |||
| 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(), | |||
| classifiers=[ | |||
| 'Development Status :: 4 - Beta', | |||
| @@ -17,7 +22,8 @@ setup(name='alarmdecoder', | |||
| 'Topic :: Home Automation', | |||
| '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', | |||
| author='Nu Tech Software Solutions, Inc.', | |||
| author_email='general@support.nutech.com', | |||