|
| 1 | +# |
| 2 | +# Copyright (c) 2023 Project CHIP Authors |
| 3 | +# All rights reserved. |
| 4 | +# |
| 5 | +# Licensed under the Apache License, Version 2.0 (the "License"); |
| 6 | +# you may not use this file except in compliance with the License. |
| 7 | +# You may obtain a copy of the License at |
| 8 | +# |
| 9 | +# http://www.apache.org/licenses/LICENSE-2.0 |
| 10 | +# |
| 11 | +# Unless required by applicable law or agreed to in writing, software |
| 12 | +# distributed under the License is distributed on an "AS IS" BASIS, |
| 13 | +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 14 | +# See the License for the specific language governing permissions and |
| 15 | +# limitations under the License. |
| 16 | +# |
| 17 | + |
| 18 | +import os |
| 19 | +import pprint |
| 20 | +import sys |
| 21 | + |
| 22 | +import chip.clusters as Clusters |
| 23 | +import graphviz |
| 24 | +from rich.console import Console |
| 25 | + |
| 26 | +# Add the path to python_testing folder, in order to be able to import from matter_testing_support |
| 27 | +sys.path.append(os.path.abspath(sys.path[0] + "/../../python_testing")) |
| 28 | +from matter_testing_support import MatterBaseTest, async_test_body, default_matter_test_main # noqa: E402 |
| 29 | + |
| 30 | +console = None |
| 31 | +maxClusterNameLength = 30 |
| 32 | + |
| 33 | + |
| 34 | +# Given there is currently no tranlation from DeviceTypeID to the device type name, |
| 35 | +# this dict is created for now. When some more general is available, it should be updated to use this. |
| 36 | +deviceTypeDict = { |
| 37 | + 22: "Root Node", |
| 38 | + 17: "Power Source", |
| 39 | + 18: "OTA Requestor", |
| 40 | + 20: "OTA Provider", |
| 41 | + 14: "Aggregator", |
| 42 | + 19: "Bridged Node", |
| 43 | + 256: "On/Off Light", |
| 44 | + 257: "Dimmable Light", |
| 45 | + 268: "Color Temperature Light", |
| 46 | + 269: "Extended Color Light", |
| 47 | + 266: "On/Off Plug-in Unit", |
| 48 | + 267: "Dimmable Plug-In Unit", |
| 49 | + 771: "Pump", |
| 50 | + 259: "On/Off Light Switch", |
| 51 | + 260: "Dimmer Switch", |
| 52 | + 261: "Color Dimmer Switch", |
| 53 | + 2112: "Control Bridge", |
| 54 | + 772: "Pump Controller", |
| 55 | + 15: "Generic Switch", |
| 56 | + 21: "Contact Sensor", |
| 57 | + 262: "Light Sensor", |
| 58 | + 263: "Occupancy Sensor", |
| 59 | + 770: "Temperature Sensor", |
| 60 | + 773: "Pressure Sensor", |
| 61 | + 774: "Flow Sensor", |
| 62 | + 775: "Humidity Sensor", |
| 63 | + 2128: "On/Off Sensor", |
| 64 | + 10: "Door Lock", |
| 65 | + 11: "Door Lock Controller", |
| 66 | + 514: "Window Covering", |
| 67 | + 515: "Window Covering Controller", |
| 68 | + 768: "Heating/Cooling Unit", |
| 69 | + 769: "Thermostat", |
| 70 | + 43: "Fan", |
| 71 | + 35: "Casting Video Player", |
| 72 | + 34: "Speaker", |
| 73 | + 36: "Content App", |
| 74 | + 40: "Basic Video Player", |
| 75 | + 41: "Casting Video Client", |
| 76 | + 42: "Video Remote Control", |
| 77 | + 39: "Mode Select", |
| 78 | + 45: "Air Purifier", |
| 79 | + 44: "Air Quality Sensor", |
| 80 | + 112: "Refrigerator", |
| 81 | + 113: "Temperature Controlled Cabinet", |
| 82 | + 114: "Room Air Conditioner", |
| 83 | + 115: "Laundry Washer", |
| 84 | + 116: "Robotic Vacuum Cleaner", |
| 85 | + 117: "Dishwasher", |
| 86 | + 118: "Smoke CO Alarm" |
| 87 | +} |
| 88 | + |
| 89 | + |
| 90 | +def AddServerOrClientNode(graphSection, endpoint, clusterName, color, nodeRef): |
| 91 | + |
| 92 | + if (len(clusterName) > maxClusterNameLength): |
| 93 | + clusterNameAdjustedLength = clusterName[:maxClusterNameLength] + '...' |
| 94 | + else: |
| 95 | + clusterNameAdjustedLength = clusterName |
| 96 | + |
| 97 | + graphSection.node(f"ep{endpoint}_{clusterName}", label=f"{clusterNameAdjustedLength}", style="filled,rounded", |
| 98 | + color=color, shape="box", fixedsize="true", width="3", height="0.5") |
| 99 | + graphSection.edge(nodeRef, f"ep{endpoint}_{clusterName}", style="invis") |
| 100 | + |
| 101 | + |
| 102 | +def CreateEndpointGraph(graph, graphSection, endpoint, wildcardResponse): |
| 103 | + |
| 104 | + numberOfRowsInEndpoint = 2 |
| 105 | + |
| 106 | + partsListFromWildcardRead = wildcardResponse[endpoint][Clusters.Objects.Descriptor][Clusters.Objects.Descriptor.Attributes.PartsList] |
| 107 | + |
| 108 | + listOfDeviceTypes = [] |
| 109 | + for deviceTypeStruct in wildcardResponse[endpoint][Clusters.Objects.Descriptor][Clusters.Objects.Descriptor.Attributes.DeviceTypeList]: |
| 110 | + try: |
| 111 | + listOfDeviceTypes.append(deviceTypeDict[deviceTypeStruct.deviceType]) |
| 112 | + except KeyError: |
| 113 | + listOfDeviceTypes.append(deviceTypeStruct.deviceType) |
| 114 | + |
| 115 | + # console.print(f"Endpoint: {endpoint}") |
| 116 | + # console.print(f"DeviceTypeList: {listOfDeviceTypes}") |
| 117 | + # console.print(f"PartsList: {partsListFromWildcardRead}") |
| 118 | + |
| 119 | + endpointLabel = f"Endpoint: {endpoint}\lDeviceTypeList: {listOfDeviceTypes}\lPartsList: {partsListFromWildcardRead}\l" # noqa: W605 |
| 120 | + |
| 121 | + nextNodeRef = "" |
| 122 | + nodeRef = f"ep{endpoint}" |
| 123 | + clusterColumnCount = 0 |
| 124 | + |
| 125 | + graphSection.node(f"ep{endpoint}", label=endpointLabel, style="filled,rounded", |
| 126 | + color="dodgerblue", shape="box", fixedsize="true", width="4", height="1") |
| 127 | + |
| 128 | + for clusterId in wildcardResponse[endpoint][Clusters.Objects.Descriptor][Clusters.Objects.Descriptor.Attributes.ServerList]: |
| 129 | + clusterColumnCount += 1 |
| 130 | + |
| 131 | + try: |
| 132 | + clusterName = Clusters.ClusterObjects.ALL_CLUSTERS[clusterId].__name__ |
| 133 | + except KeyError: |
| 134 | + clusterName = f"Custom server\l0x{clusterId:08X}" # noqa: W605 |
| 135 | + |
| 136 | + AddServerOrClientNode(graphSection, endpoint, clusterName, "olivedrab", nodeRef) |
| 137 | + |
| 138 | + if clusterColumnCount == 2: |
| 139 | + nextNodeRef = f"ep{endpoint}_{clusterName}" |
| 140 | + elif clusterColumnCount == 3: |
| 141 | + nodeRef = nextNodeRef |
| 142 | + clusterColumnCount = 0 |
| 143 | + numberOfRowsInEndpoint += 1 |
| 144 | + |
| 145 | + for clusterId in wildcardResponse[endpoint][Clusters.Objects.Descriptor][Clusters.Objects.Descriptor.Attributes.ClientList]: |
| 146 | + clusterColumnCount += 1 |
| 147 | + |
| 148 | + try: |
| 149 | + clusterName = Clusters.ClusterObjects.ALL_CLUSTERS[clusterId].__name__ |
| 150 | + except KeyError: |
| 151 | + clusterName = f"Custom client\l0x{clusterId:08X}" # noqa: W605 |
| 152 | + |
| 153 | + AddServerOrClientNode(graphSection, endpoint, clusterName, "orange", nodeRef) |
| 154 | + |
| 155 | + if clusterColumnCount == 2: |
| 156 | + nextNodeRef = f"ep{endpoint}_{clusterName}" |
| 157 | + elif clusterColumnCount == 3: |
| 158 | + nodeRef = nextNodeRef |
| 159 | + clusterColumnCount = 0 |
| 160 | + numberOfRowsInEndpoint += 1 |
| 161 | + |
| 162 | + if endpoint != 0: |
| 163 | + # Create link to endpoints in the parts list |
| 164 | + for part in partsListFromWildcardRead: |
| 165 | + graph.edge(f"ep{endpoint}", f"ep{part}", ltail=f"cluster_{endpoint}", minlen=f"{numberOfRowsInEndpoint}") |
| 166 | + |
| 167 | + |
| 168 | +class TC_MatterDeviceGraph(MatterBaseTest): |
| 169 | + @async_test_body |
| 170 | + async def test_matter_device_graph(self): |
| 171 | + |
| 172 | + # Create console to print |
| 173 | + global console |
| 174 | + console = Console() |
| 175 | + |
| 176 | + # Run descriptor validation test |
| 177 | + dev_ctrl = self.default_controller |
| 178 | + |
| 179 | + # Perform wildcard read to get all attributes from device |
| 180 | + console.print("[blue]Capturing data from device") |
| 181 | + wildcardResponse = await dev_ctrl.ReadAttribute(self.dut_node_id, [('*')]) |
| 182 | + # console.print(wildcardResponse) |
| 183 | + |
| 184 | + # Creating graph object |
| 185 | + deviceGraph = graphviz.Digraph() |
| 186 | + deviceGraph.attr(style="rounded", splines="line", compound="true") |
| 187 | + |
| 188 | + console.print("[blue]Generating graph") |
| 189 | + # Loop through each endpoint in the response from the wildcard read |
| 190 | + for endpoint in wildcardResponse: |
| 191 | + |
| 192 | + if endpoint == 0: |
| 193 | + with deviceGraph.subgraph(name='cluster_rootnode') as rootNodeSection: |
| 194 | + CreateEndpointGraph(deviceGraph, rootNodeSection, endpoint, wildcardResponse) |
| 195 | + else: |
| 196 | + with deviceGraph.subgraph(name='cluster_endpoints') as endpointsSection: |
| 197 | + with endpointsSection.subgraph(name=f'cluster_{endpoint}') as endpointSection: |
| 198 | + CreateEndpointGraph(deviceGraph, endpointSection, endpoint, wildcardResponse) |
| 199 | + |
| 200 | + deviceGraph.save(f'{sys.path[0]}/matter-device-graph.dot') |
| 201 | + |
| 202 | + deviceDataFile = open(f'{sys.path[0]}/matter-device-data.txt', 'w') |
| 203 | + deviceDataFile.write(pprint.pformat((wildcardResponse))) |
| 204 | + deviceDataFile.close() |
| 205 | + |
| 206 | + |
| 207 | +if __name__ == "__main__": |
| 208 | + default_matter_test_main() |
0 commit comments