Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 76 additions & 3 deletions scripts/west/zap_append.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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

Expand Down
41 changes: 41 additions & 0 deletions scripts/west/zap_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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'
Expand Down
11 changes: 10 additions & 1 deletion scripts/west/zap_generate.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__)))
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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)
Expand Down
Loading