Skip to content

Commit

Permalink
Add unbolt support to door lock cluster (#26595)
Browse files Browse the repository at this point in the history
* add unbolt support to door lock cluster

* Adjust Darwin availability annotation to fix codegen failure.

* add unbolt callback to door lock server

* handle setting the LockState to Unlatched

* apply restyled.io changes

* set lock state to Unlocked after Unlatch operation

* update door lock tests to handle Unlatched state

* update door lock tests

* move handling of latch pulling to door lock example

* send lock operation event Unlatch for unlatched state

* send LockOperationEvent Unlatch immediately within HandleRemoteLockOperation

* fix duplicate test step

* apply restyled changes

* add reference to door lock server issue

---------

Co-authored-by: Boris Zbarsky <[email protected]>
  • Loading branch information
2 people authored and pull[bot] committed Dec 17, 2023
1 parent 032a3be commit 3562436
Show file tree
Hide file tree
Showing 15 changed files with 478 additions and 157 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2857,6 +2857,10 @@ server cluster DoorLock = 257 {
nullable CredentialStruct credential = 0;
}

request struct UnboltDoorRequest {
optional OCTET_STRING PINCode = 0;
}

response struct GetUserResponse = 28 {
INT16U userIndex = 0;
nullable CHAR_STRING userName = 1;
Expand Down Expand Up @@ -2898,6 +2902,7 @@ server cluster DoorLock = 257 {
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;
timed command UnboltDoor(UnboltDoorRequest): DefaultSuccess = 39;
}

/** Provides an interface for controlling and adjusting automatic window coverings. */
Expand Down Expand Up @@ -5246,7 +5251,7 @@ endpoint 1 {
ram attribute wrongCodeEntryLimit default = 3;
ram attribute userCodeTemporaryDisableTime default = 10;
ram attribute requirePINforRemoteOperation default = 0;
ram attribute featureMap default = 0xD13;
ram attribute featureMap default = 0x1D13;
ram attribute clusterRevision default = 6;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13359,6 +13359,14 @@
"source": "client",
"incoming": 1,
"outgoing": 0
},
{
"name": "UnboltDoor",
"code": 39,
"mfgCode": null,
"source": "client",
"incoming": 1,
"outgoing": 0
}
],
"attributes": [
Expand Down Expand Up @@ -14048,7 +14056,7 @@
"storageOption": "RAM",
"singleton": 0,
"bounded": 0,
"defaultValue": "0xD13",
"defaultValue": "0x1D13",
"reportable": 1,
"minInterval": 1,
"maxInterval": 65534,
Expand Down
2 changes: 2 additions & 0 deletions examples/lock-app/cc13x2x7_26x2x7/src/LockManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -638,6 +638,8 @@ const char * LockManager::lockStateToString(DlLockState lockState) const
return "Locked";
case DlLockState::kUnlocked:
return "Unlocked";
case DlLockState::kUnlatched:
return "Unlatched";
case DlLockState::kUnknownEnumValue:
break;
default:
Expand Down
1 change: 1 addition & 0 deletions examples/lock-app/lock-common/include/LockEndpoint.h
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ class LockEndpoint

bool Lock(const Optional<chip::ByteSpan> & pin, OperationErrorEnum & err, OperationSourceEnum opSource);
bool Unlock(const Optional<chip::ByteSpan> & pin, OperationErrorEnum & err, OperationSourceEnum opSource);
bool Unbolt(const Optional<chip::ByteSpan> & pin, OperationErrorEnum & err, OperationSourceEnum opSource);

bool GetUser(uint16_t userIndex, EmberAfPluginDoorLockUserInfo & user) const;
bool SetUser(uint16_t userIndex, chip::FabricIndex creator, chip::FabricIndex modifier, const chip::CharSpan & userName,
Expand Down
2 changes: 2 additions & 0 deletions examples/lock-app/lock-common/include/LockManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ class LockManager
OperationSourceEnum opSource);
bool Unlock(chip::EndpointId endpointId, const Optional<chip::ByteSpan> & pin, OperationErrorEnum & err,
OperationSourceEnum opSource);
bool Unbolt(chip::EndpointId endpointId, const Optional<chip::ByteSpan> & pin, OperationErrorEnum & err,
OperationSourceEnum opSource);

bool GetUser(chip::EndpointId endpointId, uint16_t userIndex, EmberAfPluginDoorLockUserInfo & user);
bool SetUser(chip::EndpointId endpointId, uint16_t userIndex, chip::FabricIndex creator, chip::FabricIndex modifier,
Expand Down
7 changes: 6 additions & 1 deletion examples/lock-app/lock-common/lock-app.matter
Original file line number Diff line number Diff line change
Expand Up @@ -2154,6 +2154,10 @@ server cluster DoorLock = 257 {
nullable CredentialStruct credential = 0;
}

request struct UnboltDoorRequest {
optional OCTET_STRING PINCode = 0;
}

response struct GetWeekDayScheduleResponse = 12 {
INT8U weekDayIndex = 0;
INT16U userIndex = 1;
Expand Down Expand Up @@ -2226,6 +2230,7 @@ server cluster DoorLock = 257 {
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;
timed command UnboltDoor(UnboltDoorRequest): DefaultSuccess = 39;
}

endpoint 0 {
Expand Down Expand Up @@ -2595,7 +2600,7 @@ endpoint 1 {
ram attribute wrongCodeEntryLimit default = 3;
ram attribute userCodeTemporaryDisableTime default = 10;
ram attribute requirePINforRemoteOperation default = 0;
ram attribute featureMap default = 0xDB3;
ram attribute featureMap default = 0x1DB3;
ram attribute clusterRevision default = 6;
}
}
Expand Down
10 changes: 9 additions & 1 deletion examples/lock-app/lock-common/lock-app.zap
Original file line number Diff line number Diff line change
Expand Up @@ -7023,6 +7023,14 @@
"source": "client",
"incoming": 1,
"outgoing": 0
},
{
"name": "UnboltDoor",
"code": 39,
"mfgCode": null,
"source": "client",
"incoming": 1,
"outgoing": 0
}
],
"attributes": [
Expand Down Expand Up @@ -7736,7 +7744,7 @@
"storageOption": "RAM",
"singleton": 0,
"bounded": 0,
"defaultValue": "0xDB3",
"defaultValue": "0x1DB3",
"reportable": 1,
"minInterval": 1,
"maxInterval": 65534,
Expand Down
11 changes: 11 additions & 0 deletions examples/lock-app/lock-common/src/LockEndpoint.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,17 @@ bool LockEndpoint::Lock(const Optional<chip::ByteSpan> & pin, OperationErrorEnum
}

bool LockEndpoint::Unlock(const Optional<chip::ByteSpan> & pin, OperationErrorEnum & err, OperationSourceEnum opSource)
{
if (DoorLockServer::Instance().SupportsUnbolt(mEndpointId))
{
// If Unbolt is supported Unlock is supposed to pull the latch
setLockState(DlLockState::kUnlatched, pin, err, opSource);
}

return setLockState(DlLockState::kUnlocked, pin, err, opSource);
}

bool LockEndpoint::Unbolt(const Optional<chip::ByteSpan> & pin, OperationErrorEnum & err, OperationSourceEnum opSource)
{
return setLockState(DlLockState::kUnlocked, pin, err, opSource);
}
Expand Down
12 changes: 12 additions & 0 deletions examples/lock-app/lock-common/src/LockManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,18 @@ bool LockManager::Unlock(chip::EndpointId endpointId, const Optional<chip::ByteS
return lockEndpoint->Unlock(pin, err, opSource);
}

bool LockManager::Unbolt(chip::EndpointId endpointId, const Optional<chip::ByteSpan> & pin, OperationErrorEnum & err,
OperationSourceEnum opSource)
{
auto lockEndpoint = getEndpoint(endpointId);
if (nullptr == lockEndpoint)
{
ChipLogError(Zcl, "Unable to unbolt the door - endpoint does not exist or not initialized [endpointId=%d]", endpointId);
return false;
}
return lockEndpoint->Unbolt(pin, err, opSource);
}

bool LockManager::GetUser(chip::EndpointId endpointId, uint16_t userIndex, EmberAfPluginDoorLockUserInfo & user)
{
auto lockEndpoint = getEndpoint(endpointId);
Expand Down
6 changes: 6 additions & 0 deletions examples/lock-app/lock-common/src/ZCLDoorLockCallbacks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@ bool emberAfPluginDoorLockOnDoorUnlockCommand(chip::EndpointId endpointId, const
return LockManager::Instance().Unlock(endpointId, pinCode, err, OperationSourceEnum::kRemote);
}

bool emberAfPluginDoorLockOnDoorUnboltCommand(chip::EndpointId endpointId, const Optional<ByteSpan> & pinCode,
OperationErrorEnum & err)
{
return LockManager::Instance().Unbolt(endpointId, pinCode, err, OperationSourceEnum::kRemote);
}

bool emberAfPluginDoorLockGetUser(chip::EndpointId endpointId, uint16_t userIndex, EmberAfPluginDoorLockUserInfo & user)
{
return LockManager::Instance().GetUser(endpointId, userIndex, user);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,13 @@ emberAfPluginDoorLockOnDoorUnlockCommand(chip::EndpointId endpointId, const Opti
return false;
}

bool __attribute__((weak))
emberAfPluginDoorLockOnDoorUnboltCommand(chip::EndpointId endpointId, const Optional<ByteSpan> & pinCode, OperationErrorEnum & err)
{
err = OperationErrorEnum::kUnspecified;
return false;
}

void __attribute__((weak)) emberAfPluginDoorLockOnAutoRelock(chip::EndpointId endpointId) {}

// =============================================================================
Expand Down
76 changes: 71 additions & 5 deletions src/app/clusters/door-lock-server/door-lock-server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -114,10 +114,21 @@ bool DoorLockServer::SetLockState(chip::EndpointId endpointId, DlLockState newLo
// DlLockState::kNotFullyLocked has no appropriate event to send. Also it is unclear whether
// it should schedule auto-relocking. So skip it here. Check for supported states explicitly
// to handle possible enum extending in future.
VerifyOrReturnError(DlLockState::kLocked == newLockState || DlLockState::kUnlocked == newLockState, success);
VerifyOrReturnError(DlLockState::kLocked == newLockState || DlLockState::kUnlocked == newLockState ||
DlLockState::kUnlatched == newLockState,
success);

// Send LockOperation event
auto opType = (DlLockState::kLocked == newLockState) ? LockOperationTypeEnum::kLock : LockOperationTypeEnum::kUnlock;
auto opType = LockOperationTypeEnum::kUnlock;

if (DlLockState::kLocked == newLockState)
{
opType = LockOperationTypeEnum::kLock;
}
else if (DlLockState::kUnlatched == newLockState)
{
opType = LockOperationTypeEnum::kUnlatch;
}

SendLockOperationEvent(endpointId, opType, opSource, OperationErrorEnum::kUnspecified, userIndex, Nullable<chip::FabricIndex>(),
Nullable<chip::NodeId>(), credentials, success);
Expand Down Expand Up @@ -3296,7 +3307,8 @@ bool DoorLockServer::HandleRemoteLockOperation(chip::app::CommandHandler * comma
const chip::app::ConcreteCommandPath & commandPath, LockOperationTypeEnum opType,
RemoteLockOpHandler opHandler, const Optional<ByteSpan> & pinCode)
{
VerifyOrDie(LockOperationTypeEnum::kLock == opType || LockOperationTypeEnum::kUnlock == opType);
VerifyOrDie(LockOperationTypeEnum::kLock == opType || LockOperationTypeEnum::kUnlock == opType ||
LockOperationTypeEnum::kUnlatch == opType);
VerifyOrDie(nullptr != opHandler);

EndpointId endpoint = commandPath.mEndpointId;
Expand Down Expand Up @@ -3421,9 +3433,27 @@ bool DoorLockServer::HandleRemoteLockOperation(chip::app::CommandHandler * comma
credentials.SetNonNull(foundCred);
}

// Failed Unlatch requests SHALL generate only a LockOperationError event with LockOperationType set to Unlock
if (LockOperationTypeEnum::kUnlatch == opType && !success)
{
opType = LockOperationTypeEnum::kUnlock;
}

SendLockOperationEvent(endpoint, opType, OperationSourceEnum::kRemote, reason, pinUserIdx,
Nullable<chip::FabricIndex>(getFabricIndex(commandObj)), Nullable<chip::NodeId>(getNodeId(commandObj)),
credentials, success);

// SHALL generate a LockOperation event with LockOperationType set to Unlatch when the unlatched state is reached and a
// LockOperation event with LockOperationType set to Unlock when the lock successfully completes the unlock. But as the current
// implementation here is sending LockOperation events immediately we're sending both events immediately.
// https://github.com/project-chip/connectedhomeip/issues/26925
if (LockOperationTypeEnum::kUnlatch == opType && success)
{
SendLockOperationEvent(endpoint, LockOperationTypeEnum::kUnlock, OperationSourceEnum::kRemote, reason, pinUserIdx,
Nullable<chip::FabricIndex>(getFabricIndex(commandObj)),
Nullable<chip::NodeId>(getNodeId(commandObj)), credentials, success);
}

return success;
}

Expand Down Expand Up @@ -3525,7 +3555,14 @@ bool emberAfDoorLockClusterUnlockDoorCallback(
{
emberAfDoorLockClusterPrintln("Received command: UnlockDoor");

if (DoorLockServer::Instance().HandleRemoteLockOperation(commandObj, commandPath, LockOperationTypeEnum::kUnlock,
LockOperationTypeEnum lockOp = LockOperationTypeEnum::kUnlock;

if (DoorLockServer::Instance().SupportsUnbolt(commandPath.mEndpointId))
{
lockOp = LockOperationTypeEnum::kUnlatch;
}

if (DoorLockServer::Instance().HandleRemoteLockOperation(commandObj, commandPath, lockOp,
emberAfPluginDoorLockOnDoorUnlockCommand, commandData.PINCode))
{
// appclusters.pdf 5.3.3.25:
Expand All @@ -3547,7 +3584,14 @@ bool emberAfDoorLockClusterUnlockWithTimeoutCallback(
{
emberAfDoorLockClusterPrintln("Received command: UnlockWithTimeout");

if (DoorLockServer::Instance().HandleRemoteLockOperation(commandObj, commandPath, LockOperationTypeEnum::kUnlock,
LockOperationTypeEnum lockOp = LockOperationTypeEnum::kUnlock;

if (DoorLockServer::Instance().SupportsUnbolt(commandPath.mEndpointId))
{
lockOp = LockOperationTypeEnum::kUnlatch;
}

if (DoorLockServer::Instance().HandleRemoteLockOperation(commandObj, commandPath, lockOp,
emberAfPluginDoorLockOnDoorUnlockCommand, commandData.PINCode))
{
// appclusters.pdf 5.3.4.3:
Expand All @@ -3563,6 +3607,28 @@ bool emberAfDoorLockClusterUnlockWithTimeoutCallback(
return true;
}

bool emberAfDoorLockClusterUnboltDoorCallback(
chip::app::CommandHandler * commandObj, const chip::app::ConcreteCommandPath & commandPath,
const chip::app::Clusters::DoorLock::Commands::UnboltDoor::DecodableType & commandData)
{
emberAfDoorLockClusterPrintln("Received command: UnboltDoor");

if (DoorLockServer::Instance().HandleRemoteLockOperation(commandObj, commandPath, LockOperationTypeEnum::kUnlock,
emberAfPluginDoorLockOnDoorUnboltCommand, commandData.PINCode))
{
// appclusters.pdf 5.3.3.25:
// The number of seconds to wait after unlocking a lock before it automatically locks again. 0=disabled. If set, unlock
// operations from any source will be timed. For one time unlock with timeout use the specific command.
uint32_t autoRelockTime = 0;

VerifyOrReturnError(DoorLockServer::Instance().GetAutoRelockTime(commandPath.mEndpointId, autoRelockTime), true);
VerifyOrReturnError(0 != autoRelockTime, true);
DoorLockServer::Instance().ScheduleAutoRelock(commandPath.mEndpointId, autoRelockTime);
}

return true;
}

bool emberAfDoorLockClusterSetUserCallback(chip::app::CommandHandler * commandObj,
const chip::app::ConcreteCommandPath & commandPath,
const chip::app::Clusters::DoorLock::Commands::SetUser::DecodableType & commandData)
Expand Down
19 changes: 19 additions & 0 deletions src/app/clusters/door-lock-server/door-lock-server.h
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,8 @@ class DoorLockServer
return GetFeatures(endpointId).Has(Feature::kUser) && SupportsAnyCredential(endpointId);
}

inline bool SupportsUnbolt(chip::EndpointId endpointId) { return GetFeatures(endpointId).Has(Feature::kUnbolt); }

bool OnFabricRemoved(chip::EndpointId endpointId, chip::FabricIndex fabricIndex);

static void DoorLockOnAutoRelockCallback(chip::System::Layer *, void * callbackContext);
Expand Down Expand Up @@ -490,6 +492,10 @@ class DoorLockServer
chip::app::CommandHandler * commandObj, const chip::app::ConcreteCommandPath & commandPath,
const chip::app::Clusters::DoorLock::Commands::UnlockWithTimeout::DecodableType & commandData);

friend bool emberAfDoorLockClusterUnboltDoorCallback(
chip::app::CommandHandler * commandObj, const chip::app::ConcreteCommandPath & commandPath,
const chip::app::Clusters::DoorLock::Commands::UnboltDoor::DecodableType & commandData);

friend bool emberAfDoorLockClusterSetHolidayScheduleCallback(
chip::app::CommandHandler * commandObj, const chip::app::ConcreteCommandPath & commandPath,
const chip::app::Clusters::DoorLock::Commands::SetHolidaySchedule::DecodableType & commandData);
Expand Down Expand Up @@ -910,6 +916,19 @@ bool emberAfPluginDoorLockOnDoorLockCommand(chip::EndpointId endpointId, const O
bool emberAfPluginDoorLockOnDoorUnlockCommand(chip::EndpointId endpointId, const Optional<chip::ByteSpan> & pinCode,
OperationErrorEnum & err);

/**
* @brief User handler for UnboltDoor command (server)
*
* @param endpointId endpoint for which UnboltDoor command is called
* @param pinCode PIN code (optional)
* @param err error code if door unbolting failed (set only if retval==false)
*
* @retval true on success
* @retval false if error happenned (err should be set to appropriate error code)
*/
bool emberAfPluginDoorLockOnDoorUnboltCommand(chip::EndpointId endpointId, const Optional<chip::ByteSpan> & pinCode,
OperationErrorEnum & err);

/**
* @brief This callback is called when the AutoRelock timer is expired.
*
Expand Down
Loading

0 comments on commit 3562436

Please sign in to comment.