How to actually write a quirk - a complete(?) guide #4339
Replies: 2 comments 2 replies
-
@CalamityDeadshot can I suggest that you copy your guide into a That is, no one else can edit or contribute to this guide if it only available as your post here in this discussion thread, but if was available as code file in the Git repo then your new FYI, dmulcahey have an old draft for an initial introduced to ”quirks v2” which could maybe aslo be copied into your documentation. See: That new Anyway, thanks! PS: By the way, a guide on how to write v2 quirks for ZHA been requested many times here: |
Beta Was this translation helpful? Give feedback.
-
I am currently having issues with adding device_automation_triggers - this might be a useful section to add. Otherwise, great work! |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
Hey there. Got a niche quirky Zigbee device that just won't show some of its state and/or configuration in Home Assistant? Even if this device is supported by z2m, you don't want to migrate your entire network to a new integration, or don't want to have two separate coordinators and therefore two separate networks? Seems like your only option is to write a ZHA device handler, a.k.a. a quirk.
Motivation
I've been in the exact situation described above, and currently there is pretty much no way for a non-tinkerer or a non-technical person to get into writing quirks besides collecting and internalizing slivers of information across the web. I myself have only just written my first working quirk, and this is a "what I would have said to myself at the beginning" kind of article. Obviously, I'm not nearly a pro, so real pros are very welcome to correct and expand on this.Prerequisites
endpoints
,clusters
andattributes
.0. A simplified overview.
Any device has one or more endpoints. Any endpoint has one or more clusters of functionality, and any cluster has a number of attributes, which can be readable, writable, reportable or any combination of the three. Any attribute has an ID, a name, a data type, and a value. The value is what we're generally after.
1. Discovering what a device hides.
Before attempting to surface any attributes to ZHA, making it available as state or configuration in Home Assistant, you need to know where the attribute in question lives: which ID of which cluster of which endpoint.
A simple way to view that info is through the HA's Device info page:
Then, in the

Clusters
tab, you can click through every cluster's attributes. To view any attribute's value, just press the accentRead attribute
button.In this example, the device is an air quality monitor capable of measuring VOCs in the air and reporting it as an index (an integer in [1, 500]). This value is accessible through endpoint
3
, cluster0x000c
(AnalogInput
), attribute0x0055
(present_value
).If you can find all the values you are interested in via this process, you're in luck and can jump straight to section 2. But chances are, you can't find everything this way. If it is the case, you will need to do the following:
Your action yaml should be looking like this:
scan
object, as it contains everything ZHA Toolkit was able to find about the device. It has the following structure:A shortened version of the scan object
Look at that! Our attribute
present_value
of the endpoint3
, cluster0x000c
(analog_input
) is there, but so are other attributes we haven't seen before. They don't have aname
, likepresent_value
does, their name is just a decimal representation of their ID, and they won't show up in the "Manage Zigbee device" popup. Those are the attributes we'll be hunting for in our quirk.Be sure to save this data somewhere safe, as you'll be frequently consulting it when developing the quirk.
At this point you need to assign meaning to the attributes, noting the ones you want and discarding the ones you don't want. If you do not have any documentation on the device, try to find a zigbee-herdsman converter for it (it's basically the same as a quirk, but for the Zigbee2MQTT integration). It looks something like this. From it you can probably collect some relevant semantics for the attributes and assigning meaning will become a lot simpler.
Otherwise, you are on your own, and you are now a reverse engineer. Try doing something to the device, seeing which attributes change and deducing their meaning based on the change.
2. Enabling quirks.
configuration.yaml
file and add the following configuration:Where
your/quirks/dir/
is the path to the directory where you'll be placing your quirks. If this string doesn't start with a slash, the path is relative to the directory of theconfiguration.yaml
file. Create the directory if necessary.3. Writing a quirk.
We'll be using the Quirks V2 API, as it is far more user-friendly and powerful. In your quirks directory, create a Python file (
.py
extension) with a descriptive name, e.g.<manufacturer>_quirks.py
.The quirks V2 API starts with the call to a
QuirksBuilder
constructor, and ends with a call to its methodadd_to_registry()
:Pay special attention to the arguments of the constructor (manufacturer name, device name), as the device will be matched against that to decide whether to apply the quirk or not. Also notice the import at the beginning of a file - you can't use something without importing it first. Here, the
QuirkBuilder
is imported from this file. See examples of other quirks, or use the "search in repository" function if unsure where you need to import something from.3.1. Exposing a supproted attribute.
Now, for already available attributes (the ones with non-numerical names) the process of exposing them to ZHA is relatively simple:
The first three arguments of the
sensor()
function (attribute_name
,cluster_id
andendpoint_id
) combined tell Zigpy where to look for the value,state_class
anddevice_class
communicate what the value is to Home Assistant,reporting_config
is pretty self-explanatory,translation_key
is the key which HA will try to find in its translations to localize the name, and thefallback_name
is the name used iftranslation_key
is not found. Explore zigpy/quirks/v2 to see what else is possible.To apply the quirk, restart your Home Assistant instance and pay close attention to the
home-assistant.log
file (same directory asconfiguration.yaml
), as syntax errors in the code of your quirk will be reported there.If you've done everything right, your device will now expose a new sensor entity (VOC index in my case):
3.2. Exposing an unsupproted attribute.
My device has a read-write attribute in the Carbon Dioxide (CO₂) Concentration (
0x040d
) cluster under the endpoint2
with ID0x0205
, which tells the device the current altitude above sea level in meters for more accurate CO₂ measurements. This attribute being writeable means that it is a configuration attribute.Sadly, we cannot use its name directly (as it doesn't have one) - we need to give it a name, and for that we need to replace this device's CO₂ cluster with a virtual one we will write. This virtual cluster will find the attribute by its ID (
0x0205
), define its type and access level (all present in thescan_device
result), and finally give it a name.But there's a slight complication in this plan: we already have supported attributes in the Carbon Dioxide (CO₂) Concentration (
0x040d
) cluster, and replacing it with one attribute defined will make already supported attributes unsupported. Do we need to re-define them just for one more attribute? Fortunately, no.If we go to the zigpy/zcl/clusters directory of the zigpy repository, we'll find a measurement.py file with a CarbonDioxideConcentration class within. Instead of creating a brand new cluster and having to re-define all its attributes, we want to extend an existing cluster, appending a new attribute to it instead. Note that this class's
cluster_id
(0x040D
) corresponds exactly with our CO₂ cluster:Also note that this class does not define any attributes by itself, but extends another class -
_ConcentrationMixin
, which defines the attributes:That's because a lot of other classes representing concentration clusters (
CarbonMonoxideConcentration
,EthyleneConcentration
and so on) have exactly the same set of attributes.So, our custom cluster will look like this:
The
ZCLAttributeDef
'sid
argument is the ID of an attribute from the scan response, as is thetype
, as is theaccess
(r
stands forREAD
,w
stands forWRITE
andp
stands forREPORT
).So, after creating this cluster's class and replacing it, our quirk would look like this:
After successfully restarting Home Assistant, we've got a new configuration entity:

4. Conclusion
You now know how to scan devices for attributes and expose them to Home Assistatnt as entities. The basics, one might say. One can do a lot more, and if one wants to, being at least a bit familiar with the API is the first step to doing that.
Huge thanks to the authors of the Quirks V2 API.
Beta Was this translation helpful? Give feedback.
All reactions