Browse Source

Linting.

pyserial_fix
Scott 11 years ago
parent
commit
28896caa64
8 changed files with 187 additions and 133 deletions
  1. +7
    -6
      alarmdecoder/__init__.py
  2. +45
    -34
      alarmdecoder/decoder.py
  3. +74
    -44
      alarmdecoder/devices.py
  4. +5
    -4
      alarmdecoder/messages.py
  5. +1
    -1
      alarmdecoder/tests/test_zonetracking.py
  6. +8
    -6
      alarmdecoder/util.py
  7. +37
    -34
      alarmdecoder/zonetracking.py
  8. +10
    -4
      setup.py

+ 7
- 6
alarmdecoder/__init__.py View File

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

+ 45
- 34
alarmdecoder/decoder.py View File

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



+ 74
- 44
alarmdecoder/devices.py View File

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

+ 5
- 4
alarmdecoder/messages.py View File

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


+ 1
- 1
alarmdecoder/tests/test_zonetracking.py View File

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



+ 8
- 6
alarmdecoder/util.py View File

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



+ 37
- 34
alarmdecoder/zonetracking.py View File

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

+ 10
- 4
setup.py View File

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


Loading…
Cancel
Save