From e9de0ff73b1fa6cf8d969eb1ada17bef7a935a82 Mon Sep 17 00:00:00 2001 From: Scott Petersen Date: Tue, 16 Sep 2014 11:56:58 -0700 Subject: [PATCH 1/6] Added support for uploading to a networked ad2usb in ad2-firmwareupload. --- bin/ad2-firmwareupload | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/bin/ad2-firmwareupload b/bin/ad2-firmwareupload index 6791624..8223f91 100755 --- a/bin/ad2-firmwareupload +++ b/bin/ad2-firmwareupload @@ -37,7 +37,7 @@ def main(): baudrate = 115200 if len(sys.argv) < 2: - print "Syntax: {0} [interface] [baudrate]".format(sys.argv[0]) + print "Syntax: {0} [device path or hostname:port] [baudrate]".format(sys.argv[0]) sys.exit(1) firmware = sys.argv[1] @@ -49,8 +49,13 @@ def main(): 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) alarmdecoder.util.Firmware.upload(dev, firmware, handle_firmware) From e663d60a37565cee3ad90f575abc25b69d87e80b Mon Sep 17 00:00:00 2001 From: Scott Petersen Date: Fri, 20 Mar 2015 11:54:56 -0700 Subject: [PATCH 2/6] Made USB (via pyftdi) optional. Changed examples to use SerialDevice and moved USB to it's own example. Updated requirements and setup. --- alarmdecoder/devices.py | 28 ++++++++++++++++++--- examples/alarm_email.py | 9 ++++--- examples/lrr_example.py | 9 ++++--- examples/rf_device.py | 9 ++++--- examples/{detection.py => usb_detection.py} | 0 examples/{basics.py => usb_device.py} | 0 examples/virtual_zone_expander.py | 9 ++++--- requirements.txt | 16 ++++-------- setup.py | 5 ---- 9 files changed, 54 insertions(+), 31 deletions(-) rename examples/{detection.py => usb_detection.py} (100%) rename examples/{basics.py => usb_device.py} (100%) diff --git a/alarmdecoder/devices.py b/alarmdecoder/devices.py index 9462926..166b9fa 100644 --- a/alarmdecoder/devices.py +++ b/alarmdecoder/devices.py @@ -15,8 +15,6 @@ This module contains different types of devices belonging to the `AlarmDecoder`_ .. moduleauthor:: Scott Petersen """ -import usb.core -import usb.util import time import threading import serial @@ -24,10 +22,19 @@ import serial.tools.list_ports import socket from OpenSSL import SSL, crypto -from pyftdi.pyftdi.ftdi import Ftdi, FtdiError from .util import CommError, TimeoutError, NoDeviceError, InvalidMessageError 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 + class Device(object): """ @@ -198,6 +205,9 @@ class USBDevice(Device): :returns: list of devices :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 = [] query = cls.PRODUCT_IDS @@ -234,6 +244,9 @@ class USBDevice(Device): :returns: :py:class:`USBDevice` object utilizing the specified device :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() if len(cls.__devices) == 0: @@ -257,6 +270,9 @@ class USBDevice(Device): :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) try: @@ -271,6 +287,9 @@ class USBDevice(Device): """ 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: cls.__detect_thread.stop() @@ -347,6 +366,9 @@ class USBDevice(Device): index. :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) self._device = Ftdi() diff --git a/examples/alarm_email.py b/examples/alarm_email.py index 573d174..999c073 100644 --- a/examples/alarm_email.py +++ b/examples/alarm_email.py @@ -2,7 +2,7 @@ import time import smtplib from email.mime.text import MIMEText from alarmdecoder import AlarmDecoder -from alarmdecoder.devices import USBDevice +from alarmdecoder.devices import SerialDevice # Configuration values SUBJECT = "AlarmDecoder - ALARM" @@ -13,6 +13,9 @@ SMTP_SERVER = "localhost" SMTP_USERNAME = None SMTP_PASSWORD = None +SERIAL_DEVICE = '/dev/ttyUSB0' +BAUDRATE = 115200 + def main(): """ Example application that sends an email when an alarm event is @@ -20,11 +23,11 @@ def main(): """ try: # Retrieve the first USB device - device = AlarmDecoder(USBDevice.find()) + device = AlarmDecoder(SerialDevice(interface=SERIAL_DEVICE)) # Set up an event handler and open the device device.on_alarm += handle_alarm - with device.open(): + with device.open(baudrate=BAUDRATE): while True: time.sleep(1) diff --git a/examples/lrr_example.py b/examples/lrr_example.py index ba1b627..6a2f424 100644 --- a/examples/lrr_example.py +++ b/examples/lrr_example.py @@ -1,6 +1,9 @@ import time from alarmdecoder import AlarmDecoder -from alarmdecoder.devices import USBDevice +from alarmdecoder.devices import SerialDevice + +SERIAL_DEVICE = '/dev/ttyUSB0' +BAUDRATE = 115200 def main(): """ @@ -8,11 +11,11 @@ def main(): """ try: # Retrieve the first USB device - device = AlarmDecoder(USBDevice.find()) + device = AlarmDecoder(SerialDevice(interface=SERIAL_DEVICE)) # Set up an event handler and open the device device.on_lrr_message += handle_lrr_message - with device.open(): + with device.open(baudrate=BAUDRATE): while True: time.sleep(1) diff --git a/examples/rf_device.py b/examples/rf_device.py index a5faaae..6e2f8fb 100644 --- a/examples/rf_device.py +++ b/examples/rf_device.py @@ -1,9 +1,12 @@ import time from alarmdecoder import AlarmDecoder -from alarmdecoder.devices import USBDevice +from alarmdecoder.devices import SerialDevice RF_DEVICE_SERIAL_NUMBER = '0252254' +SERIAL_DEVICE = '/dev/ttyUSB0' +BAUDRATE = 115200 + def main(): """ Example application that watches for an event from a specific RF device. @@ -18,11 +21,11 @@ def main(): """ try: # Retrieve the first USB device - device = AlarmDecoder(USBDevice.find()) + device = AlarmDecoder(SerialDevice(interface=SERIAL_DEVICE)) # Set up an event handler and open the device device.on_rfx_message += handle_rfx - with device.open(): + with device.open(baudrate=BAUDRATE): while True: time.sleep(1) diff --git a/examples/detection.py b/examples/usb_detection.py similarity index 100% rename from examples/detection.py rename to examples/usb_detection.py diff --git a/examples/basics.py b/examples/usb_device.py similarity index 100% rename from examples/basics.py rename to examples/usb_device.py diff --git a/examples/virtual_zone_expander.py b/examples/virtual_zone_expander.py index bd0eb9b..42ac1c1 100644 --- a/examples/virtual_zone_expander.py +++ b/examples/virtual_zone_expander.py @@ -1,11 +1,14 @@ import time from alarmdecoder import AlarmDecoder -from alarmdecoder.devices import USBDevice +from alarmdecoder.devices import SerialDevice # Configuration values TARGET_ZONE = 41 WAIT_TIME = 10 +SERIAL_DEVICE = '/dev/ttyUSB0' +BAUDRATE = 115200 + def main(): """ Example application that periodically faults a virtual zone and then @@ -28,13 +31,13 @@ def main(): """ try: # Retrieve the first USB device - device = AlarmDecoder(USBDevice.find()) + device = AlarmDecoder(SerialDevice(interface=SERIAL_DEVICE)) # Set up an event handlers and open the device device.on_zone_fault += handle_zone_fault device.on_zone_restore += handle_zone_restore - with device.open(): + with device.open(baudrate=BAUDRATE): last_update = time.time() while True: if time.time() - last_update > WAIT_TIME: diff --git a/requirements.txt b/requirements.txt index 09a946a..62e119f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,16 +1,10 @@ -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 +cffi==0.9.2 +cryptography==0.8.1 +enum34==1.0.4 pyOpenSSL==0.14 +pyasn1==0.1.7 pycparser==2.10 -pyftdi==0.9.0 pyserial==2.7 -pyusb==1.0.0b1 -six==1.6.1 +six==1.9.0 wsgiref==0.1.2 diff --git a/setup.py b/setup.py index 0e9d527..8dc1c86 100644 --- a/setup.py +++ b/setup.py @@ -31,12 +31,7 @@ setup(name='alarmdecoder', packages=['alarmdecoder', 'alarmdecoder.event'], install_requires=[ 'pyopenssl', - 'pyusb>=1.0.0b1', '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', tests_require=['nose', 'mock'], From 25738fed5b4daf01c4befcf31782765a427b9b01 Mon Sep 17 00:00:00 2001 From: Scott Petersen Date: Fri, 20 Mar 2015 11:55:29 -0700 Subject: [PATCH 3/6] Added missing tests. --- test/test_ad2.py | 69 +++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 59 insertions(+), 10 deletions(-) diff --git a/test/test_ad2.py b/test/test_ad2.py index 53b99c6..a4771f5 100644 --- a/test/test_ad2.py +++ b/test/test_ad2.py @@ -24,6 +24,12 @@ class TestAlarmDecoder(TestCase): self._message_received = False self._rfx_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.on_open = EventHandler(Event(), self._device) @@ -32,15 +38,11 @@ class TestAlarmDecoder(TestCase): self._device.on_write = EventHandler(Event(), 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_relay_changed += self.on_relay_changed self._decoder.on_power_changed += self.on_power_changed 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_low_battery += self.on_battery self._decoder.on_fire += self.on_fire @@ -50,6 +52,11 @@ class TestAlarmDecoder(TestCase): self._decoder.on_message += self.on_message self._decoder.on_rfx_message += self.on_rfx_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.open() @@ -67,7 +74,10 @@ class TestAlarmDecoder(TestCase): self._power_changed = kwargs['status'] 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): self._bypassed = kwargs['status'] @@ -96,6 +106,21 @@ class TestAlarmDecoder(TestCase): def on_lrr_message(self, sender, *args, **kwargs): 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): self._decoder.open() self._device.open.assert_any_calls() @@ -141,7 +166,7 @@ class TestAlarmDecoder(TestCase): self._decoder._on_read(self, data='[0000000000000000----],000,[f707000600e5800c0c020000]," "') self.assertTrue(self._message_received) - def test_message_kpe(self): + def test_message_kpm(self): msg = self._decoder._handle_message('!KPM:[0000000000000000----],000,[f707000600e5800c0c020000]," "') self.assertIsInstance(msg, Message) @@ -152,6 +177,9 @@ class TestAlarmDecoder(TestCase): msg = self._decoder._handle_message('!EXP:07,01,01') 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): self._decoder.open() msg = self._decoder._handle_message('!REL:12,01,01') @@ -203,6 +231,7 @@ class TestAlarmDecoder(TestCase): msg = self._decoder._handle_message('[0000000000000000----],000,[f707000600e5800c0c020000]," "') self.assertEquals(self._alarmed, False) + self.assertEquals(self._alarm_restored, True) msg = self._decoder._handle_message('[0000000000100000----],000,[f707000600e5800c0c020000]," "') self.assertEquals(self._alarmed, True) @@ -261,6 +290,26 @@ class TestAlarmDecoder(TestCase): 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) From 3e8ebe12668b5827168262ab3a2b1ee9777ece8d Mon Sep 17 00:00:00 2001 From: Scott Petersen Date: Fri, 20 Mar 2015 12:58:00 -0700 Subject: [PATCH 4/6] Made SSL support optional. --- alarmdecoder/devices.py | 14 +++++++++++++- requirements.txt | 9 --------- setup.py | 1 - 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/alarmdecoder/devices.py b/alarmdecoder/devices.py index 166b9fa..e3acd8b 100644 --- a/alarmdecoder/devices.py +++ b/alarmdecoder/devices.py @@ -21,7 +21,6 @@ import serial import serial.tools.list_ports import socket -from OpenSSL import SSL, crypto from .util import CommError, TimeoutError, NoDeviceError, InvalidMessageError from .event import event @@ -35,6 +34,16 @@ try: 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): """ @@ -1142,6 +1151,9 @@ class SocketDevice(Device): :raises: :py:class:`~alarmdecoder.util.CommError` """ + if not have_openssl: + raise ImportError('SSL sockets have been disabled due to missing requirement: pyopenssl.') + try: ctx = SSL.Context(SSL.TLSv1_METHOD) diff --git a/requirements.txt b/requirements.txt index 62e119f..8313187 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,10 +1 @@ -argparse==1.2.1 -cffi==0.9.2 -cryptography==0.8.1 -enum34==1.0.4 -pyOpenSSL==0.14 -pyasn1==0.1.7 -pycparser==2.10 pyserial==2.7 -six==1.9.0 -wsgiref==0.1.2 diff --git a/setup.py b/setup.py index 8dc1c86..b5e733c 100644 --- a/setup.py +++ b/setup.py @@ -30,7 +30,6 @@ setup(name='alarmdecoder', license='MIT', packages=['alarmdecoder', 'alarmdecoder.event'], install_requires=[ - 'pyopenssl', 'pyserial>=2.7', ], test_suite='nose.collector', From 2f1db61db317386ba23e82c7f3438a8e198bfae7 Mon Sep 17 00:00:00 2001 From: Scott Petersen Date: Wed, 19 Aug 2015 13:59:11 -0700 Subject: [PATCH 5/6] Added support for tracking DSC zones. Added internal address mask for filtering status updates. More bugfixes with zonetracking to keep it quiet and avoid missed events. --- alarmdecoder/decoder.py | 24 +++++++++++++++-- alarmdecoder/messages.py | 3 +-- alarmdecoder/zonetracking.py | 52 +++++++++++++++++++++++++----------- 3 files changed, 59 insertions(+), 20 deletions(-) diff --git a/alarmdecoder/decoder.py b/alarmdecoder/decoder.py index 5d12ba8..2a2c47f 100644 --- a/alarmdecoder/decoder.py +++ b/alarmdecoder/decoder.py @@ -94,7 +94,7 @@ class AlarmDecoder(object): :type device: Device """ self._device = device - self._zonetracker = Zonetracker() + self._zonetracker = Zonetracker(self) self._battery_timeout = AlarmDecoder.BATTERY_TIMEOUT self._fire_timeout = AlarmDecoder.FIRE_TIMEOUT @@ -106,6 +106,7 @@ class AlarmDecoder(object): self._battery_status = (False, 0) self._panic_status = None self._relay_status = {} + self._internal_address_mask = 0xFFFFFFFF self.address = 18 self.configbits = 0xFF00 @@ -177,6 +178,25 @@ class AlarmDecoder(object): """ 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): """ Opens the device. @@ -344,7 +364,7 @@ class AlarmDecoder(object): """ msg = Message(data) - if self.address_mask & msg.mask > 0: + if self._internal_address_mask & msg.mask > 0: self._update_internal_states(msg) self.on_message(message=msg) diff --git a/alarmdecoder/messages.py b/alarmdecoder/messages.py index 6ed5d1f..c92e2e6 100644 --- a/alarmdecoder/messages.py +++ b/alarmdecoder/messages.py @@ -167,10 +167,9 @@ class Message(BaseMessage): self.panel_type = PANEL_TYPES[self.bitfield[18]] # pos 20-21 - Unused. self.text = alpha.strip('"') + self.mask = int(self.panel_data[3:3+8], 16) 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: # Current cursor location on the alpha display. self.cursor_location = int(self.panel_data[21:23], 16) diff --git a/alarmdecoder/zonetracking.py b/alarmdecoder/zonetracking.py index 36334b0..ffb8233 100644 --- a/alarmdecoder/zonetracking.py +++ b/alarmdecoder/zonetracking.py @@ -11,6 +11,7 @@ import time from .event import event from .messages import ExpanderMessage +from .panels import ADEMCO, DSC class Zone(object): @@ -37,8 +38,10 @@ class Zone(object): """Zone status""" timestamp = None """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 @@ -53,6 +56,7 @@ class Zone(object): self.name = name self.status = status self.timestamp = time.time() + self.expander = expander def __str__(self): """ @@ -116,7 +120,7 @@ class Zonetracker(object): """ self._zones_faulted = value - def __init__(self): + def __init__(self, alarmdecoder_object): """ Constructor """ @@ -124,6 +128,8 @@ class Zonetracker(object): self._zones_faulted = [] self._last_zone_fault = 0 + self.alarmdecoder_object = alarmdecoder_object + def update(self, 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` """ if isinstance(message, ExpanderMessage): + zone = -1 + 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 if message.value == 1: status = Zone.FAULT @@ -149,7 +158,7 @@ class Zonetracker(object): self._update_zone(zone, status=status) except IndexError: - self._add_zone(zone, status=status) + self._add_zone(zone, status=status, expander=True) else: # Panel is ready, restore all zones. @@ -209,7 +218,7 @@ class Zonetracker(object): 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. @@ -221,12 +230,19 @@ class Zonetracker(object): :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): """ @@ -301,7 +317,7 @@ class Zonetracker(object): if self._zones[z].status != Zone.CLEAR and self._zone_expired(z): 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. @@ -313,10 +329,9 @@ class Zonetracker(object): :type status: int """ 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): """ @@ -332,9 +347,11 @@ class Zonetracker(object): if not zone in self._zones: 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() if status == Zone.CLEAR: @@ -342,6 +359,9 @@ class Zonetracker(object): self._zones_faulted.remove(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): """ @@ -352,4 +372,4 @@ class Zonetracker(object): :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 From ce7008ab266dbbba1c4a40bf605c2e3041f14ea4 Mon Sep 17 00:00:00 2001 From: Scott Petersen Date: Thu, 17 Sep 2015 12:15:30 -0700 Subject: [PATCH 6/6] Fixed internal address mask. --- alarmdecoder/decoder.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/alarmdecoder/decoder.py b/alarmdecoder/decoder.py index 2a2c47f..ac2198c 100644 --- a/alarmdecoder/decoder.py +++ b/alarmdecoder/decoder.py @@ -367,7 +367,7 @@ class AlarmDecoder(object): if self._internal_address_mask & msg.mask > 0: self._update_internal_states(msg) - self.on_message(message=msg) + self.on_message(message=msg) return msg