Skip to content

Commit b5607d3

Browse files
committed
Rename quirk IDs to exposed features
- `exposes_features` is used by the device. - `exposed_features` is used by the MatchRules to create discovery matches.
1 parent 1133565 commit b5607d3

File tree

13 files changed

+159
-130
lines changed

13 files changed

+159
-130
lines changed

tests/test_cluster_handlers.py

Lines changed: 23 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -525,10 +525,10 @@ async def test_out_cluster_handler_config(
525525
def test_cluster_handler_registry() -> None:
526526
"""Test ZIGBEE cluster handler Registry."""
527527

528-
# get all quirk ID from zigpy quirks registry
529-
cluster_quirk_id_map: dict[int, set[str | None]] = {}
528+
# get all exposed features from zigpy quirks registry
529+
cluster_exposed_feature_map: dict[int, set[str | None]] = {}
530530
for cluster_id in CLUSTERS_BY_ID:
531-
cluster_quirk_id_map[cluster_id] = {None}
531+
cluster_exposed_feature_map[cluster_id] = {None}
532532

533533
# loop over custom clusters in v2 quirks registry
534534
for quirks in _DEVICE_REGISTRY.registry_v2.values():
@@ -538,14 +538,14 @@ def test_cluster_handler_registry() -> None:
538538
rm.add for rm in quirk_reg_entry.replaces_metadata
539539
}
540540
for metadata in all_metadata:
541-
cluster_quirk_id_map[metadata.cluster.cluster_id] = {None}
541+
cluster_exposed_feature_map[metadata.cluster.cluster_id] = {None}
542542

543543
# loop over custom clusters in v1 quirks registry
544544
for manufacturer in _DEVICE_REGISTRY.registry_v1.values():
545545
for model_quirk_list in manufacturer.values():
546546
for quirk in model_quirk_list:
547547
qid: set[str] | str = getattr(quirk, ATTR_QUIRK_ID, set())
548-
quirk_ids: set[str] = {qid} if isinstance(qid, str) else set(qid)
548+
exposed_features: set[str] = {qid} if isinstance(qid, str) else set(qid)
549549

550550
device_description: dict[str, dict[str, Any]] = getattr(
551551
quirk, "replacement", None
@@ -560,9 +560,9 @@ def test_cluster_handler_registry() -> None:
560560
for cluster_id in cluster_ids:
561561
if not isinstance(cluster_id, int):
562562
cluster_id = cluster_id.cluster_id
563-
if cluster_id not in cluster_quirk_id_map:
564-
cluster_quirk_id_map[cluster_id] = {None}
565-
cluster_quirk_id_map[cluster_id].update(quirk_ids)
563+
if cluster_id not in cluster_exposed_feature_map:
564+
cluster_exposed_feature_map[cluster_id] = {None}
565+
cluster_exposed_feature_map[cluster_id].update(exposed_features)
566566

567567
for (
568568
cluster_id,
@@ -573,16 +573,17 @@ def test_cluster_handler_registry() -> None:
573573
assert 0 <= cluster_id <= 0xFFFF
574574

575575
# test all registered clusters are used in zigpy or quirks
576-
assert cluster_id in cluster_quirk_id_map
576+
assert cluster_id in cluster_exposed_feature_map
577577

578578
assert isinstance(cluster_handler_classes, dict)
579-
for quirk_id, cluster_handler in cluster_handler_classes.items():
580-
# test cluster handler is not quirk specific or only has a single quirk ID
581-
assert quirk_id is None or isinstance(quirk_id, str)
579+
for ch_exposed_feature, cluster_handler in cluster_handler_classes.items():
580+
# test cluster handler is not quirk specific
581+
# or only has a single exposed feature
582+
assert ch_exposed_feature is None or isinstance(ch_exposed_feature, str)
582583
assert issubclass(cluster_handler, ClusterHandler)
583584

584-
# test cluster handler quirk ID is used in quirk with that cluster
585-
assert quirk_id in cluster_quirk_id_map[cluster_id]
585+
# test cluster handler exposed feature is used in quirk with that cluster
586+
assert ch_exposed_feature in cluster_exposed_feature_map[cluster_id]
586587

587588

588589
def test_epch_unclaimed_cluster_handlers(cluster_handler) -> None:
@@ -820,7 +821,7 @@ async def test_ep_cluster_handlers_configure(cluster_handler) -> None:
820821
)
821822
zha_dev = mock.MagicMock(spec=Device)
822823
zha_dev.unique_id = "00:11:22:33:44:55:66:77"
823-
type(zha_dev).quirk_id = mock.PropertyMock(return_value=set())
824+
type(zha_dev).exposes_features = mock.PropertyMock(return_value=set())
824825
endpoint = Endpoint.new(endpoint_mock, zha_dev)
825826

826827
claimed = {ch_1.id: ch_1, ch_2.id: ch_2, ch_3.id: ch_3}
@@ -1023,7 +1024,7 @@ class TestZigbeeClusterHandler(ClusterHandler):
10231024
)
10241025

10251026
mock_zha_device = mock.AsyncMock(spec=Device)
1026-
mock_zha_device.quirk_id = set()
1027+
mock_zha_device.exposes_features = set()
10271028
mock_zha_device.unique_id = "aa:bb:cc:dd:11:22:33:44"
10281029

10291030
zha_endpoint = Endpoint(zigpy_ep, mock_zha_device)
@@ -1068,14 +1069,14 @@ class TestZigbeeClusterHandler(ColorClusterHandler):
10681069
)
10691070

10701071
mock_zha_device = mock.AsyncMock(spec=Device)
1071-
mock_zha_device.quirk_id = set()
1072+
mock_zha_device.exposes_features = set()
10721073
mock_zha_device.unique_id = "aa:bb:cc:dd:11:22:33:44"
10731074

10741075
zha_endpoint = Endpoint(zigpy_ep, mock_zha_device)
10751076

10761077
with patch.dict(
10771078
CLUSTER_HANDLER_REGISTRY[cluster.cluster_id],
1078-
{"__test_quirk_id": TestZigbeeClusterHandler},
1079+
{"__exposed_feature_id": TestZigbeeClusterHandler},
10791080
):
10801081
zha_endpoint.add_all_cluster_handlers()
10811082

@@ -1085,10 +1086,10 @@ class TestZigbeeClusterHandler(ColorClusterHandler):
10851086
)
10861087

10871088

1088-
async def test_quirk_id_cluster_handler(
1089+
async def test_exposed_feature_cluster_handler(
10891090
zha_gateway: Gateway,
10901091
) -> None: # pylint: disable=unused-argument
1091-
"""Test setting up a cluster handler that matches a standard cluster."""
1092+
"""Test setting up a cluster handler with an exposed feature."""
10921093

10931094
class TestZigbeeClusterHandler(ColorClusterHandler):
10941095
"""Test cluster handler that matches a standard cluster."""
@@ -1109,12 +1110,12 @@ class TestZigbeeClusterHandler(ColorClusterHandler):
11091110

11101111
mock_zha_device = mock.AsyncMock(spec=Device)
11111112
mock_zha_device.unique_id = "aa:bb:cc:dd:11:22:33:44"
1112-
mock_zha_device.quirk_id = {"__test_quirk_id"}
1113+
mock_zha_device.exposes_features = {"__exposed_feature_id"}
11131114
zha_endpoint = Endpoint(zigpy_ep, mock_zha_device)
11141115

11151116
with patch.dict(
11161117
CLUSTER_HANDLER_REGISTRY[cluster.cluster_id],
1117-
{"__test_quirk_id": TestZigbeeClusterHandler},
1118+
{"__exposed_feature_id": TestZigbeeClusterHandler},
11181119
):
11191120
zha_endpoint.add_all_cluster_handlers()
11201121

tests/test_registries.py

Lines changed: 39 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
MANUFACTURER = "mock manufacturer"
2626
MODEL = "mock model"
2727
QUIRK_CLASS = "mock.test.quirk.class"
28-
QUIRK_ID = "quirk_id"
28+
EXPOSED_FEATURE = "EXPOSED_FEATURE_ID"
2929

3030

3131
@pytest.fixture
@@ -97,14 +97,19 @@ def cluster_handlers(cluster_handler):
9797
MatchRule(models="no match", aux_cluster_handlers="aux_cluster_handler"),
9898
False,
9999
),
100-
(MatchRule(quirk_ids=QUIRK_ID), True),
101-
(MatchRule(quirk_ids="no match"), False),
100+
(MatchRule(exposed_features=EXPOSED_FEATURE), True),
101+
(MatchRule(exposed_features="no match"), False),
102102
(
103-
MatchRule(quirk_ids=QUIRK_ID, aux_cluster_handlers="aux_cluster_handler"),
103+
MatchRule(
104+
exposed_features=EXPOSED_FEATURE,
105+
aux_cluster_handlers="aux_cluster_handler",
106+
),
104107
True,
105108
),
106109
(
107-
MatchRule(quirk_ids="no match", aux_cluster_handlers="aux_cluster_handler"),
110+
MatchRule(
111+
exposed_features="no match", aux_cluster_handlers="aux_cluster_handler"
112+
),
108113
False,
109114
),
110115
# match everything
@@ -114,7 +119,7 @@ def cluster_handlers(cluster_handler):
114119
cluster_handler_names={"on_off", "level"},
115120
manufacturers=MANUFACTURER,
116121
models=MODEL,
117-
quirk_ids=QUIRK_ID,
122+
exposed_features=EXPOSED_FEATURE,
118123
),
119124
True,
120125
),
@@ -167,39 +172,41 @@ def cluster_handlers(cluster_handler):
167172
(
168173
MatchRule(
169174
cluster_handler_names="on_off",
170-
quirk_ids={"random quirk", QUIRK_ID},
175+
exposed_features={"random quirk", EXPOSED_FEATURE},
171176
),
172177
True,
173178
),
174179
(
175180
MatchRule(
176181
cluster_handler_names="on_off",
177-
quirk_ids={"random quirk", "another quirk"},
182+
exposed_features={"random quirk", "another quirk"},
178183
),
179184
False,
180185
),
181186
(
182187
MatchRule(
183-
cluster_handler_names="on_off", quirk_ids=lambda x: x == QUIRK_ID
188+
cluster_handler_names="on_off",
189+
exposed_features=lambda x: x == EXPOSED_FEATURE,
184190
),
185191
True,
186192
),
187193
(
188194
MatchRule(
189-
cluster_handler_names="on_off", quirk_ids=lambda x: x != QUIRK_ID
195+
cluster_handler_names="on_off",
196+
exposed_features=lambda x: x != EXPOSED_FEATURE,
190197
),
191198
False,
192199
),
193200
(
194-
MatchRule(cluster_handler_names="on_off", quirk_ids=QUIRK_ID),
201+
MatchRule(cluster_handler_names="on_off", exposed_features=EXPOSED_FEATURE),
195202
True,
196203
),
197204
],
198205
)
199206
def test_registry_matching(rule, matched, cluster_handlers) -> None:
200207
"""Test strict rule matching."""
201208
assert (
202-
rule.strict_matched(MANUFACTURER, MODEL, cluster_handlers, {QUIRK_ID})
209+
rule.strict_matched(MANUFACTURER, MODEL, cluster_handlers, {EXPOSED_FEATURE})
203210
is matched
204211
)
205212

@@ -288,16 +295,16 @@ def test_registry_matching(rule, matched, cluster_handlers) -> None:
288295
(MatchRule(manufacturers=MANUFACTURER), True),
289296
(MatchRule(models=MODEL), True),
290297
(MatchRule(models="no match"), False),
291-
(MatchRule(quirk_ids=QUIRK_ID), True),
292-
(MatchRule(quirk_ids="no match"), False),
298+
(MatchRule(exposed_features=EXPOSED_FEATURE), True),
299+
(MatchRule(exposed_features="no match"), False),
293300
# match everything
294301
(
295302
MatchRule(
296303
generic_ids={"cluster_handler_0x0006", "cluster_handler_0x0008"},
297304
cluster_handler_names={"on_off", "level"},
298305
manufacturers=MANUFACTURER,
299306
models=MODEL,
300-
quirk_ids=QUIRK_ID,
307+
exposed_features=EXPOSED_FEATURE,
301308
),
302309
True,
303310
),
@@ -306,7 +313,8 @@ def test_registry_matching(rule, matched, cluster_handlers) -> None:
306313
def test_registry_loose_matching(rule, matched, cluster_handlers) -> None:
307314
"""Test loose rule matching."""
308315
assert (
309-
rule.loose_matched(MANUFACTURER, MODEL, cluster_handlers, {QUIRK_ID}) is matched
316+
rule.loose_matched(MANUFACTURER, MODEL, cluster_handlers, {EXPOSED_FEATURE})
317+
is matched
310318
)
311319

312320

@@ -370,12 +378,12 @@ def entity_registry():
370378

371379

372380
@pytest.mark.parametrize(
373-
("manufacturer", "model", "quirk_id", "match_name"),
381+
("manufacturer", "model", "exposes_features", "match_name"),
374382
[
375383
("random manufacturer", "random model", "random.class", "OnOff"),
376384
("random manufacturer", MODEL, "random.class", "OnOffModel"),
377385
(MANUFACTURER, "random model", "random.class", "OnOffManufacturer"),
378-
("random manufacturer", "random model", QUIRK_ID, "OnOffQuirk"),
386+
("random manufacturer", "random model", EXPOSED_FEATURE, "OnOffQuirk"),
379387
(MANUFACTURER, MODEL, "random.class", "OnOffModelManufacturer"),
380388
(MANUFACTURER, "some model", "random.class", "OnOffMultimodel"),
381389
],
@@ -385,7 +393,7 @@ def test_weighted_match(
385393
entity_registry: PlatformEntityRegistry,
386394
manufacturer,
387395
model,
388-
quirk_id,
396+
exposes_features,
389397
match_name,
390398
) -> None:
391399
"""Test weightedd match."""
@@ -426,7 +434,7 @@ class OnOffModelManufacturer: # pylint: disable=unused-variable
426434
"""OnOff model and manufacturer cluster handler."""
427435

428436
@entity_registry.strict_match(
429-
s.component, cluster_handler_names="on_off", quirk_ids=QUIRK_ID
437+
s.component, cluster_handler_names="on_off", exposed_features=EXPOSED_FEATURE
430438
)
431439
class OnOffQuirk: # pylint: disable=unused-variable
432440
"""OnOff quirk cluster handler."""
@@ -435,7 +443,7 @@ class OnOffQuirk: # pylint: disable=unused-variable
435443
ch_level = cluster_handler("level", 8)
436444

437445
match, claimed = entity_registry.get_entity(
438-
s.component, manufacturer, model, [ch_on_off, ch_level], {quirk_id}
446+
s.component, manufacturer, model, [ch_on_off, ch_level], {exposes_features}
439447
)
440448

441449
assert match.__name__ == match_name
@@ -463,7 +471,7 @@ class SmartEnergySensor2:
463471
"manufacturer",
464472
"model",
465473
cluster_handlers=[ch_se, ch_illuminati],
466-
quirk_ids={"quirk_id"},
474+
exposes_features={"exposed_feature_id"},
467475
)
468476

469477
assert s.binary_sensor in match
@@ -493,7 +501,7 @@ class SmartEnergySensor3:
493501
"manufacturer",
494502
"model",
495503
cluster_handlers={ch_se, ch_illuminati},
496-
quirk_ids={"quirk_id"},
504+
exposes_features={"exposed_feature_id"},
497505
)
498506

499507
assert s.binary_sensor in match
@@ -541,21 +549,23 @@ def quirk_class_validator(value):
541549
quirk_class_validator(v)
542550
return
543551

544-
if value not in all_quirk_ids:
545-
raise ValueError(f"Quirk ID '{value}' does not exist.")
552+
if value not in all_exposed_features:
553+
raise ValueError(f"Exposed feature '{value}' does not exist.")
546554

547555
# get all quirk ID from zigpy quirks registry
548-
all_quirk_ids: set[str] = set()
556+
all_exposed_features: set[str] = set()
549557
for manufacturer in zigpy_quirks._DEVICE_REGISTRY.registry_v1.values():
550558
for model_quirk_list in manufacturer.values():
551559
for quirk in model_quirk_list:
552560
qid: set[str] | str = getattr(quirk, ATTR_QUIRK_ID, set())
553-
device_quirk_ids: set[str] = {qid} if isinstance(qid, str) else set(qid)
554-
all_quirk_ids.update(device_quirk_ids)
561+
device_exposed_features: set[str] = (
562+
{qid} if isinstance(qid, str) else set(qid)
563+
)
564+
all_exposed_features.update(device_exposed_features)
555565

556566
# validate all quirk IDs used in component match rules
557567
for rule, _ in iter_all_rules():
558-
quirk_class_validator(rule.quirk_ids)
568+
quirk_class_validator(rule.exposed_features)
559569

560570

561571
def test_entity_names() -> None:

zha/application/discovery.py

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -408,7 +408,7 @@ def discover_by_device_type(
408408
endpoint.device.manufacturer,
409409
endpoint.device.model,
410410
cluster_handlers,
411-
endpoint.device.quirk_ids,
411+
endpoint.device.exposes_features,
412412
)
413413
if entity_class is None:
414414
return
@@ -437,7 +437,7 @@ def probe_single_cluster(
437437
endpoint.device.manufacturer,
438438
endpoint.device.model,
439439
[cluster_handler],
440-
endpoint.device.quirk_ids,
440+
endpoint.device.exposes_features,
441441
)
442442
if entity_class is None:
443443
return
@@ -500,15 +500,16 @@ def handle_on_off_output_cluster_exception(
500500
cluster_id, {None: ClusterHandler}
501501
)
502502

503-
# get first quirk id from device that matches a registered cluster handler
504-
cluster_quirk_id: str | None = None
505-
for qid in endpoint.device.quirk_ids:
506-
if qid in cluster_handler_classes:
507-
cluster_quirk_id = qid
503+
# get first exposed feature from device
504+
# that matches a registered cluster handler
505+
cluster_exposed_feature: str | None = None
506+
for exposed_features in endpoint.device.exposes_features:
507+
if exposed_features in cluster_handler_classes:
508+
cluster_exposed_feature = exposed_features
508509
break
509510

510511
cluster_handler_class = cluster_handler_classes.get(
511-
cluster_quirk_id, ClusterHandler
512+
cluster_exposed_feature, ClusterHandler
512513
)
513514

514515
cluster_handler = cluster_handler_class(cluster, endpoint)
@@ -540,14 +541,14 @@ def discover_multi_entities(
540541
device.manufacturer,
541542
device.model,
542543
cluster_handlers,
543-
device.quirk_ids,
544+
device.exposes_features,
544545
)
545546
else:
546547
matches, claimed = PLATFORM_ENTITIES.get_multi_entity(
547548
device.manufacturer,
548549
device.model,
549550
endpoint.unclaimed_cluster_handlers(),
550-
device.quirk_ids,
551+
device.exposes_features,
551552
)
552553

553554
endpoint.claim_cluster_handlers(claimed)

0 commit comments

Comments
 (0)