diff --git a/.github/workflows/examples-telink.yaml b/.github/workflows/examples-telink.yaml index 524f1a47f5d1ac..4af591a58c91f0 100644 --- a/.github/workflows/examples-telink.yaml +++ b/.github/workflows/examples-telink.yaml @@ -151,13 +151,13 @@ jobs: - name: clean out build output run: rm -rf ./out - - name: Build example Telink (B92 retention) Lock App with DFU + - name: Build example Telink (B92) Lock App with DFU run: | ./scripts/run_in_build_env.sh \ - "./scripts/build/build_examples.py --target 'telink-tlsr9528a_retention-lock-dfu' build" + "./scripts/build/build_examples.py --target 'telink-tlsr9528a-lock-dfu' build" .environment/pigweed-venv/bin/python3 scripts/tools/memory/gh_sizes.py \ - telink tlsr9528a_retention lock-app-dfu \ - out/telink-tlsr9528a_retention-lock-dfu/zephyr/zephyr.elf \ + telink tlsr9528a lock-app-dfu \ + out/telink-tlsr9528a-lock-dfu/zephyr/zephyr.elf \ /tmp/bloat_reports/ - name: clean out build output diff --git a/examples/lock-app/telink/CMakeLists.txt b/examples/lock-app/telink/CMakeLists.txt index 10771bd371c76e..70ac496507afe7 100755 --- a/examples/lock-app/telink/CMakeLists.txt +++ b/examples/lock-app/telink/CMakeLists.txt @@ -76,7 +76,8 @@ add_definitions( target_sources(app PRIVATE src/AppTask.cpp src/ZclCallbacks.cpp - src/BoltLockManager.cpp + src/LockManager.cpp + src/LockSettingsStorage.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/include/AppConfig.h b/examples/lock-app/telink/include/AppConfig.h index 5c5eb8819cdd68..f4b55ec79564af 100644 --- a/examples/lock-app/telink/include/AppConfig.h +++ b/examples/lock-app/telink/include/AppConfig.h @@ -20,6 +20,15 @@ // ---- Lock Example App Config ---- +#define LOCK_MANAGER_CONFIG_USE_NVM_CREDENTIAL_STORAGE 1 +#define LOCK_MANAGER_ACTUATOR_MOVEMENT_TIME_MS 2000 + +#define APP_DEFAULT_USERS_COUNT 5 +#define APP_DEFAULT_CREDENTIAL_COUNT 5 +#define APP_DEFAULT_WEEKDAY_SCHEDULE_PER_USER_COUNT 5 +#define APP_DEFAULT_YEARDAY_SCHEDULE_PER_USER_COUNT 5 +#define APP_DEFAULT_HOLYDAY_SCHEDULE_PER_USER_COUNT 5 + #define APP_USE_EXAMPLE_START_BUTTON 1 #define APP_USE_BLE_START_BUTTON 0 #define APP_USE_THREAD_START_BUTTON 0 diff --git a/examples/lock-app/telink/include/AppTask.h b/examples/lock-app/telink/include/AppTask.h index c63643ca89ca6d..d5244bb7b7728f 100644 --- a/examples/lock-app/telink/include/AppTask.h +++ b/examples/lock-app/telink/include/AppTask.h @@ -18,14 +18,20 @@ #pragma once +#include "AppConfig.h" #include "AppTaskCommon.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); - private: friend AppTask & GetAppTask(void); friend class AppTaskCommon; @@ -33,7 +39,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; }; 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..72fbcc648216cd --- /dev/null +++ b/examples/lock-app/telink/include/LockManager.h @@ -0,0 +1,228 @@ +/* + * + * 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 + +#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); + + bool LockAction(int32_t appSource, Action_t aAction, OperationSource source, chip::EndpointId endpointId, + OperationErrorEnum & err, 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; + +private: + friend LockManager & LockMgr(); + State_t mState = kState_NotFulyLocked; + StateChangeCallback mStateChangeCallback = nullptr; + OperationSource mActuatorOperationSource = OperationSource::kButton; + k_timer mActuatorTimer = {}; + +#if LOCK_MANAGER_CONFIG_USE_NVM_CREDENTIAL_STORAGE + bool ReadConfigValues(); +#endif + + bool setLockState(chip::EndpointId endpointId, DlLockState lockState, OperationSource source, OperationErrorEnum & err, + 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/include/LockSettingsStorage.h b/examples/lock-app/telink/include/LockSettingsStorage.h new file mode 100644 index 00000000000000..5cb1773eea4040 --- /dev/null +++ b/examples/lock-app/telink/include/LockSettingsStorage.h @@ -0,0 +1,43 @@ +/* + * + * 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 <../Zephyr/ZephyrConfig.h> +#include + +#if LOCK_MANAGER_CONFIG_USE_NVM_CREDENTIAL_STORAGE +namespace chip { +namespace DeviceLayer { +namespace Internal { + +class LockSettingsStorage : ZephyrConfig +{ +public: + static const ZephyrConfig::Key kConfigKey_LockUser; + static const ZephyrConfig::Key kConfigKey_Credential; + static const ZephyrConfig::Key kConfigKey_LockUserName; + static const ZephyrConfig::Key kConfigKey_CredentialData; + static const ZephyrConfig::Key kConfigKey_UserCredentials; + static const ZephyrConfig::Key kConfigKey_WeekDaySchedules; + static const ZephyrConfig::Key kConfigKey_YearDaySchedules; + static const ZephyrConfig::Key kConfigKey_HolidaySchedules; +}; +} // namespace Internal +} // namespace DeviceLayer +} // namespace chip + +#endif diff --git a/examples/lock-app/telink/src/AppTask.cpp b/examples/lock-app/telink/src/AppTask.cpp index bf7fc3311cd0e8..11f8499bd5bd41 100644 --- a/examples/lock-app/telink/src/AppTask.cpp +++ b/examples/lock-app/telink/src/AppTask.cpp @@ -17,13 +17,20 @@ */ #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 @@ -42,10 +49,82 @@ 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 %d " + "[endpointId=%d]", + APP_DEFAULT_CREDENTIAL_COUNT, endpointId); + numberOfCredentialsPerUser = APP_DEFAULT_CREDENTIAL_COUNT; + } + + 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 %d [endpointId=%d]", + APP_DEFAULT_USERS_COUNT, endpointId); + numberOfUsers = APP_DEFAULT_USERS_COUNT; + } + + 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 %d [endpointId=%d]", + APP_DEFAULT_WEEKDAY_SCHEDULE_PER_USER_COUNT, endpointId); + numberOfWeekdaySchedulesPerUser = APP_DEFAULT_WEEKDAY_SCHEDULE_PER_USER_COUNT; + } + + 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 %d [endpointId=%d]", + APP_DEFAULT_YEARDAY_SCHEDULE_PER_USER_COUNT, endpointId); + numberOfYeardaySchedulesPerUser = APP_DEFAULT_YEARDAY_SCHEDULE_PER_USER_COUNT; + } + + 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 %d [endpointId=%d]", + APP_DEFAULT_HOLYDAY_SCHEDULE_PER_USER_COUNT, endpointId); + numberOfHolidaySchedules = APP_DEFAULT_HOLYDAY_SCHEDULE_PER_USER_COUNT; + } + + 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); @@ -53,86 +132,71 @@ CHIP_ERROR AppTask::Init(void) 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_NotFulyLocked: + 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; - } - - // Handle changing attribute state in the application - sAppTask.UpdateClusterState(state, source); -} - -void AppTask::UpdateClusterState(BoltLockManager::State state, BoltLockManager::OperationSource source) -{ - DlLockState newLockState; - - switch (state) - { - case BoltLockManager::State::kLockingCompleted: - newLockState = DlLockState::kLocked; + 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 BoltLockManager::State::kUnlockingCompleted: - newLockState = DlLockState::kUnlocked; + 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; - default: - newLockState = DlLockState::kNotFullyLocked; + 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; } - - 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"); - } - } - }); } 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..4a7ee03c1a6bb6 --- /dev/null +++ b/examples/lock-app/telink/src/LockManager.cpp @@ -0,0 +1,979 @@ +/* + * + * Copyright (c) 2023 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include +#include +#include +#include +#include + +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 %d is greater than %d, the maximum amount of users currently supported on this platform", + LockParams.numberOfUsers, kMaxUsers); + return APP_ERROR_ALLOCATION_FAILED; + } + + if (LockParams.numberOfCredentialsPerUser > kMaxCredentialsPerUser) + { + ChipLogError( + Zcl, + "Max number of credentials per user %d is greater than %d, the maximum amount of users currently supported on this " + "platform", + LockParams.numberOfCredentialsPerUser, kMaxCredentialsPerUser); + return APP_ERROR_ALLOCATION_FAILED; + } + + if (LockParams.numberOfWeekdaySchedulesPerUser > kMaxWeekdaySchedulesPerUser) + { + ChipLogError( + Zcl, + "Max number of schedules %d is greater than %d, the maximum amount of schedules currently supported on this platform", + LockParams.numberOfWeekdaySchedulesPerUser, kMaxWeekdaySchedulesPerUser); + return APP_ERROR_ALLOCATION_FAILED; + } + + if (LockParams.numberOfYeardaySchedulesPerUser > kMaxYeardaySchedulesPerUser) + { + ChipLogError( + Zcl, + "Max number of schedules %d is greater than %d, the maximum amount of schedules currently supported on this platform", + LockParams.numberOfYeardaySchedulesPerUser, kMaxYeardaySchedulesPerUser); + return APP_ERROR_ALLOCATION_FAILED; + } + + if (LockParams.numberOfHolidaySchedules > kMaxHolidaySchedules) + { + ChipLogError( + Zcl, + "Max number of schedules %d is greater than %d, the maximum amount of schedules currently supported on this platform", + LockParams.numberOfHolidaySchedules, kMaxHolidaySchedules); + return APP_ERROR_ALLOCATION_FAILED; + } + +#if LOCK_MANAGER_CONFIG_USE_NVM_CREDENTIAL_STORAGE + ReadConfigValues(); +#endif + + 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 button */ +bool LockManager::LockAction(int32_t appSource, Action_t aAction, OperationSource source, chip::EndpointId endpointId) +{ + bool status = false; + switch (aAction) + { + case LOCK_ACTION: + if (mState != kState_LockCompleted) + { + mState = kState_LockInitiated; + status = DoorLockServer::Instance().SetLockState(endpointId, DlLockState::kLocked, source, NullNullable, NullNullable, + NullNullable, NullNullable); + if (status) + { + LOG_INF("Lock Action: Lock action initiated successfully. Waiting for actuator"); + k_timer_start(&mActuatorTimer, K_MSEC(LOCK_MANAGER_ACTUATOR_MOVEMENT_TIME_MS), 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 = DoorLockServer::Instance().SetLockState(endpointId, DlLockState::kUnlatched, source, NullNullable, + NullNullable, NullNullable, NullNullable); + if (status) + { + LOG_INF("Unlock Action: Step 1: Unbolt completed"); + mState = kState_UnlatchInitiated; + if (mStateChangeCallback) + mStateChangeCallback(mState); + status = DoorLockServer::Instance().SetLockState(endpointId, DlLockState::kUnlocked, source, NullNullable, + NullNullable, NullNullable, NullNullable); + if (status) + { + LOG_INF("Unlock Action: Step 2: Unlock completed"); + mState = kState_UnlockInitiated; + if (mStateChangeCallback) + mStateChangeCallback(mState); + k_timer_start(&mActuatorTimer, K_MSEC(LOCK_MANAGER_ACTUATOR_MOVEMENT_TIME_MS), 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 = DoorLockServer::Instance().SetLockState(endpointId, DlLockState::kUnlocked, source, NullNullable, + NullNullable, NullNullable, NullNullable); + if (status) + { + LOG_INF("Unlock Action: Unlock initiated"); + mState = kState_UnlockInitiated; + k_timer_start(&mActuatorTimer, K_MSEC(LOCK_MANAGER_ACTUATOR_MOVEMENT_TIME_MS), 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 = DoorLockServer::Instance().SetLockState(endpointId, DlLockState::kUnlatched, source, NullNullable, + NullNullable, NullNullable, NullNullable); + if (status) + { + LOG_INF("Unbolt Action: Unbolt initiated"); + mState = kState_UnlatchInitiated; + k_timer_start(&mActuatorTimer, K_MSEC(LOCK_MANAGER_ACTUATOR_MOVEMENT_TIME_MS), 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; +} + +/* Action related to mechanical operation. Called from ZCL */ +bool LockManager::LockAction(int32_t appSource, Action_t aAction, OperationSource source, chip::EndpointId endpointId, + OperationErrorEnum & err, 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, err, fabricIdx, nodeId, pinCode); + if (status) + { + LOG_INF("Lock Action: Lock action initiated successfully. Waiting for actuator"); + k_timer_start(&mActuatorTimer, K_MSEC(LOCK_MANAGER_ACTUATOR_MOVEMENT_TIME_MS), 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, err, 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, err, fabricIdx, nodeId, pinCode); + if (status) + { + LOG_INF("Unlock Action: Step 2: Unlock completed"); + mState = kState_UnlockInitiated; + if (mStateChangeCallback) + mStateChangeCallback(mState); + k_timer_start(&mActuatorTimer, K_MSEC(LOCK_MANAGER_ACTUATOR_MOVEMENT_TIME_MS), 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, err, fabricIdx, nodeId, pinCode); + if (status) + { + LOG_INF("Unlock Action: Unlock initiated"); + mState = kState_UnlockInitiated; + k_timer_start(&mActuatorTimer, K_MSEC(LOCK_MANAGER_ACTUATOR_MOVEMENT_TIME_MS), 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, err, fabricIdx, nodeId, pinCode); + if (status) + { + LOG_INF("Unbolt Action: Unbolt initiated"); + mState = kState_UnlatchInitiated; + k_timer_start(&mActuatorTimer, K_MSEC(LOCK_MANAGER_ACTUATOR_MOVEMENT_TIME_MS), 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); +} + +#if LOCK_MANAGER_CONFIG_USE_NVM_CREDENTIAL_STORAGE +bool LockManager::ReadConfigValues() +{ + size_t outLen; + ZephyrConfig::ReadConfigValueBin(LockSettingsStorage::kConfigKey_LockUser, reinterpret_cast(&mLockUsers), + sizeof(EmberAfPluginDoorLockUserInfo) * ArraySize(mLockUsers), outLen); + + ZephyrConfig::ReadConfigValueBin(LockSettingsStorage::kConfigKey_Credential, reinterpret_cast(&mLockCredentials), + sizeof(EmberAfPluginDoorLockCredentialInfo) * kMaxCredentials * kNumCredentialTypes, outLen); + + ZephyrConfig::ReadConfigValueBin(LockSettingsStorage::kConfigKey_LockUserName, reinterpret_cast(mUserNames), + sizeof(mUserNames), outLen); + + ZephyrConfig::ReadConfigValueBin(LockSettingsStorage::kConfigKey_CredentialData, reinterpret_cast(mCredentialData), + sizeof(mCredentialData), outLen); + + ZephyrConfig::ReadConfigValueBin(LockSettingsStorage::kConfigKey_UserCredentials, reinterpret_cast(mCredentials), + sizeof(CredentialStruct) * LockParams.numberOfUsers * LockParams.numberOfCredentialsPerUser, + outLen); + + ZephyrConfig::ReadConfigValueBin( + LockSettingsStorage::kConfigKey_WeekDaySchedules, reinterpret_cast(mWeekdaySchedule), + sizeof(EmberAfPluginDoorLockWeekDaySchedule) * LockParams.numberOfWeekdaySchedulesPerUser * LockParams.numberOfUsers, + outLen); + + ZephyrConfig::ReadConfigValueBin( + LockSettingsStorage::kConfigKey_YearDaySchedules, reinterpret_cast(mYeardaySchedule), + sizeof(EmberAfPluginDoorLockYearDaySchedule) * LockParams.numberOfYeardaySchedulesPerUser * LockParams.numberOfUsers, + outLen); + + ZephyrConfig::ReadConfigValueBin(LockSettingsStorage::kConfigKey_HolidaySchedules, + reinterpret_cast(&(mHolidaySchedule)), + sizeof(EmberAfPluginDoorLockHolidaySchedule) * LockParams.numberOfHolidaySchedules, outLen); + + return true; +} +#endif + +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) + { + ChipLogProgress(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; + + ChipLogProgress(Zcl, + "Found occupied user " + "[endpoint=%d,name=\"%.*s\",credentialsCount=%u,uniqueId=%x,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); + +#if LOCK_MANAGER_CONFIG_USE_NVM_CREDENTIAL_STORAGE + // Save user information in NVM flash + CHIP_ERROR err = + ZephyrConfig::WriteConfigValueBin(LockSettingsStorage::kConfigKey_LockUser, reinterpret_cast(&mLockUsers), + sizeof(EmberAfPluginDoorLockUserInfo) * LockParams.numberOfUsers); + if (err != CHIP_NO_ERROR) + ChipLogError(Zcl, + "Failed to write kConfigKey_LockUser. User data will be resetted during reboot. Not enough storage space \n"); + + err = ZephyrConfig::WriteConfigValueBin( + LockSettingsStorage::kConfigKey_UserCredentials, reinterpret_cast(mCredentials), + sizeof(CredentialStruct) * LockParams.numberOfUsers * LockParams.numberOfCredentialsPerUser); + if (err != CHIP_NO_ERROR) + ChipLogError( + Zcl, + "Failed to write kConfigKey_UserCredentials. User data will be resetted during reboot. Not enough storage space \n"); + + ZephyrConfig::WriteConfigValueBin(LockSettingsStorage::kConfigKey_LockUserName, reinterpret_cast(mUserNames), + sizeof(mUserNames)); + if (err != CHIP_NO_ERROR) + ChipLogError( + Zcl, "Failed to write kConfigKey_LockUserName. User data will be resetted during reboot. Not enough storage space \n"); +#endif + + 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; + ChipLogProgress(Zcl, "CredentialStatus: %d, CredentialIndex: %d ", (int) credential.status, credentialIndex); + + if (DlCredentialStatus::kAvailable == credential.status) + { + ChipLogProgress(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; + + ChipLogProgress(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() }; + +#if LOCK_MANAGER_CONFIG_USE_NVM_CREDENTIAL_STORAGE + // Save credential information in NVM flash + CHIP_ERROR err = ZephyrConfig::WriteConfigValueBin( + LockSettingsStorage::kConfigKey_Credential, reinterpret_cast(&mLockCredentials), + sizeof(EmberAfPluginDoorLockCredentialInfo) * kMaxCredentials * kNumCredentialTypes); + if (err != CHIP_NO_ERROR) + ChipLogError( + Zcl, "Failed to write kConfigKey_Credential. User data will be resetted during reboot. Not enough storage space \n"); + + err = ZephyrConfig::WriteConfigValueBin(LockSettingsStorage::kConfigKey_CredentialData, + reinterpret_cast(&mCredentialData), sizeof(mCredentialData)); + if (err != CHIP_NO_ERROR) + ChipLogError( + Zcl, + "Failed to write kConfigKey_CredentialData. User data will be resetted during reboot. Not enough storage space \n"); +#endif + + 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; + +#if LOCK_MANAGER_CONFIG_USE_NVM_CREDENTIAL_STORAGE + // Save schedule information in NVM flash + CHIP_ERROR err = ZephyrConfig::WriteConfigValueBin( + LockSettingsStorage::kConfigKey_WeekDaySchedules, reinterpret_cast(mWeekdaySchedule), + sizeof(EmberAfPluginDoorLockWeekDaySchedule) * LockParams.numberOfWeekdaySchedulesPerUser * LockParams.numberOfUsers); + if (err != CHIP_NO_ERROR) + ChipLogError( + Zcl, + "Failed to write kConfigKey_WeekDaySchedules. User data will be resetted during reboot. Not enough storage space \n"); +#endif + + 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; + +#if LOCK_MANAGER_CONFIG_USE_NVM_CREDENTIAL_STORAGE + // Save schedule information in NVM flash + CHIP_ERROR err = ZephyrConfig::WriteConfigValueBin( + LockSettingsStorage::kConfigKey_YearDaySchedules, reinterpret_cast(mYeardaySchedule), + sizeof(EmberAfPluginDoorLockYearDaySchedule) * LockParams.numberOfYeardaySchedulesPerUser * LockParams.numberOfUsers); + if (err != CHIP_NO_ERROR) + ChipLogError( + Zcl, + "Failed to write kConfigKey_YearDaySchedules. User data will be resetted during reboot. Not enough storage space \n"); +#endif + + 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; + +#if LOCK_MANAGER_CONFIG_USE_NVM_CREDENTIAL_STORAGE + // Save schedule information in NVM flash + CHIP_ERROR err = ZephyrConfig::WriteConfigValueBin( + LockSettingsStorage::kConfigKey_HolidaySchedules, reinterpret_cast(&(mHolidaySchedule)), + sizeof(EmberAfPluginDoorLockHolidaySchedule) * LockParams.numberOfHolidaySchedules); + if (err != CHIP_NO_ERROR) + ChipLogError( + Zcl, + "Failed to write kConfigKey_YearDaySchedules. User data will be resetted during reboot. Not enough storage space \n"); +#endif + + 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, OperationErrorEnum & err, + const Nullable & fabricIdx, const Nullable & nodeId, + const Optional & pin) +{ + // Assume pin is required until told otherwise + bool requirePin = true; + uint16_t userIndex = 0; + uint16_t credentialIndex = 0; + chip::app::Clusters::DoorLock::Attributes::RequirePINforRemoteOperation::Get(endpointId, &requirePin); + + // If a pin code is not given + if (!pin.HasValue()) + { + ChipLogProgress(Zcl, "Door Lock App: PIN code is not specified [endpointId=%d]", endpointId); + + // If a pin code is not required + if (!requirePin) + { + ChipLogProgress(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); + err = OperationErrorEnum::kRestricted; + return false; + } + // 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, "[setLockState] Unable to get user: app error [userIndex=%d]", i); + err = OperationErrorEnum::kInvalidCredential; + 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, + "[setLockState] Unable to get credential: app error " + "[userIndex=%d,credentialIndex=%d,credentialType=%u]", + i, credential.credentialIndex, to_underlying(CredentialTypeEnum::kPin)); + err = OperationErrorEnum::kInvalidCredential; + return false; + } + + if (credentialInfo.status != DlCredentialStatus::kOccupied) + { + ChipLogError(Zcl, + "[setLockState] 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)); + err = OperationErrorEnum::kInvalidCredential; + return false; + } + + if (credentialInfo.credentialData.data_equal(currentCredential.credentialData)) + { + userIndex = i; + credentialIndex = credential.credentialIndex; + ChipLogProgress( + 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; + } + } + } + } + } + + ChipLogProgress(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/LockSettingsStorage.cpp b/examples/lock-app/telink/src/LockSettingsStorage.cpp new file mode 100644 index 00000000000000..abe83ced65357e --- /dev/null +++ b/examples/lock-app/telink/src/LockSettingsStorage.cpp @@ -0,0 +1,49 @@ +/* + * + * Copyright (c) 2023 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include + +#include + +#if LOCK_MANAGER_CONFIG_USE_NVM_CREDENTIAL_STORAGE + +namespace chip { +namespace DeviceLayer { +namespace Internal { + +#define CONFIG_KEY(key) \ + (key); \ + static_assert(sizeof(key) <= SETTINGS_MAX_NAME_LEN, "Config key too long: " key) + +#define NAMESPACE_CONFIG CHIP_DEVICE_CONFIG_SETTINGS_KEY "/cfg/" + +const ZephyrConfig::Key LockSettingsStorage::kConfigKey_LockUser = CONFIG_KEY(NAMESPACE_CONFIG "lock-user"); +const ZephyrConfig::Key LockSettingsStorage::kConfigKey_Credential = CONFIG_KEY(NAMESPACE_CONFIG "credential"); +const ZephyrConfig::Key LockSettingsStorage::kConfigKey_LockUserName = CONFIG_KEY(NAMESPACE_CONFIG "lock-user-name"); +const ZephyrConfig::Key LockSettingsStorage::kConfigKey_CredentialData = CONFIG_KEY(NAMESPACE_CONFIG "credential-data"); +const ZephyrConfig::Key LockSettingsStorage::kConfigKey_UserCredentials = CONFIG_KEY(NAMESPACE_CONFIG "user-credentials"); +const ZephyrConfig::Key LockSettingsStorage::kConfigKey_WeekDaySchedules = CONFIG_KEY(NAMESPACE_CONFIG "week-day-schedules"); +const ZephyrConfig::Key LockSettingsStorage::kConfigKey_YearDaySchedules = CONFIG_KEY(NAMESPACE_CONFIG "year-day-schedules"); +const ZephyrConfig::Key LockSettingsStorage::kConfigKey_HolidaySchedules = CONFIG_KEY(NAMESPACE_CONFIG "holiday-schedules"); +} // namespace Internal +} // namespace DeviceLayer +} // namespace chip +#endif diff --git a/examples/lock-app/telink/src/ZclCallbacks.cpp b/examples/lock-app/telink/src/ZclCallbacks.cpp index 327db43125a035..7eaf0b303fd697 100644 --- a/examples/lock-app/telink/src/ZclCallbacks.cpp +++ b/examples/lock-app/telink/src/ZclCallbacks.cpp @@ -16,119 +16,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, err, 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, err, 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, err, 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().LockAction(AppEvent::kEventType_Lock, LockManager::LOCK_ACTION, LockManager::OperationSource::kRemote, endpointId); } 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;