@@ -12,6 +12,7 @@ from .util import CommError, NoDeviceError | |||||
from .messages import Message, ExpanderMessage, RFMessage, LRRMessage | from .messages import Message, ExpanderMessage, RFMessage, LRRMessage | ||||
from .zonetracking import Zonetracker | from .zonetracking import Zonetracker | ||||
class AlarmDecoder(object): | class AlarmDecoder(object): | ||||
""" | """ | ||||
High-level wrapper around Alarm Decoder (AD2) devices. | High-level wrapper around Alarm Decoder (AD2) devices. | ||||
@@ -237,8 +238,8 @@ class AlarmDecoder(object): | |||||
raise InvalidMessageError() | raise InvalidMessageError() | ||||
msg = None | msg = None | ||||
header = data[0:4] | header = data[0:4] | ||||
if header[0] != '!' or header == '!KPE': | if header[0] != '!' or header == '!KPE': | ||||
msg = Message(data) | msg = Message(data) | ||||
@@ -295,7 +296,7 @@ class AlarmDecoder(object): | |||||
self.on_panic(status=True) | self.on_panic(status=True) | ||||
elif msg.event_type == 'CANCEL': | elif msg.event_type == 'CANCEL': | ||||
if self._panic_status == True: | |||||
if self._panic_status is True: | |||||
self._panic_status = False | self._panic_status = False | ||||
self.on_panic(status=False) | self.on_panic(status=False) | ||||
@@ -321,11 +322,9 @@ class AlarmDecoder(object): | |||||
elif k == 'MASK': | elif k == 'MASK': | ||||
self.address_mask = int(v, 16) | self.address_mask = int(v, 16) | ||||
elif k == 'EXP': | elif k == 'EXP': | ||||
for z in range(5): | |||||
self.emulate_zone[z] = (v[z] == 'Y') | |||||
self.emulate_zone = [v[z] == 'Y' for z in range(5)] | |||||
elif k == 'REL': | elif k == 'REL': | ||||
for r in range(4): | |||||
self.emulate_relay[r] = (v[r] == 'Y') | |||||
self.emulate_relay = [v[r] == 'Y' for r in range(4)] | |||||
elif k == 'LRR': | elif k == 'LRR': | ||||
self.emulate_lrr = (v == 'Y') | self.emulate_lrr = (v == 'Y') | ||||
elif k == 'DEDUPLICATE': | elif k == 'DEDUPLICATE': | ||||
@@ -371,14 +370,14 @@ class AlarmDecoder(object): | |||||
if message.battery_low == self._battery_status[0]: | if message.battery_low == self._battery_status[0]: | ||||
self._battery_status = (self._battery_status[0], time.time()) | self._battery_status = (self._battery_status[0], time.time()) | ||||
else: | else: | ||||
if message.battery_low == True or time.time() > self._battery_status[1] + AlarmDecoder.BATTERY_TIMEOUT: | |||||
if message.battery_low is True or time.time() > self._battery_status[1] + AlarmDecoder.BATTERY_TIMEOUT: | |||||
self._battery_status = (message.battery_low, time.time()) | self._battery_status = (message.battery_low, time.time()) | ||||
self.on_low_battery(status=self._battery_status) | self.on_low_battery(status=self._battery_status) | ||||
if message.fire_alarm == self._fire_status[0]: | if message.fire_alarm == self._fire_status[0]: | ||||
self._fire_status = (self._fire_status[0], time.time()) | self._fire_status = (self._fire_status[0], time.time()) | ||||
else: | else: | ||||
if message.fire_alarm == True or time.time() > self._fire_status[1] + AlarmDecoder.FIRE_TIMEOUT: | |||||
if message.fire_alarm is True or time.time() > self._fire_status[1] + AlarmDecoder.FIRE_TIMEOUT: | |||||
self._fire_status = (message.fire_alarm, time.time()) | self._fire_status = (message.fire_alarm, time.time()) | ||||
self.on_fire(status=self._fire_status) | self.on_fire(status=self._fire_status) | ||||
@@ -4,10 +4,12 @@ Contains different types of devices belonging to the Alarm Decoder (AD2) family. | |||||
.. moduleauthor:: Scott Petersen <scott@nutech.com> | .. moduleauthor:: Scott Petersen <scott@nutech.com> | ||||
""" | """ | ||||
import usb.core, usb.util | |||||
import usb.core | |||||
import usb.util | |||||
import time | import time | ||||
import threading | import threading | ||||
import serial, serial.tools.list_ports | |||||
import serial | |||||
import serial.tools.list_ports | |||||
import socket | import socket | ||||
from OpenSSL import SSL, crypto | from OpenSSL import SSL, crypto | ||||
@@ -16,6 +18,7 @@ from pyftdi.pyftdi.usbtools import * | |||||
from .util import CommError, TimeoutError, NoDeviceError | from .util import CommError, TimeoutError, NoDeviceError | ||||
from .event import event | from .event import event | ||||
class Device(object): | class Device(object): | ||||
""" | """ | ||||
Generic parent device to all Alarm Decoder (AD2) products. | Generic parent device to all Alarm Decoder (AD2) products. | ||||
@@ -143,6 +146,7 @@ class Device(object): | |||||
time.sleep(0.01) | time.sleep(0.01) | ||||
class USBDevice(Device): | class USBDevice(Device): | ||||
""" | """ | ||||
AD2USB device exposed with PyFTDI's interface. | AD2USB device exposed with PyFTDI's interface. | ||||
@@ -334,11 +338,11 @@ class USBDevice(Device): | |||||
# Open the device and start up the thread. | # Open the device and start up the thread. | ||||
try: | try: | ||||
self._device.open(self._vendor_id, | self._device.open(self._vendor_id, | ||||
self._product_id, | |||||
self._endpoint, | |||||
self._device_number, | |||||
self._serial_number, | |||||
self._description) | |||||
self._product_id, | |||||
self._endpoint, | |||||
self._device_number, | |||||
self._serial_number, | |||||
self._description) | |||||
self._device.set_baudrate(baudrate) | self._device.set_baudrate(baudrate) | ||||
@@ -763,6 +767,7 @@ class SerialDevice(Device): | |||||
return ret | return ret | ||||
class SocketDevice(Device): | class SocketDevice(Device): | ||||
""" | """ | ||||
Device that supports communication with an Alarm Decoder (AD2) that is | Device that supports communication with an Alarm Decoder (AD2) that is | ||||
@@ -9,6 +9,7 @@ | |||||
# * Added type check in fire() | # * Added type check in fire() | ||||
# * Removed earg from fire() and added support for args/kwargs. | # * Removed earg from fire() and added support for args/kwargs. | ||||
class Event(object): | class Event(object): | ||||
def __init__(self, doc=None): | def __init__(self, doc=None): | ||||
@@ -9,6 +9,7 @@ import re | |||||
from .util import InvalidMessageError | from .util import InvalidMessageError | ||||
class BaseMessage(object): | class BaseMessage(object): | ||||
""" | """ | ||||
Base class for messages. | Base class for messages. | ||||
@@ -29,6 +30,7 @@ class BaseMessage(object): | |||||
""" | """ | ||||
return self.raw | return self.raw | ||||
class Message(BaseMessage): | class Message(BaseMessage): | ||||
""" | """ | ||||
Represents a message from the alarm panel. | Represents a message from the alarm panel. | ||||
@@ -141,6 +143,7 @@ class Message(BaseMessage): | |||||
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. | self.cursor_location = int(self.bitfield[21:23], 16) # Alpha character index that the cursor is on. | ||||
class ExpanderMessage(BaseMessage): | class ExpanderMessage(BaseMessage): | ||||
""" | """ | ||||
Represents a message from a zone or relay expansion module. | Represents a message from a zone or relay expansion module. | ||||
@@ -151,7 +154,6 @@ class ExpanderMessage(BaseMessage): | |||||
RELAY = 1 | RELAY = 1 | ||||
"""Flag indicating that the expander message relates to a Relay Expander.""" | """Flag indicating that the expander message relates to a Relay Expander.""" | ||||
type = None | type = None | ||||
"""Expander message type: ExpanderMessage.ZONE or ExpanderMessage.RELAY""" | """Expander message type: ExpanderMessage.ZONE or ExpanderMessage.RELAY""" | ||||
address = -1 | address = -1 | ||||
@@ -205,6 +207,7 @@ class ExpanderMessage(BaseMessage): | |||||
else: | else: | ||||
raise InvalidMessageError('Unknown expander message header: {0}'.format(data)) | raise InvalidMessageError('Unknown expander message header: {0}'.format(data)) | ||||
class RFMessage(BaseMessage): | class RFMessage(BaseMessage): | ||||
""" | """ | ||||
Represents a message from an RF receiver. | Represents a message from an RF receiver. | ||||
@@ -267,6 +270,7 @@ class RFMessage(BaseMessage): | |||||
except ValueError: | except ValueError: | ||||
raise InvalidMessageError('Received invalid message: {0}'.format(data)) | raise InvalidMessageError('Received invalid message: {0}'.format(data)) | ||||
class LRRMessage(BaseMessage): | class LRRMessage(BaseMessage): | ||||
""" | """ | ||||
Represent a message from a Long Range Radio. | Represent a message from a Long Range Radio. | ||||
@@ -9,6 +9,7 @@ from ..messages import Message, RFMessage, LRRMessage, ExpanderMessage | |||||
from ..event.event import Event, EventHandler | from ..event.event import Event, EventHandler | ||||
from ..zonetracking import Zonetracker | from ..zonetracking import Zonetracker | ||||
class TestAlarmDecoder(TestCase): | class TestAlarmDecoder(TestCase): | ||||
def setUp(self): | def setUp(self): | ||||
self._panicked = False | self._panicked = False | ||||
@@ -164,6 +164,7 @@ class TestUSBDevice(TestCase): | |||||
with self.assertRaises(CommError): | with self.assertRaises(CommError): | ||||
self._device.read_line() | self._device.read_line() | ||||
class TestSerialDevice(TestCase): | class TestSerialDevice(TestCase): | ||||
def setUp(self): | def setUp(self): | ||||
self._device = SerialDevice() | self._device = SerialDevice() | ||||
@@ -250,6 +251,7 @@ class TestSerialDevice(TestCase): | |||||
with self.assertRaises(CommError): | with self.assertRaises(CommError): | ||||
self._device.read_line() | self._device.read_line() | ||||
class TestSocketDevice(TestCase): | class TestSocketDevice(TestCase): | ||||
def setUp(self): | def setUp(self): | ||||
self._device = SocketDevice() | self._device = SocketDevice() | ||||
@@ -3,6 +3,7 @@ from unittest import TestCase | |||||
from ..messages import Message, ExpanderMessage, RFMessage, LRRMessage | from ..messages import Message, ExpanderMessage, RFMessage, LRRMessage | ||||
from ..util import InvalidMessageError | from ..util import InvalidMessageError | ||||
class TestMessages(TestCase): | class TestMessages(TestCase): | ||||
def setUp(self): | def setUp(self): | ||||
pass | pass | ||||
@@ -4,6 +4,7 @@ from mock import Mock, MagicMock | |||||
from ..messages import Message, ExpanderMessage | from ..messages import Message, ExpanderMessage | ||||
from ..zonetracking import Zonetracker, Zone | from ..zonetracking import Zonetracker, Zone | ||||
class TestZonetracking(TestCase): | class TestZonetracking(TestCase): | ||||
def setUp(self): | def setUp(self): | ||||
self._zonetracker = Zonetracker() | self._zonetracker = Zonetracker() | ||||
@@ -135,7 +136,7 @@ class TestZonetracking(TestCase): | |||||
self._zonetracker.update(msg) | self._zonetracker.update(msg) | ||||
self.assertIn(4, self._zonetracker._zones_faulted) | self.assertIn(4, self._zonetracker._zones_faulted) | ||||
self._zonetracker._zones[4].timestamp -= 35 # forcefully expire the zone | |||||
self._zonetracker._zones[4].timestamp -= 35 # forcefully expire the zone | |||||
# generic message to force an update. | # generic message to force an update. | ||||
msg = Message('[0000000000000000----],000,[f707000600e5800c0c020000]," "') | msg = Message('[0000000000000000----],000,[f707000600e5800c0c020000]," "') | ||||
@@ -7,30 +7,35 @@ Provides utility classes for the Alarm Decoder (AD2) devices. | |||||
import time | import time | ||||
import threading | import threading | ||||
class NoDeviceError(Exception): | class NoDeviceError(Exception): | ||||
""" | """ | ||||
No devices found. | No devices found. | ||||
""" | """ | ||||
pass | pass | ||||
class CommError(Exception): | class CommError(Exception): | ||||
""" | """ | ||||
There was an error communicating with the device. | There was an error communicating with the device. | ||||
""" | """ | ||||
pass | pass | ||||
class TimeoutError(Exception): | class TimeoutError(Exception): | ||||
""" | """ | ||||
There was a timeout while trying to communicate with the device. | There was a timeout while trying to communicate with the device. | ||||
""" | """ | ||||
pass | pass | ||||
class InvalidMessageError(Exception): | class InvalidMessageError(Exception): | ||||
""" | """ | ||||
The format of the panel message was invalid. | The format of the panel message was invalid. | ||||
""" | """ | ||||
pass | pass | ||||
class Firmware(object): | class Firmware(object): | ||||
""" | """ | ||||
Represents firmware for the Alarm Decoder devices. | Represents firmware for the Alarm Decoder devices. | ||||
@@ -10,6 +10,7 @@ import time | |||||
from .event import event | from .event import event | ||||
from .messages import ExpanderMessage | from .messages import ExpanderMessage | ||||
class Zone(object): | class Zone(object): | ||||
""" | """ | ||||
Representation of a panel zone. | Representation of a panel zone. | ||||
@@ -22,7 +23,7 @@ class Zone(object): | |||||
CHECK = 2 # Wire fault | CHECK = 2 # Wire fault | ||||
"""Status indicating that there is a wiring issue with the zone.""" | """Status indicating that there is a wiring issue with the zone.""" | ||||
STATUS = { CLEAR: 'CLEAR', FAULT: 'FAULT', CHECK: 'CHECK' } | |||||
STATUS = {CLEAR: 'CLEAR', FAULT: 'FAULT', CHECK: 'CHECK'} | |||||
def __init__(self, zone=0, name='', status=CLEAR): | def __init__(self, zone=0, name='', status=CLEAR): | ||||
""" | """ | ||||
@@ -52,6 +53,7 @@ class Zone(object): | |||||
""" | """ | ||||
return 'Zone({0}, {1}, ts {2})'.format(self.zone, Zone.STATUS[self.status], self.timestamp) | return 'Zone({0}, {1}, ts {2})'.format(self.zone, Zone.STATUS[self.status], self.timestamp) | ||||
class Zonetracker(object): | class Zonetracker(object): | ||||
""" | """ | ||||
Handles tracking of zone and their statuses. | Handles tracking of zone and their statuses. | ||||