diff --git a/alarmdecoder/decoder.py b/alarmdecoder/decoder.py index 5d12ba8..2a2c47f 100644 --- a/alarmdecoder/decoder.py +++ b/alarmdecoder/decoder.py @@ -94,7 +94,7 @@ class AlarmDecoder(object): :type device: Device """ self._device = device - self._zonetracker = Zonetracker() + self._zonetracker = Zonetracker(self) self._battery_timeout = AlarmDecoder.BATTERY_TIMEOUT self._fire_timeout = AlarmDecoder.FIRE_TIMEOUT @@ -106,6 +106,7 @@ class AlarmDecoder(object): self._battery_status = (False, 0) self._panic_status = None self._relay_status = {} + self._internal_address_mask = 0xFFFFFFFF self.address = 18 self.configbits = 0xFF00 @@ -177,6 +178,25 @@ class AlarmDecoder(object): """ self._fire_timeout = value + @property + def internal_address_mask(self): + """ + Retrieves the address mask used for updating internal status. + + :returns: address mask + """ + return self._internal_address_mask + + @internal_address_mask.setter + def internal_address_mask(self, value): + """ + Sets the address mask used internally for updating status. + + :param value: address mask + :type value: int + """ + self._internal_address_mask = value + def open(self, baudrate=None, no_reader_thread=False): """ Opens the device. @@ -344,7 +364,7 @@ class AlarmDecoder(object): """ msg = Message(data) - if self.address_mask & msg.mask > 0: + if self._internal_address_mask & msg.mask > 0: self._update_internal_states(msg) self.on_message(message=msg) diff --git a/alarmdecoder/messages.py b/alarmdecoder/messages.py index 6ed5d1f..c92e2e6 100644 --- a/alarmdecoder/messages.py +++ b/alarmdecoder/messages.py @@ -167,10 +167,9 @@ class Message(BaseMessage): 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 == 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.panel_data[21:23], 16) diff --git a/alarmdecoder/zonetracking.py b/alarmdecoder/zonetracking.py index 36334b0..ffb8233 100644 --- a/alarmdecoder/zonetracking.py +++ b/alarmdecoder/zonetracking.py @@ -11,6 +11,7 @@ import time from .event import event from .messages import ExpanderMessage +from .panels import ADEMCO, DSC class Zone(object): @@ -37,8 +38,10 @@ class Zone(object): """Zone status""" timestamp = None """Timestamp of last update""" + expander = False + """Does this zone exist on an expander?""" - def __init__(self, zone=0, name='', status=CLEAR): + def __init__(self, zone=0, name='', status=CLEAR, expander=False): """ Constructor @@ -53,6 +56,7 @@ class Zone(object): self.name = name self.status = status self.timestamp = time.time() + self.expander = expander def __str__(self): """ @@ -116,7 +120,7 @@ class Zonetracker(object): """ self._zones_faulted = value - def __init__(self): + def __init__(self, alarmdecoder_object): """ Constructor """ @@ -124,6 +128,8 @@ class Zonetracker(object): self._zones_faulted = [] self._last_zone_fault = 0 + self.alarmdecoder_object = alarmdecoder_object + def update(self, message): """ Update zone statuses based on the current message. @@ -132,9 +138,12 @@ class Zonetracker(object): :type message: :py:class:`~alarmdecoder.messages.Message` or :py:class:`~alarmdecoder.messages.ExpanderMessage` """ if isinstance(message, ExpanderMessage): + zone = -1 + if message.type == ExpanderMessage.ZONE: - zone = self.expander_to_zone(message.address, message.channel) + zone = self.expander_to_zone(message.address, message.channel, self.alarmdecoder_object.mode) + if zone != -1: status = Zone.CLEAR if message.value == 1: status = Zone.FAULT @@ -149,7 +158,7 @@ class Zonetracker(object): self._update_zone(zone, status=status) except IndexError: - self._add_zone(zone, status=status) + self._add_zone(zone, status=status, expander=True) else: # Panel is ready, restore all zones. @@ -209,7 +218,7 @@ class Zonetracker(object): self._clear_expired_zones() - def expander_to_zone(self, address, channel): + def expander_to_zone(self, address, channel, panel_type=ADEMCO): """ Convert an address and channel into a zone number. @@ -221,12 +230,19 @@ class Zonetracker(object): :returns: zone number associated with an address and channel """ - # TODO: This is going to need to be reworked to support the larger - # panels without fixed addressing on the expanders. + zone = -1 - idx = address - 7 # Expanders start at address 7. + if panel_type == ADEMCO: + # TODO: This is going to need to be reworked to support the larger + # panels without fixed addressing on the expanders. - return address + channel + (idx * 7) + 1 + idx = address - 7 # Expanders start at address 7. + zone = address + channel + (idx * 7) + 1 + + elif panel_type == DSC: + zone = (address * 8) + channel + + return zone def _clear_zones(self, zone): """ @@ -301,7 +317,7 @@ class Zonetracker(object): if self._zones[z].status != Zone.CLEAR and self._zone_expired(z): self._update_zone(z, Zone.CLEAR) - def _add_zone(self, zone, name='', status=Zone.CLEAR): + def _add_zone(self, zone, name='', status=Zone.CLEAR, expander=False): """ Adds a zone to the internal zone list. @@ -313,10 +329,9 @@ class Zonetracker(object): :type status: int """ if not zone in self._zones: - self._zones[zone] = Zone(zone=zone, name=name, status=status) + self._zones[zone] = Zone(zone=zone, name=name, status=None, expander=expander) - if status != Zone.CLEAR: - self.on_fault(zone=zone) + self._update_zone(zone, status=status) def _update_zone(self, zone, status=None): """ @@ -332,9 +347,11 @@ class Zonetracker(object): if not zone in self._zones: raise IndexError('Zone does not exist and cannot be updated: %d', zone) - if status is not None: - self._zones[zone].status = status + old_status = self._zones[zone].status + if status is None: + status = old_status + self._zones[zone].status = status self._zones[zone].timestamp = time.time() if status == Zone.CLEAR: @@ -342,6 +359,9 @@ class Zonetracker(object): self._zones_faulted.remove(zone) self.on_restore(zone=zone) + else: + if old_status != status and status is not None: + self.on_fault(zone=zone) def _zone_expired(self, zone): """ @@ -352,4 +372,4 @@ class Zonetracker(object): :returns: whether or not the zone is expired """ - return time.time() > self._zones[zone].timestamp + Zonetracker.EXPIRE + return (time.time() > self._zones[zone].timestamp + Zonetracker.EXPIRE) and self._zones[zone].expander is False