ZHA Device Handlers

repository·dev·Indexed 22 days ago

https://github.com/zigpy/zha-device-handlers

A library implementing Zigpy quirks for ZHA in Home Assistant. It provides custom device handlers to support non-standard Zigbee devices that deviate from official Zigbee Cluster Library (ZCL) specifications by defining signatures, replacements, and device automation triggers.

Tokens
8.5K
Snippets
20
Records
27
Agent score
76%

What's inside zha-device-handlers

  1. How to communicate between CustomClusters using Bus

    dev

    When a quirk requires translating data from one cluster to another, use the Bus utility class.

    1. Initialize Buses: In the __init__ method of your CustomDevice, create instances of Bus (e.g., self.power_bus = Bus()).
    2. Publish Events: In your source CustomCluster, override _update_attribute to catch incoming data and publish it to the bus using self.endpoint.device.bus_name.listener_event(EVENT_NAME, value).
    3. Subscribe to Events: In your target CustomCluster, use self.endpoint.device.bus_name.add_listener(self) in the __init__ method.
    4. Handle Events: Implement a method in the target cluster where the method name matches the EVENT_NAME used in listener_event exactly. This method will receive the value and can then call self._update_attribute to update the local cluster state.
    # 1. In the Device
    class MyDevice(CustomDevice):
        def __init__(self, *args, **kwargs):
            self.my_bus = Bus()
            super().__init__(*args, **kwargs)
    
    # 2. In the Source Cluster
    class SourceCluster(CustomCluster, StandardCluster):
        def _update_attribute(self, attrid, value):
            super()._update_attribute(attrid, value)
            if value is not None:
                # Publish the event
                self.endpoint.device.my_bus.listener_event(MY_EVENT, value)
    
    # 3. In the Target Cluster
    class TargetCluster(CustomCluster, StandardCluster):
        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)
            # Subscribe to the bus
            self.endpoint.device.my_bus.add_listener(self)
    
        def my_event(self, value):
            # This method name must match MY_EVENT exactly
            self._update_attribute(SOME_ATTRIBUTE_ID, value)
  2. What a quirk is and how it works

    dev

    A quirk acts as a translator between a device manufacturer's specific implementation and the standard format expected by Zigpy and ZHA. It ensures that device functionality is correctly interpreted and exposed to Home Assistant.

    A quirk consists of three main components:

    1. Signature: Identifies the device based on its hardware characteristics (the 'fingerprint').
    2. Replacement: Defines how the device should be represented to Zigpy and ZHA (the 'translated' version).
    3. device_automation_triggers: Represents device events as usable actions within the Home Assistant UI, allowing users to interact with the device without handling raw events.
  3. Define a quirk signature to identify devices

    dev

    The signature is used to match a quirk to a physical device during discovery. It must exactly match the data returned by the device. If any part of the signature fails to match, the quirk will not be applied.

    The signature contains:

    • MODELS_INFO: A list of tuples identifying which models use this quirk.
    • ENDPOINTS: A dictionary where keys are endpoint IDs and values describe the endpoint's properties, including PROFILE_ID, DEVICE_TYPE, INPUT_CLUSTERS, and OUTPUT_CLUSTERS.
    signature = {
        MODELS_INFO: [(LUMI, "lumi.plug.maus01")],
        ENDPOINTS: {
            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],
            },
        },
    }
  4. What are ZHA Device Handlers and Quirks?

    dev

    ZHA Device Handlers (also known as custom quirks) are implementations for the zigpy library used by the Home Assistant ZHA component. They act as a virtual representation of a physical Zigbee device to bridge functionality gaps when manufacturers deviate from the official Zigbee Cluster Library (ZCL) specifications.

    By using a quirk, you can handle custom messages, non-standard attribute reporting, or manufacturer-specific deviations, ensuring that all device functions work correctly within Home Assistant even if the device is not fully specification-compliant.

  5. Define a quirk replacement to map device functionality

    dev

    The replacement dictionary defines the standardized representation of the device that Zigpy and ZHA will use for interaction. It follows a similar structure to the signature but with key differences:

    • MODELS_INFO is typically omitted.
    • It uses actual Cluster classes (e.g., BasicCluster) instead of just IDs where applicable.
    • It can include SKIP_CONFIGURATION: True to prevent configuration calls from failing on devices that are already pre-configured (common in some non-Zigbee 3.0 Xiaomi devices). Note: You should generally avoid adding SKIP_CONFIGURATION unless necessary.
    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,
                    ElectricalMeasurementCluster,
                ],
                OUTPUT_CLUSTERS: [Ota.cluster_id, Time.cluster_id],
            },
        },
    }
  6. Understand device_automation_triggers

    dev

    Device automation triggers represent Zigbee events as actionable items in the Home Assistant UI. They map raw Zigbee events to user-friendly actions.

    • UI Representation: The first part of the definition (e.g., (SHORT_PRESS, TURN_ON)) is the text displayed to the user.
    • Event Matching: The second part is the event data required to uniquely identify the event. For simple events, this might just be a command. For complex events (like dimming), you must include additional fields like CLUSTER_ID, ENDPOINT_ID, or PARAMS to ensure the trigger matches the correct action.
  7. Understand Zigbee device abstractions: Endpoints, Clusters, and Descriptors

    dev

    To build or understand ZHA device handlers, you must use the following mental model of a Zigbee device:

    Endpoints

    Endpoints are groupings of functionality. A single physical device might have multiple endpoints (e.g., a multi-gang switch where each gang is a separate endpoint). Each endpoint contains one or more clusters.

    Clusters

    Clusters are objects containing specific functions, represented by attributes and commands. There are two types:

    • in_clusters (Server clusters): These control the device (e.g., an on_off server cluster on a light bulb). They are responsible for sending attribute reports and allowing attributes to be read.
    • out_clusters (Client clusters): These control other devices (e.g., an on_off client cluster on a remote control). They generate and send commands to server clusters.

    Descriptors

    Descriptors provide metadata about the device or its endpoints:

    • Node Descriptor: Provides basic device attributes like manufacturer_code and power type.
    • Simple Descriptor: Explains an endpoint's functionality. It includes the profile_id (e.g., 260 for Home Automation), the device_type (e.g., 1026 for a specific light type), and the collections of input_clusters and output_clusters available on that endpoint.
  8. Use Analog Input and Supply Voltage sensors

    dev

    Analog Input

    Analog input pins are exposed as sensors showing voltage as a percentage (0-100) relative to the analog reference voltage.

    • Reference Voltage: 1.2V for standard XBee. For XBee3, use the AV command to select between 1.25V, 2.5V, or VDD.
    • Requirement: You must configure periodic sampling (IR) and configure the specific pins as analog inputs.

    Supply Voltage

    The supply voltage is exposed as a sensor measured in volts.

    • Requirement: Enable it using the V+ command and configure periodic sampling (IR).
  9. Create custom device and cluster definitions

    dev

    When adding new device support (quirks), follow these requirements:

    1. Inheritance: All custom device definitions must extend CustomDevice (or a derivative), and all custom cluster definitions must extend CustomCluster (or a derivative).
    2. Signatures: You must provide a signature and replacement dict.
    3. SimpleDescriptor: For each endpoint, include the SimpleDescriptor log entry in the signature dict. This information is critical; it must match exactly what the device reports for zigpy to successfully match the handler to the device.
    4. Formatting: Use constants for all attribute values referencing appropriate Zigpy/HA labels. Use ruff for code formatting.

    To obtain the required SimpleDescriptor data, you can:

    • Check the Home Assistant logs after a device joins.
    • Query the zigbee.db.
    • Use the Zigbee Device Signature button in the Home Assistant device UI.
    # Example signature format for an endpoint
    # <SimpleDescriptor endpoint=1 profile=260 device_type=1026
    # device_version=0
    # input_clusters=[0, 1, 3, 32, 1026, 1280, 2821]
    # output_clusters=[25]>
  10. Send and receive Remote AT Commands

    dev

    You can issue remote AT commands using the zha.issue_zigbee_cluster_command service.

    • Success: The response is returned as a zha_event.
    • Failure: An exception is logged.

    To find the mapping between AT commands and Command_IDs, go to the Device info screen in HA, click Manage clusters, and select the XBeeRemoteATRequest cluster. The mapping is available in the Cluster Commands dropdown.

    Example: Reading XBee Pro Temperature via TP command

    1. Create a template sensor to capture the tp_command_response event:
    template:
      - trigger:
        - platform: event
          event_type: zha_event
          event_data:
            device_ieee: 00:13:a2:00:41:98:23:f9
            command: tp_command_response
        sensor:
          - name: "XBee Temperature"
            state: '{{ trigger.event.data.args.response }}'
            unit_of_measurement: "°C"
            device_class: temperature
            state_class: measurement
    1. Create an automation to trigger the command every 5 minutes:
    automation:
      - alias: Update XBee Temperature
        trigger:
          platform: time_pattern
          minutes: "/5"
        action:
          service: zha.issue_zigbee_cluster_command
          data:
            ieee: 00:13:a2:00:41:98:23:f9
            endpoint_id: 230
            command: 0x43
            command_type: server
            cluster_id: 33
            params: {}
    template:
      - trigger:
        - platform: event
          event_type: zha_event
          event_data:
            device_ieee: 00:13:a2:00:41:98:23:f9
            command: tp_command_response
        sensor:
          - name: "XBee Temperature"
            state: '{{ trigger.event.data.args.response }}'
            unit_of_measurement: "°C"
            device_class: temperature
            state_class: measurement
    
    automation:
      - alias: Update XBee Temperature
        trigger:
          platform: time_pattern
          minutes: "/5"
        action:
          service: zha.issue_zigbee_cluster_command
          data:
            ieee: 00:13:a2:00:41:98:23:f9
            endpoint_id: 230
            command: 0x43
            command_type: server
            cluster_id: 33
            params: {}
  11. How to build a quirk

    dev

    A quirk is used to make Zigbee devices work correctly with Zigpy and ZHA when they do not follow standard Zigbee specifications. To build a quirk, you must define a device class that extends CustomDevice (or a derivative) and define custom clusters that extend CustomCluster (or a derivative).

    A quirk typically consists of three main parts:

    1. A Device Class: Extends CustomDevice and initializes any necessary communication channels using Bus.
    2. A signature dictionary: A transcription of the device's SimpleDescriptor (obtained when the device joins the network) which identifies the device by its model and endpoints.
    3. A replacement dictionary: Defines how Zigpy should actually handle the device by swapping standard cluster IDs with your CustomCluster implementations.

    If you need to pass data between different clusters (e.g., translating data from an AnalogInput cluster to an ElectricalMeasurement cluster), you should use Bus instances attached to the device to facilitate communication via events.

    class Plug(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: {
                1: {
                    PROFILE_ID: zha.PROFILE_ID,
                    DEVICE_TYPE: zha.DeviceType.SMART_PLUG,
                    INPUT_CLUSTERS: [...],
                    OUTPUT_CLUSTERS: [...],
                },
            },
        }
    
        replacement = {
            SKIP_CONFIGURATION: True,
            ENDPOINTS: {
                1: {
                    PROFILE_ID: zha.PROFILE_ID,
                    DEVICE_TYPE: zha.DeviceType.SMART_PLUG,
                    INPUT_CLUSTERS: [
                        # Use your CustomCluster class name here instead of a cluster_id
                        ElectricalMeasurementCluster,
                    ],
                    OUTPUT_CLUSTERS: [...],
                },
            },
        }
  12. Configure zigpy for non-XBee coordinators

    dev

    When using a non-XBee coordinator, you may need to explicitly configure zigpy to listen to specific additional endpoints that are ignored by default.

    For Home Assistant ZHA users, add the additional_endpoints configuration under zigpy_config. If you are using zigpy_znp, you may also need to set prefer_endpoint_1: false within the zigpy_config section.

    zha:
      zigpy_config:
        additional_endpoints:
          - endpoint: 0xE6
            profile: 0xC105
            device_type: 0x0000
            device_version: 0b0000
            input_clusters: [0xA1]
            output_clusters: [0x21]
          - endpoint: 0xE8
            profile: 0xC105
            device_type: 0x0000
            device_version: 0b0000
            input_clusters: [0x11, 0x92]
            output_clusters: [0x11]
        znp_config:
          prefer_endpoint_1: false