def set_field_value(self, attrib, value): """ Set the value of an attribute to the value provided, setting the change flag if different. """ try: thisUpdated = False if attrib in dir(self): if getattr(self, attrib) != value: self.updated = True thisUpdated = True setattr(self, attrib, value) if self.debug and thisUpdated: self.logger.info( "\nDEBUG MODE: CHANGED FIELD {} TO {}\n{}".format( attrib, value, unicode(self))) return True except Exception as e: self.logger.error(ex.stack_trace(e)) return False
def generic_class_to_dict(obj): """ Using the same exclusions as generic_unicode_output, convert class data to a dictionary object. """ try: data = {} for a in dir(obj): if callable(getattr(obj, a)): continue if a.startswith("_"): continue if a == "factory": continue if a == "logger": continue if a == "tabtitle" or a == "tabcontent": continue if type(getattr(obj, a)) == list: listitem = [] for l in getattr(obj, a): if "'instance'" in unicode(type(l)): listitem.append(generic_class_to_dict(l)) else: listitem.append(l) data[a] = listitem else: data[a] = getattr(obj, a) except Exception as e: e.args += (unicode(type(obj)), ) raise CalcsException(kExceptionOutputPrefix + ex.stack_trace(e)) return data
def build_dict(self): try: rec = {} rec["name"] = self.name rec["index"] = self.index for a in dir(self): if a == "debug": continue if a == "name": continue if a == "index": continue if a == "logger": continue if a.startswith("_"): continue if a == "set_field_value": continue if a == "build_dict": continue value = getattr(self, a) if unicode(type(value)) == "<class 'indigo.List'>": # Build non Indigo list so it can JSON encode newlist = [] for i in value: newlist.append(i) rec[a] = newlist else: rec[a] = value except Exception as e: self.logger.error(ex.stack_trace(e)) if self.debug: self.logger.info("\nDEBUG MODE: JSON DATA\n{}".format( unicode(rec))) return rec
def __init__(self, recordDef, valuesDict): try: self.logger = logging.getLogger("Plugin.jstuff_record") self.name = recordDef.name self.index = valuesDict[recordDef.indexField] self.updated = False self.debug = False self.indexReset = False self.debug = recordDef.debug self.indexReset = recordDef.indexReset for a in recordDef.include: setattr( self, a, valuesDict[a]) # Set an attribute matching the field value for a in recordDef.manual: setattr( self, a, valuesDict[a]) # Set an attribute matching the field value except Exception as e: self.logger.error(ex.stack_trace(e))
def generic_unicode_output(tabtitle, tabcontent, obj, title=None): """ Generic unicode output for custom classes (called by __str__ functions). """ try: ret = "" if title: ret += u"{}{} : {}\n".format(tabtitle, title, type_to_unicode_output(obj)) for a in dir(obj): if callable(getattr(obj, a)): continue if a.startswith("_"): continue if a == "factory": continue if a == "logger": continue if a == "tabtitle" or a == "tabcontent": continue if type(getattr(obj, a)) == list: ret += u"{}{} : (list) \n".format(tabcontent, a) for l in getattr(obj, a): ret += u"\t{}item :\n{}{}".format(tabcontent, tabcontent, l) else: ret += u"{}{} : {}\n".format( tabcontent, a, type_to_unicode_output(getattr(obj, a))) except Exception as e: e.args += (unicode(type(obj)), ) raise CalcsException(kExceptionOutputPrefix + ex.stack_trace(e)) return ret
def __init__(self, factory): try: self.factory = factory # References the Indigo plugin self.logger = logging.getLogger("Plugin.jstuff") self.logger.debug("{} {} loaded".format(__modname__, __version__)) except Exception as e: self.logger.error(ex.stack_trace(e))
def __init__(self, args, valuesDict, typeId, targetId): """ Create the record using arguments passed from the fieldStuffIndex function in JStuff. """ try: self.logger = logging.getLogger("Plugin.jstuff_record_definition") self.name = "" self.include = [] self.exclude = [] self.manual = [] self.indexField = "" self.debug = False self.indexReset = False if "name" in args: self.name = args["name"] if "indexfield" in args: self.indexField = args["indexfield"] if "exclude" in args: self.exclude = self.process_include_exclude( args["exclude"], valuesDict, self.indexField) if "include" in args: self.include = self.process_include_exclude( args["include"], valuesDict, self.indexField) if "manual" in args: self.manual = self.process_include_exclude( args["manual"], valuesDict, self.indexField) if "indexchangereset" in args and args["indexchangereset"].lower( ) == "true": self.indexReset = True # Populate the remaining fields if we got only excludes if not self.include: for field, value in valuesDict.iteritems(): if field.startswith("ipf") or field == self.indexField: continue # Don't include these if not field in self.exclude: self.include.append(field) # Populate the remaining fields if we got only excludes if not self.exclude: for field, value in valuesDict.iteritems(): if field.startswith("ipf") or field == self.indexField: continue # Don't include these if not field in self.include: self.exclude.append(field) if "debug" in args and args["debug"].lower() == "true": self.debug = True self.logger.info(unicode(self)) except Exception as e: self.logger.error(ex.stack_trace(e))
def __init__(self, factory): try: self.factory = factory # References the Indigo plugin self.logger = logging.getLogger("Plugin.ui") self.logger.debug("{} {} loaded".format(__modname__, __version__)) self.deviceFieldCache = { } # For retrieving defaults and knowing if a field changed except Exception as e: self.logger.error(ex.stack_trace(e))
def formFieldChanged(self, valuesDict, typeId, devId, setDefault): """ Called from the plugin whenever any form field is changed, then attempts to raise an event in the plugin or ifactory and returns the result. Arguments: valuesDict = form fields typeId = device type Id devId = device Id setDefault = read last list retrieved for this field and default to the first value if the field is blank or its value doesn't exist on the list """ try: errorsDict = indigo.Dict() # If there's no version then add it, after this version changes can only happen elsewhere if kDeviceVersion not in valuesDict: valuesDict[ kDeviceVersion] = self.factory.PluginBase.pluginVersion # Process through jstuff (valuesDict, cbErrors) = self.factory.jstuff.onformFieldChanged( valuesDict, typeId, devId) if cbErrors: return (valuesDict, cbErrors) # Plugin callbacks callback = self.factory._callback([]) # Base callback if callback: (valuesDict, cbErrors) = callback if cbErrors: return (valuesDict, cbErrors) cleantypeId = ''.join( e for e in typeId if e.isalnum()) # Scrub type ID to be a valid function name callback = self.factory._callback( [valuesDict, typeId, devId], None, cleantypeId + "_") # Device type prefix callback if callback: (valuesDict, cbErrors) = callback if cbErrors: return (valuesDict, cbErrors) callback = self.factory._callback( [valuesDict, typeId, devId], None, None, "_" + cleantypeId) # Device type suffix callback if callback: (valuesDict, cbErrors) = callback if cbErrors: return (valuesDict, cbErrors) except Exception as e: self.logger.error(ex.stack_trace(e)) return (valuesDict, errorsDict)
def type_to_unicode_output(obj): """ Converts the type of the object to a string representation including the type (used for __str__ functions). """ try: if obj is None: return "None" return u"{} ({})".format( obj, unicode(type(obj)).replace("<type '", "").replace("'>", "").replace("<class '", "")) except Exception as e: e.args += (unicode(type(obj)), ) raise CalcsException(kExceptionOutputPrefix + ex.stack_trace(e))
def filter_to_dict(filter): """ Reads a filter passed from Devices.xml into a dictionary and returns it. """ try: args = {} filter = filter.replace("[", "").replace("]", "") for f in filter.split(","): f = f.strip() # Clean up spaces valkey = f.split("=") valname = valkey[0].lower().strip() args[valname] = valkey[1].strip() return args except Exception as e: e.args += (filter, ) raise CalcsException(kExceptionOutputPrefix + ex.stack_trace(e))
def _saveJSON(self, valuesDict): """ Write the contents of the cached records to the valuesDict form field in JSON format. """ try: if kFieldIndexer not in valuesDict: return valuesDict # It's not a jstuff defined form if kFieldUniqueId not in valuesDict: return valuesDict # Can't do this without the unique Id if valuesDict[kFieldUniqueId] not in self.Records: return valuesDict # Haven't saved records yet if valuesDict[kFieldUniqueId] in self.ExcludedForms: return valuesDict # This uniqueId has been excluded formrecords = self.Records[valuesDict[kFieldUniqueId]] jdata = {} if kFieldStorage in valuesDict: jdata = json.loads(valuesDict[kFieldStorage] ) # Load existing JSON if it is there for recordType, records in formrecords.iteritems(): data = {} for indexValue, record in records.iteritems(): #indigo.server.log("{}: {}: \n{}".format(recordType, indexValue, unicode(record))) data[indexValue] = record.build_dict( ) # Add record for each index jdata[ recordType] = data # Add dict of records for each record type jdump = json.dumps(jdata) indigo.server.log(unicode(jdump)) except Exception as e: self.logger.error(ex.stack_trace(e)) return valuesDict
def process_include_exclude(self, item, valuesDict, indexField): try: valueList = [] items = item.replace("(", "").replace(")", "") items = items.split(";") for e in items: if e.startswith("ipf"): continue # Don't add special library fields to any list if e == indexField: continue # Don't add the index to any list e = e.strip() #indigo.server.log(e) if "*" in e: filter = e.replace("*", "") for field, value in valuesDict.iteritems(): #indigo.server.log(field) if field.startswith("ipf"): continue # Don't add special library fields to any list if field == indexField: continue # Don't add the index to any list if e.endswith("*"): if field.startswith(filter): valueList.append(field) if e.startswith("*"): if field.endswith(filter): valueList.append(field) else: valueList.append(e) except Exception as e: self.logger.error(ex.stack_trace(e)) return valueList
def convert_temperature(value, toCelsius=False, asInteger=False): """ Convert a temperature value to Celsius or Fahrenheit. Arguments: toCelsius: convert value to Celsius (default is Fahrenheit) asInteger: returns full float value when false and integer value when true Returns: Converted value as a float """ try: if toCelsius: # Convert value to celsius value = float(value) value = (value - 32) / 1.8000 value = round(value, precision) if asInteger: return int(value) return value else: # Default: convert value to fahrenheit value = float(value) value = (value * 1.8000) + 32 value = round(value, precision) if asInteger: return int(value) return value except Exception as e: e.args += (value, ) e.args += (u"to Celsius: {}".format(toCelsius), ) raise CalcsException(kExceptionOutputPrefix + ex.stack_trace(e))
def version_check(pluginBase): global ON_PLUGIN_STORE if not ON_PLUGIN_STORE: return # We've already tried to check and failed, do not check again # Create some URLs we'll use later on pluginId = pluginBase.pluginId current_version_url = INDIGO_API_URL.format(pluginId) store_detail_url = INDIGO_STORE_URL pluginBase.next_version_check = datetime.now() + timedelta(days=1) try: # GET the url from the servers with a short timeout (avoids hanging the plugin) reply = requests.get(current_version_url, timeout=5) # This will raise an exception if the server returned an error reply.raise_for_status() # We now have a good reply so we get the json reply_dict = reply.json() plugin_dict = reply_dict["plugins"][0] # Make sure that the 'latestRelease' element is a dict (could be a string for built-in plugins). latest_release = plugin_dict["latestRelease"] if isinstance(latest_release, dict): # Compare the current version with the one returned in the reply dict if LooseVersion(latest_release["number"]) > LooseVersion(pluginBase.pluginVersion): # The release in the store is newer than the current version. # We'll do a couple of things: first, we'll just log it pluginBase.logger.error( "A new version of {} (v{}) is available at: {}".format( pluginBase.pluginDisplayName, latest_release["number"], store_detail_url.format(plugin_dict["id"]) ) ) # We'll change the value of a variable named "Plugin_Name_Current_Version" to the new version number # which the user can then build a trigger on (or whatever). You could also insert the download URL, # the store URL, whatever. try: variable_name = "u{}_Current_Version".format(pluginBase.pluginDisplayName.replace(" ", "_")) indigo.variable.updateValue(variable_name, latest_release["number"]) except: pass # We'll execute an action group named "New Version for Plugin Name". The action group could # then get the value of the variable above to do some kind of notification. try: action_group_name = "New Version for {}".format(pluginBase.pluginDisplayName) indigo.actionGroup.execute(action_group_name) except: pass # There are lots of other things you could do here. The final thing we're going to do is send # an email to self.version_check_email which I'll assume that you've set from the plugin # config. if hasattr(self, 'version_check_email') and self.version_check_email: indigo.server.sendEmailTo( pluginBase.version_check_email, subject="New version of Indigo Plugin '{}' is available".format(pluginBase.pluginDisplayName), body="It can be downloaded here: {}".format(store_detail_url) ) else: pluginBase.logger.info(u"{} is running the latest version, no update needed".format(pluginBase.pluginDisplayName)) except Exception as e: if "Not Found for url" in unicode(e): ON_PLUGIN_STORE = False pluginBase.logger.debug(u"{} is not on the Indigo Plugin Store, update checking disabled".format(pluginBase.pluginDisplayName)) else: pluginBase.logger.error(ex.stack_trace(e))
def version_check(pluginBase): global ON_PLUGIN_STORE if not ON_PLUGIN_STORE: return # We've already tried to check and failed, do not check again # Create some URLs we'll use later on pluginId = pluginBase.pluginId current_version_url = INDIGO_API_URL.format(pluginId) store_detail_url = INDIGO_STORE_URL try: # GET the url from the servers with a short timeout (avoids hanging the plugin) reply = requests.get(current_version_url, timeout=5) # This will raise an exception if the server returned an error reply.raise_for_status() # We now have a good reply so we get the json reply_dict = reply.json() plugin_dict = reply_dict["plugins"][0] # Make sure that the 'latestRelease' element is a dict (could be a string for built-in plugins). latest_release = plugin_dict["latestRelease"] if isinstance(latest_release, dict): # Compare the current version with the one returned in the reply dict if LooseVersion(latest_release["number"]) > LooseVersion( pluginBase.pluginVersion): # The release in the store is newer than the current version. # We'll do a couple of things: first, we'll just log it self.logger.error( "A new version of {} (v{}) is available at: {}".format( pluginBase.pluginDisplayName, latest_release["number"], store_detail_url.format(plugin_dict["id"]))) # We'll change the value of a variable named "Plugin_Name_Current_Version" to the new version number # which the user can then build a trigger on (or whatever). You could also insert the download URL, # the store URL, whatever. try: variable_name = "u{}_Current_Version".format( pluginBase.pluginDisplayName.replace(" ", "_")) indigo.variable.updateValue(variable_name, latest_release["number"]) except: pass # We'll execute an action group named "New Version for Plugin Name". The action group could # then get the value of the variable above to do some kind of notification. try: action_group_name = "New Version for {}".format( pluginBase.pluginDisplayName) indigo.actionGroup.execute(action_group_name) except: pass # There are lots of other things you could do here. The final thing we're going to do is send # an email to self.version_check_email which I'll assume that you've set from the plugin # config. if hasattr(self, 'version_check_email') and self.version_check_email: indigo.server.sendEmailTo( pluginBase.version_check_email, subject="New version of Indigo Plugin '{}' is available" .format(pluginBase.pluginDisplayName), body="It can be downloaded here: {}".format( store_detail_url)) else: self.logger.info( u"{} is running the latest version, no update needed". format(pluginBase.pluginDisplayName)) except Exception as e: if "Not Found for url" in unicode(e): ON_PLUGIN_STORE = False pluginBase.logger.debug( u"{} is not on the Indigo Plugin Store, update checking disabled" .format(pluginBase.pluginDisplayName)) else: pluginBase.logger.error(ex.stack_trace(e))
def fieldStuffIndex(self, filter, valuesDict, typeId, targetId): """ Device.xml dynamic list field that defines the jstuff record. """ try: ret = [("default", "")] if filter == "": self.logger.error( "Form field {} cannot be processed without a filter". format(kFieldIndexer)) return ret # This doesn't work without filters if kFieldUniqueId not in valuesDict: return ret # The first field change has not transpired if we don't this Id so we can't do this yet #self.logger.info ("Setting up jstuff record definition") # Break down the filter args = calcs.filter_to_dict(filter) #if not kFieldMethod in args: # indigo.server.log("Couldn't find {}".format(kFieldMethod)) # indigo.server.log(unicode(args)) # self.ExcludedForms.append(valuesDict[kFieldUniqueId]) # Add to excluded forms so formFieldChanged doesn't try to process # return ret # If the method= doesn't equal this then it is malformed # All record definitions for this unique Id definitions = {} if valuesDict[kFieldUniqueId] in self.RecordDefinitions: definitions = self.RecordDefinitions[ valuesDict[kFieldUniqueId]] # See if this record definition has already been added if definitions and "name" in args and args["name"] in definitions: return ret # Process the filter rec = _JStuffRecordDefinition(args, valuesDict, typeId, targetId) definitions[ rec. name] = rec # Save this record definition to this unique Id list of record definitions self.RecordDefinitions[valuesDict[ kFieldUniqueId]] = definitions # Save to global cache # Create the index so we can find this later for field in rec.include: recitem = [] if field in self.RecordFieldIndexes: recitem = self.RecordFieldIndexes[field] if not rec.name in recitem: recitem.append(valuesDict[kFieldUniqueId] + "|" + rec.name) self.RecordFieldIndexes[field] = recitem #indigo.server.log(unicode(self.RecordFieldIndexes)) #indigo.server.log(unicode(self.RecordDefinitions)) except Exception as e: self.logger.error(ex.stack_trace(e)) return ret
def convert_to_compared_datatype(source, destination): """ Converts the source value to the destination data type. Arguments: source: the source value who's data type needs to be changed destination: the value that the data type will be derived from Returns: source: value of source converted to the data type of destination """ try: converted = False # Assume failure # Convert to string types for ease stype = str(type(source)).replace("<type '", "").replace("'>", "") dtype = str(type(destination)).replace("<type '", "").replace("'>", "") # Convert from None if stype == "NoneType": if dtype == "float": source = 0.0 if dtype == "int": source = 0 if dtype == "bool": source = False if dtype == "string": source = "" converted = True # Convert from Boolean if stype == "bool": # To integer if dtype == "int": if source: source = 1 if not source: source = 0 converted = True # To float elif dtype == "float": if source: source = 1.0 if not source: source = 0.0 converted = True # To string elif dtype == "str": if source: source = "true" if not source: source = "false" converted = True # From string if stype == "str": # To unicode if dtype == "unicode": source = unicode(source) converted = True # To boolean if dtype == "bool": if source.lower() == "true": source = True else: source = False # It's either absolutely true or it's always false converted = True # To integer if dtype == "int": try: source = int(source) converted = True except: raise TypeConversionError( u"{} value {} cannot be converted to {}".format( stype, source, dtype)) # To float if dtype == "float": try: source = float(source) converted = True except: raise TypeConversionError( u"{} value {} cannot be converted to {}".format( stype, source, dtype)) # From unicode to string if stype == "unicode" and dtype == "str": source = str(source) converted = True # From integer to float if stype == "int" and dtype == "float": source = float(source) converted = True # From float to integer if stype == "float" and dtype == "int": source = int(round(source)) converted = True if not converted: raise TypeConversionError( u"Unable to convert source {} to type {}".format(stype, dtype)) except Exception as e: e.args += (u"{} to {}".format(stype, dtype), ) raise CalcsException(kExceptionOutputPrefix + ex.stack_trace(e)) return source
def onformFieldChanged(self, valuesDict, typeId, devId): """ A form field was changed, prompting a call from the factory to here in case there are JSON parameters. """ try: errorsDict = indigo.Dict() if kFieldIndexer not in valuesDict: return valuesDict # Record doesn't have record definitions so no need to do anything else if kFieldUniqueId not in valuesDict: valuesDict[kFieldUniqueId] = str( int(random.random() * 100000000)) # Add unique Id #self.logger.info ("Saving record") records = {} if valuesDict[kFieldUniqueId] in self.Records: records = self.Records[valuesDict[kFieldUniqueId]] jstuff = {} if kFieldStorage in valuesDict: jstuff = json.loads(valuesDict[kFieldStorage]) # See if any form fields are being used on records if valuesDict[kFieldUniqueId] in self.RecordDefinitions: #self.logger.info ("Extracting record data") recordSet = self.RecordDefinitions[ valuesDict[kFieldUniqueId]] # All records for this Id for field, value in valuesDict.iteritems(): if field in self.RecordFieldIndexes: attachedRecords = self.RecordFieldIndexes[field] # Loop through all record definitions attached to this field for recordDefs in attachedRecords: (uniqueId, recordDef) = recordDefs.split("|") if uniqueId != valuesDict[kFieldUniqueId]: continue # Different form # If we get here then we have a def, lets get the record definition class object recordClasses = self.RecordDefinitions[uniqueId] recordClass = recordClasses[recordDef] # No sense going further if the index value of the record def is empty if valuesDict[recordClass.indexField] == "": self.logger.info( "Index {} is empty, not saving {} record". format(recordClass.indexField, recordClass.name)) continue # Get all records for this form formRecordTypes = {} if uniqueId in self.Records: formRecordTypes = self.Records[uniqueId] # Get all records for this record type formRecords = {} if recordDef in formRecordTypes: formRecords = formRecordTypes[recordDef] # Get this index if recordClass.indexField in formRecords: rec = formRecords[recordClass.indexField] else: rec = _JStuffRecord(recordClass, valuesDict) # Capture the existing value and compare to new value if this is the index field if field == recordClass.indexField and getattr( rec, field) != value and rec.indexReset: self.logger.error("Index field changed!") # Set the value rec.set_field_value(field, value) if rec.updated: valuesDict[kFieldRecChanged] = True else: valuesDict[kFieldRecChanged] = False # Save the record to cache formRecords[valuesDict[ recordClass. indexField]] = rec # Save record with the dict key being the value of the index field formRecordTypes[recordDef] = formRecords self.Records[uniqueId] = formRecordTypes valuesDict = self._saveJSON( valuesDict) # Write the JSON data to the record except Exception as e: self.logger.error(ex.stack_trace(e)) return (valuesDict, errorsDict)