diff --git a/.github/workflows/examples-telink.yaml b/.github/workflows/examples-telink.yaml index 547e6bf7bd8ad0..2ebd9923551732 100644 --- a/.github/workflows/examples-telink.yaml +++ b/.github/workflows/examples-telink.yaml @@ -94,6 +94,18 @@ jobs: - name: clean out build output run: rm -rf ./out + - name: Build example Telink Bridge App + run: | + ./scripts/run_in_build_env.sh \ + "./scripts/build/build_examples.py --target 'telink-tlsr9518adk80d-bridge' build" + .environment/pigweed-venv/bin/python3 scripts/tools/memory/gh_sizes.py \ + telink tlsr9518adk80d bridge-app \ + out/telink-tlsr9518adk80d-bridge/zephyr/zephyr.elf \ + /tmp/bloat_reports/ + + - name: clean out build output + run: rm -rf ./out + - name: Build example Telink Contact Sensor App run: | ./scripts/run_in_build_env.sh \ diff --git a/.vscode/tasks.json b/.vscode/tasks.json index f73966cddf7c22..2e495a9536fd2c 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -638,6 +638,7 @@ "qpg-qpg6100-lock", "telink-tlsr9518adk80d-all-clusters", "telink-tlsr9518adk80d-all-clusters-minimal", + "telink-tlsr9518adk80d-bridge", "telink-tlsr9518adk80d-contact-sensor", "telink-tlsr9518adk80d-light", "telink-tlsr9518adk80d-light-switch", diff --git a/examples/bridge-app/telink/.gitignore b/examples/bridge-app/telink/.gitignore new file mode 100644 index 00000000000000..84c048a73cc2e5 --- /dev/null +++ b/examples/bridge-app/telink/.gitignore @@ -0,0 +1 @@ +/build/ diff --git a/examples/bridge-app/telink/CMakeLists.txt b/examples/bridge-app/telink/CMakeLists.txt new file mode 100644 index 00000000000000..52e498d10927fe --- /dev/null +++ b/examples/bridge-app/telink/CMakeLists.txt @@ -0,0 +1,88 @@ +# +# 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) + +if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/boards/${BOARD}.overlay") + set(LOCAL_DTC_OVERLAY_FILE "${CMAKE_CURRENT_SOURCE_DIR}/boards/${BOARD}.overlay") +else() + unset(LOCAL_DTC_OVERLAY_FILE) +endif() + +if(EXISTS "${CHIP_ROOT}/src/platform/telink/${BOARD}.overlay") + set(GLOBAL_DTC_OVERLAY_FILE "${CHIP_ROOT}/src/platform/telink/${BOARD}.overlay") +else() + unset(GLOBAL_DTC_OVERLAY_FILE) +endif() + +if(DTC_OVERLAY_FILE) + set(DTC_OVERLAY_FILE + "${DTC_OVERLAY_FILE} ${GLOBAL_DTC_OVERLAY_FILE} ${LOCAL_DTC_OVERLAY_FILE}" + CACHE STRING "" FORCE + ) +else() + set(DTC_OVERLAY_FILE ${GLOBAL_DTC_OVERLAY_FILE} ${LOCAL_DTC_OVERLAY_FILE}) +endif() + +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-bridge-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}/bridge-app + ${TELINK_COMMON}/util/include + ${TELINK_COMMON}/app/include) + +add_definitions( + "-DCHIP_ADDRESS_RESOLVE_IMPL_INCLUDE_HEADER=" + -DCHIP_DEVICE_CONFIG_DYNAMIC_ENDPOINT_COUNT=16 +) + +target_sources(app PRIVATE + src/AppTask.cpp + src/main.cpp + src/ZclCallbacks.cpp + src/Device.cpp + src/DeviceCallbacks.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}/../bridge-common/bridge-app.zap +) + +if(CONFIG_CHIP_OTA_REQUESTOR) + target_sources(app PRIVATE ${TELINK_COMMON}/util/src/OTAUtil.cpp) +endif() diff --git a/examples/bridge-app/telink/README.md b/examples/bridge-app/telink/README.md new file mode 100644 index 00000000000000..67ded6c90e751f --- /dev/null +++ b/examples/bridge-app/telink/README.md @@ -0,0 +1,321 @@ +# Matter Telink Bridge Example Application + +The Telink Bridge Example demonstrates a simple lighting bridge and the use of +dynamic endpoints. It uses buttons to test changing the lighting 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. + +Bridge together with its Bridged Devices is exposed as a single Node with a list +of endpoints. Consequently, a single Node ID and a single Operational +Certificate is assigned during Commissioning and a single pass through the +commissioning flow is required to bring the Bridge (along with its Bridged +Devices) onto a Fabric. This provides for a simple user experience, since the +user only needs to go through the commissioning flow for the Bridge, and not +separately for each of the Bridged Devices. + +![Telink B91 EVK](http://wiki.telink-semi.cn/wiki/assets/Hardware/B91_Generic_Starter_Kit_Hardware_Guide/connection_chart.png) + +## Introduction + +A prototype application that demonstrates dynamic endpoint with device +commissioning and cluster control. It adds the non-chip device as endpoints on a +bridge(Matter device). In this example four light devices supporting on-off +cluster and temperature sensor have been added as endpoints + +1. Light1 at endpoint 3 +2. Light2 at endpoint 7 +3. Light3 at endpoint 5 +4. Light4 at endpoint 6 +5. Temperature Sensor at endpoint 8 + +## Dynamic Endpoints + +The Bridge Example makes use of Dynamic Endpoints. Current SDK support is +limited for dynamic endpoints, since endpoints are typically defined (along with +the clusters and attributes they contain) in a .zap file which then generates +code and static structures to define the endpoints. + +To support endpoints that are not statically defined, the ZCL attribute storage +mechanisms will hold additional endpoint information for `NUM_DYNAMIC_ENDPOINTS` +additional endpoints. These additional endpoint structures must be defined by +the application and can change at runtime. + +To facilitate the creation of these endpoint structures, several macros are +defined: + +`DECLARE_DYNAMIC_ATTRIBUTE_LIST_BEGIN(attrListName)` +`DECLARE_DYNAMIC_ATTRIBUTE(attId, attType, attSizeBytes, attrMask)` +`DECLARE_DYNAMIC_ATTRIBUTE_LIST_END(clusterRevision)` + +- These three macros are used to declare a list of attributes for use within a + cluster. The declaration must begin with the + `DECLARE_DYNAMIC_ATTRIBUTE_LIST_BEGIN` macro which will define the name of + the allocated attribute structure. Each attribute is then added by the + `DECLARE_DYNAMIC_ATTRIBUTE` macro. Finally, + `DECLARE_DYNAMIC_ATTRIBUTE_LIST_END` macro should be used to close the + definition. + +- All attributes defined with these macros will be configured as + `ATTRIBUTE_MASK_EXTERNAL_STORAGE` in the ZCL database and therefore will + rely on the application to maintain storage for the attribute. Consequently, + reads or writes to these attributes must be handled within the application + by the `emberAfExternalAttributeWriteCallback` and + `emberAfExternalAttributeReadCallback` functions. See the bridge + application's `main.cpp` for an example of this implementation. + +`DECLARE_DYNAMIC_CLUSTER_LIST_BEGIN(clusterListName)` +`DECLARE_DYNAMIC_CLUSTER(clusterId, clusterAttrs, incomingCommands, outgoingCommands)` +`DECLARE_DYNAMIC_CLUSTER_LIST_END` + +- These three macros are used to declare a list of clusters for use within a + endpoint. The declaration must begin with the + `DECLARE_DYNAMIC_CLUSTER_LIST_BEGIN` macro which will define the name of the + allocated cluster structure. Each cluster is then added by the + `DECLARE_DYNAMIC_CLUSTER` macro referencing attribute list previously + defined by the `DECLARE_DYNAMIC_ATTRIBUTE...` macros and the lists of + incoming/outgoing commands terminated by kInvalidCommandId (or nullptr if + there aren't any commands in the list). Finally, + `DECLARE_DYNAMIC_CLUSTER_LIST_END` macro should be used to close the + definition. + +`DECLARE_DYNAMIC_ENDPOINT(endpointName, clusterList)` + +- This macro is used to declare an endpoint and its associated cluster list, + which must be previously defined by the `DECLARE_DYNAMIC_CLUSTER...` macros. + +## 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 | Perform factory reset to forget currently commissioned Thread network and back to uncommissioned state | +| Button 2 | Lighting control | Manually triggers the lighting state | +| Button 3 | Thread start | Commission thread with static credentials and enables the Thread on device | +| Button 4 | Open commission window | The button is opening commissioning window to perform commissioning over BLE | + +### LEDs + +#### 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 2 + ``` + + here: + + - **onoff** is name of cluster + - **on** command to the cluster + - **1** ID of Node + - **2** ID of endpoint + +4. Switch off the light: + + ``` + ${CHIP_TOOL_DIR}/chip-tool onoff off 1 2 + ``` + + here: + + - **onoff** is name of cluster + - **off** command to the cluster + - **1** ID of Node + - **2** ID of endpoint + +5. Read the light state: + + ``` + ${CHIP_TOOL_DIR}/chip-tool onoff read on-off 1 2 + ``` + + here: + + - **onoff** is name of cluster + - **read** command to the cluster + - **on-off** attribute to read + - **1** ID of Node + - **2** ID of endpoint + +6. Change brightness of light: + + ``` + ${CHIP_TOOL_DIR}/chip-tool levelcontrol move-to-level 32 0 0 0 1 2 + ``` + + 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 Node + - **2** ID of endpoint + +7. Read brightness level: + ``` + ./chip-tool levelcontrol read current-level 1 2 + ``` + here: + - **levelcontrol** is name of cluster + - **read** command to the cluster + - **current-level** attribute to read + - **1** ID of Node + - **2** 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. diff --git a/examples/bridge-app/telink/include/AppConfig.h b/examples/bridge-app/telink/include/AppConfig.h new file mode 100644 index 00000000000000..88918d553e078d --- /dev/null +++ b/examples/bridge-app/telink/include/AppConfig.h @@ -0,0 +1,39 @@ +/* + * + * 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 + +// ---- Bridge 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 + +// Lighting LED config +#define USE_RGB_PWM 0 + +#define LIGHTING_PWM_SPEC_RGB_BLUE PWM_DT_SPEC_GET(DT_ALIAS(pwm_led0)) +#define LIGHTING_PWM_SPEC_IDENTIFY_GREEN PWM_DT_SPEC_GET(DT_ALIAS(pwm_led3)) diff --git a/examples/bridge-app/telink/include/AppEvent.h b/examples/bridge-app/telink/include/AppEvent.h new file mode 100644 index 00000000000000..f570dfd1e7cdf7 --- /dev/null +++ b/examples/bridge-app/telink/include/AppEvent.h @@ -0,0 +1,64 @@ +/* + * + * 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, + kEventType_Lighting, + }; + + uint16_t Type; + + union + { + struct + { + uint8_t Action; + } ButtonEvent; + struct + { + void * Context; + } TimerEvent; + struct + { + uint8_t Action; + int32_t Actor; + } LightingEvent; + struct + { + LEDWidget * LedWidget; + } UpdateLedStateEvent; + }; + + EventHandler Handler; +}; diff --git a/examples/bridge-app/telink/include/AppTask.h b/examples/bridge-app/telink/include/AppTask.h new file mode 100644 index 00000000000000..35e9977ebfa0ac --- /dev/null +++ b/examples/bridge-app/telink/include/AppTask.h @@ -0,0 +1,97 @@ +/* + * + * 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" +#if CONFIG_CHIP_ENABLE_APPLICATION_STATUS_LED +#include "LEDWidget.h" +#endif +#include "PWMDevice.h" +#include + +#if CONFIG_CHIP_FACTORY_DATA +#include +#endif + +#include + +struct k_timer; +struct Identify; + +class AppTask +{ +public: + CHIP_ERROR StartApp(void); + + void PostEvent(AppEvent * aEvent); + void UpdateClusterState(void); + PWMDevice & GetPWMDevice(void) { return mPwmRgbBlueLed; } + + static void IdentifyEffectHandler(EmberAfIdentifyEffectIdentifier aEffect); + +private: + 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 FactoryResetButtonEventHandler(void); + static void StartThreadButtonEventHandler(void); + static void StartBleAdvButtonEventHandler(void); + + static void ChipEventHandler(const chip::DeviceLayer::ChipDeviceEvent * event, intptr_t arg); + + static void FactoryResetTimerTimeoutCallback(k_timer * timer); + + static void FactoryResetTimerEventHandler(AppEvent * aEvent); + static void FactoryResetHandler(AppEvent * aEvent); + static void StartThreadHandler(AppEvent * aEvent); + static void LightingActionEventHandler(AppEvent * aEvent); + static void StartBleAdvHandler(AppEvent * aEvent); + static void UpdateIdentifyStateEventHandler(AppEvent * aEvent); + + static void InitButtons(void); + static void InitServer(intptr_t context); + + static void ThreadProvisioningHandler(const chip::DeviceLayer::ChipDeviceEvent * event, intptr_t arg); + + static AppTask sAppTask; + PWMDevice mPwmRgbBlueLed; + PWMDevice mPwmIdentifyLed; + +#if CONFIG_CHIP_FACTORY_DATA + chip::DeviceLayer::FactoryDataProvider mFactoryDataProvider; +#endif +}; + +inline AppTask & GetAppTask(void) +{ + return AppTask::sAppTask; +} diff --git a/examples/bridge-app/telink/include/CHIPProjectConfig.h b/examples/bridge-app/telink/include/CHIPProjectConfig.h new file mode 100644 index 00000000000000..824acd90d46cbe --- /dev/null +++ b/examples/bridge-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/bridge-app/telink/include/Device.h b/examples/bridge-app/telink/include/Device.h new file mode 100644 index 00000000000000..bd91dcb9f346c0 --- /dev/null +++ b/examples/bridge-app/telink/include/Device.h @@ -0,0 +1,97 @@ +/* + * + * 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. + */ +#pragma once + +#include + +#include +#include + +#include + +class Device +{ +public: + static const int kDeviceNameSize = 32; + static const int kDeviceLocationSize = 32; + + enum State_t + { + kState_On = 0, + kState_Off, + } State; + + enum Changed_t + { + kChanged_Reachable = 0x01, + kChanged_State = 0x02, + kChanged_Location = 0x04, + kChanged_Name = 0x08, + kChanged_Last = kChanged_Name, + } Changed; + + Device(const char * szDeviceName, const char * szLocation); + + bool IsOn() const; + bool IsReachable() const; + void SetOnOff(bool aOn); + void SetReachable(bool aReachable); + void SetName(const char * szDeviceName); + void SetLocation(const char * szLocation); + inline void SetEndpointId(chip::EndpointId id) { mEndpointId = id; }; + inline chip::EndpointId GetEndpointId() { return mEndpointId; }; + inline char * GetName() { return mName; }; + inline char * GetLocation() { return mLocation; }; + + using DeviceCallback_fn = std::function; + void SetChangeCallback(DeviceCallback_fn aChanged_CB); + +private: + State_t mState; + bool mReachable; + char mName[kDeviceNameSize]; + char mLocation[kDeviceLocationSize]; + chip::EndpointId mEndpointId; + DeviceCallback_fn mChanged_CB; +}; + +class DeviceTempSensor : public Device +{ +public: + enum Changed_t + { + kChanged_MeasurementValue = kChanged_Last << 1, + } Changed; + + DeviceTempSensor(const char * szDeviceName, std::string szLocation, int16_t min, int16_t max, int16_t measuredValue); + + inline int16_t GetMeasuredValue() { return mMeasurement; }; + void SetMeasuredValue(int16_t measurement); + + using DeviceCallback_fn = std::function; + void SetChangeCallback(DeviceCallback_fn aChanged_CB); + + const int16_t mMin; + const int16_t mMax; + +private: + void HandleDeviceChange(Device * device, Device::Changed_t changeMask); + +private: + int16_t mMeasurement; + DeviceCallback_fn mChanged_CB; +}; diff --git a/examples/bridge-app/telink/prj.conf b/examples/bridge-app/telink/prj.conf new file mode 100644 index 00000000000000..2d69564ad49b85 --- /dev/null +++ b/examples/bridge-app/telink/prj.conf @@ -0,0 +1,79 @@ +# +# 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 +# 32769 == 0x8001 (example all-clusters-app), otherwise GoogleHome fails commissioning +CONFIG_CHIP_DEVICE_PRODUCT_ID=32769 +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 +CONFIG_CHIP_CERTIFICATION_DECLARATION_STORAGE=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_CHIP_OPENTHREAD_TX_POWER=9 diff --git a/examples/bridge-app/telink/src/AppTask.cpp b/examples/bridge-app/telink/src/AppTask.cpp new file mode 100644 index 00000000000000..8a9bcd127c7079 --- /dev/null +++ b/examples/bridge-app/telink/src/AppTask.cpp @@ -0,0 +1,1108 @@ +/* + * + * 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 "Device.h" +#include +#include +#include +#include +#include + +#include "ThreadUtil.h" + +#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 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 sPwmRgbSpecBlueLed = LIGHTING_PWM_SPEC_RGB_BLUE; + +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; +uint8_t sFactoryResetCntr = 0; + +#if CONFIG_CHIP_ENABLE_APPLICATION_STATUS_LED +LEDWidget sStatusLED; +#endif + +Button sFactoryResetButton; +Button sLightingButton; +Button sThreadStartButton; +Button sBleAdvStartButton; + +bool sIsThreadProvisioned = false; +bool sIsThreadEnabled = false; +bool sIsThreadAttached = false; +bool sHaveBLEConnections = 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; +#include + +int AddDeviceEndpoint(Device * dev, EmberAfEndpointType * ep, const Span & deviceTypeList, + const Span & dataVersionStorage, chip::EndpointId parentEndpointId); +CHIP_ERROR RemoveDeviceEndpoint(Device * dev); +void HandleDeviceTempSensorStatusChanged(DeviceTempSensor * dev, DeviceTempSensor::Changed_t itemChangedMask); +EmberAfStatus HandleReadTempMeasurementAttribute(DeviceTempSensor * dev, chip::AttributeId attributeId, uint8_t * buffer, + uint16_t maxReadLength); + +static const int kNodeLabelSize = 32; +// Current ZCL implementation of Struct uses a max-size array of 254 bytes +static const int kDescriptorAttributeArraySize = 254; + +static EndpointId gCurrentEndpointId; +static EndpointId gFirstDynamicEndpointId; + +static Device * gDevices[CHIP_DEVICE_CONFIG_DYNAMIC_ENDPOINT_COUNT]; // number of dynamic endpoints count + +const int16_t minMeasuredValue = -27315; +const int16_t maxMeasuredValue = 32766; +const int16_t initialMeasuredValue = 100; + +// 5 Bridged devices +static Device gLight1("Light 1", "Office"); +static Device gLight2("Light 2", "Office"); +static Device gLight3("Light 3", "Kitchen"); +static Device gLight4("Light 4", "Kitchen"); +static DeviceTempSensor TempSensor1("TempSensor 1", "Office", minMeasuredValue, maxMeasuredValue, initialMeasuredValue); + +// (taken from src/app/zap-templates/zcl/data-model/chip/matter-devices.xml) +#define DEVICE_TYPE_BRIDGED_NODE 0x0013 +// (taken from lo-devices.xml) +#define DEVICE_TYPE_LO_ON_OFF_LIGHT 0x0100 +#define DEVICE_TYPE_ROOT_NODE 0x0016 +#define DEVICE_TYPE_BRIDGE 0x000e +#define DEVICE_TYPE_TEMP_SENSOR 0x0302 +// Device Version for dynamic endpoints: +#define DEVICE_VERSION_DEFAULT 1 + +/* REVISION definitions: + */ + +#define ZCL_DESCRIPTOR_CLUSTER_REVISION (1u) +#define ZCL_BRIDGED_DEVICE_BASIC_INFORMATION_CLUSTER_REVISION (1u) +#define ZCL_FIXED_LABEL_CLUSTER_REVISION (1u) +#define ZCL_ON_OFF_CLUSTER_REVISION (4u) +#define ZCL_TEMPERATURE_SENSOR_CLUSTER_REVISION (4u) +#define ZCL_BRIDGED_DEVICE_BASIC_INFORMATION_FEATURE_MAP (0u) +#define ZCL_TEMPERATURE_SENSOR_FEATURE_MAP (0u) + +/* BRIDGED DEVICE ENDPOINT: contains the following clusters: + - On/Off + - Descriptor + - Bridged Device Basic Information +*/ + +// Declare On/Off cluster attributes +DECLARE_DYNAMIC_ATTRIBUTE_LIST_BEGIN(onOffAttrs) +DECLARE_DYNAMIC_ATTRIBUTE(Clusters::OnOff::Attributes::OnOff::Id, BOOLEAN, 1, 0), /* on/off */ + DECLARE_DYNAMIC_ATTRIBUTE(Clusters::OnOff::Attributes::ClusterRevision::Id, INT16U, ZCL_ON_OFF_CLUSTER_REVISION, 0), + DECLARE_DYNAMIC_ATTRIBUTE_LIST_END(); + +// Declare Descriptor cluster attributes +DECLARE_DYNAMIC_ATTRIBUTE_LIST_BEGIN(descriptorAttrs) +DECLARE_DYNAMIC_ATTRIBUTE(Clusters::Descriptor::Attributes::DeviceTypeList::Id, ARRAY, kDescriptorAttributeArraySize, + 0), /* device list */ + DECLARE_DYNAMIC_ATTRIBUTE(Clusters::Descriptor::Attributes::ServerList::Id, ARRAY, kDescriptorAttributeArraySize, + 0), /* server list */ + DECLARE_DYNAMIC_ATTRIBUTE(Clusters::Descriptor::Attributes::ClientList::Id, ARRAY, kDescriptorAttributeArraySize, + 0), /* client list */ + DECLARE_DYNAMIC_ATTRIBUTE(Clusters::Descriptor::Attributes::PartsList::Id, ARRAY, kDescriptorAttributeArraySize, + 0), /* parts list */ + DECLARE_DYNAMIC_ATTRIBUTE(Clusters::Descriptor::Attributes::ClusterRevision::Id, INT16U, ZCL_DESCRIPTOR_CLUSTER_REVISION, + 0), /* cluster revision */ + DECLARE_DYNAMIC_ATTRIBUTE_LIST_END(); + +// Declare Bridged Device Basic Information cluster attributes +DECLARE_DYNAMIC_ATTRIBUTE_LIST_BEGIN(bridgedDeviceBasicAttrs) +DECLARE_DYNAMIC_ATTRIBUTE(chip::app::Clusters::BridgedDeviceBasicInformation::Attributes::NodeLabel::Id, CHAR_STRING, + kNodeLabelSize, 0), /* NodeLabel */ + DECLARE_DYNAMIC_ATTRIBUTE(chip::app::Clusters::BridgedDeviceBasicInformation::Attributes::Reachable::Id, BOOLEAN, 1, + 0), /* Reachable */ + DECLARE_DYNAMIC_ATTRIBUTE(chip::app::Clusters::BridgedDeviceBasicInformation::Attributes::ClusterRevision::Id, INT16U, + ZCL_BRIDGED_DEVICE_BASIC_INFORMATION_CLUSTER_REVISION, 0), /* cluster revision */ + DECLARE_DYNAMIC_ATTRIBUTE_LIST_END(); + +// Declare Cluster List for Bridged Light endpoint +// TODO: It's not clear whether it would be better to get the command lists from +// the ZAP config on our last fixed endpoint instead. +constexpr CommandId onOffIncomingCommands[] = { + app::Clusters::OnOff::Commands::Off::Id, + app::Clusters::OnOff::Commands::On::Id, + app::Clusters::OnOff::Commands::Toggle::Id, + app::Clusters::OnOff::Commands::OffWithEffect::Id, + app::Clusters::OnOff::Commands::OnWithRecallGlobalScene::Id, + app::Clusters::OnOff::Commands::OnWithTimedOff::Id, + kInvalidCommandId, +}; + +DECLARE_DYNAMIC_CLUSTER_LIST_BEGIN(bridgedLightClusters) +DECLARE_DYNAMIC_CLUSTER(Clusters::OnOff::Id, onOffAttrs, onOffIncomingCommands, nullptr), + DECLARE_DYNAMIC_CLUSTER(Clusters::Descriptor::Id, descriptorAttrs, nullptr, nullptr), + DECLARE_DYNAMIC_CLUSTER(chip::app::Clusters::BridgedDeviceBasicInformation::Id, bridgedDeviceBasicAttrs, nullptr, + nullptr) DECLARE_DYNAMIC_CLUSTER_LIST_END; + +// ----------------------------Temperature sensor----------------------------------------------- +DECLARE_DYNAMIC_ATTRIBUTE_LIST_BEGIN(tempSensorAttrs) +DECLARE_DYNAMIC_ATTRIBUTE(Clusters::TemperatureMeasurement::Attributes::MeasuredValue::Id, INT16S, 2, 0), /* Measured Value */ + DECLARE_DYNAMIC_ATTRIBUTE(Clusters::TemperatureMeasurement::Attributes::MinMeasuredValue::Id, INT16S, 2, + 0), /* Min Measured Value */ + DECLARE_DYNAMIC_ATTRIBUTE(Clusters::TemperatureMeasurement::Attributes::MaxMeasuredValue::Id, INT16S, 2, + 0), /* Max Measured Value */ + DECLARE_DYNAMIC_ATTRIBUTE(Clusters::TemperatureMeasurement::Attributes::FeatureMap::Id, BITMAP32, 4, 0), /* FeatureMap */ + DECLARE_DYNAMIC_ATTRIBUTE(Clusters::TemperatureMeasurement::Attributes::ClusterRevision::Id, INT16U, + ZCL_TEMPERATURE_SENSOR_CLUSTER_REVISION, 0), /* cluster revision */ + DECLARE_DYNAMIC_ATTRIBUTE_LIST_END(); + +// TEMPERATURE SENSOR ENDPOINT: contains the following clusters: +// - Temperature measurement +// - Descriptor +// - Bridged Device Basic Information +DECLARE_DYNAMIC_CLUSTER_LIST_BEGIN(bridgedTempSensorClusters) +DECLARE_DYNAMIC_CLUSTER(Clusters::TemperatureMeasurement::Id, tempSensorAttrs, nullptr, nullptr), + DECLARE_DYNAMIC_CLUSTER(Clusters::Descriptor::Id, descriptorAttrs, nullptr, nullptr), + DECLARE_DYNAMIC_CLUSTER(Clusters::BridgedDeviceBasicInformation::Id, bridgedDeviceBasicAttrs, nullptr, nullptr), + DECLARE_DYNAMIC_CLUSTER_LIST_END; + +// Declare Bridged Light endpoint +DECLARE_DYNAMIC_ENDPOINT(bridgedTempSensorEndpoint, bridgedTempSensorClusters); +DataVersion gTempSensor1DataVersions[ArraySize(bridgedTempSensorClusters)]; + +// Declare Bridged Light endpoint +DECLARE_DYNAMIC_ENDPOINT(bridgedLightEndpoint, bridgedLightClusters); + +DataVersion gLight1DataVersions[ArraySize(bridgedLightClusters)]; +DataVersion gLight2DataVersions[ArraySize(bridgedLightClusters)]; +DataVersion gLight3DataVersions[ArraySize(bridgedLightClusters)]; +DataVersion gLight4DataVersions[ArraySize(bridgedLightClusters)]; +// DataVersion gThermostatDataVersions[ArraySize(thermostatAttrs)]; + +const EmberAfDeviceType gRootDeviceTypes[] = { { DEVICE_TYPE_ROOT_NODE, DEVICE_VERSION_DEFAULT } }; +const EmberAfDeviceType gAggregateNodeDeviceTypes[] = { { DEVICE_TYPE_BRIDGE, DEVICE_VERSION_DEFAULT } }; + +const EmberAfDeviceType gBridgedOnOffDeviceTypes[] = { { DEVICE_TYPE_LO_ON_OFF_LIGHT, DEVICE_VERSION_DEFAULT }, + { DEVICE_TYPE_BRIDGED_NODE, DEVICE_VERSION_DEFAULT } }; + +const EmberAfDeviceType gBridgedTempSensorDeviceTypes[] = { { DEVICE_TYPE_TEMP_SENSOR, DEVICE_VERSION_DEFAULT }, + { DEVICE_TYPE_BRIDGED_NODE, DEVICE_VERSION_DEFAULT } }; + +int AddDeviceEndpoint(Device * dev, EmberAfEndpointType * ep, const Span & deviceTypeList, + const Span & dataVersionStorage, chip::EndpointId parentEndpointId) +{ + uint8_t index = 0; + while (index < CHIP_DEVICE_CONFIG_DYNAMIC_ENDPOINT_COUNT) + { + if (NULL == gDevices[index]) + { + gDevices[index] = dev; + EmberAfStatus ret; + while (true) + { + dev->SetEndpointId(gCurrentEndpointId); + ret = + emberAfSetDynamicEndpoint(index, gCurrentEndpointId, ep, dataVersionStorage, deviceTypeList, parentEndpointId); + if (ret == EMBER_ZCL_STATUS_SUCCESS) + { + ChipLogProgress(DeviceLayer, "Added device %s to dynamic endpoint %d (index=%d)", dev->GetName(), + gCurrentEndpointId, index); + return index; + } + else if (ret != EMBER_ZCL_STATUS_DUPLICATE_EXISTS) + { + return -1; + } + // Handle wrap condition + if (++gCurrentEndpointId < gFirstDynamicEndpointId) + { + gCurrentEndpointId = gFirstDynamicEndpointId; + } + } + } + index++; + } + ChipLogProgress(DeviceLayer, "Failed to add dynamic endpoint: No endpoints available!"); + return -1; +} + +CHIP_ERROR RemoveDeviceEndpoint(Device * dev) +{ + for (uint8_t index = 0; index < CHIP_DEVICE_CONFIG_DYNAMIC_ENDPOINT_COUNT; index++) + { + if (gDevices[index] == dev) + { + EndpointId ep = emberAfClearDynamicEndpoint(index); + gDevices[index] = NULL; + ChipLogProgress(DeviceLayer, "Removed device %s from dynamic endpoint %d (index=%d)", dev->GetName(), ep, index); + // Silence complaints about unused ep when progress logging + // disabled. + UNUSED_VAR(ep); + return CHIP_NO_ERROR; + } + } + return CHIP_ERROR_INTERNAL; +} + +EmberAfStatus HandleReadBridgedDeviceBasicAttribute(Device * dev, chip::AttributeId attributeId, uint8_t * buffer, + uint16_t maxReadLength) +{ + using namespace chip::app::Clusters::BridgedDeviceBasicInformation::Attributes; + ChipLogProgress(DeviceLayer, "HandleReadBridgedDeviceBasicAttribute: attrId=%" PRIu32 ", maxReadLength=%u", attributeId, + maxReadLength); + + if ((attributeId == Reachable::Id) && (maxReadLength == 1)) + { + *buffer = dev->IsReachable() ? 1 : 0; + } + else if ((attributeId == NodeLabel::Id) && (maxReadLength == 32)) + { + MutableByteSpan zclNameSpan(buffer, maxReadLength); + MakeZclCharString(zclNameSpan, dev->GetName()); + } + else if ((attributeId == FeatureMap::Id) && (maxReadLength == 4)) + { + uint32_t featureMap = ZCL_BRIDGED_DEVICE_BASIC_INFORMATION_FEATURE_MAP; + memcpy(buffer, &featureMap, sizeof(featureMap)); + } + else if ((attributeId == ClusterRevision::Id) && (maxReadLength == 4)) + { + uint16_t clusterRevision = ZCL_BRIDGED_DEVICE_BASIC_INFORMATION_CLUSTER_REVISION; + memcpy(buffer, &clusterRevision, sizeof(clusterRevision)); + } + else + { + return EMBER_ZCL_STATUS_FAILURE; + } + + return EMBER_ZCL_STATUS_SUCCESS; +} + +EmberAfStatus HandleReadOnOffAttribute(Device * dev, chip::AttributeId attributeId, uint8_t * buffer, uint16_t maxReadLength) +{ + ChipLogProgress(DeviceLayer, "HandleReadOnOffAttribute: attrId=%" PRIu32 ", maxReadLength=%u", attributeId, maxReadLength); + + if ((attributeId == Clusters::OnOff::Attributes::OnOff::Id) && (maxReadLength == 1)) + { + *buffer = dev->IsOn() ? 1 : 0; + } + else if ((attributeId == Clusters::OnOff::Attributes::ClusterRevision::Id) && (maxReadLength == 4)) + { + uint16_t clusterRevision = ZCL_ON_OFF_CLUSTER_REVISION; + memcpy(buffer, &clusterRevision, sizeof(clusterRevision)); + } + else + { + return EMBER_ZCL_STATUS_FAILURE; + } + + return EMBER_ZCL_STATUS_SUCCESS; +} + +EmberAfStatus HandleWriteOnOffAttribute(Device * dev, chip::AttributeId attributeId, uint8_t * buffer) +{ + ChipLogProgress(DeviceLayer, "HandleWriteOnOffAttribute: attrId=%" PRIu32, attributeId); + + ReturnErrorCodeIf((attributeId != Clusters::OnOff::Attributes::OnOff::Id) || (!dev->IsReachable()), EMBER_ZCL_STATUS_FAILURE); + dev->SetOnOff(*buffer == 1); + return EMBER_ZCL_STATUS_SUCCESS; +} + +EmberAfStatus emberAfExternalAttributeReadCallback(EndpointId endpoint, ClusterId clusterId, + const EmberAfAttributeMetadata * attributeMetadata, uint8_t * buffer, + uint16_t maxReadLength) +{ + using namespace Clusters; + + uint16_t endpointIndex = emberAfGetDynamicIndexFromEndpoint(endpoint); + + if ((endpointIndex < CHIP_DEVICE_CONFIG_DYNAMIC_ENDPOINT_COUNT) && (gDevices[endpointIndex] != NULL)) + { + Device * dev = gDevices[endpointIndex]; + + if (clusterId == BridgedDeviceBasicInformation::Id) + { + return HandleReadBridgedDeviceBasicAttribute(dev, attributeMetadata->attributeId, buffer, maxReadLength); + } + else if (clusterId == OnOff::Id) + { + return HandleReadOnOffAttribute(dev, attributeMetadata->attributeId, buffer, maxReadLength); + } + else if (clusterId == TemperatureMeasurement::Id) + { + return HandleReadTempMeasurementAttribute(static_cast(dev), attributeMetadata->attributeId, buffer, + maxReadLength); + } + } + + return EMBER_ZCL_STATUS_FAILURE; +} + +EmberAfStatus emberAfExternalAttributeWriteCallback(EndpointId endpoint, ClusterId clusterId, + const EmberAfAttributeMetadata * attributeMetadata, uint8_t * buffer) +{ + uint16_t endpointIndex = emberAfGetDynamicIndexFromEndpoint(endpoint); + + if (endpointIndex < CHIP_DEVICE_CONFIG_DYNAMIC_ENDPOINT_COUNT) + { + Device * dev = gDevices[endpointIndex]; + + if ((dev->IsReachable()) && (clusterId == Clusters::OnOff::Id)) + { + return HandleWriteOnOffAttribute(dev, attributeMetadata->attributeId, buffer); + } + } + + return EMBER_ZCL_STATUS_FAILURE; +} + +namespace { +void CallReportingCallback(intptr_t closure) +{ + auto path = reinterpret_cast(closure); + MatterReportingAttributeChangeCallback(*path); + Platform::Delete(path); +} + +void ScheduleReportingCallback(Device * dev, ClusterId cluster, AttributeId attribute) +{ + auto * path = Platform::New(dev->GetEndpointId(), cluster, attribute); + DeviceLayer::PlatformMgr().ScheduleWork(CallReportingCallback, reinterpret_cast(path)); +} +} // anonymous namespace + +void HandleDeviceStatusChanged(Device * dev, Device::Changed_t itemChangedMask) +{ + using namespace chip::app::Clusters; + if (itemChangedMask & Device::kChanged_Reachable) + { + ScheduleReportingCallback(dev, BridgedDeviceBasicInformation::Id, BridgedDeviceBasicInformation::Attributes::Reachable::Id); + } + + if (itemChangedMask & Device::kChanged_State) + { + ScheduleReportingCallback(dev, OnOff::Id, OnOff::Attributes::OnOff::Id); + } + + if (itemChangedMask & Device::kChanged_Name) + { + ScheduleReportingCallback(dev, BridgedDeviceBasicInformation::Id, BridgedDeviceBasicInformation::Attributes::NodeLabel::Id); + } +} + +bool emberAfActionsClusterInstantActionCallback(app::CommandHandler * commandObj, const app::ConcreteCommandPath & commandPath, + const Clusters::Actions::Commands::InstantAction::DecodableType & commandData) +{ + // No actions are implemented, just return status NotFound. + commandObj->AddStatus(commandPath, Protocols::InteractionModel::Status::NotFound); + return true; +} + +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); + + UpdateStatusLED(); +#endif + + InitButtons(); + + // Initialize function button timer + k_timer_init(&sFactoryResetTimer, &AppTask::FactoryResetTimerTimeoutCallback, nullptr); + k_timer_user_data_set(&sFactoryResetTimer, this); + + // Init lighting manager + uint8_t minLightLevel = kDefaultMinLevel; + Clusters::LevelControl::Attributes::MinLevel::Get(kLightEndpointId, &minLightLevel); + + uint8_t maxLightLevel = kDefaultMaxLevel; + Clusters::LevelControl::Attributes::MaxLevel::Get(kLightEndpointId, &maxLightLevel); + + // 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; + } + + err = sAppTask.mPwmRgbBlueLed.Init(&sPwmRgbSpecBlueLed, minLightLevel, maxLightLevel, maxLightLevel); + if (err != CHIP_NO_ERROR) + { + LOG_ERR("Blue RGB PWM Device Init fail"); + return err; + } + + sAppTask.mPwmRgbBlueLed.SetCallbacks(ActionInitiated, ActionCompleted, nullptr); + 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("TelinkLight"); + 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; + } + + memset(gDevices, 0, sizeof(gDevices)); + + gLight1.SetReachable(true); + gLight2.SetReachable(true); + gLight3.SetReachable(true); + gLight4.SetReachable(true); + TempSensor1.SetReachable(true); + + // Whenever bridged device changes its state + gLight1.SetChangeCallback(&HandleDeviceStatusChanged); + gLight2.SetChangeCallback(&HandleDeviceStatusChanged); + gLight3.SetChangeCallback(&HandleDeviceStatusChanged); + gLight4.SetChangeCallback(&HandleDeviceStatusChanged); + TempSensor1.SetChangeCallback(&HandleDeviceTempSensorStatusChanged); + + PlatformMgr().ScheduleWork(InitServer, reinterpret_cast(nullptr)); + + 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); + } +} + +static void AppTask::InitServer(intptr_t context) +{ + + // Set starting endpoint id where dynamic endpoints will be assigned, which + // will be the next consecutive endpoint id after the last fixed endpoint. + gFirstDynamicEndpointId = static_cast( + static_cast(emberAfEndpointFromIndex(static_cast(emberAfFixedEndpointCount() - 1))) + 1); + gCurrentEndpointId = gFirstDynamicEndpointId; + + // Disable last fixed endpoint, which is used as a placeholder for all of the + // supported clusters so that ZAP will generate the requisite code. + emberAfEndpointEnableDisable(emberAfEndpointFromIndex(static_cast(emberAfFixedEndpointCount() - 1)), false); + + // A bridge has root node device type on EP0 and aggregate node device type (bridge) at EP1 + emberAfSetDeviceTypeList(0, Span(gRootDeviceTypes)); + emberAfSetDeviceTypeList(1, Span(gAggregateNodeDeviceTypes)); + + // Add lights 1..3 --> will be mapped to ZCL endpoints 3, 4, 5 + AddDeviceEndpoint(&gLight1, &bridgedLightEndpoint, Span(gBridgedOnOffDeviceTypes), + Span(gLight1DataVersions), 1); + AddDeviceEndpoint(&gLight2, &bridgedLightEndpoint, Span(gBridgedOnOffDeviceTypes), + Span(gLight2DataVersions), 1); + AddDeviceEndpoint(&gLight3, &bridgedLightEndpoint, Span(gBridgedOnOffDeviceTypes), + Span(gLight3DataVersions), 1); + + // Remove Light 2 -- Lights 1 & 3 will remain mapped to endpoints 3 & 5 + RemoveDeviceEndpoint(&gLight2); + + // Add Light 4 -- > will be mapped to ZCL endpoint 6 + AddDeviceEndpoint(&gLight4, &bridgedLightEndpoint, Span(gBridgedOnOffDeviceTypes), + Span(gLight4DataVersions), 1); + + // Re-add Light 2 -- > will be mapped to ZCL endpoint 7 + AddDeviceEndpoint(&gLight2, &bridgedLightEndpoint, Span(gBridgedOnOffDeviceTypes), + Span(gLight2DataVersions), 1); + + // Add Temperature Sensor devices --> will be mapped to endpoint 8 + AddDeviceEndpoint(&TempSensor1, &bridgedTempSensorEndpoint, Span(gBridgedTempSensorDeviceTypes), + Span(gTempSensor1DataVersions), 1); +} + +void HandleDeviceTempSensorStatusChanged(DeviceTempSensor * dev, DeviceTempSensor::Changed_t itemChangedMask) +{ + using namespace Clusters; + if (itemChangedMask & + (DeviceTempSensor::kChanged_Reachable | DeviceTempSensor::kChanged_Name | DeviceTempSensor::kChanged_Location)) + { + HandleDeviceStatusChanged(static_cast(dev), (Device::Changed_t) itemChangedMask); + } + if (itemChangedMask & DeviceTempSensor::kChanged_MeasurementValue) + { + ScheduleReportingCallback(dev, TemperatureMeasurement::Id, TemperatureMeasurement::Attributes::MeasuredValue::Id); + } +} + +EmberAfStatus HandleReadTempMeasurementAttribute(DeviceTempSensor * dev, chip::AttributeId attributeId, uint8_t * buffer, + uint16_t maxReadLength) +{ + using namespace Clusters::TemperatureMeasurement::Attributes; + + if ((attributeId == MeasuredValue::Id) && (maxReadLength == 2)) + { + int16_t measuredValue = dev->GetMeasuredValue(); + memcpy(buffer, &measuredValue, sizeof(measuredValue)); + } + else if ((attributeId == MinMeasuredValue::Id) && (maxReadLength == 2)) + { + int16_t minValue = dev->mMin; + memcpy(buffer, &minValue, sizeof(minValue)); + } + else if ((attributeId == MaxMeasuredValue::Id) && (maxReadLength == 2)) + { + int16_t maxValue = dev->mMax; + memcpy(buffer, &maxValue, sizeof(maxValue)); + } + else if ((attributeId == FeatureMap::Id) && (maxReadLength == 4)) + { + uint32_t featureMap = ZCL_TEMPERATURE_SENSOR_FEATURE_MAP; + memcpy(buffer, &featureMap, sizeof(featureMap)); + } + else if ((attributeId == ClusterRevision::Id) && (maxReadLength == 4)) + { + uint16_t clusterRevision = ZCL_TEMPERATURE_SENSOR_CLUSTER_REVISION; + memcpy(buffer, &clusterRevision, sizeof(clusterRevision)); + } + else + { + return EMBER_ZCL_STATUS_FAILURE; + } + + return EMBER_ZCL_STATUS_SUCCESS; +} + +void AppTask::LightingActionButtonEventHandler(void) +{ + AppEvent event; + + event.Type = AppEvent::kEventType_Button; + event.ButtonEvent.Action = kButtonPushEvent; + event.Handler = LightingActionEventHandler; + sAppTask.PostEvent(&event); +} + +void AppTask::LightingActionEventHandler(AppEvent * aEvent) +{ + PWMDevice::Action_t action = PWMDevice::INVALID_ACTION; + int32_t actor = 0; + + if (aEvent->Type == AppEvent::kEventType_Lighting) + { + action = static_cast(aEvent->LightingEvent.Action); + actor = aEvent->LightingEvent.Actor; + } + else if (aEvent->Type == AppEvent::kEventType_Button) + { + action = sAppTask.mPwmRgbBlueLed.IsTurnedOn() ? PWMDevice::OFF_ACTION : PWMDevice::ON_ACTION; + actor = AppEvent::kEventType_Button; + } + + if (action != PWMDevice::INVALID_ACTION && (!sAppTask.mPwmRgbBlueLed.InitiateAction(action, actor, NULL))) + { + LOG_INF("Action is in progress or active"); + } +} + +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::StartThreadButtonEventHandler(void) +{ + AppEvent event; + + event.Type = AppEvent::kEventType_Button; + event.ButtonEvent.Action = kButtonPushEvent; + event.Handler = StartThreadHandler; + sAppTask.PostEvent(&event); +} + +void AppTask::StartThreadHandler(AppEvent * aEvent) +{ + LOG_INF("StartThreadHandler"); + if (!chip::DeviceLayer::ConnectivityMgr().IsThreadProvisioned()) + { + // Switch context from BLE to Thread + Internal::BLEManagerImpl sInstance; + sInstance.SwitchToIeee802154(); + StartDefaultThreadNetwork(); + } + else + { + LOG_INF("Device already commissioned"); + } +} + +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"); + } +} + +#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"); + } + + if (aActor == AppEvent::kEventType_Button) + { + sAppTask.UpdateClusterState(); + } +} + +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::UpdateClusterState(void) +{ + bool isTurnedOn = sAppTask.mPwmRgbBlueLed.IsTurnedOn(); + + // write the new on/off value + EmberAfStatus status = Clusters::OnOff::Attributes::OnOff::Set(kLightEndpointId, isTurnedOn); + + if (status != EMBER_ZCL_STATUS_SUCCESS) + { + LOG_ERR("Update OnOff fail: %x", status); + } + uint8_t setLevel = sAppTask.mPwmRgbBlueLed.GetLevel(); + status = Clusters::LevelControl::Attributes::CurrentLevel::Set(kLightEndpointId, setLevel); + if (status != EMBER_ZCL_STATUS_SUCCESS) + { + LOG_ERR("Update CurrentLevel fail: %x", status); + } +} + +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); + sLightingButton.Configure(BUTTON_PORT, BUTTON_PIN_2, LightingActionButtonEventHandler); + sThreadStartButton.Configure(BUTTON_PORT, BUTTON_PIN_3, StartThreadButtonEventHandler); + sBleAdvStartButton.Configure(BUTTON_PORT, BUTTON_PIN_4, StartBleAdvButtonEventHandler); +#else + sFactoryResetButton.Configure(BUTTON_PORT, BUTTON_PIN_3, BUTTON_PIN_1, FactoryResetButtonEventHandler); + sLightingButton.Configure(BUTTON_PORT, BUTTON_PIN_4, BUTTON_PIN_1, LightingActionButtonEventHandler); + sThreadStartButton.Configure(BUTTON_PORT, BUTTON_PIN_3, BUTTON_PIN_2, StartThreadButtonEventHandler); + sBleAdvStartButton.Configure(BUTTON_PORT, BUTTON_PIN_4, BUTTON_PIN_2, StartBleAdvButtonEventHandler); +#endif + + ButtonManagerInst().AddButton(sFactoryResetButton); + ButtonManagerInst().AddButton(sLightingButton); + ButtonManagerInst().AddButton(sThreadStartButton); + ButtonManagerInst().AddButton(sBleAdvStartButton); +} diff --git a/examples/bridge-app/telink/src/Device.cpp b/examples/bridge-app/telink/src/Device.cpp new file mode 100644 index 00000000000000..053bf2ce187679 --- /dev/null +++ b/examples/bridge-app/telink/src/Device.cpp @@ -0,0 +1,164 @@ +/* + * + * 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. + */ + +#include "Device.h" + +#include +#include +#include + +using namespace ::chip::Platform; + +Device::Device(const char * szDeviceName, const char * szLocation) +{ + CopyString(mName, sizeof(mName), szDeviceName); + CopyString(mLocation, sizeof(mLocation), szLocation); + mState = kState_Off; + mReachable = false; + mEndpointId = 0; + mChanged_CB = nullptr; +} + +bool Device::IsOn() const +{ + return mState == kState_On; +} + +bool Device::IsReachable() const +{ + return mReachable; +} + +void Device::SetOnOff(bool aOn) +{ + bool changed; + + if (aOn) + { + changed = (mState != kState_On); + mState = kState_On; + ChipLogProgress(DeviceLayer, "Device[%s]: ON", mName); + } + else + { + changed = (mState != kState_Off); + mState = kState_Off; + ChipLogProgress(DeviceLayer, "Device[%s]: OFF", mName); + } + + if (changed && mChanged_CB) + { + mChanged_CB(this, kChanged_State); + } +} + +void Device::SetReachable(bool aReachable) +{ + bool changed = (mReachable != aReachable); + + mReachable = aReachable; + + if (aReachable) + { + ChipLogProgress(DeviceLayer, "Device[%s]: ONLINE", mName); + } + else + { + ChipLogProgress(DeviceLayer, "Device[%s]: OFFLINE", mName); + } + + if (changed && mChanged_CB) + { + mChanged_CB(this, kChanged_Reachable); + } +} + +void Device::SetName(const char * szName) +{ + bool changed = (strncmp(mName, szName, sizeof(mName)) != 0); + + ChipLogProgress(DeviceLayer, "Device[%s]: New Name=\"%s\"", mName, szName); + + CopyString(mName, sizeof(mName), szName); + + if (changed && mChanged_CB) + { + mChanged_CB(this, kChanged_Name); + } +} + +void Device::SetLocation(const char * szLocation) +{ + bool changed = (strncmp(mLocation, szLocation, sizeof(mLocation)) != 0); + + CopyString(mLocation, sizeof(mLocation), szLocation); + + ChipLogProgress(DeviceLayer, "Device[%s]: Location=\"%s\"", mName, mLocation); + + if (changed && mChanged_CB) + { + mChanged_CB(this, kChanged_Location); + } +} + +void Device::SetChangeCallback(DeviceCallback_fn aChanged_CB) +{ + mChanged_CB = aChanged_CB; +} + +DeviceTempSensor::DeviceTempSensor(const char * szDeviceName, std::string szLocation, int16_t min, int16_t max, + int16_t measuredValue) : + Device(szDeviceName, szLocation.c_str()), + mMin(min), mMax(max), mMeasurement(measuredValue) +{} + +void DeviceTempSensor::SetMeasuredValue(int16_t measurement) +{ + // Limit measurement based on the min and max. + if (measurement < mMin) + { + measurement = mMin; + } + else if (measurement > mMax) + { + measurement = mMax; + } + + bool changed = mMeasurement != measurement; + + ChipLogProgress(DeviceLayer, "TempSensorDevice new measurement=\"%d\"", measurement); + + mMeasurement = measurement; + + if (changed && mChanged_CB) + { + mChanged_CB(this, kChanged_MeasurementValue); + } +} + +void DeviceTempSensor::SetChangeCallback(DeviceCallback_fn aChanged_CB) +{ + mChanged_CB = aChanged_CB; +} + +void DeviceTempSensor::HandleDeviceChange(Device * device, Device::Changed_t changeMask) +{ + if (mChanged_CB) + { + mChanged_CB(this, (DeviceTempSensor::Changed_t) changeMask); + } +} diff --git a/examples/bridge-app/telink/src/DeviceCallbacks.cpp b/examples/bridge-app/telink/src/DeviceCallbacks.cpp new file mode 100644 index 00000000000000..850f88d742b8e7 --- /dev/null +++ b/examples/bridge-app/telink/src/DeviceCallbacks.cpp @@ -0,0 +1,104 @@ +/* + * + * Copyright (c) 2023 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include +#include + +using namespace ::chip; +using namespace ::chip::app; +using namespace ::chip::app::Clusters; +using namespace ::chip::app::Clusters::Actions::Attributes; +using namespace ::chip::Inet; +using namespace ::chip::System; + +namespace { + +class ActionsAttrAccess : public AttributeAccessInterface +{ +public: + // Register for the Actions cluster on all endpoints. + ActionsAttrAccess() : AttributeAccessInterface(Optional::Missing(), Actions::Id) {} + + CHIP_ERROR Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder) override; + +private: + static constexpr uint16_t ClusterRevision = 1; + + CHIP_ERROR ReadActionListAttribute(EndpointId endpoint, AttributeValueEncoder & aEncoder); + CHIP_ERROR ReadEndpointListAttribute(EndpointId endpoint, AttributeValueEncoder & aEncoder); + CHIP_ERROR ReadSetupUrlAttribute(EndpointId endpoint, AttributeValueEncoder & aEncoder); + CHIP_ERROR ReadClusterRevision(EndpointId endpoint, AttributeValueEncoder & aEncoder); +}; + +constexpr uint16_t ActionsAttrAccess::ClusterRevision; + +CHIP_ERROR ActionsAttrAccess::ReadActionListAttribute(EndpointId endpoint, AttributeValueEncoder & aEncoder) +{ + // Just return an empty list + return aEncoder.EncodeEmptyList(); +} + +CHIP_ERROR ActionsAttrAccess::ReadEndpointListAttribute(EndpointId endpoint, AttributeValueEncoder & aEncoder) +{ + // Just return an empty list + return aEncoder.EncodeEmptyList(); +} + +CHIP_ERROR ActionsAttrAccess::ReadSetupUrlAttribute(EndpointId endpoint, AttributeValueEncoder & aEncoder) +{ + const char SetupUrl[] = "https://example.com"; + return aEncoder.Encode(chip::CharSpan::fromCharString(SetupUrl)); +} + +CHIP_ERROR ActionsAttrAccess::ReadClusterRevision(EndpointId endpoint, AttributeValueEncoder & aEncoder) +{ + return aEncoder.Encode(ClusterRevision); +} + +ActionsAttrAccess gAttrAccess; + +CHIP_ERROR ActionsAttrAccess::Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder) +{ + VerifyOrDie(aPath.mClusterId == Actions::Id); + + switch (aPath.mAttributeId) + { + case ActionList::Id: + return ReadActionListAttribute(aPath.mEndpointId, aEncoder); + case EndpointLists::Id: + return ReadEndpointListAttribute(aPath.mEndpointId, aEncoder); + case SetupURL::Id: + return ReadSetupUrlAttribute(aPath.mEndpointId, aEncoder); + case ClusterRevision::Id: + return ReadClusterRevision(aPath.mEndpointId, aEncoder); + default: + break; + } + return CHIP_NO_ERROR; +} +} // anonymous namespace + +void MatterActionsPluginServerInitCallback(void) +{ + registerAttributeAccessOverride(&gAttrAccess); +} diff --git a/examples/bridge-app/telink/src/ZclCallbacks.cpp b/examples/bridge-app/telink/src/ZclCallbacks.cpp new file mode 100644 index 00000000000000..b6f5ed558c584e --- /dev/null +++ b/examples/bridge-app/telink/src/ZclCallbacks.cpp @@ -0,0 +1,58 @@ +/* + * + * 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 "ColorFormat.h" +#include "PWMDevice.h" + +#include +#include +#include +#include +#include + +LOG_MODULE_DECLARE(app, CONFIG_CHIP_APP_LOG_LEVEL); + +using namespace chip; +using namespace chip::app::Clusters; +using namespace chip::app::Clusters::OnOff; + +/** @brief OnOff Cluster Init + * + * This function is called when a specific cluster is initialized. It gives the + * application an opportunity to take care of cluster initialization procedures. + * It is called exactly once for each endpoint where cluster is present. + * + * @param endpoint Ver.: always + * + */ +void emberAfOnOffClusterInitCallback(EndpointId endpoint) +{ + EmberAfStatus status; + bool storedValue; + + // Read storedValue on/off value + status = Attributes::OnOff::Get(1, &storedValue); + if (status == EMBER_ZCL_STATUS_SUCCESS) + { + // Set actual state to stored before reboot + GetAppTask().GetPWMDevice().Set(storedValue); + } + + GetAppTask().UpdateClusterState(); +} diff --git a/examples/bridge-app/telink/src/main.cpp b/examples/bridge-app/telink/src/main.cpp new file mode 100644 index 00000000000000..71aa52f9d91901 --- /dev/null +++ b/examples/bridge-app/telink/src/main.cpp @@ -0,0 +1,82 @@ +/* + * + * 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 + +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; + + 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/bridge-app/telink/third_party/connectedhomeip b/examples/bridge-app/telink/third_party/connectedhomeip new file mode 120000 index 00000000000000..c866b86874994d --- /dev/null +++ b/examples/bridge-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 904dd81e0d063f..8c2a1cd15ef722 100755 --- a/scripts/build/build/targets.py +++ b/scripts/build/build/targets.py @@ -547,6 +547,7 @@ def BuildTelinkTarget(): target.AppendFixedTargets([ TargetPart('all-clusters', app=TelinkApp.ALL_CLUSTERS), TargetPart('all-clusters-minimal', app=TelinkApp.ALL_CLUSTERS_MINIMAL), + TargetPart('bridge', app=TelinkApp.BRIDGE), TargetPart('contact-sensor', app=TelinkApp.CONTACT_SENSOR), TargetPart('light', app=TelinkApp.LIGHT), TargetPart('light-switch', app=TelinkApp.SWITCH), diff --git a/scripts/build/builders/telink.py b/scripts/build/builders/telink.py index 9dc580016fbf7e..aec767a764100d 100644 --- a/scripts/build/builders/telink.py +++ b/scripts/build/builders/telink.py @@ -23,6 +23,7 @@ class TelinkApp(Enum): ALL_CLUSTERS = auto() ALL_CLUSTERS_MINIMAL = auto() + BRIDGE = auto() CONTACT_SENSOR = auto() LIGHT = auto() SWITCH = auto() @@ -39,6 +40,8 @@ def ExampleName(self): return 'all-clusters-app' elif self == TelinkApp.ALL_CLUSTERS_MINIMAL: return 'all-clusters-minimal-app' + elif self == TelinkApp.BRIDGE: + return 'bridge-app' elif self == TelinkApp.CONTACT_SENSOR: return 'contact-sensor-app' elif self == TelinkApp.LIGHT: @@ -67,6 +70,8 @@ def AppNamePrefix(self): return 'chip-telink-all-clusters-example' elif self == TelinkApp.ALL_CLUSTERS_MINIMAL: return 'chip-telink-all-clusters-minimal-example' + elif self == TelinkApp.BRIDGE: + return 'chip-telink-bridge-example' elif self == TelinkApp.CONTACT_SENSOR: return 'chip-telink-contact-sensor-example' elif self == TelinkApp.LIGHT: diff --git a/scripts/build/testdata/all_targets_linux_x64.txt b/scripts/build/testdata/all_targets_linux_x64.txt index a162653f1efc62..fb7005b2a3c818 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,window-covering}[-rpc][-factory-data] +telink-tlsr9518adk80d-{all-clusters,all-clusters-minimal,bridge,contact-sensor,light,light-switch,lock,ota-requestor,pump,pump-controller,temperature-measurement,thermostat,window-covering}[-rpc][-factory-data] openiotsdk-{shell,lock}