-
Couldn't load subscription status.
- Fork 53
Fix not claiming cluster handler for some quirks v2 entities #548
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
base: dev
Are you sure you want to change the base?
Changes from all commits
37390cf
0ebbce8
f98323d
1a7d243
19c9279
a9f8141
fc53e7a
4911051
bcf7b37
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,6 +12,7 @@ | |
| PROFILE_ID, | ||
| ) | ||
| from zhaquirks.tuya.tuya_valve import ParksideTuyaValveManufCluster | ||
| import zigpy | ||
| from zigpy.exceptions import ZigbeeException | ||
| from zigpy.profiles import zha | ||
| from zigpy.quirks import CustomCluster, CustomDevice, DeviceRegistry | ||
|
|
@@ -34,6 +35,7 @@ | |
| update_attribute_cache, | ||
| ) | ||
| from zha.application import Platform | ||
| from zha.application.const import ZCL_INIT_ATTRS | ||
| from zha.application.gateway import Gateway | ||
| from zha.application.platforms import EntityCategory, PlatformEntity | ||
| from zha.application.platforms.button import ( | ||
|
|
@@ -43,6 +45,7 @@ | |
| ) | ||
| from zha.application.platforms.button.const import ButtonDeviceClass | ||
| from zha.exceptions import ZHAException | ||
| from zha.zigbee.cluster_handlers.manufacturerspecific import OppleRemoteClusterHandler | ||
| from zha.zigbee.device import Device | ||
|
|
||
| ZIGPY_DEVICE = { | ||
|
|
@@ -354,3 +357,89 @@ async def test_quirks_write_attr_buttons_uid(zha_gateway: Gateway) -> None: | |
| assert entity_btn_2.translation_key == "btn_2" | ||
| assert entity_btn_2._unique_id_suffix == "btn_2" | ||
| assert entity_btn_2._attribute_value == 2 | ||
|
|
||
|
|
||
| class OppleCluster(CustomCluster, ManufacturerSpecificCluster): | ||
| """Aqara manufacturer specific cluster.""" | ||
|
|
||
| cluster_id = 0xFCC0 | ||
| ep_attribute = "opple_cluster" | ||
|
|
||
| class ServerCommandDefs(zcl_f.BaseCommandDefs): | ||
| """Server command definitions.""" | ||
|
|
||
| self_test: Final = zcl_f.ZCLCommandDef( | ||
| id=0x00, schema={"identify_time": t.uint16_t} | ||
| ) | ||
|
|
||
|
|
||
| async def test_cluster_handler_quirks_unnecessary_claiming( | ||
| zha_gateway: Gateway, | ||
| ) -> None: | ||
| """Test quirks button doesn't claim cluster handlers unnecessarily.""" | ||
|
|
||
| registry = DeviceRegistry() | ||
| ( | ||
| QuirkBuilder( | ||
| "Fake_Manufacturer_sensor_2", "Fake_Model_sensor_2", registry=registry | ||
| ) | ||
| .replaces(OppleCluster) | ||
| .command_button( | ||
| FakeManufacturerCluster.ServerCommandDefs.self_test.name, | ||
| OppleCluster.cluster_id, | ||
| command_args=(5,), | ||
| translation_key="self_test", | ||
| fallback_name="Self test", | ||
| ) | ||
| .add_to_registry() | ||
| ) | ||
|
|
||
| zigpy_device = create_mock_zigpy_device( | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Another nitpick, feel free to ignore if it's too much trouble. Is there any way to test using JSON diagnostics from a real device? I'd like to phase out use of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm, I explicitly used I feel like we always run into a bit of a risk with using JSON diagnostics in tests, as it's not clear how endpoints and clusters are layed out by just looking at the tests. You have to look at the (huge) JSON file. It's also harder to get a device exactly in the way you want it, without adding unnecessary endpoints/clusters/quirk interference. Some parts of the device/diagnostics can change over time as well (i.e. ZHA or quirk doing something different, firmware update, ...), when we may not want that for all tests where the diagnostics are used in. |
||
| zha_gateway, | ||
| { | ||
| 1: { | ||
| SIG_EP_INPUT: [ | ||
| general.Basic.cluster_id, | ||
| OppleCluster.cluster_id, | ||
| ], | ||
| SIG_EP_OUTPUT: [], | ||
| SIG_EP_TYPE: zigpy.profiles.zha.DeviceType.OCCUPANCY_SENSOR, | ||
| SIG_EP_PROFILE: zigpy.profiles.zha.PROFILE_ID, | ||
| } | ||
| }, | ||
| manufacturer="Fake_Manufacturer_sensor_2", | ||
| model="Fake_Model_sensor_2", | ||
| ) | ||
| zigpy_device = registry.get_device(zigpy_device) | ||
|
|
||
| # Suppress normal endpoint probing, as this will claim the Opple cluster handler | ||
| # already due to it being in the "CLUSTER_HANDLER_ONLY_CLUSTERS" registry. | ||
| # We want to test the handler also gets claimed via quirks v2 attributes init. | ||
| with patch("zha.application.discovery.EndpointProbe.discover_entities"): | ||
| zha_device = await join_zigpy_device(zha_gateway, zigpy_device) | ||
| assert isinstance(zha_device.device, CustomDeviceV2) | ||
|
|
||
| # get cluster handler of OppleCluster | ||
| opple_ch = zha_device.endpoints[1].all_cluster_handlers["1:0xfcc0"] | ||
| assert isinstance(opple_ch, OppleRemoteClusterHandler) | ||
|
|
||
| # make sure the cluster handler was not claimed, | ||
| # as no reporting is configured and no attributes are to be read | ||
| assert opple_ch not in zha_device.endpoints[1].claimed_cluster_handlers.values() | ||
|
|
||
| # check that BIND is left at default of True, though ZHA will ignore it | ||
| assert opple_ch.BIND is True | ||
|
|
||
| # check ZCL_INIT_ATTRS is empty | ||
| assert opple_ch.ZCL_INIT_ATTRS == {} | ||
|
|
||
| # check that no ZCL_INIT_ATTRS instance variable was created | ||
| assert opple_ch.__dict__.get(ZCL_INIT_ATTRS) is None | ||
| assert opple_ch.ZCL_INIT_ATTRS is OppleRemoteClusterHandler.ZCL_INIT_ATTRS | ||
|
|
||
| # double check we didn't modify the class variable | ||
| assert OppleRemoteClusterHandler.ZCL_INIT_ATTRS == {} | ||
|
|
||
| # check if REPORT_CONFIG is empty, both instance and class variable | ||
| assert opple_ch.REPORT_CONFIG == () | ||
| assert OppleRemoteClusterHandler.REPORT_CONFIG == () | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -232,6 +232,10 @@ def discover_quirks_v2_entities(self, device: Device) -> Iterator[PlatformEntity | |||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| assert cluster_handler | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # flags to determine if we need to claim/bind the cluster handler | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| attribute_initialization_found: bool = False | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| reporting_found: bool = False | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for entity_metadata in entity_metadata_list: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| platform = Platform(entity_metadata.entity_platform.value) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| metadata_type = type(entity_metadata) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -254,6 +258,7 @@ def discover_quirks_v2_entities(self, device: Device) -> Iterator[PlatformEntity | |||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # process the entity metadata for ZCL_INIT_ATTRS and REPORT_CONFIG | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if attr_name := getattr(entity_metadata, "attribute_name", None): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # TODO: ignore "attribute write buttons"? currently, we claim ch | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # if the entity has a reporting config, add it to the cluster handler | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if rep_conf := getattr(entity_metadata, "reporting_config", None): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # if attr is already in REPORT_CONFIG, remove it first | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -268,8 +273,8 @@ def discover_quirks_v2_entities(self, device: Device) -> Iterator[PlatformEntity | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| cluster_handler.REPORT_CONFIG += ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| AttrReportConfig(attr=attr_name, config=astuple(rep_conf)), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # claim the cluster handler, so ZHA configures and binds it | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| endpoint.claim_cluster_handlers([cluster_handler]) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # mark cluster handler for claiming and binding later | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| reporting_found = True | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # not in REPORT_CONFIG, add to ZCL_INIT_ATTRS if it not already in | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| elif attr_name not in cluster_handler.ZCL_INIT_ATTRS: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -283,6 +288,8 @@ def discover_quirks_v2_entities(self, device: Device) -> Iterator[PlatformEntity | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| cluster_handler.ZCL_INIT_ATTRS[attr_name] = ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| entity_metadata.attribute_initialized_from_cache | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # mark cluster handler for claiming later, but not binding | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| attribute_initialization_found = True | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| yield entity_class( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| cluster_handlers=[cluster_handler], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -299,6 +306,19 @@ def discover_quirks_v2_entities(self, device: Device) -> Iterator[PlatformEntity | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| [cluster_handler.name], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # if the cluster handler is unclaimed, claim it and set BIND accordingly, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wonder: should we move this to happen before the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We can't really do that in a nice way, since we need to iterate over all quirks v2 entity metadata for a specific cluster handler before we can make the final decision on whether we can set This is all still done "pre initialization", since they're just added to Lines 939 to 950 in b172549
Nothing in entity.on_add() can also ever rely on the cluster handler being claimed or not.The actual cluster handler initialization is done way later, where it then matters if a cluster handler is claimed or not. I think it's fine to keep it like this tbh.
Lines 956 to 969 in b172549
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # so ZHA configures the cluster handler: reporting + reads attributes | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (attribute_initialization_found or reporting_found) and ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| cluster_handler not in endpoint.claimed_cluster_handlers.values() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there any way for the cluster handler to already be claimed? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The cluster handler can already be claimed by entities from This is also checked by the tests. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| endpoint.claim_cluster_handlers([cluster_handler]) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # BIND is True by default, so only set to False if no reporting found. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # We can safely do this, since quirks v2 entities are initialized last, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # so if the cluster handler wasn't claimed by EndpointProbe so far, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # only v2 entities need it. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if not reporting_found: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| cluster_handler.BIND = False | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @ignore_exceptions_during_iteration | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def discover_coordinator_device_entities( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self, device: Device | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tiny nitpick:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch. Copied that from some other test, but I'll change that.
We'll probably want to adjust this in a lot of existing tests as well at a later date, including (completely) using kwargs for creating quirks v2 entities.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FakeManufacturerCluster.ServerCommandDefs.self_test.nameshould also be changed toOppleCluster[...]. It's the same command name and we never execute this, since we just need it for testing the quirks v2 metadata processing in the device discovery, but it should still be adjusted.