diff --git a/examples/lock-app/telink/CMakeLists.txt b/examples/lock-app/telink/CMakeLists.txt index 10771bd371c76e..a5f48c8a1a6bda 100755 --- a/examples/lock-app/telink/CMakeLists.txt +++ b/examples/lock-app/telink/CMakeLists.txt @@ -76,7 +76,7 @@ add_definitions( target_sources(app PRIVATE src/AppTask.cpp src/ZclCallbacks.cpp - src/BoltLockManager.cpp + src/LockManager.cpp ${TELINK_COMMON}/common/src/mainCommon.cpp ${TELINK_COMMON}/common/src/AppTaskCommon.cpp ${TELINK_COMMON}/util/src/LEDWidget.cpp diff --git a/examples/lock-app/telink/README.md b/examples/lock-app/telink/README.md index 4d20388ce4277a..9cca961b6262f6 100755 --- a/examples/lock-app/telink/README.md +++ b/examples/lock-app/telink/README.md @@ -24,7 +24,7 @@ a reference for creating your own application. 2. Activate the build environment: ```bash - $ source ./scripts/activate.sh -p all,telink + $ source ./scripts/activate.sh ``` 3. In the example dir run (replace __ with your board name, for @@ -34,13 +34,6 @@ a reference for creating your own application. $ west build -b ``` - Also use key `-DFLASH_SIZE`, if your board has memory size different from 2 - MB, for example, `-DFLASH_SIZE=1m` or `-DFLASH_SIZE=1m`: - - ```bash - $ west build -b tlsr9518adk80d -- -DFLASH_SIZE=4m - ``` - 4. Flash binary: ``` @@ -129,7 +122,7 @@ feature for another Telink example: After build application with enabled OTA feature, use next binary files: -- zephyr.bin - main binary to flash PCB (Use at least 2MB PCB). +- 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 diff --git a/examples/lock-app/telink/include/AppTask.h b/examples/lock-app/telink/include/AppTask.h index c63643ca89ca6d..05a5787d3526b7 100644 --- a/examples/lock-app/telink/include/AppTask.h +++ b/examples/lock-app/telink/include/AppTask.h @@ -19,12 +19,21 @@ #pragma once #include "AppTaskCommon.h" -#include "BoltLockManager.h" +// #include "BoltLockManager.h" +#include "LockManager.h" + +#define APP_ERROR_EVENT_QUEUE_FAILED CHIP_APPLICATION_ERROR(0x01) +#define APP_ERROR_CREATE_TASK_FAILED CHIP_APPLICATION_ERROR(0x02) +#define APP_ERROR_UNHANDLED_EVENT CHIP_APPLICATION_ERROR(0x03) +#define APP_ERROR_CREATE_TIMER_FAILED CHIP_APPLICATION_ERROR(0x04) +#define APP_ERROR_START_TIMER_FAILED CHIP_APPLICATION_ERROR(0x05) +#define APP_ERROR_STOP_TIMER_FAILED CHIP_APPLICATION_ERROR(0x06) +#define APP_ERROR_ALLOCATION_FAILED CHIP_APPLICATION_ERROR(0x07) class AppTask : public AppTaskCommon { public: - void UpdateClusterState(BoltLockManager::State state, BoltLockManager::OperationSource source); + // void UpdateClusterState(BoltLockManager::State state, BoltLockManager::OperationSource source); private: friend AppTask & GetAppTask(void); @@ -33,7 +42,7 @@ class AppTask : public AppTaskCommon CHIP_ERROR Init(void); static void LockActionEventHandler(AppEvent * event); - static void LockStateChanged(BoltLockManager::State state, BoltLockManager::OperationSource source); + static void LockStateChanged(LockManager::State_t state); static AppTask sAppTask; }; @@ -42,3 +51,4 @@ inline AppTask & GetAppTask(void) { return AppTask::sAppTask; } +CHIP_ERROR InitDoorLockHandler(); \ No newline at end of file diff --git a/examples/lock-app/telink/include/BoltLockManager.h b/examples/lock-app/telink/include/BoltLockManager.h deleted file mode 100644 index 28176c9efee3b6..00000000000000 --- a/examples/lock-app/telink/include/BoltLockManager.h +++ /dev/null @@ -1,129 +0,0 @@ -/* - * - * Copyright (c) 2023 Project CHIP Authors - * All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#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/LockManager.h b/examples/lock-app/telink/include/LockManager.h new file mode 100644 index 00000000000000..1dd50fff19db17 --- /dev/null +++ b/examples/lock-app/telink/include/LockManager.h @@ -0,0 +1,225 @@ +/* + * + * Copyright (c) 2019 Google LLC. + * 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 "AppEventCommon.h" + +#include + +struct WeekDaysScheduleInfo +{ + DlScheduleStatus status; + EmberAfPluginDoorLockWeekDaySchedule schedule; +}; + +struct YearDayScheduleInfo +{ + DlScheduleStatus status; + EmberAfPluginDoorLockYearDaySchedule schedule; +}; + +struct HolidayScheduleInfo +{ + DlScheduleStatus status; + EmberAfPluginDoorLockHolidaySchedule schedule; +}; + +namespace TelinkDoorLock { +namespace ResourceRanges { +// Used to size arrays +static constexpr uint16_t kMaxUsers = 10; +static constexpr uint8_t kMaxCredentialsPerUser = 10; +static constexpr uint8_t kMaxWeekdaySchedulesPerUser = 10; +static constexpr uint8_t kMaxYeardaySchedulesPerUser = 10; +static constexpr uint8_t kMaxHolidaySchedules = 10; +static constexpr uint8_t kMaxCredentialSize = 20; +static constexpr uint8_t kNumCredentialTypes = 6; + +static constexpr uint8_t kMaxCredentials = kMaxUsers * kMaxCredentialsPerUser; + +} // namespace ResourceRanges + +namespace LockInitParams { + +struct LockParam +{ + // Read from zap attributes + uint16_t numberOfUsers = 0; + uint8_t numberOfCredentialsPerUser = 0; + uint8_t numberOfWeekdaySchedulesPerUser = 0; + uint8_t numberOfYeardaySchedulesPerUser = 0; + uint8_t numberOfHolidaySchedules = 0; +}; + +class ParamBuilder +{ +public: + ParamBuilder & SetNumberOfUsers(uint16_t numberOfUsers) + { + lockParam_.numberOfUsers = numberOfUsers; + return *this; + } + ParamBuilder & SetNumberOfCredentialsPerUser(uint8_t numberOfCredentialsPerUser) + { + lockParam_.numberOfCredentialsPerUser = numberOfCredentialsPerUser; + return *this; + } + ParamBuilder & SetNumberOfWeekdaySchedulesPerUser(uint8_t numberOfWeekdaySchedulesPerUser) + { + lockParam_.numberOfWeekdaySchedulesPerUser = numberOfWeekdaySchedulesPerUser; + return *this; + } + ParamBuilder & SetNumberOfYeardaySchedulesPerUser(uint8_t numberOfYeardaySchedulesPerUser) + { + lockParam_.numberOfYeardaySchedulesPerUser = numberOfYeardaySchedulesPerUser; + return *this; + } + ParamBuilder & SetNumberOfHolidaySchedules(uint8_t numberOfHolidaySchedules) + { + lockParam_.numberOfHolidaySchedules = numberOfHolidaySchedules; + return *this; + } + LockParam GetLockParam() { return lockParam_; } + +private: + LockParam lockParam_; +}; + +} // namespace LockInitParams +} // namespace TelinkDoorLock + +using namespace ::chip; +using namespace TelinkDoorLock::ResourceRanges; + +class LockManager +{ +public: + enum Action_t + { + LOCK_ACTION = 0, + UNLOCK_ACTION, + UNBOLT_ACTION, + + INVALID_ACTION + } Action; + + enum State_t + { + kState_LockInitiated = 0, + kState_LockCompleted, + kState_UnlockInitiated, + kState_UnlockCompleted, + kState_UnlatchInitiated, + kState_UnlatchCompleted, + kState_NotFulyLocked + } State; + + using StateChangeCallback = void (*)(State_t); + using OperationSource = chip::app::Clusters::DoorLock::OperationSourceEnum; + + CHIP_ERROR Init(chip::app::DataModel::Nullable state, + TelinkDoorLock::LockInitParams::LockParam lockParam, StateChangeCallback callback); + + bool LockAction(int32_t appSource, Action_t aAction, OperationSource source, chip::EndpointId endpointId, + const Nullable & fabricIdx = NullNullable, const Nullable & nodeId = NullNullable, + const Optional & pinCode = NullNullable); + + bool IsLocked() const { return mState == State_t::kState_LockCompleted; } + + State_t getLockState() { return mState;} + + bool GetUser(chip::EndpointId endpointId, uint16_t userIndex, EmberAfPluginDoorLockUserInfo & user); + bool SetUser(chip::EndpointId endpointId, uint16_t userIndex, chip::FabricIndex creator, chip::FabricIndex modifier, + const chip::CharSpan & userName, uint32_t uniqueId, UserStatusEnum userStatus, UserTypeEnum usertype, + CredentialRuleEnum credentialRule, const CredentialStruct * credentials, size_t totalCredentials); + + bool GetCredential(chip::EndpointId endpointId, uint16_t credentialIndex, CredentialTypeEnum credentialType, + EmberAfPluginDoorLockCredentialInfo & credential); + + bool SetCredential(chip::EndpointId endpointId, uint16_t credentialIndex, chip::FabricIndex creator, chip::FabricIndex modifier, + DlCredentialStatus credentialStatus, CredentialTypeEnum credentialType, + const chip::ByteSpan & credentialData); + + DlStatus GetWeekdaySchedule(chip::EndpointId endpointId, uint8_t weekdayIndex, uint16_t userIndex, + EmberAfPluginDoorLockWeekDaySchedule & schedule); + + DlStatus SetWeekdaySchedule(chip::EndpointId endpointId, uint8_t weekdayIndex, uint16_t userIndex, DlScheduleStatus status, + DaysMaskMap daysMask, uint8_t startHour, uint8_t startMinute, uint8_t endHour, uint8_t endMinute); + + DlStatus GetYeardaySchedule(chip::EndpointId endpointId, uint8_t yearDayIndex, uint16_t userIndex, + EmberAfPluginDoorLockYearDaySchedule & schedule); + + DlStatus SetYeardaySchedule(chip::EndpointId endpointId, uint8_t yearDayIndex, uint16_t userIndex, DlScheduleStatus status, + uint32_t localStartTime, uint32_t localEndTime); + + DlStatus GetHolidaySchedule(chip::EndpointId endpointId, uint8_t holidayIndex, EmberAfPluginDoorLockHolidaySchedule & schedule); + + DlStatus SetHolidaySchedule(chip::EndpointId endpointId, uint8_t holidayIndex, DlScheduleStatus status, uint32_t localStartTime, + uint32_t localEndTime, OperatingModeEnum operatingMode); + + bool IsValidUserIndex(uint16_t userIndex); + bool IsValidCredentialIndex(uint16_t credentialIndex, CredentialTypeEnum type); + bool IsValidCredentialType(CredentialTypeEnum type); + bool IsValidWeekdayScheduleIndex(uint8_t scheduleIndex); + bool IsValidYeardayScheduleIndex(uint8_t scheduleIndex); + bool IsValidHolidayScheduleIndex(uint8_t scheduleIndex); + + const char * lockStateToString(DlLockState lockState) const; + + bool ReadConfigValues(); + +private: + friend LockManager & LockMgr(); + State_t mState = kState_NotFulyLocked; + StateChangeCallback mStateChangeCallback = nullptr; + static constexpr uint32_t kActuatorMovementTimeMs = 2000; + + OperationSource mActuatorOperationSource = OperationSource::kButton; + k_timer mActuatorTimer = {}; + + bool setLockState(chip::EndpointId endpointId, DlLockState lockState, OperationSource source, + const Nullable & fabricIdx, const Nullable & nodeId, + const Optional & pin); + + static void ActuatorTimerEventHandler(k_timer * timer); + static void ActuatorAppEventHandler(const AppEvent & event); + + EmberAfPluginDoorLockUserInfo mLockUsers[kMaxUsers]; + EmberAfPluginDoorLockCredentialInfo mLockCredentials[kNumCredentialTypes][kMaxCredentials]; + WeekDaysScheduleInfo mWeekdaySchedule[kMaxUsers][kMaxWeekdaySchedulesPerUser]; + YearDayScheduleInfo mYeardaySchedule[kMaxUsers][kMaxYeardaySchedulesPerUser]; + HolidayScheduleInfo mHolidaySchedule[kMaxHolidaySchedules]; + + char mUserNames[ArraySize(mLockUsers)][DOOR_LOCK_MAX_USER_NAME_SIZE]; + uint8_t mCredentialData[kNumCredentialTypes][kMaxCredentials][kMaxCredentialSize]; + CredentialStruct mCredentials[kMaxUsers][kMaxCredentials]; + + static LockManager sLock; + TelinkDoorLock::LockInitParams::LockParam LockParams; +}; + +inline LockManager & LockMgr() +{ + return LockManager::sLock; +} diff --git a/examples/lock-app/telink/src/AppTask.cpp b/examples/lock-app/telink/src/AppTask.cpp index bf7fc3311cd0e8..0d4a507c7971ea 100644 --- a/examples/lock-app/telink/src/AppTask.cpp +++ b/examples/lock-app/telink/src/AppTask.cpp @@ -17,13 +17,19 @@ */ #include "AppTask.h" -#include "BoltLockManager.h" - +#include +#include #include - +#include +#include LOG_MODULE_DECLARE(app, CONFIG_CHIP_APP_LOG_LEVEL); using namespace ::chip::app::Clusters::DoorLock; +using namespace chip; +using namespace chip::app; +using namespace ::chip::DeviceLayer; +using namespace ::chip::DeviceLayer::Internal; +using namespace TelinkDoorLock::LockInitParams; namespace { #if CONFIG_CHIP_ENABLE_APPLICATION_STATUS_LED @@ -31,6 +37,25 @@ LEDWidget sLockLED; #endif } // namespace +#if defined(CONFIG_CHIP_LIB_SHELL) +#include "lib/shell/Engine.h" +#include "lib/shell/commands/Help.h" +#endif // CONFIG_CHIP_LIB_SHELL + + +#if defined(CONFIG_CHIP_LIB_SHELL) +using Shell::Engine; +using Shell::shell_command_t; +using Shell::streamer_get; +using Shell::streamer_printf; +using chip::app::DataModel::MakeNullable; +using ::chip::app::DataModel::Nullable; +using chip::app::DataModel::NullNullable; +Engine sShellDoorLockEvents; +Engine sShellDoorLockSubEvents; +#endif // defined(CONFIG_CHIP_LIB_SHELL) + + AppTask AppTask::sAppTask; CHIP_ERROR AppTask::Init(void) @@ -42,97 +67,507 @@ CHIP_ERROR AppTask::Init(void) #if CONFIG_CHIP_ENABLE_APPLICATION_STATUS_LED sLockLED.Init(GPIO_DT_SPEC_GET(DT_ALIAS(led2), gpios)); - sLockLED.Set(BoltLockMgr().IsLocked()); + sLockLED.Set(LockMgr().IsLocked()); #endif - BoltLockMgr().Init(LockStateChanged); + chip::app::DataModel::Nullable state; + chip::EndpointId endpointId{ kExampleEndpointId }; + chip::DeviceLayer::PlatformMgr().LockChipStack(); + chip::app::Clusters::DoorLock::Attributes::LockState::Get(endpointId, state); + + uint8_t numberOfCredentialsPerUser = 0; + if (!DoorLockServer::Instance().GetNumberOfCredentialsSupportedPerUser(endpointId, numberOfCredentialsPerUser)) + { + ChipLogError(Zcl, + "Unable to get number of credentials supported per user when initializing lock endpoint, defaulting to 5 " + "[endpointId=%d]", + endpointId); + numberOfCredentialsPerUser = 5; + } + + uint16_t numberOfUsers = 0; + if (!DoorLockServer::Instance().GetNumberOfUserSupported(endpointId, numberOfUsers)) + { + ChipLogError(Zcl, + "Unable to get number of supported users when initializing lock endpoint, defaulting to 10 [endpointId=%d]", + endpointId); + numberOfUsers = 10; + } + + uint8_t numberOfWeekdaySchedulesPerUser = 0; + if (!DoorLockServer::Instance().GetNumberOfWeekDaySchedulesPerUserSupported(endpointId, numberOfWeekdaySchedulesPerUser)) + { + ChipLogError( + Zcl, + "Unable to get number of supported weekday schedules when initializing lock endpoint, defaulting to 10 [endpointId=%d]", + endpointId); + numberOfWeekdaySchedulesPerUser = 10; + } + + uint8_t numberOfYeardaySchedulesPerUser = 0; + if (!DoorLockServer::Instance().GetNumberOfYearDaySchedulesPerUserSupported(endpointId, numberOfYeardaySchedulesPerUser)) + { + ChipLogError( + Zcl, + "Unable to get number of supported yearday schedules when initializing lock endpoint, defaulting to 10 [endpointId=%d]", + endpointId); + numberOfYeardaySchedulesPerUser = 10; + } + + uint8_t numberOfHolidaySchedules = 0; + if (!DoorLockServer::Instance().GetNumberOfHolidaySchedulesSupported(endpointId, numberOfHolidaySchedules)) + { + ChipLogError( + Zcl, + "Unable to get number of supported holiday schedules when initializing lock endpoint, defaulting to 10 [endpointId=%d]", + endpointId); + numberOfHolidaySchedules = 10; + } + + chip::DeviceLayer::PlatformMgr().UnlockChipStack(); + + CHIP_ERROR err = CHIP_NO_ERROR; + + err = LockMgr().Init(state, + ParamBuilder() + .SetNumberOfUsers(numberOfUsers) + .SetNumberOfCredentialsPerUser(numberOfCredentialsPerUser) + .SetNumberOfWeekdaySchedulesPerUser(numberOfWeekdaySchedulesPerUser) + .SetNumberOfYeardaySchedulesPerUser(numberOfYeardaySchedulesPerUser) + .SetNumberOfHolidaySchedules(numberOfHolidaySchedules) + .GetLockParam(), LockStateChanged); + + if (err != CHIP_NO_ERROR) + { + LOG_ERR("LockMgr().Init() failed"); + return err; + } // Disable auto-relock time feature. DoorLockServer::Instance().SetAutoRelockTime(kExampleEndpointId, 0); + err = ConnectivityMgr().SetBLEDeviceName("Telink Lock"); + if (err != CHIP_NO_ERROR) + { + LOG_ERR("SetBLEDeviceName fail"); + return err; + } + + InitDoorLockHandler(); + return CHIP_NO_ERROR; } + +/* This is a button handler only */ void AppTask::LockActionEventHandler(AppEvent * aEvent) { - if (BoltLockMgr().IsLocked()) + switch (LockMgr().getLockState()) { - BoltLockMgr().Unlock(BoltLockManager::OperationSource::kButton); - } - else - { - BoltLockMgr().Lock(BoltLockManager::OperationSource::kButton); + case LockManager::kState_LockCompleted: + LockMgr().LockAction(AppEvent::kEventType_Lock, LockManager::UNLOCK_ACTION, LockManager::OperationSource::kButton, kExampleEndpointId); + break; + case LockManager::kState_UnlockCompleted: + LockMgr().LockAction(AppEvent::kEventType_Lock, LockManager::LOCK_ACTION, LockManager::OperationSource::kButton, kExampleEndpointId); + break; + default: + LOG_INF("Lock is in intermediate state, ignoring button"); + break; } } -void AppTask::LockStateChanged(BoltLockManager::State state, BoltLockManager::OperationSource source) +void AppTask::LockStateChanged(LockManager::State_t state) { switch (state) { - case BoltLockManager::State::kLockingInitiated: - LOG_INF("Lock action initiated"); + case LockManager::State_t::kState_LockInitiated: + LOG_INF("Callback: Lock action initiated"); #if CONFIG_CHIP_ENABLE_APPLICATION_STATUS_LED sLockLED.Blink(50, 50); #endif break; - case BoltLockManager::State::kLockingCompleted: - LOG_INF("Lock action completed"); + case LockManager::State_t::kState_LockCompleted: + LOG_INF("Callback: Lock action completed"); #if CONFIG_CHIP_ENABLE_APPLICATION_STATUS_LED sLockLED.Set(true); #endif break; - case BoltLockManager::State::kUnlockingInitiated: - LOG_INF("Unlock action initiated"); + case LockManager::State_t::kState_UnlockInitiated: + LOG_INF("Callback: Unlock action initiated"); #if CONFIG_CHIP_ENABLE_APPLICATION_STATUS_LED sLockLED.Blink(50, 50); #endif break; - case BoltLockManager::State::kUnlockingCompleted: - LOG_INF("Unlock action completed"); + case LockManager::State_t::kState_UnlockCompleted: + LOG_INF("Callback: Unlock action completed"); #if CONFIG_CHIP_ENABLE_APPLICATION_STATUS_LED sLockLED.Set(false); #endif break; + case LockManager::State_t::kState_UnlatchInitiated: + LOG_INF("Callback: Unbolt action initiated"); +#if CONFIG_CHIP_ENABLE_APPLICATION_STATUS_LED + sLockLED.Blink(75, 25); +#endif + break; + case LockManager::State_t::kState_UnlatchCompleted: + LOG_INF("Callback: Unbolt action completed"); +#if CONFIG_CHIP_ENABLE_APPLICATION_STATUS_LED + sLockLED.Blink(25, 75); +#endif + break; + case LockManager::State_t::kState_NotFulyLocked: + LOG_INF("Callback: Lock not fully locked. Unexpected state"); +#if CONFIG_CHIP_ENABLE_APPLICATION_STATUS_LED + sLockLED.Blink(10, 90); +#endif + break; + } + +} + +#ifdef CONFIG_CHIP_LIB_SHELL - // Handle changing attribute state in the application - sAppTask.UpdateClusterState(state, source); +/******************************************************** + * Switch shell functions + *********************************************************/ + +CHIP_ERROR LockHelpHandler(int argc, char ** argv) +{ + sShellDoorLockEvents.ForEachCommand(Shell::PrintCommandHelp, nullptr); + return CHIP_NO_ERROR; } -void AppTask::UpdateClusterState(BoltLockManager::State state, BoltLockManager::OperationSource source) +CHIP_ERROR DoorLockEventsHandler(int argc, char ** argv) { - DlLockState newLockState; + if (argc == 0) + { + return LockHelpHandler(argc, argv); + } - switch (state) + return sShellDoorLockEvents.ExecCommand(argc, argv); +} + +/******************************************************** + * OnOff switch shell functions + *********************************************************/ + +CHIP_ERROR DoorLockHelpHandler(int argc, char ** argv) +{ + sShellDoorLockSubEvents.ForEachCommand(Shell::PrintCommandHelp, nullptr); + return CHIP_NO_ERROR; +} + + +CHIP_ERROR DoorLockLockCommandHandler(int argc, char ** argv) +{ + if (argc == 0) { - case BoltLockManager::State::kLockingCompleted: - newLockState = DlLockState::kLocked; - break; - case BoltLockManager::State::kUnlockingCompleted: - newLockState = DlLockState::kUnlocked; - break; - default: - newLockState = DlLockState::kNotFullyLocked; - break; + return DoorLockHelpHandler(argc, argv); } - SystemLayer().ScheduleLambda([newLockState, source] { - chip::app::DataModel::Nullable currentLockState; - chip::app::Clusters::DoorLock::Attributes::LockState::Get(kExampleEndpointId, currentLockState); - - if (currentLockState.IsNull()) - { - // Initialize lock state with start value, but not invoke lock/unlock. - chip::app::Clusters::DoorLock::Attributes::LockState::Set(kExampleEndpointId, newLockState); - } - else - { - LOG_INF("Updating LockState attribute"); - - if (!DoorLockServer::Instance().SetLockState(kExampleEndpointId, newLockState, source)) - { - LOG_ERR("Failed to update LockState attribute"); - } - } - }); + return sShellDoorLockSubEvents.ExecCommand(argc, argv); } + + +CHIP_ERROR AlarmReadEventHandler(int argc, char ** argv) +{ + DoorLockServer::Instance().SendLockAlarmEvent(1, AlarmCodeEnum::kLockJammed); + return CHIP_NO_ERROR; +} + +CHIP_ERROR StateChangeReadEventHandler(int argc, char ** argv) +{ + DoorLockServer::Instance().SetDoorState(1, DoorStateEnum::kDoorClosed); + return CHIP_NO_ERROR; +} + + +/** + // * @brief Send LockOperation event if opSuccess is true, otherwise send LockOperationError with given opErr code + // * + // * @param endpointId endpoint where DoorLockServer is running + // * @param opType lock operation type (lock, unlock, etc) + // * @param opSource operation source (remote, keypad, auto, etc) + // * @param opErr operation error code (if opSuccess == false) + // * @param userId user id + // * @param fabricIdx fabric index responsible for operating the lock + // * @param nodeId node id responsible for operating the lock + // * @param credList list of credentials used in lock operation (can be NULL if no credentials were used) + // * @param credListSize size of credentials list (if 0, then no credentials were used) + // * @param opSuccess flags if operation was successful or not + // */ + // void SendLockOperationEvent(chip::EndpointId endpointId, LockOperationTypeEnum opType, OperationSourceEnum opSource, + // OperationErrorEnum opErr, const Nullable & userId, + // const Nullable & fabricIdx, const Nullable & nodeId, + // const Nullable> & credentials = NullNullable, bool opSuccess = true); + + +CHIP_ERROR OperationReadEventHandler(int argc, char ** argv) +{ + // ChipLogError(Zcl, "$$$$$$$$$$OperationReadEventHandler"); + // LockOpCredentials currentCredential; + // if (emberAfPluginDoorLockGetCredential(1, 2, CredentialTypeEnum::kRfid, currentCredential)) + // { + // ChipLogError(Zcl, "$$$$$$$$$$OperationReadEventHandler OK"); + // } + + // NodeId nodeid= 1; + + + // for (const auto & fabricInfo : : chip::Server::GetInstance().GetFabricTable()) + // { + // FabricIndex fabricIndex = MakeNullable(fabricInfo.GetFabricIndex()); + + // DoorLockServer::Instance().SendLockOperationEvent(1, LockOperationTypeEnum::kLock, OperationSourceEnum::kRemote, OperationErrorEnum::kUnspecified, chip::app::DataModel::MakeNullable(static_cast(1)), lift, lift1, + // currentCredential, true); + // } + + // for (const auto & fb : chip::Server::GetInstance().GetFabricTable()) + // { + // FabricIndex fabricIndex = fb.GetFabricIndex(); + // NodeId myNodeId = fb.GetNodeId(); + // ChipLogProgress(NotSpecified, + // "---- Current Fabric nodeId=0x" ChipLogFormatX64 " fabricId=0x" ChipLogFormatX64 " fabricIndex=%d", + // ChipLogValueX64(myNodeId), ChipLogValueX64(fb.GetFabricId()), fabricIndex); + // DoorLockServer::Instance().SendLockOperationEvent(1, LockOperationTypeEnum::kLock, OperationSourceEnum::kRemote, OperationErrorEnum::kUnspecified, NullNullable, NullNullable, NullNullable, + // NullNullable); + DoorLockServer::Instance().SetLockState(1, DlLockState::kLocked); + // } + + return CHIP_NO_ERROR; +} + + +// CHIP_ERROR OnOffSwitchCommandHandler(int argc, char ** argv) +// { +// if (argc == 0) +// { +// return OnOffHelpHandler(argc, argv); +// } + +// return sShellSwitchOnOffSubCommands.ExecCommand(argc, argv); +// } + +// CHIP_ERROR OnSwitchCommandHandler(int argc, char ** argv) +// { +// BindingCommandData * data = Platform::New(); +// data->commandId = Clusters::OnOff::Commands::On::Id; +// data->clusterId = Clusters::OnOff::Id; + +// DeviceLayer::PlatformMgr().ScheduleWork(SwitchWorkerFunction, reinterpret_cast(data)); +// return CHIP_NO_ERROR; +// } + +// CHIP_ERROR OffSwitchCommandHandler(int argc, char ** argv) +// { +// BindingCommandData * data = Platform::New(); +// data->commandId = Clusters::OnOff::Commands::Off::Id; +// data->clusterId = Clusters::OnOff::Id; + +// DeviceLayer::PlatformMgr().ScheduleWork(SwitchWorkerFunction, reinterpret_cast(data)); +// return CHIP_NO_ERROR; +// } + +// CHIP_ERROR ToggleSwitchCommandHandler(int argc, char ** argv) +// { +// BindingCommandData * data = Platform::New(); +// data->commandId = Clusters::OnOff::Commands::Toggle::Id; +// data->clusterId = Clusters::OnOff::Id; + +// DeviceLayer::PlatformMgr().ScheduleWork(SwitchWorkerFunction, reinterpret_cast(data)); +// return CHIP_NO_ERROR; +// } + +// /******************************************************** +// * bind switch shell functions +// *********************************************************/ + +// CHIP_ERROR BindingHelpHandler(int argc, char ** argv) +// { +// sShellSwitchBindingSubCommands.ForEachCommand(Shell::PrintCommandHelp, nullptr); +// return CHIP_NO_ERROR; +// } + +// CHIP_ERROR BindingSwitchCommandHandler(int argc, char ** argv) +// { +// if (argc == 0) +// { +// return BindingHelpHandler(argc, argv); +// } + +// return sShellSwitchBindingSubCommands.ExecCommand(argc, argv); +// } + +// CHIP_ERROR BindingGroupBindCommandHandler(int argc, char ** argv) +// { +// VerifyOrReturnError(argc == 2, CHIP_ERROR_INVALID_ARGUMENT); + +// EmberBindingTableEntry * entry = Platform::New(); +// entry->type = EMBER_MULTICAST_BINDING; +// entry->fabricIndex = atoi(argv[0]); +// entry->groupId = atoi(argv[1]); +// entry->local = 1; // Hardcoded to endpoint 1 for now +// entry->clusterId.SetValue(6); // Hardcoded to OnOff cluster for now + +// DeviceLayer::PlatformMgr().ScheduleWork(BindingWorkerFunction, reinterpret_cast(entry)); +// return CHIP_NO_ERROR; +// } + +// CHIP_ERROR BindingUnicastBindCommandHandler(int argc, char ** argv) +// { +// VerifyOrReturnError(argc == 3, CHIP_ERROR_INVALID_ARGUMENT); + +// EmberBindingTableEntry * entry = Platform::New(); +// entry->type = EMBER_UNICAST_BINDING; +// entry->fabricIndex = atoi(argv[0]); +// entry->nodeId = atoi(argv[1]); +// entry->local = 1; // Hardcoded to endpoint 1 for now +// entry->remote = atoi(argv[2]); +// entry->clusterId.SetValue(6); // Hardcode to OnOff cluster for now + +// DeviceLayer::PlatformMgr().ScheduleWork(BindingWorkerFunction, reinterpret_cast(entry)); +// return CHIP_NO_ERROR; +// } + +// /******************************************************** +// * Groups switch shell functions +// *********************************************************/ + +// CHIP_ERROR GroupsHelpHandler(int argc, char ** argv) +// { +// sShellSwitchGroupsSubCommands.ForEachCommand(Shell::PrintCommandHelp, nullptr); +// return CHIP_NO_ERROR; +// } + +// CHIP_ERROR GroupsSwitchCommandHandler(int argc, char ** argv) +// { +// if (argc == 0) +// { +// return GroupsHelpHandler(argc, argv); +// } + +// return sShellSwitchGroupsSubCommands.ExecCommand(argc, argv); +// } + +// /******************************************************** +// * Groups OnOff switch shell functions +// *********************************************************/ + +// CHIP_ERROR GroupsOnOffHelpHandler(int argc, char ** argv) +// { +// sShellSwitchGroupsOnOffSubCommands.ForEachCommand(Shell::PrintCommandHelp, nullptr); +// return CHIP_NO_ERROR; +// } + +// CHIP_ERROR GroupsOnOffSwitchCommandHandler(int argc, char ** argv) +// { +// if (argc == 0) +// { +// return GroupsOnOffHelpHandler(argc, argv); +// } + +// return sShellSwitchGroupsOnOffSubCommands.ExecCommand(argc, argv); +// } + +// CHIP_ERROR GroupOnSwitchCommandHandler(int argc, char ** argv) +// { +// BindingCommandData * data = Platform::New(); +// data->commandId = Clusters::OnOff::Commands::On::Id; +// data->clusterId = Clusters::OnOff::Id; +// data->isGroup = true; + +// DeviceLayer::PlatformMgr().ScheduleWork(SwitchWorkerFunction, reinterpret_cast(data)); +// return CHIP_NO_ERROR; +// } + +// CHIP_ERROR GroupOffSwitchCommandHandler(int argc, char ** argv) +// { +// BindingCommandData * data = Platform::New(); +// data->commandId = Clusters::OnOff::Commands::Off::Id; +// data->clusterId = Clusters::OnOff::Id; +// data->isGroup = true; + +// DeviceLayer::PlatformMgr().ScheduleWork(SwitchWorkerFunction, reinterpret_cast(data)); +// return CHIP_NO_ERROR; +// } + +// CHIP_ERROR GroupToggleSwitchCommandHandler(int argc, char ** argv) +// { +// BindingCommandData * data = Platform::New(); +// data->commandId = Clusters::OnOff::Commands::Toggle::Id; +// data->clusterId = Clusters::OnOff::Id; +// data->isGroup = true; + +// DeviceLayer::PlatformMgr().ScheduleWork(SwitchWorkerFunction, reinterpret_cast(data)); +// return CHIP_NO_ERROR; +// } + +/** + * @brief configures switch matter shell + * + */ +static void RegisterDoorLockCommands() +{ + static const shell_command_t sDoorLockSubCommands[] = { + { &LockHelpHandler, "help", "Usage: lock " }, + { &DoorLockLockCommandHandler, "doorlock", " Usage: lock doorlock " }, + }; + + static const shell_command_t sLockDoorLockSubCommands[] = { + { &DoorLockHelpHandler,"help", "Usage : lock doorlock " }, + { &AlarmReadEventHandler,"DoorLockAlarm", " lock doorlock DoorLockAlarm"}, + { &StateChangeReadEventHandler,"DoorStateChange", "lock doorlock DoorStateChange"}, + { &OperationReadEventHandler,"LockOperation", "lock doorlock LockOperation" } + // { &OperationErrorReadEventHandler, "LockOperationError", "lock doorlock LockOperationError" }, + // { &UserChangeReadEventHandler,"LockUserChange", "lock doorlock LockUserChange" } + }; + + // static const shell_command_t sSwitchGroupsSubCommands[] = { { &GroupsHelpHandler, "help", "Usage: switch groups " }, + // { &GroupsOnOffSwitchCommandHandler, "onoff", + // "Usage: switch groups onoff " } }; + + // static const shell_command_t sSwitchGroupsOnOffSubCommands[] = { + // { &GroupsOnOffHelpHandler, "help", "Usage: switch groups onoff " }, + // { &GroupOnSwitchCommandHandler, "on", "Sends on command to bound group" }, + // { &GroupOffSwitchCommandHandler, "off", "Sends off command to bound group" }, + // { &GroupToggleSwitchCommandHandler, "toggle", "Sends toggle command to group" } + // }; + + // static const shell_command_t sSwitchBindingSubCommands[] = { + // { &BindingHelpHandler, "help", "Usage: switch binding " }, + // { &BindingGroupBindCommandHandler, "group", "Usage: switch binding group " }, + // { &BindingUnicastBindCommandHandler, "unicast", "Usage: switch binding group " } + // }; + + static const shell_command_t sLockCommand = { &DoorLockEventsHandler, "lock", + "Door-Lock commands. Usage: lock " }; + + // sShellSwitchGroupsOnOffSubCommands.RegisterCommands(sSwitchGroupsOnOffSubCommands, ArraySize(sSwitchGroupsOnOffSubCommands)); + // sShellSwitchOnOffSubCommands.RegisterCommands(sSwitchOnOffSubCommands, ArraySize(sSwitchOnOffSubCommands)); + // sShellSwitchGroupsSubCommands.RegisterCommands(sSwitchGroupsSubCommands, ArraySize(sSwitchGroupsSubCommands)); + sShellDoorLockSubEvents.RegisterCommands(sLockDoorLockSubCommands, ArraySize(sLockDoorLockSubCommands)); + sShellDoorLockEvents.RegisterCommands(sDoorLockSubCommands, ArraySize(sDoorLockSubCommands)); + + Engine::Root().RegisterCommands(&sLockCommand, 1); +} +#endif // CONFIG_CHIP_LIB_SHELL + + + + + + + + +CHIP_ERROR InitDoorLockHandler() +{ + // The initialization of binding manager will try establishing connection with unicast peers + // so it requires the Server instance to be correctly initialized. Post the init function to + // the event queue so that everything is ready when initialization is conducted. +#if defined(CONFIG_CHIP_LIB_SHELL) + RegisterDoorLockCommands(); +#endif + return CHIP_NO_ERROR; +} \ No newline at end of file diff --git a/examples/lock-app/telink/src/BoltLockManager.cpp b/examples/lock-app/telink/src/BoltLockManager.cpp deleted file mode 100644 index 721fb11e427d72..00000000000000 --- a/examples/lock-app/telink/src/BoltLockManager.cpp +++ /dev/null @@ -1,260 +0,0 @@ -/* - * - * Copyright (c) 2023 Project CHIP Authors - * All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "BoltLockManager.h" - -#include "AppConfig.h" -#include "AppEventCommon.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/LockManager.cpp b/examples/lock-app/telink/src/LockManager.cpp new file mode 100644 index 00000000000000..f1b341db413da3 --- /dev/null +++ b/examples/lock-app/telink/src/LockManager.cpp @@ -0,0 +1,800 @@ +/* + * + * Copyright (c) 2020 Project CHIP Authors + * Copyright (c) 2019 Google LLC. + * 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 "LockManager.h" + +#include "AppConfig.h" +#include "AppTask.h" +#include +#include +#include +LOG_MODULE_DECLARE(app, CONFIG_CHIP_APP_LOG_LEVEL); + +LockManager LockManager::sLock; + +using namespace ::chip::DeviceLayer::Internal; +using namespace TelinkDoorLock::LockInitParams; + +CHIP_ERROR LockManager::Init(chip::app::DataModel::Nullable state, LockParam lockParam, StateChangeCallback callback) +{ + LockParams = lockParam; + mStateChangeCallback = callback; + + if (LockParams.numberOfUsers > kMaxUsers) + { + ChipLogError(Zcl,"Max number of users is greater than %d, the maximum amount of users currently supported on this platform", + kMaxUsers); + return APP_ERROR_ALLOCATION_FAILED; + } + + if (LockParams.numberOfCredentialsPerUser > kMaxCredentialsPerUser) + { + ChipLogError(Zcl,"Max number of credentials per user is greater than %d, the maximum amount of users currently supported on this " + "platform", + kMaxCredentialsPerUser); + return APP_ERROR_ALLOCATION_FAILED; + } + + if (LockParams.numberOfWeekdaySchedulesPerUser > kMaxWeekdaySchedulesPerUser) + { + ChipLogError(Zcl, + "Max number of schedules is greater than %d, the maximum amount of schedules currently supported on this platform", + kMaxWeekdaySchedulesPerUser); + return APP_ERROR_ALLOCATION_FAILED; + } + + if (LockParams.numberOfYeardaySchedulesPerUser > kMaxYeardaySchedulesPerUser) + { + ChipLogError(Zcl, + "Max number of schedules is greater than %d, the maximum amount of schedules currently supported on this platform", + kMaxYeardaySchedulesPerUser); + return APP_ERROR_ALLOCATION_FAILED; + } + + if (LockParams.numberOfHolidaySchedules > kMaxHolidaySchedules) + { + ChipLogError(Zcl, + "Max number of schedules is greater than %d, the maximum amount of schedules currently supported on this platform", + kMaxHolidaySchedules); + return APP_ERROR_ALLOCATION_FAILED; + } + + k_timer_init(&mActuatorTimer, &LockManager::ActuatorTimerEventHandler, nullptr); + k_timer_user_data_set(&mActuatorTimer, this); + + return CHIP_NO_ERROR; +} + +/* Action related to mechanical operation. Called from ZCL and button */ +bool LockManager::LockAction(int32_t appSource, Action_t aAction, OperationSource source, chip::EndpointId endpointId, + const Nullable & fabricIdx, + const Nullable & nodeId, const Optional & pinCode) +{ + bool status = false; + switch (aAction) + { + case LOCK_ACTION: + if (mState != kState_LockCompleted) + { + mState = kState_LockInitiated; + status = setLockState(kExampleEndpointId, DlLockState::kLocked, source, fabricIdx, nodeId, pinCode); + if (status) + { + LOG_INF("Lock Action: Lock action initiated successfully. Waiting for actuator"); + k_timer_start(&mActuatorTimer, K_MSEC(kActuatorMovementTimeMs), K_NO_WAIT); + } + else + { + LOG_INF("Lock Action: Lock action failed to initiate. No action performed"); + mState = kState_NotFulyLocked; + } + if (mStateChangeCallback) + mStateChangeCallback(mState); + } + else + { + LOG_INF("Lock Action: Lock is already locked. No action performed"); + } + break; + case UNLOCK_ACTION: + if (mState != kState_UnlockCompleted) + { + if (DoorLockServer::Instance().SupportsUnbolt(kExampleEndpointId)) + { + status = setLockState(kExampleEndpointId, DlLockState::kUnlatched, source, fabricIdx, nodeId, pinCode); + if (status) + { + LOG_INF("Unlock Action: Step 1: Unbolt completed"); + mState = kState_UnlatchInitiated; + if (mStateChangeCallback) + mStateChangeCallback(mState); + status = setLockState(kExampleEndpointId, DlLockState::kUnlocked, source, fabricIdx, nodeId, pinCode); + if (status) + { + LOG_INF("Unlock Action: Step 2: Unlock completed"); + mState = kState_UnlatchInitiated; + if (mStateChangeCallback) + mStateChangeCallback(mState); + k_timer_start(&mActuatorTimer, K_MSEC(kActuatorMovementTimeMs), K_NO_WAIT); + } + else + { + LOG_INF("Unlock Action: Step 2: Unlock failed. no action performed"); + mState = kState_NotFulyLocked; + } + } + else + { + LOG_INF("Unlock Action: Step 1: Unbolt failed. no action performed"); + mState = kState_NotFulyLocked; + } + if (mStateChangeCallback) + mStateChangeCallback(mState); + } + else + { + status = setLockState(kExampleEndpointId, DlLockState::kUnlocked, source, fabricIdx, nodeId, pinCode); + if (status) + { + LOG_INF("Unlock Action: Unlock initiated"); + mState = kState_UnlockInitiated; + k_timer_start(&mActuatorTimer, K_MSEC(kActuatorMovementTimeMs), K_NO_WAIT); + } + else + { + LOG_INF("Unlock Action: Unlock failed. no action performed"); + mState = kState_NotFulyLocked; + } + if (mStateChangeCallback) + mStateChangeCallback(mState); + } + } + else + { + LOG_INF("Unlock Action: Lock is already unlocked. no action performed"); + } + break; + case UNBOLT_ACTION: + if (mState != kState_UnlatchCompleted) + { + status = setLockState(kExampleEndpointId, DlLockState::kUnlatched, source, fabricIdx, nodeId, pinCode); + if (status) + { + LOG_INF("Unbolt Action: Unbolt initiated"); + mState = kState_UnlatchInitiated; + k_timer_start(&mActuatorTimer, K_MSEC(kActuatorMovementTimeMs), K_NO_WAIT); + } + else + { + LOG_INF("Unbolt Action: Unbolt failed. no action performed"); + mState = kState_NotFulyLocked; + } + if (mStateChangeCallback) + mStateChangeCallback(mState); + } + else + { + LOG_INF("Unbolt Action: Lock is already in unbolt state. no action performed"); + } + break; + default: + LOG_INF("Unknown lock state. no action performed"); + mState = kState_NotFulyLocked; + break; + } + return status; +} + +void LockManager::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)LockManager::ActuatorAppEventHandler; + GetAppTask().PostEvent(&event); +} + +void LockManager::ActuatorAppEventHandler(const AppEvent & event) +{ + LockManager * lock = static_cast(event.TimerEvent.Context); + + if (!lock) + { + return; + } + + switch (lock->mState) + { + case kState_LockInitiated: + LOG_INF("Lock action completed"); + lock->mState = kState_LockCompleted; + if (lock->mStateChangeCallback) + lock->mStateChangeCallback(lock->mState); + break; + case kState_UnlockInitiated: + LOG_INF("Unlock action completed"); + lock->mState = kState_UnlockCompleted; + if (lock->mStateChangeCallback) + lock->mStateChangeCallback(lock->mState); + break; + case kState_UnlatchInitiated: + LOG_INF("Unbolt action completed"); + lock->mState = kState_UnlatchCompleted; + if (lock->mStateChangeCallback) + lock->mStateChangeCallback(lock->mState); + break; + + default: + LOG_INF("Unexpected action occures"); + break; + } +} + +bool LockManager::IsValidUserIndex(uint16_t userIndex) +{ + return (userIndex < kMaxUsers); +} + +bool LockManager::IsValidCredentialIndex(uint16_t credentialIndex, CredentialTypeEnum type) +{ + if (CredentialTypeEnum::kProgrammingPIN == type) + { + return (0 == credentialIndex); // 0 is required index for Programming PIN + } + return (credentialIndex < kMaxCredentialsPerUser); +} + +bool LockManager::IsValidCredentialType(CredentialTypeEnum type) +{ + return (to_underlying(type) < kNumCredentialTypes); +} + +bool LockManager::IsValidWeekdayScheduleIndex(uint8_t scheduleIndex) +{ + return (scheduleIndex < kMaxWeekdaySchedulesPerUser); +} + +bool LockManager::IsValidYeardayScheduleIndex(uint8_t scheduleIndex) +{ + return (scheduleIndex < kMaxYeardaySchedulesPerUser); +} + +bool LockManager::IsValidHolidayScheduleIndex(uint8_t scheduleIndex) +{ + return (scheduleIndex < kMaxHolidaySchedules); +} + +bool LockManager::ReadConfigValues() +{ + // size_t outLen; + // SilabsConfig::ReadConfigValueBin(SilabsConfig::kConfigKey_LockUser, reinterpret_cast(&mLockUsers), + // sizeof(EmberAfPluginDoorLockUserInfo) * ArraySize(mLockUsers), outLen); + + // SilabsConfig::ReadConfigValueBin(SilabsConfig::kConfigKey_Credential, reinterpret_cast(&mLockCredentials), + // sizeof(EmberAfPluginDoorLockCredentialInfo) * kMaxCredentials * kNumCredentialTypes, outLen); + + // SilabsConfig::ReadConfigValueBin(SilabsConfig::kConfigKey_LockUserName, reinterpret_cast(mUserNames), + // sizeof(mUserNames), outLen); + + // SilabsConfig::ReadConfigValueBin(SilabsConfig::kConfigKey_CredentialData, reinterpret_cast(mCredentialData), + // sizeof(mCredentialData), outLen); + + // SilabsConfig::ReadConfigValueBin(SilabsConfig::kConfigKey_UserCredentials, reinterpret_cast(mCredentials), + // sizeof(CredentialStruct) * LockParams.numberOfUsers * LockParams.numberOfCredentialsPerUser, + // outLen); + + // SilabsConfig::ReadConfigValueBin(SilabsConfig::kConfigKey_WeekDaySchedules, reinterpret_cast(mWeekdaySchedule), + // sizeof(EmberAfPluginDoorLockWeekDaySchedule) * LockParams.numberOfWeekdaySchedulesPerUser * + // LockParams.numberOfUsers, + // outLen); + + // SilabsConfig::ReadConfigValueBin(SilabsConfig::kConfigKey_YearDaySchedules, reinterpret_cast(mYeardaySchedule), + // sizeof(EmberAfPluginDoorLockYearDaySchedule) * LockParams.numberOfYeardaySchedulesPerUser * + // LockParams.numberOfUsers, + // outLen); + + // SilabsConfig::ReadConfigValueBin(SilabsConfig::kConfigKey_HolidaySchedules, reinterpret_cast(&(mHolidaySchedule)), + // sizeof(EmberAfPluginDoorLockHolidaySchedule) * LockParams.numberOfHolidaySchedules, outLen); + + return true; +} + +bool LockManager::GetUser(chip::EndpointId endpointId, uint16_t userIndex, EmberAfPluginDoorLockUserInfo & user) +{ + VerifyOrReturnValue(userIndex > 0, false); // indices are one-indexed + + userIndex--; + + VerifyOrReturnValue(IsValidUserIndex(userIndex), false); + + ChipLogProgress(Zcl, "Door Lock App: LockManager::GetUser [endpoint=%d,userIndex=%hu]", endpointId, userIndex); + + const auto & userInDb = mLockUsers[userIndex]; + + user.userStatus = userInDb.userStatus; + if (UserStatusEnum::kAvailable == user.userStatus) + { + ChipLogDetail(Zcl, "Found unoccupied user [endpoint=%d]", endpointId); + return true; + } + + user.userName = chip::CharSpan(userInDb.userName.data(), userInDb.userName.size()); + user.credentials = chip::Span(mCredentials[userIndex], userInDb.credentials.size()); + user.userUniqueId = userInDb.userUniqueId; + user.userType = userInDb.userType; + user.credentialRule = userInDb.credentialRule; + // So far there's no way to actually create the credential outside Matter, so here we always set the creation/modification + // source to Matter + user.creationSource = DlAssetSource::kMatterIM; + user.createdBy = userInDb.createdBy; + user.modificationSource = DlAssetSource::kMatterIM; + user.lastModifiedBy = userInDb.lastModifiedBy; + + ChipLogDetail(Zcl, + "Found occupied user " + "[endpoint=%d,name=\"%.*s\",credentialsCount=%u,uniqueId=%lx,type=%u,credentialRule=%u," + "createdBy=%d,lastModifiedBy=%d]", + endpointId, static_cast(user.userName.size()), user.userName.data(), user.credentials.size(), + user.userUniqueId, to_underlying(user.userType), to_underlying(user.credentialRule), user.createdBy, + user.lastModifiedBy); + + return true; +} + +bool LockManager::SetUser(chip::EndpointId endpointId, uint16_t userIndex, chip::FabricIndex creator, chip::FabricIndex modifier, + const chip::CharSpan & userName, uint32_t uniqueId, UserStatusEnum userStatus, UserTypeEnum usertype, + CredentialRuleEnum credentialRule, const CredentialStruct * credentials, size_t totalCredentials) +{ + ChipLogProgress(Zcl, + "Door Lock App: LockManager::SetUser " + "[endpoint=%d,userIndex=%d,creator=%d,modifier=%d,userName=%s,uniqueId=%u " + "userStatus=%u,userType=%u,credentialRule=%u,credentials=%p,totalCredentials=%u]", + endpointId, userIndex, creator, modifier, userName.data(), uniqueId, to_underlying(userStatus), + to_underlying(usertype), to_underlying(credentialRule), credentials, totalCredentials); + + VerifyOrReturnValue(userIndex > 0, false); // indices are one-indexed + + userIndex--; + + VerifyOrReturnValue(IsValidUserIndex(userIndex), false); + + auto & userInStorage = mLockUsers[userIndex]; + + if (userName.size() > DOOR_LOCK_MAX_USER_NAME_SIZE) + { + ChipLogError(Zcl, "Cannot set user - user name is too long [endpoint=%d,index=%d]", endpointId, userIndex); + return false; + } + + if (totalCredentials > LockParams.numberOfCredentialsPerUser) + { + ChipLogError(Zcl, "Cannot set user - total number of credentials is too big [endpoint=%d,index=%d,totalCredentials=%u]", + endpointId, userIndex, totalCredentials); + return false; + } + + chip::Platform::CopyString(mUserNames[userIndex], userName); + userInStorage.userName = chip::CharSpan(mUserNames[userIndex], userName.size()); + userInStorage.userUniqueId = uniqueId; + userInStorage.userStatus = userStatus; + userInStorage.userType = usertype; + userInStorage.credentialRule = credentialRule; + userInStorage.lastModifiedBy = modifier; + userInStorage.createdBy = creator; + + for (size_t i = 0; i < totalCredentials; ++i) + { + mCredentials[userIndex][i] = credentials[i]; + } + + userInStorage.credentials = chip::Span(mCredentials[userIndex], totalCredentials); + + // Save user information in NVM flash + // SilabsConfig::WriteConfigValueBin(SilabsConfig::kConfigKey_LockUser, reinterpret_cast(&mLockUsers), + // sizeof(EmberAfPluginDoorLockUserInfo) * LockParams.numberOfUsers); + + // SilabsConfig::WriteConfigValueBin(SilabsConfig::kConfigKey_UserCredentials, reinterpret_cast(mCredentials), + // sizeof(CredentialStruct) * LockParams.numberOfUsers * LockParams.numberOfCredentialsPerUser); + + // SilabsConfig::WriteConfigValueBin(SilabsConfig::kConfigKey_LockUserName, reinterpret_cast(mUserNames), + // sizeof(mUserNames)); + + ChipLogProgress(Zcl, "Successfully set the user [mEndpointId=%d,index=%d]", endpointId, userIndex); + + return true; +} + +bool LockManager::GetCredential(chip::EndpointId endpointId, uint16_t credentialIndex, CredentialTypeEnum credentialType, + EmberAfPluginDoorLockCredentialInfo & credential) +{ + + VerifyOrReturnValue(IsValidCredentialType(credentialType), false); + + if (CredentialTypeEnum::kProgrammingPIN == credentialType) + { + VerifyOrReturnValue(IsValidCredentialIndex(credentialIndex, credentialType), + false); // programming pin index is only index allowed to contain 0 + } + else + { + VerifyOrReturnValue(IsValidCredentialIndex(--credentialIndex, credentialType), false); // otherwise, indices are one-indexed + } + + ChipLogProgress(Zcl, "Lock App: LockManager::GetCredential [credentialType=%u], credentialIndex=%d", + to_underlying(credentialType), credentialIndex); + + const auto & credentialInStorage = mLockCredentials[to_underlying(credentialType)][credentialIndex]; + + credential.status = credentialInStorage.status; + ChipLogDetail(Zcl, "CredentialStatus: %d, CredentialIndex: %d ", (int) credential.status, credentialIndex); + + if (DlCredentialStatus::kAvailable == credential.status) + { + ChipLogDetail(Zcl, "Found unoccupied credential "); + return true; + } + credential.credentialType = credentialInStorage.credentialType; + credential.credentialData = credentialInStorage.credentialData; + credential.createdBy = credentialInStorage.createdBy; + credential.lastModifiedBy = credentialInStorage.lastModifiedBy; + // So far there's no way to actually create the credential outside Matter, so here we always set the creation/modification + // source to Matter + credential.creationSource = DlAssetSource::kMatterIM; + credential.modificationSource = DlAssetSource::kMatterIM; + + ChipLogDetail(Zcl, "Found occupied credential [type=%u,dataSize=%u]", to_underlying(credential.credentialType), + credential.credentialData.size()); + + return true; +} + +bool LockManager::SetCredential(chip::EndpointId endpointId, uint16_t credentialIndex, chip::FabricIndex creator, + chip::FabricIndex modifier, DlCredentialStatus credentialStatus, CredentialTypeEnum credentialType, + const chip::ByteSpan & credentialData) +{ + + VerifyOrReturnValue(IsValidCredentialType(credentialType), false); + + if (CredentialTypeEnum::kProgrammingPIN == credentialType) + { + VerifyOrReturnValue(IsValidCredentialIndex(credentialIndex, credentialType), + false); // programming pin index is only index allowed to contain 0 + } + else + { + VerifyOrReturnValue(IsValidCredentialIndex(--credentialIndex, credentialType), false); // otherwise, indices are one-indexed + } + + ChipLogProgress(Zcl, + "Door Lock App: LockManager::SetCredential " + "[credentialStatus=%u,credentialType=%u,credentialDataSize=%u,creator=%d,modifier=%d]", + to_underlying(credentialStatus), to_underlying(credentialType), credentialData.size(), creator, modifier); + + auto & credentialInStorage = mLockCredentials[to_underlying(credentialType)][credentialIndex]; + + credentialInStorage.status = credentialStatus; + credentialInStorage.credentialType = credentialType; + credentialInStorage.createdBy = creator; + credentialInStorage.lastModifiedBy = modifier; + + memcpy(mCredentialData[to_underlying(credentialType)][credentialIndex], credentialData.data(), credentialData.size()); + credentialInStorage.credentialData = + chip::ByteSpan{ mCredentialData[to_underlying(credentialType)][credentialIndex], credentialData.size() }; + + // Save credential information in NVM flash + // SilabsConfig::WriteConfigValueBin(SilabsConfig::kConfigKey_Credential, reinterpret_cast(&mLockCredentials), + // sizeof(EmberAfPluginDoorLockCredentialInfo) * kMaxCredentials * kNumCredentialTypes); + + // SilabsConfig::WriteConfigValueBin(SilabsConfig::kConfigKey_CredentialData, reinterpret_cast(&mCredentialData), + // sizeof(mCredentialData)); + + ChipLogProgress(Zcl, "Successfully set the credential [credentialType=%u]", to_underlying(credentialType)); + + return true; +} + +DlStatus LockManager::GetWeekdaySchedule(chip::EndpointId endpointId, uint8_t weekdayIndex, uint16_t userIndex, + EmberAfPluginDoorLockWeekDaySchedule & schedule) +{ + + VerifyOrReturnValue(weekdayIndex > 0, DlStatus::kFailure); // indices are one-indexed + VerifyOrReturnValue(userIndex > 0, DlStatus::kFailure); // indices are one-indexed + + weekdayIndex--; + userIndex--; + + VerifyOrReturnValue(IsValidWeekdayScheduleIndex(weekdayIndex), DlStatus::kFailure); + VerifyOrReturnValue(IsValidUserIndex(userIndex), DlStatus::kFailure); + + const auto & scheduleInStorage = mWeekdaySchedule[userIndex][weekdayIndex]; + if (DlScheduleStatus::kAvailable == scheduleInStorage.status) + { + return DlStatus::kNotFound; + } + + schedule = scheduleInStorage.schedule; + + return DlStatus::kSuccess; +} + +DlStatus LockManager::SetWeekdaySchedule(chip::EndpointId endpointId, uint8_t weekdayIndex, uint16_t userIndex, + DlScheduleStatus status, DaysMaskMap daysMask, uint8_t startHour, uint8_t startMinute, + uint8_t endHour, uint8_t endMinute) +{ + + VerifyOrReturnValue(weekdayIndex > 0, DlStatus::kFailure); // indices are one-indexed + VerifyOrReturnValue(userIndex > 0, DlStatus::kFailure); // indices are one-indexed + + weekdayIndex--; + userIndex--; + + VerifyOrReturnValue(IsValidWeekdayScheduleIndex(weekdayIndex), DlStatus::kFailure); + VerifyOrReturnValue(IsValidUserIndex(userIndex), DlStatus::kFailure); + + auto & scheduleInStorage = mWeekdaySchedule[userIndex][weekdayIndex]; + + scheduleInStorage.schedule.daysMask = daysMask; + scheduleInStorage.schedule.startHour = startHour; + scheduleInStorage.schedule.startMinute = startMinute; + scheduleInStorage.schedule.endHour = endHour; + scheduleInStorage.schedule.endMinute = endMinute; + scheduleInStorage.status = status; + + // Save schedule information in NVM flash + // SilabsConfig::WriteConfigValueBin( + // SilabsConfig::kConfigKey_WeekDaySchedules, reinterpret_cast(mWeekdaySchedule), + // sizeof(EmberAfPluginDoorLockWeekDaySchedule) * LockParams.numberOfWeekdaySchedulesPerUser * LockParams.numberOfUsers); + + return DlStatus::kSuccess; +} + +DlStatus LockManager::GetYeardaySchedule(chip::EndpointId endpointId, uint8_t yearDayIndex, uint16_t userIndex, + EmberAfPluginDoorLockYearDaySchedule & schedule) +{ + VerifyOrReturnValue(yearDayIndex > 0, DlStatus::kFailure); // indices are one-indexed + VerifyOrReturnValue(userIndex > 0, DlStatus::kFailure); // indices are one-indexed + + yearDayIndex--; + userIndex--; + + VerifyOrReturnValue(IsValidYeardayScheduleIndex(yearDayIndex), DlStatus::kFailure); + VerifyOrReturnValue(IsValidUserIndex(userIndex), DlStatus::kFailure); + + const auto & scheduleInStorage = mYeardaySchedule[userIndex][yearDayIndex]; + if (DlScheduleStatus::kAvailable == scheduleInStorage.status) + { + return DlStatus::kNotFound; + } + + schedule = scheduleInStorage.schedule; + + return DlStatus::kSuccess; +} + +DlStatus LockManager::SetYeardaySchedule(chip::EndpointId endpointId, uint8_t yearDayIndex, uint16_t userIndex, + DlScheduleStatus status, uint32_t localStartTime, uint32_t localEndTime) +{ + VerifyOrReturnValue(yearDayIndex > 0, DlStatus::kFailure); // indices are one-indexed + VerifyOrReturnValue(userIndex > 0, DlStatus::kFailure); // indices are one-indexed + + yearDayIndex--; + userIndex--; + + VerifyOrReturnValue(IsValidYeardayScheduleIndex(yearDayIndex), DlStatus::kFailure); + VerifyOrReturnValue(IsValidUserIndex(userIndex), DlStatus::kFailure); + + auto & scheduleInStorage = mYeardaySchedule[userIndex][yearDayIndex]; + + scheduleInStorage.schedule.localStartTime = localStartTime; + scheduleInStorage.schedule.localEndTime = localEndTime; + scheduleInStorage.status = status; + + // Save schedule information in NVM flash + // SilabsConfig::WriteConfigValueBin( + // SilabsConfig::kConfigKey_YearDaySchedules, reinterpret_cast(mYeardaySchedule), + // sizeof(EmberAfPluginDoorLockYearDaySchedule) * LockParams.numberOfYeardaySchedulesPerUser * LockParams.numberOfUsers); + + return DlStatus::kSuccess; +} + +DlStatus LockManager::GetHolidaySchedule(chip::EndpointId endpointId, uint8_t holidayIndex, + EmberAfPluginDoorLockHolidaySchedule & schedule) +{ + VerifyOrReturnValue(holidayIndex > 0, DlStatus::kFailure); // indices are one-indexed + + holidayIndex--; + + VerifyOrReturnValue(IsValidHolidayScheduleIndex(holidayIndex), DlStatus::kFailure); + + const auto & scheduleInStorage = mHolidaySchedule[holidayIndex]; + if (DlScheduleStatus::kAvailable == scheduleInStorage.status) + { + return DlStatus::kNotFound; + } + + schedule = scheduleInStorage.schedule; + + return DlStatus::kSuccess; +} + +DlStatus LockManager::SetHolidaySchedule(chip::EndpointId endpointId, uint8_t holidayIndex, DlScheduleStatus status, + uint32_t localStartTime, uint32_t localEndTime, OperatingModeEnum operatingMode) +{ + VerifyOrReturnValue(holidayIndex > 0, DlStatus::kFailure); // indices are one-indexed + + holidayIndex--; + + VerifyOrReturnValue(IsValidHolidayScheduleIndex(holidayIndex), DlStatus::kFailure); + + auto & scheduleInStorage = mHolidaySchedule[holidayIndex]; + + scheduleInStorage.schedule.localStartTime = localStartTime; + scheduleInStorage.schedule.localEndTime = localEndTime; + scheduleInStorage.schedule.operatingMode = operatingMode; + scheduleInStorage.status = status; + + // Save schedule information in NVM flash + // SilabsConfig::WriteConfigValueBin(SilabsConfig::kConfigKey_HolidaySchedules, + // reinterpret_cast(&(mHolidaySchedule)), + // sizeof(EmberAfPluginDoorLockHolidaySchedule) * LockParams.numberOfHolidaySchedules); + + return DlStatus::kSuccess; +} + +const char * LockManager::lockStateToString(DlLockState lockState) const +{ + switch (lockState) + { + case DlLockState::kNotFullyLocked: + return "Not Fully Locked"; + case DlLockState::kLocked: + return "Locked"; + case DlLockState::kUnlocked: + return "Unlocked"; + case DlLockState::kUnlatched: + return "Unlatched"; + case DlLockState::kUnknownEnumValue: + break; + } + + return "Unknown"; +} + +bool LockManager::setLockState(chip::EndpointId endpointId, DlLockState lockState, OperationSource source, + const Nullable & fabricIdx, const Nullable & nodeId, + const Optional & pin) +{ + + // Assume pin is required until told otherwise + bool requirePin = true; + chip::app::Clusters::DoorLock::Attributes::RequirePINforRemoteOperation::Get(endpointId, &requirePin); + + // If a pin code is not given + if (!pin.HasValue()) + { + ChipLogDetail(Zcl, "Door Lock App: PIN code is not specified [endpointId=%d]", endpointId); + + // If a pin code is not required + if (!requirePin) + { + ChipLogDetail(Zcl, "Door Lock App: setting door lock state to \"%s\" [endpointId=%d]", lockStateToString(lockState), + endpointId); + + DoorLockServer::Instance().SetLockState(endpointId, lockState, source, NullNullable, NullNullable, + fabricIdx, nodeId); + + return true; + } + + ChipLogError(Zcl, "Door Lock App: PIN code is not specified, but it is required [endpointId=%d]", endpointId); + + return false; + } + + uint16_t userIndex = 0; + uint16_t credentialIndex = 0; + + // Check the PIN code + for (const auto & currentCredential : mLockCredentials[to_underlying(CredentialTypeEnum::kPin)]) + { + + if (currentCredential.status == DlCredentialStatus::kAvailable) + { + continue; + } + + if (currentCredential.credentialData.data_equal(pin.Value())) + { + + for (uint16_t i = 1; i <= kMaxUsers; ++i) + { + EmberAfPluginDoorLockUserInfo user; + if (!emberAfPluginDoorLockGetUser(endpointId, i, user)) + { + ChipLogError(Zcl, "[findUserIndexByCredential] Unable to get user: app error [userIndex=%d]", i); + return false; + } + + // Go through occupied users only + if (UserStatusEnum::kAvailable == user.userStatus) + { + continue; + } + + for (const auto & credential : user.credentials) + { + if (credential.credentialType != CredentialTypeEnum::kPin) + { + continue; + } + + EmberAfPluginDoorLockCredentialInfo credentialInfo; + if (!emberAfPluginDoorLockGetCredential(endpointId, credential.credentialIndex, CredentialTypeEnum::kPin, credentialInfo)) + { + ChipLogError(Zcl, + "[findUserIndexByCredential] Unable to get credential: app error " + "[userIndex=%d,credentialIndex=%d,credentialType=%u]", + i, credential.credentialIndex, to_underlying(CredentialTypeEnum::kPin)); + return false; + } + + if (credentialInfo.status != DlCredentialStatus::kOccupied) + { + ChipLogError(Zcl, + "[findUserIndexByCredential] Users/Credentials database error: credential index attached to user is " + "not occupied " + "[userIndex=%d,credentialIndex=%d,credentialType=%u]", + i, credential.credentialIndex, to_underlying(CredentialTypeEnum::kPin)); + return false; + } + + if (credentialInfo.credentialData.data_equal(currentCredential.credentialData)) + { + userIndex = i; + credentialIndex = credential.credentialIndex; + //userInfo = user; + } + } + } + ChipLogDetail(Zcl, + "Lock App: specified PIN code was found in the database, setting lock state to \"%s\" [endpointId=%d]", + lockStateToString(lockState), endpointId); + + LockOpCredentials userCredential[] = { { CredentialTypeEnum::kPin, credentialIndex } }; + auto userCredentials = chip::app::DataModel::MakeNullable>(userCredential); + + DoorLockServer::Instance().SetLockState(endpointId, lockState, source, + chip::app::DataModel::MakeNullable(static_cast(userIndex)), userCredentials, fabricIdx, nodeId); + + return true; + } + } + + ChipLogDetail(Zcl, + "Door Lock App: specified PIN code was not found in the database, ignoring command to set lock state to \"%s\" " + "[endpointId=%d]", + lockStateToString(lockState), endpointId); + + //err = OperationErrorEnum::kInvalidCredential; + return false; +} diff --git a/examples/lock-app/telink/src/ZclCallbacks.cpp b/examples/lock-app/telink/src/ZclCallbacks.cpp index 327db43125a035..7ed8dba67b46d3 100644 --- a/examples/lock-app/telink/src/ZclCallbacks.cpp +++ b/examples/lock-app/telink/src/ZclCallbacks.cpp @@ -1,7 +1,6 @@ /* * - * Copyright (c) 2023 Project CHIP Authors - * All rights reserved. + * Copyright (c) 2020 Project CHIP Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,119 +15,159 @@ * limitations under the License. */ -#include "AppTask.h" -#include "BoltLockManager.h" +/** + * @file + * This file implements the handler for data model messages. + */ + +#include "AppConfig.h" +#include "LockManager.h" +#include -#include +#include #include #include -#include #include #include -#include +#include + +#ifdef DIC_ENABLE +#include "dic.h" +#endif // DIC_ENABLE -using namespace ::chip; using namespace ::chip::app::Clusters; -using namespace ::chip::app::Clusters::DoorLock; +using namespace ::chip::DeviceLayer::Internal; using ::chip::app::DataModel::Nullable; -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); + ClusterId clusterId = attributePath.mClusterId; + AttributeId attributeId = attributePath.mAttributeId; + ChipLogProgress(Zcl, "Cluster callback: " ChipLogFormatMEI, ChipLogValueMEI(clusterId)); - switch (*value) + if (clusterId == DoorLock::Id && attributeId == DoorLock::Attributes::LockState::Id) { - case to_underlying(DlLockState::kLocked): - BoltLockMgr().Lock(BoltLockManager::OperationSource::kRemote); - break; - case to_underlying(DlLockState::kUnlocked): - BoltLockMgr().Unlock(BoltLockManager::OperationSource::kRemote); - break; - default: - break; + DoorLock::DlLockState lockState = *(reinterpret_cast(value)); + ChipLogProgress(Zcl, "Door lock cluster: " ChipLogFormatMEI " state %d", ChipLogValueMEI(clusterId), + to_underlying(lockState)); +#ifdef DIC_ENABLE + dic_sendmsg("lock/state", (const char *) (lockState == DoorLock::DlLockState::kLocked ? "lock" : "unlock")); +#endif // DIC_ENABLE } } -bool emberAfPluginDoorLockGetUser(EndpointId endpointId, uint16_t userIndex, EmberAfPluginDoorLockUserInfo & user) +/** @brief DoorLock Cluster Init + * + * This function is called when a specific cluster is initialized. It gives the + * application an opportunity to take care of cluster initialization procedures. + * It is called exactly once for each endpoint where cluster is present. + * + * @param endpoint Ver.: always + * + */ +void emberAfDoorLockClusterInitCallback(EndpointId endpoint) {} + +bool emberAfPluginDoorLockOnDoorLockCommand(chip::EndpointId endpointId, const Nullable & fabricIdx, + const Nullable & nodeId, const Optional & pinCode, + OperationErrorEnum & err) { - return BoltLockMgr().GetUser(userIndex, user); + ChipLogProgress(Zcl, "Door Lock App: Lock Command endpoint=%d", endpointId); + + return LockMgr().LockAction(AppEvent::kEventType_Lock, LockManager::LOCK_ACTION, LockManager::OperationSource::kRemote, + endpointId, fabricIdx, nodeId, pinCode); } -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) +bool emberAfPluginDoorLockOnDoorUnlockCommand(chip::EndpointId endpointId, const Nullable & fabricIdx, + const Nullable & nodeId, const Optional & pinCode, + OperationErrorEnum & err) +{ + ChipLogProgress(Zcl, "Door Lock App: Unlock Command endpoint=%d", endpointId); + + return LockMgr().LockAction(AppEvent::kEventType_Lock, LockManager::UNLOCK_ACTION, LockManager::OperationSource::kRemote, + endpointId, fabricIdx, nodeId, pinCode); +} + +// TODO : Add helper function to call from the Unlock command if we establish Unbolt doesn't need a different behaviour than Unlock +bool emberAfPluginDoorLockOnDoorUnboltCommand(chip::EndpointId endpointId, const Nullable & fabricIdx, + const Nullable & nodeId, const Optional & pinCode, + OperationErrorEnum & err) { - return BoltLockMgr().SetUser(userIndex, creator, modifier, userName, uniqueId, userStatus, userType, credentialRule, - credentials, totalCredentials); + ChipLogProgress(Zcl, "Door Lock App: Unbolt Command endpoint=%d", endpointId); + + return LockMgr().LockAction(AppEvent::kEventType_Lock, LockManager::UNBOLT_ACTION, LockManager::OperationSource::kRemote, + endpointId, fabricIdx, nodeId, pinCode); } -bool emberAfPluginDoorLockGetCredential(EndpointId endpointId, uint16_t credentialIndex, CredentialTypeEnum credentialType, +bool emberAfPluginDoorLockGetCredential(chip::EndpointId endpointId, uint16_t credentialIndex, CredentialTypeEnum credentialType, EmberAfPluginDoorLockCredentialInfo & credential) { - return BoltLockMgr().GetCredential(credentialIndex, credentialType, credential); + return LockMgr().GetCredential(endpointId, credentialIndex, credentialType, credential); } -bool emberAfPluginDoorLockSetCredential(EndpointId endpointId, uint16_t credentialIndex, FabricIndex creator, FabricIndex modifier, - DlCredentialStatus credentialStatus, CredentialTypeEnum credentialType, - const ByteSpan & secret) +bool emberAfPluginDoorLockSetCredential(chip::EndpointId endpointId, uint16_t credentialIndex, chip::FabricIndex creator, + chip::FabricIndex modifier, DlCredentialStatus credentialStatus, + CredentialTypeEnum credentialType, const chip::ByteSpan & credentialData) { - return BoltLockMgr().SetCredential(credentialIndex, creator, modifier, credentialStatus, credentialType, secret); + return LockMgr().SetCredential(endpointId, credentialIndex, creator, modifier, credentialStatus, credentialType, + credentialData); } -bool emberAfPluginDoorLockOnDoorLockCommand(EndpointId endpointId, const Nullable & fabricIdx, - const Nullable & nodeId, const Optional & pinCode, - OperationErrorEnum & err) +bool emberAfPluginDoorLockGetUser(chip::EndpointId endpointId, uint16_t userIndex, EmberAfPluginDoorLockUserInfo & user) { - bool result = BoltLockMgr().ValidatePIN(pinCode, err); + return LockMgr().GetUser(endpointId, userIndex, user); +} - /* Handle changing attribute state on command reception */ - if (result) - { - BoltLockMgr().Lock(BoltLockManager::OperationSource::kRemote); - } +bool emberAfPluginDoorLockSetUser(chip::EndpointId endpointId, uint16_t userIndex, chip::FabricIndex creator, + chip::FabricIndex modifier, const chip::CharSpan & userName, uint32_t uniqueId, + UserStatusEnum userStatus, UserTypeEnum usertype, CredentialRuleEnum credentialRule, + const CredentialStruct * credentials, size_t totalCredentials) +{ - return result; + return LockMgr().SetUser(endpointId, userIndex, creator, modifier, userName, uniqueId, userStatus, usertype, credentialRule, + credentials, totalCredentials); } -bool emberAfPluginDoorLockOnDoorUnlockCommand(EndpointId endpointId, const Nullable & fabricIdx, - const Nullable & nodeId, const Optional & pinCode, - OperationErrorEnum & err) +DlStatus emberAfPluginDoorLockGetSchedule(chip::EndpointId endpointId, uint8_t weekdayIndex, uint16_t userIndex, + EmberAfPluginDoorLockWeekDaySchedule & schedule) { - bool result = BoltLockMgr().ValidatePIN(pinCode, err); + return LockMgr().GetWeekdaySchedule(endpointId, weekdayIndex, userIndex, schedule); +} - /* Handle changing attribute state on command reception */ - if (result) - { - BoltLockMgr().Unlock(BoltLockManager::OperationSource::kRemote); - } +DlStatus emberAfPluginDoorLockGetSchedule(chip::EndpointId endpointId, uint8_t yearDayIndex, uint16_t userIndex, + EmberAfPluginDoorLockYearDaySchedule & schedule) +{ + return LockMgr().GetYeardaySchedule(endpointId, yearDayIndex, userIndex, schedule); +} - return result; +DlStatus emberAfPluginDoorLockGetSchedule(chip::EndpointId endpointId, uint8_t holidayIndex, + EmberAfPluginDoorLockHolidaySchedule & holidaySchedule) +{ + return LockMgr().GetHolidaySchedule(endpointId, holidayIndex, holidaySchedule); +} + +DlStatus emberAfPluginDoorLockSetSchedule(chip::EndpointId endpointId, uint8_t weekdayIndex, uint16_t userIndex, + DlScheduleStatus status, DaysMaskMap daysMask, uint8_t startHour, uint8_t startMinute, + uint8_t endHour, uint8_t endMinute) +{ + return LockMgr().SetWeekdaySchedule(endpointId, weekdayIndex, userIndex, status, daysMask, startHour, startMinute, endHour, + endMinute); +} + +DlStatus emberAfPluginDoorLockSetSchedule(chip::EndpointId endpointId, uint8_t yearDayIndex, uint16_t userIndex, + DlScheduleStatus status, uint32_t localStartTime, uint32_t localEndTime) +{ + return LockMgr().SetYeardaySchedule(endpointId, yearDayIndex, userIndex, status, localStartTime, localEndTime); +} + +DlStatus emberAfPluginDoorLockSetSchedule(chip::EndpointId endpointId, uint8_t holidayIndex, DlScheduleStatus status, + uint32_t localStartTime, uint32_t localEndTime, OperatingModeEnum operatingMode) +{ + return LockMgr().SetHolidaySchedule(endpointId, holidayIndex, status, localStartTime, localEndTime, operatingMode); } -void emberAfDoorLockClusterInitCallback(EndpointId endpoint) +void emberAfPluginDoorLockOnAutoRelock(chip::EndpointId endpointId) { - 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); + // Apply the relock state in the application control + //LockMgr().InitiateAction(AppEvent::kEventType_Lock, LockManager::LOCK_ACTION); } diff --git a/examples/platform/telink/common/include/AppEventCommon.h b/examples/platform/telink/common/include/AppEventCommon.h index 0110cd3c840e63..b9fa40ece20bc5 100644 --- a/examples/platform/telink/common/include/AppEventCommon.h +++ b/examples/platform/telink/common/include/AppEventCommon.h @@ -39,6 +39,7 @@ struct AppEvent kEventType_Install, kEventType_Contact, kEventType_Start, + kEventType_Lock }; uint16_t Type;