示例#1
0
	def __init__(self, factory):
		self.logger = logging.getLogger ("Plugin.cache")
		self.factory = factory
		self.items = cacheDict(factory)
		self.pluginItems = indigo.Dict() # Plugin devices by type
		self.pluginDevices = indigo.List() # All plugin devices
		self.pluginLocalCache = indigo.List() # If the plugin needs to cache something special
		
		self.logger.threaddebug ("Caching initialized")
示例#2
0
	def addDevice (self, dev):
		try:
			if self.items.isInCache (dev.id):
				self.logger.debug ("Not adding {0} to cache because it's already there".format(dev.name))
				return
			else:
				self.logger.debug ("Adding {0} to cache".format(dev.name))
		
			cDev = cacheDev(dev)
			self.items.add(cDev)
			
			if cDev.pluginId == self.factory.plugin.pluginId: 
				self._autoCacheFields (dev)
				
				if cDev.id in self.pluginDevices:
					pass
				else:
					self.pluginDevices.append(cDev.id) 
				
				pluginItem = indigo.Dict()
				if cDev.deviceTypeId in self.pluginItems:
					pluginItem = self.pluginItems[cDev.deviceTypeId]
				
				else:
					pluginItem = indigo.List()

				if cDev.id in pluginItem:
					pass
				else:
					pluginItem.append(cDev.id)
					
				self.pluginItems[cDev.deviceTypeId] = pluginItem	
				
		except Exception as e:
			self.logger.error (ext.getException(e))	
示例#3
0
    def __init__(self, device, knownDevices):
        self.logger = logging.getLogger("Plugin.X10")
        self.device = device
        deviceType = device.pluginProps['deviceType']

        if device.address not in knownDevices:
            self.logger.info("New X10 Device %s" % (device.address))
            knownDevices[device.address] = {
                "status": "Active",
                "devices": indigo.List(),
                "protocol": "1",
                "subType": deviceType,
                "description": device.address,
                "playerId": int(device.pluginProps["targetDevice"]),
                "frameData": None
            }

        devAddress = device.pluginProps['address']
        subType = knownDevices[devAddress]['subType']
        self.logger.debug(u"%s: Starting X10 device (%s) @ %s" %
                          (device.name, subType, devAddress))
        self.player = indigo.devices[knownDevices[devAddress]['playerId']]

        configDone = device.pluginProps.get('configDone', False)
        self.logger.threaddebug(u"%s: __init__ configDone = %s" %
                                (device.name, str(configDone)))
        if configDone:
            return

        knownDevices.setitem_in_item(devAddress, 'status', "Active")
        devices = knownDevices[devAddress]['devices']
        devices.append(device.id)
        knownDevices.setitem_in_item(devAddress, 'devices', devices)

        device.name = devAddress
        device.replaceOnServer()

        newProps = device.pluginProps
        newProps["configDone"] = True
        device.replacePluginPropsOnServer(newProps)

        self.logger.info(u"Configured X10 Sensor '%s' (%s) @ %s" %
                         (device.name, device.id, devAddress))

        # all done creating devices.  Use the cached data to set initial data

        frameData = knownDevices[devAddress].get('frameData', None)
        if (frameData):
            self.handler(frameData, knownDevices)
示例#4
0
    def frameCheck(cls, playerDevice, frameData, knownDevices):
        devAddress = "OWL-" + frameData['infos']['id']
        if devAddress not in knownDevices:                                        
            indigo.server.log("New device added to Known Device list: %s" % (devAddress))
            knownDevices[devAddress] = { 
                "status": "Available", 
                "devices" : indigo.List(),
                "protocol": frameData['header']['protocol'], 
                "description": frameData['infos']['id'],
                "subType": 'None',
                "playerId": playerDevice.id,
                "frameData": frameData
            }
        else:
            knownDevices[devAddress]["playerId"] = playerDevice.id

        return devAddress
class plugdetails:
	
	pluginCache = indigo.Dict()
	
	#
	# Init
	#
	def __init__(self, factory, filter = None):
		self.logger = logging.getLogger ("Plugin.plugdetails")
		self.factory = factory
		self.filter = filter
		if filter is None: self.filter = plugfilter()
		
		#self.refresh()
		self.cachePluginInformation()
		self.getDetailsByPluginId (self.factory.plugin.pluginId) # Forces "Indigo" index
		
	################################################################################
	# METHODS
	################################################################################		
	
	#
	# Get a list of fields suitable for a list or menu UI field
	#
	def getFieldUIList (self, obj):
		ret = []
		
		try:
			data = self._resolveObject (obj)
			if len(data[0]) == 0: return ret
			
			plugInfo = data[0]
			deviceTypeId = data[1]				
			
			if "xml" in plugInfo == False: return ret
			if "devices" in plugInfo["xml"] == False: return ret
										
			for id, info in plugInfo["xml"]["devices"].iteritems():
				if id == deviceTypeId:
					if len(info["ConfigUI"]) > 0:	
						for idx, configUI in info["ConfigUI"].iteritems():	
							for field in configUI:
								if field["hidden"]: continue
								
								if field["type"] == "separator":
									option = ("-line-", self.factory.ui.getSeparator())
									ret.append(option)
								elif field["type"] == "label":
									continue
								else:
									label = ""
									if field["Label"].strip() != "":
										label = field["Label"]
									else:
										label = field["Description"]
										
									if label == "": continue
									label = label.strip()
									
									option = (field["id"], label.replace(":", ""))
									ret.append (option)
							
		except Exception as e:
			self.logger.error (ext.getException(e))	
			
		return self._cleanReturnList (ret)
	
	#
	# Get a list of states suitable for a list or menu UI field
	#
	def getStateUIList (self, obj, showUi = False):
		ret = []
		
		try:
			data = self._resolveObject (obj)
			if len(data[0]) == 0: return ret
			
			plugInfo = data[0]
			deviceTypeId = data[1]				
			
			if "xml" in plugInfo == False: return ret
			if "devices" in plugInfo["xml"] == False: return ret
										
			ret = self._getStateUIList (obj, plugInfo, deviceTypeId, showUi)
							
		except Exception as e:
			self.logger.error (ext.getException(e))	
			
		return self._cleanReturnList (ret)
		
	#
	# Run the state list builder from getStateUIList
	#
	def _getStateUIList (self, obj, plugInfo, deviceTypeId, showUi = False):
		ret = []
		statesfound = []

		try:
			for id, info in plugInfo["xml"]["devices"].iteritems():
				if id == deviceTypeId:
					for state in info["States"]:
						if state["Type"] == 0:
							option = ("-line-", self.factory.ui.getSeparator())
							ret.append(option)
						else:
							option = (state["Key"], state["StateLabel"])
							ret.append (option)
														
			# Add Indigo built-in device states
			retIndigo = self.factory.ui.getBuiltInStates (obj)
			if len(retIndigo) > 0: 
				option = ("-line-", self.factory.ui.getSeparator())
				retIndigo.append(option)
				ret = retIndigo + ret
			
			# Compare actual object states to the states found to pick up stragglers
			retadded = []
			for state, statevalue in obj.states.iteritems():
				isFound = False
				for opt in ret:
					if opt[0] == state: 
						isFound = True
						continue
						
				if isFound: continue
				
				if len(state) > 4:
					if state[-3:] == ".ui": continue # don't confuse the poor user, plugins can decide to use the .ui version if needed
				
				option = (state, self.factory.ui.resolveStateNameToString(state))
				retadded.append(option)	
				
				if state + ".ui" in obj.states and showUi:
					option = (state + ".ui", self.factory.ui.resolveStateNameToString(state) + " (UI Value)")
					retadded.append(option)	
				
			if len(ret) > 0 and len(retadded) > 0:
				option = ("-line-", self.factory.ui.getSeparator())
				ret.append(option)
				
			ret += retadded	
			
			
		
		except Exception as e:
			self.logger.error (ext.getException(e))	
			
		return ret
	
	#
	# Get a list of fields suitable for a list or menu UI field
	#
	def getActions (self, obj):
		ret = {}
		
		try:
			data = self._resolveObject (obj)
			if len(data[0]) == 0: return ret
			
			plugInfo = data[0]
			deviceTypeId = data[1]				
			
			if "xml" in plugInfo == False: return ret
			
			# For some reason the line below doesn't return false properly, using IF ELSE instead
			#if "actions" in plugInfo["xml"] == False: return ret
			if "actions" in plugInfo["xml"]:
				pass
			else:
				return ret
			
			for id, action in plugInfo["xml"]["actions"].iteritems():
				isOk = True
				
				if "DeviceFilter" in action:
					isOk = self._isForDevice (plugInfo, deviceTypeId, action["DeviceFilter"])
				
				if isOk:
					if deviceTypeId[0:7] == "indigo.":
						ret["indigo_" + id] = action
					else:
						ret["plugin_" + id] = action
				 
			# Add Indigo actions as long as this was not already done above
			if deviceTypeId[0:7] != "indigo.":
				data = self._resolveIndigoDevice (obj)
				
				for id, action in data[0]["xml"]["actions"].iteritems():
					isOk = True
				
					if "DeviceFilter" in action:
						isOk = self._isForDevice (data[0], data[1], action["DeviceFilter"])
				
					if isOk:
						ret["indigo_" + id] = action
						
		except Exception as e:
			self.logger.error (ext.getException(e))	
			
		return ret
		
	#
	# Get a list of variable actions suitable for a list or menu UI field
	#
	def getVariableActionUIList (self, showUIConfig = False):
		ret = []
		
		try:
			plugInfo = self.pluginCache["Indigo"]
			deviceTypeId = "indigo.variable"	
			
			if "xml" in plugInfo == False: return ret
			if "actions" in plugInfo["xml"] == False: return ret
			
			ret = self._getActionUIList (plugInfo, deviceTypeId, showUIConfig, "indigo_")
			
		except Exception as e:
			self.logger.error (ext.getException(e))	
			
		return self._cleanReturnList (ret)
		
	#
	# Get a list of sesrver actions suitable for a list or menu UI field
	#
	def getServerActionUIList (self, showUIConfig = False):
		ret = []
		
		try:
			plugInfo = self.pluginCache["Indigo"]
			deviceTypeId = "indigo.server"	
			
			if "xml" in plugInfo == False: return ret
			if "actions" in plugInfo["xml"] == False: return ret
			
			ret = self._getActionUIList (plugInfo, deviceTypeId, showUIConfig, "indigo_")
			
		except Exception as e:
			self.logger.error (ext.getException(e))	
			
		return self._cleanReturnList (ret)
		
	#
	# Get a list of actions suitable for a list or menu UI field
	#
	def getActionUIList (self, obj, showUIConfig = False):
		ret = []
		
		try:
			data = self._resolveObject (obj)
			if len(data[0]) == 0: return ret
			
			plugInfo = data[0]
			deviceTypeId = data[1]				
			
			if "xml" in plugInfo == False: return ret
			
			#if "actions" in plugInfo["xml"] == False: return ret
			if "actions" in plugInfo["xml"]:
				pass
			else:
				return ret
			
			if deviceTypeId[0:7] == "indigo.":
				ret = self._getActionUIList (plugInfo, deviceTypeId, showUIConfig, "indigo_")
			else:
				ret = self._getActionUIList (plugInfo, deviceTypeId, showUIConfig)
			
			# Add Indigo actions as long as this was not already done above
			if deviceTypeId[0:7] != "indigo.":
				data = self._resolveIndigoDevice (obj)
				retEx = self._getActionUIList (data[0], data[1], showUIConfig, "indigo_") 
				retEx.append (("-line-", self.factory.ui.getSeparator()))
				ret = retEx + ret
			
		except Exception as e:
			self.logger.error (ext.getException(e))	
			
		return self._cleanReturnList (ret)
		
	#
	# Run the action list builder from getActionUIList
	#
	def _getActionUIList (self, plugInfo, deviceTypeId, showUIConfig, prefix = "plugin_"):
		ret = []
		
		try:
			# Run through every device action and add a placeholder, we'll clean up after
			for id, action in plugInfo["xml"]["actions"].iteritems():
				ret.append ("")
							
			for id, action in plugInfo["xml"]["actions"].iteritems():
				isOk = True
				
				if "DeviceFilter" in action:
					isOk = self._isForDevice (plugInfo, deviceTypeId, action["DeviceFilter"])
					
				if "ConfigUI" in action:
					if showUIConfig == False and len(action["ConfigUI"]) > 0: isOk = False

				if isOk:
					if action["Name"] == " - ":
						option = ("-line-", self.factory.ui.getSeparator())
						ret[action["SortOrder"]] = option	
						
					else:
						option = (prefix + id, action["Name"])
						ret[action["SortOrder"]] = option	
		
		except Exception as e:
			self.logger.error (ext.getException(e))	
			
		return ret
		
	#
	# Clean up a return list
	#
	def _cleanReturnList (self, dirtyList):
		ret = []
		
		try:
			lastRetItem = ""
			
			for i in range (0, len(dirtyList)):
				try:
					if lastRetItem != "": 
						if lastRetItem == dirtyList[i]: continue # don't add successive duplicates (mostly lines)
						
					if dirtyList[i] != "": lastRetItem = dirtyList[i]
											
					if dirtyList[i] is not None and dirtyList[i] != "": ret.append(dirtyList[i])
				except:
					continue
					
			if len(ret) > 0:
				# Make sure we don't start on a line
				if ret[0] == ("-line-", self.factory.ui.getSeparator()):
					del ret[0]
			
				# Make sure we don't end on a line
				if len(ret) > 0 and ret[len(ret) - 1] == ("-line-", self.factory.ui.getSeparator()):
					del ret[len(ret) - 1]
					
			return ret
		
		except Exception as e:
			self.logger.error (ext.getException(e))	
			
		return dirtyList
		
		
	#
	# Compare filter string to device type
	#
	def _isForDevice (self, plugInfo, deviceTypeId, filter):
		try:
			if self._deviceMatchesFilter (plugInfo, deviceTypeId, filter): return True
			
			filters = filter.split(",")
			for f in filters:
				if self._deviceMatchesFilter (plugInfo, deviceTypeId, f): return True
		
		except Exception as e:
			self.logger.error (ext.getException(e))	
		
		return False
		
	#
	# Check if a device type matches a filter
	#
	def _deviceMatchesFilter (self, plugInfo, deviceTypeId, filter):
		try:
			#self.logger.threaddebug ("Checking if filter '{0}' matches device type of '{1}'".format(filter, deviceTypeId))
		
			if filter == "": return True # Global
			filter = filter.strip()
			
			if filter == "self": return True # Global
			if filter == "self." + deviceTypeId: return True # Direct
			if filter == deviceTypeId: return True # Direct
			if filter == plugInfo["id"]: return True # Global
			if filter == plugInfo["id"] + "." + deviceTypeId: return True # Direct
			
			
		except Exception as e:
			self.logger.error (ext.getException(e))	
			
		#self.logger.threaddebug ("Filter '{0}' does not match device type of '{1}'".format(filter, deviceTypeId))
		
		return False
		
	#
	# Resolve an object to it's local plugin details
	#
	def _resolveObject (self, obj):
		try:
			plugInfo = None
			deviceTypeId = ""
			
			if type(obj) is str:
				self.logger.threaddebug ("Object is typed as '{0}'".format(unicode(type(obj))))
			else:
				self.logger.threaddebug ("Object '{0}' is typed as '{1}'".format(obj.name, unicode(type(obj))))
			
			if type(obj) is indigo.Variable:
				return self._resolveIndigoDevice (obj)
				
			elif type(obj) is indigo.Schedule:
				X = 1
				
			elif type(obj) is indigo.Trigger:
				X = 1
				
			elif type(obj) is indigo.ActionGroup:
				X = 1
				
			elif type(obj) is str:
				if obj == "server":
					plugInfo = self.pluginCache["Indigo"]
					deviceTypeId = "indigo.server"
				
			else:
				# It's a device
				if obj.pluginId != "":
					plugInfo = self.getPluginCacheItem (obj.pluginId)
					deviceTypeId = obj.deviceTypeId
				else:
					# It's an indigo built in device
					return self._resolveIndigoDevice (obj)
				
			return (plugInfo, deviceTypeId)
		
		except Exception as e:
			self.logger.error (ext.getException(e))	
			return ({}, "")
			
	#
	# Return Indigo device info
	#
	def _resolveIndigoDevice (self, obj):
		try:
			plugInfo = None
			deviceTypeId = ""
			
			#plugInfo = self.pluginCache["Indigo"] # It's Indigo
			plugInfo = self.getPluginCacheItem ("Indigo")
			if type(obj) is indigo.RelayDevice: deviceTypeId = "indigo.relay"
			if type(obj) is indigo.DimmerDevice: deviceTypeId = "indigo.dimmer"
			if type(obj) is indigo.indigo.MultiIODevice: deviceTypeId = "indigo.iodevice"
			if type(obj) is indigo.SensorDevice: deviceTypeId = "indigo.sensor"
			if type(obj) is indigo.SpeedControlDevice: deviceTypeId = "indigo.speedcontrol"
			if type(obj) is indigo.SprinklerDevice: deviceTypeId = "indigo.sprinkler"
			if type(obj) is indigo.ThermostatDevice: deviceTypeId = "indigo.thermostat"
			
			if type(obj) is indigo.Variable: deviceTypeId = "indigo.variable"
		
			return (plugInfo, deviceTypeId)
		
		except Exception as e:
			self.logger.error (ext.getException(e))	
			return ({}, "")
	
	################################################################################
	# PARSER
	################################################################################	
	
	#
	# Retrieve an item from cache or cache it if its not there
	#
	def getPluginCacheItem (self, pluginId):
		try:
			#indigo.server.log(pluginId)
				
			if pluginId in self.pluginCache:
				plugInfo = self.pluginCache[pluginId]
				
				if "xml" not in plugInfo:
					# Retrieve it
					return self.getDetailsByPluginId (pluginId)
				else:
					return plugInfo
					
			else:
				# This should never get called since we cache basics when the library is loaded
				#indigo.server.log(unicode(self.pluginCache))
				
				self.cachePluginInformation()
				return self.getDetailsByPluginId (pluginId)
				
					
		except Exception as e:
			self.logger.error (ext.getException(e))	
			
		return None
				
	
	#
	# Get and store the BASIC plugin information (the info xml essentially)
	#
	def cachePluginInformation (self):
		try:
			base = indigo.server.getInstallFolderPath() + "/Plugins"
			plugins = glob.glob(base + "/*.indigoPlugin")
			
			for plugin in plugins:
				try:
					plugInfo = self._parsePlist (plugin)
					self.pluginCache[plugInfo["id"]] = plugInfo
					
				except Exception as ex:
					self.logger.error (ext.getException(ex))		
			
		
		except Exception as e:
			self.logger.error (ext.getException(e))	
			
	#
	# Get plugin info based on an object
	#
	def getDetailsByObject (self, obj):
		try:
			# Determine if we are reading a plugin device or Indigo device
			objtype = str(type(obj)).replace("<class '", "").replace("'>", "")
			
			#indigo.server.log(objtype)
			
			# Filter out objects we do not yet support
			if objtype == "indigo.ActionGroup":
				self.logger.error ("Plugdetail beta doesn't currently process Action Groups, cannot get actions for '{0}'".format(obj.name))
				return None
			
			elif objtype == "indigo.ControlPage":
				self.logger.error ("Plugdetail beta doesn't currently process Control Pages, cannot get actions for '{0}'".format(obj.name))
				return None
				
			elif "Trigger" in objtype:
				self.logger.error ("Plugdetail beta doesn't currently process Triggers, cannot get actions for '{0}'".format(obj.name))
				return None	
				
			elif objtype == "indigo.Schedule":
				self.logger.error ("Plugdetail beta doesn't currently process Schedules, cannot get actions for '{0}'".format(obj.name))
				return None	
				
			elif objtype == "indigo.Variable":
				self.logger.error ("Plugdetail beta doesn't currently process Variables, cannot get actions for '{0}'".format(obj.name))
				return None		
				
			return self.getDetailsByPluginId (obj.pluginId, obj.deviceTypeId)
			
		except Exception as e:
			#raise
			self.logger.error (ext.getException(e))	
	
	#
	# Get the plugin details for a given object
	#
	def getDetailsByPluginId (self, pluginId, deviceTypeId = ""):
		try:
			# In case this isn't a plugin, assume the Indigo information
			plugInfo = indigo.Dict()
			plugInfo["id"] = "Indigo"
			plugInfo["name"] = "Indigo"
			plugInfo["path"] = ""
			
			# If it's a plugin then get the basic plugin info, otherwise claim it as Indigo
			if pluginId != "":
				if pluginId in self.pluginCache: plugInfo = self.pluginCache[pluginId]
			
			try:
				pluginXML = indigo.Dict()
				
				# If we are caching our own information then also cache the Indigo built-in actions since it's in our plugin folders.  This will
				# actually cache rather than simply return details
				if plugInfo["id"] == self.factory.plugin.pluginId:
					plugInfoEx = indigo.Dict()
					plugInfoEx["id"] = "Indigo"
					plugInfoEx["name"] = "Indigo"
					plugInfoEx["path"] = ""
				
					if os.path.isfile(plugInfo["path"] + "/Contents/Server Plugin/lib/actionslib.xml"):
						pluginXML["actions"] = self._parseActionsXML(plugInfo["path"] + "/Contents/Server Plugin/lib/actionslib.xml")
					
					pluginXML["devices"] = indigo.Dict() # Placeholder
					
					plugInfoEx["xml"] = pluginXML	
					self.pluginCache["Indigo"] = plugInfoEx
					#indigo.server.log(unicode(plugInfoEx))	
					
				if os.path.isfile(plugInfo["path"] + "/Contents/Server Plugin/Devices.xml"):
					pluginXML["devices"] = self._parseDevicesXML(plugInfo["path"] + "/Contents/Server Plugin/Devices.xml")
										
				if os.path.isfile(plugInfo["path"] + "/Contents/Server Plugin/Actions.xml"):
					pluginXML["actions"] = self._parseActionsXML(plugInfo["path"] + "/Contents/Server Plugin/Actions.xml")
				
				plugInfo["xml"] = pluginXML
				
			except Exception as e:
				self.logger.error ("Exception encountered with " + unicode(plugInfo["path"]) + " (this error is NOT critical)")
				self.logger.error (ext.getException(e))
			
			#indigo.server.log (unicode(plugInfo))
			return plugInfo
			
		except Exception as e:
			#raise
			self.logger.error (ext.getException(e))	
			
	#
	# Parse plist line data (pretty low brow but since plist breaks standard XML reads it works for now)
	#
	def _parsePlist (self, path):
		plugDict = indigo.Dict()
		plugDict["path"] = path
		
		try:
			plist = open(path + "/Contents/Info.plist")
			nameIdx = 0
			name = ""
			idIdx = 0
			id = ""
			for line in plist:
				if nameIdx == 1:
					name = line
					nameIdx = 0
					continue
					
				if idIdx == 1:
					id = line
					idIdx = 0
					continue
					
				x = string.find (line, 'CFBundleDisplayName')
				if x > -1: nameIdx = 1
				
				x = string.find (line, 'CFBundleIdentifier')
				if x > -1: idIdx = 1
				
			#indigo.server.log (name + "\t" + id)
			
			x = string.find (name, "<string>")
			y = string.find (name, "</string>")
			name = name[x + 8:y]
			
			x = string.find (id, "<string>")
			y = string.find (id, "</string>")
			id = id[x + 8:y]
			
			#return self.plugRecord (path, id, name)
			plugDict["id"] = id
			plugDict["name"] = name
					
		except Exception as e:
			self.logger.error (ext.getException(e))	
		
		#return self.plugRecord (path, "Unknown", "Unknown")	
		return plugDict
			
			
	################################################################################
	def _getChildElementsByTagName(self, elem, tagName):
		childList = []
		for child in elem.childNodes:
			if child.nodeType == child.ELEMENT_NODE and (tagName == u"*" or child.tagName == tagName):
				childList.append(child)
		return childList

	def _getXmlFromFile(self, filename):
		if not os.path.isfile(filename):
			return u""
		xml_file = file(filename, 'r')
		xml_data = xml_file.read()
		xml_file.close()
		return xml_data

	def _getXmlFromTemplate(self, templateName):
		filename = indigo.host.resourcesFolderPath + '/templates/' + templateName
		return self._getXmlFromFile(filename)

	def _getElementAttribute(self, elem, attrName, required=True, default=None, errorIfNotAscii=True, filename=u"unknown"):
		attrStr = elem.getAttribute(attrName)
		if attrStr is None or len(attrStr) == 0:
			if required:
				raise ValueError(u"required XML attribute '%s' is missing or empty in file %s" % (attrName,filename))
			return default
		elif errorIfNotAscii and attrStr[0] not in string.ascii_letters:
			raise ValueError(u"XML attribute '%s' in file %s has a value that starts with invalid characters: '%s' (should begin with A-Z or a-z):\n%s" % (attrName,filename,attrStr,elem.toprettyxml()))
		return attrStr

	def _getElementValueByTagName(self, elem, tagName, required=True, default=None, filename=u"unknown"):
		valueElemList = self._getChildElementsByTagName(elem, tagName)
		if len(valueElemList) == 0:
			if required:
				raise ValueError(u"required XML element <%s> is missing in file %s" % (tagName,filename))
			return default
		elif len(valueElemList) > 1:
			raise ValueError(u"found more than one XML element <%s> (should only be one) in file %s" % (tagName,filename))

		valueStr = valueElemList[0].firstChild.data
		if valueStr is None or len(valueStr) == 0:
			if required:
				raise ValueError(u"required XML element <%s> is empty in file %s" % (tagName,filename))
			return default
		return valueStr
		
	################################################################################	
		
	def _parseMenuItemsXML(self, filename):
		if not os.path.isfile(filename):
			return
		try:
			dom = xml.dom.minidom.parseString(self._getXmlFromFile(filename))
		except:
			raise
			raise LookupError(u"%s is malformed" % (filename))
		menuItemsElem = self._getChildElementsByTagName(dom, u"MenuItems")
		if len(menuItemsElem) != 1:
			raise LookupError(u"Incorrect number of <MenuItems> elements found in file %s" % (filename))

		menuItems = self._getChildElementsByTagName(menuItemsElem[0], u"MenuItem")
		for menu in menuItems:
			menuDict = indigo.Dict()
			menuId = self._getElementAttribute(menu, u"id", filename=filename)
			if menuId in self.menuItemsDict:
				raise LookupError(u"Duplicate menu id (%s) found in file %s" % (menuId, filename))

			menuDict[u"Id"] = menuId
			menuDict[u"Name"] = self._getElementValueByTagName(menu, u"Name", False, filename=filename)

			if "Name" in menuDict:
				menuDict[u"ButtonTitle"] = self._getElementValueByTagName(menu, u"ButtonTitle", False, filename=filename)

				# Plugin should specify at least a CallbackMethod or ConfigUIRawXml (possibly both)
				menuDict[u"CallbackMethod"] = self._getElementValueByTagName(menu, u"CallbackMethod", False, filename=filename)
				configUIList = self._getChildElementsByTagName(menu, u"ConfigUI")
				if len(configUIList) > 0:
					#menuDict[u"ConfigUIRawXml"] = self._parseConfigUINode(dom, configUIList[0], filename=filename).toxml()
					menuDict[u"ConfigUI"] = self._parseConfigUINode (dom, configUIList[0])
				else:
					if not "CallbackMethod" in menuDict:
						raise ValueError(u"<MenuItem> elements must contain either a <CallbackMethod> and/or a <ConfigUI> element")

			self.menuItemsList.append(menuDict)
			self.menuItemsDict[menuId] = menuDict
			
	###################
	def _getDeviceStateDictForType(self, type, stateId, triggerLabel, controlPageLabel, disabled=False):
		stateDict = indigo.Dict()
		stateDict[u"Type"] = int(type)
		stateDict[u"Key"] = stateId
		stateDict[u"Disabled"] = disabled
		stateDict[u"TriggerLabel"] = triggerLabel
		stateDict[u"StateLabel"] = controlPageLabel
		return stateDict

	def getDeviceStateDictForSeparator(self, stateId):
		return self._getDeviceStateDictForType(indigo.kTriggerKeyType.Label, stateId, u"_Separator", u"_Separator", True)

	def getDeviceStateDictForSeperator(self, stateId):
		return self.getDeviceStateDictForSeparator(stateId)

	def getDeviceStateDictForNumberType(self, stateId, triggerLabel, controlPageLabel, disabled=False):
		return self._getDeviceStateDictForType(indigo.kTriggerKeyType.Number, stateId, triggerLabel, controlPageLabel, disabled)

	def getDeviceStateDictForStringType(self, stateId, triggerLabel, controlPageLabel, disabled=False):
		return self._getDeviceStateDictForType(indigo.kTriggerKeyType.String, stateId, triggerLabel, controlPageLabel, disabled)

	def getDeviceStateDictForEnumType(self, stateId, triggerLabel, controlPageLabel, disabled=False):
		return self._getDeviceStateDictForType(indigo.kTriggerKeyType.Enumeration, stateId, triggerLabel, controlPageLabel, disabled)

	def getDeviceStateDictForBoolOnOffType(self, stateId, triggerLabel, controlPageLabel, disabled=False):
		stateDict = self._getDeviceStateDictForType(indigo.kTriggerKeyType.BoolOnOff, stateId, triggerLabel, controlPageLabel, disabled)
		stateDict[u"StateLabel"] = stateDict[u"StateLabel"] + u" (on or off)"
		return stateDict

	def getDeviceStateDictForBoolYesNoType(self, stateId, triggerLabel, controlPageLabel, disabled=False):
		stateDict = self._getDeviceStateDictForType(indigo.kTriggerKeyType.BoolYesNo, stateId, triggerLabel, controlPageLabel, disabled)
		stateDict[u"StateLabel"] = stateDict[u"StateLabel"] + u" (yes or no)"
		return stateDict

	def getDeviceStateDictForBoolOneZeroType(self, stateId, triggerLabel, controlPageLabel, disabled=False):
		stateDict = self._getDeviceStateDictForType(indigo.kTriggerKeyType.BoolOneZero, stateId, triggerLabel, controlPageLabel, disabled)
		stateDict[u"StateLabel"] = stateDict[u"StateLabel"] + u" (1 or 0)"
		return stateDict

	def getDeviceStateDictForBoolTrueFalseType(self, stateId, triggerLabel, controlPageLabel, disabled=False):
		stateDict = self._getDeviceStateDictForType(indigo.kTriggerKeyType.BoolTrueFalse, stateId, triggerLabel, controlPageLabel, disabled)
		stateDict[u"StateLabel"] = stateDict[u"StateLabel"] + u" (true or false)"
		return stateDict

	def _parseActionsXML(self, filename):
		ret = indigo.Dict()
		
		if not os.path.isfile(filename):
			return
		try:
			dom = xml.dom.minidom.parseString(self._getXmlFromFile(filename))
		except:
			raise LookupError(u"%s is malformed" % (filename))
		actionsElement = self._getChildElementsByTagName(dom, u"Actions")
		if len(actionsElement) != 1:
			raise LookupError(u"Incorrect number of <Actions> elements found in file %s" % (filename))

		sortIndex = 0
		actionElemList = self._getChildElementsByTagName(actionsElement[0], u"Action")
		for action in actionElemList:
			serverVers = self._getElementAttribute(action, u"_minServerVers", required=False, errorIfNotAscii=False, filename=filename)
			if serverVers is not None and not PluginBase.serverVersCompatWith(serverVers):
				continue	# This version of Indigo Server isn't compatible with this object (skip it)

			actionDict = indigo.Dict()
			actionTypeId = self._getElementAttribute(action, u"id", filename=filename)
			try:
				actionDict[u"DeviceFilter"] = self._getElementAttribute(action, u"deviceFilter", False, u"", filename=filename)
				actionDict[u"Name"] = self._getElementValueByTagName(action, u"Name", filename=filename)
				actionDict[u"CallbackMethod"] = self._getElementValueByTagName(action, u"CallbackMethod", filename=filename)				
			except ValueError:
				# It's missing <Name> or <CallbackMethod> so treat it as a separator
				actionDict[u"Name"] = u" - "
				actionDict[u"CallbackMethod"] = u""
				#actionDict[u"DeviceFilter"] = u""
			actionDict[u"UiPath"] = self._getElementAttribute(action, u"uiPath", required=False, filename=filename)
			actionDict[u"PrivateUiPath"] = self._getElementAttribute(action, u"privateUiPath", required=False, filename=filename)
			actionDict[u"SortOrder"] = sortIndex
			sortIndex += 1

			configUIList = self._getChildElementsByTagName(action, u"ConfigUI")
			if len(configUIList) > 0:
				#actionDict[u"ConfigUIRawXml"] = self._parseConfigUINode(dom, configUIList[0], filename=filename).toxml()
				actionDict[u"ConfigUI"] = self._parseConfigUINode(dom, configUIList[0], filename=filename)
				
			#self.actionsTypeDict[actionTypeId] = actionDict
			ret[actionTypeId] = actionDict

		return ret

	def _parseDevicesXML(self, filename):
		ret = indigo.Dict()
		
		if not os.path.isfile(filename):
			return
		try:
			dom = xml.dom.minidom.parseString(self._getXmlFromFile(filename))
		except Exception, e:
			self.logger.error(u"%s has an error: %s" % (filename, unicode(e)))
			raise LookupError(u"%s is malformed" % (filename))

		# Now get all devices from the <Devices> element
		devicesElement = self._getChildElementsByTagName(dom, u"Devices")
		if len(devicesElement) != 1:
			raise LookupError(u"Incorrect number of <Devices> elements found in file %s" % (filename))

		# Look for a DeviceFactory element - that will be used to create devices
		# rather than creating them directly using the <Device> XML. This allows
		# a plugin to discover device types rather than forcing the user to select
		# the type up-front (like how INSTEON devices are added).
		deviceFactoryElements = self._getChildElementsByTagName(devicesElement[0], u"DeviceFactory")
		if len(deviceFactoryElements) > 1:
			raise LookupError(u"Incorrect number of <DeviceFactory> elements found in file %s" % (filename))
		elif len(deviceFactoryElements) == 1:
			deviceFactory = deviceFactoryElements[0]
			elems = self._getChildElementsByTagName(deviceFactory, u"Name")
			if len(elems) != 1:
				raise LookupError(u"<DeviceFactory> element must contain exactly one <Name> element in file %s" % (filename))
			elems = self._getChildElementsByTagName(deviceFactory, u"ButtonTitle")
			if len(elems) != 1:
				raise LookupError(u"<DeviceFactory> element must contain exactly one <ButtonTitle> element in file %s" % (filename))
			elems = self._getChildElementsByTagName(deviceFactory, u"ConfigUI")
			if len(elems) != 1:
				raise LookupError(u"<DeviceFactory> element must contain exactly one <ConfigUI> element in file %s" % (filename))
			self.deviceFactoryXml = deviceFactory.toxml()
		else:
			self.deviceFactoryXml = None

		sortIndex = 0
		deviceElemList = self._getChildElementsByTagName(devicesElement[0], u"Device")
		for device in deviceElemList:
			
			deviceDict = indigo.Dict()
			deviceTypeId = self._getElementAttribute(device, u"id", filename=filename)
			deviceDict[u"Type"] = self._getElementAttribute(device, u"type", filename=filename)
			if deviceDict[u"Type"] not in validDeviceTypes:
				raise LookupError(u"Unknown device type in file %s" % (filename))
			deviceDict[u"Name"] = self._getElementValueByTagName(device, u"Name", filename=filename)
			deviceDict[u"DisplayStateId"] = self._getElementValueByTagName(device, u"UiDisplayStateId", required=False, default=u"", filename=filename)
			deviceDict[u"SortOrder"] = sortIndex
			sortIndex += 1

			configUIList = self._getChildElementsByTagName(device, u"ConfigUI")
			if len(configUIList) > 0:
				#deviceDict[u"ConfigUIRawXml"] = self._parseConfigUINode(dom, configUIList[0], filename=filename).toxml()
				deviceDict[u"ConfigUI"] = self._parseConfigUINode(dom, configUIList[0], filename=filename)

			deviceStatesElementList = self._getChildElementsByTagName(device, u"States")
			statesList = indigo.List()
			if len(deviceStatesElementList) > 1:
				raise LookupError(u"Incorrect number of <States> elements found in file %s" % (filename))
			elif len(deviceStatesElementList) == 1:
				deviceStateElements = self._getChildElementsByTagName(deviceStatesElementList[0], u"State")
				for state in deviceStateElements:
					stateId = self._getElementAttribute(state, u"id", filename=filename)
					triggerLabel = self._getElementValueByTagName(state, u"TriggerLabel", required=False, default=u"", filename=filename)
					controlPageLabel = self._getElementValueByTagName(state, u"ControlPageLabel", required=False, default=u"", filename=filename)

					disabled = False	# ToDo: need to read this?
					stateValueTypes = self._getChildElementsByTagName(state, u"ValueType")
					if len(stateValueTypes) != 1:
						raise LookupError(u"<State> elements must have exactly one <ValueType> element in file %s" % (filename))

					valueListElements = self._getChildElementsByTagName(stateValueTypes[0], u"List")
					if len(valueListElements) > 1:
						raise LookupError(u"<ValueType> elements must have zero or one <List> element in file %s" % (filename))
					elif len(valueListElements) == 1:
						# It must have a TriggerLabel and a ControlPageLabel
						if (triggerLabel == "") or (controlPageLabel == ""):
							raise LookupError(u"State elements must have both a TriggerLabel and a ControlPageLabel in file %s" % (filename))
						# It's an enumeration -- add an enum type for triggering off of any changes
						# to this enumeration type:
						stateDict = self.getDeviceStateDictForEnumType(stateId, triggerLabel, controlPageLabel, disabled)
						statesList.append(stateDict)

						# And add individual true/false types for triggering off every enumeration
						# value possiblity (as specified by the Option list):
						triggerLabelPrefix = self._getElementValueByTagName(state, u"TriggerLabelPrefix", required=False, default=u"", filename=filename)
						controlPageLabelPrefix = self._getElementValueByTagName(state, u"ControlPageLabelPrefix", required=False, default=u"", filename=filename)

						valueOptions = self._getChildElementsByTagName(valueListElements[0], u"Option")
						if len(valueOptions) < 1:
							raise LookupError(u"<List> elements must have at least one <Option> element in file %s" % (filename))
						for option in valueOptions:
							subStateId = stateId + u"." + self._getElementAttribute(option, u"value", filename=filename)

							if len(triggerLabelPrefix) > 0:
								subTriggerLabel = triggerLabelPrefix + u" " + option.firstChild.data
							else:
								subTriggerLabel = option.firstChild.data

							if len(controlPageLabelPrefix) > 0:
								subControlPageLabel = controlPageLabelPrefix + u" " + option.firstChild.data
							else:
								subControlPageLabel = option.firstChild.data

							subDisabled = False		# ToDo: need to read this?

							subStateDict = self.getDeviceStateDictForBoolTrueFalseType(subStateId, subTriggerLabel, subControlPageLabel, subDisabled)
							statesList.append(subStateDict)
					else:
						# It's not an enumeration
						stateDict = None
						valueType = stateValueTypes[0].firstChild.data.lower()
						# It must have a TriggerLabel and a ControlPageLabel if it's not a separator
						if (valueType != u"separator"):
							if (triggerLabel == "") or (controlPageLabel == ""):
								raise LookupError(u"State elements must have both a TriggerLabel and a ControlPageLabel in file %s" % (filename))
						if valueType == u"boolean":
							boolType = stateValueTypes[0].getAttribute(u"boolType").lower()
							if boolType == u"onoff":
								stateDict = self.getDeviceStateDictForBoolOnOffType(stateId, triggerLabel, controlPageLabel, disabled)
							elif boolType == u"yesno":
								stateDict = self.getDeviceStateDictForBoolYesNoType(stateId, triggerLabel, controlPageLabel, disabled)
							elif boolType == u"onezero":
								stateDict = self.getDeviceStateDictForBoolOneZeroType(stateId, triggerLabel, controlPageLabel, disabled)
							else:
								stateDict = self.getDeviceStateDictForBoolTrueFalseType(stateId, triggerLabel, controlPageLabel, disabled)
						elif valueType == u"number" or valueType == u"float" or valueType == u"integer":
							stateDict = self.getDeviceStateDictForNumberType(stateId, triggerLabel, controlPageLabel, disabled)
						elif valueType == u"string":
							stateDict = self.getDeviceStateDictForStringType(stateId, triggerLabel, controlPageLabel, disabled)
						elif valueType == u"separator":
							stateDict = self.getDeviceStateDictForSeparator(stateId)

						if stateDict:
							statesList.append(stateDict)
			deviceDict[u"States"] = statesList
	
			ret[deviceTypeId] = deviceDict

		return ret
	def _parseConfigUINode(self, mainDom, configUI, filename=u"unknown"):
		UIDict = indigo.Dict()
				
		fieldElements = self._getChildElementsByTagName(configUI, u"Field")
		if len(fieldElements) > 0:
			fieldList = indigo.List()
			
			for field in fieldElements:
				fieldDict = indigo.Dict()
				fieldDict["separator"] = False
				
				try:		
					fieldDict["id"] = self._getElementAttribute(field, u"id", required=True, default="", errorIfNotAscii=False, filename=filename)
				except:
					fieldDict["id"] = ""
					
				try:		
					fieldDict["ValueType"] = self._getElementAttribute(field, u"valueType", required=False, default="", errorIfNotAscii=False, filename=filename)
				except:
					fieldDict["ValueType"] = "string"
					
				try:		
					fieldDict["Default"] = self._getElementAttribute(field, u"defaultValue", required=False, default="", errorIfNotAscii=False, filename=filename)
				except:
					fieldDict["Default"] = None
					
				fieldDict["type"] = fieldId = self._getElementAttribute(field, u"type", filename=filename)
				if fieldDict["type"].lower() == "separator": fieldDict["separator"] = True
				isHidden = self._getElementAttribute(field, u"hidden", required=False, default="false", filename=filename)
				if isHidden.lower() == "true": 
					fieldDict["hidden"] = True
				else:
					fieldDict["hidden"] = False
					
				try:
					fieldDict["Label"] = self._getElementValueByTagName(field, u"Label", required=False, default="", filename=filename)
				except:
					fieldDict["Label"] = ""
					
				try:
					fieldDict["Description"] = self._getElementValueByTagName(field, u"Description", required=False, default="", filename=filename)
				except:
					fieldDict["Description"] = ""
			
				listList = indigo.List()
				
				listElements = self._getChildElementsByTagName(field, u"List")
				if len(listElements) > 0:
					listDict = indigo.Dict()
					listDict["class"] = self._getElementAttribute(listElements[0], u"class", required=False, default="", filename=filename)
					
					optionsList = indigo.List()
					
					optionElements = self._getChildElementsByTagName(listElements[0], u"Option")
					if len(optionElements) > 0:
						for option in optionElements:
							optionDict = indigo.Dict()
							optionDict["value"] = self._getElementAttribute(option, u"value", required=False, default="", errorIfNotAscii=False, filename=filename)
							optionDict["Label"] = option.childNodes[0].data
							optionsList.append(optionDict)
						
					listDict["Options"] = optionsList
						
					listList.append(listDict)
					
				fieldDict["List"] = listList
			
				fieldList.append(fieldDict)
				
			UIDict["Fields"] = fieldList
		
		return UIDict