Browse Source

Merge branch 'dev'

pyserial_fix
Scott Petersen 9 years ago
parent
commit
fea57478e8
14 changed files with 189 additions and 71 deletions
  1. +23
    -3
      alarmdecoder/decoder.py
  2. +38
    -4
      alarmdecoder/devices.py
  3. +1
    -2
      alarmdecoder/messages.py
  4. +36
    -16
      alarmdecoder/zonetracking.py
  5. +8
    -3
      bin/ad2-firmwareupload
  6. +6
    -3
      examples/alarm_email.py
  7. +6
    -3
      examples/lrr_example.py
  8. +6
    -3
      examples/rf_device.py
  9. +0
    -0
      examples/usb_detection.py
  10. +0
    -0
      examples/usb_device.py
  11. +6
    -3
      examples/virtual_zone_expander.py
  12. +0
    -15
      requirements.txt
  13. +0
    -6
      setup.py
  14. +59
    -10
      test/test_ad2.py

+ 23
- 3
alarmdecoder/decoder.py View File

@@ -94,7 +94,7 @@ class AlarmDecoder(object):
:type device: Device :type device: Device
""" """
self._device = device self._device = device
self._zonetracker = Zonetracker()
self._zonetracker = Zonetracker(self)


self._battery_timeout = AlarmDecoder.BATTERY_TIMEOUT self._battery_timeout = AlarmDecoder.BATTERY_TIMEOUT
self._fire_timeout = AlarmDecoder.FIRE_TIMEOUT self._fire_timeout = AlarmDecoder.FIRE_TIMEOUT
@@ -106,6 +106,7 @@ class AlarmDecoder(object):
self._battery_status = (False, 0) self._battery_status = (False, 0)
self._panic_status = None self._panic_status = None
self._relay_status = {} self._relay_status = {}
self._internal_address_mask = 0xFFFFFFFF


self.address = 18 self.address = 18
self.configbits = 0xFF00 self.configbits = 0xFF00
@@ -177,6 +178,25 @@ class AlarmDecoder(object):
""" """
self._fire_timeout = value self._fire_timeout = value


@property
def internal_address_mask(self):
"""
Retrieves the address mask used for updating internal status.

:returns: address mask
"""
return self._internal_address_mask

@internal_address_mask.setter
def internal_address_mask(self, value):
"""
Sets the address mask used internally for updating status.

:param value: address mask
:type value: int
"""
self._internal_address_mask = value

def open(self, baudrate=None, no_reader_thread=False): def open(self, baudrate=None, no_reader_thread=False):
""" """
Opens the device. Opens the device.
@@ -344,10 +364,10 @@ class AlarmDecoder(object):
""" """
msg = Message(data) msg = Message(data)


if self.address_mask & msg.mask > 0:
if self._internal_address_mask & msg.mask > 0:
self._update_internal_states(msg) self._update_internal_states(msg)


self.on_message(message=msg)
self.on_message(message=msg)


return msg return msg




+ 38
- 4
alarmdecoder/devices.py View File

@@ -15,19 +15,35 @@ This module contains different types of devices belonging to the `AlarmDecoder`_
.. moduleauthor:: Scott Petersen <scott@nutech.com> .. moduleauthor:: Scott Petersen <scott@nutech.com>
""" """


import usb.core
import usb.util
import time import time
import threading import threading
import serial import serial
import serial.tools.list_ports import serial.tools.list_ports
import socket import socket


from OpenSSL import SSL, crypto
from pyftdi.pyftdi.ftdi import Ftdi, FtdiError
from .util import CommError, TimeoutError, NoDeviceError, InvalidMessageError from .util import CommError, TimeoutError, NoDeviceError, InvalidMessageError
from .event import event from .event import event


try:
from pyftdi.pyftdi.ftdi import Ftdi, FtdiError
import usb.core
import usb.util

have_pyftdi = True

except ImportError:
have_pyftdi = False

try:
from OpenSSL import SSL, crypto

have_openssl = True

except ImportError:
from collections import namedtuple
SSL = namedtuple('SSL', ['Error', 'WantReadError', 'SysCallError'])
have_openssl = False



class Device(object): class Device(object):
""" """
@@ -198,6 +214,9 @@ class USBDevice(Device):
:returns: list of devices :returns: list of devices
:raises: :py:class:`~alarmdecoder.util.CommError` :raises: :py:class:`~alarmdecoder.util.CommError`
""" """
if not have_pyftdi:
raise ImportError('The USBDevice class has been disabled due to missing requirement: pyftdi or pyusb.')

cls.__devices = [] cls.__devices = []


query = cls.PRODUCT_IDS query = cls.PRODUCT_IDS
@@ -234,6 +253,9 @@ class USBDevice(Device):
:returns: :py:class:`USBDevice` object utilizing the specified device :returns: :py:class:`USBDevice` object utilizing the specified device
:raises: :py:class:`~alarmdecoder.util.NoDeviceError` :raises: :py:class:`~alarmdecoder.util.NoDeviceError`
""" """
if not have_pyftdi:
raise ImportError('The USBDevice class has been disabled due to missing requirement: pyftdi or pyusb.')

cls.find_all() cls.find_all()


if len(cls.__devices) == 0: if len(cls.__devices) == 0:
@@ -257,6 +279,9 @@ class USBDevice(Device):


:type on_detached: function :type on_detached: function
""" """
if not have_pyftdi:
raise ImportError('The USBDevice class has been disabled due to missing requirement: pyftdi or pyusb.')

cls.__detect_thread = USBDevice.DetectThread(on_attached, on_detached) cls.__detect_thread = USBDevice.DetectThread(on_attached, on_detached)


try: try:
@@ -271,6 +296,9 @@ class USBDevice(Device):
""" """
Stops the device detection thread. Stops the device detection thread.
""" """
if not have_pyftdi:
raise ImportError('The USBDevice class has been disabled due to missing requirement: pyftdi or pyusb.')

try: try:
cls.__detect_thread.stop() cls.__detect_thread.stop()


@@ -347,6 +375,9 @@ class USBDevice(Device):
index. index.
:type interface: string or int :type interface: string or int
""" """
if not have_pyftdi:
raise ImportError('The USBDevice class has been disabled due to missing requirement: pyftdi or pyusb.')

Device.__init__(self) Device.__init__(self)


self._device = Ftdi() self._device = Ftdi()
@@ -1120,6 +1151,9 @@ class SocketDevice(Device):
:raises: :py:class:`~alarmdecoder.util.CommError` :raises: :py:class:`~alarmdecoder.util.CommError`
""" """


if not have_openssl:
raise ImportError('SSL sockets have been disabled due to missing requirement: pyopenssl.')

try: try:
ctx = SSL.Context(SSL.TLSv1_METHOD) ctx = SSL.Context(SSL.TLSv1_METHOD)




+ 1
- 2
alarmdecoder/messages.py View File

@@ -167,10 +167,9 @@ class Message(BaseMessage):
self.panel_type = PANEL_TYPES[self.bitfield[18]] self.panel_type = PANEL_TYPES[self.bitfield[18]]
# pos 20-21 - Unused. # pos 20-21 - Unused.
self.text = alpha.strip('"') self.text = alpha.strip('"')
self.mask = int(self.panel_data[3:3+8], 16)


if self.panel_type == ADEMCO: if self.panel_type == ADEMCO:
self.mask = int(self.panel_data[3:3+8], 16)

if int(self.panel_data[19:21], 16) & 0x01 > 0: if int(self.panel_data[19:21], 16) & 0x01 > 0:
# Current cursor location on the alpha display. # Current cursor location on the alpha display.
self.cursor_location = int(self.panel_data[21:23], 16) self.cursor_location = int(self.panel_data[21:23], 16)


+ 36
- 16
alarmdecoder/zonetracking.py View File

@@ -11,6 +11,7 @@ import time


from .event import event from .event import event
from .messages import ExpanderMessage from .messages import ExpanderMessage
from .panels import ADEMCO, DSC




class Zone(object): class Zone(object):
@@ -37,8 +38,10 @@ class Zone(object):
"""Zone status""" """Zone status"""
timestamp = None timestamp = None
"""Timestamp of last update""" """Timestamp of last update"""
expander = False
"""Does this zone exist on an expander?"""


def __init__(self, zone=0, name='', status=CLEAR):
def __init__(self, zone=0, name='', status=CLEAR, expander=False):
""" """
Constructor Constructor


@@ -53,6 +56,7 @@ class Zone(object):
self.name = name self.name = name
self.status = status self.status = status
self.timestamp = time.time() self.timestamp = time.time()
self.expander = expander


def __str__(self): def __str__(self):
""" """
@@ -116,7 +120,7 @@ class Zonetracker(object):
""" """
self._zones_faulted = value self._zones_faulted = value


def __init__(self):
def __init__(self, alarmdecoder_object):
""" """
Constructor Constructor
""" """
@@ -124,6 +128,8 @@ class Zonetracker(object):
self._zones_faulted = [] self._zones_faulted = []
self._last_zone_fault = 0 self._last_zone_fault = 0


self.alarmdecoder_object = alarmdecoder_object

def update(self, message): def update(self, message):
""" """
Update zone statuses based on the current message. Update zone statuses based on the current message.
@@ -132,9 +138,12 @@ class Zonetracker(object):
:type message: :py:class:`~alarmdecoder.messages.Message` or :py:class:`~alarmdecoder.messages.ExpanderMessage` :type message: :py:class:`~alarmdecoder.messages.Message` or :py:class:`~alarmdecoder.messages.ExpanderMessage`
""" """
if isinstance(message, ExpanderMessage): if isinstance(message, ExpanderMessage):
zone = -1

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, self.alarmdecoder_object.mode)


if zone != -1:
status = Zone.CLEAR status = Zone.CLEAR
if message.value == 1: if message.value == 1:
status = Zone.FAULT status = Zone.FAULT
@@ -149,7 +158,7 @@ class Zonetracker(object):
self._update_zone(zone, status=status) self._update_zone(zone, status=status)


except IndexError: except IndexError:
self._add_zone(zone, status=status)
self._add_zone(zone, status=status, expander=True)


else: else:
# Panel is ready, restore all zones. # Panel is ready, restore all zones.
@@ -209,7 +218,7 @@ class Zonetracker(object):


self._clear_expired_zones() self._clear_expired_zones()


def expander_to_zone(self, address, channel):
def expander_to_zone(self, address, channel, panel_type=ADEMCO):
""" """
Convert an address and channel into a zone number. Convert an address and channel into a zone number.


@@ -221,12 +230,19 @@ class Zonetracker(object):
:returns: zone number associated with an address and channel :returns: 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.
zone = -1


idx = address - 7 # Expanders start at address 7.
if panel_type == ADEMCO:
# TODO: This is going to need to be reworked to support the larger
# panels without fixed addressing on the expanders.


return address + channel + (idx * 7) + 1
idx = address - 7 # Expanders start at address 7.
zone = address + channel + (idx * 7) + 1

elif panel_type == DSC:
zone = (address * 8) + channel

return zone


def _clear_zones(self, zone): def _clear_zones(self, zone):
""" """
@@ -301,7 +317,7 @@ class Zonetracker(object):
if self._zones[z].status != Zone.CLEAR and self._zone_expired(z): if self._zones[z].status != Zone.CLEAR and self._zone_expired(z):
self._update_zone(z, Zone.CLEAR) self._update_zone(z, Zone.CLEAR)


def _add_zone(self, zone, name='', status=Zone.CLEAR):
def _add_zone(self, zone, name='', status=Zone.CLEAR, expander=False):
""" """
Adds a zone to the internal zone list. Adds a zone to the internal zone list.


@@ -313,10 +329,9 @@ class Zonetracker(object):
:type status: int :type status: int
""" """
if not zone in self._zones: if not zone in self._zones:
self._zones[zone] = Zone(zone=zone, name=name, status=status)
self._zones[zone] = Zone(zone=zone, name=name, status=None, expander=expander)


if status != Zone.CLEAR:
self.on_fault(zone=zone)
self._update_zone(zone, status=status)


def _update_zone(self, zone, status=None): def _update_zone(self, zone, status=None):
""" """
@@ -332,9 +347,11 @@ class Zonetracker(object):
if not zone in self._zones: if not zone in self._zones:
raise IndexError('Zone does not exist and cannot be updated: %d', zone) raise IndexError('Zone does not exist and cannot be updated: %d', zone)


if status is not None:
self._zones[zone].status = status
old_status = self._zones[zone].status
if status is None:
status = old_status


self._zones[zone].status = status
self._zones[zone].timestamp = time.time() self._zones[zone].timestamp = time.time()


if status == Zone.CLEAR: if status == Zone.CLEAR:
@@ -342,6 +359,9 @@ class Zonetracker(object):
self._zones_faulted.remove(zone) self._zones_faulted.remove(zone)


self.on_restore(zone=zone) self.on_restore(zone=zone)
else:
if old_status != status and status is not None:
self.on_fault(zone=zone)


def _zone_expired(self, zone): def _zone_expired(self, zone):
""" """
@@ -352,4 +372,4 @@ 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) and self._zones[zone].expander is False

+ 8
- 3
bin/ad2-firmwareupload View File

@@ -37,7 +37,7 @@ def main():
baudrate = 115200 baudrate = 115200


if len(sys.argv) < 2: if len(sys.argv) < 2:
print "Syntax: {0} <firmware> [interface] [baudrate]".format(sys.argv[0])
print "Syntax: {0} <firmware> [device path or hostname:port] [baudrate]".format(sys.argv[0])
sys.exit(1) sys.exit(1)


firmware = sys.argv[1] firmware = sys.argv[1]
@@ -49,8 +49,13 @@ def main():


print "Flashing device: {0} - {2} baud\r\nFirmware: {1}".format(device, firmware, baudrate) print "Flashing device: {0} - {2} baud\r\nFirmware: {1}".format(device, firmware, baudrate)


dev = alarmdecoder.devices.SerialDevice(interface=device)
dev.open(baudrate=baudrate, no_reader_thread=True)
if ':' in device:
hostname, port = device.split(':')
dev = alarmdecoder.devices.SocketDevice(interface=(hostname, int(port)))
dev.open()
else:
dev = alarmdecoder.devices.SerialDevice(interface=device)
dev.open(baudrate=baudrate, no_reader_thread=True)


time.sleep(3) time.sleep(3)
alarmdecoder.util.Firmware.upload(dev, firmware, handle_firmware) alarmdecoder.util.Firmware.upload(dev, firmware, handle_firmware)


+ 6
- 3
examples/alarm_email.py View File

@@ -2,7 +2,7 @@ import time
import smtplib import smtplib
from email.mime.text import MIMEText from email.mime.text import MIMEText
from alarmdecoder import AlarmDecoder from alarmdecoder import AlarmDecoder
from alarmdecoder.devices import USBDevice
from alarmdecoder.devices import SerialDevice


# Configuration values # Configuration values
SUBJECT = "AlarmDecoder - ALARM" SUBJECT = "AlarmDecoder - ALARM"
@@ -13,6 +13,9 @@ SMTP_SERVER = "localhost"
SMTP_USERNAME = None SMTP_USERNAME = None
SMTP_PASSWORD = None SMTP_PASSWORD = None


SERIAL_DEVICE = '/dev/ttyUSB0'
BAUDRATE = 115200

def main(): def main():
""" """
Example application that sends an email when an alarm event is Example application that sends an email when an alarm event is
@@ -20,11 +23,11 @@ def main():
""" """
try: try:
# Retrieve the first USB device # Retrieve the first USB device
device = AlarmDecoder(USBDevice.find())
device = AlarmDecoder(SerialDevice(interface=SERIAL_DEVICE))


# Set up an event handler and open the device # Set up an event handler and open the device
device.on_alarm += handle_alarm device.on_alarm += handle_alarm
with device.open():
with device.open(baudrate=BAUDRATE):
while True: while True:
time.sleep(1) time.sleep(1)




+ 6
- 3
examples/lrr_example.py View File

@@ -1,6 +1,9 @@
import time import time
from alarmdecoder import AlarmDecoder from alarmdecoder import AlarmDecoder
from alarmdecoder.devices import USBDevice
from alarmdecoder.devices import SerialDevice

SERIAL_DEVICE = '/dev/ttyUSB0'
BAUDRATE = 115200


def main(): def main():
""" """
@@ -8,11 +11,11 @@ def main():
""" """
try: try:
# Retrieve the first USB device # Retrieve the first USB device
device = AlarmDecoder(USBDevice.find())
device = AlarmDecoder(SerialDevice(interface=SERIAL_DEVICE))


# Set up an event handler and open the device # Set up an event handler and open the device
device.on_lrr_message += handle_lrr_message device.on_lrr_message += handle_lrr_message
with device.open():
with device.open(baudrate=BAUDRATE):
while True: while True:
time.sleep(1) time.sleep(1)




+ 6
- 3
examples/rf_device.py View File

@@ -1,9 +1,12 @@
import time import time
from alarmdecoder import AlarmDecoder from alarmdecoder import AlarmDecoder
from alarmdecoder.devices import USBDevice
from alarmdecoder.devices import SerialDevice


RF_DEVICE_SERIAL_NUMBER = '0252254' RF_DEVICE_SERIAL_NUMBER = '0252254'


SERIAL_DEVICE = '/dev/ttyUSB0'
BAUDRATE = 115200

def main(): def main():
""" """
Example application that watches for an event from a specific RF device. Example application that watches for an event from a specific RF device.
@@ -18,11 +21,11 @@ def main():
""" """
try: try:
# Retrieve the first USB device # Retrieve the first USB device
device = AlarmDecoder(USBDevice.find())
device = AlarmDecoder(SerialDevice(interface=SERIAL_DEVICE))


# Set up an event handler and open the device # Set up an event handler and open the device
device.on_rfx_message += handle_rfx device.on_rfx_message += handle_rfx
with device.open():
with device.open(baudrate=BAUDRATE):
while True: while True:
time.sleep(1) time.sleep(1)




examples/detection.py → examples/usb_detection.py View File


examples/basics.py → examples/usb_device.py View File


+ 6
- 3
examples/virtual_zone_expander.py View File

@@ -1,11 +1,14 @@
import time import time
from alarmdecoder import AlarmDecoder from alarmdecoder import AlarmDecoder
from alarmdecoder.devices import USBDevice
from alarmdecoder.devices import SerialDevice


# Configuration values # Configuration values
TARGET_ZONE = 41 TARGET_ZONE = 41
WAIT_TIME = 10 WAIT_TIME = 10


SERIAL_DEVICE = '/dev/ttyUSB0'
BAUDRATE = 115200

def main(): def main():
""" """
Example application that periodically faults a virtual zone and then Example application that periodically faults a virtual zone and then
@@ -28,13 +31,13 @@ def main():
""" """
try: try:
# Retrieve the first USB device # Retrieve the first USB device
device = AlarmDecoder(USBDevice.find())
device = AlarmDecoder(SerialDevice(interface=SERIAL_DEVICE))


# Set up an event handlers and open the device # Set up an event handlers and open the device
device.on_zone_fault += handle_zone_fault device.on_zone_fault += handle_zone_fault
device.on_zone_restore += handle_zone_restore device.on_zone_restore += handle_zone_restore


with device.open():
with device.open(baudrate=BAUDRATE):
last_update = time.time() last_update = time.time()
while True: while True:
if time.time() - last_update > WAIT_TIME: if time.time() - last_update > WAIT_TIME:


+ 0
- 15
requirements.txt View File

@@ -1,16 +1 @@
Jinja2==2.7.2
MarkupSafe==0.21
Pygments==1.6
Sphinx==1.2.2
argparse==1.2.1
cffi==0.8.2
cryptography==0.3
distribute==0.7.3
docutils==0.11
pyOpenSSL==0.14
pycparser==2.10
pyftdi==0.9.0
pyserial==2.7 pyserial==2.7
pyusb==1.0.0b1
six==1.6.1
wsgiref==0.1.2

+ 0
- 6
setup.py View File

@@ -30,13 +30,7 @@ setup(name='alarmdecoder',
license='MIT', license='MIT',
packages=['alarmdecoder', 'alarmdecoder.event'], packages=['alarmdecoder', 'alarmdecoder.event'],
install_requires=[ install_requires=[
'pyopenssl',
'pyusb>=1.0.0b1',
'pyserial>=2.7', 'pyserial>=2.7',
'pyftdi>=0.9.0',
],
dependency_links=[
'https://github.com/eblot/pyftdi/archive/v0.9.0.tar.gz#egg=pyftdi-0.9.0'
], ],
test_suite='nose.collector', test_suite='nose.collector',
tests_require=['nose', 'mock'], tests_require=['nose', 'mock'],


+ 59
- 10
test/test_ad2.py View File

@@ -24,6 +24,12 @@ class TestAlarmDecoder(TestCase):
self._message_received = False self._message_received = False
self._rfx_message_received = False self._rfx_message_received = False
self._lrr_message_received = False self._lrr_message_received = False
self._expander_message_received = False
self._sending_received_status = None
self._alarm_restored = False
self._on_boot_received = False
self._zone_faulted = None
self._zone_restored = None


self._device = Mock(spec=USBDevice) self._device = Mock(spec=USBDevice)
self._device.on_open = EventHandler(Event(), self._device) self._device.on_open = EventHandler(Event(), self._device)
@@ -32,15 +38,11 @@ class TestAlarmDecoder(TestCase):
self._device.on_write = EventHandler(Event(), self._device) self._device.on_write = EventHandler(Event(), self._device)


self._decoder = AlarmDecoder(self._device) self._decoder = AlarmDecoder(self._device)

self._decoder._zonetracker = Mock(spec=Zonetracker)
self._decoder._zonetracker.on_fault = EventHandler(Event(), self._decoder._zonetracker)
self._decoder._zonetracker.on_restore = EventHandler(Event(), self._decoder._zonetracker)

self._decoder.on_panic += self.on_panic self._decoder.on_panic += self.on_panic
self._decoder.on_relay_changed += self.on_relay_changed self._decoder.on_relay_changed += self.on_relay_changed
self._decoder.on_power_changed += self.on_power_changed self._decoder.on_power_changed += self.on_power_changed
self._decoder.on_alarm += self.on_alarm self._decoder.on_alarm += self.on_alarm
self._decoder.on_alarm_restored += self.on_alarm_restored
self._decoder.on_bypass += self.on_bypass self._decoder.on_bypass += self.on_bypass
self._decoder.on_low_battery += self.on_battery self._decoder.on_low_battery += self.on_battery
self._decoder.on_fire += self.on_fire self._decoder.on_fire += self.on_fire
@@ -50,6 +52,11 @@ class TestAlarmDecoder(TestCase):
self._decoder.on_message += self.on_message self._decoder.on_message += self.on_message
self._decoder.on_rfx_message += self.on_rfx_message self._decoder.on_rfx_message += self.on_rfx_message
self._decoder.on_lrr_message += self.on_lrr_message self._decoder.on_lrr_message += self.on_lrr_message
self._decoder.on_expander_message += self.on_expander_message
self._decoder.on_sending_received += self.on_sending_received
self._decoder.on_boot += self.on_boot
self._decoder.on_zone_fault += self.on_zone_fault
self._decoder.on_zone_restore += self.on_zone_restore


self._decoder.address_mask = int('ffffffff', 16) self._decoder.address_mask = int('ffffffff', 16)
self._decoder.open() self._decoder.open()
@@ -67,7 +74,10 @@ class TestAlarmDecoder(TestCase):
self._power_changed = kwargs['status'] self._power_changed = kwargs['status']


def on_alarm(self, sender, *args, **kwargs): def on_alarm(self, sender, *args, **kwargs):
self._alarmed = kwargs['status']
self._alarmed = True

def on_alarm_restored(self, sender, *args, **kwargs):
self._alarm_restored = True


def on_bypass(self, sender, *args, **kwargs): def on_bypass(self, sender, *args, **kwargs):
self._bypassed = kwargs['status'] self._bypassed = kwargs['status']
@@ -96,6 +106,21 @@ class TestAlarmDecoder(TestCase):
def on_lrr_message(self, sender, *args, **kwargs): def on_lrr_message(self, sender, *args, **kwargs):
self._lrr_message_received = True self._lrr_message_received = True


def on_expander_message(self, sender, *args, **kwargs):
self._expander_message_received = True

def on_sending_received(self, sender, *args, **kwargs):
self._sending_received_status = kwargs['status']

def on_boot(self, sender, *args, **kwargs):
self._on_boot_received = True

def on_zone_fault(self, sender, *args, **kwargs):
self._zone_faulted = kwargs['zone']

def on_zone_restore(self, sender, *args, **kwargs):
self._zone_restored = kwargs['zone']

def test_open(self): def test_open(self):
self._decoder.open() self._decoder.open()
self._device.open.assert_any_calls() self._device.open.assert_any_calls()
@@ -141,7 +166,7 @@ class TestAlarmDecoder(TestCase):
self._decoder._on_read(self, data='[0000000000000000----],000,[f707000600e5800c0c020000]," "') self._decoder._on_read(self, data='[0000000000000000----],000,[f707000600e5800c0c020000]," "')
self.assertTrue(self._message_received) self.assertTrue(self._message_received)


def test_message_kpe(self):
def test_message_kpm(self):
msg = self._decoder._handle_message('!KPM:[0000000000000000----],000,[f707000600e5800c0c020000]," "') msg = self._decoder._handle_message('!KPM:[0000000000000000----],000,[f707000600e5800c0c020000]," "')
self.assertIsInstance(msg, Message) self.assertIsInstance(msg, Message)


@@ -152,6 +177,9 @@ class TestAlarmDecoder(TestCase):
msg = self._decoder._handle_message('!EXP:07,01,01') msg = self._decoder._handle_message('!EXP:07,01,01')
self.assertIsInstance(msg, ExpanderMessage) self.assertIsInstance(msg, ExpanderMessage)


self._decoder._on_read(self, data='!EXP:07,01,01')
self.assertTrue(self._expander_message_received)

def test_relay_message(self): def test_relay_message(self):
self._decoder.open() self._decoder.open()
msg = self._decoder._handle_message('!REL:12,01,01') msg = self._decoder._handle_message('!REL:12,01,01')
@@ -203,6 +231,7 @@ class TestAlarmDecoder(TestCase):


msg = self._decoder._handle_message('[0000000000000000----],000,[f707000600e5800c0c020000]," "') msg = self._decoder._handle_message('[0000000000000000----],000,[f707000600e5800c0c020000]," "')
self.assertEquals(self._alarmed, False) self.assertEquals(self._alarmed, False)
self.assertEquals(self._alarm_restored, True)


msg = self._decoder._handle_message('[0000000000100000----],000,[f707000600e5800c0c020000]," "') msg = self._decoder._handle_message('[0000000000100000----],000,[f707000600e5800c0c020000]," "')
self.assertEquals(self._alarmed, True) self.assertEquals(self._alarmed, True)
@@ -261,6 +290,26 @@ class TestAlarmDecoder(TestCase):


self._decoder._device.write.assert_called_with('*') self._decoder._device.write.assert_called_with('*')


def test_zonetracker_update(self):
msg = self._decoder._handle_message('[0000000000000000----],000,[f707000600e5800c0c020000]," "')
self._decoder._zonetracker.update.assert_called_with(msg)
def test_sending_received(self):
self._decoder._on_read(self, data='!Sending.done')
self.assertTrue(self._sending_received_status)

self._decoder._on_read(self, data='!Sending.....done')
self.assertFalse(self._sending_received_status)

def test_boot(self):
self._decoder._on_read(self, data='!Ready')
self.assertTrue(self._on_boot_received)

def test_zone_fault_and_restore(self):
self._decoder._on_read(self, data='[00010001000000000A--],003,[f70000051003000008020000000000],"FAULT 03 "')
self.assertEquals(self._zone_faulted, 3)

self._decoder._on_read(self, data='[00010001000000000A--],004,[f70000051003000008020000000000],"FAULT 04 "')
self.assertEquals(self._zone_faulted, 4)

self._decoder._on_read(self, data='[00010001000000000A--],005,[f70000051003000008020000000000],"FAULT 05 "')
self.assertEquals(self._zone_faulted, 5)

self._decoder._on_read(self, data='[00010001000000000A--],004,[f70000051003000008020000000000],"FAULT 04 "')
self.assertEquals(self._zone_restored, 3)

Loading…
Cancel
Save