diff --git a/integrations/appengine/webapp_config.yaml b/integrations/appengine/webapp_config.yaml index f27ad7d68b8663..84b204611ba08c 100644 --- a/integrations/appengine/webapp_config.yaml +++ b/integrations/appengine/webapp_config.yaml @@ -3,6 +3,9 @@ handlers: - url: / static_files: html/index.html upload: html/index.html + - url: /conformance/ + static_files: html/conformance_report.html + upload: html/conformance_report.html - url: /(.*) static_files: html/\1 upload: html/(.*) diff --git a/integrations/compute_engine/README.md b/integrations/compute_engine/README.md index 2260a79dd350eb..ffd821fb4b1b16 100644 --- a/integrations/compute_engine/README.md +++ b/integrations/compute_engine/README.md @@ -1,29 +1,33 @@ -## Startup Script of Compute Engine - -A startup script is a file that contains commands that run when a virtual -machine instance boots. Compute Engine provides support for running startup -scripts on Linux and Windows virtual machines. - -### Create a virtual machine instance using startup script - -The `startup-script.sh` could be used as the startup script of a virtual machine -instance which run Matter coverage report and publish the result via an App -Engine service. - -You can create a virtual machine instance by using the gcloud compute instances -create command with the `--metadata-from-file` flag. - -``` -gcloud compute instances create VM_NAME \ - --image-project=PROJECT_NAME \ - --image-family=ubuntu-22.04 \ - --metadata-from-file=startup-script=FILE_PATH -``` - -Replace the following: - -`PROJECT_NAME`: the name of the project host the virtual machine instance - -`VM_NAME`: the name of the virtual machine instance - -`FILE_PATH`: the relative path to the startup script file +## Google Cloud Compute Engine + +We have setup a Virtual Machine on +[Google Cloud](https://cloud.google.com/products/compute) to generate both the +[Matter SDK coverage report](https://matter-build-automation.ue.r.appspot.com) +and the +[Matter SDK Conformance report](https://matter-build-automation.ue.r.appspot.com/conformance_report.html). + +### The Matter SDK Virtual Machine and the "startup-script.sh" + +We created a VM named `matter-build-coverage`. The machine configuration is +located +[here](https://pantheon.corp.google.com/compute/instancesDetail/zones/us-central1-a/instances/matter-build-coverage?inv=1&invt=AbnAfg&project=matter-build-automation). +Reach out to Google team members if you need to make changes to this VM. + +This virtual machine is scheduled to run daily, starting at 11:45PM and stopping +at 2am. During boot, the machine runs the `startup-script.sh`. + +The `startup-script.sh` script contains commands to checkout the SDK repository +and create both the SDK coverage report and conformance report. The startup +script uses `scripts/build_coverage.sh` to generate the coverage report and +`scripts/examples/conformance_report.py` to generate the conformance report. The +resulting HTML files are published via an App Engine service and available here +([coverage report](https://matter-build-automation.ue.r.appspot.com/), +[conformance report](https://matter-build-automation.ue.r.appspot.com/conformance_report.html)). + +### Making Changes to "startup-script.sh" + +If you make changes to `startup-script.sh`, make sure you go to the +[VM configuration](https://pantheon.corp.google.com/compute/instancesDetail/zones/us-central1-a/instances/matter-build-coverage?inv=1&invt=AbnAfg&project=matter-build-automation), +click `edit` and update the startup script in the `Automation` text box, to +reflect your changes. The script in the Matter SDK repo is just a copy of the +configuration in the VM. diff --git a/integrations/compute_engine/startup-script.sh b/integrations/compute_engine/startup-script.sh index 113f45be2603bb..94777c5a759d93 100755 --- a/integrations/compute_engine/startup-script.sh +++ b/integrations/compute_engine/startup-script.sh @@ -16,11 +16,23 @@ # limitations under the License. # +set -x + cd /tmp rm -rf connectedhomeip git clone --recurse-submodules https://github.com/project-chip/connectedhomeip.git cd connectedhomeip + +# Generate Coverage Report ./scripts/build_coverage.sh 2>&1 | tee /tmp/matter_build.log + +# Generate Conformance Report +source scripts/activate.sh +./scripts/build_python.sh -i out/python_env +python3 -u scripts/examples/conformance_report.py +cp /tmp/conformance_report/conformance_report.html out/coverage/coverage/html + +# Upload cd out/coverage/coverage gcloud app deploy webapp_config.yaml 2>&1 | tee /tmp/matter_publish.log versions=$(gcloud app versions list \ diff --git a/integrations/docker/images/base/chip-build/Dockerfile b/integrations/docker/images/base/chip-build/Dockerfile index db209a82f20b87..2a64ab7049d2be 100644 --- a/integrations/docker/images/base/chip-build/Dockerfile +++ b/integrations/docker/images/base/chip-build/Dockerfile @@ -69,6 +69,7 @@ RUN set -x \ libnl-route-3-dev \ libnspr4-dev \ libpango1.0-dev \ + libpcsclite-dev \ libpixman-1-dev \ libreadline-dev \ libsdl2-dev \ diff --git a/integrations/docker/images/base/chip-build/version b/integrations/docker/images/base/chip-build/version index 8d292a0fda857e..ad439b9fc61f07 100644 --- a/integrations/docker/images/base/chip-build/version +++ b/integrations/docker/images/base/chip-build/version @@ -1 +1 @@ -99 : Update Dockerfile for updating java version (Java 8 -> Java 11) +100 : [linux] Added libpcsclite-dev package for NFC Commissioning on Linux platforms. diff --git a/scripts/examples/conformance_report.py b/scripts/examples/conformance_report.py new file mode 100644 index 00000000000000..fe38d5429328df --- /dev/null +++ b/scripts/examples/conformance_report.py @@ -0,0 +1,400 @@ +import argparse +import csv +import glob +import os +import re +import subprocess +from datetime import datetime + +# Constants for default values +DEFAULT_TARGETS = [ + "linux-x64-air-purifier-no-ble", + "linux-x64-air-quality-sensor-no-ble", + "linux-x64-all-clusters-minimal-no-ble", + "linux-x64-all-clusters-no-ble-clang-boringssl", + "linux-x64-bridge-no-ble-clang-boringssl", + "linux-x64-contact-sensor-no-ble", + "linux-x64-dishwasher-no-ble", + "linux-x64-energy-management-no-ble-clang-boringssl", + "linux-x64-light-data-model-no-unique-id-no-ble", + "linux-x64-light-no-ble", + "linux-x64-lit-icd-no-ble", + "linux-x64-lock-no-ble-clang-boringssl", + "linux-x64-microwave-oven-no-ble-clang-boringssl", + "linux-x64-network-manager-ipv6only-no-ble-clang-boringssl", + "linux-x64-ota-provider-no-ble", + "linux-x64-ota-provider-no-ble-clang-boringssl", + "linux-x64-ota-requestor-no-ble", + "linux-x64-refrigerator-no-ble", + "linux-x64-rvc-no-ble", + "linux-x64-rvc-no-ble-clang-boringssl", + "linux-x64-thermostat-no-ble", + "linux-x64-tv-app-no-ble-clang-boringssl", + "linux-x64-tv-casting-app-no-ble", + "linux-x64-water-leak-detector-no-ble" +] +DEFAULT_TESTS = ["TC_DeviceBasicComposition", "TC_DeviceConformance"] +TMP_RESULTS_DIR = "/tmp/conformance_report" +OUT_DIR = "./out" +TEST_COMMAND = "scripts/run_in_python_env.sh out/python_env './scripts/tests/run_python_test.py --app {} --factory-reset --app-args \"--trace-to json:log\" --script src/python_testing/{}.py --script-args \"--qr-code MT:-24J0AFN00KA0648G00\"'" +BUILD_COMMAND = "python3 scripts/build/build_examples.py --ninja-jobs {} --target {} build" +NINJA_JOBS = max(os.cpu_count() - 2, 1) # Limit # of jobs to avoid using too much CPU and RAM + + +def find_executables(dirs): + """ + Look for the first executable file in a list of directories. + + dirs is a list of directories for each sample app built. We assume there's only + a single executable in each directory, so once we find one we put it on the list to return. + + This is just to avoid maintaining a list with the names of all executables for each target. + For example, for a list of directories: + + dirs = [out/linux-x64-lock-no-ble-clang-boringssl, + out/linux-x64-network-manager-ipv6only-no-ble-clang-boringssl] + + find_executables(dirs) will return: + + [out/linux-x64-lock-no-ble-clang-boringssl/chip-lock-app, + out/linux-x64-network-manager-ipv6only-no-ble-clang-boringssl/matter-network-manager-app] + + Args: + dirs: A list of directories to search. + + Returns: + A list of paths to the first executable found in each directory. + """ + executables = [] + for dir in dirs: + if not os.path.isdir(dir): + continue + for filename in os.listdir(dir): + filepath = os.path.join(dir, filename) + if os.path.isfile(filepath) and os.access(filepath, os.X_OK): + executables.append(filepath) + break # Move to the next directory + return executables + + +def parse_test_logs(test_log_paths): + """ + Analyze test output files and return a dictionary summarizing each test. + + Args: + test_log_paths: A list of paths to test output files. + + Returns: + A dictionary where keys are test names and values are lists of results. + Each result is a list: [app_target_name, test_pass_fail, error_count, test_cmd, error_summary]. + """ + + all_tests_results = {} + for test_log_path in test_log_paths: + print(f" Parsing {test_log_path}") + try: + with open(test_log_path, "r") as f: + output_lines = f.readlines() + except FileNotFoundError: + print(f"Result file not found {test_log_path}. Skipping...") + continue + + app_target_name = "" + test_pass_fail = "" + failures = [] + test_name_from_log = "" + test_cmd = "" + + # Use a for loop with enumerate for easier line processing + for i, line in enumerate(output_lines): + if not test_name_from_log and "INFO Executing " in line: + test_name_from_log = line.split("INFO Executing ")[1].split()[0].split(".")[0] + all_tests_results.setdefault(test_name_from_log, []) + + if not app_target_name and " --app " in line: + app_target_name = os.path.basename(line.split(" --app ")[1].split()[0]) + + if not test_pass_fail and "Final result: " in line: + test_pass_fail = line.split("Final result: ")[1].split()[0] + + if not test_cmd and "run_python_test.py" in line: + test_cmd = "run_python_test.py " + line.split("run_python_test.py")[1] + + if "Problem: ProblemSeverity.ERROR" in line: + try: + error_details = "\n".join( + [" " + re.sub(r"^\[.*?\]\[.*?\]\[.*?\]", "", error_line).strip() for error_line in output_lines[i:i+8]] + ) + failures.append(error_details + "\n") + except IndexError: + print("IndexError: End of file reached unexpectedly.") + break + + if not all([app_target_name, test_pass_fail, test_name_from_log]): + print("Invalid test output file, couldn't parse it. Skipping...") + continue + + if test_pass_fail == "FAIL" and not failures: + last_log_lines = output_lines[-100:] if len(output_lines) > 100 else output_lines + failures.append("Test didn't complete. Possible timeout or crash. Last log lines:") + failures.append("\n".join(last_log_lines)) + test_pass_fail = "INVALID" + + print(f"\t{app_target_name}\t{test_pass_fail}\t{len(failures)} errors.") + all_tests_results[test_name_from_log].append( + [app_target_name, test_pass_fail, len(failures), test_cmd, "\n".join(failures)] + ) + + return all_tests_results + + +def run_tests(tests, executable_paths, tmp_results_dir, skip_testing): + """ + Runs tests against executable files. + + Args: + tests: A list of test names to run. + executable_paths: A list of paths to executable files. + tmp_results_dir: Directory to store test output. + skip_testing: Flag to skip test execution. + """ + for test_name in tests: + for executable_path in executable_paths: + app_name = os.path.basename(executable_path) + if skip_testing: + print(f"Testing {app_name} ...skipped") + continue + + print(f"Testing {app_name} against {test_name}...") + try: + command = TEST_COMMAND.format(executable_path, test_name) + test_output_path = os.path.join(tmp_results_dir, f"{test_name}_{app_name}.txt") + with open(test_output_path, "wb") as f: + result = subprocess.run(command, shell=True, capture_output=False, + text=False, stdout=f, stderr=subprocess.STDOUT) + result.check_returncode() # Raise an exception if the command returned a non-zero exit code + print(f" - Test PASSED. Logs written to {test_output_path}") + except subprocess.CalledProcessError as e: + print(f" - Test FAILED. Logs written to {test_output_path}: {e}") + except Exception as e: + print(f"Error running test for {app_name}: {e}") + + +def build_targets(targets, skip_building): + """ + Builds targets using the specified build command. + + Args: + targets: A list of targets to build. + skip_building: Flag to skip building. + """ + for target in targets: + if skip_building: + print(f"Building: {target} ...skipped") + continue + + command = BUILD_COMMAND.format(NINJA_JOBS, target) + try: + print(f"Building: {target}") + print(command) + result = subprocess.run(command, shell=True, capture_output=False, text=True) + result.check_returncode() # Raise CalledProcessError if build fails + except subprocess.CalledProcessError as e: + print(f"Error building {target}:") + print(f" Return code: {e.returncode}") + print(f" stdout: {e.stdout}") + print(f" stderr: {e.stderr}") + except Exception as e: + print(f"Error building {target}: {e}") + + +def generate_csv_summaries(all_tests_results_dict, out_dir): + """ + Generates CSV summaries of test results. + + Args: + all_tests_results_dict: Dictionary of test results. + out_dir: Directory to save CSV files. + """ + for test_name, test_results in all_tests_results_dict.items(): + csv_filename = os.path.join(out_dir, f"{test_name}_summary.csv") + with open(csv_filename, 'w', newline='') as f: + writer = csv.writer(f) + test_passed_count = sum(1 for result in test_results if result[1] == "PASS") + writer.writerow([f"{test_name} ({test_passed_count} / {len(test_results)}) examples passed"]) + writer.writerow(["Application", "Result", "Errors", "Test Command", "Error Summary"]) + writer.writerows(test_results) + + print("CSV test summary saved to " + csv_filename) + + +def csv_to_html_report(csv_file_paths, html_page_title, html_out_dir, sha): + """ + Generates an HTML report from CSV files. + + Args: + csv_file_paths: List of paths to CSV files. + html_page_title: Title of the HTML report. + html_out_dir: Directory to save the HTML report. + sha: SHA commit hash for the report. + """ + now = datetime.now().strftime("%d/%m/%Y %H:%M:%S") + html_report = f""" + + + + + {html_page_title} + +

{html_page_title}

+
+

Generated on {now}
SHA: {sha}

+
+ + """ + + for csv_file_path in csv_file_paths: + with open(csv_file_path, 'r') as csv_file: + reader = csv.reader(csv_file) + table_title = next(reader)[0] + headers = next(reader) + data = list(reader) + + html_table = f"

{table_title}

" + html_table += "" + "".join(f"" for header in headers) + "" + for row in data: + html_table += "" + for cell in row: + if len(cell) > 100: + html_table += "" + elif cell in ("PASS", "FAIL"): + html_table += f"" + else: + html_table += "" + html_table += "" + html_table += "
{header}
Show/Hide" + cell.replace('\n', '
') + "
{cell}" + cell.replace('\n', '
') + "
" + html_report += html_table + + html_report += """ + + + """ + + html_file = os.path.join(html_out_dir, "conformance_report.html") + print("Saving HTML report to " + html_file) + with open(html_file, "w") as f: + f.write(html_report) + + +def get_git_revision_hash() -> str: + return subprocess.check_output(['git', 'rev-parse', 'HEAD']).decode('ascii').strip() + + +if __name__ == "__main__": + parser = argparse.ArgumentParser( + description="Build examples, run conformance tests and generate a report with the results." + ) + parser.add_argument( + "--test-name", + help="Override the default tests with a specific test name." + ) + parser.add_argument( + "--target-name", + help="Override the default targets with a specific target name." + ) + parser.add_argument( + "--skip-building", + help="Skip building the target(s).", + action="store_true" + ) + parser.add_argument( + "--skip-testing", + help="Skip testing the target(s).", + action="store_true" + ) + parser.add_argument( + "--skip-html", + help="Skip generating the HTML report.", + action="store_true" + ) + parser.add_argument( + "--html-out-dir", + help="Specify the directory to save the HTML report.", + default=TMP_RESULTS_DIR + ) + + args = parser.parse_args() + + targets = [args.target_name] if args.target_name else DEFAULT_TARGETS + target_out_dirs = [os.path.join(OUT_DIR, target) for target in targets] + tests = [args.test_name] if args.test_name else DEFAULT_TESTS + + os.makedirs(TMP_RESULTS_DIR, exist_ok=True) + + build_targets(targets, args.skip_building) + executable_paths = find_executables(target_out_dirs) + run_tests(tests, executable_paths, TMP_RESULTS_DIR, args.skip_testing) + + print(f"Parsing all test output logs in {TMP_RESULTS_DIR}...") + test_logs = glob.glob(os.path.join(TMP_RESULTS_DIR, "*.txt")) + aggregated_results_dict = parse_test_logs(test_logs) + generate_csv_summaries(aggregated_results_dict, TMP_RESULTS_DIR) + csv_summaries_paths = glob.glob(os.path.join(TMP_RESULTS_DIR, "*.csv")) + + if not args.skip_html: + os.makedirs(args.html_out_dir, exist_ok=True) + csv_to_html_report( + csv_summaries_paths, + "Matter SDK Example Conformance Report", + args.html_out_dir, + get_git_revision_hash() + ) diff --git a/scripts/tools/zap/tests/outputs/all-clusters-app/app-templates/IMClusterCommandHandler.cpp b/scripts/tools/zap/tests/outputs/all-clusters-app/app-templates/IMClusterCommandHandler.cpp index 09f8a2cbe1e4e6..2027fc3f2a7200 100644 --- a/scripts/tools/zap/tests/outputs/all-clusters-app/app-templates/IMClusterCommandHandler.cpp +++ b/scripts/tools/zap/tests/outputs/all-clusters-app/app-templates/IMClusterCommandHandler.cpp @@ -1748,47 +1748,6 @@ Protocols::InteractionModel::Status DispatchServerCommand(CommandHandler * apCom } // namespace ValveConfigurationAndControl -namespace WiFiNetworkDiagnostics { - -Protocols::InteractionModel::Status DispatchServerCommand(CommandHandler * apCommandObj, const ConcreteCommandPath & aCommandPath, - TLV::TLVReader & aDataTlv) -{ - CHIP_ERROR TLVError = CHIP_NO_ERROR; - bool wasHandled = false; - { - switch (aCommandPath.mCommandId) - { - case Commands::ResetCounts::Id: { - Commands::ResetCounts::DecodableType commandData; - TLVError = DataModel::Decode(aDataTlv, commandData); - if (TLVError == CHIP_NO_ERROR) - { - wasHandled = emberAfWiFiNetworkDiagnosticsClusterResetCountsCallback(apCommandObj, aCommandPath, commandData); - } - break; - } - default: { - // Unrecognized command ID, error status will apply. - ChipLogError(Zcl, "Unknown command " ChipLogFormatMEI " for cluster " ChipLogFormatMEI, - ChipLogValueMEI(aCommandPath.mCommandId), ChipLogValueMEI(aCommandPath.mClusterId)); - return Protocols::InteractionModel::Status::UnsupportedCommand; - } - } - } - - if (CHIP_NO_ERROR != TLVError || !wasHandled) - { - ChipLogProgress(Zcl, "Failed to dispatch command, TLVError=%" CHIP_ERROR_FORMAT, TLVError.Format()); - return Protocols::InteractionModel::Status::InvalidCommand; - } - - // We use success as a marker that no special handling is required - // This is to avoid having a std::optional which uses slightly more code. - return Protocols::InteractionModel::Status::Success; -} - -} // namespace WiFiNetworkDiagnostics - namespace WindowCovering { Protocols::InteractionModel::Status DispatchServerCommand(CommandHandler * apCommandObj, const ConcreteCommandPath & aCommandPath, @@ -1964,9 +1923,6 @@ void DispatchSingleClusterCommand(const ConcreteCommandPath & aCommandPath, TLV: case Clusters::ValveConfigurationAndControl::Id: errorStatus = Clusters::ValveConfigurationAndControl::DispatchServerCommand(apCommandObj, aCommandPath, aReader); break; - case Clusters::WiFiNetworkDiagnostics::Id: - errorStatus = Clusters::WiFiNetworkDiagnostics::DispatchServerCommand(apCommandObj, aCommandPath, aReader); - break; case Clusters::WindowCovering::Id: errorStatus = Clusters::WindowCovering::DispatchServerCommand(apCommandObj, aCommandPath, aReader); break; diff --git a/scripts/tools/zap/tests/outputs/lighting-app/app-templates/IMClusterCommandHandler.cpp b/scripts/tools/zap/tests/outputs/lighting-app/app-templates/IMClusterCommandHandler.cpp index 11b0dc14a23f66..61dae1f9dafce6 100644 --- a/scripts/tools/zap/tests/outputs/lighting-app/app-templates/IMClusterCommandHandler.cpp +++ b/scripts/tools/zap/tests/outputs/lighting-app/app-templates/IMClusterCommandHandler.cpp @@ -969,47 +969,6 @@ Protocols::InteractionModel::Status DispatchServerCommand(CommandHandler * apCom } // namespace ThreadNetworkDiagnostics -namespace WiFiNetworkDiagnostics { - -Protocols::InteractionModel::Status DispatchServerCommand(CommandHandler * apCommandObj, const ConcreteCommandPath & aCommandPath, - TLV::TLVReader & aDataTlv) -{ - CHIP_ERROR TLVError = CHIP_NO_ERROR; - bool wasHandled = false; - { - switch (aCommandPath.mCommandId) - { - case Commands::ResetCounts::Id: { - Commands::ResetCounts::DecodableType commandData; - TLVError = DataModel::Decode(aDataTlv, commandData); - if (TLVError == CHIP_NO_ERROR) - { - wasHandled = emberAfWiFiNetworkDiagnosticsClusterResetCountsCallback(apCommandObj, aCommandPath, commandData); - } - break; - } - default: { - // Unrecognized command ID, error status will apply. - ChipLogError(Zcl, "Unknown command " ChipLogFormatMEI " for cluster " ChipLogFormatMEI, - ChipLogValueMEI(aCommandPath.mCommandId), ChipLogValueMEI(aCommandPath.mClusterId)); - return Protocols::InteractionModel::Status::UnsupportedCommand; - } - } - } - - if (CHIP_NO_ERROR != TLVError || !wasHandled) - { - ChipLogProgress(Zcl, "Failed to dispatch command, TLVError=%" CHIP_ERROR_FORMAT, TLVError.Format()); - return Protocols::InteractionModel::Status::InvalidCommand; - } - - // We use success as a marker that no special handling is required - // This is to avoid having a std::optional which uses slightly more code. - return Protocols::InteractionModel::Status::Success; -} - -} // namespace WiFiNetworkDiagnostics - } // namespace Clusters void DispatchSingleClusterCommand(const ConcreteCommandPath & aCommandPath, TLV::TLVReader & aReader, CommandHandler * apCommandObj) @@ -1054,9 +1013,6 @@ void DispatchSingleClusterCommand(const ConcreteCommandPath & aCommandPath, TLV: case Clusters::ThreadNetworkDiagnostics::Id: errorStatus = Clusters::ThreadNetworkDiagnostics::DispatchServerCommand(apCommandObj, aCommandPath, aReader); break; - case Clusters::WiFiNetworkDiagnostics::Id: - errorStatus = Clusters::WiFiNetworkDiagnostics::DispatchServerCommand(apCommandObj, aCommandPath, aReader); - break; default: ChipLogError(Zcl, "Unknown cluster " ChipLogFormatMEI, ChipLogValueMEI(aCommandPath.mClusterId)); errorStatus = Protocols::InteractionModel::Status::UnsupportedCluster; diff --git a/src/app/clusters/wifi-network-diagnostics-server/wifi-network-diagnostics-server.cpp b/src/app/clusters/wifi-network-diagnostics-server/wifi-network-diagnostics-server.cpp index 90fc1af4f5aaa9..6c4b0a4cf612d7 100644 --- a/src/app/clusters/wifi-network-diagnostics-server/wifi-network-diagnostics-server.cpp +++ b/src/app/clusters/wifi-network-diagnostics-server/wifi-network-diagnostics-server.cpp @@ -23,6 +23,8 @@ #include #include #include +#include +#include #include #include #include @@ -40,11 +42,15 @@ using chip::DeviceLayer::GetDiagnosticDataProvider; namespace { -class WiFiDiagosticsAttrAccess : public AttributeAccessInterface +class WiFiDiagosticsGlobalInstance : public AttributeAccessInterface, public CommandHandlerInterface { public: // Register for the WiFiNetworkDiagnostics cluster on all endpoints. - WiFiDiagosticsAttrAccess() : AttributeAccessInterface(Optional::Missing(), WiFiNetworkDiagnostics::Id) {} + WiFiDiagosticsGlobalInstance(DiagnosticDataProvider & diagnosticProvider) : + AttributeAccessInterface(Optional::Missing(), WiFiNetworkDiagnostics::Id), + CommandHandlerInterface(Optional::Missing(), WiFiNetworkDiagnostics::Id), + mDiagnosticProvider(diagnosticProvider) + {} CHIP_ERROR Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder) override; @@ -57,14 +63,20 @@ class WiFiDiagosticsAttrAccess : public AttributeAccessInterface CHIP_ERROR ReadWiFiVersion(AttributeValueEncoder & aEncoder); CHIP_ERROR ReadChannelNumber(AttributeValueEncoder & aEncoder); CHIP_ERROR ReadWiFiRssi(AttributeValueEncoder & aEncoder); + + void InvokeCommand(HandlerContext & ctx) override; + + void HandleResetCounts(HandlerContext & ctx, const Commands::ResetCounts::DecodableType & commandData); + + DiagnosticDataProvider & mDiagnosticProvider; }; template -CHIP_ERROR WiFiDiagosticsAttrAccess::ReadIfSupported(CHIP_ERROR (DiagnosticDataProvider::*getter)(T &), Type & data, - AttributeValueEncoder & aEncoder) +CHIP_ERROR WiFiDiagosticsGlobalInstance::ReadIfSupported(CHIP_ERROR (DiagnosticDataProvider::*getter)(T &), Type & data, + AttributeValueEncoder & aEncoder) { T value; - CHIP_ERROR err = (DeviceLayer::GetDiagnosticDataProvider().*getter)(value); + CHIP_ERROR err = (mDiagnosticProvider.*getter)(value); if (err == CHIP_NO_ERROR) { @@ -78,13 +90,13 @@ CHIP_ERROR WiFiDiagosticsAttrAccess::ReadIfSupported(CHIP_ERROR (DiagnosticDataP return aEncoder.Encode(data); } -CHIP_ERROR WiFiDiagosticsAttrAccess::ReadWiFiBssId(AttributeValueEncoder & aEncoder) +CHIP_ERROR WiFiDiagosticsGlobalInstance::ReadWiFiBssId(AttributeValueEncoder & aEncoder) { Attributes::Bssid::TypeInfo::Type bssid; uint8_t bssidBytes[chip::DeviceLayer::kMaxHardwareAddrSize]; MutableByteSpan bssidSpan(bssidBytes); - if (DeviceLayer::GetDiagnosticDataProvider().GetWiFiBssId(bssidSpan) == CHIP_NO_ERROR) + if (mDiagnosticProvider.GetWiFiBssId(bssidSpan) == CHIP_NO_ERROR) { if (!bssidSpan.empty()) { @@ -101,12 +113,12 @@ CHIP_ERROR WiFiDiagosticsAttrAccess::ReadWiFiBssId(AttributeValueEncoder & aEnco return aEncoder.Encode(bssid); } -CHIP_ERROR WiFiDiagosticsAttrAccess::ReadSecurityType(AttributeValueEncoder & aEncoder) +CHIP_ERROR WiFiDiagosticsGlobalInstance::ReadSecurityType(AttributeValueEncoder & aEncoder) { Attributes::SecurityType::TypeInfo::Type securityType; SecurityTypeEnum value = SecurityTypeEnum::kUnspecified; - if (DeviceLayer::GetDiagnosticDataProvider().GetWiFiSecurityType(value) == CHIP_NO_ERROR) + if (mDiagnosticProvider.GetWiFiSecurityType(value) == CHIP_NO_ERROR) { securityType.SetNonNull(value); ChipLogProgress(Zcl, "The current type of Wi-Fi security used: %d", to_underlying(value)); @@ -119,12 +131,12 @@ CHIP_ERROR WiFiDiagosticsAttrAccess::ReadSecurityType(AttributeValueEncoder & aE return aEncoder.Encode(securityType); } -CHIP_ERROR WiFiDiagosticsAttrAccess::ReadWiFiVersion(AttributeValueEncoder & aEncoder) +CHIP_ERROR WiFiDiagosticsGlobalInstance::ReadWiFiVersion(AttributeValueEncoder & aEncoder) { Attributes::WiFiVersion::TypeInfo::Type version; WiFiVersionEnum value = WiFiVersionEnum::kUnknownEnumValue; - if (DeviceLayer::GetDiagnosticDataProvider().GetWiFiVersion(value) == CHIP_NO_ERROR) + if (mDiagnosticProvider.GetWiFiVersion(value) == CHIP_NO_ERROR) { version.SetNonNull(value); ChipLogProgress(Zcl, "The current 802.11 standard version in use by the Node: %d", to_underlying(value)); @@ -137,12 +149,12 @@ CHIP_ERROR WiFiDiagosticsAttrAccess::ReadWiFiVersion(AttributeValueEncoder & aEn return aEncoder.Encode(version); } -CHIP_ERROR WiFiDiagosticsAttrAccess::ReadChannelNumber(AttributeValueEncoder & aEncoder) +CHIP_ERROR WiFiDiagosticsGlobalInstance::ReadChannelNumber(AttributeValueEncoder & aEncoder) { Attributes::ChannelNumber::TypeInfo::Type channelNumber; uint16_t value = 0; - if (DeviceLayer::GetDiagnosticDataProvider().GetWiFiChannelNumber(value) == CHIP_NO_ERROR) + if (mDiagnosticProvider.GetWiFiChannelNumber(value) == CHIP_NO_ERROR) { channelNumber.SetNonNull(value); ChipLogProgress(Zcl, "The channel that Wi-Fi communication is currently operating on is: %d", value); @@ -155,12 +167,12 @@ CHIP_ERROR WiFiDiagosticsAttrAccess::ReadChannelNumber(AttributeValueEncoder & a return aEncoder.Encode(channelNumber); } -CHIP_ERROR WiFiDiagosticsAttrAccess::ReadWiFiRssi(AttributeValueEncoder & aEncoder) +CHIP_ERROR WiFiDiagosticsGlobalInstance::ReadWiFiRssi(AttributeValueEncoder & aEncoder) { Attributes::Rssi::TypeInfo::Type rssi; int8_t value = 0; - if (DeviceLayer::GetDiagnosticDataProvider().GetWiFiRssi(value) == CHIP_NO_ERROR) + if (mDiagnosticProvider.GetWiFiRssi(value) == CHIP_NO_ERROR) { rssi.SetNonNull(value); ChipLogProgress(Zcl, "The current RSSI of the Node’s Wi-Fi radio in dB: %d", value); @@ -174,9 +186,7 @@ CHIP_ERROR WiFiDiagosticsAttrAccess::ReadWiFiRssi(AttributeValueEncoder & aEncod return aEncoder.Encode(rssi); } -WiFiDiagosticsAttrAccess gAttrAccess; - -CHIP_ERROR WiFiDiagosticsAttrAccess::Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder) +CHIP_ERROR WiFiDiagosticsGlobalInstance::Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder) { if (aPath.mClusterId != WiFiNetworkDiagnostics::Id) { @@ -240,6 +250,25 @@ CHIP_ERROR WiFiDiagosticsAttrAccess::Read(const ConcreteReadAttributePath & aPat return CHIP_NO_ERROR; } +void WiFiDiagosticsGlobalInstance::InvokeCommand(HandlerContext & handlerContext) +{ + switch (handlerContext.mRequestPath.mCommandId) + { + case Commands::ResetCounts::Id: + CommandHandlerInterface::HandleCommand( + handlerContext, [this](HandlerContext & ctx, const auto & commandData) { HandleResetCounts(ctx, commandData); }); + break; + } +} + +void WiFiDiagosticsGlobalInstance::HandleResetCounts(HandlerContext & ctx, const Commands::ResetCounts::DecodableType & commandData) +{ + mDiagnosticProvider.ResetWiFiNetworkDiagnosticsCounts(); + ctx.mCommandHandler.AddStatus(ctx.mRequestPath, Protocols::InteractionModel::Status::Success); +} + +WiFiDiagosticsGlobalInstance gWiFiDiagosticsInstance(DeviceLayer::GetDiagnosticDataProvider()); + } // anonymous namespace namespace chip { @@ -316,18 +345,10 @@ void WiFiDiagnosticsServer::OnConnectionStatusChanged(uint8_t connectionStatus) } // namespace app } // namespace chip -bool emberAfWiFiNetworkDiagnosticsClusterResetCountsCallback(app::CommandHandler * commandObj, - const app::ConcreteCommandPath & commandPath, - const Commands::ResetCounts::DecodableType & commandData) -{ - DeviceLayer::GetDiagnosticDataProvider().ResetWiFiNetworkDiagnosticsCounts(); - commandObj->AddStatus(commandPath, Protocols::InteractionModel::Status::Success); - - return true; -} - void MatterWiFiNetworkDiagnosticsPluginServerInitCallback() { - AttributeAccessInterfaceRegistry::Instance().Register(&gAttrAccess); + AttributeAccessInterfaceRegistry::Instance().Register(&gWiFiDiagosticsInstance); + CommandHandlerInterfaceRegistry::Instance().RegisterCommandHandler(&gWiFiDiagosticsInstance); + GetDiagnosticDataProvider().SetWiFiDiagnosticsDelegate(&WiFiDiagnosticsServer::Instance()); } diff --git a/src/app/common/templates/config-data.yaml b/src/app/common/templates/config-data.yaml index 72e548842d72ca..38f826c9ee6783 100644 --- a/src/app/common/templates/config-data.yaml +++ b/src/app/common/templates/config-data.yaml @@ -52,6 +52,7 @@ CommandHandlerInterfaceOnlyClusters: - General Commissioning - General Diagnostics - Software Diagnostics + - Wi-Fi Network Diagnostics # We need a more configurable way of deciding which clusters have which init functions.... # See https://github.com/project-chip/connectedhomeip/issues/4369 diff --git a/src/platform/ESP32/BUILD.gn b/src/platform/ESP32/BUILD.gn index 424cde44f0637c..77cbc94bb43650 100644 --- a/src/platform/ESP32/BUILD.gn +++ b/src/platform/ESP32/BUILD.gn @@ -124,13 +124,6 @@ static_library("ESP32") { "NetworkCommissioningDriver.cpp", "NetworkCommissioningDriver.h", ] - - # TODO: this is NOT ok, however we added a layering dependecy - # in NetworkCommissioningDriver accessing app/InteractionModelEngine.h - # - # Should be removed after https://github.com/project-chip/connectedhomeip/issues/37126 - # is fixed - deps += [ "${chip_root}/src/access:access_config" ] } if (chip_mdns == "platform") { diff --git a/src/platform/ESP32/NetworkCommissioningDriver.cpp b/src/platform/ESP32/NetworkCommissioningDriver.cpp index 77394d17f4e49a..1ded189218ce82 100644 --- a/src/platform/ESP32/NetworkCommissioningDriver.cpp +++ b/src/platform/ESP32/NetworkCommissioningDriver.cpp @@ -15,7 +15,6 @@ * limitations under the License. */ -#include // nogncheck #include #include #include @@ -459,9 +458,6 @@ void ESPWiFiDriver::OnScanWiFiNetworkDone() void ESPWiFiDriver::OnNetworkStatusChange() { - // This function reports the status to the data model provider, so skip it if the provider is not ready. - VerifyOrReturn(app::InteractionModelEngine::GetInstance() && - app::InteractionModelEngine::GetInstance()->GetDataModelProvider()); Network configuredNetwork; bool staEnabled = false, staConnected = false; VerifyOrReturn(ESP32Utils::IsStationEnabled(staEnabled) == CHIP_NO_ERROR); diff --git a/src/python_testing/TC_CADMIN_1_19.py b/src/python_testing/TC_CADMIN_1_19.py index a7a97aa395bed5..0faea18ef3663d 100644 --- a/src/python_testing/TC_CADMIN_1_19.py +++ b/src/python_testing/TC_CADMIN_1_19.py @@ -137,8 +137,8 @@ async def test_TC_CADMIN_1_19(self): fids_fa_dir[next_fabric] = fids_ca_dir[current_fabrics + 1].NewFabricAdmin(vendorId=0xFFF1, fabricId=next_fabric) try: - fids[next_fabric] = fids_fa_dir[next_fabric].NewController(nodeId=next_fabric) - await fids[next_fabric].CommissionOnNetwork( + next_fabric_controller = fids_fa_dir[next_fabric].NewController(nodeId=next_fabric) + await next_fabric_controller.CommissionOnNetwork( nodeId=self.dut_node_id, setupPinCode=params.commissioningParameters.setupPinCode, filterType=ChipDeviceCtrl.DiscoveryFilterType.LONG_DISCRIMINATOR, filter=params.randomDiscriminator) diff --git a/zzz_generated/app-common/app-common/zap-generated/callback.h b/zzz_generated/app-common/app-common/zap-generated/callback.h index 66537a84f94f79..b7dcf9d0a8a5f9 100644 --- a/zzz_generated/app-common/app-common/zap-generated/callback.h +++ b/zzz_generated/app-common/app-common/zap-generated/callback.h @@ -6007,12 +6007,6 @@ bool emberAfDiagnosticLogsClusterRetrieveLogsRequestCallback( bool emberAfThreadNetworkDiagnosticsClusterResetCountsCallback( chip::app::CommandHandler * commandObj, const chip::app::ConcreteCommandPath & commandPath, const chip::app::Clusters::ThreadNetworkDiagnostics::Commands::ResetCounts::DecodableType & commandData); -/** - * @brief Wi-Fi Network Diagnostics Cluster ResetCounts Command callback (from client) - */ -bool emberAfWiFiNetworkDiagnosticsClusterResetCountsCallback( - chip::app::CommandHandler * commandObj, const chip::app::ConcreteCommandPath & commandPath, - const chip::app::Clusters::WiFiNetworkDiagnostics::Commands::ResetCounts::DecodableType & commandData); /** * @brief Ethernet Network Diagnostics Cluster ResetCounts Command callback (from client) */