Skip to content

Transfer all Sinopé devices to quirks V2 #4042

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

Open
wants to merge 40 commits into
base: dev
Choose a base branch
from

Conversation

claudegel
Copy link
Contributor

Proposed change

Transfer all Sinopé devices to quirks V2 and add support for some new devices.
Add many new attributes in manufacturer cluster.

Additional information

Some manufacturer cluster attributes are already supported in ZHA but attributes values are not the same. ex. attribute 0x0002 for keypad_lockout in manufacturer cluster have three values. Unlocked = 0x00, Locked = 0x01, Partial_lock = 0x02. In ZHA there is 5 values Unlock, lock1, lock2, lock3 and lock4 which have no meaning for Sinopé devices.
Same for on_led_color which have only the color used in Neviweb. We should be able to add mode color.
They should be included in this quirks as they are only available for Sinopé devices.
This PR replace PR #3821.

Checklist

  • The changes are tested and work correctly
  • pre-commit checks pass / the code has been formatted using Black
  • Tests have been added to verify that the new code works

@TheJulianJES TheJulianJES added v2 quirk Quirks using v2 API. Might add custom entities that need translation keys in HA. needs review This PR should be reviewed soon, as it generally looks good. labels May 2, 2025
@claudegel
Copy link
Contributor Author

Look like there is a bug with some quirks:
AssertionError: Manufacturer-model pair 'Sinope Technologies' 'RM3250ZB' is shared by multiple quirks: ['/home/runner/work/zha-device-handlers/zha-device-handlers/zhaquirks/sinope/switch.py:444', '/home/runner/work/zha-device-handlers/zha-device-handlers/zhaquirks/sinope/switch.py:399']
The to devices have the same SKU RM3250ZB but there cluster are different.
Can I just rename one from QuirkBuilder(SINOPE, "RM3250ZB") to QuirkBuilder(SINOPE, "RM3250ZB-V2") to fix this. I have many devices like this and it was ok in the previous quirks.

@TheJulianJES
Copy link
Collaborator

TheJulianJES commented May 3, 2025

Quirks v2 no longer match by the whole ep/cluster signature. They only match by manufacturer and model.

If you really need to distinguish two identically named devices depending on their cluster signature, I believe you should be able to do it something like this:

sinope_device_1_signature = {
    MODELS_INFO: [(SINOPE, "RM3250ZB")],
    ENDPOINTS: {
        1: {
            PROFILE_ID: zha_p.PROFILE_ID,
            DEVICE_TYPE: zha_p.DeviceType.ON_OFF_OUTPUT,
            INPUT_CLUSTERS: [
                Basic.cluster_id,
                DeviceTemperature.cluster_id,
                Identify.cluster_id,
                Groups.cluster_id,
                Scenes.cluster_id,
                OnOff.cluster_id,
                Metering.cluster_id,
                ElectricalMeasurement.cluster_id,
                Diagnostic.cluster_id,
                SINOPE_MANUFACTURER_CLUSTER_ID,
            ],
            OUTPUT_CLUSTERS: [
                Identify.cluster_id,
                Groups.cluster_id,
                Ota.cluster_id,
            ],
        }
    },
}

# and when calling the v2 QuirkBuilder methods, add:
.filter(signature_matches(sinope_device_1_signature))
# ...

That should additionally match by signature, though you ideally don't want to do this.
The tests will likely still fail, as we haven't needed this behavior so far. If we need to go this route, I can adjust the tests to ignore quirks with a specified filter though.

If this is about preventing an entity to be created for one firmware version, there's no good way to do that at the moment.
Currently, v2 quirks entities are force-created, even if the attribute value cannot be read during pairing, for cases when the attribute isn't exposed on the device.
Ideally, we'd add an additional parameter that can be enabled to only create specified entities if the attribute is actually present in zigpy cache, so exposed by the device. This would work for older devices returning `None" and newer ones actually returning the attribute.

We do have v2 quirks that also expose the entity on older firmware versions though, even if it's not supported.
There's no big harm in that, but we'll try to fix that in the future.
Related issue: zigpy/zha#373

@TheJulianJES
Copy link
Collaborator

Can I just rename one from QuirkBuilder(SINOPE, "RM3250ZB") to QuirkBuilder(SINOPE, "RM3250ZB-V2") to fix this. I have many devices like this and it was ok in the previous quirks.

So, to answer that: No, if the model name of the device is "RM3250ZB", then the "RM3250ZB-V2" quirk wouldn't match it. Only the first one with "RM3250ZB" would match it.

@claudegel
Copy link
Contributor Author

If I keep the two as QuirkBuilder(SINOPE, "RM3250ZB") it load correctly and I can see both device in ZHA. But it is shared-ci / Run tests Python 3.12 that fail with AssertionError

@TheJulianJES
Copy link
Collaborator

No, it's the same manufacturer and model. Only one of the quirks will apply. Keep both as RM3250ZB and remove one. Everything will seem work fine. Now, add that quirk back and remove the other RM3250ZB. Everything will still work fine.
It's because only one of the quirks will ever work.
I'd have to see if it's the one added first (i.e. further up in the file) or the one further below, but that doesn't really matter. Only one of them will apply if they have the same manufacturer and model.

v2 quirks only match by manufacturer and model and you're adding two quirks with the same manufacturer and model.
ZHA will only apply one of them, since they're meant for the exact same device.
The test just informs you that you're doing something that you likely don't want to do.

v1 quirks had the signature. v2 quirks only have manufacturer and model to match against by default, no signature.
The # SimpleDescriptor comments are just comments, they don't do anything.

@TheJulianJES
Copy link
Collaborator

The great thing about v2 quirks is that we don't need multiple quirk variants if firmware updates or other variants changed the device signature ever so slightly.
Like, to v2 quirks, it doesn't matter if there's an additional Time cluster on one of the endpoints after a firmware update.

For v1 quirks, you'd have to add another quirk, with a new signature and have two quirks in the end if there are different variants of devices (e.g. with different firmware).
With v2 quirks, you only have one quirk, as it only matches the device by the manufacturer and model name. The rest of the signature (endpoints and clusters) are completely ignored.

@claudegel
Copy link
Contributor Author

Both device expose there signature for cluster but one device have more cluster than the old one:

    # <SimpleDescriptor(endpoint=1, profile=260,
    # device_type=2, device_version=0,
    # input_clusters=[0, 3, 4, 5, 6, 1794, 2820, 2821, 65281]
    # output_clusters=[3, 4, 25]>
    QuirkBuilder(SINOPE, "RM3250ZB")
    # <SimpleDescriptor(endpoint=1, profile=260,
    # device_type=2, device_version=0,
    # input_clusters=[0, 2, 3, 4, 5, 6, 1794, 2820, 2821, 65281]
    # output_clusters=[3, 4, 25]>
    QuirkBuilder(SINOPE, "RM3250ZB")

But they share the same QuirkBuilder(SINOPE, "RM3250ZB") and same device_version=0.

Can I put only the device quirks that have all cluster like the second one above. Does ZHA will load the first one above if one cluster is missing, 0x0002

Comment on lines +274 to +280
.adds(Basic, endpoint_id=1)
.adds(Identify, endpoint_id=1)
.adds(Groups, endpoint_id=1)
.adds(Scenes, endpoint_id=1)
.adds(OnOff, endpoint_id=1)
.adds(Metering, endpoint_id=1)
.adds(Diagnostic, endpoint_id=1)
Copy link
Collaborator

@TheJulianJES TheJulianJES May 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You shouldn't be doing this in v2 quirks. Only add clusters if they're missing from the device signature by default.
This won't do anything, since they're most likely all already exposed by the device itself.

Like, all of the .adds(...) should be completely removed here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm doing this only in the light.py because I had to add line
.replaces_endpoint(1, device_type=zha_p.DeviceType.ON_OFF_LIGHT)
and this was removing all cluster that I didn't .replace

    .replaces(CustomDeviceTemperatureCluster)
    .replaces(LightManufacturerCluster)

Adding those lines fix the problem.
In Sinopé light DevicesType are reported wrong so I need to do that .replace to change the DeviceType. This work ok in V2.

@TheJulianJES
Copy link
Collaborator

To re-iterate on the differences vs v1 quirks and v2 quirks:
v1 quirks had a signature and replacement. If the signature matches a device, it'll present the replacement signature to ZHA. So, if you want to replace a cluster, you'd just replace it in the replacement signature, keeping everything the same.

For v2 quirks, it works nothing like that. They match by manufacturer and model only. Now, if I want to replace a cluster only, I just do this:

(
    QuirkBuilder("IKEA", "TRADFRI control outlet")
    .replaces(MyCustomCluster, endpoint_id=1) # the endpoint_id=1 is optional for ep 1, as 1 is the default
    .add_to_registry()
)

All other clusters on the device are left untouched. For example, all devices have to expose a Basic cluster on at least one endpoint, but we don't care about that here, as we don't need to modify it. So we don't do anything with that.
Only the MyCustomCluster is replaced with one that has further attribute definitions, for example.

The example IKEA control outlet above also has an OnOff cluster exposed by default. We don't touch it, because that basic functionality already works fine.


You can kind of consider v2 quirks as the difference of v1 quirks when comparing the signature and replacement.
All clusters that you don't care about are exactly the same in signature and replacement for v1 quirks.
So, for v2 quirks, you don't do anything with those clusters.

But if you want to replace/remove/add a cluster in a v1 quirk, you change that in the replacement.
Considering the difference between the signature and replacement, you'll then want to use v2 quirk equivalents like .replaces(cluster),.remove(cluster_id), or .adds(cluster).

Is that a better explanation?

@claudegel
Copy link
Contributor Author

So I will keep only one copy of QuirkBuilder(SINOPE, "RM3250ZB") and make sure that it contain all .number, .enum, .sensor that are present in the other QuirkBuilder(SINOPE, "RM3250ZB").
I'll do it for all duplicate I have mostly in thermostat.py, switch.py and sensor.py.
What happens for the device that is missing one cluster ? The related .number, .sensor etc will be ignored or just grey out in the device card ?

@claudegel
Copy link
Contributor Author

There is an import error in test_sinope.py. Is it you who fix this ?

Also there is a AssertionError: Translation key 'valve_countdown' is shared by quirks with different fallback names:
Do I need to pay Tuya :)

@claudegel
Copy link
Contributor Author

Also where can I add missing french translation.

@TheJulianJES
Copy link
Collaborator

The Codecov project check doesn't really matter.
I think it just fails, as the overall number of lines was drastically reduced with the v2 quirks, thus also causing the whole project coverage percentage to be lower, failing the check.

All lines of the v2 quirks are covered, so that's good.

@claudegel
Copy link
Contributor Author

Thank you. check it and let me know if we can merge to V2

@claudegel
Copy link
Contributor Author

I've found my last bug for dimmer, DM2500ZB. Now all entities are up and running
Thank you for your help

@claudegel
Copy link
Contributor Author

@TheJulianJES , I've few questions:

  • For number is it possible to force display as a number instead of a slider. I have possible value from 0 to 60.
  • For number do we need to put a unit= ???, I've a number which is not in seconds, celsius or percent. Just no unit of measure. Can I omit unit= or it will use a default unit.
  • for IAS 0x0002 ZoneStatus I have different values for different status. This attribute is not in the quirks and It is set as binary_sensor, on / off. Is it possible to make a select even if that attribute is uint16 in order to list the error message instead of a value. or anything else that work.

@claudegel
Copy link
Contributor Author

Ok I've found how to make an enum out of t.uint16_t
I just put t.enum_factory(t.uint16_t, "manufacturer_specific") and it work.

class ZoneStatus(t.enum_factory(t.uint16_t, "manufacturer_specific")):
    """IAS zone status."""

    Ok = 0x0030
    Connector_1 = 0x0031
    Connector_2 = 0x0032
    Low_battery = 0x0038
    Connector_low_bat = 0x003A

Now can you tell me how to limit a value. For example I receive the value for battery_percentage_remaining which goes to 200% with new battery. How can I set the value red from the attribute not to go above 100%

@TheJulianJES
Copy link
Collaborator

TheJulianJES commented May 9, 2025

For number is it possible to force display as a number instead of a slider. I have possible value from 0 to 60.

Not possible via quirks v2 entities, yet. There's a request for it here: zigpy/zha#364

For number do we need to put a unit= ???, I've a number which is not in seconds, celsius or percent. Just no unit of measure. Can I omit unit= or it will use a default unit.

You can omit the unit and no unit will be shown in HA.
Even if you want to use a unit that's not already built-in here, you can just define a custom string, e.g. unit="°C" (though that's built-in).

  • for IAS 0x0002 ZoneStatus I have different values for different status. This attribute is not in the quirks and It is set as binary_sensor, on / off. Is it possible to make a select even if that attribute is uint16 in order to list the error message instead of a value. or anything else that work.

An IasZone entity should already be created by ZHA, though that will always trigger for bit 1 and bit 2.
If you need to disable the default entity to create your own entities, you can do something like this:

.prevent_default_entity_creation(endpoint_id=1, cluster_id=IasZone.cluster_id)  # disable ZHA entity
.binary_sensor(
    IasZone.AttributeDefs.zone_status.name,  # though you may want to use your own attribute definition here
    IasZone.cluster_id,
    unique_id_suffix="ias_1",  # to not conflict with unique ID of other entity, as only the attr name is used by default
    translation_key="leak_status",
    fallback_name="Leak status",
    attribute_converter=lambda x: x & 0b00000001,  # use only bit 1
)
.binary_sensor(
    IasZone.AttributeDefs.zone_status.name,  # though you may want to use your own attribute definition here
    IasZone.cluster_id,
    unique_id_suffix="ias_2",  # to not conflict with unique ID of other entity, as only the attr name is used by default
    translation_key="tamper_status",
    fallback_name="Tamper status",
    `,  # use only bit 2
)
.add_to_registry()

If you have multiple quirks v2 entities for the same attribute, as might be possible for zone_status, you'll need to add a unique_id_suffix to each entity like shown above to not conflict in HA, as it's normally just the attribute name, so it would be zone_status for both entities (but we specified a different unique_id_suffix above, so it's fine).

The same might also apply if you have the same quirks v2 entity for multiple endpoint ids, but I can't fully remember right now.

Above, I've also shown how you can use attribute_converter to only use some bits of the attribute value.

Ok I've found how to make an enum out of t.uint16_t I just put t.enum_factory(t.uint16_t, "manufacturer_specific") and it work.

class ZoneStatus(t.enum_factory(t.uint16_t, "manufacturer_specific")):
    """IAS zone status."""

    Ok = 0x0030
    Connector_1 = 0x0031
    Connector_2 = 0x0032
    Low_battery = 0x0038
    Connector_low_bat = 0x003A

Eh, I don't think you should/want to do that generally.
There's also a zcl_type you can set on attribute definitions, though there might still be issues when reporting needs to be set up for that. But you can have the zigpy (enum) type and the zcl_type being the integer type.

Like, the above way shows how to make separate binary sensors for that (might need to keep the type as an int?).
If you want it to be one read-only sensor entity, you can keep using .enum and just add entity_platform=EntityPlatform.SENSOR to use the sensor platform, instead of the default select platform.

Now can you tell me how to limit a value. For example I receive the value for battery_percentage_remaining which goes to 200% with new battery. How can I set the value red from the attribute not to go above 100%

ZHA already creates a default entity for battery_percentage_remaining, so you shouldn't need to create that on your own.

ZCL specifies that it's percentage = value / 2. So, 100 % is reported as value 200.
I think you should be able to add multiplier=0.5 on your sensor or number entity to divide the number by 2.

Otherwise, attribute_converter=lambda x: x / 2 should also be possible. You can also do more sophisticated calculations using attribute_converter (e.g. call a "proper method").

@TheJulianJES
Copy link
Collaborator

I've not had a proper look at everything changed in this PR, but if you have multiple quirks that use duplicated entities, you might be able to add a base quirk without any model info, and then clone that base quirk multiple times with added model info and more custom entities.

See this example, but try to ignore all the Tuya stuff:

gx02_base_quirk = (
TuyaQuirkBuilder()
.tuya_battery(dp_id=108, battery_type=BatterySize.AA, battery_qty=4)
.tuya_metering(dp_id=111)
.tuya_onoff(dp_id=2)
.tuya_number(
dp_id=103,
attribute_name="irrigation_cycles",
type=t.uint8_t,
min_value=0,
max_value=100,
step=1,
translation_key="irrigation_cycles",
fallback_name="Irrigation cycles",
)
.tuya_dp_attribute(
dp_id=1,
attribute_name="irrigation_mode",
type=t.Bool,
)
.enum(
attribute_name="irrigation_mode",
cluster_id=TUYA_CLUSTER_ID,
enum_class=GiexIrrigationMode,
translation_key="irrigation_mode",
fallback_name="Irrigation mode",
)
.tuya_enum(
dp_id=107,
attribute_name="weather_delay",
enum_class=GiexIrrigationWeatherDelay,
translation_key="weather_delay",
fallback_name="Weather delay",
initially_disabled=True,
)
.tuya_sensor(
dp_id=114,
attribute_name="irrigation_duration",
type=t.uint32_t,
converter=lambda x: giex_string_to_td(x),
state_class=SensorStateClass.MEASUREMENT,
device_class=SensorDeviceClass.DURATION,
unit=UnitOfTime.SECONDS,
translation_key="irrigation_duration",
fallback_name="Irrigation duration",
)
.tuya_sensor(
dp_id=101,
attribute_name="irrigation_start_time",
type=t.CharacterString,
converter=lambda x: giex_string_to_ts(x),
device_class=SensorDeviceClass.TIMESTAMP,
translation_key="irrigation_start_time",
fallback_name="Irrigation start time",
)
.tuya_sensor(
dp_id=102,
attribute_name="irrigation_end_time",
type=t.CharacterString,
converter=lambda x: giex_string_to_ts(x),
device_class=SensorDeviceClass.TIMESTAMP,
translation_key="irrigation_end_time",
fallback_name="Irrigation end time",
)
.skip_configuration()
)
(
gx02_base_quirk.clone()
.applies_to("_TZE200_sh1btabb", "TS0601")
.tuya_number(
dp_id=104,
attribute_name="irrigation_target",
type=t.uint32_t,
min_value=0,
max_value=GIEX_24HRS_AS_MIN,
step=1,
translation_key="irrigation_target",
fallback_name="Irrigation target",
)
.tuya_number(
dp_id=105,
attribute_name="irrigation_interval",
min_value=0,
type=t.uint32_t,
max_value=GIEX_24HRS_AS_MIN,
step=1,
unit=UnitOfTime.MINUTES,
translation_key="irrigation_interval",
fallback_name="Irrigation interval",
)
.add_to_registry()
)
(
gx02_base_quirk.clone()
.applies_to("_TZE200_a7sghmms", "TS0601")
.applies_to("_TZE204_a7sghmms", "TS0601")
.applies_to("_TZE200_7ytb3h8u", "TS0601") # Giex GX02 Var 1
.applies_to("_TZE204_7ytb3h8u", "TS0601") # Giex GX02 Var 1
.applies_to("_TZE284_7ytb3h8u", "TS0601") # Giex GX02 Var 3
.tuya_number(
dp_id=104,
attribute_name="irrigation_target",
type=t.uint32_t,
min_value=0,
max_value=GIEX_12HRS_AS_SEC,
step=1,
translation_key="irrigation_target",
fallback_name="Irrigation target",
)
.tuya_number(
dp_id=105,
attribute_name="irrigation_interval",
type=t.uint32_t,
min_value=0,
max_value=GIEX_12HRS_AS_SEC,
step=1,
unit=UnitOfTime.SECONDS,
translation_key="irrigation_interval",
fallback_name="Irrigation interval",
)
.add_to_registry()
)

gx02_base_quirk is the base quirk. Its entities will be cloned to the other two quirks below (the first for _TZE200_sh1btabb only and the second for _TZE200_a7sghmms and others).
That way, the entities added to the base gx02_base_quirk quirk don't have to be copied to the two other quirks.

Doing it this way isn't always helpful, but it might be for this PR (if you have very similar, but slightly differing devices).

@claudegel
Copy link
Contributor Author

It there a way to express data like battery voltage as integer not float. 100 instead of 100.0

@puddly
Copy link
Contributor

puddly commented May 14, 2025

You can use suggested_display_precision=0 when creating a sensor. It defaults to 1.

@claudegel
Copy link
Contributor Author

claudegel commented May 14, 2025

The sensor for battery_percentage_remaining is not created by my quirk but come from ZHA.
So I didn't create the sensor and I can't add suggested_display_precision=0. Another option?
But it's good to know for my quirks. Thanks

@claudegel
Copy link
Contributor Author

Using suggested_display_precision=0 give me the following error:
QuirkBuilder.sensor() got an unexpected keyword argument 'suggested_display_precision' and I'm not sure it give the result it should. I still get value as 20.0 instead of 20 for battery percent remaining. Do I need to import something in sensor.py ?

@claudegel
Copy link
Contributor Author

I've finished to review all sensor, number etc to avoid duplicate with ZHA quirks V2. I also moved all common sensor etc to sinope_base_quirk for thermostat.py. let me know how to fix suggested_display_precision error.

@claudegel
Copy link
Contributor Author

I've a question. For tank level sensor LM4110-ZB. if the probe is disconneted the sensor gauge_angle will report a value of -2. It there a way we can catch this error in the sensor.py code ?

@TheJulianJES
Copy link
Collaborator

Using suggested_display_precision=0 give me the following error:
QuirkBuilder.sensor() got an unexpected keyword argument 'suggested_display_precision'

We only added this for HA 2025.5.0 (and later). The minimum zigpy version required by zha-quirks doesn't have this additional parameter yet, so the tests in this repo will fail until I bump that version (and this PR being updated/rebased on top of that then).

However, it should work locally..

I've a question. For tank level sensor LM4110-ZB. if the probe is disconneted the sensor gauge_angle will report a value of -2. It there a way we can catch this error in the sensor.py code ?

We might be able to add an attribute_converter to that sensor entity:

attribute_converter=lambda x: None if x == -2 else x

That would just return None if the value is -2. That should show "Unavailable" (or "Unknown"?) in the HA UI if the value is -2.

Comment on lines +548 to +560
.sensor( # battery percent
attribute_name=SinopeTechnologiesPowerConfigurationCluster.AttributeDefs.battery_percentage_remaining.name,
cluster_id=SinopeTechnologiesPowerConfigurationCluster.cluster_id,
state_class=SensorStateClass.MEASUREMENT,
unit=PERCENTAGE,
device_class=SensorDeviceClass.BATTERY,
reporting_config=ReportingConfig(
min_interval=30, max_interval=43200, reportable_change=1
),
translation_key="battery_percentage_remaining",
fallback_name="Battery percentage remaining",
entity_type=EntityType.DIAGNOSTIC,
)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this needed? If you remove this, does the built-in ZHA battery percentage sensor not appear?

Comment on lines +396 to +408
.sensor( # Current summ delivered
attribute_name=SinopeTechnologiesMeteringCluster.AttributeDefs.current_summ_delivered.name,
cluster_id=SinopeTechnologiesMeteringCluster.cluster_id,
state_class=SensorStateClass.TOTAL_INCREASING,
unit=UnitOfEnergy.WATT_HOUR,
device_class=SensorDeviceClass.ENERGY,
reporting_config=ReportingConfig(
min_interval=59, max_interval=1799, reportable_change=60
),
translation_key="current_summ_delivered",
fallback_name="Current summ delivered",
entity_type=EntityType.STANDARD,
)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same for this. ZHA should already create an entity for this Metering cluster, so we shouldn't add a custom entity.
Does the ZHA entity not appear for some reason?

@claudegel
Copy link
Contributor Author

I've added attribute_converter=lambda x: None if x == -2 else x, in sensor.py
.sensor( # battery percent
If I remove it the battery percent does not show up in that diagnostic section.
.sensor( # Current summ delivered
yours is not working. see two screen capture: with my Current summ delivered in quirks and one without. Your summ delivered is not working and if I remove mine, both are not working
summ with mine
summ without mine

@TheJulianJES TheJulianJES added the needs reviewer answer An answer from a reviewer is needed (e.g. why a PR isn't acceptable in the current state). label May 25, 2025
@TheJulianJES
Copy link
Collaborator

I've added attribute_converter=lambda x: None if x == -2 else x, in sensor.py

Ok, and that works as expected now (showing "unavailable" if the probe is not connected)?

.sensor( # battery percent
If I remove it the battery percent does not show up in that diagnostic section.
.sensor( # Current summ delivered
yours is not working. see two screen capture: with my Current summ delivered in quirks and one without. Your summ delivered is not working and if I remove mine, both are not working

Hmm, we have to get this fixed. Were you able to try this on HA 2025.6.0 betas? They already appear very stable to me. If so, does the behavior change on those versions?

Also, do the default ZHA entities work if you use the (integrated) v1 quirks, or no quirks for the Sinope devices at all? (you'd have to remove the integrated quirks to test "no quirks" at all)
Like, did these entities already not show up with the v1 quirks, even though the clusters are default ZCL and present on the devices?

@claudegel
Copy link
Contributor Author

  • For the switch if I remove my quirk, switch.py and restart HA the Current summ delivered does not work.
  • same for the valve .sensor( # battery percent does not work.
  • Restarting HA with the quirk switch.py back in strangely both are working for the current summ delivered. My battery percent is working again.
  • For your current summ delivery for switch I've noticed that sometime it work and after a restart of HA it don't work. Mine is always ok. :)
  • For the sensor I'll check tomorrow as I need to put it back in HA. Was working on an update in another custom_component about that device.
  • I also add to change something in thermostat.py to be able to set an enum entities for PumpStatus
class PumpStatus(t.enum_factory(t.uint8_t, "manufacturer_specific")):
    """Pump protection status."""

    Off = 0x00
    On = 0x01

if I live it as:

class PumpStatus(t.uint8_t):
    """Pump protection status."""

    Off = 0x00
    On = 0x01

I get an error:

TypeError: 'type' object is not iterable
2025-06-02 11:20:01.889 ERROR (MainThread) [homeassistant.components.zha.helpers] Error while adding entity from entity data: EntityData(entity=<zha.application.platforms.select.ZCLEnumSelectEntity object at 0x7f7d39919950>, device_proxy=<homeassistant.components.zha.helpers.ZHADeviceProxy object at 0x7f7d3992d6d0>, group_proxy=None)
Traceback (most recent call last):
  File "/srv/homeassistant/lib/python3.13/site-packages/homeassistant/components/zha/helpers.py", line 1169, in async_add_entities
    entities_to_add.append(entity_class(entity_data))
                           ~~~~~~~~~~~~^^^^^^^^^^^^^
  File "/srv/homeassistant/lib/python3.13/site-packages/homeassistant/components/zha/select.py", line 55, in __init__
    super().__init__(entity_data, **kwargs)
    ~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^
  File "/srv/homeassistant/lib/python3.13/site-packages/homeassistant/components/zha/entity.py", line 45, in __init__
    meta = self.entity_data.entity.info_object
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.13/functools.py", line 1039, in __get__
    val = self.func(instance)
  File "/srv/homeassistant/lib/python3.13/site-packages/zha/application/platforms/select.py", line 232, in info_object
    options=self._attr_options,
            ^^^^^^^^^^^^^^^^^^
AttributeError: 'ZCLEnumSelectEntity' object has no attribute '_attr_options'

@TheJulianJES
Copy link
Collaborator

TheJulianJES commented Jun 4, 2025

if I live it as: [...] I get an error:

Yeah, because t.uint8_t is an int type, not an enum type. You'd need to change the class to inherit from t.enum8 instead.
Then, the zcl_type=t.uint8_t can be defined in the ZCLAttributeDef.
Some other quirks still use t.enum_factory(t.uint8_t) instead, but we should no longer do that, as it's not really correct.

However, it doesn't make a ton of sense to define an enum for Off/On. IMO, the class should be removed and you should just keep using type=t.uint8_t in the ZCLAttributeDef.
Then, use a binary_sensor or switch, depending on if it's read-only or also writeable.

@TheJulianJES
Copy link
Collaborator

For the missing default ZHA entities, there should be some more info when enabling debug logs in configuration.yaml like this:

logger:
  default: info
  logs:
    homeassistant.core: debug
    homeassistant.components.zha: debug
    bellows.zigbee.application: debug
    bellows.ezsp: debug
    zha: debug
    zigpy: debug
    zigpy_deconz.zigbee.application: debug
    zigpy_deconz.api: debug
    zigpy_xbee.zigbee.application: debug
    zigpy_xbee.api: debug
    zigpy_zigate: debug
    zigpy_znp: debug
    zhaquirks: debug

(zha: debug alone should be enough)

Please try to grab debug logs, without your custom quirks battery_percentage_remaining and current_summ_delivered sensors. Possible important lines are <ATTR_NAME> is not supported - skipping <XXX> entity creation.
Please also download diagnostics information and drag and drop them into a GitHub comment to upload them.
The diagnostics information should not contain anything sensitive, but the debug logs might.

IIRC the current_summ_delivered sensor needs unit_of_measurement on be specified on the Metering cluster. This should be done by the device, but the diagnostics information will contain the current state of the zigpy attribute cache for that device, so give us clarity on if the attribute is present on the device (or if zigpy think it is (or not)).

The battery_percentage_remaining sensor needs the entity to be marked as a non-mains device in the NodeDescriptor.

Also, are you on HA 2025.6.0b4 now and the issues still continue?

And one last thing, please try to remove(!) and re-pair the device once (without your custom quirks sensors being present, but with the v2 quirks, and maybe also without after that). Removing the device makes sure the zigpy attribute cache in the database is cleaned for that device, so it's properly re-interviewed. It will also remove all your HA entities though.

Please also grab diagnostics information after that, so we can possibly compare them.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
needs review This PR should be reviewed soon, as it generally looks good. needs reviewer answer An answer from a reviewer is needed (e.g. why a PR isn't acceptable in the current state). v2 quirk Quirks using v2 API. Might add custom entities that need translation keys in HA.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants