Hubigraphs 4.8 (The Final Chapter)

@thomas.c.howard I updated the code and tried a new gauge graph, it now only has 2 attributes
voltage and power so we have lost power factor. I was hoping we would have more options to graph. In my previous post every attribute has a "value" as long as the device is on which it is 99% of the time. Why would these then be "filtered out" if they meet the 2 requirements? Sorry for all the questions just trying to understand this a bit better. I was hoping to be able to graph anything that has a "number value" the reason for this is the gauge graphs are nice and easy to see at a distance for the "live" reading.
What about putting a warning that shows up if the person selects an attribute with a "0" value saying "This attribute currently has a "0" value are you sure you want to graph this attribute?"
These are my values from power monitoring plug as you can see they all have values.
Cabinet

Thanks again for taking the time.

Just installed it to better understand the illuminance range for some rooms, very cool, two issues...

  1. the two Dome motion/light sensors in our bathroom have noisy data, but it seems to jitter in time which is very strange

  2. We have a Zooz 4-in-1 sensor in the dining room, everywhere in hubitat it shows illuminance values but in Hubigraph that measure is missing,



Got it. Just some questions; what driver are you using? I would like to look at it. This could be a simple case of the attributes being defined as Strings. Simple fix, just need to have more faith in the community :slight_smile: Also, zero might be an issue. I can put a quick fix in.

@jared.zimmerman, strange. Just a few questions:

  1. Did this occur after some time or in the immediate graphing? This could be an error in groovy. I found this issue during v0.5alpha and added a sort to fix it. Haven’t seen it since...
  2. Which driver do you use for luminousity? I filter the attributes based on some things and it might be filtering it out. Don’t want to do that, but I need the driver to check

Would it be possible to add the option to rename a device in HubiGraph? I use long descriptive names to organize HE devices, but would like to shorten them when displayed in graphs.

Thanks

Can you give me an example of where you want it renamed? The pop-up? The Tile?

The sensor name EDIT: (pic helps but I should have stated on the bar graph, sorry)

@thomas.c.howard

This is a Sonoff R2 device running tasmota firmware and I am using @markus universal drivers. I hope this is what you are looking for?

Summary

/**

  • Copyright 2020 Markus Liljergren
  • Version: v1.0.1.0425Tb
  • Licensed under the Apache License, Version 2.0 (the "License");
  • you may not use this file except in compliance with the License.
  • You may obtain a copy of the License at:
  • http://www.apache.org/licenses/LICENSE-2.0
  • Unless required by applicable law or agreed to in writing, software
  • distributed under the License is distributed on an "AS IS" BASIS,
  • WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  • See the License for the specific language governing permissions and
  • limitations under the License.
    */

// BEGIN:getDefaultImports()
/** Default Imports */
import groovy.json.JsonSlurper
import groovy.json.JsonOutput
// Used for MD5 calculations
import java.security.MessageDigest
// END: getDefaultImports()

metadata {
// Do NOT rename the child driver name unless you also change the corresponding code in the Parent!
definition (name: "Tasmota - Universal Metering Plug/Outlet (Child)", namespace: "tasmota", author: "Markus Liljergren", importUrl: "https://raw.githubusercontent.com/markus-li/Hubitat/development/drivers/expanded/tasmota-universal-metering-plug-outlet-child-expanded.groovy") {
capability "Actuator"
capability "Switch"
capability "Outlet"
capability "Sensor"
capability "Refresh"

    // BEGIN:getDefaultMetadataCapabilitiesForEnergyMonitor()
    // Default Capabilities for Energy Monitor
    capability "VoltageMeasurement"
    capability "PowerMeter"
    capability "EnergyMeter"
    // END:  getDefaultMetadataCapabilitiesForEnergyMonitor()

    // BEGIN:getDefaultMetadataAttributesForEnergyMonitor()
    // Default Attributes for Energy Monitor
    attribute   "current", "string"
    attribute   "apparentPower", "string"
    attribute   "reactivePower", "string"
    attribute   "powerFactor", "string"
    attribute   "energyToday", "string"
    attribute   "energyYesterday", "string"
    attribute   "energyTotal", "string"
    attribute   "voltageWithUnit", "string"
    attribute   "powerWithUnit", "string"
    // END:  getDefaultMetadataAttributesForEnergyMonitor()

    // BEGIN:getMinimumChildAttributes()
    // Attributes used by all Child Drivers
    attribute   "driver", "string"
    // END:  getMinimumChildAttributes()
}

preferences {
    // BEGIN:getDefaultMetadataPreferences()
    // Default Preferences
    input(name: "debugLogging", type: "bool", title: addTitleDiv("Enable debug logging"), description: "" , defaultValue: false, submitOnChange: true, displayDuringSetup: false, required: false)
    input(name: "infoLogging", type: "bool", title: addTitleDiv("Enable descriptionText logging"), description: "", defaultValue: false, submitOnChange: true, displayDuringSetup: false, required: false)
    // END:  getDefaultMetadataPreferences()

}

// The below line needs to exist in ALL drivers for custom CSS to work!
// BEGIN:getMetadataCustomizationMethods()
// Here getPreferences() can be used to get the above preferences
metaDataExporter()
if(isCSSDisabled() == false) {
    preferences {
        input(name: "hiddenSetting", description: "" + getDriverCSSWrapper(), title: "None", displayDuringSetup: false, type: "paragraph", element: "paragraph")
    }
}
// END:  getMetadataCustomizationMethods()

}

// BEGIN:getDeviceInfoFunction()
String getDeviceInfoByName(infoName) {
// DO NOT EDIT: This is generated from the metadata!
// TODO: Figure out how to get this from Hubitat instead of generating this?
Map deviceInfo = ['name': 'Tasmota - Universal Metering Plug/Outlet (Child)', 'namespace': 'tasmota', 'author': 'Markus Liljergren', 'importUrl': 'https://raw.githubusercontent.com/markus-li/Hubitat/development/drivers/expanded/tasmota-universal-metering-plug-outlet-child-expanded.groovy']
//logging("deviceInfo[${infoName}] = ${deviceInfo[infoName]}", 1)
return(deviceInfo[infoName])
}
// END: getDeviceInfoFunction()

/* These functions are unique to each driver */
void parse(List description) {
description.each {
if (it.name in ["current", "apparentPower", "reactivePower", "powerFactor", "energyToday",
"energyYesterday", "energyTotal", "voltageWithUnit", "powerWithUnit",
"voltage", "power", "energy", "switch"]) {
logging(it.descriptionText, 100)
sendEvent(it)
} else {
log.warn "Got '$it.name' attribute data, but doesn't know what to do with it! Did you choose the right device type?"
}
}
}

void updated() {
log.info "updated()"
// BEGIN:getChildComponentDefaultUpdatedContent()
// This is code needed to run in updated() in ALL Child drivers
getDriverVersion()
// END: getChildComponentDefaultUpdatedContent()
refresh()
}

void installed() {
log.info "installed()"
device.removeSetting("logLevel")
device.updateSetting("logLevel", "100")
refresh()
}

void refresh() {
// BEGIN:getChildComponentMetaConfigCommands()
// metaConfig is what contains all fields to hide and other configuration
// processed in the "metadata" context of the driver.
def metaConfig = clearThingsToHide()
metaConfig = setDatasToHide(['metaConfig', 'isComponent', 'preferences', 'label', 'name'], metaConfig=metaConfig)
// END: getChildComponentMetaConfigCommands()
parent?.componentRefresh(this.device)
}

void on() {
parent?.componentOn(this.device)
}

void off() {
parent?.componentOff(this.device)
}

/**


  • Everything below here are LIBRARY includes and should NOT be edited manually!

  • --- Nothing to edit here, move along! ---------------------------------------

*/

// BEGIN:getDefaultFunctions()
/* Default Driver Methods go here */
private String getDriverVersion() {
//comment = ""
//if(comment != "") state.comment = comment
String version = "v1.0.1.0425Tb"
logging("getDriverVersion() = ${version}", 100)
sendEvent(name: "driver", value: version)
updateDataValue('driver', version)
return version
}
// END: getDefaultFunctions()

// Don't need this include anymore:
//#include:getHelperFunctions('all-debug')

/**

  • ALL DEFAULT METHODS (helpers-all-default)
  • Helper functions included in all drivers/apps
    */

boolean isDriver() {
try {
// If this fails, this is not a driver...
getDeviceDataByName('_unimportant')
logging("This IS a driver!", 0)
return true
} catch (MissingMethodException e) {
logging("This is NOT a driver!", 0)
return false
}
}

void deviceCommand(cmd) {
def jsonSlurper = new JsonSlurper()
cmd = jsonSlurper.parseText(cmd)
logging("deviceCommand: ${cmd}", 0)
r = this."${cmd['cmd']}"(*cmd['args'])
logging("deviceCommand return: ${r}", 0)
updateDataValue('appReturn', JsonOutput.toJson(r))
}

/*
initialize

Purpose: initialize the driver/app
Note: also called from updated()
This is called when the hub starts, DON'T declare it with return as void,
that seems like it makes it to not run? Since testing require hub reboots
and this works, this is not conclusive...

*/
// Call order: installed() -> configure() -> updated() -> initialize()
def initialize() {
logging("initialize()", 100)
unschedule("updatePresence")
// disable debug logs after 30 min, unless override is in place
if (debugLogging == true || (logLevel != "0" && logLevel != "100")) {
if(runReset != "DEBUG") {
log.warn "Debug logging will be disabled in 30 minutes..."
} else {
log.warn "Debug logging will NOT BE AUTOMATICALLY DISABLED!"
}
runIn(1800, "logsOff")
}
if(isDriver()) {
if(!isDeveloperHub()) {
device.removeSetting("logLevel")
device.updateSetting("logLevel", "0")
} else {
device.removeSetting("debugLogging")
device.updateSetting("debugLogging", "false")
device.removeSetting("infoLogging")
device.updateSetting("infoLogging", "false")
}
}
try {
// In case we have some more to run specific to this driver/app
initializeAdditional()
} catch (MissingMethodException e) {
// ignore
}
refresh()
}

/**

  • Automatically disable debug logging after 30 mins.

  • Note: scheduled in Initialize()
    */
    void logsOff() {
    if(runReset != "DEBUG") {
    log.warn "Debug logging disabled..."
    // Setting logLevel to "0" doesn't seem to work, it disables logs, but does not update the UI...
    //device.updateSetting("logLevel",[value:"0",type:"string"])
    //app.updateSetting("logLevel",[value:"0",type:"list"])
    // Not sure which ones are needed, so doing all... This works!
    if(isDriver()) {
    device.clearSetting("logLevel")
    device.removeSetting("logLevel")
    device.updateSetting("logLevel", "0")
    state?.settings?.remove("logLevel")
    device.clearSetting("debugLogging")
    device.removeSetting("debugLogging")
    device.updateSetting("debugLogging", "false")
    state?.settings?.remove("debugLogging")

     } else {
         //app.clearSetting("logLevel")
         // To be able to update the setting, it has to be removed first, clear does NOT work, at least for Apps
         app.removeSetting("logLevel")
         app.updateSetting("logLevel", "0")
         app.removeSetting("debugLogging")
         app.updateSetting("debugLogging", "false")
     }
    

    } else {
    log.warn "OVERRIDE: Disabling Debug logging will not execute with 'DEBUG' set..."
    if (logLevel != "0" && logLevel != "100") runIn(1800, "logsOff")
    }
    }

boolean isDeveloperHub() {
return generateMD5(location.hub.zigbeeId as String) == "125fceabd0413141e34bb859cd15e067_disabled"
}

def getEnvironmentObject() {
if(isDriver()) {
return device
} else {
return app
}
}

private def getFilteredDeviceDriverName() {
def deviceDriverName = getDeviceInfoByName('name')
if(deviceDriverName.toLowerCase().endsWith(' (parent)')) {
deviceDriverName = deviceDriverName.substring(0, deviceDriverName.length()-9)
}
return deviceDriverName
}

private def getFilteredDeviceDisplayName() {
def deviceDisplayName = device.displayName.replace(' (parent)', '').replace(' (Parent)', '')
return deviceDisplayName
}

/*
General Mathematical and Number Methods
*/
BigDecimal round2(BigDecimal number, Integer scale) {
Integer pow = 10;
for (Integer i = 1; i < scale; i++)
pow *= 10;
BigDecimal tmp = number * pow;
return ( (Float) ( (Integer) ((tmp - (Integer) tmp) >= 0.5f ? tmp + 1 : tmp) ) ) / pow;
}

String generateMD5(String s) {
if(s != null) {
return MessageDigest.getInstance("MD5").digest(s.bytes).encodeHex().toString()
} else {
return "null"
}
}

Integer extractInt(String input) {
return input.replaceAll("[^0-9]", "").toInteger()
}

/**

  • --END-- ALL DEFAULT METHODS (helpers-all-default)
    */

/**

  • DRIVER METADATA METHODS (helpers-driver-metadata)
  • These methods are to be used in (and/or with) the metadata section of drivers and
  • is also what contains the CSS handling and styling.
    */

// These methods can be executed in both the NORMAL driver scope as well
// as the Metadata scope.
private Map getMetaConfig() {
// This method can ALSO be executed in the Metadata Scope
def metaConfig = getDataValue('metaConfig')
if(metaConfig == null) {
metaConfig = [:]
} else {
metaConfig = parseJson(metaConfig)
}
return metaConfig
}

boolean isCSSDisabled(Map metaConfig=null) {
if(metaConfig==null) metaConfig = getMetaConfig()
boolean disableCSS = false
if(metaConfig.containsKey("disableCSS")) disableCSS = metaConfig["disableCSS"]
return disableCSS
}

// These methods are used to set which elements to hide.
// They have to be executed in the NORMAL driver scope.
private void saveMetaConfig(Map metaConfig) {
updateDataValue('metaConfig', JsonOutput.toJson(metaConfig))
}

private Map setSomethingToHide(String type, List something, Map metaConfig=null) {
if(metaConfig==null) metaConfig = getMetaConfig()
def oldData = []
something = something.unique()
if(!metaConfig.containsKey("hide")) {
metaConfig["hide"] = [type:something]
} else {
//logging("setSomethingToHide 1 else: something: '$something', type:'$type' (${metaConfig["hide"]}) containsKey:${metaConfig["hide"].containsKey(type)}", 1)
if(metaConfig["hide"].containsKey(type)) {
//logging("setSomethingToHide 1 hasKey else: something: '$something', type:'$type' (${metaConfig["hide"]}) containsKey:${metaConfig["hide"].containsKey(type)}", 1)
metaConfig["hide"][type].addAll(something)
} else {
//logging("setSomethingToHide 1 noKey else: something: '$something', type:'$type' (${metaConfig["hide"]}) containsKey:${metaConfig["hide"].containsKey(type)}", 1)
metaConfig["hide"][type] = something
}
//metaConfig["hide"]["$type"] = oldData
//logging("setSomethingToHide 2 else: something: '$something', type:'$type' (${metaConfig["hide"]}) containsKey:${metaConfig["hide"].containsKey(type)}", 1)
}
saveMetaConfig(metaConfig)
logging("setSomethingToHide() = ${metaConfig}", 1)
return metaConfig
}

private Map clearTypeToHide(String type, Map metaConfig=null) {
if(metaConfig==null) metaConfig = getMetaConfig()
if(!metaConfig.containsKey("hide")) {
metaConfig["hide"] = [(type):[]]
} else {
metaConfig["hide"][(type)] = []
}
saveMetaConfig(metaConfig)
logging("clearTypeToHide() = ${metaConfig}", 1)
return metaConfig
}

Map clearThingsToHide(Map metaConfig=null) {
metaConfig = setSomethingToHide("other", [], metaConfig=metaConfig)
metaConfig["hide"] = [:]
saveMetaConfig(metaConfig)
logging("clearThingsToHide() = ${metaConfig}", 1)
return metaConfig
}

Map setDisableCSS(boolean value, Map metaConfig=null) {
if(metaConfig==null) metaConfig = getMetaConfig()
metaConfig["disableCSS"] = value
saveMetaConfig(metaConfig)
logging("setDisableCSS(value = $value) = ${metaConfig}", 1)
return metaConfig
}

Map setStateCommentInCSS(String stateComment, Map metaConfig=null) {
if(metaConfig==null) metaConfig = getMetaConfig()
metaConfig["stateComment"] = stateComment
saveMetaConfig(metaConfig)
logging("setStateCommentInCSS(stateComment = $stateComment) = ${metaConfig}", 1)
return metaConfig
}

Map setCommandsToHide(List commands, Map metaConfig=null) {
metaConfig = setSomethingToHide("command", commands, metaConfig=metaConfig)
logging("setCommandsToHide(${commands})", 1)
return metaConfig
}

Map clearCommandsToHide(Map metaConfig=null) {
metaConfig = clearTypeToHide("command", metaConfig=metaConfig)
logging("clearCommandsToHide(metaConfig=${metaConfig})", 1)
return metaConfig
}

Map setStateVariablesToHide(List stateVariables, Map metaConfig=null) {
metaConfig = setSomethingToHide("stateVariable", stateVariables, metaConfig=metaConfig)
logging("setStateVariablesToHide(${stateVariables})", 1)
return metaConfig
}

Map clearStateVariablesToHide(Map metaConfig=null) {
metaConfig = clearTypeToHide("stateVariable", metaConfig=metaConfig)
logging("clearStateVariablesToHide(metaConfig=${metaConfig})", 1)
return metaConfig
}

Map setCurrentStatesToHide(List currentStates, Map metaConfig=null) {
metaConfig = setSomethingToHide("currentState", currentStates, metaConfig=metaConfig)
logging("setCurrentStatesToHide(${currentStates})", 1)
return metaConfig
}

Map clearCurrentStatesToHide(Map metaConfig=null) {
metaConfig = clearTypeToHide("currentState", metaConfig=metaConfig)
logging("clearCurrentStatesToHide(metaConfig=${metaConfig})", 1)
return metaConfig
}

Map setDatasToHide(List datas, Map metaConfig=null) {
metaConfig = setSomethingToHide("data", datas, metaConfig=metaConfig)
logging("setDatasToHide(${datas})", 1)
return metaConfig
}

Map clearDatasToHide(Map metaConfig=null) {
metaConfig = clearTypeToHide("data", metaConfig=metaConfig)
logging("clearDatasToHide(metaConfig=${metaConfig})", 1)
return metaConfig
}

Map setPreferencesToHide(List preferences, Map metaConfig=null) {
metaConfig = setSomethingToHide("preference", preferences, metaConfig=metaConfig)
logging("setPreferencesToHide(${preferences})", 1)
return metaConfig
}

Map clearPreferencesToHide(Map metaConfig=null) {
metaConfig = clearTypeToHide("preference", metaConfig=metaConfig)
logging("clearPreferencesToHide(metaConfig=${metaConfig})", 1)
return metaConfig
}

// These methods are for executing inside the metadata section of a driver.
def metaDataExporter() {
//log.debug "getEXECUTOR_TYPE = ${getEXECUTOR_TYPE()}"
List filteredPrefs = getPreferences()['sections']['input'].name[0]
//log.debug "filteredPrefs = ${filteredPrefs}"
if(filteredPrefs != []) updateDataValue('preferences', "${filteredPrefs}".replaceAll("\s",""))
}

// These methods are used to add CSS to the driver page
// This can be used for, among other things, to hide Commands
// They HAVE to be run in getDriverCSS() or getDriverCSSWrapper()!

/* Example usage:
r += getCSSForCommandsToHide(["off", "refresh"])
r += getCSSForStateVariablesToHide(["alertMessage", "mac", "dni", "oldLabel"])
r += getCSSForCurrentStatesToHide(["templateData", "tuyaMCU", "needUpdate"])
r += getCSSForDatasToHide(["preferences", "appReturn"])
r += getCSSToChangeCommandTitle("configure", "Run Configure2")
r += getCSSForPreferencesToHide(["numSwitches", "deviceTemplateInput"])
r += getCSSForPreferenceHiding('', overrideIndex=getPreferenceIndex('', returnMax=true) + 1)
r += getCSSForHidingLastPreference()
r += '''
form[action*="preference"]::before {
color: green;
content: "Hi, this is my content"
}
form[action*="preference"] div.mdl-grid div.mdl-cell:nth-of-type(2) {
color: green;
}
form[action*="preference"] div[for^=preferences] {
color: blue;
}
h3, h4, .property-label {
font-weight: bold;
}
'''
*/

String getDriverCSSWrapper() {
Map metaConfig = getMetaConfig()
boolean disableCSS = isCSSDisabled(metaConfig=metaConfig)
String defaultCSS = '''
/* This is part of the CSS for replacing a Command Title /
div.mdl-card__title div.mdl-grid div.mdl-grid .mdl-cell p::after {
visibility: visible;
position: absolute;
left: 50%;
transform: translate(-50%, 0%);
width: calc(100% - 20px);
padding-left: 5px;
padding-right: 5px;
margin-top: 0px;
}
/
This is general CSS Styling for the Driver page */
h3, h4, .property-label {
font-weight: bold;
}
.preference-title {
font-weight: bold;
}
.preference-description {
font-style: italic;
}
'''
String r = ""

if(disableCSS == false) {
    r += "$defaultCSS "
    try{
        // We always need to hide this element when we use CSS
        r += " ${getCSSForHidingLastPreference()} "
        
        if(disableCSS == false) {
            if(metaConfig.containsKey("hide")) {
                if(metaConfig["hide"].containsKey("command")) {
                    r += getCSSForCommandsToHide(metaConfig["hide"]["command"])
                }
                if(metaConfig["hide"].containsKey("stateVariable")) {
                    r += getCSSForStateVariablesToHide(metaConfig["hide"]["stateVariable"])
                }
                if(metaConfig["hide"].containsKey("currentState")) {
                    r += getCSSForCurrentStatesToHide(metaConfig["hide"]["currentState"])
                }
                if(metaConfig["hide"].containsKey("data")) {
                    r += getCSSForDatasToHide(metaConfig["hide"]["data"])
                }
                if(metaConfig["hide"].containsKey("preference")) {
                    r += getCSSForPreferencesToHide(metaConfig["hide"]["preference"])
                }
            }
            if(metaConfig.containsKey("stateComment")) {
                r += "div#stateComment:after { content: \"${metaConfig["stateComment"]}\" }"
            }
            r += " ${getDriverCSS()} "
        }
    }catch(MissingMethodException e) {
        if(!e.toString().contains("getDriverCSS()")) {
            log.warn "getDriverCSS() Error: $e"
        }
    } catch(e) {
        log.warn "getDriverCSS() Error: $e"
    }
}
r += " </style>"
return r

}

Integer getCommandIndex(String cmd) {
List commands = device.getSupportedCommands().unique()
Integer i = commands.findIndexOf{ "$it" == cmd}+1
//log.debug "getCommandIndex: Seeing these commands: '${commands}', index=$i}"
return i
}

String getCSSForCommandHiding(String cmdToHide) {
Integer i = getCommandIndex(cmdToHide)
String r = ""
if(i > 0) {
r = "div.mdl-card__title div.mdl-grid div.mdl-grid .mdl-cell:nth-of-type($i){display: none;}"
}
return r
}

String getCSSForCommandsToHide(List commands) {
String r = ""
commands.each {
r += getCSSForCommandHiding(it)
}
return r
}

String getCSSToChangeCommandTitle(String cmd, String newTitle) {
Integer i = getCommandIndex(cmd)
String r = ""
if(i > 0) {
r += "div.mdl-card__title div.mdl-grid div.mdl-grid .mdl-cell:nth-of-type($i) p {visibility: hidden;}"
r += "div.mdl-card__title div.mdl-grid div.mdl-grid .mdl-cell:nth-of-type($i) p::after {content: '$newTitle';}"
}
return r
}

Integer getStateVariableIndex(String stateVariable) {
def stateVariables = state.keySet()
Integer i = stateVariables.findIndexOf{ "$it" == stateVariable}+1
//log.debug "getStateVariableIndex: Seeing these State Variables: '${stateVariables}', index=$i}"
return i
}

String getCSSForStateVariableHiding(String stateVariableToHide) {
Integer i = getStateVariableIndex(stateVariableToHide)
String r = ""
if(i > 0) {
r = "ul#statev li.property-value:nth-of-type($i){display: none;}"
}
return r
}

String getCSSForStateVariablesToHide(List stateVariables) {
String r = ""
stateVariables.each {
r += getCSSForStateVariableHiding(it)
}
return r
}

String getCSSForCurrentStatesToHide(List currentStates) {
String r = ""
currentStates.each {
r += "ul#cstate li#cstate-$it {display: none;}"
}
return r
}

Integer getDataIndex(String data) {
def datas = device.getData().keySet()
Integer i = datas.findIndexOf{ "$it" == data}+1
//log.debug "getDataIndex: Seeing these Data Keys: '${datas}', index=$i}"
return i
}

String getCSSForDataHiding(String dataToHide) {
Integer i = getDataIndex(dataToHide)
String r = ""
if(i > 0) {
r = "table.property-list tr li.property-value:nth-of-type($i) {display: none;}"
}
return r
}

String getCSSForDatasToHide(List datas) {
String r = ""
datas.each {
r += getCSSForDataHiding(it)
}
return r
}

Integer getPreferenceIndex(String preference, boolean returnMax=false) {
def filteredPrefs = getPreferences()['sections']['input'].name[0]
//log.debug "getPreferenceIndex: Seeing these Preferences first: '${filteredPrefs}'"
if(filteredPrefs == [] || filteredPrefs == null) {
d = getDataValue('preferences')
//log.debug "getPreferenceIndex: getDataValue('preferences'): '${d}'"
if(d != null && d.length() > 2) {
try{
filteredPrefs = d[1..d.length()-2].tokenize(',')
} catch(e) {
// Do nothing
}
}

}
Integer i = 0
if(returnMax == true) {
    i = filteredPrefs.size()
} else {
    i = filteredPrefs.findIndexOf{ "$it" == preference}+1
}
//log.debug "getPreferenceIndex: Seeing these Preferences: '${filteredPrefs}', index=$i"
return i

}

String getCSSForPreferenceHiding(String preferenceToHide, Integer overrideIndex=0) {
Integer i = 0
if(overrideIndex == 0) {
i = getPreferenceIndex(preferenceToHide)
} else {
i = overrideIndex
}
String r = ""
if(i > 0) {
r = "form[action*="preference"] div.mdl-grid div.mdl-cell:nth-of-type($i) {display: none;} "
}else if(i == -1) {
r = "form[action*="preference"] div.mdl-grid div.mdl-cell:nth-last-child(2) {display: none;} "
}
return r
}

String getCSSForPreferencesToHide(List preferences) {
String r = ""
preferences.each {
r += getCSSForPreferenceHiding(it)
}
return r
}

String getCSSForHidingLastPreference() {
return getCSSForPreferenceHiding(null, overrideIndex=-1)
}

/**

  • --END-- DRIVER METADATA METHODS (helpers-driver-metadata)
    */

/**

  • STYLING (helpers-styling)
  • Helper functions included in all Drivers and Apps using Styling
    */
    String addTitleDiv(title) {
    return '
    ' + title + '
    '
    }

String addDescriptionDiv(description) {
return '

' + description + '
'
}

String makeTextBold(s) {
// DEPRECATED: Should be replaced by CSS styling!
if(isDriver()) {
return "$s"
} else {
return "$s"
}
}

String makeTextItalic(s) {
// DEPRECATED: Should be replaced by CSS styling!
if(isDriver()) {
return "$s"
} else {
return "$s"
}
}

String getDefaultCSS(boolean includeTags=true) {
String defaultCSS = '''
/* This is part of the CSS for replacing a Command Title /
div.mdl-card__title div.mdl-grid div.mdl-grid .mdl-cell p::after {
visibility: visible;
position: absolute;
left: 50%;
transform: translate(-50%, 0%);
width: calc(100% - 20px);
padding-left: 5px;
padding-right: 5px;
margin-top: 0px;
}
/
This is general CSS Styling for the Driver page */
h3, h4, .property-label {
font-weight: bold;
}
.preference-title {
font-weight: bold;
}
.preference-description {
font-style: italic;
}
'''
if(includeTags == true) {
return "$defaultCSS "
} else {
return defaultCSS
}
}

/**

  • --END-- STYLING METHODS (helpers-styling)
    */

// BEGIN:getLoggingFunction(specialDebugLevel=True)
/* Logging function included in all drivers */
private boolean logging(message, level) {
boolean didLogging = false
Integer logLevelLocal = (logLevel != null ? logLevel.toInteger() : 0)
//if(!isDeveloperHub()) {
logLevelLocal = 0
if (infoLogging == true) {
logLevelLocal = 100
}
if (debugLogging == true) {
logLevelLocal = 1
}
//}
if (logLevelLocal != 0){
switch (logLevelLocal) {
case -1: // Insanely verbose
if (level >= 0 && level < 100) {
log.debug "$message"
didLogging = true
} else if (level == 100) {
log.info "$message"
didLogging = true
}
break
case 1: // Very verbose
if (level >= 1 && level < 99) {
log.debug "$message"
didLogging = true
} else if (level == 100) {
log.info "$message"
didLogging = true
}
break
case 10: // A little less
if (level >= 10 && level < 99) {
log.debug "$message"
didLogging = true
} else if (level == 100) {
log.info "$message"
didLogging = true
}
break
case 50: // Rather chatty
if (level >= 50 ) {
log.debug "$message"
didLogging = true
}
break
case 99: // Only parsing reports
if (level >= 99 ) {
log.debug "$message"
didLogging = true
}
break

    case 100: // Only special debug messages, eg IR and RF codes
        if (level == 100 ) {
            log.info "$message"
            didLogging = true
        }
    break
    }
}
return didLogging

}
// END: getLoggingFunction(specialDebugLevel=True)

They are saved as String, for powerfactor that could be changed, the others are that way to display the unit in a Dashboard. That is an old driver btw, though this particular part has not changed.

Yep; that is the issue. I need to fix some other things. I will open up the selection as part of the next release... There might be a way to pull the "number" out so the gauge can use it. Question, did using a Line Graph work? Need to think on this....

Yes the line graph works as I am using voltage and current on one line graph at the moment and it works perfectly.

Chronological the jitters happened before I set up hubigraph but timelines are difficult when dealing with timequakes like this right?

Both the Dome Motion sensor and the zoos 4-in1 sensor are using the default built in drivers with the same names.

@markus, @greglsh I have removed the NUMBER check and posted an update. Tell me how it goes.

@SmartHomePrimer, @TechMedX posted update that allows custom labels for devices/attributes.

2 Likes

It allowed me to select all the attributes, and showed me the current value, but never created the graph.

Can you show the selection screen when it doesn't let you? Does it say " No recent valid events Please select a different Attribute"

No errors at all

OK; I think I see what is happening. Groovy (and JavaScript) can handle most things. Because the current state is a String and not a number (although it contains a number) it crashes on the "0.912 A". I have thought about parsing the number out of such cases.

Let me think on this...

1 Like

@thomas.c.howard
cool no problem, I am really enjoying the graphs and I know you are putting in the hours of work. Let me know if you want me to test again, time where I am is 8:30pm so might only be up for another 2 hours or so, otherwise I will test again tomorrow.

@greglsh, give the latest a try. It should fix the issues