We begin by assuming some familiarity with the Hubitat Elevation development environment and the Groovy programming language that it uses, though expert proficiency is by no means required. Read the Developer Overview if you have not already.
Unlike Zigbee, Z-Wave, and Matter devices — which are generally standardized in terms of how the hub can interact with them — LAN and cloud devices vary greatly in if or how they can communicate with your hub. Some devices have documented APIs you can use to integrate (e.g., Philips Hue — our built-in integration uses their well documented, local API). Naturally, documented APIs offer the best chance at developing a working driver. Further, we prefer local APIs whenever possible, though cloud interfaces can also work.
LAN and cloud devices may require different methods of communicating with your hub, depending on how the device or service you are writing a driver for works.
Hubitat offers several methods to communicate with devices over HTTP. Synchronous or asynchronous HTTP GET, PUT, or POST actions can be done using methods such as httpGet()
or asynchttpGet()
as described in Common Methods. The async methods are recommended whenever possible. Here is a snippet demonstrating one possible use:
// inside some app or driver method:
Map params = [
uri: "http://example.com,
requestContentType: "application/json",
contentType: "application/json",
path: "/myPath",
body: [myKey: "myValue"],
timeout: 15
]
asynchttpPost("myCallback", params)
// ...
void myCallback(resp, data) {
// Normally you'd also want to check for errors and actually
// use the data, but for this example, we'll just log:
def json = resp.json
log.trace "json = $json"
}
Drivers can connect to external devices or services using a websocket connection. See: Websocket Interface.
Drivers can connect to external devices or services using an eventstream (or Server-Sent Events) connection. See: Eventstream Interface).
Drivers can connect to MQTT brokers (the hub itself is not an MQTT broker). See: MQTT Interface
Drivers can connect to telnet servers. See: Telnet Interface.
Drivers (and apps) can build a HubAction
object and send the command this object builds. Like Z-Wave or Zigbee commands, this command will only actually be sent if it is returned from a method that implements a Hubitat device command or if you do so "manually" via sendHubCommand()
.
A HubAction
object can send HTTP, "raw" TCP, UDP, and similar messages (including WOL or UPnP SSDP).
For more information and examples, see HubAction Object.
Incoming traffic to port 39501 on the hub will be routed to a device with a DNI matching the IP address or MAC address of the source device (converted to hex, all uppercase, no separators). This is one way to handle unsolicited incoming traffic from LAN devices that can be configured to send data to a specific IP address and port. (For some example uses: some community users have developed drivers for local weather stations, "homemade" ESP8266/ESP32 devices, and a variety of other devices using this feature.)
This incoming traffic will be sent to the parse()
method in the driver. The data (passed in as the first and only parameter) can then be further processed (e.g., with parseLanMessage()
) or otherwise dealt with as necessary.
While not possible in a driver alone, apps can be configured to handle incoming HTTP traffic (GET, PUT, POST, or DELETE) by defining mappings
in the app code; specifying a path, action(s), and handler method for each action; and implementing the desired functionality inside this handler. This can include simply performing an action, returning JSON or other data, or even returning HTML content to serve.
definition( /* ... */)
preferences() {
// ...
}
// ...
mappings {
// simple example:
path("/myPath") { action: [POST: "myPathHandler"]}
// colon-prefix for path parameters:
path("/otherPath/:param1/:param2") { action: [GET: "otherPathHandler"]}
// Other possible actions: PUT and DELETE
}
def myPathHandler() {
// implicit `request` object, for example, to get body parameters:
def data = request.JSON
log.debug "request.JSON = ${data}"
// Could do nothing return JSON or other data,
// or to return HTML, return the value of render():
/*
String html = "<html><body><p>Hello, world!</p></body></html>"
render contentType: "text/html", data: html, status: 200
*/
}
def otherPathHandler() {
// implicit `params` object for path parameters:
log.trace "param1 = ${params.param1}"
log.trace "param2 = ${params.param2}"
}
To use mappings
, you will need to enable OAuth for your app using the OAuth button in the code editor for this app (as accessed via the Apps Code) page. You must also generate an access token, which will be required in order to authenticate all requests to your defined endpoints. This can be done by calling createAccessToken()
somewhere in your app code, generally only if state.accessToken
(which calling this method will automatically create) does not already exist, often early on in app setup.
In the example above, sending an HTTP GET to http://<yourHubIP>/apps/api/<installedAppID>/otherPath/abc/xyz?access_token=<yourAccessToken>
will print "abc" and "xyz" to Logs.
You can use methods such as
getFullLocalApiServerUrl()
to facilitate the construction of these endpoint URLs.
Actual apps will, of course, often do something more useful in these handlers.
Depending on the need, some apps with endpoints may be used on their own (this is how built-in apps like Maker API and Hubitat Dashboard were developed). Others may be combined with devices/drivers (e.g., if the app is used to help communicate with real-world devics), perhaps child devices of the app.
An implicit request
object (a map) is automatically available in handler methods above. Properties available on the request
object include:
body
: the raw body of the requestJSON
: an Object
(generally a map or list) representing the parsed body JSON data if the response body content type is JSONXML
: a GPathResult
representing the parsed body XML data if the response body content type is XMLrequestSource
: either "local"
or "cloud"
depending on whether a local or cloud endpoint was usedheaders
: a map with the request header names and valuesAs mentioned above, LAN and cloud devices and services vary greatly in how they work. The above summarizes approaches that can work for many of these. Which is best — or if any of these provided methods are able to work for your particular device or service — depends on exactly how that device or service works.
Some LAN or cloud devices may be best served with a driver only. Others use an app and driver combination, either to facilitate setup (e.g., so users do not have to manually create "virtual" devices with the driver) or because it is the only possible option (e.g., cloud integrations that can be configured to send data to specific endpoints that only an app can handle with mappings
— a driver cannot do this). Apps will generally create "child devices" (of the app) to represent any actual devices they handle.
For more information on methods you may need in apps or drivers, consult the remaining developer documentation. The following may be particularly helpful:
For a simple example LAN driver that sends an HTTP GET when its own (effectively virtual) switch is turned on or off, see:
https://github.com/hubitat/HubitatPublic/blob/master/examples/drivers/httpGetSwitch.groovy