From 2fab02a6d510f643c09b516bc782f7faf85b6342 Mon Sep 17 00:00:00 2001 From: Jiacheng Guo Date: Mon, 13 Dec 2021 12:05:19 +0800 Subject: [PATCH] [app] add binding cluster support The change adds `BindingManager` class for managing the connections to bound devices and forward events on bound clusters to the application. --- .../include/binding-handler.h | 22 ++ .../src/binding-handler.cpp | 56 +++++ examples/all-clusters-app/linux/BUILD.gn | 1 + examples/all-clusters-app/linux/main.cpp | 2 + src/app/BUILD.gn | 2 + src/app/BindingCallbackStub.cpp | 24 +++ src/app/CASESessionManager.cpp | 1 + src/app/chip_data_model.gni | 6 + src/app/clusters/bindings/BindingManager.cpp | 193 ++++++++++++++++++ src/app/clusters/bindings/BindingManager.h | 126 ++++++++++++ src/app/clusters/bindings/bindings.cpp | 106 ++++++---- src/app/util/attribute-table.cpp | 1 + src/app/util/types_stub.h | 25 ++- 13 files changed, 521 insertions(+), 44 deletions(-) create mode 100644 examples/all-clusters-app/all-clusters-common/include/binding-handler.h create mode 100644 examples/all-clusters-app/all-clusters-common/src/binding-handler.cpp create mode 100644 src/app/BindingCallbackStub.cpp create mode 100644 src/app/clusters/bindings/BindingManager.cpp create mode 100644 src/app/clusters/bindings/BindingManager.h diff --git a/examples/all-clusters-app/all-clusters-common/include/binding-handler.h b/examples/all-clusters-app/all-clusters-common/include/binding-handler.h new file mode 100644 index 00000000000000..602de945fabf44 --- /dev/null +++ b/examples/all-clusters-app/all-clusters-common/include/binding-handler.h @@ -0,0 +1,22 @@ +/* + * + * Copyright (c) 2021 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "app/clusters/bindings/BindingManager.h" + +CHIP_ERROR InitBindingHandlers(); diff --git a/examples/all-clusters-app/all-clusters-common/src/binding-handler.cpp b/examples/all-clusters-app/all-clusters-common/src/binding-handler.cpp new file mode 100644 index 00000000000000..58fd85a7c85498 --- /dev/null +++ b/examples/all-clusters-app/all-clusters-common/src/binding-handler.cpp @@ -0,0 +1,56 @@ +/* + * + * Copyright (c) 2021 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "binding-handler.h" + +#include "app-common/zap-generated/attribute-id.h" +#include "app-common/zap-generated/cluster-id.h" +#include "app-common/zap-generated/command-id.h" +#include "app/CommandSender.h" +#include "app/clusters/bindings/BindingManager.h" +#include "app/server/Server.h" +#include "app/util/af.h" +#include "lib/core/CHIPError.h" + +static void BoundDeviceChangedHandler(chip::EndpointId localEndpoint, chip::EndpointId remoteEndpoint, chip::ClusterId clusterId, + chip::OperationalDeviceProxy * peer_device) +{ + using namespace chip; + using namespace chip::app; + // Unfortunately generating both cluster server and client code is not supported. + // We need to manually compose the packet here. + // TODO: investigate code generation issue for binding + if (localEndpoint == 1 && clusterId == ZCL_ON_OFF_CLUSTER_ID) + { + uint8_t onOffValue; + emberAfReadServerAttribute(localEndpoint, clusterId, ZCL_ON_OFF_ATTRIBUTE_ID, &onOffValue, sizeof(onOffValue)); + CommandId command = onOffValue ? Clusters::OnOff::Commands::On::Id : Clusters::OnOff::Commands::Off::Id; + CommandPathParams cmdParams = { remoteEndpoint, /* group id */ 0, clusterId, command, + (chip::app::CommandPathFlags::kEndpointIdValid) }; + CommandSender sender(nullptr, peer_device->GetExchangeManager()); + sender.PrepareCommand(cmdParams); + sender.FinishCommand(); + peer_device->SendCommands(&sender); + } +} + +CHIP_ERROR InitBindingHandlers() +{ + chip::BindingManager::GetInstance().SetAppServer(&chip::Server::GetInstance()); + chip::BindingManager::GetInstance().RegisterBoundDeviceChangedHandler(BoundDeviceChangedHandler); + return CHIP_NO_ERROR; +} diff --git a/examples/all-clusters-app/linux/BUILD.gn b/examples/all-clusters-app/linux/BUILD.gn index 351b79e8f5f316..3f368742f7d485 100644 --- a/examples/all-clusters-app/linux/BUILD.gn +++ b/examples/all-clusters-app/linux/BUILD.gn @@ -17,6 +17,7 @@ import("//build_overrides/chip.gni") executable("chip-all-clusters-app") { sources = [ + "${chip_root}/examples/all-clusters-app/all-clusters-common/src/binding-handler.cpp", "${chip_root}/examples/all-clusters-app/all-clusters-common/src/bridged-actions-stub.cpp", "${chip_root}/examples/all-clusters-app/all-clusters-common/src/static-supported-modes-manager.cpp", "include/tv-callbacks.cpp", diff --git a/examples/all-clusters-app/linux/main.cpp b/examples/all-clusters-app/linux/main.cpp index 3c8d5faa5a8e39..0332759d2b502c 100644 --- a/examples/all-clusters-app/linux/main.cpp +++ b/examples/all-clusters-app/linux/main.cpp @@ -21,6 +21,7 @@ #include #include "AppMain.h" +#include "binding-handler.h" bool emberAfBasicClusterMfgSpecificPingCallback(chip::app::CommandHandler * commandObj) { @@ -71,6 +72,7 @@ static Identify gIdentify1 = { int main(int argc, char * argv[]) { VerifyOrDie(ChipLinuxAppInit(argc, argv) == 0); + VerifyOrDie(InitBindingHandlers() == CHIP_NO_ERROR); ChipLinuxAppMainLoop(); return 0; } diff --git a/src/app/BUILD.gn b/src/app/BUILD.gn index f159ae54470ce4..36c3e56831318d 100644 --- a/src/app/BUILD.gn +++ b/src/app/BUILD.gn @@ -40,6 +40,8 @@ static_library("app") { "AttributePathExpandIterator.h", "AttributePathParams.h", "BufferedReadCallback.cpp", + "BindingCallbackStub.cpp", + "CASEClient.h", "CASEClient.cpp", "CASEClient.h", "CASEClientPool.h", diff --git a/src/app/BindingCallbackStub.cpp b/src/app/BindingCallbackStub.cpp new file mode 100644 index 00000000000000..b46296b859cebb --- /dev/null +++ b/src/app/BindingCallbackStub.cpp @@ -0,0 +1,24 @@ +/* + * + * Copyright (c) 2021 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +// The stub callback for devices without binding server enabled. +// The functional callback is at app/clusters/bindings/bindings.cpp +void __attribute__((weak)) +MatterBindingClusterServerAttributeChangedCallback(const chip::app::ConcreteAttributePath & attributePath) +{} diff --git a/src/app/CASESessionManager.cpp b/src/app/CASESessionManager.cpp index c96a3319c51f63..c2571fad43711f 100644 --- a/src/app/CASESessionManager.cpp +++ b/src/app/CASESessionManager.cpp @@ -118,6 +118,7 @@ void CASESessionManager::OnSessionReleased(SessionHandle sessionHandle) VerifyOrReturn(session != nullptr, ChipLogDetail(Controller, "OnSessionReleased was called for unknown device, ignoring it.")); session->OnSessionReleased(sessionHandle); + ReleaseSession(session); } OperationalDeviceProxy * CASESessionManager::FindSession(SessionHandle session) diff --git a/src/app/chip_data_model.gni b/src/app/chip_data_model.gni index b8552565a575a3..8689bc49876430 100644 --- a/src/app/chip_data_model.gni +++ b/src/app/chip_data_model.gni @@ -148,6 +148,12 @@ template("chip_data_model") { "${_app_root}/util/ContentApp.cpp", "${_app_root}/util/ContentAppPlatform.cpp", ] + } else if (cluster == "bindings") { + sources += [ + "${_app_root}/clusters/${cluster}/${cluster}.cpp", + "${_app_root}/clusters/${cluster}/BindingManager.cpp", + "${_app_root}/clusters/${cluster}/BindingManager.h", + ] } else { sources += [ "${_app_root}/clusters/${cluster}/${cluster}.cpp" ] } diff --git a/src/app/clusters/bindings/BindingManager.cpp b/src/app/clusters/bindings/BindingManager.cpp new file mode 100644 index 00000000000000..0ae2251da40868 --- /dev/null +++ b/src/app/clusters/bindings/BindingManager.cpp @@ -0,0 +1,193 @@ +/* + * + * Copyright (c) 2021 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include + +namespace chip { + +BindingManager BindingManager::sBindingManager; + +CHIP_ERROR BindingManager::CreateBinding(FabricIndex fabric, NodeId node, EndpointId localEndpoint, ClusterId cluster) +{ + ReturnErrorCodeIf(mAppServer == nullptr, CHIP_ERROR_INCORRECT_STATE); + + return EnqueueClusterAndConnect(fabric, node, localEndpoint, cluster); +} + +CHIP_ERROR BindingManager::EnqueueClusterAndConnect(FabricIndex fabric, NodeId node, EndpointId endpoint, ClusterId cluster) +{ + FabricInfo * fabricInfo = mAppServer->GetFabricTable().FindFabricWithIndex(fabric); + PeerId peer = fabricInfo->GetPeerIdForNode(node); + CHIP_ERROR error = mPendingClusterMap.AddPendingCluster(peer, endpoint, cluster); + // We shouldn't fail to create the PendingClusterEntry after Binding Table size check + if (error != CHIP_NO_ERROR) + { + ChipLogError(AppServer, "Failed to create PendingClusterEntry"); + return error; + } + + error = mAppServer->GetCASESessionManager()->FindOrEstablishSession(fabricInfo, node, &mOnConnectedCallback, + &mOnConnectionFailureCallback); + if (error == CHIP_ERROR_NO_MEMORY) + { + // Release the least recently used entry + PendingClusterEntry * entry = mPendingClusterMap.FindLRUEntry(); + if (entry != nullptr) + { + mAppServer->GetCASESessionManager()->ReleaseSession(entry->mPeerId.GetNodeId()); + mPendingClusterMap.RemoveEntry(entry); + // Now retry + error = mAppServer->GetCASESessionManager()->FindOrEstablishSession(fabricInfo, node, &mOnConnectedCallback, + &mOnConnectionFailureCallback); + } + } + return error; +} + +void BindingManager::HandleDeviceConnected(void * context, OperationalDeviceProxy * device) +{ + BindingManager * manager = static_cast(context); + manager->HandleDeviceConnected(device); +} + +void BindingManager::HandleDeviceConnected(OperationalDeviceProxy * device) +{ + mPendingClusterMap.ForEachActiveObject([&](PendingClusterEntry * entry) -> Loop { + if (entry->mPeerId == device->GetPeerId()) + { + SyncPendingClustersToPeer(device, entry); + } + + return Loop::Continue; + }); +} + +void BindingManager::SyncPendingClustersToPeer(OperationalDeviceProxy * device, PendingClusterEntry * pendingClusters) +{ + for (uint8_t i = 0; i < pendingClusters->mNumPendingClusters; i++) + { + ClusterId cluster = pendingClusters->mPendingClusters[i].cluster; + EndpointId endpoint = pendingClusters->mPendingClusters[i].endpoint; + for (uint8_t j = 0; j < EMBER_BINDING_TABLE_SIZE; j++) + { + EmberBindingTableEntry entry; + if (emberGetBinding(j, &entry) == EMBER_SUCCESS && entry.type != EMBER_UNUSED_BINDING && entry.clusterId == cluster && + entry.local == endpoint && mBoundDeviceChangedHandler) + { + mBoundDeviceChangedHandler(entry.local, entry.remote, cluster, device); + } + } + } + mPendingClusterMap.RemoveEntry(pendingClusters); +} + +void BindingManager::HandleDeviceConnectionFailure(void * context, NodeId nodeId, CHIP_ERROR error) +{ + BindingManager * manager = static_cast(context); + manager->HandleDeviceConnectionFailure(nodeId, error); +} + +void BindingManager::HandleDeviceConnectionFailure(NodeId nodeId, CHIP_ERROR error) +{ + // Simply release the entry, the connection will be re-established on need. + ChipLogError(AppServer, "Failed to establish connection to node 0x" ChipLogFormatX64, ChipLogValueX64(nodeId)); + mAppServer->GetCASESessionManager()->ReleaseSession(nodeId); +} + +CHIP_ERROR BindingManager::DisconnectDevice(FabricIndex fabric, NodeId node) +{ + FabricInfo * fabricInfo = mAppServer->GetFabricTable().FindFabricWithIndex(fabric); + PendingClusterEntry * entry = mPendingClusterMap.FindEntry(fabricInfo->GetPeerIdForNode(node)); + + VerifyOrReturnError(entry != nullptr, CHIP_ERROR_NOT_FOUND); + + mAppServer->GetCASESessionManager()->ReleaseSession(node); + mPendingClusterMap.RemoveEntry(entry); + return CHIP_NO_ERROR; +} + +CHIP_ERROR BindingManager::NotifyBoundClusterChanged(EndpointId endpoint, ClusterId cluster) +{ + for (uint8_t i = 0; i < EMBER_BINDING_TABLE_SIZE; i++) + { + EmberBindingTableEntry entry; + CHIP_ERROR error = CHIP_NO_ERROR; + + if (emberGetBinding(i, &entry) == EMBER_SUCCESS && entry.type != EMBER_UNUSED_BINDING && entry.local == endpoint && + entry.clusterId == cluster) + { + OperationalDeviceProxy * peerDevice = mAppServer->GetCASESessionManager()->FindExistingSession(entry.nodeId); + if (peerDevice != nullptr && mBoundDeviceChangedHandler) + { + // We already have an active connection + mBoundDeviceChangedHandler(entry.local, entry.remote, cluster, peerDevice); + } + else + { + // Enqueue pending cluster and establish connection + error = EnqueueClusterAndConnect(entry.fabricIndex, entry.nodeId, entry.local, entry.clusterId); + } + } + } + return CHIP_NO_ERROR; +} + +BindingManager::PendingClusterEntry * BindingManager::PendingClusterMap::FindLRUEntry() +{ + PendingClusterEntry * lruEntry = nullptr; + mPendingClusterMap.ForEachActiveObject([&](PendingClusterEntry * entry) { + if (lruEntry == nullptr || lruEntry->mLastUpdateTime > entry->mLastUpdateTime) + { + lruEntry = entry; + } + return Loop::Continue; + }); + return lruEntry; +} + +BindingManager::PendingClusterEntry * BindingManager::PendingClusterMap::FindEntry(PeerId peerId) +{ + PendingClusterEntry * foundEntry = nullptr; + mPendingClusterMap.ForEachActiveObject([&](PendingClusterEntry * entry) { + if (entry->mPeerId == peerId) + { + foundEntry = entry; + return Loop::Break; + } + return Loop::Continue; + }); + return foundEntry; +} + +CHIP_ERROR BindingManager::PendingClusterMap::AddPendingCluster(PeerId peer, EndpointId endpoint, ClusterId cluster) +{ + PendingClusterEntry * entry = FindEntry(peer); + + if (entry == nullptr) + { + entry = mPendingClusterMap.CreateObject(); + VerifyOrReturnError(entry != nullptr, CHIP_ERROR_NO_MEMORY); + entry->mPeerId = peer; + } + entry->AddPendingCluster(endpoint, cluster); + entry->mLastUpdateTime = System::SystemClock().GetMonotonicTimestamp(); + return CHIP_NO_ERROR; +} + +} // namespace chip diff --git a/src/app/clusters/bindings/BindingManager.h b/src/app/clusters/bindings/BindingManager.h new file mode 100644 index 00000000000000..29fa3c0b2a97b6 --- /dev/null +++ b/src/app/clusters/bindings/BindingManager.h @@ -0,0 +1,126 @@ +/* + * + * Copyright (c) 2021 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include + +namespace chip { + +using BoundDeviceChangedHandler = void (*)(EndpointId localEndpoint, EndpointId remoteEndpoint, ClusterId clusterId, + OperationalDeviceProxy * peer_device); + +class BindingManager +{ +public: + BindingManager() : + mOnConnectedCallback(HandleDeviceConnected, this), mOnConnectionFailureCallback(HandleDeviceConnectionFailure, this) + {} + + void RegisterBoundDeviceChangedHandler(BoundDeviceChangedHandler handler) { mBoundDeviceChangedHandler = handler; } + + void SetAppServer(Server * appServer) { mAppServer = appServer; } + + CHIP_ERROR CreateBinding(FabricIndex fabric, NodeId node, EndpointId localEndpoint, ClusterId cluster); + + CHIP_ERROR DisconnectDevice(FabricIndex fabric, NodeId node); + + CHIP_ERROR NotifyBoundClusterChanged(EndpointId endpoint, ClusterId cluster); + + static BindingManager & GetInstance() { return sBindingManager; } + +private: + static BindingManager sBindingManager; + + static constexpr uint8_t kMaxPendingClusters = 5; + + struct ClusterPath + { + EndpointId endpoint; + ClusterId cluster; + }; + + class PendingClusterEntry + { + public: + PeerId mPeerId; + + ClusterPath mPendingClusters[kMaxPendingClusters]; + uint8_t mNumPendingClusters = 0; + + System::Clock::Timestamp mLastUpdateTime; + + void AddPendingCluster(EndpointId endpoint, ClusterId cluster) + { + if (mNumPendingClusters < kMaxPendingClusters) + { + mPendingClusters[mNumPendingClusters++] = { endpoint, cluster }; + } + else + { + mPendingClusters[mNumPendingClusters++] = { endpoint, cluster }; + mNextToOverride++; + mNextToOverride %= kMaxPendingClusters; + } + } + + private: + uint8_t mNextToOverride = 0; + }; + + class PendingClusterMap + { + public: + PendingClusterEntry * FindLRUEntry(); + + PendingClusterEntry * FindEntry(PeerId peerId); + + CHIP_ERROR AddPendingCluster(PeerId peer, EndpointId endpoint, ClusterId cluster); + + void RemoveEntry(PendingClusterEntry * entry) { mPendingClusterMap.ReleaseObject(entry); } + + template + Loop ForEachActiveObject(Function && function) + { + return mPendingClusterMap.ForEachActiveObject(std::forward(function)); + } + + private: + BitMapObjectPool mPendingClusterMap; + }; + + static void HandleDeviceConnected(void * context, OperationalDeviceProxy * device); + void HandleDeviceConnected(OperationalDeviceProxy * device); + + static void HandleDeviceConnectionFailure(void * context, NodeId nodeId, CHIP_ERROR error); + void HandleDeviceConnectionFailure(NodeId nodeId, CHIP_ERROR error); + + void SyncPendingClustersToPeer(OperationalDeviceProxy * device, PendingClusterEntry * pendingClusters); + + CHIP_ERROR EnqueueClusterAndConnect(FabricIndex fabric, NodeId node, EndpointId endpoint, ClusterId cluster); + + PendingClusterMap mPendingClusterMap; + BoundDeviceChangedHandler mBoundDeviceChangedHandler; + Server * mAppServer; + + Callback::Callback mOnConnectedCallback; + Callback::Callback mOnConnectionFailureCallback; +}; + +} // namespace chip diff --git a/src/app/clusters/bindings/bindings.cpp b/src/app/clusters/bindings/bindings.cpp index 73c06996717139..2e7dbecd17ea88 100644 --- a/src/app/clusters/bindings/bindings.cpp +++ b/src/app/clusters/bindings/bindings.cpp @@ -25,39 +25,15 @@ #include #include #include +#include #include +#include #include using namespace chip; using namespace chip::app::Clusters::Binding; -EmberStatus prepareBinding(EmberBindingTableEntry & binding, NodeId nodeId, GroupId groupId, EndpointId endpointId, - ClusterId clusterId) -{ - if (groupId && nodeId) - { - return EMBER_BAD_ARGUMENT; - } - - binding.clusterId = clusterId; - binding.local = emberAfCurrentCommand()->apsFrame->destinationEndpoint; - binding.networkIndex = 0; - - if (groupId) - { - binding.type = EMBER_MULTICAST_BINDING; - binding.groupId = groupId; - binding.remote = 0; - } - else - { - binding.type = EMBER_UNICAST_BINDING; - binding.nodeId = nodeId; - binding.remote = endpointId; - } - - return EMBER_SUCCESS; -} +// TODO: add binding table to the persistent storage EmberStatus getBindingIndex(EmberBindingTableEntry & newEntry, uint8_t * bindingIndex) { @@ -94,20 +70,21 @@ EmberStatus getUnusedBindingIndex(uint8_t * bindingIndex) bool emberAfBindingClusterBindCallback(app::CommandHandler * commandObj, const app::ConcreteCommandPath & commandPath, const Commands::Bind::DecodableType & commandData) { - auto & nodeId = commandData.nodeId; - auto & groupId = commandData.groupId; - auto & endpointId = commandData.endpointId; - auto & clusterId = commandData.clusterId; + NodeId nodeId = commandData.nodeId; + GroupId groupId = commandData.groupId; + ClusterId clusterId = commandData.clusterId; + EndpointId remoteEndpoint = commandData.endpointId; + EndpointId localEndpoint = commandPath.mEndpointId; + FabricIndex fabricIndex = commandObj->GetAccessingFabricIndex(); ChipLogDetail(Zcl, "RX: BindCallback"); - EmberBindingTableEntry bindingEntry; - if (prepareBinding(bindingEntry, nodeId, groupId, endpointId, clusterId) != EMBER_SUCCESS) + if (groupId && nodeId) { - emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_MALFORMED_COMMAND); - return true; + ChipLogError(Zcl, "Binding: Invalid request"); } + EmberBindingTableEntry bindingEntry(fabricIndex, nodeId, groupId, localEndpoint, remoteEndpoint, clusterId); uint8_t bindingIndex; if (getBindingIndex(bindingEntry, &bindingIndex) != EMBER_NOT_FOUND) { @@ -121,28 +98,49 @@ bool emberAfBindingClusterBindCallback(app::CommandHandler * commandObj, const a return true; } + if (BindingManager::GetInstance().CreateBinding(fabricIndex, nodeId, localEndpoint, clusterId) != CHIP_NO_ERROR) + { + emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_FAILURE); + return true; + } emberSetBinding(bindingIndex, &bindingEntry); emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_SUCCESS); return true; } +uint8_t GetNumberOfBindingForNode(FabricIndex fabric, NodeId node) +{ + uint8_t numBinding = 0; + EmberBindingTableEntry entry; + for (uint8_t i = 0; i < EMBER_BINDING_TABLE_SIZE; i++) + { + if (emberGetBinding(i, &entry) == EMBER_SUCCESS && entry.type != EMBER_UNUSED_BINDING && entry.fabricIndex == fabric && + entry.nodeId == node) + { + numBinding++; + } + } + return numBinding; +} + bool emberAfBindingClusterUnbindCallback(app::CommandHandler * commandObj, const app::ConcreteCommandPath & commandPath, const Commands::Unbind::DecodableType & commandData) { - auto & nodeId = commandData.nodeId; - auto & groupId = commandData.groupId; - auto & endpointId = commandData.endpointId; - auto & clusterId = commandData.clusterId; + NodeId nodeId = commandData.nodeId; + GroupId groupId = commandData.groupId; + ClusterId clusterId = commandData.clusterId; + EndpointId remoteEndpoint = commandData.endpointId; + EndpointId localEndpoint = commandPath.mEndpointId; + FabricIndex fabricIndex = commandObj->GetAccessingFabricIndex(); ChipLogDetail(Zcl, "RX: UnbindCallback"); - EmberBindingTableEntry bindingEntry; - if (prepareBinding(bindingEntry, nodeId, groupId, endpointId, clusterId) != EMBER_SUCCESS) + if (groupId && nodeId) { - emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_MALFORMED_COMMAND); - return true; + ChipLogError(Zcl, "Binding: Invalid request"); } + EmberBindingTableEntry bindingEntry(fabricIndex, nodeId, groupId, localEndpoint, remoteEndpoint, clusterId); uint8_t bindingIndex; if (getBindingIndex(bindingEntry, &bindingIndex) != EMBER_SUCCESS) { @@ -150,9 +148,31 @@ bool emberAfBindingClusterUnbindCallback(app::CommandHandler * commandObj, const return true; } + if (GetNumberOfBindingForNode(fabricIndex, nodeId) == 1) + { + CHIP_ERROR err = BindingManager::GetInstance().DisconnectDevice(fabricIndex, nodeId); + if (err != CHIP_NO_ERROR) + { + emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_FAILURE); + } + } + emberDeleteBinding(bindingIndex); emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_SUCCESS); return true; } void MatterBindingPluginServerInitCallback() {} + +void MatterBindingClusterServerAttributeChangedCallback(const chip::app::ConcreteAttributePath & attributePath) +{ + EmberBindingTableEntry entry; + for (uint8_t i = 0; i < EMBER_BINDING_TABLE_SIZE; i++) + { + if (emberGetBinding(i, &entry) == EMBER_SUCCESS && entry.type != EMBER_UNUSED_BINDING && + entry.local == attributePath.mEndpointId && entry.clusterId == attributePath.mClusterId) + { + BindingManager::GetInstance().NotifyBoundClusterChanged(entry.local, entry.clusterId); + } + } +} diff --git a/src/app/util/attribute-table.cpp b/src/app/util/attribute-table.cpp index da1e2dfcd0e50b..37d592ce54e0aa 100644 --- a/src/app/util/attribute-table.cpp +++ b/src/app/util/attribute-table.cpp @@ -601,6 +601,7 @@ EmberAfStatus emAfWriteAttribute(EndpointId endpoint, ClusterId cluster, Attribu emAfSaveAttributeToToken(data, endpoint, cluster, metadata); MatterReportingAttributeChangeCallback(endpoint, cluster, attributeID, mask, manufacturerCode, dataType, data); + MatterBindingClusterServerAttributeChangedCallback(attributePath); // Post write attribute callback for all attributes changes, regardless // of cluster. diff --git a/src/app/util/types_stub.h b/src/app/util/types_stub.h index 6410c6c032e8f9..2967ce7c311922 100644 --- a/src/app/util/types_stub.h +++ b/src/app/util/types_stub.h @@ -495,8 +495,30 @@ enum */ struct EmberBindingTableEntry { + EmberBindingTableEntry() = default; + + EmberBindingTableEntry(chip::FabricIndex fabric, chip::NodeId node, chip::GroupId group, chip::EndpointId localEndpoint, + chip::EndpointId remoteEndpoint, chip::ClusterId cluster) : + fabricIndex(fabric), + local(localEndpoint), clusterId(cluster), remote(remoteEndpoint) + { + if (group) + { + type = EMBER_MULTICAST_BINDING; + groupId = group; + remote = 0; + } + else + { + type = EMBER_UNICAST_BINDING; + nodeId = node; + } + } + /** The type of binding. */ EmberBindingType type; + + chip::FabricIndex fabricIndex; /** The endpoint on the local node. */ chip::EndpointId local; /** A cluster ID that matches one from the local endpoint's simple descriptor. @@ -539,7 +561,8 @@ struct EmberBindingTableEntry return false; } - return local == other.local && clusterId == other.clusterId && remote == other.remote && networkIndex == other.networkIndex; + return fabricIndex == other.fabricIndex && local == other.local && clusterId == other.clusterId && remote == other.remote && + networkIndex == other.networkIndex; } };