| @@ -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() | ||||
| @@ -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): | ||||
| @@ -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'] | ||||
| @@ -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 | ||||
| @@ -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 | |||||
| @@ -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: | ||||
| @@ -1,4 +1,7 @@ | |||||
| class FireState: | class FireState: | ||||
| """ | |||||
| Fire alarm status | |||||
| """ | |||||
| NONE = 0 | NONE = 0 | ||||
| ALARM = 1 | ALARM = 1 | ||||
| ACKNOWLEDGED = 2 | ACKNOWLEDGED = 2 | ||||
| @@ -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) | ||||