@@ -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', | |||