Browse Source

Initial work on LRR system.

pyserial_fix
Scott Petersen 7 years ago
parent
commit
14ac51c724
4 changed files with 187 additions and 35 deletions
  1. +93
    -32
      alarmdecoder/decoder.py
  2. +7
    -2
      alarmdecoder/messages/lrr/events.py
  3. +1
    -1
      alarmdecoder/messages/lrr/message.py
  4. +86
    -0
      alarmdecoder/messages/lrr/system.py

+ 93
- 32
alarmdecoder/decoder.py View File

@@ -18,6 +18,7 @@ except ImportError:
from .event import event from .event import event
from .util import InvalidMessageError from .util import InvalidMessageError
from .messages import Message, ExpanderMessage, RFMessage, LRRMessage from .messages import Message, ExpanderMessage, RFMessage, LRRMessage
from .messages.lrr import LRRSystem
from .zonetracking import Zonetracker from .zonetracking import Zonetracker
from .panels import PANEL_TYPES, ADEMCO, DSC from .panels import PANEL_TYPES, ADEMCO, DSC


@@ -99,7 +100,7 @@ class AlarmDecoder(object):
version_flags = "" version_flags = ""
"""Device flags enabled""" """Device flags enabled"""


def __init__(self, device):
def __init__(self, device, ignore_message_states=False):
""" """
Constructor Constructor


@@ -109,7 +110,9 @@ class AlarmDecoder(object):
""" """
self._device = device self._device = device
self._zonetracker = Zonetracker(self) self._zonetracker = Zonetracker(self)
self._lrr_system = LRRSystem(self)


self._ignore_message_states = ignore_message_states
self._battery_timeout = AlarmDecoder.BATTERY_TIMEOUT self._battery_timeout = AlarmDecoder.BATTERY_TIMEOUT
self._fire_timeout = AlarmDecoder.FIRE_TIMEOUT self._fire_timeout = AlarmDecoder.FIRE_TIMEOUT
self._power_status = None self._power_status = None
@@ -402,10 +405,12 @@ class AlarmDecoder(object):


:returns: :py:class:`~alarmdecoder.messages.Message` :returns: :py:class:`~alarmdecoder.messages.Message`
""" """

msg = Message(data) msg = Message(data)


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


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


@@ -453,14 +458,16 @@ class AlarmDecoder(object):
""" """
msg = LRRMessage(data) msg = LRRMessage(data)


if msg.event_type == 'ALARM_PANIC':
self._panic_status = True
self.on_panic(status=True)
# if msg.event_type == 'ALARM_PANIC':
# self._panic_status = True
# self.on_panic(status=True)
#
# elif msg.event_type == 'CANCEL':
# if self._panic_status is True:
# self._panic_status = False
# self.on_panic(status=False)


elif msg.event_type == 'CANCEL':
if self._panic_status is True:
self._panic_status = False
self.on_panic(status=False)
self._lrr_system.update(msg)


self.on_lrr_message(message=msg) self.on_lrr_message(message=msg)


@@ -534,7 +541,7 @@ class AlarmDecoder(object):
:param message: :py:class:`~alarmdecoder.messages.Message` to update internal states with :param message: :py:class:`~alarmdecoder.messages.Message` to update internal states with
:type message: :py:class:`~alarmdecoder.messages.Message`, :py:class:`~alarmdecoder.messages.ExpanderMessage`, :py:class:`~alarmdecoder.messages.LRRMessage`, or :py:class:`~alarmdecoder.messages.RFMessage` :type message: :py:class:`~alarmdecoder.messages.Message`, :py:class:`~alarmdecoder.messages.ExpanderMessage`, :py:class:`~alarmdecoder.messages.LRRMessage`, or :py:class:`~alarmdecoder.messages.RFMessage`
""" """
if isinstance(message, Message):
if isinstance(message, Message) and not self._ignore_message_states:
self._update_power_status(message) self._update_power_status(message)
self._update_alarm_status(message) self._update_alarm_status(message)
self._update_zone_bypass_status(message) self._update_zone_bypass_status(message)
@@ -547,7 +554,7 @@ class AlarmDecoder(object):


self._update_zone_tracker(message) self._update_zone_tracker(message)


def _update_power_status(self, message):
def _update_power_status(self, message=None, status=None):
""" """
Uses the provided message to update the AC power state. Uses the provided message to update the AC power state.


@@ -556,8 +563,15 @@ class AlarmDecoder(object):


:returns: bool indicating the new status :returns: bool indicating the new status
""" """
if message.ac_power != self._power_status:
self._power_status, old_status = message.ac_power, self._power_status
power_status = status
if isinstance(message, Message):
power_status = message.ac_power

if power_status is None:
return

if power_status != self._power_status:
self._power_status, old_status = power_status, self._power_status


if old_status is not None: if old_status is not None:
self.on_power_changed(status=self._power_status) self.on_power_changed(status=self._power_status)
@@ -585,7 +599,7 @@ class AlarmDecoder(object):


return self._alarm_status return self._alarm_status


def _update_zone_bypass_status(self, message):
def _update_zone_bypass_status(self, message=None, status=None):
""" """
Uses the provided message to update the zone bypass state. Uses the provided message to update the zone bypass state.


@@ -594,16 +608,22 @@ class AlarmDecoder(object):


:returns: bool indicating the new status :returns: bool indicating the new status
""" """
bypass_status = status
if isinstance(message, Message):
bypass_status = message.zone_bypassed


if message.zone_bypassed != self._bypass_status:
self._bypass_status, old_status = message.zone_bypassed, self._bypass_status
if bypass_status is None:
return

if bypass_status != self._bypass_status:
self._bypass_status, old_status = bypass_status, self._bypass_status


if old_status is not None: if old_status is not None:
self.on_bypass(status=self._bypass_status) self.on_bypass(status=self._bypass_status)


return self._bypass_status return self._bypass_status


def _update_armed_status(self, message):
def _update_armed_status(self, message=None, status=None, status_stay=None):
""" """
Uses the provided message to update the armed state. Uses the provided message to update the armed state.


@@ -612,19 +632,28 @@ class AlarmDecoder(object):


:returns: bool indicating the new status :returns: bool indicating the new status
""" """
arm_status = status
stay_status = status_stay


self._armed_status, old_status = message.armed_away, self._armed_status
self._armed_stay, old_stay = message.armed_home, self._armed_stay
if message.armed_away != old_status or message.armed_home != old_stay:
if old_status is not None:
if isinstance(message, Message):
arm_status = message.armed_away
stay_status = message.armed_home

if arm_status is None or stay_status is None:
return

self._armed_status, old_status = arm_status, self._armed_status
self._armed_stay, old_stay = stay_status, self._armed_stay
if arm_status != old_status or stay_status != old_stay:
if old_status is not None or message is None:
if self._armed_status or self._armed_stay: if self._armed_status or self._armed_stay:
self.on_arm(stay=message.armed_home)
self.on_arm(stay=stay_status)
else: else:
self.on_disarm() self.on_disarm()


return self._armed_status or self._armed_stay return self._armed_status or self._armed_stay


def _update_battery_status(self, message):
def _update_battery_status(self, message=None, status=None):
""" """
Uses the provided message to update the battery state. Uses the provided message to update the battery state.


@@ -633,18 +662,24 @@ class AlarmDecoder(object):


:returns: boolean indicating the new status :returns: boolean indicating the new status
""" """
battery_status = status
if isinstance(message, Message):
battery_status = message.battery_low

if battery_status is None:
return


last_status, last_update = self._battery_status last_status, last_update = self._battery_status
if message.battery_low == last_status:
if battery_status == last_status:
self._battery_status = (last_status, time.time()) self._battery_status = (last_status, time.time())
else: else:
if message.battery_low is True or time.time() > last_update + self._battery_timeout:
self._battery_status = (message.battery_low, time.time())
self.on_low_battery(status=message.battery_low)
if battery_status is True or time.time() > last_update + self._battery_timeout:
self._battery_status = (battery_status, time.time())
self.on_low_battery(status=battery_status)


return self._battery_status[0] return self._battery_status[0]


def _update_fire_status(self, message):
def _update_fire_status(self, message=None, status=None):
""" """
Uses the provided message to update the fire alarm state. Uses the provided message to update the fire alarm state.


@@ -653,17 +688,43 @@ class AlarmDecoder(object):


:returns: boolean indicating the new status :returns: boolean indicating the new status
""" """
fire_status = status
if isinstance(message, Message):
fire_status = message.fire_alarm

if fire_status is None:
return


last_status, last_update = self._fire_status last_status, last_update = self._fire_status
if message.fire_alarm == last_status:
if fire_status == last_status:
self._fire_status = (last_status, time.time()) self._fire_status = (last_status, time.time())
else: else:
if message.fire_alarm is True or time.time() > last_update + self._fire_timeout:
self._fire_status = (message.fire_alarm, time.time())
self.on_fire(status=message.fire_alarm)
if fire_status is True or time.time() > last_update + self._fire_timeout:
self._fire_status = (fire_status, time.time())
self.on_fire(status=fire_status)


return self._fire_status[0] return self._fire_status[0]


def _update_panic_status(self, status=None):
"""
Updates the panic status of the alarm panel.

:param status: status to use to update
:type status: boolean

:returns: boolean indicating the new status
"""
if status is None:
return

if status != self._panic_status:
self._panic_status, old_status = status, self._panic_status

if old_status is not None:
self.on_panic(status=self._panic_status)

return self._panic_status

def _update_expander_status(self, message): def _update_expander_status(self, message):
""" """
Uses the provided message to update the expander states. Uses the provided message to update the expander states.


+ 7
- 2
alarmdecoder/messages/lrr/events.py View File

@@ -702,7 +702,8 @@ LRR_PANIC_EVENTS = [
LRR_CID_EVENT.PANIC_SILENT, LRR_CID_EVENT.PANIC_SILENT,
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_ARM_EVENTS = [ LRR_ARM_EVENTS = [
@@ -710,10 +711,14 @@ LRR_ARM_EVENTS = [
LRR_CID_EVENT.OPENCLOSE_BY_USER, LRR_CID_EVENT.OPENCLOSE_BY_USER,
LRR_CID_EVENT.OPENCLOSE_GROUP, LRR_CID_EVENT.OPENCLOSE_GROUP,
LRR_CID_EVENT.OPENCLOSE_AUTOMATIC, LRR_CID_EVENT.OPENCLOSE_AUTOMATIC,
LRR_CID_EVENT.OPENCLOSE_CANCEL_BY_USER,
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,
LRR_CID_EVENT.OPENCLOSE_KEYSWITCH_ARMED_STAY LRR_CID_EVENT.OPENCLOSE_KEYSWITCH_ARMED_STAY
] ]

LRR_STAY_EVENTS = [
LRR_CID_EVENT.OPENCLOSE_ARMED_STAY,
LRR_CID_EVENT.OPENCLOSE_KEYSWITCH_ARMED_STAY
]

+ 1
- 1
alarmdecoder/messages/lrr/message.py View File

@@ -93,6 +93,6 @@ class LRRMessage(BaseMessage):
event_prefix = self.event_prefix, event_prefix = self.event_prefix,
event_source = self.event_source, event_source = self.event_source,
event_status = self.event_status, event_status = self.event_status,
event_code = self.event_code,
event_code = hex(self.event_code),
**kwargs **kwargs
) )

+ 86
- 0
alarmdecoder/messages/lrr/system.py View File

@@ -0,0 +1,86 @@

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, \
LRR_PANIC_EVENTS, LRR_ARM_EVENTS, LRR_STAY_EVENTS


class LRRSystem(object):
def __init__(self, alarmdecoder_object):
self._alarmdecoder = alarmdecoder_object

def update(self, message):
handled = False

print("LRR Message: {0}".format(message.dict()))

source = message.event_source
if source == LRR_EVENT_TYPE.CID:
handled = self._handle_cid_message(message)
elif source == LRR_EVENT_TYPE.DSC:
handled = self._handle_dsc_message(message)
elif source == LRR_EVENT_TYPE.ADEMCO:
handled = self._handle_ademco_message(message)
elif source == LRR_EVENT_TYPE.ALARMDECODER:
handled = self._handle_alarmdecoder_message(message)
elif source == LRR_EVENT_TYPE.UNKNOWN:
handled = self._handle_unknown_message(message)
else:
pass

return handled

def _handle_cid_message(self, message):
handled = True

status = self._get_event_status(message)
if status is None:
print("Unknown LRR event status: {0}".format(message))
return

if message.event_code in LRR_FIRE_EVENTS:
self._alarmdecoder._update_fire_status(status=status)
elif message.event_code in LRR_POWER_EVENTS:
self._alarmdecoder._update_power_status(status=status)
elif message.event_code in LRR_BYPASS_EVENTS:
self._alarmdecoder._update_zone_bypass_status(status=status)
elif message.event_code in LRR_BATTERY_EVENTS:
self._alarmdecoder._update_battery_status(status=status)
elif message.event_code in LRR_PANIC_EVENTS:
if message.event_code == LRR_CID_EVENT.OPENCLOSE_CANCEL_BY_USER:
status = False

self._alarmdecoder._update_panic_status(status=status)
elif message.event_code in LRR_ARM_EVENTS:
# NOTE: status on OPENCLOSE messages is backwards.
status_stay = (message.event_status == LRR_EVENT_STATUS.RESTORE \
and message.event_code in LRR_STAY_EVENTS)
self._alarmdecoder._update_armed_status(status=not status, status_stay=status_stay)
else:
handled = False

return handled

def _handle_dsc_message(self, message):
return False

def _handle_ademco_message(self, message):
return False

def _handle_alarmdecoder_message(self, message):
return False

def _handle_unknown_message(self, message):
# TODO: Log this somewhere useful.
print("UNKNOWN LRR EVENT: {0}".format(message))

return False

def _get_event_status(self, message):
status = None

if message.event_status == LRR_EVENT_STATUS.TRIGGER:
status = True
elif message.event_status == LRR_EVENT_STATUS.RESTORE:
status = False

return status

Loading…
Cancel
Save