Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Question: CustomCluster (AnalogInput) -> HomeAssistant Entity - HowTo? #3720

Open
ummarbhutta opened this issue Jan 17, 2025 · 9 comments
Open

Comments

@ummarbhutta
Copy link

Dear All,

I am working on creating a zigbee weight sensor for one of my DIY project. Quick background/summary:

Environment

  • HomeAssistant Docker based installation Core v 2024.9.1, Frontend v 20240906.0

Hardware/FW

  • I am using ESP32-H2 as a hardware platform.
  • FW is developed using ESP-IDF
  • Device is set as End device
  • I am publishing weight values on an AnalogInput cluster
  • Also included basic and identify(in/out) cluster

Out of Box - HomeAssistant behavior

  • When I pair the device to HomeAssistant, the device is discovered successfully
  • No entity is created other than identify, LQI, RSSI
  • Sensor is transmitting correct values, verified it from Manage Zigbee device menu and reading the present_value (0x0055) attribute of AnalogInput (0x000c) cluster
  • This is an expected behavior, because I need a custom quirk for HomeAssistant to identify the device.

Quirk development

  • I started working on custom quirk development, developed a version and installed it on HomeAssistant, it is loaded and applied to custom device successfully - verified from DeviceInfo panel of Home Assistant
  • I also see the cluster I have defined in replacement dictionary of quirk in Manage Zigbee device menu, with the correct values in present_value attribute.

Issue

  • The issue I am facing is that No entity is created in Home Assistant for my weight sensor
  • I am definitely missing something in quirk, couldn't figure out anything from documentation. Any help is appreciated.

Code of Quirk

import logging
from zigpy.profiles import zha
from zigpy.quirks import CustomDevice
from zigpy.zcl.clusters.general import AnalogInput, Basic, Identify
from zigpy.zcl import foundation
from zhaquirks import CustomCluster
from zhaquirks.const import (
    DEVICE_TYPE,
    ENDPOINTS,
    INPUT_CLUSTERS,
    OUTPUT_CLUSTERS,
    PROFILE_ID,
)

_LOGGER = logging.getLogger(__name__)

class WeightMeasurementInputCluster(CustomCluster, AnalogInput):
    """Custom Analog Input cluster for weight measurement."""
    
    PRESENT_VALUE_ATTRIBUTE = 0x0055
    CLUSTER_SPECIFIC_COMMANDS = {}
    
    async def bind(self):
        """Bind cluster."""
        _LOGGER.debug("WeightSensor: Binding cluster")
        result = await super().bind()
        _LOGGER.debug("WeightSensor: Configuring reporting")
        try:
            await self.configure_reporting(
                self.PRESENT_VALUE_ATTRIBUTE,
                0,    # minimum reporting interval
                3600, # maximum reporting interval
                0.01, # reportable change
            )
            _LOGGER.debug("WeightSensor: Reporting configured successfully")
        except Exception as e:
            _LOGGER.error("WeightSensor: Error configuring reporting: %s", e)
        return result

    def _update_attribute(self, attrid, value):
        _LOGGER.debug("WeightSensor: Updating attribute %s with value %s", attrid, value)
        super()._update_attribute(attrid, value)
        
    @property
    def extra_info(self):
        """Return unit of measurement and icon."""
        return {
            'unit_of_measurement': 'kg',
            'icon': 'mdi:weight-gram',
            'state_class': 'measurement'
        }

class WeightSensor(CustomDevice):
    """Custom device representing a weight sensor."""

    signature = {
        "models_info": [
            ("HQ", "HQWS200kg")
        ],
        "endpoints": {
            20: {
                "profile_id": zha.PROFILE_ID,
                "device_type": zha.DeviceType.SIMPLE_SENSOR,
                "input_clusters": [
                    Basic.cluster_id,
                    AnalogInput.cluster_id,
                    Identify.cluster_id
                ],
                "output_clusters": [
                    Identify.cluster_id
                ],
            },
        },
    }

    replacement = {
        ENDPOINTS: {
            20: {
                PROFILE_ID: zha.PROFILE_ID,
                DEVICE_TYPE: zha.DeviceType.SIMPLE_SENSOR,
                INPUT_CLUSTERS: [
                    Basic,
                    WeightMeasurementInputCluster,
                    Identify
                ],
                OUTPUT_CLUSTERS: [
                    Identify
                ],
            }
        }
    }

Signature of device

{
  "node_descriptor": {
    "logical_type": 2,
    "complex_descriptor_available": 0,
    "user_descriptor_available": 0,
    "reserved": 0,
    "aps_flags": 0,
    "frequency_band": 8,
    "mac_capability_flags": 140,
    "manufacturer_code": 4660,
    "maximum_buffer_size": 108,
    "maximum_incoming_transfer_size": 1613,
    "server_mask": 11264,
    "maximum_outgoing_transfer_size": 1613,
    "descriptor_capability_field": 0
  },
  "endpoints": {
    "20": {
      "profile_id": "0x0104",
      "device_type": "0x000c",
      "input_clusters": [
        "0x0000",
        "0x0003",
        "0x000c"
      ],
      "output_clusters": [
        "0x0003"
      ]
    }
  },
  "manufacturer": "HQ",
  "model": "HQWS200kg",
  "class": "hq_weight_sensor.WeightSensor"
}

Different UI(s) on home assistant:

Image

Image

Any help is appreciated.

@prairiesnpr
Copy link
Collaborator

We need zigpy/zha#197 merged and then it will work as expected. In the mean time use a v2 quirk and use sensor.

@prairiesnpr
Copy link
Collaborator

I think something like this would get you going.

(
    QuirkBuilder("HQ", "HQWS200kg")
    .sensor(
        "present_value",
        AnalogInput.cluster_id
        unit=UnitOfMass.KILOGRAMS,
        device_class=SensorDeviceClass.WEIGHT,
        state_class=SensorStateClass.MEASUREMENT,
        translation_key="measured_mass",
        fallback_name="Mass",
    )
    .add_to_registry()
)

@ummarbhutta
Copy link
Author

Thanks @prairiesnpr , I tried following quirk

from zigpy.quirks.v2 import QuirkBuilder
from zigpy.quirks.v2.homeassistant.sensor import SensorDeviceClass, SensorStateClass
from zigpy.quirks.v2.homeassistant import UnitOfMass
from zigpy.zcl.clusters.general import AnalogInput

(
    QuirkBuilder("HQ", "HQWS200kg")
    .sensor(
        "present_value",
        AnalogInput.cluster_id,
        #unit=UnitOfMass.KILOGRAMS,
        device_class=SensorDeviceClass.WEIGHT,
        state_class=SensorStateClass.MEASUREMENT,
        #translation_key="measured_mass",
        #fallback_name="Mass",
    )
    .add_to_registry()

but it didn't work, and from UI looks like the quirk is not applied as well.

The reason why I have commented out different parameters is:

  • fallback_name ==> getting an error that unknown named parameter fallback_name
  • unit ==> getting an error that unit can't be used with device_class
  • translation_key ==> getting an error that translation_key can't be used with state_class

Is there any documentation/reference code available to which you can point me to for this?

@prairiesnpr
Copy link
Collaborator

Documentation we are short on. Look at #3019 and also https://github.com/zigpy/zigpy/blob/dev/tests/test_quirks_v2.py

@prairiesnpr
Copy link
Collaborator

prairiesnpr commented Jan 19, 2025

Also, check that you are on the latest HA, here's a current quirk with similar kwargs,

@ummarbhutta
Copy link
Author

ummarbhutta commented Jan 20, 2025

Just checking this, as endpoint_id is set to 1 by default, and in my case as mentioned above endpoint id is 20, could it be a reason?

On the other hand, I am not clear why HomeAssistant is giving error on start-up when parameters fallback_name, unit and translation_key are present, perhaps it has to do something with HA version. However I am not on that older version, i.e. 2024.9.1, still will upgrade to latest version and try.

@ummarbhutta
Copy link
Author

Hi @prairiesnpr - I have a little success. This is what I did:

  • Upgraded HomeAssistant to latest i.e., 2025.1.2
  • Use following quirk v2:
from zigpy.quirks.v2 import (
    QuirkBuilder,
    SensorDeviceClass,
    SensorStateClass,
    ReportingConfig
)
from zigpy.quirks.v2.homeassistant import UnitOfMass
from zigpy.zcl.clusters.general import AnalogInput

(
    QuirkBuilder("HQ", "HQWS200kg")
    .sensor(
        "present_value",
        AnalogInput.cluster_id,
        endpoint_id=20,
        unit=UnitOfMass.KILOGRAMS,
        device_class=SensorDeviceClass.WEIGHT,
        state_class=SensorStateClass.MEASUREMENT,
        translation_key="measured_mass",
        fallback_name="Weight"
    )
    .add_to_registry()
)
  • HomeAssistant started without any issue, and quirk loaded/applied, I also see respected sensor entity on UI:

Image

Image

Next Problem :)

Now the problem I am struggling with is that sensor entity is not updated on UI by itself, It only gets updated when I press the read attribute button on Manage zigbee device wizard as below:

Image

I tried to set the reporting configuration as below, but no luck:

from zigpy.quirks.v2 import (
    QuirkBuilder,
    SensorDeviceClass,
    SensorStateClass,
    ReportingConfig
)
from zigpy.quirks.v2.homeassistant import UnitOfMass
from zigpy.zcl.clusters.general import AnalogInput

(
    QuirkBuilder("HQ", "HQWS200kg")
    .sensor(
        "present_value",
        AnalogInput.cluster_id,
        endpoint_id=20,
        unit=UnitOfMass.KILOGRAMS,
        device_class=SensorDeviceClass.WEIGHT,
        state_class=SensorStateClass.MEASUREMENT,
        translation_key="measured_mass",
        fallback_name="Weight",
        reporting_config=ReportingConfig(0,15,1) ##min_interval,max_interval,reportable_change
    )
    .add_to_registry()
)

FW is reporting weight value after every 15 seconds.

It looks like something is missing in data wiring from ZHA -> HomeAssistant entity, any idea/help?

@prairiesnpr
Copy link
Collaborator

prairiesnpr commented Jan 21, 2025

What are you sending from the device to HA? Post the logs of the incoming update.

You need to be sure you are reporting the attribute correctly from the ESP, if the command is something like a read attribute response, then it will be dropped.

See an example here. https://github.com/prairiesnpr/esp_zha_test_bench/blob/69e01ce8a35bbeecf34bac96aae1e1a87af9bc4b/main/main.c#L51

@ummarbhutta
Copy link
Author

ummarbhutta commented Jan 21, 2025

Following is what I am doing for reporting from ESP:

void report_weight_attr(uint8_t ep)
{
    esp_zb_zcl_report_attr_cmd_t report_attr_cmd = {
        .address_mode = ESP_ZB_APS_ADDR_MODE_DST_ADDR_ENDP_NOT_PRESENT,
        .attributeID = ESP_ZB_ZCL_ATTR_ANALOG_INPUT_PRESENT_VALUE_ID,
        .direction = ESP_ZB_ZCL_CMD_DIRECTION_TO_CLI,
        .clusterID = ESP_ZB_ZCL_CLUSTER_ID_ANALOG_INPUT,
        .zcl_basic_cmd.src_endpoint = ep};

    esp_zb_lock_acquire(portMAX_DELAY);
    esp_err_t status = esp_zb_zcl_report_attr_cmd_req(&report_attr_cmd);
    esp_zb_lock_release();
    if (status == ESP_OK)
    {
        ESP_LOGI(TAG, "Send Weight 'report attributes' command");
    }
    else
    {
        ESP_LOGE(TAG, "Send Weight failed, with error code '%d'", status);
    }
}

And for testing I sent the weight value as humidity measurement (cluster id: 0x0405, attribute id: 0x0000), and for this case a humidity sensor is created on HA (without quirk - of course) and it's values are updated correctly in entity. So it seems like the quirk attribute to entity wiring thing.

Update

I see following in logs when I press Read Attribute button.

2025-01-21 23:38:43.679 DEBUG (MainThread) [zigpy.zcl] [0x0154:20:0x000c] Decoded ZCL frame: AnalogInput:Read_Attributes_rsp(status_records=[ReadAttributeRecord(attrid=85, status=<Status.SUCCESS: 0>, value=TypeValue(type=Single, value=0.0))])
2025-01-21 23:38:43.680 DEBUG (MainThread) [zha.zigbee.cluster_handlers] [0x0154:20:0x000c]: cluster_handler[analog_input] attribute_updated - cluster[AnalogInput] attr[present_value] value[0.0]
2025-01-21 23:38:43.681 DEBUG (MainThread) [zha] Emitting event cluster_handler_attribute_updated with data ClusterAttributeUpdatedEvent(attribute_id=85, attribute_name='present_value', attribute_value=0.0, cluster_handler_unique_id='****', cluster_id=12, event_type='cluster_handler_event', event='cluster_handler_attribute_updated') (1 listeners)
2025-01-21 23:38:43.681 DEBUG (MainThread) [zha] Emitting event state_changed with data EntityStateChangedEvent(event_type='entity', event='state_changed', platform=<Platform.SENSOR: 'sensor'>, unique_id='74:4d:bd:ff:fe:61:f1:e6-20-present_value', device_ieee=*********, endpoint_id=20, group_id=None) (1 listeners)
2025-01-21 23:38:43.681 DEBUG (MainThread) [homeassistant.components.zha.entity] sensor.hq_hqws200kg_weight: Handling event from entity: EntityStateChangedEvent(event_type='entity', event='state_changed', platform=<Platform.SENSOR: 'sensor'>, unique_id='********', device_ieee=********, endpoint_id=20, group_id=None)
2025-01-21 23:38:43.682 DEBUG (MainThread) [homeassistant.components.zha.websocket_api] Read attribute for: cluster_id: [12] cluster_type: [in] endpoint_id: [20] attribute: [85] manufacturer: [None] response: [0.0] failure: [{}],

I don't see any logline when attribute change is reported from FW.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants