From a036869193ea103d89ee24ee4763ed158122a56f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Josefsen?= Date: Thu, 7 Dec 2023 10:03:04 +0100 Subject: [PATCH 01/17] Fixed missed cluster renaming and adjusted output folder behavior --- src/tools/PICS-generator/PICSGenerator.py | 37 +++++++++++++++++++---- 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/src/tools/PICS-generator/PICSGenerator.py b/src/tools/PICS-generator/PICSGenerator.py index aa774f9cb127e3..1f3c75fa282150 100644 --- a/src/tools/PICS-generator/PICSGenerator.py +++ b/src/tools/PICS-generator/PICSGenerator.py @@ -47,6 +47,11 @@ def GenerateDevicePicsXmlFiles(clusterName, clusterPicsCode, featurePicsList, at nodeOperationalCredentialsCluster = "Node Operational Credentials Cluster" basicInformationCluster = "Basic Information Cluster" networkCommissioningCluster = "Network Commissioning Cluster" + diagnosticLogsCluster = "Diagnostic Logs Cluster" + thermostatUserInterfaceConfigurationCluster = "Thermostat User Interface Configuration Cluster" + wakeOnLANCluster = "Wake On LAN Cluster" + lowPowerCluster = "Low Power Cluster" + laundryModeCluster = "Laundry Mode Cluster" if otaProviderCluster in clusterName or otaRequestorCluster in clusterName: clusterName = "OTA Software Update" @@ -57,6 +62,18 @@ def GenerateDevicePicsXmlFiles(clusterName, clusterPicsCode, featurePicsList, at elif groupKeyManagementCluster == clusterName: clusterName = "Group Communication" + elif diagnosticLogsCluster == clusterName: + clusterName = "Diagnostics Logs Cluster" + + elif thermostatUserInterfaceConfigurationCluster == clusterName: + clusterName = "Thermostat User Configuration Cluster" + + elif wakeOnLANCluster == clusterName or lowPowerCluster == clusterName: + clusterName = "Media Cluster" + + elif laundryModeCluster == clusterName: + clusterName = "Laundry Washer Mode Cluster" + elif nodeOperationalCredentialsCluster == clusterName or basicInformationCluster == clusterName or networkCommissioningCluster == clusterName: clusterName = clusterName.replace("Cluster", "").strip() @@ -255,9 +272,14 @@ async def DeviceMapping(devCtrl, nodeID, outputPathStr): acceptedCommandListPicsList = [] generatedCommandListPicsList = [] - clusterClass = getattr(Clusters, devCtrl.GetClusterHandler().GetClusterInfoById(server)['clusterName']) clusterID = f"0x{server:04x}" + try: + clusterClass = getattr(Clusters, devCtrl.GetClusterHandler().GetClusterInfoById(server)['clusterName']) + except AttributeError: + console.print(f"[red]Cluster class not found for ({clusterID}) not found! ❌") + continue + # Does the clusterInfoDict contain the found cluster ID? if clusterID not in clusterInfoDict: console.print(f"[red]Cluster ID ({clusterID}) not in list! ❌") @@ -372,6 +394,7 @@ def cleanDirectory(pathToClean): baseOutputPathStr = args.pics_output if not baseOutputPathStr.endswith('/'): baseOutputPathStr += '/' +outputPathStr = baseOutputPathStr + "GeneratedPICS/" serverTag = ".S" clientTag = ".C" @@ -421,13 +444,15 @@ def cleanDirectory(pathToClean): xmlFileList = os.listdir(xmlTemplatePathStr) # Setup output path -baseOutputPath = pathlib.Path(baseOutputPathStr) -if not baseOutputPath.exists(): +print(outputPathStr) + +outputPath = pathlib.Path(outputPathStr) +if not outputPath.exists(): print("Create output folder") - baseOutputPath.mkdir() + outputPath.mkdir() else: print("Clean output folder") - cleanDirectory(baseOutputPath) + cleanDirectory(outputPath) class DeviceMappingTest(MatterBaseTest): @@ -439,7 +464,7 @@ async def test_device_mapping(self): console = Console() # Run device mapping function - await DeviceMapping(self.default_controller, self.dut_node_id, baseOutputPathStr) + await DeviceMapping(self.default_controller, self.dut_node_id, outputPathStr) if __name__ == "__main__": From 9b7e72d8043a8100f9fd0d0696ae5e25287189b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Josefsen?= Date: Wed, 13 Dec 2023 12:33:54 +0100 Subject: [PATCH 02/17] Minor adjustments to readme to match the proper location of tool --- src/tools/PICS-generator/README.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/tools/PICS-generator/README.md b/src/tools/PICS-generator/README.md index 1f5964d80e9b5e..e9f9381ca41b75 100644 --- a/src/tools/PICS-generator/README.md +++ b/src/tools/PICS-generator/README.md @@ -45,6 +45,11 @@ NOTE: The tool has been verified using V24 PICS (used for Matter 1.2 certification) # How to run +First change the directory to the tool location. + +``` +cd src/tools/PICS-generator/ +``` The tool does, as mentioned above, have external dependencies, these are provided to the tool using these arguments: @@ -59,7 +64,7 @@ If the device has not been commissioned this can be done by passing in the commissioning information: ``` -python3 'src/python_testing/PICSGenerator.py' --cluster-data --pics-template --pics-output --commissioning-method ble-thread --discriminator --passcode --thread-dataset-hex +python3 PICSGenerator.py --cluster-data --pics-template --pics-output --commissioning-method ble-thread --discriminator --passcode --thread-dataset-hex ``` In case the device uses a development PAA, the following parameter should be @@ -79,5 +84,5 @@ added. If a device has already been commissioned, the tool can be executed like this: ``` -python3 'src/python_testing/PICSGenerator.py' --cluster-data --pics-template --pics-output +python3 PICSGenerator.py --cluster-data --pics-template --pics-output ``` From 48e4d9a07f1a9725d7af877603bc8fc566dea42c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Josefsen?= Date: Wed, 13 Dec 2023 12:34:48 +0100 Subject: [PATCH 03/17] Fix restyle --- src/tools/PICS-generator/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/src/tools/PICS-generator/README.md b/src/tools/PICS-generator/README.md index e9f9381ca41b75..d9a4d56ba037aa 100644 --- a/src/tools/PICS-generator/README.md +++ b/src/tools/PICS-generator/README.md @@ -45,6 +45,7 @@ NOTE: The tool has been verified using V24 PICS (used for Matter 1.2 certification) # How to run + First change the directory to the tool location. ``` From f9f66858a5a2b4105a48db267b3276003cfc9d6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Josefsen?= Date: Wed, 13 Dec 2023 12:47:26 +0100 Subject: [PATCH 04/17] Reverted laundry mode due to rename revert --- src/tools/PICS-generator/PICSGenerator.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/tools/PICS-generator/PICSGenerator.py b/src/tools/PICS-generator/PICSGenerator.py index 1f3c75fa282150..1dfaa3890a0bd1 100644 --- a/src/tools/PICS-generator/PICSGenerator.py +++ b/src/tools/PICS-generator/PICSGenerator.py @@ -51,7 +51,6 @@ def GenerateDevicePicsXmlFiles(clusterName, clusterPicsCode, featurePicsList, at thermostatUserInterfaceConfigurationCluster = "Thermostat User Interface Configuration Cluster" wakeOnLANCluster = "Wake On LAN Cluster" lowPowerCluster = "Low Power Cluster" - laundryModeCluster = "Laundry Mode Cluster" if otaProviderCluster in clusterName or otaRequestorCluster in clusterName: clusterName = "OTA Software Update" From 4274cb33006946f2a8b97479aaa0e673fc36d31c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Josefsen?= Date: Wed, 13 Dec 2023 12:54:06 +0100 Subject: [PATCH 05/17] Fully remove laundry mode cluster handling --- src/tools/PICS-generator/PICSGenerator.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/tools/PICS-generator/PICSGenerator.py b/src/tools/PICS-generator/PICSGenerator.py index 1dfaa3890a0bd1..2b646623ca1d13 100644 --- a/src/tools/PICS-generator/PICSGenerator.py +++ b/src/tools/PICS-generator/PICSGenerator.py @@ -70,9 +70,6 @@ def GenerateDevicePicsXmlFiles(clusterName, clusterPicsCode, featurePicsList, at elif wakeOnLANCluster == clusterName or lowPowerCluster == clusterName: clusterName = "Media Cluster" - elif laundryModeCluster == clusterName: - clusterName = "Laundry Washer Mode Cluster" - elif nodeOperationalCredentialsCluster == clusterName or basicInformationCluster == clusterName or networkCommissioningCluster == clusterName: clusterName = clusterName.replace("Cluster", "").strip() From 9c95cba9250936a650bffddb488eb0fc36bf6616 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Josefsen?= Date: Wed, 10 Jan 2024 19:40:27 +0100 Subject: [PATCH 06/17] Use DM XML for PICS code rather than cluster data file --- src/python_testing/spec_parsing_support.py | 34 +++++++- src/tools/PICS-generator/PICSGenerator.py | 94 ++++++++++------------ src/tools/PICS-generator/README.md | 13 +-- 3 files changed, 74 insertions(+), 67 deletions(-) diff --git a/src/python_testing/spec_parsing_support.py b/src/python_testing/spec_parsing_support.py index 368b25ff77865e..0f94635d19de68 100644 --- a/src/python_testing/spec_parsing_support.py +++ b/src/python_testing/spec_parsing_support.py @@ -100,6 +100,7 @@ class XmlCluster: accepted_commands: dict[uint, XmlCommand] generated_commands: dict[uint, XmlCommand] events: dict[uint, XmlEvent] + pics: str class CommandType(Enum): @@ -124,6 +125,12 @@ def __init__(self, cluster, cluster_id, name, is_alias): except (KeyError, StopIteration): self._derived = None + try: + classification = next(cluster.iter('classification')) + self._pics = classification.attrib['picsCode'] + except (KeyError, StopIteration): + self._pics = None + self.feature_elements = self.get_all_feature_elements() self.attribute_elements = self.get_all_attribute_elements() self.command_elements = self.get_all_command_elements() @@ -348,7 +355,7 @@ def create_cluster(self) -> XmlCluster: attributes=self.parse_attributes(), accepted_commands=self.parse_commands(CommandType.ACCEPTED), generated_commands=self.parse_commands(CommandType.GENERATED), - events=self.parse_events()) + events=self.parse_events(), pics=self._pics) def get_problems(self) -> list[ProblemNotice]: return self._problems @@ -482,15 +489,36 @@ def remove_problem(location: typing.Union[CommandPathLocation, FeaturePathLocati new = XmlCluster(revision=c.revision, derived=c.derived, name=c.name, feature_map=feature_map, attribute_map=attribute_map, command_map=command_map, features=features, attributes=attributes, accepted_commands=accepted_commands, - generated_commands=generated_commands, events=events) + generated_commands=generated_commands, events=events, pics=c.pics) clusters[id] = new + # workaround for aliased clusters not appearing in the xml. Remove this once https://github.com/csa-data-model/projects/issues/373 is addressed + conc_clusters = {0x040C: ('Carbon Monoxide Concentration Measurement', 'CMOCONC'), + 0x040D: ('Carbon Dioxide Concentration Measurement', 'CDOCONC'), + 0x0413: ('Nitrogen Dioxide Concentration Measurement', 'NDOCONC'), + 0x0415: ('Ozone Concentration Measurement', 'OZCONC'), + 0x042A: ('PM2.5 Concentration Measurement', 'PMICONC'), + 0x042B: ('Formaldehyde Concentration Measurement', 'FLDCONC'), + 0x042C: ('PM1 Concentration Measurement', 'PMHCONC'), + 0x042D: ('PM10 Concentration Measurement', 'PMKCONC'), + 0x042E: ('Total Volatile Organic Compounds Concentration Measurement', 'TVOCCONC'), + 0x042F: ('Radon Concentration Measurement', 'RNCONC')} + conc_base_name = 'Concentration Measurement Clusters' + resource_clusters = {0x0071: ('HEPA Filter Monitoring', 'HEPAFREMON'), + 0x0072: ('Activated Carbon Filter Monitoring', 'ACFREMON')} + resource_base_name = 'Resource Monitoring Clusters' + water_clusters = {0x0405: ('Relative Humidity Measurement', 'RH'), + 0x0407: ('Leaf Wetness Measurement', '??'), + 0x0408: ('Soil Moisture Measurement', '??')} + water_base_name = 'Water Content Measurement Clusters' + aliases = {conc_base_name: conc_clusters, resource_base_name: resource_clusters, water_base_name: water_clusters} for alias_base_name, aliased_clusters in aliases.items(): - for id, alias_name in aliased_clusters.items(): + for id, (alias_name, pics) in aliased_clusters.items(): base = derived_clusters[alias_base_name] new = deepcopy(base) new.derived = alias_base_name new.name = alias_name + new.pics = pics clusters[id] = new # TODO: All these fixups should be removed BEFORE SVE if at all possible diff --git a/src/tools/PICS-generator/PICSGenerator.py b/src/tools/PICS-generator/PICSGenerator.py index 2b646623ca1d13..15253368147505 100644 --- a/src/tools/PICS-generator/PICSGenerator.py +++ b/src/tools/PICS-generator/PICSGenerator.py @@ -28,8 +28,10 @@ # Add the path to python_testing folder, in order to be able to import from matter_testing_support sys.path.append(os.path.abspath(sys.path[0] + "/../../python_testing")) from matter_testing_support import MatterBaseTest, async_test_body, default_matter_test_main # noqa: E402 +from spec_parsing_support import build_xml_clusters # noqa: E402 console = None +xml_clusters = None def GenerateDevicePicsXmlFiles(clusterName, clusterPicsCode, featurePicsList, attributePicsList, acceptedCommandPicsList, generatedCommandPicsList, outputPathStr): @@ -40,17 +42,22 @@ def GenerateDevicePicsXmlFiles(clusterName, clusterPicsCode, featurePicsList, at print(f"Handling PICS for {clusterName}") # Map clusters to common XML template if needed - otaProviderCluster = "OTA Software Update Provider Cluster" - otaRequestorCluster = "OTA Software Update Requestor Cluster" - onOffCluster = "On/Off Cluster" - groupKeyManagementCluster = "Group Key Management Cluster" - nodeOperationalCredentialsCluster = "Node Operational Credentials Cluster" - basicInformationCluster = "Basic Information Cluster" - networkCommissioningCluster = "Network Commissioning Cluster" - diagnosticLogsCluster = "Diagnostic Logs Cluster" - thermostatUserInterfaceConfigurationCluster = "Thermostat User Interface Configuration Cluster" - wakeOnLANCluster = "Wake On LAN Cluster" - lowPowerCluster = "Low Power Cluster" + # basicInformationCluster = "Basic Information Cluster" + # networkCommissioningCluster = "Network Commissioning Cluster" + + accessControlCluster = "AccessControl" + diagnosticLogsCluster = "Diagnostic Logs" + groupKeyManagementCluster = "GroupKeyManagement" + lowPowerCluster = "Low Power" + onOffCluster = "On/Off" + operationalCredentialsCluster = "Operational Credentials" + otaProviderCluster = "OTA Software Update Provider" + otaRequestorCluster = "OTA Software Update Requestor" + thermostatUserInterfaceConfigurationCluster = "Thermostat User Interface Configuration" + wakeOnLANCluster = "Wake on LAN" + + if accessControlCluster in clusterName: + clusterName = "Access Control" if otaProviderCluster in clusterName or otaRequestorCluster in clusterName: clusterName = "OTA Software Update" @@ -70,8 +77,11 @@ def GenerateDevicePicsXmlFiles(clusterName, clusterPicsCode, featurePicsList, at elif wakeOnLANCluster == clusterName or lowPowerCluster == clusterName: clusterName = "Media Cluster" - elif nodeOperationalCredentialsCluster == clusterName or basicInformationCluster == clusterName or networkCommissioningCluster == clusterName: - clusterName = clusterName.replace("Cluster", "").strip() + elif operationalCredentialsCluster == clusterName: + clusterName = "Node Operational Credentials" + + # elif basicInformationCluster == clusterName or networkCommissioningCluster == clusterName: + # clusterName = clusterName.replace("Cluster", "").strip() # Determine if file has already been handled and use this file for outputFolderFileName in os.listdir(outputPathStr): @@ -276,19 +286,17 @@ async def DeviceMapping(devCtrl, nodeID, outputPathStr): console.print(f"[red]Cluster class not found for ({clusterID}) not found! ❌") continue - # Does the clusterInfoDict contain the found cluster ID? - if clusterID not in clusterInfoDict: - console.print(f"[red]Cluster ID ({clusterID}) not in list! ❌") - continue + # Does the the DM XML contain the found cluster ID? + try: + clusterName = xml_clusters[server].name + clusterPICS = f"{xml_clusters[server].pics}{serverTag}" - clusterName = clusterInfoDict[clusterID]['Name'] - clusterPICS = f"{clusterInfoDict[clusterID]['PICS_Code']}{serverTag}" + except KeyError: + console.print(f"[red]Cluster ({clusterID}) not found in DM XML! ❌") + continue console.print(f"{clusterName} - {clusterPICS}") - # Print PICS for specific server from dict - # console.print(clusterInfoDict[f"0x{server:04x}"]) - # Read feature map featureMapResponse = await devCtrl.ReadAttribute(nodeID, [(endpoint, clusterClass.Attributes.FeatureMap)]) # console.print(f"FeatureMap: {featureMapResponse[endpoint][clusterClass][clusterClass.Attributes.FeatureMap]}") @@ -357,8 +365,14 @@ async def DeviceMapping(devCtrl, nodeID, outputPathStr): for client in clientList: clusterID = f"0x{client:04x}" - clusterName = clusterInfoDict[clusterID]['Name'] - clusterPICS = f"{clusterInfoDict[clusterID]['PICS_Code']}{clientTag}" + + try: + clusterName = xml_clusters[client].name + clusterPICS = f"{xml_clusters[client].pics}{clientTag}" + + except KeyError: + console.print(f"[red]Cluster ({clusterID}) not found in DM XML! ❌") + continue console.print(f"{clusterName} - {clusterPICS}") @@ -375,14 +389,10 @@ def cleanDirectory(pathToClean): parser = argparse.ArgumentParser() -parser.add_argument('--cluster-data', required=True) parser.add_argument('--pics-template', required=True) parser.add_argument('--pics-output', required=True) args, unknown = parser.parse_known_args() -basePath = os.path.dirname(__file__) -clusterInfoInputPathStr = args.cluster_data - xmlTemplatePathStr = args.pics_template if not xmlTemplatePathStr.endswith('/'): xmlTemplatePathStr += '/' @@ -410,31 +420,6 @@ def cleanDirectory(pathToClean): # Endpoint define rootNodeEndpointID = 0 -# Load cluster info -inputJson = {} -clusterInfoDict = {} - -print("Generating cluster data dict from JSON input") - -with open(clusterInfoInputPathStr, 'rb') as clusterInfoInputFile: - clusterInfoJson = json.load(clusterInfoInputFile) - - for cluster in clusterInfoJson: - clusterData = clusterInfoJson[f"{cluster}"]["Data created by Script"] - - try: - # Check if cluster id is a value hex value - clusterIdValid = int(clusterData["Id"].lower(), 16) - - # Add cluster data to dict - clusterInfoDict[clusterData["Id"].lower()] = { - "Name": clusterData["Cluster Name"], - "PICS_Code": clusterData["PICS Code"], - } - - except ValueError: - print(f"Ignore ClusterID: {clusterData['Id']} - {clusterData['Cluster Name']}") - # Load PICS XML templates print("Capture list of PICS XML templates") xmlFileList = os.listdir(xmlTemplatePathStr) @@ -459,6 +444,9 @@ async def test_device_mapping(self): global console console = Console() + global xml_clusters + xml_clusters, problems = build_xml_clusters() + # Run device mapping function await DeviceMapping(self.default_controller, self.dut_node_id, outputPathStr) diff --git a/src/tools/PICS-generator/README.md b/src/tools/PICS-generator/README.md index d9a4d56ba037aa..9c20616bb5bc0a 100644 --- a/src/tools/PICS-generator/README.md +++ b/src/tools/PICS-generator/README.md @@ -30,13 +30,6 @@ Once the python environment is build it can be activated using this command: source out/python_env/bin/activate ``` -The script uses the json based data model in order to convert cluster -identifiers into PICS Codes. The file can be downloaded here: -[https://groups.csa-iot.org/wg/matter-csg/document/27290](https://groups.csa-iot.org/wg/matter-csg/document/27290) - -NOTE: The tool has been verified using the "Specification_version -0.7-spring2024.json" version. - The script uses the PICS XML templates for generate the PICS output. These files can be downloaded here: [https://groups.csa-iot.org/wg/matter-csg/document/26122](https://groups.csa-iot.org/wg/matter-csg/document/26122) @@ -55,8 +48,6 @@ cd src/tools/PICS-generator/ The tool does, as mentioned above, have external dependencies, these are provided to the tool using these arguments: -- --cluster-data is the absolute path to the JSON file containing the cluster - data - --pics-template is the absolute path to the folder containing the PICS templates - --pics-output is the absolute path to the output folder to be used @@ -65,7 +56,7 @@ If the device has not been commissioned this can be done by passing in the commissioning information: ``` -python3 PICSGenerator.py --cluster-data --pics-template --pics-output --commissioning-method ble-thread --discriminator --passcode --thread-dataset-hex +python3 PICSGenerator.py --pics-template --pics-output --commissioning-method ble-thread --discriminator --passcode --thread-dataset-hex ``` In case the device uses a development PAA, the following parameter should be @@ -85,5 +76,5 @@ added. If a device has already been commissioned, the tool can be executed like this: ``` -python3 PICSGenerator.py --cluster-data --pics-template --pics-output +python3 PICSGenerator.py --pics-template --pics-output ``` From f33ea839055b91c3eb5844a9e62cfe858d9582f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Josefsen?= Date: Mon, 15 Jan 2024 22:13:52 +0100 Subject: [PATCH 07/17] Remove unused import --- src/tools/PICS-generator/PICSGenerator.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/tools/PICS-generator/PICSGenerator.py b/src/tools/PICS-generator/PICSGenerator.py index 15253368147505..6d1f9484f5534c 100644 --- a/src/tools/PICS-generator/PICSGenerator.py +++ b/src/tools/PICS-generator/PICSGenerator.py @@ -16,7 +16,6 @@ # import argparse -import json import os import pathlib import sys From 16c5533496e6774f1b738d0877bc102468751288 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Josefsen?= Date: Mon, 15 Jan 2024 22:26:10 +0100 Subject: [PATCH 08/17] Remove uncommented code --- src/tools/PICS-generator/PICSGenerator.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/tools/PICS-generator/PICSGenerator.py b/src/tools/PICS-generator/PICSGenerator.py index 6d1f9484f5534c..c306b4df1b1a4b 100644 --- a/src/tools/PICS-generator/PICSGenerator.py +++ b/src/tools/PICS-generator/PICSGenerator.py @@ -38,12 +38,9 @@ def GenerateDevicePicsXmlFiles(clusterName, clusterPicsCode, featurePicsList, at xmlPath = xmlTemplatePathStr fileName = "" - print(f"Handling PICS for {clusterName}") + console.print(f"Handling PICS for {clusterName}") # Map clusters to common XML template if needed - # basicInformationCluster = "Basic Information Cluster" - # networkCommissioningCluster = "Network Commissioning Cluster" - accessControlCluster = "AccessControl" diagnosticLogsCluster = "Diagnostic Logs" groupKeyManagementCluster = "GroupKeyManagement" @@ -79,9 +76,6 @@ def GenerateDevicePicsXmlFiles(clusterName, clusterPicsCode, featurePicsList, at elif operationalCredentialsCluster == clusterName: clusterName = "Node Operational Credentials" - # elif basicInformationCluster == clusterName or networkCommissioningCluster == clusterName: - # clusterName = clusterName.replace("Cluster", "").strip() - # Determine if file has already been handled and use this file for outputFolderFileName in os.listdir(outputPathStr): if clusterName in outputFolderFileName: From 0664310caa70cea0b7ab45ca785f591803828863 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Josefsen?= Date: Tue, 30 Jan 2024 22:57:49 +0100 Subject: [PATCH 09/17] Update special cluster handling and fixed crash when node is not present in PICS XML --- src/tools/PICS-generator/PICSGenerator.py | 131 +++++++++++----------- 1 file changed, 66 insertions(+), 65 deletions(-) diff --git a/src/tools/PICS-generator/PICSGenerator.py b/src/tools/PICS-generator/PICSGenerator.py index c306b4df1b1a4b..e0c100e0d448dc 100644 --- a/src/tools/PICS-generator/PICSGenerator.py +++ b/src/tools/PICS-generator/PICSGenerator.py @@ -42,20 +42,19 @@ def GenerateDevicePicsXmlFiles(clusterName, clusterPicsCode, featurePicsList, at # Map clusters to common XML template if needed accessControlCluster = "AccessControl" - diagnosticLogsCluster = "Diagnostic Logs" groupKeyManagementCluster = "GroupKeyManagement" lowPowerCluster = "Low Power" onOffCluster = "On/Off" operationalCredentialsCluster = "Operational Credentials" otaProviderCluster = "OTA Software Update Provider" otaRequestorCluster = "OTA Software Update Requestor" - thermostatUserInterfaceConfigurationCluster = "Thermostat User Interface Configuration" + thermostatCluster = "Thermostat" wakeOnLANCluster = "Wake on LAN" if accessControlCluster in clusterName: clusterName = "Access Control" - if otaProviderCluster in clusterName or otaRequestorCluster in clusterName: + elif otaProviderCluster in clusterName or otaRequestorCluster in clusterName: clusterName = "OTA Software Update" elif onOffCluster == clusterName: @@ -64,18 +63,15 @@ def GenerateDevicePicsXmlFiles(clusterName, clusterPicsCode, featurePicsList, at elif groupKeyManagementCluster == clusterName: clusterName = "Group Communication" - elif diagnosticLogsCluster == clusterName: - clusterName = "Diagnostics Logs Cluster" - - elif thermostatUserInterfaceConfigurationCluster == clusterName: - clusterName = "Thermostat User Configuration Cluster" - elif wakeOnLANCluster == clusterName or lowPowerCluster == clusterName: clusterName = "Media Cluster" elif operationalCredentialsCluster == clusterName: clusterName = "Node Operational Credentials" + elif thermostatCluster == clusterName: + clusterName = "Thermostat Cluster" + # Determine if file has already been handled and use this file for outputFolderFileName in os.listdir(outputPathStr): if clusterName in outputFolderFileName: @@ -124,57 +120,61 @@ def GenerateDevicePicsXmlFiles(clusterName, clusterPicsCode, featurePicsList, at # Feature PICS # console.print(featurePicsList) featureNode = root.find("./clusterSide[@type='Server']/features") - for picsItem in featureNode: - itemNumberElement = picsItem.find('itemNumber') + if featureNode is not None: + for picsItem in featureNode: + itemNumberElement = picsItem.find('itemNumber') - console.print(f"Searching for {itemNumberElement.text}") + console.print(f"Searching for {itemNumberElement.text}") - if f"{itemNumberElement.text}" in featurePicsList: - console.print("Found feature PICS value in XML template ✅") - supportElement = picsItem.find('support') - supportElement.text = "true" + if f"{itemNumberElement.text}" in featurePicsList: + console.print("Found feature PICS value in XML template ✅") + supportElement = picsItem.find('support') + supportElement.text = "true" # Attributes PICS # TODO: Only check if list is not empty # console.print(attributePicsList) serverAttributesNode = root.find("./clusterSide[@type='Server']/attributes") - for picsItem in serverAttributesNode: - itemNumberElement = picsItem.find('itemNumber') + if serverAttributesNode is not None: + for picsItem in serverAttributesNode: + itemNumberElement = picsItem.find('itemNumber') - console.print(f"Searching for {itemNumberElement.text}") + console.print(f"Searching for {itemNumberElement.text}") - if f"{itemNumberElement.text}" in attributePicsList: - console.print("Found attribute PICS value in XML template ✅") - supportElement = picsItem.find('support') - supportElement.text = "true" + if f"{itemNumberElement.text}" in attributePicsList: + console.print("Found attribute PICS value in XML template ✅") + supportElement = picsItem.find('support') + supportElement.text = "true" # AcceptedCommandList PICS # TODO: Only check if list is not empty # console.print(acceptedCommandPicsList) serverCommandsReceivedNode = root.find("./clusterSide[@type='Server']/commandsReceived") - for picsItem in serverCommandsReceivedNode: - itemNumberElement = picsItem.find('itemNumber') + if serverCommandsReceivedNode is not None: + for picsItem in serverCommandsReceivedNode: + itemNumberElement = picsItem.find('itemNumber') - console.print(f"Searching for {itemNumberElement.text}") + console.print(f"Searching for {itemNumberElement.text}") - if f"{itemNumberElement.text}" in acceptedCommandPicsList: - console.print("Found acceptedCommand PICS value in XML template ✅") - supportElement = picsItem.find('support') - supportElement.text = "true" + if f"{itemNumberElement.text}" in acceptedCommandPicsList: + console.print("Found acceptedCommand PICS value in XML template ✅") + supportElement = picsItem.find('support') + supportElement.text = "true" # GeneratedCommandList PICS # console.print(generatedCommandPicsList) # TODO: Only check if list is not empty serverCommandsGeneratedNode = root.find("./clusterSide[@type='Server']/commandsGenerated") - for picsItem in serverCommandsGeneratedNode: - itemNumberElement = picsItem.find('itemNumber') + if serverCommandsGeneratedNode is not None: + for picsItem in serverCommandsGeneratedNode: + itemNumberElement = picsItem.find('itemNumber') - console.print(f"Searching for {itemNumberElement.text}") + console.print(f"Searching for {itemNumberElement.text}") - if f"{itemNumberElement.text}" in generatedCommandPicsList: - console.print("Found generatedCommand PICS value in XML template ✅") - supportElement = picsItem.find('support') - supportElement.text = "true" + if f"{itemNumberElement.text}" in generatedCommandPicsList: + console.print("Found generatedCommand PICS value in XML template ✅") + supportElement = picsItem.find('support') + supportElement.text = "true" # Event PICS (Work in progress) # The ability to set event PICS is fairly limited, due to EventList not being supported, @@ -184,35 +184,36 @@ def GenerateDevicePicsXmlFiles(clusterName, clusterPicsCode, featurePicsList, at # 1) Event is mandatody # 2) The event is mandatory based on a feature that is supported (Cross check against feature list) (Not supported yet) serverEventsNode = root.find("./clusterSide[@type='Server']/events") - for picsItem in serverEventsNode: - itemNumberElement = picsItem.find('itemNumber') - statusElement = picsItem.find('status') - - try: - condition = statusElement.attrib['cond'] - console.print(f"Checking {itemNumberElement.text} with conformance {statusElement.text} and condition {condition}") - except ET.ParseError: - condition = "" - console.print(f"Checking {itemNumberElement.text} with conformance {statusElement.text}") - - if statusElement.text == "M": + if serverEventsNode is not None: + for picsItem in serverEventsNode: + itemNumberElement = picsItem.find('itemNumber') + statusElement = picsItem.find('status') - # Is event mandated by the server - if condition == clusterPicsCode: - console.print("Found event mandated by server ✅") - supportElement = picsItem.find('support') - supportElement.text = "true" - continue - - if condition in featurePicsList: - console.print("Found event mandated by feature ✅") - supportElement = picsItem.find('support') - supportElement.text = "true" - continue - - if condition == "": - console.print("Event is mandated without a condition ✅") - continue + try: + condition = statusElement.attrib['cond'] + console.print(f"Checking {itemNumberElement.text} with conformance {statusElement.text} and condition {condition}") + except ET.ParseError: + condition = "" + console.print(f"Checking {itemNumberElement.text} with conformance {statusElement.text}") + + if statusElement.text == "M": + + # Is event mandated by the server + if condition == clusterPicsCode: + console.print("Found event mandated by server ✅") + supportElement = picsItem.find('support') + supportElement.text = "true" + continue + + if condition in featurePicsList: + console.print("Found event mandated by feature ✅") + supportElement = picsItem.find('support') + supportElement.text = "true" + continue + + if condition == "": + console.print("Event is mandated without a condition ✅") + continue # Grabbing the header from the XML templates inputFile = open(f"{xmlPath}{fileName}", "r") From bca72fea41c373aa7393ac63fcf7410f445bb6a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Josefsen?= Date: Tue, 30 Jan 2024 23:27:54 +0100 Subject: [PATCH 10/17] Added special handling for Laundry clusters --- src/tools/PICS-generator/PICSGenerator.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/tools/PICS-generator/PICSGenerator.py b/src/tools/PICS-generator/PICSGenerator.py index e0c100e0d448dc..ecbe85f7b0363f 100644 --- a/src/tools/PICS-generator/PICSGenerator.py +++ b/src/tools/PICS-generator/PICSGenerator.py @@ -43,6 +43,8 @@ def GenerateDevicePicsXmlFiles(clusterName, clusterPicsCode, featurePicsList, at # Map clusters to common XML template if needed accessControlCluster = "AccessControl" groupKeyManagementCluster = "GroupKeyManagement" + laundryDryerControls = "Laundry Dryer Controls" + laundryWasherControls = "Laundry Washer Controls" lowPowerCluster = "Low Power" onOffCluster = "On/Off" operationalCredentialsCluster = "Operational Credentials" @@ -72,6 +74,12 @@ def GenerateDevicePicsXmlFiles(clusterName, clusterPicsCode, featurePicsList, at elif thermostatCluster == clusterName: clusterName = "Thermostat Cluster" + elif laundryDryerControls == clusterName: + clusterName = "Dryer Controls" + + elif laundryWasherControls == clusterName: + clusterName = "Washer Controls" + # Determine if file has already been handled and use this file for outputFolderFileName in os.listdir(outputPathStr): if clusterName in outputFolderFileName: From c851c011c8220ce3391b71b396e048ba396e7c3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Josefsen?= Date: Wed, 31 Jan 2024 12:46:54 +0100 Subject: [PATCH 11/17] Improved handling for non standard clusters --- src/tools/PICS-generator/PICSGenerator.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/tools/PICS-generator/PICSGenerator.py b/src/tools/PICS-generator/PICSGenerator.py index ecbe85f7b0363f..801eff65bc4831 100644 --- a/src/tools/PICS-generator/PICSGenerator.py +++ b/src/tools/PICS-generator/PICSGenerator.py @@ -282,6 +282,10 @@ async def DeviceMapping(devCtrl, nodeID, outputPathStr): clusterID = f"0x{server:04x}" + if server > 0x7FFF: + console.print(f"[red]Cluster outside standard range ({clusterID}) not handled! ❌") + continue + try: clusterClass = getattr(Clusters, devCtrl.GetClusterHandler().GetClusterInfoById(server)['clusterName']) except AttributeError: From dde76195743261fe4428c21e37374540e3c9aa3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Josefsen?= Date: Mon, 5 Feb 2024 17:56:50 +0100 Subject: [PATCH 12/17] Updated handling for special cluster names --- src/tools/PICS-generator/PICSGenerator.py | 38 +++++++++-------------- 1 file changed, 15 insertions(+), 23 deletions(-) diff --git a/src/tools/PICS-generator/PICSGenerator.py b/src/tools/PICS-generator/PICSGenerator.py index 801eff65bc4831..019cebd630f75c 100644 --- a/src/tools/PICS-generator/PICSGenerator.py +++ b/src/tools/PICS-generator/PICSGenerator.py @@ -41,45 +41,37 @@ def GenerateDevicePicsXmlFiles(clusterName, clusterPicsCode, featurePicsList, at console.print(f"Handling PICS for {clusterName}") # Map clusters to common XML template if needed - accessControlCluster = "AccessControl" - groupKeyManagementCluster = "GroupKeyManagement" - laundryDryerControls = "Laundry Dryer Controls" - laundryWasherControls = "Laundry Washer Controls" - lowPowerCluster = "Low Power" - onOffCluster = "On/Off" - operationalCredentialsCluster = "Operational Credentials" - otaProviderCluster = "OTA Software Update Provider" - otaRequestorCluster = "OTA Software Update Requestor" - thermostatCluster = "Thermostat" - wakeOnLANCluster = "Wake on LAN" - - if accessControlCluster in clusterName: + if "AccessControl" in clusterName: clusterName = "Access Control" - elif otaProviderCluster in clusterName or otaRequestorCluster in clusterName: + elif "OTA Software Update Provider" in clusterName or "OTA Software Update Requestor" in clusterName: clusterName = "OTA Software Update" - elif onOffCluster == clusterName: + elif "On/Off" == clusterName: clusterName = clusterName.replace("/", "-") - elif groupKeyManagementCluster == clusterName: + elif "GroupKeyManagement" == clusterName: clusterName = "Group Communication" - elif wakeOnLANCluster == clusterName or lowPowerCluster == clusterName: + elif "Wake on LAN" == clusterName or "Low Power" == clusterName: clusterName = "Media Cluster" - elif operationalCredentialsCluster == clusterName: + elif "Operational Credentials" == clusterName: clusterName = "Node Operational Credentials" - elif thermostatCluster == clusterName: - clusterName = "Thermostat Cluster" - - elif laundryDryerControls == clusterName: + elif "Laundry Dryer Controls" == clusterName: clusterName = "Dryer Controls" - elif laundryWasherControls == clusterName: + elif "Laundry Washer Controls" == clusterName: clusterName = "Washer Controls" + # Workaround for naming colisions with current logic + elif "Thermostat" == clusterName: + clusterName = "Thermostat Cluster" + + elif "Boolean State" == clusterName: + clusterName = "Boolean State Cluster" + # Determine if file has already been handled and use this file for outputFolderFileName in os.listdir(outputPathStr): if clusterName in outputFolderFileName: From c87442378d5ad0c892fbb5f81a96056d8d76c019 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Josefsen?= Date: Thu, 8 Feb 2024 10:14:59 +0100 Subject: [PATCH 13/17] Updated Access Control handling --- src/tools/PICS-generator/PICSGenerator.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/tools/PICS-generator/PICSGenerator.py b/src/tools/PICS-generator/PICSGenerator.py index 019cebd630f75c..6d87620d2671eb 100644 --- a/src/tools/PICS-generator/PICSGenerator.py +++ b/src/tools/PICS-generator/PICSGenerator.py @@ -41,10 +41,7 @@ def GenerateDevicePicsXmlFiles(clusterName, clusterPicsCode, featurePicsList, at console.print(f"Handling PICS for {clusterName}") # Map clusters to common XML template if needed - if "AccessControl" in clusterName: - clusterName = "Access Control" - - elif "OTA Software Update Provider" in clusterName or "OTA Software Update Requestor" in clusterName: + if "OTA Software Update Provider" in clusterName or "OTA Software Update Requestor" in clusterName: clusterName = "OTA Software Update" elif "On/Off" == clusterName: @@ -72,6 +69,9 @@ def GenerateDevicePicsXmlFiles(clusterName, clusterPicsCode, featurePicsList, at elif "Boolean State" == clusterName: clusterName = "Boolean State Cluster" + if "Access Control" in clusterName: + clusterName = "Access Control cluster" + # Determine if file has already been handled and use this file for outputFolderFileName in os.listdir(outputPathStr): if clusterName in outputFolderFileName: From 8e557791378e3e3620312cc9e4863ca70eaa591b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Josefsen?= Date: Thu, 8 Feb 2024 17:29:15 +0100 Subject: [PATCH 14/17] Remove space in AccessControl --- src/tools/PICS-generator/PICSGenerator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/PICS-generator/PICSGenerator.py b/src/tools/PICS-generator/PICSGenerator.py index 6d87620d2671eb..d4a38d64f6c775 100644 --- a/src/tools/PICS-generator/PICSGenerator.py +++ b/src/tools/PICS-generator/PICSGenerator.py @@ -69,7 +69,7 @@ def GenerateDevicePicsXmlFiles(clusterName, clusterPicsCode, featurePicsList, at elif "Boolean State" == clusterName: clusterName = "Boolean State Cluster" - if "Access Control" in clusterName: + if "AccessControl" in clusterName: clusterName = "Access Control cluster" # Determine if file has already been handled and use this file From 50ccb29d08e0ec93b623bbe94c8465a06f518e5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Josefsen?= Date: Wed, 14 Feb 2024 13:28:34 +0100 Subject: [PATCH 15/17] Added handling for ICDManagement --- src/tools/PICS-generator/PICSGenerator.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/tools/PICS-generator/PICSGenerator.py b/src/tools/PICS-generator/PICSGenerator.py index d4a38d64f6c775..92aac1e1310e37 100644 --- a/src/tools/PICS-generator/PICSGenerator.py +++ b/src/tools/PICS-generator/PICSGenerator.py @@ -41,7 +41,10 @@ def GenerateDevicePicsXmlFiles(clusterName, clusterPicsCode, featurePicsList, at console.print(f"Handling PICS for {clusterName}") # Map clusters to common XML template if needed - if "OTA Software Update Provider" in clusterName or "OTA Software Update Requestor" in clusterName: + if "ICDManagement" == clusterName + clusterName = "ICD Management" + + elif "OTA Software Update Provider" in clusterName or "OTA Software Update Requestor" in clusterName: clusterName = "OTA Software Update" elif "On/Off" == clusterName: From c083d962b7bb5feb7aaee05ff19936d145f08b78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Josefsen?= Date: Wed, 28 Feb 2024 14:58:42 +0100 Subject: [PATCH 16/17] Fix bug in if statement syntax --- src/tools/PICS-generator/PICSGenerator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/PICS-generator/PICSGenerator.py b/src/tools/PICS-generator/PICSGenerator.py index 92aac1e1310e37..763579b79371b4 100644 --- a/src/tools/PICS-generator/PICSGenerator.py +++ b/src/tools/PICS-generator/PICSGenerator.py @@ -41,7 +41,7 @@ def GenerateDevicePicsXmlFiles(clusterName, clusterPicsCode, featurePicsList, at console.print(f"Handling PICS for {clusterName}") # Map clusters to common XML template if needed - if "ICDManagement" == clusterName + if "ICDManagement" == clusterName: clusterName = "ICD Management" elif "OTA Software Update Provider" in clusterName or "OTA Software Update Requestor" in clusterName: From 5e2d169838457b2ead34040bcf37420196b02c99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Josefsen?= Date: Wed, 28 Feb 2024 16:08:33 +0100 Subject: [PATCH 17/17] Minor updates to match spring 2024 SVE PICS --- src/tools/PICS-generator/PICSGenerator.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/tools/PICS-generator/PICSGenerator.py b/src/tools/PICS-generator/PICSGenerator.py index 763579b79371b4..2a3f3613274245 100644 --- a/src/tools/PICS-generator/PICSGenerator.py +++ b/src/tools/PICS-generator/PICSGenerator.py @@ -50,18 +50,15 @@ def GenerateDevicePicsXmlFiles(clusterName, clusterPicsCode, featurePicsList, at elif "On/Off" == clusterName: clusterName = clusterName.replace("/", "-") - elif "GroupKeyManagement" == clusterName: + elif "Group Key Management" == clusterName: clusterName = "Group Communication" - elif "Wake on LAN" == clusterName or "Low Power" == clusterName: + elif "Wake On LAN" == clusterName or "Low Power" == clusterName: clusterName = "Media Cluster" elif "Operational Credentials" == clusterName: clusterName = "Node Operational Credentials" - elif "Laundry Dryer Controls" == clusterName: - clusterName = "Dryer Controls" - elif "Laundry Washer Controls" == clusterName: clusterName = "Washer Controls"