diff --git a/scripts/west/zap_append.py b/scripts/west/zap_append.py index a90f155201..7ba25aa813 100644 --- a/scripts/west/zap_append.py +++ b/scripts/west/zap_append.py @@ -3,13 +3,83 @@ # SPDX-License-Identifier: LicenseRef-Nordic-5-Clause import argparse -from pathlib import Path import json +import xml.etree.ElementTree as ET +from pathlib import Path -from west.commands import WestCommand from west import log +from west.commands import WestCommand +from zap_common import DEFAULT_MATTER_PATH, DEFAULT_MATTER_TYPES_RELATIVE_PATH, DEFAULT_ZCL_JSON_RELATIVE_PATH + -from zap_common import DEFAULT_MATTER_PATH, DEFAULT_ZCL_JSON_RELATIVE_PATH +def add_custom_attributes_from_xml(xml_file: Path, zcl_data: dict): + """ + Parse the cluster XML file and add attributes with custom types to + attributeAccessInterfaceAttributes in zcl_data. + + Args: + cluster_xml_path: Path to the cluster XML file + zcl_data: The loaded zcl.json data dictionary + """ + + # Step 1: Load all type names from chip-types.xml into a list + types = [] + tree = ET.parse(DEFAULT_MATTER_PATH / DEFAULT_MATTER_TYPES_RELATIVE_PATH) + root = tree.getroot() + + for type_element in root.findall('.//type'): + description = type_element.get('name') + if description: + types.append(description) + + # Step 2: Parse the cluster XML file + cluster_tree = ET.parse(xml_file) + cluster_root = cluster_tree.getroot() + + # Find cluster name and attributes with missing types + attributes_with_missing_types = [] + + for cluster in cluster_root.findall('.//cluster'): + cluster_name = cluster.find('name') + if cluster_name is not None: + cluster_name = cluster_name.text + else: + continue + + # Check all attributes in the cluster + for attribute in cluster.findall('attribute'): + attr_type = attribute.get('type') + attr_name = attribute.get('name') + + if attr_type and attr_type not in types: + attributes_with_missing_types.append({ + 'cluster': cluster_name, + 'attribute': attr_name, + 'type': attr_type + }) + + # Step 3: Update zcl_data with missing attributes + if 'attributeAccessInterfaceAttributes' not in zcl_data: + zcl_data['attributeAccessInterfaceAttributes'] = {} + + attr_access_attrs = zcl_data['attributeAccessInterfaceAttributes'] + modified = False + + for attr_info in attributes_with_missing_types: + cluster_name = attr_info['cluster'] + attr_name = attr_info['attribute'] + + if cluster_name not in attr_access_attrs: + attr_access_attrs[cluster_name] = [attr_name] + modified = True + print(f"Added new cluster '{cluster_name}' with attribute '{attr_name}' (type: {attr_info['type']})") + else: + if attr_name not in attr_access_attrs[cluster_name]: + attr_access_attrs[cluster_name].append(attr_name) + modified = True + print(f"Added attribute '{attr_name}' to cluster '{cluster_name}' (type: {attr_info['type']})") + + return modified def add_cluster_to_zcl(zcl_base: Path, cluster_xml_paths: list, output: Path): @@ -64,6 +134,9 @@ def add_cluster_to_zcl(zcl_base: Path, cluster_xml_paths: list, output: Path): zcl_json.get("xmlFile").append(file) log.dbg(f"Successfully added {file}") + # Add custom attributes from the XML file to the ZCL file + add_custom_attributes_from_xml(Path(cluster), zcl_json) + # If output file is not provided, we will edit the existing ZCL file file_to_write = output if output else zcl_base diff --git a/scripts/west/zap_common.py b/scripts/west/zap_common.py index 0eb0b9b07e..902367c6e0 100644 --- a/scripts/west/zap_common.py +++ b/scripts/west/zap_common.py @@ -23,6 +23,7 @@ DEFAULT_MATTER_PATH = Path(__file__).parents[2] DEFAULT_ZCL_JSON_RELATIVE_PATH = Path('src/app/zap-templates/zcl/zcl.json') DEFAULT_APP_TEMPLATES_RELATIVE_PATH = Path('src/app/zap-templates/app-templates.json') +DEFAULT_MATTER_TYPES_RELATIVE_PATH = Path('src/app/zap-templates/zcl/data-model/chip/chip-types.xml') def find_zap(root: Path = Path.cwd(), max_depth: int = 2): @@ -158,6 +159,46 @@ def post_process_generated_files(output_path: Path): continue +def synchronize_zcl_with_base(zcl_json: Path): + """ + Synchronizes a zcl.json file with the base/default zcl.json from Matter SDK. + + This function ensures that all fields present in the default zcl.json are also + present in the target zcl_json file. Missing fields are added with their default + values from the base file. + """ + + print(f"Synchronizing {zcl_json} with base zcl.json") + + base_zcl_path = DEFAULT_MATTER_PATH / DEFAULT_ZCL_JSON_RELATIVE_PATH + with open(base_zcl_path, 'r') as f: + base_zcl_data = json.load(f) + + with open(zcl_json, 'r') as f: + target_zcl_data = json.load(f) + + modified = False + fields_to_skip = {'xmlRoot'} # Fields to skip in comparison + + for key, value in base_zcl_data.items(): + if key in fields_to_skip: + continue + + if key not in target_zcl_data: + target_zcl_data[key] = value + modified = True + print(f"Added missing field: '{key}'") + + if modified: + with open(zcl_json, 'w') as f: + json.dump(target_zcl_data, f, indent=4) + print(f"Updated {zcl_json}") + else: + print("No changes needed - all fields are present") + + print("Done") + + class ZapInstaller: INSTALL_DIR = Path('.zap-install') ZAP_URL_PATTERN = 'https://github.com/project-chip/zap/releases/download/v%04d.%02d.%02d/%s.zip' diff --git a/scripts/west/zap_generate.py b/scripts/west/zap_generate.py index ebd1000fe9..21e21cbd0f 100644 --- a/scripts/west/zap_generate.py +++ b/scripts/west/zap_generate.py @@ -14,7 +14,7 @@ from west import log from west.commands import CommandError, WestCommand from zap_common import (DEFAULT_MATTER_PATH, ZapInstaller, existing_dir_path, existing_file_path, find_zap, - post_process_generated_files) + post_process_generated_files, synchronize_zcl_with_base, update_zcl_in_zap) # fmt: off scripts_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) @@ -161,6 +161,10 @@ def do_run(self, args, unknown_args): # Generate .matter file self.check_call(self.build_command(zap.zap_file, output_path)) + # Update the zcl in zap file if needed + # We need to do this in case the zap gui was not called before. + update_zcl_in_zap(zap.zap_file, zcl_file, app_templates_path) + if args.full: # Full build is about generating an apropertiate Matter data model files in a specific directory layout. # Currently, we must align to the following directory layout: @@ -196,6 +200,11 @@ def do_run(self, args, unknown_args): zap_output_dir = output_path / 'app-common' / 'zap-generated' codegen_output_dir = output_path / 'clusters' + # Synchronize the zcl.json file with the base zcl.json file + # We need to do this to update the zcl.json file with the new clusters and attributes. + # It may be helpful if the Matter SDK was updated. + synchronize_zcl_with_base(zcl_file) + # Temporarily change directory to matter_path so JinjaCodegenTarget and ZAPGenerateTarget can find their scripts original_cwd = os.getcwd() os.chdir(args.matter_path)