[Fabric-Admin] Refactor to use API methods instead of PushCommand (3/3) (#36033)
* [Fabric-Admin] Implement the bridge subscription API
* Restyle
* Address review comments
* Reset the subscription state only in OnDone
* Keep subscription
diff --git a/examples/fabric-admin/BUILD.gn b/examples/fabric-admin/BUILD.gn
index ab58445..38252cd 100644
--- a/examples/fabric-admin/BUILD.gn
+++ b/examples/fabric-admin/BUILD.gn
@@ -80,6 +80,8 @@
"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 2fdb965..df6f329 100644
--- a/examples/fabric-admin/commands/clusters/ReportCommand.cpp
+++ b/examples/fabric-admin/commands/clusters/ReportCommand.cpp
@@ -72,6 +72,4 @@
}
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 0000000..2efcada
--- /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 <device_manager/DeviceManager.h>
+
+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<BridgeSubscription *>(context)->OnDeviceConnected(exchangeMgr, sessionHandle);
+}
+
+void OnDeviceConnectionFailureWrapper(void * context, const ScopedNodeId & peerId, CHIP_ERROR error)
+{
+ reinterpret_cast<BridgeSubscription *>(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<ReadClient>(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 0000000..bd2a702
--- /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 <app/ReadClient.h>
+#include <controller/CHIPDeviceController.h>
+
+#include <memory>
+#include <optional>
+
+/**
+ * @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<chip::app::ReadClient> mClient;
+
+ chip::Callback::Callback<chip::OnDeviceConnected> mOnDeviceConnectedCallback;
+ chip::Callback::Callback<chip::OnDeviceConnectionFailure> 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 3a27c68..2968bcf 100644
--- a/examples/fabric-admin/device_manager/DeviceManager.cpp
+++ b/examples/fabric-admin/device_manager/DeviceManager.cpp
@@ -33,9 +33,6 @@
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::SubscribeRemoteFabricBridge()
{
- // Listen to the state changes of the remote fabric bridge.
- StringBuilder<kMaxCommandSize> commandBuilder;
+ ChipLogProgress(NotSpecified, "Start subscription to the remote bridge.")
- // 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());
+ CHIP_ERROR error = mBridgeSubscriber.StartSubscription(PairingManager::Instance().CurrentCommissioner(),
+ mRemoteBridgeNodeId, kAggregatorEndpointId);
- // Clear the builder for the next command
- commandBuilder.Reset();
-
- // 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 1514c41..62d5ae0 100644
--- a/examples/fabric-admin/device_manager/DeviceManager.h
+++ b/examples/fabric-admin/device_manager/DeviceManager.h
@@ -19,6 +19,7 @@
#pragma once
#include <app-common/zap-generated/cluster-objects.h>
+#include <device_manager/BridgeSubscription.h>
#include <device_manager/PairingManager.h>
#include <platform/CHIPDeviceLayer.h>
@@ -209,6 +210,8 @@
bool mAutoSyncEnabled = false;
bool mInitialized = false;
uint64_t mRequestId = 0;
+
+ BridgeSubscription mBridgeSubscriber;
};
/**