diff --git a/examples/common/pigweed/protos/fabric_admin_service.proto b/examples/common/pigweed/protos/fabric_admin_service.proto index 7f6ec4f4995b12..b2c81d5ee0eaae 100644 --- a/examples/common/pigweed/protos/fabric_admin_service.proto +++ b/examples/common/pigweed/protos/fabric_admin_service.proto @@ -14,6 +14,11 @@ message DeviceCommissioningWindowInfo { bytes verifier = 6; } +message KeepActiveParameters { + uint64 node_id = 1; + uint32 stay_active_duration_ms = 2; +} + // Define the response message to convey the status of the operation message OperationStatus { bool success = 1; @@ -21,4 +26,5 @@ message OperationStatus { service FabricAdmin { rpc OpenCommissioningWindow(DeviceCommissioningWindowInfo) returns (OperationStatus){} + rpc KeepActive(KeepActiveParameters) returns (pw.protobuf.Empty){} } diff --git a/examples/common/pigweed/protos/fabric_bridge_service.proto b/examples/common/pigweed/protos/fabric_bridge_service.proto index e0370dd278c7a2..0de3b3fa243d77 100644 --- a/examples/common/pigweed/protos/fabric_bridge_service.proto +++ b/examples/common/pigweed/protos/fabric_bridge_service.proto @@ -21,8 +21,13 @@ message SynchronizedDevice { optional bool is_icd = 12; } +message KeepActiveChanged { + uint64 node_id = 1; + uint32 promised_active_duration_ms = 2; +} + service FabricBridge { rpc AddSynchronizedDevice(SynchronizedDevice) returns (pw.protobuf.Empty){} - rpc RemoveSynchronizedDevice(SynchronizedDevice) returns (pw.protobuf.Empty){} + rpc RemoveSynchronizedDevice(SynchronizedDevice) returns (pw.protobuf.Empty){} + rpc ActiveChanged(KeepActiveChanged) returns (pw.protobuf.Empty){} } - diff --git a/examples/common/pigweed/rpc_services/FabricAdmin.h b/examples/common/pigweed/rpc_services/FabricAdmin.h index 14de9d50f60673..125d322e3ab8bf 100644 --- a/examples/common/pigweed/rpc_services/FabricAdmin.h +++ b/examples/common/pigweed/rpc_services/FabricAdmin.h @@ -39,6 +39,11 @@ class FabricAdmin : public pw_rpc::nanopb::FabricAdmin::Service { return pw::Status::Unimplemented(); } + + virtual pw::Status KeepActive(const chip_rpc_KeepActiveParameters & request, pw_protobuf_Empty & response) + { + return pw::Status::Unimplemented(); + } }; } // namespace rpc diff --git a/examples/common/pigweed/rpc_services/FabricBridge.h b/examples/common/pigweed/rpc_services/FabricBridge.h index 4b9c4d93f1eb51..aab714223968df 100644 --- a/examples/common/pigweed/rpc_services/FabricBridge.h +++ b/examples/common/pigweed/rpc_services/FabricBridge.h @@ -43,6 +43,11 @@ class FabricBridge : public pw_rpc::nanopb::FabricBridge::Service { return pw::Status::Unimplemented(); } + + virtual pw::Status ActiveChanged(const chip_rpc_KeepActiveChanged & request, pw_protobuf_Empty & response) + { + return pw::Status::Unimplemented(); + } }; } // namespace rpc diff --git a/examples/fabric-admin/rpc/RpcClient.cpp b/examples/fabric-admin/rpc/RpcClient.cpp index f7892abd61172a..f5672fc0bd8f85 100644 --- a/examples/fabric-admin/rpc/RpcClient.cpp +++ b/examples/fabric-admin/rpc/RpcClient.cpp @@ -98,6 +98,23 @@ void OnRemoveDeviceResponseCompleted(const pw_protobuf_Empty & response, pw::Sta } } +void RpcCompletedWithEmptyResponse(const pw_protobuf_Empty & response, pw::Status status) +{ + std::lock_guard lock(responseMutex); + responseReceived = true; + responseError = status.ok() ? CHIP_NO_ERROR : CHIP_ERROR_INTERNAL; + responseCv.notify_one(); + + if (status.ok()) + { + ChipLogProgress(NotSpecified, "RPC call succeeded!"); + } + else + { + ChipLogProgress(NotSpecified, "RPC call failed with status: %d", status.code()); + } +} + } // namespace CHIP_ERROR InitRpcClient(uint16_t rpcServerPort) @@ -142,3 +159,24 @@ CHIP_ERROR RemoveSynchronizedDevice(chip::NodeId nodeId) return WaitForResponse(call); } + +CHIP_ERROR ActiveChanged(chip::NodeId nodeId, uint32_t promisedActiveDurationMs) +{ + ChipLogProgress(NotSpecified, "ActiveChanged"); + + chip_rpc_KeepActiveChanged parameters; + parameters.node_id = nodeId; + parameters.promised_active_duration_ms = promisedActiveDurationMs; + + // The RPC call is kept alive until it completes. When a response is received, it will be logged by the handler + // function and the call will complete. + auto call = fabricBridgeClient.ActiveChanged(parameters, RpcCompletedWithEmptyResponse); + + if (!call.active()) + { + // The RPC call was not sent. This could occur due to, for example, an invalid channel ID. Handle if necessary. + return CHIP_ERROR_INTERNAL; + } + + return WaitForResponse(call); +} diff --git a/examples/fabric-admin/rpc/RpcClient.h b/examples/fabric-admin/rpc/RpcClient.h index a5bd833ad5d5b3..8edee9a3b256ed 100644 --- a/examples/fabric-admin/rpc/RpcClient.h +++ b/examples/fabric-admin/rpc/RpcClient.h @@ -62,3 +62,15 @@ CHIP_ERROR AddSynchronizedDevice(const chip_rpc_SynchronizedDevice & data); * - CHIP_ERROR_INTERNAL: An internal error occurred while activating the RPC call. */ CHIP_ERROR RemoveSynchronizedDevice(chip::NodeId nodeId); + +/** + * @brief Received StayActiveResponse on behalf of client that previously called KeepActive + * + * @param nodeId The Node ID of the device we recieved a StayActiveResponse. + * @param promisedActiveDurationMs the computed duration (in milliseconds) that the ICD intends to stay active for. + * @return CHIP_ERROR An error code indicating the success or failure of the operation. + * - CHIP_NO_ERROR: The RPC command was successfully processed. + * - CHIP_ERROR_BUSY: Another operation is currently in progress. + * - CHIP_ERROR_INTERNAL: An internal error occurred while activating the RPC call. + */ +CHIP_ERROR ActiveChanged(chip::NodeId nodeId, uint32_t promisedActiveDurationMs); diff --git a/examples/fabric-admin/rpc/RpcServer.cpp b/examples/fabric-admin/rpc/RpcServer.cpp index d4979e5a27c427..cb06b359d68a71 100644 --- a/examples/fabric-admin/rpc/RpcServer.cpp +++ b/examples/fabric-admin/rpc/RpcServer.cpp @@ -65,6 +65,18 @@ class FabricAdmin final : public rpc::FabricAdmin return pw::OkStatus(); } + + pw::Status KeepActive(const chip_rpc_KeepActiveParameters & request, pw_protobuf_Empty & response) override + { + ChipLogProgress(NotSpecified, "Received KeepActive request: 0x%lx, %u", request.node_id, request.stay_active_duration_ms); + // TODO(#33221): When we get this command hopefully we are already registered with an ICD device to be + // notified when it wakes up. We will need to add in hooks there to make sure we send the StayActiveRequest + // Important thing to note: + // * If we get this call multiple times before we get a wakeup from ICD, we only send out one StayActiveRequest command + // * After 60 mins from last exipry we no longer will send out a StayActiveRequest. + + return pw::OkStatus(); + } }; FabricAdmin fabric_admin_service; diff --git a/examples/fabric-bridge-app/fabric-bridge-common/include/BridgedDevice.h b/examples/fabric-bridge-app/fabric-bridge-common/include/BridgedDevice.h index 5c09cfa04d2521..a65ddb15a736f5 100644 --- a/examples/fabric-bridge-app/fabric-bridge-common/include/BridgedDevice.h +++ b/examples/fabric-bridge-app/fabric-bridge-common/include/BridgedDevice.h @@ -43,7 +43,10 @@ class BridgedDevice BridgedDevice(chip::NodeId nodeId); virtual ~BridgedDevice() = default; + void LogActiveChangeEvent(uint32_t promisedActiveDurationMs); + bool IsReachable(); + bool IsIcd(); void SetReachable(bool reachable); inline void SetEndpointId(chip::EndpointId id) { mEndpointId = id; }; @@ -61,6 +64,7 @@ class BridgedDevice protected: bool mReachable; + bool mIsIcd = false; chip::NodeId mNodeId; chip::EndpointId mEndpointId; chip::EndpointId mParentEndpointId; diff --git a/examples/fabric-bridge-app/fabric-bridge-common/src/BridgedDevice.cpp b/examples/fabric-bridge-app/fabric-bridge-common/src/BridgedDevice.cpp index 425bb4da32e896..a5e041b487d962 100644 --- a/examples/fabric-bridge-app/fabric-bridge-common/src/BridgedDevice.cpp +++ b/examples/fabric-bridge-app/fabric-bridge-common/src/BridgedDevice.cpp @@ -19,9 +19,36 @@ #include "BridgedDevice.h" #include +#include + +#include #include -#include +namespace { + +struct ActiveChangeEventWorkData +{ + chip::EndpointId mEndpointId; + uint32_t mPromisedActiveDuration; +}; + +static void ActiveChangeEventWork(intptr_t arg) +{ + ActiveChangeEventWorkData * data = reinterpret_cast(arg); + + chip::app::Clusters::BridgedDeviceBasicInformation::Events::ActiveChanged::Type event{}; + event.promisedActiveDuration = data->mPromisedActiveDuration; + chip::EventNumber eventNumber = 0; + + CHIP_ERROR err = chip::app::LogEvent(event, data->mEndpointId, eventNumber); + if (err != CHIP_NO_ERROR) + { + ChipLogProgress(NotSpecified, "LogEvent for ActiveChanged failed %s", err.AsString()); + } + chip::Platform::Delete(data); +} + +} // namespace using namespace chip::app::Clusters::Actions; @@ -32,11 +59,25 @@ BridgedDevice::BridgedDevice(chip::NodeId nodeId) mEndpointId = chip::kInvalidEndpointId; } +void BridgedDevice::LogActiveChangeEvent(uint32_t promisedActiveDurationMs) +{ + ActiveChangeEventWorkData * workdata = chip::Platform::New(); + workdata->mEndpointId = mEndpointId; + workdata->mPromisedActiveDuration = promisedActiveDurationMs; + + chip::DeviceLayer::PlatformMgr().ScheduleWork(ActiveChangeEventWork, reinterpret_cast(workdata)); +} + bool BridgedDevice::IsReachable() { return mReachable; } +bool BridgedDevice::IsIcd() +{ + return mIsIcd; +} + void BridgedDevice::SetReachable(bool reachable) { mReachable = reachable; diff --git a/examples/fabric-bridge-app/fabric-bridge-common/src/BridgedDeviceManager.cpp b/examples/fabric-bridge-app/fabric-bridge-common/src/BridgedDeviceManager.cpp index 33caf9e16fe909..b361e6667278a4 100644 --- a/examples/fabric-bridge-app/fabric-bridge-common/src/BridgedDeviceManager.cpp +++ b/examples/fabric-bridge-app/fabric-bridge-common/src/BridgedDeviceManager.cpp @@ -129,6 +129,11 @@ DECLARE_DYNAMIC_ATTRIBUTE_LIST_BEGIN(AdministratorCommissioningAttrs) DECLARE_DYNAMIC_ATTRIBUTE_LIST_END(); // clang-format on +constexpr CommandId bridgedDeviceBasicInformationCommands[] = { + app::Clusters::BridgedDeviceBasicInformation::Commands::KeepActive::Id, + kInvalidCommandId, +}; + constexpr CommandId administratorCommissioningCommands[] = { app::Clusters::AdministratorCommissioning::Commands::OpenCommissioningWindow::Id, app::Clusters::AdministratorCommissioning::Commands::OpenBasicCommissioningWindow::Id, @@ -139,7 +144,8 @@ constexpr CommandId administratorCommissioningCommands[] = { // Declare Cluster List for Bridged Node endpoint DECLARE_DYNAMIC_CLUSTER_LIST_BEGIN(bridgedNodeClusters) DECLARE_DYNAMIC_CLUSTER(Descriptor::Id, descriptorAttrs, ZAP_CLUSTER_MASK(SERVER), nullptr, nullptr), - DECLARE_DYNAMIC_CLUSTER(BridgedDeviceBasicInformation::Id, bridgedDeviceBasicAttrs, ZAP_CLUSTER_MASK(SERVER), nullptr, nullptr), + DECLARE_DYNAMIC_CLUSTER(BridgedDeviceBasicInformation::Id, bridgedDeviceBasicAttrs, ZAP_CLUSTER_MASK(SERVER), + bridgedDeviceBasicInformationCommands, nullptr), DECLARE_DYNAMIC_CLUSTER(EcosystemInformation::Id, ecosystemInformationBasicAttrs, ZAP_CLUSTER_MASK(SERVER), nullptr, nullptr), DECLARE_DYNAMIC_CLUSTER(AdministratorCommissioning::Id, AdministratorCommissioningAttrs, ZAP_CLUSTER_MASK(SERVER), administratorCommissioningCommands, nullptr) DECLARE_DYNAMIC_CLUSTER_LIST_END; diff --git a/examples/fabric-bridge-app/linux/RpcClient.cpp b/examples/fabric-bridge-app/linux/RpcClient.cpp index d2aef5d1d82e5e..1260c8744b67f2 100644 --- a/examples/fabric-bridge-app/linux/RpcClient.cpp +++ b/examples/fabric-bridge-app/linux/RpcClient.cpp @@ -87,6 +87,24 @@ void OnOpenCommissioningWindowCompleted(const chip_rpc_OperationStatus & respons } } +// Callback function to be called when the RPC response is received for generic empty response. +void RpcCompletedWithEmptyResponse(const pw_protobuf_Empty & response, pw::Status status) +{ + std::lock_guard lock(responseMutex); + responseReceived = true; + responseError = status.ok() ? CHIP_NO_ERROR : CHIP_ERROR_INTERNAL; + responseCv.notify_one(); + + if (status.ok()) + { + ChipLogProgress(NotSpecified, "RPC call succeeded!"); + } + else + { + ChipLogProgress(NotSpecified, "RPC call failed with status: %d", status.code()); + } +} + } // namespace CHIP_ERROR InitRpcClient(uint16_t rpcServerPort) @@ -143,3 +161,22 @@ OpenCommissioningWindow(chip::Controller::CommissioningWindowVerifierParams para return OpenCommissioningWindow(device); } + +CHIP_ERROR KeepActive(chip::NodeId nodeId, uint32_t stayActiveDurationMs) +{ + chip_rpc_KeepActiveParameters params; + params.node_id = nodeId; + params.stay_active_duration_ms = stayActiveDurationMs; + + // The RPC call is kept alive until it completes. When a response is received, it will be logged by the handler + // function and the call will complete. + auto call = fabricAdminClient.KeepActive(params, RpcCompletedWithEmptyResponse); + + if (!call.active()) + { + // The RPC call was not sent. This could occur due to, for example, an invalid channel ID. Handle if necessary. + return CHIP_ERROR_INTERNAL; + } + + return WaitForResponse(call); +} diff --git a/examples/fabric-bridge-app/linux/RpcServer.cpp b/examples/fabric-bridge-app/linux/RpcServer.cpp index 65bb365b8de19d..8838360ee7b1e7 100644 --- a/examples/fabric-bridge-app/linux/RpcServer.cpp +++ b/examples/fabric-bridge-app/linux/RpcServer.cpp @@ -45,6 +45,7 @@ class FabricBridge final : public chip::rpc::FabricBridge public: pw::Status AddSynchronizedDevice(const chip_rpc_SynchronizedDevice & request, pw_protobuf_Empty & response) override; pw::Status RemoveSynchronizedDevice(const chip_rpc_SynchronizedDevice & request, pw_protobuf_Empty & response) override; + pw::Status ActiveChanged(const chip_rpc_KeepActiveChanged & request, pw_protobuf_Empty & response) override; }; pw::Status FabricBridge::AddSynchronizedDevice(const chip_rpc_SynchronizedDevice & request, pw_protobuf_Empty & response) @@ -138,6 +139,23 @@ pw::Status FabricBridge::RemoveSynchronizedDevice(const chip_rpc_SynchronizedDev return pw::OkStatus(); } +pw::Status FabricBridge::ActiveChanged(const chip_rpc_KeepActiveChanged & request, pw_protobuf_Empty & response) +{ + NodeId nodeId = request.node_id; + ChipLogProgress(NotSpecified, "Received ActiveChanged: " ChipLogFormatX64, ChipLogValueX64(nodeId)); + + auto * device = BridgeDeviceMgr().GetDeviceByNodeId(nodeId); + if (device == nullptr) + { + ChipLogError(NotSpecified, "Could not find bridged device associated with nodeId=0x" ChipLogFormatX64, + ChipLogValueX64(nodeId)); + return pw::Status::NotFound(); + } + + device->LogActiveChangeEvent(request.promised_active_duration_ms); + return pw::OkStatus(); +} + FabricBridge fabric_bridge_service; #endif // defined(PW_RPC_FABRIC_BRIDGE_SERVICE) && PW_RPC_FABRIC_BRIDGE_SERVICE diff --git a/examples/fabric-bridge-app/linux/include/RpcClient.h b/examples/fabric-bridge-app/linux/include/RpcClient.h index e7e2cc5b48505c..6913a66d8c24a1 100644 --- a/examples/fabric-bridge-app/linux/include/RpcClient.h +++ b/examples/fabric-bridge-app/linux/include/RpcClient.h @@ -56,3 +56,5 @@ OpenCommissioningWindow(chip::Controller::CommissioningWindowPasscodeParams para */ CHIP_ERROR OpenCommissioningWindow(chip::Controller::CommissioningWindowVerifierParams params); + +CHIP_ERROR KeepActive(chip::NodeId nodeId, uint32_t stayActiveDurationMs); diff --git a/examples/fabric-bridge-app/linux/main.cpp b/examples/fabric-bridge-app/linux/main.cpp index 46e1e35005ff70..1678ec59a81a1d 100644 --- a/examples/fabric-bridge-app/linux/main.cpp +++ b/examples/fabric-bridge-app/linux/main.cpp @@ -47,6 +47,7 @@ using namespace chip; using namespace chip::app; using namespace chip::app::Clusters; using namespace chip::app::Clusters::AdministratorCommissioning; +using namespace chip::app::Clusters::BridgedDeviceBasicInformation; namespace { @@ -132,7 +133,8 @@ void AdministratorCommissioningCommandHandler::InvokeCommand(HandlerContext & ha EndpointId endpointId = handlerContext.mRequestPath.mEndpointId; ChipLogProgress(NotSpecified, "Received command to open commissioning window on Endpoint: %d", endpointId); - if (handlerContext.mRequestPath.mCommandId != Commands::OpenCommissioningWindow::Id || endpointId == kRootEndpointId) + if (handlerContext.mRequestPath.mCommandId != AdministratorCommissioning::Commands::OpenCommissioningWindow::Id || + endpointId == kRootEndpointId) { // Proceed with default handling in Administrator Commissioning Server return; @@ -140,7 +142,7 @@ void AdministratorCommissioningCommandHandler::InvokeCommand(HandlerContext & ha handlerContext.SetCommandHandled(); - Commands::OpenCommissioningWindow::DecodableType commandData; + AdministratorCommissioning::Commands::OpenCommissioningWindow::DecodableType commandData; if (DataModel::Decode(handlerContext.mPayload, commandData) != CHIP_NO_ERROR) { handlerContext.mCommandHandler.AddStatus(handlerContext.mRequestPath, Status::InvalidCommand); @@ -176,7 +178,63 @@ void AdministratorCommissioningCommandHandler::InvokeCommand(HandlerContext & ha handlerContext.mCommandHandler.AddStatus(handlerContext.mRequestPath, status); } +class BridgedDeviceInformationCommandHandler : public CommandHandlerInterface +{ +public: + // Register for the BridgedDeviceBasicInformation cluster on all endpoints. + BridgedDeviceInformationCommandHandler() : + CommandHandlerInterface(Optional::Missing(), BridgedDeviceBasicInformation::Id) + {} + + void InvokeCommand(HandlerContext & handlerContext) override; +}; + +void BridgedDeviceInformationCommandHandler::InvokeCommand(HandlerContext & handlerContext) +{ + using Protocols::InteractionModel::Status; + VerifyOrReturn(handlerContext.mRequestPath.mCommandId == BridgedDeviceBasicInformation::Commands::KeepActive::Id); + + EndpointId endpointId = handlerContext.mRequestPath.mEndpointId; + ChipLogProgress(NotSpecified, "Received command to KeepActive on Endpoint: %d", endpointId); + + BridgedDevice * device = BridgeDeviceMgr().GetDevice(endpointId); + + handlerContext.SetCommandHandled(); + + if (device == nullptr || !device->IsIcd()) + { + handlerContext.mCommandHandler.AddStatus(handlerContext.mRequestPath, Status::Failure); + return; + } + + BridgedDeviceBasicInformation::Commands::KeepActive::DecodableType commandData; + if (DataModel::Decode(handlerContext.mPayload, commandData) != CHIP_NO_ERROR) + { + handlerContext.mCommandHandler.AddStatus(handlerContext.mRequestPath, Status::InvalidCommand); + return; + } + + Status status = Status::Failure; + +#if defined(PW_RPC_FABRIC_BRIDGE_SERVICE) && PW_RPC_FABRIC_BRIDGE_SERVICE + if (KeepActive(device->GetNodeId(), commandData.stayActiveDuration) == CHIP_NO_ERROR) + { + ChipLogProgress(NotSpecified, "KeepActive successfully processed"); + status = Status::Success; + } + else + { + ChipLogProgress(NotSpecified, "KeepActive failed to process"); + } +#else + ChipLogProgress(NotSpecified, "Unable to properly call KeepActive: PW_RPC_FABRIC_BRIDGE_SERVICE not defined"); +#endif // defined(PW_RPC_FABRIC_BRIDGE_SERVICE) && PW_RPC_FABRIC_BRIDGE_SERVICE + + handlerContext.mCommandHandler.AddStatus(handlerContext.mRequestPath, status); +} + AdministratorCommissioningCommandHandler gAdministratorCommissioningCommandHandler; +BridgedDeviceInformationCommandHandler gBridgedDeviceInformationCommandHandler; } // namespace @@ -186,6 +244,7 @@ void ApplicationInit() MatterEcosystemInformationPluginServerInitCallback(); CommandHandlerInterfaceRegistry::Instance().RegisterCommandHandler(&gAdministratorCommissioningCommandHandler); + CommandHandlerInterfaceRegistry::Instance().RegisterCommandHandler(&gBridgedDeviceInformationCommandHandler); AttributeAccessInterfaceRegistry::Instance().Register(&gBridgedDeviceBasicInformationAttributes); #if defined(PW_RPC_FABRIC_BRIDGE_SERVICE) && PW_RPC_FABRIC_BRIDGE_SERVICE