Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PICS Generator tool update #30866

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
a036869
Fixed missed cluster renaming and adjusted output folder behavior
ReneJosefsen Dec 7, 2023
d10fc6b
Merge branch 'master' into rjosefsen/PICS-generator-fixes
ReneJosefsen Dec 7, 2023
9b7e72d
Minor adjustments to readme to match the proper location of tool
ReneJosefsen Dec 13, 2023
48e4d9a
Fix restyle
ReneJosefsen Dec 13, 2023
a7d8bc8
Merge branch 'master' into rjosefsen/PICS-generator-fixes
ReneJosefsen Dec 13, 2023
f9f6685
Reverted laundry mode due to rename revert
ReneJosefsen Dec 13, 2023
4274cb3
Fully remove laundry mode cluster handling
ReneJosefsen Dec 13, 2023
6f57ca4
Merge branch 'master' into rjosefsen/PICS-generator-fixes
ReneJosefsen Jan 10, 2024
9c95cba
Use DM XML for PICS code rather than cluster data file
ReneJosefsen Jan 10, 2024
70c2ecc
Merge branch 'master' into rjosefsen/PICS-generator-fixes
ReneJosefsen Jan 15, 2024
f33ea83
Remove unused import
ReneJosefsen Jan 15, 2024
16c5533
Remove uncommented code
ReneJosefsen Jan 15, 2024
123d2eb
Merge branch 'master' into rjosefsen/PICS-generator-fixes
ReneJosefsen Jan 23, 2024
0664310
Update special cluster handling and fixed crash when node is not pres…
ReneJosefsen Jan 30, 2024
bca72fe
Added special handling for Laundry clusters
ReneJosefsen Jan 30, 2024
c851c01
Improved handling for non standard clusters
ReneJosefsen Jan 31, 2024
dde7619
Updated handling for special cluster names
ReneJosefsen Feb 5, 2024
c874423
Updated Access Control handling
ReneJosefsen Feb 8, 2024
8e55779
Remove space in AccessControl
ReneJosefsen Feb 8, 2024
50ccb29
Added handling for ICDManagement
ReneJosefsen Feb 14, 2024
6637036
Merge branch 'master' into rjosefsen/PICS-generator-fixes
ReneJosefsen Feb 28, 2024
c083d96
Fix bug in if statement syntax
ReneJosefsen Feb 28, 2024
5e2d169
Minor updates to match spring 2024 SVE PICS
ReneJosefsen Feb 28, 2024
4bb5e1b
Merge branch 'master' into rjosefsen/PICS-generator-fixes
ReneJosefsen Feb 29, 2024
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
241 changes: 124 additions & 117 deletions src/tools/PICS-generator/PICSGenerator.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
#

import argparse
import json
import os
import pathlib
import sys
Expand All @@ -28,37 +27,50 @@
# 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):

xmlPath = xmlTemplatePathStr
fileName = ""

print(f"Handling PICS for {clusterName}")
console.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"

if otaProviderCluster in clusterName or otaRequestorCluster 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 onOffCluster == clusterName:
elif "On/Off" == clusterName:
clusterName = clusterName.replace("/", "-")

elif groupKeyManagementCluster == clusterName:
elif "Group Key Management" == clusterName:
clusterName = "Group Communication"

elif nodeOperationalCredentialsCluster == clusterName or basicInformationCluster == clusterName or networkCommissioningCluster == clusterName:
clusterName = clusterName.replace("Cluster", "").strip()
elif "Wake On LAN" == clusterName or "Low Power" == clusterName:
clusterName = "Media Cluster"

elif "Operational Credentials" == clusterName:
clusterName = "Node Operational Credentials"

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"

if "AccessControl" in clusterName:
clusterName = "Access Control cluster"

# Determine if file has already been handled and use this file
for outputFolderFileName in os.listdir(outputPathStr):
Expand Down Expand Up @@ -108,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,
Expand All @@ -168,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":

# 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
if serverEventsNode is not None:
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":

# 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")
Expand Down Expand Up @@ -255,21 +272,28 @@ async def DeviceMapping(devCtrl, nodeID, outputPathStr):
acceptedCommandListPicsList = []
generatedCommandListPicsList = []

clusterClass = getattr(Clusters, devCtrl.GetClusterHandler().GetClusterInfoById(server)['clusterName'])
clusterID = f"0x{server:04x}"

# Does the clusterInfoDict contain the found cluster ID?
if clusterID not in clusterInfoDict:
console.print(f"[red]Cluster ID ({clusterID}) not in list! ❌")
if server > 0x7FFF:
console.print(f"[red]Cluster outside standard range ({clusterID}) not handled! ❌")
continue

clusterName = clusterInfoDict[clusterID]['Name']
clusterPICS = f"{clusterInfoDict[clusterID]['PICS_Code']}{serverTag}"
try:
clusterClass = getattr(Clusters, devCtrl.GetClusterHandler().GetClusterInfoById(server)['clusterName'])
except AttributeError:
console.print(f"[red]Cluster class not found for ({clusterID}) not found! ❌")
continue

console.print(f"{clusterName} - {clusterPICS}")
# Does the the DM XML contain the found cluster ID?
try:
clusterName = xml_clusters[server].name
clusterPICS = f"{xml_clusters[server].pics}{serverTag}"

except KeyError:
console.print(f"[red]Cluster ({clusterID}) not found in DM XML! ❌")
continue

# Print PICS for specific server from dict
# console.print(clusterInfoDict[f"0x{server:04x}"])
console.print(f"{clusterName} - {clusterPICS}")

# Read feature map
featureMapResponse = await devCtrl.ReadAttribute(nodeID, [(endpoint, clusterClass.Attributes.FeatureMap)])
Expand Down Expand Up @@ -339,8 +363,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}")

Expand All @@ -357,21 +387,18 @@ 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 += '/'

baseOutputPathStr = args.pics_output
if not baseOutputPathStr.endswith('/'):
baseOutputPathStr += '/'
outputPathStr = baseOutputPathStr + "GeneratedPICS/"

serverTag = ".S"
clientTag = ".C"
Expand All @@ -391,43 +418,20 @@ 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)

# 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):
Expand All @@ -438,8 +442,11 @@ 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, baseOutputPathStr)
await DeviceMapping(self.default_controller, self.dut_node_id, outputPathStr)


if __name__ == "__main__":
Expand Down
Loading
Loading