1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191
|
"""
Message representations received from the panel through the `AlarmDecoder`_ (AD2)
devices.
:py:class:`Message`: The standard and most common message received from a panel.
.. _AlarmDecoder: http://www.alarmdecoder.com
.. moduleauthor:: Scott Petersen <scott@nutech.com>
"""
import re
from . import BaseMessage
from ..util import InvalidMessageError
from ..panels import PANEL_TYPES, ADEMCO, DSC
class Message(BaseMessage):
"""
Represents a message from the alarm panel.
"""
ready = False
"""Indicates whether or not the panel is in a ready state."""
armed_away = False
"""Indicates whether or not the panel is armed away."""
armed_home = False
"""Indicates whether or not the panel is armed home."""
backlight_on = False
"""Indicates whether or not the keypad backlight is on."""
programming_mode = False
"""Indicates whether or not we're in programming mode."""
beeps = -1
"""Number of beeps associated with a message."""
zone_bypassed = False
"""Indicates whether or not a zone is bypassed."""
ac_power = False
"""Indicates whether or not the panel is on AC power."""
chime_on = False
"""Indicates whether or not the chime is enabled."""
alarm_event_occurred = False
"""Indicates whether or not an alarm event has occurred."""
alarm_sounding = False
"""Indicates whether or not an alarm is sounding."""
battery_low = False
"""Indicates whether or not there is a low battery."""
entry_delay_off = False
"""Indicates whether or not the entry delay is enabled."""
fire_alarm = False
"""Indicates whether or not a fire alarm is sounding."""
check_zone = False
"""Indicates whether or not there are zones that require attention."""
perimeter_only = False
"""Indicates whether or not the perimeter is armed."""
system_fault = -1
"""Indicates if any panel specific system fault exists."""
panel_type = ADEMCO
"""Indicates which panel type was the source of this message."""
numeric_code = None
"""The numeric code associated with the message."""
text = None
"""The human-readable text to be displayed on the panel LCD."""
cursor_location = -1
"""Current cursor location on the keypad."""
mask = 0xFFFFFFFF
"""Address mask this message is intended for."""
bitfield = None
"""The bitfield associated with this message."""
panel_data = None
"""The panel data field associated with this message."""
_regex = re.compile('^(!KPM:){0,1}(\[[a-fA-F0-9\-]+\]),([a-fA-F0-9]+),(\[[a-fA-F0-9]+\]),(".+")$')
def __init__(self, data=None):
"""
Constructor
:param data: message data to parse
:type data: string
"""
BaseMessage.__init__(self, data)
if data is not None:
self._parse_message(data)
def _parse_message(self, data):
"""
Parse the message from the device.
:param data: message data
:type data: string
:raises: :py:class:`~alarmdecoder.util.InvalidMessageError`
"""
match = self._regex.match(str(data))
if match is None:
raise InvalidMessageError('Received invalid message: {0}'.format(data))
header, self.bitfield, self.numeric_code, self.panel_data, alpha = match.group(1, 2, 3, 4, 5)
is_bit_set = lambda bit: not self.bitfield[bit] == "0"
self.ready = is_bit_set(1)
self.armed_away = is_bit_set(2)
self.armed_home = is_bit_set(3)
self.backlight_on = is_bit_set(4)
self.programming_mode = is_bit_set(5)
self.beeps = int(self.bitfield[6], 16)
self.zone_bypassed = is_bit_set(7)
self.ac_power = is_bit_set(8)
self.chime_on = is_bit_set(9)
self.alarm_event_occurred = is_bit_set(10)
self.alarm_sounding = is_bit_set(11)
self.battery_low = is_bit_set(12)
self.entry_delay_off = is_bit_set(13)
self.fire_alarm = is_bit_set(14)
self.check_zone = is_bit_set(15)
self.perimeter_only = is_bit_set(16)
self.system_fault = int(self.bitfield[17], 16)
if self.bitfield[18] in list(PANEL_TYPES):
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 in (ADEMCO, DSC):
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)
def parse_numeric_code(self, force_hex=False):
"""
Parses and returns the numeric code as an integer.
The numeric code can be either base 10 or base 16, depending on
where the message came from.
:param force_hex: force the numeric code to be processed as base 16.
:type force_hex: boolean
:raises: ValueError
"""
code = None
got_error = False
if not force_hex:
try:
code = int(self.numeric_code)
except ValueError:
got_error = True
if force_hex or got_error:
try:
code = int(self.numeric_code, 16)
except ValueError:
raise
return code
def dict(self, **kwargs):
"""
Dictionary representation.
"""
return dict(
time = self.timestamp,
bitfield = self.bitfield,
numeric_code = self.numeric_code,
panel_data = self.panel_data,
mask = self.mask,
ready = self.ready,
armed_away = self.armed_away,
armed_home = self.armed_home,
backlight_on = self.backlight_on,
programming_mode = self.programming_mode,
beeps = self.beeps,
zone_bypassed = self.zone_bypassed,
ac_power = self.ac_power,
chime_on = self.chime_on,
alarm_event_occurred = self.alarm_event_occurred,
alarm_sounding = self.alarm_sounding,
battery_low = self.battery_low,
entry_delay_off = self.entry_delay_off,
fire_alarm = self.fire_alarm,
check_zone = self.check_zone,
perimeter_only = self.perimeter_only,
text = self.text,
cursor_location = self.cursor_location,
**kwargs
)
|