def load_debounce(event): """Called at startup or when the Reload Debounce rule is triggered. It deletes and recreates the Debounce rule. Should be called at startup and when the metadata is changes on Items since there is no event to do this automatically. """ if not delete_rule(debounce, log): log.error("Failed to delete rule!") return debounce_items = load_rule_with_metadata( "debounce", get_config, "changed", "Debounce", debounce, log, description=("Delays updating a proxy Item until the configured " "Item remains in that state for the configured amount " "of time"), tags=["openhab-rules-tools", "debounce"]) if debounce_items: # Cancel any existing timers for Items that are no longer debounced. # Leave the timers for those that are still debounced. for i in [i for i in timers.timers if not i in debounce_items]: timers.cancel(i) # Synchronize the debounce_item with it's configured proxy. log.debug("debounce_items = {}".format(debounce_items)) for i in debounce_items: cfg = get_config(i, log) if cfg: post_update_if_different(cfg.value, items[i].toString())
def setArmingMode(self, newArmingMode, sendCommand=False): ''' Sets the zones current arming mode ''' oldArmingMode = self._armingMode if newArmingMode not in [ ARMINGMODE['DISARMED'], ARMINGMODE['ARMED_HOME'], ARMINGMODE['ARMED_AWAY'] ]: raise IdeAlarmError( "Trying to set an invalid arming mode: {}".format( newArmingMode)) # There might be open sensors when trying to arm. If so, the custom function onArmingWithOpenSensors # gets called. (That doesn't necessarily need to be an error condition). # However if the zone has been configured not to allow opened sensors during arming, # the zone status will be set to ERROR and onZoneStatusChange will be able to trap track it down. if newArmingMode in [ARMINGMODE['ARMED_AWAY'], ARMINGMODE['ARMED_HOME']] \ and self.getZoneStatus() != ZONESTATUS['ARMING'] and self.getZoneStatus() is not None \ and self.openSections > 0: if 'onArmingWithOpenSensors' in dir(custom): custom.onArmingWithOpenSensors(self, newArmingMode) if not self.canArmWithTrippedSensors: self.setZoneStatus( ZONESTATUS['ERROR'], errorMessage='Arming is not allowed with open sensors') self.log.warn( u"Zone \'{}'\' can not be set to new arming mode: {} due to that there are open sensors!" .format(self.name.decode('utf8'), kw(ARMINGMODE, newArmingMode))) import time time.sleep(1) self.setZoneStatus(ZONESTATUS['NORMAL']) return # Don't set arming mode to 'ARMED_AWAY' immediately, we need to wait for the exit timer # self.getZoneStatus() returns None when initializing if newArmingMode == ARMINGMODE['ARMED_AWAY'] \ and self.getZoneStatus() is not None and self.getZoneStatus() != ZONESTATUS['ARMING']: self.setZoneStatus(ZONESTATUS['ARMING']) post_update_if_different("Z{}_Exit_Timer".format(self.zoneNumber), scope.ON) return self._armingMode = newArmingMode # Sync the Item post_update_if_different(self.armingModeItem, newArmingMode, sendCommand) # Call custom function if available if 'onArmingModeChange' in dir(custom): custom.onArmingModeChange(self, newArmingMode, oldArmingMode) # Whenever the arming mode is set, reset the zones status to NORMAL self.setZoneStatus(ZONESTATUS['NORMAL'])
def onSensorChange(self, sensor): ''' Called whenever an enabled sensor has tripped ON or OPEN ''' if self.getArmingMode() not in [ARMINGMODE['ARMED_HOME'], ARMINGMODE['ARMED_AWAY']] \ or self.getZoneStatus() not in [ZONESTATUS['NORMAL']] \ or (self.getArmingMode() == ARMINGMODE['ARMED_HOME'] and sensor.sensorClass == 'B') \ or getItemValue("Z{}_Exit_Timer".format(self.zoneNumber), scope.OFF) == scope.ON: self.log.info(u"{} was tripped, but we are ignoring it".format( sensor.name.decode('utf8'))) return self.setZoneStatus(ZONESTATUS['TRIPPED']) self.log.info(u"{} was tripped, starting entry timer".format( sensor.name.decode('utf8'))) post_update_if_different("Z{}_Entry_Timer".format(self.zoneNumber), scope.ON)
def item_init(event): """Rule that triggers at System started and populates Items with an initial value. The initialization value is defined in metadata with three key/values. - value: value to send update the Item to - override: optional boolean value to override the state of the Item even if it isn't NULL, defaults to "False". - clear: optional boolean value to delete the metadata once the Item is updated. Defaults to "False". For example: - { init="ON"[override="true", clear="true"] }: Initialize the Switch Item to ON whether or not it already has a value and then delete the metadata. - { init="123,45,67" }: Initialize the Color Item to the value only if it is NULL or UNDEF. Limitations: - The clear option only works for Items not created through .items files. For Items defined in .items files, you must manually remove the metadata or else it will get reloaded next time the file is loaded. """ item_init.log.info("Initializing Items") for item_name in [i for i in items if get_metadata(i, "init")]: value = get_value(item_name, "init") # Always update if override is True if get_key_value(item_name, "init", "override") == "True": post_update_if_different(item_name, value) item_init.log.info( "Overriding current value {} of {} to {}".format( items[item_name], item_name, value)) # If not overridden, only update if the Item is currently NULL or UNDEF. elif isinstance(items[item_name], UnDefType): item_init.log.info("Initializing {} to {}".format( item_name, value)) postUpdate(item_name, value) # Delete the metadata now that the Item is initialized. if get_key_value(item_name, "init", "clear") == "true": item_init.log.info( "Removing init metadata from {}".format(item_name)) remove_metadata(item_name, "init")
def onEntryTimer(self): ''' Called whenever the entry timer times out. ''' # Double check that the zone status is tripped, we can probably remove this check later if self.getZoneStatus() not in [ZONESTATUS['TRIPPED']]: raise IdeAlarmError( 'Entry Timer timed out but zone status is not tripped') self.setZoneStatus(ZONESTATUS['ALERT']) # We need to make some noise here! if not self.alarmTestMode: for alertDevice in self.alertDevices: send_command_if_different(alertDevice, scope.ON) self.log.info('You should be able to hear the sirens now...') else: self.log.info('ALARM_TEST_MODE is activated. No sirens!') post_update_if_different("Z{}_Alert_Max_Timer".format(self.zoneNumber), scope.ON)
def end_debounce(state, proxy_name, is_command): """Called at the end of the debounce period, update or commands the proxy Item with the passed in state if it's different from the proxy's current state. Arguments: state: the state to update or command the proxy Item to proxy_name: the name of the proxy Item is_command: flag that when true will cause the function to issue a command instead of an update. log: logger used for debug logging """ if is_command: log.debug("Commanding {} to {} if it's not already that state".format( proxy_name, state)) send_command_if_different(proxy_name, state) else: log.debug("Updating {} to {} if it's not already that state".format( proxy_name, state)) post_update_if_different(proxy_name, state)
def countOpenSections(self): ''' A sensor has changed its state. We are here to calculate how many open sensors there are in the zone at this very moment. Saves the result in self.openSections and returns it. WE DO NOT INCLUDE MOTION DETECTORS IN THE COUNT UNLESS ARMED AWAY! E.G. Those sensors that belongs to group 'G_Motion' ''' self.openSections = 0 for sensor in self.sensors: #self.log.debug(u"Checking sensor: {} : {}".format(sensor.name.decode('utf8'), sensor.isEnabled() and sensor.isActive())) if sensor.isEnabled() and sensor.isActive() \ and ('G_Motion' not in scope.itemRegistry.getItem(sensor.name).groupNames or self.getArmingMode() in [ARMINGMODE['ARMED_AWAY']]): self.openSections += 1 self.log.debug(u"Open sensor: {}".format( sensor.name.decode('utf8'))) self.log.debug(u"Number of open sections in {} is: {}".format( self.name.decode('utf8'), self.openSections)) post_update_if_different("Z{}_Open_Sections".format(self.zoneNumber), self.openSections) return self.openSections
def getNagSensors(self, timerTimedOut=False): ''' Check if nagging is required. Performed when a sensor changes its state and when the nag timer ends. Nagging is only performed when a zone is disarmed. ''' nagSensors = [] for sensor in self.sensors: if sensor.isEnabled() and sensor.isActive( ) and sensor.nag and self.getArmingMode( ) == ARMINGMODE['DISARMED']: nagSensors.append(sensor) if len(nagSensors) == 0: post_update_if_different("Z{}_Nag_Timer".format(self.zoneNumber), scope.OFF) # Cancel the nag timer else: post_update_if_different("Z{}_Nag_Timer".format(self.zoneNumber), scope.ON) if timerTimedOut and 'onNagTimer' in dir(custom): self.log.debug('Calling custom onNagTimer function') custom.onNagTimer(self, nagSensors) return nagSensors
def setZoneStatus(self, newZoneStatus, sendCommand=False, errorMessage=None): ''' Sets the zones current status ''' if newZoneStatus not in [ ZONESTATUS['NORMAL'], ZONESTATUS['ALERT'], ZONESTATUS['ERROR'], ZONESTATUS['TRIPPED'], ZONESTATUS['ARMING'] ]: raise IdeAlarmError('Trying to set an invalid zone status') oldZoneStatus = self._zoneStatus self._zoneStatus = newZoneStatus if newZoneStatus in [ZONESTATUS['NORMAL']]: # Cancel all timers so they won't fire post_update_if_different("Z{}_Entry_Timer".format(self.zoneNumber), scope.OFF) post_update_if_different("Z{}_Exit_Timer".format(self.zoneNumber), scope.OFF) post_update_if_different( "Z{}_Alert_Max_Timer".format(self.zoneNumber), scope.OFF) # Cancel sirens for alertDevice in self.alertDevices: send_command_if_different(alertDevice, scope.OFF) # Sync the Zone Status Item post_update_if_different(self.statusItem, newZoneStatus, sendCommand) # Call custom function if available if 'onZoneStatusChange' in dir(custom): custom.onZoneStatusChange(self, newZoneStatus, oldZoneStatus, errorMessage=errorMessage)