From 74462ffbf2159525b0a612a2277400464073426d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arkadiusz=20Ba=C5=82ys?= Date: Fri, 25 Nov 2022 15:50:18 +0100 Subject: [PATCH] [nrfconnect] Added Wi-Fi support to nrfconnect examples. (#23715) * [nrfconnect] Added Wi-Fi support to nrfconnect examples. In this commit: - Added the first support for Wi-Fi to nrfconnect examples: lightinig-app, lock-app, light-switch-app, all-clusters-app - Enabled CI all wi-fi samples checking for Wi-Fi target nrf7002 - all-clusters-app on each commit, others related to nrfconnect sources. - Aligned Common api to NCS 2.1.1. - Updated documentation for all samples and added Wi-Fi nRF7002 target and descriptions * Restyled by clang-format * Restyled by prettier-markdown * Some fixes after a review. Co-authored-by: Restyled.io --- .github/workflows/examples-nrfconnect.yaml | 35 ++ config/nrfconnect/chip-module/Kconfig | 9 + config/zephyr/Kconfig | 8 +- .../nrfconnect_android_commissioning.md | 7 +- .../nrfconnect/CMakeLists.txt | 1 + examples/all-clusters-app/nrfconnect/Kconfig | 13 + .../all-clusters-app/nrfconnect/README.md | 106 +++-- .../boards/nrf52840dk_nrf52840.overlay | 3 - .../boards/nrf7002dk_nrf5340_cpuapp.overlay | 21 + .../nrfconnect/child_image/hci_rpmsg/prj.conf | 25 ++ .../child_image/hci_rpmsg/prj_no_dfu.conf | 25 ++ .../child_image/hci_rpmsg/prj_release.conf | 25 ++ .../pm_static_dfu.yml | 56 +++ .../nrfconnect/main/AppTask.cpp | 334 ++++++++------ .../nrfconnect/main/include/AppConfig.h | 14 + .../nrfconnect/main/include/AppEvent.h | 39 +- .../nrfconnect/main/include/AppTask.h | 63 +-- .../all-clusters-app/nrfconnect/main/main.cpp | 2 +- examples/all-clusters-app/nrfconnect/prj.conf | 21 +- .../all-clusters-app/nrfconnect/prj_dfu.conf | 20 +- .../nrfconnect/prj_release.conf | 21 +- .../nrfconnect/Kconfig | 13 + .../nrfconnect/README.md | 20 +- .../boards/nrf52840dk_nrf52840.overlay | 3 - .../nrfconnect/main/AppTask.cpp | 222 +++++---- .../nrfconnect/main/include/AppConfig.h | 3 + .../nrfconnect/main/include/AppEvent.h | 34 +- .../nrfconnect/main/include/AppTask.h | 55 +-- .../nrfconnect/main/main.cpp | 2 +- .../nrfconnect/prj.conf | 21 +- .../nrfconnect/prj_dfu.conf | 25 +- .../nrfconnect/prj_release.conf | 21 +- .../nrfconnect/CMakeLists.txt | 1 + examples/light-switch-app/nrfconnect/Kconfig | 17 + .../light-switch-app/nrfconnect/README.md | 157 +++++-- .../boards/nrf7002dk_nrf5340_cpuapp.overlay | 22 + .../nrfconnect/child_image/hci_rpmsg/prj.conf | 25 ++ .../child_image/hci_rpmsg/prj_no_dfu.conf | 25 ++ .../child_image/hci_rpmsg/prj_release.conf | 25 ++ .../nrfconnect/child_image/mcuboot/prj.conf | 1 - .../child_image/mcuboot/prj_release.conf | 1 - .../pm_static_dfu.yml | 56 +++ .../nrfconnect/main/AppTask.cpp | 421 +++++++++-------- .../nrfconnect/main/BindingHandler.cpp | 6 +- .../nrfconnect/main/include/AppConfig.h | 21 +- .../nrfconnect/main/include/AppEvent.h | 43 +- .../nrfconnect/main/include/AppTask.h | 51 +-- .../main/include/CHIPProjectConfig.h | 1 + .../light-switch-app/nrfconnect/main/main.cpp | 4 +- examples/light-switch-app/nrfconnect/prj.conf | 26 +- .../nrfconnect/prj_no_dfu.conf | 27 +- .../nrfconnect/prj_release.conf | 27 +- .../lighting-app/nrfconnect/CMakeLists.txt | 1 + examples/lighting-app/nrfconnect/Kconfig | 14 + examples/lighting-app/nrfconnect/README.md | 141 ++++-- .../boards/nrf7002dk_nrf5340_cpuapp.overlay | 61 +++ .../nrfconnect/child_image/hci_rpmsg/prj.conf | 25 ++ .../child_image/hci_rpmsg/prj_no_dfu.conf | 25 ++ .../child_image/hci_rpmsg/prj_release.conf | 25 ++ .../nrfconnect/child_image/mcuboot/prj.conf | 1 - .../child_image/mcuboot/prj_release.conf | 1 - .../pm_static_dfu.yml | 56 +++ .../lighting-app/nrfconnect/main/AppTask.cpp | 425 +++++++++++------- .../nrfconnect/main/ZclCallbacks.cpp | 16 +- .../nrfconnect/main/include/AppConfig.h | 20 +- .../nrfconnect/main/include/AppEvent.h | 42 +- .../nrfconnect/main/include/AppTask.h | 59 +-- .../lighting-app/nrfconnect/main/main.cpp | 4 +- examples/lighting-app/nrfconnect/prj.conf | 33 +- .../lighting-app/nrfconnect/prj_no_dfu.conf | 32 +- .../lighting-app/nrfconnect/prj_release.conf | 30 +- examples/lock-app/nrfconnect/CMakeLists.txt | 1 + examples/lock-app/nrfconnect/Kconfig | 17 + examples/lock-app/nrfconnect/README.md | 125 ++++-- .../boards/nrf52840dk_nrf52840.overlay | 3 - .../boards/nrf7002dk_nrf5340_cpuapp.overlay | 21 + .../nrfconnect/child_image/hci_rpmsg/prj.conf | 25 ++ .../child_image/hci_rpmsg/prj_no_dfu.conf | 25 ++ .../child_image/hci_rpmsg/prj_release.conf | 25 ++ .../nrfconnect/child_image/mcuboot/prj.conf | 1 - .../child_image/mcuboot/prj_release.conf | 1 - .../pm_static_dfu.yml | 56 +++ examples/lock-app/nrfconnect/main/AppTask.cpp | 329 +++++++++----- .../nrfconnect/main/BoltLockManager.cpp | 13 +- .../lock-app/nrfconnect/main/ZclCallbacks.cpp | 2 +- .../nrfconnect/main/include/AppConfig.h | 16 +- .../nrfconnect/main/include/AppEvent.h | 39 +- .../nrfconnect/main/include/AppTask.h | 62 ++- .../nrfconnect/main/include/BoltLockManager.h | 2 +- examples/lock-app/nrfconnect/main/main.cpp | 2 +- examples/lock-app/nrfconnect/prj.conf | 25 +- examples/lock-app/nrfconnect/prj_no_dfu.conf | 26 +- examples/lock-app/nrfconnect/prj_release.conf | 26 +- examples/platform/nrfconnect/Rpc.cpp | 3 +- .../nrfconnect/doc/images/nrf7002dk.jpg | Bin 0 -> 231981 bytes .../platform/nrfconnect/util/PWMDevice.cpp | 11 +- .../nrfconnect/util/include/BoardUtil.h | 26 ++ .../nrfconnect/util/include/EventTypes.h | 21 + .../nrfconnect/util/include/LEDUtil.h | 4 +- .../nrfconnect/util/include/PWMDevice.h | 3 +- examples/pump-app/nrfconnect/Kconfig | 15 +- examples/pump-app/nrfconnect/README.md | 23 +- examples/pump-app/nrfconnect/main/AppTask.cpp | 315 ++++++------- .../pump-app/nrfconnect/main/PumpManager.cpp | 14 +- .../pump-app/nrfconnect/main/ZclCallbacks.cpp | 2 +- .../nrfconnect/main/include/AppConfig.h | 2 + .../nrfconnect/main/include/AppEvent.h | 39 +- .../nrfconnect/main/include/AppTask.h | 61 +-- .../nrfconnect/main/include/PumpManager.h | 4 +- examples/pump-app/nrfconnect/main/main.cpp | 4 +- examples/pump-app/nrfconnect/prj.conf | 22 +- examples/pump-app/nrfconnect/prj_no_dfu.conf | 26 +- examples/pump-app/nrfconnect/prj_release.conf | 26 +- .../pump-controller-app/nrfconnect/Kconfig | 13 + .../pump-controller-app/nrfconnect/README.md | 25 +- .../nrfconnect/main/AppTask.cpp | 282 ++++++------ .../nrfconnect/main/PumpManager.cpp | 14 +- .../nrfconnect/main/ZclCallbacks.cpp | 2 +- .../nrfconnect/main/include/AppConfig.h | 2 + .../nrfconnect/main/include/AppEvent.h | 39 +- .../nrfconnect/main/include/AppTask.h | 62 +-- .../nrfconnect/main/include/PumpManager.h | 4 +- .../nrfconnect/main/main.cpp | 4 +- .../pump-controller-app/nrfconnect/prj.conf | 25 +- .../nrfconnect/prj_no_dfu.conf | 27 +- .../nrfconnect/prj_release.conf | 27 +- examples/window-app/nrfconnect/README.md | 24 +- .../boards/nrf52840dk_nrf52840.overlay | 3 - .../window-app/nrfconnect/main/AppTask.cpp | 279 ++++++------ .../nrfconnect/main/WindowCovering.cpp | 2 +- .../nrfconnect/main/include/AppConfig.h | 1 + .../nrfconnect/main/include/AppEvent.h | 33 +- .../nrfconnect/main/include/AppTask.h | 63 +-- .../nrfconnect/main/include/WindowCovering.h | 3 + examples/window-app/nrfconnect/main/main.cpp | 2 +- examples/window-app/nrfconnect/prj.conf | 22 +- .../window-app/nrfconnect/prj_no_dfu.conf | 26 +- .../window-app/nrfconnect/prj_release.conf | 24 +- 138 files changed, 3567 insertions(+), 2139 deletions(-) create mode 100644 examples/all-clusters-app/nrfconnect/boards/nrf7002dk_nrf5340_cpuapp.overlay create mode 100644 examples/all-clusters-app/nrfconnect/child_image/hci_rpmsg/prj.conf create mode 100644 examples/all-clusters-app/nrfconnect/child_image/hci_rpmsg/prj_no_dfu.conf create mode 100644 examples/all-clusters-app/nrfconnect/child_image/hci_rpmsg/prj_release.conf create mode 100644 examples/all-clusters-app/nrfconnect/configuration/nrf7002dk_nrf5340_cpuapp/pm_static_dfu.yml create mode 100644 examples/light-switch-app/nrfconnect/boards/nrf7002dk_nrf5340_cpuapp.overlay create mode 100644 examples/light-switch-app/nrfconnect/child_image/hci_rpmsg/prj.conf create mode 100644 examples/light-switch-app/nrfconnect/child_image/hci_rpmsg/prj_no_dfu.conf create mode 100644 examples/light-switch-app/nrfconnect/child_image/hci_rpmsg/prj_release.conf create mode 100644 examples/light-switch-app/nrfconnect/configuration/nrf7002dk_nrf5340_cpuapp/pm_static_dfu.yml create mode 100644 examples/lighting-app/nrfconnect/boards/nrf7002dk_nrf5340_cpuapp.overlay create mode 100644 examples/lighting-app/nrfconnect/child_image/hci_rpmsg/prj.conf create mode 100644 examples/lighting-app/nrfconnect/child_image/hci_rpmsg/prj_no_dfu.conf create mode 100644 examples/lighting-app/nrfconnect/child_image/hci_rpmsg/prj_release.conf create mode 100644 examples/lighting-app/nrfconnect/configuration/nrf7002dk_nrf5340_cpuapp/pm_static_dfu.yml create mode 100644 examples/lock-app/nrfconnect/boards/nrf7002dk_nrf5340_cpuapp.overlay create mode 100644 examples/lock-app/nrfconnect/child_image/hci_rpmsg/prj.conf create mode 100644 examples/lock-app/nrfconnect/child_image/hci_rpmsg/prj_no_dfu.conf create mode 100644 examples/lock-app/nrfconnect/child_image/hci_rpmsg/prj_release.conf create mode 100644 examples/lock-app/nrfconnect/configuration/nrf7002dk_nrf5340_cpuapp/pm_static_dfu.yml create mode 100644 examples/platform/nrfconnect/doc/images/nrf7002dk.jpg create mode 100644 examples/platform/nrfconnect/util/include/BoardUtil.h create mode 100644 examples/platform/nrfconnect/util/include/EventTypes.h diff --git a/.github/workflows/examples-nrfconnect.yaml b/.github/workflows/examples-nrfconnect.yaml index ecfcc8a2fa5b48..4b9430d38a4b96 100644 --- a/.github/workflows/examples-nrfconnect.yaml +++ b/.github/workflows/examples-nrfconnect.yaml @@ -192,6 +192,41 @@ jobs: nrfconnect nrf5340dk_nrf5340_cpuapp lighting-app \ examples/lighting-app/nrfconnect/build/zephyr/zephyr.elf \ /tmp/bloat_reports/ + - name: Build example nRF Connect SDK Lock App on nRF7002 PDK + if: github.event_name == 'push' || steps.changed_paths.outputs.nrfconnect == 'true' + timeout-minutes: 20 + run: | + scripts/examples/nrfconnect_example.sh lock-app nrf7002dk_nrf5340_cpuapp + .environment/pigweed-venv/bin/python3 scripts/tools/memory/gh_sizes.py \ + nrfconnect nrf7002dk_nrf5340_cpuapp lock-app \ + examples/lock-app/nrfconnect/build/zephyr/zephyr.elf \ + /tmp/bloat_reports/ + - name: Build example nRF Connect SDK Light Switch App on nRF7002 PDK + if: github.event_name == 'push' || steps.changed_paths.outputs.nrfconnect == 'true' + timeout-minutes: 20 + run: | + scripts/examples/nrfconnect_example.sh light-switch-app nrf7002dk_nrf5340_cpuapp + .environment/pigweed-venv/bin/python3 scripts/tools/memory/gh_sizes.py \ + nrfconnect nrf7002dk_nrf5340_cpuapp light-switch-app \ + examples/light-switch-app/nrfconnect/build/zephyr/zephyr.elf \ + /tmp/bloat_reports/ + - name: Build example nRF Connect SDK Lighting App on nRF7002 PDK + if: github.event_name == 'push' || steps.changed_paths.outputs.nrfconnect == 'true' + timeout-minutes: 20 + run: | + scripts/examples/nrfconnect_example.sh lighting-app nrf7002dk_nrf5340_cpuapp + .environment/pigweed-venv/bin/python3 scripts/tools/memory/gh_sizes.py \ + nrfconnect nrf7002dk_nrf5340_cpuapp lighting-app \ + examples/light-switch-app/nrfconnect/build/zephyr/zephyr.elf \ + /tmp/bloat_reports/ + - name: Build example nRF Connect SDK All Clusters App on nRF7002 PDK + timeout-minutes: 20 + run: | + scripts/examples/nrfconnect_example.sh all-clusters-app nrf7002dk_nrf5340_cpuapp + .environment/pigweed-venv/bin/python3 scripts/tools/memory/gh_sizes.py \ + nrfconnect nrf7002dk_nrf5340_cpuapp all-clusters-app \ + examples/all-clusters-app/nrfconnect/build/zephyr/zephyr.elf \ + /tmp/bloat_reports/ - name: Run unit tests for Zephyr native_posix_64 platform if: github.event_name == 'push' || steps.changed_paths.outputs.tests == 'true' timeout-minutes: 15 diff --git a/config/nrfconnect/chip-module/Kconfig b/config/nrfconnect/chip-module/Kconfig index e4a0a9f3a1abab..ee51bfb15f4b99 100644 --- a/config/nrfconnect/chip-module/Kconfig +++ b/config/nrfconnect/chip-module/Kconfig @@ -18,6 +18,15 @@ rsource "../../zephyr/Kconfig" if CHIP +config CHIP_APP_LOG_LEVEL + int "Set logging level in application" + default LOG_DEFAULT_LEVEL + help + Sets the logging level in Matter application. + This config should be used only within application. + To set the logging level for Matter stack use MATTER_LOG_LEVEL + config. + config CHIP_NFC_COMMISSIONING bool "Enable NFC commissioning support" default n diff --git a/config/zephyr/Kconfig b/config/zephyr/Kconfig index c7c5b356a160d9..048855d23e2263 100644 --- a/config/zephyr/Kconfig +++ b/config/zephyr/Kconfig @@ -154,10 +154,10 @@ config CHIP_SED_ACTIVE_INTERVAL endif # CHIP_ENABLE_SLEEPY_END_DEVICE_SUPPORT config CHIP_THREAD_SSED - bool "Enable Thread Synchronized Sleepy End Device support" - depends on OPENTHREAD_CSL_RECEIVER && CHIP_ENABLE_SLEEPY_END_DEVICE_SUPPORT - help - Enables Thread Synchronized Sleepy End Device support in Matter. + bool "Enable Thread Synchronized Sleepy End Device support" + depends on OPENTHREAD_CSL_RECEIVER && CHIP_ENABLE_SLEEPY_END_DEVICE_SUPPORT + help + Enables Thread Synchronized Sleepy End Device support in Matter. config CHIP_OTA_REQUESTOR bool "Enable OTA requestor" diff --git a/docs/guides/nrfconnect_android_commissioning.md b/docs/guides/nrfconnect_android_commissioning.md index 0e2146ba980b8f..21096da4d2bab6 100644 --- a/docs/guides/nrfconnect_android_commissioning.md +++ b/docs/guides/nrfconnect_android_commissioning.md @@ -6,9 +6,9 @@ example for the nRF Connect platform into a Matter fabric. This guide references the nRF52840 DK and Matter nRF Connect Lighting Example Application that communicates with other nodes over a Thread network, but the -instructions can be adapted to other platforms and applications. In particular, -some sections of this guide include deviations from the original procedure that -are needed to test a Wi-Fi device. +instructions can be adapted to other platforms and applications. For instance, +some sections of this guide include steps for testing a Wi-Fi device, which are +adapted from the original Thread-based procedure.
@@ -68,6 +68,7 @@ accessory using Android CHIPTool: replace this DK with another compatible device, such as the nRF5340 DK or nRF7002 DK. nRF52840 DK and nRF5340 DK can be used to test Matter over Thread, and nRF7002 DK can be used to test Matter over Wi-Fi. + - 1x nRF52840 DK for running the [OpenThread Radio Co-Processor](https://openthread.io/platforms/co-processor) firmware. You can replace this DK with another compatible device, such as diff --git a/examples/all-clusters-app/nrfconnect/CMakeLists.txt b/examples/all-clusters-app/nrfconnect/CMakeLists.txt index e065613e4ef577..abcd8fb5060011 100644 --- a/examples/all-clusters-app/nrfconnect/CMakeLists.txt +++ b/examples/all-clusters-app/nrfconnect/CMakeLists.txt @@ -25,6 +25,7 @@ include(${CHIP_ROOT}/config/nrfconnect/app/check-nrfconnect-version.cmake) # Set Kconfig root files that will be processed as a first Kconfig for used child images. set(mcuboot_KCONFIG_ROOT ${CHIP_ROOT}/config/nrfconnect/chip-module/Kconfig.mcuboot.root) set(multiprotocol_rpmsg_KCONFIG_ROOT ${CHIP_ROOT}/config/nrfconnect/chip-module/Kconfig.multiprotocol_rpmsg.root) +set(hci_rpmsg_KCONFIG_ROOT ${CHIP_ROOT}/config/nrfconnect/chip-module/Kconfig.hci_rpmsg.root) if(DEFINED CONF_FILE AND NOT CONF_FILE STREQUAL "prj.conf") set(PM_STATIC_YML_FILE ${CMAKE_CURRENT_SOURCE_DIR}/configuration/${BOARD}/pm_static_dfu.yml) diff --git a/examples/all-clusters-app/nrfconnect/Kconfig b/examples/all-clusters-app/nrfconnect/Kconfig index 9fcdb41ab33208..33919ac5fc2337 100644 --- a/examples/all-clusters-app/nrfconnect/Kconfig +++ b/examples/all-clusters-app/nrfconnect/Kconfig @@ -22,6 +22,19 @@ config STATE_LEDS Use LEDs to render the current state of the device such as the progress of commissioning of the device into a network or the factory reset initiation. +# Sample configuration used for Thread networking +if NET_L2_OPENTHREAD + +choice OPENTHREAD_NORDIC_LIBRARY_CONFIGURATION + default OPENTHREAD_NORDIC_LIBRARY_MTD +endchoice + +choice OPENTHREAD_DEVICE_TYPE + default OPENTHREAD_MTD +endchoice + +endif # NET_L2_OPENTHREAD + rsource "../../../config/nrfconnect/chip-module/Kconfig.features" rsource "../../../config/nrfconnect/chip-module/Kconfig.defaults" source "Kconfig.zephyr" diff --git a/examples/all-clusters-app/nrfconnect/README.md b/examples/all-clusters-app/nrfconnect/README.md index b0befaebaa1a8d..8f4eb0422a4326 100644 --- a/examples/all-clusters-app/nrfconnect/README.md +++ b/examples/all-clusters-app/nrfconnect/README.md @@ -10,12 +10,14 @@ creating your own application. The example is based on [Matter](https://github.com/project-chip/connectedhomeip) and Nordic Semiconductor's nRF Connect SDK, and was created to facilitate testing and -certification of a Matter device communicating over a low-power, 802.15.4 Thread -network. +certification of a Matter device communicating over a low-power 802.15.4 Thread +network, or Wi-Fi network. The example behaves as a Matter accessory, that is a device that can be paired -into an existing Matter network and can be controlled by this network. The -device works as a Thread Minimal End Device. +into an existing Matter network and can be controlled by this network. In the +case of Thread, this device works as a Thread Sleepy End Device. Support for +both Thread and Wi-Fi is mutually exclusive and depends on the hardware +platform, so only one protocol can be supported for a specific device.
@@ -24,6 +26,7 @@ device works as a Thread Minimal End Device. - [Bluetooth LE rendezvous](#bluetooth-le-rendezvous) - [Requirements](#requirements) - [Supported devices](#supported_devices) + - [IPv6 network support](#ipv6-network-support) - [Device UI](#device-ui) - [Setting up the environment](#setting-up-the-environment) - [Using Docker container for setup](#using-docker-container-for-setup) @@ -55,17 +58,27 @@ and [Zephyr RTOS](https://zephyrproject.org/). Visit Matter's [nRF Connect platform overview](../../../docs/guides/nrfconnect_platform_overview.md) to read more about the platform structure and dependencies. -The Matter device that runs the all clusters application is controlled by the -Matter controller device over the Thread protocol. By default, the Matter device -has Thread disabled, and it should be paired with Matter controller and get -configuration from it. Some actions required before establishing full -communication are described below. +By default, the Matter accessory device has IPv6 networking disabled. You must +pair it with the Matter controller over Bluetooth® LE to get the configuration +from the controller to use the device within a Thread or Wi-Fi network. You have +to make the device discoverable manually (for security reasons). See +[Bluetooth LE advertising](#bluetooth-le-advertising) to learn how to do this. +The controller must get the commissioning information from the Matter accessory +device and provision the device into the network. + +You can test this application remotely over the Thread or the Wi-Fi protocol, +which in either case requires more devices, including a Matter controller that +you can configure either on a PC or a mobile device. ### Bluetooth LE advertising In this example, to commission the device onto a Matter network, it must be discoverable over Bluetooth LE. For security reasons, you must start Bluetooth -LE advertising manually after powering up the device by pressing **Button 4**. +LE advertising manually after powering up the device by pressing: + +- On nRF52840 DK, nRF5340 DK, and nRF21540 DK: **Button 4**. + +- On nRF7002 DK: **Button 2**. ### Bluetooth LE rendezvous @@ -75,14 +88,16 @@ commissioner role. To start the rendezvous, the controller must get the commissioning information from the Matter device. The data payload is encoded within a QR code, printed to -the UART console. +the UART console, and shared using an NFC tag. The emulation of the NFC tag +starts automatically when Bluetooth LE advertising is started and stays enabled +until Bluetooth LE advertising timeout expires. -#### Thread provisioning +#### Thread or Wi-Fi provisioning Last part of the rendezvous procedure, the provisioning operation involves -sending the Thread network credentials from the Matter controller to the Matter -device. As a result, device is able to join the Thread network and communicate -with other Thread devices in the network. +sending the Thread or Wi-Fi network credentials from the Matter controller to +the Matter device. As a result, the device joins the Thread or Wi-Fi network and +can communicate with other devices in the network.
@@ -100,14 +115,24 @@ more information. The example supports building and running on the following devices: -| Hardware platform | Build target | Platform image | -| ------------------------------------------------------------------------------------------------- | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | -| [nRF52840 DK](https://www.nordicsemi.com/Software-and-Tools/Development-Kits/nRF52840-DK) | `nrf52840dk_nrf52840` |
nRF52840 DKnRF52840 DK
| -| [nRF5340 DK](https://www.nordicsemi.com/Software-and-Tools/Development-Kits/nRF5340-DK) | `nrf5340dk_nrf5340_cpuapp` |
nRF5340 DKnRF5340 DK
| -| [nRF52840 Dongle](https://www.nordicsemi.com/Software-and-Tools/Development-Kits/nRF52840-Dongle) | `nrf52840dongle_nrf52840` |
nRF52840 DonglenRF52840 Dongle
| +| Hardware platform | Build target | Platform image | +| --------------------------------------------------------------------------------------------------------------- | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | +| [nRF52840 DK](https://www.nordicsemi.com/Software-and-Tools/Development-Kits/nRF52840-DK) | `nrf52840dk_nrf52840` |
nRF52840 DKnRF52840 DK
| +| [nRF5340 DK](https://www.nordicsemi.com/Software-and-Tools/Development-Kits/nRF5340-DK) | `nrf5340dk_nrf5340_cpuapp` |
nRF5340 DKnRF5340 DK
| +| [nRF52840 Dongle](https://www.nordicsemi.com/Software-and-Tools/Development-Kits/nRF52840-Dongle) | `nrf52840dongle_nrf52840` |
nRF52840 DonglenRF52840 Dongle
| +| [nRF7002 DK](https://developer.nordicsemi.com/nRF_Connect_SDK/doc/latest/nrf/ug_nrf7002.html#nrf7002dk-nrf5340) | `nrf7002dk_nrf5340_cpuapp` |
nRF7002DKnRF7002 DK
|
+### IPv6 network support + +The development kits for this sample offer the following IPv6 network support +for Matter: + +- Matter over Thread is supported for `nrf52840dk_nrf52840` and + `nrf5340dk_nrf5340_cpuapp`. +- Matter over Wi-Fi is supported for `nrf7002dk_nrf5340_cpuapp`. + ## Device UI @@ -137,21 +162,42 @@ following states are possible: Bluetooth LE. - _Short Flash Off (950ms on/50ms off)_ — The device is fully - provisioned, but does not yet have full Thread network or service - connectivity. + provisioned, but does not yet have full connectivity for Thread or Wi-Fi + network. -- _Solid On_ — The device is fully provisioned and has full Thread - network and service connectivity. +- _Solid On_ — The device is fully provisioned. **Button 1** can be used for the following purposes: -- _Pressed for 6 s_ — Initiates the factory reset of the device. - Releasing the button within the 6-second window cancels the factory reset - procedure. **LEDs 1-4** blink in unison when the factory reset procedure is - initiated. +- _Pressed for less than 3 s_ — Initiates the OTA software update + process. This feature is disabled by default, but can be enabled by + following the + [Building with Device Firmware Upgrade support](#building-with-device-firmware-upgrade-support) + instructions. + +- _Pressed for more than 3 s_ — initiates the factory reset of the + device. Releasing the button within the 3-second window cancels the factory + reset procedure. + +**Button 2**: + +- On nRF52840 DK, nRF5340 DK, and nRF21540 DK: Not available. + +- On nRF7002 DK: + + - If pressed for more than three seconds, it starts the NFC tag emulation, + enables Bluetooth LE advertising for the predefined period of time (15 + minutes by default), and makes the device discoverable over Bluetooth + LE. + +**Button 4**: + +- On nRF52840 DK, nRF5340 DK, and nRF21540 DK: Starts the NFC tag emulation, + enables Bluetooth LE advertising for the predefined period of time (15 + minutes by default), and makes the device discoverable over Bluetooth LE. + This button is used during the commissioning procedure. -**Button 4** — Pressing the button once starts Bluetooth LE advertising -for the predefined period of time (15 minutes by default). +- On nRF7002 DK: Not available. **SEGGER J-Link USB port** can be used to get logs from the device or communicate with it using the diff --git a/examples/all-clusters-app/nrfconnect/boards/nrf52840dk_nrf52840.overlay b/examples/all-clusters-app/nrfconnect/boards/nrf52840dk_nrf52840.overlay index 04253ef9667617..566236731653a5 100644 --- a/examples/all-clusters-app/nrfconnect/boards/nrf52840dk_nrf52840.overlay +++ b/examples/all-clusters-app/nrfconnect/boards/nrf52840dk_nrf52840.overlay @@ -37,9 +37,6 @@ &uart1 { status = "disabled"; }; -&gpio1 { - status = "disabled"; -}; &i2c0 { status = "disabled"; }; diff --git a/examples/all-clusters-app/nrfconnect/boards/nrf7002dk_nrf5340_cpuapp.overlay b/examples/all-clusters-app/nrfconnect/boards/nrf7002dk_nrf5340_cpuapp.overlay new file mode 100644 index 00000000000000..3063fbbcdee779 --- /dev/null +++ b/examples/all-clusters-app/nrfconnect/boards/nrf7002dk_nrf5340_cpuapp.overlay @@ -0,0 +1,21 @@ +/* + * 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. + */ + +/ { + chosen { + nordic,pm-ext-flash = &mx25r64; + }; +}; diff --git a/examples/all-clusters-app/nrfconnect/child_image/hci_rpmsg/prj.conf b/examples/all-clusters-app/nrfconnect/child_image/hci_rpmsg/prj.conf new file mode 100644 index 00000000000000..1622ffd00dbb91 --- /dev/null +++ b/examples/all-clusters-app/nrfconnect/child_image/hci_rpmsg/prj.conf @@ -0,0 +1,25 @@ +# +# 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. +# + +# This target uses Kconfig.hci_rpmsg.defaults to set options common for all +# samples using hci_rpmsg. This file should contain only options specific for this sample +# hci_rpmsg configuration or overrides of default values. + +# Disable not used modules that cannot be set in Kconfig.hci_rpmsg.defaults due to overriding +# in board files. + +CONFIG_SERIAL=n +CONFIG_UART_CONSOLE=n diff --git a/examples/all-clusters-app/nrfconnect/child_image/hci_rpmsg/prj_no_dfu.conf b/examples/all-clusters-app/nrfconnect/child_image/hci_rpmsg/prj_no_dfu.conf new file mode 100644 index 00000000000000..1622ffd00dbb91 --- /dev/null +++ b/examples/all-clusters-app/nrfconnect/child_image/hci_rpmsg/prj_no_dfu.conf @@ -0,0 +1,25 @@ +# +# 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. +# + +# This target uses Kconfig.hci_rpmsg.defaults to set options common for all +# samples using hci_rpmsg. This file should contain only options specific for this sample +# hci_rpmsg configuration or overrides of default values. + +# Disable not used modules that cannot be set in Kconfig.hci_rpmsg.defaults due to overriding +# in board files. + +CONFIG_SERIAL=n +CONFIG_UART_CONSOLE=n diff --git a/examples/all-clusters-app/nrfconnect/child_image/hci_rpmsg/prj_release.conf b/examples/all-clusters-app/nrfconnect/child_image/hci_rpmsg/prj_release.conf new file mode 100644 index 00000000000000..1622ffd00dbb91 --- /dev/null +++ b/examples/all-clusters-app/nrfconnect/child_image/hci_rpmsg/prj_release.conf @@ -0,0 +1,25 @@ +# +# 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. +# + +# This target uses Kconfig.hci_rpmsg.defaults to set options common for all +# samples using hci_rpmsg. This file should contain only options specific for this sample +# hci_rpmsg configuration or overrides of default values. + +# Disable not used modules that cannot be set in Kconfig.hci_rpmsg.defaults due to overriding +# in board files. + +CONFIG_SERIAL=n +CONFIG_UART_CONSOLE=n diff --git a/examples/all-clusters-app/nrfconnect/configuration/nrf7002dk_nrf5340_cpuapp/pm_static_dfu.yml b/examples/all-clusters-app/nrfconnect/configuration/nrf7002dk_nrf5340_cpuapp/pm_static_dfu.yml new file mode 100644 index 00000000000000..3c56dc0ddb1968 --- /dev/null +++ b/examples/all-clusters-app/nrfconnect/configuration/nrf7002dk_nrf5340_cpuapp/pm_static_dfu.yml @@ -0,0 +1,56 @@ +mcuboot: + address: 0x0 + size: 0xC000 + region: flash_primary +mcuboot_pad: + address: 0xC000 + size: 0x200 +app: + address: 0xC200 + size: 0xeee00 +mcuboot_primary: + orig_span: &id001 + - mcuboot_pad + - app + span: *id001 + address: 0xC000 + size: 0xef000 + region: flash_primary +mcuboot_primary_app: + orig_span: &id002 + - app + span: *id002 + address: 0xC200 + size: 0xeee00 +factory_data: + address: 0xfb000 + size: 0x1000 + region: flash_primary +settings_storage: + address: 0xfc000 + size: 0x4000 + region: flash_primary +mcuboot_primary_1: + address: 0x0 + size: 0x40000 + device: flash_ctrl + region: ram_flash +mcuboot_secondary: + address: 0x0 + size: 0xef000 + device: MX25R64 + region: external_flash +mcuboot_secondary_1: + address: 0xef000 + size: 0x40000 + device: MX25R64 + region: external_flash +external_flash: + address: 0x12f000 + size: 0x6D1000 + device: MX25R64 + region: external_flash +pcd_sram: + address: 0x20000000 + size: 0x2000 + region: sram_primary diff --git a/examples/all-clusters-app/nrfconnect/main/AppTask.cpp b/examples/all-clusters-app/nrfconnect/main/AppTask.cpp index 40b987b620332d..f8646a19f7bd94 100644 --- a/examples/all-clusters-app/nrfconnect/main/AppTask.cpp +++ b/examples/all-clusters-app/nrfconnect/main/AppTask.cpp @@ -36,6 +36,11 @@ #include #include +#ifdef CONFIG_CHIP_WIFI +#include +#include +#endif + #if CONFIG_CHIP_OTA_REQUESTOR #include "OTAUtil.h" #endif @@ -44,72 +49,48 @@ #include #include +LOG_MODULE_DECLARE(app, CONFIG_CHIP_APP_LOG_LEVEL); + using namespace ::chip; +using namespace ::chip::app; using namespace ::chip::Credentials; using namespace ::chip::DeviceLayer; -#define FACTORY_RESET_TRIGGER_TIMEOUT 3000 -#define FACTORY_RESET_CANCEL_WINDOW_TIMEOUT 3000 -#define APP_EVENT_QUEUE_SIZE 10 -#define BUTTON_PUSH_EVENT 1 -#define BUTTON_RELEASE_EVENT 0 - -LOG_MODULE_DECLARE(app, CONFIG_MATTER_LOG_LEVEL); -K_MSGQ_DEFINE(sAppEventQueue, sizeof(AppEvent), APP_EVENT_QUEUE_SIZE, alignof(AppEvent)); - namespace { +constexpr uint32_t kFactoryResetTriggerTimeout = 3000; +constexpr uint32_t kFactoryResetCancelWindowTimeout = 3000; +constexpr size_t kAppEventQueueSize = 10; +constexpr EndpointId kIdentifyEndpointId = 1; +constexpr EndpointId kNetworkCommissioningEndpointSecondary = 0xFFFE; // NOTE! This key is for test/certification only and should not be available in production devices! // If CONFIG_CHIP_FACTORY_DATA is enabled, this value is read from the factory data. uint8_t sTestEventTriggerEnableKey[TestEventTriggerDelegate::kEnableKeyLength] = { 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff }; -LEDWidget sStatusLED; -UnusedLedsWrapper<3> sUnusedLeds{ { DK_LED2, DK_LED3, DK_LED4 } }; +K_MSGQ_DEFINE(sAppEventQueue, sizeof(AppEvent), kAppEventQueueSize, alignof(AppEvent)); k_timer sFunctionTimer; chip::DeviceLayer::DeviceInfoProviderImpl gExampleDeviceInfoProvider; -constexpr EndpointId kIdentifyEndpointId = 1; +Identify sIdentify = { kIdentifyEndpointId, AppTask::IdentifyStartHandler, AppTask::IdentifyStopHandler, + EMBER_ZCL_IDENTIFY_IDENTIFY_TYPE_VISIBLE_LED }; -void OnIdentifyTriggerEffect(Identify * identify) -{ - ChipLogProgress(Zcl, "OnIdentifyTriggerEffect"); - switch (identify->mCurrentEffectIdentifier) - { - case EMBER_ZCL_IDENTIFY_EFFECT_IDENTIFIER_BLINK: - ChipLogProgress(Zcl, "Effect: EMBER_ZCL_IDENTIFY_EFFECT_IDENTIFIER_BLINK"); - break; - case EMBER_ZCL_IDENTIFY_EFFECT_IDENTIFIER_BREATHE: - ChipLogProgress(Zcl, "Effect: EMBER_ZCL_IDENTIFY_EFFECT_IDENTIFIER_BREATHE"); - break; - case EMBER_ZCL_IDENTIFY_EFFECT_IDENTIFIER_OKAY: - ChipLogProgress(Zcl, "Effect: EMBER_ZCL_IDENTIFY_EFFECT_IDENTIFIER_OKAY"); - break; - case EMBER_ZCL_IDENTIFY_EFFECT_IDENTIFIER_CHANNEL_CHANGE: - ChipLogProgress(Zcl, "Effect: EMBER_ZCL_IDENTIFY_EFFECT_IDENTIFIER_CHANNEL_CHANGE"); - break; - default: - ChipLogProgress(Zcl, "Effect: No identifier effect"); - break; - } - return; -} +LEDWidget sStatusLED; +LEDWidget sIdentifyLED; +#if NUMBER_OF_LEDS == 4 +FactoryResetLEDsWrapper<2> sFactoryResetLEDs{ { FACTORY_RESET_SIGNAL_LED, FACTORY_RESET_SIGNAL_LED1 } }; +#endif -Identify sIdentify = { - chip::EndpointId{ kIdentifyEndpointId }, - [](Identify *) { ChipLogProgress(Zcl, "OnIdentifyStart"); }, - [](Identify *) { ChipLogProgress(Zcl, "OnIdentifyStop"); }, - EMBER_ZCL_IDENTIFY_IDENTIFY_TYPE_NONE, - OnIdentifyTriggerEffect, -}; +bool sIsNetworkProvisioned = false; +bool sIsNetworkEnabled = false; +bool sHaveBLEConnections = false; } // namespace -constexpr EndpointId kNetworkCommissioningEndpointSecondary = 0xFFFE; - namespace LedConsts { constexpr uint32_t kBlinkRate_ms{ 500 }; +constexpr uint32_t kIdentifyBlinkRate_ms{ 500 }; namespace StatusLed { namespace Unprovisioned { constexpr uint32_t kOn_ms{ 100 }; @@ -123,6 +104,10 @@ constexpr uint32_t kOff_ms{ 950 }; } // namespace StatusLed } // namespace LedConsts +#ifdef CONFIG_CHIP_WIFI +app::Clusters::NetworkCommissioning::Instance sWiFiCommissioningInstance(0, &(NetworkCommissioning::NrfWiFiDriver::Instance())); +#endif + CHIP_ERROR AppTask::Init() { // Initialize CHIP stack @@ -160,13 +145,19 @@ CHIP_ERROR AppTask::Init() LOG_ERR("ConnectivityMgr().SetThreadDeviceType() failed"); return err; } -#endif +#elif defined(CONFIG_CHIP_WIFI) + sWiFiCommissioningInstance.Init(); +#else + return CHIP_ERROR_INTERNAL; +#endif // CONFIG_NET_L2_OPENTHREAD // Initialize LEDs LEDWidget::InitGpio(); LEDWidget::SetStateUpdateCallback(LEDStateUpdateHandler); sStatusLED.Init(SYSTEM_STATE_LED); + sIdentifyLED.Init(IDENTIFY_STATE_LED); + sIdentifyLED.Set(false); UpdateStatusLED(); @@ -184,7 +175,7 @@ CHIP_ERROR AppTask::Init() } // Initialize timer user data - k_timer_init(&sFunctionTimer, &AppTask::TimerEventHandler, nullptr); + k_timer_init(&sFunctionTimer, &AppTask::FunctionTimerTimeoutCallback, nullptr); k_timer_user_data_set(&sFunctionTimer, this); // Initialize CHIP server @@ -241,120 +232,164 @@ CHIP_ERROR AppTask::StartApp() while (true) { k_msgq_get(&sAppEventQueue, &event, K_FOREVER); - DispatchEvent(&event); + DispatchEvent(event); } return CHIP_NO_ERROR; } -void AppTask::ButtonEventHandler(uint32_t aButtonState, uint32_t aHasChanged) +void AppTask::IdentifyStartHandler(Identify *) { AppEvent event; - event.Type = AppEvent::Type::Button; + event.Type = AppEventType::IdentifyStart; + event.Handler = [](const AppEvent &) { sIdentifyLED.Blink(LedConsts::kIdentifyBlinkRate_ms); }; + PostEvent(event); +} + +void AppTask::IdentifyStopHandler(Identify *) +{ + AppEvent event; + event.Type = AppEventType::IdentifyStop; + event.Handler = [](const AppEvent &) { sIdentifyLED.Set(false); }; + PostEvent(event); +} - if (FUNCTION_BUTTON_MASK & aHasChanged) +void AppTask::ButtonEventHandler(uint32_t buttonState, uint32_t hasChanged) +{ + AppEvent button_event; + button_event.Type = AppEventType::Button; + + if (BLE_ADVERTISEMENT_START_BUTTON_MASK & buttonState & hasChanged) { - event.ButtonEvent.PinNo = FUNCTION_BUTTON; - event.ButtonEvent.Action = (FUNCTION_BUTTON_MASK & aButtonState) ? BUTTON_PUSH_EVENT : BUTTON_RELEASE_EVENT; - event.Handler = FunctionHandler; - PostEvent(&event); + button_event.ButtonEvent.PinNo = BLE_ADVERTISEMENT_START_BUTTON; + button_event.ButtonEvent.Action = static_cast(AppEventType::ButtonPushed); + button_event.Handler = StartBLEAdvertisementHandler; + PostEvent(button_event); } - if (BLE_ADVERTISEMENT_START_BUTTON_MASK & aButtonState & aHasChanged) + if (FUNCTION_BUTTON_MASK & hasChanged) { - event.ButtonEvent.PinNo = BLE_ADVERTISEMENT_START_BUTTON; - event.ButtonEvent.Action = BUTTON_PUSH_EVENT; - event.Handler = StartBLEAdvertisementHandler; - PostEvent(&event); + button_event.ButtonEvent.PinNo = FUNCTION_BUTTON; + button_event.ButtonEvent.Action = + static_cast((FUNCTION_BUTTON_MASK & buttonState) ? AppEventType::ButtonPushed : AppEventType::ButtonReleased); + button_event.Handler = FunctionHandler; + PostEvent(button_event); } } -void AppTask::TimerEventHandler(k_timer * aTimer) +void AppTask::FunctionTimerTimeoutCallback(k_timer * timer) { - if (!aTimer) + if (!timer) + { return; + } AppEvent event; - event.Type = AppEvent::Type::Timer; - event.TimerEvent.Context = k_timer_user_data_get(aTimer); + event.Type = AppEventType::Timer; + event.TimerEvent.Context = k_timer_user_data_get(timer); event.Handler = FunctionTimerEventHandler; - PostEvent(&event); + PostEvent(event); } -void AppTask::FunctionTimerEventHandler(AppEvent * aEvent) +void AppTask::FunctionTimerEventHandler(const AppEvent & event) { - if (!aEvent) - return; - if (aEvent->Type != AppEvent::Type::Timer) + if (event.Type != AppEventType::Timer || !Instance().mFunctionTimerActive) + { return; + } - // If we reached here, the button was held past FACTORY_RESET_TRIGGER_TIMEOUT, initiate factory reset - if (Instance().mFunctionTimerActive && Instance().mMode == OperatingMode::Normal) + // If we reached here, the button was held past kFactoryResetTriggerTimeout, initiate factory reset + if (Instance().mFunction == FunctionEvent::SoftwareUpdate) { - LOG_INF("Factory Reset Triggered. Release button within %ums to cancel.", FACTORY_RESET_TRIGGER_TIMEOUT); + LOG_INF("Factory Reset Triggered. Release button within %ums to cancel.", kFactoryResetTriggerTimeout); - // Start timer for FACTORY_RESET_CANCEL_WINDOW_TIMEOUT to allow user to cancel, if required. - StartTimer(FACTORY_RESET_CANCEL_WINDOW_TIMEOUT); - Instance().mMode = OperatingMode::FactoryReset; + // Start timer for kFactoryResetCancelWindowTimeout to allow user to cancel, if required. + Instance().StartTimer(kFactoryResetCancelWindowTimeout); + Instance().mFunction = FunctionEvent::FactoryReset; -#ifdef CONFIG_STATE_LEDS - // Turn off all LEDs before starting blink to make sure blink is co-ordinated. + // Turn off all LEDs before starting blink to make sure blink is coordinated. sStatusLED.Set(false); - sUnusedLeds.Set(false); +#if NUMBER_OF_LEDS == 4 + sFactoryResetLEDs.Set(false); +#endif sStatusLED.Blink(LedConsts::kBlinkRate_ms); - sUnusedLeds.Blink(LedConsts::kBlinkRate_ms); +#if NUMBER_OF_LEDS == 4 + sFactoryResetLEDs.Blink(LedConsts::kBlinkRate_ms); #endif } - else if (Instance().mFunctionTimerActive && Instance().mMode == OperatingMode::FactoryReset) + else if (Instance().mFunction == FunctionEvent::FactoryReset) { // Actually trigger Factory Reset - Instance().mMode = OperatingMode::Normal; - ConfigurationMgr().InitiateFactoryReset(); + Instance().mFunction = FunctionEvent::NoneSelected; + chip::Server::GetInstance().ScheduleFactoryReset(); + } + else if (Instance().mFunction == FunctionEvent::AdvertisingStart) + { + // The button was held past kAdvertisingTriggerTimeout, start BLE advertisement if we have 2 buttons UI +#if NUMBER_OF_BUTTONS == 2 + StartBLEAdvertisementHandler(event); +#endif } } -void AppTask::FunctionHandler(AppEvent * aEvent) +#ifdef CONFIG_MCUMGR_SMP_BT +void AppTask::RequestSMPAdvertisingStart(void) { - if (!aEvent) - return; - if (aEvent->ButtonEvent.PinNo != FUNCTION_BUTTON) + AppEvent event; + event.Type = AppEvent::kEventType_StartSMPAdvertising; + event.Handler = [](AppEvent *) { GetDFUOverSMP().StartBLEAdvertising(); }; + sAppTask.PostEvent(&event); +} +#endif + +void AppTask::FunctionHandler(const AppEvent & event) +{ + if (event.ButtonEvent.PinNo != FUNCTION_BUTTON) return; + // To trigger software update: press the FUNCTION_BUTTON button briefly (< FACTORY_RESET_TRIGGER_TIMEOUT) // To initiate factory reset: press the FUNCTION_BUTTON for FACTORY_RESET_TRIGGER_TIMEOUT + FACTORY_RESET_CANCEL_WINDOW_TIMEOUT // All LEDs start blinking after FACTORY_RESET_TRIGGER_TIMEOUT to signal factory reset has been initiated. // To cancel factory reset: release the FUNCTION_BUTTON once all LEDs start blinking within the // FACTORY_RESET_CANCEL_WINDOW_TIMEOUT - if (aEvent->ButtonEvent.Action == BUTTON_PUSH_EVENT) + if (event.ButtonEvent.Action == static_cast(AppEventType::ButtonPushed)) { - if (!Instance().mFunctionTimerActive && Instance().mMode == OperatingMode::Normal) + if (!Instance().mFunctionTimerActive && Instance().mFunction == FunctionEvent::NoneSelected) { - StartTimer(FACTORY_RESET_TRIGGER_TIMEOUT); + Instance().StartTimer(kFactoryResetTriggerTimeout); + + Instance().mFunction = FunctionEvent::SoftwareUpdate; } } else { - if (Instance().mFunctionTimerActive && Instance().mMode == OperatingMode::FactoryReset) + // If the button was released before factory reset got initiated, trigger a software update. + if (Instance().mFunctionTimerActive && Instance().mFunction == FunctionEvent::SoftwareUpdate) { - sUnusedLeds.Set(false); + Instance().CancelTimer(); + Instance().mFunction = FunctionEvent::NoneSelected; - UpdateStatusLED(); - CancelTimer(); - - // Change the function to none selected since factory reset has been canceled. - Instance().mMode = OperatingMode::Normal; - - LOG_INF("Factory Reset has been Canceled"); +#ifdef CONFIG_MCUMGR_SMP_BT + GetDFUOverSMP().StartServer(); +#else + LOG_INF("Software update is disabled"); +#endif } - else if (Instance().mFunctionTimerActive) + else if (Instance().mFunctionTimerActive && Instance().mFunction == FunctionEvent::FactoryReset) { - CancelTimer(); - Instance().mMode = OperatingMode::Normal; +#if NUMBER_OF_LEDS == 4 + sFactoryResetLEDs.Set(false); +#endif + UpdateStatusLED(); + Instance().CancelTimer(); + Instance().mFunction = FunctionEvent::NoneSelected; + LOG_INF("Factory Reset has been Canceled"); } } } -void AppTask::StartBLEAdvertisementHandler(AppEvent *) +void AppTask::StartBLEAdvertisementHandler(const AppEvent &) { if (Server::GetInstance().GetFabricTable().FabricCount() != 0) { @@ -374,41 +409,39 @@ void AppTask::StartBLEAdvertisementHandler(AppEvent *) } } -void AppTask::UpdateLedStateEventHandler(AppEvent * aEvent) +void AppTask::UpdateLedStateEventHandler(const AppEvent & event) { - if (!aEvent) - return; - if (aEvent->Type == AppEvent::Type::UpdateLedState) + if (event.Type == AppEventType::UpdateLedState) { - aEvent->UpdateLedStateEvent.LedWidget->UpdateState(); + event.UpdateLedStateEvent.LedWidget->UpdateState(); } } -void AppTask::LEDStateUpdateHandler(LEDWidget & aLedWidget) +void AppTask::LEDStateUpdateHandler(LEDWidget & ledWidget) { AppEvent event; - event.Type = AppEvent::Type::UpdateLedState; + event.Type = AppEventType::UpdateLedState; event.Handler = UpdateLedStateEventHandler; - event.UpdateLedStateEvent.LedWidget = &aLedWidget; - PostEvent(&event); + event.UpdateLedStateEvent.LedWidget = &ledWidget; + PostEvent(event); } void AppTask::UpdateStatusLED() { #ifdef CONFIG_STATE_LEDS - /* Update the status LED. - * - * If thread and service provisioned, keep the LED On constantly. - * - * If the system has ble connection(s) uptill the stage above, THEN blink the LED at an even - * rate of 100ms. - * - * Otherwise, blink the LED On for a very short time. */ - if (Instance().mIsThreadProvisioned && Instance().mIsThreadEnabled) + // Update the status LED. + // + // If IPv6 network and service provisioned, keep the LED On constantly. + // + // If the system has BLE connection(s) until the stage above, THEN blink the LED at an even + // rate of 100ms. + // + // Otherwise, blink the LED for a very short time. + if (sIsNetworkProvisioned && sIsNetworkEnabled) { sStatusLED.Set(true); } - else if (Instance().mHaveBLEConnections) + else if (sHaveBLEConnections) { sStatusLED.Blink(LedConsts::StatusLed::Unprovisioned::kOn_ms, LedConsts::StatusLed::Unprovisioned::kOff_ms); } @@ -419,25 +452,52 @@ void AppTask::UpdateStatusLED() #endif } -void AppTask::ChipEventHandler(const ChipDeviceEvent * aEvent, intptr_t /* aArg */) +void AppTask::ChipEventHandler(const ChipDeviceEvent * event, intptr_t /* arg */) { - if (!aEvent) - return; - switch (aEvent->Type) + switch (event->Type) { case DeviceEventType::kCHIPoBLEAdvertisingChange: - Instance().mHaveBLEConnections = ConnectivityMgr().NumBLEConnections() != 0; - UpdateStatusLED(); - break; - case DeviceEventType::kThreadStateChange: - Instance().mIsThreadProvisioned = ConnectivityMgr().IsThreadProvisioned(); - Instance().mIsThreadEnabled = ConnectivityMgr().IsThreadEnabled(); +#ifdef CONFIG_CHIP_NFC_COMMISSIONING + if (event->CHIPoBLEAdvertisingChange.Result == kActivity_Started) + { + if (NFCMgr().IsTagEmulationStarted()) + { + LOG_INF("NFC Tag emulation is already started"); + } + else + { + ShareQRCodeOverNFC(chip::RendezvousInformationFlags(chip::RendezvousInformationFlag::kBLE)); + } + } + else if (event->CHIPoBLEAdvertisingChange.Result == kActivity_Stopped) + { + NFCMgr().StopTagEmulation(); + } +#endif + sHaveBLEConnections = ConnectivityMgr().NumBLEConnections() != 0; UpdateStatusLED(); break; +#if defined(CONFIG_NET_L2_OPENTHREAD) case DeviceEventType::kDnssdPlatformInitialized: #if CONFIG_CHIP_OTA_REQUESTOR InitBasicOTARequestor(); +#endif // CONFIG_CHIP_OTA_REQUESTOR + break; + case DeviceEventType::kThreadStateChange: + sIsNetworkProvisioned = ConnectivityMgr().IsThreadProvisioned(); + sIsNetworkEnabled = ConnectivityMgr().IsThreadEnabled(); +#elif defined(CONFIG_CHIP_WIFI) + case DeviceEventType::kWiFiConnectivityChange: + sIsNetworkProvisioned = ConnectivityMgr().IsWiFiStationProvisioned(); + sIsNetworkEnabled = ConnectivityMgr().IsWiFiStationEnabled(); +#if CONFIG_CHIP_OTA_REQUESTOR + if (event->WiFiConnectivityChange.Result == kConnectivity_Established) + { + InitBasicOTARequestor(); + } +#endif // CONFIG_CHIP_OTA_REQUESTOR #endif + UpdateStatusLED(); break; default: break; @@ -456,23 +516,19 @@ void AppTask::StartTimer(uint32_t aTimeoutInMs) Instance().mFunctionTimerActive = true; } -void AppTask::PostEvent(AppEvent * aEvent) +void AppTask::PostEvent(const AppEvent & event) { - if (!aEvent) - return; - if (k_msgq_put(&sAppEventQueue, aEvent, K_NO_WAIT)) + if (k_msgq_put(&sAppEventQueue, &event, K_NO_WAIT) != 0) { LOG_INF("Failed to post event to app task event queue"); } } -void AppTask::DispatchEvent(AppEvent * aEvent) +void AppTask::DispatchEvent(const AppEvent & event) { - if (!aEvent) - return; - if (aEvent->Handler) + if (event.Handler) { - aEvent->Handler(aEvent); + event.Handler(event); } else { diff --git a/examples/all-clusters-app/nrfconnect/main/include/AppConfig.h b/examples/all-clusters-app/nrfconnect/main/include/AppConfig.h index 01465d3bb17a62..91b8ad96cbc05b 100644 --- a/examples/all-clusters-app/nrfconnect/main/include/AppConfig.h +++ b/examples/all-clusters-app/nrfconnect/main/include/AppConfig.h @@ -17,11 +17,25 @@ #pragma once +#include "BoardUtil.h" + // ---- All Clusters Application example config ---- #define FUNCTION_BUTTON DK_BTN1 #define FUNCTION_BUTTON_MASK DK_BTN1_MSK + +#if NUMBER_OF_BUTTONS == 2 +#define BLE_ADVERTISEMENT_START_BUTTON DK_BTN2 +#define BLE_ADVERTISEMENT_START_BUTTON_MASK DK_BTN2_MSK +#else #define BLE_ADVERTISEMENT_START_BUTTON DK_BTN4 #define BLE_ADVERTISEMENT_START_BUTTON_MASK DK_BTN4_MSK +#endif #define SYSTEM_STATE_LED DK_LED1 +#define IDENTIFY_STATE_LED DK_LED2 + +#if NUMBER_OF_LEDS == 4 +#define FACTORY_RESET_SIGNAL_LED DK_LED3 +#define FACTORY_RESET_SIGNAL_LED1 DK_LED4 +#endif diff --git a/examples/all-clusters-app/nrfconnect/main/include/AppEvent.h b/examples/all-clusters-app/nrfconnect/main/include/AppEvent.h index f6cac85b182b32..ccefe5d52bbdac 100644 --- a/examples/all-clusters-app/nrfconnect/main/include/AppEvent.h +++ b/examples/all-clusters-app/nrfconnect/main/include/AppEvent.h @@ -19,22 +19,33 @@ #include +#include "EventTypes.h" + class LEDWidget; -struct AppEvent +enum class AppEventType : uint8_t { - using EventHandler = void (*)(AppEvent *); - - enum class Type : uint8_t - { - None, - Button, - Timer, - UpdateLedState, - }; + None = 0, + Button, + ButtonPushed, + ButtonReleased, + Timer, + UpdateLedState, + IdentifyStart, + IdentifyStop, + StartSMPAdvertising +}; - Type Type{ Type::None }; +enum class FunctionEvent : uint8_t +{ + NoneSelected = 0, + SoftwareUpdate = 0, + FactoryReset, + AdvertisingStart +}; +struct AppEvent +{ union { struct @@ -47,10 +58,16 @@ struct AppEvent void * Context; } TimerEvent; struct + { + uint8_t Action; + int32_t Actor; + } LockEvent; + struct { LEDWidget * LedWidget; } UpdateLedStateEvent; }; + AppEventType Type{ AppEventType::None }; EventHandler Handler; }; diff --git a/examples/all-clusters-app/nrfconnect/main/include/AppTask.h b/examples/all-clusters-app/nrfconnect/main/include/AppTask.h index 04f44f41e49551..72e87e480f9fc1 100644 --- a/examples/all-clusters-app/nrfconnect/main/include/AppTask.h +++ b/examples/all-clusters-app/nrfconnect/main/include/AppTask.h @@ -19,13 +19,19 @@ #include +#include "AppEvent.h" +#include "LEDWidget.h" + #if CONFIG_CHIP_FACTORY_DATA #include #endif +#ifdef CONFIG_MCUMGR_SMP_BT +#include "DFUOverSMP.h" +#endif + struct k_timer; -class AppEvent; -class LEDWidget; +struct Identify; class AppTask { @@ -37,36 +43,35 @@ class AppTask }; CHIP_ERROR StartApp(); -private: - enum class OperatingMode : uint8_t - { - Normal, - FactoryReset, - Invalid - }; + static void IdentifyStartHandler(Identify *); + static void IdentifyStopHandler(Identify *); + + static void PostEvent(const AppEvent & event); +private: CHIP_ERROR Init(); - void DispatchEvent(AppEvent * aEvent); - - // statics needed to interact with zephyr C API - static void CancelTimer(void); - static void StartTimer(uint32_t aTimeoutInMs); - static void FunctionTimerEventHandler(AppEvent * aEvent); - static void FunctionHandler(AppEvent * aEvent); - static void ButtonEventHandler(uint32_t aButtonsState, uint32_t aHasChanged); - static void TimerEventHandler(k_timer * aTimer); - static void PostEvent(AppEvent * aEvent); + + static void CancelTimer(); + static void StartTimer(uint32_t timeoutInMs); + + static void DispatchEvent(const AppEvent & event); + static void FunctionTimerEventHandler(const AppEvent & event); + static void FunctionHandler(const AppEvent & event); + static void StartBLEAdvertisementHandler(const AppEvent & event); + static void UpdateLedStateEventHandler(const AppEvent & event); + + static void ChipEventHandler(const chip::DeviceLayer::ChipDeviceEvent * event, intptr_t arg); + static void ButtonEventHandler(uint32_t buttonState, uint32_t hasChanged); + static void LEDStateUpdateHandler(LEDWidget & ledWidget); + static void FunctionTimerTimeoutCallback(k_timer * timer); static void UpdateStatusLED(); - static void LEDStateUpdateHandler(LEDWidget & aLedWidget); - static void UpdateLedStateEventHandler(AppEvent * aEvent); - static void StartBLEAdvertisementHandler(AppEvent * aEvent); - static void ChipEventHandler(const chip::DeviceLayer::ChipDeviceEvent * aEvent, intptr_t aArg); - - OperatingMode mMode{ OperatingMode::Normal }; - bool mFunctionTimerActive{ false }; - bool mIsThreadProvisioned{ false }; - bool mIsThreadEnabled{ false }; - bool mHaveBLEConnections{ false }; + +#ifdef CONFIG_MCUMGR_SMP_BT + static void RequestSMPAdvertisingStart(void); +#endif + + FunctionEvent mFunction = FunctionEvent::NoneSelected; + bool mFunctionTimerActive = false; #if CONFIG_CHIP_FACTORY_DATA chip::DeviceLayer::FactoryDataProvider mFactoryDataProvider; diff --git a/examples/all-clusters-app/nrfconnect/main/main.cpp b/examples/all-clusters-app/nrfconnect/main/main.cpp index 8563856d1cffd6..400f9b30e0dd01 100644 --- a/examples/all-clusters-app/nrfconnect/main/main.cpp +++ b/examples/all-clusters-app/nrfconnect/main/main.cpp @@ -24,7 +24,7 @@ #include #endif -LOG_MODULE_REGISTER(app, CONFIG_MATTER_LOG_LEVEL); +LOG_MODULE_REGISTER(app, CONFIG_CHIP_APP_LOG_LEVEL); using namespace ::chip; diff --git a/examples/all-clusters-app/nrfconnect/prj.conf b/examples/all-clusters-app/nrfconnect/prj.conf index f56965b831a7cf..ee2bee67b64ce4 100644 --- a/examples/all-clusters-app/nrfconnect/prj.conf +++ b/examples/all-clusters-app/nrfconnect/prj.conf @@ -14,21 +14,20 @@ # limitations under the License. # -CONFIG_CHIP=y -CONFIG_STD_CPP14=y - # This sample uses Kconfig.defaults to set options common for all # samples. This file should contain only options specific for this sample # or overrides of default values. +# Enable CHIP +CONFIG_CHIP=y +CONFIG_CHIP_PROJECT_CONFIG="main/include/CHIPProjectConfig.h" +# CHIP PID: 32769 == 0x8001 (all-clusters-app) +CONFIG_CHIP_DEVICE_PRODUCT_ID=32769 +CONFIG_STD_CPP14=y + # Add support for LEDs and buttons on Nordic development kits CONFIG_DK_LIBRARY=y -# OpenThread settings -CONFIG_OPENTHREAD_NORDIC_LIBRARY_MTD=y -CONFIG_OPENTHREAD_MTD=y -CONFIG_OPENTHREAD_FTD=n - # Bluetooth overrides CONFIG_BT_DEVICE_NAME="AllClusters" @@ -42,9 +41,3 @@ CONFIG_CHIP_OTA_REQUESTOR=n # Disable QSPI NOR CONFIG_CHIP_QSPI_NOR=n - -# CHIP configuration -CONFIG_CHIP_PROJECT_CONFIG="main/include/CHIPProjectConfig.h" - -# CHIP PID: 32769 == 0x8001 (all-clusters-app) -CONFIG_CHIP_DEVICE_PRODUCT_ID=32769 diff --git a/examples/all-clusters-app/nrfconnect/prj_dfu.conf b/examples/all-clusters-app/nrfconnect/prj_dfu.conf index 9738e9fc77ae7a..b91bf3fa8298a6 100644 --- a/examples/all-clusters-app/nrfconnect/prj_dfu.conf +++ b/examples/all-clusters-app/nrfconnect/prj_dfu.conf @@ -14,21 +14,19 @@ # limitations under the License. # -CONFIG_CHIP=y -CONFIG_STD_CPP14=y - # This sample uses Kconfig.defaults to set options common for all # samples. This file should contain only options specific for this sample # or overrides of default values. +# Enable CHIP +CONFIG_CHIP=y +CONFIG_CHIP_PROJECT_CONFIG="main/include/CHIPProjectConfig.h" +# CHIP PID: 32769 == 0x8001 (all-clusters-app) +CONFIG_CHIP_DEVICE_PRODUCT_ID=32769 +CONFIG_STD_CPP14=y # Add support for LEDs and buttons on Nordic development kits CONFIG_DK_LIBRARY=y -# OpenThread settings -CONFIG_OPENTHREAD_NORDIC_LIBRARY_MTD=y -CONFIG_OPENTHREAD_MTD=y -CONFIG_OPENTHREAD_FTD=n - # Bluetooth overrides CONFIG_BT_DEVICE_NAME="AllClusters" @@ -37,12 +35,6 @@ CONFIG_THREAD_NAME=y CONFIG_MPU_STACK_GUARD=y CONFIG_RESET_ON_FATAL_ERROR=n -# CHIP configuration -CONFIG_CHIP_PROJECT_CONFIG="main/include/CHIPProjectConfig.h" - -# CHIP PID: 32769 == 0x8001 (all-clusters-app) -CONFIG_CHIP_DEVICE_PRODUCT_ID=32769 - # reduce application size by disabling including assertions in the output file. CONFIG_ASSERT_VERBOSE=n CONFIG_ASSERT_NO_FILE_INFO=y diff --git a/examples/all-clusters-app/nrfconnect/prj_release.conf b/examples/all-clusters-app/nrfconnect/prj_release.conf index d9905e84464429..5816426f0fc09e 100644 --- a/examples/all-clusters-app/nrfconnect/prj_release.conf +++ b/examples/all-clusters-app/nrfconnect/prj_release.conf @@ -14,33 +14,26 @@ # limitations under the License. # -CONFIG_CHIP=y -CONFIG_STD_CPP14=y - # This sample uses Kconfig.defaults to set options common for all # samples. This file should contain only options specific for this sample # or overrides of default values. +# Enable CHIP +CONFIG_CHIP=y +CONFIG_CHIP_PROJECT_CONFIG="main/include/CHIPProjectConfig.h" +# CHIP PID: 32769 == 0x8001 (all-clusters-app) +CONFIG_CHIP_DEVICE_PRODUCT_ID=32769 +CONFIG_STD_CPP14=y + # Add support for LEDs and buttons on Nordic development kits CONFIG_DK_LIBRARY=y -# OpenThread settings -CONFIG_OPENTHREAD_NORDIC_LIBRARY_MTD=y -CONFIG_OPENTHREAD_MTD=y -CONFIG_OPENTHREAD_FTD=n - # Bluetooth overrides CONFIG_BT_DEVICE_NAME="AllClusters" # Enable system reset on fatal error CONFIG_RESET_ON_FATAL_ERROR=y -# CHIP configuration -CONFIG_CHIP_PROJECT_CONFIG="main/include/CHIPProjectConfig.h" - -# CHIP PID: 32769 == 0x8001 (all-clusters-app) -CONFIG_CHIP_DEVICE_PRODUCT_ID=32769 - # Disable all debug features CONFIG_SHELL=n CONFIG_OPENTHREAD_SHELL=n diff --git a/examples/all-clusters-minimal-app/nrfconnect/Kconfig b/examples/all-clusters-minimal-app/nrfconnect/Kconfig index 9fcdb41ab33208..33919ac5fc2337 100644 --- a/examples/all-clusters-minimal-app/nrfconnect/Kconfig +++ b/examples/all-clusters-minimal-app/nrfconnect/Kconfig @@ -22,6 +22,19 @@ config STATE_LEDS Use LEDs to render the current state of the device such as the progress of commissioning of the device into a network or the factory reset initiation. +# Sample configuration used for Thread networking +if NET_L2_OPENTHREAD + +choice OPENTHREAD_NORDIC_LIBRARY_CONFIGURATION + default OPENTHREAD_NORDIC_LIBRARY_MTD +endchoice + +choice OPENTHREAD_DEVICE_TYPE + default OPENTHREAD_MTD +endchoice + +endif # NET_L2_OPENTHREAD + rsource "../../../config/nrfconnect/chip-module/Kconfig.features" rsource "../../../config/nrfconnect/chip-module/Kconfig.defaults" source "Kconfig.zephyr" diff --git a/examples/all-clusters-minimal-app/nrfconnect/README.md b/examples/all-clusters-minimal-app/nrfconnect/README.md index 5f2d742502ed81..a8430950ab9c5c 100644 --- a/examples/all-clusters-minimal-app/nrfconnect/README.md +++ b/examples/all-clusters-minimal-app/nrfconnect/README.md @@ -55,11 +55,17 @@ and [Zephyr RTOS](https://zephyrproject.org/). Visit Matter's [nRF Connect platform overview](../../../docs/guides/nrfconnect_platform_overview.md) to read more about the platform structure and dependencies. -The Matter device that runs the all clusters application is controlled by the -Matter controller device over the Thread protocol. By default, the Matter device -has Thread disabled, and it should be paired with Matter controller and get -configuration from it. Some actions required before establishing full -communication are described below. +By default, the Matter accessory device has IPv6 networking disabled. You must +pair it with the Matter controller over Bluetooth® LE to get the configuration +from the controller to use the device within a Thread or Wi-Fi network. You have +to make the device discoverable manually (for security reasons). See +[Bluetooth LE advertising](#bluetooth-le-advertising) to learn how to do this. +The controller must get the commissioning information from the Matter accessory +device and provision the device into the network. + +You can test this application remotely over the Thread or the Wi-Fi protocol, +which in either case requires more devices, including a Matter controller that +you can configure either on a PC or a mobile device. ### Bluetooth LE advertising @@ -137,8 +143,8 @@ following states are possible: Bluetooth LE. - _Short Flash Off (950ms on/50ms off)_ — The device is fully - provisioned, but does not yet have full Thread network or service - connectivity. + provisioned, but does not yet have full connectivity for Thread or Wi-Fi + network, or the related services. - _Solid On_ — The device is fully provisioned and has full Thread network and service connectivity. diff --git a/examples/all-clusters-minimal-app/nrfconnect/boards/nrf52840dk_nrf52840.overlay b/examples/all-clusters-minimal-app/nrfconnect/boards/nrf52840dk_nrf52840.overlay index 04253ef9667617..566236731653a5 100644 --- a/examples/all-clusters-minimal-app/nrfconnect/boards/nrf52840dk_nrf52840.overlay +++ b/examples/all-clusters-minimal-app/nrfconnect/boards/nrf52840dk_nrf52840.overlay @@ -37,9 +37,6 @@ &uart1 { status = "disabled"; }; -&gpio1 { - status = "disabled"; -}; &i2c0 { status = "disabled"; }; diff --git a/examples/all-clusters-minimal-app/nrfconnect/main/AppTask.cpp b/examples/all-clusters-minimal-app/nrfconnect/main/AppTask.cpp index b78d291f1b74c0..25c4190544a18a 100644 --- a/examples/all-clusters-minimal-app/nrfconnect/main/AppTask.cpp +++ b/examples/all-clusters-minimal-app/nrfconnect/main/AppTask.cpp @@ -40,24 +40,28 @@ #include #include +LOG_MODULE_DECLARE(app, CONFIG_CHIP_APP_LOG_LEVEL); + using namespace ::chip; using namespace ::chip::Credentials; using namespace ::chip::DeviceLayer; -#define FACTORY_RESET_TRIGGER_TIMEOUT 3000 -#define FACTORY_RESET_CANCEL_WINDOW_TIMEOUT 3000 -#define APP_EVENT_QUEUE_SIZE 10 -#define BUTTON_PUSH_EVENT 1 -#define BUTTON_RELEASE_EVENT 0 - -LOG_MODULE_DECLARE(app, CONFIG_MATTER_LOG_LEVEL); -K_MSGQ_DEFINE(sAppEventQueue, sizeof(AppEvent), APP_EVENT_QUEUE_SIZE, alignof(AppEvent)); +namespace { +constexpr uint32_t kFactoryResetTriggerTimeout = 3000; +constexpr uint32_t kFactoryResetCancelWindowTimeout = 3000; +constexpr size_t kAppEventQueueSize = 10; +constexpr EndpointId kNetworkCommissioningEndpointSecondary = 0xFFFE; -static LEDWidget sStatusLED; -static UnusedLedsWrapper<3> sUnusedLeds{ { DK_LED2, DK_LED3, DK_LED4 } }; +K_MSGQ_DEFINE(sAppEventQueue, sizeof(AppEvent), kAppEventQueueSize, alignof(AppEvent)); static k_timer sFunctionTimer; -constexpr EndpointId kNetworkCommissioningEndpointSecondary = 0xFFFE; +LEDWidget sStatusLED; +FactoryResetLEDsWrapper<3> sFactoryResetLEDs{ { FACTORY_RESET_SIGNAL_LED, FACTORY_RESET_SIGNAL_LED1, FACTORY_RESET_SIGNAL_LED2 } }; + +bool sIsNetworkProvisioned = false; +bool sIsNetworkEnabled = false; +bool sHaveBLEConnections = false; +} // namespace namespace LedConsts { constexpr uint32_t kBlinkRate_ms{ 500 }; @@ -133,7 +137,7 @@ CHIP_ERROR AppTask::Init() } // Initialize timer user data - k_timer_init(&sFunctionTimer, &AppTask::TimerEventHandler, nullptr); + k_timer_init(&sFunctionTimer, &AppTask::FunctionTimerTimeoutCallback, nullptr); k_timer_user_data_set(&sFunctionTimer, this); // Initialize CHIP server @@ -177,120 +181,141 @@ CHIP_ERROR AppTask::StartApp() while (true) { k_msgq_get(&sAppEventQueue, &event, K_FOREVER); - DispatchEvent(&event); + DispatchEvent(event); } return CHIP_NO_ERROR; } -void AppTask::ButtonEventHandler(uint32_t aButtonState, uint32_t aHasChanged) +void AppTask::ButtonEventHandler(uint32_t buttonState, uint32_t hasChanged) { AppEvent event; - event.Type = AppEvent::Type::Button; + event.Type = AppEventType::Button; - if (FUNCTION_BUTTON_MASK & aHasChanged) + if (FUNCTION_BUTTON_MASK & hasChanged) { - event.ButtonEvent.PinNo = FUNCTION_BUTTON; - event.ButtonEvent.Action = (FUNCTION_BUTTON_MASK & aButtonState) ? BUTTON_PUSH_EVENT : BUTTON_RELEASE_EVENT; - event.Handler = FunctionHandler; - PostEvent(&event); + event.ButtonEvent.PinNo = FUNCTION_BUTTON; + event.ButtonEvent.Action = + static_cast((FUNCTION_BUTTON_MASK & buttonState) ? AppEventType::ButtonPushed : AppEventType::ButtonReleased); + event.Handler = FunctionHandler; + PostEvent(event); } - if (BLE_ADVERTISEMENT_START_BUTTON_MASK & aButtonState & aHasChanged) + if (BLE_ADVERTISEMENT_START_BUTTON_MASK & buttonState & hasChanged) { event.ButtonEvent.PinNo = BLE_ADVERTISEMENT_START_BUTTON; - event.ButtonEvent.Action = BUTTON_PUSH_EVENT; + event.ButtonEvent.Action = static_cast(AppEventType::ButtonPushed); event.Handler = StartBLEAdvertisementHandler; - PostEvent(&event); + PostEvent(event); } } -void AppTask::TimerEventHandler(k_timer * aTimer) +#ifdef CONFIG_MCUMGR_SMP_BT +void AppTask::RequestSMPAdvertisingStart(void) { - if (!aTimer) + AppEvent event; + event.Type = AppEventType::StartSMPAdvertising; + event.Handler = [](const AppEvent &) { GetDFUOverSMP().StartBLEAdvertising(); }; + PostEvent(event); +} +#endif + +void AppTask::FunctionTimerTimeoutCallback(k_timer * timer) +{ + if (!timer) + { return; + } AppEvent event; - event.Type = AppEvent::Type::Timer; - event.TimerEvent.Context = k_timer_user_data_get(aTimer); + event.Type = AppEventType::Timer; + event.TimerEvent.Context = k_timer_user_data_get(timer); event.Handler = FunctionTimerEventHandler; - PostEvent(&event); + PostEvent(event); } -void AppTask::FunctionTimerEventHandler(AppEvent * aEvent) +void AppTask::FunctionTimerEventHandler(const AppEvent & event) { - if (!aEvent) - return; - if (aEvent->Type != AppEvent::Type::Timer) + if (event.Type != AppEventType::Timer) return; - // If we reached here, the button was held past FACTORY_RESET_TRIGGER_TIMEOUT, initiate factory reset - if (Instance().mFunctionTimerActive && Instance().mMode == OperatingMode::Normal) + // If we reached here, the button was held past kFactoryResetTriggerTimeout, initiate factory reset + if (Instance().mFunctionTimerActive && Instance().mFunction == FunctionEvent::SoftwareUpdate) { - LOG_INF("Factory Reset Triggered. Release button within %ums to cancel.", FACTORY_RESET_TRIGGER_TIMEOUT); + LOG_INF("Factory Reset Triggered. Release button within %ums to cancel.", kFactoryResetTriggerTimeout); - // Start timer for FACTORY_RESET_CANCEL_WINDOW_TIMEOUT to allow user to cancel, if required. - StartTimer(FACTORY_RESET_CANCEL_WINDOW_TIMEOUT); - Instance().mMode = OperatingMode::FactoryReset; + // Start timer for kFactoryResetCancelWindowTimeout to allow user to cancel, if required. + Instance().StartTimer(kFactoryResetCancelWindowTimeout); + Instance().mFunction = FunctionEvent::FactoryReset; #ifdef CONFIG_STATE_LEDS // Turn off all LEDs before starting blink to make sure blink is co-ordinated. sStatusLED.Set(false); - sUnusedLeds.Set(false); + sFactoryResetLEDs.Set(false); sStatusLED.Blink(LedConsts::kBlinkRate_ms); - sUnusedLeds.Blink(LedConsts::kBlinkRate_ms); + sFactoryResetLEDs.Blink(LedConsts::kBlinkRate_ms); #endif } - else if (Instance().mFunctionTimerActive && Instance().mMode == OperatingMode::FactoryReset) + else if (Instance().mFunctionTimerActive && Instance().mFunction == FunctionEvent::FactoryReset) { // Actually trigger Factory Reset - Instance().mMode = OperatingMode::Normal; - ConfigurationMgr().InitiateFactoryReset(); + Instance().mFunction = FunctionEvent::NoneSelected; + + chip::Server::GetInstance().ScheduleFactoryReset(); } } -void AppTask::FunctionHandler(AppEvent * aEvent) +void AppTask::FunctionHandler(const AppEvent & event) { - if (!aEvent) - return; - if (aEvent->ButtonEvent.PinNo != FUNCTION_BUTTON) + if (event.ButtonEvent.PinNo != FUNCTION_BUTTON) return; // To initiate factory reset: press the FUNCTION_BUTTON for FACTORY_RESET_TRIGGER_TIMEOUT + FACTORY_RESET_CANCEL_WINDOW_TIMEOUT // All LEDs start blinking after FACTORY_RESET_TRIGGER_TIMEOUT to signal factory reset has been initiated. // To cancel factory reset: release the FUNCTION_BUTTON once all LEDs start blinking within the // FACTORY_RESET_CANCEL_WINDOW_TIMEOUT - if (aEvent->ButtonEvent.Action == BUTTON_PUSH_EVENT) + if (event.ButtonEvent.Action == static_cast(AppEventType::ButtonPushed)) { - if (!Instance().mFunctionTimerActive && Instance().mMode == OperatingMode::Normal) + if (!Instance().mFunctionTimerActive && Instance().mFunction == FunctionEvent::NoneSelected) { - StartTimer(FACTORY_RESET_TRIGGER_TIMEOUT); + Instance().StartTimer(kFactoryResetTriggerTimeout); + + Instance().mFunction = FunctionEvent::SoftwareUpdate; } } else { - if (Instance().mFunctionTimerActive && Instance().mMode == OperatingMode::FactoryReset) + // If the button was released before factory reset got initiated, trigger a software update. + if (Instance().mFunctionTimerActive && Instance().mFunction == FunctionEvent::SoftwareUpdate) { - sUnusedLeds.Set(false); + Instance().CancelTimer(); - UpdateStatusLED(); - CancelTimer(); - - // Change the function to none selected since factory reset has been canceled. - Instance().mMode = OperatingMode::Normal; +#ifdef CONFIG_MCUMGR_SMP_BT + GetDFUOverSMP().StartServer(); +#else + LOG_INF("Software update is disabled"); +#endif + Instance().mFunction = FunctionEvent::NoneSelected; + } + else if (Instance().mFunctionTimerActive && Instance().mFunction == FunctionEvent::FactoryReset) + { + sFactoryResetLEDs.Set(false); + UpdateStatusLED(); + Instance().CancelTimer(); + Instance().mFunction = FunctionEvent::NoneSelected; LOG_INF("Factory Reset has been Canceled"); } else if (Instance().mFunctionTimerActive) { CancelTimer(); - Instance().mMode = OperatingMode::Normal; + Instance().mFunction = FunctionEvent::NoneSelected; } } } -void AppTask::StartBLEAdvertisementHandler(AppEvent *) +void AppTask::StartBLEAdvertisementHandler(const AppEvent &) { if (Server::GetInstance().GetFabricTable().FabricCount() != 0) { @@ -310,41 +335,39 @@ void AppTask::StartBLEAdvertisementHandler(AppEvent *) } } -void AppTask::UpdateLedStateEventHandler(AppEvent * aEvent) +void AppTask::UpdateLedStateEventHandler(const AppEvent & event) { - if (!aEvent) - return; - if (aEvent->Type == AppEvent::Type::UpdateLedState) + if (event.Type == AppEventType::UpdateLedState) { - aEvent->UpdateLedStateEvent.LedWidget->UpdateState(); + event.UpdateLedStateEvent.LedWidget->UpdateState(); } } -void AppTask::LEDStateUpdateHandler(LEDWidget & aLedWidget) +void AppTask::LEDStateUpdateHandler(LEDWidget & ledWidget) { AppEvent event; - event.Type = AppEvent::Type::UpdateLedState; + event.Type = AppEventType::UpdateLedState; event.Handler = UpdateLedStateEventHandler; - event.UpdateLedStateEvent.LedWidget = &aLedWidget; - PostEvent(&event); + event.UpdateLedStateEvent.LedWidget = &ledWidget; + PostEvent(event); } void AppTask::UpdateStatusLED() { #ifdef CONFIG_STATE_LEDS - /* Update the status LED. - * - * If thread and service provisioned, keep the LED On constantly. - * - * If the system has ble connection(s) uptill the stage above, THEN blink the LED at an even - * rate of 100ms. - * - * Otherwise, blink the LED On for a very short time. */ - if (Instance().mIsThreadProvisioned && Instance().mIsThreadEnabled) + // Update the status LED. + // + // If thread and service provisioned, keep the LED On constantly. + // + // If the system has ble connection(s) uptill the stage above, THEN blink the LED at an even + // rate of 100ms. + // + // Otherwise, blink the LED On for a very short time. + if (sIsNetworkProvisioned && sIsNetworkEnabled) { sStatusLED.Set(true); } - else if (Instance().mHaveBLEConnections) + else if (sHaveBLEConnections) { sStatusLED.Blink(LedConsts::StatusLed::Unprovisioned::kOn_ms, LedConsts::StatusLed::Unprovisioned::kOff_ms); } @@ -355,19 +378,34 @@ void AppTask::UpdateStatusLED() #endif } -void AppTask::ChipEventHandler(const ChipDeviceEvent * aEvent, intptr_t /* aArg */) +void AppTask::ChipEventHandler(const ChipDeviceEvent * event, intptr_t /* arg */) { - if (!aEvent) - return; - switch (aEvent->Type) + switch (event->Type) { case DeviceEventType::kCHIPoBLEAdvertisingChange: - Instance().mHaveBLEConnections = ConnectivityMgr().NumBLEConnections() != 0; +#ifdef CONFIG_CHIP_NFC_COMMISSIONING + if (event->CHIPoBLEAdvertisingChange.Result == kActivity_Started) + { + if (NFCMgr().IsTagEmulationStarted()) + { + LOG_INF("NFC Tag emulation is already started"); + } + else + { + ShareQRCodeOverNFC(chip::RendezvousInformationFlags(chip::RendezvousInformationFlag::kBLE)); + } + } + else if (event->CHIPoBLEAdvertisingChange.Result == kActivity_Stopped) + { + NFCMgr().StopTagEmulation(); + } +#endif + sHaveBLEConnections = ConnectivityMgr().NumBLEConnections() != 0; UpdateStatusLED(); break; case DeviceEventType::kThreadStateChange: - Instance().mIsThreadProvisioned = ConnectivityMgr().IsThreadProvisioned(); - Instance().mIsThreadEnabled = ConnectivityMgr().IsThreadEnabled(); + sIsNetworkProvisioned = ConnectivityMgr().IsThreadProvisioned(); + sIsNetworkEnabled = ConnectivityMgr().IsThreadEnabled(); UpdateStatusLED(); break; case DeviceEventType::kDnssdPlatformInitialized: @@ -392,23 +430,19 @@ void AppTask::StartTimer(uint32_t aTimeoutInMs) Instance().mFunctionTimerActive = true; } -void AppTask::PostEvent(AppEvent * aEvent) +void AppTask::PostEvent(const AppEvent & event) { - if (!aEvent) - return; - if (k_msgq_put(&sAppEventQueue, aEvent, K_NO_WAIT)) + if (k_msgq_put(&sAppEventQueue, &event, K_NO_WAIT) != 0) { LOG_INF("Failed to post event to app task event queue"); } } -void AppTask::DispatchEvent(AppEvent * aEvent) +void AppTask::DispatchEvent(const AppEvent & event) { - if (!aEvent) - return; - if (aEvent->Handler) + if (event.Handler) { - aEvent->Handler(aEvent); + event.Handler(event); } else { diff --git a/examples/all-clusters-minimal-app/nrfconnect/main/include/AppConfig.h b/examples/all-clusters-minimal-app/nrfconnect/main/include/AppConfig.h index 01465d3bb17a62..e36ebba4e03d81 100644 --- a/examples/all-clusters-minimal-app/nrfconnect/main/include/AppConfig.h +++ b/examples/all-clusters-minimal-app/nrfconnect/main/include/AppConfig.h @@ -25,3 +25,6 @@ #define BLE_ADVERTISEMENT_START_BUTTON_MASK DK_BTN4_MSK #define SYSTEM_STATE_LED DK_LED1 +#define FACTORY_RESET_SIGNAL_LED DK_LED2 +#define FACTORY_RESET_SIGNAL_LED1 DK_LED3 +#define FACTORY_RESET_SIGNAL_LED2 DK_LED4 diff --git a/examples/all-clusters-minimal-app/nrfconnect/main/include/AppEvent.h b/examples/all-clusters-minimal-app/nrfconnect/main/include/AppEvent.h index f6cac85b182b32..4589d636a4decf 100644 --- a/examples/all-clusters-minimal-app/nrfconnect/main/include/AppEvent.h +++ b/examples/all-clusters-minimal-app/nrfconnect/main/include/AppEvent.h @@ -19,22 +19,33 @@ #include +#include "EventTypes.h" + class LEDWidget; -struct AppEvent +enum class AppEventType : uint8_t { - using EventHandler = void (*)(AppEvent *); - - enum class Type : uint8_t - { - None, - Button, - Timer, - UpdateLedState, - }; + None = 0, + Button, + ButtonPushed, + ButtonReleased, + Timer, + UpdateLedState, + IdentifyStart, + IdentifyStop, + StartSMPAdvertising +}; - Type Type{ Type::None }; +enum class FunctionEvent : uint8_t +{ + NoneSelected = 0, + SoftwareUpdate = 0, + FactoryReset, + AdvertisingStart +}; +struct AppEvent +{ union { struct @@ -52,5 +63,6 @@ struct AppEvent } UpdateLedStateEvent; }; + AppEventType Type{ AppEventType::None }; EventHandler Handler; }; diff --git a/examples/all-clusters-minimal-app/nrfconnect/main/include/AppTask.h b/examples/all-clusters-minimal-app/nrfconnect/main/include/AppTask.h index 04f44f41e49551..94a3de35f04038 100644 --- a/examples/all-clusters-minimal-app/nrfconnect/main/include/AppTask.h +++ b/examples/all-clusters-minimal-app/nrfconnect/main/include/AppTask.h @@ -19,13 +19,19 @@ #include +#include "AppEvent.h" +#include "LEDWidget.h" + #if CONFIG_CHIP_FACTORY_DATA #include #endif +#ifdef CONFIG_MCUMGR_SMP_BT +#include "DFUOverSMP.h" +#endif + struct k_timer; -class AppEvent; -class LEDWidget; +struct Identify; class AppTask { @@ -38,35 +44,30 @@ class AppTask CHIP_ERROR StartApp(); private: - enum class OperatingMode : uint8_t - { - Normal, - FactoryReset, - Invalid - }; - CHIP_ERROR Init(); - void DispatchEvent(AppEvent * aEvent); - // statics needed to interact with zephyr C API - static void CancelTimer(void); - static void StartTimer(uint32_t aTimeoutInMs); - static void FunctionTimerEventHandler(AppEvent * aEvent); - static void FunctionHandler(AppEvent * aEvent); - static void ButtonEventHandler(uint32_t aButtonsState, uint32_t aHasChanged); - static void TimerEventHandler(k_timer * aTimer); - static void PostEvent(AppEvent * aEvent); + static void CancelTimer(); + static void StartTimer(uint32_t timeoutInMs); + + static void PostEvent(const AppEvent & event); + static void DispatchEvent(const AppEvent & event); + static void FunctionTimerEventHandler(const AppEvent & event); + static void FunctionHandler(const AppEvent & event); + static void StartBLEAdvertisementHandler(const AppEvent & event); + static void UpdateLedStateEventHandler(const AppEvent & event); + + static void ChipEventHandler(const chip::DeviceLayer::ChipDeviceEvent * event, intptr_t arg); + static void ButtonEventHandler(uint32_t buttonState, uint32_t hasChanged); + static void LEDStateUpdateHandler(LEDWidget & ledWidget); + static void FunctionTimerTimeoutCallback(k_timer * timer); static void UpdateStatusLED(); - static void LEDStateUpdateHandler(LEDWidget & aLedWidget); - static void UpdateLedStateEventHandler(AppEvent * aEvent); - static void StartBLEAdvertisementHandler(AppEvent * aEvent); - static void ChipEventHandler(const chip::DeviceLayer::ChipDeviceEvent * aEvent, intptr_t aArg); - OperatingMode mMode{ OperatingMode::Normal }; - bool mFunctionTimerActive{ false }; - bool mIsThreadProvisioned{ false }; - bool mIsThreadEnabled{ false }; - bool mHaveBLEConnections{ false }; +#ifdef CONFIG_MCUMGR_SMP_BT + static void RequestSMPAdvertisingStart(void); +#endif + + FunctionEvent mFunction = FunctionEvent::NoneSelected; + bool mFunctionTimerActive = false; #if CONFIG_CHIP_FACTORY_DATA chip::DeviceLayer::FactoryDataProvider mFactoryDataProvider; diff --git a/examples/all-clusters-minimal-app/nrfconnect/main/main.cpp b/examples/all-clusters-minimal-app/nrfconnect/main/main.cpp index 8f71ca55e41070..ec11d180c8c911 100644 --- a/examples/all-clusters-minimal-app/nrfconnect/main/main.cpp +++ b/examples/all-clusters-minimal-app/nrfconnect/main/main.cpp @@ -24,7 +24,7 @@ #include #endif -LOG_MODULE_REGISTER(app, CONFIG_MATTER_LOG_LEVEL); +LOG_MODULE_REGISTER(app, CONFIG_CHIP_APP_LOG_LEVEL); using namespace ::chip; diff --git a/examples/all-clusters-minimal-app/nrfconnect/prj.conf b/examples/all-clusters-minimal-app/nrfconnect/prj.conf index 496ca240bc3ecd..2c2c8d2fc81c28 100644 --- a/examples/all-clusters-minimal-app/nrfconnect/prj.conf +++ b/examples/all-clusters-minimal-app/nrfconnect/prj.conf @@ -14,21 +14,20 @@ # limitations under the License. # -CONFIG_CHIP=y -CONFIG_STD_CPP14=y - # This sample uses Kconfig.defaults to set options common for all # samples. This file should contain only options specific for this sample # or overrides of default values. +# Enable CHIP +CONFIG_CHIP=y +CONFIG_CHIP_PROJECT_CONFIG="main/include/CHIPProjectConfig.h" +# CHIP PID: 32769 == 0x8001 (all-clusters-app-minimal) +CONFIG_CHIP_DEVICE_PRODUCT_ID=32769 +CONFIG_STD_CPP14=y + # Add support for LEDs and buttons on Nordic development kits CONFIG_DK_LIBRARY=y -# OpenThread settings -CONFIG_OPENTHREAD_NORDIC_LIBRARY_MTD=y -CONFIG_OPENTHREAD_MTD=y -CONFIG_OPENTHREAD_FTD=n - # Bluetooth overrides CONFIG_BT_DEVICE_NAME="AllClusters" @@ -42,9 +41,3 @@ CONFIG_CHIP_OTA_REQUESTOR=n # Disable QSPI NOR CONFIG_CHIP_QSPI_NOR=n - -# CHIP configuration -CONFIG_CHIP_PROJECT_CONFIG="main/include/CHIPProjectConfig.h" - -# CHIP PID: 32769 == 0x8001 (all-clusters-minimal-app) -CONFIG_CHIP_DEVICE_PRODUCT_ID=32769 diff --git a/examples/all-clusters-minimal-app/nrfconnect/prj_dfu.conf b/examples/all-clusters-minimal-app/nrfconnect/prj_dfu.conf index 3812357495deb6..292d7a086d4e89 100644 --- a/examples/all-clusters-minimal-app/nrfconnect/prj_dfu.conf +++ b/examples/all-clusters-minimal-app/nrfconnect/prj_dfu.conf @@ -14,21 +14,20 @@ # limitations under the License. # -CONFIG_CHIP=y -CONFIG_STD_CPP14=y - # This sample uses Kconfig.defaults to set options common for all # samples. This file should contain only options specific for this sample # or overrides of default values. +# Enable CHIP +CONFIG_CHIP=y +CONFIG_CHIP_PROJECT_CONFIG="main/include/CHIPProjectConfig.h" +# CHIP PID: 32769 == 0x8001 (all-clusters-minimal-app) +CONFIG_CHIP_DEVICE_PRODUCT_ID=32769 +CONFIG_STD_CPP14=y + # Add support for LEDs and buttons on Nordic development kits CONFIG_DK_LIBRARY=y -# OpenThread settings -CONFIG_OPENTHREAD_NORDIC_LIBRARY_MTD=y -CONFIG_OPENTHREAD_MTD=y -CONFIG_OPENTHREAD_FTD=n - # Bluetooth overrides CONFIG_BT_DEVICE_NAME="AllClusters" @@ -37,8 +36,8 @@ CONFIG_THREAD_NAME=y CONFIG_MPU_STACK_GUARD=y CONFIG_RESET_ON_FATAL_ERROR=n -# CHIP configuration -CONFIG_CHIP_PROJECT_CONFIG="main/include/CHIPProjectConfig.h" - -# CHIP PID: 32769 == 0x8001 (all-clusters-minimal-app) -CONFIG_CHIP_DEVICE_PRODUCT_ID=32769 +# reduce application size by disabling including assertions in the output file. +CONFIG_ASSERT_VERBOSE=n +CONFIG_ASSERT_NO_FILE_INFO=y +CONFIG_ASSERT_NO_COND_INFO=y +CONFIG_ASSERT_NO_MSG_INFO=y diff --git a/examples/all-clusters-minimal-app/nrfconnect/prj_release.conf b/examples/all-clusters-minimal-app/nrfconnect/prj_release.conf index 27667cbafad79b..33cd3f38589653 100644 --- a/examples/all-clusters-minimal-app/nrfconnect/prj_release.conf +++ b/examples/all-clusters-minimal-app/nrfconnect/prj_release.conf @@ -14,33 +14,26 @@ # limitations under the License. # -CONFIG_CHIP=y -CONFIG_STD_CPP14=y - # This sample uses Kconfig.defaults to set options common for all # samples. This file should contain only options specific for this sample # or overrides of default values. +# Enable CHIP +CONFIG_CHIP=y +CONFIG_CHIP_PROJECT_CONFIG="main/include/CHIPProjectConfig.h" +# CHIP PID: 32769 == 0x8001 (all-clusters-app-minimal) +CONFIG_CHIP_DEVICE_PRODUCT_ID=32769 +CONFIG_STD_CPP14=y + # Add support for LEDs and buttons on Nordic development kits CONFIG_DK_LIBRARY=y -# OpenThread settings -CONFIG_OPENTHREAD_NORDIC_LIBRARY_MTD=y -CONFIG_OPENTHREAD_MTD=y -CONFIG_OPENTHREAD_FTD=n - # Bluetooth overrides CONFIG_BT_DEVICE_NAME="AllClusters" # Enable system reset on fatal error CONFIG_RESET_ON_FATAL_ERROR=y -# CHIP configuration -CONFIG_CHIP_PROJECT_CONFIG="main/include/CHIPProjectConfig.h" - -# CHIP PID: 32769 == 0x8001 (all-clusters-minimal-app) -CONFIG_CHIP_DEVICE_PRODUCT_ID=32769 - # Disable all debug features CONFIG_SHELL=n CONFIG_OPENTHREAD_SHELL=n diff --git a/examples/light-switch-app/nrfconnect/CMakeLists.txt b/examples/light-switch-app/nrfconnect/CMakeLists.txt index b4210cbc069083..58c380e4d83c9f 100644 --- a/examples/light-switch-app/nrfconnect/CMakeLists.txt +++ b/examples/light-switch-app/nrfconnect/CMakeLists.txt @@ -25,6 +25,7 @@ include(${CHIP_ROOT}/config/nrfconnect/app/check-nrfconnect-version.cmake) # Set Kconfig root files that will be processed as a first Kconfig for used child images. set(mcuboot_KCONFIG_ROOT ${CHIP_ROOT}/config/nrfconnect/chip-module/Kconfig.mcuboot.root) set(multiprotocol_rpmsg_KCONFIG_ROOT ${CHIP_ROOT}/config/nrfconnect/chip-module/Kconfig.multiprotocol_rpmsg.root) +set(hci_rpmsg_KCONFIG_ROOT ${CHIP_ROOT}/config/nrfconnect/chip-module/Kconfig.hci_rpmsg.root) if(NOT CONF_FILE STREQUAL "prj_no_dfu.conf") set(PM_STATIC_YML_FILE ${CMAKE_CURRENT_SOURCE_DIR}/configuration/${BOARD}/pm_static_dfu.yml) diff --git a/examples/light-switch-app/nrfconnect/Kconfig b/examples/light-switch-app/nrfconnect/Kconfig index 8b4d87b0a956f0..042bdac8f72b68 100644 --- a/examples/light-switch-app/nrfconnect/Kconfig +++ b/examples/light-switch-app/nrfconnect/Kconfig @@ -23,6 +23,23 @@ config STATE_LEDS the device into a network or the factory reset initiation. Note that setting this option to 'n' does not disable the LED indicating the state of the simulated bolt. +# Sample configuration used for Thread networking +if NET_L2_OPENTHREAD + +choice OPENTHREAD_NORDIC_LIBRARY_CONFIGURATION + default OPENTHREAD_NORDIC_LIBRARY_MTD +endchoice + +choice OPENTHREAD_DEVICE_TYPE + default OPENTHREAD_MTD +endchoice + +config CHIP_ENABLE_SLEEPY_END_DEVICE_SUPPORT + bool + default y + +endif # NET_L2_OPENTHREAD + rsource "../../../config/nrfconnect/chip-module/Kconfig.features" rsource "../../../config/nrfconnect/chip-module/Kconfig.defaults" source "Kconfig.zephyr" diff --git a/examples/light-switch-app/nrfconnect/README.md b/examples/light-switch-app/nrfconnect/README.md index 7d55b031e6b73d..7aef1f6742fcb4 100644 --- a/examples/light-switch-app/nrfconnect/README.md +++ b/examples/light-switch-app/nrfconnect/README.md @@ -15,12 +15,16 @@ creating your own application. The example is based on [Matter](https://github.com/project-chip/connectedhomeip) and Nordic -Semiconductor's nRF Connect SDK, and supports remote access and control of a -lighting examples over a low-power, 802.15.4 Thread network. +Semiconductor's nRF Connect SDK, and was created to facilitate testing and +certification of a Matter device communicating over a low-power, 802.15.4 Thread +network, or Wi-Fi network. The example behaves as a Matter accessory, that is a device that can be paired -into an existing Matter network and can be controlled by this network. The -device works as a Thread Sleepy End Device. +into an existing Matter network and can be controlled by this network. In the +case of Thread, this device works as a Thread Sleepy End Device. Support for +both Thread and Wi-Fi is mutually exclusive and depends on the hardware +platform, so only one protocol can be supported for a specific light switch +device.
@@ -30,6 +34,7 @@ device works as a Thread Sleepy End Device. - [Device Firmware Upgrade](#device-firmware-upgrade) - [Requirements](#requirements) - [Supported devices](#supported_devices) + - [IPv6 network support](#ipv6-network-support) - [Device UI](#device-ui) - [LEDs](#leds) - [Buttons](#buttons) @@ -46,7 +51,12 @@ device works as a Thread Sleepy End Device. - [Example build types](#example-build-types) - [Flashing and debugging](#flashing-and-debugging) - [Testing the example](#testing-the-example) - - [Binding process](#binding-process) + - [Commissioning the lighting device](#commissioning-the-lighting-device) + - [Binding cluster and endpoints](#binding-cluster-and-endpoints) + - [Unicast binding to a remote endpoint using the CHIP Tool for Windows or Linux](#unicast-binding-to-a-remote-endpoint-using-the-chip-tool-for-windows-or-linux) + - [Group multicast binding to the group of remote endpoints using the CHIP Tool for Windows or Linux](#group-multicast-binding-to-the-group-of-remote-endpoints-using-the-chip-tool-for-windows-or-linux) + - [Testing the communication](#testing-the-communication) + - [Testing the Generic Switch](#testing-the-generic-switch) - [Testing Device Firmware Upgrade](#testing-device-firmware-upgrade)
@@ -62,6 +72,27 @@ and [Zephyr RTOS](https://zephyrproject.org/). Visit Matter's [nRF Connect platform overview](../../../docs/guides/nrfconnect_platform_overview.md) to read more about the platform structure and dependencies. +By default, the Matter accessory device has IPv6 networking disabled. You must +pair it with the Matter controller over Bluetooth® LE to get the configuration +from the controller to use the device within a Thread or Wi-Fi network. You have +to make the device discoverable manually (for security reasons). See +[Bluetooth LE advertising](#bluetooth-le-advertising) to learn how to do this. +The controller must get the commissioning information from the Matter accessory +device and provision the device into the network. + +You can test this application remotely over the Thread or the Wi-Fi protocol, +which in either case requires more devices, including a Matter controller that +you can configure either on a PC or a mobile device. + +The sample uses buttons for controlling the bound device's LEDs. You can test it +in the following ways: + +- Standalone, using a single DK that runs the light switch application. + +- Remotely over the Thread or the Wi-Fi protocol, which in either case + requires more devices, including a Matter controller that you can configure + either on a PC or a mobile device. + In Matter, the following types of light switch devices are available: - Group 1: On/Off Light Switch, Dimmer Switch, Color Dimmer Switch, Control @@ -98,7 +129,7 @@ features. For this reason, it sends event notifications `InitialPress` and The Matter device that runs the light switch application is controlled by the Matter controller device over the Thread protocol. By default, the Matter device -has Thread disabled, and it should be paired with Matter controller and get +has Thread disabled, and it should be paired with the Matter controller and get configuration from it. Some actions required before establishing full communication are described below. @@ -109,7 +140,11 @@ performing over-the-air Device Firmware Upgrade using Bluetooth LE. In this example, to commission the device onto a Matter network, it must be discoverable over Bluetooth LE. For security reasons, you must start Bluetooth -LE advertising manually after powering up the device by pressing **Button 4**. +LE advertising manually after powering up the device by pressing: + +- On nRF52840 DK, nRF5340 DK, and nRF21540 DK: **Button 4**. + +- On nRF7002 DK: **Button 2**. ### Bluetooth LE rendezvous @@ -119,16 +154,16 @@ commissioner role. To start the rendezvous, the controller must get the commissioning information from the Matter device. The data payload is encoded within a QR code, printed to -the UART console, and shared using an NFC tag. NFC tag emulation starts -automatically when Bluetooth LE advertising is started and stays enabled until -Bluetooth LE advertising timeout expires. +the UART console, and shared using an NFC tag. The emulation of the NFC tag +emulation starts automatically when Bluetooth LE advertising is started and +stays enabled until Bluetooth LE advertising timeout expires. -#### Thread provisioning +#### Thread or Wi-Fi provisioning -Last part of the rendezvous procedure, the provisioning operation involves -sending the Thread network credentials from the Matter controller to the Matter -device. As a result, the device is able to join the Thread network and -communicate with other Thread devices in the network. +The provisioning operation, which is the Last part of the rendezvous procedure, +involves sending the Thread or Wi-Fi network credentials from the Matter +controller to the Matter device. As a result, the device joins the Thread or +Wi-Fi network and can communicate with other devices in the network. ### Device Firmware Upgrade @@ -203,10 +238,20 @@ more information. The example supports building and running on the following devices: -| Hardware platform | Build target | Platform image | -| ----------------------------------------------------------------------------------------- | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | -| [nRF52840 DK](https://www.nordicsemi.com/Software-and-Tools/Development-Kits/nRF52840-DK) | `nrf52840dk_nrf52840` |
nRF52840 DKnRF52840 DK
| -| [nRF5340 DK](https://www.nordicsemi.com/Software-and-Tools/Development-Kits/nRF5340-DK) | `nrf5340dk_nrf5340_cpuapp` |
nRF5340 DKnRF5340 DK
| +| Hardware platform | Build target | Platform image | +| --------------------------------------------------------------------------------------------------------------- | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | +| [nRF52840 DK](https://www.nordicsemi.com/Software-and-Tools/Development-Kits/nRF52840-DK) | `nrf52840dk_nrf52840` |
nRF52840 DKnRF52840 DK
| +| [nRF5340 DK](https://www.nordicsemi.com/Software-and-Tools/Development-Kits/nRF5340-DK) | `nrf5340dk_nrf5340_cpuapp` |
nRF5340 DKnRF5340 DK
| +| [nRF7002 DK](https://developer.nordicsemi.com/nRF_Connect_SDK/doc/latest/nrf/ug_nrf7002.html#nrf7002dk-nrf5340) | `nrf7002dk_nrf5340_cpuapp` |
nRF7002DKnRF7002 DK
| + +### IPv6 network support + +The development kits for this sample offer the following IPv6 network support +for Matter: + +- Matter over Thread is supported for `nrf52840dk_nrf52840` and + `nrf5340dk_nrf5340_cpuapp`. +- Matter over Wi-Fi is supported for `nrf7002dk_nrf5340_cpuapp`. ### Additional requirements for testing @@ -246,11 +291,10 @@ following states are possible: Bluetooth LE. - _Short Flash Off (950ms on/50ms off)_ — The device is fully - provisioned, but does not yet have full Thread network or service - connectivity. + provisioned, but does not yet have full connectivity for Thread or Wi-Fi + network. -- _Solid On_ — The device is fully provisioned and has full Thread - network and service connectivity. +- _Solid On_ — The device is fully provisioned. **LED 2** simulates the BLE DFU process. The following states are possible: @@ -259,10 +303,9 @@ following states are possible: - _Rapid Even Flashing (30 ms off / 170 ms on)_ — BLE is advertising, DFU process can be started. -**LED 3** can be used to identify the device. The LED starts blinking evenly -(500 ms on/500 ms off) when the Identify command of the Identify cluster is -received. The command's argument can be used to specify the duration of the -effect. +**All LEDs** + +Blink in unison when the factory reset procedure is initiated. ### Buttons @@ -271,36 +314,58 @@ platform image. **Button 1** can be used for the following purposes: -- _Pressed for 6 s_ — Initiates the factory reset of the device. - Releasing the button within the 3-second window cancels the factory reset - procedure. **LEDs 1-4** blink in unison when the factory reset procedure is - initiated. - - _Pressed for less than 3 s_ — Initiates the OTA software update process. This feature is disabled by default, but can be enabled by following the [Building with Device Firmware Upgrade support](#building-with-device-firmware-upgrade-support) - instruction. + instructions. + +- _Pressed for more than 3 s_ — initiates the factory reset of the + device. Releasing the button within the 3-second window cancels the factory + reset procedure. **Button 2** can be used for the following purposes: -- _Pressed once_ — Changes the light state to the opposite one on a - bound lighting bulb device ([lighting-app](../../lighting-app/nrfconnect/) - example). +- On nRF52840 DK, nRF5340 DK and nRF21540 DK: + + - If pressed for less than 0.5 seconds, it changes the light state to the + opposite one on the bound lighting device + ([lighting-app](../../lighting-app/nrfconnect/)) + + - If pressed for more than 0.5 seconds, it changes the brightness of the + light on the bound lighting bulb device + ([lighting-app](../../lighting-app/nrfconnect/)). The brightness is + changing from 0% to 100% with 1% increments every 300 milliseconds as + long as **Button 2** is pressed. + +- On nRF7002 DK: + + - If the device is not commissioned to a Matter network, it starts the NFC + tag emulation, enables Bluetooth LE advertising for the predefined + period of time (15 minutes by default), and makes the device + discoverable over Bluetooth LE. This button is used during the + commissioning procedure. + + - If the device is commissioned to a Matter network, it controls the light + on the bound lighting device. Depending on how long you press the + button: + + - If pressed for less than 0.5 seconds, it changes the light state to the opposite one on the bound lighting device ([lighting-app](../../lighting-app/nrfconnect/)). + + - If pressed for more than 0.5 seconds, it changes the brightness of the light on the bound lighting bulb device ([lighting-app](../../lighting-app/nrfconnect/)). The brightness is changing from 0% to 100% with 1% increments every 300 milliseconds as long as **Button 2** is pressed. + +**Button 4** -- _Pressed for more than 2 s_ — Changes the brightness of the light on a - bound lighting bulb device ([lighting-app](../../lighting-app/nrfconnect/) - example) (dimmer functionality). The brightness is changing from 0% to 100% - with 1% increments every 300 milliseconds as long as **Button 2** is - pressed. +- On nRF52840 DK, nRF5340 DK and nRF21540 DK: -**Button 3** can be used for the following purposes: + Starts the NFC tag emulation, enables Bluetooth LE advertising for the + predefined period of time (15 minutes by default), and makes the device + discoverable over Bluetooth LE. This button is used during the commissioning + procedure. -- _Pressed once_ — Changes the value of the attribute `CurrentPosition` - and (if subscribed) sends the event notifications to the controller. +- On nRF7002 DK: -**Button 4** can be used to start the NFC tag emulation and enable Bluetooth LE -advertising for the predefined period of time (15 minutes by default). + Not available. **SEGGER J-Link USB port** can be used to get logs from the device or communicate with it using the diff --git a/examples/light-switch-app/nrfconnect/boards/nrf7002dk_nrf5340_cpuapp.overlay b/examples/light-switch-app/nrfconnect/boards/nrf7002dk_nrf5340_cpuapp.overlay new file mode 100644 index 00000000000000..90f303f82cc4e0 --- /dev/null +++ b/examples/light-switch-app/nrfconnect/boards/nrf7002dk_nrf5340_cpuapp.overlay @@ -0,0 +1,22 @@ +/* + * 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. + */ + + +/ { + chosen { + nordic,pm-ext-flash = &mx25r64; + }; +}; diff --git a/examples/light-switch-app/nrfconnect/child_image/hci_rpmsg/prj.conf b/examples/light-switch-app/nrfconnect/child_image/hci_rpmsg/prj.conf new file mode 100644 index 00000000000000..1622ffd00dbb91 --- /dev/null +++ b/examples/light-switch-app/nrfconnect/child_image/hci_rpmsg/prj.conf @@ -0,0 +1,25 @@ +# +# 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. +# + +# This target uses Kconfig.hci_rpmsg.defaults to set options common for all +# samples using hci_rpmsg. This file should contain only options specific for this sample +# hci_rpmsg configuration or overrides of default values. + +# Disable not used modules that cannot be set in Kconfig.hci_rpmsg.defaults due to overriding +# in board files. + +CONFIG_SERIAL=n +CONFIG_UART_CONSOLE=n diff --git a/examples/light-switch-app/nrfconnect/child_image/hci_rpmsg/prj_no_dfu.conf b/examples/light-switch-app/nrfconnect/child_image/hci_rpmsg/prj_no_dfu.conf new file mode 100644 index 00000000000000..1622ffd00dbb91 --- /dev/null +++ b/examples/light-switch-app/nrfconnect/child_image/hci_rpmsg/prj_no_dfu.conf @@ -0,0 +1,25 @@ +# +# 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. +# + +# This target uses Kconfig.hci_rpmsg.defaults to set options common for all +# samples using hci_rpmsg. This file should contain only options specific for this sample +# hci_rpmsg configuration or overrides of default values. + +# Disable not used modules that cannot be set in Kconfig.hci_rpmsg.defaults due to overriding +# in board files. + +CONFIG_SERIAL=n +CONFIG_UART_CONSOLE=n diff --git a/examples/light-switch-app/nrfconnect/child_image/hci_rpmsg/prj_release.conf b/examples/light-switch-app/nrfconnect/child_image/hci_rpmsg/prj_release.conf new file mode 100644 index 00000000000000..1622ffd00dbb91 --- /dev/null +++ b/examples/light-switch-app/nrfconnect/child_image/hci_rpmsg/prj_release.conf @@ -0,0 +1,25 @@ +# +# 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. +# + +# This target uses Kconfig.hci_rpmsg.defaults to set options common for all +# samples using hci_rpmsg. This file should contain only options specific for this sample +# hci_rpmsg configuration or overrides of default values. + +# Disable not used modules that cannot be set in Kconfig.hci_rpmsg.defaults due to overriding +# in board files. + +CONFIG_SERIAL=n +CONFIG_UART_CONSOLE=n diff --git a/examples/light-switch-app/nrfconnect/child_image/mcuboot/prj.conf b/examples/light-switch-app/nrfconnect/child_image/mcuboot/prj.conf index 287c7829c6a5cf..0dbf7106e88e22 100644 --- a/examples/light-switch-app/nrfconnect/child_image/mcuboot/prj.conf +++ b/examples/light-switch-app/nrfconnect/child_image/mcuboot/prj.conf @@ -23,7 +23,6 @@ CONFIG_MBEDTLS_CFG_FILE="mcuboot-mbedtls-cfg.h" # Bootloader size optimization # Disable not used modules that cannot be set in Kconfig.mcuboot.defaults due to overriding # in board files. -CONFIG_GPIO=n CONFIG_CONSOLE=n CONFIG_SERIAL=n CONFIG_UART_CONSOLE=n diff --git a/examples/light-switch-app/nrfconnect/child_image/mcuboot/prj_release.conf b/examples/light-switch-app/nrfconnect/child_image/mcuboot/prj_release.conf index 287c7829c6a5cf..0dbf7106e88e22 100644 --- a/examples/light-switch-app/nrfconnect/child_image/mcuboot/prj_release.conf +++ b/examples/light-switch-app/nrfconnect/child_image/mcuboot/prj_release.conf @@ -23,7 +23,6 @@ CONFIG_MBEDTLS_CFG_FILE="mcuboot-mbedtls-cfg.h" # Bootloader size optimization # Disable not used modules that cannot be set in Kconfig.mcuboot.defaults due to overriding # in board files. -CONFIG_GPIO=n CONFIG_CONSOLE=n CONFIG_SERIAL=n CONFIG_UART_CONSOLE=n diff --git a/examples/light-switch-app/nrfconnect/configuration/nrf7002dk_nrf5340_cpuapp/pm_static_dfu.yml b/examples/light-switch-app/nrfconnect/configuration/nrf7002dk_nrf5340_cpuapp/pm_static_dfu.yml new file mode 100644 index 00000000000000..3c56dc0ddb1968 --- /dev/null +++ b/examples/light-switch-app/nrfconnect/configuration/nrf7002dk_nrf5340_cpuapp/pm_static_dfu.yml @@ -0,0 +1,56 @@ +mcuboot: + address: 0x0 + size: 0xC000 + region: flash_primary +mcuboot_pad: + address: 0xC000 + size: 0x200 +app: + address: 0xC200 + size: 0xeee00 +mcuboot_primary: + orig_span: &id001 + - mcuboot_pad + - app + span: *id001 + address: 0xC000 + size: 0xef000 + region: flash_primary +mcuboot_primary_app: + orig_span: &id002 + - app + span: *id002 + address: 0xC200 + size: 0xeee00 +factory_data: + address: 0xfb000 + size: 0x1000 + region: flash_primary +settings_storage: + address: 0xfc000 + size: 0x4000 + region: flash_primary +mcuboot_primary_1: + address: 0x0 + size: 0x40000 + device: flash_ctrl + region: ram_flash +mcuboot_secondary: + address: 0x0 + size: 0xef000 + device: MX25R64 + region: external_flash +mcuboot_secondary_1: + address: 0xef000 + size: 0x40000 + device: MX25R64 + region: external_flash +external_flash: + address: 0x12f000 + size: 0x6D1000 + device: MX25R64 + region: external_flash +pcd_sram: + address: 0x20000000 + size: 0x2000 + region: sram_primary diff --git a/examples/light-switch-app/nrfconnect/main/AppTask.cpp b/examples/light-switch-app/nrfconnect/main/AppTask.cpp index 4eee5a57bfef8d..3ba17c73a5c147 100644 --- a/examples/light-switch-app/nrfconnect/main/AppTask.cpp +++ b/examples/light-switch-app/nrfconnect/main/AppTask.cpp @@ -18,7 +18,8 @@ #include "AppTask.h" #include "AppConfig.h" -#include "LEDWidget.h" +#include "BoardUtil.h" +#include "LEDUtil.h" #include "LightSwitch.h" #include @@ -34,6 +35,11 @@ #include #include +#ifdef CONFIG_CHIP_WIFI +#include +#include +#endif + #ifdef CONFIG_CHIP_OTA_REQUESTOR #include "OTAUtil.h" #endif @@ -42,23 +48,23 @@ #include #include +LOG_MODULE_DECLARE(app, CONFIG_CHIP_APP_LOG_LEVEL); + using namespace ::chip; using namespace ::chip::app; using namespace ::chip::Credentials; using namespace ::chip::DeviceLayer; -LOG_MODULE_DECLARE(app, CONFIG_MATTER_LOG_LEVEL); namespace { constexpr EndpointId kLightDimmerSwitchEndpointId = 1; constexpr EndpointId kLightGenericSwitchEndpointId = 2; constexpr EndpointId kLightEndpointId = 1; -constexpr uint32_t kFactoryResetTriggerTimeout = 3000; -constexpr uint32_t kFactoryResetCancelWindow = 3000; -constexpr uint32_t kDimmerTriggeredTimeout = 500; -constexpr uint32_t kDimmerInterval = 300; -constexpr uint32_t kIdentifyBlinkRateMs = 500; -constexpr size_t kAppEventQueueSize = 10; +constexpr uint32_t kFactoryResetTriggerTimeout = 3000; +constexpr uint32_t kFactoryResetCancelWindowTimeout = 3000; +constexpr uint32_t kDimmerTriggeredTimeout = 500; +constexpr uint32_t kDimmerInterval = 300; +constexpr size_t kAppEventQueueSize = 10; K_MSGQ_DEFINE(sAppEventQueue, sizeof(AppEvent), kAppEventQueueSize, alignof(AppEvent)); @@ -71,28 +77,43 @@ uint8_t sTestEventTriggerEnableKey[TestEventTriggerDelegate::kEnableKeyLength] = 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff }; LEDWidget sStatusLED; -LEDWidget sBleLED; LEDWidget sIdentifyLED; -LEDWidget sUnusedLED; - -bool sIsThreadProvisioned = false; -bool sIsThreadEnabled = false; -bool sIsThreadBLEAdvertising = false; -#ifdef CONFIG_MCUMGR_SMP_BT -bool sIsSMPAdvertising = false; +#if NUMBER_OF_LEDS == 4 +FactoryResetLEDsWrapper<2> sFactoryResetLEDs{ { FACTORY_RESET_SIGNAL_LED, FACTORY_RESET_SIGNAL_LED1 } }; #endif -bool sHaveBLEConnections = false; -bool sWasDimmerTriggered = false; + +bool sIsNetworkProvisioned = false; +bool sIsNetworkEnabled = false; +bool sHaveBLEConnections = false; +bool sWasDimmerTriggered = false; k_timer sFunctionTimer; k_timer sDimmerPressKeyTimer; k_timer sDimmerTimer; chip::DeviceLayer::DeviceInfoProviderImpl gExampleDeviceInfoProvider; - -} /* namespace */ - -AppTask AppTask::sAppTask; +} // namespace + +namespace LedConsts { +constexpr uint32_t kBlinkRate_ms{ 500 }; +constexpr uint32_t kIdentifyBlinkRate_ms{ 500 }; + +namespace StatusLed { +namespace Unprovisioned { +constexpr uint32_t kOn_ms{ 100 }; +constexpr uint32_t kOff_ms{ kOn_ms }; +} // namespace Unprovisioned +namespace Provisioned { +constexpr uint32_t kOn_ms{ 50 }; +constexpr uint32_t kOff_ms{ 950 }; +} // namespace Provisioned + +} // namespace StatusLed +} // namespace LedConsts + +#ifdef CONFIG_CHIP_WIFI +app::Clusters::NetworkCommissioning::Instance sWiFiCommissioningInstance(0, &(NetworkCommissioning::NrfWiFiDriver::Instance())); +#endif CHIP_ERROR AppTask::Init() { @@ -131,7 +152,9 @@ CHIP_ERROR AppTask::Init() LOG_ERR("ConnectivityMgr().SetThreadDeviceType() failed: %s", ErrorStr(err)); return err; } -#elif !defined(CONFIG_WIFI_NRF700X) +#elif defined(CONFIG_CHIP_WIFI) + sWiFiCommissioningInstance.Init(); +#else return CHIP_ERROR_INTERNAL; #endif @@ -140,14 +163,12 @@ CHIP_ERROR AppTask::Init() // Initialize UI components LEDWidget::InitGpio(); LEDWidget::SetStateUpdateCallback(LEDStateUpdateHandler); + sStatusLED.Init(SYSTEM_STATE_LED); - sBleLED.Init(DFU_BLE_LED); sIdentifyLED.Init(IDENTIFY_LED); - sUnusedLED.Init(DK_LED4); UpdateStatusLED(); int ret = dk_buttons_init(ButtonEventHandler); - if (ret) { LOG_ERR("dk_buttons_init() failed"); @@ -155,9 +176,9 @@ CHIP_ERROR AppTask::Init() } // Initialize Timers - k_timer_init(&sFunctionTimer, AppTask::TimerEventHandler, nullptr); - k_timer_init(&sDimmerPressKeyTimer, AppTask::TimerEventHandler, nullptr); - k_timer_init(&sDimmerTimer, AppTask::TimerEventHandler, nullptr); + k_timer_init(&sFunctionTimer, AppTask::FunctionTimerTimeoutCallback, nullptr); + k_timer_init(&sDimmerPressKeyTimer, AppTask::FunctionTimerTimeoutCallback, nullptr); + k_timer_init(&sDimmerTimer, AppTask::FunctionTimerTimeoutCallback, nullptr); k_timer_user_data_set(&sDimmerTimer, this); k_timer_user_data_set(&sDimmerPressKeyTimer, this); k_timer_user_data_set(&sFunctionTimer, this); @@ -222,30 +243,35 @@ CHIP_ERROR AppTask::StartApp() while (true) { k_msgq_get(&sAppEventQueue, &event, K_FOREVER); - DispatchEvent(&event); + DispatchEvent(event); } return CHIP_NO_ERROR; } -void AppTask::ButtonPushHandler(AppEvent * aEvent) +void AppTask::ButtonPushHandler(const AppEvent & event) { - if (aEvent->Type == AppEvent::kEventType_Button) + if (event.Type == AppEventType::Button) { - switch (aEvent->ButtonEvent.PinNo) + switch (event.ButtonEvent.PinNo) { case FUNCTION_BUTTON: - sAppTask.StartTimer(Timer::Function, kFactoryResetTriggerTimeout); - sAppTask.mFunction = TimerFunction::SoftwareUpdate; + Instance().StartTimer(Timer::Function, kFactoryResetTriggerTimeout); + Instance().mFunction = FunctionEvent::SoftwareUpdate; break; - case DIMMER_SWITCH_BUTTON: + case +#if NUMBER_OF_BUTTONS == 2 + BLE_ADVERTISEMENT_START_AND_SWITCH_BUTTON: + if (!ConnectivityMgr().IsBLEAdvertisingEnabled() && Server::GetInstance().GetFabricTable().FabricCount() == 0) + { + break; + } +#else + SWITCH_BUTTON: +#endif LOG_INF("Button has been pressed, keep in this state for at least 500 ms to change light sensitivity of binded " "lighting devices."); - sAppTask.StartTimer(Timer::DimmerTrigger, kDimmerTriggeredTimeout); - break; - case GENERIC_SWITCH_BUTTON: - LOG_INF("GenericSwitch: InitialPress"); - LightSwitch::GetInstance().GenericSwitchInitialPress(); + Instance().StartTimer(Timer::DimmerTrigger, kDimmerTriggeredTimeout); break; default: break; @@ -253,94 +279,105 @@ void AppTask::ButtonPushHandler(AppEvent * aEvent) } } -void AppTask::ButtonReleaseHandler(AppEvent * aEvent) +void AppTask::ButtonReleaseHandler(const AppEvent & event) { - - if (aEvent->Type == AppEvent::kEventType_Button) + if (event.Type == AppEventType::Button) { - switch (aEvent->ButtonEvent.PinNo) + switch (event.ButtonEvent.PinNo) { case FUNCTION_BUTTON: - if (sAppTask.mFunction == TimerFunction::SoftwareUpdate) + if (Instance().mFunction == FunctionEvent::SoftwareUpdate) { - sAppTask.CancelTimer(Timer::Function); - sAppTask.mFunction = TimerFunction::NoneSelected; + Instance().CancelTimer(Timer::Function); + Instance().mFunction = FunctionEvent::NoneSelected; #ifdef CONFIG_MCUMGR_SMP_BT GetDFUOverSMP().StartServer(); - sIsSMPAdvertising = true; UpdateStatusLED(); #else LOG_INF("Software update is disabled"); #endif } - else if (sAppTask.mFunction == TimerFunction::FactoryReset) + else if (Instance().mFunction == FunctionEvent::FactoryReset) { UpdateStatusLED(); - sAppTask.CancelTimer(Timer::Function); - sAppTask.mFunction = TimerFunction::NoneSelected; + Instance().CancelTimer(Timer::Function); + Instance().mFunction = FunctionEvent::NoneSelected; LOG_INF("Factory Reset has been canceled"); } break; - case DIMMER_SWITCH_BUTTON: +#if NUMBER_OF_BUTTONS == 4 + case SWITCH_BUTTON: +#else + case BLE_ADVERTISEMENT_START_AND_SWITCH_BUTTON: + if (!ConnectivityMgr().IsBLEAdvertisingEnabled() && Server::GetInstance().GetFabricTable().FabricCount() == 0) + { + AppEvent buttonEvent; + buttonEvent.Type = AppEventType::Button; + buttonEvent.ButtonEvent.PinNo = BLE_ADVERTISEMENT_START_AND_SWITCH_BUTTON; + buttonEvent.ButtonEvent.Action = static_cast(AppEventType::ButtonPushed); + buttonEvent.Handler = StartBLEAdvertisementHandler; + PostEvent(buttonEvent); + break; + } +#endif if (!sWasDimmerTriggered) { LightSwitch::GetInstance().InitiateActionSwitch(LightSwitch::Action::Toggle); } - sAppTask.CancelTimer(Timer::Dimmer); - sAppTask.CancelTimer(Timer::DimmerTrigger); + Instance().CancelTimer(Timer::Dimmer); + Instance().CancelTimer(Timer::DimmerTrigger); sWasDimmerTriggered = false; break; - case GENERIC_SWITCH_BUTTON: - LOG_INF("GenericSwitch: ShortRelease"); - LightSwitch::GetInstance().GenericSwitchReleasePress(); - break; default: break; } } } -void AppTask::TimerEventHandler(AppEvent * aEvent) +void AppTask::TimerEventHandler(const AppEvent & event) { - if (aEvent->Type == AppEvent::kEventType_Timer) + if (event.Type == AppEventType::Timer) { - switch ((Timer) aEvent->TimerEvent.TimerType) + switch (static_cast(event.TimerEvent.TimerType)) { case Timer::Function: - if (sAppTask.mFunction == TimerFunction::SoftwareUpdate) + if (Instance().mFunction == FunctionEvent::SoftwareUpdate) { - LOG_INF("Factory Reset has been triggered. Release button within %u ms to cancel.", kFactoryResetCancelWindow); - sAppTask.StartTimer(Timer::Function, kFactoryResetCancelWindow); - sAppTask.mFunction = TimerFunction::FactoryReset; + LOG_INF("Factory Reset has been triggered. Release button within %u ms to cancel.", + kFactoryResetCancelWindowTimeout); + Instance().StartTimer(Timer::Function, kFactoryResetCancelWindowTimeout); + Instance().mFunction = FunctionEvent::FactoryReset; #ifdef CONFIG_STATE_LEDS // reset all LEDs to synchronize factory reset blinking sStatusLED.Set(false); sIdentifyLED.Set(false); - sBleLED.Set(false); - sUnusedLED.Set(false); +#if NUMBER_OF_LEDS == 4 + sFactoryResetLEDs.Set(false); +#endif - sStatusLED.Blink(500); - sIdentifyLED.Blink(500); - sBleLED.Blink(500); - sUnusedLED.Blink(500); + sStatusLED.Blink(LedConsts::kBlinkRate_ms); + sIdentifyLED.Blink(LedConsts::kBlinkRate_ms); +#if NUMBER_OF_LEDS == 4 + sFactoryResetLEDs.Blink(LedConsts::kBlinkRate_ms); +#endif #endif } - else if (sAppTask.mFunction == TimerFunction::FactoryReset) + else if (Instance().mFunction == FunctionEvent::FactoryReset) { - sAppTask.mFunction = TimerFunction::NoneSelected; + Instance().mFunction = FunctionEvent::NoneSelected; LOG_INF("Factory Reset triggered"); - ConfigurationMgr().InitiateFactoryReset(); + chip::Server::GetInstance().ScheduleFactoryReset(); } break; case Timer::DimmerTrigger: LOG_INF("Dimming started..."); sWasDimmerTriggered = true; LightSwitch::GetInstance().InitiateActionSwitch(LightSwitch::Action::On); - sAppTask.StartTimer(Timer::Dimmer, kDimmerInterval); - sAppTask.CancelTimer(Timer::DimmerTrigger); + Instance().StartTimer(Timer::Dimmer, kDimmerInterval); + Instance().CancelTimer(Timer::DimmerTrigger); break; case Timer::Dimmer: LightSwitch::GetInstance().DimmerChangeBrightness(); @@ -354,22 +391,21 @@ void AppTask::TimerEventHandler(AppEvent * aEvent) void AppTask::IdentifyStartHandler(Identify *) { AppEvent event; - event.Type = AppEvent::kEventType_IdentifyStart; - event.Handler = [](AppEvent *) { sIdentifyLED.Blink(kIdentifyBlinkRateMs); }; - sAppTask.PostEvent(&event); + event.Type = AppEventType::IdentifyStart; + event.Handler = [](const AppEvent &) { sIdentifyLED.Blink(LedConsts::kIdentifyBlinkRate_ms); }; + PostEvent(event); } void AppTask::IdentifyStopHandler(Identify *) { AppEvent event; - event.Type = AppEvent::kEventType_IdentifyStop; - event.Handler = [](AppEvent *) { sIdentifyLED.Set(false); }; - sAppTask.PostEvent(&event); + event.Type = AppEventType::IdentifyStop; + event.Handler = [](const AppEvent &) { sIdentifyLED.Set(false); }; + PostEvent(event); } -void AppTask::StartBLEAdvertisingHandler(AppEvent * aEvent) +void AppTask::StartBLEAdvertisementHandler(const AppEvent &) { - /// Don't allow on starting Matter service BLE advertising after Thread provisioning. if (Server::GetInstance().GetFabricTable().FabricCount() != 0) { LOG_INF("Matter service BLE advertising not started - device is already commissioned"); @@ -382,22 +418,20 @@ void AppTask::StartBLEAdvertisingHandler(AppEvent * aEvent) return; } - LOG_INF("Enabling BLE advertising..."); if (Server::GetInstance().GetCommissioningWindowManager().OpenBasicCommissioningWindow() != CHIP_NO_ERROR) { LOG_ERR("OpenBasicCommissioningWindow() failed"); } } -void AppTask::ChipEventHandler(const ChipDeviceEvent * aEvent, intptr_t /* arg */) +void AppTask::ChipEventHandler(const ChipDeviceEvent * event, intptr_t /* arg */) { - switch (aEvent->Type) + switch (event->Type) { case DeviceEventType::kCHIPoBLEAdvertisingChange: - sIsThreadBLEAdvertising = true; UpdateStatusLED(); #ifdef CONFIG_CHIP_NFC_COMMISSIONING - if (aEvent->CHIPoBLEAdvertisingChange.Result == kActivity_Started) + if (event->CHIPoBLEAdvertisingChange.Result == kActivity_Started) { if (NFCMgr().IsTagEmulationStarted()) { @@ -405,10 +439,10 @@ void AppTask::ChipEventHandler(const ChipDeviceEvent * aEvent, intptr_t /* arg * } else { - ShareQRCodeOverNFC(RendezvousInformationFlags(RendezvousInformationFlag::kBLE)); + ShareQRCodeOverNFC(chip::RendezvousInformationFlags(chip::RendezvousInformationFlag::kBLE)); } } - else if (aEvent->CHIPoBLEAdvertisingChange.Result == kActivity_Stopped) + else if (event->CHIPoBLEAdvertisingChange.Result == kActivity_Stopped) { NFCMgr().StopTagEmulation(); } @@ -416,24 +450,29 @@ void AppTask::ChipEventHandler(const ChipDeviceEvent * aEvent, intptr_t /* arg * sHaveBLEConnections = ConnectivityMgr().NumBLEConnections() != 0; UpdateStatusLED(); break; - case DeviceEventType::kThreadStateChange: - sIsThreadProvisioned = ConnectivityMgr().IsThreadProvisioned(); - sIsThreadEnabled = ConnectivityMgr().IsThreadEnabled(); - UpdateStatusLED(); - break; +#if defined(CONFIG_NET_L2_OPENTHREAD) case DeviceEventType::kDnssdPlatformInitialized: #if CONFIG_CHIP_OTA_REQUESTOR InitBasicOTARequestor(); -#endif +#endif // CONFIG_CHIP_OTA_REQUESTOR break; - default: - if ((ConnectivityMgr().NumBLEConnections() == 0) && (!sIsThreadProvisioned || !sIsThreadEnabled)) + case DeviceEventType::kThreadStateChange: + sIsNetworkProvisioned = ConnectivityMgr().IsThreadProvisioned(); + sIsNetworkEnabled = ConnectivityMgr().IsThreadEnabled(); +#elif defined(CONFIG_CHIP_WIFI) + case DeviceEventType::kWiFiConnectivityChange: + sIsNetworkProvisioned = ConnectivityMgr().IsWiFiStationProvisioned(); + sIsNetworkEnabled = ConnectivityMgr().IsWiFiStationEnabled(); +#if CONFIG_CHIP_OTA_REQUESTOR + if (event->WiFiConnectivityChange.Result == kConnectivity_Established) { - LOG_ERR("Commissioning with a Thread network has not been done. An error occurred..."); - sIsThreadBLEAdvertising = false; - sHaveBLEConnections = false; - UpdateStatusLED(); + InitBasicOTARequestor(); } +#endif // CONFIG_CHIP_OTA_REQUESTOR +#endif + UpdateStatusLED(); + break; + default: break; } } @@ -441,127 +480,106 @@ void AppTask::ChipEventHandler(const ChipDeviceEvent * aEvent, intptr_t /* arg * void AppTask::UpdateStatusLED() { #ifdef CONFIG_STATE_LEDS - sUnusedLED.Set(false); +#if NUMBER_OF_LEDS == 4 + sFactoryResetLEDs.Set(false); +#endif - // Status LED indicates: - // - blinking 1 s - advertising, ready to commission - // - blinking 200 ms - commissioning in progress - // - constant lightning means commissioned with Thread network - if (sIsThreadBLEAdvertising && !sHaveBLEConnections) - { - sStatusLED.Blink(50, 950); - } - else if (sIsThreadProvisioned && sIsThreadEnabled) + // Update the status LED. + // + // If IPv6 network and service provisioned, keep the LED on constantly. + // + // If the system has BLE connection(s) up till the stage above, THEN blink the LED at an even + // rate of 100ms. + // + // Otherwise, blink the LED for a very short time. + if (sIsNetworkProvisioned && sIsNetworkEnabled) { sStatusLED.Set(true); } else if (sHaveBLEConnections) { - sStatusLED.Blink(30, 170); + sStatusLED.Blink(LedConsts::StatusLed::Unprovisioned::kOn_ms, LedConsts::StatusLed::Unprovisioned::kOff_ms); } else { - sStatusLED.Set(false); + sStatusLED.Blink(LedConsts::StatusLed::Provisioned::kOn_ms, LedConsts::StatusLed::Provisioned::kOff_ms); } - -// Ble LED indicates BLE connectivity: -//- blinking 200 ms means BLE advertising -#ifdef CONFIG_MCUMGR_SMP_BT - if (sIsSMPAdvertising) - { - sBleLED.Blink(30, 170); - } - else - { - sBleLED.Set(false); - } -#else - sBleLED.Set(false); -#endif #endif } -void AppTask::ButtonEventHandler(uint32_t aButtonState, uint32_t aHasChanged) +void AppTask::ButtonEventHandler(uint32_t buttonState, uint32_t hasChanged) { - AppEvent buttonEvent; - buttonEvent.Type = AppEvent::kEventType_Button; + buttonEvent.Type = AppEventType::Button; - if (FUNCTION_BUTTON_MASK & aButtonState & aHasChanged) + if (FUNCTION_BUTTON_MASK & buttonState & hasChanged) { buttonEvent.ButtonEvent.PinNo = FUNCTION_BUTTON; - buttonEvent.ButtonEvent.Action = AppEvent::kButtonPushEvent; + buttonEvent.ButtonEvent.Action = static_cast(AppEventType::ButtonPushed); buttonEvent.Handler = ButtonPushHandler; - sAppTask.PostEvent(&buttonEvent); + PostEvent(buttonEvent); } - else if (FUNCTION_BUTTON_MASK & aHasChanged) + else if (FUNCTION_BUTTON_MASK & hasChanged) { buttonEvent.ButtonEvent.PinNo = FUNCTION_BUTTON; - buttonEvent.ButtonEvent.Action = AppEvent::kButtonReleaseEvent; + buttonEvent.ButtonEvent.Action = static_cast(AppEventType::ButtonReleased); buttonEvent.Handler = ButtonReleaseHandler; - sAppTask.PostEvent(&buttonEvent); + PostEvent(buttonEvent); } - if (DIMMER_SWITCH_BUTTON_MASK & aButtonState & aHasChanged) - { - buttonEvent.ButtonEvent.PinNo = DIMMER_SWITCH_BUTTON; - buttonEvent.ButtonEvent.Action = AppEvent::kButtonPushEvent; - buttonEvent.Handler = ButtonPushHandler; - sAppTask.PostEvent(&buttonEvent); - } - else if (DIMMER_SWITCH_BUTTON_MASK & aHasChanged) - { - buttonEvent.ButtonEvent.PinNo = DIMMER_SWITCH_BUTTON; - buttonEvent.ButtonEvent.Action = AppEvent::kButtonReleaseEvent; - buttonEvent.Handler = ButtonReleaseHandler; - sAppTask.PostEvent(&buttonEvent); - } +#if NUMBER_OF_BUTTONS == 2 + uint32_t buttonMask = BLE_ADVERTISEMENT_START_AND_SWITCH_BUTTON_MASK; + buttonEvent.ButtonEvent.PinNo = BLE_ADVERTISEMENT_START_AND_SWITCH_BUTTON; +#else + uint32_t buttonMask = SWITCH_BUTTON_MASK; + buttonEvent.ButtonEvent.PinNo = SWITCH_BUTTON; +#endif - if (GENERIC_SWITCH_BUTTON_MASK & aButtonState & aHasChanged) + if (buttonMask & buttonState & hasChanged) { - buttonEvent.ButtonEvent.PinNo = GENERIC_SWITCH_BUTTON; - buttonEvent.ButtonEvent.Action = AppEvent::kButtonPushEvent; + buttonEvent.ButtonEvent.Action = static_cast(AppEventType::ButtonPushed); buttonEvent.Handler = ButtonPushHandler; - sAppTask.PostEvent(&buttonEvent); + PostEvent(buttonEvent); } - else if (GENERIC_SWITCH_BUTTON_MASK & aHasChanged) + else if (buttonMask & hasChanged) { - buttonEvent.ButtonEvent.PinNo = GENERIC_SWITCH_BUTTON; - buttonEvent.ButtonEvent.Action = AppEvent::kButtonReleaseEvent; + buttonEvent.ButtonEvent.Action = static_cast(AppEventType::ButtonReleased); buttonEvent.Handler = ButtonReleaseHandler; - sAppTask.PostEvent(&buttonEvent); + PostEvent(buttonEvent); } - if (BLE_ADVERTISEMENT_START_BUTTON_MASK & aHasChanged & aButtonState) +#if NUMBER_OF_BUTTONS == 4 + if (BLE_ADVERTISEMENT_START_BUTTON_MASK & hasChanged & buttonState) { buttonEvent.ButtonEvent.PinNo = BLE_ADVERTISEMENT_START_BUTTON; - buttonEvent.ButtonEvent.Action = AppEvent::kButtonPushEvent; - buttonEvent.Handler = StartBLEAdvertisingHandler; - sAppTask.PostEvent(&buttonEvent); + buttonEvent.ButtonEvent.Action = static_cast(AppEventType::ButtonPushed); + buttonEvent.Handler = StartBLEAdvertisementHandler; + PostEvent(buttonEvent); } +#endif } -void AppTask::StartTimer(Timer aTimer, uint32_t aTimeoutMs) +void AppTask::StartTimer(Timer timer, uint32_t timeoutMs) { - switch (aTimer) + switch (timer) { case Timer::Function: - k_timer_start(&sFunctionTimer, K_MSEC(aTimeoutMs), K_NO_WAIT); + k_timer_start(&sFunctionTimer, K_MSEC(timeoutMs), K_NO_WAIT); break; case Timer::DimmerTrigger: - k_timer_start(&sDimmerPressKeyTimer, K_MSEC(aTimeoutMs), K_NO_WAIT); + k_timer_start(&sDimmerPressKeyTimer, K_MSEC(timeoutMs), K_NO_WAIT); break; case Timer::Dimmer: - k_timer_start(&sDimmerTimer, K_MSEC(aTimeoutMs), K_MSEC(aTimeoutMs)); + k_timer_start(&sDimmerTimer, K_MSEC(timeoutMs), K_MSEC(timeoutMs)); break; default: break; } } -void AppTask::CancelTimer(Timer aTimer) +void AppTask::CancelTimer(Timer timer) { - switch (aTimer) + switch (timer) { case Timer::Function: k_timer_stop(&sFunctionTimer); @@ -577,49 +595,54 @@ void AppTask::CancelTimer(Timer aTimer) } } -void AppTask::UpdateLedStateEventHandler(AppEvent * aEvent) +void AppTask::UpdateLedStateEventHandler(const AppEvent & event) { - if (aEvent->Type == AppEvent::kEventType_UpdateLedState) + if (event.Type == AppEventType::UpdateLedState) { - aEvent->UpdateLedStateEvent.LedWidget->UpdateState(); + event.UpdateLedStateEvent.LedWidget->UpdateState(); } } void AppTask::LEDStateUpdateHandler(LEDWidget & aLedWidget) { AppEvent event; - event.Type = AppEvent::kEventType_UpdateLedState; + event.Type = AppEventType::UpdateLedState; event.Handler = UpdateLedStateEventHandler; event.UpdateLedStateEvent.LedWidget = &aLedWidget; - sAppTask.PostEvent(&event); + PostEvent(event); } -void AppTask::TimerEventHandler(k_timer * aTimer) +void AppTask::FunctionTimerTimeoutCallback(k_timer * timer) { + if (!timer) + { + return; + } + AppEvent event; - if (aTimer == &sFunctionTimer) + if (timer == &sFunctionTimer) { - event.Type = AppEvent::kEventType_Timer; + event.Type = AppEventType::Timer; event.TimerEvent.TimerType = (uint8_t) Timer::Function; - event.TimerEvent.Context = k_timer_user_data_get(aTimer); + event.TimerEvent.Context = k_timer_user_data_get(timer); event.Handler = TimerEventHandler; - sAppTask.PostEvent(&event); + PostEvent(event); } - if (aTimer == &sDimmerPressKeyTimer) + if (timer == &sDimmerPressKeyTimer) { - event.Type = AppEvent::kEventType_Timer; + event.Type = AppEventType::Timer; event.TimerEvent.TimerType = (uint8_t) Timer::DimmerTrigger; - event.TimerEvent.Context = k_timer_user_data_get(aTimer); + event.TimerEvent.Context = k_timer_user_data_get(timer); event.Handler = TimerEventHandler; - sAppTask.PostEvent(&event); + PostEvent(event); } - if (aTimer == &sDimmerTimer) + if (timer == &sDimmerTimer) { - event.Type = AppEvent::kEventType_Timer; + event.Type = AppEventType::Timer; event.TimerEvent.TimerType = (uint8_t) Timer::Dimmer; - event.TimerEvent.Context = k_timer_user_data_get(aTimer); + event.TimerEvent.Context = k_timer_user_data_get(timer); event.Handler = TimerEventHandler; - sAppTask.PostEvent(&event); + PostEvent(event); } } @@ -627,25 +650,25 @@ void AppTask::TimerEventHandler(k_timer * aTimer) void AppTask::RequestSMPAdvertisingStart(void) { AppEvent event; - event.Type = AppEvent::kEventType_StartSMPAdvertising; - event.Handler = [](AppEvent *) { GetDFUOverSMP().StartBLEAdvertising(); }; - sAppTask.PostEvent(&event); + event.Type = AppEventType::StartSMPAdvertising; + event.Handler = [](const AppEvent &) { GetDFUOverSMP().StartBLEAdvertising(); }; + PostEvent(event); } #endif -void AppTask::PostEvent(AppEvent * aEvent) +void AppTask::PostEvent(const AppEvent & event) { - if (k_msgq_put(&sAppEventQueue, aEvent, K_NO_WAIT) != 0) + if (k_msgq_put(&sAppEventQueue, &event, K_NO_WAIT) != 0) { LOG_INF("Failed to post event to app task event queue"); } } -void AppTask::DispatchEvent(AppEvent * aEvent) +void AppTask::DispatchEvent(const AppEvent & event) { - if (aEvent->Handler) + if (event.Handler) { - aEvent->Handler(aEvent); + event.Handler(event); } else { diff --git a/examples/light-switch-app/nrfconnect/main/BindingHandler.cpp b/examples/light-switch-app/nrfconnect/main/BindingHandler.cpp index fd435cd0014243..3dc8efee96963e 100644 --- a/examples/light-switch-app/nrfconnect/main/BindingHandler.cpp +++ b/examples/light-switch-app/nrfconnect/main/BindingHandler.cpp @@ -22,7 +22,7 @@ #endif #include -LOG_MODULE_DECLARE(app, CONFIG_MATTER_LOG_LEVEL); +LOG_MODULE_DECLARE(app, CONFIG_CHIP_APP_LOG_LEVEL); using namespace chip; using namespace chip::app; @@ -209,7 +209,7 @@ void BindingHandler::LightSwitchChangedHandler(const EmberBindingTableEntry & bi LevelControlProcessCommand(data->CommandId, binding, nullptr, context); break; default: - ChipLogError(NotSpecified, "Invalid binding group command data"); + LOG_ERR("Invalid binding group command data"); break; } } @@ -224,7 +224,7 @@ void BindingHandler::LightSwitchChangedHandler(const EmberBindingTableEntry & bi LevelControlProcessCommand(data->CommandId, binding, deviceProxy, context); break; default: - ChipLogError(NotSpecified, "Invalid binding unicast command data"); + LOG_ERR("Invalid binding unicast command data"); break; } } diff --git a/examples/light-switch-app/nrfconnect/main/include/AppConfig.h b/examples/light-switch-app/nrfconnect/main/include/AppConfig.h index 7e71ce1fa1381f..4b94870dc35166 100644 --- a/examples/light-switch-app/nrfconnect/main/include/AppConfig.h +++ b/examples/light-switch-app/nrfconnect/main/include/AppConfig.h @@ -20,15 +20,24 @@ // ---- Lighting Example App Config ---- +#include "BoardUtil.h" + #define FUNCTION_BUTTON DK_BTN1 #define FUNCTION_BUTTON_MASK DK_BTN1_MSK -#define DIMMER_SWITCH_BUTTON DK_BTN2 -#define DIMMER_SWITCH_BUTTON_MASK DK_BTN2_MSK -#define GENERIC_SWITCH_BUTTON DK_BTN3 -#define GENERIC_SWITCH_BUTTON_MASK DK_BTN3_MSK +#if NUMBER_OF_BUTTONS == 2 +#define BLE_ADVERTISEMENT_START_AND_SWITCH_BUTTON DK_BTN2 +#define BLE_ADVERTISEMENT_START_AND_SWITCH_BUTTON_MASK DK_BTN2_MSK +#else +#define SWITCH_BUTTON DK_BTN2 +#define SWITCH_BUTTON_MASK DK_BTN2_MSK #define BLE_ADVERTISEMENT_START_BUTTON DK_BTN4 #define BLE_ADVERTISEMENT_START_BUTTON_MASK DK_BTN4_MSK +#endif #define SYSTEM_STATE_LED DK_LED1 -#define DFU_BLE_LED DK_LED2 -#define IDENTIFY_LED DK_LED3 +#define IDENTIFY_LED DK_LED2 + +#if NUMBER_OF_LEDS == 4 +#define FACTORY_RESET_SIGNAL_LED DK_LED3 +#define FACTORY_RESET_SIGNAL_LED1 DK_LED4 +#endif diff --git a/examples/light-switch-app/nrfconnect/main/include/AppEvent.h b/examples/light-switch-app/nrfconnect/main/include/AppEvent.h index 4a2bd347290703..b3248e3e0c29d6 100644 --- a/examples/light-switch-app/nrfconnect/main/include/AppEvent.h +++ b/examples/light-switch-app/nrfconnect/main/include/AppEvent.h @@ -19,32 +19,32 @@ #pragma once #include -#include "LEDWidget.h" +#include "EventTypes.h" -struct AppEvent; -typedef void (*EventHandler)(AppEvent *); +class LEDWidget; -struct AppEvent +enum class AppEventType : uint8_t { + None = 0, + Button, + ButtonPushed, + ButtonReleased, + Timer, + UpdateLedState, + IdentifyStart, + IdentifyStop, + StartSMPAdvertising +}; - constexpr static uint8_t kButtonPushEvent = 1; - constexpr static uint8_t kButtonReleaseEvent = 0; - - enum AppEventTypes : uint8_t - { - kEventType_StartBLEAdvertising, - kEventType_Button, - kEventType_Timer, - kEventType_UpdateLedState, - kEventType_IdentifyStart, - kEventType_IdentifyStop, -#ifdef CONFIG_MCUMGR_SMP_BT - kEventType_StartSMPAdvertising, -#endif - }; - - uint8_t Type; +enum class FunctionEvent : uint8_t +{ + NoneSelected = 0, + SoftwareUpdate = 0, + FactoryReset +}; +struct AppEvent +{ union { struct @@ -63,5 +63,6 @@ struct AppEvent } UpdateLedStateEvent; }; + AppEventType Type{ AppEventType::None }; EventHandler Handler; }; diff --git a/examples/light-switch-app/nrfconnect/main/include/AppTask.h b/examples/light-switch-app/nrfconnect/main/include/AppTask.h index 06f9e1d574cb0a..6d464638f11e7a 100644 --- a/examples/light-switch-app/nrfconnect/main/include/AppTask.h +++ b/examples/light-switch-app/nrfconnect/main/include/AppTask.h @@ -40,9 +40,16 @@ struct Identify; class AppTask { public: + static AppTask & Instance() + { + static AppTask sAppTask; + return sAppTask; + }; + CHIP_ERROR StartApp(); - void PostEvent(AppEvent *); + void UpdateClusterState(); + static void IdentifyStartHandler(Identify *); static void IdentifyStopHandler(Identify *); @@ -53,52 +60,38 @@ class AppTask DimmerTrigger, Dimmer }; - enum class TimerFunction : uint8_t - { - NoneSelected = 0, - SoftwareUpdate, - FactoryReset, - }; - TimerFunction mFunction = TimerFunction::NoneSelected; - enum class Button : uint8_t { Function, Dimmer, }; - friend AppTask & GetAppTask(); - static AppTask sAppTask; - CHIP_ERROR Init(); - void DispatchEvent(AppEvent *); - - static void ButtonPushHandler(AppEvent *); - static void ButtonReleaseHandler(AppEvent *); - static void TimerEventHandler(AppEvent *); - static void StartBLEAdvertisingHandler(AppEvent *); - static void UpdateLedStateEventHandler(AppEvent *); - - static void ChipEventHandler(const chip::DeviceLayer::ChipDeviceEvent *, intptr_t); + static void PostEvent(const AppEvent & event); + static void DispatchEvent(const AppEvent & event); + static void ButtonPushHandler(const AppEvent & event); + static void ButtonReleaseHandler(const AppEvent & event); + static void TimerEventHandler(const AppEvent & event); + static void StartBLEAdvertisementHandler(const AppEvent & event); + static void UpdateLedStateEventHandler(const AppEvent & event); + + static void ChipEventHandler(const chip::DeviceLayer::ChipDeviceEvent * event, intptr_t arg); + static void ButtonEventHandler(uint32_t buttonState, uint32_t hasChanged); + static void LEDStateUpdateHandler(LEDWidget & ledWidget); + static void FunctionTimerTimeoutCallback(k_timer * timer); static void UpdateStatusLED(); - static void ButtonEventHandler(uint32_t, uint32_t); - static void LEDStateUpdateHandler(LEDWidget &); static void StartTimer(Timer, uint32_t); static void CancelTimer(Timer); - static void TimerEventHandler(k_timer *); #ifdef CONFIG_MCUMGR_SMP_BT static void RequestSMPAdvertisingStart(void); #endif + FunctionEvent mFunction = FunctionEvent::NoneSelected; + #if CONFIG_CHIP_FACTORY_DATA chip::DeviceLayer::FactoryDataProvider mFactoryDataProvider; #endif }; - -inline AppTask & GetAppTask() -{ - return AppTask::sAppTask; -} diff --git a/examples/light-switch-app/nrfconnect/main/include/CHIPProjectConfig.h b/examples/light-switch-app/nrfconnect/main/include/CHIPProjectConfig.h index ffd8d9b208940c..f152bdb75be4d2 100644 --- a/examples/light-switch-app/nrfconnect/main/include/CHIPProjectConfig.h +++ b/examples/light-switch-app/nrfconnect/main/include/CHIPProjectConfig.h @@ -27,6 +27,7 @@ #pragma once +#define CHIP_CONFIG_CONTROLLER_MAX_ACTIVE_DEVICES 2 /* Use a default pairing code if one hasn't been provisioned in flash. */ #define CHIP_DEVICE_CONFIG_USE_TEST_SETUP_PIN_CODE 20202021 #define CHIP_DEVICE_CONFIG_USE_TEST_SETUP_DISCRIMINATOR 0xF00 diff --git a/examples/light-switch-app/nrfconnect/main/main.cpp b/examples/light-switch-app/nrfconnect/main/main.cpp index e463898978fb91..4e114745604035 100644 --- a/examples/light-switch-app/nrfconnect/main/main.cpp +++ b/examples/light-switch-app/nrfconnect/main/main.cpp @@ -20,13 +20,13 @@ #include -LOG_MODULE_REGISTER(app, CONFIG_MATTER_LOG_LEVEL); +LOG_MODULE_REGISTER(app, CONFIG_CHIP_APP_LOG_LEVEL); using namespace ::chip; int main() { - CHIP_ERROR err = GetAppTask().StartApp(); + CHIP_ERROR err = AppTask::Instance().StartApp(); LOG_ERR("Exited with code %" CHIP_ERROR_FORMAT, err.Format()); return err == CHIP_NO_ERROR ? EXIT_SUCCESS : EXIT_FAILURE; diff --git a/examples/light-switch-app/nrfconnect/prj.conf b/examples/light-switch-app/nrfconnect/prj.conf index 45e06e243f28bf..19f3cb266a6abc 100644 --- a/examples/light-switch-app/nrfconnect/prj.conf +++ b/examples/light-switch-app/nrfconnect/prj.conf @@ -14,31 +14,27 @@ # limitations under the License. # -CONFIG_CHIP=y -CONFIG_STD_CPP14=y - # This sample uses Kconfig.defaults to set options common for all # samples. This file should contain only options specific for this sample # or overrides of default values. +# Enable CHIP +CONFIG_CHIP=y +CONFIG_CHIP_PROJECT_CONFIG="main/include/CHIPProjectConfig.h" +# 32772 == 0x8004 (example light-switch-app) +CONFIG_CHIP_DEVICE_PRODUCT_ID=32772 +CONFIG_STD_CPP14=y + # Add support for LEDs and buttons on Nordic development kits CONFIG_DK_LIBRARY=y -# OpenThread configs -CONFIG_OPENTHREAD_NORDIC_LIBRARY_MTD=y -CONFIG_OPENTHREAD_MTD=y -CONFIG_OPENTHREAD_FTD=n -CONFIG_CHIP_ENABLE_SLEEPY_END_DEVICE_SUPPORT=y +# General networking settings +CONFIG_NET_IF_MCAST_IPV6_ADDR_COUNT=14 -# Bluetooth overrides +# Bluetooth Low Energy configuration CONFIG_BT_DEVICE_NAME="MatterSwitch" -# Additional configs for debbugging experience. +# Other settings CONFIG_THREAD_NAME=y CONFIG_MPU_STACK_GUARD=y CONFIG_RESET_ON_FATAL_ERROR=n - -# CHIP configuration -CONFIG_CHIP_PROJECT_CONFIG="main/include/CHIPProjectConfig.h" -# 32772 == 0x8004 (example light-switch-app) -CONFIG_CHIP_DEVICE_PRODUCT_ID=32772 diff --git a/examples/light-switch-app/nrfconnect/prj_no_dfu.conf b/examples/light-switch-app/nrfconnect/prj_no_dfu.conf index fe1305f9c5eb8b..27475ce596444c 100644 --- a/examples/light-switch-app/nrfconnect/prj_no_dfu.conf +++ b/examples/light-switch-app/nrfconnect/prj_no_dfu.conf @@ -14,26 +14,27 @@ # limitations under the License. # -CONFIG_CHIP=y -CONFIG_STD_CPP14=y - # This sample uses Kconfig.defaults to set options common for all # samples. This file should contain only options specific for this sample # or overrides of default values. +# Enable CHIP +CONFIG_CHIP=y +CONFIG_CHIP_PROJECT_CONFIG="main/include/CHIPProjectConfig.h" +# 32772 == 0x8004 (example light-switch-app) +CONFIG_CHIP_DEVICE_PRODUCT_ID=32772 +CONFIG_STD_CPP14=y + # Add support for LEDs and buttons on Nordic development kits CONFIG_DK_LIBRARY=y -# OpenThread configs -CONFIG_OPENTHREAD_NORDIC_LIBRARY_MTD=y -CONFIG_OPENTHREAD_MTD=y -CONFIG_OPENTHREAD_FTD=n -CONFIG_CHIP_ENABLE_SLEEPY_END_DEVICE_SUPPORT=y +# General networking settings +CONFIG_NET_IF_MCAST_IPV6_ADDR_COUNT=14 -# Bluetooth overrides +# Bluetooth Low Energy configuration CONFIG_BT_DEVICE_NAME="MatterSwitch" -# Additional configs for debbugging experience. +# Other settings CONFIG_THREAD_NAME=y CONFIG_MPU_STACK_GUARD=y CONFIG_RESET_ON_FATAL_ERROR=n @@ -41,7 +42,5 @@ CONFIG_RESET_ON_FATAL_ERROR=n # Disable Matter OTA DFU CONFIG_CHIP_OTA_REQUESTOR=n -# CHIP configuration -CONFIG_CHIP_PROJECT_CONFIG="main/include/CHIPProjectConfig.h" -# 32772 == 0x8004 (example light-switch-app) -CONFIG_CHIP_DEVICE_PRODUCT_ID=32772 +# Disable QSPI NOR +CONFIG_CHIP_QSPI_NOR=n diff --git a/examples/light-switch-app/nrfconnect/prj_release.conf b/examples/light-switch-app/nrfconnect/prj_release.conf index b9ff24ac0eceb2..a55449c991b17c 100644 --- a/examples/light-switch-app/nrfconnect/prj_release.conf +++ b/examples/light-switch-app/nrfconnect/prj_release.conf @@ -14,33 +14,29 @@ # limitations under the License. # -CONFIG_CHIP=y -CONFIG_STD_CPP14=y - # This sample uses Kconfig.defaults to set options common for all # samples. This file should contain only options specific for this sample # or overrides of default values. +# Enable CHIP +CONFIG_CHIP=y +CONFIG_CHIP_PROJECT_CONFIG="main/include/CHIPProjectConfig.h" +# 32772 == 0x8004 (example light-switch-app) +CONFIG_CHIP_DEVICE_PRODUCT_ID=32772 +CONFIG_STD_CPP14=y + # Add support for LEDs and buttons on Nordic development kits CONFIG_DK_LIBRARY=y -# OpenThread configs -CONFIG_OPENTHREAD_NORDIC_LIBRARY_MTD=y -CONFIG_OPENTHREAD_MTD=y -CONFIG_OPENTHREAD_FTD=n -CONFIG_CHIP_ENABLE_SLEEPY_END_DEVICE_SUPPORT=y +# General networking settings +CONFIG_NET_IF_MCAST_IPV6_ADDR_COUNT=14 -# Bluetooth overrides +# Bluetooth Low Energy configuration CONFIG_BT_DEVICE_NAME="MatterSwitch" # Enable system reset on fatal error CONFIG_RESET_ON_FATAL_ERROR=y -# CHIP configuration -CONFIG_CHIP_PROJECT_CONFIG="main/include/CHIPProjectConfig.h" -# 32772 == 0x8004 (example light-switch-app) -CONFIG_CHIP_DEVICE_PRODUCT_ID=32772 - # Suspend devices when the CPU goes into sleep CONFIG_PM_DEVICE=y @@ -52,7 +48,8 @@ CONFIG_UART_CONSOLE=n CONFIG_SERIAL=n CONFIG_LOG=n CONFIG_LOG_MODE_MINIMAL=n -CONFIG_ASSERT_NO_FILE_INFO=y CONFIG_ASSERT_VERBOSE=n +CONFIG_ASSERT_NO_FILE_INFO=y CONFIG_PRINTK=n +CONFIG_PRINTK_SYNC=n CONFIG_THREAD_NAME=n diff --git a/examples/lighting-app/nrfconnect/CMakeLists.txt b/examples/lighting-app/nrfconnect/CMakeLists.txt index 02ee050825841e..81e2bd7f42c0cc 100644 --- a/examples/lighting-app/nrfconnect/CMakeLists.txt +++ b/examples/lighting-app/nrfconnect/CMakeLists.txt @@ -25,6 +25,7 @@ include(${CHIP_ROOT}/config/nrfconnect/app/check-nrfconnect-version.cmake) # Set Kconfig root files that will be processed as a first Kconfig for used child images. set(mcuboot_KCONFIG_ROOT ${CHIP_ROOT}/config/nrfconnect/chip-module/Kconfig.mcuboot.root) set(multiprotocol_rpmsg_KCONFIG_ROOT ${CHIP_ROOT}/config/nrfconnect/chip-module/Kconfig.multiprotocol_rpmsg.root) +set(hci_rpmsg_KCONFIG_ROOT ${CHIP_ROOT}/config/nrfconnect/chip-module/Kconfig.hci_rpmsg.root) if(NOT CONF_FILE STREQUAL "prj_no_dfu.conf" AND NOT BOARD STREQUAL "nrf52840dongle_nrf52840") set(PM_STATIC_YML_FILE ${CMAKE_CURRENT_SOURCE_DIR}/configuration/${BOARD}/pm_static_dfu.yml) diff --git a/examples/lighting-app/nrfconnect/Kconfig b/examples/lighting-app/nrfconnect/Kconfig index 94a99f971ac195..6a35f3b1115437 100644 --- a/examples/lighting-app/nrfconnect/Kconfig +++ b/examples/lighting-app/nrfconnect/Kconfig @@ -15,6 +15,20 @@ # mainmenu "Matter nRF Connect Lighting Example Application" +# Sample configuration used for Thread networking +if NET_L2_OPENTHREAD + +choice OPENTHREAD_NORDIC_LIBRARY_CONFIGURATION + default OPENTHREAD_NORDIC_LIBRARY_FTD +endchoice + +choice OPENTHREAD_DEVICE_TYPE + default OPENTHREAD_FTD +endchoice + +endif # NET_L2_OPENTHREAD + + rsource "../../../config/nrfconnect/chip-module/Kconfig.features" rsource "../../../config/nrfconnect/chip-module/Kconfig.defaults" source "Kconfig.zephyr" diff --git a/examples/lighting-app/nrfconnect/README.md b/examples/lighting-app/nrfconnect/README.md index 0c25aba55ff94b..29f15ea497d37f 100644 --- a/examples/lighting-app/nrfconnect/README.md +++ b/examples/lighting-app/nrfconnect/README.md @@ -10,12 +10,15 @@ a reference for creating your own application. The example is based on [Matter](https://github.com/project-chip/connectedhomeip) and Nordic -Semiconductor's nRF Connect SDK, and supports remote access and control of a -lighting over a low-power, 802.15.4 Thread network. +Semiconductor's nRF Connect SDK, and was created to facilitate testing and +certification of a Matter device communicating over a low-power, 802.15.4 Thread +network, or Wi-Fi network. The example behaves as a Matter accessory, that is a device that can be paired -into an existing Matter network and can be controlled by this network. The -device works as a Thread Router. +into an existing Matter network and can be controlled by this network. In the +case of Thread, this device works as a Thread Sleepy End Device. Support for +both Thread and Wi-Fi is mutually exclusive and depends on the hardware +platform, so only one protocol can be supported for a specific light device.
@@ -25,6 +28,7 @@ device works as a Thread Router. - [Device Firmware Upgrade](#device-firmware-upgrade) - [Requirements](#requirements) - [Supported devices](#supported_devices) + - [IPv6 network support](#ipv6-network-support) - [Device UI](#device-ui) - [Setting up the environment](#setting-up-the-environment) - [Using Docker container for setup](#using-docker-container-for-setup) @@ -58,21 +62,37 @@ and [Zephyr RTOS](https://zephyrproject.org/). Visit Matter's [nRF Connect platform overview](../../../docs/guides/nrfconnect_platform_overview.md) to read more about the platform structure and dependencies. -The Matter device that runs the lighting application is controlled by the Matter -controller device over the Thread protocol. By default, the Matter device has -Thread disabled, and it should be paired with Matter controller and get -configuration from it. Some actions required before establishing full -communication are described below. +By default, the Matter accessory device has IPv6 networking disabled. You must +pair it with the Matter controller over Bluetooth® LE to get the configuration +from the controller to use the device within a Thread or Wi-Fi network. The +device starts advertising automatically and you can commission the device within +15 minutes. If the advertising time elapsed you can re-enable it using buttons. +See [Bluetooth LE advertising](#bluetooth-le-advertising) to learn how to do +this. The controller must get the commissioning information from the Matter +accessory device and provision the device into the network. -The example can be configured to use the secure bootloader and utilize it for -performing over-the-air Device Firmware Upgrade using Bluetooth LE. +You can test this application remotely over the Thread or the Wi-Fi protocol, +which in either case requires more devices, including a Matter controller that +you can configure either on a PC or a mobile device. + +The sample uses buttons for changing LED states to show the state of these +changes. You can test it in the following ways: + +- Standalone, using a single DK that runs the lighting application. + +- Remotely over the Thread or the Wi-Fi protocol, which in either case + requires more devices, including a Matter controller that you can configure + either on a PC or a mobile device. ### Bluetooth LE advertising -To commission the device onto a Matter network, the device must be discoverable -over Bluetooth LE that starts automatically upon the device startup, but only -for a predefined period of time (15 minutes by default). If the Bluetooth LE -advertising times out, you can re-enable it manually using **Button 4**. +In this example, to commission the device onto a Matter network, it must be +discoverable over Bluetooth LE. For security reasons, you must start Bluetooth +LE advertising manually after powering up the device by pressing: + +- On nRF52840 DK, nRF5340 DK, and nRF21540 DK: **Button 4**. + +- On nRF7002 DK: **Button 2**. ### Bluetooth LE rendezvous @@ -82,16 +102,16 @@ commissioner role. To start the rendezvous, the controller must get the commissioning information from the Matter device. The data payload is encoded within a QR code, printed to -the UART console, and shared using an NFC tag. NFC tag emulation starts -automatically when Bluetooth LE advertising is started and stays enabled until -Bluetooth LE advertising timeout expires. +the UART console, and shared using an NFC tag. The emulation of the NFC tag +emulation starts automatically when Bluetooth LE advertising is started and +stays enabled until Bluetooth LE advertising timeout expires. -#### Thread provisioning +#### Thread or Wi-Fi provisioning -Last part of the rendezvous procedure, the provisioning operation involves -sending the Thread network credentials from the Matter controller to the Matter -device. As a result, device is able to join the Thread network and communicate -with other Thread devices in the network. +The provisioning operation, which is the Last part of the rendezvous procedure, +involves sending the Thread or Wi-Fi network credentials from the Matter +controller to the Matter device. As a result, the device joins the Thread or +Wi-Fi network and can communicate with other devices in the network. ### Device Firmware Upgrade @@ -166,14 +186,24 @@ more information. The example supports building and running on the following devices: -| Hardware platform | Build target | Platform image | -| ------------------------------------------------------------------------------------------------- | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | -| [nRF52840 DK](https://www.nordicsemi.com/Software-and-Tools/Development-Kits/nRF52840-DK) | `nrf52840dk_nrf52840` |
nRF52840 DKnRF52840 DK
| -| [nRF5340 DK](https://www.nordicsemi.com/Software-and-Tools/Development-Kits/nRF5340-DK) | `nrf5340dk_nrf5340_cpuapp` |
nRF5340 DKnRF5340 DK
| -| [nRF52840 Dongle](https://www.nordicsemi.com/Software-and-Tools/Development-Kits/nRF52840-Dongle) | `nrf52840dongle_nrf52840` |
nRF52840 DonglenRF52840 Dongle
| +| Hardware platform | Build target | Platform image | +| --------------------------------------------------------------------------------------------------------------- | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | +| [nRF52840 DK](https://www.nordicsemi.com/Software-and-Tools/Development-Kits/nRF52840-DK) | `nrf52840dk_nrf52840` |
nRF52840 DKnRF52840 DK
| +| [nRF5340 DK](https://www.nordicsemi.com/Software-and-Tools/Development-Kits/nRF5340-DK) | `nrf5340dk_nrf5340_cpuapp` |
nRF5340 DKnRF5340 DK
| +| [nRF52840 Dongle](https://www.nordicsemi.com/Software-and-Tools/Development-Kits/nRF52840-Dongle) | `nrf52840dongle_nrf52840` |
nRF52840 DonglenRF52840 Dongle
| +| [nRF7002 DK](https://developer.nordicsemi.com/nRF_Connect_SDK/doc/latest/nrf/ug_nrf7002.html#nrf7002dk-nrf5340) | `nrf7002dk_nrf5340_cpuapp` |
nRF7002DKnRF7002 DK
|
+### IPv6 network support + +The development kits for this sample offer the following IPv6 network support +for Matter: + +- Matter over Thread is supported for `nrf52840dk_nrf52840` and + `nrf5340dk_nrf5340_cpuapp`. +- Matter over Wi-Fi is supported for `nrf7002dk_nrf5340_cpuapp`. + ## Device UI @@ -203,11 +233,10 @@ following states are possible: Bluetooth LE. - _Short Flash Off (950ms on/50ms off)_ — The device is fully - provisioned, but does not yet have full Thread network or service - connectivity. + provisioned, but does not yet have full connectivity for Thread or Wi-Fi + network. -- _Solid On_ — The device is fully provisioned and has full Thread - network and service connectivity. +- _Solid On_ — The device is fully provisioned. **LED 2** simulates the light bulb and shows the state of the lighting. The following states are possible: @@ -216,30 +245,46 @@ following states are possible: - _Off_ — The light bulb is off. -**LED 3** can be used to identify the device. The LED starts blinking evenly -(500 ms on/500 ms off) when the Identify command of the Identify cluster is -received. The command's argument can be used to specify the duration of the -effect. + Additionally, the LED starts blinking evenly (500 ms on/500 ms off) when the + Identify command of the Identify cluster is received on the endpoint 1. The + command’s argument can be used to specify the duration of the effect. **Button 1** can be used for the following purposes: -- _Pressed for 6 s_ — Initiates the factory reset of the device. - Releasing the button within the 6-second window cancels the factory reset - procedure. **LEDs 1-4** blink in unison when the factory reset procedure is - initiated. - -- _Pressed for less than 3 s_ — Initiates the OTA software update over - Bluetooth LE process. This feature is disabled by default, but can be - enabled by following the +- _Pressed for less than 3 s_ — Initiates the OTA software update + process. This feature is disabled by default, but can be enabled by + following the [Building with Device Firmware Upgrade support](#building-with-device-firmware-upgrade-support) - instruction. + instructions. + +- _Pressed for more than 3 s_ — initiates the factory reset of the + device. Releasing the button within the 3-second window cancels the factory + reset procedure. **Button 2** — Pressing the button once changes the lighting state to the opposite one. -**Button 4** — Pressing the button once starts the NFC tag emulation and -enables Bluetooth LE advertising for the predefined period of time (15 minutes -by default). +- On nRF52840 DK, nRF5340 DK, and nRF21540 DK: Changes the LED state to the + opposite one. + +- On nRF7002 DK: + + - If pressed for less than three seconds, it changes the LED state to the + opposite one. + + - If pressed for more than three seconds, it starts the NFC tag emulation, + enables Bluetooth LE advertising for the predefined period of time (15 + minutes by default), and makes the device discoverable over Bluetooth + LE. + +**Button 4** : + +- On nRF52840 DK, nRF5340 DK, and nRF21540 DK: Starts the NFC tag emulation, + enables Bluetooth LE advertising for the predefined period of time (15 + minutes by default), and makes the device discoverable over Bluetooth LE. + This button is used during the commissioning procedure. + +- On nRF7002 DK: Not available. **SEGGER J-Link USB port** can be used to get logs from the device or communicate with it using the diff --git a/examples/lighting-app/nrfconnect/boards/nrf7002dk_nrf5340_cpuapp.overlay b/examples/lighting-app/nrfconnect/boards/nrf7002dk_nrf5340_cpuapp.overlay new file mode 100644 index 00000000000000..481ff46231e7d1 --- /dev/null +++ b/examples/lighting-app/nrfconnect/boards/nrf7002dk_nrf5340_cpuapp.overlay @@ -0,0 +1,61 @@ +/* + * 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. + */ + + +/ { + chosen { + nordic,pm-ext-flash = &mx25r64; + }; + + /* + * By default, PWM module is only configured for led0 (LED1 on the board). + * The light bulb app, however, uses LED2 to show the state of the lighting, + * including its brightness level. + */ + aliases { + pwm-led1 = &pwm_led1; + }; + + pwmleds { + compatible = "pwm-leds"; + pwm_led1: pwm_led_1 { + pwms = < &pwm0 1 PWM_MSEC(20) PWM_POLARITY_INVERTED>; + }; + }; +}; + +&pwm0 { + pinctrl-0 = <&pwm0_default_alt>; + pinctrl-1 = <&pwm0_sleep_alt>; + pinctrl-names = "default", "sleep"; +}; + +&pinctrl { + pwm0_default_alt: pwm0_default_alt { + group1 { + psels = ; + nordic,invert; + }; + }; + + pwm0_sleep_alt: pwm0_sleep_alt { + group1 { + psels = ; + low-power-enable; + }; + }; + +}; diff --git a/examples/lighting-app/nrfconnect/child_image/hci_rpmsg/prj.conf b/examples/lighting-app/nrfconnect/child_image/hci_rpmsg/prj.conf new file mode 100644 index 00000000000000..1622ffd00dbb91 --- /dev/null +++ b/examples/lighting-app/nrfconnect/child_image/hci_rpmsg/prj.conf @@ -0,0 +1,25 @@ +# +# 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. +# + +# This target uses Kconfig.hci_rpmsg.defaults to set options common for all +# samples using hci_rpmsg. This file should contain only options specific for this sample +# hci_rpmsg configuration or overrides of default values. + +# Disable not used modules that cannot be set in Kconfig.hci_rpmsg.defaults due to overriding +# in board files. + +CONFIG_SERIAL=n +CONFIG_UART_CONSOLE=n diff --git a/examples/lighting-app/nrfconnect/child_image/hci_rpmsg/prj_no_dfu.conf b/examples/lighting-app/nrfconnect/child_image/hci_rpmsg/prj_no_dfu.conf new file mode 100644 index 00000000000000..1622ffd00dbb91 --- /dev/null +++ b/examples/lighting-app/nrfconnect/child_image/hci_rpmsg/prj_no_dfu.conf @@ -0,0 +1,25 @@ +# +# 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. +# + +# This target uses Kconfig.hci_rpmsg.defaults to set options common for all +# samples using hci_rpmsg. This file should contain only options specific for this sample +# hci_rpmsg configuration or overrides of default values. + +# Disable not used modules that cannot be set in Kconfig.hci_rpmsg.defaults due to overriding +# in board files. + +CONFIG_SERIAL=n +CONFIG_UART_CONSOLE=n diff --git a/examples/lighting-app/nrfconnect/child_image/hci_rpmsg/prj_release.conf b/examples/lighting-app/nrfconnect/child_image/hci_rpmsg/prj_release.conf new file mode 100644 index 00000000000000..1622ffd00dbb91 --- /dev/null +++ b/examples/lighting-app/nrfconnect/child_image/hci_rpmsg/prj_release.conf @@ -0,0 +1,25 @@ +# +# 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. +# + +# This target uses Kconfig.hci_rpmsg.defaults to set options common for all +# samples using hci_rpmsg. This file should contain only options specific for this sample +# hci_rpmsg configuration or overrides of default values. + +# Disable not used modules that cannot be set in Kconfig.hci_rpmsg.defaults due to overriding +# in board files. + +CONFIG_SERIAL=n +CONFIG_UART_CONSOLE=n diff --git a/examples/lighting-app/nrfconnect/child_image/mcuboot/prj.conf b/examples/lighting-app/nrfconnect/child_image/mcuboot/prj.conf index 287c7829c6a5cf..0dbf7106e88e22 100644 --- a/examples/lighting-app/nrfconnect/child_image/mcuboot/prj.conf +++ b/examples/lighting-app/nrfconnect/child_image/mcuboot/prj.conf @@ -23,7 +23,6 @@ CONFIG_MBEDTLS_CFG_FILE="mcuboot-mbedtls-cfg.h" # Bootloader size optimization # Disable not used modules that cannot be set in Kconfig.mcuboot.defaults due to overriding # in board files. -CONFIG_GPIO=n CONFIG_CONSOLE=n CONFIG_SERIAL=n CONFIG_UART_CONSOLE=n diff --git a/examples/lighting-app/nrfconnect/child_image/mcuboot/prj_release.conf b/examples/lighting-app/nrfconnect/child_image/mcuboot/prj_release.conf index 287c7829c6a5cf..0dbf7106e88e22 100644 --- a/examples/lighting-app/nrfconnect/child_image/mcuboot/prj_release.conf +++ b/examples/lighting-app/nrfconnect/child_image/mcuboot/prj_release.conf @@ -23,7 +23,6 @@ CONFIG_MBEDTLS_CFG_FILE="mcuboot-mbedtls-cfg.h" # Bootloader size optimization # Disable not used modules that cannot be set in Kconfig.mcuboot.defaults due to overriding # in board files. -CONFIG_GPIO=n CONFIG_CONSOLE=n CONFIG_SERIAL=n CONFIG_UART_CONSOLE=n diff --git a/examples/lighting-app/nrfconnect/configuration/nrf7002dk_nrf5340_cpuapp/pm_static_dfu.yml b/examples/lighting-app/nrfconnect/configuration/nrf7002dk_nrf5340_cpuapp/pm_static_dfu.yml new file mode 100644 index 00000000000000..3c56dc0ddb1968 --- /dev/null +++ b/examples/lighting-app/nrfconnect/configuration/nrf7002dk_nrf5340_cpuapp/pm_static_dfu.yml @@ -0,0 +1,56 @@ +mcuboot: + address: 0x0 + size: 0xC000 + region: flash_primary +mcuboot_pad: + address: 0xC000 + size: 0x200 +app: + address: 0xC200 + size: 0xeee00 +mcuboot_primary: + orig_span: &id001 + - mcuboot_pad + - app + span: *id001 + address: 0xC000 + size: 0xef000 + region: flash_primary +mcuboot_primary_app: + orig_span: &id002 + - app + span: *id002 + address: 0xC200 + size: 0xeee00 +factory_data: + address: 0xfb000 + size: 0x1000 + region: flash_primary +settings_storage: + address: 0xfc000 + size: 0x4000 + region: flash_primary +mcuboot_primary_1: + address: 0x0 + size: 0x40000 + device: flash_ctrl + region: ram_flash +mcuboot_secondary: + address: 0x0 + size: 0xef000 + device: MX25R64 + region: external_flash +mcuboot_secondary_1: + address: 0xef000 + size: 0x40000 + device: MX25R64 + region: external_flash +external_flash: + address: 0x12f000 + size: 0x6D1000 + device: MX25R64 + region: external_flash +pcd_sram: + address: 0x20000000 + size: 0x2000 + region: sram_primary diff --git a/examples/lighting-app/nrfconnect/main/AppTask.cpp b/examples/lighting-app/nrfconnect/main/AppTask.cpp index 1ee75869d3ddfc..15ab885139e20b 100644 --- a/examples/lighting-app/nrfconnect/main/AppTask.cpp +++ b/examples/lighting-app/nrfconnect/main/AppTask.cpp @@ -20,7 +20,7 @@ #include "AppConfig.h" #include "AppEvent.h" -#include "LEDWidget.h" +#include "LEDUtil.h" #include "PWMDevice.h" #include @@ -41,6 +41,11 @@ #include #include +#ifdef CONFIG_CHIP_WIFI +#include +#include +#endif + #if CONFIG_CHIP_OTA_REQUESTOR #include "OTAUtil.h" #endif @@ -49,7 +54,7 @@ #include #include -LOG_MODULE_DECLARE(app, CONFIG_MATTER_LOG_LEVEL); +LOG_MODULE_DECLARE(app, CONFIG_CHIP_APP_LOG_LEVEL); using namespace ::chip; using namespace ::chip::app; @@ -61,12 +66,12 @@ namespace { constexpr int kFactoryResetTriggerTimeout = 3000; constexpr int kFactoryResetCancelWindowTimeout = 3000; constexpr int kAppEventQueueSize = 10; -constexpr uint8_t kButtonPushEvent = 1; -constexpr uint8_t kButtonReleaseEvent = 0; constexpr EndpointId kLightEndpointId = 1; -constexpr uint32_t kIdentifyBlinkRateMs = 500; constexpr uint8_t kDefaultMinLevel = 0; constexpr uint8_t kDefaultMaxLevel = 254; +#if NUMBER_OF_BUTTONS == 2 +constexpr uint32_t kAdvertisingTriggerTimeout = 3000; +#endif // NOTE! This key is for test/certification only and should not be available in production devices! // If CONFIG_CHIP_FACTORY_DATA is enabled, this value is read from the factory data. @@ -81,12 +86,15 @@ Identify sIdentify = { kLightEndpointId, AppTask::IdentifyStartHandler, AppTask: LEDWidget sStatusLED; LEDWidget sIdentifyLED; -LEDWidget sUnusedLED; +#if NUMBER_OF_LEDS == 4 +FactoryResetLEDsWrapper<2> sFactoryResetLEDs{ { FACTORY_RESET_SIGNAL_LED, FACTORY_RESET_SIGNAL_LED1 } }; +#endif + +bool sIsNetworkProvisioned = false; +bool sIsNetworkEnabled = false; +bool sHaveBLEConnections = false; const struct pwm_dt_spec sLightPwmDevice = PWM_DT_SPEC_GET(DT_ALIAS(pwm_led1)); -bool sIsThreadProvisioned = false; -bool sIsThreadEnabled = false; -bool sHaveBLEConnections = false; chip::DeviceLayer::DeviceInfoProviderImpl gExampleDeviceInfoProvider; @@ -103,7 +111,26 @@ DeferredAttributePersistenceProvider gDeferredAttributePersister(Server::GetInst } // namespace -AppTask AppTask::sAppTask; +namespace LedConsts { +constexpr uint32_t kBlinkRate_ms{ 500 }; +constexpr uint32_t kIdentifyBlinkRate_ms{ 500 }; + +namespace StatusLed { +namespace Unprovisioned { +constexpr uint32_t kOn_ms{ 100 }; +constexpr uint32_t kOff_ms{ kOn_ms }; +} /* namespace Unprovisioned */ +namespace Provisioned { +constexpr uint32_t kOn_ms{ 50 }; +constexpr uint32_t kOff_ms{ 950 }; +} /* namespace Provisioned */ + +} /* namespace StatusLed */ +} /* namespace LedConsts */ + +#ifdef CONFIG_CHIP_WIFI +app::Clusters::NetworkCommissioning::Instance sWiFiCommissioningInstance(0, &(NetworkCommissioning::NrfWiFiDriver::Instance())); +#endif CHIP_ERROR AppTask::Init() { @@ -139,36 +166,24 @@ CHIP_ERROR AppTask::Init() LOG_ERR("ConnectivityMgr().SetThreadDeviceType() failed"); return err; } -#elif !defined(CONFIG_WIFI_NRF700X) +#elif defined(CONFIG_CHIP_WIFI) + sWiFiCommissioningInstance.Init(); +#else return CHIP_ERROR_INTERNAL; -#endif +#endif // CONFIG_NET_L2_OPENTHREAD // Initialize LEDs LEDWidget::InitGpio(); LEDWidget::SetStateUpdateCallback(LEDStateUpdateHandler); sStatusLED.Init(SYSTEM_STATE_LED); - sIdentifyLED.Init(DK_LED3); - sUnusedLED.Init(DK_LED4); + sIdentifyLED.Init(LIGHTING_STATE_LED); + sIdentifyLED.Set(false); UpdateStatusLED(); - // Initialize lighting device (PWM) - uint8_t minLightLevel = kDefaultMinLevel; - Clusters::LevelControl::Attributes::MinLevel::Get(kLightEndpointId, &minLightLevel); - - uint8_t maxLightLevel = kDefaultMaxLevel; - Clusters::LevelControl::Attributes::MaxLevel::Get(kLightEndpointId, &maxLightLevel); - - int ret = mPWMDevice.Init(&sLightPwmDevice, minLightLevel, maxLightLevel, maxLightLevel); - if (ret != 0) - { - return chip::System::MapErrorZephyr(ret); - } - mPWMDevice.SetCallbacks(ActionInitiated, ActionCompleted); - // Initialize buttons - ret = dk_buttons_init(ButtonEventHandler); + int ret = dk_buttons_init(ButtonEventHandler); if (ret) { LOG_ERR("dk_buttons_init() failed"); @@ -176,7 +191,7 @@ CHIP_ERROR AppTask::Init() } // Initialize function button timer - k_timer_init(&sFunctionTimer, &AppTask::TimerEventHandler, nullptr); + k_timer_init(&sFunctionTimer, &AppTask::FunctionTimerTimeoutCallback, nullptr); k_timer_user_data_set(&sFunctionTimer, this); #ifdef CONFIG_MCUMGR_SMP_BT @@ -185,6 +200,20 @@ CHIP_ERROR AppTask::Init() GetDFUOverSMP().ConfirmNewImage(); #endif + // Initialize lighting device (PWM) + uint8_t minLightLevel = kDefaultMinLevel; + Clusters::LevelControl::Attributes::MinLevel::Get(kLightEndpointId, &minLightLevel); + + uint8_t maxLightLevel = kDefaultMaxLevel; + Clusters::LevelControl::Attributes::MaxLevel::Get(kLightEndpointId, &maxLightLevel); + + ret = mPWMDevice.Init(&sLightPwmDevice, minLightLevel, maxLightLevel, maxLightLevel); + if (ret != 0) + { + return chip::System::MapErrorZephyr(ret); + } + mPWMDevice.SetCallbacks(ActionInitiated, ActionCompleted); + // Initialize CHIP server #if CONFIG_CHIP_FACTORY_DATA ReturnErrorOnFailure(mFactoryDataProvider.Init()); @@ -239,132 +268,195 @@ CHIP_ERROR AppTask::StartApp() while (true) { k_msgq_get(&sAppEventQueue, &event, K_FOREVER); - DispatchEvent(&event); + DispatchEvent(event); } return CHIP_NO_ERROR; } -void AppTask::LightingActionEventHandler(AppEvent * aEvent) +void AppTask::IdentifyStartHandler(Identify *) +{ + AppEvent event; + event.Type = AppEventType::IdentifyStart; + event.Handler = [](const AppEvent &) { + Instance().mPWMDevice.SuppressOutput(); + sIdentifyLED.Blink(LedConsts::kIdentifyBlinkRate_ms); + }; + PostEvent(event); +} + +void AppTask::IdentifyStopHandler(Identify *) +{ + AppEvent event; + event.Type = AppEventType::IdentifyStop; + event.Handler = [](const AppEvent &) { + sIdentifyLED.Set(false); + Instance().mPWMDevice.ApplyLevel(); + }; + PostEvent(event); +} + +#if NUMBER_OF_BUTTONS == 2 +void AppTask::StartBLEAdvertisementAndLightActionEventHandler(const AppEvent & event) +{ + if (event.ButtonEvent.Action == static_cast(AppEventType::ButtonPushed)) + { + Instance().StartTimer(kAdvertisingTriggerTimeout); + Instance().mFunction = FunctionEvent::AdvertisingStart; + } + else + { + if (Instance().mFunction == FunctionEvent::AdvertisingStart && Instance().mFunctionTimerActive) + { + Instance().CancelTimer(); + Instance().mFunction = FunctionEvent::NoneSelected; + + AppEvent button_event; + button_event.Type = AppEventType::Button; + button_event.ButtonEvent.PinNo = BLE_ADVERTISEMENT_START_AND_LIGHTING_BUTTON; + button_event.ButtonEvent.Action = static_cast(AppEventType::ButtonReleased); + button_event.Handler = LightingActionEventHandler; + PostEvent(button_event); + } + } +} +#endif + +void AppTask::LightingActionEventHandler(const AppEvent & event) { PWMDevice::Action_t action = PWMDevice::INVALID_ACTION; int32_t actor = 0; - if (aEvent->Type == AppEvent::kEventType_Lighting) + if (event.Type == AppEventType::Lighting) { - action = static_cast(aEvent->LightingEvent.Action); - actor = aEvent->LightingEvent.Actor; + action = static_cast(event.LightingEvent.Action); + actor = event.LightingEvent.Actor; } - else if (aEvent->Type == AppEvent::kEventType_Button) + else if (event.Type == AppEventType::Button) { - action = GetAppTask().mPWMDevice.IsTurnedOn() ? PWMDevice::OFF_ACTION : PWMDevice::ON_ACTION; - actor = AppEvent::kEventType_Button; + action = Instance().mPWMDevice.IsTurnedOn() ? PWMDevice::OFF_ACTION : PWMDevice::ON_ACTION; + actor = static_cast(AppEventType::Button); } - if (action != PWMDevice::INVALID_ACTION && GetAppTask().mPWMDevice.InitiateAction(action, actor, NULL)) + if (action != PWMDevice::INVALID_ACTION && Instance().mPWMDevice.InitiateAction(action, actor, NULL)) + { LOG_INF("Action is already in progress or active."); + } } -void AppTask::ButtonEventHandler(uint32_t button_state, uint32_t has_changed) +void AppTask::ButtonEventHandler(uint32_t buttonState, uint32_t hasChanged) { AppEvent button_event; - button_event.Type = AppEvent::kEventType_Button; + button_event.Type = AppEventType::Button; - if (LIGHTING_BUTTON_MASK & button_state & has_changed) +#if NUMBER_OF_BUTTONS == 2 + if (BLE_ADVERTISEMENT_START_AND_LIGHTING_BUTTON_MASK & hasChanged) + { + button_event.ButtonEvent.PinNo = BLE_ADVERTISEMENT_START_AND_LIGHTING_BUTTON; + button_event.ButtonEvent.Action = + static_cast((BLE_ADVERTISEMENT_START_AND_LIGHTING_BUTTON_MASK & buttonState) ? AppEventType::ButtonPushed + : AppEventType::ButtonReleased); + button_event.Handler = StartBLEAdvertisementAndLightActionEventHandler; + PostEvent(button_event); + } +#else + if (LIGHTING_BUTTON_MASK & buttonState & hasChanged) { button_event.ButtonEvent.PinNo = LIGHTING_BUTTON; - button_event.ButtonEvent.Action = kButtonPushEvent; + button_event.ButtonEvent.Action = static_cast(AppEventType::ButtonPushed); button_event.Handler = LightingActionEventHandler; - sAppTask.PostEvent(&button_event); + PostEvent(button_event); } - if (FUNCTION_BUTTON_MASK & has_changed) + if (BLE_ADVERTISEMENT_START_BUTTON_MASK & buttonState & hasChanged) { - button_event.ButtonEvent.PinNo = FUNCTION_BUTTON; - button_event.ButtonEvent.Action = (FUNCTION_BUTTON_MASK & button_state) ? kButtonPushEvent : kButtonReleaseEvent; - button_event.Handler = FunctionHandler; - sAppTask.PostEvent(&button_event); + button_event.ButtonEvent.PinNo = BLE_ADVERTISEMENT_START_BUTTON; + button_event.ButtonEvent.Action = static_cast(AppEventType::ButtonPushed); + button_event.Handler = StartBLEAdvertisementHandler; + PostEvent(button_event); } +#endif - if (BLE_ADVERTISEMENT_START_BUTTON_MASK & button_state & has_changed) + if (FUNCTION_BUTTON_MASK & hasChanged) { - button_event.ButtonEvent.PinNo = BLE_ADVERTISEMENT_START_BUTTON; - button_event.ButtonEvent.Action = kButtonPushEvent; - button_event.Handler = StartBLEAdvertisementHandler; - sAppTask.PostEvent(&button_event); + button_event.ButtonEvent.PinNo = FUNCTION_BUTTON; + button_event.ButtonEvent.Action = + static_cast((FUNCTION_BUTTON_MASK & buttonState) ? AppEventType::ButtonPushed : AppEventType::ButtonReleased); + button_event.Handler = FunctionHandler; + PostEvent(button_event); } } -void AppTask::TimerEventHandler(k_timer * timer) +void AppTask::FunctionTimerTimeoutCallback(k_timer * timer) { + if (!timer) + { + return; + } + AppEvent event; - event.Type = AppEvent::kEventType_Timer; + event.Type = AppEventType::Timer; event.TimerEvent.Context = k_timer_user_data_get(timer); event.Handler = FunctionTimerEventHandler; - sAppTask.PostEvent(&event); + PostEvent(event); } -void AppTask::IdentifyStartHandler(Identify *) +void AppTask::FunctionTimerEventHandler(const AppEvent & event) { - AppEvent event; - event.Type = AppEvent::kEventType_IdentifyStart; - event.Handler = [](AppEvent *) { sIdentifyLED.Blink(kIdentifyBlinkRateMs); }; - sAppTask.PostEvent(&event); -} - -void AppTask::IdentifyStopHandler(Identify *) -{ - AppEvent event; - event.Type = AppEvent::kEventType_IdentifyStop; - event.Handler = [](AppEvent *) { sIdentifyLED.Set(false); }; - sAppTask.PostEvent(&event); -} - -void AppTask::FunctionTimerEventHandler(AppEvent * aEvent) -{ - if (aEvent->Type != AppEvent::kEventType_Timer) + if (event.Type != AppEventType::Timer) + { return; + } // If we reached here, the button was held past kFactoryResetTriggerTimeout, initiate factory reset - if (sAppTask.mFunctionTimerActive && sAppTask.mFunction == kFunction_SoftwareUpdate) + if (Instance().mFunction == FunctionEvent::SoftwareUpdate) { LOG_INF("Factory Reset Triggered. Release button within %ums to cancel.", kFactoryResetTriggerTimeout); // Start timer for kFactoryResetCancelWindowTimeout to allow user to cancel, if required. - sAppTask.StartTimer(kFactoryResetCancelWindowTimeout); - sAppTask.mFunction = kFunction_FactoryReset; + Instance().StartTimer(kFactoryResetCancelWindowTimeout); + Instance().mFunction = FunctionEvent::FactoryReset; // Turn off all LEDs before starting blink to make sure blink is co-ordinated. sStatusLED.Set(false); - sIdentifyLED.Set(false); - sUnusedLED.Set(false); +#if NUMBER_OF_LEDS == 4 + sFactoryResetLEDs.Set(false); +#endif - sStatusLED.Blink(500); - sIdentifyLED.Blink(500); - sUnusedLED.Blink(500); + sStatusLED.Blink(LedConsts::kBlinkRate_ms); +#if NUMBER_OF_LEDS == 4 + sFactoryResetLEDs.Blink(LedConsts::kBlinkRate_ms); +#endif } - else if (sAppTask.mFunctionTimerActive && sAppTask.mFunction == kFunction_FactoryReset) + else if (Instance().mFunction == FunctionEvent::FactoryReset) { // Actually trigger Factory Reset - sAppTask.mFunction = kFunction_NoneSelected; - + Instance().mFunction = FunctionEvent::NoneSelected; chip::Server::GetInstance().ScheduleFactoryReset(); } + else if (Instance().mFunction == FunctionEvent::AdvertisingStart) + { + // The button was held past kAdvertisingTriggerTimeout, start BLE advertisement if we have 2 buttons UI +#if NUMBER_OF_BUTTONS == 2 + StartBLEAdvertisementHandler(event); + Instance().mFunction = FunctionEvent::NoneSelected; +#endif + } } #ifdef CONFIG_MCUMGR_SMP_BT void AppTask::RequestSMPAdvertisingStart(void) { AppEvent event; - event.Type = AppEvent::kEventType_StartSMPAdvertising; - event.Handler = [](AppEvent *) { GetDFUOverSMP().StartBLEAdvertising(); }; - sAppTask.PostEvent(&event); + event.Type = AppEventType::StartSMPAdvertising; + event.Handler = [](const AppEvent &) { GetDFUOverSMP().StartBLEAdvertising(); }; + PostEvent(event); } #endif -void AppTask::FunctionHandler(AppEvent * aEvent) +void AppTask::FunctionHandler(const AppEvent & event) { - if (aEvent->ButtonEvent.PinNo != FUNCTION_BUTTON) + if (event.ButtonEvent.PinNo != FUNCTION_BUTTON) return; // To trigger software update: press the FUNCTION_BUTTON button briefly (< kFactoryResetTriggerTimeout) @@ -372,22 +464,21 @@ void AppTask::FunctionHandler(AppEvent * aEvent) // All LEDs start blinking after kFactoryResetTriggerTimeout to signal factory reset has been initiated. // To cancel factory reset: release the FUNCTION_BUTTON once all LEDs start blinking within the // kFactoryResetCancelWindowTimeout - if (aEvent->ButtonEvent.Action == kButtonPushEvent) + if (event.ButtonEvent.Action == static_cast(AppEventType::ButtonPushed)) { - if (!sAppTask.mFunctionTimerActive && sAppTask.mFunction == kFunction_NoneSelected) + if (!Instance().mFunctionTimerActive && Instance().mFunction == FunctionEvent::NoneSelected) { - sAppTask.StartTimer(kFactoryResetTriggerTimeout); - - sAppTask.mFunction = kFunction_SoftwareUpdate; + Instance().StartTimer(kFactoryResetTriggerTimeout); + Instance().mFunction = FunctionEvent::SoftwareUpdate; } } else { // If the button was released before factory reset got initiated, trigger a software update. - if (sAppTask.mFunctionTimerActive && sAppTask.mFunction == kFunction_SoftwareUpdate) + if (Instance().mFunctionTimerActive && Instance().mFunction == FunctionEvent::SoftwareUpdate) { - sAppTask.CancelTimer(); - sAppTask.mFunction = kFunction_NoneSelected; + Instance().CancelTimer(); + Instance().mFunction = FunctionEvent::NoneSelected; #ifdef CONFIG_MCUMGR_SMP_BT GetDFUOverSMP().StartServer(); @@ -395,19 +486,20 @@ void AppTask::FunctionHandler(AppEvent * aEvent) LOG_INF("Software update is disabled"); #endif } - else if (sAppTask.mFunctionTimerActive && sAppTask.mFunction == kFunction_FactoryReset) + else if (Instance().mFunctionTimerActive && Instance().mFunction == FunctionEvent::FactoryReset) { - sIdentifyLED.Set(false); - sUnusedLED.Set(false); +#if NUMBER_OF_LEDS == 4 + sFactoryResetLEDs.Set(false); +#endif UpdateStatusLED(); - sAppTask.CancelTimer(); - sAppTask.mFunction = kFunction_NoneSelected; + Instance().CancelTimer(); + Instance().mFunction = FunctionEvent::NoneSelected; LOG_INF("Factory Reset has been Canceled"); } } } -void AppTask::StartBLEAdvertisementHandler(AppEvent *) +void AppTask::StartBLEAdvertisementHandler(const AppEvent &) { if (Server::GetInstance().GetFabricTable().FabricCount() != 0) { @@ -427,44 +519,44 @@ void AppTask::StartBLEAdvertisementHandler(AppEvent *) } } -void AppTask::UpdateLedStateEventHandler(AppEvent * aEvent) +void AppTask::UpdateLedStateEventHandler(const AppEvent & event) { - if (aEvent->Type == AppEvent::kEventType_UpdateLedState) + if (event.Type == AppEventType::UpdateLedState) { - aEvent->UpdateLedStateEvent.LedWidget->UpdateState(); + event.UpdateLedStateEvent.LedWidget->UpdateState(); } } void AppTask::LEDStateUpdateHandler(LEDWidget & ledWidget) { AppEvent event; - event.Type = AppEvent::kEventType_UpdateLedState; + event.Type = AppEventType::UpdateLedState; event.Handler = UpdateLedStateEventHandler; event.UpdateLedStateEvent.LedWidget = &ledWidget; - sAppTask.PostEvent(&event); + PostEvent(event); } void AppTask::UpdateStatusLED() { - /* Update the status LED. - * - * If thread and service provisioned, keep the LED On constantly. - * - * If the system has ble connection(s) uptill the stage above, THEN blink the LED at an even - * rate of 100ms. - * - * Otherwise, blink the LED On for a very short time. */ - if (sIsThreadProvisioned && sIsThreadEnabled) + // Update the status LED. + // + // If IPv6 network and service provisioned, keep the LED On constantly. + // + // If the system has ble connection(s) uptill the stage above, THEN blink the LED at an even + // rate of 100ms. + // + // Otherwise, blink the LED for a very short time. + if (sIsNetworkProvisioned && sIsNetworkEnabled) { sStatusLED.Set(true); } else if (sHaveBLEConnections) { - sStatusLED.Blink(100, 100); + sStatusLED.Blink(LedConsts::StatusLed::Unprovisioned::kOn_ms, LedConsts::StatusLed::Unprovisioned::kOff_ms); } else { - sStatusLED.Blink(50, 950); + sStatusLED.Blink(LedConsts::StatusLed::Provisioned::kOn_ms, LedConsts::StatusLed::Provisioned::kOff_ms); } } @@ -493,92 +585,94 @@ void AppTask::ChipEventHandler(const ChipDeviceEvent * event, intptr_t /* arg */ sHaveBLEConnections = ConnectivityMgr().NumBLEConnections() != 0; UpdateStatusLED(); break; - case DeviceEventType::kThreadStateChange: - sIsThreadProvisioned = ConnectivityMgr().IsThreadProvisioned(); - sIsThreadEnabled = ConnectivityMgr().IsThreadEnabled(); - UpdateStatusLED(); - break; +#if defined(CONFIG_NET_L2_OPENTHREAD) case DeviceEventType::kDnssdPlatformInitialized: #if CONFIG_CHIP_OTA_REQUESTOR InitBasicOTARequestor(); +#endif /* CONFIG_CHIP_OTA_REQUESTOR */ + break; + case DeviceEventType::kThreadStateChange: + sIsNetworkProvisioned = ConnectivityMgr().IsThreadProvisioned(); + sIsNetworkEnabled = ConnectivityMgr().IsThreadEnabled(); +#elif defined(CONFIG_CHIP_WIFI) + case DeviceEventType::kWiFiConnectivityChange: + sIsNetworkProvisioned = ConnectivityMgr().IsWiFiStationProvisioned(); + sIsNetworkEnabled = ConnectivityMgr().IsWiFiStationEnabled(); +#if CONFIG_CHIP_OTA_REQUESTOR + if (event->WiFiConnectivityChange.Result == kConnectivity_Established) + { + InitBasicOTARequestor(); + } +#endif /* CONFIG_CHIP_OTA_REQUESTOR */ #endif + UpdateStatusLED(); break; default: break; } } - void AppTask::CancelTimer() { k_timer_stop(&sFunctionTimer); mFunctionTimerActive = false; } -void AppTask::StartTimer(uint32_t aTimeoutInMs) +void AppTask::StartTimer(uint32_t timeoutInMs) { - k_timer_start(&sFunctionTimer, K_MSEC(aTimeoutInMs), K_NO_WAIT); + k_timer_start(&sFunctionTimer, K_MSEC(timeoutInMs), K_NO_WAIT); mFunctionTimerActive = true; } -void AppTask::ActionInitiated(PWMDevice::Action_t aAction, int32_t aActor) +void AppTask::ActionInitiated(PWMDevice::Action_t action, int32_t actor) { - if (aAction == PWMDevice::ON_ACTION) + if (action == PWMDevice::ON_ACTION) { LOG_INF("Turn On Action has been initiated"); } - else if (aAction == PWMDevice::OFF_ACTION) + else if (action == PWMDevice::OFF_ACTION) { LOG_INF("Turn Off Action has been initiated"); } - else if (aAction == PWMDevice::LEVEL_ACTION) + else if (action == PWMDevice::LEVEL_ACTION) { LOG_INF("Level Action has been initiated"); } } -void AppTask::ActionCompleted(PWMDevice::Action_t aAction, int32_t aActor) +void AppTask::ActionCompleted(PWMDevice::Action_t action, int32_t actor) { - if (aAction == PWMDevice::ON_ACTION) + if (action == PWMDevice::ON_ACTION) { LOG_INF("Turn On Action has been completed"); } - else if (aAction == PWMDevice::OFF_ACTION) + else if (action == PWMDevice::OFF_ACTION) { LOG_INF("Turn Off Action has been completed"); } - else if (aAction == PWMDevice::LEVEL_ACTION) + else if (action == PWMDevice::LEVEL_ACTION) { LOG_INF("Level Action has been completed"); } - if (aActor == AppEvent::kEventType_Button) + if (actor == static_cast(AppEventType::Button)) { - sAppTask.UpdateClusterState(); + Instance().UpdateClusterState(); } } -void AppTask::PostLightingActionRequest(PWMDevice::Action_t aAction) +void AppTask::PostEvent(const AppEvent & event) { - AppEvent event; - event.Type = AppEvent::kEventType_Lighting; - event.LightingEvent.Action = aAction; - event.Handler = LightingActionEventHandler; - PostEvent(&event); -} - -void AppTask::PostEvent(AppEvent * aEvent) -{ - if (k_msgq_put(&sAppEventQueue, aEvent, K_NO_WAIT) != 0) + if (k_msgq_put(&sAppEventQueue, &event, K_NO_WAIT) != 0) { LOG_INF("Failed to post event to app task event queue"); } } -void AppTask::DispatchEvent(AppEvent * aEvent) +void AppTask::DispatchEvent(const AppEvent & event) { - if (aEvent->Handler) + if (event.Handler) { - aEvent->Handler(aEvent); + event.Handler(event); } else { @@ -588,18 +682,21 @@ void AppTask::DispatchEvent(AppEvent * aEvent) void AppTask::UpdateClusterState() { - // write the new on/off value - EmberAfStatus status = Clusters::OnOff::Attributes::OnOff::Set(kLightEndpointId, mPWMDevice.IsTurnedOn()); + SystemLayer().ScheduleLambda([this] { + // write the new on/off value + EmberAfStatus status = Clusters::OnOff::Attributes::OnOff::Set(kLightEndpointId, mPWMDevice.IsTurnedOn()); - if (status != EMBER_ZCL_STATUS_SUCCESS) - { - LOG_ERR("Updating on/off cluster failed: %x", status); - } + if (status != EMBER_ZCL_STATUS_SUCCESS) + { + LOG_ERR("Updating on/off cluster failed: %x", status); + } - status = Clusters::LevelControl::Attributes::CurrentLevel::Set(kLightEndpointId, mPWMDevice.GetLevel()); + // write the current level + status = Clusters::LevelControl::Attributes::CurrentLevel::Set(kLightEndpointId, mPWMDevice.GetLevel()); - if (status != EMBER_ZCL_STATUS_SUCCESS) - { - LOG_ERR("Updating level cluster failed: %x", status); - } + if (status != EMBER_ZCL_STATUS_SUCCESS) + { + LOG_ERR("Updating level cluster failed: %x", status); + } + }); } diff --git a/examples/lighting-app/nrfconnect/main/ZclCallbacks.cpp b/examples/lighting-app/nrfconnect/main/ZclCallbacks.cpp index 024d70a33f60f0..eeb83262bfb804 100644 --- a/examples/lighting-app/nrfconnect/main/ZclCallbacks.cpp +++ b/examples/lighting-app/nrfconnect/main/ZclCallbacks.cpp @@ -38,15 +38,16 @@ void MatterPostAttributeChangeCallback(const chip::app::ConcreteAttributePath & if (clusterId == OnOff::Id && attributeId == OnOff::Attributes::OnOff::Id) { ChipLogProgress(Zcl, "Cluster OnOff: attribute OnOff set to %u", *value); - GetAppTask().GetLightingDevice().InitiateAction(*value ? PWMDevice::ON_ACTION : PWMDevice::OFF_ACTION, - AppEvent::kEventType_Lighting, value); + AppTask::Instance().GetPWMDevice().InitiateAction(*value ? PWMDevice::ON_ACTION : PWMDevice::OFF_ACTION, + static_cast(AppEventType::Lighting), value); } else if (clusterId == LevelControl::Id && attributeId == LevelControl::Attributes::CurrentLevel::Id) { ChipLogProgress(Zcl, "Cluster LevelControl: attribute CurrentLevel set to %u", *value); - if (GetAppTask().GetLightingDevice().IsTurnedOn()) + if (AppTask::Instance().GetPWMDevice().IsTurnedOn()) { - GetAppTask().GetLightingDevice().InitiateAction(PWMDevice::LEVEL_ACTION, AppEvent::kEventType_Lighting, value); + AppTask::Instance().GetPWMDevice().InitiateAction(PWMDevice::LEVEL_ACTION, static_cast(AppEventType::Lighting), + value); } else { @@ -80,9 +81,10 @@ void emberAfOnOffClusterInitCallback(EndpointId endpoint) if (status == EMBER_ZCL_STATUS_SUCCESS) { // Set actual state to the cluster state that was last persisted - GetAppTask().GetLightingDevice().InitiateAction(storedValue ? PWMDevice::ON_ACTION : PWMDevice::OFF_ACTION, - AppEvent::kEventType_Lighting, reinterpret_cast(&storedValue)); + AppTask::Instance().GetPWMDevice().InitiateAction(storedValue ? PWMDevice::ON_ACTION : PWMDevice::OFF_ACTION, + static_cast(AppEventType::Lighting), + reinterpret_cast(&storedValue)); } - GetAppTask().UpdateClusterState(); + AppTask::Instance().UpdateClusterState(); } diff --git a/examples/lighting-app/nrfconnect/main/include/AppConfig.h b/examples/lighting-app/nrfconnect/main/include/AppConfig.h index 4f55191d9ae7c3..536e92e37bfd37 100644 --- a/examples/lighting-app/nrfconnect/main/include/AppConfig.h +++ b/examples/lighting-app/nrfconnect/main/include/AppConfig.h @@ -18,16 +18,28 @@ #pragma once +#include "BoardUtil.h" + // ---- Lighting Example App Config ---- -#define LIGHTING_BUTTON DK_BTN2 -#define LIGHTING_BUTTON_MASK DK_BTN2_MSK #define FUNCTION_BUTTON DK_BTN1 #define FUNCTION_BUTTON_MASK DK_BTN1_MSK + +#if NUMBER_OF_BUTTONS == 2 +#define BLE_ADVERTISEMENT_START_AND_LIGHTING_BUTTON DK_BTN2 +#define BLE_ADVERTISEMENT_START_AND_LIGHTING_BUTTON_MASK DK_BTN2_MSK +#else +#define LIGHTING_BUTTON DK_BTN2 +#define LIGHTING_BUTTON_MASK DK_BTN2_MSK #define BLE_ADVERTISEMENT_START_BUTTON DK_BTN4 #define BLE_ADVERTISEMENT_START_BUTTON_MASK DK_BTN4_MSK +#endif -#define SYSTEM_STATE_LED DK_LED1 // led0 in device tree - +#define SYSTEM_STATE_LED DK_LED1 +#define LIGHTING_STATE_LED DK_LED2 +#if NUMBER_OF_LEDS == 4 +#define FACTORY_RESET_SIGNAL_LED DK_LED3 +#define FACTORY_RESET_SIGNAL_LED1 DK_LED4 +#endif // Time it takes in ms for the simulated actuator to move from one state to another. #define ACTUATOR_MOVEMENT_PERIOS_MS 2000 diff --git a/examples/lighting-app/nrfconnect/main/include/AppEvent.h b/examples/lighting-app/nrfconnect/main/include/AppEvent.h index a5af0c939539cc..19e8cede4dab5a 100644 --- a/examples/lighting-app/nrfconnect/main/include/AppEvent.h +++ b/examples/lighting-app/nrfconnect/main/include/AppEvent.h @@ -20,29 +20,34 @@ #include -#include "LEDWidget.h" +#include "EventTypes.h" -struct AppEvent; -typedef void (*EventHandler)(AppEvent *); +class LEDWidget; -struct AppEvent +enum class AppEventType : uint8_t { - enum AppEventTypes - { - kEventType_Button = 0, - kEventType_Timer, - kEventType_Lighting, - kEventType_Install, - kEventType_UpdateLedState, - kEventType_IdentifyStart, - kEventType_IdentifyStop, -#ifdef CONFIG_MCUMGR_SMP_BT - kEventType_StartSMPAdvertising, -#endif - }; + None = 0, + Button, + ButtonPushed, + ButtonReleased, + Timer, + UpdateLedState, + Lighting, + IdentifyStart, + IdentifyStop, + StartSMPAdvertising +}; - uint16_t Type; +enum class FunctionEvent : uint8_t +{ + NoneSelected = 0, + SoftwareUpdate = 0, + FactoryReset, + AdvertisingStart +}; +struct AppEvent +{ union { struct @@ -65,5 +70,6 @@ struct AppEvent } UpdateLedStateEvent; }; + AppEventType Type{ AppEventType::None }; EventHandler Handler; }; diff --git a/examples/lighting-app/nrfconnect/main/include/AppTask.h b/examples/lighting-app/nrfconnect/main/include/AppTask.h index a1772735cedbf8..461981e44835f7 100644 --- a/examples/lighting-app/nrfconnect/main/include/AppTask.h +++ b/examples/lighting-app/nrfconnect/main/include/AppTask.h @@ -44,70 +44,57 @@ struct Identify; class AppTask { public: + static AppTask & Instance() + { + static AppTask sAppTask; + return sAppTask; + }; + CHIP_ERROR StartApp(); - void PostLightingActionRequest(PWMDevice::Action_t aAction); - void PostEvent(AppEvent * event); void UpdateClusterState(); + PWMDevice & GetPWMDevice() { return mPWMDevice; } static void IdentifyStartHandler(Identify *); static void IdentifyStopHandler(Identify *); - PWMDevice & GetLightingDevice() { return mPWMDevice; } private: #ifdef CONFIG_CHIP_PW_RPC friend class chip::rpc::NrfButton; #endif - friend AppTask & GetAppTask(void); CHIP_ERROR Init(); - static void ActionInitiated(PWMDevice::Action_t aAction, int32_t aActor); - static void ActionCompleted(PWMDevice::Action_t aAction, int32_t aActor); + void CancelTimer(); + void StartTimer(uint32_t timeoutInMs); - void CancelTimer(void); + static void PostEvent(const AppEvent & event); + static void DispatchEvent(const AppEvent & event); + static void FunctionTimerEventHandler(const AppEvent & event); + static void LightingActionEventHandler(const AppEvent & event); + static void StartBLEAdvertisementHandler(const AppEvent & event); + static void UpdateLedStateEventHandler(const AppEvent & event); - void DispatchEvent(AppEvent * event); + static void ChipEventHandler(const chip::DeviceLayer::ChipDeviceEvent * event, intptr_t arg); + static void ButtonEventHandler(uint32_t buttonState, uint32_t hasChanged); + static void FunctionTimerTimeoutCallback(k_timer * timer); + static void ActionInitiated(PWMDevice::Action_t action, int32_t actor); + static void ActionCompleted(PWMDevice::Action_t action, int32_t actor); static void UpdateStatusLED(); static void LEDStateUpdateHandler(LEDWidget & ledWidget); - static void UpdateLedStateEventHandler(AppEvent * aEvent); - static void FunctionTimerEventHandler(AppEvent * aEvent); - static void FunctionHandler(AppEvent * aEvent); - static void LightingActionEventHandler(AppEvent * aEvent); - static void StartBLEAdvertisementHandler(AppEvent * aEvent); - - static void ChipEventHandler(const chip::DeviceLayer::ChipDeviceEvent * event, intptr_t arg); - - static void ButtonEventHandler(uint32_t button_state, uint32_t has_changed); - static void TimerEventHandler(k_timer * timer); + static void FunctionHandler(const AppEvent & event); + static void StartBLEAdvertisementAndLightActionEventHandler(const AppEvent & event); #ifdef CONFIG_MCUMGR_SMP_BT static void RequestSMPAdvertisingStart(void); #endif - void StartTimer(uint32_t aTimeoutInMs); - - enum Function_t - { - kFunction_NoneSelected = 0, - kFunction_SoftwareUpdate = 0, - kFunction_FactoryReset, - - kFunction_Invalid - }; - - Function_t mFunction = kFunction_NoneSelected; + FunctionEvent mFunction = FunctionEvent::NoneSelected; bool mFunctionTimerActive = false; PWMDevice mPWMDevice; - static AppTask sAppTask; #if CONFIG_CHIP_FACTORY_DATA chip::DeviceLayer::FactoryDataProvider mFactoryDataProvider; #endif }; - -inline AppTask & GetAppTask(void) -{ - return AppTask::sAppTask; -} diff --git a/examples/lighting-app/nrfconnect/main/main.cpp b/examples/lighting-app/nrfconnect/main/main.cpp index 9a487fcc8c2895..69e83a6935d05a 100644 --- a/examples/lighting-app/nrfconnect/main/main.cpp +++ b/examples/lighting-app/nrfconnect/main/main.cpp @@ -31,7 +31,7 @@ #include #endif -LOG_MODULE_REGISTER(app, CONFIG_MATTER_LOG_LEVEL); +LOG_MODULE_REGISTER(app, CONFIG_CHIP_APP_LOG_LEVEL); using namespace ::chip; @@ -73,7 +73,7 @@ int main() if (err == CHIP_NO_ERROR) { - err = GetAppTask().StartApp(); + err = AppTask::Instance().StartApp(); } LOG_ERR("Exited with code %" CHIP_ERROR_FORMAT, err.Format()); diff --git a/examples/lighting-app/nrfconnect/prj.conf b/examples/lighting-app/nrfconnect/prj.conf index 4bbb8a5a2a7076..1a796988571cf5 100644 --- a/examples/lighting-app/nrfconnect/prj.conf +++ b/examples/lighting-app/nrfconnect/prj.conf @@ -14,32 +14,35 @@ # limitations under the License. # -CONFIG_CHIP=y -CONFIG_STD_CPP14=y - # This sample uses Kconfig.defaults to set options common for all # samples. This file should contain only options specific for this sample # or overrides of default values. +# Enable CHIP +CONFIG_CHIP=y +CONFIG_CHIP_PROJECT_CONFIG="main/include/CHIPProjectConfig.h" +# 32773 == 0x8005 (example lighting-app) +CONFIG_CHIP_DEVICE_PRODUCT_ID=32773 +CONFIG_STD_CPP14=y + +# Enable CHIP pairing automatically on application start. +CONFIG_CHIP_ENABLE_PAIRING_AUTOSTART=y + # Add support for LEDs and buttons on Nordic development kits CONFIG_DK_LIBRARY=y CONFIG_PWM=y -# OpenThread configs -CONFIG_OPENTHREAD_NORDIC_LIBRARY_FTD=y +# General networking settings +CONFIG_NET_IF_MCAST_IPV6_ADDR_COUNT=14 + -# Bluetooth overrides +# Bluetooth Low Energy configuration CONFIG_BT_DEVICE_NAME="MatterLight" -# Additional configs for debbugging experience. +# Stack size settings +CONFIG_IEEE802154_NRF5_RX_STACK_SIZE=1024 + +# Other settings CONFIG_THREAD_NAME=y CONFIG_MPU_STACK_GUARD=y CONFIG_RESET_ON_FATAL_ERROR=n - -# CHIP configuration -CONFIG_CHIP_PROJECT_CONFIG="main/include/CHIPProjectConfig.h" -# 32773 == 0x8005 (example lighting-app) -CONFIG_CHIP_DEVICE_PRODUCT_ID=32773 - -# Enable CHIP pairing automatically on application start. -CONFIG_CHIP_ENABLE_PAIRING_AUTOSTART=y diff --git a/examples/lighting-app/nrfconnect/prj_no_dfu.conf b/examples/lighting-app/nrfconnect/prj_no_dfu.conf index 0e286be102e3fc..7fc72e8fcbd704 100644 --- a/examples/lighting-app/nrfconnect/prj_no_dfu.conf +++ b/examples/lighting-app/nrfconnect/prj_no_dfu.conf @@ -14,38 +14,40 @@ # limitations under the License. # -CONFIG_CHIP=y -CONFIG_STD_CPP14=y - # This sample uses Kconfig.defaults to set options common for all # samples. This file should contain only options specific for this sample # or overrides of default values. +# Enable CHIP +CONFIG_CHIP=y +CONFIG_CHIP_PROJECT_CONFIG="main/include/CHIPProjectConfig.h" +# 32773 == 0x8005 (example lighting-app) +CONFIG_CHIP_DEVICE_PRODUCT_ID=32773 +CONFIG_STD_CPP14=y + +# Enable CHIP pairing automatically on application start. +CONFIG_CHIP_ENABLE_PAIRING_AUTOSTART=y + # Add support for LEDs and buttons on Nordic development kits CONFIG_DK_LIBRARY=y CONFIG_PWM=y -# OpenThread configs -CONFIG_OPENTHREAD_NORDIC_LIBRARY_FTD=y +# General networking settings +CONFIG_NET_IF_MCAST_IPV6_ADDR_COUNT=14 -# Bluetooth overrides +# Bluetooth Low Energy configuration CONFIG_BT_DEVICE_NAME="MatterLight" -# Additional configs for debbugging experience. +# Other settings CONFIG_THREAD_NAME=y CONFIG_MPU_STACK_GUARD=y CONFIG_RESET_ON_FATAL_ERROR=n +# Stack size settings +CONFIG_IEEE802154_NRF5_RX_STACK_SIZE=1024 + # Disable Matter OTA DFU CONFIG_CHIP_OTA_REQUESTOR=n # Disable QSPI NOR CONFIG_CHIP_QSPI_NOR=n - -# CHIP configuration -CONFIG_CHIP_PROJECT_CONFIG="main/include/CHIPProjectConfig.h" -# 32773 == 0x8005 (example lighting-app) -CONFIG_CHIP_DEVICE_PRODUCT_ID=32773 - -# Enable CHIP pairing automatically on application start. -CONFIG_CHIP_ENABLE_PAIRING_AUTOSTART=y diff --git a/examples/lighting-app/nrfconnect/prj_release.conf b/examples/lighting-app/nrfconnect/prj_release.conf index d5302def6aa3bd..5ff25156b6861b 100644 --- a/examples/lighting-app/nrfconnect/prj_release.conf +++ b/examples/lighting-app/nrfconnect/prj_release.conf @@ -14,33 +14,35 @@ # limitations under the License. # -CONFIG_CHIP=y -CONFIG_STD_CPP14=y - # This sample uses Kconfig.defaults to set options common for all # samples. This file should contain only options specific for this sample # or overrides of default values. +# Enable CHIP +CONFIG_CHIP=y +CONFIG_CHIP_PROJECT_CONFIG="main/include/CHIPProjectConfig.h" +# 32773 == 0x8005 (example lighting-app) +CONFIG_CHIP_DEVICE_PRODUCT_ID=32773 +CONFIG_STD_CPP14=y + +# Enable CHIP pairing automatically on application start. +CONFIG_CHIP_ENABLE_PAIRING_AUTOSTART=y + # Add support for LEDs and buttons on Nordic development kits CONFIG_DK_LIBRARY=y CONFIG_PWM=y -# OpenThread configs -CONFIG_OPENTHREAD_NORDIC_LIBRARY_FTD=y +# General networking settings +CONFIG_NET_IF_MCAST_IPV6_ADDR_COUNT=14 -# Bluetooth overrides +# Bluetooth Low Energy configuration CONFIG_BT_DEVICE_NAME="MatterLight" # Enable system reset on fatal error CONFIG_RESET_ON_FATAL_ERROR=y -# CHIP configuration -CONFIG_CHIP_PROJECT_CONFIG="main/include/CHIPProjectConfig.h" -# 32773 == 0x8005 (example lighting-app) -CONFIG_CHIP_DEVICE_PRODUCT_ID=32773 - -# Enable CHIP pairing automatically on application start. -CONFIG_CHIP_ENABLE_PAIRING_AUTOSTART=y +# Stack size settings +CONFIG_IEEE802154_NRF5_RX_STACK_SIZE=1024 # Disable all debug features CONFIG_SHELL=n @@ -53,4 +55,4 @@ CONFIG_LOG_MODE_MINIMAL=n CONFIG_ASSERT_VERBOSE=n CONFIG_ASSERT_NO_FILE_INFO=y CONFIG_PRINTK=n -CONFIG_THREAD_NAME=n +CONFIG_PRINTK_SYNC=n diff --git a/examples/lock-app/nrfconnect/CMakeLists.txt b/examples/lock-app/nrfconnect/CMakeLists.txt index aa932382b8fe10..5c9bd36fbc31c6 100644 --- a/examples/lock-app/nrfconnect/CMakeLists.txt +++ b/examples/lock-app/nrfconnect/CMakeLists.txt @@ -24,6 +24,7 @@ include(${CHIP_ROOT}/config/nrfconnect/app/check-nrfconnect-version.cmake) # Set Kconfig root files that will be processed as a first Kconfig for used child images. set(mcuboot_KCONFIG_ROOT ${CHIP_ROOT}/config/nrfconnect/chip-module/Kconfig.mcuboot.root) set(multiprotocol_rpmsg_KCONFIG_ROOT ${CHIP_ROOT}/config/nrfconnect/chip-module/Kconfig.multiprotocol_rpmsg.root) +set(hci_rpmsg_KCONFIG_ROOT ${CHIP_ROOT}/config/nrfconnect/chip-module/Kconfig.hci_rpmsg.root) if(NOT CONF_FILE STREQUAL "prj_no_dfu.conf") set(PM_STATIC_YML_FILE ${CMAKE_CURRENT_SOURCE_DIR}/configuration/${BOARD}/pm_static_dfu.yml) diff --git a/examples/lock-app/nrfconnect/Kconfig b/examples/lock-app/nrfconnect/Kconfig index e9dfd7de6eff89..98ed2d5ce9a9b8 100644 --- a/examples/lock-app/nrfconnect/Kconfig +++ b/examples/lock-app/nrfconnect/Kconfig @@ -35,6 +35,23 @@ config STATE_LEDS the device into a network or the factory reset initiation. Note that setting this option to 'n' does not disable the LED indicating the state of the simulated bolt. +# Sample configuration used for Thread networking +if NET_L2_OPENTHREAD + +choice OPENTHREAD_NORDIC_LIBRARY_CONFIGURATION + default OPENTHREAD_NORDIC_LIBRARY_MTD +endchoice + +choice OPENTHREAD_DEVICE_TYPE + default OPENTHREAD_MTD +endchoice + +config CHIP_ENABLE_SLEEPY_END_DEVICE_SUPPORT + bool + default y + +endif # NET_L2_OPENTHREAD + rsource "../../../config/nrfconnect/chip-module/Kconfig.features" rsource "../../../config/nrfconnect/chip-module/Kconfig.defaults" source "Kconfig.zephyr" diff --git a/examples/lock-app/nrfconnect/README.md b/examples/lock-app/nrfconnect/README.md index 1ed12be7e8ce79..050d5a7e0389d9 100644 --- a/examples/lock-app/nrfconnect/README.md +++ b/examples/lock-app/nrfconnect/README.md @@ -12,12 +12,15 @@ a reference for creating your own application. The example is based on [Matter](https://github.com/project-chip/connectedhomeip) and Nordic -Semiconductor's nRF Connect SDK, and supports remote access and control of a -simulated door lock over a low-power, 802.15.4 Thread network. +Semiconductor's nRF Connect SDK, and was created to facilitate testing and +certification of a Matter device communicating over a low-power, 802.15.4 Thread +network, or Wi-Fi network. The example behaves as a Matter accessory, that is a device that can be paired -into an existing Matter network and can be controlled by this network. The -device works as a Thread Sleepy End Device. +into an existing Matter network and can be controlled by this network. In the +case of Thread, this device works as a Thread Sleepy End Device. Support for +both Thread and Wi-Fi is mutually exclusive and depends on the hardware +platform, so only one protocol can be supported for a specific lock device.
@@ -27,6 +30,7 @@ device works as a Thread Sleepy End Device. - [Device Firmware Upgrade](#device-firmware-upgrade) - [Requirements](#requirements) - [Supported devices](#supported_devices) + - [IPv6 network support](#ipv6-network-support) - [Device UI](#device-ui) - [Setting up the environment](#setting-up-the-environment) - [Using Docker container for setup](#using-docker-container-for-setup) @@ -57,20 +61,32 @@ and [Zephyr RTOS](https://zephyrproject.org/). Visit Matter's [nRF Connect platform overview](../../../docs/guides/nrfconnect_platform_overview.md) to read more about the platform structure and dependencies. -The Matter device that runs the lock application is controlled by the Matter -controller device over the Thread protocol. By default, the Matter device has -Thread disabled, and it should be paired with Matter controller and get -configuration from it. Some actions required before establishing full -communication are described below. +By default, the Matter accessory device has IPv6 networking disabled. You must +pair it with the Matter controller over Bluetooth® LE to get the configuration +from the controller to use the device within a Thread or Wi-Fi network. You have +to make the device discoverable manually (for security reasons). See +[Bluetooth LE advertising](#bluetooth-le-advertising) to learn how to do this. +The controller must get the commissioning information from the Matter accessory +device and provision the device into the network. -The example can be configured to use the secure bootloader and utilize it for -performing over-the-air Device Firmware Upgrade using Bluetooth LE. +The sample uses buttons for changing the lock and device states, and LEDs to +show the state of these changes. You can test it in the following ways: + +- Standalone, using a single DK that runs the door lock application. + +- Remotely over the Thread or the Wi-Fi protocol, which in either case + requires more devices, including a Matter controller that you can configure + either on a PC or a mobile device. ### Bluetooth LE advertising In this example, to commission the device onto a Matter network, it must be discoverable over Bluetooth LE. For security reasons, you must start Bluetooth -LE advertising manually after powering up the device by pressing **Button 4**. +LE advertising manually after powering up the device by pressing: + +- On nRF52840 DK, nRF5340 DK, and nRF21540 DK: **Button 4**. + +- On nRF7002 DK: **Button 2**. ### Bluetooth LE rendezvous @@ -80,16 +96,16 @@ commissioner role. To start the rendezvous, the controller must get the commissioning information from the Matter device. The data payload is encoded within a QR code, printed to -the UART console, and shared using an NFC tag. NFC tag emulation starts -automatically when Bluetooth LE advertising is started and stays enabled until -Bluetooth LE advertising timeout expires. +the UART console, and shared using an NFC tag. The emulation of the NFC tag +emulation starts automatically when Bluetooth LE advertising is started and +stays enabled until Bluetooth LE advertising timeout expires. -#### Thread provisioning +#### Thread or Wi-Fi provisioning -Last part of the rendezvous procedure, the provisioning operation involves -sending the Thread network credentials from the Matter controller to the Matter -device. As a result, device is able to join the Thread network and communicate -with other Thread devices in the network. +The provisioning operation, which is the Last part of the rendezvous procedure, +involves sending the Thread or Wi-Fi network credentials from the Matter +controller to the Matter device. As a result, the device joins the Thread or +Wi-Fi network and can communicate with other devices in the network. ### Device Firmware Upgrade @@ -164,13 +180,23 @@ more information. The example supports building and running on the following devices: -| Hardware platform | Build target | Platform image | -| ----------------------------------------------------------------------------------------- | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | -| [nRF52840 DK](https://www.nordicsemi.com/Software-and-Tools/Development-Kits/nRF52840-DK) | `nrf52840dk_nrf52840` |
nRF52840 DKnRF52840 DK
| -| [nRF5340 DK](https://www.nordicsemi.com/Software-and-Tools/Development-Kits/nRF5340-DK) | `nrf5340dk_nrf5340_cpuapp` |
nRF5340 DKnRF5340 DK
| +| Hardware platform | Build target | Platform image | +| --------------------------------------------------------------------------------------------------------------- | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | +| [nRF52840 DK](https://www.nordicsemi.com/Software-and-Tools/Development-Kits/nRF52840-DK) | `nrf52840dk_nrf52840` |
nRF52840 DKnRF52840 DK
| +| [nRF5340 DK](https://www.nordicsemi.com/Software-and-Tools/Development-Kits/nRF5340-DK) | `nrf5340dk_nrf5340_cpuapp` |
nRF5340 DKnRF5340 DK
| +| [nRF7002 DK](https://developer.nordicsemi.com/nRF_Connect_SDK/doc/latest/nrf/ug_nrf7002.html#nrf7002dk-nrf5340) | `nrf7002dk_nrf5340_cpuapp` |
nRF7002DKnRF7002 DK
|
+### IPv6 network support + +The development kits for this sample offer the following IPv6 network support +for Matter: + +- Matter over Thread is supported for `nrf52840dk_nrf52840` and + `nrf5340dk_nrf5340_cpuapp`. +- Matter over Wi-Fi is supported for `nrf7002dk_nrf5340_cpuapp`. + ## Device UI @@ -191,11 +217,10 @@ following states are possible: Bluetooth LE. - _Short Flash Off (950ms on/50ms off)_ — The device is fully - provisioned, but does not yet have full Thread network or service - connectivity. + provisioned, but does not yet have full connectivity for Thread or Wi-Fi + network. -- _Solid On_ — The device is fully provisioned and has full Thread - network and service connectivity. +- _Solid On_ — The device is fully provisioned. **LED 2** simulates the lock bolt and shows the state of the lock. The following states are possible: @@ -207,25 +232,46 @@ states are possible: - _Rapid Even Flashing (100 ms on/100 ms off during 2 s)_ — The simulated bolt is in motion from one position to another. -**Button 1** can be used for the following purposes: + Additionally, the LED starts blinking evenly (500 ms on/500 ms off) when the + Identify command of the Identify cluster is received on the endpoint 1. The + command’s argument can be used to specify the duration of the effect. -- _Pressed for 6 s_ — Initiates the factory reset of the device. - Releasing the button within the 6-second window cancels the factory reset - procedure. **LEDs 1-4** blink in unison when the factory reset procedure is - initiated. +**Button 1** can be used for the following purposes: - _Pressed for less than 3 s_ — Initiates the OTA software update process. This feature is disabled by default, but can be enabled by following the [Building with Device Firmware Upgrade support](#building-with-device-firmware-upgrade-support) - instruction. + instructions. + +- _Pressed for more than 3 s_ — initiates the factory reset of the + device. Releasing the button within the 3-second window cancels the factory + reset procedure. **Button 2** — Pressing the button once changes the lock state to the opposite one. -**Button 4** — Pressing the button once starts the NFC tag emulation and -enables Bluetooth LE advertising for the predefined period of time (15 minutes -by default). +- On nRF52840 DK, nRF5340 DK, and nRF21540 DK: Changes the lock state to the + opposite one. + +- On nRF7002 DK: + + - If pressed for less than three seconds, it changes the lock state to the + opposite one. + + - If pressed for more than three seconds, it starts the NFC tag emulation, + enables Bluetooth LE advertising for the predefined period of time (15 + minutes by default), and makes the device discoverable over Bluetooth + LE. + +**Button 4**: + +- On nRF52840 DK, nRF5340 DK, and nRF21540 DK: Starts the NFC tag emulation, + enables Bluetooth LE advertising for the predefined period of time (15 + minutes by default), and makes the device discoverable over Bluetooth LE. + This button is used during the commissioning procedure. + +- On nRF7002 DK: Not available. **SEGGER J-Link USB port** can be used to get logs from the device or communicate with it using the @@ -503,7 +549,8 @@ learn how to use command-line interface of the application. Read the [CHIP Tool user guide](../../../docs/guides/chip_tool_guide.md) to see how to use [CHIP Tool for Linux or mac OS](../../chip-tool/README.md) to -commission and control the application within a Matter-enabled Thread network. +commission and control the application within a Matter-enabled Thread or Wi-Fi +network. ### Testing using Android CHIPTool @@ -511,7 +558,7 @@ Read the [Android commissioning guide](../../../docs/guides/nrfconnect_android_commissioning.md) to see how to use [CHIPTool](../../../examples/android/CHIPTool/README.md) for Android smartphones to commission and control the application within a -Matter-enabled Thread network. +Matter-enabled Thread or Wi-Fi network. ### Testing Device Firmware Upgrade diff --git a/examples/lock-app/nrfconnect/boards/nrf52840dk_nrf52840.overlay b/examples/lock-app/nrfconnect/boards/nrf52840dk_nrf52840.overlay index a4bac9fffc2f9d..b211bc4f356ff5 100644 --- a/examples/lock-app/nrfconnect/boards/nrf52840dk_nrf52840.overlay +++ b/examples/lock-app/nrfconnect/boards/nrf52840dk_nrf52840.overlay @@ -37,9 +37,6 @@ &uart1 { status = "disabled"; }; -&gpio1 { - status = "disabled"; -}; &i2c0 { status = "disabled"; }; diff --git a/examples/lock-app/nrfconnect/boards/nrf7002dk_nrf5340_cpuapp.overlay b/examples/lock-app/nrfconnect/boards/nrf7002dk_nrf5340_cpuapp.overlay new file mode 100644 index 00000000000000..3063fbbcdee779 --- /dev/null +++ b/examples/lock-app/nrfconnect/boards/nrf7002dk_nrf5340_cpuapp.overlay @@ -0,0 +1,21 @@ +/* + * 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. + */ + +/ { + chosen { + nordic,pm-ext-flash = &mx25r64; + }; +}; diff --git a/examples/lock-app/nrfconnect/child_image/hci_rpmsg/prj.conf b/examples/lock-app/nrfconnect/child_image/hci_rpmsg/prj.conf new file mode 100644 index 00000000000000..1622ffd00dbb91 --- /dev/null +++ b/examples/lock-app/nrfconnect/child_image/hci_rpmsg/prj.conf @@ -0,0 +1,25 @@ +# +# 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. +# + +# This target uses Kconfig.hci_rpmsg.defaults to set options common for all +# samples using hci_rpmsg. This file should contain only options specific for this sample +# hci_rpmsg configuration or overrides of default values. + +# Disable not used modules that cannot be set in Kconfig.hci_rpmsg.defaults due to overriding +# in board files. + +CONFIG_SERIAL=n +CONFIG_UART_CONSOLE=n diff --git a/examples/lock-app/nrfconnect/child_image/hci_rpmsg/prj_no_dfu.conf b/examples/lock-app/nrfconnect/child_image/hci_rpmsg/prj_no_dfu.conf new file mode 100644 index 00000000000000..1622ffd00dbb91 --- /dev/null +++ b/examples/lock-app/nrfconnect/child_image/hci_rpmsg/prj_no_dfu.conf @@ -0,0 +1,25 @@ +# +# 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. +# + +# This target uses Kconfig.hci_rpmsg.defaults to set options common for all +# samples using hci_rpmsg. This file should contain only options specific for this sample +# hci_rpmsg configuration or overrides of default values. + +# Disable not used modules that cannot be set in Kconfig.hci_rpmsg.defaults due to overriding +# in board files. + +CONFIG_SERIAL=n +CONFIG_UART_CONSOLE=n diff --git a/examples/lock-app/nrfconnect/child_image/hci_rpmsg/prj_release.conf b/examples/lock-app/nrfconnect/child_image/hci_rpmsg/prj_release.conf new file mode 100644 index 00000000000000..1622ffd00dbb91 --- /dev/null +++ b/examples/lock-app/nrfconnect/child_image/hci_rpmsg/prj_release.conf @@ -0,0 +1,25 @@ +# +# 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. +# + +# This target uses Kconfig.hci_rpmsg.defaults to set options common for all +# samples using hci_rpmsg. This file should contain only options specific for this sample +# hci_rpmsg configuration or overrides of default values. + +# Disable not used modules that cannot be set in Kconfig.hci_rpmsg.defaults due to overriding +# in board files. + +CONFIG_SERIAL=n +CONFIG_UART_CONSOLE=n diff --git a/examples/lock-app/nrfconnect/child_image/mcuboot/prj.conf b/examples/lock-app/nrfconnect/child_image/mcuboot/prj.conf index 287c7829c6a5cf..0dbf7106e88e22 100644 --- a/examples/lock-app/nrfconnect/child_image/mcuboot/prj.conf +++ b/examples/lock-app/nrfconnect/child_image/mcuboot/prj.conf @@ -23,7 +23,6 @@ CONFIG_MBEDTLS_CFG_FILE="mcuboot-mbedtls-cfg.h" # Bootloader size optimization # Disable not used modules that cannot be set in Kconfig.mcuboot.defaults due to overriding # in board files. -CONFIG_GPIO=n CONFIG_CONSOLE=n CONFIG_SERIAL=n CONFIG_UART_CONSOLE=n diff --git a/examples/lock-app/nrfconnect/child_image/mcuboot/prj_release.conf b/examples/lock-app/nrfconnect/child_image/mcuboot/prj_release.conf index 287c7829c6a5cf..0dbf7106e88e22 100644 --- a/examples/lock-app/nrfconnect/child_image/mcuboot/prj_release.conf +++ b/examples/lock-app/nrfconnect/child_image/mcuboot/prj_release.conf @@ -23,7 +23,6 @@ CONFIG_MBEDTLS_CFG_FILE="mcuboot-mbedtls-cfg.h" # Bootloader size optimization # Disable not used modules that cannot be set in Kconfig.mcuboot.defaults due to overriding # in board files. -CONFIG_GPIO=n CONFIG_CONSOLE=n CONFIG_SERIAL=n CONFIG_UART_CONSOLE=n diff --git a/examples/lock-app/nrfconnect/configuration/nrf7002dk_nrf5340_cpuapp/pm_static_dfu.yml b/examples/lock-app/nrfconnect/configuration/nrf7002dk_nrf5340_cpuapp/pm_static_dfu.yml new file mode 100644 index 00000000000000..3c56dc0ddb1968 --- /dev/null +++ b/examples/lock-app/nrfconnect/configuration/nrf7002dk_nrf5340_cpuapp/pm_static_dfu.yml @@ -0,0 +1,56 @@ +mcuboot: + address: 0x0 + size: 0xC000 + region: flash_primary +mcuboot_pad: + address: 0xC000 + size: 0x200 +app: + address: 0xC200 + size: 0xeee00 +mcuboot_primary: + orig_span: &id001 + - mcuboot_pad + - app + span: *id001 + address: 0xC000 + size: 0xef000 + region: flash_primary +mcuboot_primary_app: + orig_span: &id002 + - app + span: *id002 + address: 0xC200 + size: 0xeee00 +factory_data: + address: 0xfb000 + size: 0x1000 + region: flash_primary +settings_storage: + address: 0xfc000 + size: 0x4000 + region: flash_primary +mcuboot_primary_1: + address: 0x0 + size: 0x40000 + device: flash_ctrl + region: ram_flash +mcuboot_secondary: + address: 0x0 + size: 0xef000 + device: MX25R64 + region: external_flash +mcuboot_secondary_1: + address: 0xef000 + size: 0x40000 + device: MX25R64 + region: external_flash +external_flash: + address: 0x12f000 + size: 0x6D1000 + device: MX25R64 + region: external_flash +pcd_sram: + address: 0x20000000 + size: 0x2000 + region: sram_primary diff --git a/examples/lock-app/nrfconnect/main/AppTask.cpp b/examples/lock-app/nrfconnect/main/AppTask.cpp index b7d4d02a42e75c..4f5c18d869a73e 100644 --- a/examples/lock-app/nrfconnect/main/AppTask.cpp +++ b/examples/lock-app/nrfconnect/main/AppTask.cpp @@ -19,6 +19,7 @@ #include "AppTask.h" #include "AppConfig.h" #include "BoltLockManager.h" +#include "LEDUtil.h" #include "LEDWidget.h" #include @@ -27,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -37,6 +39,11 @@ #include #include +#ifdef CONFIG_CHIP_WIFI +#include +#include +#endif + #if CONFIG_CHIP_OTA_REQUESTOR #include "OTAUtil.h" #endif @@ -45,44 +52,66 @@ #include #include +LOG_MODULE_DECLARE(app, CONFIG_MATTER_LOG_LEVEL); + using namespace ::chip; using namespace ::chip::app; using namespace ::chip::app::Clusters::DoorLock; using namespace ::chip::Credentials; using namespace ::chip::DeviceLayer; -#define FACTORY_RESET_TRIGGER_TIMEOUT 3000 -#define FACTORY_RESET_CANCEL_WINDOW_TIMEOUT 3000 -#define APP_EVENT_QUEUE_SIZE 10 -#define BUTTON_PUSH_EVENT 1 -#define BUTTON_RELEASE_EVENT 0 - namespace { -constexpr EndpointId kLockEndpointId = 1; +constexpr uint32_t kFactoryResetTriggerTimeout = 3000; +constexpr uint32_t kFactoryResetCancelWindowTimeout = 3000; +constexpr size_t kAppEventQueueSize = 10; +constexpr EndpointId kLockEndpointId = 1; +#if NUMBER_OF_BUTTONS == 2 +constexpr uint32_t kAdvertisingTriggerTimeout = 3000; +#endif // NOTE! This key is for test/certification only and should not be available in production devices! // If CONFIG_CHIP_FACTORY_DATA is enabled, this value is read from the factory data. uint8_t sTestEventTriggerEnableKey[TestEventTriggerDelegate::kEnableKeyLength] = { 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff }; -LOG_MODULE_DECLARE(app, CONFIG_MATTER_LOG_LEVEL); -K_MSGQ_DEFINE(sAppEventQueue, sizeof(AppEvent), APP_EVENT_QUEUE_SIZE, alignof(AppEvent)); +K_MSGQ_DEFINE(sAppEventQueue, sizeof(AppEvent), kAppEventQueueSize, alignof(AppEvent)); k_timer sFunctionTimer; +Identify sIdentify = { kLockEndpointId, AppTask::IdentifyStartHandler, AppTask::IdentifyStopHandler, + EMBER_ZCL_IDENTIFY_IDENTIFY_TYPE_VISIBLE_LED }; + LEDWidget sStatusLED; LEDWidget sLockLED; -LEDWidget sUnusedLED; -LEDWidget sUnusedLED_1; +#if NUMBER_OF_LEDS == 4 +FactoryResetLEDsWrapper<2> sFactoryResetLEDs{ { FACTORY_RESET_SIGNAL_LED, FACTORY_RESET_SIGNAL_LED1 } }; +#endif -bool sIsThreadProvisioned = false; -bool sIsThreadEnabled = false; -bool sHaveBLEConnections = false; +bool sIsNetworkProvisioned = false; +bool sIsNetworkEnabled = false; +bool sHaveBLEConnections = false; chip::DeviceLayer::DeviceInfoProviderImpl gExampleDeviceInfoProvider; - } // namespace -AppTask AppTask::sAppTask; +namespace LedConsts { +constexpr uint32_t kBlinkRate_ms{ 500 }; +constexpr uint32_t kIdentifyBlinkRate_ms{ 500 }; +namespace StatusLed { +namespace Unprovisioned { +constexpr uint32_t kOn_ms{ 100 }; +constexpr uint32_t kOff_ms{ kOn_ms }; +} // namespace Unprovisioned +namespace Provisioned { +constexpr uint32_t kOn_ms{ 50 }; +constexpr uint32_t kOff_ms{ 950 }; +} // namespace Provisioned + +} // namespace StatusLed +} // namespace LedConsts + +#ifdef CONFIG_CHIP_WIFI +app::Clusters::NetworkCommissioning::Instance sWiFiCommissioningInstance(0, &(NetworkCommissioning::NrfWiFiDriver::Instance())); +#endif CHIP_ERROR AppTask::Init() { @@ -121,9 +150,11 @@ CHIP_ERROR AppTask::Init() LOG_ERR("ConnectivityMgr().SetThreadDeviceType() failed"); return err; } +#elif defined(CONFIG_CHIP_WIFI) + sWiFiCommissioningInstance.Init(); #else return CHIP_ERROR_INTERNAL; -#endif +#endif // CONFIG_NET_L2_OPENTHREAD // Initialize LEDs LEDWidget::InitGpio(); @@ -133,13 +164,8 @@ CHIP_ERROR AppTask::Init() sLockLED.Init(LOCK_STATE_LED); sLockLED.Set(BoltLockMgr().IsLocked()); - sUnusedLED.Init(DK_LED3); - sUnusedLED_1.Init(DK_LED4); - UpdateStatusLED(); - BoltLockMgr().Init(LockStateChanged); - // Initialize buttons int ret = dk_buttons_init(ButtonEventHandler); if (ret) @@ -149,7 +175,7 @@ CHIP_ERROR AppTask::Init() } // Initialize function button timer - k_timer_init(&sFunctionTimer, &AppTask::TimerEventHandler, nullptr); + k_timer_init(&sFunctionTimer, &AppTask::FunctionTimerTimeoutCallback, nullptr); k_timer_user_data_set(&sFunctionTimer, this); #ifdef CONFIG_MCUMGR_SMP_BT @@ -158,6 +184,8 @@ CHIP_ERROR AppTask::Init() GetDFUOverSMP().ConfirmNewImage(); #endif + BoltLockMgr().Init(LockStateChanged); + // Initialize CHIP server #if CONFIG_CHIP_FACTORY_DATA ReturnErrorOnFailure(mFactoryDataProvider.Init()); @@ -213,13 +241,55 @@ CHIP_ERROR AppTask::StartApp() while (true) { k_msgq_get(&sAppEventQueue, &event, K_FOREVER); - DispatchEvent(&event); + DispatchEvent(event); } return CHIP_NO_ERROR; } -void AppTask::LockActionEventHandler(AppEvent * aEvent) +void AppTask::IdentifyStartHandler(Identify *) +{ + AppEvent event; + event.Type = AppEventType::IdentifyStart; + event.Handler = [](const AppEvent &) { sLockLED.Blink(LedConsts::kIdentifyBlinkRate_ms); }; + PostEvent(event); +} + +void AppTask::IdentifyStopHandler(Identify *) +{ + AppEvent event; + event.Type = AppEventType::IdentifyStop; + event.Handler = [](const AppEvent &) { sLockLED.Set(BoltLockMgr().IsLocked()); }; + PostEvent(event); +} + +#if NUMBER_OF_BUTTONS == 2 +void AppTask::StartBLEAdvertisementAndLockActionEventHandler(const AppEvent & event) +{ + if (event.ButtonEvent.Action == static_cast(AppEventType::ButtonPushed)) + { + Instance().StartTimer(kAdvertisingTriggerTimeout); + Instance().mFunction = FunctionEvent::AdvertisingStart; + } + else + { + if (Instance().mFunction == FunctionEvent::AdvertisingStart) + { + Instance().CancelTimer(); + Instance().mFunction = FunctionEvent::NoneSelected; + + AppEvent button_event; + button_event.Type = AppEventType::Button; + button_event.ButtonEvent.PinNo = BLE_ADVERTISEMENT_START_AND_LOCK_BUTTON; + button_event.ButtonEvent.Action = static_cast(AppEventType::ButtonReleased); + button_event.Handler = LockActionEventHandler; + PostEvent(button_event); + } + } +} +#endif + +void AppTask::LockActionEventHandler(const AppEvent & event) { if (BoltLockMgr().IsLocked()) { @@ -231,79 +301,103 @@ void AppTask::LockActionEventHandler(AppEvent * aEvent) } } -void AppTask::ButtonEventHandler(uint32_t button_state, uint32_t has_changed) +void AppTask::ButtonEventHandler(uint32_t buttonState, uint32_t hasChanged) { AppEvent button_event; - button_event.Type = AppEvent::kEventType_Button; + button_event.Type = AppEventType::Button; - if (LOCK_BUTTON_MASK & button_state & has_changed) +#if NUMBER_OF_BUTTONS == 2 + if (BLE_ADVERTISEMENT_START_AND_LOCK_BUTTON_MASK & hasChanged) + { + button_event.ButtonEvent.PinNo = BLE_ADVERTISEMENT_START_AND_LOCK_BUTTON; + button_event.ButtonEvent.Action = + static_cast((BLE_ADVERTISEMENT_START_AND_LOCK_BUTTON_MASK & buttonState) ? AppEventType::ButtonPushed + : AppEventType::ButtonReleased); + button_event.Handler = StartBLEAdvertisementAndLockActionEventHandler; + PostEvent(button_event); + } +#else + if (LOCK_BUTTON_MASK & buttonState & hasChanged) { button_event.ButtonEvent.PinNo = LOCK_BUTTON; - button_event.ButtonEvent.Action = BUTTON_PUSH_EVENT; + button_event.ButtonEvent.Action = static_cast(AppEventType::ButtonPushed); button_event.Handler = LockActionEventHandler; - sAppTask.PostEvent(&button_event); + PostEvent(button_event); } - if (FUNCTION_BUTTON_MASK & has_changed) + if (BLE_ADVERTISEMENT_START_BUTTON_MASK & buttonState & hasChanged) { - button_event.ButtonEvent.PinNo = FUNCTION_BUTTON; - button_event.ButtonEvent.Action = (FUNCTION_BUTTON_MASK & button_state) ? BUTTON_PUSH_EVENT : BUTTON_RELEASE_EVENT; - button_event.Handler = FunctionHandler; - sAppTask.PostEvent(&button_event); + button_event.ButtonEvent.PinNo = BLE_ADVERTISEMENT_START_BUTTON; + button_event.ButtonEvent.Action = static_cast(AppEventType::ButtonPushed); + button_event.Handler = StartBLEAdvertisementHandler; + PostEvent(button_event); } +#endif - if (BLE_ADVERTISEMENT_START_BUTTON_MASK & button_state & has_changed) + if (FUNCTION_BUTTON_MASK & hasChanged) { - button_event.ButtonEvent.PinNo = BLE_ADVERTISEMENT_START_BUTTON; - button_event.ButtonEvent.Action = BUTTON_PUSH_EVENT; - button_event.Handler = StartBLEAdvertisementHandler; - sAppTask.PostEvent(&button_event); + button_event.ButtonEvent.PinNo = FUNCTION_BUTTON; + button_event.ButtonEvent.Action = + static_cast((FUNCTION_BUTTON_MASK & buttonState) ? AppEventType::ButtonPushed : AppEventType::ButtonReleased); + button_event.Handler = FunctionHandler; + PostEvent(button_event); } } -void AppTask::TimerEventHandler(k_timer * timer) +void AppTask::FunctionTimerTimeoutCallback(k_timer * timer) { + if (!timer) + { + return; + } + AppEvent event; - event.Type = AppEvent::kEventType_Timer; + event.Type = AppEventType::Timer; event.TimerEvent.Context = k_timer_user_data_get(timer); event.Handler = FunctionTimerEventHandler; - sAppTask.PostEvent(&event); + PostEvent(event); } -void AppTask::FunctionTimerEventHandler(AppEvent * aEvent) +void AppTask::FunctionTimerEventHandler(const AppEvent & event) { - if (aEvent->Type != AppEvent::kEventType_Timer) + if (event.Type != AppEventType::Timer || !Instance().mFunctionTimerActive) + { return; + } - // If we reached here, the button was held past FACTORY_RESET_TRIGGER_TIMEOUT, initiate factory reset - if (sAppTask.mFunctionTimerActive && sAppTask.mFunction == kFunction_SoftwareUpdate) + // If we reached here, the button was held past kFactoryResetTriggerTimeout, initiate factory reset + if (Instance().mFunction == FunctionEvent::SoftwareUpdate) { - LOG_INF("Factory Reset Triggered. Release button within %ums to cancel.", FACTORY_RESET_TRIGGER_TIMEOUT); + LOG_INF("Factory Reset Triggered. Release button within %ums to cancel.", kFactoryResetTriggerTimeout); - // Start timer for FACTORY_RESET_CANCEL_WINDOW_TIMEOUT to allow user to cancel, if required. - sAppTask.StartTimer(FACTORY_RESET_CANCEL_WINDOW_TIMEOUT); - sAppTask.mFunction = kFunction_FactoryReset; + // Start timer for kFactoryResetCancelWindowTimeout to allow user to cancel, if required. + Instance().StartTimer(kFactoryResetCancelWindowTimeout); + Instance().mFunction = FunctionEvent::FactoryReset; -#ifdef CONFIG_STATE_LEDS - // Turn off all LEDs before starting blink to make sure blink is co-ordinated. + // Turn off all LEDs before starting blink to make sure blink is coordinated. sStatusLED.Set(false); - sLockLED.Set(false); - sUnusedLED_1.Set(false); - sUnusedLED.Set(false); +#if NUMBER_OF_LEDS == 4 + sFactoryResetLEDs.Set(false); +#endif - sStatusLED.Blink(500); - sLockLED.Blink(500); - sUnusedLED.Blink(500); - sUnusedLED_1.Blink(500); + sStatusLED.Blink(LedConsts::kBlinkRate_ms); +#if NUMBER_OF_LEDS == 4 + sFactoryResetLEDs.Blink(LedConsts::kBlinkRate_ms); #endif } - else if (sAppTask.mFunctionTimerActive && sAppTask.mFunction == kFunction_FactoryReset) + else if (Instance().mFunction == FunctionEvent::FactoryReset) { // Actually trigger Factory Reset - sAppTask.mFunction = kFunction_NoneSelected; - + Instance().mFunction = FunctionEvent::NoneSelected; chip::Server::GetInstance().ScheduleFactoryReset(); } + else if (Instance().mFunction == FunctionEvent::AdvertisingStart) + { + // The button was held past kAdvertisingTriggerTimeout, start BLE advertisement if we have 2 buttons UI +#if NUMBER_OF_BUTTONS == 2 + StartBLEAdvertisementHandler(event); +#endif + } } #ifdef CONFIG_MCUMGR_SMP_BT @@ -316,9 +410,9 @@ void AppTask::RequestSMPAdvertisingStart(void) } #endif -void AppTask::FunctionHandler(AppEvent * aEvent) +void AppTask::FunctionHandler(const AppEvent & event) { - if (aEvent->ButtonEvent.PinNo != FUNCTION_BUTTON) + if (event.ButtonEvent.PinNo != FUNCTION_BUTTON) return; // To trigger software update: press the FUNCTION_BUTTON button briefly (< FACTORY_RESET_TRIGGER_TIMEOUT) @@ -326,22 +420,22 @@ void AppTask::FunctionHandler(AppEvent * aEvent) // All LEDs start blinking after FACTORY_RESET_TRIGGER_TIMEOUT to signal factory reset has been initiated. // To cancel factory reset: release the FUNCTION_BUTTON once all LEDs start blinking within the // FACTORY_RESET_CANCEL_WINDOW_TIMEOUT - if (aEvent->ButtonEvent.Action == BUTTON_PUSH_EVENT) + if (event.ButtonEvent.Action == static_cast(AppEventType::ButtonPushed)) { - if (!sAppTask.mFunctionTimerActive && sAppTask.mFunction == kFunction_NoneSelected) + if (!Instance().mFunctionTimerActive && Instance().mFunction == FunctionEvent::NoneSelected) { - sAppTask.StartTimer(FACTORY_RESET_TRIGGER_TIMEOUT); + Instance().StartTimer(kFactoryResetTriggerTimeout); - sAppTask.mFunction = kFunction_SoftwareUpdate; + Instance().mFunction = FunctionEvent::SoftwareUpdate; } } else { // If the button was released before factory reset got initiated, trigger a software update. - if (sAppTask.mFunctionTimerActive && sAppTask.mFunction == kFunction_SoftwareUpdate) + if (Instance().mFunctionTimerActive && Instance().mFunction == FunctionEvent::SoftwareUpdate) { - sAppTask.CancelTimer(); - sAppTask.mFunction = kFunction_NoneSelected; + Instance().CancelTimer(); + Instance().mFunction = FunctionEvent::NoneSelected; #ifdef CONFIG_MCUMGR_SMP_BT GetDFUOverSMP().StartServer(); @@ -349,26 +443,20 @@ void AppTask::FunctionHandler(AppEvent * aEvent) LOG_INF("Software update is disabled"); #endif } - else if (sAppTask.mFunctionTimerActive && sAppTask.mFunction == kFunction_FactoryReset) + else if (Instance().mFunctionTimerActive && Instance().mFunction == FunctionEvent::FactoryReset) { - sUnusedLED.Set(false); - sUnusedLED_1.Set(false); - - // Set lock status LED back to show state of lock. - sLockLED.Set(BoltLockMgr().IsLocked()); - +#if NUMBER_OF_LEDS == 4 + sFactoryResetLEDs.Set(false); +#endif UpdateStatusLED(); - sAppTask.CancelTimer(); - - // Change the function to none selected since factory reset has been canceled. - sAppTask.mFunction = kFunction_NoneSelected; - + Instance().CancelTimer(); + Instance().mFunction = FunctionEvent::NoneSelected; LOG_INF("Factory Reset has been Canceled"); } } } -void AppTask::StartBLEAdvertisementHandler(AppEvent *) +void AppTask::StartBLEAdvertisementHandler(const AppEvent &) { if (Server::GetInstance().GetFabricTable().FabricCount() != 0) { @@ -388,45 +476,45 @@ void AppTask::StartBLEAdvertisementHandler(AppEvent *) } } -void AppTask::UpdateLedStateEventHandler(AppEvent * aEvent) +void AppTask::UpdateLedStateEventHandler(const AppEvent & event) { - if (aEvent->Type == AppEvent::kEventType_UpdateLedState) + if (event.Type == AppEventType::UpdateLedState) { - aEvent->UpdateLedStateEvent.LedWidget->UpdateState(); + event.UpdateLedStateEvent.LedWidget->UpdateState(); } } void AppTask::LEDStateUpdateHandler(LEDWidget & ledWidget) { AppEvent event; - event.Type = AppEvent::kEventType_UpdateLedState; + event.Type = AppEventType::UpdateLedState; event.Handler = UpdateLedStateEventHandler; event.UpdateLedStateEvent.LedWidget = &ledWidget; - sAppTask.PostEvent(&event); + PostEvent(event); } void AppTask::UpdateStatusLED() { #ifdef CONFIG_STATE_LEDS - /* Update the status LED. - * - * If thread and service provisioned, keep the LED On constantly. - * - * If the system has ble connection(s) uptill the stage above, THEN blink the LED at an even - * rate of 100ms. - * - * Otherwise, blink the LED On for a very short time. */ - if (sIsThreadProvisioned && sIsThreadEnabled) + // Update the status LED. + // + // If IPv6 network and service provisioned, keep the LED On constantly. + // + // If the system has BLE connection(s) until the stage above, THEN blink the LED at an even + // rate of 100ms. + // + // Otherwise, blink the LED for a very short time. + if (sIsNetworkProvisioned && sIsNetworkEnabled) { sStatusLED.Set(true); } else if (sHaveBLEConnections) { - sStatusLED.Blink(100, 100); + sStatusLED.Blink(LedConsts::StatusLed::Unprovisioned::kOn_ms, LedConsts::StatusLed::Unprovisioned::kOff_ms); } else { - sStatusLED.Blink(50, 950); + sStatusLED.Blink(LedConsts::StatusLed::Provisioned::kOn_ms, LedConsts::StatusLed::Provisioned::kOff_ms); } #endif } @@ -456,15 +544,27 @@ void AppTask::ChipEventHandler(const ChipDeviceEvent * event, intptr_t /* arg */ sHaveBLEConnections = ConnectivityMgr().NumBLEConnections() != 0; UpdateStatusLED(); break; - case DeviceEventType::kThreadStateChange: - sIsThreadProvisioned = ConnectivityMgr().IsThreadProvisioned(); - sIsThreadEnabled = ConnectivityMgr().IsThreadEnabled(); - UpdateStatusLED(); - break; +#if defined(CONFIG_NET_L2_OPENTHREAD) case DeviceEventType::kDnssdPlatformInitialized: #if CONFIG_CHIP_OTA_REQUESTOR InitBasicOTARequestor(); +#endif // CONFIG_CHIP_OTA_REQUESTOR + break; + case DeviceEventType::kThreadStateChange: + sIsNetworkProvisioned = ConnectivityMgr().IsThreadProvisioned(); + sIsNetworkEnabled = ConnectivityMgr().IsThreadEnabled(); +#elif defined(CONFIG_CHIP_WIFI) + case DeviceEventType::kWiFiConnectivityChange: + sIsNetworkProvisioned = ConnectivityMgr().IsWiFiStationProvisioned(); + sIsNetworkEnabled = ConnectivityMgr().IsWiFiStationEnabled(); +#if CONFIG_CHIP_OTA_REQUESTOR + if (event->WiFiConnectivityChange.Result == kConnectivity_Established) + { + InitBasicOTARequestor(); + } +#endif // CONFIG_CHIP_OTA_REQUESTOR #endif + UpdateStatusLED(); break; default: break; @@ -477,9 +577,9 @@ void AppTask::CancelTimer() mFunctionTimerActive = false; } -void AppTask::StartTimer(uint32_t aTimeoutInMs) +void AppTask::StartTimer(uint32_t timeoutInMs) { - k_timer_start(&sFunctionTimer, K_MSEC(aTimeoutInMs), K_NO_WAIT); + k_timer_start(&sFunctionTimer, K_MSEC(timeoutInMs), K_NO_WAIT); mFunctionTimerActive = true; } @@ -505,22 +605,23 @@ void AppTask::LockStateChanged(BoltLockManager::State state, BoltLockManager::Op break; } - sAppTask.UpdateClusterState(state, source); + // Handle changing attribute state in the application + Instance().UpdateClusterState(state, source); } -void AppTask::PostEvent(AppEvent * aEvent) +void AppTask::PostEvent(const AppEvent & event) { - if (k_msgq_put(&sAppEventQueue, aEvent, K_NO_WAIT)) + if (k_msgq_put(&sAppEventQueue, &event, K_NO_WAIT) != 0) { LOG_INF("Failed to post event to app task event queue"); } } -void AppTask::DispatchEvent(AppEvent * aEvent) +void AppTask::DispatchEvent(const AppEvent & event) { - if (aEvent->Handler) + if (event.Handler) { - aEvent->Handler(aEvent); + event.Handler(event); } else { diff --git a/examples/lock-app/nrfconnect/main/BoltLockManager.cpp b/examples/lock-app/nrfconnect/main/BoltLockManager.cpp index 0a860818b0e5ce..9f82f856063aa6 100644 --- a/examples/lock-app/nrfconnect/main/BoltLockManager.cpp +++ b/examples/lock-app/nrfconnect/main/BoltLockManager.cpp @@ -172,15 +172,20 @@ void BoltLockManager::ActuatorTimerEventHandler(k_timer * timer) // context of the application thread. AppEvent event; - event.Type = AppEvent::kEventType_Timer; + event.Type = AppEventType::Timer; event.TimerEvent.Context = static_cast(k_timer_user_data_get(timer)); event.Handler = BoltLockManager::ActuatorAppEventHandler; - GetAppTask().PostEvent(&event); + AppTask::Instance().PostEvent(event); } -void BoltLockManager::ActuatorAppEventHandler(AppEvent * aEvent) +void BoltLockManager::ActuatorAppEventHandler(const AppEvent & event) { - BoltLockManager * lock = static_cast(aEvent->TimerEvent.Context); + BoltLockManager * lock = static_cast(event.TimerEvent.Context); + + if (!lock) + { + return; + } switch (lock->mState) { diff --git a/examples/lock-app/nrfconnect/main/ZclCallbacks.cpp b/examples/lock-app/nrfconnect/main/ZclCallbacks.cpp index 3b737aa2b472f6..24b6b7709fd9b8 100644 --- a/examples/lock-app/nrfconnect/main/ZclCallbacks.cpp +++ b/examples/lock-app/nrfconnect/main/ZclCallbacks.cpp @@ -123,5 +123,5 @@ void emberAfDoorLockClusterInitCallback(EndpointId endpoint) // (kUsersManagement|kAccessSchedules|kRFIDCredentials|kPINCredentials) 0x113 logOnFailure(DoorLock::Attributes::FeatureMap::Set(endpoint, 0x101), "feature map"); - GetAppTask().UpdateClusterState(BoltLockMgr().GetState(), BoltLockManager::OperationSource::kUnspecified); + AppTask::Instance().UpdateClusterState(BoltLockMgr().GetState(), BoltLockManager::OperationSource::kUnspecified); } diff --git a/examples/lock-app/nrfconnect/main/include/AppConfig.h b/examples/lock-app/nrfconnect/main/include/AppConfig.h index 63e523e1922c80..0bfeb53be180f7 100644 --- a/examples/lock-app/nrfconnect/main/include/AppConfig.h +++ b/examples/lock-app/nrfconnect/main/include/AppConfig.h @@ -18,14 +18,26 @@ #pragma once +#include "BoardUtil.h" + // ---- Lock Example App Config ---- -#define LOCK_BUTTON DK_BTN2 -#define LOCK_BUTTON_MASK DK_BTN2_MSK #define FUNCTION_BUTTON DK_BTN1 #define FUNCTION_BUTTON_MASK DK_BTN1_MSK + +#if NUMBER_OF_BUTTONS == 2 +#define BLE_ADVERTISEMENT_START_AND_LOCK_BUTTON DK_BTN2 +#define BLE_ADVERTISEMENT_START_AND_LOCK_BUTTON_MASK DK_BTN2_MSK +#else +#define LOCK_BUTTON DK_BTN2 +#define LOCK_BUTTON_MASK DK_BTN2_MSK #define BLE_ADVERTISEMENT_START_BUTTON DK_BTN4 #define BLE_ADVERTISEMENT_START_BUTTON_MASK DK_BTN4_MSK +#endif #define SYSTEM_STATE_LED DK_LED1 #define LOCK_STATE_LED DK_LED2 +#if NUMBER_OF_LEDS == 4 +#define FACTORY_RESET_SIGNAL_LED DK_LED3 +#define FACTORY_RESET_SIGNAL_LED1 DK_LED4 +#endif diff --git a/examples/lock-app/nrfconnect/main/include/AppEvent.h b/examples/lock-app/nrfconnect/main/include/AppEvent.h index a45ccfdcc7b04c..f171149750e181 100644 --- a/examples/lock-app/nrfconnect/main/include/AppEvent.h +++ b/examples/lock-app/nrfconnect/main/include/AppEvent.h @@ -20,27 +20,33 @@ #include -#include "LEDWidget.h" +#include "EventTypes.h" -struct AppEvent; -typedef void (*EventHandler)(AppEvent *); +class LEDWidget; -struct AppEvent +enum class AppEventType : uint8_t { - enum AppEventTypes - { - kEventType_Button = 0, - kEventType_Timer, - kEventType_Lock, - kEventType_Install, - kEventType_UpdateLedState, -#ifdef CONFIG_MCUMGR_SMP_BT - kEventType_StartSMPAdvertising, -#endif - }; + None = 0, + Button, + ButtonPushed, + ButtonReleased, + Timer, + UpdateLedState, + IdentifyStart, + IdentifyStop, + StartSMPAdvertising +}; - uint16_t Type; +enum class FunctionEvent : uint8_t +{ + NoneSelected = 0, + SoftwareUpdate = 0, + FactoryReset, + AdvertisingStart +}; +struct AppEvent +{ union { struct @@ -63,5 +69,6 @@ struct AppEvent } UpdateLedStateEvent; }; + AppEventType Type{ AppEventType::None }; EventHandler Handler; }; diff --git a/examples/lock-app/nrfconnect/main/include/AppTask.h b/examples/lock-app/nrfconnect/main/include/AppTask.h index 95c3ed2d1f116e..a23ad0f64b4ad7 100644 --- a/examples/lock-app/nrfconnect/main/include/AppTask.h +++ b/examples/lock-app/nrfconnect/main/include/AppTask.h @@ -34,64 +34,56 @@ #endif struct k_timer; +struct Identify; class AppTask { public: + static AppTask & Instance() + { + static AppTask sAppTask; + return sAppTask; + }; + CHIP_ERROR StartApp(); - void PostEvent(AppEvent * event); void UpdateClusterState(BoltLockManager::State state, BoltLockManager::OperationSource source); -private: - friend AppTask & GetAppTask(void); + static void PostEvent(const AppEvent & event); - CHIP_ERROR Init(); + static void IdentifyStartHandler(Identify *); + static void IdentifyStopHandler(Identify *); - static void LockStateChanged(BoltLockManager::State state, BoltLockManager::OperationSource source); - - void CancelTimer(void); +private: + CHIP_ERROR Init(); - void DispatchEvent(AppEvent * event); + void CancelTimer(); + void StartTimer(uint32_t timeoutInMs); - static void UpdateStatusLED(); - static void LEDStateUpdateHandler(LEDWidget & ledWidget); - static void UpdateLedStateEventHandler(AppEvent * aEvent); - static void FunctionTimerEventHandler(AppEvent * aEvent); - static void FunctionHandler(AppEvent * aEvent); - static void LockActionEventHandler(AppEvent * aEvent); - static void StartBLEAdvertisementHandler(AppEvent * aEvent); + static void DispatchEvent(const AppEvent & event); + static void FunctionTimerEventHandler(const AppEvent & event); + static void FunctionHandler(const AppEvent & event); + static void StartBLEAdvertisementAndLockActionEventHandler(const AppEvent & event); + static void LockActionEventHandler(const AppEvent & event); + static void StartBLEAdvertisementHandler(const AppEvent & event); + static void UpdateLedStateEventHandler(const AppEvent & event); static void ChipEventHandler(const chip::DeviceLayer::ChipDeviceEvent * event, intptr_t arg); + static void ButtonEventHandler(uint32_t buttonState, uint32_t hasChanged); + static void LEDStateUpdateHandler(LEDWidget & ledWidget); + static void FunctionTimerTimeoutCallback(k_timer * timer); + static void UpdateStatusLED(); - static void ButtonEventHandler(uint32_t buttons_state, uint32_t has_changed); - static void TimerEventHandler(k_timer * timer); + static void LockStateChanged(BoltLockManager::State state, BoltLockManager::OperationSource source); #ifdef CONFIG_MCUMGR_SMP_BT static void RequestSMPAdvertisingStart(void); #endif - void StartTimer(uint32_t aTimeoutInMs); - - enum Function_t - { - kFunction_NoneSelected = 0, - kFunction_SoftwareUpdate = 0, - kFunction_FactoryReset, - - kFunction_Invalid - }; - - Function_t mFunction = kFunction_NoneSelected; + FunctionEvent mFunction = FunctionEvent::NoneSelected; bool mFunctionTimerActive = false; - static AppTask sAppTask; #if CONFIG_CHIP_FACTORY_DATA chip::DeviceLayer::FactoryDataProvider mFactoryDataProvider; #endif }; - -inline AppTask & GetAppTask(void) -{ - return AppTask::sAppTask; -} diff --git a/examples/lock-app/nrfconnect/main/include/BoltLockManager.h b/examples/lock-app/nrfconnect/main/include/BoltLockManager.h index 940346e176fa2f..8811723cd8421f 100644 --- a/examples/lock-app/nrfconnect/main/include/BoltLockManager.h +++ b/examples/lock-app/nrfconnect/main/include/BoltLockManager.h @@ -82,7 +82,7 @@ class BoltLockManager void SetState(State state, OperationSource source); static void ActuatorTimerEventHandler(k_timer * timer); - static void ActuatorAppEventHandler(AppEvent * aEvent); + static void ActuatorAppEventHandler(const AppEvent & aEvent); friend BoltLockManager & BoltLockMgr(); State mState = State::kLockingCompleted; diff --git a/examples/lock-app/nrfconnect/main/main.cpp b/examples/lock-app/nrfconnect/main/main.cpp index a20f86082131a4..0c3b58e3d4f196 100644 --- a/examples/lock-app/nrfconnect/main/main.cpp +++ b/examples/lock-app/nrfconnect/main/main.cpp @@ -27,7 +27,7 @@ using namespace ::chip; int main() { - CHIP_ERROR err = GetAppTask().StartApp(); + CHIP_ERROR err = AppTask::Instance().StartApp(); LOG_ERR("Exited with code %" CHIP_ERROR_FORMAT, err.Format()); return err == CHIP_NO_ERROR ? EXIT_SUCCESS : EXIT_FAILURE; diff --git a/examples/lock-app/nrfconnect/prj.conf b/examples/lock-app/nrfconnect/prj.conf index e6d757137db86a..676ae5a05f2599 100644 --- a/examples/lock-app/nrfconnect/prj.conf +++ b/examples/lock-app/nrfconnect/prj.conf @@ -14,31 +14,24 @@ # limitations under the License. # -CONFIG_CHIP=y -CONFIG_STD_CPP14=y - # This sample uses Kconfig.defaults to set options common for all # samples. This file should contain only options specific for this sample # or overrides of default values. +# Enable CHIP +CONFIG_CHIP=y +CONFIG_CHIP_PROJECT_CONFIG="main/include/CHIPProjectConfig.h" +# 32774 == 0x8006 (example lock-app) +CONFIG_CHIP_DEVICE_PRODUCT_ID=32774 +CONFIG_STD_CPP14=y + # Add support for LEDs and buttons on Nordic development kits CONFIG_DK_LIBRARY=y -# OpenThread configs -CONFIG_OPENTHREAD_NORDIC_LIBRARY_MTD=y -CONFIG_OPENTHREAD_MTD=y -CONFIG_OPENTHREAD_FTD=n -CONFIG_CHIP_ENABLE_SLEEPY_END_DEVICE_SUPPORT=y - -# Bluetooth overrides +# Bluetooth Low Energy configuration CONFIG_BT_DEVICE_NAME="MatterLock" -# Additional configs for debbugging experience. +# Other settings CONFIG_THREAD_NAME=y CONFIG_MPU_STACK_GUARD=y CONFIG_RESET_ON_FATAL_ERROR=n - -# CHIP configuration -CONFIG_CHIP_PROJECT_CONFIG="main/include/CHIPProjectConfig.h" -# 32774 == 0x8006 (example lock-app) -CONFIG_CHIP_DEVICE_PRODUCT_ID=32774 diff --git a/examples/lock-app/nrfconnect/prj_no_dfu.conf b/examples/lock-app/nrfconnect/prj_no_dfu.conf index 9b0339d336b4e1..839f839fc2e74f 100644 --- a/examples/lock-app/nrfconnect/prj_no_dfu.conf +++ b/examples/lock-app/nrfconnect/prj_no_dfu.conf @@ -14,26 +14,24 @@ # limitations under the License. # -CONFIG_CHIP=y -CONFIG_STD_CPP14=y - # This sample uses Kconfig.defaults to set options common for all # samples. This file should contain only options specific for this sample # or overrides of default values. +# Enable CHIP +CONFIG_CHIP=y +CONFIG_CHIP_PROJECT_CONFIG="main/include/CHIPProjectConfig.h" +# 32774 == 0x8006 (example lock-app) +CONFIG_CHIP_DEVICE_PRODUCT_ID=32774 +CONFIG_STD_CPP14=y + # Add support for LEDs and buttons on Nordic development kits CONFIG_DK_LIBRARY=y -# OpenThread configs -CONFIG_OPENTHREAD_NORDIC_LIBRARY_MTD=y -CONFIG_OPENTHREAD_MTD=y -CONFIG_OPENTHREAD_FTD=n -CONFIG_CHIP_ENABLE_SLEEPY_END_DEVICE_SUPPORT=y - -# Bluetooth overrides +# Bluetooth Low Energy configuration CONFIG_BT_DEVICE_NAME="MatterLock" -# Additional configs for debbugging experience. +# Other settings CONFIG_THREAD_NAME=y CONFIG_MPU_STACK_GUARD=y CONFIG_RESET_ON_FATAL_ERROR=n @@ -41,7 +39,5 @@ CONFIG_RESET_ON_FATAL_ERROR=n # Disable Matter OTA DFU CONFIG_CHIP_OTA_REQUESTOR=n -# CHIP configuration -CONFIG_CHIP_PROJECT_CONFIG="main/include/CHIPProjectConfig.h" -# 32774 == 0x8006 (example lock-app) -CONFIG_CHIP_DEVICE_PRODUCT_ID=32774 +# Disable QSPI NOR +CONFIG_CHIP_QSPI_NOR=n diff --git a/examples/lock-app/nrfconnect/prj_release.conf b/examples/lock-app/nrfconnect/prj_release.conf index a71e97536cfd6c..b41ed9ede98909 100644 --- a/examples/lock-app/nrfconnect/prj_release.conf +++ b/examples/lock-app/nrfconnect/prj_release.conf @@ -14,33 +14,26 @@ # limitations under the License. # -CONFIG_CHIP=y -CONFIG_STD_CPP14=y - # This sample uses Kconfig.defaults to set options common for all # samples. This file should contain only options specific for this sample # or overrides of default values. +# Enable CHIP +CONFIG_CHIP=y +CONFIG_CHIP_PROJECT_CONFIG="main/include/CHIPProjectConfig.h" +# 32774 == 0x8006 (example lock-app) +CONFIG_CHIP_DEVICE_PRODUCT_ID=32774 +CONFIG_STD_CPP14=y + # Add support for LEDs and buttons on Nordic development kits CONFIG_DK_LIBRARY=y -# OpenThread configs -CONFIG_OPENTHREAD_NORDIC_LIBRARY_MTD=y -CONFIG_OPENTHREAD_MTD=y -CONFIG_OPENTHREAD_FTD=n -CONFIG_CHIP_ENABLE_SLEEPY_END_DEVICE_SUPPORT=y - -# Bluetooth overrides +# Bluetooth Low Energy configuration CONFIG_BT_DEVICE_NAME="MatterLock" # Enable system reset on fatal error CONFIG_RESET_ON_FATAL_ERROR=y -# CHIP configuration -CONFIG_CHIP_PROJECT_CONFIG="main/include/CHIPProjectConfig.h" -# 32774 == 0x8006 (example lock-app) -CONFIG_CHIP_DEVICE_PRODUCT_ID=32774 - # Suspend devices when the CPU goes into sleep CONFIG_PM_DEVICE=y @@ -52,7 +45,8 @@ CONFIG_UART_CONSOLE=n CONFIG_SERIAL=n CONFIG_LOG=n CONFIG_LOG_MODE_MINIMAL=n -CONFIG_ASSERT_NO_FILE_INFO=y CONFIG_ASSERT_VERBOSE=n +CONFIG_ASSERT_NO_FILE_INFO=y CONFIG_PRINTK=n +CONFIG_PRINTK_SYNC=n CONFIG_THREAD_NAME=n diff --git a/examples/platform/nrfconnect/Rpc.cpp b/examples/platform/nrfconnect/Rpc.cpp index c857cc360065c9..c9961063e0407f 100644 --- a/examples/platform/nrfconnect/Rpc.cpp +++ b/examples/platform/nrfconnect/Rpc.cpp @@ -109,7 +109,8 @@ class NrfButton final : public Button public: pw::Status Event(const chip_rpc_ButtonEvent & request, pw_protobuf_Empty & response) override { - GetAppTask().ButtonEventHandler(request.pushed << request.idx /* button_state */, 1 << request.idx /* has_changed */); + AppTask::Instance().ButtonEventHandler(request.pushed << request.idx /* button_state */, + 1 << request.idx /* has_changed */); return pw::OkStatus(); } }; diff --git a/examples/platform/nrfconnect/doc/images/nrf7002dk.jpg b/examples/platform/nrfconnect/doc/images/nrf7002dk.jpg new file mode 100644 index 0000000000000000000000000000000000000000..1308472505e123fc9bfa990d0e42e3932575930b GIT binary patch literal 231981 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

4CS;8Trl;kA+`Fml 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ྛ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 z zt7@skpGWPLy^WW6@B8guW7$^s^V=?JJ8yY>3qqGIDcNwn&VfK!V~{;A&E+Q=~P02hJ#b2?T;lwamMF-XF>t1Z+Cwh2<#Q1}1RVI0eAdPwXd?#dqkyLq(! z6~+m*+@_hs$vvu?Rt!{|!&))^=2lMVobe(QWxkQ-1&rh5r2|8`je-huK6>* zYjPhd3n45(qhy@laFf3^S!EWB=x*qJ7>mkVhgcj&DFky*UV&HL@7Tuskh`&Kguw6i zlFTOqibtjefP2E?J)}LgaXe7c22dr74%je*lE#?{5uWDve1j zGyCRmCBFu62av%AN?R6%t8cnrU|ED__BjyY1AJNR!^4ai=+HGw zTGmS}N zZ~MzsefUU~d6>a@a!+r}~L?Pb9U zSm^j=50k{FE-$*tZ=$_1wd2XdV3yHIL+Ox=$CX78%POx@Jt$xo90D;3%vOw^LAWJY z7W-348GwJBm6wnC>MQofEJtp&p;ZA4Ursso{)`C(Y&*zvq^{m374f7yl7q;E_l^3; zpYT8`T(Vl%YxLqWG>AmOVE&V!55E*EZq~2#BWcnB4)MHG!80Ct>X^^W5B~rJ1!IqQ zan*1WC7fw zJVb5cwkZRB@8#5dD6bRk2gT#PxC9WETu6I2gPS+lC3Bkll`p5Ijc2c-t4_`^8xSB~ z^LB!}q0GHR!*->BFD8Z|U%N+v^$T<2+c}zpRJKfNHj$#B^AV~8f6h-_m5FX3%22e) zX=XC_xff93@LByV1m-EK*asOn*vXG-6{9{Oa~GpFJk&<%`RqQvcTKqcHv8`y?= zR>a)(rpM`;TEp-49zq9i^3xu)29#ZoUTLtxnkTUDxS#UTL zj=`o)JleAea5VyU`jA+xU(B9M&0HV7H@hfZ1z(f&4_uLdK+g_7KwxRhuE&{fTsl$4 z8Pn7}Z{_m50~3P4pd5egT{??Ja-w8UftgL>5k%+N4NI*tY@UF=IPP_cW1Mk{;W7r> zH|;|Fa*NcTogaS4jaLGnkqS_kuMoL%o2d;~&-$zw48QeZ3aI3xutj}#tB*>4N*45N z#=_tbp>dz=Hrj`qa5YP$$FC|F^JCJ^HG1p&ca3p8Mp^+IJxEhj=M4RmPxWb>%8oBE zMjPq56eMM|U$R7~a%a38xJ^c*I)5{8kJ!HaGbch26`$!V{V~dS^8%wSKk$;owVSMm zIp53#9?CAANE*8(5hw{?Pi$0WHNxH!cpMP-yPgMECV6aKk^Q=JU8s+)a_? zB>`QK6ar6Z54EPv9JrVSuyJ<}xiA@VbkmKNu(6`mL1DDz#2XrAHhd`cPWeTnX8Gbd zMWQEh>~)k;6aSy?QtLQ?H_d(Jqg~|n&qqcyoz0nj{1|0oPcH$!**-IB={I&A=?puOxmcG<)HguVj~#zj zwW|m_CEC>3fVoBhbH92*x}u0g>us;;*=&JodP@)eEh8_~k}tk)d3W^)jVx!G(016i z$8YljK`!EBok|#iq~k^H?K@KwbNXUc+g$ejCav3beX6j#ZM)-t0ByU|NZ69+6%!A` zQzry)yXFjqN9zcwS>}Q+F}-<0n4<7J zbiN10@tM0t=<-(5I{RyTO!{CRv;;-`B&at;j)>7XtaKPezlp8<5~;lv!yWjbWbhBr z{T%rlCxTYYNKpMSmUbnOM&2JDSo{^Sh_yD`fL`D(q+mW?g&`w;X{9;#adXX*Xp6Z< z|G~4QnDDJ=wCT&|vjRVK=ssxj_bIHo-5O09mltpF|TGu

)PEmD>fp+~jPto_rWqhx6#a#EzC1Gi0 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 - mMinLevel, maxEffectiveLevel) : 0; diff --git a/examples/platform/nrfconnect/util/include/BoardUtil.h b/examples/platform/nrfconnect/util/include/BoardUtil.h new file mode 100644 index 00000000000000..ccd8e69a4fcd86 --- /dev/null +++ b/examples/platform/nrfconnect/util/include/BoardUtil.h @@ -0,0 +1,26 @@ +/* + * + * 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. + */ + +#pragma once + +#include + +#define LEDS_NODE_ID DT_PATH(leds) +#define BUTTONS_NODE_ID DT_PATH(buttons) +#define INCREMENT_BY_ONE(button_or_led) +1 +#define NUMBER_OF_LEDS (0 DT_FOREACH_CHILD(LEDS_NODE_ID, INCREMENT_BY_ONE)) +#define NUMBER_OF_BUTTONS (0 DT_FOREACH_CHILD(BUTTONS_NODE_ID, INCREMENT_BY_ONE)) diff --git a/examples/platform/nrfconnect/util/include/EventTypes.h b/examples/platform/nrfconnect/util/include/EventTypes.h new file mode 100644 index 00000000000000..557254c6cb9c0a --- /dev/null +++ b/examples/platform/nrfconnect/util/include/EventTypes.h @@ -0,0 +1,21 @@ +/* + * Copyright (c) 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. + */ + +#pragma once + +struct AppEvent; // Needs to be implemented in the application code. +using EventHandler = void (*)(const AppEvent &); diff --git a/examples/platform/nrfconnect/util/include/LEDUtil.h b/examples/platform/nrfconnect/util/include/LEDUtil.h index 3066f1506b89b5..8f25fc783e902b 100644 --- a/examples/platform/nrfconnect/util/include/LEDUtil.h +++ b/examples/platform/nrfconnect/util/include/LEDUtil.h @@ -21,13 +21,13 @@ // A lightweight wrapper for unused LEDs template -class UnusedLedsWrapper +class FactoryResetLEDsWrapper { public: using Gpio = uint32_t; using Leds = std::array, size>; - explicit UnusedLedsWrapper(std::array aLeds) + explicit FactoryResetLEDsWrapper(std::array aLeds) { auto idx{ 0 }; for (const auto & led : aLeds) diff --git a/examples/platform/nrfconnect/util/include/PWMDevice.h b/examples/platform/nrfconnect/util/include/PWMDevice.h index 250383efa93c0e..7b1ff5326b3a3d 100644 --- a/examples/platform/nrfconnect/util/include/PWMDevice.h +++ b/examples/platform/nrfconnect/util/include/PWMDevice.h @@ -49,6 +49,8 @@ class PWMDevice bool InitiateAction(Action_t aAction, int32_t aActor, uint8_t * aValue); void SetCallbacks(PWMCallback aActionInitiatedClb, PWMCallback aActionCompletedClb); const device * GetDevice() { return mPwmDevice->dev; } + void SuppressOutput(); + void ApplyLevel(); private: State_t mState; @@ -63,5 +65,4 @@ class PWMDevice void Set(bool aOn); void SetLevel(uint8_t aLevel); - void UpdateLight(); }; diff --git a/examples/pump-app/nrfconnect/Kconfig b/examples/pump-app/nrfconnect/Kconfig index 94a99f971ac195..ffa1e2cfd5bcce 100644 --- a/examples/pump-app/nrfconnect/Kconfig +++ b/examples/pump-app/nrfconnect/Kconfig @@ -13,7 +13,20 @@ # See the License for the specific language governing permissions and # limitations under the License. # -mainmenu "Matter nRF Connect Lighting Example Application" +mainmenu "Matter nRF Connect Pump Application" + +# Sample configuration used for Thread networking +if NET_L2_OPENTHREAD + +choice OPENTHREAD_NORDIC_LIBRARY_CONFIGURATION + default OPENTHREAD_NORDIC_LIBRARY_MTD +endchoice + +choice OPENTHREAD_DEVICE_TYPE + default OPENTHREAD_MTD +endchoice + +endif # NET_L2_OPENTHREAD rsource "../../../config/nrfconnect/chip-module/Kconfig.features" rsource "../../../config/nrfconnect/chip-module/Kconfig.defaults" diff --git a/examples/pump-app/nrfconnect/README.md b/examples/pump-app/nrfconnect/README.md index 1ea3730c18e873..c4a02dc8a2ebb0 100644 --- a/examples/pump-app/nrfconnect/README.md +++ b/examples/pump-app/nrfconnect/README.md @@ -54,13 +54,18 @@ and [Zephyr RTOS](https://zephyrproject.org/). Visit Matter's to read more about the platform structure and dependencies. The Matter device that runs the pump application is controlled by the Matter -controller device over the Thread protocol. By default, the Matter device has -Thread disabled, and it should be paired with Matter controller and get -configuration from it. Some actions required before establishing full -communication are described below. - -The example can be configured to use the secure bootloader and utilize it for -performing over-the-air Device Firmware Upgrade using Bluetooth LE. +controller device over the Thread protocol. By default, the Matter accessory +device has IPv6 networking disabled. You must pair it with the Matter controller +over Bluetooth® LE to get the configuration from the controller to use the +device within a Thread or Wi-Fi network. You have to make the device +discoverable manually (for security reasons). See +[Bluetooth LE advertising](#bluetooth-le-advertising) to learn how to do this. +The controller must get the commissioning information from the Matter accessory +device and provision the device into the network. + +You can test this application remotely over the Thread or the Wi-Fi protocol, +which in either case requires more devices, including a Matter controller that +you can configure either on a PC or a mobile device. ### Bluetooth LE advertising @@ -187,8 +192,8 @@ following states are possible: Bluetooth LE. - _Short Flash Off (950ms on/50ms off)_ — The device is fully - provisioned, but does not yet have full Thread network or service - connectivity. + provisioned, but does not yet have full connectivity for Thread or Wi-Fi + network. - _Solid On_ — The device is fully provisioned and has full Thread network and service connectivity. diff --git a/examples/pump-app/nrfconnect/main/AppTask.cpp b/examples/pump-app/nrfconnect/main/AppTask.cpp index d8ccc47f393f53..209eaa0805b666 100644 --- a/examples/pump-app/nrfconnect/main/AppTask.cpp +++ b/examples/pump-app/nrfconnect/main/AppTask.cpp @@ -18,6 +18,7 @@ #include "AppTask.h" #include "AppConfig.h" +#include "LEDUtil.h" #include "LEDWidget.h" #include "PumpManager.h" @@ -45,45 +46,54 @@ #include #include +LOG_MODULE_DECLARE(app, CONFIG_CHIP_APP_LOG_LEVEL); + using namespace ::chip; using namespace ::chip::app::Clusters; using namespace ::chip::Credentials; using namespace ::chip::DeviceLayer; -#define FACTORY_RESET_TRIGGER_TIMEOUT 3000 -#define FACTORY_RESET_CANCEL_WINDOW_TIMEOUT 3000 -#define APP_EVENT_QUEUE_SIZE 10 -#define BUTTON_PUSH_EVENT 1 -#define BUTTON_RELEASE_EVENT 0 - -#define PCC_CLUSTER_ENDPOINT 1 -#define ONOFF_CLUSTER_ENDPOINT 1 - namespace { +constexpr uint32_t kFactoryResetTriggerTimeout = 3000; +constexpr uint32_t kFactoryResetCancelWindowTimeout = 3000; +constexpr size_t kAppEventQueueSize = 10; +constexpr EndpointId kPccClusterEndpoint = 1; +constexpr EndpointId kOnOffClusterEndpoint = 1; // NOTE! This key is for test/certification only and should not be available in production devices! // If CONFIG_CHIP_FACTORY_DATA is enabled, this value is read from the factory data. uint8_t sTestEventTriggerEnableKey[TestEventTriggerDelegate::kEnableKeyLength] = { 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff }; -LOG_MODULE_DECLARE(app, CONFIG_MATTER_LOG_LEVEL); -K_MSGQ_DEFINE(sAppEventQueue, sizeof(AppEvent), APP_EVENT_QUEUE_SIZE, alignof(AppEvent)); +K_MSGQ_DEFINE(sAppEventQueue, sizeof(AppEvent), kAppEventQueueSize, alignof(AppEvent)); k_timer sFunctionTimer; LEDWidget sStatusLED; LEDWidget sPumpStateLED; -LEDWidget sUnusedLED; -LEDWidget sUnusedLED_1; +FactoryResetLEDsWrapper<2> sFactoryResetLEDs{ { FACTORY_RESET_SIGNAL_LED, FACTORY_RESET_SIGNAL_LED1 } }; -bool sIsThreadProvisioned = false; -bool sIsThreadEnabled = false; -bool sHaveBLEConnections = false; +bool sIsNetworkProvisioned = false; +bool sIsNetworkEnabled = false; +bool sHaveBLEConnections = false; chip::DeviceLayer::DeviceInfoProviderImpl gExampleDeviceInfoProvider; } // namespace -AppTask AppTask::sAppTask; +namespace LedConsts { +constexpr uint32_t kBlinkRate_ms{ 500 }; +namespace StatusLed { +namespace Unprovisioned { +constexpr uint32_t kOn_ms{ 100 }; +constexpr uint32_t kOff_ms{ kOn_ms }; +} // namespace Unprovisioned +namespace Provisioned { +constexpr uint32_t kOn_ms{ 50 }; +constexpr uint32_t kOff_ms{ 950 }; +} // namespace Provisioned + +} // namespace StatusLed +} // namespace LedConsts CHIP_ERROR AppTask::Init() { @@ -130,9 +140,6 @@ CHIP_ERROR AppTask::Init() sPumpStateLED.Init(PUMP_STATE_LED); sPumpStateLED.Set(!PumpMgr().IsStopped()); - sUnusedLED.Init(DK_LED3); - sUnusedLED_1.Init(DK_LED4); - UpdateStatusLED(); PumpMgr().Init(); @@ -147,7 +154,7 @@ CHIP_ERROR AppTask::Init() } // Initialize function button timer - k_timer_init(&sFunctionTimer, &AppTask::TimerEventHandler, nullptr); + k_timer_init(&sFunctionTimer, &AppTask::FunctionTimerTimeoutCallback, nullptr); k_timer_user_data_set(&sFunctionTimer, this); #ifdef CONFIG_MCUMGR_SMP_BT @@ -209,100 +216,104 @@ CHIP_ERROR AppTask::StartApp() while (true) { k_msgq_get(&sAppEventQueue, &event, K_FOREVER); - DispatchEvent(&event); + DispatchEvent(event); } return CHIP_NO_ERROR; } -void AppTask::StartActionEventHandler(AppEvent * aEvent) +void AppTask::StartActionEventHandler(const AppEvent & event) { PumpManager::Action_t action = PumpManager::INVALID_ACTION; int32_t actor = 0; - if (aEvent->Type == AppEvent::kEventType_Start) + if (event.Type == AppEventType::Start) { - action = static_cast(aEvent->StartEvent.Action); - actor = aEvent->StartEvent.Actor; + action = static_cast(event.StartEvent.Action); + actor = event.StartEvent.Actor; } - else if (aEvent->Type == AppEvent::kEventType_Button) + else if (event.Type == AppEventType::Button) { action = PumpMgr().IsStopped() ? PumpManager::START_ACTION : PumpManager::STOP_ACTION; - actor = AppEvent::kEventType_Button; + actor = static_cast(AppEventType::Button); } if (action != PumpManager::INVALID_ACTION && !PumpMgr().InitiateAction(actor, action)) LOG_INF("Action is already in progress or active."); } -void AppTask::ButtonEventHandler(uint32_t button_state, uint32_t has_changed) +void AppTask::ButtonEventHandler(uint32_t buttonState, uint32_t hasChanged) { - AppEvent button_event; - button_event.Type = AppEvent::kEventType_Button; + AppEvent event; + event.Type = AppEventType::Button; - if (START_BUTTON_MASK & button_state & has_changed) + if (START_BUTTON_MASK & buttonState & hasChanged) { - button_event.ButtonEvent.PinNo = START_BUTTON; - button_event.ButtonEvent.Action = BUTTON_PUSH_EVENT; - button_event.Handler = StartActionEventHandler; - sAppTask.PostEvent(&button_event); + event.ButtonEvent.PinNo = START_BUTTON; + event.ButtonEvent.Action = static_cast(AppEventType::Button); + event.Handler = StartActionEventHandler; + PostEvent(event); } - if (FUNCTION_BUTTON_MASK & has_changed) + if (FUNCTION_BUTTON_MASK & hasChanged) { - button_event.ButtonEvent.PinNo = FUNCTION_BUTTON; - button_event.ButtonEvent.Action = (FUNCTION_BUTTON_MASK & button_state) ? BUTTON_PUSH_EVENT : BUTTON_RELEASE_EVENT; - button_event.Handler = FunctionHandler; - sAppTask.PostEvent(&button_event); + event.ButtonEvent.PinNo = FUNCTION_BUTTON; + event.ButtonEvent.Action = + static_cast((FUNCTION_BUTTON_MASK & buttonState) ? AppEventType::ButtonPushed : AppEventType::ButtonReleased); + event.Handler = FunctionHandler; + PostEvent(event); } - if (BLE_ADVERTISEMENT_START_BUTTON_MASK & button_state & has_changed) + if (BLE_ADVERTISEMENT_START_BUTTON_MASK & buttonState & hasChanged) { - button_event.ButtonEvent.PinNo = BLE_ADVERTISEMENT_START_BUTTON; - button_event.ButtonEvent.Action = BUTTON_PUSH_EVENT; - button_event.Handler = StartBLEAdvertisementHandler; - sAppTask.PostEvent(&button_event); + event.ButtonEvent.PinNo = BLE_ADVERTISEMENT_START_BUTTON; + event.ButtonEvent.Action = static_cast(AppEventType::ButtonPushed); + event.Handler = StartBLEAdvertisementHandler; + PostEvent(event); } } -void AppTask::TimerEventHandler(k_timer * timer) +void AppTask::FunctionTimerTimeoutCallback(k_timer * timer) { + if (!timer) + { + return; + } + AppEvent event; - event.Type = AppEvent::kEventType_Timer; + event.Type = AppEventType::Timer; event.TimerEvent.Context = k_timer_user_data_get(timer); event.Handler = FunctionTimerEventHandler; - sAppTask.PostEvent(&event); + PostEvent(event); } -void AppTask::FunctionTimerEventHandler(AppEvent * aEvent) +void AppTask::FunctionTimerEventHandler(const AppEvent & event) { - if (aEvent->Type != AppEvent::kEventType_Timer) + if (event.Type != AppEventType::Timer) return; - // If we reached here, the button was held past FACTORY_RESET_TRIGGER_TIMEOUT, initiate factory reset - if (sAppTask.mFunctionTimerActive && sAppTask.mFunction == kFunction_SoftwareUpdate) + // If we reached here, the button was held past kFactoryResetTriggerTimeout, initiate factory reset + if (Instance().mFunctionTimerActive && Instance().mFunction == FunctionEvent::SoftwareUpdate) { - LOG_INF("Factory Reset Triggered. Release button within %ums to cancel.", FACTORY_RESET_TRIGGER_TIMEOUT); + LOG_INF("Factory Reset Triggered. Release button within %ums to cancel.", kFactoryResetTriggerTimeout); - // Start timer for FACTORY_RESET_CANCEL_WINDOW_TIMEOUT to allow user to cancel, if required. - sAppTask.StartTimer(FACTORY_RESET_CANCEL_WINDOW_TIMEOUT); - sAppTask.mFunction = kFunction_FactoryReset; + // Start timer for kFactoryResetCancelWindowTimeout to allow user to cancel, if required. + Instance().StartTimer(kFactoryResetCancelWindowTimeout); + Instance().mFunction = FunctionEvent::FactoryReset; +#ifdef CONFIG_STATE_LEDS // Turn off all LEDs before starting blink to make sure blink is co-ordinated. sStatusLED.Set(false); - sPumpStateLED.Set(false); - sUnusedLED_1.Set(false); - sUnusedLED.Set(false); + sFactoryResetLEDs.Set(false); - sStatusLED.Blink(500); - sPumpStateLED.Blink(500); - sUnusedLED.Blink(500); - sUnusedLED_1.Blink(500); + sStatusLED.Blink(LedConsts::kBlinkRate_ms); + sFactoryResetLEDs.Blink(LedConsts::kBlinkRate_ms); +#endif } - else if (sAppTask.mFunctionTimerActive && sAppTask.mFunction == kFunction_FactoryReset) + else if (Instance().mFunctionTimerActive && Instance().mFunction == FunctionEvent::FactoryReset) { // Actually trigger Factory Reset - sAppTask.mFunction = kFunction_NoneSelected; + Instance().mFunction = FunctionEvent::NoneSelected; chip::Server::GetInstance().ScheduleFactoryReset(); } @@ -312,65 +323,62 @@ void AppTask::FunctionTimerEventHandler(AppEvent * aEvent) void AppTask::RequestSMPAdvertisingStart(void) { AppEvent event; - event.Type = AppEvent::kEventType_StartSMPAdvertising; - event.Handler = [](AppEvent *) { GetDFUOverSMP().StartBLEAdvertising(); }; - sAppTask.PostEvent(&event); + event.Type = AppEventType::StartSMPAdvertising; + event.Handler = [](const AppEvent &) { GetDFUOverSMP().StartBLEAdvertising(); }; + PostEvent(event); } #endif -void AppTask::FunctionHandler(AppEvent * aEvent) +void AppTask::FunctionHandler(const AppEvent & event) { - if (aEvent->ButtonEvent.PinNo != FUNCTION_BUTTON) + if (event.ButtonEvent.PinNo != FUNCTION_BUTTON) return; - // To trigger software update: press the FUNCTION_BUTTON button briefly (< FACTORY_RESET_TRIGGER_TIMEOUT) // To initiate factory reset: press the FUNCTION_BUTTON for FACTORY_RESET_TRIGGER_TIMEOUT + FACTORY_RESET_CANCEL_WINDOW_TIMEOUT // All LEDs start blinking after FACTORY_RESET_TRIGGER_TIMEOUT to signal factory reset has been initiated. // To cancel factory reset: release the FUNCTION_BUTTON once all LEDs start blinking within the // FACTORY_RESET_CANCEL_WINDOW_TIMEOUT - if (aEvent->ButtonEvent.Action == BUTTON_PUSH_EVENT) + if (event.ButtonEvent.Action == static_cast(AppEventType::ButtonPushed)) { - if (!sAppTask.mFunctionTimerActive && sAppTask.mFunction == kFunction_NoneSelected) + if (!Instance().mFunctionTimerActive && Instance().mFunction == FunctionEvent::NoneSelected) { - sAppTask.StartTimer(FACTORY_RESET_TRIGGER_TIMEOUT); + Instance().StartTimer(kFactoryResetTriggerTimeout); - sAppTask.mFunction = kFunction_SoftwareUpdate; + Instance().mFunction = FunctionEvent::SoftwareUpdate; } } else { // If the button was released before factory reset got initiated, trigger a software update. - if (sAppTask.mFunctionTimerActive && sAppTask.mFunction == kFunction_SoftwareUpdate) + if (Instance().mFunctionTimerActive && Instance().mFunction == FunctionEvent::SoftwareUpdate) { - sAppTask.CancelTimer(); - sAppTask.mFunction = kFunction_NoneSelected; + Instance().CancelTimer(); #ifdef CONFIG_MCUMGR_SMP_BT GetDFUOverSMP().StartServer(); #else LOG_INF("Software update is disabled"); #endif + Instance().mFunction = FunctionEvent::NoneSelected; } - else if (sAppTask.mFunctionTimerActive && sAppTask.mFunction == kFunction_FactoryReset) + else if (Instance().mFunctionTimerActive && Instance().mFunction == FunctionEvent::FactoryReset) { - sUnusedLED.Set(false); - sUnusedLED_1.Set(false); - - // Set pump state LED back to show state of pump. - sPumpStateLED.Set(!PumpMgr().IsStopped()); + sFactoryResetLEDs.Set(false); UpdateStatusLED(); - sAppTask.CancelTimer(); - - // Change the function to none selected since factory reset has been canceled. - sAppTask.mFunction = kFunction_NoneSelected; - + Instance().CancelTimer(); + Instance().mFunction = FunctionEvent::NoneSelected; LOG_INF("Factory Reset has been Canceled"); } + else if (Instance().mFunctionTimerActive) + { + CancelTimer(); + Instance().mFunction = FunctionEvent::NoneSelected; + } } } -void AppTask::StartBLEAdvertisementHandler(AppEvent *) +void AppTask::StartBLEAdvertisementHandler(const AppEvent &) { if (Server::GetInstance().GetFabricTable().FabricCount() != 0) { @@ -390,45 +398,47 @@ void AppTask::StartBLEAdvertisementHandler(AppEvent *) } } -void AppTask::UpdateLedStateEventHandler(AppEvent * aEvent) +void AppTask::UpdateLedStateEventHandler(const AppEvent & event) { - if (aEvent->Type == AppEvent::kEventType_UpdateLedState) + if (event.Type == AppEventType::UpdateLedState) { - aEvent->UpdateLedStateEvent.LedWidget->UpdateState(); + event.UpdateLedStateEvent.LedWidget->UpdateState(); } } void AppTask::LEDStateUpdateHandler(LEDWidget & ledWidget) { AppEvent event; - event.Type = AppEvent::kEventType_UpdateLedState; + event.Type = AppEventType::UpdateLedState; event.Handler = UpdateLedStateEventHandler; event.UpdateLedStateEvent.LedWidget = &ledWidget; - sAppTask.PostEvent(&event); + PostEvent(event); } void AppTask::UpdateStatusLED() { - /* Update the status LED. - * - * If thread and service provisioned, keep the LED On constantly. - * - * If the system has ble connection(s) uptill the stage above, THEN blink the LED at an even - * rate of 100ms. - * - * Otherwise, blink the LED On for a very short time. */ - if (sIsThreadProvisioned && sIsThreadEnabled) +#ifdef CONFIG_STATE_LEDS + // Update the status LED. + // + // If thread and service provisioned, keep the LED On constantly. + // + // If the system has ble connection(s) uptill the stage above, THEN blink the LED at an even + // rate of 100ms. + // + // Otherwise, blink the LED On for a very short time. + if (sIsNetworkProvisioned && sIsNetworkEnabled) { sStatusLED.Set(true); } else if (sHaveBLEConnections) { - sStatusLED.Blink(100, 100); + sStatusLED.Blink(LedConsts::StatusLed::Unprovisioned::kOn_ms, LedConsts::StatusLed::Unprovisioned::kOff_ms); } else { - sStatusLED.Blink(50, 950); + sStatusLED.Blink(LedConsts::StatusLed::Provisioned::kOn_ms, LedConsts::StatusLed::Provisioned::kOff_ms); } +#endif } void AppTask::ChipEventHandler(const ChipDeviceEvent * event, intptr_t /* arg */) @@ -436,21 +446,8 @@ void AppTask::ChipEventHandler(const ChipDeviceEvent * event, intptr_t /* arg */ switch (event->Type) { case DeviceEventType::kCHIPoBLEAdvertisingChange: - if (event->CHIPoBLEAdvertisingChange.Result == kActivity_Stopped) - { #ifdef CONFIG_CHIP_NFC_COMMISSIONING - NFCMgr().StopTagEmulation(); -#endif -#ifdef CONFIG_MCUMGR_SMP_BT - // After CHIPoBLE advertising stop, start advertising SMP in case Thread is enabled or there are no active CHIPoBLE - // connections (exclude the case when CHIPoBLE advertising is stopped on the connection time) - if (GetDFUOverSMP().IsEnabled() && - (ConnectivityMgr().IsThreadProvisioned() || ConnectivityMgr().NumBLEConnections() == 0)) - sAppTask.RequestSMPAdvertisingStart(); -#endif - } -#ifdef CONFIG_CHIP_NFC_COMMISSIONING - else if (event->CHIPoBLEAdvertisingChange.Result == kActivity_Started) + if (event->CHIPoBLEAdvertisingChange.Result == kActivity_Started) { if (NFCMgr().IsTagEmulationStarted()) { @@ -461,13 +458,17 @@ void AppTask::ChipEventHandler(const ChipDeviceEvent * event, intptr_t /* arg */ ShareQRCodeOverNFC(chip::RendezvousInformationFlags(chip::RendezvousInformationFlag::kBLE)); } } + else if (event->CHIPoBLEAdvertisingChange.Result == kActivity_Stopped) + { + NFCMgr().StopTagEmulation(); + } #endif sHaveBLEConnections = ConnectivityMgr().NumBLEConnections() != 0; UpdateStatusLED(); break; case DeviceEventType::kThreadStateChange: - sIsThreadProvisioned = ConnectivityMgr().IsThreadProvisioned(); - sIsThreadEnabled = ConnectivityMgr().IsThreadEnabled(); + sIsNetworkProvisioned = ConnectivityMgr().IsThreadProvisioned(); + sIsNetworkEnabled = ConnectivityMgr().IsThreadEnabled(); UpdateStatusLED(); break; case DeviceEventType::kDnssdPlatformInitialized: @@ -483,24 +484,24 @@ void AppTask::ChipEventHandler(const ChipDeviceEvent * event, intptr_t /* arg */ void AppTask::CancelTimer() { k_timer_stop(&sFunctionTimer); - mFunctionTimerActive = false; + Instance().mFunctionTimerActive = false; } void AppTask::StartTimer(uint32_t aTimeoutInMs) { k_timer_start(&sFunctionTimer, K_MSEC(aTimeoutInMs), K_NO_WAIT); - mFunctionTimerActive = true; + Instance().mFunctionTimerActive = true; } -void AppTask::ActionInitiated(PumpManager::Action_t aAction, int32_t aActor) +void AppTask::ActionInitiated(PumpManager::Action_t action, int32_t actor) { // If the action has been initiated by the pump, update the pump trait // and start flashing the LEDs rapidly to indicate action initiation. - if (aAction == PumpManager::START_ACTION) + if (action == PumpManager::START_ACTION) { LOG_INF("Pump Start Action has been initiated"); } - else if (aAction == PumpManager::STOP_ACTION) + else if (action == PumpManager::STOP_ACTION) { LOG_INF("Pump Stop Action has been initiated"); } @@ -508,51 +509,51 @@ void AppTask::ActionInitiated(PumpManager::Action_t aAction, int32_t aActor) sPumpStateLED.Blink(50, 50); } -void AppTask::ActionCompleted(PumpManager::Action_t aAction, int32_t aActor) +void AppTask::ActionCompleted(PumpManager::Action_t action, int32_t actor) { // If the action has been completed by the pump, update the pump trait. // Turn on the pump state LED if in a STARTED state OR // Turn off the pump state LED if in a STOPPED state. - if (aAction == PumpManager::START_ACTION) + if (action == PumpManager::START_ACTION) { LOG_INF("Pump Start Action has been completed"); sPumpStateLED.Set(true); } - else if (aAction == PumpManager::STOP_ACTION) + else if (action == PumpManager::STOP_ACTION) { LOG_INF("Pump Stop Action has been completed"); sPumpStateLED.Set(false); } - if (aActor == AppEvent::kEventType_Button) + if (actor == static_cast(AppEventType::Button)) { - sAppTask.UpdateClusterState(); + Instance().UpdateClusterState(); } } -void AppTask::PostStartActionRequest(int32_t aActor, PumpManager::Action_t aAction) +void AppTask::PostStartActionRequest(int32_t actor, PumpManager::Action_t action) { AppEvent event; - event.Type = AppEvent::kEventType_Start; - event.StartEvent.Actor = aActor; - event.StartEvent.Action = aAction; + event.Type = AppEventType::Start; + event.StartEvent.Actor = actor; + event.StartEvent.Action = action; event.Handler = StartActionEventHandler; - PostEvent(&event); + PostEvent(event); } -void AppTask::PostEvent(AppEvent * aEvent) +void AppTask::PostEvent(const AppEvent & event) { - if (k_msgq_put(&sAppEventQueue, aEvent, K_NO_WAIT)) + if (k_msgq_put(&sAppEventQueue, &event, K_NO_WAIT) != 0) { LOG_INF("Failed to post event to app task event queue"); } } -void AppTask::DispatchEvent(AppEvent * aEvent) +void AppTask::DispatchEvent(const AppEvent & event) { - if (aEvent->Handler) + if (event.Handler) { - aEvent->Handler(aEvent); + event.Handler(event); } else { @@ -570,98 +571,98 @@ void AppTask::UpdateClusterState() bool onOffState = !PumpMgr().IsStopped(); - status = OnOff::Attributes::OnOff::Set(ONOFF_CLUSTER_ENDPOINT, onOffState); + status = OnOff::Attributes::OnOff::Set(kOnOffClusterEndpoint, onOffState); if (status != EMBER_ZCL_STATUS_SUCCESS) { ChipLogError(NotSpecified, "ERR: Updating On/Off state %x", status); } int16_t maxPressure = PumpMgr().GetMaxPressure(); - status = PumpConfigurationAndControl::Attributes::MaxPressure::Set(PCC_CLUSTER_ENDPOINT, maxPressure); + status = PumpConfigurationAndControl::Attributes::MaxPressure::Set(kPccClusterEndpoint, maxPressure); if (status != EMBER_ZCL_STATUS_SUCCESS) { ChipLogError(NotSpecified, "ERR: Updating MaxPressure %x", status); } uint16_t maxSpeed = PumpMgr().GetMaxSpeed(); - status = PumpConfigurationAndControl::Attributes::MaxSpeed::Set(PCC_CLUSTER_ENDPOINT, maxSpeed); + status = PumpConfigurationAndControl::Attributes::MaxSpeed::Set(kPccClusterEndpoint, maxSpeed); if (status != EMBER_ZCL_STATUS_SUCCESS) { ChipLogError(NotSpecified, "ERR: Updating MaxSpeed %x", status); } uint16_t maxFlow = PumpMgr().GetMaxFlow(); - status = PumpConfigurationAndControl::Attributes::MaxFlow::Set(PCC_CLUSTER_ENDPOINT, maxFlow); + status = PumpConfigurationAndControl::Attributes::MaxFlow::Set(kPccClusterEndpoint, maxFlow); if (status != EMBER_ZCL_STATUS_SUCCESS) { ChipLogError(NotSpecified, "ERR: Updating MaxFlow %x", status); } int16_t minConstPress = PumpMgr().GetMinConstPressure(); - status = PumpConfigurationAndControl::Attributes::MinConstPressure::Set(PCC_CLUSTER_ENDPOINT, minConstPress); + status = PumpConfigurationAndControl::Attributes::MinConstPressure::Set(kPccClusterEndpoint, minConstPress); if (status != EMBER_ZCL_STATUS_SUCCESS) { ChipLogError(NotSpecified, "ERR: Updating MinConstPressure %x", status); } int16_t maxConstPress = PumpMgr().GetMaxConstPressure(); - status = PumpConfigurationAndControl::Attributes::MaxConstPressure::Set(PCC_CLUSTER_ENDPOINT, maxConstPress); + status = PumpConfigurationAndControl::Attributes::MaxConstPressure::Set(kPccClusterEndpoint, maxConstPress); if (status != EMBER_ZCL_STATUS_SUCCESS) { ChipLogError(NotSpecified, "ERR: Updating MaxConstPressure %x", status); } int16_t minCompPress = PumpMgr().GetMinCompPressure(); - status = PumpConfigurationAndControl::Attributes::MinCompPressure::Set(PCC_CLUSTER_ENDPOINT, minCompPress); + status = PumpConfigurationAndControl::Attributes::MinCompPressure::Set(kPccClusterEndpoint, minCompPress); if (status != EMBER_ZCL_STATUS_SUCCESS) { ChipLogError(NotSpecified, "ERR: Updating MinCompPressure %x", status); } int16_t maxCompPress = PumpMgr().GetMaxCompPressure(); - status = PumpConfigurationAndControl::Attributes::MaxCompPressure::Set(PCC_CLUSTER_ENDPOINT, maxCompPress); + status = PumpConfigurationAndControl::Attributes::MaxCompPressure::Set(kPccClusterEndpoint, maxCompPress); if (status != EMBER_ZCL_STATUS_SUCCESS) { ChipLogError(NotSpecified, "ERR: Updating MaxCompPressure %x", status); } uint16_t minConstSpeed = PumpMgr().GetMinConstSpeed(); - status = PumpConfigurationAndControl::Attributes::MinConstSpeed::Set(PCC_CLUSTER_ENDPOINT, minConstSpeed); + status = PumpConfigurationAndControl::Attributes::MinConstSpeed::Set(kPccClusterEndpoint, minConstSpeed); if (status != EMBER_ZCL_STATUS_SUCCESS) { ChipLogError(NotSpecified, "ERR: Updating MinConstSpeed %x", status); } uint16_t maxConstSpeed = PumpMgr().GetMaxConstSpeed(); - status = PumpConfigurationAndControl::Attributes::MaxConstSpeed::Set(PCC_CLUSTER_ENDPOINT, maxConstSpeed); + status = PumpConfigurationAndControl::Attributes::MaxConstSpeed::Set(kPccClusterEndpoint, maxConstSpeed); if (status != EMBER_ZCL_STATUS_SUCCESS) { ChipLogError(NotSpecified, "ERR: Updating MaxConstSpeed %x", status); } uint16_t minConstFlow = PumpMgr().GetMinConstFlow(); - status = PumpConfigurationAndControl::Attributes::MinConstFlow::Set(PCC_CLUSTER_ENDPOINT, minConstFlow); + status = PumpConfigurationAndControl::Attributes::MinConstFlow::Set(kPccClusterEndpoint, minConstFlow); if (status != EMBER_ZCL_STATUS_SUCCESS) { ChipLogError(NotSpecified, "ERR: Updating MinConstFlow %x", status); } uint16_t maxConstFlow = PumpMgr().GetMaxConstFlow(); - status = PumpConfigurationAndControl::Attributes::MaxConstFlow::Set(PCC_CLUSTER_ENDPOINT, maxConstFlow); + status = PumpConfigurationAndControl::Attributes::MaxConstFlow::Set(kPccClusterEndpoint, maxConstFlow); if (status != EMBER_ZCL_STATUS_SUCCESS) { ChipLogError(NotSpecified, "ERR: Updating MaxConstFlow %x", status); } int16_t minConstTemp = PumpMgr().GetMinConstTemp(); - status = PumpConfigurationAndControl::Attributes::MinConstTemp::Set(PCC_CLUSTER_ENDPOINT, minConstTemp); + status = PumpConfigurationAndControl::Attributes::MinConstTemp::Set(kPccClusterEndpoint, minConstTemp); if (status != EMBER_ZCL_STATUS_SUCCESS) { ChipLogError(NotSpecified, "ERR: Updating MinConstTemp %x", status); } int16_t maxConstTemp = PumpMgr().GetMaxConstTemp(); - status = PumpConfigurationAndControl::Attributes::MaxConstTemp::Set(PCC_CLUSTER_ENDPOINT, maxConstTemp); + status = PumpConfigurationAndControl::Attributes::MaxConstTemp::Set(kPccClusterEndpoint, maxConstTemp); if (status != EMBER_ZCL_STATUS_SUCCESS) { ChipLogError(NotSpecified, "ERR: Updating MaxConstTemp %x", status); diff --git a/examples/pump-app/nrfconnect/main/PumpManager.cpp b/examples/pump-app/nrfconnect/main/PumpManager.cpp index e7c0b6240c554b..f24bd3262f45b8 100644 --- a/examples/pump-app/nrfconnect/main/PumpManager.cpp +++ b/examples/pump-app/nrfconnect/main/PumpManager.cpp @@ -25,7 +25,7 @@ #include #include -LOG_MODULE_DECLARE(app, CONFIG_MATTER_LOG_LEVEL); +LOG_MODULE_DECLARE(app, CONFIG_CHIP_APP_LOG_LEVEL); static k_timer sStartTimer; @@ -130,15 +130,15 @@ void PumpManager::TimerEventHandler(k_timer * timer) // once sStartTimer expires. Post an event to apptask queue with the actual handler // so that the event can be handled in the context of the apptask. AppEvent event; - event.Type = AppEvent::kEventType_Timer; + event.Type = AppEventType::Timer; event.TimerEvent.Context = pump; event.Handler = pump->mAutoStartTimerArmed ? AutoRestartTimerEventHandler : PumpStartTimerEventHandler; - GetAppTask().PostEvent(&event); + AppTask::Instance().PostEvent(event); } -void PumpManager::AutoRestartTimerEventHandler(AppEvent * aEvent) +void PumpManager::AutoRestartTimerEventHandler(const AppEvent & aEvent) { - PumpManager * pump = static_cast(aEvent->TimerEvent.Context); + PumpManager * pump = static_cast(aEvent.TimerEvent.Context); int32_t actor = 0; // Make sure auto start timer is still armed. @@ -152,11 +152,11 @@ void PumpManager::AutoRestartTimerEventHandler(AppEvent * aEvent) pump->InitiateAction(actor, START_ACTION); } -void PumpManager::PumpStartTimerEventHandler(AppEvent * aEvent) +void PumpManager::PumpStartTimerEventHandler(const AppEvent & aEvent) { Action_t actionCompleted = INVALID_ACTION; - PumpManager * pump = static_cast(aEvent->TimerEvent.Context); + PumpManager * pump = static_cast(aEvent.TimerEvent.Context); if (pump->mState == kState_StartInitiated) { diff --git a/examples/pump-app/nrfconnect/main/ZclCallbacks.cpp b/examples/pump-app/nrfconnect/main/ZclCallbacks.cpp index 49ec8f15946d77..6a1c4334a4eef5 100644 --- a/examples/pump-app/nrfconnect/main/ZclCallbacks.cpp +++ b/examples/pump-app/nrfconnect/main/ZclCallbacks.cpp @@ -51,5 +51,5 @@ void MatterPostAttributeChangeCallback(const chip::app::ConcreteAttributePath & */ void emberAfOnOffClusterInitCallback(EndpointId endpoint) { - GetAppTask().UpdateClusterState(); + AppTask::Instance().UpdateClusterState(); } diff --git a/examples/pump-app/nrfconnect/main/include/AppConfig.h b/examples/pump-app/nrfconnect/main/include/AppConfig.h index e2e1812ae48718..7bea410efc1fca 100644 --- a/examples/pump-app/nrfconnect/main/include/AppConfig.h +++ b/examples/pump-app/nrfconnect/main/include/AppConfig.h @@ -29,6 +29,8 @@ #define SYSTEM_STATE_LED DK_LED1 #define PUMP_STATE_LED DK_LED2 +#define FACTORY_RESET_SIGNAL_LED DK_LED3 +#define FACTORY_RESET_SIGNAL_LED1 DK_LED4 // Time it takes in ms for the simulated pump to move from one state to another. #define PUMP_START_PERIOS_MS 2000 diff --git a/examples/pump-app/nrfconnect/main/include/AppEvent.h b/examples/pump-app/nrfconnect/main/include/AppEvent.h index 2f2a0d99f74b0f..3dfff7913f3a47 100644 --- a/examples/pump-app/nrfconnect/main/include/AppEvent.h +++ b/examples/pump-app/nrfconnect/main/include/AppEvent.h @@ -20,27 +20,33 @@ #include -#include "LEDWidget.h" +#include "EventTypes.h" -struct AppEvent; -typedef void (*EventHandler)(AppEvent *); +class LEDWidget; -struct AppEvent +enum class AppEventType : uint8_t { - enum AppEventTypes - { - kEventType_Button = 0, - kEventType_Timer, - kEventType_Start, - kEventType_Install, - kEventType_UpdateLedState, -#ifdef CONFIG_MCUMGR_SMP_BT - kEventType_StartSMPAdvertising, -#endif - }; + None = 0, + Button, + ButtonPushed, + ButtonReleased, + Timer, + UpdateLedState, + StartSMPAdvertising, + Start, + Install +}; - uint16_t Type; +enum class FunctionEvent : uint8_t +{ + NoneSelected = 0, + SoftwareUpdate = 0, + FactoryReset, + AdvertisingStart +}; +struct AppEvent +{ union { struct @@ -63,5 +69,6 @@ struct AppEvent } UpdateLedStateEvent; }; + AppEventType Type{ AppEventType::None }; EventHandler Handler; }; diff --git a/examples/pump-app/nrfconnect/main/include/AppTask.h b/examples/pump-app/nrfconnect/main/include/AppTask.h index 29a2103925bdb2..a551dae7d2b326 100644 --- a/examples/pump-app/nrfconnect/main/include/AppTask.h +++ b/examples/pump-app/nrfconnect/main/include/AppTask.h @@ -38,62 +38,47 @@ struct k_timer; class AppTask { public: + static AppTask & Instance(void) + { + static AppTask sAppTask; + return sAppTask; + }; CHIP_ERROR StartApp(); - void PostStartActionRequest(int32_t aActor, PumpManager::Action_t aAction); - void PostEvent(AppEvent * event); - void UpdateClusterState(); + static void PostStartActionRequest(int32_t actor, PumpManager::Action_t action); + static void UpdateClusterState(); + static void PostEvent(const AppEvent & event); private: - friend AppTask & GetAppTask(void); - CHIP_ERROR Init(); - static void ActionInitiated(PumpManager::Action_t aAction, int32_t aActor); - static void ActionCompleted(PumpManager::Action_t aAction, int32_t aActor); - - void CancelTimer(void); + static void CancelTimer(); + static void StartTimer(uint32_t timeoutInMs); - void DispatchEvent(AppEvent * event); + static void ActionInitiated(PumpManager::Action_t action, int32_t actor); + static void ActionCompleted(PumpManager::Action_t action, int32_t actor); - static void UpdateStatusLED(); - static void LEDStateUpdateHandler(LEDWidget & ledWidget); - static void UpdateLedStateEventHandler(AppEvent * aEvent); - static void FunctionTimerEventHandler(AppEvent * aEvent); - static void FunctionHandler(AppEvent * aEvent); - static void StartActionEventHandler(AppEvent * aEvent); - static void StartBLEAdvertisementHandler(AppEvent * aEvent); + static void DispatchEvent(const AppEvent & event); + static void FunctionTimerEventHandler(const AppEvent & event); + static void FunctionHandler(const AppEvent & event); + static void StartBLEAdvertisementHandler(const AppEvent & event); + static void UpdateLedStateEventHandler(const AppEvent & event); + static void StartActionEventHandler(const AppEvent & event); static void ChipEventHandler(const chip::DeviceLayer::ChipDeviceEvent * event, intptr_t arg); - - static void ButtonEventHandler(uint32_t buttons_state, uint32_t has_changed); - static void TimerEventHandler(k_timer * timer); + static void ButtonEventHandler(uint32_t buttonState, uint32_t hasChanged); + static void LEDStateUpdateHandler(LEDWidget & ledWidget); + static void FunctionTimerTimeoutCallback(k_timer * timer); + static void UpdateStatusLED(); #ifdef CONFIG_MCUMGR_SMP_BT static void RequestSMPAdvertisingStart(void); #endif - void StartTimer(uint32_t aTimeoutInMs); - - enum Function_t - { - kFunction_NoneSelected = 0, - kFunction_SoftwareUpdate = 0, - kFunction_FactoryReset, - - kFunction_Invalid - }; - - Function_t mFunction = kFunction_NoneSelected; + FunctionEvent mFunction = FunctionEvent::NoneSelected; bool mFunctionTimerActive = false; - static AppTask sAppTask; #if CONFIG_CHIP_FACTORY_DATA chip::DeviceLayer::FactoryDataProvider mFactoryDataProvider; #endif }; - -inline AppTask & GetAppTask(void) -{ - return AppTask::sAppTask; -} diff --git a/examples/pump-app/nrfconnect/main/include/PumpManager.h b/examples/pump-app/nrfconnect/main/include/PumpManager.h index eb2f0868e1242a..5c1bec2919266f 100644 --- a/examples/pump-app/nrfconnect/main/include/PumpManager.h +++ b/examples/pump-app/nrfconnect/main/include/PumpManager.h @@ -85,9 +85,9 @@ class PumpManager void StartTimer(uint32_t aTimeoutMs); static void TimerEventHandler(k_timer * timer); - static void AutoRestartTimerEventHandler(AppEvent * aEvent); + static void AutoRestartTimerEventHandler(const AppEvent & aEvent); - static void PumpStartTimerEventHandler(AppEvent * aEvent); + static void PumpStartTimerEventHandler(const AppEvent & aEvent); static PumpManager sPump; }; diff --git a/examples/pump-app/nrfconnect/main/main.cpp b/examples/pump-app/nrfconnect/main/main.cpp index a20f86082131a4..93a0062a0ef219 100644 --- a/examples/pump-app/nrfconnect/main/main.cpp +++ b/examples/pump-app/nrfconnect/main/main.cpp @@ -21,13 +21,13 @@ #include -LOG_MODULE_REGISTER(app, CONFIG_MATTER_LOG_LEVEL); +LOG_MODULE_REGISTER(app, CONFIG_CHIP_APP_LOG_LEVEL); using namespace ::chip; int main() { - CHIP_ERROR err = GetAppTask().StartApp(); + CHIP_ERROR err = AppTask::Instance().StartApp(); LOG_ERR("Exited with code %" CHIP_ERROR_FORMAT, err.Format()); return err == CHIP_NO_ERROR ? EXIT_SUCCESS : EXIT_FAILURE; diff --git a/examples/pump-app/nrfconnect/prj.conf b/examples/pump-app/nrfconnect/prj.conf index 916358d65905c5..3195f884e65720 100644 --- a/examples/pump-app/nrfconnect/prj.conf +++ b/examples/pump-app/nrfconnect/prj.conf @@ -21,22 +21,20 @@ CONFIG_STD_CPP14=y # samples. This file should contain only options specific for this sample # or overrides of default values. +# Enable CHIP +CONFIG_CHIP=y +CONFIG_CHIP_PROJECT_CONFIG="main/include/CHIPProjectConfig.h" +# 32784 == 0x8010 (example pump-app) +CONFIG_CHIP_DEVICE_PRODUCT_ID=32784 +CONFIG_STD_CPP14=y + # Add support for LEDs and buttons on Nordic development kits CONFIG_DK_LIBRARY=y -# OpenThread configs -CONFIG_OPENTHREAD_NORDIC_LIBRARY_MTD=y -CONFIG_OPENTHREAD_MTD=y -CONFIG_OPENTHREAD_FTD=n - -# Bluetooth overrides +# Bluetooth Low Energy configuration CONFIG_BT_DEVICE_NAME="MatterPump" -# Additional configs for debbugging experience. +# Other settings CONFIG_THREAD_NAME=y CONFIG_MPU_STACK_GUARD=y - -# CHIP configuration -CONFIG_CHIP_PROJECT_CONFIG="main/include/CHIPProjectConfig.h" -# 32784 == 0x8010 (example pump-app) -CONFIG_CHIP_DEVICE_PRODUCT_ID=32784 +CONFIG_RESET_ON_FATAL_ERROR=n diff --git a/examples/pump-app/nrfconnect/prj_no_dfu.conf b/examples/pump-app/nrfconnect/prj_no_dfu.conf index 8c102a90dc41d4..67c31871ce797f 100644 --- a/examples/pump-app/nrfconnect/prj_no_dfu.conf +++ b/examples/pump-app/nrfconnect/prj_no_dfu.conf @@ -14,32 +14,30 @@ # limitations under the License. # -CONFIG_CHIP=y -CONFIG_STD_CPP14=y - # This sample uses Kconfig.defaults to set options common for all # samples. This file should contain only options specific for this sample # or overrides of default values. +# Enable CHIP +CONFIG_CHIP=y +CONFIG_CHIP_PROJECT_CONFIG="main/include/CHIPProjectConfig.h" +# 32784 == 0x8010 (example pump-app) +CONFIG_CHIP_DEVICE_PRODUCT_ID=32784 +CONFIG_STD_CPP14=y + # Add support for LEDs and buttons on Nordic development kits CONFIG_DK_LIBRARY=y -# OpenThread configs -CONFIG_OPENTHREAD_NORDIC_LIBRARY_MTD=y -CONFIG_OPENTHREAD_MTD=y -CONFIG_OPENTHREAD_FTD=n - -# Bluetooth overrides +# Bluetooth Low Energy configuration CONFIG_BT_DEVICE_NAME="MatterPump" -# Additional configs for debbugging experience. +# Other settings CONFIG_THREAD_NAME=y CONFIG_MPU_STACK_GUARD=y +CONFIG_RESET_ON_FATAL_ERROR=n # Disable Matter OTA DFU CONFIG_CHIP_OTA_REQUESTOR=n -# CHIP configuration -CONFIG_CHIP_PROJECT_CONFIG="main/include/CHIPProjectConfig.h" -# 32784 == 0x8010 (example pump-app) -CONFIG_CHIP_DEVICE_PRODUCT_ID=32784 +# Disable QSPI NOR +CONFIG_CHIP_QSPI_NOR=n diff --git a/examples/pump-app/nrfconnect/prj_release.conf b/examples/pump-app/nrfconnect/prj_release.conf index 01c70c0217c02e..adb09f72ef3db2 100644 --- a/examples/pump-app/nrfconnect/prj_release.conf +++ b/examples/pump-app/nrfconnect/prj_release.conf @@ -14,31 +14,28 @@ # limitations under the License. # -CONFIG_CHIP=y -CONFIG_STD_CPP14=y - # This sample uses Kconfig.defaults to set options common for all # samples. This file should contain only options specific for this sample # or overrides of default values. +# Enable CHIP +CONFIG_CHIP=y +CONFIG_CHIP_PROJECT_CONFIG="main/include/CHIPProjectConfig.h" +# 32784 == 0x8010 (example pump-app) +CONFIG_CHIP_DEVICE_PRODUCT_ID=32784 +CONFIG_STD_CPP14=y + # Add support for LEDs and buttons on Nordic development kits CONFIG_DK_LIBRARY=y -# OpenThread configs -CONFIG_OPENTHREAD_NORDIC_LIBRARY_MTD=y -CONFIG_OPENTHREAD_MTD=y -CONFIG_OPENTHREAD_FTD=n - -# Bluetooth overrides +# Bluetooth Low Energy configuration CONFIG_BT_DEVICE_NAME="MatterPump" # Enable system reset on fatal error CONFIG_RESET_ON_FATAL_ERROR=y -# CHIP configuration -CONFIG_CHIP_PROJECT_CONFIG="main/include/CHIPProjectConfig.h" -# 32784 == 0x8010 (example pump-app) -CONFIG_CHIP_DEVICE_PRODUCT_ID=32784 +# Suspend devices when the CPU goes into sleep +CONFIG_PM_DEVICE=y # Disable all debug features CONFIG_SHELL=n @@ -48,7 +45,8 @@ CONFIG_UART_CONSOLE=n CONFIG_SERIAL=n CONFIG_LOG=n CONFIG_LOG_MODE_MINIMAL=n -CONFIG_ASSERT_NO_FILE_INFO=y CONFIG_ASSERT_VERBOSE=n +CONFIG_ASSERT_NO_FILE_INFO=y CONFIG_PRINTK=n +CONFIG_PRINTK_SYNC=n CONFIG_THREAD_NAME=n diff --git a/examples/pump-controller-app/nrfconnect/Kconfig b/examples/pump-controller-app/nrfconnect/Kconfig index 94a99f971ac195..d5eb64a514079a 100644 --- a/examples/pump-controller-app/nrfconnect/Kconfig +++ b/examples/pump-controller-app/nrfconnect/Kconfig @@ -15,6 +15,19 @@ # mainmenu "Matter nRF Connect Lighting Example Application" +# Sample configuration used for Thread networking +if NET_L2_OPENTHREAD + +choice OPENTHREAD_NORDIC_LIBRARY_CONFIGURATION + default OPENTHREAD_NORDIC_LIBRARY_MTD +endchoice + +choice OPENTHREAD_DEVICE_TYPE + default OPENTHREAD_MTD +endchoice + +endif # NET_L2_OPENTHREAD + rsource "../../../config/nrfconnect/chip-module/Kconfig.features" rsource "../../../config/nrfconnect/chip-module/Kconfig.defaults" source "Kconfig.zephyr" diff --git a/examples/pump-controller-app/nrfconnect/README.md b/examples/pump-controller-app/nrfconnect/README.md index c353a93e889416..8fb710d4d272f2 100644 --- a/examples/pump-controller-app/nrfconnect/README.md +++ b/examples/pump-controller-app/nrfconnect/README.md @@ -54,14 +54,19 @@ and [Zephyr RTOS](https://zephyrproject.org/). Visit CHIP's [nRF Connect platform overview](../../../docs/guides/nrfconnect_platform_overview.md) to read more about the platform structure and dependencies. -The CHIP device that runs the pump application is controlled by the Matter -controller device over the Thread protocol. By default, the Matter device has -Thread disabled, and it should be paired with Matter controller and get -configuration from it. Some actions required before establishing full -communication are described below. - -The example can be configured to use the secure bootloader and utilize it for -performing over-the-air Device Firmware Upgrade using Bluetooth LE. +The Matter device that runs the pump application is controlled by the Matter +controller device over the Thread protocol. By default, the Matter accessory +device has IPv6 networking disabled. You must pair it with the Matter controller +over Bluetooth® LE to get the configuration from the controller to use the +device within a Thread or Wi-Fi network. You have to make the device +discoverable manually (for security reasons). See +[Bluetooth LE advertising](#bluetooth-le-advertising) to learn how to do this. +The controller must get the commissioning information from the Matter accessory +device and provision the device into the network. + +You can test this application remotely over the Thread or the Wi-Fi protocol, +which in either case requires more devices, including a Matter controller that +you can configure either on a PC or a mobile device. ### Bluetooth LE advertising @@ -188,8 +193,8 @@ following states are possible: Bluetooth LE. - _Short Flash Off (950ms on/50ms off)_ — The device is fully - provisioned, but does not yet have full Thread network or service - connectivity. + provisioned, but does not yet have full connectivity for Thread or Wi-Fi + network. - _Solid On_ — The device is fully provisioned and has full Thread network and service connectivity. diff --git a/examples/pump-controller-app/nrfconnect/main/AppTask.cpp b/examples/pump-controller-app/nrfconnect/main/AppTask.cpp index ea4bf106e0c437..248b39255cbd51 100644 --- a/examples/pump-controller-app/nrfconnect/main/AppTask.cpp +++ b/examples/pump-controller-app/nrfconnect/main/AppTask.cpp @@ -18,6 +18,7 @@ #include "AppTask.h" #include "AppConfig.h" +#include "LEDUtil.h" #include "LEDWidget.h" #include "PumpManager.h" @@ -45,42 +46,52 @@ #include #include +LOG_MODULE_DECLARE(app, CONFIG_CHIP_APP_LOG_LEVEL); + using namespace ::chip; using namespace ::chip::app; using namespace ::chip::Credentials; using namespace ::chip::DeviceLayer; -#define FACTORY_RESET_TRIGGER_TIMEOUT 3000 -#define FACTORY_RESET_CANCEL_WINDOW_TIMEOUT 3000 -#define APP_EVENT_QUEUE_SIZE 10 -#define BUTTON_PUSH_EVENT 1 -#define BUTTON_RELEASE_EVENT 0 - namespace { +constexpr uint32_t kFactoryResetTriggerTimeout = 3000; +constexpr uint32_t kFactoryResetCancelWindowTimeout = 3000; +constexpr size_t kAppEventQueueSize = 10; // NOTE! This key is for test/certification only and should not be available in production devices! // If CONFIG_CHIP_FACTORY_DATA is enabled, this value is read from the factory data. uint8_t sTestEventTriggerEnableKey[TestEventTriggerDelegate::kEnableKeyLength] = { 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff }; -LOG_MODULE_DECLARE(app, CONFIG_MATTER_LOG_LEVEL); -K_MSGQ_DEFINE(sAppEventQueue, sizeof(AppEvent), APP_EVENT_QUEUE_SIZE, alignof(AppEvent)); +K_MSGQ_DEFINE(sAppEventQueue, sizeof(AppEvent), kAppEventQueueSize, alignof(AppEvent)); k_timer sFunctionTimer; LEDWidget sStatusLED; LEDWidget sPumpStateLED; -LEDWidget sUnusedLED; -LEDWidget sUnusedLED_1; +FactoryResetLEDsWrapper<2> sFactoryResetLEDs{ { FACTORY_RESET_SIGNAL_LED, FACTORY_RESET_SIGNAL_LED1 } }; -bool sIsThreadProvisioned = false; -bool sIsThreadEnabled = false; -bool sHaveBLEConnections = false; +bool sIsNetworkProvisioned = false; +bool sIsNetworkEnabled = false; +bool sHaveBLEConnections = false; chip::DeviceLayer::DeviceInfoProviderImpl gExampleDeviceInfoProvider; } // namespace -AppTask AppTask::sAppTask; +namespace LedConsts { +constexpr uint32_t kBlinkRate_ms{ 500 }; +namespace StatusLed { +namespace Unprovisioned { +constexpr uint32_t kOn_ms{ 100 }; +constexpr uint32_t kOff_ms{ kOn_ms }; +} // namespace Unprovisioned +namespace Provisioned { +constexpr uint32_t kOn_ms{ 50 }; +constexpr uint32_t kOff_ms{ 950 }; +} // namespace Provisioned + +} // namespace StatusLed +} // namespace LedConsts CHIP_ERROR AppTask::Init() { @@ -127,9 +138,6 @@ CHIP_ERROR AppTask::Init() sPumpStateLED.Init(PUMP_STATE_LED); sPumpStateLED.Set(!PumpMgr().IsStopped()); - sUnusedLED.Init(DK_LED3); - sUnusedLED_1.Init(DK_LED4); - UpdateStatusLED(); PumpMgr().Init(); @@ -144,7 +152,7 @@ CHIP_ERROR AppTask::Init() } // Initialize function button timer - k_timer_init(&sFunctionTimer, &AppTask::TimerEventHandler, nullptr); + k_timer_init(&sFunctionTimer, &AppTask::FunctionTimerTimeoutCallback, nullptr); k_timer_user_data_set(&sFunctionTimer, this); #ifdef CONFIG_MCUMGR_SMP_BT @@ -205,100 +213,104 @@ CHIP_ERROR AppTask::StartApp() while (true) { k_msgq_get(&sAppEventQueue, &event, K_FOREVER); - DispatchEvent(&event); + DispatchEvent(event); } return CHIP_NO_ERROR; } -void AppTask::StartActionEventHandler(AppEvent * aEvent) +void AppTask::StartActionEventHandler(const AppEvent & event) { PumpManager::Action_t action = PumpManager::INVALID_ACTION; int32_t actor = 0; - if (aEvent->Type == AppEvent::kEventType_Start) + if (event.Type == AppEventType::Start) { - action = static_cast(aEvent->StartEvent.Action); - actor = aEvent->StartEvent.Actor; + action = static_cast(event.StartEvent.Action); + actor = event.StartEvent.Actor; } - else if (aEvent->Type == AppEvent::kEventType_Button) + else if (event.Type == AppEventType::Button) { action = PumpMgr().IsStopped() ? PumpManager::START_ACTION : PumpManager::STOP_ACTION; - actor = AppEvent::kEventType_Button; + actor = static_cast(AppEventType::Button); } if (action != PumpManager::INVALID_ACTION && !PumpMgr().InitiateAction(actor, action)) LOG_INF("Action is already in progress or active."); } -void AppTask::ButtonEventHandler(uint32_t button_state, uint32_t has_changed) +void AppTask::ButtonEventHandler(uint32_t buttonState, uint32_t hasChanged) { - AppEvent button_event; - button_event.Type = AppEvent::kEventType_Button; + AppEvent event; + event.Type = AppEventType::Button; - if (START_BUTTON_MASK & button_state & has_changed) + if (START_BUTTON_MASK & buttonState & hasChanged) { - button_event.ButtonEvent.PinNo = START_BUTTON; - button_event.ButtonEvent.Action = BUTTON_PUSH_EVENT; - button_event.Handler = StartActionEventHandler; - sAppTask.PostEvent(&button_event); + event.ButtonEvent.PinNo = START_BUTTON; + event.ButtonEvent.Action = static_cast(AppEventType::Button); + event.Handler = StartActionEventHandler; + PostEvent(event); } - if (FUNCTION_BUTTON_MASK & has_changed) + if (FUNCTION_BUTTON_MASK & hasChanged) { - button_event.ButtonEvent.PinNo = FUNCTION_BUTTON; - button_event.ButtonEvent.Action = (FUNCTION_BUTTON_MASK & button_state) ? BUTTON_PUSH_EVENT : BUTTON_RELEASE_EVENT; - button_event.Handler = FunctionHandler; - sAppTask.PostEvent(&button_event); + event.ButtonEvent.PinNo = FUNCTION_BUTTON; + event.ButtonEvent.Action = + static_cast((FUNCTION_BUTTON_MASK & buttonState) ? AppEventType::ButtonPushed : AppEventType::ButtonReleased); + event.Handler = FunctionHandler; + PostEvent(event); } - if (BLE_ADVERTISEMENT_START_BUTTON_MASK & button_state & has_changed) + if (BLE_ADVERTISEMENT_START_BUTTON_MASK & buttonState & hasChanged) { - button_event.ButtonEvent.PinNo = BLE_ADVERTISEMENT_START_BUTTON; - button_event.ButtonEvent.Action = BUTTON_PUSH_EVENT; - button_event.Handler = StartBLEAdvertisementHandler; - sAppTask.PostEvent(&button_event); + event.ButtonEvent.PinNo = BLE_ADVERTISEMENT_START_BUTTON; + event.ButtonEvent.Action = static_cast(AppEventType::ButtonPushed); + event.Handler = StartBLEAdvertisementHandler; + PostEvent(event); } } -void AppTask::TimerEventHandler(k_timer * timer) +void AppTask::FunctionTimerTimeoutCallback(k_timer * timer) { + if (!timer) + { + return; + } + AppEvent event; - event.Type = AppEvent::kEventType_Timer; + event.Type = AppEventType::Timer; event.TimerEvent.Context = k_timer_user_data_get(timer); event.Handler = FunctionTimerEventHandler; - sAppTask.PostEvent(&event); + PostEvent(event); } -void AppTask::FunctionTimerEventHandler(AppEvent * aEvent) +void AppTask::FunctionTimerEventHandler(const AppEvent & event) { - if (aEvent->Type != AppEvent::kEventType_Timer) + if (event.Type != AppEventType::Timer) return; - // If we reached here, the button was held past FACTORY_RESET_TRIGGER_TIMEOUT, initiate factory reset - if (sAppTask.mFunctionTimerActive && sAppTask.mFunction == kFunction_SoftwareUpdate) + // If we reached here, the button was held past kFactoryResetTriggerTimeout, initiate factory reset + if (Instance().mFunctionTimerActive && Instance().mFunction == FunctionEvent::SoftwareUpdate) { - LOG_INF("Factory Reset Triggered. Release button within %ums to cancel.", FACTORY_RESET_TRIGGER_TIMEOUT); + LOG_INF("Factory Reset Triggered. Release button within %ums to cancel.", kFactoryResetTriggerTimeout); - // Start timer for FACTORY_RESET_CANCEL_WINDOW_TIMEOUT to allow user to cancel, if required. - sAppTask.StartTimer(FACTORY_RESET_CANCEL_WINDOW_TIMEOUT); - sAppTask.mFunction = kFunction_FactoryReset; + // Start timer for kFactoryResetCancelWindowTimeout to allow user to cancel, if required. + Instance().StartTimer(kFactoryResetCancelWindowTimeout); + Instance().mFunction = FunctionEvent::FactoryReset; +#ifdef CONFIG_STATE_LEDS // Turn off all LEDs before starting blink to make sure blink is co-ordinated. sStatusLED.Set(false); - sPumpStateLED.Set(false); - sUnusedLED_1.Set(false); - sUnusedLED.Set(false); + sFactoryResetLEDs.Set(false); - sStatusLED.Blink(500); - sPumpStateLED.Blink(500); - sUnusedLED.Blink(500); - sUnusedLED_1.Blink(500); + sStatusLED.Blink(LedConsts::kBlinkRate_ms); + sFactoryResetLEDs.Blink(LedConsts::kBlinkRate_ms); +#endif } - else if (sAppTask.mFunctionTimerActive && sAppTask.mFunction == kFunction_FactoryReset) + else if (Instance().mFunctionTimerActive && Instance().mFunction == FunctionEvent::FactoryReset) { // Actually trigger Factory Reset - sAppTask.mFunction = kFunction_NoneSelected; + Instance().mFunction = FunctionEvent::NoneSelected; chip::Server::GetInstance().ScheduleFactoryReset(); } @@ -308,65 +320,62 @@ void AppTask::FunctionTimerEventHandler(AppEvent * aEvent) void AppTask::RequestSMPAdvertisingStart(void) { AppEvent event; - event.Type = AppEvent::kEventType_StartSMPAdvertising; - event.Handler = [](AppEvent *) { GetDFUOverSMP().StartBLEAdvertising(); }; - sAppTask.PostEvent(&event); + event.Type = AppEventType::StartSMPAdvertising; + event.Handler = [](const AppEvent &) { GetDFUOverSMP().StartBLEAdvertising(); }; + PostEvent(event); } #endif -void AppTask::FunctionHandler(AppEvent * aEvent) +void AppTask::FunctionHandler(const AppEvent & event) { - if (aEvent->ButtonEvent.PinNo != FUNCTION_BUTTON) + if (event.ButtonEvent.PinNo != FUNCTION_BUTTON) return; - // To trigger software update: press the FUNCTION_BUTTON button briefly (< FACTORY_RESET_TRIGGER_TIMEOUT) // To initiate factory reset: press the FUNCTION_BUTTON for FACTORY_RESET_TRIGGER_TIMEOUT + FACTORY_RESET_CANCEL_WINDOW_TIMEOUT // All LEDs start blinking after FACTORY_RESET_TRIGGER_TIMEOUT to signal factory reset has been initiated. // To cancel factory reset: release the FUNCTION_BUTTON once all LEDs start blinking within the // FACTORY_RESET_CANCEL_WINDOW_TIMEOUT - if (aEvent->ButtonEvent.Action == BUTTON_PUSH_EVENT) + if (event.ButtonEvent.Action == static_cast(AppEventType::ButtonPushed)) { - if (!sAppTask.mFunctionTimerActive && sAppTask.mFunction == kFunction_NoneSelected) + if (!Instance().mFunctionTimerActive && Instance().mFunction == FunctionEvent::NoneSelected) { - sAppTask.StartTimer(FACTORY_RESET_TRIGGER_TIMEOUT); + Instance().StartTimer(kFactoryResetTriggerTimeout); - sAppTask.mFunction = kFunction_SoftwareUpdate; + Instance().mFunction = FunctionEvent::SoftwareUpdate; } } else { // If the button was released before factory reset got initiated, trigger a software update. - if (sAppTask.mFunctionTimerActive && sAppTask.mFunction == kFunction_SoftwareUpdate) + if (Instance().mFunctionTimerActive && Instance().mFunction == FunctionEvent::SoftwareUpdate) { - sAppTask.CancelTimer(); - sAppTask.mFunction = kFunction_NoneSelected; + Instance().CancelTimer(); #ifdef CONFIG_MCUMGR_SMP_BT GetDFUOverSMP().StartServer(); #else LOG_INF("Software update is disabled"); #endif + Instance().mFunction = FunctionEvent::NoneSelected; } - else if (sAppTask.mFunctionTimerActive && sAppTask.mFunction == kFunction_FactoryReset) + else if (Instance().mFunctionTimerActive && Instance().mFunction == FunctionEvent::FactoryReset) { - sUnusedLED.Set(false); - sUnusedLED_1.Set(false); - - // Set pump state LED back to show state of pump. - sPumpStateLED.Set(!PumpMgr().IsStopped()); + sFactoryResetLEDs.Set(false); UpdateStatusLED(); - sAppTask.CancelTimer(); - - // Change the function to none selected since factory reset has been canceled. - sAppTask.mFunction = kFunction_NoneSelected; - + Instance().CancelTimer(); + Instance().mFunction = FunctionEvent::NoneSelected; LOG_INF("Factory Reset has been Canceled"); } + else if (Instance().mFunctionTimerActive) + { + CancelTimer(); + Instance().mFunction = FunctionEvent::NoneSelected; + } } } -void AppTask::StartBLEAdvertisementHandler(AppEvent *) +void AppTask::StartBLEAdvertisementHandler(const AppEvent &) { if (Server::GetInstance().GetFabricTable().FabricCount() != 0) { @@ -386,45 +395,47 @@ void AppTask::StartBLEAdvertisementHandler(AppEvent *) } } -void AppTask::UpdateLedStateEventHandler(AppEvent * aEvent) +void AppTask::UpdateLedStateEventHandler(const AppEvent & event) { - if (aEvent->Type == AppEvent::kEventType_UpdateLedState) + if (event.Type == AppEventType::UpdateLedState) { - aEvent->UpdateLedStateEvent.LedWidget->UpdateState(); + event.UpdateLedStateEvent.LedWidget->UpdateState(); } } void AppTask::LEDStateUpdateHandler(LEDWidget & ledWidget) { AppEvent event; - event.Type = AppEvent::kEventType_UpdateLedState; + event.Type = AppEventType::UpdateLedState; event.Handler = UpdateLedStateEventHandler; event.UpdateLedStateEvent.LedWidget = &ledWidget; - sAppTask.PostEvent(&event); + PostEvent(event); } void AppTask::UpdateStatusLED() { - /* Update the status LED. - * - * If thread and service provisioned, keep the LED On constantly. - * - * If the system has ble connection(s) uptill the stage above, THEN blink the LED at an even - * rate of 100ms. - * - * Otherwise, blink the LED On for a very short time. */ - if (sIsThreadProvisioned && sIsThreadEnabled) +#ifdef CONFIG_STATE_LEDS + // Update the status LED. + // + // If thread and service provisioned, keep the LED On constantly. + // + // If the system has ble connection(s) uptill the stage above, THEN blink the LED at an even + // rate of 100ms. + // + // Otherwise, blink the LED On for a very short time. + if (sIsNetworkProvisioned && sIsNetworkEnabled) { sStatusLED.Set(true); } else if (sHaveBLEConnections) { - sStatusLED.Blink(100, 100); + sStatusLED.Blink(LedConsts::StatusLed::Unprovisioned::kOn_ms, LedConsts::StatusLed::Unprovisioned::kOff_ms); } else { - sStatusLED.Blink(50, 950); + sStatusLED.Blink(LedConsts::StatusLed::Provisioned::kOn_ms, LedConsts::StatusLed::Provisioned::kOff_ms); } +#endif } void AppTask::ChipEventHandler(const ChipDeviceEvent * event, intptr_t /* arg */) @@ -432,21 +443,8 @@ void AppTask::ChipEventHandler(const ChipDeviceEvent * event, intptr_t /* arg */ switch (event->Type) { case DeviceEventType::kCHIPoBLEAdvertisingChange: - if (event->CHIPoBLEAdvertisingChange.Result == kActivity_Stopped) - { #ifdef CONFIG_CHIP_NFC_COMMISSIONING - NFCMgr().StopTagEmulation(); -#endif -#ifdef CONFIG_MCUMGR_SMP_BT - // After CHIPoBLE advertising stop, start advertising SMP in case Thread is enabled or there are no active CHIPoBLE - // connections (exclude the case when CHIPoBLE advertising is stopped on the connection time) - if (GetDFUOverSMP().IsEnabled() && - (ConnectivityMgr().IsThreadProvisioned() || ConnectivityMgr().NumBLEConnections() == 0)) - sAppTask.RequestSMPAdvertisingStart(); -#endif - } -#ifdef CONFIG_CHIP_NFC_COMMISSIONING - else if (event->CHIPoBLEAdvertisingChange.Result == kActivity_Started) + if (event->CHIPoBLEAdvertisingChange.Result == kActivity_Started) { if (NFCMgr().IsTagEmulationStarted()) { @@ -457,13 +455,17 @@ void AppTask::ChipEventHandler(const ChipDeviceEvent * event, intptr_t /* arg */ ShareQRCodeOverNFC(chip::RendezvousInformationFlags(chip::RendezvousInformationFlag::kBLE)); } } + else if (event->CHIPoBLEAdvertisingChange.Result == kActivity_Stopped) + { + NFCMgr().StopTagEmulation(); + } #endif sHaveBLEConnections = ConnectivityMgr().NumBLEConnections() != 0; UpdateStatusLED(); break; case DeviceEventType::kThreadStateChange: - sIsThreadProvisioned = ConnectivityMgr().IsThreadProvisioned(); - sIsThreadEnabled = ConnectivityMgr().IsThreadEnabled(); + sIsNetworkProvisioned = ConnectivityMgr().IsThreadProvisioned(); + sIsNetworkEnabled = ConnectivityMgr().IsThreadEnabled(); UpdateStatusLED(); break; case DeviceEventType::kDnssdPlatformInitialized: @@ -479,24 +481,24 @@ void AppTask::ChipEventHandler(const ChipDeviceEvent * event, intptr_t /* arg */ void AppTask::CancelTimer() { k_timer_stop(&sFunctionTimer); - mFunctionTimerActive = false; + Instance().mFunctionTimerActive = false; } void AppTask::StartTimer(uint32_t aTimeoutInMs) { k_timer_start(&sFunctionTimer, K_MSEC(aTimeoutInMs), K_NO_WAIT); - mFunctionTimerActive = true; + Instance().mFunctionTimerActive = true; } -void AppTask::ActionInitiated(PumpManager::Action_t aAction, int32_t aActor) +void AppTask::ActionInitiated(PumpManager::Action_t action, int32_t actor) { // If the action has been initiated by the pump, update the pump trait // and start flashing the LEDs rapidly to indicate action initiation. - if (aAction == PumpManager::START_ACTION) + if (action == PumpManager::START_ACTION) { LOG_INF("Pump Start Action has been initiated"); } - else if (aAction == PumpManager::STOP_ACTION) + else if (action == PumpManager::STOP_ACTION) { LOG_INF("Pump Stop Action has been initiated"); } @@ -504,51 +506,51 @@ void AppTask::ActionInitiated(PumpManager::Action_t aAction, int32_t aActor) sPumpStateLED.Blink(50, 50); } -void AppTask::ActionCompleted(PumpManager::Action_t aAction, int32_t aActor) +void AppTask::ActionCompleted(PumpManager::Action_t action, int32_t actor) { // If the action has been completed by the pump, update the pump trait. // Turn on the pump state LED if in a STARTED state OR // Turn off the pump state LED if in a STOPPED state. - if (aAction == PumpManager::START_ACTION) + if (action == PumpManager::START_ACTION) { LOG_INF("Pump Start Action has been completed"); sPumpStateLED.Set(true); } - else if (aAction == PumpManager::STOP_ACTION) + else if (action == PumpManager::STOP_ACTION) { LOG_INF("Pump Stop Action has been completed"); sPumpStateLED.Set(false); } - if (aActor == AppEvent::kEventType_Button) + if (actor == static_cast(AppEventType::Button)) { - sAppTask.UpdateClusterState(); + Instance().UpdateClusterState(); } } -void AppTask::PostStartActionRequest(int32_t aActor, PumpManager::Action_t aAction) +void AppTask::PostStartActionRequest(int32_t actor, PumpManager::Action_t action) { AppEvent event; - event.Type = AppEvent::kEventType_Start; - event.StartEvent.Actor = aActor; - event.StartEvent.Action = aAction; + event.Type = AppEventType::Start; + event.StartEvent.Actor = actor; + event.StartEvent.Action = action; event.Handler = StartActionEventHandler; - PostEvent(&event); + PostEvent(event); } -void AppTask::PostEvent(AppEvent * aEvent) +void AppTask::PostEvent(const AppEvent & event) { - if (k_msgq_put(&sAppEventQueue, aEvent, K_NO_WAIT)) + if (k_msgq_put(&sAppEventQueue, &event, K_NO_WAIT) != 0) { LOG_INF("Failed to post event to app task event queue"); } } -void AppTask::DispatchEvent(AppEvent * aEvent) +void AppTask::DispatchEvent(const AppEvent & event) { - if (aEvent->Handler) + if (event.Handler) { - aEvent->Handler(aEvent); + event.Handler(event); } else { diff --git a/examples/pump-controller-app/nrfconnect/main/PumpManager.cpp b/examples/pump-controller-app/nrfconnect/main/PumpManager.cpp index a7f654baeeead6..2b6215dc896938 100644 --- a/examples/pump-controller-app/nrfconnect/main/PumpManager.cpp +++ b/examples/pump-controller-app/nrfconnect/main/PumpManager.cpp @@ -25,7 +25,7 @@ #include #include -LOG_MODULE_DECLARE(app, CONFIG_MATTER_LOG_LEVEL); +LOG_MODULE_DECLARE(app, CONFIG_CHIP_APP_LOG_LEVEL); static k_timer sStartTimer; @@ -130,15 +130,15 @@ void PumpManager::TimerEventHandler(k_timer * timer) // once sStartTimer expires. Post an event to apptask queue with the actual handler // so that the event can be handled in the context of the apptask. AppEvent event; - event.Type = AppEvent::kEventType_Timer; + event.Type = AppEventType::Timer; event.TimerEvent.Context = pump; event.Handler = pump->mAutoStartTimerArmed ? AutoRestartTimerEventHandler : PumpStartTimerEventHandler; - GetAppTask().PostEvent(&event); + AppTask::Instance().PostEvent(event); } -void PumpManager::AutoRestartTimerEventHandler(AppEvent * aEvent) +void PumpManager::AutoRestartTimerEventHandler(const AppEvent & aEvent) { - PumpManager * pump = static_cast(aEvent->TimerEvent.Context); + PumpManager * pump = static_cast(aEvent.TimerEvent.Context); int32_t actor = 0; // Make sure auto start timer is still armed. @@ -152,11 +152,11 @@ void PumpManager::AutoRestartTimerEventHandler(AppEvent * aEvent) pump->InitiateAction(actor, START_ACTION); } -void PumpManager::PumpStartTimerEventHandler(AppEvent * aEvent) +void PumpManager::PumpStartTimerEventHandler(const AppEvent & aEvent) { Action_t actionCompleted = INVALID_ACTION; - PumpManager * pump = static_cast(aEvent->TimerEvent.Context); + PumpManager * pump = static_cast(aEvent.TimerEvent.Context); if (pump->mState == kState_StartInitiated) { diff --git a/examples/pump-controller-app/nrfconnect/main/ZclCallbacks.cpp b/examples/pump-controller-app/nrfconnect/main/ZclCallbacks.cpp index 49ec8f15946d77..6a1c4334a4eef5 100644 --- a/examples/pump-controller-app/nrfconnect/main/ZclCallbacks.cpp +++ b/examples/pump-controller-app/nrfconnect/main/ZclCallbacks.cpp @@ -51,5 +51,5 @@ void MatterPostAttributeChangeCallback(const chip::app::ConcreteAttributePath & */ void emberAfOnOffClusterInitCallback(EndpointId endpoint) { - GetAppTask().UpdateClusterState(); + AppTask::Instance().UpdateClusterState(); } diff --git a/examples/pump-controller-app/nrfconnect/main/include/AppConfig.h b/examples/pump-controller-app/nrfconnect/main/include/AppConfig.h index e63e9d8f2a3588..a6a172ef231052 100644 --- a/examples/pump-controller-app/nrfconnect/main/include/AppConfig.h +++ b/examples/pump-controller-app/nrfconnect/main/include/AppConfig.h @@ -29,6 +29,8 @@ #define SYSTEM_STATE_LED DK_LED1 #define PUMP_STATE_LED DK_LED2 +#define FACTORY_RESET_SIGNAL_LED DK_LED3 +#define FACTORY_RESET_SIGNAL_LED1 DK_LED4 // Time it takes in ms for the simulated pump to move from one state to another. #define PUMP_START_PERIOS_MS 2000 diff --git a/examples/pump-controller-app/nrfconnect/main/include/AppEvent.h b/examples/pump-controller-app/nrfconnect/main/include/AppEvent.h index 2f2a0d99f74b0f..3dfff7913f3a47 100644 --- a/examples/pump-controller-app/nrfconnect/main/include/AppEvent.h +++ b/examples/pump-controller-app/nrfconnect/main/include/AppEvent.h @@ -20,27 +20,33 @@ #include -#include "LEDWidget.h" +#include "EventTypes.h" -struct AppEvent; -typedef void (*EventHandler)(AppEvent *); +class LEDWidget; -struct AppEvent +enum class AppEventType : uint8_t { - enum AppEventTypes - { - kEventType_Button = 0, - kEventType_Timer, - kEventType_Start, - kEventType_Install, - kEventType_UpdateLedState, -#ifdef CONFIG_MCUMGR_SMP_BT - kEventType_StartSMPAdvertising, -#endif - }; + None = 0, + Button, + ButtonPushed, + ButtonReleased, + Timer, + UpdateLedState, + StartSMPAdvertising, + Start, + Install +}; - uint16_t Type; +enum class FunctionEvent : uint8_t +{ + NoneSelected = 0, + SoftwareUpdate = 0, + FactoryReset, + AdvertisingStart +}; +struct AppEvent +{ union { struct @@ -63,5 +69,6 @@ struct AppEvent } UpdateLedStateEvent; }; + AppEventType Type{ AppEventType::None }; EventHandler Handler; }; diff --git a/examples/pump-controller-app/nrfconnect/main/include/AppTask.h b/examples/pump-controller-app/nrfconnect/main/include/AppTask.h index 29a2103925bdb2..5681b25b32313c 100644 --- a/examples/pump-controller-app/nrfconnect/main/include/AppTask.h +++ b/examples/pump-controller-app/nrfconnect/main/include/AppTask.h @@ -38,62 +38,48 @@ struct k_timer; class AppTask { public: + static AppTask & Instance(void) + { + static AppTask sAppTask; + return sAppTask; + }; + CHIP_ERROR StartApp(); - void PostStartActionRequest(int32_t aActor, PumpManager::Action_t aAction); - void PostEvent(AppEvent * event); - void UpdateClusterState(); + static void PostStartActionRequest(int32_t actor, PumpManager::Action_t action); + static void UpdateClusterState(); + static void PostEvent(const AppEvent & event); private: - friend AppTask & GetAppTask(void); - CHIP_ERROR Init(); - static void ActionInitiated(PumpManager::Action_t aAction, int32_t aActor); - static void ActionCompleted(PumpManager::Action_t aAction, int32_t aActor); + static void CancelTimer(); + static void StartTimer(uint32_t timeoutInMs); - void CancelTimer(void); + static void ActionInitiated(PumpManager::Action_t action, int32_t actor); + static void ActionCompleted(PumpManager::Action_t action, int32_t actor); - void DispatchEvent(AppEvent * event); - - static void UpdateStatusLED(); - static void LEDStateUpdateHandler(LEDWidget & ledWidget); - static void UpdateLedStateEventHandler(AppEvent * aEvent); - static void FunctionTimerEventHandler(AppEvent * aEvent); - static void FunctionHandler(AppEvent * aEvent); - static void StartActionEventHandler(AppEvent * aEvent); - static void StartBLEAdvertisementHandler(AppEvent * aEvent); + static void DispatchEvent(const AppEvent & event); + static void FunctionTimerEventHandler(const AppEvent & event); + static void FunctionHandler(const AppEvent & event); + static void StartBLEAdvertisementHandler(const AppEvent & event); + static void UpdateLedStateEventHandler(const AppEvent & event); + static void StartActionEventHandler(const AppEvent & event); static void ChipEventHandler(const chip::DeviceLayer::ChipDeviceEvent * event, intptr_t arg); - - static void ButtonEventHandler(uint32_t buttons_state, uint32_t has_changed); - static void TimerEventHandler(k_timer * timer); + static void ButtonEventHandler(uint32_t buttonState, uint32_t hasChanged); + static void LEDStateUpdateHandler(LEDWidget & ledWidget); + static void FunctionTimerTimeoutCallback(k_timer * timer); + static void UpdateStatusLED(); #ifdef CONFIG_MCUMGR_SMP_BT static void RequestSMPAdvertisingStart(void); #endif - void StartTimer(uint32_t aTimeoutInMs); - - enum Function_t - { - kFunction_NoneSelected = 0, - kFunction_SoftwareUpdate = 0, - kFunction_FactoryReset, - - kFunction_Invalid - }; - - Function_t mFunction = kFunction_NoneSelected; + FunctionEvent mFunction = FunctionEvent::NoneSelected; bool mFunctionTimerActive = false; - static AppTask sAppTask; #if CONFIG_CHIP_FACTORY_DATA chip::DeviceLayer::FactoryDataProvider mFactoryDataProvider; #endif }; - -inline AppTask & GetAppTask(void) -{ - return AppTask::sAppTask; -} diff --git a/examples/pump-controller-app/nrfconnect/main/include/PumpManager.h b/examples/pump-controller-app/nrfconnect/main/include/PumpManager.h index afc1b571e40acd..f8111e55c64ba2 100644 --- a/examples/pump-controller-app/nrfconnect/main/include/PumpManager.h +++ b/examples/pump-controller-app/nrfconnect/main/include/PumpManager.h @@ -71,9 +71,9 @@ class PumpManager void StartTimer(uint32_t aTimeoutMs); static void TimerEventHandler(k_timer * timer); - static void AutoRestartTimerEventHandler(AppEvent * aEvent); + static void AutoRestartTimerEventHandler(const AppEvent & aEvent); - static void PumpStartTimerEventHandler(AppEvent * aEvent); + static void PumpStartTimerEventHandler(const AppEvent & aEvent); static PumpManager sPump; }; diff --git a/examples/pump-controller-app/nrfconnect/main/main.cpp b/examples/pump-controller-app/nrfconnect/main/main.cpp index a20f86082131a4..93a0062a0ef219 100644 --- a/examples/pump-controller-app/nrfconnect/main/main.cpp +++ b/examples/pump-controller-app/nrfconnect/main/main.cpp @@ -21,13 +21,13 @@ #include -LOG_MODULE_REGISTER(app, CONFIG_MATTER_LOG_LEVEL); +LOG_MODULE_REGISTER(app, CONFIG_CHIP_APP_LOG_LEVEL); using namespace ::chip; int main() { - CHIP_ERROR err = GetAppTask().StartApp(); + CHIP_ERROR err = AppTask::Instance().StartApp(); LOG_ERR("Exited with code %" CHIP_ERROR_FORMAT, err.Format()); return err == CHIP_NO_ERROR ? EXIT_SUCCESS : EXIT_FAILURE; diff --git a/examples/pump-controller-app/nrfconnect/prj.conf b/examples/pump-controller-app/nrfconnect/prj.conf index f3936ac83a0291..4a38ec1177c699 100644 --- a/examples/pump-controller-app/nrfconnect/prj.conf +++ b/examples/pump-controller-app/nrfconnect/prj.conf @@ -14,29 +14,24 @@ # limitations under the License. # -CONFIG_CHIP=y -CONFIG_STD_CPP14=y - # This sample uses Kconfig.defaults to set options common for all # samples. This file should contain only options specific for this sample # or overrides of default values. +# Enable CHIP +CONFIG_CHIP=y +CONFIG_CHIP_PROJECT_CONFIG="main/include/CHIPProjectConfig.h" +# 32785 == 0x8011 (example pump-controller-app) +CONFIG_CHIP_DEVICE_PRODUCT_ID=32785 +CONFIG_STD_CPP14=y + # Add support for LEDs and buttons on Nordic development kits CONFIG_DK_LIBRARY=y -# OpenThread configs -CONFIG_OPENTHREAD_NORDIC_LIBRARY_MTD=y -CONFIG_OPENTHREAD_MTD=y -CONFIG_OPENTHREAD_FTD=n - -# Bluetooth overrides +# Bluetooth Low Energy configuration CONFIG_BT_DEVICE_NAME="MatterPumpCtrl" -# Additional configs for debbugging experience. +# Other settings CONFIG_THREAD_NAME=y CONFIG_MPU_STACK_GUARD=y - -# CHIP configuration -CONFIG_CHIP_PROJECT_CONFIG="main/include/CHIPProjectConfig.h" -# 32785 == 0x8011 (example pump-controller-app) -CONFIG_CHIP_DEVICE_PRODUCT_ID=32785 \ No newline at end of file +CONFIG_RESET_ON_FATAL_ERROR=n diff --git a/examples/pump-controller-app/nrfconnect/prj_no_dfu.conf b/examples/pump-controller-app/nrfconnect/prj_no_dfu.conf index f946af99d95204..dba7042ad82864 100644 --- a/examples/pump-controller-app/nrfconnect/prj_no_dfu.conf +++ b/examples/pump-controller-app/nrfconnect/prj_no_dfu.conf @@ -14,32 +14,31 @@ # limitations under the License. # -CONFIG_CHIP=y -CONFIG_STD_CPP14=y - # This sample uses Kconfig.defaults to set options common for all # samples. This file should contain only options specific for this sample # or overrides of default values. +# Enable CHIP +CONFIG_CHIP=y +CONFIG_CHIP_PROJECT_CONFIG="main/include/CHIPProjectConfig.h" +# 32785 == 0x8011 (example pump-controller-app) +CONFIG_CHIP_DEVICE_PRODUCT_ID=32785 + +CONFIG_STD_CPP14=y + # Add support for LEDs and buttons on Nordic development kits CONFIG_DK_LIBRARY=y -# OpenThread configs -CONFIG_OPENTHREAD_NORDIC_LIBRARY_MTD=y -CONFIG_OPENTHREAD_MTD=y -CONFIG_OPENTHREAD_FTD=n - -# Bluetooth overrides +# Bluetooth Low Energy configuration CONFIG_BT_DEVICE_NAME="MatterPumpCtrl" -# Additional configs for debbugging experience. +# Other settings CONFIG_THREAD_NAME=y CONFIG_MPU_STACK_GUARD=y +CONFIG_RESET_ON_FATAL_ERROR=n # Disable Matter OTA DFU CONFIG_CHIP_OTA_REQUESTOR=n -# CHIP configuration -CONFIG_CHIP_PROJECT_CONFIG="main/include/CHIPProjectConfig.h" -# 32785 == 0x8011 (example pump-controller-app) -CONFIG_CHIP_DEVICE_PRODUCT_ID=32785 +# Disable QSPI NOR +CONFIG_CHIP_QSPI_NOR=n diff --git a/examples/pump-controller-app/nrfconnect/prj_release.conf b/examples/pump-controller-app/nrfconnect/prj_release.conf index bf7f1a452896de..88ea95c777e1a1 100644 --- a/examples/pump-controller-app/nrfconnect/prj_release.conf +++ b/examples/pump-controller-app/nrfconnect/prj_release.conf @@ -14,31 +14,29 @@ # limitations under the License. # -CONFIG_CHIP=y -CONFIG_STD_CPP14=y - # This sample uses Kconfig.defaults to set options common for all # samples. This file should contain only options specific for this sample # or overrides of default values. +# Enable CHIP +CONFIG_CHIP=y +CONFIG_CHIP_PROJECT_CONFIG="main/include/CHIPProjectConfig.h" +# 32785 == 0x8011 (example pump-controller-app) +CONFIG_CHIP_DEVICE_PRODUCT_ID=32785 + +CONFIG_STD_CPP14=y + # Add support for LEDs and buttons on Nordic development kits CONFIG_DK_LIBRARY=y -# OpenThread configs -CONFIG_OPENTHREAD_NORDIC_LIBRARY_MTD=y -CONFIG_OPENTHREAD_MTD=y -CONFIG_OPENTHREAD_FTD=n - -# Bluetooth overrides +# Bluetooth Low Energy configuration CONFIG_BT_DEVICE_NAME="MatterPumpCtrl" # Enable system reset on fatal error CONFIG_RESET_ON_FATAL_ERROR=y -# CHIP configuration -CONFIG_CHIP_PROJECT_CONFIG="main/include/CHIPProjectConfig.h" -# 32785 == 0x8011 (example pump-controller-app) -CONFIG_CHIP_DEVICE_PRODUCT_ID=32785 +# Suspend devices when the CPU goes into sleep +CONFIG_PM_DEVICE=y # Disable all debug features CONFIG_SHELL=n @@ -48,7 +46,8 @@ CONFIG_UART_CONSOLE=n CONFIG_SERIAL=n CONFIG_LOG=n CONFIG_LOG_MODE_MINIMAL=n -CONFIG_ASSERT_NO_FILE_INFO=y CONFIG_ASSERT_VERBOSE=n +CONFIG_ASSERT_NO_FILE_INFO=y CONFIG_PRINTK=n +CONFIG_PRINTK_SYNC=n CONFIG_THREAD_NAME=n diff --git a/examples/window-app/nrfconnect/README.md b/examples/window-app/nrfconnect/README.md index 9fd8e4f7cb8a4f..e800b65733f381 100644 --- a/examples/window-app/nrfconnect/README.md +++ b/examples/window-app/nrfconnect/README.md @@ -57,14 +57,18 @@ and [Zephyr RTOS](https://zephyrproject.org/). Visit Matter's to read more about the platform structure and dependencies. The Matter device that runs the window shutter application is controlled by the -Matter controller device over the Thread protocol. By default, the Matter device -has Thread disabled, and it should be paired with Matter controller and get -configuration from it. Some actions required before establishing full -communication are described below. - -The example can be configured to use the secure bootloader and utilize it for -performing over-the-air Device Firmware Upgrade using Bluetooth LE. The device -works as a Thread Synchronized Sleepy End Device. +Matter controller device over the Thread protocol. By default, the Matter +accessory device has IPv6 networking disabled. You must pair it with the Matter +controller over Bluetooth® LE to get the configuration from the controller to +use the device within a Thread or Wi-Fi network. You have to make the device +discoverable manually (for security reasons). See +[Bluetooth LE advertising](#bluetooth-le-advertising) to learn how to do this. +The controller must get the commissioning information from the Matter accessory +device and provision the device into the network. + +You can test this application remotely over the Thread or the Wi-Fi protocol, +which in either case requires more devices, including a Matter controller that +you can configure either on a PC or a mobile device. ### Bluetooth LE advertising @@ -185,8 +189,8 @@ following states are possible: Bluetooth LE. - _Short Flash Off (950ms on/50ms off)_ — The device is fully - provisioned, but does not yet have full Thread network or service - connectivity. + provisioned, but does not yet have full connectivity for Thread or Wi-Fi + network. - _Solid On_ — The device is fully provisioned and has full Thread network and service connectivity. diff --git a/examples/window-app/nrfconnect/boards/nrf52840dk_nrf52840.overlay b/examples/window-app/nrfconnect/boards/nrf52840dk_nrf52840.overlay index 00c7b57a192e28..c1672db18b1d2c 100644 --- a/examples/window-app/nrfconnect/boards/nrf52840dk_nrf52840.overlay +++ b/examples/window-app/nrfconnect/boards/nrf52840dk_nrf52840.overlay @@ -58,9 +58,6 @@ &uart1 { status = "disabled"; }; -&gpio1 { - status = "disabled"; -}; &i2c0 { status = "disabled"; }; diff --git a/examples/window-app/nrfconnect/main/AppTask.cpp b/examples/window-app/nrfconnect/main/AppTask.cpp index 08df914781835b..311640093e35c0 100644 --- a/examples/window-app/nrfconnect/main/AppTask.cpp +++ b/examples/window-app/nrfconnect/main/AppTask.cpp @@ -23,15 +23,14 @@ #include -#include -#include - #include #include #include +#include #include +#include +#include #include - #include #include @@ -43,36 +42,42 @@ #include #include -#define FACTORY_RESET_TRIGGER_TIMEOUT 3000 -#define FACTORY_RESET_CANCEL_WINDOW_TIMEOUT 3000 -#define MOVEMENT_START_WINDOW_TIMEOUT 2000 -#define APP_EVENT_QUEUE_SIZE 10 -#define BUTTON_PUSH_EVENT 1 -#define BUTTON_RELEASE_EVENT 0 - -LOG_MODULE_DECLARE(app, CONFIG_MATTER_LOG_LEVEL); -K_MSGQ_DEFINE(sAppEventQueue, sizeof(AppEvent), APP_EVENT_QUEUE_SIZE, alignof(AppEvent)); +LOG_MODULE_DECLARE(app, CONFIG_CHIP_APP_LOG_LEVEL); using namespace ::chip; using namespace ::chip::Credentials; using namespace ::chip::DeviceLayer; namespace { +constexpr uint32_t kFactoryResetTriggerTimeout = 3000; +constexpr uint32_t kFactoryResetCancelWindowTimeout = 3000; +constexpr size_t kAppEventQueueSize = 10; + +K_MSGQ_DEFINE(sAppEventQueue, sizeof(AppEvent), kAppEventQueueSize, alignof(AppEvent)); +k_timer sFunctionTimer; // NOTE! This key is for test/certification only and should not be available in production devices! // If CONFIG_CHIP_FACTORY_DATA is enabled, this value is read from the factory data. uint8_t sTestEventTriggerEnableKey[TestEventTriggerDelegate::kEnableKeyLength] = { 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff }; +Identify sIdentify = { WindowCovering::Endpoint(), AppTask::IdentifyStartHandler, AppTask::IdentifyStopHandler, + EMBER_ZCL_IDENTIFY_IDENTIFY_TYPE_VISIBLE_LED }; + LEDWidget sStatusLED; -UnusedLedsWrapper<1> sUnusedLeds{ { DK_LED4 } }; -k_timer sFunctionTimer; +LEDWidget sIdentifyLED; +FactoryResetLEDsWrapper<1> sFactoryResetLEDs{ { FACTORY_RESET_SIGNAL_LED } }; chip::DeviceLayer::DeviceInfoProviderImpl gExampleDeviceInfoProvider; +bool sIsNetworkProvisioned = false; +bool sIsNetworkEnabled = false; +bool sHaveBLEConnections = false; } // namespace namespace LedConsts { constexpr uint32_t kBlinkRate_ms{ 500 }; +constexpr uint32_t kIdentifyBlinkRate_ms{ 500 }; + namespace StatusLed { namespace Unprovisioned { constexpr uint32_t kOn_ms{ 100 }; @@ -135,6 +140,8 @@ CHIP_ERROR AppTask::Init() LEDWidget::SetStateUpdateCallback(LEDStateUpdateHandler); sStatusLED.Init(SYSTEM_STATE_LED); + sIdentifyLED.Init(LIFT_STATE_LED); + sIdentifyLED.Set(false); UpdateStatusLED(); @@ -151,7 +158,7 @@ CHIP_ERROR AppTask::Init() k_timer_user_data_set(&sFunctionTimer, this); #ifdef CONFIG_MCUMGR_SMP_BT - /* Initialize DFU over SMP */ + // Initialize DFU over SMP GetDFUOverSMP().Init(RequestSMPAdvertisingStart); GetDFUOverSMP().ConfirmNewImage(); #endif @@ -201,7 +208,7 @@ CHIP_ERROR AppTask::Init() WindowCovering::Instance().PositionLEDUpdate(WindowCovering::MoveType::LIFT); WindowCovering::Instance().PositionLEDUpdate(WindowCovering::MoveType::TILT); - return err; + return CHIP_NO_ERROR; } CHIP_ERROR AppTask::StartApp() @@ -213,47 +220,72 @@ CHIP_ERROR AppTask::StartApp() while (true) { k_msgq_get(&sAppEventQueue, &event, K_FOREVER); - DispatchEvent(&event); + DispatchEvent(event); } return CHIP_NO_ERROR; } -void AppTask::ButtonEventHandler(uint32_t aButtonState, uint32_t aHasChanged) +void AppTask::IdentifyStartHandler(Identify *) +{ + AppEvent event; + event.Type = AppEventType::IdentifyStart; + event.Handler = [](const AppEvent &) { + WindowCovering::Instance().GetLiftIndicator().SuppressOutput(); + sIdentifyLED.Blink(LedConsts::kIdentifyBlinkRate_ms); + }; + PostEvent(event); +} + +void AppTask::IdentifyStopHandler(Identify *) +{ + AppEvent event; + event.Type = AppEventType::IdentifyStop; + event.Handler = [](const AppEvent &) { + sIdentifyLED.Set(false); + WindowCovering::Instance().GetLiftIndicator().ApplyLevel(); + }; + PostEvent(event); +} + +void AppTask::ButtonEventHandler(uint32_t buttonState, uint32_t hasChanged) { AppEvent event; - event.Type = AppEvent::Type::Button; + event.Type = AppEventType::Button; - if (FUNCTION_BUTTON_MASK & aHasChanged) + if (FUNCTION_BUTTON_MASK & hasChanged) { - event.ButtonEvent.PinNo = FUNCTION_BUTTON; - event.ButtonEvent.Action = (FUNCTION_BUTTON_MASK & aButtonState) ? BUTTON_PUSH_EVENT : BUTTON_RELEASE_EVENT; - event.Handler = FunctionHandler; - PostEvent(&event); + event.ButtonEvent.PinNo = FUNCTION_BUTTON; + event.ButtonEvent.Action = + static_cast((FUNCTION_BUTTON_MASK & buttonState) ? AppEventType::ButtonPushed : AppEventType::ButtonReleased); + event.Handler = FunctionHandler; + PostEvent(event); } - if (BLE_ADVERTISEMENT_START_BUTTON_MASK & aButtonState & aHasChanged) + if (BLE_ADVERTISEMENT_START_BUTTON_MASK & buttonState & hasChanged) { event.ButtonEvent.PinNo = BLE_ADVERTISEMENT_START_BUTTON; - event.ButtonEvent.Action = BUTTON_PUSH_EVENT; + event.ButtonEvent.Action = static_cast(AppEventType::ButtonPushed); event.Handler = StartBLEAdvertisementHandler; - PostEvent(&event); + PostEvent(event); } - if (OPEN_BUTTON_MASK & aHasChanged) + if (OPEN_BUTTON_MASK & hasChanged) { - event.ButtonEvent.PinNo = OPEN_BUTTON; - event.ButtonEvent.Action = (OPEN_BUTTON_MASK & aButtonState) ? BUTTON_PUSH_EVENT : BUTTON_RELEASE_EVENT; - event.Handler = OpenHandler; - PostEvent(&event); + event.ButtonEvent.PinNo = OPEN_BUTTON; + event.ButtonEvent.Action = + static_cast((OPEN_BUTTON_MASK & buttonState) ? AppEventType::ButtonPushed : AppEventType::ButtonReleased); + event.Handler = OpenHandler; + PostEvent(event); } - if (CLOSE_BUTTON_MASK & aHasChanged) + if (CLOSE_BUTTON_MASK & hasChanged) { - event.ButtonEvent.PinNo = CLOSE_BUTTON; - event.ButtonEvent.Action = (CLOSE_BUTTON_MASK & aButtonState) ? BUTTON_PUSH_EVENT : BUTTON_RELEASE_EVENT; - event.Handler = CloseHandler; - PostEvent(&event); + event.ButtonEvent.PinNo = CLOSE_BUTTON; + event.ButtonEvent.Action = + static_cast((CLOSE_BUTTON_MASK & buttonState) ? AppEventType::ButtonPushed : AppEventType::ButtonReleased); + event.Handler = CloseHandler; + PostEvent(event); } } @@ -261,103 +293,108 @@ void AppTask::ButtonEventHandler(uint32_t aButtonState, uint32_t aHasChanged) void AppTask::RequestSMPAdvertisingStart(void) { AppEvent event; - event.Type = AppEvent::Type::StartSMPAdvertising; - event.Handler = [](AppEvent *) { GetDFUOverSMP().StartBLEAdvertising(); }; - PostEvent(&event); + event.Type = AppEventType::StartSMPAdvertising; + event.Handler = [](const AppEvent &) { GetDFUOverSMP().StartBLEAdvertising(); }; + PostEvent(event); } #endif -void AppTask::FunctionTimerTimeoutCallback(k_timer * aTimer) +void AppTask::FunctionTimerTimeoutCallback(k_timer * timer) { - if (!aTimer) + if (!timer) + { return; + } AppEvent event; - event.Type = AppEvent::Type::Timer; - event.TimerEvent.Context = k_timer_user_data_get(aTimer); + event.Type = AppEventType::Timer; + event.TimerEvent.Context = k_timer_user_data_get(timer); event.Handler = FunctionTimerEventHandler; - PostEvent(&event); + PostEvent(event); } -void AppTask::FunctionTimerEventHandler(AppEvent * aEvent) +void AppTask::FunctionTimerEventHandler(const AppEvent & event) { - if (!aEvent) - return; - if (aEvent->Type != AppEvent::Type::Timer) + if (event.Type != AppEventType::Timer) return; - // If we reached here, the button was held past FACTORY_RESET_TRIGGER_TIMEOUT, initiate factory reset - if (Instance().mFunctionTimerActive && Instance().mMode == OperatingMode::Normal) + // If we reached here, the button was held past kFactoryResetTriggerTimeout, initiate factory reset + if (Instance().mFunctionTimerActive && Instance().mFunction == FunctionEvent::SoftwareUpdate) { - LOG_INF("Factory Reset Triggered. Release button within %ums to cancel.", FACTORY_RESET_TRIGGER_TIMEOUT); + LOG_INF("Factory Reset Triggered. Release button within %ums to cancel.", kFactoryResetTriggerTimeout); - // Start timer for FACTORY_RESET_CANCEL_WINDOW_TIMEOUT to allow user to cancel, if required. - StartTimer(FACTORY_RESET_CANCEL_WINDOW_TIMEOUT); - Instance().mMode = OperatingMode::FactoryReset; + // Start timer for kFactoryResetCancelWindowTimeout to allow user to cancel, if required. + Instance().StartTimer(kFactoryResetCancelWindowTimeout); + Instance().mFunction = FunctionEvent::FactoryReset; #ifdef CONFIG_STATE_LEDS // Turn off all LEDs before starting blink to make sure blink is co-ordinated. sStatusLED.Set(false); - sUnusedLeds.Set(false); + sFactoryResetLEDs.Set(false); sStatusLED.Blink(LedConsts::kBlinkRate_ms); - sUnusedLeds.Blink(LedConsts::kBlinkRate_ms); + sFactoryResetLEDs.Blink(LedConsts::kBlinkRate_ms); #endif } - else if (Instance().mFunctionTimerActive && Instance().mMode == OperatingMode::FactoryReset) + else if (Instance().mFunctionTimerActive && Instance().mFunction == FunctionEvent::FactoryReset) { // Actually trigger Factory Reset - Instance().mMode = OperatingMode::Normal; - LOG_INF("Factory Reset triggered"); + Instance().mFunction = FunctionEvent::NoneSelected; + chip::Server::GetInstance().ScheduleFactoryReset(); } } -void AppTask::FunctionHandler(AppEvent * aEvent) +void AppTask::FunctionHandler(const AppEvent & event) { - if (!aEvent) - return; - if (aEvent->ButtonEvent.PinNo != FUNCTION_BUTTON) + if (event.ButtonEvent.PinNo != FUNCTION_BUTTON) return; // To initiate factory reset: press the FUNCTION_BUTTON for FACTORY_RESET_TRIGGER_TIMEOUT + FACTORY_RESET_CANCEL_WINDOW_TIMEOUT // All LEDs start blinking after FACTORY_RESET_TRIGGER_TIMEOUT to signal factory reset has been initiated. // To cancel factory reset: release the FUNCTION_BUTTON once all LEDs start blinking within the // FACTORY_RESET_CANCEL_WINDOW_TIMEOUT - if (aEvent->ButtonEvent.Action == BUTTON_PUSH_EVENT) + if (event.ButtonEvent.Action == static_cast(AppEventType::ButtonPushed)) { - if (!Instance().mFunctionTimerActive && Instance().mMode == OperatingMode::Normal) + if (!Instance().mFunctionTimerActive && Instance().mFunction == FunctionEvent::NoneSelected) { - StartTimer(FACTORY_RESET_TRIGGER_TIMEOUT); + Instance().StartTimer(kFactoryResetTriggerTimeout); + + Instance().mFunction = FunctionEvent::SoftwareUpdate; } } else { - if (Instance().mFunctionTimerActive && Instance().mMode == OperatingMode::FactoryReset) + // If the button was released before factory reset got initiated, trigger a software update. + if (Instance().mFunctionTimerActive && Instance().mFunction == FunctionEvent::SoftwareUpdate) { - sUnusedLeds.Set(false); + Instance().CancelTimer(); - UpdateStatusLED(); - CancelTimer(); #ifdef CONFIG_MCUMGR_SMP_BT GetDFUOverSMP().StartServer(); #else LOG_INF("Software update is disabled"); #endif - // Change the function to none selected since factory reset has been canceled. - Instance().mMode = OperatingMode::Normal; + Instance().mFunction = FunctionEvent::NoneSelected; + } + else if (Instance().mFunctionTimerActive && Instance().mFunction == FunctionEvent::FactoryReset) + { + sFactoryResetLEDs.Set(false); + UpdateStatusLED(); + Instance().CancelTimer(); + Instance().mFunction = FunctionEvent::NoneSelected; LOG_INF("Factory Reset has been Canceled"); } else if (Instance().mFunctionTimerActive) { CancelTimer(); - Instance().mMode = OperatingMode::Normal; + Instance().mFunction = FunctionEvent::NoneSelected; } } } -void AppTask::StartBLEAdvertisementHandler(AppEvent *) +void AppTask::StartBLEAdvertisementHandler(const AppEvent &) { if (Server::GetInstance().GetFabricTable().FabricCount() != 0) { @@ -377,14 +414,12 @@ void AppTask::StartBLEAdvertisementHandler(AppEvent *) } } -void AppTask::OpenHandler(AppEvent * aEvent) +void AppTask::OpenHandler(const AppEvent & event) { - if (!aEvent) - return; - if (aEvent->ButtonEvent.PinNo != OPEN_BUTTON || Instance().mMode != OperatingMode::Normal) + if (event.ButtonEvent.PinNo != OPEN_BUTTON || Instance().mFunction != FunctionEvent::NoneSelected) return; - if (aEvent->ButtonEvent.Action == BUTTON_PUSH_EVENT) + if (event.ButtonEvent.Action == static_cast(AppEventType::ButtonPushed)) { Instance().mOpenButtonIsPressed = true; if (Instance().mCloseButtonIsPressed) @@ -392,7 +427,7 @@ void AppTask::OpenHandler(AppEvent * aEvent) Instance().ToggleMoveType(); } } - else if (aEvent->ButtonEvent.Action == BUTTON_RELEASE_EVENT) + else if (event.ButtonEvent.Action == static_cast(AppEventType::ButtonReleased)) { if (!Instance().mCloseButtonIsPressed) { @@ -409,14 +444,12 @@ void AppTask::OpenHandler(AppEvent * aEvent) } } -void AppTask::CloseHandler(AppEvent * aEvent) +void AppTask::CloseHandler(const AppEvent & event) { - if (!aEvent) - return; - if (aEvent->ButtonEvent.PinNo != CLOSE_BUTTON || Instance().mMode != OperatingMode::Normal) + if (event.ButtonEvent.PinNo != CLOSE_BUTTON || Instance().mFunction != FunctionEvent::NoneSelected) return; - if (aEvent->ButtonEvent.Action == BUTTON_PUSH_EVENT) + if (event.ButtonEvent.Action == static_cast(AppEventType::ButtonPushed)) { Instance().mCloseButtonIsPressed = true; if (Instance().mOpenButtonIsPressed) @@ -424,7 +457,7 @@ void AppTask::CloseHandler(AppEvent * aEvent) Instance().ToggleMoveType(); } } - else if (aEvent->ButtonEvent.Action == BUTTON_RELEASE_EVENT) + else if (event.ButtonEvent.Action == static_cast(AppEventType::ButtonReleased)) { if (!Instance().mOpenButtonIsPressed) { @@ -456,41 +489,39 @@ void AppTask::ToggleMoveType() mMoveTypeRecentlyChanged = true; } -void AppTask::UpdateLedStateEventHandler(AppEvent * aEvent) +void AppTask::UpdateLedStateEventHandler(const AppEvent & event) { - if (!aEvent) - return; - if (aEvent->Type == AppEvent::Type::UpdateLedState) + if (event.Type == AppEventType::UpdateLedState) { - aEvent->UpdateLedStateEvent.LedWidget->UpdateState(); + event.UpdateLedStateEvent.LedWidget->UpdateState(); } } -void AppTask::LEDStateUpdateHandler(LEDWidget & aLedWidget) +void AppTask::LEDStateUpdateHandler(LEDWidget & ledWidget) { AppEvent event; - event.Type = AppEvent::Type::UpdateLedState; + event.Type = AppEventType::UpdateLedState; event.Handler = UpdateLedStateEventHandler; - event.UpdateLedStateEvent.LedWidget = &aLedWidget; - PostEvent(&event); + event.UpdateLedStateEvent.LedWidget = &ledWidget; + PostEvent(event); } void AppTask::UpdateStatusLED() { #ifdef CONFIG_STATE_LEDS - /* Update the status LED. - * - * If thread and service provisioned, keep the LED On constantly. - * - * If the system has ble connection(s) uptill the stage above, THEN blink the LED at an even - * rate of 100ms. - * - * Otherwise, blink the LED On for a very short time. */ - if (Instance().mIsThreadProvisioned && Instance().mIsThreadEnabled) + // Update the status LED. + // + // If thread and service provisioned, keep the LED On constantly. + // + // If the system has ble connection(s) uptill the stage above, THEN blink the LED at an even + // rate of 100ms. + // + // Otherwise, blink the LED On for a very short time. + if (sIsNetworkProvisioned && sIsNetworkEnabled) { sStatusLED.Set(true); } - else if (Instance().mHaveBLEConnections) + else if (sHaveBLEConnections) { sStatusLED.Blink(LedConsts::StatusLed::Unprovisioned::kOn_ms, LedConsts::StatusLed::Unprovisioned::kOff_ms); } @@ -501,15 +532,13 @@ void AppTask::UpdateStatusLED() #endif } -void AppTask::ChipEventHandler(const ChipDeviceEvent * aEvent, intptr_t) +void AppTask::ChipEventHandler(const ChipDeviceEvent * event, intptr_t /* arg */) { - if (!aEvent) - return; - switch (aEvent->Type) + switch (event->Type) { case DeviceEventType::kCHIPoBLEAdvertisingChange: #ifdef CONFIG_CHIP_NFC_COMMISSIONING - if (aEvent->CHIPoBLEAdvertisingChange.Result == kActivity_Started) + if (event->CHIPoBLEAdvertisingChange.Result == kActivity_Started) { if (NFCMgr().IsTagEmulationStarted()) { @@ -520,17 +549,17 @@ void AppTask::ChipEventHandler(const ChipDeviceEvent * aEvent, intptr_t) ShareQRCodeOverNFC(chip::RendezvousInformationFlags(chip::RendezvousInformationFlag::kBLE)); } } - else if (aEvent->CHIPoBLEAdvertisingChange.Result == kActivity_Stopped) + else if (event->CHIPoBLEAdvertisingChange.Result == kActivity_Stopped) { NFCMgr().StopTagEmulation(); } #endif - Instance().mHaveBLEConnections = ConnectivityMgr().NumBLEConnections() != 0; + sHaveBLEConnections = ConnectivityMgr().NumBLEConnections() != 0; UpdateStatusLED(); break; case DeviceEventType::kThreadStateChange: - Instance().mIsThreadProvisioned = ConnectivityMgr().IsThreadProvisioned(); - Instance().mIsThreadEnabled = ConnectivityMgr().IsThreadEnabled(); + sIsNetworkProvisioned = ConnectivityMgr().IsThreadProvisioned(); + sIsNetworkEnabled = ConnectivityMgr().IsThreadEnabled(); UpdateStatusLED(); break; case DeviceEventType::kDnssdPlatformInitialized: @@ -549,29 +578,25 @@ void AppTask::CancelTimer() Instance().mFunctionTimerActive = false; } -void AppTask::StartTimer(uint32_t aTimeoutInMs) +void AppTask::StartTimer(uint32_t timeoutMs) { - k_timer_start(&sFunctionTimer, K_MSEC(aTimeoutInMs), K_NO_WAIT); + k_timer_start(&sFunctionTimer, K_MSEC(timeoutMs), K_NO_WAIT); Instance().mFunctionTimerActive = true; } -void AppTask::PostEvent(AppEvent * aEvent) +void AppTask::PostEvent(const AppEvent & event) { - if (!aEvent) - return; - if (k_msgq_put(&sAppEventQueue, aEvent, K_NO_WAIT)) + if (k_msgq_put(&sAppEventQueue, &event, K_NO_WAIT)) { LOG_INF("Failed to post event to app task event queue"); } } -void AppTask::DispatchEvent(AppEvent * aEvent) +void AppTask::DispatchEvent(const AppEvent & event) { - if (!aEvent) - return; - if (aEvent->Handler) + if (event.Handler) { - aEvent->Handler(aEvent); + event.Handler(event); } else { diff --git a/examples/window-app/nrfconnect/main/WindowCovering.cpp b/examples/window-app/nrfconnect/main/WindowCovering.cpp index f1fd5a7059cadb..96320a6e92e189 100644 --- a/examples/window-app/nrfconnect/main/WindowCovering.cpp +++ b/examples/window-app/nrfconnect/main/WindowCovering.cpp @@ -27,7 +27,7 @@ #include #include -LOG_MODULE_DECLARE(app, CONFIG_MATTER_LOG_LEVEL); +LOG_MODULE_DECLARE(app, CONFIG_CHIP_APP_LOG_LEVEL); using namespace ::chip::Credentials; using namespace ::chip::DeviceLayer; diff --git a/examples/window-app/nrfconnect/main/include/AppConfig.h b/examples/window-app/nrfconnect/main/include/AppConfig.h index 7dadc1bc572667..933cce3b6bb413 100644 --- a/examples/window-app/nrfconnect/main/include/AppConfig.h +++ b/examples/window-app/nrfconnect/main/include/AppConfig.h @@ -29,3 +29,4 @@ #define SYSTEM_STATE_LED DK_LED1 #define LIFT_STATE_LED DK_LED2 #define TILT_STATE_LED DK_LED3 +#define FACTORY_RESET_SIGNAL_LED DK_LED4 diff --git a/examples/window-app/nrfconnect/main/include/AppEvent.h b/examples/window-app/nrfconnect/main/include/AppEvent.h index f6cac85b182b32..035a92cc0d8e20 100644 --- a/examples/window-app/nrfconnect/main/include/AppEvent.h +++ b/examples/window-app/nrfconnect/main/include/AppEvent.h @@ -19,22 +19,32 @@ #include +#include "EventTypes.h" + class LEDWidget; -struct AppEvent +enum class AppEventType : uint8_t { - using EventHandler = void (*)(AppEvent *); - - enum class Type : uint8_t - { - None, - Button, - Timer, - UpdateLedState, - }; + None = 0, + Button, + ButtonPushed, + ButtonReleased, + Timer, + UpdateLedState, + IdentifyStart, + IdentifyStop, + StartSMPAdvertising +}; - Type Type{ Type::None }; +enum class FunctionEvent : uint8_t +{ + NoneSelected = 0, + SoftwareUpdate = 0, + FactoryReset +}; +struct AppEvent +{ union { struct @@ -52,5 +62,6 @@ struct AppEvent } UpdateLedStateEvent; }; + AppEventType Type{ AppEventType::None }; EventHandler Handler; }; diff --git a/examples/window-app/nrfconnect/main/include/AppTask.h b/examples/window-app/nrfconnect/main/include/AppTask.h index a082c4582aaf59..1d4adb0b97c647 100644 --- a/examples/window-app/nrfconnect/main/include/AppTask.h +++ b/examples/window-app/nrfconnect/main/include/AppTask.h @@ -17,6 +17,8 @@ #pragma once +#include "AppEvent.h" +#include "LEDWidget.h" #include "WindowCovering.h" #include @@ -24,9 +26,12 @@ #include #endif +#ifdef CONFIG_MCUMGR_SMP_BT +#include "dfu_over_smp.h" +#endif + struct k_timer; -class AppEvent; -class LEDWidget; +struct Identify; class AppTask { @@ -38,44 +43,42 @@ class AppTask }; CHIP_ERROR StartApp(); + static void IdentifyStartHandler(Identify *); + static void IdentifyStopHandler(Identify *); + private: - enum class OperatingMode : uint8_t - { - Normal, - FactoryReset, - MoveSelection, - Movement, - Invalid - }; CHIP_ERROR Init(); - void DispatchEvent(AppEvent * aEvent); void ToggleMoveType(); - // statics needed to interact with zephyr C API static void CancelTimer(); - static void StartTimer(uint32_t aTimeoutInMs); - static void FunctionTimerEventHandler(AppEvent * aEvent); - static void MovementTimerEventHandler(AppEvent * aEvent); - static void FunctionHandler(AppEvent * aEvent); - static void ButtonEventHandler(uint32_t aButtonsState, uint32_t aHasChanged); - static void TimerTimeoutCallback(k_timer * aTimer); - static void FunctionTimerTimeoutCallback(k_timer * aTimer); - static void PostEvent(AppEvent * aEvent); + static void StartTimer(uint32_t timeoutMs); + + static void PostEvent(const AppEvent & event); + static void DispatchEvent(const AppEvent & event); + static void FunctionTimerEventHandler(const AppEvent & event); + static void FunctionHandler(const AppEvent & event); + static void UpdateLedStateEventHandler(const AppEvent & event); + static void StartBLEAdvertisementHandler(const AppEvent & event); + static void MovementTimerEventHandler(const AppEvent & event); + static void OpenHandler(const AppEvent & event); + static void CloseHandler(const AppEvent & event); + + static void TimerTimeoutCallback(k_timer * timer); + static void FunctionTimerTimeoutCallback(k_timer * timer); + static void LEDStateUpdateHandler(LEDWidget & ledWidget); + + static void ChipEventHandler(const chip::DeviceLayer::ChipDeviceEvent * event, intptr_t arg); + static void ButtonEventHandler(uint32_t buttonState, uint32_t hasChanged); static void UpdateStatusLED(); - static void LEDStateUpdateHandler(LEDWidget & aLedWidget); - static void UpdateLedStateEventHandler(AppEvent * aEvent); - static void StartBLEAdvertisementHandler(AppEvent * aEvent); - static void ChipEventHandler(const chip::DeviceLayer::ChipDeviceEvent * aEvent, intptr_t aArg); - static void OpenHandler(AppEvent * aEvent); - static void CloseHandler(AppEvent * aEvent); - OperatingMode mMode{ OperatingMode::Normal }; +#ifdef CONFIG_MCUMGR_SMP_BT + static void RequestSMPAdvertisingStart(void); +#endif + + FunctionEvent mFunction{ FunctionEvent::NoneSelected }; OperationalState mMoveType{ OperationalState::MovingUpOrOpen }; bool mFunctionTimerActive{ false }; bool mMovementTimerActive{ false }; - bool mIsThreadProvisioned{ false }; - bool mIsThreadEnabled{ false }; - bool mHaveBLEConnections{ false }; bool mOpenButtonIsPressed{ false }; bool mCloseButtonIsPressed{ false }; bool mMoveTypeRecentlyChanged{ false }; diff --git a/examples/window-app/nrfconnect/main/include/WindowCovering.h b/examples/window-app/nrfconnect/main/include/WindowCovering.h index 99868a9fe41bfb..f03c691fe38018 100644 --- a/examples/window-app/nrfconnect/main/include/WindowCovering.h +++ b/examples/window-app/nrfconnect/main/include/WindowCovering.h @@ -49,6 +49,9 @@ class WindowCovering return sInstance; } + PWMDevice & GetLiftIndicator() { return mLiftIndicator; } + PWMDevice & GetTiltIndicator() { return mTiltIndicator; } + void StartMove(MoveType aMoveType); void SetSingleStepTarget(OperationalState aDirection); void SetMoveType(MoveType aMoveType) { mCurrentUIMoveType = aMoveType; } diff --git a/examples/window-app/nrfconnect/main/main.cpp b/examples/window-app/nrfconnect/main/main.cpp index 40233508ccc702..4cbd69782ac199 100644 --- a/examples/window-app/nrfconnect/main/main.cpp +++ b/examples/window-app/nrfconnect/main/main.cpp @@ -19,7 +19,7 @@ #include -LOG_MODULE_REGISTER(app, CONFIG_MATTER_LOG_LEVEL); +LOG_MODULE_REGISTER(app, CONFIG_CHIP_APP_LOG_LEVEL); using namespace ::chip; diff --git a/examples/window-app/nrfconnect/prj.conf b/examples/window-app/nrfconnect/prj.conf index bb8601bf9c5dbf..a284009a305d99 100644 --- a/examples/window-app/nrfconnect/prj.conf +++ b/examples/window-app/nrfconnect/prj.conf @@ -14,15 +14,17 @@ # limitations under the License. # +# Enable CHIP CONFIG_CHIP=y +CONFIG_CHIP_PROJECT_CONFIG="main/include/CHIPProjectConfig.h" +# 32784 == 0x8010 (example window-app) +CONFIG_CHIP_DEVICE_PRODUCT_ID=32784 CONFIG_STD_CPP14=y -# This sample uses Kconfig.defaults to set options common for all -# samples. This file should contain only options specific for this sample -# or overrides of default values. - # Add support for LEDs and buttons on Nordic development kits CONFIG_DK_LIBRARY=y + +# PWM support CONFIG_PWM=y # OpenThread configs @@ -34,15 +36,13 @@ CONFIG_CHIP_THREAD_SSED=y CONFIG_CHIP_SED_IDLE_INTERVAL=500 CONFIG_CHIP_SED_ACTIVE_INTERVAL=500 -# Bluetooth overrides +# Bluetooth Low Energy configuration CONFIG_BT_DEVICE_NAME="MatterWinCov" -# Additional configs for debbugging experience. +# Stack size settings +CONFIG_IEEE802154_NRF5_RX_STACK_SIZE=1024 + +# Other settings CONFIG_THREAD_NAME=y CONFIG_MPU_STACK_GUARD=y CONFIG_RESET_ON_FATAL_ERROR=n - -# CHIP configuration -CONFIG_CHIP_PROJECT_CONFIG="main/include/CHIPProjectConfig.h" -# 32784 == 0x8010 (example window-app) -CONFIG_CHIP_DEVICE_PRODUCT_ID=32784 diff --git a/examples/window-app/nrfconnect/prj_no_dfu.conf b/examples/window-app/nrfconnect/prj_no_dfu.conf index f2787a7619fe73..3f55608bc1357e 100644 --- a/examples/window-app/nrfconnect/prj_no_dfu.conf +++ b/examples/window-app/nrfconnect/prj_no_dfu.conf @@ -14,17 +14,24 @@ # limitations under the License. # -CONFIG_CHIP=y -CONFIG_STD_CPP14=y - # This sample uses Kconfig.defaults to set options common for all # samples. This file should contain only options specific for this sample # or overrides of default values. +# Enable CHIP +CONFIG_CHIP=y +CONFIG_CHIP_PROJECT_CONFIG="main/include/CHIPProjectConfig.h" +# 32784 == 0x8010 (example window-app) +CONFIG_CHIP_DEVICE_PRODUCT_ID=32784 +CONFIG_STD_CPP14=y + # Add support for LEDs and buttons on Nordic development kits CONFIG_DK_LIBRARY=y CONFIG_PWM=y +# PWM support +CONFIG_PWM=y + # OpenThread configs CONFIG_OPENTHREAD_NORDIC_LIBRARY_MTD=y CONFIG_OPENTHREAD_MTD=y @@ -34,10 +41,13 @@ CONFIG_CHIP_THREAD_SSED=y CONFIG_CHIP_SED_IDLE_INTERVAL=500 CONFIG_CHIP_SED_ACTIVE_INTERVAL=500 -# Bluetooth overrides +# Bluetooth Low Energy configuration CONFIG_BT_DEVICE_NAME="MatterWinCov" -# Additional configs for debbugging experience. +# Stack size settings +CONFIG_IEEE802154_NRF5_RX_STACK_SIZE=1024 + +# Other settings CONFIG_THREAD_NAME=y CONFIG_MPU_STACK_GUARD=y CONFIG_RESET_ON_FATAL_ERROR=n @@ -45,7 +55,5 @@ CONFIG_RESET_ON_FATAL_ERROR=n # Disable Matter OTA DFU CONFIG_CHIP_OTA_REQUESTOR=n -# CHIP configuration -CONFIG_CHIP_PROJECT_CONFIG="main/include/CHIPProjectConfig.h" -# 32784 == 0x8010 (example window-app) -CONFIG_CHIP_DEVICE_PRODUCT_ID=32784 +# Disable QSPI NOR +CONFIG_CHIP_QSPI_NOR=n diff --git a/examples/window-app/nrfconnect/prj_release.conf b/examples/window-app/nrfconnect/prj_release.conf index caf0ee1a5be1e9..c801e2f86bb9a7 100644 --- a/examples/window-app/nrfconnect/prj_release.conf +++ b/examples/window-app/nrfconnect/prj_release.conf @@ -14,17 +14,24 @@ # limitations under the License. # -CONFIG_CHIP=y -CONFIG_STD_CPP14=y - # This sample uses Kconfig.defaults to set options common for all # samples. This file should contain only options specific for this sample # or overrides of default values. +# Enable CHIP +CONFIG_CHIP=y +CONFIG_CHIP_PROJECT_CONFIG="main/include/CHIPProjectConfig.h" +# 32784 == 0x8010 (example window-app) +CONFIG_CHIP_DEVICE_PRODUCT_ID=32784 +CONFIG_STD_CPP14=y + # Add support for LEDs and buttons on Nordic development kits CONFIG_DK_LIBRARY=y CONFIG_PWM=y +# PWM support +CONFIG_PWM=y + # OpenThread configs CONFIG_OPENTHREAD_NORDIC_LIBRARY_MTD=y CONFIG_OPENTHREAD_MTD=y @@ -34,16 +41,14 @@ CONFIG_CHIP_THREAD_SSED=y CONFIG_CHIP_SED_IDLE_INTERVAL=500 CONFIG_CHIP_SED_ACTIVE_INTERVAL=500 -# Bluetooth overrides +# Bluetooth Low Energy configuration CONFIG_BT_DEVICE_NAME="MatterWinCov" # Enable system reset on fatal error CONFIG_RESET_ON_FATAL_ERROR=y -# CHIP configuration -CONFIG_CHIP_PROJECT_CONFIG="main/include/CHIPProjectConfig.h" -# 32784 == 0x8010 (example window-app) -CONFIG_CHIP_DEVICE_PRODUCT_ID=32784 +# Stack size settings +CONFIG_IEEE802154_NRF5_RX_STACK_SIZE=1024 # Suspend devices when the CPU goes into sleep CONFIG_PM_DEVICE=y @@ -56,7 +61,8 @@ CONFIG_UART_CONSOLE=n CONFIG_SERIAL=n CONFIG_LOG=n CONFIG_LOG_MODE_MINIMAL=n -CONFIG_ASSERT_NO_FILE_INFO=y CONFIG_ASSERT_VERBOSE=n +CONFIG_ASSERT_NO_FILE_INFO=y CONFIG_PRINTK=n +CONFIG_PRINTK_SYNC=n CONFIG_THREAD_NAME=n