Example #1
0
    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.")
Example #2
0
 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
Example #3
0
    def waitForData(self, endChar=chr(4)):
        # Wait for a connection.
        try:
            conn, address = self._socket.accept()
        except socket.timeout:
            return (None, None)

        # Read incoming data.
        data = b''
        while True:
            try:
                chunk = conn.recv(4)
                endCharIx = chunk.find(endChar)
                isLastChunk = endCharIx != -1
                if isLastChunk:
                    chunk = chunk[:endCharIx]
                data += chunk
                if isLastChunk:
                    break
            except:
                logger.reportException(
                    'Exception when waiting for incoming data.')
                try:
                    if not conn is None: conn.close()
                except:
                    logger.reportException(
                        'Could not close connection. Connection is discarded and process continues.'
                    )
                    pass
                return None, None
        return (data, conn)
Example #4
0
    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'))
Example #5
0
    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.')
Example #6
0
    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
Example #7
0
 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
Example #8
0
 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.')
Example #9
0
    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
Example #10
0
        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
Example #11
0
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))
Example #12
0
        raise Exception('Malformed value for ' + option +'. Expecting a tuple (hostname:port)')
    return (addrStr[0:ix], int(addrStr[ix + 1:]))

def makeArgumentParser(description):
    parser = argparse.ArgumentParser(description=description)
    parser.add_argument('-c', '--comm-addr', dest='communicatorAddress', help='Address of the communicator. This argument must specify the hostname or the ip address followed by a colon and the port to listen on. Default is "localhost:1029"', default='localhost:1029')
    parser.add_argument('-l', '--linknx-addr', dest='linknxAddress', help='Address of the linknx server to bind to. This argument must specify the hostname or the ip address followed by a colon and the port. Default is "localhost:1028"', default='localhost:1028')
    parser.add_argument('userFile', help='use FILE as the user python script that implements callbacks functions declared in the linknx configuration (see the pyknxcallback attributes in XML).', metavar='FILE')
    parser.add_argument('--log-file', dest='logFile', help='write communicator\'s output to FILE rather than to standard output.', metavar='FILE', default=None)
    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='writes the PID of the daemon process to PIDFILE.', metavar='PIDFILE')
    parser.add_argument('-v', '--verbose', dest='verbosityLevel', help='set verbosity level. Default is "error".', metavar='LEVEL', choices=[l.lower() for l in logger.getLevelsToString()], default='error')
    return parser

if __name__ == '__main__':
    parser = makeArgumentParser(__doc__)
    args = parser.parse_args()

    # Configure logger.
    logger.initLogger(None, args.verbosityLevel.upper())

    args.communicatorAddress = parseAddress(args.communicatorAddress, 'communicator address')

    try:
        Communicator.run(args.linknxAddress, args.userFile, args.communicatorAddress, logFile=args.logFile, verbosityLevel=args.verbosityLevel, daemonizes=args.daemonize, pidFile=args.pidFile)
    except SystemExit:
        # This is a normal exit.
        pass
    except:
        logger.reportException()
Example #13
0
import argparse
import sys
import logging
import os
from pyknx import logger

__doc__ = __doc__.format(scriptname=os.path.basename(__file__))

if __name__ == '__main__':
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument('linknxConfig', help='use LKNCONF as the source linknx configuration.', metavar='LKNCONF')
    parser.add_argument('-i', '--input-file', dest='homewatcherConfig', help='read homewatcher configuration from HWCONF rather than from standard input.', metavar='HWCONF')
    parser.add_argument('-o', '--output-file', dest='outputFile', help='write the modified linknx configuration to FILE rather than to standard output.', metavar='FILE')
    parser.add_argument('-v', '--verbose', dest='verbosityLevel', help='set verbosity level.', metavar='LEVEL', choices=[l.lower() for l in logger.getLevelsToString()], default='error')
    args = parser.parse_args()

    # Configure logger.
    logger.initLogger(None, args.verbosityLevel.upper())

    # Start configurator.
    configurator = Configurator(args.homewatcherConfig, args.linknxConfig, args.outputFile)

    # Generate config.
    try:
        configurator.cleanConfig()
        configurator.generateConfig()
        configurator.writeConfig()
    except:
        logger.reportException()
        sys.exit(2)
Example #14
0
def handleRequest(requestType, doc):
    logger.initLogger(None, logging.INFO, usesDetailedLogging=False)

    parser = argparse.ArgumentParser(description=doc)
    parser.add_argument(
        '-s',
        '--server',
        dest='host',
        help=
        'Hostname of the machine running the linknx daemon. Default is localhost.',
        default='localhost')
    parser.add_argument('-p',
                        '--port',
                        dest='port',
                        help='Port linknx listens on. Default is 1028.',
                        default=1028)
    if requestType == 'read':
        parser.add_argument(
            'objectIds',
            help='ID represents the identifier of the object to read.',
            metavar='ID',
            nargs='+')
        parser.add_argument(
            '-R',
            '--regex',
            action='store_true',
            help=
            'ID in the "object" argument is interpreted as a regex and used to find objects to read. The pattern must comply with the \'re\' python module.'
        )
        parser.add_argument(
            '--value-only',
            action='store_true',
            help=
            'Output the value of the queried object but do not prefix it with the object\'s id.'
        )
        parser.add_argument(
            '--expected-value',
            help=
            'Expected value of the object. This script will exit with a non-zero return code if the value is not the expected one. This is useful when using this script in a "if" test of a shell script.'
        )
    elif requestType == 'write':
        parser.add_argument(
            'object',
            help='ID represents the identifier of the object to write to.',
            metavar='ID')
        parser.add_argument(
            'value',
            help='Assigns VALUE to the object identified by ID.',
            metavar='VALUE')
    elif requestType == 'execute':
        parser.add_argument(
            '--action',
            help=
            'use the ACTION string as the XML representation of the action to execute rather than reading it from standard input.',
            metavar='ACTION')
    else:
        raise Exception('Unsupported request type "{0}".'.format(requestType))
    parser.add_argument(
        '-v',
        '--verbose',
        dest='verbosityLevel',
        help='Set verbosity level. Default is "error".',
        metavar='LEVEL',
        choices=[l.lower() for l in logger.getLevelsToString()],
        default='error')
    args = parser.parse_args()

    # Configure logger.
    logger.initLogger(None, args.verbosityLevel.upper())

    # Start linknx.
    linknx = Linknx(args.host, int(args.port))
    try:
        if requestType == 'read':
            objects = linknx.getObjects(
                objectIds=args.objectIds
            ) if not args.regex else linknx.getObjects(patterns=args.objectIds)

            # No object.
            if not objects:
                logger.reportWarning('No such object.')
                sys.exit(10)

            # Count tabs to align columns.
            report = objects.getValues()
            longestId = max([len(obj) for obj in report.keys()])
            succeeds = True
            for o in sorted(report):
                v = report[o]
                spaceCount = longestId - len(o)
                spaces = ''
                while spaceCount > 0:
                    spaces += ' '
                    spaceCount -= 1
                if args.value_only:
                    print('{0}'.format(v))
                else:
                    print('{0} {2} {1}'.format(o, v, spaces))

                if args.expected_value != None:
                    obj = linknx.getObject(o)
                    convertedExpectedValue = obj.convertValueToString(
                        args.expected_value)
                    convertedObjectValue = obj.convertValueToString(v)
                    succeeds = succeeds and convertedExpectedValue == convertedObjectValue

            if not succeeds: exit(100)

        elif requestType == 'write':
            linknx.getObject(args.object).value = args.value
        elif requestType == 'execute':
            if args.action == None:
                action = ''.join(sys.stdin.readlines())
            else:
                action = args.action
            linknx.executeAction(action)
        else:
            raise Exception('Unsupported request type.')

    except Exception as e:
        logger.reportException()
        sys.exit(3)
Example #15
0
    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
Example #16
0
def handleRequest(requestType, doc):
    logger.initLogger(None, logging.INFO, usesDetailedLogging=False)

    parser = argparse.ArgumentParser(description=doc)
    parser.add_argument('-s', '--server', dest='host', help='Hostname of the machine running the linknx daemon. Default is localhost.', default='localhost')
    parser.add_argument('-p', '--port', dest='port', help='Port linknx listens on. Default is 1028.', default=1028)
    if requestType == 'read':
        parser.add_argument('objectIds', help='ID represents the identifier of the object to read.', metavar='ID', nargs='+')
        parser.add_argument('-R', '--regex', action='store_true', help='ID in the "object" argument is interpreted as a regex and used to find objects to read. The pattern must comply with the \'re\' python module.')
        parser.add_argument('--value-only', action='store_true', help='Output the value of the queried object but do not prefix it with the object\'s id.')
        parser.add_argument('--expected-value', help='Expected value of the object. This script will exit with a non-zero return code if the value is not the expected one. This is useful when using this script in a "if" test of a shell script.')
    elif requestType == 'write':
        parser.add_argument('object', help='ID represents the identifier of the object to write to.', metavar='ID')
        parser.add_argument('value', help='Assigns VALUE to the object identified by ID.', metavar='VALUE')
    elif requestType == 'execute':
        parser.add_argument('--action', help='use the ACTION string as the XML representation of the action to execute rather than reading it from standard input.', metavar='ACTION')
    else:
        raise Exception('Unsupported request type "{0}".'.format(requestType))
    parser.add_argument('-v', '--verbose', dest='verbosityLevel', help='Set verbosity level. Default is "error".', metavar='LEVEL', choices=[l.lower() for l in logger.getLevelsToString()], default='error')
    args = parser.parse_args()

    # Configure logger.
    logger.initLogger(None, args.verbosityLevel.upper())

    # Start linknx.
    linknx = Linknx(args.host, int(args.port))
    try:
        if requestType == 'read':
            objects = linknx.getObjects(objectIds=args.objectIds) if not args.regex else linknx.getObjects(patterns=args.objectIds)

            # No object.
            if not objects:
                logger.reportWarning('No such object.')
                sys.exit(10)

            # Count tabs to align columns.
            report = objects.getValues()
            longestId = max([len(obj) for obj in report.keys()])
            succeeds = True
            for o in sorted(report):
                v = report[o]
                spaceCount = longestId - len(o)
                spaces=''
                while spaceCount > 0:
                    spaces+=' '
                    spaceCount -= 1
                if args.value_only:
                    print('{0}'.format(v))
                else:
                    print('{0} {2} {1}'.format(o, v, spaces))

                if args.expected_value != None:
                    obj = linknx.getObject(o)
                    convertedExpectedValue = obj.convertValueToString(args.expected_value)
                    convertedObjectValue = obj.convertValueToString(v)
                    succeeds = succeeds and convertedExpectedValue == convertedObjectValue

            if not succeeds: exit(100)

        elif requestType == 'write':
            linknx.getObject(args.object).value = args.value
        elif requestType == 'execute':
            if args.action == None:
                action = ''.join(sys.stdin.readlines())
            else:
                action = args.action
            linknx.executeAction(action)
        else:
            raise Exception('Unsupported request type.')

    except Exception as e:
        logger.reportException()
        sys.exit(3)