[Request]Plaato Keg API App/Driver

I'm a pretty technical guy but I've never actually learned much scripting. This is probably outside of my current skillset since I've never done more than slightly modify a driver or two.

I've got two of the Plaato Keg sensors for my kegerator and it'd be handy to get the temp reading and level reading out of them into Hubitat. (And maybe the current pouring status for cool effect.. I could do something with the color lights on my bar during a pour :slight_smile: )

There is an API available as seen here. I also found via Google search a HomeAssistant integration.

I was wondering if someone had any interest in writing a driver/app to bring these in to Hubitat.

Or at the very least point me in the right direction. I've written a fair bit of batch scripts and worked with LUA here and there. I might be able to trudge along and figure this all out with a little hand holding. :eyes:

1 Like

This might work as a starter for you

Keg Test Code

/*
 * 
 *
 *  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: "Keg Test", 
		namespace: "thebearmay", 
		author: "Jean P. May, Jr.",
	        importUrl:""
	) {
        capability "Actuator"
       
        attribute "getReturn", "string"
        
        
        command "getPin",[[name:"pin*", type:"STRING", description:"Pin to retrieve"]]
    
    }   
}

preferences {
    input("auth_token","string",title: "Auth Token", required:true, 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()"
    updateAttr("getReturn"," ")

}

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

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

def initialize(){

}


def getPin(pin){
    if(security) {
        httpPost(
                [
                    uri: "http://127.0.0.1:8080",
                    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)
    
}


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)
    } 

}

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

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

Oh excellent. Thank you. I'll holler back if I have questions.

1 Like

Ouch. I'm building a home bar as we speak and thought your post was interesting. But $120 for this? And the API is 100% cloud based, meaning if they ever go out of business I've got a $120 coaster for under my kegs? Doesn't seem worth it to me. I wish they had a local API :frowning:

I spent some time playing around with this last night finally. I immediately ran into an issue where the response to any PIN gets this message.

groovy.lang.MissingPropertyException: No such property: getStatus for class: hubitat.scheduling.AsyncResponse

Best guess after searching the community board for a bit is that the response from Plaato is a JSON string rather than a JSON object, perhaps? I was trying to learn if there was a way to parse the JSON. But then I decided I should try and get the log to print the full response from Plaato. I never did figure out either. Could you point me in the right direction to figure out this error?

I’ve found that that error is generally the result of a failure to recieve a response from the other end of the communication attempt.

Could also try adding

requestContentType: 'application/json'

to the params…

I decided to try it with a different API testing app since the call is so simple. Every PIN was responding with "Requested pin doesn't exist in the app". Long story short, it turns out they have a different help center with PINs specific to Plaato Keg. So using the correct PIN gets the appropriate response! Now we're getting somewhere.

So the above issue is just that line 109 doesn't grab the failure string. But that's OK because I'm going to edit this to grab certain data with hardcoded PINs anyway.

The next step after that for me is to learn how to get this to check values every N minutes.

Thank you for your help so far.

1 Like
runIn(numSeconds,methodToRun)
1 Like

What is the security option for that uses the Hubitat user/pass? I think I understand what most of the script is doing, but that one part I'm not sure about.

Short version: If you enabled security on your hub any http call that it makes wants to have you authenticate before it sends it. The security option allows the driver to to handle that request automatically for you. If you aren't using security on your hub, then it does nothing.

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