Browse Source

Cleanup, comments.

pyserial_fix
Scott Petersen 9 years ago
parent
commit
08519474e2
8 changed files with 240 additions and 72 deletions
  1. +32
    -5
      alarmdecoder/decoder.py
  2. +15
    -0
      alarmdecoder/devices.py
  3. +2
    -2
      alarmdecoder/messages/lrr/__init__.py
  4. +80
    -13
      alarmdecoder/messages/lrr/events.py
  5. +10
    -21
      alarmdecoder/messages/lrr/message.py
  6. +73
    -31
      alarmdecoder/messages/lrr/system.py
  7. +3
    -0
      alarmdecoder/states.py
  8. +25
    -0
      alarmdecoder/util.py

+ 32
- 5
alarmdecoder/decoder.py View File

@@ -92,6 +92,8 @@ class AlarmDecoder(object):
"""The status of message deduplication as configured on the device.""" """The status of message deduplication as configured on the device."""
mode = ADEMCO mode = ADEMCO
"""The panel mode that the AlarmDecoder is in. Currently supports ADEMCO and DSC.""" """The panel mode that the AlarmDecoder is in. Currently supports ADEMCO and DSC."""
emulate_com = False
"""The status of the devices COM emulation."""


#Version Information #Version Information
serial_number = 0xFFFFFFFF serial_number = 0xFFFFFFFF
@@ -101,10 +103,6 @@ class AlarmDecoder(object):
version_flags = "" version_flags = ""
"""Device flags enabled""" """Device flags enabled"""


FIRE_STATE_NONE = 0
FIRE_STATE_FIRE = 1
FIRE_STATE_ACKNOWLEDGED = 2

def __init__(self, device, ignore_message_states=False): def __init__(self, device, ignore_message_states=False):
""" """
Constructor Constructor
@@ -112,6 +110,8 @@ class AlarmDecoder(object):
:param device: The low-level device used for this `AlarmDecoder`_ :param device: The low-level device used for this `AlarmDecoder`_
interface. interface.
:type device: Device :type device: Device
:param ignore_message_states: Ignore regular panel messages when updating internal states
:type ignore_message_states: bool
""" """
self._device = device self._device = device
self._zonetracker = Zonetracker(self) self._zonetracker = Zonetracker(self)
@@ -142,6 +142,7 @@ class AlarmDecoder(object):
self.emulate_lrr = False self.emulate_lrr = False
self.deduplicate = False self.deduplicate = False
self.mode = ADEMCO self.mode = ADEMCO
self.emulate_com = False


self.serial_number = 0xFFFFFFFF self.serial_number = 0xFFFFFFFF
self.version_number = 'Unknown' self.version_number = 'Unknown'
@@ -284,6 +285,12 @@ class AlarmDecoder(object):
self.send("C{0}\r".format(self.get_config_string())) self.send("C{0}\r".format(self.get_config_string()))


def get_config_string(self): def get_config_string(self):
"""
Build a configuration string that's compatible with the AlarmDecoder configuration
command from the current values in the object.

:returns: string
"""
config_entries = [] config_entries = []


# HACK: This is ugly.. but I can't think of an elegant way of doing it. # HACK: This is ugly.. but I can't think of an elegant way of doing it.
@@ -297,6 +304,7 @@ class AlarmDecoder(object):
config_entries.append(('LRR', 'Y' if self.emulate_lrr else 'N')) config_entries.append(('LRR', 'Y' if self.emulate_lrr else 'N'))
config_entries.append(('DEDUPLICATE', 'Y' if self.deduplicate else 'N')) config_entries.append(('DEDUPLICATE', 'Y' if self.deduplicate else 'N'))
config_entries.append(('MODE', list(PANEL_TYPES)[list(PANEL_TYPES.values()).index(self.mode)])) config_entries.append(('MODE', list(PANEL_TYPES)[list(PANEL_TYPES.values()).index(self.mode)]))
config_entries.append(('COM', 'Y' if self.emulate_com else 'N'))


config_string = '&'.join(['='.join(t) for t in config_entries]) config_string = '&'.join(['='.join(t) for t in config_entries])


@@ -515,6 +523,8 @@ class AlarmDecoder(object):
self.deduplicate = (val == 'Y') self.deduplicate = (val == 'Y')
elif key == 'MODE': elif key == 'MODE':
self.mode = PANEL_TYPES[val] self.mode = PANEL_TYPES[val]
elif key == 'COM':
self.emulate_com = (val == 'Y')


self.on_config_received() self.on_config_received()


@@ -560,6 +570,8 @@ class AlarmDecoder(object):


:param message: message to use to update :param message: message to use to update
:type message: :py:class:`~alarmdecoder.messages.Message` :type message: :py:class:`~alarmdecoder.messages.Message`
:param status: power status, overrides message bits.
:type status: bool


:returns: bool indicating the new status :returns: bool indicating the new status
""" """
@@ -584,6 +596,10 @@ class AlarmDecoder(object):


:param message: message to use to update :param message: message to use to update
:type message: :py:class:`~alarmdecoder.messages.Message` :type message: :py:class:`~alarmdecoder.messages.Message`
:param status: alarm status, overrides message bits.
:type status: bool
:param user: user associated with alarm event
:type user: string


:returns: bool indicating the new status :returns: bool indicating the new status
""" """
@@ -611,6 +627,10 @@ class AlarmDecoder(object):


:param message: message to use to update :param message: message to use to update
:type message: :py:class:`~alarmdecoder.messages.Message` :type message: :py:class:`~alarmdecoder.messages.Message`
:param status: bypass status, overrides message bits.
:type status: bool
:param zone: zone associated with bypass event
:type zone: int


:returns: bool indicating the new status :returns: bool indicating the new status
""" """
@@ -640,6 +660,10 @@ class AlarmDecoder(object):


:param message: message to use to update :param message: message to use to update
:type message: :py:class:`~alarmdecoder.messages.Message` :type message: :py:class:`~alarmdecoder.messages.Message`
:param status: armed status, overrides message bits
:type status: bool
:param status_stay: armed stay status, overrides message bits
:type status_stay: bool


:returns: bool indicating the new status :returns: bool indicating the new status
""" """
@@ -670,6 +694,8 @@ class AlarmDecoder(object):


:param message: message to use to update :param message: message to use to update
:type message: :py:class:`~alarmdecoder.messages.Message` :type message: :py:class:`~alarmdecoder.messages.Message`
:param status: battery status, overrides message bits
:type status: bool


:returns: boolean indicating the new status :returns: boolean indicating the new status
""" """
@@ -696,6 +722,8 @@ class AlarmDecoder(object):


:param message: message to use to update :param message: message to use to update
:type message: :py:class:`~alarmdecoder.messages.Message` :type message: :py:class:`~alarmdecoder.messages.Message`
:param status: fire status, overrides message bits
:type status: bool


:returns: boolean indicating the new status :returns: boolean indicating the new status
""" """
@@ -807,7 +835,6 @@ class AlarmDecoder(object):
Internal handler for opening the device. Internal handler for opening the device.
""" """
self.get_config() self.get_config()

self.get_version() self.get_version()


self.on_open() self.on_open()


+ 15
- 0
alarmdecoder/devices.py View File

@@ -495,6 +495,11 @@ class USBDevice(Device):
pass pass


def fileno(self): def fileno(self):
"""
File number not supported for USB devices.
:raises: NotImplementedError
"""
raise NotImplementedError('USB devices do not support fileno()') raise NotImplementedError('USB devices do not support fileno()')


def write(self, data): def write(self, data):
@@ -788,6 +793,11 @@ class SerialDevice(Device):
pass pass


def fileno(self): def fileno(self):
"""
Returns the file number associated with the device
:returns: int
"""
return self._device.fileno() return self._device.fileno()


def write(self, data): def write(self, data):
@@ -1103,6 +1113,11 @@ class SocketDevice(Device):
Device.close(self) Device.close(self)


def fileno(self): def fileno(self):
"""
Returns the file number associated with the device
:returns: int
"""
return self._device.fileno() return self._device.fileno()


def write(self, data): def write(self, data):


+ 2
- 2
alarmdecoder/messages/lrr/__init__.py View File

@@ -1,9 +1,9 @@
from .message import LRRMessage from .message import LRRMessage
from .system import LRRSystem from .system import LRRSystem
from .events import get_event_description, LRR_EVENT_TYPE, LRR_CID_EVENT, LRR_DSC_EVENT, LRR_ADEMCO_EVENT, \
from .events import get_event_description, get_event_source, LRR_EVENT_TYPE, LRR_EVENT_STATUS, LRR_CID_EVENT, LRR_DSC_EVENT, LRR_ADEMCO_EVENT, \
LRR_ALARMDECODER_EVENT, LRR_UNKNOWN_EVENT, LRR_CID_MAP, LRR_DSC_MAP, LRR_ADEMCO_MAP, \ LRR_ALARMDECODER_EVENT, LRR_UNKNOWN_EVENT, LRR_CID_MAP, LRR_DSC_MAP, LRR_ADEMCO_MAP, \
LRR_ALARMDECODER_MAP, LRR_UNKNOWN_MAP LRR_ALARMDECODER_MAP, LRR_UNKNOWN_MAP


__all__ = ['get_event_description', 'LRRMessage', 'LRR_EVENT_TYPE', 'LRR_CID_EVENT', 'LRR_DSC_EVENT',
__all__ = ['get_event_description', 'get_event_source', 'LRRMessage', 'LRR_EVENT_TYPE', 'LRR_EVENT_STATUS', 'LRR_CID_EVENT', 'LRR_DSC_EVENT',
'LRR_ADEMCO_EVENT', 'LRR_ALARMDECODER_EVENT', 'LRR_UNKNOWN_EVENT', 'LRR_CID_MAP', 'LRR_ADEMCO_EVENT', 'LRR_ALARMDECODER_EVENT', 'LRR_UNKNOWN_EVENT', 'LRR_CID_MAP',
'LRR_DSC_MAP', 'LRR_ADEMCO_MAP', 'LRR_ALARMDECODER_MAP', 'LRR_UNKNOWN_MAP'] 'LRR_DSC_MAP', 'LRR_ADEMCO_MAP', 'LRR_ALARMDECODER_MAP', 'LRR_UNKNOWN_MAP']

+ 80
- 13
alarmdecoder/messages/lrr/events.py View File

@@ -4,35 +4,76 @@ Constants and utility functions used for LRR event handling.
.. moduleauthor:: Scott Petersen <scott@nutech.com> .. moduleauthor:: Scott Petersen <scott@nutech.com>
""" """


def get_event_description(event_type, value):
description = 'Unknown'
lookup_map = None
def get_event_description(event_type, event_code):
"""
Retrieves the human-readable description of an LRR event.

:param event_type: Base LRR event type. Use LRR_EVENT_TYPE.*
:type event_type: int
:param event_code: LRR event code
:type event_code: int


if event_type in LRR_TYPE_MAP.keys():
lookup_map = LRR_TYPE_MAP[event_type]
:returns: string
"""
description = 'Unknown'
lookup_map = LRR_TYPE_MAP.get(event_type, None)


if value in lookup_map.keys():
description = lookup_map[value]
if lookup_map is not None:
description = lookup_map.get(event_code, description)


return description return description


def get_event_source(prefix):
"""
Retrieves the LRR_EVENT_TYPE corresponding to the prefix provided.abs

:param prefix: Prefix to convert to event type
:type prefix: string

:returns: int
"""
source = LRR_EVENT_TYPE.UNKNOWN

if prefix == 'CID':
source = LRR_EVENT_TYPE.CID
elif prefix == 'DSC':
source = LRR_EVENT_TYPE.DSC
elif prefix == 'AD2':
source = LRR_EVENT_TYPE.ALARMDECODER
elif prefix == 'ADEMCO':
source = LRR_EVENT_TYPE.ADEMCO

return source


class LRR_EVENT_TYPE: class LRR_EVENT_TYPE:
"""
Base LRR event types
"""
CID = 1 CID = 1
DSC = 2 DSC = 2
ADEMCO = 3 ADEMCO = 3
ALARMDECODER = 4 ALARMDECODER = 4
UNKNOWN = 5 UNKNOWN = 5



class LRR_EVENT_STATUS: class LRR_EVENT_STATUS:
"""
LRR event status codes
"""
TRIGGER = 1 TRIGGER = 1
RESTORE = 3 RESTORE = 3



class LRR_CID_EVENT: class LRR_CID_EVENT:
"""
ContactID event codes
"""
MEDICAL = 0x100 MEDICAL = 0x100
MEDICAL_PENDANT = 0x101 MEDICAL_PENDANT = 0x101
MEDICAL_FAIL_TO_REPORT = 0x102 MEDICAL_FAIL_TO_REPORT = 0x102
# 103-108: ? # 103-108: ?
TAMPER_ZONE = 0x109 # Where did we find this?
TAMPER_ZONE = 0x109 # NOTE: Where did we find this?
FIRE = 0x110 FIRE = 0x110
FIRE_SMOKE = 0x111 FIRE_SMOKE = 0x111
FIRE_COMBUSTION = 0x112 FIRE_COMBUSTION = 0x112
@@ -231,7 +272,8 @@ class LRR_CID_EVENT:
STATUS_PANIC_ALARM_RESET = 0x465 STATUS_PANIC_ALARM_RESET = 0x465
ACCESS_SERVICE_ONOFF_PREMISES = 0x466 ACCESS_SERVICE_ONOFF_PREMISES = 0x466
# 467-469: ? # 467-469: ?
OPENCLOSE_PARTIAL_CLOSING = 0x470 # HACK: This is from DSC, and is named far too closely to 0
OPENCLOSE_PARTIAL_CLOSING = 0x470 # HACK: This is from our DSC firmware implementation,
# and is named far too closely to 0x480.
# 471-479: ? # 471-479: ?
OPENCLOSE_PARTIAL_CLOSE = 0x480 OPENCLOSE_PARTIAL_CLOSE = 0x480
# 481-500: ? # 481-500: ?
@@ -343,6 +385,9 @@ class LRR_CID_EVENT:




class LRR_DSC_EVENT: class LRR_DSC_EVENT:
"""
DSC event codes
"""
ZONE_EXPANDER_SUPERVISORY_ALARM = 0x04c ZONE_EXPANDER_SUPERVISORY_ALARM = 0x04c
ZONE_EXPANDER_SUPERVISORY_RESTORE = 0x04d ZONE_EXPANDER_SUPERVISORY_RESTORE = 0x04d
AUX_INPUT_ALARM = 0x051 AUX_INPUT_ALARM = 0x051
@@ -354,18 +399,28 @@ class LRR_DSC_EVENT:




class LRR_ADEMCO_EVENT: class LRR_ADEMCO_EVENT:
"""
ADEMCO event codes
"""
pass pass




class LRR_ALARMDECODER_EVENT: class LRR_ALARMDECODER_EVENT:
"""
AlarmDecoder event codes
"""
CUSTOM_PROG_MSG = 0x0 CUSTOM_PROG_MSG = 0x0
CUSTOM_PROG_KEY = 0x1 CUSTOM_PROG_KEY = 0x1




class LRR_UNKNOWN_EVENT: class LRR_UNKNOWN_EVENT:
"""
Unknown event codes. Realistically there shouldn't ever be anything here.
"""
pass pass




# Map of ContactID event codes to human-readable text.
LRR_CID_MAP = { LRR_CID_MAP = {
LRR_CID_EVENT.MEDICAL: 'Medical Emergency: Non-specific', LRR_CID_EVENT.MEDICAL: 'Medical Emergency: Non-specific',
LRR_CID_EVENT.MEDICAL_PENDANT: 'Emergency Assistance Request', LRR_CID_EVENT.MEDICAL_PENDANT: 'Emergency Assistance Request',
@@ -640,6 +695,7 @@ LRR_CID_MAP = {
LRR_CID_EVENT.OTHER_NO_READ_LOG: 'Other: No Read Log', LRR_CID_EVENT.OTHER_NO_READ_LOG: 'Other: No Read Log',
} }


# Map of DSC event codes to human-readable text.
LRR_DSC_MAP = { LRR_DSC_MAP = {
LRR_DSC_EVENT.ZONE_EXPANDER_SUPERVISORY_ALARM: 'Zone Expander Supervisory Alarm', LRR_DSC_EVENT.ZONE_EXPANDER_SUPERVISORY_ALARM: 'Zone Expander Supervisory Alarm',
LRR_DSC_EVENT.ZONE_EXPANDER_SUPERVISORY_RESTORE: 'Zone Expander Supervisory Restore', LRR_DSC_EVENT.ZONE_EXPANDER_SUPERVISORY_RESTORE: 'Zone Expander Supervisory Restore',
@@ -651,6 +707,7 @@ LRR_DSC_MAP = {
LRR_DSC_EVENT.REPORT_DSC_USER_LOG_EVENT: 'Report DSC User Log Event', LRR_DSC_EVENT.REPORT_DSC_USER_LOG_EVENT: 'Report DSC User Log Event',
} }


# Map of ADEMCO event codes to human-readable text.
LRR_ADEMCO_MAP = { LRR_ADEMCO_MAP = {


} }
@@ -660,10 +717,12 @@ LRR_ALARMDECODER_MAP = {
LRR_ALARMDECODER_EVENT.CUSTOM_PROG_KEY: 'Custom Programming Key' LRR_ALARMDECODER_EVENT.CUSTOM_PROG_KEY: 'Custom Programming Key'
} }


# Map of UNKNOWN event codes to human-readable text.
LRR_UNKNOWN_MAP = { LRR_UNKNOWN_MAP = {


} }


# Map of event type codes to text maps.
LRR_TYPE_MAP = { LRR_TYPE_MAP = {
LRR_EVENT_TYPE.CID: LRR_CID_MAP, LRR_EVENT_TYPE.CID: LRR_CID_MAP,
LRR_EVENT_TYPE.DSC: LRR_DSC_MAP, LRR_EVENT_TYPE.DSC: LRR_DSC_MAP,
@@ -672,6 +731,7 @@ LRR_TYPE_MAP = {
LRR_EVENT_TYPE.UNKNOWN: LRR_UNKNOWN_MAP, LRR_EVENT_TYPE.UNKNOWN: LRR_UNKNOWN_MAP,
} }


# LRR events that should be considered Fire events.
LRR_FIRE_EVENTS = [ LRR_FIRE_EVENTS = [
LRR_CID_EVENT.FIRE, LRR_CID_EVENT.FIRE,
LRR_CID_EVENT.FIRE_SMOKE, LRR_CID_EVENT.FIRE_SMOKE,
@@ -681,9 +741,10 @@ LRR_FIRE_EVENTS = [
LRR_CID_EVENT.FIRE_PULL_STATION, LRR_CID_EVENT.FIRE_PULL_STATION,
LRR_CID_EVENT.FIRE_DUCT, LRR_CID_EVENT.FIRE_DUCT,
LRR_CID_EVENT.FIRE_FLAME, LRR_CID_EVENT.FIRE_FLAME,
LRR_CID_EVENT.OPENCLOSE_CANCEL_BY_USER
LRR_CID_EVENT.OPENCLOSE_CANCEL_BY_USER # HACK: Don't really like having this here
] ]


# LRR events that should be considered Alarm events.
LRR_ALARM_EVENTS = [ LRR_ALARM_EVENTS = [
LRR_CID_EVENT.BURGLARY, LRR_CID_EVENT.BURGLARY,
LRR_CID_EVENT.BURGLARY_PERIMETER, LRR_CID_EVENT.BURGLARY_PERIMETER,
@@ -704,23 +765,27 @@ LRR_ALARM_EVENTS = [
LRR_CID_EVENT.ALARM_LOW_TEMP, LRR_CID_EVENT.ALARM_LOW_TEMP,
LRR_CID_EVENT.ALARM_LOSS_OF_AIR_FLOW, LRR_CID_EVENT.ALARM_LOSS_OF_AIR_FLOW,
LRR_CID_EVENT.ALARM_CARBON_MONOXIDE, LRR_CID_EVENT.ALARM_CARBON_MONOXIDE,
LRR_CID_EVENT.OPENCLOSE_CANCEL_BY_USER
LRR_CID_EVENT.OPENCLOSE_CANCEL_BY_USER # HACK: Don't really like having this here
] ]


# LRR events that should be considered Power events.
LRR_POWER_EVENTS = [ LRR_POWER_EVENTS = [
LRR_CID_EVENT.TROUBLE_AC_LOSS LRR_CID_EVENT.TROUBLE_AC_LOSS
] ]


# LRR events that should be considered Bypass events.
LRR_BYPASS_EVENTS = [ LRR_BYPASS_EVENTS = [
LRR_CID_EVENT.BYPASS_ZONE, LRR_CID_EVENT.BYPASS_ZONE,
LRR_CID_EVENT.BYPASS_24HOUR_ZONE, LRR_CID_EVENT.BYPASS_24HOUR_ZONE,
LRR_CID_EVENT.BYPASS_BURGLARY LRR_CID_EVENT.BYPASS_BURGLARY
] ]


# LRR events that should be considered Battery events.
LRR_BATTERY_EVENTS = [ LRR_BATTERY_EVENTS = [
LRR_CID_EVENT.TROUBLE_LOW_BATTERY LRR_CID_EVENT.TROUBLE_LOW_BATTERY
] ]


# LRR events that should be considered Panic events.
LRR_PANIC_EVENTS = [ LRR_PANIC_EVENTS = [
LRR_CID_EVENT.MEDICAL, LRR_CID_EVENT.MEDICAL,
LRR_CID_EVENT.MEDICAL_PENDANT, LRR_CID_EVENT.MEDICAL_PENDANT,
@@ -731,9 +796,10 @@ LRR_PANIC_EVENTS = [
LRR_CID_EVENT.PANIC_AUDIBLE, LRR_CID_EVENT.PANIC_AUDIBLE,
LRR_CID_EVENT.PANIC_DURESS_ACCESS_GRANTED, LRR_CID_EVENT.PANIC_DURESS_ACCESS_GRANTED,
LRR_CID_EVENT.PANIC_DURESS_EGRESS_GRANTED, LRR_CID_EVENT.PANIC_DURESS_EGRESS_GRANTED,
LRR_CID_EVENT.OPENCLOSE_CANCEL_BY_USER # Canceled panic
LRR_CID_EVENT.OPENCLOSE_CANCEL_BY_USER # HACK: Don't really like having this here
] ]


# LRR events that should be considered Arm events.
LRR_ARM_EVENTS = [ LRR_ARM_EVENTS = [
LRR_CID_EVENT.OPENCLOSE, LRR_CID_EVENT.OPENCLOSE,
LRR_CID_EVENT.OPENCLOSE_BY_USER, LRR_CID_EVENT.OPENCLOSE_BY_USER,
@@ -742,10 +808,11 @@ LRR_ARM_EVENTS = [
LRR_CID_EVENT.OPENCLOSE_REMOTE_ARMDISARM, LRR_CID_EVENT.OPENCLOSE_REMOTE_ARMDISARM,
LRR_CID_EVENT.OPENCLOSE_QUICK_ARM, LRR_CID_EVENT.OPENCLOSE_QUICK_ARM,
LRR_CID_EVENT.OPENCLOSE_KEYSWITCH, LRR_CID_EVENT.OPENCLOSE_KEYSWITCH,
LRR_CID_EVENT.OPENCLOSE_ARMED_STAY,
LRR_CID_EVENT.OPENCLOSE_ARMED_STAY, # HACK: Not sure if I like having these in here.
LRR_CID_EVENT.OPENCLOSE_KEYSWITCH_ARMED_STAY LRR_CID_EVENT.OPENCLOSE_KEYSWITCH_ARMED_STAY
] ]


# LRR events that should be considered Arm Stay events.
LRR_STAY_EVENTS = [ LRR_STAY_EVENTS = [
LRR_CID_EVENT.OPENCLOSE_ARMED_STAY, LRR_CID_EVENT.OPENCLOSE_ARMED_STAY,
LRR_CID_EVENT.OPENCLOSE_KEYSWITCH_ARMED_STAY LRR_CID_EVENT.OPENCLOSE_KEYSWITCH_ARMED_STAY


+ 10
- 21
alarmdecoder/messages/lrr/message.py View File

@@ -12,7 +12,7 @@ devices.
from .. import BaseMessage from .. import BaseMessage
from ...util import InvalidMessageError from ...util import InvalidMessageError


from .events import LRR_EVENT_TYPE, get_event_description
from .events import LRR_EVENT_TYPE, get_event_description, get_event_source




class LRRMessage(BaseMessage): class LRRMessage(BaseMessage):
@@ -70,18 +70,22 @@ class LRRMessage(BaseMessage):


_, values = data.split(':') _, values = data.split(':')
values = values.split(',') values = values.split(',')

# Handle older-format events
if len(values) <= 3: if len(values) <= 3:
self.event_data, self.partition, self.event_type = values self.event_data, self.partition, self.event_type = values
self.version = 1 self.version = 1

# Newer-format events
else: else:
self.event_data, self.partition, self.event_type, self.report_code = values self.event_data, self.partition, self.event_type, self.report_code = values
self.version = 2 self.version = 2


event_type_data = self.event_type.split('_') event_type_data = self.event_type.split('_')
self.event_prefix = event_type_data[0]
self.event_source = _get_event_source(self.event_prefix)
self.event_status = int(event_type_data[1][0])
self.event_code = int(event_type_data[1][1:], 16)
self.event_prefix = event_type_data[0] # Ex: CID
self.event_source = get_event_source(self.event_prefix) # Ex: LRR_EVENT_TYPE.CID
self.event_status = int(event_type_data[1][0]) # Ex: 1 or 3
self.event_code = int(event_type_data[1][1:], 16) # Ex: 0x100 = Medical


# replace last 2 digits of event_code with report_code, if applicable. # replace last 2 digits of event_code with report_code, if applicable.
if not self.skip_report_override and self.report_code not in ['00', 'ff']: if not self.skip_report_override and self.report_code not in ['00', 'ff']:
@@ -94,7 +98,7 @@ class LRRMessage(BaseMessage):


def dict(self, **kwargs): def dict(self, **kwargs):
""" """
Dictionary representation.
Dictionary representation
""" """
return dict( return dict(
time = self.timestamp, time = self.timestamp,
@@ -109,18 +113,3 @@ class LRRMessage(BaseMessage):
event_description = self.event_description, event_description = self.event_description,
**kwargs **kwargs
) )


def _get_event_source(prefix):
source = LRR_EVENT_TYPE.UNKNOWN

if prefix == 'CID':
source = LRR_EVENT_TYPE.CID
elif prefix == 'DSC':
source = LRR_EVENT_TYPE.DSC
elif prefix == 'AD2':
source = LRR_EVENT_TYPE.ALARMDECODER
elif prefix == 'ADEMCO':
source = LRR_EVENT_TYPE.ADEMCO

return source

+ 73
- 31
alarmdecoder/messages/lrr/system.py View File

@@ -1,3 +1,8 @@
"""
Primary system for handling LRR events.

.. moduleauthor:: Scott Petersen <scott@nutech.com>
"""


from .events import LRR_EVENT_TYPE, LRR_EVENT_STATUS, LRR_CID_EVENT from .events import LRR_EVENT_TYPE, LRR_EVENT_STATUS, LRR_CID_EVENT
from .events import LRR_FIRE_EVENTS, LRR_POWER_EVENTS, LRR_BYPASS_EVENTS, LRR_BATTERY_EVENTS, \ from .events import LRR_FIRE_EVENTS, LRR_POWER_EVENTS, LRR_BYPASS_EVENTS, LRR_BATTERY_EVENTS, \
@@ -5,44 +10,60 @@ from .events import LRR_FIRE_EVENTS, LRR_POWER_EVENTS, LRR_BYPASS_EVENTS, LRR_BA




class LRRSystem(object): class LRRSystem(object):
"""
Handles LRR events and triggers higher-level events in the AlarmDecoder object.
"""

def __init__(self, alarmdecoder_object): def __init__(self, alarmdecoder_object):
"""
Constructor

:param alarmdecoder_object: Main AlarmDecoder object
:type alarmdecoder_object: :py:class:`~alarmdecoder.AlarmDecoder`
"""
self._alarmdecoder = alarmdecoder_object self._alarmdecoder = alarmdecoder_object


def update(self, message): def update(self, message):
handled = False

"""
Updates the states in the primary AlarmDecoder object based on
the LRR message provided.

:param message: LRR message object
:type message: :py:class:`~alarmdecoder.messages.LRRMessage`
"""
# Firmware version < 2.2a.8.6
if message.version == 1: if message.version == 1:
if msg.event_type == 'ALARM_PANIC':
if message.event_type == 'ALARM_PANIC':
self._alarmdecoder._update_panic_status(True) self._alarmdecoder._update_panic_status(True)
handled = True
elif msg.event_type == 'CANCEL':
elif message.event_type == 'CANCEL':
self._alarmdecoder._update_panic_status(False) self._alarmdecoder._update_panic_status(False)
handled = True


# Firmware version >= 2.2a.8.6
elif message.version == 2: elif message.version == 2:
source = message.event_source source = message.event_source
if source == LRR_EVENT_TYPE.CID: if source == LRR_EVENT_TYPE.CID:
handled = self._handle_cid_message(message)
self._handle_cid_message(message)
elif source == LRR_EVENT_TYPE.DSC: elif source == LRR_EVENT_TYPE.DSC:
handled = self._handle_dsc_message(message)
self._handle_dsc_message(message)
elif source == LRR_EVENT_TYPE.ADEMCO: elif source == LRR_EVENT_TYPE.ADEMCO:
handled = self._handle_ademco_message(message)
self._handle_ademco_message(message)
elif source == LRR_EVENT_TYPE.ALARMDECODER: elif source == LRR_EVENT_TYPE.ALARMDECODER:
handled = self._handle_alarmdecoder_message(message)
self._handle_alarmdecoder_message(message)
elif source == LRR_EVENT_TYPE.UNKNOWN: elif source == LRR_EVENT_TYPE.UNKNOWN:
handled = self._handle_unknown_message(message)
self._handle_unknown_message(message)
else: else:
pass pass


return handled

def _handle_cid_message(self, message): def _handle_cid_message(self, message):
handled = False
"""
Handles ContactID LRR events.


:param message: LRR message object
:type message: :py:class:`~alarmdecoder.messages.LRRMessage`
"""
status = self._get_event_status(message) status = self._get_event_status(message)
if status is None: if status is None:
print("Unknown LRR event status: {0}".format(message))
return return


if message.event_code in LRR_FIRE_EVENTS: if message.event_code in LRR_FIRE_EVENTS:
@@ -50,7 +71,6 @@ class LRRSystem(object):
status = False status = False


self._alarmdecoder._update_fire_status(status=status) self._alarmdecoder._update_fire_status(status=status)
handled = True
if message.event_code in LRR_ALARM_EVENTS: if message.event_code in LRR_ALARM_EVENTS:
kwargs = {} kwargs = {}
@@ -60,27 +80,21 @@ class LRRSystem(object):


kwargs[field_name] = int(message.event_data) kwargs[field_name] = int(message.event_data)
self._alarmdecoder._update_alarm_status(status=status, **kwargs) self._alarmdecoder._update_alarm_status(status=status, **kwargs)
handled = True


if message.event_code in LRR_POWER_EVENTS: if message.event_code in LRR_POWER_EVENTS:
self._alarmdecoder._update_power_status(status=status) self._alarmdecoder._update_power_status(status=status)
handled = True


if message.event_code in LRR_BYPASS_EVENTS: if message.event_code in LRR_BYPASS_EVENTS:
zone = int(message.event_data)
self._alarmdecoder._update_zone_bypass_status(status=status, zone=zone)
handled = True
self._alarmdecoder._update_zone_bypass_status(status=status, zone=int(message.event_data))


if message.event_code in LRR_BATTERY_EVENTS: if message.event_code in LRR_BATTERY_EVENTS:
self._alarmdecoder._update_battery_status(status=status) self._alarmdecoder._update_battery_status(status=status)
handled = True


if message.event_code in LRR_PANIC_EVENTS: if message.event_code in LRR_PANIC_EVENTS:
if message.event_code == LRR_CID_EVENT.OPENCLOSE_CANCEL_BY_USER: if message.event_code == LRR_CID_EVENT.OPENCLOSE_CANCEL_BY_USER:
status = False status = False


self._alarmdecoder._update_panic_status(status=status) self._alarmdecoder._update_panic_status(status=status)
handled = True


if message.event_code in LRR_ARM_EVENTS: if message.event_code in LRR_ARM_EVENTS:
# NOTE: status on OPENCLOSE messages is backwards. # NOTE: status on OPENCLOSE messages is backwards.
@@ -93,25 +107,53 @@ class LRRSystem(object):
status = not status status = not status


self._alarmdecoder._update_armed_status(status=status, status_stay=status_stay) self._alarmdecoder._update_armed_status(status=status, status_stay=status_stay)
handled = True


return handled


def _handle_dsc_message(self, message): def _handle_dsc_message(self, message):
return False
"""
Handles DSC LRR events.

:param message: LRR message object
:type message: :py:class:`~alarmdecoder.messages.LRRMessage`
"""
pass


def _handle_ademco_message(self, message): def _handle_ademco_message(self, message):
return False
"""
Handles ADEMCO LRR events.

:param message: LRR message object
:type message: :py:class:`~alarmdecoder.messages.LRRMessage`
"""
pass


def _handle_alarmdecoder_message(self, message): def _handle_alarmdecoder_message(self, message):
return False
"""
Handles AlarmDecoder LRR events.

:param message: LRR message object
:type message: :py:class:`~alarmdecoder.messages.LRRMessage`
"""
pass


def _handle_unknown_message(self, message): def _handle_unknown_message(self, message):
"""
Handles UNKNOWN LRR events.

:param message: LRR message object
:type message: :py:class:`~alarmdecoder.messages.LRRMessage`
"""
# TODO: Log this somewhere useful. # TODO: Log this somewhere useful.
return False
pass


def _get_event_status(self, message): def _get_event_status(self, message):
"""
Retrieves the boolean status of an LRR message.

:param message: LRR message object
:type message: :py:class:`~alarmdecoder.messages.LRRMessage`

:returns: Boolean indicating whether the event was triggered or restored.
"""
status = None status = None


if message.event_status == LRR_EVENT_STATUS.TRIGGER: if message.event_status == LRR_EVENT_STATUS.TRIGGER:


+ 3
- 0
alarmdecoder/states.py View File

@@ -1,4 +1,7 @@
class FireState: class FireState:
"""
Fire alarm status
"""
NONE = 0 NONE = 0
ALARM = 1 ALARM = 1
ACKNOWLEDGED = 2 ACKNOWLEDGED = 2

+ 25
- 0
alarmdecoder/util.py View File

@@ -58,6 +58,15 @@ class UploadChecksumError(UploadError):




def bytes_available(device): def bytes_available(device):
"""
Determines the number of bytes available for reading from an
AlarmDecoder device

:param device: the AlarmDecoder device
:type device: :py:class:`~alarmdecoder.devices.Device`

:returns: int
"""
bytes_avail = 0 bytes_avail = 0


if isinstance(device, alarmdecoder.devices.SerialDevice): if isinstance(device, alarmdecoder.devices.SerialDevice):
@@ -71,6 +80,14 @@ def bytes_available(device):
return bytes_avail return bytes_avail


def read_firmware_file(file_path): def read_firmware_file(file_path):
"""
Reads a firmware file into a dequeue for processing.

:param file_path: Path to the firmware file
:type file_path: string

:returns: deque
"""
data_queue = deque() data_queue = deque()


with open(file_path) as firmware_handle: with open(file_path) as firmware_handle:
@@ -99,6 +116,14 @@ class Firmware(object):


@staticmethod @staticmethod
def read(device): def read(device):
"""
Reads data from the specified device.

:param device: the AlarmDecoder device
:type device: :py:class:`~alarmdecoder.devices.Device`

:returns: string
"""
response = None response = None
bytes_avail = bytes_available(device) bytes_avail = bytes_available(device)




Loading…
Cancel
Save