Adding multiple temperature metrics when developing a device driver

Hey all,

I'm working on adding more data to the excellent Octoprint Driver by @mike10, however the oft lamented lack of documentation on how to write a driver for HE is slowing me down.

The data that is returned by the Octoprint API is in the following format:

{
  "temperature": {
    "tool0": {
      "actual": 214.8821,
      "target": 220.0,
      "offset": 0
    },
    "tool1": {
      "actual": 25.3,
      "target": null,
      "offset": 0
    },
    "bed": {
      "actual": 50.221,
      "target": 70.0,
      "offset": 5
    },
  },
  "sd": {
    "ready": true
  },
  "state": {
    "text": "Operational",
    "flags": {
      "operational": true,
      "paused": false,
      "printing": false,
      "cancelling": false,
      "pausing": false,
      "sdReady": true,
      "error": false,
      "ready": true,
      "closedOrError": false
    }
  }
}

And I want to extract and report on the temperature for each of the tools and the printer bed.

So far, I've modified the code from hubitatdrivers/OctoPrint.groovy at master ยท mikec85/hubitatdrivers ยท GitHub to include the following:

metadata {
...
        capability "TemperatureMeasurement"
...
}
...

def CheckJob() {
...
   GetStatus()

...
}

def GetStatus() {
    
    def wxURI2 = "http://${ip_addr}:${url_port}/api/printer"
    def toReturn = " "
    
    log.info "Connecting to ${ip_addr}"
        
    def requestParams2 =
	[
		uri:  wxURI2,
        headers: [ 
                   "User-Agent": "Wget/1.20.1",
                   Accept: "*/*",
                   "Accept-Encoding": "identity",
                   Host: "${ip_addr}",
                   Connection: "Keep-Alive",
                   "X-Api-Key": "${api_key}",
                 ],
	]
    
    try{
    httpGet(requestParams2)
	{
	  response ->
		if (response?.status == 200)
		{
            sendEvent(name: "printer_status", value: response.data)
            if (response.data.temperature.tool0 != null)
            {
                log.info "Getting information about tool0"
                sendEvent(name: "tool_0_actual_temperature", value: response.data.temperature.tool0.actual)
                sendEvent(name: "tool_0_target_temperature", value: response.data.temperature.tool0.target)
                sendEvent(name: "tool_0_temperature_offset", value: response.data.temperature.tool0.offset)
            }
            if (response.data.temperature.tool1 != null)
            {
                log.info "Getting information about tool1"
                sendEvent(name: "tool_1_actual_temperature", value: response.data.temperature.tool1.actual)
                sendEvent(name: "tool_1_target_temperature", value: response.data.temperature.tool1.target)
                sendEvent(name: "tool_1_temperature_offset", value: response.data.temperature.tool1.offset)
            }
            
            if (response.data.temperature.bed != null)
            {
                log.info "Getting information about printer bed"
                sendEvent(name: "bed_actual_temperature", value: response.data.temperature.bed.actual)
                sendEvent(name: "bed_target_temperature", value: response.data.temperature.bed.target)
            }
            
			toReturn = response.data.toString() 
		}
		else
		{
			log.warn "${response?.status}"
		}
	}
    
    } catch (Exception e){
        log.info e
        toReturn = e.toString()
    }
    
    return toReturn
}

And this returns the expected values in the results, and even updates the device status, but I can't find out how to associate the various temperatures returned with the TemperatureMeasurement metric type, meaning that it doesn't get picked up properly by the Hubitat/InfluxDB connector.

I'm sure this is a really simple fix, but I just can't work it out from the driver code I've got!

Did you define the custom attributes in the header?

I see you updating the "tool_1_actual_temperature" for example but without the custom attribute it won't actually stick.

As an example, In the metadata section add (you will need to add for every custom attribute too).

attribute "tool_0_actual_temperature", "String"

Then you should be able to reference that custom attribute in other places.

I'm assuming you've seen that the TemperatureMeasurement capability comes with a "temperature" attribute. What you might be missing--and I think are asking here--is that attributes on Hubitat are updated by way of events. That is what you're doing with sendEvent() in your code. But unless you specify name: "temperature" with your sendEvent(), then you aren't updating the temperature attribute.

I do see you creating events for attributes like bed_actual_temperature. However, unless you define these as custom attributes in your driver, this won't do much (it actually does make them appear temporarily under "Current States" until the device page is refreshed, but the behavior is not documented and I have no idea whether this actually generates an event, though I'd guess it might; but it's still not very useful). The example above shows you how to define a custom attribute if this is really what you want to do (though you may want to make it a "number" type instead of a string; you can specify units in sendEvent() if needed).

But: then there's the issue of whether you want to use custom attributes at all. The temperature attribute is a standard attribute (part of a capability) that many Hubitat apps know and love. I'm not sure if your graphing solution cares, or if you can specify any attribute. If you need to stick with the stock temperature attribute for some reason, you only get one per device, so the solution (or I guess one of many but probably the best option--with the downside that it can get messy if there are a lot) is to create child devices, one for each thing being measured, that implement this capability with the standard attribute. Which you choose is up to you, as long as it works with whatever apps you're using.

1 Like

Thanks both, I'll play with this over the next few days.

The idea of each tool/bed being a child device may be the best way to go here as there is talk of Octoprint supporting multiple printers in future.

I'll give it some thought...