From f170ae0f73e27b996ab0d88d5c91a150204cf83c Mon Sep 17 00:00:00 2001 From: Shao Ling Tan <161761051+shaoltan-amazon@users.noreply.github.com> Date: Thu, 20 Jun 2024 05:16:37 -0700 Subject: [PATCH 1/2] Simplify the implementation of `run_tv_casting_test.py` for easier addition of future test sequences that verifies that Linux tv-casting-app continues to work with Linux tv-app. (#33855) * Simplify the implementation of `run_tv_casting_test.py` for easier addition of future test sequences that verifies that Linux tv-casting-app continues to work with Linux tv-app. * Addressed PR comments from @sharadb-amazon. * Addressed @sharadb-amazon PR comments. Also restructured the code to prevent circular import issues. * Run restyle. * Fixed typo. --- .../linux/tv_casting_test_sequence_utils.py | 107 +++ .../tests/linux/tv_casting_test_sequences.py | 152 ++++ scripts/tests/run_tv_casting_test.py | 723 +++++------------- 3 files changed, 462 insertions(+), 520 deletions(-) create mode 100644 scripts/tests/linux/tv_casting_test_sequence_utils.py create mode 100644 scripts/tests/linux/tv_casting_test_sequences.py diff --git a/scripts/tests/linux/tv_casting_test_sequence_utils.py b/scripts/tests/linux/tv_casting_test_sequence_utils.py new file mode 100644 index 00000000000000..e95bc434972a2c --- /dev/null +++ b/scripts/tests/linux/tv_casting_test_sequence_utils.py @@ -0,0 +1,107 @@ +#!/usr/bin/env -S python3 -B + +# Copyright (c) 2024 Project CHIP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from enum import Enum +from typing import List, Optional + +""" +This file defines the utility classes for creating and managing test sequences to validate the casting experience between +the Linux tv-casting-app and the Linux tv-app. It includes an enumeration for the supported applications (App), a class to +represent individual steps in a test sequence (Step), and a class to represent a complete test sequence (Sequence). +Additionally, it provides helper functions to retrieve specific test sequences or all defined test sequences. +""" + + +class App(Enum): + """An enumeration of the supported applications.""" + + TV_APP = 'tv-app' + TV_CASTING_APP = 'tv-casting-app' + + +class Step: + """A class to represent a step in a test sequence for validation. + + A `Step` object contains attributes relevant to a test step where each object contains: + - `app` subprocess to parse for `output_msg` or send `input_cmd` + - `timeout_sec` specified the timeout duration for parsing the `output_msg` (optional, defaults to DEFAULT_TIMEOUT_SEC) + - `output_msg` or `input_cmd` (mutually exclusive) + + For output message blocks, define the start line, relevant lines, and the last line. If the last line contains trivial closing + characters (e.g., closing brackets, braces, or commas), include the line before it with actual content. For example: + `Step(subprocess_='tv-casting-app', output_msg=['InvokeResponseMessage =', 'exampleData', 'InteractionModelRevision =', '},'])` + + For input commands, define the command string with placeholders for variables that need to be updated. For example: + `Step(subprocess_='tv-casting-app', input_cmd='cast request 0\n')` + """ + + # The maximum default time to wait while parsing for output string(s). + DEFAULT_TIMEOUT_SEC = 10 + + def __init__( + self, + app: App, + timeout_sec: Optional[int] = DEFAULT_TIMEOUT_SEC, + output_msg: Optional[List[str]] = None, + input_cmd: Optional[str] = None, + ): + # Validate that either `output_msg` or `input_cmd` is provided, but not both. + if output_msg is not None and input_cmd is not None: + raise ValueError( + 'Step cannot contain both `output_msg` and `input_cmd`. Either `output_msg` or `input_cmd` should be provided.') + elif output_msg is None and input_cmd is None: + raise ValueError('Step must contain either `output_msg` or `input_cmd`. Both are `None`.') + + # Define either `App.TV_APP` or `App.TV_CASTING_APP` on which we need to parse for `output_msg` or send `input_cmd`. + self.app = app + + # Define the maximum time in seconds for timeout while parsing for the `output_msg`. If not provided, then we use the DEFAULT_TIMEOUT_SEC. + self.timeout_sec = timeout_sec + + # Define the `output_msg` that we need to parse for in a list format. + self.output_msg = output_msg + + # Define the `input_cmd` that we need to send to either the `App.TV_APP` or `App.TV_CASTING_APP`. + self.input_cmd = input_cmd + + +class Sequence: + """A class representing a sequence of steps for testing the casting experience between the Linux tv-casting-app and the tv-app. + + A Sequence object needs to be defined with an appropriate test sequence `name` along with its list of `Step` objects that will + be used for validating the casting experience. + """ + + def __init__(self, name: str, steps: List[Step]): + self.name = name + self.steps = steps + + @staticmethod + def get_test_sequence_by_name(test_sequences: List['Sequence'], test_sequence_name: str) -> Optional['Sequence']: + """Retrieve a test sequence from a list of sequences by its name.""" + + for sequence in test_sequences: + if sequence.name == test_sequence_name: + return sequence + return None + + @staticmethod + def get_test_sequences() -> List['Sequence']: + """Retrieve all the test sequences to validate the casting experience between the Linux tv-casting-app and the Linux tv-app.""" + + from linux.tv_casting_test_sequences import test_sequences + + return test_sequences diff --git a/scripts/tests/linux/tv_casting_test_sequences.py b/scripts/tests/linux/tv_casting_test_sequences.py new file mode 100644 index 00000000000000..9b65e64c9bb575 --- /dev/null +++ b/scripts/tests/linux/tv_casting_test_sequences.py @@ -0,0 +1,152 @@ +#!/usr/bin/env -S python3 -B + +# Copyright (c) 2024 Project CHIP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from linux.tv_casting_test_sequence_utils import App, Sequence, Step + +""" +In this file, we define the test sequences with the relevant steps that will be used in the `scripts/tests/run_tv_casting_test.py` +for validating the casting experience between the Linux tv-casting-app and the Linux tv-app. + +At the beginning of each test sequence we need to indicate the start up of the tv-app using the `START_APP` string as the `input_cmd` +followed by the same for the tv-casting-app. On the other hand, at the end of each test sequence we need to ensure that each app will +be stopped by providing the `STOP_APP` string as the `input_cmd`. As noted in the example below of `example_test_sequence`, the first +four steps pertain to starting the apps while the last two are for signaling stopping the apps. + +Note: `START_APP` and `STOP_APP` are reserved for signaling the starting and stopping of apps. + +Example: + test_sequences = [ + Sequence( + name='example_test_sequence', + step=[ + # Signal to start the tv-app. + Step(app=App.TV_APP, input_cmd=START_APP), + + # Validate that the tv-app is up and running. + Step(app=App.TV_APP, timeout_sec=APP_MAX_START_WAIT_SEC, output_msg=['Started commissioner']), + + # Signal to start the tv-casting-app. + Step(app=App.TV_CASTING_APP, input_cmd=START_APP), + + # Validate that the server is properly initialized in the tv-casting-app output. + Step(app=App.TV_CASTING_APP, timeout_sec=APP_MAX_START_WAIT_SEC, output_msg=['Server initialization complete']), + + # Additional steps for testing the casting experience. + + # Signal to stop the tv-casting-app as we finished validation. + Step(app=App.TV_CASTING_APP, input_cmd=STOP_APP), + + # Signal to stop the tv-app as we finished validation. + Step(app=App.TV_APP, input_cmd=STOP_APP) + ] + ) + ] +""" + +# Signal to start the app. +START_APP = 'START' + +# Signal to stop the app. +STOP_APP = 'STOP' + +# The maximum amount of time to wait for the Linux tv-app or Linux tv-casting-app to start before timeout. +APP_MAX_START_WAIT_SEC = 2 + +# Values that identify the Linux tv-app and are noted in the 'Device Configuration' in the Linux tv-app output +# as well as under the 'Discovered Commissioner' details in the Linux tv-casting-app output. +VENDOR_ID = 0xFFF1 # Spec 7.20.2.1 MEI code: test vendor IDs are 0xFFF1 to 0xFFF4 +PRODUCT_ID = 0x8001 # Test product id +DEVICE_TYPE_CASTING_VIDEO_PLAYER = 0x23 # Device type library 10.3: Casting Video Player + +TEST_TV_CASTING_APP_DEVICE_NAME = 'Test TV casting app' # Test device name for identifying the tv-casting-app + +# Values to verify the subscription state against from the `ReportDataMessage` in the Linux tv-casting-app output. +CLUSTER_MEDIA_PLAYBACK = '0x506' # Application Cluster Spec 6.10.3 Cluster ID: Media Playback +ATTRIBUTE_CURRENT_PLAYBACK_STATE = '0x0000_0000' # Application Cluster Spec 6.10.6 Attribute ID: Current State of Playback + +test_sequences = [ + Sequence( + name='commissionee_generated_passcode_test', + steps=[ + # Signal to start the tv-app. + Step(app=App.TV_APP, input_cmd=START_APP), + + # Validate that the tv-app is up and running. + Step(app=App.TV_APP, timeout_sec=APP_MAX_START_WAIT_SEC, output_msg=['Started commissioner']), + + # Signal to start the tv-casting-app. + Step(app=App.TV_CASTING_APP, input_cmd=START_APP), + + # Validate that the server is properly initialized in the tv-casting-app output. + Step(app=App.TV_CASTING_APP, timeout_sec=APP_MAX_START_WAIT_SEC, output_msg=['Server initialization complete']), + + # Validate that there is a valid discovered commissioner with {VENDOR_ID}, {PRODUCT_ID}, and {DEVICE_TYPE_CASTING_VIDEO_PLAYER} in the tv-casting-app output. + Step(app=App.TV_CASTING_APP, output_msg=['Discovered Commissioner #0', f'Vendor ID: {VENDOR_ID}', f'Product ID: {PRODUCT_ID}', + f'Device Type: {DEVICE_TYPE_CASTING_VIDEO_PLAYER}', 'Supports Commissioner Generated Passcode: true']), + + # Validate that we are ready to send `cast request` command to the tv-casting-app subprocess. + Step(app=App.TV_CASTING_APP, output_msg=['Example: cast request 0']), + + # Send `cast request {valid_discovered_commissioner_number}\n` command to the tv-casting-app subprocess. + Step(app=App.TV_CASTING_APP, input_cmd='cast request 0\n'), + + # Validate that the `Identification Declaration` message block in the tv-casting-app output has the expected values for `device Name`, `vendor id`, and `product id`. + Step(app=App.TV_CASTING_APP, output_msg=['Identification Declaration Start', f'device Name: {TEST_TV_CASTING_APP_DEVICE_NAME}', + f'vendor id: {VENDOR_ID}', f'product id: {PRODUCT_ID}', 'Identification Declaration End']), + + # Validate that the `Identification Declaration` message block in the tv-app output has the expected values for `device Name`, `vendor id`, and `product id`. + Step(app=App.TV_APP, output_msg=['Identification Declaration Start', f'device Name: {TEST_TV_CASTING_APP_DEVICE_NAME}', + f'vendor id: {VENDOR_ID}', f'product id: {PRODUCT_ID}', 'Identification Declaration End']), + + # Validate that we received the cast request from the tv-casting-app on the tv-app output. + Step(app=App.TV_APP, + output_msg=['PROMPT USER: Test TV casting app is requesting permission to cast to this TV, approve?']), + + # Validate that we received the instructions on the tv-app output for sending the `controller ux ok` command. + Step(app=App.TV_APP, output_msg=['Via Shell Enter: controller ux ok|cancel']), + + # Send `controller ux ok` command to the tv-app subprocess. + Step(app=App.TV_APP, input_cmd='controller ux ok\n'), + + # Validate that pairing succeeded between the tv-casting-app and the tv-app. + Step(app=App.TV_APP, output_msg=['Secure Pairing Success']), + + # Validate that commissioning succeeded in the tv-casting-app output. + Step(app=App.TV_CASTING_APP, output_msg=['Commissioning completed successfully']), + + # Validate that commissioning succeeded in the tv-app output. + Step(app=App.TV_APP, output_msg=['------PROMPT USER: commissioning success']), + + # Validate the subscription state by looking at the `Cluster` and `Attribute` values in the `ReportDataMessage` block in the tv-casting-app output. + Step(app=App.TV_CASTING_APP, output_msg=[ + 'ReportDataMessage =', f'Cluster = {CLUSTER_MEDIA_PLAYBACK}', f'Attribute = {ATTRIBUTE_CURRENT_PLAYBACK_STATE}', 'InteractionModelRevision =', '}']), + + # Validate the LaunchURL in the tv-app output. + Step(app=App.TV_APP, + output_msg=['ContentLauncherManager::HandleLaunchUrl TEST CASE ContentURL=https://www.test.com/videoid DisplayString=Test video']), + + # Validate the LaunchURL in the tv-casting-app output. + Step(app=App.TV_CASTING_APP, output_msg=['InvokeResponseMessage =', + 'exampleData', 'InteractionModelRevision =', '},']), + + # Signal to stop the tv-casting-app as we finished validation. + Step(app=App.TV_CASTING_APP, input_cmd=STOP_APP), + + # Signal to stop the tv-app as we finished validation. + Step(app=App.TV_APP, input_cmd=STOP_APP) + ] + ) +] diff --git a/scripts/tests/run_tv_casting_test.py b/scripts/tests/run_tv_casting_test.py index 074d4592d59afe..45cc6171bc3dca 100644 --- a/scripts/tests/run_tv_casting_test.py +++ b/scripts/tests/run_tv_casting_test.py @@ -16,44 +16,31 @@ import logging import os -import re +import signal import subprocess import sys import tempfile import time -from typing import List, Optional, TextIO, Tuple, Union +from typing import List, TextIO, Tuple import click +from linux.tv_casting_test_sequence_utils import App, Sequence, Step +from linux.tv_casting_test_sequences import START_APP, STOP_APP -# Configure logging format. -logging.basicConfig(level=logging.INFO, format='%(levelname)s - %(message)s') - -# The maximum amount of time to wait for the Linux tv-app to start before timeout. -TV_APP_MAX_START_WAIT_SEC = 2 +""" +This script can be used to validate the casting experience between the Linux tv-casting-app and the Linux tv-app. -# The maximum amount of time to commission the Linux tv-casting-app and the tv-app before timeout. -COMMISSIONING_STAGE_MAX_WAIT_SEC = 15 +It runs a series of test sequences that check for expected output lines from the tv-casting-app and the tv-app in +a deterministic order. If these lines are not found, it indicates an issue with the casting experience. +""" -# The maximum amount of time to test that the launchURL is sent from the Linux tv-casting-app and received on the tv-app before timeout. -TEST_LAUNCHURL_MAX_WAIT_SEC = 10 - -# The maximum amount of time to verify the subscription state in the Linux tv-casting-app output before timeout. -VERIFY_SUBSCRIPTION_STATE_MAX_WAIT_SEC = 10 +# Configure logging format. +logging.basicConfig(level=logging.INFO, format='%(levelname)s - %(message)s') # File names of logs for the Linux tv-casting-app and the Linux tv-app. LINUX_TV_APP_LOGS = 'Linux-tv-app-logs.txt' LINUX_TV_CASTING_APP_LOGS = 'Linux-tv-casting-app-logs.txt' -# Values that identify the Linux tv-app and are noted in the 'Device Configuration' in the Linux tv-app output -# as well as under the 'Discovered Commissioner' details in the Linux tv-casting-app output. -VENDOR_ID = 0xFFF1 # Spec 7.20.2.1 MEI code: test vendor IDs are 0xFFF1 to 0xFFF4 -PRODUCT_ID = 0x8001 # Test product id -DEVICE_TYPE_CASTING_VIDEO_PLAYER = 0x23 # Device type library 10.3: Casting Video Player - -# Values to verify the subscription state against from the `ReportDataMessage` in the Linux tv-casting-app output. -CLUSTER_MEDIA_PLAYBACK = '0x506' # Application Cluster Spec 6.10.3 Cluster ID: Media Playback -ATTRIBUTE_CURRENT_PLAYBACK_STATE = '0x0000_0000' # Application Cluster Spec 6.10.6 Attribute ID: Current State of Playback - class ProcessManager: """A context manager for managing subprocesses. @@ -76,26 +63,6 @@ def __exit__(self, exception_type, exception_value, traceback): self.process.wait() -class LogValueExtractor: - """A utility class for extracting values from log lines. - - This class provides a centralized way to extract values from log lines and manage the error handling and logging process. - """ - - def __init__(self, casting_state: str, log_paths: List[str]): - self.casting_state = casting_state - self.log_paths = log_paths - - def extract_from(self, line: str, value_name: str): - if value_name in line: - try: - return extract_value_from_string(line, value_name, self.casting_state, self.log_paths) - except ValueError: - logging.error(f'Failed to extract `{value_name}` value from line: {line}') - handle_casting_failure(self.casting_state, self.log_paths) - return None - - def dump_temporary_logs_to_console(log_file_path: str): """Dump log file to the console; log file will be removed once the function exits.""" """Write the entire content of `log_file_path` to the console.""" @@ -106,508 +73,200 @@ def dump_temporary_logs_to_console(log_file_path: str): print(line.rstrip()) -def handle_casting_failure(casting_state: str, log_file_paths: List[str]): - """Log '{casting_state} failed!' as error, dump log files to console, exit on error.""" - logging.error(f'{casting_state} failed!') +def handle_casting_failure(test_sequence_name: str, log_file_paths: List[str]): + """Log failure of validation of test sequence as error, dump log files to console, exit on error.""" + logging.error(f'{test_sequence_name} - Validation of test sequence failed.') for log_file_path in log_file_paths: try: dump_temporary_logs_to_console(log_file_path) except Exception as e: - logging.exception(f'Failed to dump {log_file_path}: {e}') + logging.exception(f'{test_sequence_name} - Failed to dump {log_file_path}: {e}') sys.exit(1) -def extract_value_from_string(line: str, value_name: str, casting_state: str, log_paths) -> str: - """Extract and return value from given input string. - - Some string examples as they are received from the Linux tv-casting-app and/or tv-app output: - 1. On 'darwin' machines: - \x1b[0;34m[1715206773402] [20056:2842184] [DMG] Cluster = 0x506,\x1b[0m - The substring to be extracted here is '0x506'. - - Or: - \x1b[0;32m[1714582264602] [77989:2286038] [SVR] Discovered Commissioner #0\x1b[0m - The integer value to be extracted here is '0'. - - Or: - \x1b[0;34m[1713741926895] [7276:9521344] [DIS] Vendor ID: 65521\x1b[0m - The integer value to be extracted here is '65521'. - - Or: - \x1b[0;34m[1714583616179] [7029:2386956] [SVR] device Name: Test TV casting app\x1b[0m - The substring to be extracted here is 'Test TV casting app'. +def stop_app(test_sequence_name: str, app_name: str, app: subprocess.Popen): + """Stop the given `app` subprocess.""" - 2. On 'linux' machines: - [1716224960.316809][6906:6906] CHIP:DMG: \t\t\t\t\tCluster = 0x506,\n - [1716224958.576320][6906:6906] CHIP:SVR: Discovered Commissioner #0 - [1716224958.576407][6906:6906] CHIP:DIS: \tVendor ID: 65521\n - [1716224959.580746][6906:6906] CHIP:SVR: \tdevice Name: Test TV casting app\n - """ - log_line_pattern = '' - if sys.platform == 'darwin': - log_line_pattern = r'\x1b\[0;\d+m\[\d+\] \[\d+:\d+\] \[[A-Z]{1,3}\] (.+)\x1b\[0m' - elif sys.platform == 'linux': - log_line_pattern = r'\[\d+\.\d+\]\[\d+:\d+\] [A-Z]{1,4}:[A-Z]{1,3}: (.+)' - - log_line_match = re.search(log_line_pattern, line) - - if log_line_match: - log_text_of_interest = log_line_match.group(1) - - if '=' in log_text_of_interest: - delimiter = '=' - elif '#' in log_text_of_interest: - delimiter = '#' - else: - delimiter = ':' + app.terminate() + app_exit_code = app.wait() - return log_text_of_interest.split(delimiter)[-1].strip(' ,') + if app.poll() is None: + logging.error(f'{test_sequence_name}: Failed to stop running {app_name}. Process is still running.') else: - raise ValueError(f'Could not extract {value_name} from the following line: {line}') - - -def validate_value(casting_state: str, expected_value: Union[str, int], log_paths: List[str], line: str, value_name: str) -> Optional[str]: - """Validate a value in a string against an expected value during a given casting state.""" - log_value_extractor = LogValueExtractor(casting_state, log_paths) - value = log_value_extractor.extract_from(line, value_name) - if not value: - logging.error(f'Failed to extract {value_name} value from the following line: {line}') - logging.error(f'Failed to validate against the expected {value_name} value: {expected_value}!') - handle_casting_failure(casting_state, log_paths) - - if isinstance(expected_value, int): - value = int(value) - - if value != expected_value: - logging.error(f'{value_name} does not match the expected value!') - logging.error(f'Expected {value_name}: {expected_value}') - logging.error(line.rstrip('\n')) - handle_casting_failure(casting_state, log_paths) - - # Return the line containing the valid value. - return line.rstrip('\n') - - -def start_up_tv_app_success(tv_app_process: subprocess.Popen, linux_tv_app_log_file: TextIO) -> bool: - """Check if the Linux tv-app is able to successfully start or until timeout occurs.""" - start_wait_time = time.time() - - while True: - # Check if the time elapsed since the start wait time exceeds the maximum allowed startup time for the TV app. - if time.time() - start_wait_time > TV_APP_MAX_START_WAIT_SEC: - logging.error('The Linux tv-app process did not start successfully within the timeout.') - return False - - tv_app_output_line = tv_app_process.stdout.readline() - - linux_tv_app_log_file.write(tv_app_output_line) - linux_tv_app_log_file.flush() - - # Check if the Linux tv-app started successfully. - if "Started commissioner" in tv_app_output_line: - logging.info('Linux tv-app is up and running!') - return True - - -def initiate_cast_request_success(tv_casting_app_info: Tuple[subprocess.Popen, TextIO], valid_discovered_commissioner_number: str) -> bool: - """Initiate commissioning between Linux tv-casting-app and tv-app by sending `cast request {valid_discovered_commissioner_number}` via Linux tv-casting-app process.""" - tv_casting_app_process, linux_tv_casting_app_log_file = tv_casting_app_info - - start_wait_time = time.time() - - while True: - # Check if we exceeded the maximum wait time for initiating 'cast request' from the Linux tv-casting-app to the Linux tv-app. - if time.time() - start_wait_time > COMMISSIONING_STAGE_MAX_WAIT_SEC: - logging.error( - f'The command `cast request {valid_discovered_commissioner_number}` was not issued to the Linux tv-casting-app process within the timeout.') - return False - - tv_casting_app_output_line = tv_casting_app_process.stdout.readline() - if tv_casting_app_output_line: - linux_tv_casting_app_log_file.write(tv_casting_app_output_line) - linux_tv_casting_app_log_file.flush() - - if 'cast request 0' in tv_casting_app_output_line: - tv_casting_app_process.stdin.write('cast request ' + valid_discovered_commissioner_number + '\n') - tv_casting_app_process.stdin.flush() - # Move to the next line otherwise we will keep entering this code block - next_line = tv_casting_app_process.stdout.readline() - linux_tv_casting_app_log_file.write(next_line) - linux_tv_casting_app_log_file.flush() - next_line = next_line.rstrip('\n') - logging.info(f'Sent `{next_line}` to the Linux tv-casting-app process.') - - return True - - -def extract_device_info_from_tv_casting_app(tv_casting_app_info: Tuple[subprocess.Popen, TextIO], casting_state: str, log_paths: List[str]) -> Tuple[Optional[str], Optional[int], Optional[int]]: - """Extract device information from the 'Identification Declaration' block in the Linux tv-casting-app output.""" - tv_casting_app_process, linux_tv_casting_app_log_file = tv_casting_app_info - log_value_extractor = LogValueExtractor(casting_state, log_paths) - - device_name = None - vendor_id = None - product_id = None - - for line in tv_casting_app_process.stdout: - linux_tv_casting_app_log_file.write(line) - linux_tv_casting_app_log_file.flush() - - if value := log_value_extractor.extract_from(line, 'device Name'): - device_name = value - elif value := log_value_extractor.extract_from(line, 'vendor id'): - vendor_id = int(value) - elif value := log_value_extractor.extract_from(line, 'product id'): - product_id = int(value) - - if device_name and vendor_id and product_id: - break - - return device_name, vendor_id, product_id - - -def validate_identification_declaration_message_on_tv_app(tv_app_info: Tuple[subprocess.Popen, TextIO], expected_device_name: str, expected_vendor_id: int, expected_product_id: int, log_paths: List[str]) -> bool: - """Validate device information from the 'Identification Declaration' block from the Linux tv-app output against the expected values.""" - tv_app_process, linux_tv_app_log_file = tv_app_info - - parsing_identification_block = False - start_wait_time = time.time() - - while True: - # Check if we exceeded the maximum wait time for validating the device information from the Linux tv-app to the corresponding values from the Linux tv-app. - if time.time() - start_wait_time > COMMISSIONING_STAGE_MAX_WAIT_SEC: - logging.error( - 'The device information from the Linux tv-app output was not validated against the corresponding values from the Linux tv-casting-app output within the timeout.') - return False - - tv_app_line = tv_app_process.stdout.readline() - - if tv_app_line: - linux_tv_app_log_file.write(tv_app_line) - linux_tv_app_log_file.flush() - - if 'Identification Declaration Start' in tv_app_line: - logging.info('Found the `Identification Declaration` block in the Linux tv-app output:') - logging.info(tv_app_line.rstrip('\n')) - parsing_identification_block = True - elif parsing_identification_block: - logging.info(tv_app_line.rstrip('\n')) - if 'device Name' in tv_app_line: - validate_value('Commissioning', expected_device_name, log_paths, tv_app_line, 'device Name') - elif 'vendor id' in tv_app_line: - validate_value('Commissioning', expected_vendor_id, log_paths, tv_app_line, 'vendor id') - elif 'product id' in tv_app_line: - validate_value('Commissioning', expected_product_id, log_paths, tv_app_line, 'product id') - elif 'Identification Declaration End' in tv_app_line: - parsing_identification_block = False - return True - - -def validate_tv_casting_request_approval(tv_app_info: Tuple[subprocess.Popen, TextIO], log_paths: List[str]) -> bool: - """Validate that the TV casting request from the Linux tv-casting-app to the Linux tv-app is approved by sending `controller ux ok` via Linux tv-app process.""" - tv_app_process, linux_tv_app_log_file = tv_app_info - - start_wait_time = time.time() - - while True: - # Check if we exceeded the maximum wait time for sending 'controller ux ok' from the Linux tv-app to the Linux tv-casting-app. - if time.time() - start_wait_time > COMMISSIONING_STAGE_MAX_WAIT_SEC: - logging.error('The cast request from the Linux tv-casting-app to the Linux tv-app was not approved within the timeout.') - return False - - tv_app_line = tv_app_process.stdout.readline() - - if tv_app_line: - linux_tv_app_log_file.write(tv_app_line) - linux_tv_app_log_file.flush() - - if 'PROMPT USER: Test TV casting app is requesting permission to cast to this TV, approve?' in tv_app_line: - logging.info(tv_app_line.rstrip('\n')) - elif 'Via Shell Enter: controller ux ok|cancel' in tv_app_line: - logging.info(tv_app_line.rstrip('\n')) - - tv_app_process.stdin.write('controller ux ok\n') - tv_app_process.stdin.flush() - - tv_app_line = tv_app_process.stdout.readline() - linux_tv_app_log_file.write(tv_app_line) - linux_tv_app_log_file.flush() - tv_app_line = tv_app_line.rstrip('\n') - logging.info(f'Sent `{tv_app_line}` to the Linux tv-app process.') - return True - - -def validate_commissioning_success(tv_casting_app_info: Tuple[subprocess.Popen, TextIO], tv_app_info: Tuple[subprocess.Popen, TextIO], log_paths: List[str]) -> bool: - """Parse output of Linux tv-casting-app and Linux tv-app output for strings indicating commissioning status.""" - tv_casting_app_process, linux_tv_casting_app_log_file = tv_casting_app_info - tv_app_process, linux_tv_app_log_file = tv_app_info - - start_wait_time = time.time() - - while True: - # Check if we exceeded the maximum wait time for validating commissioning success between the Linux tv-casting-app and the Linux tv-app. - if time.time() - start_wait_time > COMMISSIONING_STAGE_MAX_WAIT_SEC: - logging.error( - 'The commissioning between the Linux tv-casting-app process and the Linux tv-app process did not complete successfully within the timeout.') - return False - - tv_casting_line = tv_casting_app_process.stdout.readline() - tv_app_line = tv_app_process.stdout.readline() - - if tv_casting_line: - linux_tv_casting_app_log_file.write(tv_casting_line) - linux_tv_casting_app_log_file.flush() - - if 'Commissioning completed successfully' in tv_casting_line: - logging.info('Commissioning success noted on the Linux tv-casting-app output:') - logging.info(tv_casting_line.rstrip('\n')) - elif 'Commissioning failed' in tv_casting_line: - logging.error('Commissioning failed noted on the Linux tv-casting-app output:') - logging.error(tv_casting_line.rstrip('\n')) - return False - - if tv_app_line: - linux_tv_app_log_file.write(tv_app_line) - linux_tv_app_log_file.flush() - - if 'PROMPT USER: commissioning success' in tv_app_line: - logging.info('Commissioning success noted on the Linux tv-app output:') - logging.info(tv_app_line) + if app_exit_code < 0: + signal_number = -app_exit_code + if signal_number == signal.SIGTERM.value: + logging.info(f'{test_sequence_name}: {app_name} stopped by {signal_number} (SIGTERM) signal.') return True + else: + logging.error( + f'{test_sequence_name}: {app_name} stopped by signal {signal_number} instead of {signal.SIGTERM.value} (SIGTERM).') + else: + logging.error(f'{test_sequence_name}: {app_name} exited with unexpected exit code {app_exit_code}.') + return False -def parse_tv_casting_app_for_report_data_msg(tv_casting_app_info: Tuple[subprocess.Popen, TextIO], log_paths: List[str]): - """Parse the Linux tv-casting-app for `ReportDataMessage` block and return the first message block with valid `Cluster` and `Attribute` values.""" - tv_casting_app_process, linux_tv_casting_app_log_file = tv_casting_app_info - log_value_extractor = LogValueExtractor('Testing subscription', log_paths) - - continue_parsing = False - report_data_message = [] - - start_wait_time = time.time() - - while True: - # Check if we exceeded the maximum wait time to parse the Linux tv-casting-app output for `ReportDataMessage` block. - if time.time() - start_wait_time > VERIFY_SUBSCRIPTION_STATE_MAX_WAIT_SEC: - logging.error( - 'The relevant `ReportDataMessage` block for the MediaPlayback:CurrentState subscription was not found in the Linux tv-casting-app process within the timeout.') - report_data_message.clear() - return report_data_message - - tv_casting_line = tv_casting_app_process.stdout.readline() - - if tv_casting_line: - linux_tv_casting_app_log_file.write(tv_casting_line) - linux_tv_casting_app_log_file.flush() - - if 'ReportDataMessage =' in tv_casting_line: - report_data_message.append(tv_casting_line.rstrip('\n')) - continue_parsing = True - elif continue_parsing: - report_data_message.append(tv_casting_line.rstrip('\n')) - - if cluster_value := log_value_extractor.extract_from(tv_casting_line, 'Cluster ='): - if cluster_value != CLUSTER_MEDIA_PLAYBACK: - report_data_message.clear() - continue_parsing = False - - elif attribute_value := log_value_extractor.extract_from(tv_casting_line, 'Attribute ='): - if attribute_value != ATTRIBUTE_CURRENT_PLAYBACK_STATE: - report_data_message.clear() - continue_parsing = False - - elif 'InteractionModelRevision' in tv_casting_line: - # Capture the closing brace `}` of the `ReportDataMessage` block. - tv_casting_line = tv_casting_app_process.stdout.readline() - linux_tv_casting_app_log_file.write(tv_casting_line) - linux_tv_casting_app_log_file.flush() - report_data_message.append(tv_casting_line.rstrip('\n')) - return report_data_message +def parse_output_msg_in_subprocess( + tv_casting_app_info: Tuple[subprocess.Popen, TextIO], + tv_app_info: Tuple[subprocess.Popen, TextIO], + log_paths: List[str], + test_sequence_name: str, + test_sequence_step: Step +): + """Parse the output of a given `app` subprocess and validate its output against the expected `output_msg` in the given `Step`.""" -def parse_tv_app_output_for_launchUrl_msg_success(tv_app_info: Tuple[subprocess.Popen, TextIO], log_paths: List[str]): - """Parse the Linux tv-app output for the relevant string indicating that the launchUrl was received.""" + if not test_sequence_step.output_msg: + logging.error(f'{test_sequence_name} - No output message provided in the test sequence step.') + return False - tv_app_process, linux_tv_app_log_file = tv_app_info + app_subprocess, app_log_file = (tv_casting_app_info if test_sequence_step.app == App.TV_CASTING_APP else tv_app_info) start_wait_time = time.time() + msg_block = [] - while True: - # Check if we exceeded the maximum wait time to parse the Linux tv-app output for the string related to the launchUrl. - if time.time() - start_wait_time > COMMISSIONING_STAGE_MAX_WAIT_SEC: + current_index = 0 + while current_index < len(test_sequence_step.output_msg): + # Check if we exceeded the maximum wait time to parse for the output string(s). + if time.time() - start_wait_time > test_sequence_step.timeout_sec: logging.error( - 'The relevant launchUrl string was not found in the Linux tv-app process within the timeout.') + f'{test_sequence_name} - Did not find the expected output string(s) in the {test_sequence_step.app.value} subprocess within the timeout: {test_sequence_step.output_msg}') return False - tv_app_line = tv_app_process.stdout.readline() + output_line = app_subprocess.stdout.readline() + + if output_line: + app_log_file.write(output_line) + app_log_file.flush() + + if (test_sequence_step.output_msg[current_index] in output_line): + msg_block.append(output_line.rstrip('\n')) + current_index += 1 + elif msg_block: + msg_block.append(output_line.rstrip('\n')) + if (test_sequence_step.output_msg[0] in output_line): + msg_block.clear() + msg_block.append(output_line.rstrip('\n')) + current_index = 1 + # Sanity check that `Discovered Commissioner #0` is the valid commissioner. + elif 'Discovered Commissioner #' in output_line: + logging.error(f'{test_sequence_name} - The valid discovered commissioner should be `Discovered Commissioner #0`.') + handle_casting_failure(test_sequence_name, log_paths) + + if current_index == len(test_sequence_step.output_msg): + logging.info(f'{test_sequence_name} - Found the expected output string(s) in the {test_sequence_step.app.value} subprocess:') + for line in msg_block: + logging.info(f'{test_sequence_name} - {line}') - if tv_app_line: - linux_tv_app_log_file.write(tv_app_line) - linux_tv_app_log_file.flush() - - if 'ContentLauncherManager::HandleLaunchUrl TEST CASE ContentURL=https://www.test.com/videoid DisplayString=Test video' in tv_app_line: - logging.info('Found the launchUrl in the Linux tv-app output:') - logging.info(tv_app_line.rstrip('\n')) return True -def parse_tv_casting_app_output_for_launchUrl_msg_success(tv_casting_app_info: Tuple[subprocess.Popen, TextIO], log_paths: List[str]): - """Parse the Linux tv-casting-app output for relevant strings indicating that the launchUrl was sent.""" - - tv_casting_app_process, linux_tv_casting_app_log_file = tv_casting_app_info - - continue_parsing_invoke_response_msg_block = False - found_example_data_msg = False - start_wait_time = time.time() - - while True: - # Check if we exceeded the maximum wait time to parse the Linux tv-casting-app output for strings related to the launchUrl. - if time.time() - start_wait_time > TEST_LAUNCHURL_MAX_WAIT_SEC: - logging.error( - 'The relevant launchUrl strings were not found in the Linux tv-casting-app process within the timeout.') - return False - - tv_casting_line = tv_casting_app_process.stdout.readline() - - if tv_casting_line: - linux_tv_casting_app_log_file.write(tv_casting_line) - linux_tv_casting_app_log_file.flush() - - if 'InvokeResponseMessage =' in tv_casting_line: - logging.info('Found the `InvokeResponseMessage` block in the Linux tv-casting-app output:') - logging.info(tv_casting_line.rstrip('\n')) - continue_parsing_invoke_response_msg_block = True - - elif continue_parsing_invoke_response_msg_block: - # Sanity check for `exampleData` in the `InvokeResponseMessage` block. - if 'exampleData' in tv_casting_line: - found_example_data_msg = True - - elif 'Received Command Response Data' in tv_casting_line: - if not found_example_data_msg: - logging.error('The `exampleData` string was not found in the `InvokeResponseMessage` block.') - return False - - logging.info('Found the `Received Command Response Data` string in the Linux tv-casting-app output:') - logging.info(tv_casting_line.rstrip('\n')) - return True - - logging.info(tv_casting_line.rstrip('\n')) - - -def test_discovery_fn(tv_casting_app_info: Tuple[subprocess.Popen, TextIO], log_paths: List[str]) -> Optional[str]: - """Parse the output of the Linux tv-casting-app to find a valid commissioner.""" - tv_casting_app_process, linux_tv_casting_app_log_file = tv_casting_app_info - - valid_discovered_commissioner = None - valid_vendor_id = None - valid_product_id = None - valid_device_type = None - - # Read the output as we receive it from the tv-casting-app subprocess. - for line in tv_casting_app_process.stdout: - linux_tv_casting_app_log_file.write(line) - linux_tv_casting_app_log_file.flush() - - # Fail fast if "No commissioner discovered" string found. - if 'No commissioner discovered' in line: - logging.error(line.rstrip('\n')) - handle_casting_failure('Discovery', log_paths) - - elif 'Discovered Commissioner' in line: - valid_discovered_commissioner = line.rstrip('\n') - - elif valid_discovered_commissioner: - # Continue parsing the output for the information of interest under 'Discovered Commissioner' - if 'Vendor ID:' in line: - valid_vendor_id = validate_value('Discovery', VENDOR_ID, log_paths, line, 'Vendor ID') - - elif 'Product ID:' in line: - valid_product_id = validate_value('Discovery', PRODUCT_ID, log_paths, line, 'Product ID') - - elif 'Device Type:' in line: - valid_device_type = validate_value('Discovery', DEVICE_TYPE_CASTING_VIDEO_PLAYER, log_paths, line, 'Device Type') - - # A valid commissioner has VENDOR_ID, PRODUCT_ID, and DEVICE TYPE in its list of entries. - if valid_vendor_id and valid_product_id and valid_device_type: - logging.info('Found a valid commissioner in the Linux tv-casting-app output:') - logging.info(valid_discovered_commissioner) - logging.info(valid_vendor_id) - logging.info(valid_product_id) - logging.info(valid_device_type) - logging.info('Discovery success!\n') - break - - return valid_discovered_commissioner - - -def test_commissioning_fn(valid_discovered_commissioner_number, tv_casting_app_info: Tuple[subprocess.Popen, TextIO], tv_app_info: Tuple[subprocess.Popen, TextIO], log_paths: List[str]): - """Test commissioning between Linux tv-casting-app and Linux tv-app.""" - - if not initiate_cast_request_success(tv_casting_app_info, valid_discovered_commissioner_number): - handle_casting_failure('Commissioning', log_paths) - - # Extract the values from the 'Identification Declaration' block in the tv-casting-app output that we want to validate against. - expected_device_name, expected_vendor_id, expected_product_id = extract_device_info_from_tv_casting_app( - tv_casting_app_info, 'Commissioning', log_paths) - - if not expected_device_name or not expected_vendor_id or not expected_product_id: - logging.error('There is an error with the expected device info values that were extracted from the `Identification Declaration` block.') - logging.error( - f'expected_device_name: {expected_device_name}, expected_vendor_id: {expected_vendor_id}, expected_product_id: {expected_product_id}') - handle_casting_failure('Commissioning', log_paths) - - if not validate_identification_declaration_message_on_tv_app(tv_app_info, expected_device_name, expected_vendor_id, expected_product_id, log_paths): - handle_casting_failure('Commissioning', log_paths) - - if not validate_tv_casting_request_approval(tv_app_info, log_paths): - handle_casting_failure('Commissioning', log_paths) - - if not validate_commissioning_success(tv_casting_app_info, tv_app_info, log_paths): - handle_casting_failure('Commissioning', log_paths) - - -def test_subscription_fn(tv_casting_app_info: Tuple[subprocess.Popen, TextIO], log_paths: List[str]): - """Test the subscription state of the Linux tv-casting-app by validating the `ReportDataMessage` block.""" - - valid_report_data_msg = parse_tv_casting_app_for_report_data_msg(tv_casting_app_info, log_paths) - - if valid_report_data_msg: - logging.info('Found the `ReportDataMessage` block in the Linux tv-casting-app output:') - - for line in valid_report_data_msg: - logging.info(line) - - logging.info('Testing subscription success!\n') - valid_report_data_msg.clear() +def send_input_cmd_to_subprocess( + tv_casting_app_info: Tuple[subprocess.Popen, TextIO], + tv_app_info: Tuple[subprocess.Popen, TextIO], + test_sequence_name: str, + test_sequence_step: Step +): + """Send a given input command (`input_cmd`) from the `Step` to its given `app` subprocess.""" + + if not test_sequence_step.input_cmd: + logging.error(f'{test_sequence_name} - No input command provided in the test sequence step.') + return False + + app_subprocess, app_log_file = (tv_casting_app_info if test_sequence_step.app == App.TV_CASTING_APP else tv_app_info) + + app_subprocess.stdin.write(test_sequence_step.input_cmd) + app_subprocess.stdin.flush() + + # Read in the next line which should be the `input_cmd` that was issued. + next_line = app_subprocess.stdout.readline() + app_log_file.write(next_line) + app_log_file.flush() + next_line = next_line.rstrip('\n') + + logging.info(f'{test_sequence_name} - Sent `{next_line}` to the {test_sequence_step.app.value} subprocess.') + + return True + + +def handle_output_msg( + tv_casting_app_info: Tuple[subprocess.Popen, TextIO], + tv_app_info: Tuple[subprocess.Popen, TextIO], + log_paths: List[str], + test_sequence_name: str, + test_sequence_step: Step +): + """Handle the output message (`output_msg`) from a test sequence step.""" + + if not parse_output_msg_in_subprocess(tv_casting_app_info, tv_app_info, log_paths, test_sequence_name, test_sequence_step): + handle_casting_failure(test_sequence_name, log_paths) + + +def handle_input_cmd( + tv_casting_app_info: Tuple[subprocess.Popen, TextIO], + tv_app_info: Tuple[subprocess.Popen, TextIO], + log_paths: List[str], + test_sequence_name: str, + test_sequence_step: Step +): + """Handle the input command (`input_cmd`) from a test sequence step.""" + + tv_casting_app_process, tv_casting_app_log_file = tv_casting_app_info + tv_app_process, tv_app_log_file = tv_app_info + + if test_sequence_step.input_cmd == STOP_APP: + if test_sequence_step.app == App.TV_CASTING_APP: + # Stop the tv-casting-app subprocess. + if not stop_app(test_sequence_name, test_sequence_step.app.value, tv_casting_app_process): + handle_casting_failure(test_sequence_name, log_paths) + elif test_sequence_step.app == App.TV_APP: + # Stop the tv-app subprocess. + if not stop_app(test_sequence_name, test_sequence_step.app.value, tv_app_process): + handle_casting_failure(test_sequence_name, log_paths) else: - handle_casting_failure('Testing subscription', log_paths) + if not send_input_cmd_to_subprocess(tv_casting_app_info, tv_app_info, test_sequence_name, test_sequence_step): + handle_casting_failure(test_sequence_name, log_paths) + +def run_test_sequence_steps( + current_index: int, + test_sequence_name: str, + test_sequence_steps: List[Step], + tv_casting_app_info: Tuple[subprocess.Popen, TextIO], + tv_app_info: Tuple[subprocess.Popen, TextIO], + log_paths: List[str] +): + """Run through the test steps from a test sequence starting from the current index and perform actions based on the presence of `output_msg` or `input_cmd`.""" -def test_launchUrl_fn(tv_casting_app_info: Tuple[subprocess.Popen, TextIO], tv_app_info: Tuple[subprocess.Popen, TextIO], log_paths: List[str]): - """Test that the Linux tv-casting-app sent the launchUrl and that the Linux tv-app received the launchUrl.""" + if test_sequence_steps is None: + logging.error('No test sequence steps provided.') - if not parse_tv_app_output_for_launchUrl_msg_success(tv_app_info, log_paths): - handle_casting_failure('Testing launchUrl', log_paths) + while current_index < len(test_sequence_steps): + # Current step in the list of steps. + test_sequence_step = test_sequence_steps[current_index] - if not parse_tv_casting_app_output_for_launchUrl_msg_success(tv_casting_app_info, log_paths): - handle_casting_failure('Testing launchUrl', log_paths) + # A test sequence step contains either an output_msg or input_cmd entry. + if test_sequence_step.output_msg: + handle_output_msg(tv_casting_app_info, tv_app_info, log_paths, test_sequence_name, test_sequence_step) + elif test_sequence_step.input_cmd: + handle_input_cmd(tv_casting_app_info, tv_app_info, log_paths, test_sequence_name, test_sequence_step) - logging.info('Testing launchUrl success!') + current_index += 1 @click.command() @click.option('--tv-app-rel-path', type=str, default='out/tv-app/chip-tv-app', help='Path to the Linux tv-app executable.') @click.option('--tv-casting-app-rel-path', type=str, default='out/tv-casting-app/chip-tv-casting-app', help='Path to the Linux tv-casting-app executable.') def test_casting_fn(tv_app_rel_path, tv_casting_app_rel_path): - """Test if the Linux tv-casting-app is able to discover and commission the Linux tv-app as part of casting. + """Test if the casting experience between the Linux tv-casting-app and the Linux tv-app continues to work. Default paths for the executables are provided but can be overridden via command line arguments. For example: python3 run_tv_casting_test.py --tv-app-rel-path=path/to/tv-app --tv-casting-app-rel-path=path/to/tv-casting-app """ + # Store the log files to a temporary directory. with tempfile.TemporaryDirectory() as temp_dir: linux_tv_app_log_path = os.path.join(temp_dir, LINUX_TV_APP_LOGS) @@ -615,41 +274,65 @@ def test_casting_fn(tv_app_rel_path, tv_casting_app_rel_path): with open(linux_tv_app_log_path, 'w') as linux_tv_app_log_file, open(linux_tv_casting_app_log_path, 'w') as linux_tv_casting_app_log_file: + # Get all the test sequences. + test_sequences = Sequence.get_test_sequences() + + # Get the test sequence of interest. + test_sequence = Sequence.get_test_sequence_by_name(test_sequences, 'commissionee_generated_passcode_test') + + if not test_sequence: + logging.error('No test sequence found by the test sequence name provided.') + handle_casting_failure(None, []) + + # At this point, we have retrieved the test sequence of interest. + test_sequence_name = test_sequence.name + test_sequence_steps = test_sequence.steps + # Configure command options to disable stdout buffering during tests. disable_stdout_buffering_cmd = [] # On Unix-like systems, use stdbuf to disable stdout buffering. if sys.platform == 'darwin' or sys.platform == 'linux': disable_stdout_buffering_cmd = ['stdbuf', '-o0', '-i0'] + current_index = 0 + if test_sequence_steps[current_index].input_cmd != START_APP: + raise ValueError( + f'{test_sequence_name}: The first step in the test sequence must contain `START_APP` as `input_cmd` to indicate starting the tv-app.') + elif test_sequence_steps[current_index].app != App.TV_APP: + raise ValueError(f'{test_sequence_name}: The first step in the test sequence must be to start up the tv-app.') + current_index += 1 + tv_app_abs_path = os.path.abspath(tv_app_rel_path) # Run the Linux tv-app subprocess. with ProcessManager(disable_stdout_buffering_cmd + [tv_app_abs_path], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) as tv_app_process: + tv_app_info = (tv_app_process, linux_tv_app_log_file) + + # Verify that the tv-app is up and running. + handle_output_msg(None, tv_app_info, [linux_tv_app_log_path], + test_sequence_name, test_sequence_steps[current_index]) + current_index += 1 - if not start_up_tv_app_success(tv_app_process, linux_tv_app_log_file): - handle_casting_failure('Discovery', [linux_tv_app_log_path]) + if test_sequence_steps[current_index].input_cmd != START_APP: + raise ValueError( + f'{test_sequence_name}: The third step in the test sequence must contain `START_APP` as `input_cmd` to indicate starting the tv-casting-app.') + elif test_sequence_steps[current_index].app != App.TV_CASTING_APP: + raise ValueError( + f'{test_sequence_name}: The third step in the test sequence must be to start up the tv-casting-app.') + current_index += 1 tv_casting_app_abs_path = os.path.abspath(tv_casting_app_rel_path) # Run the Linux tv-casting-app subprocess. with ProcessManager(disable_stdout_buffering_cmd + [tv_casting_app_abs_path], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) as tv_casting_app_process: log_paths = [linux_tv_app_log_path, linux_tv_casting_app_log_path] tv_casting_app_info = (tv_casting_app_process, linux_tv_casting_app_log_file) - tv_app_info = (tv_app_process, linux_tv_app_log_file) - valid_discovered_commissioner = test_discovery_fn(tv_casting_app_info, log_paths) - - if not valid_discovered_commissioner: - handle_casting_failure('Discovery', log_paths) - - # We need the valid discovered commissioner number to continue with commissioning. - log_value_extractor = LogValueExtractor('Commissioning', log_paths) - valid_discovered_commissioner_number = log_value_extractor.extract_from( - valid_discovered_commissioner, 'Discovered Commissioner #') - if not valid_discovered_commissioner_number: - logging.error(f'Failed to find `Discovered Commissioner #` in line: {valid_discovered_commissioner}') - handle_casting_failure('Commissioning', log_paths) - - test_commissioning_fn(valid_discovered_commissioner_number, tv_casting_app_info, tv_app_info, log_paths) - test_subscription_fn(tv_casting_app_info, log_paths) - test_launchUrl_fn(tv_casting_app_info, tv_app_info, log_paths) + + # Verify that the server initialization is completed in the tv-casting-app output. + handle_output_msg(tv_casting_app_info, tv_app_info, log_paths, + test_sequence_name, test_sequence_steps[current_index]) + current_index += 1 + + run_test_sequence_steps(current_index, test_sequence_name, test_sequence_steps, + tv_casting_app_info, tv_app_info, log_paths) if __name__ == '__main__': From be26a8f48417f8ed5102240001c6e8a386fc28d2 Mon Sep 17 00:00:00 2001 From: Alex Tsitsiura Date: Thu, 20 Jun 2024 15:27:42 +0300 Subject: [PATCH 2/2] [Telink] W91 BLE/WiFi updates & Update build to docker version 57 (#34027) * [Telink] Enable IPv4 * [Telink] Logs for WiFi debug * [Telink] Get NetIf interface instead of static * [Telink] Add GetSupportedWiFiBandsMask * [Telink] Use ssid for OnNetworkingStatusChange * [Telink] Add join default WiFi network for debug * [Telink] Use key matrix * [Telink] Enable temporarry IPv4 & DHCPV4 Issue with IPv6 UDP bind & CHIP_IPV4 config * [Telink] Fixes to enable and test BLE * [Telink] Alignment with NRF/Zephyr platform * [Telink] Fix minimal mDNS init issue * [Telink] Add DNS server refresh after adding new IPv6 address * [Telink] Move NFCManagerImpl.cpp under chip_enable_nfc * [Telink] Add basic switch context from BLE to WiFi * [Telink] Enable Thread/WiFi start buttons with enabled BLE * [Telink] Places for SwitchToWiFi on finish BLE commisioning part * [Telink] Enable BLE for W91 * [Telink] fix CI error * [Telink] Use BLE/WiFi concurrent mode for W91 * [Telink] Use BLE/WiFi concurrent mode for W91 * [Telink] Remove unused includes * [Telink] Update set ble mac address for w91 and b9x * [Telink] Update to docker version 57 * Restyled by clang-format --------- Co-authored-by: Serhii Salamakha Co-authored-by: Restyled.io --- .github/workflows/chef.yaml | 4 +- .github/workflows/examples-telink.yaml | 4 +- config/telink/chip-module/CMakeLists.txt | 31 ++-- config/telink/chip-module/Kconfig | 11 +- config/telink/chip-module/Kconfig.defaults | 32 +++- .../telink/common/include/AppTaskCommon.h | 6 +- .../telink/common/src/AppTaskCommon.cpp | 70 ++++++-- .../platform/telink/common/src/mainCommon.cpp | 3 +- .../platform/telink/util/include/ThreadUtil.h | 2 +- src/platform/telink/BLEManagerImpl.cpp | 39 +++-- src/platform/telink/BLEManagerImpl.h | 10 +- src/platform/telink/BUILD.gn | 11 +- src/platform/telink/CHIPPlatformConfig.h | 7 +- src/platform/telink/NFCManagerImpl.cpp | 2 - src/platform/telink/ThreadStackManagerImpl.h | 4 +- src/platform/telink/args.gni | 4 - .../telink/tlsr9118bdk40d_3m_flash.overlay | 3 +- .../wifi/ConnectivityManagerImplWiFi.cpp | 1 - src/platform/telink/wifi/TelinkWiFiDriver.cpp | 45 +++++- src/platform/telink/wifi/TelinkWiFiDriver.h | 5 +- src/platform/telink/wifi/WiFiManager.cpp | 150 +++++++++++------- src/platform/telink/wifi/WiFiManager.h | 22 ++- 22 files changed, 321 insertions(+), 145 deletions(-) diff --git a/.github/workflows/chef.yaml b/.github/workflows/chef.yaml index 77c1a21be6b388..b3d3f7f3a90f7c 100644 --- a/.github/workflows/chef.yaml +++ b/.github/workflows/chef.yaml @@ -98,7 +98,7 @@ jobs: if: github.actor != 'restyled-io[bot]' container: - image: ghcr.io/project-chip/chip-build-telink:54 + image: ghcr.io/project-chip/chip-build-telink:57 options: --user root steps: @@ -110,7 +110,7 @@ jobs: platform: telink # - name: Update Zephyr to specific revision (for developers purpose) # shell: bash - # run: scripts/run_in_build_env.sh "python3 scripts/tools/telink/update_zephyr.py 0e8032dfef7e02498f34ba0b5d5d2df71a62adb1" + # run: scripts/run_in_build_env.sh "python3 scripts/tools/telink/update_zephyr.py ab81a585fca6a83b30e1f4e58a021113d6a3acb8" - name: CI Examples Telink shell: bash run: | diff --git a/.github/workflows/examples-telink.yaml b/.github/workflows/examples-telink.yaml index 6c836294381a0f..15926e99cd470c 100644 --- a/.github/workflows/examples-telink.yaml +++ b/.github/workflows/examples-telink.yaml @@ -38,7 +38,7 @@ jobs: if: github.actor != 'restyled-io[bot]' container: - image: ghcr.io/project-chip/chip-build-telink:54 + image: ghcr.io/project-chip/chip-build-telink:57 volumes: - "/tmp/bloat_reports:/tmp/bloat_reports" @@ -57,7 +57,7 @@ jobs: gh-context: ${{ toJson(github) }} # - name: Update Zephyr to specific revision (for developers purpose) - # run: scripts/run_in_build_env.sh "python3 scripts/tools/telink/update_zephyr.py 0e8032dfef7e02498f34ba0b5d5d2df71a62adb1" + # run: scripts/run_in_build_env.sh "python3 scripts/tools/telink/update_zephyr.py ab81a585fca6a83b30e1f4e58a021113d6a3acb8" - name: Build example Telink (B92 retention) Air Quality Sensor App run: | diff --git a/config/telink/chip-module/CMakeLists.txt b/config/telink/chip-module/CMakeLists.txt index 0f2e4edce0f8d1..f8986e7a954fb4 100644 --- a/config/telink/chip-module/CMakeLists.txt +++ b/config/telink/chip-module/CMakeLists.txt @@ -95,39 +95,40 @@ matter_add_gn_arg_bool ("chip_logging" CONFIG_LOG) matter_add_gn_arg_bool ("chip_enable_openthread" CONFIG_NET_L2_OPENTHREAD) matter_add_gn_arg_bool ("chip_openthread_ftd" CONFIG_OPENTHREAD_FTD) matter_add_gn_arg_bool ("chip_config_network_layer_ble" CONFIG_BT) -matter_add_gn_arg_bool ("chip_inet_config_enable_ipv4" CONFIG_NET_IPV4) +matter_add_gn_arg_bool ("chip_inet_config_enable_ipv4" CONFIG_CHIP_IPV4) matter_add_gn_arg_bool ("chip_enable_nfc" CONFIG_CHIP_NFC_COMMISSIONING) matter_add_gn_arg_bool ("chip_enable_ota_requestor" CONFIG_CHIP_OTA_REQUESTOR) -matter_add_gn_arg_bool ("chip_inet_config_enable_tcp_endpoint" CONFIG_CHIP_BUILD_TESTS) +matter_add_gn_arg_bool ("chip_inet_config_enable_tcp_endpoint" FALSE) matter_add_gn_arg_bool ("chip_error_logging" CONFIG_MATTER_LOG_LEVEL GREATER_EQUAL 1) matter_add_gn_arg_bool ("chip_progress_logging" CONFIG_MATTER_LOG_LEVEL GREATER_EQUAL 3) matter_add_gn_arg_bool ("chip_detail_logging" CONFIG_MATTER_LOG_LEVEL GREATER_EQUAL 4) matter_add_gn_arg_bool ("chip_automation_logging" FALSE) matter_add_gn_arg_bool ("chip_enable_wifi" CONFIG_WIFI_W91) matter_add_gn_arg_bool ("chip_enable_icd_server" CONFIG_CHIP_ENABLE_ICD_SUPPORT) +matter_add_gn_arg_bool ("chip_enable_factory_data" CONFIG_CHIP_FACTORY_DATA) +matter_add_gn_arg_bool ("chip_mdns_minimal" CONFIG_WIFI_W91) +matter_add_gn_arg_bool ("chip_mdns_platform" CONFIG_NET_L2_OPENTHREAD) if (CONFIG_CHIP_ENABLE_ICD_SUPPORT) - matter_add_gn_arg_bool ("chip_enable_icd_lit" CONFIG_CHIP_ICD_LIT_SUPPORT) - matter_add_gn_arg_bool ("chip_enable_icd_checkin" CONFIG_CHIP_ICD_CHECK_IN_SUPPORT) - matter_add_gn_arg_bool ("chip_enable_icd_user_active_mode_trigger" CONFIG_CHIP_ICD_UAT_SUPPORT) + matter_add_gn_arg_bool ("chip_enable_icd_lit" CONFIG_CHIP_ICD_LIT_SUPPORT) + matter_add_gn_arg_bool ("chip_enable_icd_checkin" CONFIG_CHIP_ICD_CHECK_IN_SUPPORT) + matter_add_gn_arg_bool ("chip_enable_icd_user_active_mode_trigger" CONFIG_CHIP_ICD_UAT_SUPPORT) endif() -if (CONFIG_CHIP_FACTORY_DATA) - matter_add_gn_arg_bool ("chip_use_transitional_commissionable_data_provider" "false") - matter_add_gn_arg_bool ("chip_enable_factory_data" "true") -elseif (CONFIG_CHIP_FACTORY_DATA_CUSTOM_BACKEND) - matter_add_gn_arg_bool ("chip_use_transitional_commissionable_data_provider" "false") +if (CONFIG_CHIP_FACTORY_DATA OR CONFIG_CHIP_FACTORY_DATA_CUSTOM_BACKEND) + matter_add_gn_arg_bool("chip_use_transitional_commissionable_data_provider" FALSE) + matter_add_gn_arg_bool("chip_use_transitional_device_instance_info_provider" FALSE) endif() if (CONFIG_CHIP_ROTATING_DEVICE_ID) - matter_add_gn_arg_bool("chip_enable_rotating_device_id" "true") - matter_add_gn_arg_bool("chip_enable_additional_data_advertising" "true") + matter_add_gn_arg_bool("chip_enable_rotating_device_id" TRUE) + matter_add_gn_arg_bool("chip_enable_additional_data_advertising" TRUE) endif() -if (CONFIG_NET_L2_OPENTHREAD) - matter_add_gn_arg_string("chip_mdns" "platform") -elseif(CONFIG_WIFI_W91) +if(CONFIG_WIFI_W91) matter_add_gn_arg_string("chip_mdns" "minimal") +elseif (CONFIG_NET_L2_OPENTHREAD) + matter_add_gn_arg_string("chip_mdns" "platform") else() matter_add_gn_arg_string("chip_mdns" "none") endif() diff --git a/config/telink/chip-module/Kconfig b/config/telink/chip-module/Kconfig index 4444cc8e9e44ed..46dc36b8973d77 100644 --- a/config/telink/chip-module/Kconfig +++ b/config/telink/chip-module/Kconfig @@ -178,9 +178,18 @@ config CHIP_LOG_SIZE_OPTIMIZATION full configuration enabled by this option in the platform/telink/CHIPPlatformConfig.h file. +config CHIP_IPV4 + bool "IPv4 support for Matter" + default n + depends on NET_IPV4 + help + If disabled, it allows to build Telink SDK application + with IPv4 support independently of the Matter stack still + running over IPv6. + config CHIP_BUTTON_MANAGER_IRQ_MODE bool "Use GPIO in an IRQ mode instead of polling the GPIO" - default PM || BOARD_TLSR9118BDK40D + default PM help Use GPIO in an IRQ mode to avoid button polling loop and extend the battery lifetime by waking up by GPIO event. GPIO events are working only with GPIO IRQ. This option changes button matrix configuration. diff --git a/config/telink/chip-module/Kconfig.defaults b/config/telink/chip-module/Kconfig.defaults index e6554774187a18..ea5a6822b74cb5 100644 --- a/config/telink/chip-module/Kconfig.defaults +++ b/config/telink/chip-module/Kconfig.defaults @@ -34,11 +34,11 @@ choice LOG_MODE endchoice choice MATTER_LOG_LEVEL_CHOICE - default MATTER_LOG_LEVEL_INF + default MATTER_LOG_LEVEL_DBG endchoice config CHIP_APP_LOG_LEVEL - default 3 # info + default 4 # debug config LOG_DEFAULT_LEVEL default 1 # error @@ -108,7 +108,6 @@ config GPIO # Bluetooth Low Energy configs config BT - default n if BOARD_TLSR9118BDK40D default y if BT @@ -138,10 +137,12 @@ config BT_BUF_ACL_TX_SIZE default 251 config BT_RX_STACK_SIZE - default 810 + default 810 if BT_B9X + default 2048 if BT_W91 config BT_HCI_TX_STACK_SIZE - default 640 + default 640 if BT_B9X + default 2048 if BT_W91 config BT_DEVICE_NAME_GATT_WRITABLE bool @@ -300,13 +301,32 @@ config CHIP_WIFI select WIFI_W91 select WIFI select NET_STATISTICS - select NET_L2_ETHERNET select NET_IPV6_ND # enable Neighbor Discovery to handle Router Advertisements select NET_IPV6_NBR_CACHE select NET_STATISTICS_USER_API +# select NET_IPV4 # TODO: remove IPv4 when IPv6 will be ready (see CHIP_IPV4) +# select NET_CONFIG_NEED_IPV4 +# select NET_DHCPV4 if CHIP_WIFI +config DEFAULT_WIFI_SSID + string "Default WiFi SSID" + depends on CHIP_WIFI + default "" + help + The SSID of network to connect to if no WiFi station configuration exists in NV storage + at the time the device boots. + This option is for testing only and should be disabled in production releases + +config DEFAULT_WIFI_PASSWORD + string "Default WiFi Password" + depends on CHIP_WIFI + default "" + help + The password for the default WiFi network. + This option is for testing only and should be disabled in production releases. + config CHIP_WIFI_CONNECTION_RECOVERY_MINIMUM_INTERVAL int "Define the minimum connection recovery time interval in milliseconds" depends on CHIP_WIFI diff --git a/examples/platform/telink/common/include/AppTaskCommon.h b/examples/platform/telink/common/include/AppTaskCommon.h index b1e5ad356692bb..ff3f7bf174a939 100644 --- a/examples/platform/telink/common/include/AppTaskCommon.h +++ b/examples/platform/telink/common/include/AppTaskCommon.h @@ -76,6 +76,7 @@ class AppTaskCommon { kButtonId_ExampleAction = 1, kButtonId_FactoryReset, + kButtonId_StartWiFi, kButtonId_StartThread, kButtonId_StartBleAdv } ButtonId; @@ -102,9 +103,12 @@ class AppTaskCommon static void StartBleAdvButtonEventHandler(void); static void StartBleAdvHandler(AppEvent * aEvent); -#if !CHIP_DEVICE_CONFIG_ENABLE_CHIPOBLE && CHIP_DEVICE_CONFIG_ENABLE_THREAD +#if CHIP_DEVICE_CONFIG_ENABLE_THREAD static void StartThreadButtonEventHandler(void); static void StartThreadHandler(AppEvent * aEvent); +#elif CHIP_DEVICE_CONFIG_ENABLE_WIFI + static void StartWiFiButtonEventHandler(void); + static void StartWiFiHandler(AppEvent * aEvent); #endif static void ExampleActionButtonEventHandler(void); diff --git a/examples/platform/telink/common/src/AppTaskCommon.cpp b/examples/platform/telink/common/src/AppTaskCommon.cpp index 96bb3ca87463dc..59ad41b3c45bde 100644 --- a/examples/platform/telink/common/src/AppTaskCommon.cpp +++ b/examples/platform/telink/common/src/AppTaskCommon.cpp @@ -24,7 +24,12 @@ #include "LEDManager.h" #include "PWMManager.h" +#if CHIP_DEVICE_CONFIG_ENABLE_THREAD #include "ThreadUtil.h" +#elif CHIP_DEVICE_CONFIG_ENABLE_WIFI +#include +#include +#endif #include #include @@ -74,7 +79,7 @@ uint8_t sFactoryResetCntr = 0; bool sIsCommissioningFailed = false; bool sIsNetworkProvisioned = false; bool sIsNetworkEnabled = false; -bool sIsThreadAttached = false; +bool sIsNetworkAttached = false; bool sHaveBLEConnections = false; #if APP_SET_DEVICE_INFO_PROVIDER @@ -213,13 +218,16 @@ CHIP_ERROR AppTaskCommon::StartApp(void) AppEvent event = {}; -#if !CHIP_DEVICE_CONFIG_ENABLE_CHIPOBLE && CHIP_DEVICE_CONFIG_ENABLE_THREAD +#if !CHIP_DEVICE_CONFIG_ENABLE_CHIPOBLE +#if CHIP_DEVICE_CONFIG_ENABLE_THREAD StartThreadButtonEventHandler(); +#elif CHIP_DEVICE_CONFIG_ENABLE_WIFI + StartWiFiButtonEventHandler(); #endif +#endif /* CHIP_DEVICE_CONFIG_ENABLE_CHIPOBLE */ #ifdef CONFIG_BOOTLOADER_MCUBOOT - if (!chip::DeviceLayer::ConnectivityMgr().IsThreadProvisioned() && - !chip::DeviceLayer::ConnectivityMgr().IsWiFiStationProvisioned()) + if (!sIsNetworkProvisioned) { LOG_INF("Confirm image."); OtaConfirmNewImage(); @@ -359,10 +367,14 @@ void AppTaskCommon::ButtonEventHandler(ButtonId_t btnId, bool btnPressed) case kButtonId_FactoryReset: FactoryResetButtonEventHandler(); break; -#if !CHIP_DEVICE_CONFIG_ENABLE_CHIPOBLE +#if CHIP_DEVICE_CONFIG_ENABLE_THREAD case kButtonId_StartThread: StartThreadButtonEventHandler(); break; +#elif CHIP_DEVICE_CONFIG_ENABLE_WIFI + case kButtonId_StartWiFi: + StartWiFiButtonEventHandler(); + break; #endif case kButtonId_StartBleAdv: StartBleAdvButtonEventHandler(); @@ -433,8 +445,10 @@ void AppTaskCommon::LinkButtons(ButtonManager & buttonManager) buttonManager.addCallback(FactoryResetButtonEventHandler, 0, true); buttonManager.addCallback(ExampleActionButtonEventHandler, 1, true); buttonManager.addCallback(StartBleAdvButtonEventHandler, 2, true); -#if !CHIP_DEVICE_CONFIG_ENABLE_CHIPOBLE && CHIP_DEVICE_CONFIG_ENABLE_THREAD +#if CHIP_DEVICE_CONFIG_ENABLE_THREAD buttonManager.addCallback(StartThreadButtonEventHandler, 3, true); +#elif CHIP_DEVICE_CONFIG_ENABLE_WIFI + buttonManager.addCallback(StartWiFiButtonEventHandler, 3, true); #endif } @@ -442,7 +456,7 @@ void AppTaskCommon::UpdateStatusLED() { if (sIsNetworkProvisioned && sIsNetworkEnabled) { - if (sIsThreadAttached) + if (sIsNetworkAttached) { LedManager::getInstance().setLed(LedManager::EAppLed_Status, 950, 50); } @@ -506,8 +520,8 @@ void AppTaskCommon::StartBleAdvHandler(AppEvent * aEvent) { LOG_INF("StartBleAdvHandler"); - // Don't allow on starting Matter service BLE advertising after Thread provisioning. - if (ConnectivityMgr().IsThreadProvisioned()) + // Disable manual Matter service BLE advertising after device provisioning. + if (sIsNetworkProvisioned) { LOG_INF("Device already commissioned"); return; @@ -578,7 +592,7 @@ void AppTaskCommon::FactoryResetTimerEventHandler(AppEvent * aEvent) LOG_INF("Factory Reset Trigger Counter is cleared"); } -#if !CHIP_DEVICE_CONFIG_ENABLE_CHIPOBLE && CHIP_DEVICE_CONFIG_ENABLE_THREAD +#if CHIP_DEVICE_CONFIG_ENABLE_THREAD void AppTaskCommon::StartThreadButtonEventHandler(void) { AppEvent event; @@ -592,7 +606,7 @@ void AppTaskCommon::StartThreadButtonEventHandler(void) void AppTaskCommon::StartThreadHandler(AppEvent * aEvent) { LOG_INF("StartThreadHandler"); - if (!chip::DeviceLayer::ConnectivityMgr().IsThreadProvisioned()) + if (!sIsNetworkProvisioned) { // Switch context from BLE to Thread #if CHIP_DEVICE_CONFIG_ENABLE_CHIPOBLE @@ -609,6 +623,37 @@ void AppTaskCommon::StartThreadHandler(AppEvent * aEvent) LOG_INF("Device already commissioned"); } } + +#elif CHIP_DEVICE_CONFIG_ENABLE_WIFI +void AppTaskCommon::StartWiFiButtonEventHandler(void) +{ + AppEvent event; + + event.Type = AppEvent::kEventType_Button; + event.ButtonEvent.Action = kButtonPushEvent; + event.Handler = StartWiFiHandler; + GetAppTask().PostEvent(&event); +} + +void AppTaskCommon::StartWiFiHandler(AppEvent * aEvent) +{ + LOG_INF("StartWiFiHandler"); + + if (!strlen(CONFIG_DEFAULT_WIFI_SSID) || !strlen(CONFIG_DEFAULT_WIFI_PASSWORD)) + { + LOG_ERR("default WiFi SSID/Password are not set"); + } + + if (!sIsNetworkProvisioned) + { + net_if_up(InetUtils::GetWiFiInterface()); + NetworkCommissioning::TelinkWiFiDriver().StartDefaultWiFiNetwork(); + } + else + { + LOG_INF("Device already commissioned"); + } +} #endif void AppTaskCommon::ExampleActionButtonEventHandler(void) @@ -673,11 +718,12 @@ void AppTaskCommon::ChipEventHandler(const ChipDeviceEvent * event, intptr_t /* case DeviceEventType::kThreadStateChange: sIsNetworkProvisioned = ConnectivityMgr().IsThreadProvisioned(); sIsNetworkEnabled = ConnectivityMgr().IsThreadEnabled(); - sIsThreadAttached = ConnectivityMgr().IsThreadAttached(); + sIsNetworkAttached = ConnectivityMgr().IsThreadAttached(); #elif CHIP_DEVICE_CONFIG_ENABLE_WIFI case DeviceEventType::kWiFiConnectivityChange: sIsNetworkProvisioned = ConnectivityMgr().IsWiFiStationProvisioned(); sIsNetworkEnabled = ConnectivityMgr().IsWiFiStationEnabled(); + sIsNetworkAttached = ConnectivityMgr().IsWiFiStationConnected(); #if CONFIG_CHIP_OTA_REQUESTOR if (event->WiFiConnectivityChange.Result == kConnectivity_Established) { diff --git a/examples/platform/telink/common/src/mainCommon.cpp b/examples/platform/telink/common/src/mainCommon.cpp index 7018adaa1b8c7c..583d61c0082c57 100644 --- a/examples/platform/telink/common/src/mainCommon.cpp +++ b/examples/platform/telink/common/src/mainCommon.cpp @@ -167,7 +167,8 @@ int main(void) #elif CHIP_DEVICE_CONFIG_ENABLE_WIFI sWiFiCommissioningInstance.Init(); #else - return CHIP_ERROR_INTERNAL; + err = CHIP_ERROR_INTERNAL; + goto exit; #endif /* CHIP_DEVICE_CONFIG_ENABLE_THREAD */ err = GetAppTask().StartApp(); diff --git a/examples/platform/telink/util/include/ThreadUtil.h b/examples/platform/telink/util/include/ThreadUtil.h index df5e8d2eb147ba..429d114647b4d1 100644 --- a/examples/platform/telink/util/include/ThreadUtil.h +++ b/examples/platform/telink/util/include/ThreadUtil.h @@ -19,6 +19,6 @@ #include "AppConfig.h" -#if !CHIP_DEVICE_CONFIG_ENABLE_CHIPOBLE && CHIP_DEVICE_CONFIG_ENABLE_THREAD +#if CHIP_DEVICE_CONFIG_ENABLE_THREAD void StartDefaultThreadNetwork(void); #endif diff --git a/src/platform/telink/BLEManagerImpl.cpp b/src/platform/telink/BLEManagerImpl.cpp index 0d48b8b43cf0f7..55125cc07ef0be 100644 --- a/src/platform/telink/BLEManagerImpl.cpp +++ b/src/platform/telink/BLEManagerImpl.cpp @@ -1,6 +1,6 @@ /* * - * Copyright (c) 2020-2022 Project CHIP Authors + * Copyright (c) 2020-2024 Project CHIP Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -46,7 +46,7 @@ #include extern "C" { -#include +extern __attribute__((noinline)) void telink_bt_blc_mac_init(uint8_t * bt_mac); } #if defined(CONFIG_PM) && !defined(CONFIG_CHIP_ENABLE_PM_DURING_BLE) @@ -117,7 +117,7 @@ CHIP_ERROR InitBLEMACAddress() int error = 0; bt_addr_le_t addr; - b9x_bt_blc_mac_init(addr.a.val); + telink_bt_blc_mac_init(addr.a.val); if (BT_ADDR_IS_STATIC(&addr.a)) // in case of Random static address, create a new id { @@ -284,22 +284,26 @@ inline CHIP_ERROR BLEManagerImpl::PrepareAdvertisingRequest(void) CHIP_ERROR BLEManagerImpl::StartAdvertising(void) { + CHIP_ERROR err = CHIP_NO_ERROR; + +#if CHIP_DEVICE_CONFIG_ENABLE_THREAD if (ConnectivityMgr().IsThreadProvisioned()) { - ChipLogProgress(DeviceLayer, "Thread provisioned, can't StartAdvertising"); + ChipLogProgress(DeviceLayer, "Device provisioned, can't StartAdvertising"); - return CHIP_ERROR_INCORRECT_STATE; + err = CHIP_ERROR_INCORRECT_STATE; } else if (!mBLERadioInitialized) { ThreadStackMgrImpl().StartThreadScan(mInternalScanCallback); } else +#endif // CHIP_DEVICE_CONFIG_ENABLE_THREAD { - return StartAdvertisingProcess(); + err = StartAdvertisingProcess(); } - return CHIP_NO_ERROR; + return err; } CHIP_ERROR BLEManagerImpl::StartAdvertisingProcess(void) @@ -308,11 +312,13 @@ CHIP_ERROR BLEManagerImpl::StartAdvertisingProcess(void) if (!mBLERadioInitialized) { - /* Switch off Thread */ +#if CHIP_DEVICE_CONFIG_ENABLE_THREAD + // Deinit Thread ThreadStackMgrImpl().SetThreadEnabled(false); ThreadStackMgrImpl().SetRadioBlocked(true); +#endif - /* Init BLE stack */ + // Init BLE err = bt_enable(NULL); VerifyOrReturnError(err == 0, MapErrorZephyr(err)); @@ -379,12 +385,14 @@ CHIP_ERROR BLEManagerImpl::StartAdvertisingProcess(void) CHIP_ERROR BLEManagerImpl::StopAdvertising(void) { +#if CHIP_DEVICE_CONFIG_ENABLE_THREAD if (ConnectivityMgr().IsThreadProvisioned()) { - ChipLogProgress(DeviceLayer, "Thread provisioned, StopAdvertising done"); + ChipLogProgress(DeviceLayer, "Device provisioned, StopAdvertising done"); return CHIP_ERROR_INCORRECT_STATE; } +#endif // CHIP_DEVICE_CONFIG_ENABLE_THREAD ReturnErrorOnFailure(System::MapErrorZephyr(bt_le_adv_stop())); @@ -655,6 +663,7 @@ void BLEManagerImpl::_OnPlatformEvent(const ChipDeviceEvent * event) err = HandleTXCharComplete(event); break; +#if CHIP_DEVICE_CONFIG_ENABLE_THREAD case DeviceEventType::kThreadStateChange: err = HandleThreadStateChange(event); break; @@ -666,6 +675,7 @@ void BLEManagerImpl::_OnPlatformEvent(const ChipDeviceEvent * event) case DeviceEventType::kOperationalNetworkEnabled: err = HandleOperationalNetworkEnabled(event); break; +#endif // CHIP_DEVICE_CONFIG_ENABLE_THREAD default: break; @@ -893,6 +903,7 @@ ssize_t BLEManagerImpl::HandleC3Read(struct bt_conn * conId, const struct bt_gat } #endif +#if CHIP_DEVICE_CONFIG_ENABLE_THREAD CHIP_ERROR BLEManagerImpl::HandleOperationalNetworkEnabled(const ChipDeviceEvent * event) { ChipLogDetail(DeviceLayer, "HandleOperationalNetworkEnabled"); @@ -936,12 +947,11 @@ CHIP_ERROR BLEManagerImpl::HandleBleConnectionClosed(const ChipDeviceEvent * eve return CHIP_NO_ERROR; } -/* @todo: move to RadioSwitch module */ void BLEManagerImpl::SwitchToIeee802154(void) { - ChipLogProgress(DeviceLayer, "SwitchToIeee802154"); + ChipLogProgress(DeviceLayer, "Switch context from BLE to Thread"); - /* Deinit BLE stack */ + // Deinit BLE bt_disable(); mBLERadioInitialized = false; @@ -949,10 +959,11 @@ void BLEManagerImpl::SwitchToIeee802154(void) pm_policy_state_lock_put(PM_STATE_SUSPEND_TO_IDLE, PM_ALL_SUBSTATES); #endif - /* Init IEEE802154 */ + // Init Thread ThreadStackMgrImpl().SetRadioBlocked(false); ThreadStackMgrImpl().SetThreadEnabled(true); } +#endif // CHIP_DEVICE_CONFIG_ENABLE_THREAD } // namespace Internal } // namespace DeviceLayer diff --git a/src/platform/telink/BLEManagerImpl.h b/src/platform/telink/BLEManagerImpl.h index 3a2c5d6ca0ed41..38ef878cee7171 100644 --- a/src/platform/telink/BLEManagerImpl.h +++ b/src/platform/telink/BLEManagerImpl.h @@ -1,6 +1,6 @@ /* * - * Copyright (c) 2022 Project CHIP Authors + * Copyright (c) 2022-2024 Project CHIP Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -120,14 +120,16 @@ class BLEManagerImpl final : public BLEManager, private BleLayer, private BlePla CHIP_ERROR HandleBleConnectionClosed(const ChipDeviceEvent * event); /* - @todo WORKAROUND: Due to abscense of non-cuncurrent mode in Matter + WORKAROUND: Due to abscense of non-cuncurrent mode in Matter we are emulating connection to Thread with this events and manually disconnect BLE ass soon as OperationalNetworkEnabled occures. This functionality shall be removed as soon as non-cuncurrent mode would be implemented */ +#if CHIP_DEVICE_CONFIG_ENABLE_THREAD CHIP_ERROR HandleThreadStateChange(const ChipDeviceEvent * event); CHIP_ERROR HandleOperationalNetworkEnabled(const ChipDeviceEvent * event); +#endif // CHIP_DEVICE_CONFIG_ENABLE_THREAD InternalScanCallback * mInternalScanCallback; @@ -164,8 +166,10 @@ class BLEManagerImpl final : public BLEManager, private BleLayer, private BlePla static ssize_t HandleC3Read(struct bt_conn * conn, const struct bt_gatt_attr * attr, void * buf, uint16_t len, uint16_t offset); #endif - /* Switch to IEEE802154 interface. @todo: remove to other module? */ +#if CHIP_DEVICE_CONFIG_ENABLE_THREAD + // Switch context from BLE to Thread void SwitchToIeee802154(void); +#endif // CHIP_DEVICE_CONFIG_ENABLE_THREAD CHIP_ERROR StartAdvertisingProcess(void); }; diff --git a/src/platform/telink/BUILD.gn b/src/platform/telink/BUILD.gn index 787b45ca7c06eb..4d97b41bc2f0ef 100644 --- a/src/platform/telink/BUILD.gn +++ b/src/platform/telink/BUILD.gn @@ -45,8 +45,6 @@ static_library("telink") { "ConnectivityManagerImpl.h", "InetPlatformConfig.h", "KeyValueStoreManagerImpl.h", - "NFCManagerImpl.cpp", - "NFCManagerImpl.h", "PlatformManagerImpl.h", "SystemPlatformConfig.h", ] @@ -83,7 +81,7 @@ static_library("telink") { "ThreadStackManagerImpl.h", ] - if (chip_mdns == "platform") { + if (chip_mdns_platform) { sources += [ "../OpenThread/DnssdImpl.cpp", "../OpenThread/OpenThreadDnssdImpl.cpp", @@ -104,6 +102,13 @@ static_library("telink") { ] } + if (chip_enable_nfc) { + sources += [ + "NFCManagerImpl.cpp", + "NFCManagerImpl.h", + ] + } + if (chip_enable_ota_requestor) { sources += [ "OTAImageProcessorImpl.cpp", diff --git a/src/platform/telink/CHIPPlatformConfig.h b/src/platform/telink/CHIPPlatformConfig.h index 1bb4ab7b07203d..edd68b95380a84 100644 --- a/src/platform/telink/CHIPPlatformConfig.h +++ b/src/platform/telink/CHIPPlatformConfig.h @@ -102,11 +102,7 @@ #define CHIP_CONFIG_MAX_FABRICS 5 #endif -#ifndef CONFIG_CHIP_LOG_SIZE_OPTIMIZATION -#define CONFIG_CHIP_LOG_SIZE_OPTIMIZATION 0 -#endif - -#if CONFIG_CHIP_LOG_SIZE_OPTIMIZATION +#ifdef CONFIG_CHIP_LOG_SIZE_OPTIMIZATION // Disable some of the too detailed log modules to save flash #define CHIP_CONFIG_LOG_MODULE_ExchangeManager_DETAIL 0 #define CHIP_CONFIG_LOG_MODULE_Crypto_DETAIL 0 @@ -129,7 +125,6 @@ #define CHIP_CONFIG_LOG_MODULE_AppServer_DETAIL 0 #define CHIP_CONFIG_LOG_MODULE_Support_DETAIL 0 #define CHIP_CONFIG_LOG_MODULE_Support_PROGRESS 0 -#define CHIP_CONFIG_LOG_MODULE_DeviceLayer_DETAIL 0 #endif #ifndef CHIP_CONFIG_ICD_IDLE_MODE_DURATION_SEC diff --git a/src/platform/telink/NFCManagerImpl.cpp b/src/platform/telink/NFCManagerImpl.cpp index bb40b55305da73..084b297dfb8c46 100644 --- a/src/platform/telink/NFCManagerImpl.cpp +++ b/src/platform/telink/NFCManagerImpl.cpp @@ -17,7 +17,6 @@ #include -#if CHIP_DEVICE_CONFIG_ENABLE_NFC #include #include @@ -102,4 +101,3 @@ CHIP_ERROR NFCManagerImpl::_StopTagEmulation() } // namespace DeviceLayer } // namespace chip -#endif diff --git a/src/platform/telink/ThreadStackManagerImpl.h b/src/platform/telink/ThreadStackManagerImpl.h index b06e33d9f5e0b2..fd3dc6330f2fe7 100644 --- a/src/platform/telink/ThreadStackManagerImpl.h +++ b/src/platform/telink/ThreadStackManagerImpl.h @@ -39,7 +39,7 @@ class ThreadStackManager; class ThreadStackManagerImpl; /** - * Concrete implementation of the ThreadStackManager singleton object for nRF Connect platforms. + * Concrete implementation of the ThreadStackManager singleton object for Telink platforms. */ class ThreadStackManagerImpl final : public ThreadStackManager, public Internal::GenericThreadStackManagerImpl_OpenThread @@ -118,7 +118,7 @@ inline ThreadStackManager & ThreadStackMgr(void) * Returns the platform-specific implementation of the ThreadStackManager singleton object. * * chip applications can use this to gain access to features of the ThreadStackManager - * that are specific to nRF Connect platforms. + * that are specific to Telink platforms. */ inline ThreadStackManagerImpl & ThreadStackMgrImpl(void) { diff --git a/src/platform/telink/args.gni b/src/platform/telink/args.gni index 8a0a79b3a59761..23d446dd82bc64 100644 --- a/src/platform/telink/args.gni +++ b/src/platform/telink/args.gni @@ -12,10 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -chip_device_platform = "telink" - -chip_inet_config_enable_ipv4 = false - declare_args() { # Enable factory data support chip_enable_factory_data = false diff --git a/src/platform/telink/tlsr9118bdk40d_3m_flash.overlay b/src/platform/telink/tlsr9118bdk40d_3m_flash.overlay index 3e42145fb44370..02cd3b6582c8e0 100644 --- a/src/platform/telink/tlsr9118bdk40d_3m_flash.overlay +++ b/src/platform/telink/tlsr9118bdk40d_3m_flash.overlay @@ -7,6 +7,7 @@ /delete-node/ partition@88000; /delete-node/ partition@f0000; /delete-node/ partition@f4000; + /delete-node/ partition@fe000; boot_partition: partition@0 { label = "mcuboot"; reg = <0x00000000 0x20000>; @@ -27,7 +28,7 @@ label = "image-1"; reg = <0x118000 0xe8000>; }; - reserved_partition: partition@200000 { + vendor_partition: partition@200000 { label = "vendor-data"; reg = <0x200000 0x100000>; }; diff --git a/src/platform/telink/wifi/ConnectivityManagerImplWiFi.cpp b/src/platform/telink/wifi/ConnectivityManagerImplWiFi.cpp index 3fefcf709e1b9b..5bef79bb22fdc5 100644 --- a/src/platform/telink/wifi/ConnectivityManagerImplWiFi.cpp +++ b/src/platform/telink/wifi/ConnectivityManagerImplWiFi.cpp @@ -29,7 +29,6 @@ using namespace ::chip; using namespace ::chip::Inet; using namespace ::chip::System; -using namespace ::chip::TLV; namespace chip { namespace DeviceLayer { diff --git a/src/platform/telink/wifi/TelinkWiFiDriver.cpp b/src/platform/telink/wifi/TelinkWiFiDriver.cpp index d6b47053839e35..f6c19a2c1f3030 100644 --- a/src/platform/telink/wifi/TelinkWiFiDriver.cpp +++ b/src/platform/telink/wifi/TelinkWiFiDriver.cpp @@ -122,7 +122,22 @@ void TelinkWiFiDriver::OnNetworkStatusChanged(Status status) if (mpNetworkStatusChangeCallback) { - mpNetworkStatusChangeCallback->OnNetworkingStatusChange(status, NullOptional, NullOptional); + const uint8_t * ssid{}; + size_t ssidLen{}; + WiFiManager::WiFiInfo wifiInfo; + + if (CHIP_NO_ERROR == WiFiManager::Instance().GetWiFiInfo(wifiInfo)) + { + ssid = wifiInfo.mSsid; + ssidLen = wifiInfo.mSsidLen; + } + else + { + ssid = WiFiManager::Instance().GetWantedNetwork().ssid; + ssidLen = WiFiManager::Instance().GetWantedNetwork().ssidLen; + } + mpNetworkStatusChangeCallback->OnNetworkingStatusChange(status, MakeOptional(ByteSpan(wifiInfo.mSsid, wifiInfo.mSsidLen)), + NullOptional); } if (mpConnectCallback) @@ -249,11 +264,10 @@ void TelinkWiFiDriver::LoadFromStorage() mStagingNetwork = network; } -void TelinkWiFiDriver::OnScanWiFiNetworkDone(WiFiManager::WiFiRequestStatus status) +void TelinkWiFiDriver::OnScanWiFiNetworkDone(const WiFiManager::ScanDoneStatus & status) { VerifyOrReturn(mScanCallback != nullptr); - mScanCallback->OnFinished(status == WiFiManager::WiFiRequestStatus::SUCCESS ? Status::kSuccess : Status::kUnknownError, - CharSpan(), &mScanResponseIterator); + mScanCallback->OnFinished(status ? Status::kUnknownError : Status::kSuccess, CharSpan(), &mScanResponseIterator); mScanCallback = nullptr; } @@ -267,7 +281,7 @@ void TelinkWiFiDriver::ScanNetworks(ByteSpan ssid, WiFiDriver::ScanCallback * ca mScanCallback = callback; CHIP_ERROR error = WiFiManager::Instance().Scan( ssid, [](const WiFiScanResponse & response) { Instance().OnScanWiFiNetworkResult(response); }, - [](WiFiManager::WiFiRequestStatus status) { Instance().OnScanWiFiNetworkDone(status); }); + [](const WiFiManager::ScanDoneStatus & status) { Instance().OnScanWiFiNetworkDone(status); }); if (error != CHIP_NO_ERROR) { @@ -276,6 +290,27 @@ void TelinkWiFiDriver::ScanNetworks(ByteSpan ssid, WiFiDriver::ScanCallback * ca } } +uint32_t TelinkWiFiDriver::GetSupportedWiFiBandsMask() const +{ + uint32_t bands = static_cast(1UL << chip::to_underlying(WiFiBandEnum::k2g4)); + return bands; +} + +void TelinkWiFiDriver::StartDefaultWiFiNetwork(void) +{ + chip::ByteSpan ssidSpan = ByteSpan(Uint8::from_const_char(CONFIG_DEFAULT_WIFI_SSID), strlen(CONFIG_DEFAULT_WIFI_SSID)); + chip::ByteSpan passwordSpan = + ByteSpan(Uint8::from_const_char(CONFIG_DEFAULT_WIFI_PASSWORD), strlen(CONFIG_DEFAULT_WIFI_PASSWORD)); + + char debugBuffer[1] = { 0 }; + MutableCharSpan debugText(debugBuffer, 0); + uint8_t outNetworkIndex = 0; + + AddOrUpdateNetwork(ssidSpan, passwordSpan, debugText, outNetworkIndex); + CommitConfiguration(); + RevertConfiguration(); +} + } // namespace NetworkCommissioning } // namespace DeviceLayer } // namespace chip diff --git a/src/platform/telink/wifi/TelinkWiFiDriver.h b/src/platform/telink/wifi/TelinkWiFiDriver.h index b9f9b6374c42e3..9c5b1e61d1f0e1 100644 --- a/src/platform/telink/wifi/TelinkWiFiDriver.h +++ b/src/platform/telink/wifi/TelinkWiFiDriver.h @@ -86,6 +86,7 @@ class TelinkWiFiDriver final : public WiFiDriver Status AddOrUpdateNetwork(ByteSpan ssid, ByteSpan credentials, MutableCharSpan & outDebugText, uint8_t & outNetworkIndex) override; void ScanNetworks(ByteSpan ssid, ScanCallback * callback) override; + uint32_t GetSupportedWiFiBandsMask() const override; static TelinkWiFiDriver & Instance() { @@ -95,7 +96,9 @@ class TelinkWiFiDriver final : public WiFiDriver void OnNetworkStatusChanged(Status status); void OnScanWiFiNetworkResult(const WiFiScanResponse & result); - void OnScanWiFiNetworkDone(WiFiManager::WiFiRequestStatus status); + void OnScanWiFiNetworkDone(const WiFiManager::ScanDoneStatus & status); + + void StartDefaultWiFiNetwork(void); private: void LoadFromStorage(); diff --git a/src/platform/telink/wifi/WiFiManager.cpp b/src/platform/telink/wifi/WiFiManager.cpp index 88195fdbee2ab8..50ea5cf5682089 100644 --- a/src/platform/telink/wifi/WiFiManager.cpp +++ b/src/platform/telink/wifi/WiFiManager.cpp @@ -23,8 +23,6 @@ #include "WiFiManager.h" #include -#include -#include #include #include #include @@ -127,27 +125,44 @@ const Map { WIFI_STATE_GROUP_HANDSHAKE, WiFiManager::StationStatus::PROVISIONING }, { WIFI_STATE_COMPLETED, WiFiManager::StationStatus::FULLY_PROVISIONED } }); -const Map - WiFiManager::sEventHandlerMap({ { NET_EVENT_WIFI_SCAN_RESULT, WiFiManager::ScanResultHandler }, - { NET_EVENT_WIFI_SCAN_DONE, WiFiManager::ScanDoneHandler }, - { NET_EVENT_WIFI_CONNECT_RESULT, WiFiManager::ConnectHandler }, - { NET_EVENT_WIFI_DISCONNECT_RESULT, WiFiManager::DisconnectHandler } }); +const Map WiFiManager::sEventHandlerMap({ + { NET_EVENT_WIFI_SCAN_RESULT, WiFiManager::ScanResultHandler }, + { NET_EVENT_WIFI_SCAN_DONE, WiFiManager::ScanDoneHandler }, + { NET_EVENT_WIFI_CONNECT_RESULT, WiFiManager::ConnectHandler }, + { NET_EVENT_WIFI_DISCONNECT_RESULT, WiFiManager::DisconnectHandler }, +}); void WiFiManager::WifiMgmtEventHandler(net_mgmt_event_callback * cb, uint32_t mgmtEvent, net_if * iface) { - if (0 == strcmp(iface->if_dev->dev->name, "wlan0")) + if (iface == Instance().mNetIf) { Platform::UniquePtr eventData(new uint8_t[cb->info_length]); VerifyOrReturn(eventData); memcpy(eventData.get(), cb->info, cb->info_length); - sEventHandlerMap[mgmtEvent](std::move(eventData)); + sEventHandlerMap[mgmtEvent](std::move(eventData), cb->info_length); + } +} + +void WiFiManager::IPv6MgmtEventHandler(net_mgmt_event_callback * cb, uint32_t mgmtEvent, net_if * iface) +{ + if (((mgmtEvent == NET_EVENT_IPV6_ADDR_ADD) || (mgmtEvent == NET_EVENT_IPV6_ADDR_DEL)) && cb->info) + { + IPv6AddressChangeHandler(cb->info); } } CHIP_ERROR WiFiManager::Init() { + mNetIf = InetUtils::GetWiFiInterface(); + VerifyOrReturnError(mNetIf != nullptr, INET_ERROR_UNKNOWN_INTERFACE); + + net_if_down(mNetIf); // block netif auto start + net_mgmt_init_event_callback(&mWiFiMgmtClbk, WifiMgmtEventHandler, kWifiManagementEvents); + net_mgmt_init_event_callback(&mIPv6MgmtClbk, IPv6MgmtEventHandler, kIPv6ManagementEvents); + net_mgmt_add_event_callback(&mWiFiMgmtClbk); + net_mgmt_add_event_callback(&mIPv6MgmtClbk); ChipLogDetail(DeviceLayer, "WiFiManager has been initialized"); @@ -156,9 +171,6 @@ CHIP_ERROR WiFiManager::Init() CHIP_ERROR WiFiManager::Scan(const ByteSpan & ssid, ScanResultCallback resultCallback, ScanDoneCallback doneCallback, bool internalScan) { - net_if * iface = InetUtils::GetInterface(); - VerifyOrReturnError(nullptr != iface, CHIP_ERROR_INTERNAL); - mInternalScan = internalScan; mScanResultCallback = resultCallback; mScanDoneCallback = doneCallback; @@ -166,7 +178,7 @@ CHIP_ERROR WiFiManager::Scan(const ByteSpan & ssid, ScanResultCallback resultCal mWiFiState = WIFI_STATE_SCANNING; mSsidFound = false; - if (0 != net_mgmt(NET_REQUEST_WIFI_SCAN, iface, NULL, 0)) + if (0 != net_mgmt(NET_REQUEST_WIFI_SCAN, mNetIf, NULL, 0)) { ChipLogError(DeviceLayer, "Scan request failed"); return CHIP_ERROR_INTERNAL; @@ -207,11 +219,8 @@ CHIP_ERROR WiFiManager::Connect(const ByteSpan & ssid, const ByteSpan & credenti CHIP_ERROR WiFiManager::Disconnect() { - net_if * iface = InetUtils::GetInterface(); - VerifyOrReturnError(nullptr != iface, CHIP_ERROR_INTERNAL); - mApplicationDisconnectRequested = true; - int status = net_mgmt(NET_REQUEST_WIFI_DISCONNECT, iface, NULL, 0); + int status = net_mgmt(NET_REQUEST_WIFI_DISCONNECT, mNetIf, NULL, 0); if (status) { @@ -236,11 +245,9 @@ CHIP_ERROR WiFiManager::Disconnect() CHIP_ERROR WiFiManager::GetWiFiInfo(WiFiInfo & info) const { - net_if * iface = InetUtils::GetInterface(); - VerifyOrReturnError(nullptr != iface, CHIP_ERROR_INTERNAL); - struct wifi_iface_status status = { 0 }; + wifi_iface_status status = { 0 }; - if (net_mgmt(NET_REQUEST_WIFI_IFACE_STATUS, iface, &status, sizeof(struct wifi_iface_status))) + if (net_mgmt(NET_REQUEST_WIFI_IFACE_STATUS, mNetIf, &status, sizeof(wifi_iface_status))) { ChipLogError(DeviceLayer, "Status request failed"); return CHIP_ERROR_INTERNAL; @@ -265,7 +272,7 @@ CHIP_ERROR WiFiManager::GetWiFiInfo(WiFiInfo & info) const CHIP_ERROR WiFiManager::GetNetworkStatistics(NetworkStatistics & stats) const { net_stats_wifi data{}; - net_mgmt(NET_REQUEST_STATS_GET_WIFI, InetUtils::GetInterface(), &data, sizeof(data)); + net_mgmt(NET_REQUEST_STATS_GET_WIFI, mNetIf, &data, sizeof(data)); stats.mPacketMulticastRxCount = data.multicast.rx; stats.mPacketMulticastTxCount = data.multicast.tx; @@ -277,10 +284,13 @@ CHIP_ERROR WiFiManager::GetNetworkStatistics(NetworkStatistics & stats) const return CHIP_NO_ERROR; } -void WiFiManager::ScanResultHandler(Platform::UniquePtr data) +void WiFiManager::ScanResultHandler(Platform::UniquePtr data, size_t length) { + // Validate that input data size matches the expected one. + VerifyOrReturn(length == sizeof(wifi_scan_result)); + // Contrary to other handlers, offload accumulating of the scan results from the CHIP thread to the caller's thread - const struct wifi_scan_result * scanResult = reinterpret_cast(data.get()); + const wifi_scan_result * scanResult = reinterpret_cast(data.get()); if (Instance().mInternalScan && Instance().mWantedNetwork.GetSsidSpan().data_equal(ByteSpan(scanResult->ssid, scanResult->ssid_length))) @@ -311,6 +321,7 @@ void WiFiManager::ScanResultHandler(Platform::UniquePtr data) Instance().mWiFiParams.mParams.timeout = Instance().mHandling.mConnectionTimeout.count(); Instance().mWiFiParams.mParams.channel = WIFI_CHANNEL_ANY; Instance().mWiFiParams.mRssi = scanResult->rssi; + Instance().mWiFiParams.mParams.band = WIFI_FREQ_BAND_UNKNOWN; Instance().mSsidFound = true; } } @@ -321,26 +332,29 @@ void WiFiManager::ScanResultHandler(Platform::UniquePtr data) } } -void WiFiManager::ScanDoneHandler(Platform::UniquePtr data) +void WiFiManager::ScanDoneHandler(Platform::UniquePtr data, size_t length) { + // Validate that input data size matches the expected one. + VerifyOrReturn(length == sizeof(wifi_status)); + CHIP_ERROR err = SystemLayer().ScheduleLambda([capturedData = data.get()] { Platform::UniquePtr safePtr(capturedData); - uint8_t * rawData = safePtr.get(); - const wifi_status * status = reinterpret_cast(rawData); - WiFiRequestStatus requestStatus = static_cast(status->status); + uint8_t * rawData = safePtr.get(); + const wifi_status * status = reinterpret_cast(rawData); + ScanDoneStatus scanDoneStatus = status->status; - if (requestStatus == WiFiRequestStatus::FAILURE) + if (scanDoneStatus) { - ChipLogError(DeviceLayer, "Wi-Fi scan finalization failure (%d)", status->status); + ChipLogError(DeviceLayer, "Wi-Fi scan finalization failure (%d)", scanDoneStatus); } else { - ChipLogProgress(DeviceLayer, "Wi-Fi scan done (%d)", status->status); + ChipLogProgress(DeviceLayer, "Wi-Fi scan done"); } if (Instance().mScanDoneCallback && !Instance().mInternalScan) { - Instance().mScanDoneCallback(requestStatus); + Instance().mScanDoneCallback(scanDoneStatus); // restore the connection state from before the scan request was issued Instance().mWiFiState = Instance().mCachedWiFiState; return; @@ -356,13 +370,13 @@ void WiFiManager::ScanDoneHandler(Platform::UniquePtr data) ChipLogProgress(DeviceLayer, "Starting connection recover: re-scanning... (next attempt in %d ms)", currentTimeout.count()); DeviceLayer::SystemLayer().StartTimer(currentTimeout, Recover, nullptr); + return; } Instance().mWiFiState = WIFI_STATE_ASSOCIATING; - net_if * iface = InetUtils::GetInterface(); - VerifyOrReturn(nullptr != iface, CHIP_ERROR_INTERNAL); - if (net_mgmt(NET_REQUEST_WIFI_CONNECT, iface, &(Instance().mWiFiParams.mParams), sizeof(wifi_connect_req_params))) + if (net_mgmt(NET_REQUEST_WIFI_CONNECT, Instance().mNetIf, &(Instance().mWiFiParams.mParams), + sizeof(wifi_connect_req_params))) { ChipLogError(DeviceLayer, "Connection request failed"); if (Instance().mHandling.mOnConnectionFailed) @@ -387,25 +401,24 @@ void WiFiManager::ScanDoneHandler(Platform::UniquePtr data) void WiFiManager::SendRouterSolicitation(System::Layer * layer, void * param) { - net_if * iface = InetUtils::GetInterface(); - if (iface && iface->if_dev->link_addr.type == NET_LINK_ETHERNET) + net_if_start_rs(Instance().mNetIf); + Instance().mRouterSolicitationCounter++; + if (Instance().mRouterSolicitationCounter < kRouterSolicitationMaxCount) { - net_if_start_rs(iface); - Instance().mRouterSolicitationCounter++; - if (Instance().mRouterSolicitationCounter < kRouterSolicitationMaxCount) - { - DeviceLayer::SystemLayer().StartTimer(System::Clock::Milliseconds32(kRouterSolicitationIntervalMs), - SendRouterSolicitation, nullptr); - } - else - { - Instance().mRouterSolicitationCounter = 0; - } + DeviceLayer::SystemLayer().StartTimer(System::Clock::Milliseconds32(kRouterSolicitationIntervalMs), SendRouterSolicitation, + nullptr); + } + else + { + Instance().mRouterSolicitationCounter = 0; } } -void WiFiManager::ConnectHandler(Platform::UniquePtr data) +void WiFiManager::ConnectHandler(Platform::UniquePtr data, size_t length) { + // Validate that input data size matches the expected one. + VerifyOrReturn(length == sizeof(wifi_status)); + CHIP_ERROR err = SystemLayer().ScheduleLambda([capturedData = data.get()] { Platform::UniquePtr safePtr(capturedData); uint8_t * rawData = safePtr.get(); @@ -457,13 +470,41 @@ void WiFiManager::ConnectHandler(Platform::UniquePtr data) } } -void WiFiManager::DisconnectHandler(Platform::UniquePtr) +void WiFiManager::DisconnectHandler(Platform::UniquePtr data, size_t length) { - SystemLayer().ScheduleLambda([] { + // Validate that input data size matches the expected one. + VerifyOrReturn(length == sizeof(wifi_status)); + + CHIP_ERROR err = SystemLayer().ScheduleLambda([] { ChipLogProgress(DeviceLayer, "WiFi station disconnected"); Instance().mWiFiState = WIFI_STATE_DISCONNECTED; Instance().PostConnectivityStatusChange(kConnectivity_Lost); }); + + if (CHIP_NO_ERROR == err) + { + // the ownership has been transferred to the worker thread - release the buffer + data.release(); + } +} + +void WiFiManager::IPv6AddressChangeHandler(const void * data) +{ + const in6_addr * addr = reinterpret_cast(data); + + // Filter out link-local addresses that are not routable outside of a local network. + if (!net_ipv6_is_ll_addr(addr)) + { + // This is needed to send mDNS queries containing updated IPv6 addresses. + ChipDeviceEvent event; + event.Type = DeviceEventType::kDnssdRestartNeeded; + + CHIP_ERROR error = PlatformMgr().PostEvent(&event); + if (error != CHIP_NO_ERROR) + { + ChipLogError(DeviceLayer, "Cannot post event: %" CHIP_ERROR_FORMAT, error.Format()); + } + } } WiFiManager::StationStatus WiFiManager::GetStationStatus() const @@ -529,11 +570,10 @@ System::Clock::Milliseconds32 WiFiManager::CalculateNextRecoveryTime() CHIP_ERROR WiFiManager::SetLowPowerMode(bool onoff) { - net_if * iface = InetUtils::GetInterface(); - VerifyOrReturnError(nullptr != iface, CHIP_ERROR_INTERNAL); + VerifyOrReturnError(nullptr != mNetIf, CHIP_ERROR_INTERNAL); wifi_ps_config currentConfig{}; - if (net_mgmt(NET_REQUEST_WIFI_PS_CONFIG, iface, ¤tConfig, sizeof(currentConfig))) + if (net_mgmt(NET_REQUEST_WIFI_PS_CONFIG, mNetIf, ¤tConfig, sizeof(currentConfig))) { ChipLogError(DeviceLayer, "Get current low power mode config request failed"); return CHIP_ERROR_INTERNAL; @@ -543,7 +583,7 @@ CHIP_ERROR WiFiManager::SetLowPowerMode(bool onoff) (currentConfig.ps_params.enabled == WIFI_PS_DISABLED && onoff == true)) { wifi_ps_params params{ .enabled = onoff ? WIFI_PS_ENABLED : WIFI_PS_DISABLED }; - if (net_mgmt(NET_REQUEST_WIFI_PS, iface, ¶ms, sizeof(params))) + if (net_mgmt(NET_REQUEST_WIFI_PS, mNetIf, ¶ms, sizeof(params))) { ChipLogError(DeviceLayer, "Set low power mode request failed"); return CHIP_ERROR_INTERNAL; diff --git a/src/platform/telink/wifi/WiFiManager.h b/src/platform/telink/wifi/WiFiManager.h index f48d8f9b372469..49c9496f5d13be 100644 --- a/src/platform/telink/wifi/WiFiManager.h +++ b/src/platform/telink/wifi/WiFiManager.h @@ -90,8 +90,9 @@ class WiFiManager TERMINATED = 2 }; + using ScanDoneStatus = decltype(wifi_status::status); using ScanResultCallback = void (*)(const NetworkCommissioning::WiFiScanResponse &); - using ScanDoneCallback = void (*)(WiFiRequestStatus); + using ScanDoneCallback = void (*)(const ScanDoneStatus &); using ConnectionCallback = void (*)(); enum class StationStatus : uint8_t @@ -177,12 +178,13 @@ class WiFiManager CHIP_ERROR ClearStationProvisioningData(); CHIP_ERROR Disconnect(); CHIP_ERROR GetWiFiInfo(WiFiInfo & info) const; + const WiFiNetwork & GetWantedNetwork() const { return mWantedNetwork; } CHIP_ERROR GetNetworkStatistics(NetworkStatistics & stats) const; void AbortConnectionRecovery(); CHIP_ERROR SetLowPowerMode(bool onoff); private: - using NetEventHandler = void (*)(Platform::UniquePtr); + using NetEventHandler = void (*)(Platform::UniquePtr, size_t); struct ConnectionParams { @@ -193,14 +195,18 @@ class WiFiManager constexpr static uint32_t kWifiManagementEvents = NET_EVENT_WIFI_SCAN_RESULT | NET_EVENT_WIFI_SCAN_DONE | NET_EVENT_WIFI_CONNECT_RESULT | NET_EVENT_WIFI_DISCONNECT_RESULT | NET_EVENT_WIFI_IFACE_STATUS; + constexpr static uint32_t kIPv6ManagementEvents = NET_EVENT_IPV6_ADDR_ADD | NET_EVENT_IPV6_ADDR_DEL; + // Event handling static void WifiMgmtEventHandler(net_mgmt_event_callback * cb, uint32_t mgmtEvent, net_if * iface); - static void ScanResultHandler(Platform::UniquePtr data); - static void ScanDoneHandler(Platform::UniquePtr data); - static void ConnectHandler(Platform::UniquePtr data); - static void DisconnectHandler(Platform::UniquePtr data); + static void IPv6MgmtEventHandler(net_mgmt_event_callback * cb, uint32_t mgmtEvent, net_if * iface); + static void ScanResultHandler(Platform::UniquePtr data, size_t length); + static void ScanDoneHandler(Platform::UniquePtr data, size_t length); + static void ConnectHandler(Platform::UniquePtr data, size_t length); + static void DisconnectHandler(Platform::UniquePtr data, size_t length); static void PostConnectivityStatusChange(ConnectivityChange changeType); static void SendRouterSolicitation(System::Layer * layer, void * param); + static void IPv6AddressChangeHandler(const void * data); // Connection Recovery feature // This feature allows re-scanning and re-connecting the connection to the known network after @@ -215,11 +221,13 @@ class WiFiManager void ResetRecoveryTime(); System::Clock::Milliseconds32 CalculateNextRecoveryTime(); + net_if * mNetIf{ nullptr }; ConnectionParams mWiFiParams{}; - ConnectionHandling mHandling; + ConnectionHandling mHandling{}; wifi_iface_state mWiFiState; wifi_iface_state mCachedWiFiState; net_mgmt_event_callback mWiFiMgmtClbk{}; + net_mgmt_event_callback mIPv6MgmtClbk{}; ScanResultCallback mScanResultCallback{ nullptr }; ScanDoneCallback mScanDoneCallback{ nullptr }; WiFiNetwork mWantedNetwork{};