diff --git a/.flake8 b/.flake8 deleted file mode 100644 index 65b1c2a93da0e8..00000000000000 --- a/.flake8 +++ /dev/null @@ -1,6 +0,0 @@ -[flake8] -max-line-length = 132 -exclude = third_party - .* - out/* - ./examples/common/QRCode/* diff --git a/.github/.wordlist.txt b/.github/.wordlist.txt index 21708845b4ccfb..018ed267925642 100644 --- a/.github/.wordlist.txt +++ b/.github/.wordlist.txt @@ -1440,6 +1440,7 @@ ttymxc ttyUSB TurbidityConcentrationMeasurement TvCasting +TVOC tvOS TXD txt diff --git a/.github/actions/bootstrap-cache/action.yaml b/.github/actions/bootstrap-cache/action.yaml index af0d09819a5e26..b22ed1e0ac60bf 100644 --- a/.github/actions/bootstrap-cache/action.yaml +++ b/.github/actions/bootstrap-cache/action.yaml @@ -3,7 +3,7 @@ description: Bootstrap cache runs: using: "composite" steps: - - uses: Wandalen/wretry.action@v1.3.0 + - uses: Wandalen/wretry.action@v1.4.10 name: Bootstrap cache continue-on-error: true with: diff --git a/.github/actions/checkout-submodules-and-bootstrap/action.yaml b/.github/actions/checkout-submodules-and-bootstrap/action.yaml index b514b8dd795509..7424ca529f74df 100644 --- a/.github/actions/checkout-submodules-and-bootstrap/action.yaml +++ b/.github/actions/checkout-submodules-and-bootstrap/action.yaml @@ -11,7 +11,7 @@ inputs: bootstrap-log-name: description: "Bootstrap log name" required: false - default: bootstrap-logs + default: bootstrap-logs-${{ github.job }} runs: using: "composite" steps: @@ -41,3 +41,10 @@ runs: uses: ./.github/actions/upload-bootstrap-logs with: bootstrap-log-name: ${{ inputs.bootstrap-log-name }} + - name: Work around TSAN ASLR issues + if: runner.os == 'Linux' && !env.ACT + shell: bash + run: | + # See https://stackoverflow.com/a/77856955/2365113 + if [[ "$UID" == 0 ]]; then function sudo() { "$@"; }; fi + sudo sysctl vm.mmap_rnd_bits=28 diff --git a/.github/actions/checkout-submodules/action.yaml b/.github/actions/checkout-submodules/action.yaml index 429fb1a5427011..e180a8c49458a2 100644 --- a/.github/actions/checkout-submodules/action.yaml +++ b/.github/actions/checkout-submodules/action.yaml @@ -11,7 +11,7 @@ inputs: runs: using: "composite" steps: - - uses: Wandalen/wretry.action@v1.3.0 + - uses: Wandalen/wretry.action@v1.4.10 name: Checkout submodules with: command: scripts/checkout_submodules.py --allow-changing-global-git-config --shallow --platform ${{ inputs.platform }} ${{ inputs.extra-parameters }} diff --git a/.github/actions/maximize-runner-disk/action.yaml b/.github/actions/maximize-runner-disk/action.yaml index fe5f95352aa53e..d71ba3646d3279 100644 --- a/.github/actions/maximize-runner-disk/action.yaml +++ b/.github/actions/maximize-runner-disk/action.yaml @@ -4,47 +4,45 @@ runs: using: "composite" steps: - name: Free up disk space on the github runner - if: ${{ !env.ACT }} + if: runner.os == 'Linux' && !env.ACT shell: bash run: | # maximize-runner-disk - if [[ "$RUNNER_OS" == Linux ]]; then - # Directories to prune to free up space. Candidates: - # 1.6G /usr/share/dotnet - # 1.1G /usr/local/lib/android/sdk/platforms - # 1000M /usr/local/lib/android/sdk/build-tools - # 8.9G /usr/local/lib/android/sdk - # This list can be amended later to change the trade-off between the amount of - # disk space freed up, and how long it takes to do so (deleting many files is slow). - prune=(/usr/share/dotnet /usr/local/lib/android/sdk/platforms /usr/local/lib/android/sdk/build-tools) + # Directories to prune to free up space. Candidates: + # 1.6G /usr/share/dotnet + # 1.1G /usr/local/lib/android/sdk/platforms + # 1000M /usr/local/lib/android/sdk/build-tools + # 8.9G /usr/local/lib/android/sdk + # This list can be amended later to change the trade-off between the amount of + # disk space freed up, and how long it takes to do so (deleting many files is slow). + prune=(/usr/share/dotnet /usr/local/lib/android/sdk/platforms /usr/local/lib/android/sdk/build-tools) - if [[ "$UID" -eq 0 && -d /__w ]]; then - root=/runner-root-volume - if [[ ! -d "$root" ]]; then - echo "Unable to maximize disk space, job is running inside a container and $root is not mounted" - exit 0 - fi - function sudo() { "$@"; } # we're already root (and sudo is probably unavailable) - elif [[ "$UID" -ne 0 && "$RUNNER_ENVIRONMENT" == github-hosted ]]; then - root= - else - echo "Unable to maximize disk space, unknown runner environment" + if [[ "$UID" -eq 0 && -d /__w ]]; then + root=/runner-root-volume + if [[ ! -d "$root" ]]; then + echo "Unable to maximize disk space, job is running inside a container and $root is not mounted" exit 0 fi - - echo "Freeing up runner disk space on ${root:-/}" - function avail() { df -k --output=avail "${root:-/}" | grep '^[0-9]*$'; } - function now() { date '+%s'; } - before="$(avail)" start="$(now)" - for dir in "${prune[@]}"; do - if [[ -d "${root}${dir}" ]]; then - echo "- $dir" - # du -sh -- "${root}${dir}" - sudo rm -rf -- "${root}${dir}" - else - echo "- $dir (not found)" - fi - done - after="$(avail)" end="$(now)" - echo "Done, freed up $(( (after - before) / 1024 ))M of disk space in $(( end - start )) seconds." + function sudo() { "$@"; } # we're already root (and sudo is probably unavailable) + elif [[ "$UID" -ne 0 && "$RUNNER_ENVIRONMENT" == github-hosted ]]; then + root= + else + echo "Unable to maximize disk space, unknown runner environment" + exit 0 fi + + echo "Freeing up runner disk space on ${root:-/}" + function avail() { df -k --output=avail "${root:-/}" | grep '^[0-9]*$'; } + function now() { date '+%s'; } + before="$(avail)" start="$(now)" + for dir in "${prune[@]}"; do + if [[ -d "${root}${dir}" ]]; then + echo "- $dir" + # du -sh -- "${root}${dir}" + sudo rm -rf -- "${root}${dir}" + else + echo "- $dir (not found)" + fi + done + after="$(avail)" end="$(now)" + echo "Done, freed up $(( (after - before) / 1024 ))M of disk space in $(( end - start )) seconds." diff --git a/.github/actions/perform-codeql-analysis/action.yaml b/.github/actions/perform-codeql-analysis/action.yaml index af12a9e716ee61..f1caf10dc3df1c 100644 --- a/.github/actions/perform-codeql-analysis/action.yaml +++ b/.github/actions/perform-codeql-analysis/action.yaml @@ -27,7 +27,7 @@ runs: with: sarif_file: "sarif-results/${{ inputs.language }}.sarif" - name: Upload loc as a Build Artifact - uses: actions/upload-artifact@v2.2.0 + uses: actions/upload-artifact@v4 with: name: sarif-results path: sarif-results diff --git a/.github/actions/upload-bootstrap-logs/action.yaml b/.github/actions/upload-bootstrap-logs/action.yaml index 85209c7302f2a5..522058c16ff93c 100644 --- a/.github/actions/upload-bootstrap-logs/action.yaml +++ b/.github/actions/upload-bootstrap-logs/action.yaml @@ -4,12 +4,12 @@ inputs: bootstrap-log-name: description: "Bootstrap log name" required: false - default: bootstrap-logs + default: bootstrap-logs-${{ github.job }} runs: using: "composite" steps: - name: Uploading bootstrap logs - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 if: ${{ always() && !env.ACT }} with: name: ${{ inputs.bootstrap-log-name }} diff --git a/.github/actions/upload-size-reports/action.yaml b/.github/actions/upload-size-reports/action.yaml index c71312ae6578ee..41fe8c820c7cc0 100644 --- a/.github/actions/upload-size-reports/action.yaml +++ b/.github/actions/upload-size-reports/action.yaml @@ -9,7 +9,7 @@ runs: using: "composite" steps: - name: Uploading Size Reports - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 if: ${{ !env.ACT }} with: name: Size,${{ inputs.platform-name }}-Examples,${{ env.GH_EVENT_PR }},${{ env.GH_EVENT_HASH }},${{ env.GH_EVENT_PARENT }},${{ github.event_name }} diff --git a/.github/workflows/cirque.yaml b/.github/workflows/cirque.yaml index c9fe4b296d6edb..fafe7265aea5d9 100644 --- a/.github/workflows/cirque.yaml +++ b/.github/workflows/cirque.yaml @@ -71,7 +71,7 @@ jobs: - name: Get Cirque Bootstrap cache key id: cirque-bootstrap-cache-key run: echo "val=$(scripts/tests/cirque_tests.sh cachekeyhash)" >> $GITHUB_OUTPUT - - uses: Wandalen/wretry.action@v1.4.5 + - uses: Wandalen/wretry.action@v1.4.10 name: Cirque Bootstrap cache if: ${{ !env.ACT }} continue-on-error: true diff --git a/.github/workflows/darwin.yaml b/.github/workflows/darwin.yaml index 088c541495665b..19afaa6da98c40 100644 --- a/.github/workflows/darwin.yaml +++ b/.github/workflows/darwin.yaml @@ -54,6 +54,11 @@ jobs: # Disable availability annotations, since we are not building a system # Matter.framework. run: xcodebuild -target "Matter" -sdk watchos -configuration Debug GCC_PREPROCESSOR_DEFINITIONS='${inherited} MTR_NO_AVAILABILITY=1' + - name: Run tvOS Build Debug + working-directory: src/darwin/Framework + # Disable availability annotations, since we are not building a system + # Matter.framework. + run: xcodebuild -target "Matter" -sdk appletvos -configuration Debug GCC_PREPROCESSOR_DEFINITIONS='${inherited} MTR_NO_AVAILABILITY=1' - name: Run iOS Build Debug working-directory: src/darwin/Framework # Disable availability annotations, since we are not building a system diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 70edd539eee0bf..8e67de5878ccf9 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -29,7 +29,7 @@ jobs: if: github.actor != 'restyled-io[bot]' container: - image: ghcr.io/project-chip/chip-build:35 + image: ghcr.io/project-chip/chip-build:39 steps: - name: Checkout @@ -267,12 +267,11 @@ jobs: run: | git grep -I -n 'emberAfWriteAttribute' -- './*' ':(exclude).github/workflows/lint.yml' ':(exclude)src/app/util/af.h' ':(exclude)zzz_generated/app-common/app-common/zap-generated/attributes/Accessors.cpp' ':(exclude)src/app/zap-templates/templates/app/attributes/Accessors-src.zapt' ':(exclude)src/app/util/attribute-table.cpp' ':(exclude)examples/common/pigweed/rpc_services/Attributes.h' ':(exclude)src/app/util/attribute-table.h' ':(exclude)src/app/util/ember-compatibility-functions.cpp' && exit 1 || exit 0 - # Run python Linter (flake8) and verify python files - # ignore some style errors, restyler should do that - - name: Check for errors using flake8 Python linter + # Run ruff python linter + - name: Check for errors using ruff Python linter if: always() run: | - flake8 --extend-ignore=E501,W391 + ruff check # git grep exits with 0 if it finds a match, but we want # to fail (exit nonzero) on match. And we want to exclude this file, diff --git a/.github/workflows/qemu.yaml b/.github/workflows/qemu.yaml index c4992953780911..714b5b8782f1ae 100644 --- a/.github/workflows/qemu.yaml +++ b/.github/workflows/qemu.yaml @@ -58,6 +58,8 @@ jobs: build \ " - name: Run all tests + # Disabled being tracked here: https://github.com/project-chip/connectedhomeip/issues/32587 + if: false run: | src/test_driver/esp32/run_qemu_image.py \ --verbose \ diff --git a/.github/workflows/unit_integration_test.yaml b/.github/workflows/unit_integration_test.yaml index 15199d4d6532e3..be602f2ee9f142 100644 --- a/.github/workflows/unit_integration_test.yaml +++ b/.github/workflows/unit_integration_test.yaml @@ -50,6 +50,7 @@ jobs: uses: ./.github/actions/checkout-submodules-and-bootstrap with: platform: linux + bootstrap-log-name: bootstrap-logs-unittest-${{ matrix.type }} - name: Artifact suffix id: outsuffix uses: haya14busa/action-cond@v1 diff --git a/.restyled.yaml b/.restyled.yaml index 56ed3061e6e1fa..9acbe8e5134aed 100644 --- a/.restyled.yaml +++ b/.restyled.yaml @@ -230,6 +230,8 @@ restylers: command: - autopep8 - "--in-place" + - "--max-line-length" + - "132" arguments: [] include: - "**/*.py" diff --git a/config/openiotsdk/chip-gn/.gn b/config/openiotsdk/chip-gn/.gn index 4b9894b0f943ac..f2404233d6f74d 100644 --- a/config/openiotsdk/chip-gn/.gn +++ b/config/openiotsdk/chip-gn/.gn @@ -14,6 +14,7 @@ import("//build_overrides/build.gni") import("//build_overrides/chip.gni") +import("//build_overrides/pigweed.gni") # The location of the build configuration file. buildconfig = "//build/config/BUILDCONFIG.gn" @@ -26,4 +27,14 @@ default_args = { target_os = "cmsis-rtos" import("${chip_root}/config/openiotsdk/chip-gn/args.gni") + + pw_sys_io_BACKEND = dir_pw_sys_io_stdio + + pw_assert_BACKEND = dir_pw_assert_log + pw_log_BACKEND = dir_pw_log_basic + + pw_build_LINK_DEPS = [ + "$dir_pw_assert:impl", + "$dir_pw_log:impl", + ] } diff --git a/config/openiotsdk/chip-gn/BUILD.gn b/config/openiotsdk/chip-gn/BUILD.gn index 6e6fb51d404bb1..94e2f28635d114 100644 --- a/config/openiotsdk/chip-gn/BUILD.gn +++ b/config/openiotsdk/chip-gn/BUILD.gn @@ -19,7 +19,10 @@ group("openiotsdk") { deps = [ "${chip_root}/src/lib" ] if (chip_build_tests) { - deps += [ "${chip_root}/src:tests" ] + deps += [ + "${chip_root}/src:tests", + "${chip_root}/src/lib/support:pw_tests_wrapper", + ] } } diff --git a/credentials/generate-revocation-set.py b/credentials/generate-revocation-set.py new file mode 100644 index 00000000000000..534edc15b0f203 --- /dev/null +++ b/credentials/generate-revocation-set.py @@ -0,0 +1,248 @@ +#!/usr/bin/python + +# +# Copyright (c) 2023 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. +# + +# Generates a basic RevocationSet from TestNet +# Usage: +# python ./credentials/generate-revocation-set.py --help + +import base64 +import json +import subprocess +import sys +from enum import Enum + +import click +import requests +from click_option_group import RequiredMutuallyExclusiveOptionGroup, optgroup +from cryptography import x509 + + +class RevocationType(Enum): + CRL = 1 + + +OID_VENDOR_ID = x509.ObjectIdentifier("1.3.6.1.4.1.37244.2.1") +OID_PRODUCT_ID = x509.ObjectIdentifier("1.3.6.1.4.1.37244.2.2") + +PRODUCTION_NODE_URL = "https://on.dcl.csa-iot.org:26657" +PRODUCTION_NODE_URL_REST = "https://on.dcl.csa-iot.org" +TEST_NODE_URL_REST = "https://on.test-net.dcl.csa-iot.org" + + +def use_dcld(dcld, production, cmdlist): + return [dcld] + cmdlist + (['--node', PRODUCTION_NODE_URL] if production else []) + + +def extract_single_integer_attribute(subject, oid): + attribute_list = subject.get_attributes_for_oid(oid) + + if len(attribute_list) == 1: + if attribute_list[0].value.isdigit(): + return int(attribute_list[0].value) + + return None + + +@click.command() +@click.help_option('-h', '--help') +@optgroup.group('Input data sources', cls=RequiredMutuallyExclusiveOptionGroup) +@optgroup.option('--use-main-net-dcld', type=str, default='', metavar='PATH', help="Location of `dcld` binary, to use `dcld` for mirroring MainNet.") +@optgroup.option('--use-test-net-dcld', type=str, default='', metavar='PATH', help="Location of `dcld` binary, to use `dcld` for mirroring TestNet.") +@optgroup.option('--use-main-net-http', is_flag=True, type=str, help="Use RESTful API with HTTPS against public MainNet observer.") +@optgroup.option('--use-test-net-http', is_flag=True, type=str, help="Use RESTful API with HTTPS against public TestNet observer.") +@optgroup.group('Optional arguments') +@optgroup.option('--output', default='sample_revocation_set_list.json', type=str, metavar='FILEPATH', help="Output filename (default: sample_revocation_set_list.json)") +def main(use_main_net_dcld, use_test_net_dcld, use_main_net_http, use_test_net_http, output): + """DCL PAA mirroring tools""" + + production = False + dcld = use_test_net_dcld + + if len(use_main_net_dcld) > 0: + dcld = use_main_net_dcld + production = True + + use_rest = use_main_net_http or use_test_net_http + if use_main_net_http: + production = True + + rest_node_url = PRODUCTION_NODE_URL_REST if production else TEST_NODE_URL_REST + + # TODO: Extract this to a helper function + if use_rest: + revocation_point_list = requests.get(f"{rest_node_url}/dcl/pki/revocation-points").json()["PkiRevocationDistributionPoint"] + else: + cmdlist = ['config', 'output', 'json'] + subprocess.Popen([dcld] + cmdlist) + + cmdlist = ['query', 'pki', 'all-revocation-points'] + + cmdpipe = subprocess.Popen(use_dcld(dcld, production, cmdlist), stdout=subprocess.PIPE, stderr=subprocess.PIPE) + + revocation_point_list = json.loads(cmdpipe.stdout.read())["PkiRevocationDistributionPoint"] + + revocation_set = [] + + for revocation_point in revocation_point_list: + # 1. Validate Revocation Type + if revocation_point["revocationType"] != RevocationType.CRL: + continue + + # 2. Parse the certificate + crl_signer_certificate = x509.load_pem_x509_certificate(revocation_point["crlSignerCertificate"]) + + vid = revocation_point["vid"] + pid = revocation_point["pid"] + is_paa = revocation_point["isPAA"] + + # 3. && 4. Validate VID/PID + # TODO: Need to support alternate representation of VID/PID (see spec "6.2.2.2. Encoding of Vendor ID and Product ID in subject and issuer fields") + crl_vid = extract_single_integer_attribute(crl_signer_certificate.subject, OID_VENDOR_ID) + crl_pid = extract_single_integer_attribute(crl_signer_certificate.subject, OID_PRODUCT_ID) + + if is_paa: + if crl_vid is not None: + if vid != crl_vid: + # TODO: Need to log all situations where a continue is called + continue + else: + if crl_vid is None or vid != crl_vid: + continue + if crl_pid is not None: + if pid != crl_pid: + continue + + # 5. Validate the certification path containing CRLSignerCertificate. + crl_signer_issuer_name = base64.b64encode(crl_signer_certificate.issuer.public_bytes()).decode('utf-8') + + crl_signer_authority_key_id = crl_signer_certificate.extensions.get_extension_for_oid( + x509.OID_AUTHORITY_KEY_IDENTIFIER).value.key_identifier + + paa_certificate = None + + # TODO: Extract this to a helper function + if use_rest: + response = requests.get( + f"{rest_node_url}/dcl/pki/certificates/{crl_signer_issuer_name}/{crl_signer_authority_key_id}").json()["approvedCertificates"]["certs"][0] + paa_certificate = response["pemCert"] + else: + cmdlist = ['query', 'pki', 'x509-cert', '-u', crl_signer_issuer_name, '-k', crl_signer_authority_key_id] + cmdpipe = subprocess.Popen(use_dcld(dcld, production, cmdlist), stdout=subprocess.PIPE, stderr=subprocess.PIPE) + paa_certificate = json.loads(cmdpipe.stdout.read())["approvedCertificates"]["certs"][0]["pemCert"] + + if paa_certificate is None: + continue + + paa_certificate_object = x509.load_pem_x509_certificate(paa_certificate) + + try: + crl_signer_certificate.verify_directly_issued_by(paa_certificate_object) + except Exception: + continue + + # 6. Obtain the CRL + r = requests.get(revocation_point["dataURL"]) + crl_file = x509.load_der_x509_crl(r.content) + + # 7. Perform CRL File Validation + crl_authority_key_id = crl_file.extensions.get_extension_for_oid(x509.OID_AUTHORITY_KEY_IDENTIFIER).value.key_identifier + crl_signer_subject_key_id = crl_signer_certificate.extensions.get_extension_for_oid( + x509.OID_SUBJECT_KEY_IDENTIFIER).value.key_identifier + if crl_authority_key_id != crl_signer_subject_key_id: + continue + + issuer_subject_key_id = ''.join('{:02X}'.format(x) for x in crl_authority_key_id) + + same_issuer_points = None + + # TODO: Extract this to a helper function + if use_rest: + response = requests.get( + f"{rest_node_url}/dcl/pki/revocation-points/{issuer_subject_key_id}").json()["pkiRevocationDistributionPointsByIssuerSubjectKeyID"] + same_issuer_points = response["points"] + else: + cmdlist = ['query', 'pki', 'revocation-points', '--issuer-subject-key-id', issuer_subject_key_id] + cmdpipe = subprocess.Popen(use_dcld(dcld, production, cmdlist), stdout=subprocess.PIPE, stderr=subprocess.PIPE) + same_issuer_points = json.loads(cmdpipe.stdout.read())[ + "pkiRevocationDistributionPointsByIssuerSubjectKeyID"]["points"] + + matching_entries = False + for same_issuer_point in same_issuer_points: + if same_issuer_point["vid"] == vid: + matching_entries = True + break + + if matching_entries: + try: + issuing_distribution_point = crl_file.extensions.get_extension_for_oid( + x509.OID_ISSUING_DISTRIBUTION_POINT).value + except Exception: + continue + + uri_list = issuing_distribution_point.full_name + if len(uri_list) == 1 and isinstance(uri_list[0], x509.UniformResourceIdentifier): + if uri_list[0].value != revocation_point["dataURL"]: + continue + else: + continue + + # 9. Assign CRL File Issuer + certificate_authority_name = base64.b64encode(crl_file.issuer.public_bytes()).decode('utf-8') + + serialnumber_list = [] + # 10. Iterate through the Revoked Certificates List + for revoked_cert in crl_file: + try: + revoked_cert_issuer = revoked_cert.extensions.get_extension_for_oid( + x509.CRLEntryExtensionOID.CERTIFICATE_ISSUER).value.get_values_for_type(x509.DirectoryName).value + + if revoked_cert_issuer is not None: + if revoked_cert_issuer != certificate_authority_name: + continue + except Exception: + pass + + # b. + try: + revoked_cert_authority_key_id = revoked_cert.extensions.get_extension_for_oid( + x509.OID_AUTHORITY_KEY_IDENTIFIER).value.key_identifier + + if revoked_cert_authority_key_id is None or revoked_cert_authority_key_id != crl_signer_subject_key_id: + continue + except Exception: + continue + + # c. and d. + serialnumber_list.append(bytes(str('{:02X}'.format(revoked_cert.serial_number)), 'utf-8').decode('utf-8')) + + issuer_name = base64.b64encode(crl_file.issuer.public_bytes()).decode('utf-8') + + revocation_set.append({"type": "revocation_set", + "issuer_subject_key_id": issuer_subject_key_id, + "issuer_name": issuer_name, + "revoked_serial_numbers": serialnumber_list}) + + with open(output, 'w+') as outfile: + json.dump(revocation_set, outfile, indent=4) + + +if __name__ == "__main__": + if len(sys.argv) == 1: + main.main(['--help']) + else: + main() diff --git a/docs/cluster_and_device_type_dev/img/cluster_commands.png b/docs/cluster_and_device_type_dev/img/cluster_commands.png index c04dbe7279b28b..7df7a8a75412ab 100644 Binary files a/docs/cluster_and_device_type_dev/img/cluster_commands.png and b/docs/cluster_and_device_type_dev/img/cluster_commands.png differ diff --git a/examples/air-quality-sensor-app/linux/README.md b/examples/air-quality-sensor-app/linux/README.md index 38a0adfb2fe7bf..b1c2e92461d8f0 100644 --- a/examples/air-quality-sensor-app/linux/README.md +++ b/examples/air-quality-sensor-app/linux/README.md @@ -136,10 +136,11 @@ $ echo '{"Name":"TemperatureMeasurement","NewValue":1800}' > /tmp/chip_air_quali ### Trigger Humidity change event -Generate event `RelativeHumidityMeasurement`, to change the temperate value. +Generate event `RelativeHumidityMeasurement`, to change the relative humidity +value (6000 for 60,0 %). ``` -$ echo '{"Name":"RelativeHumidityMeasurement","NewValue":60}' > /tmp/chip_air_quality_fifo_ +$ echo '{"Name":"RelativeHumidityMeasurement","NewValue":6000}' > /tmp/chip_air_quality_fifo_ ``` ### Trigger concentration change event @@ -183,3 +184,10 @@ Generate event `Pm10ConcentrationMeasurement`, to change the PM10 value. ``` echo '{"Name":"Pm10ConcentrationMeasurement","NewValue":10}' > /tmp/chip_air_quality_fifo_ ``` + +Generate event `TotalVolatileOrganicCompoundsConcentrationMeasurement`, to +change the TVOC value. + +``` +$ echo '{"Name":"TotalVolatileOrganicCompoundsConcentrationMeasurement","NewValue":100}' > /tmp/chip_air_quality_fifo_ +``` diff --git a/examples/all-clusters-app/all-clusters-common/src/operational-state-delegate-impl.cpp b/examples/all-clusters-app/all-clusters-common/src/operational-state-delegate-impl.cpp index 2070e1b86754fe..d258b8261a1aed 100644 --- a/examples/all-clusters-app/all-clusters-common/src/operational-state-delegate-impl.cpp +++ b/examples/all-clusters-app/all-clusters-common/src/operational-state-delegate-impl.cpp @@ -55,15 +55,6 @@ CHIP_ERROR GenericOperationalStateDelegateImpl::GetOperationalPhaseAtIndex(size_ void GenericOperationalStateDelegateImpl::HandlePauseStateCallback(GenericOperationalError & err) { - OperationalState::OperationalStateEnum state = - static_cast(GetInstance()->GetCurrentOperationalState()); - - if (state == OperationalState::OperationalStateEnum::kStopped || state == OperationalState::OperationalStateEnum::kError) - { - err.Set(to_underlying(OperationalState::ErrorStateEnum::kCommandInvalidInState)); - return; - } - // placeholder implementation auto error = GetInstance()->SetOperationalState(to_underlying(OperationalState::OperationalStateEnum::kPaused)); if (error == CHIP_NO_ERROR) @@ -78,15 +69,6 @@ void GenericOperationalStateDelegateImpl::HandlePauseStateCallback(GenericOperat void GenericOperationalStateDelegateImpl::HandleResumeStateCallback(GenericOperationalError & err) { - OperationalState::OperationalStateEnum state = - static_cast(GetInstance()->GetCurrentOperationalState()); - - if (state == OperationalState::OperationalStateEnum::kStopped || state == OperationalState::OperationalStateEnum::kError) - { - err.Set(to_underlying(OperationalState::ErrorStateEnum::kCommandInvalidInState)); - return; - } - // placeholder implementation auto error = GetInstance()->SetOperationalState(to_underlying(OperationalStateEnum::kRunning)); if (error == CHIP_NO_ERROR) diff --git a/examples/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/ChipClient.kt b/examples/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/ChipClient.kt index 77c59d75d00c36..0534f7f3ee3589 100644 --- a/examples/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/ChipClient.kt +++ b/examples/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/ChipClient.kt @@ -22,6 +22,8 @@ import android.util.Log import chip.devicecontroller.ChipDeviceController import chip.devicecontroller.ControllerParams import chip.devicecontroller.GetConnectedDeviceCallbackJni.GetConnectedDeviceCallback +import chip.devicecontroller.ICDCheckInDelegate +import chip.devicecontroller.ICDClientInfo import chip.platform.AndroidBleManager import chip.platform.AndroidChipPlatform import chip.platform.ChipMdnsCallbackImpl @@ -48,13 +50,35 @@ object ChipClient { if (!this::chipDeviceController.isInitialized) { chipDeviceController = - ChipDeviceController(ControllerParams.newBuilder().setControllerVendorId(VENDOR_ID).build()) + ChipDeviceController( + ControllerParams.newBuilder() + .setControllerVendorId(VENDOR_ID) + .setEnableServerInteractions(true) + .build() + ) // Set delegate for attestation trust store for device attestation verifier. // It will replace the default attestation trust store. chipDeviceController.setAttestationTrustStoreDelegate( ExampleAttestationTrustStoreDelegate(chipDeviceController) ) + + chipDeviceController.setICDCheckInDelegate( + object : ICDCheckInDelegate { + override fun onCheckInComplete(info: ICDClientInfo) { + Log.d(TAG, "onCheckInComplete : $info") + } + + override fun onKeyRefreshNeeded(info: ICDClientInfo): ByteArray? { + Log.d(TAG, "onKeyRefreshNeeded : $info") + return null + } + + override fun onKeyRefreshDone(errorCode: Long) { + Log.d(TAG, "onKeyRefreshDone : $errorCode") + } + } + ) } return chipDeviceController @@ -69,7 +93,10 @@ object ChipClient { AndroidBleManager(context), PreferencesKeyValueStoreManager(context), PreferencesConfigurationManager(context), - NsdManagerServiceResolver(context), + NsdManagerServiceResolver( + context, + NsdManagerServiceResolver.NsdManagerResolverAvailState() + ), NsdManagerServiceBrowser(context), ChipMdnsCallbackImpl(), DiagnosticDataProviderImpl(context) diff --git a/examples/chef/common/chef-operational-state-delegate-impl.cpp b/examples/chef/common/chef-operational-state-delegate-impl.cpp new file mode 100644 index 00000000000000..48863b0cfa6c26 --- /dev/null +++ b/examples/chef/common/chef-operational-state-delegate-impl.cpp @@ -0,0 +1,219 @@ +/* + * Copyright (c) 2023 Project CHIP Authors + * All rights reserved. + * + * 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. + */ +#include +#include + +using namespace chip; +using namespace chip::app; +using namespace chip::app::Clusters; +using namespace chip::app::Clusters::OperationalState; +using namespace chip::app::Clusters::RvcOperationalState; + +static void onOperationalStateTimerTick(System::Layer * systemLayer, void * data); + +DataModel::Nullable GenericOperationalStateDelegateImpl::GetCountdownTime() +{ + if (mCountDownTime.IsNull()) + return DataModel::NullNullable; + + return DataModel::MakeNullable((uint32_t) (mCountDownTime.Value() - mRunningTime)); +} + +CHIP_ERROR GenericOperationalStateDelegateImpl::GetOperationalStateAtIndex(size_t index, GenericOperationalState & operationalState) +{ + if (index >= mOperationalStateList.size()) + { + return CHIP_ERROR_NOT_FOUND; + } + operationalState = mOperationalStateList[index]; + return CHIP_NO_ERROR; +} + +CHIP_ERROR GenericOperationalStateDelegateImpl::GetOperationalPhaseAtIndex(size_t index, MutableCharSpan & operationalPhase) +{ + if (index >= mOperationalPhaseList.size()) + { + return CHIP_ERROR_NOT_FOUND; + } + return CopyCharSpanToMutableCharSpan(mOperationalPhaseList[index], operationalPhase); +} + +void GenericOperationalStateDelegateImpl::HandlePauseStateCallback(GenericOperationalError & err) +{ + OperationalState::OperationalStateEnum state = + static_cast(GetInstance()->GetCurrentOperationalState()); + + if (state == OperationalState::OperationalStateEnum::kStopped || state == OperationalState::OperationalStateEnum::kError) + { + err.Set(to_underlying(OperationalState::ErrorStateEnum::kCommandInvalidInState)); + return; + } + + // placeholder implementation + auto error = GetInstance()->SetOperationalState(to_underlying(OperationalState::OperationalStateEnum::kPaused)); + if (error == CHIP_NO_ERROR) + { + err.Set(to_underlying(ErrorStateEnum::kNoError)); + } + else + { + err.Set(to_underlying(ErrorStateEnum::kUnableToCompleteOperation)); + } +} + +void GenericOperationalStateDelegateImpl::HandleResumeStateCallback(GenericOperationalError & err) +{ + OperationalState::OperationalStateEnum state = + static_cast(GetInstance()->GetCurrentOperationalState()); + + if (state == OperationalState::OperationalStateEnum::kStopped || state == OperationalState::OperationalStateEnum::kError) + { + err.Set(to_underlying(OperationalState::ErrorStateEnum::kCommandInvalidInState)); + return; + } + + // placeholder implementation + auto error = GetInstance()->SetOperationalState(to_underlying(OperationalStateEnum::kRunning)); + if (error == CHIP_NO_ERROR) + { + err.Set(to_underlying(ErrorStateEnum::kNoError)); + } + else + { + err.Set(to_underlying(ErrorStateEnum::kUnableToCompleteOperation)); + } +} + +void GenericOperationalStateDelegateImpl::HandleStartStateCallback(GenericOperationalError & err) +{ + OperationalState::GenericOperationalError current_err(to_underlying(OperationalState::ErrorStateEnum::kNoError)); + GetInstance()->GetCurrentOperationalError(current_err); + + if (current_err.errorStateID != to_underlying(OperationalState::ErrorStateEnum::kNoError)) + { + err.Set(to_underlying(OperationalState::ErrorStateEnum::kUnableToStartOrResume)); + return; + } + + // placeholder implementation + auto error = GetInstance()->SetOperationalState(to_underlying(OperationalStateEnum::kRunning)); + if (error == CHIP_NO_ERROR) + { + (void) DeviceLayer::SystemLayer().StartTimer(System::Clock::Seconds16(1), onOperationalStateTimerTick, this); + err.Set(to_underlying(ErrorStateEnum::kNoError)); + } + else + { + err.Set(to_underlying(ErrorStateEnum::kUnableToCompleteOperation)); + } +} + +void GenericOperationalStateDelegateImpl::HandleStopStateCallback(GenericOperationalError & err) +{ + // placeholder implementation + auto error = GetInstance()->SetOperationalState(to_underlying(OperationalStateEnum::kStopped)); + if (error == CHIP_NO_ERROR) + { + (void) DeviceLayer::SystemLayer().CancelTimer(onOperationalStateTimerTick, this); + + OperationalState::GenericOperationalError current_err(to_underlying(OperationalState::ErrorStateEnum::kNoError)); + GetInstance()->GetCurrentOperationalError(current_err); + + Optional> totalTime((DataModel::Nullable(mRunningTime + mPausedTime))); + Optional> pausedTime((DataModel::Nullable(mPausedTime))); + + GetInstance()->OnOperationCompletionDetected(static_cast(current_err.errorStateID), totalTime, pausedTime); + + mRunningTime = 0; + mPausedTime = 0; + err.Set(to_underlying(ErrorStateEnum::kNoError)); + } + else + { + err.Set(to_underlying(ErrorStateEnum::kUnableToCompleteOperation)); + } +} + +static void onOperationalStateTimerTick(System::Layer * systemLayer, void * data) +{ + GenericOperationalStateDelegateImpl * delegate = reinterpret_cast(data); + + OperationalState::Instance * instance = OperationalState::GetOperationalStateInstance(); + OperationalState::OperationalStateEnum state = + static_cast(instance->GetCurrentOperationalState()); + + auto countdown_time = delegate->GetCountdownTime(); + + if (countdown_time.IsNull() || (!countdown_time.IsNull() && countdown_time.Value() > 0)) + { + if (state == OperationalState::OperationalStateEnum::kRunning) + { + delegate->mRunningTime++; + } + else if (state == OperationalState::OperationalStateEnum::kPaused) + { + delegate->mPausedTime++; + } + } + + if (state == OperationalState::OperationalStateEnum::kRunning || state == OperationalState::OperationalStateEnum::kPaused) + { + (void) DeviceLayer::SystemLayer().StartTimer(System::Clock::Seconds16(1), onOperationalStateTimerTick, delegate); + } + else + { + (void) DeviceLayer::SystemLayer().CancelTimer(onOperationalStateTimerTick, delegate); + } +} + +// Init Operational State cluster + +static OperationalState::Instance * gOperationalStateInstance = nullptr; +static OperationalStateDelegate * gOperationalStateDelegate = nullptr; + +OperationalState::Instance * OperationalState::GetOperationalStateInstance() +{ + return gOperationalStateInstance; +} + +void OperationalState::Shutdown() +{ + if (gOperationalStateInstance != nullptr) + { + delete gOperationalStateInstance; + gOperationalStateInstance = nullptr; + } + if (gOperationalStateDelegate != nullptr) + { + delete gOperationalStateDelegate; + gOperationalStateDelegate = nullptr; + } +} + +void emberAfOperationalStateClusterInitCallback(chip::EndpointId endpointId) +{ + VerifyOrDie(endpointId == 1); // this cluster is only enabled for endpoint 1. + VerifyOrDie(gOperationalStateInstance == nullptr && gOperationalStateDelegate == nullptr); + + gOperationalStateDelegate = new OperationalStateDelegate; + EndpointId operationalStateEndpoint = 0x01; + gOperationalStateInstance = new OperationalState::Instance(gOperationalStateDelegate, operationalStateEndpoint); + + gOperationalStateInstance->SetOperationalState(to_underlying(OperationalState::OperationalStateEnum::kStopped)); + + gOperationalStateInstance->Init(); +} diff --git a/examples/chef/common/chef-operational-state-delegate-impl.h b/examples/chef/common/chef-operational-state-delegate-impl.h new file mode 100644 index 00000000000000..60b6b09e9b6511 --- /dev/null +++ b/examples/chef/common/chef-operational-state-delegate-impl.h @@ -0,0 +1,147 @@ +/* + * + * Copyright (c) 2023 Project CHIP Authors + * All rights reserved. + * + * 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. + */ + +#pragma once + +#include +#include + +#include + +namespace chip { +namespace app { +namespace Clusters { + +namespace OperationalState { + +// This is an application level delegate to handle operational state commands according to the specific business logic. +class GenericOperationalStateDelegateImpl : public Delegate +{ +public: + uint32_t mRunningTime = 0; + uint32_t mPausedTime = 0; + app::DataModel::Nullable mCountDownTime; + + /** + * Get the countdown time. This attribute is not used in this application. + * @return The current countdown time. + */ + app::DataModel::Nullable GetCountdownTime() override; + + /** + * Fills in the provided GenericOperationalState with the state at index `index` if there is one, + * or returns CHIP_ERROR_NOT_FOUND if the index is out of range for the list of states. + * Note: This is used by the SDK to populate the operational state list attribute. If the contents of this list changes, + * the device SHALL call the Instance's ReportOperationalStateListChange method to report that this attribute has changed. + * @param index The index of the state, with 0 representing the first state. + * @param operationalState The GenericOperationalState is filled. + */ + CHIP_ERROR GetOperationalStateAtIndex(size_t index, GenericOperationalState & operationalState) override; + + /** + * Fills in the provided MutableCharSpan with the phase at index `index` if there is one, + * or returns CHIP_ERROR_NOT_FOUND if the index is out of range for the list of phases. + * + * If CHIP_ERROR_NOT_FOUND is returned for index 0, that indicates that the PhaseList attribute is null + * (there are no phases defined at all). + * + * Note: This is used by the SDK to populate the phase list attribute. If the contents of this list changes, the + * device SHALL call the Instance's ReportPhaseListChange method to report that this attribute has changed. + * @param index The index of the phase, with 0 representing the first phase. + * @param operationalPhase The MutableCharSpan is filled. + */ + CHIP_ERROR GetOperationalPhaseAtIndex(size_t index, MutableCharSpan & operationalPhase) override; + + // command callback + /** + * Handle Command Callback in application: Pause + * @param[out] get operational error after callback. + */ + void HandlePauseStateCallback(GenericOperationalError & err) override; + + /** + * Handle Command Callback in application: Resume + * @param[out] get operational error after callback. + */ + void HandleResumeStateCallback(GenericOperationalError & err) override; + + /** + * Handle Command Callback in application: Start + * @param[out] get operational error after callback. + */ + void HandleStartStateCallback(GenericOperationalError & err) override; + + /** + * Handle Command Callback in application: Stop + * @param[out] get operational error after callback. + */ + void HandleStopStateCallback(GenericOperationalError & err) override; + +protected: + Span mOperationalStateList; + Span mOperationalPhaseList; +}; + +// This is an application level delegate to handle operational state commands according to the specific business logic. +class OperationalStateDelegate : public GenericOperationalStateDelegateImpl +{ +private: + const GenericOperationalState opStateList[4] = { + GenericOperationalState(to_underlying(OperationalStateEnum::kStopped)), + GenericOperationalState(to_underlying(OperationalStateEnum::kRunning)), + GenericOperationalState(to_underlying(OperationalStateEnum::kPaused)), + GenericOperationalState(to_underlying(OperationalStateEnum::kError)), + }; + + const uint32_t kExampleCountDown = 30; + +public: + OperationalStateDelegate() + { + GenericOperationalStateDelegateImpl::mOperationalStateList = Span(opStateList); + } + + /** + * Handle Command Callback in application: Start + * @param[out] get operational error after callback. + */ + void HandleStartStateCallback(GenericOperationalError & err) override + { + mCountDownTime.SetNonNull(static_cast(kExampleCountDown)); + GenericOperationalStateDelegateImpl::HandleStartStateCallback(err); + } + + /** + * Handle Command Callback in application: Stop + * @param[out] get operational error after callback. + */ + void HandleStopStateCallback(GenericOperationalError & err) override + { + GenericOperationalStateDelegateImpl::HandleStopStateCallback(err); + mCountDownTime.SetNull(); + } +}; + +Instance * GetOperationalStateInstance(); + +void Shutdown(); + +} // namespace OperationalState +} // namespace Clusters +} // namespace app +} // namespace chip diff --git a/examples/chef/common/clusters/door-lock/chef-doorlock-stubs.cpp b/examples/chef/common/clusters/door-lock/chef-doorlock-stubs.cpp new file mode 100644 index 00000000000000..7d92300ca5659c --- /dev/null +++ b/examples/chef/common/clusters/door-lock/chef-doorlock-stubs.cpp @@ -0,0 +1,133 @@ +/* + * + * Copyright (c) 2020-2024 Project CHIP Authors + * All rights reserved. + * + * 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. + */ + +#include +#include +#include + +#ifdef MATTER_DM_PLUGIN_DOOR_LOCK_SERVER +#include "chef-lock-manager.h" +#include + +using namespace chip; +using namespace chip::app::Clusters; +using namespace chip::app::Clusters::DoorLock; +using chip::app::DataModel::Nullable; + +// ============================================================================= +// 'Default' callbacks for cluster commands +// ============================================================================= + +// App handles physical aspects of locking but not locking logic. That is it +// should wait for door to be locked on lock command and return success) but +// door lock server should check pin before even calling the lock-door +// callback. +bool emberAfPluginDoorLockOnDoorLockCommand(chip::EndpointId endpointId, const Nullable & fabricIdx, + const Nullable & nodeId, const Optional & pinCode, + OperationErrorEnum & err) +{ + return LockManager::Instance().Lock(endpointId, fabricIdx, nodeId, pinCode, err, OperationSourceEnum::kRemote); +} + +bool emberAfPluginDoorLockOnDoorUnlockCommand(chip::EndpointId endpointId, const Nullable & fabricIdx, + const Nullable & nodeId, const Optional & pinCode, + OperationErrorEnum & err) +{ + return LockManager::Instance().Unlock(endpointId, fabricIdx, nodeId, pinCode, err, OperationSourceEnum::kRemote); +} + +bool emberAfPluginDoorLockOnDoorUnboltCommand(chip::EndpointId endpointId, const Nullable & fabricIdx, + const Nullable & nodeId, const Optional & pinCode, + OperationErrorEnum & err) +{ + return LockManager::Instance().Unbolt(endpointId, fabricIdx, nodeId, pinCode, err, OperationSourceEnum::kRemote); +} + +bool emberAfPluginDoorLockGetUser(chip::EndpointId endpointId, uint16_t userIndex, EmberAfPluginDoorLockUserInfo & user) +{ + return LockManager::Instance().GetUser(endpointId, userIndex, user); +} + +bool emberAfPluginDoorLockSetUser(chip::EndpointId endpointId, uint16_t userIndex, chip::FabricIndex creator, + chip::FabricIndex modifier, const chip::CharSpan & userName, uint32_t uniqueId, + UserStatusEnum userStatus, UserTypeEnum usertype, CredentialRuleEnum credentialRule, + const CredentialStruct * credentials, size_t totalCredentials) +{ + + return LockManager::Instance().SetUser(endpointId, userIndex, creator, modifier, userName, uniqueId, userStatus, usertype, + credentialRule, credentials, totalCredentials); +} + +bool emberAfPluginDoorLockGetCredential(chip::EndpointId endpointId, uint16_t credentialIndex, CredentialTypeEnum credentialType, + EmberAfPluginDoorLockCredentialInfo & credential) +{ + return LockManager::Instance().GetCredential(endpointId, credentialIndex, credentialType, credential); +} + +bool emberAfPluginDoorLockSetCredential(chip::EndpointId endpointId, uint16_t credentialIndex, chip::FabricIndex creator, + chip::FabricIndex modifier, DlCredentialStatus credentialStatus, + CredentialTypeEnum credentialType, const chip::ByteSpan & credentialData) +{ + return LockManager::Instance().SetCredential(endpointId, credentialIndex, creator, modifier, credentialStatus, credentialType, + credentialData); +} + +DlStatus emberAfPluginDoorLockGetSchedule(chip::EndpointId endpointId, uint8_t weekdayIndex, uint16_t userIndex, + EmberAfPluginDoorLockWeekDaySchedule & schedule) +{ + return LockManager::Instance().GetSchedule(endpointId, weekdayIndex, userIndex, schedule); +} + +DlStatus emberAfPluginDoorLockGetSchedule(chip::EndpointId endpointId, uint8_t holidayIndex, + EmberAfPluginDoorLockHolidaySchedule & schedule) +{ + return LockManager::Instance().GetSchedule(endpointId, holidayIndex, schedule); +} + +DlStatus emberAfPluginDoorLockSetSchedule(chip::EndpointId endpointId, uint8_t weekdayIndex, uint16_t userIndex, + DlScheduleStatus status, DaysMaskMap daysMask, uint8_t startHour, uint8_t startMinute, + uint8_t endHour, uint8_t endMinute) +{ + return LockManager::Instance().SetSchedule(endpointId, weekdayIndex, userIndex, status, daysMask, startHour, startMinute, + endHour, endMinute); +} + +DlStatus emberAfPluginDoorLockSetSchedule(chip::EndpointId endpointId, uint8_t yearDayIndex, uint16_t userIndex, + DlScheduleStatus status, uint32_t localStartTime, uint32_t localEndTime) +{ + return LockManager::Instance().SetSchedule(endpointId, yearDayIndex, userIndex, status, localStartTime, localEndTime); +} + +DlStatus emberAfPluginDoorLockGetSchedule(chip::EndpointId endpointId, uint8_t yearDayIndex, uint16_t userIndex, + EmberAfPluginDoorLockYearDaySchedule & schedule) +{ + return LockManager::Instance().GetSchedule(endpointId, yearDayIndex, userIndex, schedule); +} + +DlStatus emberAfPluginDoorLockSetSchedule(chip::EndpointId endpointId, uint8_t holidayIndex, DlScheduleStatus status, + uint32_t localStartTime, uint32_t localEndTime, OperatingModeEnum operatingMode) +{ + return LockManager::Instance().SetSchedule(endpointId, holidayIndex, status, localStartTime, localEndTime, operatingMode); +} + +void emberAfDoorLockClusterInitCallback(EndpointId endpoint) +{ + DoorLockServer::Instance().InitServer(endpoint); + LockManager::Instance().InitEndpoint(endpoint); +} +#endif // MATTER_DM_PLUGIN_DOOR_LOCK_SERVER diff --git a/examples/chef/common/clusters/door-lock/chef-lock-endpoint.cpp b/examples/chef/common/clusters/door-lock/chef-lock-endpoint.cpp new file mode 100644 index 00000000000000..7cff212dba743b --- /dev/null +++ b/examples/chef/common/clusters/door-lock/chef-lock-endpoint.cpp @@ -0,0 +1,680 @@ +/* + * + * Copyright (c) 2022-2024 Project CHIP Authors + * All rights reserved. + * + * 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. + */ + +#include +#include +#include +#include +#include + +#ifdef MATTER_DM_PLUGIN_DOOR_LOCK_SERVER +#include "chef-lock-endpoint.h" + +using chip::to_underlying; +using chip::app::DataModel::MakeNullable; + +struct LockActionData +{ + chip::EndpointId endpointId; + DlLockState lockState; + OperationSourceEnum opSource; + Nullable userIndex; + uint16_t credentialIndex; + Nullable fabricIdx; + Nullable nodeId; + bool moving = false; +}; + +static LockActionData gCurrentAction; + +bool LockEndpoint::Lock(const Nullable & fabricIdx, const Nullable & nodeId, + const Optional & pin, OperationErrorEnum & err, OperationSourceEnum opSource) +{ + return setLockState(fabricIdx, nodeId, DlLockState::kLocked, pin, err, opSource); +} + +bool LockEndpoint::Unlock(const Nullable & fabricIdx, const Nullable & nodeId, + const Optional & pin, OperationErrorEnum & err, OperationSourceEnum opSource) +{ + if (DoorLockServer::Instance().SupportsUnbolt(mEndpointId)) + { + // If Unbolt is supported Unlock is supposed to pull the latch + return setLockState(fabricIdx, nodeId, DlLockState::kUnlatched, pin, err, opSource); + } + + return setLockState(fabricIdx, nodeId, DlLockState::kUnlocked, pin, err, opSource); +} + +bool LockEndpoint::Unbolt(const Nullable & fabricIdx, const Nullable & nodeId, + const Optional & pin, OperationErrorEnum & err, OperationSourceEnum opSource) +{ + return setLockState(fabricIdx, nodeId, DlLockState::kUnlocked, pin, err, opSource); +} + +bool LockEndpoint::GetUser(uint16_t userIndex, EmberAfPluginDoorLockUserInfo & user) const +{ + ChipLogProgress(Zcl, "Lock App: LockEndpoint::GetUser [endpoint=%d,userIndex=%hu]", mEndpointId, userIndex); + + auto adjustedUserIndex = static_cast(userIndex - 1); + if (adjustedUserIndex > mLockUsers.size()) + { + ChipLogError(Zcl, "Cannot get user - index out of range [endpoint=%d,index=%hu,adjustedIndex=%d]", mEndpointId, userIndex, + adjustedUserIndex); + return false; + } + + const auto & userInDb = mLockUsers[adjustedUserIndex]; + user.userStatus = userInDb.userStatus; + if (UserStatusEnum::kAvailable == user.userStatus) + { + ChipLogDetail(Zcl, "Found unoccupied user [endpoint=%d,adjustedIndex=%hu]", mEndpointId, adjustedUserIndex); + return true; + } + + user.userName = userInDb.userName; + user.credentials = chip::Span(userInDb.credentials.data(), userInDb.credentials.size()); + user.userUniqueId = userInDb.userUniqueId; + user.userType = userInDb.userType; + user.credentialRule = userInDb.credentialRule; + // So far there's no way to actually create the credential outside the matter, so here we always set the creation/modification + // source to Matter + user.creationSource = DlAssetSource::kMatterIM; + user.createdBy = userInDb.createdBy; + user.modificationSource = DlAssetSource::kMatterIM; + user.lastModifiedBy = userInDb.lastModifiedBy; + + ChipLogDetail(Zcl, + "Found occupied user " + "[endpoint=%d,adjustedIndex=%hu,name=\"%.*s\",credentialsCount=%u,uniqueId=%x,type=%u,credentialRule=%u," + "createdBy=%d,lastModifiedBy=%d]", + mEndpointId, adjustedUserIndex, static_cast(user.userName.size()), user.userName.data(), + static_cast(user.credentials.size()), user.userUniqueId, to_underlying(user.userType), + to_underlying(user.credentialRule), user.createdBy, user.lastModifiedBy); + + return true; +} + +bool LockEndpoint::SetUser(uint16_t userIndex, chip::FabricIndex creator, chip::FabricIndex modifier, + const chip::CharSpan & userName, uint32_t uniqueId, UserStatusEnum userStatus, UserTypeEnum usertype, + CredentialRuleEnum credentialRule, const CredentialStruct * credentials, size_t totalCredentials) +{ + ChipLogProgress(Zcl, + "Lock App: LockEndpoint::SetUser " + "[endpoint=%d,userIndex=%u,creator=%d,modifier=%d,userName=\"%.*s\",uniqueId=%" PRIx32 + ",userStatus=%u,userType=%u," + "credentialRule=%u,credentials=%p,totalCredentials=%u]", + mEndpointId, userIndex, creator, modifier, static_cast(userName.size()), userName.data(), uniqueId, + to_underlying(userStatus), to_underlying(usertype), to_underlying(credentialRule), credentials, + static_cast(totalCredentials)); + + auto adjustedUserIndex = static_cast(userIndex - 1); + if (adjustedUserIndex > mLockUsers.size()) + { + ChipLogError(Zcl, "Cannot set user - index out of range [endpoint=%d,index=%d,adjustedUserIndex=%u]", mEndpointId, + userIndex, adjustedUserIndex); + return false; + } + + auto & userInStorage = mLockUsers[adjustedUserIndex]; + if (userName.size() > DOOR_LOCK_MAX_USER_NAME_SIZE) + { + ChipLogError(Zcl, "Cannot set user - user name is too long [endpoint=%d,index=%d,adjustedUserIndex=%u]", mEndpointId, + userIndex, adjustedUserIndex); + return false; + } + + if (totalCredentials > userInStorage.credentials.capacity()) + { + ChipLogError(Zcl, + "Cannot set user - total number of credentials is too big [endpoint=%d,index=%d,adjustedUserIndex=%u" + ",totalCredentials=%u,maxNumberOfCredentials=%u]", + mEndpointId, userIndex, adjustedUserIndex, static_cast(totalCredentials), + static_cast(userInStorage.credentials.capacity())); + return false; + } + + userInStorage.userName = chip::MutableCharSpan(userInStorage.userNameBuf, DOOR_LOCK_USER_NAME_BUFFER_SIZE); + CopyCharSpanToMutableCharSpan(userName, userInStorage.userName); + userInStorage.userUniqueId = uniqueId; + userInStorage.userStatus = userStatus; + userInStorage.userType = usertype; + userInStorage.credentialRule = credentialRule; + userInStorage.lastModifiedBy = modifier; + userInStorage.createdBy = creator; + + userInStorage.credentials.clear(); + for (size_t i = 0; i < totalCredentials; ++i) + { + userInStorage.credentials.push_back(credentials[i]); + } + + ChipLogProgress(Zcl, "Successfully set the user [mEndpointId=%d,index=%d,adjustedIndex=%d]", mEndpointId, userIndex, + adjustedUserIndex); + + return true; +} + +DoorStateEnum LockEndpoint::GetDoorState() const +{ + return mDoorState; +} + +bool LockEndpoint::SetDoorState(DoorStateEnum newState) +{ + if (mDoorState != newState) + { + ChipLogProgress(Zcl, "Changing the door state to: %d [endpointId=%d,previousState=%d]", to_underlying(newState), + mEndpointId, to_underlying(mDoorState)); + + mDoorState = newState; + return DoorLockServer::Instance().SetDoorState(mEndpointId, mDoorState); + } + return true; +} + +bool LockEndpoint::SendLockAlarm(AlarmCodeEnum alarmCode) const +{ + ChipLogProgress(Zcl, "Sending the LockAlarm event [endpointId=%d,alarmCode=%u]", mEndpointId, to_underlying(alarmCode)); + return DoorLockServer::Instance().SendLockAlarmEvent(mEndpointId, alarmCode); +} + +bool LockEndpoint::GetCredential(uint16_t credentialIndex, CredentialTypeEnum credentialType, + EmberAfPluginDoorLockCredentialInfo & credential) const +{ + ChipLogProgress(Zcl, "Lock App: LockEndpoint::GetCredential [endpoint=%d,credentialIndex=%u,credentialType=%u]", mEndpointId, + credentialIndex, to_underlying(credentialType)); + + if (to_underlying(credentialType) >= mLockCredentials.size()) + { + ChipLogError(Zcl, "Cannot get the credential - index out of range [endpoint=%d,index=%d]", mEndpointId, credentialIndex); + return false; + } + + if (credentialIndex >= mLockCredentials.at(to_underlying(credentialType)).size() || + (0 == credentialIndex && CredentialTypeEnum::kProgrammingPIN != credentialType)) + { + ChipLogError(Zcl, "Cannot get the credential - index out of range [endpoint=%d,index=%d]", mEndpointId, credentialIndex); + return false; + } + + const auto & credentialInStorage = mLockCredentials[to_underlying(credentialType)][credentialIndex]; + + credential.status = credentialInStorage.status; + if (DlCredentialStatus::kAvailable == credential.status) + { + ChipLogDetail(Zcl, "Found unoccupied credential [endpoint=%d,index=%u]", mEndpointId, credentialIndex); + return true; + } + credential.credentialType = credentialInStorage.credentialType; + credential.credentialData = chip::ByteSpan(credentialInStorage.credentialData, credentialInStorage.credentialDataSize); + // So far there's no way to actually create the credential outside the matter, so here we always set the creation/modification + // source to Matter + credential.creationSource = DlAssetSource::kMatterIM; + credential.createdBy = credentialInStorage.createdBy; + credential.modificationSource = DlAssetSource::kMatterIM; + credential.lastModifiedBy = credentialInStorage.modifiedBy; + + ChipLogDetail(Zcl, "Found occupied credential [endpoint=%d,index=%u,type=%u,dataSize=%u,createdBy=%u,modifiedBy=%u]", + mEndpointId, credentialIndex, to_underlying(credential.credentialType), + static_cast(credential.credentialData.size()), credential.createdBy, credential.lastModifiedBy); + + return true; +} + +bool LockEndpoint::SetCredential(uint16_t credentialIndex, chip::FabricIndex creator, chip::FabricIndex modifier, + DlCredentialStatus credentialStatus, CredentialTypeEnum credentialType, + const chip::ByteSpan & credentialData) +{ + ChipLogProgress( + Zcl, + "Lock App: LockEndpoint::SetCredential " + "[endpoint=%d,credentialIndex=%u,credentialStatus=%u,credentialType=%u,credentialDataSize=%u,creator=%u,modifier=%u]", + mEndpointId, credentialIndex, to_underlying(credentialStatus), to_underlying(credentialType), + static_cast(credentialData.size()), creator, modifier); + + if (to_underlying(credentialType) >= mLockCredentials.capacity()) + { + ChipLogError(Zcl, "Cannot set the credential - type out of range [endpoint=%d,type=%d]", mEndpointId, + to_underlying(credentialType)); + return false; + } + + if (credentialIndex >= mLockCredentials.at(to_underlying(credentialType)).size() || + (0 == credentialIndex && CredentialTypeEnum::kProgrammingPIN != credentialType)) + { + ChipLogError(Zcl, "Cannot set the credential - index out of range [endpoint=%d,index=%d]", mEndpointId, credentialIndex); + return false; + } + + // Assign to array by credentialIndex. Note: 0 is reserved for programmingPIN only + auto & credentialInStorage = mLockCredentials[to_underlying(credentialType)][credentialIndex]; + if (credentialData.size() > DOOR_LOCK_CREDENTIAL_INFO_MAX_DATA_SIZE) + { + ChipLogError(Zcl, + "Cannot get the credential - data size exceeds limit " + "[endpoint=%d,index=%d,dataSize=%u,maxDataSize=%u]", + mEndpointId, credentialIndex, static_cast(credentialData.size()), + static_cast(DOOR_LOCK_CREDENTIAL_INFO_MAX_DATA_SIZE)); + return false; + } + credentialInStorage.status = credentialStatus; + credentialInStorage.credentialType = credentialType; + credentialInStorage.createdBy = creator; + credentialInStorage.modifiedBy = modifier; + std::memcpy(credentialInStorage.credentialData, credentialData.data(), credentialData.size()); + credentialInStorage.credentialDataSize = credentialData.size(); + + ChipLogProgress(Zcl, "Successfully set the credential [mEndpointId=%d,index=%d,credentialType=%u,creator=%u,modifier=%u]", + mEndpointId, credentialIndex, to_underlying(credentialType), credentialInStorage.createdBy, + credentialInStorage.modifiedBy); + + return true; +} + +DlStatus LockEndpoint::GetSchedule(uint8_t weekDayIndex, uint16_t userIndex, EmberAfPluginDoorLockWeekDaySchedule & schedule) +{ + if (0 == userIndex || userIndex > mWeekDaySchedules.size()) + { + return DlStatus::kFailure; + } + + if (0 == weekDayIndex || weekDayIndex > mWeekDaySchedules.at(userIndex - 1).size()) + { + return DlStatus::kFailure; + } + + const auto & scheduleInStorage = mWeekDaySchedules.at(userIndex - 1).at(weekDayIndex - 1); + if (DlScheduleStatus::kAvailable == scheduleInStorage.status) + { + return DlStatus::kNotFound; + } + + schedule = scheduleInStorage.schedule; + + return DlStatus::kSuccess; +} + +DlStatus LockEndpoint::SetSchedule(uint8_t weekDayIndex, uint16_t userIndex, DlScheduleStatus status, DaysMaskMap daysMask, + uint8_t startHour, uint8_t startMinute, uint8_t endHour, uint8_t endMinute) +{ + if (0 == userIndex || userIndex > mWeekDaySchedules.size()) + { + return DlStatus::kFailure; + } + + if (0 == weekDayIndex || weekDayIndex > mWeekDaySchedules.at(userIndex - 1).size()) + { + return DlStatus::kFailure; + } + + auto & scheduleInStorage = mWeekDaySchedules.at(userIndex - 1).at(weekDayIndex - 1); + + scheduleInStorage.schedule.daysMask = daysMask; + scheduleInStorage.schedule.startHour = startHour; + scheduleInStorage.schedule.startMinute = startMinute; + scheduleInStorage.schedule.endHour = endHour; + scheduleInStorage.schedule.endMinute = endMinute; + scheduleInStorage.status = status; + + return DlStatus::kSuccess; +} + +DlStatus LockEndpoint::GetSchedule(uint8_t yearDayIndex, uint16_t userIndex, EmberAfPluginDoorLockYearDaySchedule & schedule) +{ + if (0 == userIndex || userIndex > mYearDaySchedules.size()) + { + return DlStatus::kFailure; + } + + if (0 == yearDayIndex || yearDayIndex > mYearDaySchedules.at(userIndex - 1).size()) + { + return DlStatus::kFailure; + } + + const auto & scheduleInStorage = mYearDaySchedules.at(userIndex - 1).at(yearDayIndex - 1); + if (DlScheduleStatus::kAvailable == scheduleInStorage.status) + { + return DlStatus::kNotFound; + } + + schedule = scheduleInStorage.schedule; + + return DlStatus::kSuccess; +} + +DlStatus LockEndpoint::SetSchedule(uint8_t yearDayIndex, uint16_t userIndex, DlScheduleStatus status, uint32_t localStartTime, + uint32_t localEndTime) +{ + if (0 == userIndex || userIndex > mYearDaySchedules.size()) + { + return DlStatus::kFailure; + } + + if (0 == yearDayIndex || yearDayIndex > mYearDaySchedules.at(userIndex - 1).size()) + { + return DlStatus::kFailure; + } + + auto & scheduleInStorage = mYearDaySchedules.at(userIndex - 1).at(yearDayIndex - 1); + scheduleInStorage.schedule.localStartTime = localStartTime; + scheduleInStorage.schedule.localEndTime = localEndTime; + scheduleInStorage.status = status; + + return DlStatus::kSuccess; +} + +DlStatus LockEndpoint::GetSchedule(uint8_t holidayIndex, EmberAfPluginDoorLockHolidaySchedule & schedule) +{ + if (0 == holidayIndex || holidayIndex > mHolidaySchedules.size()) + { + return DlStatus::kFailure; + } + + const auto & scheduleInStorage = mHolidaySchedules[holidayIndex - 1]; + if (DlScheduleStatus::kAvailable == scheduleInStorage.status) + { + return DlStatus::kNotFound; + } + + schedule = scheduleInStorage.schedule; + return DlStatus::kSuccess; +} + +DlStatus LockEndpoint::SetSchedule(uint8_t holidayIndex, DlScheduleStatus status, uint32_t localStartTime, uint32_t localEndTime, + OperatingModeEnum operatingMode) +{ + if (0 == holidayIndex || holidayIndex > mHolidaySchedules.size()) + { + return DlStatus::kFailure; + } + + auto & scheduleInStorage = mHolidaySchedules[holidayIndex - 1]; + scheduleInStorage.schedule.localStartTime = localStartTime; + scheduleInStorage.schedule.localEndTime = localEndTime; + scheduleInStorage.schedule.operatingMode = operatingMode; + scheduleInStorage.status = status; + + return DlStatus::kSuccess; +} + +bool LockEndpoint::setLockState(const Nullable & fabricIdx, const Nullable & nodeId, + DlLockState lockState, const Optional & pin, OperationErrorEnum & err, + OperationSourceEnum opSource) +{ + // Assume pin is required until told otherwise + bool requirePin = true; + chip::app::Clusters::DoorLock::Attributes::RequirePINforRemoteOperation::Get(mEndpointId, &requirePin); + + // If a pin code is not given + if (!pin.HasValue()) + { + ChipLogDetail(Zcl, "Door Lock App: PIN code is not specified [endpointId=%d]", mEndpointId); + + // If a pin code is not required + if (!requirePin) + { + ChipLogProgress(Zcl, "Door Lock App: setting door lock state to \"%s\" [endpointId=%d]", lockStateToString(lockState), + mEndpointId); + + if (gCurrentAction.moving == true) + { + ChipLogProgress(Zcl, "Lock App: not executing lock action as another lock action is already active [endpointId=%d]", + mEndpointId); + return false; + } + + gCurrentAction.moving = true; + gCurrentAction.endpointId = mEndpointId; + gCurrentAction.lockState = lockState; + gCurrentAction.opSource = opSource; + gCurrentAction.userIndex = NullNullable; + gCurrentAction.fabricIdx = fabricIdx; + gCurrentAction.nodeId = nodeId; + + // Do this async as a real lock would do too but use 0s delay to speed up CI tests + chip::DeviceLayer::SystemLayer().StartTimer(chip::System::Clock::Seconds16(0), OnLockActionCompleteCallback, nullptr); + + return true; + } + + ChipLogError(Zcl, "Door Lock App: PIN code is not specified, but it is required [endpointId=%d]", mEndpointId); + + err = OperationErrorEnum::kInvalidCredential; + return false; + } + + // Find the credential so we can make sure it is not absent right away + auto & pinCredentials = mLockCredentials[to_underlying(CredentialTypeEnum::kPin)]; + auto credential = std::find_if(pinCredentials.begin(), pinCredentials.end(), [&pin](const LockCredentialInfo & c) { + return (c.status != DlCredentialStatus::kAvailable) && + chip::ByteSpan{ c.credentialData, c.credentialDataSize }.data_equal(pin.Value()); + }); + if (credential == pinCredentials.end()) + { + ChipLogDetail(Zcl, + "Lock App: specified PIN code was not found in the database, ignoring command to set lock state to \"%s\" " + "[endpointId=%d]", + lockStateToString(lockState), mEndpointId); + + err = OperationErrorEnum::kInvalidCredential; + return false; + } + + // Find a user that correspond to this credential + auto credentialIndex = static_cast(credential - pinCredentials.begin()); + auto user = std::find_if(mLockUsers.begin(), mLockUsers.end(), [credential, credentialIndex](const LockUserInfo & u) { + return std::any_of(u.credentials.begin(), u.credentials.end(), [&credential, credentialIndex](const CredentialStruct & c) { + return c.credentialIndex == credentialIndex && c.credentialType == credential->credentialType; + }); + }); + if (user == mLockUsers.end()) + { + ChipLogDetail(Zcl, + "Lock App: specified PIN code was found in the database, but the lock user is not associated with it " + "[endpointId=%d,credentialIndex=%u]", + mEndpointId, credentialIndex); + } + + auto userIndex = static_cast(user - mLockUsers.begin()); + + // Check if schedules affect the user + bool haveWeekDaySchedules = false; + bool haveYearDaySchedules = false; + if (weekDayScheduleForbidsAccess(userIndex, &haveWeekDaySchedules) || + yearDayScheduleForbidsAccess(userIndex, &haveYearDaySchedules) || + // Also disallow access for a user that's supposed to have _some_ + // schedule but doesn't have any + (user->userType == UserTypeEnum::kScheduleRestrictedUser && !haveWeekDaySchedules && !haveYearDaySchedules)) + { + ChipLogDetail(Zcl, + "Lock App: associated user is not allowed to operate the lock due to schedules" + "[endpointId=%d,userIndex=%u]", + mEndpointId, userIndex); + err = OperationErrorEnum::kRestricted; + return false; + } + ChipLogProgress( + Zcl, + "Lock App: specified PIN code was found in the database, setting door lock state to \"%s\" [endpointId=%d,userIndex=%u]", + lockStateToString(lockState), mEndpointId, userIndex); + + if (gCurrentAction.moving == true) + { + ChipLogProgress(Zcl, + "Lock App: not executing lock action as another lock action is already active [endpointId=%d,userIndex=%u]", + mEndpointId, userIndex); + return false; + } + + gCurrentAction.moving = true; + gCurrentAction.endpointId = mEndpointId; + gCurrentAction.lockState = lockState; + gCurrentAction.opSource = opSource; + gCurrentAction.userIndex = MakeNullable(static_cast(userIndex + 1)); + gCurrentAction.credentialIndex = static_cast(credentialIndex); + gCurrentAction.fabricIdx = fabricIdx; + gCurrentAction.nodeId = nodeId; + + // Do this async as a real lock would do too but use 0s delay to speed up CI tests + chip::DeviceLayer::SystemLayer().StartTimer(chip::System::Clock::Seconds16(0), OnLockActionCompleteCallback, nullptr); + + return true; +} + +void LockEndpoint::OnLockActionCompleteCallback(chip::System::Layer *, void * callbackContext) +{ + if (gCurrentAction.userIndex.IsNull()) + { + DoorLockServer::Instance().SetLockState(gCurrentAction.endpointId, gCurrentAction.lockState, gCurrentAction.opSource, + NullNullable, NullNullable, gCurrentAction.fabricIdx, gCurrentAction.nodeId); + } + else + { + LockOpCredentials userCredential[] = { { CredentialTypeEnum::kPin, gCurrentAction.credentialIndex } }; + auto userCredentials = MakeNullable>(userCredential); + + DoorLockServer::Instance().SetLockState(gCurrentAction.endpointId, gCurrentAction.lockState, gCurrentAction.opSource, + gCurrentAction.userIndex, userCredentials, gCurrentAction.fabricIdx, + gCurrentAction.nodeId); + } + + // move back to Unlocked after Unlatch + if (gCurrentAction.lockState == DlLockState::kUnlatched) + { + gCurrentAction.lockState = DlLockState::kUnlocked; + + // Do this async as a real lock would do too but use 0s delay to speed up CI tests + chip::DeviceLayer::SystemLayer().StartTimer(chip::System::Clock::Seconds16(0), OnLockActionCompleteCallback, nullptr); + } + else + { + gCurrentAction.moving = false; + } +} + +bool LockEndpoint::weekDayScheduleForbidsAccess(uint16_t userIndex, bool * haveSchedule) const +{ + *haveSchedule = std::any_of(mWeekDaySchedules[userIndex].begin(), mWeekDaySchedules[userIndex].end(), + [](const WeekDaysScheduleInfo & s) { return s.status == DlScheduleStatus::kOccupied; }); + + const auto & user = mLockUsers[userIndex]; + if (user.userType != UserTypeEnum::kScheduleRestrictedUser && user.userType != UserTypeEnum::kWeekDayScheduleUser) + { + // Weekday schedules don't apply to this user. + return false; + } + + if (user.userType == UserTypeEnum::kScheduleRestrictedUser && !*haveSchedule) + { + // It's valid to not have any schedules of a given type; on its own this + // does not prevent access. + return false; + } + + chip::System::Clock::Milliseconds64 cTMs; + auto chipError = chip::System::SystemClock().GetClock_RealTimeMS(cTMs); + if (chipError != CHIP_NO_ERROR) + { + ChipLogError(Zcl, "Lock App: unable to get current time to check user schedules [endpointId=%d,error=%d (%s)]", mEndpointId, + chipError.AsInteger(), chipError.AsString()); + return true; + } + time_t unixEpoch = std::chrono::duration_cast(cTMs).count(); + + tm calendarTime{}; + localtime_r(&unixEpoch, &calendarTime); + + auto currentTime = + calendarTime.tm_hour * chip::kSecondsPerHour + calendarTime.tm_min * chip::kSecondsPerMinute + calendarTime.tm_sec; + + // Now check whether any schedule allows the current time. If it does, + // access is not forbidden. + return !std::any_of( + mWeekDaySchedules[userIndex].begin(), mWeekDaySchedules[userIndex].end(), + [currentTime, calendarTime](const WeekDaysScheduleInfo & s) { + auto startTime = s.schedule.startHour * chip::kSecondsPerHour + s.schedule.startMinute * chip::kSecondsPerMinute; + auto endTime = s.schedule.endHour * chip::kSecondsPerHour + s.schedule.endMinute * chip::kSecondsPerMinute; + + return s.status == DlScheduleStatus::kOccupied && (to_underlying(s.schedule.daysMask) & (1 << calendarTime.tm_wday)) && + startTime <= currentTime && currentTime <= endTime; + }); +} + +bool LockEndpoint::yearDayScheduleForbidsAccess(uint16_t userIndex, bool * haveSchedule) const +{ + *haveSchedule = std::any_of(mYearDaySchedules[userIndex].begin(), mYearDaySchedules[userIndex].end(), + [](const YearDayScheduleInfo & sch) { return sch.status == DlScheduleStatus::kOccupied; }); + + const auto & user = mLockUsers[userIndex]; + if (user.userType != UserTypeEnum::kScheduleRestrictedUser && user.userType != UserTypeEnum::kYearDayScheduleUser) + { + return false; + } + + if (user.userType == UserTypeEnum::kScheduleRestrictedUser && !*haveSchedule) + { + // It's valid to not have any schedules of a given type; on its own this + // does not prevent access. + return false; + } + + chip::System::Clock::Milliseconds64 cTMs; + auto chipError = chip::System::SystemClock().GetClock_RealTimeMS(cTMs); + if (chipError != CHIP_NO_ERROR) + { + ChipLogError(Zcl, "Lock App: unable to get current time to check user schedules [endpointId=%d,error=%d (%s)]", mEndpointId, + chipError.AsInteger(), chipError.AsString()); + return true; + } + auto unixEpoch = std::chrono::duration_cast(cTMs).count(); + uint32_t chipEpoch = 0; + if (!chip::UnixEpochToChipEpochTime(unixEpoch, chipEpoch)) + { + ChipLogError(Zcl, + "Lock App: unable to convert Unix Epoch time to Matter Epoch Time to check user schedules " + "[endpointId=%d,userIndex=%d]", + mEndpointId, userIndex); + return false; + } + + return !std::any_of(mYearDaySchedules[userIndex].begin(), mYearDaySchedules[userIndex].end(), + [chipEpoch](const YearDayScheduleInfo & sch) { + return sch.status == DlScheduleStatus::kOccupied && sch.schedule.localStartTime <= chipEpoch && + chipEpoch <= sch.schedule.localEndTime; + }); +} + +const char * LockEndpoint::lockStateToString(DlLockState lockState) const +{ + switch (lockState) + { + case DlLockState::kNotFullyLocked: + return "Not Fully Locked"; + case DlLockState::kLocked: + return "Locked"; + case DlLockState::kUnlocked: + return "Unlocked"; + case DlLockState::kUnlatched: + return "Unlatched"; + case DlLockState::kUnknownEnumValue: + break; + } + + return "Unknown"; +} +#endif // MATTER_DM_PLUGIN_DOOR_LOCK_SERVER diff --git a/examples/chef/common/clusters/door-lock/chef-lock-endpoint.h b/examples/chef/common/clusters/door-lock/chef-lock-endpoint.h new file mode 100644 index 00000000000000..285e703dfaf73c --- /dev/null +++ b/examples/chef/common/clusters/door-lock/chef-lock-endpoint.h @@ -0,0 +1,162 @@ +/* + * + * Copyright (c) 2022-2024 Project CHIP Authors + * All rights reserved. + * + * 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. + */ + +#pragma once + +#include +#include + +struct LockUserInfo +{ + char userNameBuf[DOOR_LOCK_USER_NAME_BUFFER_SIZE]; + chip::MutableCharSpan userName; + uint32_t userUniqueId; + UserStatusEnum userStatus; + UserTypeEnum userType; + CredentialRuleEnum credentialRule; + std::vector credentials; + chip::FabricIndex createdBy; + chip::FabricIndex lastModifiedBy; +}; + +struct LockCredentialInfo; +struct WeekDaysScheduleInfo; +struct YearDayScheduleInfo; +struct HolidayScheduleInfo; + +static constexpr size_t DOOR_LOCK_CREDENTIAL_INFO_MAX_DATA_SIZE = 20; +static constexpr size_t DOOR_LOCK_CREDENTIAL_INFO_MAX_TYPES = 6; // 0: ProgrammingPIN ~ 5: Face + +class LockEndpoint +{ +public: + LockEndpoint(chip::EndpointId endpointId, uint16_t numberOfLockUsersSupported, uint16_t numberOfCredentialsSupported, + uint8_t weekDaySchedulesPerUser, uint8_t yearDaySchedulesPerUser, uint8_t numberOfCredentialsPerUser, + uint8_t numberOfHolidaySchedules) : + mEndpointId{ endpointId }, + mLockState{ DlLockState::kLocked }, mDoorState{ DoorStateEnum::kDoorClosed }, mLockUsers(numberOfLockUsersSupported), + mLockCredentials(DOOR_LOCK_CREDENTIAL_INFO_MAX_TYPES, std::vector(numberOfCredentialsSupported + 1)), + mWeekDaySchedules(numberOfLockUsersSupported, std::vector(weekDaySchedulesPerUser)), + mYearDaySchedules(numberOfLockUsersSupported, std::vector(yearDaySchedulesPerUser)), + mHolidaySchedules(numberOfHolidaySchedules) + { + for (auto & lockUser : mLockUsers) + { + lockUser.credentials.reserve(numberOfCredentialsPerUser); + } + DoorLockServer::Instance().SetDoorState(endpointId, mDoorState); + DoorLockServer::Instance().SetLockState(endpointId, mLockState); + } + + inline chip::EndpointId GetEndpointId() const { return mEndpointId; } + + bool Lock(const Nullable & fabricIdx, const Nullable & nodeId, + const Optional & pin, OperationErrorEnum & err, OperationSourceEnum opSource); + bool Unlock(const Nullable & fabricIdx, const Nullable & nodeId, + const Optional & pin, OperationErrorEnum & err, OperationSourceEnum opSource); + bool Unbolt(const Nullable & fabricIdx, const Nullable & nodeId, + const Optional & pin, OperationErrorEnum & err, OperationSourceEnum opSource); + + bool GetUser(uint16_t userIndex, EmberAfPluginDoorLockUserInfo & user) const; + bool SetUser(uint16_t userIndex, chip::FabricIndex creator, chip::FabricIndex modifier, const chip::CharSpan & userName, + uint32_t uniqueId, UserStatusEnum userStatus, UserTypeEnum usertype, CredentialRuleEnum credentialRule, + const CredentialStruct * credentials, size_t totalCredentials); + + bool SetDoorState(DoorStateEnum newState); + + DoorStateEnum GetDoorState() const; + + bool SendLockAlarm(AlarmCodeEnum alarmCode) const; + + bool GetCredential(uint16_t credentialIndex, CredentialTypeEnum credentialType, + EmberAfPluginDoorLockCredentialInfo & credential) const; + + bool SetCredential(uint16_t credentialIndex, chip::FabricIndex creator, chip::FabricIndex modifier, + DlCredentialStatus credentialStatus, CredentialTypeEnum credentialType, + const chip::ByteSpan & credentialData); + + DlStatus GetSchedule(uint8_t weekDayIndex, uint16_t userIndex, EmberAfPluginDoorLockWeekDaySchedule & schedule); + DlStatus GetSchedule(uint8_t yearDayIndex, uint16_t userIndex, EmberAfPluginDoorLockYearDaySchedule & schedule); + DlStatus GetSchedule(uint8_t holidayIndex, EmberAfPluginDoorLockHolidaySchedule & schedule); + + DlStatus SetSchedule(uint8_t weekDayIndex, uint16_t userIndex, DlScheduleStatus status, DaysMaskMap daysMask, uint8_t startHour, + uint8_t startMinute, uint8_t endHour, uint8_t endMinute); + DlStatus SetSchedule(uint8_t yearDayIndex, uint16_t userIndex, DlScheduleStatus status, uint32_t localStartTime, + uint32_t localEndTime); + DlStatus SetSchedule(uint8_t holidayIndex, DlScheduleStatus status, uint32_t localStartTime, uint32_t localEndTime, + OperatingModeEnum operatingMode); + +private: + bool setLockState(const Nullable & fabricIdx, const Nullable & nodeId, DlLockState lockState, + const Optional & pin, OperationErrorEnum & err, + OperationSourceEnum opSource = OperationSourceEnum::kUnspecified); + const char * lockStateToString(DlLockState lockState) const; + + // Returns true if week day schedules should apply to the user, there are + // schedules defined for the user, and access is not currently allowed by + // those schedules. The outparam indicates whether there were in fact any + // year day schedules defined for the user. + bool weekDayScheduleForbidsAccess(uint16_t userIndex, bool * haveSchedule) const; + // Returns true if year day schedules should apply to the user, there are + // schedules defined for the user, and access is not currently allowed by + // those schedules. The outparam indicates whether there were in fact any + // year day schedules defined for the user. + bool yearDayScheduleForbidsAccess(uint16_t userIndex, bool * haveSchedule) const; + + static void OnLockActionCompleteCallback(chip::System::Layer *, void * callbackContext); + + chip::EndpointId mEndpointId; + DlLockState mLockState; + DoorStateEnum mDoorState; + + // This is very naive implementation of users/credentials/schedules database and by no means the best practice. Proper storage + // of those items is out of scope of this example. + std::vector mLockUsers; + std::vector> mLockCredentials; + std::vector> mWeekDaySchedules; + std::vector> mYearDaySchedules; + std::vector mHolidaySchedules; +}; + +struct LockCredentialInfo +{ + DlCredentialStatus status; + CredentialTypeEnum credentialType; + chip::FabricIndex createdBy; + chip::FabricIndex modifiedBy; + uint8_t credentialData[DOOR_LOCK_CREDENTIAL_INFO_MAX_DATA_SIZE]; + size_t credentialDataSize; +}; + +struct WeekDaysScheduleInfo +{ + DlScheduleStatus status; + EmberAfPluginDoorLockWeekDaySchedule schedule; +}; + +struct YearDayScheduleInfo +{ + DlScheduleStatus status; + EmberAfPluginDoorLockYearDaySchedule schedule; +}; + +struct HolidayScheduleInfo +{ + DlScheduleStatus status; + EmberAfPluginDoorLockHolidaySchedule schedule; +}; diff --git a/examples/chef/common/clusters/door-lock/chef-lock-manager.cpp b/examples/chef/common/clusters/door-lock/chef-lock-manager.cpp new file mode 100644 index 00000000000000..0b81cce895b264 --- /dev/null +++ b/examples/chef/common/clusters/door-lock/chef-lock-manager.cpp @@ -0,0 +1,376 @@ +/* + * + * Copyright (c) 2020-2024 Project CHIP Authors + * All rights reserved. + * + * 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. + */ +#include +#include +#include + +#ifdef MATTER_DM_PLUGIN_DOOR_LOCK_SERVER +#include "chef-lock-manager.h" + +using chip::to_underlying; + +LockManager LockManager::instance; + +LockManager & LockManager::Instance() +{ + return instance; +} + +bool LockManager::InitEndpoint(chip::EndpointId endpointId) +{ + uint16_t numberOfSupportedUsers = 0; + if (!DoorLockServer::Instance().GetNumberOfUserSupported(endpointId, numberOfSupportedUsers)) + { + ChipLogError(Zcl, + "Unable to get number of supported users when initializing lock endpoint, defaulting to 10 [endpointId=%d]", + endpointId); + numberOfSupportedUsers = 10; + } + + uint16_t numberOfSupportedCredentials = 0; + // We're planning to use shared storage for PIN and RFID users so we will have the maximum of both sizes her to simplify logic + uint16_t numberOfPINCredentialsSupported = 0; + uint16_t numberOfRFIDCredentialsSupported = 0; + if (!DoorLockServer::Instance().GetNumberOfPINCredentialsSupported(endpointId, numberOfPINCredentialsSupported) || + !DoorLockServer::Instance().GetNumberOfRFIDCredentialsSupported(endpointId, numberOfRFIDCredentialsSupported)) + { + ChipLogError( + Zcl, "Unable to get number of supported credentials when initializing lock endpoint, defaulting to 10 [endpointId=%d]", + endpointId); + numberOfSupportedCredentials = 10; + } + else + { + numberOfSupportedCredentials = std::max(numberOfPINCredentialsSupported, numberOfRFIDCredentialsSupported); + } + + uint8_t numberOfCredentialsSupportedPerUser = 0; + if (!DoorLockServer::Instance().GetNumberOfCredentialsSupportedPerUser(endpointId, numberOfCredentialsSupportedPerUser)) + { + ChipLogError(Zcl, + "Unable to get number of credentials supported per user when initializing lock endpoint, defaulting to 5 " + "[endpointId=%d]", + endpointId); + numberOfCredentialsSupportedPerUser = 5; + } + + uint8_t numberOfWeekDaySchedulesPerUser = 0; + if (!DoorLockServer::Instance().GetNumberOfWeekDaySchedulesPerUserSupported(endpointId, numberOfWeekDaySchedulesPerUser)) + { + ChipLogError(Zcl, + "Unable to get number of supported week day schedules per user when initializing lock endpoint, defaulting to " + "10 [endpointId=%d]", + endpointId); + numberOfWeekDaySchedulesPerUser = 10; + } + + uint8_t numberOfYearDaySchedulesPerUser = 0; + if (!DoorLockServer::Instance().GetNumberOfYearDaySchedulesPerUserSupported(endpointId, numberOfYearDaySchedulesPerUser)) + { + ChipLogError(Zcl, + "Unable to get number of supported year day schedules per user when initializing lock endpoint, defaulting to " + "10 [endpointId=%d]", + endpointId); + numberOfYearDaySchedulesPerUser = 10; + } + + uint8_t numberOfHolidaySchedules = 0; + if (!DoorLockServer::Instance().GetNumberOfHolidaySchedulesSupported(endpointId, numberOfHolidaySchedules)) + { + ChipLogError( + Zcl, + "Unable to get number of supported holiday schedules when initializing lock endpoint, defaulting to 10 [endpointId=%d]", + endpointId); + numberOfHolidaySchedules = 10; + } + + mEndpoints.emplace_back(endpointId, numberOfSupportedUsers, numberOfSupportedCredentials, numberOfWeekDaySchedulesPerUser, + numberOfYearDaySchedulesPerUser, numberOfCredentialsSupportedPerUser, numberOfHolidaySchedules); + + // Refer to 5.2.10.34. SetUser Command, when Creat a new user record + // - UserIndex value SHALL be set to a user record with UserType set to Available + // - UserName MAY be null causing new user record to use empty string for UserName + // otherwise UserName SHALL be set to the value provided in the new user record. + // - UserUniqueID MAY be null causing new user record to use 0xFFFFFFFF for UserUniqueID + // otherwise UserUniqueID SHALL be set to the value provided in the new user record + // - UserStatus MAY be null causing new user record to use OccupiedEnabled for UserStatus + // otherwise UserStatus SHALL be set to the value provided in the new user record + // - UserType MAY be null causing new user record to use UnrestrictedUser for UserType + // otherwise UserType SHALL be set to the value provided in the new user record. + uint16_t userIndex(1); + chip::FabricIndex creator(1); + chip::FabricIndex modifier(1); + const chip::CharSpan userName = chip::CharSpan::fromCharString("user1"); // default + // username + uint32_t uniqueId = 0xFFFFFFFF; // null + UserStatusEnum userStatus = UserStatusEnum::kOccupiedEnabled; + // Set to programming user instead of unrestrict user to perform + // priviledged function + UserTypeEnum usertype = UserTypeEnum::kProgrammingUser; + CredentialRuleEnum credentialRule = CredentialRuleEnum::kSingle; + + constexpr size_t totalCredentials(2); + // According to spec (5.2.6.26.2. CredentialIndex Field), programming PIN credential should be always indexed as 0 + uint16_t credentialIndex0(0); + // 1st non ProgrammingPIN credential should be indexed as 1 + uint16_t credentialIndex1(1); + + const CredentialStruct credentials[totalCredentials] = { + { credentialType : CredentialTypeEnum::kProgrammingPIN, credentialIndex : credentialIndex0 }, + { credentialType : CredentialTypeEnum::kPin, credentialIndex : credentialIndex1 } + }; + + if (!SetUser(endpointId, userIndex, creator, modifier, userName, uniqueId, userStatus, usertype, credentialRule, + &credentials[0], totalCredentials)) + { + ChipLogError(Zcl, "Unable to set the User [endpointId=%d]", endpointId); + return false; + } + + DlCredentialStatus credentialStatus = DlCredentialStatus::kOccupied; + + // Set the default user's ProgrammingPIN credential + uint8_t defaultProgrammingPIN[6] = { 0x39, 0x39, 0x39, 0x39, 0x39, 0x39 }; // 000000 + if (!SetCredential(endpointId, credentialIndex0, creator, modifier, credentialStatus, CredentialTypeEnum::kProgrammingPIN, + chip::ByteSpan(defaultProgrammingPIN))) + { + ChipLogError(Zcl, "Unable to set the credential - endpoint does not exist or not initialized [endpointId=%d]", endpointId); + return false; + } + + // Set the default user's non ProgrammingPIN credential + uint8_t defaultPin[6] = { 0x31, 0x32, 0x33, 0x34, 0x35, 0x36 }; // 123456 + if (!SetCredential(endpointId, credentialIndex1, creator, modifier, credentialStatus, CredentialTypeEnum::kPin, + chip::ByteSpan(defaultPin))) + { + ChipLogError(Zcl, "Unable to set the credential - endpoint does not exist or not initialized [endpointId=%d]", endpointId); + return false; + } + + ChipLogProgress(Zcl, + "Initialized new lock door endpoint " + "[id=%d,users=%d,credentials=%d,weekDaySchedulesPerUser=%d,yearDaySchedulesPerUser=%d," + "numberOfCredentialsSupportedPerUser=%d,holidaySchedules=%d]", + endpointId, numberOfSupportedUsers, numberOfSupportedCredentials, numberOfWeekDaySchedulesPerUser, + numberOfYearDaySchedulesPerUser, numberOfCredentialsSupportedPerUser, numberOfHolidaySchedules); + + return true; +} + +bool LockManager::SetDoorState(chip::EndpointId endpointId, DoorStateEnum doorState) +{ + auto lockEndpoint = getEndpoint(endpointId); + if (nullptr == lockEndpoint) + { + ChipLogError(Zcl, "Unable to toggle the door state - endpoint does not exist or not initialized [endpointId=%d]", + endpointId); + return false; + } + return lockEndpoint->SetDoorState(doorState); +} + +bool LockManager::SendLockAlarm(chip::EndpointId endpointId, AlarmCodeEnum alarmCode) +{ + auto lockEndpoint = getEndpoint(endpointId); + if (nullptr == lockEndpoint) + { + ChipLogError(Zcl, "Unable to send lock alarm - endpoint does not exist or not initialized [endpointId=%d]", endpointId); + return false; + } + return lockEndpoint->SendLockAlarm(alarmCode); +} + +bool LockManager::Lock(chip::EndpointId endpointId, const Nullable & fabricIdx, + const Nullable & nodeId, const Optional & pin, OperationErrorEnum & err, + OperationSourceEnum opSource) +{ + auto lockEndpoint = getEndpoint(endpointId); + if (nullptr == lockEndpoint) + { + ChipLogError(Zcl, "Unable to lock the door - endpoint does not exist or not initialized [endpointId=%d]", endpointId); + return false; + } + return lockEndpoint->Lock(fabricIdx, nodeId, pin, err, opSource); +} + +bool LockManager::Unlock(chip::EndpointId endpointId, const Nullable & fabricIdx, + const Nullable & nodeId, const Optional & pin, OperationErrorEnum & err, + OperationSourceEnum opSource) +{ + auto lockEndpoint = getEndpoint(endpointId); + if (nullptr == lockEndpoint) + { + ChipLogError(Zcl, "Unable to unlock the door - endpoint does not exist or not initialized [endpointId=%d]", endpointId); + return false; + } + return lockEndpoint->Unlock(fabricIdx, nodeId, pin, err, opSource); +} + +bool LockManager::Unbolt(chip::EndpointId endpointId, const Nullable & fabricIdx, + const Nullable & nodeId, const Optional & pin, OperationErrorEnum & err, + OperationSourceEnum opSource) +{ + auto lockEndpoint = getEndpoint(endpointId); + if (nullptr == lockEndpoint) + { + ChipLogError(Zcl, "Unable to unbolt the door - endpoint does not exist or not initialized [endpointId=%d]", endpointId); + return false; + } + return lockEndpoint->Unbolt(fabricIdx, nodeId, pin, err, opSource); +} + +bool LockManager::GetUser(chip::EndpointId endpointId, uint16_t userIndex, EmberAfPluginDoorLockUserInfo & user) +{ + auto lockEndpoint = getEndpoint(endpointId); + if (nullptr == lockEndpoint) + { + ChipLogError(Zcl, "Unable to get the user - endpoint does not exist or not initialized [endpointId=%d]", endpointId); + return false; + } + return lockEndpoint->GetUser(userIndex, user); +} + +bool LockManager::SetUser(chip::EndpointId endpointId, uint16_t userIndex, chip::FabricIndex creator, chip::FabricIndex modifier, + const chip::CharSpan & userName, uint32_t uniqueId, UserStatusEnum userStatus, UserTypeEnum usertype, + CredentialRuleEnum credentialRule, const CredentialStruct * credentials, size_t totalCredentials) +{ + auto lockEndpoint = getEndpoint(endpointId); + if (nullptr == lockEndpoint) + { + ChipLogError(Zcl, "Unable to set the user - endpoint does not exist or not initialized [endpointId=%d]", endpointId); + return false; + } + return lockEndpoint->SetUser(userIndex, creator, modifier, userName, uniqueId, userStatus, usertype, credentialRule, + credentials, totalCredentials); +} + +bool LockManager::GetCredential(chip::EndpointId endpointId, uint16_t credentialIndex, CredentialTypeEnum credentialType, + EmberAfPluginDoorLockCredentialInfo & credential) +{ + auto lockEndpoint = getEndpoint(endpointId); + if (nullptr == lockEndpoint) + { + ChipLogError(Zcl, "Unable to get the credential - endpoint does not exist or not initialized [endpointId=%d]", endpointId); + return false; + } + return lockEndpoint->GetCredential(credentialIndex, credentialType, credential); +} + +bool LockManager::SetCredential(chip::EndpointId endpointId, uint16_t credentialIndex, chip::FabricIndex creator, + chip::FabricIndex modifier, DlCredentialStatus credentialStatus, CredentialTypeEnum credentialType, + const chip::ByteSpan & credentialData) +{ + auto lockEndpoint = getEndpoint(endpointId); + if (nullptr == lockEndpoint) + { + ChipLogError(Zcl, "Unable to set the credential - endpoint does not exist or not initialized [endpointId=%d]", endpointId); + return false; + } + return lockEndpoint->SetCredential(credentialIndex, creator, modifier, credentialStatus, credentialType, credentialData); +} + +DlStatus LockManager::GetSchedule(chip::EndpointId endpointId, uint8_t weekDayIndex, uint16_t userIndex, + EmberAfPluginDoorLockWeekDaySchedule & schedule) +{ + auto lockEndpoint = getEndpoint(endpointId); + if (nullptr == lockEndpoint) + { + ChipLogError(Zcl, "Unable to get the week day schedule - endpoint does not exist or not initialized [endpointId=%d]", + endpointId); + return DlStatus::kFailure; + } + return lockEndpoint->GetSchedule(weekDayIndex, userIndex, schedule); +} + +DlStatus LockManager::SetSchedule(chip::EndpointId endpointId, uint8_t weekDayIndex, uint16_t userIndex, DlScheduleStatus status, + DaysMaskMap daysMask, uint8_t startHour, uint8_t startMinute, uint8_t endHour, uint8_t endMinute) +{ + auto lockEndpoint = getEndpoint(endpointId); + if (nullptr == lockEndpoint) + { + ChipLogError(Zcl, "Unable to set the week day schedule - endpoint does not exist or not initialized [endpointId=%d]", + endpointId); + return DlStatus::kFailure; + } + return lockEndpoint->SetSchedule(weekDayIndex, userIndex, status, daysMask, startHour, startMinute, endHour, endMinute); +} + +DlStatus LockManager::GetSchedule(chip::EndpointId endpointId, uint8_t yearDayIndex, uint16_t userIndex, + EmberAfPluginDoorLockYearDaySchedule & schedule) +{ + auto lockEndpoint = getEndpoint(endpointId); + if (nullptr == lockEndpoint) + { + ChipLogError(Zcl, "Unable to get the year day schedule - endpoint does not exist or not initialized [endpointId=%d]", + endpointId); + return DlStatus::kFailure; + } + return lockEndpoint->GetSchedule(yearDayIndex, userIndex, schedule); +} + +DlStatus LockManager::SetSchedule(chip::EndpointId endpointId, uint8_t yearDayIndex, uint16_t userIndex, DlScheduleStatus status, + uint32_t localStartTime, uint32_t localEndTime) +{ + auto lockEndpoint = getEndpoint(endpointId); + if (nullptr == lockEndpoint) + { + ChipLogError(Zcl, "Unable to set the year day schedule - endpoint does not exist or not initialized [endpointId=%d]", + endpointId); + return DlStatus::kFailure; + } + return lockEndpoint->SetSchedule(yearDayIndex, userIndex, status, localStartTime, localEndTime); +} + +DlStatus LockManager::GetSchedule(chip::EndpointId endpointId, uint8_t holidayIndex, + EmberAfPluginDoorLockHolidaySchedule & schedule) +{ + auto lockEndpoint = getEndpoint(endpointId); + if (nullptr == lockEndpoint) + { + ChipLogError(Zcl, "Unable to get the holiday schedule - endpoint does not exist or not initialized [endpointId=%d]", + endpointId); + return DlStatus::kFailure; + } + return lockEndpoint->GetSchedule(holidayIndex, schedule); +} + +DlStatus LockManager::SetSchedule(chip::EndpointId endpointId, uint8_t holidayIndex, DlScheduleStatus status, + uint32_t localStartTime, uint32_t localEndTime, OperatingModeEnum operatingMode) +{ + auto lockEndpoint = getEndpoint(endpointId); + if (nullptr == lockEndpoint) + { + ChipLogError(Zcl, "Unable to set the holiday schedule - endpoint does not exist or not initialized [endpointId=%d]", + endpointId); + return DlStatus::kFailure; + } + return lockEndpoint->SetSchedule(holidayIndex, status, localStartTime, localEndTime, operatingMode); +} + +LockEndpoint * LockManager::getEndpoint(chip::EndpointId endpointId) +{ + for (auto & mEndpoint : mEndpoints) + { + if (mEndpoint.GetEndpointId() == endpointId) + { + return &mEndpoint; + } + } + return nullptr; +} +#endif // MATTER_DM_PLUGIN_DOOR_LOCK_SERVER diff --git a/examples/chef/common/clusters/door-lock/chef-lock-manager.h b/examples/chef/common/clusters/door-lock/chef-lock-manager.h new file mode 100644 index 00000000000000..709f13c1addc66 --- /dev/null +++ b/examples/chef/common/clusters/door-lock/chef-lock-manager.h @@ -0,0 +1,78 @@ +/* + * + * Copyright (c) 2020-2024 Project CHIP Authors + * All rights reserved. + * + * 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. + */ + +#pragma once + +#include "chef-lock-endpoint.h" +#include +#include + +#include + +class LockManager +{ +public: + LockManager() = default; + + bool InitEndpoint(chip::EndpointId endpointId); + + bool SetDoorState(chip::EndpointId endpointId, DoorStateEnum doorState); + + bool SendLockAlarm(chip::EndpointId endpointId, AlarmCodeEnum alarmCode); + + bool Lock(chip::EndpointId endpointId, const Nullable & fabricIdx, const Nullable & nodeId, + const Optional & pin, OperationErrorEnum & err, OperationSourceEnum opSource); + bool Unlock(chip::EndpointId endpointId, const Nullable & fabricIdx, const Nullable & nodeId, + const Optional & pin, OperationErrorEnum & err, OperationSourceEnum opSource); + bool Unbolt(chip::EndpointId endpointId, const Nullable & fabricIdx, const Nullable & nodeId, + const Optional & pin, OperationErrorEnum & err, OperationSourceEnum opSource); + + bool GetUser(chip::EndpointId endpointId, uint16_t userIndex, EmberAfPluginDoorLockUserInfo & user); + bool SetUser(chip::EndpointId endpointId, uint16_t userIndex, chip::FabricIndex creator, chip::FabricIndex modifier, + const chip::CharSpan & userName, uint32_t uniqueId, UserStatusEnum userStatus, UserTypeEnum usertype, + CredentialRuleEnum credentialRule, const CredentialStruct * credentials, size_t totalCredentials); + + bool GetCredential(chip::EndpointId endpointId, uint16_t credentialIndex, CredentialTypeEnum credentialType, + EmberAfPluginDoorLockCredentialInfo & credential); + + bool SetCredential(chip::EndpointId endpointId, uint16_t credentialIndex, chip::FabricIndex creator, chip::FabricIndex modifier, + DlCredentialStatus credentialStatus, CredentialTypeEnum credentialType, + const chip::ByteSpan & credentialData); + + DlStatus GetSchedule(chip::EndpointId endpointId, uint8_t weekDayIndex, uint16_t userIndex, + EmberAfPluginDoorLockWeekDaySchedule & schedule); + DlStatus GetSchedule(chip::EndpointId endpointId, uint8_t yearDayIndex, uint16_t userIndex, + EmberAfPluginDoorLockYearDaySchedule & schedule); + DlStatus GetSchedule(chip::EndpointId endpointId, uint8_t holidayIndex, EmberAfPluginDoorLockHolidaySchedule & schedule); + + DlStatus SetSchedule(chip::EndpointId endpointId, uint8_t weekDayIndex, uint16_t userIndex, DlScheduleStatus status, + DaysMaskMap daysMask, uint8_t startHour, uint8_t startMinute, uint8_t endHour, uint8_t endMinute); + DlStatus SetSchedule(chip::EndpointId endpointId, uint8_t yearDayIndex, uint16_t userIndex, DlScheduleStatus status, + uint32_t localStartTime, uint32_t localEndTime); + DlStatus SetSchedule(chip::EndpointId endpointId, uint8_t holidayIndex, DlScheduleStatus status, uint32_t localStartTime, + uint32_t localEndTime, OperatingModeEnum operatingMode); + + static LockManager & Instance(); + +private: + LockEndpoint * getEndpoint(chip::EndpointId endpointId); + + std::vector mEndpoints; + + static LockManager instance; +}; diff --git a/examples/chef/common/stubs.cpp b/examples/chef/common/stubs.cpp index 48c48620c98fb0..91a7b5a84d10e1 100644 --- a/examples/chef/common/stubs.cpp +++ b/examples/chef/common/stubs.cpp @@ -111,238 +111,6 @@ Protocols::InteractionModel::Status emberAfExternalAttributeWriteCallback(Endpoi return Protocols::InteractionModel::Status::Success; } -// Include door lock callbacks only when the server is enabled -#ifdef MATTER_DM_PLUGIN_DOOR_LOCK_SERVER -#include - -class LockManager -{ -public: - static constexpr uint32_t kNumEndpoints = 1; - static constexpr uint32_t kNumUsersPerEndpoint = 2; - static constexpr uint32_t kNumCredentialsPerEndpoint = 20; - static constexpr uint32_t kNumCredentialsPerUser = 10; - static constexpr uint32_t kMaxNameLength = 32; - static constexpr uint32_t kMaxDataLength = 16; - - struct Credential - { - bool set(DlCredentialStatus status, CredentialTypeEnum type, chip::ByteSpan newData) - { - if (newData.size() > kMaxDataLength || type != CredentialTypeEnum::kPin) - return false; - memcpy(data, newData.data(), newData.size()); - info = EmberAfPluginDoorLockCredentialInfo{ - status, - type, - chip::ByteSpan(data, newData.size()), - }; - return true; - } - - EmberAfPluginDoorLockCredentialInfo info = { DlCredentialStatus::kAvailable }; - uint8_t data[kMaxDataLength]; - }; - - struct User - { - void set(chip::CharSpan newName, uint32_t userId, UserStatusEnum userStatus, UserTypeEnum type, - CredentialRuleEnum credentialRule) - { - size_t sz = std::min(sizeof(name), newName.size()); - memcpy(name, newName.data(), sz); - info = EmberAfPluginDoorLockUserInfo{ - chip::CharSpan(name, sz), chip::Span(), userId, userStatus, type, credentialRule, - }; - } - bool addCredential(CredentialTypeEnum type, uint16_t index) - { - if (info.credentials.size() == kNumCredentialsPerUser) - return false; - auto & cr = credentialMap[info.credentials.size()]; - cr.credentialType = type; - cr.credentialIndex = index; - info.credentials = chip::Span(credentialMap, info.credentials.size() + 1); - return true; - } - - EmberAfPluginDoorLockUserInfo info = { .userStatus = UserStatusEnum::kAvailable }; - char name[kMaxNameLength]; - CredentialStruct credentialMap[kNumCredentialsPerUser]; - }; - - struct Endpoint - { - chip::EndpointId id; - User users[kNumUsersPerEndpoint]; - Credential credentials[kNumCredentialsPerEndpoint]; - }; - - static LockManager & Instance() - { - static LockManager instance; - return instance; - } - - LockManager() { defaultInitialize(); } - - bool getUser(chip::EndpointId endpointId, uint16_t userIndex, EmberAfPluginDoorLockUserInfo & user) - { - auto ep = findEndpoint(endpointId); - if (!ep) - return false; - if (userIndex >= kNumUsersPerEndpoint) - return false; - user = ep->users[userIndex].info; - return true; - } - - bool setUser(chip::EndpointId endpointId, uint16_t userIndex, chip::FabricIndex creator, chip::FabricIndex modifier, - const chip::CharSpan & userName, uint32_t uniqueId, UserStatusEnum userStatus, UserTypeEnum usertype, - CredentialRuleEnum credentialRule, const CredentialStruct * credentials, size_t totalCredentials) - { - auto ep = findEndpoint(endpointId); - if (!ep) - return false; - if (userIndex >= kNumUsersPerEndpoint || totalCredentials > kNumCredentialsPerUser) - return false; - ep->users[userIndex].set(userName, uniqueId, userStatus, usertype, credentialRule); - ep->users[userIndex].info.creationSource = DlAssetSource::kMatterIM; - ep->users[userIndex].info.createdBy = creator; - ep->users[userIndex].info.modificationSource = DlAssetSource::kMatterIM; - ep->users[userIndex].info.lastModifiedBy = modifier; - for (size_t i = 0; i < totalCredentials; i++) - ep->users[userIndex].addCredential(credentials[i].credentialType, credentials[i].credentialIndex); - return true; - } - - bool getCredential(chip::EndpointId endpointId, uint16_t credentialIndex, CredentialTypeEnum credentialType, - EmberAfPluginDoorLockCredentialInfo & credential) - { - auto ep = findEndpoint(endpointId); - if (!ep) - return false; - if (credentialIndex >= kNumCredentialsPerEndpoint) - return false; - if (credentialType != CredentialTypeEnum::kPin) - return false; - credential = ep->credentials[credentialIndex].info; - return true; - } - - bool setCredential(chip::EndpointId endpointId, uint16_t credentialIndex, chip::FabricIndex creator, chip::FabricIndex modifier, - DlCredentialStatus credentialStatus, CredentialTypeEnum credentialType, - const chip::ByteSpan & credentialData) - { - auto ep = findEndpoint(endpointId); - if (!ep) - return false; - if (credentialIndex >= kNumCredentialsPerEndpoint) - return false; - if (credentialType != CredentialTypeEnum::kPin) - return false; - auto & credential = ep->credentials[credentialIndex]; - if (!credential.set(credentialStatus, credentialType, credentialData)) - return false; - credential.info.creationSource = DlAssetSource::kMatterIM; - credential.info.createdBy = creator; - credential.info.modificationSource = DlAssetSource::kMatterIM; - credential.info.lastModifiedBy = modifier; - return true; - } - - bool checkPin(chip::EndpointId endpointId, const chip::Optional & pinCode, - chip::app::Clusters::DoorLock::OperationErrorEnum & err) - { - if (!pinCode.HasValue()) - { - err = OperationErrorEnum::kInvalidCredential; - return false; - } - auto ep = findEndpoint(endpointId); - if (!ep) - return false; - for (auto & pin : ep->credentials) - { - if (pin.info.status == DlCredentialStatus::kOccupied && pin.info.credentialData.data_equal(pinCode.Value())) - { - return true; - } - } - err = OperationErrorEnum::kInvalidCredential; - return false; - } - -private: - Endpoint * findEndpoint(chip::EndpointId endpointId) - { - for (auto & e : endpoints) - { - if (e.id == endpointId) - return &e; - } - return nullptr; - } - - void defaultInitialize() - { - endpoints[0].id = 1; - uint8_t pin[6] = { 0x31, 0x32, 0x33, 0x34, 0x35, 0x36 }; - endpoints[0].credentials[0].set(DlCredentialStatus::kOccupied, CredentialTypeEnum::kPin, chip::ByteSpan(pin)); - endpoints[0].users[0].set("default"_span, 1, UserStatusEnum::kOccupiedEnabled, UserTypeEnum::kUnrestrictedUser, - CredentialRuleEnum::kSingle); - endpoints[0].users[0].addCredential(CredentialTypeEnum::kPin, 1); - } - - Endpoint endpoints[kNumEndpoints]; -}; - -bool emberAfPluginDoorLockOnDoorLockCommand(chip::EndpointId endpointId, const Nullable & fabricIdx, - const Nullable & nodeId, const chip::Optional & pinCode, - chip::app::Clusters::DoorLock::OperationErrorEnum & err) -{ - err = OperationErrorEnum::kUnspecified; - return DoorLockServer::Instance().SetLockState(endpointId, DlLockState::kLocked); -} - -bool emberAfPluginDoorLockOnDoorUnlockCommand(chip::EndpointId endpointId, const Nullable & fabricIdx, - const Nullable & nodeId, const chip::Optional & pinCode, - chip::app::Clusters::DoorLock::OperationErrorEnum & err) -{ - err = OperationErrorEnum::kUnspecified; - return DoorLockServer::Instance().SetLockState(endpointId, DlLockState::kUnlocked); -} - -bool emberAfPluginDoorLockGetUser(chip::EndpointId endpointId, uint16_t userIndex, EmberAfPluginDoorLockUserInfo & user) -{ - return LockManager::Instance().getUser(endpointId, userIndex - 1, user); -} - -bool emberAfPluginDoorLockSetUser(chip::EndpointId endpointId, uint16_t userIndex, chip::FabricIndex creator, - chip::FabricIndex modifier, const chip::CharSpan & userName, uint32_t uniqueId, - UserStatusEnum userStatus, UserTypeEnum usertype, CredentialRuleEnum credentialRule, - const CredentialStruct * credentials, size_t totalCredentials) -{ - return LockManager::Instance().setUser(endpointId, userIndex - 1, creator, modifier, userName, uniqueId, userStatus, usertype, - credentialRule, credentials, totalCredentials); -} - -bool emberAfPluginDoorLockGetCredential(chip::EndpointId endpointId, uint16_t credentialIndex, CredentialTypeEnum credentialType, - EmberAfPluginDoorLockCredentialInfo & credential) -{ - return LockManager::Instance().getCredential(endpointId, credentialIndex - 1, credentialType, credential); -} - -bool emberAfPluginDoorLockSetCredential(chip::EndpointId endpointId, uint16_t credentialIndex, chip::FabricIndex creator, - chip::FabricIndex modifier, DlCredentialStatus credentialStatus, - CredentialTypeEnum credentialType, const chip::ByteSpan & credentialData) -{ - return LockManager::Instance().setCredential(endpointId, credentialIndex - 1, creator, modifier, credentialStatus, - credentialType, credentialData); -} - -#endif /* MATTER_DM_PLUGIN_DOOR_LOCK_SERVER */ - void emberAfPluginSmokeCoAlarmSelfTestRequestCommand(EndpointId endpointId) {} void MatterPostAttributeChangeCallback(const chip::app::ConcreteAttributePath & attributePath, uint8_t type, uint16_t size, diff --git a/examples/chef/devices/rootnode_doorlock_aNKYAreMXE.matter b/examples/chef/devices/rootnode_doorlock_aNKYAreMXE.matter index 5819641c778086..bceebc611c7ca3 100644 --- a/examples/chef/devices/rootnode_doorlock_aNKYAreMXE.matter +++ b/examples/chef/devices/rootnode_doorlock_aNKYAreMXE.matter @@ -51,83 +51,6 @@ cluster Identify = 3 { command access(invoke: manage) TriggerEffect(TriggerEffectRequest): DefaultSuccess = 64; } -/** Attributes and commands for group configuration and manipulation. */ -cluster Groups = 4 { - revision 4; - - bitmap Feature : bitmap32 { - kGroupNames = 0x1; - } - - bitmap NameSupportBitmap : bitmap8 { - kGroupNames = 0x80; - } - - readonly attribute NameSupportBitmap nameSupport = 0; - readonly attribute command_id generatedCommandList[] = 65528; - readonly attribute command_id acceptedCommandList[] = 65529; - readonly attribute event_id eventList[] = 65530; - readonly attribute attrib_id attributeList[] = 65531; - readonly attribute bitmap32 featureMap = 65532; - readonly attribute int16u clusterRevision = 65533; - - request struct AddGroupRequest { - group_id groupID = 0; - char_string<16> groupName = 1; - } - - response struct AddGroupResponse = 0 { - enum8 status = 0; - group_id groupID = 1; - } - - request struct ViewGroupRequest { - group_id groupID = 0; - } - - response struct ViewGroupResponse = 1 { - enum8 status = 0; - group_id groupID = 1; - char_string<16> groupName = 2; - } - - request struct GetGroupMembershipRequest { - group_id groupList[] = 0; - } - - response struct GetGroupMembershipResponse = 2 { - nullable int8u capacity = 0; - group_id groupList[] = 1; - } - - request struct RemoveGroupRequest { - group_id groupID = 0; - } - - response struct RemoveGroupResponse = 3 { - enum8 status = 0; - group_id groupID = 1; - } - - request struct AddGroupIfIdentifyingRequest { - group_id groupID = 0; - char_string<16> groupName = 1; - } - - /** Command description for AddGroup */ - fabric command access(invoke: manage) AddGroup(AddGroupRequest): AddGroupResponse = 0; - /** Command description for ViewGroup */ - fabric command ViewGroup(ViewGroupRequest): ViewGroupResponse = 1; - /** Command description for GetGroupMembership */ - fabric command GetGroupMembership(GetGroupMembershipRequest): GetGroupMembershipResponse = 2; - /** Command description for RemoveGroup */ - fabric command access(invoke: manage) RemoveGroup(RemoveGroupRequest): RemoveGroupResponse = 3; - /** Command description for RemoveAllGroups */ - fabric command access(invoke: manage) RemoveAllGroups(): DefaultSuccess = 4; - /** Command description for AddGroupIfIdentifying */ - fabric command access(invoke: manage) AddGroupIfIdentifying(AddGroupIfIdentifyingRequest): DefaultSuccess = 5; -} - /** The Descriptor Cluster is meant to replace the support from the Zigbee Device Object (ZDO) for describing a node, its endpoints and clusters. */ cluster Descriptor = 29 { revision 2; @@ -161,27 +84,6 @@ cluster Descriptor = 29 { readonly attribute int16u clusterRevision = 65533; } -/** The Binding Cluster is meant to replace the support from the Zigbee Device Object (ZDO) for supporting the binding table. */ -cluster Binding = 30 { - revision 1; // NOTE: Default/not specifically set - - fabric_scoped struct TargetStruct { - optional node_id node = 1; - optional group_id group = 2; - optional endpoint_no endpoint = 3; - optional cluster_id cluster = 4; - fabric_idx fabricIndex = 254; - } - - attribute access(write: manage) TargetStruct binding[] = 0; - readonly attribute command_id generatedCommandList[] = 65528; - readonly attribute command_id acceptedCommandList[] = 65529; - readonly attribute event_id eventList[] = 65530; - readonly attribute attrib_id attributeList[] = 65531; - readonly attribute bitmap32 featureMap = 65532; - readonly attribute int16u clusterRevision = 65533; -} - /** The Access Control Cluster exposes a data model view of a Node's Access Control List (ACL), which codifies the rules used to manage and enforce Access Control for the Node's endpoints and their associated @@ -569,6 +471,265 @@ cluster TimeFormatLocalization = 44 { readonly attribute int16u clusterRevision = 65533; } +/** This cluster is used to describe the configuration and capabilities of a physical power source that provides power to the Node. */ +cluster PowerSource = 47 { + revision 1; // NOTE: Default/not specifically set + + enum BatApprovedChemistryEnum : enum16 { + kUnspecified = 0; + kAlkaline = 1; + kLithiumCarbonFluoride = 2; + kLithiumChromiumOxide = 3; + kLithiumCopperOxide = 4; + kLithiumIronDisulfide = 5; + kLithiumManganeseDioxide = 6; + kLithiumThionylChloride = 7; + kMagnesium = 8; + kMercuryOxide = 9; + kNickelOxyhydride = 10; + kSilverOxide = 11; + kZincAir = 12; + kZincCarbon = 13; + kZincChloride = 14; + kZincManganeseDioxide = 15; + kLeadAcid = 16; + kLithiumCobaltOxide = 17; + kLithiumIon = 18; + kLithiumIonPolymer = 19; + kLithiumIronPhosphate = 20; + kLithiumSulfur = 21; + kLithiumTitanate = 22; + kNickelCadmium = 23; + kNickelHydrogen = 24; + kNickelIron = 25; + kNickelMetalHydride = 26; + kNickelZinc = 27; + kSilverZinc = 28; + kSodiumIon = 29; + kSodiumSulfur = 30; + kZincBromide = 31; + kZincCerium = 32; + } + + enum BatChargeFaultEnum : enum8 { + kUnspecified = 0; + kAmbientTooHot = 1; + kAmbientTooCold = 2; + kBatteryTooHot = 3; + kBatteryTooCold = 4; + kBatteryAbsent = 5; + kBatteryOverVoltage = 6; + kBatteryUnderVoltage = 7; + kChargerOverVoltage = 8; + kChargerUnderVoltage = 9; + kSafetyTimeout = 10; + } + + enum BatChargeLevelEnum : enum8 { + kOK = 0; + kWarning = 1; + kCritical = 2; + } + + enum BatChargeStateEnum : enum8 { + kUnknown = 0; + kIsCharging = 1; + kIsAtFullCharge = 2; + kIsNotCharging = 3; + } + + enum BatCommonDesignationEnum : enum16 { + kUnspecified = 0; + kAAA = 1; + kAA = 2; + kC = 3; + kD = 4; + k4v5 = 5; + k6v0 = 6; + k9v0 = 7; + k12AA = 8; + kAAAA = 9; + kA = 10; + kB = 11; + kF = 12; + kN = 13; + kNo6 = 14; + kSubC = 15; + kA23 = 16; + kA27 = 17; + kBA5800 = 18; + kDuplex = 19; + k4SR44 = 20; + k523 = 21; + k531 = 22; + k15v0 = 23; + k22v5 = 24; + k30v0 = 25; + k45v0 = 26; + k67v5 = 27; + kJ = 28; + kCR123A = 29; + kCR2 = 30; + k2CR5 = 31; + kCRP2 = 32; + kCRV3 = 33; + kSR41 = 34; + kSR43 = 35; + kSR44 = 36; + kSR45 = 37; + kSR48 = 38; + kSR54 = 39; + kSR55 = 40; + kSR57 = 41; + kSR58 = 42; + kSR59 = 43; + kSR60 = 44; + kSR63 = 45; + kSR64 = 46; + kSR65 = 47; + kSR66 = 48; + kSR67 = 49; + kSR68 = 50; + kSR69 = 51; + kSR516 = 52; + kSR731 = 53; + kSR712 = 54; + kLR932 = 55; + kA5 = 56; + kA10 = 57; + kA13 = 58; + kA312 = 59; + kA675 = 60; + kAC41E = 61; + k10180 = 62; + k10280 = 63; + k10440 = 64; + k14250 = 65; + k14430 = 66; + k14500 = 67; + k14650 = 68; + k15270 = 69; + k16340 = 70; + kRCR123A = 71; + k17500 = 72; + k17670 = 73; + k18350 = 74; + k18500 = 75; + k18650 = 76; + k19670 = 77; + k25500 = 78; + k26650 = 79; + k32600 = 80; + } + + enum BatFaultEnum : enum8 { + kUnspecified = 0; + kOverTemp = 1; + kUnderTemp = 2; + } + + enum BatReplaceabilityEnum : enum8 { + kUnspecified = 0; + kNotReplaceable = 1; + kUserReplaceable = 2; + kFactoryReplaceable = 3; + } + + enum PowerSourceStatusEnum : enum8 { + kUnspecified = 0; + kActive = 1; + kStandby = 2; + kUnavailable = 3; + } + + enum WiredCurrentTypeEnum : enum8 { + kAC = 0; + kDC = 1; + } + + enum WiredFaultEnum : enum8 { + kUnspecified = 0; + kOverVoltage = 1; + kUnderVoltage = 2; + } + + bitmap Feature : bitmap32 { + kWired = 0x1; + kBattery = 0x2; + kRechargeable = 0x4; + kReplaceable = 0x8; + } + + struct BatChargeFaultChangeType { + BatChargeFaultEnum current[] = 0; + BatChargeFaultEnum previous[] = 1; + } + + struct BatFaultChangeType { + BatFaultEnum current[] = 0; + BatFaultEnum previous[] = 1; + } + + struct WiredFaultChangeType { + WiredFaultEnum current[] = 0; + WiredFaultEnum previous[] = 1; + } + + info event WiredFaultChange = 0 { + WiredFaultEnum current[] = 0; + WiredFaultEnum previous[] = 1; + } + + info event BatFaultChange = 1 { + BatFaultEnum current[] = 0; + BatFaultEnum previous[] = 1; + } + + info event BatChargeFaultChange = 2 { + BatChargeFaultEnum current[] = 0; + BatChargeFaultEnum previous[] = 1; + } + + readonly attribute PowerSourceStatusEnum status = 0; + readonly attribute int8u order = 1; + readonly attribute char_string<60> description = 2; + readonly attribute optional nullable int32u wiredAssessedInputVoltage = 3; + readonly attribute optional nullable int16u wiredAssessedInputFrequency = 4; + readonly attribute optional WiredCurrentTypeEnum wiredCurrentType = 5; + readonly attribute optional nullable int32u wiredAssessedCurrent = 6; + readonly attribute optional int32u wiredNominalVoltage = 7; + readonly attribute optional int32u wiredMaximumCurrent = 8; + readonly attribute optional boolean wiredPresent = 9; + readonly attribute optional WiredFaultEnum activeWiredFaults[] = 10; + readonly attribute optional nullable int32u batVoltage = 11; + readonly attribute optional nullable int8u batPercentRemaining = 12; + readonly attribute optional nullable int32u batTimeRemaining = 13; + readonly attribute optional BatChargeLevelEnum batChargeLevel = 14; + readonly attribute optional boolean batReplacementNeeded = 15; + readonly attribute optional BatReplaceabilityEnum batReplaceability = 16; + readonly attribute optional boolean batPresent = 17; + readonly attribute optional BatFaultEnum activeBatFaults[] = 18; + readonly attribute optional char_string<60> batReplacementDescription = 19; + readonly attribute optional BatCommonDesignationEnum batCommonDesignation = 20; + readonly attribute optional char_string<20> batANSIDesignation = 21; + readonly attribute optional char_string<20> batIECDesignation = 22; + readonly attribute optional BatApprovedChemistryEnum batApprovedChemistry = 23; + readonly attribute optional int32u batCapacity = 24; + readonly attribute optional int8u batQuantity = 25; + readonly attribute optional BatChargeStateEnum batChargeState = 26; + readonly attribute optional nullable int32u batTimeToFullCharge = 27; + readonly attribute optional boolean batFunctionalWhileCharging = 28; + readonly attribute optional nullable int32u batChargingCurrent = 29; + readonly attribute optional BatChargeFaultEnum activeBatChargeFaults[] = 30; + readonly attribute endpoint_no endpointList[] = 31; + readonly attribute command_id generatedCommandList[] = 65528; + readonly attribute command_id acceptedCommandList[] = 65529; + readonly attribute event_id eventList[] = 65530; + readonly attribute attrib_id attributeList[] = 65531; + readonly attribute bitmap32 featureMap = 65532; + readonly attribute int16u clusterRevision = 65533; +} + /** This cluster is used to manage global aspects of the Commissioning flow. */ cluster GeneralCommissioning = 48 { revision 1; // NOTE: Default/not specifically set @@ -2178,40 +2339,22 @@ endpoint 0 { } } endpoint 1 { + device type ma_powersource = 17, version 1; device type ma_doorlock = 10, version 1; - binding cluster Binding; server cluster Identify { ram attribute identifyTime default = 0x0; ram attribute identifyType default = 0x0; callback attribute generatedCommandList; callback attribute acceptedCommandList; + callback attribute eventList; callback attribute attributeList; ram attribute featureMap default = 0; ram attribute clusterRevision default = 2; handle command Identify; - } - - server cluster Groups { - ram attribute nameSupport default = 0; - callback attribute generatedCommandList; - callback attribute acceptedCommandList; - callback attribute attributeList; - ram attribute featureMap default = 0; - ram attribute clusterRevision default = 3; - - handle command AddGroup; - handle command AddGroupResponse; - handle command ViewGroup; - handle command ViewGroupResponse; - handle command GetGroupMembership; - handle command GetGroupMembershipResponse; - handle command RemoveGroup; - handle command RemoveGroupResponse; - handle command RemoveAllGroups; - handle command AddGroupIfIdentifying; + handle command TriggerEffect; } server cluster Descriptor { @@ -2221,39 +2364,90 @@ endpoint 1 { callback attribute partsList; callback attribute generatedCommandList; callback attribute acceptedCommandList; + callback attribute eventList; callback attribute attributeList; callback attribute featureMap; callback attribute clusterRevision; } + server cluster PowerSource { + ram attribute status default = 1; + ram attribute order default = 1; + ram attribute description default = "Battery"; + ram attribute batVoltage; + ram attribute batPercentRemaining; + ram attribute batTimeRemaining; + ram attribute batChargeLevel; + ram attribute batReplacementNeeded; + ram attribute batReplaceability; + ram attribute batPresent; + ram attribute batReplacementDescription; + ram attribute batQuantity default = 1; + callback attribute endpointList; + callback attribute generatedCommandList; + callback attribute acceptedCommandList; + callback attribute eventList; + callback attribute attributeList; + ram attribute featureMap default = 0x0A; + ram attribute clusterRevision default = 2; + } + server cluster DoorLock { emits event DoorLockAlarm; + emits event DoorStateChange; emits event LockOperation; emits event LockOperationError; + emits event LockUserChange; ram attribute lockState default = 1; ram attribute lockType default = 0; ram attribute actuatorEnabled default = 0; - ram attribute numberOfTotalUsersSupported default = 2; - ram attribute numberOfPINUsersSupported default = 2; - ram attribute maxPINCodeLength default = 10; - ram attribute minPINCodeLength default = 5; + ram attribute doorState default = 1; + ram attribute doorOpenEvents; + ram attribute doorClosedEvents; + ram attribute numberOfTotalUsersSupported default = 10; + ram attribute numberOfPINUsersSupported default = 10; + ram attribute numberOfRFIDUsersSupported default = 10; + ram attribute numberOfWeekDaySchedulesSupportedPerUser default = 10; + ram attribute numberOfYearDaySchedulesSupportedPerUser default = 10; + ram attribute numberOfHolidaySchedulesSupported default = 10; + ram attribute maxPINCodeLength default = 8; + ram attribute minPINCodeLength default = 6; + ram attribute maxRFIDCodeLength default = 20; + ram attribute minRFIDCodeLength default = 10; + ram attribute credentialRulesSupport default = 1; ram attribute numberOfCredentialsSupportedPerUser default = 5; - ram attribute autoRelockTime default = 0; + ram attribute language default = "en"; + ram attribute autoRelockTime default = 5; + ram attribute soundVolume default = 0; ram attribute operatingMode default = 0; - ram attribute supportedOperatingModes default = 0xFFF6; + ram attribute supportedOperatingModes default = 0xFFFF; + ram attribute enableOneTouchLocking default = 0; + ram attribute enablePrivacyModeButton default = 0; ram attribute wrongCodeEntryLimit default = 3; ram attribute userCodeTemporaryDisableTime default = 10; - ram attribute sendPINOverTheAir default = 0; - ram attribute requirePINforRemoteOperation default = 1; + ram attribute requirePINforRemoteOperation default = 0; callback attribute generatedCommandList; callback attribute acceptedCommandList; + callback attribute eventList; callback attribute attributeList; - ram attribute featureMap default = 0x0181; - ram attribute clusterRevision default = 6; + ram attribute featureMap default = 0x1DB3; + ram attribute clusterRevision default = 7; handle command LockDoor; handle command UnlockDoor; handle command UnlockWithTimeout; + handle command SetWeekDaySchedule; + handle command GetWeekDaySchedule; + handle command GetWeekDayScheduleResponse; + handle command ClearWeekDaySchedule; + handle command SetYearDaySchedule; + handle command GetYearDaySchedule; + handle command GetYearDayScheduleResponse; + handle command ClearYearDaySchedule; + handle command SetHolidaySchedule; + handle command GetHolidaySchedule; + handle command GetHolidayScheduleResponse; + handle command ClearHolidaySchedule; handle command SetUser; handle command GetUser; handle command GetUserResponse; @@ -2263,6 +2457,7 @@ endpoint 1 { handle command GetCredentialStatus; handle command GetCredentialStatusResponse; handle command ClearCredential; + handle command UnboltDoor; } } diff --git a/examples/chef/devices/rootnode_doorlock_aNKYAreMXE.zap b/examples/chef/devices/rootnode_doorlock_aNKYAreMXE.zap index 66f7adbb12fd02..1e9094f9d7a96e 100644 --- a/examples/chef/devices/rootnode_doorlock_aNKYAreMXE.zap +++ b/examples/chef/devices/rootnode_doorlock_aNKYAreMXE.zap @@ -2480,13 +2480,21 @@ "profileId": 259, "label": "MA-doorlock", "name": "MA-doorlock" + }, + { + "code": 17, + "profileId": 259, + "label": "MA-powersource", + "name": "MA-powersource" } ], "deviceVersions": [ + 1, 1 ], "deviceIdentifiers": [ - 10 + 10, + 17 ], "deviceTypeName": "MA-doorlock", "deviceTypeCode": 10, @@ -2507,6 +2515,14 @@ "source": "client", "isIncoming": 1, "isEnabled": 1 + }, + { + "name": "TriggerEffect", + "code": 64, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 } ], "attributes": [ @@ -2574,6 +2590,22 @@ "maxInterval": 65534, "reportableChange": 0 }, + { + "name": "EventList", + "code": 65530, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, { "name": "AttributeList", "code": 65531, @@ -2625,106 +2657,72 @@ ] }, { - "name": "Groups", - "code": 4, + "name": "Descriptor", + "code": 29, "mfgCode": null, - "define": "GROUPS_CLUSTER", + "define": "DESCRIPTOR_CLUSTER", "side": "server", "enabled": 1, - "commands": [ - { - "name": "AddGroup", - "code": 0, - "mfgCode": null, - "source": "client", - "isIncoming": 1, - "isEnabled": 1 - }, + "attributes": [ { - "name": "AddGroupResponse", + "name": "DeviceTypeList", "code": 0, "mfgCode": null, - "source": "server", - "isIncoming": 0, - "isEnabled": 1 - }, - { - "name": "ViewGroup", - "code": 1, - "mfgCode": null, - "source": "client", - "isIncoming": 1, - "isEnabled": 1 + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 }, { - "name": "ViewGroupResponse", + "name": "ServerList", "code": 1, "mfgCode": null, - "source": "server", - "isIncoming": 0, - "isEnabled": 1 - }, - { - "name": "GetGroupMembership", - "code": 2, - "mfgCode": null, - "source": "client", - "isIncoming": 1, - "isEnabled": 1 + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 }, { - "name": "GetGroupMembershipResponse", + "name": "ClientList", "code": 2, "mfgCode": null, - "source": "server", - "isIncoming": 0, - "isEnabled": 1 - }, - { - "name": "RemoveGroup", - "code": 3, - "mfgCode": null, - "source": "client", - "isIncoming": 1, - "isEnabled": 1 + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 }, { - "name": "RemoveGroupResponse", + "name": "PartsList", "code": 3, "mfgCode": null, - "source": "server", - "isIncoming": 0, - "isEnabled": 1 - }, - { - "name": "RemoveAllGroups", - "code": 4, - "mfgCode": null, - "source": "client", - "isIncoming": 1, - "isEnabled": 1 - }, - { - "name": "AddGroupIfIdentifying", - "code": 5, - "mfgCode": null, - "source": "client", - "isIncoming": 1, - "isEnabled": 1 - } - ], - "attributes": [ - { - "name": "NameSupport", - "code": 0, - "mfgCode": null, "side": "server", - "type": "NameSupportBitmap", + "type": "array", "included": 1, - "storageOption": "RAM", + "storageOption": "External", "singleton": 0, "bounded": 0, - "defaultValue": "0", + "defaultValue": null, "reportable": 1, "minInterval": 1, "maxInterval": 65534, @@ -2762,6 +2760,22 @@ "maxInterval": 65534, "reportableChange": 0 }, + { + "name": "EventList", + "code": 65530, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, { "name": "AttributeList", "code": 65531, @@ -2785,10 +2799,10 @@ "side": "server", "type": "bitmap32", "included": 1, - "storageOption": "RAM", + "storageOption": "External", "singleton": 0, "bounded": 0, - "defaultValue": "0", + "defaultValue": null, "reportable": 1, "minInterval": 1, "maxInterval": 65534, @@ -2801,10 +2815,10 @@ "side": "server", "type": "int16u", "included": 1, - "storageOption": "RAM", + "storageOption": "External", "singleton": 0, "bounded": 0, - "defaultValue": "3", + "defaultValue": null, "reportable": 1, "minInterval": 1, "maxInterval": 65534, @@ -2813,53 +2827,69 @@ ] }, { - "name": "Descriptor", - "code": 29, + "name": "Power Source", + "code": 47, "mfgCode": null, - "define": "DESCRIPTOR_CLUSTER", + "define": "POWER_SOURCE_CLUSTER", "side": "server", "enabled": 1, "attributes": [ { - "name": "DeviceTypeList", + "name": "Status", "code": 0, "mfgCode": null, "side": "server", - "type": "array", + "type": "PowerSourceStatusEnum", "included": 1, - "storageOption": "External", + "storageOption": "RAM", "singleton": 0, "bounded": 0, - "defaultValue": null, + "defaultValue": "1", "reportable": 1, "minInterval": 1, "maxInterval": 65534, "reportableChange": 0 }, { - "name": "ServerList", + "name": "Order", "code": 1, "mfgCode": null, "side": "server", - "type": "array", + "type": "int8u", "included": 1, - "storageOption": "External", + "storageOption": "RAM", "singleton": 0, "bounded": 0, - "defaultValue": null, + "defaultValue": "1", "reportable": 1, "minInterval": 1, "maxInterval": 65534, "reportableChange": 0 }, { - "name": "ClientList", + "name": "Description", "code": 2, "mfgCode": null, "side": "server", - "type": "array", + "type": "char_string", "included": 1, - "storageOption": "External", + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "Battery", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "BatVoltage", + "code": 11, + "mfgCode": null, + "side": "server", + "type": "int32u", + "included": 1, + "storageOption": "RAM", "singleton": 0, "bounded": 0, "defaultValue": null, @@ -2869,8 +2899,136 @@ "reportableChange": 0 }, { - "name": "PartsList", - "code": 3, + "name": "BatPercentRemaining", + "code": 12, + "mfgCode": null, + "side": "server", + "type": "int8u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "BatTimeRemaining", + "code": 13, + "mfgCode": null, + "side": "server", + "type": "int32u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "BatChargeLevel", + "code": 14, + "mfgCode": null, + "side": "server", + "type": "BatChargeLevelEnum", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "BatReplacementNeeded", + "code": 15, + "mfgCode": null, + "side": "server", + "type": "boolean", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "BatReplaceability", + "code": 16, + "mfgCode": null, + "side": "server", + "type": "BatReplaceabilityEnum", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "BatPresent", + "code": 17, + "mfgCode": null, + "side": "server", + "type": "boolean", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "BatReplacementDescription", + "code": 19, + "mfgCode": null, + "side": "server", + "type": "char_string", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "BatQuantity", + "code": 25, + "mfgCode": null, + "side": "server", + "type": "int8u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "1", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "EndpointList", + "code": 31, "mfgCode": null, "side": "server", "type": "array", @@ -2917,8 +3075,8 @@ "reportableChange": 0 }, { - "name": "AttributeList", - "code": 65531, + "name": "EventList", + "code": 65530, "mfgCode": null, "side": "server", "type": "array", @@ -2933,11 +3091,11 @@ "reportableChange": 0 }, { - "name": "FeatureMap", - "code": 65532, + "name": "AttributeList", + "code": 65531, "mfgCode": null, "side": "server", - "type": "bitmap32", + "type": "array", "included": 1, "storageOption": "External", "singleton": 0, @@ -2949,42 +3107,32 @@ "reportableChange": 0 }, { - "name": "ClusterRevision", - "code": 65533, + "name": "FeatureMap", + "code": 65532, "mfgCode": null, "side": "server", - "type": "int16u", + "type": "bitmap32", "included": 1, - "storageOption": "External", + "storageOption": "RAM", "singleton": 0, "bounded": 0, - "defaultValue": null, + "defaultValue": "0x0A", "reportable": 1, "minInterval": 1, "maxInterval": 65534, "reportableChange": 0 - } - ] - }, - { - "name": "Binding", - "code": 30, - "mfgCode": null, - "define": "BINDING_CLUSTER", - "side": "client", - "enabled": 1, - "attributes": [ + }, { "name": "ClusterRevision", "code": 65533, "mfgCode": null, - "side": "client", + "side": "server", "type": "int16u", "included": 1, "storageOption": "RAM", "singleton": 0, "bounded": 0, - "defaultValue": "1", + "defaultValue": "2", "reportable": 1, "minInterval": 1, "maxInterval": 65534, @@ -3009,16 +3157,112 @@ "isEnabled": 1 }, { - "name": "UnlockDoor", - "code": 1, + "name": "UnlockDoor", + "code": 1, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "UnlockWithTimeout", + "code": 3, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "SetWeekDaySchedule", + "code": 11, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "GetWeekDaySchedule", + "code": 12, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "GetWeekDayScheduleResponse", + "code": 12, + "mfgCode": null, + "source": "server", + "isIncoming": 0, + "isEnabled": 1 + }, + { + "name": "ClearWeekDaySchedule", + "code": 13, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "SetYearDaySchedule", + "code": 14, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "GetYearDaySchedule", + "code": 15, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "GetYearDayScheduleResponse", + "code": 15, + "mfgCode": null, + "source": "server", + "isIncoming": 0, + "isEnabled": 1 + }, + { + "name": "ClearYearDaySchedule", + "code": 16, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "SetHolidaySchedule", + "code": 17, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "GetHolidaySchedule", + "code": 18, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "GetHolidayScheduleResponse", + "code": 18, "mfgCode": null, - "source": "client", - "isIncoming": 1, + "source": "server", + "isIncoming": 0, "isEnabled": 1 }, { - "name": "UnlockWithTimeout", - "code": 3, + "name": "ClearHolidaySchedule", + "code": 19, "mfgCode": null, "source": "client", "isIncoming": 1, @@ -3095,6 +3339,14 @@ "source": "client", "isIncoming": 1, "isEnabled": 1 + }, + { + "name": "UnboltDoor", + "code": 39, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 } ], "attributes": [ @@ -3146,6 +3398,54 @@ "maxInterval": 65534, "reportableChange": 0 }, + { + "name": "DoorState", + "code": 3, + "mfgCode": null, + "side": "server", + "type": "DoorStateEnum", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "1", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "DoorOpenEvents", + "code": 4, + "mfgCode": null, + "side": "server", + "type": "int32u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "DoorClosedEvents", + "code": 5, + "mfgCode": null, + "side": "server", + "type": "int32u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, { "name": "NumberOfTotalUsersSupported", "code": 17, @@ -3156,7 +3456,7 @@ "storageOption": "RAM", "singleton": 0, "bounded": 0, - "defaultValue": "2", + "defaultValue": "10", "reportable": 1, "minInterval": 1, "maxInterval": 65534, @@ -3172,7 +3472,71 @@ "storageOption": "RAM", "singleton": 0, "bounded": 0, - "defaultValue": "2", + "defaultValue": "10", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "NumberOfRFIDUsersSupported", + "code": 19, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "10", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "NumberOfWeekDaySchedulesSupportedPerUser", + "code": 20, + "mfgCode": null, + "side": "server", + "type": "int8u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "10", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "NumberOfYearDaySchedulesSupportedPerUser", + "code": 21, + "mfgCode": null, + "side": "server", + "type": "int8u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "10", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "NumberOfHolidaySchedulesSupported", + "code": 22, + "mfgCode": null, + "side": "server", + "type": "int8u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "10", "reportable": 1, "minInterval": 1, "maxInterval": 65534, @@ -3188,7 +3552,7 @@ "storageOption": "RAM", "singleton": 0, "bounded": 0, - "defaultValue": "10", + "defaultValue": "8", "reportable": 1, "minInterval": 1, "maxInterval": 65534, @@ -3204,7 +3568,55 @@ "storageOption": "RAM", "singleton": 0, "bounded": 0, - "defaultValue": "5", + "defaultValue": "6", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "MaxRFIDCodeLength", + "code": 25, + "mfgCode": null, + "side": "server", + "type": "int8u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "20", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "MinRFIDCodeLength", + "code": 26, + "mfgCode": null, + "side": "server", + "type": "int8u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "10", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "CredentialRulesSupport", + "code": 27, + "mfgCode": null, + "side": "server", + "type": "DlCredentialRuleMask", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "1", "reportable": 1, "minInterval": 1, "maxInterval": 65534, @@ -3226,6 +3638,22 @@ "maxInterval": 65534, "reportableChange": 0 }, + { + "name": "Language", + "code": 33, + "mfgCode": null, + "side": "server", + "type": "char_string", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "en", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, { "name": "AutoRelockTime", "code": 35, @@ -3236,6 +3664,22 @@ "storageOption": "RAM", "singleton": 0, "bounded": 0, + "defaultValue": "5", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "SoundVolume", + "code": 36, + "mfgCode": null, + "side": "server", + "type": "int8u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, "defaultValue": "0", "reportable": 1, "minInterval": 1, @@ -3268,7 +3712,39 @@ "storageOption": "RAM", "singleton": 0, "bounded": 0, - "defaultValue": "0xFFF6", + "defaultValue": "0xFFFF", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "EnableOneTouchLocking", + "code": 41, + "mfgCode": null, + "side": "server", + "type": "boolean", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "0", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "EnablePrivacyModeButton", + "code": 43, + "mfgCode": null, + "side": "server", + "type": "boolean", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "0", "reportable": 1, "minInterval": 1, "maxInterval": 65534, @@ -3307,8 +3783,8 @@ "reportableChange": 0 }, { - "name": "SendPINOverTheAir", - "code": 50, + "name": "RequirePINforRemoteOperation", + "code": 51, "mfgCode": null, "side": "server", "type": "boolean", @@ -3323,24 +3799,24 @@ "reportableChange": 0 }, { - "name": "RequirePINforRemoteOperation", - "code": 51, + "name": "GeneratedCommandList", + "code": 65528, "mfgCode": null, "side": "server", - "type": "boolean", + "type": "array", "included": 1, - "storageOption": "RAM", + "storageOption": "External", "singleton": 0, "bounded": 0, - "defaultValue": "1", + "defaultValue": null, "reportable": 1, "minInterval": 1, "maxInterval": 65534, "reportableChange": 0 }, { - "name": "GeneratedCommandList", - "code": 65528, + "name": "AcceptedCommandList", + "code": 65529, "mfgCode": null, "side": "server", "type": "array", @@ -3355,8 +3831,8 @@ "reportableChange": 0 }, { - "name": "AcceptedCommandList", - "code": 65529, + "name": "EventList", + "code": 65530, "mfgCode": null, "side": "server", "type": "array", @@ -3396,7 +3872,7 @@ "storageOption": "RAM", "singleton": 0, "bounded": 0, - "defaultValue": "0x0181", + "defaultValue": "0x1DB3", "reportable": 1, "minInterval": 1, "maxInterval": 65534, @@ -3412,7 +3888,7 @@ "storageOption": "RAM", "singleton": 0, "bounded": 0, - "defaultValue": "6", + "defaultValue": "7", "reportable": 1, "minInterval": 1, "maxInterval": 65534, @@ -3427,6 +3903,13 @@ "side": "server", "included": 1 }, + { + "name": "DoorStateChange", + "code": 1, + "mfgCode": null, + "side": "server", + "included": 1 + }, { "name": "LockOperation", "code": 2, @@ -3440,6 +3923,13 @@ "mfgCode": null, "side": "server", "included": 1 + }, + { + "name": "LockUserChange", + "code": 4, + "mfgCode": null, + "side": "server", + "included": 1 } ] } @@ -3458,10 +3948,10 @@ { "endpointTypeName": "Anonymous Endpoint Type", "endpointTypeIndex": 1, - "profileId": 260, + "profileId": 259, "endpointId": 1, "networkId": 0, "parentEndpointIdentifier": null } ] -} \ No newline at end of file +} diff --git a/examples/chef/esp32/main/CMakeLists.txt b/examples/chef/esp32/main/CMakeLists.txt index 8278cf9a735675..75cf0b85222d95 100644 --- a/examples/chef/esp32/main/CMakeLists.txt +++ b/examples/chef/esp32/main/CMakeLists.txt @@ -63,14 +63,15 @@ set(SRC_DIRS_LIST ${SRC_DIRS_LIST} "${CMAKE_CURRENT_LIST_DIR}" "${CMAKE_SOURCE_DIR}/../common" - "${CMAKE_SOURCE_DIR}/../common/clusters/media-input/" + "${CMAKE_SOURCE_DIR}/../common/clusters/audio-output/" + "${CMAKE_SOURCE_DIR}/../common/clusters/channel/" + "${CMAKE_SOURCE_DIR}/../common/clusters/door-lock/" + "${CMAKE_SOURCE_DIR}/../common/clusters/keypad-input/" "${CMAKE_SOURCE_DIR}/../common/clusters/low-power/" + "${CMAKE_SOURCE_DIR}/../common/clusters/media-input/" "${CMAKE_SOURCE_DIR}/../common/clusters/media-playback/" "${CMAKE_SOURCE_DIR}/../common/clusters/target-navigator/" "${CMAKE_SOURCE_DIR}/../common/clusters/wake-on-lan/" - "${CMAKE_SOURCE_DIR}/../common/clusters/channel/" - "${CMAKE_SOURCE_DIR}/../common/clusters/keypad-input/" - "${CMAKE_SOURCE_DIR}/../common/clusters/audio-output/" "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/zzz_generated/app-common/app-common/zap-generated/attributes" "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/src/app/server" "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/src/app/util" diff --git a/examples/chef/linux/BUILD.gn b/examples/chef/linux/BUILD.gn index 02fa77dac864e5..3cf4bdc7dd5d98 100644 --- a/examples/chef/linux/BUILD.gn +++ b/examples/chef/linux/BUILD.gn @@ -43,11 +43,15 @@ executable("${sample_name}") { "${project_dir}/common/chef-air-quality.cpp", "${project_dir}/common/chef-concentration-measurement.cpp", "${project_dir}/common/chef-fan-control-manager.cpp", + "${project_dir}/common/chef-operational-state-delegate-impl.cpp", "${project_dir}/common/chef-resource-monitoring-delegates.cpp", "${project_dir}/common/chef-rvc-mode-delegate.cpp", "${project_dir}/common/chef-rvc-operational-state-delegate.cpp", "${project_dir}/common/clusters/audio-output/AudioOutputManager.cpp", "${project_dir}/common/clusters/channel/ChannelManager.cpp", + "${project_dir}/common/clusters/door-lock/chef-doorlock-stubs.cpp", + "${project_dir}/common/clusters/door-lock/chef-lock-endpoint.cpp", + "${project_dir}/common/clusters/door-lock/chef-lock-manager.cpp", "${project_dir}/common/clusters/keypad-input/KeypadInputManager.cpp", "${project_dir}/common/clusters/low-power/LowPowerManager.cpp", "${project_dir}/common/clusters/media-input/MediaInputManager.cpp", diff --git a/examples/chef/nrfconnect/CMakeLists.txt b/examples/chef/nrfconnect/CMakeLists.txt index 0a408e829dd04f..29b21817705afa 100644 --- a/examples/chef/nrfconnect/CMakeLists.txt +++ b/examples/chef/nrfconnect/CMakeLists.txt @@ -84,17 +84,21 @@ target_sources(app PRIVATE ${CHEF}/common/chef-air-quality.cpp ${CHEF}/common/chef-concentration-measurement.cpp ${CHEF}/common/chef-fan-control-manager.cpp + ${CHEF}/common/chef-operational-state-delegate-impl.cpp ${CHEF}/common/chef-resource-monitoring-delegates.cpp ${CHEF}/common/chef-rvc-mode-delegate.cpp ${CHEF}/common/chef-rvc-operational-state-delegate.cpp - ${CHEF}/common/clusters/media-input/MediaInputManager.cpp + ${CHEF}/common/clusters/audio-output/AudioOutputManager.cpp + ${CHEF}/common/clusters/channel/ChannelManager.cpp + ${CHEF}/common/clusters/door-lock/chef-doorlock-stubs.cpp + ${CHEF}/common/clusters/door-lock/chef-lock-endpoint.cpp + ${CHEF}/common/clusters/door-lock/chef-lock-manager.cpp + ${CHEF}/common/clusters/keypad-input/KeypadInputManager.cpp ${CHEF}/common/clusters/low-power/LowPowerManager.cpp + ${CHEF}/common/clusters/media-input/MediaInputManager.cpp ${CHEF}/common/clusters/media-playback/MediaPlaybackManager.cpp ${CHEF}/common/clusters/target-navigator/TargetNavigatorManager.cpp ${CHEF}/common/clusters/wake-on-lan/WakeOnLanManager.cpp - ${CHEF}/common/clusters/channel/ChannelManager.cpp - ${CHEF}/common/clusters/keypad-input/KeypadInputManager.cpp - ${CHEF}/common/clusters/audio-output/AudioOutputManager.cpp ${CHEF}/common/stubs.cpp ${CHEF}/nrfconnect/main.cpp ) diff --git a/examples/darwin-framework-tool/BUILD.gn b/examples/darwin-framework-tool/BUILD.gn index 6228643a958f81..665dccf4848df4 100644 --- a/examples/darwin-framework-tool/BUILD.gn +++ b/examples/darwin-framework-tool/BUILD.gn @@ -193,6 +193,11 @@ executable("darwin-framework-tool") { "commands/common/MTRLogging.h", "commands/common/RemoteDataModelLogger.h", "commands/common/RemoteDataModelLogger.mm", + "commands/configuration/Commands.h", + "commands/configuration/ResetMRPParametersCommand.h", + "commands/configuration/ResetMRPParametersCommand.mm", + "commands/configuration/SetMRPParametersCommand.h", + "commands/configuration/SetMRPParametersCommand.mm", "commands/delay/Commands.h", "commands/delay/SleepCommand.h", "commands/delay/SleepCommand.mm", diff --git a/examples/darwin-framework-tool/commands/common/CHIPCommandBridge.h b/examples/darwin-framework-tool/commands/common/CHIPCommandBridge.h index 842ded8f839ab7..7dc5b848e22324 100644 --- a/examples/darwin-framework-tool/commands/common/CHIPCommandBridge.h +++ b/examples/darwin-framework-tool/commands/common/CHIPCommandBridge.h @@ -34,8 +34,8 @@ inline constexpr char kIdentityGamma[] = "gamma"; class CHIPCommandBridge : public Command { public: - CHIPCommandBridge(const char * commandName) - : Command(commandName) + CHIPCommandBridge(const char * commandName, const char * helpText = nullptr) + : Command(commandName, helpText) { AddArgument("commissioner-name", &mCommissionerName); AddArgument("commissioner-nodeId", 0, UINT64_MAX, &mCommissionerNodeId, diff --git a/examples/darwin-framework-tool/commands/configuration/Commands.h b/examples/darwin-framework-tool/commands/configuration/Commands.h new file mode 100644 index 00000000000000..8ee148e63b8f8b --- /dev/null +++ b/examples/darwin-framework-tool/commands/configuration/Commands.h @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2024 Project CHIP Authors + * All rights reserved. + * + * 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. + * + */ + +#pragma once + +#import + +#include "ResetMRPParametersCommand.h" +#include "SetMRPParametersCommand.h" + +void registerCommandsConfiguration(Commands & commands) +{ + const char * clusterName = "Configuration"; + + commands_list clusterCommands = { + make_unique(), + make_unique(), + }; + + commands.RegisterCommandSet(clusterName, clusterCommands, "Commands for configuring various state of the Matter framework."); +} diff --git a/examples/darwin-framework-tool/commands/configuration/ResetMRPParametersCommand.h b/examples/darwin-framework-tool/commands/configuration/ResetMRPParametersCommand.h new file mode 100644 index 00000000000000..10cfe2f177c66d --- /dev/null +++ b/examples/darwin-framework-tool/commands/configuration/ResetMRPParametersCommand.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2024 Project CHIP Authors + * All rights reserved. + * + * 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. + */ + +#pragma once + +#import + +#include "../common/CHIPCommandBridge.h" + +class ResetMRPParametersCommand : public CHIPCommandBridge +{ +public: + ResetMRPParametersCommand() : CHIPCommandBridge("reset-mrp-parameters", "Reset MRP parameters to default values.") {} + +protected: + /////////// CHIPCommandBridge Interface ///////// + CHIP_ERROR RunCommand() override; + + // Our command is synchronous, so no need to wait. + chip::System::Clock::Timeout GetWaitDuration() const override { return chip::System::Clock::kZero; } +}; diff --git a/examples/darwin-framework-tool/commands/configuration/ResetMRPParametersCommand.mm b/examples/darwin-framework-tool/commands/configuration/ResetMRPParametersCommand.mm new file mode 100644 index 00000000000000..26b82e540ef716 --- /dev/null +++ b/examples/darwin-framework-tool/commands/configuration/ResetMRPParametersCommand.mm @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2024 Project CHIP Authors + * All rights reserved. + * + * 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. + */ + +#include "ResetMRPParametersCommand.h" + +CHIP_ERROR ResetMRPParametersCommand::RunCommand() +{ + MTRSetMessageReliabilityParameters(nil, nil, nil, nil); + return CHIP_NO_ERROR; +} diff --git a/examples/darwin-framework-tool/commands/configuration/SetMRPParametersCommand.h b/examples/darwin-framework-tool/commands/configuration/SetMRPParametersCommand.h new file mode 100644 index 00000000000000..e9dc2faeb06c6d --- /dev/null +++ b/examples/darwin-framework-tool/commands/configuration/SetMRPParametersCommand.h @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2024 Project CHIP Authors + * All rights reserved. + * + * 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. + */ + +#pragma once + +#import + +#include "../common/CHIPCommandBridge.h" + +class SetMRPParametersCommand : public CHIPCommandBridge +{ +public: + SetMRPParametersCommand() : + CHIPCommandBridge("set-mrp-parameters", "Set various MRP parameters. At least one value must be provided.") + { + AddArgument("idle-interval", 0, UINT32_MAX, &mIdleRetransmitMs, + "Our MRP idle interval (SII) in milliseconds. Defaults to current value if not set."); + AddArgument("active-interval", 0, UINT32_MAX, &mActiveRetransmitMs, + "Our MRP active interval (SAI) in milliseconds. Defaults to current value if not set."); + AddArgument("active-threshold", 0, UINT32_MAX, &mActiveThresholdMs, + "Our MRP active threshold: how long we stay in active mode before transitioning to idle mode. Defaults to " + "current value if not set."); + AddArgument("additional-retransmit-delay", 0, UINT32_MAX, &mAdditionalRetransmitDelayMs, + "Additional delay between retransmits that we do. Defaults to current value if not set."); + } + +protected: + /////////// CHIPCommandBridge Interface ///////// + CHIP_ERROR RunCommand() override; + + // Our command is synchronous, so no need to wait. + chip::System::Clock::Timeout GetWaitDuration() const override { return chip::System::Clock::kZero; } + + chip::Optional mIdleRetransmitMs; + chip::Optional mActiveRetransmitMs; + chip::Optional mActiveThresholdMs; + chip::Optional mAdditionalRetransmitDelayMs; +}; diff --git a/examples/darwin-framework-tool/commands/configuration/SetMRPParametersCommand.mm b/examples/darwin-framework-tool/commands/configuration/SetMRPParametersCommand.mm new file mode 100644 index 00000000000000..9ec49e7a87d942 --- /dev/null +++ b/examples/darwin-framework-tool/commands/configuration/SetMRPParametersCommand.mm @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2024 Project CHIP Authors + * All rights reserved. + * + * 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. + */ + +#include "SetMRPParametersCommand.h" + +using namespace chip; + +namespace { + +template +NSNumber * _Nullable AsNumber(const Optional & value) +{ + if (!value.HasValue()) { + return nil; + } + + return @(value.Value()); +} + +} // anonymous namespace + +CHIP_ERROR SetMRPParametersCommand::RunCommand() +{ + if (!mIdleRetransmitMs.HasValue() && !mActiveRetransmitMs.HasValue() && !mActiveThresholdMs.HasValue() && !mAdditionalRetransmitDelayMs.HasValue()) { + ChipLogError(chipTool, "set-mrp-parameters needs to have at least one argument provided"); + return CHIP_ERROR_INVALID_ARGUMENT; + } + + MTRSetMessageReliabilityParameters(AsNumber(mIdleRetransmitMs), + AsNumber(mActiveRetransmitMs), + AsNumber(mActiveThresholdMs), + AsNumber(mAdditionalRetransmitDelayMs)); + return CHIP_NO_ERROR; +} diff --git a/examples/darwin-framework-tool/main.mm b/examples/darwin-framework-tool/main.mm index 0ba2d1553e9c88..5f31cb6abf1cd1 100644 --- a/examples/darwin-framework-tool/main.mm +++ b/examples/darwin-framework-tool/main.mm @@ -22,6 +22,7 @@ #include "commands/bdx/Commands.h" #include "commands/common/Commands.h" +#include "commands/configuration/Commands.h" #include "commands/delay/Commands.h" #include "commands/discover/Commands.h" #include "commands/interactive/Commands.h" @@ -46,6 +47,7 @@ int main(int argc, const char * argv[]) registerCommandsPayload(commands); registerClusterOtaSoftwareUpdateProviderInteractive(commands); registerCommandsStorage(commands); + registerCommandsConfiguration(commands); registerClusters(commands); return commands.Run(argc, (char **) argv); } diff --git a/examples/light-switch-app/light-switch-common/light-switch-app.matter b/examples/light-switch-app/light-switch-common/light-switch-app.matter index 65bef372495d85..e51a772add14e2 100644 --- a/examples/light-switch-app/light-switch-common/light-switch-app.matter +++ b/examples/light-switch-app/light-switch-common/light-switch-app.matter @@ -2628,6 +2628,7 @@ endpoint 0 { ram attribute clusterRevision default = 1; handle command RetrieveLogsRequest; + handle command RetrieveLogsResponse; } server cluster GeneralDiagnostics { diff --git a/examples/light-switch-app/light-switch-common/light-switch-app.zap b/examples/light-switch-app/light-switch-common/light-switch-app.zap index 2c61c123c83437..ba56db35a2669b 100644 --- a/examples/light-switch-app/light-switch-common/light-switch-app.zap +++ b/examples/light-switch-app/light-switch-common/light-switch-app.zap @@ -1581,6 +1581,14 @@ "source": "client", "isIncoming": 1, "isEnabled": 1 + }, + { + "name": "RetrieveLogsResponse", + "code": 1, + "mfgCode": null, + "source": "server", + "isIncoming": 0, + "isEnabled": 1 } ], "attributes": [ @@ -4605,6 +4613,24 @@ "define": "IDENTIFY_CLUSTER", "side": "client", "enabled": 1, + "commands": [ + { + "name": "Identify", + "code": 0, + "mfgCode": null, + "source": "client", + "isIncoming": 0, + "isEnabled": 1 + }, + { + "name": "TriggerEffect", + "code": 64, + "mfgCode": null, + "source": "client", + "isIncoming": 0, + "isEnabled": 1 + } + ], "attributes": [ { "name": "ClusterRevision", diff --git a/examples/lighting-app/silabs/src/AppTask.cpp b/examples/lighting-app/silabs/src/AppTask.cpp index bce7b06cb5cb07..7430bd2828d233 100644 --- a/examples/lighting-app/silabs/src/AppTask.cpp +++ b/examples/lighting-app/silabs/src/AppTask.cpp @@ -40,7 +40,7 @@ #include -#if (defined(SL_CATALOG_SIMPLE_LED_LED1_PRESENT) || defined(SIWX_917)) +#ifdef SL_CATALOG_SIMPLE_LED_LED1_PRESENT #define LIGHT_LED 1 #else #define LIGHT_LED 0 diff --git a/examples/lock-app/lock-common/lock-app.matter b/examples/lock-app/lock-common/lock-app.matter index b886f3e52d067a..8fee37c9493c67 100644 --- a/examples/lock-app/lock-common/lock-app.matter +++ b/examples/lock-app/lock-common/lock-app.matter @@ -2663,6 +2663,7 @@ endpoint 0 { ram attribute clusterRevision default = 1; handle command RetrieveLogsRequest; + handle command RetrieveLogsResponse; } server cluster GeneralDiagnostics { diff --git a/examples/lock-app/lock-common/lock-app.zap b/examples/lock-app/lock-common/lock-app.zap index 0c9ce257b3cf31..04689b5ca64a00 100644 --- a/examples/lock-app/lock-common/lock-app.zap +++ b/examples/lock-app/lock-common/lock-app.zap @@ -2126,6 +2126,14 @@ "source": "client", "isIncoming": 1, "isEnabled": 1 + }, + { + "name": "RetrieveLogsResponse", + "code": 1, + "mfgCode": null, + "source": "server", + "isIncoming": 0, + "isEnabled": 1 } ], "attributes": [ diff --git a/examples/platform/linux/Options.cpp b/examples/platform/linux/Options.cpp index 3017e77901adef..8bf5c0b7f4e4b4 100644 --- a/examples/platform/linux/Options.cpp +++ b/examples/platform/linux/Options.cpp @@ -28,6 +28,8 @@ #include #include #include +#include +#include #include #include @@ -36,6 +38,12 @@ #include // nogncheck #endif +#if CHIP_WITH_NLFAULTINJECTION +#include +#include +#include +#endif + using namespace chip; using namespace chip::ArgParser; @@ -94,6 +102,9 @@ enum #if CONFIG_BUILD_FOR_HOST_UNIT_TEST kDeviceOption_SubscriptionResumptionRetryIntervalSec = 0x1026, #endif +#if CHIP_WITH_NLFAULTINJECTION + kDeviceOption_FaultInjection = 0x1027, +#endif }; constexpr unsigned kAppUsageLength = 64; @@ -155,6 +166,9 @@ OptionDef sDeviceOptionDefs[] = { #if CONFIG_BUILD_FOR_HOST_UNIT_TEST { "subscription-capacity", kArgumentRequired, kDeviceOption_SubscriptionCapacity }, { "subscription-resumption-retry-interval", kArgumentRequired, kDeviceOption_SubscriptionResumptionRetryIntervalSec }, +#endif +#if CHIP_WITH_NLFAULTINJECTION + { "faults", kArgumentRequired, kDeviceOption_FaultInjection }, #endif {} }; @@ -286,6 +300,10 @@ const char * sDeviceOptionHelp = " Max number of subscriptions the device will allow\n" " --subscription-resumption-retry-interval\n" " subscription timeout resumption retry interval in seconds\n" +#endif +#if CHIP_WITH_NLFAULTINJECTION + " --faults \n" + " Inject specified fault(s) at runtime.\n" #endif "\n"; @@ -556,6 +574,20 @@ bool HandleOption(const char * aProgram, OptionSet * aOptions, int aIdentifier, case kDeviceOption_SubscriptionResumptionRetryIntervalSec: LinuxDeviceOptions::GetInstance().subscriptionResumptionRetryIntervalSec = static_cast(atoi(aValue)); break; +#endif +#if CHIP_WITH_NLFAULTINJECTION + case kDeviceOption_FaultInjection: { + constexpr nl::FaultInjection::GetManagerFn faultManagerFns[] = { FaultInjection::GetManager, + Inet::FaultInjection::GetManager, + System::FaultInjection::GetManager }; + Platform::ScopedMemoryString mutableArg(aValue, strlen(aValue)); // ParseFaultInjectionStr may mutate + if (!nl::FaultInjection::ParseFaultInjectionStr(mutableArg.Get(), faultManagerFns, ArraySize(faultManagerFns))) + { + PrintArgError("%s: Invalid fault injection specification\n", aProgram); + retval = false; + } + break; + } #endif default: PrintArgError("%s: INTERNAL ERROR: Unhandled option: %s\n", aProgram, aName); diff --git a/examples/platform/silabs/BaseApplication.cpp b/examples/platform/silabs/BaseApplication.cpp index c98882f30aba96..abd2c207da0b06 100644 --- a/examples/platform/silabs/BaseApplication.cpp +++ b/examples/platform/silabs/BaseApplication.cpp @@ -82,7 +82,7 @@ #define APP_EVENT_QUEUE_SIZE 10 #define EXAMPLE_VENDOR_ID 0xcafe -#if (defined(ENABLE_WSTK_LEDS) && (defined(SL_CATALOG_SIMPLE_LED_LED1_PRESENT) || defined(SIWX_917))) +#if (defined(ENABLE_WSTK_LEDS) && (defined(SL_CATALOG_SIMPLE_LED_LED1_PRESENT))) #define SYSTEM_STATE_LED 0 #endif // ENABLE_WSTK_LEDS #define APP_FUNCTION_BUTTON 0 @@ -104,7 +104,7 @@ TimerHandle_t sLightTimer; TaskHandle_t sAppTaskHandle; QueueHandle_t sAppEventQueue; -#if (defined(ENABLE_WSTK_LEDS) && (defined(SL_CATALOG_SIMPLE_LED_LED1_PRESENT) || defined(SIWX_917))) +#if (defined(ENABLE_WSTK_LEDS) && (defined(SL_CATALOG_SIMPLE_LED_LED1_PRESENT))) LEDWidget sStatusLED; #endif // ENABLE_WSTK_LEDS @@ -270,7 +270,7 @@ CHIP_ERROR BaseApplication::Init() ConfigurationMgr().LogDeviceConfig(); OutputQrCode(true /*refreshLCD at init*/); -#if (defined(ENABLE_WSTK_LEDS) && (defined(SL_CATALOG_SIMPLE_LED_LED1_PRESENT) || defined(SIWX_917))) +#if (defined(ENABLE_WSTK_LEDS) && (defined(SL_CATALOG_SIMPLE_LED_LED1_PRESENT))) LEDWidget::InitGpio(); sStatusLED.Init(SYSTEM_STATE_LED); #endif // ENABLE_WSTK_LEDS @@ -321,7 +321,7 @@ void BaseApplication::FunctionEventHandler(AppEvent * aEvent) bool BaseApplication::ActivateStatusLedPatterns() { bool isPatternSet = false; -#if (defined(ENABLE_WSTK_LEDS) && (defined(SL_CATALOG_SIMPLE_LED_LED1_PRESENT) || defined(SIWX_917))) +#if (defined(ENABLE_WSTK_LEDS) && (defined(SL_CATALOG_SIMPLE_LED_LED1_PRESENT))) #ifdef MATTER_DM_PLUGIN_IDENTIFY_SERVER if (gIdentify.mActive) { @@ -427,7 +427,7 @@ void BaseApplication::LightEventHandler() #endif // CHIP_CONFIG_ENABLE_ICD_SERVER #if defined(ENABLE_WSTK_LEDS) -#if (defined(SL_CATALOG_SIMPLE_LED_LED1_PRESENT) || defined(SIWX_917)) +#ifdef SL_CATALOG_SIMPLE_LED_LED1_PRESENT // Update the status LED if factory reset has not been initiated. // // If system has "full connectivity", keep the LED On constantly. @@ -563,7 +563,7 @@ void BaseApplication::StartFactoryResetSequence() StartStatusLEDTimer(); #endif // CHIP_CONFIG_ENABLE_ICD_SERVER -#if (defined(ENABLE_WSTK_LEDS) && (defined(SL_CATALOG_SIMPLE_LED_LED1_PRESENT) || defined(SIWX_917))) +#if (defined(ENABLE_WSTK_LEDS) && (defined(SL_CATALOG_SIMPLE_LED_LED1_PRESENT))) // Turn off all LEDs before starting blink to make sure blink is // co-ordinated. sStatusLED.Set(false); @@ -596,7 +596,7 @@ void BaseApplication::StartStatusLEDTimer() void BaseApplication::StopStatusLEDTimer() { -#if (defined(ENABLE_WSTK_LEDS) && (defined(SL_CATALOG_SIMPLE_LED_LED1_PRESENT) || defined(SIWX_917))) +#if (defined(ENABLE_WSTK_LEDS) && (defined(SL_CATALOG_SIMPLE_LED_LED1_PRESENT))) sStatusLED.Set(false); #endif // ENABLE_WSTK_LEDS diff --git a/examples/platform/silabs/FreeRTOSConfig.h b/examples/platform/silabs/FreeRTOSConfig.h index 478dfa10be547f..fd6658c7af35e2 100644 --- a/examples/platform/silabs/FreeRTOSConfig.h +++ b/examples/platform/silabs/FreeRTOSConfig.h @@ -107,7 +107,7 @@ extern "C" { #include -#ifdef SIWX_917 +#ifdef SLI_SI91X_MCU_INTERFACE #include "si91x_device.h" extern uint32_t SystemCoreClock; #else // For EFR32 @@ -169,23 +169,23 @@ extern uint32_t SystemCoreClock; #define configTIMER_QUEUE_LENGTH (10) #define configTIMER_TASK_STACK_DEPTH (1024) -#ifdef SIWX_917 +#ifdef SLI_SI91X_MCU_INTERFACE #ifdef __NVIC_PRIO_BITS #undef __NVIC_PRIO_BITS #endif #define configPRIO_BITS 6 /* 6 priority levels. */ -#endif // SIWX_917 +#endif // SLI_SI91X_MCU_INTERFACE /* Interrupt priorities used by the kernel port layer itself. These are generic to all Cortex-M ports, and do not rely on any particular library functions. */ #define configKERNEL_INTERRUPT_PRIORITY (255) /* !!!! configMAX_SYSCALL_INTERRUPT_PRIORITY must not be set to zero !!!! See http://www.FreeRTOS.org/RTOS-Cortex-M3-M4.html. */ -#ifdef SIWX_917 +#ifdef SLI_SI91X_MCU_INTERFACE #define configMAX_SYSCALL_INTERRUPT_PRIORITY 20 #else #define configMAX_SYSCALL_INTERRUPT_PRIORITY 48 -#endif // SIWX_917 +#endif // SLI_SI91X_MCU_INTERFACE #define configENABLE_FPU 0 #define configENABLE_MPU 0 @@ -232,11 +232,11 @@ See http://www.FreeRTOS.org/RTOS-Cortex-M3-M4.html. */ #ifndef configTOTAL_HEAP_SIZE #ifdef SL_WIFI #ifdef DIC_ENABLE -#ifdef SIWX_917 +#ifdef SLI_SI91X_MCU_INTERFACE #define configTOTAL_HEAP_SIZE ((size_t) ((75 + EXTRA_HEAP_k) * 1024)) #else #define configTOTAL_HEAP_SIZE ((size_t) ((68 + EXTRA_HEAP_k) * 1024)) -#endif // SIWX_917 +#endif // SLI_SI91X_MCU_INTERFACE #else #define configTOTAL_HEAP_SIZE ((size_t) ((42 + EXTRA_HEAP_k) * 1024)) #endif // DIC diff --git a/examples/platform/silabs/MatterConfig.cpp b/examples/platform/silabs/MatterConfig.cpp index 32bf04cf0e0f7b..dd2368f566d252 100644 --- a/examples/platform/silabs/MatterConfig.cpp +++ b/examples/platform/silabs/MatterConfig.cpp @@ -42,9 +42,9 @@ #include "MemMonitoring.h" #endif -#ifdef SIWX_917 +#ifdef SLI_SI91X_MCU_INTERFACE #include "wfx_rsi.h" -#endif /* SIWX_917 */ +#endif /* SLI_SI91X_MCU_INTERFACE */ using namespace ::chip; using namespace ::chip::Inet; @@ -53,7 +53,7 @@ using namespace ::chip::DeviceLayer; #include // If building with the EFR32-provided crypto backend, we can use the // opaque keystore -#if CHIP_CRYPTO_PLATFORM && !(defined(SIWX_917)) +#if CHIP_CRYPTO_PLATFORM && !(defined(SLI_SI91X_MCU_INTERFACE)) #include static chip::DeviceLayer::Internal::Efr32PsaOperationalKeystore gOperationalKeystore; #endif @@ -229,7 +229,7 @@ CHIP_ERROR SilabsMatterConfig::InitMatter(const char * appName) initParams.testEventTriggerDelegate = &sTestEventTriggerDelegate; #endif // SILABS_TEST_EVENT_TRIGGER_ENABLED -#if CHIP_CRYPTO_PLATFORM && !(defined(SIWX_917)) +#if CHIP_CRYPTO_PLATFORM && !(defined(SLI_SI91X_MCU_INTERFACE)) // When building with EFR32 crypto, use the opaque key store // instead of the default (insecure) one. gOperationalKeystore.Init(); @@ -289,13 +289,14 @@ CHIP_ERROR SilabsMatterConfig::InitWiFi(void) #endif // SL_WFX_USE_SECURE_LINK #endif /* WF200_WIFI */ -#ifdef SIWX_917 +#ifdef SLI_SI91X_MCU_INTERFACE sl_status_t status; if ((status = wfx_wifi_rsi_init()) != SL_STATUS_OK) { + SILABS_LOG("wfx_wifi_rsi_init failed with status: %x", status); ReturnErrorOnFailure((CHIP_ERROR) status); } -#endif // SIWX_917 +#endif // SLI_SI91X_MCU_INTERFACE return CHIP_NO_ERROR; } @@ -306,7 +307,7 @@ CHIP_ERROR SilabsMatterConfig::InitWiFi(void) // ================================================================================ extern "C" void vApplicationIdleHook(void) { -#if SIWX_917 && CHIP_CONFIG_ENABLE_ICD_SERVER +#if SLI_SI91X_MCU_INTERFACE && CHIP_CONFIG_ENABLE_ICD_SERVER sl_wfx_host_si91x_sleep_wakeup(); #endif } diff --git a/examples/platform/silabs/OTAConfig.cpp b/examples/platform/silabs/OTAConfig.cpp index adc6f23abb4606..f35c9770a6aab7 100644 --- a/examples/platform/silabs/OTAConfig.cpp +++ b/examples/platform/silabs/OTAConfig.cpp @@ -17,9 +17,10 @@ */ #include "OTAConfig.h" +#include "silabs_utils.h" #include -#ifndef SIWX_917 +#ifndef SLI_SI91X_MCU_INTERFACE #include "application_properties.h" @@ -74,14 +75,13 @@ __attribute__((used)) ApplicationProperties_t sl_app_properties = { .longTokenSectionAddress = NULL, }; #endif // SL_CATALOG_GECKO_BOOTLOADER_INTERFACE_PRESENT -#endif // SIWX_917 +#endif // SLI_SI91X_MCU_INTERFACE // Global OTA objects chip::DefaultOTARequestor gRequestorCore; chip::DefaultOTARequestorStorage gRequestorStorage; chip::DeviceLayer::DefaultOTARequestorDriver gRequestorUser; chip::BDXDownloader gDownloader; -chip::OTAImageProcessorImpl gImageProcessor; void OTAConfig::Init() { @@ -93,12 +93,23 @@ void OTAConfig::Init() // Periodic query timeout must be set prior to requestor being initialized gRequestorUser.SetPeriodicQueryTimeout(OTA_PERIODIC_TIMEOUT); - gRequestorUser.Init(&gRequestorCore, &gImageProcessor); - gImageProcessor.SetOTAImageFile("test.txt"); - gImageProcessor.SetOTADownloader(&gDownloader); +#if CHIP_DEVICE_CONFIG_ENABLE_MULTI_OTA_REQUESTOR + auto & imageProcessor = chip::OTAMultiImageProcessorImpl::GetDefaultInstance(); +#else + auto & imageProcessor = chip::OTAImageProcessorImpl::GetDefaultInstance(); +#endif + + gRequestorUser.Init(&gRequestorCore, &imageProcessor); + + CHIP_ERROR err = imageProcessor.Init(&gDownloader); + if (err != CHIP_NO_ERROR) + { + SILABS_LOG("Image processor init failed"); + assert(err == CHIP_NO_ERROR); + } // Connect the Downloader and Image Processor objects - gDownloader.SetImageProcessorDelegate(&gImageProcessor); + gDownloader.SetImageProcessorDelegate(&imageProcessor); // Initialize and interconnect the Requestor and Image Processor objects -- END } diff --git a/examples/platform/silabs/OTAConfig.h b/examples/platform/silabs/OTAConfig.h index 2d5dbcf3635223..2b7ed9a45fa257 100644 --- a/examples/platform/silabs/OTAConfig.h +++ b/examples/platform/silabs/OTAConfig.h @@ -22,7 +22,12 @@ #include #include #include + +#if CHIP_DEVICE_CONFIG_ENABLE_MULTI_OTA_REQUESTOR +#include +#else #include +#endif class OTAConfig { diff --git a/examples/platform/silabs/SiWx917/SiWx917/sl_wifi_if.c b/examples/platform/silabs/SiWx917/SiWx917/sl_wifi_if.c index c5044fcf255a8e..48b8f54ff540fa 100644 --- a/examples/platform/silabs/SiWx917/SiWx917/sl_wifi_if.c +++ b/examples/platform/silabs/SiWx917/SiWx917/sl_wifi_if.c @@ -41,7 +41,7 @@ #include "ble_config.h" -#if SL_ICD_ENABLED && SIWX_917 +#if SL_ICD_ENABLED && SLI_SI91X_MCU_INTERFACE #include "rsi_rom_power_save.h" #include "sl_si91x_button_pin_config.h" #include "sl_si91x_driver.h" @@ -50,7 +50,7 @@ // TODO: should be removed once we are getting the press interrupt for button 0 with sleep #define BUTTON_PRESSED 1 bool btn0_pressed = false; -#endif // SL_ICD_ENABLED && SIWX_917 +#endif // SL_ICD_ENABLED && SLI_SI91X_MCU_INTERFACE #include "dhcp_client.h" #include "sl_wifi.h" @@ -63,12 +63,15 @@ bool btn0_pressed = false; #define ADV_MULTIPROBE 1 #define ADV_SCAN_PERIODICITY 10 -#ifdef SIWX_917 +// TODO: Confirm that this value works for size and timing +#define WFX_QUEUE_SIZE 10 + +#if SLI_SI91X_MCU_INTERFACE #include "sl_si91x_trng.h" #define TRNGKEY_SIZE 4 -#endif // SIWX_917 +#endif // SLI_SI91X_MCU_INTERFACE -struct wfx_rsi wfx_rsi; +WfxRsi_t wfx_rsi; /* Declare a variable to hold the data associated with the created event group. */ StaticEventGroup_t rsiDriverEventGroup; @@ -86,6 +89,8 @@ bool is_wifi_disconnection_event = false; uint32_t retryInterval = WLAN_MIN_RETRY_TIMER_MS; volatile bool scan_results_complete = false; volatile bool bg_scan_results_complete = false; + +// TODO: Figure out why we actually need this, we are already handling failure and retries somewhere else. #define WIFI_SCAN_TIMEOUT_TICK 10000 extern osSemaphoreId_t sl_rs_ble_init_sem; @@ -94,10 +99,55 @@ extern osSemaphoreId_t sl_rs_ble_init_sem; * This file implements the interface to the wifi sdk */ -wfx_wifi_scan_ext_t * temp_reset; +static wfx_wifi_scan_ext_t temp_reset; volatile sl_status_t callback_status = SL_STATUS_OK; +// Scan semaphore +static osSemaphoreId_t sScanSemaphore; +// DHCP Poll timer +static osTimerId_t sDHCPTimer; +static osMessageQueueId_t sWifiEventQueue = NULL; + +static void DHCPTimerEventHandler(void * arg) +{ + WfxEvent_t event; + event.eventType = WFX_EVT_DHCP_POLL; + WfxPostEvent(&event); +} + +static void CancelDHCPTimer() +{ + osStatus_t status; + + // Check if timer started + if (!osTimerIsRunning(sDHCPTimer)) + { + SILABS_LOG("CancelDHCPTimer: timer not running"); + return; + } + + status = osTimerStop(sDHCPTimer); + if (status != osOK) + { + SILABS_LOG("CancelDHCPTimer: failed to stop timer with status: %d", status); + } +} + +static void StartDHCPTimer(uint32_t timeout) +{ + osStatus_t status; + + // Cancel timer if already started + CancelDHCPTimer(); + + status = osTimerStart(sDHCPTimer, pdMS_TO_TICKS(timeout)); + if (status != osOK) + { + SILABS_LOG("StartDHCPTimer: failed to start timer with status: %d", status); + } +} + /****************************************************************** * @fn int32_t wfx_rsi_get_ap_info(wfx_wifi_scan_result_t *ap) * @brief @@ -132,13 +182,13 @@ int32_t wfx_rsi_get_ap_ext(wfx_wifi_scan_ext_t * extra_info) sl_wifi_statistics_t test = { 0 }; status = sl_wifi_get_statistics(SL_WIFI_CLIENT_INTERFACE, &test); VERIFY_STATUS_AND_RETURN(status); - extra_info->beacon_lost_count = test.beacon_lost_count - temp_reset->beacon_lost_count; - extra_info->beacon_rx_count = test.beacon_rx_count - temp_reset->beacon_rx_count; - extra_info->mcast_rx_count = test.mcast_rx_count - temp_reset->mcast_rx_count; - extra_info->mcast_tx_count = test.mcast_tx_count - temp_reset->mcast_tx_count; - extra_info->ucast_rx_count = test.ucast_rx_count - temp_reset->ucast_rx_count; - extra_info->ucast_tx_count = test.ucast_tx_count - temp_reset->ucast_tx_count; - extra_info->overrun_count = test.overrun_count - temp_reset->overrun_count; + extra_info->beacon_lost_count = test.beacon_lost_count - temp_reset.beacon_lost_count; + extra_info->beacon_rx_count = test.beacon_rx_count - temp_reset.beacon_rx_count; + extra_info->mcast_rx_count = test.mcast_rx_count - temp_reset.mcast_rx_count; + extra_info->mcast_tx_count = test.mcast_tx_count - temp_reset.mcast_tx_count; + extra_info->ucast_rx_count = test.ucast_rx_count - temp_reset.ucast_rx_count; + extra_info->ucast_tx_count = test.ucast_tx_count - temp_reset.ucast_tx_count; + extra_info->overrun_count = test.overrun_count - temp_reset.overrun_count; return status; } @@ -156,13 +206,13 @@ int32_t wfx_rsi_reset_count() sl_status_t status = SL_STATUS_OK; status = sl_wifi_get_statistics(SL_WIFI_CLIENT_INTERFACE, &test); VERIFY_STATUS_AND_RETURN(status); - temp_reset->beacon_lost_count = test.beacon_lost_count; - temp_reset->beacon_rx_count = test.beacon_rx_count; - temp_reset->mcast_rx_count = test.mcast_rx_count; - temp_reset->mcast_tx_count = test.mcast_tx_count; - temp_reset->ucast_rx_count = test.ucast_rx_count; - temp_reset->ucast_tx_count = test.ucast_tx_count; - temp_reset->overrun_count = test.overrun_count; + temp_reset.beacon_lost_count = test.beacon_lost_count; + temp_reset.beacon_rx_count = test.beacon_rx_count; + temp_reset.mcast_rx_count = test.mcast_rx_count; + temp_reset.mcast_tx_count = test.mcast_tx_count; + temp_reset.ucast_rx_count = test.ucast_rx_count; + temp_reset.ucast_tx_count = test.ucast_tx_count; + temp_reset.overrun_count = test.overrun_count; return status; } @@ -181,9 +231,9 @@ int32_t wfx_rsi_disconnect() sl_status_t join_callback_handler(sl_wifi_event_t event, char * result, uint32_t result_length, void * arg) { + WfxEvent_t WfxEvent; + wfx_rsi.dev_state &= ~(WFX_RSI_ST_STA_CONNECTING); - temp_reset = (wfx_wifi_scan_ext_t *) malloc(sizeof(wfx_wifi_scan_ext_t)); - memset(temp_reset, 0, sizeof(wfx_wifi_scan_ext_t)); if (SL_WIFI_CHECK_IF_EVENT_FAILED(event)) { SILABS_LOG("F: Join Event received with %u bytes payload\n", result_length); @@ -193,16 +243,20 @@ sl_status_t join_callback_handler(sl_wifi_event_t event, char * result, uint32_t wfx_retry_interval_handler(is_wifi_disconnection_event, wfx_rsi.join_retries++); if (is_wifi_disconnection_event || wfx_rsi.join_retries <= WFX_RSI_CONFIG_MAX_JOIN) { - xEventGroupSetBits(wfx_rsi.events, WFX_EVT_STA_START_JOIN); + WfxEvent.eventType = WFX_EVT_STA_START_JOIN; + WfxPostEvent(&WfxEvent); } return SL_STATUS_FAIL; } /* * Join was complete - Do the DHCP */ + memset(&temp_reset, 0, sizeof(wfx_wifi_scan_ext_t)); SILABS_LOG("join_callback_handler: join completed."); SILABS_LOG("%c: Join Event received with %u bytes payload\n", *result, result_length); - xEventGroupSetBits(wfx_rsi.events, WFX_EVT_STA_CONN); + + WfxEvent.eventType = WFX_EVT_STA_CONN; + WfxPostEvent(&WfxEvent); wfx_rsi.join_retries = 0; retryInterval = WLAN_MIN_RETRY_TIMER_MS; if (is_wifi_disconnection_event) @@ -215,7 +269,7 @@ sl_status_t join_callback_handler(sl_wifi_event_t event, char * result, uint32_t #if SL_ICD_ENABLED -#if SIWX_917 +#if SLI_SI91X_MCU_INTERFACE /****************************************************************** * @fn sl_wfx_host_si91x_sleep_wakeup() * @brief @@ -250,7 +304,7 @@ void sl_wfx_host_si91x_sleep_wakeup() } } } -#endif /* SIWX_917 */ +#endif // SLI_SI91X_MCU_INTERFACE /****************************************************************** * @fn wfx_rsi_power_save() @@ -306,8 +360,30 @@ int32_t wfx_wifi_rsi_init(void) status = sl_wifi_init(&config, NULL, sl_wifi_default_event_handler); if (status != SL_STATUS_OK) { - SILABS_LOG("wfx_wifi_rsi_init failed %x", status); + return status; + } + + // Create Sempaphore for scan + sScanSemaphore = osSemaphoreNew(1, 0, NULL); + if (sScanSemaphore == NULL) + { + return SL_STATUS_ALLOCATION_FAILED; } + // Create the message queue + sWifiEventQueue = osMessageQueueNew(WFX_QUEUE_SIZE, sizeof(WfxEvent_t), NULL); + if (sWifiEventQueue == NULL) + { + return SL_STATUS_ALLOCATION_FAILED; + } + + // Create timer for DHCP polling + // TODO: Use LWIP timer instead of creating a new one here + sDHCPTimer = osTimerNew(DHCPTimerEventHandler, osTimerPeriodic, NULL, NULL); + if (sDHCPTimer == NULL) + { + return SL_STATUS_ALLOCATION_FAILED; + } + return status; } @@ -373,7 +449,7 @@ static sl_status_t wfx_rsi_init(void) SILABS_LOG("sl_wifi_get_mac_address failed: %x", status); return status; } -#ifdef SIWX_917 + const uint32_t trngKey[TRNGKEY_SIZE] = { 0x16157E2B, 0xA6D2AE28, 0x8815F7AB, 0x3C4FCF09 }; // To check the Entropy of TRNG and verify TRNG functioning. @@ -391,7 +467,7 @@ static sl_status_t wfx_rsi_init(void) SILABS_LOG("TRNG Key Programming Failed"); return status; } -#endif // SIWX_917 + wfx_rsi.events = xEventGroupCreateStatic(&rsiDriverEventGroup); wfx_rsi.dev_state |= WFX_RSI_ST_DEV_READY; osSemaphoreRelease(sl_rs_ble_init_sem); @@ -422,6 +498,8 @@ sl_status_t scan_callback_handler(sl_wifi_event_t event, sl_wifi_scan_result_t * #else wfx_rsi.sec.security = WFX_SEC_WPA2; #endif /* WIFI_ENABLE_SECURITY_WPA3_TRANSITION */ + + osSemaphoreRelease(sScanSemaphore); return SL_STATUS_FAIL; } wfx_rsi.sec.security = WFX_SEC_UNSPECIFIED; @@ -458,6 +536,8 @@ sl_status_t scan_callback_handler(sl_wifi_event_t event, sl_wifi_scan_result_t * } wfx_rsi.dev_state &= ~WFX_RSI_ST_SCANSTARTED; scan_results_complete = true; + + osSemaphoreRelease(sScanSemaphore); return SL_STATUS_OK; } sl_status_t show_scan_results(sl_wifi_scan_result_t * scan_result) @@ -502,6 +582,7 @@ sl_status_t bg_scan_callback_handler(sl_wifi_event_t event, sl_wifi_scan_result_ { callback_status = show_scan_results(result); bg_scan_results_complete = true; + osSemaphoreRelease(sScanSemaphore); return SL_STATUS_OK; } /*************************************************************************************** @@ -530,12 +611,7 @@ static void wfx_rsi_save_ap_info() // translation #endif if (SL_STATUS_IN_PROGRESS == status) { - const uint32_t start = osKernelGetTickCount(); - while (!scan_results_complete && (osKernelGetTickCount() - start) <= WIFI_SCAN_TIMEOUT_TICK) - { - osThreadYield(); - } - status = scan_results_complete ? callback_status : SL_STATUS_TIMEOUT; + osSemaphoreAcquire(sScanSemaphore, WIFI_SCAN_TIMEOUT_TICK); } } @@ -550,6 +626,7 @@ static sl_status_t wfx_rsi_do_join(void) { sl_status_t status = SL_STATUS_OK; sl_wifi_security_t connect_security_mode; + WfxEvent_t event; switch (wfx_rsi.sec.security) { case WFX_SEC_WEP: @@ -645,7 +722,8 @@ static sl_status_t wfx_rsi_do_join(void) wfx_retry_interval_handler(is_wifi_disconnection_event, wfx_rsi.join_retries); if (is_wifi_disconnection_event || wfx_rsi.join_retries <= MAX_JOIN_RETRIES_COUNT) { - xEventGroupSetBits(wfx_rsi.events, WFX_EVT_STA_START_JOIN); + event.eventType = WFX_EVT_STA_START_JOIN; + WfxPostEvent(&event); } } } @@ -653,6 +731,184 @@ static sl_status_t wfx_rsi_do_join(void) return status; } +/// NotifyConnectivity +/// @brief Notify the application about the connectivity status if it has not been notified yet. +/// Helper function for HandleDHCPPolling. +void NotifyConnectivity() +{ + if (!hasNotifiedWifiConnectivity) + { + wfx_connected_notify(CONNECTION_STATUS_SUCCESS, &wfx_rsi.ap_mac); + hasNotifiedWifiConnectivity = true; + } +} + +void HandleDHCPPolling() +{ + struct netif * sta_netif; + WfxEvent_t event; + + sta_netif = wfx_get_netif(SL_WFX_STA_INTERFACE); + if (sta_netif == NULL) + { + // TODO: Notify the application that the interface is not set up or Chipdie here because we are in an unkonwn state + SILABS_LOG("HandleDHCPPolling: failed to get STA netif"); + return; + } +#if (CHIP_DEVICE_CONFIG_ENABLE_IPV4) + uint8_t dhcp_state = dhcpclient_poll(sta_netif); + if (dhcp_state == DHCP_ADDRESS_ASSIGNED && !hasNotifiedIPV4) + { + wfx_dhcp_got_ipv4((uint32_t) sta_netif->ip_addr.u_addr.ip4.addr); + hasNotifiedIPV4 = true; + NotifyConnectivity(); + } + else if (dhcp_state == DHCP_OFF) + { + wfx_ip_changed_notify(IP_STATUS_FAIL); + hasNotifiedIPV4 = false; + } +#endif /* CHIP_DEVICE_CONFIG_ENABLE_IPV4 */ + /* Checks if the assigned IPv6 address is preferred by evaluating + * the first block of IPv6 address ( block 0) + */ + if ((ip6_addr_ispreferred(netif_ip6_addr_state(sta_netif, 0))) && !hasNotifiedIPV6) + { + wfx_ipv6_notify(GET_IPV6_SUCCESS); + hasNotifiedIPV6 = true; + event.eventType = WFX_EVT_STA_DHCP_DONE; + WfxPostEvent(&event); + NotifyConnectivity(); + } +} + +void WfxPostEvent(WfxEvent_t * event) +{ + sl_status_t status = osMessageQueuePut(sWifiEventQueue, event, 0, 0); + + if (status != osOK) + { + SILABS_LOG("WfxPostEvent: failed to post event with status: %d", status); + // TODO: Handle error, requeue event depending on queue size or notify relevant task, Chipdie, etc. + } +} + +/// ResetDHCPNotificationFlags +/// @brief Reset the flags that are used to notify the application about DHCP connectivity +/// and emits a WFX_EVT_STA_DO_DHCP event to trigger DHCP polling checks. Helper function for ProcessEvent. +void ResetDHCPNotificationFlags() +{ + WfxEvent_t outEvent; + +#if (CHIP_DEVICE_CONFIG_ENABLE_IPV4) + hasNotifiedIPV4 = false; +#endif // CHIP_DEVICE_CONFIG_ENABLE_IPV4 + hasNotifiedIPV6 = false; + hasNotifiedWifiConnectivity = false; + + outEvent.eventType = WFX_EVT_STA_DO_DHCP; + WfxPostEvent(&outEvent); +} + +void ProcessEvent(WfxEvent_t inEvent) +{ + // Process event + switch (inEvent.eventType) + { + case WFX_EVT_STA_CONN: + SILABS_LOG("%s: starting LwIP STA", __func__); + wfx_rsi.dev_state |= WFX_RSI_ST_STA_CONNECTED; + ResetDHCPNotificationFlags(); + wfx_lwip_set_sta_link_up(); + /* We need to get AP Mac - TODO */ + // Uncomment once the hook into MATTER is moved to IP connectivty instead + // of AP connectivity. + // wfx_connected_notify(0, &wfx_rsi.ap_mac); // This + // is independant of IP connectivity. + break; + case WFX_EVT_STA_DISCONN: + // TODO: This event is not being posted anywhere, seems to be a dead code or we are missing something + wfx_rsi.dev_state &= + ~(WFX_RSI_ST_STA_READY | WFX_RSI_ST_STA_CONNECTING | WFX_RSI_ST_STA_CONNECTED | WFX_RSI_ST_STA_DHCP_DONE); + SILABS_LOG("%s: disconnect notify", __func__); + /* TODO: Implement disconnect notify */ + ResetDHCPNotificationFlags(); + wfx_lwip_set_sta_link_down(); // Internally dhcpclient_poll(netif) -> + // wfx_ip_changed_notify(0) for IPV4 +#if (CHIP_DEVICE_CONFIG_ENABLE_IPV4) + wfx_ip_changed_notify(IP_STATUS_FAIL); +#endif /* CHIP_DEVICE_CONFIG_ENABLE_IPV4 */ + wfx_ipv6_notify(GET_IPV6_FAIL); + break; + case WFX_EVT_AP_START: + // TODO: Currently unimplemented + break; + case WFX_EVT_AP_STOP: + // TODO: Currently unimplemented + break; + case WFX_EVT_SCAN: +#ifdef SL_WFX_CONFIG_SCAN + if (!(wfx_rsi.dev_state & WFX_RSI_ST_SCANSTARTED)) + { + SILABS_LOG("%s: start SSID scan", __func__); + sl_wifi_scan_configuration_t wifi_scan_configuration = { 0 }; + + // TODO: Add scan logic + sl_wifi_advanced_scan_configuration_t advanced_scan_configuration = { 0 }; + int32_t status; + advanced_scan_configuration.active_channel_time = ADV_ACTIVE_SCAN_DURATION; + advanced_scan_configuration.passive_channel_time = ADV_PASSIVE_SCAN_DURATION; + advanced_scan_configuration.trigger_level = ADV_SCAN_THRESHOLD; + advanced_scan_configuration.trigger_level_change = ADV_RSSI_TOLERANCE_THRESHOLD; + advanced_scan_configuration.enable_multi_probe = ADV_MULTIPROBE; + status = sl_wifi_set_advanced_scan_configuration(&advanced_scan_configuration); + if (SL_STATUS_OK != status) + { + // TODO: Seems like Chipdie should be called here, the device should be initialized here + SILABS_LOG("Failed to set advanced scan configuration with status: %d", status); + return; + } + + if (wfx_rsi.dev_state & WFX_RSI_ST_STA_CONNECTED) + { + /* Terminate with end of scan which is no ap sent back */ + wifi_scan_configuration.type = SL_WIFI_SCAN_TYPE_ADV_SCAN; + wifi_scan_configuration.periodic_scan_interval = ADV_SCAN_PERIODICITY; + } + else + { + wifi_scan_configuration = default_wifi_scan_configuration; + } + sl_wifi_set_scan_callback(bg_scan_callback_handler, NULL); + scan_results_complete = false; + wfx_rsi.dev_state |= WFX_RSI_ST_SCANSTARTED; + status = sl_wifi_start_scan(SL_WIFI_CLIENT_2_4GHZ_INTERFACE, NULL, &wifi_scan_configuration); + if (SL_STATUS_IN_PROGRESS == status) + { + osSemaphoreAcquire(sScanSemaphore, WIFI_SCAN_TIMEOUT_TICK); + } + } + break; +#endif /* SL_WFX_CONFIG_SCAN */ + case WFX_EVT_STA_START_JOIN: + // saving the AP related info + wfx_rsi_save_ap_info(); + // Joining to the network + wfx_rsi_do_join(); + break; + case WFX_EVT_STA_DO_DHCP: + StartDHCPTimer(WFX_RSI_DHCP_POLL_INTERVAL); + break; + case WFX_EVT_STA_DHCP_DONE: + CancelDHCPTimer(); + break; + case WFX_EVT_DHCP_POLL: + HandleDHCPPolling(); + default: + break; + } +} + /********************************************************************************* * @fn void wfx_rsi_task(void *arg) * @brief @@ -667,176 +923,31 @@ static sl_status_t wfx_rsi_do_join(void) void wfx_rsi_task(void * arg) { EventBits_t flags; - TickType_t last_dhcp_poll, now; - struct netif * sta_netif; (void) arg; sl_status_t status = wfx_rsi_init(); + + WfxEvent_t wfxEvent; if (status != RSI_SUCCESS) { SILABS_LOG("wfx_rsi_task: error: wfx_rsi_init with status: %02x", status); return; } wfx_lwip_start(); - last_dhcp_poll = xTaskGetTickCount(); - sta_netif = wfx_get_netif(SL_WFX_STA_INTERFACE); wfx_started_notify(); - SILABS_LOG("wfx_rsi_task: starting event wait"); + SILABS_LOG("wfx_rsi_task: starting event loop"); for (;;) { - /* - * This is the main job of this task. - * Wait for commands from the ConnectivityManager - * Make state changes (based on call backs) - */ - flags = xEventGroupWaitBits(wfx_rsi.events, - WFX_EVT_STA_CONN | WFX_EVT_STA_DISCONN | WFX_EVT_STA_START_JOIN -#ifdef SL_WFX_CONFIG_SOFTAP - | WFX_EVT_AP_START | WFX_EVT_AP_STOP -#endif /* SL_WFX_CONFIG_SOFTAP */ -#ifdef SL_WFX_CONFIG_SCAN - | WFX_EVT_SCAN -#endif /* SL_WFX_CONFIG_SCAN */ - | 0, - pdTRUE, /* Clear the bits */ - pdFALSE, /* Wait for any bit */ - pdMS_TO_TICKS(250)); /* 250 mSec */ - - if (flags) - { - SILABS_LOG("%s: wait event encountered: %x", __func__, flags); - } - /* - * Let's handle DHCP polling here - */ - if (wfx_rsi.dev_state & WFX_RSI_ST_STA_CONNECTED) - { - if ((now = xTaskGetTickCount()) > (last_dhcp_poll + pdMS_TO_TICKS(250))) - { -#if (CHIP_DEVICE_CONFIG_ENABLE_IPV4) - uint8_t dhcp_state = dhcpclient_poll(sta_netif); - if (dhcp_state == DHCP_ADDRESS_ASSIGNED && !hasNotifiedIPV4) - { - wfx_dhcp_got_ipv4((uint32_t) sta_netif->ip_addr.u_addr.ip4.addr); - hasNotifiedIPV4 = true; - if (!hasNotifiedWifiConnectivity) - { - wfx_connected_notify(CONNECTION_STATUS_SUCCESS, &wfx_rsi.ap_mac); - hasNotifiedWifiConnectivity = true; - } - } - else if (dhcp_state == DHCP_OFF) - { - wfx_ip_changed_notify(IP_STATUS_FAIL); - hasNotifiedIPV4 = false; - } -#endif /* CHIP_DEVICE_CONFIG_ENABLE_IPV4 */ - /* Checks if the assigned IPv6 address is preferred by evaluating - * the first block of IPv6 address ( block 0) - */ - if ((ip6_addr_ispreferred(netif_ip6_addr_state(sta_netif, 0))) && !hasNotifiedIPV6) - { - wfx_ipv6_notify(GET_IPV6_SUCCESS); - hasNotifiedIPV6 = true; - if (!hasNotifiedWifiConnectivity) - { - wfx_connected_notify(CONNECTION_STATUS_SUCCESS, &wfx_rsi.ap_mac); - hasNotifiedWifiConnectivity = true; - } - } - last_dhcp_poll = now; - } - } - if (flags & WFX_EVT_STA_START_JOIN) - { - // saving the AP related info - wfx_rsi_save_ap_info(); - // Joining to the network - status = wfx_rsi_do_join(); - } - if (flags & WFX_EVT_STA_CONN) - { - SILABS_LOG("%s: starting LwIP STA", __func__); - wfx_rsi.dev_state |= WFX_RSI_ST_STA_CONNECTED; - hasNotifiedWifiConnectivity = false; -#if (CHIP_DEVICE_CONFIG_ENABLE_IPV4) - hasNotifiedIPV4 = false; -#endif // CHIP_DEVICE_CONFIG_ENABLE_IPV4 - hasNotifiedIPV6 = false; - wfx_lwip_set_sta_link_up(); - /* We need to get AP Mac - TODO */ - // Uncomment once the hook into MATTER is moved to IP connectivty instead - // of AP connectivity. wfx_connected_notify(0, &wfx_rsi.ap_mac); // This - // is independant of IP connectivity. - } - if (flags & WFX_EVT_STA_DISCONN) + status = osMessageQueueGet(sWifiEventQueue, &wfxEvent, NULL, osWaitForever); + if (status == osOK) { - wfx_rsi.dev_state &= - ~(WFX_RSI_ST_STA_READY | WFX_RSI_ST_STA_CONNECTING | WFX_RSI_ST_STA_CONNECTED | WFX_RSI_ST_STA_DHCP_DONE); - SILABS_LOG("%s: disconnect notify", __func__); - /* TODO: Implement disconnect notify */ - wfx_lwip_set_sta_link_down(); // Internally dhcpclient_poll(netif) -> - // wfx_ip_changed_notify(0) for IPV4 -#if (CHIP_DEVICE_CONFIG_ENABLE_IPV4) - wfx_ip_changed_notify(IP_STATUS_FAIL); - hasNotifiedIPV4 = false; -#endif /* CHIP_DEVICE_CONFIG_ENABLE_IPV4 */ - wfx_ipv6_notify(GET_IPV6_FAIL); - hasNotifiedIPV6 = false; - hasNotifiedWifiConnectivity = false; + ProcessEvent(wfxEvent); } -#ifdef SL_WFX_CONFIG_SCAN - if (flags & WFX_EVT_SCAN) - { - if (!(wfx_rsi.dev_state & WFX_RSI_ST_SCANSTARTED)) - { - SILABS_LOG("%s: start SSID scan", __func__); - sl_wifi_scan_configuration_t wifi_scan_configuration = { 0 }; - - // TODO: Add scan logic - sl_wifi_advanced_scan_configuration_t advanced_scan_configuration = { 0 }; - int32_t status; - advanced_scan_configuration.active_channel_time = ADV_ACTIVE_SCAN_DURATION; - advanced_scan_configuration.passive_channel_time = ADV_PASSIVE_SCAN_DURATION; - advanced_scan_configuration.trigger_level = ADV_SCAN_THRESHOLD; - advanced_scan_configuration.trigger_level_change = ADV_RSSI_TOLERANCE_THRESHOLD; - advanced_scan_configuration.enable_multi_probe = ADV_MULTIPROBE; - status = sl_wifi_set_advanced_scan_configuration(&advanced_scan_configuration); - if (wfx_rsi.dev_state & WFX_RSI_ST_STA_CONNECTED) - { - /* Terminate with end of scan which is no ap sent back */ - wifi_scan_configuration.type = SL_WIFI_SCAN_TYPE_ADV_SCAN; - wifi_scan_configuration.periodic_scan_interval = ADV_SCAN_PERIODICITY; - } - else - { - wifi_scan_configuration = default_wifi_scan_configuration; - } - sl_wifi_set_scan_callback(bg_scan_callback_handler, NULL); - scan_results_complete = false; - wfx_rsi.dev_state |= WFX_RSI_ST_SCANSTARTED; - status = sl_wifi_start_scan(SL_WIFI_CLIENT_2_4GHZ_INTERFACE, NULL, &wifi_scan_configuration); - if (SL_STATUS_IN_PROGRESS == status) - { - const uint32_t start = osKernelGetTickCount(); - while (!bg_scan_results_complete && (osKernelGetTickCount() - start) <= WIFI_SCAN_TIMEOUT_TICK) - { - osThreadYield(); - } - status = bg_scan_results_complete ? callback_status : SL_STATUS_TIMEOUT; - } - } - } -#endif /* SL_WFX_CONFIG_SCAN */ -#ifdef SL_WFX_CONFIG_SOFTAP - /* TODO */ - if (flags & WFX_EVT_AP_START) - { - } - if (flags & WFX_EVT_AP_STOP) + else { + // TODO: Everywhere in this file(and related) SILABS_LOG ---> Chiplog + SILABS_LOG("Failed to get event with status: %x", status); } -#endif /* SL_WFX_CONFIG_SOFTAP */ } } diff --git a/examples/platform/silabs/SiWx917/SiWx917/sl_wlan_config.h b/examples/platform/silabs/SiWx917/SiWx917/sl_wlan_config.h index d73989617fe6e3..813ab399af5ab8 100644 --- a/examples/platform/silabs/SiWx917/SiWx917/sl_wlan_config.h +++ b/examples/platform/silabs/SiWx917/SiWx917/sl_wlan_config.h @@ -48,21 +48,11 @@ static const sl_wifi_device_configuration_t config = { #endif | SL_SI91X_TCP_IP_FEAT_ICMP | SL_SI91X_TCP_IP_FEAT_EXTENSION_VALID), .custom_feature_bit_map = (SL_SI91X_CUSTOM_FEAT_EXTENTION_VALID | RSI_CUSTOM_FEATURE_BIT_MAP), - .ext_custom_feature_bit_map = ( -#ifdef SLI_SI917 - (RSI_EXT_CUSTOM_FEATURE_BIT_MAP) -#else // defaults -#ifdef SLI_SI91X_MCU_INTERFACE - (SL_SI91X_EXT_FEAT_256K_MODE | RSI_EXT_CUSTOM_FEATURE_BIT_MAP) -#else - (SL_SI91X_EXT_FEAT_384K_MODE | RSI_EXT_CUSTOM_FEATURE_BIT_MAP) -#endif -#endif // SLI_SI917 - | (SL_SI91X_EXT_FEAT_BT_CUSTOM_FEAT_ENABLE) + .ext_custom_feature_bit_map = (RSI_EXT_CUSTOM_FEATURE_BIT_MAP | (SL_SI91X_EXT_FEAT_BT_CUSTOM_FEAT_ENABLE) #if (defined A2DP_POWER_SAVE_ENABLE) - | SL_SI91X_EXT_FEAT_XTAL_CLK_ENABLE(2) + | SL_SI91X_EXT_FEAT_XTAL_CLK_ENABLE(2) #endif - ), + ), .bt_feature_bit_map = (RSI_BT_FEATURE_BITMAP #if (RSI_BT_GATT_ON_CLASSIC) | SL_SI91X_BT_ATT_OVER_CLASSIC_ACL /* to support att over classic acl link */ diff --git a/examples/platform/silabs/SiWx917/SiWx917/wfx_rsi.h b/examples/platform/silabs/SiWx917/SiWx917/wfx_rsi.h index 998b3ac1ef5d7a..acafeb30d72b25 100644 --- a/examples/platform/silabs/SiWx917/SiWx917/wfx_rsi.h +++ b/examples/platform/silabs/SiWx917/SiWx917/wfx_rsi.h @@ -28,35 +28,52 @@ #define WFX_RSI_TASK_SZ (1024 + 1024) /* Stack for the WFX/RSI task */ #define WFX_RSI_BUF_SZ (1024 * 10) /* May need tweak */ #define WFX_RSI_CONFIG_MAX_JOIN (5) /* Max join retries */ +// TODO: Default values are usually in minutes, but this is in ms. Confirm if this is correct +#define WFX_RSI_DHCP_POLL_INTERVAL (250) /* Poll interval in ms for DHCP */ -/* - * Various events fielded by the wfx_rsi task - * Make sure that we only use 8 bits (otherwise freeRTOS - may need some changes) - */ -#define WFX_EVT_STA_CONN (0x01) -#define WFX_EVT_STA_DISCONN (0x02) -#define WFX_EVT_AP_START (0x04) -#define WFX_EVT_AP_STOP (0x08) -#define WFX_EVT_SCAN (0x10) /* This is used as scan result and start */ -#define WFX_EVT_STA_START_JOIN (0x20) -#define WFX_EVT_STA_DO_DHCP (0x40) -#define WFX_EVT_STA_DHCP_DONE (0x80) +typedef enum +{ + WFX_EVT_STA_CONN, + WFX_EVT_STA_DISCONN, + WFX_EVT_AP_START, + WFX_EVT_AP_STOP, + WFX_EVT_SCAN, /* This is used as scan result and start */ + WFX_EVT_STA_START_JOIN, + WFX_EVT_STA_DO_DHCP, + WFX_EVT_STA_DHCP_DONE, + WFX_EVT_DHCP_POLL +} WfxEventType_e; + +typedef enum +{ + WFX_RSI_ST_DEV_READY = (1 << 0), + WFX_RSI_ST_AP_READY = (1 << 1), + WFX_RSI_ST_STA_PROVISIONED = (1 << 2), + WFX_RSI_ST_STA_CONNECTING = (1 << 3), + WFX_RSI_ST_STA_CONNECTED = (1 << 4), + WFX_RSI_ST_STA_DHCP_DONE = (1 << 6), /* Requested to do DHCP after conn */ + WFX_RSI_ST_STA_MODE = (1 << 7), /* Enable Station Mode */ + WFX_RSI_ST_AP_MODE = (1 << 8), /* Enable AP Mode */ + WFX_RSI_ST_STA_READY = (WFX_RSI_ST_STA_CONNECTED | WFX_RSI_ST_STA_DHCP_DONE), + WFX_RSI_ST_STARTED = (1 << 9), /* RSI task started */ + WFX_RSI_ST_SCANSTARTED = (1 << 10), /* Scan Started */ + WFX_RSI_ST_SLEEP_READY = (1 << 11) /* Notify the M4 to go to sleep*/ +} WfxStateType_e; + +typedef struct WfxEvent_s +{ + WfxEventType_e eventType; + void * eventData; // event data TODO: confirm needed +} WfxEvent_t; -#define WFX_RSI_ST_DEV_READY (0x01) -#define WFX_RSI_ST_AP_READY (0x02) -#define WFX_RSI_ST_STA_PROVISIONED (0x04) -#define WFX_RSI_ST_STA_CONNECTING (0x08) -#define WFX_RSI_ST_STA_CONNECTED (0x10) -#define WFX_RSI_ST_STA_DHCP_DONE (0x40) /* Requested to do DHCP after conn */ -#define WFX_RSI_ST_STA_MODE (0x80) /* Enable Station Mode */ -#define WFX_RSI_ST_AP_MODE (0x100) /* Enable AP Mode */ -#define WFX_RSI_ST_STA_READY (WFX_RSI_ST_STA_CONNECTED | WFX_RSI_ST_STA_DHCP_DONE) -#define WFX_RSI_ST_STARTED (0x200) /* RSI task started */ -#define WFX_RSI_ST_SCANSTARTED (0x400) /* Scan Started */ -#define WFX_RSI_ST_SLEEP_READY (0x800) /* Notify the M4 to go to sleep*/ +/// WfxPostEvent +/// @brief Allows to allocate an event to the WFX task event queue from outside of sl_wifi_if.c +/// @param event The event that will be allocated to the event queue +void WfxPostEvent(WfxEvent_t * event); -struct wfx_rsi +typedef struct wfx_rsi_s { + // TODO: Change tp WfxEventType_e once the event queue is implemented EventGroupHandle_t events; TaskHandle_t drv_task; TaskHandle_t wlan_task; @@ -77,9 +94,9 @@ struct wfx_rsi sl_wfx_mac_address_t ap_bssid; /* To which our STA is connected */ uint16_t join_retries; uint8_t ip4_addr[4]; /* Not sure if this is enough */ -}; +} WfxRsi_t; -extern struct wfx_rsi wfx_rsi; +extern WfxRsi_t wfx_rsi; #ifdef __cplusplus extern "C" { #endif diff --git a/examples/platform/silabs/SiWx917/SiWx917/wfx_rsi_host.c b/examples/platform/silabs/SiWx917/SiWx917/wfx_rsi_host.c index 85525fc1bc6dd0..1711076e46bd1c 100644 --- a/examples/platform/silabs/SiWx917/SiWx917/wfx_rsi_host.c +++ b/examples/platform/silabs/SiWx917/SiWx917/wfx_rsi_host.c @@ -176,10 +176,12 @@ void wfx_clear_wifi_provision(void) ****************************************************************************/ sl_status_t wfx_connect_to_ap(void) { + WfxEvent_t event; if (wfx_rsi.dev_state & WFX_RSI_ST_STA_PROVISIONED) { SILABS_LOG("%s: connecting to access point -> SSID: %s", __func__, &wfx_rsi.sec.ssid[0]); - xEventGroupSetBits(wfx_rsi.events, WFX_EVT_STA_START_JOIN); + event.eventType = WFX_EVT_STA_START_JOIN; + WfxPostEvent(&event); } else { @@ -385,7 +387,7 @@ int32_t wfx_reset_counts() bool wfx_start_scan(char * ssid, void (*callback)(wfx_wifi_scan_result_t *)) { int sz; - + WfxEvent_t event; if (wfx_rsi.scan_cb) return false; /* Already in progress */ if (ssid) @@ -398,7 +400,9 @@ bool wfx_start_scan(char * ssid, void (*callback)(wfx_wifi_scan_result_t *)) strcpy(wfx_rsi.scan_ssid, ssid); } wfx_rsi.scan_cb = callback; - xEventGroupSetBits(wfx_rsi.events, WFX_EVT_SCAN); + + event.eventType = WFX_EVT_SCAN; + WfxPostEvent(&event); return true; } diff --git a/examples/platform/silabs/SilabsDeviceAttestationCreds.cpp b/examples/platform/silabs/SilabsDeviceAttestationCreds.cpp index d442f520debcbc..bc69573c13286b 100644 --- a/examples/platform/silabs/SilabsDeviceAttestationCreds.cpp +++ b/examples/platform/silabs/SilabsDeviceAttestationCreds.cpp @@ -101,7 +101,7 @@ class DeviceAttestationCredsSilabs : public DeviceAttestationCredentialsProvider if (SilabsConfig::ConfigValueExists(SilabsConfig::kConfigKey_Creds_KeyId)) { // Provisioned DAC key -#ifdef SIWX_917 +#ifdef SLI_SI91X_MCU_INTERFACE return CHIP_ERROR_NOT_IMPLEMENTED; #else uint32_t key_id = SILABS_CREDENTIALS_DAC_KEY_ID; diff --git a/examples/platform/silabs/SoftwareFaultReports.cpp b/examples/platform/silabs/SoftwareFaultReports.cpp index 0b730576807628..fd9be7f7b9f02b 100644 --- a/examples/platform/silabs/SoftwareFaultReports.cpp +++ b/examples/platform/silabs/SoftwareFaultReports.cpp @@ -82,7 +82,7 @@ void OnSoftwareFaultEventHandler(const char * faultRecordString) /** * Log register contents to UART when a hard fault occurs. */ -extern "C" void debugHardfault(uint32_t * sp) +extern "C" __attribute__((used)) void debugHardfault(uint32_t * sp) { #if SILABS_LOG_ENABLED uint32_t cfsr = SCB->CFSR; diff --git a/examples/platform/silabs/display/demo-ui.c b/examples/platform/silabs/display/demo-ui.c index bccde3e3eaa449..479af92fd5bb90 100644 --- a/examples/platform/silabs/display/demo-ui.c +++ b/examples/platform/silabs/display/demo-ui.c @@ -25,9 +25,9 @@ #include "glib.h" #include "sl_component_catalog.h" #include "sl_memlcd.h" -#if SL_WIFI && !SIWX_917 +#if SL_WIFI && !SLI_SI91X_MCU_INTERFACE #include "spi_multiplex.h" -#endif // SL_WIFI && !SIWX_917 +#endif // SL_WIFI && !SLI_SI91X_MCU_INTERFACE #include #include @@ -106,9 +106,9 @@ void demoUIInit(GLIB_Context_t * context) sl_status_t updateDisplay(void) { sl_status_t status = SL_STATUS_OK; -#if SIWX_917 && SL_ICD_ENABLED && DISPLAY_ENABLED +#if SLI_SI91X_MCU_INTERFACE && SL_ICD_ENABLED && DISPLAY_ENABLED sl_memlcd_post_wakeup_init(); -#endif // SIWX_917 && SL_ICD_ENABLED && DISPLAY_ENABLED +#endif // SLI_SI91X_MCU_INTERFACE && SL_ICD_ENABLED && DISPLAY_ENABLED #if SL_LCDCTRL_MUX status = sl_wfx_host_pre_lcd_spi_transfer(); if (status != SL_STATUS_OK) diff --git a/examples/platform/silabs/display/lcd.cpp b/examples/platform/silabs/display/lcd.cpp index 43a134f923e24c..bf40cd0f6d543b 100644 --- a/examples/platform/silabs/display/lcd.cpp +++ b/examples/platform/silabs/display/lcd.cpp @@ -25,7 +25,7 @@ #include "dmd.h" #include "glib.h" -#if (SIWX_917) +#if (SLI_SI91X_MCU_INTERFACE) #include "rsi_chip.h" #endif @@ -66,7 +66,7 @@ CHIP_ERROR SilabsLCD::Init(uint8_t * name, bool initialState) } /* Enable the memory lcd */ -#if (SIWX_917) +#if (SLI_SI91X_MCU_INTERFACE) RSI_NPSSGPIO_InputBufferEn(SL_BOARD_ENABLE_DISPLAY_PIN, 1U); RSI_NPSSGPIO_SetPinMux(SL_BOARD_ENABLE_DISPLAY_PIN, 0); RSI_NPSSGPIO_SetDir(SL_BOARD_ENABLE_DISPLAY_PIN, 0); diff --git a/examples/platform/silabs/efr32/BUILD.gn b/examples/platform/silabs/efr32/BUILD.gn index 02f1317f55b643..8b7b1ebf86e264 100644 --- a/examples/platform/silabs/efr32/BUILD.gn +++ b/examples/platform/silabs/efr32/BUILD.gn @@ -180,6 +180,10 @@ config("efr32-common-config") { defines += [ "HEAP_MONITORING" ] } + if (chip_enable_multi_ota_requestor) { + defines += [ "CHIP_DEVICE_CONFIG_ENABLE_MULTI_OTA_REQUESTOR=1" ] + } + ldflags = [ "-Wl,--no-warn-rwx-segment" ] } diff --git a/examples/platform/silabs/efr32/rs911x/hal/rsi_hal_mcu_interrupt.c b/examples/platform/silabs/efr32/rs911x/hal/rsi_hal_mcu_interrupt.c index f7a40551fdc5b5..079a5e0c9c32f4 100644 --- a/examples/platform/silabs/efr32/rs911x/hal/rsi_hal_mcu_interrupt.c +++ b/examples/platform/silabs/efr32/rs911x/hal/rsi_hal_mcu_interrupt.c @@ -38,7 +38,7 @@ #include "wfx_host_events.h" #include "wfx_rsi.h" -#if (SIWX_917 | EXP_BOARD) +#if (SLI_SI91X_MCU_INTERFACE | EXP_BOARD) #include "sl_board_configuration.h" #include "sl_rsi_utility.h" @@ -61,7 +61,7 @@ void rsi_gpio_irq_cb(uint8_t irqnum) { if (irqnum != SL_WFX_HOST_PINOUT_SPI_IRQ) return; -#if (SIWX_917 | EXP_BOARD) +#if (SLI_SI91X_MCU_INTERFACE | EXP_BOARD) sl_si91x_host_set_bus_event(NCP_HOST_BUS_RX_EVENT); #else GPIO_IntClear(1 << SL_WFX_HOST_PINOUT_SPI_IRQ); diff --git a/examples/platform/silabs/efr32/rs911x/rsi_if.c b/examples/platform/silabs/efr32/rs911x/rsi_if.c index 1ba06d0e715c16..00efeee09a4efb 100644 --- a/examples/platform/silabs/efr32/rs911x/rsi_if.c +++ b/examples/platform/silabs/efr32/rs911x/rsi_if.c @@ -88,7 +88,7 @@ extern rsi_semaphore_handle_t sl_rs_ble_init_sem; * This file implements the interface to the RSI SAPIs */ static uint8_t wfx_rsi_drv_buf[WFX_RSI_BUF_SZ]; -static wfx_wifi_scan_ext_t * temp_reset; +static wfx_wifi_scan_ext_t temp_reset; /****************************************************************** * @fn int32_t wfx_rsi_get_ap_info(wfx_wifi_scan_result_t *ap) @@ -133,13 +133,13 @@ int32_t wfx_rsi_get_ap_ext(wfx_wifi_scan_ext_t * extra_info) else { rsi_wlan_ext_stats_t * test = (rsi_wlan_ext_stats_t *) buff; - extra_info->beacon_lost_count = test->beacon_lost_count - temp_reset->beacon_lost_count; - extra_info->beacon_rx_count = test->beacon_rx_count - temp_reset->beacon_rx_count; - extra_info->mcast_rx_count = test->mcast_rx_count - temp_reset->mcast_rx_count; - extra_info->mcast_tx_count = test->mcast_tx_count - temp_reset->mcast_tx_count; - extra_info->ucast_rx_count = test->ucast_rx_count - temp_reset->ucast_rx_count; - extra_info->ucast_tx_count = test->ucast_tx_count - temp_reset->ucast_tx_count; - extra_info->overrun_count = test->overrun_count - temp_reset->overrun_count; + extra_info->beacon_lost_count = test->beacon_lost_count - temp_reset.beacon_lost_count; + extra_info->beacon_rx_count = test->beacon_rx_count - temp_reset.beacon_rx_count; + extra_info->mcast_rx_count = test->mcast_rx_count - temp_reset.mcast_rx_count; + extra_info->mcast_tx_count = test->mcast_tx_count - temp_reset.mcast_tx_count; + extra_info->ucast_rx_count = test->ucast_rx_count - temp_reset.ucast_rx_count; + extra_info->ucast_tx_count = test->ucast_tx_count - temp_reset.ucast_tx_count; + extra_info->overrun_count = test->overrun_count - temp_reset.overrun_count; } return status; } @@ -163,14 +163,14 @@ int32_t wfx_rsi_reset_count() } else { - rsi_wlan_ext_stats_t * test = (rsi_wlan_ext_stats_t *) buff; - temp_reset->beacon_lost_count = test->beacon_lost_count; - temp_reset->beacon_rx_count = test->beacon_rx_count; - temp_reset->mcast_rx_count = test->mcast_rx_count; - temp_reset->mcast_tx_count = test->mcast_tx_count; - temp_reset->ucast_rx_count = test->ucast_rx_count; - temp_reset->ucast_tx_count = test->ucast_tx_count; - temp_reset->overrun_count = test->overrun_count; + rsi_wlan_ext_stats_t * test = (rsi_wlan_ext_stats_t *) buff; + temp_reset.beacon_lost_count = test->beacon_lost_count; + temp_reset.beacon_rx_count = test->beacon_rx_count; + temp_reset.mcast_rx_count = test->mcast_rx_count; + temp_reset.mcast_tx_count = test->mcast_tx_count; + temp_reset.ucast_rx_count = test->ucast_rx_count; + temp_reset.ucast_tx_count = test->ucast_tx_count; + temp_reset.overrun_count = test->overrun_count; } return status; } @@ -236,8 +236,6 @@ static void wfx_rsi_join_cb(uint16_t status, const uint8_t * buf, const uint16_t { SILABS_LOG("%s: status: %02x", __func__, status); wfx_rsi.dev_state &= ~WFX_RSI_ST_STA_CONNECTING; - temp_reset = (wfx_wifi_scan_ext_t *) malloc(sizeof(wfx_wifi_scan_ext_t)); - memset(temp_reset, 0, sizeof(wfx_wifi_scan_ext_t)); if (status != RSI_SUCCESS) { /* @@ -253,6 +251,7 @@ static void wfx_rsi_join_cb(uint16_t status, const uint8_t * buf, const uint16_t /* * Join was complete - Do the DHCP */ + memset(&temp_reset, 0, sizeof(wfx_wifi_scan_ext_t)); SILABS_LOG("%s: join completed.", __func__); xEventGroupSetBits(wfx_rsi.events, WFX_EVT_STA_CONN); wfx_rsi.join_retries = 0; @@ -867,4 +866,4 @@ int32_t wfx_rsi_send_data(void * p, uint16_t len) return status; } -struct wfx_rsi wfx_rsi; +WfxRsi_t wfx_rsi; diff --git a/examples/platform/silabs/efr32/rs911x/sl_wlan_config.h b/examples/platform/silabs/efr32/rs911x/sl_wlan_config.h index c8eef5c60e830e..2ce2060cd7deeb 100644 --- a/examples/platform/silabs/efr32/rs911x/sl_wlan_config.h +++ b/examples/platform/silabs/efr32/rs911x/sl_wlan_config.h @@ -1,109 +1,109 @@ -/* - * - * Copyright (c) 2022 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. - */ - -#ifndef RSI_CONFIG_H -#define RSI_CONFIG_H - -#include "ble_config.h" -#include "sl_wifi_device.h" - -//! Enable feature -#define RSI_ENABLE 1 -//! Disable feature -#define RSI_DISABLE 0 - -static const sl_wifi_device_configuration_t config = { - .boot_option = LOAD_NWP_FW, - .mac_address = NULL, - .band = SL_SI91X_WIFI_BAND_2_4GHZ, - .region_code = US, - .boot_config = { .oper_mode = SL_SI91X_CLIENT_MODE, - .coex_mode = SL_SI91X_WLAN_BLE_MODE, - .feature_bit_map = -#ifdef SLI_SI91X_MCU_INTERFACE - (SL_SI91X_FEAT_SECURITY_OPEN | SL_SI91X_FEAT_WPS_DISABLE), -#else - (SL_SI91X_FEAT_SECURITY_OPEN | SL_SI91X_FEAT_AGGREGATION), -#endif - .tcp_ip_feature_bit_map = (SL_SI91X_TCP_IP_FEAT_DHCPV4_CLIENT | SL_SI91X_TCP_IP_FEAT_DNS_CLIENT | - SL_SI91X_TCP_IP_FEAT_SSL | SL_SI91X_TCP_IP_FEAT_BYPASS -#ifdef ipv6_FEATURE_REQUIRED - | SL_SI91X_TCP_IP_FEAT_DHCPV6_CLIENT | SL_SI91X_TCP_IP_FEAT_IPV6 -#endif - | SL_SI91X_TCP_IP_FEAT_ICMP | SL_SI91X_TCP_IP_FEAT_EXTENSION_VALID), - .custom_feature_bit_map = (SL_SI91X_CUSTOM_FEAT_EXTENTION_VALID | RSI_CUSTOM_FEATURE_BIT_MAP), - .ext_custom_feature_bit_map = ( -#ifdef SLI_SI917 - (RSI_EXT_CUSTOM_FEATURE_BIT_MAP) -#else // defaults -#ifdef SLI_SI91X_MCU_INTERFACE - (SL_SI91X_EXT_FEAT_256K_MODE | RSI_EXT_CUSTOM_FEATURE_BIT_MAP) -#else - (SL_SI91X_EXT_FEAT_384K_MODE | RSI_EXT_CUSTOM_FEATURE_BIT_MAP) -#endif -#endif - | (SL_SI91X_EXT_FEAT_BT_CUSTOM_FEAT_ENABLE) -#if (defined A2DP_POWER_SAVE_ENABLE) - | SL_SI91X_EXT_FEAT_XTAL_CLK_ENABLE(2) -#endif - ), - .bt_feature_bit_map = (RSI_BT_FEATURE_BITMAP -#if (RSI_BT_GATT_ON_CLASSIC) - | SL_SI91X_BT_ATT_OVER_CLASSIC_ACL /* to support att over classic acl link */ -#endif - ), -#ifdef RSI_PROCESS_MAX_RX_DATA - .ext_tcp_ip_feature_bit_map = - (RSI_EXT_TCPIP_FEATURE_BITMAP | SL_SI91X_CONFIG_FEAT_EXTENTION_VALID | SL_SI91X_EXT_TCP_MAX_RECV_LENGTH), -#else - .ext_tcp_ip_feature_bit_map = (RSI_EXT_TCPIP_FEATURE_BITMAP | SL_SI91X_CONFIG_FEAT_EXTENTION_VALID), -#endif - //! ENABLE_BLE_PROTOCOL in bt_feature_bit_map - .ble_feature_bit_map = - ((SL_SI91X_BLE_MAX_NBR_PERIPHERALS(RSI_BLE_MAX_NBR_PERIPHERALS) | - SL_SI91X_BLE_MAX_NBR_CENTRALS(RSI_BLE_MAX_NBR_CENTRALS) | - SL_SI91X_BLE_MAX_NBR_ATT_SERV(RSI_BLE_MAX_NBR_ATT_SERV) | - SL_SI91X_BLE_MAX_NBR_ATT_REC(RSI_BLE_MAX_NBR_ATT_REC)) | - SL_SI91X_FEAT_BLE_CUSTOM_FEAT_EXTENTION_VALID | SL_SI91X_BLE_PWR_INX(RSI_BLE_PWR_INX) | - SL_SI91X_BLE_PWR_SAVE_OPTIONS(RSI_BLE_PWR_SAVE_OPTIONS) | SL_SI91X_916_BLE_COMPATIBLE_FEAT_ENABLE -#if RSI_BLE_GATT_ASYNC_ENABLE - | SL_SI91X_BLE_GATT_ASYNC_ENABLE -#endif - ), - - .ble_ext_feature_bit_map = ((SL_SI91X_BLE_NUM_CONN_EVENTS(RSI_BLE_NUM_CONN_EVENTS) | - SL_SI91X_BLE_NUM_REC_BYTES(RSI_BLE_NUM_REC_BYTES)) -#if RSI_BLE_INDICATE_CONFIRMATION_FROM_HOST - | SL_SI91X_BLE_INDICATE_CONFIRMATION_FROM_HOST // indication response from app -#endif -#if RSI_BLE_MTU_EXCHANGE_FROM_HOST - | SL_SI91X_BLE_MTU_EXCHANGE_FROM_HOST // MTU Exchange request initiation from app -#endif -#if RSI_BLE_SET_SCAN_RESP_DATA_FROM_HOST - | (SL_SI91X_BLE_SET_SCAN_RESP_DATA_FROM_HOST) // Set SCAN Resp Data from app -#endif -#if RSI_BLE_DISABLE_CODED_PHY_FROM_HOST - | (SL_SI91X_BLE_DISABLE_CODED_PHY_FROM_HOST) // Disable Coded PHY from app -#endif -#if BLE_SIMPLE_GATT - | SL_SI91X_BLE_GATT_INIT -#endif - ), - .config_feature_bit_map = (SL_SI91X_FEAT_SLEEP_GPIO_SEL_BITMAP | RSI_CONFIG_FEATURE_BITMAP) } -}; - -#endif \ No newline at end of file +/* + * + * Copyright (c) 2022 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. + */ + +#ifndef RSI_CONFIG_H +#define RSI_CONFIG_H + +#include "ble_config.h" +#include "sl_wifi_device.h" + +//! Enable feature +#define RSI_ENABLE 1 +//! Disable feature +#define RSI_DISABLE 0 + +static const sl_wifi_device_configuration_t config = { + .boot_option = LOAD_NWP_FW, + .mac_address = NULL, + .band = SL_SI91X_WIFI_BAND_2_4GHZ, + .region_code = US, + .boot_config = { .oper_mode = SL_SI91X_CLIENT_MODE, + .coex_mode = SL_SI91X_WLAN_BLE_MODE, + .feature_bit_map = +#ifdef SLI_SI91X_MCU_INTERFACE + (SL_SI91X_FEAT_SECURITY_OPEN | SL_SI91X_FEAT_WPS_DISABLE), +#else + (SL_SI91X_FEAT_SECURITY_OPEN | SL_SI91X_FEAT_AGGREGATION), +#endif + .tcp_ip_feature_bit_map = (SL_SI91X_TCP_IP_FEAT_DHCPV4_CLIENT | SL_SI91X_TCP_IP_FEAT_DNS_CLIENT | + SL_SI91X_TCP_IP_FEAT_SSL | SL_SI91X_TCP_IP_FEAT_BYPASS +#ifdef ipv6_FEATURE_REQUIRED + | SL_SI91X_TCP_IP_FEAT_DHCPV6_CLIENT | SL_SI91X_TCP_IP_FEAT_IPV6 +#endif + | SL_SI91X_TCP_IP_FEAT_ICMP | SL_SI91X_TCP_IP_FEAT_EXTENSION_VALID), + .custom_feature_bit_map = (SL_SI91X_CUSTOM_FEAT_EXTENTION_VALID | RSI_CUSTOM_FEATURE_BIT_MAP), + .ext_custom_feature_bit_map = ( +#ifdef SLI_SI917 + (RSI_EXT_CUSTOM_FEATURE_BIT_MAP) +#else // defaults +#ifdef SLI_SI91X_MCU_INTERFACE + (SL_SI91X_EXT_FEAT_256K_MODE | RSI_EXT_CUSTOM_FEATURE_BIT_MAP) +#else + (SL_SI91X_EXT_FEAT_384K_MODE | RSI_EXT_CUSTOM_FEATURE_BIT_MAP) +#endif +#endif + | (SL_SI91X_EXT_FEAT_BT_CUSTOM_FEAT_ENABLE) +#if (defined A2DP_POWER_SAVE_ENABLE) + | SL_SI91X_EXT_FEAT_XTAL_CLK_ENABLE(2) +#endif + ), + .bt_feature_bit_map = (RSI_BT_FEATURE_BITMAP +#if (RSI_BT_GATT_ON_CLASSIC) + | SL_SI91X_BT_ATT_OVER_CLASSIC_ACL /* to support att over classic acl link */ +#endif + ), +#ifdef RSI_PROCESS_MAX_RX_DATA + .ext_tcp_ip_feature_bit_map = + (RSI_EXT_TCPIP_FEATURE_BITMAP | SL_SI91X_CONFIG_FEAT_EXTENTION_VALID | SL_SI91X_EXT_TCP_MAX_RECV_LENGTH), +#else + .ext_tcp_ip_feature_bit_map = (RSI_EXT_TCPIP_FEATURE_BITMAP | SL_SI91X_CONFIG_FEAT_EXTENTION_VALID), +#endif + //! ENABLE_BLE_PROTOCOL in bt_feature_bit_map + .ble_feature_bit_map = + ((SL_SI91X_BLE_MAX_NBR_PERIPHERALS(RSI_BLE_MAX_NBR_PERIPHERALS) | + SL_SI91X_BLE_MAX_NBR_CENTRALS(RSI_BLE_MAX_NBR_CENTRALS) | + SL_SI91X_BLE_MAX_NBR_ATT_SERV(RSI_BLE_MAX_NBR_ATT_SERV) | + SL_SI91X_BLE_MAX_NBR_ATT_REC(RSI_BLE_MAX_NBR_ATT_REC)) | + SL_SI91X_FEAT_BLE_CUSTOM_FEAT_EXTENTION_VALID | SL_SI91X_BLE_PWR_INX(RSI_BLE_PWR_INX) | + SL_SI91X_BLE_PWR_SAVE_OPTIONS(RSI_BLE_PWR_SAVE_OPTIONS) | SL_SI91X_916_BLE_COMPATIBLE_FEAT_ENABLE +#if RSI_BLE_GATT_ASYNC_ENABLE + | SL_SI91X_BLE_GATT_ASYNC_ENABLE +#endif + ), + + .ble_ext_feature_bit_map = ((SL_SI91X_BLE_NUM_CONN_EVENTS(RSI_BLE_NUM_CONN_EVENTS) | + SL_SI91X_BLE_NUM_REC_BYTES(RSI_BLE_NUM_REC_BYTES)) +#if RSI_BLE_INDICATE_CONFIRMATION_FROM_HOST + | SL_SI91X_BLE_INDICATE_CONFIRMATION_FROM_HOST // indication response from app +#endif +#if RSI_BLE_MTU_EXCHANGE_FROM_HOST + | SL_SI91X_BLE_MTU_EXCHANGE_FROM_HOST // MTU Exchange request initiation from app +#endif +#if RSI_BLE_SET_SCAN_RESP_DATA_FROM_HOST + | (SL_SI91X_BLE_SET_SCAN_RESP_DATA_FROM_HOST) // Set SCAN Resp Data from app +#endif +#if RSI_BLE_DISABLE_CODED_PHY_FROM_HOST + | (SL_SI91X_BLE_DISABLE_CODED_PHY_FROM_HOST) // Disable Coded PHY from app +#endif +#if BLE_SIMPLE_GATT + | SL_SI91X_BLE_GATT_INIT +#endif + ), + .config_feature_bit_map = (SL_SI91X_FEAT_SLEEP_GPIO_SEL_BITMAP | RSI_CONFIG_FEATURE_BITMAP) } +}; + +#endif diff --git a/examples/platform/silabs/efr32/rs911x/wfx_rsi.h b/examples/platform/silabs/efr32/rs911x/wfx_rsi.h index 07217056cef11b..86591df72d36d3 100644 --- a/examples/platform/silabs/efr32/rs911x/wfx_rsi.h +++ b/examples/platform/silabs/efr32/rs911x/wfx_rsi.h @@ -53,7 +53,7 @@ #define WFX_RSI_ST_SCANSTARTED (0x400) /* Scan Started */ #define WFX_RSI_ST_SLEEP_READY (0x800) /* Notify the M4 to go to sleep*/ -struct wfx_rsi +typedef struct wfx_rsi_s { EventGroupHandle_t events; TaskHandle_t drv_task; @@ -76,9 +76,9 @@ struct wfx_rsi sl_wfx_mac_address_t ap_bssid; /* To which our STA is connected */ uint16_t join_retries; uint8_t ip4_addr[4]; /* Not sure if this is enough */ -}; +} WfxRsi_t; -extern struct wfx_rsi wfx_rsi; +extern WfxRsi_t wfx_rsi; #ifdef __cplusplus extern "C" { #endif diff --git a/examples/platform/silabs/heap_4_silabs.c b/examples/platform/silabs/heap_4_silabs.c index a19bff034362fb..8abb295caefd2d 100644 --- a/examples/platform/silabs/heap_4_silabs.c +++ b/examples/platform/silabs/heap_4_silabs.c @@ -647,7 +647,7 @@ void * __wrap_realloc(void * ptr, size_t new_size) return pvPortRealloc(ptr, new_size); } -void * __wrap_calloc(size_t num, size_t size) +__attribute__((used)) void * __wrap_calloc(size_t num, size_t size) { return pvPortCalloc(num, size); } diff --git a/examples/rvc-app/rvc-common/pics/rvc-app-pics-values b/examples/rvc-app/rvc-common/pics/rvc-app-pics-values index 2a6adeb31e38f6..669abd26c33f94 100644 --- a/examples/rvc-app/rvc-common/pics/rvc-app-pics-values +++ b/examples/rvc-app/rvc-common/pics/rvc-app-pics-values @@ -40,6 +40,7 @@ RVCOPSTATE.S.M.ERR_WATER_TANK_EMPTY=1 RVCOPSTATE.S.M.ERR_WATER_TANK_MISSING=1 RVCOPSTATE.S.M.ERR_WATER_TANK_LID_OPEN=1 RVCOPSTATE.S.M.ERR_MOP_CLEANING_PAD_MISSING=1 +RVCOPSTATE.S.M.RESUME_AFTER_ERR=0 RVCRUNM.S=1 RVCRUNM.S.A0000=1 diff --git a/examples/thermostat/silabs/src/ThermostatUI.cpp b/examples/thermostat/silabs/src/ThermostatUI.cpp index 77184151e1d1d3..f82ee36387e99a 100644 --- a/examples/thermostat/silabs/src/ThermostatUI.cpp +++ b/examples/thermostat/silabs/src/ThermostatUI.cpp @@ -25,7 +25,7 @@ #include "glib.h" #include "lcd.h" -#if SL_WIFI && !defined(SIWX_917) +#if SL_WIFI && !defined(SLI_SI91X_MCU_INTERFACE) // Only needed for wifi NCP devices #include "spi_multiplex.h" #endif // SL_WIFI diff --git a/examples/thermostat/thermostat-common/thermostat.matter b/examples/thermostat/thermostat-common/thermostat.matter index d821e6a6d609f6..60427f9ac0f854 100644 --- a/examples/thermostat/thermostat-common/thermostat.matter +++ b/examples/thermostat/thermostat-common/thermostat.matter @@ -2097,6 +2097,7 @@ endpoint 0 { server cluster Identify { ram attribute identifyTime default = 0x0000; + ram attribute identifyType default = 0x00; ram attribute featureMap default = 0; ram attribute clusterRevision default = 4; @@ -2240,6 +2241,7 @@ endpoint 0 { ram attribute clusterRevision default = 1; handle command RetrieveLogsRequest; + handle command RetrieveLogsResponse; } server cluster GeneralDiagnostics { diff --git a/examples/thermostat/thermostat-common/thermostat.zap b/examples/thermostat/thermostat-common/thermostat.zap index 79396ba5d313c4..64c87bce7e15c6 100644 --- a/examples/thermostat/thermostat-common/thermostat.zap +++ b/examples/thermostat/thermostat-common/thermostat.zap @@ -94,6 +94,22 @@ "maxInterval": 65344, "reportableChange": 0 }, + { + "name": "IdentifyType", + "code": 1, + "mfgCode": null, + "side": "server", + "type": "IdentifyTypeEnum", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "0x00", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, { "name": "FeatureMap", "code": 65532, @@ -1707,6 +1723,14 @@ "source": "client", "isIncoming": 1, "isEnabled": 1 + }, + { + "name": "RetrieveLogsResponse", + "code": 1, + "mfgCode": null, + "source": "server", + "isIncoming": 0, + "isEnabled": 1 } ], "attributes": [ diff --git a/examples/tv-app/android/java/MediaPlaybackManager.cpp b/examples/tv-app/android/java/MediaPlaybackManager.cpp index 667ab2ec41b382..99afaaa02c942b 100644 --- a/examples/tv-app/android/java/MediaPlaybackManager.cpp +++ b/examples/tv-app/android/java/MediaPlaybackManager.cpp @@ -152,7 +152,7 @@ CHIP_ERROR MediaPlaybackManager::HandleGetActiveTrack(bool audio, AttributeValue } else { - err = aEncoder.EncodeNull(); + return aEncoder.EncodeNull(); } exit: diff --git a/examples/tv-app/android/java/src/com/matter/tv/server/tvapp/MediaPlaybackManagerStub.java b/examples/tv-app/android/java/src/com/matter/tv/server/tvapp/MediaPlaybackManagerStub.java index a6b9afcc603338..e72d544f153f21 100755 --- a/examples/tv-app/android/java/src/com/matter/tv/server/tvapp/MediaPlaybackManagerStub.java +++ b/examples/tv-app/android/java/src/com/matter/tv/server/tvapp/MediaPlaybackManagerStub.java @@ -35,11 +35,11 @@ public class MediaPlaybackManagerStub implements MediaPlaybackManager { private static int playbackMaxForwardSpeed = 10; private static int playbackMaxRewindSpeed = -10; - private static MediaTrack activeAudioTrack = null; private static MediaTrack[] audioTracks = { new MediaTrack("activeAudioTrackId_0", "languageCode1", "displayName1"), new MediaTrack("activeAudioTrackId_1", "languageCode2", "displayName2") }; + private static MediaTrack activeAudioTrack = audioTracks[0]; private static MediaTrack activeTextTrack = null; private static MediaTrack[] textTracks = { diff --git a/examples/tv-casting-app/APIs.md b/examples/tv-casting-app/APIs.md index f254df1d7e2d14..d4a51dcc3146bf 100644 --- a/examples/tv-casting-app/APIs.md +++ b/examples/tv-casting-app/APIs.md @@ -34,15 +34,11 @@ samples so you can see the experience end to end. A Casting Client (e.g. a mobile phone app) is expected to be a Matter Commissionable Node and a `CastingPlayer` (i.e. a TV) is expected to be a Matter -Commissioner. In the context of the -[Matter Video Player architecture](https://github.com/CHIP-Specifications/connectedhomeip-spec/blob/master/src/app_clusters/media/VideoPlayerArchitecture.adoc), -a `CastingPlayer` would map to -[Casting "Video" Player](https://github.com/CHIP-Specifications/connectedhomeip-spec/blob/master/src/app_clusters/media/VideoPlayerArchitecture.adoc#1-introduction). -The `CastingPlayer` is expected to be hosting one or more `Endpoints` (some of -which can represent -[Content Apps](https://github.com/CHIP-Specifications/connectedhomeip-spec/blob/master/src/app_clusters/media/VideoPlayerArchitecture.adoc#1-introduction) -in the Matter Video Player architecture) that support one or more Matter Media -`Clusters`. +Commissioner. In the context of the Matter Video Player architecture, a +`CastingPlayer` would map to Casting "Video" Player. The `CastingPlayer` is +expected to be hosting one or more `Endpoints` (some of which can represent +Content Apps in the Matter Video Player architecture) that support one or more +Matter Media `Clusters`. The steps to start a casting session are: @@ -80,6 +76,8 @@ consume each platform's specific libraries. The libraries MUST be built with the client's specific values for `CHIP_DEVICE_CONFIG_DEVICE_VENDOR_ID` and `CHIP_DEVICE_CONFIG_DEVICE_PRODUCT_ID` updated in the [CHIPProjectAppConfig.h](tv-casting-common/include/CHIPProjectAppConfig.h) file. +Other values like the `CHIP_DEVICE_CONFIG_DEVICE_NAME` may be updated as well to +correspond to the client being built. ### Initialize the Casting Client @@ -91,10 +89,10 @@ A Casting Client must first initialize the Matter SDK and define the following `DataProvider` objects for the the Matter Casting library to use throughout the client's lifecycle: -1. **Rotating Device Identifier** - Refer to the Matter specification for - details on how to generate the - [Rotating Device Identifier](https://github.com/CHIP-Specifications/connectedhomeip-spec/blob/master/src/rendezvous/DeviceDiscovery.adoc#245-rotating-device-identifier)). - Then, instantiate a `DataProvider` object as described below. +1. **Rotating Device Identifier** - "This unique per-device identifier SHALL + consist of a randomly-generated 128-bit or longer octet string." Refer to + the Matter specification for more details. Instantiate a `DataProvider` + object as described below to provide this identifier. On Linux, define a `RotatingDeviceIdUniqueIdProvider` to provide the Casting Client's `RotatingDeviceIdUniqueId`, by implementing a @@ -152,10 +150,13 @@ client's lifecycle: ``` 2. **Commissioning Data** - This object contains the passcode, discriminator, - etc which identify the app and are provided to the `CastingPlayer` during - the commissioning process. Refer to the Matter specification's - [Onboarding Payload](https://github.com/CHIP-Specifications/connectedhomeip-spec/blob/master/src/qr_code/OnboardingPayload.adoc#ref_OnboardingPayload) - section for details on commissioning data. + etc. which identify the app and are provided to the `CastingPlayer` during + the commissioning process. "A Passcode SHALL be included as a 27-bit + unsigned integer, which serves as proof of possession during commissioning." + "A Discriminator SHALL be included as a 12-bit unsigned integer, which SHALL + match the value which a device advertises during commissioning." Refer to + the Matter specification's "Onboarding Payload" section for more details on + commissioning data. On Linux, define a function `InitCommissionableDataProvider` to initialize initialize a `LinuxCommissionableDataProvider` that can provide the required @@ -217,9 +218,8 @@ client's lifecycle: 3. **Device Attestation Credentials** - This object contains the `DeviceAttestationCertificate`, `ProductAttestationIntermediateCertificate`, etc. and implements a way to sign messages when called upon by the Matter TV - Casting Library as part of the - [Device Attestation process](https://github.com/CHIP-Specifications/connectedhomeip-spec/blob/master/src/device_attestation/Device_Attestation_Specification.adoc) - during commissioning. + Casting Library as part of the Matter Device Attestation process during + commissioning. On Linux, implement a define a `dacProvider` to provide the Casting Client's Device Attestation Credentials, by implementing a @@ -487,8 +487,8 @@ potentially skipping the longer commissioning process and instead, simply re-establishing the CASE session. This cache can be cleared by calling the `ClearCache` API on the `CastingApp`, say when the user signs out of the app. See API and its documentation for [Linux](tv-casting-common/core/CastingApp.h), -Android and -[iOS](darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCCastingApp.h). +[Android](android/App/app/src/main/jni/com/matter/casting/core/CastingApp.java) +and [iOS](darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCCastingApp.h). ### Discover Casting Players @@ -702,10 +702,9 @@ Each `CastingPlayer` object created during [Discovery](#discover-casting-players) contains information such as `deviceName`, `vendorId`, `productId`, etc. which can help the user pick the right `CastingPlayer`. A Casting Client can attempt to connect to the -`selectedCastingPlayer` using -[Matter User Directed Commissioning (UDC)](https://github.com/CHIP-Specifications/connectedhomeip-spec/blob/master/src/rendezvous/UserDirectedCommissioning.adoc). -The Matter TV Casting library locally caches information required to reconnect -to a `CastingPlayer`, once the Casting client has been commissioned by it. After +`selectedCastingPlayer` using Matter User Directed Commissioning (UDC). The +Matter TV Casting library locally caches information required to reconnect to a +`CastingPlayer`, once the Casting client has been commissioned by it. After that, the Casting client is able to skip the full UDC process by establishing CASE with the `CastingPlayer` directly. Once connected, the `CastingPlayer` object will contain the list of available Endpoints on that `CastingPlayer`. @@ -743,6 +742,60 @@ targetCastingPlayer->VerifyOrEstablishConnection(ConnectionHandler, ... ``` +On Android, the Casting Client may call `verifyOrEstablishConnection` on the +`CastingPlayer` object it wants to connect to. + +```java +private static final long MIN_CONNECTION_TIMEOUT_SEC = 3 * 60; + +EndpointFilter desiredEndpointFilter = new EndpointFilter(); +desiredEndpointFilter.vendorId = DESIRED_ENDPOINT_VENDOR_ID; + +MatterError err = targetCastingPlayer.verifyOrEstablishConnection( + MIN_CONNECTION_TIMEOUT_SEC, + desiredEndpointFilter, + new MatterCallback() { + @Override + public void handle(Void v) { + Log.i( + TAG, + "Connected to CastingPlayer with deviceId: " + + targetCastingPlayer.getDeviceId()); + getActivity() + .runOnUiThread( + () -> { + connectionFragmentStatusTextView.setText( + "Connected to Casting Player with device name: " + + targetCastingPlayer.getDeviceName() + + "\n\n"); + connectionFragmentNextButton.setEnabled(true); + }); + } + }, + new MatterCallback() { + @Override + public void handle(MatterError err) { + Log.e(TAG, "CastingPLayer connection failed: " + err); + getActivity() + .runOnUiThread( + () -> { + connectionFragmentStatusTextView.setText( + "Casting Player connection failed due to: " + err + "\n\n"); + }); + } + }); + +if (err.hasError()) +{ + getActivity() + .runOnUiThread( + () -> { + connectionFragmentStatusTextView.setText( + "Casting Player connection failed due to: " + err + "\n\n"); + }); +} +``` + On iOS, the Casting Client may call `verifyOrEstablishConnection` on the `MCCastingPlayer` object it wants to connect to and handle any `NSErrors` that may happen in the process. @@ -777,6 +830,8 @@ func connect(selectedCastingPlayer: MCCastingPlayer?) { ### Select an Endpoint on the Casting Player _{Complete Endpoint selection examples: [Linux](linux/simple-app-helper.cpp) | +[Android](android/App/app/src/main/java/com/matter/casting/ContentLauncherLaunchURLExampleFragment.java) +| [iOS](darwin/TvCasting/TvCasting/MCContentLauncherLaunchURLExampleViewModel.swift)}_ On a successful connection with a `CastingPlayer`, a Casting Client may select @@ -803,6 +858,34 @@ if (it != endpoints.end()) } ``` +On Android, it can select an `Endpoint` as shown below. + +```java +private static final Integer SAMPLE_ENDPOINT_VID = 65521; + +private Endpoint selectEndpoint() +{ + Endpoint endpoint = null; + if(selectedCastingPlayer != null) + { + List endpoints = selectedCastingPlayer.getEndpoints(); + if (endpoints == null) + { + Log.e(TAG, "No Endpoints found on CastingPlayer"); + } + else + { + endpoint = endpoints + .stream() + .filter(e -> SAMPLE_ENDPOINT_VID.equals(e.getVendorId())) + .findFirst() + .get(); + } + } + return endpoint; +} +``` + On iOS, it can select an `MCEndpoint` similarly and as shown below. ```swift @@ -1045,11 +1128,8 @@ vendorIDAttribute!.read(nil) { context, before, after, err in ### Subscriptions -_{Complete Attribute subscription examples: -[Linux](linux/simple-app-helper.cpp)}_ - -_{Complete Attribute Read examples: [Linux](linux/simple-app-helper.cpp) | -[iOS](darwin/TvCasting/TvCasting/MCMediaPlaybackSubscribeToCurrentStateExampleViewModel.swift)}_ +_{Complete Attribute subscription examples: [Linux](linux/simple-app-helper.cpp) +|[iOS](darwin/TvCasting/TvCasting/MCMediaPlaybackSubscribeToCurrentStateExampleViewModel.swift)}_ A Casting Client may subscribe to an attribute on an `Endpoint` of the `CastingPlayer` to get data reports when the attributes change. diff --git a/examples/tv-casting-app/android/App/app/src/main/java/com/chip/casting/app/MainActivity.java b/examples/tv-casting-app/android/App/app/src/main/java/com/chip/casting/app/MainActivity.java index 9db82781d855b8..6e54ff61b01c7e 100644 --- a/examples/tv-casting-app/android/App/app/src/main/java/com/chip/casting/app/MainActivity.java +++ b/examples/tv-casting-app/android/App/app/src/main/java/com/chip/casting/app/MainActivity.java @@ -10,10 +10,12 @@ import com.chip.casting.DiscoveredNodeData; import com.chip.casting.TvCastingApp; import com.chip.casting.util.GlobalCastingConstants; -import com.chip.casting.util.PreferencesConfigurationManager; +import com.matter.casting.ActionSelectorFragment; import com.matter.casting.ConnectionExampleFragment; +import com.matter.casting.ContentLauncherLaunchURLExampleFragment; import com.matter.casting.DiscoveryExampleFragment; import com.matter.casting.InitializationExample; +import com.matter.casting.PreferencesConfigurationManager; import com.matter.casting.core.CastingPlayer; import java.util.Random; @@ -22,7 +24,8 @@ public class MainActivity extends AppCompatActivity ConnectionFragment.Callback, SelectClusterFragment.Callback, DiscoveryExampleFragment.Callback, - ConnectionExampleFragment.Callback { + ConnectionExampleFragment.Callback, + ActionSelectorFragment.Callback { private static final String TAG = MainActivity.class.getSimpleName(); @@ -33,6 +36,7 @@ protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); + Log.i(TAG, "ChipCastingSimplified = " + GlobalCastingConstants.ChipCastingSimplified); boolean ret = GlobalCastingConstants.ChipCastingSimplified ? InitializationExample.initAndStart(this.getApplicationContext()).hasNoError() @@ -73,9 +77,12 @@ public void handleCommissioningComplete() { @Override public void handleConnectionComplete(CastingPlayer castingPlayer) { Log.i(TAG, "MainActivity.handleConnectionComplete() called "); + showFragment(ActionSelectorFragment.newInstance(castingPlayer)); + } - // TODO: Implement in following PRs. Select Cluster Fragment. - // showFragment(SelectClusterFragment.newInstance(tvCastingApp)); + @Override + public void handleContentLauncherLaunchURLSelected(CastingPlayer selectedCastingPlayer) { + showFragment(ContentLauncherLaunchURLExampleFragment.newInstance(selectedCastingPlayer)); } @Override @@ -95,7 +102,10 @@ public void handleMediaPlaybackSelected() { @Override public void handleDisconnect() { - showFragment(CommissionerDiscoveryFragment.newInstance(tvCastingApp)); + showFragment( + GlobalCastingConstants.ChipCastingSimplified + ? DiscoveryExampleFragment.newInstance() + : CommissionerDiscoveryFragment.newInstance(tvCastingApp)); } /** diff --git a/examples/tv-casting-app/android/App/app/src/main/java/com/chip/casting/util/PreferencesConfigurationManager.java b/examples/tv-casting-app/android/App/app/src/main/java/com/chip/casting/util/PreferencesConfigurationManager.java deleted file mode 100644 index 5f7cb1cb7a2a16..00000000000000 --- a/examples/tv-casting-app/android/App/app/src/main/java/com/chip/casting/util/PreferencesConfigurationManager.java +++ /dev/null @@ -1,153 +0,0 @@ -/* - * Copyright (c) 2021-2022 Project CHIP Authors - * All rights reserved. - * - * 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. - * - */ -package com.chip.casting.util; - -import android.content.Context; -import android.content.SharedPreferences; -import android.util.Log; -import chip.platform.AndroidChipPlatformException; -import chip.platform.ConfigurationManager; -import chip.platform.KeyValueStoreManager; -import java.util.Base64; -import java.util.Map; -import java.util.UUID; - -/** Java interface for ConfigurationManager */ -public class PreferencesConfigurationManager implements ConfigurationManager { - - private final String TAG = KeyValueStoreManager.class.getSimpleName(); - private SharedPreferences preferences; - - public PreferencesConfigurationManager(Context context, String preferenceFileKey) { - preferences = context.getSharedPreferences(preferenceFileKey, Context.MODE_PRIVATE); - - try { - String keyUniqueId = getKey(kConfigNamespace_ChipFactory, kConfigKey_UniqueId); - if (!preferences.contains(keyUniqueId)) { - preferences - .edit() - .putString(keyUniqueId, UUID.randomUUID().toString().replaceAll("-", "")) - .apply(); - } - } catch (AndroidChipPlatformException e) { - e.printStackTrace(); - } - } - - @Override - public long readConfigValueLong(String namespace, String name) - throws AndroidChipPlatformException { - String key = getKey(namespace, name); - if (preferences.contains(key)) { - long value = preferences.getLong(key, Long.MAX_VALUE); - return value; - } else { - Log.d(TAG, "Key '" + key + "' not found in shared preferences"); - throw new AndroidChipPlatformException(); - } - } - - @Override - public String readConfigValueStr(String namespace, String name) - throws AndroidChipPlatformException { - String key = getKey(namespace, name); - if (preferences.contains(key)) { - String value = preferences.getString(key, null); - return value; - } else { - Log.d(TAG, "Key '" + key + "' not found in shared preferences"); - throw new AndroidChipPlatformException(); - } - } - - @Override - public byte[] readConfigValueBin(String namespace, String name) - throws AndroidChipPlatformException { - String key = getKey(namespace, name); - if (preferences.contains(key)) { - String value = preferences.getString(key, null); - byte[] byteValue = Base64.getDecoder().decode(value); - return byteValue; - } else { - Log.d(TAG, "Key '" + key + "' not found in shared preferences"); - throw new AndroidChipPlatformException(); - } - } - - @Override - public void writeConfigValueLong(String namespace, String name, long val) - throws AndroidChipPlatformException { - String key = getKey(namespace, name); - preferences.edit().putLong(key, val).apply(); - } - - @Override - public void writeConfigValueStr(String namespace, String name, String val) - throws AndroidChipPlatformException { - String key = getKey(namespace, name); - preferences.edit().putString(key, val).apply(); - } - - @Override - public void writeConfigValueBin(String namespace, String name, byte[] val) - throws AndroidChipPlatformException { - String key = getKey(namespace, name); - if (val != null) { - String valStr = Base64.getEncoder().encodeToString(val); - preferences.edit().putString(key, valStr).apply(); - } else { - preferences.edit().remove(key).apply(); - } - } - - @Override - public void clearConfigValue(String namespace, String name) throws AndroidChipPlatformException { - if (namespace != null && name != null) { - preferences.edit().remove(getKey(namespace, name)).apply(); - } else if (namespace != null && name == null) { - String pre = getKey(namespace, null); - SharedPreferences.Editor editor = preferences.edit(); - Map allEntries = preferences.getAll(); - for (Map.Entry entry : allEntries.entrySet()) { - String key = entry.getKey(); - if (key.startsWith(pre)) { - editor.remove(key); - } - } - editor.apply(); - } else if (namespace == null && name == null) { - preferences.edit().clear().apply(); - } - } - - @Override - public boolean configValueExists(String namespace, String name) - throws AndroidChipPlatformException { - return preferences.contains(getKey(namespace, name)); - } - - private String getKey(String namespace, String name) throws AndroidChipPlatformException { - if (namespace != null && name != null) { - return namespace + ":" + name; - } else if (namespace != null && name == null) { - return namespace + ":"; - } - - throw new AndroidChipPlatformException(); - } -} diff --git a/examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/ActionSelectorFragment.java b/examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/ActionSelectorFragment.java new file mode 100644 index 00000000000000..2906ad186d6054 --- /dev/null +++ b/examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/ActionSelectorFragment.java @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2024 Project CHIP Authors + * All rights reserved. + * + * 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. + */ +package com.matter.casting; + +import android.os.Bundle; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import com.R; +import com.matter.casting.core.CastingPlayer; + +/** An interstitial {@link Fragment} to select one of the supported media actions to perform */ +public class ActionSelectorFragment extends Fragment { + private static final String TAG = ActionSelectorFragment.class.getSimpleName(); + + private final CastingPlayer selectedCastingPlayer; + + private View.OnClickListener selectContentLauncherButtonClickListener; + private View.OnClickListener disconnectButtonClickListener; + + public ActionSelectorFragment(CastingPlayer selectedCastingPlayer) { + this.selectedCastingPlayer = selectedCastingPlayer; + } + + /** + * Use this factory method to create a new instance of this fragment using the provided + * parameters. + * + * @param selectedCastingPlayer CastingPlayer that the casting app connected to + * @return A new instance of fragment SelectActionFragment. + */ + public static ActionSelectorFragment newInstance(CastingPlayer selectedCastingPlayer) { + return new ActionSelectorFragment(selectedCastingPlayer); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + } + + @Override + public View onCreateView( + LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + ActionSelectorFragment.Callback callback = (ActionSelectorFragment.Callback) this.getActivity(); + this.selectContentLauncherButtonClickListener = + v -> { + Log.d(TAG, "handle() called on selectContentLauncherButtonClickListener"); + callback.handleContentLauncherLaunchURLSelected(selectedCastingPlayer); + }; + + this.disconnectButtonClickListener = + v -> { + Log.d(TAG, "Disconnecting from current casting player"); + selectedCastingPlayer.disconnect(); + callback.handleDisconnect(); + }; + + return inflater.inflate(R.layout.fragment_matter_action_selector, container, false); + } + + @Override + public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + Log.d(TAG, "ActionSelectorFragment.onViewCreated called"); + getView() + .findViewById(R.id.selectContentLauncherLaunchURLButton) + .setOnClickListener(selectContentLauncherButtonClickListener); + + getView().findViewById(R.id.disconnectButton).setOnClickListener(disconnectButtonClickListener); + } + + /** Interface for notifying the host. */ + public interface Callback { + /** Notifies listener to trigger transition on selection of Content Launcher cluster */ + void handleContentLauncherLaunchURLSelected(CastingPlayer selectedCastingPlayer); + + /** Notifies listener to trigger transition on click of the Disconnect button */ + void handleDisconnect(); + } +} diff --git a/examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/ConnectionExampleFragment.java b/examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/ConnectionExampleFragment.java index c6462cd52690e5..690a9b02e840bc 100644 --- a/examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/ConnectionExampleFragment.java +++ b/examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/ConnectionExampleFragment.java @@ -27,10 +27,9 @@ import androidx.fragment.app.Fragment; import com.R; import com.matter.casting.core.CastingPlayer; -import com.matter.casting.support.DeviceTypeStruct; import com.matter.casting.support.EndpointFilter; -import java.util.ArrayList; -import java.util.concurrent.CompletableFuture; +import com.matter.casting.support.MatterCallback; +import com.matter.casting.support.MatterError; import java.util.concurrent.Executors; /** A {@link Fragment} to Verify or establish a connection with a selected Casting Player. */ @@ -39,12 +38,16 @@ public class ConnectionExampleFragment extends Fragment { // Time (in sec) to keep the commissioning window open, if commissioning is required. // Must be >= 3 minutes. private static final long MIN_CONNECTION_TIMEOUT_SEC = 3 * 60; + private static final Integer DESIRED_ENDPOINT_VENDOR_ID = 65521; private final CastingPlayer targetCastingPlayer; private TextView connectionFragmentStatusTextView; private Button connectionFragmentNextButton; public ConnectionExampleFragment(CastingPlayer targetCastingPlayer) { - Log.i(TAG, "ConnectionExampleFragment() called with target CastingPlayer"); + Log.i( + TAG, + "ConnectionExampleFragment() called with target CastingPlayer ID: " + + targetCastingPlayer.getDeviceId()); this.targetCastingPlayer = targetCastingPlayer; } @@ -81,7 +84,11 @@ public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { connectionFragmentStatusTextView = getView().findViewById(R.id.connectionFragmentStatusText); connectionFragmentStatusTextView.setText( "Verifying or establishing connection with Casting Player with device name: " - + targetCastingPlayer.getDeviceName()); + + targetCastingPlayer.getDeviceName() + + "\nSetup Passcode: " + + InitializationExample.commissionableDataProvider.get().getSetupPasscode() + + "\nDiscriminator: " + + InitializationExample.commissionableDataProvider.get().getDiscriminator()); connectionFragmentNextButton = getView().findViewById(R.id.connectionFragmentNextButton); Callback callback = (ConnectionExampleFragment.Callback) this.getActivity(); @@ -94,48 +101,54 @@ public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { Executors.newSingleThreadExecutor() .submit( () -> { - Log.d(TAG, "onViewCreated() calling verifyOrEstablishConnection()"); + Log.d(TAG, "onViewCreated() calling CastingPlayer.verifyOrEstablishConnection()"); - EndpointFilter desiredEndpointFilter = - new EndpointFilter(null, 65521, new ArrayList()); - // The desired commissioning window timeout and EndpointFilter are optional. - CompletableFuture completableFuture = - targetCastingPlayer.VerifyOrEstablishConnection( - MIN_CONNECTION_TIMEOUT_SEC, desiredEndpointFilter); + EndpointFilter desiredEndpointFilter = new EndpointFilter(); + desiredEndpointFilter.vendorId = DESIRED_ENDPOINT_VENDOR_ID; - Log.d(TAG, "onViewCreated() verifyOrEstablishConnection() called"); - - completableFuture - .thenRun( - () -> { - Log.i( - TAG, - "CompletableFuture.thenRun(), connected to CastingPlayer with deviceId: " - + targetCastingPlayer.getDeviceId()); - getActivity() - .runOnUiThread( - () -> { - connectionFragmentStatusTextView.setText( - "Connected to Casting Player with device name: " - + targetCastingPlayer.getDeviceName()); - connectionFragmentNextButton.setEnabled(true); - }); - }) - .exceptionally( - exc -> { - Log.e( - TAG, - "CompletableFuture.exceptionally(), CastingPLayer connection failed: " - + exc.getMessage()); - getActivity() - .runOnUiThread( - () -> { - connectionFragmentStatusTextView.setText( - "Casting Player connection failed due to: " - + exc.getMessage()); - }); - return null; + MatterError err = + targetCastingPlayer.verifyOrEstablishConnection( + MIN_CONNECTION_TIMEOUT_SEC, + desiredEndpointFilter, + new MatterCallback() { + @Override + public void handle(Void v) { + Log.i( + TAG, + "Connected to CastingPlayer with deviceId: " + + targetCastingPlayer.getDeviceId()); + getActivity() + .runOnUiThread( + () -> { + connectionFragmentStatusTextView.setText( + "Connected to Casting Player with device name: " + + targetCastingPlayer.getDeviceName() + + "\n\n"); + connectionFragmentNextButton.setEnabled(true); + }); + } + }, + new MatterCallback() { + @Override + public void handle(MatterError err) { + Log.e(TAG, "CastingPlayer connection failed: " + err); + getActivity() + .runOnUiThread( + () -> { + connectionFragmentStatusTextView.setText( + "Casting Player connection failed due to: " + err + "\n\n"); + }); + } }); + + if (err.hasError()) { + getActivity() + .runOnUiThread( + () -> { + connectionFragmentStatusTextView.setText( + "Casting Player connection failed due to: " + err + "\n\n"); + }); + } }); } diff --git a/examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/ContentLauncherLaunchURLExampleFragment.java b/examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/ContentLauncherLaunchURLExampleFragment.java new file mode 100644 index 00000000000000..c1bde0f9bb0323 --- /dev/null +++ b/examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/ContentLauncherLaunchURLExampleFragment.java @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2024 Project CHIP Authors + * All rights reserved. + * + * 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. + */ +package com.matter.casting; + +import android.os.Bundle; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import com.R; +import com.matter.casting.core.CastingPlayer; +import com.matter.casting.core.Endpoint; +import java.util.List; + +/** A {@link Fragment} to send Content Launcher LaunchURL command from the TV Casting App. */ +public class ContentLauncherLaunchURLExampleFragment extends Fragment { + private static final String TAG = ContentLauncherLaunchURLExampleFragment.class.getSimpleName(); + private static final Integer SAMPLE_ENDPOINT_VID = 65521; + + private final CastingPlayer selectedCastingPlayer; + + private View.OnClickListener launchUrlButtonClickListener; + + public ContentLauncherLaunchURLExampleFragment(CastingPlayer selectedCastingPlayer) { + this.selectedCastingPlayer = selectedCastingPlayer; + } + + /** + * Use this factory method to create a new instance of this fragment using the provided + * parameters. + * + * @param selectedCastingPlayer CastingPlayer that the casting app connected to + * @return A new instance of fragment ContentLauncherLaunchURLExampleFragment. + */ + public static ContentLauncherLaunchURLExampleFragment newInstance( + CastingPlayer selectedCastingPlayer) { + return new ContentLauncherLaunchURLExampleFragment(selectedCastingPlayer); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + } + + @Override + public View onCreateView( + LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + this.launchUrlButtonClickListener = + v -> { + Endpoint endpoint = selectEndpoint(); + if (endpoint == null) { + Log.e( + TAG, + "No Endpoint with chosen vendorID: " + + SAMPLE_ENDPOINT_VID + + " found on CastingPlayer"); + return; + } + + // TODO: add command invocation API call + }; + return inflater.inflate(R.layout.fragment_content_launcher, container, false); + } + + @Override + public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + Log.d(TAG, "ContentLauncherLaunchURLExampleFragment.onViewCreated called"); + getView().findViewById(R.id.launchUrlButton).setOnClickListener(launchUrlButtonClickListener); + } + + private Endpoint selectEndpoint() { + Endpoint endpoint = null; + if (selectedCastingPlayer != null) { + List endpoints = selectedCastingPlayer.getEndpoints(); + if (endpoints == null) { + Log.e(TAG, "No Endpoints found on CastingPlayer"); + } else { + endpoint = + endpoints + .stream() + .filter(e -> SAMPLE_ENDPOINT_VID.equals(e.getVendorId())) + .findFirst() + .get(); + } + } + return endpoint; + } +} diff --git a/examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/DiscoveryExampleFragment.java b/examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/DiscoveryExampleFragment.java index 67db95be2637df..b847e17f5b7026 100644 --- a/examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/DiscoveryExampleFragment.java +++ b/examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/DiscoveryExampleFragment.java @@ -203,8 +203,13 @@ public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { @Override public void onResume() { + Log.i(TAG, "onResume() called"); super.onResume(); - Log.i(TAG, "onResume() called. Calling startDiscovery()"); + MatterError err = + matterCastingPlayerDiscovery.removeCastingPlayerChangeListener(castingPlayerChangeListener); + if (err.hasError()) { + Log.e(TAG, "onResume() removeCastingPlayerChangeListener() err: " + err); + } if (!startDiscovery()) { Log.e(TAG, "onResume() Warning: startDiscovery() call Failed"); } @@ -253,13 +258,9 @@ private boolean startDiscovery() { matterDiscoveryMessageTextView.setText( getString(R.string.matter_discovery_message_discovering_text)); - Log.d( - TAG, - "startDiscovery() text set to: " - + getString(R.string.matter_discovery_message_discovering_text)); // TODO: In following PRs. Enable this to auto-stop discovery after stopDiscovery is - // implemented in the core Matter SKD DNS-SD API. + // implemented in the core Matter SDK DNS-SD API. // Schedule a service to stop discovery and remove the CastingPlayerChangeListener // Safe to call if discovery is not running // scheduledFutureTask = diff --git a/examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/InitializationExample.java b/examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/InitializationExample.java index 0d2135e1a7bc66..aa602c79a66a94 100644 --- a/examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/InitializationExample.java +++ b/examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/InitializationExample.java @@ -19,7 +19,6 @@ import android.content.Context; import android.util.Log; import chip.platform.ConfigurationManager; -import com.chip.casting.util.PreferencesConfigurationManager; import com.matter.casting.core.CastingApp; import com.matter.casting.support.AppParameters; import com.matter.casting.support.CommissionableData; @@ -49,7 +48,7 @@ public byte[] get() { * DataProvider implementation for the Commissioning Data used by the SDK when the CastingApp goes * through commissioning */ - private static final DataProvider commissionableDataProvider = + static final DataProvider commissionableDataProvider = new DataProvider() { @Override public CommissionableData get() { diff --git a/examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/PreferencesConfigurationManager.java b/examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/PreferencesConfigurationManager.java new file mode 100644 index 00000000000000..1b6c4dfffe6e72 --- /dev/null +++ b/examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/PreferencesConfigurationManager.java @@ -0,0 +1,265 @@ +/* + * Copyright (c) 2021-2024 Project CHIP Authors + * All rights reserved. + * + * 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. + * + */ +package com.matter.casting; + +import android.content.Context; +import android.content.SharedPreferences; +import android.util.Log; +import chip.platform.AndroidChipPlatformException; +import chip.platform.ConfigurationManager; +import chip.platform.KeyValueStoreManager; +import java.util.Base64; +import java.util.Map; +import java.util.UUID; + +/** Java interface for ConfigurationManager */ +public class PreferencesConfigurationManager implements ConfigurationManager { + + private final String TAG = KeyValueStoreManager.class.getSimpleName(); + private SharedPreferences preferences; + + public PreferencesConfigurationManager(Context context, String preferenceFileKey) { + preferences = context.getSharedPreferences(preferenceFileKey, Context.MODE_PRIVATE); + + try { + String keyUniqueId = getKey(kConfigNamespace_ChipFactory, kConfigKey_UniqueId); + if (!preferences.contains(keyUniqueId)) { + preferences + .edit() + .putString(keyUniqueId, UUID.randomUUID().toString().replaceAll("-", "")) + .apply(); + } + } catch (AndroidChipPlatformException e) { + e.printStackTrace(); + } + } + + @Override + public long readConfigValueLong(String namespace, String name) + throws AndroidChipPlatformException { + String key = getKey(namespace, name); + switch (key) { + /** + * The unique id assigned by the device vendor to identify the product or device type. This + * number is scoped to the device vendor id. return a different value than + * src/include/platform/CHIPDeviceConfig.h for debug + */ + case kConfigNamespace_ChipFactory + ":" + kConfigKey_ProductId: + return 0x8003; + + /** + * The default hardware version number assigned to the device or product by the device + * vendor. + * + *

Hardware versions are specific to a particular device vendor and product id, and + * typically correspond to a revision of the physical device, a change to its packaging, + * and/or a change to its marketing presentation. This value is generally *not* incremented + * for device software revisions. + * + *

This is a default value which is used when a hardware version has not been stored in + * device persistent storage (e.g. by a factory provisioning process). + * + *

return a different value than src/include/platform/CHIPDeviceConfig.h for debug + */ + case kConfigNamespace_ChipFactory + ":" + kConfigKey_HardwareVersion: + return 1; + + /** + * A monothonic number identifying the software version running on the device. + * + *

return a different value than src/include/platform/CHIPDeviceConfig.h for debug + */ + case kConfigNamespace_ChipFactory + ":" + kConfigKey_SoftwareVersion: + return 1; + + /** Matter Casting Video Client has device type ID 41 (i.e. 0x0029) */ + case kConfigNamespace_ChipFactory + ":" + kConfigKey_DeviceTypeId: + return 41; + } + + if (preferences.contains(key)) { + long value = preferences.getLong(key, Long.MAX_VALUE); + return value; + } else { + Log.d(TAG, "Key '" + key + "' not found in shared preferences"); + throw new AndroidChipPlatformException(); + } + } + + @Override + public String readConfigValueStr(String namespace, String name) + throws AndroidChipPlatformException { + String key = getKey(namespace, name); + + switch (key) { + /** + * Human readable name of the device model. return a different value than + * src/include/platform/CHIPDeviceConfig.h for debug + */ + case kConfigNamespace_ChipFactory + ":" + kConfigKey_ProductName: + return "TEST_ANDROID_PRODUCT"; + + /** + * Human readable string identifying version of the product assigned by the device vendor. + * + *

return a different value than src/include/platform/CHIPDeviceConfig.h for debug + */ + case kConfigNamespace_ChipFactory + ":" + kConfigKey_HardwareVersionString: + return "TEST_ANDROID_VERSION"; + + /** + * A string identifying the software version running on the device. + * + *

return a different value than src/include/platform/CHIPDeviceConfig.h for debug + */ + case kConfigNamespace_ChipFactory + ":" + kConfigKey_SoftwareVersionString: + return "prerelease(android)"; + + /** + * The ManufacturingDate attribute SHALL specify the date that the Node was manufactured. + * The first 8 characters SHALL specify the date of manufacture of the Node in international + * date notation according to ISO 8601, i.e., YYYYMMDD, e.g., 20060814. The final 8 + * characters MAY include country, factory, line, shift or other related information at the + * option of the vendor. The format of this information is vendor defined. + */ + case kConfigNamespace_ChipFactory + ":" + kConfigKey_ManufacturingDate: + return "2021-12-06"; + + /** + * Enables the use of a hard-coded default serial number if none * is found in Chip NV + * storage. + * + *

return a different value than src/include/platform/CHIPDeviceConfig.h for debug + */ + case kConfigNamespace_ChipFactory + ":" + kConfigKey_SerialNum: + return "TEST_ANDROID_SN"; + + /** + * The PartNumber attribute SHALL specify a human-readable (displayable) vendor assigned + * part number for the Node whose meaning and numbering scheme is vendor defined. Multiple + * products (and hence PartNumbers) can share a ProductID. For instance, there may be + * different packaging (with different PartNumbers) for different regions; also different + * colors of a product might share the ProductID but may have a different PartNumber. + */ + case kConfigNamespace_ChipFactory + ":" + kConfigKey_PartNumber: + return "TEST_ANDROID_PRODUCT_BLUE"; + + /** + * The ProductURL attribute SHALL specify a link to a product specific web page. The syntax + * of the ProductURL attribute SHALL follow the syntax as specified in RFC 3986. The + * specified URL SHOULD resolve to a maintained web page available for the lifetime of the + * product. The maximum length of the ProductUrl attribute is 256 ASCII characters. + */ + case kConfigNamespace_ChipFactory + ":" + kConfigKey_ProductURL: + return "https://buildwithmatter.com/"; + + /** + * The ProductLabel attribute SHALL specify a vendor specific human readable (displayable) + * product label. The ProductLabel attribute MAY be used to provide a more user-friendly + * value than that represented by the ProductName attribute. The ProductLabel attribute + * SHOULD NOT include the name of the vendor as defined within the VendorName attribute. + */ + case kConfigNamespace_ChipFactory + ":" + kConfigKey_ProductLabel: + return "X10"; + } + + if (preferences.contains(key)) { + String value = preferences.getString(key, null); + return value; + } else { + Log.d(TAG, "Key '" + key + "' not found in shared preferences"); + throw new AndroidChipPlatformException(); + } + } + + @Override + public byte[] readConfigValueBin(String namespace, String name) + throws AndroidChipPlatformException { + String key = getKey(namespace, name); + if (preferences.contains(key)) { + String value = preferences.getString(key, null); + byte[] byteValue = Base64.getDecoder().decode(value); + return byteValue; + } else { + Log.d(TAG, "Key '" + key + "' not found in shared preferences"); + throw new AndroidChipPlatformException(); + } + } + + @Override + public void writeConfigValueLong(String namespace, String name, long val) + throws AndroidChipPlatformException { + String key = getKey(namespace, name); + preferences.edit().putLong(key, val).apply(); + } + + @Override + public void writeConfigValueStr(String namespace, String name, String val) + throws AndroidChipPlatformException { + String key = getKey(namespace, name); + preferences.edit().putString(key, val).apply(); + } + + @Override + public void writeConfigValueBin(String namespace, String name, byte[] val) + throws AndroidChipPlatformException { + String key = getKey(namespace, name); + if (val != null) { + String valStr = Base64.getEncoder().encodeToString(val); + preferences.edit().putString(key, valStr).apply(); + } else { + preferences.edit().remove(key).apply(); + } + } + + @Override + public void clearConfigValue(String namespace, String name) throws AndroidChipPlatformException { + if (namespace != null && name != null) { + preferences.edit().remove(getKey(namespace, name)).apply(); + } else if (namespace != null && name == null) { + String pre = getKey(namespace, null); + SharedPreferences.Editor editor = preferences.edit(); + Map allEntries = preferences.getAll(); + for (Map.Entry entry : allEntries.entrySet()) { + String key = entry.getKey(); + if (key.startsWith(pre)) { + editor.remove(key); + } + } + editor.apply(); + } else if (namespace == null && name == null) { + preferences.edit().clear().apply(); + } + } + + @Override + public boolean configValueExists(String namespace, String name) + throws AndroidChipPlatformException { + return preferences.contains(getKey(namespace, name)); + } + + private String getKey(String namespace, String name) throws AndroidChipPlatformException { + if (namespace != null && name != null) { + return namespace + ":" + name; + } else if (namespace != null && name == null) { + return namespace + ":"; + } + + throw new AndroidChipPlatformException(); + } +} diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/CastingApp.java b/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/CastingApp.java index 0823920503d9db..f79eab859a8ce3 100644 --- a/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/CastingApp.java +++ b/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/CastingApp.java @@ -37,6 +37,8 @@ */ public final class CastingApp { private static final String TAG = CastingApp.class.getSimpleName(); + private static final long BROWSE_SERVICE_TIMEOUT = 2500; + private static final long RESOLVE_SERVICE_TIMEOUT = 3000; private static CastingApp sInstance; @@ -75,8 +77,9 @@ public MatterError initialize(AppParameters appParameters) { new AndroidBleManager(), new PreferencesKeyValueStoreManager(appParameters.getApplicationContext()), appParameters.getConfigurationManagerProvider().get(), - new NsdManagerServiceResolver(applicationContext, nsdManagerResolverAvailState), - new NsdManagerServiceBrowser(applicationContext), + new NsdManagerServiceResolver( + applicationContext, nsdManagerResolverAvailState, RESOLVE_SERVICE_TIMEOUT), + new NsdManagerServiceBrowser(applicationContext, BROWSE_SERVICE_TIMEOUT), new ChipMdnsCallbackImpl(), new DiagnosticDataProviderImpl(applicationContext)); @@ -147,6 +150,16 @@ public MatterError stop() { return MatterError.NO_ERROR; } + /** @brief Tears down all active subscriptions. */ + public native MatterError shutdownAllSubscriptions(); + + /** + * Clears app cache that contains the information about CastingPlayers previously connected to + * + * @return + */ + public native MatterError clearCache(); + /** * Sets DeviceAttestationCrdentials provider and RotatingDeviceIdUniqueId * diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/CastingAppState.java b/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/CastingAppState.java index 89ddef7aa0dbf6..9f2e952216f067 100644 --- a/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/CastingAppState.java +++ b/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/CastingAppState.java @@ -1,3 +1,19 @@ +/* + * Copyright (c) 2024 Project CHIP Authors + * All rights reserved. + * + * 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. + */ package com.matter.casting.core; /** Represents the state of the CastingApp */ diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/CastingPlayer.java b/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/CastingPlayer.java index 723f1b8e93b1a5..39db6488fa8ed8 100644 --- a/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/CastingPlayer.java +++ b/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/CastingPlayer.java @@ -17,9 +17,10 @@ package com.matter.casting.core; import com.matter.casting.support.EndpointFilter; +import com.matter.casting.support.MatterCallback; +import com.matter.casting.support.MatterError; import java.net.InetAddress; import java.util.List; -import java.util.concurrent.CompletableFuture; /** * The CastingPlayer interface defines a Matter commissioner that is able to play media to a @@ -48,6 +49,8 @@ public interface CastingPlayer { long getDeviceType(); + List getEndpoints(); + @Override String toString(); @@ -68,27 +71,30 @@ public interface CastingPlayer { * that the client wants to interact with after commissioning. If this value is passed in, the * VerifyOrEstablishConnection will force User Directed Commissioning, in case the desired * Endpoint is not found in the on device CastingStore. - * @return A CompletableFuture that completes when the VerifyOrEstablishConnection is completed. - * The CompletableFuture will be completed with a Void value if the - * VerifyOrEstablishConnection is successful. Otherwise, the CompletableFuture will be - * completed with an Exception. The Exception will be of type - * com.matter.casting.core.CastingException. If the VerifyOrEstablishConnection fails, the - * CastingException will contain the error code and message from the CastingApp. + * @param successCallback called when the connection is established successfully + * @param failureCallback called with MatterError when the connection is fails to establish + * @return MatterError - Matter.NO_ERROR if request submitted successfully, otherwise a + * MatterError object corresponding to the error */ - CompletableFuture VerifyOrEstablishConnection( - long commissioningWindowTimeoutSec, EndpointFilter desiredEndpointFilter); + MatterError verifyOrEstablishConnection( + long commissioningWindowTimeoutSec, + EndpointFilter desiredEndpointFilter, + MatterCallback successCallback, + MatterCallback failureCallback); /** * Verifies that a connection exists with this CastingPlayer, or triggers a new session request. * If the CastingApp does not have the nodeId and fabricIndex of this CastingPlayer cached on * disk, this will execute the user directed commissioning process. * - * @return A CompletableFuture that completes when the VerifyOrEstablishConnection is completed. - * The CompletableFuture will be completed with a Void value if the - * VerifyOrEstablishConnection is successful. Otherwise, the CompletableFuture will be - * completed with an Exception. The Exception will be of type - * com.matter.casting.core.CastingException. If the VerifyOrEstablishConnection fails, the - * CastingException will contain the error code and message from the CastingApp. + * @param successCallback called when the connection is established successfully + * @param failureCallback called with MatterError when the connection is fails to establish + * @return MatterError - Matter.NO_ERROR if request submitted successfully, otherwise a + * MatterError object corresponding to the error */ - CompletableFuture VerifyOrEstablishConnection(); + MatterError verifyOrEstablishConnection( + MatterCallback successCallback, MatterCallback failureCallback); + + /** @brief Sets the internal connection state of this CastingPlayer to "disconnected" */ + void disconnect(); } diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/Endpoint.java b/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/Endpoint.java new file mode 100644 index 00000000000000..6d1b63555aad08 --- /dev/null +++ b/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/Endpoint.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2024 Project CHIP Authors + * All rights reserved. + * + * 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. + */ + +package com.matter.casting.core; + +import com.matter.casting.support.DeviceTypeStruct; +import java.util.List; + +public interface Endpoint { + int getId(); + + int getVendorId(); + + int getProductId(); + + List getDeviceTypeList(); + + CastingPlayer getCastingPlayer(); +} diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/MatterCastingPlayer.java b/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/MatterCastingPlayer.java index d5d93c3204ec34..dd4bd0ba6531c6 100644 --- a/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/MatterCastingPlayer.java +++ b/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/MatterCastingPlayer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Project CHIP Authors + * Copyright (c) 2024 Project CHIP Authors * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,10 +17,11 @@ package com.matter.casting.core; import com.matter.casting.support.EndpointFilter; +import com.matter.casting.support.MatterCallback; +import com.matter.casting.support.MatterError; import java.net.InetAddress; import java.util.List; import java.util.Objects; -import java.util.concurrent.CompletableFuture; /** * A Matter Casting Player represents a Matter commissioner that is able to play media to a physical @@ -130,6 +131,9 @@ public long getDeviceType() { return this.deviceType; } + @Override + public native List getEndpoints(); + @Override public String toString() { return this.deviceId; @@ -167,8 +171,11 @@ public boolean equals(Object o) { * CastingException will contain the error code and message from the CastingApp. */ @Override - public native CompletableFuture VerifyOrEstablishConnection( - long commissioningWindowTimeoutSec, EndpointFilter desiredEndpointFilter); + public native MatterError verifyOrEstablishConnection( + long commissioningWindowTimeoutSec, + EndpointFilter desiredEndpointFilter, + MatterCallback successCallback, + MatterCallback failureCallback); /** * Verifies that a connection exists with this CastingPlayer, or triggers a new session request. @@ -183,7 +190,12 @@ public native CompletableFuture VerifyOrEstablishConnection( * CastingException will contain the error code and message from the CastingApp. */ @Override - public CompletableFuture VerifyOrEstablishConnection() { - return VerifyOrEstablishConnection(MIN_CONNECTION_TIMEOUT_SEC, null); + public MatterError verifyOrEstablishConnection( + MatterCallback successCallback, MatterCallback failureCallback) { + return verifyOrEstablishConnection( + MIN_CONNECTION_TIMEOUT_SEC, null, successCallback, failureCallback); } + + @Override + public native void disconnect(); } diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/MatterEndpoint.java b/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/MatterEndpoint.java new file mode 100644 index 00000000000000..b9dd564d6ff95f --- /dev/null +++ b/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/MatterEndpoint.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2024 Project CHIP Authors + * All rights reserved. + * + * 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. + */ +package com.matter.casting.core; + +import com.matter.casting.support.DeviceTypeStruct; +import java.util.List; +import java.util.Objects; + +public class MatterEndpoint implements Endpoint { + private static final String TAG = MatterEndpoint.class.getSimpleName(); + protected long _cppEndpoint; + + @Override + public native int getId(); + + @Override + public native int getVendorId(); + + @Override + public native int getProductId(); + + @Override + public native List getDeviceTypeList(); + + @Override + public native CastingPlayer getCastingPlayer(); + + @Override + public String toString() { + return "MatterEndpoint{" + "id=" + getId() + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + MatterEndpoint that = (MatterEndpoint) o; + return getId() == that.getId(); + } + + @Override + public int hashCode() { + return Objects.hash(getId()); + } +} diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/support/EndpointFilter.java b/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/support/EndpointFilter.java index 1152e48b95890c..833c24fdc57544 100644 --- a/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/support/EndpointFilter.java +++ b/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/support/EndpointFilter.java @@ -26,6 +26,8 @@ public class EndpointFilter { public Integer vendorId; public List requiredDeviceTypes; + public EndpointFilter() {} + public EndpointFilter( Integer productId, Integer vendorId, List requiredDeviceTypes) { this.productId = productId; diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/support/MatterCallback.java b/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/support/MatterCallback.java new file mode 100644 index 00000000000000..2d1f01dedb262d --- /dev/null +++ b/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/support/MatterCallback.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2024 Project CHIP Authors + * All rights reserved. + * + * 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. + * + */ +package com.matter.casting.support; + +import android.util.Log; + +public abstract class MatterCallback { + private static final String TAG = MatterCallback.class.getSimpleName(); + + public abstract void handle(R response); + + protected final void handleInternal(R response) { + try { + handle(response); + } catch (Throwable t) { + Log.e(TAG, "MatterCallback::Caught an unhandled Throwable from the client: " + t); + } + } +} diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/CastingApp-JNI.cpp b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/CastingApp-JNI.cpp index e3a4c79b956822..81a42115070da7 100644 --- a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/CastingApp-JNI.cpp +++ b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/CastingApp-JNI.cpp @@ -19,7 +19,7 @@ #include "CastingApp-JNI.h" #include "../JNIDACProvider.h" -#include "../support/ErrorConverter-JNI.h" +#include "../support/Converters-JNI.h" #include "../support/RotatingDeviceIdUniqueIdProvider-JNI.h" // from tv-casting-common @@ -48,15 +48,15 @@ JNI_METHOD(jobject, finishInitialization)(JNIEnv *, jobject, jobject jAppParamet { chip::DeviceLayer::StackLock lock; ChipLogProgress(AppServer, "JNI_METHOD CastingApp-JNI::finishInitialization() called"); - VerifyOrReturnValue(jAppParameters != nullptr, support::createJMatterError(CHIP_ERROR_INVALID_ARGUMENT)); + VerifyOrReturnValue(jAppParameters != nullptr, support::convertMatterErrorFromCppToJava(CHIP_ERROR_INVALID_ARGUMENT)); CHIP_ERROR err = CHIP_NO_ERROR; jobject jUniqueIdProvider = extractJAppParameter(jAppParameters, "getRotatingDeviceIdUniqueIdProvider", "()Lcom/matter/casting/support/DataProvider;"); - VerifyOrReturnValue(jUniqueIdProvider != nullptr, support::createJMatterError(CHIP_ERROR_INCORRECT_STATE)); + VerifyOrReturnValue(jUniqueIdProvider != nullptr, support::convertMatterErrorFromCppToJava(CHIP_ERROR_INCORRECT_STATE)); support::RotatingDeviceIdUniqueIdProviderJNI * uniqueIdProvider = new support::RotatingDeviceIdUniqueIdProviderJNI(); err = uniqueIdProvider->Initialize(jUniqueIdProvider); - VerifyOrReturnValue(err == CHIP_NO_ERROR, support::createJMatterError(CHIP_ERROR_INVALID_ARGUMENT)); + VerifyOrReturnValue(err == CHIP_NO_ERROR, support::convertMatterErrorFromCppToJava(CHIP_ERROR_INVALID_ARGUMENT)); // set the RotatingDeviceIdUniqueId #if CHIP_ENABLE_ROTATING_DEVICE_ID @@ -69,13 +69,13 @@ JNI_METHOD(jobject, finishInitialization)(JNIEnv *, jobject, jobject jAppParamet // get the DACProvider jobject jDACProvider = extractJAppParameter(jAppParameters, "getDacProvider", "()Lcom/matter/casting/support/DACProvider;"); - VerifyOrReturnValue(jDACProvider != nullptr, support::createJMatterError(CHIP_ERROR_INCORRECT_STATE)); + VerifyOrReturnValue(jDACProvider != nullptr, support::convertMatterErrorFromCppToJava(CHIP_ERROR_INCORRECT_STATE)); // set the DACProvider JNIDACProvider * dacProvider = new JNIDACProvider(jDACProvider); chip::Credentials::SetDeviceAttestationCredentialsProvider(dacProvider); - return support::createJMatterError(CHIP_NO_ERROR); + return support::convertMatterErrorFromCppToJava(CHIP_NO_ERROR); } JNI_METHOD(jobject, finishStartup)(JNIEnv *, jobject) @@ -92,17 +92,35 @@ JNI_METHOD(jobject, finishStartup)(JNIEnv *, jobject) // Initialize binding handlers err = chip::BindingManager::GetInstance().Init( { &server.GetFabricTable(), server.GetCASESessionManager(), &server.GetPersistentStorage() }); - VerifyOrReturnValue(err == CHIP_NO_ERROR, support::createJMatterError(err), + VerifyOrReturnValue(err == CHIP_NO_ERROR, support::convertMatterErrorFromCppToJava(err), ChipLogError(AppServer, "Failed to init BindingManager %" CHIP_ERROR_FORMAT, err.Format())); // TODO: Set FabricDelegate // chip::Server::GetInstance().GetFabricTable().AddFabricDelegate(&mPersistenceManager); err = chip::DeviceLayer::PlatformMgrImpl().AddEventHandler(support::ChipDeviceEventHandler::Handle, 0); - VerifyOrReturnValue(err == CHIP_NO_ERROR, support::createJMatterError(err), + VerifyOrReturnValue(err == CHIP_NO_ERROR, support::convertMatterErrorFromCppToJava(err), ChipLogError(AppServer, "Failed to register ChipDeviceEventHandler %" CHIP_ERROR_FORMAT, err.Format())); - return support::createJMatterError(CHIP_NO_ERROR); + return support::convertMatterErrorFromCppToJava(CHIP_NO_ERROR); +} + +JNI_METHOD(jobject, shutdownAllSubscriptions)(JNIEnv * env, jobject) +{ + chip::DeviceLayer::StackLock lock; + ChipLogProgress(AppServer, "JNI_METHOD CastingApp-JNI::shutdownAllSubscriptions called"); + + CHIP_ERROR err = matter::casting::core::CastingApp::GetInstance()->ShutdownAllSubscriptions(); + return support::convertMatterErrorFromCppToJava(err); +} + +JNI_METHOD(jobject, clearCache)(JNIEnv * env, jobject) +{ + chip::DeviceLayer::StackLock lock; + ChipLogProgress(AppServer, "JNI_METHOD CastingApp-JNI::clearCache called"); + + CHIP_ERROR err = matter::casting::core::CastingApp::GetInstance()->ClearCache(); + return support::convertMatterErrorFromCppToJava(err); } jobject extractJAppParameter(jobject jAppParameters, const char * methodName, const char * methodSig) diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/CastingPlayer-JNI.cpp b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/CastingPlayer-JNI.cpp deleted file mode 100644 index ba5a8e765908f1..00000000000000 --- a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/CastingPlayer-JNI.cpp +++ /dev/null @@ -1,171 +0,0 @@ -/* - * Copyright (c) 2024 Project CHIP Authors - * All rights reserved. - * - * 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. - * - */ - -#include "CastingPlayer-JNI.h" - -#include "../JNIDACProvider.h" -#include "../support/CastingPlayerConverter-JNI.h" -#include "../support/ErrorConverter-JNI.h" -#include "../support/RotatingDeviceIdUniqueIdProvider-JNI.h" -#include "core/CastingApp.h" // from tv-casting-common -#include "core/CastingPlayer.h" // from tv-casting-common -#include "core/CastingPlayerDiscovery.h" // from tv-casting-common - -#include -#include -#include -#include -#include - -using namespace chip; - -#define JNI_METHOD(RETURN, METHOD_NAME) \ - extern "C" JNIEXPORT RETURN JNICALL Java_com_matter_casting_core_MatterCastingPlayer_##METHOD_NAME - -namespace matter { -namespace casting { -namespace core { - -JNI_METHOD(jobject, VerifyOrEstablishConnection) -(JNIEnv * env, jobject thiz, jlong commissioningWindowTimeoutSec, jobject desiredEndpointFilterJavaObject) -{ - chip::DeviceLayer::StackLock lock; - ChipLogProgress(AppServer, "CastingPlayer-JNI::VerifyOrEstablishConnection() called with a timeout of: %ld seconds", - static_cast(commissioningWindowTimeoutSec)); - - // Convert the CastingPlayer jlong to a CastingPlayer pointer - jclass castingPlayerClass = env->GetObjectClass(thiz); - jfieldID _cppCastingPlayerFieldId = env->GetFieldID(castingPlayerClass, "_cppCastingPlayer", "J"); - VerifyOrReturnValue( - _cppCastingPlayerFieldId != nullptr, nullptr, - ChipLogError(AppServer, "CastingPlayer-JNI::VerifyOrEstablishConnection() _cppCastingPlayerFieldId == nullptr")); - - jlong _cppCastingPlayerValue = env->GetLongField(thiz, _cppCastingPlayerFieldId); - CastingPlayer * castingPlayer = reinterpret_cast(_cppCastingPlayerValue); - VerifyOrReturnValue(castingPlayer != nullptr, nullptr, - ChipLogError(AppServer, "CastingPlayer-JNI::VerifyOrEstablishConnection() castingPlayer == nullptr")); - - // Create a new Java CompletableFuture - jclass completableFutureClass = env->FindClass("java/util/concurrent/CompletableFuture"); - jmethodID completableFutureConstructor = env->GetMethodID(completableFutureClass, "", "()V"); - jobject completableFutureObj = env->NewObject(completableFutureClass, completableFutureConstructor); - jobject completableFutureObjGlobalRef = env->NewGlobalRef(completableFutureObj); - VerifyOrReturnValue( - completableFutureObjGlobalRef != nullptr, nullptr, - ChipLogError(AppServer, "CastingPlayer-JNI::VerifyOrEstablishConnection() completableFutureObjGlobalRef == nullptr")); - - ConnectCallback callback = [completableFutureObjGlobalRef](CHIP_ERROR err, CastingPlayer * playerPtr) { - ChipLogProgress(AppServer, "CastingPlayer-JNI::VerifyOrEstablishConnection() ConnectCallback called"); - VerifyOrReturn(completableFutureObjGlobalRef != nullptr, - ChipLogError(AppServer, "ConnectCallback, completableFutureObjGlobalRef == nullptr")); - - JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread(); - VerifyOrReturn(env != nullptr, ChipLogError(AppServer, "ConnectCallback, env == nullptr")); - // Ensures proper cleanup of local references to Java objects. - JniLocalReferenceScope scope(env); - // Ensures proper cleanup of global references to Java objects. - JniGlobalRefWrapper globalRefWrapper(completableFutureObjGlobalRef); - - jclass completableFutureClass = env->FindClass("java/util/concurrent/CompletableFuture"); - VerifyOrReturn(completableFutureClass != nullptr, - ChipLogError(AppServer, "ConnectCallback, completableFutureClass == nullptr"); - env->DeleteGlobalRef(completableFutureObjGlobalRef);); - - if (err == CHIP_NO_ERROR) - { - ChipLogProgress(AppServer, "ConnectCallback, Connected to Casting Player with device ID: %s", playerPtr->GetId()); - jmethodID completeMethod = env->GetMethodID(completableFutureClass, "complete", "(Ljava/lang/Object;)Z"); - VerifyOrReturn(completeMethod != nullptr, ChipLogError(AppServer, "ConnectCallback, completeMethod == nullptr")); - - chip::DeviceLayer::StackUnlock unlock; - env->CallBooleanMethod(completableFutureObjGlobalRef, completeMethod, nullptr); - } - else - { - ChipLogError(AppServer, "ConnectCallback, connection error: %" CHIP_ERROR_FORMAT, err.Format()); - jmethodID completeExceptionallyMethod = - env->GetMethodID(completableFutureClass, "completeExceptionally", "(Ljava/lang/Throwable;)Z"); - VerifyOrReturn(completeExceptionallyMethod != nullptr, - ChipLogError(AppServer, "ConnectCallback, completeExceptionallyMethod == nullptr")); - - // Create a Throwable object (e.g., RuntimeException) to pass to completeExceptionallyMethod - jclass throwableClass = env->FindClass("java/lang/RuntimeException"); - VerifyOrReturn(throwableClass != nullptr, ChipLogError(AppServer, "ConnectCallback, throwableClass == nullptr")); - jmethodID throwableConstructor = env->GetMethodID(throwableClass, "", "(Ljava/lang/String;)V"); - VerifyOrReturn(throwableConstructor != nullptr, - ChipLogError(AppServer, "ConnectCallback, throwableConstructor == nullptr")); - jstring errorMessage = env->NewStringUTF(err.Format()); - VerifyOrReturn(errorMessage != nullptr, ChipLogError(AppServer, "ConnectCallback, errorMessage == nullptr")); - jobject throwableObject = env->NewObject(throwableClass, throwableConstructor, errorMessage); - VerifyOrReturn(throwableObject != nullptr, ChipLogError(AppServer, "ConnectCallback, throwableObject == nullptr")); - - chip::DeviceLayer::StackUnlock unlock; - env->CallBooleanMethod(completableFutureObjGlobalRef, completeExceptionallyMethod, throwableObject); - } - }; - - if (desiredEndpointFilterJavaObject == nullptr) - { - ChipLogProgress(AppServer, - "CastingPlayer-JNI::VerifyOrEstablishConnection() calling CastingPlayer::VerifyOrEstablishConnection() on " - "Casting Player with device ID: %s", - castingPlayer->GetId()); - castingPlayer->VerifyOrEstablishConnection(callback, static_cast(commissioningWindowTimeoutSec)); - } - else - { - // Convert the EndpointFilter Java class to a C++ EndpointFilter - jclass endpointFilterJavaClass = env->GetObjectClass(desiredEndpointFilterJavaObject); - jfieldID vendorIdFieldId = env->GetFieldID(endpointFilterJavaClass, "vendorId", "Ljava/lang/Integer;"); - jfieldID productIdFieldId = env->GetFieldID(endpointFilterJavaClass, "productId", "Ljava/lang/Integer;"); - jobject vendorIdIntegerObject = env->GetObjectField(desiredEndpointFilterJavaObject, vendorIdFieldId); - jobject productIdIntegerObject = env->GetObjectField(desiredEndpointFilterJavaObject, productIdFieldId); - // jfieldID requiredDeviceTypesFieldId = env->GetFieldID(endpointFilterJavaClass, "requiredDeviceTypes", - // "Ljava/util/List;"); - - matter::casting::core::EndpointFilter desiredEndpointFilter; - // Value of 0 means unspecified - desiredEndpointFilter.vendorId = vendorIdIntegerObject != nullptr - ? static_cast(env->CallIntMethod( - vendorIdIntegerObject, env->GetMethodID(env->GetObjectClass(vendorIdIntegerObject), "intValue", "()I"))) - : 0; - desiredEndpointFilter.productId = productIdIntegerObject != nullptr - ? static_cast(env->CallIntMethod( - productIdIntegerObject, env->GetMethodID(env->GetObjectClass(productIdIntegerObject), "intValue", "()I"))) - : 0; - ChipLogProgress(AppServer, "CastingPlayer-JNI::VerifyOrEstablishConnection() desiredEndpointFilter.vendorId: %d", - desiredEndpointFilter.vendorId); - ChipLogProgress(AppServer, "CastingPlayer-JNI::VerifyOrEstablishConnection() desiredEndpointFilter.productId: %d", - desiredEndpointFilter.productId); - // TODO: In following PRs. Translate the Java requiredDeviceTypes list to a C++ requiredDeviceTypes vector. For now we're - // passing an empty list of DeviceTypeStruct. - - ChipLogProgress(AppServer, - "CastingPlayer-JNI::VerifyOrEstablishConnection() calling " - "CastingPlayer::VerifyOrEstablishConnection() on Casting Player with device ID: %s", - castingPlayer->GetId()); - castingPlayer->VerifyOrEstablishConnection(callback, static_cast(commissioningWindowTimeoutSec), - desiredEndpointFilter); - } - - return completableFutureObjGlobalRef; -} - -}; // namespace core -}; // namespace casting -}; // namespace matter diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/CastingPlayerDiscovery-JNI.cpp b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/CastingPlayerDiscovery-JNI.cpp index 5838d4039ae6a3..5981ab80bbd52e 100644 --- a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/CastingPlayerDiscovery-JNI.cpp +++ b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/CastingPlayerDiscovery-JNI.cpp @@ -19,8 +19,7 @@ #include "CastingPlayerDiscovery-JNI.h" #include "../JNIDACProvider.h" -#include "../support/CastingPlayerConverter-JNI.h" -#include "../support/ErrorConverter-JNI.h" +#include "../support/Converters-JNI.h" #include "../support/RotatingDeviceIdUniqueIdProvider-JNI.h" #include "core/CastingApp.h" // from tv-casting-common #include "core/CastingPlayerDiscovery.h" // from tv-casting-common @@ -81,7 +80,7 @@ class DiscoveryDelegateImpl : public DiscoveryDelegate "CastingPlayerDiscovery-JNI::DiscoveryDelegateImpl::HandleOnAdded() Warning: Not set, " "onAddedCallbackJavaMethodID == nullptr")); - jobject matterCastingPlayerJavaObject = support::createJCastingPlayer(player); + jobject matterCastingPlayerJavaObject = support::convertCastingPlayerFromCppToJava(player); VerifyOrReturn(matterCastingPlayerJavaObject != nullptr, ChipLogError(AppServer, "CastingPlayerDiscovery-JNI::DiscoveryDelegateImpl::HandleOnAdded() Warning: Could not create " @@ -108,7 +107,7 @@ class DiscoveryDelegateImpl : public DiscoveryDelegate "CastingPlayerDiscovery-JNI::DiscoveryDelegateImpl::HandleOnUpdated() Warning: Not set, " "onChangedCallbackJavaMethodID == nullptr")); - jobject matterCastingPlayerJavaObject = support::createJCastingPlayer(player); + jobject matterCastingPlayerJavaObject = support::convertCastingPlayerFromCppToJava(player); VerifyOrReturn(matterCastingPlayerJavaObject != nullptr, ChipLogError(AppServer, "CastingPlayerDiscovery-JNI::DiscoveryDelegateImpl::HandleOnUpdated() Warning: Could not " @@ -160,10 +159,10 @@ JNI_METHOD(jobject, startDiscovery)(JNIEnv * env, jobject, jobject targetDeviceT if (err != CHIP_NO_ERROR) { ChipLogError(AppServer, "CastingPlayerDiscovery-JNI startDiscovery() err: %" CHIP_ERROR_FORMAT, err.Format()); - return support::createJMatterError(err); + return support::convertMatterErrorFromCppToJava(err); } - return support::createJMatterError(CHIP_NO_ERROR); + return support::convertMatterErrorFromCppToJava(CHIP_NO_ERROR); } JNI_METHOD(jobject, stopDiscovery)(JNIEnv * env, jobject) @@ -176,53 +175,55 @@ JNI_METHOD(jobject, stopDiscovery)(JNIEnv * env, jobject) if (err != CHIP_NO_ERROR) { ChipLogError(AppServer, "CastingPlayerDiscovery-JNI::StopDiscovery() err: %" CHIP_ERROR_FORMAT, err.Format()); - return support::createJMatterError(err); + return support::convertMatterErrorFromCppToJava(err); } - return support::createJMatterError(CHIP_NO_ERROR); + return support::convertMatterErrorFromCppToJava(CHIP_NO_ERROR); } JNI_METHOD(jobject, addCastingPlayerChangeListener)(JNIEnv * env, jobject, jobject castingPlayerChangeListenerJavaObject) { chip::DeviceLayer::StackLock lock; ChipLogProgress(AppServer, "CastingPlayerDiscovery-JNI::addCastingPlayerChangeListener() called"); - VerifyOrReturnValue(castingPlayerChangeListenerJavaObject != nullptr, support::createJMatterError(CHIP_ERROR_INCORRECT_STATE)); + VerifyOrReturnValue(castingPlayerChangeListenerJavaObject != nullptr, + support::convertMatterErrorFromCppToJava(CHIP_ERROR_INCORRECT_STATE)); if (DiscoveryDelegateImpl::GetInstance()->castingPlayerChangeListenerJavaObject.HasValidObjectRef()) { ChipLogError(AppServer, "CastingPlayerDiscovery-JNI::addCastingPlayerChangeListener() Warning: Call removeCastingPlayerChangeListener " "before adding a new one"); - return support::createJMatterError(CHIP_ERROR_INCORRECT_STATE); + return support::convertMatterErrorFromCppToJava(CHIP_ERROR_INCORRECT_STATE); } // Get the class and method IDs for the CastingPlayerChangeListener Java class jclass castingPlayerChangeListenerJavaClass = env->GetObjectClass(castingPlayerChangeListenerJavaObject); - VerifyOrReturnValue(castingPlayerChangeListenerJavaClass != nullptr, support::createJMatterError(CHIP_ERROR_INCORRECT_STATE)); + VerifyOrReturnValue(castingPlayerChangeListenerJavaClass != nullptr, + support::convertMatterErrorFromCppToJava(CHIP_ERROR_INCORRECT_STATE)); jmethodID onAddedJavaMethodID = env->GetMethodID(castingPlayerChangeListenerJavaClass, "_onAdded", "(Lcom/matter/casting/core/CastingPlayer;)V"); - VerifyOrReturnValue(onAddedJavaMethodID != nullptr, support::createJMatterError(CHIP_ERROR_INCORRECT_STATE)); + VerifyOrReturnValue(onAddedJavaMethodID != nullptr, support::convertMatterErrorFromCppToJava(CHIP_ERROR_INCORRECT_STATE)); jmethodID onChangedJavaMethodID = env->GetMethodID(castingPlayerChangeListenerJavaClass, "_onChanged", "(Lcom/matter/casting/core/CastingPlayer;)V"); - VerifyOrReturnValue(onChangedJavaMethodID != nullptr, support::createJMatterError(CHIP_ERROR_INCORRECT_STATE)); + VerifyOrReturnValue(onChangedJavaMethodID != nullptr, support::convertMatterErrorFromCppToJava(CHIP_ERROR_INCORRECT_STATE)); // jmethodID onRemovedJavaMethodID = env->GetMethodID(castingPlayerChangeListenerJavaClass, "_onRemoved", // "(Lcom/matter/casting/core/CastingPlayer;)V"); VerifyOrReturnValue(onRemovedJavaMethodID != nullptr, - // support::createJMatterError(CHIP_ERROR_INCORRECT_STATE)); + // support::convertMatterErrorFromCppToJava(CHIP_ERROR_INCORRECT_STATE)); // Set Java callbacks in the DiscoveryDelegateImpl Singleton CHIP_ERROR err = DiscoveryDelegateImpl::GetInstance()->castingPlayerChangeListenerJavaObject.Init(castingPlayerChangeListenerJavaObject); if (err != CHIP_NO_ERROR) { - return support::createJMatterError(err); + return support::convertMatterErrorFromCppToJava(err); } DiscoveryDelegateImpl::GetInstance()->onAddedCallbackJavaMethodID = onAddedJavaMethodID; DiscoveryDelegateImpl::GetInstance()->onChangedCallbackJavaMethodID = onChangedJavaMethodID; // DiscoveryDelegateImpl::GetInstance()->onRemovedCallbackJavaMethodID = onRemovedJavaMethodID; - return support::createJMatterError(CHIP_NO_ERROR); + return support::convertMatterErrorFromCppToJava(CHIP_NO_ERROR); } JNI_METHOD(jobject, removeCastingPlayerChangeListener)(JNIEnv * env, jobject, jobject castingPlayerChangeListenerJavaObject) @@ -243,14 +244,14 @@ JNI_METHOD(jobject, removeCastingPlayerChangeListener)(JNIEnv * env, jobject, jo DiscoveryDelegateImpl::GetInstance()->onChangedCallbackJavaMethodID = nullptr; // DiscoveryDelegateImpl::GetInstance()->onRemovedCallbackJavaMethodID = nullptr; - return support::createJMatterError(CHIP_NO_ERROR); + return support::convertMatterErrorFromCppToJava(CHIP_NO_ERROR); } else { ChipLogError(AppServer, "CastingPlayerDiscovery-JNI::removeCastingPlayerChangeListener() Warning: Cannot remove listener. Received a " "different CastingPlayerChangeListener object"); - return support::createJMatterError(CHIP_ERROR_INCORRECT_STATE); + return support::convertMatterErrorFromCppToJava(CHIP_ERROR_INCORRECT_STATE); } } @@ -269,7 +270,7 @@ JNI_METHOD(jobject, getCastingPlayers)(JNIEnv * env, jobject) for (const auto & player : castingPlayersList) { - jobject matterCastingPlayerJavaObject = support::createJCastingPlayer(player); + jobject matterCastingPlayerJavaObject = support::convertCastingPlayerFromCppToJava(player); if (matterCastingPlayerJavaObject != nullptr) { jboolean added = env->CallBooleanMethod(arrayList, addMethod, matterCastingPlayerJavaObject); diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/MatterCastingPlayer-JNI.cpp b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/MatterCastingPlayer-JNI.cpp new file mode 100644 index 00000000000000..caa64b4ac8d717 --- /dev/null +++ b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/MatterCastingPlayer-JNI.cpp @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2024 Project CHIP Authors + * All rights reserved. + * + * 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. + * + */ + +#include "MatterCastingPlayer-JNI.h" + +#include "../JNIDACProvider.h" +#include "../support/Converters-JNI.h" +#include "../support/RotatingDeviceIdUniqueIdProvider-JNI.h" +#include "core/CastingApp.h" // from tv-casting-common +#include "core/CastingPlayer.h" // from tv-casting-common +#include "core/CastingPlayerDiscovery.h" // from tv-casting-common + +#include +#include +#include +#include +#include + +using namespace chip; + +#define JNI_METHOD(RETURN, METHOD_NAME) \ + extern "C" JNIEXPORT RETURN JNICALL Java_com_matter_casting_core_MatterCastingPlayer_##METHOD_NAME + +namespace matter { +namespace casting { +namespace core { + +MatterCastingPlayerJNI MatterCastingPlayerJNI::sInstance; + +JNI_METHOD(jobject, verifyOrEstablishConnection) +(JNIEnv * env, jobject thiz, jlong commissioningWindowTimeoutSec, jobject desiredEndpointFilterJavaObject, jobject jSuccessCallback, + jobject jFailureCallback) +{ + chip::DeviceLayer::StackLock lock; + ChipLogProgress(AppServer, "MatterCastingPlayer-JNI::verifyOrEstablishConnection() called with a timeout of: %ld seconds", + static_cast(commissioningWindowTimeoutSec)); + + CastingPlayer * castingPlayer = support::convertCastingPlayerFromJavaToCpp(thiz); + VerifyOrReturnValue(castingPlayer != nullptr, support::convertMatterErrorFromCppToJava(CHIP_ERROR_INVALID_ARGUMENT)); + + matter::casting::core::EndpointFilter desiredEndpointFilter; + if (desiredEndpointFilterJavaObject != nullptr) + { + // Convert the EndpointFilter Java class to a C++ EndpointFilter + jclass endpointFilterJavaClass = env->GetObjectClass(desiredEndpointFilterJavaObject); + jfieldID vendorIdFieldId = env->GetFieldID(endpointFilterJavaClass, "vendorId", "Ljava/lang/Integer;"); + jfieldID productIdFieldId = env->GetFieldID(endpointFilterJavaClass, "productId", "Ljava/lang/Integer;"); + jobject vendorIdIntegerObject = env->GetObjectField(desiredEndpointFilterJavaObject, vendorIdFieldId); + jobject productIdIntegerObject = env->GetObjectField(desiredEndpointFilterJavaObject, productIdFieldId); + // jfieldID requiredDeviceTypesFieldId = env->GetFieldID(endpointFilterJavaClass, "requiredDeviceTypes", + // "Ljava/util/List;"); + + // Value of 0 means unspecified + desiredEndpointFilter.vendorId = vendorIdIntegerObject != nullptr + ? static_cast(env->CallIntMethod( + vendorIdIntegerObject, env->GetMethodID(env->GetObjectClass(vendorIdIntegerObject), "intValue", "()I"))) + : 0; + desiredEndpointFilter.productId = productIdIntegerObject != nullptr + ? static_cast(env->CallIntMethod( + productIdIntegerObject, env->GetMethodID(env->GetObjectClass(productIdIntegerObject), "intValue", "()I"))) + : 0; + // TODO: In following PRs. Translate the Java requiredDeviceTypes list to a C++ requiredDeviceTypes vector. For now we're + // passing an empty list of DeviceTypeStruct. + } + + MatterCastingPlayerJNIMgr().mConnectionSuccessHandler.SetUp(env, jSuccessCallback); + MatterCastingPlayerJNIMgr().mConnectionFailureHandler.SetUp(env, jFailureCallback); + castingPlayer->VerifyOrEstablishConnection( + [](CHIP_ERROR err, CastingPlayer * playerPtr) { + ChipLogProgress(AppServer, "MatterCastingPlayer-JNI::verifyOrEstablishConnection() ConnectCallback called"); + if (err == CHIP_NO_ERROR) + { + ChipLogProgress(AppServer, "MatterCastingPlayer-JNI:: Connected to Casting Player with device ID: %s", + playerPtr->GetId()); + MatterCastingPlayerJNIMgr().mConnectionSuccessHandler.Handle(nullptr); + } + else + { + ChipLogError(AppServer, "MatterCastingPlayer-JNI:: ConnectCallback, connection error: %" CHIP_ERROR_FORMAT, + err.Format()); + MatterCastingPlayerJNIMgr().mConnectionFailureHandler.Handle(err); + } + }, + static_cast(commissioningWindowTimeoutSec), desiredEndpointFilter); + return support::convertMatterErrorFromCppToJava(CHIP_NO_ERROR); +} + +JNI_METHOD(void, disconnect) +(JNIEnv * env, jobject thiz) +{ + chip::DeviceLayer::StackLock lock; + ChipLogProgress(AppServer, "MatterCastingPlayer-JNI::disconnect()"); + + core::CastingPlayer * castingPlayer = support::convertCastingPlayerFromJavaToCpp(thiz); + VerifyOrReturn(castingPlayer != nullptr, + ChipLogError(AppServer, "MatterCastingPlayer-JNI::disconnect() castingPlayer == nullptr")); + + castingPlayer->Disconnect(); +} + +JNI_METHOD(jobject, getEndpoints) +(JNIEnv * env, jobject thiz) +{ + chip::DeviceLayer::StackLock lock; + ChipLogProgress(AppServer, "MatterCastingPlayer-JNI::getEndpoints() called"); + + CastingPlayer * castingPlayer = support::convertCastingPlayerFromJavaToCpp(thiz); + VerifyOrReturnValue(castingPlayer != nullptr, nullptr, + ChipLogError(AppServer, "MatterCastingPlayer-JNI::getEndpoints() castingPlayer == nullptr")); + + const std::vector> endpoints = castingPlayer->GetEndpoints(); + jobject jEndpointList = nullptr; + chip::JniReferences::GetInstance().CreateArrayList(jEndpointList); + for (memory::Strong endpoint : endpoints) + { + jobject matterEndpointJavaObject = support::convertEndpointFromCppToJava(endpoint); + VerifyOrReturnValue(matterEndpointJavaObject != nullptr, jEndpointList, + ChipLogError(AppServer, "MatterCastingPlayer-JNI::getEndpoints(): Could not create Endpoint jobject")); + chip::JniReferences::GetInstance().AddToList(jEndpointList, matterEndpointJavaObject); + } + return jEndpointList; +} + +}; // namespace core +}; // namespace casting +}; // namespace matter diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/MatterCastingPlayer-JNI.h b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/MatterCastingPlayer-JNI.h new file mode 100644 index 00000000000000..7f58162fff52b3 --- /dev/null +++ b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/MatterCastingPlayer-JNI.h @@ -0,0 +1,48 @@ +/* + * + * Copyright (c) 2020-2024 Project CHIP Authors + * All rights reserved. + * + * 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. + */ + +#pragma once + +#include "../support/MatterCallback-JNI.h" +#include "core/CastingPlayer.h" // from tv-casting-common + +#include + +namespace matter { +namespace casting { +namespace core { + +class MatterCastingPlayerJNI +{ +public: + MatterCastingPlayerJNI() : mConnectionSuccessHandler([](void *) { return nullptr; }) {} + support::MatterCallbackJNI mConnectionSuccessHandler; + support::MatterFailureCallbackJNI mConnectionFailureHandler; + +private: + friend MatterCastingPlayerJNI & MatterCastingPlayerJNIMgr(); + static MatterCastingPlayerJNI sInstance; +}; + +inline class MatterCastingPlayerJNI & MatterCastingPlayerJNIMgr() +{ + return MatterCastingPlayerJNI::sInstance; +} +}; // namespace core +}; // namespace casting +}; // namespace matter diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/MatterEndpoint-JNI.cpp b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/MatterEndpoint-JNI.cpp new file mode 100644 index 00000000000000..2e28b873599a9a --- /dev/null +++ b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/MatterEndpoint-JNI.cpp @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2020-2024 Project CHIP Authors + * All rights reserved. + * + * 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. + * + */ + +#include "MatterEndpoint-JNI.h" + +#include "../JNIDACProvider.h" +#include "../support/Converters-JNI.h" +#include "../support/MatterCallback-JNI.h" +#include "../support/RotatingDeviceIdUniqueIdProvider-JNI.h" +#include "clusters/Clusters.h" // from tv-casting-common +#include "core/CastingApp.h" // from tv-casting-common +#include "core/CastingPlayer.h" // from tv-casting-common +#include "core/CastingPlayerDiscovery.h" // from tv-casting-common +#include "core/Endpoint.h" // from tv-casting-common + +#include +#include +#include +#include +#include + +using namespace chip; + +#define JNI_METHOD(RETURN, METHOD_NAME) \ + extern "C" JNIEXPORT RETURN JNICALL Java_com_matter_casting_core_MatterEndpoint_##METHOD_NAME + +namespace matter { +namespace casting { +namespace core { + +MatterEndpointJNI MatterEndpointJNI::sInstance; + +JNI_METHOD(jint, getId) +(JNIEnv * env, jobject thiz) +{ + chip::DeviceLayer::StackLock lock; + ChipLogProgress(AppServer, "MatterEndpoint-JNI::getId() called"); + Endpoint * endpoint = support::convertEndpointFromJavaToCpp(thiz); + VerifyOrReturnValue(endpoint != nullptr, -1, ChipLogError(AppServer, "MatterEndpoint-JNI::getId() endpoint == nullptr")); + return endpoint->GetId(); +} + +JNI_METHOD(jint, getProductId) +(JNIEnv * env, jobject thiz) +{ + chip::DeviceLayer::StackLock lock; + ChipLogProgress(AppServer, "MatterEndpoint-JNI::getProductId() called"); + Endpoint * endpoint = support::convertEndpointFromJavaToCpp(thiz); + VerifyOrReturnValue(endpoint != nullptr, -1, ChipLogError(AppServer, "MatterEndpoint-JNI::getProductId() endpoint == nullptr")); + return endpoint->GetProductId(); +} + +JNI_METHOD(jint, getVendorId) +(JNIEnv * env, jobject thiz) +{ + chip::DeviceLayer::StackLock lock; + ChipLogProgress(AppServer, "MatterEndpoint-JNI::getVendorId() called"); + Endpoint * endpoint = support::convertEndpointFromJavaToCpp(thiz); + VerifyOrReturnValue(endpoint != nullptr, -1, ChipLogError(AppServer, "MatterEndpoint-JNI::getVendorId() endpoint == nullptr")); + return endpoint->GetVendorId(); +} + +JNI_METHOD(jobject, getCastingPlayer) +(JNIEnv * env, jobject thiz) +{ + chip::DeviceLayer::StackLock lock; + ChipLogProgress(AppServer, "MatterEndpoint-JNI::getCastingPlayer() called"); + Endpoint * endpoint = support::convertEndpointFromJavaToCpp(thiz); + VerifyOrReturnValue(endpoint != nullptr, nullptr, + ChipLogError(AppServer, "MatterEndpoint-JNI::getCastingPlayer() endpoint == nullptr")); + return support::convertCastingPlayerFromCppToJava(std::shared_ptr(endpoint->GetCastingPlayer())); +} + +}; // namespace core +}; // namespace casting +}; // namespace matter diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/CastingPlayer-JNI.h b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/MatterEndpoint-JNI.h similarity index 66% rename from examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/CastingPlayer-JNI.h rename to examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/MatterEndpoint-JNI.h index 2870866895c868..f9534435ab1903 100644 --- a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/CastingPlayer-JNI.h +++ b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/MatterEndpoint-JNI.h @@ -1,6 +1,6 @@ /* * - * Copyright (c) 2024 Project CHIP Authors + * Copyright (c) 2020-24 Project CHIP Authors * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,23 +18,26 @@ #pragma once +#include "core/Endpoint.h" // from tv-casting-common + #include +#include +#include namespace matter { namespace casting { namespace core { -class CastingPlayerJNI +class MatterEndpointJNI { -public: private: - friend CastingPlayerJNI & CastingAppJNIMgr(); - static CastingPlayerJNI sInstance; + friend MatterEndpointJNI & MatterEndpointJNIMgr(); + static MatterEndpointJNI sInstance; }; -inline class CastingPlayerJNI & CastingAppJNIMgr() +inline class MatterEndpointJNI & MatterEndpointJNIMgr() { - return CastingPlayerJNI::sInstance; + return MatterEndpointJNI::sInstance; } }; // namespace core }; // namespace casting diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/support/CastingPlayerConverter-JNI.cpp b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/support/CastingPlayerConverter-JNI.cpp deleted file mode 100644 index 72c3677f357707..00000000000000 --- a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/support/CastingPlayerConverter-JNI.cpp +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright (c) 2023 Project CHIP Authors - * All rights reserved. - * - * 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. - * - */ - -#include "CastingPlayerConverter-JNI.h" -#include - -namespace matter { -namespace casting { -namespace support { - -using namespace chip; - -jobject createJCastingPlayer(matter::casting::memory::Strong player) -{ - ChipLogProgress(AppServer, "CastingPlayerConverter-JNI.createJCastingPlayer() called"); - JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread(); - - // Get a reference to the MatterCastingPlayer Java class - jclass matterCastingPlayerJavaClass = env->FindClass("com/matter/casting/core/MatterCastingPlayer"); - if (matterCastingPlayerJavaClass == nullptr) - { - ChipLogError(AppServer, - "CastingPlayerConverter-JNI.createJCastingPlayer() could not locate MatterCastingPlayer Java class"); - return nullptr; - } - - // Get the constructor for the com/matter/casting/core/MatterCastingPlayer Java class - jmethodID constructor = - env->GetMethodID(matterCastingPlayerJavaClass, "", - "(ZLjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;IIIJ)V"); - if (constructor == nullptr) - { - ChipLogError( - AppServer, - "CastingPlayerConverter-JNI.createJCastingPlayer() could not locate MatterCastingPlayer Java class constructor"); - return nullptr; - } - - // Convert the CastingPlayer fields to MatterCastingPlayer Java types - jobject jIpAddressList = nullptr; - const chip::Inet::IPAddress * ipAddresses = player->GetIPAddresses(); - if (ipAddresses != nullptr) - { - chip::JniReferences::GetInstance().CreateArrayList(jIpAddressList); - for (size_t i = 0; i < player->GetNumIPs() && i < chip::Dnssd::CommonResolutionData::kMaxIPAddresses; i++) - { - char addrCString[chip::Inet::IPAddress::kMaxStringLength]; - ipAddresses[i].ToString(addrCString, chip::Inet::IPAddress::kMaxStringLength); - jstring jIPAddressStr = env->NewStringUTF(addrCString); - - jclass jIPAddressClass = env->FindClass("java/net/InetAddress"); - jmethodID jGetByNameMid = - env->GetStaticMethodID(jIPAddressClass, "getByName", "(Ljava/lang/String;)Ljava/net/InetAddress;"); - jobject jIPAddress = env->CallStaticObjectMethod(jIPAddressClass, jGetByNameMid, jIPAddressStr); - - chip::JniReferences::GetInstance().AddToList(jIpAddressList, jIPAddress); - } - } - - // Create a new instance of the MatterCastingPlayer Java class - jobject jMatterCastingPlayer = nullptr; - jMatterCastingPlayer = env->NewObject(matterCastingPlayerJavaClass, constructor, static_cast(player->IsConnected()), - env->NewStringUTF(player->GetId()), env->NewStringUTF(player->GetHostName()), - env->NewStringUTF(player->GetDeviceName()), env->NewStringUTF(player->GetInstanceName()), - jIpAddressList, (jint) (player->GetPort()), (jint) (player->GetProductId()), - (jint) (player->GetVendorId()), (jlong) (player->GetDeviceType())); - if (jMatterCastingPlayer == nullptr) - { - ChipLogError(AppServer, - "CastingPlayerConverter-JNI.createJCastingPlayer() Warning: Could not create MatterCastingPlayer Java object"); - return jMatterCastingPlayer; - } - // Set the value of the _cppCastingPlayer field in the Java object to the C++ CastingPlayer pointer. - jfieldID longFieldId = env->GetFieldID(matterCastingPlayerJavaClass, "_cppCastingPlayer", "J"); - env->SetLongField(jMatterCastingPlayer, longFieldId, reinterpret_cast(player.get())); - return jMatterCastingPlayer; -} - -}; // namespace support -}; // namespace casting -}; // namespace matter diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/support/CastingPlayerConverter-JNI.h b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/support/CastingPlayerConverter-JNI.h deleted file mode 100644 index 91b0ac6c79b92f..00000000000000 --- a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/support/CastingPlayerConverter-JNI.h +++ /dev/null @@ -1,41 +0,0 @@ -/* - * - * Copyright (c) 2023-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. - */ -#pragma once - -#include "core/CastingPlayer.h" - -#include - -#include - -namespace matter { -namespace casting { -namespace support { - -/** - * @brief Convertes a native CastingPlayer into a MatterCastingPlayer jobject - * - * @param CastingPlayer represents a Matter commissioner that is able to play media to a physical - * output or to a display screen which is part of the device. - * - * @return pointer to the CastingPlayer jobject if created successfully, nullptr otherwise. - */ -jobject createJCastingPlayer(matter::casting::memory::Strong player); - -}; // namespace support -}; // namespace casting -}; // namespace matter diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/support/Converters-JNI.cpp b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/support/Converters-JNI.cpp new file mode 100644 index 00000000000000..9798f2b48b9359 --- /dev/null +++ b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/support/Converters-JNI.cpp @@ -0,0 +1,346 @@ +/* + * Copyright (c) 2020-24 Project CHIP Authors + * All rights reserved. + * + * 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. + * + */ + +#include "Converters-JNI.h" +#include + +namespace matter { +namespace casting { +namespace support { + +using namespace chip; + +jobject convertMatterErrorFromCppToJava(CHIP_ERROR inErr) +{ + ChipLogProgress(AppServer, "convertMatterErrorFromCppToJava() called"); + JNIEnv * env = chip::JniReferences::GetInstance().GetEnvForCurrentThread(); + VerifyOrReturnValue(env != nullptr, nullptr, ChipLogError(AppServer, "Could not get JNIEnv for current thread")); + + jclass jMatterErrorClass; + CHIP_ERROR err = + chip::JniReferences::GetInstance().GetLocalClassRef(env, "com/matter/casting/support/MatterError", jMatterErrorClass); + VerifyOrReturnValue(err == CHIP_NO_ERROR, nullptr); + + jmethodID jMatterErrorConstructor = env->GetMethodID(jMatterErrorClass, "", "(JLjava/lang/String;)V"); + if (jMatterErrorConstructor == nullptr) + { + ChipLogError(AppServer, "Failed to access MatterError constructor"); + env->ExceptionClear(); + return nullptr; + } + + return env->NewObject(jMatterErrorClass, jMatterErrorConstructor, inErr.AsInteger(), nullptr); +} + +jobject convertEndpointFromCppToJava(matter::casting::memory::Strong endpoint) +{ + ChipLogProgress(AppServer, "convertEndpointFromCppToJava() called"); + JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread(); + VerifyOrReturnValue(env != nullptr, nullptr, ChipLogError(AppServer, "Could not get JNIEnv for current thread")); + + // Get a reference to the MatterEndpoint Java class + jclass matterEndpointJavaClass = env->FindClass("com/matter/casting/core/MatterEndpoint"); + if (matterEndpointJavaClass == nullptr) + { + ChipLogError(AppServer, "convertEndpointFromCppToJava() could not locate MatterEndpoint Java class"); + env->ExceptionClear(); + return nullptr; + } + + // Get the constructor for the com/matter/casting/core/MatterEndpoint Java class + jmethodID constructor = env->GetMethodID(matterEndpointJavaClass, "", "()V"); + if (constructor == nullptr) + { + ChipLogError(AppServer, "convertEndpointFromCppToJava() could not locate MatterEndpoint Java class constructor"); + env->ExceptionClear(); + return nullptr; + } + + // Create a new instance of the MatterEndpoint Java class + jobject jMatterEndpoint = nullptr; + jMatterEndpoint = env->NewObject(matterEndpointJavaClass, constructor); + if (jMatterEndpoint == nullptr) + { + ChipLogError(AppServer, "convertEndpointFromCppToJava(): Could not create MatterEndpoint Java object"); + return jMatterEndpoint; + } + // Set the value of the _cppEndpoint field in the Java object to the C++ Endpoint pointer. + jfieldID longFieldId = env->GetFieldID(matterEndpointJavaClass, "_cppEndpoint", "J"); + env->SetLongField(jMatterEndpoint, longFieldId, reinterpret_cast(endpoint.get())); + return jMatterEndpoint; +} + +/** + * @brief Get the matter::casting::core::Endpoint object from the jobject jEndpointObject + */ +core::Endpoint * convertEndpointFromJavaToCpp(jobject jEndpointObject) +{ + ChipLogProgress(AppServer, "convertEndpointFromJavaToCpp() called"); + JNIEnv * env = chip::JniReferences::GetInstance().GetEnvForCurrentThread(); + VerifyOrReturnValue(env != nullptr, nullptr, ChipLogError(AppServer, "Could not get JNIEnv for current thread")); + + jclass endpointClass = env->GetObjectClass(jEndpointObject); + if (endpointClass == nullptr) + { + ChipLogError(AppServer, "convertEndpointFromJavaToCpp() could not locate Endpoint Java class"); + env->ExceptionClear(); + return nullptr; + } + + jfieldID _cppEndpointFieldId = env->GetFieldID(endpointClass, "_cppEndpoint", "J"); + VerifyOrReturnValue(_cppEndpointFieldId != nullptr, nullptr, + ChipLogError(AppServer, "convertEndpointFromJavaToCpp _cppEndpointFieldId == nullptr")); + jlong _cppEndpointValue = env->GetLongField(jEndpointObject, _cppEndpointFieldId); + return reinterpret_cast(_cppEndpointValue); +} + +jobject convertCastingPlayerFromCppToJava(matter::casting::memory::Strong player) +{ + ChipLogProgress(AppServer, "convertCastingPlayerFromCppToJava() called"); + JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread(); + VerifyOrReturnValue(env != nullptr, nullptr, ChipLogError(AppServer, "Could not get JNIEnv for current thread")); + + // Get a reference to the MatterCastingPlayer Java class + jclass matterCastingPlayerJavaClass = env->FindClass("com/matter/casting/core/MatterCastingPlayer"); + if (matterCastingPlayerJavaClass == nullptr) + { + ChipLogError(AppServer, "convertCastingPlayerFromCppToJava() could not locate MatterCastingPlayer Java class"); + env->ExceptionClear(); + return nullptr; + } + + // Get the constructor for the com/matter/casting/core/MatterCastingPlayer Java class + jmethodID constructor = + env->GetMethodID(matterCastingPlayerJavaClass, "", + "(ZLjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;IIIJ)V"); + if (constructor == nullptr) + { + ChipLogError(AppServer, "convertCastingPlayerFromCppToJava() could not locate MatterCastingPlayer Java class constructor"); + env->ExceptionClear(); + return nullptr; + } + + // Convert the CastingPlayer fields to MatterCastingPlayer Java types + jobject jIpAddressList = nullptr; + const chip::Inet::IPAddress * ipAddresses = player->GetIPAddresses(); + if (ipAddresses != nullptr) + { + chip::JniReferences::GetInstance().CreateArrayList(jIpAddressList); + for (size_t i = 0; i < player->GetNumIPs() && i < chip::Dnssd::CommonResolutionData::kMaxIPAddresses; i++) + { + char addrCString[chip::Inet::IPAddress::kMaxStringLength]; + ipAddresses[i].ToString(addrCString, chip::Inet::IPAddress::kMaxStringLength); + jstring jIPAddressStr = env->NewStringUTF(addrCString); + + jclass jIPAddressClass = env->FindClass("java/net/InetAddress"); + jmethodID jGetByNameMid = + env->GetStaticMethodID(jIPAddressClass, "getByName", "(Ljava/lang/String;)Ljava/net/InetAddress;"); + jobject jIPAddress = env->CallStaticObjectMethod(jIPAddressClass, jGetByNameMid, jIPAddressStr); + + chip::JniReferences::GetInstance().AddToList(jIpAddressList, jIPAddress); + } + } + + // Create a new instance of the MatterCastingPlayer Java class + jobject jMatterCastingPlayer = nullptr; + jMatterCastingPlayer = env->NewObject(matterCastingPlayerJavaClass, constructor, static_cast(player->IsConnected()), + env->NewStringUTF(player->GetId()), env->NewStringUTF(player->GetHostName()), + env->NewStringUTF(player->GetDeviceName()), env->NewStringUTF(player->GetInstanceName()), + jIpAddressList, (jint) (player->GetPort()), (jint) (player->GetProductId()), + (jint) (player->GetVendorId()), (jlong) (player->GetDeviceType())); + if (jMatterCastingPlayer == nullptr) + { + ChipLogError(AppServer, "convertCastingPlayerFromCppToJava(): Could not create MatterCastingPlayer Java object"); + env->ExceptionClear(); + return jMatterCastingPlayer; + } + // Set the value of the _cppCastingPlayer field in the Java object to the C++ CastingPlayer pointer. + jfieldID longFieldId = env->GetFieldID(matterCastingPlayerJavaClass, "_cppCastingPlayer", "J"); + env->SetLongField(jMatterCastingPlayer, longFieldId, reinterpret_cast(player.get())); + return jMatterCastingPlayer; +} + +core::CastingPlayer * convertCastingPlayerFromJavaToCpp(jobject jCastingPlayerObject) +{ + ChipLogProgress(AppServer, "convertCastingPlayerFromJavaToCpp() called"); + JNIEnv * env = chip::JniReferences::GetInstance().GetEnvForCurrentThread(); + VerifyOrReturnValue(env != nullptr, nullptr, ChipLogError(AppServer, "Could not get JNIEnv for current thread")); + + jclass castingPlayerClass = env->GetObjectClass(jCastingPlayerObject); + if (castingPlayerClass == nullptr) + { + ChipLogError(AppServer, "convertCastingPlayerFromJavaToCpp() could not locate CastingPlayer Java class"); + env->ExceptionClear(); + return nullptr; + } + + jfieldID _cppCastingPlayerFieldId = env->GetFieldID(castingPlayerClass, "_cppCastingPlayer", "J"); + VerifyOrReturnValue(_cppCastingPlayerFieldId != nullptr, nullptr, + ChipLogError(AppServer, "convertCastingPlayerFromJavaToCpp _cppCastingPlayerFieldId == nullptr")); + jlong _cppCastingPlayerValue = env->GetLongField(jCastingPlayerObject, _cppCastingPlayerFieldId); + return reinterpret_cast(_cppCastingPlayerValue); +} + +jobject convertClusterFromCppToJava(matter::casting::memory::Strong cluster, const char * className) +{ + ChipLogProgress(AppServer, "convertClusterFromCppToJava() called"); + VerifyOrReturnValue(cluster.get() != nullptr, nullptr, + ChipLogError(AppServer, "convertClusterFromCppToJava() cluster.get() == nullptr")); + JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread(); + VerifyOrReturnValue(env != nullptr, nullptr, ChipLogError(AppServer, "Could not get JNIEnv for current thread")); + + // Get a reference to the cluster's Java class + jclass clusterJavaClass = env->FindClass(className); + if (clusterJavaClass == nullptr) + { + ChipLogError(AppServer, "convertClusterFromCppToJava could not locate cluster's Java class"); + env->ExceptionClear(); + return nullptr; + } + + // Get the constructor for the cluster's Java class + jmethodID constructor = env->GetMethodID(clusterJavaClass, "", "()V"); + if (constructor == nullptr) + { + ChipLogError(AppServer, "convertClusterFromCppToJava could not locate cluster's Java class constructor"); + env->ExceptionClear(); + return nullptr; + } + + // Create a new instance of the cluster's Java class + jobject jMatterCluster = nullptr; + jMatterCluster = env->NewObject(clusterJavaClass, constructor); + if (jMatterCluster == nullptr) + { + ChipLogError(AppServer, "convertClusterFromCppToJava: Could not create cluster's Java object"); + return jMatterCluster; + } + // Set the value of the _cppEndpoint field in the Java object to the C++ Endpoint pointer. + jfieldID longFieldId = env->GetFieldID(clusterJavaClass, "_cppCluster", "J"); + env->SetLongField(jMatterCluster, longFieldId, reinterpret_cast(cluster.get())); + return jMatterCluster; +} + +/** + * @brief Get the matter::casting::core::Cluster object from the jobject jClusterObject + */ +core::BaseCluster * convertClusterFromJavaToCpp(jobject jClusterObject) +{ + ChipLogProgress(AppServer, "convertClusterFromJavaToCpp() called"); + JNIEnv * env = chip::JniReferences::GetInstance().GetEnvForCurrentThread(); + VerifyOrReturnValue(env != nullptr, nullptr, ChipLogError(AppServer, "Could not get JNIEnv for current thread")); + + jclass clusterClass = env->GetObjectClass(jClusterObject); + if (clusterClass == nullptr) + { + ChipLogError(AppServer, "convertClusterFromJavaToCpp could not locate cluster's Java class"); + env->ExceptionClear(); + return nullptr; + } + + jfieldID _cppClusterFieldId = env->GetFieldID(clusterClass, "_cppCluster", "J"); + VerifyOrReturnValue(_cppClusterFieldId != nullptr, nullptr, + ChipLogError(AppServer, "convertClusterFromJavaToCpp() _cppCluster == nullptr")); + jlong _cppClusterValue = env->GetLongField(jClusterObject, _cppClusterFieldId); + return reinterpret_cast(_cppClusterValue); +} + +jobject convertCommandFromCppToJava(void * command, const char * className) +{ + ChipLogProgress(AppServer, "convertCommandFromCppToJava() called"); + VerifyOrReturnValue(command != nullptr, nullptr, ChipLogError(AppServer, "convertCommandFromCppToJava() command == nullptr")); + JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread(); + VerifyOrReturnValue(env != nullptr, nullptr, ChipLogError(AppServer, "Could not get JNIEnv for current thread")); + + // Get a reference to the command's Java class + jclass commandJavaClass = env->FindClass(className); + if (commandJavaClass == nullptr) + { + ChipLogError(AppServer, "convertCommandFromCppToJava() could not locate command's Java class"); + env->ExceptionClear(); + return nullptr; + } + + // Get the constructor for the command's Java class + jmethodID constructor = env->GetMethodID(commandJavaClass, "", "()V"); + if (constructor == nullptr) + { + ChipLogError(AppServer, "convertCommandFromCppToJava() could not locate command's Java class constructor"); + env->ExceptionClear(); + return nullptr; + } + + // Create a new instance of the command's Java class + jobject jMatterCommand = env->NewObject(commandJavaClass, constructor); + if (jMatterCommand == nullptr) + { + ChipLogError(AppServer, "convertCommandFromCppToJava(): Could not create command's Java object"); + env->ExceptionClear(); + return jMatterCommand; + } + // Set the value of the _cppEndpoint field in the Java object to the C++ Endpoint pointer. + jfieldID longFieldId = env->GetFieldID(commandJavaClass, "_cppCommand", "J"); + env->SetLongField(jMatterCommand, longFieldId, reinterpret_cast(command)); + return jMatterCommand; +} + +/** + * @brief Get the matter::casting::core::Command object from the jobject jCommandObject + */ +void * convertCommandFromJavaToCpp(jobject jCommandObject) +{ + ChipLogProgress(AppServer, "convertCommandFromJavaToCpp() called"); + JNIEnv * env = chip::JniReferences::GetInstance().GetEnvForCurrentThread(); + VerifyOrReturnValue(env != nullptr, nullptr, ChipLogError(AppServer, "Could not get JNIEnv for current thread")); + + jclass commandClass = env->GetObjectClass(jCommandObject); + if (commandClass == nullptr) + { + ChipLogError(AppServer, "convertCommandFromJavaToCpp() could not locate command's Java class"); + env->ExceptionClear(); + return nullptr; + } + + jfieldID _cppCommandFieldId = env->GetFieldID(commandClass, "_cppCommand", "J"); + VerifyOrReturnValue(_cppCommandFieldId != nullptr, nullptr, + ChipLogError(AppServer, "convertCommandFromJavaToCpp() _cppCommand == nullptr")); + jlong _cppCommandValue = env->GetLongField(jCommandObject, _cppCommandFieldId); + return reinterpret_cast(_cppCommandValue); +} + +jobject convertLongFromCppToJava(uint64_t responseData) +{ + ChipLogProgress(AppServer, "convertLongFromCppToJava() called"); + JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread(); + VerifyOrReturnValue(env != nullptr, nullptr, ChipLogError(AppServer, "Could not get JNIEnv for current thread")); + + jclass responseTypeClass = env->FindClass("java/lang/Long"); + if (responseTypeClass == nullptr) + { + ChipLogError(AppServer, "convertLongFromCppToJava: Class for Response Type not found!"); + env->ExceptionClear(); + return nullptr; + } + + jmethodID constructor = env->GetMethodID(responseTypeClass, "", "(J)V"); + return env->NewObject(responseTypeClass, constructor, responseData); +} + +}; // namespace support +}; // namespace casting +}; // namespace matter diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/support/Converters-JNI.h b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/support/Converters-JNI.h new file mode 100644 index 00000000000000..ecc3a95d15bd74 --- /dev/null +++ b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/support/Converters-JNI.h @@ -0,0 +1,77 @@ +/* + * + * Copyright (c) 2020-24 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. + */ +#pragma once + +#include "core/BaseCluster.h" +#include "core/CastingPlayer.h" +#include "core/Command.h" +#include "core/Endpoint.h" + +#include + +#include + +namespace matter { +namespace casting { +namespace support { + +jobject convertMatterErrorFromCppToJava(CHIP_ERROR inErr); + +/** + * @brief Converts a native Endpoint into a MatterEndpoint jobject + * + * @return pointer to the Endpoint jobject if created successfully, nullptr otherwise. + */ +jobject convertEndpointFromCppToJava(matter::casting::memory::Strong endpoint); + +core::Endpoint * convertEndpointFromJavaToCpp(jobject jEndpointObject); + +/** + * @brief Convertes a native CastingPlayer into a MatterCastingPlayer jobject + * + * @param CastingPlayer represents a Matter commissioner that is able to play media to a physical + * output or to a display screen which is part of the device. + * + * @return pointer to the CastingPlayer jobject if created successfully, nullptr otherwise. + */ +jobject convertCastingPlayerFromCppToJava(matter::casting::memory::Strong player); + +core::CastingPlayer * convertCastingPlayerFromJavaToCpp(jobject jCastingPlayerObject); + +/** + * @brief Converts a native Cluster into a MatterCluster jobject + * + * @return pointer to the Cluster jobject if created successfully, nullptr otherwise. + */ +jobject convertClusterFromCppToJava(matter::casting::memory::Strong cluster, const char * className); + +core::BaseCluster * convertClusterFromJavaToCpp(jobject jClusterObject); + +/** + * @brief Converts a native Command into a MatterCommand jobject + * + * @return pointer to the Command jobject if created successfully, nullptr otherwise. + */ +jobject convertCommandFromCppToJava(void * command, const char * className); + +void * convertCommandFromJavaToCpp(jobject jCommandObject); + +jobject convertLongFromCppToJava(uint64_t responseData); + +}; // namespace support +}; // namespace casting +}; // namespace matter diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/support/ErrorConverter-JNI.cpp b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/support/ErrorConverter-JNI.cpp deleted file mode 100644 index 1dce6f19d74776..00000000000000 --- a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/support/ErrorConverter-JNI.cpp +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (c) 2023 Project CHIP Authors - * All rights reserved. - * - * 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. - * - */ - -#include "ErrorConverter-JNI.h" -#include - -namespace matter { -namespace casting { -namespace support { - -using namespace chip; - -jobject createJMatterError(CHIP_ERROR inErr) -{ - JNIEnv * env = chip::JniReferences::GetInstance().GetEnvForCurrentThread(); - jclass jMatterErrorClass; - CHIP_ERROR err = - chip::JniReferences::GetInstance().GetLocalClassRef(env, "com/matter/casting/support/MatterError", jMatterErrorClass); - VerifyOrReturnValue(err == CHIP_NO_ERROR, nullptr); - - jmethodID jMatterErrorConstructor = env->GetMethodID(jMatterErrorClass, "", "(JLjava/lang/String;)V"); - - return env->NewObject(jMatterErrorClass, jMatterErrorConstructor, inErr.AsInteger(), nullptr); -} - -}; // namespace support -}; // namespace casting -}; // namespace matter diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/support/MatterCallback-JNI.h b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/support/MatterCallback-JNI.h new file mode 100644 index 00000000000000..3c56c426359d5c --- /dev/null +++ b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/support/MatterCallback-JNI.h @@ -0,0 +1,95 @@ +/* + * + * Copyright (c) 2020-24 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. + */ +#pragma once + +#include "Converters-JNI.h" + +#include +#include +#include + +namespace matter { +namespace casting { +namespace support { + +template +class MatterCallbackJNI +{ +public: + MatterCallbackJNI(std::function conversionFn) { mConversionFn = conversionFn; } + + MatterCallbackJNI(const char * methodSignature, std::function conversionFn) + { + mMethodSignature = methodSignature; + mConversionFn = conversionFn; + } + + CHIP_ERROR SetUp(JNIEnv * env, jobject inCallback) + { + ChipLogProgress(AppServer, "MatterCallbackJNI::SetUp called"); + VerifyOrReturnError(env != nullptr, CHIP_JNI_ERROR_NO_ENV, ChipLogError(AppServer, "JNIEnv was null!")); + + ReturnErrorOnFailure(mCallbackObject.Init(inCallback)); + + jclass mClazz = env->GetObjectClass(mCallbackObject.ObjectRef()); + VerifyOrReturnError(mClazz != nullptr, CHIP_JNI_ERROR_TYPE_NOT_FOUND, + ChipLogError(AppServer, "Failed to get callback Java class")); + + jclass mSuperClazz = env->GetSuperclass(mClazz); + VerifyOrReturnError(mSuperClazz != nullptr, CHIP_JNI_ERROR_TYPE_NOT_FOUND, + ChipLogError(AppServer, "Failed to get callback's parent's Java class")); + + mMethod = env->GetMethodID(mClazz, "handleInternal", mMethodSignature); + VerifyOrReturnError( + mMethod != nullptr, CHIP_JNI_ERROR_METHOD_NOT_FOUND, + ChipLogError(AppServer, "Failed to access 'handleInternal' method with signature %s", mMethodSignature)); + + return CHIP_NO_ERROR; + } + + void Handle(T responseData) + { + ChipLogProgress(AppServer, "MatterCallbackJNI::Handle called"); + + JNIEnv * env = chip::JniReferences::GetInstance().GetEnvForCurrentThread(); + VerifyOrReturn(env != nullptr, ChipLogError(AppServer, "Failed to get JNIEnv")); + + jobject jResponseData = mConversionFn(responseData); + + chip::DeviceLayer::StackUnlock unlock; + VerifyOrReturn(mCallbackObject.HasValidObjectRef(), + ChipLogError(AppServer, "MatterCallbackJNI::Handle mCallbackObject has no valid ObjectRef")); + VerifyOrReturn(mMethod != nullptr, ChipLogError(AppServer, "MatterCallbackJNI::Handle mMethod is nullptr")); + env->CallVoidMethod(mCallbackObject.ObjectRef(), mMethod, jResponseData); + } + +protected: + chip::JniGlobalReference mCallbackObject; + jmethodID mMethod = nullptr; + const char * mMethodSignature = "(Ljava/lang/Object;)V"; + std::function mConversionFn = nullptr; +}; + +class MatterFailureCallbackJNI : public MatterCallbackJNI +{ +public: + MatterFailureCallbackJNI() : MatterCallbackJNI(matter::casting::support::convertMatterErrorFromCppToJava) {} +}; + +}; // namespace support +}; // namespace casting +}; // namespace matter diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/support/RotatingDeviceIdUniqueIdProvider-JNI.cpp b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/support/RotatingDeviceIdUniqueIdProvider-JNI.cpp index f1d29cd063c450..2af0e1000d6aaf 100644 --- a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/support/RotatingDeviceIdUniqueIdProvider-JNI.cpp +++ b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/support/RotatingDeviceIdUniqueIdProvider-JNI.cpp @@ -82,7 +82,7 @@ MutableByteSpan * RotatingDeviceIdUniqueIdProviderJNI::Get() ChipLogProgress(AppServer, "RotatingDeviceIdUniqueIdProviderJNI.Get() called"); mRotatingDeviceIdUniqueIdSpan = MutableByteSpan(mRotatingDeviceIdUniqueId); CHIP_ERROR err = GetJavaByteByMethod(mGetMethod, mRotatingDeviceIdUniqueIdSpan); - VerifyOrReturnValue(err != CHIP_NO_ERROR, nullptr, + VerifyOrReturnValue(err == CHIP_NO_ERROR, nullptr, ChipLogError(AppServer, "Error calling GetJavaByteByMethod %" CHIP_ERROR_FORMAT, err.Format())); return &mRotatingDeviceIdUniqueIdSpan; } diff --git a/examples/tv-casting-app/android/App/app/src/main/res/layout/fragment_matter_action_selector.xml b/examples/tv-casting-app/android/App/app/src/main/res/layout/fragment_matter_action_selector.xml new file mode 100644 index 00000000000000..ae61681a0bbf2f --- /dev/null +++ b/examples/tv-casting-app/android/App/app/src/main/res/layout/fragment_matter_action_selector.xml @@ -0,0 +1,39 @@ + + + + + + + +