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.logDebugMessage(
                    u"Triggering property update due to missing device property: "
                    + RPFrameworkUtils.to_unicode(newPropertyDefn[0]),
                    RPFrameworkPlugin.DEBUGLEVEL_LOW,
                )
                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.logDebugMessage(
                    u"Triggering state reload due to missing device state: "
                    + RPFrameworkUtils.to_unicode(newStateName),
                    RPFrameworkPlugin.DEBUGLEVEL_LOW,
                )
                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 terminateCommunications(self):
		self.hostPlugin.logger.debug(u'Initiating shutdown of communications with ' + RPFrameworkUtils.to_unicode(self.indigoDevice.name))
		if not (self.concurrentThread is None) and self.concurrentThread.isAlive() == True:
			self.concurrentThread.terminateThread()
			self.concurrentThread.join()
		self.concurrentThread = None
		self.hostPlugin.logger.debug(u'Shutdown of communications with ' + RPFrameworkUtils.to_unicode(self.indigoDevice.name) + u' complete')
	def readIfAvailable(self, connection, lineEndingToken, commandResponseTimeout):
		if self.connectionType == CONNECTIONTYPE_TELNET:
			return RPFrameworkUtils.to_unicode(connection.read_eager())
		elif connection.inWaiting() > 0:
			return RPFrameworkUtils.to_unicode(self.readLine(connection, lineEndingToken, commandResponseTimeout))
		else:
			return u''
	def __init__(self, response):
		r = httplib.HTTPResponse(self._FakeSocket(response))
		r.begin()
		
		self.location = u''
		self.usn = u''
		self.st = u''
		self.server = u''
		self.cache = u''
		
		if r.getheader("location") is not None:
			self.location = RPFrameworkUtils.to_unicode(r.getheader("location"))
			
		if r.getheader("usn") is not None:
			self.usn = RPFrameworkUtils.to_unicode(r.getheader("usn"))
	
		if r.getheader("st") is not None:
			self.st = RPFrameworkUtils.to_unicode(r.getheader("st"))
	
		if r.getheader("server") is not None:
			self.server = RPFrameworkUtils.to_unicode(r.getheader("server"))
		
		if r.getheader("cache-control") is not None:
			try:
				cacheControlHeader = RPFrameworkUtils.to_unicode(r.getheader("cache-control"))
				cacheControlHeader = cacheControlHeader.split(u'=')[1]
				self.cache = cacheControlHeader
			except:
				pass
		
		self.allHeaders = r.getheaders()
	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:
			indigo.server.log(u'Invalid values sent for action ' + RPFrameworkUtils.to_unicode(self.indigoActionId) + u'; the following errors were found:')
			indigo.server.log(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.logDebugMessage(u'Execute condition failed, skipping execution for command: ' + commandName, RPFrameworkPlugin.DEBUGLEVEL_HIGH)
					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 terminateCommunications(self):
     self.hostPlugin.logDebugMessage(
         u"Initiating shutdown of communications with " + RPFrameworkUtils.to_unicode(self.indigoDevice.name),
         RPFrameworkPlugin.DEBUGLEVEL_LOW,
     )
     if not (self.concurrentThread is None) and self.concurrentThread.isAlive() == True:
         self.concurrentThread.terminate()
         self.concurrentThread.join()
     self.concurrentThread = None
     self.hostPlugin.logDebugMessage(
         u"Shutdown of communications with " + RPFrameworkUtils.to_unicode(self.indigoDevice.name) + u" complete",
         RPFrameworkPlugin.DEBUGLEVEL_LOW,
     )
    def removeChildDevice(self, device):
        self.hostPlugin.logDebugMessage(
            u"Removing child device "
            + RPFrameworkUtils.to_unicode(device.indigoDevice.id)
            + u" from "
            + RPFrameworkUtils.to_unicode(self.indigoDevice.id),
            RPFrameworkPlugin.DEBUGLEVEL_MED,
        )

        # the key into the dictionary will be specified by the GUI configuration variable
        childDeviceKey = self.getChildDeviceKeyByDevice(device)

        # remove the device...
        del self.childDevices[childDeviceKey]
    def addChildDevice(self, device):
        self.hostPlugin.logDebugMessage(
            u"Adding child device "
            + RPFrameworkUtils.to_unicode(device.indigoDevice.id)
            + u" to "
            + RPFrameworkUtils.to_unicode(self.indigoDevice.id),
            RPFrameworkPlugin.DEBUGLEVEL_MED,
        )

        # the key into the dictionary will be specified by the GUI configuration variable
        childDeviceKey = self.getChildDeviceKeyByDevice(device)
        self.hostPlugin.logDebugMessage(u"Created device key: " + childDeviceKey, RPFrameworkPlugin.DEBUGLEVEL_HIGH)

        # add the device to the list of those managed by this device...
        self.childDevices[childDeviceKey] = device
	def getChildDeviceKeyByDevice(self, device):
		# the key into the dictionary will be specified by the GUI configuration variable
		# of THIS (parent) device... by default it will just be the child device's ID
		childDeviceKey = self.hostPlugin.substituteIndigoValues(self.hostPlugin.getGUIConfigValue(self.indigoDevice.deviceTypeId, RPFrameworkPlugin.GUI_CONFIG_CHILDDICTIONARYKEYFORMAT, u''), device, None)
		if childDeviceKey == u'':
			childDeviceKey = RPFrameworkUtils.to_unicode(device.indigoDevice.id)
		return childDeviceKey
    def scheduleReconnectionAttempt(self):
        self.hostPlugin.logDebugMessage(u"Scheduling reconnection attempt...", RPFrameworkPlugin.DEBUGLEVEL_MED)
        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.logDebugMessage(
                    u"Maximum reconnection attempts reached (or not allowed) for device "
                    + RPFrameworkUtils.to_unicode(self.indigoDevice.id),
                    RPFrameworkPlugin.DEBUGLEVEL_LOW,
                )
            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.logDebugMessage(
                    u"Reconnection attempt scheduled for "
                    + RPFrameworkUtils.to_unicode(reconnectSeconds)
                    + u" seconds",
                    RPFrameworkPlugin.DEBUGLEVEL_MED,
                )
        except e:
            self.hostPlugin.logErrorMessage(u"Failed to schedule reconnection attempt to device")
	def handleDeviceTextResponse(self, responseObj, rpCommand):
		# loop through the list of response definitions defined in the (base) class
		# and determine if any match
		responseText = responseObj.text
		for rpResponse in self.hostPlugin.getDeviceResponseDefinitions(self.indigoDevice.deviceTypeId):
			if rpResponse.isResponseMatch(responseText, rpCommand, self, self.hostPlugin):
				self.hostPlugin.logDebugMessage(u'Found response match: ' + RPFrameworkUtils.to_unicode(rpResponse.responseId), RPFrameworkPlugin.DEBUGLEVEL_MED)
				rpResponse.executeEffects(responseText, rpCommand, self, self.hostPlugin)
	def removeChildDevice(self, device):
		self.hostPlugin.logger.threaddebug(u'Removing child device ' + RPFrameworkUtils.to_unicode(device.indigoDevice.id) + u' from ' + RPFrameworkUtils.to_unicode(self.indigoDevice.id))
		
		# the key into the dictionary will be specified by the GUI configuration variable
		childDeviceKey = self.getChildDeviceKeyByDevice(device)
		
		# remove the device...
		del self.childDevices[childDeviceKey]
	def handleRESTfulError(self, rpCommand, err, response=None):
		if rpCommand.commandName == CMD_RESTFUL_PUT or rpCommand.commandName == CMD_RESTFUL_GET:
			self.hostPlugin.logger.error(u'An error occurred executing the GET/PUT request (Device: ' + RPFrameworkUtils.to_unicode(self.indigoDevice.id) + u'): ' + RPFrameworkUtils.to_unicode(err))
		else:
			self.hostPlugin.logger.error(u'An error occurred processing the SOAP/JSON POST request: (Device: ' + RPFrameworkUtils.to_unicode(self.indigoDevice.id) + u'): ' + RPFrameworkUtils.to_unicode(err))
			
		if not response is None:
			self.hostPlugin.logger.debug(RPFrameworkUtils.to_unicode(response.text))
	def addChildDevice(self, device):
		self.hostPlugin.logger.threaddebug(u'Adding child device ' + RPFrameworkUtils.to_unicode(device.indigoDevice.id) + u' to ' + RPFrameworkUtils.to_unicode(self.indigoDevice.id))
		
		# the key into the dictionary will be specified by the GUI configuration variable
		childDeviceKey = self.getChildDeviceKeyByDevice(device)
		self.hostPlugin.logger.threaddebug(u'Created device key: ' + childDeviceKey)
			
		# add the device to the list of those managed by this device...
		self.childDevices[childDeviceKey] = device
	def readLine(self, connection, lineEndingToken, commandResponseTimeout):
		if self.connectionType == CONNECTIONTYPE_TELNET:
			return RPFrameworkUtils.to_unicode(connection.read_until(lineEndingToken, commandResponseTimeout))
		elif self.connectionType == CONNECTIONTYPE_SERIAL:
			# Python 2.6 changed the readline signature to not include a line-ending token,
			# so we have to "manually" re-create that here
			#return connection.readline(None)
			lineRead = u''
			lineEndingTokenLen = len(lineEndingToken)
			while True:
				c = connection.read(1)
				if c:
					lineRead += c
					if lineRead[-lineEndingTokenLen:] == lineEndingToken:
						break
				else:
					break
			return RPFrameworkUtils.to_unicode(lineRead)
 def readLine(self, connection, lineEndingToken, commandResponseTimeout):
     if self.connectionType == CONNECTIONTYPE_TELNET:
         return RPFrameworkUtils.to_unicode(
             connection.read_until(lineEndingToken, commandResponseTimeout))
     elif self.connectionType == CONNECTIONTYPE_SERIAL:
         # Python 2.6 changed the readline signature to not include a line-ending token,
         # so we have to "manually" re-create that here
         #return connection.readline(None)
         lineRead = u''
         lineEndingTokenLen = len(lineEndingToken)
         while True:
             c = connection.read(1)
             if c:
                 lineRead += c
                 if lineRead[-lineEndingTokenLen:] == lineEndingToken:
                     break
             else:
                 break
         return RPFrameworkUtils.to_unicode(lineRead)
	def getDeviceAddressInfo(self):
		if self.connectionType == CONNECTIONTYPE_TELNET:
			return (u'', 0)
		else:
			portName = RPFrameworkUtils.to_unicode(self.hostPlugin.substituteIndigoValues(self.hostPlugin.getGUIConfigValue(self.indigoDevice.deviceTypeId, GUI_CONFIG_SERIALPORT_PORTNAME, ""), self, None))
			baudRate = int(self.hostPlugin.substituteIndigoValues(self.hostPlugin.getGUIConfigValue(self.indigoDevice.deviceTypeId, GUI_CONFIG_SERIALPORT_BAUDRATE, "115200"), self, None))
			parity = eval("serial." + self.hostPlugin.substituteIndigoValues(self.hostPlugin.getGUIConfigValue(self.indigoDevice.deviceTypeId, GUI_CONFIG_SERIALPORT_PARITY, "PARITY_NONE"), self, None))
			byteSize = eval("serial." + self.hostPlugin.substituteIndigoValues(self.hostPlugin.getGUIConfigValue(self.indigoDevice.deviceTypeId, GUI_CONFIG_SERIALPORT_BYTESIZE, "EIGHTBITS"), self, None))
			stopBits = eval("serial." + self.hostPlugin.substituteIndigoValues(self.hostPlugin.getGUIConfigValue(self.indigoDevice.deviceTypeId, GUI_CONFIG_SERIALPORT_STOPBITS, "STOPBITS_ONE"), self, None))
			timeout = float(self.hostPlugin.substituteIndigoValues(self.hostPlugin.getGUIConfigValue(self.indigoDevice.deviceTypeId, GUI_CONFIG_SERIALPORT_READTIMEOUT, "1.0"), self, None))
			writeTimeout = float(self.hostPlugin.substituteIndigoValues(self.hostPlugin.getGUIConfigValue(self.indigoDevice.deviceTypeId, GUI_CONFIG_SERIALPORT_WRITETIMEOUT, "1.0"), self, None))
			return (portName, (baudRate, parity, byteSize, stopBits, timeout, writeTimeout))
	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 uPnPDiscover(service, timeout=2, retries=1):
    group = ("239.255.255.250", 1900)
    message = "\r\n".join([
        "M-SEARCH * HTTP/1.1",
        "HOST: " + group[0] + ":" + RPFrameworkUtils.to_str(group[1]),
        "MAN: ""ssdp:discover""",
        "ST: " + service,"MX: 3","",""])
    socket.setdefaulttimeout(timeout)
    responses = {}
    for _ in range(retries):
        sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
        sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 2)
        sock.sendto(message, group)
        while True:
            try:
                response = SSDPResponse(sock.recv(1024))
                responses[response.location] = response
            except socket.timeout:
                break
    return responses.values()
    def substituteCriteriaFormatString(self, formatString, responseObj, rpCommand, rpDevice, rpPlugin):
        substitutedCriteria = formatString
        if substitutedCriteria is None:
            return u""

            # substitute the response/command object values as those are
            # specific to commands
        if rpCommand is not None:
            substitutedCriteria = substitutedCriteria.replace(u"%cp:name%", rpCommand.commandName)
            substitutedCriteria = substitutedCriteria.replace(
                u"%cp:payload%", RPFrameworkUtils.to_unicode(rpCommand.commandPayload)
            )

        if isinstance(responseObj, (str, unicode)):
            substitutedCriteria = substitutedCriteria.replace("%cp:response%", responseObj)

            # substitute the standard RPFramework substitutions
        substitutedCriteria = rpPlugin.substituteIndigoValues(substitutedCriteria, rpDevice, None)

        # return the result back to the caller
        return substitutedCriteria
 def getDeviceAddressInfo(self):
     if self.connectionType == CONNECTIONTYPE_TELNET:
         return (u'', 0)
     else:
         portName = RPFrameworkUtils.to_unicode(
             self.hostPlugin.substituteIndigoValues(
                 self.hostPlugin.getGUIConfigValue(
                     self.indigoDevice.deviceTypeId,
                     GUI_CONFIG_SERIALPORT_PORTNAME, ""), self, None))
         baudRate = int(
             self.hostPlugin.substituteIndigoValues(
                 self.hostPlugin.getGUIConfigValue(
                     self.indigoDevice.deviceTypeId,
                     GUI_CONFIG_SERIALPORT_BAUDRATE, "115200"), self, None))
         parity = eval("serial." + self.hostPlugin.substituteIndigoValues(
             self.hostPlugin.getGUIConfigValue(
                 self.indigoDevice.deviceTypeId,
                 GUI_CONFIG_SERIALPORT_PARITY, "PARITY_NONE"), self, None))
         byteSize = eval("serial." + self.hostPlugin.substituteIndigoValues(
             self.hostPlugin.getGUIConfigValue(
                 self.indigoDevice.deviceTypeId,
                 GUI_CONFIG_SERIALPORT_BYTESIZE, "EIGHTBITS"), self, None))
         stopBits = eval("serial." + self.hostPlugin.substituteIndigoValues(
             self.hostPlugin.getGUIConfigValue(
                 self.indigoDevice.deviceTypeId,
                 GUI_CONFIG_SERIALPORT_STOPBITS, "STOPBITS_ONE"), self,
             None))
         timeout = float(
             self.hostPlugin.substituteIndigoValues(
                 self.hostPlugin.getGUIConfigValue(
                     self.indigoDevice.deviceTypeId,
                     GUI_CONFIG_SERIALPORT_READTIMEOUT, "1.0"), self, None))
         writeTimeout = float(
             self.hostPlugin.substituteIndigoValues(
                 self.hostPlugin.getGUIConfigValue(
                     self.indigoDevice.deviceTypeId,
                     GUI_CONFIG_SERIALPORT_WRITETIMEOUT, "1.0"), self,
                 None))
         return (portName, (baudRate, parity, byteSize, stopBits, timeout,
                            writeTimeout))
								
							else:
								self.handleRESTfulError(command, str(responseObj.status_code), responseObj)

						except Exception, e:
							self.handleRESTfulError(command, e, responseObj)
					
					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(deviceHTTPAddress, 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.logDebugMessage(u'Post Command Pause: ' + RPFrameworkUtils.to_unicode(command.postCommandPause), RPFrameworkPlugin.DEBUGLEVEL_MED)
						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
				
				# when the queue is empty, pause a bit on each iteration
				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:
	def __repr__(self):
		return u'<SSDPResponse(%(location)s, %(st)s, %(usn)s, %(server)s)>' % (self.__dict__) + RPFrameworkUtils.to_unicode(self.allHeaders) + u'</SSDPResonse>'
	def isValueValid(self, proposedValue):
		# if the value is required but empty then error here
		if proposedValue == None or proposedValue == u'':
			return not self.isRequired
		
		# now validate that the type is correct...
		if self.paramType == ParamTypeInteger:
			try:
				proposedIntValue = int(proposedValue)
				if proposedIntValue < self.minValue or proposedIntValue > self.maxValue:
					raise u'Param value not in range'
				return True
			except:
				return False
				
		elif self.paramType == ParamTypeFloat:
			try:
				proposedFltValue = float(proposedValue)
				if proposedFltValue < self.minValue or proposedFltValue > self.maxValue:
					raise u'Param value not in range'
				return True
			except:
				return False
				
		elif self.paramType == ParamTypeBoolean:
			if type(proposedValue) is bool:
				return True
			else:
				return proposedValue.lower() == u'true'
			
		elif self.paramType == ParamTypeOSDirectoryPath:
			# validate that the path exists... and that it is a directory
			return os.path.isdir(RPFrameworkUtils.to_str(proposedValue))
			
		elif self.paramType == ParamTypeOSFilePath:
			# validate that the file exists (and that it is a file)
			return os.path.isfile(RPFrameworkUtils.to_str(proposedValue))
		
		elif self.paramType == ParamTypeIPAddress:
			# validate the IP address using IPv4 standards for now...
			return self.isIPv4Valid(RPFrameworkUtils.to_str(proposedValue))
			
		elif self.paramType == ParamTypeList:
			# validate that the list contains between the minimum and maximum
			# number of entries
			if len(proposedValue) < self.minValue or len(proposedValue) > self.maxValue:
				return False
			else:
				return True
			
		else:
			# default is a string value... so this will need to check against the
			# validation expression, if set, and string length
			if self.validationExpression != u'':
				if re.search(self.validationExpression, proposedValue, re.I) == None:
					return False
					
			strLength = len(proposedValue)
			if strLength < self.minValue or strLength > self.maxValue:
				return False
				
			# if string processing makes it here then all is good
			return True
	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()
	def concurrentCommandProcessingThread(self, commandQueue):
		try:
			self.hostPlugin.logDebugMessage(u'Concurrent Processing Thread started for device ' + RPFrameworkUtils.to_unicode(self.indigoDevice.id), RPFrameworkPlugin.DEBUGLEVEL_MED)
		
			# 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:
				indigo.server.log(u'No IP address specified for device ' + RPFrameworkUtils.to_unicode(self.indigoDevice.id) + u'; ending command processing thread.', isError=True)
				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.logDebugMessage(u'Command queue has ' + RPFrameworkUtils.to_unicode(lenQueue) + u' command(s) waiting', RPFrameworkPlugin.DEBUGLEVEL_HIGH)
					
					# 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.logDebugMessage(u'Create connection command de-queued', RPFrameworkPlugin.DEBUGLEVEL_MED)
						
						# 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.logDebugMessage(u'Initiating sleep of ' + RPFrameworkUtils.to_unicode(pauseTime) + u' seconds from command.', RPFrameworkPlugin.DEBUGLEVEL_MED)
							time.sleep(pauseTime)
						except:
							indigo.server.log(u'Invalid pause time requested', isError=True)
							
					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.logDebugMessage(u'Executing full status update request...', RPFrameworkPlugin.DEBUGLEVEL_MED)
							self.hostPlugin.executeAction(None, indigoActionId=updateStatusPollerActionId, indigoDeviceId=self.indigoDevice.id, paramValues=None)
							updateStatusPollerNextRun = time.time() + updateStatusPollerInterval
						else:
							self.hostPlugin.logDebugMessage(u'Ignoring status update request, no action specified to update device status', RPFrameworkPlugin.DEBUGLEVEL_HIGH)
							
					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.logErrorMessage(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.logDebugMessage(u'Processing GET operation: ' + RPFrameworkUtils.to_unicode(command.commandPayload), RPFrameworkPlugin.DEBUGLEVEL_MED)
							
							# 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.logDebugMessage(u'Using login credentials... Username=> ' + username + u'; Password=>' + RPFrameworkUtils.to_unicode(len(password)) + u' characters long', RPFrameworkPlugin.DEBUGLEVEL_HIGH)
								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.logDebugMessage(u'Command Response: [' + RPFrameworkUtils.to_unicode(responseObj.status_code) + u'] -=- binary data written to ' + RPFrameworkUtils.to_unicode(saveLocation) + u'-=-', RPFrameworkPlugin.DEBUGLEVEL_HIGH)
										
											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.logDebugMessage(u'No image size specified for ' + RPFrameworkUtils.to_unicode(saveLocation) + u'; skipping resize.', RPFrameworkPlugin.DEBUGLEVEL_MED)
												else:
													self.hostPlugin.logDebugMessage(u'Executing resize via command line "' + resizeCommandLine + u'"', RPFrameworkPlugin.DEBUGLEVEL_HIGH)
													try:
														subprocess.Popen(resizeCommandLine, shell=True)
														self.hostPlugin.logDebugMessage(saveLocation + u' resized via sip shell command', RPFrameworkPlugin.DEBUGLEVEL_HIGH)
													except:
														self.hostPlugin.logErrorMessage(u'Error resizing image via sips')
										finally:
											if not localFile is None:
												localFile.close()					
									else:
										indigo.server.log(u'Unable to complete download action - no filename specified', isError=True)
								else:
									# handle this return as a text-based return
									self.hostPlugin.logDebugMessage(u'Command Response: [' + RPFrameworkUtils.to_unicode(responseObj.status_code) + u'] ' + RPFrameworkUtils.to_unicode(responseObj.text), RPFrameworkPlugin.DEBUGLEVEL_HIGH)
									self.hostPlugin.logDebugMessage(command.commandName + u' command completed; beginning response processing', RPFrameworkPlugin.DEBUGLEVEL_HIGH)
									self.handleDeviceTextResponse(responseObj, command)
									self.hostPlugin.logDebugMessage(command.commandName + u' command response processing completed', RPFrameworkPlugin.DEBUGLEVEL_HIGH)
									
							elif responseObj.status_code == 401:
								self.handleRESTfulError(command, u'401 - Unauthorized', responseObj)
							
							else:
								self.handleRESTfulError(command, str(responseObj.status_code), responseObj)
							 	
						except Exception, e:
							self.handleRESTfulError(command, e, responseObj)
						
					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.logDebugMessage(u'Received SOAP/JSON command request: ' + command.commandPayload, RPFrameworkPlugin.DEBUGLEVEL_HIGH)
							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.logDebugMessage(u'Processing SOAP/JSON operation to ' + fullGetUrl, RPFrameworkPlugin.DEBUGLEVEL_MED)

							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.logDebugMessage(u'Sending SOAP/JSON request:\n' + RPFrameworkUtils.to_str(soapBody), RPFrameworkPlugin.DEBUGLEVEL_HIGH)
							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.logDebugMessage(u'Command Response: [' + RPFrameworkUtils.to_unicode(responseObj.status_code) + u'] ' + RPFrameworkUtils.to_unicode(responseObj.text), RPFrameworkPlugin.DEBUGLEVEL_HIGH)
								self.hostPlugin.logDebugMessage(command.commandName + u' command completed; beginning response processing', RPFrameworkPlugin.DEBUGLEVEL_HIGH)
								self.handleDeviceTextResponse(responseObj, command)
								self.hostPlugin.logDebugMessage(command.commandName + u' command response processing completed', RPFrameworkPlugin.DEBUGLEVEL_HIGH)
								
							else:
								self.handleRESTfulError(command, str(responseObj.status_code), responseObj)

						except Exception, e:
							self.handleRESTfulError(command, e, responseObj)
					
					else:
    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)
                )
							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:
						# 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(deviceHTTPAddress, 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
				
				# when the queue is empty, pause a bit on each iteration
				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:
    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()