Zigbee Drivers starting point

Does anyone know the starting point for writing drivers? I feel like the documentation is like reading a dictionary. It is meant for reference for someone who already knows what they are doing. There isn't even a good definition of what a driver is. I can see the list of capabilities but there is no description of what exactly a capability is or descriptions of the individual capabilities. I have created a driver for just turning an outlet on and off but I just did that from copying existing drivers. Also the zigbee commands are not defined anywhere in the documentation. Tokens like "he" or "rattr" are not described in the docs anywhere. I assume "he" is "hub transmit", "rattr" is zcl read attribute but I don't want to be reverse engineering the sample code to figure out what the commands are.

I learned (and am still learning) driver writing by looking at examples to other drivers and reading the smartthings documentation. Their documentation is pretty good and explains alot.

I have found though that lot of drivers have overcomplicated things and added so many features and custom things that when you try to read them you get lost in all of that. So I have over the years gone through and rewritten all of my own drivers. It has not only given me a indepth knowledge of every device I own but also the power to fix it when something doesn't work right.

I recommend taking a simple device you own and start just writing your own driver for it. A switch is usually very simple as most just have on/off and power reporting. You can then play with the more advanced stuff later, which will become easier as you understand whats going on.

Here is a good reference for the zigbee drivers.
https://docs.smartthings.com/en/latest/ref-docs/zigbee-ref.html

In regards to the actual coding structure, you will notice a few things common in all of the drivers.

  • they will all have a metadata section that has the definition and preferences section
  • their are some key methods that are called at certain times: installed(),updated(),configure(),uninstalled() as well as parse() which handles all incoming messages.

There are also some good driver examples in the Hubitat github. They have some basic ones to start with that are really good.

I was actually rewritting my zigbee drivers last night and looking up the information. For the most part it has become a lot more simpler than that. They have built in methods like zigbee.on() and zigbee.off() that wrap the raw commands. So it gets you away from the rattr and he raw commands. You will also see things like zigbee.readAttribute and zigbee.writeAttribute that will also make it a lot more understandable.

And while its not the most detailed documentation, you get an idea of whats available in the Hubitat docs.

https://docs.hubitat.com/index.php?title=Zigbee_Object

Good luck and sorry for the mind dump. Its just a big topic.

2 Likes

So far all the documentation I've found has been garbage. I still haven't seen any good definition of what a driver even is.
So first question what is a driver? I sort of know what it can do and it's parts but I can't actually give a good description of what it is, which means I really don't know what it is. I also assume drivers are needed to make a zigbee device look like a wifi device. Since the zigbee app layer is already well defined I can see no reason devices would ever actually need this. The hub should just discover what a device is capable of and know how to use those capabilities.

metadata
The meta data seems to have parts describing the driver -

  • definition where are the fields of this used? The word definition looks like a function call. OK - I don't know groovy, maybe the syntax makes sense to a groovy expert.
  • capabilities - what are they, is this like some sort of inheritance. So if I support an outlet Driver Capability List - Hubitat Documentation my device will then have on/off in the application but I have to implement on() and off()
  • attribute - this looks like a way to define a variable, variables have a name and a type. I assume the scope is the namespace in the definition?
  • command - these look like function prototypes but without the signatures
  • fingerprint - these seem to be to match a device that has joined the network. I've never gotten one to work. I need a finger print to match an endpoint and then have some sort of instance of the driver is associated with that endpoint created or made available to the user. However I suspect finger prints match the entire device, or some hybrid of the device.
  • preferences - are these setting values of inherited attributes? Inherited from the capabilities?

Writing functions

  • sendEvent() seems to update variables/attributes in the namespace - maybe it triggers an update of the UI?
  • cmd - this appears to be a raw string send to a zigbee co processor that has some sort of CLI
    he means transmit a packet
    cmd means create a cluster specific command
    rattr means create a read attribute command

parse appears to be a bunch of key value pairs that are the sent when a zigbee packet is received. Each driver appears to have to implement this even though it's results should be identical for all devices. A parse function ends with a call to sendEvent() to update the namespace and the UI.

Does this make sense?

A driver tells Hubitat how to communicate with the device, both in terms of "translating" Hubitat commands to actual Zigbee and parsing incoming Zigbee messages into events (or not) on the hub. The goal isn't really to "look like a Wi-Fi device," but the goal is to abstract way device-specific details--including protocol (Zigbee, Z-Wave, Wi-Fi, etc.)--into Hubitat-defined "capabilities" and the commands and attributes that correspond to those. That way, Hubitat apps (automations, including rules) can use them in fairly standard ways without worrying about device-specific details.

For the rest of your questions, this may help:

It may be good to start with capabilities. It appears you've seen Hubitat's official list, but if not: Driver Capability List - Hubitat Documentation. If your driver declares capability "Switch", then what the docs for this mean is that you must have a switch attribute (more on attributes later) that reports either "on" or "off" as a value, and you must implement on() and off() commands. I'd say a capability is more like an interface than inheritance--nothing comes for free (except the display of the commands in the UI and an error if you don't implement them), so it's more of a contract that you'll implement them. To implement a command, you just need a Groovy method in your driver with the same name and matching signature(s).

Attributes are the values you see under "Current States" on the device page. These changed with sendEvent() (the purpose of events is generally to announce that an attribute has a new value). So if you hear back from the switch that it is on, something like this would change the "switch" attribute to "on" under "Current States" on the device page in Hubitat:

sendEvent(name: "switch", value: "on")

Anything Zigbee-wise that the device sends to the hub should come into the driver's parse() method. Here is one of Hubitat's examples:

def parse(String description) {
    if (logEnable) log.debug "parse description: ${description}"
    if (description.startsWith("catchall")) return
    def descMap = zigbee.parseDescriptionAsMap(description)
   // ...

This implementation really shouldn't be the same for every device--different devices are going to use different clusters (in the above, I'd look at descMap.clusterInt) and attribute values, but I do suppose you mean that for most devices of similar types, you'd expect these to be largely the same. I think the bigger issue is that you cannot dynamically adjust the capabilities (and therefore the expected commands and attributes) of a device, so you at the very least need something written for that general kind of device so it works more like users expect on the platform--e.g., so a thermostat doesn't show up with color bulbs, though nothing is stopping anyone from writing a "Zigbee everything" driver if they wanted to. Hubitat does have a lot of "Generic Zigbee <device type>" drivers built-in that cover large swaths of devices, so custom or device-specific drivers are not always necessary.

Anyway, in parse() (or something that parse() may call), you will generally do a sendEvent() as needed to update the attibutes. This isn't just a UI thing, as Hubitat is an event-driven automation system where apps (automations) can "subscribe" to device events and do things in response--an important part of how the platform works.

I'm not much of a Zigbee person so can't help if you have specific questions about how Hubitat's "raw" Zigbee commands work (there are some methods that are supposed to help you so you don't actually have to write these), but perhaps someone else can. I can tell you that I'm pretty sure "he" just stands for "Hubitat Elevation." :slight_smile:

For a fingerprint, I normally find it easier to just switch to the "Device" driver, then run the "Get Info" command, which will spit out a fingerprint to "Logs." Then, use that in your driver to facilitate a match on pairing.

Also, Hubitat's app and device model was inspired by (and largely compatible with) that of SmartThings, whose documentation people also like to make fun of but is somewhat better at explaining these roles. One key difference is that everything runs on the hub, not the ST cloud. Also, "device handlers" are called "drivers" over here. This may be a good starting point: Overview — SmartThings Classic Developer Documentation. Their Z-Wave docs still look a bit better to me, but maybe that's because I understand them better...

Also, something I wish someone would have told me when I started out: a driver will "automatically" send a message to a device if that command (a string or list of strings) is the return value from a method and that method is a command. That is why a method like this works in Hubitat's RGBW bulb driver example:

def on() {
    def cmd = [
            "he cmd 0x${device.deviceNetworkId} 0x${device.endpointId} 0x0006 1 {}",
            "delay 1000",
            "he rattr 0x${device.deviceNetworkId} 0x${device.endpointId} 0x0006 0 {}"
    ]
    return cmd
}

If you declared that method as void instead or were looking to do something outside of a command, you can "manually" send it with HubAction instead, but that is often not necessary. On the topic of return values, you may also see createEvent() instead of sendEvent() in some ST docs (doesn't seem to be as popular on Hubitat), which I think only actually sends the event of the parse() method returns what createEvent() returns. You can also declare parse() as void() and just call sendEvent() (which always works) yourself wherever needed--my preference by far!

I probably missed a few questions, but hopefully this helps a bit. :slight_smile:

4 Likes

This is no different on Hubitat than on Windows. When you plug in a USB device, it doesn't just know what the device is. It has to have a driver to know what it is, and what it does. And if the new device doesn't match an existing device, you may have to download a new driver.

In our case with Hubitat, you can either import someone's driver they wrote, or create your own to get this same effect.

So in my non-coder view, I don't expect the hub to automatically "discover what a device is capable of" (and here is probably the more relevant point) "know how to use those capabilities" any more than I would Windows to know what something is.

Maybe I missed it somewhere upthread, but there are quite a few examples of Hubitat drivers (and apps) on their Github. Those might be a good starting point along with the Smartthings documentation linked above. HubitatPublic/examples/drivers at master · hubitat/HubitatPublic · GitHub

Technically it is possible for the hub to know what a device is capable of and then support it. When a device is added to the hub (zigbee or zwave) it gets a list of clusters or classes that the device supports so it knows.

However, the issue is with the device manufacturers. They don't all stick to the standards that they shood. For example, one contact sensor I wrote a zigbee driver for reports switches as on/off instead of a contact sensor as open/closed. This would throw the hub off. So you have to create a driver to handle this situation know that this device isn't playing nicely.

There are a lot of devices like this. Some report battery % while some report battery voltage and others may just report "battery low" as an alert.

In your zigbee parse method look at use the "zigbee.getEvent" method. It will parse the description into a known event object that you can just use "sendEvent(evt)" to send off. It will only parse a few of them. If it doesn't parse it then use the "zigbee.parseDescriptionAsMap" and then act on the clusters reported.

The zigbee cluster manual helps you understand the various clusters. They start in chapter 3.

2 Likes

The difference here is that Zigbee specified the app layer of the protocol and all ON/OFF devices will work the same. All light dimmers are the same. All batteries are...OK - Batteries are a mess. But lights, electrical outlets, switches, metering devices, sensors - {temperature, pressure, humidity, CO2 etc}, they are all exactly the same. Over the Air Upgrades - the same.

I beg to differ. There are lots of manufacturers who use non-standard clusters even on "standardized" zigbee 3.0 devices. Creating a need for device-specific drivers. And this isn't unique to Hubitat.

Edit - off course, this digression isn't making your job easier. If I were you, I would use existing community drivers for similar devices as a starting point.

They should be, but they aren't.

1 Like

Thank you Bertabcd1234, that's an amazing starting point. Looks like I have to write 48 different drivers for all my devices. This really helps.

I still can't get the finger print to work. Here is my simplest device. It is an outlet with power measurement on both outlets but only one of the outlets can be turned on or off. It has 3 endpoints. Endpoint 1 contains the OTA upgrade cluster, Endpoint 3 the switchable outlet and endpoint 4 the power measurement for the other outlet.
ID: F11F //this is the zigbee(802.15.4) short address
Manufacturer: Swidget
Product Name:
Model Number: Insert
deviceTypeId: 354 //no idea where this is coming from

manufacturer : Swidget
idAsInt : 1
inClusters : 0000,0003
endpointId : 01
profileId : 0104
application :
outClusters : 0019
initialized : true
model : Insert
stage : 4

manufacturer : //this isn't populated, not sure why, could this be my problem?
idAsInt : 3
inClusters : 0000,0003,0006,0702,0B04
endpointId : 03
profileId : 0104
application :
outClusters :
initialized : true
model : //again, only populated on endpoint 1

stage : 4
manufacturer :
idAsInt : 4
inClusters : 0000,0003,0702,0B04
endpointId : 04
profileId : 0104
application :
outClusters :
initialized : true
model :
stage : 4

Note that Hubitat firmware 2.2.8 introduced the "libraries" feature, which lets you define a small (or large) code snippet that can be inserted into apps or drivers with an #include. See:

If all of your devices should ultimately expose different capabilities to Hubitat, then you may need different drivers for each, but if there is significant overlap between some or all, this will save you some typing. Again, I'm not as versed on Zigbee driver development, but with Z-Wave the parse() method often calls other methods that take care of handling incoming messages from specific "command classes" (roughly like Zigbee clusters), and for lots of Z-Wave devices these work the same--just a matter of having the right ones in the driver. Up to you how you do it either way, though! And I'd definitely second the recommendation to take a look at Hubitat's example drivers above if you haven't already.

I would switch to the "Device" driver (this is the actual name of the driver you can find in the "Type" dropdown on the device page). Then, open "Logs" in a new tab/window and hit "Get Info" on the device page. This will write a fingerprint to the logs that you can copy/paste into your driver verbatim. Whether it may have trouble working without specific parts specified by the device, I don't know...

1 Like

I can't see the [get info] button on the device page.

This is what you should see if you make the driver "Device" for any device.

1 Like

Here is what my page looks like:

Your driver needs to be "Device" and not "Ikea Tradfri Signal Repeater".

No idea why it is matching on an Ikea TRADFRI Signal Repeater - maybe that's the closest match for my endpoint 1.

Perhaps. But you can change the driver to whatever you want. In this case, change it to "Device".

And when you do that, and click "Save", you'll see a "Get Info" button. When that button is clicked, the logs will show the fingerprint for the device .... something like this

Ah, OK, I set the parent device type to device. Then the button appears. I press it, the finger print shows up in the log.
I can now also see all three of my endpoints as children on the devices page.

I'm now screwed. All 48 of my devices will have the exact same endpoint 1, so their finger prints will all be identical. I have to think about this.

1 Like

Sorry to sound frustrated. You guys really are a lot of help.

Thanks,
Jonathan

1 Like

What type of devices are these 48 Zigbee devices? Are all 48 devices truly unique in their capabilities? If some are identical, then the same driver can be used. Hard to believe that you'd need to write custom drivers for all of them, unless that are truly custom Zigbee devices that you've built?

1 Like

I think I should explain what I'm building. I have one insert that is a zigbee radio module that can have different sensor modules attached to the radio module. The insert can go into a number of different host types. For example a host could be an electrical outlet, an on/off switch or a dimmer switch. The daughter boards can sense occupancy, temperature, humidity, pressure, vibration, luminosity, CO2 content, air quality and a few other things.
The insert will reveal different zigbee endpoints depending on what host it is in and what sensing abilities the daughter board gives it. All permutations will have on endpoint 1 the basic cluster and the Over the Air upgrade cluster.
Currently I have 6 hosts and 8 possible sensor combinations. When I use the devices with Amazon Alexa she just discovers the clusters she understands and uses them. Although she has lots of limitations in what zigbee clusters she supports.

2 Likes

Download the Hubitat app