| @@ -13,6 +13,7 @@ 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 .zonetracking import Zonetracker | from .zonetracking import Zonetracker | ||||
| from .panels import PANEL_TYPES, ADEMCO, DSC | |||||
| class AlarmDecoder(object): | class AlarmDecoder(object): | ||||
| @@ -80,6 +81,8 @@ class AlarmDecoder(object): | |||||
| """The status of the devices LRR emulation.""" | """The status of the devices LRR emulation.""" | ||||
| deduplicate = False | deduplicate = False | ||||
| """The status of message deduplication as configured on the device.""" | """The status of message deduplication as configured on the device.""" | ||||
| mode = ADEMCO | |||||
| """The panel mode that the AlarmDecoder is in. Currently supports ADEMCO and DSC.""" | |||||
| def __init__(self, device): | def __init__(self, device): | ||||
| """ | """ | ||||
| @@ -110,6 +113,7 @@ class AlarmDecoder(object): | |||||
| self.emulate_relay = [False for x in range(4)] | self.emulate_relay = [False for x in range(4)] | ||||
| self.emulate_lrr = False | self.emulate_lrr = False | ||||
| self.deduplicate = False | self.deduplicate = False | ||||
| self.mode = ADEMCO | |||||
| def __enter__(self): | def __enter__(self): | ||||
| """ | """ | ||||
| @@ -222,20 +226,16 @@ class AlarmDecoder(object): | |||||
| 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. | ||||
| config_entries.append(('ADDRESS', | |||||
| '{0}'.format(self.address))) | |||||
| config_entries.append(('CONFIGBITS', | |||||
| '{0:x}'.format(self.configbits))) | |||||
| config_entries.append(('MASK', | |||||
| '{0:x}'.format(self.address_mask))) | |||||
| config_entries.append(('ADDRESS', '{0}'.format(self.address))) | |||||
| config_entries.append(('CONFIGBITS', '{0:x}'.format(self.configbits))) | |||||
| config_entries.append(('MASK', '{0:x}'.format(self.address_mask))) | |||||
| config_entries.append(('EXP', | config_entries.append(('EXP', | ||||
| ''.join(['Y' if z else 'N' for z in self.emulate_zone]))) | ''.join(['Y' if z else 'N' for z in self.emulate_zone]))) | ||||
| config_entries.append(('REL', | config_entries.append(('REL', | ||||
| ''.join(['Y' if r else 'N' for r in self.emulate_relay]))) | ''.join(['Y' if r else 'N' for r in self.emulate_relay]))) | ||||
| config_entries.append(('LRR', | |||||
| 'Y' if self.emulate_lrr else 'N')) | |||||
| config_entries.append(('DEDUPLICATE', | |||||
| 'Y' if self.deduplicate 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(('MODE', PANEL_TYPES.keys()[PANEL_TYPES.values().index(self.mode)])) | |||||
| config_string = '&'.join(['='.join(t) for t in config_entries]) | config_string = '&'.join(['='.join(t) for t in config_entries]) | ||||
| @@ -430,6 +430,8 @@ class AlarmDecoder(object): | |||||
| self.emulate_lrr = (val == 'Y') | self.emulate_lrr = (val == 'Y') | ||||
| elif key == 'DEDUPLICATE': | elif key == 'DEDUPLICATE': | ||||
| self.deduplicate = (val == 'Y') | self.deduplicate = (val == 'Y') | ||||
| elif key == 'MODE': | |||||
| self.mode = PANEL_TYPES[val] | |||||
| self.on_config_received() | self.on_config_received() | ||||
| @@ -16,6 +16,7 @@ import re | |||||
| import datetime | import datetime | ||||
| from .util import InvalidMessageError | from .util import InvalidMessageError | ||||
| from .panels import PANEL_TYPES, ADEMCO, DSC | |||||
| class BaseMessage(object): | class BaseMessage(object): | ||||
| @@ -95,13 +96,17 @@ class Message(BaseMessage): | |||||
| """Indicates whether or not there are zones that require attention.""" | """Indicates whether or not there are zones that require attention.""" | ||||
| perimeter_only = False | perimeter_only = False | ||||
| """Indicates whether or not the perimeter is armed.""" | """Indicates whether or not the perimeter is armed.""" | ||||
| system_fault = False | |||||
| """Indicates whether a system fault has occurred.""" | |||||
| panel_type = ADEMCO | |||||
| """Indicates which panel type was the source of this message.""" | |||||
| numeric_code = None | numeric_code = None | ||||
| """The numeric code associated with the message.""" | """The numeric code associated with the message.""" | ||||
| text = None | text = None | ||||
| """The human-readable text to be displayed on the panel LCD.""" | """The human-readable text to be displayed on the panel LCD.""" | ||||
| cursor_location = -1 | cursor_location = -1 | ||||
| """Current cursor location on the keypad.""" | """Current cursor location on the keypad.""" | ||||
| mask = None | |||||
| mask = 0xFFFFFFFF | |||||
| """Address mask this message is intended for.""" | """Address mask this message is intended for.""" | ||||
| bitfield = None | bitfield = None | ||||
| """The bitfield associated with this message.""" | """The bitfield associated with this message.""" | ||||
| @@ -137,7 +142,6 @@ class Message(BaseMessage): | |||||
| raise InvalidMessageError('Received invalid message: {0}'.format(data)) | 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) | header, self.bitfield, self.numeric_code, self.panel_data, alpha = match.group(1, 2, 3, 4, 5) | ||||
| self.mask = int(self.panel_data[3:3+8], 16) | |||||
| is_bit_set = lambda bit: not self.bitfield[bit] == "0" | is_bit_set = lambda bit: not self.bitfield[bit] == "0" | ||||
| @@ -158,12 +162,18 @@ class Message(BaseMessage): | |||||
| self.fire_alarm = is_bit_set(14) | self.fire_alarm = is_bit_set(14) | ||||
| self.check_zone = is_bit_set(15) | self.check_zone = is_bit_set(15) | ||||
| self.perimeter_only = is_bit_set(16) | self.perimeter_only = is_bit_set(16) | ||||
| # bits 17-20 unused. | |||||
| self.system_fault = is_bit_set(17) | |||||
| if self.bitfield[18] in PANEL_TYPES.keys(): | |||||
| self.panel_type = PANEL_TYPES[self.bitfield[18]] | |||||
| # pos 20-21 - Unused. | |||||
| self.text = alpha.strip('"') | self.text = alpha.strip('"') | ||||
| if int(self.panel_data[19:21], 16) & 0x01 > 0: | |||||
| # Current cursor location on the alpha display. | |||||
| self.cursor_location = int(self.bitfield[21:23], 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.bitfield[21:23], 16) | |||||
| def dict(self, **kwargs): | def dict(self, **kwargs): | ||||
| """ | """ | ||||
| @@ -4,6 +4,14 @@ Representations of Panels and their templates. | |||||
| .. moduleauthor:: Scott Petersen <scott@nutech.com> | .. moduleauthor:: Scott Petersen <scott@nutech.com> | ||||
| """ | """ | ||||
| ADEMCO = 0 | |||||
| DSC = 1 | |||||
| PANEL_TYPES = { | |||||
| 'A': ADEMCO, | |||||
| 'D': DSC, | |||||
| } | |||||
| VISTA20 = 0 | VISTA20 = 0 | ||||
| TEMPLATES = { | TEMPLATES = { | ||||