diff --git a/.github/workflows/examples-telink.yaml b/.github/workflows/examples-telink.yaml index ca1b0dc7a4887b..2d03c8ef1549af 100644 --- a/.github/workflows/examples-telink.yaml +++ b/.github/workflows/examples-telink.yaml @@ -115,6 +115,15 @@ jobs: out/telink-tlsr9518adk80d-light-switch/zephyr/zephyr.elf \ /tmp/bloat_reports/ + - name: Build example Telink Lock App + run: | + ./scripts/run_in_build_env.sh \ + "./scripts/build/build_examples.py --target 'telink-tlsr9518adk80d-lock' build" + .environment/pigweed-venv/bin/python3 scripts/tools/memory/gh_sizes.py \ + telink tlsr9518adk80d lock-app \ + out/telink-tlsr9518adk80d-lock/zephyr/zephyr.elf \ + /tmp/bloat_reports/ + - name: Build example Telink OTA Requestor App run: | ./scripts/run_in_build_env.sh \ @@ -124,6 +133,24 @@ jobs: out/telink-tlsr9518adk80d-ota-requestor/zephyr/zephyr.elf \ /tmp/bloat_reports/ + - name: Build example Telink Pump App + run: | + ./scripts/run_in_build_env.sh \ + "./scripts/build/build_examples.py --target 'telink-tlsr9518adk80d-pump' build" + .environment/pigweed-venv/bin/python3 scripts/tools/memory/gh_sizes.py \ + telink tlsr9518adk80d pump-app \ + out/telink-tlsr9518adk80d-pump/zephyr/zephyr.elf \ + /tmp/bloat_reports/ + + - name: Build example Telink Pump Controller App + run: | + ./scripts/run_in_build_env.sh \ + "./scripts/build/build_examples.py --target 'telink-tlsr9518adk80d-pump-controller' build" + .environment/pigweed-venv/bin/python3 scripts/tools/memory/gh_sizes.py \ + telink tlsr9518adk80d pump-controller-app \ + out/telink-tlsr9518adk80d-pump-controller/zephyr/zephyr.elf \ + /tmp/bloat_reports/ + - name: Build example Telink Thermostat App run: | ./scripts/run_in_build_env.sh \ diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 32db085151c081..5df772ddbaedbe 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -641,7 +641,10 @@ "telink-tlsr9518adk80d-contact-sensor", "telink-tlsr9518adk80d-light", "telink-tlsr9518adk80d-light-switch", + "telink-tlsr9518adk80d-lock-app", "telink-tlsr9518adk80d-ota-requestor", + "telink-tlsr9518adk80d-pump-app", + "telink-tlsr9518adk80d-pump-controller-app", "telink-tlsr9518adk80d-thermostat", "tizen-arm-light" ] diff --git a/examples/contact-sensor-app/telink/README.md b/examples/contact-sensor-app/telink/README.md index a41fadcc3bacb8..d6c8bd7c307133 100755 --- a/examples/contact-sensor-app/telink/README.md +++ b/examples/contact-sensor-app/telink/README.md @@ -92,7 +92,7 @@ be used to specify the the effect. It is able to be in following effects: #### Indicate current state of Contact Sensor -**Blue** LED shows current state of Contact Sensor +**White** LED shows current state of Contact Sensor ### CHIP tool commands diff --git a/examples/contact-sensor-app/telink/include/AppConfig.h b/examples/contact-sensor-app/telink/include/AppConfig.h index 4e4f556036bcfa..d32944992e2cfb 100755 --- a/examples/contact-sensor-app/telink/include/AppConfig.h +++ b/examples/contact-sensor-app/telink/include/AppConfig.h @@ -31,5 +31,5 @@ // LEDs config #define LEDS_PORT DEVICE_DT_GET(DT_NODELABEL(gpiob)) #define SYSTEM_STATE_LED 7 -#define CONTACT_STATE_LED 4 +#define CONTACT_STATE_LED 6 #define LIGHTING_PWM_SPEC_IDENTIFY_GREEN PWM_DT_SPEC_GET(DT_ALIAS(pwm_led3)) diff --git a/examples/lock-app/telink/.gitignore b/examples/lock-app/telink/.gitignore new file mode 100644 index 00000000000000..84c048a73cc2e5 --- /dev/null +++ b/examples/lock-app/telink/.gitignore @@ -0,0 +1 @@ +/build/ diff --git a/examples/lock-app/telink/CMakeLists.txt b/examples/lock-app/telink/CMakeLists.txt new file mode 100755 index 00000000000000..434903038ed3b7 --- /dev/null +++ b/examples/lock-app/telink/CMakeLists.txt @@ -0,0 +1,65 @@ +# +# 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-lock-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}/lock-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/BoltLockManager.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}/../lock-common/lock-app.zap +) + +if(CONFIG_CHIP_OTA_REQUESTOR) + target_sources(app PRIVATE ${TELINK_COMMON}/util/src/OTAUtil.cpp) +endif() diff --git a/examples/lock-app/telink/README.md b/examples/lock-app/telink/README.md new file mode 100755 index 00000000000000..0729dfb108933e --- /dev/null +++ b/examples/lock-app/telink/README.md @@ -0,0 +1,181 @@ +# Matter Telink Lock Example Application + +The Telink Lock Example shows the functionality of a remote control door-lock +device with one basic bolt. It uses button to test changing the lock 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 | Perform factory reset to forget currently commissioned Thread network and back to uncommissioned state | +| Button 2 | Lock control | Manually triggers the bolt lock 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) | + +#### Indicate current Lock state + +**White** LED shows current state of Lock + +### 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 + ``` + +### 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/lock-app/telink/include/AppConfig.h b/examples/lock-app/telink/include/AppConfig.h new file mode 100755 index 00000000000000..a6b0bc0b7f3ddf --- /dev/null +++ b/examples/lock-app/telink/include/AppConfig.h @@ -0,0 +1,35 @@ +/* + * + * 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 + +// ---- Lock Example App 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 LOCK_STATE_LED 6 +#define LIGHTING_PWM_SPEC_IDENTIFY_GREEN PWM_DT_SPEC_GET(DT_ALIAS(pwm_led3)) diff --git a/examples/lock-app/telink/include/AppEvent.h b/examples/lock-app/telink/include/AppEvent.h new file mode 100644 index 00000000000000..9f2436c592dc8c --- /dev/null +++ b/examples/lock-app/telink/include/AppEvent.h @@ -0,0 +1,60 @@ +/* + * + * 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, + kEventType_Install, + }; + + uint16_t Type; + + union + { + struct + { + uint8_t Action; + } ButtonEvent; + struct + { + void * Context; + } TimerEvent; + struct + { + LEDWidget * LedWidget; + } UpdateLedStateEvent; + }; + + EventHandler Handler; +}; diff --git a/examples/lock-app/telink/include/AppTask.h b/examples/lock-app/telink/include/AppTask.h new file mode 100644 index 00000000000000..5f26b3d6895634 --- /dev/null +++ b/examples/lock-app/telink/include/AppTask.h @@ -0,0 +1,92 @@ +/* + * + * 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 "AppEvent.h" +#include "BoltLockManager.h" +#if CONFIG_CHIP_ENABLE_APPLICATION_STATUS_LED +#include "LEDWidget.h" +#endif +#include "PWMDevice.h" + +#include + +#if CONFIG_CHIP_FACTORY_DATA +#include +#endif + +struct k_timer; +struct Identify; + +class AppTask +{ +public: + CHIP_ERROR StartApp(); + + void PostEvent(AppEvent * event); + void UpdateClusterState(BoltLockManager::State state, BoltLockManager::OperationSource source); + static void IdentifyEffectHandler(EmberAfIdentifyEffectIdentifier aEffect); + +private: + friend AppTask & GetAppTask(void); + + CHIP_ERROR Init(void); + + 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 LockActionButtonEventHandler(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 SwitchActionEventHandler(AppEvent * aEvent); + static void StartBleAdvHandler(AppEvent * aEvent); + static void UpdateIdentifyStateEventHandler(AppEvent * aEvent); + + static void LockActionEventHandler(AppEvent * event); + static void LockStateChanged(BoltLockManager::State state, BoltLockManager::OperationSource source); + + static void InitButtons(void); + + static AppTask sAppTask; + PWMDevice mPwmIdentifyLed; + +#if CONFIG_CHIP_FACTORY_DATA + chip::DeviceLayer::FactoryDataProvider mFactoryDataProvider; +#endif +}; + +inline AppTask & GetAppTask(void) +{ + return AppTask::sAppTask; +} diff --git a/examples/lock-app/telink/include/BoltLockManager.h b/examples/lock-app/telink/include/BoltLockManager.h new file mode 100644 index 00000000000000..28176c9efee3b6 --- /dev/null +++ b/examples/lock-app/telink/include/BoltLockManager.h @@ -0,0 +1,129 @@ +/* + * + * Copyright (c) 2023 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include + +#include + +#include +#include + +// Maximum number of credentials per user supported by lock +#define CONFIG_LOCK_NUM_CREDENTIALS_PER_USER (2) +// Maximum number of users supported by lock +#define CONFIG_LOCK_NUM_USERS (5) +// Maximum number of credentials supported by lock +#define CONFIG_LOCK_NUM_CREDENTIALS (10) + +struct LockCredentialInfo; + +#define CONFIG_LOCK_CREDENTIAL_INFO_MAX_DATA_SIZE (20) +#define CONFIG_LOCK_CREDENTIAL_INFO_MAX_TYPES (6) + +class AppEvent; + +class BoltLockManager +{ +public: + BoltLockManager() : + mCredentials(CONFIG_LOCK_CREDENTIAL_INFO_MAX_TYPES, std::vector(CONFIG_LOCK_NUM_CREDENTIALS + 1)) + {} + static constexpr size_t kMaxCredentialLength = 128; + + enum class State : uint8_t + { + kLockingInitiated = 0, + kLockingCompleted, + kUnlockingInitiated, + kUnlockingCompleted, + }; + + struct UserData + { + char mName[DOOR_LOCK_USER_NAME_BUFFER_SIZE]; + CredentialStruct mCredentials[CONFIG_LOCK_NUM_CREDENTIALS_PER_USER]; + }; + + struct CredentialData + { + chip::Platform::ScopedMemoryBuffer mSecret; + }; + + using OperationSource = chip::app::Clusters::DoorLock::OperationSourceEnum; + using StateChangeCallback = void (*)(State, OperationSource); + + static constexpr uint32_t kActuatorMovementTimeMs = 2000; + + void Init(StateChangeCallback callback); + + State GetState() const { return mState; } + bool IsLocked() const { return mState == State::kLockingCompleted; } + + bool GetUser(uint16_t userIndex, EmberAfPluginDoorLockUserInfo & user) const; + bool SetUser(uint16_t userIndex, chip::FabricIndex creator, chip::FabricIndex modifier, const chip::CharSpan & userName, + uint32_t uniqueId, UserStatusEnum userStatus, UserTypeEnum userType, CredentialRuleEnum credentialRule, + const CredentialStruct * credentials, size_t totalCredentials); + + bool GetCredential(uint16_t credentialIndex, CredentialTypeEnum credentialType, + EmberAfPluginDoorLockCredentialInfo & credential) const; + bool SetCredential(uint16_t credentialIndex, chip::FabricIndex creator, chip::FabricIndex modifier, + DlCredentialStatus credentialStatus, CredentialTypeEnum credentialType, + const chip::ByteSpan & credentialData); + + bool ValidatePIN(const Optional & pinCode, OperationErrorEnum & err) const; + + void Lock(OperationSource source); + void Unlock(OperationSource source); + +private: + void SetState(State state, OperationSource source); + + static void ActuatorTimerEventHandler(k_timer * timer); + static void ActuatorAppEventHandler(const AppEvent & aEvent); + friend BoltLockManager & BoltLockMgr(); + + State mState = State::kLockingCompleted; + StateChangeCallback mStateChangeCallback = nullptr; + OperationSource mActuatorOperationSource = OperationSource::kButton; + k_timer mActuatorTimer = {}; + + UserData mUserData[CONFIG_LOCK_NUM_USERS]; + EmberAfPluginDoorLockUserInfo mUsers[CONFIG_LOCK_NUM_USERS] = {}; + + struct LockCredentialInfo + { + DlCredentialStatus status; + CredentialTypeEnum credentialType; + chip::FabricIndex createdBy; + chip::FabricIndex modifiedBy; + uint8_t credentialData[CONFIG_LOCK_CREDENTIAL_INFO_MAX_TYPES]; + size_t credentialDataSize; + }; + std::vector> mCredentials; + + static BoltLockManager sLock; +}; + +inline BoltLockManager & BoltLockMgr() +{ + return BoltLockManager::sLock; +} diff --git a/examples/lock-app/telink/include/CHIPProjectConfig.h b/examples/lock-app/telink/include/CHIPProjectConfig.h new file mode 100755 index 00000000000000..412932a59c3726 --- /dev/null +++ b/examples/lock-app/telink/include/CHIPProjectConfig.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. + */ + +/** + * @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 + +/** + * 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/lock-app/telink/prj.conf b/examples/lock-app/telink/prj.conf new file mode 100755 index 00000000000000..0f34d45d8f9cd4 --- /dev/null +++ b/examples/lock-app/telink/prj.conf @@ -0,0 +1,74 @@ +# +# 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=y +CONFIG_OPENTHREAD_FTD=n +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 +# 32774 == 0x8006 (example lock-app) +CONFIG_CHIP_DEVICE_PRODUCT_ID=32774 +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 diff --git a/examples/lock-app/telink/src/AppTask.cpp b/examples/lock-app/telink/src/AppTask.cpp new file mode 100644 index 00000000000000..91fc9540b15faf --- /dev/null +++ b/examples/lock-app/telink/src/AppTask.cpp @@ -0,0 +1,620 @@ +/* + * + * 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 "BoltLockManager.h" +#include "ButtonManager.h" + +#include "ThreadUtil.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if CONFIG_CHIP_OTA_REQUESTOR +#include "OTAUtil.h" +#endif + +#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::app::Clusters::DoorLock; +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 kLockEndpointId = 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; +uint8_t sFactoryResetCntr = 0; + +#if CONFIG_CHIP_ENABLE_APPLICATION_STATUS_LED +LEDWidget sStatusLED; +LEDWidget sLockLED; +#endif + +Button sFactoryResetButton; +Button sLockButton; +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 = { + kLockEndpointId, + [](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 status LED +#if CONFIG_CHIP_ENABLE_APPLICATION_STATUS_LED + LEDWidget::InitGpio(LEDS_PORT); + LEDWidget::SetStateUpdateCallback(LEDStateUpdateHandler); + sStatusLED.Init(SYSTEM_STATE_LED); + + UpdateStatusLED(); + + sLockLED.Init(LOCK_STATE_LED); + sLockLED.Set(BoltLockMgr().IsLocked()); +#endif + + InitButtons(); + + // Initialize function button timer + k_timer_init(&sFactoryResetTimer, &AppTask::FactoryResetTimerTimeoutCallback, nullptr); + k_timer_user_data_set(&sFactoryResetTimer, this); + + // Initialize PWM Identify led + 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); + + BoltLockMgr().Init(LockStateChanged); + + // 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("mFactoryDataProvider.GetEnableKey() failed. Could not delegate a test event trigger"); + 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); + + // Disable auto-relock time feature. + DoorLockServer::Instance().SetAutoRelockTime(kLockEndpointId, 0); + + err = ConnectivityMgr().SetBLEDeviceName("Telink Lock"); + 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::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::LockActionButtonEventHandler(void) +{ + AppEvent event; + + event.Type = AppEvent::kEventType_Button; + event.ButtonEvent.Action = kButtonPushEvent; + event.Handler = LockActionEventHandler; + sAppTask.PostEvent(&event); +} + +void AppTask::LockActionEventHandler(AppEvent * aEvent) +{ + if (BoltLockMgr().IsLocked()) + { + BoltLockMgr().Unlock(BoltLockManager::OperationSource::kButton); + } + else + { + BoltLockMgr().Lock(BoltLockManager::OperationSource::kButton); + } +} + +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::LockStateChanged(BoltLockManager::State state, BoltLockManager::OperationSource source) +{ + switch (state) + { + case BoltLockManager::State::kLockingInitiated: + LOG_INF("Lock action initiated"); + sLockLED.Blink(50, 50); + break; + case BoltLockManager::State::kLockingCompleted: + LOG_INF("Lock action completed"); + sLockLED.Set(true); + break; + case BoltLockManager::State::kUnlockingInitiated: + LOG_INF("Unlock action initiated"); + sLockLED.Blink(50, 50); + break; + case BoltLockManager::State::kUnlockingCompleted: + LOG_INF("Unlock action completed"); + sLockLED.Set(false); + break; + } + + // Handle changing attribute state in the application + sAppTask.UpdateClusterState(state, source); +} + +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(BoltLockManager::State state, BoltLockManager::OperationSource source) +{ + DlLockState newLockState; + + switch (state) + { + case BoltLockManager::State::kLockingCompleted: + newLockState = DlLockState::kLocked; + break; + case BoltLockManager::State::kUnlockingCompleted: + newLockState = DlLockState::kUnlocked; + break; + default: + newLockState = DlLockState::kNotFullyLocked; + break; + } + + SystemLayer().ScheduleLambda([newLockState, source] { + chip::app::DataModel::Nullable currentLockState; + chip::app::Clusters::DoorLock::Attributes::LockState::Get(kLockEndpointId, currentLockState); + + if (currentLockState.IsNull()) + { + // Initialize lock state with start value, but not invoke lock/unlock. + chip::app::Clusters::DoorLock::Attributes::LockState::Set(kLockEndpointId, newLockState); + } + else + { + LOG_INF("Updating LockState attribute"); + + if (!DoorLockServer::Instance().SetLockState(kLockEndpointId, newLockState, source)) + { + LOG_ERR("Failed to update LockState attribute"); + } + } + }); +} + +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); + sLockButton.Configure(BUTTON_PORT, BUTTON_PIN_2, LockActionButtonEventHandler); + 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); + sLockButton.Configure(BUTTON_PORT, BUTTON_PIN_4, BUTTON_PIN_1, LockActionButtonEventHandler); + 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(sLockButton); + ButtonManagerInst().AddButton(sThreadStartButton); + ButtonManagerInst().AddButton(sBleAdvStartButton); +} diff --git a/examples/lock-app/telink/src/BoltLockManager.cpp b/examples/lock-app/telink/src/BoltLockManager.cpp new file mode 100644 index 00000000000000..3abf145f482653 --- /dev/null +++ b/examples/lock-app/telink/src/BoltLockManager.cpp @@ -0,0 +1,260 @@ +/* + * + * 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 "BoltLockManager.h" + +#include "AppConfig.h" +#include "AppEvent.h" +#include "AppTask.h" +#include + +using namespace chip; +using chip::to_underlying; + +BoltLockManager BoltLockManager::sLock; + +void BoltLockManager::Init(StateChangeCallback callback) +{ + mStateChangeCallback = callback; + + k_timer_init(&mActuatorTimer, &BoltLockManager::ActuatorTimerEventHandler, nullptr); + k_timer_user_data_set(&mActuatorTimer, this); +} + +bool BoltLockManager::GetUser(uint16_t userIndex, EmberAfPluginDoorLockUserInfo & user) const +{ + // userIndex is guaranteed by the caller to be between 1 and CONFIG_LOCK_NUM_USERS + user = mUsers[userIndex - 1]; + + ChipLogProgress(Zcl, "Getting lock user %u: %s", static_cast(userIndex), + user.userStatus == UserStatusEnum::kAvailable ? "available" : "occupied"); + + return true; +} + +bool BoltLockManager::SetUser(uint16_t userIndex, FabricIndex creator, FabricIndex modifier, const CharSpan & userName, + uint32_t uniqueId, UserStatusEnum userStatus, UserTypeEnum userType, + CredentialRuleEnum credentialRule, const CredentialStruct * credentials, size_t totalCredentials) +{ + // userIndex is guaranteed by the caller to be between 1 and CONFIG_LOCK_NUM_USERS + UserData & userData = mUserData[userIndex - 1]; + auto & user = mUsers[userIndex - 1]; + + VerifyOrReturnError(userName.size() <= DOOR_LOCK_MAX_USER_NAME_SIZE, false); + VerifyOrReturnError(totalCredentials <= CONFIG_LOCK_NUM_CREDENTIALS_PER_USER, false); + + Platform::CopyString(userData.mName, userName); + memcpy(userData.mCredentials, credentials, totalCredentials * sizeof(CredentialStruct)); + + user.userName = CharSpan(userData.mName, userName.size()); + user.credentials = Span(userData.mCredentials, totalCredentials); + user.userUniqueId = uniqueId; + user.userStatus = userStatus; + user.userType = userType; + user.credentialRule = credentialRule; + user.creationSource = DlAssetSource::kMatterIM; + user.createdBy = creator; + user.modificationSource = DlAssetSource::kMatterIM; + user.lastModifiedBy = modifier; + + ChipLogProgress(Zcl, "Setting lock user %u: %s", static_cast(userIndex), + userStatus == UserStatusEnum::kAvailable ? "available" : "occupied"); + + return true; +} + +bool BoltLockManager::GetCredential(uint16_t credentialIndex, CredentialTypeEnum credentialType, + EmberAfPluginDoorLockCredentialInfo & credential) const +{ + ChipLogProgress(Zcl, "Lock App: LockEndpoint::GetCredential [credentialIndex=%u,credentialType=%u]", credentialIndex, + to_underlying(credentialType)); + + if (to_underlying(credentialType) >= mCredentials.size()) + { + ChipLogError(Zcl, "Cannot get the credential - index out of range [index=%d]", credentialIndex); + return false; + } + + if (credentialIndex >= mCredentials.at(to_underlying(credentialType)).size() || + (0 == credentialIndex && CredentialTypeEnum::kProgrammingPIN != credentialType)) + { + ChipLogError(Zcl, "Cannot get the credential - index out of range [index=%d]", credentialIndex); + return false; + } + + const auto & credentialInStorage = mCredentials[to_underlying(credentialType)][credentialIndex]; + + credential.status = credentialInStorage.status; + if (DlCredentialStatus::kAvailable == credential.status) + { + ChipLogDetail(Zcl, "Found unoccupied credential [index=%u]", credentialIndex); + return true; + } + credential.credentialType = credentialInStorage.credentialType; + credential.credentialData = chip::ByteSpan(credentialInStorage.credentialData, credentialInStorage.credentialDataSize); + // So far there's no way to actually create the credential outside the matter, so here we always set the creation/modification + // source to Matter + credential.creationSource = DlAssetSource::kMatterIM; + credential.createdBy = credentialInStorage.createdBy; + credential.modificationSource = DlAssetSource::kMatterIM; + credential.lastModifiedBy = credentialInStorage.modifiedBy; + + ChipLogDetail(Zcl, "Found occupied credential [index=%u,type=%u,dataSize=%u,createdBy=%u,modifiedBy=%u]", credentialIndex, + to_underlying(credential.credentialType), static_cast(credential.credentialData.size()), + credential.createdBy, credential.lastModifiedBy); + + return true; +} + +bool BoltLockManager::SetCredential(uint16_t credentialIndex, FabricIndex creator, FabricIndex modifier, + DlCredentialStatus credentialStatus, CredentialTypeEnum credentialType, + const ByteSpan & credentialData) +{ + ChipLogProgress(Zcl, + "Lock App: LockEndpoint::SetCredential " + "[credentialIndex=%u,credentialStatus=%u,credentialType=%u,credentialDataSize=%u,creator=%u,modifier=%u]", + credentialIndex, to_underlying(credentialStatus), to_underlying(credentialType), + static_cast(credentialData.size()), creator, modifier); + + if (to_underlying(credentialType) >= mCredentials.capacity()) + { + ChipLogError(Zcl, "Cannot set the credential - type out of range [type=%d]", to_underlying(credentialType)); + return false; + } + + if (credentialIndex >= mCredentials.at(to_underlying(credentialType)).size() || + (0 == credentialIndex && CredentialTypeEnum::kProgrammingPIN != credentialType)) + { + ChipLogError(Zcl, "Cannot set the credential - index out of range [index=%d]", credentialIndex); + return false; + } + + auto & credentialInStorage = mCredentials[to_underlying(credentialType)][credentialIndex]; + if (credentialData.size() > CONFIG_LOCK_CREDENTIAL_INFO_MAX_DATA_SIZE) + { + ChipLogError(Zcl, + "Cannot get the credential - data size exceeds limit " + "index=%d,dataSize=%u,maxDataSize=%u]", + credentialIndex, static_cast(credentialData.size()), + static_cast(CONFIG_LOCK_CREDENTIAL_INFO_MAX_DATA_SIZE)); + return false; + } + credentialInStorage.status = credentialStatus; + credentialInStorage.credentialType = credentialType; + credentialInStorage.createdBy = creator; + credentialInStorage.modifiedBy = modifier; + std::memcpy(credentialInStorage.credentialData, credentialData.data(), credentialData.size()); + credentialInStorage.credentialDataSize = credentialData.size(); + + ChipLogProgress(Zcl, "Successfully set the credential [index=%d,credentialType=%u,creator=%u,modifier=%u]", credentialIndex, + to_underlying(credentialType), credentialInStorage.createdBy, credentialInStorage.modifiedBy); + + return true; +} + +bool BoltLockManager::ValidatePIN(const Optional & pinCode, OperationErrorEnum & err) const +{ + // Optionality of the PIN code is validated by the caller, so assume it is OK not to provide the PIN code. + if (!pinCode.HasValue()) + { + return true; + } + + // Find the credential so we can make sure it is not absent right away + auto & pinCredentials = mCredentials[to_underlying(CredentialTypeEnum::kPin)]; + auto credential = std::find_if(pinCredentials.begin(), pinCredentials.end(), [&pinCode](const LockCredentialInfo & c) { + return (c.status != DlCredentialStatus::kAvailable) && + chip::ByteSpan{ c.credentialData, c.credentialDataSize }.data_equal(pinCode.Value()); + }); + + if (credential == pinCredentials.end()) + { + ChipLogDetail(Zcl, "Lock App: specified PIN code was not found in the database"); + + err = OperationErrorEnum::kInvalidCredential; + return false; + } + + ChipLogDetail(Zcl, "Invalid lock PIN code provided"); + err = OperationErrorEnum::kInvalidCredential; + + return false; +} + +void BoltLockManager::Lock(OperationSource source) +{ + VerifyOrReturn(mState != State::kLockingCompleted); + SetState(State::kLockingInitiated, source); + + mActuatorOperationSource = source; + k_timer_start(&mActuatorTimer, K_MSEC(kActuatorMovementTimeMs), K_NO_WAIT); +} + +void BoltLockManager::Unlock(OperationSource source) +{ + VerifyOrReturn(mState != State::kUnlockingCompleted); + SetState(State::kUnlockingInitiated, source); + + mActuatorOperationSource = source; + k_timer_start(&mActuatorTimer, K_MSEC(kActuatorMovementTimeMs), K_NO_WAIT); +} + +void BoltLockManager::ActuatorTimerEventHandler(k_timer * timer) +{ + // The timer event handler is called in the context of the system clock ISR. + // Post an event to the application task queue to process the event in the + // context of the application thread. + + AppEvent event; + event.Type = AppEvent::kEventType_Timer; + event.TimerEvent.Context = static_cast(k_timer_user_data_get(timer)); + event.Handler = (EventHandler) BoltLockManager::ActuatorAppEventHandler; + GetAppTask().PostEvent(&event); +} + +void BoltLockManager::ActuatorAppEventHandler(const AppEvent & event) +{ + BoltLockManager * lock = static_cast(event.TimerEvent.Context); + + if (!lock) + { + return; + } + + switch (lock->mState) + { + case State::kLockingInitiated: + lock->SetState(State::kLockingCompleted, lock->mActuatorOperationSource); + break; + case State::kUnlockingInitiated: + lock->SetState(State::kUnlockingCompleted, lock->mActuatorOperationSource); + break; + default: + break; + } +} + +void BoltLockManager::SetState(State state, OperationSource source) +{ + mState = state; + + if (mStateChangeCallback != nullptr) + { + mStateChangeCallback(state, source); + } +} diff --git a/examples/lock-app/telink/src/ZclCallbacks.cpp b/examples/lock-app/telink/src/ZclCallbacks.cpp new file mode 100644 index 00000000000000..b304dc51e68cb5 --- /dev/null +++ b/examples/lock-app/telink/src/ZclCallbacks.cpp @@ -0,0 +1,127 @@ +/* + * + * 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 "BoltLockManager.h" + +#include +#include +#include +#include +#include + +using namespace ::chip; +using namespace ::chip::app::Clusters; +using namespace ::chip::app::Clusters::DoorLock; + +LOG_MODULE_DECLARE(app, CONFIG_CHIP_APP_LOG_LEVEL); + +void MatterPostAttributeChangeCallback(const chip::app::ConcreteAttributePath & attributePath, uint8_t type, uint16_t size, + uint8_t * value) +{ + VerifyOrReturn(attributePath.mClusterId == DoorLock::Id && attributePath.mAttributeId == DoorLock::Attributes::LockState::Id); + + switch (*value) + { + case to_underlying(DlLockState::kLocked): + BoltLockMgr().Lock(BoltLockManager::OperationSource::kRemote); + break; + case to_underlying(DlLockState::kUnlocked): + BoltLockMgr().Unlock(BoltLockManager::OperationSource::kRemote); + break; + default: + break; + } +} + +bool emberAfPluginDoorLockGetUser(EndpointId endpointId, uint16_t userIndex, EmberAfPluginDoorLockUserInfo & user) +{ + return BoltLockMgr().GetUser(userIndex, user); +} + +bool emberAfPluginDoorLockSetUser(EndpointId endpointId, uint16_t userIndex, FabricIndex creator, FabricIndex modifier, + const CharSpan & userName, uint32_t uniqueId, UserStatusEnum userStatus, UserTypeEnum userType, + CredentialRuleEnum credentialRule, const CredentialStruct * credentials, size_t totalCredentials) +{ + return BoltLockMgr().SetUser(userIndex, creator, modifier, userName, uniqueId, userStatus, userType, credentialRule, + credentials, totalCredentials); +} + +bool emberAfPluginDoorLockGetCredential(EndpointId endpointId, uint16_t credentialIndex, CredentialTypeEnum credentialType, + EmberAfPluginDoorLockCredentialInfo & credential) +{ + return BoltLockMgr().GetCredential(credentialIndex, credentialType, credential); +} + +bool emberAfPluginDoorLockSetCredential(EndpointId endpointId, uint16_t credentialIndex, FabricIndex creator, FabricIndex modifier, + DlCredentialStatus credentialStatus, CredentialTypeEnum credentialType, + const ByteSpan & secret) +{ + return BoltLockMgr().SetCredential(credentialIndex, creator, modifier, credentialStatus, credentialType, secret); +} + +bool emberAfPluginDoorLockOnDoorLockCommand(EndpointId endpointId, const Optional & pinCode, OperationErrorEnum & err) +{ + bool result = BoltLockMgr().ValidatePIN(pinCode, err); + + /* Handle changing attribute state on command reception */ + if (result) + { + BoltLockMgr().Lock(BoltLockManager::OperationSource::kRemote); + } + + return result; +} + +bool emberAfPluginDoorLockOnDoorUnlockCommand(EndpointId endpointId, const Optional & pinCode, OperationErrorEnum & err) +{ + bool result = BoltLockMgr().ValidatePIN(pinCode, err); + + /* Handle changing attribute state on command reception */ + if (result) + { + BoltLockMgr().Unlock(BoltLockManager::OperationSource::kRemote); + } + + return result; +} + +void emberAfDoorLockClusterInitCallback(EndpointId endpoint) +{ + DoorLockServer::Instance().InitServer(endpoint); + + const auto logOnFailure = [](EmberAfStatus status, const char * attributeName) { + if (status != EMBER_ZCL_STATUS_SUCCESS) + { + ChipLogError(Zcl, "Failed to set DoorLock %s: %x", attributeName, status); + } + }; + + logOnFailure(DoorLock::Attributes::LockType::Set(endpoint, DlLockType::kDeadBolt), "type"); + logOnFailure(DoorLock::Attributes::NumberOfTotalUsersSupported::Set(endpoint, CONFIG_LOCK_NUM_USERS), "number of users"); + logOnFailure(DoorLock::Attributes::NumberOfPINUsersSupported::Set(endpoint, CONFIG_LOCK_NUM_USERS), "number of PIN users"); + logOnFailure(DoorLock::Attributes::NumberOfRFIDUsersSupported::Set(endpoint, 0), "number of RFID users"); + logOnFailure(DoorLock::Attributes::NumberOfCredentialsSupportedPerUser::Set(endpoint, CONFIG_LOCK_NUM_CREDENTIALS_PER_USER), + "number of credentials per user"); + + // Set FeatureMap to (kUser|kPinCredential), default is: + // (kUser|kAccessSchedules|kRfidCredential|kPinCredential) 0x113 + logOnFailure(DoorLock::Attributes::FeatureMap::Set(endpoint, 0x101), "feature map"); + + GetAppTask().UpdateClusterState(BoltLockMgr().GetState(), BoltLockManager::OperationSource::kUnspecified); +} diff --git a/examples/lock-app/telink/src/main.cpp b/examples/lock-app/telink/src/main.cpp new file mode 100644 index 00000000000000..71aa52f9d91901 --- /dev/null +++ b/examples/lock-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/lock-app/telink/third_party/connectedhomeip b/examples/lock-app/telink/third_party/connectedhomeip new file mode 120000 index 00000000000000..c866b86874994d --- /dev/null +++ b/examples/lock-app/telink/third_party/connectedhomeip @@ -0,0 +1 @@ +../../../.. \ No newline at end of file diff --git a/examples/pump-app/telink/.gitignore b/examples/pump-app/telink/.gitignore new file mode 100644 index 00000000000000..84c048a73cc2e5 --- /dev/null +++ b/examples/pump-app/telink/.gitignore @@ -0,0 +1 @@ +/build/ diff --git a/examples/pump-app/telink/CMakeLists.txt b/examples/pump-app/telink/CMakeLists.txt new file mode 100755 index 00000000000000..b1b9bac240e127 --- /dev/null +++ b/examples/pump-app/telink/CMakeLists.txt @@ -0,0 +1,64 @@ +# +# 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-pump-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}/pump-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/PumpManager.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}/../pump-common/pump-app.zap +) + +if(CONFIG_CHIP_OTA_REQUESTOR) + target_sources(app PRIVATE ${TELINK_COMMON}/util/src/OTAUtil.cpp) +endif() diff --git a/examples/pump-app/telink/README.md b/examples/pump-app/telink/README.md new file mode 100755 index 00000000000000..1281b6972ccdb6 --- /dev/null +++ b/examples/pump-app/telink/README.md @@ -0,0 +1,182 @@ +# Matter Telink Pump Controller Example Application + +The Telink Pump Controller Example demonstrates how to remotely control a pump +device. It uses buttons to test changing the pump state and device states and +LEDs to show the state of these changes. This example is inherited from the +"lock-app" example but modified to simulate a pump device and can be used as a +reference for creating your own pump 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 | Perform factory reset to forget currently commissioned Thread network and back to uncommissioned state | +| Button 2 | Lock control | Manually triggers the bolt lock 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) | + +#### Indicate current Pump state + +**White** LED shows current state of Pump (running/stopped) + +### 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 + ``` + +### 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/pump-app/telink/include/AppConfig.h b/examples/pump-app/telink/include/AppConfig.h new file mode 100755 index 00000000000000..9d54306cc0bb40 --- /dev/null +++ b/examples/pump-app/telink/include/AppConfig.h @@ -0,0 +1,37 @@ +/* + * + * 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 + +// ---- Pump Example App 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 PUMP_STATE_LED 6 +#define LIGHTING_PWM_SPEC_IDENTIFY_GREEN PWM_DT_SPEC_GET(DT_ALIAS(pwm_led3)) +// Time it takes in ms for the simulated pump to move from one state to another. +#define PUMP_START_PERIOS_MS 2000 diff --git a/examples/pump-app/telink/include/AppEvent.h b/examples/pump-app/telink/include/AppEvent.h new file mode 100644 index 00000000000000..3d9b6a28f56689 --- /dev/null +++ b/examples/pump-app/telink/include/AppEvent.h @@ -0,0 +1,66 @@ +/* + * + * 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, + kEventType_Install, + kEventType_Start, + }; + + uint16_t Type; + + union + { + struct + { + uint8_t Action; + } ButtonEvent; + struct + { + void * Context; + } TimerEvent; + struct + { + uint8_t Action; + int32_t Actor; + } StartEvent; + struct + { + LEDWidget * LedWidget; + } UpdateLedStateEvent; + }; + + EventHandler Handler; +}; diff --git a/examples/pump-app/telink/include/AppTask.h b/examples/pump-app/telink/include/AppTask.h new file mode 100644 index 00000000000000..a0ccc23a79dfcb --- /dev/null +++ b/examples/pump-app/telink/include/AppTask.h @@ -0,0 +1,95 @@ +/* + * + * 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 "AppEvent.h" +#include "PumpManager.h" +#if CONFIG_CHIP_ENABLE_APPLICATION_STATUS_LED +#include "LEDWidget.h" +#endif +#include "PWMDevice.h" + +#include + +#if CONFIG_CHIP_FACTORY_DATA +#include +#endif + +struct k_timer; +struct Identify; + +class AppTask +{ +public: + CHIP_ERROR StartApp(); + + static void PostStartActionRequest(int32_t actor, PumpManager::Action_t action); + void PostEvent(AppEvent * event); + void UpdateClusterState(); + static void IdentifyEffectHandler(EmberAfIdentifyEffectIdentifier aEffect); + +private: + friend AppTask & GetAppTask(void); + + CHIP_ERROR Init(void); + + static void ActionIdentifyStateUpdateHandler(k_timer * timer); + + static void ActionInitiated(PumpManager::Action_t action, int32_t actor); + static void ActionCompleted(PumpManager::Action_t action, int32_t actor); + + 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 StartActionButtonEventHandler(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 StartActionEventHandler(AppEvent * aEvent); + static void StartBleAdvHandler(AppEvent * aEvent); + static void UpdateIdentifyStateEventHandler(AppEvent * aEvent); + + static void LockActionEventHandler(AppEvent * event); + + static void InitButtons(void); + + static AppTask sAppTask; + PWMDevice mPwmIdentifyLed; + +#if CONFIG_CHIP_FACTORY_DATA + chip::DeviceLayer::FactoryDataProvider mFactoryDataProvider; +#endif +}; + +inline AppTask & GetAppTask(void) +{ + return AppTask::sAppTask; +} diff --git a/examples/pump-app/telink/include/CHIPProjectConfig.h b/examples/pump-app/telink/include/CHIPProjectConfig.h new file mode 100755 index 00000000000000..412932a59c3726 --- /dev/null +++ b/examples/pump-app/telink/include/CHIPProjectConfig.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. + */ + +/** + * @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 + +/** + * 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/pump-app/telink/include/PumpManager.h b/examples/pump-app/telink/include/PumpManager.h new file mode 100644 index 00000000000000..986e2605e9fa15 --- /dev/null +++ b/examples/pump-app/telink/include/PumpManager.h @@ -0,0 +1,99 @@ +/* + * + * Copyright (c) 2023 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +#include + +#include "AppEvent.h" + +struct k_timer; + +class PumpManager +{ +public: + enum Action_t + { + START_ACTION = 0, + STOP_ACTION, + + INVALID_ACTION + }; + + enum State_t + { + kState_StartInitiated = 0, + kState_StartCompleted, + kState_StopInitiated, + kState_StopCompleted, + }; + + void Init(); + bool IsStopped(); + void EnableAutoRestart(bool aOn); + void SetAutoStartDuration(uint32_t aDurationInSecs); + bool IsActionInProgress(); + bool InitiateAction(int32_t aActor, Action_t aAction); + + int16_t GetMaxPressure(); + uint16_t GetMaxSpeed(); + uint16_t GetMaxFlow(); + int16_t GetMinConstPressure(); + int16_t GetMaxConstPressure(); + int16_t GetMinCompPressure(); + int16_t GetMaxCompPressure(); + uint16_t GetMinConstSpeed(); + uint16_t GetMaxConstSpeed(); + uint16_t GetMinConstFlow(); + uint16_t GetMaxConstFlow(); + int16_t GetMinConstTemp(); + int16_t GetMaxConstTemp(); + + typedef void (*Callback_fn_initiated)(Action_t, int32_t aActor); + typedef void (*Callback_fn_completed)(Action_t, int32_t aActor); + void SetCallbacks(Callback_fn_initiated aActionInitiated_CB, Callback_fn_completed aActionCompleted_CB); + +private: + friend PumpManager & PumpMgr(void); + State_t mState; + + Callback_fn_initiated mActionInitiated_CB; + Callback_fn_completed mActionCompleted_CB; + + bool mAutoRestart; + uint32_t mAutoStartDuration; + bool mAutoStartTimerArmed; + int32_t mCurrentActor; + + void CancelTimer(void); + void StartTimer(uint32_t aTimeoutMs); + + static void TimerEventHandler(k_timer * timer); + static void AutoRestartTimerEventHandler(const AppEvent & aEvent); + + static void PumpStartTimerEventHandler(const AppEvent & aEvent); + + static PumpManager sPump; +}; + +inline PumpManager & PumpMgr(void) +{ + return PumpManager::sPump; +} diff --git a/examples/pump-app/telink/prj.conf b/examples/pump-app/telink/prj.conf new file mode 100755 index 00000000000000..93c255aacc7e76 --- /dev/null +++ b/examples/pump-app/telink/prj.conf @@ -0,0 +1,74 @@ +# +# 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=y +CONFIG_OPENTHREAD_FTD=n +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 pump-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 diff --git a/examples/pump-app/telink/src/AppTask.cpp b/examples/pump-app/telink/src/AppTask.cpp new file mode 100644 index 00000000000000..7d7080d82fc721 --- /dev/null +++ b/examples/pump-app/telink/src/AppTask.cpp @@ -0,0 +1,653 @@ +/* + * + * 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 "ButtonManager.h" +#include "PumpManager.h" + +#include "ThreadUtil.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if CONFIG_CHIP_OTA_REQUESTOR +#include "OTAUtil.h" +#endif + +#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 kPccClusterEndpoint = 1; +constexpr EndpointId kOnOffClusterEndpoint = 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; +uint8_t sFactoryResetCntr = 0; + +#if CONFIG_CHIP_ENABLE_APPLICATION_STATUS_LED +LEDWidget sStatusLED; +LEDWidget sPumpStateLED; +#endif + +Button sFactoryResetButton; +Button sPumpButton; +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 = { + kOnOffClusterEndpoint, + [](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 status LED +#if CONFIG_CHIP_ENABLE_APPLICATION_STATUS_LED + LEDWidget::InitGpio(LEDS_PORT); + LEDWidget::SetStateUpdateCallback(LEDStateUpdateHandler); + sStatusLED.Init(SYSTEM_STATE_LED); + + UpdateStatusLED(); + + sPumpStateLED.Init(PUMP_STATE_LED); + sPumpStateLED.Set(!PumpMgr().IsStopped()); +#endif + + InitButtons(); + + // Initialize function button timer + k_timer_init(&sFactoryResetTimer, &AppTask::FactoryResetTimerTimeoutCallback, nullptr); + k_timer_user_data_set(&sFactoryResetTimer, this); + + // Initialize PWM Identify led + 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); + + PumpMgr().Init(); + PumpMgr().SetCallbacks(ActionInitiated, ActionCompleted); + + // 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("mFactoryDataProvider.GetEnableKey() failed. Could not delegate a test event trigger"); + 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("Telink Pump"); + 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::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::StartActionButtonEventHandler(void) +{ + AppEvent event; + + event.Type = AppEvent::kEventType_Button; + event.ButtonEvent.Action = kButtonPushEvent; + event.Handler = StartActionEventHandler; + sAppTask.PostEvent(&event); +} + +void AppTask::StartActionEventHandler(AppEvent * aEvent) +{ + PumpManager::Action_t action = PumpManager::INVALID_ACTION; + int32_t actor = 0; + + if (aEvent->Type == AppEvent::kEventType_Start) + { + action = static_cast(aEvent->StartEvent.Action); + actor = aEvent->StartEvent.Actor; + } + else if (aEvent->Type == AppEvent::kEventType_Button) + { + action = PumpMgr().IsStopped() ? PumpManager::START_ACTION : PumpManager::STOP_ACTION; + actor = static_cast(AppEvent::kEventType_Button); + } + + if (action != PumpManager::INVALID_ACTION && !PumpMgr().InitiateAction(actor, action)) + LOG_INF("Action is already in progress or active."); +} + +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(PumpManager::Action_t action, int32_t actor) +{ + // If the action has been initiated by the pump, update the pump trait + // and start flashing the LEDs rapidly to indicate action initiation. + if (action == PumpManager::START_ACTION) + { + LOG_INF("Pump Start Action has been initiated"); + } + else if (action == PumpManager::STOP_ACTION) + { + LOG_INF("Pump Stop Action has been initiated"); + } + + sPumpStateLED.Blink(50, 50); +} + +void AppTask::ActionCompleted(PumpManager::Action_t action, int32_t actor) +{ + // If the action has been completed by the pump, update the pump trait. + // Turn on the pump state LED if in a STARTED state OR + // Turn off the pump state LED if in a STOPPED state. + if (action == PumpManager::START_ACTION) + { + LOG_INF("Pump Start Action has been completed"); + sPumpStateLED.Set(true); + } + else if (action == PumpManager::STOP_ACTION) + { + LOG_INF("Pump Stop Action has been completed"); + sPumpStateLED.Set(false); + } + + if (actor == static_cast(AppEvent::kEventType_Button)) + { + GetAppTask().UpdateClusterState(); + } +} + +void AppTask::PostStartActionRequest(int32_t actor, PumpManager::Action_t action) +{ + AppEvent event; + event.Type = AppEvent::kEventType_Start; + event.StartEvent.Actor = actor; + event.StartEvent.Action = action; + event.Handler = StartActionEventHandler; + sAppTask.PostEvent(&event); +} + +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() +{ + EmberStatus status; + + ChipLogProgress(NotSpecified, "UpdateClusterState"); + + // Write the new values + + bool onOffState = !PumpMgr().IsStopped(); + + status = Clusters::OnOff::Attributes::OnOff::Set(kOnOffClusterEndpoint, onOffState); + if (status != EMBER_ZCL_STATUS_SUCCESS) + { + ChipLogError(NotSpecified, "ERR: Updating On/Off state %x", status); + } + + int16_t maxPressure = PumpMgr().GetMaxPressure(); + + uint16_t maxSpeed = PumpMgr().GetMaxSpeed(); + + uint16_t maxFlow = PumpMgr().GetMaxFlow(); + + int16_t minConstPress = PumpMgr().GetMinConstPressure(); + + int16_t maxConstPress = PumpMgr().GetMaxConstPressure(); + + int16_t minCompPress = PumpMgr().GetMinCompPressure(); + + int16_t maxCompPress = PumpMgr().GetMaxCompPressure(); + + uint16_t minConstSpeed = PumpMgr().GetMinConstSpeed(); + + uint16_t maxConstSpeed = PumpMgr().GetMaxConstSpeed(); + + uint16_t minConstFlow = PumpMgr().GetMinConstFlow(); + + uint16_t maxConstFlow = PumpMgr().GetMaxConstFlow(); + + int16_t minConstTemp = PumpMgr().GetMinConstTemp(); + + int16_t maxConstTemp = PumpMgr().GetMaxConstTemp(); +} + +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); + sPumpButton.Configure(BUTTON_PORT, BUTTON_PIN_2, StartActionButtonEventHandler); + 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); + sPumpButton.Configure(BUTTON_PORT, BUTTON_PIN_4, BUTTON_PIN_1, StartActionButtonEventHandler); + 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(sPumpButton); + ButtonManagerInst().AddButton(sThreadStartButton); + ButtonManagerInst().AddButton(sBleAdvStartButton); +} diff --git a/examples/pump-app/telink/src/PumpManager.cpp b/examples/pump-app/telink/src/PumpManager.cpp new file mode 100644 index 00000000000000..3e8654a1d8efa5 --- /dev/null +++ b/examples/pump-app/telink/src/PumpManager.cpp @@ -0,0 +1,318 @@ +/* + * + * 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 "PumpManager.h" + +#include "AppConfig.h" +#include "AppTask.h" + +#include +#include + +LOG_MODULE_DECLARE(app, CONFIG_CHIP_APP_LOG_LEVEL); + +static k_timer sStartTimer; + +PumpManager PumpManager::sPump; + +void PumpManager::Init() +{ + k_timer_init(&sStartTimer, &PumpManager::TimerEventHandler, nullptr); + k_timer_user_data_set(&sStartTimer, this); + + mState = kState_StartCompleted; + mAutoStartTimerArmed = false; + mAutoRestart = false; + mAutoStartDuration = 0; +} + +void PumpManager::SetCallbacks(Callback_fn_initiated aActionInitiated_CB, Callback_fn_completed aActionCompleted_CB) +{ + mActionInitiated_CB = aActionInitiated_CB; + mActionCompleted_CB = aActionCompleted_CB; +} + +bool PumpManager::IsActionInProgress() +{ + return (mState == kState_StartInitiated || mState == kState_StartInitiated) ? true : false; +} + +bool PumpManager::IsStopped() +{ + return (mState == kState_StopCompleted) ? true : false; +} + +void PumpManager::EnableAutoRestart(bool aOn) +{ + mAutoRestart = aOn; +} + +void PumpManager::SetAutoStartDuration(uint32_t aDurationInSecs) +{ + mAutoStartDuration = aDurationInSecs; +} + +bool PumpManager::InitiateAction(int32_t aActor, Action_t aAction) +{ + bool action_initiated = false; + State_t new_state; + + // Initiate Start/Stop Action only when the previous one is complete. + if (mState == kState_StartCompleted && aAction == STOP_ACTION) + { + action_initiated = true; + mCurrentActor = aActor; + new_state = kState_StopInitiated; + } + else if (mState == kState_StopCompleted && aAction == START_ACTION) + { + action_initiated = true; + mCurrentActor = aActor; + new_state = kState_StartInitiated; + } + + if (action_initiated) + { + if (mAutoStartTimerArmed && new_state == kState_StartInitiated) + { + // If auto start timer has been armed and someone initiates stop, + // cancel the timer and continue as normal. + mAutoStartTimerArmed = false; + + CancelTimer(); + } + + StartTimer(PUMP_START_PERIOS_MS); + + // Since the timer started successfully, update the state and trigger callback + mState = new_state; + + if (mActionInitiated_CB) + { + mActionInitiated_CB(aAction, aActor); + } + } + + return action_initiated; +} + +void PumpManager::StartTimer(uint32_t aTimeoutMs) +{ + k_timer_start(&sStartTimer, K_MSEC(aTimeoutMs), K_NO_WAIT); +} + +void PumpManager::CancelTimer(void) +{ + k_timer_stop(&sStartTimer); +} + +void PumpManager::TimerEventHandler(k_timer * timer) +{ + PumpManager * pump = static_cast(k_timer_user_data_get(timer)); + + // The timer event handler will be called in the context of the timer task + // once sStartTimer expires. Post an event to apptask queue with the actual handler + // so that the event can be handled in the context of the apptask. + AppEvent event; + event.Type = AppEvent::kEventType_Timer; + event.TimerEvent.Context = pump; + event.Handler = pump->mAutoStartTimerArmed ? AutoRestartTimerEventHandler : PumpStartTimerEventHandler; + GetAppTask().PostEvent(&event); +} + +void PumpManager::AutoRestartTimerEventHandler(const AppEvent & aEvent) +{ + PumpManager * pump = static_cast(aEvent.TimerEvent.Context); + int32_t actor = 0; + + // Make sure auto start timer is still armed. + if (!pump->mAutoStartTimerArmed) + return; + + pump->mAutoStartTimerArmed = false; + + LOG_INF("Auto Re-Start has been triggered!"); + + pump->InitiateAction(actor, START_ACTION); +} + +void PumpManager::PumpStartTimerEventHandler(const AppEvent & aEvent) +{ + Action_t actionCompleted = INVALID_ACTION; + + PumpManager * pump = static_cast(aEvent.TimerEvent.Context); + + if (pump->mState == kState_StartInitiated) + { + pump->mState = kState_StartCompleted; + actionCompleted = START_ACTION; + } + else if (pump->mState == kState_StopInitiated) + { + pump->mState = kState_StopCompleted; + actionCompleted = STOP_ACTION; + } + + if (actionCompleted != INVALID_ACTION) + { + if (pump->mActionCompleted_CB) + { + pump->mActionCompleted_CB(actionCompleted, pump->mCurrentActor); + } + + if (pump->mAutoRestart && actionCompleted == START_ACTION) + { + // Start the timer for auto restart + pump->StartTimer(pump->mAutoStartDuration * 1000); + + pump->mAutoStartTimerArmed = true; + + LOG_INF("Auto Re-start enabled. Will be triggered in %u seconds", pump->mAutoStartDuration); + } + } +} + +int16_t PumpManager::GetMaxPressure() +{ + // 1.6.1. MaxPressure Attribute + // Range -3276.7 kPa to 3276.7 kPa (steps of 0.1 kPa) + // -3276.8 is invalid value - perhaps 'null' + + // Return 2000.0 kPa as Max Pressure + return 20000; +} + +uint16_t PumpManager::GetMaxSpeed() +{ + // 1.6.2. MaxSpeed Attribute + // Range 0 RPM to 65534 RPM (steps of 1 RPM) + // 65535 is invalid value - perhaps 'null' + + // Return 1000 RPM as MaxSpeed + return 1000; +} + +uint16_t PumpManager::GetMaxFlow() +{ + // 1.6.3. MaxFlow Attribute + // Range 0 m3/h to 6553.4 m3/h (steps of 0.1 m3/h) + // 6553.5 m3/h is invalid value - perhaps 'null' + + // Return 200.0 m3/h as MaxFlow + return 2000; +} + +int16_t PumpManager::GetMinConstPressure() +{ + // 1.6.4. MinConstPressure Attribute + // Range -3276.7 kPa to 3276.7 kPa (steps of 0.1 kPa) + // -3276.8 is invalid value - perhaps 'null' + + // Return -100.0 kPa as MinConstPressure + return -1000; +} + +int16_t PumpManager::GetMaxConstPressure() +{ + // 1.6.5. MaxConstPressure Attribute + // Range -3276.7 kPa to 3276.7 kPa (steps of 0.1 kPa) + // -3276.8 is invalid value - perhaps 'null' + + // Return 100.0 kPa as MaxConstPressure + return 1000; +} + +int16_t PumpManager::GetMinCompPressure() +{ + // 1.6.6. MinCompPressure Attribute + // Range -3276.7 kPa to 3276.7 kPa (steps of 0.1 kPa) + // -3276.8 is invalid value - perhaps 'null' + + // Return -20.0 kPa as MinCompPressure + return -200; +} + +int16_t PumpManager::GetMaxCompPressure() +{ + // 1.6.7. MaxCompPressure Attribute + // Range -3276.7 kPa to 3276.7 kPa (steps of 0.1 kPa) + // -3276.8 is invalid value - perhaps 'null' + + // Return 20.0 kPa as MaxCompPressure + return 200; +} + +uint16_t PumpManager::GetMinConstSpeed() +{ + // 1.6.8. MinConstSpeed Attribute + // Range 0 to 65534 RPM (steps of 1 RPM) + // 65535 RPM is invalid valud - perhaps 'null' + + // Return 200 RPM as MinConstSpeed + return 200; +} + +uint16_t PumpManager::GetMaxConstSpeed() +{ + // 1.6.9. MaxConstSpeed Attribute + // Range 0 to 65534 RPM (steps of 1 RPM) + // 65535 RPM is invalid valud - perhaps 'null' + + // Return 2000 RPM as MaxConstSpeed + return 2000; +} + +uint16_t PumpManager::GetMinConstFlow() +{ + // 1.6.10. MinConstFlow Attribute + // Range 0 m3/h to 6553.4 m3/h (steps of 0.1 m3/h) + // 6553.5 m3/h is invalid value - perhaps 'null' + + // Return 12.5 m3/h as MinConstFlow + return 125; +} + +uint16_t PumpManager::GetMaxConstFlow() +{ + // 1.6.11. MaxConstFlow Attribute + // Range 0 m3/h to 6553.4 m3/h (steps of 0.1 m3/h) + // 6553.5 m3/h is invalid value - perhaps 'null' + + // Return 655.7 m3/h as MaxConstFlow + return 6557; +} + +int16_t PumpManager::GetMinConstTemp() +{ + // 1.6.12. MinConstTemp Attribute + // Range -273.15 C to 327.67 C (steps of 0.01 C) + // All other values are invalid values - perhaps 'null' + + // Return 30.00 C as MinConstTemp + return 3000; +} + +int16_t PumpManager::GetMaxConstTemp() +{ + // 1.6.13. MaxConstTemp Attribute + // Range -273.15 C to 327.67 C (steps of 0.01 C) + // All other values are invalid values - perhaps 'null' + + // Return 56.00 C as MaxConstTemp + return 5600; +} diff --git a/examples/pump-app/telink/src/main.cpp b/examples/pump-app/telink/src/main.cpp new file mode 100644 index 00000000000000..71aa52f9d91901 --- /dev/null +++ b/examples/pump-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/pump-app/telink/third_party/connectedhomeip b/examples/pump-app/telink/third_party/connectedhomeip new file mode 120000 index 00000000000000..c866b86874994d --- /dev/null +++ b/examples/pump-app/telink/third_party/connectedhomeip @@ -0,0 +1 @@ +../../../.. \ No newline at end of file diff --git a/examples/pump-controller-app/telink/.gitignore b/examples/pump-controller-app/telink/.gitignore new file mode 100644 index 00000000000000..84c048a73cc2e5 --- /dev/null +++ b/examples/pump-controller-app/telink/.gitignore @@ -0,0 +1 @@ +/build/ diff --git a/examples/pump-controller-app/telink/CMakeLists.txt b/examples/pump-controller-app/telink/CMakeLists.txt new file mode 100755 index 00000000000000..55e6adedf49a87 --- /dev/null +++ b/examples/pump-controller-app/telink/CMakeLists.txt @@ -0,0 +1,64 @@ +# +# 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-pump-controller-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}/pump-controller-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/PumpManager.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}/../pump-controller-common/pump-controller-app.zap +) + +if(CONFIG_CHIP_OTA_REQUESTOR) + target_sources(app PRIVATE ${TELINK_COMMON}/util/src/OTAUtil.cpp) +endif() diff --git a/examples/pump-controller-app/telink/README.md b/examples/pump-controller-app/telink/README.md new file mode 100755 index 00000000000000..3ea6b0413e241b --- /dev/null +++ b/examples/pump-controller-app/telink/README.md @@ -0,0 +1,183 @@ +# Matter Telink Pump Controller Example Application + +The Telink Pump Controller Example demonstrates how to implement a pump +controller client device with basic start/stop functionality. It uses buttons to +test changing the pump state and device states and LEDs to show the state of +these changes. This example is inherited from the "lock-app" example but +modified to simulate a pump device and can be used as a reference for creating +your own pump 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 | Perform factory reset to forget currently commissioned Thread network and back to uncommissioned state | +| Button 2 | Lock control | Manually triggers the bolt lock 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) | + +#### Indicate current Pump state + +**White** LED shows current state of Pump (running/stopped) + +### 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 + ``` + +### 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/pump-controller-app/telink/include/AppConfig.h b/examples/pump-controller-app/telink/include/AppConfig.h new file mode 100755 index 00000000000000..70ee2c5ca0f425 --- /dev/null +++ b/examples/pump-controller-app/telink/include/AppConfig.h @@ -0,0 +1,37 @@ +/* + * + * 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 + +// ---- Pump Controller Example App 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 PUMP_STATE_LED 6 +#define LIGHTING_PWM_SPEC_IDENTIFY_GREEN PWM_DT_SPEC_GET(DT_ALIAS(pwm_led3)) +// Time it takes in ms for the simulated pump to move from one state to another. +#define PUMP_START_PERIOS_MS 2000 diff --git a/examples/pump-controller-app/telink/include/AppEvent.h b/examples/pump-controller-app/telink/include/AppEvent.h new file mode 100644 index 00000000000000..3d9b6a28f56689 --- /dev/null +++ b/examples/pump-controller-app/telink/include/AppEvent.h @@ -0,0 +1,66 @@ +/* + * + * 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, + kEventType_Install, + kEventType_Start, + }; + + uint16_t Type; + + union + { + struct + { + uint8_t Action; + } ButtonEvent; + struct + { + void * Context; + } TimerEvent; + struct + { + uint8_t Action; + int32_t Actor; + } StartEvent; + struct + { + LEDWidget * LedWidget; + } UpdateLedStateEvent; + }; + + EventHandler Handler; +}; diff --git a/examples/pump-controller-app/telink/include/AppTask.h b/examples/pump-controller-app/telink/include/AppTask.h new file mode 100644 index 00000000000000..a0ccc23a79dfcb --- /dev/null +++ b/examples/pump-controller-app/telink/include/AppTask.h @@ -0,0 +1,95 @@ +/* + * + * 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 "AppEvent.h" +#include "PumpManager.h" +#if CONFIG_CHIP_ENABLE_APPLICATION_STATUS_LED +#include "LEDWidget.h" +#endif +#include "PWMDevice.h" + +#include + +#if CONFIG_CHIP_FACTORY_DATA +#include +#endif + +struct k_timer; +struct Identify; + +class AppTask +{ +public: + CHIP_ERROR StartApp(); + + static void PostStartActionRequest(int32_t actor, PumpManager::Action_t action); + void PostEvent(AppEvent * event); + void UpdateClusterState(); + static void IdentifyEffectHandler(EmberAfIdentifyEffectIdentifier aEffect); + +private: + friend AppTask & GetAppTask(void); + + CHIP_ERROR Init(void); + + static void ActionIdentifyStateUpdateHandler(k_timer * timer); + + static void ActionInitiated(PumpManager::Action_t action, int32_t actor); + static void ActionCompleted(PumpManager::Action_t action, int32_t actor); + + 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 StartActionButtonEventHandler(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 StartActionEventHandler(AppEvent * aEvent); + static void StartBleAdvHandler(AppEvent * aEvent); + static void UpdateIdentifyStateEventHandler(AppEvent * aEvent); + + static void LockActionEventHandler(AppEvent * event); + + static void InitButtons(void); + + static AppTask sAppTask; + PWMDevice mPwmIdentifyLed; + +#if CONFIG_CHIP_FACTORY_DATA + chip::DeviceLayer::FactoryDataProvider mFactoryDataProvider; +#endif +}; + +inline AppTask & GetAppTask(void) +{ + return AppTask::sAppTask; +} diff --git a/examples/pump-controller-app/telink/include/CHIPProjectConfig.h b/examples/pump-controller-app/telink/include/CHIPProjectConfig.h new file mode 100755 index 00000000000000..412932a59c3726 --- /dev/null +++ b/examples/pump-controller-app/telink/include/CHIPProjectConfig.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. + */ + +/** + * @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 + +/** + * 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/pump-controller-app/telink/include/PumpManager.h b/examples/pump-controller-app/telink/include/PumpManager.h new file mode 100644 index 00000000000000..11c7b0886bd7b3 --- /dev/null +++ b/examples/pump-controller-app/telink/include/PumpManager.h @@ -0,0 +1,85 @@ +/* + * + * Copyright (c) 2023 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +#include + +#include "AppEvent.h" + +struct k_timer; + +class PumpManager +{ +public: + enum Action_t + { + START_ACTION = 0, + STOP_ACTION, + + INVALID_ACTION + }; + + enum State_t + { + kState_StartInitiated = 0, + kState_StartCompleted, + kState_StopInitiated, + kState_StopCompleted, + }; + + void Init(); + bool IsStopped(); + void EnableAutoRestart(bool aOn); + void SetAutoStartDuration(uint32_t aDurationInSecs); + bool IsActionInProgress(); + bool InitiateAction(int32_t aActor, Action_t aAction); + + typedef void (*Callback_fn_initiated)(Action_t, int32_t aActor); + typedef void (*Callback_fn_completed)(Action_t, int32_t aActor); + void SetCallbacks(Callback_fn_initiated aActionInitiated_CB, Callback_fn_completed aActionCompleted_CB); + +private: + friend PumpManager & PumpMgr(void); + State_t mState; + + Callback_fn_initiated mActionInitiated_CB; + Callback_fn_completed mActionCompleted_CB; + + bool mAutoRestart; + uint32_t mAutoStartDuration; + bool mAutoStartTimerArmed; + int32_t mCurrentActor; + + void CancelTimer(void); + void StartTimer(uint32_t aTimeoutMs); + + static void TimerEventHandler(k_timer * timer); + static void AutoRestartTimerEventHandler(const AppEvent & aEvent); + + static void PumpStartTimerEventHandler(const AppEvent & aEvent); + + static PumpManager sPump; +}; + +inline PumpManager & PumpMgr(void) +{ + return PumpManager::sPump; +} diff --git a/examples/pump-controller-app/telink/prj.conf b/examples/pump-controller-app/telink/prj.conf new file mode 100755 index 00000000000000..60a7f8ef53ed99 --- /dev/null +++ b/examples/pump-controller-app/telink/prj.conf @@ -0,0 +1,74 @@ +# +# 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=y +CONFIG_OPENTHREAD_FTD=n +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 +# 32785 == 0x8011 (example pump-controller-app) +CONFIG_CHIP_DEVICE_PRODUCT_ID=32785 +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 diff --git a/examples/pump-controller-app/telink/src/AppTask.cpp b/examples/pump-controller-app/telink/src/AppTask.cpp new file mode 100644 index 00000000000000..80dd632d2038f8 --- /dev/null +++ b/examples/pump-controller-app/telink/src/AppTask.cpp @@ -0,0 +1,612 @@ +/* + * + * 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 "ButtonManager.h" +#include "PumpManager.h" + +#include "ThreadUtil.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if CONFIG_CHIP_OTA_REQUESTOR +#include "OTAUtil.h" +#endif + +#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 kPccClusterEndpoint = 1; +constexpr EndpointId kOnOffClusterEndpoint = 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; +uint8_t sFactoryResetCntr = 0; + +#if CONFIG_CHIP_ENABLE_APPLICATION_STATUS_LED +LEDWidget sStatusLED; +LEDWidget sPumpStateLED; +#endif + +Button sFactoryResetButton; +Button sPumpButton; +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 = { + kOnOffClusterEndpoint, + [](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 status LED +#if CONFIG_CHIP_ENABLE_APPLICATION_STATUS_LED + LEDWidget::InitGpio(LEDS_PORT); + LEDWidget::SetStateUpdateCallback(LEDStateUpdateHandler); + sStatusLED.Init(SYSTEM_STATE_LED); + + UpdateStatusLED(); + + sPumpStateLED.Init(PUMP_STATE_LED); + sPumpStateLED.Set(!PumpMgr().IsStopped()); +#endif + + InitButtons(); + + // Initialize function button timer + k_timer_init(&sFactoryResetTimer, &AppTask::FactoryResetTimerTimeoutCallback, nullptr); + k_timer_user_data_set(&sFactoryResetTimer, this); + + // Initialize PWM Identify led + 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); + + PumpMgr().Init(); + PumpMgr().SetCallbacks(ActionInitiated, ActionCompleted); + + // 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("mFactoryDataProvider.GetEnableKey() failed. Could not delegate a test event trigger"); + 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("Telink Pump Controller"); + 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::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::StartActionButtonEventHandler(void) +{ + AppEvent event; + + event.Type = AppEvent::kEventType_Button; + event.ButtonEvent.Action = kButtonPushEvent; + event.Handler = StartActionEventHandler; + sAppTask.PostEvent(&event); +} + +void AppTask::StartActionEventHandler(AppEvent * aEvent) +{ + PumpManager::Action_t action = PumpManager::INVALID_ACTION; + int32_t actor = 0; + + if (aEvent->Type == AppEvent::kEventType_Start) + { + action = static_cast(aEvent->StartEvent.Action); + actor = aEvent->StartEvent.Actor; + } + else if (aEvent->Type == AppEvent::kEventType_Button) + { + action = PumpMgr().IsStopped() ? PumpManager::START_ACTION : PumpManager::STOP_ACTION; + actor = static_cast(AppEvent::kEventType_Button); + } + + if (action != PumpManager::INVALID_ACTION && !PumpMgr().InitiateAction(actor, action)) + LOG_INF("Action is already in progress or active."); +} + +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(PumpManager::Action_t action, int32_t actor) +{ + // If the action has been initiated by the pump, update the pump trait + // and start flashing the LEDs rapidly to indicate action initiation. + if (action == PumpManager::START_ACTION) + { + LOG_INF("Pump Start Action has been initiated"); + } + else if (action == PumpManager::STOP_ACTION) + { + LOG_INF("Pump Stop Action has been initiated"); + } + + sPumpStateLED.Blink(50, 50); +} + +void AppTask::ActionCompleted(PumpManager::Action_t action, int32_t actor) +{ + // If the action has been completed by the pump, update the pump trait. + // Turn on the pump state LED if in a STARTED state OR + // Turn off the pump state LED if in a STOPPED state. + if (action == PumpManager::START_ACTION) + { + LOG_INF("Pump Start Action has been completed"); + sPumpStateLED.Set(true); + } + else if (action == PumpManager::STOP_ACTION) + { + LOG_INF("Pump Stop Action has been completed"); + sPumpStateLED.Set(false); + } + + if (actor == static_cast(AppEvent::kEventType_Button)) + { + GetAppTask().UpdateClusterState(); + } +} + +void AppTask::PostStartActionRequest(int32_t actor, PumpManager::Action_t action) +{ + AppEvent event; + event.Type = AppEvent::kEventType_Start; + event.StartEvent.Actor = actor; + event.StartEvent.Action = action; + event.Handler = StartActionEventHandler; + sAppTask.PostEvent(&event); +} + +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 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); + sPumpButton.Configure(BUTTON_PORT, BUTTON_PIN_2, StartActionButtonEventHandler); + 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); + sPumpButton.Configure(BUTTON_PORT, BUTTON_PIN_4, BUTTON_PIN_1, StartActionButtonEventHandler); + 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(sPumpButton); + ButtonManagerInst().AddButton(sThreadStartButton); + ButtonManagerInst().AddButton(sBleAdvStartButton); +} diff --git a/examples/pump-controller-app/telink/src/PumpManager.cpp b/examples/pump-controller-app/telink/src/PumpManager.cpp new file mode 100644 index 00000000000000..18b7e655cabaf7 --- /dev/null +++ b/examples/pump-controller-app/telink/src/PumpManager.cpp @@ -0,0 +1,188 @@ +/* + * + * 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 "PumpManager.h" + +#include "AppConfig.h" +#include "AppTask.h" + +#include +#include + +LOG_MODULE_DECLARE(app, CONFIG_CHIP_APP_LOG_LEVEL); + +static k_timer sStartTimer; + +PumpManager PumpManager::sPump; + +void PumpManager::Init() +{ + k_timer_init(&sStartTimer, &PumpManager::TimerEventHandler, nullptr); + k_timer_user_data_set(&sStartTimer, this); + + mState = kState_StartCompleted; + mAutoStartTimerArmed = false; + mAutoRestart = false; + mAutoStartDuration = 0; +} + +void PumpManager::SetCallbacks(Callback_fn_initiated aActionInitiated_CB, Callback_fn_completed aActionCompleted_CB) +{ + mActionInitiated_CB = aActionInitiated_CB; + mActionCompleted_CB = aActionCompleted_CB; +} + +bool PumpManager::IsActionInProgress() +{ + return (mState == kState_StartInitiated || mState == kState_StartInitiated) ? true : false; +} + +bool PumpManager::IsStopped() +{ + return (mState == kState_StopCompleted) ? true : false; +} + +void PumpManager::EnableAutoRestart(bool aOn) +{ + mAutoRestart = aOn; +} + +void PumpManager::SetAutoStartDuration(uint32_t aDurationInSecs) +{ + mAutoStartDuration = aDurationInSecs; +} + +bool PumpManager::InitiateAction(int32_t aActor, Action_t aAction) +{ + bool action_initiated = false; + State_t new_state; + + // Initiate Start/Stop Action only when the previous one is complete. + if (mState == kState_StartCompleted && aAction == STOP_ACTION) + { + action_initiated = true; + mCurrentActor = aActor; + new_state = kState_StopInitiated; + } + else if (mState == kState_StopCompleted && aAction == START_ACTION) + { + action_initiated = true; + mCurrentActor = aActor; + new_state = kState_StartInitiated; + } + + if (action_initiated) + { + if (mAutoStartTimerArmed && new_state == kState_StartInitiated) + { + // If auto start timer has been armed and someone initiates stop, + // cancel the timer and continue as normal. + mAutoStartTimerArmed = false; + + CancelTimer(); + } + + StartTimer(PUMP_START_PERIOS_MS); + + // Since the timer started successfully, update the state and trigger callback + mState = new_state; + + if (mActionInitiated_CB) + { + mActionInitiated_CB(aAction, aActor); + } + } + + return action_initiated; +} + +void PumpManager::StartTimer(uint32_t aTimeoutMs) +{ + k_timer_start(&sStartTimer, K_MSEC(aTimeoutMs), K_NO_WAIT); +} + +void PumpManager::CancelTimer(void) +{ + k_timer_stop(&sStartTimer); +} + +void PumpManager::TimerEventHandler(k_timer * timer) +{ + PumpManager * pump = static_cast(k_timer_user_data_get(timer)); + + // The timer event handler will be called in the context of the timer task + // once sStartTimer expires. Post an event to apptask queue with the actual handler + // so that the event can be handled in the context of the apptask. + AppEvent event; + event.Type = AppEvent::kEventType_Timer; + event.TimerEvent.Context = pump; + event.Handler = pump->mAutoStartTimerArmed ? AutoRestartTimerEventHandler : PumpStartTimerEventHandler; + GetAppTask().PostEvent(&event); +} + +void PumpManager::AutoRestartTimerEventHandler(const AppEvent & aEvent) +{ + PumpManager * pump = static_cast(aEvent.TimerEvent.Context); + int32_t actor = 0; + + // Make sure auto start timer is still armed. + if (!pump->mAutoStartTimerArmed) + return; + + pump->mAutoStartTimerArmed = false; + + LOG_INF("Auto Re-Start has been triggered!"); + + pump->InitiateAction(actor, START_ACTION); +} + +void PumpManager::PumpStartTimerEventHandler(const AppEvent & aEvent) +{ + Action_t actionCompleted = INVALID_ACTION; + + PumpManager * pump = static_cast(aEvent.TimerEvent.Context); + + if (pump->mState == kState_StartInitiated) + { + pump->mState = kState_StartCompleted; + actionCompleted = START_ACTION; + } + else if (pump->mState == kState_StopInitiated) + { + pump->mState = kState_StopCompleted; + actionCompleted = STOP_ACTION; + } + + if (actionCompleted != INVALID_ACTION) + { + if (pump->mActionCompleted_CB) + { + pump->mActionCompleted_CB(actionCompleted, pump->mCurrentActor); + } + + if (pump->mAutoRestart && actionCompleted == START_ACTION) + { + // Start the timer for auto restart + pump->StartTimer(pump->mAutoStartDuration * 1000); + + pump->mAutoStartTimerArmed = true; + + LOG_INF("Auto Re-start enabled. Will be triggered in %u seconds", pump->mAutoStartDuration); + } + } +} diff --git a/examples/pump-controller-app/telink/src/main.cpp b/examples/pump-controller-app/telink/src/main.cpp new file mode 100644 index 00000000000000..71aa52f9d91901 --- /dev/null +++ b/examples/pump-controller-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/pump-controller-app/telink/third_party/connectedhomeip b/examples/pump-controller-app/telink/third_party/connectedhomeip new file mode 120000 index 00000000000000..c866b86874994d --- /dev/null +++ b/examples/pump-controller-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 b45e467d284745..dbaae497aa0aee 100755 --- a/scripts/build/build/targets.py +++ b/scripts/build/build/targets.py @@ -552,7 +552,10 @@ def BuildTelinkTarget(): TargetPart('contact-sensor', app=TelinkApp.CONTACT_SENSOR), TargetPart('light', app=TelinkApp.LIGHT), TargetPart('light-switch', app=TelinkApp.SWITCH), + TargetPart('lock', app=TelinkApp.LOCK), TargetPart('ota-requestor', app=TelinkApp.OTA_REQUESTOR), + TargetPart('pump', app=TelinkApp.PUMP), + TargetPart('pump-controller', app=TelinkApp.PUMP_CONTROLLER), TargetPart('thermostat', app=TelinkApp.THERMOSTAT), ]) diff --git a/scripts/build/builders/telink.py b/scripts/build/builders/telink.py index 5be30cb72ceb80..e86c3712b9f185 100644 --- a/scripts/build/builders/telink.py +++ b/scripts/build/builders/telink.py @@ -26,7 +26,10 @@ class TelinkApp(Enum): CONTACT_SENSOR = auto() LIGHT = auto() SWITCH = auto() + LOCK = auto() OTA_REQUESTOR = auto() + PUMP = auto() + PUMP_CONTROLLER = auto() THERMOSTAT = auto() def ExampleName(self): @@ -40,8 +43,14 @@ def ExampleName(self): return 'lighting-app' elif self == TelinkApp.SWITCH: return 'light-switch-app' + elif self == TelinkApp.LOCK: + return 'lock-app' elif self == TelinkApp.OTA_REQUESTOR: return 'ota-requestor-app' + elif self == TelinkApp.PUMP: + return 'pump-app' + elif self == TelinkApp.PUMP_CONTROLLER: + return 'pump-controller-app' elif self == TelinkApp.THERMOSTAT: return 'thermostat' else: @@ -58,8 +67,14 @@ def AppNamePrefix(self): return 'chip-telink-lighting-example' elif self == TelinkApp.SWITCH: return 'chip-telink-light-switch-example' + elif self == TelinkApp.LOCK: + return 'chip-telink-lock-example' elif self == TelinkApp.OTA_REQUESTOR: return 'chip-telink-ota-requestor-example' + elif self == TelinkApp.PUMP: + return 'chip-telink-pump-example' + elif self == TelinkApp.PUMP_CONTROLLER: + return 'chip-telink-pump-controller-example' elif self == TelinkApp.THERMOSTAT: return 'chip-telink-thermostat-example' else: diff --git a/scripts/build/testdata/all_targets_linux_x64.txt b/scripts/build/testdata/all_targets_linux_x64.txt index 995219379393bc..4c89f3fb3a0bf3 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}[-no-ble][-no-wifi][-asan][-ubsan] -telink-tlsr9518adk80d-{all-clusters,all-clusters-minimal,contact-sensor,light,light-switch,ota-requestor,thermostat} +telink-tlsr9518adk80d-{all-clusters,all-clusters-minimal,contact-sensor,light,light-switch,lock,ota-requestor,pump,pump-controller,thermostat} openiotsdk-{shell,lock}