|
16 | 16 | # |
17 | 17 |
|
18 | 18 |
|
19 | | -from typing import Callable |
| 19 | +from typing import Callable, Optional |
20 | 20 |
|
21 | 21 | import matter.clusters as Clusters |
22 | 22 | from matter.testing.basic_composition import BasicCompositionTests |
|
27 | 27 | device_type_id_type, is_valid_device_type_id) |
28 | 28 | from matter.testing.problem_notices import (AttributePathLocation, ClusterPathLocation, CommandPathLocation, DeviceTypePathLocation, |
29 | 29 | ProblemNotice, ProblemSeverity) |
30 | | -from matter.testing.spec_parsing import CommandType, XmlDeviceType, XmlDeviceTypeClusterRequirements |
| 30 | +from matter.testing.spec_parsing import (CommandType, PrebuiltDataModelDirectory, XmlDeviceType, XmlDeviceTypeClusterRequirements, |
| 31 | + build_xml_device_types, build_xml_namespaces) |
31 | 32 | from matter.tlv import uint |
32 | 33 |
|
33 | 34 |
|
@@ -76,8 +77,10 @@ async def setup_class_helper(self): |
76 | 77 | await super().setup_class_helper() |
77 | 78 | self.build_spec_xmls() |
78 | 79 |
|
79 | | - def _get_device_type_id(self, device_type_name: str) -> int: |
80 | | - id = [id for id, dt in self.xml_device_types.items() if dt.name.lower() == device_type_name.lower()] |
| 80 | + def _get_device_type_id(self, device_type_name: str, xml_device_types: Optional[dict[uint, XmlDeviceType]] = None) -> int: |
| 81 | + if xml_device_types is None: |
| 82 | + xml_device_types = self.xml_device_types |
| 83 | + id = [id for id, dt in xml_device_types.items() if dt.name.lower() == device_type_name.lower()] |
81 | 84 | if len(id) != 1: |
82 | 85 | raise KeyError(f"Unable to find {device_type_name} device type") |
83 | 86 | return id[0] |
@@ -494,3 +497,82 @@ def check_root_node_restricted_clusters(self) -> list[ProblemNotice]: |
494 | 497 | problems.append(ProblemNotice("TC-IDM-14.1", location=ClusterPathLocation(endpoint_id=endpoint_id, cluster_id=cluster.id), |
495 | 498 | severity=ProblemSeverity.ERROR, problem=f"Root-node-restricted cluster {cluster} appears on non-root-node endpoint")) |
496 | 499 | return problems |
| 500 | + |
| 501 | + def check_closure_restricted_clusters(self) -> list[ProblemNotice]: |
| 502 | + # This is a test that is SPECIFIC to the 1.5 spec, and thus we need the 1.5 spec information specifically |
| 503 | + # to assess the revisions. |
| 504 | + one_five_device_types, _ = build_xml_device_types(PrebuiltDataModelDirectory.k1_5) |
| 505 | + # TODO: change this once https://github.com/project-chip/matter-test-scripts/issues/689 is implemented |
| 506 | + |
| 507 | + window_covering_id = self._get_device_type_id('Window Covering', one_five_device_types) |
| 508 | + closure_id = self._get_device_type_id('Closure', one_five_device_types) |
| 509 | + closure_panel_id = self._get_device_type_id('Closure Panel', one_five_device_types) |
| 510 | + restricted_device_type_ids = [window_covering_id, closure_id, closure_panel_id] |
| 511 | + |
| 512 | + problems = [] |
| 513 | + for endpoint_id, endpoint in self.endpoints.items(): |
| 514 | + device_types = endpoint[Clusters.Descriptor][Clusters.Descriptor.Attributes.DeviceTypeList] |
| 515 | + have_closure = Clusters.ClosureControl in endpoint.keys() or Clusters.ClosureDimension in endpoint.keys() |
| 516 | + have_window_covering = Clusters.WindowCovering in endpoint.keys() |
| 517 | + |
| 518 | + if have_closure and have_window_covering: |
| 519 | + for dt in device_types: |
| 520 | + device_type_id = dt.deviceType |
| 521 | + if device_type_id in restricted_device_type_ids and dt.revision <= one_five_device_types[device_type_id].revision: |
| 522 | + problems.append(ProblemNotice("TC-IDM-14.1", location=DeviceTypePathLocation(endpoint_id=endpoint_id, device_type_id=device_type_id), severity=ProblemSeverity.ERROR, |
| 523 | + problem=f"Endpoint with device type {one_five_device_types[device_type_id].name} has both window covering and closure clusters")) |
| 524 | + return problems |
| 525 | + |
| 526 | + def check_closure_restricted_sem_tags(self) -> list[ProblemNotice]: |
| 527 | + # This is a test that is SPECIFIC to the 1.5 spec, and thus we need the 1.5 spec information specifically |
| 528 | + # to assess the revisions. |
| 529 | + one_five_device_types, _ = build_xml_device_types(PrebuiltDataModelDirectory.k1_5) |
| 530 | + one_five_namespaces, _ = build_xml_namespaces(PrebuiltDataModelDirectory.k1_5) |
| 531 | + # TODO: change this once https://github.com/project-chip/matter-test-scripts/issues/689 is implemented |
| 532 | + |
| 533 | + def get_namespace_id(name: str) -> uint: |
| 534 | + ids = [id for id, xml in one_five_namespaces.items() if xml.name.lower() == name.lower()] |
| 535 | + if len(ids) != 1: |
| 536 | + raise ValueError(f"Unable to find unique namespace for '{name}'") |
| 537 | + return ids[0] |
| 538 | + |
| 539 | + closure_id = self._get_device_type_id('Closure', one_five_device_types) |
| 540 | + closure_panel_id = self._get_device_type_id('Closure Panel', one_five_device_types) |
| 541 | + closure_namespace_id = get_namespace_id('Closure') |
| 542 | + closure_panel_namespace_id = get_namespace_id('Closure Panel') |
| 543 | + |
| 544 | + def check_tags_on_endpoint(device_type_id: int, allowed_namespace: int, max_num_tags_allowed_namespace: int, disallowed_namespace: int): |
| 545 | + tag_list = endpoint[Clusters.Descriptor][Clusters.Descriptor.Attributes.TagList] |
| 546 | + allowed_tag_count = 0 |
| 547 | + |
| 548 | + for tag in tag_list: |
| 549 | + if tag.namespaceID == disallowed_namespace: |
| 550 | + problems.append(ProblemNotice("TC-IDM-14.1", location=DeviceTypePathLocation(endpoint_id=endpoint_id, device_type_id=device_type_id), severity=ProblemSeverity.ERROR, |
| 551 | + problem=f"Endpoint with device type {one_five_device_types[device_type_id].name} has semantic tag {tag.tag} from namespace {one_five_namespaces[disallowed_namespace].name}")) |
| 552 | + elif tag.namespaceID == allowed_namespace: |
| 553 | + allowed_tag_count += 1 |
| 554 | + |
| 555 | + if allowed_tag_count == 0: |
| 556 | + problems.append(ProblemNotice("TC-IDM-14.1", location=DeviceTypePathLocation(endpoint_id=endpoint_id, device_type_id=device_type_id), severity=ProblemSeverity.ERROR, |
| 557 | + problem=f"Endpoint with device type {one_five_device_types[device_type_id].name} is missing a {one_five_namespaces[allowed_namespace].name} namespace tag")) |
| 558 | + elif allowed_tag_count > max_num_tags_allowed_namespace: |
| 559 | + problems.append(ProblemNotice("TC-IDM-14.1", location=DeviceTypePathLocation(endpoint_id=endpoint_id, device_type_id=device_type_id), severity=ProblemSeverity.ERROR, |
| 560 | + problem=f"Endpoint with device type {one_five_device_types[device_type_id].name} has multiple {one_five_namespaces[allowed_namespace].name} namespace tags")) |
| 561 | + |
| 562 | + problems = [] |
| 563 | + for endpoint_id, endpoint in self.endpoints.items(): |
| 564 | + # If a Cloure or Closure Panel does not implement TagList, this is also invalid but is verified by another IDM test, |
| 565 | + if Clusters.Descriptor.Attributes.TagList not in endpoint[Clusters.Descriptor]: |
| 566 | + continue |
| 567 | + |
| 568 | + device_types = endpoint[Clusters.Descriptor][Clusters.Descriptor.Attributes.DeviceTypeList] |
| 569 | + |
| 570 | + for dt in device_types: |
| 571 | + if dt.deviceType == closure_id: |
| 572 | + check_tags_on_endpoint(device_type_id=closure_id, allowed_namespace=closure_namespace_id, |
| 573 | + max_num_tags_allowed_namespace=1, disallowed_namespace=closure_panel_namespace_id) |
| 574 | + elif dt.deviceType == closure_panel_id: |
| 575 | + check_tags_on_endpoint(device_type_id=closure_panel_id, allowed_namespace=closure_panel_namespace_id, |
| 576 | + max_num_tags_allowed_namespace=1, disallowed_namespace=closure_namespace_id) |
| 577 | + |
| 578 | + return problems |
0 commit comments