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

Add AnalogInput, MultistateInput, use description attribute for fallback_name #197

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

Conversation

prairiesnpr
Copy link

@prairiesnpr prairiesnpr commented Sep 7, 2024

This ensures that the description attribute (0x001C), if present, is used for the fallback_name and sets the translation_key to None. The entity id and name will use the fallback_name and align with the devices value. This is mostly useful for devices using general clusters, if not set, the current functionality is maintained.

Due to testing requirements, this now also updates the cluster handlers for AnalogInput, MultistateInput, and BinaryInput.

Closes: #160

image

image

Multistate still requires zigpy/zigpy#1456 to be fully implemented, since the LVLists aren't serialized, we create a default value for now.
image

AnalogOutput is putting the description in the name twice.
image

This is due to this logic in core, should we add similar logic to core for the rest of the Entities and remove it from ZHA, keep the logic in ZHA, or have it mixed?

https://github.com/home-assistant/core/blob/f1e4229b23ef7837bb4e46877f55d6533f8d4607/homeassistant/components/zha/number.py#L52C9-L52C58

Copy link

codecov bot commented Sep 7, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 96.46%. Comparing base (cda1b95) to head (bd34bfd).

Additional details and impacted files
@@            Coverage Diff             @@
##              dev     #197      +/-   ##
==========================================
+ Coverage   96.40%   96.46%   +0.05%     
==========================================
  Files          61       61              
  Lines        9359     9469     +110     
==========================================
+ Hits         9023     9134     +111     
+ Misses        336      335       -1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@puddly
Copy link
Contributor

puddly commented Sep 7, 2024

I wonder: would it be feasible to instead set the translation key to the ZCL description? This could allow the description to be localized.

@prairiesnpr
Copy link
Author

prairiesnpr commented Sep 7, 2024

I wonder: would it be feasible to instead set the translation key to the ZCL description? This could allow the description to be localized.

That was the approach I started with, unfortunately, when the translation key wasn't found, it would return None for the name and id. I understood it should then fallback, but that's not the result I observed in testing.

@prairiesnpr prairiesnpr marked this pull request as draft September 7, 2024 20:08
@prairiesnpr prairiesnpr marked this pull request as ready for review September 7, 2024 23:44
@prairiesnpr prairiesnpr changed the title Use description attribute for fallback_name Add AnalogInput, MultistateInput, use description attribute for fallback_name Sep 9, 2024
@@ -390,3 +390,36 @@ class SensorDeviceClass(enum.StrEnum):
SensorDeviceClass.ENUM,
SensorDeviceClass.TIMESTAMP,
}


class AnalogInputStateClass(enum.Enum):
Copy link
Author

Choose a reason for hiding this comment

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

The ZCL specifies units for each value of ApplicationType, currently we are using units as specified in EngineeringUnits. Both attributes are optional, do we leave this as is, where sitting a ApplicationType only sets the device class, should it also set the units, if so, what should happen if both attributes are set?

Copy link
Contributor

Choose a reason for hiding this comment

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

If the ApplicationType attribute is implemented, and is set to a value with a defined physical unit, the physical unit defined in ApplicationType takes priority over EngineeringUnits.

I think we should keep things as simple as possible. The spec is expansive and we should only implement the bare minimum to support real devices.

super().__init__(unique_id, cluster_handlers, endpoint, device, **kwargs)
engineering_units = self._cluster_handler.engineering_units
self._attr_native_unit_of_measurement = UNITS.get(engineering_units)
self._attr_state_class = SensorStateClass.MEASUREMENT
Copy link
Author

Choose a reason for hiding this comment

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

Should this always be measurement? I can see a situation where total or total increasing makes sense, but I don't see a good way to choose based on the attributes we have available.

Copy link
Contributor

@TheJulianJES TheJulianJES left a comment

Choose a reason for hiding this comment

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

There are some restrictions on what we can provide as a translation_key to Home Assistant. AFAIK, we are not allowed to provide translation keys blindly, so not directly from the description attribute, for example.

We'd need to map all known description attribute values to translation keys (and fall-back to a default or untranslated name otherwise).

So, I'm wondering if it would make sense to split this PR into two: one with the translation key changes and one with the added AnalogInput and MultistateInput sensors.

@prairiesnpr
Copy link
Author

There are some restrictions on what we can provide as a translation_key to Home Assistant. AFAIK, we are not allowed to provide translation keys blindly, so not directly from the description attribute, for example.

We'd need to map all known description attribute values to translation keys (and fall-back to a default or untranslated name otherwise).

So, I'm wondering if it would make sense to split this PR into two: one with the translation key changes and one with the added AnalogInput and MultistateInput sensors.

We aren't modifying the translation_key here, if a description attribute is provided, we set the key to None and then provide a fallback_name, that way we can use arbitrary values without having a matching translation_key. This follows the same logic currently used for custom quirks https://github.com/home-assistant/core/blob/f1e4229b23ef7837bb4e46877f55d6533f8d4607/homeassistant/components/zha/entity.py#L46

As far as splitting the PR, it was originally planned to have multiple PRs, one for the fallback_name functionality and then additional PRs for each cluster. I ran into issues with writing tests there, since we really needed them to both be present to get the required coverage. That said, I'm not opposed to splitting it, if we have a plan on how best to proceed.

@@ -390,3 +390,36 @@ class SensorDeviceClass(enum.StrEnum):
SensorDeviceClass.ENUM,
SensorDeviceClass.TIMESTAMP,
}


class AnalogInputStateClass(enum.Enum):
Copy link
Contributor

Choose a reason for hiding this comment

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

If the ApplicationType attribute is implemented, and is set to a value with a defined physical unit, the physical unit defined in ApplicationType takes priority over EngineeringUnits.

I think we should keep things as simple as possible. The spec is expansive and we should only implement the bare minimum to support real devices.

super().__init__(unique_id, cluster_handlers, endpoint, device, **kwargs)
if self._cluster_handler.cluster.get("state_text"):
self._enum = enum.Enum( # type: ignore [misc]
"state_text", self._cluster_handler.cluster.get("state_text")
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
"state_text", self._cluster_handler.cluster.get("state_text")
"state_text", self._cluster_handler.cluster["state_text"]

[
(f"state_{i+1}", i + 1)
for i in range(
self._cluster_handler.cluster.get("number_of_states")
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
self._cluster_handler.cluster.get("number_of_states")
self._cluster_handler.cluster["number_of_states"]

class AnalogInputStateClass(enum.Enum):
"""State class for AnalogInput Types."""

TEMPERATURE = (0x00, SensorDeviceClass.TEMPERATURE)
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's move this enum to zigpy. We generally don't want to define ZCL enums and types in the ZHA library itself. From the spec:

image

I think we can define this easily in zigpy:

class AnalogInputType(t.enum8):
    Temp_In_Degrees_C = 0x00
    ...


ANALOG_INPUT_TYPES = {
    AnalogInputType.Temp_In_Degrees_C: TempInDegreesC,
}


class TempInDegreesC(t.enum16):
    Two_Pipe_Entering_Water_Temperature = 0x0000
    ...


class ApplicationType(t.uint32_t, t.Struct):
    group: t.uint8_t
    type: AnalogInputType
    index: t.uint16_t

And then ZHA will only deal with creating a mapping between the ZCL spec units and the HA device classes.

@prairiesnpr
Copy link
Author

This will need a zigpy release before tests will pass.

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

Successfully merging this pull request may close these issues.

Query and use device values when creating entities
3 participants