Package: zha-quirks
ZHA Device Handlers For Home AssistantZHA Device Handlers are custom quirks implementations forZigpy, the library that provides theZigbeesupport for theZHAcomponent inHome Assistant.ZHA device handlers bridge the functionality gap created when manufacturers deviate from the ZCL specification, handling deviations and exceptions by parsing custom messages to and from Zigbee devices. Zigbee devices that deviate from or do not fully conform to the standard specifications set by the Zigbee Alliance may require the development of custom ZHA Device Handlers (ZHA custom quirks handler implementation) to for all their functions to work properly with the ZHA component in Home Assistant.Custom quirks implementations for zigpy implemented as ZHA Device Handlers are a similar concept to that ofHub-connected Device Handlers for the SmartThings Classics platformas well that ofZigbee-Herdsman Converters (formerly Zigbee-Shepherd Converters) as used by Zigbee2mqtt, meaning they are virtual representation of a physical device that expose additional functionality that is not provided out-of-the-box by the existing integration between these platforms. SeeDevice Specificsfor details.How to contributePrimerZHA device handlers and its provided Quirks allow Zigpy, ZHA and Home Assistant to work with non-standard Zigbee devices. If you are reading this you may have a device that isn't working as expected. This can be the case for a number of reasons but in this guide we will cover the cases where functionality is provided by a device in a non specification compliant manner by the device manufacturer.What are these specificationsReference official Zigbee specification documentation from Connectivity Standards Alliance (a.k.a. "CSA-IOT", formerly "Zigbee Alliance"):Zigbee Cluster Library SpecificationZigbee Cluster Library Specification R8 (Revision 8)Zigbee Cluster Library Specification R7 (Revision 7)Zigbee Cluster Library Specification R6 (Revision 6)Zigbee Protocol Specification (also known as "Zigbee Pro" specifications)Zigbee Protocol Specification 2023 (also known as "Zigbee PRO 2023" / Zigbee R23)Zigbee Protocol Specification 2017 (also known as "Zigbee PRO 2017" / Zigbee R22)Zigbee Protocol Specification 2015 (also known as "Zigbee PRO 2015" / Zigbee R21)Zigbee Device SpecificationsZigbee Base Device Behavior Specification (v1.0)Zigbee Lighting & Occupancy Device Specification (v1.0)Zigbee Green Power (ZGP "GreenPower" Profile) specificationsZigbee PRO Green Power feature specification Basic functionality set (v1.1.1)Zigbee PRO Green Power feature specification 1.0a (Revision 26)Zigbee Smart Energy (ZSE / Zigbee SE "Smart Energy" Profile) specificationsZigbee Smart Energy Standard 1.4ZigBee Smart Energy Standard (v1.2a)Additionally, see these third-party and manufacturer specific documents:Tuya - Zigbee Connection Standard (Tuya Smart Documentation)Zigbee2MQTT guide on understanding the custom 'manuSpecificTuya' cluster that TuYa devices usesSamsung SmartThings -Device HandlersSamsung SmartThings - Zigbee PrimerSamsung SmartThings - Building ZigBee Device HandlersWhat is a device in human termsA device is a physical object that you want to join to a Zigbee network: a light bulb, a switch, a sensor etc. The host application, in this case Zigpy, needs to understand how to interact with the device so there are standards that define how the application and devices can communicate. The device's functionality is described by severaldescriptorswhile the device itself containsendpointsandendpointscontainclusters. There are two types of clusters an endpoint contains:in_clusters- are "Server" clusters in ZCL terms. These clusters control the device, e.g. a smart plug or light bulb would have anon_offserver cluster.in_clustersare also the ones which also send attribute reports and/or you can read an attribute from anin_cluster.out_clusters- are "Client" clusters. These clusters control some other device, as "Client" cluster sends commands to "Server" cluster. For example an On/Off remote would have anon_offclient cluster and will generate cluster commands and send those to some other device.
Zigpy needs to understand all these elements in order to correctly work with the device.EndpointsEndpoints are essentially groupings of functionality. For example, a typical Zigbee light bulb will have a single endpoint for the light. A multi-gang wall switch may have an endpoint for each individual switch, so they can all be controlled separately. Each endpoint has several functions represented by clusters.ClustersClusters are objects that contain the information (attributes and commands) for individual functions. There is the ability to turn the switch on and off, maybe there is energy monitoring, maybe there is the ability to add each switch to an individual group or a scene, etc. Each of these functions belong to a cluster.DescriptorsFor the purposes of Zigpy and Quirks we will focus on two descriptors:Node DescriptorandSimple Descriptor.Node DescriptorA node descriptor explains some basic device attributes to Zigpy. The manufacturer code and the power type are the ones that we generally care about. In most cases you won't have to worry about this, but it is good to know why it is there in case you come across it while looking at an existing quirk. Here is an example:<Optional byte1=2 byte2=64 mac_capability_flags=128 manufacturer_code=4174 maximum_buffer_size=82 maximum_incoming_transfer_size=82 server_mask=0 maximum_outgoing_transfer_size=82 descriptor_capability_field=0>Simple DescriptorA simple descriptor is a description of a Zigbee device endpoint and is responsible for explaining the endpoint's functionality. It contains a profile id, the device type, and collections of clusters. The profile id tells the application what set of Zigbee rules to use. The most common profile will be 260 (0x0104) for the Home Automation profile. The device type tells the application what logical type of device this is ex: on off light, color light, etc. The clusters explain to the application what types of functionality exist on the endpoint. Here is an example:<SimpleDescriptor endpoint=1 profile=260 device_type=1026 device_version=0 input_clusters=[0, 1, 3, 32, 1026, 1280, 2821] output_clusters=[25]>What the heck is a quirkIn human terms you can think of a quirk like Google Translate. I know it's a weird comparison but let's dig in a bit. You may only speak one language but there is an interesting article written in another language that you really want to read. Google Translate takes the original article and displays it in a format (language) that you understand. A quirk is a file that translates device functionality from the format that the manufacturer chose to implement it in to a format that Zigpy and in turn ZHA understand. The main purpose of a quirk is to serve as a translator. A quirk comprises several parts:Signature - To identify and apply the correct quirkReplacement - To allow Zigpy and ZHA to correctly work with the devicedevice_automation_triggers - To let the Home Assistant Device Automation engine and users interact with the deviceSignatureThe signature on a quirk identifies the device as the manufacturer implemented it. You can think of it as a fingerprint or the dna of the device. The signature is what we use to identify the device. If any part of the signature doesn't match what the device returns during discovery the quirk will not match and as a result it will not be applied. The signature is made up of several parts:models_infoendpointsModels info tells the application which devices should use this particular quirk. Endpoints are the simple descriptors that we spoke about earlier exactly as they are on the device.endpointsis a dict where the key is the id of the endpoint and the value is an object with the following properties:profile_id,device_type,input_clustersandoutput_clusters. Creating the signature element is generally just a job of transcribing what the device gives us. Here is an example:signature={MODELS_INFO:[(LUMI,"lumi.plug.maus01")],ENDPOINTS:{# <SimpleDescriptor endpoint=1 profile=260 device_type=81# device_version=1# input_clusters=[0, 4, 3, 6, 16, 5, 10, 1, 2, 2820]# output_clusters=[25, 10]>1:{PROFILE_ID:zha.PROFILE_ID,DEVICE_TYPE:zha.DeviceType.SMART_PLUG,INPUT_CLUSTERS:[Basic.cluster_id,PowerConfiguration.cluster_id,DeviceTemperature.cluster_id,Groups.cluster_id,Identify.cluster_id,OnOff.cluster_id,Scenes.cluster_id,BinaryOutput.cluster_id,Time.cluster_id,ElectricalMeasurement.cluster_id,],OUTPUT_CLUSTERS:[Ota.cluster_id,Time.cluster_id],},},}ReplacementThe replacement on a quirk is what we want the device to be. Remember, we said that quirks were like Google Translate... You can think of the replacement like the output from Google Translate. The replacement dict is what will actually be used by Zigpy and ZHA to interact with the device. The structure ofreplacementis the same as signature with 2 key differences:models_infois generally omitted and there is an extra elementskip_configurationthat instructs the application to skip configuration if necessary. Some manufacturers have not implemented the specifications correctly and the devices come pre-configured and therefore the configuration calls fail (non Zigbee 3.0 Xiaomi devices for instance). Usually, you should not addskip_configuration.Here is an example:replacement={SKIP_CONFIGURATION:True,ENDPOINTS:{1:{PROFILE_ID:zha.PROFILE_ID,DEVICE_TYPE:zha.DeviceType.SMART_PLUG,INPUT_CLUSTERS:[BasicCluster,PowerConfiguration.cluster_id,DeviceTemperature.cluster_id,Groups.cluster_id,Identify.cluster_id,OnOff.cluster_id,Scenes.cluster_id,BinaryOutput.cluster_id,Time.cluster_id,ElectricalMeasurementCluster,],OUTPUT_CLUSTERS:[Ota.cluster_id,Time.cluster_id],},},}device_automation_triggersDevice automation triggers are essentially representations of the events that the devices fire in HA. They allow users to use actions in the UI instead of using the raw events.Building a quirkNow that we got that out of the way we can focus on the task at hand: make our devices work the way they should with Zigpy and ZHA. Because the device doesn't work correctly out of the box we have to write a quirk for it. First lets look at what the quirk looks like when complete:classPlug(XiaomiCustomDevice):"""lumi.plug.maus01 plug."""def__init__(self,*args,**kwargs):"""Init."""self.voltage_bus=Bus()self.consumption_bus=Bus()self.power_bus=Bus()super().__init__(*args,**kwargs)signature={MODELS_INFO:[(LUMI,"lumi.plug.maus01")],ENDPOINTS:{# <SimpleDescriptor endpoint=1 profile=260 device_type=81# device_version=1# input_clusters=[0, 4, 3, 6, 16, 5, 10, 1, 2, 2820]# output_clusters=[25, 10]>1:{PROFILE_ID:zha.PROFILE_ID,DEVICE_TYPE:zha.DeviceType.SMART_PLUG,INPUT_CLUSTERS:[Basic.cluster_id,PowerConfiguration.cluster_id,DeviceTemperature.cluster_id,Groups.cluster_id,Identify.cluster_id,OnOff.cluster_id,Scenes.cluster_id,BinaryOutput.cluster_id,Time.cluster_id,ElectricalMeasurement.cluster_id,],OUTPUT_CLUSTERS:[Ota.cluster_id,Time.cluster_id],},# <SimpleDescriptor endpoint=2 profile=260 device_type=9# device_version=1# input_clusters=[12]# output_clusters=[12, 4]>2:{PROFILE_ID:zha.PROFILE_ID,DEVICE_TYPE:zha.DeviceType.MAIN_POWER_OUTLET,INPUT_CLUSTERS:[AnalogInput.cluster_id],OUTPUT_CLUSTERS:[AnalogInput.cluster_id,Groups.cluster_id],},# <SimpleDescriptor endpoint=3 profile=260 device_type=83# device_version=1# input_clusters=[12]# output_clusters=[12]>3:{PROFILE_ID:zha.PROFILE_ID,DEVICE_TYPE:zha.DeviceType.METER_INTERFACE,INPUT_CLUSTERS:[AnalogInput.cluster_id],OUTPUT_CLUSTERS:[AnalogInput.cluster_id],},# <SimpleDescriptor endpoint=100 profile=260 device_type=263# device_version=2# input_clusters=[15]# output_clusters=[15, 4]>100:{PROFILE_ID:zha.PROFILE_ID,DEVICE_TYPE:zha.DeviceType.OCCUPANCY_SENSOR,INPUT_CLUSTERS:[BinaryInput.cluster_id],OUTPUT_CLUSTERS:[BinaryInput.cluster_id,Groups.cluster_id],},},}replacement={SKIP_CONFIGURATION:True,ENDPOINTS:{1:{PROFILE_ID:zha.PROFILE_ID,DEVICE_TYPE:zha.DeviceType.SMART_PLUG,INPUT_CLUSTERS:[BasicCluster,PowerConfiguration.cluster_id,DeviceTemperature.cluster_id,Groups.cluster_id,Identify.cluster_id,OnOff.cluster_id,Scenes.cluster_id,BinaryOutput.cluster_id,Time.cluster_id,ElectricalMeasurementCluster,],OUTPUT_CLUSTERS:[Ota.cluster_id,Time.cluster_id],},2:{PROFILE_ID:zha.PROFILE_ID,DEVICE_TYPE:zha.DeviceType.MAIN_POWER_OUTLET,INPUT_CLUSTERS:[AnalogInputCluster],OUTPUT_CLUSTERS:[AnalogInput.cluster_id,Groups.cluster_id],},3:{PROFILE_ID:zha.PROFILE_ID,DEVICE_TYPE:zha.DeviceType.METER_INTERFACE,INPUT_CLUSTERS:[AnalogInput.cluster_id],OUTPUT_CLUSTERS:[AnalogInput.cluster_id],},100:{PROFILE_ID:zha.PROFILE_ID,DEVICE_TYPE:zha.DeviceType.OCCUPANCY_SENSOR,INPUT_CLUSTERS:[BinaryInput.cluster_id],OUTPUT_CLUSTERS:[BinaryInput.cluster_id,Groups.cluster_id],},},}This quirk is for the US version of the Xiaomi plug. Xiaomi is notorious for not following the Zigbee specifications and most of their non Zigbee 3.0 devices need a quirk to function correctly. In this case we are correcting theElectricalMeasurementcluster readings. Xiaomi decided to report the values for this cluster on theAnalogInputcluster instead. To fix this we will create a custom cluster to replace theAnalogInputandElectricalMeasurementclusters. We will take the values that are reported on theAnalogInputcluster and publish them to theElectricalMeasurementcluster. Doing this allows the device to work as if Xiaomi had implemented this in the first place. This is the act of translating that was mentioned in the Google Translate analogy above.First things first. All device definitions in quirks must extendCustomDeviceor a derivative of it and all clusters that you define must extendCustomClusteror a derivative of it. If you want to send messages betweenCustomClusterdefinitions as we do here you need to create channels for the communication to flow through. We do this by adding instances ofBuson ourCustomDeviceimplementation.Busis a utility class used specifically for this purpose and adding it to the device implementation ensures that all clusters that you define will have access to theBusso that they can communicate with each other.classPlug(XiaomiCustomDevice):"""lumi.plug.maus01 plug."""def__init__(self,*args,**kwargs):"""Init."""self.voltage_bus=Bus()self.consumption_bus=Bus()self.power_bus=Bus()super().__init__(*args,**kwargs)You can see that we have extendedXiaomiCustomDevicewhich is a derivative ofCustomDeviceshared by Xiaomi devices. You can also see that we have added some instances ofBusso that we can pass messages betweenCustomClusterdefinitions. To be clear, this is not always necessary. Quirks can be used to change formats of data on an existing cluster, to add manufacturer specific attributes or commands to clusters etc. In these instances you just need to create a derivative ofCustomClusterand add your logic. This is more of an advanced example to illustrate what is possible.Here are the custom cluster definitions:classAnalogInputCluster(CustomCluster,AnalogInput):"""Analog input cluster, only used to relay power consumption information to ElectricalMeasurementCluster."""cluster_id=AnalogInput.cluster_iddef__init__(self,*args,**kwargs):"""Init."""self._current_state={}super().__init__(*args,**kwargs)def_update_attribute(self,attrid,value):super()._update_attribute(attrid,value)ifvalueisnotNoneandvalue>=0:self.endpoint.device.power_bus.listener_event(POWER_REPORTED,value)classElectricalMeasurementCluster(LocalDataCluster,ElectricalMeasurement):"""Electrical measurement cluster to receive reports that are sent to the basic cluster."""cluster_id=ElectricalMeasurement.cluster_idPOWER_ID=0x050BVOLTAGE_ID=0x0500CONSUMPTION_ID=0x0304def__init__(self,*args,**kwargs):"""Init."""super().__init__(*args,**kwargs)self.endpoint.device.voltage_bus.add_listener(self)self.endpoint.device.consumption_bus.add_listener(self)self.endpoint.device.power_bus.add_listener(self)defpower_reported(self,value):"""Power reported."""self._update_attribute(self.POWER_ID,value)defvoltage_reported(self,value):"""Voltage reported."""self._update_attribute(self.VOLTAGE_ID,value)defconsumption_reported(self,value):"""Consumption reported."""self._update_attribute(self.CONSUMPTION_ID,value)In theAnalogInputcluster we override the_update_attributemethod so that we can access the data that the cluster receives when the device sends a report and we send the data via an event on a bus to theElectricalMeasurementcluster. This is the line that does the heavy lifting:self.endpoint.device.power_bus.listener_event(POWER_REPORTED, value)Then in theElectricalMeasurementcluster we need to subscribe to these events and handle them. This is how we subscribe to our custom events:self.endpoint.device.power_bus.add_listener(self)and this method (the method name must match the event name that you publish EXACTLY):defpower_reported(self,value):"""Power reported."""self._update_attribute(self.POWER_ID,value)receives the event and handles updating the attribute on the correct zigbee cluster. As you can see there really isn't much here that needs to be done to accomplish our goal.Once we have created ourCustomClusterimplementations we have to tell theCustomDeviceimplementation to use them. We do this in thereplacementdict in the quirk definition. Start by copying thesignaturedict and remove themodels_infofrom it. Then we replace the cluster ids that we want to override with the names of ourCustomClusterimplementations that we have created. The result looks like this:replacement={SKIP_CONFIGURATION:True,ENDPOINTS:{1:{PROFILE_ID:zha.PROFILE_ID,DEVICE_TYPE:zha.DeviceType.SMART_PLUG,INPUT_CLUSTERS:[BasicCluster,PowerConfiguration.cluster_id,DeviceTemperature.cluster_id,Groups.cluster_id,Identify.cluster_id,OnOff.cluster_id,Scenes.cluster_id,BinaryOutput.cluster_id,Time.cluster_id,ElectricalMeasurementCluster,],OUTPUT_CLUSTERS:[Ota.cluster_id,Time.cluster_id],},2:{PROFILE_ID:zha.PROFILE_ID,DEVICE_TYPE:zha.DeviceType.MAIN_POWER_OUTLET,INPUT_CLUSTERS:[AnalogInputCluster],OUTPUT_CLUSTERS:[AnalogInput.cluster_id,Groups.cluster_id],},3:{PROFILE_ID:zha.PROFILE_ID,DEVICE_TYPE:zha.DeviceType.METER_INTERFACE,INPUT_CLUSTERS:[AnalogInput.cluster_id],OUTPUT_CLUSTERS:[AnalogInput.cluster_id],},100:{PROFILE_ID:zha.PROFILE_ID,DEVICE_TYPE:zha.DeviceType.OCCUPANCY_SENSOR,INPUT_CLUSTERS:[BinaryInput.cluster_id],OUTPUT_CLUSTERS:[BinaryInput.cluster_id,Groups.cluster_id],},You can see that we have replacedElectricalMeasurement.cluster_idfrom endpoint 1 in thesignaturedict with the name of our cluster that we created:ElectricalMeasurementClusterand on endpoint 2 we replacedAnalogInput.cluster_idwith the implementation we created for that:AnalogInputCluster. This instructs Zigpy to use theseCustomClusterderivatives instead of the normal cluster definitions for these clusters and this is why this part of the quirk is calledreplacement.Now lets put this all together. If you examine the device definition above you will see that we have defined our custom device, we defined thesignaturedict where we transcribed theSimpleDescriptoroutput we obtained when the device joined the network and we defined thereplacementdict where we swapped the cluster ids for the culsters that we wanted to replace with theCustomClusterimplementations that we created.Contribution GuidelinesAll code is formatted with black. The check format script that runs in CI will ensure that code meets this requirement and that it is correctly formatted with black. Instructions for installing black in many editors can be found here:https://github.com/psf/black#editor-integrationCapture the SimpleDescriptor log entries for each endpoint on the device. These can be found in the HA logs after joining a device and they look like this:<SimpleDescriptor endpoint=1 profile=260 device_type=1026 device_version=0 input_clusters=[0, 1, 3, 32, 1026, 1280, 2821] output_clusters=[25]>. This information can also be obtained from the zigbee.db if you want to take the time to query the tables and reconstitute the log entry. I find it easier to just remove and rejoin the device. ZHA entity ids are stable for the most part so itshouldn'tdisrupt anything you have configured. These need to match what the device reports EXACTLY or zigpy will not match them when a device joins and the handler will not be used for the device. You can also obtain this information from the device screen in HA for the device. TheZigbee Device Signaturebutton will launch a dialog that contains all of the information necessary to create quirks.All custom device definitions must extendCustomDeviceor a derivative of itAll custom cluster definitions must extendCustomClusteror a derivative of itUse constants for all attribute values referencing the appropriate labels from Zigpy / HA as necessaryUse an existing handler as a guide. signature and replacement dicts are required. Include the SimpleDescriptor entry for each endpoint in the signature dict above the definition of the endpoint in this format:#  <SimpleDescriptor endpoint=1 profile=260 device_type=1026#  device_version=0#  input_clusters=[0, 1, 3, 32, 1026, 1280, 2821]#  output_clusters=[25]>Howdevice_automation_triggersworkDevice automation triggers are essentially representations of the events that the devices fire in HA. They allow users to use actions in the UI instead of using the raw events. Ex: For the Hue remote - the on button fires this event:<Event zha_event[L]: unique_id=00:17:88:01:04:e7:f9:37:1:0x0006, device_ieee=00:17:88:01:04:e7:f9:37, endpoint_id=1, cluster_id=6, command=on, args=[]>and the action defined for this is:(SHORT_PRESS, TURN_ON): {COMMAND: COMMAND_ON}The first part(SHORT_PRESS, TURN_ON)corresponds to the txt the user will see in the UI:The second part is the event data. You only need to supply enough of the event data to uniquely match the event which in this case is just the command for this event fired by this device:{COMMAND: COMMAND_ON}If you look at another example for the same device:(SHORT_PRESS, DIM_UP): {COMMAND: COMMAND_STEP, CLUSTER_ID: 8, ENDPOINT_ID: 1, PARAMS: {'step_mode': 0},}You can see a pattern that illustrates how to match a more complex event. In this case the step command is used for the dim up and dim down buttons so we need to match more of the event data to uniquely match the event.Setting up the development environmentOpen a terminal at the root of the project and run the setup script:script/setupThis script will install all necessary dependencies and it will install the precommit hook.The tests use thepytestframework.Getting startedTo get set up, you need install the test dependencies:pipinstall-rrequirements_test_all.txtRunning the testsSee thepytest documentationfor details about how to run
the tests. For example, to run all thetest_tuya.pytests:$pytest--disable-warningstests/test_tuya.py
Testsessionstarts(platform:linux,Python3.9.2,pytest6.2.5,pytest-sugar0.9.4)collecting...tests/test_tuya.py✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓100%██████████

Results(3.58s):41passedWriting testsTo add a new test, start by adding a new function to one of the existing test files. You
can follow the instructions in theGetting startedsection of the pytest documentation.Using fixtures to set things upIn order to write a test, you will need to access an instance of a quirk to run the tests against. Pytest
provides a useful feature called Fixtures that allow you to write and use the setup code necessary in one
place, similar to how we use libraries to provide common functions to other code.You can read more about fixtureshere.You can find the common fixtures in files namedconftest.py. Pytest will list them for you as follows:$pytest--fixtures[...]---fixturesdefinedfromtests.conftest---
MockAppControllerAppcontrollermock.

ieee_mockReturnastaticieee.

zigpy_device_mockZigpydevicemock.

zigpy_device_from_quirkCreatezigpydevicefromQuirkssignature.[...]---fixturesdefinedfromtests.test_tuya_clusters---
TuyaClusterMockofthenewTuyamanufacturercluster.Some fixtures such asapp_controller_mockwill provide an object instance that you can
use directly. Others, such aszigpy_device_mockwill return a function, which you can
call to create a customised object during your own setup.Testing the quirk signature matchingThe fixtureassert_signature_matches_quirkprovides a function that can be
used to check that a particular device signature matches the corresponding quirk.
By capturing the signature and adding a few lines to the test file, this means that
you can verify that your device will be matched against the quirk without needing to
go through the paring process directly.You need to capture the device signature and save it. If you have previously started the
pairing process in Home assistant, you can find the signature under 'Zigbee Device Signature'
on the device page.Now you can create a test that checks the signature as follows:deftest_ts0121_signature(assert_signature_matches_quirk):signature={"node_descriptor":"NodeDescriptor(logical_type=<LogicalType.Router: 1>, complex_descriptor_available=0, user_descriptor_available=0, reserved=0, aps_flags=0, frequency_band=<FrequencyBand.Freq2400MHz: 8>, mac_capability_flags=<MACCapabilityFlags.AllocateAddress|RxOnWhenIdle|MainsPowered|FullFunctionDevice: 142>, manufacturer_code=4098, maximum_buffer_size=82, maximum_incoming_transfer_size=82, server_mask=11264, maximum_outgoing_transfer_size=82, descriptor_capability_field=<DescriptorCapability.NONE: 0>, *allocate_address=True, *is_alternate_pan_coordinator=False, *is_coordinator=False, *is_end_device=False, *is_full_function_device=True, *is_mains_powered=True, *is_receiver_on_when_idle=True, *is_router=True, *is_security_capable=False)","endpoints":{"1":{"profile_id":260,"device_type":"0x0051","in_clusters":["0x0000","0x0004","0x0005","0x0006","0x0702","0x0b04"],"out_clusters":["0x000a","0x0019"]}},"manufacturer":"_TZ3000_g5xawfcq","model":"TS0121","class":"zhaquirks.tuya.ts0121_plug.Plug"}assert_signature_matches_quirk(zhaquirks.tuya.ts0121_plug.Plug,signature)ThanksSpecial thanks to damarco for the majority of the device tracker codeSpecial thanks to Yoda-x for the Xioami attribute parsing codeSpecial thanks to damarco and Adminiuga for allowing me to bounce ideas off of them and for listening to me rambleRelated projectsZigpyzigpyis aZigbee protocol stackintegration project to implement theZigbee Home Automationstandard as a Python 3 library. Zigbee Home Automation integration with zigpy allows you to connect one of many off-the-shelf Zigbee adapters using one of the available Zigbee radio library modules compatible with zigpy to control Zigbee based devices. There is currently support for controlling Zigbee device types such as binary sensors (e.g., motion and door sensors), sensors (e.g., temperature sensors), light bulbs, switches, locks, fans, covers (blinds, marquees, and more). A working implementation of zigpy exists inHome Assistant(Python based open source home automation software) as part of itsZHA componentZHA Network Cardzha-network-cardis a custom Lovelace card that displays ZHA network and device information in Home Assistant