diff --git a/examples/fabric-admin/BUILD.gn b/examples/fabric-admin/BUILD.gn index 58d9cee914e410..e95d6cb7c8f555 100644 --- a/examples/fabric-admin/BUILD.gn +++ b/examples/fabric-admin/BUILD.gn @@ -82,6 +82,8 @@ static_library("fabric-admin-utils") { "commands/pairing/ToTLVCert.cpp", "device_manager/BridgeSubscription.cpp", "device_manager/BridgeSubscription.h", + "device_manager/CommissionerControl.cpp", + "device_manager/CommissionerControl.h", "device_manager/DeviceManager.cpp", "device_manager/DeviceManager.h", "device_manager/DeviceSubscription.cpp", diff --git a/examples/fabric-admin/commands/clusters/ClusterCommand.h b/examples/fabric-admin/commands/clusters/ClusterCommand.h index edf2302219fa1c..7c66bebcecfbea 100644 --- a/examples/fabric-admin/commands/clusters/ClusterCommand.h +++ b/examples/fabric-admin/commands/clusters/ClusterCommand.h @@ -84,7 +84,6 @@ class ClusterCommand : public InteractionModelCommands, public ModelCommand, pub if (data != nullptr) { LogErrorOnFailure(RemoteDataModelLogger::LogCommandAsJSON(path, data)); - DeviceMgr().HandleCommandResponse(path, *data); } } diff --git a/examples/fabric-admin/device_manager/CommissionerControl.cpp b/examples/fabric-admin/device_manager/CommissionerControl.cpp new file mode 100644 index 00000000000000..b919ceb0683192 --- /dev/null +++ b/examples/fabric-admin/device_manager/CommissionerControl.cpp @@ -0,0 +1,141 @@ +#include "CommissionerControl.h" +#include + +using namespace ::chip; + +void CommissionerControl::Init(Controller::DeviceCommissioner & commissioner, NodeId nodeId, EndpointId endpointId) +{ + // Ensure that mCommissioner is not already initialized + VerifyOrDie(mCommissioner == nullptr); + + ChipLogProgress(NotSpecified, "Initilize CommissionerControl"); + mCommissioner = &commissioner; + mDestinationId = nodeId; + mEndpointId = endpointId; +} + +CHIP_ERROR CommissionerControl::RequestCommissioningApproval(uint64_t requestId, uint16_t vendorId, uint16_t productId, + Optional label) +{ + VerifyOrReturnError(mCommissioner != nullptr, CHIP_ERROR_INCORRECT_STATE); + + ChipLogProgress(NotSpecified, "Sending RequestCommissioningApproval to node " ChipLogFormatX64, + ChipLogValueX64(mDestinationId)); + + mRequestCommissioningApproval.requestID = requestId; + mRequestCommissioningApproval.vendorID = static_cast(vendorId); + mRequestCommissioningApproval.productID = productId; + + if (label.HasValue()) + { + VerifyOrReturnError(label.Value().size() <= kMaxDeviceLabelLength, CHIP_ERROR_BUFFER_TOO_SMALL); + memcpy(mLabelBuffer, label.Value().data(), label.Value().size()); + mRequestCommissioningApproval.label = Optional>(CharSpan(mLabelBuffer, label.Value().size())); + } + + mCommandType = CommandType::kRequestCommissioningApproval; + return mCommissioner->GetConnectedDevice(mDestinationId, &mOnDeviceConnectedCallback, &mOnDeviceConnectionFailureCallback); +} + +CHIP_ERROR CommissionerControl::CommissionNode(uint64_t requestId, uint16_t responseTimeoutSeconds) +{ + VerifyOrReturnError(mCommissioner != nullptr, CHIP_ERROR_INCORRECT_STATE); + + ChipLogProgress(NotSpecified, "Sending CommissionNode to node " ChipLogFormatX64, ChipLogValueX64(mDestinationId)); + + mCommissionNode.requestID = requestId; + mCommissionNode.responseTimeoutSeconds = responseTimeoutSeconds; + + mCommandType = CommandType::kCommissionNode; + return mCommissioner->GetConnectedDevice(mDestinationId, &mOnDeviceConnectedCallback, &mOnDeviceConnectionFailureCallback); +} + +void CommissionerControl::OnResponse(app::CommandSender * client, const app::ConcreteCommandPath & path, + const app::StatusIB & status, TLV::TLVReader * data) +{ + ChipLogProgress(NotSpecified, "CommissionerControl: OnResponse."); + + CHIP_ERROR error = status.ToChipError(); + if (CHIP_NO_ERROR != error) + { + ChipLogError(NotSpecified, "Response Failure: %s", ErrorStr(error)); + return; + } + + if (data != nullptr) + { + DeviceMgr().HandleCommandResponse(path, *data); + } +} + +void CommissionerControl::OnError(const app::CommandSender * client, CHIP_ERROR error) +{ + // Handle the error, then reset mCommandSender + ChipLogProgress(NotSpecified, "CommissionerControl: OnError: Error: %s", ErrorStr(error)); +} + +void CommissionerControl::OnDone(app::CommandSender * client) +{ + ChipLogProgress(NotSpecified, "CommissionerControl: OnDone."); + + switch (mCommandType) + { + case CommandType::kRequestCommissioningApproval: + ChipLogProgress(NotSpecified, "CommissionerControl: Command RequestCommissioningApproval has been successfully processed."); + break; + + case CommandType::kCommissionNode: + ChipLogProgress(NotSpecified, "CommissionerControl: Command CommissionNode has been successfully processed."); + break; + + default: + ChipLogError(NotSpecified, "CommissionerControl: Unknown or unhandled command type in OnDone."); + break; + } + + // Reset command type to undefined after processing is done + mCommandType = CommandType::kUndefined; + + // Ensure that mCommandSender is cleaned up after it is done + mCommandSender.reset(); +} + +CHIP_ERROR CommissionerControl::SendCommandForType(CommandType commandType, DeviceProxy * device) +{ + switch (commandType) + { + case CommandType::kRequestCommissioningApproval: + return SendCommand(device, mEndpointId, app::Clusters::CommissionerControl::Id, + app::Clusters::CommissionerControl::Commands::RequestCommissioningApproval::Id, + mRequestCommissioningApproval); + case CommandType::kCommissionNode: + return SendCommand(device, mEndpointId, app::Clusters::CommissionerControl::Id, + app::Clusters::CommissionerControl::Commands::CommissionNode::Id, mCommissionNode); + default: + return CHIP_ERROR_INVALID_ARGUMENT; + } +} + +void CommissionerControl::OnDeviceConnectedFn(void * context, Messaging::ExchangeManager & exchangeMgr, + const SessionHandle & sessionHandle) +{ + CommissionerControl * self = reinterpret_cast(context); + VerifyOrReturn(self != nullptr, ChipLogError(NotSpecified, "OnDeviceConnectedFn: context is null")); + + OperationalDeviceProxy device(&exchangeMgr, sessionHandle); + + CHIP_ERROR err = self->SendCommandForType(self->mCommandType, &device); + if (err != CHIP_NO_ERROR) + { + ChipLogError(NotSpecified, "Failed to send CommissionerControl command."); + self->OnDone(nullptr); + } +} + +void CommissionerControl::OnDeviceConnectionFailureFn(void * context, const ScopedNodeId & peerId, CHIP_ERROR err) +{ + LogErrorOnFailure(err); + CommissionerControl * self = reinterpret_cast(context); + VerifyOrReturn(self != nullptr, ChipLogError(NotSpecified, "OnDeviceConnectedFn: context is null")); + self->OnDone(nullptr); +} diff --git a/examples/fabric-admin/device_manager/CommissionerControl.h b/examples/fabric-admin/device_manager/CommissionerControl.h new file mode 100644 index 00000000000000..1fad323e125c2a --- /dev/null +++ b/examples/fabric-admin/device_manager/CommissionerControl.h @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2024 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 + +/** + * @class CommissionerControl + * @brief This class handles sending CHIP commands related to commissioning, including sending + * commissioning approval requests and commissioning nodes. + * + * The class acts as a command sender and implements the `chip::app::CommandSender::Callback` interface + * to handle responses, errors, and completion events for the commands it sends. It relies on external + * CCTRL delegate and server mechanisms to manage the overall protocol and state transitions, including + * processing the CommissioningRequestResult and invoking CommissionNode. + */ +class CommissionerControl : public chip::app::CommandSender::Callback +{ +public: + CommissionerControl() : + mOnDeviceConnectedCallback(OnDeviceConnectedFn, this), mOnDeviceConnectionFailureCallback(OnDeviceConnectionFailureFn, this) + {} + + /** + * @brief Initializes the CommissionerControl with a DeviceCommissioner, NodeId, and EndpointId. + * + * @param commissioner The DeviceCommissioner to use for the commissioning process. + * @param nodeId The node ID of the remote fabric bridge. + * @param endpointId The endpoint on which to send CommissionerControl commands. + */ + void Init(chip::Controller::DeviceCommissioner & commissioner, chip::NodeId nodeId, chip::EndpointId endpointId); + + /** + * @brief Sends a RequestCommissioningApproval command to the device. + * + * @param requestId The unique request ID. + * @param vendorId The vendor ID of the device. + * @param productId The product ID of the device. + * @param label Optional label for the device. + * @return CHIP_ERROR CHIP_NO_ERROR on success, or an appropriate error code on failure. + */ + CHIP_ERROR RequestCommissioningApproval(uint64_t requestId, uint16_t vendorId, uint16_t productId, + chip::Optional label); + /** + * @brief Sends a CommissionNode command to the device. + * + * @param requestId The unique request ID. + * @param responseTimeoutSeconds Timeout for the response in seconds. + * @return CHIP_ERROR CHIP_NO_ERROR on success, or an appropriate error code on failure. + */ + CHIP_ERROR CommissionNode(uint64_t requestId, uint16_t responseTimeoutSeconds); + + /////////// CommandSender Callback Interface ///////// + virtual void OnResponse(chip::app::CommandSender * client, const chip::app::ConcreteCommandPath & path, + const chip::app::StatusIB & status, chip::TLV::TLVReader * data) override; + + virtual void OnError(const chip::app::CommandSender * client, CHIP_ERROR error) override; + + virtual void OnDone(chip::app::CommandSender * client) override; + +private: + static constexpr uint16_t kMaxDeviceLabelLength = 64; + + enum class CommandType : uint8_t + { + kUndefined = 0, + kRequestCommissioningApproval = 1, + kCommissionNode = 2, + }; + + template + CHIP_ERROR SendCommand(chip::DeviceProxy * device, chip::EndpointId endpointId, chip::ClusterId clusterId, + chip::CommandId commandId, const T & value) + { + chip::app::CommandPathParams commandPath = { endpointId, clusterId, commandId, + (chip::app::CommandPathFlags::kEndpointIdValid) }; + mCommandSender = std::make_unique(this, device->GetExchangeManager(), false, false, + device->GetSecureSession().Value()->AllowsLargePayload()); + + VerifyOrReturnError(mCommandSender != nullptr, CHIP_ERROR_NO_MEMORY); + + chip::app::CommandSender::AddRequestDataParameters addRequestDataParams(chip::NullOptional); + ReturnErrorOnFailure(mCommandSender->AddRequestData(commandPath, value, addRequestDataParams)); + ReturnErrorOnFailure(mCommandSender->SendCommandRequest(device->GetSecureSession().Value())); + + return CHIP_NO_ERROR; + } + + CHIP_ERROR SendCommandForType(CommandType commandType, chip::DeviceProxy * device); + + static void OnDeviceConnectedFn(void * context, chip::Messaging::ExchangeManager & exchangeMgr, + const chip::SessionHandle & sessionHandle); + static void OnDeviceConnectionFailureFn(void * context, const chip::ScopedNodeId & peerId, CHIP_ERROR error); + + // Private data members + chip::Controller::DeviceCommissioner * mCommissioner = nullptr; + std::unique_ptr mCommandSender; + chip::NodeId mDestinationId = chip::kUndefinedNodeId; + chip::EndpointId mEndpointId = chip::kRootEndpointId; + CommandType mCommandType = CommandType::kUndefined; + char mLabelBuffer[kMaxDeviceLabelLength]; + + chip::Callback::Callback mOnDeviceConnectedCallback; + chip::Callback::Callback mOnDeviceConnectionFailureCallback; + + chip::app::Clusters::CommissionerControl::Commands::RequestCommissioningApproval::Type mRequestCommissioningApproval; + chip::app::Clusters::CommissionerControl::Commands::CommissionNode::Type mCommissionNode; +}; diff --git a/examples/fabric-admin/device_manager/DeviceManager.cpp b/examples/fabric-admin/device_manager/DeviceManager.cpp index 45a40942a27be9..0d7d656a4e685e 100644 --- a/examples/fabric-admin/device_manager/DeviceManager.cpp +++ b/examples/fabric-admin/device_manager/DeviceManager.cpp @@ -26,7 +26,6 @@ #include using namespace chip; -using namespace chip::app::Clusters; namespace { @@ -67,6 +66,12 @@ void DeviceManager::UpdateLastUsedNodeId(NodeId nodeId) } } +void DeviceManager::SetRemoteBridgeNodeId(chip::NodeId nodeId) +{ + mRemoteBridgeNodeId = nodeId; + mCommissionerControl.Init(PairingManager::Instance().CurrentCommissioner(), mRemoteBridgeNodeId, kAggregatorEndpointId); +} + void DeviceManager::AddSyncedDevice(const Device & device) { mSyncedDevices.insert(device); @@ -230,7 +235,7 @@ void DeviceManager::HandleReadSupportedDeviceCategories(TLV::TLVReader & data) { ChipLogProgress(NotSpecified, "Attribute SupportedDeviceCategories detected."); - BitMask value; + BitMask value; CHIP_ERROR error = app::DataModel::Decode(data, value); if (error != CHIP_NO_ERROR) { @@ -238,7 +243,7 @@ void DeviceManager::HandleReadSupportedDeviceCategories(TLV::TLVReader & data) return; } - if (value.Has(CommissionerControl::SupportedDeviceCategoryBitmap::kFabricSynchronization)) + if (value.Has(app::Clusters::CommissionerControl::SupportedDeviceCategoryBitmap::kFabricSynchronization)) { ChipLogProgress(NotSpecified, "Remote Fabric-Bridge supports Fabric Synchronization, start reverse commissioning."); RequestCommissioningApproval(); @@ -254,19 +259,24 @@ void DeviceManager::RequestCommissioningApproval() uint16_t vendorId = static_cast(CHIP_DEVICE_CONFIG_DEVICE_VENDOR_ID); uint16_t productId = static_cast(CHIP_DEVICE_CONFIG_DEVICE_PRODUCT_ID); - StringBuilder commandBuilder; - commandBuilder.Add("commissionercontrol request-commissioning-approval "); - commandBuilder.AddFormat("%lu %u %u %lu %d", requestId, vendorId, productId, mRemoteBridgeNodeId, kAggregatorEndpointId); + CHIP_ERROR error = mCommissionerControl.RequestCommissioningApproval(requestId, vendorId, productId, NullOptional); + + if (error != CHIP_NO_ERROR) + { + ChipLogError(NotSpecified, + "Failed to request commissioning-approval to the remote bridge (NodeId: %lu). Error: %" CHIP_ERROR_FORMAT, + mRemoteBridgeNodeId, error.Format()); + return; + } mRequestId = requestId; - PushCommand(commandBuilder.c_str()); } void DeviceManager::HandleCommissioningRequestResult(TLV::TLVReader & data) { ChipLogProgress(NotSpecified, "CommissioningRequestResult event received."); - CommissionerControl::Events::CommissioningRequestResult::DecodableType value; + app::Clusters::CommissionerControl::Events::CommissioningRequestResult::DecodableType value; CHIP_ERROR error = app::DataModel::Decode(data, value); if (error != CHIP_NO_ERROR) { @@ -372,16 +382,20 @@ void DeviceManager::SendCommissionNodeRequest(uint64_t requestId, uint16_t respo { ChipLogProgress(NotSpecified, "Request the Commissioner Control Server to begin commissioning a previously approved request."); - StringBuilder commandBuilder; - commandBuilder.Add("commissionercontrol commission-node "); - commandBuilder.AddFormat("%lu %u %lu %d", requestId, responseTimeoutSeconds, mRemoteBridgeNodeId, kAggregatorEndpointId); + CHIP_ERROR error = mCommissionerControl.CommissionNode(requestId, responseTimeoutSeconds); - PushCommand(commandBuilder.c_str()); + if (error != CHIP_NO_ERROR) + { + ChipLogError(NotSpecified, + "Failed to send CommissionNode command to the remote bridge (NodeId: %lu). Error: %" CHIP_ERROR_FORMAT, + mRemoteBridgeNodeId, error.Format()); + return; + } } void DeviceManager::HandleReverseOpenCommissioningWindow(TLV::TLVReader & data) { - CommissionerControl::Commands::ReverseOpenCommissioningWindow::DecodableType value; + app::Clusters::CommissionerControl::Commands::ReverseOpenCommissioningWindow::DecodableType value; CHIP_ERROR error = app::DataModel::Decode(data, value); if (error != CHIP_NO_ERROR) @@ -405,7 +419,8 @@ void DeviceManager::HandleReverseOpenCommissioningWindow(TLV::TLVReader & data) void DeviceManager::HandleAttributeData(const app::ConcreteDataAttributePath & path, TLV::TLVReader & data) { - if (path.mClusterId == Descriptor::Id && path.mAttributeId == Descriptor::Attributes::PartsList::Id) + if (path.mClusterId == app::Clusters::Descriptor::Id && + path.mAttributeId == app::Clusters::Descriptor::Attributes::PartsList::Id) { HandleAttributePartsListUpdate(data); return; @@ -414,8 +429,8 @@ void DeviceManager::HandleAttributeData(const app::ConcreteDataAttributePath & p void DeviceManager::HandleEventData(const app::EventHeader & header, TLV::TLVReader & data) { - if (header.mPath.mClusterId == CommissionerControl::Id && - header.mPath.mEventId == CommissionerControl::Events::CommissioningRequestResult::Id) + if (header.mPath.mClusterId == app::Clusters::CommissionerControl::Id && + header.mPath.mEventId == app::Clusters::CommissionerControl::Events::CommissioningRequestResult::Id) { HandleCommissioningRequestResult(data); } @@ -425,8 +440,8 @@ void DeviceManager::HandleCommandResponse(const app::ConcreteCommandPath & path, { ChipLogProgress(NotSpecified, "Command Response received."); - if (path.mClusterId == CommissionerControl::Id && - path.mCommandId == CommissionerControl::Commands::ReverseOpenCommissioningWindow::Id) + if (path.mClusterId == app::Clusters::CommissionerControl::Id && + path.mCommandId == app::Clusters::CommissionerControl::Commands::ReverseOpenCommissioningWindow::Id) { HandleReverseOpenCommissioningWindow(data); } diff --git a/examples/fabric-admin/device_manager/DeviceManager.h b/examples/fabric-admin/device_manager/DeviceManager.h index 54d3bf97864820..d405bd358d5fdb 100644 --- a/examples/fabric-admin/device_manager/DeviceManager.h +++ b/examples/fabric-admin/device_manager/DeviceManager.h @@ -20,10 +20,10 @@ #include #include +#include #include #include #include - #include constexpr uint32_t kDefaultSetupPinCode = 20202021; @@ -63,7 +63,7 @@ class DeviceManager : public PairingDelegate void UpdateLastUsedNodeId(chip::NodeId nodeId); - void SetRemoteBridgeNodeId(chip::NodeId nodeId) { mRemoteBridgeNodeId = nodeId; } + void SetRemoteBridgeNodeId(chip::NodeId nodeId); void SetLocalBridgePort(uint16_t port) { mLocalBridgePort = port; } void SetLocalBridgeSetupPinCode(uint32_t pinCode) { mLocalBridgeSetupPinCode = pinCode; } @@ -208,6 +208,7 @@ class DeviceManager : public PairingDelegate uint64_t mRequestId = 0; BridgeSubscription mBridgeSubscriber; + CommissionerControl mCommissionerControl; FabricSyncGetter mFabricSyncGetter; };