def cleanConfig(self): callbackAttributeName = self.callbackAttributeName for objectXmlConfig in self.config.getElementsByTagName('object'): if objectXmlConfig.hasAttribute(callbackAttributeName): logger.reportInfo('Removed callback {0} for {1}'.format(objectXmlConfig.getAttribute(callbackAttributeName), objectXmlConfig.getAttribute('id'))) objectXmlConfig.removeAttribute(callbackAttributeName) pyknx.configurator.Configurator.cleanConfig(self)
def addSensorToAlert(self, sensor): if self.isInhibited: logger.reportInfo( '{0} will not join {1} since alert is currently inhibited (cf value of {2}).' .format(sensor, self, self.inhibitionObject)) return with self._lock: logger.reportInfo('Sensor {0} joins {1}'.format(sensor, self)) # Decide whether sensor should go through an initial prealert state. if self.status in (Alert.Status.STOPPED, Alert.Status.INITIALIZING): self._sensorsInPrealert.add(sensor) self.invalidateStatus() elif self.status in (Alert.Status.PAUSED, Alert.Status.ACTIVE): if not sensor in self._sensorsInAlert: # Sensor joins the alert. self._sensorsInAlert.add(sensor) self.invalidateStatus() else: # Sensor is retriggered during its alert. Extend alert # duration. self._sensorTimers[sensor].extend() self.updateStatus()
def _updateModeFromLinknx(self): """ Update integral mode to reflect the current mode in linknx. """ with self.suspendAlertStatusUpdates(): modeValue = self.modeValue newMode = self.getMode(modeValue) if self._isTerminated: self.disableAllSensors() return # Notify mode change. hasModeChanged = self._currentMode == None or self._currentMode != newMode if not hasModeChanged: self._currentMode = newMode return # Mode left event. if self._currentMode != None: self._currentMode.notifyLeft() self._currentMode = newMode logger.reportInfo('Current alarm mode is now {0}'.format(self._currentMode)) # Update sensors enabled state. for sensor in self.sensors: if sensor.isRequiredByCurrentMode(): if not sensor.isEnabled: sensor.startActivationTimer() else: sensor.stopActivationTimer() # Issue 23: to help prevent data race with the activation timer. sensor.isEnabled = False # Mode entered event. if self._currentMode != None: self._currentMode.notifyEntered()
def setUp(self, linknxConfFile='linknx_test_conf.xml', usesCommunicator=True, hwConfigFile=os.path.join(os.path.dirname(__file__), 'homewatcher_test_conf.xml')): usesLinknx = linknxConfFile != None communicatorAddress = ('localhost', 1031) if usesCommunicator else None userScript = os.path.join(os.path.dirname(configuration.__file__), 'linknxuserfile.py') userScriptArgs = {'hwconfig':hwConfigFile} try: if usesCommunicator: linknxPatchedFile = tempfile.mkstemp(suffix='.xml', text=True)[1] hwConfigurator = configurator.Configurator(hwConfigFile, linknxConfFile, linknxPatchedFile) hwConfigurator.generateConfig() hwConfigurator.writeConfig() else: linknxPatchedFile = None base.WithLinknxTestCase.setUp(self, linknxConfFile=linknxPatchedFile, communicatorAddr=communicatorAddress, patchLinknxConfig=False, userScript=userScript, userScriptArgs=userScriptArgs) finally: if linknxPatchedFile is not None: os.remove(linknxPatchedFile) self.homewatcherScriptsDirectory = os.path.normpath(os.path.join(os.path.dirname(__file__), '..', '..')) self.homewatcherModulesDirectory = os.path.normpath(os.path.join(os.path.dirname(__file__), '..')) try: # Redirect the emailing capability of the daemon. if self.alarmDaemon: logger.reportInfo('Redirecting email capability of the alarm daemon to the mock for testing.') self.alarmDaemon.sendEmail = self.sendEmailMock else: logger.reportInfo('No alarm daemon. Email redirection is not set.') self.emailInfo = None except: logger.reportException('Error in setUp.') self.tearDown() self.fail('Test setup failed.') raise
def addCallbackForObject(self, objectId, callbackName, callbackDestination): if objectId == None or objectId == '': logger.reportWarning( '{0} is not defined, skipping callback.'.format( callbackDestination)) return # Search object in config. found = False callbackAttributeName = self.callbackAttributeName for objectXmlConfig in self.config.getElementsByTagName('object'): if objectXmlConfig.getAttribute('id') == objectId: if found: raise Exception( 'Two objects with id {id} found.'.format(id=objectId)) found = True objectXmlConfig.setAttribute(callbackAttributeName, callbackName) logger.reportInfo('Added callback {0} for {1}'.format( callbackName, objectId)) if not found: raise Exception( 'Object {id} not found in linknx configuration'.format( id=objectId))
def testModeChange(self): """ Test that merely exercises the everyday changing of mode. Nothing special here, we just switch from one mode to another to check that sensors are activated/deactivated as expected. """ daemon = self.alarmDaemon # Check mode objects are properly set up. for modeName in ('Presence', 'Away', 'Night'): mode = daemon.getMode(modeName) self.assertEqual(len(mode.eventManager.eventConfigs), 2) self.assertEqual(mode.eventManager.eventConfigs[0].type, 'entered') self.assertEqual(len(mode.eventManager.eventConfigs[0].actions), 2) self.assertEqual(mode.eventManager.eventConfigs[0].actions[0].type, 'send-email') self.assertEqual(mode.eventManager.eventConfigs[1].type, 'left') self.assertEqual(len(mode.eventManager.eventConfigs[1].actions), 1) # Prepare sensors involved in this test. entranceDoor = daemon.getSensorByName('EntranceDoorOpening') livingWindow = daemon.getSensorByName('LivingRoomWindowOpening') kitchenSmoke = daemon.getSensorByName('KitchenSmokeSensor') bedroomSmoke = daemon.getSensorByName('BedroomSmokeSensor') # Initialize state to a known one. self.alarmModeObject.value = 1 livingWindow.watchedObject.value = True # Open window. self.waitDuring(1, 'Initializing') modeChangeTime = time.time() logger.reportInfo('\n\n********* SWITCH TO MODE: AWAY ********************') self.emailInfo = None # In case mode initialization has raised an email. self.changeAlarmMode('Away', '*****@*****.**') # Check smoke detectors are immediately active. self.waitDuring(1, 'Waiting for some sensors to be enabled.') for sensor in (kitchenSmoke, bedroomSmoke): self.assertTrue(sensor.isEnabled, '{0} should now be enabled.'.format(sensor)) # Check entrance door is not immediately active. self.waitUntil(modeChangeTime + 5.5, 'Consuming activation delay for {0}'.format(entranceDoor), [lambda: self.assertFalse(entranceDoor.isEnabled)], 0, 0.5) self.assertTrue(entranceDoor.isEnabled) # Check living window is not active since it is triggered. self.assertTrue(livingWindow.isTriggered, '{0} has been manually triggered at the beginning of the test, it should still be so.'.format(livingWindow)) self.assertFalse(livingWindow.isEnabled, '{0} should not be enabled since it is triggered.'.format(livingWindow)) # Close window and check that it becomes enabled in a short time. livingWindow.watchedObject.value = False self.waitDuring(1.2, 'Waiting for {0} to be enabled.'.format(livingWindow), [lambda: self.assertFalse(livingWindow.isEnabled)], 0, 0.2) self.assertTrue(livingWindow.isEnabled, '{0} should now be enabled.'.format(livingWindow)) logger.reportInfo('\n\n************ SWITCH TO MODE: PRESENCE **********************') self.changeAlarmMode('Presence', '*****@*****.**') # Check all sensors are now inactive. def checkAllSensorsEnabledState(): for s in daemon.getAlertByName('Intrusion').sensors: self.assertFalse(s.isEnabled, '{0} should now be disabled.'.format(s)) self.waitDuring(4, 'Let little time go to test sensors\' state in the long run.', [checkAllSensorsEnabledState])
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 testValues(objectId, values): logger.reportInfo('Testing {0} with values {1}'.format(objectId, values)) for value in values: obj = self.linknx.getObject(objectId) assignedValue = value if not isinstance(value, tuple) else value[0] readValue = value if not isinstance(value, tuple) else value[1] obj.value = assignedValue self.assertEqual(obj.value, readValue)
def writeConfig(self): if self._outputFile != None: outputXMLFile = codecs.open(self._outputFile, mode='w', encoding='utf-8') outputXMLFile.write(self.config.toxml()) outputXMLFile.close() logger.reportInfo('Output config written to ' + self._outputFile) else: print(self.config.toxml())
def startActivationTimer(self): # Already enabled. if self.isEnabled: return if self.isActivationPending(): logger.reportInfo('An activation timer for {0} is already running. Cancel it and start a new one.'.format(self)) self._activationTimer.stop() self._activationTimer = timer.Timer(self, self.getActivationDelay(), 'Activation timer', onTimeoutReached=self._onActivationTimerTimeout, onIterate=self._onActivationTimerIterate) self._activationTimer.start()
def _getOrAddConfigElement(self, parent, elementTagName): elementNodes = parent.getElementsByTagName(elementTagName) if not elementNodes: elementNode = parent.ownerDocument.createElement(elementTagName) parent.appendChild(elementNode) logger.reportInfo('No <' + elementTagName + '> element in config, creating one.') else: elementNode = elementNodes[0] return elementNode
def cleanConfig(self): callbackAttributeName = self.callbackAttributeName for objectXmlConfig in self.config.getElementsByTagName('object'): if objectXmlConfig.hasAttribute(callbackAttributeName): logger.reportInfo('Removed callback {0} for {1}'.format( objectXmlConfig.getAttribute(callbackAttributeName), objectXmlConfig.getAttribute('id'))) objectXmlConfig.removeAttribute(callbackAttributeName) pyknx.configurator.Configurator.cleanConfig(self)
def testDocumentationSamples(self): allAObjects = self.linknx.getObjects('A.*') logger.reportInfo(str(allAObjects)) self.assertEqual(len(allAObjects), 3) for objectId, value in allAObjects.getValues().items(): self.assertIn(objectId, [ 'Angle Unsigned Byte', 'Ascii String14', 'Extended Ascii String14' ])
def _onActivationTimerIterate(self, timer): if self.activationCriterion != None and not self.activationCriterion.isValid(): if not timer.isPaused: logger.reportInfo('Pausing activation timer for {0} because activation criterion is not satisfied.'.format(self)) timer.pause() else: if timer.isPaused: # Restart activation delay. logger.reportInfo('Restarting activation timer for {0} because activation criterion is now satisfied.'.format(self)) timer.reset()
def testValues(objectId, values): logger.reportInfo('Testing {0} with values {1}'.format( objectId, values)) for value in values: obj = self.linknx.getObject(objectId) assignedValue = value if not isinstance(value, tuple) else value[0] readValue = value if not isinstance(value, tuple) else value[1] obj.value = assignedValue self.assertEqual(obj.value, readValue)
def setUp(self): self.name = self.id()[len(self.__module__) + 1:] logFile = 'test_files/{0}.log'.format(self.name) if os.path.exists(logFile): os.remove(logFile) logger.initLogger((logFile, logging.DEBUG), logging.INFO) logger.reportInfo('*******Start {0}*************'.format(self.name)) self.currentAssertions = [] self.pyknxScriptsDirectory = os.path.normpath(os.path.join(os.path.dirname(__file__), '..', '..')) self.pyknxModulesDirectory = os.path.normpath(os.path.join(os.path.dirname(__file__), '..'))
def setUp(self): self.name = self.id()[len(self.__module__) + 1:] logFile = 'test_files/{0}.log'.format(self.name) if os.path.exists(logFile): os.remove(logFile) logger.initLogger((logFile, logging.DEBUG), logging.INFO) logger.reportInfo('*******Start {0}*************'.format(self.name)) self.currentAssertions = [] self.pyknxScriptsDirectory = os.path.normpath( os.path.join(os.path.dirname(__file__), '..', '..')) self.pyknxModulesDirectory = os.path.normpath( os.path.join(os.path.dirname(__file__), '..'))
def setUp(self, linknxConfFile='linknx_test_conf.xml', usesCommunicator=True, hwConfigFile=os.path.join(os.path.dirname(__file__), 'homewatcher_test_conf.xml')): usesLinknx = linknxConfFile != None communicatorAddress = ('localhost', 1031) if usesCommunicator else None userScript = os.path.join(os.path.dirname(configuration.__file__), 'linknxuserfile.py') userScriptArgs = {'hwconfig': hwConfigFile} try: if usesCommunicator: linknxPatchedFile = tempfile.mkstemp(suffix='.xml', text=True)[1] hwConfigurator = configurator.Configurator( hwConfigFile, linknxConfFile, linknxPatchedFile) hwConfigurator.generateConfig() hwConfigurator.writeConfig() else: linknxPatchedFile = None base.WithLinknxTestCase.setUp(self, linknxConfFile=linknxPatchedFile, communicatorAddr=communicatorAddress, patchLinknxConfig=False, userScript=userScript, userScriptArgs=userScriptArgs) finally: if linknxPatchedFile is not None: os.remove(linknxPatchedFile) self.homewatcherScriptsDirectory = os.path.normpath( os.path.join(os.path.dirname(__file__), '..', '..')) self.homewatcherModulesDirectory = os.path.normpath( os.path.join(os.path.dirname(__file__), '..')) try: # Redirect the emailing capability of the daemon. if self.alarmDaemon: logger.reportInfo( 'Redirecting email capability of the alarm daemon to the mock for testing.' ) self.alarmDaemon.sendEmail = self.sendEmailMock mock = TestCaseBase.ExecuteActionMock(self.alarmDaemon.linknx, self) self.alarmDaemon.linknx.executeAction = mock.executeAction else: logger.reportInfo( 'No alarm daemon. Email redirection is not set.') self.emailInfo = None self.shellCmdInfo = None except: logger.reportException('Error in setUp.') self.tearDown() self.fail('Test setup failed.') raise
def setUp(self, linknxConfFile='linknx_test_conf.xml', communicatorAddr=('localhost', 1031), patchLinknxConfig=True, userScript='linknxuserfile.py', userScriptArgs=None): TestCaseBase.setUp(self) self.linknxProcess = None self.linknx = None self.communicator = None self.linknxOutputFDs = None self.linknxXMLConfig = linknxConfFile self.communicatorAddress = communicatorAddr try: # Patch config. if self.linknxXMLConfig != None: testDir = 'test_files' if not os.path.exists(testDir): os.mkdir(testDir) if self.communicatorAddress != None and patchLinknxConfig: linknxPatchedConfigFile = os.path.join(testDir, 'autogenlinknx.conf.xml') if os.path.exists(linknxPatchedConfigFile): os.remove(linknxPatchedConfigFile) self.configurator = configurator.Configurator(self.linknxXMLConfig, linknxPatchedConfigFile, self.communicatorAddress) self.configurator.cleanConfig() self.configurator.generateConfig() self.configurator.writeConfig() else: linknxPatchedConfigFile = self.linknxXMLConfig # Start linknx. linknxErrFilename = 'test_files/{0}.linknx.err'.format(self.name) linknxOutFilename = 'test_files/{0}.linknx.out'.format(self.name) efdw = open(linknxErrFilename, 'w') efdr = open(linknxErrFilename, 'r') ofdw = open(linknxOutFilename, 'w') self.linknxOutputFDs = (efdr, efdw, ofdw) self.linknxProcess = subprocess.Popen( ['linknx', '--config={0}'.format(linknxPatchedConfigFile)], stdout=self.linknxOutputFDs[2], stderr=self.linknxOutputFDs[1]) logger.reportInfo('linknx started with pid {0}'.format(self.linknxProcess.pid)) self.linknx = linknx.Linknx('localhost', 1030) self.currentAssertions.append(self.checkLinknx) # Start pyknx. if self.communicatorAddress != None: self.communicator = communicator.Communicator(self.linknx, userScript, self.communicatorAddress, userScriptArgs) self.communicator.startListening() else: self.communicator = None logger.reportInfo('Set up finished.') self.waitDuring(0.5, 'Pause to make sure everything gets ready.') except: logger.reportException('Error in setUp.') self.tearDown() self.fail('Test setup failed.') raise
def notifyWatchedObjectChanged(self): """ Notifies the sensor that its watched object's value has just changed. """ newTriggeredState = self.getUpdatedTriggerState() # Depends on the concrete sensor class. Most of them will do nothing as trigger state IS the watched object state. But for FloatSensor for instance, trigger may take an hysteresis into account. if self._isTriggered == newTriggeredState: return self._isTriggered = newTriggeredState if self.isTriggered: logger.reportInfo('{0} is triggered.'.format(self.name)) if self.isEnabled: self.alert.addSensorToAlert(self) else: # Nothing to do regarding alert here. If sensor was previously # triggered, alert will not end by simply releasing trigger. logger.reportInfo('{0}\'s trigger is released.'.format(self.name))
def initializeUserScript(context): global alarmDaemon logger.reportInfo('hw config is {0}'.format(context.hwconfig)) # motionConfigDir = context.customArgs.get('motionconfigdir') # motionOutputDir = context.customArgs.get('motionoutputdir') if isinstance(context.hwconfig, str): config = configuration.Configuration.parseFile(context.hwconfig) elif isinstance(context.hwconfig, configuration.Configuration): config = context.hwconfig else: raise Exception('The hwconfig argument must be either a string or a homewatcher.configuration.Configuration object. "{0}" was passed.'.format(type(context.hwconfig))) # Instanciate daemon. alarmDaemon = alarm.Daemon(context.communicator, config)
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 _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 waitUntil(self, endTime, reason, assertions=[], assertStartMargin=0, assertEndMargin=0): state = 0 # 0: undefined, 1: before assertions, 2: during assertions, 3: after assertions with AssertionsHandle(self, assertions): startTime = time.time() assertStart = startTime + assertStartMargin assertEnd = endTime - assertEndMargin while time.time() < endTime: currentTime = time.time() if currentTime < assertStart and assertStartMargin != 0: newState = 1 status = 'before assertions' duration = assertStart - currentTime elif currentTime > assertEnd and assertEndMargin != 0: newState = 3 status = 'after assertions' duration = endTime - currentTime else: newState = 2 status = 'with assertions' if assertions else 'no assertions' duration = assertEnd - currentTime # Notify progress. if newState != state: state = newState logger.reportInfo('{0} ({2}) (during {1} seconds)'.format( reason, round(duration, 1), status)) # Check all pending assertions. for assertion in self.currentAssertions: try: if state == 2: assertion() except: logger.reportInfo( 'Exception caught in waitUntil after {0}s'.format( time.time() - startTime)) raise # Sleep until optimal end of iteration. time.sleep(0.1)
def tearDown(self): logger.reportInfo('Tearing down...') if self.communicator: logger.reportInfo('Stopping communicator...') self.communicator.stopListening() logger.reportInfo('communicator is stopped.') if self.linknxProcess: self.linknxProcess.kill() logger.reportInfo('linknx is stopped.') if self.linknxOutputFDs: self.linknxOutputFDs[0].close() self.linknxOutputFDs[1].close() self.linknxOutputFDs[2].close() TestCaseBase.tearDown(self) logger.reportInfo('*******End of {0}*************\n\n\n'.format(self.name))
def addCallbackForObject(self, objectId, callbackName, callbackDestination): if objectId == None or objectId == '': logger.reportWarning('{0} is not defined, skipping callback.'.format(callbackDestination)) return # Search object in config. found = False callbackAttributeName = self.callbackAttributeName for objectXmlConfig in self.config.getElementsByTagName('object'): if objectXmlConfig.getAttribute('id') == objectId: if found: raise Exception('Two objects with id {id} found.'.format(id=objectId)) found = True objectXmlConfig.setAttribute(callbackAttributeName, callbackName) logger.reportInfo('Added callback {0} for {1}'.format(callbackName, objectId)) if not found: raise Exception('Object {id} not found in linknx configuration'.format(id=objectId))
def testTimer(self): class TimerStatus: def __init__(self): self.isTimeoutReached = False self.isTerminated = False def onTimeout(self, timer): self.isTimeoutReached = True def onTerminated(self, timer): logger.reportInfo('onTerminated') self.isTerminated = True status = TimerStatus() timer = Timer(None, 2, 'Test timer', onTimeoutReached=status.onTimeout, onTerminated=status.onTerminated) timer.start() self.waitDuring(2.1, 'Waiting for test timer to complete', assertions=[lambda: self.assertFalse(status.isTimeoutReached or status.isTerminated)], assertEndMargin=0.2) logger.reportInfo('isTimeoutReached={isTimeoutReached}, isTerminated={isTerminated}'.format(isTimeoutReached=status.isTimeoutReached, isTerminated=status.isTerminated)) self.assertTrue(status.isTimeoutReached and status.isTerminated)
def tearDown(self): logger.reportInfo('Tearing down...') if self.communicator: logger.reportInfo('Stopping communicator...') self.communicator.stopListening() logger.reportInfo('communicator is stopped.') if self.linknxProcess: self.linknxProcess.kill() logger.reportInfo('linknx is stopped.') if self.linknxOutputFDs: self.linknxOutputFDs[0].close() self.linknxOutputFDs[1].close() self.linknxOutputFDs[2].close() TestCaseBase.tearDown(self) logger.reportInfo('*******End of {0}*************\n\n\n'.format( self.name))
def stopListening(self): """ Stop communicator. No new incoming connection will be possible. """ # Notify user script first. This allows linknx to notify a few object # changes before communicator really stop listening. if self._userFile and self.isUserScriptInitialized: self._executeUserCallback('finalizeUserScript', CallbackContext(self), True) logger.reportInfo('User script finalized.') if not self.isListening: return self._listenerThread.stop() # Wait for listener thread to end (to be sure that no callback # request originating from linknx can reach the user script anymore). while not self._listenerThread.isStopped: time.sleep(0.5) self._listenerThread = None if self._userFile: self._executeUserCallback('endUserScript', CallbackContext(self), True) logger.reportInfo('User script ended.')
def waitForRemoteConnectionReady(self): """ Wait for Linknx's XML server to accept incoming connections. This method should be called if unsure about when the Linknx has been started. It may take some time to get ready. This method attempts to connect to Linknx during 10 seconds and raises an Exception if Linknx still is unreachable after this delay. """ # Ask for config. logger.reportInfo("Start connecting to linknx on {0}.".format(self.address)) attemptId = 0 maxAttemptCount = 10 while attemptId < maxAttemptCount: attemptId += 1 try: conf = self.config # Linknx is ready if we reach this point. logger.reportInfo("Linknx is up and ready, let's start.") return except ConnectionRefusedError: logger.reportInfo("Linknx is not yet ready... (attempt {0}/{1})".format(attemptId, maxAttemptCount)) except Exception as e: logger.reportException() time.sleep(1) raise Exception("Linknx is not reachable.")
def waitForRemoteConnectionReady(self): """ Wait for Linknx's XML server to accept incoming connections. This method should be called if unsure about when the Linknx has been started. It may take some time to get ready. This method attempts to connect to Linknx during 10 seconds and raises an Exception if Linknx still is unreachable after this delay. """ # Ask for config. logger.reportInfo('Start connecting to linknx on {0}.'.format( self.address)) attemptId = 0 maxAttemptCount = 10 while attemptId < maxAttemptCount: attemptId += 1 try: conf = self.config # Linknx is ready if we reach this point. logger.reportInfo('Linknx is up and ready, let\'s start.') return except ConnectionRefusedError: logger.reportInfo( 'Linknx is not yet ready... (attempt {0}/{1})'.format( attemptId, maxAttemptCount)) except Exception as e: logger.reportException() time.sleep(1) raise Exception('Linknx is not reachable.')
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 startListening(self): """ Start the communicator. It is then waiting for incoming information from Linknx. If provided, the initializeUserScript function of the user file is called. Its context contains the arguments that were optionally given to the __init__ method. """ if self.isListening: return # Make sure linknx is ready. self.linknx.waitForRemoteConnectionReady() # Start listening early to avoid communication errors from linknx. Those # errors are never harmful but the user may be surprized and worried # about them! self._listenerThread = Communicator.Listener(self._address, self) self._listenerThread.start() timeout = time.time() + 4 while not self._listenerThread.isReady and time.time() < timeout: time.sleep(0.3) if not self._listenerThread.isReady: raise Exception('Could not initialize listening socket.') # Initialize user-provided script. The purpose of this callback is to # let the user initialize its script by reading state from linknx (and # possibly anywhere else). Thus, linknx should not raise events yet, # since user script would likely be partially initialized. The # isUserScriptInitialized flag is used for that purpose. if self._loadUserFile(): logger.reportInfo('Initializing user script...') try: self._executeUserCallback('initializeUserScript', CallbackContext(self, args=self._userScriptArgs), True) except Exception as e: logger.reportException('User script initialization failed, communicator will stop immediately.') self.stopListening() return logger.reportInfo('User script initialized.') self.isUserScriptInitialized = True
def addSensorToAlert(self, sensor): if self.isInhibited: logger.reportInfo('{0} will not join {1} since alert is currently inhibited (cf value of {2}).'.format(sensor, self, self.inhibitionObject)) return with self._lock: logger.reportInfo('Sensor {0} joins {1}'.format(sensor, self)) # Decide whether sensor should go through an initial prealert state. if self.status in (Alert.Status.STOPPED, Alert.Status.INITIALIZING): self._sensorsInPrealert.add(sensor) self.invalidateStatus() elif self.status in (Alert.Status.PAUSED, Alert.Status.ACTIVE): if not sensor in self._sensorsInAlert: # Sensor joins the alert. self._sensorsInAlert.add(sensor) self.invalidateStatus() else: # Sensor is retriggered during its alert. Extend alert # duration. self._sensorTimers[sensor].extend() self.updateStatus()
def waitUntil(self, endTime, reason, assertions=[], assertStartMargin=0, assertEndMargin=0): state = 0 # 0: undefined, 1: before assertions, 2: during assertions, 3: after assertions with AssertionsHandle(self, assertions): startTime = time.time() assertStart = startTime + assertStartMargin assertEnd = endTime - assertEndMargin while time.time() < endTime: currentTime = time.time() if currentTime < assertStart and assertStartMargin != 0: newState = 1 status = 'before assertions' duration = assertStart - currentTime elif currentTime > assertEnd and assertEndMargin != 0: newState = 3 status = 'after assertions' duration = endTime - currentTime else: newState = 2 status = 'with assertions' if assertions else 'no assertions' duration = assertEnd - currentTime # Notify progress. if newState != state: state = newState logger.reportInfo('{0} ({2}) (during {1} seconds)'.format(reason, round(duration, 1), status)) # Check all pending assertions. for assertion in self.currentAssertions: try: if state == 2: assertion() except: logger.reportInfo('Exception caught in waitUntil after {0}s'.format(time.time() - startTime)) raise # Sleep until optimal end of iteration. time.sleep(0.1)
def _updateModeFromLinknx(self): """ Update integral mode to reflect the current mode in linknx. """ with self.suspendAlertStatusUpdates(): modeValue = self.modeValue newMode = self.getMode(modeValue) if self._isTerminated: self.disableAllSensors() return # Notify mode change. hasModeChanged = self._currentMode == None or self._currentMode != newMode if not hasModeChanged: self._currentMode = newMode return # Mode left event. if self._currentMode != None: self._currentMode.notifyLeft() self._currentMode = newMode logger.reportInfo('Current alarm mode is now {0}'.format( self._currentMode)) # Update sensors enabled state. for sensor in self.sensors: if sensor.isRequiredByCurrentMode(): if not sensor.isEnabled: sensor.startActivationTimer() else: sensor.stopActivationTimer( ) # Issue 23: to help prevent data race with the activation timer. sensor.isEnabled = False # Mode entered event. if self._currentMode != None: self._currentMode.notifyEntered()
def loadPlugins(): global _plugins pluginDirectory = os.path.dirname(__file__) pluginModules = glob.glob(os.path.join(pluginDirectory, '*.py')) # Scan all modules in the 'plugins' subdirectory and instanciate all classes # that inherit Plugin. for moduleFile in pluginModules: if os.path.basename(moduleFile) == '__init__.py': continue module = importlib.import_module('homewatcher.plugins.{0}'.format( os.path.splitext(os.path.basename(moduleFile))[0])) for symbolName in dir(module): symbol = vars(module)[symbolName] if isinstance(symbol, type) and issubclass( symbol, homewatcher.plugin.Plugin): logger.reportInfo('Loading {0}'.format(symbol)) plugin = symbol() try: plugin.load() logger.reportInfo('{0} loaded.'.format(symbol)) except Exception as e: logger.reportException( 'Failed to load plugin {0}'.format(plugin))
def run(self): statusFormat = 'Logger thread for std{0} of {1} PID=<{2}> is {3}.'.format('out' if self.readsStdout else 'err', self.name, self.process.pid, '{0}') logger.reportInfo(statusFormat.format('started')) while self.process.returncode is None: stream = self.process.stdout if self.readsStdout else self.process.stderr line = stream.readline() if line == '': time.sleep(1) continue line = line.rstrip('\n') log = '[{0}.{3} pid={1}] {2}'.format(self.name, self.process.pid, line, 'out' if self.readsStdout else 'err') logger.reportInfo(log) logger.reportInfo(statusFormat.format('terminated'))
def cleanConfig(self): # Delete all pyknx rules before creating only those that apply to the # current config. rulesNode = self._getOrAddConfigElement(self.config, 'rules') prefixLength = len(self._communicatorName) configuredAtLeastOne = False for ruleNode in rulesNode.getElementsByTagName('rule'): ruleId = ruleNode.getAttribute('id') if ruleId[:prefixLength] == self._communicatorName: configuredAtLeastOne = True logger.reportInfo('Clean rule ' + ruleId + ' coming from a previous configure.') rulesNode.removeChild(ruleNode) if not configuredAtLeastOne: logger.reportInfo('Input XML config does not define any pyknx rule. Nothing to clean.') servicesNode = self._getOrAddConfigElement(self.config, 'services') ioportsNode = self._getOrAddConfigElement(servicesNode, 'ioports') for ioportNode in ioportsNode.getElementsByTagName('ioport'): if ioportNode.getAttribute('id') == self._communicatorName: logger.reportInfo('Clean ' + ioportNode.toxml()) ioportsNode.removeChild(ioportNode)
def testPostponedActivation(self): """ Test that exercises the postponing of the activation of a sensor whenever its canEnabled property returns False. """ logger.reportInfo('\n\n*********INITIALIZE testPostponedActivation********************') daemon = self.alarmDaemon # Prepare useful sensors. garageDoor = daemon.getSensorByName('GarageDoorOpening') entranceDoor = daemon.getSensorByName('EntranceDoorOpening') # Initialize state to a known one. self.alarmModeObject.value = 1 # Presence. entranceDoor.watchedObject.value = True # Open entrance. self.waitDuring(1, 'Initialization.') # Go to away mode. self.emailInfo = None # In case mode initialization has raised an email. self.changeAlarmMode('Away', '*****@*****.**') # Neither door nor garage should be active for now. def assertNoAlert(): self.assertAlert(sensorsInPrealert=[], sensorsInAlert=[], sensorsInPersistentAlert=[]) def assertNotEnabled(): self.assertFalse(entranceDoor.isEnabled, '{0} should not be enabled for now since it is open.'.format(entranceDoor)) self.assertFalse(garageDoor.isEnabled, '{0} should not be enabled for now since it depends on the entrance door which is open.'.format(garageDoor)) self.waitDuring(4, 'Wait for a while to make sure neither door nor garage are enabled.', assertions=[assertNoAlert, assertNotEnabled]) # Close door. entranceDoor.watchedObject.value = False # No immediate activation is expected! self.waitDuring(4, 'Wait for a while to make sure doors do not get enabled for now.', assertions=[assertNoAlert, assertNotEnabled]) assertEnabled = lambda sensor, isEnabled, format: self.assertEqual(sensor.isEnabled, isEnabled, format.format(sensor)) assertPaused = lambda sensor, isPaused, format: self.assertEqual(sensor._activationTimer.isPaused, isPaused, format.format(sensor)) disabledFormat = '{0} should not be enabled for now since its activation delay is not over.' enabledFormat = '{0} should be enabled since its activation delay is now over.' pausedFormat = 'Activation timer for {0} should be paused.' runningFormat = 'Activation timer for {0} should be running.' assertEnabled(entranceDoor, False, disabledFormat) assertEnabled(garageDoor, False, disabledFormat) assertPaused(entranceDoor, False, runningFormat) assertPaused(garageDoor, False, runningFormat) # Reopening door should cancel activation. entranceDoor.watchedObject.value = True self.waitDuring(2, 'Let activation timer go to pause.') for i in range(2): assertEnabled(entranceDoor, False, disabledFormat) assertEnabled(garageDoor, False, disabledFormat) assertPaused(entranceDoor, True, pausedFormat) assertPaused(garageDoor, True, pausedFormat) self.waitDuring(2, 'Check that state is stable.') # Close again and wait for activation. doorClosingTime = time.time() entranceDoor.watchedObject.value = False self.waitDuring(1, 'Let linknx handle door close event.') assertEnabled(entranceDoor, False, disabledFormat) assertEnabled(garageDoor, False, disabledFormat) assertPaused(entranceDoor, False, runningFormat) assertPaused(garageDoor, False, runningFormat) # Entrance door gets enabled first. assertions=[lambda: assertEnabled(entranceDoor, False, disabledFormat), lambda: assertEnabled(garageDoor, False, disabledFormat), lambda: assertPaused(entranceDoor, False, runningFormat), lambda: assertPaused(garageDoor, False, runningFormat)] self.waitUntil(doorClosingTime + 5 + 0.5, 'Wait for doors to be enabled.', assertions=assertions, assertStartMargin=0, assertEndMargin=1) assertEnabled(entranceDoor, True, enabledFormat) assertEnabled(garageDoor, True, enabledFormat) self.assertFalse(entranceDoor.isActivationPending(), 'Activation timer should now be released.') self.assertFalse(garageDoor.isActivationPending(), 'Activation timer should now be released.')
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 testAlertLifeCycle(self): logger.reportInfo('\n\n*********INITIALIZE testAlertLifeCycle ********************') # Prepare useful sensors. kitchenWindow = self.alarmDaemon.getSensorByName('KitchenWindowOpening') livingWindow = self.alarmDaemon.getSensorByName('LivingRoomWindowOpening') intrusionAlert = self.alarmDaemon.getAlertByName('Intrusion') # Initialize state to a known one. self.alarmModeObject.value = 1 # Presence. kitchenWindow.watchedObject.value = False livingWindow.watchedObject.value = False self.waitDuring(1, 'Initialization.') # Go to away mode. self.emailInfo = None # In case mode initialization has raised an email. self.changeAlarmMode('Away', '*****@*****.**') # Wait for window activation. self.waitDuring(2, 'Wait for activation of window sensors.') self.assertTrue(kitchenWindow.isEnabled) self.assertTrue(livingWindow.isEnabled) def assertAlertEvents(firedEvents, alertName, resetsToOff=True): eventObjectIds = {} eventObjectIds[configuration.AlertEvent.Type.PREALERT_STARTED] = '{0}AlertStarted'.format(alertName) eventObjectIds[configuration.AlertEvent.Type.ALERT_ACTIVATED] = '{0}AlertActivated'.format(alertName) eventObjectIds[configuration.AlertEvent.Type.ALERT_DEACTIVATED] = '{0}AlertDeactivated'.format(alertName) eventObjectIds[configuration.AlertEvent.Type.ALERT_PAUSED] = '{0}AlertPaused'.format(alertName) eventObjectIds[configuration.AlertEvent.Type.ALERT_RESUMED] = '{0}AlertResumed'.format(alertName) eventObjectIds[configuration.AlertEvent.Type.ALERT_STOPPED] = '{0}AlertStopped'.format(alertName) eventObjectIds[configuration.AlertEvent.Type.ALERT_ABORTED] = '{0}AlertAborted'.format(alertName) eventObjectIds[configuration.AlertEvent.Type.ALERT_RESET] = '{0}AlertReset'.format(alertName) eventObjectIds[configuration.AlertEvent.Type.SENSOR_JOINED] = '{0}SensorJoined'.format(alertName) eventObjectIds[configuration.AlertEvent.Type.SENSOR_LEFT] = '{0}SensorLeft'.format(alertName) eventObjects = self.linknx.getObjects(objectIds=eventObjectIds.values()) eventObjectValues = eventObjects.getValues() # Check all events are in the dictionary. If not, that denotes a # coding error in the test. for eventType in configuration.AlertEvent.Type.getAll(): self.assertTrue(eventType in eventObjectIds) for eventType, eventObjectId in eventObjectIds.items(): eventState = eventObjectValues[eventObjectId] expectedState = eventType in firedEvents self.assertEqual(eventState, expectedState, 'Event {0} should be {1}.\nState of all event objects is following:{2}'.format(eventObjectId, expectedState, eventObjectValues)) if resetsToOff: for eventType, eventObjectId in eventObjectIds.items(): self.linknx.getObject(eventObjectId).value = False # Clear email so that test does not complain about emails not # been treated. self.emailInfo = None self.assertAlert([], [], []) assertAlertEvents([], 'Intrusion') # Prealert. prealertStartTime = time.time() kitchenWindow.watchedObject.value = True self.waitDuring(0.1, "Let 'alert started' event be raised.") assertAlertEvents((configuration.AlertEvent.Type.PREALERT_STARTED,), 'Intrusion') self.waitUntil(prealertStartTime + kitchenWindow.getPrealertDuration() + 0.2, 'Waiting for prealert to expire.', [lambda: self.assertAlert([kitchenWindow],[],[]), lambda: assertAlertEvents([], 'Intrusion', resetsToOff=False)], 0.2, 0.4) kitchenWindow.watchedObject.value = False # Release sensor trigger now to be able to trigger it again in a while. assertAlertEvents((configuration.AlertEvent.Type.SENSOR_JOINED, configuration.AlertEvent.Type.ALERT_ACTIVATED), 'Intrusion') # Alert. self.waitUntil(prealertStartTime + kitchenWindow.getPrealertDuration() + kitchenWindow.getAlertDuration() + 0.8, 'Waiting for alert to expire', [lambda: self.assertAlert([],[kitchenWindow],[kitchenWindow]), lambda: assertAlertEvents([], 'Intrusion')], 0.2, 1.0) assertAlertEvents((configuration.AlertEvent.Type.ALERT_PAUSED, configuration.AlertEvent.Type.SENSOR_LEFT, configuration.AlertEvent.Type.ALERT_DEACTIVATED), 'Intrusion') # Paused. self.assertAlert([], [], [kitchenWindow]) # Resumed. alertResumeTime = time.time() kitchenWindow.watchedObject.value = True self.waitDuring(0.3, 'Waiting for alert to resume', []) kitchenWindow.watchedObject.value = False # Release sensor trigger now to be able to trigger it again in a while. assertAlertEvents((configuration.AlertEvent.Type.ALERT_RESUMED, configuration.AlertEvent.Type.SENSOR_JOINED, configuration.AlertEvent.Type.ALERT_ACTIVATED), 'Intrusion') # Alert. self.waitUntil(alertResumeTime + kitchenWindow.getAlertDuration() + 0.5, 'Waiting for alert to expire', [lambda: self.assertAlert([],[kitchenWindow],[kitchenWindow]), lambda: assertAlertEvents([], 'Intrusion')], 0.2, 0.7) assertAlertEvents((configuration.AlertEvent.Type.ALERT_PAUSED, configuration.AlertEvent.Type.SENSOR_LEFT, configuration.AlertEvent.Type.ALERT_DEACTIVATED), 'Intrusion') # Paused. self.assertAlert([], [], [kitchenWindow]) # Stopped. self.alarmDaemon.getAlertByName('Intrusion').persistenceObject.value = False self.waitDuring(0.4, 'Waiting for alert to stop.') assertAlertEvents((configuration.AlertEvent.Type.ALERT_RESET, configuration.AlertEvent.Type.ALERT_STOPPED), 'Intrusion') # Raise a new alert. Should begin with a prealert. # Prealert. prealertStartTime = time.time() kitchenWindow.watchedObject.value = True self.waitDuring(0.1, "Let 'alert started' event be raised.") assertAlertEvents((configuration.AlertEvent.Type.PREALERT_STARTED,), 'Intrusion') self.waitUntil(prealertStartTime + kitchenWindow.getPrealertDuration() + 0.2, 'Waiting for prealert to expire.', [lambda: self.assertAlert([kitchenWindow],[],[]), lambda: assertAlertEvents([], 'Intrusion', resetsToOff=False)], 0.2, 0.4) kitchenWindow.watchedObject.value = False # Release sensor trigger now to be able to trigger it again in a while. assertAlertEvents((configuration.AlertEvent.Type.SENSOR_JOINED, configuration.AlertEvent.Type.ALERT_ACTIVATED), 'Intrusion') # Alert. Stop it in the middle of the alert to test manual alert # abortion. self.alarmDaemon.getAlertByName('Intrusion').persistenceObject.value = False self.waitDuring(0.4, 'Waiting for alert to stop.') assertAlertEvents((configuration.AlertEvent.Type.SENSOR_LEFT, configuration.AlertEvent.Type.ALERT_DEACTIVATED, configuration.AlertEvent.Type.ALERT_RESET, configuration.AlertEvent.Type.ALERT_STOPPED), 'Intrusion') # Raise a new alert. Should begin with a prealert. # Prealert. prealertStartTime = time.time() kitchenWindow.watchedObject.value = True self.waitDuring(0.1, "Let 'alert started' event be raised.") assertAlertEvents((configuration.AlertEvent.Type.PREALERT_STARTED,), 'Intrusion') prealertDuration = kitchenWindow.getPrealertDuration() self.waitUntil(prealertStartTime + prealertDuration / 2.0, 'Waiting for half of the prealert to expire.', [lambda: self.assertAlert([kitchenWindow],[],[]), lambda: assertAlertEvents([], 'Intrusion', resetsToOff=False)], 0.2, 0.4) # Abort alert before it becomes active. self.changeAlarmMode('Presence', '*****@*****.**') self.alarmDaemon.getAlertByName('Intrusion').persistenceObject.value = False self.waitDuring(prealertDuration + 0.4, 'Waiting for alert to stop.') assertAlertEvents((configuration.AlertEvent.Type.ALERT_ABORTED, configuration.AlertEvent.Type.ALERT_STOPPED), 'Intrusion') # Check the Temperature alert since it has no persistence. temperatureSensor = self.alarmDaemon.getSensorByName('OutdoorTemperature') temperatureSensor.watchedObject.value = 31.0 self.waitDuring(0.8, 'Let the Temperature alert be raised.') assertAlertEvents((configuration.AlertEvent.Type.SENSOR_JOINED, configuration.AlertEvent.Type.SENSOR_LEFT, configuration.AlertEvent.Type.ALERT_DEACTIVATED, configuration.AlertEvent.Type.PREALERT_STARTED, configuration.AlertEvent.Type.ALERT_STOPPED, configuration.AlertEvent.Type.ALERT_RESET, configuration.AlertEvent.Type.ALERT_ACTIVATED), 'Temperature')
def fireEvent(self, eventType): logger.reportInfo('Firing event {0} for {1}'.format(eventType, self)) self.eventManager.fireEvent( eventType, 'Alert {0}: {1}'.format(self.name, eventType), self)
def terminate(self): logger.reportInfo('Terminating homewatcher daemon...') self._isTerminated = True self.disableAllSensors()