diff --git a/examples/fabric-admin/BUILD.gn b/examples/fabric-admin/BUILD.gn index ab584459582657..38252cd30e7648 100644 --- a/examples/fabric-admin/BUILD.gn +++ b/examples/fabric-admin/BUILD.gn @@ -80,6 +80,8 @@ static_library("fabric-admin-utils") { "commands/pairing/OpenCommissioningWindowCommand.h", "commands/pairing/PairingCommand.cpp", "commands/pairing/ToTLVCert.cpp", + "device_manager/BridgeSubscription.cpp", + "device_manager/BridgeSubscription.h", "device_manager/DeviceManager.cpp", "device_manager/DeviceManager.h", "device_manager/DeviceSubscription.cpp", diff --git a/examples/fabric-admin/commands/clusters/ReportCommand.cpp b/examples/fabric-admin/commands/clusters/ReportCommand.cpp index 2fdb965ddc844c..df6f329896036c 100644 --- a/examples/fabric-admin/commands/clusters/ReportCommand.cpp +++ b/examples/fabric-admin/commands/clusters/ReportCommand.cpp @@ -72,6 +72,4 @@ void ReportCommand::OnEventData(const app::EventHeader & eventHeader, TLV::TLVRe } LogErrorOnFailure(RemoteDataModelLogger::LogEventAsJSON(eventHeader, data)); - - DeviceMgr().HandleEventData(eventHeader, *data); } diff --git a/examples/fabric-admin/device_manager/BridgeSubscription.cpp b/examples/fabric-admin/device_manager/BridgeSubscription.cpp new file mode 100644 index 00000000000000..2efcadaaa63ee5 --- /dev/null +++ b/examples/fabric-admin/device_manager/BridgeSubscription.cpp @@ -0,0 +1,159 @@ +/* + * 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. + * + */ + +#include "BridgeSubscription.h" +#include + +using namespace ::chip; +using namespace ::chip::app; +using chip::app::ReadClient; + +namespace { + +constexpr uint16_t kSubscribeMinInterval = 0; +constexpr uint16_t kSubscribeMaxInterval = 60; + +void OnDeviceConnectedWrapper(void * context, Messaging::ExchangeManager & exchangeMgr, const SessionHandle & sessionHandle) +{ + reinterpret_cast(context)->OnDeviceConnected(exchangeMgr, sessionHandle); +} + +void OnDeviceConnectionFailureWrapper(void * context, const ScopedNodeId & peerId, CHIP_ERROR error) +{ + reinterpret_cast(context)->OnDeviceConnectionFailure(peerId, error); +} + +} // namespace + +BridgeSubscription::BridgeSubscription() : + mOnDeviceConnectedCallback(OnDeviceConnectedWrapper, this), + mOnDeviceConnectionFailureCallback(OnDeviceConnectionFailureWrapper, this) +{} + +CHIP_ERROR BridgeSubscription::StartSubscription(Controller::DeviceController & controller, NodeId nodeId, EndpointId endpointId) +{ + assertChipStackLockedByCurrentThread(); + + VerifyOrDie(!subscriptionStarted); // Ensure it's not called multiple times. + + // Mark as started + subscriptionStarted = true; + + mEndpointId = endpointId; + + CHIP_ERROR err = controller.GetConnectedDevice(nodeId, &mOnDeviceConnectedCallback, &mOnDeviceConnectionFailureCallback); + if (err != CHIP_NO_ERROR) + { + ChipLogError(NotSpecified, "Failed to connect to remote fabric sync bridge %" CHIP_ERROR_FORMAT, err.Format()); + } + return err; +} + +void BridgeSubscription::OnAttributeData(const ConcreteDataAttributePath & path, TLV::TLVReader * data, const StatusIB & status) +{ + if (!status.IsSuccess()) + { + ChipLogError(NotSpecified, "Response Failure: %" CHIP_ERROR_FORMAT, status.ToChipError().Format()); + return; + } + + if (data == nullptr) + { + ChipLogError(NotSpecified, "Response Failure: No Data"); + return; + } + + DeviceMgr().HandleAttributeData(path, *data); +} + +void BridgeSubscription::OnEventData(const app::EventHeader & eventHeader, TLV::TLVReader * data, const app::StatusIB * status) +{ + if (status != nullptr) + { + CHIP_ERROR error = status->ToChipError(); + if (CHIP_NO_ERROR != error) + { + ChipLogError(NotSpecified, "Response Failure: %" CHIP_ERROR_FORMAT, error.Format()); + return; + } + } + + if (data == nullptr) + { + ChipLogError(NotSpecified, "Response Failure: No Data"); + return; + } + + DeviceMgr().HandleEventData(eventHeader, *data); +} + +void BridgeSubscription::OnError(CHIP_ERROR error) +{ + ChipLogProgress(NotSpecified, "Error on remote fabric sync bridge subscription: %" CHIP_ERROR_FORMAT, error.Format()); +} + +void BridgeSubscription::OnDone(ReadClient * apReadClient) +{ + mClient.reset(); + ChipLogProgress(NotSpecified, "The remote fabric sync bridge subscription is terminated"); + + // Reset the subscription state to allow retry + subscriptionStarted = false; + + // TODO:(#36092) Fabric-Admin should attempt to re-subscribe when the subscription to the remote bridge is terminated. +} + +void BridgeSubscription::OnDeviceConnected(Messaging::ExchangeManager & exchangeMgr, const SessionHandle & sessionHandle) +{ + mClient = std::make_unique(app::InteractionModelEngine::GetInstance(), &exchangeMgr /* echangeMgr */, + *this /* callback */, ReadClient::InteractionType::Subscribe); + VerifyOrDie(mClient); + + AttributePathParams readPaths[1]; + readPaths[0] = AttributePathParams(mEndpointId, Clusters::Descriptor::Id, Clusters::Descriptor::Attributes::PartsList::Id); + + EventPathParams eventPaths[1]; + eventPaths[0] = EventPathParams(mEndpointId, Clusters::CommissionerControl::Id, + Clusters::CommissionerControl::Events::CommissioningRequestResult::Id); + eventPaths[0].mIsUrgentEvent = true; + + ReadPrepareParams readParams(sessionHandle); + + readParams.mpAttributePathParamsList = readPaths; + readParams.mAttributePathParamsListSize = 1; + readParams.mpEventPathParamsList = eventPaths; + readParams.mEventPathParamsListSize = 1; + readParams.mMinIntervalFloorSeconds = kSubscribeMinInterval; + readParams.mMaxIntervalCeilingSeconds = kSubscribeMaxInterval; + readParams.mKeepSubscriptions = true; + + CHIP_ERROR err = mClient->SendRequest(readParams); + + if (err != CHIP_NO_ERROR) + { + ChipLogError(NotSpecified, "Failed to issue subscription to the Descriptor Cluster of the remote bridged device."); + OnDone(nullptr); + return; + } +} + +void BridgeSubscription::OnDeviceConnectionFailure(const ScopedNodeId & peerId, CHIP_ERROR error) +{ + ChipLogError(NotSpecified, "BridgeSubscription failed to connect to " ChipLogFormatX64, ChipLogValueX64(peerId.GetNodeId())); + OnDone(nullptr); +} diff --git a/examples/fabric-admin/device_manager/BridgeSubscription.h b/examples/fabric-admin/device_manager/BridgeSubscription.h new file mode 100644 index 00000000000000..bd2a70279af065 --- /dev/null +++ b/examples/fabric-admin/device_manager/BridgeSubscription.h @@ -0,0 +1,77 @@ +/* + * 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 + +#include +#include + +/** + * @brief Class used to subscribe to attributes and events from the remote bridged device. + * + * The Descriptor Cluster contains attributes such as the Parts List, which provides a list + * of endpoints or devices that are part of a composite device or bridge. The CommissionerControl + * Cluster generates events related to commissioning requests, which can be monitored to track + * device commissioning status. + * + * When subscribing to attributes and events of a bridged device from another fabric, the class: + * - Establishes a secure session with the device (if needed) via CASE (Chip over + * Authenticated Session Establishment) session. + * - Subscribes to the specified attributes in the Descriptor Cluster (e.g., Parts List) and + * events in the CommissionerControl Cluster (e.g., CommissioningRequestResult) of the remote + * device on the specified node and endpoint. + * - Invokes the provided callback upon successful or unsuccessful subscription, allowing + * further handling of data or errors. + * + * This class also implements the necessary callbacks to handle attribute data reports, event data, + * errors, and session establishment procedures. + */ +class BridgeSubscription : public chip::app::ReadClient::Callback +{ +public: + BridgeSubscription(); + + CHIP_ERROR StartSubscription(chip::Controller::DeviceController & controller, chip::NodeId nodeId, chip::EndpointId endpointId); + + /////////////////////////////////////////////////////////////// + // ReadClient::Callback implementation + /////////////////////////////////////////////////////////////// + void OnAttributeData(const chip::app::ConcreteDataAttributePath & path, chip::TLV::TLVReader * data, + const chip::app::StatusIB & status) override; + void OnEventData(const chip::app::EventHeader & eventHeader, chip::TLV::TLVReader * data, + const chip::app::StatusIB * status) override; + void OnError(CHIP_ERROR error) override; + void OnDone(chip::app::ReadClient * apReadClient) override; + + /////////////////////////////////////////////////////////////// + // callbacks for CASE session establishment + /////////////////////////////////////////////////////////////// + void OnDeviceConnected(chip::Messaging::ExchangeManager & exchangeMgr, const chip::SessionHandle & sessionHandle); + void OnDeviceConnectionFailure(const chip::ScopedNodeId & peerId, CHIP_ERROR error); + +private: + std::unique_ptr mClient; + + chip::Callback::Callback mOnDeviceConnectedCallback; + chip::Callback::Callback mOnDeviceConnectionFailureCallback; + chip::EndpointId mEndpointId; + bool subscriptionStarted = false; +}; diff --git a/examples/fabric-admin/device_manager/DeviceManager.cpp b/examples/fabric-admin/device_manager/DeviceManager.cpp index 3a27c68184c8b0..2968bcf001e9bc 100644 --- a/examples/fabric-admin/device_manager/DeviceManager.cpp +++ b/examples/fabric-admin/device_manager/DeviceManager.cpp @@ -33,9 +33,6 @@ namespace { constexpr EndpointId kAggregatorEndpointId = 1; constexpr uint16_t kWindowTimeout = 300; constexpr uint16_t kIteration = 1000; -constexpr uint16_t kSubscribeMinInterval = 0; -constexpr uint16_t kSubscribeMaxInterval = 60; -constexpr uint16_t kAggragatorEndpointId = 1; constexpr uint16_t kMaxDiscriminatorLength = 4095; } // namespace @@ -193,23 +190,17 @@ void DeviceManager::UnpairLocalFabricBridge() void DeviceManager::SubscribeRemoteFabricBridge() { - // Listen to the state changes of the remote fabric bridge. - StringBuilder commandBuilder; - - // Prepare and push the descriptor subscribe command - commandBuilder.Add("descriptor subscribe parts-list "); - commandBuilder.AddFormat("%d %d %lu %d", kSubscribeMinInterval, kSubscribeMaxInterval, mRemoteBridgeNodeId, - kAggragatorEndpointId); - PushCommand(commandBuilder.c_str()); + ChipLogProgress(NotSpecified, "Start subscription to the remote bridge.") - // Clear the builder for the next command - commandBuilder.Reset(); + CHIP_ERROR error = mBridgeSubscriber.StartSubscription(PairingManager::Instance().CurrentCommissioner(), + mRemoteBridgeNodeId, kAggregatorEndpointId); - // Prepare and push the commissioner control subscribe command - commandBuilder.Add("commissionercontrol subscribe-event commissioning-request-result "); - commandBuilder.AddFormat("%d %d %lu %d --is-urgent true --keepSubscriptions true", kSubscribeMinInterval, kSubscribeMaxInterval, - mRemoteBridgeNodeId, kAggregatorEndpointId); - PushCommand(commandBuilder.c_str()); + if (error != CHIP_NO_ERROR) + { + ChipLogError(NotSpecified, "Failed to subscribe to the remote bridge (NodeId: %lu). Error: %" CHIP_ERROR_FORMAT, + mRemoteBridgeNodeId, error.Format()); + return; + } } void DeviceManager::ReadSupportedDeviceCategories() diff --git a/examples/fabric-admin/device_manager/DeviceManager.h b/examples/fabric-admin/device_manager/DeviceManager.h index 1514c417be4b4d..62d5ae045bb6af 100644 --- a/examples/fabric-admin/device_manager/DeviceManager.h +++ b/examples/fabric-admin/device_manager/DeviceManager.h @@ -19,6 +19,7 @@ #pragma once #include +#include #include #include @@ -209,6 +210,8 @@ class DeviceManager : public PairingDelegate bool mAutoSyncEnabled = false; bool mInitialized = false; uint64_t mRequestId = 0; + + BridgeSubscription mBridgeSubscriber; }; /**