[Request]Plaato Keg API App/Driver

I haven't had to do this with any of my drivers, but I also don't have hub security enabled. What is authenticating with Hubitat if the driver command is just attempting to send an HTTP call to the remote server?

If security is enabled on your hub, it won't send an HTTP call without prompting you for the hub username and password (unless it already thinks it has it which is what the code does).

What prompts you for the hub security? It's an operation in a driver command - do the HTTP operations fail without (EDIT for clarity -- without Hubitat auth) authentication somehow? The driver is already running on the hub and talking to a remote server.

What I've normally seen is that when the driver makes a call the Hubitat Login page comes back as the response, and the HTTP request never gets sent. Not an issue if you don't use hub security or you include the hub credentials as part of the call (that's the cookie in the parameters).

Very interesting -- I'll have to experiment with it some, and thanks for the tip. Are you sure that's not only for calls to an API directly on the Hubitat hub? It would be a very strange design choice (and terrible user experience) to require a copy of the credentials and an authentication step with the Hubitat server before talking to an unrelated remote server.

OK, bear with me I'm learning...

I'm pretty easily able to copy/paste and move things around to get the data I want into the attributes I want. Now I'm wondering if there's a more efficient way to write all this.

I have three of these commands that hardcode the pin to use.

def getRemaining(){    
        params = [
            uri: "http://plaato.blynk.cc",
            path: "/$auth_token/get/v48",    //Returns a number
        ]  
        asynchttpGet("sendGetHandler", params)
}

Now I can make duplicate callback methods that only swap out the attribute it saves to.

def sendGetHandler(resp, data) {
    try {
        if(resp.getStatus() == 200) {
		    strWork = resp.data.toString()
    		if(debugEnable) log.debug strWork
	        updateAttr("getReturn",strWork)
  	    } else
            updateAttr("getReturn","Return Status: ${resp.getStatus()}")
    } catch(Exception ex) { 
        updateAttr("getReturn", ex)
    } 
}

Copying that, changing the function name, and swapping out getReturn is the easy way. However I'm trying to figure out if there's a way I can pass that data back to the previous function and save it to an attribute there OR pass an additional parameter that defines what attribute to save the data to. I've tried a few things and I don't think I yet understand enough about groovy or the way the Hubitat http request works to figure it out without another push.

I'd use the option data parameter to send this info from asynchttpGet to the handler: Async HTTP calls

Tell me a little more. I was reading that page and tried a few things over the last hour, but none were successful.

I'm trying to understand the "why" of how this works instead of just typing script at random until it seems to do what I want.

This example is the critical part:

def sendAsynchttpPost() {
    def postParams = [
		uri: "http://httpbin.org/post",
		requestContentType: 'application/json',
		contentType: 'application/json',
		headers: ['CustomHeader':'CustomHeaderValue'],
		body : ["name": "value"]
	]
    
	asynchttpPost('myCallbackMethod', postParams, [dataitem1: "datavalue1"])
}

def myCallbackMethod(response, data) {
    if(data["dataitem1"] == "datavalue1")
    	log.debug "data was passed successfully"
    log.debug "status of post call is: ${response.status}"
}

Send a value in the data map like dataitem1 that represents the method that you're querying. Then, in the handler, pull out that data value and update the attribute based on that. Simplest way would be to pass the attribute name and maybe the type if needed to build out the logic in the handler.

For example:

def getRemaining(){
       getPin("v48", "remaining")
}

def getPin(pin, attrName){    
        params = [
            uri: "http://plaato.blynk.cc",
            path: "/$auth_token/get/$pin",    //Returns a number
        ]  
        asynchttpGet("sendGetHandler", params, [name: attrName])
}

def sendGetHandler(resp, data) {
    try {
        if(resp.getStatus() == 200) {
		    strWork = resp.data.toString()
    		if(debugEnable) log.debug strWork
	        updateAttr(data.name,strWork)
  	    } else
            updateAttr(data.name,"Return Status: ${resp.getStatus()}")
    } catch(Exception ex) { 
        updateAttr(data.name, ex)
    } 
}
2 Likes

Oh I see how that works now. The example in the documentation confused me, but your example helps me understand what is going on and why my attempts failed. Thank you.

1 Like

Ok, next hurdle...

Some of the calls return something like...

["23.850"]

...as a string. Looks like a really simple JSON object with the data at index 0. So what's the simplest way to grab that out as a number?

Something like this should work:

respData = '["23.850"]'
fVal = respData.substring(3,9).toFloat()

oh.. duh. Skip the json object entirely and just grab the characters in the middle.

Sometimes it's just simpler...

I would do this, just in case the data format changes (less digits or whatever):

def resp = '["23.850"]'
def map = new groovy.json.JsonSlurper().parseText(resp)
log.debug "${map}"
log.debug "${map?.getAt(0)?.toFloat()}"
1 Like

OK! I think I got it doing exactly what I want. Open to suggestions for improvements or otherwise cleaning it up. This is my first attempt at anything quite like this for Hubitat. Thank you both for all the help.

What are the limits with runIn for something like this? I don't want to bog down the hub, but if I could do a shorter runIn without worry I would add the Pouring Status to the driver. But that would have to be run every minute or use runInMillis() to make that even worth it. Not really a big deal. Party trick at best to have something happen when someone's pouring a beer.

/*
 * 
 *
 *  Licensed Virtual 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.
 *
 *  Change History:
 *
 *    Date        Who            What
 *    ----        ---            ----
 *    
 *
 */

static String version()	{  return '0.0.1'  }


metadata {
    definition (
		name: "Plaato Keg", 
		namespace: "thebearmay", 
		author: "Jean P. May, Jr.",
	        importUrl:""
	) {
        capability "Sensor"
       
        attribute "vpinReturn", "string"
		attribute "beerLeft", "decimal"
		attribute "temperature", "decimal"
		attribute "leakDetected", "bool"
        
		command "configure"
		command "refresh"
		//command "getPin",[[name:"pin*", type:"STRING", description:"Pin to retrieve"]]
    }   
}

preferences {
    input("auth_token","string", title: "Auth Token", required:true, submitOnChange:true)
	input("timer_pref","number", title: "Refresh Timer", description: "In minutes 1-60", required:false, defaultValue: 5, range: 1..60, submitOnChange:true)
    input("debugEnable", "bool", title: "Enable debug logging?")
    input("security", "bool", title: "Hub Security Enabled", defaultValue: false, submitOnChange: true)
    if (security) { 
        input("username", "string", title: "Hub Security Username", required: false)
        input("password", "password", title: "Hub Security Password", required: false)
    }
}


def installed() {
	log.trace "installed()"
}

def configure() {
    if(debugEnable) log.debug "configure()"
    refresh()
}

def updateAttr(aKey, aValue){
    sendEvent(name:aKey, value:aValue)
}

def updateAttr(aKey, aValue, aUnit){
    sendEvent(name:aKey, value:aValue, unit:aUnit)
}

def initialize(){

}


def refresh(){
		if(debugEnable) log.debug "Fetching data..."
		getRemaining()
		getTemp()
		getLeak()
		//updateAttr("vpinReturn"," ")
		timer = (timer_pref * 60)
		if(debugEnable) log.debug "Running refresh in ${timer} seconds"
		runIn(timer, refresh)
}


def getPin(pin){    
        makeGetHandler("$pin", "vpinReturn")	//Returns a string
}

def getRemaining(){    
        makeGetHandler("v48", "beerLeft")	//Returns a number
}

def getTemp(){    
        makeGetHandler("v56", "temperature")	//Returns a string
}

def getLeak(){    
        makeGetHandler("v83", "leakDetected")	//Returns a string; 0 if no leak, 1 if leak
}


def makeGetHandler(pin, attrName){
    if(security) {
		if(debugEnable) log.debug "Security enabled..."
        httpPost(
                [
                    uri: "http://remoteaccess.aws.hubitat.com",
                    path: "/login",
                    query: [ loginRedirect: "/" ],
                    body: [
                        username: username,
                        password: password,
                        submit: "Login"
                    ]
                ]
            ) { resp -> cookie = resp?.headers?.'Set-Cookie'?.split(';')?.getAt(0) }
     }    
        params = [
            uri: "http://plaato.blynk.cc",
            path: "/$auth_token/get/$pin",
            headers: [ "Cookie": cookie ],
        ]  
	
        asynchttpGet("sendGetHandler", params, [name: attrName])
}


def sendGetHandler(resp, data) {
    try {
        if(resp.getStatus() == 200) {
			if(data.name == "beerLeft") {
				pltoResp = resp.data.toFloat()
			} else if(data.name == "temperature"){
				strWork = resp.data.toString()
				pltoResp = strWork.substring(2,8).toFloat()
			} else if(data.name == "leakDetected"){
				strWork = resp.data.toString()
				intWork = strWork.substring(2,3).toInteger()
				if(intWork > 0){
					pltoResp = true
				} else
					pltoResp = false
			} else{
				pltoResp = resp.data.toString()
			}
    		if(debugEnable) log.debug "${data.name} returned ${pltoResp}"
	        updateAttr(data.name,pltoResp)
  	    } else
            updateAttr(data.name,"Return Status: ${resp.getStatus()}")
			if(debugEnable) log.debug "${data.name} returned Error ${resp.getStatus()}"
    } catch(Exception ex) { 
        updateAttr(data.name, ex)
    } 
}

def updated(){
	log.trace "updated()"
	if(debugEnable) runIn(1800,logsOff)
}

void logsOff(){
     device.updateSetting("debugEnable",[value:"false",type:"bool"])
}

Congratulations you are now a developer😆.

Every minute could work, lot of variables though based on app and device mix. Need to watch memory as there seems to be a small memory leak with doing http calls - normally not an issue (my Hub Information driver uses several and It’s usually a couple months before I need to reboot) but something to keep in mind.

2 Likes

Perhaps not a developer.. but this exercise taught me a lot about drivers code. In fact, I was finally able to fix up a buggy driver I got from SmarthomeDB that kept spewing groovy errors into the debug log.

3 Likes

This topic was automatically closed 365 days after the last reply. New replies are no longer allowed.