def isEnabled(self, value): with self._lock: logger.reportDebug('{1}.isEnabled={0}, activationTimer is {2}'.format(value, self, self._activationTimer)) if not value: self.stopActivationTimer() if self._enabledObject.value == value: return # Make sure this sensor is still required by the current mode. # Safer in case of data race between the thread that runs # updateModeFromLinknx and the one that runs the activation timer. if self.isRequiredByCurrentMode() or not value: self._enabledObject.value = value if value: try: if self.persistenceObject != None: self.persistenceObject.value = False self.onEnabled() except Exception as e: self._enabledObject.value = False logger.reportException() else: # Sensor may currently be in alert. self.alert.removeSensorFromAlert(self) self.onDisabled() logger.reportInfo('Sensor {0} is now {1}'.format(self.name, 'enabled' if value else 'disabled'))
def sendEmailMock(self, actionXml): logger.reportDebug('sendEmailMock: {0}'.format(actionXml.toxml())) self.assertIsNone( self.emailInfo, 'An unconsumed email is about to be deleted. It is likely to be an unexpected email. Details are {0}' .format(self.emailInfo)) self.emailInfo = {'action': actionXml, 'date': time.ctime()} logger.reportInfo('sendEmail mock received {0}'.format(self.emailInfo))
def extend(self, starts = False): """ Prolong duration by the timeout amount of time. """ if self.isTerminating: raise Exception('Timer {0} is terminating, it cannot be extended.'.format(self)) self.endTime = time.time() + self.timeout if starts: logger.reportDebug('{0} started for {1} seconds.'.format(self, self.timeout)) else: logger.reportDebug('{0} is extended by {1} seconds.'.format(self, self.timeout))
def mockForTestMultipleConnections(self, context): logger.reportDebug('Mock called with objectId={0}'.format(context.objectId)) self.callbackCalledFor.append(context.object) while self.blockCallback: time.sleep(0.5) logger.reportDebug('Mock ended for objectId={0}'.format(context.objectId)) # Make sure next callback is blocked! self.blockCallback = True
def stop(self): with self._lock: if self.isStopped: return logger.reportDebug('Stopping {0}: sensorsInPrealert={1} sensorsInAlert={2}'.format(self, self._sensorsInPrealert, self._sensorsInAlert)) hasChanged = len(self._sensorsInPrealert) + len(self._sensorsInAlert) != 0 self._sensorsInPrealert.clear() self._sensorsInAlert.clear() self.invalidateStatus() self.updateStatus()
def testMultipleConnections(self): """ Checks that communicator can handle several connections at once (with a queue of connections when a connection is being treated). """ # Set state to a known one. booleanObject = self.linknx.getObject('Boolean') floatObject = self.linknx.getObject('Float16') booleanObject.value = False floatObject.value = 0.0 self.waitDuring(2, 'Initializing...') # Redirect some events to be able to block them. self.callbackCalledFor = [] try: self.blockCallback = True with self.patchUserModule({ 'onBooleanChanged': self.mockForTestMultipleConnections, 'onFloatChanged': self.mockForTestMultipleConnections }): # Change an object. booleanObject.value = True # Wait for callback. while not self.callbackCalledFor: logger.reportDebug('Waiting for first callback...') time.sleep(1) self.assertEqual(self.callbackCalledFor, [booleanObject]) self.assertTrue(booleanObject.value) # Chain with second callback while first is blocked. floatObject.value = 1.0 # Wait a few seconds, callback should not be called now (since it is not # reentrant). self.waitDuring( 5, 'Checking that second callback is not called until first callback is released.', [ lambda: self.assertEqual(self.callbackCalledFor, [booleanObject]) ]) # Release callback. self.blockCallback = False # Wait for second callback. self.waitDuring(3, 'Waiting for second callback...') self.assertEqual(self.callbackCalledFor, [booleanObject, floatObject]) finally: # Release all threads in case test went wrong. self.blockCallback = False
def mockForTestMultipleConnections(self, context): logger.reportDebug('Mock called with objectId={0}'.format( context.objectId)) self.callbackCalledFor.append(context.object) while self.blockCallback: time.sleep(0.5) logger.reportDebug('Mock ended for objectId={0}'.format( context.objectId)) # Make sure next callback is blocked! self.blockCallback = True
def stop(self): with self._lock: if self.isStopped: return logger.reportDebug( 'Stopping {0}: sensorsInPrealert={1} sensorsInAlert={2}'. format(self, self._sensorsInPrealert, self._sensorsInAlert)) hasChanged = len(self._sensorsInPrealert) + len( self._sensorsInAlert) != 0 self._sensorsInPrealert.clear() self._sensorsInAlert.clear() self.invalidateStatus() self.updateStatus()
def run(self): try: logger.reportDebug('Starting {0}'.format(self)) while not self.isTerminating: if self.onIterate is not None: self.onIterate(self) # Check for termination. if self.isTerminating: return # Main loop. if not self.isPaused: if self.endTime is None: self.extend(starts=True) if time.time() > self.endTime: # Execute delayed job. logger.reportDebug('Timeout reached for {0}.'.format(self)) if callable(self.onTimeoutReached): self.onTimeoutReached(self) break time.sleep(0.2) finally: if self.isTerminating: # Timer has been stopped from outside. logger.reportDebug('{0} is canceled.'.format(self)) else: # Maybe useless but set it for consistency. self.isTerminating = True if callable(self.onTerminated): self.onTerminated(self) logger.reportDebug('{0} is now terminated.'.format(self)) self.isTerminated = True self.isTerminating = False
def _loadUserFile(self): # Append the directory that contains the user script to python path. if self._userFile: dirName, fileName = os.path.split(self._userFile) dirName = os.path.abspath(dirName) moduleName, fileExt = os.path.splitext(fileName) sys.path.append(dirName) logger.reportDebug('_loadUserFile: moduleName={0} fileExt={1} dirName={2}'.format(moduleName, fileExt, dirName)) self._userModule = importlib.import_module(moduleName) logger.reportDebug('Imported {0}'.format(self._userModule.__file__)) return True else: logger.reportError('No user file specified.') return False
def run(self): try: self.socket.connect((self.linknx.host, self.linknx.port)) logger.reportDebug('Message sent to linknx: ' + self.messageWithEncodingHeader) answer = self.socket.sendString(self.messageWithEncodingHeader, encoding='utf8') while True: logger.reportDebug('Linknx answered ' + answer) answerDom = parseString(answer[0:answer.rfind(chr(4))]) execNodes = answerDom.getElementsByTagName( self.commandName) status = execNodes[0].getAttribute("status") if status == "ongoing": # Wait for the final status. answer = self.socket.waitForStringAnswer() logger.reportDebug('New answer is {0}'.format(answer)) else: if status != "success": self.error = self._getErrorFromXML(execNodes[0]) logger.reportError(self.error) self.finalStatus = status self.answerDom = answerDom break finally: self.socket.close() if self.is_alive(): logger.reportDebug('Thread is now stopped.')
def run(self): try: self.socket.connect((self.linknx.host, self.linknx.port)) logger.reportDebug("Message sent to linknx: " + self.messageWithEncodingHeader) answer = self.socket.sendString(self.messageWithEncodingHeader, encoding="utf8") while True: logger.reportDebug("Linknx answered " + answer) answerDom = parseString(answer[0 : answer.rfind(chr(4))]) execNodes = answerDom.getElementsByTagName(self.commandName) status = execNodes[0].getAttribute("status") if status == "ongoing": # Wait for the final status. answer = self.socket.waitForStringAnswer() logger.reportDebug("New answer is {0}".format(answer)) else: if status != "success": self.error = self._getErrorFromXML(execNodes[0]) logger.reportError(self.error) self.finalStatus = status self.answerDom = answerDom break finally: self.socket.close() if self.isAlive(): logger.reportDebug("Thread is now stopped.")
def _executeUserCallback(self, callbackName, context, isOptional=False): try: if hasattr(self._userModule, callbackName): logger.reportDebug('Calling user callback {0} with context {1}'.format(callbackName, context)) callback = getattr(self._userModule, callbackName) res = callback(context) logger.reportDebug('Callback {0} returned {1}'.format(callbackName, res)) return res else: message='No function {0} defined in {1}'.format(callbackName, self._userFile) if isOptional: logger.reportInfo(message + ', skipping') else: logger.reportWarning(message) except Exception as e: logger.reportException('User code execution failed.')
def executeAction(self, actionXml): logger.reportDebug('executeActionMock: {0}'.format( actionXml.toxml())) actionNode = actionXml.getElementsByTagName('action')[0] if actionNode.getAttribute('type') == 'shell-cmd': self.test.assertIsNone( self.test.shellCmdInfo, 'An unconsumed shell command is about to be deleted. It is likely to be an unexpected shell command. Details are {0}' .format(self.test.shellCmdInfo)) self.test.shellCmdInfo = { 'action': actionXml, 'date': time.ctime() } logger.reportInfo('executeAction mock received {0}'.format( self.test.shellCmdInfo)) self.realExecute(actionXml)
def value(self, objValue): """ Write object's value to linknx. """ # Convert value to the linknx format. logger.reportDebug("Attempting to set value of {0} to {1}".format(self._id, objValue)) objectValue = self.convertValueToString(objValue) if not objValue is objectValue: logger.reportDebug("Value has been converted to " + str(objectValue)) # Initialize DOM with a simple string, then use minidom to write # attributes so that special characters are properly encoded (for # instance, &ersand; in place of &, etc). messageDom = parseString("<write><object/></write>") objectNode = messageDom.getElementsByTagName("object")[0] objectNode.setAttribute("id", self._id) objectNode.setAttribute("value", objectValue) answerDom = self._linknx._sendMessage("Write {0}={1}".format(self.id, objValue), messageDom.toxml(), "write")
def onPersistentAlertChanged(self, persistentObject): logger.reportDebug('onPersistentAlertChanged {0}={1}'.format(persistentObject.id, persistentObject.value)) # Do nothing when persistent alert becomes true. if persistentObject.value: return # Reset persistence of sensors that belong to this alert type. deactivatedAlert = self.getAlertByPersistenceObjectId(persistentObject.id) # Not found. if deactivatedAlert is None: raise Exception('Persistent alert object does not match any alert type.') deactivatedAlert.stop() # Reset persistent alert for all sensors. for sensor in [s for s in self.sensors if s.alert == deactivatedAlert]: if sensor.persistenceObject != None: sensor.persistenceObject.value = False
def executeAction(self, actionDetails): if isinstance(actionDetails, str): actionXML = actionDetails elif isinstance(actionDetails, Document): actionXML = actionDetails.childNodes[0].toxml() elif isinstance(actionDetails, Element): actionXML = actionDetails.toxml() else: raise Exception("Unsupported action details: must be a minidom XML document or element or an XML string.") # Build XML document to send to linknx. self._sendMessage( "Execute {0}".format(actionXML), "<execute>{action}</execute>".format(action=actionXML), "execute", waitsForAnswer=False, ) logger.reportDebug("Action execution has been sent to linknx.")
def executeAction(self, actionDetails): if isinstance(actionDetails, str): actionXML = actionDetails elif isinstance(actionDetails, Document): actionXML = actionDetails.childNodes[0].toxml() elif isinstance(actionDetails, Element): actionXML = actionDetails.toxml() else: raise Exception( 'Unsupported action details: must be a minidom XML document or element or an XML string.' ) # Build XML document to send to linknx. self._sendMessage( 'Execute {0}'.format(actionXML), '<execute>{action}</execute>'.format(action=actionXML), 'execute', waitsForAnswer=False) logger.reportDebug('Action execution has been sent to linknx.')
def testMultipleConnections(self): """ Checks that communicator can handle several connections at once (with a queue of connections when a connection is being treated). """ # Set state to a known one. booleanObject = self.linknx.getObject('Boolean') floatObject = self.linknx.getObject('Float16') booleanObject.value = False floatObject.value = 0.0 self.waitDuring(2, 'Initializing...') # Redirect some events to be able to block them. self.callbackCalledFor = [] try: self.blockCallback = True with self.patchUserModule({'onBooleanChanged' : self.mockForTestMultipleConnections, 'onFloatChanged' : self.mockForTestMultipleConnections}): # Change an object. booleanObject.value = True # Wait for callback. while not self.callbackCalledFor: logger.reportDebug('Waiting for first callback...') time.sleep(1) self.assertEqual(self.callbackCalledFor, [booleanObject]) self.assertTrue(booleanObject.value) # Chain with second callback while first is blocked. floatObject.value = 1.0 # Wait a few seconds, callback should not be called now (since it is not # reentrant). self.waitDuring(5, 'Checking that second callback is not called until first callback is released.', [lambda: self.assertEqual(self.callbackCalledFor, [booleanObject])]) # Release callback. self.blockCallback = False # Wait for second callback. self.waitDuring(3, 'Waiting for second callback...') self.assertEqual(self.callbackCalledFor, [booleanObject, floatObject]) finally: # Release all threads in case test went wrong. self.blockCallback = False
def value(self, objValue): """ Write object's value to linknx. """ # Convert value to the linknx format. logger.reportDebug('Attempting to set value of {0} to {1}'.format( self._id, objValue)) objectValue = self.convertValueToString(objValue) if not objValue is objectValue: logger.reportDebug('Value has been converted to ' + str(objectValue)) # Initialize DOM with a simple string, then use minidom to write # attributes so that special characters are properly encoded (for # instance, &ersand; in place of &, etc). messageDom = parseString('<write><object/></write>') objectNode = messageDom.getElementsByTagName('object')[0] objectNode.setAttribute('id', self._id) objectNode.setAttribute('value', objectValue) answerDom = self._linknx._sendMessage( 'Write {0}={1}'.format(self.id, objValue), messageDom.toxml(), 'write')
def onPersistentAlertChanged(self, persistentObject): logger.reportDebug('onPersistentAlertChanged {0}={1}'.format( persistentObject.id, persistentObject.value)) # Do nothing when persistent alert becomes true. if persistentObject.value: return # Reset persistence of sensors that belong to this alert type. deactivatedAlert = self.getAlertByPersistenceObjectId( persistentObject.id) # Not found. if deactivatedAlert is None: raise Exception( 'Persistent alert object does not match any alert type.') deactivatedAlert.stop() # Reset persistent alert for all sensors. for sensor in [s for s in self.sensors if s.alert == deactivatedAlert]: if sensor.persistenceObject != None: sensor.persistenceObject.value = False
def run(self): logger.reportInfo('Listening on ' + str(self._address)) self._isStopRequested = False try: self._socket.bind(self._address) # Thread loop. while not self._isStopRequested: self.isReady = True data, conn = self._socket.waitForString(endChar='$') # Throw data away if script has not been initialized yet. # See startListening for details. if data is None or not self._communicator.isUserScriptInitialized: time.sleep(0.1) continue logger.reportDebug('Data received: {0}'.format(data)) # Handle request. tokens = data.split('|') callbackName = tokens[0] # Parse arguments. First is object id. args={} for token in tokens[1:]: argName, sep, argValue = token.partition('=') if argValue: argValue = argValue.strip() args[argName.strip()] = argValue context = CallbackContext(self, args) res = self._communicator._executeUserCallback(callbackName, context) if res: conn.sendall(res + '$') conn.close() except Exception as e: logger.reportException() finally: logger.reportDebug('Closing socket...') self._socket.close() logger.reportInfo('Socket closed. Listening terminated.') self._socket = None
def fireEvent(self, eventType, description, context): """ Raises event (i.e executes every action related to this event). """ logger.reportDebug('Firing event {0}'.format(description)) for event in self.eventConfigs: if event.type != eventType: continue # Set up the various actions for that event type. logger.reportDebug('Executing actions {0}'.format(event.actions)) for actionConfig in event.actions: if actionConfig.type == 'send-email': action = SendEmailAction(self.daemon, actionConfig) elif actionConfig.type == 'send-sms': action = SendSMSAction(self.daemon, actionConfig) else: # Delegate execution to linknx. action = LinknxAction(self.daemon, actionConfig) action.execute(context) logger.reportDebug('Event {0} is now finished.'.format(description))
def fireEvent(self, eventType, description, context): """ Raises event (i.e executes every action related to this event). """ logger.reportDebug('Firing event {0}'.format(description)) for event in self.eventConfigs: if event.type != eventType: continue # Set up the various actions for that event type. logger.reportDebug('Executing actions {0}'.format(event.actions)) for actionConfig in event.actions: if actionConfig.type == 'send-email': action = SendEmailAction(self.daemon, actionConfig) elif actionConfig.type == 'send-sms': action = SendSMSAction(self.daemon, actionConfig) elif actionConfig.type == 'shell-cmd': action = ShellCommandAction(self.daemon, actionConfig) else: # Delegate execution to linknx. action = LinknxAction(self.daemon, actionConfig) action.execute(context) logger.reportDebug('Event {0} is now finished.'.format(description))
def onModeObjectChanged(context): global alarmDaemon logger.reportDebug('Alarm mode changed to ' + str(context.object.value)) alarmDaemon.onModeValueChanged(context.object.value)
def sendEmailMock(self, actionXml): logger.reportDebug('sendEmailMock: {0}'.format(actionXml.toxml())) self.assertIsNone(self.emailInfo, 'An unconsumed email is about to be deleted. It is likely to be an unexpected email. Details are {0}'.format(self.emailInfo)) self.emailInfo = {'action' : actionXml, 'date' : time.ctime()} logger.reportInfo('sendEmail mock received {0}'.format(self.emailInfo))
def doTestIntrusion(self, togglesSensorBeforeEndOfPrealert, shuntsprealertWithFasterSensor, cancelsAlarm, testsInhibition): """ Test exercising the alert handling when an intrusion is detected. """ logger.reportInfo('\n\n*********INITIALIZE testIntrusion togglesSensorBeforeEndOfPrealert={0} shuntsprealertWithFasterSensor={1} cancelsAlarm={2}********************'.format(togglesSensorBeforeEndOfPrealert, shuntsprealertWithFasterSensor, cancelsAlarm)) daemon = self.alarmDaemon self.assertTrue(togglesSensorBeforeEndOfPrealert ^ shuntsprealertWithFasterSensor ^ cancelsAlarm ^ testsInhibition) # Prepare sensors involved in this test. entranceSensor = daemon.getSensorByName('EntranceDoorOpening') livingRoomWindowSensor = daemon.getSensorByName('LivingRoomWindowOpening') kitchenWindowSensor = daemon.getSensorByName('KitchenWindowOpening') intrusionAlert = daemon.getAlertByName('Intrusion') # Initialize state to a known one. self.alarmModeObject.value = 1 for sensor in (entranceSensor, livingRoomWindowSensor, kitchenWindowSensor): sensor.watchedObject.value = False self.waitDuring(1.5, 'Initializing') modeChangeTime = time.time() self.emailInfo = None # In case mode initialization has raised an email. self.changeAlarmMode('Away', '*****@*****.**') self.waitUntil(modeChangeTime + entranceSensor.getActivationDelay() + 0.5, 'Waiting for door to be enabled.') for sensor in (entranceSensor, livingRoomWindowSensor, kitchenWindowSensor): self.assertTrue(sensor.isEnabled, '{0} should now be enabled.'.format(sensor)) self.assertEqual(entranceSensor.getPrealertDuration(), 6) # Step inside home. firstTriggerTime = time.time() entranceSensor.watchedObject.value = True sensorsInPrealert = [entranceSensor] sensorsInAlert = [] sensorsInPersistentAlert = [] checkAlertStatus = lambda: self.assertAlert(sensorsInPrealert, sensorsInAlert, sensorsInPersistentAlert) intermediaryDelay = entranceSensor.getPrealertDuration() / 4 if togglesSensorBeforeEndOfPrealert: # Release sensor as quickly as possible. self.waitDuring(intermediaryDelay, [checkAlertStatus]) entranceSensor.watchedObject.value = False # Toggle sensor again. self.waitDuring(intermediaryDelay, [checkAlertStatus]) entranceSensor.watchedObject.value = True # Release again and leave it in that state until end of prealert (to # make sure alert state is not taken from the current sensor # status). self.waitDuring(intermediaryDelay, [checkAlertStatus]) entranceSensor.watchedObject.value = False if cancelsAlarm: self.waitDuring(intermediaryDelay, [checkAlertStatus]) self.changeAlarmMode('Presence', '*****@*****.**') # Takes some time. del(sensorsInPrealert[:]) for sensor in (entranceSensor, livingRoomWindowSensor, kitchenWindowSensor): self.assertFalse(sensor.isEnabled, '{0} should not be enabled anymore.'.format(sensor)) # Make sure alert is not raised after prealert delay of entrance # door. self.waitUntil(firstTriggerTime + entranceSensor.getPrealertDuration() + 1.5, 'Wait a few seconds to make sure no alert is being raised.', [checkAlertStatus]) else: if shuntsprealertWithFasterSensor: # Prealert duration is driven by living room window, as it is a faster sensor # than entrance door. self.waitDuring(intermediaryDelay, [checkAlertStatus]) sensorsInPrealert.append(livingRoomWindowSensor) livingRoomWindowSensor.watchedObject.value = True # Check that living room window will raise alert faster than # entrance door (the opposite would denote a configuration # error). remainingTimeBeforeDoorAlert = entranceSensor.getPrealertDuration() - (time.time() - firstTriggerTime) logger.reportDebug('Remaining time before door alert: {0}s, before living room alert: {1} (expected to be shorter in living room!)'.format(remainingTimeBeforeDoorAlert, livingRoomWindowSensor.getPrealertDuration())) self.assertLess(livingRoomWindowSensor.getPrealertDuration(), remainingTimeBeforeDoorAlert, 'Living room window will not raise alert before entrance door, this test is not properly set up.') self.waitDuring(livingRoomWindowSensor.getPrealertDuration() + 1, 'Let living room window prealert pass.', [checkAlertStatus], 0.2, 1.2) else: # Normal prealert with entrance door. self.waitUntil(firstTriggerTime + entranceSensor.getPrealertDuration() + 1, 'Let prealert delay pass.', [checkAlertStatus], 0.5, 1.2) # Whichever strategy should now lead to entrance door being in alert # (either because of its own prealert or because an intrusion alert has # been raised by kitchen blinds meanwhile). sensorsInAlert.extend(sensorsInPrealert) sensorsInPersistentAlert.extend(sensorsInAlert) del(sensorsInPrealert[:]) # Wait for first sensor to quit alert. At this point, entranceSensor # should already have been in alert for 1 second. self.assertTrue(entranceSensor.getAlertDuration() < livingRoomWindowSensor.getAlertDuration(), 'This test assumes that door\'s alert is shorter than kitchen\'s one.') self.assertEmail('Sensor joined', ['*****@*****.**'], 'Alert Intrusion: sensor joined', []) self.waitDuring(entranceSensor.getAlertDuration() - 0.5, 'Wait for first sensor to quit alert.', [checkAlertStatus], 0, 1) sensorsInAlert.remove(entranceSensor) self.assertFalse(entranceSensor.isAlertActive) # Wait for the end of second sensor's alert. self.waitUntil(firstTriggerTime + intermediaryDelay + livingRoomWindowSensor.getPrealertDuration() + livingRoomWindowSensor.getAlertDuration() + 1, 'Waiting for second sensor to quit alert.', [checkAlertStatus], 0, 1) usesLivingWindow = livingRoomWindowSensor in sensorsInAlert if usesLivingWindow: sensorsInAlert.remove(livingRoomWindowSensor) # Check that test is properly set up at this point. self.assertFalse(sensorsInAlert) self.assertFalse(sensorsInPrealert) self.assertEqual(set(sensorsInPersistentAlert), set([livingRoomWindowSensor, entranceSensor] if usesLivingWindow else [entranceSensor])) # Wait a few seconds more, to check everything stays ok. self.waitDuring(5, 'Checking that no event occurs...', [checkAlertStatus]) if testsInhibition: # Relaunch alert => shunt prealert and go to alert immediately. sensorsInAlert.append(entranceSensor) sensorsInPersistentAlert.append(entranceSensor) entranceSensor.watchedObject.value = False self.waitDuring(0.2, 'Closing entrance door.') entranceSensor.watchedObject.value = True self.waitDuring(0.5, 'Waiting for alert to be reraised...') self.assertEmail('Sensor joined', ['*****@*****.**'], 'Alert Intrusion: sensor joined', []) checkAlertStatus() # Stop current alert without inhibiting for now. intrusionAlert.persistenceObject.value = False sensorsInAlert.remove(entranceSensor) sensorsInPersistentAlert.clear() self.waitDuring(0.5, 'Stopping current alert without inhibiting...') checkAlertStatus() # Relaunch alert again. entranceSensor.watchedObject.value = False self.waitDuring(0.2, 'Closing entrance door.') entranceSensor.watchedObject.value = True self.waitDuring(0.2, 'Reopening entrance door.') entranceSensor.watchedObject.value = False sensorsInPrealert.append(entranceSensor) self.waitDuring(5.9, 'Waiting for alert to be reraised...', [checkAlertStatus], 0.5, 0.2) self.assertEmail('Sensor joined', ['*****@*****.**'], 'Alert Intrusion: sensor joined', []) sensorsInPrealert.remove(entranceSensor) sensorsInAlert.append(entranceSensor) sensorsInPersistentAlert.append(entranceSensor) checkAlertStatus() # Now inhibit intrusion alert. intrusionAlert.inhibitionObject.value = True intrusionAlert.persistenceObject.value = False del(sensorsInAlert[:]) del(sensorsInPersistentAlert[:]) self.waitDuring(0.6, 'Inhibiting intrusion alert...') checkAlertStatus() # Trigger another sensor to make sure alert is really inhibited. kitchenWindowSensor.watchedObject.value = False self.waitDuring(0.2, 'Closing kitchen window.') kitchenWindowSensor.watchedObject.value = True self.waitDuring(kitchenWindowSensor.getPrealertDuration() + 1.5, 'Checking that opening sesnors do not fire alert anymore (alert is inhibited)...', [checkAlertStatus]) # Remove inhibition: nothing should occur until a sensor gets # triggered again (currently triggered sensors are still ignored). intrusionAlert.inhibitionObject.value = False self.waitDuring(4.5, 'Checking no event occurs...', [checkAlertStatus], 1.5) # Close window and open it again: alert! kitchenWindowSensor.watchedObject.value = False self.waitDuring(0.2, 'Closing kitchen window.', [checkAlertStatus]) triggerTime = time.time() kitchenWindowSensor.watchedObject.value = True sensorsInPrealert.append(kitchenWindowSensor) self.waitDuring(kitchenWindowSensor.getPrealertDuration() + 0.2, 'Opening door again...', [checkAlertStatus], 0.2, 0.4) sensorsInPrealert.remove(kitchenWindowSensor) sensorsInAlert.append(kitchenWindowSensor) sensorsInPersistentAlert.append(kitchenWindowSensor) self.assertEmail('Sensor joined', ['*****@*****.**'], 'Alert Intrusion: sensor joined', []) self.waitUntil(triggerTime + kitchenWindowSensor.getPrealertDuration() + kitchenWindowSensor.getAlertDuration(), 'Checking door alert...', [checkAlertStatus], 0.2, 0.2) sensorsInAlert.remove(kitchenWindowSensor) self.waitUntil(2, 'Alert should now be paused...', [checkAlertStatus], 0.2, 0)
def stop(self): if not self.isCancelled: self.isCancelled = True self.isTerminating = True logger.reportDebug('Cancelling {0}.'.format(self))
def __enter__(self): for k, v in self.patches.items(): logger.reportDebug('Patching function object {0}.{1}={2}'.format( self.module, k, v)) self.originalMethodObjects[k] = getattr(self.module, k) setattr(self.module, k, v)
def __exit__(self, exc_type, exc_value, traceback): for k, v in self.originalMethodObjects.items(): logger.reportDebug('Restoring function object {0}.{1}={2}'.format( self.module, k, v)) setattr(self.module, k, v)
def generateConfig(self): # Read xml to get pyknx special attributes. config = self.config doc = config.ownerDocument rulesNode = self._getOrAddConfigElement(config, 'rules') # Generate a rule for each object that has a callback in the user file. objectNodes = config.getElementsByTagName('objects')[0] configuredAtLeastOne = False definesLegacyCallbackAttribute = False callbackAttributeName = self.callbackAttributeName for objectNode in objectNodes.getElementsByTagName('object'): objectConfig = ObjectConfig(objectNode) objectId = objectConfig.id callback = objectNode.getAttribute(callbackAttributeName) if callback == None or callback == '': if objectNode.getAttribute('pyknxcallback'): logger.reportError('pyknxcallback found on {0}'.format(objectNode.toxml())) definesLegacyCallbackAttribute = True logger.reportDebug('No callback found for object ' + objectConfig.id + ' (no {0} attribute for this object)'.format(callbackAttributeName)) continue configuredAtLeastOne = True ruleNode = doc.createElement('rule') ruleId = '{0}{1}'.format(self._communicatorName, objectId) logger.reportInfo('Generating rule {0}'.format(ruleId)) ruleNode.setAttribute('id', ruleId) ruleNode.setAttribute('init', 'false') conditionNode = doc.createElement('condition') conditionNode.setAttribute('type', 'object') conditionNode.setAttribute('id', objectId) # conditionNode.setAttribute('value', objectConfig.defaultValue) conditionNode.setAttribute('trigger', 'true') ruleNode.appendChild(conditionNode) actionListNode = doc.createElement('actionlist') actionListNode.setAttribute('type', 'if-true') ruleNode.appendChild(actionListNode) actionNode = self.createActionNode(callback, {'objectId' : objectId}) actionListNode.appendChild(actionNode) # actionListIfFalseNode = actionListNode.cloneNode(True) # actionListIfFalseNode.setAttribute('type', 'on-false') # # ruleNode.appendChild(actionListIfFalseNode) rulesNode.appendChild(ruleNode) if not configuredAtLeastOne: logger.reportInfo('Nothing to do. None of the objects does define a callback attribute.') if definesLegacyCallbackAttribute: logger.reportWarning('There is at least one pyknxcallback attribute in the config file. These attributes were recognized by Pyknx before version 2.2. Did you forget to rename them to {0}?'.format(callbackAttributeName)) else: # Add an ioport service for the communicator. servicesNode = self._getOrAddConfigElement(config, 'services') ioportsNode = self._getOrAddConfigElement(servicesNode, 'ioports') ioportNode = doc.createElement('ioport') ioportNode.setAttribute('id', self._communicatorName) try: hostIP = socket.gethostbyname(self._address[0]) except: logger.reportWarning('Could not check that {0} is a valid ip address. Please check the output configuration. Linknx does not support hostnames, it requires IP address.'.format(self._address[0])) hostIP = self._address[0] ioportNode.setAttribute('host', hostIP) #gethostbyname converts the hostname into an ip. Linknx does not support ioport hostnames. ioportNode.setAttribute('port', str(self._address[1])) ioportNode.setAttribute('type', 'tcp') ioportsNode.appendChild(ioportNode)
def updateStatus(self): """ Updates the status of this alert and raises the required events accordingly. """ # Do not update if the daemon is in a process that may trigger # irrelevant intermediary states. if self.daemon.areAlertStatusUpdatesSuspended: return if not self.isStatusDirty: logger.reportDebug('Status of {0} is already up-to-date, nothing to change.'.format(self)) return logger.reportDebug('Updating status of {0}'.format(self)) # Compute current status. if self._sensorsInAlert: newStatus = Alert.Status.ACTIVE elif self._sensorsInPrealert: newStatus = Alert.Status.INITIALIZING elif self.status == Alert.Status.ACTIVE and (self.persistenceObject != None and self.persistenceObject.value): # PAUSED status may only occur if persistence is supported. # Otherwise, as soon as last sensor leaves the alert, alert is # stopped and will start if a sensor gets triggered afterwards. This # is not the most convenient behaviour but with it, the user is free not to # define persistence. newStatus = Alert.Status.PAUSED else: newStatus = Alert.Status.STOPPED logger.reportDebug('New status for {0} is {1}'.format(self, newStatus)) # When the alert is active, all sensors should leave the "prealert" # state to join the alert. if newStatus in (Alert.Status.ACTIVE, Alert.Status.PAUSED): # PAUSED is to be on the safe side as alert should always go through the ACTIVE state before going to PAUSED. for sensor in self._sensorsInPrealert: if not sensor in self._sensorsInAlert: self._sensorsInAlert.add(sensor) # None at this point. Timers will be created later in this method. # sensor.makeAlertTimer(onTimeoutReached=None, onTerminated=lambda: self.removeSensorFromAlert(sensor)) self._sensorsInPrealert = set() # Diff registered sensors. joiningSensors = self._sensorsInAlert - self._sensorsInAlertOnLastUpdateStatus leavingSensors = self._sensorsInAlertOnLastUpdateStatus- self._sensorsInAlert logger.reportDebug('Updating status for {0}: joiningSensors={1}, leavingSensors={2}'.format(self, joiningSensors, leavingSensors)) if newStatus == Alert.Status.ACTIVE: if self.persistenceObject != None: self.persistenceObject.value = True # Handle consequences of status change. if self.status == Alert.Status.STOPPED: if newStatus == Alert.Status.STOPPED: # No change. pass elif newStatus == Alert.Status.INITIALIZING: self.notifyAlertStarted() else: # Should not happen. logger.reportError('Unsupported switch from "{old}" to "{new}" for alert {alert}'.format(alert=self, old=self.status, new=newStatus)) elif self.status == Alert.Status.ACTIVE: if newStatus == Alert.Status.ACTIVE: # Check if a sensor joined or left. if joiningSensors: self.notifySensorJoined() if leavingSensors: self.notifySensorLeft() elif newStatus in (Alert.Status.PAUSED, Alert.Status.STOPPED): if not leavingSensors: logger.reportError('A sensor should have left the alert.') else: self.notifySensorLeft() self.notifyAlertDeactivated() if newStatus == Alert.Status.STOPPED: self.notifyAlertReset() self.notifyAlertStopped() elif newStatus == Alert.Status.PAUSED: self.notifyAlertPaused() else: raise Exception('Not implemented.') else: # Should not happen. logger.reportError('Unsupported switch from "{old}" to "{new}" for alert {alert}'.format(alert=self, old=self.status, new=newStatus)) elif self.status == Alert.Status.PAUSED: if newStatus == Alert.Status.PAUSED: # No change. pass elif newStatus == Alert.Status.STOPPED: self.notifyAlertReset() self.notifyAlertStopped() elif newStatus == Alert.Status.ACTIVE: self.notifyAlertResumed() if not joiningSensors: logger.reportError('A sensor should have joined the alert.') else: self.notifySensorJoined() self.notifyAlertActivated() elif self.status == Alert.Status.INITIALIZING: if newStatus == Alert.Status.INITIALIZING: # No change. pass elif newStatus == Alert.Status.ACTIVE: # Events to raise: started, sensor-joined, activated. if not joiningSensors: logger.reportError('A sensor should have joined the alert.') else: self.notifySensorJoined() self.notifyAlertActivated() elif newStatus == Alert.Status.STOPPED: self.notifyAlertAborted() self.notifyAlertStopped() # Stop obsolete timers for all sensors related to this alert. if newStatus in (Alert.Status.PAUSED, Alert.Status.STOPPED) or (self.status == Alert.Status.INITIALIZING and newStatus == Alert.Status.ACTIVE): for sensor in self.sensors: # Get the optional timer currently running for this sensor. timer = self._sensorTimers.get(sensor) if timer == None: continue timer.stop() timer = None del self._sensorTimers[sensor] for sensor in self._sensorsInAlert.union(self._sensorsInPrealert): # Start a new timer? if newStatus in (Alert.Status.INITIALIZING, Alert.Status.ACTIVE) and self._sensorTimers.get(sensor) == None: timer = sensor.makePrealertTimer() if newStatus == Alert.Status.INITIALIZING else sensor.makeAlertTimer() self._sensorTimers[sensor] = timer # Prealert timer has been deleted above if applicable. timer.start() # Update persistence objects for all sensors. for s in self._sensorsInAlert: if s.persistenceObject != None: s.persistenceObject.value = True # Store current status. self._sensorsInAlertOnLastUpdateStatus = self._sensorsInAlert.copy() self.status = newStatus self.isStatusDirty = False
def getActivationDelay(self): delay = self._config.activationDelay.getForMode(self.daemon.currentMode.name) logger.reportDebug('getActivationDelay of {2} for {0}, currentMode={1}'.format(self, self.daemon.currentMode, delay)) return delay
def __exit__(self, exc_type, exc_value, traceback): for k,v in self.originalMethodObjects.items(): logger.reportDebug('Restoring function object {0}.{1}={2}'.format(self.module, k, v)) setattr(self.module, k, v)
def forceTimeout(self): logger.reportDebug('Forcing timeout of {0}'.format(self)) self.endTime = 0
# Configure logger. logger.initLogger(None, args.verbosityLevel.upper()) # The homewatcher daemon is represented by an instance of # a pyknx.communicator.Communicator that runs with an "user script" dedicated to # interfacing linknx with homewatcher's capabilities. # First: read homewatcher config to read the linknx server url. # Second: start pyknxcommunicator with homewatcher's user script. logger.reportInfo( 'Reading config file {file}'.format(file=args.homewatcherConfig)) config = configuration.Configuration.parseFile(args.homewatcherConfig) userScript = os.path.join(os.path.dirname(configuration.__file__), 'linknxuserfile.py') logger.reportDebug( 'Pyknx\'s user script for homewatcher is {script}'.format( script=userScript)) userScriptArgs = {'hwconfig': config} services = config.servicesRepository communicatorAddress = (services.daemon.host, services.daemon.port) logger.reportInfo( 'Starting Homewatcher at {communicatorAddr}, linked to linknx at {linknxAddr}' .format(communicatorAddr=communicatorAddress, linknxAddr=services.linknx.address)) linknx = linknx.Linknx(services.linknx.host, services.linknx.port) communicator.Communicator.run(linknxAddress=linknx.address, userFile=userScript, communicatorAddress=communicatorAddress, userScriptArgs=userScriptArgs, verbosityLevel=args.verbosityLevel, logFile=args.logFile,
def __enter__(self): for k,v in self.patches.items(): logger.reportDebug('Patching function object {0}.{1}={2}'.format(self.module, k, v)) self.originalMethodObjects[k] = getattr(self.module, k) setattr(self.module, k, v)
def doTestIntrusion(self, togglesSensorBeforeEndOfPrealert, shuntsprealertWithFasterSensor, cancelsAlarm, testsInhibition): """ Test exercising the alert handling when an intrusion is detected. """ logger.reportInfo( '\n\n*********INITIALIZE testIntrusion togglesSensorBeforeEndOfPrealert={0} shuntsprealertWithFasterSensor={1} cancelsAlarm={2}********************' .format(togglesSensorBeforeEndOfPrealert, shuntsprealertWithFasterSensor, cancelsAlarm)) daemon = self.alarmDaemon self.assertTrue(togglesSensorBeforeEndOfPrealert ^ shuntsprealertWithFasterSensor ^ cancelsAlarm ^ testsInhibition) # Prepare sensors involved in this test. entranceSensor = daemon.getSensorByName('EntranceDoorOpening') livingRoomWindowSensor = daemon.getSensorByName( 'LivingRoomWindowOpening') kitchenWindowSensor = daemon.getSensorByName('KitchenWindowOpening') intrusionAlert = daemon.getAlertByName('Intrusion') # Initialize state to a known one. self.alarmModeObject.value = 1 for sensor in (entranceSensor, livingRoomWindowSensor, kitchenWindowSensor): sensor.watchedObject.value = False self.waitDuring(1.5, 'Initializing') modeChangeTime = time.time() self.emailInfo = None # In case mode initialization has raised an email. self.changeAlarmMode('Away', '*****@*****.**') self.waitUntil( modeChangeTime + entranceSensor.getActivationDelay() + 0.5, 'Waiting for door to be enabled.') for sensor in (entranceSensor, livingRoomWindowSensor, kitchenWindowSensor): self.assertTrue(sensor.isEnabled, '{0} should now be enabled.'.format(sensor)) self.assertEqual(entranceSensor.getPrealertDuration(), 6) # Step inside home. firstTriggerTime = time.time() entranceSensor.watchedObject.value = True sensorsInPrealert = [entranceSensor] sensorsInAlert = [] sensorsInPersistentAlert = [] checkAlertStatus = lambda: self.assertAlert( sensorsInPrealert, sensorsInAlert, sensorsInPersistentAlert) intermediaryDelay = entranceSensor.getPrealertDuration() / 4 if togglesSensorBeforeEndOfPrealert: # Release sensor as quickly as possible. self.waitDuring(intermediaryDelay, [checkAlertStatus]) entranceSensor.watchedObject.value = False # Toggle sensor again. self.waitDuring(intermediaryDelay, [checkAlertStatus]) entranceSensor.watchedObject.value = True # Release again and leave it in that state until end of prealert (to # make sure alert state is not taken from the current sensor # status). self.waitDuring(intermediaryDelay, [checkAlertStatus]) entranceSensor.watchedObject.value = False if cancelsAlarm: self.waitDuring(intermediaryDelay, [checkAlertStatus]) self.changeAlarmMode('Presence', '*****@*****.**') # Takes some time. del (sensorsInPrealert[:]) for sensor in (entranceSensor, livingRoomWindowSensor, kitchenWindowSensor): self.assertFalse( sensor.isEnabled, '{0} should not be enabled anymore.'.format(sensor)) # Make sure alert is not raised after prealert delay of entrance # door. self.waitUntil( firstTriggerTime + entranceSensor.getPrealertDuration() + 1.5, 'Wait a few seconds to make sure no alert is being raised.', [checkAlertStatus]) else: if shuntsprealertWithFasterSensor: # Prealert duration is driven by living room window, as it is a faster sensor # than entrance door. self.waitDuring(intermediaryDelay, [checkAlertStatus]) sensorsInPrealert.append(livingRoomWindowSensor) livingRoomWindowSensor.watchedObject.value = True # Check that living room window will raise alert faster than # entrance door (the opposite would denote a configuration # error). remainingTimeBeforeDoorAlert = entranceSensor.getPrealertDuration( ) - (time.time() - firstTriggerTime) logger.reportDebug( 'Remaining time before door alert: {0}s, before living room alert: {1} (expected to be shorter in living room!)' .format(remainingTimeBeforeDoorAlert, livingRoomWindowSensor.getPrealertDuration())) self.assertLess( livingRoomWindowSensor.getPrealertDuration(), remainingTimeBeforeDoorAlert, 'Living room window will not raise alert before entrance door, this test is not properly set up.' ) self.waitDuring( livingRoomWindowSensor.getPrealertDuration() + 1, 'Let living room window prealert pass.', [checkAlertStatus], 0.2, 1.2) else: # Normal prealert with entrance door. self.waitUntil( firstTriggerTime + entranceSensor.getPrealertDuration() + 1, 'Let prealert delay pass.', [checkAlertStatus], 0.5, 1.2) # Whichever strategy should now lead to entrance door being in alert # (either because of its own prealert or because an intrusion alert has # been raised by kitchen blinds meanwhile). sensorsInAlert.extend(sensorsInPrealert) sensorsInPersistentAlert.extend(sensorsInAlert) del (sensorsInPrealert[:]) # Wait for first sensor to quit alert. At this point, entranceSensor # should already have been in alert for 1 second. self.assertTrue( entranceSensor.getAlertDuration() < livingRoomWindowSensor.getAlertDuration(), 'This test assumes that door\'s alert is shorter than kitchen\'s one.' ) self.assertEmail('Sensor joined', ['*****@*****.**'], 'Alert Intrusion: sensor joined', []) self.waitDuring(entranceSensor.getAlertDuration() - 0.5, 'Wait for first sensor to quit alert.', [checkAlertStatus], 0, 1) sensorsInAlert.remove(entranceSensor) self.assertFalse(entranceSensor.isAlertActive) # Wait for the end of second sensor's alert. self.waitUntil( firstTriggerTime + intermediaryDelay + livingRoomWindowSensor.getPrealertDuration() + livingRoomWindowSensor.getAlertDuration() + 1, 'Waiting for second sensor to quit alert.', [checkAlertStatus], 0, 1) usesLivingWindow = livingRoomWindowSensor in sensorsInAlert if usesLivingWindow: sensorsInAlert.remove(livingRoomWindowSensor) # Check that test is properly set up at this point. self.assertFalse(sensorsInAlert) self.assertFalse(sensorsInPrealert) self.assertEqual( set(sensorsInPersistentAlert), set([livingRoomWindowSensor, entranceSensor] if usesLivingWindow else [entranceSensor])) # Wait a few seconds more, to check everything stays ok. self.waitDuring(5, 'Checking that no event occurs...', [checkAlertStatus]) if testsInhibition: # Relaunch alert => shunt prealert and go to alert immediately. sensorsInAlert.append(entranceSensor) sensorsInPersistentAlert.append(entranceSensor) entranceSensor.watchedObject.value = False self.waitDuring(0.2, 'Closing entrance door.') entranceSensor.watchedObject.value = True self.waitDuring(0.5, 'Waiting for alert to be reraised...') self.assertEmail('Sensor joined', ['*****@*****.**'], 'Alert Intrusion: sensor joined', []) checkAlertStatus() # Stop current alert without inhibiting for now. intrusionAlert.persistenceObject.value = False sensorsInAlert.remove(entranceSensor) sensorsInPersistentAlert.clear() self.waitDuring( 0.5, 'Stopping current alert without inhibiting...') checkAlertStatus() # Relaunch alert again. entranceSensor.watchedObject.value = False self.waitDuring(0.2, 'Closing entrance door.') entranceSensor.watchedObject.value = True self.waitDuring(0.2, 'Reopening entrance door.') entranceSensor.watchedObject.value = False sensorsInPrealert.append(entranceSensor) self.waitDuring(5.9, 'Waiting for alert to be reraised...', [checkAlertStatus], 0.5, 0.2) self.assertEmail('Sensor joined', ['*****@*****.**'], 'Alert Intrusion: sensor joined', []) sensorsInPrealert.remove(entranceSensor) sensorsInAlert.append(entranceSensor) sensorsInPersistentAlert.append(entranceSensor) checkAlertStatus() # Now inhibit intrusion alert. intrusionAlert.inhibitionObject.value = True intrusionAlert.persistenceObject.value = False del (sensorsInAlert[:]) del (sensorsInPersistentAlert[:]) self.waitDuring(0.6, 'Inhibiting intrusion alert...') checkAlertStatus() # Trigger another sensor to make sure alert is really inhibited. kitchenWindowSensor.watchedObject.value = False self.waitDuring(0.2, 'Closing kitchen window.') kitchenWindowSensor.watchedObject.value = True self.waitDuring( kitchenWindowSensor.getPrealertDuration() + 1.5, 'Checking that opening sesnors do not fire alert anymore (alert is inhibited)...', [checkAlertStatus]) # Remove inhibition: nothing should occur until a sensor gets # triggered again (currently triggered sensors are still ignored). intrusionAlert.inhibitionObject.value = False self.waitDuring(4.5, 'Checking no event occurs...', [checkAlertStatus], 1.5) # Close window and open it again: alert! kitchenWindowSensor.watchedObject.value = False self.waitDuring(0.2, 'Closing kitchen window.', [checkAlertStatus]) triggerTime = time.time() kitchenWindowSensor.watchedObject.value = True sensorsInPrealert.append(kitchenWindowSensor) self.waitDuring( kitchenWindowSensor.getPrealertDuration() + 0.2, 'Opening door again...', [checkAlertStatus], 0.2, 0.4) sensorsInPrealert.remove(kitchenWindowSensor) sensorsInAlert.append(kitchenWindowSensor) sensorsInPersistentAlert.append(kitchenWindowSensor) self.assertEmail('Sensor joined', ['*****@*****.**'], 'Alert Intrusion: sensor joined', []) self.waitUntil( triggerTime + kitchenWindowSensor.getPrealertDuration() + kitchenWindowSensor.getAlertDuration(), 'Checking door alert...', [checkAlertStatus], 0.2, 0.2) sensorsInAlert.remove(kitchenWindowSensor) self.waitUntil(2, 'Alert should now be paused...', [checkAlertStatus], 0.2, 0)
import os if __name__ == '__main__': parser = argparse.ArgumentParser(description=__doc__) parser.add_argument('homewatcherConfig', help='use HWCONF as homewatcher configuration.', metavar='HWCONF') parser.add_argument('-d', '--daemonize', help='ask daemon to detach and run as a background daemon.', action='store_true', default=False) parser.add_argument('--pid-file', dest='pidFile', help='write the PID of the daemon process to PIDFILE.', metavar='PIDFILE') parser.add_argument('--log-file', dest='logFile', help='output daemon\'s activity to LOGFILE rather than to standard output.', metavar='LOGFILE', default=None) parser.add_argument('-v', '--verbosity', dest='verbosityLevel', help='set verbosity level.', metavar='LEVEL', choices=[l.lower() for l in logger.getLevelsToString()], default='info') args = parser.parse_args() # Configure logger. logger.initLogger(None, args.verbosityLevel.upper()) # The homewatcher daemon is represented by an instance of # a pyknx.communicator.Communicator that runs with an "user script" dedicated to # interfacing linknx with homewatcher's capabilities. # First: read homewatcher config to read the linknx server url. # Second: start pyknxcommunicator with homewatcher's user script. logger.reportInfo('Reading config file {file}'.format(file=args.homewatcherConfig)) config = configuration.Configuration.parseFile(args.homewatcherConfig) userScript = os.path.join(os.path.dirname(configuration.__file__), 'linknxuserfile.py') logger.reportDebug('Pyknx\'s user script for homewatcher is {script}'.format(script=userScript)) userScriptArgs = {'hwconfig' : config} services = config.servicesRepository communicatorAddress=(services.daemon.host, services.daemon.port) logger.reportInfo('Starting Homewatcher at {communicatorAddr}, linked to linknx at {linknxAddr}'.format(communicatorAddr=communicatorAddress, linknxAddr=services.linknx.address)) linknx = linknx.Linknx(services.linknx.host, services.linknx.port) communicator.Communicator.run(linknxAddress=linknx.address, userFile=userScript, communicatorAddress=communicatorAddress, userScriptArgs=userScriptArgs, verbosityLevel=args.verbosityLevel, logFile=args.logFile, daemonizes=args.daemonize, pidFile=args.pidFile)
def onModeValueChanged(self, value): logger.reportDebug('onModeValueChanged value={0}'.format(value)) self._updateModeFromLinknx()
def updateStatus(self): """ Updates the status of this alert and raises the required events accordingly. """ # Do not update if the daemon is in a process that may trigger # irrelevant intermediary states. if self.daemon.areAlertStatusUpdatesSuspended: return if not self.isStatusDirty: logger.reportDebug( 'Status of {0} is already up-to-date, nothing to change.'. format(self)) return logger.reportDebug('Updating status of {0}'.format(self)) # Compute current status. if self._sensorsInAlert: newStatus = Alert.Status.ACTIVE elif self._sensorsInPrealert: newStatus = Alert.Status.INITIALIZING elif self.status == Alert.Status.ACTIVE and ( self.persistenceObject != None and self.persistenceObject.value): # PAUSED status may only occur if persistence is supported. # Otherwise, as soon as last sensor leaves the alert, alert is # stopped and will start if a sensor gets triggered afterwards. This # is not the most convenient behaviour but with it, the user is free not to # define persistence. newStatus = Alert.Status.PAUSED else: newStatus = Alert.Status.STOPPED logger.reportDebug('New status for {0} is {1}'.format(self, newStatus)) # When the alert is active, all sensors should leave the "prealert" # state to join the alert. if newStatus in ( Alert.Status.ACTIVE, Alert.Status.PAUSED ): # PAUSED is to be on the safe side as alert should always go through the ACTIVE state before going to PAUSED. for sensor in self._sensorsInPrealert: if not sensor in self._sensorsInAlert: self._sensorsInAlert.add( sensor ) # None at this point. Timers will be created later in this method. # sensor.makeAlertTimer(onTimeoutReached=None, onTerminated=lambda: self.removeSensorFromAlert(sensor)) self._sensorsInPrealert = set() # Diff registered sensors. joiningSensors = self._sensorsInAlert - self._sensorsInAlertOnLastUpdateStatus leavingSensors = self._sensorsInAlertOnLastUpdateStatus - self._sensorsInAlert logger.reportDebug( 'Updating status for {0}: joiningSensors={1}, leavingSensors={2}'. format(self, joiningSensors, leavingSensors)) if newStatus == Alert.Status.ACTIVE: if self.persistenceObject != None: self.persistenceObject.value = True # Handle consequences of status change. if self.status == Alert.Status.STOPPED: if newStatus == Alert.Status.STOPPED: # No change. pass elif newStatus == Alert.Status.INITIALIZING: self.notifyAlertStarted() else: # Should not happen. logger.reportError( 'Unsupported switch from "{old}" to "{new}" for alert {alert}' .format(alert=self, old=self.status, new=newStatus)) elif self.status == Alert.Status.ACTIVE: if newStatus == Alert.Status.ACTIVE: # Check if a sensor joined or left. if joiningSensors: self.notifySensorJoined() if leavingSensors: self.notifySensorLeft() elif newStatus in (Alert.Status.PAUSED, Alert.Status.STOPPED): if not leavingSensors: logger.reportError('A sensor should have left the alert.') else: self.notifySensorLeft() self.notifyAlertDeactivated() if newStatus == Alert.Status.STOPPED: self.notifyAlertReset() self.notifyAlertStopped() elif newStatus == Alert.Status.PAUSED: self.notifyAlertPaused() else: raise Exception('Not implemented.') else: # Should not happen. logger.reportError( 'Unsupported switch from "{old}" to "{new}" for alert {alert}' .format(alert=self, old=self.status, new=newStatus)) elif self.status == Alert.Status.PAUSED: if newStatus == Alert.Status.PAUSED: # No change. pass elif newStatus == Alert.Status.STOPPED: self.notifyAlertReset() self.notifyAlertStopped() elif newStatus == Alert.Status.ACTIVE: self.notifyAlertResumed() if not joiningSensors: logger.reportError( 'A sensor should have joined the alert.') else: self.notifySensorJoined() self.notifyAlertActivated() elif self.status == Alert.Status.INITIALIZING: if newStatus == Alert.Status.INITIALIZING: # No change. pass elif newStatus == Alert.Status.ACTIVE: # Events to raise: started, sensor-joined, activated. if not joiningSensors: logger.reportError( 'A sensor should have joined the alert.') else: self.notifySensorJoined() self.notifyAlertActivated() elif newStatus == Alert.Status.STOPPED: self.notifyAlertAborted() self.notifyAlertStopped() # Stop obsolete timers for all sensors related to this alert. if newStatus in (Alert.Status.PAUSED, Alert.Status.STOPPED) or ( self.status == Alert.Status.INITIALIZING and newStatus == Alert.Status.ACTIVE): for sensor in self.sensors: # Get the optional timer currently running for this sensor. timer = self._sensorTimers.get(sensor) if timer == None: continue timer.stop() timer = None del self._sensorTimers[sensor] for sensor in self._sensorsInAlert.union(self._sensorsInPrealert): # Start a new timer? if newStatus in (Alert.Status.INITIALIZING, Alert.Status.ACTIVE ) and self._sensorTimers.get(sensor) == None: timer = sensor.makePrealertTimer( ) if newStatus == Alert.Status.INITIALIZING else sensor.makeAlertTimer( ) self._sensorTimers[ sensor] = timer # Prealert timer has been deleted above if applicable. timer.start() # Update persistence objects for all sensors. for s in self._sensorsInAlert: if s.persistenceObject != None: s.persistenceObject.value = True # Store current status. self._sensorsInAlertOnLastUpdateStatus = self._sensorsInAlert.copy() self.status = newStatus self.isStatusDirty = False