def initiateCommunications(self, initializeConnect=True):
		# determine if this device is missing any properties that were added
		# during device/plugin upgrades
		propertiesDictUpdateRequired = False
		pluginPropsCopy = self.indigoDevice.pluginProps
		for newPropertyDefn in self.upgradedDeviceProperties:
			if not (newPropertyDefn[0] in pluginPropsCopy):
				self.hostPlugin.logger.info(u'Triggering property update due to missing device property: ' + RPFrameworkUtils.to_unicode(newPropertyDefn[0]))
				pluginPropsCopy[newPropertyDefn[0]] = newPropertyDefn[1]
				propertiesDictUpdateRequired = True
		if propertiesDictUpdateRequired == True:
			self.indigoDevice.replacePluginPropsOnServer(pluginPropsCopy)
	
		# determine if this device is missing any states that were defined in upgrades
		stateReloadRequired = False
		for newStateName in self.upgradedDeviceStates:
			if not (newStateName in self.indigoDevice.states):
				self.hostPlugin.logger.info(u'Triggering state reload due to missing device state: ' + RPFrameworkUtils.to_unicode(newStateName))
				stateReloadRequired = True	
		if stateReloadRequired == True:
			self.indigoDevice.stateListOrDisplayStateIdChanged();
		
		# start concurrent processing thread by injecting a placeholder
		# command to the queue
		if initializeConnect == True:
			self.queueDeviceCommand(RPFrameworkCommand.RPFrameworkCommand(RPFrameworkCommand.CMD_INITIALIZE_CONNECTION))
	def scheduleReconnectionAttempt(self):
		self.hostPlugin.logger.debug(u'Scheduling reconnection attempt...')
		try:
			self.failedConnectionAttempts = self.failedConnectionAttempts + 1
			maxReconnectAttempts = int(self.hostPlugin.getGUIConfigValue(self.indigoDevice.deviceTypeId, RPFrameworkPlugin.GUI_CONFIG_RECONNECTIONATTEMPT_LIMIT, u'0'))
			if self.failedConnectionAttempts > maxReconnectAttempts:
				self.hostPlugin.logger.debug(u'Maximum reconnection attempts reached (or not allowed) for device ' + RPFrameworkUtils.to_unicode(self.indigoDevice.id))
			else:
				reconnectAttemptDelay = int(self.hostPlugin.getGUIConfigValue(self.indigoDevice.deviceTypeId, RPFrameworkPlugin.GUI_CONFIG_RECONNECTIONATTEMPT_DELAY, u'60'))
				reconnectAttemptScheme = self.hostPlugin.getGUIConfigValue(self.indigoDevice.deviceTypeId, RPFrameworkPlugin.GUI_CONFIG_RECONNECTIONATTEMPT_SCHEME, RPFrameworkPlugin.GUI_CONFIG_RECONNECTIONATTEMPT_SCHEME_REGRESS)
			
				if reconnectAttemptScheme == RPFrameworkPlugin.GUI_CONFIG_RECONNECTIONATTEMPT_SCHEME_FIXED:
					reconnectSeconds = reconnectAttemptDelay
				else:
					reconnectSeconds = reconnectAttemptDelay * self.failedConnectionAttempts
				reconnectAttemptTime = time.time() + reconnectSeconds

				self.hostPlugin.pluginCommandQueue.put(RPFrameworkCommand.RPFrameworkCommand(RPFrameworkCommand.CMD_DEVICE_RECONNECT, commandPayload=(self.indigoDevice.id, self.deviceInstanceIdentifier, reconnectAttemptTime)))
				self.hostPlugin.logger.debug(u'Reconnection attempt scheduled for ' + RPFrameworkUtils.to_unicode(reconnectSeconds) + u' seconds')
		except e:
			self.hostPlugin.logger.error(u'Failed to schedule reconnection attempt to device')			
    def concurrentCommandProcessingThread(self, commandQueue):
        try:
            self.hostPlugin.logger.debug(
                u'Concurrent Processing Thread started for device ' +
                RPFrameworkUtils.to_unicode(self.indigoDevice.id))

            # obtain the IP or host address that will be used in connecting to the
            # RESTful service via a function call to allow overrides
            deviceHTTPAddress = self.getRESTfulDeviceAddress()
            if deviceHTTPAddress is None:
                self.hostPlugin.logger.error(
                    u'No IP address specified for device ' +
                    RPFrameworkUtils.to_unicode(self.indigoDevice.id) +
                    u'; ending command processing thread.')
                return

            # retrieve any configuration information that may have been setup in the
            # plugin configuration and/or device configuration
            updateStatusPollerPropertyName = self.hostPlugin.getGUIConfigValue(
                self.indigoDevice.deviceTypeId,
                GUI_CONFIG_RESTFULSTATUSPOLL_INTERVALPROPERTY,
                u'updateInterval')
            updateStatusPollerInterval = int(
                self.indigoDevice.pluginProps.get(
                    updateStatusPollerPropertyName, u'90'))
            updateStatusPollerNextRun = None
            updateStatusPollerActionId = self.hostPlugin.getGUIConfigValue(
                self.indigoDevice.deviceTypeId,
                GUI_CONFIG_RESTFULSTATUSPOLL_ACTIONID, u'')
            emptyQueueReducedWaitCycles = int(
                self.hostPlugin.getGUIConfigValue(
                    self.indigoDevice.deviceTypeId,
                    GUI_CONFIG_RESTFULDEV_EMPTYQUEUE_SPEEDUPCYCLES, u'80'))

            # spin up the database connection, if this plugin supports databases
            self.dbConn = self.hostPlugin.openDatabaseConnection(
                self.indigoDevice.deviceTypeId)

            # begin the infinite loop which will run as long as the queue contains commands
            # and we have not received an explicit shutdown request
            continueProcessingCommands = True
            lastQueuedCommandCompleted = 0
            while continueProcessingCommands == True:
                # process pending commands now...
                while not commandQueue.empty():
                    lenQueue = commandQueue.qsize()
                    self.hostPlugin.logger.threaddebug(
                        u'Command queue has ' +
                        RPFrameworkUtils.to_unicode(lenQueue) +
                        u' command(s) waiting')

                    # the command name will identify what action should be taken... we will handle the known
                    # commands and dispatch out to the device implementation, if necessary, to handle unknown
                    # commands
                    command = commandQueue.get()
                    if command.commandName == RPFrameworkCommand.CMD_INITIALIZE_CONNECTION:
                        # specialized command to instanciate the concurrent thread
                        # safely ignore this... just used to spin up the thread
                        self.hostPlugin.logger.threaddebug(
                            u'Create connection command de-queued')

                        # if the device supports polling for status, it may be initiated here now; however, we should implement a pause to ensure that
                        # devices are created properly (RESTFul devices may respond too fast since no connection need be established)
                        statusUpdateStartupDelay = float(
                            self.hostPlugin.getGUIConfigValue(
                                self.indigoDevice.deviceTypeId,
                                GUI_CONFIG_RESTFULSTATUSPOLL_STARTUPDELAY,
                                u'3'))
                        if statusUpdateStartupDelay > 0.0:
                            commandQueue.put(
                                RPFrameworkCommand.RPFrameworkCommand(
                                    RPFrameworkCommand.CMD_PAUSE_PROCESSING,
                                    commandPayload=str(
                                        statusUpdateStartupDelay)))
                        commandQueue.put(
                            RPFrameworkCommand.RPFrameworkCommand(
                                RPFrameworkCommand.
                                CMD_UPDATE_DEVICE_STATUS_FULL,
                                parentAction=updateStatusPollerActionId))

                    elif command.commandName == RPFrameworkCommand.CMD_TERMINATE_PROCESSING_THREAD:
                        # a specialized command designed to stop the processing thread indigo
                        # the event of a shutdown
                        continueProcessingCommands = False

                    elif command.commandName == RPFrameworkCommand.CMD_PAUSE_PROCESSING:
                        # the amount of time to sleep should be a float found in the
                        # payload of the command
                        try:
                            pauseTime = float(command.commandPayload)
                            self.hostPlugin.logger.threaddebug(
                                u'Initiating sleep of ' +
                                RPFrameworkUtils.to_unicode(pauseTime) +
                                u' seconds from command.')
                            time.sleep(pauseTime)
                        except:
                            self.hostPlugin.logger.warning(
                                u'Invalid pause time requested')

                    elif command.commandName == RPFrameworkCommand.CMD_UPDATE_DEVICE_STATUS_FULL:
                        # this command instructs the plugin to update the full status of the device (all statuses
                        # that may be read from the device should be read)
                        if updateStatusPollerActionId != u'':
                            self.hostPlugin.logger.debug(
                                u'Executing full status update request...')
                            self.hostPlugin.executeAction(
                                None,
                                indigoActionId=updateStatusPollerActionId,
                                indigoDeviceId=self.indigoDevice.id,
                                paramValues=None)
                            updateStatusPollerNextRun = time.time(
                            ) + updateStatusPollerInterval
                        else:
                            self.hostPlugin.logger.threaddebug(
                                u'Ignoring status update request, no action specified to update device status'
                            )

                    elif command.commandName == RPFrameworkCommand.CMD_NETWORKING_WOL_REQUEST:
                        # this is a request to send a Wake-On-LAN request to a network-enabled device
                        # the command payload should be the MAC address of the device to wake up
                        try:
                            RPFrameworkNetworkingWOL.sendWakeOnLAN(
                                command.commandPayload)
                        except:
                            self.hostPlugin.logger.error(
                                u'Failed to send Wake-on-LAN packet')

                    elif command.commandName == CMD_RESTFUL_GET or command.commandName == CMD_RESTFUL_PUT or command.commandName == CMD_DOWNLOADFILE or command.commandName == CMD_DOWNLOADIMAGE:
                        try:
                            self.hostPlugin.logger.debug(
                                u'Processing GET operation: ' +
                                RPFrameworkUtils.to_unicode(
                                    command.commandPayload))

                            # gather all of the parameters from the command payload
                            # the payload should have the following format:
                            # [0] => request method (http|https|etc.)
                            # [1] => path for the GET operation
                            # [2] => authentication type: none|basic|digest
                            # [3] => username
                            # [4] => password
                            #
                            # CMD_DOWNLOADFILE or CMD_DOWNLOADIMAGE
                            # [5] => download filename/path
                            # [6] => image resize width
                            # [7] => image resize height
                            #
                            # CMD_RESTFUL_PUT
                            # [5] => data to post as the body (if any, may be blank)
                            commandPayloadList = command.getPayloadAsList()
                            fullGetUrl = commandPayloadList[
                                0] + u'://' + deviceHTTPAddress[
                                    0] + u':' + RPFrameworkUtils.to_unicode(
                                        deviceHTTPAddress[1]
                                    ) + commandPayloadList[1]

                            customHeaders = {}
                            self.addCustomHTTPHeaders(customHeaders)

                            authenticationParam = None
                            authenticationType = u'none'
                            username = u''
                            password = u''
                            if len(commandPayloadList) >= 3:
                                authenticationType = commandPayloadList[2]
                            if len(commandPayloadList) >= 4:
                                username = commandPayloadList[3]
                            if len(commandPayloadList) >= 5:
                                password = commandPayloadList[4]
                            if authenticationType != 'none' and username != u'':
                                self.hostPlugin.logger.threaddebug(
                                    u'Using login credentials... Username=> ' +
                                    username + u'; Password=>' +
                                    RPFrameworkUtils.to_unicode(len(
                                        password)) + u' characters long')
                                if authenticationType.lower() == 'digest':
                                    self.hostPlugin.logger.threaddebug(
                                        u'Enabling digest authentication')
                                    authenticationParam = HTTPDigestAuth(
                                        username, password)
                                else:
                                    authenticationParam = (username, password)

                            # execute the URL fetching depending upon the method requested
                            if command.commandName == CMD_RESTFUL_GET or command.commandName == CMD_DOWNLOADFILE or command.commandName == CMD_DOWNLOADIMAGE:
                                responseObj = requests.get(
                                    fullGetUrl,
                                    auth=authenticationParam,
                                    headers=customHeaders,
                                    verify=False)
                            elif command.commandName == CMD_RESTFUL_PUT:
                                dataToPost = None
                                if len(commandPayloadList) >= 6:
                                    dataToPost = commandPayloadList[5]
                                responseObj = requests.post(
                                    fullGetUrl,
                                    auth=authenticationParam,
                                    headers=customHeaders,
                                    verify=False,
                                    data=dataToPost)

                            # if the network command failed then allow the error processor to handle the issue
                            if responseObj.status_code == 200:
                                # the response handling will depend upon the type of command... binary returns must be
                                # handled separately from (expected) text-based ones
                                if command.commandName == CMD_DOWNLOADFILE or command.commandName == CMD_DOWNLOADIMAGE:
                                    # this is a binary return that should be saved to the file system without modification
                                    if len(commandPayloadList) >= 6:
                                        saveLocation = commandPayloadList[5]

                                        # execute the actual save from the binary response stream
                                        try:
                                            localFile = open(
                                                RPFrameworkUtils.to_str(
                                                    saveLocation), "wb")
                                            localFile.write(
                                                responseObj.content)
                                            self.hostPlugin.logger.threaddebug(
                                                u'Command Response: [' +
                                                RPFrameworkUtils.to_unicode(
                                                    responseObj.status_code) +
                                                u'] -=- binary data written to '
                                                + RPFrameworkUtils.to_unicode(
                                                    saveLocation) + u'-=-')

                                            if command.commandName == CMD_DOWNLOADIMAGE:
                                                imageResizeWidth = 0
                                                imageResizeHeight = 0
                                                if len(command.commandPayload
                                                       ) >= 7:
                                                    imageResizeWidth = int(
                                                        command.
                                                        commandPayload[6])
                                                if len(command.commandPayload
                                                       ) >= 8:
                                                    imageResizeHeight = int(
                                                        command.
                                                        commandPayload[7])

                                                resizeCommandLine = u''
                                                if imageResizeWidth > 0 and imageResizeHeight > 0:
                                                    # we have a specific size as a target...
                                                    resizeCommandLine = u'sips -z ' + RPFrameworkUtils.to_unicode(
                                                        imageResizeHeight
                                                    ) + u' ' + RPFrameworkUtils.to_unicode(
                                                        imageResizeWidth
                                                    ) + u' ' + saveLocation
                                                elif imageResizeWidth > 0:
                                                    # we have a maximum size measurement
                                                    resizeCommandLine = u'sips -Z ' + RPFrameworkUtils.to_unicode(
                                                        imageResizeWidth
                                                    ) + u' ' + saveLocation

                                                # if a command line has been formed, fire that off now...
                                                if resizeCommandLine == u'':
                                                    self.hostPlugin.logger.debug(
                                                        u'No image size specified for '
                                                        + RPFrameworkUtils.
                                                        to_unicode(
                                                            saveLocation) +
                                                        u'; skipping resize.')
                                                else:
                                                    self.hostPlugin.logger.threaddebug(
                                                        u'Executing resize via command line "'
                                                        + resizeCommandLine +
                                                        u'"')
                                                    try:
                                                        subprocess.Popen(
                                                            resizeCommandLine,
                                                            shell=True)
                                                        self.hostPlugin.logger.debug(
                                                            saveLocation +
                                                            u' resized via sip shell command'
                                                        )
                                                    except:
                                                        self.hostPlugin.logger.error(
                                                            u'Error resizing image via sips'
                                                        )

                                            # we have completed the download and processing successfully... allow the
                                            # device (or its descendants) to process successful operations
                                            self.notifySuccessfulDownload(
                                                command, saveLocation)
                                        finally:
                                            if not localFile is None:
                                                localFile.close()
                                    else:
                                        self.hostPlugin.logger.error(
                                            u'Unable to complete download action - no filename specified'
                                        )
                                else:
                                    # handle this return as a text-based return
                                    self.hostPlugin.logger.threaddebug(
                                        u'Command Response: [' +
                                        RPFrameworkUtils.to_unicode(
                                            responseObj.status_code) + u'] ' +
                                        RPFrameworkUtils.to_unicode(
                                            responseObj.text))
                                    self.hostPlugin.logger.threaddebug(
                                        command.commandName +
                                        u' command completed; beginning response processing'
                                    )
                                    self.handleDeviceTextResponse(
                                        responseObj, command)
                                    self.hostPlugin.logger.threaddebug(
                                        command.commandName +
                                        u' command response processing completed'
                                    )

                            elif responseObj.status_code == 401:
                                self.handleRESTfulError(
                                    command, u'401 - Unauthorized',
                                    responseObj)

                            else:
                                self.handleRESTfulError(
                                    command, str(responseObj.status_code),
                                    responseObj)

                        except Exception, e:
                            # the response value really should not be defined here as it bailed without
                            # catching any of our response error conditions
                            self.handleRESTfulError(command, e, None)

                    elif command.commandName == CMD_SOAP_REQUEST or command.commandName == CMD_JSON_REQUEST:
                        responseObj = None
                        try:
                            # this is to post a SOAP request to a web service... this will be similar to a restful put request
                            # but will contain a body payload
                            self.hostPlugin.logger.threaddebug(
                                u'Received SOAP/JSON command request: ' +
                                command.commandPayload)
                            soapPayloadParser = re.compile(
                                "^\s*([^\n]+)\n\s*([^\n]+)\n(.*)$", re.DOTALL)
                            soapPayloadData = soapPayloadParser.match(
                                command.commandPayload)
                            soapPath = soapPayloadData.group(1).strip()
                            soapAction = soapPayloadData.group(2).strip()
                            soapBody = soapPayloadData.group(3).strip()
                            fullGetUrl = u'http://' + deviceHTTPAddress[
                                0] + u':' + RPFrameworkUtils.to_str(
                                    deviceHTTPAddress[1]
                                ) + RPFrameworkUtils.to_str(soapPath)
                            self.hostPlugin.logger.debug(
                                u'Processing SOAP/JSON operation to ' +
                                fullGetUrl)

                            customHeaders = {}
                            self.addCustomHTTPHeaders(customHeaders)
                            if command.commandName == CMD_SOAP_REQUEST:
                                customHeaders[
                                    "Content-type"] = "text/xml; charset=\"UTF-8\""
                                customHeaders[
                                    "SOAPAction"] = RPFrameworkUtils.to_str(
                                        soapAction)
                            else:
                                customHeaders[
                                    "Content-type"] = "application/json"

                            # execute the URL post to the web service
                            self.hostPlugin.logger.threaddebug(
                                u'Sending SOAP/JSON request:\n' +
                                RPFrameworkUtils.to_unicode(soapBody))
                            self.hostPlugin.logger.threaddebug(
                                u'Using headers: \n' +
                                RPFrameworkUtils.to_unicode(customHeaders))
                            responseObj = requests.post(
                                fullGetUrl,
                                headers=customHeaders,
                                verify=False,
                                data=RPFrameworkUtils.to_str(soapBody))

                            if responseObj.status_code == 200:
                                # handle this return as a text-based return
                                self.hostPlugin.logger.threaddebug(
                                    u'Command Response: [' +
                                    RPFrameworkUtils.to_unicode(
                                        responseObj.status_code) + u'] ' +
                                    RPFrameworkUtils.to_unicode(
                                        responseObj.text))
                                self.hostPlugin.logger.threaddebug(
                                    command.commandName +
                                    u' command completed; beginning response processing'
                                )
                                self.handleDeviceTextResponse(
                                    responseObj, command)
                                self.hostPlugin.logger.threaddebug(
                                    command.commandName +
                                    u' command response processing completed')

                            else:
                                self.hostPlugin.logger.threaddebug(
                                    u'Command Response was not HTTP OK, handling RESTful error'
                                )
                                self.handleRESTfulError(
                                    command, str(responseObj.status_code),
                                    responseObj)

                        except Exception, e:
                            self.handleRESTfulError(command, e, responseObj)

                    else:
                if continueProcessingCommands == True:
                    # if we have just completed a command recently, half the amount of
                    # wait time, assuming that a subsequent command could be forthcoming
                    if lastQueuedCommandCompleted > 0:
                        time.sleep(self.emptyQueueProcessingThreadSleepTime /
                                   2)
                        lastQueuedCommandCompleted = lastQueuedCommandCompleted - 1
                    else:
                        time.sleep(self.emptyQueueProcessingThreadSleepTime)

                # check to see if we need to issue an update...
                if updateStatusPollerNextRun is not None and time.time(
                ) > updateStatusPollerNextRun:
                    commandQueue.put(
                        RPFrameworkCommand.RPFrameworkCommand(
                            RPFrameworkCommand.CMD_UPDATE_DEVICE_STATUS_FULL,
                            parentAction=updateStatusPollerActionId))

        # handle any exceptions that are thrown during execution of the plugin... note that this
        # should terminate the thread, but it may get spun back up again
        except SystemExit:
            pass
        except Exception:
            self.hostPlugin.logger.exception(
                u'Exception in background processing')
        except:
            self.hostPlugin.logger.exception(
                u'Exception in background processing')
        finally:
            self.hostPlugin.logger.debug(u'Command thread ending processing')
            self.hostPlugin.closeDatabaseConnection(self.dbConn)
Esempio n. 5
0
    def generateActionCommands(self, rpPlugin, rpDevice, paramValues):
        # validate that the values sent in are valid for this action
        validationResults = self.validateActionValues(paramValues)
        if validationResults[0] == False:
            rpPlugin.logger.error(
                u'Invalid values sent for action ' +
                RPFrameworkUtils.to_unicode(self.indigoActionId) +
                u'; the following errors were found:')
            rpPlugin.logger.error(
                RPFrameworkUtils.to_unicode(validationResults[2]))
            return

        # determine the list of parameter values based upon the parameter definitions
        # and the values provided (these will be used during substitutions below)
        resolvedValues = dict()
        for rpParam in self.indigoParams:
            resolvedValues[rpParam.indigoId] = paramValues.get(
                rpParam.indigoId, rpParam.defaultValue)

        # generate the command for each of the ones defined for this action
        commandsToQueue = []
        for (commandName, commandFormatString, commandExecuteCount,
             repeatCommandDelay, executeCondition) in self.actionCommands:
            # this command may have an execute condition which could prevent the command
            # from firing...
            if executeCondition != None and executeCondition != u'':
                # this should eval to a boolean value
                if eval(
                        rpPlugin.substituteIndigoValues(
                            executeCondition, rpDevice,
                            resolvedValues)) == False:
                    rpPlugin.logger.threaddebug(
                        u'Execute condition failed, skipping execution for command: '
                        + commandName)
                    continue

            # determine the number of times to execute this command (supports sending the same request
            # multiple times in a row)
            executeTimesStr = rpPlugin.substituteIndigoValues(
                commandExecuteCount, rpDevice, resolvedValues)
            if executeTimesStr.startswith(u'eval:'):
                executeTimesStr = eval(executeTimesStr.replace(u'eval:', u''))
            if executeTimesStr == None or executeTimesStr == u'':
                executeTimesStr = u'1'
            executeTimes = int(executeTimesStr)

            # create a new command for each of the count requested...
            for i in range(0, executeTimes):
                # create the payload based upon the format string provided for the command
                payload = rpPlugin.substituteIndigoValues(
                    commandFormatString, rpDevice, resolvedValues)
                if payload.startswith(u'eval:'):
                    payload = eval(payload.replace(u'eval:', u''))

                # determine the delay that should be added after the command (delay between repeats)
                delayTimeStr = rpPlugin.substituteIndigoValues(
                    repeatCommandDelay, rpDevice, resolvedValues)
                delayTime = 0.0
                if executeTimes > 1 and delayTimeStr != u'':
                    delayTime = float(delayTimeStr)

                # create and add the command to the queue
                commandsToQueue.append(
                    RPFrameworkCommand.RPFrameworkCommand(
                        commandName,
                        commandPayload=payload,
                        postCommandPause=delayTime,
                        parentAction=self))

        # if the execution made it here then the list of commands has been successfully built without
        # error and may be queued up on the device
        for commandForDevice in commandsToQueue:
            rpDevice.queueDeviceCommand(commandForDevice)
    def executeEffects(self, responseObj, rpCommand, rpDevice, rpPlugin):
        for effect in self.matchResultEffects:
            # first we need to determine if this effect should be executed (based upon a condition; by default all
            # effects will be executed!)
            if effect.updateExecCondition != None and effect.updateExecCondition != u'':
                # this should eval to a boolean value
                if eval(
                        rpPlugin.substituteIndigoValues(
                            effect.updateExecCondition, rpDevice,
                            dict())) == False:
                    rpPlugin.logger.threaddebug(
                        u'Execute condition failed for response, skipping execution for effect: '
                        + effect.effectType)
                    continue

            # processing for this effect is dependent upon the type
            try:
                if effect.effectType == RESPONSE_EFFECT_UPDATESTATE:
                    # this effect should update a device state (param) with a value as formated
                    newStateValueString = self.substituteCriteriaFormatString(
                        effect.updateValueFormatString, responseObj, rpCommand,
                        rpDevice, rpPlugin)
                    if effect.evalUpdateValue == True:
                        newStateValue = eval(newStateValueString)
                    else:
                        newStateValue = newStateValueString

                    # the effect may have a UI value set... if not leave at an empty string so that
                    # we don't attempt to update it
                    newStateUIValue = u''
                    if effect.updateValueFormatExString != u"":
                        newStateUIValueString = self.substituteCriteriaFormatString(
                            effect.updateValueFormatExString, responseObj,
                            rpCommand, rpDevice, rpPlugin)
                        if effect.evalUpdateValue == True:
                            newStateUIValue = eval(newStateUIValueString)
                        else:
                            newStateUIValue = newStateUIValueString

                    # update the state...
                    if newStateUIValue == u'':
                        rpPlugin.logger.debug(
                            u'Effect execution: Update state "' +
                            effect.updateParam + u'" to "' +
                            RPFrameworkUtils.to_unicode(newStateValue) + u'"')
                        rpDevice.indigoDevice.updateStateOnServer(
                            key=effect.updateParam, value=newStateValue)
                    else:
                        rpPlugin.logger.debug(
                            u'Effect execution: Update state "' +
                            effect.updateParam + '" to "' +
                            RPFrameworkUtils.to_unicode(newStateValue) +
                            u'" with UIValue "' +
                            RPFrameworkUtils.to_unicode(newStateUIValue) +
                            u'"')
                        rpDevice.indigoDevice.updateStateOnServer(
                            key=effect.updateParam,
                            value=newStateValue,
                            uiValue=newStateUIValue)

                elif effect.effectType == RESPONSE_EFFECT_QUEUECOMMAND:
                    # this effect will enqueue a new command... the updateParam will define the command name
                    # and the updateValueFormat will define the new payload
                    queueCommandName = self.substituteCriteriaFormatString(
                        effect.updateParam, responseObj, rpCommand, rpDevice,
                        rpPlugin)

                    queueCommandPayloadStr = self.substituteCriteriaFormatString(
                        effect.updateValueFormatString, responseObj, rpCommand,
                        rpDevice, rpPlugin)
                    if effect.evalUpdateValue == True:
                        queueCommandPayload = eval(queueCommandPayloadStr)
                    else:
                        queueCommandPayload = queueCommandPayloadStr

                    rpPlugin.logger.debug(
                        u'Effect execution: Queuing command {' +
                        queueCommandName + u'}')
                    rpDevice.queueDeviceCommand(
                        RPFrameworkCommand.RPFrameworkCommand(
                            queueCommandName, queueCommandPayload))

                elif effect.effectType == RESPONSE_EFFECT_CALLBACK:
                    # this should kick off a callback to a python call on the device...
                    rpPlugin.logger.debug(
                        u'Effect execution: Calling function ' +
                        effect.updateParam)
                    eval(u'rpDevice.' + effect.updateParam +
                         u'(responseObj, rpCommand)')
            except:
                rpPlugin.logger.exception(
                    u'Error executing effect for device id ' +
                    RPFrameworkUtils.to_unicode(rpDevice.indigoDevice.id))
    def concurrentCommandProcessingThread(self, commandQueue):
        try:
            # retrieve the keys and settings that will be used during the command processing
            # for this telnet device
            isConnectedStateKey = self.hostPlugin.getGUIConfigValue(
                self.indigoDevice.deviceTypeId, GUI_CONFIG_ISCONNECTEDSTATEKEY,
                u'')
            connectionStateKey = self.hostPlugin.getGUIConfigValue(
                self.indigoDevice.deviceTypeId, GUI_CONFIG_CONNECTIONSTATEKEY,
                u'')
            self.hostPlugin.logger.threaddebug(
                u'Read device state config... isConnected: "' +
                RPFrameworkUtils.to_unicode(isConnectedStateKey) +
                u'"; connectionState: "' +
                RPFrameworkUtils.to_unicode(connectionStateKey) + u'"')
            telnetConnectionInfo = self.getDeviceAddressInfo()

            # establish the telenet connection to the telnet-based which handles the primary
            # network remote operations
            self.hostPlugin.logger.debug(
                u'Establishing connection to ' +
                RPFrameworkUtils.to_unicode(telnetConnectionInfo[0]))
            ipConnection = self.establishDeviceConnection(telnetConnectionInfo)
            self.failedConnectionAttempts = 0
            self.hostPlugin.logger.debug(u'Connection established')

            # update the states on the server to show that we have established a connectionStateKey
            self.indigoDevice.setErrorStateOnServer(None)
            if isConnectedStateKey != u'':
                self.indigoDevice.updateStateOnServer(key=isConnectedStateKey,
                                                      value=u'true')
            if connectionStateKey != u'':
                self.indigoDevice.updateStateOnServer(key=connectionStateKey,
                                                      value=u'Connected')

            # retrieve any configuration information that may have been setup in the
            # plugin configuration and/or device configuration
            lineEndingToken = self.hostPlugin.getGUIConfigValue(
                self.indigoDevice.deviceTypeId, GUI_CONFIG_EOL, u'\r')
            lineEncoding = self.hostPlugin.getGUIConfigValue(
                self.indigoDevice.deviceTypeId, GUI_CONFIG_SENDENCODING,
                u'ascii')
            commandResponseTimeout = float(
                self.hostPlugin.getGUIConfigValue(
                    self.indigoDevice.deviceTypeId,
                    GUI_CONFIG_COMMANDREADTIMEOUT, u'0.5'))

            telnetConnectionRequiresLoginDP = self.hostPlugin.getGUIConfigValue(
                self.indigoDevice.deviceTypeId, GUI_CONFIG_REQUIRES_LOGIN_DP,
                u'')
            telnetConnectionRequiresLogin = (RPFrameworkUtils.to_unicode(
                self.indigoDevice.pluginProps.get(
                    telnetConnectionRequiresLoginDP,
                    u'False')).lower() == u'true')

            updateStatusPollerPropertyName = self.hostPlugin.getGUIConfigValue(
                self.indigoDevice.deviceTypeId,
                GUI_CONFIG_STATUSPOLL_INTERVALPROPERTY, u'updateInterval')
            updateStatusPollerInterval = int(
                self.indigoDevice.pluginProps.get(
                    updateStatusPollerPropertyName, u'90'))
            updateStatusPollerNextRun = None
            updateStatusPollerActionId = self.hostPlugin.getGUIConfigValue(
                self.indigoDevice.deviceTypeId, GUI_CONFIG_STATUSPOLL_ACTIONID,
                u'')

            emptyQueueReducedWaitCycles = int(
                self.hostPlugin.getGUIConfigValue(
                    self.indigoDevice.deviceTypeId,
                    GUI_CONFIG_TELNETDEV_EMPTYQUEUE_SPEEDUPCYCLES, u'200'))

            # begin the infinite loop which will run as long as the queue contains commands
            # and we have not received an explicit shutdown request
            continueProcessingCommands = True
            lastQueuedCommandCompleted = 0
            while continueProcessingCommands == True:
                # process pending commands now...
                while not commandQueue.empty():
                    lenQueue = commandQueue.qsize()
                    self.hostPlugin.logger.threaddebug(
                        u'Command queue has ' +
                        RPFrameworkUtils.to_unicode(lenQueue) +
                        u' command(s) waiting')

                    # the command name will identify what action should be taken... we will handle the known
                    # commands and dispatch out to the device implementation, if necessary, to handle unknown
                    # commands
                    command = commandQueue.get()
                    if command.commandName == RPFrameworkCommand.CMD_INITIALIZE_CONNECTION:
                        # specialized command to instanciate the thread/telnet connection
                        # safely ignore this... just used to spin up the thread
                        self.hostPlugin.logger.threaddebug(
                            u'Create connection command de-queued')

                        # if the device supports polling for status, it may be initiated here now that
                        # the connection has been established; no additional command will come through
                        if telnetConnectionRequiresLogin == False:
                            commandQueue.put(
                                RPFrameworkCommand.RPFrameworkCommand(
                                    RPFrameworkCommand.
                                    CMD_UPDATE_DEVICE_STATUS_FULL,
                                    parentAction=updateStatusPollerActionId))

                    elif command.commandName == RPFrameworkCommand.CMD_TERMINATE_PROCESSING_THREAD:
                        # a specialized command designed to stop the processing thread indigo
                        # the event of a shutdown
                        continueProcessingCommands = False

                    elif command.commandName == RPFrameworkCommand.CMD_PAUSE_PROCESSING:
                        # the amount of time to sleep should be a float found in the
                        # payload of the command
                        try:
                            pauseTime = float(command.commandPayload)
                            self.hostPlugin.logger.threaddebug(
                                u'Initiating sleep of ' +
                                RPFrameworkUtils.to_unicode(pauseTime) +
                                u' seconds from command.')
                            time.sleep(pauseTime)
                        except:
                            self.hostPlugin.logger.error(
                                u'Invalid pause time requested')

                    elif command.commandName == RPFrameworkCommand.CMD_UPDATE_DEVICE_STATUS_FULL:
                        # this command instructs the plugin to update the full status of the device (all statuses
                        # that may be read from the device should be read)
                        if updateStatusPollerActionId != u'':
                            self.hostPlugin.logger.debug(
                                u'Executing full status update request...')
                            self.hostPlugin.executeAction(
                                None,
                                indigoActionId=updateStatusPollerActionId,
                                indigoDeviceId=self.indigoDevice.id,
                                paramValues=None)
                            if updateStatusPollerInterval > 0:
                                updateStatusPollerNextRun = time.time(
                                ) + updateStatusPollerInterval
                        else:
                            self.hostPlugin.logger.threaddebug(
                                u'Ignoring status update request, no action specified to update device status'
                            )

                    elif command.commandName == RPFrameworkCommand.CMD_UPDATE_DEVICE_STATE:
                        # this command is to update a device state with the payload (which may be an
                        # eval command)
                        newStateInfo = re.match(
                            '^\{ds\:([a-zA-Z\d]+)\}\{(.+)\}$',
                            command.commandPayload, re.I)
                        if newStateInfo is None:
                            self.hostPlugin.logger.error(
                                u'Invalid new device state specified')
                        else:
                            # the new device state may include an eval statement...
                            updateStateName = newStateInfo.group(1)
                            updateStateValue = newStateInfo.group(2)
                            if updateStateValue.startswith(u'eval'):
                                updateStateValue = eval(
                                    updateStateValue.replace(u'eval:', u''))

                            self.hostPlugin.logger.debug(
                                u'Updating state "' +
                                RPFrameworkUtils.to_unicode(updateStateName) +
                                u'" to: ' +
                                RPFrameworkUtils.to_unicode(updateStateValue))
                            self.indigoDevice.updateStateOnServer(
                                key=updateStateName, value=updateStateValue)

                    elif command.commandName == CMD_WRITE_TO_DEVICE:
                        # this command initiates a write of data to the device
                        self.hostPlugin.logger.debug(u'Sending command: ' +
                                                     command.commandPayload)
                        writeCommand = command.commandPayload + lineEndingToken
                        ipConnection.write(writeCommand.encode(lineEncoding))
                        self.hostPlugin.logger.threaddebug(
                            u'Write command completed.')

                    else:
                        # this is an unknown command; dispatch it to another routine which is
                        # able to handle the commands (to be overridden for individual devices)
                        self.handleUnmanagedCommandInQueue(
                            ipConnection, command)

                    # determine if any response has been received from the telnet device...
                    responseText = RPFrameworkUtils.to_unicode(
                        self.readLine(ipConnection, lineEndingToken,
                                      commandResponseTimeout))
                    if responseText != u'':
                        self.hostPlugin.logger.threaddebug("Received: " +
                                                           responseText)
                        self.handleDeviceResponse(
                            responseText.replace(lineEndingToken, u''),
                            command)

                    # if the command has a pause defined for after it is completed then we
                    # should execute that pause now
                    if command.postCommandPause > 0.0 and continueProcessingCommands == True:
                        self.hostPlugin.logger.threaddebug(
                            u'Post Command Pause: ' +
                            RPFrameworkUtils.to_unicode(
                                command.postCommandPause))
                        time.sleep(command.postCommandPause)

                    # complete the dequeuing of the command, allowing the next
                    # command in queue to rise to the top
                    commandQueue.task_done()
                    lastQueuedCommandCompleted = emptyQueueReducedWaitCycles

                # continue with empty-queue processing unless the connection is shutting down...
                if continueProcessingCommands == True:
                    # check for any pending data coming IN from the telnet connection; note this is after the
                    # command queue has been emptied so it may be un-prompted incoming data
                    responseText = RPFrameworkUtils.to_unicode(
                        self.readIfAvailable(ipConnection, lineEndingToken,
                                             commandResponseTimeout))
                    if responseText != u'':
                        self.hostPlugin.logger.threaddebug(
                            u'Received w/o Command: ' + responseText)
                        self.handleDeviceResponse(
                            responseText.replace(lineEndingToken, u''), None)

                    # when the queue is empty, pause a bit on each iteration
                    if lastQueuedCommandCompleted > 0:
                        time.sleep(self.emptyQueueProcessingThreadSleepTime /
                                   2)
                        lastQueuedCommandCompleted = lastQueuedCommandCompleted - 1
                    else:
                        time.sleep(self.emptyQueueProcessingThreadSleepTime)

                    # check to see if we need to issue an update...
                    if updateStatusPollerNextRun is not None and time.time(
                    ) > updateStatusPollerNextRun:
                        commandQueue.put(
                            RPFrameworkCommand.RPFrameworkCommand(
                                RPFrameworkCommand.
                                CMD_UPDATE_DEVICE_STATUS_FULL,
                                parentAction=updateStatusPollerActionId))

        # handle any exceptions that are thrown during execution of the plugin... note that this
        # should terminate the thread, but it may get spun back up again
        except SystemExit:
            # the system is shutting down communications... we can kill access now by allowing
            # the thread to expire
            pass
        except (socket.timeout, EOFError):
            # this is a standard timeout/disconnect
            if self.failedConnectionAttempts == 0 or self.hostPlugin.debug == True:
                self.hostPlugin.logger.error(
                    u'Connection timed out for device ' +
                    RPFrameworkUtils.to_unicode(self.indigoDevice.id))

            if connectionStateKey != u'':
                self.indigoDevice.updateStateOnServer(key=connectionStateKey,
                                                      value=u'Unavailable')
                connectionStateKey = u''  # prevents the finally from re-updating to disconnected

            # this really is an error from the user's perspective, so set that state now
            self.indigoDevice.setErrorStateOnServer(u'Connection Error')

            # check to see if we should attempt a reconnect
            self.scheduleReconnectionAttempt()
        except socket.error, e:
            # this is a standard socket error, such as a reset... we can attempt to recover from this with
            # a scheduled reconnect
            if self.failedConnectionAttempts == 0 or self.hostPlugin.debug == True:
                self.hostPlugin.logg.error(
                    u'Connection failed for device ' +
                    RPFrameworkUtils.to_unicode(self.indigoDevice.id) + u': ' +
                    RPFrameworkUtils.to_unicode(e))

            if connectionStateKey != u'':
                self.indigoDevice.updateStateOnServer(key=connectionStateKey,
                                                      value=u'Unavailable')
                connectionStateKey = u''  # prevents the finally from re-updating to disconnected

            # this really is an error from the user's perspective, so set that state now
            self.indigoDevice.setErrorStateOnServer(u'Connection Error')

            # check to see if we should attempt a reconnect
            self.scheduleReconnectionAttempt()