Default Graphing for Hubitat

You could also just use InfluxDB Logger to put data from your hub into InfluxDB. I am not sure where the most updated version of the app i so this is what i have been running for a very long time.

/*****************************************************************************************************************
 *  Source: https://github.com/HubitatCommunity/InfluxDB-Logger
 *
 *  Raw Source: https://raw.githubusercontent.com/HubitatCommunity/InfluxDB-Logger/master/influxdb-logger.groovy
 *
 *  Forked from: https://github.com/codersaur/SmartThings/tree/master/smartapps/influxdb-logger
 *  Original Author: David Lomas (codersaur)
 *  Hubitat Elevation version maintained by Joshua Marker (@tooluser)
 *
 *  Description: A SmartApp to log Hubitat device states to an InfluxDB database.
 *  See Codersaur's github repo for more information.
 *
 *  NOTE: Hubitat does not currently support group names.
 *
 *  License:
 *   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.
 *
 *   Modifcation History
 *   Date       Name		Change 
 *   2019-02-02 Dan Ogorchock	Use asynchttpPost() instead of httpPost() call
 *   2019-09-09 Caleb Morse     Support deferring writes and doing buld writes to influxdb
 *****************************************************************************************************************/
definition(
    name: "InfluxDB Logger",
    namespace: "nowhereville",
    author: "Joshua Marker (tooluser)",
    description: "Log SmartThings device states to InfluxDB",
    category: "My Apps",
    iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png",
    iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png",
    iconX3Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png")

	import groovy.transform.Field
	@Field static java.util.concurrent.ConcurrentLinkedQueue loggerQueue = new java.util.concurrent.ConcurrentLinkedQueue()
	@Field static java.util.concurrent.Semaphore mutex = new java.util.concurrent.Semaphore(1)

preferences {
      	page(name: "newPage")
}


def newPage() {
    dynamicPage(name: "newPage", title: "New Settings Page", install: true, uninstall: true) {
	    section("General:") {
    	    //input "prefDebugMode", "bool", title: "Enable debug logging?", defaultValue: true, displayDuringSetup: true
        	input (
        		name: "configLoggingLevelIDE",
        		title: "IDE Live Logging Level:\nMessages with this level and higher will be logged to the IDE.",
        		type: "enum",
        		options: [
	        	    "0" : "None",
    	    	    "1" : "Error",
        		    "2" : "Warning",
        		    "3" : "Info",
        	    	"4" : "Debug",
	        	    "5" : "Trace"
    	    	],
        		defaultValue: "3",
            	displayDuringSetup: true,
	        	required: false
    	    )
    	}

    	section ("InfluxDB Database:") {
	        input "prefDatabaseHost", "text", title: "Host", defaultValue: "192.168.1.100", required: true
    	    input "prefDatabasePort", "text", title: "Port", defaultValue: "8086", required: true
        	input "prefDatabaseName", "text", title: "Database Name", defaultValue: "Hubitat", required: true
        	input "prefDatabaseUser", "text", title: "Username", required: false
        	input "prefDatabasePass", "text", title: "Password", required: false
    	}
    
  	section("Polling / Write frequency:") {
	        input "prefSoftPollingInterval", "number", title:"Soft-Polling interval (minutes)", defaultValue: 10, required: true

    	    input "writeInterval", "enum", title:"How often to write to db (minutes)", defaultValue: "5", required: true,
        		options: ["1",  "2", "3", "4", "5", "10", "15"]
    	}
    
	    section("System Monitoring:") {
    	    input "prefLogModeEvents", "bool", title:"Log Mode Events?", defaultValue: true, required: true
        	input "prefLogHubProperties", "bool", title:"Log Hub Properties?", defaultValue: true, required: true
        	input "prefLogLocationProperties", "bool", title:"Log Location Properties?", defaultValue: true, required: true
    	}
    
		section("Input Format Preference:") {
			input "accessAllAttributes", "bool", title:"Get Access To All Attributes?", defaultValue: false, required: true, submitOnChange: true
		}
		
		if(!accessAllAttributes)
		{
	       	section("Devices To Monitor:", hideable:true,hidden:true) {
    	 	  	input "accelerometers", "capability.accelerationSensor", title: "Accelerometers", multiple: true, required: false
       			input "alarms", "capability.alarm", title: "Alarms", multiple: true, required: false
       			input "batteries", "capability.battery", title: "Batteries", multiple: true, required: false
       			input "beacons", "capability.beacon", title: "Beacons", multiple: true, required: false
	       		input "buttons", "capability.button", title: "Buttons", multiple: true, required: false
    	  		input "cos", "capability.carbonMonoxideDetector", title: "Carbon Monoxide Detectors", multiple: true, required: false
       			input "co2s", "capability.carbonDioxideMeasurement", title: "Carbon Dioxide Detectors", multiple: true, required: false
        		input "colors", "capability.colorControl", title: "Color Controllers", multiple: true, required: false
	        	input "consumables", "capability.consumable", title: "Consumables", multiple: true, required: false
    	    	input "contacts", "capability.contactSensor", title: "Contact Sensors", multiple: true, required: false
        		input "doorsControllers", "capability.doorControl", title: "Door Controllers", multiple: true, required: false
        		input "energyMeters", "capability.energyMeter", title: "Energy Meters", multiple: true, required: false
        		input "humidities", "capability.relativeHumidityMeasurement", title: "Humidity Meters", multiple: true, required: false
	        	input "illuminances", "capability.illuminanceMeasurement", title: "Illuminance Meters", multiple: true, required: false
    	    	input "locks", "capability.lock", title: "Locks", multiple: true, required: false
        		input "motions", "capability.motionSensor", title: "Motion Sensors", multiple: true, required: false
        		input "musicPlayers", "capability.musicPlayer", title: "Music Players", multiple: true, required: false
	        	input "peds", "capability.stepSensor", title: "Pedometers", multiple: true, required: false
    	    	input "phMeters", "capability.pHMeasurement", title: "pH Meters", multiple: true, required: false
        		input "powerMeters", "capability.powerMeter", title: "Power Meters", multiple: true, required: false
        		input "presences", "capability.presenceSensor", title: "Presence Sensors", multiple: true, required: false
	        	input "pressures", "capability.sensor", title: "Pressure Sensors", multiple: true, required: false
    	    	input "shockSensors", "capability.shockSensor", title: "Shock Sensors", multiple: true, required: false
        		input "signalStrengthMeters", "capability.signalStrength", title: "Signal Strength Meters", multiple: true, required: false
        		input "sleepSensors", "capability.sleepSensor", title: "Sleep Sensors", multiple: true, required: false
	        	input "smokeDetectors", "capability.smokeDetector", title: "Smoke Detectors", multiple: true, required: false
    	    	input "soundSensors", "capability.soundSensor", title: "Sound Sensors", multiple: true, required: false
				input "spls", "capability.soundPressureLevel", title: "Sound Pressure Level Sensors", multiple: true, required: false
				input "switches", "capability.switch", title: "Switches", multiple: true, required: false
	        	input "switchLevels", "capability.switchLevel", title: "Switch Levels", multiple: true, required: false
    	    	input "tamperAlerts", "capability.tamperAlert", title: "Tamper Alerts", multiple: true, required: false
        		input "temperatures", "capability.temperatureMeasurement", title: "Temperature Sensors", multiple: true, required: false
        		input "thermostats", "capability.thermostat", title: "Thermostats", multiple: true, required: false
	        	input "threeAxis", "capability.threeAxis", title: "Three-axis (Orientation) Sensors", multiple: true, required: false
    	    	input "touchs", "capability.touchSensor", title: "Touch Sensors", multiple: true, required: false
        		input "uvs", "capability.ultravioletIndex", title: "UV Sensors", multiple: true, required: false
        		input "valves", "capability.valve", title: "Valves", multiple: true, required: false
	        	input "volts", "capability.voltageMeasurement", title: "Voltage Meters", multiple: true, required: false
    	    	input "waterSensors", "capability.waterSensor", title: "Water Sensors", multiple: true, required: false
        		input "windowShades", "capability.windowShade", title: "Window Shades", multiple: true, required: false
    		}
		} else {
			section("Devices To Monitor:", hideable:true,hidden:true) {
				input name: "allDevices", type: "capability.*", title: "Selected Devices", multiple: true, required: false, submitOnChange: true
			}

			section() {
				state.deviceList = [:]
				allDevices.each { device -> 
					state.deviceList[device.id] = "${device.label ?: device.name}"
				}
			}
		
			section() {
				state.selectedAttr=[:]
				settings.allDevices.each { deviceName ->
					if(deviceName) {
						deviceId = deviceName.getId()
        				attr = deviceName.getSupportedAttributes().unique()
						if(attr) {
							state.options =[]
							index = 0
							attr.each {at->
								state.options[index] = "${at}"
								index = index+1
							}
			
							section("$deviceName", hideable: true) {
								input name:"attrForDev$deviceId", type: "enum", title: "$deviceName", options: state.options, multiple: true, required: false, submitOnChange: true
							}
				
							state.selectedAttr[deviceId] = settings["attrForDev"+deviceId]
						}
					}
            	}
			}
			/*
			section("allo") {
				state.pollForAttr=[:]
				section("$softPolling", hideable: true) {
					state.selectedAttr.each { entry ->
						deviceId = entry.key
						state.temp=[]
						index = 0
						entry.value.each{ theAttr ->
								input name:"pollForDev$deviceId$theAttr", type: "enum", title: "$theAttr", options: [0,1,2,3,4,5,10,15,20,30,45,90,120], multiple: true, required: false, submitOnChange: true
						
						}
						
						
						log.debug "$deviceId - $theAttr"
						log.debug "allo"
						log.debug settings["pollForDev"+deviceId+theAttr]
						state.temp[index] =  settings["pollForDev"+deviceId+theAttr]
						index = index+1
						log.debug state.temp
						//state.pollForAttr[deviceId][theAttr] = settings["pollForDev"+deviceId+theAttr]

					}
				}
			} */
		}
		
	}
}


def getDeviceObj(id) {
    def found
    settings.allDevices.each { device -> 
        if (device.getId() == id) {
            //log.debug "Found at $device for $id with id: ${device.id}"
            found = device
        }
    }
    return found
}



/*****************************************************************************************************************
 *  SmartThings System Commands:
 *****************************************************************************************************************/

/**
 *  installed()
 *
 *  Runs when the app is first installed.
 **/
def installed() {
    state.installedAt = now()
    state.loggingLevelIDE = 5
    // Needs to be synchronized in case another event happens at the same time
    synchronized(this) {
        state.queuedData = []
    }
    log.debug "${app.label}: Installed with settings: ${settings}" 
}

/**
 *  uninstalled()
 *
 *  Runs when the app is uninstalled.
 **/
def uninstalled() {
    logger("uninstalled()","trace")
}

/**
 *  updated()
 * 
 *  Runs when app settings are changed.
 * 
 *  Updates device.state with input values and other hard-coded values.
 *  Builds state.deviceAttributes which describes the attributes that will be monitored for each device collection 
 *  (used by manageSubscriptions() and softPoll()).
 *  Refreshes scheduling and subscriptions.
 **/
def updated() {
    logger("updated()","trace")

    // Update internal state:
    state.loggingLevelIDE = (settings.configLoggingLevelIDE) ? settings.configLoggingLevelIDE.toInteger() : 3
    
    // Database config:
    state.databaseHost = settings.prefDatabaseHost
    state.databasePort = settings.prefDatabasePort
    state.databaseName = settings.prefDatabaseName
    state.databaseUser = settings.prefDatabaseUser
    state.databasePass = settings.prefDatabasePass 
    
    state.path = "/write?db=${state.databaseName}"
    state.headers = [:] 
    state.headers.put("HOST", "${state.databaseHost}:${state.databasePort}")
    //state.headers.put("Content-Type", "application/x-www-form-urlencoded")
    if (state.databaseUser && state.databasePass) {
        state.headers.put("Authorization", encodeCredentialsBasic(state.databaseUser, state.databasePass))
    }

    // Build array of device collections and the attributes we want to report on for that collection:
    //  Note, the collection names are stored as strings. Adding references to the actual collection 
    //  objects causes major issues (possibly memory issues?).
    state.deviceAttributes = []
    state.deviceAttributes << [ devices: 'accelerometers', attributes: ['acceleration']]
    state.deviceAttributes << [ devices: 'alarms', attributes: ['alarm']]
    state.deviceAttributes << [ devices: 'batteries', attributes: ['battery']]
    state.deviceAttributes << [ devices: 'beacons', attributes: ['presence']]
    state.deviceAttributes << [ devices: 'buttons', attributes: ['button']]
    state.deviceAttributes << [ devices: 'cos', attributes: ['carbonMonoxide']]
    state.deviceAttributes << [ devices: 'co2s', attributes: ['carbonDioxide']]
    state.deviceAttributes << [ devices: 'colors', attributes: ['hue','saturation','color']]
    state.deviceAttributes << [ devices: 'consumables', attributes: ['consumableStatus']]
    state.deviceAttributes << [ devices: 'contacts', attributes: ['contact']]
    state.deviceAttributes << [ devices: 'doorsControllers', attributes: ['door']]
    state.deviceAttributes << [ devices: 'energyMeters', attributes: ['energy']]
    state.deviceAttributes << [ devices: 'humidities', attributes: ['humidity']]
    state.deviceAttributes << [ devices: 'illuminances', attributes: ['illuminance']]
    state.deviceAttributes << [ devices: 'locks', attributes: ['lock']]
    state.deviceAttributes << [ devices: 'motions', attributes: ['motion']]
    state.deviceAttributes << [ devices: 'musicPlayers', attributes: ['status','level','trackDescription','trackData','mute']]
    state.deviceAttributes << [ devices: 'peds', attributes: ['steps','goal']]
    state.deviceAttributes << [ devices: 'phMeters', attributes: ['pH']]
    state.deviceAttributes << [ devices: 'powerMeters', attributes: ['power','voltage','current','powerFactor']]
    state.deviceAttributes << [ devices: 'presences', attributes: ['presence']]
    state.deviceAttributes << [ devices: 'pressures', attributes: ['pressure']]
    state.deviceAttributes << [ devices: 'shockSensors', attributes: ['shock']]
    state.deviceAttributes << [ devices: 'signalStrengthMeters', attributes: ['lqi','rssi']]
    state.deviceAttributes << [ devices: 'sleepSensors', attributes: ['sleeping']]
    state.deviceAttributes << [ devices: 'smokeDetectors', attributes: ['smoke']]
    state.deviceAttributes << [ devices: 'soundSensors', attributes: ['sound']]
	state.deviceAttributes << [ devices: 'spls', attributes: ['soundPressureLevel']]
	state.deviceAttributes << [ devices: 'switches', attributes: ['switch']]
    state.deviceAttributes << [ devices: 'switchLevels', attributes: ['level']]
    state.deviceAttributes << [ devices: 'tamperAlerts', attributes: ['tamper']]
    state.deviceAttributes << [ devices: 'temperatures', attributes: ['temperature']]
    state.deviceAttributes << [ devices: 'thermostats', attributes: ['temperature','heatingSetpoint','coolingSetpoint','thermostatSetpoint','thermostatMode','thermostatFanMode','thermostatOperatingState','thermostatSetpointMode','scheduledSetpoint','optimisation','windowFunction']]
    state.deviceAttributes << [ devices: 'threeAxis', attributes: ['threeAxis']]
    state.deviceAttributes << [ devices: 'touchs', attributes: ['touch']]
    state.deviceAttributes << [ devices: 'uvs', attributes: ['ultravioletIndex']]
    state.deviceAttributes << [ devices: 'valves', attributes: ['contact']]
    state.deviceAttributes << [ devices: 'volts', attributes: ['voltage']]
    state.deviceAttributes << [ devices: 'waterSensors', attributes: ['water']]
    state.deviceAttributes << [ devices: 'windowShades', attributes: ['windowShade']]

    // Configure Scheduling:
    state.softPollingInterval = settings.prefSoftPollingInterval.toInteger()
    state.writeInterval = settings.writeInterval
    manageSchedules()
    
    // Configure Subscriptions:
    manageSubscriptions()
}

/*****************************************************************************************************************
 *  Event Handlers:
 *****************************************************************************************************************/

/**
 *  handleAppTouch(evt)
 * 
 *  Used for testing.
 **/
def handleAppTouch(evt) {
    logger("handleAppTouch()","trace")
    
    softPoll()
}

/**
 *  handleModeEvent(evt)
 * 
 *  Log Mode changes.
 **/
def handleModeEvent(evt) {
    logger("handleModeEvent(): Mode changed to: ${evt.value}","info")

    def locationId = escapeStringForInfluxDB(location.id.toString())
    def locationName = escapeStringForInfluxDB(location.name)
    def mode = '"' + escapeStringForInfluxDB(evt.value) + '"'
	def data = "_stMode,locationId=${locationId},locationName=${locationName} mode=${mode}"
    queueToInfluxDb(data)
}

/**
 *  handleEvent(evt)
 *
 *  Builds data to send to InfluxDB.
 *   - Escapes and quotes string values.
 *   - Calculates logical binary values where string values can be 
 *     represented as binary values (e.g. contact: closed = 1, open = 0)
 * 
 *  Useful references: 
 *   - http://docs.smartthings.com/en/latest/capabilities-reference.html
 *   - https://docs.influxdata.com/influxdb/v0.10/guides/writing_data/
 **/
def handleEvent(evt) {
    //logger("handleEvent(): $evt.unit","info")
    logger("handleEvent(): $evt.displayName($evt.name:$evt.unit) $evt.value","info")
    
    // Build data string to send to InfluxDB:
    //  Format: <measurement>[,<tag_name>=<tag_value>] field=<field_value>
    //    If value is an integer, it must have a trailing "i"
    //    If value is a string, it must be enclosed in double quotes.
    def measurement = evt.name
    // tags:
    def deviceId = escapeStringForInfluxDB(evt.deviceId.toString())
    def deviceName = escapeStringForInfluxDB(evt.displayName)
    def groupId = escapeStringForInfluxDB(evt?.device.device.groupId)
    def groupName = escapeStringForInfluxDB(getGroupName(evt?.device.device.groupId))
    def hubId = escapeStringForInfluxDB(evt?.device.device.hubId.toString())
    def hubName = escapeStringForInfluxDB(evt?.device.device.hub.name.toString())
    // Don't pull these from the evt.device as the app itself will be associated with one location.
    def locationId = escapeStringForInfluxDB(location.id.toString())
    def locationName = escapeStringForInfluxDB(location.name)

    def unit = escapeStringForInfluxDB(evt.unit)
    def value = escapeStringForInfluxDB(evt.value)
    def valueBinary = ''
    
    def data = "${measurement},deviceId=${deviceId},deviceName=${deviceName},groupId=${groupId},groupName=${groupName},hubId=${hubId},hubName=${hubName},locationId=${locationId},locationName=${locationName}"
    
    // Unit tag and fields depend on the event type:
    //  Most string-valued attributes can be translated to a binary value too.
    if ('acceleration' == evt.name) { // acceleration: Calculate a binary value (active = 1, inactive = 0)
        unit = 'acceleration'
        value = '"' + value + '"'
        valueBinary = ('active' == evt.value) ? '1i' : '0i'
        data += ",unit=${unit} value=${value},valueBinary=${valueBinary}"
    }
    else if ('alarm' == evt.name) { // alarm: Calculate a binary value (strobe/siren/both = 1, off = 0)
        unit = 'alarm'
        value = '"' + value + '"'
        valueBinary = ('off' == evt.value) ? '0i' : '1i'
        data += ",unit=${unit} value=${value},valueBinary=${valueBinary}"
    }
    else if ('button' == evt.name) { // button: Calculate a binary value (held = 1, pushed = 0)
        unit = 'button'
        value = '"' + value + '"'
        valueBinary = ('pushed' == evt.value) ? '0i' : '1i'
        data += ",unit=${unit} value=${value},valueBinary=${valueBinary}"
    }
    else if ('carbonMonoxide' == evt.name) { // carbonMonoxide: Calculate a binary value (detected = 1, clear/tested = 0)
        unit = 'carbonMonoxide'
        value = '"' + value + '"'
        valueBinary = ('detected' == evt.value) ? '1i' : '0i'
        data += ",unit=${unit} value=${value},valueBinary=${valueBinary}"
    }
    else if ('consumableStatus' == evt.name) { // consumableStatus: Calculate a binary value ("good" = 1, "missing"/"replace"/"maintenance_required"/"order" = 0)
        unit = 'consumableStatus'
        value = '"' + value + '"'
        valueBinary = ('good' == evt.value) ? '1i' : '0i'
        data += ",unit=${unit} value=${value},valueBinary=${valueBinary}"
    }
    else if ('contact' == evt.name) { // contact: Calculate a binary value (closed = 1, open = 0)
        unit = 'contact'
        value = '"' + value + '"'
        valueBinary = ('closed' == evt.value) ? '1i' : '0i'
        data += ",unit=${unit} value=${value},valueBinary=${valueBinary}"
    }
    else if ('door' == evt.name) { // door: Calculate a binary value (closed = 1, open/opening/closing/unknown = 0)
        unit = 'door'
        value = '"' + value + '"'
        valueBinary = ('closed' == evt.value) ? '1i' : '0i'
        data += ",unit=${unit} value=${value},valueBinary=${valueBinary}"
    }
    else if ('lock' == evt.name) { // door: Calculate a binary value (locked = 1, unlocked = 0)
        unit = 'lock'
        value = '"' + value + '"'
        valueBinary = ('locked' == evt.value) ? '1i' : '0i'
        data += ",unit=${unit} value=${value},valueBinary=${valueBinary}"
    }
    else if ('motion' == evt.name) { // Motion: Calculate a binary value (active = 1, inactive = 0)
        unit = 'motion'
        value = '"' + value + '"'
        valueBinary = ('active' == evt.value) ? '1i' : '0i'
        data += ",unit=${unit} value=${value},valueBinary=${valueBinary}"
    }
    else if ('mute' == evt.name) { // mute: Calculate a binary value (muted = 1, unmuted = 0)
        unit = 'mute'
        value = '"' + value + '"'
        valueBinary = ('muted' == evt.value) ? '1i' : '0i'
        data += ",unit=${unit} value=${value},valueBinary=${valueBinary}"
    }
    else if ('presence' == evt.name) { // presence: Calculate a binary value (present = 1, not present = 0)
        unit = 'presence'
        value = '"' + value + '"'
        valueBinary = ('present' == evt.value) ? '1i' : '0i'
        data += ",unit=${unit} value=${value},valueBinary=${valueBinary}"
    }
    else if ('shock' == evt.name) { // shock: Calculate a binary value (detected = 1, clear = 0)
        unit = 'shock'
        value = '"' + value + '"'
        valueBinary = ('detected' == evt.value) ? '1i' : '0i'
        data += ",unit=${unit} value=${value},valueBinary=${valueBinary}"
    }
    else if ('sleeping' == evt.name) { // sleeping: Calculate a binary value (sleeping = 1, not sleeping = 0)
        unit = 'sleeping'
        value = '"' + value + '"'
        valueBinary = ('sleeping' == evt.value) ? '1i' : '0i'
        data += ",unit=${unit} value=${value},valueBinary=${valueBinary}"
    }
    else if ('smoke' == evt.name) { // smoke: Calculate a binary value (detected = 1, clear/tested = 0)
        unit = 'smoke'
        value = '"' + value + '"'
        valueBinary = ('detected' == evt.value) ? '1i' : '0i'
        data += ",unit=${unit} value=${value},valueBinary=${valueBinary}"
    }
    else if ('sound' == evt.name) { // sound: Calculate a binary value (detected = 1, not detected = 0)
        unit = 'sound'
        value = '"' + value + '"'
        valueBinary = ('detected' == evt.value) ? '1i' : '0i'
        data += ",unit=${unit} value=${value},valueBinary=${valueBinary}"
    }
    else if ('switch' == evt.name) { // switch: Calculate a binary value (on = 1, off = 0)
        unit = 'switch'
        value = '"' + value + '"'
        valueBinary = ('on' == evt.value) ? '1i' : '0i'
        data += ",unit=${unit} value=${value},valueBinary=${valueBinary}"
    }
    else if ('tamper' == evt.name) { // tamper: Calculate a binary value (detected = 1, clear = 0)
        unit = 'tamper'
        value = '"' + value + '"'
        valueBinary = ('detected' == evt.value) ? '1i' : '0i'
        data += ",unit=${unit} value=${value},valueBinary=${valueBinary}"
    }
    else if ('thermostatMode' == evt.name) { // thermostatMode: Calculate a binary value (<any other value> = 1, off = 0)
        unit = 'thermostatMode'
        value = '"' + value + '"'
        valueBinary = ('off' == evt.value) ? '0i' : '1i'
        data += ",unit=${unit} value=${value},valueBinary=${valueBinary}"
    }
    else if ('thermostatFanMode' == evt.name) { // thermostatFanMode: Calculate a binary value (<any other value> = 1, off = 0)
        unit = 'thermostatFanMode'
        value = '"' + value + '"'
        valueBinary = ('off' == evt.value) ? '0i' : '1i'
        data += ",unit=${unit} value=${value},valueBinary=${valueBinary}"
    }
    else if ('thermostatOperatingState' == evt.name) { // thermostatOperatingState: Calculate a binary value (heating = 1, <any other value> = 0)
        unit = 'thermostatOperatingState'
        value = '"' + value + '"'
        valueBinary = ('heating' == evt.value) ? '1i' : '0i'
        data += ",unit=${unit} value=${value},valueBinary=${valueBinary}"
    }
    else if ('thermostatSetpointMode' == evt.name) { // thermostatSetpointMode: Calculate a binary value (followSchedule = 0, <any other value> = 1)
        unit = 'thermostatSetpointMode'
        value = '"' + value + '"'
        valueBinary = ('followSchedule' == evt.value) ? '0i' : '1i'
        data += ",unit=${unit} value=${value},valueBinary=${valueBinary}"
    }
    else if ('threeAxis' == evt.name) { // threeAxis: Format to x,y,z values.
        unit = 'threeAxis'
        def valueXYZ = evt.value.split(",")
        def valueX = valueXYZ[0]
        def valueY = valueXYZ[1]
        def valueZ = valueXYZ[2]
        data += ",unit=${unit} valueX=${valueX}i,valueY=${valueY}i,valueZ=${valueZ}i" // values are integers.
    }
    else if ('touch' == evt.name) { // touch: Calculate a binary value (touched = 1, "" = 0)
        unit = 'touch'
        value = '"' + value + '"'
        valueBinary = ('touched' == evt.value) ? '1i' : '0i'
        data += ",unit=${unit} value=${value},valueBinary=${valueBinary}"
    }
    else if ('optimisation' == evt.name) { // optimisation: Calculate a binary value (active = 1, inactive = 0)
        unit = 'optimisation'
        value = '"' + value + '"'
        valueBinary = ('active' == evt.value) ? '1i' : '0i'
        data += ",unit=${unit} value=${value},valueBinary=${valueBinary}"
    }
    else if ('windowFunction' == evt.name) { // windowFunction: Calculate a binary value (active = 1, inactive = 0)
        unit = 'windowFunction'
        value = '"' + value + '"'
        valueBinary = ('active' == evt.value) ? '1i' : '0i'
        data += ",unit=${unit} value=${value},valueBinary=${valueBinary}"
    }
    else if ('touch' == evt.name) { // touch: Calculate a binary value (touched = 1, <any other value> = 0)
        unit = 'touch'
        value = '"' + value + '"'
        valueBinary = ('touched' == evt.value) ? '1i' : '0i'
        data += ",unit=${unit} value=${value},valueBinary=${valueBinary}"
    }
    else if ('water' == evt.name) { // water: Calculate a binary value (wet = 1, dry = 0)
        unit = 'water'
        value = '"' + value + '"'
        valueBinary = ('wet' == evt.value) ? '1i' : '0i'
        data += ",unit=${unit} value=${value},valueBinary=${valueBinary}"
    }
    else if ('windowShade' == evt.name) { // windowShade: Calculate a binary value (closed = 1, <any other value> = 0)
        unit = 'windowShade'
        value = '"' + value + '"'
        valueBinary = ('closed' == evt.value) ? '1i' : '0i'
        data += ",unit=${unit} value=${value},valueBinary=${valueBinary}"
    }
    // Catch any other event with a string value that hasn't been handled:
    else if (evt.value ==~ /.*[^0-9\.,-].*/) { // match if any characters are not digits, period, comma, or hyphen.
		logger("handleEvent(): Found a string value that's not explicitly handled: Device Name: ${deviceName}, Event Name: ${evt.name}, Value: ${evt.value}","warn")
        value = '"' + value + '"'
        data += ",unit=${unit} value=${value}"
    }
    // Catch any other general numerical event (carbonDioxide, power, energy, humidity, level, temperature, ultravioletIndex, voltage, etc).
    else {
        data += ",unit=${unit} value=${value}"
    }
    
    //logger("$data","info")
    
    // Queue data for later write to InfluxDB
    queueToInfluxDb(data)
}


/*****************************************************************************************************************
 *  Main Commands:
 *****************************************************************************************************************/

/**
 *  softPoll()
 *
 *  Executed by schedule.
 * 
 *  Forces data to be posted to InfluxDB (even if an event has not been triggered).
 *  Doesn't poll devices, just builds a fake event to pass to handleEvent().
 *
 *  Also calls LogSystemProperties().
 **/
def softPoll() {
    logger("softPoll()","trace")
    
    logSystemProperties()
	if(!accessAllAttributes) {
		// Iterate over each attribute for each device, in each device collection in deviceAttributes:
    	def devs // temp variable to hold device collection.
    	state.deviceAttributes.each { da ->
	        devs = settings."${da.devices}"
    	    if (devs && (da.attributes)) {
        	    devs.each { d ->
            	    da.attributes.each { attr ->
                	    if (d.hasAttribute(attr) && d.latestState(attr)?.value != null) {
                    	    logger("softPoll(): Softpolling device ${d} for attribute: ${attr}","info")
                        	// Send fake event to handleEvent():

	                        handleEvent([
    	                        name: attr, 
        	                    value: d.latestState(attr)?.value,
            	                unit: d.latestState(attr)?.unit,
                	            device: d,
                    	        deviceId: d.id,
                        	    displayName: d.displayName
                        	])
						}
					}
				}
			}
		}
	} else {
		state.selectedAttr.each{ entry -> 
			d = getDeviceObj(entry.key)
			entry.value.each{ attr ->
            	if (d.hasAttribute(attr) && d.latestState(attr)?.value != null) {
            		logger("softPoll(): Softpolling device ${d} for attribute: ${attr}","info")
                	// Send fake event to handleEvent():
                	handleEvent([
	                	name: attr, 
    	                value: d.latestState(attr)?.value,
        	            unit: d.latestState(attr)?.unit,
            	        device: d,
                	    deviceId: d.id,
                    	displayName: d.displayName
                	])
				}
			}
		}
	}
}

/**
 *  logSystemProperties()
 *
 *  Generates measurements for SmartThings system (hubs and locations) properties.
 **/
def logSystemProperties() {
    logger("logSystemProperties()","trace")

    def locationId = '"' + escapeStringForInfluxDB(location.id.toString()) + '"'
    def locationName = '"' + escapeStringForInfluxDB(location.name) + '"'

	// Location Properties:
    if (prefLogLocationProperties) {
        try {
            def tz = '"' + escapeStringForInfluxDB(location.timeZone.ID.toString()) + '"'
            def mode = '"' + escapeStringForInfluxDB(location.mode) + '"'
            def hubCount = location.hubs.size()
            def times = getSunriseAndSunset()
            def srt = '"' + times.sunrise.format("HH:mm", location.timeZone) + '"'
            def sst = '"' + times.sunset.format("HH:mm", location.timeZone) + '"'

            def data = "_heLocation,locationId=${locationId},locationName=${locationName},latitude=${location.latitude},longitude=${location.longitude},timeZone=${tz} mode=${mode},hubCount=${hubCount}i,sunriseTime=${srt},sunsetTime=${sst}"
            queueToInfluxDb(data)
            //log.debug("LocationData = ${data}")
        } catch (e) {
		    logger("logSystemProperties(): Unable to log Location properties: ${e}","error")
        }
	}

	// Hub Properties:
    if (prefLogHubProperties) {
       	location.hubs.each { h ->
        	try {
                def hubId = '"' + escapeStringForInfluxDB(h.id.toString()) + '"'
                def hubName = '"' + escapeStringForInfluxDB(h.name.toString()) + '"'
                def hubIP = '"' + escapeStringForInfluxDB(h.localIP.toString()) + '"'
                //def hubStatus = '"' + escapeStringForInfluxDB(h.status) + '"'
                //def batteryInUse = ("false" == h.hub.getDataValue("batteryInUse")) ? "0i" : "1i"
                // See fix here for null time returned: https://github.com/codersaur/SmartThings/pull/33/files
                //def hubUptime = h.hub.getDataValue("uptime") + 'i'
                //def hubLastBootUnixTS = h.hub.uptime + 'i'
                //def zigbeePowerLevel = h.hub.getDataValue("zigbeePowerLevel") + 'i'
                //def zwavePowerLevel =  '"' + escapeStringForInfluxDB(h.hub.getDataValue("zwavePowerLevel")) + '"'
                def firmwareVersion =  '"' + escapeStringForInfluxDB(h.firmwareVersionString) + '"'
                
                def data = "_heHub,locationId=${locationId},locationName=${locationName},hubId=${hubId},hubName=${hubName},hubIP=${hubIP} "
                data += "firmwareVersion=${firmwareVersion}"
                // See fix here for null time returned: https://github.com/codersaur/SmartThings/pull/33/files
                //data += "status=${hubStatus},batteryInUse=${batteryInUse},uptime=${hubUptime},zigbeePowerLevel=${zigbeePowerLevel},zwavePowerLevel=${zwavePowerLevel},firmwareVersion=${firmwareVersion}"
                //data += "status=${hubStatus},batteryInUse=${batteryInUse},uptime=${hubLastBootUnixTS},zigbeePowerLevel=${zigbeePowerLevel},zwavePowerLevel=${zwavePowerLevel},firmwareVersion=${firmwareVersion}"
                queueToInfluxDb(data)
                //log.debug("HubData = ${data}")
            } catch (e) {
				logger("logSystemProperties(): Unable to log Hub properties: ${e}","error")
        	}
       	}

	}
}

def queueToInfluxDb(data) {
    // Add timestamp (influxdb does this automatically, but since we're batching writes, we need to add it
    long timeNow = (new Date().time) * 1e6 // Time is in milliseconds, needs to be in nanoseconds
    data += " ${timeNow}"
    
    int queueSize = 0
	try {
		mutex.acquire()
		//if(!mutex.tryAcquire()) {
		//	logger("Error 1 in queueToInfluxDb","Warning")
		//	mutex.release()
		//}
		
		loggerQueue.offer(data)
		queueSize = loggerQueue.size()
		
		// Give some visibility at the interface level
		state.queuedData = loggerQueue.toArray()
	} 
	catch(e) {
		logger("Error 2 in queueToInfluxDb","Warning")
	} 
	finally {
		mutex.release()
	}
	
    if (queueSize > 100) {
        logger("Queue size is too big, triggering write now", "info")
        writeQueuedDataToInfluxDb()
    }
}

def writeQueuedDataToInfluxDb() {
    String writeData = ""
	
	try {
		mutex.acquire()
		//if(!mutex.tryAcquire()) {
		//	logger("Error 1 in writeQueuedDataToInfluxDb","Warning")
		//	mutex.release()
		//}
				
		if(loggerQueue.size() == 0) {
            logger("No queued data to write to InfluxDB", "info")
            return
		}
        logger("Writing queued data of size ${loggerQueue.size()} out", "info")
		a = loggerQueue.toArray() 
		writeData = a.join('\n')
		loggerQueue.clear()
		state.queuedData = []
	}	   
	catch(e) {
		logger("Error 2 in writeQueuedDataToInfluxDb","Warning")
	} 
	finally {
		mutex.release()
	}
	
    postToInfluxDB(writeData)
}

/**
 *  postToInfluxDB()
 *
 *  Posts data to InfluxDB.
 *
 *  Uses hubAction instead of httpPost() in case InfluxDB server is on the same LAN as the Smartthings Hub.
 **/
def postToInfluxDB(data) {
    logger("postToInfluxDB(): Posting data to InfluxDB: Host: ${state.databaseHost}, Port: ${state.databasePort}, Database: ${state.databaseName}, Data: [${data}]","info")
    //logger("$state", "info")
    //try {
    //    //def hubAction = new physicalgraph.device.HubAction(
    //    def hubAction = new hubitat.device.HubAction(
    //    	[
    //            method: "POST",
    //            path: state.path,
    //            body: data,
    //            headers: state.headers
    //        ],
    //        null,
    //        [ callback: handleInfluxResponse ]
    //    )
	//	
    //    sendHubCommand(hubAction)
    //    //logger("hubAction command sent", "info")
    //}
    //catch (Exception e) {
	//	logger("postToInfluxDB(): Exception ${e} on ${hubAction}","error")
    //}

    // Hubitat Async http Post
     
	try {
		def postParams = [
			uri: "http://${state.databaseHost}:${state.databasePort}/write?db=${state.databaseName}" ,
			requestContentType: 'application/json',
			contentType: 'application/json',
			body : data
			]
		asynchttpPost('handleInfluxResponse', postParams) 
	} catch (e) {	
		logger("postToInfluxDB(): Something went wrong when posting: ${e}","error")
	}

}

/**
 *  handleInfluxResponse()
 *
 *  Handles response from post made in postToInfluxDB().
 **/
def handleInfluxResponse(hubResponse, data) {
    //logger("postToInfluxDB(): status of post call is: ${hubResponse.status}", "info")
    if(hubResponse.status >= 400) {
		logger("postToInfluxDB(): Something went wrong! Response from InfluxDB: Status: ${hubResponse.status}, Headers: ${hubResponse.headers}, Data: ${data}","error")
    }
}


/*****************************************************************************************************************
 *  Private Helper Functions:
 *****************************************************************************************************************/

/**
 *  manageSchedules()
 * 
 *  Configures/restarts scheduled tasks: 
 *   softPoll() - Run every {state.softPollingInterval} minutes.
 **/
private manageSchedules() {
	logger("manageSchedules()","trace")

    // Generate a random offset (1-60):
    Random rand = new Random(now())
    def randomOffset = 0
    
    try {
        unschedule(softPoll)
        unschedule(writeQueuedDataToInfluxDb)
    }
    catch(e) {
        // logger("manageSchedules(): Unschedule failed!","error")
    }

    randomOffset = rand.nextInt(50)
    if (state.softPollingInterval > 0) {
        
        logger("manageSchedules(): Scheduling softpoll to run every ${state.softPollingInterval} minutes (offset of ${randomOffset} seconds).","trace")
        schedule("${randomOffset} 0/${state.softPollingInterval} * * * ?", "softPoll")
    }
    
    randomOffset = randomOffset+8
    schedule("${randomOffset} 0/${state.writeInterval} * * * ?", "writeQueuedDataToInfluxDb")
}

/**
 *  manageSubscriptions()
 * 
 *  Configures subscriptions.
 **/
private manageSubscriptions() {
	logger("manageSubscriptions()","trace")

    // Unsubscribe:
    unsubscribe()
    
    // Subscribe to App Touch events:
    subscribe(app,handleAppTouch)
    
    // Subscribe to mode events:
    if (prefLogModeEvents) subscribe(location, "mode", handleModeEvent)
    
	if(!accessAllAttributes) {
    	// Subscribe to device attributes (iterate over each attribute for each device collection in state.deviceAttributes):
    	def devs // dynamic variable holding device collection.
    	state.deviceAttributes.each { da ->
        	devs = settings."${da.devices}"
        	if (devs && (da.attributes)) {
            	da.attributes.each { attr ->
                	logger("manageSubscriptions(): Subscribing to attribute: ${attr}, for devices: ${da.devices}","info")
                	// There is no need to check if all devices in the collection have the attribute.
                	subscribe(devs, attr, handleEvent)
				}
			}
		}
	} else {
		state.selectedAttr.each{ entry -> 
			d = getDeviceObj(entry.key)
			entry.value.each{ attr ->
				logger("manageSubscriptions(): Subscribing to attribute: ${attr}, for device: ${d}","info")
				subscribe(d, attr, handleEvent)
			}
		}
	}
}

/**
 *  logger()
 *
 *  Wrapper function for all logging.
 **/
private logger(msg, level = "debug") {

    switch(level) {
        case "error":
            if (state.loggingLevelIDE >= 1) log.error msg
            break

        case "warn":
            if (state.loggingLevelIDE >= 2) log.warn msg
            break

        case "info":
            if (state.loggingLevelIDE >= 3) log.info msg
            break

        case "debug":
            if (state.loggingLevelIDE >= 4) log.debug msg
            break

        case "trace":
            if (state.loggingLevelIDE >= 5) log.trace msg
            break

        default:
            log.debug msg
            break
    }
}

/**
 *  encodeCredentialsBasic()
 *
 *  Encode credentials for HTTP Basic authentication.
 **/
private encodeCredentialsBasic(username, password) {
	def rawString = "Basic " + "${username}:${password}"
    return rawString.bytes.encodeBase64().toString()
}

/**
 *  escapeStringForInfluxDB()
 *
 *  Escape values to InfluxDB.
 *  
 *  If a tag key, tag value, or field key contains a space, comma, or an equals sign = it must 
 *  be escaped using the backslash character \. Backslash characters do not need to be escaped. 
 *  Commas and spaces will also need to be escaped for measurements, though equals signs = do not.
 *
 *  Further info: https://docs.influxdata.com/influxdb/v0.10/write_protocols/write_syntax/
 **/
private escapeStringForInfluxDB(str) {
    //logger("$str", "info")
    if (str) {
        str = str.replaceAll(" ", "\\\\ ") // Escape spaces.
        str = str.replaceAll(",", "\\\\,") // Escape commas.
        str = str.replaceAll("=", "\\\\=") // Escape equal signs.
        str = str.replaceAll("\"", "\\\\\"") // Escape double quotes.
        //str = str.replaceAll("'", "_")  // Replace apostrophes with underscores.
    }
    else {
        str = 'null'
    }
    return str
}

/**
 *  getGroupName()
 *
 *  Get the name of a 'Group' (i.e. Room) from its ID.
 *  
 *  This is done manually as there does not appear to be a way to enumerate
 *  groups from a SmartApp currently.
 * 
 *  GroupIds can be obtained from the SmartThings IDE under 'My Locations'.
 *
 *  See: https://community.smartthings.com/t/accessing-group-within-a-smartapp/6830
 **/
private getGroupName(id) {

    if (id == null) {return 'Home'}
    //else if (id == 'XXXXXXXXXXXXX') {return 'Group'}
    else {return 'Unknown'}    
} 

This Smartapp allows you to have a smartapp in Hubitat that directly puts the data into the InfluxDB database. It was ported from a app writen a long time ago used with Smartthings that did the same for that platform. It was just tweaked to run in Hubitat. I think some have concerns about it's impact on the Hubitat hub performance. My 100+ devices on a C7 seem to not suffer any issues with that. The actual cpu usage seems pretty low. 

My stack for this is InfluxDB logger from Hubitat to Influx DB. InfluxDB runs on my unraid server now, but initially was running on a raspberry Pi. Then Grafana running on the Unraid server as well. This solution has worked really well for me over time. The only hiccup i have experienced was when the default docker got upgraded to influx DB 2.x. Just make sure you still install the 1.x version I think the current one is 1.86. 

I had this all working with a raspberry pi before upgrading my server os to Unraid and working with docker more.

Good idea, thanks.

I will focus on getting Hubigraph working better, making progress….

Then if I run into a wall will look at alternatives.

But thank you for this..

Bummer to hear this as I was hoping to get Hubigraphs up and running in the near future. Another option might be something like a Raspberry Pi running Home Assistant for graphing.

I use HA to push data from my Ecobee into hubitat. When I added my sensors into HA it automatically created a 24 hour graph. I've no idea how to customize it, but it must be possible. And I've also read that it is possible to do the opposite... send data from HE devices into Home Assistant/HomeKit. I think the Homebridge app does this.

here are the versions and files i am using for hubigraph hope it helps

3 Likes

The issue appears to be any device that was in LTS in Mar/Apr - ones I add now work ok.

If I remove then re-add the device into LTS it still does not work. :slight_smile:

I can get around this and largely not an issue now I have some graphing working and identified the boundaries of LTS.

Thanks to everyone who contributed - excellent community response - thank you!!!

It works for me too.. but if I access my dashboard over cloud api.. then the hubigraph tile loads up empty for some reason. Works perfectly if loaded locally.

1 Like

I don't recall it ever working over the cloud link...

1 Like

It doesn't work over Cloud dashboards.

1 Like

Hubigraph is working.

For most people totally, for some issues with the Long Term Storage sub system is causing issues.

You may never see an issue, but as long as you know it is no longer supported by the developer so no fixes on the horizon.

There will be a day, in the not so distant future...where the most functional & friendly best-in-class HA hubs will not be silo-ing these functions.

It will seem a short shrift not to have something as basic as graphs, a level of historical data tracking, and decision analysis based on data statistics and trends ....if not AI.

In an age of $600+ phones being bought on a more frequent cycle than what seems logical I don't get why HA hubs have to be so price limited (and thus capability restricted) in order to be adopted. Even at $225 a well developed, proven, and resilient hub that get upgrades and solves the broad spectrum of use requirements in a home over a 4+ year period is a worthwhile investment.

People in this forum regularly buy smart locks in this price range. Even Ubiquiti networking gear is in that price range; bought with the comfort that it is solid, functional kit that will last (and one could argue that for some, their HA hub is no less critical in their home topology).

The OP's use is clear case for more capability, and Hubigraph was a clear user/developer response to one level of such integrated solutions.

4 Likes

As I said it i am completely aware that it is a opinion. With that said very rarely is a integrated solution like that which is far from the intent of core design of something that is a complete solution.

For something to be anywhere as close to something like a InfluxDB, and Grafana they would wither need to integrate them(or similar solution) with its own database. Yes they could do that and then add something unrelated to their core to support and increase the hub to support needed cpu and storage requirements. This also then doesn't account for the then potential issue of folks then creating performance issues because of poorly written queries then reaching out for support.

AI is a intersting discussion but until something is anywhere close to useable i won't include that in any discussion.

The OP does have a use case no doubt. The best performant option would be use a smartapp or node-red to load all of the events from Hubitat to a external database. Then use one of the fantastic graphing dashboard tools avaliable like grafana. This minimizes load on the hub and eliminates the users ability to negatively impact the hub because of large or bad written queries.

For $35 someone can get a raspberry pi and do all of that. Shouldn't we prefer that over adding a significant cost to the hub.

3 Likes

Hmmmm, sounds like something I may have already done.... :sunglasses:

2 Likes

An 80-20 solution. In this case the 20% is the population of the general market that would opt for it.

I, for one, am satisfied with the current tools that HE offers. If I wasn't, I would have sold the hub by now. For a one-time payment I have received a "best in class" automation hub with 12-months (so far) of free support and upgrades. And some day, if the upgrades and new apps disappear, the community is likely to keep the system working as long as there is hardware still in the field. It seems unrealistic to believe that Hubitat will continue to provide support and updates to me, for free, in perpetuity. That said, I was pleasantly surprised when I installed Home Assistant and my three temperature sensors were automatically populated to a dashboard and clicking on them gave me a full 24-hour trend of historical data for the sensor.

I would love to have this available on HE without relying on 3rd party tools. My background is in industrial automation and SCADA systems, where the automation hardware (PLC/PAC), logic programming software, "dashboard" software (HMI), and database/trending software (historian) are all separate costs with very expensive annual maintenance fees. For Hubitat, where the emphasis is on data privacy and actual "ownership" of your home automation, I could see them offering a separate product geared towards trending and graphing. Perhaps it would be just a software app on your current hardware. Or more likely it would be a second hub running a database/graphing engine. I would have no concerns buying a second HE for dedicated use as a historian...ideally running genuine "Hubitat Historian" (trademark claimed! :wink:) software.

As far as the AI portion goes, I don't see that taking off with the current crowd of HE users. Most of us are tinkerers by nature, and are reluctant to hand the reins over to the machines.

2 Likes

I found this thread searching for how to add a graph to my dashboard.

So far what I gather is the following:

  1. Hubitat out of the box does not offer any type of way to view the data from my devices in a graph, or over time
  2. The recommended method of viewing my data via a graph is to buy another piece of hardware (Raspberry Pi), install an OS and software on that, and then somehow connect that to the Hubitat. If I was willing to set up a Raspberry Pi, I probably would have gone with HA as my software instead of the Hubitat hardware/software combo....
  3. There is a method within Hubitat to graph but it is not native Hubitat software and is no longer supported, and installing it is involved. I'm willing to put some effort into this, but the level of effort into just getting around the Hubitat interface and installing stuff to make it work is astonishing to me. I'm not putting effort into something that is not supported and may crash at any time.
  4. There is a new way to create graphs from data the Hubitat collects, but it is not native, and it requires an internet connection, and sending data to a third party and the internet. [RELEASE] HundredGraphs - create Graphs and Alerts

I read so many praises of the Hubitat before I bought this; looked forward to having a totally local device for 2 years. Finally took the plunge at the $99 sale. I'm quite disappointed - it never occured to me that with all the praises the Hubitat gets, I could not do something as simple as view my data in a graph. Very disappointed.

6 Likes

I would really love a native graphing solution too, but I feel like my Hubitat has been an incredible value... and I paid more than the $99. If you only focus on what it can't do, you'll never be satisfied. But if you use it to its full potential with what it can do, you're almost certain to be pleased.

Spend some time with it. Find a problem in your home and solve it. Just one or two small victories is all it takes.

5 Likes

One like to him for sympathizing with his disappointment that came with an all-too-easy misinterpretation of the scope of the product offering,
and one like to you for this true statement.


Whoops, the rest of this post triggered a FLAGGING by a member of the Fan Club. Revised to see if we're going to keep playing this game. Wow.

Hi @calinatl

While we are at HundredGraphs are not associated with Hubitat, we maybe still can help you. Can you let me know what issue you have with HundredGraphs and data collection? (if any)

It does require an internet connection since it is a cloud service, but besides that or maybe because of that we hope we are delivering a good value

Stanp: I have no issue at all with HundredGraphs. In fact, I think you have stepped up and provided a function to Hubitat that seems to me should be part of the basic device.

However, I bought the Hubitat to have a completely local solution. Thus a graph that requires an internet connection is not something I would implement.

I planned to not even connect the Hubitat to the internet, but since it looks like I need to download software to it every time I try to get something to function, I will keep it internet connected until I get everything set up. At that point I'd like to unplug the internet connection and have it all work local, and not share any data at all with anyone. That may not be possible, but at least I want to limit what goes to the internet.

If I were inclined to implement a graphing solution that requires me to send data elsewhere, I would want to understand what happens to that data. Your firm is offering the graphing solution for free; that usually means my data is the product. I have not read your firm's privacy policy.

It may be helpful, if you can advise, in general terms, what your privacy policy allows you to do with the data (not what you are currently doing or not doing with it, but what the policy allows you to do). Thanks.

1 Like

The issue is I may have a problem (with humidity) and bought both the Hubitat and some humidity sensors as part of my quest to solve this problem. However, since the Hubitat can't report on humidity over time, it appears the Hubitat may not be the appropriate device to be part of the solution...