From 2463889c33b86a875b1ae86ebc06f8b72a6ba69f Mon Sep 17 00:00:00 2001 From: achaulk-goog <107196446+achaulk-goog@users.noreply.github.com> Date: Tue, 28 Feb 2023 12:43:34 -0500 Subject: [PATCH] Chef doorlock sample update (#24118) Add attributes, features and extra commands to support adding/removing PINs Implements basic lock logic Can lock/unlock with default pin, can add pin to existing user and use that pin --- examples/chef/common/stubs.cpp | 212 +++++++++++++++++- .../rootnode_doorlock_aNKYAreMXE.matter | 85 ++++++- .../devices/rootnode_doorlock_aNKYAreMXE.zap | 104 ++++++++- 3 files changed, 395 insertions(+), 6 deletions(-) diff --git a/examples/chef/common/stubs.cpp b/examples/chef/common/stubs.cpp index d43c46ae9317b1..75e80468b0c044 100644 --- a/examples/chef/common/stubs.cpp +++ b/examples/chef/common/stubs.cpp @@ -6,11 +6,191 @@ #ifdef EMBER_AF_PLUGIN_DOOR_LOCK_SERVER #include +class LockManager +{ +public: + static constexpr uint32_t kNumEndpoints = 1; + static constexpr uint32_t kNumUsersPerEndpoint = 2; + static constexpr uint32_t kNumCredentialsPerEndpoint = 20; + static constexpr uint32_t kNumCredentialsPerUser = 10; + static constexpr uint32_t kMaxNameLength = 32; + static constexpr uint32_t kMaxDataLength = 16; + + struct Credential + { + bool set(DlCredentialStatus status, DlCredentialType type, chip::ByteSpan newData) + { + if (newData.size() > kMaxDataLength || type != DlCredentialType::kPIN) + return false; + memcpy(data, newData.data(), newData.size()); + info = EmberAfPluginDoorLockCredentialInfo{ + status, + type, + chip::ByteSpan(data, newData.size()), + }; + return true; + } + + EmberAfPluginDoorLockCredentialInfo info = { DlCredentialStatus::kAvailable }; + uint8_t data[kMaxDataLength]; + }; + + struct User + { + void set(chip::CharSpan newName, uint32_t userId, DlUserStatus userStatus, DlUserType type, DlCredentialRule credentialRule) + { + size_t sz = std::min(sizeof(name), newName.size()); + memcpy(name, newName.data(), sz); + info = EmberAfPluginDoorLockUserInfo{ + chip::CharSpan(name, sz), chip::Span(), userId, userStatus, type, credentialRule, + }; + } + bool addCredential(uint8_t type, uint16_t index) + { + if (info.credentials.size() == kNumCredentialsPerUser) + return false; + auto & cr = credentialMap[info.credentials.size()]; + cr.CredentialType = type; + cr.CredentialIndex = index; + info.credentials = chip::Span(credentialMap, info.credentials.size() + 1); + return true; + } + + EmberAfPluginDoorLockUserInfo info = { .userStatus = DlUserStatus::kAvailable }; + char name[kMaxNameLength]; + DlCredential credentialMap[kNumCredentialsPerUser]; + }; + + struct Endpoint + { + chip::EndpointId id; + User users[kNumUsersPerEndpoint]; + Credential credentials[kNumCredentialsPerEndpoint]; + }; + + static LockManager & Instance() + { + static LockManager instance; + return instance; + } + + LockManager() { defaultInitialize(); } + + bool getUser(chip::EndpointId endpointId, uint16_t userIndex, EmberAfPluginDoorLockUserInfo & user) + { + auto ep = findEndpoint(endpointId); + if (!ep) + return false; + if (userIndex >= kNumUsersPerEndpoint) + return false; + user = ep->users[userIndex].info; + return true; + } + + bool setUser(chip::EndpointId endpointId, uint16_t userIndex, chip::FabricIndex creator, chip::FabricIndex modifier, + const chip::CharSpan & userName, uint32_t uniqueId, DlUserStatus userStatus, DlUserType usertype, + DlCredentialRule credentialRule, const DlCredential * credentials, size_t totalCredentials) + { + auto ep = findEndpoint(endpointId); + if (!ep) + return false; + if (userIndex >= kNumUsersPerEndpoint || totalCredentials > kNumCredentialsPerUser) + return false; + ep->users[userIndex].set(userName, uniqueId, userStatus, usertype, credentialRule); + ep->users[userIndex].info.creationSource = DlAssetSource::kMatterIM; + ep->users[userIndex].info.createdBy = creator; + ep->users[userIndex].info.modificationSource = DlAssetSource::kMatterIM; + ep->users[userIndex].info.lastModifiedBy = modifier; + for (size_t i = 0; i < totalCredentials; i++) + ep->users[userIndex].addCredential(credentials[i].CredentialType, credentials[i].CredentialIndex); + return true; + } + + bool getCredential(chip::EndpointId endpointId, uint16_t credentialIndex, DlCredentialType credentialType, + EmberAfPluginDoorLockCredentialInfo & credential) + { + auto ep = findEndpoint(endpointId); + if (!ep) + return false; + if (credentialIndex >= kNumCredentialsPerEndpoint) + return false; + if (credentialType != DlCredentialType::kPIN) + return false; + credential = ep->credentials[credentialIndex].info; + return true; + } + + bool setCredential(chip::EndpointId endpointId, uint16_t credentialIndex, chip::FabricIndex creator, chip::FabricIndex modifier, + DlCredentialStatus credentialStatus, DlCredentialType credentialType, const chip::ByteSpan & credentialData) + { + auto ep = findEndpoint(endpointId); + if (!ep) + return false; + if (credentialIndex >= kNumCredentialsPerEndpoint) + return false; + if (credentialType != DlCredentialType::kPIN) + return false; + auto & credential = ep->credentials[credentialIndex]; + if (!credential.set(credentialStatus, credentialType, credentialData)) + return false; + credential.info.creationSource = DlAssetSource::kMatterIM; + credential.info.createdBy = creator; + credential.info.modificationSource = DlAssetSource::kMatterIM; + credential.info.lastModifiedBy = modifier; + return true; + } + + bool checkPin(chip::EndpointId endpointId, const chip::Optional & pinCode, + chip::app::Clusters::DoorLock::DlOperationError & err) + { + if (!pinCode.HasValue()) + { + err = DlOperationError::kInvalidCredential; + return false; + } + auto ep = findEndpoint(endpointId); + if (!ep) + return false; + for (auto & pin : ep->credentials) + { + if (pin.info.status == DlCredentialStatus::kOccupied && pin.info.credentialData.data_equal(pinCode.Value())) + { + return true; + } + } + err = DlOperationError::kInvalidCredential; + return false; + } + +private: + Endpoint * findEndpoint(chip::EndpointId endpointId) + { + for (auto & e : endpoints) + { + if (e.id == endpointId) + return &e; + } + return nullptr; + } + + void defaultInitialize() + { + endpoints[0].id = 1; + uint8_t pin[6] = { 0x31, 0x32, 0x33, 0x34, 0x35, 0x36 }; + endpoints[0].credentials[chip::to_underlying(DlCredentialType::kPin)][0].set(DlCredentialStatus::kOccupied, + DlCredentialType::kPin, chip::ByteSpan(pin)); + endpoints[0].users[0].set(chip::CharSpan("default"), 1, DlUserStatus::kOccupiedEnabled, DlUserType::kUnrestrictedUser, + DlCredentialRule::kSingle); + endpoints[0].users[0].addCredential(chip::to_underlying(DlCredentialType::kPin), 1); + } + + Endpoint endpoints[kNumEndpoints]; +}; + bool emberAfPluginDoorLockOnDoorLockCommand(chip::EndpointId endpointId, const chip::Optional & pinCode, chip::app::Clusters::DoorLock::OperationErrorEnum & err) { err = OperationErrorEnum::kUnspecified; - // TBD: LockManager, check pinCode, ... return DoorLockServer::Instance().SetLockState(endpointId, DlLockState::kLocked); } @@ -18,7 +198,35 @@ bool emberAfPluginDoorLockOnDoorUnlockCommand(chip::EndpointId endpointId, const chip::app::Clusters::DoorLock::OperationErrorEnum & err) { err = OperationErrorEnum::kUnspecified; - // TBD: LockManager, check pinCode, ... return DoorLockServer::Instance().SetLockState(endpointId, DlLockState::kUnlocked); } + +bool emberAfPluginDoorLockGetUser(chip::EndpointId endpointId, uint16_t userIndex, EmberAfPluginDoorLockUserInfo & user) +{ + return LockManager::Instance().getUser(endpointId, userIndex - 1, user); +} + +bool emberAfPluginDoorLockSetUser(chip::EndpointId endpointId, uint16_t userIndex, chip::FabricIndex creator, + chip::FabricIndex modifier, const chip::CharSpan & userName, uint32_t uniqueId, + DlUserStatus userStatus, DlUserType usertype, DlCredentialRule credentialRule, + const DlCredential * credentials, size_t totalCredentials) +{ + return LockManager::Instance().setUser(endpointId, userIndex - 1, creator, modifier, userName, uniqueId, userStatus, usertype, + credentialRule, credentials, totalCredentials); +} + +bool emberAfPluginDoorLockGetCredential(chip::EndpointId endpointId, uint16_t credentialIndex, DlCredentialType credentialType, + EmberAfPluginDoorLockCredentialInfo & credential) +{ + return LockManager::Instance().getCredential(endpointId, credentialIndex - 1, credentialType, credential); +} + +bool emberAfPluginDoorLockSetCredential(chip::EndpointId endpointId, uint16_t credentialIndex, chip::FabricIndex creator, + chip::FabricIndex modifier, DlCredentialStatus credentialStatus, + DlCredentialType credentialType, const chip::ByteSpan & credentialData) +{ + return LockManager::Instance().setCredential(endpointId, credentialIndex - 1, creator, modifier, credentialStatus, + credentialType, credentialData); +} + #endif /* EMBER_AF_PLUGIN_DOOR_LOCK_SERVER */ diff --git a/examples/chef/devices/rootnode_doorlock_aNKYAreMXE.matter b/examples/chef/devices/rootnode_doorlock_aNKYAreMXE.matter index a08a7ee4a4da66..fb86627a8f02dd 100644 --- a/examples/chef/devices/rootnode_doorlock_aNKYAreMXE.matter +++ b/examples/chef/devices/rootnode_doorlock_aNKYAreMXE.matter @@ -1428,6 +1428,11 @@ server cluster DoorLock = 257 { kHolidaySchedules = 0x800; } + struct CredentialStruct { + CredentialTypeEnum credentialType = 0; + int16u credentialIndex = 1; + } + critical event DoorLockAlarm = 0 { AlarmCodeEnum alarmCode = 0; } @@ -1468,9 +1473,11 @@ server cluster DoorLock = 257 { readonly attribute nullable DlLockState lockState = 0; readonly attribute DlLockType lockType = 1; readonly attribute boolean actuatorEnabled = 2; + readonly attribute int16u numberOfTotalUsersSupported = 17; readonly attribute int16u numberOfPINUsersSupported = 18; readonly attribute int8u maxPINCodeLength = 23; readonly attribute int8u minPINCodeLength = 24; + readonly attribute int8u numberOfCredentialsSupportedPerUser = 28; attribute access(write: manage) int32u autoRelockTime = 35; attribute access(write: manage) OperatingModeEnum operatingMode = 37; readonly attribute DlSupportedOperatingModes supportedOperatingModes = 38; @@ -1493,8 +1500,82 @@ server cluster DoorLock = 257 { optional OCTET_STRING PINCode = 0; } + request struct UnlockWithTimeoutRequest { + INT16U timeout = 0; + optional OCTET_STRING PINCode = 1; + } + + request struct SetUserRequest { + DataOperationTypeEnum operationType = 0; + INT16U userIndex = 1; + nullable CHAR_STRING userName = 2; + nullable INT32U userUniqueID = 3; + nullable UserStatusEnum userStatus = 4; + nullable UserTypeEnum userType = 5; + nullable CredentialRuleEnum credentialRule = 6; + } + + request struct GetUserRequest { + INT16U userIndex = 0; + } + + request struct ClearUserRequest { + INT16U userIndex = 0; + } + + request struct SetCredentialRequest { + DataOperationTypeEnum operationType = 0; + CredentialStruct credential = 1; + LONG_OCTET_STRING credentialData = 2; + nullable INT16U userIndex = 3; + nullable UserStatusEnum userStatus = 4; + nullable UserTypeEnum userType = 5; + } + + request struct GetCredentialStatusRequest { + CredentialStruct credential = 0; + } + + request struct ClearCredentialRequest { + nullable CredentialStruct credential = 0; + } + + response struct GetUserResponse = 28 { + INT16U userIndex = 0; + nullable CHAR_STRING userName = 1; + nullable INT32U userUniqueID = 2; + nullable UserStatusEnum userStatus = 3; + nullable UserTypeEnum userType = 4; + nullable CredentialRuleEnum credentialRule = 5; + nullable CredentialStruct credentials[] = 6; + nullable fabric_idx creatorFabricIndex = 7; + nullable fabric_idx lastModifiedFabricIndex = 8; + nullable INT16U nextUserIndex = 9; + } + + response struct SetCredentialResponse = 35 { + DlStatus status = 0; + nullable INT16U userIndex = 1; + nullable INT16U nextCredentialIndex = 2; + } + + response struct GetCredentialStatusResponse = 37 { + boolean credentialExists = 0; + nullable INT16U userIndex = 1; + nullable fabric_idx creatorFabricIndex = 2; + nullable fabric_idx lastModifiedFabricIndex = 3; + nullable INT16U nextCredentialIndex = 4; + } + timed command LockDoor(LockDoorRequest): DefaultSuccess = 0; timed command UnlockDoor(UnlockDoorRequest): DefaultSuccess = 1; + timed command UnlockWithTimeout(UnlockWithTimeoutRequest): DefaultSuccess = 3; + timed command access(invoke: administer) SetUser(SetUserRequest): DefaultSuccess = 26; + command access(invoke: administer) GetUser(GetUserRequest): GetUserResponse = 27; + timed command access(invoke: administer) ClearUser(ClearUserRequest): DefaultSuccess = 29; + timed command access(invoke: administer) SetCredential(SetCredentialRequest): SetCredentialResponse = 34; + command access(invoke: administer) GetCredentialStatus(GetCredentialStatusRequest): GetCredentialStatusResponse = 36; + timed command access(invoke: administer) ClearCredential(ClearCredentialRequest): DefaultSuccess = 38; } endpoint 0 { @@ -1711,9 +1792,11 @@ endpoint 1 { ram attribute lockState default = 1; ram attribute lockType; ram attribute actuatorEnabled; + ram attribute numberOfTotalUsersSupported default = 2; ram attribute numberOfPINUsersSupported default = 2; ram attribute maxPINCodeLength default = 10; ram attribute minPINCodeLength default = 5; + ram attribute numberOfCredentialsSupportedPerUser default = 5; ram attribute autoRelockTime; ram attribute operatingMode; ram attribute supportedOperatingModes default = 0xFFF6; @@ -1724,7 +1807,7 @@ endpoint 1 { callback attribute generatedCommandList; callback attribute acceptedCommandList; callback attribute attributeList; - ram attribute featureMap default = 0x0081; + ram attribute featureMap default = 0x0181; ram attribute clusterRevision default = 6; } } diff --git a/examples/chef/devices/rootnode_doorlock_aNKYAreMXE.zap b/examples/chef/devices/rootnode_doorlock_aNKYAreMXE.zap index af99ca5266a961..c4202ef0502fc4 100644 --- a/examples/chef/devices/rootnode_doorlock_aNKYAreMXE.zap +++ b/examples/chef/devices/rootnode_doorlock_aNKYAreMXE.zap @@ -7040,6 +7040,62 @@ "source": "client", "incoming": 1, "outgoing": 0 + }, + { + "name": "UnlockWithTimeout", + "code": 3, + "mfgCode": null, + "source": "client", + "incoming": 1, + "outgoing": 1 + }, + { + "name": "SetUser", + "code": 26, + "mfgCode": null, + "source": "client", + "incoming": 1, + "outgoing": 0 + }, + { + "name": "GetUser", + "code": 27, + "mfgCode": null, + "source": "client", + "incoming": 1, + "outgoing": 0 + }, + { + "name": "ClearUser", + "code": 29, + "mfgCode": null, + "source": "client", + "incoming": 1, + "outgoing": 0 + }, + { + "name": "SetCredential", + "code": 34, + "mfgCode": null, + "source": "client", + "incoming": 1, + "outgoing": 0 + }, + { + "name": "GetCredentialStatus", + "code": 36, + "mfgCode": null, + "source": "client", + "incoming": 1, + "outgoing": 0 + }, + { + "name": "ClearCredential", + "code": 38, + "mfgCode": null, + "source": "client", + "incoming": 1, + "outgoing": 0 } ], "attributes": [ @@ -7084,6 +7140,32 @@ "define": "DOOR_LOCK_CLUSTER", "side": "server", "enabled": 1, + "commands": [ + { + "name": "GetUserResponse", + "code": 28, + "mfgCode": null, + "source": "server", + "incoming": 0, + "outgoing": 1 + }, + { + "name": "SetCredentialResponse", + "code": 35, + "mfgCode": null, + "source": "server", + "incoming": 0, + "outgoing": 1 + }, + { + "name": "GetCredentialStatusResponse", + "code": 37, + "mfgCode": null, + "source": "server", + "incoming": 0, + "outgoing": 1 + } + ], "attributes": [ { "name": "LockState", @@ -7203,11 +7285,11 @@ "mfgCode": null, "side": "server", "type": "int16u", - "included": 0, + "included": 1, "storageOption": "RAM", "singleton": 0, "bounded": 0, - "defaultValue": "0", + "defaultValue": "2", "reportable": 1, "minInterval": 1, "maxInterval": 65534, @@ -7373,6 +7455,22 @@ "maxInterval": 65534, "reportableChange": 0 }, + { + "name": "NumberOfCredentialsSupportedPerUser", + "code": 28, + "mfgCode": null, + "side": "server", + "type": "int8u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "5", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, { "name": "Language", "code": 33, @@ -7703,7 +7801,7 @@ "storageOption": "RAM", "singleton": 0, "bounded": 0, - "defaultValue": "0x0081", + "defaultValue": "0x0181", "reportable": 1, "minInterval": 1, "maxInterval": 65534,