Local API

Please correct me if I am wrong but this is making a call from Hubitat to a remote device/service. I need the reverse. I need my Hubitat device driver to receive an HTTP post from RoomMe.

I was looking at the Hubitat dev docs and was surprised to see websockets and associated events but not rest. A simple hubitat http event would do the trick.

That's the mechanism for you, I suspect. :slight_smile:

I looked at Maker but all I found was statefull connections. Didnā€™t see a way to just listen for http posts. Maybe I missed it. I am surprised as what I am looking for is so simple makes me think Iā€™ve missed it in my searches.

Is RoomMe a local device or cloud service?

The traditional method for integrating a HTTP type of device like this is to write an OAuth2 Application. These apps expose an HTTP endpoint which can easily be used to integrate both LAN and CLOUD connections, and are secure.

If the device is LAN connected, there is another fairly simple option to have a Device Driver receive unsolicited HTTP posts which avoids the OAuth2 complexities.

It is strictly local with no oauth support (at this point with V1). The unsolicited posts device driver sounds perfect. Could you please point me to an example? Thanks for your help.

Not exactly on point, but the UDP listener in Lifx might add some depth to the options.

Nothing is more 'unsolicited' than UDP :smiley:

Very interesting. Please forgive me if I am wrong but from what you said ad looking at the code I surmised that Hubitat is listening on 39501 and routes http calls to a device with the ID of the http sender IP. When a match is found it calls the parse method on the device.

Is this correct? If so could I ask where you found this documented? Iā€™ve searched poorly it seems.

You have the basic concept correct. As for where it is documented....:thinking:

SmartThings listened on port 39500 in the same way. I donā€™t think they documented it either.

1 Like

Gotcha. Thanks for the help.

Would be great if they added http as a first class citizen like the other protocols so a device driver could simply implement http_post(json) and go from there.

Absolutely.. and for the 77 things ahead of that :smiley:

2 more examples of drivers with MAC addresses or IP:PORT in the device network ID so they can receive messages from LAN devices.

I believe I have helper methods in one of both of those take set the DNI as well from an IP/PORT combo in decimal (since it needs to be in caps hex I think).

Cool, thanks.

I hear ya, the joys of managing a big feature backlog.

I'm guessing you want to write an app that can receive http calls. Here's an example you can use. It may not work as I stripped out a lot of code but the key points is looking at the mappings section as that routes it to the handler. You also need to enable oauth when you add the app.

It should give a you a good idea as to how you can receive a call and action on it.

definition (
    name: "Sample Endpoint App", 
    namespace: "GvnCampbell", 
    author: "Gavin Campbell", 
    description: "",
    iconUrl: "",
    iconX2Url: "",
    singleInstance: true
) 

preferences { page(name: "pageConfig") }
def pageConfig() {
    dynamicPage(name: "", title: "", install: true, uninstall: true, refreshInterval:0) {       
        section ("Devices",hideable:true,hidden:true) {
            input(name:"requestSource",type:"enum",title:"Allow Requests From",options:[0:"Cloud and Local",1:"Cloud Only",2:"Local Only"],defaultValue:0,required:true)
            
        }
        
        if (!state.accessToken) {
            createAccessToken() // create our own OAUTH access token to use in webhook url
        }

		section("URL's",hideable:true,hidden:true) {
            paragraph("<b>Running a command locally:</b>\n" + 
                      "${getLocalApiServerUrl()}/${app.id}/device/command/[Device ID]/[Command]?access_token=${state.accessToken}\n" + 
                      "<b>Setting an attribute locally:</b>\n" + 
                      "${getLocalApiServerUrl()}/${app.id}/device/attribute/[Device ID]/[Attribute]/[Value]?access_token=${state.accessToken}")
            paragraph("<b>Running a command remotely:</b>\n" + 
                      "${getApiServerUrl()}/${hubUID}/apps/${app.id}/device/command/[Device ID]/[Command]?access_token=${state.accessToken}\n" + 
                      "<b>Setting an attribute remotely:</b>\n" + 
                      "${getApiServerUrl()}/${hubUID}/apps/${app.id}/device/attribute/[Device ID]/[Attribute]/[Value]?access_token=${state.accessToken}")
        }

    }
}

// *** [ Initialization Methods ] *********************************************
def installed() {
    initialize()
}
def updated() {
    initialize()
}
def initialize() {
    
}

// *** [ Webhook Methods ] ****************************************************
mappings {
    path("/device/command/:d/:c") {
        action: [
            GET: "handlerWebhook"
        ]
    }
    path("/device/attribute/:d/:a/:v") {
        action: [
            GET: "handlerWebhook"
        ]
    }
}
def handlerWebhook() {
    log.debug "request: ${request}"
    log.debug "params: ${params}"
    if (requestSource=="0" || (requestSource=="1" && request?.requestSource=="cloud") || (requestSource=="2" && request?.requestSource=="local")) {
    } else {
        log.debug "Requests from '${request?.requestSource}' are blocked."
    }
}
2 Likes

Thanks. It seems RoomMe isnā€™t doing what itā€™ expected to do in terms of the http post. I am going over this with their head of R&D.

Is there any way to log or view a log of the traffic that Hubinet is getting at 39501 port?

In the Live Logs, if there is not a corresponding device with a matching deviceNetworkId (DNI), Hubitat will log a message in the Live Logs. I don't think it includes the payload, though.

What are you using as your DNI? It needs to be HEX encoded. It does NOT need the sending port as a suffix, just the IP Address. Look in my Parent Driver and you'll see the code that sets the DNI based on the entered IP address of the sending device.

Here's the helper function to convert a string IP address to HEX

private String convertIPtoHex(ipAddress) { 
    String hex = ipAddress.tokenize( '.' ).collect {  String.format( '%02x', it.toInteger() ) }.join()
    return hex.toUpperCase()

}

Which is used by the updated() function which is called after a user types in the IP address to the user setting and clicks SAVE

def updated() {
    log.info "Executing 'updated()'"
    log.info "Hub IP Address = ${device.hub.getDataValue("localIP")}, Hub Port = ${device.hub.getDataValue("localSrvPortTCP")}"
    log.info "Arduino IP Address = ${ip}, Hub Port = ${port}"
    
    def iphex = convertIPtoHex(ip)
    log.info "Setting DNI = ${iphex}"
    device.setDeviceNetworkId("${iphex}")
    
}
1 Like

Thanks. I put a sniffer on it and determined RoomMe is not respecting the destination port setting and defaulting to 80 instead. I assume there is no way to get traffic on 80?

I let the RoomMe guys know about this problem.

You'd have to create an OAuth2 enabled Hubitat App, instead of a driver. This would require the RoomMe folks to allow you to fully configure the URL that is being called, so you could include the OAuth2 key.

Ya, that makes sense. Easier for them to fix the port issue.

Turns out it was a bug in the RoomMe app and I got past that with their help so Iā€™ve got a proof working. Now on to building out the real RoomMe support.

1 Like