From 456588746efe3a6674cb9a2ca60eb7453263fb94 Mon Sep 17 00:00:00 2001 From: Dmytro Huz <75682372+interfer@users.noreply.github.com> Date: Tue, 7 Mar 2023 16:00:13 +0200 Subject: [PATCH] [Telink] Add Window app demo (#25240) * [Telink] Window App demo draft * [Telink] modified action buttons & Updated CI * [Telink] Fix CI build and spelling * [Telink] PWM LED fixed, code cleanup * [Telink] small fixes * [Telink] review fixes & styling --------- Co-authored-by: Alex Tsitsiura --- .github/workflows/examples-telink.yaml | 12 + .vscode/tasks.json | 1 + examples/window-app/telink/.gitignore | 1 + examples/window-app/telink/CMakeLists.txt | 225 +++++++ examples/window-app/telink/README.md | 257 +++++++ .../window-app/telink/include/AppConfig.h | 40 ++ examples/window-app/telink/include/AppEvent.h | 59 ++ examples/window-app/telink/include/AppTask.h | 109 +++ .../telink/include/CHIPProjectConfig.h | 44 ++ .../telink/include/WindowCovering.h | 78 +++ examples/window-app/telink/prj.conf | 78 +++ examples/window-app/telink/rpc.overlay | 47 ++ examples/window-app/telink/src/AppTask.cpp | 631 ++++++++++++++++++ .../window-app/telink/src/WindowCovering.cpp | 315 +++++++++ .../window-app/telink/src/ZclCallbacks.cpp | 78 +++ examples/window-app/telink/src/main.cpp | 90 +++ .../telink/third_party/connectedhomeip | 1 + scripts/build/build/targets.py | 1 + scripts/build/builders/telink.py | 5 + .../build/testdata/all_targets_linux_x64.txt | 2 +- 20 files changed, 2073 insertions(+), 1 deletion(-) create mode 100644 examples/window-app/telink/.gitignore create mode 100644 examples/window-app/telink/CMakeLists.txt create mode 100644 examples/window-app/telink/README.md create mode 100644 examples/window-app/telink/include/AppConfig.h create mode 100644 examples/window-app/telink/include/AppEvent.h create mode 100644 examples/window-app/telink/include/AppTask.h create mode 100644 examples/window-app/telink/include/CHIPProjectConfig.h create mode 100644 examples/window-app/telink/include/WindowCovering.h create mode 100644 examples/window-app/telink/prj.conf create mode 100644 examples/window-app/telink/rpc.overlay create mode 100644 examples/window-app/telink/src/AppTask.cpp create mode 100644 examples/window-app/telink/src/WindowCovering.cpp create mode 100644 examples/window-app/telink/src/ZclCallbacks.cpp create mode 100644 examples/window-app/telink/src/main.cpp create mode 120000 examples/window-app/telink/third_party/connectedhomeip diff --git a/.github/workflows/examples-telink.yaml b/.github/workflows/examples-telink.yaml index cb3adc7c426bb4..f26f0a3c7313da 100644 --- a/.github/workflows/examples-telink.yaml +++ b/.github/workflows/examples-telink.yaml @@ -214,6 +214,18 @@ jobs: - name: clean out build output run: rm -rf ./out + - name: Build example Telink Window Covering App + run: | + ./scripts/run_in_build_env.sh \ + "./scripts/build/build_examples.py --target 'telink-tlsr9518adk80d-window-covering' build" + .environment/pigweed-venv/bin/python3 scripts/tools/memory/gh_sizes.py \ + telink tlsr9518adk80d window-covering \ + out/telink-tlsr9518adk80d-window-covering/zephyr/zephyr.elf \ + /tmp/bloat_reports/ + + - name: clean out build output + run: rm -rf ./out + - name: Uploading Size Reports uses: actions/upload-artifact@v3 if: ${{ !env.ACT }} diff --git a/.vscode/tasks.json b/.vscode/tasks.json index bd3c7821934dd5..f73966cddf7c22 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -647,6 +647,7 @@ "telink-tlsr9518adk80d-pump-controller-app", "telink-tlsr9518adk80d-temperature-measurement", "telink-tlsr9518adk80d-thermostat", + "telink-tlsr9518adk80d-window-covering", "tizen-arm-light" ] }, diff --git a/examples/window-app/telink/.gitignore b/examples/window-app/telink/.gitignore new file mode 100644 index 00000000000000..84c048a73cc2e5 --- /dev/null +++ b/examples/window-app/telink/.gitignore @@ -0,0 +1 @@ +/build/ diff --git a/examples/window-app/telink/CMakeLists.txt b/examples/window-app/telink/CMakeLists.txt new file mode 100644 index 00000000000000..c7323fe9aa79fe --- /dev/null +++ b/examples/window-app/telink/CMakeLists.txt @@ -0,0 +1,225 @@ +# +# Copyright (c) 2023 Project CHIP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +cmake_minimum_required(VERSION 3.13.1) + +set(BOARD tlsr9518adk80d) + +get_filename_component(CHIP_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/third_party/connectedhomeip REALPATH) +get_filename_component(TELINK_COMMON ${CHIP_ROOT}/examples/platform/telink REALPATH) +get_filename_component(GEN_DIR ${CHIP_ROOT}/zzz_generated/ REALPATH) + +set(CONF_FILE ${CHIP_ROOT}/config/telink/app/zephyr.conf prj.conf) + +# Load NCS/Zephyr build system +list(APPEND ZEPHYR_EXTRA_MODULES ${CHIP_ROOT}/config/telink/chip-module) +find_package(Zephyr HINTS $ENV{ZEPHYR_BASE}) + +project(chip-telink-window-app-example) + +include(${CHIP_ROOT}/config/telink/app/enable-gnu-std.cmake) +include(${CHIP_ROOT}/src/app/chip_data_model.cmake) + +target_compile_options(app PRIVATE -fpermissive) + +target_include_directories(app PRIVATE + include + ${GEN_DIR}/app-common + ${GEN_DIR}/window-app + ${TELINK_COMMON}/util/include + ${TELINK_COMMON}/app/include) + +add_definitions( + "-DCHIP_ADDRESS_RESOLVE_IMPL_INCLUDE_HEADER=" +) + +target_sources(app PRIVATE + src/AppTask.cpp + src/main.cpp + src/ZclCallbacks.cpp + src/WindowCovering.cpp + ${TELINK_COMMON}/util/src/LEDWidget.cpp + ${TELINK_COMMON}/util/src/ButtonManager.cpp + ${TELINK_COMMON}/util/src/ThreadUtil.cpp + ${TELINK_COMMON}/util/src/PWMDevice.cpp + ) + +chip_configure_data_model(app + INCLUDE_SERVER + ZAP_FILE ${CMAKE_CURRENT_SOURCE_DIR}/../common/window-app.zap +) + +if(CONFIG_CHIP_OTA_REQUESTOR) + target_sources(app PRIVATE ${TELINK_COMMON}/util/src/OTAUtil.cpp) +endif() + +# Fix for unused swap parameter in: zephyr/include/zephyr/arch/riscv/irq.h:70 +add_compile_options(-Wno-error=unused-parameter) + +if (CONFIG_CHIP_PW_RPC) + +# Make all targets created below depend on zephyr_interface to inherit MCU-related compilation flags +link_libraries($) + +set(PIGWEED_ROOT "${CHIP_ROOT}/third_party/pigweed/repo") +include(${PIGWEED_ROOT}/pw_build/pigweed.cmake) +include(${PIGWEED_ROOT}/pw_protobuf_compiler/proto.cmake) + +pw_set_module_config(pw_rpc_CONFIG pw_rpc.disable_global_mutex_config) +pw_set_backend(pw_log pw_log_basic) +pw_set_backend(pw_assert.check pw_assert_log.check_backend) +pw_set_backend(pw_assert.assert pw_assert.assert_compatibility_backend) +pw_set_backend(pw_sys_io pw_sys_io.telink) +pw_set_backend(pw_trace pw_trace_tokenized) +set(dir_pw_third_party_nanopb "${CHIP_ROOT}/third_party/nanopb/repo" CACHE STRING "" FORCE) + +add_subdirectory(third_party/connectedhomeip/third_party/pigweed/repo) +add_subdirectory(third_party/connectedhomeip/third_party/nanopb/repo) +add_subdirectory(third_party/connectedhomeip/examples/platform/telink/pw_sys_io) + +pw_proto_library(attributes_service + SOURCES + ${CHIP_ROOT}/examples/common/pigweed/protos/attributes_service.proto + INPUTS + ${CHIP_ROOT}/examples/common/pigweed/protos/attributes_service.options + PREFIX + attributes_service + STRIP_PREFIX + ${CHIP_ROOT}/examples/common/pigweed/protos + DEPS + pw_protobuf.common_proto +) + +pw_proto_library(button_service + SOURCES + ${CHIP_ROOT}/examples/common/pigweed/protos/button_service.proto + PREFIX + button_service + STRIP_PREFIX + ${CHIP_ROOT}/examples/common/pigweed/protos + DEPS + pw_protobuf.common_proto +) + +pw_proto_library(descriptor_service + SOURCES + ${CHIP_ROOT}/examples/common/pigweed/protos/descriptor_service.proto + PREFIX + descriptor_service + STRIP_PREFIX + ${CHIP_ROOT}/examples/common/pigweed/protos + DEPS + pw_protobuf.common_proto +) + +pw_proto_library(device_service + SOURCES + ${CHIP_ROOT}/examples/common/pigweed/protos/device_service.proto + INPUTS + ${CHIP_ROOT}/examples/common/pigweed/protos/device_service.options + PREFIX + device_service + STRIP_PREFIX + ${CHIP_ROOT}/examples/common/pigweed/protos + DEPS + pw_protobuf.common_proto +) + +pw_proto_library(lighting_service + SOURCES + ${CHIP_ROOT}/examples/common/pigweed/protos/lighting_service.proto + STRIP_PREFIX + ${CHIP_ROOT}/examples/common/pigweed/protos + PREFIX + lighting_service + DEPS + pw_protobuf.common_proto +) + +pw_proto_library(ot_cli_service + SOURCES + ${CHIP_ROOT}/examples/common/pigweed/protos/ot_cli_service.proto + INPUTS + ${CHIP_ROOT}/examples/common/pigweed/protos/ot_cli_service.options + STRIP_PREFIX + ${CHIP_ROOT}/examples/common/pigweed/protos + PREFIX + ot_cli_service + DEPS + pw_protobuf.common_proto +) + +pw_proto_library(thread_service + SOURCES + ${CHIP_ROOT}/examples/common/pigweed/protos/thread_service.proto + INPUTS + ${CHIP_ROOT}/examples/common/pigweed/protos/thread_service.options + STRIP_PREFIX + ${CHIP_ROOT}/examples/common/pigweed/protos + PREFIX + thread_service + DEPS + pw_protobuf.common_proto +) + +target_sources(app PRIVATE + ../../common/pigweed/RpcService.cpp + ../../common/pigweed/telink/PigweedLoggerMutex.cpp + ${TELINK_COMMON}/Rpc.cpp + ${TELINK_COMMON}/util/src/PigweedLogger.cpp +) + +target_include_directories(app PRIVATE + ${PIGWEED_ROOT}/pw_sys_io/public + ${CHIP_ROOT}/src/lib/support + ${CHIP_ROOT}/src/system + ${TELINK_COMMON} + ../../common + ../../common/pigweed + ../../common/pigweed/telink) + +target_compile_options(app PRIVATE + "-DPW_RPC_ATTRIBUTE_SERVICE=1" + "-DPW_RPC_BUTTON_SERVICE=1" + "-DPW_RPC_DESCRIPTOR_SERVICE=1" + "-DPW_RPC_DEVICE_SERVICE=1" + "-DPW_RPC_LIGHTING_SERVICE=1" + "-DPW_RPC_THREAD_SERVICE=1" + "-DPW_RPC_TRACING_SERVICE=1" + "-DPW_TRACE_BACKEND_SET=1") + +target_link_libraries(app PRIVATE + attributes_service.nanopb_rpc + button_service.nanopb_rpc + descriptor_service.nanopb_rpc + device_service.nanopb_rpc + lighting_service.nanopb_rpc + thread_service.nanopb_rpc + pw_checksum + pw_hdlc + pw_log + pw_rpc.server + pw_trace_tokenized + pw_trace_tokenized.trace_buffer + pw_trace_tokenized.rpc_service + pw_trace_tokenized.protos.nanopb_rpc +) + +target_link_options(app + PUBLIC + "-T${PIGWEED_ROOT}/pw_tokenizer/pw_tokenizer_linker_sections.ld" +) + +endif(CONFIG_CHIP_PW_RPC) diff --git a/examples/window-app/telink/README.md b/examples/window-app/telink/README.md new file mode 100644 index 00000000000000..b2e7d3df219243 --- /dev/null +++ b/examples/window-app/telink/README.md @@ -0,0 +1,257 @@ +# Matter Telink Window Example Application + +The Telink Window Example demonstrates how to remotely control a window shutter +device. It uses buttons to test changing cover position and device states and +LEDs to show the state of these changes. You can use this example as a reference +for creating your own application. + +![Telink B91 EVK](http://wiki.telink-semi.cn/wiki/assets/Hardware/B91_Generic_Starter_Kit_Hardware_Guide/connection_chart.png) + +## Build and flash + +1. Pull docker image from repository: + + ```bash + $ docker pull connectedhomeip/chip-build-telink:latest + ``` + +1. Run docker container: + + ```bash + $ docker run -it --rm -v ${CHIP_BASE}:/root/chip -v /dev/bus/usb:/dev/bus/usb --device-cgroup-rule "c 189:* rmw" connectedhomeip/chip-build-telink:latest + ``` + + here `${CHIP_BASE}` is directory which contains CHIP repo files **!!!Pay + attention that OUTPUT_DIR should contains ABSOLUTE path to output dir** + +1. Activate the build environment: + + ```bash + $ source ./scripts/activate.sh + ``` + +1. In the example dir run: + + ```bash + $ west build + ``` + +1. Flash binary: + + ``` + $ west flash --erase + ``` + +## Usage + +### UART + +To get output from device, connect UART to following pins: + +| Name | Pin | +| :--: | :---------------------------- | +| RX | PB3 (pin 17 of J34 connector) | +| TX | PB2 (pin 16 of J34 connector) | +| GND | GND | + +### Buttons + +The following buttons are available on **tlsr9518adk80d** board: + +| Name | Function | Description | +| :------- | :-------------------------------- | :------------------------------------------------------------------------------------------------------------------- | +| Button 1 | Factory reset | Triple press performs factory reset to forget currently commissioned Thread network and back to uncommissioned state | +| Button 2 | Open and Toggle Move Type control | Manually triggers the Open state by one press and double press triggers the Lift-Tilt move type | +| Button 3 | Open commission window | The button is opening commissioning window to perform commissioning over BLE | +| Button 4 | Close control | Manually triggers the Close state by one press | + +### LEDs + +#### Indicate open-close position of Window Cover + +**Blue** LED indicates current Lift position (PWM in range of 0-254). To +indicate the Tilt state in the same way connect external LED to pin PE0. + +#### Indicate current state of Thread network + +**Red** LED indicates current state of Thread network. It is able to be in +following states: + +| State | Description | +| :-------------------------- | :--------------------------------------------------------------------------- | +| Blinks with short pulses | Device is not commissioned to Thread, Thread is disabled | +| Blinks with frequent pulses | Device is commissioned, Thread enabled. Device trying to JOIN thread network | +| Blinks with wide pulses | Device commissioned and joined to thread network as CHILD | + +#### Indicate identify of device + +**Green** LED used to identify the device. The LED starts blinking when the +Identify command of the Identify cluster is received. The command's argument can +be used to specify the the effect. It is able to be in following effects: + +| Effect | Description | +| :------------------------------ | :------------------------------------------------------------------- | +| Blinks (200 ms on/200 ms off) | Blink (EMBER_ZCL_IDENTIFY_EFFECT_IDENTIFIER_BLINK) | +| Breathe (during 1000 ms) | Breathe (EMBER_ZCL_IDENTIFY_EFFECT_IDENTIFIER_BREATHE) | +| Blinks (50 ms on/950 ms off) | Okay (EMBER_ZCL_IDENTIFY_EFFECT_IDENTIFIER_OKAY) | +| Blinks (1000 ms on/1000 ms off) | Channel Change (EMBER_ZCL_IDENTIFY_EFFECT_IDENTIFIER_CHANNEL_CHANGE) | +| Blinks (950 ms on/50 ms off) | Finish (EMBER_ZCL_IDENTIFY_EFFECT_IDENTIFIER_FINISH_EFFECT) | +| LED off | Stop (EMBER_ZCL_IDENTIFY_EFFECT_IDENTIFIER_STOP_EFFECT) | + +### CHIP tool commands + +1. Build + [chip-tool cli](https://github.com/project-chip/connectedhomeip/blob/master/examples/chip-tool/README.md) + +2. Pair with device + + ``` + ${CHIP_TOOL_DIR}/chip-tool pairing ble-thread ${NODE_ID} hex:${DATASET} ${PIN_CODE} ${DISCRIMINATOR} + ``` + + Example: + + ``` + ./chip-tool pairing ble-thread 1234 hex:0e080000000000010000000300000f35060004001fffe0020811111111222222220708fd61f77bd3df233e051000112233445566778899aabbccddeeff030e4f70656e54687265616444656d6f010212340410445f2b5ca6f2a93a55ce570a70efeecb0c0402a0fff8 20202021 3840 + ``` + +3. Switch on the light: + + ``` + ${CHIP_TOOL_DIR}/chip-tool onoff on 1 + ``` + + here: + + - **onoff** is name of cluster + - **on** command to the cluster + - **1** ID of endpoint + +4. Switch off the light: + + ``` + ${CHIP_TOOL_DIR}/chip-tool onoff off 1 + ``` + + here: + + - **onoff** is name of cluster + - **off** command to the cluster + - **1** ID of endpoint + +5. Read the light state: + + ``` + ${CHIP_TOOL_DIR}/chip-tool onoff read on-off 1 + ``` + + here: + + - **onoff** is name of cluster + - **read** command to the cluster + - **on-off** attribute to read + - **1** ID of endpoint + +6. Change brightness of light: + + ``` + ${CHIP_TOOL_DIR}/chip-tool levelcontrol move-to-level 32 0 0 0 1 + ``` + + here: + + - **levelcontrol** is name of cluster + - **move-to-level** command to the cluster + - **32** brightness value + - **0** transition time + - **0** option mask + - **0** option override + - **1** ID of endpoint + +7. Read brightness level: + ``` + ./chip-tool levelcontrol read current-level 1 + ``` + here: + - **levelcontrol** is name of cluster + - **read** command to the cluster + - **current-level** attribute to read + - **1** ID of endpoint + +### OTA with Linux OTA Provider + +OTA feature enabled by default only for ota-requestor-app example. To enable OTA +feature for another Telink example: + +- set CONFIG_CHIP_OTA_REQUESTOR=y in corresponding "prj.conf" configuration + file. + +After build application with enabled OTA feature, use next binary files: + +- zephyr.bin - main binary to flash PCB (Use 2MB PCB). +- zephyr-ota.bin - binary for OTA Provider + +All binaries has the same SW version. To test OTA “zephyr-ota.bin” should have +higher SW version than base SW. Set CONFIG_CHIP_DEVICE_SOFTWARE_VERSION=2 in +corresponding “prj.conf” configuration file. + +Usage of OTA: + +- Build the [Linux OTA Provider](../../ota-provider-app/linux) + + ``` + ./scripts/examples/gn_build_example.sh examples/ota-provider-app/linux out/ota-provider-app chip_config_network_layer_ble=false + ``` + +- Run the Linux OTA Provider with OTA image. + + ``` + ./chip-ota-provider-app -f zephyr-ota.bin + ``` + +- Provision the Linux OTA Provider using chip-tool + + ``` + ./chip-tool pairing onnetwork ${OTA_PROVIDER_NODE_ID} 20202021 + ``` + + here: + + - \${OTA_PROVIDER_NODE_ID} is the node id of Linux OTA Provider + +- Configure the ACL of the ota-provider-app to allow access + + ``` + ./chip-tool accesscontrol write acl '[{"fabricIndex": 1, "privilege": 5, "authMode": 2, "subjects": [112233], "targets": null}, {"fabricIndex": 1, "privilege": 3, "authMode": 2, "subjects": null, "targets": null}]' ${OTA_PROVIDER_NODE_ID} 0 + ``` + + here: + + - \${OTA_PROVIDER_NODE_ID} is the node id of Linux OTA Provider + +- Use the chip-tool to announce the ota-provider-app to start the OTA process + + ``` + ./chip-tool otasoftwareupdaterequestor announce-ota-provider ${OTA_PROVIDER_NODE_ID} 0 0 0 ${DEVICE_NODE_ID} 0 + ``` + + here: + + - \${OTA_PROVIDER_NODE_ID} is the node id of Linux OTA Provider + - \${DEVICE_NODE_ID} is the node id of paired device + +Once the transfer is complete, OTA requestor sends ApplyUpdateRequest command to +OTA provider for applying the image. Device will restart on successful +application of OTA image. + +### Building with Pigweed RPCs + +The RPCs in `lighting-common/lighting_service/lighting_service.proto` can be +used to control various functionalities of the lighting app from a USB-connected +host computer. To build the example with the RPC server, run the following +command with _build-target_ replaced with the build target name of the Nordic +Semiconductor's kit you own: + + ``` + $ west build -b tlsr9518adk80d -- -DOVERLAY_CONFIG=rpc.overlay + ``` diff --git a/examples/window-app/telink/include/AppConfig.h b/examples/window-app/telink/include/AppConfig.h new file mode 100644 index 00000000000000..3193c1b186b21b --- /dev/null +++ b/examples/window-app/telink/include/AppConfig.h @@ -0,0 +1,40 @@ +/* + * + * Copyright (c) 2023 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +// ---- Window App Example Config ---- + +// Buttons config +#define BUTTON_PORT DEVICE_DT_GET(DT_NODELABEL(gpioc)) + +#define BUTTON_PIN_1 2 +#define BUTTON_PIN_3 3 +#define BUTTON_PIN_4 1 +#define BUTTON_PIN_2 0 + +// LEDs config +#define LEDS_PORT DEVICE_DT_GET(DT_NODELABEL(gpiob)) +#define SYSTEM_STATE_LED 7 +#define LIFT_STATE_LED 6 + +#define LIGHTING_PWM_SPEC_IDENTIFY_GREEN PWM_DT_SPEC_GET(DT_ALIAS(pwm_led3)) +#define LIGHTING_PWM_SPEC_RGB_BLUE PWM_DT_SPEC_GET(DT_ALIAS(pwm_led0)) + +// NOTE: pwm_led1 by default DTS is used by PE0, so get an external LED connected to that pin +#define LIGHTING_PWM_SPEC_RGB_GREEN PWM_DT_SPEC_GET(DT_ALIAS(pwm_led1)) diff --git a/examples/window-app/telink/include/AppEvent.h b/examples/window-app/telink/include/AppEvent.h new file mode 100644 index 00000000000000..c6c12517c32b66 --- /dev/null +++ b/examples/window-app/telink/include/AppEvent.h @@ -0,0 +1,59 @@ +/* + * + * Copyright (c) 2023 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +struct AppEvent; +typedef void (*EventHandler)(AppEvent *); + +class LEDWidget; + +struct AppEvent +{ + enum AppEventTypes + { + kEventType_Button = 0, + kEventType_Timer, + kEventType_UpdateLedState, + kEventType_IdentifyStart, + kEventType_IdentifyStop, + }; + + uint16_t Type; + + union + { + struct + { + uint8_t PinNo; + uint8_t Action; + } ButtonEvent; + struct + { + void * Context; + } TimerEvent; + struct + { + LEDWidget * LedWidget; + } UpdateLedStateEvent; + }; + + EventHandler Handler; +}; diff --git a/examples/window-app/telink/include/AppTask.h b/examples/window-app/telink/include/AppTask.h new file mode 100644 index 00000000000000..9bfe3a8ec76a4c --- /dev/null +++ b/examples/window-app/telink/include/AppTask.h @@ -0,0 +1,109 @@ +/* + * + * Copyright (c) 2023 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "AppConfig.h" +#include "AppEvent.h" +#include "WindowCovering.h" +#if CONFIG_CHIP_ENABLE_APPLICATION_STATUS_LED +#include "LEDWidget.h" +#endif +#include "PWMDevice.h" +#include + +#if CONFIG_CHIP_FACTORY_DATA +#include +#endif + +#ifdef CONFIG_CHIP_PW_RPC +#include "Rpc.h" +#endif + +#include + +struct k_timer; +struct Identify; + +class AppTask +{ +public: + CHIP_ERROR StartApp(void); + + void PostEvent(AppEvent * aEvent); + void UpdateClusterState(void); + + static void IdentifyEffectHandler(EmberAfIdentifyEffectIdentifier aEffect); + +private: +#ifdef CONFIG_CHIP_PW_RPC + friend class chip::rpc::TelinkButton; +#endif + friend AppTask & GetAppTask(void); + CHIP_ERROR Init(void); + + static void ActionInitiated(PWMDevice::Action_t aAction, int32_t aActor); + static void ActionCompleted(PWMDevice::Action_t aAction, int32_t aActor); + static void ActionIdentifyStateUpdateHandler(k_timer * timer); + + void DispatchEvent(AppEvent * event); + +#if CONFIG_CHIP_ENABLE_APPLICATION_STATUS_LED + static void UpdateLedStateEventHandler(AppEvent * aEvent); + static void LEDStateUpdateHandler(LEDWidget * ledWidget); + static void UpdateStatusLED(); +#endif + static void LightingActionButtonEventHandler(void); + static void OpenActionAndToggleMoveTypeButtonEventHandler(void); + static void CloseActionButtonEventHandler(void); + static void FactoryResetButtonEventHandler(void); + static void StartBleAdvButtonEventHandler(void); + + static void ChipEventHandler(const chip::DeviceLayer::ChipDeviceEvent * event, intptr_t arg); + + static void FactoryResetTimerTimeoutCallback(k_timer * timer); + static void OpenTimerTimeoutCallback(k_timer * timer); + static void OpenTimerEventHandler(AppEvent * aEvent); + static void ToggleMoveTypeHandler(AppEvent * aEvent); + + static void FactoryResetTimerEventHandler(AppEvent * aEvent); + static void FactoryResetHandler(AppEvent * aEvent); + static void LightingActionEventHandler(AppEvent * aEvent); + static void StartBleAdvHandler(AppEvent * aEvent); + static void UpdateIdentifyStateEventHandler(AppEvent * aEvent); + + static void OpenHandler(AppEvent * aEvent); + static void CloseHandler(AppEvent * aEvent); + static void ToggleMoveType(); + + static void InitButtons(void); + + static AppTask sAppTask; + PWMDevice mPwmIdentifyLed; + + OperationalState mMoveType{ OperationalState::MovingUpOrOpen }; + +#if CONFIG_CHIP_FACTORY_DATA + chip::DeviceLayer::FactoryDataProvider mFactoryDataProvider; +#endif +}; + +inline AppTask & GetAppTask(void) +{ + return AppTask::sAppTask; +} diff --git a/examples/window-app/telink/include/CHIPProjectConfig.h b/examples/window-app/telink/include/CHIPProjectConfig.h new file mode 100644 index 00000000000000..824acd90d46cbe --- /dev/null +++ b/examples/window-app/telink/include/CHIPProjectConfig.h @@ -0,0 +1,44 @@ +/* + * + * Copyright (c) 2023 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file + * Example project configuration file for CHIP. + * + * This is a place to put application or project-specific overrides + * to the default configuration values for general CHIP features. + * + */ + +#pragma once + +// 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 + +// Switching from Thread child to router may cause a few second packet stall. +// Until this is improved in OpenThread we need to increase the retransmission +// interval to survive the stall. +#define CHIP_CONFIG_MRP_LOCAL_ACTIVE_RETRY_INTERVAL (1000_ms32) + +/** + * CHIP_SYSTEM_CONFIG_PACKETBUFFER_POOL_SIZE + * + * Reduce packet buffer pool size to 8 (default 15) to reduce ram consumption + */ +#define CHIP_SYSTEM_CONFIG_PACKETBUFFER_POOL_SIZE 8 diff --git a/examples/window-app/telink/include/WindowCovering.h b/examples/window-app/telink/include/WindowCovering.h new file mode 100644 index 00000000000000..872e3c5c083284 --- /dev/null +++ b/examples/window-app/telink/include/WindowCovering.h @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2023 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "LEDWidget.h" +#include "PWMDevice.h" + +#include +#include + +#include + +using namespace chip::app::Clusters::WindowCovering; + +class WindowCovering +{ +public: + struct AttributeUpdateData + { + chip::EndpointId mEndpoint; + chip::AttributeId mAttributeId; + }; + + WindowCovering(); + static WindowCovering & Instance() + { + static WindowCovering sInstance; + return sInstance; + } + + PWMDevice & GetLiftIndicator() { return mLiftIndicator; } + PWMDevice & GetTiltIndicator() { return mTiltIndicator; } + + void StartMove(WindowCoveringType aMoveType); + void SetSingleStepTarget(OperationalState aDirection); + void SetMoveType(WindowCoveringType aMoveType) { mCurrentUIMoveType = aMoveType; } + WindowCoveringType GetMoveType() { return mCurrentUIMoveType; } + void PositionLEDUpdate(WindowCoveringType aMoveType); + + static void SchedulePostAttributeChange(chip::EndpointId aEndpoint, chip::AttributeId aAttributeId); + static constexpr chip::EndpointId Endpoint() { return 1; }; + +private: + void SetBrightness(WindowCoveringType aMoveType, uint16_t aPosition); + void SetTargetPosition(OperationalState aDirection, chip::Percent100ths aPosition); + uint8_t PositionToBrightness(uint16_t aPosition, WindowCoveringType aMoveType); + + static void UpdateOperationalStatus(WindowCoveringType aMoveType, OperationalState aDirection); + static bool TargetCompleted(WindowCoveringType aMoveType, NPercent100ths aCurrent, NPercent100ths aTarget); + static void StartTimer(WindowCoveringType aMoveType, uint32_t aTimeoutMs); + static chip::Percent100ths CalculateSingleStep(WindowCoveringType aMoveType); + static void DriveCurrentLiftPosition(intptr_t); + static void DriveCurrentTiltPosition(intptr_t); + static void MoveTimerTimeoutCallback(chip::System::Layer * systemLayer, void * appState); + static void DoPostAttributeChange(intptr_t aArg); + + WindowCoveringType mCurrentUIMoveType; + + PWMDevice mLiftIndicator; + PWMDevice mTiltIndicator; + bool mInLiftMove{ false }; + bool mInTiltMove{ false }; +}; diff --git a/examples/window-app/telink/prj.conf b/examples/window-app/telink/prj.conf new file mode 100644 index 00000000000000..a148cdaac140c9 --- /dev/null +++ b/examples/window-app/telink/prj.conf @@ -0,0 +1,78 @@ +# +# Copyright (c) 2023 Project CHIP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# This sample uses sample-defaults.conf to set options common for all +# samples. This file should contain only options specific for this sample +# or overrides of default values. + +# enable GPIO +CONFIG_GPIO=y + +# enable PWM +CONFIG_PWM=y + +# OpenThread configs +CONFIG_OPENTHREAD_MTD=n +CONFIG_OPENTHREAD_FTD=y +CONFIG_CHIP_ENABLE_SLEEPY_END_DEVICE_SUPPORT=n +CONFIG_CHIP_SED_IDLE_INTERVAL=200 +CONFIG_CHIP_THREAD_SSED=n + +# Default OpenThread network settings +CONFIG_OPENTHREAD_PANID=4660 +CONFIG_OPENTHREAD_CHANNEL=15 +CONFIG_OPENTHREAD_NETWORK_NAME="OpenThreadDemo" +CONFIG_OPENTHREAD_XPANID="11:11:11:11:22:22:22:22" + +# Disable Matter OTA DFU +CONFIG_CHIP_OTA_REQUESTOR=n + +# CHIP configuration +CONFIG_CHIP_PROJECT_CONFIG="include/CHIPProjectConfig.h" +CONFIG_CHIP_OPENTHREAD_CONFIG="../../platform/telink/project_include/OpenThreadConfig.h" + +CONFIG_CHIP_DEVICE_VENDOR_ID=65521 +# 32784 == 0x8010 (example window-app) +CONFIG_CHIP_DEVICE_PRODUCT_ID=32784 +CONFIG_CHIP_DEVICE_TYPE=65535 + +CONFIG_CHIP_DEVICE_SOFTWARE_VERSION=1 +CONFIG_CHIP_DEVICE_SOFTWARE_VERSION_STRING="2022" + +# Enable CHIP pairing automatically on application start. +CONFIG_CHIP_ENABLE_PAIRING_AUTOSTART=y + +# CHIP shell +CONFIG_CHIP_LIB_SHELL=n + +# Disable factory data support. +CONFIG_CHIP_FACTORY_DATA=n +CONFIG_CHIP_FACTORY_DATA_BUILD=n +CONFIG_CHIP_FACTORY_DATA_MERGE_WITH_FIRMWARE=n + +# Enable Button IRQ mode. The poling mode is used by default. +CONFIG_CHIP_BUTTON_MANAGER_IRQ_MODE=n + +# Disable Status LED. +CONFIG_CHIP_ENABLE_APPLICATION_STATUS_LED=y + +# Enable Power Management +CONFIG_PM=n +CONFIG_PM_DEVICE=n + +# Custom RF power values +CONFIG_B91_BLE_CTRL_RF_POWER_P9P11DBM=y +CONFIG_IEEE802154_B91_CUSTOM_RF_POWER=9 diff --git a/examples/window-app/telink/rpc.overlay b/examples/window-app/telink/rpc.overlay new file mode 100644 index 00000000000000..a97621181efc75 --- /dev/null +++ b/examples/window-app/telink/rpc.overlay @@ -0,0 +1,47 @@ +# +# Copyright (c) 2020 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 file should be used as a configuration overlay to build Pigweed RPCs to +# lighting-app. + +# Enable Pigweed RPC +CONFIG_CHIP_PW_RPC=y + +# Add support for C++17 to build Pigweed components +CONFIG_STD_CPP14=n +CONFIG_STD_CPP17=y + +# Add support for Zephyr console component to use it for Pigweed console purposes +CONFIG_CONSOLE_SUBSYS=y +CONFIG_CONSOLE_GETCHAR=y +CONFIG_CONSOLE_PUTCHAR_BUFSIZE=256 + +# Disable features which may interfere with Pigweed HDLC transport +CONFIG_SHELL=n +CONFIG_OPENTHREAD_SHELL=n +CONFIG_BOOT_BANNER=n + +# Configure Zephyr logger with defaults backends disabled as the app provides its own, +# based on Pigweed HDLC. +CONFIG_LOG=y +CONFIG_LOG_MODE_MINIMAL=n +CONFIG_LOG_MODE_IMMEDIATE=y +CONFIG_LOG_BACKEND_UART=n +CONFIG_LOG_BACKEND_RTT=n +CONFIG_LOG_OUTPUT=y + +# Increase zephyr tty rx buffer +CONFIG_CONSOLE_GETCHAR_BUFSIZE=128 diff --git a/examples/window-app/telink/src/AppTask.cpp b/examples/window-app/telink/src/AppTask.cpp new file mode 100644 index 00000000000000..6f86059a8b6b40 --- /dev/null +++ b/examples/window-app/telink/src/AppTask.cpp @@ -0,0 +1,631 @@ +/* + * + * Copyright (c) 2023 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "AppTask.h" + +#include "AppConfig.h" +#include "AppEvent.h" +#include "ButtonManager.h" +#include "WindowCovering.h" + +#include "ThreadUtil.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if CONFIG_CHIP_OTA_REQUESTOR +#include "OTAUtil.h" +#endif + +#include +#include + +#include + +#if CONFIG_CHIP_LIB_SHELL +#include +#include + +static int cmd_telink_reboot(const struct shell * shell, size_t argc, char ** argv) +{ + ARG_UNUSED(argc); + ARG_UNUSED(argv); + + shell_print(shell, "Performing board reboot..."); + sys_reboot(); +} + +SHELL_STATIC_SUBCMD_SET_CREATE(sub_telink, SHELL_CMD(reboot, NULL, "Reboot board command", cmd_telink_reboot), + SHELL_SUBCMD_SET_END); +SHELL_CMD_REGISTER(telink, &sub_telink, "Telink commands", NULL); +#endif // CONFIG_CHIP_LIB_SHELL + +LOG_MODULE_DECLARE(app, CONFIG_CHIP_APP_LOG_LEVEL); + +using namespace ::chip; +using namespace ::chip::app; +using namespace ::chip::Credentials; +using namespace ::chip::DeviceLayer; + +namespace { +constexpr int kFactoryResetCalcTimeout = 3000; +constexpr int kToggleMoveTypeTriggerTimeout = 700; +constexpr int kFactoryResetTriggerCntr = 3; +constexpr int kAppEventQueueSize = 10; +constexpr uint8_t kButtonPushEvent = 1; +constexpr uint8_t kButtonReleaseEvent = 0; +constexpr EndpointId kLightEndpointId = 1; +constexpr uint8_t kDefaultMinLevel = 0; +constexpr uint8_t kDefaultMaxLevel = 254; +constexpr uint32_t kIdentifyBlinkRateMs = 200; +constexpr uint32_t kIdentifyOkayOnRateMs = 50; +constexpr uint32_t kIdentifyOkayOffRateMs = 950; +constexpr uint32_t kIdentifyFinishOnRateMs = 950; +constexpr uint32_t kIdentifyFinishOffRateMs = 50; +constexpr uint32_t kIdentifyChannelChangeRateMs = 1000; +constexpr uint32_t kIdentifyBreatheRateMs = 1000; + +const struct pwm_dt_spec sPwmIdentifySpecGreenLed = LIGHTING_PWM_SPEC_IDENTIFY_GREEN; + +#if CONFIG_CHIP_FACTORY_DATA +// NOTE! This key is for test/certification only and should not be available in production devices! +uint8_t sTestEventTriggerEnableKey[TestEventTriggerDelegate::kEnableKeyLength] = { 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, + 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff }; +#endif + +K_MSGQ_DEFINE(sAppEventQueue, sizeof(AppEvent), kAppEventQueueSize, alignof(AppEvent)); +k_timer sFactoryResetTimer; +k_timer sToggleMoveTypeTimer; + +uint8_t sFactoryResetCntr = 0; + +#if CONFIG_CHIP_ENABLE_APPLICATION_STATUS_LED +LEDWidget sStatusLED; +LEDWidget sIdentifyLED; +#endif + +Button sFactoryResetButton; +Button sBleAdvStartButton; +Button sOpenButton; +Button sCloseButton; + +bool sIsThreadProvisioned = false; +bool sIsThreadEnabled = false; +bool sIsThreadAttached = false; +bool sHaveBLEConnections = false; +bool sIsToggleMoveTypeTimerActive = false; + +chip::DeviceLayer::DeviceInfoProviderImpl gExampleDeviceInfoProvider; + +void OnIdentifyTriggerEffect(Identify * identify) +{ + AppTask::IdentifyEffectHandler(identify->mCurrentEffectIdentifier); +} + +Identify sIdentify = { + kLightEndpointId, + [](Identify *) { ChipLogProgress(Zcl, "OnIdentifyStart"); }, + [](Identify *) { ChipLogProgress(Zcl, "OnIdentifyStop"); }, + EMBER_ZCL_IDENTIFY_IDENTIFY_TYPE_VISIBLE_LED, + OnIdentifyTriggerEffect, +}; + +} // namespace + +AppTask AppTask::sAppTask; + +class AppFabricTableDelegate : public FabricTable::Delegate +{ + void OnFabricRemoved(const FabricTable & fabricTable, FabricIndex fabricIndex) + { + if (chip::Server::GetInstance().GetFabricTable().FabricCount() == 0) + { + chip::Server::GetInstance().ScheduleFactoryReset(); + } + } +}; + +CHIP_ERROR AppTask::Init(void) +{ + LOG_INF("SW Version: %u, %s", CHIP_DEVICE_CONFIG_DEVICE_SOFTWARE_VERSION, CHIP_DEVICE_CONFIG_DEVICE_SOFTWARE_VERSION_STRING); + + // Initialize LEDs +#if CONFIG_CHIP_ENABLE_APPLICATION_STATUS_LED + LEDWidget::InitGpio(LEDS_PORT); + LEDWidget::SetStateUpdateCallback(LEDStateUpdateHandler); + + sStatusLED.Init(SYSTEM_STATE_LED); + sIdentifyLED.Init(LIFT_STATE_LED); + sIdentifyLED.Set(false); + + UpdateStatusLED(); +#endif + + InitButtons(); + + // Initialize function button timer + k_timer_init(&sFactoryResetTimer, &AppTask::FactoryResetTimerTimeoutCallback, nullptr); + k_timer_user_data_set(&sFactoryResetTimer, this); + // Initialize ToggleMoveType timer + k_timer_init(&sToggleMoveTypeTimer, &AppTask::OpenTimerTimeoutCallback, nullptr); + k_timer_user_data_set(&sToggleMoveTypeTimer, this); + + // // Initialize PWM LEDs + CHIP_ERROR err = sAppTask.mPwmIdentifyLed.Init(&sPwmIdentifySpecGreenLed, kDefaultMinLevel, kDefaultMaxLevel, kDefaultMaxLevel); + if (err != CHIP_NO_ERROR) + { + LOG_ERR("Green IDENTIFY PWM Device Init fail"); + return err; + } + sAppTask.mPwmIdentifyLed.SetCallbacks(nullptr, nullptr, ActionIdentifyStateUpdateHandler); + + // Initialize CHIP server +#if CONFIG_CHIP_FACTORY_DATA + ReturnErrorOnFailure(mFactoryDataProvider.Init()); + SetDeviceInstanceInfoProvider(&mFactoryDataProvider); + SetDeviceAttestationCredentialsProvider(&mFactoryDataProvider); + SetCommissionableDataProvider(&mFactoryDataProvider); + // Read EnableKey from the factory data. + MutableByteSpan enableKey(sTestEventTriggerEnableKey); + err = mFactoryDataProvider.GetEnableKey(enableKey); + if (err != CHIP_NO_ERROR) + { + LOG_ERR("GetEnableKey fail"); + memset(sTestEventTriggerEnableKey, 0, sizeof(sTestEventTriggerEnableKey)); + } +#else + SetDeviceAttestationCredentialsProvider(Examples::GetExampleDACProvider()); +#endif + + static CommonCaseDeviceServerInitParams initParams; + (void) initParams.InitializeStaticResourcesBeforeServerInit(); + ReturnErrorOnFailure(chip::Server::GetInstance().Init(initParams)); + + gExampleDeviceInfoProvider.SetStorageDelegate(&Server::GetInstance().GetPersistentStorage()); + chip::DeviceLayer::SetDeviceInfoProvider(&gExampleDeviceInfoProvider); + +#if CONFIG_CHIP_OTA_REQUESTOR + InitBasicOTARequestor(); +#endif + + ConfigurationMgr().LogDeviceConfig(); + PrintOnboardingCodes(chip::RendezvousInformationFlags(chip::RendezvousInformationFlag::kBLE)); + + // Add CHIP event handler and start CHIP thread. + // Note that all the initialization code should happen prior to this point to avoid data races + // between the main and the CHIP threads. + PlatformMgr().AddEventHandler(ChipEventHandler, 0); + + err = ConnectivityMgr().SetBLEDeviceName("TelinkWindow"); + if (err != CHIP_NO_ERROR) + { + LOG_ERR("SetBLEDeviceName fail"); + return err; + } + + err = chip::Server::GetInstance().GetFabricTable().AddFabricDelegate(new AppFabricTableDelegate); + if (err != CHIP_NO_ERROR) + { + LOG_ERR("AppFabricTableDelegate fail"); + return err; + } + + return CHIP_NO_ERROR; +} + +CHIP_ERROR AppTask::StartApp(void) +{ + CHIP_ERROR err = Init(); + + if (err != CHIP_NO_ERROR) + { + LOG_ERR("AppTask Init fail"); + return err; + } + + AppEvent event = {}; + + while (true) + { + k_msgq_get(&sAppEventQueue, &event, K_FOREVER); + DispatchEvent(&event); + } +} + +void AppTask::OpenActionAndToggleMoveTypeButtonEventHandler(void) +{ + AppEvent event; + + event.Type = AppEvent::kEventType_Button; + event.ButtonEvent.Action = kButtonPushEvent; + event.Handler = ToggleMoveTypeHandler; + sAppTask.PostEvent(&event); +} + +void AppTask::CloseActionButtonEventHandler(void) +{ + AppEvent event; + + event.Type = AppEvent::kEventType_Button; + event.ButtonEvent.Action = kButtonPushEvent; + event.Handler = CloseHandler; + sAppTask.PostEvent(&event); +} + +void AppTask::IdentifyEffectHandler(EmberAfIdentifyEffectIdentifier aEffect) +{ + AppEvent event; + event.Type = AppEvent::kEventType_IdentifyStart; + + switch (aEffect) + { + case EMBER_ZCL_IDENTIFY_EFFECT_IDENTIFIER_BLINK: + ChipLogProgress(Zcl, "EMBER_ZCL_IDENTIFY_EFFECT_IDENTIFIER_BLINK"); + event.Handler = [](AppEvent *) { + sAppTask.mPwmIdentifyLed.InitiateBlinkAction(kIdentifyBlinkRateMs, kIdentifyBlinkRateMs); + }; + break; + case EMBER_ZCL_IDENTIFY_EFFECT_IDENTIFIER_BREATHE: + ChipLogProgress(Zcl, "EMBER_ZCL_IDENTIFY_EFFECT_IDENTIFIER_BREATHE"); + event.Handler = [](AppEvent *) { + sAppTask.mPwmIdentifyLed.InitiateBreatheAction(PWMDevice::kBreatheType_Both, kIdentifyBreatheRateMs); + }; + break; + case EMBER_ZCL_IDENTIFY_EFFECT_IDENTIFIER_OKAY: + ChipLogProgress(Zcl, "EMBER_ZCL_IDENTIFY_EFFECT_IDENTIFIER_OKAY"); + event.Handler = [](AppEvent *) { + sAppTask.mPwmIdentifyLed.InitiateBlinkAction(kIdentifyOkayOnRateMs, kIdentifyOkayOffRateMs); + }; + break; + case EMBER_ZCL_IDENTIFY_EFFECT_IDENTIFIER_CHANNEL_CHANGE: + ChipLogProgress(Zcl, "EMBER_ZCL_IDENTIFY_EFFECT_IDENTIFIER_CHANNEL_CHANGE"); + event.Handler = [](AppEvent *) { + sAppTask.mPwmIdentifyLed.InitiateBlinkAction(kIdentifyChannelChangeRateMs, kIdentifyChannelChangeRateMs); + }; + break; + case EMBER_ZCL_IDENTIFY_EFFECT_IDENTIFIER_FINISH_EFFECT: + ChipLogProgress(Zcl, "EMBER_ZCL_IDENTIFY_EFFECT_IDENTIFIER_FINISH_EFFECT"); + event.Handler = [](AppEvent *) { + sAppTask.mPwmIdentifyLed.InitiateBlinkAction(kIdentifyFinishOnRateMs, kIdentifyFinishOffRateMs); + }; + break; + case EMBER_ZCL_IDENTIFY_EFFECT_IDENTIFIER_STOP_EFFECT: + ChipLogProgress(Zcl, "EMBER_ZCL_IDENTIFY_EFFECT_IDENTIFIER_STOP_EFFECT"); + event.Handler = [](AppEvent *) { sAppTask.mPwmIdentifyLed.StopAction(); }; + event.Type = AppEvent::kEventType_IdentifyStop; + break; + default: + ChipLogProgress(Zcl, "No identifier effect"); + return; + } + + sAppTask.PostEvent(&event); +} + +void AppTask::FactoryResetButtonEventHandler(void) +{ + AppEvent event; + + event.Type = AppEvent::kEventType_Button; + event.ButtonEvent.Action = kButtonPushEvent; + event.Handler = FactoryResetHandler; + sAppTask.PostEvent(&event); +} + +void AppTask::FactoryResetHandler(AppEvent * aEvent) +{ + if (sFactoryResetCntr == 0) + { + k_timer_start(&sFactoryResetTimer, K_MSEC(kFactoryResetCalcTimeout), K_NO_WAIT); + } + + sFactoryResetCntr++; + LOG_INF("Factory Reset Trigger Counter: %d/%d", sFactoryResetCntr, kFactoryResetTriggerCntr); + + if (sFactoryResetCntr == kFactoryResetTriggerCntr) + { + k_timer_stop(&sFactoryResetTimer); + sFactoryResetCntr = 0; + + chip::Server::GetInstance().ScheduleFactoryReset(); + } +} + +void AppTask::StartBleAdvButtonEventHandler(void) +{ + AppEvent event; + + event.Type = AppEvent::kEventType_Button; + event.ButtonEvent.Action = kButtonPushEvent; + event.Handler = StartBleAdvHandler; + sAppTask.PostEvent(&event); +} + +void AppTask::StartBleAdvHandler(AppEvent * aEvent) +{ + LOG_INF("StartBleAdvHandler"); + + // Don't allow on starting Matter service BLE advertising after Thread provisioning. + if (ConnectivityMgr().IsThreadProvisioned()) + { + LOG_INF("Device already commissioned"); + return; + } + + if (ConnectivityMgr().IsBLEAdvertisingEnabled()) + { + LOG_INF("BLE adv already enabled"); + return; + } + + if (chip::Server::GetInstance().GetCommissioningWindowManager().OpenBasicCommissioningWindow() != CHIP_NO_ERROR) + { + LOG_ERR("OpenBasicCommissioningWindow fail"); + } +} + +void AppTask::OpenTimerTimeoutCallback(k_timer * timer) +{ + if (!timer) + { + return; + } + + AppEvent event; + event.Type = AppEvent::kEventType_Timer; + event.Handler = OpenTimerEventHandler; + sAppTask.PostEvent(&event); +} + +void AppTask::OpenTimerEventHandler(AppEvent * aEvent) +{ + if (aEvent->Type != AppEvent::kEventType_Timer) + { + return; + } + + sIsToggleMoveTypeTimerActive = false; + + OpenHandler(aEvent); +} + +void AppTask::ToggleMoveTypeHandler(AppEvent * aEvent) +{ + if (!sIsToggleMoveTypeTimerActive) + { + k_timer_start(&sToggleMoveTypeTimer, K_MSEC(kToggleMoveTypeTriggerTimeout), K_NO_WAIT); + sIsToggleMoveTypeTimerActive = true; + } + else + { + k_timer_stop(&sToggleMoveTypeTimer); + sIsToggleMoveTypeTimerActive = false; + + sAppTask.ToggleMoveType(); + } +} + +void AppTask::OpenHandler(AppEvent * aEvent) +{ + WindowCovering::Instance().SetSingleStepTarget(OperationalState::MovingUpOrOpen); +} + +void AppTask::CloseHandler(AppEvent * aEvent) +{ + WindowCovering::Instance().SetSingleStepTarget(OperationalState::MovingDownOrClose); +} + +void AppTask::ToggleMoveType() +{ + if (WindowCovering::Instance().GetMoveType() == WindowCoveringType::Lift) + { + WindowCovering::Instance().SetMoveType(WindowCoveringType::Tilt); + LOG_INF("Window covering move: tilt"); + } + else + { + WindowCovering::Instance().SetMoveType(WindowCoveringType::Lift); + LOG_INF("Window covering move: lift"); + } +} + +#if CONFIG_CHIP_ENABLE_APPLICATION_STATUS_LED +void AppTask::UpdateLedStateEventHandler(AppEvent * aEvent) +{ + if (aEvent->Type == AppEvent::kEventType_UpdateLedState) + { + aEvent->UpdateLedStateEvent.LedWidget->UpdateState(); + } +} + +void AppTask::LEDStateUpdateHandler(LEDWidget * ledWidget) +{ + AppEvent event; + event.Type = AppEvent::kEventType_UpdateLedState; + event.Handler = UpdateLedStateEventHandler; + event.UpdateLedStateEvent.LedWidget = ledWidget; + sAppTask.PostEvent(&event); +} + +void AppTask::UpdateStatusLED(void) +{ + if (sIsThreadProvisioned && sIsThreadEnabled) + { + if (sIsThreadAttached) + { + sStatusLED.Blink(950, 50); + } + else + { + sStatusLED.Blink(100, 100); + } + } + else + { + sStatusLED.Blink(50, 950); + } +} +#endif + +void AppTask::ChipEventHandler(const ChipDeviceEvent * event, intptr_t /* arg */) +{ + switch (event->Type) + { + case DeviceEventType::kCHIPoBLEAdvertisingChange: + sHaveBLEConnections = ConnectivityMgr().NumBLEConnections() != 0; +#if CONFIG_CHIP_ENABLE_APPLICATION_STATUS_LED + UpdateStatusLED(); +#endif + break; + case DeviceEventType::kThreadStateChange: + sIsThreadProvisioned = ConnectivityMgr().IsThreadProvisioned(); + sIsThreadEnabled = ConnectivityMgr().IsThreadEnabled(); + sIsThreadAttached = ConnectivityMgr().IsThreadAttached(); +#if CONFIG_CHIP_ENABLE_APPLICATION_STATUS_LED + UpdateStatusLED(); +#endif + break; + case DeviceEventType::kThreadConnectivityChange: +#if CONFIG_CHIP_OTA_REQUESTOR + if (event->ThreadConnectivityChange.Result == kConnectivity_Established) + { + InitBasicOTARequestor(); + } +#endif + break; + default: + break; + } +} + +void AppTask::ActionIdentifyStateUpdateHandler(k_timer * timer) +{ + AppEvent event; + event.Type = AppEvent::kEventType_UpdateLedState; + event.Handler = UpdateIdentifyStateEventHandler; + sAppTask.PostEvent(&event); +} + +void AppTask::UpdateIdentifyStateEventHandler(AppEvent * aEvent) +{ + sAppTask.mPwmIdentifyLed.UpdateAction(); +} + +void AppTask::ActionInitiated(PWMDevice::Action_t aAction, int32_t aActor) +{ + if (aAction == PWMDevice::ON_ACTION) + { + LOG_DBG("ON_ACTION initiated"); + } + else if (aAction == PWMDevice::OFF_ACTION) + { + LOG_DBG("OFF_ACTION initiated"); + } + else if (aAction == PWMDevice::LEVEL_ACTION) + { + LOG_DBG("LEVEL_ACTION initiated"); + } +} + +void AppTask::ActionCompleted(PWMDevice::Action_t aAction, int32_t aActor) +{ + if (aAction == PWMDevice::ON_ACTION) + { + LOG_DBG("ON_ACTION completed"); + } + else if (aAction == PWMDevice::OFF_ACTION) + { + LOG_DBG("OFF_ACTION completed"); + } + else if (aAction == PWMDevice::LEVEL_ACTION) + { + LOG_DBG("LEVEL_ACTION completed"); + } +} + +void AppTask::PostEvent(AppEvent * aEvent) +{ + if (k_msgq_put(&sAppEventQueue, aEvent, K_NO_WAIT) != 0) + { + LOG_INF("PostEvent fail"); + } +} + +void AppTask::DispatchEvent(AppEvent * aEvent) +{ + if (aEvent->Handler) + { + aEvent->Handler(aEvent); + } + else + { + LOG_INF("Dropping event without handler"); + } +} + +void AppTask::FactoryResetTimerTimeoutCallback(k_timer * timer) +{ + if (!timer) + { + return; + } + + AppEvent event; + event.Type = AppEvent::kEventType_Timer; + event.Handler = FactoryResetTimerEventHandler; + sAppTask.PostEvent(&event); +} + +void AppTask::FactoryResetTimerEventHandler(AppEvent * aEvent) +{ + if (aEvent->Type != AppEvent::kEventType_Timer) + { + return; + } + + sFactoryResetCntr = 0; + LOG_INF("Factory Reset Trigger Counter is cleared"); +} + +void AppTask::InitButtons(void) +{ +#if CONFIG_CHIP_BUTTON_MANAGER_IRQ_MODE + sFactoryResetButton.Configure(BUTTON_PORT, BUTTON_PIN_1, FactoryResetButtonEventHandler); + sOpenButton.Configure(BUTTON_PORT, BUTTON_PIN_2, OpenActionAndToggleMoveTypeButtonEventHandler); + sCloseButton.Configure(BUTTON_PORT, BUTTON_PIN_3, CloseActionButtonEventHandler); + sBleAdvStartButton.Configure(BUTTON_PORT, BUTTON_PIN_4, StartBleAdvButtonEventHandler); +#else + sFactoryResetButton.Configure(BUTTON_PORT, BUTTON_PIN_3, BUTTON_PIN_1, FactoryResetButtonEventHandler); + sOpenButton.Configure(BUTTON_PORT, BUTTON_PIN_4, BUTTON_PIN_1, OpenActionAndToggleMoveTypeButtonEventHandler); + sCloseButton.Configure(BUTTON_PORT, BUTTON_PIN_3, BUTTON_PIN_2, CloseActionButtonEventHandler); + sBleAdvStartButton.Configure(BUTTON_PORT, BUTTON_PIN_4, BUTTON_PIN_2, StartBleAdvButtonEventHandler); +#endif + + ButtonManagerInst().AddButton(sFactoryResetButton); + ButtonManagerInst().AddButton(sOpenButton); + ButtonManagerInst().AddButton(sCloseButton); + ButtonManagerInst().AddButton(sBleAdvStartButton); +} diff --git a/examples/window-app/telink/src/WindowCovering.cpp b/examples/window-app/telink/src/WindowCovering.cpp new file mode 100644 index 00000000000000..77889418abef3b --- /dev/null +++ b/examples/window-app/telink/src/WindowCovering.cpp @@ -0,0 +1,315 @@ +/* + * Copyright (c) 2023 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "WindowCovering.h" +#include "AppConfig.h" +#include "PWMDevice.h" + +#include +#include +#include +#include +#include + +LOG_MODULE_DECLARE(app, CONFIG_CHIP_APP_LOG_LEVEL); + +using namespace ::chip::Credentials; +using namespace ::chip::DeviceLayer; +using namespace chip::app::Clusters::WindowCovering; + +static const struct pwm_dt_spec sLiftPwmDevice = LIGHTING_PWM_SPEC_RGB_BLUE; +static const struct pwm_dt_spec sTiltPwmDevice = LIGHTING_PWM_SPEC_RGB_GREEN; + +static constexpr uint32_t sMoveTimeoutMs{ 200 }; +constexpr uint16_t sPercentDelta = 500; +constexpr uint8_t kDefaultMinLevel = 0; +constexpr uint8_t kDefaultMaxLevel = 254; + +WindowCovering::WindowCovering() +{ + if (mLiftIndicator.Init(&sLiftPwmDevice, kDefaultMinLevel, kDefaultMaxLevel, kDefaultMinLevel) != CHIP_NO_ERROR) + { + LOG_ERR("Cannot initialize the lift indicator"); + } + if (mTiltIndicator.Init(&sTiltPwmDevice, kDefaultMinLevel, kDefaultMaxLevel, kDefaultMinLevel) != CHIP_NO_ERROR) + { + LOG_ERR("Cannot initialize the tilt indicator"); + } + mLiftIndicator.InitiateAction(PWMDevice::ON_ACTION, 0, nullptr); + mTiltIndicator.InitiateAction(PWMDevice::ON_ACTION, 0, nullptr); +} + +void WindowCovering::DriveCurrentLiftPosition(intptr_t) +{ + NPercent100ths current{}; + NPercent100ths target{}; + NPercent100ths positionToSet{}; + + VerifyOrReturn(Attributes::CurrentPositionLiftPercent100ths::Get(Endpoint(), current) == EMBER_ZCL_STATUS_SUCCESS); + VerifyOrReturn(Attributes::TargetPositionLiftPercent100ths::Get(Endpoint(), target) == EMBER_ZCL_STATUS_SUCCESS); + + UpdateOperationalStatus(WindowCoveringType::Lift, ComputeOperationalState(target, current)); + + positionToSet.SetNonNull(CalculateSingleStep(WindowCoveringType::Lift)); + LiftPositionSet(Endpoint(), positionToSet); + + // assume single move completed + Instance().mInLiftMove = false; + + VerifyOrReturn(Attributes::CurrentPositionLiftPercent100ths::Get(Endpoint(), current) == EMBER_ZCL_STATUS_SUCCESS); + + if (!TargetCompleted(WindowCoveringType::Lift, current, target)) + { + // continue to move + StartTimer(WindowCoveringType::Lift, sMoveTimeoutMs); + } + else + { + // the OperationalStatus should indicate no-lift-movement after the target is completed + UpdateOperationalStatus(WindowCoveringType::Lift, ComputeOperationalState(target, current)); + } +} + +chip::Percent100ths WindowCovering::CalculateSingleStep(WindowCoveringType aMoveType) +{ + EmberAfStatus status{}; + chip::Percent100ths percent100ths{}; + NPercent100ths current{}; + OperationalState opState = OperationalState::Stall; + + if (aMoveType == WindowCoveringType::Lift) + { + status = Attributes::CurrentPositionLiftPercent100ths::Get(Endpoint(), current); + opState = OperationalStateGet(Endpoint(), OperationalStatus::kLift); + } + else if (aMoveType == WindowCoveringType::Tilt) + { + status = Attributes::CurrentPositionTiltPercent100ths::Get(Endpoint(), current); + opState = OperationalStateGet(Endpoint(), OperationalStatus::kTilt); + } + + if ((status == EMBER_ZCL_STATUS_SUCCESS) && !current.IsNull()) + { + percent100ths = ComputePercent100thsStep(opState, current.Value(), sPercentDelta); + } + else + { + LOG_ERR("Cannot read the current lift position. Error: %d", static_cast(status)); + } + + return percent100ths; +} + +bool WindowCovering::TargetCompleted(WindowCoveringType aMoveType, NPercent100ths aCurrent, NPercent100ths aTarget) +{ + return (OperationalState::Stall == ComputeOperationalState(aTarget, aCurrent)); +} + +void WindowCovering::StartTimer(WindowCoveringType aMoveType, uint32_t aTimeoutMs) +{ + WindowCoveringType * moveType = chip::Platform::New(); + VerifyOrReturn(moveType != nullptr); + + *moveType = aMoveType; + (void) chip::DeviceLayer::SystemLayer().StartTimer(chip::System::Clock::Milliseconds32(aTimeoutMs), MoveTimerTimeoutCallback, + reinterpret_cast(moveType)); +} + +void WindowCovering::MoveTimerTimeoutCallback(chip::System::Layer * systemLayer, void * appState) +{ + WindowCoveringType * moveType = reinterpret_cast(appState); + VerifyOrReturn(moveType != nullptr); + + if (*moveType == WindowCoveringType::Lift) + { + chip::DeviceLayer::PlatformMgr().ScheduleWork(WindowCovering::DriveCurrentLiftPosition); + } + else if (*moveType == WindowCoveringType::Tilt) + { + chip::DeviceLayer::PlatformMgr().ScheduleWork(WindowCovering::DriveCurrentTiltPosition); + } + + chip::Platform::Delete(moveType); +} + +void WindowCovering::DriveCurrentTiltPosition(intptr_t) +{ + NPercent100ths current{}; + NPercent100ths target{}; + NPercent100ths positionToSet{}; + + VerifyOrReturn(Attributes::CurrentPositionTiltPercent100ths::Get(Endpoint(), current) == EMBER_ZCL_STATUS_SUCCESS); + VerifyOrReturn(Attributes::TargetPositionTiltPercent100ths::Get(Endpoint(), target) == EMBER_ZCL_STATUS_SUCCESS); + + UpdateOperationalStatus(WindowCoveringType::Lift, ComputeOperationalState(target, current)); + + positionToSet.SetNonNull(CalculateSingleStep(WindowCoveringType::Tilt)); + TiltPositionSet(Endpoint(), positionToSet); + + // assume single move completed + Instance().mInTiltMove = false; + + VerifyOrReturn(Attributes::CurrentPositionTiltPercent100ths::Get(Endpoint(), current) == EMBER_ZCL_STATUS_SUCCESS); + + if (!TargetCompleted(WindowCoveringType::Tilt, current, target)) + { + // continue to move + StartTimer(WindowCoveringType::Tilt, sMoveTimeoutMs); + } + else + { + // the OperationalStatus should indicate no-tilt-movement after the target is completed + UpdateOperationalStatus(WindowCoveringType::Tilt, ComputeOperationalState(target, current)); + } +} + +void WindowCovering::StartMove(WindowCoveringType aMoveType) +{ + switch (aMoveType) + { + case WindowCoveringType::Lift: + if (!mInLiftMove) + { + mInLiftMove = true; + StartTimer(aMoveType, sMoveTimeoutMs); + } + break; + case WindowCoveringType::Tilt: + if (!mInTiltMove) + { + mInTiltMove = true; + StartTimer(aMoveType, sMoveTimeoutMs); + } + break; + default: + break; + }; +} + +void WindowCovering::SetSingleStepTarget(OperationalState aDirection) +{ + UpdateOperationalStatus(mCurrentUIMoveType, aDirection); + SetTargetPosition(aDirection, CalculateSingleStep(mCurrentUIMoveType)); +} + +void WindowCovering::UpdateOperationalStatus(WindowCoveringType aMoveType, OperationalState aDirection) +{ + switch (aMoveType) + { + case WindowCoveringType::Lift: + OperationalStateSet(Endpoint(), OperationalStatus::kLift, aDirection); + break; + case WindowCoveringType::Tilt: + OperationalStateSet(Endpoint(), OperationalStatus::kTilt, aDirection); + break; + case WindowCoveringType::Reserved: + break; + default: + break; + } +} + +void WindowCovering::SetTargetPosition(OperationalState aDirection, chip::Percent100ths aPosition) +{ + EmberAfStatus status{}; + if (Instance().mCurrentUIMoveType == WindowCoveringType::Lift) + { + status = Attributes::TargetPositionLiftPercent100ths::Set(Endpoint(), aPosition); + } + else if (Instance().mCurrentUIMoveType == WindowCoveringType::Tilt) + { + status = Attributes::TargetPositionTiltPercent100ths::Set(Endpoint(), aPosition); + } + + if (status != EMBER_ZCL_STATUS_SUCCESS) + { + LOG_ERR("Cannot set the target position. Error: %d", static_cast(status)); + } +} + +void WindowCovering::PositionLEDUpdate(WindowCoveringType aMoveType) +{ + EmberAfStatus status{}; + NPercent100ths currentPosition{}; + + if (aMoveType == WindowCoveringType::Lift) + { + status = Attributes::CurrentPositionLiftPercent100ths::Get(Endpoint(), currentPosition); + if (EMBER_ZCL_STATUS_SUCCESS == status && !currentPosition.IsNull()) + { + Instance().SetBrightness(WindowCoveringType::Lift, currentPosition.Value()); + } + } + else if (aMoveType == WindowCoveringType::Tilt) + { + status = Attributes::CurrentPositionTiltPercent100ths::Get(Endpoint(), currentPosition); + if (EMBER_ZCL_STATUS_SUCCESS == status && !currentPosition.IsNull()) + { + Instance().SetBrightness(WindowCoveringType::Tilt, currentPosition.Value()); + } + } +} + +void WindowCovering::SetBrightness(WindowCoveringType aMoveType, uint16_t aPosition) +{ + uint8_t brightness = PositionToBrightness(aPosition, aMoveType); + if (aMoveType == WindowCoveringType::Lift) + { + mLiftIndicator.InitiateAction(PWMDevice::LEVEL_ACTION, 0, &brightness); + } + else if (aMoveType == WindowCoveringType::Tilt) + { + mTiltIndicator.InitiateAction(PWMDevice::LEVEL_ACTION, 0, &brightness); + } +} + +uint8_t WindowCovering::PositionToBrightness(uint16_t aPosition, WindowCoveringType aMoveType) +{ + AbsoluteLimits pwmLimits{}; + + if (aMoveType == WindowCoveringType::Lift) + { + pwmLimits.open = mLiftIndicator.GetMinLevel(); + pwmLimits.closed = mLiftIndicator.GetMaxLevel(); + } + else if (aMoveType == WindowCoveringType::Tilt) + { + pwmLimits.open = mTiltIndicator.GetMinLevel(); + pwmLimits.closed = mTiltIndicator.GetMaxLevel(); + } + + return Percent100thsToValue(pwmLimits, aPosition); +} + +void WindowCovering::SchedulePostAttributeChange(chip::EndpointId aEndpoint, chip::AttributeId aAttributeId) +{ + AttributeUpdateData * data = chip::Platform::New(); + VerifyOrReturn(data != nullptr); + + data->mEndpoint = aEndpoint; + data->mAttributeId = aAttributeId; + + chip::DeviceLayer::PlatformMgr().ScheduleWork(DoPostAttributeChange, reinterpret_cast(data)); +} + +void WindowCovering::DoPostAttributeChange(intptr_t aArg) +{ + AttributeUpdateData * data = reinterpret_cast(aArg); + VerifyOrReturn(data != nullptr); + + PostAttributeChange(data->mEndpoint, data->mAttributeId); +} diff --git a/examples/window-app/telink/src/ZclCallbacks.cpp b/examples/window-app/telink/src/ZclCallbacks.cpp new file mode 100644 index 00000000000000..844e58e317dc20 --- /dev/null +++ b/examples/window-app/telink/src/ZclCallbacks.cpp @@ -0,0 +1,78 @@ +/* + * + * Copyright (c) 2023 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "AppTask.h" +#include "PWMDevice.h" +#include "WindowCovering.h" +#include + +#include +#include +#include +#include +#include +#include + +LOG_MODULE_DECLARE(app, CONFIG_CHIP_APP_LOG_LEVEL); + +using namespace ::chip; +using namespace ::chip::app::Clusters::WindowCovering; + +void MatterPostAttributeChangeCallback(const app::ConcreteAttributePath & attributePath, uint8_t mask, uint8_t type, uint16_t size, + uint8_t * value) +{ + switch (attributePath.mClusterId) + { + case app::Clusters::Identify::Id: + ChipLogProgress(Zcl, "Identify cluster ID: " ChipLogFormatMEI " Type: %u Value: %u, length: %u", + ChipLogValueMEI(attributePath.mAttributeId), type, *value, size); + break; + case app::Clusters::WindowCovering::Id: + ChipLogProgress(Zcl, "Window covering cluster ID: " ChipLogFormatMEI " Type: %u Value: %u, length: %u", + ChipLogValueMEI(attributePath.mAttributeId), type, *value, size); + break; + default: + break; + } +} + +/* Forwards all attributes changes */ +void MatterWindowCoveringClusterServerAttributeChangedCallback(const app::ConcreteAttributePath & attributePath) +{ + if (attributePath.mEndpointId == WindowCovering::Endpoint()) + { + switch (attributePath.mAttributeId) + { + case Attributes::TargetPositionLiftPercent100ths::Id: + WindowCovering::Instance().StartMove(WindowCoveringType::Lift); + break; + case Attributes::TargetPositionTiltPercent100ths::Id: + WindowCovering::Instance().StartMove(WindowCoveringType::Tilt); + break; + case Attributes::CurrentPositionLiftPercent100ths::Id: + WindowCovering::Instance().PositionLEDUpdate(WindowCoveringType::Lift); + break; + case Attributes::CurrentPositionTiltPercent100ths::Id: + WindowCovering::Instance().PositionLEDUpdate(WindowCoveringType::Tilt); + break; + default: + WindowCovering::Instance().SchedulePostAttributeChange(attributePath.mEndpointId, attributePath.mAttributeId); + break; + }; + } +} diff --git a/examples/window-app/telink/src/main.cpp b/examples/window-app/telink/src/main.cpp new file mode 100644 index 00000000000000..1d423f0a516457 --- /dev/null +++ b/examples/window-app/telink/src/main.cpp @@ -0,0 +1,90 @@ +/* + * + * Copyright (c) 2023 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "AppTask.h" + +#include +#include + +#include + +#ifdef CONFIG_CHIP_PW_RPC +#include "Rpc.h" +#endif + +LOG_MODULE_REGISTER(app, CONFIG_CHIP_APP_LOG_LEVEL); + +using namespace ::chip; +using namespace ::chip::Inet; +using namespace ::chip::DeviceLayer; + +int main(void) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + +#ifdef CONFIG_CHIP_PW_RPC + rpc::Init(); +#endif + + err = chip::Platform::MemoryInit(); + if (err != CHIP_NO_ERROR) + { + LOG_ERR("MemoryInit fail"); + goto exit; + } + + err = PlatformMgr().InitChipStack(); + if (err != CHIP_NO_ERROR) + { + LOG_ERR("InitChipStack fail"); + goto exit; + } + + err = PlatformMgr().StartEventLoopTask(); + if (err != CHIP_NO_ERROR) + { + LOG_ERR("StartEventLoopTask fail"); + goto exit; + } + + err = ThreadStackMgr().InitThreadStack(); + if (err != CHIP_NO_ERROR) + { + LOG_ERR("InitThreadStack fail"); + goto exit; + } + +#ifdef CONFIG_OPENTHREAD_MTD_SED + err = ConnectivityMgr().SetThreadDeviceType(ConnectivityManager::kThreadDeviceType_SleepyEndDevice); +#elif CONFIG_OPENTHREAD_MTD + err = ConnectivityMgr().SetThreadDeviceType(ConnectivityManager::kThreadDeviceType_MinimalEndDevice); +#else + err = ConnectivityMgr().SetThreadDeviceType(ConnectivityManager::kThreadDeviceType_Router); +#endif + if (err != CHIP_NO_ERROR) + { + LOG_ERR("SetThreadDeviceType fail"); + goto exit; + } + + err = GetAppTask().StartApp(); + +exit: + LOG_ERR("Exit err %" CHIP_ERROR_FORMAT, err.Format()); + return (err == CHIP_NO_ERROR) ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/examples/window-app/telink/third_party/connectedhomeip b/examples/window-app/telink/third_party/connectedhomeip new file mode 120000 index 00000000000000..c866b86874994d --- /dev/null +++ b/examples/window-app/telink/third_party/connectedhomeip @@ -0,0 +1 @@ +../../../.. \ No newline at end of file diff --git a/scripts/build/build/targets.py b/scripts/build/build/targets.py index b7edc6d13dcc68..4b9fe6440cd121 100755 --- a/scripts/build/build/targets.py +++ b/scripts/build/build/targets.py @@ -554,6 +554,7 @@ def BuildTelinkTarget(): TargetPart('pump-controller', app=TelinkApp.PUMP_CONTROLLER), TargetPart('temperature-measurement', app=TelinkApp.TEMPERATURE_MEASUREMENT), TargetPart('thermostat', app=TelinkApp.THERMOSTAT), + TargetPart('window-covering', app=TelinkApp.WINDOW_COVERING), ]) target.AppendModifier('rpc', enable_rpcs=True) diff --git a/scripts/build/builders/telink.py b/scripts/build/builders/telink.py index 348233d331baf9..afaff3cefa0650 100644 --- a/scripts/build/builders/telink.py +++ b/scripts/build/builders/telink.py @@ -32,6 +32,7 @@ class TelinkApp(Enum): PUMP_CONTROLLER = auto() TEMPERATURE_MEASUREMENT = auto() THERMOSTAT = auto() + WINDOW_COVERING = auto() def ExampleName(self): if self == TelinkApp.ALL_CLUSTERS: @@ -56,6 +57,8 @@ def ExampleName(self): return 'temperature-measurement-app' elif self == TelinkApp.THERMOSTAT: return 'thermostat' + elif self == TelinkApp.WINDOW_COVERING: + return 'window-app' else: raise Exception('Unknown app type: %r' % self) @@ -82,6 +85,8 @@ def AppNamePrefix(self): return 'chip-telink-temperature-measurement-example' elif self == TelinkApp.THERMOSTAT: return 'chip-telink-thermostat-example' + elif self == TelinkApp.WINDOW_COVERING: + return 'chip-telink-window-example' else: raise Exception('Unknown app type: %r' % self) diff --git a/scripts/build/testdata/all_targets_linux_x64.txt b/scripts/build/testdata/all_targets_linux_x64.txt index f22e7d1820d549..01376508a38f83 100644 --- a/scripts/build/testdata/all_targets_linux_x64.txt +++ b/scripts/build/testdata/all_targets_linux_x64.txt @@ -19,5 +19,5 @@ nrf-{nrf5340dk,nrf52840dk,nrf52840dongle}-{all-clusters,all-clusters-minimal,loc nrf-native-posix-64-tests qpg-qpg6105-{lock,light,shell,persistent-storage} tizen-arm-{all-clusters,all-clusters-minimal,chip-tool,light,tests}[-no-ble][-no-wifi][-asan][-ubsan] -telink-tlsr9518adk80d-{all-clusters,all-clusters-minimal,contact-sensor,light,light-switch,lock,ota-requestor,pump,pump-controller,temperature-measurement,thermostat}[-rpc] +telink-tlsr9518adk80d-{all-clusters,all-clusters-minimal,contact-sensor,light,light-switch,lock,ota-requestor,pump,pump-controller,temperature-measurement,thermostat,window-covering}[-rpc] openiotsdk-{shell,lock}